|
|
@@ -0,0 +1,1224 @@
|
|
|
+"use client"
|
|
|
+
|
|
|
+import {useCallback, useEffect, useState} from "react"
|
|
|
+import dynamic from "next/dynamic"
|
|
|
+import {
|
|
|
+ Badge,
|
|
|
+ Button,
|
|
|
+ Card,
|
|
|
+ Checkbox,
|
|
|
+ Col,
|
|
|
+ DatePicker,
|
|
|
+ Dropdown,
|
|
|
+ Form,
|
|
|
+ Input,
|
|
|
+ Layout,
|
|
|
+ Menu,
|
|
|
+ Modal,
|
|
|
+ Progress,
|
|
|
+ Row,
|
|
|
+ Select,
|
|
|
+ Space,
|
|
|
+ Statistic,
|
|
|
+ Switch,
|
|
|
+ Table,
|
|
|
+ Tabs,
|
|
|
+ Tag,
|
|
|
+} from "antd"
|
|
|
+// 为避免未定义错误,添加缺失的图标导入
|
|
|
+import {
|
|
|
+ BarChartOutlined,
|
|
|
+ BellOutlined,
|
|
|
+ BlockOutlined,
|
|
|
+ CheckOutlined,
|
|
|
+ DashboardOutlined,
|
|
|
+ DownOutlined,
|
|
|
+ EnvironmentOutlined,
|
|
|
+ ExportOutlined,
|
|
|
+ FilterOutlined,
|
|
|
+ LineChartOutlined,
|
|
|
+ MonitorOutlined,
|
|
|
+ PieChartOutlined,
|
|
|
+ ReloadOutlined,
|
|
|
+ SearchOutlined,
|
|
|
+ SettingOutlined,
|
|
|
+ UserOutlined,
|
|
|
+ VideoCameraOutlined,
|
|
|
+ WarningOutlined,
|
|
|
+} from "@ant-design/icons"
|
|
|
+import MonitoringCharts from "./components/MonitoringCharts"
|
|
|
+import AlarmPanel from "./components/AlarmPanel"
|
|
|
+import VideoMonitoring from "./components/VideoMonitoring"
|
|
|
+import DataManagement from "./components/DataManagement"
|
|
|
+import globalMessage from "@/app/_modules/globalMessage"
|
|
|
+
|
|
|
+const { RangePicker } = DatePicker
|
|
|
+const MapView = dynamic(() => import("./components/MapView"), {
|
|
|
+ ssr: false,
|
|
|
+ loading: () => <div className="flex items-center justify-center h-full">地图加载中...</div>,
|
|
|
+})
|
|
|
+
|
|
|
+const { Header, Sider, Content } = Layout
|
|
|
+const { Option } = Select
|
|
|
+const { TabPane } = Tabs
|
|
|
+
|
|
|
+export default function DrainageMonitoringSystem() {
|
|
|
+ const [selectedMenu, setSelectedMenu] = useState("dashboard")
|
|
|
+ const [collapsed, setCollapsed] = useState(false)
|
|
|
+ const [videoModalVisible, setVideoModalVisible] = useState(false)
|
|
|
+ const [currentTime, setCurrentTime] = useState("")
|
|
|
+ const [notifications, setNotifications] = useState([
|
|
|
+ { id: 1, message: "人民路积水点液位超过阈值", time: "2分钟前", type: "warning" },
|
|
|
+ { id: 2, message: "第一泵站设备离线", time: "15分钟前", type: "error" },
|
|
|
+ ])
|
|
|
+ const [autoRefresh, setAutoRefresh] = useState(true)
|
|
|
+ const [videoSettingsModal, setVideoSettingsModal] = useState(false)
|
|
|
+
|
|
|
+ // 只在客户端更新时间,避免服务端和客户端渲染不一致
|
|
|
+ useEffect(() => {
|
|
|
+ const updateTime = () => {
|
|
|
+ const now = new Date()
|
|
|
+ const hours = now.getHours().toString().padStart(2, "0")
|
|
|
+ const minutes = now.getMinutes().toString().padStart(2, "0")
|
|
|
+ const seconds = now.getSeconds().toString().padStart(2, "0")
|
|
|
+ setCurrentTime(`${hours}:${minutes}:${seconds}`)
|
|
|
+ }
|
|
|
+
|
|
|
+ updateTime()
|
|
|
+ const interval = setInterval(updateTime, 1000)
|
|
|
+
|
|
|
+ return () => clearInterval(interval)
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ // 自动刷新数据
|
|
|
+ useEffect(() => {
|
|
|
+ let interval: NodeJS.Timeout | null = null
|
|
|
+ if (autoRefresh) {
|
|
|
+ interval = setInterval(() => {
|
|
|
+ // 这里可以添加刷新数据的逻辑
|
|
|
+ console.log("自动刷新数据")
|
|
|
+ }, 30000) // 30秒刷新一次
|
|
|
+ }
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ if (interval) clearInterval(interval)
|
|
|
+ }
|
|
|
+ }, [autoRefresh])
|
|
|
+
|
|
|
+ const menuItems = [
|
|
|
+ {
|
|
|
+ key: "dashboard",
|
|
|
+ icon: <DashboardOutlined />,
|
|
|
+ label: "综合监控",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "data-management",
|
|
|
+ icon: <EnvironmentOutlined />,
|
|
|
+ label: "基础数据管理",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "real-time-monitoring",
|
|
|
+ icon: <MonitorOutlined />,
|
|
|
+ label: "实时监测",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "alarm-warning",
|
|
|
+ icon: <WarningOutlined />,
|
|
|
+ label: "监测报警",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "video-monitoring",
|
|
|
+ icon: <VideoCameraOutlined />,
|
|
|
+ label: "视频监测",
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const handleButtonClick = (action: string) => {
|
|
|
+ globalMessage.success(`${action} 操作已执行`)
|
|
|
+ }
|
|
|
+
|
|
|
+ const renderContent = () => {
|
|
|
+ switch (selectedMenu) {
|
|
|
+ case "dashboard":
|
|
|
+ return <DashboardContent onButtonClick={handleButtonClick} autoRefresh={autoRefresh} setAutoRefresh={setAutoRefresh} />
|
|
|
+ case "data-management":
|
|
|
+ return <DataManagement onButtonClick={handleButtonClick} />
|
|
|
+ case "real-time-monitoring":
|
|
|
+ return <RealTimeMonitoring onButtonClick={handleButtonClick} />
|
|
|
+ case "alarm-warning":
|
|
|
+ return <AlarmPanel onButtonClick={handleButtonClick} />
|
|
|
+ case "video-monitoring":
|
|
|
+ return (
|
|
|
+ <VideoMonitoringContent
|
|
|
+ onVideoClick={() => setVideoModalVisible(true)}
|
|
|
+ onButtonClick={handleButtonClick}
|
|
|
+ onRefreshVideos={() => console.log("刷新视频列表")}
|
|
|
+ onVideoSettings={() => setVideoSettingsModal(true)}
|
|
|
+ />
|
|
|
+ )
|
|
|
+ default:
|
|
|
+ return <DashboardContent onButtonClick={handleButtonClick} autoRefresh={autoRefresh} setAutoRefresh={setAutoRefresh} />
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const notificationMenu = {
|
|
|
+ items: [
|
|
|
+ {
|
|
|
+ key: 'notification-header',
|
|
|
+ label: (
|
|
|
+ <div className="flex justify-between items-center">
|
|
|
+ <h4 className="font-bold">通知</h4>
|
|
|
+ <Button type="link" size="small">清除所有</Button>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ...notifications.map(notification => ({
|
|
|
+ key: `notification-${notification.id}`,
|
|
|
+ label: (
|
|
|
+ <div className="p-2 hover:bg-gray-50">
|
|
|
+ <div className="flex justify-between">
|
|
|
+ <span className={notification.type === "warning" ? "text-orange-500" : "text-red-500"}>
|
|
|
+ {notification.message}
|
|
|
+ </span>
|
|
|
+ <span className="text-gray-400 text-sm">{notification.time}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ })),
|
|
|
+ {
|
|
|
+ key: 'notification-footer',
|
|
|
+ label: (
|
|
|
+ <div className="text-center">
|
|
|
+ <Button type="link">查看全部通知</Button>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Layout className="h-screen">
|
|
|
+ <Header className="bg-gray-50 border-b border-gray-200 px-4 flex items-center justify-between" style={{ background: "#f8f9fa" }}>
|
|
|
+ <div className="flex items-center space-x-4">
|
|
|
+ <h1 className="text-xl font-bold m-0 text-gray-800">排水管网安全运行监测子系统</h1>
|
|
|
+ </div>
|
|
|
+ <div className="flex items-center space-x-4 text-gray-600">
|
|
|
+ <Dropdown menu={notificationMenu} trigger={["click"]}>
|
|
|
+ <Badge count={notifications.length} size="small">
|
|
|
+ <BellOutlined className="text-lg cursor-pointer" />
|
|
|
+ </Badge>
|
|
|
+ </Dropdown>
|
|
|
+ <span>管理员</span>
|
|
|
+ {/* 使用 suppressHydrationWarning 忽略时间显示的 hydration 警告 */}
|
|
|
+ <span suppressHydrationWarning>{currentTime}</span>
|
|
|
+ <UserOutlined className="text-lg" />
|
|
|
+ </div>
|
|
|
+ </Header>
|
|
|
+
|
|
|
+ <Layout className="flex-1">
|
|
|
+ <Sider
|
|
|
+ collapsible
|
|
|
+ collapsed={collapsed}
|
|
|
+ onCollapse={setCollapsed}
|
|
|
+ className="bg-gray-50 border-r border-gray-200 h-full"
|
|
|
+ width={200}
|
|
|
+ >
|
|
|
+ <Menu
|
|
|
+ mode="inline"
|
|
|
+ selectedKeys={[selectedMenu]}
|
|
|
+ onClick={({ key }) => setSelectedMenu(key)}
|
|
|
+ items={menuItems}
|
|
|
+ className="border-r-0 h-full bg-gray-50"
|
|
|
+ />
|
|
|
+ </Sider>
|
|
|
+
|
|
|
+ <Content className="p-6 bg-white overflow-auto">{renderContent()}</Content>
|
|
|
+ </Layout>
|
|
|
+
|
|
|
+ <Modal
|
|
|
+ title="视频监控"
|
|
|
+ open={videoModalVisible}
|
|
|
+ onCancel={() => setVideoModalVisible(false)}
|
|
|
+ width={1200}
|
|
|
+ footer={[
|
|
|
+ <Button key="close" onClick={() => setVideoModalVisible(false)}>
|
|
|
+ 关闭
|
|
|
+ </Button>,
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <VideoMonitoring onButtonClick={handleButtonClick} />
|
|
|
+ </Modal>
|
|
|
+
|
|
|
+ <Modal
|
|
|
+ title="视频设置"
|
|
|
+ open={videoSettingsModal}
|
|
|
+ onCancel={() => setVideoSettingsModal(false)}
|
|
|
+ width={600}
|
|
|
+ footer={[
|
|
|
+ <Button key="cancel" onClick={() => setVideoSettingsModal(false)}>
|
|
|
+ 取消
|
|
|
+ </Button>,
|
|
|
+ <Button key="submit" type="primary" onClick={() => {
|
|
|
+ setVideoSettingsModal(false)
|
|
|
+ globalMessage.success("视频设置已保存")
|
|
|
+ }}>
|
|
|
+ 保存
|
|
|
+ </Button>
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <VideoSettingsForm />
|
|
|
+ </Modal>
|
|
|
+ </Layout>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+function VideoSettingsForm() {
|
|
|
+ const [form] = Form.useForm()
|
|
|
+
|
|
|
+ const onFinish = (values: any) => {
|
|
|
+ console.log('视频设置:', values)
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Form
|
|
|
+ form={form}
|
|
|
+ layout="vertical"
|
|
|
+ onFinish={onFinish}
|
|
|
+ initialValues={{
|
|
|
+ quality: 'high',
|
|
|
+ autoRecord: false,
|
|
|
+ motionDetection: true,
|
|
|
+ nightVision: true,
|
|
|
+ resolution: '1080p',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Form.Item name="quality" label="视频质量">
|
|
|
+ <Select>
|
|
|
+ <Option value="low">低</Option>
|
|
|
+ <Option value="medium">中</Option>
|
|
|
+ <Option value="high">高</Option>
|
|
|
+ <Option value="ultra">超高清</Option>
|
|
|
+ </Select>
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item name="resolution" label="分辨率">
|
|
|
+ <Select>
|
|
|
+ <Option value="720p">720p</Option>
|
|
|
+ <Option value="1080p">1080p</Option>
|
|
|
+ <Option value="2k">2K</Option>
|
|
|
+ <Option value="4k">4K</Option>
|
|
|
+ </Select>
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item name="autoRecord" valuePropName="checked">
|
|
|
+ <Checkbox>自动录像</Checkbox>
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item name="motionDetection" valuePropName="checked">
|
|
|
+ <Checkbox>移动侦测</Checkbox>
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item name="nightVision" valuePropName="checked">
|
|
|
+ <Checkbox>夜视功能</Checkbox>
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item name="storagePath" label="录像存储路径">
|
|
|
+ <Input placeholder="请输入存储路径" />
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+function DashboardContent({ onButtonClick, autoRefresh, setAutoRefresh }: {
|
|
|
+ onButtonClick: (action: string) => void,
|
|
|
+ autoRefresh: boolean,
|
|
|
+ setAutoRefresh: (value: boolean) => void
|
|
|
+}) {
|
|
|
+ const [deviceStats, setDeviceStats] = useState({
|
|
|
+ total: 1234,
|
|
|
+ online: 1180,
|
|
|
+ alarms: 23,
|
|
|
+ waterPoints: 156
|
|
|
+ })
|
|
|
+
|
|
|
+ const [timeRange, setTimeRange] = useState<[string, string]>(['今天', ''])
|
|
|
+
|
|
|
+ // 模拟数据更新
|
|
|
+ const updateStats = useCallback(() => {
|
|
|
+ setDeviceStats(prev => ({
|
|
|
+ total: prev.total,
|
|
|
+ online: prev.online + Math.floor(Math.random() * 3) - 1,
|
|
|
+ alarms: Math.max(0, prev.alarms + Math.floor(Math.random() * 3) - 1),
|
|
|
+ waterPoints: prev.waterPoints
|
|
|
+ }))
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (autoRefresh) {
|
|
|
+ const interval = setInterval(updateStats, 10000)
|
|
|
+ return () => clearInterval(interval)
|
|
|
+ }
|
|
|
+ }, [autoRefresh, updateStats])
|
|
|
+
|
|
|
+ const timeRangeOptions = [
|
|
|
+ { label: '今天', value: 'today' },
|
|
|
+ { label: '昨天', value: 'yesterday' },
|
|
|
+ { label: '最近7天', value: '7days' },
|
|
|
+ { label: '最近30天', value: '30days' },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const handleTimeRangeChange = (value: string) => {
|
|
|
+ setTimeRange([value, ''])
|
|
|
+ onButtonClick(`切换时间范围到${value}`)
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="space-y-6">
|
|
|
+ {/* 控制面板 */
|
|
|
+ }
|
|
|
+ <Card size="small">
|
|
|
+ <div className="flex flex-wrap items-center justify-between gap-4">
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
+ <span>时间范围:</span>
|
|
|
+ <Select
|
|
|
+ defaultValue="today"
|
|
|
+ onChange={handleTimeRangeChange}
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ {timeRangeOptions.map(option => (
|
|
|
+ <Option key={option.value} value={option.value}>{option.label}</Option>
|
|
|
+ ))}
|
|
|
+ </Select>
|
|
|
+ <RangePicker size="small" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="flex items-center space-x-4">
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
+ <span>自动刷新:</span>
|
|
|
+ <Switch
|
|
|
+ checked={autoRefresh}
|
|
|
+ onChange={setAutoRefresh}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <Button
|
|
|
+ icon={<ReloadOutlined />}
|
|
|
+ onClick={() => {
|
|
|
+ updateStats()
|
|
|
+ onButtonClick("手动刷新数据")
|
|
|
+ }}
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ 刷新
|
|
|
+ </Button>
|
|
|
+ <Dropdown
|
|
|
+ menu={{
|
|
|
+ items: [
|
|
|
+ { key: 'export-excel', label: '导出为Excel' },
|
|
|
+ { key: 'export-pdf', label: '导出为PDF' },
|
|
|
+ { key: 'export-image', label: '导出为图片' },
|
|
|
+ ],
|
|
|
+ onClick: ({ key }) => onButtonClick(`执行导出操作: ${key}`)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Button icon={<ExportOutlined />} size="small">
|
|
|
+ 导出 <DownOutlined />
|
|
|
+ </Button>
|
|
|
+ </Dropdown>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ {/* 统计卡片 */}
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card
|
|
|
+ className="hover:shadow-md transition-shadow cursor-pointer"
|
|
|
+ onClick={() => onButtonClick("查看设备总数详情")}
|
|
|
+ >
|
|
|
+ <Statistic
|
|
|
+ title="监测设备总数"
|
|
|
+ value={deviceStats.total}
|
|
|
+ valueStyle={{ color: "#3f8600" }}
|
|
|
+ suffix="台"
|
|
|
+ />
|
|
|
+ <div className="mt-2">
|
|
|
+ <Progress
|
|
|
+ percent={Math.round((deviceStats.online / deviceStats.total) * 100)}
|
|
|
+ size="small"
|
|
|
+ status="normal"
|
|
|
+ />
|
|
|
+ <div className="text-xs text-gray-500 mt-1">
|
|
|
+ 在线率: {Math.round((deviceStats.online / deviceStats.total) * 100)}%
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card
|
|
|
+ className="hover:shadow-md transition-shadow cursor-pointer"
|
|
|
+ onClick={() => onButtonClick("查看在线设备详情")}
|
|
|
+ >
|
|
|
+ <Statistic
|
|
|
+ title="在线设备"
|
|
|
+ value={deviceStats.online}
|
|
|
+ valueStyle={{ color: "#1890ff" }}
|
|
|
+ suffix="台"
|
|
|
+ />
|
|
|
+ <div className="mt-2 flex items-center">
|
|
|
+ <Tag color="success">正常</Tag>
|
|
|
+ <span className="text-xs text-gray-500 ml-2">
|
|
|
+ {deviceStats.total - deviceStats.online} 台离线
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card
|
|
|
+ className="hover:shadow-md transition-shadow cursor-pointer"
|
|
|
+ onClick={() => onButtonClick("查看当前报警详情")}
|
|
|
+ >
|
|
|
+ <Statistic
|
|
|
+ title="当前报警"
|
|
|
+ value={deviceStats.alarms}
|
|
|
+ valueStyle={{ color: deviceStats.alarms > 0 ? "#cf1322" : "#1890ff" }}
|
|
|
+ suffix="条"
|
|
|
+ />
|
|
|
+ <div className="mt-2">
|
|
|
+ <Badge
|
|
|
+ status={deviceStats.alarms > 0 ? "error" : "success"}
|
|
|
+ text={deviceStats.alarms > 0 ? "有报警" : "无报警"}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={6}>
|
|
|
+ <Card
|
|
|
+ className="hover:shadow-md transition-shadow cursor-pointer"
|
|
|
+ onClick={() => onButtonClick("查看易积水点详情")}
|
|
|
+ >
|
|
|
+ <Statistic
|
|
|
+ title="易积水点"
|
|
|
+ value={deviceStats.waterPoints}
|
|
|
+ valueStyle={{ color: "#722ed1" }}
|
|
|
+ suffix="个"
|
|
|
+ />
|
|
|
+ <div className="mt-2 flex items-center">
|
|
|
+ <Tag color={deviceStats.alarms > 5 ? "error" : "warning"}>
|
|
|
+ {deviceStats.alarms > 5 ? "高风险" : "中风险"}
|
|
|
+ </Tag>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ {/* 地图和图表 */}
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col span={16}>
|
|
|
+ <Card
|
|
|
+ title="GIS 一张图"
|
|
|
+ className="h-96"
|
|
|
+ extra={
|
|
|
+ <Space>
|
|
|
+ <Button size="small" icon={<SettingOutlined />} onClick={() => onButtonClick("地图设置")}>
|
|
|
+ 设置
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <MapView />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={8}>
|
|
|
+ <Card
|
|
|
+ title="实时监测数据"
|
|
|
+ className="h-96"
|
|
|
+ extra={
|
|
|
+ <Dropdown
|
|
|
+ menu={{
|
|
|
+ items: [
|
|
|
+ { key: 'line', label: '折线图', icon: <LineChartOutlined /> },
|
|
|
+ { key: 'bar', label: '柱状图', icon: <BarChartOutlined /> },
|
|
|
+ { key: 'pie', label: '饼图', icon: <PieChartOutlined /> },
|
|
|
+ ],
|
|
|
+ onClick: ({ key }) => onButtonClick(`切换图表类型: ${key}`)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Button size="small" icon={<BarChartOutlined />}>
|
|
|
+ 图表类型 <DownOutlined />
|
|
|
+ </Button>
|
|
|
+ </Dropdown>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <MonitoringCharts />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+
|
|
|
+ {/* 报警信息 */}
|
|
|
+ <Card
|
|
|
+ title="最新报警信息"
|
|
|
+ extra={
|
|
|
+ <Button type="link" onClick={() => onButtonClick("查看全部报警")}>
|
|
|
+ 查看更多
|
|
|
+ </Button>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <AlarmList onButtonClick={onButtonClick} />
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+function RealTimeMonitoring({ onButtonClick }: { onButtonClick: (action: string) => void }) {
|
|
|
+ const [filterStatus, setFilterStatus] = useState<string | null>(null)
|
|
|
+ const [filterType, setFilterType] = useState<string | null>(null)
|
|
|
+ const [searchText, setSearchText] = useState("")
|
|
|
+
|
|
|
+ const deviceData = [
|
|
|
+ {
|
|
|
+ key: "1",
|
|
|
+ deviceId: "FL001",
|
|
|
+ deviceName: "主干道流量计",
|
|
|
+ deviceType: "流量计",
|
|
|
+ status: "online",
|
|
|
+ currentValue: "2.5 m³/s",
|
|
|
+ threshold: "3.0 m³/s",
|
|
|
+ lastUpdate: "2024-01-15 14:29:30",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "2",
|
|
|
+ deviceId: "LV002",
|
|
|
+ deviceName: "积水点液位计",
|
|
|
+ deviceType: "液位计",
|
|
|
+ status: "alarm",
|
|
|
+ currentValue: "0.8 m",
|
|
|
+ threshold: "0.5 m",
|
|
|
+ lastUpdate: "2024-01-15 14:29:25",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "3",
|
|
|
+ deviceId: "PS001",
|
|
|
+ deviceName: "第一泵站",
|
|
|
+ deviceType: "泵站",
|
|
|
+ status: "offline",
|
|
|
+ currentValue: "-",
|
|
|
+ threshold: "-",
|
|
|
+ lastUpdate: "2024-01-15 14:25:10",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "4",
|
|
|
+ deviceId: "LV003",
|
|
|
+ deviceName: "商业区液位计",
|
|
|
+ deviceType: "液位计",
|
|
|
+ status: "online",
|
|
|
+ currentValue: "0.2 m",
|
|
|
+ threshold: "0.5 m",
|
|
|
+ lastUpdate: "2024-01-15 14:29:35",
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const filteredData = deviceData.filter(device => {
|
|
|
+ const matchesStatus = !filterStatus || device.status === filterStatus
|
|
|
+ const matchesType = !filterType || device.deviceType === filterType
|
|
|
+ const matchesSearch = !searchText ||
|
|
|
+ device.deviceId.toLowerCase().includes(searchText.toLowerCase()) ||
|
|
|
+ device.deviceName.toLowerCase().includes(searchText.toLowerCase())
|
|
|
+
|
|
|
+ return matchesStatus && matchesType && matchesSearch
|
|
|
+ })
|
|
|
+
|
|
|
+ const clearFilters = () => {
|
|
|
+ setFilterStatus(null)
|
|
|
+ setFilterType(null)
|
|
|
+ setSearchText("")
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="space-y-6">
|
|
|
+ <Card title="设备运行实时监测">
|
|
|
+ <div className="mb-4">
|
|
|
+ <Space wrap>
|
|
|
+ <Input
|
|
|
+ placeholder="搜索设备编号或名称"
|
|
|
+ prefix={<SearchOutlined />}
|
|
|
+ value={searchText}
|
|
|
+ onChange={e => setSearchText(e.target.value)}
|
|
|
+ style={{ width: 200 }}
|
|
|
+ />
|
|
|
+ <Select
|
|
|
+ placeholder="设备类型"
|
|
|
+ style={{ width: 120 }}
|
|
|
+ value={filterType}
|
|
|
+ onChange={setFilterType}
|
|
|
+ allowClear
|
|
|
+ >
|
|
|
+ <Option value="流量计">流量计</Option>
|
|
|
+ <Option value="液位计">液位计</Option>
|
|
|
+ <Option value="泵站">泵站</Option>
|
|
|
+ </Select>
|
|
|
+ <Select
|
|
|
+ placeholder="运行状态"
|
|
|
+ style={{ width: 120 }}
|
|
|
+ value={filterStatus}
|
|
|
+ onChange={setFilterStatus}
|
|
|
+ allowClear
|
|
|
+ >
|
|
|
+ <Option value="online">在线</Option>
|
|
|
+ <Option value="offline">离线</Option>
|
|
|
+ <Option value="alarm">报警</Option>
|
|
|
+ </Select>
|
|
|
+ <Button
|
|
|
+ icon={<FilterOutlined />}
|
|
|
+ onClick={clearFilters}
|
|
|
+ >
|
|
|
+ 清除筛选
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ icon={<SearchOutlined />}
|
|
|
+ onClick={() => onButtonClick("查询设备")}
|
|
|
+ >
|
|
|
+ 查询
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ icon={<ExportOutlined />}
|
|
|
+ onClick={() => onButtonClick("导出数据")}
|
|
|
+ >
|
|
|
+ 导出
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ icon={<ReloadOutlined />}
|
|
|
+ onClick={() => onButtonClick("刷新数据")}
|
|
|
+ >
|
|
|
+ 刷新
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <Table
|
|
|
+ columns={[
|
|
|
+ {
|
|
|
+ title: "设备编号",
|
|
|
+ dataIndex: "deviceId",
|
|
|
+ key: "deviceId",
|
|
|
+ sorter: (a, b) => a.deviceId.localeCompare(b.deviceId),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "设备名称",
|
|
|
+ dataIndex: "deviceName",
|
|
|
+ key: "deviceName",
|
|
|
+ sorter: (a, b) => a.deviceName.localeCompare(b.deviceName),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "设备类型",
|
|
|
+ dataIndex: "deviceType",
|
|
|
+ key: "deviceType",
|
|
|
+ filters: [
|
|
|
+ { text: '流量计', value: '流量计' },
|
|
|
+ { text: '液位计', value: '液位计' },
|
|
|
+ { text: '泵站', value: '泵站' },
|
|
|
+ ],
|
|
|
+ onFilter: (value, record) => record.deviceType === value,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "运行状态",
|
|
|
+ dataIndex: "status",
|
|
|
+ key: "status",
|
|
|
+ filters: [
|
|
|
+ { text: '在线', value: 'online' },
|
|
|
+ { text: '离线', value: 'offline' },
|
|
|
+ { text: '报警', value: 'alarm' },
|
|
|
+ ],
|
|
|
+ onFilter: (value, record) => record.status === value,
|
|
|
+ render: (status: string) => (
|
|
|
+ <Badge
|
|
|
+ status={status === "online" ? "success" : status === "offline" ? "default" : "error"}
|
|
|
+ text={status === "online" ? "在线" : status === "offline" ? "离线" : "报警"}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "当前值",
|
|
|
+ dataIndex: "currentValue",
|
|
|
+ key: "currentValue",
|
|
|
+ sorter: (a, b) => {
|
|
|
+ const aVal = parseFloat(a.currentValue) || 0
|
|
|
+ const bVal = parseFloat(b.currentValue) || 0
|
|
|
+ return aVal - bVal
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "报警阈值",
|
|
|
+ dataIndex: "threshold",
|
|
|
+ key: "threshold"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "最后更新",
|
|
|
+ dataIndex: "lastUpdate",
|
|
|
+ key: "lastUpdate",
|
|
|
+ sorter: (a, b) => a.lastUpdate.localeCompare(b.lastUpdate),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "操作",
|
|
|
+ key: "action",
|
|
|
+ render: (_, record) => (
|
|
|
+ <Space>
|
|
|
+ <Button
|
|
|
+ size="small"
|
|
|
+ onClick={() => onButtonClick(`查看设备详情: ${record.deviceName}`)}
|
|
|
+ >
|
|
|
+ 详情
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ size="small"
|
|
|
+ onClick={() => onButtonClick(`设备定位: ${record.deviceName}`)}
|
|
|
+ >
|
|
|
+ 定位
|
|
|
+ </Button>
|
|
|
+ <Dropdown
|
|
|
+ menu={{
|
|
|
+ items: [
|
|
|
+ { key: 'repair', label: '维修派单' },
|
|
|
+ { key: 'history', label: '历史数据' },
|
|
|
+ { key: 'settings', label: '设备设置' },
|
|
|
+ ],
|
|
|
+ onClick: ({ key }) => onButtonClick(`${key === 'repair' ? '维修派单' : key === 'history' ? '查看历史数据' : '设备设置'}: ${record.deviceName}`)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Button size="small">
|
|
|
+ 更多 <DownOutlined />
|
|
|
+ </Button>
|
|
|
+ </Dropdown>
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ]}
|
|
|
+ dataSource={filteredData}
|
|
|
+ pagination={{
|
|
|
+ pageSize: 10,
|
|
|
+ showSizeChanger: true,
|
|
|
+ showQuickJumper: true,
|
|
|
+ }}
|
|
|
+ scroll={{ x: 'max-content' }}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ <Row gutter={[16, 16]}>
|
|
|
+ <Col span={12}>
|
|
|
+ <Card
|
|
|
+ title="积水情况 GIS 一张图"
|
|
|
+ extra={
|
|
|
+ <Button
|
|
|
+ size="small"
|
|
|
+ onClick={() => onButtonClick("刷新积水地图")}
|
|
|
+ >
|
|
|
+ 刷新
|
|
|
+ </Button>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <MapView type="waterlogging" />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ <Col span={12}>
|
|
|
+ <Card
|
|
|
+ title="管网运行情况 GIS 一张图"
|
|
|
+ extra={
|
|
|
+ <Button
|
|
|
+ size="small"
|
|
|
+ onClick={() => onButtonClick("刷新管网地图")}
|
|
|
+ >
|
|
|
+ 刷新
|
|
|
+ </Button>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <MapView type="pipeline" />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+function VideoMonitoringContent({
|
|
|
+ onVideoClick,
|
|
|
+ onButtonClick,
|
|
|
+ onRefreshVideos,
|
|
|
+ onVideoSettings
|
|
|
+ }: {
|
|
|
+ onVideoClick: () => void;
|
|
|
+ onButtonClick: (action: string) => void;
|
|
|
+ onRefreshVideos: () => void;
|
|
|
+ onVideoSettings: () => void;
|
|
|
+ }) {
|
|
|
+ const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
|
|
+ const [selectedVideos, setSelectedVideos] = useState<number[]>([])
|
|
|
+
|
|
|
+ const videoData = [
|
|
|
+ { id: 1, name: "人民路积水点监控", status: "online", location: "人民路与南京路交叉口" },
|
|
|
+ { id: 2, name: "第一泵站监控", status: "online", location: "城东泵站" },
|
|
|
+ { id: 3, name: "主干道管网监控", status: "offline", location: "淮海路主干管网" },
|
|
|
+ { id: 4, name: "南京路积水点监控", status: "online", location: "南京路商业区" },
|
|
|
+ { id: 5, name: "第二泵站监控", status: "alarm", location: "城西泵站" },
|
|
|
+ { id: 6, name: "商业区管网监控", status: "online", location: "中心商业区" },
|
|
|
+ { id: 7, name: "学校区域监控", status: "online", location: "第一中学附近" },
|
|
|
+ { id: 8, name: "住宅区监控", status: "offline", location: "阳光小区" },
|
|
|
+ ]
|
|
|
+
|
|
|
+ const toggleVideoSelection = (id: number) => {
|
|
|
+ setSelectedVideos(prev =>
|
|
|
+ prev.includes(id)
|
|
|
+ ? prev.filter(videoId => videoId !== id)
|
|
|
+ : [...prev, id]
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ const selectAllVideos = () => {
|
|
|
+ setSelectedVideos(videoData.map(video => video.id))
|
|
|
+ }
|
|
|
+
|
|
|
+ const clearVideoSelection = () => {
|
|
|
+ setSelectedVideos([])
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="space-y-6">
|
|
|
+ <Card
|
|
|
+ title="视频监控概览"
|
|
|
+ extra={
|
|
|
+ <Space>
|
|
|
+ <Button
|
|
|
+ icon={viewMode === 'grid' ? <LineChartOutlined /> : <BlockOutlined />}
|
|
|
+ onClick={() => setViewMode(viewMode === 'grid' ? 'list' : 'grid')}
|
|
|
+ >
|
|
|
+ {viewMode === 'grid' ? '列表视图' : '网格视图'}
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <div className="mb-4">
|
|
|
+ <Space wrap>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ onClick={onVideoClick}
|
|
|
+ icon={<VideoCameraOutlined />}
|
|
|
+ >
|
|
|
+ 打开视频监控
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ icon={<ReloadOutlined />}
|
|
|
+ onClick={() => {
|
|
|
+ onRefreshVideos()
|
|
|
+ globalMessage.success("视频列表已刷新")
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 刷新列表
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ icon={<SettingOutlined />}
|
|
|
+ onClick={() => {
|
|
|
+ onVideoSettings()
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 视频设置
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ onClick={selectAllVideos}
|
|
|
+ >
|
|
|
+ 全选
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ onClick={clearVideoSelection}
|
|
|
+ >
|
|
|
+ 清除选择
|
|
|
+ </Button>
|
|
|
+ {selectedVideos.length > 0 && (
|
|
|
+ <span className="text-gray-500">
|
|
|
+ 已选择 {selectedVideos.length} 个视频
|
|
|
+ </span>
|
|
|
+ )}
|
|
|
+ </Space>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {viewMode === 'grid' ? (
|
|
|
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
|
|
+ {videoData.map((video) => (
|
|
|
+ <Card
|
|
|
+ key={video.id}
|
|
|
+ size="small"
|
|
|
+ className={`cursor-pointer hover:shadow-md transition-all ${
|
|
|
+ selectedVideos.includes(video.id) ? 'ring-2 ring-blue-500' : ''
|
|
|
+ }`}
|
|
|
+ onClick={() => toggleVideoSelection(video.id)}
|
|
|
+ >
|
|
|
+ <div className="aspect-video bg-gray-200 flex items-center justify-center relative">
|
|
|
+ <VideoCameraOutlined className="text-2xl text-gray-400" />
|
|
|
+ <div className="absolute top-2 right-2">
|
|
|
+ <Badge
|
|
|
+ status={video.status === "online" ? "success" : video.status === "alarm" ? "error" : "default"}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="mt-2">
|
|
|
+ <div className="font-medium text-sm flex justify-between">
|
|
|
+ <span>{video.name}</span>
|
|
|
+ {selectedVideos.includes(video.id) && (
|
|
|
+ <CheckOutlined className="text-blue-500" />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ <div className="text-xs text-gray-500">{video.location}</div>
|
|
|
+ <div className="text-xs">
|
|
|
+ <span className={
|
|
|
+ video.status === "online"
|
|
|
+ ? "text-green-600"
|
|
|
+ : video.status === "alarm"
|
|
|
+ ? "text-red-600"
|
|
|
+ : "text-gray-400"
|
|
|
+ }>
|
|
|
+ {video.status === "online" ? "在线" : video.status === "alarm" ? "报警" : "离线"}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <Table
|
|
|
+ dataSource={videoData}
|
|
|
+ columns={[
|
|
|
+ {
|
|
|
+ title: '选择',
|
|
|
+ key: 'selection',
|
|
|
+ render: (_, record) => (
|
|
|
+ <Checkbox
|
|
|
+ checked={selectedVideos.includes(record.id)}
|
|
|
+ onChange={() => toggleVideoSelection(record.id)}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '监控点名称',
|
|
|
+ dataIndex: 'name',
|
|
|
+ key: 'name',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '位置',
|
|
|
+ dataIndex: 'location',
|
|
|
+ key: 'location',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '状态',
|
|
|
+ dataIndex: 'status',
|
|
|
+ key: 'status',
|
|
|
+ render: (status: string) => (
|
|
|
+ <Badge
|
|
|
+ status={status === "online" ? "success" : status === "alarm" ? "error" : "default"}
|
|
|
+ text={status === "online" ? "在线" : status === "alarm" ? "报警" : "离线"}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '操作',
|
|
|
+ key: 'action',
|
|
|
+ render: (_, record) => (
|
|
|
+ <Space>
|
|
|
+ <Button
|
|
|
+ size="small"
|
|
|
+ type="primary"
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ onVideoClick()
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 查看
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ size="small"
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ onButtonClick(`查看视频详情: ${record.name}`)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 详情
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ]}
|
|
|
+ rowSelection={{
|
|
|
+ selectedRowKeys: selectedVideos,
|
|
|
+ onChange: (selectedRowKeys) => {
|
|
|
+ setSelectedVideos(selectedRowKeys.map(key => Number(key)))
|
|
|
+ },
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+function AlarmList({ onButtonClick }: { onButtonClick: (action: string) => void }) {
|
|
|
+ const [alarmData] = useState([
|
|
|
+ {
|
|
|
+ key: "1",
|
|
|
+ time: "2024-01-15 14:25:30",
|
|
|
+ device: "LV002",
|
|
|
+ location: "人民路积水点",
|
|
|
+ level: "高",
|
|
|
+ message: "液位超过报警阈值",
|
|
|
+ status: "未处理",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "2",
|
|
|
+ time: "2024-01-15 14:20:15",
|
|
|
+ device: "FL001",
|
|
|
+ location: "主干道流量计",
|
|
|
+ level: "中",
|
|
|
+ message: "流量异常波动",
|
|
|
+ status: "处理中",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "3",
|
|
|
+ time: "2024-01-15 14:15:45",
|
|
|
+ device: "PS001",
|
|
|
+ location: "第一泵站",
|
|
|
+ level: "高",
|
|
|
+ message: "设备离线",
|
|
|
+ status: "未处理",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "4",
|
|
|
+ time: "2024-01-15 14:10:20",
|
|
|
+ device: "LV003",
|
|
|
+ location: "商业区液位计",
|
|
|
+ level: "低",
|
|
|
+ message: "电池电量低",
|
|
|
+ status: "已处理",
|
|
|
+ },
|
|
|
+ ])
|
|
|
+
|
|
|
+ const [filteredAlarms, setFilteredAlarms] = useState(alarmData)
|
|
|
+ const [statusFilter, setStatusFilter] = useState<string | null>(null)
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (statusFilter) {
|
|
|
+ setFilteredAlarms(alarmData.filter(alarm => alarm.status === statusFilter))
|
|
|
+ } else {
|
|
|
+ setFilteredAlarms(alarmData)
|
|
|
+ }
|
|
|
+ }, [statusFilter, alarmData])
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ <div className="mb-4 flex justify-between">
|
|
|
+ <Space>
|
|
|
+ <Select
|
|
|
+ placeholder="处理状态"
|
|
|
+ style={{ width: 120 }}
|
|
|
+ onChange={setStatusFilter}
|
|
|
+ allowClear
|
|
|
+ value={statusFilter}
|
|
|
+ >
|
|
|
+ <Option value="未处理">未处理</Option>
|
|
|
+ <Option value="处理中">处理中</Option>
|
|
|
+ <Option value="已处理">已处理</Option>
|
|
|
+ </Select>
|
|
|
+ <Button onClick={() => setStatusFilter(null)}>清除筛选</Button>
|
|
|
+ </Space>
|
|
|
+ <Button type="primary" onClick={() => onButtonClick("刷新报警列表")}>
|
|
|
+ 刷新
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ <Table
|
|
|
+ columns={[
|
|
|
+ {
|
|
|
+ title: "报警时间",
|
|
|
+ dataIndex: "time",
|
|
|
+ key: "time",
|
|
|
+ sorter: (a, b) => a.time.localeCompare(b.time),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "设备编号",
|
|
|
+ dataIndex: "device",
|
|
|
+ key: "device"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "位置",
|
|
|
+ dataIndex: "location",
|
|
|
+ key: "location"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "报警级别",
|
|
|
+ dataIndex: "level",
|
|
|
+ key: "level",
|
|
|
+ filters: [
|
|
|
+ { text: '高', value: '高' },
|
|
|
+ { text: '中', value: '中' },
|
|
|
+ { text: '低', value: '低' },
|
|
|
+ ],
|
|
|
+ onFilter: (value, record) => record.level === value,
|
|
|
+ render: (level: string) => (
|
|
|
+ <Badge
|
|
|
+ color={level === "高" ? "red" : level === "中" ? "orange" : "blue"}
|
|
|
+ text={level}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "报警信息",
|
|
|
+ dataIndex: "message",
|
|
|
+ key: "message"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "处理状态",
|
|
|
+ dataIndex: "status",
|
|
|
+ key: "status",
|
|
|
+ filters: [
|
|
|
+ { text: '未处理', value: '未处理' },
|
|
|
+ { text: '处理中', value: '处理中' },
|
|
|
+ { text: '已处理', value: '已处理' },
|
|
|
+ ],
|
|
|
+ onFilter: (value, record) => record.status === value,
|
|
|
+ render: (status: string) => (
|
|
|
+ <Badge
|
|
|
+ status={status === "未处理" ? "error" : status === "处理中" ? "processing" : "success"}
|
|
|
+ text={status}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "操作",
|
|
|
+ key: "action",
|
|
|
+ render: (_, record) => (
|
|
|
+ <Space>
|
|
|
+ <Button
|
|
|
+ size="small"
|
|
|
+ onClick={() => onButtonClick(`查看报警详情: ${record.device}`)}
|
|
|
+ >
|
|
|
+ 详情
|
|
|
+ </Button>
|
|
|
+ {record.status !== "已处理" && (
|
|
|
+ <Button
|
|
|
+ size="small"
|
|
|
+ type="primary"
|
|
|
+ onClick={() => onButtonClick(`处理报警: ${record.device}`)}
|
|
|
+ >
|
|
|
+ 处理
|
|
|
+ </Button>
|
|
|
+ )}
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ]}
|
|
|
+ dataSource={filteredAlarms}
|
|
|
+ pagination={{
|
|
|
+ pageSize: 5,
|
|
|
+ showSizeChanger: true,
|
|
|
+ showQuickJumper: true,
|
|
|
+ }}
|
|
|
+ size="small"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|