"use client" import "antd/dist/reset.css" import {useEffect, useMemo, useRef, useState} from "react" import dynamic from "next/dynamic" import type {MenuProps} from "antd" import { Badge, Button, Card, Col, Collapse, Descriptions, Divider, Drawer, Form, Input, Layout, Menu, Modal, Popover, Progress, Row, Segmented, Select, Slider, Space, Statistic, Steps, Switch, Table, Tabs, Tag, Timeline, Typography, Upload, } from "antd" import { Activity, Bell, Box, Boxes, Check, CheckCircle2, CloudUpload, Cog, Database, Factory, FileJson2, Globe, Layers, LinkIcon, Lock, Network, Package, Radar, RefreshCw, Send, Server, Settings, ShieldCheck, SignalHigh, Sprout, UploadIcon, Users, } from "lucide-react" import globalMessage from "@/app/_modules/globalMessage"; const { Header, Sider, Content } = Layout const { TextArea } = Input const { Title, Text } = Typography // Lazy components that require browser APIs const DeviceMap = dynamic(() => import("./components/device-map"), { ssr: false }) const RealtimeLine = dynamic(() => import("./components/realtime-line"), { ssr: false }) const QueueGauge = dynamic(() => import("./components/queue-gauge"), { ssr: false }) type Project = { id: string name: string description?: string createdAt: string } type Product = { id: string name: string protocol: "TCP" | "UDP" | "LWM2M" | "MQTTS" | "HTTP/HTTPS" projectId: string thirdParty: boolean authToken?: string } type ThingModel = { id: string productId: string json: string } type Device = { id: string name: string productId: string projectId: string status: "online" | "offline" lastSeen?: string lat: number lng: number } type Telemetry = { ts: number value: number deviceId: string } const initialProjects: Project[] = [ { id: "p1", name: "城市燃气", description: "管网与阀井监测", createdAt: new Date().toISOString() }, { id: "p2", name: "城市水务", description: "供排水管线监测", createdAt: new Date().toISOString() }, ] const initialProducts: Product[] = [ { id: "prod1", name: "压力传感器A", protocol: "MQTTS", projectId: "p1", thirdParty: false }, { id: "prod2", name: "井盖监测B", protocol: "HTTP/HTTPS", projectId: "p1", thirdParty: true, authToken: "TP-XXXX" }, { id: "prod3", name: "流量计C", protocol: "LWM2M", projectId: "p2", thirdParty: false }, ] const initialThingModels: ThingModel[] = [ { id: "tm1", productId: "prod1", json: JSON.stringify( { properties: [ { id: "pressure", name: "压力", type: "number", unit: "kPa" }, { id: "battery", name: "电量", type: "number", unit: "%" }, ], events: [{ id: "alarm", name: "告警", level: "high" }], services: [{ id: "reset", name: "复位", in: {}, out: {} }], }, null, 2, ), }, ] const initialDevices: Device[] = [ { id: "dev-001", name: "压力A-001", productId: "prod1", projectId: "p1", status: "online", lastSeen: new Date().toISOString(), lat: 31.231706, lng: 121.472644, }, { id: "dev-002", name: "井盖B-002", productId: "prod2", projectId: "p1", status: "offline", lat: 31.220706, lng: 121.462644, }, { id: "dev-003", name: "流量C-003", productId: "prod3", projectId: "p2", status: "online", lastSeen: new Date().toISOString(), lat: 31.241706, lng: 121.482644, }, ] function useInterval(callback: () => void, delay: number | null) { const savedRef = useRef<() => void>(null) useEffect(() => { savedRef.current = callback }, [callback]) useEffect(() => { if (delay === null) return const id = setInterval(() => savedRef.current && savedRef.current(), delay) return () => clearInterval(id) }, [delay]) } export default function Page() { const [collapsed, setCollapsed] = useState(false) const [activeKey, setActiveKey] = useState("overview") // Core states const [projects, setProjects] = useState(initialProjects) const [products, setProducts] = useState(initialProducts) const [thingModels, setThingModels] = useState(initialThingModels) const [devices, setDevices] = useState(initialDevices) const [telemetry, setTelemetry] = useState([]) const [backlog, setBacklog] = useState(8) const [throughput, setThroughput] = useState(120) const [alerts, setAlerts] = useState(1) const [todayCount, setTodayCount] = useState(3200) const [logs, setLogs] = useState< { key: string; time: string; deviceId: string; product: string; msg: string; level: "INFO" | "WARN" | "ERROR" }[] >([]) // UI states for feedback const [projectModalOpen, setProjectModalOpen] = useState(false) const [productModalOpen, setProductModalOpen] = useState(false) const [deviceModalOpen, setDeviceModalOpen] = useState(false) const [cmdModal, setCmdModal] = useState<{ open: boolean; device?: Device }>({ open: false }) const [dataDrawer, setDataDrawer] = useState<{ open: boolean; device?: Device }>({ open: false }) const [registerDrawer, setRegisterDrawer] = useState(false) const [notifOpen, setNotifOpen] = useState(false) // Extra feedback modals const [svcModal, setSvcModal] = useState<{ open: boolean; svc?: any }>({ open: false }) const [weightModal, setWeightModal] = useState<{ open: boolean; weight: number }>({ open: false, weight: 50 }) const [scheduleModal, setScheduleModal] = useState(false) const [apiAccessModal, setApiAccessModal] = useState(false) const [rbacModal, setRbacModal] = useState<{ open: boolean; user?: any }>({ open: false }) const [form] = Form.useForm() const [productForm] = Form.useForm() const [deviceForm] = Form.useForm() const [cmdForm] = Form.useForm() // Simulate telemetry stream, queue and device health useInterval(() => { const onlineDevices = devices.filter((d) => d.status === "online") if (onlineDevices.length === 0) return const randDevice = onlineDevices[Math.floor(Math.random() * onlineDevices.length)] const v = Math.round(80 + Math.random() * 40 - 20) const now = Date.now() setTelemetry((prev) => { const next = [...prev, { ts: now, value: v, deviceId: randDevice.id }].slice(-120) return next }) setTodayCount((c) => c + 1) setBacklog((b) => Math.max(0, b + 1 - Math.floor(throughput / 100))) setLogs((prev) => { const product = products.find((p) => p.id === randDevice.productId)?.name ?? "-" const next = [ { key: `${now}`, time: new Date(now).toLocaleTimeString(), deviceId: randDevice.id, product, msg: `上报: value=${v}`, level: "INFO" as const, }, ...prev, ].slice(0, 50) return next }) if (Math.random() < 0.03) { setAlerts((a) => a + 1) setLogs((prev) => [ { key: `${now}-warn`, time: new Date(now).toLocaleTimeString(), deviceId: randDevice.id, product: products.find((p) => p.id === randDevice.productId)?.name ?? "-", msg: "告警: 压力异常", level: "WARN", }, ...prev, ]) } setDevices((prev) => prev.map((d) => (d.id === randDevice.id ? { ...d, lastSeen: new Date().toISOString() } : d))) }, 1200) const onlineCount = devices.filter((d) => d.status === "online").length const menuItems: MenuProps["items"] = [ { key: "overview", icon: , label: "总览" }, { key: "south", icon: , label: "南向接入" }, { key: "terminal", icon: , label: "终端接入" }, { key: "distribution", icon: , label: "数据分发" }, { key: "mq", icon: , label: "北向消息队列" }, { key: "warehouse", icon: , label: "数据仓库" }, { key: "openapi", icon: , label: "数据共享开放" }, { key: "micro", icon: , label: "微服务" }, { key: "auth", icon: , label: "认证鉴权" }, ] const projectOptions = projects.map((p) => ({ label: p.name, value: p.id })) const productOptions = products.map((p) => ({ label: p.name, value: p.id })) function addProject(values: any) { const p: Project = { id: `p-${Math.random().toString(36).slice(2, 8)}`, name: values.name, description: values.description, createdAt: new Date().toISOString(), } setProjects((prev) => [p, ...prev]) setProjectModalOpen(false) form.resetFields() globalMessage.success("项目已创建") } function addProduct(values: any) { const prod: Product = { id: `prod-${Math.random().toString(36).slice(2, 8)}`, name: values.name, protocol: values.protocol, projectId: values.projectId, thirdParty: values.thirdParty || false, authToken: values.thirdParty ? `TP-${Math.random().toString(36).slice(2, 8)}` : undefined, } setProducts((prev) => [prod, ...prev]) setProductModalOpen(false) productForm.resetFields() globalMessage.success("产品已创建") } function addDevice(values: any) { const dev: Device = { id: values.id, name: values.name, productId: values.productId, projectId: values.projectId, status: "offline", lat: values.lat ?? 31.23 + Math.random() * 0.05, lng: values.lng ?? 121.47 + Math.random() * 0.05, } setDevices((prev) => [dev, ...prev]) setDeviceModalOpen(false) deviceForm.resetFields() globalMessage.success("设备已注册") } function toggleDevice(id: string, online: boolean) { setDevices((prev) => prev.map((d) => d.id === id ? { ...d, status: online ? "online" : "offline", lastSeen: online ? new Date().toISOString() : d.lastSeen } : d, ), ) globalMessage.success(online ? "设备上线成功" : "设备已下线") } function sendCommand(values: any) { const device = cmdModal.device if (!device) return const now = Date.now() setLogs((prev) => [ { key: `${now}-cmd`, time: new Date(now).toLocaleTimeString(), deviceId: device.id, product: products.find((p) => p.id === device.productId)?.name ?? "-", msg: `下发指令: ${values.serviceId} ${values.payload}`, level: "INFO", }, ...prev, ]) setCmdModal({ open: false }) cmdForm.resetFields() globalMessage.success("指令已发送") } const telemetrySeries = useMemo(() => { const xs = telemetry.map((t) => new Date(t.ts).toLocaleTimeString()) const ys = telemetry.map((t) => t.value) return { xs, ys } }, [telemetry]) const alertLogs = logs.filter((l) => l.level !== "INFO").slice(0, 10) return (
{!collapsed && 生命线物联网平台}
setActiveKey(e.key)} />
城市生命线物联网平台
{activeKey === "overview" && (
} /> } /> } /> } />
处理吞吐 { setThroughput(Number(v)) globalMessage.success(`已切换吞吐:${v}/s`) }} />
lv === "INFO" ? ( INFO ) : lv === "WARN" ? ( WARN ) : ( ERROR ), }, { title: "消息", dataIndex: "msg" }, ]} /> )} {activeKey === "south" && ( 项目管理 ), children: ( <>
按项目分类设备,方便资产统一管理
rowKey="id" dataSource={projects} columns={[ { title: "项目名称", dataIndex: "name" }, { title: "描述", dataIndex: "description" }, { title: "创建时间", dataIndex: "createdAt", render: (t) => new Date(t).toLocaleString() }, { title: "设备数", render: (_, r) => devices.filter((d) => d.projectId === r.id).length, }, ]} /> setProjectModalOpen(false)} onOk={() => form.submit()} okText="保存" >
), }, { key: "products", label: (
产品管理
), children: ( <>
定义产品、添加设备,管理自建与第三方授权产品
rowKey="id" dataSource={products} columns={[ { title: "名称", dataIndex: "name" }, { title: "协议", dataIndex: "protocol" }, { title: "归属项目", dataIndex: "projectId", render: (id) => projects.find((p) => p.id === id)?.name, }, { title: "类型", dataIndex: "thirdParty", render: (v: boolean) => v ? 第三方 : 自建, }, { title: "授权Token", dataIndex: "authToken", render: (t) => (t ? {t} : -), }, { title: "设备数", render: (_, r) => devices.filter((d) => d.productId === r.id).length, }, ]} /> setProductModalOpen(false)} onOk={() => productForm.submit()} okText="保存" >
), }, { key: "thing", label: (
物模型管理
), children: ( <>
{ const t: ThingModel = { id: `tm-${Math.random().toString(36).slice(2, 8)}`, productId: products[0]?.id ?? "", json: JSON.stringify( { properties: [{ id: "temp", name: "温度", type: "number", unit: "℃" }], events: [], services: [], }, null, 2, ), } setThingModels((prev) => [t, ...prev]) globalMessage.success("已创建示例物模型") }} > 创建示例 } >
products.find((p) => p.id === id)?.name ?? "-", }, { title: "属性数", render: (_, r: ThingModel) => { try { const o = JSON.parse(r.json) return o.properties?.length ?? 0 } catch { return "-" } }, }, { title: "操作", render: (_, r: ThingModel) => ( {r.json} } > ), }, ]} />
{ try { const obj = JSON.parse(v.json) if (!Array.isArray(obj.properties)) throw new Error("缺少 properties") globalMessage.success("物模型JSON校验通过") } catch (e: any) { globalMessage.error(`JSON错误: ${e.message}`) } }} >