feat: 对齐 vue 研发项目基础设施并落地列表页规范样板
- 新增 axios/utils/directives/api/assets 五大基础设施目录(与生产 vue 项目 1:1) - 重构 FilterBar / TableCard 支持 :table 对象与 default slot - 新增列表页/弹框/抽屉/详情页/Tabs 等代码规范文档 - arcEmployee 作为新规范标准列表页样板重构 - 清理 .claude/tmp 临时截图
This commit is contained in:
+152
-124
@@ -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 子应用对齐):
|
||||
## 项目背景
|
||||
|
||||
```
|
||||
Portal(admin-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 + types;api 视需要) |
|
||||
**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 按钮才加 icon(PlusOutlined / 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 即可
|
||||
|
||||
Reference in New Issue
Block a user