1
0

refactor: 优化卡片显示一致性与可扩展性

- 统一 Summary Cards 和 Target Cards 宽度为 280px(CSS 变量控制)
- 分组统计改为徽章展示(纯数字 + 颜色区分)
- 目标名称添加 title 属性支持显示完整名称
- 建立类型映射系统,Command 显示为 CMD,支持扩展
- 移除移动端响应式代码,简化维护
- 新增 target-type-display 能力规格
- 更新 card-dashboard 和 target-detail-modal 规格
This commit is contained in:
2026-05-11 18:36:16 +08:00
parent 48a9e96ec2
commit 767f26617e
10 changed files with 163 additions and 34 deletions

View File

@@ -11,9 +11,17 @@ export function GroupHeader({ name, total, up, down }: GroupHeaderProps) {
return (
<div className="group-header">
<h2 className="group-title">{displayName}</h2>
<span className="group-stats">
({total}, {up} UP / {down} DOWN)
</span>
<div className="group-stats">
<span className="stat-badge stat-badge-total" title="总数">
{total}
</span>
<span className="stat-badge stat-badge-up" title="正常">
{up}
</span>
<span className="stat-badge stat-badge-down" title="异常">
{down}
</span>
</div>
</div>
);
}

View File

@@ -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) {
<div className="target-card" onClick={onClick} role="button" tabIndex={0}>
<div className="card-header">
<StatusDot up={!!isUp} />
<span className="card-name">{target.name}</span>
<span className="card-type-badge">{target.type === "http" ? "HTTP" : "Command"}</span>
<span className="card-name" title={target.name}>
{target.name}
</span>
<span className="card-type-badge">{getTargetTypeDisplay(target.type)}</span>
</div>
<div className="card-status-bar">
<StatusBar samples={target.recentSamples} />

View File

@@ -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({
<div className="modal-title-row">
<StatusDot up={!!isUp} />
<h3 className="modal-title">{target.name}</h3>
<span className="card-type-badge">{target.type === "http" ? "HTTP" : "Command"}</span>
<span className="card-type-badge">{getTargetTypeDisplay(target.type)}</span>
</div>
<button className="modal-close-btn" onClick={onClose}>
&times;

View File

@@ -0,0 +1,10 @@
export const TARGET_TYPE_DISPLAY = {
http: "HTTP",
command: "CMD",
} as const;
export type TargetType = keyof typeof TARGET_TYPE_DISPLAY;
export function getTargetTypeDisplay(type: string): string {
return TARGET_TYPE_DISPLAY[type as TargetType] || type.toUpperCase();
}

View File

@@ -9,6 +9,7 @@
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
--dashboard-card-width: 280px;
}
* {
@@ -60,7 +61,7 @@ body {
}
.summary-card {
width: 280px;
width: var(--dashboard-card-width);
flex-shrink: 0;
padding: 20px;
border: 1px solid rgba(49, 83, 126, 0.12);
@@ -122,8 +123,33 @@ body {
}
.group-stats {
color: #61728a;
font-size: 0.85rem;
display: flex;
align-items: center;
gap: 6px;
}
.stat-badge {
padding: 2px 8px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 600;
min-width: 24px;
text-align: center;
}
.stat-badge-total {
background: rgba(53, 109, 210, 0.1);
color: #356dd2;
}
.stat-badge-up {
background: rgba(31, 191, 117, 0.1);
color: #1fbf75;
}
.stat-badge-down {
background: rgba(229, 72, 77, 0.1);
color: #e5484d;
}
.card-grid {
@@ -133,7 +159,7 @@ body {
}
.target-card {
width: 270px;
width: var(--dashboard-card-width);
flex-shrink: 0;
padding: 16px;
border: 1px solid rgba(49, 83, 126, 0.12);
@@ -579,23 +605,3 @@ body {
color: #94a3b8;
padding: 0 4px;
}
@media (max-width: 768px) {
.dashboard {
padding: 16px;
}
.modal-content {
width: 95vw;
max-height: 90vh;
}
.modal-charts {
flex-direction: column;
}
.time-range-picker {
flex-direction: column;
align-items: flex-start;
}
}