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:
@@ -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 仓库
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
- [ ] 所有平台 view(GlobalSettings / 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`
|
||||
|
||||
@@ -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>` 自动 16px(iOS 防缩放,已在 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` 就知道长什么样。
|
||||
Reference in New Issue
Block a user