Files
cn-rdms-web/AGENTS.md
hongawen 7a4d831c10 feat(file): 优化文件上传处理和ID管理规范
- 新增 buildFileProxyUrl 函数构建永久代理路径,避免富文本图片链接过期
- 重构 uploadFile 函数,统一将后端返回的数值型 ID 转换为字符串
- 在业务富文本编辑器中使用永久代理路径替换临时签名 URL
- 完善 API 适配层 ID 规范,确保所有 ID 字段统一转换为字符串类型
- 移除废弃的编辑器相关路由和组件
- 更新构建代理配置以支持富文本图片直连访问
- 删除冗余的类型定义和依赖包
2026-05-15 10:06:51 +08:00

322 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AGENTS.md
本文件为后续编码代理提供 `cn-rdms-web` 的稳定仓库上下文。
在修改代码前请先阅读。
## 适用范围
本说明适用于以 `C:\code\gitea\rdms\cn-rdms-web` 为根目录的整个仓库。
描述仓库现状时,以当前代码、当前配置、当前文档中可直接验证的事实为准;除非用户明确要求,不引入历史实现、过渡方案或猜测来解释当前行为。
默认回答保持精简,优先给结论、改动点、验证方式和必要风险;如果用户只要求分析、审阅或方案,就停留在分析层,不主动扩展到实现层。
分析、解释、方案类回答优先用业务和逻辑语言把结构、差异与结论说清楚,不要大段贴源码、罗列 `file:line` 或把实现细节当解释;只有用户明确要求看代码、或某行确实是讨论焦点的关键佐证时,才贴最小必要的代码片段。
## 交互与执行原则
- 进入实施阶段前,先说明目标、涉及模块、预计改动点和验证方式。
- 先定义验证方式,再执行修改和校验;如果没有实际运行命令,需要明确说明只做了静态检查。
- 只在当前任务需要的最小范围内改动,避免把无关重构混入同一次修改。
## 项目概览
- 应用类型RDMS 系统的 Vue 3 后台前端
- 包管理器:`pnpm`
- 运行时与工具链Vite 7、TypeScript、Pinia、Element Plus、UnoCSS
- 工作区包位于 `packages/*`,通过 `@sa/*` 引用
- Node 版本要求:`>=20.19.0`
- pnpm 版本要求:`>=8.7.0`
## 项目骨架主线
当前项目不是边写边拼的页面集合,而是已经形成闭环的后台前端骨架。后续改动优先顺着这几条主线做,而不是平行再起一套:
- 路由来源统一:页面文件与自定义路由作为源头,经 `elegant-router` 生成路由产物,再由 `build/plugins/router.ts` 集中补齐 `meta`
- 权限入口统一:常量路由和权限路由明确分流,`route store` 负责初始化、菜单生成、缓存路由和面包屑
- 请求入口统一:所有业务请求默认走 `src/service/request/index.ts`
- 页面套路统一:列表页通常拆为搜索区、表格区、操作弹层/抽屉和 `modules/*` 子组件
- 衍生资产统一:页面资源白名单从路由结构生成,不手工维护第二份页面清单
## 环境与构建说明
- Vite 路径别名:`@` -> `src`
- Vite 路径别名:`~` -> 仓库根目录
- 开发服务器默认端口:`9527`
- 预览服务器默认端口:`9725`
- 环境文件包括 `.env``.env.dev``.env.prod`
## 关键目录与文件
- `src/views`:业务页面
- `src/components`:共享组件
- `src/layouts`:应用壳、头部、侧栏、菜单、标签页、主题抽屉
- `src/store/modules`Pinia 模块,包含 app、auth、route、tab、theme
- `src/service/api`:接口封装与请求参数拼装
- `src/service/request`统一请求实例、鉴权头、加密、错误处理、token 刷新
- `src/router/routes`:自定义路由定义
- `src/router/elegant`:自动生成的路由产物
- `src/theme/settings.ts`:默认主题与布局设置
- `build/plugins/router.ts`elegant-router 配置与路由元信息生成逻辑
- `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/`:当前工作上下文的一部分,做架构级、权限级、页面规范级改动前优先查阅
## 生成文件
除非有非常明确的理由并且同步维护生成流程,否则不要手工修改生成文件。
- `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`
## 路由与导航开发口径
- 新增业务页面时,优先通过页面文件与 `build/plugins/router.ts` 补齐路由,不要手工在多个位置重复注册同一页面。
- 路由 `meta` 的中心落点是 `build/plugins/router.ts`;新增业务页的 `icon``order``roles``keepAlive` 优先在那里集中维护。
- 当前代码链路仍保留 `i18nKey` 兼容字段,但它是兼容保留项,不是新增业务页面必须补齐的默认要求。
- `meta.constant = true` 的路由属于常量路由;其余默认属于权限路由。
- 常量路由维护入口优先是 `build/plugins/router.ts``src/router/routes/custom-routes.ts`,不要把常量路由散落到业务页面逻辑里。
- 菜单图标约定属于路由契约的一部分:`meta.icon` 表示 Iconify 图标,`meta.localIcon` 表示本地 SVG 图标;不要混用字段语义。
### 对象上下文业务域入口页口径
- `product``project` 这类对象上下文业务域的入口页,按 `docs/rdms/rdms-object-context-navigation-implementation-notes.md` 口径,本来就是“先进入业务域入口页,再选择对象建立上下文”;不要把“入口页是可点击菜单”误判成配置错误。
- 对象上下文业务域的“入口态”页面,例如 `product_list -> /product/list -> view.product_list`,可以作为左侧一级入口菜单实际命中的页面;这不等于已经进入对象上下文态。
- 不要为了修复“点击入口页后只剩内容页、布局壳消失”的问题,直接要求把对象域入口菜单从“菜单”改成“目录”。先检查当前是不是动态权限路由模式,以及后端 `get-user-routes` 返回是否缺少业务域根路由。
-`VITE_AUTH_ROUTE_MODE=dynamic` 下,如果后端只返回对象域入口叶子页,而没有返回本地静态骨架中的业务域根路由,例如缺少 `product -> layout.base`、只返回 `product_list -> view.product_list`,前端必须在动态路由归一化阶段补回本地业务域骨架,而不是让入口页直接裸挂成顶层 `view.*` 路由。
- 对象上下文业务域的稳定来源仍应是本地路由骨架:业务域根路由负责 `layout.base`,入口页负责对象列表或对象选择,真正的对象功能页继续挂在该业务域下。动态路由兼容逻辑只能做“补骨架”和“对齐入口”,不要反过来推翻这层结构。
- 后续新增新的对象上下文业务域时,至少同步检查这几处是否闭环:本地静态路由骨架、`src/constants/object-context.ts` 中的 `domainKey / entryRouteKey / entryRoutePath / fallbackDefaultRouteKey`、动态路由归一化逻辑、对象上下文 store 与头部菜单切换逻辑。
## 分层职责约束
### `src/views`
- 页面层负责页面编排、交互状态、表单行为和对 store/service 的组合调用。
- 不要在页面组件里散落 URL 拼接、token 注入、统一错误提示或权限路由推导逻辑。
### `src/components`
- 共享组件负责可复用 UI 或局部业务部件。
- 不要把只服务于单个页面的复杂流程长期堆在公共组件目录中。
### `src/service/api`
- API 层负责接口封装、请求参数归一化、查询字符串拼装和返回类型对齐。
- 不要在 `views``store``components` 中重复手写同一接口地址和参数序列化逻辑。
### `src/service/request`
- 请求层负责统一请求实例、鉴权头、接口加密、成功码判定、token 刷新和通用错误处理。
- 除非任务明确需要,不要平行引入新的 `axios`/`fetch` 调用链绕开现有封装。
### `src/store/modules`
- Store 负责跨页面共享状态,例如认证、路由、标签页、主题、布局和全局 UI 状态。
- 临时性的页面局部状态优先留在页面组件或 composable 中,不要无边界堆进全局 store。
### `src/router` 与 `build/plugins/router.ts`
- 路由、菜单、权限标识、首页配置和路由元信息优先沿用当前 elegant-router 与 route store 链路。
- 不要只在页面里临时写条件分支来替代正式的路由、菜单或权限配置。
### `src/layouts` 与 `src/theme`
- 布局壳和主题设置是全局行为源头相关改动要同时检查布局组件、theme store 和默认设置。
- 不要在业务页面里复制一套平行的布局状态或主题状态。
## 业务页面开发风格
- 页面组件保持“编排层薄”。页面文件主要负责搜索参数、表格 hook、列定义、弹层开关、接口调用编排不把大量表单细节和重复交互直接堆在页面根组件里。
- 列表页优先拆出同目录下的 `modules/*` 子组件,例如搜索组件、操作弹层、详情抽屉、资源面板等。
- 系统管理下现有 `user``role``menu``dict` 页面可以作为参考实现,新增同类页面优先沿用它们的拆分方式。
- 新增或触达列表页搜索组件时,必须按 `docs/table-search-fields-usage.md` 使用 `src/components/custom/table-search-fields.vue``fields` 声明式配置,不得手写 `ElRow / ElCol / ElFormItem` 搜索区骨架;只有字段存在复杂联动、自定义插槽或 `TableSearchFields` 明确无法承载时,才允许退回 `src/components/custom/table-search-panel.vue`,并需要在实施说明中写明原因。搜索模块本身应尽量只接收 `model` 和必要选项,只向外发出 `reset` / `search`,不直接承载列表请求逻辑。
- 列表能力优先复用 `src/hooks/common/table.ts` 中的 `useUIPaginatedTable``useTableOperate``defaultTransform`
- 表单能力优先复用 `src/hooks/common/form.ts` 中的 `useForm``useFormRules`
- 当前项目的真实业务口径是“内网中文优先”。新增业务页不必为了形式强行补全国际化键;但如果是在已有大量 `$t(...)` 的页面或模块内继续开发,优先保持该局部代码风格一致,不要半页中文直写、半页国际化混用。
## 表格、搜索区与操作列约束
- 搜索区按钮组必须固定在第一行最后一个位置;存在折叠项时,按钮顺序保持为“展开/收起 -> 重置 -> 查询”。这是 `TableSearchFields` 的布局契约,不允许因为查询条件不足、展开/收起或响应式样式把按钮提前到中间位置或挤到后续行。
- 不要在每个页面重新拼一套搜索区骨架;常规查询条件必须使用 `TableSearchFields`,通过 `columns` 控制每行格子数和折叠阈值。`columns` 表示首行总格数,其中最后 1 格永远留给按钮区;字段不足 `columns - 1` 时由公共组件补空占位,字段超过时剩余字段进入展开区。类似项目管理入口页这类 4 个查询条件的场景,必须使用 `:columns="4"`形成“3 个条件 + 按钮区”的首行布局。
- 表格操作列优先复用 `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` 私有样式。历史页面已有私有类时不强制专项回改,当前任务触达相关页面再按公共类收敛。
- 表格、按钮、弹层、表单的尺寸和间距标准优先由 `src/styles/scss/element-plus.scss` 和公共组件承接,不在业务页面散落写新的局部尺寸作为事实标准。
## 表单与弹层约束
- 新增、编辑能力优先沿用 `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`
- `dialog` 宽度优先按纯表单字段数分三档:`<= 6` 个字段用 `sm`,默认单列,目标宽度 `520px``7 ~ 14` 个字段用 `md`,默认双列,目标宽度 `720px``> 14` 个字段用 `lg`,仍以双列为主,目标宽度 `960px`。宽度只做响应式收缩,实际宽度不超过 `calc(100vw - 32px)`;不因为单个 `textarea` 自动升档,也不做列数响应式折叠。
- 常规 CRUD 表单优先使用 `label-position="top"``ElRow + ElCol` 双列布局、`gutter=16`;普通字段优先 `span=12`,长文本或重量级字段优先 `span=24`。如果整体字段数 `<= 6`,默认按单列表单理解。
- 当纯表单 `dialog` 因字段数 `<= 6` 归入 `sm` 时,不能只改 `preset`;字段布局也要同步落到单列,常规 `ElCol` 应使用 `span=24`,除非该弹框已经被明确判定为复合内容特例。
- 左右分栏、表单 + 表格、表单 + 树、关系编辑器、时间线、大段说明区这类复合内容 `dialog`,不强行按字段数归类;可按内容复杂度单独评估使用 `md``lg` 或更宽值,但只有在无法合理归入“纯表单三档”时才允许特例。
- 禁止用页面级宽范围样式直接覆盖整页 `.business-form-dialog` 来统一放大弹框;如确实需要特殊宽度,只能精确作用于目标弹框,且不能误伤同页面其他 `dialog`
- 底部按钮顺序固定为“取消 -> 确认”,并保持右对齐。
- 单选组和开关类字段优先复用仓库既有样式钩子,例如 `business-form-radio-group``business-form-switch-field`
- 权限控制按钮默认采用“无权限不渲染”口径,不要把纯权限不足的入口做成禁用态再展示给用户;只有业务状态暂时不可操作、但仍需让用户感知入口存在时,才允许保留禁用态。
## 接口、路由与权限约束
- 默认沿用 `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`,不要把手工修补生成文件当成常规方案。
## 防重复提交(两层联防)
用户快速双击、键盘连按 Enter、`ElMessageBox.confirm` 的"确定"按钮内置无 loading 等场景,都可能让同一写操作发出多次。仓库采用两层防御,新增写操作功能时按顺序检查:
### 第一层:业务按钮的 loading 锁(视觉防御)
- 新增、编辑入口优先使用 `src/components/custom/business-form-dialog.vue``src/components/custom/business-form-drawer.vue`,它们在 `submit` 流程内 await 接口期间会自动将"确认"按钮置为 `loading` + `disabled`
- 不要裸手写 `<ElButton @click="submit">` 直接调接口;若必须使用裸 `ElButton`,需要自行绑定 `:loading` 并在 await 接口期间锁住按钮。
- 删除二次确认使用 `ElMessageBox.confirm` 时,其内部"确定"按钮没有 loading 能力,必须依赖第二层兜底,不要尝试改造 confirm 的内部按钮。
### 第二层:请求层全局去重(逻辑兜底)
- 入口:`src/service/request/dedupe.ts` 提供 `withDedupe`,已在 `src/service/request/index.ts` 包住统一的 `request` 实例;`demoRequest` 未启用。
- 指纹:`method + 完整 URL + 排序后的 params + 稳定序列化的 body`body 内对象按 key 排序,数组保序。
- 行为:写操作(`POST` / `PUT` / `DELETE` / `PATCH`)在第一次请求 pending 期内,若再次发起指纹相同的请求,自动复用第一次的 Promise不发出第二次实际请求调用方两次拿到完全相同的返回对象。
- 跳过条件(即不去重,按原逻辑发出):`GET` / `HEAD` / `OPTIONS`,请求体为 `FormData``Blob`(上传场景),调用方显式传 `{ dedupe: false }`
- 业务调用方零感知:新增接口默认即享受兜底,不需要在 `src/service/api/*` 或页面层做任何改动。
- 极少数业务确实允许短时间内并发提交完全相同的写请求时,在调用处显式传 `request({ ..., dedupe: false })` 单接口关闭。
- 兜底超时 30 秒:极端情况下若某次 Promise 未 settlepending 条目过期后下一次相同请求视为新请求,避免内存泄漏。
### 设计责任划分
- 视觉层负责"按下立刻锁住按钮"的用户感知;逻辑层负责"即使锁失败也只发一次"的实际接口保护。
- 不要因为有第二层兜底就省略第一层 loading 锁:用户没有视觉反馈会再次点击;也不要试图在业务页面再造一套请求去重逻辑。
## 运行时字典使用口径
- 运行时字典统一由 `src/store/modules/dict/index.ts` 管理,登录后通过 `/system/dict-data/frontend-cache` 初始化;不要在业务页面重复直调字典接口。
- 字典编码常量优先收敛在 `src/constants/dict.ts`,不要在页面里散落硬编码 `dictType`
- 不要猜测字典编码。新增某个业务字段对应的字典前,先从“后端接口文档、后端字段契约、系统字典管理页”确认真实 `dictType`,再写入 `src/constants/dict.ts`
- `src/constants/dict.ts` 中每个导出的字典常量,尽量补中文注释,至少说明两件事:对应哪个业务字段、这个编码是从哪里确认出来的。
- 如果后端实际 `dictType` 带有历史命名痕迹,例如当前对象方向仍叫 `rdms_product_direction`,前端常量名优先按真实业务语义命名,不要继续把历史误导扩散到页面代码里。
- 表单下拉优先使用 `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)`,常用能力包括 `dictOptions``getItem``getLabel``getLabels``hasValue`
- `DictSelect` 默认只展示启用项;确实需要包含禁用项时,显式传 `:only-enabled="false"`
简单示例:
```vue
<DictSelect v-model="form.directionCode" :dict-code="RDMS_OBJECT_DIRECTION_DICT_CODE" />
<DictText :dict-code="RDMS_OBJECT_DIRECTION_DICT_CODE" :value="row.directionCode" />
<DictTag :dict-code="SYSTEM_USER_COMPANY_DICT_CODE" :value="row.companyCode" type="info" />
```
```ts
const { getLabel, getLabels } = useDict(RDMS_OBJECT_DIRECTION_DICT_CODE);
const directionLabel = getLabel(row.directionCode);
const directionLabels = getLabels(row.directionCodes, { separator: '' });
```
确认字典编码的典型方式:
- 后端接口文档直接写明字段使用哪个字典,例如产品 `directionCode -> rdms_product_direction`;如果该编码只是历史命名,前端常量名仍按通用业务语义命名。
- 当前系统已有页面或接口已经稳定使用某个字典,例如用户所属公司 `company -> system_user_company`
- 如果以上两种都没有,就先让后端或业务明确 `dictType`,不要前端自己命名。
## 业务对象状态颜色集中口径
- 各业务域(产品、项目、需求、任务、执行、工单等)的 `statusCode -> ElTag type` 集中维护在 `src/constants/status-tag.ts`,不要在各业务页面或模块内散落硬编码同一份映射。
- 通用入口是 `getStatusTagType(domain, statusCode)`,未匹配的 `statusCode` 默认回退到 `'info'`
- 业务模块按域写薄包装暴露给页面调用,例如 `getExecutionStatusTagType(code)` 内部调用 `getStatusTagType('projectExecution', code)`,避免页面直接耦合到 domain 字符串。
- 新增对象域时同步两处:`StatusDomain` 增加枚举值;`statusTagTypeRegistry` 添加对应 `statusCode -> StatusTagType` 映射。
- 后端契约:未来若状态字典开始返回颜色字段,调用方应优先使用后端值,缺失时再回退到 `getStatusTagType` 的前端兜底映射,不要直接绕开集中配置另写一份。
## 页面资源与菜单目录约束
- 页面组件键、页面资源、菜单目录是三层不同概念,不要把它们当成同一个值。
- `component` 决定“渲染哪个页面组件”;菜单目录决定“挂在哪个业务目录下”和最终 URL页面资源主要用于从白名单中选择并回填组件信息。
- 不要因为组件键是 `view.system_dict`,就推导它只能挂在 `/system/dict`;同一个页面组件允许挂在新的业务目录下复用。
- 页面资源白名单中的标准路径是参考路径,不应反向覆盖当前菜单树已经确定的最终 URL。
- 涉及菜单编辑器或页面资源选择逻辑时,优先保证“组件可解析、资源合法、最终 URL 由菜单树决定”,不要强绑页面资源标准路径和父级目录前缀。
## 代码约定
- 优先使用现有别名导入(`@/...``~/...`),避免过长的相对路径。
- 保持与 TypeScript 严格模式兼容。
- 后端返回的主键 ID、用户 ID、对象 ID、雪花 ID、Long ID 等,一律优先按 `string` 在前端接收和传递,不要默认写成 `number`
- 这是强约束不是建议项。原因包括JavaScript `number` 无法稳定承载长整型精度、接口序列化后可能出现精度丢失、运行时还容易出现 `number/string` 键不一致,最终导致回显、筛选、映射、路由参数、对象上下文等逻辑异常。
- 这条约束要落实到所有层:`typings`、API 返回类型、页面表单 `model`、组件 `props` / `emits``ElSelect``value`、路由参数、查询参数、`Map` 键、筛选条件、store 状态,一律优先使用 `string` / `string[]`
- 明确禁止把 ID 当成普通数值处理。禁止写法包括但不限于:`Number(id)``+id``parseInt(id)``parseFloat(id)``Math.floor(id)`,以及任何“为了比较、传参、回填、提交而把 ID 转成 number”的做法。
- 比较、映射、筛选 ID 时,默认按字符串语义处理,例如 `id === targetId``Map<string, T>``Set<string>`,不要混用 `number/string` 双口径。
- 如果后端当前接口暂时还返回数值型 ID前端也必须尽可能在 `typings`、API 适配层或进入业务层前转成 `string`,不要把 ID 按 `number` 扩散到页面、store 和组件里。
- 但要注意:如果后端把超出 JS 安全整数范围的 Long 直接作为 JSON 数字返回,前端在业务层再 `String(number)` 只能得到“已经丢精度后的错误字符串”。这种情况必须明确记为接口契约风险,不要误判为“前端已安全处理”。
- 因此,新增或改造接口时,最稳妥的契约仍然是:后端长整型 ID 直接按字符串返回;前端全链路按字符串接收和传递。若后端暂未改,前端侧也不得新增 `number` 口径 ID 用法。
- API 适配层兜底(实操约束):所有从后端接收的数值型 ID 字段(不论后端实际返回 `string``number` 或两者混合),都必须在 `src/service/api/*` 的 normalize 或 map 函数中显式调用 `String(rawId)` 归一一次;前端业务层(`views``store`、组件、`Map` 键、路由参数)只接收 `string` 形态,永远不需要自己 `String()`。这条与后端是否做了 Long → String 全局序列化无关——后端做了是双保险,没做且字段取值始终在 JS 安全整数内(例如 `infra_file_config.id` 永远是两位数)也是合理选择,前端 normalize 已经把口径收死,业务层无感。但这条不开按字段取值范围豁免的口子:前端 normalize 是无差别的,任何 ID 都要 `String()`,不要按某个字段当前取值大小决定要不要走 normalize避免后续逐步污染仓库的 ID 纪律。
- 对仓库中的历史代码,原则是“不再新增 number 口径 ID当前任务触达相关链路时优先顺手矫正”不要继续复制历史写法。
- 遵循仓库现有的 Vue SFC 风格:`script setup`、类型化 store、职责单一的小型 composable/helper。
- 修改界面时优先延续 `src/layouts``src/theme` 中已有的 UI 模式,不要平行引入另一套设计体系。
- 注释保持克制,只在代码本身不够直观时补充必要说明。
## 注释与编码
- 新增或修改代码时,关键分支、关键约束和非直观实现可以补充简洁中文注释。
- 不要为了省事删除原有有效注释,也不要添加没有信息量的注释。
- 写入中文内容时保持 UTF-8 编码,并自行确认显示正常;不要用改成英文来规避编码问题。
## 校验建议
对有实际影响的代码改动,优先执行:
- 对前端页面、交互、样式类任务,除非用户明确要求新增测试、运行测试,或当前任务本身就是修测试/补测试,否则默认不补前端测试,也不主动跑前端测试命令。
- 上述前端任务默认只做静态校验;最小校验口径是 `pnpm typecheck`。如果需要更严格的静态检查,再补 `pnpm lint`
- `pnpm typecheck`
- `pnpm lint`
如果改动涉及路由,额外执行:
- `pnpm gen-route`
如果改动影响页面资源清单、菜单资源选择或页面白名单,额外执行:
- `pnpm gen:page-resource-manifest`
静态校验时,至少自查以下几点:
- 调用链是否闭环,改动是否落在正确的分层位置
- 路由、菜单、权限标识、主题状态或资源注册是否前后一致
- 改动范围是否控制在当前任务所需的最小集合内
- 文档、类型定义、接口封装或生成产物是否需要同步更新
## 提交与脚本约束
- `pre-commit` 会执行 `pnpm typecheck && pnpm lint && git diff --exit-code`,因此“代码能跑”不等于“可以提交”。
- `pnpm lint` 实际会执行 `eslint . --fix`;提交失败后要检查是否有被自动修复但尚未重新暂存的文件。
- 提交规范说明以 `docs/前端提交规范与示例.md` 为准;最稳妥的提交方式是执行 `pnpm commit:zh`,按交互选择 `type``scope``description`
- `commit-msg` 钩子会校验 Conventional Commits推荐使用 `pnpm commit:zh` 生成提交信息。
- 如果手动提交,执行 `git commit -m "type(scope): 描述"`,并确保 `type``scope`、描述写法与 `docs/前端提交规范与示例.md` 保持一致。
- 提交信息基础格式遵循 `type(scope): 描述`
- 写 Node ESM 脚本时,避免沿用 `__filename``__dirname` 这类下划线悬挂命名。
- 能并发的批量异步任务优先 `Promise.all(...)`,不要默认在循环体里直接 `await`
- 手写 `new Promise(...)` 时优先使用 block 写法,不要把 executor 写成隐式返回值的单表达式箭头函数。
- 一个函数如果开始同时承担“判断 + 转换 + 组装 + 递归”,优先拆 helper避免把复杂度堆到单个函数里。
## 代理工作说明
- 除非用户明确要求,否则不要主动执行任何 git 操作,包括但不限于 `git status``git diff``git add``git commit``git restore``git reset``git checkout`
- 如果任务需要识别用户已有改动,优先通过当前文件内容和直接读取文件来判断;只有用户明确要求查看 git 状态时,才执行对应 git 命令。
- 在工作树不干净时,不要回退与当前任务无关的变更。
- 修改布局或主题行为时,同时检查 `src/layouts/*``src/store/modules/theme/*`,因为相关逻辑分散在界面层和状态层。
- 修改路由或菜单时,同时检查 `build/plugins/router.ts``src/router/routes/*`
- 做架构级、权限级或页面规范级修改前,优先查阅 `docs/` 中现有说明,避免与当前文档约定冲突。