352 lines
17 KiB
TypeScript
352 lines
17 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
Card, Row, Col, Table, Select, Input, Tag,
|
|
Typography, Button, Space, Avatar, Pagination
|
|
} from 'antd';
|
|
import {
|
|
CloudOutlined, CheckCircleOutlined, WarningOutlined,
|
|
MinusCircleOutlined, ThunderboltOutlined,
|
|
RiseOutlined, SunOutlined, BarChartOutlined,
|
|
VideoCameraOutlined, RocketOutlined, AppstoreOutlined,
|
|
RobotOutlined, BranchesOutlined, ExperimentOutlined,
|
|
SearchOutlined, MoreOutlined, ArrowRightOutlined,
|
|
FullscreenOutlined, SyncOutlined
|
|
} from '@ant-design/icons';
|
|
import { Line, Bar, Pie } from '@ant-design/charts';
|
|
import bgMain from '../assets/images/bgmain.jpg';
|
|
import { UniversalBarChart, UniversalLineChart, UniversalPieChart } from '../components/recharts';
|
|
import CesiumMap from '../components/Map';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
const { Option } = Select;
|
|
const { Search } = Input;
|
|
const { Text, Title } = Typography;
|
|
|
|
// 模拟数据扩展
|
|
|
|
|
|
const powerTrendData = [
|
|
{ time: '00:00', value: 0, type: '实时功率' },
|
|
{ time: '04:00', value: 50, type: '实时功率' },
|
|
{ time: '08:00', value: 350, type: '实时功率' },
|
|
{ time: '12:00', value: 750, type: '实时功率' },
|
|
{ time: '16:00', value: 500, type: '实时功率' },
|
|
{ time: '20:00', value: 50, type: '实时功率' },
|
|
{ time: '24:00', value: 0, type: '实时功率' },
|
|
];
|
|
|
|
const inverterPowerData = [
|
|
{ name: 'INV-01', power: 164.2 },
|
|
{ name: 'INV-02', power: 166.8 },
|
|
{ name: 'INV-03', power: 162.1 },
|
|
{ name: 'INV-04', power: 159.7 },
|
|
{ name: 'INV-05', power: 159.8 }
|
|
];
|
|
|
|
const heatmapColors = ['#52c41a', '#1890ff', '#faad14', '#1890ff', '#52c41a'];
|
|
|
|
const RealTimeMonitorPage = () => {
|
|
const { t } = useTranslation();
|
|
const [filterStatus, setFilterStatus] = useState('all');
|
|
const [filterType, setFilterType] = useState(undefined);
|
|
const [searchName, setSearchName] = useState('');
|
|
|
|
const cardStyle = { borderRadius: 12, border: '1px solid #f0f0f0', boxShadow: '0 2px 8px rgba(0,0,0,0.04)' };
|
|
|
|
const getStatusTag = (status) => {
|
|
const color = status === t('roles.normal') ? '#52c41a' : status === t('constants.sourceAlert') ? '#faad14' : '#999';
|
|
return (
|
|
<Space size={4}>
|
|
<span style={{ width: 8, height: 8, borderRadius: '50%', background: color, display: 'inline-block' }} />
|
|
<span style={{ color }}>{status}</span>
|
|
</Space>
|
|
);
|
|
};
|
|
|
|
const columns = [
|
|
{
|
|
title: t('devices.deviceName'), dataIndex: 'name', key: 'name',
|
|
render: (text, record) => (
|
|
<Space>
|
|
<div style={{ width: 24, height: 24, background: '#f5f5f5', borderRadius: 4, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
|
{record.type === t('home.inverter') ? <ThunderboltOutlined style={{ fontSize: 14 }} /> : <BranchesOutlined style={{ fontSize: 14 }} />}
|
|
</div>
|
|
<span style={{ fontSize: 13 }}>{text}</span>
|
|
</Space>
|
|
)
|
|
},
|
|
{ title: t('common.type'), dataIndex: 'type', render: (t) => <span style={{ fontSize: 13, color: '#666' }}>{t}</span> },
|
|
{ title: t('workOrder.currentStatus'), dataIndex: 'status', render: getStatusTag },
|
|
{ title: '实时功率/数据', dataIndex: 'data', render: (d) => <span style={{ fontSize: 13, fontWeight: 500 }}>{d}</span> },
|
|
{
|
|
title: '通讯状态', dataIndex: 'communication',
|
|
render: (c) => (
|
|
<Space size={4}>
|
|
<span style={{ fontSize: 13 }}>{c}</span>
|
|
<RiseOutlined style={{ color: '#52c41a' }} />
|
|
</Space>
|
|
)
|
|
},
|
|
{ title: t('common.updateTime'), dataIndex: 'updateTime', render: (t) => <span style={{ fontSize: 12, color: '#999' }}>{t}</span> }
|
|
];
|
|
const deviceList = [
|
|
{ id: 1, name: '逆变器 INV-01', type: t('home.inverter'), status: t('roles.normal'), data: '164.2 KW', communication: '通讯正常', updateTime: '2025-05-25 10:24:15' },
|
|
{ id: 2, name: '逆变器 INV-02', type: t('home.inverter'), status: t('roles.normal'), data: '166.8 KW', communication: '通讯正常', updateTime: '2025-05-25 10:24:15' },
|
|
{ id: 3, name: '逆变器 INV-03', type: t('home.inverter'), status: t('roles.normal'), data: '162.1 KW', communication: '通讯正常', updateTime: '2025-05-25 10:24:15' },
|
|
{ id: 4, name: '逆变器 INV-04', type: t('home.inverter'), status: t('roles.normal'), data: '159.7 KW', communication: '通讯正常', updateTime: '2025-05-25 10:24:15' },
|
|
{ id: 5, name: '逆变器 INV-05', type: t('home.inverter'), status: t('constants.sourceAlert'), data: '159.8 KW', communication: '通讯正常', updateTime: '2025-05-25 10:24:15' },
|
|
{ id: 6, name: '汇流箱 01', type: t('home.combinerBox'), status: t('roles.normal'), data: '32 组 / 32 组', communication: '通讯正常', updateTime: '2025-05-25 10:24:15' },
|
|
{ id: 7, name: '汇流箱 02', type: t('home.combinerBox'), status: t('roles.normal'), data: '32 组 / 32 组', communication: '通讯正常', updateTime: '2025-05-25 10:24:15' },
|
|
{ id: 8, name: '气象站', type: '气象站', status: t('roles.normal'), data: '28.0 ℃ / 815W/m²', communication: '通讯正常', updateTime: '2025-05-25 10:24:15' },
|
|
];
|
|
|
|
return (
|
|
<div style={{ background: '#f8fafc', padding: '8px 0px', minHeight: '100vh', width: '100%', boxSizing: 'border-box' }}>
|
|
<div style={{ marginBottom: 8 }}>
|
|
<Title level={4} style={{ margin: 0, fontSize: 'clamp(14px, 1.2vw, 18px)' }}>实时监控中心</Title>
|
|
<Text type="secondary" style={{ fontSize: '12px' }}>全方位实时掌握电站设备运行状态与发电表现</Text>
|
|
</div>
|
|
<Row gutter={[8, 8]}>
|
|
{/* 左侧大栏 */}
|
|
<Col xs={24} xl={16}>
|
|
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
|
{/* 电站拓扑图 */}
|
|
<Card
|
|
title={<span style={{ fontWeight: 800, fontSize: 13 }}>电站拓扑图 (实时)</span>}
|
|
extra={
|
|
<div style={{ display: 'flex', gap: 12, fontSize: 11 }}>
|
|
<span style={{ display: 'flex', alignItems: 'center' }}><span style={{ width: 6, height: 6, borderRadius: '50%', background: '#52c41a', marginRight: 4 }}></span>roles.normal</span>
|
|
<span style={{ display: 'flex', alignItems: 'center' }}><span style={{ width: 6, height: 6, borderRadius: '50%', background: '#faad14', marginRight: 4 }}></span>constants.sourceAlert</span>
|
|
<span style={{ display: 'flex', alignItems: 'center' }}><span style={{ width: 6, height: 6, borderRadius: '50%', background: '#888', marginRight: 4 }}></span>devices.offline</span>
|
|
<span style={{ display: 'flex', alignItems: 'center' }}><span style={{ color: '#1890ff', marginLeft: 4 }}>•• 通讯中</span></span>
|
|
</div>
|
|
}
|
|
bordered={false}
|
|
style={{ ...cardStyle, borderRadius: 8 }}
|
|
bodyStyle={{ padding: 0, position: 'relative', height: 400, overflow: 'hidden' }}
|
|
>
|
|
<div style={{ height: "100%", background: '#111827', borderRadius: 8, width: '100%', overflow: 'hidden', position: "relative" }}>
|
|
{/*<iframe
|
|
src="https://pvs.satabot.com/?platform=web"
|
|
style={{
|
|
width: '100%',
|
|
height: '100%',
|
|
border: 'none',
|
|
borderRadius: '8px',
|
|
position: "absolute",
|
|
}}
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
allowFullScreen
|
|
title="外部系统"
|
|
/>*/}
|
|
|
|
|
|
<CesiumMap from="other" />
|
|
|
|
</div>
|
|
</Card>
|
|
|
|
{/* 设备实时监控列表 */}
|
|
<Card
|
|
title={<span style={{ fontWeight: 800, fontSize: 13 }}>设备实时监控列表</span>}
|
|
bordered={false}
|
|
style={{ ...cardStyle, borderRadius: 8 }}
|
|
extra={
|
|
<Space size={8}>
|
|
<span style={{ fontSize: 11, color: '#999' }}>全部 56</span>
|
|
<span style={{ fontSize: 11, color: '#52c41a' }}>正常 52</span>
|
|
<span style={{ fontSize: 11, color: '#faad14' }}>告警 1</span>
|
|
<span style={{ fontSize: 11, color: '#999' }}>离线 3</span>
|
|
<Select defaultValue="all" size="small" style={{ width: 90 }}>
|
|
<Option value="all">全部类型</Option>
|
|
</Select>
|
|
<Input size="small" placeholder="搜索设备名称" prefix={<SearchOutlined />} style={{ width: 120 }} />
|
|
</Space>
|
|
}
|
|
>
|
|
<Table
|
|
rowKey="id"
|
|
columns={columns}
|
|
dataSource={deviceList}
|
|
pagination={false}
|
|
size="small"
|
|
/>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 12, alignItems: 'center' }}>
|
|
<span style={{ color: '#999', fontSize: 11 }}>共 56 条</span>
|
|
<Pagination size="small" total={56} current={1} pageSize={10} showSizeChanger />
|
|
</div>
|
|
</Card>
|
|
</Space>
|
|
</Col>
|
|
|
|
{/* 右侧小栏 */}
|
|
<Col xs={24} xl={8}>
|
|
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
|
{/* 6个统计卡片 */}
|
|
<Row gutter={[4, 4]}>
|
|
{[
|
|
{ title: '当前总功率', value: '812.6', unit: 'kW', sub: '额定功率 1,000 kW', color: '#165DFF', icon: <ThunderboltOutlined /> },
|
|
{ title: '并网状态', value: '并网正常', unit: '', sub: '频率 50.02 Hz', color: '#00B42A', icon: <CheckCircleOutlined /> },
|
|
{ title: '电网电压', value: '380.2', unit: 'V', sub: 'A相 380.1 V', color: '#165DFF', icon: <BarChartOutlined /> },
|
|
{ title: '今日累计发电量', value: '4,752', unit: t('home.kWh'), sub: '昨日 5,000 kWh', color: '#00B42A', icon: <RiseOutlined /> },
|
|
{ title: '当前辐照度', value: '815', unit: 'W/m²', sub: '较昨日 +5.6%', color: '#faad14', icon: <SunOutlined /> },
|
|
{ title: '环境温度', value: '28.0', unit: '℃', sub: '较昨日 +1.2 ℃', color: '#165DFF', icon: <CloudOutlined /> },
|
|
].map((item, idx) => (
|
|
<Col span={8} key={idx}>
|
|
<Card bordered={false} style={{ ...cardStyle, height: '100%', borderRadius: 8 }} bodyStyle={{ padding: '8px' }}>
|
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 6 }}>
|
|
<div style={{ color: item.color, fontSize: 18 }}>{item.icon}</div>
|
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
<div style={{ fontSize: 'clamp(10px, 0.8vw, 11px)', color: '#666', marginBottom: 4, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{item.title}</div>
|
|
<div style={{ display: 'flex', alignItems: 'baseline', flexWrap: 'wrap' }}>
|
|
<span style={{ fontSize: 'clamp(14px, 1.2vw, 18px)', fontWeight: 700 }}>{item.value}</span>
|
|
<span style={{ fontSize: 10, marginLeft: 2 }}>{item.unit}</span>
|
|
</div>
|
|
<div style={{ fontSize: 9, color: '#999', marginTop: 4 }}>{item.sub}</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</Col>
|
|
))}
|
|
</Row>
|
|
|
|
{/* 实时功率曲线 */}
|
|
<Card title={<span style={{ fontWeight: 800 }}>实时功率曲线</span>} extra={<Select defaultValue="today" size="small"><Option value="today">今天</Option></Select>} bordered={false} style={cardStyle} bodyStyle={{ padding: '12px' }}>
|
|
<UniversalLineChart
|
|
height={180}
|
|
data={powerTrendData}
|
|
xKey="time"
|
|
smooth
|
|
dot
|
|
showLegend={false}
|
|
backgroundColor="transparent"
|
|
series={[
|
|
{
|
|
key: 'value',
|
|
name: '实时功率',
|
|
color: '#1677ff',
|
|
strokeWidth: 3,
|
|
},
|
|
]}
|
|
/>
|
|
</Card>
|
|
|
|
{/* 逆变器功率对比 */}
|
|
<Card title={<span style={{ fontWeight: 800 }}>逆变器功率对比 (实时)</span>} bordered={false} style={cardStyle} bodyStyle={{ padding: '12px' }}>
|
|
<UniversalBarChart
|
|
height={180}
|
|
data={inverterPowerData}
|
|
xKey="name"
|
|
barSize={22}
|
|
showLegend={false}
|
|
backgroundColor="transparent"
|
|
series={[
|
|
{
|
|
key: 'power',
|
|
name: '实时功率',
|
|
color: '#1677ff',
|
|
},
|
|
]}
|
|
/>
|
|
</Card>
|
|
|
|
{/* 热力图与在线率 */}
|
|
<Row gutter={8}>
|
|
<Col span={12}>
|
|
<Card title={<span style={{ fontWeight: 800 }}>组串电流热力图 (单位: A)</span>} extra={<Select defaultValue="a" size="small" style={{ width: 60 }}><Option value="a">电流</Option></Select>} bordered={false} style={cardStyle} bodyStyle={{ padding: '12px' }}>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
{['INV-01', 'INV-02', 'INV-03', 'INV-04', 'INV-05'].map((name, i) => (
|
|
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
<span style={{ fontSize: 10, width: 40 }}>{name}</span>
|
|
<div style={{ display: 'flex', gap: 2, flex: 1 }}>
|
|
{Array.from({ length: 12 }).map((_, j) => (
|
|
<div key={j} style={{ flex: 1, height: 16, background: heatmapColors[Math.floor(Math.random() * 5)], borderRadius: 2 }} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 9, color: '#999', marginTop: 4 }}>
|
|
<span>1</span><span>5</span><span>10</span><span>15</span><span>20</span><span>25</span><span>30</span><span>32</span>
|
|
</div>
|
|
</div>
|
|
<div style={{ maxHeight: 180, overflow: 'auto' }} className="custom-scrollbar">
|
|
{[
|
|
{ name: '逆变器 INV-01', status: t('roles.normal'), color: '#52c41a' },
|
|
{ name: '逆变器 INV-02', status: t('roles.normal'), color: '#52c41a' },
|
|
{ name: '逆变器 INV-03', status: t('roles.normal'), color: '#52c41a' },
|
|
{ name: '逆变器 INV-04', status: t('roles.normal'), color: '#52c41a' },
|
|
{ name: '逆变器 INV-05', status: t('constants.sourceAlert'), color: '#faad14' },
|
|
{ name: '气象站', status: t('roles.normal'), color: '#52c41a' },
|
|
{ name: '无人机机场', status: t('roles.normal'), color: '#52c41a' },
|
|
{ name: t('home.inspectionRobot'), status: t('roles.normal'), color: '#52c41a' },
|
|
].map((item, idx) => (
|
|
<div key={idx} style={{ display: 'flex', justifyContent: 'space-between', padding: '6px 4px', borderBottom: '1px solid #f5f5f5' }}>
|
|
<Space size={4}>
|
|
{item.name.includes(t('home.inverter')) ? <ThunderboltOutlined style={{ fontSize: 10 }} /> : <ExperimentOutlined style={{ fontSize: 10 }} />}
|
|
<span style={{ fontSize: 11 }}>{item.name}</span>
|
|
</Space>
|
|
<span style={{ fontSize: 11, color: item.color }}>{item.status}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Card title={<span style={{ fontWeight: 800 }}>设备在线率</span>} bordered={false} style={{ ...cardStyle, height: "100%" }} bodyStyle={{ padding: '12px' }}>
|
|
<div style={{ position: 'relative', height: 160 }}>
|
|
<UniversalPieChart
|
|
height={160}
|
|
backgroundColor="transparent"
|
|
showLegend={false}
|
|
showLabel={false}
|
|
innerRadius={45}
|
|
outerRadius={65}
|
|
data={[
|
|
{
|
|
name: t('devices.online'),
|
|
value: 52,
|
|
color: '#52c41a',
|
|
},
|
|
{
|
|
name: t('devices.offline'),
|
|
value: 3,
|
|
color: '#d9d9d9',
|
|
},
|
|
{
|
|
name: t('constants.sourceAlert'),
|
|
value: 1,
|
|
color: '#faad14',
|
|
},
|
|
]}
|
|
/>
|
|
<div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', textAlign: 'center' }}>
|
|
<div style={{ fontSize: 16, fontWeight: 800 }}>92.1%</div>
|
|
<div style={{ fontSize: 10, color: '#999' }}>在线率</div>
|
|
</div>
|
|
</div>
|
|
<div style={{ fontSize: 10, display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 8 }}>
|
|
<span style={{ display: 'flex', alignItems: 'center' }}><span style={{ width: 6, height: 6, borderRadius: '50%', background: '#52c41a', marginRight: 4 }}></span>在线 52 (92.1%)</span>
|
|
<span style={{ display: 'flex', alignItems: 'center' }}><span style={{ width: 6, height: 6, borderRadius: '50%', background: '#f5f5f5', marginRight: 4 }}></span>离线 3 (5.3%)</span>
|
|
<span style={{ display: 'flex', alignItems: 'center' }}><span style={{ width: 6, height: 6, borderRadius: '50%', background: '#faad14', marginRight: 4 }}></span>告警 1 (1.8%)</span>
|
|
<span style={{ display: 'flex', alignItems: 'center' }}><span style={{ width: 6, height: 6, borderRadius: '50%', background: '#999', marginRight: 4 }}></span>总数 56</span>
|
|
</div>
|
|
<div style={{ width: '100%', height: '100%', background: `url(${bgMain}) center/cover no-repeat`, position: 'relative' }}>
|
|
{/* 简易地图点位 */}
|
|
{/*<div style={{ position: 'absolute', top: '20%', left: '30%', width: 8, height: 8, background: '#52c41a', borderRadius: '50%', border: '1px solid #fff' }} />
|
|
<div style={{ position: 'absolute', top: '40%', left: '60%', width: 8, height: 8, background: '#faad14', borderRadius: '50%', border: '1px solid #fff' }} />
|
|
<div style={{ position: 'absolute', bottom: '20%', left: '40%', width: 8, height: 8, background: '#52c41a', borderRadius: '50%', border: '1px solid #fff' }} />*/}
|
|
</div>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
|
|
|
|
</Space>
|
|
</Col>
|
|
</Row>
|
|
</div >
|
|
);
|
|
};
|
|
|
|
export default RealTimeMonitorPage;
|