refactor: 优化卡片显示一致性与可扩展性
- 统一 Summary Cards 和 Target Cards 宽度为 280px(CSS 变量控制) - 分组统计改为徽章展示(纯数字 + 颜色区分) - 目标名称添加 title 属性支持显示完整名称 - 建立类型映射系统,Command 显示为 CMD,支持扩展 - 移除移动端响应式代码,简化维护 - 新增 target-type-display 能力规格 - 更新 card-dashboard 和 target-detail-modal 规格
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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}>
|
||||
×
|
||||
|
||||
10
src/web/constants/target-type-display.ts
Normal file
10
src/web/constants/target-type-display.ts
Normal 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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user