|
|
@@ -0,0 +1,287 @@
|
|
|
+"use client"
|
|
|
+
|
|
|
+import type React from "react"
|
|
|
+import {useState} from "react"
|
|
|
+import {Badge, Button, Descriptions, Layout, List, Menu, Modal, Space, Switch, Tag} from "antd"
|
|
|
+import {
|
|
|
+ AlertOutlined,
|
|
|
+ BellOutlined,
|
|
|
+ CheckCircleOutlined,
|
|
|
+ ClockCircleOutlined,
|
|
|
+ DashboardOutlined,
|
|
|
+ DatabaseOutlined,
|
|
|
+ ExclamationCircleOutlined,
|
|
|
+ MenuFoldOutlined,
|
|
|
+ MenuUnfoldOutlined,
|
|
|
+ MonitorOutlined,
|
|
|
+ MoonOutlined,
|
|
|
+ SunOutlined,
|
|
|
+} from "@ant-design/icons"
|
|
|
+import globalMessage from "@/app/_modules/globalMessage";
|
|
|
+
|
|
|
+const { Header, Sider, Content } = Layout
|
|
|
+
|
|
|
+interface AdminLayoutProps {
|
|
|
+ children: React.ReactNode
|
|
|
+ activeKey?: string
|
|
|
+ onMenuSelect?: (key: string) => void
|
|
|
+}
|
|
|
+
|
|
|
+export default function AdminLayout({ children, activeKey = "dashboard", onMenuSelect }: AdminLayoutProps) {
|
|
|
+ const [collapsed, setCollapsed] = useState(false)
|
|
|
+ const [isDarkMode, setIsDarkMode] = useState(false)
|
|
|
+ const [notificationModalVisible, setNotificationModalVisible] = useState(false)
|
|
|
+ const [emergencyModalVisible, setEmergencyModalVisible] = useState(false)
|
|
|
+
|
|
|
+ const menuItems = [
|
|
|
+ { key: "dashboard", icon: <DashboardOutlined />, label: "系统概览" },
|
|
|
+ { key: "facilities", icon: <DatabaseOutlined />, label: "基础数据管理" },
|
|
|
+ { key: "devices", icon: <MonitorOutlined />, label: "监测设备管理" },
|
|
|
+ { key: "monitoring", icon: <MonitorOutlined />, label: "运行监测" },
|
|
|
+ { key: "alarms", icon: <AlertOutlined />, label: "监测报警" },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const notifications = [
|
|
|
+ { id: 1, title: "设备维护提醒", content: "岳麓区泵站#001需要进行定期维护", time: "2024-01-15 14:30", type: "info", read: false },
|
|
|
+ { id: 2, title: "数据同步完成", content: "今日监测数据已成功同步至中央数据库", time: "2024-01-15 12:00", type: "success", read: true },
|
|
|
+ { id: 3, title: "系统更新通知", content: "监测系统将于今晚22:00进行版本更新", time: "2024-01-15 10:15", type: "warning", read: false },
|
|
|
+ { id: 4, title: "月度报告生成", content: "12月份供水监测月度报告已生成完成", time: "2024-01-14 16:45", type: "info", read: true },
|
|
|
+ { id: 5, title: "新设备接入", content: "五一大道新增3台流量监测设备已成功接入系统", time: "2024-01-14 09:20", type: "success", read: false },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const emergencyAlarms = [
|
|
|
+ { id: 1, location: "湘江水源地取水口", type: "水质异常", level: "高", description: "检测到浊度超标,当前值:15.2 NTU,标准值:≤10 NTU", time: "2024-01-15 15:45", status: "处理中" },
|
|
|
+ { id: 2, location: "长沙第一水厂", type: "压力异常", level: "中", description: "出厂压力偏低,当前值:0.25 MPa,标准值:≥0.3 MPa", time: "2024-01-15 14:20", status: "待处理" },
|
|
|
+ { id: 3, location: "岳麓区主管网", type: "流量异常", level: "高", description: "检测到异常流量波动,疑似管道破裂", time: "2024-01-15 13:10", status: "已处理" },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const getNotificationIcon = (type: string) => {
|
|
|
+ switch (type) {
|
|
|
+ case "success": return <CheckCircleOutlined style={{ color: "#52c41a" }} />
|
|
|
+ case "warning": return <ExclamationCircleOutlined style={{ color: "#faad14" }} />
|
|
|
+ default: return <ClockCircleOutlined style={{ color: "#1890ff" }} />
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const getAlarmLevelColor = (level: string) => {
|
|
|
+ switch (level) {
|
|
|
+ case "高": return "red"
|
|
|
+ case "中": return "orange"
|
|
|
+ case "低": return "yellow"
|
|
|
+ default: return "default"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const getStatusColor = (status: string) => {
|
|
|
+ switch (status) {
|
|
|
+ case "已处理": return "green"
|
|
|
+ case "处理中": return "blue"
|
|
|
+ case "待处理": return "red"
|
|
|
+ default: return "default"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Layout hasSider className="h-screen">
|
|
|
+ {/* 固定侧边栏 */}
|
|
|
+ <Sider
|
|
|
+ trigger={null}
|
|
|
+ collapsible
|
|
|
+ collapsed={collapsed}
|
|
|
+ width={200}
|
|
|
+ className={`shadow-lg fixed left-0 top-0 z-50 transition-all duration-300 ${
|
|
|
+ isDarkMode ? "bg-gray-800" : "bg-white"
|
|
|
+ }`}
|
|
|
+ style={{ background: isDarkMode ? "#1f2937" : "#ffffff" }}
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ className={`h-16 flex items-center justify-center border-b ${
|
|
|
+ isDarkMode ? "border-gray-700" : "border-gray-200"
|
|
|
+ }`}
|
|
|
+ >
|
|
|
+ <div className={`font-bold text-lg ${isDarkMode ? "text-white" : "text-gray-800"}`}>
|
|
|
+ {collapsed ? "供水" : "供水管网监测系统"}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <Menu
|
|
|
+ theme={isDarkMode ? "dark" : "light"}
|
|
|
+ mode="inline"
|
|
|
+ selectedKeys={[activeKey]}
|
|
|
+ items={menuItems}
|
|
|
+ onClick={({ key }) => onMenuSelect?.(key)}
|
|
|
+ style={{ background: "transparent", border: "none" }}
|
|
|
+ className="mt-4"
|
|
|
+ />
|
|
|
+ </Sider>
|
|
|
+
|
|
|
+ {/* 右侧内容区 */}
|
|
|
+ <Layout className="transition-all duration-300" >
|
|
|
+ <Header
|
|
|
+ className={`shadow-sm px-4 flex items-center justify-between fixed top-0 right-0 z-9999 transition-all duration-300 ${
|
|
|
+ isDarkMode ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"
|
|
|
+ }`}
|
|
|
+ style={{
|
|
|
+ width: `calc(100% - ${collapsed?"80px":"200px"})`,
|
|
|
+ background: isDarkMode ? "#111827" : "#f8f9fa",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div className="flex items-center">
|
|
|
+ <Button
|
|
|
+ type="text"
|
|
|
+ icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
|
|
+ onClick={() => setCollapsed(!collapsed)}
|
|
|
+ className={`text-lg transition-all duration-200 hover:scale-105 ${
|
|
|
+ isDarkMode ? "text-gray-300 hover:bg-gray-700" : "text-gray-600 hover:bg-gray-100"
|
|
|
+ }`}
|
|
|
+ />
|
|
|
+ <div className="ml-4">
|
|
|
+ <h1 className={`text-xl font-semibold ${isDarkMode ? "text-white" : "text-gray-800"}`}>
|
|
|
+ 长沙市供水管网安全运行监测平台
|
|
|
+ </h1>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <Space size="large">
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
+ <SunOutlined className={isDarkMode ? "text-gray-400" : "text-yellow-500"} />
|
|
|
+ <Switch checked={isDarkMode} onChange={setIsDarkMode} size="small" />
|
|
|
+ <MoonOutlined className={isDarkMode ? "text-blue-400" : "text-gray-400"} />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <Badge count={5} size="small">
|
|
|
+ <Button
|
|
|
+ type="text"
|
|
|
+ icon={<BellOutlined />}
|
|
|
+ onClick={() => setNotificationModalVisible(true)}
|
|
|
+ className={isDarkMode ? "text-gray-300 hover:bg-gray-700" : "text-gray-600 hover:bg-gray-100"}
|
|
|
+ />
|
|
|
+ </Badge>
|
|
|
+
|
|
|
+ <Badge count={3} size="small">
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ danger
|
|
|
+ onClick={() => setEmergencyModalVisible(true)}
|
|
|
+ className="hover:scale-105 transition-transform duration-200 shadow-lg hover:shadow-xl"
|
|
|
+ >
|
|
|
+ 紧急报警
|
|
|
+ </Button>
|
|
|
+ </Badge>
|
|
|
+ </Space>
|
|
|
+ </Header>
|
|
|
+
|
|
|
+ <Content
|
|
|
+ className={`p-6 pt-[80px] min-h-screen transition-all duration-300 ${
|
|
|
+ isDarkMode ? "bg-gray-900" : "bg-gray-50"
|
|
|
+ }`}
|
|
|
+ >
|
|
|
+ {children}
|
|
|
+ </Content>
|
|
|
+
|
|
|
+
|
|
|
+ </Layout>
|
|
|
+
|
|
|
+ {/* 系统通知 Modal */}
|
|
|
+ <Modal
|
|
|
+ title={
|
|
|
+ <div className="flex items-center">
|
|
|
+ <BellOutlined className="mr-2 text-blue-500" />
|
|
|
+ 系统通知
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ open={notificationModalVisible}
|
|
|
+ onCancel={() => setNotificationModalVisible(false)}
|
|
|
+ footer={[
|
|
|
+ <Button key="close" onClick={() => setNotificationModalVisible(false)}>关闭</Button>,
|
|
|
+ <Button
|
|
|
+ key="markAll"
|
|
|
+ type="primary"
|
|
|
+ onClick={() => {
|
|
|
+ globalMessage.success("已标记所有通知为已读")
|
|
|
+ setNotificationModalVisible(false)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 全部标记为已读
|
|
|
+ </Button>,
|
|
|
+ ]}
|
|
|
+ width={600}
|
|
|
+ >
|
|
|
+ <List
|
|
|
+ dataSource={notifications}
|
|
|
+ renderItem={(item) => (
|
|
|
+ <List.Item
|
|
|
+ className={`transition-all duration-200 hover:bg-gray-50 rounded-lg p-2 ${
|
|
|
+ !item.read ? "bg-blue-50 border-l-4 border-blue-400" : ""
|
|
|
+ }`}
|
|
|
+ >
|
|
|
+ <List.Item.Meta
|
|
|
+ avatar={getNotificationIcon(item.type)}
|
|
|
+ title={
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
+ <span className={!item.read ? "font-semibold" : ""}>{item.title}</span>
|
|
|
+ {!item.read && <Tag color="blue">未读</Tag>}
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ description={
|
|
|
+ <div>
|
|
|
+ <p className="mb-1">{item.content}</p>
|
|
|
+ <span className="text-xs text-gray-400">{item.time}</span>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ />
|
|
|
+ </List.Item>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ </Modal>
|
|
|
+
|
|
|
+ {/* 紧急报警 Modal */}
|
|
|
+ <Modal
|
|
|
+ title={
|
|
|
+ <div className="flex items-center">
|
|
|
+ <ExclamationCircleOutlined className="mr-2 text-red-500" />
|
|
|
+ 紧急报警处理
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ open={emergencyModalVisible}
|
|
|
+ onCancel={() => setEmergencyModalVisible(false)}
|
|
|
+ footer={[
|
|
|
+ <Button key="close" onClick={() => setEmergencyModalVisible(false)}>关闭</Button>,
|
|
|
+ <Button
|
|
|
+ key="handle"
|
|
|
+ type="primary"
|
|
|
+ danger
|
|
|
+ onClick={() => {
|
|
|
+ globalMessage.success("已派发处理任务给相关人员")
|
|
|
+ setEmergencyModalVisible(false)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 立即处理
|
|
|
+ </Button>,
|
|
|
+ ]}
|
|
|
+ width={800}
|
|
|
+ >
|
|
|
+ <List
|
|
|
+ dataSource={emergencyAlarms}
|
|
|
+ renderItem={(item) => (
|
|
|
+ <List.Item className="transition-all duration-200 hover:bg-gray-50 rounded-lg p-4 border border-gray-200 mb-3">
|
|
|
+ <div className="w-full">
|
|
|
+ <div className="flex items-center justify-between mb-3">
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
+ <Tag color={getAlarmLevelColor(item.level)}>{item.level}级报警</Tag>
|
|
|
+ <Tag color={getStatusColor(item.status)}>{item.status}</Tag>
|
|
|
+ </div>
|
|
|
+ <span className="text-sm text-gray-500">{item.time}</span>
|
|
|
+ </div>
|
|
|
+ <Descriptions column={1} size="small">
|
|
|
+ <Descriptions.Item label="报警位置">{item.location}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="报警类型">{item.type}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="详细描述">{item.description}</Descriptions.Item>
|
|
|
+ </Descriptions>
|
|
|
+ </div>
|
|
|
+ </List.Item>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ </Modal>
|
|
|
+ </Layout>
|
|
|
+ )
|
|
|
+}
|