From 767f26617ec83d1aefe687fa13df1120ddc65126 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Mon, 11 May 2026 18:36:16 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E6=98=BE=E7=A4=BA=E4=B8=80=E8=87=B4=E6=80=A7=E4=B8=8E?= =?UTF-8?q?=E5=8F=AF=E6=89=A9=E5=B1=95=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一 Summary Cards 和 Target Cards 宽度为 280px(CSS 变量控制) - 分组统计改为徽章展示(纯数字 + 颜色区分) - 目标名称添加 title 属性支持显示完整名称 - 建立类型映射系统,Command 显示为 CMD,支持扩展 - 移除移动端响应式代码,简化维护 - 新增 target-type-display 能力规格 - 更新 card-dashboard 和 target-detail-modal 规格 --- README.md | 1 + openspec/specs/card-dashboard/spec.md | 16 ++++-- openspec/specs/target-detail-modal/spec.md | 11 ++++ openspec/specs/target-type-display/spec.md | 42 +++++++++++++++ src/web/components/GroupHeader.tsx | 14 +++-- src/web/components/TargetCard.tsx | 7 ++- src/web/components/TargetDetailModal.tsx | 3 +- src/web/constants/target-type-display.ts | 10 ++++ src/web/styles.css | 54 ++++++++++--------- .../web/constants/target-type-display.test.ts | 39 ++++++++++++++ 10 files changed, 163 insertions(+), 34 deletions(-) create mode 100644 openspec/specs/target-type-display/spec.md create mode 100644 src/web/constants/target-type-display.ts create mode 100644 tests/web/constants/target-type-display.test.ts diff --git a/README.md b/README.md index 81dd857..a4e69a5 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ src/ api.ts 前后端共享 TypeScript 类型 web/ Vite + React 前端 Dashboard components/ UI 组件(卡片、分组、模态框、状态条等) + constants/ 常量定义(类型映射等) hooks/ 数据轮询和详情管理 hooks utils/ 前端工具函数 scripts/ 开发、构建和 smoke test 脚本 diff --git a/openspec/specs/card-dashboard/spec.md b/openspec/specs/card-dashboard/spec.md index 745b04c..a5f00d0 100644 --- a/openspec/specs/card-dashboard/spec.md +++ b/openspec/specs/card-dashboard/spec.md @@ -11,9 +11,13 @@ Dashboard SHALL 按分组展示所有拨测目标,每个分组包含带统计 - **WHEN** 用户打开 Dashboard 页面 - **THEN** 页面 SHALL 按分组展示目标卡片,"默认分组" 排在最上面,其余分组按 YAML 配置顺序排列 -#### Scenario: 分组标题带统计 +#### Scenario: 分组标题带统计徽章 - **WHEN** 页面渲染某个分组 -- **THEN** 分组标题 SHALL 显示分组名称、该分组内目标总数、正常数和异常数,格式为 `分组名 (N个, X UP / Y DOWN)` +- **THEN** 分组标题 SHALL 显示分组名称和三个徽章:总数(蓝色)、正常数(绿色)、异常数(红色),徽章仅显示数字 + +#### Scenario: 分组统计徽章提示 +- **WHEN** 鼠标悬停在分组统计徽章上 +- **THEN** 徽章 SHALL 显示提示文字("总数"、"正常"、"异常") #### Scenario: "default" 分组显示名称 - **WHEN** 分组名称为 "default" @@ -28,7 +32,7 @@ Dashboard SHALL 使用固定宽度的卡片配合 Flexbox 流动布局,容器 #### Scenario: 卡片固定宽度 - **WHEN** 页面渲染卡片(包括 Summary Cards 和 Target Cards) -- **THEN** 每个卡片 SHALL 固定宽度 270px,使用 `flex-shrink: 0` 防止收缩 +- **THEN** 每个卡片 SHALL 固定宽度 280px,使用 CSS 变量 `--dashboard-card-width` 统一控制 #### Scenario: 流动式布局 - **WHEN** 视口宽度变化 @@ -47,7 +51,11 @@ Dashboard SHALL 使用固定宽度的卡片配合 Flexbox 流动布局,容器 #### Scenario: 卡片第一层内容 - **WHEN** 卡片渲染 -- **THEN** 卡片第一层 SHALL 展示状态指示圆点(UP 绿色 / DOWN 红色)、目标名称和类型标签(HTTP / Command) +- **THEN** 卡片第一层 SHALL 展示状态指示圆点(UP 绿色 / DOWN 红色)、目标名称和类型标签(HTTP / CMD) + +#### Scenario: 卡片名称完整提示 +- **WHEN** 目标名称过长被截断显示 +- **THEN** 鼠标悬停在名称上 SHALL 通过浏览器原生 tooltip 显示完整名称 #### Scenario: 卡片状态指示圆点 - **WHEN** 目标最近一次拨测 matched=true diff --git a/openspec/specs/target-detail-modal/spec.md b/openspec/specs/target-detail-modal/spec.md index 166b237..a535016 100644 --- a/openspec/specs/target-detail-modal/spec.md +++ b/openspec/specs/target-detail-modal/spec.md @@ -74,3 +74,14 @@ Dashboard SHALL 在用户点击目标卡片后弹出模态框,展示该目标 #### Scenario: 自上而下渲染 - **WHEN** 模态框渲染 - **THEN** 内容区域 SHALL 分为上下两部分,上方展示统计图表,下方展示检查结果列表和分页器 + +### Requirement: 模态框标题栏类型标签 +模态框标题栏 SHALL 显示目标类型标签,使用统一的类型显示映射系统。 + +#### Scenario: 类型标签显示 +- **WHEN** 模态框标题栏渲染 +- **THEN** 标题栏 SHALL 在目标名称旁显示类型标签(HTTP / CMD) + +#### Scenario: 类型标签使用映射系统 +- **WHEN** 模态框渲染类型标签 +- **THEN** 类型标签 SHALL 使用统一的类型显示映射函数,不硬编码映射逻辑 diff --git a/openspec/specs/target-type-display/spec.md b/openspec/specs/target-type-display/spec.md new file mode 100644 index 0000000..2f61ec8 --- /dev/null +++ b/openspec/specs/target-type-display/spec.md @@ -0,0 +1,42 @@ +## Purpose + +定义目标类型(Target Type)的前端显示名称映射系统,支持从后端类型标识符到前端展示名称的可扩展转换。 + +## Requirements + +### Requirement: 类型显示名称映射 +系统 SHALL 提供目标类型到显示名称的映射,将后端类型标识符转换为前端展示的简短名称。 + +#### Scenario: HTTP 类型显示 +- **WHEN** 目标类型为 "http" +- **THEN** 前端 SHALL 显示 "HTTP" + +#### Scenario: Command 类型显示 +- **WHEN** 目标类型为 "command" +- **THEN** 前端 SHALL 显示 "CMD" + +#### Scenario: 未知类型处理 +- **WHEN** 目标类型不在映射表中 +- **THEN** 前端 SHALL 将类型名称转换为大写显示 + +### Requirement: 映射可扩展性 +类型映射系统 SHALL 支持后续新增类型,无需修改多处代码。 + +#### Scenario: 新增类型映射 +- **WHEN** 需要新增目标类型(如 "tcp"、"dns"、"grpc") +- **THEN** 开发者 SHALL 仅需在映射常量中添加一条记录 + +#### Scenario: 映射单一数据源 +- **WHEN** 前端组件需要显示目标类型 +- **THEN** 组件 SHALL 调用统一的映射函数,不直接硬编码映射逻辑 + +### Requirement: 类型安全 +类型映射系统 SHALL 提供类型安全的访问方式。 + +#### Scenario: TypeScript 类型推导 +- **WHEN** 使用映射常量 +- **THEN** TypeScript SHALL 能够推导出正确的类型(使用 `as const`) + +#### Scenario: 运行时安全 +- **WHEN** 传入无效类型 +- **THEN** 系统 SHALL 返回 fallback 值,不抛出异常 diff --git a/src/web/components/GroupHeader.tsx b/src/web/components/GroupHeader.tsx index 25f4be8..b10fc5d 100644 --- a/src/web/components/GroupHeader.tsx +++ b/src/web/components/GroupHeader.tsx @@ -11,9 +11,17 @@ export function GroupHeader({ name, total, up, down }: GroupHeaderProps) { return (

{displayName}

- - ({total}个, {up} UP / {down} DOWN) - +
+ + {total} + + + {up} + + + {down} + +
); } diff --git a/src/web/components/TargetCard.tsx b/src/web/components/TargetCard.tsx index 3a8d9d6..8a12273 100644 --- a/src/web/components/TargetCard.tsx +++ b/src/web/components/TargetCard.tsx @@ -2,6 +2,7 @@ import type { TargetStatus } from "../../shared/api"; import { StatusDot } from "./StatusDot"; import { StatusBar } from "./StatusBar"; import { MiniSparkline } from "./MiniSparkline"; +import { getTargetTypeDisplay } from "../constants/target-type-display"; interface TargetCardProps { target: TargetStatus; @@ -15,8 +16,10 @@ export function TargetCard({ target, onClick }: TargetCardProps) {
- {target.name} - {target.type === "http" ? "HTTP" : "Command"} + + {target.name} + + {getTargetTypeDisplay(target.type)}
diff --git a/src/web/components/TargetDetailModal.tsx b/src/web/components/TargetDetailModal.tsx index 2a87886..a5de5c7 100644 --- a/src/web/components/TargetDetailModal.tsx +++ b/src/web/components/TargetDetailModal.tsx @@ -4,6 +4,7 @@ import { StatusDonut } from "./StatusDonut"; import { StatusDot } from "./StatusDot"; import { TimeRangePicker } from "./TimeRangePicker"; import { Pagination } from "./Pagination"; +import { getTargetTypeDisplay } from "../constants/target-type-display"; interface TargetDetailModalProps { target: TargetStatus; @@ -48,7 +49,7 @@ export function TargetDetailModal({

{target.name}

- {target.type === "http" ? "HTTP" : "Command"} + {getTargetTypeDisplay(target.type)}