| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225 |
- "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"
- import GisMapBaidu from "@/components/gisMapBaidu";
- 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>
- }
- >
- <GisMapBaidu height={'50'} />
- </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>
- )
- }
|