1
0

refactor: 全面重构前端样式,消除内联 style 和硬编码色值,统一 TDesign 规范

- 重写 styles.css:CSS 变量化可用率色阶、状态色类、工具类、安全选择器
- 组件改造:StatusDot/StatusBar/TargetDetailDrawer/GroupHeader 等改用 CSS 类和 Typography
- color-threshold 移除 getLatencyColor 死代码,保留 getAvailabilityProgressColor 返回 CSS 变量
- target-table-columns 状态列和延迟列切换为 CSS 类
- 新增 css-utility-classes spec,更新 4 个 main specs(probe/card/table/drawer)
- README 和 config.yaml 写入前端样式开发规范
This commit is contained in:
2026-05-12 12:42:11 +08:00
parent 3e8d01715f
commit 9f2b906063
19 changed files with 332 additions and 198 deletions

View File

@@ -1,4 +1,4 @@
import { Space, Tag } from "tdesign-react";
import { Tag, Typography } from "tdesign-react";
interface GroupHeaderProps {
name: string;
@@ -11,8 +11,8 @@ export function GroupHeader({ name, total, up, down }: GroupHeaderProps) {
const displayName = name === "default" ? "默认分组" : name;
return (
<Space align="center" size={8} style={{ marginBottom: 12 }}>
<h2 style={{ margin: 0, fontSize: "1.1rem", fontWeight: 600 }}>{displayName}</h2>
<div className="group-header">
<Typography.Title level="h4">{displayName}</Typography.Title>
<Tag theme="primary" variant="light" title="总数">
{total}
</Tag>
@@ -22,6 +22,6 @@ export function GroupHeader({ name, total, up, down }: GroupHeaderProps) {
<Tag theme="danger" variant="light" title="异常">
{down}
</Tag>
</Space>
</div>
);
}

View File

@@ -10,14 +10,11 @@ export function StatusBar({ samples }: StatusBarProps) {
blocks.push(
<span
key={i}
className="status-bar-block"
style={{ background: sample.up ? "var(--td-success-color)" : "var(--td-error-color)" }}
className={`status-bar-block ${sample.up ? "status-bar-block--up" : "status-bar-block--down"}`}
/>,
);
} else {
blocks.push(
<span key={i} className="status-bar-block" style={{ background: "var(--td-bg-color-component-disabled)" }} />,
);
blocks.push(<span key={i} className="status-bar-block status-bar-block--empty" />);
}
}

View File

@@ -3,12 +3,5 @@ interface StatusDotProps {
}
export function StatusDot({ up }: StatusDotProps) {
const color = up ? "var(--td-success-color)" : "var(--td-error-color)";
const shadow = up ? "var(--td-success-color)" : "var(--td-error-color)";
return (
<span
className="status-dot"
style={{ background: color, boxShadow: `0 0 0 6px color-mix(in srgb, ${shadow} 14%, transparent)` }}
/>
);
return <span className={`status-dot ${up ? "status-dot--up" : "status-dot--down"}`} />;
}

View File

@@ -15,7 +15,7 @@ export function SummaryCards({ summary }: SummaryCardsProps) {
];
return (
<Row gutter={16} style={{ marginBottom: 32 }}>
<Row gutter={16} className="summary-cards-row">
{cards.map((card) => (
<Col key={card.label} span={4}>
<Card bordered>

View File

@@ -25,7 +25,7 @@ export function TargetBoard({ targets, onTargetClick }: TargetBoardProps) {
});
return (
<Space direction="vertical" size={32} style={{ width: "100%" }}>
<Space direction="vertical" size={32} className="full-width">
{sortedGroups.map(([name, groupTargets]) => (
<TargetGroup key={name} name={name} targets={groupTargets} onTargetClick={onTargetClick} />
))}

View File

@@ -11,6 +11,9 @@ import {
Descriptions,
Skeleton,
PrimaryTable,
Divider,
Space,
Typography,
} from "tdesign-react";
import type { TabValue } from "tdesign-react";
import type { CheckResult, TargetStatus, TrendPoint, HistoryResponse } from "../../shared/api";
@@ -40,13 +43,6 @@ const TIME_SHORTCUTS = [
{ label: "7天", hours: 168, value: "7d" },
] as const;
const SECTION_TITLE_STYLE: React.CSSProperties = {
fontSize: 14,
fontWeight: 600,
color: "var(--td-text-color-primary)",
margin: "16px 0 8px",
};
const HISTORY_COLUMNS = [
{
colKey: "matched",
@@ -136,16 +132,16 @@ export function TargetDetailDrawer({
footer={false}
onClose={onClose}
header={
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Space align="center" size={8}>
<StatusDot up={!!isUp} />
<span style={{ fontWeight: 600 }}>{target.name}</span>
<Typography.Text strong>{target.name}</Typography.Text>
<Tag size="small" theme="primary" variant="light-outline">
{getTargetTypeDisplay(target.type)}
</Tag>
</div>
</Space>
}
>
<div style={{ marginBottom: 16 }}>
<Space direction="vertical" size={16} className="full-width">
<RadioGroup
theme="button"
variant="default-filled"
@@ -153,8 +149,6 @@ export function TargetDetailDrawer({
options={TIME_SHORTCUTS.map((s) => ({ label: s.label, value: s.value }))}
onChange={handleShortcut}
/>
</div>
<div style={{ marginBottom: 16 }}>
<DateRangePicker
mode="date"
enableTimePicker
@@ -162,51 +156,53 @@ export function TargetDetailDrawer({
valueType="YYYY-MM-DD HH:mm"
defaultTime={["00:00:00", "23:59:00"]}
timePickerProps={{ format: "HH:mm", steps: [1, 1, 60] }}
style={{ width: "100%" }}
className="full-width"
value={timeFrom && timeTo ? [timeFrom, timeTo] : undefined}
onChange={handleDateRangeChange}
/>
</div>
</Space>
<Tabs className="drawer-tabs" value={activeTab} onChange={(val: TabValue) => setActiveTab(val)}>
<Tabs.TabPanel value="overview" label="概览">
<h4 style={{ ...SECTION_TITLE_STYLE, marginTop: 0 }}></h4>
<Row gutter={16} style={{ marginBottom: 16 }}>
<Col span={3}>
<Statistic title="总检查" value={totalChecks} color="blue" />
</Col>
<Col span={3}>
<Statistic title="正常" value={upChecks} color="green" />
</Col>
<Col span={3}>
<Statistic title="异常" value={downChecks} color="red" />
</Col>
<Col span={3}>
<Statistic title="可用率" value={target.stats?.availability ?? 0} color="green" suffix="%" />
</Col>
</Row>
<Tabs value={activeTab} onChange={(val: TabValue) => setActiveTab(val)}>
<Tabs.TabPanel value="overview" label="概览" className="tab-panel-padded">
<Space direction="vertical" size={16} className="full-width">
<Divider align="left"></Divider>
<Row gutter={16}>
<Col span={3}>
<Statistic title="总检查" value={totalChecks} color="blue" />
</Col>
<Col span={3}>
<Statistic title="正常" value={upChecks} color="green" />
</Col>
<Col span={3}>
<Statistic title="异常" value={downChecks} color="red" />
</Col>
<Col span={3}>
<Statistic title="可用率" value={target.stats?.availability ?? 0} color="green" suffix="%" />
</Col>
</Row>
<h4 style={SECTION_TITLE_STYLE}></h4>
{trendLoading ? <Skeleton animation="gradient" /> : <TrendChart data={trendData} loading={false} />}
<Divider align="left"></Divider>
{trendLoading ? <Skeleton animation="gradient" /> : <TrendChart data={trendData} loading={false} />}
<h4 style={SECTION_TITLE_STYLE}></h4>
<StatusDonut up={upChecks} down={downChecks} />
<Divider align="left"></Divider>
<StatusDonut up={upChecks} down={downChecks} />
<h4 style={SECTION_TITLE_STYLE}></h4>
<Descriptions
items={[
{ label: "目标地址", content: target.target },
{ label: "检查间隔", content: target.interval },
{
label: "最新检查时间",
content: target.latestCheck ? new Date(target.latestCheck.timestamp).toLocaleString("zh-CN") : "-",
},
{ label: "状态详情", content: target.latestCheck?.statusDetail ?? "-" },
]}
/>
<Divider align="left"></Divider>
<Descriptions
items={[
{ label: "目标地址", content: target.target },
{ label: "检查间隔", content: target.interval },
{
label: "最新检查时间",
content: target.latestCheck ? new Date(target.latestCheck.timestamp).toLocaleString("zh-CN") : "-",
},
{ label: "状态详情", content: target.latestCheck?.statusDetail ?? "-" },
]}
/>
</Space>
</Tabs.TabPanel>
<Tabs.TabPanel value="history" label="记录">
<Tabs.TabPanel value="history" label="记录" className="tab-panel-padded">
<PrimaryTable
columns={HISTORY_COLUMNS}
data={historyData.items}

View File

@@ -30,7 +30,7 @@ export function TargetGroup({ name, targets, onTargetClick }: TargetGroupProps)
const target = row as TargetStatus;
return target.latestCheck?.matched === false ? "row-down" : "";
}}
style={{ cursor: "pointer" }}
className="clickable-table"
/>
</div>
);