Skip to content

请求时序

本篇把前端的动态行为拆成五条端到端流程,每条配一张时序图:登录 → 启动引导 → 列表 → 保存 → 详情与删除。读完你能把整体架构的九层落到具体的请求链路上。

所有请求都经过统一的 axios 实例(baseURL: /v1,自动带 token、统一弹错、雪花 ID 转字符串),这层的细节见网络层与状态

登录

要点:

  • tokenlocalStorage(key forge_token),user 只存内存。刷新后 token 还在(保持登录),但 user 需要 loadMe() 重新拉。
  • 登录成功回跳 route.query.redirect——这个 redirect 是被路由守卫拦截时塞进去的(见下「未登录拦截」)。
  • 之后每个请求由 axios 请求拦截器自动加 Authorization: Bearer <token>

未登录拦截

守卫只看本地是否有 token;token 是否真有效由后端在后续请求里判定——一旦后端返回 401 或业务码 40100/40101,响应拦截器清 token 并触发跳登录回调(setUnauthorizedHandler)。

启动引导

首页 / 不展示内容,而是把用户引导到第一个可用的列表页:

  • firstLeaf 递归找「第一个声明了 model 的叶子菜单」——没有 model 的菜单是纯分组,不可作为落地页。
  • 进入某模块首页 /m/:module 时,ModuleHome 做同样的事:加载该模块菜单 → firstLeaf → 重定向;菜单全空则显示「无菜单」。
  • 模块列表、菜单树都进 Pinia 缓存,深链刷新(直接打开 /m/biz/biz:productList)也能还原。

列表

列表页是前端最复杂的流程,核心是 URL 单一事实源驱动的闭环:

闭环的关键:用户操作不直接请求数据,而是改 URLwatch(route.query) 捕获变化后才重新解析状态、重新请求。这保证了「刷新/分享/前进后退」状态一致。状态 ⇄ URL 的对称编解码见网络层与状态 · URL 单一事实源

状态页签过滤:切换非「全部」页签时,在请求的 filters 上追加一条 { field: schema.tabField, operator: 'eq', value: 页签key }

保存

保存(新建/编辑)的精髓是提交端点三级回退,目的是让「内置 CRUD 绑了自定义 Controller」时能真正打到那个 Controller:

  • 三级回退:显式 submitEndpoint(抽屉动作传入)→ 本页 create/edit 内置动作的 endpoint → 自动 CRUD(create/update)。
  • callByEndpoint 解析 "POST /v1/data/biz/product/{id}/approve" 这类字符串:去掉 /v1 前缀(baseURL 已带)、把 {id} 替换成实际 id,再发请求。
  • 校验规则不是写死的,由字段 schema 推导:required → 必填规则,length + 文本类 → 长度规则。

详情与删除

详情

详情页标题优先用 schema.instanceName 指向的字段值(如商品名),缺省回退到 schema.title

删除

  • 单行删除:行动作里内置 delete,走 API 行为,a-popconfirm 二次确认后调 data.remove(model, id)
  • 批量删除:选中行后出现批量操作条,确认后调 data.removeBatch(model, ids)
  • 删除成功后重新 fetchData() 刷新列表。

删除在后端默认是逻辑删除(软删,写 deleted 时间戳),其语义见后端逻辑删除与删表删列

动作行为分发总表

列表的工具栏/行/批量动作与表单提交,都走统一的 behaviorOf 分发:

behavior触发实现
ROUTE新建/编辑/详情router.pushtarget 拼路由
DRAWER抽屉式编辑/详情就地打开 ForgeDrawer + 复用 ResourceForm/Detail
DOWNLOAD导出callByEndpoint(文件流细化待后端 export 端点)
API删除/审核等callByEndpoint(endpoint, {id}) 后刷新

behavior 缺省时按内置 key 兜底:create/edit/detail→ROUTE,export→DOWNLOAD,其余→API。行动作可见性由 ActionSchema.when 经极简表达式 evalWhen 求值(当前仅支持单条件 field == 'value',解析失败一律可见)。

下一步