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
+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 反馈都要修完再合并下一阶段。