| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224 |
- "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>
- )
- }
|