- 新增 PageShell/PageTabs/ContentPanel/PageLoading/PageEmpty 页面骨架 - index.css 设计令牌文档化,布局令牌与纯色主按钮(去除渐变粉彩) - 全站路由页接入 PageShell,云账单三 Tab 为标杆 - 主题体系:跟随系统 + 主流/开发者分组,新增 Dracula/Tokyo Night,One Dark 暖灰差异化 - 登录页/侧栏/顶栏去渐变光球,企业克制风 - 修复 AIAlertAnalyzer JSX 与构建依赖导入问题
77 lines
3.8 KiB
TypeScript
77 lines
3.8 KiB
TypeScript
// 双线图表(网卡带宽 rx+tx)// Logan.Li
|
||
import { Network } from 'lucide-react';
|
||
import { Card, CardContent } from '@/components/ui/card';
|
||
import {
|
||
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip,
|
||
ResponsiveContainer, Brush,
|
||
} from 'recharts';
|
||
import type { AggType, BucketPoint } from './types';
|
||
|
||
interface DualLineChartProps {
|
||
title: string;
|
||
currentRx: string;
|
||
currentTx: string;
|
||
rxData: BucketPoint[];
|
||
txData: BucketPoint[];
|
||
aggType: AggType;
|
||
unit: string;
|
||
}
|
||
|
||
export function DualLineChart({
|
||
title, currentRx, currentTx, rxData, txData, aggType, unit,
|
||
}: DualLineChartProps) {
|
||
const chartData = rxData.map((d, i) => ({
|
||
time: d.time,
|
||
timestamp: d.timestamp,
|
||
rx: d[aggType] ?? 0,
|
||
tx: txData[i]?.[aggType] ?? 0,
|
||
}));
|
||
const rxGradId = `grad-rx-${title.replace(/\s/g, '')}`;
|
||
const txGradId = `grad-tx-${title.replace(/\s/g, '')}`;
|
||
return (
|
||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||
<CardContent className="p-3">
|
||
<div className="flex items-center justify-between mb-1">
|
||
<div className="flex items-center gap-1.5">
|
||
<Network className="h-4 w-4 dg-text-blue" />
|
||
<span className="text-xs text-[var(--dg-text-primary)] font-medium">{title}</span>
|
||
</div>
|
||
<div className="flex items-center gap-3 text-xs">
|
||
<span><span className="inline-block w-2.5 h-2.5 rounded-sm bg-blue-400 mr-1 align-middle" />接收 <b className="text-blue-300">{currentRx}</b></span>
|
||
<span><span className="inline-block w-2.5 h-2.5 rounded-sm bg-emerald-400 mr-1 align-middle" />发送 <b className="dg-text-green">{currentTx}</b></span>
|
||
</div>
|
||
</div>
|
||
<div className="h-56">
|
||
{chartData.length === 0 ? (
|
||
<div className="h-full flex items-center justify-center text-xs text-[var(--dg-text-muted)] border border-dashed border-[var(--dg-border)] rounded-md">暂无数据</div>
|
||
) : (
|
||
<ResponsiveContainer width="100%" height="100%">
|
||
<AreaChart data={chartData} margin={{ top: 2, right: 2, bottom: 0, left: -10 }}>
|
||
<defs>
|
||
<linearGradient id={rxGradId} x1="0" y1="0" x2="0" y2="1">
|
||
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.3} />
|
||
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0} />
|
||
</linearGradient>
|
||
<linearGradient id={txGradId} x1="0" y1="0" x2="0" y2="1">
|
||
<stop offset="5%" stopColor="#10b981" stopOpacity={0.3} />
|
||
<stop offset="95%" stopColor="#10b981" stopOpacity={0} />
|
||
</linearGradient>
|
||
</defs>
|
||
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" strokeOpacity={0.5} />
|
||
<XAxis dataKey="time" hide />
|
||
<YAxis hide domain={[0, 'auto']} />
|
||
<Tooltip contentStyle={{ backgroundColor: '#0f172a', border: '1px solid #1e293b', borderRadius: '4px', color: '#f8fafc', fontSize: '11px', padding: '4px 8px' }}
|
||
formatter={(v: number, name: string) => [`${v.toFixed(2)} ${unit}`, name === 'rx' ? '接收' : '发送']}
|
||
labelFormatter={(label) => `时间: ${label}`} />
|
||
<Area type="monotone" dataKey="rx" stroke="#3b82f6" strokeWidth={1.5} fill={`url(#${rxGradId})`} name="rx" />
|
||
<Area type="monotone" dataKey="tx" stroke="#10b981" strokeWidth={1.5} fill={`url(#${txGradId})`} name="tx" />
|
||
<Brush dataKey="time" height={24} stroke="#334155" fill="#0f172a"
|
||
startIndex={Math.max(0, chartData.length - Math.min(20, chartData.length))} />
|
||
</AreaChart>
|
||
</ResponsiveContainer>
|
||
)}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
} |