Skip to content

快速开始

本篇带你把 forge-admin-web 跑起来,并理解「打开浏览器到看见第一个页面」之间前端做了什么。

1. 安装与启动

前端工程在仓库的 forge-admin-web/ 目录,使用 pnpm:

bash
cd forge-admin-web
pnpm install
pnpm dev

启动后访问 http://localhost:5371

端口与代理

dev server 固定在 5371 端口。vite.config.ts/v1 代理到后端 http://localhost:18080changeOrigin: true 规避跨域):

ts
// vite.config.ts
server: {
  port: 5371,
  host: true,
  proxy: {
    '/v1': { target: 'http://localhost:18080', changeOrigin: true },
  },
}

所以前端所有请求都以 /v1 开头(axios 的 baseURL/v1),最终落到后端的 18080。后端如何起,见应用架构 · 快速开始

2. 前置:先有一个后端模型与菜单

前端是「schema 解释器」,没有后端下发的 schema,前端就是一张空壳。所以跑通的前提是后端已经:

  1. 声明了一个 @Model(例如商品 biz.product);
  2. 把它绑到了一个菜单(例如 biz:productList)。

这两步全部在后端完成,前端零参与。具体怎么声明,见后端文档数据建模 · 快速开始。本前端文档统一用商品 biz.product 作为贯穿示例。

3. 从打开页面到看见列表:发生了什么

第一次打开控制台,前端按下面的顺序把页面「拼」出来:

几个关键点:

  • 登录守卫router.beforeEach 检查 auth.isLogged(),未登录跳 /login 并带上 redirect 查询参数,登录后原路返回。
  • 首页自动重定向/ 不展示内容,而是 HomeRedirect 加载模块列表、逐个找到第一个model 的叶子菜单router.replace 过去。所以你看到的「第一个页面」其实是第一个可用列表页。
  • 两次数据请求:进入列表页后,前端先要 PageSchema(页面长什么样),再要 queryPage(页面里填什么数据)。这两者职责分离——一个是元数据、一个是业务数据。

4. 登录流程

登录页 LoginView 的提交逻辑很薄:

ts
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 并跳回登录页。细节见设计文档 · 网络层与状态

5. 没有后端也想看渲染效果?

如果暂时没有后端,可以临时把三个元数据接口换成本地 mock,让渲染引擎跑起来。核心是构造一份 PageSchema

ts
// 一份最小可渲染的 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.tsfetchPageSchema 临时返回这份对象、api/data.tsqueryPage 临时返回 { records: [...], total: n },就能在没有后端的情况下验证表格/表单/单元格的渲染。各字段含义见设计文档 · 协议消费映射

常见踩坑

现象原因处理
打开就跳登录、登录后又跳回登录后端 401,或代理没通到 18080确认后端已起、/v1 代理目标正确
首页空白不跳转没有任何「有 model 的叶子菜单」后端先把模型绑到菜单(见后端文档)
列表能出但点详情报错主键字段 primaryKey 没下发后端确保字段 schema 有 primaryKey: true,前端取主键即靠它
ID 在前端变成科学计数法/精度丢失没走 json-bigint框架已用 json-bigint 把长整型 ID 转字符串,切勿对 ID 做 Number()

下一步

  • 页面与路由:菜单如何派生出列表/表单/详情三类页面,URL 如何承载列表状态。
  • 字段渲染:一个字段如何被选到对应组件。