feat():安全监测模块开发
parent
048c9e2faf
commit
db011df5a6
|
|
@ -123,6 +123,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-table-cell-fix-left, .ant-table-cell-fix-right{
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
.ant-table-thead > tr > th, .ant-table-tbody > tr > td, .ant-table tfoot > tr > th, .ant-table tfoot > tr > td{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
// Table Scrollbar Fix (Remove white strip)
|
// Table Scrollbar Fix (Remove white strip)
|
||||||
.ant-table-body, .ant-table-content {
|
.ant-table-body, .ant-table-content {
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,304 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Row, Col, Table, Radio } from 'antd';
|
||||||
|
import ReactECharts from 'echarts-for-react';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
// import './ProcessLineChart.less'; // Reuse styles or create new ones
|
||||||
|
|
||||||
|
// Mock Data
|
||||||
|
const mockData = {
|
||||||
|
dates: ['2026-01-26', '2026-01-27', '2026-01-28', '2026-01-29', '2026-01-30', '2026-01-31', '2026-02-01', '2026-02-02', '2026-02-03', '2026-02-04', '2026-02-05', '2026-02-06', '2026-02-07'],
|
||||||
|
reservoirLevel: [704.5, 704.4, 704.6, 704.5, 704.4, 704.5, 704.6, 704.5, 704.39, 704.4, 704.5, 704.6, 704.5],
|
||||||
|
points: {
|
||||||
|
'01': {
|
||||||
|
x: [-89.88, -89.48, -88.77, -89.0, -88.19, -88.5, -88.44, -89.1, -88.75, -89.2, -88.9, -89.0, -88.8],
|
||||||
|
y: [-26.96, -27.22, -28.33, -28.17, -28.48, -29.06, -28.73, -27.9, -28.5, -28.2, -28.1, -28.0, -28.4],
|
||||||
|
z: [10.5, 10.6, 10.4, 10.5, 10.7, 10.6, 10.5, 10.6, 10.5, 10.4, 10.5, 10.6, 10.5]
|
||||||
|
},
|
||||||
|
'02': {
|
||||||
|
x: [-114.67, -114.43, -113.94, -113.45, -113.72, -113.51, -113.9, -114.2, -114.29, -113.8, -114.0, -114.1, -114.3],
|
||||||
|
y: [-24.63, -25.46, -24.91, -25.42, -24.57, -25.23, -25.19, -24.8, -25.0, -25.3, -25.1, -25.2, -25.4],
|
||||||
|
z: [15.2, 15.3, 15.1, 15.2, 15.4, 15.3, 15.2, 15.3, 15.2, 15.1, 15.2, 15.3, 15.2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getChartOption = (data, mainType, subType) => {
|
||||||
|
const pointNames = Object.keys(data.points);
|
||||||
|
const colors = ['#5470C6', '#91CC75', '#EE6666'];
|
||||||
|
|
||||||
|
// Determine Y-axis name and data key based on types
|
||||||
|
let yAxisName = '';
|
||||||
|
let dataKey = '';
|
||||||
|
|
||||||
|
if (mainType === 'horizontal') {
|
||||||
|
if (subType === 'upDown') {
|
||||||
|
yAxisName = '上下游水平位移(mm)';
|
||||||
|
dataKey = 'x';
|
||||||
|
} else {
|
||||||
|
yAxisName = '左右岸水平位移(mm)';
|
||||||
|
dataKey = 'y';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yAxisName = '垂直位移(mm)';
|
||||||
|
dataKey = 'z';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: { type: 'cross', crossStyle: { color: '#999' } }
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['库水位(m)', ...pointNames],
|
||||||
|
textStyle: { color: '#fff' }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '10%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
data: data.dates,
|
||||||
|
axisPointer: { type: 'shadow' },
|
||||||
|
axisLine: { lineStyle: { color: '#86909C' } },
|
||||||
|
axisLabel: { color: '#fff' }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: yAxisName,
|
||||||
|
axisLabel: { formatter: '{value}', color: '#fff' },
|
||||||
|
nameTextStyle: { color: '#fff' },
|
||||||
|
splitLine: { lineStyle: { color: '#4E5969', type: 'dashed' } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '库水位(m)',
|
||||||
|
min: 690,
|
||||||
|
max: 715,
|
||||||
|
interval: 5,
|
||||||
|
axisLabel: { formatter: '{value}', color: '#fff' },
|
||||||
|
nameTextStyle: { color: '#fff' },
|
||||||
|
splitLine: { show: false }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '库水位(m)',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: data.reservoirLevel,
|
||||||
|
lineStyle: { color: '#EE6666', width: 2 },
|
||||||
|
itemStyle: { color: '#EE6666' },
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
...pointNames.map((name, index) => ({
|
||||||
|
name: name,
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
data: data.points[name][dataKey],
|
||||||
|
lineStyle: { color: colors[index] },
|
||||||
|
itemStyle: { color: colors[index] }
|
||||||
|
}))
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{ type: 'inside', start: 0, end: 100 },
|
||||||
|
{
|
||||||
|
start: 0, end: 100,
|
||||||
|
handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
|
||||||
|
handleSize: '80%',
|
||||||
|
handleStyle: { color: '#fff', shadowBlur: 3, shadowColor: 'rgba(0, 0, 0, 0.6)', shadowOffsetX: 2, shadowOffsetY: 2 },
|
||||||
|
textStyle: { color: '#fff' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeformationPanel = () => {
|
||||||
|
const [chartData] = useState(mockData);
|
||||||
|
const [mainType, setMainType] = useState('horizontal'); // 'horizontal' | 'vertical'
|
||||||
|
const [subType, setSubType] = useState('upDown'); // 'upDown' | 'leftRight'
|
||||||
|
|
||||||
|
// Generate Table Columns
|
||||||
|
const getTableColumns = () => {
|
||||||
|
const pointNames = Object.keys(chartData.points);
|
||||||
|
|
||||||
|
// Fixed columns
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '监测日期',
|
||||||
|
dataIndex: 'date',
|
||||||
|
key: 'date',
|
||||||
|
width: 120,
|
||||||
|
fixed: 'left',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Dynamic columns for each point
|
||||||
|
if (mainType === 'horizontal') {
|
||||||
|
// For horizontal, show x and y
|
||||||
|
columns.push({
|
||||||
|
title: '水平位移(mm)',
|
||||||
|
children: pointNames.map(name => ({
|
||||||
|
title: name,
|
||||||
|
children: [
|
||||||
|
{ title: 'x', dataIndex: `${name}_x`, key: `${name}_x`, width: 80 },
|
||||||
|
{ title: 'y', dataIndex: `${name}_y`, key: `${name}_y`, width: 80 }
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// For vertical, show z
|
||||||
|
columns.push({
|
||||||
|
title: '垂直位移(mm)',
|
||||||
|
children: pointNames.map(name => ({
|
||||||
|
title: name,
|
||||||
|
dataIndex: `${name}_z`,
|
||||||
|
key: `${name}_z`,
|
||||||
|
width: 100
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate Table Data
|
||||||
|
const getTableData = () => {
|
||||||
|
const dataSource = chartData.dates.map((date, index) => {
|
||||||
|
const row = { key: index, date: date };
|
||||||
|
|
||||||
|
Object.keys(chartData.points).forEach(name => {
|
||||||
|
if (mainType === 'horizontal') {
|
||||||
|
row[`${name}_x`] = chartData.points[name].x[index];
|
||||||
|
row[`${name}_y`] = chartData.points[name].y[index];
|
||||||
|
} else {
|
||||||
|
row[`${name}_z`] = chartData.points[name].z[index];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Summary (Max, Min, Range)
|
||||||
|
const summary = {
|
||||||
|
max: { date: '最大值', key: 'max' },
|
||||||
|
min: { date: '最小值', key: 'min' },
|
||||||
|
range: { date: '变幅', key: 'range' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const pointNames = Object.keys(chartData.points);
|
||||||
|
const fields = [];
|
||||||
|
pointNames.forEach(name => {
|
||||||
|
if (mainType === 'horizontal') {
|
||||||
|
fields.push(`${name}_x`, `${name}_y`);
|
||||||
|
} else {
|
||||||
|
fields.push(`${name}_z`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fields.forEach(field => {
|
||||||
|
const [name, key] = field.split('_');
|
||||||
|
const values = chartData.points[name][key];
|
||||||
|
const validValues = values.filter(v => v !== null && v !== undefined);
|
||||||
|
|
||||||
|
if (validValues.length > 0) {
|
||||||
|
const max = Math.max(...validValues);
|
||||||
|
const min = Math.min(...validValues);
|
||||||
|
summary.max[field] = max.toFixed(2);
|
||||||
|
summary.min[field] = min.toFixed(2);
|
||||||
|
summary.range[field] = (max - min).toFixed(2);
|
||||||
|
} else {
|
||||||
|
summary.max[field] = '-';
|
||||||
|
summary.min[field] = '-';
|
||||||
|
summary.range[field] = '-';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...dataSource, summary.max, summary.min, summary.range];
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row gutter={16} style={{ height: '100%' }}>
|
||||||
|
{/* Left Chart */}
|
||||||
|
<Col span={14} style={{ height: '100%' }}>
|
||||||
|
<ReactECharts
|
||||||
|
option={getChartOption(chartData, mainType, subType)}
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
{/* Right Table */}
|
||||||
|
<Col span={10} style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<div style={{ marginBottom: 10, display: 'flex', justifyContent: 'flex-end', gap: 10 }}>
|
||||||
|
{mainType === 'horizontal' && (
|
||||||
|
<Radio.Group
|
||||||
|
value={subType}
|
||||||
|
onChange={e => setSubType(e.target.value)}
|
||||||
|
buttonStyle="solid"
|
||||||
|
size="small"
|
||||||
|
style={{ marginRight: 12 }}
|
||||||
|
>
|
||||||
|
<Radio.Button value="upDown" style={{
|
||||||
|
borderRadius: '4px 0 0 4px',
|
||||||
|
marginLeft: '-1px',
|
||||||
|
background: subType === 'upDown' ? 'rgba(0, 160, 233, 0.8)' : 'rgba(18, 56, 102, 0.6)',
|
||||||
|
borderColor: '#00a0e9',
|
||||||
|
color: '#fff'
|
||||||
|
}}>上下游</Radio.Button>
|
||||||
|
<Radio.Button value="leftRight" style={{
|
||||||
|
borderRadius: '0 4px 4px 0',
|
||||||
|
marginLeft: '-1px',
|
||||||
|
background: subType === 'leftRight' ? 'rgba(0, 160, 233, 0.8)' : 'rgba(18, 56, 102, 0.6)',
|
||||||
|
borderColor: '#00a0e9',
|
||||||
|
color: '#fff'
|
||||||
|
}}>左右岸</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Radio.Group
|
||||||
|
value={mainType}
|
||||||
|
onChange={e => {
|
||||||
|
setMainType(e.target.value);
|
||||||
|
if (e.target.value === 'horizontal') {
|
||||||
|
setSubType('upDown'); // Reset subType when switching back
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
buttonStyle="solid"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<Radio.Button value="horizontal" style={{
|
||||||
|
borderRadius: '4px 0 0 4px',
|
||||||
|
background: mainType === 'horizontal' ? 'rgba(0, 160, 233, 0.8)' : 'rgba(18, 56, 102, 0.6)',
|
||||||
|
borderColor: '#00a0e9',
|
||||||
|
color: '#fff'
|
||||||
|
}}>水平位移</Radio.Button>
|
||||||
|
<Radio.Button value="vertical" style={{
|
||||||
|
borderRadius: '0 4px 4px 0',
|
||||||
|
marginLeft: '-1px',
|
||||||
|
background: mainType === 'vertical' ? 'rgba(0, 160, 233, 0.8)' : 'rgba(18, 56, 102, 0.6)',
|
||||||
|
borderColor: '#00a0e9',
|
||||||
|
color: '#fff'
|
||||||
|
}}>垂直位移</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
columns={getTableColumns()}
|
||||||
|
dataSource={getTableData()}
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ y: 'calc(100vh - 350px)' }}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeformationPanel;
|
||||||
|
|
@ -0,0 +1,308 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Row, Col, Table } from 'antd';
|
||||||
|
import ReactECharts from 'echarts-for-react';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
// import './ProcessLineChart.less';
|
||||||
|
|
||||||
|
// Mock Data based on the screenshot
|
||||||
|
const mockData = {
|
||||||
|
dates: ['2026-01-26', '2026-01-27', '2026-01-28', '2026-01-29', '2026-01-30', '2026-01-31', '2026-02-01', '2026-02-02', '2026-02-03', '2026-02-04', '2026-02-05', '2026-02-06', '2026-02-07'],
|
||||||
|
rainfall: [0, 0, 0.5, 0.5, 0, 0, 0, 0, 3, 0, 0, 0, 0],
|
||||||
|
reservoirLevel: [null, null, null, null, 1141.7, 1141.69, 1141.65, 1141.52, 1144.41, 1144.34, 1144.34, 1144.33, 1144.3],
|
||||||
|
pipes: {
|
||||||
|
'UPD1': [1142, 1141.96, 1141.8, 1141.71, 1141.7, 1141.69, 1141.65, 1141.52, 1141.39, 1141.42, 1141.64, 1141.55, 1141.73],
|
||||||
|
'UPD2': [1132.42, 1132.43, 1132.43, 1132.4, 1132.42, 1132.43, 1132.43, 1132.43, 1132.43, 1132.41, 1132.4, 1132.4, 1132.41],
|
||||||
|
'UPD3': [1125.47, 1125.47, 1125.46, 1125.41, 1125.48, 1125.51, 1125.51, 1125.52, 1125.48, 1125.42, 1125.41, 1125.46, 1125.46],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getChartOption = (data) => {
|
||||||
|
const pipeNames = Object.keys(data.pipes);
|
||||||
|
const colors = ['#5470C6', '#91CC75', '#EE6666', '#73C0DE', '#3BA272', '#FC8452', '#9A60B4', '#EA7CCC'];
|
||||||
|
|
||||||
|
return {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
crossStyle: {
|
||||||
|
color: '#999'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['雨量', '库水位', ...pipeNames],
|
||||||
|
textStyle: {
|
||||||
|
color: '#fff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '10%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
data: data.dates,
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#86909C'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '水位(m)',
|
||||||
|
min: 1115,
|
||||||
|
max: 1155,
|
||||||
|
interval: 5,
|
||||||
|
axisLabel: {
|
||||||
|
formatter: '{value}',
|
||||||
|
color: '#fff'
|
||||||
|
},
|
||||||
|
nameTextStyle: {
|
||||||
|
color: '#fff'
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#4E5969'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '雨量(mm)',
|
||||||
|
min: 0,
|
||||||
|
max: 500,
|
||||||
|
interval: 100,
|
||||||
|
axisLabel: {
|
||||||
|
formatter: '{value}',
|
||||||
|
color: '#fff'
|
||||||
|
},
|
||||||
|
nameTextStyle: {
|
||||||
|
color: '#fff'
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '雨量',
|
||||||
|
type: 'bar',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
tooltip: {
|
||||||
|
valueFormatter: function (value) {
|
||||||
|
return value + ' mm';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: data.rainfall,
|
||||||
|
itemStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: '#83bff6' },
|
||||||
|
{ offset: 0.5, color: '#188df0' },
|
||||||
|
{ offset: 1, color: '#188df0' }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '库水位',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
tooltip: {
|
||||||
|
valueFormatter: function (value) {
|
||||||
|
return value + ' m';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: data.reservoirLevel,
|
||||||
|
lineStyle: {
|
||||||
|
color: colors[2]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...pipeNames.map((name, index) => ({
|
||||||
|
name: name,
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
tooltip: {
|
||||||
|
valueFormatter: function (value) {
|
||||||
|
return value + ' m';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: data.pipes[name],
|
||||||
|
lineStyle: {
|
||||||
|
color: colors[index + 3]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: 0,
|
||||||
|
end: 100,
|
||||||
|
handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
|
||||||
|
handleSize: '80%',
|
||||||
|
handleStyle: {
|
||||||
|
color: '#fff',
|
||||||
|
shadowBlur: 3,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.6)',
|
||||||
|
shadowOffsetX: 2,
|
||||||
|
shadowOffsetY: 2
|
||||||
|
},
|
||||||
|
textStyle: {
|
||||||
|
color: '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProcessLineChart = () => {
|
||||||
|
const [chartData] = useState(mockData);
|
||||||
|
|
||||||
|
const tableData = chartData.dates.map((date, index) => {
|
||||||
|
const row = {
|
||||||
|
key: index,
|
||||||
|
date: date,
|
||||||
|
reservoirLevel: chartData.reservoirLevel[index],
|
||||||
|
rainfall: chartData.rainfall[index],
|
||||||
|
};
|
||||||
|
for (const pipeName in chartData.pipes) {
|
||||||
|
row[pipeName] = chartData.pipes[pipeName][index];
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
|
||||||
|
const pipeNames = Object.keys(chartData.pipes);
|
||||||
|
const columns = [
|
||||||
|
{ title: '监测日期', dataIndex: 'date', key: 'date', width: 120, fixed: 'left', align: 'center' },
|
||||||
|
{ title: '库水位(m)', dataIndex: 'reservoirLevel', key: 'reservoirLevel', width: 100, align: 'center' },
|
||||||
|
{ title: '雨量(mm)', dataIndex: 'rainfall', key: 'rainfall', width: 100, align: 'center' },
|
||||||
|
{
|
||||||
|
title: '渗压水位(m)',
|
||||||
|
align: 'center',
|
||||||
|
children: pipeNames.map(name => ({
|
||||||
|
title: name,
|
||||||
|
dataIndex: name,
|
||||||
|
key: name,
|
||||||
|
width: 100,
|
||||||
|
align: 'center'
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const summaryNode = () => {
|
||||||
|
const summary = {
|
||||||
|
max: { date: '最大值' },
|
||||||
|
min: { date: '最小值' },
|
||||||
|
range: { date: '变幅' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = ['reservoirLevel', 'rainfall', ...pipeNames];
|
||||||
|
fields.forEach(field => {
|
||||||
|
const values = chartData[field] || chartData.pipes[field];
|
||||||
|
let max = -Infinity, min = Infinity, maxDate = '', minDate = '';
|
||||||
|
values.forEach((v, i) => {
|
||||||
|
if (v !== null && v !== undefined) {
|
||||||
|
if (v > max) { max = v; maxDate = chartData.dates[i]; }
|
||||||
|
if (v < min) { min = v; minDate = chartData.dates[i]; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (max === -Infinity) {
|
||||||
|
summary.max[field] = 'N/A';
|
||||||
|
summary.min[field] = 'N/A';
|
||||||
|
summary.range[field] = 'N/A';
|
||||||
|
summary.max[`${field}_date`] = '';
|
||||||
|
summary.min[`${field}_date`] = '';
|
||||||
|
} else {
|
||||||
|
summary.max[field] = max.toFixed(2);
|
||||||
|
summary.min[field] = min.toFixed(2);
|
||||||
|
summary.range[field] = (max - min).toFixed(2);
|
||||||
|
summary.max[`${field}_date`] = maxDate;
|
||||||
|
summary.min[`${field}_date`] = minDate;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cellContentStyle = { whiteSpace: 'pre-line', textAlign: 'center', verticalAlign: 'middle', borderBottom: '1px solid rgba(0, 160, 233, 0.3)' };
|
||||||
|
const rowStyle = { background: 'rgba(0, 33, 64, 0.85)', color: '#fff' };
|
||||||
|
const fixedCellStyle = {
|
||||||
|
...cellContentStyle,
|
||||||
|
background: 'rgba(0, 33, 64, 0.95)',
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
boxShadow: '2px 0 5px rgba(0,0,0,0.3)'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table.Summary fixed>
|
||||||
|
<Table.Summary.Row style={rowStyle}>
|
||||||
|
<Table.Summary.Cell index={0} fixed="left" style={fixedCellStyle}>{summary.max.date}</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={1} style={cellContentStyle}>{summary.max.reservoirLevel}</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={2} style={cellContentStyle}>{summary.max.rainfall}</Table.Summary.Cell>
|
||||||
|
{pipeNames.map((name, i) => <Table.Summary.Cell key={`${name}-max`} index={i + 3} style={cellContentStyle}>{summary.max[name]}</Table.Summary.Cell>)}
|
||||||
|
</Table.Summary.Row>
|
||||||
|
<Table.Summary.Row style={rowStyle}>
|
||||||
|
<Table.Summary.Cell index={0} fixed="left" style={fixedCellStyle}>日期</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={1} style={cellContentStyle}>{summary.max.reservoirLevel_date}</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={2} style={cellContentStyle}>{summary.max.rainfall_date}</Table.Summary.Cell>
|
||||||
|
{pipeNames.map((name, i) => <Table.Summary.Cell key={`${name}-max-date`} index={i + 3} style={cellContentStyle}>{summary.max[`${name}_date`]}</Table.Summary.Cell>)}
|
||||||
|
</Table.Summary.Row>
|
||||||
|
<Table.Summary.Row style={rowStyle}>
|
||||||
|
<Table.Summary.Cell index={0} fixed="left" style={fixedCellStyle}>{summary.min.date}</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={1} style={cellContentStyle}>{summary.min.reservoirLevel}</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={2} style={cellContentStyle}>{summary.min.rainfall}</Table.Summary.Cell>
|
||||||
|
{pipeNames.map((name, i) => <Table.Summary.Cell key={`${name}-min`} index={i + 3} style={cellContentStyle}>{summary.min[name]}</Table.Summary.Cell>)}
|
||||||
|
</Table.Summary.Row>
|
||||||
|
<Table.Summary.Row style={rowStyle}>
|
||||||
|
<Table.Summary.Cell index={0} fixed="left" style={fixedCellStyle}>日期</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={1} style={cellContentStyle}>{summary.min.reservoirLevel_date}</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={2} style={cellContentStyle}>{summary.min.rainfall_date}</Table.Summary.Cell>
|
||||||
|
{pipeNames.map((name, i) => <Table.Summary.Cell key={`${name}-min-date`} index={i + 3} style={cellContentStyle}>{summary.min[`${name}_date`]}</Table.Summary.Cell>)}
|
||||||
|
</Table.Summary.Row>
|
||||||
|
<Table.Summary.Row style={rowStyle}>
|
||||||
|
<Table.Summary.Cell index={0} fixed="left" style={fixedCellStyle}>{summary.range.date}</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={1} style={cellContentStyle}>{summary.range.reservoirLevel}</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={2} style={cellContentStyle}>{summary.range.rainfall}</Table.Summary.Cell>
|
||||||
|
{pipeNames.map((name, i) => <Table.Summary.Cell key={`${name}-range`} index={i + 3} style={cellContentStyle}>{summary.range[name]}</Table.Summary.Cell>)}
|
||||||
|
</Table.Summary.Row>
|
||||||
|
</Table.Summary>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row gutter={16} style={{ height: '100%' }}>
|
||||||
|
<Col span={12} style={{ height: '98%' }}>
|
||||||
|
<ReactECharts
|
||||||
|
option={getChartOption(chartData)}
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={12} style={{ height: '100%' }}>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={tableData}
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ y: 'calc(100vh - 650px)' }}
|
||||||
|
summary={summaryNode}// Adjust based on your layout
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProcessLineChart;
|
||||||
|
|
@ -0,0 +1,401 @@
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export const getOption = (data = {}) => {
|
||||||
|
const {
|
||||||
|
waterLevel = 113.8,
|
||||||
|
floodLevel = 128.42,
|
||||||
|
pipes = [
|
||||||
|
{ name: 'UPD4', x: 110, top: 175, bottom: 60, level: 103.67 },
|
||||||
|
{ name: 'UPD1', x: 160, top: 175, bottom: 60, level: 103.67 },
|
||||||
|
{ name: 'UPD2', x: 190, top: 150, bottom: 60, level: 102.89 },
|
||||||
|
{ name: 'UPD3', x: 210, top: 130, bottom: 60, level: 101.12 },
|
||||||
|
]
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
// Dam Geometry Configuration
|
||||||
|
const foundationY = 40;
|
||||||
|
const crestY = 180;
|
||||||
|
|
||||||
|
// Left Slope (Upstream)
|
||||||
|
const leftToe = [20, foundationY];
|
||||||
|
const leftCrest = [130, crestY];
|
||||||
|
|
||||||
|
// Right Slope (Downstream) with Berm
|
||||||
|
const rightCrest = [150, crestY];
|
||||||
|
const bermElevation = 120;
|
||||||
|
const bermStart = [190, bermElevation];
|
||||||
|
const bermEnd = [210, bermElevation];
|
||||||
|
const rightToe = [250, foundationY];
|
||||||
|
const distanceFromCrest = 20; // Distance from top of core to dam crest
|
||||||
|
|
||||||
|
const coreBaseLeft = 125;
|
||||||
|
const coreBaseRight = 155;
|
||||||
|
const coreTopLeft = 135;
|
||||||
|
const coreTopRight = 145;
|
||||||
|
const coreBottomY = foundationY;
|
||||||
|
// const coreTopY = crestY - distanceFromCrest;
|
||||||
|
const coreTopY = floodLevel;
|
||||||
|
|
||||||
|
// Dam Toe Weight (Gray Mound)
|
||||||
|
// Shifted left to intersect with the main slope
|
||||||
|
const toeWeightPoints = [
|
||||||
|
[230, foundationY],
|
||||||
|
[240, 55],
|
||||||
|
[245, 55],
|
||||||
|
[255, foundationY]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const damColor = '#Decbb6'; // Light beige for shell
|
||||||
|
const coreColor = '#D2a88d'; // Darker for core
|
||||||
|
const foundationColor = '#8B4513'; // Dark brown
|
||||||
|
const waterColor = 'rgba(0, 150, 136, 0.8)';
|
||||||
|
const saturationLineColor = '#00a0e9'; // Bright blue
|
||||||
|
const floodLineColor = 'red';
|
||||||
|
const toeColor = '#808080'; // Gray for toe
|
||||||
|
|
||||||
|
// Calculations
|
||||||
|
// Upstream Slope Line: y - y1 = m(x - x1)
|
||||||
|
const mUp = (leftCrest[1] - leftToe[1]) / (leftCrest[0] - leftToe[0]);
|
||||||
|
const cUp = leftToe[1] - mUp * leftToe[0];
|
||||||
|
|
||||||
|
// Intersection of Water Level with Upstream Slope
|
||||||
|
// x = (y - c) / m
|
||||||
|
const waterX = (waterLevel - cUp) / mUp;
|
||||||
|
const floodX = (floodLevel - cUp) / mUp;
|
||||||
|
|
||||||
|
// Render Pipe Function
|
||||||
|
const renderPipe = (params, api) => {
|
||||||
|
const x = api.value(0);
|
||||||
|
const top = api.value(1);
|
||||||
|
const bottom = api.value(2);
|
||||||
|
const level = api.value(3);
|
||||||
|
const name = api.value(4);
|
||||||
|
|
||||||
|
const topPos = api.coord([x, top]);
|
||||||
|
const bottomPos = api.coord([x, bottom]);
|
||||||
|
const levelPos = api.coord([x, level]);
|
||||||
|
|
||||||
|
const width = 12;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'group',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'rect',
|
||||||
|
shape: {
|
||||||
|
x: topPos[0] - width / 2,
|
||||||
|
y: topPos[1],
|
||||||
|
width: width,
|
||||||
|
height: levelPos[1] - topPos[1]
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
fill: 'rgba(200,200,200,0.2)',
|
||||||
|
stroke: '#999',
|
||||||
|
lineWidth: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'rect',
|
||||||
|
shape: {
|
||||||
|
x: levelPos[0] - width / 2,
|
||||||
|
y: levelPos[1],
|
||||||
|
width: width,
|
||||||
|
height: bottomPos[1] - levelPos[1]
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
fill: '#00a0e9',
|
||||||
|
stroke: '#999',
|
||||||
|
lineWidth: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
style: {
|
||||||
|
text: name,
|
||||||
|
x: topPos[0],
|
||||||
|
y: topPos[1] - 15,
|
||||||
|
textAlign: 'center',
|
||||||
|
fill: '#fff',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
style: {
|
||||||
|
text: level.toFixed(2) + 'm',
|
||||||
|
x: levelPos[0] + width / 2 + 5,
|
||||||
|
y: levelPos[1],
|
||||||
|
textAlign: 'left',
|
||||||
|
textVerticalAlign: 'middle',
|
||||||
|
fill: '#fff',
|
||||||
|
fontSize: 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Saturation Line Logic
|
||||||
|
const damCenterX = 140;
|
||||||
|
const leftPipes = pipes.filter(p => p.x < damCenterX).sort((a, b) => a.x - b.x);
|
||||||
|
const rightPipes = pipes.filter(p => p.x >= damCenterX).sort((a, b) => a.x - b.x);
|
||||||
|
|
||||||
|
const saturationPoints = [];
|
||||||
|
|
||||||
|
// 1. Start at water surface
|
||||||
|
saturationPoints.push([waterX, waterLevel]);
|
||||||
|
|
||||||
|
// 2. Handle Left Side
|
||||||
|
if (leftPipes.length > 0) {
|
||||||
|
// Connect all left pipes
|
||||||
|
leftPipes.forEach(p => saturationPoints.push([p.x, p.level]));
|
||||||
|
// Extend to center from the last left pipe
|
||||||
|
saturationPoints.push([damCenterX, leftPipes[leftPipes.length - 1].level]);
|
||||||
|
} else {
|
||||||
|
// No left pipes: Extend from water level to center
|
||||||
|
saturationPoints.push([damCenterX, waterLevel]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Handle Right Side
|
||||||
|
// Connect to the nearest pipe on the right (and subsequent ones)
|
||||||
|
rightPipes.forEach(p => saturationPoints.push([p.x, p.level]));
|
||||||
|
|
||||||
|
// 4. End logic
|
||||||
|
if (rightPipes.length > 0) {
|
||||||
|
const lastPipe = rightPipes[rightPipes.length - 1];
|
||||||
|
if (lastPipe.level < foundationY) {
|
||||||
|
// If last pipe level is below foundation, extend horizontally to x=250
|
||||||
|
saturationPoints.push([250, lastPipe.level]);
|
||||||
|
} else {
|
||||||
|
// Otherwise, connect to toe
|
||||||
|
saturationPoints.push(rightToe);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No right pipes, connect to toe
|
||||||
|
saturationPoints.push(rightToe);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
formatter: () => ''
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 60,
|
||||||
|
right: 60,
|
||||||
|
top: 60,
|
||||||
|
bottom: 40
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: 280,
|
||||||
|
axisLabel: { color: '#fff' },
|
||||||
|
splitLine: { show: false }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: 200,
|
||||||
|
axisLabel: { color: '#fff', formatter: '{value}' },
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: { type: 'dashed', color: 'rgba(255,255,255,0.1)' }
|
||||||
|
},
|
||||||
|
name: '断面高(m)',
|
||||||
|
nameTextStyle: { color: '#fff', padding: [0, 0, 0, 20] }
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
// 1. Foundation
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
renderItem: (params, api) => {
|
||||||
|
const points = [[0, 0], [280, 0], [280, foundationY], [0, foundationY]].map(p => api.coord(p));
|
||||||
|
return {
|
||||||
|
type: 'polygon',
|
||||||
|
shape: { points },
|
||||||
|
style: { fill: foundationColor },
|
||||||
|
silent: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data: [[0, 0]]
|
||||||
|
},
|
||||||
|
// 2. Dam Body Shell (Left)
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
renderItem: (params, api) => {
|
||||||
|
const points = [leftToe, leftCrest, [coreTopLeft, coreTopY], [coreBaseLeft, foundationY]].map(p => api.coord(p));
|
||||||
|
return {
|
||||||
|
type: 'polygon',
|
||||||
|
shape: { points },
|
||||||
|
style: { fill: damColor, stroke: damColor },
|
||||||
|
silent: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data: [[0, 0]]
|
||||||
|
},
|
||||||
|
// 3. Dam Body Shell (Right with Berm)
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
renderItem: (params, api) => {
|
||||||
|
const points = [
|
||||||
|
[coreBaseRight, foundationY],
|
||||||
|
[coreTopRight, coreTopY],
|
||||||
|
rightCrest,
|
||||||
|
bermStart,
|
||||||
|
bermEnd,
|
||||||
|
rightToe
|
||||||
|
].map(p => api.coord(p));
|
||||||
|
return {
|
||||||
|
type: 'polygon',
|
||||||
|
shape: { points },
|
||||||
|
style: { fill: damColor, stroke: damColor },
|
||||||
|
silent: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data: [[0, 0]]
|
||||||
|
},
|
||||||
|
// 4. Core Wall
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
renderItem: (params, api) => {
|
||||||
|
const points = [
|
||||||
|
[coreBaseLeft, coreBottomY],
|
||||||
|
[coreTopLeft, coreTopY],
|
||||||
|
[coreTopRight, coreTopY],
|
||||||
|
[coreBaseRight, coreBottomY]
|
||||||
|
].map(p => api.coord(p));
|
||||||
|
return {
|
||||||
|
type: 'polygon',
|
||||||
|
shape: { points },
|
||||||
|
style: { fill: coreColor, stroke: coreColor },
|
||||||
|
silent: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data: [[0, 0]]
|
||||||
|
},
|
||||||
|
// 5. Dam Crest Filler (Top Part)
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
renderItem: (params, api) => {
|
||||||
|
const points = [
|
||||||
|
[coreTopLeft, coreTopY],
|
||||||
|
[coreTopRight, coreTopY],
|
||||||
|
rightCrest,
|
||||||
|
leftCrest
|
||||||
|
].map(p => api.coord(p));
|
||||||
|
return {
|
||||||
|
type: 'polygon',
|
||||||
|
shape: { points },
|
||||||
|
style: { fill: damColor, stroke: damColor },
|
||||||
|
silent: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data: [[0, 0]]
|
||||||
|
},
|
||||||
|
// 6. Dam Toe Weight
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
renderItem: (params, api) => {
|
||||||
|
const points = toeWeightPoints.map(p => api.coord(p));
|
||||||
|
return {
|
||||||
|
type: 'polygon',
|
||||||
|
shape: { points },
|
||||||
|
style: { fill: toeColor, stroke: toeColor },
|
||||||
|
silent: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data: [[0, 0]]
|
||||||
|
},
|
||||||
|
// 6. Upstream Water Level (Correctly Filled)
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
renderItem: (params, api) => {
|
||||||
|
const points = [
|
||||||
|
[0, foundationY],
|
||||||
|
leftToe, // Join the slope toe
|
||||||
|
[waterX, waterLevel], // Join the slope at water level
|
||||||
|
[0, waterLevel]
|
||||||
|
].map(p => api.coord(p));
|
||||||
|
return {
|
||||||
|
type: 'polygon',
|
||||||
|
shape: { points },
|
||||||
|
style: { fill: waterColor },
|
||||||
|
silent: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data: [[0, 0]]
|
||||||
|
},
|
||||||
|
// 7. Saturation Line
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
smooth: 0.4,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: { color: saturationLineColor, width: 3 },
|
||||||
|
data: saturationPoints,
|
||||||
|
z: 11
|
||||||
|
},
|
||||||
|
// 8. Flood Level (Dashed Horizontal)
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
markLine: {
|
||||||
|
symbol: ['none', 'none'],
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'middle',
|
||||||
|
formatter: `校核洪水位 ${floodLevel}m`,
|
||||||
|
color: '#fff',
|
||||||
|
padding: [0, 0, 10, 0]
|
||||||
|
},
|
||||||
|
lineStyle: { color: floodLineColor, type: 'dashed', width: 2 },
|
||||||
|
data: [
|
||||||
|
[
|
||||||
|
{ x:70, yAxis: floodLevel },
|
||||||
|
{ xAxis: floodX, yAxis: floodLevel } // To the dam body
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
// 9. Red Line from Flood Level to Dam Toe
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: { color: '#ff0000', width: 2, type: 'solid' }, // Red solid line
|
||||||
|
data: [
|
||||||
|
[floodX, floodLevel],
|
||||||
|
[coreTopLeft, coreTopY],
|
||||||
|
[coreTopRight, coreTopY],
|
||||||
|
[237, 50] // To dam toe top
|
||||||
|
],
|
||||||
|
z: 10 // Ensure it's on top
|
||||||
|
},
|
||||||
|
// 10. Measured Water Level Label
|
||||||
|
{
|
||||||
|
type: 'scatter',
|
||||||
|
symbol: 'triangle',
|
||||||
|
symbolSize: 10,
|
||||||
|
symbolRotate: 180,
|
||||||
|
itemStyle: { color: saturationLineColor },
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
formatter: `实测水位 ${waterLevel}m`,
|
||||||
|
position: 'top',
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 12,
|
||||||
|
offset: [0, 0]
|
||||||
|
},
|
||||||
|
data: [[waterX / 2, waterLevel]]
|
||||||
|
},
|
||||||
|
// 10. Pipes
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
renderItem: renderPipe,
|
||||||
|
data: pipes.map(p => [p.x, p.top, p.bottom, p.level, p.name]),
|
||||||
|
z: 12
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import ReactEcharts from 'echarts-for-react';
|
||||||
|
import { DatePicker, Select, Button } from 'antd';
|
||||||
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { getOption } from './chartOption';
|
||||||
|
import ProcessLineChart from './ProcessLineChart';
|
||||||
|
import DeformationPanel from './DeformationPanel';
|
||||||
|
import './index.less';
|
||||||
|
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
const SafetyPanel = () => {
|
||||||
|
const [activeTab, setActiveTab] = useState('seepage');
|
||||||
|
const [filter, setFilter] = useState({
|
||||||
|
type: 'saturation', // saturation | process
|
||||||
|
section: 'main_0_086',
|
||||||
|
date: [moment().subtract(7, 'days'), moment()]
|
||||||
|
});
|
||||||
|
|
||||||
|
const [chartData, setChartData] = useState({});
|
||||||
|
const [quickDate, setQuickDate] = useState('1m'); // Default to 1 month
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Mock fetching data
|
||||||
|
// In a real scenario, this would depend on the selected section
|
||||||
|
setChartData({
|
||||||
|
waterLevel: 113.8,
|
||||||
|
floodLevel: 128.42,
|
||||||
|
pipes: [
|
||||||
|
{ name: 'UPD4', x: 110, top: 175, bottom: 60, level: 117.67 },
|
||||||
|
{ name: 'UPD1', x: 160, top: 175, bottom: 60, level: 100.67 },
|
||||||
|
{ name: 'UPD2', x: 190, top: 150, bottom: 30, level: 40 },
|
||||||
|
{ name: 'UPD3', x: 210, top: 130, bottom: 20, level: 30 },
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}, [filter]);
|
||||||
|
|
||||||
|
const handleTabChange = (key) => {
|
||||||
|
setActiveTab(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuickDate = (type) => {
|
||||||
|
setQuickDate(type);
|
||||||
|
let start = moment();
|
||||||
|
if (type === '1m') start = moment().subtract(1, 'months');
|
||||||
|
if (type === '6m') start = moment().subtract(6, 'months');
|
||||||
|
if (type === '1y') start = moment().subtract(1, 'years');
|
||||||
|
setFilter({ ...filter, date: [start, moment()] });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="safety-panel-container">
|
||||||
|
{/* Sidebar for navigation */}
|
||||||
|
<div className="sidebar">
|
||||||
|
<div
|
||||||
|
className={`sidebar-btn ${activeTab === 'seepage' ? 'active' : ''}`}
|
||||||
|
onClick={() => handleTabChange('seepage')}
|
||||||
|
>
|
||||||
|
渗压监测
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`sidebar-btn ${activeTab === 'deformation' ? 'active' : ''}`}
|
||||||
|
onClick={() => handleTabChange('deformation')}
|
||||||
|
>
|
||||||
|
变形监测
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main content area */}
|
||||||
|
<div className="main-content-area">
|
||||||
|
{/* Filters bar */}
|
||||||
|
<div className="filters-bar">
|
||||||
|
{activeTab === 'seepage' ? (
|
||||||
|
<>
|
||||||
|
<Select
|
||||||
|
value={filter.type}
|
||||||
|
style={{ width: 120, marginRight: 12 }}
|
||||||
|
onChange={v => setFilter({...filter, type: v})}
|
||||||
|
dropdownStyle={{ background: '#0d2c4d', color: '#fff' }}
|
||||||
|
>
|
||||||
|
<Option value="saturation">浸润线图</Option>
|
||||||
|
<Option value="process">过程线图</Option>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<span className="filter-label">断面:</span>
|
||||||
|
<Select
|
||||||
|
value={filter.section}
|
||||||
|
style={{ width: 150, marginRight: 12 }}
|
||||||
|
onChange={v => setFilter({...filter, section: v})}
|
||||||
|
dropdownStyle={{ background: '#0d2c4d', color: '#fff' }}
|
||||||
|
>
|
||||||
|
<Option value="main_0_086">主坝0+086</Option>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<span className="filter-label">时间:</span>
|
||||||
|
<RangePicker
|
||||||
|
value={filter.date}
|
||||||
|
onChange={v => setFilter({...filter, date: v})}
|
||||||
|
style={{ width: 260, marginRight: 12 }}
|
||||||
|
dropdownClassName="dark-picker-dropdown"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button className="ant-btn-ghost-blue" icon={<SearchOutlined />} onClick={() => {}}>查询</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Select value="过程线图" style={{ width: 120, marginRight: 12 }} disabled dropdownStyle={{ background: '#0d2c4d', color: '#fff' }}>
|
||||||
|
<Option value="process">过程线图</Option>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<RangePicker
|
||||||
|
value={filter.date}
|
||||||
|
onChange={v => {
|
||||||
|
setFilter({...filter, date: v});
|
||||||
|
setQuickDate('');
|
||||||
|
}}
|
||||||
|
style={{ width: 260, marginRight: 12 }}
|
||||||
|
dropdownClassName="dark-picker-dropdown"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="quick-date-group" style={{ display: 'flex', marginRight: 12 }}>
|
||||||
|
<Button type={quickDate === '1m' ? 'primary' : 'default'} onClick={() => handleQuickDate('1m')} style={{ borderRadius: '4px 0 0 4px' }}>近一月</Button>
|
||||||
|
<Button type={quickDate === '6m' ? 'primary' : 'default'} onClick={() => handleQuickDate('6m')} style={{ borderRadius: '0', marginLeft: '-1px' }}>近半年</Button>
|
||||||
|
<Button type={quickDate === '1y' ? 'primary' : 'default'} onClick={() => handleQuickDate('1y')} style={{ borderRadius: '0 4px 4px 0', marginLeft: '-1px' }}>近一年</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="ant-btn-ghost-blue" type="primary" onClick={() => {}}>查询</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Chart/Table content */}
|
||||||
|
<div className="chart-content">
|
||||||
|
{activeTab === 'seepage' ? (
|
||||||
|
filter.type === 'saturation' ? (
|
||||||
|
<ReactEcharts
|
||||||
|
option={getOption(chartData)}
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
notMerge={true}
|
||||||
|
lazyUpdate={true}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ProcessLineChart />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<DeformationPanel />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SafetyPanel;
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
.safety-panel-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding: 16px;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 140px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
.sidebar-btn {
|
||||||
|
height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid rgba(0, 160, 233, 0.6);
|
||||||
|
background: rgba(0, 70, 120, 0.3);
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 160, 233, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: rgba(0, 160, 233, 0.8);
|
||||||
|
border-color: #00a0e9;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 160, 233, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content-area {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.filters-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.filter-label {
|
||||||
|
margin-right: 8px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-content {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden; // Prevent overflow
|
||||||
|
|
||||||
|
.echarts-for-react {
|
||||||
|
height: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,14 +8,9 @@ import './index.less';
|
||||||
import RainMonitor from './RainMonitor';
|
import RainMonitor from './RainMonitor';
|
||||||
import ReservoirPanel from './ReservoirPanel';
|
import ReservoirPanel from './ReservoirPanel';
|
||||||
import FlowPanel from './FlowPanel';
|
import FlowPanel from './FlowPanel';
|
||||||
|
import SafetyPanel from './SafetyPanel';
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
const SafetyPanel = () => {
|
|
||||||
return (
|
|
||||||
<div className="awm-empty">内容待接入</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AllWeatherModal = ({ active }) => {
|
const AllWeatherModal = ({ active }) => {
|
||||||
if (active === 'rain') return <RainMonitor />;
|
if (active === 'rain') return <RainMonitor />;
|
||||||
if (active === 'reservoir') return <ReservoirPanel />;
|
if (active === 'reservoir') return <ReservoirPanel />;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue