|
|
@@ -0,0 +1,468 @@
|
|
|
+"use client"
|
|
|
+
|
|
|
+import {useState} from "react"
|
|
|
+import {Button, Card, Col, Row, Select, Slider, Space, Switch, Tag} from "antd"
|
|
|
+import {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 mockRiskPoints = [
|
|
|
+ {
|
|
|
+ id: "R001",
|
|
|
+ name: "解放路段高压管线风险",
|
|
|
+ position: [39.9042, 116.4074],
|
|
|
+ riskLevel: "高",
|
|
|
+ riskScore: 85,
|
|
|
+ riskType: "管线老化",
|
|
|
+ color: "#f5222d",
|
|
|
+ affectedPopulation: 2000,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: "R002",
|
|
|
+ name: "人民路段泄漏风险",
|
|
|
+ position: [39.9052, 116.4084],
|
|
|
+ riskLevel: "中",
|
|
|
+ riskScore: 65,
|
|
|
+ riskType: "泄漏隐患",
|
|
|
+ color: "#faad14",
|
|
|
+ affectedPopulation: 800,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: "R003",
|
|
|
+ name: "建设路段压力风险",
|
|
|
+ position: [39.9032, 116.4064],
|
|
|
+ riskLevel: "低",
|
|
|
+ riskScore: 35,
|
|
|
+ riskType: "压力异常",
|
|
|
+ color: "#52c41a",
|
|
|
+ affectedPopulation: 300,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: "R004",
|
|
|
+ name: "商业街管网风险",
|
|
|
+ position: [39.9062, 116.4094],
|
|
|
+ riskLevel: "中",
|
|
|
+ riskScore: 70,
|
|
|
+ riskType: "管线老化",
|
|
|
+ color: "#faad14",
|
|
|
+ affectedPopulation: 1500,
|
|
|
+ },
|
|
|
+]
|
|
|
+
|
|
|
+// 模拟风险区域数据(热力图)
|
|
|
+const mockRiskHeatmapData = [
|
|
|
+ { x: "解放路", y: "北段", value: 85 },
|
|
|
+ { x: "解放路", y: "中段", value: 65 },
|
|
|
+ { x: "解放路", y: "南段", value: 45 },
|
|
|
+ { x: "人民路", y: "北段", value: 70 },
|
|
|
+ { x: "人民路", y: "中段", value: 55 },
|
|
|
+ { x: "人民路", y: "南段", value: 40 },
|
|
|
+ { x: "建设路", y: "北段", value: 35 },
|
|
|
+ { x: "建设路", y: "中段", value: 50 },
|
|
|
+ { x: "建设路", y: "南段", value: 60 },
|
|
|
+ { x: "商业街", y: "东段", value: 75 },
|
|
|
+ { x: "商业街", y: "西段", value: 80 },
|
|
|
+]
|
|
|
+
|
|
|
+// 模拟风险统计数据
|
|
|
+const mockRiskTypeStats = [
|
|
|
+ { type: "管线老化", count: 12, percentage: 48 },
|
|
|
+ { type: "泄漏隐患", count: 8, percentage: 32 },
|
|
|
+ { type: "压力异常", count: 3, percentage: 12 },
|
|
|
+ { type: "其他", count: 2, percentage: 8 },
|
|
|
+]
|
|
|
+
|
|
|
+const mockRiskLevelStats = [
|
|
|
+ { level: "高风险", count: 5, color: "#f5222d" },
|
|
|
+ { level: "中风险", count: 12, color: "#faad14" },
|
|
|
+ { level: "低风险", count: 8, color: "#52c41a" },
|
|
|
+]
|
|
|
+
|
|
|
+const mockRiskTrendData = [
|
|
|
+ { month: "2023-07", 高风险: 3, 中风险: 8, 低风险: 12 },
|
|
|
+ { month: "2023-08", 高风险: 4, 中风险: 10, 低风险: 11 },
|
|
|
+ { month: "2023-09", 高风险: 6, 中风险: 12, 低风险: 9 },
|
|
|
+ { month: "2023-10", 高风险: 5, 中风险: 11, 低风险: 10 },
|
|
|
+ { month: "2023-11", 高风险: 4, 中风险: 9, 低风险: 12 },
|
|
|
+ { month: "2023-12", 高风险: 3, 中风险: 8, 低风险: 14 },
|
|
|
+ { month: "2024-01", 高风险: 5, 中风险: 12, 低风险: 8 },
|
|
|
+]
|
|
|
+
|
|
|
+export default function RiskVisualization() {
|
|
|
+ const [layerVisibility, setLayerVisibility] = useState({
|
|
|
+ pipeline: true,
|
|
|
+ highRisk: true,
|
|
|
+ mediumRisk: true,
|
|
|
+ lowRisk: true,
|
|
|
+ riskHeatmap: true,
|
|
|
+ riskBuffer: true,
|
|
|
+ })
|
|
|
+ const [riskThreshold, setRiskThreshold] = useState([40, 70])
|
|
|
+ const [selectedRiskType, setSelectedRiskType] = useState("all")
|
|
|
+ const [isFullscreen, setIsFullscreen] = useState(false)
|
|
|
+
|
|
|
+ const handleLayerToggle = (layer: string, visible: boolean) => {
|
|
|
+ setLayerVisibility((prev) => ({
|
|
|
+ ...prev,
|
|
|
+ [layer]: visible,
|
|
|
+ }))
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleRefresh = () => {
|
|
|
+ console.log("刷新风险数据")
|
|
|
+ }
|
|
|
+
|
|
|
+ const getRiskLevelColor = (level: string) => {
|
|
|
+ switch (level) {
|
|
|
+ case "高":
|
|
|
+ return "#f5222d"
|
|
|
+ case "中":
|
|
|
+ return "#faad14"
|
|
|
+ case "低":
|
|
|
+ return "#52c41a"
|
|
|
+ default:
|
|
|
+ return "#1890ff"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const pieOption = {
|
|
|
+ title: { text: "风险类型分布", left: "center" },
|
|
|
+ tooltip: { trigger: "item" },
|
|
|
+ legend: { orient: "vertical", left: "left" },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: "pie",
|
|
|
+ radius: "50%",
|
|
|
+ data: mockRiskTypeStats.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: "风险趋势分析", left: "center" },
|
|
|
+ tooltip: { trigger: "axis" },
|
|
|
+ legend: { data: ["高风险", "中风险", "低风险"] },
|
|
|
+ grid: { left: "3%", right: "4%", bottom: "3%", containLabel: true },
|
|
|
+ xAxis: {
|
|
|
+ type: "category",
|
|
|
+ data: mockRiskTrendData.map((item) => item.month),
|
|
|
+ },
|
|
|
+ yAxis: { type: "value", stack: "total" },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: "高风险",
|
|
|
+ type: "bar",
|
|
|
+ stack: "total",
|
|
|
+ data: mockRiskTrendData.map((item) => item.高风险),
|
|
|
+ itemStyle: { color: "#f5222d" },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "中风险",
|
|
|
+ type: "bar",
|
|
|
+ stack: "total",
|
|
|
+ data: mockRiskTrendData.map((item) => item.中风险),
|
|
|
+ itemStyle: { color: "#faad14" },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "低风险",
|
|
|
+ type: "bar",
|
|
|
+ stack: "total",
|
|
|
+ data: mockRiskTrendData.map((item) => item.低风险),
|
|
|
+ itemStyle: { color: "#52c41a" },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+
|
|
|
+ const heatmapOption = {
|
|
|
+ title: { text: "区域风险热力图", left: "center" },
|
|
|
+ tooltip: { position: "top" },
|
|
|
+ grid: { height: "50%", top: "10%" },
|
|
|
+ xAxis: {
|
|
|
+ type: "category",
|
|
|
+ data: [...new Set(mockRiskHeatmapData.map((item) => item.x))],
|
|
|
+ splitArea: { show: true },
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: "category",
|
|
|
+ data: [...new Set(mockRiskHeatmapData.map((item) => item.y))],
|
|
|
+ splitArea: { show: true },
|
|
|
+ },
|
|
|
+ visualMap: {
|
|
|
+ min: 0,
|
|
|
+ max: 100,
|
|
|
+ calculable: true,
|
|
|
+ orient: "horizontal",
|
|
|
+ left: "center",
|
|
|
+ bottom: "15%",
|
|
|
+ inRange: {
|
|
|
+ color: ["#52c41a", "#faad14", "#f5222d"],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: "heatmap",
|
|
|
+ data: mockRiskHeatmapData.map((item) => [item.x, item.y, item.value]),
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ color: "#fff",
|
|
|
+ fontSize: 12,
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowColor: "rgba(0, 0, 0, 0.5)",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+
|
|
|
+ const highRiskCount = mockRiskPoints.filter((point) => point.riskLevel === "高").length
|
|
|
+ const mediumRiskCount = mockRiskPoints.filter((point) => point.riskLevel === "中").length
|
|
|
+ const lowRiskCount = mockRiskPoints.filter((point) => point.riskLevel === "低").length
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="p-6">
|
|
|
+ <Card title="风险评估可视化">
|
|
|
+ {/* 风险概览统计 */}
|
|
|
+ <Row gutter={16} className="mb-6">
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small">
|
|
|
+ <div className="text-center">
|
|
|
+ <div className="text-2xl font-bold text-red-600">{highRiskCount}</div>
|
|
|
+ <div className="text-gray-600">高风险区域</div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small">
|
|
|
+ <div className="text-center">
|
|
|
+ <div className="text-2xl font-bold text-orange-600">{mediumRiskCount}</div>
|
|
|
+ <div className="text-gray-600">中风险区域</div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small">
|
|
|
+ <div className="text-center">
|
|
|
+ <div className="text-2xl font-bold text-blue-600">{lowRiskCount}</div>
|
|
|
+ <div className="text-gray-600">低风险区域</div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card size="small">
|
|
|
+ <div className="text-center">
|
|
|
+ <div className="text-2xl font-bold text-gray-600">{mockRiskPoints.length}</div>
|
|
|
+ <div className="text-gray-600">风险点总数</div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ {/* 图层控制 */}
|
|
|
+ <Row gutter={16} className="mb-4">
|
|
|
+ <Col span={24}>
|
|
|
+ <Card size="small" title="风险图层控制">
|
|
|
+ <Row gutter={16}>
|
|
|
+ <Col span={4}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.pipeline}
|
|
|
+ onChange={(checked) => handleLayerToggle("pipeline", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">管网管线</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={4}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.highRisk}
|
|
|
+ onChange={(checked) => handleLayerToggle("highRisk", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">高风险区域</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={4}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.mediumRisk}
|
|
|
+ onChange={(checked) => handleLayerToggle("mediumRisk", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">中风险区域</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={4}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.lowRisk}
|
|
|
+ onChange={(checked) => handleLayerToggle("lowRisk", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">低风险区域</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={4}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.riskHeatmap}
|
|
|
+ onChange={(checked) => handleLayerToggle("riskHeatmap", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">风险热力图</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ <Col span={4}>
|
|
|
+ <div className="mb-2">
|
|
|
+ <Switch
|
|
|
+ checked={layerVisibility.riskBuffer}
|
|
|
+ onChange={(checked) => handleLayerToggle("riskBuffer", checked)}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ <span className="ml-2">影响范围</span>
|
|
|
+ </div>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ {/* 风险控制参数 */}
|
|
|
+ <Row gutter={16} className="mb-4">
|
|
|
+ <Col span={8}>
|
|
|
+ <Card size="small" title="风险阈值设置">
|
|
|
+ <div className="mb-2">
|
|
|
+ <span className="text-sm text-gray-600">风险评分范围: </span>
|
|
|
+ </div>
|
|
|
+ <Slider
|
|
|
+ range
|
|
|
+ min={0}
|
|
|
+ max={100}
|
|
|
+ value={riskThreshold}
|
|
|
+ onChange={setRiskThreshold}
|
|
|
+ marks={{ 0: "0", 40: "40", 70: "70", 100: "100" }}
|
|
|
+ />
|
|
|
+ <div className="text-center text-sm text-gray-500 mt-2">
|
|
|
+ 当前范围: {riskThreshold[0]} - {riskThreshold[1]}
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={8}>
|
|
|
+ <Card size="small" title="风险类型筛选">
|
|
|
+ <Select value={selectedRiskType} onChange={setSelectedRiskType} style={{ width: "100%" }}>
|
|
|
+ <Option value="all">全部类型</Option>
|
|
|
+ <Option value="aging">管线老化</Option>
|
|
|
+ <Option value="leak">泄漏隐患</Option>
|
|
|
+ <Option value="pressure">压力异常</Option>
|
|
|
+ <Option value="other">其他</Option>
|
|
|
+ </Select>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={8}>
|
|
|
+ <Card size="small" title="操作控制">
|
|
|
+ <Space>
|
|
|
+ <Button icon={<ReloadOutlined />} onClick={handleRefresh} size="small">
|
|
|
+ 刷新数据
|
|
|
+ </Button>
|
|
|
+ <Button icon={<FullscreenOutlined />} onClick={() => setIsFullscreen(!isFullscreen)} size="small">
|
|
|
+ 全屏显示
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ {/* 风险四色图例 */}
|
|
|
+ <Row gutter={16} className="mb-4">
|
|
|
+ <Col span={24}>
|
|
|
+ <Card size="small" title="风险四色图例">
|
|
|
+ <Space>
|
|
|
+ <Tag color="#f5222d">红色 - 高风险 (80-100分)</Tag>
|
|
|
+ <Tag color="#faad14">橙色 - 中高风险 (60-79分)</Tag>
|
|
|
+ <Tag color="#52c41a">黄色 - 中低风险 (40-59分)</Tag>
|
|
|
+ <Tag color="#1890ff">蓝色 - 低风险 (0-39分)</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: 200 }} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={24}>
|
|
|
+ <Card title="风险趋势分析" size="small" className="mb-4">
|
|
|
+ <EChart option={columnOption} style={{ height: 200 }} />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={24}>
|
|
|
+ <Card title="区域风险热力图" size="small">
|
|
|
+ <EChart option={heatmapOption} style={{ height: 200 }} />
|
|
|
+ </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">
|
|
|
+ {mockRiskPoints
|
|
|
+ .filter((point) => point.riskLevel === "高")
|
|
|
+ .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={getRiskLevelColor(point.riskLevel)}>{point.riskLevel}风险</Tag>
|
|
|
+ <Tag color="blue">评分: {point.riskScore}</Tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="text-sm text-gray-600 space-y-1">
|
|
|
+ <div>风险类型: {point.riskType}</div>
|
|
|
+ <div>影响人口: {point.affectedPopulation}人</div>
|
|
|
+ <div>风险等级: {point.riskLevel}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|