docs: clarify /api/carlog/* prefix + drop W3+ fitness plan

按用户反馈调整:

1. 路由前缀改成 /api/carlog/* (不再是 /api/vehicles 等)
   - DEV-PLAN.md Task 2.2 mount 全部加前缀
   - 新增 Task 2.3.5: 前端 carlogApi helper
   - ARCHITECTURE.md 架构图 + 6.1/6.2 代码结构更新
   - README.md 同步

2. 删除 W3+ 健身子系统计划 (用户还没决定下一子系统)
   - ARCHITECTURE.md W3+ 章节改成 "暂不规划"
   - README.md 实施路线只保留当前阶段
   - DEV-PLAN.md "不做的事" 显式列出来

3. 加 Mavis review checklist (6.2/6.3/6.4)
   - 10 项检查清单 (DB/后端/前端/API baseURL/E2E/数据完整性)
   - 10 个常见坑 (JSON parse / TINYINT boolean / mount 顺序 ...)
   - review 通过标准
This commit is contained in:
2026-06-20 22:43:43 +08:00
parent 65b0bb04f8
commit a365c8be10
3 changed files with 302 additions and 76 deletions
+27 -23
View File
@@ -1,8 +1,8 @@
# i — 个人生活操作系统
一个**单 Vue + 单 Express + 单 MySQL** 的生活操作系统平台,按表前缀分多子系统,永远单用户。
一个**单 Vue + 单 Express + 单 MySQL** 的生活操作系统平台,按子系统分代码目录和 API 路径,永远单用户。
第一个子系统是 [CarLog](https://gitea.img2img.com/wsh5485/CarLog)(洗车管理系统),后续要加:健身、阅读、健康等。
第一个子系统是 [CarLog](https://gitea.img2img.com/wsh5485/CarLog)(洗车管理系统),将来可能加:健身、阅读、健康等(**用户还没决定下一子系统,本阶段不做**)
## 架构核心
@@ -15,7 +15,7 @@
│ └──────────────────────────────────────────────────────┘│
│ │
│ ┌──────────────────────────────────────────────────────┐│
│ │ 🚗 CarLog | 💪 Fitness | 📚 Reading ││
│ │ 🚗 CarLog | 未来子系统 ││
│ └──────────────────────────────────────────────────────┘│
│ │
└────────────────────────────────────────────────────────┘
@@ -23,8 +23,9 @@
┌────────────────────────────────────────────────────────┐
│ Express (一个进程) │
│ ├── /api/platform/* (总设置 / 子系统管理)
│ ├── /api/{resource} (业务路由, 按子系统分组)
│ ├── /api/platform/* (总设置 / 子系统管理 / Dashboard)
│ ├── /api/carlog/* (CarLog 子系统, 全部带前缀)
│ └── /api/{future}/* (将来加的子系统) │
└────────────────────────────────────────────────────────┘
@@ -36,6 +37,8 @@
│ │
│ 子系统表 (有前缀): │
│ carlog_vehicles / carlog_wash_records / ... │
│ │
│ (将来加): │
│ fitness_workouts / fitness_plans / ... │
│ reading_books / reading_notes / ... │
└────────────────────────────────────────────────────────┘
@@ -45,8 +48,8 @@
| 维度 | 做法 |
|---|---|
| 数据 | 表前缀 `{subsystem}_*`(同一 DB 内) |
| 路由 | 子系统自己的路径空间(`/api/{resource}` |
| 数据 | 表前缀 `{subsystem}_*`(同一 DB 内;CarLog 还没加前缀,留给将来加第二个子系统时 |
| 路由 | 子系统自己的路径空间(`/api/carlog/*` |
| 代码 | 子系统独立目录(`server/src/subsystems/{name}/``client/src/views/subsystems/{name}/` |
| 设置 | 每个子系统有自己的 settings schemaJSON Schema,存 `platform_settings` 表,key 前缀 `{name}.*` |
| 菜单 | 每个子系统在 `subsystems` 表注册,平台层根据 `category` 分组渲染左侧导航 |
@@ -55,24 +58,24 @@
## 实施路线
### W1: 平台骨架
### 当前阶段(W1 + W2 一并做)
- [ ] `server/migrations/001_platform.sql`subsystems + platform_settings 表 + seed CarLog
- [ ] `server/src/routes/platform/settings.js`
- [ ] `server/src/routes/platform/subsystems.js`
- [ ] `server/src/views/Platform/GlobalSettings.vue`
- [ ] `client/src/views/Platform/SubsystemSettings.vue`(通用 JSON Schema 渲染器)
- [ ] `client/src/AppLayout.vue` 改左侧菜单(按 category 分组
- [ ] `server/migrations/019_platform.sql`subsystems + platform_settings 表 + seed CarLog
- [ ] `server/src/routes/platform/{subsystems,settings,dashboard}.js`
- [ ] CarLog 代码移到 `server/src/subsystems/carlog/`13 个路由文件)
- [ ] CarLog 路由全部 mount 到 `/api/carlog/*`(不再是 `/api/vehicles`
- [ ] CarLog 前端 view 移到 `client/src/views/subsystems/carlog/`
- [ ] 前端 API 改用 `carlogApi` helper(自动加 `/api/carlog/` 前缀
- [ ] `client/src/views/Platform/{GlobalSettings,SubsystemSettings,Subsystems}.vue`
- [ ] `client/src/stores/platform.js` + `AppLayout.vue` 元数据驱动菜单
- [ ] 测试(12-20 个平台测试)+ 手动 E2E 验证
### W2: CarLog 表前缀迁移
### 暂不做(用户没决定)
- [ ] 把 CarLog 现有 14 张表加 `carlog_` 前缀(migration
- [ ] 改 server 端所有 SQL
- [ ] 跑全测试套件
### W3+: 加第二个真子系统
- [ ] 写一个 Fitness 子系统(健身记录),验证端到端流程
- ❌ 加第二个子系统(健身 / 阅读 / 任何
- ❌ CarLog 表前缀迁移(`carlog_*`
- ❌ 跨子系统 dashboard widget
- ❌ 多用户 / RBAC / 多租户
## 路线原则
@@ -82,10 +85,11 @@
- **物理目录隔离**:subsystem 代码独立目录,加新子系统不会乱碰现有代码
- **永远向后兼容**:CarLog 的现有功能不破
详细见 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)。
详细见 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) 和 [docs/DEV-PLAN.md](docs/DEV-PLAN.md)
## Git 仓库
- 仓库:https://gitea.img2img.com/wsh5485/i.git
- 平台:Gitea
- 推送:osxkeychain 自动记住 token
- 推送:osxkeychain 自动记住 token
+25 -16
View File
@@ -32,10 +32,8 @@ i 平台是一个「生活操作系统」:单 Vue SPA + 单 Express 进程 +
┌────────────────────────────────────────────────────────┐
│ Express (一个进程) │
│ ├── /api/platform/* (总设置 / 子系统管理 / Dashboard) │
│ ├── /api/vehicles (CarLog 路由, 保持现状)
── /api/washes (CarLog)
│ ├── /api/fitness/* (将来加的 Fitness 子系统) │
│ └── /api/reading/* (将来加的 Reading 子系统) │
│ ├── /api/carlog/* (CarLog 子系统)
── /api/{future}/* (将来加的子系统)
└────────────────────────────────────────────────────────┘
@@ -65,8 +63,8 @@ i 平台是一个「生活操作系统」:单 Vue SPA + 单 Express 进程 +
| 隔离维度 | 做法 |
|---|---|
| 数据 | 表前缀 `{subsystem}_*`(同一 DB 内) |
| 路由 | 子系统自己的路径空间(`/api/{resource}` 现有;将来 `/api/fitness/*` |
| 数据 | 表前缀 `{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` 分组渲染左侧导航 |
@@ -168,9 +166,10 @@ server/src/
│ │ ├── settings.js # GET/PUT /api/platform/settings
│ │ ├── subsystems.js # GET /api/platform/subsystems
│ │ └── dashboard.js # GET /api/platform/dashboard (跨子系统聚合)
── vehicles.js # CarLog
├── washes.js # CarLog
└── ...
── subsystems/
└── carlog/
├── index.js # 聚合导出 13 个 CarLog router
│ └── routes/ # 13 个 CarLog 路由文件
└── ...
```
@@ -184,9 +183,13 @@ client/src/
│ │ ├── GlobalSettings.vue
│ │ ├── SubsystemSettings.vue # 通用渲染器
│ │ └── Subsystems.vue # 启停 + 注册
│ ├── Login.vue # i 平台统一登录
│ ├── Home.vue # Dashboard, 读 /api/platform/dashboard
── WashesList.vue
└── ...
── subsystems/
└── carlog/ # 20 个 CarLog view
├── api/
│ ├── client.js # 底层 axios 实例(auth interceptor + 解包)
│ └── subsystems.js # carlogApi helperbaseURL = /api/carlog
├── router/index.js
└── stores/
├── auth.js
@@ -257,13 +260,19 @@ export function tableName(name) {
}
```
### W3+: 加第二个子系统(健身
### W3+(暂不规划
1.`subsystems.fitness`3-5 张表 + 3-5 个路由 + 5-10 个前端 view
用户还没决定下一子系统做什么。本阶段只做:
1. ✅ 平台基座(subsystems 表 + platform_settings + 3 个平台路由 + 3 个平台前端 + 元数据驱动菜单)
2. ✅ CarLog 子系统化(目录迁移 + 加 `/api/carlog/` 前缀 + 注册到 subsystems 表)
将来加新子系统时按这个流程:
1. 写子系统的表(带 `{subsystem}_` 前缀)+ 路由 + 前端
2. 注册到 `subsystems`
3. 平台菜单自动出现 Fitness 入口
4. 通用设置渲染器自动支持 Fitness 的 settings
5. 验证:能从平台菜单点进 Fitness,操作后回平台
3. 平台菜单自动出现新子系统入口
4. 通用设置渲染器自动支持新子系统的 settings
5. 验证端到端流程
## 8. 不做的事
+250 -37
View File
@@ -31,11 +31,13 @@ i 是一个生活操作系统平台,**单 Vue SPA + 单 Express 进程 + 单 M
|---|---|---|
| 子系统隔离 | 物理目录 + 表前缀 | 单用户场景,分进程/分库是过度工程 |
| 平台层和子系统共享一个进程 | 是 | 一个 Express server |
| 路由前缀 | **保持现状** `/api/*` | 改 URL 路径影响前端所有 link/api call,工作量大且无收益 |
| **子系统 API 路径前缀** | **`/api/{subsystem}/*`**(如 `/api/carlog/vehicles` | 子系统隔离 + 以后加 fitness 直接 `/api/fitness/*` 不冲突 |
| CarLog 代码目录 | 移到 `server/src/subsystems/carlog/` | 物理隔离,加子系统不会乱碰 |
| CarLog 前端目录 | 移到 `client/src/views/subsystems/carlog/` | 同上 |
| CarLog 路由 path | 不变(`/washes``/vehicles` | 用户体验一致 |
| CarLog 表前缀 | **Phase 2 不做**(留给 Phase 3) | 当前阶段数据库里只有 CarLog 的表,没必要急着加前缀;加第二个子系统前再做 |
| **CarLog 后端路由 path** | `/api/carlog/*`(不再是 `/api/vehicles` | 跟平台层 `/api/platform/*` 对齐;前端调用要改 |
| **CarLog 前端 API 调用** | 所有 CarLog API 加 `/api/carlog/` 前缀 | 跟后端路由对齐 |
| CarLog 前端路由 path | 不变(`/washes``/vehicles` | 用户体验一致;router path 还是前端路径,不是 API 路径 |
| CarLog 表前缀 | **本阶段不做**(留给将来加第二个子系统时) | 当前阶段数据库里只有 CarLog 的表,没必要急着加前缀 |
| 平台层路由前缀 | `/api/platform/*` | 平台层独立路径空间 |
| 平台层 UI 路径 | `/settings/global`, `/settings/:subsystem`, `/admin/subsystems` | 用户能直接 URL 进入 |
| 元数据驱动 | `subsystems` 表的 `settings_schema` + `nav_items` JSON 字段 | 加新子系统不用改平台前端代码 |
@@ -427,8 +429,7 @@ router.get('/', requireAuth, async (req, res, next) => {
refuel_liters: Number(refuelsThisMonth[0].liters),
},
},
// fitness: {...}, // Phase 3 加
// reading: {...}, // Phase 3 加
// 将来加第二个子系统时扩: fitness: {...}, reading: {...}
},
});
} catch (err) {
@@ -440,7 +441,7 @@ export default router;
```
**注意**:
- 现在只聚 CarLogFitness / Reading 子系统加进来后再扩
- 现在只聚 CarLog将来加新子系统时再扩字段
- 用 UTC_DATE()mysql2 timezone='Z' 配置已经设为 UTC
- 总数 / 总额用 `Number()` 转 JS numbermysql2 返回 DECIMAL 是字符串)
- 后续扩字段时按需加
@@ -594,11 +595,11 @@ cd server && node -e "import('./src/subsystems/carlog/index.js').then(m => conso
---
### Task 2.2: 后端路由挂载从新目录
### Task 2.2: 后端路由挂载从新目录(加 /api/carlog/ 前缀)
**修改文件**: `server/src/index.js`
**改动**: 把现有 mount 改用新 index.js 导出的 router
**改动**: 把现有 mount 改用新 index.js 导出的 router, **所有 CarLog 路由加 `/api/carlog/` 前缀**
```js
// 原来的:
// import vehiclesRouter from './routes/vehicles.js';
@@ -613,25 +614,25 @@ import {
notificationsRouter, achievementsRouter,
} from './subsystems/carlog/index.js';
app.use('/api/vehicles', vehiclesRouter);
app.use('/api/washes', washesRouter);
app.use('/api/refuels', refuelsRouter); // 注意: 如果原文件名是 refuels.js 就这样, 看实际
// ... 其他 11 个
app.use('/api/insurance', insuranceRouter);
app.use('/api/chemicals', chemicalsRouter);
app.use('/api/ai', aiRouter);
app.use('/api/auth', authRouter);
app.use('/api/settings', settingsRouter);
app.use('/api/logs', logsRouter);
app.use('/api/operation-logs', operationLogsRouter);
app.use('/api/extra', extraRouter);
app.use('/api/tags', tagsRouter);
app.use('/api/notifications', notificationsRouter);
app.use('/api/achievements', achievementsRouter);
// ⚠️ 全部加 /api/carlog/ 前缀(不再是 /api/vehicles
app.use('/api/carlog/vehicles', vehiclesRouter);
app.use('/api/carlog/washes', washesRouter);
app.use('/api/carlog/refuels', refuelsRouter); // 看实际文件名, 可能是 refuel
app.use('/api/carlog/insurance', insuranceRouter);
app.use('/api/carlog/chemicals', chemicalsRouter);
app.use('/api/carlog/ai', aiRouter);
app.use('/api/carlog/auth', authRouter); // 登录路由也加前缀
app.use('/api/carlog/settings', settingsRouter);
app.use('/api/carlog/logs', logsRouter);
app.use('/api/carlog/operation-logs', operationLogsRouter);
app.use('/api/carlog/extra', extraRouter);
app.use('/api/carlog/tags', tagsRouter);
app.use('/api/carlog/notifications', notificationsRouter);
app.use('/api/carlog/achievements', achievementsRouter);
```
**重要**:
- 路由 path 不要变(保持 `/api/vehicles` 而不是 `/api/carlog/vehicles`
**重要**:
- **路由 path `/api/carlog/` 前缀**(用户决定 — 跟 `/api/platform/*` 对齐,方便以后加 fitness/reading 不冲突
- 文件名要按 `ls server/src/subsystems/carlog/routes/` 实际看到的写
- 一定要先 cd 到 server/src/subsystems/carlog/routes 看看实际文件名, 常见陷阱:
- `refuels.js` vs `refuel.js`
@@ -643,10 +644,10 @@ app.use('/api/achievements', achievementsRouter);
```bash
cd server && npm run dev
# 登录 + 列车辆 + 列洗车 + 列加油 + 列成就 + 列通知 + 全 OK
TOKEN=$(curl -s -X POST http://localhost:8787/api/auth/login -H 'Content-Type: application/json' -d '{"username":"admin","password":"carwash2026"}' | python3 -c "import json,sys; print(json.load(sys.stdin)['data']['token'])")
TOKEN=$(curl -s -X POST http://localhost:8787/api/carlog/auth/login -H 'Content-Type: application/json' -d '{"username":"admin","password":"carwash2026"}' | python3 -c "import json,sys; print(json.load(sys.stdin)['data']['token'])")
for ep in vehicles washes refuels insurance chemicals tags notifications achievements; do
echo "Testing /api/$ep ..."
curl -s -H "Authorization: Bearer $TOKEN" "http://localhost:8787/api/$ep" | head -1
echo "Testing /api/carlog/$ep ..."
curl -s -H "Authorization: Bearer $TOKEN" "http://localhost:8787/api/carlog/$ep" | head -1
done
# 期望: 每行返回 JSON 不报错
```
@@ -699,6 +700,84 @@ cd client && npm run dev
---
### Task 2.3.5: 前端 API 调用加 `/api/carlog/` 前缀(重要!)
**为什么**:后端路由全部从 `/api/vehicles` 改到 `/api/carlog/vehicles`,前端所有 API 调用必须同步改,否则登录后所有页面都 404。
**方案:每个子系统一个 API helperbaseURL**
不要在每个 view 里手写 `/api/carlog/...`。在 `client/src/api/subsystems.js` 加子系统 baseURL helper
```js
// client/src/api/subsystems.js
import { api } from './client.js';
// 子系统 API helper —— 一个 helper 一个 baseURL
export const carlogApi = {
get: (path, config) => api.get(`/api/carlog${path}`, config),
post: (path, data, config) => api.post(`/api/carlog${path}`, data, config),
put: (path, data, config) => api.put(`/api/carlog${path}`, data, config),
patch: (path, data, config) => api.patch(`/api/carlog${path}`, data, config),
delete: (path, config) => api.delete(`/api/carlog${path}`, config),
};
// 将来加 fitness 子系统直接加一行:
// export const fitnessApi = { ... 同结构, baseURL: '/api/fitness' };
// 平台层 API(不走子系统前缀)
export const platformApi = api; // 平台路由已经是 /api/platform/*, 用原始 api
```
**改造 client/src/api/ 下 9 个 CarLog 文件**:每个文件把 `import { api } from './client.js'` 改成 `import { carlogApi } from './subsystems.js'`,所有 `api.get('xxx')` 改成 `carlogApi.get('xxx')` —— **path 里不要带 `/api/carlog/` 前缀,helper 自动加**
```js
// 原来 (client/src/api/vehicles.js):
import { api } from './client.js';
export const listVehicles = () => api.get('/vehicles');
// 改成:
import { carlogApi } from './subsystems.js';
export const listVehicles = () => carlogApi.get('/vehicles');
// ^^^^^^^^^^ 注意是 carlogApi, path 不带 /api/carlog/
```
**改造 20 个 view 里直接调 api 的地方**(如果有 inline api 调用):
```js
// 原来: api.get('/vehicles')
// 改成: carlogApi.get('/vehicles')
```
**9 个 CarLog api 文件清单**(要看 `ls client/src/api/` 实际):
```
client/src/api/ai.js
client/src/api/auth.js
client/src/api/chemicals.js
client/src/api/client.js ← 这个不动!它是底层 axios 实例
client/src/api/insurance.js
client/src/api/logs.js
client/src/api/operationLogs.js
client/src/api/settings.js
```
**注意**:
- `client/src/api/client.js` 是底层 axios 实例(带 auth interceptor + 解包 `{ok,data}`),**不要动**
- 其他 8 个 api 文件全部 import `carlogApi` 而不是 `api`
- 平台层 viewGlobalSettings / SubsystemSettings / Subsystems)继续用 `api`(因为是 `/api/platform/*`
**验证**:
```bash
# 跑 grep 确认没有残留的 /api/vehicles /api/washes 字符串
grep -rn "/api/vehicles\|/api/washes\|/api/refuels\|/api/insurance\|/api/chemicals\|/api/auth" client/src/ --include="*.js" --include="*.vue"
# 期望: 没有结果(除了 api/subsystems.js 里写 baseURL 那行)
# 跑 server 起来 + 浏览器登录测试
cd server && npm run dev
cd client && npm run dev
# 浏览器登录 → 进所有页面 → 都正常加载
```
---
### Task 2.4: 前端平台层 — 总设置 UI
**新增文件**: `client/src/views/Platform/GlobalSettings.vue`
@@ -1062,12 +1141,13 @@ SELECT COUNT(*) AS refuels FROM refuel_records;
## 4. 不做的事(明确边界)
-**表前缀迁移**:本 Phase 不加 `carlog_` 前缀,留到 Phase 3+ 加第二个子系统时再做
-**表前缀迁移**:本 Phase 不加 `carlog_` 前缀,留到将来加第二个子系统时再做
-**JWT / SSO**:单用户 cookie session 已经够
-**iframe 嵌入**:同 SPA,不需要 iframe
-**独立子系统部署**:所有子系统一个进程
-**manifest 协议**subsystem 直接走 SQL 注册,不走 HTTP manifest 拉取
-**子系统 dashboard widget 跨子系统聚合**Phase 4 加第二子系统再做
-**加第二个子系统**(健身 / 阅读 / 任何):用户还没决定下一子系统做什么,本阶段**只做底座 + CarLog 子系统化**
-**子系统 dashboard widget 跨子系统聚合**:等 2+ 子系统再做
-**现有 CarLog 业务逻辑修改**:迁移目录不改 SQL、不改业务、不改 UI
-**现有 CarLog 测试代码修改**:除非路径变了(深层 import
@@ -1124,11 +1204,144 @@ Phase 2: CarLog 子系统化
## 6. 提交给 Mavis(我)做 code review 时
请把以下附上:
1. `git diff main --stat`(看了多少行)
2. `cd server && npm test` 输出
3. `cd client && npm run lint` 输出
4. MySQL `SHOW TABLES;` 输出
5. 手动 E2E 截图(关键 5 个页面: Dashboard / 总设置 / CarLog 设置 / 子系统管理 / 任意业务页面)
### 6.1 必须附上的材料
我会逐文件 review + 跑测试 + 给改进意见。
```bash
# 1. diff stat
cd /Users/yabozi/wzpstudio/i
git diff main --stat > review-diff-stat.txt
# 2. server 测试
cd server && npm install && npm test > review-server-test.txt 2>&1
# 3. client lint + build
cd ../client && npm install && npm run lint > review-client-lint.txt 2>&1
# 4. DB 表清单
mysql -h 162.14.110.130 -P 33306 -u carlog -pZeMRBwXP8JC6B3rF carlog \
-e "SHOW TABLES; SELECT COUNT(*) AS subsystems FROM subsystems; SELECT COUNT(*) AS settings FROM platform_settings;" > review-db.txt 2>&1
# 5. 关键 5 个页面截图(手动)
# - Dashboard (登录后首页)
# - 总设置 (/settings/global)
# - CarLog 设置 (/settings/carlog)
# - 子系统管理 (/admin/subsystems)
# - 任一业务页面 (/washes 或 /vehicles)
# 命名: review-screen-{1..5}-{name}.png
```
### 6.2 我会逐项检查的内容
#### 6.2.1 数据库(migration 019
- [ ] `019_platform.sql` 是否幂等(再跑一次不报错)
- [ ] `subsystems` 表字段齐全:`id, name, description, icon, color, category, version, enabled, sort_order, settings_schema (JSON), nav_items (JSON), created_at, updated_at`
- [ ] `platform_settings` 表字段齐全:`key (PK), value (JSON), type, description, updated_at`
- [ ] CarLog seed 数据:id='carlog', category='vehicle', nav_items 至少含 `/, /vehicles, /washes, /refuels, /stats, /settings` 6 项
- [ ] settings_schema 至少 5 个字段(string/number/boolean/select/password 各覆盖)
#### 6.2.2 平台后端路由
- [ ] `routes/platform/subsystems.js`: GET 列表、GET 单个、PATCH 启停三件齐
- [ ] `routes/platform/settings.js`: GET list、GET single、PUT single、POST batch 四件齐
- [ ] `routes/platform/dashboard.js`: 聚合 CarLog 4-6 个 stats 字段
- [ ] 所有路由都走 `requireAuth` middleware
- [ ] 响应统一 `{ok: true, data: ...}` 包装
- [ ] JSON 字段(settings_schema / nav_items / value)正确 parse
- [ ] mount 到 `/api/platform/*` 三条线(subsystems / settings / dashboard
#### 6.2.3 CarLog 后端目录迁移
- [ ] `server/src/routes/` 只剩 `.gitkeep`13 个文件全移走)
- [ ] `server/src/subsystems/carlog/routes/` 13 个文件齐全
- [ ] 每个文件 import 路径:`'../../db.js'``'../../../db.js'`, middleware 同理
- [ ] `subsystems/carlog/index.js` 正确聚合导出(**实际 ls 一下 routes/ 名字再 import,别凭印象**
- [ ] `server/src/index.js` mount 全部加 `/api/carlog/` 前缀(13 条 mount line
- [ ] **没有任何残留的 `/api/vehicles` 等老路径 mount**
#### 6.2.4 CarLog 前端目录迁移
- [ ] `client/src/views/subsystems/carlog/` 20 个 view 齐全
- [ ] `client/src/views/` 只剩 `.gitkeep + Login.vue + Offline.vue` 等非子系统
- [ ] `client/src/router/index.js` 所有 CarLog view import 改成新路径
- [ ] **前端 router path 不变**`/washes` 仍是 `/washes`,不是 `/carlog/washes`
#### 6.2.5 前端 API baseURL(最易出 bug 的地方)
- [ ] `client/src/api/subsystems.js` 创建 `carlogApi` helperget/post/put/patch/delete 5 个方法 + `/api/carlog` 前缀)
- [ ] 8 个 CarLog api 文件(ai / auth / chemicals / insurance / logs / operationLogs / settings / ... 实际 ls 一下)**全部 import carlogApi 而不是 api**
- [ ] **grep 验证无残留**:
```bash
grep -rn "/api/vehicles\|/api/washes\|/api/refuels\|/api/insurance\|/api/chemicals\|/api/auth" \
client/src/ --include="*.js" --include="*.vue" \
| grep -v "client/src/api/subsystems.js"
# 期望: 没有输出
```
- [ ] 20 个 view 里如果有 inline `api.get('/xxx')` 调用也改成 `carlogApi.get('/xxx')`
#### 6.2.6 平台前端
- [ ] `GlobalSettings.vue`: 5 个总设置字段 + 加载/保存 + alert 提示
- [ ] `SubsystemSettings.vue`: 通用渲染器支持 string/number/boolean/select/password/textarea 6 种类型
- [ ] `Subsystems.vue`: 列表 + 启停开关
- [ ] `stores/platform.js`: `subsystems` ref + `loadSubsystems()` + `groupedCategories` computed
- [ ] `AppLayout.vue`: 菜单读 `platform.groupedCategories` 而不是硬编码
- [ ] 启动时调 `platform.loadSubsystems()`mounted / onMounted
#### 6.2.7 路由
- [ ] `client/src/router/index.js` 加 3 条平台路由:`/settings/global`, `/settings/:subsystem`, `/admin/subsystems`
- [ ] 3 个平台 view 都在 `meta.requiresAuth = true`
- [ ] path **不重复**现有 router
#### 6.2.8 测试
- [ ] `server/test/routes/platform.*.test.js` 至少 12 个新测试
- [ ] **旧 101 个测试一个不破**(任何破测试 = Phase 2 失败)
- [ ] 测试用 supertest + mock db,参考 `server/test/setup.js`
#### 6.2.9 E2E 手动验证
我会跑这 14 步(Dev Plan Phase 3.3 清单),任何一步失败都要返工:
1. 登录 admin/carwash2026
2. 左侧菜单按 🚗 车辆 / ⚙️ 系统 分组
3. `/washes` 加载列表
4. `/vehicles` 加载列表
5. `/refuels` 加载列表
6. `/stats` 加载图表
7. `/settings/global` 加载 + 5 字段显示
8. 改主题 → 保存 → 刷新保持
9. `/settings/carlog` 加载 + 5 字段显示
10. 改 `ai.provider` → 保存 → DB 里有 `carlog.ai.provider`
11. `/admin/subsystems` 看到 CarLog 一条
12. 取消启用 → 刷新 → 菜单无 CarLog
13. 重新启用 → 菜单恢复
14. 重新登录 → 设置还在
#### 6.2.10 数据完整性
- [ ] migration 019 **没动**现有 14 张 CarLog 表
- [ ] `SELECT COUNT(*) FROM vehicles;` 等数字跟改造前一致
### 6.3 我会特别盯的几个常见坑
1. **mysql2 JSON 字段 parse 漏** — 直接返回字符串给前端,前端 `data.settings_schema` 变 `[object Object]` 或 undefined
2. **TINYINT(1) 不转 boolean** — 前端 `data.enabled` 可能是 0/1 而不是 true/false
3. **mount 顺序错** — auth middleware 在业务 route 之后 mount 导致 token 检查失败
4. **carwash 路由文件 import 路径没改全** — `from '../../db.js'` 还是老的,跑起来才报错
5. **前端 API 没改全** — 有些 view 里 inline `axios.get('/vehicles')` 漏改,所有页面 404
6. **router path 改了** — 用户 URL 从 `/washes` 变 `/carlog/washes`,用户书签/链接全失效(**不要改 router path**
7. **platform store 没在 AppLayout mounted 加载** — 菜单永远空
8. **subsystem 启停后没 reload store** — 切启用状态菜单没更新
9. **nav_items JSON 没 parse** — 菜单变 `[object Object]`
10. **测试 mock 没覆盖新路由** — 测试覆盖率掉了
### 6.4 review 通过标准
- 所有 6.2 子项勾完
- 6.3 10 个常见坑都不存在
- 6.1 五份材料齐全
- 我跑完测试 + E2E 后给「OK」反馈
任何 review 反馈都要修完再合并下一阶段。