187 lines
7.0 KiB
TypeScript
187 lines
7.0 KiB
TypeScript
import {
|
|
TrendingUp,
|
|
TrendingDown,
|
|
Server,
|
|
Wifi,
|
|
CheckCircle,
|
|
PackageCheck,
|
|
Wrench,
|
|
Target,
|
|
Clock,
|
|
Upload
|
|
} from "lucide-react";
|
|
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell } from "recharts";
|
|
|
|
interface MetricCardProps {
|
|
label: string;
|
|
value: string;
|
|
trend?: "up" | "down";
|
|
trendValue?: string;
|
|
color?: string;
|
|
icon: React.ElementType;
|
|
}
|
|
|
|
function MetricCard({ label, value, trend, trendValue, color = "#1890FF", icon: Icon }: MetricCardProps) {
|
|
return (
|
|
<div className="bg-white p-6 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="text-sm mb-2" style={{ color: 'rgba(0, 0, 0, 0.65)' }}>{label}</div>
|
|
<div className="text-3xl font-semibold mb-2">{value}</div>
|
|
{trend && trendValue && (
|
|
<div className="flex items-center gap-1" style={{ color: trend === "up" ? "#52C41A" : "#FF4D4F" }}>
|
|
{trend === "up" ? <TrendingUp size={14} /> : <TrendingDown size={14} />}
|
|
<span className="text-sm">{trendValue}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="w-12 h-12 rounded-lg flex items-center justify-center" style={{ backgroundColor: `${color}15` }}>
|
|
<Icon size={24} style={{ color }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface TaskItemProps {
|
|
deviceSN: string;
|
|
description: string;
|
|
time?: string;
|
|
}
|
|
|
|
function TaskItem({ deviceSN, description, time }: TaskItemProps) {
|
|
return (
|
|
<div className="flex items-start justify-between py-3" style={{ borderBottom: '1px solid #F0F0F0' }}>
|
|
<div className="flex-1">
|
|
<div className="text-sm mb-1">{deviceSN}</div>
|
|
<div className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>{description}</div>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
{time && <span className="text-xs" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>{time}</span>}
|
|
<button className="text-sm" style={{ color: '#1890FF' }}>处理</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function Dashboard() {
|
|
const metrics = [
|
|
{ label: "设备总数", value: "5,234", trend: "up" as const, trendValue: "+5.2%", color: "#1890FF", icon: Server },
|
|
{ label: "在线设备", value: "4,856", trend: "up" as const, trendValue: "+2.8%", color: "#52C41A", icon: Wifi },
|
|
{ label: "已激活", value: "4,912", trend: "up" as const, trendValue: "+1.5%", color: "#1890FF", icon: CheckCircle },
|
|
{ label: "有新版本", value: "156", color: "#722ED1", icon: PackageCheck },
|
|
{ label: "维修中", value: "23", trend: "down" as const, trendValue: "-12.3%", color: "#FF4D4F", icon: Wrench },
|
|
{ label: "待校准", value: "56", color: "#FA8C16", icon: Target },
|
|
{ label: "授权即将到期", value: "45", color: "#FAAD14", icon: Clock },
|
|
{ label: "升级中", value: "8", color: "#13C2C2", icon: Upload },
|
|
];
|
|
|
|
const deviceStatusData = [
|
|
{ name: "在线", value: 4856, color: "#52C41A" },
|
|
{ name: "离线", value: 378, color: "#FF4D4F" },
|
|
{ name: "维修", value: 23, color: "#FAAD14" },
|
|
{ name: "报废", value: 77, color: "#8C8C8C" },
|
|
];
|
|
|
|
const taskGroups = [
|
|
{
|
|
title: "待校准设备",
|
|
count: 12,
|
|
tasks: [
|
|
{ deviceSN: "SN2024030801", description: "温度传感器校准到期", time: "2小时前" },
|
|
{ deviceSN: "SN2024030802", description: "压力传感器校准到期", time: "3小时前" },
|
|
{ deviceSN: "SN2024030803", description: "湿度传感器校准到期", time: "5小时前" },
|
|
],
|
|
},
|
|
{
|
|
title: "固件升级通知",
|
|
count: 8,
|
|
tasks: [
|
|
{ deviceSN: "SN2024030710", description: "固件版本v2.3.5可用", time: "1天前" },
|
|
{ deviceSN: "SN2024030711", description: "固件版本v2.3.5可用", time: "1天前" },
|
|
],
|
|
},
|
|
{
|
|
title: "待授权审批",
|
|
count: 15,
|
|
tasks: [
|
|
{ deviceSN: "SN2024030620", description: "设备授权申请待审批", time: "30分钟前" },
|
|
{ deviceSN: "SN2024030621", description: "设备授权申请待审批", time: "1小时前" },
|
|
{ deviceSN: "SN2024030622", description: "设备授权延期申请", time: "2小时前" },
|
|
],
|
|
},
|
|
{
|
|
title: "维修工单",
|
|
count: 5,
|
|
tasks: [
|
|
{ deviceSN: "SN2024030530", description: "设备故障报修", time: "4小时前" },
|
|
{ deviceSN: "SN2024030531", description: "定期维护到期", time: "6小时前" },
|
|
],
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="p-6">
|
|
{/* Page Header */}
|
|
<div className="mb-6">
|
|
<h2 className="text-2xl font-semibold mb-1">首页</h2>
|
|
<p className="text-sm" style={{ color: 'rgba(0, 0, 0, 0.45)' }}>设备管理数据总览</p>
|
|
</div>
|
|
|
|
{/* Metric Cards */}
|
|
<div className="grid grid-cols-4 gap-6 mb-6">
|
|
{metrics.map((metric, index) => (
|
|
<MetricCard key={index} {...metric} />
|
|
))}
|
|
</div>
|
|
|
|
{/* Device Status Chart */}
|
|
<div className="bg-white p-6 rounded-lg mb-6" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
|
|
<h3 className="text-lg font-semibold mb-6">设备状态分布</h3>
|
|
<ResponsiveContainer width="100%" height={300}>
|
|
<BarChart data={deviceStatusData} layout="vertical">
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#F0F0F0" />
|
|
<XAxis type="number" />
|
|
<YAxis dataKey="name" type="category" width={60} />
|
|
<Tooltip />
|
|
<Bar dataKey="value" radius={[0, 4, 4, 0]}>
|
|
{deviceStatusData.map((entry) => (
|
|
<Cell key={`bar-cell-${entry.name}`} fill={entry.color} />
|
|
))}
|
|
</Bar>
|
|
</BarChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
|
|
{/* Pending Tasks */}
|
|
<div className="bg-white p-6 rounded-lg" style={{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }}>
|
|
<h3 className="text-lg font-semibold mb-6">待处理任务</h3>
|
|
<div className="grid grid-cols-2 gap-6">
|
|
{taskGroups.map((group, groupIndex) => (
|
|
<div key={groupIndex}>
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h4 className="text-base font-medium">{group.title}</h4>
|
|
<span
|
|
className="px-2 py-1 rounded text-xs"
|
|
style={{ backgroundColor: '#F0F2F5', color: 'rgba(0, 0, 0, 0.65)' }}
|
|
>
|
|
{group.count}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
{group.tasks.map((task, taskIndex) => (
|
|
<TaskItem key={taskIndex} {...task} />
|
|
))}
|
|
</div>
|
|
{group.tasks.length < group.count && (
|
|
<button className="w-full mt-3 text-center text-sm" style={{ color: '#1890FF' }}>
|
|
查看全部 {group.count} 项
|
|
</button>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |