feat(状态管理): 新增设备状态管理页面及功能
- 添加状态管理页面路由配置和导航菜单项 - 实现状态管理页面基础布局和功能组件 - 包含设备状态图表展示和列表管理功能 - 删除无用的样式文件untitled-1.txtmain
parent
e4eeefc275
commit
d1ca918075
@ -1,17 +0,0 @@
|
||||
.item{
|
||||
padding: 20px;
|
||||
border: 1px solid;
|
||||
border-image-slice: 1;
|
||||
border-image-source: conic-gradient(from 102.21deg at 52.75% 38.75%, rgba(249, 249, 249, 0.5) -32.95deg, rgba(64, 64, 64, 0.5) 10.52deg, rgba(64, 64, 64, 0.35) 32.12deg, #FFFFFF 60.28deg, rgba(255, 255, 255, 0.5) 107.79deg, rgba(64, 64, 64, 0.35) 187.59deg, #F9F9F9 207.58deg, #FFFFFF 287.31deg, rgba(249, 249, 249, 0.5) 327.05deg, rgba(64, 64, 64, 0.5) 370.52deg);
|
||||
background: #FFFFFF4D;
|
||||
backdrop-filter: blur(15px);
|
||||
border-radius: 8px;
|
||||
|
||||
// 合并多个阴影效果,用逗号分隔
|
||||
box-shadow:
|
||||
-2px 4px 10px 0px #9191910D,
|
||||
-7px 17px 18px 0px #9191910A,
|
||||
-15px 37px 24px 0px #91919108,
|
||||
-27px 66px 29px 0px #91919103,
|
||||
-42px 103px 31px 0px #91919100;
|
||||
}
|
||||
@ -0,0 +1,533 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Row, Col, Card, Button, Input, Select, DatePicker, Space, Tag, Progress } from 'antd';
|
||||
import { SearchOutlined, InfoCircleOutlined, BellOutlined, DownloadOutlined, ReloadOutlined,PlusOutlined } from '@ant-design/icons';
|
||||
import * as echarts from 'echarts';
|
||||
import TableWithPagination from '@/components/assetmangement/table';
|
||||
import Title from '../homepage/compontent/title';
|
||||
import styles from './StateManagement.less';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const StateManagement = () => {
|
||||
const pieChartRef = useRef(null);
|
||||
const lineChartRef = useRef(null);
|
||||
const barChartRef = useRef(null);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [selectedStatus, setSelectedStatus] = useState('all');
|
||||
const [selectedDate, setSelectedDate] = useState(null);
|
||||
|
||||
// 设备状态数据
|
||||
const deviceStatusData = [
|
||||
{ key: '1', deviceId: 'DEV-001', deviceName: '温度传感器A', deviceType: '传感器', status: '在线', ip: '192.168.1.10', protocol: 'MQTT', location: '车间A', uptime: '在线8小时' },
|
||||
{ key: '2', deviceId: 'DEV-002', deviceName: '工业网关B', deviceType: '网关', status: '离线', ip: '192.168.1.11', protocol: 'CoAP', location: '机房B', uptime: '离线2小时' },
|
||||
{ key: '3', deviceId: 'DEV-003', deviceName: '高清摄像头C', deviceType: '摄像头', status: '故障', ip: '192.168.1.12', protocol: 'HTTP', location: '园区C', uptime: '离线1天(故障)' },
|
||||
{ key: '4', deviceId: 'DEV-004', deviceName: '压力传感器D', deviceType: '传感器', status: '在线', ip: '192.168.1.13', protocol: 'Modbus', location: '产线D', uptime: '在线12小时' },
|
||||
{ key: '5', deviceId: 'DEV-005', deviceName: '边缘计算节点E', deviceType: '网关', status: '在线', ip: '192.168.1.14', protocol: 'MQTT', location: '机房A', uptime: '在线24小时' },
|
||||
{ key: '6', deviceId: 'DEV-006', deviceName: '电磁阀F', deviceType: '执行器', status: '离线', ip: '192.168.1.15', protocol: 'Modbus', location: '车间B', uptime: '离线5小时' },
|
||||
];
|
||||
|
||||
// 设备状态占比数据
|
||||
const statusDistributionData = [
|
||||
{ value: 60, name: '在线', itemStyle: { color: '#006665' } },
|
||||
{ value: 25, name: '离线', itemStyle: { color: '#999999' } },
|
||||
{ value: 15, name: '故障', itemStyle: { color: '#FF826D' } },
|
||||
];
|
||||
|
||||
// 在线率趋势数据
|
||||
const onlineRateData = {
|
||||
dates: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
|
||||
rates: [88, 85, 90, 87, 82, 89]
|
||||
};
|
||||
|
||||
// 设备类型分布数据
|
||||
const deviceTypeData = {
|
||||
categories: ['传感器A', '网关A', '摄像头', '传感器B', '网关B', '执行器'],
|
||||
values: [40, 88, 60, 100, 68, 58]
|
||||
};
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{ title: '', dataIndex: 'checkbox', key: 'checkbox', width: 40 },
|
||||
{ title: '设备ID', dataIndex: 'deviceId', key: 'deviceId' },
|
||||
{ title: '设备名称', dataIndex: 'deviceName', key: 'deviceName' },
|
||||
{ title: '设备类型', dataIndex: 'deviceType', key: 'deviceType' },
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (status) => {
|
||||
const colorMap = {
|
||||
'在线': 'green',
|
||||
'离线': 'gray',
|
||||
'故障': 'red'
|
||||
};
|
||||
const statusTextMap = {
|
||||
'在线': '在线(绿色)',
|
||||
'离线': '离线(灰色)',
|
||||
'故障': '故障(红色)'
|
||||
};
|
||||
return <Tag color={colorMap[status]}>{statusTextMap[status]}</Tag>;
|
||||
}
|
||||
},
|
||||
{ title: 'IP地址', dataIndex: 'ip', key: 'ip' },
|
||||
{ title: '协议类型', dataIndex: 'protocol', key: 'protocol' },
|
||||
{ title: '位置信息', dataIndex: 'location', key: 'location' },
|
||||
{ title: '在线时长/离线时间', dataIndex: 'uptime', key: 'uptime' },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<a style={{ color: '#2C9E9D' }}>查看详情</a>
|
||||
<a style={{ color: '#2C9E9D' }}>编辑</a>
|
||||
<a style={{ color: '#ff4d4f' }}>删除</a>
|
||||
{record.status === '在线' && <a style={{ color: '#2C9E9D' }}>巡检</a>}
|
||||
{record.status === '离线' && <a style={{ color: '#2C9E9D' }}>工单</a>}
|
||||
{record.status === '故障' && <a style={{ color: '#2C9E9D' }}>升级</a>}
|
||||
{record.status === '离线' && record.deviceType === '执行器' && <a style={{ color: '#2C9E9D' }}>报修</a>}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
// 初始化图表
|
||||
useEffect(() => {
|
||||
// 设备状态占比饼图
|
||||
if (pieChartRef.current) {
|
||||
const pieChart = echarts.init(pieChartRef.current);
|
||||
const pieOption = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 10,
|
||||
top: 'center',
|
||||
itemWidth: 14,
|
||||
itemHeight: 14,
|
||||
textStyle: {
|
||||
fontSize: 12
|
||||
},
|
||||
itemGap: 30,
|
||||
formatter: function(name) {
|
||||
// 找到对应的数值
|
||||
const item = statusDistributionData.find(item => item.name === name);
|
||||
return `${name} ${item.value}%`;
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: '设备状态',
|
||||
type: 'pie',
|
||||
radius: [0, '70%'],
|
||||
center: ['60%', '50%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 0,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
data: statusDistributionData
|
||||
}]
|
||||
};
|
||||
pieChart.setOption(pieOption);
|
||||
}
|
||||
|
||||
// 在线率趋势折线图
|
||||
if (lineChartRef.current) {
|
||||
const lineChart = echarts.init(lineChartRef.current);
|
||||
const lineOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '{b}<br/>{a}: {c}%'
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top:'5%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: onlineRateData.dates,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#d9d9d9',
|
||||
type: 'dashed'
|
||||
},
|
||||
show:true
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666',
|
||||
show:true
|
||||
},
|
||||
axisTick: {
|
||||
alignWithLabel: true // 使x轴刻度与标签对齐
|
||||
},
|
||||
boundaryGap: false, // 设置坐标轴两端是否留白
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#CCCCCC',
|
||||
type: 'deshed'
|
||||
},
|
||||
show:true
|
||||
}
|
||||
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 100,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#d9d9d9'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: '{value}%',
|
||||
color: '#666'
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f0f0f0',
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: '在线率',
|
||||
type: 'line',
|
||||
data: onlineRateData.rates,
|
||||
smooth: false,
|
||||
lineStyle: {
|
||||
color: '#2C9E9D',
|
||||
width: 3,
|
||||
// type: 'dashed'
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#2C9E9D'
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0, color: '#E8F3EFcc'
|
||||
}, {
|
||||
offset: 1, color: '#EAF6F2cc'
|
||||
}]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0, color: '#E8F3EFcc'
|
||||
}, {
|
||||
offset: 1, color: '#EAF6F2cc'
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
lineChart.setOption(lineOption);
|
||||
}
|
||||
|
||||
// 设备类型分布柱状图
|
||||
if (barChartRef.current) {
|
||||
const barChart = echarts.init(barChartRef.current);
|
||||
const barOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top:'10%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: deviceTypeData.categories,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#d9d9d9'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666',
|
||||
interval: 0
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#CCCCCC',
|
||||
type: 'deshed'
|
||||
},
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#d9d9d9'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666'
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f0f0f0',
|
||||
type: 'dashed' // 类目之间的虚线
|
||||
}
|
||||
},
|
||||
max:()=>{
|
||||
return Math.max(...deviceTypeData.values) * 1.2;
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: '设备数量',
|
||||
type: 'bar',
|
||||
data: deviceTypeData.values,
|
||||
itemStyle: {
|
||||
color: function(params) {
|
||||
// 根据数据值设置不同的颜色
|
||||
const colors = ['#86bbd8', '#8884d8', '#006665', '#2e8bc0', '#a0c4e2', '#e8d4b0'];
|
||||
return colors[params.dataIndex % colors.length];
|
||||
}
|
||||
},
|
||||
barWidth: '30%',
|
||||
label: {
|
||||
normal: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
textStyle: {
|
||||
color: '#666'
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
barChart.setOption(barOption);
|
||||
}
|
||||
|
||||
// 响应式处理
|
||||
const handleResize = () => {
|
||||
if (pieChartRef.current) {
|
||||
const pieChart = echarts.getInstanceByDom(pieChartRef.current);
|
||||
pieChart && pieChart.resize();
|
||||
}
|
||||
if (lineChartRef.current) {
|
||||
const lineChart = echarts.getInstanceByDom(lineChartRef.current);
|
||||
lineChart && lineChart.resize();
|
||||
}
|
||||
if (barChartRef.current) {
|
||||
const barChart = echarts.getInstanceByDom(barChartRef.current);
|
||||
barChart && barChart.resize();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 筛选处理函数
|
||||
const handleSearch = () => {
|
||||
console.log('搜索条件:', { searchValue, selectedStatus, selectedDate });
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSearchValue('');
|
||||
setSelectedStatus('all');
|
||||
setSelectedDate(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px', backgroundColor: '#fff', minHeight: '100vh' }}>
|
||||
{/* 筛选条件区域 */}
|
||||
<Card
|
||||
style={{
|
||||
border:'none'
|
||||
}}
|
||||
bodyStyle={{ padding: '16px 24px' }}
|
||||
title={
|
||||
<>
|
||||
<Row justify="space-between">
|
||||
<Title title="筛选条件" />
|
||||
<Col span={8} style={{ textAlign: 'right' }}>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
onClick={handleSearch}
|
||||
style={{ borderRadius: '6px' }}
|
||||
className={styles['search-button']}
|
||||
>
|
||||
查询
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleReset}
|
||||
style={{ borderRadius: '6px' }}
|
||||
className={styles['reset-button']}
|
||||
icon={<ReloadOutlined /> }
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16} align="middle" style={{margin:'20px 0'}} justify='space-between'>
|
||||
<Col span={6}>
|
||||
<Input
|
||||
placeholder="请输入设备名称或编号"
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
style={{ borderRadius: '6px' }}
|
||||
suffix={<SearchOutlined style={{ color: '#bfbfbf' }} />}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<label>设备状态:</label>
|
||||
<Select
|
||||
value={selectedStatus}
|
||||
onChange={setSelectedStatus}
|
||||
style={{ width: '152px', borderRadius: '6px' ,marginLeft:'10px'}}
|
||||
>
|
||||
<Option value="all">全部状态</Option>
|
||||
<Option value="online">在线</Option>
|
||||
<Option value="offline">离线</Option>
|
||||
<Option value="abnormal">异常</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<label>位置信息:</label>
|
||||
<Select
|
||||
value={selectedStatus}
|
||||
// onChange={setSelectedStatus}
|
||||
style={{ width: '152px', borderRadius: '6px' ,marginLeft:'10px'}}
|
||||
>
|
||||
<Option value="all">全部</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<label>设备状态:</label>
|
||||
<Select
|
||||
value={selectedStatus}
|
||||
// onChange={setSelectedStatus}
|
||||
style={{ width: '152px', borderRadius: '6px' ,marginLeft:'10px'}}
|
||||
>
|
||||
<Option value="all">全部</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<label>协议类型:</label>
|
||||
<Select
|
||||
value={selectedStatus}
|
||||
// onChange={setSelectedStatus}
|
||||
style={{ width: '152px', borderRadius: '6px' ,marginLeft:'10px'}}
|
||||
>
|
||||
<Option value="all">全部</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
</Card>
|
||||
|
||||
{/* 数据可视化区域 */}
|
||||
<Row gutter={20} style={{ marginBottom: '20px' }}>
|
||||
<Col span={6}>
|
||||
<Card
|
||||
title= {<Title title="设备状态占比饼图"/>}
|
||||
style={{ borderRadius: '8px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}
|
||||
bodyStyle={{ padding: '16px', height: '200px' }}
|
||||
>
|
||||
<div ref={pieChartRef} style={{ width: '100%', height: '100%' }} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={9}>
|
||||
<Card
|
||||
title={<Title title="设备在线率趋势折线图"/>}
|
||||
style={{ borderRadius: '8px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}
|
||||
bodyStyle={{ padding: '16px', height: '200px' }}
|
||||
>
|
||||
<div ref={lineChartRef} style={{ width: '100%', height: '100%' }} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={9}>
|
||||
<Card
|
||||
title={<Title title="设备类型分布柱状图"/>}
|
||||
style={{ borderRadius: '8px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}
|
||||
bodyStyle={{ padding: '16px', height: '200px' }}
|
||||
>
|
||||
<div ref={barChartRef} style={{ width: '100%', height: '100%' }} />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 设备列表表格 */}
|
||||
<Card
|
||||
style={{
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
|
||||
}}
|
||||
bodyStyle={{ padding: '0' }}
|
||||
>
|
||||
<div style={{ padding: '20px 24px', borderBottom: '1px solid #f0f0f0' }}>
|
||||
<Row justify="space-between" align="middle">
|
||||
<Title title="设备状态列表"/>
|
||||
</Row>
|
||||
<Row justify="space-between" align="middle" style={{ margin: '20px 0' }}>
|
||||
<Col span={12}>
|
||||
<Button style={{marginRight:30}} className={styles['search-button']}>导入数据</Button>
|
||||
<Button style={{marginRight:30}} className={styles['reset-button']}>导出数据</Button>
|
||||
<Button className={styles['del-button']}>删除</Button>
|
||||
</Col>
|
||||
<Col span={12} style={{ textAlign: 'right' }}>
|
||||
<Button className={styles['search-button']} icon={<PlusOutlined />}>新增设备</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<TableWithPagination
|
||||
columns={columns}
|
||||
dataSource={deviceStatusData}
|
||||
rowSelection={true}
|
||||
style={{ padding: '0 24px' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StateManagement;
|
||||
@ -0,0 +1,34 @@
|
||||
.search-button{
|
||||
background-image: url('../../assets/img/assetmangement1.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position:center;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
height: 36px;
|
||||
border-color:#d9d9d9 ;
|
||||
background-color: #045F5E80;
|
||||
}
|
||||
.reset-button{
|
||||
background-image: url('../../assets/img/assetmangement2.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position:center;
|
||||
color: rgba(0, 102, 101, 1);
|
||||
border-radius: 4px;
|
||||
height: 36px;
|
||||
border-color:#d9d9d9 ;
|
||||
background-color: #B7E5D533;
|
||||
}
|
||||
.del-button{
|
||||
background-image: url('../../assets/img/assetmangement3.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position:center;
|
||||
color: #000;
|
||||
border-radius: 4px;
|
||||
height: 36px;
|
||||
width:88px;
|
||||
border-color:#d9d9d9 ;
|
||||
background-color: #E5B7B733;
|
||||
}
|
||||
Loading…
Reference in New Issue