搜索 K
Appearance
Appearance
本篇带你把 forge-admin-web 跑起来,并理解「打开浏览器到看见第一个页面」之间前端做了什么。
前端工程在仓库的 forge-admin-web/ 目录,使用 pnpm:
cd forge-admin-web
pnpm install
pnpm dev启动后访问 http://localhost:5371。
端口与代理
dev server 固定在 5371 端口。vite.config.ts 把 /v1 代理到后端 http://localhost:18080(changeOrigin: true 规避跨域):
// vite.config.ts
server: {
port: 5371,
host: true,
proxy: {
'/v1': { target: 'http://localhost:18080', changeOrigin: true },
},
}所以前端所有请求都以 /v1 开头(axios 的 baseURL 即 /v1),最终落到后端的 18080。后端如何起,见应用架构 · 快速开始。
前端是「schema 解释器」,没有后端下发的 schema,前端就是一张空壳。所以跑通的前提是后端已经:
@Model(例如商品 biz.product);biz:productList)。这两步全部在后端完成,前端零参与。具体怎么声明,见后端文档数据建模 · 快速开始。本前端文档统一用商品 biz.product 作为贯穿示例。
第一次打开控制台,前端按下面的顺序把页面「拼」出来:
几个关键点:
router.beforeEach 检查 auth.isLogged(),未登录跳 /login 并带上 redirect 查询参数,登录后原路返回。/ 不展示内容,而是 HomeRedirect 加载模块列表、逐个找到第一个有 model 的叶子菜单,router.replace 过去。所以你看到的「第一个页面」其实是第一个可用列表页。PageSchema(页面长什么样),再要 queryPage(页面里填什么数据)。这两者职责分离——一个是元数据、一个是业务数据。登录页 LoginView 的提交逻辑很薄:
async function onSubmit() {
await auth.login(form.username, form.password) // POST /v1/api/auth/login
toast.success('登录成功')
const redirect = (route.query.redirect as string) || '/'
router.replace(redirect) // 回到来时的页面
}auth.login 把后端返回的 token 写入 localStorage,之后每个请求由 axios 拦截器自动带上 Authorization: Bearer <token>。鉴权失败(401 / 业务码 40100/40101)时,拦截器会清 token 并跳回登录页。细节见设计文档 · 网络层与状态。
如果暂时没有后端,可以临时把三个元数据接口换成本地 mock,让渲染引擎跑起来。核心是构造一份 PageSchema:
// 一份最小可渲染的 PageSchema(商品列表)
const mockSchema: PageSchema = {
menuCode: 'biz:productList',
model: 'biz.product',
modelPath: 'biz/product',
viewType: 'TABLE',
title: '商品',
fields: [
{ field: 'id', label: 'ID', component: 'text', dataType: 'LONG',
operator: 'eq', required: false, readonly: true, list: true, form: false,
detail: true, search: false, sortable: true, primaryKey: true },
{ field: 'name', label: '名称', component: 'text', dataType: 'STRING',
operator: 'like', required: true, readonly: false, list: true, form: true,
detail: true, search: true, sortable: false, primaryKey: false },
{ field: 'status', label: '状态', component: 'select', dataType: 'ENUM',
operator: 'eq', required: true, readonly: false, list: true, form: true,
detail: true, search: true, sortable: false, primaryKey: false,
options: [
{ value: 1, label: '在售' },
{ value: 2, label: '已售' },
{ value: 3, label: '下架' },
] },
],
}把 api/meta.ts 的 fetchPageSchema 临时返回这份对象、api/data.ts 的 queryPage 临时返回 { records: [...], total: n },就能在没有后端的情况下验证表格/表单/单元格的渲染。各字段含义见设计文档 · 协议消费映射。
| 现象 | 原因 | 处理 |
|---|---|---|
| 打开就跳登录、登录后又跳回登录 | 后端 401,或代理没通到 18080 | 确认后端已起、/v1 代理目标正确 |
| 首页空白不跳转 | 没有任何「有 model 的叶子菜单」 | 后端先把模型绑到菜单(见后端文档) |
| 列表能出但点详情报错 | 主键字段 primaryKey 没下发 | 后端确保字段 schema 有 primaryKey: true,前端取主键即靠它 |
| ID 在前端变成科学计数法/精度丢失 | 没走 json-bigint | 框架已用 json-bigint 把长整型 ID 转字符串,切勿对 ID 做 Number() |