init: 智养餐饮平台 V3 原型工程初始化

This commit is contained in:
W10-0020\Administrator
2026-06-04 19:43:15 +08:00
commit 87fabc49c1
99 changed files with 13000 additions and 0 deletions
+200
View File
@@ -0,0 +1,200 @@
---
name: design-overview
description: 把一个端 / 模块所有页面 + 弹窗 + 状态变体截图平铺到一个页面,交付给 UI 设计岗评审。截图来自 PRD 目录 src/prd/**/screenshots,按"端 → 模块 → 页面 → 状态"4 层组织,弹窗与状态变体分别用橙色 / 蓝色标签区分。在线查看页 /__overview 自动渲染。当用户说"生成 xx 的设计总览"、"截一组截图给 UI"、"design-overview xx" 时触发。
---
# Skill: 页面设计总览
## 用途定位
**交付物**:给 UI 设计岗评审用的"一页式截图墙",包含一个模块(或一个端)下:
- 所有页面的默认状态
- 每个页面的全部弹窗(modal / dialog / popup
- 每个页面的状态变体(empty / loading / error / tab 切换 / 展开收起 …)
UI 岗一眼能看完整模块视觉,不用切换页面。
> 与 PRD 文档的区别:PRD 关注字段/接口/操作;设计总览只关注**截图视觉**。两者共享同一 `screenshots/` 目录。
## 触发场景
- 模块原型画完,需要给 UI 评审
- 调整了视觉风格,要重新出一份总览给 UI 复检
- 弹窗 / 状态变体多,散落在 PRD 各段不便整体看
## 输入参数
1. **端**(必须):`miniprogram``hardware`(**仅这两个端**;Web 后台用 PRD 查看代替,详见决策 22)
2. **模块**(可选):如 `eat`(小程序)或 `smart-cabinet`(硬件);不传 = 整个端
3. **截图状态清单**(必须):要包含的状态,至少有 `default`,其余按页面定(如 `search-active` / `modal-confirm` / `empty`
## 执行流程
### Step 1:读业务架构与现有 PRD
必读:
- `src/docs/business-architecture.md`(理解模块在端中的位置)
- `src/prd/<端>/<模块>/*.md`(已有页面清单与截图引用)
确认要补截图的页面集合。
### Step 2:截图(核心)
**目标路径统一**`src/prd/<端>/<模块>/screenshots/<页面 path>/<状态>.jpg`
例如:
```
src/prd/miniprogram/eat/screenshots/home/default.jpg
src/prd/miniprogram/eat/screenshots/home/modal-confirm.jpg
src/prd/miniprogram/eat/screenshots/home/empty.jpg
src/prd/miniprogram/eat/screenshots/order/default.jpg
src/prd/miniprogram/eat/screenshots/order/state-paid.jpg
```
#### 视口规范(必须严格执行)
| 端 | 视口 | DPR | 输出 | 体积上限 |
|----|------|-----|------|---------|
| miniprogram | 375 × 812 | 2 | jpeg q=85 | **≤ 60KB** |
| hardware(按设备) | 见 `nav.ts``viewport` 字段 | 1 | jpeg q=80 | **≤ 80KB** |
#### 工具优先级
1. **puppeteer**(首选)—— 输出质量稳定
2. **chrome-devtools MCP**(降级)—— puppeteer 失真或尺寸不对时使用
#### puppeteer 截图模板
```js
import puppeteer from 'puppeteer'
const browser = await puppeteer.launch({ headless: 'new' })
const page = await browser.newPage()
// 小程序:iPhone X 规格 @2x
await page.setViewport({ width: 375, height: 812, deviceScaleFactor: 2 })
await page.goto('http://localhost:5180/#/miniprogram/eat', { waitUntil: 'networkidle0' })
// 等待小程序壳渲染完毕
await page.waitForSelector('.phone-shell__content > *')
// 仅截"内容区"(不截整个手机壳)
const content = await page.$('.phone-shell__content')
await content.screenshot({
path: 'src/prd/miniprogram/eat/screenshots/home/default.jpg',
type: 'jpeg',
quality: 85,
})
// 触发弹窗状态再截
await page.click('[data-test="confirm-btn"]')
await page.waitForSelector('.modal-confirm')
await content.screenshot({
path: 'src/prd/miniprogram/eat/screenshots/home/modal-confirm.jpg',
type: 'jpeg',
quality: 85,
})
await browser.close()
```
#### chrome-devtools MCP 降级路径
仅当 puppeteer 输出尺寸不对 / 文字模糊时切换:
```
1. mcp__chrome-devtools__resize_page → width=375 height=812
2. mcp__chrome-devtools__navigate_page → URL
3. mcp__chrome-devtools__wait_for / click → 触发状态
4. mcp__chrome-devtools__take_screenshot → format=jpeg quality=85 filePath=...
```
#### 体积控制
每张截图保存后立即检查:
```bash
stat -c %s src/prd/.../<状态>.jpg
```
- 超 60KB(小程序)/ 80KB(硬件)→ 降 quality (85→75) 重截
- 仍超 → `npx sharp -i in.jpg -o out.jpg --quality 70`
- 仍超 → 检查是否长截图溢出(应只截视口)
### Step 3:命名规范(严格)
文件名 = **状态 key**,决定视觉分类标签:
| 文件名前缀 / 关键词 | 类型 | 总览页标签颜色 |
|--------------------|------|---------------|
| `default.jpg` / 模块入口 | 默认状态 | 无标签 |
| `modal-<名>.jpg` / `dialog-<名>.jpg` / `popup-<名>.jpg` | 弹窗 | **橙色 tag--modal** |
| `state-<名>.jpg` / `tab-<名>.jpg` / `empty.jpg` / `loading.jpg` / `error.jpg` / `expanded.jpg` / `collapsed.jpg` | 状态变体 | **蓝色 tag--state** |
| 其他自定义 | 其他变体 | 蓝色 tag--state |
总览页 `/__overview` 自动按命名解析 + 渲染标签,**不需要额外配置文件**。
### Step 4:在线查看
完成截图后,访问:
```
http://localhost:5180/#/__overview?portal=<端>&module=<模块>
```
如:
```
http://localhost:5180/#/__overview?portal=miniprogram&module=eat
```
页面布局参考原 platform-prototype/_templates/design-overview-template.html
- 暗色背景 `#1a1a2e`
- 顶部 sticky 导航 + 模块锚点
- 按"页面"分小组,每组横向滚动卡片
- 每张卡 260×564(小程序)/ 比例缩略(硬件)
- 弹窗橙标、状态变体蓝标
- 点击卡片灯箱放大
### Step 5:检查清单
交付前自检:
- [ ] 模块下每个页面**至少**有 `default.jpg`
- [ ] 重要弹窗都有截图,命名 `modal-<名>.jpg`
- [ ] 关键状态变体覆盖完整(空数据 / 加载 / 错误 / 主要 tab)
- [ ] 单图体积全部达标
- [ ] 总览页 `/__overview?portal=...&module=...` 渲染正常,无缺图占位
### Step 6:交付提示
打印:
- 截图清单(路径 + 体积)
- 总图数 / 弹窗数 / 状态变体数
- 在线查看 URL
## 输出物
- `src/prd/<端>/<模块>/screenshots/<页面 path>/<状态>.jpg` (多张)
- 终端打印检查清单
## 禁止事项
- **禁止给 Web 后台**admin / tenant)生成设计总览(用 PRD 替代)
- 禁止超过 60KB / 80KB 体积上限
- 禁止把截图存到 `src/prd/<端>/<模块>/screenshots/` 之外
- 禁止用截图脚本控制设备屏幕外尺寸(必须严格按 viewport)
- 禁止跨模块共享同一截图(每个模块独立 screenshots 目录)
## 注意事项
- 截图前确保 dev server 运行,且**已登录 designer 模式**(否则部分 draft 页被过滤)
- 弹窗截图时点击触发后用 `waitForSelector` 等真实弹窗 DOM 出现,避免拍到过渡态
- 硬件设备视口往往超大(1080×1920 / 1920×1080 / 3840×1080),puppeteer 必须设对应 viewport
- 截图前后**不要修改**布局组件(Layout / phone-shell),否则边距错位
- 总览页是"静态截图浏览器",不会调起业务页面 — 截图缺失就是缺失,必须先截
+180
View File
@@ -0,0 +1,180 @@
---
name: gen-fields
description: 扫描指定原型页面的 useTable.ts 与 useSearch.ts,反向提取字段语义,生成跨岗位协作用的 fields.md 字段清单(含表格列字段、搜索字段、接口契约、操作流程),供后端 / 测试 / 移动端开发参考。当用户说"生成字段清单"、"gen-fields xxx"、"给 xxx 出一份接口文档" 时触发。
---
# Skill: 反向生成跨岗位字段清单
## 触发场景
原型页面已经画完,需要把"字段语义"沉淀成结构化文档,给后端写接口、测试写用例、移动端开发参考。
## 输入参数
1. **目标页面路径**:相对 `src/pages/` 的完整路径,例如 `tenant-portal/nutrition/arc/arcEmployee`4 端 / NavApp / NavSection / 页面)
2. **是否覆盖已有 fields.md**:默认 `false`,发现冲突时询问用户
## 执行流程
### Step 1:定位页面文件
读取以下文件:
- `src/pages/<路径>/<页面key>.vue` — 模板层,提取页面标题、子组件用法
- `src/pages/<路径>/types/index.ts` — 类型定义,提取实体接口(如 EmployeeNutritionRecord
- `src/pages/<路径>/api/index.ts` — 接口契约
- `src/pages/<路径>/init/useTable.ts` — 表格列
- `src/pages/<路径>/init/useSearch.ts` — 搜索字段
- `src/pages/<路径>/init/usePage.ts` — 页面级行为
任一文件缺失则提示用户并继续(部分页面如详情页可能没有 useTable)。
### Step 2:提取字段语义
**从 `useTable.ts` 的 columns 数组提取**
| 提取项 | 来源 |
|--------|------|
| 字段名 | `dataIndex` |
| 中文标题 | `title` |
| 列宽 | `width` |
| 是否冻结 | `fixed: 'left'/'right'` |
| 自定义渲染 | `key` 字段(在模板 bodyCell 中找规则) |
**从 `useSearch.ts` 的 filterFields 数组提取**
| 提取项 | 来源 |
|--------|------|
| 字段名 | `name` |
| 标签 | `label` |
| 控件类型 | `type` |
| 必填规则 | (从 placeholder 启发,原型阶段大多无强制规则) |
| 下拉选项 | `options`(从 useSearch.ts 同文件 unitOptions 等取) |
**从 `types/index.ts` 提取**
- 主实体 interface 的全部字段类型(含 TS 类型)
- 与 columns 对齐补充字段说明
**从 `api/index.ts` 提取**
- 接口路径、请求方法
- 请求 / 响应类型
### Step 3:生成 fields.md
写入路径:`src/docs/<portal>/<app>/<section 短名>/<页面key>.md`(镜像 pages 目录结构)
> 注意:新规范下列表页不再有 PageHeader,因此生成文档时**不要**从 vue 模板里抓取标题——以 nav.ts 中的 leaf.label 为准。
文档结构:
```markdown
# <页面中文名>
> 自动生成于 YYYY-MM-DD,对应原型路径 `src/pages/<路径>`
## 一、页面概览
- **业务定位**:<从 PRD / 页面标题推断的一段说明>
- **路由路径**`/<端>/<模块>/<路由>`
- **页面类型**:列表页 / 详情页 / 弹窗
## 二、接口契约
| 用途 | 方法 | 路径 | 请求参数 | 响应 |
|------|------|------|---------|------|
| 分页查询 | POST | /nutrition/employee/page | EmployeeListParams | EmployeeListResponse |
| 导出 | POST | /nutrition/employee/export | EmployeeListParams | Blob |
## 三、实体字段(对照后端 DTO)
| 字段名 | TS 类型 | 中文 | 来源接口 | 必填 | 备注 |
|--------|---------|------|---------|------|------|
| id | string | 主键 | page | 是 | row-key |
| name | string | 姓名 | page | 是 | 冻结左侧 |
| ... | ... | ... | ... | ... | ... |
## 四、表格列展示规则
| 列 | 列宽 | 冻结 | 自定义渲染 |
|----|------|------|-----------|
| 姓名 | 80 | 左 | - |
| 营养达标率 | 110 | - | ≥85 绿色 / ≥70 黄 / <70 红 |
| 操作 | 100 | 右 | 「查看详情」链接 |
## 五、搜索字段
| 字段 | 控件 | 选项来源 | placeholder |
|------|------|---------|-------------|
| 姓名/工号 | 文本框 | - | 请输入姓名或工号 |
| 所属单位 | 下拉 | unitOptions3 个值) | 全部 |
| ... | ... | ... | ... |
## 六、操作流程
> 自动从模板的 @click 与 usePage.ts 暴露的方法提取
- **查看详情**:调用 `viewDetail(record)`,期望跳转到 detail 路由(原型用 message 占位)
- **导出列表**:调用 `handleExport()`,期望调用 `exportEmployeeList` 接口下载 Blob
- **查看导出任务**:调用 `viewExportTasks()`,期望打开抽屉展示历史任务
## 七、测试关注点(供测试同事参考)
- 搜索字段重置后能否清空所有筛选条件
- 营养达标率 tag 颜色阈值(85 / 70)
- 表格横向滚动时左侧"姓名"与右侧"操作"列冻结正确
- 分页:首页禁用上一页;尾页禁用下一页;每页条数切换后回到第一页
## 八、后端接口期望(供 Java 开发参考)
接口路径:`POST /nutrition/employee/page`
请求体(JSON):
\`\`\`json
{
"current": 1,
"pageSize": 30,
"keyword": "张",
"unit": "hq",
"dept": "rd",
"year": 2026
}
\`\`\`
响应体(JSON,符合 ApiResponse 包装):
\`\`\`json
{
"code": "00000",
"msg": "操作成功",
"data": {
"records": [ ... ],
"total": 1280
}
}
\`\`\`
```
### Step 4:交付提示
- 打印生成的 fields.md 路径
- 提示用户可以把这份文档**分享给后端、测试、移动端**作为接口契约依据
- 提醒:如果原型 columns / filterFields 之后有变更,再次执行本 skill 会重新生成(除非配置 `--no-overwrite`
## 输出物
`src/docs/<portal>/<app>/<section 短名>/<页面key>.md`
## 禁止事项
- 禁止主动修改原型源代码(即使发现命名不规范,仅在文档末尾"备注"中说明)
- 禁止生成不在 useTable / useSearch 中体现的字段(避免凭空构造)
- 禁止把 mock 数据复制到 fields.md(只放结构和规则)
## 注意事项
- 生成的字段类型必须与 types/index.ts 保持一致
- 接口路径采用 `api/index.ts` 注释里声明的路径
- 若 useTable 中某列用了 `key` 而非 `dataIndex`(自定义渲染列),在"表格列展示规则"中说明渲染规则,不要凭空推断字段
+125
View File
@@ -0,0 +1,125 @@
---
name: gen-prd
description: 为指定原型页面生成 / 更新 PRD 文档(src/prd/<端>/<模块>/<页面>.md),并截取关键状态截图存入 screenshots/。截图严格控制尺寸(Web 1440×900 1x、移动 375×812 2x)与体积(≤ 60KB)。当用户说"生成 xx 的 PRD"、"截一组截图"、"gen-prd xx" 时触发。
---
# Skill: 生成 PRD 文档 + 截图
## 触发场景
- 完成一个页面原型后,需要把字段语义、操作流程、接口契约沉淀为可分发的 markdown
- 页面有较大改动,PRD 需重新生成
- 给后端 / 测试 / 移动端开发提交"对外可读文档 + 关键截图"
## 输入参数
1. **目标页面路径**(必须):相对 `src/pages/`,如 `tenant-portal/nutrition/arcEmployee`
2. **状态清单**(可选):要截图的状态,默认 `default`;常见还有 `search-active` / `empty` / `loading` / `error` / `<弹窗名>`
3. **覆盖策略**(可选):默认增量更新(保留"页面概述"等手工编辑部分),传 `--overwrite` 全量重写
## 执行流程
### Step 1:扫描页面源
读取以下文件并提取信息:
| 文件 | 提取项 |
|------|--------|
| `<页面>.vue` | 页面标题、组件结构、@click 行为 |
| `init/usePage.ts` | 页头信息、统计卡数据、详情跳转 |
| `init/useSearch.ts` | 搜索字段配置、下拉选项 |
| `init/useTable.ts` | 表格 columnsdataIndex / title / width / fixed |
| `types/index.ts` | 主实体接口 / 搜索参数 / 响应类型 |
| `api/index.ts` | 接口路径、方法、参数、返回 |
对照 `src/docs/business-architecture.md` 理解模块业务上下文,避免凭空生成。
### Step 2:生成 / 合并 markdown
落地路径:`src/prd/<端>/<模块>/<页面 path>.md`(与 `pages/` 路径同构,文件名使用路由 path 短横线版)
模板结构(与 `arc-employee.md` 一致):
1. 页面概述(保留旧版手工编辑内容)
2. 页面截图(自动写入截图引用)
3. 统计区(自动从 usePage.ts stats 提取)
4. 搜索区(自动从 useSearch.ts filterFields 提取)
5. 表格字段(自动从 useTable.ts columns + types 实体对齐)
6. 操作(自动从模板 @click 与 usePage.ts 暴露的方法提取)
7. 接口契约(自动从 api/index.ts 注释提取)
8. 状态流转(如有 status 字段,输出 mermaid 状态机)
9. 测试关注点(自动 + 手工编辑保留)
10. 变更记录(追加新条目)
**保留手工编辑**:第 1、9 节若已存在内容,保留并在末尾追加自动生成部分。
### Step 3:截图
按状态清单逐张截图,存放路径:`src/prd/<端>/<模块>/screenshots/<页面 path>/<状态>.jpg`
**优先 puppeteer**;若 puppeteer 输出失真 / 尺寸不对,降级到 chrome-devtools MCP。
#### 通用截图规范(严格)
| 类别 | 视口 | DPR | 输出 | 体积上限 |
|------|------|-----|------|---------|
| Web 后台 | 1440 × 900 | 1 | jpeg q=85 | **≤ 60KB** |
| 小程序 | 375 × 812 | 2 | jpeg q=85 | **≤ 60KB** |
| 硬件 | 按设备 viewport | 1 | jpeg q=80 | **≤ 80KB**(大屏可放宽) |
#### puppeteer 代码模板(伪代码)
```js
import puppeteer from 'puppeteer'
const browser = await puppeteer.launch({ headless: 'new' })
const page = await browser.newPage()
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 })
await page.goto('http://localhost:5180/#/tenant-portal/nutrition/arc-employee', { waitUntil: 'networkidle0' })
// 触发状态(如 search-active 时执行 fill + click
await page.screenshot({ path: 'src/prd/tenant-portal/nutrition/screenshots/arc-employee/default.jpg', type: 'jpeg', quality: 85 })
await browser.close()
```
#### chrome-devtools MCP 降级路径(仅当 puppeteer 输出有问题时)
```
1. mcp__chrome-devtools__navigate_page → 目标 URL
2. mcp__chrome-devtools__resize_page → 设视口
3. (如需触发状态)mcp__chrome-devtools__fill / click
4. mcp__chrome-devtools__take_screenshot → format=jpeg quality=85 filePath=...
```
#### 体积控制
截图后立即检查文件体积:
- 若 > 60KB 且 quality > 70:降低 quality 重截
- 若 > 60KB 且尺寸过大:检查是否长截图溢出(应只截视口)
- 仍不达标:用 `sharp` 二次压缩 `npx sharp -i input.jpg -o output.jpg --quality 75`
### Step 4:交付提示
打印:
- 生成 / 更新的 md 路径
- 截图清单 + 单张体积
- PRD 在线查看 URL`http://localhost:5180/#/__prd?path=<端>/<模块>/<页面>.md`
## 输出物
- `src/prd/<端>/<模块>/<页面>.md`
- `src/prd/<端>/<模块>/screenshots/<页面>/*.jpg`
## 禁止事项
- 禁止把截图存到其他目录(如 `public/`
- 禁止生成超过 60KB 的单张 Web 截图(必须二次压缩或降 quality)
- 禁止把 mock 数据复制到 PRD(PRD 关注规则,不关注具体数据值)
- 禁止删除 PRD 中"页面概述"等手工编辑章节
## 注意事项
- 所有字段类型严格采用 types/index.ts 中的 TS 字面量,禁止自译为 Java 类型
- 接口路径必须与 api/index.ts 注释一致
- 写完后引导用户访问 `/__prd` 验证
- 业务架构上下文:必读 `src/docs/business-architecture.md`,禁止跨端引用
+215
View File
@@ -0,0 +1,215 @@
---
name: new-page
description: 在原型工程中新建一个标准列表页 / 详情页 / 弹窗,按 init 三件套规范生成完整骨架(vue + types + api + 三个 init 文件),并自动注册到 src/meta/nav.ts。当用户说"新增 xxx 页面"、"加一个 xxx 列表"或"new-page" 时触发。
---
# Skill: 新建原型页面脚手架
## 图标规范(强制)
所有图标必须取自 `@ant-design/icons-vue`4.x),**禁止使用 emoji / 自定义 svg / 第三方 icon** 作为菜单或按钮图标。
> 应用图标(NavApp.icon)可继续使用 emoji,仅用于"应用网格首页"与左上角应用切换器。
**菜单图标**(在 nav.ts 中以字符串形式声明组件名,渲染时 layouts 用 `<component :is="icon" />`):
| 层级 | 字段 | 是否必填 | 兜底 |
|------|------|---------|------|
| NavSection(一级菜单) | `icon: 'XxxOutlined'` | 必填 | AppstoreOutlined |
| NavGroup(二级分组) | `icon: 'XxxOutlined'` | 必填 | AppstoreOutlined |
| NavLeaf(叶子页面) | `icon: 'XxxOutlined'` | 可选 | 二级叶子 FileOutlined / 三级叶子 MinusOutlined |
常用菜单图标候选(写 nav.ts 时优先复用):
- 档案/记录:FolderOpenOutlined / FileTextOutlined / ProfileOutlined
- 监控/统计:MonitorOutlined / DashboardOutlined / BarChartOutlined / PieChartOutlined
- 设备/工具:DesktopOutlined / ToolOutlined / ApiOutlined
- 异常/告警:WarningOutlined / ExclamationCircleOutlined
- 配置/字典:SettingOutlined / DatabaseOutlined / BookOutlined
- 安全/检测:SafetyCertificateOutlined / FileSearchOutlined
- 环境/地理:EnvironmentOutlined / CloudOutlined / GlobalOutlined
**操作按钮图标**(所有页面按钮 100% 遵循,统一 antd icon):
| 操作 | 图标 |
|------|------|
| 查询 / 搜索 | SearchOutlined |
| 重置 | RedoOutlined |
| 新增 / 添加 | PlusOutlined |
| 编辑 / 修改 | EditOutlined |
| 删除 | DeleteOutlined |
| 导入 | UploadOutlined |
| 导出 | DownloadOutlined |
| 查看详情 | EyeOutlined |
| 查看任务/列表 | UnorderedListOutlined |
| 批量操作 | AppstoreOutlined |
| 刷新 | ReloadOutlined / SyncOutlined |
| 复制 | CopyOutlined |
| 返回 | RollbackOutlined |
| 上一步/下一步 | LeftOutlined / RightOutlined |
`FilterBar` 公共组件已内置"查询/重置"按钮(带图标),新页面无需重写。
## 项目背景(必读)
本项目 4 端架构 + 后台菜单四层模型(与生产 vue 项目 `D:\Work\platform-vue-tenant` qiankun 子应用对齐):
```
Portaladmin-portal / tenant-portal / miniprogram / hardware
└─ NavApp (应用,独立可部署,如"营养管理"/"健康监测")
└─ NavSection (应用内一级菜单,顶部 tab,如"档案"/"绿色种采")
└─ NavGroup (二级分组,可选) | NavLeaf (二级页面)
└─ NavLeaf (三级页面)
```
URL`/<portal>/<app>/<section>/<page>`
## 输入参数(向用户依次提问)
1. **页面类型**`list`(列表页)| `detail`(详情页)| `modal`(弹窗)
2. **所属 Portal**`admin-portal` | `tenant-portal` | `miniprogram` | `hardware`
3. **所属 NavApp** key:如 `nutrition``monitor``checkup`
4. **所属 NavSection** key:如 `nutrition-arc`(档案)、`nutrition-farm`(绿色种采)
5. **如有二级分组**NavGroup key 与 label(如 `nutrition-farm-monitor` / 种养监控)
6. **页面 key**:唯一 + 小驼峰,如 `arcUnit`nav.ts 中 leaf key 建议 `<app>-<section 简称>-<页面简称>` 风格
7. **页面中文名**:如 `单位营养报表`
8. **路由 path 片段**:短横线小写,如 `arc-unit`
9. **图标**(可选):emoji,未指定时 Sidebar 自动用默认图标
## 执行流程
### Step 1:参数校验
- 检查 `src/pages/<portal>/<app>/<section 短名>/<页面key>/` 目录不存在
- 检查 `src/meta/nav.ts` 对应 NavSection 的 children 内不存在同名 leaf
如已存在,告知用户并中止。
### Step 2:以 arcEmployee 为蓝本生成骨架
参考路径:`src/pages/tenant-portal/nutrition/arc/arcEmployee/`
生成文件清单(list 类型):
```
src/pages/<portal>/<app>/<section 短名>/<页面key>/
├── <页面key>.vue # 薄模板层(仅渲染,不写逻辑)
├── types/index.ts # 接口类型集中
├── api/index.ts # 接口契约占位
└── init/
├── usePage.ts # 页面级 state + 行为(无 PageHeader / 无 pageInfo
├── useSearch.ts # 搜索字段 + 选项 + 查询/重置
└── useTable.ts # 列 + 数据 + 分页(mock 数据写这里)
```
模板差异:
| 页面类型 | 包含文件 |
|---------|---------|
| `list` | 全部 6 个文件 |
| `detail` | 5 个文件(去掉 useSearch + useTable,新增 useDetail.ts |
| `modal` | 4 个文件(vue + usePage + typesapi 视需要) |
### Step 3:填充内容(遵循新规范)
**列表页 vue 模板**
```vue
<template>
<div class="<页面key kebab>">
<!-- 顶部统计卡可选 -->
<a-row :gutter="16" class="<页面key kebab>__stats">
<a-col :span="6" v-for="s in stats" :key="s.label">
<StatCard :icon="s.icon" :color="s.color" :value="s.value" :label="s.label" />
</a-col>
</a-row>
<!-- 搜索栏 -->
<FilterBar
v-model="searchForm"
:fields="filterFields"
@search="handleSearch"
@reset="handleReset"
/>
<!-- 表格无标题主操作放 #toolbar 左上方 -->
<TableCard
:columns="columns"
:data-source="dataList"
:pagination="pagination"
row-key="id"
>
<template #toolbar>
<a-button type="primary" @click="handleExport">
<template #icon><DownloadOutlined /></template>
导出列表数据
</a-button>
<a-button @click="viewExportTasks">
<template #icon><UnorderedListOutlined /></template>
查看导出任务
</a-button>
</template>
<template #bodyCell="{ column, record }">
<!-- 业务渲染 -->
</template>
</TableCard>
</div>
</template>
```
**重要规范**
- ❌ **禁止**渲染 `PageHeader`(标题、面包屑由顶部菜单/历史栏表达)
- ❌ **禁止**给 `TableCard``title`(菜单已经表达了所在位置)
- ✅ **必须**通过 `TableCard #toolbar` 放置主操作按钮(导出、新增等),位置在表格卡片左上方
- ✅ **可选**使用 `TableCard #extra` 放置次要操作(视图切换、批量操作等)
- ✅ **可选**统计卡片放在 `FilterBar` 之上
- ✅ 替换实体名占位:`Employee` → 用户输入的页面 key 推导
- ✅ API 接口路径占位:`POST /api/<app>/<页面key>/page`
### Step 4:注册到 nav.ts
读取 `src/meta/nav.ts`,找到目标 NavApp → NavSection 对象:
- 若指定了 `分组 key`:找到该 NavGroup(若不存在则新建),在其 children 末尾追加 NavLeaf
- 若未指定分组:直接在 NavSection.children 末尾追加 NavLeaf
- NavLeaf 模板:
```ts
{
key: '<leaf key>',
label: '<中文名>',
path: '<路由 path 片段>',
icon: '<emoji>', // 可选,省略时 Sidebar 用默认图标
status: 'draft', // 新页面默认 draft
component: () => import('@pages/<portal>/<app>/<section 短名>/<页面key>/<页面key>.vue'),
}
```
### Step 5:验证
- 打印生成清单,告知用户在浏览器访问的路由路径:`/<portal>/<app>/<section key>/<path>`
- 若 dev 已启动,提示用户直接刷新即可看到新菜单与页面
## 输出物
1. 6 个新文件(list 类型)
2. 修改后的 `src/meta/nav.ts`
3. 终端打印的访问 URL
## 禁止事项
- 禁止跨页面修改其他模块的代码
- 禁止跨应用 import(不同 NavApp 之间模拟独立部署)
- 禁止省略 `init/` 拆分;即使只有一个搜索字段或一列表格,也必须独立文件
- 禁止把 mock 数据写在 .vue 的 setup 里,必须放 useTable.ts
- 禁止修改 `src/components/` 下的公共组件,原型阶段所有定制都在页面内
- 禁止渲染 PageHeader 标题 / TableCard title(新规范)
- 禁止把导出/新增按钮放在页面顶部独立位置(必须放 TableCard #toolbar
## 注意事项
- 所有注释使用简体中文
- TypeScript 类型必须明确声明,不允许 `any`
- 字段命名小驼峰;CSS 类名 BEM
- 生成后再次提醒用户:原型阶段 mock 数据写在 useTable.ts,工程化时只需替换为 api 调用即可
+158
View File
@@ -0,0 +1,158 @@
---
name: prototype-lint
description: 扫描原型工程的代码规范一致性。检查每个页面目录是否符合 init 三件套结构、columns 与 types 字段是否对齐、是否存在禁止的反模式(mock 写在 .vue / 业务逻辑写在模板 / 跨模块引用 / 缺类型)。当用户说"检查规范"、"prototype-lint"、"扫描代码风格" 时触发。
---
# Skill: 原型工程规范检查
## 触发场景
- PR 合并前自检
- 多人协作时确保一致性
- 周期性巡检(建议每周一次)
## 检查项
### A. 目录结构合规(关键)
`src/pages/**` 下的每个页面目录检查:
| 项 | 要求 | 严重度 |
|----|------|--------|
| A1 | 列表页必须含 `init/usePage.ts + init/useSearch.ts + init/useTable.ts` 三件套 | 🔴 错误 |
| A2 | 必须有 `types/index.ts` | 🔴 错误 |
| A3 | 必须有 `api/index.ts`(占位也算) | 🟡 警告 |
| A4 | 主 .vue 文件名必须与目录名一致(小驼峰) | 🔴 错误 |
| A5 | 禁止在页面目录直接放 `.css/.less` 文件,样式必须 `<style scoped>` | 🟡 警告 |
### B. 模板层 .vue 文件检查
对每个页面主 .vue 文件:
| 项 | 要求 | 严重度 |
|----|------|--------|
| B1 | `<script setup lang="ts">` 必须设置 `lang="ts"` | 🔴 错误 |
| B2 | setup 内不允许出现表格数据数组字面量(`const dataList = ref([{...},{...}])` | 🔴 错误 |
| B3 | setup 内不允许出现 `columns` 数组字面量 | 🔴 错误 |
| B4 | 不允许直接 `import` 其他模块 / 其他端的页面文件 | 🔴 错误 |
| B5 | `<style>` 必须带 `scoped`(除非顶层 Layout) | 🟡 警告 |
| B6 | 模板中 class 命名建议使用 BEM | 🟢 提示 |
### C. init/*.ts 检查
| 项 | 要求 | 严重度 |
|----|------|--------|
| C1 | 每个 use*.ts 必须用 `export const useXxx = () => { ... }` 命名规范 | 🔴 错误 |
| C2 | useTable.ts 的 `columns` 数组应使用 `TableColumnsType` 类型注解 | 🟡 警告 |
| C3 | useSearch.ts 的 `filterFields` 应使用 `FilterField[]` 类型注解 | 🟡 警告 |
| C4 | 禁止使用 `any` 类型 | 🟡 警告 |
### D. types/index.ts 与 columns 对齐
对照 useTable.ts 的 `columns` 和 types/index.ts 中的主实体接口字段:
| 项 | 要求 | 严重度 |
|----|------|--------|
| D1 | columns.dataIndex 中出现的字段,必须在主实体 interface 中声明 | 🔴 错误 |
| D2 | 主实体的必填字段(无 `?`),应该至少出现在 columns 或 useDetail 中 | 🟢 提示 |
### E. api/index.ts 检查
| 项 | 要求 | 严重度 |
|----|------|--------|
| E1 | 接口函数必须有 `@接口路径` 注释(供后端 / 测试参考) | 🟡 警告 |
| E2 | 接口参数应使用 types/index.ts 中的 Params 类型 | 🟡 警告 |
| E3 | 原型阶段允许返回 `Promise.reject` 占位,但必须含 `[原型阶段]` 标识 | 🟢 提示 |
### F. 公共组件使用规范
| 项 | 要求 | 严重度 |
|----|------|--------|
| F1 | 列表页若有顶部统计区,应使用 `StatCard` 而非自定义 div | 🟡 警告 |
| F2 | 列表页搜索区应使用 `FilterBar` + filterFields 配置驱动 | 🟡 警告 |
| F3 | 列表页表格应使用 `TableCard` 包装 | 🟡 警告 |
| F4 | 禁止修改 `src/components/` 下的公共组件(按需扩展走"提一个 PR 评审" | 🔴 错误 |
### G. 截图与文档体积巡检
| 项 | 要求 | 严重度 |
|----|------|--------|
| G1 | `src/prd/**/screenshots/**/*.jpg`Web 截图体积 ≤ 60KB | 🔴 错误 |
| G2 | `src/prd/**/screenshots/**/*.jpg`:小程序截图体积 ≤ 60KB | 🔴 错误 |
| G3 | `src/prd/**/screenshots/**/*.jpg`:硬件截图体积 ≤ 80KB | 🟡 警告 |
| G4 | `src/prd/**/_overview/**/*.png`:拼图体积 ≤ 500KB | 🟡 警告 |
| G5 | 单个 PRD `.md` 体积 ≤ 200KB(防止粘贴大段截图 base64) | 🟡 警告 |
| G6 | `dist/assets/*.js`:单 chunk 体积 ≤ 700KB(信息提示,可通过 manualChunks 优化) | 🟢 提示 |
实现:用 Bash `find ... -size +60k` 找出超限文件并列出。
## 执行流程
### Step 1:枚举页面目录
使用 Glob`src/pages/web-admin/**/*/``src/pages/employee-app/**/*/``src/pages/emergency-app/**/*/`
跳过:
- 占位页(`AppPlaceholder.vue` / `PlaceholderPage.vue`
- `home/`
### Step 2:逐目录跑全部检查项
对每个目录,串行执行 A → F 的检查,收集问题。
### Step 3:输出报告
```markdown
# 原型规范检查报告(YYYY-MM-DD
## 总览
- 检查页面数:12
- 🔴 错误:3
- 🟡 警告:8
- 🟢 提示:5
## 错误详情
### pages/web-admin/nutrition/arcUnit/
- [A1] 缺少 `init/useSearch.ts`
- [B2] arcUnit.vue 第 45 行:表格数据写在了 setup 里,应迁移到 init/useTable.ts
### pages/employee-app/profile/
- [B4] Profile.vue 第 12 行:import 了 web-admin 的组件,禁止跨端引用
## 警告详情
...
```
### Step 4:可选自动修复
询问用户:"以下 N 个警告可以自动修复(如 missing scoped、any 类型替换、缺类型注解),是否执行?"
仅对**白名单内的可逆改动**执行自动修复:
-`<style>``scoped`
-`columns``TableColumnsType` 类型注解
-`filterFields``FilterField[]` 类型注解
不在白名单内的(如 B2 数据搬迁),打印手工修复指引但不动代码。
## 输出物
- 终端打印的检查报告
- 可选:`.claude/memory/lint_report.md`(持久化最近一次报告)
## 禁止事项
- 禁止执行可能破坏页面渲染的"自动修复"
- 禁止跨页面/跨模块自动重构(仅同文件内的微调)
- 禁止重命名文件(命名问题只报告)
## 注意事项
- 列表页 vs 详情页 vs 弹窗的"必须文件"清单不同,按页面类型差异化检查
- 占位页(标题含 "Placeholder" 或文件名为 Placeholder*)不参与全部检查
- 检查耗时:100 个页面 < 5 秒(基于 Grep + Read,不调用任何工具子进程)
+109
View File
@@ -0,0 +1,109 @@
---
name: sync-nav
description: 检查 src/meta/nav.ts 与 src/pages/ 实际文件的一致性。扫描 pages 下所有 .vue 文件,对照 nav.ts 中的 component 懒加载路径,报告:在 pages 但未注册到 nav 的孤儿页面 / 在 nav 但 pages 不存在的死引用 / 路径与目录命名不一致的问题。当用户说"检查菜单同步"、"sync-nav"、"看看哪些页面没注册到菜单" 时触发。
---
# Skill: 菜单与页面文件一致性检查
## 触发场景
- 大量新增 / 删除原型页面后,不放心 nav.ts 是否同步
- PR 评审前的快速自检
- 出现"菜单点击 404 / 页面访问 404 / 菜单没显示新页面"等异常时排查
## 执行流程
### Step 1:扫描 src/pages 下所有 .vue 文件
使用 Glob`src/pages/**/*.vue`
排除:
- `src/pages/home/Home.vue`(导航首页,不参与菜单)
- 任何以 `_` 开头的目录(约定为草稿)
收集每个 .vue 的相对路径(相对 `src/pages/`)。
### Step 2:读取 src/meta/nav.ts 中的所有 component 路径
正则匹配:`import\('@pages/(.+?)\.vue'\)`
提取每条匹配的相对路径,建立"已注册"集合。
### Step 3:比对差异
**差异 A:孤儿页面(pages 中存在但 nav 未注册)**
```
src/pages/web-admin/nutrition/arcUnit/ArcUnit.vue
→ nav.ts 未引用,菜单不会显示,无法访问
```
**差异 B:死引用(nav 引用但 pages 不存在)**
```
nav.ts → import('@pages/web-admin/foo/Foo.vue')
→ 实际文件不存在,路由懒加载会失败(404 或 Vite 报错)
```
**差异 C:命名不规范**
检查每个 .vue 文件路径是否符合:
- 目录名小驼峰:`arcEmployee` ✅ / `ArcEmployee` ❌ / `arc-employee` ❌(路由 path 用短横线,目录不用)
- 文件名与目录名一致:`arcEmployee/arcEmployee.vue`
- 不允许多层冗余:`arcEmployee/index.vue`(除非阶段一约定的占位页)
### Step 4:输出诊断报告
```markdown
# 菜单同步检查报告(YYYY-MM-DD HH:mm
## 总览
- pages 下 .vue 文件总数:12
- nav.ts 已注册路径数:10
- 一致性:⚠️ 发现 3 处差异
## 差异 A:孤儿页面(2
| 文件 | 建议 |
|------|------|
| pages/web-admin/nutrition/arcUnit/arcUnit.vue | 建议在 nav.ts 的 nutrition.arc 分组追加 NavLeaf |
| pages/web-admin/nutrition/farmHarvest/farmHarvest.vue | 同上 |
## 差异 B:死引用(1
| nav 引用 | 建议 |
|---------|------|
| @pages/web-admin/foo/Foo.vue | 删除该 NavLeaf 或补齐文件 |
## 差异 C:命名不规范(0
## 建议执行命令
如需自动补齐 nav,可继续调用 new-page 重新生成(仅注册步骤)。
```
### Step 5:交互处理
询问用户:
- "是否要我自动为孤儿页面在 nav.ts 中追加 NavLeaf" → 默认放在该模块最后一个 NavGroup 末尾
- "是否要删除死引用?" → 默认不删,仅标注
## 输出物
终端打印诊断报告。若用户确认修复,直接更新 `src/meta/nav.ts`
## 禁止事项
- 禁止主动重命名文件 / 目录(命名问题只报告,让用户决定)
- 禁止删除 .vue 文件(即使是死引用对应的孤儿目录)
- 禁止跨模块移动文件
## 注意事项
- 占位组件(`PlaceholderPage.vue` / `AppPlaceholder.vue`)属于"通用占位",可以被多个 NavLeaf 共享引用,不算死引用
- 检测路径时区分大小写:因 Vite dev 严格区分,`ArcEmployee.vue``arcEmployee.vue` 视为不同文件