覆盖架构、API 端点、前端组件、数据映射, 1:1 复刻原型的实时监控和统计报表。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
243 lines
8.5 KiB
Markdown
243 lines
8.5 KiB
Markdown
# 里程管理模块设计
|
||
|
||
## 背景
|
||
|
||
在模块化重构的基础上,实现里程管理 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: 前缀样式,照搬即可但不做额外适配工作)
|
||
- 后端缓存
|
||
- 新增依赖
|