ソースを参照

feat(test8): 新增报警可视化组件

- 实现了监测报警的可视化展示,包括报警概览统计、图层控制、时间范围选择等功能
- 集成了地图组件和统计图表,直观展示报警态势和分布情况
- 添加了报警点位详情展示,便于用户查看具体的报警信息
nahida 9 ヶ月 前
コミット
52c6f91fe9
1 ファイル変更408 行追加0 行削除
  1. 408 0
      app/(other)/test8/components/alarm-visualization.tsx

+ 408 - 0
app/(other)/test8/components/alarm-visualization.tsx

@@ -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>
+  )
+}