diff --git a/README.md b/README.md index 7cbd268..bfe7479 100644 --- a/README.md +++ b/README.md @@ -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 schema(JSON 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 \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a4c14c2..3703f5c 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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 schema(JSON 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 helper(baseURL = /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. 不做的事 diff --git a/docs/DEV-PLAN.md b/docs/DEV-PLAN.md index ed4addd..64b246c 100644 --- a/docs/DEV-PLAN.md +++ b/docs/DEV-PLAN.md @@ -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; ``` **注意**: -- 现在只聚 CarLog;Fitness / Reading 子系统加进来后再扩 +- 现在只聚 CarLog;将来加新子系统时再扩字段 - 用 UTC_DATE()(mysql2 timezone='Z' 配置已经设为 UTC) - 总数 / 总额用 `Number()` 转 JS number(mysql2 返回 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 helper(baseURL)** + +不要在每个 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` +- 平台层 view(GlobalSettings / 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 + 跑测试 + 给改进意见。 \ No newline at end of file +```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` helper(get/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 反馈都要修完再合并下一阶段。 \ No newline at end of file