6.0 KiB
Context
当前 Dashboard 使用 TargetTable + TargetRow + TargetDetail 的表格布局,所有目标扁平排列在同一个表格中,点击行展开内联详情面板。前端组件结构为:
App → SummaryCards(4) + TargetTable → TargetRow → TargetDetail(内联)
后端 API 提供 GET /api/targets 返回含 sparkline: number[] 的目标列表,GET /api/targets/:id/trend?hours=24 返回趋势数据,GET /api/targets/:id/history?limit=20 返回历史记录。全局汇总含 avgDurationMs。
本次重构涉及全栈变更:YAML 配置格式、后端数据存储、API 接口、前端组件和样式。
Goals / Non-Goals
Goals:
- 引入 target 分组概念,按组展示卡片,default 组排最前
- 卡片内同时展示状态条(UP/DOWN 可视化)和迷你 sparkline(耗时趋势)
- 模态框提供丰富的详情查看体验:多维统计图 + 带分页的检查结果列表
- 模态框支持自定义时间范围筛选(分钟精度)和快捷时间范围按钮
- 移除无实际意义的全局平均耗时统计
- 统一卡片迷你可视化的采样数量为全局可配置项
recentSampleCount - 不引入新依赖,复用现有 recharts 库
Non-Goals:
- 分组折叠/展开功能
- 分组排序自定义(固定为 default 最前,其余按 YAML 出现顺序)
- per-target 的 sparkline 数量自定义(统一使用全局配置)
- 模态框内的状态筛选(仅支持时间范围筛选)
- 卡片内显示可用率数字
- 按耗时阈值筛选
Decisions
D1: group 配置采用扁平字段(方案 A)
选择: 在每个 target 上加 group?: string 可选字段。
替代方案: 嵌套结构(targets: [{ group: "x", items: [...] }])。
理由: 扁平字段是增量变更,完全向后兼容,不破坏现有 targets 数组格式。嵌套结构会改变整个配置文件的顶层结构,影响面大且无额外收益。
D2: sparkline 替换为 recentSamples 结构化数据
选择: 将 sparkline: number[] 替换为 recentSamples: RecentSample[],每个 sample 包含 timestamp、durationMs、up。
替代方案: 新增独立的 status-bar API。
理由: 合并为一个接口减少请求数,前端一次数据同时满足状态条和 sparkline 两种可视化。timestamp 的包含使得 hover tooltip 有意义。
D3: recentSampleCount 固定为 30
选择: StatusBar 和 MiniSparkline 的采样数量硬编码为 30。
理由: 30 是合理的默认值,覆盖最近 30 次检查,无需暴露配置项增加复杂度。
D4: 模态框时间筛选同时支持快捷按钮和自定义日期选择器
选择: 快捷按钮(1h/6h/24h/7d)与分钟精度日期选择器并存,联动设计——点击快捷按钮自动填入日期,手动修改日期则快捷按钮取消高亮。
理由: 快捷按钮覆盖绝大多数场景,日期选择器提供精确控制能力。分钟精度对于拨测监控场景足够精确。
D5: trend API 改用 from/to 时间范围参数
选择: GET /api/targets/:id/trend?from=ISO&to=ISO 替代 ?hours=24。
理由: 模态框支持自定义时间范围,hours 参数无法表达任意时间范围。from/to 是更通用的设计。
D6: history API 新增分页支持
选择: GET /api/targets/:id/history?from=ISO&to=ISO&page=1&pageSize=20,返回 { items, total, page, pageSize }。
理由: 自定义时间范围可能导致大量数据(如选择 7 天范围),分页避免一次性传输过多数据。
D7: SummaryCards 从 4 个减为 3 个
选择: 移除"平均耗时"卡片,保留"全部/正常/异常"。
理由: 引入分组后,不同分组目标的平均耗时混合计算没有实际参考价值。具体目标的耗时信息在模态框中查看。
D8: targets 表使用 grp 列名
选择: 数据库列名使用 grp 而非 group。
理由: group 是 SQL 关键字,使用 grp 避免转义问题。API 层和前端仍使用 group 作为字段名。
D9: 卡片固定宽度 280px + CSS Grid auto-fill 响应式
选择: grid-template-columns: repeat(auto-fill, 280px) 实现响应式布局。
替代方案: 百分比宽度或 flex-wrap。
理由: 固定宽度保证卡片内容一致性,auto-fill 自动适应视口宽度变化,从 1 列到多列无缝适配。
D10: 分组排序由后端 SQL 保证
选择: ORDER BY CASE WHEN grp='default' THEN 0 ELSE 1 END, grp, id。
理由: 后端排序后前端只需顺序遍历渲染,无需额外排序逻辑。分组名按 YAML 首次出现顺序(即 id 顺序)自然排序。
D11: 环形图(Donut Chart)展示状态分布
选择: 模态框统计图使用 recharts 的 PieChart + 内部标签实现环形图,中间显示可用率百分比。
替代方案: 纯饼图。
理由: 环形图中间可展示关键数字(可用率 %),信息密度更高。
D12: 状态条使用连续色块
选择: 方块数量固定 30 个,每个 6px 宽 2px 间距,UP 绿色 #1fbf75,DOWN 红色 #e5484d,无数据灰色 #e2e8f0。
理由: 类似 GitHub contribution graph 的可视化方式,直观展示最近检查状态。
Risks / Trade-offs
- [卡片信息密度] 卡片宽度仅 280px,同时放状态条和 sparkline 可能显得拥挤 → 状态条和 sparkline 各占一行,垂直堆叠,控制高度在合理范围
- [API BREAKING 变更] sparkline → recentSamples、trend hours → from/to、history limit → page/pageSize 均为不兼容变更 → 项目未上线无需向前兼容,一次性完成
- [targets 表 schema 变更] 新增 grp 列需要数据库 migration → SQLite ALTER TABLE ADD COLUMN 是安全操作,新列有默认值不影响已有数据
- [模态框复杂度] 时间选择器 + 分页 + 多图表实现复杂度较高 → 拆分为独立子组件,每个组件职责单一
- [recentSampleCount 默认值] 固定为 30,无法通过配置调整 → 合理值,30 覆盖足够长的最近检查周期