| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- "use client"
- import {useState} from "react"
- import {Circle, LayersControl, MapContainer, Marker, Polyline, Popup, TileLayer} from "react-leaflet"
- import {Badge, Card, Descriptions, Divider, Switch} from "antd"
- import {Icon} from "leaflet"
- import "leaflet/dist/leaflet.css"
- // 修复 Leaflet 默认图标问题
- if (typeof window !== "undefined") {
- delete (Icon.Default.prototype as any)._getIconUrl
- Icon.Default.mergeOptions({
- iconRetinaUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png",
- iconUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png",
- shadowUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png",
- })
- }
- const mockPipelineData = [
- {
- id: "pipeline_001",
- name: "五一大道主干管",
- coordinates: [
- [28.2282, 112.9388],
- [28.2292, 112.9398],
- [28.2302, 112.9408],
- [28.2312, 112.9418],
- ],
- pressure: "中压",
- material: "钢管",
- diameter: "DN400",
- status: "正常",
- },
- {
- id: "pipeline_002",
- name: "芙蓉路支管",
- coordinates: [
- [28.2312, 112.9418],
- [28.2322, 112.9428],
- [28.2332, 112.9438],
- ],
- pressure: "低压",
- material: "PE管",
- diameter: "DN200",
- status: "维护中",
- },
- {
- id: "pipeline_003",
- name: "韶山路干管",
- coordinates: [
- [28.2202, 112.9308],
- [28.2212, 112.9318],
- [28.2222, 112.9328],
- [28.2232, 112.9338],
- ],
- pressure: "高压",
- material: "钢管",
- diameter: "DN600",
- status: "正常",
- },
- ]
- const mockMonitoringDevices = [
- {
- id: "device_CS001",
- name: "压力监测点CS001",
- position: [28.2282, 112.9388],
- type: "压力监测",
- status: "在线",
- value: "0.28MPa",
- lastUpdate: "2024-01-15 14:30:00",
- },
- {
- id: "device_CS002",
- name: "流量监测点CS002",
- position: [28.2302, 112.9408],
- type: "流量监测",
- status: "在线",
- value: "156m³/h",
- lastUpdate: "2024-01-15 14:29:45",
- },
- {
- id: "device_CS003",
- name: "甲烷浓度监测CS003",
- position: [28.2322, 112.9428],
- type: "气体浓度",
- status: "报警",
- value: "0.6%LEL",
- lastUpdate: "2024-01-15 14:31:12",
- },
- {
- id: "device_CS004",
- name: "温度监测点CS004",
- position: [28.2222, 112.9328],
- type: "温度监测",
- status: "在线",
- value: "15.2°C",
- lastUpdate: "2024-01-15 14:30:30",
- },
- ]
- const mockHazardPoints = [
- {
- id: "hazard_CS001",
- name: "管道老化隐患",
- position: [28.2292, 112.9398],
- level: "较大",
- type: "设备老化",
- description: "五一大道段管道使用年限超过20年,存在老化风险",
- status: "待处理",
- },
- {
- id: "hazard_CS002",
- name: "地铁施工风险",
- position: [28.2332, 112.9438],
- level: "重大",
- type: "外部威胁",
- description: "芙蓉路地铁3号线施工,可能影响管道安全",
- status: "处理中",
- },
- {
- id: "hazard_CS003",
- name: "道路改造影响",
- position: [28.2212, 112.9318],
- level: "一般",
- type: "外部威胁",
- description: "韶山路改造工程可能对管道造成影响",
- status: "已处理",
- },
- ]
- const mockRiskAreas = [
- {
- id: "risk_CS001",
- center: [28.2292, 112.9398],
- radius: 200,
- level: "较大风险",
- color: "#faad14",
- },
- {
- id: "risk_CS002",
- center: [28.2332, 112.9438],
- radius: 300,
- level: "重大风险",
- color: "#f5222d",
- },
- {
- id: "risk_CS003",
- center: [28.2212, 112.9318],
- radius: 150,
- level: "低风险",
- color: "#52c41a",
- },
- ]
- // 自定义图标
- const createCustomIcon = (color: string, symbol: string) => {
- return new Icon({
- iconUrl: `data:image/svg+xml;base64,${btoa(`
- <svg width="25" height="41" viewBox="0 0 25 41" xmlns="http://www.w3.org/2000/svg">
- <path fill="${color}" stroke="#fff" strokeWidth="2" d="M12.5 0C5.6 0 0 5.6 0 12.5c0 12.5 12.5 28.5 12.5 28.5s12.5-16 12.5-28.5C25 5.6 19.4 0 12.5 0z"/>
- <text x="12.5" y="17" textAnchor="middle" fill="white" fontSize="12" fontWeight="bold">${symbol}</text>
- </svg>
- `)}`,
- iconSize: [25, 41],
- iconAnchor: [12, 41],
- popupAnchor: [1, -34],
- })
- }
- const deviceIcon = createCustomIcon("#1890ff", "D")
- const hazardIcon = createCustomIcon("#faad14", "!")
- const criticalHazardIcon = createCustomIcon("#f5222d", "!!")
- interface GasNetworkMapProps {
- height?: string
- }
- export default function GasNetworkMap({ height = "600px" }: GasNetworkMapProps) {
- const [showPipelines, setShowPipelines] = useState(true)
- const [showDevices, setShowDevices] = useState(true)
- const [showHazards, setShowHazards] = useState(true)
- const [showRiskAreas, setShowRiskAreas] = useState(true)
- return (
- <div className="w-full">
- <Card className="mb-4">
- <div className="flex flex-wrap gap-4 items-center">
- <div className="flex items-center gap-2">
- <Switch checked={showPipelines} onChange={setShowPipelines} size="small" />
- <span>管网管线</span>
- </div>
- <div className="flex items-center gap-2">
- <Switch checked={showDevices} onChange={setShowDevices} size="small" />
- <span>监测设备</span>
- </div>
- <div className="flex items-center gap-2">
- <Switch checked={showHazards} onChange={setShowHazards} size="small" />
- <span>隐患点位</span>
- </div>
- <div className="flex items-center gap-2">
- <Switch checked={showRiskAreas} onChange={setShowRiskAreas} size="small" />
- <span>风险区域</span>
- </div>
- </div>
- </Card>
- <Card>
- <div style={{ height }}>
- <MapContainer center={[28.2282, 112.9388]} zoom={14} style={{ height: "100%", width: "100%" }}>
- <LayersControl position="topright">
- <LayersControl.BaseLayer checked name="标准地图">
- <TileLayer
- attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
- url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
- />
- </LayersControl.BaseLayer>
- <LayersControl.BaseLayer name="卫星地图">
- <TileLayer
- attribution='© <a href="https://www.esri.com/">Esri</a>'
- url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
- />
- </LayersControl.BaseLayer>
- </LayersControl>
- {/* 管网管线 */}
- {showPipelines &&
- mockPipelineData.map((pipeline) => (
- <Polyline
- key={pipeline.id}
- positions={pipeline.coordinates as [number, number][]}
- color={pipeline.status === "正常" ? "#52c41a" : "#faad14"}
- weight={6}
- opacity={0.8}
- >
- <Popup>
- <div className="min-w-64">
- <h4 className="font-semibold mb-2">{pipeline.name}</h4>
- <Descriptions size="small" column={1}>
- <Descriptions.Item label="管道编号">{pipeline.id}</Descriptions.Item>
- <Descriptions.Item label="压力等级">{pipeline.pressure}</Descriptions.Item>
- <Descriptions.Item label="管材">{pipeline.material}</Descriptions.Item>
- <Descriptions.Item label="管径">{pipeline.diameter}</Descriptions.Item>
- <Descriptions.Item label="状态">
- <Badge status={pipeline.status === "正常" ? "success" : "warning"} text={pipeline.status} />
- </Descriptions.Item>
- </Descriptions>
- </div>
- </Popup>
- </Polyline>
- ))}
- {/* 监测设备 */}
- {showDevices &&
- mockMonitoringDevices.map((device) => (
- <Marker key={device.id} position={device.position as [number, number]} icon={deviceIcon}>
- <Popup>
- <div className="min-w-64">
- <h4 className="font-semibold mb-2">{device.name}</h4>
- <Descriptions size="small" column={1}>
- <Descriptions.Item label="设备编号">{device.id}</Descriptions.Item>
- <Descriptions.Item label="监测类型">{device.type}</Descriptions.Item>
- <Descriptions.Item label="当前值">{device.value}</Descriptions.Item>
- <Descriptions.Item label="状态">
- <Badge
- status={
- device.status === "在线" ? "success" : device.status === "报警" ? "error" : "default"
- }
- text={device.status}
- />
- </Descriptions.Item>
- <Descriptions.Item label="更新时间">{device.lastUpdate}</Descriptions.Item>
- </Descriptions>
- </div>
- </Popup>
- </Marker>
- ))}
- {/* 隐患点位 */}
- {showHazards &&
- mockHazardPoints.map((hazard) => (
- <Marker
- key={hazard.id}
- position={hazard.position as [number, number]}
- icon={hazard.level === "重大" ? criticalHazardIcon : hazardIcon}
- >
- <Popup>
- <div className="min-w-64">
- <h4 className="font-semibold mb-2">{hazard.name}</h4>
- <Descriptions size="small" column={1}>
- <Descriptions.Item label="隐患编号">{hazard.id}</Descriptions.Item>
- <Descriptions.Item label="风险等级">
- <Badge color={hazard.level === "重大" ? "red" : "orange"} text={hazard.level} />
- </Descriptions.Item>
- <Descriptions.Item label="隐患类型">{hazard.type}</Descriptions.Item>
- <Descriptions.Item label="处理状态">
- <Badge status={hazard.status === "待处理" ? "warning" : "processing"} text={hazard.status} />
- </Descriptions.Item>
- </Descriptions>
- <Divider style={{ margin: "8px 0" }} />
- <p className="text-sm text-gray-600">{hazard.description}</p>
- </div>
- </Popup>
- </Marker>
- ))}
- {/* 风险区域 */}
- {showRiskAreas &&
- mockRiskAreas.map((area) => (
- <Circle
- key={area.id}
- center={area.center as [number, number]}
- radius={area.radius}
- fillColor={area.color}
- fillOpacity={0.2}
- color={area.color}
- weight={2}
- >
- <Popup>
- <div>
- <h4 className="font-semibold mb-2">风险评估区域</h4>
- <p>
- 风险等级: <Badge color={area.color} text={area.level} />
- </p>
- <p>影响半径: {area.radius}米</p>
- </div>
- </Popup>
- </Circle>
- ))}
- </MapContainer>
- </div>
- </Card>
- </div>
- )
- }
|