- 设备管理(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>
14 KiB
14 KiB
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 |
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/<名>/(编辑/新增弹框,emitload)component/drawer/<名>/(详情抽屉,emitok)
6. 代码风格强制约束
6.1 函数与请求
// ✅ 正确:箭头函数 + .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 调用
// ✅ 走 @axios,签名与 vue 研发项目 1:1
import { postRequest, getRequest } from '@axios'
postRequest('/api/employee/list', params)
// ❌ 禁止直接 import axios
import axios from 'axios'
6.3 弹窗静态方法
// ✅ 必须用 useAntdStaticMethods
import { useAntdStaticMethods } from '@utils/antDesign/popUp'
const { message, Modal, notification } = useAntdStaticMethods()
// ❌ 禁止直接 import
import { message } from 'ant-design-vue'
6.4 表格状态
// ✅ useTable 返回 TableState 对象,数据存 table.dataSource
const table = reactive<TableState<Employee>>({
dataSource: [],
loading: false,
pagination: createPaginationConfig(),
columns: [...],
})
// ❌ 禁止单独 dataList ref
const dataList = ref<Employee[]>([])
6.5 搜索状态
// ✅ 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
// ✅ 在父 usePage.ts 定义,命名 open + 组件名
const editModalRef = ref<InstanceType<typeof EditEmployeeModal>>()
const openEditEmployeeModal = (record?: Employee) => {
editModalRef.value?.open(record)
}
// ❌ 禁止在 .vue 中定义子组件 ref
// ❌ 禁止命名为 open / show / visible
6.7 删除二次确认
// ✅ 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 数据规则
// ✅ 位置: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对象(推荐),不显示 titleFilterBar用 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-vue4.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.idfinally中必须pageInfo.spin = false- Modal emit
load,Drawer emitok
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维护独立dataListref - ❌
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 编写代码时应保证可平滑迁移):
- 替换
src/axios/为真实 axios 封装(保留方法签名) - 删除
src/axios/handlers/ - 删除原型独有页面(
gate / console / prd / overview)和composables/useVisibility - 把
<TableCard :table>拆为<a-row class="listPageTable">+<a-table> - 把
<FilterBar v-model>拆为<a-row class="listPageForm">+<a-form> - 业务逻辑 100% 不改