Files
i/docs/ARCHITECTURE.md
T
wsh5485 60b7df9015 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 的视觉风格 + 元数据驱动。
2026-06-20 22:53:37 +08:00

15 KiB
Raw Blame History

架构方案:单 SPA + 单库 + 表前缀

1. 目标

i 平台是一个「生活操作系统」:单 Vue SPA + 单 Express 进程 + 单 MySQL,按表前缀分多子系统,永远单用户。

  • 不引入分布式复杂度:不分库、不分进程、不分部署
  • 数据隔离靠表前缀carlog_* / fitness_* / reading_* / ...
  • 元数据驱动 UIsubsystem 注册到 subsystems 表,平台前端通用渲染设置页和导航
  • 永远单用户:管什么 RBAC / 多租户

理由:个人用,数据量小;分库分进程反而增加运维复杂度而没收益。

2. 顶层架构

┌────────────────────────────────────────────────────────┐
│  Vue SPA (一个壳子)                                       │
│                                                            │
│  ┌──────────────────────────────────────────────────────┐│
│  │ 总设置 / 子系统管理 / Dashboard  (平台层)              ││
│  └──────────────────────────────────────────────────────┘│
│                                                            │
│  ┌──────────────────────────────────────────────────────┐│
│  │ 🚗 CarLog     | 💪 Fitness    | 📚 Reading             ││
│  │ 概览/洗车/加油 | 训练/打卡/计划  | 书/笔记/进度       ││
│  └──────────────────────────────────────────────────────┘│
│                                                            │
└────────────────────────────────────────────────────────┘
                          │ axios + cookie session
                          ▼
┌────────────────────────────────────────────────────────┐
│  Express (一个进程)                                       │
│  ├── /api/platform/*    (总设置 / 子系统管理 / Dashboard) │
│  ├── /api/carlog/*       (CarLog 子系统)                  │
│  └── /api/{future}/*     (将来加的子系统)                  │
└────────────────────────────────────────────────────────┘
                          │
                          ▼
┌────────────────────────────────────────────────────────┐
│  MySQL @ 162.14.110.130:33306 / carlog                     │
│                                                            │
│  平台表 (无前缀):                                          │
│    users / sessions / subsystems / platform_settings      │
│                                                            │
│  CarLog 表:                                                │
│    carlog_vehicles / carlog_wash_records / carlog_refuels  │
│    carlog_charging_records / carlog_maintenance_records    │
│    carlog_insurance_records / carlog_chemicals             │
│    carlog_weather_snapshots / carlog_operation_logs        │
│    carlog_tags / carlog_achievements / carlog_notifications│
│    ...                                                     │
│                                                            │
│  Fitness 表 (将来):                                       │
│    fitness_workouts / fitness_plans / fitness_measurements│
│                                                            │
│  Reading 表 (将来):                                       │
│    reading_books / reading_notes / reading_progress        │
└────────────────────────────────────────────────────────┘

3. 物理隔离靠什么

隔离维度 做法
数据 表前缀 {subsystem}_*(同一 DB 内;本阶段 CarLog 还没加前缀,留给将来加第二个子系统时)
路由 子系统自己的路径空间(/api/carlog/* 现有;将来 /api/fitness/* 等)
代码 子系统独立目录(server/src/subsystems/{name}/client/src/views/subsystems/{name}/
设置 每个子系统有自己的 settings schemaJSON Schema,存 platform_settings 表,key 前缀 {name}.*
菜单 每个子系统在 subsystems 表注册,平台层根据 category 分组渲染左侧导航

没有 JWT / SSO / iframe / 6 端点协议 / 独立 DB — 那些都是过度设计。

4. Subsystem 注册表

平台层只管一张元数据表:

CREATE TABLE subsystems (
    id              VARCHAR(50) PRIMARY KEY,        -- 'carlog' / 'fitness' / 'reading'
    name            VARCHAR(100) NOT NULL,           -- 显示名:「洗车管理系统」
    description     TEXT,
    icon            VARCHAR(20),                     -- emoji 或文件名
    color           VARCHAR(20),
    category        VARCHAR(50),                     -- vehicle / fitness / finance / reading
    version         VARCHAR(20),
    enabled         TINYINT(1) DEFAULT 1,
    sort_order      INT DEFAULT 0,
    settings_schema JSON,                            -- JSON Schema 描述这个子系统的设置项
    nav_items       JSON,                            -- [{label, icon, path}]
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB;

INSERT INTO subsystems (id, name, icon, color, category, version, settings_schema, nav_items) VALUES
('carlog', '洗车管理系统', '🚗', '#1B6EF3', 'vehicle', '2.8.0',
'{"fields":[
  {"key":"weather.default_city","label":"默认城市","type":"select","options":["Beijing","Shanghai","Korla"],"default":"Korla"},
  {"key":"ai.provider","label":"AI 识别 provider","type":"select","options":["openai_compat","minimax_vl"],"default":"minimax_vl"}
]}',
'[{"label":"概览","path":"/","icon":"🏠"},{"label":"洗车记录","path":"/washes","icon":"🧽"}]'
);

注意subsystem 表里不存业务数据,只存「我是谁 / 我有什么设置 / 我在菜单里长啥样」。

5. 总设置 vs 子系统设置

5.1 总设置(平台级)

platform_settings 表,不带前缀

key value 说明
ui.theme auto / light / dark UI 主题
ui.language zh-CN / en 界面语言
dashboard.layout default / compact Dashboard 布局
backup.enabled 1 / 0 自动备份开关
backup.path /path/ 备份路径

UI 在 /settings/global,平台前端渲染。

5.2 子系统设置

platform_settings 表,{subsystem}. 前缀

key 说明
carlog.weather.default_city 来自 CarLog manifest 的 settings_schema
carlog.ai.provider 同上
carlog.grocy.url Grocy 实例 URL(将来从 settings 表迁过来)
fitness.units.metric Fitness 子系统的「公制单位」开关
reading.sort.default Reading 子系统的默认排序

UI 在 /settings/{subsystem},平台前端通用渲染器(按 settings_schema 的 type 渲染 input / select / textarea):

// 通用渲染器伪代码
async function loadSubsystemSettings(subsystemId) {
    const sub = await api.get(`/platform/subsystems/${subsystemId}`);
    const values = await api.get(`/platform/settings?prefix=${subsystemId}.`);
    return { schema: sub.settings_schema, values };
}

好处:加新子系统不用改平台前端代码,只写 settings_schema 就完事。

5.3 兼容现有 settings 表

CarLog 现在已经有 settings 表(21 个键),存了 AI / Grocy / 登录锁定等配置。两种方案

  • (A) 保留 settings,平台层加 platform_settings 表,新子系统的设置走 platform_settingsCarLog 自己的设置继续用 settings
  • (B) 迁移到 platform_settings,所有子系统统一,CarLog 的 key 加 carlog. 前缀

推荐 (A):迁移工作量小,CarLog 的现有逻辑不用改。platform_settings 主要管「平台级 UI 设置 + 将来新子系统的设置」。

6. 路由 / 代码目录

6.1 服务端

server/src/
├── index.js              # 统一入口
├── routes/
│   ├── platform/         # 总设置 / 子系统管理 / Dashboard 聚合
│   │   ├── settings.js   # GET/PUT /api/platform/settings
│   │   ├── subsystems.js # GET /api/platform/subsystems
│   │   └── dashboard.js  # GET /api/platform/dashboard (跨子系统聚合)
│   └── subsystems/
│       └── carlog/
│           ├── index.js   # 聚合导出 13 个 CarLog router
│           └── routes/    # 13 个 CarLog 路由文件
└── ...

6.2 前端

client/src/
├── AppLayout.vue         # 左侧菜单按 category 分组
├── views/
│   ├── Platform/         # 平台层
│   │   ├── GlobalSettings.vue
│   │   ├── SubsystemSettings.vue   # 通用渲染器
│   │   └── Subsystems.vue          # 启停 + 注册
│   ├── Login.vue         # i 平台统一登录
│   ├── Home.vue          # Dashboard, 读 /api/platform/dashboard
│   └── subsystems/
│       └── carlog/       # 20 个 CarLog view
├── api/
│   ├── client.js         # 底层 axios 实例(auth interceptor + 解包)
│   └── subsystems.js     # carlogApi helperbaseURL = /api/carlog
├── router/index.js
└── stores/
    ├── auth.js
    └── platform.js       # 总设置 / 子系统列表 / 当前激活子系统

6.3 子系统如何在平台菜单里显示

<!-- AppLayout.vue 左侧导航 -->
<template v-for="cat in groupedCategories" :key="cat">
  <div class="nav-category">{{ cat.label }}</div>
  <router-link v-for="item in cat.items" :key="item.path" :to="item.path">
    {{ item.icon }} {{ item.label }}
  </router-link>
</template>

<script setup>
import { computed } from 'vue';
import { usePlatformStore } from '../stores/platform';
const platform = usePlatformStore();
const groupedCategories = computed(() => {
    const groups = {};
    for (const sub of platform.subsystems.filter(s => s.enabled)) {
        if (!groups[sub.category]) groups[sub.category] = { label: sub.category, items: [] };
        for (const nav of (sub.nav_items || [])) {
            groups[sub.category].items.push({ ...nav, subsystem: sub.id });
        }
    }
    return Object.values(groups);
});
</script>

7. 实施步骤

W1: 平台骨架(极简版)

  1. subsystems + platform_settings 两张表(migration 001_platform.sql
  2. CarLog 自己注册一条记录到 subsystems 表(migration 里 INSERT
  3. server/src/routes/platform/{settings,subsystems,dashboard}.js 三个文件
  4. index.js 把这三个 router mount 到 /api/platform
  5. client/src/views/Platform/GlobalSettings.vue(总设置 UI
  6. client/src/views/Platform/SubsystemSettings.vue(通用子系统设置渲染器)
  7. AppLayout.vue 改成读 platform.subsystems 动态渲染导航

W2: 表前缀迁移(CarLog

把所有 CarLog 表加 carlog_ 前缀:

RENAME TABLE vehicles TO carlog_vehicles;
RENAME TABLE wash_records TO carlog_wash_records;
RENAME TABLE refuel_records TO carlog_refuels;
RENAME TABLE charging_records TO carlog_chargings;
-- ... 其他表

db.js 加 helper:

const TABLE_PREFIX = {
    vehicles: 'carlog_vehicles',
    wash_records: 'carlog_wash_records',
};
export function tableName(name) {
    return TABLE_PREFIX[name] || name;
}

W3+(暂不规划)

用户还没决定下一子系统做什么。本阶段只做:

  1. 平台基座(subsystems 表 + platform_settings + 3 个平台路由 + 3 个平台前端 + 元数据驱动菜单)
  2. CarLog 子系统化(目录迁移 + 加 /api/carlog/ 前缀 + 注册到 subsystems 表)

将来加新子系统时按这个流程:

  1. 写子系统的表(带 {subsystem}_ 前缀)+ 路由 + 前端
  2. 注册到 subsystems
  3. 平台菜单自动出现新子系统入口
  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(设计令牌 + 工具类速查 + 4 个平台 view 完整模板 + Mavis review 验收点)。

8. 不做的事

  • JWT / SSO / 跨域 auth
  • iframe 嵌入子系统
  • 子系统独立部署 / 独立 DB
  • RBAC / 多用户 / 多租户
  • manifest + 6 端点协议
  • SSR / 原生 app
  • Marketplace / 插件化
  • 跨子系统 widget(等 5+ 子系统再做)

9. 风险 & 缓解

风险 缓解
表前缀迁移漏表 写 Python 脚本扫描所有 SQL 提取表名检查
子系统相互依赖(A 写 B 的表) 代码 review + lint 规则禁止跨前缀 SQL
表越来越多 DB 卡 单用户不可能;几万条再分库
总设置 vs 子系统设置混淆 key 命名约定:总设置无前缀,子系统 {sub}.{key}

10. 立即动手清单

  1. 本周写 migration 001_platform.sqlsubsystems 表 + platform_settings 表 + seed CarLog
  2. 本周写平台路由:settings / subsystems / dashboard 三个文件
  3. 本周写平台前端:GlobalSettings + SubsystemSettings + 改 AppLayout 动态菜单
  4. 下下周做表前缀迁移 + 改 server SQL
  5. 下下周跑完整测试套件 + 手动 E2E

简单胜过复杂。元数据驱动(settings_schema / nav_items 两个 JSON 字段)覆盖 80% 场景,剩下的真要 iframe / SSO 时再说。