docs: add UI-STYLE.md — platform must reuse CarLog UI

按用户要求「UI 统一」新建 UI 设计规范 docs/UI-STYLE.md (623 行):

- §0 不做的事 (不引新 UI 库 / 不写新 CSS 变量 / 不另起设计)
- §1 设计令牌 (CSS 变量速查: bg/card/text/accent/brand/green/warn/danger)
- §2 工具类速查 (btn/card/input/pill/text-*/flex/gap/mt-*/mobile-only)
- §3 布局组件 (AppLayout / AppHeader / StatCard / MobileCardList / ChartBlock)
- §4 4 个平台 view 完整模板 (GlobalSettings / SubsystemSettings / Subsystems / Dashboard)
- §5 错误 / 消息样式
- §6 标题层级
- §7 主题色使用规则
- §8 移动端注意事项
- §9 Trae 自检清单 (4 条 grep 命令)
- §10 Mavis review 验收点 (12 项)

DEV-PLAN.md 更新:
- Task 2.4 / 2.5 / 2.6 / 2.7 顶部加 ⚠️ UI-STYLE.md 引用 + 强制要求清单
- 6.2.6b 新增「平台前端 UI 规范」子节 (12 项检查 + 3 条 grep 自检)

ARCHITECTURE.md 更新:
- 新增 §7.5 UI 设计原则 + 引用 UI-STYLE.md

README.md:
- 文档列表加 UI-STYLE.md

核心原则: 平台层新页面 = Settings.vue 的视觉风格 + 元数据驱动。
This commit is contained in:
2026-06-20 22:53:37 +08:00
parent a365c8be10
commit 60b7df9015
4 changed files with 720 additions and 1 deletions
+1 -1
View File
@@ -85,7 +85,7 @@
- **物理目录隔离**:subsystem 代码独立目录,加新子系统不会乱碰现有代码
- **永远向后兼容**:CarLog 的现有功能不破
详细见 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) [docs/DEV-PLAN.md](docs/DEV-PLAN.md)。
详细见 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) / [docs/DEV-PLAN.md](docs/DEV-PLAN.md) / [docs/UI-STYLE.md](docs/UI-STYLE.md)(UI 规范 — 平台层 100% 复用 CarLog UI
## Git 仓库
+24
View File
@@ -274,6 +274,30 @@ export function tableName(name) {
4. 通用设置渲染器自动支持新子系统的 settings
5. 验证端到端流程
## 7.5 UI 设计原则
**平台层 UI 100% 复用 CarLog 现有 UI,不另起设计。**
CarLog 已经有完整设计系统:
- 设计令牌:`client/src/style.css` 顶部 `:root`(颜色 / 圆角 / 字体 / 阴影 / 响应式断点)
- 工具类:60+ 个(`.btn` / `.card` / `.input` / `.pill` / `.flex` / `.mt-3` / `.mobile-only` ...
- 组件:`AppLayout` / `AppHeader` / `StatCard` / `ChartBlock` / `MobileCardList` / `ConfirmDangerDialog`
**平台层新页面(GlobalSettings / SubsystemSettings / Subsystems / Dashboard)必须**
- ✅ 用 `<AppLayout>` 包整个页面
- ✅ 颜色 / 字体 / 间距全部走 CSS 变量(`var(--xxx)`)或工具类
- ✅ 复用现有组件(`StatCard` / `MobileCardList` / `ConfirmDangerDialog`
- ✅ 跟现有 `Settings.vue` 视觉一致(卡片 + form + 按钮 + 消息)
**禁止**
- ❌ 引入新 UI 库(ant-design / element-plus / naive-ui / vuetify
- ❌ 写新 CSS 变量(除非有明确理由)
- ❌ inline `style="color: ..."`
- ❌ 自定义 button / card 样式
- ❌ 另起设计语言
完整规范见 [docs/UI-STYLE.md](UI-STYLE.md)(设计令牌 + 工具类速查 + 4 个平台 view 完整模板 + Mavis review 验收点)。
## 8. 不做的事
- ❌ JWT / SSO / 跨域 auth
+72
View File
@@ -780,6 +780,26 @@ cd client && npm run dev
### Task 2.4: 前端平台层 — 总设置 UI
**⚠️ 先看 [docs/UI-STYLE.md](UI-STYLE.md) — 平台层 UI 必须 100% 复用 CarLog 现有 UI,不另起设计。**
参考页面:`client/src/views/Settings.vue`(最接近总设置的风格)
**强制要求**:
- ✅ 用 `<AppLayout>` 包整个页面
- ✅ 标题 `<h1 class="title">` + 副标题 `<p class="text-soft">`
- ✅ loading 用 `<div class="card card-pad text-soft">`
- ✅ error 用 `<div class="card card-pad text-danger">`
- ✅ 按钮 `<button class="btn btn-primary">`
- ✅ 输入用 `.input / .select / .textarea` + `.label`
- ✅ 卡片用 `.card.card-pad`
- ✅ 消息 `<p class="msg ok">` / `<p class="msg err">`
- ❌ 不要 inline `style="color: ..."`
- ❌ 不要引入新 UI 库
- ❌ 不要写新的 CSS 变量
- ❌ 不要自定义 button 样式
完整模板 + 设计令牌 + 工具类速查表见 UI-STYLE.md。
**新增文件**: `client/src/views/Platform/GlobalSettings.vue`
**内容**: 总设置页面
@@ -854,6 +874,21 @@ onMounted(load);
### Task 2.5: 前端平台层 — 通用子系统设置渲染器
**⚠️ UI 规范同 Task 2.4 — 看 [docs/UI-STYLE.md](UI-STYLE.md) 第 4.2 节有完整模板**
参考 Settings.vue 的卡片 + form 结构。字段类型 → 控件映射:
| settings_schema.type | 控件 |
|---|---|
| `string` | `<input class="input">` |
| `password` | `<input type="password" class="input">` |
| `number` | `<input type="number" class="input">` |
| `boolean` | `<input type="checkbox">` |
| `textarea` | `<textarea class="textarea">` |
| `select` | `<select class="select">` + `<option>` |
**强制要求**: 用 `.card.card-pad` + `.section-title` + `.form` 包裹;每个字段用 `<div class="mt-3"><label class="label">` 包裹;未支持的类型显示 `.text-warn`
**新增文件**: `client/src/views/Platform/SubsystemSettings.vue`
**核心能力**: 读 URL 参数 `:subsystem`(比如 `carlog`),加载 `GET /api/platform/subsystems/:id` 拿 settings_schema,渲染动态表单,保存到 `/api/platform/settings/batch`
@@ -940,6 +975,8 @@ onMounted(load);
### Task 2.6: 前端平台层 — 左侧菜单按 category 分组
**⚠️ UI 规范**: 看 [docs/UI-STYLE.md](UI-STYLE.md) — 不要改 AppHeader 现有样式,只改数据来源
**修改文件**: `client/src/AppLayout.vue`(或类似 — 找到现有左侧导航 component)
**改动**:
@@ -1017,6 +1054,8 @@ export const usePlatformStore = defineStore('platform', () => {
### Task 2.7: 前端平台层 — 路由 + 入口
**⚠️ Subsystems.vue 完整模板见 [docs/UI-STYLE.md 第 4.3 节](UI-STYLE.md#43-subsystemsvue管理页)**(桌面 table.data + 移动 MobileCardList
**修改文件**: `client/src/router/index.js`
**新增路由**:
@@ -1288,6 +1327,39 @@ mysql -h 162.14.110.130 -P 33306 -u carlog -pZeMRBwXP8JC6B3rF carlog \
- [ ] `AppLayout.vue`: 菜单读 `platform.groupedCategories` 而不是硬编码
- [ ] 启动时调 `platform.loadSubsystems()`mounted / onMounted
#### 6.2.6b 平台前端 UI 规范(必查,按 [docs/UI-STYLE.md](UI-STYLE.md)
- [ ] 所有平台 viewGlobalSettings / SubsystemSettings / Subsystems / Dashboard)顶层用 `<AppLayout>` 包
- [ ] 标题用 `<h1 class="title">` + 副标题 `<p class="text-soft">`
- [ ] loading 用 `<div class="card card-pad text-soft">`error 用 `<div class="card card-pad text-danger">`
- [ ] 按钮 `<button class="btn btn-primary">` / `.btn-ghost` / `.btn-danger`,不用自定义
- [ ] 输入控件用 `.input / .select / .textarea` + `.label`
- [ ] 卡片用 `.card.card-pad`pill 用 `.pill` + 颜色变体
- [ ] 消息用 `<p class="msg ok">` / `<p class="msg err">`
- [ ] 响应式用 `.mobile-only` / `.desktop-only` + 现有断点
- [ ] **没有任何 inline `style="color: ..."`**grep 检查)
- [ ] **没有引入新 UI 库**package.json + 静态分析)
- [ ] **没有新加 CSS 变量**(除了 style.css 里已有的)
- [ ] **颜色 / 字体 / 间距全部走 CSS 变量**(`var(--xxx)` 或工具类)
- [ ] **跟现有 `Settings.vue` 视觉一致**(颜色 / 圆角 / 间距)
Trae 自检命令:
```bash
# 1. inline 颜色
grep -rn 'style="color:' client/src/views/Platform/
# 期望: 空
# 2. 新 UI 库
grep -rn 'ant-design\|element-plus\|naive-ui\|vuetify\|@mui' client/src/ package.json
# 期望: 空
# 3. AppLayout 引用
for f in client/src/views/Platform/*.vue; do
grep -L 'AppLayout' "$f" && echo "❌ $f 缺 AppLayout"
done
# 期望: 没有输出
```
#### 6.2.7 路由
- [ ] `client/src/router/index.js` 加 3 条平台路由:`/settings/global`, `/settings/:subsystem`, `/admin/subsystems`
+623
View File
@@ -0,0 +1,623 @@
# UI 设计规范 — 平台层必须 100% 复用 CarLog UI
> **核心原则**:用户很喜欢 CarLog 现有 UI。**平台层新页面(GlobalSettings / SubsystemSettings / Subsystems / Dashboard)必须严格复用现有设计系统**,不另起设计,不引入新依赖,不写新 CSS 变量。
>
> 一切样式从 `client/src/style.css` 的 CSS 变量 + 工具类 + 现有组件取。
---
## 0. 不做的事(明确边界)
-**不引入新 UI 库**ant-design / element-plus / naive-ui / vuetify 都不行)
-**不写新的 CSS 变量**(除非有明确理由并记录)
-**不写 styled-component / CSS module / scoped 颜色**
-**不另起一套设计语言**(不要"平台层用 X 配色 / Y 字体"
-**不要 inline `style="color: #xxx"`**(用 `.text-brand` / `.text-danger` 等类)
-**不要自定义 button 样式**(用 `.btn` + 变体)
---
## 1. 设计令牌(CSS 变量)
全部定义在 `client/src/style.css` 顶部 `:root`。**直接用,不要复制**
```css
/* 颜色 */
--bg: #E8F4F9; /* 页面背景(淡蓝) */
--bg-soft: #F2F8FB; /* 卡片 hover / 二级背景 */
--card: #FFFFFF; /* 卡片背景 */
--text: #0F2233; /* 主文字(深蓝近黑) */
--text-soft: #5A6F80; /* 次要文字 */
--text-mute: #8A9CAB; /* 辅助文字 / 描述 */
--line: #E1ECF2; /* 分隔线 / 输入框边框 */
--accent: #0F2233; /* 主按钮背景(深蓝近黑) */
--accent-soft: #1A3A55; /* 主按钮 hover */
--brand: #1E5B8A; /* 蓝色 brand(链接 / 强调) */
--brand-soft: #2C7AB0;
--green: #4DBA9A; /* 成功 */
--warn: #E8A33D; /* 警告 */
--danger: #D9695C; /* 错误 */
--info: #5AA8D8; /* 信息 */
/* 圆角 */
--radius: 14px;
--radius-sm: 8px;
--radius-lg: 22px;
--pill: 999px;
/* 字体 */
--font: 'Outfit', system-ui, -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif;
/* 阴影 */
--card-shadow: 0 2px 8px rgba(40, 80, 110, 0.06);
--card-shadow-hover: 0 4px 16px rgba(40, 80, 110, 0.10);
```
---
## 2. 工具类速查表
全部在 `client/src/style.css`**直接 `<div class="card card-pad">` 这样用**
### 2.1 按钮
```html
<button class="btn btn-primary">主操作</button>
<button class="btn btn-ghost">次操作</button>
<button class="btn btn-danger">危险</button>
<button class="btn btn-danger-outline btn-sm">小危险</button>
<button class="btn btn-primary btn-sm">小主</button>
```
| 类 | 用途 |
|---|---|
| `.btn` | 基础(inline-flex / 圆角 / 过渡) |
| `.btn-primary` | 主操作(深蓝背景白字) |
| `.btn-ghost` | 次要(透明 + 边框) |
| `.btn-danger` | 危险(红背景白字) |
| `.btn-danger-outline` | 危险但次要(红边框) |
| `.btn-sm` | 小尺寸 |
### 2.2 卡片
```html
<div class="card card-pad">
<h2 class="section-title">标题</h2>
内容
</div>
```
- `.card` — 白底 + 圆角 + 阴影
- `.card-pad` — 24px padding(默认所有卡片都用这个)
### 2.3 输入控件
```html
<div>
<label class="label">字段名</label>
<input class="input" />
</div>
<div>
<label class="label">下拉</label>
<select class="select">
<option>A</option>
</select>
</div>
<div>
<label class="label">多行</label>
<textarea class="textarea"></textarea>
</div>
<div class="form-check">
<input type="checkbox" id="x" />
<label for="x">启用</label>
</div>
```
### 2.4 标签 / Pill
```html
<span class="pill pill-blue"></span>
<span class="pill pill-green">完成</span>
<span class="pill pill-warn">待办</span>
<span class="pill pill-danger">错误</span>
<span class="pill pill-gray">默认</span>
```
### 2.5 文字颜色
```html
<p class="text">普通</p>
<p class="text-soft">次要</p>
<p class="text-mute">辅助</p>
<p class="text-brand">蓝色强调</p>
<p class="text-green">成功</p>
<p class="text-danger">错误</p>
```
### 2.6 布局
```html
<div class="flex items-center gap-3">
<span></span>
<span></span>
<span></span>
</div>
<div class="flex-col gap-2 mt-4">
<div></div>
<div></div>
</div>
<div class="row mt-3">
<button>保存</button>
<button>取消</button>
</div>
```
| 类 | 效果 |
|---|---|
| `.flex` / `.flex-col` | flex 布局 |
| `.items-center` / `.justify-between` / `.justify-center` | flex 对齐 |
| `.gap-2` / `gap-3` / `gap-4` / `gap-6` | gap 8/12/16/24px |
| `.mt-2/3/4/6` / `.mb-2/3/4/6` | margin-top/bottom 8/12/16/24px |
| `.row` | flex 横向 + gap |
### 2.7 响应式
```html
<span class="mobile-only">只在手机显示</span>
<span class="desktop-only">只在桌面显示</span>
```
断点(在 `style.css`):
- `--bp-sm` 480px(小屏手机)
- `--bp-md` 768px(大屏手机/小平板)
- `--bp-lg` 1024px(平板)
- `--bp-xl` 1440px(桌面)
移动端样式(在 `style.css` 已自动处理):
- `<input>` 自动 16px(防 iOS 缩放)
- `table.data` 自动横向滚动
- `.container` 自动 padding 调整
### 2.8 表格
```html
<table class="data">
<thead>
<tr><th>列1</th><th>列2</th></tr>
</thead>
<tbody>
<tr><td>A</td><td>B</td></tr>
</tbody>
</table>
```
- 自动 hover 高亮(`tr:hover td`
- 移动端自动横向滚动
---
## 3. 布局组件(必须复用)
### 3.1 AppLayout.vue — 所有页面顶层
```vue
<template>
<AppLayout>
<h1 class="title">页面标题</h1>
<p class="text-soft" style="margin: 4px 0 24px; font-size:14px">副标题</p>
<div class="card card-pad">
内容
</div>
</AppLayout>
</template>
<script setup>
import AppLayout from '../../components/AppLayout.vue';
</script>
```
**所有平台层 view 必须用 `<AppLayout>` 包起来**Settings.vue / WashesList.vue 等都这么做)。
### 3.2 AppHeader.vue — 顶栏
现有 AppHeader.vue 已经包含左侧菜单 + 用户信息 + 主题切换。**平台层不需要新写顶栏**,复用现有的。
### 3.3 MobileCardList.vue — 移动端列表
```vue
<MobileCardList :items="data" :columns="columns" />
```
平台层如有列表(Subsystems.vue),桌面端用 `table.data`,移动端用 `<MobileCardList>` 兜底(参考 `WashesList.vue` 怎么用)。
### 3.4 StatCard.vue — 数据卡片
```vue
<StatCard icon="🚗" label="车辆数" :value="12" />
```
平台 Dashboard 用这个组件展示统计数据。
### 3.5 ChartBlock.vue — 图表
```vue
<ChartBlock title="趋势">
<Line :data="..." :options="..." />
</ChartBlock>
```
平台 Dashboard 如果有图表就用这个。
### 3.6 ConfirmDangerDialog.vue — 危险操作确认
```vue
<ConfirmDangerDialog
:open="showConfirm"
title="删除子系统?"
message="此操作不可恢复"
@confirm="onConfirm"
@cancel="showConfirm = false"
/>
```
平台层启停 / 删除子系统前用这个。
### 3.7 AiFallbackModal.vue — AI 失败兜底
平台层暂不需要(无 AI 集成)。保留供将来用。
---
## 4. 平台层 view 模板(参考 Settings.vue
### 4.1 GlobalSettings.vue(总设置)
```vue
<template>
<AppLayout>
<h1 class="title">总设置</h1>
<p class="text-soft" style="margin: 4px 0 24px; font-size:14px">
全局 UI / 备份 / Dashboard 配置
</p>
<div v-if="loading" class="card card-pad text-soft">加载中</div>
<div v-else-if="error" class="card card-pad text-danger">{{ error }}</div>
<div v-else>
<section class="card card-pad mb-4">
<h2 class="section-title">界面</h2>
<form @submit.prevent="save" class="form">
<div>
<label class="label">主题</label>
<select v-model="form.theme" class="select">
<option value="auto">跟随系统</option>
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
</div>
<div class="mt-3">
<label class="label">语言</label>
<select v-model="form.language" class="select">
<option value="zh-CN">简体中文</option>
<option value="en">English</option>
</select>
</div>
<div class="mt-3">
<label class="label">Dashboard 布局</label>
<select v-model="form.dashboardLayout" class="select">
<option value="default">默认</option>
<option value="compact">紧凑</option>
</select>
</div>
<p v-if="msg" class="msg ok mt-3">{{ msg }}</p>
<div class="row mt-3">
<button class="btn btn-primary" :disabled="busy">
{{ busy ? '保存中…' : '保存' }}
</button>
</div>
</form>
</section>
<section class="card card-pad">
<h2 class="section-title">备份</h2>
<form @submit.prevent="save" class="form">
<div>
<label class="label">启用自动备份</label>
<input type="checkbox" v-model="form.backupEnabled" />
</div>
<div class="mt-3">
<label class="label">备份路径</label>
<input v-model="form.backupPath" class="input" placeholder="/path/to/backup" />
</div>
<div class="row mt-3">
<button class="btn btn-primary" :disabled="busy">
{{ busy ? '保存中…' : '保存' }}
</button>
</div>
</form>
</section>
</div>
</AppLayout>
</template>
```
### 4.2 SubsystemSettings.vue(通用渲染器)
跟 GlobalSettings 同结构,**每个字段用 `<div>` 包 label + 控件**
```vue
<template>
<AppLayout>
<h1 class="title">{{ subsystemName }} 设置</h1>
<p class="text-soft" style="margin: 4px 0 24px; font-size:14px">
子系统配置保存到 platform_settings
</p>
<div v-if="loading" class="card card-pad text-soft">加载中</div>
<div v-else-if="error" class="card card-pad text-danger">{{ error }}</div>
<div v-else class="card card-pad">
<form @submit.prevent="save" class="form">
<div v-for="field in schema.fields" :key="field.key" class="mt-3">
<label class="label">{{ field.label }}</label>
<input v-if="field.type === 'string'"
v-model="values[field.key]"
class="input" />
<input v-else-if="field.type === 'password'"
v-model="values[field.key]"
type="password" class="input" />
<input v-else-if="field.type === 'number'"
v-model.number="values[field.key]"
type="number" class="input" />
<input v-else-if="field.type === 'boolean'"
v-model="values[field.key]"
type="checkbox" />
<textarea v-else-if="field.type === 'textarea'"
v-model="values[field.key]"
class="textarea" />
<select v-else-if="field.type === 'select'"
v-model="values[field.key]"
class="select">
<option v-for="opt in field.options" :key="opt" :value="opt">{{ opt }}</option>
</select>
<span v-else class="text-warn">未支持的字段类型: {{ field.type }}</span>
</div>
<p v-if="msg" class="msg ok mt-3">{{ msg }}</p>
<div class="row mt-3">
<button class="btn btn-primary" :disabled="busy">
{{ busy ? '保存中…' : '保存' }}
</button>
</div>
</form>
</div>
</AppLayout>
</template>
```
### 4.3 Subsystems.vue(管理页)
```vue
<template>
<AppLayout>
<h1 class="title">子系统管理</h1>
<p class="text-soft" style="margin: 4px 0 24px; font-size:14px">
启停 / 注册子系统
</p>
<div v-if="loading" class="card card-pad text-soft">加载中</div>
<div v-else-if="error" class="card card-pad text-danger">{{ error }}</div>
<div v-else class="card card-pad">
<!-- 桌面端表格 -->
<table class="data desktop-only">
<thead>
<tr>
<th>图标</th>
<th>名称</th>
<th>类别</th>
<th>版本</th>
<th>启用</th>
</tr>
</thead>
<tbody>
<tr v-for="sub in subsystems" :key="sub.id">
<td>{{ sub.icon }}</td>
<td><strong>{{ sub.name }}</strong><br /><span class="text-mute sm">{{ sub.description }}</span></td>
<td><span class="pill pill-blue">{{ sub.category }}</span></td>
<td>{{ sub.version }}</td>
<td>
<input type="checkbox" :checked="sub.enabled" @change="toggle(sub, $event.target.checked)" />
</td>
</tr>
</tbody>
</table>
<!-- 移动端卡片列表 -->
<MobileCardList
class="mobile-only"
:items="subsystems"
:columns="[
{ key: 'icon', label: '', render: r => r.icon },
{ key: 'name', label: '名称', render: r => r.name },
{ key: 'enabled', label: '启用', render: r => r.enabled ? '✅' : '❌' },
]"
/>
</div>
</AppLayout>
</template>
<script setup>
import AppLayout from '../../components/AppLayout.vue';
import MobileCardList from '../../components/MobileCardList.vue';
</script>
```
### 4.4 Dashboard.vue(替换现有 Home.vue
参考现有 `Home.vue``StatCard` + `ChartBlock`
```vue
<template>
<AppLayout>
<h1 class="title">Dashboard</h1>
<p class="text-soft" style="margin: 4px 0 24px; font-size:14px">
i 平台概览
</p>
<div class="dashboard-grid">
<StatCard icon="🚗" label="车辆" :value="data.carlog?.vehicles_count ?? 0" />
<StatCard icon="🧽" label="本月洗车" :value="data.carlog?.this_month?.washes ?? 0" />
<StatCard icon="💰" label="本月洗车支出"
:value="`¥${data.carlog?.this_month?.wash_cost ?? 0}`" />
<StatCard icon="⛽" label="本月加油 (升)"
:value="data.carlog?.this_month?.refuel_liters ?? 0" />
</div>
</AppLayout>
</template>
<script setup>
import AppLayout from '../../components/AppLayout.vue';
import StatCard from '../../components/StatCard.vue';
</script>
<style scoped>
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
</style>
```
---
## 5. 错误 / 消息样式
### 5.1 加载中
```html
<div class="card card-pad text-soft">加载中…</div>
```
### 5.2 错误
```html
<div class="card card-pad text-danger">{{ error }}</div>
```
### 5.3 操作成功 / 失败消息(表单内)
```html
<p class="msg ok">已保存</p>
<p class="msg err">保存失败:{{ error }}</p>
```
### 5.4 提示文字(灰色小字)
```html
<p class="text-soft" style="font-size:13px; margin: 0 0 16px">
说明文字
</p>
```
---
## 6. 标题层级
```html
<h1 class="title">页面大标题</h1>
<h2 class="section-title">卡片内分节标题</h2>
<h3 class="subsection-title">小节标题</h3>
```
- `class="title"` — 页面级(h1
- `class="section-title"` — 卡片内分节(h2
- `class="subsection-title"` — 小节(h3
(参考 `Settings.vue``<h1 class="title">` + `<h2 class="section-title">` 的用法)
---
## 7. 主题色使用规则
| 场景 | 用什么 |
|---|---|
| 主操作按钮 | `.btn-primary`(深蓝近黑) |
| 次要按钮 | `.btn-ghost` |
| 危险操作(删除/禁用) | `.btn-danger` / `.btn-danger-outline` |
| 链接 / 强调文字 | `.text-brand` |
| 成功提示 | `.text-green` / `.pill-green` |
| 警告 | `.text-warn`(无对应工具类但 CSS 变量有 `var(--warn)` / `.pill-warn` |
| 错误 | `.text-danger` / `.pill-danger` |
| 描述文字 | `.text-soft` |
| 占位 / 不可点 | `.text-mute` |
---
## 8. 移动端注意事项
- 所有 `<input>` 自动 16pxiOS 防缩放,已在 style.css 处理)
- `<table.data>` 自动横向滚动
-`.mobile-only` / `.desktop-only` 切换桌面/移动组件
- 不要写 fixed width(用 max-width + 自适应)
- Container padding 自带响应式(不用自己写)
---
## 9. Trae 自检清单(提交前过一遍)
提交 PR 前 Trae 自己 grep 检查:
```bash
# 1. 没有任何 inline style 写颜色
grep -rn "style=\"color:" client/src/views/Platform/
# 期望: 没有输出
# 2. 没有任何引入新 UI 库
grep -rn "ant-design\|element-plus\|naive-ui\|vuetify\|@mui" client/src/
# 期望: 没有输出
# 3. 所有平台 view 用了 AppLayout
grep -L "AppLayout" client/src/views/Platform/*.vue
# 期望: 没有输出(每个文件都 import 了)
# 4. 没有写新的 CSS 变量(除非 style.css 里没有的)
grep -rn "^ --" client/src/views/Platform/
# 期望: 没有输出(变量定义在 style.css,不在 view 里)
```
---
## 10. Mavis review 时会看的 UI 验收点
我(reviewer)会按这个清单看:
- [ ] 所有平台 view 顶部用 `<AppLayout>` 包(看 template 第一行)
- [ ] 颜色 / 字体 / 间距全部走 CSS 变量(grep `style="color"` 应该没结果)
- [ ] 没有引入新 UI 库(package.json + 静态分析)
- [ ] 没有新加 CSS 变量(除非 style.css 里没的)
- [ ] 按钮用 `.btn` + 变体,不是 `<button class="my-btn">`
- [ ] 卡片用 `.card.card-pad`,不是自定义 background + border-radius
- [ ] 输入用 `.input/.select/.textarea` + `.label`
- [ ] Pill 用 `.pill` + 颜色变体
- [ ] 移动端用 `.mobile-only` / `.desktop-only` 切换
- [ ] 加载/错误用 `.card.card-pad.text-soft/text-danger`
- [ ] 消息用 `.msg.ok` / `.msg.err`
- [ ] 跟现有 `Settings.vue` 视觉一致(颜色 / 间距 / 圆角)
任何一项不通过都要改完再合并。
---
> 总结:**所有平台层新页面 = Settings.vue 的视觉风格 + 元数据驱动**。看 `client/src/views/Settings.vue` 就知道长什么样。