1
0
Files
DiAL/openspec/specs/dashboard/spec.md
lanyuanxiaoyao cfca03b4d6 refactor: 规范审查与重组,合并细粒度规范,清理过时内容
- 合并 20+ 细粒度 spec 为粗粒度主题规范:dashboard、data-store、probe-engine、probe-api、probe-config 等
- 删除完全冗余规范:data-retention(被 probe-engine+data-store 覆盖)、backend-code-quality(DEVELOPMENT.md 已记录)
- 补充 http-checker 规范至完整标准(配置+执行+expect+校验+observation),匹配代码 440 行实现
- 清理 tcp/udp/llm checker 规范中已废弃 defaults 配置段的残留 Scenario
- 清理 checker-cohesion-structure 中的实现路径引用(src/server/...)
- 统一所有 spec 格式(## Purpose 开头,去除 # Capability/Title 形式)
- 更新 prompt-spec-review.md 审查提示文档
2026-05-22 18:55:18 +08:00

17 KiB
Raw Blame History

Purpose

定义拨测系统前端 Dashboard 页面页面骨架布局、刷新频率控制与倒计时、CSS 工具类基础设施、总览统计卡片、Dashboard 数据查询、加载和错误状态处理。分组表格布局见 target-table,目标详情 Drawer 见 target-detail-drawer,数据轮询和缓存见 tanstack-query-data-layer

Requirements

Requirement: 页面骨架布局

Dashboard SHALL 使用 TDesign Layout 组件体系构建页面骨架,包含顶部导航栏和内容区域。

Scenario: Layout 结构

  • WHEN Dashboard 页面渲染
  • THEN 页面 SHALL 使用 TDesign Layout 组件包裹 Layout.HeaderLayout.Content

Scenario: 顶部导航栏

  • WHEN Dashboard 页面渲染
  • THEN Layout.Header SHALL 内嵌 TDesign HeadMenu 组件,logo prop 渲染品牌名 "DiAL" 和副标题 "统一拨测平台"(水平排列),operations prop 渲染主题模式选择器、刷新频率选择器和倒计时/刷新按钮组合控件

Scenario: Header 右侧操作区

  • WHEN Dashboard 页面渲染
  • THEN HeadMenu operations 区域 SHALL 包含主题模式 RadioGroup、刷新频率 RadioGroup 和倒计时文本(或手动刷新按钮),三者水平排列并垂直居中

Scenario: 主题选择器位置

  • WHEN HeadMenu operations 区域渲染
  • THEN 主题模式 RadioGroup SHALL 位于刷新频率 RadioGroup 前面

Scenario: Header 右侧操作区位置

  • WHEN HeadMenu 渲染
  • THEN operations 区域 SHALL 使用右侧 margin 向内收缩,避免紧贴浏览器右边缘

Scenario: 内容区域居中

  • WHEN Dashboard 内容区渲染
  • THEN Layout.Content 内部 SHALL 使用 CSS 类限制最大宽度max-width: 1400px并水平居中

Scenario: 页面背景色

  • WHEN Dashboard 页面渲染
  • THEN 页面背景色 SHALL 使用 var(--td-bg-color-page),内容卡片浮于当前 TDesign 主题背景之上

Requirement: Header 版本号展示

Dashboard SHALL 在顶部导航栏品牌区域展示当前运行实例的应用版本号,版本号 SHALL 使用 /api/meta 返回的 version 字段,并以 v 前缀显示。

Scenario: Meta 数据已加载

  • WHEN Dashboard 成功获取 /api/meta 且返回 version: "0.1.0"
  • THEN Header 品牌区域 SHALL 展示 v0.1.0

Scenario: Meta 数据尚未加载或请求失败

  • WHEN Dashboard 尚未获取到有效 version
  • THEN Header SHALL 保持可用并省略版本号占位,不影响品牌名、主题模式选择器、刷新频率选择器和倒计时/刷新按钮渲染

Scenario: 版本号视觉层级

  • WHEN Header 展示版本号
  • THEN 版本号 SHALL 使用次级文本样式弱展示,不得使用内联 style、硬编码色值、!important 或覆盖 TDesign 内部类名

Requirement: 刷新频率选择器

HeadMenu operations 区域 SHALL 提供 RadioGroup 组件供用户选择刷新频率。

Scenario: RadioGroup 渲染

  • WHEN Dashboard 页面渲染
  • THEN HeadMenu operations 区域 SHALL 显示 RadioGrouptheme="button", variant="default-filled"选项为手动、10秒、30秒、1分钟、5分钟

Scenario: 默认选中

  • WHEN 页面首次加载
  • THEN RadioGroup SHALL 默认选中"30秒"

Scenario: 切换频率立即刷新

  • WHEN 用户切换刷新频率选项
  • THEN 系统 SHALL 立即触发一次数据刷新,然后应用新的刷新间隔

Requirement: 倒计时显示

RadioGroup 右侧 SHALL 显示距下次自动刷新的倒计时。倒计时逻辑 SHALL 封装在独立的 RefreshCountdown 组件中App 组件 SHALL NOT 持有每秒更新的 now state。自动倒计时数字 SHALL 使用 @number-flow/react 提供滚动过渡,非倒计时状态 SHALL 保持普通文本或按钮语义。

Scenario: RefreshCountdown 组件封装

  • WHEN Dashboard 页面渲染
  • THEN 倒计时显示 SHALL 由独立的 RefreshCountdown 组件负责,该组件内部持有 now state 和每秒 setInterval,渲染边界限制在该组件内部

Scenario: RefreshCountdown props

  • WHEN RefreshCountdown 组件渲染
  • THEN 组件 SHALL 接收 dashboardUpdatedAt: numberrefreshInterval: numberisFetching: booleanisManualRefresh: booleanonRefresh: () => void 作为 props

Scenario: NumberFlow 数字滚动

  • WHEN 自动刷新模式下已完成首次刷新且当前未处于刷新中状态
  • THEN 倒计时数字 SHALL 使用 @number-flow/reactNumberFlow 渲染,并使用向下滚动趋势表达倒计时递减

Scenario: 秒级间隔格式

  • WHEN 自动刷新间隔小于 60 秒
  • THEN 倒计时 SHALL 显示为"xx秒"格式(如"26秒"

Scenario: 分钟级稳定格式

  • WHEN 自动刷新间隔大于等于 60 秒
  • THEN 倒计时 SHALL 显示为"x分xx秒"格式,秒数 SHALL 固定为两位(如"4分30秒"、"0分09秒"

Scenario: 时间数字边界

  • WHEN 分钟级倒计时中的秒数在 59 到 00 边界变化
  • THEN 秒数十位 SHALL 按时间显示规则限制在 0 到 5 之间滚动

Scenario: 无前缀

  • WHEN 倒计时显示
  • THEN 可见倒计时文本 SHALL 不包含任何前缀(如"下一次刷新:"),直接显示时间

Scenario: 可访问文本

  • WHEN NumberFlow 倒计时渲染
  • THEN 倒计时容器 SHALL 暴露与当前倒计时等价的可访问文本,供测试和辅助技术读取

Scenario: 刷新中状态

  • WHEN 数据正在刷新isFetching=true 且 isLoading=false
  • THEN 倒计时文本 SHALL 显示为"刷新中..."

Scenario: 等待首次刷新状态

  • WHEN 自动刷新模式下尚未完成首次刷新
  • THEN 倒计时文本 SHALL 显示为"等待首次刷新"

Requirement: App 组件渲染隔离

App 组件 SHALL NOT 持有任何高频更新的 state如每秒更新的时钟确保 App 的重渲染频率与数据刷新频率一致(默认 30 秒一次)。

Scenario: App 无 now state

  • WHEN App 组件渲染
  • THEN App SHALL NOT 包含 useState 管理的时钟 state也 SHALL NOT 包含每秒触发的 setInterval

Scenario: App 重渲染频率

  • WHEN Dashboard 处于自动刷新模式
  • THEN App 组件的重渲染 SHALL 仅由 TanStack Query 的 refetch 触发(频率等于用户选择的刷新间隔),而非每秒触发

Requirement: 手动刷新按钮

选择"手动"模式时,倒计时区域 SHALL 替换为刷新按钮。

Scenario: 手动模式显示按钮

  • WHEN 用户选择"手动"刷新频率
  • THEN 倒计时区域 SHALL 替换为刷新图标按钮

Scenario: 点击刷新

  • WHEN 用户点击刷新按钮
  • THEN 系统 SHALL 触发一次数据刷新

Scenario: 刷新中禁用

  • WHEN 数据正在刷新
  • THEN 刷新按钮 SHALL 显示 loading 状态且 disabled防止连续点击

Requirement: 布局稳定性

倒计时/按钮容器 SHALL 保持布局稳定避免内容变化导致的抖动。NumberFlow 倒计时 SHALL 通过分组同步和等宽数字样式降低位数、单位和动画变化带来的布局偏移。

Scenario: 数字等宽

  • WHEN 倒计时数字变化
  • THEN 容器和 NumberFlow 倒计时 SHALL 使用 tabular-nums 字体特性,确保数字等宽不抖动

Scenario: NumberFlow 分组同步

  • WHEN 分钟级倒计时同时渲染分钟和秒数
  • THEN 分钟和秒数 SHALL 使用 NumberFlowGroup 同步布局变化

Scenario: 格式切换不抖动

  • WHEN 倒计时在按钮、秒级文本和分钟级文本之间切换
  • THEN 容器 SHALL 使用 min-width 确保最小宽度,避免 RadioGroup 位移

Requirement: 状态色 CSS 类

styles.css SHALL 定义状态指示相关的 CSS 类,颜色使用 TDesign tokens。

Scenario: StatusDot 颜色类

  • WHEN StatusDot 组件渲染
  • THEN 组件 SHALL 使用 .status-dot 基础类 + .status-dot--upbackground: --td-success-color)或 .status-dot--downbackground: --td-error-color)修饰类,不使用内联 style

Scenario: StatusDot 发光阴影

  • WHEN StatusDot 组件渲染
  • THEN .status-dot--up SHALL 定义 box-shadow 使用 --td-success-color.status-dot--down SHALL 定义 box-shadow 使用 --td-error-color

Scenario: StatusBar 色块类

  • WHEN StatusBar 组件渲染色块
  • THEN 组件 SHALL 使用 .status-bar-block 基础类 + .status-bar-block--upbackground: --td-success-color)、.status-bar-block--downbackground: --td-error-color)或 .status-bar-block--emptybackground: --td-bg-color-component-disabled)修饰类,不使用内联 style

Requirement: 可用率色阶 CSS 变量

styles.css SHALL 定义 10 级可用率色阶 CSS 自定义属性,使用项目自定义色值。

Scenario: 色阶变量定义

  • WHEN 可用率进度条渲染
  • THEN 色阶 SHALL 通过 CSS 自定义属性 --avail-0--avail-9 定义,值为项目自定义色值(#d54941#3dba60

Scenario: 色阶渐变方向

  • WHEN 色阶变量被引用
  • THEN 色阶 SHALL 从红色0-30%经橙色30-60%过渡到绿色60-100%

Requirement: 辅助工具类

styles.css SHALL 定义前端组件复用的工具类,包含页面布局相关类。

Scenario: 文本禁用色类

  • WHEN 延迟列无数据需要显示占位符
  • THEN 组件 SHALL 使用 .text-disabledcolor: --td-text-color-disabled

Scenario: 等宽数字类

  • WHEN 数值需要等宽显示
  • THEN 组件 SHALL 使用 .tabular-numsfont-variant-numeric: tabular-nums

Scenario: 延迟色值类

  • WHEN 延迟数值渲染
  • THEN 组件 SHALL 使用 .latency-ok.latency-warn.latency-error

Scenario: 延迟值容器类

  • WHEN 延迟数值需要固定宽度对齐
  • THEN 组件 SHALL 使用 .latency-valuedisplay: inline-block; min-width: 7ch; white-space: nowrap

Scenario: 全宽布局类

  • WHEN 组件需要占满父容器宽度
  • THEN 组件 SHALL 使用 .full-widthwidth: 100%

Scenario: 可点击表格类

  • WHEN PrimaryTable 行支持点击交互
  • THEN 表格 SHALL 使用 .clickable-tablecursor: pointer

Scenario: Tab 面板内边距类

  • WHEN Drawer 内 Tabs 面板需要内边距
  • THEN TabPanel SHALL 使用 className="tab-panel-padded" prop 传入类名

Scenario: 内容区居中类

  • WHEN Dashboard 内容区需要居中且限制最大宽度
  • THEN 内容区 SHALL 使用 .dashboard-contentmax-width: 1400px; margin: 0 auto; padding: var(--td-comp-paddingTB-xl) var(--td-comp-paddingLR-xl)

Scenario: 页面背景色

  • WHEN Dashboard 页面渲染
  • THEN .dashboard 类 SHALL 设置 background: var(--td-bg-color-page)min-height: 100vhwidth: 100%

Scenario: 品牌标识类

  • WHEN HeadMenu logo 区域渲染品牌名和副标题
  • THEN 品牌 SHALL 使用 .dashboard-branddisplay: inline-flex; align-items: baseline; gap: var(--td-comp-margin-s)),品牌名 SHALL 使用 .dashboard-logofont-size: calc(var(--td-font-size-title-large) + 6px); font-weight: 700副标题 SHALL 使用 .dashboard-subtitlefont-size: var(--td-font-size-body-medium); color: var(--td-text-color-secondary)

Scenario: Header 右侧操作区类

  • WHEN HeadMenu operations 区域渲染主题模式选择器、刷新频率选择器和倒计时/按钮
  • THEN 容器 SHALL 使用 .dashboard-header-controlsdisplay: inline-flex; align-items: center; gap: var(--td-comp-margin-s); margin-right: var(--td-comp-margin-xxl)

Scenario: Header 右侧操作区单行布局

  • WHEN Header 右侧操作区渲染
  • THEN .dashboard-header-controls SHALL 保持桌面单行水平布局,不为该区域新增窄屏换行或收纳规则

Scenario: 倒计时文本类

  • WHEN 倒计时文本或刷新按钮渲染
  • THEN 容器 SHALL 使用 .dashboard-countdowndisplay: inline-flex; align-items: center; font-variant-numeric: tabular-nums; min-width: 5ch确保数字等宽且格式切换不抖动

Scenario: SummaryCard 居中类

  • WHEN SummaryCards 内 Statistic 需要居中
  • THEN Statistic 所在的 Col SHALL 使用 .summary-stat-coltext-align: center

Scenario: SummaryCards 行间距类

  • WHEN SummaryCards 容器需要与下方内容保持间距
  • THEN 容器 SHALL 使用 .summary-cards-rowmargin-bottom: var(--td-comp-margin-xl)

Scenario: Drawer 时间控件单行类

  • WHEN Drawer 时间选择器需要单行布局
  • THEN 控件容器 SHALL 使用 .drawer-time-controlsdisplay: flex; align-items: center; gap: var(--td-comp-margin-m); width: 100%),日期选择器 SHALL 使用 .drawer-date-rangeflex: 1; min-width: 360px

Scenario: Drawer 时间控件响应式

  • WHEN 视口宽度 ≤ 768px
  • THEN .drawer-time-controls SHALL 启用 flex-wrap.drawer-date-range min-width 改为 100%

Scenario: 概览统计卡片类

  • WHEN Drawer 概览统计区渲染
  • THEN 统计卡片 SHALL 使用 .overview-stat-cardbackground: var(--td-bg-color-container-hover)),并使用 TDesign Statistic 组件自带的上下布局title 在上、value 在下),通过 .summary-stat-coltext-align: center实现内容居中。系统 SHALL NOT 使用已移除的 .overview-stat-item.overview-stat-value 类。

Requirement: NumberFlow 倒计时样式类

styles.css SHALL 定义 NumberFlow 倒计时相关样式类,供 Header 倒计时组件使用。样式 SHALL 继承 TDesign 文本颜色或使用 TDesign CSS tokens不得使用组件内联 style、硬编码色值、!important 或覆盖 TDesign 内部类名。

Scenario: 倒计时滚动容器类

  • WHEN Header 自动刷新倒计时以 NumberFlow 形式渲染
  • THEN 倒计时 SHALL 使用集中定义的滚动容器类,保持 inline-flex、baseline 对齐、nowrap 和 tabular-nums

Scenario: 倒计时数字类

  • WHEN NumberFlow 数字渲染
  • THEN 数字 SHALL 使用集中定义的数字类配置 line-height 和 NumberFlow mask CSS 变量,减少滚动边缘突兀感

Scenario: 倒计时单位类

  • WHEN 分钟或秒单位文本渲染
  • THEN 单位 SHALL 使用集中定义的单位类与数字保持基线对齐,并继承当前 TDesign 文本色

Scenario: 不使用内联样式

  • WHEN RefreshCountdown 组件渲染 NumberFlow 倒计时
  • THEN 组件 SHALL 通过 className 引用 styles.css 中的样式类,不得通过 React style prop 设置 NumberFlow 展示样式

Requirement: 异常行背景类

styles.css SHALL 定义 DOWN 行的背景色和左侧竖线,使用安全选择器且不使用 !important

Scenario: DOWN 行背景色

  • WHEN 表格行标记为 DOWN 状态
  • THEN 行 SHALL 通过 .t-table tr.row-down 选择器获得浅红色背景

Scenario: DOWN 行左侧竖线

  • WHEN 表格行标记为 DOWN 状态
  • THEN 行 SHALL 通过 .t-table tr.row-down 选择器获得 border-left: 3px solid var(--td-error-color)

Scenario: DOWN 行 hover 状态

  • WHEN 鼠标悬停在 DOWN 行上
  • THEN 行背景 SHALL 通过 .t-table--hoverable tbody tr.row-down:hover 选择器显示 hover 状态色

Requirement: Dashboard 数据查询

Dashboard SHALL 通过 GET /api/dashboard 获取首屏总览统计和目标列表数据。

Scenario: 查询 Dashboard 数据

  • WHEN 页面处于打开状态
  • THEN 前端 SHALL 使用 TanStack Query 请求 GET /api/dashboard?window=24h&recentLimit=30

Scenario: 统计数据自动刷新

  • WHEN 页面处于打开状态
  • THEN Dashboard 数据 SHALL 通过 TanStack Query 的 refetchInterval=8000 自动刷新

Scenario: 元信息独立查询

  • WHEN 页面需要 checker 类型列表
  • THEN 前端 SHALL 继续通过 GET /api/meta 独立查询 checkerTypes

Requirement: 总览统计卡片

Dashboard SHALL 在页面顶部使用单个 TDesign Card 组件内嵌一行居中的 Statistic 展示总览统计,包含总目标数、正常数、异常数和窗口异常事件数。

Scenario: 展示统计卡片

  • WHEN 用户打开 Dashboard 页面
  • THEN 页面顶部 SHALL 使用单个 TDesign Card无 shadow、无 bordered内嵌 TDesign Row/Col 布局展示 4 个居中的 Statistic全部目标数color=blue、正常目标数color=green、异常目标数color=red、24h 异常事件数color=orange

Scenario: 指标居中显示

  • WHEN SummaryCards 渲染
  • THEN 每个 Statistic 所在的 Col SHALL 使用 .summary-stat-col 类实现标题和数字居中对齐

Scenario: 异常事件数据来源

  • WHEN SummaryCards 渲染 24h 异常事件数
  • THEN 该数值 SHALL 使用 DashboardResponse.summary.incidents 字段,标题 SHALL 基于当前 window 展示为"24h 异常事件数"

Requirement: 页面加载与错误状态

Dashboard SHALL 使用 TDesign Skeleton 组件处理首次加载状态,使用 Alert 处理错误。

Scenario: 首次加载

  • WHEN 页面首次加载且数据尚未返回
  • THEN 页面 SHALL 使用 TDesign Skeleton 组件animation="gradient")展示页面骨架,模拟 Summary 区域和 Table 区域的大致结构

Scenario: API 请求失败

  • WHEN 前端 API 请求失败
  • THEN 页面 SHALL 使用 TDesign Alert 组件theme=error显示错误提示