diff --git a/src/components/ant_override.less b/src/components/ant_override.less
index e68048b..98437ec 100644
--- a/src/components/ant_override.less
+++ b/src/components/ant_override.less
@@ -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)
.ant-table-body, .ant-table-content {
&::-webkit-scrollbar {
diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/DeformationPanel.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/DeformationPanel.js
new file mode 100644
index 0000000..344f1ea
--- /dev/null
+++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/DeformationPanel.js
@@ -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 (
+
+ {/* Left Chart */}
+
+
+
+
+ {/* Right Table */}
+
+
+ {mainType === 'horizontal' && (
+ setSubType(e.target.value)}
+ buttonStyle="solid"
+ size="small"
+ style={{ marginRight: 12 }}
+ >
+ 上下游
+ 左右岸
+
+ )}
+
+ {
+ setMainType(e.target.value);
+ if (e.target.value === 'horizontal') {
+ setSubType('upDown'); // Reset subType when switching back
+ }
+ }}
+ buttonStyle="solid"
+ size="small"
+ >
+ 水平位移
+ 垂直位移
+
+
+
+
+
+
+ );
+};
+
+export default DeformationPanel;
diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/ProcessLineChart.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/ProcessLineChart.js
new file mode 100644
index 0000000..c4a07b0
--- /dev/null
+++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/ProcessLineChart.js
@@ -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 (
+
+
+ {summary.max.date}
+ {summary.max.reservoirLevel}
+ {summary.max.rainfall}
+ {pipeNames.map((name, i) => {summary.max[name]})}
+
+
+ 日期
+ {summary.max.reservoirLevel_date}
+ {summary.max.rainfall_date}
+ {pipeNames.map((name, i) => {summary.max[`${name}_date`]})}
+
+
+ {summary.min.date}
+ {summary.min.reservoirLevel}
+ {summary.min.rainfall}
+ {pipeNames.map((name, i) => {summary.min[name]})}
+
+
+ 日期
+ {summary.min.reservoirLevel_date}
+ {summary.min.rainfall_date}
+ {pipeNames.map((name, i) => {summary.min[`${name}_date`]})}
+
+
+ {summary.range.date}
+ {summary.range.reservoirLevel}
+ {summary.range.rainfall}
+ {pipeNames.map((name, i) => {summary.range[name]})}
+
+
+ );
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ProcessLineChart;
diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/chartOption.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/chartOption.js
new file mode 100644
index 0000000..b116dac
--- /dev/null
+++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/chartOption.js
@@ -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
+ },
+ ]
+ };
+};
diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/index.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/index.js
new file mode 100644
index 0000000..bee5b54
--- /dev/null
+++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/index.js
@@ -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 (
+
+ {/* Sidebar for navigation */}
+
+
handleTabChange('seepage')}
+ >
+ 渗压监测
+
+
handleTabChange('deformation')}
+ >
+ 变形监测
+
+
+
+ {/* Main content area */}
+
+ {/* Filters bar */}
+
+ {activeTab === 'seepage' ? (
+ <>
+
+
+
断面:
+
+
+
时间:
+
setFilter({...filter, date: v})}
+ style={{ width: 260, marginRight: 12 }}
+ dropdownClassName="dark-picker-dropdown"
+ />
+
+ } onClick={() => {}}>查询
+ >
+ ) : (
+ <>
+
+
+ {
+ setFilter({...filter, date: v});
+ setQuickDate('');
+ }}
+ style={{ width: 260, marginRight: 12 }}
+ dropdownClassName="dark-picker-dropdown"
+ />
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+ {/* Chart/Table content */}
+
+ {activeTab === 'seepage' ? (
+ filter.type === 'saturation' ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ )}
+
+
+
+ );
+};
+
+export default SafetyPanel;
diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/index.less b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/index.less
new file mode 100644
index 0000000..edc8375
--- /dev/null
+++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/SafetyPanel/index.less
@@ -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;
+ }
+ }
+ }
+}
diff --git a/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/index.js b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/index.js
index 7d137ef..b08bd18 100644
--- a/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/index.js
+++ b/src/views/Home/components/Business/SiQuan/components/ModalComponents/AllWeatherModal/index.js
@@ -8,14 +8,9 @@ import './index.less';
import RainMonitor from './RainMonitor';
import ReservoirPanel from './ReservoirPanel';
import FlowPanel from './FlowPanel';
+import SafetyPanel from './SafetyPanel';
const { RangePicker } = DatePicker;
-const SafetyPanel = () => {
- return (
- 内容待接入
- );
-};
-
const AllWeatherModal = ({ active }) => {
if (active === 'rain') return ;
if (active === 'reservoir') return ;