> **视觉基线更新(2026-08-15)**:本提案落地时,图表与配色须遵循 `DESIGN.md` 的白皮书语言(全站浅色单主题),不得引入深色样式。 # 数据统计页 · 趋势可视化升级 设计提案 > 创建时间:2026-06-27 > 范围:仅 `components/admin/DataStatisticsTab.tsx` 的趋势展示区 + 必要的后端口径微调 > 原则:只做加法和体验升级,不删任何现有功能;不改数据库、不动业务逻辑 --- ## 1. 现状诊断(已通过代码摸底确认) ### 1.1 趋势区当前形态 ``` ┌─ 每日趋势(近31天)─────┐ ┌─ 月度趋势 ─────────────┐ │ 纯 表格 │ │ 纯
表格 │ │ 日期 | 上传数 │ │ 月份 | 上传数 │ │ 2026-06-01 | 12 │ │ 2026-01 | 320 │ │ 2026-06-02 | 8 │ │ 2026-02 | 410 │ │ ...(最多31行,可滚动) │ │ ...(可滚动) │ └──────────────────────────┘ └──────────────────────────┘ ``` **问题**: 1. **信息密度低**:纯表格,看不出趋势走向(涨/跌/峰值/低谷),需要肉眼逐行比对数字 2. **缺对比基准**:只看到绝对值,不知道"这个数是高还是低"——没有环比/同比/均值线 3. **P1-2 bug**:每日趋势标题写死"近31天",但数据实际是后端按筛选时间范围返回的日期桶,选"全部"时表格可能是几年前的31天,标题与数据口径不符 4. **交互弱**:不能hover看明细、不能缩放、点击日期不能联动其它图表 ### 1.2 数据基础(已确认) 后端 `lib/admin-material-stats.ts` 返回的趋势数据: ```ts dailyUploads: { bucket: '2026-06-01', count: number }[] // 按筛选范围内的天 monthlyUploads: { bucket: '2026-06', count: number }[] // 按筛选范围内的月 ``` **数据已经是干净的时序数组,无需后端改动即可做折线图**。唯一的口径问题是 `dailyUploads` 只返回"有数据的天"(稀疏),绘图时需要补零。 ### 1.3 技术约束 - 项目**无图表库**(recharts/d3/echarts 都没装),现有柱状图是手写 `div + height%` - 引入新库 = 增加 bundle 体积 + 构建依赖。需权衡"自己画 SVG"vs"引库" --- ## 2. 设计目标 1. **一眼看出趋势**:用折线/面积图替代表格,涨跌、峰值、低谷立刻可见 2. **提供对比基准**:加环比(vs 上一周期)、移动平均线,让数字有参照系 3. **修复 P1-2**:每日趋势标题与数据口径一致 4. **保持轻量**:不引入重量级图表库,自己用 SVG 画(与现有柱状图风格一致) 5. **可降级**:表格作为辅助视图保留(切换"图表/表格"),照顾想看精确数字的场景 --- ## 3. 提案:三档方案(成本递增,任选一档) ### 方案 A:最小改动(推荐先做) **成本:约 1 个前端组件 + 0 后端改动** 把"每日趋势"和"月度趋势"两个表格,各替换为**自绘 SVG 折线/面积图**,并保留一个"切换表格视图"的按钮。 ``` 每日趋势 [📈 图表] [📊 表格] ← 切换 ┌──────────────────────────────────────────────────────────────┐ │ ╱╲ ╱╲ │ │ ╱╳╯ ╲ ╱ ╲ ╱╲ ← 面积折线(sky 渐变填充) │ │ ╱ ╲__╱ ╲___╱ ╲___ │ │ ─────────── 均值线(虚线)───────────────── │ │ 06-01 06-10 06-20 06-30 ← x轴 │ │ 峰值 06-08 (42条) · 均值 18.3条/天 · 合计 549条 │ ← 统计摘要 └──────────────────────────────────────────────────────────────┘ hover 任意点 → tooltip 显示 "2026-06-08:42 条" ``` **新增信息**(表格完全没有的): - 📍 峰值日期标注(哪天最高) - 📍 均值参考线(虚线,一眼判断高低) - 📍 统计摘要条(峰值/均值/合计/环比%) - 📍 hover tooltip(精确值) **修复 P1-2**:标题动态显示真实范围,如"每日趋势(2026-06-01 ~ 2026-06-27,共27天)"。 ### 方案 B:中等改动(A + 同比对比) **成本:A + 前端补零逻辑 + 可能的后端口径微调** 在方案 A 基础上,给月度趋势加**同比折线**(今年 vs 去年同期): ``` 月度趋势(含同比) ┌────────────────────────────────────────────────┐ │ ─── 今年(实线) │ │ ┄┄┄ 去年同期(虚线) ← 需后端返回去年同期数据│ │ 1月 2月 3月 4月 5月 6月 │ │ 环比 +12.3% 同比 +5.1% │ └────────────────────────────────────────────────┘ ``` **后端改动**(可选):`getMaterialUploadStats` 增加 `previousMonthlyUploads`(取筛选范围向前平移一年的同长度区间)。不改表结构,只多一次聚合查询。 ### 方案 C:大改动(B + 交互联动) **成本:B + 图表点击联动 + 时间粒度切换** - 点击折线上某月 → 自动把上方"部门/素材类型分布"切换到该月范围 - 加"日/周/月"粒度切换器(当前只有日和月两张表) - 区域选择缩放(拖选一段时间放大看) --- ## 4. 推荐落地路径 **强烈建议先做方案 A**,理由: | 维度 | 方案 A | 方案 B | 方案 C | |---|---|---|---| | 视觉提升 | ★★★★ 大 | ★★★★★ 很大 | ★★★★★ 很大 | | 信息增量 | 中(均值/峰值/摘要) | 高(同比) | 很高(联动) | | 后端改动 | **0** | 小(多一次聚合) | 中 | | 新代码量 | ~250 行(1个SVG组件) | +100 行 | +300 行 | | bundle 体积 | **0**(自己画SVG) | 0 | 0 | | 回归风险 | 低(纯展示层) | 中(碰后端) | 高(联动状态多) | | 用户感知 | 立竿见影 | 需有去年数据才显价值 | 依赖使用习惯 | 方案 A 的**性价比最高**:零后端风险、零依赖、视觉提升立竿见影。做完 A 看实际效果,再决定要不要追加 B/C。 --- ## 5. 方案 A 详细技术设计 ### 5.1 新增组件:`TrendLineChart` **位置**:`components/admin/TrendLineChart.tsx`(独立出来,便于复用和测试) **Props**: ```ts interface TrendPoint { bucket: string; count: number } interface TrendLineChartProps { data: TrendPoint[] // 后端返回的 dailyUploads / monthlyUploads granularity: 'day' | 'month' // 决定 x 轴标签格式和补零策略 accent: 'sky' | 'teal' // 与现有柱状图配色呼应 startDate?: string // 用于动态标题和补零范围 endDate?: string } ``` ### 5.2 SVG 折线图核心算法 ``` 1. 数据补零:把 data 按 [startDate, endDate] 补齐成连续日期序列 (后端只返回有数据的天,绘图需要连续 x 轴) - day: 按天补,跨度大时自动降采样到"每周一个点" - month: 按月补 2. 坐标映射: - x = (index / (n-1)) * width - y = height - (count / maxCount) * height 3. 路径生成: - 折线: M x0,y0 L x1,y1 ... - 面积: 折线 + 回到底边的闭合路径,fill 用渐变(opacity 0.15→0) 4. 均值参考线: - avg = sum(count) / n - y = height - (avg / maxCount) * height - 虚线 stroke-dasharray="4 4" 5. 峰值标注: - 找 max count 的点,上方画圆点 + 文字标签 6. hover tooltip: - 监听 mousemove,找最近的数据点,浮层显示 "日期:N条" ``` ### 5.3 统计摘要条(图表上方) ``` 峰值 06-08(42条) · 均值 18.3条/天 · 合计 549条 · 环比 +12.3% ▲ ``` - 峰值/均值/合计:前端从 data 算 - 环比:对比"后半段 vs 前半段"的均值变化(无需后端,纯前端切分) ### 5.4 视图切换 图表右上角 `[📈 图表] [📊 表格]` 切换按钮(用现有 `rounded-apple` 风格)。 - 默认图表视图 - 切到表格视图 = 当前形态(保留,零删减,满足"都不删") ### 5.5 标题修复(P1-2) ```ts // 修改前(写死):

每日趋势(近31天)

// 修改后(动态): const dayCount = dailyUploads.length const rangeText = startDate && endDate ? `${startDate} ~ ${endDate},共${dayCount}天` : `全部时间,共${dayCount}天`

每日趋势(${rangeText})

``` --- ## 6. 配色与视觉规范(对齐已完成的 UI 重做) 延续上一轮 UI 重做的 Apple 基线: - 折线/面积主色:`sky-500`(每日)/ `teal-500`(月度),与两个柱状图呼应 - 面积填充:`from-sky-500/20 to-transparent` 渐变 - 均值线:`gray-400` 虚线 - 峰值点:`sky-600` 实心圆 + 白色描边 - tooltip:`rounded-apple` 白底 + `shadow-apple-lg` - 字体:`tabular-nums`(数字等宽对齐) --- ## 7. 改动清单(方案 A) | 文件 | 改动 | 风险 | |---|---|---| | `components/admin/TrendLineChart.tsx` | **新增**(~250行 SVG 折线组件) | 低,新文件 | | `components/admin/DataStatisticsTab.tsx` | 引入 TrendLineChart,替换两个趋势表格的渲染,加视图切换按钮,修 P1-2 标题 | 低,只动渲染层 | | 后端 `lib/admin-material-stats.ts` | **不改** | - | | 数据库 / Prisma | **不改** | - | **不涉及**:接口签名、响应结构、权限、数据写入、其它页面。 --- ## 8. 验证清单(做完后) 1. `npx tsc --noEmit` 类型通过 2. `npm test` 现有测试不回归 3. 切换"今天/近7天/近30天/本月/上月/全部"6个预设,图表正确补零、标题正确显示 4. 表格视图切换正常,数据与图表一致 5. hover tooltip 正确显示日期和数值 6. 峰值/均值/合计/环比 4 个摘要数字计算正确 7. shadow 环境实测视觉 --- ## 9. 待你确认的开放问题 1. **方案选 A / B / C?**(我推荐 A) 2. **视图切换默认显示图表还是表格?**(我推荐默认图表) 3. **每日趋势跨度很大时(比如选"全部",跨2年=730天),怎么处理?** - 选项:(a) 自动降采样到每周一个点 (b) 横向滚动 (c) 强制只显示最近N天 - 我推荐 (a) 降采样,保证图表可读 4. **要不要这个提案先作为文档存档,等你 review 后再决定是否实施?**