"use client" import React, {useEffect, useMemo, useState, useRef} from "react" import type {MenuProps, TableColumnsType} from "antd" import { Badge, Button, Card, Col, ConfigProvider, DatePicker, Descriptions, Divider, Drawer, Empty, Layout, Menu, Progress, Row, Segmented, Select, Space, Statistic, Switch, Table, Tabs, Tag, Timeline, Tooltip as AntdTooltip, } from "antd" import { Activity, AlertTriangle, BellRing, ClipboardList, Database, Droplets, Factory, Flame, Gauge, Layers3, LineChartIcon, Moon, Settings2, ShieldCheck, Sun, Table2, TrendingUp, Users, Waves } from 'lucide-react' import EChart from "@/components/echarts" import dayjs from "dayjs" import "dayjs/locale/zh-cn" import zhCN from "antd/es/locale/zh_CN" import {MapContainer, Marker, Popup, TileLayer} from 'react-leaflet' import 'leaflet/dist/leaflet.css' import L from 'leaflet' //地图控件部分 import './BaiduMapPage.css'; import dataJson from './index.json'; import markerData from './markerData.json'; import fameng from './fameng.png'; import shuifa from './shuifa.png'; import deom1 from './deom1.png'; import deom2 from './deom2.png'; import Uptow from './up-two.png'; import BaiduMapPage from "@/app/(business)/test/test2/page"; // 修复Leaflet默认图标问题 delete (L.Icon.Default.prototype as any)._getIconUrl; L.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', }); dayjs.locale("zh-cn") const { Header, Sider, Content } = Layout const { RangePicker } = DatePicker // Helpers const primary = "#10b981" // teal, avoid blue const danger = "#ef4444" const warn = "#f59e0b" const ok = "#22c55e" const muted = "#6b7280" type Facility = { id: string industry: "燃气" | "供水" | "排水" type: string count: number age: number region: string } type Device = { id: string type: string status: "在线" | "离线" industry: Facility["industry"] risk: "高" | "中" | "低" lat: number lng: number } type Alert = { id: string level: "I级" | "II级" | "III级" | "IV级" industry: Facility["industry"] type: string time: string status: "待处置" | "处置中" | "已闭环" location: string desc: string } function useMockData() { const [facilities] = useState(() => { const regions = ["中心城区", "东区", "西区", "南区", "北区"] const types = { 燃气: ["高压管道", "调压站", "门站", "阀门井"], 供水: ["原水管", "净水厂", "二次供水", "监测点"], 排水: ["主干管", "泵站", "检查井", "溢流口"], } as const let i = 0 return (["燃气", "供水", "排水"] as Facility["industry"][]).flatMap((ind) => types[ind].map((t) => ({ id: `f-${i++}`, industry: ind, type: t, count: Math.floor(Math.random() * 900 + 100), age: Math.floor(Math.random() * 30 + 1), region: regions[Math.floor(Math.random() * regions.length)], })) ) }) const [devices, setDevices] = useState(() => { const types = ["压力", "流量", "液位", "阀位", "水质", "气体浓度"] let i = 0 return Array.from({ length: 250 }).map(() => ({ id: `d-${i++}`, type: types[Math.floor(Math.random() * types.length)], status: Math.random() > 0.12 ? "在线" : "离线", industry: (["燃气", "供水", "排水"] as const)[Math.floor(Math.random() * 3)], risk: Math.random() > 0.8 ? "高" : Math.random() > 0.5 ? "中" : "低", lat: Math.random(), lng: Math.random(), })) }) // Simulate device status fluctuation useEffect(() => { const t = setInterval(() => { setDevices((prev) => prev.map((d) => Math.random() > 0.97 ? { ...d, status: d.status === "在线" ? "离线" : "在线" } : d ) ) }, 4000) return () => clearInterval(t) }, []) const [alerts, setAlerts] = useState(() => { const levels: Alert["level"][] = ["I级", "II级", "III级", "IV级"] const types = ["泄漏", "压力异常", "流量突变", "停电", "液位过低", "浊度异常"] let i = 0 return Array.from({ length: 30 }).map(() => ({ id: `a-${i++}`, level: levels[Math.floor(Math.random() * levels.length)], industry: (["燃气", "供水", "排水"] as const)[Math.floor(Math.random() * 3)], type: types[Math.floor(Math.random() * types.length)], time: dayjs().subtract(Math.floor(Math.random() * 200), "minute").format("YYYY-MM-DD HH:mm"), status: Math.random() > 0.6 ? "已闭环" : Math.random() > 0.3 ? "处置中" : "待处置", location: ["中心城区", "东区", "西区", "南区", "北区"][Math.floor(Math.random() * 5)], desc: "自动监测发现异常,已推送至管理单位核实。", })) }) // Simulate new alerts useEffect(() => { const t = setInterval(() => { setAlerts((prev) => { const n: Alert = { id: `a-${prev.length + 1}`, level: Math.random() > 0.85 ? "I级" : Math.random() > 0.6 ? "II级" : Math.random() > 0.3 ? "III级" : "IV级", industry: (["燃气", "供水", "排水"] as const)[Math.floor(Math.random() * 3)], type: ["泄漏", "压力异常", "流量突变", "停电", "液位过低", "浊度异常"][Math.floor(Math.random() * 6)], time: dayjs().format("YYYY-MM-DD HH:mm"), status: "待处置", location: ["中心城区", "东区", "西区", "南区", "北区"][Math.floor(Math.random() * 5)], desc: "前端设备上报新预警,请尽快核查。", } return [n, ...prev].slice(0, 60) }) }, 20000) return () => clearInterval(t) }, []) return { facilities, devices, alerts } } function LevelTag({ level }: { level: Alert["level"] }) { const map = { "I级": { color: danger }, "II级": { color: warn }, "III级": { color: "#f97316" }, "IV级": { color: ok }, } as const return {level} } function StatusBadge({ s }: { s: Alert["status"] }) { const status = { 待处置: "error", 处置中: "processing", 已闭环: "success", } as const // @ts-expect-error antd types for Badge status return } export default function Page() { const [collapsed, setCollapsed] = useState(false) const { facilities, devices, alerts } = useMockData() const [selectedMenu, setSelectedMenu] = useState("overview") const [drawerOpen, setDrawerOpen] = useState(false) const [selectedAlert, setSelectedAlert] = useState(null) const [isDark, setIsDark] = useState(false) // 默认白色主题 const [assetTab, setAssetTab] = useState("archive") const [monitorTab, setMonitorTab] = useState("summary") const [alertTab, setAlertTab] = useState("current") const totals = useMemo(() => { const sum = { 燃气: 0, 供水: 0, 排水: 0 } as Record facilities.forEach((f) => (sum[f.industry] += f.count)) return sum }, [facilities]) const onlineRate = useMemo(() => { const total = devices.length const online = devices.filter((d) => d.status === "在线").length return Math.round((online / Math.max(total, 1)) * 100) }, [devices]) const riskDist = useMemo(() => { const dist = { 高: 0, 中: 0, 低: 0 } as Record devices.forEach((d) => (dist[d.risk] += 1)) return dist }, [devices]) // Menu const menuItems: MenuProps["items"] = [ { key: "overview", icon: , label: "总体概览" }, { key: "asset", icon: , label: "基础设施管理" }, { key: "monitor", icon: , label: "运行监测管理" }, { key: "alert", icon: , label: "预警处置管理" }, { type: "divider" as const }, { key: "devices", icon: , label: "设备管理" }, { key: "reports", icon: , label: "数据报表" }, { key: "incidents", icon: , label: "事件中心" }, { key: "bigscreen", icon: , label: "数据大屏(示意)" }, { type: "divider" as const }, { key: "settings", icon: , label: "设置" }, ] // Charts Options const infraPieOption = useMemo( () => ({ title: { text: "设施类型占比", left: "center", textStyle: { fontSize: 14 } }, tooltip: { trigger: "item" }, legend: { bottom: 0 }, color: [primary, "#f59e0b", "#6366f1", "#ef4444", "#14b8a6", "#a855f7"], series: [ { type: "pie", radius: ["35%", "60%"], avoidLabelOverlap: false, itemStyle: { borderRadius: 6, borderColor: "#fff", borderWidth: 2 }, data: ["燃气", "供水", "排水"].map((ind) => ({ name: ind, value: facilities .filter((f) => f.industry === ind) .reduce((acc, f) => acc + f.count, 0), })), }, ], }), [facilities] ) const deviceBarOption = useMemo(() => { const types = Array.from(new Set(devices.map((d) => d.type))) const byType = types.map((t) => devices.filter((d) => d.type === t).length) return { title: { text: "监测设备类型规模", left: "center", textStyle: { fontSize: 14 } }, tooltip: { trigger: "axis" }, xAxis: { type: "category", data: types, axisLabel: { rotate: 20 } }, yAxis: { type: "value" }, grid: { left: 40, right: 10, bottom: 50, top: 40 }, color: [primary], series: [{ type: "bar", data: byType, barWidth: "50%", itemStyle: { borderRadius: [4, 4, 0, 0] } }], } as const }, [devices]) const alertTrendOption = useMemo(() => { const days = 14 const labels = Array.from({ length: days }).map((_, i) => dayjs().subtract(days - i - 1, "day").format("MM-DD")) const series = (["燃气", "供水", "排水"] as const).map((ind, idx) => ({ name: ind, type: "line", smooth: true, data: labels.map(() => Math.floor(Math.random() * (idx === 0 ? 12 : idx === 1 ? 9 : 7)) + (idx === 0 ? 3 : 1)), })) return { title: { text: "预警趋势(近14天)", left: "center", textStyle: { fontSize: 14 } }, tooltip: { trigger: "axis" }, legend: { bottom: 0 }, grid: { left: 40, right: 10, bottom: 40, top: 40 }, xAxis: { type: "category", data: labels }, yAxis: { type: "value" }, color: [primary, "#f59e0b", "#a855f7"], series, } as const }, []) const onlineGaugeOption = useMemo( () => ({ title: { text: "设备在线率", left: "center", top: 10, textStyle: { fontSize: 14 } }, series: [ { type: "gauge", startAngle: 200, endAngle: -20, radius: "90%", pointer: { show: false }, progress: { show: true, width: 16, itemStyle: { color: onlineRate > 95 ? ok : onlineRate > 85 ? "#84cc16" : warn } }, axisLine: { lineStyle: { width: 16 } }, axisTick: { show: false }, splitLine: { show: false }, axisLabel: { show: false }, detail: { valueAnimation: true, formatter: "{value}%", fontSize: 22, offsetCenter: [0, "10%"] }, data: [{ value: onlineRate }], }, ], }), [onlineRate] ) const riskBarOption = useMemo( () => ({ title: { text: "风险等级分布", left: "center", textStyle: { fontSize: 14 } }, tooltip: { trigger: "axis" }, grid: { left: 40, right: 10, top: 40, bottom: 20 }, xAxis: { type: "category", data: ["高", "中", "低"] }, yAxis: { type: "value" }, color: [danger, warn, ok], series: [{ type: "bar", data: [riskDist["高"], riskDist["中"], riskDist["低"]], barWidth: "50%" }], }), [riskDist] ) const efficiencyPieOption = useMemo(() => { const closed = alerts.filter((a) => a.status === "已闭环").length const correct = Math.floor(closed * (0.85 + Math.random() * 0.1)) // 假定人工复核正确率 const wrong = Math.max(closed - correct, 0) return { title: { text: "预警正确性(闭环内)", left: "center", textStyle: { fontSize: 14 } }, tooltip: { trigger: "item", formatter: "{b}: {c} ({d}%)" }, legend: { bottom: 0 }, color: [ok, danger], series: [ { type: "pie", radius: ["35%", "60%"], data: [ { name: "正确预警", value: correct }, { name: "误报", value: wrong }, ], }, ], } as const }, [alerts]) const radarCapacityOption = useMemo( () => ({ title: { text: "应急保障能力雷达", left: "center", textStyle: { fontSize: 14 } }, tooltip: {}, radar: { indicator: [ { name: "抢修速度", max: 100 }, { name: "物资充足", max: 100 }, { name: "跨部门联动", max: 100 }, { name: "覆盖广度", max: 100 }, { name: "应急演练", max: 100 }, ], }, color: [primary], series: [ { type: "radar", data: [{ value: [78, 85, 72, 88, 76], name: "当前" }], areaStyle: { color: primary + "33" }, }, ], }), [] ) // Tables const facilityColumns: TableColumnsType = [ { title: "行业", dataIndex: "industry", key: "industry", width: 90 }, { title: "类型", dataIndex: "type", key: "type" }, { title: "数量", dataIndex: "count", key: "count", width: 100 }, { title: "平均年限", dataIndex: "age", key: "age", width: 120, render: (v) => `${v} 年` }, { title: "分布区域", dataIndex: "region", key: "region", width: 120 }, ] const alertColumns: TableColumnsType = [ { title: "等级", dataIndex: "level", key: "level", width: 90, render: (v) => }, { title: "行业", dataIndex: "industry", key: "industry", width: 90 }, { title: "类型", dataIndex: "type", key: "type", width: 140 }, { title: "时间", dataIndex: "time", key: "time", width: 160 }, { title: "状态", dataIndex: "status", key: "status", width: 120, render: (s) => }, { title: "位置", dataIndex: "location", key: "location", width: 120 }, { title: "操作", key: "action", render: (_, record) => ( { setSelectedAlert(record) setDrawerOpen(true) }} > 查看 派单 联动 ), width: 160, fixed: "right", }, ] const deviceColumns: TableColumnsType = [ { title: "行业", dataIndex: "industry", key: "industry", width: 90 }, { title: "类型", dataIndex: "type", key: "type", width: 100 }, { title: "状态", dataIndex: "status", key: "status", width: 100, render: (s) => , filters: [ { text: "在线", value: "在线" }, { text: "离线", value: "离线" }, ], onFilter: (v, r) => r.status === v, }, { title: "风险", dataIndex: "risk", key: "risk", width: 100, render: (r) => {r}, filters: [ { text: "高", value: "高" }, { text: "中", value: "中" }, { text: "低", value: "低" }, ], onFilter: (v, r) => r.risk === v, }, { title: "编号", dataIndex: "id", key: "id" }, ] const alertLevelColor = (lvl: Alert["level"]) => lvl === "I级" ? danger : lvl === "II级" ? warn : lvl === "III级" ? "#f97316" : ok // Derived numbers const stats = [ { title: "燃气总规模", value: totals["燃气"], icon: , bg: "bg-emerald-500", }, { title: "供水总规模", value: totals["供水"], icon: , bg: "bg-teal-500", }, { title: "排水总规模", value: totals["排水"], icon: , bg: "bg-cyan-500", }, { title: "在线设备", value: devices.filter((d) => d.status === "在线").length, icon: , bg: "bg-lime-500", }, ] // Map markers for device distribution const markers = devices.slice(0, 120).map((d) => ({ id: d.id, top: `${Math.floor(d.lat * 85) + 5}%`, left: `${Math.floor(d.lng * 90) + 5}%`, color: d.risk === "高" ? danger : d.risk === "中" ? warn : ok, title: `${d.industry}/${d.type}(${d.status})`, })) const themeTokens = useMemo( () => ({ token: { colorPrimary: primary, colorInfo: primary, borderRadius: 8, fontSize: 13, }, components: { Layout: { headerBg: isDark ? "#0f172a" : "#ffffff", siderBg: isDark ? "#0b1220" : "#ffffff", bodyBg: isDark ? "#0b1220" : "#f6f7f9", }, Menu: { itemSelectedBg: isDark ? "#0ea5a8" : "#d1fae5", itemSelectedColor: isDark ? "#fff" : "#0f172a", itemActiveBg: "#0ea5a822", }, }, }), [isDark] ) // const BaiduMapPage = () => { const mapRef = useRef(null); const [map, setMap] = useState(null); var lineLayer = null; var icon1 = deom1.src; var icon2 = deom2.src; var upTwo = Uptow.src; // 初始化地图 useEffect(() => { // 检查是否已加载百度地图 GL JS API if (!window.BMapGL) { // 动态加载百度地图 GL JS API const script = document.createElement('script'); script.src = `https://api.map.baidu.com/api?v=2.0&ak=jWDCUpsk33htQsF6IEwk4ctkERTOFFH0&type=webgl`; script.async = true; document.head.appendChild(script); window.initMap = () => { initializeMap(); }; } else { initializeMap(); } return () => { // 清理工作 if (map) { map.destroy(); } }; }, []); const initializeMap = () => { // 创建地图实例 const mapInstance = new window.BMapGL.Map(mapRef.current); // 设置中心点坐标(北京) const point = new window.BMapGL.Point(110.39573035064166, 28.44628067667752); mapInstance.centerAndZoom(point, 16); // 启用滚轮放大缩小 mapInstance.enableScrollWheelZoom(true); // 添加控件 // mapInstance.addControl(new window.BMapGL.NavigationControl()); // mapInstance.addControl(new window.BMapGL.ScaleControl()); // mapInstance.addControl(new window.BMapGL.OverviewMapControl()); // mapInstance.addControl(new window.BMapGL.MapTypeControl()); mapInstance.setMinZoom(16); mapInstance.setMaxZoom(19); mapInstance.setMapStyleV2({ styleId: '62ce43ad2a1362c23e612b783d7406e7' }); markerData.list.dataOne.forEach(item => { const icon = new window.BMapGL.Icon(fameng.src, new window.BMapGL.Size(23, 25), { anchor: new window.BMapGL.Size(10, 25), }); const markers = new window.BMapGL.Point(item.lng, item.lat); const marker = new window.BMapGL.Marker(markers,{ icon:icon }); mapInstance.addOverlay(marker); // 创建信息窗口 let opts = { width: 300, title: item.name }; let infoWindow = new window.BMapGL.InfoWindow("倾斜角度:" + '188deg' + "
" + "状态:" + '正常运行' + "
" + "信噪比:" + '16db' + "
" + "负责人:" + '张三' + "
" + "联系方式:" + '121234564', opts); // 点标记添加点击事件 marker.addEventListener('click', function() { mapInstance.openInfoWindow(infoWindow, markers); // 开启信息窗口 }); }) markerData.list.dataTwo.forEach(item => { const icon = new window.BMapGL.Icon(shuifa.src, new window.BMapGL.Size(23, 25), { anchor: new window.BMapGL.Size(20, 0), }); const markers = new window.BMapGL.Point(item.lng, item.lat); const marker = new window.BMapGL.Marker(markers,{ icon:icon }); mapInstance.addOverlay(marker); // 创建信息窗口 let opts = { width: 300, title: item.name }; let infoWindow = new window.BMapGL.InfoWindow("倾斜角度:" + '188deg' + "
" + "状态:" + '正常运行' + "
" + "信噪比:" + '16db' + "
" + "负责人:" + '张三' + "
" + "联系方式:" + '121234564', opts); // 点标记添加点击事件 marker.addEventListener('click', function() { mapInstance.openInfoWindow(infoWindow, markers); // 开启信息窗口 }); }) if (!lineLayer) { lineLayer = new window.BMapGL.LineLayer({ enablePicked: true, autoSelect: true, pickWidth: 30, pickHeight: 30, opacity: 1, selectedColor: 'blue', // 选中项颜色 style: { sequence: false, // 是否采用间隔填充纹理,默认false marginLength: 18, // 间隔距离,默认16,单位像素 // borderColor: '#999', borderMask: true, // 是否受内部填充区域掩膜,默认true,如果存在borderWeight小于0,则自动切换false // borderWeight: ['match', ['get', 'name'], 'demo1', 2, 0], // 描边宽度,可以设置负值 strokeWeight: 10, // 描边线宽度,默认0 strokeLineJoin: 'round',//描边线连接处类型, 可选'miter', 'round', 'bevel' strokeLineCap: 'round',// 描边线端头类型,可选'round', 'butt', 'square',默认round // 填充纹理图片地址,默认是空。图片需要是竖向表达,在填充时会自动横向处理。 strokeTextureUrl: ['match', ['get', 'name'], 'demo1', icon1, 'demo2', icon2, upTwo], } }); } lineLayer.setData(dataJson); mapInstance.addNormalLayer(lineLayer); setMap(mapInstance); // 添加点击地图事件 mapInstance.addEventListener('click', (e) => { console.log('点击位置经纬度:', e.latlng.lng, e.latlng.lat); }); // }; }; return (
{!collapsed && (
城市生命线驾驶舱
)}
setSelectedMenu(e.key)} items={menuItems} />
综合运行态势
流量突变阈值
液位过低