# UI 契约 — 界面规范

## 色彩系统

### 主色调（从 adminLayout.css / dxCustom.css 提取）

| 用途 | 色值 | 使用场景 |
|------|------|---------|
| 侧边栏背景 | `#1c2b4a` | `#sidebar` 背景 |
| 侧边栏深色（Header/子菜单） | `#16213c` | `#sidebarHeader`、`.menu-children` |
| 侧边栏文字 | `#cfd6e4` | 默认菜单项文字 |
| **主色（蓝）** | `#0C6C9E` | 链接、Grid 表头文字、输入框文字、主按钮 |
| Grid 表头背景 | `#CCE4F6` | `.dx-header-row` |
| 菜单分组标题 | `#f0a830` | `.menu-group-title`（金黄强调色） |
| Tab 激活底线 | `#1c2b4a` | `.tab-item.active` inset shadow |
| Tab 栏背景 | `#eceff3` | `#tabBar` |
| 内容区背景 | `#fff` | `#contentArea` |
| 通知徽章 | `#f5862a` | `#notifyBadge`（橙色） |
| 危险/关闭 | `#d33` | `.tab-close:hover` |
| 必填标记 | `red` | dxForm 必填项 |

### 辅助色

| 用途 | 色值 |
|------|------|
| 可编辑列头底线 | `#03A9F4`（亮蓝） |
| 必填列头底线 | `#f80a79`（品红） |
| 结果列头渐变 | `#b2e264` → `#8ebb5c`（绿色） |
| 间隔列头 | `#a6e1fc`（淡蓝） |
| debug 输出背景 | `#272822`（Monokai 暗色） |
| debug 数字/布尔/null | `#6badf7`（浅蓝） |

### 禁止事项

- 不得引入上表之外的新主色，如需扩展先在本文件添加
- 不得用纯黑 `#000` 作为正文色，用 `#333` 或 `#555`
- 渐变色只用于 Grid 结果列头，其他地方不用渐变

---

## 字体

| 属性 | 值 |
|------|----|
| 字体族 | `"Microsoft YaHei", Arial, sans-serif` |
| 正文字号 | `14px`（body 基准） |
| 最小字号 | `11pt`（dxForm 输入框，约 14.67px），**不得低于此值** |
| 弹窗字号 | `11pt`（`.dx-texteditor-input`） |
| 表单标签 | `font-weight: bold`（`.dx-field-item-label-text`） |
| 通知徽章 | `10px` |
| 标题（系统Logo） | `18px` |

---

## 布局尺寸

| 元素 | 尺寸 |
|------|------|
| 侧边栏展开宽度 | `220px` |
| 侧边栏折叠宽度 | `56px` |
| 侧边栏动画 | `transition: width 0.2s ease` |
| Header / Tab栏高度 | `56px` / `40px` |
| Tab 项高度 | `30px`，padding `0 14px` |
| 图标按钮 | `32×32px` |
| 菜单折叠动画 | `transition: max-height 0.2s ease` |

---

## DevExtreme 配置规范

### 主题

```
dx.material.blue.light.compact.min.css
```

- **不得改用其他主题**，自定义样式统一写在 `dxCustom.css`
- `dxCustom.css` 是唯一允许覆盖 DevExtreme 样式的地方

### DataGrid 标准配置（通过 dxGrid.js 封装）

- 远程操作：`filtering: true, sorting: true, paging: true, grouping: true`
- 列头可编辑标记：`dx-editable-header`（可编辑）/ `dx-editable-must-header`（必填）
- 多列头：`dx-multi-headers`
- 可点击的单元格文字（如点标题打开子页面）：`dx-cell-link`
- 主从页面限定数据范围：`params: { 主表主键: 值 }`，会附加到 grid/save/remove/export 每次请求上，
  由服务端限定范围，不要靠前端 filter 兜底（用户能清掉 filter）
- 业务自定义工具条按钮：`toolbar.custom: [{icon, text, hint, onClick(dataGrid)}]`

### 单元格模板动态改高度

开了 `columnFixing` 之后，固定列（勾选框、操作列）和内容区是**两张独立的 table**，
DevExtreme 只在渲染和 resize 时对齐两边行高。单元格模板在渲染之后自己改高度
（展开/收起、异步插内容）会让两边错位——左边勾选框和右边内容对不上。

`createDxGrid()` 已经在点击后自动补一次对齐（`dxSyncRowHeights()`），**模板里不用写任何东西**。
不走 `createDxGrid()` 的场景，改完高度后自己调一次 `dxSyncRowHeights(dataGrid, selector)`。

不要用 `ResizeObserver` / `requestAnimationFrame` 做这件事：后台标签页的 iframe 处于
`display:none`，不渲染的文档里这两者不派发回调，切回来就已经错位了。

修的永远是 `dxGrid.js`，不是 DevExtreme 源码（见根契约「第三方代码不可改」）。

### 弹窗宽度约定（三档）

| 档位 | 宽度 | 适用场景 |
|------|------|---------|
| 小 | `400px` | 确认框、简单表单 |
| 中 | `700px` | 标准编辑弹窗 |
| 大 | `1000px` | 复杂表单、多字段编辑 |

---

## JS 全局变量

页面通过 `window.APP` 注入，所有 JS 文件只从这里读配置：

```js
window.APP = {
    HOST,          // 站点根路径，如 http://127.0.0.1:8087/mdm/pdc/admin
    STATIC_URL,    // 静态资源根路径
    LOCALE,        // 当前语言，如 'zh-CN'
    GRID_TEXT,     // Grid 工具栏通用文案（由 PHP lg() 翻译后注入）
}
```

- JS 文件里**不直接调用 `lg()`**，文案统一由 PHP 端翻译后通过 `APP.GRID_TEXT` 传入
- 新增 Grid 工具栏文案必须同时在 `header.view.php` 的 `GRID_TEXT` 和语言包里添加

---

## CSS 书写规范

- 自定义样式写在 `admin/static/css/` 下，不内联在 HTML
- 类名语义化，不用 `div1`、`wrap2` 等无意义名称
- 布局用 Flexbox，不用 float
- 不引入 Bootstrap 或其他 CSS 框架（已有 DevExtreme + 自定义 CSS 足够）
