搜索 K
Appearance
Appearance
本篇把前端的动态行为拆成五条端到端流程,每条配一张时序图:登录 → 启动引导 → 列表 → 保存 → 详情与删除。读完你能把整体架构的九层落到具体的请求链路上。
所有请求都经过统一的 axios 实例(baseURL: /v1,自动带 token、统一弹错、雪花 ID 转字符串),这层的细节见网络层与状态。
要点:
token 存 localStorage(key forge_token),user 只存内存。刷新后 token 还在(保持登录),但 user 需要 loadMe() 重新拉。route.query.redirect——这个 redirect 是被路由守卫拦截时塞进去的(见下「未登录拦截」)。Authorization: Bearer <token>。守卫只看本地是否有 token;token 是否真有效由后端在后续请求里判定——一旦后端返回 401 或业务码 40100/40101,响应拦截器清 token 并触发跳登录回调(setUnauthorizedHandler)。
首页 / 不展示内容,而是把用户引导到第一个可用的列表页:
firstLeaf 递归找「第一个声明了 model 的叶子菜单」——没有 model 的菜单是纯分组,不可作为落地页。/m/:module 时,ModuleHome 做同样的事:加载该模块菜单 → firstLeaf → 重定向;菜单全空则显示「无菜单」。/m/biz/biz:productList)也能还原。列表页是前端最复杂的流程,核心是 URL 单一事实源驱动的闭环:
闭环的关键:用户操作不直接请求数据,而是改 URL;watch(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,再发请求。required → 必填规则,length + 文本类 → 长度规则。详情页标题优先用 schema.instanceName 指向的字段值(如商品名),缺省回退到 schema.title。
delete,走 API 行为,a-popconfirm 二次确认后调 data.remove(model, id)。data.removeBatch(model, ids)。fetchData() 刷新列表。删除在后端默认是逻辑删除(软删,写 deleted 时间戳),其语义见后端逻辑删除与删表删列。
列表的工具栏/行/批量动作与表单提交,都走统一的 behaviorOf 分发:
| behavior | 触发 | 实现 |
|---|---|---|
ROUTE | 新建/编辑/详情 | router.push 据 target 拼路由 |
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',解析失败一律可见)。