# 里程管理模块设计 ## 背景 在模块化重构的基础上,实现里程管理 BI 模块,1:1 复刻原型 `/Users/kkfluous/Projects/ai-coding/ln-yuanxing/lnoneos-1` 中的里程管理部分。包含 3 个子 Tab:实时监控、统计报表、每日汇报(占位)。 ## 目标 - 1:1 复刻原型 UI(样式、动画、交互细节完全一致) - 接入真实数据源(两个数据库) - 每日汇报 Tab 暂做占位 ## 数据源 ### 数据库 1:lingniu_prod(已有连接) - `tab_mileage_assessment_target` — 5 个考核项目定义(目标名称、车辆数、年考核里程、考核年限等) - `tab_mileage_assessment_vehicle` — 492 辆考核车辆(今日里程、累计里程、完成率、达标状态等) - `tab_truck` → `tab_truck_status_info` → `tab_contract` → `tab_customer` / `tab_user` → `tab_department` — 车辆关联客户名、部门、经理 ### 数据库 2:hydrogen_energy(新增连接) - 连接信息:`101.133.130.65:3306`,用户 `bi_reader_02`,密码 `bi_reader_02_Pass`,库名 `hydrogen_energy` - `v_vehicle_daily_stats` — 1004 辆车的每日里程明细(plate, vin, stat_date, daily_km, total_km, day_hydrogen, daily_run_secs, source) ## 架构 ### 后端 新增 `src/server/mileage-db.ts` — hydrogen_energy 数据库连接池。 新增 `src/server/routes/mileage.ts` — 里程管理 API 路由。 修改 `src/server/index.ts` — 注册新路由 `/api/mileage`。 ### 前端 ``` src/modules/mileage/ ├── MileageModule.tsx # 主组件:3个子Tab切换(实时监控/统计报表/每日汇报) ├── MonitoringView.tsx # 实时监控视图 ├── StatisticsView.tsx # 统计报表视图 ├── DailyReportView.tsx # 每日汇报(占位) ├── api.ts # API 客户端 └── types.ts # 类型定义 ``` ### 数据流 ``` 前端 MileageModule → fetch /api/mileage/* ↓ 后端 mileage.ts 路由 ├── lingniu_prod 池:考核目标/车辆、车辆关联信息(客户/部门/经理) └── hydrogen_energy 池:v_vehicle_daily_stats(日里程/趋势) ↓ 内存合并 前端渲染(Recharts 图表 + 列表) ``` ## API 端点 ### `GET /api/mileage/monitoring` 实时监控数据:全部 1004 辆车的今日里程 + 关联信息。 **查询逻辑:** 1. 从 `v_vehicle_daily_stats` 取最新日期的所有车辆数据(plate, daily_km, total_km, source) 2. 从 `lingniu_prod` 取车辆关联信息(客户名、部门、经理),使用现有的 `MAIN_SQL` 关联链 3. 内存按 plate 合并 **返回:** ```ts { vehicles: Array<{ plate: string; vin: string; dailyKm: number; totalKm: number | null; source: string; // TBOX / G7S / NONE isOnline: boolean; // source !== 'NONE' && dailyKm > 0 isDataSynced: boolean; // source !== 'NONE' customer: string | null; department: string | null; manager: string | null; }>; updatedAt: string; } ``` ### `GET /api/mileage/targets` 考核项目列表 + 每个项目的汇总统计。 **查询逻辑:** 1. 从 `tab_mileage_assessment_target` 取全部未删除项目 2. 从 `tab_mileage_assessment_vehicle` 按 target_id 聚合统计 **返回:** ```ts Array<{ id: number; targetName: string; vehicleCount: number; totalMileagePerVehicle: number; annualMileagePerVehicle: number; assessmentYears: number; period: string; // "YYYY-MM-DD ~ YYYY-MM-DD" todayTotal: number; // SUM(today_mileage) cumulativeTotal: number; // SUM(current_mileage) avgCompletion: number; // AVG(completion_rate) * 100 qualifiedCount: number; // SUM(is_qualified) yearQualifiedCount: number; // SUM(current_year_is_qualified) halfQualifiedCount: number; // completion_rate >= 0.5 的车辆数 currentYearTarget: number; // SUM(current_year_mileage_task) currentYearCompleted: number; // SUM(current_year_mileage) remaining: number; // currentYearTarget - currentYearCompleted daysLeft: number; // current_year_assessment_end_date - today dailyTarget: number; // remaining / daysLeft }> ``` ### `GET /api/mileage/target/:id/vehicles` 某考核项目的车辆明细列表。 **查询逻辑:** 从 `tab_mileage_assessment_vehicle` WHERE target_id = :id AND is_deleted = 0 **返回:** ```ts Array<{ plateNumber: string; todayMileage: number; totalMileage: number; completionRate: number; isQualified: boolean; currentYearIsQualified: boolean; dailyRequiredMileage: number; }> ``` ### `GET /api/mileage/trend?targetId=...&days=7` 7天里程趋势,按考核项目筛选。 **查询逻辑:** 1. 若有 targetId:从 `tab_mileage_assessment_vehicle` 取该项目的所有 plate_number 2. 从 `v_vehicle_daily_stats` WHERE plate IN (...) AND stat_date >= (today - days) GROUP BY stat_date **返回:** ```ts Array<{ date: string; // "MM-DD" mileage: number; // SUM(daily_km) }> ``` ## 前端组件设计 ### MileageModule.tsx 主组件,管理子 Tab 切换(monitoring / statistics / report),包含: - 子导航栏(实时监控/统计报表/每日汇报),带 motion layoutId 动画下划线 - 条件渲染对应 View 组件 ### MonitoringView.tsx 1:1 复刻原型实时监控视图。 **状态:** - activeSubTab 由父组件管理 - searchTerm, filterDept, filterPlate, filterProject, filterEntity, filterRegionCode, filterYear, filterDate, filterDateRange, filterMileageRange - sortBy ('today' | 'total'), sortOrder ('asc' | 'desc') - isFilterOpen, isFullscreen **UI 结构:** 1. 看板头部(标题 + 全屏按钮 + 排序切换) 2. 快捷筛选栏(3 个 SearchableSelect + 高级筛选图标) 3. 可展开高级筛选面板 4. KPI 卡片网格(4列:总里程深色卡、平均单车、监控台数) 5. 车辆详情清单(motion.div 列表) 6. 全屏叠加层(AnimatePresence) **SearchableSelect 组件:** 在 MonitoringView 内部定义(原型中的实现与公共 SearchSelect 不同,它使用 motion 动画、"无限制"默认选项、不同样式)。 ### StatisticsView.tsx 1:1 复刻原型统计报表视图。 **状态:** - selectedProject, chartType ('bar' | 'line' | 'area') - isTableFullscreen, expandedModel, viewAllModel, viewAllSearch, viewAllSort **UI 结构:** 1. 项目选择器(横向滚动按钮组) 2. 左侧:7天趋势图(Recharts BarChart/LineChart/AreaChart 切换)+ landscape KPI 卡片 3. 右侧:车型考核里程汇总卡片列表(可展开详情 + 车辆明细前5台) 4. 全屏表格叠加层(15列明细表) 5. 查看全部侧滑面板(搜索 + 排序 + 车辆列表) ### DailyReportView.tsx 占位组件,显示"每日汇报 - 开发中"。 ## 数据映射 ### 实时监控 | UI 字段 | 数据来源 | |---------|---------| | 车牌号 | `v_vehicle_daily_stats.plate` | | 今日里程 | `daily_km`(最新日期) | | 累计里程 | `total_km`(最近非空值,用用户提供的变量填充 SQL) | | 在线状态 | `source !== 'NONE' && daily_km > 0` | | 数据同步 | `source !== 'NONE'` | | 客户名 | `lingniu_prod`: tab_truck → tab_truck_status_info → tab_contract → tab_customer.customer_name | | 部门 | `lingniu_prod`: → tab_user → tab_department.dep_name | ### 统计报表 | UI 字段 | 数据来源 | |---------|---------| | 项目列表 | `tab_mileage_assessment_target`(target_name, vehicle_count 等) | | 今日总里程 | `SUM(tab_mileage_assessment_vehicle.today_mileage)` by target_id | | 累计总里程 | `SUM(current_mileage)` by target_id | | 平均完成率 | `AVG(completion_rate) * 100` by target_id | | 达标车辆数 | `SUM(current_year_is_qualified)` by target_id | | 50%达标数 | `COUNT(completion_rate >= 0.5)` by target_id | | 考核区间 | `default_start_date ~ default_end_date` | | 年考核任务/辆 | `annual_mileage_per_vehicle` | | 本年需完成 | `SUM(current_year_mileage_task)` | | 已完成 | `SUM(current_year_mileage)` | | 未完成总数 | 本年需完成 - 已完成 | | 剩余天数 | `current_year_assessment_end_date - today`(取 vehicle 中的值) | | 日均需完成 | 未完成 / 剩余天数 | | 7天趋势 | `v_vehicle_daily_stats` 按项目车牌过滤聚合 | | 车辆明细 | `tab_mileage_assessment_vehicle` 的 plate_number, today_mileage, total_mileage 等 | ## 不在范围内 - 每日汇报 Tab 具体实现(占位) - landscape 适配(原型中有 landscape: 前缀样式,照搬即可但不做额外适配工作) - 后端缓存 - 新增依赖