|
|
@@ -0,0 +1,408 @@
|
|
|
+"use client"
|
|
|
+
|
|
|
+import {useState} from "react"
|
|
|
+import {Button, Card, Col, Row, Select, Space, Statistic, Switch, Tag} from "antd"
|
|
|
+import {BellOutlined, FullscreenOutlined, ReloadOutlined} from "@ant-design/icons"
|
|
|
+import EChart from "@/components/echarts"
|
|
|
+import dynamic from "next/dynamic"
|
|
|
+
|
|
|
+const GasNetworkMap = dynamic(() => import("./gas-network-map"), {
|
|
|
+ ssr: false,
|
|
|
+ loading: () => <div className="flex items-center justify-center h-96">加载地图中...</div>,
|
|
|
+})
|
|
|
+
|
|
|
+const { Option } = Select
|
|
|
+
|
|
|
+// 模拟报警点位数据
|
|
|
+const mockAlarmPoints = [
|
|
|
+ {
|
|
|
+ id: "A001",
|
|
|
+ name: "解放路压力报警",
|
|
|
+ position: [39.9042, 116.4074],
|
|
|
+ type: "pressure",
|
|
|
+ level: "高",
|
|
|
+ status: "未处理",
|
|
|
+ createTime: "2024-01-15 14:30:25",
|
|
|
+ value: 0.52,
|
|
|
+ threshold: 0.45,
|
|
|
+ unit: "MPa",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: "A002",
|
|
|
+ name: "人民路流量报警",
|
|
|
+ position: [39.9052, 116.4084],
|
|
|
+ type: "flow",
|
|
|
+ level: "中",
|
|
|
+ status: "处理中",
|
|
|
+ createTime: "2024-01-15 13:45:10",
|
|
|
+ value: 4200,
|
|
|
+ threshold: 3500,
|
|
|
+ unit: "m³/h",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: "A003",
|
|
|
+ name: "建设路泄漏报警",
|
|
|
+ position: [39.9032, 116.4064],
|
|
|
+ type: "leakage",
|
|
|
+ level: "高",
|
|
|
+ status: "已处理",
|
|
|
+ createTime: "2024-01-15 12:20:15",
|
|
|
+ value: 0.08,
|
|
|
+ threshold: 0.05,
|
|
|
+ unit: "ppm",
|
|
|
+ },
|
|
|
+]
|
|
|
+
|
|
|
+// 模拟报警统计数据
|
|
|
+const mockAlarmTypeStats = [
|
|
|
+ { type: "压力异常", count: 15, percentage: 42.9 },
|
|
|
+ { type: "流量异常", count: 12, percentage: 34.3 },
|
|
|
+ { type: "泄漏检测", count: 6, percentage: 17.1 },
|
|
|
+ { type: "温度异常", count: 2, percentage: 5.7 },
|
|
|
+]
|
|
|
+
|
|
|
+const mockAlarmLevelStats = [
|
|
|
+ { level: "高", count: 8, color: "#f5222d" },
|
|
|
+ { level: "中", count: 18, color: "#faad14" },
|
|
|
+ { level: "低", count: 9, color: "#52c41a" },
|
|
|
+]
|
|
|
+
|
|
|
+const mockHourlyStats = [
|
|
|
+ { hour: "00", count: 2 },
|
|
|
+ { hour: "01", count: 1 },
|
|
|
+ { hour: "02", count: 0 },
|
|
|
+ { hour: "03", count: 1 },
|
|
|
+ { hour: "04", count: 3 },
|
|
|
+ { hour: "05", count: 2 },
|
|
|
+ { hour: "06", count: 4 },
|
|
|
+ { hour: "07", count: 6 },
|
|
|
+ { hour: "08", count: 8 },
|
|
|
+ { hour: "09", count: 5 },
|
|
|
+ { hour: "10", count: 7 },
|
|
|
+ { hour: "11", count: 9 },
|
|
|
+ { hour: "12", count: 12 },
|
|
|
+ { hour: "13", count: 8 },
|
|
|
+ { hour: "14", count: 6 },
|
|
|
+ { hour: "15", count: 4 },
|
|
|
+ { hour: "16", count: 3 },
|
|
|
+ { hour: "17", count: 5 },
|
|
|
+ { hour: "18", count: 7 },
|
|
|
+ { hour: "19", count: 4 },
|
|
|
+ { hour: "20", count: 2 },
|
|
|
+ { hour: "21", count: 1 },
|
|
|
+ { hour: "22", count: 1 },
|
|
|
+ { hour: "23", count: 0 },
|
|
|
+]
|
|
|
+
|
|
|
+export default function AlarmVisualization() {
|
|
|
+ const [layerVisibility, setLayerVisibility] = useState({
|
|
|
+ pipeline: true,
|
|
|
+ pressureAlarm: true,
|
|
|
+ flowAlarm: true,
|
|
|
+ temperatureAlarm: true,
|
|
|
+ leakageAlarm: true,
|
|
|
+ highLevel: true,
|
|
|
+ mediumLevel: true,
|
|
|
+ lowLevel: true,
|
|
|
+ })
|
|
|
+ const [selectedTimeRange, setSelectedTimeRange] = useState("24hours")
|
|
|
+ const [isFullscreen, setIsFullscreen] = useState(false)
|
|
|
+
|
|
|
+ const handleLayerToggle = (layer: string, visible: boolean) => {
|
|
|
+ setLayerVisibility((prev) => ({
|
|
|
+ ...prev,
|
|
|
+ [layer]: visible,
|
|
|
+ }))
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleRefresh = () => {
|
|
|
+ console.log("刷新报警数据")
|
|
|
+ }
|
|
|
+
|
|
|
+ const getStatusColor = (status: string) => {
|
|
|
+ switch (status) {
|
|
|
+ case "未处理":
|
|
|
+ return "#f5222d"
|
|
|
+ case "处理中":
|
|
|
+ return "#faad14"
|
|
|
+ case "已处理":
|
|
|
+ return "#52c41a"
|
|
|
+ default:
|
|
|
+ return "#1890ff"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const getLevelColor = (level: string) => {
|
|
|
+ switch (level) {
|
|
|
+ case "高":
|
|
|
+ return "#f5222d"
|
|
|
+ case "中":
|
|
|
+ return "#faad14"
|
|
|
+ case "低":
|
|
|
+ return "#52c41a"
|
|
|
+ default:
|
|
|
+ return "#1890ff"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const getTypeLabel = (type: string) => {
|
|
|
+ const typeMap = {
|
|
|
+ pressure: "压力",
|
|
|
+ flow: "流量",
|
|
|
+ temperature: "温度",
|
|
|
+ leakage: "泄漏",
|
|
|
+ }
|
|
|
+ return typeMap[type as keyof typeof typeMap] || type
|
|
|
+ }
|
|
|
+
|
|
|
+ const pieOption = {
|
|
|
+ title: { text: "报警类型分布", left: "center" },
|
|
|
+ tooltip: { trigger: "item" },
|
|
|
+ legend: { orient: "vertical", left: "left" },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: "pie",
|
|
|
+ radius: "50%",
|
|
|
+ data: mockAlarmTypeStats.map((item) => ({
|
|
|
+ value: item.count,
|
|
|
+ name: item.type,
|
|
|
+ })),
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowColor: "rgba(0, 0, 0, 0.5)",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+
|
|
|
+ const columnOption = {
|
|
|
+ title: { text: "24小时报警分布", left: "center" },
|
|
|
+ tooltip: { trigger: "axis" },
|
|
|
+ grid: { left: "3%", right: "4%", bottom: "3%", containLabel: true },
|
|
|
+ xAxis: {
|
|
|
+ type: "category",
|
|
|
+ data: mockHourlyStats.map((item) => item.hour + ":00"),
|
|
|
+ },
|
|
|
+ yAxis: { type: "value", name: "报警数量" },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: "bar",
|
|
|
+ data: mockHourlyStats.map((item) => item.count),
|
|
|
+ itemStyle: { color: "#f5222d" },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+
|
|
|
+ const unhandledCount = mockAlarmPoints.filter((point) => point.status === "未处理").length
|
|
|
+ const processingCount = mockAlarmPoints.filter((point) => point.status === "处理中").length
|
|
|
+ const handledCount = mockAlarmPoints.filter((point) => point.status === "已处理").length
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="p-6">
|
|
|
+ <Card title="监测报警可视化">
|
|
|
+ {/* 报警概览统计 */}
|
|
|
+ <Row gutter={16} className="mb-6">
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small">
|
|
|
+ <Statistic
|
|
|
+ title="未处理报警"
|
|
|
+ value={unhandledCount}
|
|
|
+ valueStyle={{ color: "#f5222d" }}
|
|
|
+ prefix={<BellOutlined />}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small">
|
|
|
+ <Statistic title="处理中" value={processingCount} valueStyle={{ color: "#faad14" }} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small">
|
|
|
+ <Statistic title="已处理" value={handledCount} valueStyle={{ color: "#52c41a" }} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small">
|
|
|
+ <Statistic title="报警总数" value={mockAlarmPoints.length} valueStyle={{ color: "#1890ff" }} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ {/* 图层控制 */}
|
|
|
+ <Row gutter={16} className="mb-4">
|
|
|
+ <Col span={24}>
|
|
|
+ <Card size="small" title="报警图层控制">
|
|
|
+ <Row gutter={16}>
|
|
|
+ <Col span={3}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.pipeline}
|
|
|
+ onChange={(checked) => handleLayerToggle("pipeline", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">管网管线</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={3}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.pressureAlarm}
|
|
|
+ onChange={(checked) => handleLayerToggle("pressureAlarm", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">压力报警</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={3}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.flowAlarm}
|
|
|
+ onChange={(checked) => handleLayerToggle("flowAlarm", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">流量报警</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={3}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.leakageAlarm}
|
|
|
+ onChange={(checked) => handleLayerToggle("leakageAlarm", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">泄漏报警</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={3}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.highLevel}
|
|
|
+ onChange={(checked) => handleLayerToggle("highLevel", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">高级报警</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={3}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.mediumLevel}
|
|
|
+ onChange={(checked) => handleLayerToggle("mediumLevel", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">中级报警</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={3}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.lowLevel}
|
|
|
+ onChange={(checked) => handleLayerToggle("lowLevel", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">低级报警</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={3}>
|
|
|
+ <Space>
|
|
|
+ <Button icon={<ReloadOutlined />} onClick={handleRefresh} size="small">
|
|
|
+ 刷新
|
|
|
+ </Button>
|
|
|
+ <Button icon={<FullscreenOutlined />} onClick={() => setIsFullscreen(!isFullscreen)} size="small">
|
|
|
+ 全屏
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ {/* 时间范围选择 */}
|
|
|
+ <Row gutter={16} className="mb-4">
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small" title="时间范围">
|
|
|
+ <Select value={selectedTimeRange} onChange={setSelectedTimeRange} style={{ width: "100%" }}>
|
|
|
+ <Option value="1hour">近1小时</Option>
|
|
|
+ <Option value="6hours">近6小时</Option>
|
|
|
+ <Option value="24hours">近24小时</Option>
|
|
|
+ <Option value="7days">近7天</Option>
|
|
|
+ <Option value="30days">近30天</Option>
|
|
|
+ </Select>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={18}>
|
|
|
+ <Card size="small" title="报警态势图例">
|
|
|
+ <Space>
|
|
|
+ <Tag color="#f5222d">高级报警</Tag>
|
|
|
+ <Tag color="#faad14">中级报警</Tag>
|
|
|
+ <Tag color="#52c41a">低级报警</Tag>
|
|
|
+ <Tag color="#1890ff">压力异常</Tag>
|
|
|
+ <Tag color="#722ed1">流量异常</Tag>
|
|
|
+ <Tag color="#13c2c2">泄漏检测</Tag>
|
|
|
+ <Tag color="#eb2f96">温度异常</Tag>
|
|
|
+ </Space>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ {/* 地图和统计图表 */}
|
|
|
+ <Row gutter={16}>
|
|
|
+ <Col span={16}>
|
|
|
+ <Card title="报警态势一张图" size="small" className={isFullscreen ? "fixed inset-0 z-50" : ""}>
|
|
|
+ <GasNetworkMap height={isFullscreen ? "calc(100vh - 120px)" : "600px"} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={8}>
|
|
|
+ <Row gutter={16}>
|
|
|
+ <Col span={24}>
|
|
|
+ <Card title="报警类型分布" size="small" className="mb-4">
|
|
|
+ <EChart option={pieOption} style={{ height: 250 }} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={24}>
|
|
|
+ <Card title="24小时报警分布" size="small">
|
|
|
+ <EChart option={columnOption} style={{ height: 250 }} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ {/* 报警点位详情 */}
|
|
|
+ <Row gutter={16} className="mt-4">
|
|
|
+ <Col span={24}>
|
|
|
+ <Card title="当前活跃报警" size="small">
|
|
|
+ <div className="space-y-3 max-h-64 overflow-y-auto">
|
|
|
+ {mockAlarmPoints
|
|
|
+ .filter((point) => point.status !== "已处理")
|
|
|
+ .map((point) => (
|
|
|
+ <div key={point.id} className="p-3 border rounded-lg">
|
|
|
+ <div className="flex justify-between items-center mb-2">
|
|
|
+ <span className="font-medium">{point.name}</span>
|
|
|
+ <div className="space-x-2">
|
|
|
+ <Tag color={getLevelColor(point.level)}>{point.level}</Tag>
|
|
|
+ <Tag color={getStatusColor(point.status)}>{point.status}</Tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="text-sm text-gray-600 space-y-1">
|
|
|
+ <div>类型: {getTypeLabel(point.type)}</div>
|
|
|
+ <div>
|
|
|
+ 异常值: {point.value} {point.unit} (阈值: {point.threshold} {point.unit})
|
|
|
+ </div>
|
|
|
+ <div>报警时间: {point.createTime}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|