# 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/
/`(无应用层) - 租户后台:`/tenant-portal//
/`(例:`/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 必须用 包裹 ├── 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/
/ │ ├── tenant-portal//
// │ ├── miniprogram/
/ │ └── hardware/
/ ├── 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>({ dataSource: [], loading: false, pagination: createPaginationConfig(), columns: [...], }) // ❌ 禁止单独 dataList ref const dataList = ref([]) ``` ### 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>() 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 批量删除 ``` ### 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 - **操作列 `` 一律不加图标**;多个按钮用 `` 分隔 - **次要操作**放 `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 作菜单或按钮图标 - ❌ 操作列 `` 加图标 - ❌ AI 脚手架默认生成 CRUD 按钮 - ❌ 操作列按钮之间不加 `` - ❌ `customRender` 写 JSX(统一用 `h()`) - ❌ `a-input-number` 不设 `style="width: 100%"` - ❌ 顶栏高度偏离 64px - ❌ `App.vue` 漏写 `` --- ## 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. 把 `` 拆为 `` + `` 5. 把 `` 拆为 `` + `` 6. **业务逻辑 100% 不改**