---
description: Admin 前端 admin-framework 严格约束（详尽版，对照 admin_core）
globs: admin/src/**/*.{vue,js},admin_core/src/**/*.{vue,js}
alwaysApply: false
---

# Admin Framework 严格约束（详尽）

本规则用于 `admin` / `admin_core` 业务页面与入口，**优先级高于建议类文档**。与 `admin_core` 中 `http.js`、`store/user.js`、`router/index.js`、`index.js` 行为冲突的写法一律禁止。

---

## 1. 禁止项（违反即应重构）

### 1.1 HTTP 与 axios

- **禁止**在业务 `vue/js` 中 `import axios` 或 `axios.create`，用于替代 `this.$http`（`http.js` 已集中处理拦截器、loading、`admin-token`）。
- **禁止**手工设置请求头 **`admin-token`**（或复制一套与 `getHttpInstance` 等价的拦截器）；例外仅存在于框架内部的 **`fileExport`**（独立 `axios.post`），业务仍应调用 **`this.$http.fileExport`**。
- **禁止**在页面内维护第二套 **`apiUrl` / baseURL** 常量拼接 URL；统一 `createApp({ apiUrl })` 与 **`this.$http`**。

### 1.2 与框架重复的基础设施

- **禁止**复制 **`formatParamete`**（去空串、日期格式化）、全局 Loading DOM（`spin-box-one`）、401 跳转登录等与 **`http`** 重复的逻辑。
- **禁止**在已有 **`editModal` / `AsyncModal` / `FieldRenderer`** 能满足需求时，再写一套通用弹窗/动态表单渲染（除非产品明确要求完全不同的交互且与框架无关）。

### 1.3 路由与菜单

- **禁止**直接写 **`localStorage.authorityMenus`**、**`localStorage.menuList`** 替代 **`user` 模块 mutation/action**（除非你在做迁移脚本且不在运行时页面）。
- **禁止**绕过 **`setAuthorityMenus`** 直接 **`router.addRoutes`** 拼装主布局（会与框架内 **`router.options.routes`  splice + addRoute** 逻辑冲突）。

### 1.4 命名与兼容

- **禁止**接口/表单字段 **`snake_case` 与 `camelCase` 双读兜底**（如 `row.user_id ?? row.userId`），除非后端合同明文双写且规范文档特许。
- **禁止**对框架已约定 API 的 **`ref`** 写 **`typeof ref.show === 'function'`**、**`if (!this.$refs.modal) return`** 等防御式分支（规范用法下 ref 由模板绑定保证；若条件渲染导致 ref 不稳定，应调整 `v-if` 与调用时机，而非堆兼容分支）。

---

## 2. 必须项（代码审查硬标准）

### 2.1 请求与错误

- **必须**使用 **`this.$http`**（组件）或 **`AdminFramework.http`**（非组件）；二者指向同一封装实例。
- **必须**对 `await this.$http.*` 使用 **`try/catch`**（或等价的 `.catch`）；原因：后端 **`code !== 0`**、网络错误、401 均可能 **reject**；拦截器已 `Message.error` 时，catch 内可避免重复弹窗，但**不可**空 catch 吞掉需上报的逻辑。
- **必须**对**写库、改状态**类接口优先使用 **`POST`**，路径 **`snake_case`** 分段（与后端控制器风格一致）。

### 2.2 Loading 策略

- **必须**用 **`this.$http.get(url, params, { hideLoad: true })`** 实现无感轮询/预加载；**不得**为绕过 `post` 的强制 loading 而引入 axios。
- 若业务确需「POST 且无 loading」，**正确做法**是扩展 **`admin_core` 的 `http.post`**（例如增加可选 `config.hideLoad`）并升版框架，**禁止**仅在业务仓库私改 axios。

### 2.3 动态路由与 `componentMap`

- **必须**保证后端菜单「页面/功能」行的 **`component`** 在 **`setupComponentMap` / `addComponentMap`** 合并后的表可解析。
- **必须**在新菜单页合并前本地验证：**`uiTool.getComponent(菜单路径)`**（或通过实际登录 + 点击菜单）无「组件未找到」占位。
- 映射键：**建议**与菜单字符串完全一致；框架虽自动补 **无 `.vue` / 有 `.vue`** 双键，但**路径前缀、大小写、子目录**仍须一致。

### 2.4 刷新菜单与标题

- 菜单 CRUD 后刷新侧栏：**必须** `dispatch('user/setAuthorityMenus', { Main, ParentView, Page404, HomePage })`（参数与 **`sys_menu.vue`** 等处一致，使用 **`this.$framework.components`** 与 **`this.$framework.pages`** / **`HomePage`**）。
- 修改站点标题/Logo 后：**必须** `dispatch('app/getSysTitle', { defaultTitle: this.$config.title, defaultLogo: '' })`（**带 `app/` 命名空间**）。

### 2.5 框架组件 `ref` 调用

- **`AsyncModal`、`FloatPanel`、`editModal`**：**直接** `this.$refs.xxx.show()` / `hide()` / 文档约定方法；**禁止**多套 hide 回退、反射内部 state。
- 若组件为 `v-if` 控制，**必须**在 **`$nextTick`** 后再调 `ref` 方法，而不是写「若不存在则静默 return」掩盖时序 bug。

### 2.6 子组件内聚

- 表单 **`data` / `rules` / `loading` / `visible`**、校验、重置：**必须**放在子组件内；父组件只传 **`id`**、**只读上下文**、**`@success`** 等；**禁止**父组件 `v-model` 绑十几项内部字段。

---

## 3. `createApp` 入口（硬约束）

- **必须** `AdminFramework.createApp(config)` 得到根实例后再 **`$mount`**。
- **`config.apiUrl` 必填**；`title`、`componentMap`、`HomePage`、`onReady` 按项目需要。
- **不得**在业务页重复 `Vue.use(VueRouter)` / `Vuex` / `ViewUI` 以替换框架已注册插件（二次 `createApp` 也不会卸载已 `Vue.use` 的插件，易脏状态）。

### 3.1 运行时补映射

- **必须**使用 **`AdminFramework.addComponentMap(map)`**；**禁止**直接改 **`uiTool`** 内部 `componentMap` 对象引用。

---

## 4. Token 与 Cookie（与 `tools.js` 一致）

- Cookie 键 **`TOKEN_KEY === 'token'`**（不是 `admin-token`）；**HTTP 请求头**才是 **`admin-token`**，值来自 **`store.state.user.token`**。
- 登录/登出须走 **`user`** 模块的 **`setToken` / `handleLogOut`**，保证 Cookie 与 store 一致。

---

## 5. 反例与正例（摘要）

| 反例 | 正例 |
|------|------|
| `import axios from 'axios'; axios.get(...)` | `await this.$http.get(...)` |
| `headers: { 'admin-token': xxx }` | 使用 `this.$http`，token 由 store 注入 |
| `dispatch('getSysTitle')` | `dispatch('app/getSysTitle', { ... })` |
| `post` 轮询且无 loading | `get` + `{ hideLoad: true }` 或扩展框架 |
| `receiving_id ?? receivingId` | 只使用合同字段名（如 `receiving_id`） |
| `if (this.$refs.m && this.$refs.m.show) this.$refs.m.show()` | `this.$nextTick(() => this.$refs.m.show())` |

---

## 6. 提交前自检（详尽清单）

- [ ] 全文搜索 **`import axios`** / **`axios.create`**（业务目录应为 0）。
- [ ] 全文搜索 **`admin-token`**（除注释外应为 0，框架内 `fileExport` 除外若复制代码）。
- [ ] 变更接口是否 **`POST`** + **`snake_case`** URL。
- [ ] 新页面是否已写入 **`componentMap`**（或运行时 **`addComponentMap`**）。
- [ ] 标题刷新是否 **`app/getSysTitle`**。
- [ ] 所有 **`await this.$http`** 是否处于 **`try/catch`** 或链式 **`catch`**。
- [ ] 是否存在 **`??` 双字段名**、**`typeof === 'function'` ref 防御**（应删除或改为规范时序）。
- [ ] 子组件是否仍接收大量「仅表单内部使用」的 props（应内聚）。
