Skip to content

Latest commit

 

History

History
566 lines (418 loc) · 31.8 KB

File metadata and controls

566 lines (418 loc) · 31.8 KB

前端架构设计(Vue 3 + TDesign)

这份文档是 web 的架构真值与边界设计说明,不再承担日常执行级 import/目录/验证清单的唯一入口。

前端执行级治理真值现在收口到 web/AGENTS.md

  • web/AGENTS.md 负责日常实现约束、模块边界、跨模块 import 规则、route 注册规则、i18n key 规范、shared 上提与 frontend validation
  • 本文档负责解释为什么采用 app + modules + shared、为什么保持模块拥有权清晰,以及为什么这些边界服务于可复用多 worktree 并行开发

如果实现级规则与本文档的表述需要分层,执行时以 web/AGENTS.md 为准;若该执行级规则改变了前端长期架构或 owned scope,再同步更新本文档。

authority-first overlay 补充:

  • web 的 owned scope 表示前端长期实现归属,不表示前端拥有 shared contract 的最终 authority
  • bounded scope forbids unrelated expansion, not required authority repair
  • 当页面、bootstrap、route/menu 组装或 generated consumer 发现上游 authority drift 时,应优先修上游 authority,再同步前端消费层

1. 目标

前端的职责不是做一个复杂的营销站,而是提供一个稳定的后台平台壳,让新模块能快速接入。

它要优先满足:

  • 登录和权限控制
  • 动态菜单
  • 标准后台页面布局
  • 标准列表 / 表单 / 详情 / 弹窗模式
  • 模块页面快速接入

2. 选型结论

最终技术路线:

  • Vue 3
  • TypeScript
  • Vite
  • TDesign Vue Next
  • Pinia
  • Vue Router
  • Axios
  • @tanstack/vue-query

前端本地化采用壳层收口策略:

  • locale 状态和消息查找集中在 web 应用层,而不是散落到页面内部
  • web/src/locales/** 拥有 shell/root catalog、locale 状态、消息聚合入口、回退语言与持久化策略
  • web/src/modules/<name>/locales/** 拥有模块 catalog;模块消息 key 不得复制到 root catalog 形成第二套真值
  • 应用壳层负责在中心运行时入口聚合 root catalog 与 module catalog;页面、壳层和模块都不应绕过该入口自行拼装第二套 messages
  • 同一 locale 内不允许重复 exact key 定义;unused key 必须通过治理脚本清理,避免历史文案继续占据 catalog
  • 重复 value 允许存在,前提是 key 归属和业务语义不同;治理目标是消除重复真值,不是强行把语义不同的相同展示文案合并
  • 英文 locale 的可见 UI 文案必须首字母大写;连接词、统计单位等语法片段可按语境保留小写
  • 当前 locale 范围与 server 设计面对齐,只收敛到 zh-CN / en-US,默认语言与回退语言都为 zh-CN
  • 请求层统一透传当前 locale,避免每个 API 调用手写语言头
  • 用户可见时间必须显式绑定当前 vue-i18n locale;禁止用宿主环境默认值渲染可见时间,例如 new Intl.DateTimeFormat(undefined, ...) 或无参数 toLocaleString() / toLocaleDateString() / toLocaleTimeString()
  • 可见时间格式化默认收口到 web/src/shared/observability 的 locale-aware formatter;API payload、URL query 与持久化状态仍保持 canonical UTC 或页面本地输入语义,不把 wire contract 改成本地化字符串
  • 当前阶段先建立消息 key、回退和持久化口子,不要求一次性把全部页面铺成多语言资源

选择 TDesign 的原因:

  • 更贴近中后台场景
  • 组件体系规整,适合批量生成标准页面
  • 设计风格比传统后台库更现代一些
  • 对 AI / MCP 辅助生成代码较友好

TDesign Vue Next 运行时引入方式:

  • 默认通过 vite.config.ts 的 resolver 将模板中的 t-* 组件按需解析到 tdesign-vue-next/es/<component> 子入口
  • 应用入口不得使用 app.use(TDesign) 全量注册,也不得引入 tdesign-vue-next/es/style/index.css 全量样式
  • 程序式插件和渲染函数组件只在使用点显式 import;如 resolver 未覆盖样式,只补对应组件级入口或 style import

前端视觉真值额外收口到仓库根目录 DESIGN.md,它负责定义 Graft 的统一视觉语气、页面骨架和 agent 生成口令;更细的页面模板与约束收口到 ai-plan/design/governance/frontend/前端视觉设计规范.mdai-plan/design/graft-design-system/

前端页面实现流程额外固定为“先声明页面类型,再进入实现”:

  • 首阶段内置 4 类基础页面母版:
    • shell
    • auth
    • overview-dashboard
    • list-form-detail
  • 它们不是页面类型全集;若新需求无法自然归类,必须先登记扩展页面类型并补齐其结构、状态、主题响应与 i18n 规则
  • 新增页面、重构页面和复杂布局页默认先输出结构方案,再进入代码实现;简单文案/样式/小交互修复可直接实现,但仍服从同一套类型、主题和文案治理规则

AI 辅助生成或评审前端代码时,应优先按 TDesign MCP 辅助开发规范 获取组件列表、组件文档、DOM 结构和变更日志。 MCP 只作为开发时知识源,不作为 web 运行时依赖。 当任务涉及 TDesign Vue Next 组件用法时,前端 Agent 默认先做 TDesign MCP preflight,以 vue-next 为查询框架, 完成组件资料查询后再进入编码;只有 MCP 不可用时,才允许退回官方文档并在 closeout 中说明原因。 这项要求属于前端治理和 closeout 审计要求:需要在任务结束时留下 MCP evidence,但不要求把 MCP 接入 CI、hooks 或仓库脚本硬门禁。

时间输入与共享查询语义补充规则:

  • 页面级 DatePicker / DateRangePicker 状态必须保存为用户本地展示时间语义
  • 禁止长期持有 DateDate[]、或混合 Date/string 作为筛选状态
  • 需要稳定跨时区复现的 URL query 与 API request,必须在共享边界统一转换为 canonical UTC
  • 禁止 Date -> toISOString() -> 直接绑定 DatePicker / DateRangePicker
  • 恢复 deep link 时,必须先把 canonical UTC 转回本地展示时间,再回填日期控件

3. 目录标准

web/src/
├─ app/
├─ layouts/
├─ modules/
├─ shared/
├─ contracts/
├─ config/
├─ locales/
├─ router/
├─ store/
├─ api/
├─ style/
└─ assets/

职责约束:

  • app/ 放壳层页面入口、应用装配入口和其他不归单个业务模块长期拥有的页面;认证页只允许作为壳层装配点或薄包装,长期实现真值收口到 modules/auth
  • modules/ 放 backend modules 对应的前端模块,是业务能力的唯一长期真值
  • shared/ 只放跨模块可复用且无业务语义的组件、composables、helpers 与样式片段
  • contracts/ 放平台级前端稳定契约,不放模块私有契约
  • config/ 放壳层级平台配置真值;如果该目录仍存在,它继续属于 shell-owned,不作为模块业务真值落点
  • locales/ 处理应用级 locale 状态、消息目录和格式化入口
  • router/ 处理静态路由和动态路由装配
  • store/ 只放跨页面共享状态
  • api/ 只放平台级 request/auth/session adapter,不放模块业务 API 真值;auth 运行时实现迁入 modules/auth 后,这里只保留平台 adapter 与桥接消费面

最终运行面约束:

  • app/** 是壳层页面真值,不承担业务模块实现
  • web/src/modules/<name>/** 是业务模块页面、API、局部类型、模块私有契约、模块消息源和模块 bootstrap 声明的长期真值
  • web/src/shared/** 是跨模块共享真值;只有在资产被多个模块或壳层复用且不携带业务语义时才允许进入
  • 新模块默认目录为 pagescomponentsapicontracttypeslocales
  • 模块存在稳定子菜单或平级子页面时,页面真值默认收口到 pages/<subpage>/index.vue,并让 index.test.tsindex.less 与该页面同目录共置
  • 仅供模块内部多个页面/组件复用的 helper、snapshot、样式片段与 composable,默认放在 modules/<name>/shared/**,不要与根级 web/src/shared/** 混用
  • modules/<name>/bootstrap-routes.ts 是模块拥有的动态路由声明文件;壳层通过模块注册入口消费它,而不是把它当作任意跨模块 import 面
  • 永远不要重新引入根级 web/src/pages/** 作为长期运行面
  • 根级 web/src/api/** 只允许保留平台级 adapter;模块业务 API 不得落回根级
  • 根级 web/src/contracts/** 只允许保留平台级稳定契约;模块稳定契约与跨模块稳定 DTO 必须收口到 web/src/modules/<name>/contract/**
  • web/src/modules/<name>/types/** 默认是模块私有实现面,不作为跨模块依赖入口
  • 根级 components/hooks/utils/ 不是最终所有权层;可复用且无业务语义的资产应进入 shared/**,平台级基础设施应回到壳层边界
  • 当前代码中如果暂时还不存在 web/src/shared/**,表示尚未抽出符合条件的共享资产,而不是允许继续把业务真值散落到根级目录

多 worktree 准备阶段的 web 目录所有权补充约束:

  • layouts/router/locales/config/、全局 store、平台级 contracts/、平台级 api/app/ 属于 shell-owned 目录
  • web/src/modules/<name>/ 属于 module-owned 边界,模块内部应承载该功能的页面、API、局部类型、模块私有稳定契约、模块消息源与模块注册面
  • web/src/shared/** 属于 shared-owned 边界,只承载业务无关的复用资产
  • 临时 agent worktree 的默认 web owned scope 按模块粒度声明,不按 api/app/store/ 等技术层切分
  • shell-ownedshared-owned 目录不归任何 agent worktree 长期占用;需要修改时通过开发者集成切片协调

目录边界补充约束:

  • app/** 只允许依赖 shared/**contracts/**router/**store/**、平台级 api/**,以及模块显式对外暴露的注册面
  • config/** 仍存在时,它继续作为壳层/平台配置依赖面被 app/**layouts/**router/** 等 shell 目录消费,不转化为模块 owned scope
  • app/** 不得导入 modules/*/api、模块 pages、模块内部 components、模块内部 locales 或模块局部 composables
  • modules/** 不得反向导入 app/**
  • shared/** 必须保持业务无关,不得承载权限码、路由名、DTO 或 user / rbac / dashboard 这类业务语义
  • 根级 api/contracts/components/hooks/utils/ 不得重新成为模块实现溢出的默认落点

4. 布局模型

建议至少提供两个布局:

  • AuthLayout 用于登录等无需完整后台壳的页面
  • BasicLayout 用于后台主界面

BasicLayout 应包含:

  • 顶部导航
  • 侧边菜单
  • 面包屑
  • 内容区
  • 底部 Footer 与统一安全留白
  • 用户操作区

所有标准后台页面都挂在 BasicLayout 下。

当前阶段的默认后台壳基座必须继续以真实 web/ 工程为唯一运行面,不得把 web/ai-libs/tdesign-vue-next-starter 或其他 starter/demo 工程当作临时运行基线。

收口原则:

  • web/ai-libs/tdesign-vue-next-starter 只作为本地参考源,用于借鉴布局、页面组织、治理配置和 TDesign 写法
  • ai-plan/design/graft-design-system/ 作为当前项目的 Graft 风格参考模板目录,只提供页面骨架、设计约束与组合方式,不提供运行时依赖
  • 如需复用 starter 壳层或页面样板,应把必要结构显式迁入当前 web/ 工程,并按 Graft 的 menu + route + page + api + permission 边界收口
  • 不允许在 web 外保留并行 runtime surface、并行路由真值、并行 mock/demo 入口或第二套前端工作台
  • starter 自带的前端权限旁路、mock 业务接口、tabs-router 策略和演示业务页面只能作为迁移参考,不得成为 当前运行态依赖
  • 当真实运行面已经不再引用某个 demo 页面时,应直接从 web 删除该页面与残留引用,而不是在真实工程里继续保留“备用示例”

5. 路由与菜单

前端路由分两类:

  • 静态路由 例如登录页、404、基础首页
  • 动态路由 根据后端返回的菜单与权限生成

建议流程:

  1. 登录成功后获取用户信息、权限集、菜单树
  2. 前端根据菜单元信息装配可访问路由
  3. 使用全局守卫阻止未授权访问
  4. 首页快捷入口由当前可见菜单派生;新增菜单时不再单独维护第二份 homepage quick action / quick link 注册

5.1 Realtime 消费边界

前端实时能力统一采用两段式模型:

  1. 普通 HTTP 请求申请单次 realtime subscription ticket
  2. 使用统一 /ws?topic=...&ticket=... 连接消费对应 topic

约束:

  • 页面不得直接长期依赖模块私有 WebSocket 订阅路径来消费普通资源/事件推送
  • web 的实时消费 authority 是 canonical topic,而不是页面私有 socket URL 拼接规则
  • 容器资源统计这类持续刷新视图,应以 WebSocket 推送为 authority;HTTP 继续承担基础详情/列表结构数据,不再承担实时 stats authority
  • 前端收到新的实时快照前,应保留最后一次成功值,避免因为一次刷新失败把 UI 退回“未采集”

多 worktree 收口补充约束:

  • router 壳层不应继续直接维护“页面 path -> component”的功能白名单
  • 模块应仅通过 modules/<name>/bootstrap-routes.ts 向壳层声明可接入的 bootstrap 动态路由
  • 首页快捷入口是壳层消费当前 visible menu/bootstrap state 的派生结果,不是模块声明式 dashboard contract
  • 首页 widget area 若要展示模块摘要、状态或操作组件,仍需单独设计 dashboard widget contribution;不要把 widget 需求混入菜单注册
  • 对 Notification Center 这类不属于左侧业务菜单、但仍属于后台壳的全局页面,模块应通过注册面的 globalRoutes 声明菜单外全局路由;后端不应返回 hidden menu 来撑路由
  • 菜单隐藏和页面/Tab 隐藏必须分离:hiddenMenu 只影响菜单渲染,不能阻断 Tab、breadcrumb 或 keep-alive 上下文同步
  • 共享壳层只负责装配,不负责替模块持有长期 feature 真值

路由元信息至少要包含:

  • name
  • path
  • component
  • title
  • title_key
  • icon
  • permission
  • module

菜单本地化约束:

  • title_keyweb 内部菜单与路由装配的唯一长期真值
  • 如果上游输入仍同时提供 title,只能在 request adapter 边界把它当作外部输入回退,不得把 title 写回模块或壳层作为第二套内部真值
  • 菜单、标题与错误显示在存在稳定 key 时必须优先走 key + fallback,不得把后端最终文案当作前端主真相
  • 模块与壳层内部都不应再存放“title_key + 独立 title 文案”的长期并行定义

契约治理补充约束:

  • route name/pathstorage keyrequest headerauth schemeAPI error codefeature flag key 与前端按值分支的稳定状态枚举,统一受 契约治理与魔法值治理规范 约束
  • 平台级前端契约优先收口到 web/src/contracts/*,模块私有稳定契约优先收口到 web/src/modules/<module>/contract
  • routerrequeststorage、模块 contract 边界优先消费显式 typed contract,而不是继续扩散裸字符串
  • 不得用 alias、重复 path 常量或根级 re-export 维持第二套长期 contract 真值;旧 contract 完成调用方迁移后必须删除

6. 模块接入规范

新增一个后台模块时,前端至少新增:

  • 模块 API
  • 模块路由定义
  • 页面组件
  • 权限码映射
  • 菜单元信息

当仓库处于多 worktree 准备阶段时,新增模块还必须新增:

  • web/src/modules/<module>/ 下的模块注册入口
  • 模块自己的 bootstrap-routes.ts 路由声明
  • 默认目录 pagescomponentsapicontracttypeslocales
  • 模块自己的消息源、局部 UI 片段与 typed contract

模块接入壳层只能通过 modules/ 下的注册入口完成;壳层文件只消费注册结果,不承担模块实现。

新增模块时禁止出现以下落点:

  • 根级 web/src/api/** 中的模块业务 API
  • 根级 web/src/contracts/** 中的模块私有契约
  • 根级 components/hooks/utils/ 中的模块专有实现

如果某个能力确实被多个模块复用,先判断它是否业务无关;只有业务无关时才允许抽到 shared/**,否则继续留在模块边界内。

当模块文案未来可能需要切换语言时,应优先存放稳定消息 key,而不是把可变文案直接写死在路由或菜单元信息里;菜单场景优先对齐 title_key + title fallback

对于后端返回但直接面向用户展示的稳定业务文案,例如 permission code -> 名称/说明 这类可预测元数据,前端应优先在所属模块内 建立 stable contract -> locale key 的映射,再由 locales/** 提供多语言文案。接口原文只作为未知项、兼容期或迁移期回退, 不得长期作为唯一显示真值,否则页面会出现“菜单已本地化但表格说明仍是后端英文种子”的分裂状态。

如果是标准 CRUD 模块,建议统一模式:

  • 列表页
  • 创建 / 编辑弹窗
  • 详情抽屉
  • 搜索区域
  • 批量操作区

这样 AI 辅助生成时代码更容易保持一致。

为降低 AI 在新页面生成时的自由发挥,web 可以保留一组 starter 风格的页面样板作为设计参考或显式迁移模板, 但这些样板必须收口在当前 web 工程的真实模块和页面边界内,不得再把 starter 整体工程视为运行时基线:

  • dashboard
  • list
  • form
  • detail
  • result

这些页面样板如确有保留必要,必须满足两个条件:

  • 仍被当前真实运行面直接引用
  • 已明确归属到壳层异常页或具体模块 owned scope

不满足上述条件的旧 starter/demo 页面应删除,示例来源统一回到 web/ai-libs/tdesign-vue-next-starter,而不是继续在 app/**modules/<name>/** 之外重新维持根级 web/src/pages/** 平行运行面。


7. 状态管理建议

Pinia store 保持克制,只保留真正跨页面状态:

  • auth token、当前用户、登录状态
  • i18n 当前语言、回退语言、持久化偏好
  • permission 权限码集合、路由可访问性
  • navigation 菜单树、当前激活菜单
  • module 模块列表、模块启用状态

不要把页面局部表单状态全部塞进 store。

7.1 服务端状态与 Query Cache

Pinia 只拥有跨页面客户端状态;后端资源快照、列表、详情和 mutation 状态由 @tanstack/vue-query 管理。

  • 唯一 QueryClient 位于 web/src/shared/query/**,应用启动时注册;模块业务 query key、query function 与 mutation 仍属于 modules/<name>/**
  • query function 必须继续调用模块 api/**,由 web/src/utils/request.ts 保持唯一 Axios、认证、locale 与错误边界
  • URL 查询参数、表单草稿、表格列可见性和本地选择状态不是 Query cache 真值;query key 只使用已归一化的 API 输入
  • mutation 完成后精确失效或更新受影响的 module query;realtime 消息同样通过失效或 setQueryData 更新,不维护第二份 page-local server snapshot
  • 当前不持久化 Query cache;认证会话清理必须清空 QueryClient,避免不同用户读取到旧资源

AI 在页面设计、重构或 review 阶段,遇到以下信号时必须先评估模块级 @tanstack/vue-query,再新增 onMounted + watch + ref 的手工请求状态:同接口被多个组件读取、页面重新进入后重复读取、手工 loading/error/data、 手工 refresh / 去重 / 轮询,或 realtime 重连后需要刷新 HTTP 快照。

  • 优先迁移 Query 的是 server-owned 列表、详情、目录、摘要和非流式轮询快照;先复用模块 api/**,再定义模块私有 key
  • TanStack TableTanStack VirtualTanStack RouterTanStack Form 不是默认依赖;只有现有 TDesign、日志虚拟化、 Vue Router 或 TDesign Form 已被性能/维护性证据证明不足时,才建立有验收指标的独立设计
  • Monaco、xterm、WebSocket 流、编辑器草稿、表格列偏好和 URL filter 不进入 Query cache;不要为了采用 TanStack 建第二套状态真值

7.2 Signals 试点边界

Piniaweb 唯一正式共享客户端状态层;它不替代 Query cache。

约束如下:

  • 不设计 Pinia 的全局替换方案,也不把 signals 作为默认状态管理标准
  • 只有在 setting/theme runtime 被证明存在真实的 computed / watch / store action 维护问题时,才允许评估局部试点
  • 评估阶段必须先完成文档级 go/no-go 判断;在准入结论明确前,不新增依赖、不改业务代码
  • 如需进入局部试点,候选方案优先选择纯运行时的 alien-signals,不考虑需要预编译耦合的方案
  • 如果未来引入 useThemeRuntime(),它只能作为 setting/theme 内部 adapter,不得升级为业务模块通用标准

明确禁用范围:

  • auth
  • permission
  • router
  • tabs-router
  • API cache
  • 表单提交状态
  • 后端业务实体状态

当前针对 signals 的评估、准入条件、最小 POC 候选边界、成功标准、退出标准与测试范围,记录在 ai-plan/public/archive/mvp-extension-path/subtopics/web/design/signals-theme-runtime-evaluation.md


8. 组件风格约束

统一使用 TDesign 组件为主,避免混搭多套 UI 库。

页面设计原则:

  • 统一表格与表单间距
  • 统一页头、卡片、筛选区结构
  • 统一弹窗与抽屉交互模式
  • 统一状态标签与操作按钮风格

UnoCSS 只做辅助布局和少量原子样式,不要用来重写整套视觉体系。

涉及 TDesign 组件 API、事件、插槽、DOM 结构、组件升级影响时,AI 必须先查询 TDesign MCP 或官方文档, 再生成或修改代码。没有查询依据时,不应凭经验猜测组件属性名、事件名或内部 DOM 选择器。若本轮涉及组件改动,closeout 必须记录查询组件、查询能力、采用状态与原因。


9. 日志基础设施约束

前端日志基础设施用于统一开发期诊断、运行期故障记录和后续可扩展传输,不等同于页面消息提示,也不作为业务逻辑分支控制器。

基础模型约定为:

  • LoggerCore
  • LogEvent
  • Transport

默认能力约束:

  • 业务代码只通过 createLogger 获取 logger,不直接拼装底层 transport
  • 默认 transport 使用 consola
  • 需要静默时使用 NoopTransport,而不是让业务代码到处判断是否输出
  • logger 必须支持 child()withContext(),用于派生子模块上下文和附加稳定上下文

调用治理约束:

  • 每个 logger 都必须带稳定的 moduleName,并以模块边界而不是页面控件命名
  • moduleName 应与 web/src/modulesapplayoutsrouterstores 等实际责任边界一致,避免临时字符串漂移
  • 传入 metacontext 的数据必须可序列化,避免函数、类实例、DOM 节点、响应式代理和循环引用对象进入日志管道
  • 日志里不得记录 token、密码、cookie、完整响应体中的敏感字段、用户输入的高风险原文或其他未经脱敏的敏感数据
  • logger 记录错误不代表已经完成用户提示;logger 与 UI message 是两条独立责任链,是否提示用户要由页面或交互层显式判断
  • catch 中调用 logger.error 后,不得以“已记录日志”为理由静默吞掉异常;必须继续按调用边界选择抛出、转换、返回显式失败态或补充 UI 反馈

调试期治理约束:

  • 临时 debug 日志必须有明确生命周期,只允许服务于当前排障、联调或迁移窗口
  • 临时 debug 输出在问题关闭、契约稳定或切片提交前应删除、降级或收口到明确开关下
  • AI 生成或修改前端代码时,不应默认加入噪音型 debug 日志;确需加入时,应优先放在最小边界并说明后续清理点
  • AI 不应把 logger.debugconsole.log、注释掉的调试代码或一次性追踪字段长期留在页面、store、router 守卫或 API 封装层
  • 长期保留的 debug 开关必须通过 shell-owned Debug Runtime 统一管理,flag 按模块功能使用 namespaced 命名,例如 tabs.layouttabs.storeproject.monaco
  • .env* 中的调试键必须与 namespaced flag 对齐,优先使用 VITE_DEBUG_<MODULE>_<SUBMODULE> 形式;不要继续平铺无边界 debug key
  • window.__GRAFT_DEBUG__ 只作为开发者控制台 API,真实运行时状态由 debug store 持有;控制台入口不应成为第二套状态真值
  • Debug Runtime 可在所有环境常驻但默认关闭;未来若提供危险调试能力,必须额外受 import.meta.env.DEV 约束
  • 调试日志默认输出为扁平单行 key-value 摘要,保证排障信息可 grep、可复制和可稳定比对,不把关键事实藏在深层嵌套对象中
  • 对交互类、路由类、菜单类、图表重排类问题,默认优先通过结构化控制台日志、守卫链路日志、事件处理日志或最小复现测试获取运行时证据,而不是只凭静态代码猜测真实交互路径
  • 当用户可以提供浏览器控制台输出、录屏、网络面板或稳定复现步骤时,应优先利用这些运行时证据收敛问题,再修改实现;日志只是诊断手段,不应作为长期业务输出保留

扩展边界约束:

  • Transport 设计应允许后续接入远端收集、审计桥接或测试替身,但当前阶段不提前引入复杂采集平台
  • 业务模块只依赖稳定 logger 接口,不依赖 consola 的具体 API,避免后续 transport 替换时扩散改动

10. 前端治理基线

当前 web 的治理基线应按一条完整质量链来设计,而不是把类型、格式、Lint、测试拆成彼此独立的可选项。

治理基线至少包含:

  • TypeScript strict
  • format:check
  • ESLint
  • Stylelint
  • Vitest
  • Husky + lint-staged
  • commitlint

当前仓库已经把统一验收入口收敛到显式脚本 bun run check,并要求完成态校验顺序固定为:

  1. format:check
  2. typecheck
  3. openapi:frontend-governance:check
  4. lint:i18n
  5. lint
  6. stylelint
  7. hygiene:check
  8. test:run
  9. build

仓库根 Justfile 可以提供 optional developer entrypoint / convenience layer,例如把 just checkjust lintjust generate 作为常见工作流捷径;但这些 recipe 只允许包装 bun run check、相关 web/package.json scripts、现有 Go CLI 与仓库脚本,不得升级为新的 startup authority、frontend completion truth、runtime contract 或 CI contract。

执行环境约束:

  • 当前仓库的 web 命令统一通过仓库当前环境中的 bun 执行,包括 bun install、bun run dev、 bun run typecheck、bun run test:run、bun run build、bun run check
  • 不应混用多套 Bun 或其他包管理器刷新 web/node_modules,避免生成不一致的依赖目录
  • .ai/environment/tools.ai.yaml 应记录当前前端工具链真值,AI 和自动化不得自行发明第二套本地命令矩阵

约束说明:

  • pre-commit 只处理暂存文件,优先通过 lint-staged 执行格式化与局部修复,不把完整 check 链路塞进提交前钩子;本地 contract governance changed-scan 也应在这里阻断
  • pre-push 不再重复执行本地 changed-scan;远端 push 阶段的 contract governance 阻断统一由 CI job 负责,避免本地提交链与 Windows Git / WSL 执行环境分叉
  • i18n governance 通过 bun run lint:i18n 收口到 bun run check,且 lint:i18n 默认以 STRICT_I18N_KEY_FIRST=true 执行 strict key-first governance;CI 继续执行 bun run check 即可覆盖 i18n,不得新增第二个 CI i18n 真值;本地 hooks 只在 web/src/**web/scripts/check-i18n-governance.tsweb/package.json 变化时增量触发 lint:i18n
  • strict key-first governance 下,fallback-only server key-first registry findings 是阻断项;key + fallback pair 只允许作为兼容回退,不得重新变成展示主真相
  • web 而言,功能完成、任务完成、准备合并三个节点都必须跑完整 check;开发中间态才允许退回最小验证
  • 完整 check 是当前前端手动验收、合并前检查和 CI 的正式入口,而不是可选建议
  • Vitest 是当前前端治理的正式单元测试基线,不再把“前端没有测试”当作默认前提
  • Stylelint 用于约束 Vue 样式块与样式覆盖边界,避免随意改写 TDesign 内部 DOM
  • hygiene:check 负责死代码、重复代码和非白名单固定字号治理;必要固定字号必须带 selector 级白名单原因
  • commitlint 与仓库 commit-msg hook 负责把仓库提交规范收口到可自动校验的入口,但提交治理真值统一以根 AGENTS.md 为准;提交标题默认语言、ownership 判定后的自动提交条件、scope 强制要求、普通提交正文 bullet 要求,以及 escaped control text 禁令都不得在 web 文档里另立第二套规则
  • 当前最小自动提交信息约束至少包含:标题符合 Conventional Commits、必须显式带 scope、普通非 merge/revert 提交必须包含至少一条 - 正文 bullet,并禁止把字面量 \n\t\r 一类 escaped control text 直接写进提交信息;默认标题语言与自动提交边界均跟随根 AGENTS.md
  • JetBrains / WebStorm Inspection、TS language service suggestion、IDE 拼写检查默认只作为本地辅助信息,不直接 视为仓库完成态阻塞项;只有当同类规则被 typechecklintstylelinttest:runbuild 之一覆盖,或仓库文档 明确提升为项目规则时,才进入正式验收口径
  • 默认完成态要求 typechecklintstylelinttest:runbuild 无 warning,不把 Vite/Rollup 构建 warning、 chunk size warning 或第三方依赖 warning 当作天然可忽略噪音
  • 只有确认 warning 来自第三方依赖,或当前切片无法在不引入更大风险的前提下安全消除时,才允许走受控例外
  • 受控例外必须记录在当前 active topic 或 subtopic 的 tracking 文档中,并写清楚 warning 来源、影响、暂不处理原因、 下一步治理动作;不得继续使用 non-blocking warning 这类弱化完成态要求的表述
  • 对高价值、低争议的问题,优先把规则前移到 CLI: 例如未使用变量、未使用导入、import 排序、debugger、调试期 console.log 不把 JSDoc 完整性、suggestion 级 TS 提示、IDE 专属重构建议默认塞进 CI

类型治理策略:

  • 默认保持 strict 开启
  • 不允许通过大面积 as any 规避类型问题
  • any 仅用于第三方库边界、动态 JSON、临时迁移兼容层等少数边界场景,并尽量收口到 adapterclientschema 一类位置

参考源边界:

  • web/ai-libs/tdesign-vue-next-starter 默认仍是本地参考源,用于借鉴治理配置、TDesign 页面组织和样式写法
  • 允许从 starter 提取壳层、页面样板、治理配置或样式模式,但必须显式改造后并入当前 web 工程,不能把 starter 全量工程当作临时运行基线
  • web/ai-libs/ 不是 web 的运行时依赖,也不是第二个前端真值或第二个 Git root
  • 不应把 starter 内的 mock、前端权限旁路、tabs-router、演示页结构不加约束地长期保留在 Graft 当前 web

11. 第一阶段页面范围

首批页面:

  • 登录页
  • 仪表盘
  • 用户管理
  • 角色管理
  • 权限管理
  • 模块管理
  • 审计日志基础页

后续页面再按模块逐步接入。


12. 结论

这套前端设计的重点不是炫技,而是让页面新增路径固定、风格统一、权限接入自然。

如果一个新模块能在较少决策下完成“菜单 + 路由 + 页面 + API + 权限”接入,就说明前端架构是成功的。