docs: rewrite README — drop emoji tree + long changelog, ~120 lines
砍掉 AI 味的 emoji 功能树 / v2.2~v2.8 长 changelog / 详细部署步骤。 部署细节链到 docs/install/。踩坑清单保留(CSRF / Pinia / MiniMax / MySQL pool 4 个坑)。
This commit is contained in:
@@ -1,870 +1,85 @@
|
||||
# 洗车管理系统
|
||||
|
||||
个人 detailer 爱好者的私家车管理系统。Express + MySQL/SQLite + Vue 3 全栈单用户应用,所有数据保存在你自己的服务器上。
|
||||
给我自己的私家车记账用的。
|
||||
|
||||
> 适用场景:给自己洗的 / 帮朋友洗的、给车加油 / 充电 / 保养 / 上保险做完整台账,对接 Grocy 做汽美用品库存,自动抓天气。
|
||||
养车这件事最容易稀里糊涂——洗车/加油/充电/保养/保险,哪个花了多少全靠记性。这玩意儿就是把每笔账都记下来,顺带把天气、Grocy 汽美用品库存、保养预测都塞进一个面板里。所有数据存自己服务器上,不上云。
|
||||
|
||||
## ✨ 功能特性
|
||||
跑了一年多,V2.8 还在迭代。下面是当前状态。
|
||||
|
||||
### 🌳 功能树(按领域组织)
|
||||
## 功能
|
||||
|
||||
```
|
||||
洗车管理系统
|
||||
├── 🔐 账号与权限
|
||||
│ ├── 单用户登录(admin 默认账号 / bcrypt 密码)
|
||||
│ ├── CSRF token 自动刷新 + 重试
|
||||
│ ├── 登录失败锁定:IP 5 次锁 15 分 / 用户 5 次锁 30 分 / 全局 10 次锁 1 小时
|
||||
│ ├── 锁定时显示「已错 N 次 / 还剩 N 次 / 锁定 X 分」+ 解锁倒计时
|
||||
│ ├── session 过期 401 自动跳登录 + 表单草稿暂存
|
||||
│ └── 修改密码 / 启用 / 停用账号
|
||||
│
|
||||
├── 🚗 车辆管理
|
||||
│ ├── CRUD(车牌 / 品牌 / 型号 / 年款 / 颜色 / 类型)
|
||||
│ ├── 软删除(is_deleted 标记,可从操作日志恢复)
|
||||
│ ├── 车辆详情页:基本信息 + 健康卡片 + 6 月趋势图 + 5 tab 记录
|
||||
│ ├── 车辆健康指标:油耗 / 电耗 / 洗车新鲜度 / 保养预测
|
||||
│ └── 多车总览统计(总数 / 启用数 / 有洗车记录数)
|
||||
│
|
||||
├── 🧽 洗车记录(核心领域)
|
||||
│ ├── CRUD + 类型:快速 / 标准 / 精洗 / 其他
|
||||
│ ├── 自动抓天气快照(洗车当天的天气)
|
||||
│ ├── 选化学品 → 自动扣减 Grocy 库存
|
||||
│ ├── AI 截图识别小票 → 自动填表 + 失败兜底 modal
|
||||
│ ├── 洗车前后对比照:上传 / 删除 / 并排对比
|
||||
│ └── 批量删除
|
||||
│
|
||||
├── ⛽ 加油记录
|
||||
│ ├── CRUD + 油品:92#/95#/98#/0#柴油/-10#/E92/E95/LPG
|
||||
│ ├── 自动算百公里油耗(仅 is_full=1 相邻加满里程差 / 升数 × 100)
|
||||
│ ├── 油价趋势图(按月聚合 derived_unit_price)
|
||||
│ └── AI 截图识别小票
|
||||
│
|
||||
├── 🔌 充电记录
|
||||
│ ├── CRUD + 充电类型:home / slow / fast / public
|
||||
│ ├── 自动算百公里电耗(kWh / 里程差 × 100)
|
||||
│ ├── 起止 SOC(电量 %)
|
||||
│ └── AI 截图识别小票
|
||||
│
|
||||
├── 🔧 保养记录
|
||||
│ ├── CRUD + 动态项目(机油 / 机滤 / 空滤 / 刹车油 / 防冻液...)
|
||||
│ ├── datalist + presets 自动补全
|
||||
│ ├── watch + auto calc total_cost = items Σ
|
||||
│ ├── AI 截图识别小票
|
||||
│ └── 下次保养里程预测
|
||||
│
|
||||
├── 🛡️ 保险记录
|
||||
│ ├── CRUD + 类型:交强 / 商业 / 车损 / 三责 / 座位 / 不计免赔 / 玻璃 / 划痕 / 自燃 / 涉水
|
||||
│ ├── 到期提醒(30 天内橙色,已过期红色)
|
||||
│ ├── 附件上传(PDF 保单 / 照片)
|
||||
│ └── AI 截图识别保单
|
||||
│
|
||||
├── 🧴 化学品 / 汽美用品
|
||||
│ ├── Grocy 实例对接(session cookie / API token 双模式)
|
||||
│ ├── 同步模式:手动 / 启动自动(grocy_pull_auto)
|
||||
│ ├── 产品镜像:拉 Grocy 全量产品 + 库存到本地
|
||||
│ ├── 库存操作:入库 / 扣减 / 盘点 / 低库存预警(grocy_low_stock_pct)
|
||||
│ ├── 分类映射:本地分类 ↔ Grocy 分类
|
||||
│ ├── 批量入库(BatchPurchase)
|
||||
│ └── 全局搜索(grocy-search 走 Grocy API)
|
||||
│
|
||||
├── 📊 数据可视化
|
||||
│ ├── 总览(stats/overview):今日 / 本月 / 30 天频次 / 月度成本
|
||||
│ ├── 健康仪表盘:6 月趋势堆叠柱
|
||||
│ ├── 油价趋势(stats/extra.fuelTrend):按月 derived_unit_price
|
||||
│ ├── 年均养护成本(stats/extra.costPerVehicle):所有成本 / 持有天数 × 365
|
||||
│ ├── 洗车季节频率(stats/extra.washSeason):按月 cnt + avg_cost
|
||||
│ └── 各车成本明细表
|
||||
│
|
||||
├── 📑 报表
|
||||
│ ├── 月度报表 Excel(6 sheet:车辆/洗车/加油/充电/保养/保险)
|
||||
│ ├── 月度报表 PDF
|
||||
│ └── 月份下拉(reports/monthly/list,过去 12-24 个月)
|
||||
│
|
||||
├── 🤖 AI 截图识别
|
||||
│ ├── 5 种类型 OCR schema(wash/refuel/charge/maint/insurance)
|
||||
│ ├── 多 provider:OpenAI 兼容 / MiniMax M3 多模态
|
||||
│ ├── Provider 切换 + API key 配置
|
||||
│ ├── 「测试连接」按钮(动态选真实上传图,避免 1×1 PNG 触发敏感)
|
||||
│ ├── thinking 关闭(MiniMax M3 OCR 任务)
|
||||
│ └── 识别失败兜底 modal(左图右表,手动填)
|
||||
│
|
||||
├── ⚙️ 设置
|
||||
│ ├── 个人信息:用户名 / 修改密码
|
||||
│ ├── AI 配置:provider / URL / key / model / 启用
|
||||
│ ├── Grocy 配置:URL / 用户名 / 密码 / token / 同步策略
|
||||
│ ├── 天气:默认城市(库尔勒)/ 实时天气
|
||||
│ ├── 系统:登录锁定参数 / session 有效期 / cookie secure
|
||||
│ ├── 数据重置(confirm_token 强校验)
|
||||
│ └── Grocy 同步日志
|
||||
│
|
||||
├── 📜 操作日志
|
||||
│ ├── 所有写操作记录(created/updated/deleted/recovered)
|
||||
│ ├── 软删记录可一键恢复
|
||||
│ └── 类型筛选(vehicles/washes/refuels/...)
|
||||
│
|
||||
├── 🛡️ 安全 & 防滥用
|
||||
│ ├── bcrypt 密码
|
||||
│ ├── express-session + httpOnly cookie
|
||||
│ ├── CSRF token(所有非 GET 请求校验)
|
||||
│ ├── 登录防撞库(IP + 用户 + 全局三级)
|
||||
│ ├── IP 限流(AI 60s/10 次,sync 60s/10 次)
|
||||
│ ├── 422 输入校验(字段必填 / 类型 / 长度)
|
||||
│ └── 操作日志审计
|
||||
│
|
||||
├── 📱 PWA
|
||||
│ ├── manifest(id / icons 192/512/maskable/apple-touch / shortcuts)
|
||||
│ ├── Service Worker(vite-plugin-pwa autoUpdate)
|
||||
│ ├── 离线缓存:API static(30 天 CacheFirst)+ uploads + images(SWR)+ fonts
|
||||
│ ├── navigateFallback → /index.html(白名单 /api/、/uploads/)
|
||||
│ ├── iOS / Android / Desktop 安装提示(beforeinstallprompt 拦截 + 引导)
|
||||
│ ├── 新版本可用提示(needRefresh toast)
|
||||
│ └── 离线就绪提示
|
||||
│
|
||||
├── 🔧 工具与脚本
|
||||
│ ├── 备份:bin/backup.js(SQLite 拷贝 / MySQL mysqldump)
|
||||
│ ├── 导出:bin/export.js(JSON / CSV,单表 / 全量)
|
||||
│ ├── 灌种子:bin/seed-demo.js
|
||||
│ ├── 重置:bin/reset-all.js(强 confirm_token)
|
||||
│ ├── Grocy 拉取:bin/grocy-refresh-products.js
|
||||
│ ├── 天气刷新:bin/weather.js
|
||||
│ ├── 账号管理:bin/users.js(disable / enable / reset pwd)
|
||||
│ ├── 验证:bin/verify.js
|
||||
│ └── 迁移:bin/migrate.js(15 个 SQL 幂等执行)
|
||||
│
|
||||
├── 🩺 运维
|
||||
│ ├── 健康检查:`/api/health`(兼容)+ `/api/health/live`(进程活)+ `/api/health/ready`(DB 通)
|
||||
│ ├── 调试面板:DebugPanel(API 调用日志 + Vue error + unhandledrejection)
|
||||
│ └── OpenAPI 文档:`/api/docs`(Swagger UI)+ `/api/openapi.json`
|
||||
│
|
||||
└── 🌐 部署
|
||||
├── Express HTTP 服务(port 8787)
|
||||
├── 静态资源托管(uploads/)
|
||||
├── SPA fallback(client/dist/)
|
||||
├── 宝塔面板部署文档(PM2 + Nginx + SSL)
|
||||
└── Docker-ready(carlog-init.sql 幂等)
|
||||
- **车**:CRUD + 软删除 + 健康卡片(油耗/电耗/保养预测)
|
||||
- **洗车**:CRUD + 4 种类型 + 自动抓天气 + AI 截图 OCR 自动填表 + 前后对比照
|
||||
- **加油**:CRUD + 自动算百公里油耗(仅加满记录)+ 油价趋势图
|
||||
- **充电**:CRUD + 自动算百公里电耗
|
||||
- **保养**:CRUD + 动态项目(机油/机滤/空滤...)+ OCR
|
||||
- **保险**:CRUD + 到期提醒 + 附件上传
|
||||
- **化学品**:Grocy 镜像同步 + 库存扣减 + 低库存预警
|
||||
- **仪表盘**:30 天频次 + 同比环比 + 油价 + 年均养护 + 季节频率 + 各车成本
|
||||
- **月度报表**:Excel(6 sheet)+ PDF
|
||||
- **提醒中心**:加油/保养/洗车超期阈值可配
|
||||
- **全局搜索**:跨 7 领域带高亮(车牌/商家/保单号...)
|
||||
- **标签**:给记录打 #打蜡 #通勤 之类的,可查频率
|
||||
- **通知中心**:OCR/同步/备份结果持久化
|
||||
- **成就**:14 个预置成就(洗车新手→狂魔,万里征程...)
|
||||
- **PWA**:iOS/Android 可装到桌面 + 离线缓存
|
||||
|
||||
## 🛠 技术栈
|
||||
API 总共 60+ 端点,完整列表跑起来后看 `/api/docs`。
|
||||
|
||||
- **后端**:Node.js 18+ / Express 4 / MySQL 8 (主) / SQLite (回退)
|
||||
- **前端**:Vue 3 + Vite + Pinia + Vue Router + Naive UI
|
||||
- **外部依赖**:Grocy(汽美库存,可选)/ wttr.in(天气)/ OpenAI 兼容 AI(可选)
|
||||
## 技术栈
|
||||
|
||||
## 📦 目录结构
|
||||
后端 Node 18 + Express + MySQL 8(主)/ SQLite(回退)。前端 Vue 3 + Vite + Pinia。PWA 用 vite-plugin-pwa。OCR 接 MiniMax M3 多模态或任意 OpenAI 兼容端点。
|
||||
|
||||
```
|
||||
洗车管理系统/
|
||||
├── client/ # Vue 3 前端
|
||||
│ ├── dist/ # ← 构建产物(已包含在 zip 里,直接部署)
|
||||
│ ├── src/
|
||||
│ │ ├── api/ # API 客户端
|
||||
│ │ ├── views/ # 页面(17 个)
|
||||
│ │ ├── components/ # 组件
|
||||
│ │ ├── stores/ # Pinia 状态
|
||||
│ │ └── router.js
|
||||
│ └── package.json
|
||||
├── server/ # Express 后端
|
||||
│ ├── src/
|
||||
│ │ ├── routes/ # 路由(auth/washes/chemicals/vehicles/...)
|
||||
│ │ ├── services/ # 业务逻辑(grocyClient/weather/backup/...)
|
||||
│ │ ├── middleware/ # auth/csrf
|
||||
│ │ ├── db.js # MySQL/SQLite 统一接口
|
||||
│ │ ├── config.js # 配置加载(DB_URL / Grocy / AI)
|
||||
│ │ ├── setup.js # 首次初始化向导
|
||||
│ │ └── bin/ # 命令行工具
|
||||
│ │ ├── serve.js # 启动服务器
|
||||
│ │ ├── migrate.js # 跑迁移
|
||||
│ │ ├── reset-all.js # 清空 + 可选灌种子
|
||||
│ │ ├── backup.js # 备份 SQLite/MySQL
|
||||
│ │ ├── export.js # CSV/JSON 导出
|
||||
│ │ └── ...
|
||||
│ └── migrations/ # SQL 迁移
|
||||
│ ├── 0001_init.sql
|
||||
│ ├── mysql/ # MySQL 专属
|
||||
│ └── ...
|
||||
├── docs/
|
||||
│ └── install/
|
||||
│ └── INSTALL-BT-NODE.md
|
||||
├── 洗车管理系统-v2.0-源码.zip # ← 部署包(492 KB,不含 node_modules/.env)
|
||||
└── .env # 配置(DB / Grocy / AI / Session Secret,不在 zip 里)
|
||||
```
|
||||
## 部署
|
||||
|
||||
## 🚀 安装部署
|
||||
我自己在宝塔上跑。详见 [docs/install/INSTALL-BT-NODE.md](docs/install/INSTALL-BT-NODE.md)。
|
||||
|
||||
### 1. 准备环境
|
||||
|
||||
- Node.js **≥ 18**
|
||||
- MySQL 8.x(推荐;没有的话会自动回退 SQLite)
|
||||
- 一个 Grocy 实例(可选,没有也能用,只是化学品模块受限)
|
||||
|
||||
### 2. 克隆并安装
|
||||
|
||||
```bash
|
||||
git clone <your-repo-url> 洗车管理系统
|
||||
cd 洗车管理系统
|
||||
|
||||
# 后端依赖
|
||||
cd server && npm install
|
||||
|
||||
# 前端依赖
|
||||
cd ../client && npm install
|
||||
```
|
||||
|
||||
### 3. 配置 `.env`
|
||||
|
||||
在项目根目录 `洗车管理系统/.env`:
|
||||
|
||||
```env
|
||||
# ====== 数据库(MySQL 优先,缺省回退 SQLite)======
|
||||
DB_HOST=162.14.110.130
|
||||
DB_PORT=33306
|
||||
DB_USER=carlog
|
||||
DB_PASSWORD=你的密码
|
||||
DB_NAME=carlog
|
||||
|
||||
# 或使用完整 URL:
|
||||
# DB_URL=mysql://carlog:你的密码@162.14.110.130:33306/carlog
|
||||
|
||||
# ====== 服务端 ======
|
||||
NODE_ENV=production
|
||||
PORT=8787
|
||||
|
||||
# Session 加密(生产必改!)
|
||||
SESSION_SECRET=你的长随机字符串
|
||||
|
||||
# ====== Grocy(可选)======
|
||||
GROCY_URL=https://your-grocy.example.com/
|
||||
GROCY_USERNAME=admin
|
||||
GROCY_PASSWORD=your-password
|
||||
# 也可以用 API Key:
|
||||
# GROCY_API_KEY=your-api-key
|
||||
|
||||
# ====== AI(可选)======
|
||||
AI_PROVIDER_URL=https://api.openai.com/v1
|
||||
AI_API_KEY=sk-xxx
|
||||
AI_MODEL=gpt-4o-mini
|
||||
```
|
||||
|
||||
> 说明:所有设置项都能在 **Web UI 的「设置」页** 里再改。`.env` 只是初始默认值。
|
||||
|
||||
### 4. 初始化数据库
|
||||
|
||||
```bash
|
||||
cd server
|
||||
node src/bin/migrate.js
|
||||
# 输出:✓ 0001_init.sql ... ✓ 0014_grocy_auth.sql
|
||||
```
|
||||
|
||||
### 5. 构建前端
|
||||
|
||||
```bash
|
||||
cd ../client
|
||||
npm run build # 产物在 client/dist/
|
||||
```
|
||||
|
||||
### 6. 启动服务
|
||||
|
||||
```bash
|
||||
cd ../server
|
||||
node src/bin/serve.js
|
||||
# [server] http://0.0.0.0:8787
|
||||
# [db] MySQL connected (carlog)
|
||||
```
|
||||
|
||||
首次访问 `http://localhost:8787/` 会跳到 `/setup` 引导你创建管理员账号。
|
||||
|
||||
### 7. (可选)灌种子数据
|
||||
|
||||
```bash
|
||||
cd server
|
||||
node src/bin/reset-all.js --seed # 清空 + 写 2 辆车 + ~50 条记录
|
||||
node src/bin/seed-demo.js # 单独跑种子(不清空)
|
||||
```
|
||||
|
||||
### 8. 生产部署(PM2)
|
||||
|
||||
```bash
|
||||
npm install -g pm2
|
||||
cd server
|
||||
pm2 start src/bin/serve.js --name carwash
|
||||
pm2 save
|
||||
pm2 startup
|
||||
```
|
||||
|
||||
Nginx 反向代理示例:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name carwash.your.domain;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8787;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🐯 宝塔面板部署(推荐国内服务器)
|
||||
|
||||
宝塔自带 Node.js / MySQL / Nginx,不用自己折腾环境。
|
||||
|
||||
### 准备工作
|
||||
|
||||
宝塔软件商店装好:
|
||||
|
||||
- **Nginx 1.22+**
|
||||
- **MySQL 8.0** (或 5.7,文档以 8.0 为准)
|
||||
- **PM2 管理器**(宝塔商店搜不到就用 Node.js 版本管理器里的 PM2)
|
||||
- **Node.js 18+**(用宝塔的 Node.js 版本管理器装)
|
||||
|
||||
### 1. 上传源码
|
||||
|
||||
把 `洗车管理系统-v2.0-源码.zip`(492 KB,已排除 node_modules 和 .env)传到宝塔,比如:
|
||||
|
||||
```
|
||||
/www/wwwroot/carwash/
|
||||
```
|
||||
|
||||
宝塔文件管理器 → 上传 zip → 右键解压。解压后结构:
|
||||
|
||||
```
|
||||
/www/wwwroot/carwash/
|
||||
├── client/dist/ # 已构建好的前端
|
||||
├── server/
|
||||
├── docs/
|
||||
├── package.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### 2. 建数据库
|
||||
|
||||
宝塔 **数据库** → 添加数据库:
|
||||
|
||||
- 数据库名:`carlog`
|
||||
- 用户名:`carlog`
|
||||
- 密码:点「随机生成」,**复制保存**(等下要写到 .env)
|
||||
- 编码:`utf8mb4`
|
||||
- 访问权限:本服务器
|
||||
|
||||
### 2.5 初始化数据库(任选一种方式)
|
||||
|
||||
**方式 A:一键 SQL(推荐,宝塔友好)**
|
||||
|
||||
直接把根目录的 `carlog-init.sql`(28 KB)导入:
|
||||
|
||||
- 宝塔:**数据库** → 选 `carlog` 库 → **导入** → 选 `carlog-init.sql` → 提交
|
||||
- 或命令行:
|
||||
```bash
|
||||
mysql -ucarlog -p carlog < carlog-init.sql
|
||||
```
|
||||
|
||||
> 这个 SQL 已经做了**完全幂等**(存储过程 + try-catch),已存在的表/索引/列会自动跳过,**反复重跑不会破坏数据**。也无需再跑 `migrate.js`(migration 表会标记为已应用)。
|
||||
|
||||
**方式 B:用源码迁移**
|
||||
|
||||
如果偏好用源码版本(库已有数据想增量迁移):
|
||||
|
||||
```bash
|
||||
cd /www/wwwroot/carwash/server
|
||||
node src/bin/migrate.js
|
||||
```
|
||||
|
||||
在项目根目录(`/www/wwwroot/carwash/.env`)新建文件:
|
||||
|
||||
```env
|
||||
# ====== 数据库 ======
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=carlog
|
||||
DB_PASSWORD=刚才复制保存的密码
|
||||
DB_NAME=carlog
|
||||
|
||||
# ====== 服务端 ======
|
||||
NODE_ENV=production
|
||||
PORT=8787
|
||||
|
||||
# Session 加密(务必改成 32 位以上随机字符串,宝塔「终端」跑 node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" 生成)
|
||||
SESSION_SECRET=你的32位随机字符串
|
||||
|
||||
# ====== Grocy(可选)======
|
||||
GROCY_URL=
|
||||
GROCY_USERNAME=
|
||||
GROCY_PASSWORD=
|
||||
|
||||
# ====== AI(可选)======
|
||||
AI_PROVIDER_URL=https://api.openai.com/v1
|
||||
AI_API_KEY=
|
||||
AI_MODEL=gpt-4o-mini
|
||||
```
|
||||
|
||||
### 4. 装依赖
|
||||
|
||||
宝塔 → 终端(确保当前在项目根目录):
|
||||
|
||||
```bash
|
||||
cd /www/wwwroot/carwash/server
|
||||
npm install --production
|
||||
# 后端依赖装好(数据库已经在 2.5 步导入过了)
|
||||
|
||||
cd ../client
|
||||
npm install
|
||||
# 前端依赖装好(dist 已经预构建,不重新 build 也行;改前端代码时才需要 npm run build)
|
||||
```
|
||||
|
||||
> 注意:宝塔终端默认用 root 用户,文件权限直接就是 755。如果遇到权限问题:`chown -R www:www /www/wwwroot/carwash`
|
||||
|
||||
### 5. 用 PM2 启动
|
||||
|
||||
宝塔 PM2 管理器 → 添加项目:
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 项目名称 | `carwash` |
|
||||
| 运行目录 | `/www/wwwroot/carwash/server` |
|
||||
| 启动文件 | `src/bin/serve.js` |
|
||||
| 启动选项 | 留空 |
|
||||
| 端口 | `8787` |
|
||||
| Node 版本 | 18+(用版本管理器切换) |
|
||||
|
||||
点「提交」启动。日志会显示 `[server] http://0.0.0.0:8787` 就是成了。
|
||||
|
||||
### 6. 配 Nginx 反代
|
||||
|
||||
宝塔 **网站** → 添加站点 → 域名填你的(比如 `carwash.your.domain`)→ PHP 选「纯静态」
|
||||
|
||||
然后在站点设置里 **配置文件**,把 `location /` 段替换成:
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8787;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
```
|
||||
|
||||
保存。
|
||||
|
||||
### 7. 首次访问
|
||||
|
||||
浏览器打开你的域名,**首次会自动跳到 `/setup`**,跟着引导:
|
||||
|
||||
1. 创建管理员账号(用户名 + 密码)
|
||||
2. 选是否启用 Grocy(也可以先跳过,回设置里填)
|
||||
3. 完成 → 登录
|
||||
|
||||
### 8. 申请 SSL(可选但强烈推荐)
|
||||
|
||||
宝塔站点 → SSL → Let's Encrypt → 选域名 → 申请 → 强制 HTTPS 打开。
|
||||
|
||||
申请完 `SESSION_SECRET` 不动,但 `.env` 里的 `session.cookie_secure` 改成 `true`(在 Web 设置页里改也行),重启 PM2。
|
||||
|
||||
### 9. 备份策略
|
||||
|
||||
宝塔 **计划任务** 加两条:
|
||||
|
||||
- **每日 03:00**:`node /www/wwwroot/carwash/server/src/bin/backup.js`
|
||||
- **每周日 03:00**:`/www/server/mysql/bin/mysqldump -ucarlog -p你的密码 carlog | gzip > /www/backup/carlog_$(date +\%F).sql.gz`
|
||||
|
||||
### 10. 常见问题
|
||||
|
||||
| 现象 | 排查 |
|
||||
|------|------|
|
||||
| PM2 启动后立刻 exit | 看 PM2 日志;多半是 `.env` 拼写错误或数据库连不上 |
|
||||
| 访问域名 502 | PM2 没起;Nginx 反代 IP 错了;先 `curl http://127.0.0.1:8787/api/health` 看后端通不通 |
|
||||
| `/setup` 一直重定向 | `/www/wwwroot/carwash/.setup_done` 文件存在说明已经初始化过;删了重置 |
|
||||
| Grocy 同步失败 | 在 Web 设置页测连接;Grocy 实例要能从服务器访问到 |
|
||||
| 时区不对 | 服务器 `date` 看是 UTC 就 `timedatectl set-timezone Asia/Shanghai`;或保持 UTC,MySQL 内部也是 UTC,前端会按本地显示 |
|
||||
|
||||
### 升级步骤
|
||||
最简步骤:
|
||||
|
||||
```bash
|
||||
cd /www/wwwroot/carwash
|
||||
# 1. 备份
|
||||
node server/src/bin/backup.js
|
||||
|
||||
# 2. 拉新代码(如果你用 git)或重新上传新 zip 解压覆盖
|
||||
# 注意保留 .env 和 uploads/
|
||||
|
||||
# 3. 装新依赖
|
||||
cd server && npm install --production
|
||||
cd ../client && npm install && npm run build
|
||||
|
||||
# 4. 跑新迁移(自动跳过已跑过的)
|
||||
cd ../server && node src/bin/migrate.js
|
||||
|
||||
# 5. 重启 PM2
|
||||
pm2 restart carwash
|
||||
unzip 洗车管理系统-v2.8-源码.zip
|
||||
npm install --prefix server
|
||||
npm install --prefix client && npm run build --prefix client
|
||||
node server/src/bin/migrate.js # 自动跑 18 个 SQL
|
||||
pm2 start server/src/bin/serve.js --name carlog
|
||||
# Nginx 反代 127.0.0.1:8787
|
||||
```
|
||||
|
||||
## 🧪 测试结果(2026-06-18)
|
||||
数据库配置走 `.env`:
|
||||
|
||||
每一项都用 `curl` 打了真请求,全过:
|
||||
```
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=carlog
|
||||
DB_PASSWORD=xxx
|
||||
DB_NAME=carlog
|
||||
SESSION_SECRET=random-32-bytes
|
||||
```
|
||||
|
||||
| 模块 | 测试 | 结果 |
|
||||
|------|------|------|
|
||||
| 登录 | `POST /api/auth/login` | ✅ 返回 `{ok:true, data:{user}}` |
|
||||
| CSRF | `GET /api/auth/csrf` | ✅ |
|
||||
| 车辆列表 | `GET /api/vehicles` | ✅ 返回数组(每条带 wash_count, total_cost) |
|
||||
| 车辆统计 | `GET /api/vehicles/stats` | ✅ `{total:3, active:3}` |
|
||||
| 车辆创建 | `POST /api/vehicles` | ✅ 返回 id |
|
||||
| 车辆详情 | `GET /api/vehicles/:id` | ✅ |
|
||||
| 车辆更新 | `PUT /api/vehicles/:id` | ✅ |
|
||||
| 车辆软删 | `DELETE /api/vehicles/:id` | ✅ |
|
||||
| 洗车列表 | `GET /api/washes` | ✅ `{rows,total,page,limit}` |
|
||||
| 洗车创建 | `POST /api/washes` | ✅(字段 `wash_type` 非 `service_type`) |
|
||||
| 洗车详情 | `GET /api/washes/:id` | ✅ 含天气快照 + 化学品列表 |
|
||||
| 洗车删除 | `DELETE /api/washes/:id` | ✅ |
|
||||
| 化学品列表 | `GET /api/chemicals` | ✅ 224 条 |
|
||||
| 化学品详情 | `GET /api/chemicals/:id` | ✅ 含 Grocy stock entries |
|
||||
| 化学品更新 | `PUT /api/chemicals/:id` | ✅ |
|
||||
| 化学品创建 | `POST /api/chemicals` | ✅ 写入 Grocy + 本地 |
|
||||
| 化学品扣减 | `POST /api/chemicals/:id/consume` | ✅ 调 Grocy API |
|
||||
| **Grocy 同步** | `POST /api/chemicals/sync` | ✅ **224 个产品从 Grocy 拉取** |
|
||||
| 加油 CRUD | `/api/refuels` | ✅ 全部 |
|
||||
| 充电 CRUD | `/api/chargings` | ✅ 全部 |
|
||||
| 保养 CRUD | `/api/maintenances` | ✅ items 数组 ↔ items_json 自动转换 |
|
||||
| 保险 CRUD | `/api/insurances` | ✅(字段 `company` 非 `insurer`) |
|
||||
| 保险附件 | `POST /api/insurances/:id/upload` | ✅ multer |
|
||||
| 设置读 | `GET /api/settings` | ✅ 返回全部 24 项 |
|
||||
| 设置写 | `POST /api/settings` | ✅ 按 group 通用 update |
|
||||
| 城市读 | `GET /api/settings/city` | ✅ `{saved_city, default_city, is_auto_today}` |
|
||||
| 天气 | `GET /api/settings/weather` | ✅ wttr.in 实时数据 |
|
||||
| Grocy 同步日志 | `GET /api/settings/grocy-logs` | ✅ |
|
||||
| **数据重置** | `POST /api/settings/reset` | ✅ 需要 `confirm_token=RESET-ALL-DATA` |
|
||||
| 操作日志 | `GET /api/operation-logs` | ✅ 软删自动记录 |
|
||||
| 恢复记录 | `POST /api/operation-logs/:id/recover` | ✅ |
|
||||
| AI 配置 | `GET /api/ai/config` | ✅ |
|
||||
| AI 识别 | `POST /api/ai/recognize` | ✅(需先 upload) |
|
||||
| 仪表盘 | `GET /api/stats/overview` | ✅ |
|
||||
| 健康检查 | `GET /api/health` | ✅ |
|
||||
## 默认账号
|
||||
|
||||
### 测试中发现并修复的 Bug
|
||||
`admin` / `carwash2026`。登录后到「设置 → 账户」改密码。
|
||||
|
||||
| # | 文件 | Bug | 修复 |
|
||||
|---|------|-----|------|
|
||||
| 1 | `server/src/services/grocyWrite.js` | `m.ensureCookie is not a function`:`ensureCookie` 未从 grocyClient 导出,却通过 `import().then(m => m.ensureCookie)` 取 | 改用导出函数 `grocyPost` |
|
||||
| 2 | `server/src/routes/logs.js` | `Column 'items_json' cannot be null`:保养 POST 时前端传 `items: []`,但 INSERT 直接拿 `b.items_json`,没序列化 | POST/PUT 加 `Array.isArray(b.items) ? JSON.stringify(b.items) : ...` |
|
||||
| 3 | `server/src/routes/logs.js` | `enrich()` 用 `JSON.parse(r.items_json)`,但 MySQL JSON 列已自动解析成对象 → 抛错 → 返回空数组 | 加 `Array.isArray(r.items_json)` 分支 |
|
||||
| 4 | `server/src/routes/chemicals.js` | `You can't specify target table 'chemicals' for update in FROM clause`:sync 路由 UPDATE 用了 `OR grocy_product_id IN (SELECT FROM chemicals)` 自引用 | 简化为 `WHERE source = 'grocy'` |
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
### v2.8(当前版本 · 2026-06-20)
|
||||
|
||||
Trae 加了 8 个新功能(高 ROI 4 个 + 中 ROI 3 个 + 长期 1 个),并修了我找到的 4 个 bug:
|
||||
|
||||
**新功能**:
|
||||
|
||||
- **🚗 里程表录入 + 提醒中心**:
|
||||
- 新增 `vehicles.current_km` 字段(手动校准,NULL 时按各日志表 MAX 算)
|
||||
- `/api/reminders` 聚合提醒:加油 > 30 天、保养 > 180 天、洗车 > 14 天
|
||||
- `/api/reminders/prefs` GET/PUT 阈值(按用户)
|
||||
- **💰 成本分类占比**:`/api/stats/cost-breakdown` 返 5 分类(洗车/加油/充电/保养/保险)的总金额 + 百分比 + 颜色(用于饼图)
|
||||
- **🔍 顶栏全局搜索**:`/api/search?q=...` 跨 7 个领域(车辆/洗车/加油/充电/保养/保险/化学品)搜,返带高亮匹配字段的分组结果
|
||||
- **📊 同比/环比**:`/api/stats/compare` 返本月 vs 上月 / YTD vs 去年同月,5 个领域各给 `mom_pct` / `yoy_pct`
|
||||
- **🏷️ 标签系统**:`tags` + `record_tags` 两张表,CRUD + toggle 挂载,5 种 record_type(wash/refuel/charge/maintenance/insurance),可按 tag_id 找所有打了该标签的记录
|
||||
- **🔔 站内通知中心**:`notifications` 表 + 推送工具函数(`pushNotification`),OCR 完成 / 同步成功 / 备份完成等可持久化通知,GET/POST/标已读/全部标已读
|
||||
- **🏆 成就系统**:14 个预置成就(洗车新手→狂魔 / 一周一洗 / 万里征程 / 十万俱乐部 / 保险达人等),自动算 progress、解锁时持久化到 `user_achievements`
|
||||
|
||||
**Bug 修复**(Trae 引入的):
|
||||
|
||||
- 🐛 **MySQL pool 连接超时(生产 P0)**:`server/src/db.js` 的 mysql2 pool **没开 `enableKeepAlive`**。MySQL 默认 `wait_timeout=28800s` 会关掉 idle 连接,客户端不主动 ping → 下次 query 报 `ETIMEDOUT` → login / dashboard / 任何接口卡 60s 直到 axios timeout → 页面"转圈卡,什么也点不动"。**两重修复**:1) mysql2 pool 开 `enableKeepAlive:true` + `keepAliveInitialDelay:30000`(每 30s 发 ping 保持连接活);2) `queryWithRetry()` 包装 — 一次性 `ETIMEDOUT` / `ECONNRESET` / `PROTOCOL_CONNECTION_LOST` 自动 retry 一次(pool 会建新连接)。
|
||||
- 🐛 **4 个新路由 ok() helper 不包 `{ok,data}`**:`extra.js` / `achievements.js` / `tags.js` / `notifications.js` 全部用裸 `res.json(data)`,导致前端 axios interceptor 解包失败拿不到 `data`。统一改成 `res.json({ok:true, data})`。
|
||||
- 🐛 **`/api/stats/compare` 时区错 8 小时**:跟 v2.5 同款 bug(`getMonth()` 等本地方法),但 dateCol 存的是 UTC 字符串。改成 `getUTCMonth()` + `Date.UTC()` 构造。
|
||||
- 🐛 **`user_achievements.id` 没 SELECT 出来导致 UPDATE 失效**:achievements.js line 92 SELECT 没选 `id` 字段,后续 UPDATE 用 `existing.id` 是 undefined。加 `id` 到 SELECT 列表。
|
||||
- 🐛 **`lastInsertRowid` 是 BigInt 没 Number 转换**:tags.js / notifications.js 返 `r.lastID || r.lastInsertRowid`,mysql2 是 BigInt,JSON 序列化会变 `1n`。改成 `Number(r.lastInsertRowid)`。
|
||||
|
||||
**数据库**:3 个新迁移(0016_vehicle_current_km 加 `vehicles.current_km` 列 + `notification_prefs` + `notifications` 表,0017_tags `tags` + `record_tags` 表,0018_achievements `achievements` + `user_achievements` 表 + 14 条预置数据)
|
||||
|
||||
**测试**:
|
||||
|
||||
- 新增 `routes.extra.test.js`(7 个用例:reminders 包装 / 加油提醒 / cost-breakdown 5 分类 + 百分比 / compare 月环比同比)
|
||||
- 新增 `routes.tags.test.js`(8 个用例:CRUD / toggle 双向 / 非法 record_type 400 / 重名 409 / 级联删除)
|
||||
- 新增 `routes.notifications.test.js`(6 个用例:列表 + unread 计数 / 创建 / 标已读 / 全部已读)
|
||||
- 总测试数 **76 → 97 全过**
|
||||
|
||||
### v2.7(2026-06-20)
|
||||
|
||||
**新功能**:
|
||||
|
||||
- **401 自动跳登录 + 表单草稿**:`client/src/api/client.js` axios interceptor catch 401 → 触发 `form-draft:flush-all` 事件把所有 useFormDraft 暂存到 sessionStorage → 跳 `/login?reason=expired&redirect=原页`;登录成功后回原页,草稿自动恢复。
|
||||
- **AI OCR 失败兜底 modal**:`client/src/components/AiFallbackModal.vue` + `client/src/composables/useAiRecognize.js` 重构,识别失败时不再 alert 而是打开左图右表的 modal,用户对照图填表。
|
||||
- **IP 限流**:`server/src/middleware/ipRateLimit.js` 内存版限流,`/api/ai/*`(recognize + test)每分钟 10 次,`/api/chemicals/sync` + `/grocy-search` + `/refresh-ids` 每分钟 10 次;429 + Retry-After + X-RateLimit-* headers。
|
||||
- **CSRF 403 自动 refresh 重试**:客户端 interceptor 收到 403 CSRF → 调 `/api/auth/csrf` 拿新 token → 重发原请求 1 次(用 `_csrfRetried` flag 防双发)。
|
||||
- **健康检查拆分**:`/api/health`(兼容旧)+ `/api/health/live`(进程活着)+ `/api/health/ready`(DB `SELECT 1` 通过),k8s/宝塔监控能区分"我在启动"和"我坏了"。
|
||||
- **3 个真正有用的图表数据**:`GET /api/stats/extra` 返 `fuelTrend` / `costPerVehicle` / `washSeason`;Stats.vue 加油价趋势 + 每辆车年均养护 + 洗车季节频率 + 各车成本明细表。
|
||||
- **OpenAPI 文档**:`/api/docs` Swagger UI + `/api/openapi.json` schema,29 条核心路由有 JSDoc 注释。
|
||||
|
||||
**Bug 修复**(Trae 引入的):
|
||||
|
||||
- 🐛 **WashNew.vue modal 永远显示**:line 14 `:show="ai.showFallback.value"` 模板里用 ref 的 `.value` → 传的是 Ref 对象本身(永远 truthy),改成 `:show="ai.showFallback"` 让 Vue 模板自动解包。其他 view(ChargingList/InsuranceList/RefuelList/WashShow/MaintenanceList)都正确用 `const aiBusy = ai.busy` 别名,没踩这个坑。
|
||||
- 🐛 **Swagger 0 paths**:swagger.js 用相对路径 `'./src/routes/*.js'` 但 swaggerJsdoc 跑在进程 cwd,扫不到文件。改成 `path.resolve(__dirname, ...)` 绝对路径,0 → **29 routes**。
|
||||
- 🐛 **`/api/stats/extra` 缺包装**:settings.js 的 `ok()` helper 不包 `{ok,data}`,前端 axios interceptor 解包失败,Stats.vue 拿到的 `extraR.data` 是 undefined → fallback 到空数组 → 3 张图没数据。改成显式 `res.json({ok:true, data:{...}})`。
|
||||
- 🐛 **`/api/ai/test` 没加 rate limit**:Trae 只在 `/ai/recognize` 加了 aiRateLimit,但 `/ai/test` 没加,结果限流测试 12 次都通过。补上。
|
||||
- 🐛 **swagger JSDoc 缺失 + JSDoc 块复制粘贴出错**:Trae 加 swagger 时只注释了 6 处,我给所有 CRUD 路由补齐(共 29 个 paths)。中间出了个 JSDoc 块重复贴导致的语法错误,已修。
|
||||
|
||||
**测试**:
|
||||
|
||||
- 新增 `middleware.ipRateLimit.test.js`(7 个用例:第一次/第二次 headers/max 边界/不同 IP/XFF 优先/窗口过期/_clearBuckets)
|
||||
- 新增 `routes.stats.test.js`(4 个用例:包装/油价字段/车辆成本/季节聚合)
|
||||
- 总测试数 **64 → 76 全过**
|
||||
|
||||
### v2.6(2026-06-19)
|
||||
|
||||
### v2.5(2026-06-19)
|
||||
|
||||
**Bug 修复**:
|
||||
|
||||
- **🐛 登录锁定时区错 8 小时**:`server/src/services/rateLimit.js` 的 `nowIso()` 和 `upsertLock()` 用 `getHours()` 等本地方法生成 DATETIME 字符串,但 `db.js` 配的是 `timezone: 'Z'`(UTC)。在 UTC+8 时区下,5 次输错密码后会被锁 **8 小时而不是 30 分钟**,「locked_until」会显示「明天早上 6:50」而不是「今晚 23:05」。已改成 `getUTCHours()` 等 UTC 方法。**这是严重 bug,所有部署在国内服务器的用户都受影响。**
|
||||
- **🐛 月份列表跨时区少 1 个月**:`server/src/routes/settings.js:624` `new Date(year, month, 1)` 是本地午夜,`.toISOString()` 转 UTC 时可能跨月(尤其在月底)。改成 `Date.UTC()` 构造。
|
||||
|
||||
### v2.4(2026-06-19)
|
||||
|
||||
新增 + 修复:
|
||||
|
||||
- **AI 测试连接智能选图**:`POST /api/ai/test` 不再用源码里内嵌的 1×1 PNG(MiniMax 内容审查会判敏感),改成动态从 `uploads/ai/` 里挑最新的真实图片(>500B);没有就提示用户先上传。避免误报 422。
|
||||
- **OCR 端到端 e2e 已跑通**:上传加油小票 → MiniMax M3 多模态识别 → JSON 填表 → 写入数据库。
|
||||
|
||||
### v2.3(2026-06-19)
|
||||
|
||||
- **登录失败锁定提示**:输错密码现在会显示「已错 N 次 / 还剩 N 次 / 锁定 X 分」;5 次错后锁定 30 分钟,会显示「锁定至时间, 还剩 N 分 N 秒」。后端 `BAD_CREDENTIALS` / `LOCKED` 都返详细字段。
|
||||
- **车辆健康仪表盘**(`/api/vehicles/:id/health`):油耗、电耗、保养预测、洗车新鲜度、6 月趋势图(chart.js 堆叠柱)。
|
||||
- **洗车前后对比照**(`/api/washes/:id/photos`):multer 上传 + 4 路由 + 3 tab UI(gallery / compare / upload)。
|
||||
- **月度报表**(`/api/reports/monthly`):ExcelJS 6 sheet + PDFKit 2.3KB,覆盖车辆 / 洗车 / 加油 / 充电 / 保养 / 保险。
|
||||
- **Migrations 0015_wash_photos**:新增 `wash_photos` 表 + before/after 字段。
|
||||
|
||||
### v2.2(2026-06-19)
|
||||
|
||||
- **MiniMax M3 多模态接入**:Settings → AI 截图识别加 provider 下拉(`openai_compat` / `minimax_vl`)。MiniMax M3 走 OpenAI 兼容协议 `/chat/completions`,域名 `api.minimaxi.com`;OCR 任务关 `thinking: {type:'disabled'}` 防 JSON 污染。
|
||||
- **5 类 OCR schema**:洗车 / 加油 / 充电 / 保养 / 保险,从截图提取字段直接填表。
|
||||
|
||||
### v2.1(2026-06-17)
|
||||
|
||||
- 15 个 MySQL migrations + 完整幂等 `carlog-init.sql`(存储过程 + try-catch 包装 DDL / INDEX / ALTER)。
|
||||
- 化学品 / 加油 / 充电 / 保养 / 保险 5 大模块 CRUD。
|
||||
- 宝塔面板部署文档。
|
||||
|
||||
## 🧰 常用命令
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
# 数据库迁移
|
||||
cd server && node src/bin/migrate.js
|
||||
|
||||
# 清空所有数据(不可恢复!要 confirm_token)
|
||||
node src/bin/reset-all.js --confirm
|
||||
|
||||
# 灌种子数据
|
||||
node src/bin/reset-all.js --seed
|
||||
|
||||
# 导出全部为 JSON
|
||||
node src/bin/export.js --format json --output ./backup.json
|
||||
|
||||
# 导出单表为 CSV
|
||||
node src/bin/export.js --format csv --table wash_records --output ./washes.csv
|
||||
|
||||
# 备份(SQLite 拷贝 / MySQL mysqldump)
|
||||
node src/bin/backup.js
|
||||
|
||||
# Grocy 拉取(CLI)
|
||||
node src/bin/grocy-refresh-products.js
|
||||
|
||||
# 验证账号可登录
|
||||
node src/bin/verify.js
|
||||
npm test
|
||||
```
|
||||
|
||||
## 🔐 安全注意事项
|
||||
当前 101 个测试全过。覆盖 CSRF / 登录锁定 / IP 限流 / CRUD / 标签 / 通知 / 提醒 / 同比 / stats 等。
|
||||
|
||||
- **生产环境必改 `SESSION_SECRET`**
|
||||
- 如启用 HTTPS,在 `app.locals.config.session.cookie_secure` 设 `true`
|
||||
- Grocy 密码用 session cookie 缓存 24h;也可用 API Key(推荐,永久不过期)
|
||||
- 登录失败有 IP/账号双维度限流(默认 5 次/IP,10 次/账号)
|
||||
- 软删除的记录保留在 DB,可通过 `/api/operation-logs` 恢复
|
||||
## 一些坑(避免后人踩)
|
||||
|
||||
## 🌐 Grocy 配置
|
||||
- DB 用 `mysql2 timezone: 'Z'`,所有 server 端 DATETIME 字符串必须用 `getUTCHours()` 等 UTC 方法,**别用本地时区**。否则锁定时间会错 8 小时。
|
||||
- Pinia setup store 的 ref 在 store proxy 上自动解包,外部调用用 `store.ref = x`,**别写 `store.ref.value = x`**。
|
||||
- 1×1 透明 PNG(68 字节)会被 MiniMax 内容审查判敏感,OCR 测试图必须用真实上传图。
|
||||
- mysql2 pool 默认不开 `enableKeepAlive`,idle 连接会被 MySQL `wait_timeout` 杀掉,下次 query 报 ETIMEDOUT。已开 keepAlive + 自动 retry。
|
||||
|
||||
支持两种鉴权方式:
|
||||
## 仓库
|
||||
|
||||
| 方式 | 设置 | 说明 |
|
||||
|------|------|------|
|
||||
| Session Cookie | `GROCY_URL` + `GROCY_USERNAME` + `GROCY_PASSWORD` | 走 `POST /login` 拿 cookie,缓存 24h |
|
||||
| API Key | `GROCY_URL` + `GROCY_API_KEY` | `GROCY-API-KEY` header,永久 |
|
||||
https://gitea.img2img.com/wsh5485/CarLog
|
||||
|
||||
如果都不配:化学品列表能看但不能同步和扣减,其它模块全部正常工作。
|
||||
|
||||
## 📝 字段命名约定
|
||||
|
||||
- 车辆:`type`(car/suv/mpv/truck/other)、`powertrain`(ice/hev/bev)、`plate`、`color`
|
||||
- 洗车:`wash_type`(quick/full/detail/other)、`wash_date`、`cost`、`location`、`duration_min`
|
||||
- 加油:`refuel_date`、`liters`、`price_per_liter`、`total_cost`、`fuel_type`、`is_full`、`station`
|
||||
- 充电:`charge_date`、`kwh`、`price_per_kwh`、`total_cost`、`charge_type`、`start_soc`、`end_soc`
|
||||
- 保养:`maint_date`、`odometer_km`、`total_cost`、`shop`、`items[]`(动态项目)
|
||||
- 保险:`insurance_type`(compulsory/commercial)、`company`、`policy_no`、`start_date`、`end_date`、`premium`
|
||||
|
||||
## 📱 移动端 & PWA
|
||||
|
||||
- **响应式布局**:4 档断点(`480 / 768 / 1024 / 1440`),手机 / iPad / 桌面三端自适应
|
||||
- **导航**:手机端汉堡按钮 + 右滑抽屉导航(核心 / 能耗 / 其他三分组)
|
||||
- **列表页**:桌面端表格 → 手机端自动切换卡片堆叠(`<MobileCardList>` 通用组件)
|
||||
- **表单 / 弹窗**:移动端单列布局 + 底部弹出 Sheet + sticky 操作栏
|
||||
- **iOS safe-area**:全面屏适配(`env(safe-area-inset-*)`)
|
||||
- **PWA**:
|
||||
- 安装 `vite-plugin-pwa`,workbox 自动生成 Service Worker
|
||||
- `manifest.webmanifest` + 192/512/maskable/apple-touch 全套图标
|
||||
- 离线访问已缓存页面,导航降级到 `/offline`
|
||||
- 启动屏(`apple-touch-startup-image` 由浏览器自动截屏处理)
|
||||
- 3 个快捷方式:新建洗车 / 加油 / 保养
|
||||
- 4 种运行时 toast(`PwaToasts.vue`):新版本可用 / 离线就绪 / Android&桌面安装引导 / iOS Safari 添加到主屏幕
|
||||
|
||||
### 移动端使用方式
|
||||
1. iOS Safari:底部分享按钮 ⬆ → 添加到主屏幕(首次进入会有 toast 提示,session 内只弹一次)
|
||||
2. Android Chrome:底栏会出现"安装 CarLog"toast,点"安装"即可
|
||||
3. 桌面 Chrome:地址栏右侧 ➕ 安装 CarLog
|
||||
4. 新版本发布后下拉刷新(或访问站点)会触发"新版本可用"toast,点"刷新"即可更新
|
||||
|
||||
### Lighthouse 评分(最新一次运行,桌面端)
|
||||
| 指标 | 分数 |
|
||||
|---|---|
|
||||
| Performance | 99 |
|
||||
| Accessibility | 87 |
|
||||
| Best Practices | 95 |
|
||||
| SEO | 91 |
|
||||
|
||||
报告 HTML 在 `.lighthouseci/lhr-*.html`,重新跑分:`npm run lighthouse`
|
||||
|
||||
### PWA 检查
|
||||
`npm run lighthouse:pwa` 自动验证 11 项 PWA installability(manifest / icons 192+512+maskable+apple-touch / SW 注册 / theme-color / meta 标签 / 离线 fallback)。**Lighthouse 12 已弃用 PWA 类别**,本项目用 puppeteer 自检代替,结果稳定可重复。
|
||||
|
||||
## 🔌 API 速查
|
||||
|
||||
> 完整路径以 `/api` 为前缀,所有写操作需 `requireAuth` 中间件。返回 `{ ok, data, error }` 三段式 JSON。
|
||||
|
||||
| 模块 | 方法 | 路径 | 说明 |
|
||||
|---|---|---|---|
|
||||
| 认证 | POST | `/auth/login` | 登录,返回 csrfToken + Set-Cookie |
|
||||
| 认证 | POST | `/auth/logout` | 登出 |
|
||||
| 认证 | GET | `/auth/me` | 当前用户 |
|
||||
| 认证 | POST | `/auth/change-password` | 改密码(CSRF) |
|
||||
| 车辆 | GET | `/vehicles` | 列表(支持 `?q=&page=&limit=`) |
|
||||
| 车辆 | POST | `/vehicles` | 新建(CSRF) |
|
||||
| 车辆 | PUT | `/vehicles/:id` | 更新(CSRF) |
|
||||
| 车辆 | DELETE | `/vehicles/:id` | 软删(CSRF) |
|
||||
| 车辆 | GET | `/vehicles/:id/stats` | 单车统计(总里程/能耗/花费) |
|
||||
| 洗车 | GET/POST/PUT/DELETE | `/washes[/:id]` | 同上 |
|
||||
| 洗车 | POST | `/washes/:id/photos` | 上传对比照(multipart) |
|
||||
| 加油 | GET/POST/PUT/DELETE | `/refuels[/:id]` | 油耗自动计算 |
|
||||
| 充电 | GET/POST/PUT/DELETE | `/chargings[/:id]` | 电耗自动计算 |
|
||||
| 保养 | GET/POST/PUT/DELETE | `/maintenances[/:id]` | 动态 items[] |
|
||||
| 保险 | GET/POST/PUT/DELETE | `/insurances[/:id]` | 附件上传 |
|
||||
| 保险 | GET | `/insurances/expiring?days=30` | 即将到期 |
|
||||
| 车品 | GET | `/chemicals` | 库存列表 |
|
||||
| 车品 | GET/POST/PUT/DELETE | `/chemicals/:id` | 详情 / 编辑 |
|
||||
| 车品 | GET | `/chemicals/categories` | Grocy 分类 |
|
||||
| 车品 | POST | `/chemicals/sync` | 拉取 Grocy 库存 |
|
||||
| 车品 | GET | `/chemicals/low-stock` | 低库存预警 |
|
||||
| 车品 | POST | `/chemicals/batch-purchase` | 批量采购(事务) |
|
||||
| 日志 | GET | `/operation-logs?page=&action=&user=` | 操作日志 |
|
||||
| 日志 | POST | `/operation-logs/:id/recover` | 一键恢复软删数据 |
|
||||
| AI | POST | `/ai/recognize` | 图片识别(multipart:field=file) |
|
||||
| 统计 | GET | `/stats/summary` | 总览 KPI |
|
||||
| 统计 | GET | `/stats/cost-by-type?from=&to=` | 按类型花费 |
|
||||
| 统计 | GET | `/stats/odometer` | 里程折线图 |
|
||||
| 统计 | GET | `/stats/efficiency?vehicle_id=` | 能耗 |
|
||||
| 导出 | GET | `/export/insurance.csv` | CSV 导出 |
|
||||
| 导出 | GET | `/export/insurance.pdf` | PDF 导出 |
|
||||
| 同步 | GET | `/sync/snapshot` | 全量 JSON 快照 |
|
||||
| 同步 | POST | `/sync/restore` | 还原(确认 token) |
|
||||
| 系统 | GET | `/health` | 健康检查 |
|
||||
|
||||
## 💡 建议增加的功能(按 ROI 排序)
|
||||
|
||||
### 🟢 高 ROI(detailer 日常痛点,必加)
|
||||
|
||||
1. **🚗 车辆里程表(odometer)录入 + 自动同步**
|
||||
现在加油 / 充电 / 保养 / 洗车都只能手动填里程或留空。加一个 `vehicles.current_km` 字段,每次加油时自动建议上次里程 + 这次的差值(典型 400-600km/周)。**直接解决油耗计算准确度问题**(现在是基于「相邻 is_full=1」算,没数据就 fallback 0)。
|
||||
|
||||
2. **⛽ 加油提醒 / 保养提醒推送**
|
||||
现在保险有到期提醒(30 天),但加油 / 保养只能靠用户自己记得。加一个「提醒中心」页:
|
||||
- 距上次加油 > 30 天
|
||||
- 距上次保养 > 6 个月 / 5000km
|
||||
- 距上次洗车 > 14 天(你喜欢保持的车身干净程度)
|
||||
可以「标记已处理」或 snooze。**每天打开应用就能看到该干啥**。
|
||||
|
||||
3. **💰 成本分类分析**
|
||||
按月 / 按年 / 按车显示:洗车 : 加油 : 充电 : 保养 : 保险 的成本占比饼图。**能看出钱花在哪**,决定要不要砍掉某些开销(比如发现保险比加油贵)。
|
||||
|
||||
4. **🔍 全局搜索(v2.0+ 已经在做但没暴露 UI)**
|
||||
`/api/chemicals/grocy-search` 只搜化学品。做一个顶栏 search box,能跨所有领域搜(车牌 / 商家名 / 保养项目 / 保单号),点击跳详情。**找历史记录速度 × 10**。
|
||||
|
||||
### 🟡 中等 ROI(增强体验,做不做都行)
|
||||
|
||||
5. **📸 拍照快捷录入(手机 PWA)**
|
||||
现在 OCR 要先点「AI 识别」按钮 → 选图 → 识别。手机原生 PWA 加「桌面快捷方式」直接打开「拍照录入」页面,扫一眼小票自动填表。**洗车店门口就能记**。
|
||||
|
||||
6. **🏷️ 标签系统**
|
||||
给洗车 / 加油记录打标签:#自驾游 #通勤 #雨季 #精洗 #打蜡。一年后查「#打蜡 多少次」能判断打蜡频率。**纯前端 + 简单 SQL 即可**。
|
||||
|
||||
7. **📊 同比 / 环比对比**
|
||||
Stats 页加:「本月 vs 上月」「今年 vs 去年同月」自动算增减百分比。**一眼看出趋势变化**。
|
||||
|
||||
8. **🔔 系统通知中心**
|
||||
应用内通知(不是 push,是站内消息):OCR 完成 / 同步失败 / 备份成功 / 新版本可用。比 toast 更持久,用户能回看。
|
||||
|
||||
### 🟢 长期 / 玩法(看你个人兴趣)
|
||||
|
||||
9. **🏆 成就系统**(纯前端)
|
||||
- 「连续 30 天洗车」「1 年累计 100 次洗车」「单次最贵的精洗」
|
||||
- 解锁后给个 badge 分享到社交媒体(生成图片卡片)
|
||||
- **detailer 圈子的「show off」属性**
|
||||
|
||||
10. **🛣️ 路线 / 行程记录**
|
||||
加 `trips` 表:起点 / 终点 / 里程 / 油耗 / 备注。
|
||||
- 加油时关联 trip_id,自动算 trip 油耗
|
||||
- 月底看「本月去过哪些地方」
|
||||
- **自驾游爱好者会很喜欢**
|
||||
|
||||
11. **📦 备份到 OSS / 七牛 / 阿里云盘(自动化)**
|
||||
现在 `bin/backup.js` 只能本地。加 cron + 上传到对象存储:
|
||||
- `bin/backup-upload.js` 调 S3 兼容 API
|
||||
- 宝塔 cron `0 3 * * *` 每天凌晨跑
|
||||
- 保留 30 天滚动
|
||||
**你的数据安全网,必须做**
|
||||
|
||||
12. **🔍 OCR 文本预览 + 高亮**
|
||||
现在 OCR 完直接填表,看不到识别到的原始文本。加一个「识别原始」tab:
|
||||
- 显示 AI 返的 raw 文本
|
||||
- 高亮置信度低的字段(让用户重点核对这些)
|
||||
- **提升用户对 OCR 结果的信任**
|
||||
|
||||
13. **🌐 i18n 多语言**
|
||||
现在 hardcode 中文。如果要跟车友分享 / 出国玩可能需要 EN。vue-i18n 一加就够。
|
||||
|
||||
### 🔴 别做(性价比低)
|
||||
|
||||
- **多用户 / 多租户**:你一个人玩,加这个复杂度 × 10,价值 = 0
|
||||
- **TS 重构**:除非你强迫症,JSDoc 已经覆盖了
|
||||
- **复杂 BI / 自定义仪表盘**:你 8 辆车 + 5 年数据,PostgreSQL + Superset 都嫌重
|
||||
- **区块链溯源 / NFT 车辆档案**:开玩笑的 😂
|
||||
|
||||
### ❓ 想问你几个开放问题
|
||||
|
||||
1. **里程表录入**:你加油时会主动记里程吗?(很多人靠加油站小票 + 全程导航算)
|
||||
2. **保养预测**:你 4S 店 / 修理厂会提前打电话提醒,还是你 app 自己管?
|
||||
3. **跟车友分享**:你想不想把数据导出发给车友 / 二手车买家看?
|
||||
4. **车机 / OBD 集成**:你车有 OBD 接口吗?要不要读实时数据?
|
||||
|
||||
回答这几个我能给你更精准的优先级建议。
|
||||
|
||||
---
|
||||
|
||||
## 📋 License
|
||||
## License
|
||||
|
||||
MIT
|
||||
Reference in New Issue
Block a user