# CLAUDE.md 本文件是我(Claude)在 `cn-rdms-web` 项目中的个人工作笔记,沉淀团队既有规范(来源:`AGENTS.md`)与协作惯例。每次进入仓库前先读这一份,避免重复踩坑。 > 本文件仅本地保留,已加入 `.gitignore`,请勿提交。 --- ## 0. 行为基线(最重要,先记住) - **描述现状以代码、配置、文档可直接验证的事实为准**;不引入历史实现/过渡方案/猜测。 - **默认精简回答**:先给结论 → 改动点 → 验证方式 → 必要风险。**除非用户主动要求详细,否则不要展开**——不复述清单、不列每条改动的小理由、不堆"汇总"段。用户只让分析就停在分析层,不主动跳到实现。 - **分析/解释类回答不要堆代码层面描述**:默认用业务/逻辑语言说清楚结构、差异与结论;不要大段贴源码、不要罗列 `file:line`、不要把"实现细节"当解释。只有用户明确要求看代码、或非贴不可的关键佐证(如某行就是争议焦点),才贴最少代码片段。 - **进入实施阶段前,先说目标、涉及模块、预计改动点、验证方式**。 - **最小改动原则**:只改当前任务必需的范围,不顺手重构无关代码。 - **不主动执行 git 操作**(status/diff/add/commit/restore/reset/checkout 全部不主动跑),除非用户明确要求。识别用户改动优先用 Read 直接看文件。 - 工作树脏的时候,**不要回退与当前任务无关的变更**。 - 静态校验默认只跑 `pnpm typecheck`;UI/交互/样式类任务**默认不补也不跑前端测试**,除非用户明确要求。 --- ## 1. 项目骨架(认知地图) | 维度 | 现状 | |---|---| | 应用 | RDMS 系统的 Vue 3 后台前端 | | 包管理 | `pnpm`(>=8.7.0),Node `>=20.19.0` | | 工具链 | Vite 7、TypeScript、Pinia、Element Plus、UnoCSS | | 工作区 | `packages/*`,通过 `@sa/*` 引用 | | 别名 | `@` → `src`;`~` → 仓库根 | | 端口 | dev 9527 / preview 9725 | | 环境文件 | `.env`、`.env.dev`、`.env.prod` | **已经形成闭环的五条主线,后续改动顺着做,不平行起新的:** 1. **路由来源统一**:页面文件 + 自定义路由 → `elegant-router` 生成 → `build/plugins/router.ts` 集中补 `meta`。 2. **权限入口统一**:常量路由 / 权限路由分流;`route store` 负责初始化、菜单生成、缓存路由、面包屑。 3. **请求入口统一**:所有业务请求走 `src/service/request/index.ts`。 4. **页面套路统一**:列表页 = 搜索区 + 表格区 + 操作弹层/抽屉 + `modules/*` 子组件。 5. **衍生资产统一**:页面资源白名单从路由结构生成,不手工维护第二份。 --- ## 2. 关键目录速查 | 路径 | 职责 | |---|---| | `src/views` | 业务页面(编排层薄) | | `src/components` | 共享组件 | | `src/layouts` | 应用壳、头部、侧栏、菜单、标签页、主题抽屉 | | `src/store/modules` | Pinia 模块:app / auth / route / tab / theme / dict | | `src/service/api` | 接口封装、参数归一化、查询字符串拼装、返回类型对齐 | | `src/service/request` | 统一请求实例、鉴权、加密、错误处理、token 刷新 | | `src/router/routes` | 自定义路由 | | `src/router/elegant` | **生成产物,不要手改** | | `src/theme/settings.ts` | 默认主题与布局设置 | | `build/plugins/router.ts` | elegant-router 配置 + 路由 meta 生成 | | `src/hooks/common/table.ts` | 列表页表格 hook 主入口 | | `src/hooks/common/form.ts` | 表单校验与表单实例 hook | | `src/constants/status-tag.ts` | 业务对象状态颜色(ElTag type)集中配置 | | `src/styles/scss/element-plus.scss` | 表格/弹层/按钮/表单 密度与公共壳样式 | | `packages/*` | 项目内本地共享库 | | `docs/` | 架构/权限/页面规范文档,做相关改动前先查 | --- ## 3. 生成文件(不要手改) - `src/router/elegant/imports.ts` - `src/router/elegant/routes.ts` - `src/router/elegant/transform.ts` - `src/typings/elegant-router.d.ts` - `src/typings/components.d.ts` - `docs/frontend-page-resource-manifest.json` **再生命令:** - 路由产物过期 → `pnpm gen-route` - 页面资源清单需同步 → `pnpm gen:page-resource-manifest` --- ## 4. 路由与导航 - 新增业务页:通过页面文件 + `build/plugins/router.ts` 补齐,**不要在多个位置重复注册**。 - `meta.icon` = Iconify 图标;`meta.localIcon` = 本地 SVG。**不要混用字段语义。** - `meta` 中心落点是 `build/plugins/router.ts`,新页的 `icon`/`order`/`roles`/`keepAlive` 在那里集中维护。 - `meta.constant = true` → 常量路由;其他默认权限路由。常量路由维护入口是 `build/plugins/router.ts` 和 `src/router/routes/custom-routes.ts`。 - `i18nKey` 是兼容字段,不是新页必须补齐项。 ### 4.1 对象上下文业务域(重要陷阱) - `product`、`project` 这类业务域,**入口页是设计如此**:先进业务域入口页 → 再选对象建上下文。**不要把"入口页是可点击菜单"误判成 bug。** - 入口页(如 `product_list -> /product/list -> view.product_list`)可作为左侧一级菜单实际命中页。这 ≠ 已进入对象上下文态。 - **遇到"点入口页后布局壳消失、只剩内容页"**:先查是否动态权限路由模式 + 后端 `get-user-routes` 是否缺业务域根路由。**不要直接把入口菜单从"菜单"改成"目录"**。 - 在 `VITE_AUTH_ROUTE_MODE=dynamic` 下,若后端只返回叶子页(如缺 `product -> layout.base`,只返 `product_list`),前端必须在动态路由归一化阶段**补回本地业务域骨架**,不能让入口裸挂为顶层 `view.*`。 - 对象上下文稳定来源仍是本地路由骨架;动态路由兼容只能"补骨架 + 对齐入口",不能反推。 - 新增业务域时同步检查:本地静态骨架、`src/constants/object-context.ts` 中的 `domainKey/entryRouteKey/entryRoutePath/fallbackDefaultRouteKey`、动态路由归一化、对象上下文 store、头部菜单切换。 --- ## 5. 分层职责 | 层 | 该做 | 不该做 | |---|---|---| | `src/views` | 编排状态、表单行为、组合 store/service | 散落 URL 拼接、token 注入、错误提示、权限路由推导 | | `src/components` | 可复用 UI / 局部业务部件 | 长期堆只服务单页面的复杂流程 | | `src/service/api` | 接口封装、参数归一化、查询拼装、类型对齐 | 在 views/store/components 重复手写接口地址和序列化 | | `src/service/request` | 统一鉴权/加密/成功码/token 刷新/错误处理 | 平行引入新的 axios/fetch 链绕开封装 | | `src/store/modules` | 跨页面共享状态 | 把临时局部状态堆进全局 store | | `src/router` & `build/plugins/router.ts` | 路由/菜单/权限标识/首页/路由 meta | 在页面里临时写条件分支替代正式配置 | | `src/layouts` & `src/theme` | 全局布局壳与主题 | 在业务页面复制平行布局/主题状态 | --- ## 6. 业务页面开发风格 - **页面组件保持"编排层薄"**:页面文件主管搜索参数、表格 hook、列定义、弹层开关、接口编排。 - 列表页拆同目录 `modules/*`:搜索组件、操作弹层、详情抽屉、资源面板等。 - **参考实现**:系统管理下 `user`/`role`/`menu`/`dict`。 - 列表 hook 优先复用:`src/hooks/common/table.ts` 的 `useUIPaginatedTable`、`useTableOperate`、`defaultTransform`。 - 表单 hook 优先复用:`src/hooks/common/form.ts` 的 `useForm`、`useFormRules`。 - **业务口径是"内网中文优先"**:新页不必强行国际化;但已有大量 `$t(...)` 的页面继续开发时,保持局部一致,不要中文/i18n 混用。 --- ## 7. 表格、搜索区、操作列 ### 7.1 搜索区(强约束) - **必须用** `src/components/custom/table-search-fields.vue` 的 `fields` 声明式配置,不得手写 `ElRow/ElCol/ElFormItem` 骨架。 - 仅当字段存在复杂联动、自定义插槽或 `TableSearchFields` 明确无法承载时,才退回 `src/components/custom/table-search-panel.vue`,并在实施说明中写明原因。 - **搜索区按钮组固定在第一行最后一格**;存在折叠时按钮顺序固定为 **展开/收起 → 重置 → 查询**。**不允许**因查询条件不足、展开收起或响应式样式把按钮提前或挤到下一行。 - `columns` 表示首行总格数,**最后 1 格永远留给按钮**;字段不足 `columns - 1` 由组件补空占位;超过则进入展开区。 - 4 个查询条件的场景必须 `:columns="4"`(3 条件 + 按钮)。 - 搜索模块只接 `model` 和必要选项,只发 `reset`/`search`,**不直接承载列表请求**。 - 详细规范见 `docs/table-search-fields-usage.md`。 ### 7.2 表格 - 操作列优先复用 `src/components/custom/business-table-action-cell.tsx`。 - 操作数 ≤ 2:直出;操作数 > 2:**1 个直出主按钮 + 1 个更多按钮**。 - `ElCard` 承载 `ElTable height="100%"` 时,`body-class` 优先用公共类 **`business-table-card-body`**(由 `src/styles/scss/element-plus.scss` 维护)。**不要为每页新建 `xxx-table-card-body` 私有样式**。历史私有类不强制专项回改,触达再收敛。 - 表格/按钮/弹层/表单的尺寸与间距标准走 `element-plus.scss` 和公共组件,**不要在业务页散落写局部尺寸作为事实标准**。 --- ## 8. 表单与弹层(强约束) ### 8.1 组件选择 - 标准组合:`ElDialog / ElDrawer / ElForm / ElScrollbar / #footer`。 - 轻中量表单:`src/components/custom/business-form-dialog.vue`。 - 字段较多 / 需保留列表上下文 / 重型控件:`src/components/custom/business-form-drawer.vue`。 - 表单分组:`src/components/custom/business-form-section.vue`。 ### 8.2 Dialog 宽度三档(按纯表单字段数) | 字段数 | preset | 默认列数 | 目标宽度 | |---|---|---|---| | ≤ 6 | `sm` | 单列 | 520px | | 7 ~ 14 | `md` | 双列 | 720px | | > 14 | `lg` | 双列为主 | 960px | - 实际宽度上限:`calc(100vw - 32px)`。 - **不因为单个 textarea 自动升档**,不做列数响应式折叠。 - 归到 `sm` 时不能只改 preset,**字段布局也要落到单列**:常规 `ElCol` 用 `span=24`,除非已判定为复合内容特例。 ### 8.3 复合内容特例 左右分栏 / 表单+表格 / 表单+树 / 关系编辑器 / 时间线 / 大段说明区 → 不强按字段数归类,按内容复杂度评估 `md`/`lg` 或更宽。**只有无法合理归入"纯表单三档"时才允许特例。** ### 8.4 表单布局 - 常规 CRUD:`label-position="top"` + `ElRow + ElCol` 双列 + `gutter=16`。 - 普通字段 `span=12`;长文本/重量级字段 `span=24`。 - 字段 ≤ 6 默认按单列理解。 ### 8.5 其他 - **禁止**用页面级宽范围样式覆盖整页 `.business-form-dialog` 来统一放大;如需特殊宽度,必须精确作用于目标弹框,不误伤同页其他 dialog。 - 底部按钮固定 **取消 → 确认**,右对齐。 - 单选组/开关字段优先复用既有钩子:`business-form-radio-group`、`business-form-switch-field`。 - **权限按钮默认"无权限不渲染"**;只有业务状态暂时不可操作但仍需让用户感知入口存在时,才允许保留禁用态。 ### 8.6 全局反馈(Toast / Message) - **全局反馈通道只有一个**:`window.$message`(`src/components/common/app-provider.vue` 注入的 `ElMessage`),全仓 30+ 处都用它。**不要平行引入 `ElNotification` / 自定义 toast**;要求"全局风格切换"则单独立项,不要在小改动里悄悄启动。 - **type 语义**(4 种 type → 3 类视觉语义): - `error` → 错误(红):操作失败、明确异常 - `warning` → 告警(橙):用户即将出错、风险确认 - `success` → 通知-成功(绿):操作成功 - `info` → 通知-信息(蓝):信息告知、默认兜底说明 - **type 选错就丑**:`warning` 是"出错警告",不要拿来表达普通信息(用 `info`);`info` 是"信息告知",不要拿来报错(用 `error`)。 - **"先做 A 再做 B" 的引导性提示**:用 `ElFormItem :error="msg"` 红字内联(跟校验同款),**不要用 toast**——toast 适合事后反馈、不阻断流程,对引导性提示体验差。 - **全局视觉**(实色背景 + 白字 + 阴影 + `$radius` 圆角)由 `src/styles/scss/element-plus.scss` 末尾的 `.el-message` 块统一维护,**业务页面禁止覆盖** `.el-message-*` 样式。要调颜色就改 `element-plus.scss`,不要在业务页 scoped 散落。 ```ts window.$message?.success('保存成功'); window.$message?.error('保存失败:xxx'); window.$message?.warning('当前修改未保存,确认离开?'); window.$message?.info('未选择计划开始日期,已按今日为基准计算'); ``` --- ## 9. 接口、路由、权限 - 默认走 `src/service/request/index.ts`,不另造鉴权/加密/错误处理/token 刷新。 - 接口前缀、服务常量优先复用 `src/constants/service.ts`。 - 后端契约变化时同步检查 `src/service/api/*`、`src/typings/api/*`、相关页面、说明文档。 - 路由/菜单/权限改动时同步检查 `build/plugins/router.ts`、`src/router/routes/*`、`src/store/modules/route/*`、相关文档。 - 路由产物过期:改源配置 + `pnpm gen-route`,**不要把手工修补生成文件当常规方案**。 --- ## 10. 运行时字典 - 由 `src/store/modules/dict/index.ts` 管理,登录后通过 `/system/dict-data/frontend-cache` 初始化。**不要在页面重复直调字典接口。** - 字典编码常量收敛在 `src/constants/dict.ts`。**不要散落硬编码 `dictType`。** - **不要猜字典编码**:先从后端接口文档/字段契约/系统字典管理页确认真实 `dictType`,再写入常量。 - 常量加中文注释:对应业务字段 + 编码确认来源。 - 后端编码带历史命名痕迹(如 `rdms_product_direction`)时,前端常量名按真实业务语义命名,**不扩散历史误导**。 ### 字典使用方式 | 场景 | 组件/Hook | |---|---| | 表单下拉 | `src/components/custom/dict-select.vue` | | 普通文案回显 | `src/components/custom/dict-text.vue` | | 标签态回显 | `src/components/custom/dict-tag.vue`(标签颜色业务页自决) | | script setup / TSX 列格式化 / 复杂判断 | `src/hooks/business/dict.ts` 的 `useDict(dictCode)` | `useDict` 常用能力:`dictOptions`、`getItem`、`getLabel`、`getLabels`、`hasValue`。 `DictSelect` 默认只展示启用项;需包含禁用项显式 `:only-enabled="false"`。 ```vue ``` ```ts const { getLabel, getLabels } = useDict(RDMS_OBJECT_DIRECTION_DICT_CODE); const directionLabel = getLabel(row.directionCode); const directionLabels = getLabels(row.directionCodes, { separator: ',' }); ``` --- ## 11. 页面资源 & 菜单目录(三层不同概念) - `component` = 渲染哪个页面组件 - 菜单目录 = 挂在哪个业务目录、最终 URL - 页面资源 = 白名单中选择并回填组件信息 **不要混淆**:组件键 `view.system_dict` ≠ 必须挂 `/system/dict`,同一个组件允许在新业务目录下复用。 页面资源白名单的标准路径是**参考路径,不应反向覆盖菜单树已确定的最终 URL**。 菜单编辑器/页面资源选择逻辑改动时,保证"组件可解析、资源合法、最终 URL 由菜单树决定",**不要强绑标准路径与父目录前缀**。 --- ## 12. ID 类型铁律(强约束,必须严格执行) > 后端主键 ID / 用户 ID / 对象 ID / 雪花 ID / Long ID **一律按 `string` 接收和传递**。 **原因**:JS `number` 无法稳定承载 Long 精度;序列化精度丢失;`number/string` 键不一致 → 回显/筛选/映射/路由参数/对象上下文异常。 ### 落实范围(全部) `typings`、API 返回类型、表单 model、组件 props/emits、`ElSelect` 的 value、路由参数、查询参数、`Map` 键、筛选条件、store 状态 → **全部 `string` / `string[]`**。 ### 禁止写法 - ❌ `Number(id)` / `+id` / `parseInt(id)` / `parseFloat(id)` / `Math.floor(id)` - ❌ 任何"为了比较/传参/回填/提交而把 ID 转 number" ### 比较与映射 - ✅ `id === targetId` - ✅ `Map` / `Set` - ❌ 不混用 `number/string` 双口径 ### 后端契约风险(关键) - 后端暂返数值型 ID 时,**前端在 `typings` / API 适配层 / 进业务层前转 `string`**,不要按 `number` 扩散。 - **但如果后端把超 JS 安全整数的 Long 直接作为 JSON 数字返回,前端再 `String(number)` 只能得到"已经丢精度后的错误字符串"**。这种情况必须明确记为接口契约风险,不能误判为"已安全处理"。 - 最稳妥契约:**后端 Long ID 直接按字符串返回**;前端全链路按字符串。后端未改,前端也不得新增 `number` 口径 ID。 ### API 适配层兜底(操作约束) - 所有从后端接收的数值型 ID 字段,**必须**在 `src/service/api/*` 的 normalize/map 函数里显式 `String(rawId)` 一次——**不管后端返回 string、number、还是混合**。 - 业务层(views / store / 组件 / `Map` key / 路由参数)**只接收 string**,从不需要自己 `String()`。 - 与"后端是否已经全局 Long → String"**无关**: - 后端做了 → 双保险 - 后端没做但取值在 JS 安全整数内 → 单层防御也对(实际值不丢精度) - 后端没做且取值超安全整数 → 不安全,必须推后端改 - **不开"按取值范围豁免"的口子**:哪怕后端说"这个字段永远是两位数"(如 `infra_file_config.id`),前端照样 `String()`。否则后续会冒出"projectStatus 是 Long 但只有 0-99,也可以保留 number"等连锁例外,铁律字面被掏空。 ### 历史代码原则 不再新增 `number` 口径;当前任务触达相关链路时**顺手矫正**;不要继续复制历史写法。 --- ## 13. 代码约定 - 优先用别名导入(`@/...`、`~/...`),避免长相对路径。 - 与 TypeScript 严格模式兼容。 - 沿用 Vue SFC 风格:`script setup`、类型化 store、职责单一的小型 composable/helper。 - UI 沿用 `src/layouts` 和 `src/theme` 现有模式,不平行引入新设计体系。 - **注释克制**:只在代码本身不直观时补必要中文说明;不删原有有效注释;不写没信息量的注释。 - 中文内容用 UTF-8,自检显示;**不要用改成英文规避编码问题**。 - Node ESM 脚本:避免 `__filename`/`__dirname` 这类下划线悬挂命名。 - 批量异步并发优先 `Promise.all(...)`,不在循环里默认 `await`。 - 手写 `new Promise(...)` 用 block 写法,不要写成隐式返回的单表达式箭头函数。 - 函数若同时承担"判断 + 转换 + 组装 + 递归",拆 helper。 --- ## 14. 校验 ### 14.1 校验口径 | 任务类型 | 默认校验 | |---|---| | 前端页面/交互/样式 | `pnpm typecheck`,不主动跑测试 | | 需更严格静态检查 | 加 `pnpm lint` | | 涉及路由 | 加 `pnpm gen-route` | | 影响页面资源清单/菜单资源选择/页面白名单 | 加 `pnpm gen:page-resource-manifest` | ### 14.2 静态校验自查清单 - 调用链是否闭环?改动是否在正确分层? - 路由/菜单/权限标识/主题状态/资源注册 是否前后一致? - 改动范围是否控制在最小集合? - 文档/类型/接口封装/生成产物 是否需要同步更新? --- ## 15. 提交规范 - **`pre-commit` 执行 `pnpm typecheck && pnpm lint && git diff --exit-code`**:能跑 ≠ 能提交。 - `pnpm lint` 会跑 `eslint . --fix`:提交失败后检查是否有被自动修复但未重新暂存的文件。 - 推荐提交方式:`pnpm commit:zh`(交互选 type/scope/description)。 - 手动提交:`git commit -m "type(scope): 描述"`,参考 `docs/前端提交规范与示例.md`。 - `commit-msg` 钩子校验 Conventional Commits。 --- ## 16. 协作记忆(与本仓库用户共事) - 用户语言:**中文**(始终用中文回复)。 - **不主动跑 git 命令**(用户已强调)。 - 默认精简、结论先行。 - 工作树脏时不要回退无关变更。 - 改架构/权限/页面规范前先翻 `docs/`,避免与现有约定冲突。 - 改布局/主题时同时检查 `src/layouts/*` 与 `src/store/modules/theme/*`。 - 改路由/菜单时同时检查 `build/plugins/router.ts` 与 `src/router/routes/*`。 --- ## 17. 常用命令速查 ```bash pnpm typecheck # 最小静态校验 pnpm lint # eslint . --fix pnpm gen-route # 重新生成路由产物 pnpm gen:page-resource-manifest # 同步页面资源清单 pnpm commit:zh # 交互式提交(推荐) pnpm dev # dev server (9527) pnpm preview # preview server (9725) ``` --- ## 18. 业务对象状态颜色 - 集中文件:`src/constants/status-tag.ts` - 各业务域 `statusCode → ElTag type` 在此统一维护,**不要在各页面散落硬编码**。 - 已支持域:`projectExecution`、`projectTask`;预留:`project`、`product`、`requirement`、`workOrder`。 - helper:`getStatusTagType(domain, statusCode)`,未匹配回退 `'info'`。 - 业务模块写薄包装,例如 `getExecutionStatusTagType(code) = getStatusTagType('projectExecution', code)`。 - 新增对象域:在 `StatusDomain` 加枚举 + `statusTagTypeRegistry` 加对应 map;调用方写一个 wrapper 即可。 - 后端契约:未来若状态字典返颜色字段,调用方优先取后端值,缺失时回退 helper(前端兜底)。 --- ## 19. 防重复提交(两层联防,强约束) > 用户双击、键盘连按 Enter、`ElMessageBox.confirm` 的"确定"按钮无内置 loading 都会让同一写操作发多次。两层防御缺一不可。 ### 两层各自的职责 | 层 | 谁负责 | 行为 | |---|---|---| | 视觉层 | `business-form-dialog.vue` / `business-form-drawer.vue` | submit 触发后立即把"确认"按钮置 loading + disabled,挡住二次点击 | | 逻辑层(兜底) | `src/service/request/dedupe.ts`(已通过 `withDedupe` 包住 `request` 实例) | 写操作 pending 期内复用同一 Promise,不真正发出第二次请求 | ### 业务侧关注点 - **不要裸手写** `` 调接口;用 `business-form-dialog` / `business-form-drawer` 包;非要用裸 `ElButton` 时**必须**自行绑 `:loading` 并在 await 期间锁住。 - **`ElMessageBox.confirm` 的"确定"按钮没 loading 能力**——不要尝试改它,靠第二层兜底就够。 - **新接口默认享受去重**,调用方零改动;不要在 `src/service/api/*` 或页面层再造一套去重。 ### 去重生效边界 - 自动去重:`POST` / `PUT` / `DELETE` / `PATCH`。 - 不去重:`GET` / `HEAD` / `OPTIONS`(避免误伤分页 / 多 widget 并发查询);请求体为 `FormData` / `Blob`(上传场景)。 - 单接口逃生口:`request({ ..., dedupe: false })`——极少用,仅当业务真允许短时间内连发完全相同的写请求。 - 兜底超时 30s:保险丝,防止 Promise 永不 settle 时内存泄漏。 ### 指纹算法 `method 大写 | URL + 排序后的 params 序列化 | 稳定序列化的 body`。body 内对象按 key 排序、数组保序——保证调用顺序不同但参数等价的两次请求拿到同一指纹。 ### 何时回到本节查 - 新建写操作页面 → 视觉层用对组件、不裸 `ElButton` 调接口 - 新建写接口 → 不用管,默认兜底;只有明确"允许短时间连发"才传 `dedupe: false` - 新建上传 / 下载流程 → FormData / Blob 天然不在去重范围,按原方式写即可 - 用户报"双击双发"复现不出来 → 检查目标按钮是否走了 `business-form-dialog`;若用的是 `ElMessageBox.confirm`,靠第二层就该挡住 --- ## 20. 我生成文档的输出格式(强约束) - **superpowers 工作流(`docs/superpowers/plans/`、`docs/superpowers/specs/`)下输出的文档继续用 `.md`**——工作流以 markdown 为前提。 - **其他**我生成的文档(设计方案、复盘、规约、技术经验沉淀等)**默认用 `.html`**,沿用 `docs/debt/` 现有 HTML 文档(参考 `token-刷新机制对齐分析.html`、`技术负债台账.html`)的样式骨架: - 单文件、内联 CSS - `max-width: 980px` 居中容器、`padding: 32px 28px 80px` - 14px / `line-height: 1.7`、`PingFang SC` / `Microsoft YaHei` 中文字体优先 - 模块化区块:`section` + 编号 h2、`card`、`table.cmp`、`pre`、`tag-ok/warn/bad/crit` - 配色用 `--bg / --panel / --border / --text / --primary` 一套 CSS 变量 - **`README.md`** 是目录索引约定文件,**保持 `.md`**(不强行 `.html`)。 - **已有 `.md` 文档不主动改写**,等用户明确要求再转。