admin-layout.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. "use client"
  2. import type React from "react"
  3. import {useState} from "react"
  4. import {Badge, Button, Descriptions, Layout, List, Menu, Modal, Space, Switch, Tag} from "antd"
  5. import {
  6. AlertOutlined,
  7. BellOutlined,
  8. CheckCircleOutlined,
  9. ClockCircleOutlined,
  10. DashboardOutlined,
  11. DatabaseOutlined,
  12. ExclamationCircleOutlined,
  13. MenuFoldOutlined,
  14. MenuUnfoldOutlined,
  15. MonitorOutlined,
  16. MoonOutlined,
  17. SunOutlined,
  18. } from "@ant-design/icons"
  19. import globalMessage from "@/app/_modules/globalMessage";
  20. const { Header, Sider, Content } = Layout
  21. interface AdminLayoutProps {
  22. children: React.ReactNode
  23. activeKey?: string
  24. onMenuSelect?: (key: string) => void
  25. }
  26. export default function AdminLayout({ children, activeKey = "dashboard", onMenuSelect }: AdminLayoutProps) {
  27. const [collapsed, setCollapsed] = useState(false)
  28. const [isDarkMode, setIsDarkMode] = useState(false)
  29. const [notificationModalVisible, setNotificationModalVisible] = useState(false)
  30. const [emergencyModalVisible, setEmergencyModalVisible] = useState(false)
  31. const menuItems = [
  32. { key: "dashboard", icon: <DashboardOutlined />, label: "系统概览" },
  33. { key: "facilities", icon: <DatabaseOutlined />, label: "基础数据管理" },
  34. { key: "devices", icon: <MonitorOutlined />, label: "监测设备管理" },
  35. { key: "monitoring", icon: <MonitorOutlined />, label: "运行监测" },
  36. { key: "alarms", icon: <AlertOutlined />, label: "监测报警" },
  37. ]
  38. const notifications = [
  39. { id: 1, title: "设备维护提醒", content: "岳麓区泵站#001需要进行定期维护", time: "2024-01-15 14:30", type: "info", read: false },
  40. { id: 2, title: "数据同步完成", content: "今日监测数据已成功同步至中央数据库", time: "2024-01-15 12:00", type: "success", read: true },
  41. { id: 3, title: "系统更新通知", content: "监测系统将于今晚22:00进行版本更新", time: "2024-01-15 10:15", type: "warning", read: false },
  42. { id: 4, title: "月度报告生成", content: "12月份供水监测月度报告已生成完成", time: "2024-01-14 16:45", type: "info", read: true },
  43. { id: 5, title: "新设备接入", content: "五一大道新增3台流量监测设备已成功接入系统", time: "2024-01-14 09:20", type: "success", read: false },
  44. ]
  45. const emergencyAlarms = [
  46. { id: 1, location: "湘江水源地取水口", type: "水质异常", level: "高", description: "检测到浊度超标,当前值:15.2 NTU,标准值:≤10 NTU", time: "2024-01-15 15:45", status: "处理中" },
  47. { id: 2, location: "长沙第一水厂", type: "压力异常", level: "中", description: "出厂压力偏低,当前值:0.25 MPa,标准值:≥0.3 MPa", time: "2024-01-15 14:20", status: "待处理" },
  48. { id: 3, location: "岳麓区主管网", type: "流量异常", level: "高", description: "检测到异常流量波动,疑似管道破裂", time: "2024-01-15 13:10", status: "已处理" },
  49. ]
  50. const getNotificationIcon = (type: string) => {
  51. switch (type) {
  52. case "success": return <CheckCircleOutlined style={{ color: "#52c41a" }} />
  53. case "warning": return <ExclamationCircleOutlined style={{ color: "#faad14" }} />
  54. default: return <ClockCircleOutlined style={{ color: "#1890ff" }} />
  55. }
  56. }
  57. const getAlarmLevelColor = (level: string) => {
  58. switch (level) {
  59. case "高": return "red"
  60. case "中": return "orange"
  61. case "低": return "yellow"
  62. default: return "default"
  63. }
  64. }
  65. const getStatusColor = (status: string) => {
  66. switch (status) {
  67. case "已处理": return "green"
  68. case "处理中": return "blue"
  69. case "待处理": return "red"
  70. default: return "default"
  71. }
  72. }
  73. return (
  74. <Layout hasSider className="h-screen">
  75. {/* 固定侧边栏 */}
  76. <Sider
  77. trigger={null}
  78. collapsible
  79. collapsed={collapsed}
  80. width={200}
  81. className={`shadow-lg fixed left-0 top-0 z-50 transition-all duration-300 ${
  82. isDarkMode ? "bg-gray-800" : "bg-white"
  83. }`}
  84. style={{ background: isDarkMode ? "#1f2937" : "#ffffff" }}
  85. >
  86. <div
  87. className={`h-16 flex items-center justify-center border-b ${
  88. isDarkMode ? "border-gray-700" : "border-gray-200"
  89. }`}
  90. >
  91. <div className={`font-bold text-lg ${isDarkMode ? "text-white" : "text-gray-800"}`}>
  92. {collapsed ? "供水" : "供水管网监测系统"}
  93. </div>
  94. </div>
  95. <Menu
  96. theme={isDarkMode ? "dark" : "light"}
  97. mode="inline"
  98. selectedKeys={[activeKey]}
  99. items={menuItems}
  100. onClick={({ key }) => onMenuSelect?.(key)}
  101. style={{ background: "transparent", border: "none" }}
  102. className="mt-4"
  103. />
  104. </Sider>
  105. {/* 右侧内容区 */}
  106. <Layout className="transition-all duration-300" >
  107. <Header
  108. className={`shadow-sm px-4 flex items-center justify-between fixed top-0 right-0 z-9999 transition-all duration-300 ${
  109. isDarkMode ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"
  110. }`}
  111. style={{
  112. width: `calc(100% - ${collapsed?"80px":"200px"})`,
  113. background: isDarkMode ? "#111827" : "#f8f9fa",
  114. }}
  115. >
  116. <div className="flex items-center">
  117. <Button
  118. type="text"
  119. icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
  120. onClick={() => setCollapsed(!collapsed)}
  121. className={`text-lg transition-all duration-200 hover:scale-105 ${
  122. isDarkMode ? "text-gray-300 hover:bg-gray-700" : "text-gray-600 hover:bg-gray-100"
  123. }`}
  124. />
  125. <div className="ml-4">
  126. <h1 className={`text-xl font-semibold ${isDarkMode ? "text-white" : "text-gray-800"}`}>
  127. 长沙市供水管网安全运行监测平台
  128. </h1>
  129. </div>
  130. </div>
  131. <Space size="large">
  132. <div className="flex items-center space-x-2">
  133. <SunOutlined className={isDarkMode ? "text-gray-400" : "text-yellow-500"} />
  134. <Switch checked={isDarkMode} onChange={setIsDarkMode} size="small" />
  135. <MoonOutlined className={isDarkMode ? "text-blue-400" : "text-gray-400"} />
  136. </div>
  137. <Badge count={5} size="small">
  138. <Button
  139. type="text"
  140. icon={<BellOutlined />}
  141. onClick={() => setNotificationModalVisible(true)}
  142. className={isDarkMode ? "text-gray-300 hover:bg-gray-700" : "text-gray-600 hover:bg-gray-100"}
  143. />
  144. </Badge>
  145. <Badge count={3} size="small">
  146. <Button
  147. type="primary"
  148. danger
  149. onClick={() => setEmergencyModalVisible(true)}
  150. className="hover:scale-105 transition-transform duration-200 shadow-lg hover:shadow-xl"
  151. >
  152. 紧急报警
  153. </Button>
  154. </Badge>
  155. </Space>
  156. </Header>
  157. <Content
  158. className={`p-6 pt-[80px] min-h-screen transition-all duration-300 ${
  159. isDarkMode ? "bg-gray-900" : "bg-gray-50"
  160. }`}
  161. >
  162. {children}
  163. </Content>
  164. </Layout>
  165. {/* 系统通知 Modal */}
  166. <Modal
  167. title={
  168. <div className="flex items-center">
  169. <BellOutlined className="mr-2 text-blue-500" />
  170. 系统通知
  171. </div>
  172. }
  173. open={notificationModalVisible}
  174. onCancel={() => setNotificationModalVisible(false)}
  175. footer={[
  176. <Button key="close" onClick={() => setNotificationModalVisible(false)}>关闭</Button>,
  177. <Button
  178. key="markAll"
  179. type="primary"
  180. onClick={() => {
  181. globalMessage.success("已标记所有通知为已读")
  182. setNotificationModalVisible(false)
  183. }}
  184. >
  185. 全部标记为已读
  186. </Button>,
  187. ]}
  188. width={600}
  189. >
  190. <List
  191. dataSource={notifications}
  192. renderItem={(item) => (
  193. <List.Item
  194. className={`transition-all duration-200 hover:bg-gray-50 rounded-lg p-2 ${
  195. !item.read ? "bg-blue-50 border-l-4 border-blue-400" : ""
  196. }`}
  197. >
  198. <List.Item.Meta
  199. avatar={getNotificationIcon(item.type)}
  200. title={
  201. <div className="flex items-center justify-between">
  202. <span className={!item.read ? "font-semibold" : ""}>{item.title}</span>
  203. {!item.read && <Tag color="blue">未读</Tag>}
  204. </div>
  205. }
  206. description={
  207. <div>
  208. <p className="mb-1">{item.content}</p>
  209. <span className="text-xs text-gray-400">{item.time}</span>
  210. </div>
  211. }
  212. />
  213. </List.Item>
  214. )}
  215. />
  216. </Modal>
  217. {/* 紧急报警 Modal */}
  218. <Modal
  219. title={
  220. <div className="flex items-center">
  221. <ExclamationCircleOutlined className="mr-2 text-red-500" />
  222. 紧急报警处理
  223. </div>
  224. }
  225. open={emergencyModalVisible}
  226. onCancel={() => setEmergencyModalVisible(false)}
  227. footer={[
  228. <Button key="close" onClick={() => setEmergencyModalVisible(false)}>关闭</Button>,
  229. <Button
  230. key="handle"
  231. type="primary"
  232. danger
  233. onClick={() => {
  234. globalMessage.success("已派发处理任务给相关人员")
  235. setEmergencyModalVisible(false)
  236. }}
  237. >
  238. 立即处理
  239. </Button>,
  240. ]}
  241. width={800}
  242. >
  243. <List
  244. dataSource={emergencyAlarms}
  245. renderItem={(item) => (
  246. <List.Item className="transition-all duration-200 hover:bg-gray-50 rounded-lg p-4 border border-gray-200 mb-3">
  247. <div className="w-full">
  248. <div className="flex items-center justify-between mb-3">
  249. <div className="flex items-center space-x-2">
  250. <Tag color={getAlarmLevelColor(item.level)}>{item.level}级报警</Tag>
  251. <Tag color={getStatusColor(item.status)}>{item.status}</Tag>
  252. </div>
  253. <span className="text-sm text-gray-500">{item.time}</span>
  254. </div>
  255. <Descriptions column={1} size="small">
  256. <Descriptions.Item label="报警位置">{item.location}</Descriptions.Item>
  257. <Descriptions.Item label="报警类型">{item.type}</Descriptions.Item>
  258. <Descriptions.Item label="详细描述">{item.description}</Descriptions.Item>
  259. </Descriptions>
  260. </div>
  261. </List.Item>
  262. )}
  263. />
  264. </Modal>
  265. </Layout>
  266. )
  267. }