feat: 设备中心模块全量落地 + 列表页同级数据切换规范
- 设备管理(manage):5 类设备 tab 改为 a-radio-group button-style="solid"(参考 vue 研发项目 unitReportData);新增投用状态字段、固件版本→软件版本、移除数据中转;所属租户/布点场所非必填,留空自动判定在库;导出改异步(导出列表数据 + 查看导出任务) - 设备统计(statistics):从图标展示重构为列表页,14 列定义 + 12 维搜索条件 + 4 张统计卡 + a-table-summary 统计行 + 异步导出 - 设备工作台(workbench/overview):新增概览页 + DonutChart 组件 - 资产/库存/运维/分发/租户场所等子模块全量落地(含 mock handler、6 文件结构、modal/drawer 子组件) - 系统设置/运营中心扁平化页面落地(员工/角色/菜单/应用/字典/租户列表/小程序用户) - 实施运维小程序(miniprogram/ops):工作台/工单/扫码/巡检/我的 5 个 tab 页 - 规范:list-page-spec §7.4 新增「同级数据切换(radio-button 样式,强制)」;tabs-page-spec 文首新增「〇、适用范围」明确与同表过滤场景的边界 - 路由/菜单(nav.ts + routes.ts):同步设备中心菜单层级与 ops 小程序实例 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
2727cfda86
commit
74d1488d3b
@@ -0,0 +1,418 @@
|
||||
# AGENTS.md — Codex 约束文件
|
||||
|
||||
> 本文件为 **Codex / Codex CLI** 等 AI 编码助手在本项目工作时的约束规范。
|
||||
>
|
||||
> **权威源**:详细规则以同目录 `CLAUDE.md` 为准;本文件为 Codex 友好的精简快查版。
|
||||
> 当本文件与 `CLAUDE.md` 冲突时,**以 `CLAUDE.md` 为准**。
|
||||
|
||||
---
|
||||
|
||||
## 0. 语言强制
|
||||
|
||||
- **所有对话回复必须使用简体中文**(代码标识符除外)
|
||||
- **所有代码注释必须使用简体中文**(JavaDoc / JSDoc / Docstring 同样要求)
|
||||
- 变量名、函数名、类名仍使用英文(遵循编程语言规范)
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目定位
|
||||
|
||||
**智养餐饮平台 V3** 的高保真交互原型工程:**Vue 3 + Vite + Ant Design Vue + Vant** 半工程化方案。
|
||||
|
||||
**核心目标**:让产品经理画的原型 ≈ 开发要写的代码骨架,研发接手时改动最小。
|
||||
|
||||
### 四个终端(互不引用)
|
||||
|
||||
| 终端 | key | UI 库 | 业务定位 |
|
||||
|------|-----|-------|---------|
|
||||
| 综合管理后台 | `admin-portal` | Ant Design Vue | 平台级管理(单应用形态,3 个一级菜单:系统设置 / 运营中心 / 设备中心) |
|
||||
| 租户运营后台 | `tenant-portal` | Ant Design Vue | 多应用形态(17 个独立可部署应用,如 nutrition / monitor 等) |
|
||||
| C 端微信小程序 | `miniprogram` | Vant 4(375×812) | 用户自助查询、上报、咨询 |
|
||||
| 硬件终端屏 | `hardware` | 自定义(动态 viewport) | 净菜柜屏 / 自助查询屏 / 大屏 |
|
||||
|
||||
### URL 规范
|
||||
|
||||
- 综合后台:`/admin-portal/<section>/<page>`(无应用层)
|
||||
- 租户后台:`/tenant-portal/<app>/<section>/<page>`(例:`/tenant-portal/nutrition/arc/employee`)
|
||||
- 应用首页:`/tenant-portal`(应用网格卡片)
|
||||
|
||||
---
|
||||
|
||||
## 2. 技术栈与启动
|
||||
|
||||
| 类别 | 选型 |
|
||||
|------|------|
|
||||
| 框架 | Vue 3.5 + TypeScript 5.9 |
|
||||
| 构建 | Vite 8(端口 5180) |
|
||||
| Web 端 UI | Ant Design Vue 4.2 |
|
||||
| 移动端 UI | Vant 4 |
|
||||
| 路由 | Vue Router 4(Hash 模式) |
|
||||
| 状态管理 | **不引入 Pinia** |
|
||||
| 样式 | LESS + CSS 变量 |
|
||||
| 自动导入 | unplugin-auto-import + unplugin-vue-components |
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev # 端口 5180
|
||||
npm run build
|
||||
```
|
||||
|
||||
**门禁密码**(`.env`):设计 `design2026` / 研发 `dev2026`,登录后 7 天有效。
|
||||
|
||||
---
|
||||
|
||||
## 3. 路径别名(与 vue 研发项目 1:1 对齐)
|
||||
|
||||
| 别名 | 指向 |
|
||||
|------|------|
|
||||
| `@` | `src/` |
|
||||
| `@meta` | `src/meta/`(菜单数据,原型特有) |
|
||||
| `@layouts` | `src/layouts/` |
|
||||
| `@components` | `src/components/` |
|
||||
| `@composables` | `src/composables/`(原型特有) |
|
||||
| `@pages` | `src/pages/`(研发项目用 `@views`) |
|
||||
| `@router` | `src/router/` |
|
||||
| `@utils` | `src/utils/` |
|
||||
| `@axios` | `src/axios/` |
|
||||
| `@assets` | `src/assets/` |
|
||||
| `@directives` | `src/directives/` |
|
||||
| `@api` | `src/api/` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 目录结构(精简版)
|
||||
|
||||
```
|
||||
src/
|
||||
├── main.ts 入口:mock handlers / directives / antd 静态方法 初始化
|
||||
├── App.vue 必须用 <a-config-provider :locale="zhCN"> 包裹
|
||||
├── router/
|
||||
│ ├── index.ts 门禁 + designerOnly 守卫
|
||||
│ └── routes.ts 由 nav.ts 自动派生
|
||||
├── meta/nav.ts 🌟 菜单数据单一来源(adminApps / tenantApps)
|
||||
├── axios/ 🆕 假 axios 层(签名与研发 1:1)
|
||||
│ ├── index.ts getRequest / postRequest / putRequest / deleteRequest / restfulRequest / uploadRequest / downloadRequest
|
||||
│ ├── mockBus.ts registerMock / dispatchMock(200~500ms 延迟)
|
||||
│ └── handlers/<模块>/<页面>.ts 🌟 mock handler 注册(禁止写在 useTable.ts)
|
||||
├── api/index.ts 🆕 跨模块公共接口(字典 / 部门树 / 组织树)
|
||||
├── utils/antDesign/ table.ts / popUp.ts / select.ts
|
||||
├── directives/ v-no-space / v-only-number / v-only-alphanumeric ...
|
||||
├── assets/styles/listPage.less 列表页统一布局
|
||||
├── composables/useVisibility.ts 可见性核心(原型特有)
|
||||
├── layouts/
|
||||
│ ├── PortalLayout.vue admin / tenant 共用
|
||||
│ ├── MiniprogramLayout.vue
|
||||
│ ├── HardwareLayout.vue
|
||||
│ └── components/ TopBar / Sidebar / AppTabBar / RoleBadge
|
||||
├── components/ StatCard / FilterBar / TableCard / PageHeader / PlaceholderPage / AppPlaceholder
|
||||
├── pages/ 按 端/应用/一级菜单 分目录
|
||||
│ ├── home/Home.vue 🏠 全局导航首页
|
||||
│ ├── gate/Gate.vue 🔒 门禁页
|
||||
│ ├── console/ 🎛 设计完成度控制台
|
||||
│ ├── prd/ 📄 PRD 查看
|
||||
│ ├── overview/ 🖼 设计总览
|
||||
│ ├── admin-portal/<section>/
|
||||
│ ├── tenant-portal/<app>/<section>/<page>/
|
||||
│ ├── miniprogram/<section>/
|
||||
│ └── hardware/<section>/
|
||||
├── prd/ markdown 文档 + 截图(≤ 60KB)
|
||||
├── docs/ 🌟 业务架构 + 代码规范(生成代码前必读)
|
||||
└── types/ 自动生成 + shims
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 列表页 6 文件结构(强制)
|
||||
|
||||
```
|
||||
arcEmployee/
|
||||
├── arcEmployee.vue 模板:FilterBar + TableCard,仅引用 hook 暴露的字段/方法
|
||||
├── types.ts 接口/表单/记录类型
|
||||
├── api/index.ts listRequest / addRequest / editRequest / deleteRequest / detailRequest
|
||||
└── init/
|
||||
├── usePage.ts 组合根:发起接口 / 子组件 ref / 打开方法
|
||||
├── useSearch.ts reactive(search) + createSearchKey() + 下拉数据
|
||||
└── useTable.ts TableState 对象 + columns(禁调接口、禁存数据 ref)
|
||||
```
|
||||
|
||||
**子组件**按需追加:
|
||||
- `component/modal/<名>/`(编辑/新增弹框,emit `load`)
|
||||
- `component/drawer/<名>/`(详情抽屉,emit `ok`)
|
||||
|
||||
---
|
||||
|
||||
## 6. 代码风格强制约束
|
||||
|
||||
### 6.1 函数与请求
|
||||
```ts
|
||||
// ✅ 正确:箭头函数 + .then 链式
|
||||
const listRequest = () => {
|
||||
table.spin = true
|
||||
getList(searchParams)
|
||||
.then((res) => {
|
||||
table.dataSource = res.data
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
table.spin = false
|
||||
})
|
||||
}
|
||||
|
||||
// ❌ 禁止:function 关键字 + async/await
|
||||
async function listRequest() {
|
||||
const res = await getList(searchParams)
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 HTTP 调用
|
||||
```ts
|
||||
// ✅ 走 @axios,签名与 vue 研发项目 1:1
|
||||
import { postRequest, getRequest } from '@axios'
|
||||
postRequest('/api/employee/list', params)
|
||||
|
||||
// ❌ 禁止直接 import axios
|
||||
import axios from 'axios'
|
||||
```
|
||||
|
||||
### 6.3 弹窗静态方法
|
||||
```ts
|
||||
// ✅ 必须用 useAntdStaticMethods
|
||||
import { useAntdStaticMethods } from '@utils/antDesign/popUp'
|
||||
const { message, Modal, notification } = useAntdStaticMethods()
|
||||
|
||||
// ❌ 禁止直接 import
|
||||
import { message } from 'ant-design-vue'
|
||||
```
|
||||
|
||||
### 6.4 表格状态
|
||||
```ts
|
||||
// ✅ useTable 返回 TableState 对象,数据存 table.dataSource
|
||||
const table = reactive<TableState<Employee>>({
|
||||
dataSource: [],
|
||||
loading: false,
|
||||
pagination: createPaginationConfig(),
|
||||
columns: [...],
|
||||
})
|
||||
|
||||
// ❌ 禁止单独 dataList ref
|
||||
const dataList = ref<Employee[]>([])
|
||||
```
|
||||
|
||||
### 6.5 搜索状态
|
||||
```ts
|
||||
// ✅ reactive + createSearchKey 工厂 + Object.assign 重置
|
||||
const createSearchKey = () => ({ name: undefined, status: undefined })
|
||||
const search = reactive(createSearchKey())
|
||||
const resetSearch = () => {
|
||||
Object.assign(search, createSearchKey())
|
||||
searchQuery()
|
||||
}
|
||||
|
||||
// ✅ searchQuery 重置页码但保留排序
|
||||
const searchQuery = () => {
|
||||
table.pagination.current = 1
|
||||
listRequest()
|
||||
}
|
||||
|
||||
// ❌ 禁止 Object.keys 遍历清空
|
||||
// ❌ 禁止 searchQuery 调 resetTable()
|
||||
```
|
||||
|
||||
### 6.6 子组件 ref
|
||||
```ts
|
||||
// ✅ 在父 usePage.ts 定义,命名 open + 组件名
|
||||
const editModalRef = ref<InstanceType<typeof EditEmployeeModal>>()
|
||||
const openEditEmployeeModal = (record?: Employee) => {
|
||||
editModalRef.value?.open(record)
|
||||
}
|
||||
|
||||
// ❌ 禁止在 .vue 中定义子组件 ref
|
||||
// ❌ 禁止命名为 open / show / visible
|
||||
```
|
||||
|
||||
### 6.7 删除二次确认
|
||||
```ts
|
||||
// ✅ Modal.confirm + okType: 'danger'
|
||||
const onDelete = (record: Employee) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除?',
|
||||
okType: 'danger',
|
||||
onOk: () => deleteRequest({ id: record.id }),
|
||||
})
|
||||
}
|
||||
|
||||
// ✅ 批量按钮必须 disabled
|
||||
<a-button :disabled="!selectedRowKeys.length" @click="onBatchDelete">批量删除</a-button>
|
||||
```
|
||||
|
||||
### 6.8 空值 / 时间 / ID
|
||||
- 空值统一 `{{ x ?? '—' }}`(禁空串/null)
|
||||
- 时间 `YYYY-MM-DD HH:mm:ss`,日期 `YYYY-MM-DD`
|
||||
- ID 统一 `string | undefined`(禁 number)
|
||||
|
||||
### 6.9 类型
|
||||
- **禁用 `any`**,TS 严格模式
|
||||
|
||||
---
|
||||
|
||||
## 7. Mock 数据规则
|
||||
|
||||
```ts
|
||||
// ✅ 位置:src/axios/handlers/<模块>/<页面>.ts
|
||||
// 文件示例:src/axios/handlers/nutrition/employee.ts
|
||||
import { registerMock } from '@axios/mockBus'
|
||||
|
||||
registerMock('GET', '/api/employee/list', (params) => {
|
||||
return {
|
||||
code: '00000',
|
||||
data: { records: [...], total: 8 }, // ⚠️ 单个 dataset ≤ 8 条
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**禁止**:
|
||||
- 写在 `useTable.ts`
|
||||
- 单个 dataset 超过 **8 条**(原型用于展示而非性能压测)
|
||||
- 在 `api/index.ts` 写业务判断(如 `if res.code === '00000'`)
|
||||
|
||||
---
|
||||
|
||||
## 8. 列表页 UI 规范
|
||||
|
||||
- **不渲染 `PageHeader`**(菜单 + 顶部历史栏已表达所在位置)
|
||||
- **`TableCard`** 接 `:table` 对象(推荐),不显示 title
|
||||
- **`FilterBar`** 用 default slot 写裸 `a-form-item`(推荐),或 `:fields` 配置驱动
|
||||
- **顶部按钮**(导出/新增/导入/批量删除)放 `TableCard #toolbar` 左上方,必须带 antd icon
|
||||
- **操作列 `<a-button type="link">` 一律不加图标**;多个按钮用 `<a-divider type="vertical" />` 分隔
|
||||
- **次要操作**放 `TableCard #extra` 右上方
|
||||
- **统计卡**放 `FilterBar` 上方(可选)
|
||||
- **顶栏高度** 固定 64px
|
||||
|
||||
> ⚠️ **CRUD 不是列表页标配**:是否有"新增/编辑/删除/导出"由业务决定,Codex 不要默认生成。
|
||||
|
||||
---
|
||||
|
||||
## 9. 图标规范(强制)
|
||||
|
||||
- 菜单图标 / 操作按钮图标必须取自 `@ant-design/icons-vue` 4.x
|
||||
- **禁止 emoji / 自定义 svg**(NavApp.icon 仅在应用网格首页 + 应用切换器允许 emoji)
|
||||
|
||||
**标准映射**:
|
||||
|
||||
| 操作 | 图标 |
|
||||
|------|------|
|
||||
| 查询 | `SearchOutlined` |
|
||||
| 重置 | `RedoOutlined` |
|
||||
| 新增 | `PlusOutlined` |
|
||||
| 编辑 | `EditOutlined` |
|
||||
| 删除 | `DeleteOutlined` |
|
||||
| 导入 | `UploadOutlined` |
|
||||
| 导出 | `DownloadOutlined` |
|
||||
| 查看详情 | `EyeOutlined` |
|
||||
| 查看任务 | `UnorderedListOutlined` |
|
||||
|
||||
---
|
||||
|
||||
## 10. Modal / Drawer 约束
|
||||
|
||||
- a-modal 必须 `:keyboard="false" :mask-closable="false"`(防编辑数据丢失)
|
||||
- a-drawer(含列表型)必须 `destroy-on-close`
|
||||
- 弹框/抽屉 `.vue` 内**禁写业务逻辑**,逻辑放 `init/` hooks
|
||||
- 用 `params.id` 判断新增/编辑(**禁用** `pageInfo.title / type` 文本判断)
|
||||
- `addRequest` 必须 `delete params.id`
|
||||
- `finally` 中必须 `pageInfo.spin = false`
|
||||
- Modal emit `load`,Drawer emit `ok`
|
||||
|
||||
---
|
||||
|
||||
## 11. 禁止事项(速查)
|
||||
|
||||
### 跨模块 / 跨端
|
||||
- ❌ 跨端引用(admin ↛ tenant ↛ miniprogram ↛ hardware)
|
||||
- ❌ 跨应用引用(同 portal 下不同 NavApp 之间)
|
||||
- ❌ Vant 用在后台 / antd 用在小程序
|
||||
- ❌ 在 `api/index.ts` 写业务私有接口
|
||||
|
||||
### 列表页结构
|
||||
- ❌ 省略 init 三件套
|
||||
- ❌ 业务逻辑写在模板
|
||||
- ❌ 渲染 `PageHeader`
|
||||
- ❌ `TableCard` 显式传 title
|
||||
- ❌ 主操作按钮放在页面顶部独立位置
|
||||
|
||||
### Mock / 接口
|
||||
- ❌ mock 数据写在 `useTable.ts`
|
||||
- ❌ 单个 dataset 超过 8 条
|
||||
- ❌ 业务代码直接 `import axios`
|
||||
- ❌ `api/index.ts` 写业务判断
|
||||
|
||||
### 代码风格
|
||||
- ❌ `async/await`(统一 `.then().catch().finally()`)
|
||||
- ❌ `function` 关键字(统一箭头函数)
|
||||
- ❌ 直接 `import { message } from 'ant-design-vue'`
|
||||
- ❌ `any` 类型
|
||||
|
||||
### Hook 拆分
|
||||
- ❌ `useTable.ts` 调接口
|
||||
- ❌ `usePage.ts` 维护独立 `dataList` ref
|
||||
- ❌ `usePage.ts` 写表格列或下拉请求
|
||||
- ❌ `useSearch` 用 `Object.keys` 清空
|
||||
- ❌ `searchQuery` 调 `resetTable()`
|
||||
- ❌ 父 `.vue` 定义子组件 ref
|
||||
- ❌ 子组件打开方法命名 `open / show / visible`
|
||||
|
||||
### Modal / Drawer
|
||||
- ❌ 弹框/抽屉 `.vue` 写业务逻辑
|
||||
- ❌ a-modal 不加 `:keyboard="false" :mask-closable="false"`
|
||||
- ❌ 省略 `finally` 中 `pageInfo.spin = false`
|
||||
- ❌ 用文本判断新增/编辑
|
||||
- ❌ `addRequest` 保留 id 字段
|
||||
- ❌ 省略 a-drawer 的 `destroy-on-close`
|
||||
|
||||
### 数据展示
|
||||
- ❌ 删除/批量不经二次确认
|
||||
- ❌ 批量按钮未选中时可点击
|
||||
- ❌ 空值显示为空串/null
|
||||
- ❌ 时间/日期格式不统一
|
||||
|
||||
### 图标 / UI
|
||||
- ❌ emoji / 自定义 svg 作菜单或按钮图标
|
||||
- ❌ 操作列 `<a-button type="link">` 加图标
|
||||
- ❌ AI 脚手架默认生成 CRUD 按钮
|
||||
- ❌ 操作列按钮之间不加 `<a-divider type="vertical" />`
|
||||
- ❌ `customRender` 写 JSX(统一用 `h()`)
|
||||
- ❌ `a-input-number` 不设 `style="width: 100%"`
|
||||
- ❌ 顶栏高度偏离 64px
|
||||
- ❌ `App.vue` 漏写 `<a-config-provider :locale="zhCN">`
|
||||
|
||||
---
|
||||
|
||||
## 12. 进一步阅读
|
||||
|
||||
生成代码前 Codex **必须先读**对应文档:
|
||||
|
||||
| 任务 | 必读文档 |
|
||||
|------|---------|
|
||||
| 任何业务页面 | `src/docs/business-architecture.md` |
|
||||
| 列表页 | `src/docs/list-page-spec.md` |
|
||||
| 弹框 | `src/docs/modal-spec.md` |
|
||||
| 抽屉 | `src/docs/drawer-spec.md` |
|
||||
| 详情页 | `src/docs/detail-page-spec.md` |
|
||||
| Tabs 页 | `src/docs/tabs-page-spec.md` |
|
||||
| 表格工具 | `src/docs/utils-table.md` |
|
||||
| 弹窗工具 | `src/docs/utils-popup.md` |
|
||||
| 对照研发项目 | `src/docs/cross-project-mapping.md` |
|
||||
| 完整规则 | `CLAUDE.md`(项目根目录,权威源) |
|
||||
|
||||
---
|
||||
|
||||
## 13. 迁移到 vue 研发项目(设计意图)
|
||||
|
||||
研发接手时步骤(Codex 编写代码时应保证可平滑迁移):
|
||||
|
||||
1. 替换 `src/axios/` 为真实 axios 封装(**保留方法签名**)
|
||||
2. 删除 `src/axios/handlers/`
|
||||
3. 删除原型独有页面(`gate / console / prd / overview`)和 `composables/useVisibility`
|
||||
4. 把 `<TableCard :table>` 拆为 `<a-row class="listPageTable">` + `<a-table>`
|
||||
5. 把 `<FilterBar v-model>` 拆为 `<a-row class="listPageForm">` + `<a-form>`
|
||||
6. **业务逻辑 100% 不改**
|
||||
Reference in New Issue
Block a user