623e6393d5
遮罩底色被 Naive 写死在 css-render 里(Modal / Drawer 主题没有 mask token, themeOverrides 改不到),只能上 CSS;加 backdrop-filter 后遮罩后面的页面糊掉、 弹层本体仍是纯白,NModal / NDrawer / useDialog 共用 .n-modal-mask 一条规则覆盖。 折行的根因是格子宽度写死(半宽 264px / span 8 192px / 定宽 160px),与窗口宽度无关: 能压短的压短;「文件名预览」那处是 .n-form-item-blank 属于 flex 行、把提示挤成 3 行, 改成包一层块级容器;材料预览失败的占位在 108px 小格里曾折 3 行且被裁掉。 约定写进 frontend-pc/AGENTS.md 第 11 条与 code-css skill;实测与刻意不改的清单见 docs/交接摘要.md 的 §一·四十二。
115 lines
8.1 KiB
Markdown
115 lines
8.1 KiB
Markdown
# AGENTS.md
|
||
|
||
面向 AI 编码助手的项目指南(PC 管理端)。规范以 `.agents/` 为唯一来源。
|
||
|
||
> ⚠️ **2026-10 已重搭**:从 **Gi Admin Pro + Arco Design Vue** 换成 **纯 Vue 3 + Naive UI 2.42**。
|
||
> 上一版基于 Gi/Arco 的 skills 已全部重写 —— **不要再按 Arco 的组件名(`a-table` / `a-form`)、
|
||
> `v-hasPerm`、auto-import、Stylelint 去写代码**。
|
||
|
||
## 常驻规则(Agent 行为准则)
|
||
|
||
> 源文件:`.agents/rules/agent.md`(修改后请同步本节)
|
||
|
||
- 始终使用**简体中文**与用户沟通。
|
||
- 未经用户明确要求,**不要**创建说明文档、执行 git commit/push、修改 git 配置。
|
||
- 改动范围最小化;优先复用项目已有抽象,禁止凭空引入新架构。
|
||
- 改代码前先阅读项目既有实现;任务完成后视情况运行 lint / typecheck。
|
||
- **按需读取** skills,不要预加载全部。
|
||
|
||
**编码前按任务加载 skill:**
|
||
|
||
| 任务 | 技能 |
|
||
|------|------|
|
||
| 写/改 `.vue` | `.agents/skills/code-vue/SKILL.md` |
|
||
| 写/改样式 | `.agents/skills/code-css/SKILL.md` |
|
||
| 写/改 `.ts` / composable | `.agents/skills/code-ts/SKILL.md` |
|
||
| 项目约定、Naive UI 组件与目录 | `.agents/skills/project-guide/SKILL.md` |
|
||
| CRUD 列表/表单 | `.agents/skills/project-crud/SKILL.md` |
|
||
| 接口调用(`src/apis/xt/`) | `.agents/skills/project-api/SKILL.md` |
|
||
| 提交代码(git commit) | `.agents/skills/git-commit/SKILL.md` |
|
||
|
||
## 快速入口
|
||
|
||
```bash
|
||
pnpm dev # 开发(/api 代理到 http://localhost:8080,没有 Mock)
|
||
pnpm build # 生产构建
|
||
pnpm typecheck # vue-tsc(**当前 0 错**,有报错就是新引入的)
|
||
pnpm lint # ESLint(@antfu/eslint-config)
|
||
```
|
||
|
||
- 路径别名:`@` → `src/`
|
||
- **开发没有 Mock**:`pnpm dev` 把 `/api` 代理到 `http://localhost:8080`
|
||
(见 `vite.config.ts` 的 `server.proxy`)→ **后端不起着就没有数据**
|
||
- 接口模块在 `src/apis/xt/`(按业务域分文件),统一从 `@/apis/xt` 或 `@/apis/xt/<域>` 引
|
||
- 演示账号:`admin / Admin@123` + 租户码 `demo`(⚠️ **不是** Gi 模板 README 里的 `admin/123456`)
|
||
|
||
## 十条最容易写错的约定
|
||
|
||
1. **没有 auto-import**:naive-ui 组件、Vue API、工具函数**全部显式 `import`**,
|
||
标签统一写 **PascalCase**(`<NButton>`)。
|
||
⚠️ 漏 import 是**静默故障**:写小写 `<n-button>` 而没 import 时,Vue 把它当未知自定义元素渲染,
|
||
**不报错、typecheck/lint 全绿,只是样式整块丢掉**(2026-10 实测踩到 4 个文件 6 处)。
|
||
`python scripts/check-wiring.py` 第 ⑧ 项会静态核对这件事。
|
||
2. **权限判断**:`useUserStore().has('码')` / `hasAny(...)`(**没有 `v-hasPerm` 指令**);
|
||
权限码字符串必须与后端 `PermissionDef` **逐字一致**(`progress:viewAll` 是驼峰)。
|
||
3. **状态色**:`n-tag` 的 `type` 必须过 **`@/utils/tag.ts`** 的 `tagOf()` ——
|
||
`apis/xt/*.ts` 里的展示映射仍是 Arco 色词(`green`/`arcoblue`/`gold`),
|
||
直接透传给 `n-tag` 会**静默变灰**且不报错。
|
||
4. **图标**:用 `@/icons/AppIcon.vue` + 图标名;图标数据由
|
||
`scripts/extract-pc-icons.py` 生成到 `src/icons/icon-data.ts`(37 个),
|
||
缺图标是**空占位、不报错** → **图标集升级后要重跑该脚本**。
|
||
5. **`n-tab-pane` 的标签用 `#tab` 插槽,不要用 `tab="文字"` 属性** ——
|
||
naive-ui 2.45.3 的属性写法只渲染出一个空注释节点,**整排 tab 一个字都不显示**(不报错)。
|
||
6. **卡片式版面**:白卡片靠全局的 `--xt-bg` 灰底浮起来;卡片外观(描边/阴影/圆角/卡片头色带)
|
||
由 `src/styles/index.scss` 的 `.n-card.n-card` 统一给,**页面不要再自己写**;
|
||
卡片内部分区用 `.section-title` + 浅灰小格子,**不要卡里套卡**。
|
||
工具条控件同一行尺寸要一致(统一默认尺寸,别混 `size="small"`)。
|
||
7. **品牌标**:几何标在 `src/assets/images/logo.svg`(**不要换回文字字形标** —— 形状的出处与
|
||
构造参数写在该文件注释里);页签用 `public/favicon.svg`(加粗版),改一个要同步另一个,
|
||
外加侧栏/登录页的引用尺寸与 `docs/prototype/admin-web.html` 的 `.mark`。
|
||
⚠️ **SVG 的注释里不能出现连续两个减号**(XML 规范禁止):会让整个文件 not well-formed,
|
||
表现是图片裂开、**控制台一句话都不报**,typecheck/lint 也全绿。
|
||
`python scripts/check-wiring.py` 第 ⑨ 项会核对这件事。
|
||
8. **详情用抽屉,不要为详情新开路由**:`NDrawer` + `NDrawerContent` + `NDescriptions`,
|
||
模板抄 `src/views/audit/AuditDetailDrawer.vue`。列表页的"详情"入口是操作列里的一个
|
||
`text` 按钮(`size="small"`,见第 6 条的行内按钮约定)。
|
||
⚠️ 抽屉打开时该调哪个接口要看清楚:**列表接口可能刻意不返回某个字段**
|
||
(例:`/tenants` 的 `contactPhone` 恒为 `null`,脱敏值只有 `/tenants/{id}` 给),
|
||
拿行数据凑详情会显示成"没有联系电话"。
|
||
9. **页面内的页签用工具条上的分段按钮,不要用 `NTabs` / `NTabPane`**:
|
||
① `NTabPane` 的内容要额外 flex 化才能维持 `.page-card` 的高度链,
|
||
否则表格高度算成 0、**一行数据都看不见**(本仓踩过);
|
||
② 见第 5 条,naive 2.45.3 的 `tab="..."` 属性写法本身也是坏的。
|
||
两个按钮做的分段控件在视觉上就是页签,且不动高度链。
|
||
多个页签各自的 `list` / `total` / `pageNum` / `loading` 要分开维护,切换时重置到第 1 页。
|
||
10. **`NFormItem` 一旦用了 `#feedback` 插槽,校验错误就被完全遮住** ——
|
||
naive 的渲染是 `children || feedback ? feedback : renderExplains`(2.45.3 的 `FormItem.mjs`),
|
||
插槽存在时 `renderExplains` **永不渲染**,用户看到的是「红框 + 一句无关的正常提示」,
|
||
现象上等价于「点了保存没反应」。所以**凡是有 `#feedback` 的表单,`validate()` 失败必须自己弹**:
|
||
|
||
```ts
|
||
} catch (errors) {
|
||
message.error(firstError(errors) ?? '请检查表单填写')
|
||
return
|
||
}
|
||
```
|
||
|
||
⚠️ `firstError` **必须摊平后再取**:naive 的 `Form.validate()` reject 的是**嵌套数组**
|
||
(`Form.mjs` 把每层 `result.errors` 整个 push 进去),`errors[0].message` 恒为 `undefined`,
|
||
兜底文案会把每条错误都吞成「请检查表单填写」。写法抄 `src/views/tenant/TenantEditModal.vue`。
|
||
⚠️ 这个插槽**不接收任何参数**(内部用 `resolveWrappedSlot(slot, wrapper)` = `wrapper(slot())`),
|
||
所以**没有**「`#feedback="{ status }"` 里顺手渲染错误」这种写法 —— 想内联显示错误只能给
|
||
`NFormItem` 的 `feedback` **prop** 传渲染函数,那需要自写包装组件,别顺手试。
|
||
11. **提示(`#feedback` / `__hint`)要在自己的格子里单行放下**:放不下就**改短**,或把这一项改成
|
||
`:span="24"`。格子的**可用宽度是写死的**(弹窗宽度写死、两列网格、`span 8`),**窗口拉宽没有用**。
|
||
2026-10 实测过一轮:提示折成 2~3 行时读起来根本不像一句话(最惨的一处被 flex 挤到 191px、折了 3 行)。
|
||
12px 字的经验值:`span 12`(620px 弹窗)≈ **23 个汉字**、`span 8`(680px 弹窗)≈ **16 个**、
|
||
`.uf__grid` 两列 ≈ **21 个**、全宽表单项(520px 弹窗)≈ **39 个**
|
||
(11px 的 `.org-drawer__fallback` 在 160px 定宽列里 ≈ 14 个)。
|
||
⚠️ **`.n-form-item-blank` 是 flex 行**(`form-item` 是 grid、blank 区是 flex)→ 同一个 `NFormItem`
|
||
里放两个元素会被并排压缩;要"提示在上、内容在下"必须包一层块级容器
|
||
(写法见 `src/views/task/TaskFormModal.vue` 的 `__preview-box`)。
|
||
⚠️ **弹窗遮罩的毛玻璃只能用 CSS**:Naive 把遮罩色写死在 css-render 里,Modal / Drawer 主题
|
||
**没有 mask token**,`App.vue` 的 `themeOverrides` 改不到它 —— 规则在 `src/styles/index.scss`,
|
||
细节见 `.agents/skills/code-css/SKILL.md`。
|