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:
fengpu
2026-07-03 14:53:56 +08:00
co-authored by Claude Opus 4.7
parent 2727cfda86
commit 74d1488d3b
305 changed files with 23157 additions and 205 deletions
+418
View File
@@ -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 4375×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 4Hash 模式) |
| 状态管理 | **不引入 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 / dispatchMock200~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% 不改**