"use client" import React, {useEffect, useMemo, useState} from "react" import "antd/dist/reset.css" import { BadgeAlertIcon as Alert, BarChartIcon as ChartBar, BellRing, CheckCircle2, ChevronsUp, FileText, LineChart, MapPin, Settings, ShieldAlert, Siren, SquareGanttChart, TimerIcon as Timeline, UserRoundCheck, } from "lucide-react" // Declare ShieldAlert variable import { Badge as AntdBadge, Button, Card, Col, DatePicker, Descriptions, Divider, Drawer, Form, Input, Modal, notification, Popconfirm, Radio, Row, Segmented, Select, Space, Statistic, Steps, Table, Tabs, Tag, Tooltip, Upload, } from "antd" import type {ColumnsType} from "antd/es/table" import type {UploadFile} from "antd/es/upload/interface" import dayjs, {type Dayjs} from "dayjs" import relativeTime from "dayjs/plugin/relativeTime" import isBetween from "dayjs/plugin/isBetween" import EChart from "@/components/echarts" import MatterManagement from "./components/matter-management" import globalMessage from "@/app/_modules/globalMessage"; dayjs.extend(relativeTime) dayjs.extend(isBetween) type WarningStatus = | "draft" | "published" | "in_process" | "supervising" | "returned" | "upgraded" | "resolved" | "released" type Level = "红色" | "橙色" | "黄色" | "蓝色" type WarnType = "燃气" | "供水" | "电力" | "交通" | "综合" type Industry = WarnType type ProcessLog = { time: string user: string action: string remark?: string } type Alarm = { id: string device: string metric: string value: number threshold: number time: string } type WarningItem = { id: string code: string name: string type: WarnType level: Level industry: Industry status: WarningStatus createdAt: string releasedAt?: string deadline?: string assignee?: string handler?: string location?: string coords?: [number, number] description?: string attachments?: UploadFile[] relatedAlarms?: Alarm[] process: ProcessLog[] } type MatterItemStatus = "启用" | "禁用" | "作废" type MatterItem = { id: string code: string name: string type: WarnType level: Level status: MatterItemStatus createdAt: string updatedAt: string } type ReasonEntry = { id: string type: WarnType level: Level reason: string createdAt: string } const LEVELS: Level[] = ["红色", "橙色", "黄色", "蓝色"] const TYPES: WarnType[] = ["燃气", "供水", "电力", "交通", "综合"] const INDUSTRIES: Industry[] = ["燃气", "供水", "电力", "交通", "综合"] const USERS = ["张三", "李四", "王五", "赵六", "钱七", "管理员"] const LEADERS = ["总指挥-陈总", "副总指挥-李总", "应急办-王主任"] const levelColor = (lvl: Level) => { switch (lvl) { case "红色": return "red" case "橙色": return "orange" case "黄色": return "gold" case "蓝色": return "blue" default: return "default" } } const statusTag = (status: WarningStatus) => { const map: Record = { draft: { color: "default", text: "草稿", icon: }, published: { color: "processing", text: "已发布", icon: }, in_process: { color: "blue", text: "处置中", icon: , }, supervising: { color: "purple", text: "督办中", icon: , }, returned: { color: "warning", text: "已退回", icon: }, upgraded: { color: "magenta", text: "已升级", icon: , }, resolved: { color: "success", text: "已处置", icon: , }, released: { color: "green", text: "已解除", icon: , }, } const cfg = map[status] return ( {cfg.icon} {cfg.text} ) } function randPick(arr: T[]): T { return arr[Math.floor(Math.random() * arr.length)] } function genId(prefix = "id") { return `${prefix}_${Math.random().toString(36).slice(2, 10)}` } function autoCode(type: WarnType, level: Level, seq: number) { const date = dayjs().format("YYYYMMDD") const t = { 燃气: "GAS", 供水: "WTR", 电力: "ELE", 交通: "TRF", 综合: "COM" }[type] const l = { 红色: "R", 橙色: "O", 黄色: "Y", 蓝色: "B" }[level] return `YW-${t}-${l}-${date}-${seq.toString().padStart(3, "0")}` } function isToday(iso?: string) { if (!iso) return false return dayjs(iso).isSame(dayjs(), "day") } function daysAgo(n: number) { return dayjs().subtract(n, "day").toISOString() } // demo seeds (client-only) const seedAlarms: Alarm[] = new Array(24).fill(0).map((_, i) => ({ id: genId("alarm"), device: `设备-${(i % 8) + 1}`, metric: ["压力", "温度", "流量"][i % 3], value: +(80 + Math.random() * 50).toFixed(1), threshold: 100, time: dayjs() .subtract(Math.floor(Math.random() * 72), "hour") .toISOString(), })) const seedWarnings: WarningItem[] = (() => { const arr: WarningItem[] = [] for (let i = 0; i < 26; i++) { const type = randPick(TYPES) const level = randPick(LEVELS) const status = randPick([ "draft", "published", "in_process", "supervising", "returned", "upgraded", "resolved", "released", ]) const createdAt = dayjs() .subtract(Math.floor(Math.random() * 15), "day") .subtract(Math.floor(Math.random() * 24), "hour") .toISOString() const releasedAt = status === "released" || status === "resolved" ? dayjs(createdAt) .add(Math.floor(Math.random() * 48), "hour") .toISOString() : undefined arr.push({ id: genId("warn"), code: autoCode(type, level, i + 1), name: `${type}专项-${["一号", "二号", "三号", "四号"][i % 4]}预警`, type, level, industry: type, status, createdAt, releasedAt, deadline: dayjs(createdAt).add(2, "day").toISOString(), assignee: randPick(USERS), handler: randPick(USERS), location: ["城区A", "城区B", "园区C", "沿线D"][i % 4], coords: [120.1 + Math.random(), 30.2 + Math.random()], description: "设备异常波动,指标超限,建议立即排查。包含监测曲线、现场图片等信息。", attachments: [], relatedAlarms: seedAlarms.slice(i, i + 3), process: [ { time: createdAt, user: randPick(USERS), action: "创建预警" }, ...(status !== "draft" ? [{ time: dayjs(createdAt).add(1, "hour").toISOString(), user: randPick(USERS), action: "发布预警" }] : []), ...(status === "resolved" || status === "released" ? [ { time: dayjs(createdAt).add(2, "day").toISOString(), user: randPick(USERS), action: status === "released" ? "解除预警" : "完成处置", }, ] : []), ], }) } return arr })() const seedMatters: MatterItem[] = new Array(12).fill(0).map((_, i) => { const type = randPick(TYPES) const level = randPick(LEVELS) return { id: genId("matter"), code: autoCode(type, level, i + 1), name: `${type}专项事项-${i + 1}`, type, level, status: randPick(["启用", "禁用", "作废"]), createdAt: daysAgo(30 - i), updatedAt: daysAgo(20 - i), } }) const seedReasons: ReasonEntry[] = [ { id: genId("reason"), type: "燃气", level: "红色", reason: "主干管压力骤降", createdAt: daysAgo(40) }, { id: genId("reason"), type: "供水", level: "黄色", reason: "水质浊度升高", createdAt: daysAgo(25) }, { id: genId("reason"), type: "电力", level: "橙色", reason: "变压器过载", createdAt: daysAgo(12) }, ] export default function WarningDashboard() { const [currentUser] = useState("刘昊林") const [warnings, setWarnings] = useState([]) const [matters, setMatters] = useState([]) const [reasons, setReasons] = useState([]) useEffect(() => { // 仅在客户端生成随机数据 setWarnings(seedWarnings) setMatters(seedMatters) setReasons(seedReasons) }, []) const [activeTab, setActiveTab] = useState("overview") const [detail, setDetail] = useState(null) const [publishForm] = Form.useForm() const [publishOpen, setPublishOpen] = useState(false) const [superviseOpen, setSuperviseOpen] = useState(null) const [upgradeOpen, setUpgradeOpen] = useState(null) const [leaderOpen, setLeaderOpen] = useState(null) const [returnOpen, setReturnOpen] = useState(null) const [todoFilter, setTodoFilter] = useState<{ q?: string; status?: "todo" | "done" }>({ status: "todo" }) const [mgmtFilters, setMgmtFilters] = useState<{ type?: WarnType level?: Level industry?: Industry status?: WarningStatus date?: [Dayjs, Dayjs] q?: string }>({}) const [todayOnly, setTodayOnly] = useState(true) // Derived stats const todayStats = useMemo(() => { const todayList = warnings.filter((w) => isToday(w.createdAt)) const released = todayList.filter((w) => w.status === "released").length const unresolved = todayList.filter((w) => !["released"].includes(w.status)).length return { total: todayList.length, released, unresolved, list: todayList } }, [warnings]) const todayUnhandled = useMemo(() => { return warnings.filter( (w) => isToday(w.createdAt) && ["published", "in_process", "supervising", "upgraded", "returned"].includes(w.status), ) }, [warnings]) const historyByType = useMemo(() => { const map = new Map() TYPES.forEach((t) => map.set(t, 0)) warnings.forEach((w) => map.set(w.type, (map.get(w.type) || 0) + 1)) return Array.from(map.entries()).map(([name, value]) => ({ name, value })) }, [warnings]) const historyByLevel = useMemo(() => { const map = new Map() LEVELS.forEach((l) => map.set(l, 0)) warnings.forEach((w) => map.set(w.level, (map.get(w.level) || 0) + 1)) return Array.from(map.entries()).map(([name, value]) => ({ name, value })) }, [warnings]) const mineTodo = useMemo(() => { return warnings.filter( (w) => w.assignee === currentUser && ["published", "in_process", "supervising", "upgraded", "returned"].includes(w.status), ) }, [warnings, currentUser]) const mineDone = useMemo(() => { return warnings.filter((w) => w.assignee === currentUser && ["resolved", "released"].includes(w.status)) }, [warnings, currentUser]) // Actions function pushProcess(w: WarningItem, action: string, remark?: string) { w.process = [...w.process, { time: new Date().toISOString(), user: currentUser, action, remark }] } function updateWarning(wid: string, patch: Partial, processAction?: string, remark?: string) { setWarnings((prev) => prev.map((w) => { if (w.id !== wid) return w const next = { ...w, ...patch } if (processAction) { const cp = { ...next } pushProcess(cp, processAction, remark) return cp } return next }), ) } function handleRelease(w: WarningItem) { updateWarning(w.id, { status: "released", releasedAt: new Date().toISOString() }, "解除预警") globalMessage.success("预警已解除") } function handleResolve(w: WarningItem) { updateWarning(w.id, { status: "resolved" }, "完成处置") globalMessage.success("预警已标记为已处置") } function handleReturn(w: WarningItem, reason: string) { updateWarning(w.id, { status: "returned" }, "退回预警", reason) globalMessage.warning("预警已退回处置单位") } function handleUpgrade(w: WarningItem, newLevel: Level, reason?: string) { updateWarning(w.id, { status: "upgraded", level: newLevel }, "升级预警", reason) notification.info({ message: "预警已升级", description: `${w.code} -> ${newLevel}` }) } function handleSupervise(w: WarningItem, payload: { people: string[]; opinion: string; channels: string[] }) { updateWarning( w.id, { status: "supervising" }, "督办", `人员: ${payload.people.join(",")} | 方式: ${payload.channels.join(",")} | 意见: ${payload.opinion}`, ) notification.warning({ message: "已发起督办", description: `${w.name} - ${payload.people.join(", ")}`, }) } function handleLeaderInstruction(w: WarningItem, payload: { leaders: string[]; sms: string }) { updateWarning(w.id, {}, "领导批示", `已短信推送给: ${payload.leaders.join(", ")};内容:${payload.sms}`) notification.success({ message: "已推送领导批示", description: payload.sms, }) } function handlePublish(values: any) { const seq = warnings.length + 1 const code = autoCode(values.type, values.level, seq) const newItem: WarningItem = { id: genId("warn"), code, name: values.name, type: values.type, level: values.level, industry: values.type, status: "published", createdAt: values.releasedAt?.toISOString?.() ?? new Date().toISOString(), releasedAt: undefined, deadline: dayjs().add(2, "day").toISOString(), assignee: values.assignee, handler: currentUser, location: values.location, coords: [120.18 + Math.random() * 0.1, 30.25 + Math.random() * 0.1], description: values.description, attachments: values.attachments?.fileList ?? [], relatedAlarms: seedAlarms.slice(0, Math.min(3, seedAlarms.length)), process: [ { time: new Date().toISOString(), user: currentUser, action: "发布预警", remark: values.description, }, ], } setWarnings((prev) => [newItem, ...prev]) setPublishOpen(false) publishForm.resetFields() globalMessage.success("预警已发布") setActiveTab("publish") } // Filters const filteredMgmtList = useMemo(() => { return warnings.filter((w) => { if (todayOnly && !isToday(w.createdAt)) return false if (mgmtFilters.type && w.type !== mgmtFilters.type) return false if (mgmtFilters.level && w.level !== mgmtFilters.level) return false if (mgmtFilters.industry && w.industry !== mgmtFilters.industry) return false if (mgmtFilters.status && w.status !== mgmtFilters.status) return false if (mgmtFilters.date && mgmtFilters.date.length === 2) { const [s, e] = mgmtFilters.date if (!dayjs(w.createdAt).isBetween(s, e, "day", "[]")) return false } if (mgmtFilters.q) { const q = mgmtFilters.q.trim() if (!q) return true return w.name.includes(q) || w.code.includes(q) || w.location?.includes(q) || w.description?.includes(q) } return true }) }, [warnings, mgmtFilters, todayOnly]) // Tables const warnColumns: ColumnsType = [ { title: "预警名称", dataIndex: "name", key: "name", render: (text, record) => ( setDetail(record)}>{text} {statusTag(record.status)} ), }, { title: "编码", dataIndex: "code", key: "code" }, { title: "类型/行业", key: "type", render: (_, r) => ( {r.type} {r.level} ), }, { title: "位置", dataIndex: "location", key: "location" }, { title: "创建时间", dataIndex: "createdAt", key: "createdAt", render: (t: string) => dayjs(t).format("YYYY-MM-DD HH:mm"), }, { title: "责任人", dataIndex: "assignee", key: "assignee" }, { title: "操作", key: "actions", fixed: "right", render: (_, r) => ( handleRelease(r)} okText="解除" cancelText="取消"> handleResolve(r)} okText="确定" cancelText="取消"> ), }, ] // Charts options const todayPieOption = useMemo(() => { return { tooltip: { trigger: "item" }, legend: { top: "bottom" }, series: [ { name: "今日预警", type: "pie", radius: ["40%", "60%"], label: { formatter: "{b}: {c} ({d}%)" }, data: [ { name: "未解除", value: todayStats.unresolved }, { name: "已解除", value: todayStats.released }, ], }, ], color: ["#f97316", "#10b981"], } }, [todayStats]) const historyBarOption = useMemo(() => { return { tooltip: { trigger: "axis" }, legend: { data: ["各专项累计"] }, xAxis: { type: "category", data: historyByType.map((d) => d.name) }, yAxis: { type: "value" }, series: [ { name: "各专项累计", type: "bar", data: historyByType.map((d) => d.value), itemStyle: { color: "#6b7280" }, }, ], } }, [historyByType]) const levelRingOption = useMemo(() => { return { tooltip: { trigger: "item" }, series: [ { name: "等级分布", type: "pie", radius: ["30%", "55%"], label: { formatter: "{b}: {c}" }, data: historyByLevel, color: ["#ef4444", "#fb923c", "#facc15", "#3b82f6"], }, ], } }, [historyByLevel]) const trendOption = useMemo(() => { const days = Array.from({ length: 14 }, (_, i) => dayjs() .subtract(13 - i, "day") .format("MM-DD"), ) const series = TYPES.map((t) => ({ name: t, type: "line", smooth: true, data: days.map(() => Math.floor(Math.random() * 6)), areaStyle: { opacity: 0.08 }, emphasis: { focus: "series" as const }, })) return { tooltip: { trigger: "axis" }, legend: { top: 0 }, grid: { left: 24, right: 16, bottom: 24, top: 40 }, xAxis: { type: "category", data: days }, yAxis: { type: "value" }, series, } }, []) const efficiencyTopOption = useMemo(() => { const data = TYPES.map((t) => ({ name: t, value: +(80 + Math.random() * 20).toFixed(1), })).sort((a, b) => b.value - a.value) return { tooltip: { trigger: "axis" }, xAxis: { type: "value", max: 100 }, yAxis: { type: "category", data: data.map((d) => d.name), inverse: true }, series: [ { type: "bar", data: data.map((d) => d.value), itemStyle: { color: (params: any) => ["#10b981", "#22c55e", "#84cc16", "#eab308", "#f97316"][params.dataIndex % 5], }, label: { show: true, position: "right", formatter: "{c}%" }, }, ], } }, []) // Modals function UpgradeModal() { const [form] = Form.useForm<{ level: Level; reason: string }>() const rec = upgradeOpen return ( setUpgradeOpen(null)} onOk={() => { form.validateFields().then((vals) => { if (rec) handleUpgrade(rec, vals.level, vals.reason) setUpgradeOpen(null) }) }} okText="升级" >
({ label: u, value: u }))} placeholder="选择人员" /> ({ label: l, value: l }))} />
) } function ReturnModal() { const [form] = Form.useForm<{ reason: string }>() const rec = returnOpen return ( setReturnOpen(null)} onOk={() => { form.validateFields().then((vals) => { if (rec) handleReturn(rec, vals.reason) setReturnOpen(null) }) }} okText="退回" >
) } function DetailDrawer() { const rec = detail const stepIndex: number = useMemo(() => { if (!rec) return 0 const stepMap: Record = { draft: 0, published: 1, in_process: 2, supervising: 2, returned: 2, upgraded: 2, resolved: 3, released: 4, } return stepMap[rec.status] }, [rec]) return ( 预警详情 {rec ? statusTag(rec.status) : null} } width={720} open={!!rec} onClose={() => setDetail(null)} > {!rec ? null : ( {rec.name} {rec.code} {rec.type} {rec.level} {rec.industry} {rec.assignee} {dayjs(rec.createdAt).format("YYYY-MM-DD HH:mm")} {rec.deadline ? dayjs(rec.deadline).format("YYYY-MM-DD HH:mm") : "-"} {rec.location} {rec.description} GIS 定位示意图 size="small" rowKey="id" pagination={false} dataSource={rec.relatedAlarms} columns={[ { title: "设备", dataIndex: "device" }, { title: "指标", dataIndex: "metric" }, { title: "数值/阈值", render: (_, a) => `${a.value} / ${a.threshold}` }, { title: "时间", dataIndex: "time", render: (t) => dayjs(t).format("YYYY-MM-DD HH:mm") }, ]} /> size="small" pagination={false} rowKey={(r) => `${r.time}-${r.action}`} dataSource={[...rec.process].reverse()} columns={[ { title: "时间", dataIndex: "time", render: (t) => dayjs(t).format("YYYY-MM-DD HH:mm") }, { title: "人员", dataIndex: "user" }, { title: "动作", dataIndex: "action" }, { title: "备注", dataIndex: "remark" }, ]} /> )} ) } // Upload handler const normFile = (e: any) => { if (Array.isArray(e)) return e return e?.fileList } // Sections function Overview() { return ( } /> } /> } /> } /> size="small" rowKey="id" pagination={{ pageSize: 5 }} dataSource={todayStats.list} columns={[ { title: "名称", dataIndex: "name", render: (t, r) => setDetail(r)}>{t} }, { title: "等级", dataIndex: "level", render: (l: Level) => {l} }, { title: "状态", dataIndex: "status", render: (s: WarningStatus) => statusTag(s) }, ]} /> size="small" rowKey="id" dataSource={todayUnhandled} pagination={{ pageSize: 8 }} columns={warnColumns} scroll={{ x: 1000 }} /> ) } function Publish() { return ( 预警发布 } extra={ } >
({ label: l, value: l }))} /> } placeholder="输入预警位置" /> ({ label: `${a.device}-${a.metric}(${dayjs(a.time).format("MM-DD HH:mm")})`, value: a.id, }))} /> false} multiple>

点击或拖拽文件到此处上传

支持多文件

仅演示}> size="small" rowKey="id" dataSource={seedAlarms.slice(0, 8)} pagination={false} columns={[ { title: "设备", dataIndex: "device" }, { title: "指标", dataIndex: "metric" }, { title: "值/阈值", render: (_, a) => `${a.value}/${a.threshold}` }, { title: "时间", dataIndex: "time", render: (t) => dayjs(t).format("MM-DD HH:mm") }, ]} /> size="small" rowKey="id" pagination={false} dataSource={[...warnings].filter((w) => isToday(w.createdAt)).slice(0, 6)} columns={[ { title: "名称", dataIndex: "name" }, { title: "编码", dataIndex: "code" }, { title: "等级", dataIndex: "level", render: (l: Level) => {l} }, { title: "状态", dataIndex: "status", render: (s: WarningStatus) => statusTag(s) }, ]} /> setPublishOpen(false)} footer={null} destroyOnHidden >
({ label: l, value: l }))} /> ({ label: t, value: t }))} onChange={(type) => setMgmtFilters((p) => ({ ...p, type: type as WarnType | undefined }))} style={{ width: 140 }} /> ({ label: i, value: i }))} onChange={(industry) => setMgmtFilters((p) => ({ ...p, industry: industry as Industry | undefined }))} style={{ width: 140 }} />