|
|
@@ -12,10 +12,12 @@ import {
|
|
|
Descriptions,
|
|
|
Divider,
|
|
|
Drawer,
|
|
|
+ Empty,
|
|
|
Layout,
|
|
|
Menu,
|
|
|
Progress,
|
|
|
Row,
|
|
|
+ Segmented,
|
|
|
Select,
|
|
|
Space,
|
|
|
Statistic,
|
|
|
@@ -24,29 +26,36 @@ import {
|
|
|
Tabs,
|
|
|
Tag,
|
|
|
Timeline,
|
|
|
- Tooltip as AntdTooltip
|
|
|
+ Tooltip as AntdTooltip,
|
|
|
} from "antd"
|
|
|
import {
|
|
|
Activity,
|
|
|
AlertTriangle,
|
|
|
BellRing,
|
|
|
+ ClipboardList,
|
|
|
Database,
|
|
|
Droplets,
|
|
|
Factory,
|
|
|
Flame,
|
|
|
Gauge,
|
|
|
Layers3,
|
|
|
- Map,
|
|
|
+ LineChartIcon,
|
|
|
+ MapIcon,
|
|
|
Moon,
|
|
|
Settings2,
|
|
|
ShieldCheck,
|
|
|
Sun,
|
|
|
+ Table2,
|
|
|
TrendingUp,
|
|
|
+ Users,
|
|
|
Waves
|
|
|
} from 'lucide-react'
|
|
|
import EChart from "@/components/echarts"
|
|
|
import dayjs from "dayjs"
|
|
|
-import {EChartsOption} from "echarts";
|
|
|
+import "dayjs/locale/zh-cn"
|
|
|
+import zhCN from "antd/es/locale/zh_CN"
|
|
|
+
|
|
|
+dayjs.locale("zh-cn")
|
|
|
|
|
|
const { Header, Sider, Content } = Layout
|
|
|
const { RangePicker } = DatePicker
|
|
|
@@ -190,6 +199,7 @@ function StatusBadge({ s }: { s: Alert["status"] }) {
|
|
|
处置中: "processing",
|
|
|
已闭环: "success",
|
|
|
} as const
|
|
|
+ // @ts-expect-error antd types for Badge status
|
|
|
return <Badge status={status[s]} text={s} />
|
|
|
}
|
|
|
|
|
|
@@ -199,7 +209,10 @@ export default function Page() {
|
|
|
const [selectedMenu, setSelectedMenu] = useState("overview")
|
|
|
const [drawerOpen, setDrawerOpen] = useState(false)
|
|
|
const [selectedAlert, setSelectedAlert] = useState<Alert | null>(null)
|
|
|
- const [isDark, setIsDark] = useState(true)
|
|
|
+ const [isDark, setIsDark] = useState(false) // 默认白色主题
|
|
|
+ const [assetTab, setAssetTab] = useState("archive")
|
|
|
+ const [monitorTab, setMonitorTab] = useState("summary")
|
|
|
+ const [alertTab, setAlertTab] = useState("current")
|
|
|
|
|
|
const totals = useMemo(() => {
|
|
|
const sum = { 燃气: 0, 供水: 0, 排水: 0 } as Record<Facility["industry"], number>
|
|
|
@@ -226,6 +239,11 @@ export default function Page() {
|
|
|
{ key: "monitor", icon: <Activity size={18} />, label: "运行监测管理" },
|
|
|
{ key: "alert", icon: <BellRing size={18} />, label: "预警处置管理" },
|
|
|
{ type: "divider" as const },
|
|
|
+ { key: "devices", icon: <Gauge size={18} />, label: "设备管理" },
|
|
|
+ { key: "reports", icon: <ClipboardList size={18} />, label: "数据报表" },
|
|
|
+ { key: "incidents", icon: <Users size={18} />, label: "事件中心" },
|
|
|
+ { key: "bigscreen", icon: <LineChartIcon size={18} />, label: "数据大屏(示意)" },
|
|
|
+ { type: "divider" as const },
|
|
|
{ key: "settings", icon: <Settings2 size={18} />, label: "设置" },
|
|
|
]
|
|
|
|
|
|
@@ -244,9 +262,9 @@ export default function Page() {
|
|
|
itemStyle: { borderRadius: 6, borderColor: "#fff", borderWidth: 2 },
|
|
|
data: ["燃气", "供水", "排水"].map((ind) => ({
|
|
|
name: ind,
|
|
|
- value: Object.values(facilities)
|
|
|
- .filter((f) => (f as Facility).industry === ind)
|
|
|
- .reduce((acc, f) => acc + (f as Facility).count, 0),
|
|
|
+ value: facilities
|
|
|
+ .filter((f) => f.industry === ind)
|
|
|
+ .reduce((acc, f) => acc + f.count, 0),
|
|
|
})),
|
|
|
},
|
|
|
],
|
|
|
@@ -254,22 +272,19 @@ export default function Page() {
|
|
|
[facilities]
|
|
|
)
|
|
|
|
|
|
- const deviceBarOption = useMemo(
|
|
|
- () => {
|
|
|
- const types = Array.from(new Set(devices.map((d) => d.type)))
|
|
|
- const byType = types.map((t) => devices.filter((d) => d.type === t).length)
|
|
|
- return {
|
|
|
- title: { text: "监测设备类型规模", left: "center", textStyle: { fontSize: 14 } },
|
|
|
- tooltip: { trigger: "axis" },
|
|
|
- xAxis: { type: "category", data: types, axisLabel: { rotate: 20 } },
|
|
|
- yAxis: { type: "value" },
|
|
|
- grid: { left: 40, right: 10, bottom: 50, top: 40 },
|
|
|
- color: [primary],
|
|
|
- series: [{ type: "bar", data: byType, barWidth: "50%", itemStyle: { borderRadius: [4, 4, 0, 0] } }],
|
|
|
- } as const
|
|
|
- },
|
|
|
- [devices]
|
|
|
- )
|
|
|
+ const deviceBarOption = useMemo(() => {
|
|
|
+ const types = Array.from(new Set(devices.map((d) => d.type)))
|
|
|
+ const byType = types.map((t) => devices.filter((d) => d.type === t).length)
|
|
|
+ return {
|
|
|
+ title: { text: "监测设备类型规模", left: "center", textStyle: { fontSize: 14 } },
|
|
|
+ tooltip: { trigger: "axis" },
|
|
|
+ xAxis: { type: "category", data: types, axisLabel: { rotate: 20 } },
|
|
|
+ yAxis: { type: "value" },
|
|
|
+ grid: { left: 40, right: 10, bottom: 50, top: 40 },
|
|
|
+ color: [primary],
|
|
|
+ series: [{ type: "bar", data: byType, barWidth: "50%", itemStyle: { borderRadius: [4, 4, 0, 0] } }],
|
|
|
+ } as const
|
|
|
+ }, [devices])
|
|
|
|
|
|
const alertTrendOption = useMemo(() => {
|
|
|
const days = 14
|
|
|
@@ -328,31 +343,27 @@ export default function Page() {
|
|
|
[riskDist]
|
|
|
)
|
|
|
|
|
|
- const efficiencyPieOption = useMemo(
|
|
|
- () => {
|
|
|
- const total = alerts.length
|
|
|
- const closed = alerts.filter((a) => a.status === "已闭环").length
|
|
|
- const correct = Math.floor(closed * (0.85 + Math.random() * 0.1)) // 假定人工复核正确率
|
|
|
- const wrong = Math.max(closed - correct, 0)
|
|
|
- return {
|
|
|
- title: { text: "预警正确性(闭环内)", left: "center", textStyle: { fontSize: 14 } },
|
|
|
- tooltip: { trigger: "item", formatter: "{b}: {c} ({d}%)" },
|
|
|
- legend: { bottom: 0 },
|
|
|
- color: [ok, danger],
|
|
|
- series: [
|
|
|
- {
|
|
|
- type: "pie",
|
|
|
- radius: ["35%", "60%"],
|
|
|
- data: [
|
|
|
- { name: "正确预警", value: correct },
|
|
|
- { name: "误报", value: wrong },
|
|
|
- ],
|
|
|
- },
|
|
|
- ],
|
|
|
- } as const
|
|
|
- },
|
|
|
- [alerts]
|
|
|
- )
|
|
|
+ const efficiencyPieOption = useMemo(() => {
|
|
|
+ const closed = alerts.filter((a) => a.status === "已闭环").length
|
|
|
+ const correct = Math.floor(closed * (0.85 + Math.random() * 0.1)) // 假定人工复核正确率
|
|
|
+ const wrong = Math.max(closed - correct, 0)
|
|
|
+ return {
|
|
|
+ title: { text: "预警正确性(闭环内)", left: "center", textStyle: { fontSize: 14 } },
|
|
|
+ tooltip: { trigger: "item", formatter: "{b}: {c} ({d}%)" },
|
|
|
+ legend: { bottom: 0 },
|
|
|
+ color: [ok, danger],
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: "pie",
|
|
|
+ radius: ["35%", "60%"],
|
|
|
+ data: [
|
|
|
+ { name: "正确预警", value: correct },
|
|
|
+ { name: "误报", value: wrong },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ } as const
|
|
|
+ }, [alerts])
|
|
|
|
|
|
const radarCapacityOption = useMemo(
|
|
|
() => ({
|
|
|
@@ -417,6 +428,37 @@ export default function Page() {
|
|
|
},
|
|
|
]
|
|
|
|
|
|
+ const deviceColumns: TableColumnsType<Device> = [
|
|
|
+ { title: "行业", dataIndex: "industry", key: "industry", width: 90 },
|
|
|
+ { title: "类型", dataIndex: "type", key: "type", width: 100 },
|
|
|
+ {
|
|
|
+ title: "状态",
|
|
|
+ dataIndex: "status",
|
|
|
+ key: "status",
|
|
|
+ width: 100,
|
|
|
+ render: (s) => <Badge status={s === "在线" ? "success" : "default"} text={s} />,
|
|
|
+ filters: [
|
|
|
+ { text: "在线", value: "在线" },
|
|
|
+ { text: "离线", value: "离线" },
|
|
|
+ ],
|
|
|
+ onFilter: (v, r) => r.status === v,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "风险",
|
|
|
+ dataIndex: "risk",
|
|
|
+ key: "risk",
|
|
|
+ width: 100,
|
|
|
+ render: (r) => <Tag color={r === "高" ? danger : r === "中" ? warn : ok}>{r}</Tag>,
|
|
|
+ filters: [
|
|
|
+ { text: "高", value: "高" },
|
|
|
+ { text: "中", value: "中" },
|
|
|
+ { text: "低", value: "低" },
|
|
|
+ ],
|
|
|
+ onFilter: (v, r) => r.risk === v,
|
|
|
+ },
|
|
|
+ { title: "编号", dataIndex: "id", key: "id" },
|
|
|
+ ]
|
|
|
+
|
|
|
const alertLevelColor = (lvl: Alert["level"]) =>
|
|
|
lvl === "I级" ? danger : lvl === "II级" ? warn : lvl === "III级" ? "#f97316" : ok
|
|
|
|
|
|
@@ -449,7 +491,7 @@ export default function Page() {
|
|
|
]
|
|
|
|
|
|
// Map markers for device distribution
|
|
|
- const markers = devices.slice(0, 120).map((d, i) => ({
|
|
|
+ const markers = devices.slice(0, 120).map((d) => ({
|
|
|
id: d.id,
|
|
|
top: `${Math.floor(d.lat * 85) + 5}%`,
|
|
|
left: `${Math.floor(d.lng * 90) + 5}%`,
|
|
|
@@ -482,7 +524,7 @@ export default function Page() {
|
|
|
)
|
|
|
|
|
|
return (
|
|
|
- <ConfigProvider theme={themeTokens}>
|
|
|
+ <ConfigProvider locale={zhCN} theme={themeTokens}>
|
|
|
<Layout style={{ minHeight: "100vh" }}>
|
|
|
<Sider collapsible collapsed={collapsed} onCollapse={setCollapsed} width={240} theme={isDark ? "dark" : "light"}>
|
|
|
<div className="flex items-center gap-2 px-4 py-3">
|
|
|
@@ -525,11 +567,7 @@ export default function Page() {
|
|
|
style={{ width: 120 }}
|
|
|
/>
|
|
|
<RangePicker size="small" />
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- onClick={() => setIsDark((v) => !v)}
|
|
|
- icon={isDark ? <Sun size={14} /> : <Moon size={14} />}
|
|
|
- >
|
|
|
+ <Button size="small" onClick={() => setIsDark((v) => !v)} icon={isDark ? <Sun size={14} /> : <Moon size={14} />}>
|
|
|
{isDark ? "暗色" : "白色"}
|
|
|
</Button>
|
|
|
</div>
|
|
|
@@ -564,7 +602,7 @@ export default function Page() {
|
|
|
</Col>
|
|
|
<Col xs={24} md={12} lg={8}>
|
|
|
<Card>
|
|
|
- <EChart option={deviceBarOption as EChartsOption} />
|
|
|
+ <EChart option={deviceBarOption} />
|
|
|
</Card>
|
|
|
</Col>
|
|
|
<Col xs={24} md={24} lg={8}>
|
|
|
@@ -577,7 +615,7 @@ export default function Page() {
|
|
|
<Row gutter={[16, 16]}>
|
|
|
<Col xs={24} md={12}>
|
|
|
<Card>
|
|
|
- <EChart option={alertTrendOption as EChartsOption} />
|
|
|
+ <EChart option={alertTrendOption} />
|
|
|
</Card>
|
|
|
</Col>
|
|
|
<Col xs={24} md={12}>
|
|
|
@@ -594,7 +632,6 @@ export default function Page() {
|
|
|
alt="城市简化地图"
|
|
|
className="absolute inset-0 w-full h-full object-cover opacity-90"
|
|
|
/>
|
|
|
- {/* Markers */}
|
|
|
{markers.map((m) => (
|
|
|
<AntdTooltip key={m.id} title={m.title}>
|
|
|
<div
|
|
|
@@ -603,11 +640,9 @@ export default function Page() {
|
|
|
/>
|
|
|
</AntdTooltip>
|
|
|
))}
|
|
|
-
|
|
|
- {/* Legend */}
|
|
|
<div className="absolute right-3 top-3 bg-white/90 backdrop-blur rounded-md p-2 text-xs shadow">
|
|
|
<div className="font-medium mb-1 flex items-center gap-1">
|
|
|
- <Map size={14} /> 覆盖与风险
|
|
|
+ <MapIcon size={14} /> 覆盖与风险
|
|
|
</div>
|
|
|
<div className="flex items-center gap-3">
|
|
|
<div className="flex items-center gap-1">
|
|
|
@@ -629,7 +664,8 @@ export default function Page() {
|
|
|
{selectedMenu === "asset" && (
|
|
|
<section className="space-y-4">
|
|
|
<Tabs
|
|
|
- defaultActiveKey="archive"
|
|
|
+ activeKey={assetTab}
|
|
|
+ onChange={setAssetTab}
|
|
|
items={[
|
|
|
{ key: "archive", label: "基础档案" },
|
|
|
{ key: "run", label: "运行信息" },
|
|
|
@@ -637,141 +673,190 @@ export default function Page() {
|
|
|
{ key: "emergency", label: "应急保障" },
|
|
|
]}
|
|
|
/>
|
|
|
- <Row gutter={[16, 16]}>
|
|
|
- <Col xs={24} lg={14}>
|
|
|
- <Card title="设施基础档案">
|
|
|
- <Table
|
|
|
- size="small"
|
|
|
- rowKey="id"
|
|
|
- columns={facilityColumns}
|
|
|
- dataSource={facilities}
|
|
|
- pagination={{ pageSize: 8 }}
|
|
|
- scroll={{ x: 700 }}
|
|
|
- />
|
|
|
- </Card>
|
|
|
- </Col>
|
|
|
- <Col xs={24} lg={10}>
|
|
|
- <Card title="行业重点指标">
|
|
|
- <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
|
- <div>
|
|
|
- <div className="text-xs text-slate-500 mb-1">平均设施年限</div>
|
|
|
- <Statistic value={Math.round(facilities.reduce((a, b) => a + b.age, 0) / facilities.length)} suffix="年" />
|
|
|
- <Progress percent={Math.min(100, Math.round((facilities.filter((f) => f.age >= 20).length / facilities.length) * 100))} size="small" />
|
|
|
- <div className="text-xs text-slate-500 mt-1">20年以上占比</div>
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <div className="text-xs text-slate-500 mb-1">重点设施数量</div>
|
|
|
- <Statistic value={facilities.filter((f) => f.type.includes("主") || f.type.includes("厂")).reduce((a, b) => a + b.count, 0)} />
|
|
|
- <div className="text-xs text-slate-500 mt-1">主干/厂站等关键设施总量</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </Card>
|
|
|
- <Card className="mt-4" title="分布热点(示意)">
|
|
|
- <EChart
|
|
|
- option={{
|
|
|
- title: { text: "区域设施数量", left: "center", textStyle: { fontSize: 14 } },
|
|
|
- xAxis: { type: "category", data: ["中心城区", "东区", "西区", "南区", "北区"] },
|
|
|
- yAxis: { type: "value" },
|
|
|
- grid: { left: 40, right: 10, bottom: 20, top: 40 },
|
|
|
- color: [primary],
|
|
|
- series: [
|
|
|
- {
|
|
|
- type: "bar",
|
|
|
- data: ["中心城区", "东区", "西区", "南区", "北区"].map(
|
|
|
- (r) => facilities.filter((f) => f.region === r).reduce((a, b) => a + b.count, 0)
|
|
|
- ),
|
|
|
- barWidth: "50%",
|
|
|
- },
|
|
|
- ],
|
|
|
- }}
|
|
|
- />
|
|
|
- </Card>
|
|
|
- </Col>
|
|
|
- </Row>
|
|
|
|
|
|
- <Row gutter={[16, 16]}>
|
|
|
- <Col xs={24} md={12}>
|
|
|
- <Card title="日常供应能力(示意)">
|
|
|
- <EChart
|
|
|
- option={{
|
|
|
- tooltip: { trigger: "axis" },
|
|
|
- legend: { bottom: 0 },
|
|
|
- grid: { left: 40, right: 10, top: 20, bottom: 40 },
|
|
|
- xAxis: { type: "category", data: Array.from({ length: 24 }).map((_, i) => `${i}:00`) },
|
|
|
- yAxis: { type: "value" },
|
|
|
- color: [primary, "#f59e0b"],
|
|
|
- series: [
|
|
|
- { name: "供水(万m³/h)", type: "line", smooth: true, data: Array.from({ length: 24 }).map(() => 50 + Math.random() * 20) },
|
|
|
- { name: "燃气(万m³/h)", type: "line", smooth: true, data: Array.from({ length: 24 }).map(() => 30 + Math.random() * 15) },
|
|
|
- ],
|
|
|
- }}
|
|
|
- />
|
|
|
- </Card>
|
|
|
- </Col>
|
|
|
- <Col xs={24} md={12}>
|
|
|
- <Card title="管理单位信息">
|
|
|
- <Descriptions size="small" column={1} bordered>
|
|
|
- <Descriptions.Item label="燃气管理单位">市燃气集团、区属燃气公司等(共 6 家)</Descriptions.Item>
|
|
|
- <Descriptions.Item label="供水管理单位">市自来水公司、二供物业等(共 9 家)</Descriptions.Item>
|
|
|
- <Descriptions.Item label="排水管理单位">城排中心、各区运营公司(共 7 家)</Descriptions.Item>
|
|
|
- <Descriptions.Item label="产权分布">市属 60%,区属 30%,社会 10%</Descriptions.Item>
|
|
|
- </Descriptions>
|
|
|
- <Divider className="my-3" />
|
|
|
- <Timeline
|
|
|
- items={[
|
|
|
- { color: "green", children: "制度更新:设施巡检标准(本月)" },
|
|
|
- { color: "blue", children: "完成年度普查 80%" },
|
|
|
- { color: "orange", children: "外包维保单位进场(上周)" },
|
|
|
- ]}
|
|
|
- />
|
|
|
- </Card>
|
|
|
- </Col>
|
|
|
- </Row>
|
|
|
+ {assetTab === "archive" && (
|
|
|
+ <>
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col xs={24} lg={14}>
|
|
|
+ <Card title="设施基础档案">
|
|
|
+ <Table
|
|
|
+ size="small"
|
|
|
+ rowKey="id"
|
|
|
+ columns={facilityColumns}
|
|
|
+ dataSource={facilities}
|
|
|
+ pagination={{ pageSize: 8 }}
|
|
|
+ scroll={{ x: 700 }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} lg={10}>
|
|
|
+ <Card title="行业重点指标">
|
|
|
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
|
+ <div>
|
|
|
+ <div className="text-xs text-slate-500 mb-1">平均设施年限</div>
|
|
|
+ <Statistic value={Math.round(facilities.reduce((a, b) => a + b.age, 0) / facilities.length)} suffix="年" />
|
|
|
+ <Progress percent={Math.min(100, Math.round((facilities.filter((f) => f.age >= 20).length / facilities.length) * 100))} size="small" />
|
|
|
+ <div className="text-xs text-slate-500 mt-1">20年以上占比</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div className="text-xs text-slate-500 mb-1">重点设施数量</div>
|
|
|
+ <Statistic value={facilities.filter((f) => f.type.includes("主") || f.type.includes("厂")).reduce((a, b) => a + b.count, 0)} />
|
|
|
+ <div className="text-xs text-slate-500 mt-1">主干/厂站等关键设施总量</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ <Card className="mt-4" title="分布热点(示意)">
|
|
|
+ <EChart
|
|
|
+ option={{
|
|
|
+ title: { text: "区域设施数量", left: "center", textStyle: { fontSize: 14 } },
|
|
|
+ xAxis: { type: "category", data: ["中心城区", "东区", "西区", "南区", "北区"] },
|
|
|
+ yAxis: { type: "value" },
|
|
|
+ grid: { left: 40, right: 10, bottom: 20, top: 40 },
|
|
|
+ color: [primary],
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: "bar",
|
|
|
+ data: ["中心城区", "东区", "西区", "南区", "北区"].map(
|
|
|
+ (r) => facilities.filter((f) => f.region === r).reduce((a, b) => a + b.count, 0)
|
|
|
+ ),
|
|
|
+ barWidth: "50%",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
|
|
|
- <Row gutter={[16, 16]}>
|
|
|
- <Col xs={24} md={12}>
|
|
|
- <Card title="应急资源配置">
|
|
|
- <div className="grid grid-cols-2 gap-4">
|
|
|
- <Card size="small">
|
|
|
- <div className="text-xs text-slate-500">物资仓库</div>
|
|
|
- <div className="text-xl font-semibold mt-1">12 处</div>
|
|
|
- <Progress percent={78} status="active" size="small" />
|
|
|
- <div className="text-xs text-slate-500 mt-1">库存充足度</div>
|
|
|
+ {assetTab === "run" && (
|
|
|
+ <>
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card title="日常供应能力(示意)">
|
|
|
+ <EChart
|
|
|
+ option={{
|
|
|
+ tooltip: { trigger: "axis" },
|
|
|
+ legend: { bottom: 0 },
|
|
|
+ grid: { left: 40, right: 10, top: 20, bottom: 40 },
|
|
|
+ xAxis: { type: "category", data: Array.from({ length: 24 }).map((_, i) => `${i}:00`) },
|
|
|
+ yAxis: { type: "value" },
|
|
|
+ color: [primary, "#f59e0b"],
|
|
|
+ series: [
|
|
|
+ { name: "供水(万m³/h)", type: "line", smooth: true, data: Array.from({ length: 24 }).map(() => 50 + Math.random() * 20) },
|
|
|
+ { name: "燃气(万m³/h)", type: "line", smooth: true, data: Array.from({ length: 24 }).map(() => 30 + Math.random() * 15) },
|
|
|
+ ],
|
|
|
+ }}
|
|
|
+ />
|
|
|
</Card>
|
|
|
- <Card size="small">
|
|
|
- <div className="text-xs text-slate-500">抢修队伍</div>
|
|
|
- <div className="text-xl font-semibold mt-1">26 支</div>
|
|
|
- <Progress percent={86} status="active" size="small" />
|
|
|
- <div className="text-xs text-slate-500 mt-1">到岗率</div>
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card title="承载能力(示意)">
|
|
|
+ <EChart
|
|
|
+ option={{
|
|
|
+ tooltip: { trigger: "axis" },
|
|
|
+ grid: { left: 40, right: 10, top: 20, bottom: 40 },
|
|
|
+ xAxis: { type: "category", data: ["中心", "东", "西", "南", "北"] },
|
|
|
+ yAxis: { type: "value" },
|
|
|
+ color: [primary],
|
|
|
+ series: [{ type: "bar", data: [92, 86, 74, 80, 77] }],
|
|
|
+ }}
|
|
|
+ />
|
|
|
</Card>
|
|
|
- <Card size="small">
|
|
|
- <div className="text-xs text-slate-500">重点防护目标</div>
|
|
|
- <div className="text-xl font-semibold mt-1">143 处</div>
|
|
|
- <Progress percent={92} status="active" size="small" />
|
|
|
- <div className="text-xs text-slate-500 mt-1">纳入台账比例</div>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {assetTab === "mgmt" && (
|
|
|
+ <>
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card title="管理单位信息">
|
|
|
+ <Descriptions size="small" column={1} bordered>
|
|
|
+ <Descriptions.Item label="燃气管理单位">市燃气集团、区属燃气公司等(共 6 家)</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="供水管理单位">市自来水公司、二供物业等(共 9 家)</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="排水管理单位">城排中心、各区运营公司(共 7 家)</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="产权分布">市属 60%,区属 30%,社会 10%</Descriptions.Item>
|
|
|
+ </Descriptions>
|
|
|
+ <Divider className="my-3" />
|
|
|
+ <Timeline
|
|
|
+ items={[
|
|
|
+ { color: "green", children: "制度更新:设施巡检标准(本月)" },
|
|
|
+ { color: "blue", children: "完成年度普查 80%" },
|
|
|
+ { color: "orange", children: "外包维保单位进场(上周)" },
|
|
|
+ ]}
|
|
|
+ />
|
|
|
</Card>
|
|
|
- <Card size="small">
|
|
|
- <div className="text-xs text-slate-500">联动单位</div>
|
|
|
- <div className="text-xl font-semibold mt-1">19 家</div>
|
|
|
- <Progress percent={74} status="active" size="small" />
|
|
|
- <div className="text-xs text-slate-500 mt-1">联动成熟度</div>
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card title="产权分布">
|
|
|
+ <EChart
|
|
|
+ option={{
|
|
|
+ tooltip: { trigger: "item" },
|
|
|
+ legend: { bottom: 0 },
|
|
|
+ color: [primary, "#34d399", "#a7f3d0"],
|
|
|
+ series: [
|
|
|
+ { type: "pie", radius: ["35%", "60%"], data: [{ value: 60, name: "市属" }, { value: 30, name: "区属" }, { value: 10, name: "社会" }] },
|
|
|
+ ],
|
|
|
+ }}
|
|
|
+ />
|
|
|
</Card>
|
|
|
- </div>
|
|
|
- </Card>
|
|
|
- </Col>
|
|
|
- <Col xs={24} md={12}>
|
|
|
- <Card>
|
|
|
- <EChart option={radarCapacityOption} />
|
|
|
- </Card>
|
|
|
- </Col>
|
|
|
- </Row>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {assetTab === "emergency" && (
|
|
|
+ <>
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card title="应急资源配置">
|
|
|
+ <div className="grid grid-cols-2 gap-4">
|
|
|
+ <Card size="small">
|
|
|
+ <div className="text-xs text-slate-500">物资仓库</div>
|
|
|
+ <div className="text-xl font-semibold mt-1">12 处</div>
|
|
|
+ <Progress percent={78} status="active" size="small" />
|
|
|
+ <div className="text-xs text-slate-500 mt-1">库存充足度</div>
|
|
|
+ </Card>
|
|
|
+ <Card size="small">
|
|
|
+ <div className="text-xs text-slate-500">抢修队伍</div>
|
|
|
+ <div className="text-xl font-semibold mt-1">26 支</div>
|
|
|
+ <Progress percent={86} status="active" size="small" />
|
|
|
+ <div className="text-xs text-slate-500 mt-1">到岗率</div>
|
|
|
+ </Card>
|
|
|
+ <Card size="small">
|
|
|
+ <div className="text-xs text-slate-500">重点防护目标</div>
|
|
|
+ <div className="text-xl font-semibold mt-1">143 处</div>
|
|
|
+ <Progress percent={92} status="active" size="small" />
|
|
|
+ <div className="text-xs text-slate-500 mt-1">纳入台账比例</div>
|
|
|
+ </Card>
|
|
|
+ <Card size="small">
|
|
|
+ <div className="text-xs text-slate-500">联动单位</div>
|
|
|
+ <div className="text-xl font-semibold mt-1">19 家</div>
|
|
|
+ <Progress percent={74} status="active" size="small" />
|
|
|
+ <div className="text-xs text-slate-500 mt-1">联动成熟度</div>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card>
|
|
|
+ <EChart option={radarCapacityOption} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
</section>
|
|
|
)}
|
|
|
|
|
|
{selectedMenu === "monitor" && (
|
|
|
<section className="space-y-4">
|
|
|
<Tabs
|
|
|
- defaultActiveKey="summary"
|
|
|
+ activeKey={monitorTab}
|
|
|
+ onChange={setMonitorTab}
|
|
|
items={[
|
|
|
{ key: "summary", label: "监测概览" },
|
|
|
{ key: "distribution", label: "监测分布" },
|
|
|
@@ -779,75 +864,127 @@ export default function Page() {
|
|
|
]}
|
|
|
/>
|
|
|
|
|
|
- <Row gutter={[16, 16]}>
|
|
|
- <Col xs={24} md={12} lg={8}>
|
|
|
- <Card>
|
|
|
- <EChart option={onlineGaugeOption} />
|
|
|
- </Card>
|
|
|
- </Col>
|
|
|
- <Col xs={24} md={12} lg={8}>
|
|
|
- <Card>
|
|
|
- <EChart option={deviceBarOption as EChartsOption} />
|
|
|
+ {monitorTab === "summary" && (
|
|
|
+ <>
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col xs={24} md={12} lg={8}>
|
|
|
+ <Card>
|
|
|
+ <EChart option={onlineGaugeOption} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} md={12} lg={8}>
|
|
|
+ <Card>
|
|
|
+ <EChart option={deviceBarOption} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} md={24} lg={8}>
|
|
|
+ <Card>
|
|
|
+ <EChart option={riskBarOption} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {monitorTab === "distribution" && (
|
|
|
+ <>
|
|
|
+ <Card title="监测分布(按行业与风险)">
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ {(["燃气", "供水", "排水"] as const).map((ind) => (
|
|
|
+ <Col xs={24} md={8} key={ind}>
|
|
|
+ <Card size="small" title={ind}>
|
|
|
+ <div className="flex items-center gap-4">
|
|
|
+ <div className="flex-1">
|
|
|
+ <div className="text-xs text-slate-500">设备在线</div>
|
|
|
+ <div className="text-lg font-semibold">
|
|
|
+ {devices.filter((d) => d.industry === ind && d.status === "在线").length}
|
|
|
+ </div>
|
|
|
+ <div className="text-xs text-slate-500">总量 {devices.filter((d) => d.industry === ind).length}</div>
|
|
|
+ </div>
|
|
|
+ <div className="flex-1">
|
|
|
+ <div className="text-xs text-slate-500">高风险</div>
|
|
|
+ <div className="text-lg font-semibold">
|
|
|
+ {devices.filter((d) => d.industry === ind && d.risk === "高").length}
|
|
|
+ </div>
|
|
|
+ <Progress
|
|
|
+ percent={Math.round(
|
|
|
+ (devices.filter((d) => d.industry === ind && d.risk === "高").length /
|
|
|
+ Math.max(devices.filter((d) => d.industry === ind).length, 1)) *
|
|
|
+ 100
|
|
|
+ )}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ ))}
|
|
|
+ </Row>
|
|
|
</Card>
|
|
|
- </Col>
|
|
|
- <Col xs={24} md={24} lg={8}>
|
|
|
- <Card>
|
|
|
- <EChart option={riskBarOption} />
|
|
|
+
|
|
|
+ <Card title="一张图(示意)">
|
|
|
+ <div className="relative w-full h-[380px] rounded-md overflow-hidden bg-slate-100">
|
|
|
+ <img
|
|
|
+ src="/images/city-map.png"
|
|
|
+ alt="城市简化地图"
|
|
|
+ className="absolute inset-0 w-full h-full object-cover opacity-90"
|
|
|
+ />
|
|
|
+ {markers.map((m) => (
|
|
|
+ <AntdTooltip key={m.id} title={m.title}>
|
|
|
+ <div
|
|
|
+ className="absolute w-2.5 h-2.5 rounded-full ring-2 ring-white/70"
|
|
|
+ style={{ top: m.top, left: m.left, backgroundColor: m.color }}
|
|
|
+ />
|
|
|
+ </AntdTooltip>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
</Card>
|
|
|
- </Col>
|
|
|
- </Row>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
|
|
|
- <Card title="监测分布(按行业与风险)">
|
|
|
- <Row gutter={[16, 16]}>
|
|
|
- {(["燃气", "供水", "排水"] as const).map((ind) => (
|
|
|
- <Col xs={24} md={8} key={ind}>
|
|
|
- <Card size="small" title={ind}>
|
|
|
- <div className="flex items-center gap-4">
|
|
|
- <div className="flex-1">
|
|
|
- <div className="text-xs text-slate-500">设备在线</div>
|
|
|
- <div className="text-lg font-semibold">
|
|
|
- {devices.filter((d) => d.industry === ind && d.status === "在线").length}
|
|
|
- </div>
|
|
|
- <div className="text-xs text-slate-500">总量 {devices.filter((d) => d.industry === ind).length}</div>
|
|
|
- </div>
|
|
|
- <div className="flex-1">
|
|
|
- <div className="text-xs text-slate-500">高风险</div>
|
|
|
- <div className="text-lg font-semibold">{devices.filter((d) => d.industry === ind && d.risk === "高").length}</div>
|
|
|
- <Progress percent={Math.round((devices.filter((d) => d.industry === ind && d.risk === "高").length / Math.max(devices.filter((d) => d.industry === ind).length, 1)) * 100)} size="small" />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ {monitorTab === "running" && (
|
|
|
+ <>
|
|
|
+ <Card title="运行概况(报警覆盖)">
|
|
|
+ <EChart
|
|
|
+ option={{
|
|
|
+ tooltip: { trigger: "axis" },
|
|
|
+ legend: { bottom: 0 },
|
|
|
+ grid: { left: 40, right: 10, top: 20, bottom: 40 },
|
|
|
+ xAxis: { type: "category", data: Array.from({ length: 12 }).map((_, i) => dayjs().subtract(11 - i, "month").format("YYYY-MM")) },
|
|
|
+ yAxis: { type: "value" },
|
|
|
+ color: [primary, "#f59e0b", "#a855f7"],
|
|
|
+ series: (["燃气", "供水", "排水"] as const).map((ind, idx) => ({
|
|
|
+ name: ind,
|
|
|
+ type: "bar",
|
|
|
+ stack: "sum",
|
|
|
+ emphasis: { focus: "series" },
|
|
|
+ data: Array.from({ length: 12 }).map(() => Math.floor(Math.random() * (idx === 0 ? 50 : idx === 1 ? 40 : 30))),
|
|
|
+ })),
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ <Row gutter={[16, 16]} className="mt-1">
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card>
|
|
|
+ <EChart option={alertTrendOption} />
|
|
|
</Card>
|
|
|
</Col>
|
|
|
- ))}
|
|
|
- </Row>
|
|
|
- </Card>
|
|
|
-
|
|
|
- <Card title="运行概况(报警覆盖)">
|
|
|
- <EChart
|
|
|
- option={{
|
|
|
- tooltip: { trigger: "axis" },
|
|
|
- legend: { bottom: 0 },
|
|
|
- grid: { left: 40, right: 10, top: 20, bottom: 40 },
|
|
|
- xAxis: { type: "category", data: Array.from({ length: 12 }).map((_, i) => dayjs().subtract(11 - i, "month").format("YYYY-MM")) },
|
|
|
- yAxis: { type: "value" },
|
|
|
- color: [primary, "#f59e0b", "#a855f7"],
|
|
|
- series: (["燃气", "供水", "排水"] as const).map((ind, idx) => ({
|
|
|
- name: ind,
|
|
|
- type: "bar",
|
|
|
- stack: "sum",
|
|
|
- emphasis: { focus: "series" },
|
|
|
- data: Array.from({ length: 12 }).map(() => Math.floor(Math.random() * (idx === 0 ? 50 : idx === 1 ? 40 : 30))),
|
|
|
- })),
|
|
|
- }}
|
|
|
- />
|
|
|
- </Card>
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card>
|
|
|
+ <EChart option={deviceBarOption} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
</section>
|
|
|
)}
|
|
|
|
|
|
{selectedMenu === "alert" && (
|
|
|
<section className="space-y-4">
|
|
|
<Tabs
|
|
|
- defaultActiveKey="current"
|
|
|
+ activeKey={alertTab}
|
|
|
+ onChange={setAlertTab}
|
|
|
items={[
|
|
|
{ key: "current", label: "当前预警" },
|
|
|
{ key: "history", label: "历史分析" },
|
|
|
@@ -856,156 +993,344 @@ export default function Page() {
|
|
|
]}
|
|
|
/>
|
|
|
|
|
|
- <Row gutter={[16, 16]}>
|
|
|
- <Col xs={24} lg={16}>
|
|
|
- <Card title="总体预警处置情况">
|
|
|
- <Table
|
|
|
- size="small"
|
|
|
- rowKey="id"
|
|
|
- columns={alertColumns}
|
|
|
- dataSource={alerts}
|
|
|
- pagination={{ pageSize: 8 }}
|
|
|
- onRow={(record) => ({
|
|
|
- onClick: () => {
|
|
|
- setSelectedAlert(record)
|
|
|
- setDrawerOpen(true)
|
|
|
- },
|
|
|
- })}
|
|
|
- scroll={{ x: 900 }}
|
|
|
- />
|
|
|
- </Card>
|
|
|
- </Col>
|
|
|
- <Col xs={24} lg={8}>
|
|
|
- <Card title="处置效率">
|
|
|
- <div className="grid grid-cols-2 gap-4">
|
|
|
- <div>
|
|
|
- <div className="text-xs text-slate-500">平均处置时效</div>
|
|
|
- <div className="text-xl font-semibold mt-1">{(45 + Math.random() * 30).toFixed(0)} 分钟</div>
|
|
|
- <Progress percent={78} status="active" size="small" />
|
|
|
- <div className="text-xs text-slate-500 mt-1">较上月 +6%</div>
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <div className="text-xs text-slate-500">闭环率</div>
|
|
|
- <div className="text-xl font-semibold mt-1">
|
|
|
- {Math.round((alerts.filter((a) => a.status === "已闭环").length / Math.max(alerts.length, 1)) * 100)}%
|
|
|
+ {alertTab === "current" && (
|
|
|
+ <>
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col xs={24} lg={16}>
|
|
|
+ <Card title="总体预警处置情况">
|
|
|
+ <Table
|
|
|
+ size="small"
|
|
|
+ rowKey="id"
|
|
|
+ columns={alertColumns}
|
|
|
+ dataSource={alerts}
|
|
|
+ pagination={{ pageSize: 8 }}
|
|
|
+ onRow={(record) => ({
|
|
|
+ onClick: () => {
|
|
|
+ setSelectedAlert(record)
|
|
|
+ setDrawerOpen(true)
|
|
|
+ },
|
|
|
+ })}
|
|
|
+ scroll={{ x: 900 }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} lg={8}>
|
|
|
+ <Card title="处置效率">
|
|
|
+ <div className="grid grid-cols-2 gap-4">
|
|
|
+ <div>
|
|
|
+ <div className="text-xs text-slate-500">平均处置时效</div>
|
|
|
+ <div className="text-xl font-semibold mt-1">{(45 + Math.random() * 30).toFixed(0)} 分钟</div>
|
|
|
+ <Progress percent={78} status="active" size="small" />
|
|
|
+ <div className="text-xs text-slate-500 mt-1">较上月 +6%</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div className="text-xs text-slate-500">闭环率</div>
|
|
|
+ <div className="text-xl font-semibold mt-1">
|
|
|
+ {Math.round((alerts.filter((a) => a.status === "已闭环").length / Math.max(alerts.length, 1)) * 100)}%
|
|
|
+ </div>
|
|
|
+ <Progress percent={86} status="active" size="small" />
|
|
|
+ <div className="text-xs text-slate-500 mt-1">较上周 +2%</div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <Progress percent={86} status="active" size="small" />
|
|
|
- <div className="text-xs text-slate-500 mt-1">较上周 +2%</div>
|
|
|
+ </Card>
|
|
|
+ <Card className="mt-4">
|
|
|
+ <EChart option={efficiencyPieOption} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ <Drawer
|
|
|
+ title={
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <AlertTriangle size={18} color={selectedAlert ? alertLevelColor(selectedAlert.level) : primary} />
|
|
|
+ <span>预警详情</span>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </Card>
|
|
|
- <Card className="mt-4">
|
|
|
- <EChart option={efficiencyPieOption as EChartsOption} />
|
|
|
- </Card>
|
|
|
+ }
|
|
|
+ placement="right"
|
|
|
+ width={520}
|
|
|
+ open={drawerOpen}
|
|
|
+ onClose={() => setDrawerOpen(false)}
|
|
|
+ >
|
|
|
+ {selectedAlert ? (
|
|
|
+ <div className="space-y-4">
|
|
|
+ <Descriptions size="small" column={1} bordered>
|
|
|
+ <Descriptions.Item label="预警编号">{selectedAlert.id}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="等级">
|
|
|
+ <LevelTag level={selectedAlert.level} />
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="行业">{selectedAlert.industry}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="类型">{selectedAlert.type}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="时间">{selectedAlert.time}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="位置">{selectedAlert.location}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="状态">
|
|
|
+ <StatusBadge s={selectedAlert.status} />
|
|
|
+ </Descriptions.Item>
|
|
|
+ <Descriptions.Item label="描述">{selectedAlert.desc}</Descriptions.Item>
|
|
|
+ </Descriptions>
|
|
|
+ <Card size="small" title="事件时序">
|
|
|
+ <EChart
|
|
|
+ option={{
|
|
|
+ grid: { left: 30, right: 10, top: 20, bottom: 20 },
|
|
|
+ xAxis: { type: "category", data: ["发现", "确认", "处置", "复盘"] },
|
|
|
+ yAxis: { type: "value" },
|
|
|
+ color: [primary],
|
|
|
+ series: [{ type: "line", smooth: true, data: [0, 12, 38, 60] }],
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ <Card size="small" title="一张图联动(示意)">
|
|
|
+ <div className="relative w-full h-[200px] rounded-md overflow-hidden">
|
|
|
+ <img src="/images/city-map.png" alt="地图联动示意图" className="absolute inset-0 w-full h-full object-cover opacity-90" />
|
|
|
+ <div className="absolute inset-0">
|
|
|
+ <div
|
|
|
+ className="absolute w-3 h-3 rounded-full ring-2 ring-white animate-pulse"
|
|
|
+ style={{ top: "42%", left: "56%", background: alertLevelColor(selectedAlert.level) }}
|
|
|
+ title="预警位置"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ <Space>
|
|
|
+ <a className="text-emerald-600">查看工单</a>
|
|
|
+ <a className="text-emerald-600">下达指令</a>
|
|
|
+ <a className="text-emerald-600">通知联动</a>
|
|
|
+ </Space>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <div className="text-slate-500 text-sm">请选择左侧预警查看详情。</div>
|
|
|
+ )}
|
|
|
+ </Drawer>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {alertTab === "history" && (
|
|
|
+ <>
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card title="各行业预警数量(近6月)">
|
|
|
+ <EChart
|
|
|
+ option={{
|
|
|
+ tooltip: { trigger: "axis" },
|
|
|
+ legend: { bottom: 0 },
|
|
|
+ grid: { left: 40, right: 10, top: 20, bottom: 40 },
|
|
|
+ xAxis: { type: "category", data: Array.from({ length: 6 }).map((_, i) => dayjs().subtract(5 - i, "month").format("YYYY-MM")) },
|
|
|
+ yAxis: { type: "value" },
|
|
|
+ color: [primary, "#f59e0b", "#a855f7"],
|
|
|
+ series: (["燃气", "供水", "排水"] as const).map((ind, idx) => ({
|
|
|
+ name: ind,
|
|
|
+ type: "line",
|
|
|
+ smooth: true,
|
|
|
+ data: Array.from({ length: 6 }).map(() => Math.floor(Math.random() * (idx === 0 ? 60 : idx === 1 ? 45 : 35)) + 5),
|
|
|
+ })),
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card title="已闭环预警(示意)">
|
|
|
+ <Table
|
|
|
+ size="small"
|
|
|
+ rowKey="id"
|
|
|
+ columns={[
|
|
|
+ { title: "编号", dataIndex: "id" },
|
|
|
+ { title: "类型", dataIndex: "type" },
|
|
|
+ { title: "行业", dataIndex: "industry", width: 90 },
|
|
|
+ { title: "完成时间", dataIndex: "time", width: 160 },
|
|
|
+ ]}
|
|
|
+ dataSource={alerts.filter((a) => a.status === "已闭环")}
|
|
|
+ pagination={{ pageSize: 6 }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {alertTab === "efficiency" && (
|
|
|
+ <>
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card title="整体效率">
|
|
|
+ <div className="grid grid-cols-2 gap-4">
|
|
|
+ <div>
|
|
|
+ <div className="text-xs text-slate-500">平均处置时效</div>
|
|
|
+ <div className="text-xl font-semibold mt-1">{(42 + Math.random() * 25).toFixed(0)} 分钟</div>
|
|
|
+ <Progress percent={82} status="active" size="small" />
|
|
|
+ <div className="text-xs text-slate-500 mt-1">较上月 +4%</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div className="text-xs text-slate-500">正确预警率</div>
|
|
|
+ <div className="text-xl font-semibold mt-1">{(88 + Math.random() * 4).toFixed(1)}%</div>
|
|
|
+ <Progress percent={89} status="active" size="small" />
|
|
|
+ <div className="text-xs text-slate-500 mt-1">核实误报下降</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card>
|
|
|
+ <EChart option={efficiencyPieOption} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {alertTab === "analysis" && (
|
|
|
+ <>
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col xs={24}>
|
|
|
+ <Card title="预警类型与成因分布">
|
|
|
+ <EChart
|
|
|
+ option={{
|
|
|
+ tooltip: { trigger: "axis" },
|
|
|
+ legend: { bottom: 0 },
|
|
|
+ grid: { left: 40, right: 10, top: 20, bottom: 40 },
|
|
|
+ xAxis: {
|
|
|
+ type: "category",
|
|
|
+ data: ["泄漏", "压力异常", "流量突变", "停电", "液位过低", "浊度异常"],
|
|
|
+ axisLabel: { rotate: 20 },
|
|
|
+ },
|
|
|
+ yAxis: { type: "value" },
|
|
|
+ color: [primary, "#f59e0b"],
|
|
|
+ series: [
|
|
|
+ { name: "设备原因", type: "bar", data: [30, 22, 18, 12, 9, 7] },
|
|
|
+ { name: "外部原因", type: "bar", data: [18, 15, 14, 28, 11, 10] },
|
|
|
+ ],
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </section>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {selectedMenu === "devices" && (
|
|
|
+ <section className="space-y-4">
|
|
|
+ <Row justify="space-between" align="middle">
|
|
|
+ <Col>
|
|
|
+ <div className="text-base font-medium">设备管理</div>
|
|
|
+ </Col>
|
|
|
+ <Col>
|
|
|
+ <Space>
|
|
|
+ <Segmented
|
|
|
+ options={["全部", "在线", "离线"]}
|
|
|
+ onChange={() => {}}
|
|
|
+ aria-label="设备状态筛选"
|
|
|
+ />
|
|
|
+ <Button size="small" icon={<Table2 size={14} />}>
|
|
|
+ 批量导出
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
</Col>
|
|
|
</Row>
|
|
|
+ <Card>
|
|
|
+ <Table
|
|
|
+ size="small"
|
|
|
+ rowKey="id"
|
|
|
+ columns={deviceColumns}
|
|
|
+ dataSource={devices}
|
|
|
+ pagination={{ pageSize: 10 }}
|
|
|
+ scroll={{ x: 700 }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </section>
|
|
|
+ )}
|
|
|
|
|
|
+ {selectedMenu === "reports" && (
|
|
|
+ <section className="space-y-4">
|
|
|
<Row gutter={[16, 16]}>
|
|
|
<Col xs={24} md={12}>
|
|
|
- <Card title="各行业预警数量(近6月)">
|
|
|
+ <Card title="月度运行报告(示意)" extra={<Button size="small">导出 PDF</Button>}>
|
|
|
<EChart
|
|
|
option={{
|
|
|
tooltip: { trigger: "axis" },
|
|
|
- legend: { bottom: 0 },
|
|
|
- grid: { left: 40, right: 10, top: 20, bottom: 40 },
|
|
|
- xAxis: { type: "category", data: Array.from({ length: 6 }).map((_, i) => dayjs().subtract(5 - i, "month").format("YYYY-MM")) },
|
|
|
+ grid: { left: 40, right: 10, top: 20, bottom: 30 },
|
|
|
+ xAxis: { type: "category", data: ["一", "二", "三", "四", "五", "六"] },
|
|
|
yAxis: { type: "value" },
|
|
|
- color: [primary, "#f59e0b", "#a855f7"],
|
|
|
- series: (["燃气", "供水", "排水"] as const).map((ind, idx) => ({
|
|
|
- name: ind,
|
|
|
- type: "line",
|
|
|
- smooth: true,
|
|
|
- data: Array.from({ length: 6 }).map(() => Math.floor(Math.random() * (idx === 0 ? 60 : idx === 1 ? 45 : 35)) + 5),
|
|
|
- })),
|
|
|
+ color: [primary],
|
|
|
+ series: [{ type: "line", smooth: true, data: [120, 132, 101, 134, 90, 230] }],
|
|
|
}}
|
|
|
/>
|
|
|
</Card>
|
|
|
</Col>
|
|
|
<Col xs={24} md={12}>
|
|
|
- <Card title="预警类型与成因分布">
|
|
|
+ <Card title="安全事件统计(示意)" extra={<Button size="small">导出 Excel</Button>}>
|
|
|
<EChart
|
|
|
option={{
|
|
|
tooltip: { trigger: "axis" },
|
|
|
- legend: { bottom: 0 },
|
|
|
- grid: { left: 40, right: 10, top: 20, bottom: 40 },
|
|
|
- xAxis: {
|
|
|
- type: "category",
|
|
|
- data: ["泄漏", "压力异常", "流量突变", "停电", "液位过低", "浊度异常"],
|
|
|
- axisLabel: { rotate: 20 },
|
|
|
- },
|
|
|
+ grid: { left: 40, right: 10, top: 20, bottom: 30 },
|
|
|
+ xAxis: { type: "category", data: ["燃气", "供水", "排水"] },
|
|
|
yAxis: { type: "value" },
|
|
|
- color: [primary, "#f59e0b"],
|
|
|
- series: [
|
|
|
- { name: "设备原因", type: "bar", data: [30, 22, 18, 12, 9, 7] },
|
|
|
- { name: "外部原因", type: "bar", data: [18, 15, 14, 28, 11, 10] },
|
|
|
- ],
|
|
|
+ color: [primary, "#f59e0b", "#a855f7"],
|
|
|
+ series: [{ type: "bar", data: [32, 21, 17], barWidth: "50%" }],
|
|
|
}}
|
|
|
/>
|
|
|
</Card>
|
|
|
</Col>
|
|
|
</Row>
|
|
|
+ </section>
|
|
|
+ )}
|
|
|
|
|
|
- <Drawer
|
|
|
- title={
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- <AlertTriangle size={18} color={selectedAlert ? alertLevelColor(selectedAlert.level) : primary} />
|
|
|
- <span>预警详情</span>
|
|
|
- </div>
|
|
|
- }
|
|
|
- placement="right"
|
|
|
- width={520}
|
|
|
- open={drawerOpen}
|
|
|
- onClose={() => setDrawerOpen(false)}
|
|
|
- >
|
|
|
- {selectedAlert ? (
|
|
|
- <div className="space-y-4">
|
|
|
- <Descriptions size="small" column={1} bordered>
|
|
|
- <Descriptions.Item label="预警编号">{selectedAlert.id}</Descriptions.Item>
|
|
|
- <Descriptions.Item label="等级">
|
|
|
- <LevelTag level={selectedAlert.level} />
|
|
|
- </Descriptions.Item>
|
|
|
- <Descriptions.Item label="行业">{selectedAlert.industry}</Descriptions.Item>
|
|
|
- <Descriptions.Item label="类型">{selectedAlert.type}</Descriptions.Item>
|
|
|
- <Descriptions.Item label="时间">{selectedAlert.time}</Descriptions.Item>
|
|
|
- <Descriptions.Item label="位置">{selectedAlert.location}</Descriptions.Item>
|
|
|
- <Descriptions.Item label="状态">
|
|
|
- <StatusBadge s={selectedAlert.status} />
|
|
|
- </Descriptions.Item>
|
|
|
- <Descriptions.Item label="描述">{selectedAlert.desc}</Descriptions.Item>
|
|
|
- </Descriptions>
|
|
|
- <Card size="small" title="事件时序">
|
|
|
- <EChart
|
|
|
- option={{
|
|
|
- grid: { left: 30, right: 10, top: 20, bottom: 20 },
|
|
|
- xAxis: { type: "category", data: ["发现", "确认", "处置", "复盘"] },
|
|
|
- yAxis: { type: "value" },
|
|
|
- color: [primary],
|
|
|
- series: [{ type: "line", smooth: true, data: [0, 12, 38, 60] }],
|
|
|
- }}
|
|
|
- />
|
|
|
+ {selectedMenu === "incidents" && (
|
|
|
+ <section className="space-y-4">
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col xs={24} md={16}>
|
|
|
+ <Card title="事件列表(示意)" extra={<Button size="small">新建事件</Button>}>
|
|
|
+ <Empty description="暂无数据(示意)" />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} md={8}>
|
|
|
+ <Card title="事件趋势(示意)">
|
|
|
+ <EChart
|
|
|
+ option={{
|
|
|
+ tooltip: { trigger: "axis" },
|
|
|
+ grid: { left: 40, right: 10, top: 20, bottom: 30 },
|
|
|
+ xAxis: { type: "category", data: Array.from({ length: 12 }).map((_, i) => `${i + 1}月`) },
|
|
|
+ yAxis: { type: "value" },
|
|
|
+ color: [primary],
|
|
|
+ series: [{ type: "line", smooth: true, data: Array.from({ length: 12 }).map(() => Math.floor(Math.random() * 20) + 5) }],
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </section>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {selectedMenu === "bigscreen" && (
|
|
|
+ <section className="space-y-4">
|
|
|
+ <Card title="数据大屏(示意)">
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ {[
|
|
|
+ { title: "设备总数", val: devices.length },
|
|
|
+ { title: "在线率", val: `${onlineRate}%` },
|
|
|
+ { title: "本月预警", val: (alerts.length + Math.floor(Math.random() * 30)).toString() },
|
|
|
+ { title: "闭环率", val: `${Math.round((alerts.filter((a) => a.status === "已闭环").length / Math.max(alerts.length, 1)) * 100)}%` },
|
|
|
+ ].map((s) => (
|
|
|
+ <Col xs={12} md={6} key={s.title}>
|
|
|
+ <Card size="small">
|
|
|
+ <div className="text-xs text-slate-500">{s.title}</div>
|
|
|
+ <div className="text-2xl font-semibold mt-1">{s.val}</div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ ))}
|
|
|
+ </Row>
|
|
|
+ <Row gutter={[16, 16]} className="mt-1">
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card>
|
|
|
+ <EChart option={deviceBarOption} />
|
|
|
</Card>
|
|
|
- <Card size="small" title="一张图联动(示意)">
|
|
|
- <div className="relative w-full h-[200px] rounded-md overflow-hidden">
|
|
|
- <img src="/images/city-map.png" alt="地图联动示意图" className="absolute inset-0 w-full h-full object-cover opacity-90" />
|
|
|
- <div className="absolute inset-0">
|
|
|
- <div
|
|
|
- className="absolute w-3 h-3 rounded-full ring-2 ring-white animate-pulse"
|
|
|
- style={{ top: "42%", left: "56%", background: alertLevelColor(selectedAlert.level) }}
|
|
|
- title="预警位置"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ </Col>
|
|
|
+ <Col xs={24} md={12}>
|
|
|
+ <Card>
|
|
|
+ <EChart option={riskBarOption} />
|
|
|
</Card>
|
|
|
- <Space>
|
|
|
- <a className="text-emerald-600">查看工单</a>
|
|
|
- <a className="text-emerald-600">下达指令</a>
|
|
|
- <a className="text-emerald-600">通知联动</a>
|
|
|
- </Space>
|
|
|
- </div>
|
|
|
- ) : (
|
|
|
- <div className="text-slate-500 text-sm">请选择左侧预警查看详情。</div>
|
|
|
- )}
|
|
|
- </Drawer>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </Card>
|
|
|
</section>
|
|
|
)}
|
|
|
|