---
name: "industrial-page-standard"
version: "1.2.0"
origin: "captured"
generation: 0
parent_skill_ids: []
status: "stable"
description: "IMEX EOMS 前端页面生成基础规范。定义 Industrial Modernism 视觉体系下所有业务页面的布局结构、组件组合、主题兼容、交互模式和代码骨架。任何 agent 在生成新业务页面时必须读取本 Skill 作为强约束输入。"
trigger_phases: ["implementation"]
applicable_agents: ["Copilot Frontend Developer", "Copilot Implementation"]
priority: 10
---

# Industrial Page Standard — 前端页面生成基础规范

> **适用范围**: 本文件是 IMEX EOMS 所有新业务模块前端页面的唯一参考规范。  
> **权威性**: 高于 HTML 原型、高于 Avue 存量模式、高于个人偏好。与 `newUIUX.md` 互补——本文件聚焦"怎么落地"，`newUIUX.md` 聚焦"为什么这样设计"。  
> **更新方式**: 当 design-tokens.scss 或 NativeSearchTable 组件发生破坏性变更时，同步更新本文件。

## 0. Enterprise 同构门禁

`src/views/partner/enterprise/` 是当前工作区唯一允许复用的业务页面结构活体蓝本；`references/enterprise-baseline.md` 是该蓝本的轻量契约摘要，用于 agents、review 与健康检查共享同一套 DOM / 文件拓扑 / Inspector 门禁。若该目录下当前文件漂移为 shared CRUD workspace、raw `el-table`、缺少 `NativeSearchTable`、缺少 `usePageCommandBar` 或缺少 Inspector，必须先恢复为 Enterprise 终态蓝本，再作为新页面参考。恢复期间可参考同域 `src/views/partner/enterpriseRelation/list.vue`、`src/views/partner/businessRelation/list.vue` 的基础结构，以及 `src/views/eam/assetLedger/list.vue` 的 Inspector 密度和微交互；最终准则落点仍是恢复后的 `src/views/partner/enterprise/` 七件套。

禁止将 `src/views/partner/enterprise/` 下的完整 SFC 源码复制到 `.github/agents/` 或其他 agent 指令文件。agent 只引用活体路径与 `references/enterprise-baseline.md`，避免双份页面实现漂移。

所有新页面与存量重构页面必须满足：

1. 视图文件数量同构：`route shell + entry + list + form + paper`
2. 目录组织同构：业务目录内固定 `list.vue / form.vue / paper.vue / <module>-<page>-entry.vue`
3. 主布局同构：`basic-container -> enterprise-workspace -> workspace-main -> page-title-block -> quick-filter-bar -> grid-shell -> NativeSearchTable`
4. 主列表交互同构：单击行选中并驱动 Inspector，双击行打开 Paper，页面级动作注册到 `usePageCommandBar`
5. Inspector 同构：Header → Info Card → Action Grid → Timeline，且支持展开/收起、独立详情/时间线取数和过期请求丢弃
6. 交互拆分同构：列表逻辑不内联复杂表单与详情实现，表单和只读详情独立文件承载
7. 样式基线同构：标题区、快联过滤器、数据网格、Inspector、详情 paper 的样式语言与 enterprise 一致

禁止事项：

1. 新生成页面引入 `@/mock` 或 `MOCK_ENABLED`
2. 用单个 `list.vue` 同时承载大段表单和详情主实现
3. 用 `partner-crud-workspace`、`crm-crud-workspace`、raw `el-table` 替代主列表工作台
4. 以“功能可跑”为由保留与 enterprise 明显不同的文件拓扑、主表格交互或 Inspector 结构

若现有页面不满足以上任一项，视为未闭环交付，必须在 Implementation 阶段完成重构后才能进入 closure。

### 0.1 隐藏契约字段保真门禁

Enterprise 页面生成与重构除了满足七件套和 DOM 契约，还必须满足 **编辑往返保真**。凡是通过 `detail` / `getDetail()` / `prepare-*` 接口回填到表单模型、但未在主表单中显式编辑展示的字段，默认视为 **隐藏契约字段**，必须被保留到提交命令中，除非架构契约明确声明由后端重算或允许清空。

典型字段包括但不限于：

1. 模板/快照桥接字段：`templateSnapshotJson`、`templateType`、`snapshotVersion`
2. 附件/证据桥接字段：`attachmentJson`、`imageJson`、`evidenceJson`
3. 子表/分解项字段：`chargeItems`、`lineItems`、`costItems`
4. 明细回算字段：`copperComponent`、`processingFee`、`lineAmount`、`quotationDetailSortOrder`
5. 任何 `read-only-derived` 但参与后端保存、审批、回算、对外呈现的字段

强制检查规则：

1. `getDetail()` / `prepare-*` / 详情回填结果中的隐藏契约字段，必须在 `syncFormData`、等价表单初始化逻辑或局部状态同步逻辑中被保留。
2. `buildSubmitPayload`、`toSubmitCmd`、`handleSave` 等提交路径必须显式保留这些字段，禁止因为表单 UI 未展示而省略。
3. 子表/分解项字段若不在当前页面可编辑，仍必须在二次编辑保存时原样保留，或由架构契约明确声明后端重建来源。
4. `read-only-derived` 字段如果后端命令契约要求提交，则前端必须保留；若不需要提交，必须在 UI handoff contract 中标注 `backend-recomputed`。
5. 任何“详情加载存在、提交丢失”的情况视为 `Critical`，不得进入 closure。

推荐最小可执行核查：

1. 对照 `detail` 返回结构与提交 DTO，列出所有隐藏契约字段。
2. 逐项核对 `syncFormData` / `clone*` / `buildSubmitPayload` 是否覆盖。
3. 对二次编辑场景执行一次“加载详情 → 不改隐藏字段 → 保存”的 round-trip 检查。

---

## 1. 技术栈硬约束

| 层级 | 技术 | 禁止项 |
|---|---|---|
| 框架 | Vue 3 + `<script setup>` Composition API | 禁止 Options API、禁止 `avue-crud` |
| UI 库 | Element Plus（全局注册） | 禁止引入 Ant Design / Vuetify / 第三方 CRUD 框架 |
| 状态 | Vuex（沿用平台约定） | 禁止引入 Pinia（除非平台统一迁移） |
| HTTP | `@/axios.js`（含认证注入、加密） | 禁止裸 fetch / 裸 axios 实例 |
| 国际化 | vue-i18n，所有用户可见文案走 `t()` | 禁止硬编码中文 label |
| 样式 | CSS 变量 `var(--im-*)` + scoped style | 禁止 Tailwind / UnoCSS 直接内联 |
| 路由 | 菜单驱动动态路由 | 新业务页面不在 `router/views/index.js` 添加静态路由 |

---

## 2. 设计令牌（Design Tokens）— 唯一数据源

所有页面样式**必须**通过 `var(--im-*)` 引用，**禁止**硬编码十六进制色值。

### 2.1 核心令牌速查

```
┌─────────────────── 颜色 ───────────────────┐
│ --im-color-primary          #0f6cbd        │ 按钮/链接/强调
│ --im-color-primary-hover    #005a9e        │ 悬停态
│ --im-color-primary-alpha-5  rgba(…,0.05)   │ 行 hover 背景
│ --im-color-primary-alpha-10 rgba(…,0.10)   │ 选中行背景
│ --im-color-primary-alpha-15 rgba(…,0.15)   │ focus ring
│ --im-color-success          #10b981        │ 正常/运行中
│ --im-color-warning          #f59e0b        │ 预警/降级
│ --im-color-danger           #ef4444        │ 异常/红线
├─────────────────── 背景层级 ─────────────────┤
│ --im-bg-level-1             #1e1e1e        │ AppRail（最深）
│ --im-bg-level-2             #f3f4f6        │ NavPane / 页面背景
│ --im-bg-level-3             #ffffff        │ 内容区 / 卡片
│ --im-bg-level-4             #f9fafb        │ Inspector / 摘要栏
├─────────────────── 文字 ────────────────────┤
│ --im-text-primary           #111827        │ 标题/关键数据
│ --im-text-regular           #4b5563        │ 正文/表格
│ --im-text-secondary         #6b7280        │ Label/辅助
│ --im-text-placeholder       #9ca3af        │ 占位符
├─────────────────── 边框 ────────────────────┤
│ --im-border-color           #e5e7eb        │ 主边框
│ --im-border-color-light     #f3f4f6        │ 网格分割线
├─────────────────── 字号 ────────────────────┤
│ --im-font-2xs  11px │ --im-font-xs  12px   │ Label/Tag/表头
│ --im-font-sm   13px │ --im-font-base 14px  │ 正文/菜单
│ --im-font-lg   16px │ --im-font-xl  20px   │ 页面/模块标题
├─────────────────── 间距 ────────────────────┤
│ --im-spacing-1  4px  │ --im-spacing-2  8px  │
│ --im-spacing-3  12px │ --im-spacing-4  16px │
│ --im-spacing-6  24px │ --im-spacing-8  32px │
├─────────────────── 动效 ────────────────────┤
│ --im-duration-fast    150ms                │ 色彩/边界变更
│ --im-duration-normal  300ms                │ 面板/抽屉过渡
│ --im-duration-slow    400ms                │ 大尺度空间滑入
│ --im-easing-standard  cubic-bezier(0.16,1,0.3,1) │ 物理阻尼
├─────────────────── 布局 ────────────────────┤
│ --im-app-rail-width       56px             │
│ --im-nav-pane-width       160px            │
│ --im-command-bar-height   48px             │
│ --im-inspector-width      320px            │
│ --im-row-height-compact   32px             │
│ --im-a4-max-width         1000px           │
├─────────────────── Z-Index ─────────────────┤
│ --im-z-header   50  │ --im-z-drawer 100    │
│ --im-z-modal   200  │ --im-z-popover 300   │
│ --im-z-toast   400  │                      │
└─────────────────────────────────────────────┘
```

### 2.2 主题兼容规则

**核心机制**: `body.className` 为替换式设置，Industrial 系列主题双 class 叠加。

| 主题键 | body class | 仅覆写的令牌 |
|---|---|---|
| `theme-modern` (默认) | `theme-industrial theme-modern` | — (使用 design-tokens.scss 原值) |
| `theme-app-rail` | `theme-industrial theme-app-rail` | `--im-bg-level-1: #0b1220`, `--im-color-primary: #14b8a6` |
| `theme-d356` | `theme-industrial theme-d356` | `--im-bg-level-1: #0f6cbd`, `--im-bg-level-2: #f3f8fc` |
| `theme-salesforce` | `theme-industrial theme-salesforce` | `--im-bg-level-1: #0176d3`, `--im-bg-level-2: #f4f8fb` |
| `theme-fiori` | `theme-industrial theme-fiori` | `--im-bg-level-1: #0a6ed1`, `--im-bg-level-2: #f4f7fb` |

**页面代码主题兼容要求**:
1. **禁止硬编码颜色** — 所有颜色必须通过 `var(--im-*)` 引用，切换主题时自动跟随
2. **禁止硬编码尺寸** — AppRail/NavPane/CommandBar 尺寸使用令牌变量
3. **背景色分层使用** — 页面背景用 `--im-bg-level-2`，卡片用 `--im-bg-level-3`，Inspector 用 `--im-bg-level-4`
4. **选中态强制使用** — `background: var(--im-color-primary-alpha-10)` + `box-shadow: var(--im-shadow-inset-left)`
5. **scoped style 中引用全局令牌** — 通过 `var()` 在 scoped 中自然可达，无需额外导入

### 2.3 设计令牌桥接架构（Bridge Token Layer）

业务模块不直接使用 `--im-*` 基础令牌来定义表面语义色，而是通过 **`--eam-*` 桥接令牌层**间接引用，实现业务视觉可定制：

```
┌─────────────────────────────────────────────────────────┐
│ Layer 1: design-tokens.scss        → --im-*  (基础)      │
│ Layer 2: business-theme-bridge.scss → --eam-* (业务语义)  │
│ Layer 3: page scoped style         → var(--eam-*, var(--im-*)) │
└─────────────────────────────────────────────────────────┘
```

**桥接令牌速查** (定义在 `src/styles/modules/eam-theme-bridge.scss`):
```
--eam-surface-form          表单页背景 (默认 → --im-bg-level-3)
--eam-surface-section       分节卡片背景
--eam-surface-section-head  分节头背景 (默认 transparent)
--eam-surface-paper         Paper 详情页面纸背景
--eam-surface-paper-bg      Paper 外层背景
--eam-surface-topbar        Banner Topbar 背景
--eam-accent                业务强调色 (默认 → --im-color-primary)
--eam-accent-light          业务强调色浅色
--eam-paper-stamp-border    Paper 状态戳边框色
--eam-paper-stamp-fg        Paper 状态戳文字色
```

**使用规则**:
1. Skeleton 中所有表面背景色必须使用 `var(--eam-*, var(--im-*))` 双层 fallback
2. 非表面色（文字、边框、间距、字号）继续直接使用 `--im-*`
3. 新业务模块可创建自己的桥接 SCSS（如 `crm-theme-bridge.scss`），覆盖 `--eam-*` 命名空间或使用新前缀

### 2.4 页面内禁止出现的样式模式

```css
/* ❌ 禁止 */
.my-card { background: #ffffff; }
.my-text { color: #333333; font-size: 14px; }
.my-btn  { border-radius: 4px; }

/* ✅ 正确 */
.my-card { background: var(--im-bg-level-3); }
.my-text { color: var(--im-text-primary); font-size: var(--im-font-base); }
.my-btn  { border-radius: var(--im-border-radius-md); }
```

---

## 3. Shell 布局架构 — 页面在哪里渲染

```
┌──────────────────────────────────────────────────────────────┐
│ <body class="theme-industrial theme-modern">                 │
│  <div class="im-shell">                                     │
│   ┌────────┬──────────┬────────────────────────────────────┐ │
│   │AppRail │ NavPane  │  im-shell__main                    │ │
│   │ 56px   │ 160px    │ ┌──────────────────────────────┐   │ │
│   │ 深色    │ 树形菜单  │ │ CommandBar  48px             │   │ │
│   │ 不可控  │ 不可控    │ │ 面包屑 ← → 搜索 + 页面操作   │   │ │
│   │        │          │ ├──────────────────────────────┤   │ │
│   │        │          │ │ im-shell__workspace          │   │ │
│   │        │          │ │   ← <router-view />          │   │ │
│   │        │          │ │   ← 你的页面在此渲染           │   │ │
│   │        │          │ └──────────────────────────────┘   │ │
│   └────────┴──────────┴────────────────────────────────────┘ │
│  </div>                                                      │
│ </body>                                                      │
└──────────────────────────────────────────────────────────────┘
```

**关键约束**:
- 页面作者**不控制** AppRail / NavPane / CommandBar — 它们由 IndustrialShell 组件管理
- 页面内容渲染在 `im-shell__workspace` 区域，自身需要完整地管理内部布局
- `usePageCommandBar` composable 是页面向 CommandBar 注册操作按钮的唯一通道
- 主题切换通过 ThemeSwitcher 组件触发，页面无需关心

---

## 4. 标准页面类型与骨架

### 4.1 列表台账页（Standard Grid Page）

> 最高频页面类型。参考实现: 恢复后的 `src/views/partner/enterprise/list.vue`，并作为 Lead / Opportunity / Contract 等台账页的强制结构样板。

**稳定结构顺序（不得交换或省略）**:

```
CommandBar (Shell层, 页面级操作：新建/导出/批量)
  ↓
basic-container
  ├─ wrapper (display: grid)
  │   ├─ Workspace
  │   │   ├─ [可选] 左侧树面板 (org-tree-panel)
  │   │   ├─ [可选] 拖拽分割条 (splitter)
  │   │   └─ workspace-main
  │   │       ├─ 标题区域 (page-title-block)
  │   │       │   ├─ h1 主标题 (font-size: --im-font-xl, font-weight: 600)
  │   │       │   └─ p 业务说明 (font-size: --im-font-sm, color: --im-text-secondary)
  │   │       ├─ 快联过滤器 (quick-filter-bar)
  │   │       │   └─ el-form inline: 3~5个高频字段 + 搜索/重置按钮
  │   │       └─ 数据网格 (grid-shell)
  │   │           └─ <NativeSearchTable>
  │   │               ├─ 自定义列插槽 (#propName)
  │   │               ├─ 行操作插槽 (#menu)
  │   │               └─ 底栏: 左侧统计 + 右侧分页
  │   └─ Inspector 面板 (aside, Enterprise 标准页必选)
  ├─ el-drawer (新建表单)
  ├─ el-drawer (编辑表单)
  ├─ el-drawer (详情/Paper)
  └─ [可选] el-dialog (业务选择器)
```

**标题区域硬约束**:
- 标题区域为**必选结构**，不得省略
- 仅承载：主标题 + 一行业务说明 + 可选状态标签
- **禁止**在标题区域放操作按钮 — 操作按钮统一在 CommandBar
- 面包屑不替代主标题

**快联过滤器硬约束**:
- 3~5 个高频字段常驻
- `el-form` inline 模式，末尾搜索+重置按钮
- 与 NativeSearchTable 的 `v-model:search` 共用**同一个**查询状态对象
- 快联变更后触发 `onLoad()`，不单独管理第二套查询

**底栏双区结构硬约束**:
- 左侧：数据结构统计（至少"当前结果总数"，可选 2~4 个分类统计）
- 右侧：分页控制（总条数、每页条数、页码、跳页）
- 左侧**禁止**放批量操作按钮/金额汇总/详情信息
- 右侧**禁止**放业务统计/筛选条件

**字段覆盖与排序硬约束**:
- 列表页应优先展示 `business-core` 字段：主识别编码、名称、归属组织、关键状态、关键时间、核心数量/金额指标
- 列表页不强求平铺所有字段，但不得以“屏幕不够”为理由永久遗漏业务字段；未进入列表的业务字段必须进入详情页
- 系统控制字段（`id`、`tenantId`、`createUser`、`createTime`、`updateUser`、`updateTime`、`isDeleted`、`version` 等）默认不作为列表主列，不作为快联过滤器
- FK/reference 字段在列表中显示 label/name 字段，不显示原始 ID

**列表精细度硬约束**:
- 主展示列优先采用“双行信息密度”模式：第一行承载编码/名称等核心识别，第二行承载组织、联系人、描述摘要等次信息
- 状态、类型、优先级、审批结果统一用语义 badge/chip 渲染，禁止列表直接暴露原始枚举值
- 标题区只承载标题、说明、状态摘要；业务操作统一放 CommandBar，不得回退到标题区按钮堆叠
- Inspector 仅承载摘要、快捷动作、最近活动，不得把完整表单或完整详情主实现塞入右侧摘要面板
- 行操作遵循“高频直显、低频收纳”原则；当动作超过 4 个时必须分层，避免列表行横向失控

**主表格交互与视觉契约**:
- 单击行只做选中和 Inspector 更新；双击行打开 Paper 详情；行内按钮必须 `@click.stop`，不得触发行选中副作用
- CommandBar 承载新增、刷新、导出、批量、Inspector 展开/收起等页面级动作；行内 menu 只承载当前行的详情、编辑和状态动作
- 快联过滤器与 `NativeSearchTable v-model:search` 共用同一个 `search` 对象；禁止维护第二套 query/filter 状态
- 底栏左侧只放结果统计和轻量分类统计，右侧只放分页；不得把批量操作、金额总计或详情信息塞入底栏
- 第一列必须是“主信息 + 次信息”身份单元格，状态/类型字段必须为语义 badge，金额/数量/日期字段按业务语义对齐，操作列固定右侧
- 列宽、行高、空态、加载态、选中态和 hover 态必须稳定，不因文案、语言切换、状态切换或按钮显隐造成布局跳动
- 主列表禁止 raw `el-table`；`el-table` 仅允许用于 form/paper 内部子表或局部业务明细

### 4.2 新增/编辑表单页（Form Page）

> 通常内嵌于 `el-drawer` 或 `CustomDrawer` 中。参考: `src/views/partner/enterprise/form.vue`
> **骨架模板**: `references/form-page-skeleton.md` — 可直接复制的 Vue 3 `<script setup>` 骨架

**三段式结构 (Header-Body-Footer)**:

```
Drawer/Dialog
  ├─ Header (sticky): 返回按钮 + 标题 + 模式标签 + 保存/暂存按钮
  ├─ Progress Steps (≥3 sections 时): el-steps 导航
  ├─ Body (scroll): el-form.im-form-field--underline
  │   ├─ .im-section-card: Section 1 (im-section-head + im-section-body)
  │   │   └─ el-row > el-col > el-form-item
  │   ├─ .im-section-card: Section 2
  │   ├─ .im-section-card--inherited: MDM 继承字段 (disabled + dashed 底线)
  │   └─ .im-hint-card: 自动建议/说明 (不混入字段网格)
  └─ Footer (sticky): 取消按钮 + 保存按钮 (primary)
```

**表单规范**:
- 必填字段先行，可选字段后置或折叠
- 分组标题使用 `.im-section-head` (图标 + 标题 + 可选 badge)
- 表单域使用 `el-form` + `:rules` 校验
- 提交使用 `async/await` + loading 态 + 成功消息
- 支持 `mode` prop (`create` / `edit`) 控制行为差异
- 编辑模式自动加载已有数据
- 新建模式支持 `sessionStorage` 草稿持久化

**控件双状态契约（编辑态 / 阅读态）**:
- Enterprise 表单必须支持编辑态与阅读态两种控件表现，不允许只实现可编辑输入态
- 阅读态保持原有 `el-form`、`el-row`、`el-col`、`el-form-item` 布局，不得替换为散落的 `div` / `span` 文本块
- 阅读态字段默认使用下划线只读样式：保留标签、值、底线，移除可编辑 affordance；视觉上应为“可读但不可写”，而不是默认禁用灰框
- 系统回填字段、继承字段、审批查看态字段优先复用同一套阅读态控件样式，避免页面内同时出现多种只读表现
- 若同一表单组件需覆盖 `create` / `edit` / `read`，推荐显式暴露 `mode` 或 `readonly` 输入，禁止依赖散落条件分支临时拼装

**Section 图标契约**:
- 每个业务 section 必须有图标，至少覆盖 `.im-section-head` 标题区与 section 导航项
- 图标按“主题语义”选择，而不是按个人偏好选择；同一主题在同一模块内必须保持一致
- 标准主题图标应统一登记在 `src/config/enterpriseSectionIcons.js`；页面内优先通过 registry 解析，不为标准 section 重复本地 import 图标
- 由于 `src/main.js` 已全局注册 `@element-plus/icons-vue`，registry 应存储全局组件名字符串（如 `Document`、`User`、`Coin`），页面通过 `<component :is="iconName" />` 渲染
- 新增 section 主题时，先补 registry，再落页面；禁止先在页面内发明局部图标语义、后续无人收敛

**字段治理硬约束**:
- 新增/编辑页应尽可能覆盖全部业务字段；屏幕密度问题通过 section、折叠区、detail 页解决，而不是静默删字段
- 系统控制字段默认不进入 create/edit，可在 detail 中只读显示
- 重要字段必须前置，且在 create/edit 中显式配置必填规则
- FK/reference 字段必须按照 selector contract 渲染：`lookup` → `ImLookupSelector`，`select` → `el-select`，`cascader` → 级联组件
- 继承只读字段、系统生成字段、状态回填字段必须用只读样式区分，不得伪装成普通可编辑输入框

**下划线字段样式 (强制)**:
- 表单容器添加 `.im-form-field--underline` 类（全局定义在 `page-common.scss`）
- 输入域默认 hover 为 1px 底线增强，focus 为 2px 深色底线
- 禁用字段 dashed 底线 + 浅灰背景
- 阅读态字段保持下划线样式，但应去除输入态 hover/focus 反馈，仅保留值与底线的阅读性
- 禁止退化为默认矩形边框表单

**Progress Steps 触发规则**:
```
IF sectionList.length >= 3 THEN 显示 el-steps (simple 模式)
ELSE 不显示，仅用 section card 分段
```

**提示信息规则**:
- 自动建议类信息不混入字段网格，使用 `.im-hint-card` 只读容器
- MDM 继承字段使用 `.im-section-card--inherited` + `disabled` + `.im-field--readonly`

**字段分组规则**:
- 字段数 ≥ 5 时，必须按用户任务主题分组，禁止按数据库字段顺序平铺
- 推荐顺序：核心识别信息 → 业务主属性 → 组织/归属 → 状态/审批 → 财务/数量 → 备注/扩展
- 字段分组输出应明确：每组标题、组内字段、哪些字段必填、哪些字段只读、哪些字段来自外部主数据

### 4.3 只读详情页（Paper/Detail Page）

> 参考: `src/views/partner/enterprise/paper.vue`
> **骨架模板**: `references/paper-page-skeleton.md` — 可直接复制的 Vue 3 `<script setup>` 骨架

**A4 数字档案纸模式**:
```
Wrapper (centered, bg: --im-bg-level-4)
  ├─ Paper Toolbar (sticky): 返回 + 刷新 + 复制编码 + 打印
  └─ .im-paper (max-width: --im-a4-max-width, min-height: 297mm, padding: 20mm 15mm)
      ├─ .im-paper-watermark: 编码水印 (30° 旋转, 超浅灰)
      ├─ .im-paper-header: 宋体标题 + 双下划线 + 编码 + 状态 Badge
      ├─ .im-metric-grid: 4 格关键指标 (可选)
      ├─ .im-archive-section: Section 1
      │   ├─ .im-archive-section-header: 4px 蓝左线 + 渐变背景 + 序号 + 标题
      │   └─ .im-kv-grid: 2 列键值网格 (140px label + auto value × 2)
      ├─ .im-archive-section: Section 2
      ├─ .im-archive-section: Section N
      └─ .im-archive-section: 时间线/操作日志
          └─ .im-timeline: 左竖线 + 语义色圆点 + 事件卡片
```

**Paper 强约束**:
- Header 必须采用"档案纸抬头"结构：`.im-paper-doc-title` (宋体) + `.im-paper-doc-code` + `.im-badge` 状态标签 + 4 格关键指标区，不得直接以 `el-descriptions border` 替代
- 正文分节使用 `.im-archive-section-header`（蓝左线+渐变+序号+标题），字段展示优先使用 `.im-kv-grid` (label/value 组合)，而不是密集表格堆叠
- 全宽字段（描述/备注）使用 `.im-kv-val--full` (grid-column: 2 / -1)
- 存在状态迁移或跟进过程时，必须补 `.im-timeline`，不允许只给静态字段快照
- 时间线圆点使用语义色: `--success`(绿) / `--warning`(黄) / `--danger`(红) / `--primary`(蓝) / `--default`(灰)
- 状态/类型字段统一使用 `.im-badge--{type}` 渲染，颜色通过 `var(--im-badge-*)` 令牌
- 必须包含 `@media print` 优化：隐藏工具栏、移除阴影、Badge 保色
- 数据加载使用 `Promise.all` 并行加载详情 + 时间线，配合竞态计数器防止过期响应
- Banner Topbar 必须包含: eyebrow 面包屑 + 标题 + 状态 chips + 操作按钮
- Paper 页 CSS 使用 `@starting-style` 入口动画（配 `prefers-reduced-motion` 回退）
- Paper 的 section 图标语义必须与 Form 对应 section 保持一致；同一业务主题不得在表单与详情页切换不同图标语言
- Paper 仍是只读页面，但 section 抬头、关键指标和时间线的视觉语义必须足以替代“阅读态表单”，不能退化成一页静态文本

### 4.4 Inspector 面板（辅助属性面板）

> 列表行选中后右侧摘要。参考: 恢复后的 `partner/enterprise/list.vue` 中的 `enterprise-inspector` 区域；恢复完成前以 `eam/assetLedger/list.vue` 的右侧面板作为密度和交互细节参照。

```
aside.inspector (width: --im-inspector-width)
  ├─ Inspector Header: 单行标题 + 收起按钮
  ├─ Info Card: 标题 + 副标题 + 编码链接 + 编号行右侧状态 badge + 双列核心摘要
  ├─ Action Grid: 3 列快捷操作按钮
  ├─ Timeline: 最近 N 条操作记录
```

**Inspector 独立取数规则**:
- 不直接只复用列表行数据，优先以 `getDetail()` 为权威输入；若存在独立 timeline 接口则追加聚合，没有独立接口时允许由详情字段派生 timeline
- 使用 `inspectorRequestToken` 递增计数器做竞态保护（快速切换行时丢弃过期响应），不使用 `Promise.allSettled`
- Inspector 展开/收起时左侧表格宽度平滑调整，使用 `.is-inspector-open` 显式类（非 `:has()` 选择器）
- Inspector 的标准内容顺序：Header → Info Card → Action Grid → Recent Timeline；禁止额外插入 Quick Actions 标题和底部 Footer CTA
- Inspector 用于“摘要”和“快捷动作”，不要把完整详情字段全部塞进 Inspector，避免视觉污染
- Inspector 的 enterprise 标准最终落点是恢复后的 `src/views/partner/enterprise/list.vue`：克制 header、单张 info card、三列动作矩阵、纵向 timeline；`src/views/eam/assetLedger/list.vue` 仅作为恢复期间的微交互和密度参考，不得替代 Enterprise 准则
- Header 保持单行标题，不在 header 内堆叠多层 meta、页内位次或状态 chips；这些信息应收纳在 info card 或 timeline 中
- Info Card 必须由 `info-top` 和 `info-bottom` 两层组成：上层放对象标题、副标题、编号行，下层放 2 个核心摘要字段；编号行必须是“左侧可点击编号标签 + 右侧状态 badge”结构，禁止把状态 badge 挪到 header 或底部
- Info Card 的标题、副标题、编号、状态、核心摘要值必须绑定真实字段或字典翻译；字段缺失时使用统一空值占位，不得人为注入“系统”“默认联系人”“演示链接”等伪业务值补版
- Action Grid 固定 3 列，每个按钮包含 icon、label、可选的 small meta；优先绑定真实动作（详情、刷新、外链、编辑等），按钮数控制在 3~4 个，且不得再额外渲染“快速动作”标题
- Action Grid 中的 meta 文案必须来自真实字段；无值时允许留空或隐藏，不得为了对齐版式拼接示例文案
- Timeline 允许在缺少独立日志接口时由详情字段派生，但必须包含时间、操作者、标题、说明和语义色圆点；视觉上对齐 EAM `tl-item / tl-dot / tl-meta / tl-text / tl-desc` 组织，不得用纯 `el-descriptions` 或纯文本列表替代
- Inspector 最小精度基线按恢复后的 `src/views/partner/enterprise/list.vue` 执行；恢复完成前按 `src/views/eam/assetLedger/list.vue` 的右侧面板约束：`.insp-body` 的 `padding/gap <= 8px`，`.insp-info-card` 的 `padding <= 12px`，`.info-top` 的 `padding-bottom` 与 `margin-bottom <= 10px`，`.info-title` 的 `margin-bottom <= 4px`，`.info-subtitle` 的 `margin-bottom <= 8px`，`.info-code-row` 的 `gap <= 6px`，`.info-status-badge` 的 `min-height <= 26px` 且保持右侧着色，`.action-grid` 的 `gap <= 6px`，`.action-btn` 的垂直内边距不大于 `10px 0 9px`，`.action-btn__meta` 的 `line-height <= 1.25`，`.tl-item` 的 `margin-bottom <= 12px`
- Inspector 视觉目标是“EAM 风格轻量操作摘要面板”，保持高信息密度、低装饰度、强边框层级，不采用大面积渐变 paper-lite 视觉

---

## 5. 核心组件使用规范

### 5.1 NativeSearchTable

**位置**: `src/components/NativeSearchTable/index.vue`

**必传 Props**:
```vue
<NativeSearchTable
  v-model:page="page"
  v-model:search="search"
  :data="data"
  :option="tableOption"
  :table-loading="loading"
  :stats="tableStats"
  @on-load="onLoad"
  @row-click="handleRowClick"
  @row-dblclick="handleRowDblClick"
>
  <!-- 自定义列: 状态 Badge -->
  <template #status="{ row }">
    <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
  </template>
  <!-- 行操作菜单 -->
  <template #menu="{ row }">
    <el-button link type="primary" @click="openEdit(row)" v-if="permissionValue.edit">
      {{ t('common.edit') }}
    </el-button>
    <el-button link type="primary" @click="openDetail(row)">
      {{ t('common.detail') }}
    </el-button>
  </template>
</NativeSearchTable>
```

**Option Schema 标准写法** (`src/option/<module>/<entity>.js`):
```javascript
export const createTableOption = (t) => ({
  border: true,
  stripe: false,
  index: true,
  selection: false,
  menuWidth: 160,
  menuDisplayMode: 'floating',   // 'inline' | 'floating'
  columnWidthMode: 'fixed',
  rowDisplayMode: 'single',      // 'single' | 'double'
  column: [
    {
      label: t('module.entity.field1'),
      prop: 'field1',
      width: 140,
      fixed: 'left',
      sortable: 'custom',
      filterType: 'input',
      headStatus: 1,
    },
    {
      label: t('module.entity.status'),
      prop: 'status',
      width: 100,
      filterType: 'select',
      dicData: createStatusDic(t),
      headStatus: 1,
    },
    // ... 更多列
  ],
});

export const createStatusDic = (t) => [
  { label: t('module.entity.status.active'), value: 'ACTIVE' },
  { label: t('module.entity.status.inactive'), value: 'INACTIVE' },
];
```

**外键字段列配置** (foreignKey=true):

当字段声明为外键时，Option 中应使用 `{fieldName}Name` 作为列 prop，显示引用实体的可读名称，而非原始 ID 值：
```javascript
{
  label: t('module.entity.enterpriseId'),  // "所属企业"
  prop: 'enterpriseIdName',                // ← VO 中由 Wrapper 翻译填充的名称字段
  minWidth: 180,
  headStatus: 1,
},
```

**page 状态标准初始值**:
```javascript
const page = ref({ currentPage: 1, pageSize: 15, total: 0 });
```

**stats 统计标准格式**:
```javascript
const tableStats = computed(() => [
  { label: t('common.stats.total'), count: page.value.total },
  { label: t('module.entity.status.active'), count: activeCount.value, color: 'success' },
  { label: t('module.entity.status.inactive'), count: inactiveCount.value, color: 'warning' },
]);
```

### 5.2 usePageCommandBar

**位置**: `src/composables/usePageCommandBar.js`

**标准接入模式**:
```javascript
const { activatePageCommandBar } = usePageCommandBar({
  pageCode: 'module-entity-list',
  pageTitle: t('module.entity.pageTitle'),
  actions: [
    {
      key: 'add',
      label: () => t('common.add'),
      type: 'primary',
      order: 10,
      permissionCode: 'module_entity_add',
    },
    {
      key: 'refresh',
      label: () => t('common.refresh'),
      order: 20,
    },
    {
      key: 'export',
      label: () => t('common.export'),
      order: 30,
      permissionCode: 'module_entity_export',
    },
  ],
  handlers: {
    add: openCreate,
    refresh: () => onLoad(),
    export: handleExport,
  },
  getContext: () => ({
    selectedRowId: selectedRow.value?.id,
    hasSelection: !!selectedRow.value,
  }),
});
```

**pageCode 命名规则**: `{module}-{entity}-{pageType}` ，例如 `partner-enterprise-list`

### 5.3 basic-container

**用法**:
```vue
<basic-container>
  <!-- 页面内容 -->
</basic-container>
```
- 提供 `el-card` 包装 + 标准内边距
- 自动继承主题色（通过 industrial.scss 的 el-card 覆写）
- 所有列表页和表单页的最外层容器

### 5.4 CustomDrawer

**用法**:
```vue
<CustomDrawer
  v-model="drawerVisible"
  :title="t('module.entity.createTitle')"
  :size="'70%'"
  @saveFun="handleSave"
>
  <el-form ref="formRef" :model="formData" :rules="formRules">
    <!-- 表单内容 -->
  </el-form>
</CustomDrawer>
```
- `destroy-on-close` 默认开启
- 也可直接用 `el-drawer` — CustomDrawer 只是快捷封装

---

## 6. 脚本区标准状态设计

### 6.1 必备状态分组

```javascript
// ── 1. 数据状态 ──
const data = ref([]);
const page = ref({ currentPage: 1, pageSize: 15, total: 0 });
const search = ref({});
const loading = ref(false);

// ── 2. 快联过滤器（与 search 同步） ──
const quickFilters = ref({
  keyword: '',
  status: '',
  // ... 3~5 个高频字段
});

// ── 3. 选中与联动态 ──
const selectedRow = ref(null);

// ── 4. 弹层开关（每个弹层独立开关，禁止字符串枚举混用） ──
const createDrawerOpen = ref(false);
const editDrawerOpen = ref(false);
const detailDrawerOpen = ref(false);
const editTargetId = ref(null);
const detailTargetId = ref(null);

// ── 5. Inspector 状态 ──
const inspectorOpen = ref(false);
const inspectorData = ref({});
const inspectorTimelineData = ref([]);

```

### 6.2 核心方法骨架

```javascript
// ── 列表加载 ──
const onLoad = async (pageParam = page.value) => {
  loading.value = true;
  try {
    const params = normalizeSearch(search.value);
    const res = await getList(pageParam.currentPage, pageParam.pageSize, params);
    data.value = res.data.data.records;
    page.value.total = res.data.data.total;
  } catch (err) {
    ElMessage.error(err?.message || t('common.loadFailed'));
  } finally {
    loading.value = false;
  }
};

// ── 快联过滤器同步 ──
const applyQuickFilters = () => {
  Object.entries(quickFilters.value).forEach(([key, val]) => {
    if (val !== '' && val != null) search.value[key] = val;
    else delete search.value[key];
  });
  page.value.currentPage = 1;
  onLoad();
};

const resetQuickFilters = () => {
  Object.keys(quickFilters.value).forEach(k => quickFilters.value[k] = '');
  search.value = {};
  page.value.currentPage = 1;
  onLoad();
};

// ── 行交互 ──
const handleRowClick = (row) => {
  selectedRow.value = row;
  if (inspectorOpen.value) loadInspector(row.id);
};

const handleRowDblClick = (row) => {
  openDetail(row);
};

// ── CRUD 操作 ──
const openCreate = () => { createDrawerOpen.value = true; };
const openEdit = (row) => { editTargetId.value = row.id; editDrawerOpen.value = true; };
const openDetail = (row) => { detailTargetId.value = row.id; detailDrawerOpen.value = true; };

const handleCreateSuccess = () => {
  createDrawerOpen.value = false;
  ElMessage.success(t('common.createSuccess'));
  onLoad();
};

const handleEditSuccess = () => {
  editDrawerOpen.value = false;
  ElMessage.success(t('common.updateSuccess'));
  onLoad();
};

// ── Inspector 加载（竞态保护 + 多接口聚合） ──
const inspectorRequestToken = ref(0);
const inspectorLoading = ref(false);

const loadInspector = async (row) => {
  const token = ++inspectorRequestToken.value;
  inspectorLoading.value = true;
  try {
    const [detailRes, timelineRes] = await Promise.all([
      getDetail(row.id),
      getTimeline(row.id),
    ]);
    if (token !== inspectorRequestToken.value) return; // 过期请求丢弃
    inspectorData.value = detailRes.data.data;
    inspectorTimelineData.value = timelineRes.data.data;
  } finally {
    if (token === inspectorRequestToken.value) inspectorLoading.value = false;
  }
};
```

### 6.3 权限接入

```javascript
import { computed } from 'vue';
import { useStore } from 'vuex';

const store = useStore();
const permissionValue = computed(() => ({
  add: store.getters.permission?.module_entity_add ?? false,
  edit: store.getters.permission?.module_entity_edit ?? false,
  delete: store.getters.permission?.module_entity_delete ?? false,
  view: store.getters.permission?.module_entity_view ?? false,
  export: store.getters.permission?.module_entity_export ?? false,
}));
```

### 6.2 四维权限架构

| 权限维度 | 前端实现 | 后端实现 | 配置方式 |
|---|---|---|---|
| **菜单权限** | 入口 `index.vue` 路径匹配动态路由 | `blade_menu` 表 (category=1) | blade-design 自动生成 menu SQL |
| **按钮权限** | `permissionValue` computed + `v-if` + CommandBar `permissionCode` | `blade_menu` 表 (category=2) | blade-design 自动生成 (add/edit/delete/view)，export 需手工追加 |
| **接口权限** | 全局 axios 拦截器处理 403 | Controller `@PreAuth(menu = "{modelCode}")` + ApiScopeHandler | blade-design 自动生成 @PreAuth，ApiScope 手工配置 |
| **数据权限** | 前端透明（无感知） | DataScopeHandler 行级过滤 | 运行时管理页面配置 |

**权限守卫层级**:
```
前端按钮层 (permissionValue v-if)
  → 前端 CommandBar 层 (permissionCode 隐藏按钮)
    → 后端 @PreAuth (接口级 403)
      → 后端 DataScope (行级过滤)
```

---

## 7. 测试 SQL 与联调数据标准

### 7.1 基本原则

1. 新页面禁止生成前端 Mock 文件、Mock 导入和 Mock 降级逻辑
2. 需要联调、演示或验收数据时，生成后端测试 SQL，而不是在前端伪造数据源
3. 测试 SQL 仅在用户要求或切片明确需要演示数据时生成

### 7.2 产物位置

文件位置：`mes-enreach-mom-base-server/doc/sql/<module>/<entity>-test-seed.sql`

命名要求：

1. 与业务对象一一对应
2. 可重复执行，优先使用 `DELETE + INSERT` 或幂等 upsert 形式
3. 与字典种子、菜单种子、DDL 命名口径一致

### 7.3 示例

```sql
-- crm-opportunity-test-seed.sql
DELETE FROM crm_opportunity WHERE oppty_code IN ('OPP-DEMO-0001', 'OPP-DEMO-0002');

INSERT INTO crm_opportunity (
  id,
  oppty_code,
  oppty_name,
  oppty_status,
  current_stage_id,
  estimated_amount,
  currency,
  tenant_id,
  create_user,
  create_time,
  is_deleted
) VALUES
  (1900000000000000001, 'OPP-DEMO-0001', '演示商机A', 'ACTIVE', 101, 250000.00, 'CNY', '000000', 112233, NOW(), 0),
  (1900000000000000002, 'OPP-DEMO-0002', '演示商机B', 'SUSPENDED', 102, 800000.00, 'CNY', '000000', 112233, NOW(), 0);
```

---

## 8. 国际化规范

### 8.1 命名空间模式

每个业务模块使用独立命名空间:

```javascript
// src/lang/zh.js 中追加
module: {
  entity: {
    pageTitle: '企业主档管理',
    pageDesc: '管理合作伙伴企业的基本信息、合作状态和生命周期',
    table: {
      columns: {
        code: '企业编码',
        name: '企业名称',
        status: '合作状态',
      },
    },
    quickFilter: {
      keyword: { label: '搜索', placeholder: '编码或名称' },
      status: { label: '状态', placeholder: '选择状态' },
    },
    status: {
      active: '有效',
      inactive: '无效',
    },
    commandBar: {
      actions: { add: '新建企业', refresh: '刷新', export: '导出' },
    },
    form: {
      createTitle: '新建企业',
      editTitle: '编辑企业',
    },
    messages: {
      createSuccess: '企业创建成功',
      updateSuccess: '企业更新成功',
      deleteConfirm: '确定删除该企业？',
    },
  },
},
```

### 8.2 中英文双语言同步要求

每次新增 `zh.js` 命名空间时，必须同步在 `en.js` 中追加对应英文条目。

---

## 9. 文件清单与命名规范

### 9.1 列表页标准文件清单

一个完整的列表型业务模块至少包含:

```
src/
├─ views/<module>/
│   ├─ <entity>.vue                          路由壳文件（仅挂载 entry）
│   └─ <entity>/
│       ├─ <module>-<entity>-entry.vue      入口包装页
│       ├─ list.vue                         主列表页
│       ├─ form.vue                         新增/编辑表单页
│       └─ paper.vue                        只读详情/档案页
├─ api/<module>/
│   └─ <entity>.js                           API 封装
├─ option/<module>/
│   └─ <entity>.js                           表格/字典配置
└─ lang/
    ├─ zh.js                                 (追加)
    └─ en.js                                 (追加)

mes-enreach-mom-base-server/
└─ doc/sql/<module>/
   └─ <entity>-test-seed.sql                 按需生成的测试 SQL
```

### 9.2 入口包装页模式

```vue
<!-- src/views/<module>/<entity>.vue -->
<template>
  <EntityEntry />
</template>

<script setup>
import EntityEntry from '@/views/<module>/<entity>/<module>-<entity>-entry.vue';
</script>
```

**为什么需要双层包装**: 菜单 path `/module/entity` 先落到 `views/module/entity.vue`，再由 `<module>-<entity>-entry.vue` 承接 keep-alive 与页面实例边界；这是 enterprise 现行模式，必须直接复用。

### 9.3 API 文件标准

```javascript
// src/api/<module>/<entity>.js
import request from '@/axios';

const prefix = '/<module>/<entity>';

export const getList = (current, size, params) =>
  request({ url: `${prefix}/list`, method: 'get', params: { current, size, ...params } });

export const getDetail = (id) =>
  request({ url: `${prefix}/detail`, method: 'get', params: { id } });

export const create = (data) =>
  request({ url: `${prefix}/submit`, method: 'post', data });

export const update = (data) =>
  request({ url: `${prefix}/submit`, method: 'post', data });

export const remove = (ids) =>
  request({ url: `${prefix}/remove`, method: 'post', params: { ids } });

// 可选：时间线、导出等扩展接口
export const getTimeline = (id) =>
  request({ url: `${prefix}/timeline`, method: 'get', params: { id } });
```

---

## 10. 视觉微调工具类

以下 CSS 类在 `industrial.scss` 中已定义，页面可直接使用（无需在 scoped style 中重复）:

| 类名 | 用途 |
|---|---|
| `.im-mono` | 等宽字体 (编码、金额、配置数字) |
| `.im-section-label` | 极客排版分组标题 (11px 全大写 加宽字距 低保和灰) |

页面自定义 scoped style 应遵循:
- 使用 `var(--im-*)` 引用所有令牌
- 过渡动画使用 `var(--im-duration-*)` + `var(--im-easing-standard)`
- 阴影使用 `var(--im-shadow-*)` 分级

---

## 10.5 View Transitions 与 @starting-style

**View Transitions composable** (`src/composables/useViewTransition.js`):
- 封装 `document.startViewTransition`，自动检测 `prefers-reduced-motion`
- 用于列表页 Inspector 切换、Paper 页面进入等大状态变更

**@starting-style 入口动画** (Paper 骨架已内置):
```css
.im-archive-section {
  opacity: 1; transform: translateY(0);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
@starting-style {
  .im-archive-section { opacity: 0; transform: translateY(8px); }
}
@media (prefers-reduced-motion: reduce) {
  .im-archive-section { transition: none; }
  @starting-style { .im-archive-section { opacity: 1; transform: none; } }
}
```

**使用规则**:
1. 所有 `@starting-style` 必须配套 `prefers-reduced-motion: reduce` 回退
2. 仅用于页面级状态变更（Paper 分节出现、Inspector 展开），不用于表格行
3. 详见 `.github/skills/modern-css-2026/SKILL.md`

---

## 11. 业务选择器组件规范

> 参考: `src/views/eam/components/StandardProductSelector.vue`（EAM 模块实现示例；通用选择器模式适用于所有业务模块）

### 11.1 标准选择器骨架

```vue
<template>
  <el-dialog
    v-model="visible"
    :title="t('module.selector.title')"
    width="800px"
    destroy-on-close
    append-to-body
  >
    <!-- 搜索区 -->
    <el-form inline @submit.prevent="doSearch">
      <el-form-item :label="t('common.keyword')">
        <el-input v-model="keyword" clearable />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="doSearch">{{ t('common.search') }}</el-button>
        <el-button @click="doReset">{{ t('common.reset') }}</el-button>
      </el-form-item>
    </el-form>

    <!-- 数据表格 -->
    <el-table
      :data="tableData"
      highlight-current-row
      @current-change="handleSelect"
      v-loading="loading"
    >
      <el-table-column prop="code" :label="t('module.selector.code')" />
      <el-table-column prop="name" :label="t('module.selector.name')" />
      <!-- 更多列 -->
    </el-table>

    <!-- 分页 -->
    <el-pagination
      v-model:current-page="selectorPage.currentPage"
      v-model:page-size="selectorPage.pageSize"
      :total="selectorPage.total"
      layout="total, prev, pager, next"
      @current-change="loadData"
    />

    <template #footer>
      <el-button @click="visible = false">{{ t('common.cancel') }}</el-button>
      <el-button type="primary" :disabled="!selected" @click="confirm">
        {{ t('common.confirm') }}
      </el-button>
    </template>
  </el-dialog>
</template>
```

### 11.2 选择器 Props/Events 约定

| Prop | Type | 说明 |
|---|---|---|
| `modelValue` | Boolean | v-model 控制显隐 |
| `excludeIds` | Array | 排除已添加的 ID |

| Event | 载荷 | 说明 |
|---|---|---|
| `confirm` | selectedObject | 确认选择后回传完整对象 |

---

## 12. 生成前检查清单

在生成任何页面之前，agent 必须验证:

- [ ] 确认 `bizName` (小驼峰)、`routePath`、`permissionPrefix`、`pageCode` 已定义
- [ ] 确认 i18n 命名空间不与已有模块冲突
- [ ] 确认 API 前缀路径与后端 Controller `@RequestMapping` 一致
- [ ] 设计文档中的核心字段已映射到 option column
- [ ] 每个非系统字段至少出现在一种可见 UI 状态（列表/新建/编辑/详情）
- [ ] 快联过滤器字段已从业务高频字段中选定（3~5 个）
- [ ] 页面 bundle 已按 enterprise 同构准备 `route shell + entry + list + form + paper`
- [ ] 若用户要求演示/联调数据，已规划对应 `doc/sql/<module>/<entity>-test-seed.sql`
- [ ] 权限码已定义（`module_entity_add/edit/delete/view/export`）
- [ ] 权限码与 blade-design 生成的 `{name}.menu.sql` 中 `code` 字段一致
- [ ] 所有颜色使用 `var(--im-*)` — 零硬编码色值
- [ ] 所有文案使用 `t()` — 零硬编码中文

---

## 13. 页面生成后验证清单

生成完成后，agent 必须自检:

- [ ] 在 5 个主题下颜色均从令牌继承，无视觉污染
- [ ] NativeSearchTable 的 option 使用 `createTableOption(t)` 工厂函数
- [ ] 快联过滤器与 `v-model:search` 共用同一个 search 对象
- [ ] CommandBar 已通过 `usePageCommandBar` 注册，actions 带 permissionCode
- [ ] 主列表未使用 raw `el-table`、`partner-crud-workspace`、`crm-crud-workspace` 或 `avue-crud`
- [ ] 主表格第一列为“主信息 + 次信息”身份单元格，状态/类型字段为语义 badge，操作列固定右侧
- [ ] 单击行选中并驱动 Inspector，双击行打开 Paper；行内操作均使用 `@click.stop`
- [ ] 行内操作按钮（编辑/删除/查看）均有 `v-if="permissionValue.xxx"` 权限守卫
- [ ] 删除操作使用 `ElMessageBox.confirm` 二次确认
- [ ] 弹层使用独立 boolean ref 控制（非字符串枚举）
- [ ] 页面结构、抽屉拆分、标题区、quick-filter-bar、grid-shell、Inspector 布局与 enterprise 同构
- [ ] 无新增 `@/mock` import、`MOCK_ENABLED` 分支或前端伪数据降级逻辑
- [ ] Inspector 使用 `inspectorRequestToken` 或等价机制做竞态保护
- [ ] Inspector 结构为 Header → Info Card → Action Grid → Timeline，且无 Quick Actions 标题、无底部 Footer CTA
- [ ] Inspector 所有展示值和动作 meta 均绑定真实字段或字典翻译，无伪业务占位值
- [ ] Inspector 紧凑度不松于 enterprise 基线：body 8px、info card 12px、编号行 6px、动作矩阵 6px、timeline item 12px 以内
- [ ] 路由壳文件与 entry 包装页已创建且路径符合菜单约定
- [ ] 文案已写入 `src/lang/modules/zh-cn/<subsystem>.js` 与 `src/lang/modules/en-us/<subsystem>.js`，不新增 legacy `zh.js / en.js` 业务 key
- [ ] 若用户要求演示/联调数据，测试 SQL 已交付到后端 `doc/sql` 目录
- [ ] 无残留他模块标识（如 `assetLedger`、`eam_asset_ledger` 等）
- [ ] 菜单 SQL 已生成（blade-design `{name}.menu.sql`）且权限码与前端 `permissionValue` 一致
- [ ] 若含导出按钮，菜单 SQL 中已追加 `{modelCode}_export` 按钮行

---

## 14. Legacy References (DO NOT USE)

> ⚠️ `blade-design/references/frontend.md` 中 §二～§六 为 **Avue CRUD 遗留模板**，已标注 DEPRECATED。
> 新业务模块 **禁止** 使用该文件中的 Avue 代码生成。仅 §一（API 层封装）保留有效。
> 所有前端页面生成以本 Skill 和下方骨架文件为权威输入。

---

## 15. Skeleton File Index

| 骨架文件 | 页面类型 | 说明 |
|---|---|---|
| `references/list-page-skeleton.md` | 列表页 (List) | NativeSearchTable + usePageCommandBar + 快联过滤 + 底栏分页 |
| `references/form-page-skeleton.md` | 表单页 (Form) | 三段式 Header-Body-Footer + 下划线字段 + Progress Steps + 草稿持久化 |
| `references/paper-page-skeleton.md` | 详情页 (Paper) | A4 档案纸 + KV Grid + Timeline + 打印优化 + 水印 |
| `references/enterprise-baseline.md` | Enterprise 契约 | 七件套拓扑 + DOM contract + Inspector/Form/Paper closure gates |

**使用方式**: 生成新页面时，复制对应骨架文件中的 Vue SFC 模板，替换 `__MODULE__` / `__ENTITY__` / `__BIZ_NAME__` 占位符，再根据业务字段填充具体内容。
