feat: 对齐 vue 研发项目基础设施并落地列表页规范样板

- 新增 axios/utils/directives/api/assets 五大基础设施目录(与生产 vue 项目 1:1)
- 重构 FilterBar / TableCard 支持 :table 对象与 default slot
- 新增列表页/弹框/抽屉/详情页/Tabs 等代码规范文档
- arcEmployee 作为新规范标准列表页样板重构
- 清理 .claude/tmp 临时截图
This commit is contained in:
W10-0020\Administrator
2026-06-05 11:07:05 +08:00
parent 87fabc49c1
commit 785e25d231
45 changed files with 4080 additions and 531 deletions
+152 -124
View File
@@ -1,17 +1,31 @@
---
name: new-page
description: 在原型工程中新建一个标准列表页 / 详情页 / 弹窗,按 init 三件套规范生成完整骨架(vue + types + api + 三个 init 文件),并自动注册到 src/meta/nav.ts。当用户说"新增 xxx 页面"、"加一个 xxx 列表"或"new-page" 时触发。
description: 在原型工程中新建一个标准列表页 / 详情页 / 弹窗,按 vue 研发项目规范生成完整骨架(vue + types + api + 三个 init 文件 + mock handler),并自动注册到 src/meta/nav.ts。当用户说"新增 xxx 页面"、"加一个 xxx 列表"或"new-page" 时触发。
---
# Skill: 新建原型页面脚手架
# Skill: 新建原型页面脚手架(对齐 vue 研发项目)
## 总体原则
本 skill 生成的页面骨架必须与生产 vue 项目(D:\Work\platform-vue-tenant)的代码规范 1:1 对齐,研发接手时几乎无需改动。
**强制规范来源**
- `src/docs/list-page-spec.md`(列表页规范)
- `src/docs/modal-spec.md`(弹框规范)
- `src/docs/drawer-spec.md`(抽屉规范)
- `src/docs/detail-page-spec.md`(详情页规范)
- `src/docs/tabs-page-spec.md`Tabs 页规范)
- `src/docs/cross-project-mapping.md`(原型 ↔ 研发对照表)
**标准参考样板**`src/pages/tenant-portal/nutrition/arc/arcEmployee/`
## 图标规范(强制)
所有图标必须取自 `@ant-design/icons-vue`4.x),**禁止使用 emoji / 自定义 svg / 第三方 icon** 作为菜单或按钮图标
所有图标必须取自 `@ant-design/icons-vue`4.x),**禁止使用 emoji / 自定义 svg / 第三方 icon**。
> 应用图标(NavApp.icon可继续使用 emoji,仅用于"应用网格首页"与左上角应用切换器。
> 应用图标(NavApp.icon允许 emoji,仅用于"应用网格首页"与左上角应用切换器。
**菜单图标**(在 nav.ts 中以字符串形式声明组件名,渲染时 layouts 用 `<component :is="icon" />`
**菜单图标**
| 层级 | 字段 | 是否必填 | 兜底 |
|------|------|---------|------|
@@ -19,23 +33,14 @@ description: 在原型工程中新建一个标准列表页 / 详情页 / 弹窗
| 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):
**操作按钮图标**100% 遵循统一映射):
| 操作 | 图标 |
|------|------|
| 查询 / 搜索 | SearchOutlined |
| 重置 | RedoOutlined |
| 新增 / 添加 | PlusOutlined |
| 编辑 / 修改 | EditOutlined |
| 新增 | PlusOutlined |
| 编辑 | EditOutlined |
| 删除 | DeleteOutlined |
| 导入 | UploadOutlined |
| 导出 | DownloadOutlined |
@@ -43,37 +48,37 @@ description: 在原型工程中新建一个标准列表页 / 详情页 / 弹窗
| 查看任务/列表 | 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 (三级页面)
└─ NavApp(独立可部署应用)
└─ NavSection应用内一级菜单,顶部 tab
└─ NavGroup二级分组,可选| NavLeaf二级页面
└─ NavLeaf三级页面
```
URL`/<portal>/<app>/<section>/<page>`
## 输入参数(向用户依次问)
## 输入参数(向用户依次问)
1. **页面类型**`list`(列表页)| `detail`(详情页)| `modal`(弹窗)
1. **页面类型**`list`(列表页)| `detail`(详情页)| `modal`(弹窗)| `tabs`Tabs 页)
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 简称>-<页面简称>` 风格
3. **所属 NavApp** key:如 `nutrition``monitor`
4. **所属 NavSection** key:如 `nutrition-arc``nutrition-farm`
5. **如有二级分组**NavGroup key 与 label
6. **页面 key**:唯一 + 小驼峰,如 `arcUnit`;建议 `<app>-<section 简称>-<页面简称>` 风格
7. **页面中文名**:如 `单位营养报表`
8. **路由 path 片段**:短横线小写,如 `arc-unit`
9. **图标**(可选):emoji,未指定时 Sidebar 自动用默认图标
9. **菜单图标**:必须是 `@ant-design/icons-vue``XxxOutlined` 名称
10. **页面功能**(重要 - 决定生成哪些按钮/组件):
- **列表页**:必问 `["search"|"detail"|"add"|"edit"|"delete"|"import"|"export"|"batch-delete"|"audit"|...]` 至少包含一项
- 默认只生成 `search`(查询)+ `detail`(查看详情,挂 InfoDrawer
- 用户未明确指定 `add/edit/delete` 时**禁止**生成 addOrEdit 弹框
- 用户未明确指定 `export` 时**禁止**生成导出按钮
- 用户未明确指定 `import` 时**禁止**生成导入按钮
## 执行流程
@@ -82,134 +87,157 @@ URL`/<portal>/<app>/<section>/<page>`
- 检查 `src/pages/<portal>/<app>/<section 短名>/<页面key>/` 目录不存在
- 检查 `src/meta/nav.ts` 对应 NavSection 的 children 内不存在同名 leaf
如已存在,告知用户并中止。
### Step 2:生成骨架(以列表页 list 为例)
### Step 2:以 arcEmployee 为蓝本生成骨架
参考路径:`src/pages/tenant-portal/nutrition/arc/arcEmployee/`
生成文件清单(list 类型):
**文件清单(6 文件 + 可选 component/modal/addOrEdit/ 4 文件)**
```
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 数据写这里)
├── <页面key>.vue # 模板层(
├── api/index.ts # postRequest 调用
├── types/index.ts # 类型集中定义
── init/
├── usePage.ts # 页面主逻辑
├── useTable.ts # TableState 表格状态
└── useSearch.ts # reactive + Object.assign 重置
└── component/modal/addOrEdit/ # 可选:新增/编辑弹框
├── addOrEdit.vue
├── init/usePage.ts
├── api/index.ts
└── types/index.ts
```
模板差异:
### Step 3:填充内容(强制按 list-page-spec.md 模板)
| 页面类型 | 包含文件 |
|---------|---------|
| `list` | 全部 6 个文件 |
| `detail` | 5 个文件(去掉 useSearch + useTable,新增 useDetail.ts |
| `modal` | 4 个文件(vue + usePage + typesapi 视需要) |
**types/index.ts**
- `SearchForm`(字段全 optional
- `ListItem`id 统一 string 类型)
- `ListParams`(继承 SearchForm + pageNum/pageSize/order/column
### Step 3:填充内容(遵循新规范)
**api/index.ts**
- import `postRequest from '@axios'`
- 接口路径 `/<app>/<页面key>/page``/info``/add``/update``/delete`
- 每个方法返回 `Promise<ApiResponse<...>>`
**列表页 vue 模板**
**init/useSearch.ts**
- `createSearchKey()` 工厂函数
- `createOptions()` 工厂函数
- `search = reactive(createSearchKey())`
- `resetSearch``Object.assign(search, createSearchKey())`
- `initOptions``Promise.all` 并行加载下拉
```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>
**init/useTable.ts**
- 顶部定义 `INIT_SORT: TableSort`
- 顶部定义 `tableColumns: TableColumnsType`customRender 用 `h()`
- `createInitState()` 工厂返回 `TableState`
- 暴露 `{ table, dataSourceChange, resetTable, resizeColumn }`
<!-- 搜索栏 -->
<FilterBar
v-model="searchForm"
:fields="filterFields"
@search="handleSearch"
@reset="handleReset"
/>
**init/usePage.ts**
- import `useAntdStaticMethods from '@utils/antDesign/popUp'`
- 子组件 ref 必须在此定义:`ref<{ openModal: ... } | null>(null)`
- `buildParams()` 统一构造请求参数
- `listRequest()` 链式 `.then().catch().finally()`
- `searchQuery``table.pagination.current = 1``listRequest()`
- `resetQuery` 顺序 `resetSearch → resetTable → listRequest`
- 删除强制 `Modal.confirm` + `okType: 'danger'`
- `onMounted` 通过 `loadData()` 统一触发
<!-- 表格无标题主操作放 #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>
**<页面key>.vue**
- 顶部 statCards 可选
- FilterBar 用 default slot 写裸 a-form-item
- TableCard 接 `:table` 对象
- **toolbar slot 仅在用户指定了 add/import/export/batch 时才生成**;只读查询页 toolbar slot 整段省略
- 顶部 toolbar 按钮才加 iconPlusOutlined / UploadOutlined / DownloadOutlined / DeleteOutlined
- **操作列 `<a-button type="link">` 一律无图标**(与 vue 项目一致)
- 操作列多个按钮之间用 `<a-divider type="vertical" />` 分隔
- 操作列只读页只需 `查看详情`,挂 InfoDrawer
- `@import "@assets/styles/listPage.less"`
<template #bodyCell="{ column, record }">
<!-- 业务渲染 -->
</template>
</TableCard>
</div>
</template>
### Step 4:生成 mock handler
`src/axios/handlers/<app>/<页面key>.ts` 创建 mock,注册到 `src/axios/handlers/index.ts`
```typescript
import { registerMocks } from '@/axios/mockBus'
registerMocks([
{ url: '/<app>/<页面key>/page', method: 'POST', handler: (ctx) => { ... } },
{ url: '/<app>/<页面key>/info', method: 'POST', handler: (ctx) => { ... } },
{ url: '/<app>/<页面key>/add', method: 'POST', handler: (ctx) => { ... } },
{ url: '/<app>/<页面key>/update', method: 'POST', handler: (ctx) => { ... } },
{ url: '/<app>/<页面key>/delete', method: 'POST', handler: (ctx) => { ... } },
])
```
**重要规范**
并在 `src/axios/handlers/index.ts` 末尾追加
- ❌ **禁止**渲染 `PageHeader`(标题、面包屑由顶部菜单/历史栏表达)
- ❌ **禁止**给 `TableCard``title`(菜单已经表达了所在位置)
- ✅ **必须**通过 `TableCard #toolbar` 放置主操作按钮(导出、新增等),位置在表格卡片左上方
- ✅ **可选**使用 `TableCard #extra` 放置次要操作(视图切换、批量操作等)
- ✅ **可选**统计卡片放在 `FilterBar` 之上
- ✅ 替换实体名占位:`Employee` → 用户输入的页面 key 推导
- ✅ API 接口路径占位:`POST /api/<app>/<页面key>/page`
```typescript
import './<app>/<页面key>'
```
### Step 4:注册到 nav.ts
### Step 5:注册到 nav.ts
读取 `src/meta/nav.ts`,找到目标 NavApp → NavSection 对象:
- 若指定`分组 key`:找到 NavGroup(若不存在则新建),在其 children 末尾追加 NavLeaf
- 若指定分组:找到 NavGroup(若不存在则新建),children 末尾追加 NavLeaf
- 若未指定分组:直接在 NavSection.children 末尾追加 NavLeaf
- NavLeaf 模板:
```ts
{
key: '<leaf key>',
label: '<中文名>',
path: '<路由 path 片段>',
icon: '<emoji>', // 可选,省略时 Sidebar 用默认图标
status: 'draft', // 新页面默认 draft
icon: '<XxxOutlined>',
status: 'draft',
component: () => import('@pages/<portal>/<app>/<section 短名>/<页面key>/<页面key>.vue'),
}
```
### Step 5:验证
### Step 6:验证
- 打印生成清单,告知用户在浏览器访问的路由路径:`/<portal>/<app>/<section key>/<path>`
- 若 dev 已启动,提示用户直接刷新即可看到新菜单与页面
- 打印生成清单
- 打印访问 URL`/<portal>/<app>/<section key>/<path>`
- 打印 mock 路径列表
## 输出物
## 强制规范(与 vue 研发项目对齐)
1. 6 个新文件(list 类型)
2. 修改后的 `src/meta/nav.ts`
3. 终端打印的访问 URL
| 项 | 要求 |
|---|------|
| 请求风格 | `.then().catch().finally()`,禁 `async/await` |
| 函数关键字 | 仅 `const x = () =>`,禁 `function` |
| 表格状态 | `TableState` 统一对象 |
| 搜索状态 | `reactive + 工厂函数 + Object.assign` |
| 子组件 ref | 必须在父 `usePage.ts` 定义 |
| 子组件打开方法 | `open + 组件名` |
| 子组件 emit | Modal 用 `load`Drawer 用 `ok` |
| 删除二次确认 | `Modal.confirm` + `okType: 'danger'` |
| 弹窗静态方法 | `useAntdStaticMethods()` |
| 空值展示 | `{{ x ?? '—' }}` |
| 时间格式 | 时间 `YYYY-MM-DD HH:mm:ss`,日期 `YYYY-MM-DD` |
| ID 类型 | `string \| undefined` |
| mock 数据 | 必须放 `src/axios/handlers/`,禁写 useTable.ts |
## 禁止事项
- 禁止跨页面修改其模块代码
- 禁止跨应用 import(不同 NavApp 之间模拟独立部署)
- 禁止省略 `init/` 拆分;即使只有一个搜索字段或一列表格,也必须独立文件
- 禁止把 mock 数据写在 .vue 的 setup 里,必须放 useTable.ts
- 禁止修改 `src/components/` 下的公共组件,原型阶段所有定制都在页面内
- 禁止渲染 PageHeader 标题 / TableCard title(新规范)
- 禁止把导出/新增按钮放页面顶部独立位置(必须放 TableCard #toolbar
- 禁止跨页面修改其模块代码
- 禁止跨应用 import
- 禁止省略 `init/` 三件套,即使只有一个字段也必须独立
- 禁止把 mock 数据写在 useTable.ts,必须放 `src/axios/handlers/`
- **禁止单个 mock dataset 超过 10 条**(原型展示而非性能压测)
- 禁止渲染 PageHeader / TableCard title
- 禁止把主操作按钮放页面顶部独立位置(必须放 TableCard #toolbar
- ❌ 禁止使用 emoji / 自定义 svg 作为菜单或按钮图标
-**禁止给操作列的 `<a-button type="link">` 加图标**
-**禁止脚手架默认生成"新增/编辑/删除"按钮**,必须根据用户指定的页面功能裁剪
- ❌ 禁止 `async/await`
- ❌ 禁止 `function` 关键字
- ❌ 禁止直接 `import { message } from 'ant-design-vue'`,必须 `useAntdStaticMethods()`
- ❌ 禁止在 `.vue` 文件定义子组件 ref
- ❌ 禁止 `.vue` 文件写业务逻辑
## 注意事项
- 所有注释使用简体中文
- TypeScript 类型必须明确声明,不允许 `any`
- TypeScript 类型必须明确,禁止 `any`
- 字段命名小驼峰;CSS 类名 BEM
- 生成后再次提醒用户:原型阶段 mock 数据useTable.ts,工程化时只需替换为 api 调用即可
- 生成后提醒用户:mock 数据在 `src/axios/handlers/`,研发接手时仅需替换 `src/axios/` 为真实 axios 即可