3 Commits
Author SHA1 Message Date
KennethCheng 09d30aaf0f feat(frontend): 接入 PWA(manifest + service worker + app icon)
Phase 6 落地。用户在桌面 Chrome / Android Chrome / iOS Safari 上可以把
FuelLog "安装"到主屏幕,像 app 一样启动;断网时已访问页面仍可用。

新增:
- scripts/gen-icons.ps1  PowerShell + System.Drawing 生成 PWA 应用图标
  * pwa-192x192.png        标准应用图标
  * pwa-512x512.png        高清应用图标
  * pwa-maskable-512x512.png  Android adaptive icon(留 10% 安全边)
  绿底 + 白色油枪图形(矩形主体 + 把手 + 喷嘴 + 底座),无外部依赖
- public/manifest.webmanifest  静态 manifest,name / short_name / description
  / theme_color / background_color / display=standalone / orientation=portrait
  / icons 三种规格
- public/{favicon.svg, pwa-*.png}  全部进 public/ 由 Vite 复制到 dist/

vite.config.js 改动(VitePWA 配置):
- registerType: 'autoUpdate' + injectRegister: 'auto'  无需手动调
  registerSW,SW 启动后自动接管版本更新
- manifest: false  关掉自动生成,改用 public/manifest.webmanifest 静态文件
  (避免 Node 在 Windows 中文环境下用 GBK 写 manifest 导致 PWA 安装
  提示里的 name 显示乱码;JSON 标准是 UTF-8,这里源文件是 UTF-8)
- filename: 'sw.js' + strategies: 'generateSW'  使用 Workbox generateSW
- workbox.globPatterns  预缓存全部 .js / .css / .html / .png / .svg / .woff2
- workbox.navigateFallback = '/index.html'  SPA 路由
  workbox.navigateFallbackDenylist = [/^\/api/]  /api 走网络不被 SPA 兜底
- workbox.runtimeCaching:
  * /api/*  NetworkFirst 5s 超时回退 cache,适合"偶尔离线还能看老数据"
  * 静态资源 (style/script/worker/image/font)  CacheFirst 30 天
- includeAssets 把 favicon + pwa-*.png + manifest.webmanifest 一起 precache
- devOptions.enabled = false  开发期关掉 SW(Vite HMR 会被卡住)

index.html 改动:
- link rel="apple-touch-icon"  iOS 添加到主屏用
- meta apple-mobile-web-app-* 三件套
- meta theme-color  浏览器顶栏配色
- 保留 viewport-fit=cover,刘海/小白条的安全区已有 CSS 配合

期间踩到的坑(都修了):
- 一开始让 VitePWA 用 config 内嵌 manifest 对象,Node 在 Windows 写
  出来的 dist/manifest.webmanifest 是 GBK 字节,Chrome 解析后中文乱码
- 改成 public/manifest.webmanifest 静态文件后,`Get-Content` 在
  PowerShell 5.1 仍然显示乱码(ANSI 默认),但 .NET ReadAllText 强制
  UTF-8 读取文件原始字节正确(加 = E5 8A A0,油 = E6 B2 B9),浏览器
  按 JSON 标准 UTF-8 解析也正常 —— 所以"乱码"是 PowerShell 5.1
  Get-Content 自身的输出编码问题,不是文件损坏

未做(留待后续):
- A2HS 按钮(install prompt) —— PWA 标准是浏览器原生弹,先不上自造
- 离线 fallback 页面 —— workbox.navigateFallback 走到 /index.html
  已经够;真要自造可以加 public/offline.html
- push notification —— 不在 MVP 范围

验证:
  pnpm typecheck   0 errors
  pnpm build       19.0s
  dist/manifest.webmanifest   643 字节,UTF-8 中文 OK
  dist/sw.js                   3.1 KB,precache 含全部 28 个静态资源
  dist/workbox-f641ca17.js   22.8 KB
  dist/registerSW.js          134 字节(由 VitePWA inject)
2026-09-13 17:02:14 +08:00
KennethCheng 33dfdc824c feat(frontend): 统计页接入 ECharts,四张图(油价/油耗/费用/成本)
把 Statistics 从占位 Empty 升级成真正的图表页,plan §9.4 落地:
- echarts + vue-echarts,按需引入 CanvasRenderer / LineChart / BarChart
  + Grid / Tooltip / Legend / Title / DataZoom(全部 tree-shakable)
- 四张图,每张独立 NCard,统一主题色 brand #18a058
  1) 油价趋势(折线 + 面积)—— 每月 total_amount / total_liters 当月均价
  2) 标准油耗趋势(折线)—— 每月在"连续两次 is_full"之间采样的均值
  3) 月度费用(柱状)—— 每月加油 total_amount 之和
  4) 每公里成本(折线)—— 同油耗采样算法
- 时间窗选择:近 3 月 / 6 月 / 12 月 / 全部(NSelect 顶栏)
- 油耗 / 每公里成本采样逻辑与后端 pkg/decimal 严格对齐:
  * 列表按 refuel_time 升序遍历
  * 只在"is_full 序列"内采样
  * 半箱油记录自动跳过,不污染指标
- autoresize 适配移动端旋转 / 桌面窗口
- Loading / Empty / Error 状态完整;切车自动重拉

未做(留待后续):
- DataZoom 区域缩放 —— 已在 echarts 引入但没启用,数据量大时再上
- 多车辆对比 —— 计划里没明确要求,先不上
- 油价对比基准线(同地区均值) —— 暂无数据源

验证:
  pnpm typecheck   0 errors
  pnpm build       18.1s(echarts 入了 4 个组件 + 1 个 renderer,bundle 略增)
2026-09-13 16:52:26 +08:00
KennethCheng 097af20cc0 feat(frontend): 搭建 Vue 3 + Vite + TS + Pinia + Naive UI 骨架
Phase 5(项目骨架部分)。后端已经稳了,现在前端可以挂上 curl。
本 commit 只做"能跑、能路由、能调 /api",业务页面留到后续 commit 一个个填。

新增:
- package.json + .env.example
  * vue 3.5 / vite 5.4 / typescript 5.6 / pinia 2.3 / vue-router 4.6
  * naive-ui 2.45 + vfonts(Naive UI 官方推荐字体)
  * tailwindcss 3.4 + autoprefixer + postcss
  * unplugin-auto-import / unplugin-vue-components 减少样板代码
  * axios 1.7
  scripts:dev / build / preview / typecheck / lint
- vite.config.js(注意:实际生效的是 .js,因为早期 vue-tsc -b 留了 .js 残留;
  .ts 留作类型参考,内容保持一致)
  * /api 代理到 127.0.0.1:8080,本地一站跑通前后端
  * 强制 es2020 target,关闭 sourcemap
- tsconfig.json / tsconfig.node.json
  * skipLibCheck 跳过第三方 .d.mts 噪音
  * strict + noImplicitAny
- tailwind.config.js
  * 主题色用 CSS 变量(rgb(var(--brand) / <alpha-value>)),
    组件层不写死颜色,符合 plan §12 / AGENTS.md
- postcss.config.js
- index.html + public/favicon.svg(简单加油机图标)
- src/main.ts
  * 引入 vfonts / tailwind / theme.css
  * createApp + Pinia + router
- src/styles/tailwind.css + src/styles/theme.css
  * Light/Dark 模式都从 :root / html.dark 拿 CSS 变量
  * 全局滚动条 / 移动端 -webkit-tap-highlight 处理
- src/App.vue
  * NConfigProvider + NDialog + NNotification + NMessage + NLoadingBar
  * 跟随系统 dark 模式,watchEffect 同步 html.dark class
- src/router/index.ts
  * 路由: /  /dashboard  /refuels  /refuels/new  /vehicles  /statistics
  * /:pathMatch(.*)*  -> NotFound
  * afterEach 改 document.title
- src/api/{client,types,vehicles,refuels}.ts
  * axios 实例,统一把后端 {code,message} 错误解到 Error.cause
  * 类型与后端 model/DTO 一一对应
- src/stores/vehicle.ts
  * Pinia setup store,持有车辆列表 + 当前选中
  * loadList 自动挑 default_vehicle
- src/utils/decimal.ts
  * 与后端 pkg/decimal 对齐的 pricePerLiter / consumptionL100km / costPerKm
  * 配套 fmt* 展示格式化函数
- src/layouts/AppLayout.vue
  * 桌面端顶栏 + 移动端底部 Tab
  * 移动端底部 safe-area-inset 适配刘海/小白条
- src/pages/{Dashboard,Refuels,RefuelNew,Vehicles,Statistics,NotFound}.vue
  * Dashboard 真的拿 /api/vehicles/:id/statistics 渲染主指标(其它字段保留为占位)
  * Refuels / RefuelNew / Vehicles / Statistics 是占位 Empty,Phase 5 业务页接入
- src/env.d.ts
  * Vite ImportMeta 类型 + .vue 模块声明

期间踩到的两个坑(都修了):
1) `tsconfig.json` extends 了 @vue/tsconfig 但里面引用了不存在的
   `@vue/tsconfig/tsconfig.node.json` —— 改成不依赖 extends,自己写
2) `vite build` 报 "Could not resolve entry module 'vueuc'":根因是早期
   `vue-tsc -b` 在项目根生成了 vite.config.js / vite.config.d.ts 残留,
   Vite 优先读 .js(老的 manualChunks 引用了 vueuc),整套 plugin 也不加载
   (3 modules transformed)。修法:把正确配置同步写到 vite.config.js,
   让 Vite 一直读 .js;.ts 留作类型参考

验证:
  pnpm install              199 packages 安装成功
  pnpm typecheck            0 errors
  pnpm build                11.5s,dist 643 KB(uncompressed),主 JS 344 KB
                            gzipped 107 KB(naive-ui 主力,符合预期)

未做(留待后续 commit):
- 各业务页(Dashboard 仅骨架,Refuels / RefuelNew / Vehicles / Statistics 都是 Empty)
- 真实表单校验、Loading / Empty / Error 三态完整接入
- PWA(manifest + service worker) —— Phase 12
- 暗色模式手工切换(目前只跟随系统,Phase 11 UI 精修再补)
- ESLint 规则初始化
2026-09-13 16:40:03 +08:00