Merge feature/asset-statistics into main and release v1.2.1
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
shishengliang
2026-09-23 14:28:26 +08:00
116 changed files with 18694 additions and 4137 deletions
+41
View File
@@ -0,0 +1,41 @@
# 资产 AI 问数 Demo
入口:本地运行 `npm run dev` 后打开 `http://localhost:3000/asset#assets`,点击「AI 问数」。
## 已实现
- 采用 Beautiful UI Thinking / Prompt Bar / Chat 的视觉语言;Thinking 步骤改为后端 SSE 实际查询进度,保留 MIT 许可于 `docs/licenses/beautiful-ui.txt`。
- DeepSeek Flash 将自然语言和上一轮查询计划转成受校验的结构化查询;后端复用资产仓储、权限过滤和业务分类;第二次模型调用只解释汇总数据。
- 查询当前资产数量、库存、可运营/待运营、运营率、异动和待交车;按区域、城市、车型、型号、部门和运营状态分组。
- 查询最多 93 天的交车、还车、替换趋势。交车包含替换;流转按业务车次计数,不以车辆去重。
- 图表/统计表/明细切换、分组下钻、明细搜索及分页、CSV 导出、连续追问、取消、错误重试、新对话、深浅主题及移动端。
- 浏览器 SpeechRecognition 语音输入:用户点击麦克风启动,转写后可编辑再发送。不支持或拒绝授权时提示文字输入。
## 本机配置
`DEEPSEEK_API_KEY` 仅位于 Git 忽略的 `.env`,后端读取。客户端构建不包含密钥。无需配置 `VITE_` 密钥。更换临时密钥后重启后端。
现有开发环境本身已开启本地免登录;本次未改变该开关。问答路由挂在应用的认证中间件之后,未认证直接调用被拒绝。部署环境必须沿用正常认证配置。
模型接收问题、维度目录(区域/城市/车型/型号)、已校验的上一轮计划和汇总统计;不会主动发送车牌、VIN、客户或车辆明细。用户在提问中自行输入的文字会发送给模型。业务数据没有伪造或演示替换。
Portainer / Docker 部署见 [DeepSeek 环境变量注入](./portainer-deepseek.md)。
## 口径和边界
- 当前公司从资产页面继承,变更公司请关闭面板后选择公司;服务端执行账号权限与公司范围过滤。
- 资产来源为当前台账与现有服务端缓存,结果标记查询时间,不声称是历史库存快照。
- 分组与明细使用同一次查询返回的数据;区域按现有省市映射。
- 运营率 = 当前筛选车辆中的运营车辆 / 当前筛选车辆数。
- 历史库存、金额、成本、预测等暂不支持,助手应说明能力范围或追问,不执行模型生成的 SQL。
- 会话只保存在打开的面板内,刷新/关闭不会保存。语音依赖浏览器和其在线识别服务,尚未接专用 ASR。
## 验证
- TypeScript 检查、Vite 生产构建;构建仍有现有 ECharts/AG Grid 大块提示。
- 自动验证个人权限与所属公司范围、库存子集计数、无认证拒绝、非法查询/日期拒绝,以及模型请求不包含车辆明细。
- 桌面及手机浏览器实际查询:库存 291 台 → 追问可运营库存 181 台;华东库存 198 条明细与分页对应。
- 2026-09-01 至 2026-09-23 流转:交车 63 车次、还车 69 车次,其中替换 19 车次;2026-09-22 下钻及 CSV 均为 17 条记录。
- 深浅模式、无横向页面溢出、空明细搜索和结果展示切换已验证。真实麦克风识别未自动录制测试。
开发大脑回执:DB-C-003,知识索引版本 1 校验通过,未发现冲突;本次是产品 Demo 实现,未形成新的跨任务复盘模式,无知识写入或 GitHub 同步。
+43
View File
@@ -0,0 +1,43 @@
# 资产管理组件库迁移
## 范围与约束
资产管理的总览、库存统计、车型/批次、交还车、部门、区域、客户、筛选与明细弹窗。
- shadcn/ui:官方 CLI 生成的 Radix Nova 组件,中文化关闭按钮;使用组件默认尺寸、语义主题与 Tailwind 布局工具类。
- Apache ECharts:趋势、区域对比和省份分布,按需加载;ResizeObserver 适配容器,卸载时销毁实例。
- AG Grid Community:排序、列筛选、列宽调整、固定识别列和分页。未使用 Enterprise、授权密钥、企业分组或 Excel 导出接口。
- H5:同一数据/列定义驱动 shadcn 卡片与分页;关键数量、异动类型/时间、待交车合同/脱敏客户直接可见,辅助信息按需展开。手机不加载桌面 Grid 模块。
- Excel 导出沿用现有 xlsx,按需加载。
- 移除旧资产展示组件及 assets.css/apple-tokens.css;没有新增资产自定义选择器。视觉变量直接采用 apple-design 技能提供的 `src/shared/styles/apple-tokens.css`,组件空间、颜色与暗色适配使用 shadcn/ui 属性和 Tailwind 工具类。其他 BI 模块未迁移。
资产总览采用靛蓝标题区、浅色分组底、蓝色运营卡与靛蓝库存卡,并缩小卡片高度以提高信息密度。操作控件集中在标题下方,主视图与库存子视图选中态更清晰。资产页提供浅色/深色切换并记住当前浏览器选择;图表坐标、图例及明细表格颜色跟随主题。
## 数据口径
总运营 = 租赁 + 自营;异动、待交车属于总库存子集,不重复累计。
交还车日期统计为车次;车型/批次的本周交还车沿用后端按车辆去重统计。
省份分布使用既有公司范围接口,页面明确标示不受表格筛选影响。
待交车客户来自后端脱敏字段,合同仅使用当前待交车任务,不回退历史合同。
修正库存型号误用为合同批次条件、周明细漏传公司条件、区域统计与明细范围不同,以及区域“其他车型”误排除挂车的问题。区域城市折叠列表保留实际城市,不再把第九个及后续城市合并成无法准确下钻的“其他”。新增离线接口回归覆盖。
## 验证
- TypeScript 检查通过。
- 全仓 108 项测试通过(包含新的公司/区域下钻测试),无数据库依赖的新增测试使用 repository stub。
- 生产构建通过。AG Grid 与 ECharts 独立懒加载包仍超过 Vite 的 500 kB 提示阈值;未屏蔽该提示。
- Web:总览、库存下钻 55 → 55、部门、客户脱敏/省份图、公司切换、区域“其他”下钻 6 → 6、车型入口、日期筛选。
- 390 px H5:页面无横向溢出;车辆弹窗宽 358 px;搜索结果 1 条及分页、辅助信息折叠、日期区间应用可用。
- 浏览器验收未发现 console error/warn。
- 验收时异动、待交车均为 0,只完成其空状态验证;有记录的业务数据展示仍需后续验收。
迁移初次验收基于 1.1.16;本次随 1.1.17 版本提交交付,完整回归测试共 130 项通过。
## 本轮可读性与交互调整
- 恢复深蓝标题、亮蓝操作、橙色异动提示,降低背景饱和度;库存数字使用深色,和运营数字区分。
- 所有共享筛选面板默认折叠,已选条件在折叠标题显示,清空后回到未设条件状态。所属公司作为全局数据范围保留可见。
- Web 与 H5 的记录数不超过 20 条时不分页,桌面表格高度适应数据;超过 20 条仍分页,避免大量车辆渲染。8 条以内不显示无必要的搜索框。
- 手机标题与总数并排,主导航和统计页签触控高度至少 44 px。
- 浏览器验证:部门 8 条完整展示且分页隐藏;766 条车辆仍分页;区域筛选后折叠摘要和库存计数一致。
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Shane Levine
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,198 @@
# OneOS 日里程与累计里程差额排查报告
- 排查日期:2026-09-16(Asia/Shanghai)
- 涉及数据日期:2026-09-11 至 2026-09-15
- 接收方:OneOS 开放平台及里程计算服务维护人员
- 状态:已复现;待上游核查计算基准、跨日缺报及协议选源逻辑
## 1. 问题概述
BI 用户发现:昨日累计总里程减去前日累计总里程,不等于昨日的区间里程;继续向前检查,多日均存在差额。
本次在相同车辆范围、相同协议优先级下复现,并绕过 BI 直接请求 OneOS 单日接口核对异常车辆。上游返回本身就存在相邻日期累计差与日里程不一致的情况,不能仅用 BI 显示取整解释。
已确认的现象:
1. 某查询日返回的累计里程对应更早日期的报文;后续更新后的累计差跨越多天,与当日日里程不一致。
2. 同一车辆相邻日期切换 MQTT / GB32960,累计读数发生跳变,且切换后可能选中很早的读数。
尚未确认:日里程的内部计算公式、有效数据判定、跨日补算规则及异常过滤规则。本文不将差额直接认定为漏算里程,也不将累计差直接认定为真实日行驶里程。
## 2. 查询范围与 BI 计算口径
本次 BI 查询未附加客户、部门、车牌或里程筛选条件,每天返回 916 台车辆。
```text
GET /api/mileage/monitoring
?startDate=2026-09-15
&endDate=2026-09-15
&sourcePriority=instrument
&limit=2000
```
`instrument` 对应上游协议优先级:
```json
["GB32960", "MQTT"]
```
BI 当前计算方式:
| 指标 | 计算方式 |
| --- | --- |
| 单车累计里程 | OneOS `totalMileageKm`,不以日里程反推 |
| 单车单日里程 | OneOS `dailyMileageKm` |
| 单车区间里程 | 区间内每日 `dailyMileageKm` 求和 |
| 累计总里程 | 查询范围内车辆的累计里程求和;空值按 0 参与汇总 |
| 当日总里程 | 查询范围内车辆的日里程求和;汇总接口对结果取整 |
以下对账表使用逐车日里程原始数值求和,保留三位小数,避免整数显示带来的误差。
## 3. 汇总对账结果
单位:km。定义 `差额 = 当天累计总里程 - 前一天累计总里程 - 当天日里程合计`。
| 日期 | 累计总里程 | 相邻日累计差 | 当日日里程合计 | 差额 |
| --- | ---: | ---: | ---: | ---: |
| 2026-09-11 | 48,448,486.800 | — | 98,182.208 | — |
| 2026-09-12 | 48,546,128.300 | 97,641.500 | 97,321.774 | +319.726 |
| 2026-09-13 | 48,619,784.800 | 73,656.500 | 82,322.847 | −8,666.347 |
| 2026-09-14 | 48,721,190.600 | 101,405.800 | 94,242.687 | +7,163.113 |
| 2026-09-15 | 48,824,917.800 | 103,727.200 | 103,554.604 | +172.596 |
按车牌匹配相邻日期,差额来源分解如下:
| 日期 | 两日累计非空且协议相同的车辆数 / 净差额 | 两日累计非空且协议变化的车辆数 / 净差额 | 至少一日累计为空的车辆数 / 净差额 |
| --- | --- | --- | --- |
| 09-12 | 759 / +319.726 | 0 / 0 | 157 / 0 |
| 09-13 | 758 / +674.653 | 1 / −9,341.000 | 157 / 0 |
| 09-14 | 759 / +7,163.113 | 0 / 0 | 157 / 0 |
| 09-15 | 759 / +172.596 | 0 / 0 | 157 / 0 |
本次样本中,空累计值组对差额净贡献为 0;同协议数据也存在明显不一致,不能只修复协议切换问题。
## 4. 异常样本一:累计读数跨日,日里程未与累计差对齐
以下记录已通过 OneOS 单日接口直接复核,响应均为 HTTP 200、`code=SUCCESS`,记录状态均为 `NORMAL`。
### 4.1 粤AGQ8377:9 月 15 日差额 +70.2 km
| 查询日期 | sourceProtocol | totalMileageKm | dailyMileageKm | dataTime(+08:00) |
| --- | --- | ---: | ---: | --- |
| 2026-09-13 | GB32960 | 14,366.0 | 6.1 | 2026-09-13 07:17:27 |
| 2026-09-14 | GB32960 | 14,366.0 | 0 | 2026-09-13 07:17:27 |
| 2026-09-15 | GB32960 | 14,467.4 | 31.2 | 2026-09-15 20:57:04 |
```text
累计差 = 14,467.4 - 14,366.0 = 101.4
差额 = 101.4 - 31.2 = 70.2 km
```
查询 9 月 14 日时,累计读数仍来自 9 月 13 日。请核查 9 月 15 日日里程的起点读数及其时间,并解释 70.2 km 在日统计中的归属或过滤原因。
### 4.2 粤AGR6839:9 月 15 日差额 +38.0 km
| 查询日期 | sourceProtocol | totalMileageKm | dailyMileageKm | dataTime(+08:00) |
| --- | --- | ---: | ---: | --- |
| 2026-09-14 | GB32960 | 45,122.2 | 0 | 2026-09-11 11:10:53 |
| 2026-09-15 | GB32960 | 45,162.8 | 2.6 | 2026-09-15 10:34:02 |
累计差为 40.6 km,日里程为 2.6 km,差额为 38.0 km。请核查跨多日无新读数后的统计基准及补算策略。
### 4.3 粤AGP3609:9 月 14 日差额 +808.7 km
| 查询日期 | sourceProtocol | totalMileageKm | dailyMileageKm | dataTime(+08:00) |
| --- | --- | ---: | ---: | --- |
| 2026-09-12 | GB32960 | 48,102.9 | 1,293.6 | 2026-09-12 15:08:47 |
| 2026-09-13 | GB32960 | 48,102.9 | 0 | 2026-09-12 15:08:47 |
| 2026-09-14 | GB32960 | 48,981.7 | 70.1 | 2026-09-14 23:59:51 |
| 2026-09-15 | GB32960 | 50,349.1 | 1,367.4 | 2026-09-15 23:59:50 |
9 月 14 日累计差为 878.8 km,日里程为 70.1 km,差额为 808.7 km;9 月 15 日累计差与日里程则同为 1,367.4 km。该车适合对比正常日期与跨日缺报日期的计算路径。
## 5. 异常样本二:协议切换导致累计值倒退
车辆:沪A60591F。以下记录也已直接请求上游复核,均返回 `NORMAL`。
| 查询日期 | sourceProtocol | totalMileageKm | dailyMileageKm | dataTime(+08:00) |
| --- | --- | ---: | ---: | --- |
| 2026-09-12 | MQTT | 51,416 | 0 | 2026-09-12 04:53:49 |
| 2026-09-13 | GB32960 | 42,075 | 0 | 2026-07-16 23:59:59 |
| 2026-09-14 | GB32960 | 42,075 | 0 | 2026-07-16 23:59:59 |
| 2026-09-15 | GB32960 | 42,075 | 0 | 2026-07-16 23:59:59 |
```text
9 月 13 日累计差 = 42,075 - 51,416 = -9,341 km
9 月 13 日日里程 = 0 km
```
该车单独贡献 −9,341 km,叠加其他同协议车辆 +674.653 km 的差额后,形成当日全量净差额 −8,666.347 km。
请重点核查:
1. 同一请求优先级下,9 月 12 日采用 MQTT,9 月 13 日为何改用 7 月 16 日的 GB32960 读数?
2. 协议“有效数据”的定义是否包含数据时间、新鲜度和查询日期边界?
3. `NORMAL` 是否仅表示读数数值有效,而不表示查询当日存在有效报文?
4. 两协议累计读数是否具有同一基准?切换时是否应提供不连续标识?
5. 请核查该车协议记录对应的 VIN、设备及绑定历史,排除历史绑定变化造成的读数混用。
## 6. 上游直接复现方法
接口:`POST https://open.d.lnoneos.com/api/v1/vehicles/mileage/query`。
使用已授权的 API Key,通过环境变量注入,不将真实凭证写入报告或日志。
```bash
curl --fail-with-body --silent --show-error \
'https://open.d.lnoneos.com/api/v1/vehicles/mileage/query' \
-H "Authorization: Bearer ${ONEOS_MILEAGE_API_KEY}" \
-H 'Content-Type: application/json' \
--data '{
"date": "2026-09-15",
"plateNumbers": ["粤AGQ8377", "粤AGR6839", "粤AGP3609", "沪A60591F"],
"protocolPriority": ["GB32960", "MQTT"]
}'
```
依次将 `date` 替换为 `2026-09-12`、`2026-09-13`、`2026-09-14`、`2026-09-15`。
逐车对比字段:`date`、`plateNumber`、`vin`、`status`、`dailyMileageKm`、`totalMileageKm`、`sourceProtocol`、`dataTime`、`calculatedAt`、`updatedAt`。
本次直接复核保留了报告中的关键字段,未记录响应 `traceId`;上游重放时请保存完整响应及 `traceId`。历史数据如已重算或补报,当前返回值可能与本报告不同,请同时提供重算时间和修改前后结果。
## 7. 请上游提供的定位结果
### 7.1 明确字段契约
- `dailyMileageKm`:是当天首尾读数差、前日末值到当日末值的差,还是逐报文有效增量之和?是否丢弃跨日增量、异常跳变或负增量?
- `totalMileageKm`:是查询日最后读数、截至查询日最近读数,还是其他快照?无当日报文时是否延用历史值?
- `dataTime`:具体对应累计读数、日里程终点还是其他时间?
- 两个里程字段是否保证来自同一协议、同一设备、同一计算快照?
- 自然日边界是否统一为 Asia/Shanghai 的 00:00:00 至次日 00:00:00?
- 是否承诺相邻日累计差等于日里程;若不承诺,请明确哪些场景不成立及差额如何解释。
### 7.2 提供样本计算明细
对上述四辆车,请返回:
1. 各协议参与选源的候选记录、时间及未选中原因。
2. 日里程计算起止报文的时间、累计读数、设备/VIN 标识。
3. 有效增量、被过滤增量及过滤原因。
4. 缺报、补报、跨日增量归属,以及历史重算记录。
5. 对每个差额给出可核对的数值分解,不能只解释为“统计口径不同”。
## 8. 修复或口径确认后的验收建议
1. 使用同一车辆集合和协议配置,重放 9 月 11 至 15 日数据,逐车核对后再核对汇总。
2. 对连续上报且同协议的正常车辆,若契约约定可对账,应满足累计差与日里程一致;容差按上游实际数值精度明确。
3. 对跨日缺报车辆,明确跨日增量是否补算、归属哪天,并能解释全部差额。
4. 对协议切换车辆,避免将不同基准的累计值视为连续可减的序列;提供明确的协议切换和不连续信息。
5. 对陈旧读数,区分“历史累计值仍有效”和“当日有有效报文”,避免用单一 `NORMAL` 混淆两者。
6. 验证单日接口与区间接口对同一车、同一天的日里程、累计值和选源结果一致。
7. 如修复涉及历史重算,提供重算范围及完成时间;BI 历史接口缓存最长 6 小时,应在刷新后复核。
## 9. 本次排查边界
- 已检查 BI 里程映射、日/区间聚合和汇总逻辑,并对异常样本直接调用上游单日接口复核。
- 尚未访问 OneOS 原始报文、计算服务源码或内部存储,不能确定其内部算法根因。
- 本次未修改业务代码、未变更里程口径、未触发历史数据重算。
+38
View File
@@ -0,0 +1,38 @@
# Portainer 部署资产 AI 问数
DeepSeek Key 由 Node 服务端在运行时读取,变量名为 `DEEPSEEK_API_KEY`。模型固定为 `deepseek-flash`,接口为 `https://api.deepseek.com/chat/completions`,部署节点需要能够访问该 HTTPS 地址。
## Stack 配置
仓库的 `docker-compose.yml` 已包含:
```yaml
services:
ln-bi:
environment:
DEEPSEEK_API_KEY: "${DEEPSEEK_API_KEY:-}"
```
1. 等待 main 分支的 Woodpecker 构建与镜像推送成功。本次版本 1.2.1 对应镜像 `harbor.lnh2e.com/lingniu-v1/ln-bi:main-1.2.1`。
2. Portainer → Stacks → 现有 `ln-bi` Stack。确认 Compose 的 `image` 使用上述镜像,且保留现有数据库、认证、网络和端口配置。
3. 在 **Environment variables** 中新增名称 `DEEPSEEK_API_KEY`,值粘贴实际 API Key。Compose 必须包含上面的变量引用,才能将 Stack 变量传入容器。
4. 点击 **Update the stack** 并重新部署,确保拉取新镜像。Git 管理的 Stack 需要从仓库拉取新版 Compose;Web Editor 管理的 Stack 则直接更新编辑器内容。
5. 登录应用,在资产管理中打开「AI 问数」,查询“当前库存按区域统计”验证。
后续更换 Key 只需更新 Stack 环境变量并重新部署/创建容器,无需重新构建镜像。仅重启旧容器不会刷新其环境变量。未配置 Key 时 AI 问数返回配置提示,其他资产功能可继续使用。
## 检查是否注入
在 Portainer 中打开正在运行的应用容器 Console,执行:
```sh
node -e 'console.log(process.env.DEEPSEEK_API_KEY ? "已配置" : "未配置")'
```
该命令只检查是否存在,不打印 Key;实际有效性由问数请求验证。Swarm 部署请进入对应任务容器检查。
Key 不使用 `VITE_` 前缀,不写入前端代码、Dockerfile、构建参数或 Git。`.env` 与本机日志已排除出 Git 和 Docker 构建上下文。现有应用端口保持 8111,无需为 DeepSeek 新增监听端口。
仓库 Compose 采用显式 `environment` 引用,可用于 Docker Standalone 与 Swarm;不依赖仅 Standalone 支持的 `env_file: stack.env`。
参考:[Portainer 创建 Stack 与环境变量](https://docs.portainer.io/sts/user/docker/stacks/add)、[更新现有 Stack](https://docs.portainer.io/user/docker/stacks/edit)。
+141
View File
@@ -0,0 +1,141 @@
# LN-BI UI 优化待办清单
> 生成日期:2026-09-20
> 来源:全局外壳/导航、数据看板与图表、业务操作页面三路 UI/UX 审查汇总
> 用法:`- [ ]` 为未完成,`- [x]` 为已完成;每条附 `文件:行号` 证据,完成后建议连同代码一起提交。
> 关联文档:`../LN-BI-项目分析报告.md` §11.4、`../LN-BI-重构方案.md` 阶段 5/6
---
## P0 可用性与数据信任(最优先)
### 可访问性:下拉组件
- [ ] SearchSelect 键盘/读屏不可用:补 `role="combobox"` + `aria-expanded` + listbox/option 语义、↑↓/Enter/Escape,或直接替换 Headless UI / Radix Combobox — `src/components/SearchSelect.tsx:47-68`
- [ ] MultiSearchSelect 同上,且焦点环颜色与单选版不一致(emerald vs blue)— `src/components/MultiSearchSelect.tsx:59-85`
- [ ] mileage SearchableSelect 选项为裸 div,键盘不可达 — `src/modules/mileage/monitoring/components/SearchableSelect.tsx:60-72`
- [ ] 排序表头补 `aria-sort`;三张 div 模拟表格中期换成真 `<table>` — `src/modules/mileage/daily-report/VehicleTable.tsx:220-271`、`GroupRow.tsx:86-132`、`ElectricDaily.tsx:124-186`、`HydrogenDaily.tsx:192-272`
### 可访问性:弹窗/全屏
- [ ] 抽共用 Modal/BottomSheet 壳:统一 `role="dialog" aria-modal`、Escape 关闭、遮罩点击关闭、焦点圈禁与还原、滚动锁定、移动端安全区 — 参考 `src/components/feedback/FeedbackModal.tsx:59-66`、`FeedbackHistoryDrawer.tsx:67-83`
- [ ] 全屏监控/全屏表格补 aria-label + Escape 关闭 + 打开时焦点移到关闭按钮 — `src/modules/mileage/monitoring/components/FullscreenMonitoring.tsx:111-119`、`src/modules/mileage/statistics/FullscreenTargetTable.tsx:49-60`
- [ ] 抽 `useScrollLock` hook,统一锁定 body 滚动(VehicleDetailModal/FlowDetailModal/NotificationHistory 目前未锁)— `src/modules/assets/components/VehicleDetailModal.tsx:47`、`FlowDetailModal.tsx:33`、`src/modules/scheduling/NotificationHistory.tsx:122`
- [ ] 移动端底栏分组弹层:修溢出屏幕(定位约束)、加遮罩点击关闭、活动项补 `aria-current` — `src/components/Shell.tsx:194-206`
- [ ] 全部图标按钮补 `aria-label`/`title`(监控工具栏等)— `src/modules/mileage/monitoring/components/MonitoringToolbar.tsx:88-93`
- [ ] 移动端卡片用 span 冒充可点击元素:改 `<button type="button">` + 稳定业务 key — `src/modules/assets/components/overview/AssetSummaryMobileCards.tsx:60-63`、`InventoryMobileList.tsx:50,93`
### 数据可信
- [ ] 全屏监控筛选口径与普通视图静默不一致:全屏复用同一 `monitoringQuery`,或在全屏头部明示"以下筛选未生效:项目/主体/里程区间" — `src/modules/mileage/monitoring/query-model.ts:92-96`、`MonitoringView.tsx:108-110`
- [ ] 热力图刷新期间旧数据无标识:请求期间地图区加半透明遮罩 + spinner,或 KPI/热力层 `opacity-50` 过渡(两个热力图文件同步改)— `src/modules/vehicle-heatmap/VehicleHeatmapModule.tsx:73-92,143-147`、`src/modules/hydrogen-heatmap/HydrogenHeatmapModule.tsx`
- [ ] 洞察卡片"月度动能"对进行中当月做按天折算或加"本月未完整"标注;正收益改用 TrendingUp/CheckCircle 图标 — `src/modules/energy/hydrogen-overview/model.ts:64-66`、`components/InsightCards.tsx:77-79`
- [ ] 区分"无数据"与"数据未就绪"两种空态文案;未就绪时隐藏 KPI 行 — `src/modules/energy/ElectricDaily.tsx:62,101-119`、`HydrogenDaily.tsx:91-109`
### 写操作防错与反馈
- [ ] 「登记干预」加二次确认弹层(复用 BatchConfirmModal 底部弹模式,展示 原车牌→候选车牌+客户)— `src/modules/scheduling/SwapPreview.tsx:33-40,155-163`
- [ ] 批量干预成功后保持 `resp.message` 可见(弹窗延迟关闭或页面 Toast),失败才留在弹窗内 — `src/modules/scheduling/SchedulingModule.tsx:142-144`、`scheduling-module/BatchConfirmModal.tsx:59-61`
- [ ] 替换全部原生 alert/confirm 为统一 Toast/内联错误 — `src/modules/scheduling/SwapPreview.tsx:44-47`、`NotificationHistory.tsx:109-110`
### 上传/导入工作流
- [ ] UploadDropzone 前置校验扩展名/大小并内联报错;uploading 期间禁用点击/拖入防并发 — `src/modules/ele/import-page/UploadDropzone.tsx:42-58`、`EleImportPage.tsx:77-95`
- [ ] 大文件上传给进度反馈(XMLHttpRequest 进度),成功后状态改"✓ 已导入,点击重新上传" — `src/modules/ele/EleImportPage.tsx:77-95`
- [ ] 导入记录表分页或"加载更多",修 total 与可见 50 条脱节;搜索改防抖即时生效或显式搜索按钮;输入框加清除按钮 — `src/modules/ele/EleImportPage.tsx:58`、`import-page/RecordFilters.tsx:30`
- [ ] 空态区分"无记录"与"筛选无结果" — `src/modules/ele/import-page/RecordTable.tsx:46`
---
## P1 明显体验缺陷
### 加载/刷新范式统一(目标:氢能总览为模板)
- [ ] 日报已有数据时改顶部进度条 + 内容淡化(去掉全屏遮罩),取消或缩短 280ms 强制延迟;全屏遮罩仅留首次加载 — `src/modules/mileage/DailyReportView.tsx:24,134-136`、`MileageLoadingOverlay.tsx:13`
- [ ] 统计报表补骨架屏 + ErrorState + 重试,不再静默吞错 — `src/modules/mileage/StatisticsView.tsx:39-100`
- [ ] 电能/氢能"每日"页补首屏骨架屏 — `src/modules/energy/ElectricDaily.tsx:131-132`、`HydrogenDaily.tsx:208-209`
- [ ] 电能总览 LoadingState 单卡片升级为骨架屏 — `src/modules/energy/ElectricOverview.tsx:31`
### 布局错位与遮挡
- [ ] 监控 KPI 吸顶条按断点修正 sticky 偏移(导航 62px + 顶栏 48px),375px 与 1440px 实机滚动验证 — `src/modules/mileage/monitoring/components/MonitoringSummary.tsx:27`、`src/modules/mileage/MileageModule.tsx:33`
- [ ] 面包屑遮罩与页面内容重叠:`<main>` 加 `md:pt-12` 或 PageFrame 桌面顶距提至 ≥56px — `src/components/Shell.tsx:159-164`、`src/components/ui/surface.tsx:34`
- [ ] 面包屑纳入子 tab(如 `羚牛氢能 BI / 里程管理 / 统计报表`),结构化 breadcrumb 标记;移动端补子 tab 位置指示 — `src/components/Shell.tsx:160-163`、`src/modules/energy/useHashSubTab.ts:30-35`
### 移动端专项
- [ ] 热力图移动端图例不直接隐藏:折叠为渐变条按钮,点击展开 — `src/modules/vehicle-heatmap/VehicleHeatmapModule.tsx:220`、`src/modules/hydrogen-heatmap/HydrogenHeatmapModule.tsx:206`
- [ ] VehicleDetailModal 客户视图 13 列在移动端改卡片列表(借鉴 FlowDetailModal 模式)— `src/modules/assets/components/VehicleDetailModal.tsx:194-236`
- [ ] 库存统计筛选弹层加遮罩点击关闭或改底部弹层 — `src/modules/assets/components/overview/InventoryStatisticsSection.tsx:85`
- [ ] 地图点击查询打临时 Marker 或二次确认,避免误点覆盖概览 — `src/modules/vehicle-heatmap/VehicleHeatmapModule.tsx:94-103`
- [ ] 热力图 ToolBar/Scale 避免与底部详情面板重叠(改左上或随面板状态调整)— `src/modules/vehicle-heatmap/AmapHeatmapCanvas.tsx:88-89`
### 视觉一致性
- [ ] 次级文字对比度整改:定义对比度达标的语义色 token(slate-500 起步),全站替换 9–12px 的 `text-slate-400/300` 关键文字 — `src/components/ui/surface.tsx:137,181,269`、`src/components/Shell.tsx:122,154,186`
- [ ] 全站字号下限 10–11px,清掉 7–8px — `src/modules/mileage/monitoring/components/VehicleList.tsx:77-126`、`FullscreenMonitoring.tsx:159-318`、`MonitoringSummary.tsx:30-44`
- [ ] SearchSelect/MultiSearchSelect 统一到 slate 系 + `rounded-xl` + 统一 focus ring,尺寸默认值移入组件内部 — `src/components/SearchSelect.tsx:34,48`、`MultiSearchSelect.tsx:46`
- [ ] 资产域表格数字列统一右对齐 + `tabular-nums`,表头随列对齐 — `src/modules/assets/components/overview/AssetSummaryDesktopTable.tsx:22-34,87-192`、`InventoryDesktopTable.tsx:36,76`(对齐标准:`src/modules/ele/import-page/BatchTable.tsx:27-47`)
- [ ] 分组大表表头 sticky + 容器右缘渐变阴影提示可横滑 — `src/modules/assets/components/overview/AssetSummaryDesktopTable.tsx:16-17`
### 筛选/标签交互
- [ ] ActiveFilterTags 默认日期区间不进"已选条件"标签(区间降级为统计周期信息条)— `src/modules/mileage/monitoring/components/ActiveFilterTags.tsx:90-97`、`MonitoringView.tsx:68-69`
- [ ] `searchTerm` 死筛选:工具栏补搜索框,或删除该状态 — `src/modules/mileage/MonitoringView.tsx:43`、`query-model.ts:44`
- [ ] 筛选统一即时生效(防抖),删除 filterMileageRange/appliedMileageRange 双状态 — `src/modules/mileage/monitoring/components/FilterPanel.tsx:259-267`、`MonitoringView.tsx:64-65`
- [ ] 筛选 tag 清除热区扩至 24px+,tag 整片可点清除 — `src/modules/scheduling/scheduling-module/ActiveFilterTags.tsx:15-20`
- [ ] 多选模式补"全选当前筛选结果";不可选项加 `title="已干预,不可重复登记"` — `src/modules/scheduling/SuggestionList.tsx:85-103`
- [ ] 监控工具栏快速区间补"近7天" — `src/modules/mileage/monitoring/components/MonitoringToolbar.tsx:149-155`
### 小 Bug 集群(改动极小,顺手修)
- [ ] TargetDetailPanel:徽标硬编码"在线"改随 `isOnline`;ChevronDown 条件旋转;非法类名 `bg-slate-50/50/50` — `src/modules/mileage/statistics/TargetDetailPanel.tsx:90-95,213,216-218`
- [ ] 图表车型长名截断加 `title` 悬浮完整名,复用 shortTargetName 缩写规则 — `src/modules/mileage/daily-report/ReportCharts.tsx:111`、`GroupRow.tsx:94`
- [ ] 热力图默认日期兜底改为"近 30 天"由当前日期推导 — `src/modules/vehicle-heatmap/VehicleHeatmapModule.tsx:9-10`、`src/modules/hydrogen-heatmap/HydrogenHeatmapModule.tsx:9-10`
- [ ] 堆叠柱 radius 接缝缺口 — `src/modules/mileage/daily-report/ReportCharts.tsx:114-115`
- [ ] ReportDatePicker 补点击外部/Esc 关闭;日期加业务下界 — `src/modules/mileage/daily-report/ReportDatePicker.tsx:56-110`、`ReportHeader.tsx:45-53`
- [ ] FullscreenTargetTable 行 key 改业务值、补空态、刷新按钮转动反馈 — `src/modules/mileage/statistics/FullscreenTargetTable.tsx:49-54,86`
- [ ] 电能每日表月份分组行环比列留空问题;氢能每日图 Y 轴 `k` 与 Kg 混淆 — `src/modules/energy/ElectricDaily.tsx:124-153`、`HydrogenDaily.tsx:155`
- [ ] forceLandscape 监听 resize/orientationchange — `src/modules/mileage/MonitoringView.tsx:298-308`
- [ ] ReportInsights key 撞车改 index 或 id — `src/modules/mileage/daily-report/ReportInsights.tsx:26`
- [ ] DailyRangeControls 去掉 onChange+onInput 重复触发、加 max 限制未来日期 — `src/modules/energy/daily-range/DailyRangeControls.tsx:56-72`
- [ ] ETC 占位页里程碑与 meta 文案矛盾统一 — `src/modules/energy/EtcModule.tsx:12`、`ETCView.tsx:50`
- [ ] FeedbackHistoryDrawer 死 prop `onBack` 移除;截图链接补 `aria-label`;状态文案"已忽略"改"暂不处理" — `src/components/FeedbackHistoryDrawer.tsx:44,135-136`、`FeedbackFab.tsx:169`
- [ ] MultiSearchSelect 标签移除按钮补 `aria-label` — `src/components/MultiSearchSelect.tsx:40`
- [ ] SearchSelect 容器 onClick 与 input onFocus 双触发误关问题;清理不可达的 placeholder 分支 — `src/components/SearchSelect.tsx:35,40,45`
- [ ] FeedbackAdminPage 反馈列表 limit=200 静默截断:加分页或溢出说明;tab 计数改后端聚合 — `src/modules/admin/FeedbackAdminPage.tsx:79,124-171`
- [ ] VehicleDetailModal 周明细表补空态行;footer 计数口径区分"本周流转 n 台/共 n 台" — `src/modules/assets/components/VehicleDetailModal.tsx:158-178,267`
- [ ] BatchTable 补空态占位;上传成功后最新批次高亮或加"新"徽标 — `src/modules/ele/import-page/BatchTable.tsx:12`
- [ ] FlowDateRangePicker 超出 370 天日期直接置灰,而非事后报错 — `src/modules/assets/components/overview/FlowDateRangePicker.tsx:18-19,110`
- [ ] NotificationHistory 增加相对时间(x 小时/天前);待执行超 48h 加"滞留"警示徽标;"近7天"过滤改后端参数化 — `src/modules/scheduling/NotificationHistory.tsx:67-72,222-228`
- [ ] SuggestionDetail 当前车摘要 sticky;保留滚动条或滚动阴影 — `src/modules/scheduling/SuggestionDetail.tsx:145-169`
- [ ] FeedbackFab 改走统一 api-client(401 触发全局登出)— `src/components/FeedbackFab.tsx:16-25`(同类:`EleImportPage.tsx:23-33`、`FeedbackAdminPage.tsx:52-53`)
- [ ] 全局补 `:focus-visible` 兜底样式 — 目前仅 4 处模块组件有
- [ ] SegmentedNav 的 `layoutId` 加 `useId()` 前缀防同页撞车 — `src/components/ui/surface.tsx:202-243`
---
## P2 体系化改进(对应重构方案阶段 5/6)
- [ ] **设计令牌化**:`index.css` 用 Tailwind 4 `@theme` 定义语义 token(`--color-ink-secondary`、`--shadow-card`、`--shadow-nav`、`--color-surface-bg`),替换散落 arbitrary value;删除无人引用的 `--panel-bg`/`--hairline` — `src/index.css:3-7`、`src/components/ui/surface.tsx:41`、`src/App.tsx:49`、`src/components/Shell.tsx:103`
- [ ] **统一格式化层**:`fmtKm` 三套实现合并到 shared;TrendBadge 环比小数位统一 — `src/modules/mileage/daily-report/utils.ts:54`、`src/modules/mileage/statistics/model.ts:48`、`src/modules/energy/TrendBadge.tsx:21`
- [ ] **统一"今天"定义与日期区间预设**:日报按上海自然日 / 监控按本地 05:00 / 电能按本地自然日,收敛为一套 — `src/modules/mileage/daily-report/utils.ts:13-24`、`monitoring/date-range.ts:11-15`、`energy/daily-range/model.ts:22-39`
- [ ] **配置化硬编码**:高里程告警规则(写死车型名+800km)挪后端或配置常量 — `src/modules/mileage/MonitoringView.tsx:36-40`;站点横幅硬编码 — `src/modules/energy/ElectricOverview.tsx:50-52`
- [ ] **z-index 分层约定**:定义 `z-modal`/`z-overlay` 等 token,替换 `z-[1000]`/`z-[80]`/`z-[60]` 随意值 — `src/modules/assets/components/FlowDetailModal.tsx:33`、`VehicleDetailModal.tsx:47`、`src/modules/scheduling/SwapPreview.tsx:58`、`NotificationHistory.tsx:122,262`
- [ ] **错误呈现统一**:表单内联错误用统一 `FormError` 组件 + `role="alert"`,对齐 ErrorState — `src/components/feedback/FeedbackFormStep.tsx:145`、`FeedbackHistoryDrawer.tsx:107`
- [ ] **深色模式显式决策**:短期写进设计约定;中期按 令牌化 → Shell/surface → 图表 palette → 地图底图 顺序评估
- [ ] **导航扩容策略**:一级模块超过 5 个时收口为"更多"抽屉;分组首点直达第一个子模块 — `src/components/Shell.tsx:117,173,183`
- [ ] **PageFrame 拆分**:`hideHeader`/`compactInfo` 布尔分叉拆成 PageHeader 与 CompactHeader 两个组合 — `src/components/ui/surface.tsx:46-110`
- [ ] **死代码清理**:移除 69 处 no-op `<Blur>` 包裹与 `DemoModeProvider enabled={false}` — `src/components/Blur.tsx:13-16`、`Shell.tsx:93`
- [ ] **杂项**:`cn.ts` 换 clsx + tailwind-merge — `src/lib/cn.ts`;RotatingFooterHint keyframes 移入 index.css — `src/components/RotatingFooterHint.tsx:43-48`;UnauthorizedPage 英文 eyebrow 中文化 — `src/auth/UnauthorizedPage.tsx:10`;简化 index.css 双层滚动容器 — `src/index.css:9-26`;相对时间 `relativeNow` 5s 整表重渲染优化 — `src/modules/mileage/MonitoringView.tsx:131-134`;Top5 榜 Y 轴刻度与 width 解耦 — `src/modules/energy/hydrogen-overview/components/DistributionCharts.tsx:31-36,62`;饼图 >9 区域颜色循环失真 — 同文件 :15-19,109;水印时间戳随会话刷新 — `src/components/Shell.tsx:86-90`
---
## 已确认的正面实践(保持,勿回退)
- 氢能总览:骨架屏 + 非打断 RefreshOverlay(全站推广模板)
- VehicleTable 可点行:role/tabIndex/aria-pressed/键盘支持、吸顶首列 + 右侧阴影
- GroupRow 的 aria-expanded;日报日期写回 URL 可刷新恢复
- BatchTable 数字右对齐 + tabular-nums(资产域对齐目标)
- FlowDetailModal 移动端卡片模式(VehicleDetailModal 降级参考)
- 动效克制(layoutId 弹簧/FadeIn/shimmer 均为 transform/opacity 级)
---
## 建议实施顺序
| 阶段 | 内容 |
|---|---|
| 第 1 周 | P0 写操作反馈 / 上传工作流 / 数据可信 / 小 Bug 集群 |
| 第 2 周 | P0 可访问性(Modal 壳 + useScrollLock + 下拉组件)+ 加载范式统一 + 布局错位 |
| 第 3 周 | P1 移动端专项 + 视觉一致性 |
| 持续 | P2 令牌化 → 格式化/日期层 → 配置化(重构方案阶段 5/6) |