|
|
@@ -0,0 +1,438 @@
|
|
|
+"use client"
|
|
|
+
|
|
|
+import {useEffect, useState} from "react"
|
|
|
+import {Alert, Card, Col, DatePicker, Row, Select, Space, Statistic, Table, Tag} from "antd"
|
|
|
+import EChart from "@/components/echarts"
|
|
|
+import {ArrowDownOutlined, ArrowUpOutlined, WarningOutlined} from "@ant-design/icons"
|
|
|
+
|
|
|
+const { RangePicker } = DatePicker
|
|
|
+const { Option } = Select
|
|
|
+
|
|
|
+// 模拟实时监测数据
|
|
|
+const mockRealTimeData = {
|
|
|
+ pressure: {
|
|
|
+ current: 0.42,
|
|
|
+ normal: [0.35, 0.45],
|
|
|
+ status: "normal",
|
|
|
+ trend: "up",
|
|
|
+ change: 0.02,
|
|
|
+ },
|
|
|
+ flow: {
|
|
|
+ current: 2850,
|
|
|
+ normal: [2000, 3500],
|
|
|
+ status: "normal",
|
|
|
+ trend: "down",
|
|
|
+ change: -150,
|
|
|
+ },
|
|
|
+ temperature: {
|
|
|
+ current: 18.5,
|
|
|
+ normal: [15, 25],
|
|
|
+ status: "normal",
|
|
|
+ trend: "stable",
|
|
|
+ change: 0.1,
|
|
|
+ },
|
|
|
+ leakage: {
|
|
|
+ current: 0.02,
|
|
|
+ normal: [0, 0.05],
|
|
|
+ status: "warning",
|
|
|
+ trend: "up",
|
|
|
+ change: 0.01,
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+// 模拟历史趋势数据
|
|
|
+const mockTrendData = [
|
|
|
+ { time: "00:00", pressure: 0.38, flow: 2200, temperature: 16.2, leakage: 0.01 },
|
|
|
+ { time: "02:00", pressure: 0.39, flow: 2100, temperature: 16.8, leakage: 0.01 },
|
|
|
+ { time: "04:00", pressure: 0.41, flow: 2300, temperature: 17.2, leakage: 0.015 },
|
|
|
+ { time: "06:00", pressure: 0.43, flow: 2800, temperature: 18.1, leakage: 0.018 },
|
|
|
+ { time: "08:00", pressure: 0.42, flow: 3200, temperature: 18.5, leakage: 0.02 },
|
|
|
+ { time: "10:00", pressure: 0.41, flow: 3100, temperature: 18.8, leakage: 0.022 },
|
|
|
+ { time: "12:00", pressure: 0.4, flow: 2900, temperature: 19.2, leakage: 0.019 },
|
|
|
+ { time: "14:00", pressure: 0.42, flow: 2850, temperature: 18.5, leakage: 0.02 },
|
|
|
+]
|
|
|
+
|
|
|
+// 模拟设备监测数据
|
|
|
+const mockDeviceData = [
|
|
|
+ {
|
|
|
+ id: "D001",
|
|
|
+ name: "解放路压力监测点",
|
|
|
+ type: "压力监测",
|
|
|
+ value: 0.42,
|
|
|
+ unit: "MPa",
|
|
|
+ status: "正常",
|
|
|
+ lastUpdate: "2024-01-15 14:30:25",
|
|
|
+ location: "解放路调压站",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: "D002",
|
|
|
+ name: "人民路流量监测点",
|
|
|
+ type: "流量监测",
|
|
|
+ value: 2850,
|
|
|
+ unit: "m³/h",
|
|
|
+ status: "正常",
|
|
|
+ lastUpdate: "2024-01-15 14:30:20",
|
|
|
+ location: "人民路段",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: "D003",
|
|
|
+ name: "建设路温度监测点",
|
|
|
+ type: "温度监测",
|
|
|
+ value: 18.5,
|
|
|
+ unit: "°C",
|
|
|
+ status: "正常",
|
|
|
+ lastUpdate: "2024-01-15 14:30:15",
|
|
|
+ location: "建设路段",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: "D004",
|
|
|
+ name: "解放路泄漏检测点",
|
|
|
+ type: "泄漏检测",
|
|
|
+ value: 0.02,
|
|
|
+ unit: "ppm",
|
|
|
+ status: "预警",
|
|
|
+ lastUpdate: "2024-01-15 14:30:30",
|
|
|
+ location: "解放路北段",
|
|
|
+ },
|
|
|
+]
|
|
|
+
|
|
|
+export default function KeyIndicatorsMonitoring() {
|
|
|
+ const [selectedIndicator, setSelectedIndicator] = useState("pressure")
|
|
|
+ const [realTimeData, setRealTimeData] = useState(mockRealTimeData)
|
|
|
+
|
|
|
+ // 模拟实时数据更新
|
|
|
+ useEffect(() => {
|
|
|
+ const interval = setInterval(() => {
|
|
|
+ setRealTimeData((prev) => ({
|
|
|
+ ...prev,
|
|
|
+ pressure: {
|
|
|
+ ...prev.pressure,
|
|
|
+ current: +(prev.pressure.current + (Math.random() - 0.5) * 0.02).toFixed(3),
|
|
|
+ },
|
|
|
+ flow: {
|
|
|
+ ...prev.flow,
|
|
|
+ current: Math.round(prev.flow.current + (Math.random() - 0.5) * 100),
|
|
|
+ },
|
|
|
+ temperature: {
|
|
|
+ ...prev.temperature,
|
|
|
+ current: +(prev.temperature.current + (Math.random() - 0.5) * 0.5).toFixed(1),
|
|
|
+ },
|
|
|
+ leakage: {
|
|
|
+ ...prev.leakage,
|
|
|
+ current: +(prev.leakage.current + (Math.random() - 0.5) * 0.005).toFixed(3),
|
|
|
+ },
|
|
|
+ }))
|
|
|
+ }, 5000)
|
|
|
+
|
|
|
+ return () => clearInterval(interval)
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ const getStatusColor = (status: string) => {
|
|
|
+ switch (status) {
|
|
|
+ case "normal":
|
|
|
+ return "#52c41a"
|
|
|
+ case "warning":
|
|
|
+ return "#faad14"
|
|
|
+ case "danger":
|
|
|
+ return "#f5222d"
|
|
|
+ default:
|
|
|
+ return "#1890ff"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const getTrendIcon = (trend: string, change: number) => {
|
|
|
+ if (trend === "up") {
|
|
|
+ return <ArrowUpOutlined style={{ color: change > 0 ? "#f5222d" : "#52c41a" }} />
|
|
|
+ } else if (trend === "down") {
|
|
|
+ return <ArrowDownOutlined style={{ color: change < 0 ? "#f5222d" : "#52c41a" }} />
|
|
|
+ }
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ const gaugeOption = {
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: "gauge",
|
|
|
+ startAngle: 180,
|
|
|
+ endAngle: 0,
|
|
|
+ center: ["50%", "75%"],
|
|
|
+ radius: "90%",
|
|
|
+ min: 0,
|
|
|
+ max: 1,
|
|
|
+ splitNumber: 8,
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ width: 6,
|
|
|
+ color: [
|
|
|
+ [0.25, "#FF6E76"],
|
|
|
+ [0.5, "#FDDD60"],
|
|
|
+ [0.75, "#58D9F9"],
|
|
|
+ [1, "#7CFFB2"],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ pointer: {
|
|
|
+ icon: "path://M12.8,0.7l12,40.1H0.7L12.8,0.7z",
|
|
|
+ length: "12%",
|
|
|
+ width: 20,
|
|
|
+ offsetCenter: [0, "-60%"],
|
|
|
+ itemStyle: {
|
|
|
+ color: "auto",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ axisTick: {
|
|
|
+ length: 12,
|
|
|
+ lineStyle: {
|
|
|
+ color: "auto",
|
|
|
+ width: 2,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ length: 20,
|
|
|
+ lineStyle: {
|
|
|
+ color: "auto",
|
|
|
+ width: 5,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: "#464646",
|
|
|
+ fontSize: 20,
|
|
|
+ distance: -60,
|
|
|
+ rotate: "tangential",
|
|
|
+ formatter: (value: number) => {
|
|
|
+ if (value === 0.875) {
|
|
|
+ return "A+"
|
|
|
+ } else if (value === 0.625) {
|
|
|
+ return "A"
|
|
|
+ } else if (value === 0.375) {
|
|
|
+ return "B"
|
|
|
+ } else if (value === 0.125) {
|
|
|
+ return "C"
|
|
|
+ }
|
|
|
+ return ""
|
|
|
+ },
|
|
|
+ },
|
|
|
+ title: {
|
|
|
+ offsetCenter: [0, "-10%"],
|
|
|
+ fontSize: 20,
|
|
|
+ },
|
|
|
+ detail: {
|
|
|
+ fontSize: 30,
|
|
|
+ offsetCenter: [0, "-35%"],
|
|
|
+ valueAnimation: true,
|
|
|
+ formatter: (value: number) => Math.round(value * 100) / 100,
|
|
|
+ color: "inherit",
|
|
|
+ },
|
|
|
+ data: [
|
|
|
+ {
|
|
|
+ value: realTimeData.pressure.current,
|
|
|
+ name: "压力 (MPa)",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+
|
|
|
+ const getIndicatorName = (indicator: string) => {
|
|
|
+ const names = {
|
|
|
+ pressure: "压力",
|
|
|
+ flow: "流量",
|
|
|
+ temperature: "温度",
|
|
|
+ leakage: "泄漏",
|
|
|
+ }
|
|
|
+ return names[indicator as keyof typeof names] || indicator
|
|
|
+ }
|
|
|
+
|
|
|
+ const areaOption = {
|
|
|
+ title: { text: `${getIndicatorName(selectedIndicator)}趋势`, left: "center" },
|
|
|
+ tooltip: { trigger: "axis" },
|
|
|
+ grid: { left: "3%", right: "4%", bottom: "3%", containLabel: true },
|
|
|
+ xAxis: {
|
|
|
+ type: "category",
|
|
|
+ data: mockTrendData.map((item) => item.time),
|
|
|
+ },
|
|
|
+ yAxis: { type: "value" },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: "line",
|
|
|
+ data: mockTrendData.map((item) => item[selectedIndicator as keyof typeof item]),
|
|
|
+ smooth: true,
|
|
|
+ areaStyle: {
|
|
|
+ color: {
|
|
|
+ type: "linear",
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ x2: 0,
|
|
|
+ y2: 1,
|
|
|
+ colorStops: [
|
|
|
+ {
|
|
|
+ offset: 0,
|
|
|
+ color: "rgba(24, 144, 255, 0.8)",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ offset: 1,
|
|
|
+ color: "rgba(24, 144, 255, 0.1)",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ itemStyle: { color: "#1890ff" },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+
|
|
|
+ const deviceColumns = [
|
|
|
+ { title: "设备编号", dataIndex: "id", key: "id", width: 100 },
|
|
|
+ { title: "设备名称", dataIndex: "name", key: "name", width: 200 },
|
|
|
+ { title: "监测类型", dataIndex: "type", key: "type", width: 120 },
|
|
|
+ {
|
|
|
+ title: "当前值",
|
|
|
+ dataIndex: "value",
|
|
|
+ key: "value",
|
|
|
+ width: 100,
|
|
|
+ render: (value: number, record: any) => `${value} ${record.unit}`,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "状态",
|
|
|
+ dataIndex: "status",
|
|
|
+ key: "status",
|
|
|
+ width: 100,
|
|
|
+ render: (status: string) => {
|
|
|
+ const colorMap = { 正常: "green", 预警: "orange", 异常: "red" }
|
|
|
+ return <Tag color={colorMap[status as keyof typeof colorMap]}>{status}</Tag>
|
|
|
+ },
|
|
|
+ },
|
|
|
+ { title: "位置", dataIndex: "location", key: "location", width: 150 },
|
|
|
+ { title: "更新时间", dataIndex: "lastUpdate", key: "lastUpdate", width: 180 },
|
|
|
+ ]
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="p-6">
|
|
|
+ <Card title="关键指标实时监测">
|
|
|
+ {/* 实时指标概览 */}
|
|
|
+ <Row gutter={16} className="mb-6">
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small" className="text-center">
|
|
|
+ <Statistic
|
|
|
+ title="管网压力"
|
|
|
+ value={realTimeData.pressure.current}
|
|
|
+ suffix="MPa"
|
|
|
+ valueStyle={{ color: getStatusColor(realTimeData.pressure.status) }}
|
|
|
+ prefix={getTrendIcon(realTimeData.pressure.trend, realTimeData.pressure.change)}
|
|
|
+ />
|
|
|
+ <div className="text-xs text-gray-500 mt-2">
|
|
|
+ 正常范围: {realTimeData.pressure.normal[0]} - {realTimeData.pressure.normal[1]} MPa
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small" className="text-center">
|
|
|
+ <Statistic
|
|
|
+ title="流量监测"
|
|
|
+ value={realTimeData.flow.current}
|
|
|
+ suffix="m³/h"
|
|
|
+ valueStyle={{ color: getStatusColor(realTimeData.flow.status) }}
|
|
|
+ prefix={getTrendIcon(realTimeData.flow.trend, realTimeData.flow.change)}
|
|
|
+ />
|
|
|
+ <div className="text-xs text-gray-500 mt-2">
|
|
|
+ 正常范围: {realTimeData.flow.normal[0]} - {realTimeData.flow.normal[1]} m³/h
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small" className="text-center">
|
|
|
+ <Statistic
|
|
|
+ title="温度监测"
|
|
|
+ value={realTimeData.temperature.current}
|
|
|
+ suffix="°C"
|
|
|
+ valueStyle={{ color: getStatusColor(realTimeData.temperature.status) }}
|
|
|
+ prefix={getTrendIcon(realTimeData.temperature.trend, realTimeData.temperature.change)}
|
|
|
+ />
|
|
|
+ <div className="text-xs text-gray-500 mt-2">
|
|
|
+ 正常范围: {realTimeData.temperature.normal[0]} - {realTimeData.temperature.normal[1]} °C
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small" className="text-center">
|
|
|
+ <Statistic
|
|
|
+ title="泄漏检测"
|
|
|
+ value={realTimeData.leakage.current}
|
|
|
+ suffix="ppm"
|
|
|
+ valueStyle={{ color: getStatusColor(realTimeData.leakage.status) }}
|
|
|
+ prefix={getTrendIcon(realTimeData.leakage.trend, realTimeData.leakage.change)}
|
|
|
+ />
|
|
|
+ <div className="text-xs text-gray-500 mt-2">
|
|
|
+ 安全范围: {realTimeData.leakage.normal[0]} - {realTimeData.leakage.normal[1]} ppm
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ {/* 预警信息 */}
|
|
|
+ {realTimeData.leakage.status === "warning" && (
|
|
|
+ <Alert
|
|
|
+ message="泄漏预警"
|
|
|
+ description="解放路北段检测到燃气泄漏浓度超标,请及时处理"
|
|
|
+ type="warning"
|
|
|
+ icon={<WarningOutlined />}
|
|
|
+ showIcon
|
|
|
+ className="mb-6"
|
|
|
+ />
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 图表展示 */}
|
|
|
+ <Row gutter={16} className="mb-6">
|
|
|
+ <Col span={12}>
|
|
|
+ <Card title="压力监测仪表" size="small">
|
|
|
+ <EChart option={gaugeOption} style={{ height: 300 }} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={12}>
|
|
|
+ <Card
|
|
|
+ title="指标趋势分析"
|
|
|
+ size="small"
|
|
|
+ extra={
|
|
|
+ <Select value={selectedIndicator} onChange={setSelectedIndicator} style={{ width: 120 }}>
|
|
|
+ <Option value="pressure">压力</Option>
|
|
|
+ <Option value="flow">流量</Option>
|
|
|
+ <Option value="temperature">温度</Option>
|
|
|
+ <Option value="leakage">泄漏</Option>
|
|
|
+ </Select>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <EChart option={areaOption} style={{ height: 300 }} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ {/* 设备监测状态 */}
|
|
|
+ <Card title="设备监测状态" size="small">
|
|
|
+ <div className="mb-4">
|
|
|
+ <Space>
|
|
|
+ <Select placeholder="设备类型" style={{ width: 120 }}>
|
|
|
+ <Option value="pressure">压力监测</Option>
|
|
|
+ <Option value="flow">流量监测</Option>
|
|
|
+ <Option value="temperature">温度监测</Option>
|
|
|
+ <Option value="leak">泄漏检测</Option>
|
|
|
+ </Select>
|
|
|
+ <Select placeholder="设备状态" style={{ width: 120 }}>
|
|
|
+ <Option value="normal">正常</Option>
|
|
|
+ <Option value="warning">预警</Option>
|
|
|
+ <Option value="error">异常</Option>
|
|
|
+ </Select>
|
|
|
+ <RangePicker showTime />
|
|
|
+ </Space>
|
|
|
+ </div>
|
|
|
+ <Table
|
|
|
+ columns={deviceColumns}
|
|
|
+ dataSource={mockDeviceData}
|
|
|
+ rowKey="id"
|
|
|
+ pagination={{ pageSize: 10 }}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|