| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- import React, { useState, useEffect, useRef } from 'react';
- import { getDeviceStatus, getDeviceHistory } from './api/monitor.ts';
- import dayjs from 'dayjs';
- import { Card, Space, Table, message, Select, Tag, Tabs, Button } from 'antd';
- import ReactECharts from 'echarts-for-react';
- import type { EChartsType } from 'echarts';
- interface DeviceStatusResponse {
- status: 0 | 1;
- timestamp: string;
- }
- interface DeviceData {
- id: string;
- name: string;
- type: 'smoke' | 'water';
- status: 0 | 1; // 1:正常, 0:异常
- timestamp: string;
- value?: number;
- }
- interface HistoryData {
- status: 0 | 1;
- timestamp: string;
- }
- interface WaterIconProps {
- timestamp?: string;
- }
- const WaterIcon = () => (
- <div style={{position: 'relative', width: 80, height: 80}}>
- <img
- src="/client/shuijintubiao.png"
- alt="水浸图标"
- style={{width: '100%', height: '100%'}}
- />
- </div>
- );
- interface SmokeIconProps {
- status?: 0 | 1;
- timestamp?: string;
- }
- const SmokeIcon = ({status = 1}: SmokeIconProps) => (
- <div style={{position: 'relative', width: 80, height: 80}}>
- <img
- src="/client/admin/api/yangantubiao.png"
- alt="烟感图标"
- style={{
- width: '100%',
- height: '100%',
- border: status === 0 ? '2px solid #ff4d4f' : 'none',
- backgroundColor: status === 0 ? 'rgba(255, 77, 79, 0.1)' : 'transparent',
- borderRadius: 4
- }}
- />
- </div>
- );
- const StatusDisplay = ({type, status}: {type: 'smoke' | 'water', status: 0 | 1}) => (
- <div style={{
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'center',
- alignItems: 'center',
- backgroundColor: status === 1 ? '#f6ffed' : '#fff2f0',
- border: `1px solid ${status === 1 ? '#b7eb8f' : '#ffccc7'}`,
- borderRadius: 4,
- fontSize: 14,
- fontWeight: 'bold',
- padding: 8
- }}>
- <div>{type === 'smoke' ? '烟感' : '水浸'}</div>
- <div style={{color: status === 1 ? '#52c41a' : '#f5222d'}}>
- {status === 1 ? '正常' : '异常'}
- </div>
- </div>
- );
- const SmokeWaterPage: React.FC = () => {
- const [devices, setDevices] = useState<DeviceData[]>([
- {id: 'smoke1', name: '烟感1', type: 'smoke', status: 0, timestamp: new Date().toISOString()},
- {id: 'smoke2', name: '烟感2', type: 'smoke', status: 0, timestamp: new Date().toISOString()},
- {id: 'water1', name: '水浸1', type: 'water', status: 0, timestamp: new Date().toISOString()},
- {id: 'water2', name: '水浸2', type: 'water', status: 0, timestamp: new Date().toISOString()}
- ]);
- const [loading, setLoading] = useState(false);
- const [selectedDevice, setSelectedDevice] = useState<string>('smoke1');
- const [activeTab, setActiveTab] = useState<'table' | 'chart'>('table');
- const [isPolling, setIsPolling] = useState(true);
- const [pollInterval, setPollInterval] = useState(30000); // 默认30秒
- const chartRef = useRef<EChartsType>(null);
- const fetchDeviceStatus = async () => {
- try {
- setLoading(true);
- const res = await getDeviceStatus({
- device_id: Number(selectedDevice.replace(/\D/g, '')),
- device_type: selectedDevice.startsWith('smoke') ? 'smoke' : 'water'
- });
- setDevices(prev => prev.map(d =>
- d.id === selectedDevice
- ? {...d, status: res.status, timestamp: res.timestamp}
- : d
- ));
- } catch (error) {
- message.error('获取设备状态失败');
- } finally {
- setLoading(false);
- }
- };
- const fetchDeviceHistory = async (deviceId: string) => {
- try {
- const res = await getDeviceHistory({
- device_id: Number(deviceId.replace(/\D/g, '')),
- device_type: selectedDevice.startsWith('smoke') ? 'smoke' : 'water',
- start_time: new Date(Date.now() - 86400000).toISOString(),
- end_time: new Date().toISOString()
- });
- return res.data;
- } catch (error) {
- message.error('获取历史数据失败');
- return [];
- }
- };
- useEffect(() => {
- fetchDeviceStatus();
- if (!isPolling) return;
-
- const timer = setInterval(fetchDeviceStatus, pollInterval);
- return () => clearInterval(timer);
- }, [isPolling, pollInterval]);
- useEffect(() => {
- if (activeTab === 'chart' && selectedDevice && chartRef.current) {
- fetchDeviceHistory(selectedDevice).then(data => {
- const option = {
- xAxis: {
- type: 'category',
- data: data.map((d: HistoryData) => new Date(d.timestamp).toLocaleTimeString())
- },
- yAxis: {
- type: 'value',
- min: 0,
- max: 1,
- interval: 1
- },
- series: [{
- data: data.map((d: HistoryData) => d.status),
- type: 'line',
- smooth: true
- }]
- };
- chartRef.current?.setOption(option);
- });
- }
- }, [activeTab, selectedDevice]);
- const columns = [
- {
- title: '设备名称',
- dataIndex: 'name',
- key: 'name',
- },
- {
- title: '设备类型',
- dataIndex: 'type',
- key: 'type',
- render: (type: string) => type === 'smoke' ? '烟感' : '水浸',
- },
- {
- title: '状态',
- dataIndex: 'status',
- key: 'status',
- render: (status: number) => (
- <Tag color={status === 1 ? 'green' : 'red'}>
- {status === 1 ? '正常' : '异常'}
- </Tag>
- ),
- },
- {
- title: '更新时间',
- dataIndex: 'timestamp',
- key: 'timestamp',
- render: (text?: string) => text ? new Date(text).toLocaleString() : '-',
- }
- ];
- const currentDevice = devices.find(d => d.id === selectedDevice);
- return (
- <div style={{ padding: 24 }}>
- <Space direction="vertical" size="large" style={{ width: '100%' }}>
- <Card
- title={
- <Space>
- <Select
- value={selectedDevice}
- style={{ width: 200 }}
- onChange={setSelectedDevice}
- options={devices.map(d => ({
- value: d.id,
- label: d.name
- }))}
- />
- <Button
- type={isPolling ? 'default' : 'primary'}
- onClick={() => setIsPolling(!isPolling)}
- >
- {isPolling ? '停止轮询' : '开始轮询'}
- </Button>
- <Select
- value={pollInterval}
- style={{ width: 120 }}
- onChange={(value) => setPollInterval(value)}
- options={[
- { value: 10000, label: '10秒' },
- { value: 30000, label: '30秒' },
- { value: 60000, label: '1分钟' }
- ]}
- />
- </Space>
- }
- >
- <div style={{
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- height: 120
- }}>
- {currentDevice && (
- <div style={{display: 'flex', gap: 16, position: 'relative', justifyContent: 'center', alignItems: 'center'}}>
- {currentDevice.type === 'water' ? (
- <WaterIcon />
- ) : (
- <SmokeIcon status={currentDevice.status} />
- )}
- <StatusDisplay type={currentDevice.type} status={currentDevice.status} />
- <div style={{marginLeft: '100px'}}>
- <div style={{
- color: 'black',
- fontSize: 12,
- padding: '2px 8px',
- borderRadius: 4
- }}>
- 时间
- </div>
- <div style={{
- color: 'black',
- fontSize: 12,
- padding: '2px 8px',
- borderRadius: 4,
- marginTop: 4
- }}>
- {dayjs(currentDevice.timestamp).format('YYYY/M/D HH:mm:ss')}
- </div>
- </div>
- </div>
- )}
- </div>
- </Card>
- <Tabs
- activeKey={activeTab}
- onChange={(key: string) => setActiveTab(key as 'table' | 'chart')}
- items={[
- {
- key: 'table',
- label: '数据表格',
- children: (
- <Table
- columns={columns}
- dataSource={devices}
- rowKey="id"
- loading={loading}
- />
- )
- },
- {
- key: 'chart',
- label: '趋势图表',
- children: (
- <ReactECharts
- // @ts-ignore - ReactECharts ref类型问题
- ref={chartRef}
- style={{ height: 400 }}
- option={{
- xAxis: { type: 'category' },
- yAxis: { type: 'value' },
- series: [{ type: 'line' }]
- }}
- />
- )
- }
- ]}
- />
- </Space>
- </div>
- );
- };
- export default SmokeWaterPage;
|