From d873484938e65372cc0f35e57ff79c6953d46928 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Mon, 11 May 2026 16:26:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E5=B8=83=E5=B1=80=E4=B8=BA=E6=B5=81=E5=8A=A8=E5=BC=8F=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E5=BC=8F=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 Dashboard max-width 限制,充分利用屏幕空间 - Summary Cards 和 Target Cards 改为 Flexbox wrap 流动布局 - 卡片固定宽度 280px,根据页面宽度自动调整单行数量 - 添加平滑过渡动画效果(0.3s ease) - 移除移动端媒体查询覆盖规则 - 同步更新 card-dashboard 规格文档 --- README.md | 24 ++++++++--------- openspec/specs/card-dashboard/spec.md | 33 +++++++++++++++++++---- src/web/styles.css | 39 +++++++++++++-------------- 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index dc5d406..81dd857 100644 --- a/README.md +++ b/README.md @@ -170,13 +170,13 @@ targets: ## API 端点 -| 端点 | 说明 | -| ----------------------------------------------------- | --------------------------------------- | -| `GET /health` | 健康检查 | -| `GET /api/summary` | 总览统计(total/up/down/lastCheckTime) | -| `GET /api/targets` | 目标列表及最新状态、分组和采样数据 | -| `GET /api/targets/:id/history?from=ISO&to=ISO&page=1&pageSize=20` | 指定目标的拨测记录(时间范围 + 分页) | -| `GET /api/targets/:id/trend?from=ISO&to=ISO` | 指定目标的按小时聚合趋势 | +| 端点 | 说明 | +| ----------------------------------------------------------------- | --------------------------------------- | +| `GET /health` | 健康检查 | +| `GET /api/summary` | 总览统计(total/up/down/lastCheckTime) | +| `GET /api/targets` | 目标列表及最新状态、分组和采样数据 | +| `GET /api/targets/:id/history?from=ISO&to=ISO&page=1&pageSize=20` | 指定目标的拨测记录(时间范围 + 分页) | +| `GET /api/targets/:id/trend?from=ISO&to=ISO` | 指定目标的按小时聚合趋势 | ### 响应字段 @@ -204,11 +204,11 @@ API 错误返回 `ApiErrorResponse` 格式: { "error": "描述信息", "status": 400 } ``` -| 状态码 | 触发场景 | -|--------|---------| -| 400 | 参数格式错误(无效 ID、from/to 缺失或格式错误、page/pageSize 非正整数) | -| 404 | 目标不存在、API 路由未匹配 | -| 405 | 非 GET 方法请求 API 路由 | +| 状态码 | 触发场景 | +| ------ | ----------------------------------------------------------------------- | +| 400 | 参数格式错误(无效 ID、from/to 缺失或格式错误、page/pageSize 非正整数) | +| 404 | 目标不存在、API 路由未匹配 | +| 405 | 非 GET 方法请求 API 路由 | ## 代码质量 diff --git a/openspec/specs/card-dashboard/spec.md b/openspec/specs/card-dashboard/spec.md index 29f2e70..005850c 100644 --- a/openspec/specs/card-dashboard/spec.md +++ b/openspec/specs/card-dashboard/spec.md @@ -20,15 +20,27 @@ Dashboard SHALL 按分组展示所有拨测目标,每个分组包含带统计 - **THEN** 分组标题 SHALL 显示 "默认分组" ### Requirement: 响应式卡片网格 -Dashboard SHALL 使用固定宽度的卡片配合响应式网格布局。 +Dashboard SHALL 使用固定宽度的卡片配合 Flexbox 流动布局,容器无最大宽度限制。 + +#### Scenario: Dashboard 容器占满宽度 +- **WHEN** 用户打开 Dashboard 页面 +- **THEN** Dashboard 容器 SHALL 占满浏览器宽度,不设置 max-width 限制 #### Scenario: 卡片固定宽度 -- **WHEN** 页面渲染卡片 -- **THEN** 每个卡片 SHALL 固定宽度 280px +- **WHEN** 页面渲染卡片(包括 Summary Cards 和 Target Cards) +- **THEN** 每个卡片 SHALL 固定宽度 280px,使用 `flex-shrink: 0` 防止收缩 -#### Scenario: 响应式列数 +#### Scenario: 流动式布局 - **WHEN** 视口宽度变化 -- **THEN** 卡片网格 SHALL 自动调整列数,使用 CSS Grid auto-fill 适配可用空间 +- **THEN** 卡片网格 SHALL 使用 Flexbox wrap 自动换行,根据可用宽度调整单行卡片数量 + +#### Scenario: 卡片左对齐 +- **WHEN** 页面渲染卡片网格 +- **THEN** 卡片 SHALL 左对齐排列,右侧自然留白 + +#### Scenario: 统一间距 +- **WHEN** 页面渲染 Summary Cards 和 Target Cards +- **THEN** 两种卡片网格 SHALL 使用相同的 gap 间距(16px) ### Requirement: 目标卡片内容 每个目标卡片 SHALL 展示目标名称、当前状态、类型标签、状态条和迷你耗时趋势线。 @@ -61,3 +73,14 @@ Dashboard SHALL 使用固定宽度的卡片配合响应式网格布局。 #### Scenario: 卡片点击打开详情 - **WHEN** 用户点击某个目标卡片 - **THEN** 系统 SHALL 打开该目标的详情模态框 + +### Requirement: 平滑过渡动画 +卡片 SHALL 具有平滑的交互过渡动画效果。 + +#### Scenario: 卡片悬停动画 +- **WHEN** 鼠标悬停在卡片上 +- **THEN** 卡片 SHALL 平滑过渡显示上浮效果(阴影加深、轻微上移),过渡时长 0.3s + +#### Scenario: 布局变化过渡 +- **WHEN** 视口宽度变化导致卡片重新排列 +- **THEN** 卡片位置变化 SHALL 有平滑的过渡动画 diff --git a/src/web/styles.css b/src/web/styles.css index 3dc2fa2..155902b 100644 --- a/src/web/styles.css +++ b/src/web/styles.css @@ -22,9 +22,8 @@ body { } .dashboard { - max-width: 1200px; - margin: 0 auto; padding: 32px 24px; + width: 100%; } .dashboard-header { @@ -54,18 +53,28 @@ body { } .summary-cards { - display: grid; - grid-template-columns: repeat(3, 1fr); + display: flex; + flex-wrap: wrap; gap: 16px; margin-bottom: 32px; } .summary-card { + width: 280px; + flex-shrink: 0; padding: 20px; border: 1px solid rgba(49, 83, 126, 0.12); border-radius: 16px; background: rgba(255, 255, 255, 0.85); box-shadow: 0 4px 16px rgba(34, 57, 91, 0.08); + transition: + transform 0.3s ease, + box-shadow 0.3s ease; +} + +.summary-card:hover { + box-shadow: 0 8px 32px rgba(34, 57, 91, 0.12); + transform: translateY(-4px); } .card-value { @@ -118,12 +127,14 @@ body { } .card-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + display: flex; + flex-wrap: wrap; gap: 16px; } .target-card { + width: 280px; + flex-shrink: 0; padding: 16px; border: 1px solid rgba(49, 83, 126, 0.12); border-radius: 12px; @@ -131,8 +142,8 @@ body { box-shadow: 0 2px 8px rgba(34, 57, 91, 0.06); cursor: pointer; transition: - box-shadow 0.15s, - transform 0.15s; + box-shadow 0.3s ease, + transform 0.3s ease; } .target-card:hover { @@ -566,18 +577,6 @@ body { padding: 16px; } - .summary-cards { - grid-template-columns: repeat(2, 1fr); - } - - .card-grid { - grid-template-columns: 1fr; - } - - .target-card { - width: 100%; - } - .modal-content { width: 95vw; max-height: 90vh;