feat(): ai洪水预报开发
parent
475e670b7c
commit
9b5187625b
|
|
@ -7,7 +7,8 @@ const service_test = '/gunshiApp/dcpj'
|
||||||
|
|
||||||
const apiurl = {
|
const apiurl = {
|
||||||
test: {
|
test: {
|
||||||
find: service_test + '/stPRHisData/getHisData'
|
find: service_test + '/stPRHisData/getHisData',
|
||||||
|
find1: service_test + '/stPRHisData/getHisDataV2'
|
||||||
},
|
},
|
||||||
setMenu: service_fxdd + '/visitMenuLog/insert',
|
setMenu: service_fxdd + '/visitMenuLog/insert',
|
||||||
setPassword: service_fxdd + '/user/updateSecretKey',
|
setPassword: service_fxdd + '/user/updateSecretKey',
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import Dept from './SystemMangant/dept'
|
||||||
import Role from './SystemMangant/role'
|
import Role from './SystemMangant/role'
|
||||||
import MenuM from './SystemMangant/menuM'
|
import MenuM from './SystemMangant/menuM'
|
||||||
import TestLine from './TestLine'
|
import TestLine from './TestLine'
|
||||||
|
import TestLine2 from './TestLine2'
|
||||||
// const HomePage = lazy(() => import('./Home'))
|
// const HomePage = lazy(() => import('./Home'))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -41,6 +42,7 @@ const AppRouters: React.FC = () => {
|
||||||
let element = useRoutes([
|
let element = useRoutes([
|
||||||
// { path: '/', element: <LoginPage /> },
|
// { path: '/', element: <LoginPage /> },
|
||||||
{ path: '/', element: <TestLine /> },
|
{ path: '/', element: <TestLine /> },
|
||||||
|
{ path: '/test2', element: <TestLine2 /> },
|
||||||
{ path: '/home', element: <HomePage /> },
|
{ path: '/home', element: <HomePage /> },
|
||||||
{
|
{
|
||||||
path: '/mgr',
|
path: '/mgr',
|
||||||
|
|
@ -74,6 +76,7 @@ const AppRouters: React.FC = () => {
|
||||||
|
|
||||||
// 测试曲线
|
// 测试曲线
|
||||||
{ path: 'testLine/testLine', element: <TestLine /> },
|
{ path: 'testLine/testLine', element: <TestLine /> },
|
||||||
|
{ path: 'testLine/testLine2', element: <TestLine2 /> },
|
||||||
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ export default function drpOption(predict=[],history=[],type) {
|
||||||
}],
|
}],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: "实时雨量",
|
name: "实测雨量",
|
||||||
type: "bar",
|
type: "bar",
|
||||||
color: '#F59A23',
|
color: '#F59A23',
|
||||||
yAxisIndex: 1,
|
yAxisIndex: 1,
|
||||||
|
|
@ -138,7 +138,7 @@ export default function drpOption(predict=[],history=[],type) {
|
||||||
smooth: true,
|
smooth: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "实时水位",
|
name: "实测水位",
|
||||||
type: "line",
|
type: "line",
|
||||||
showSymbol: false,
|
showSymbol: false,
|
||||||
data: history.map(item => [item.tm, item.waters]),
|
data: history.map(item => [item.tm, item.waters]),
|
||||||
|
|
|
||||||
|
|
@ -44,14 +44,14 @@ export default function TestLine() {
|
||||||
fixed:'left'
|
fixed:'left'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '实际雨量(mm)',
|
title: '实测雨量(mm)',
|
||||||
dataIndex: 'rain',
|
dataIndex: 'rain',
|
||||||
key: 'rain',
|
key: 'rain',
|
||||||
width: 100,
|
width: 100,
|
||||||
align: 'center'
|
align: 'center'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '实际水位(m)',
|
title: '实测水位(m)',
|
||||||
dataIndex: 'water',
|
dataIndex: 'water',
|
||||||
key: 'water',
|
key: 'water',
|
||||||
width: 100,
|
width: 100,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Form, Input, Button, DatePicker, InputNumber, message } from 'antd';
|
import { Form, Input, Button, DatePicker, InputNumber, message } from 'antd';
|
||||||
import NormalSelect from '../../components/Form/NormalSelect';
|
import NormalSelect from '../../components/Form/NormalSelect';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
const ToolBar = ({ setSearchVal, setType, save, form1 }) => {
|
const ToolBar = ({ setSearchVal, setType, save, form1 }) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const types = [
|
const types = [
|
||||||
// {
|
// {
|
||||||
|
|
@ -135,17 +136,19 @@ const ToolBar = ({ setSearchVal, setType, save, form1 }) => {
|
||||||
// form1.resetFields()
|
// form1.resetFields()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const jump = () => {
|
||||||
|
navigate('/test2');
|
||||||
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const stm = moment('2024-01-01').format('YYYY-MM-DD 00:00:00')
|
const stm = moment('2024-07-10 00:00:00').format('YYYY-MM-DD 00:00:00')
|
||||||
const etm = moment('2024-01-10').format('YYYY-MM-DD 00:00:00')
|
const etm = moment('2024-07-31 16:00:00').format('YYYY-MM-DD 00:00:00')
|
||||||
const params = {
|
const params = {
|
||||||
code: "cq",
|
code: "cq",
|
||||||
time: '1h',
|
|
||||||
stm,
|
stm,
|
||||||
etm
|
etm
|
||||||
}
|
}
|
||||||
form.setFieldValue('code', params.code);
|
form.setFieldValue('code', params.code);
|
||||||
form.setFieldValue('tm', [moment('2024-01-01'), moment('2024-01-10')]);
|
form.setFieldValue('tm', [moment('2024-07-10 00:00:00'), moment('2024-07-31 16:00:00')]);
|
||||||
setSearchVal(params)
|
setSearchVal(params)
|
||||||
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
@ -191,6 +194,9 @@ const ToolBar = ({ setSearchVal, setType, save, form1 }) => {
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button type="primary" htmlType="submit">查询</Button>
|
<Button type="primary" htmlType="submit">查询</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" onClick={jump}>时刻预测</Button>
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,176 @@
|
||||||
|
import { Button, Form, Input, InputNumber, Popconfirm, Table } from 'antd';
|
||||||
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
|
const EditableContext = React.createContext(null);
|
||||||
|
const EditableRow = ({ index, ...props }) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
return (
|
||||||
|
<Form form={form} component={false}>
|
||||||
|
<EditableContext.Provider value={form}>
|
||||||
|
<tr {...props} />
|
||||||
|
</EditableContext.Provider>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const EditableCell = ({
|
||||||
|
title,
|
||||||
|
editable,
|
||||||
|
children,
|
||||||
|
dataIndex,
|
||||||
|
record,
|
||||||
|
handleSave,
|
||||||
|
...restProps
|
||||||
|
}) => {
|
||||||
|
const [editing, setEditing] = useState(true);
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
const form = useContext(EditableContext);
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (editing) {
|
||||||
|
// inputRef.current?.focus();
|
||||||
|
// }
|
||||||
|
// }, [editing]);
|
||||||
|
const toggleEdit = (title) => {
|
||||||
|
if (title == '雨量') {
|
||||||
|
// setEditing(!editing);
|
||||||
|
if (record[dataIndex]) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
[dataIndex]: record[dataIndex],
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
const save = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
toggleEdit(title);
|
||||||
|
handleSave({
|
||||||
|
...record,
|
||||||
|
...values,
|
||||||
|
});
|
||||||
|
} catch (errInfo) {
|
||||||
|
console.log('Save failed:', errInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let childNode = children;
|
||||||
|
if (editable) {
|
||||||
|
childNode = editing ? (
|
||||||
|
<Form.Item
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
name={dataIndex}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: `${title}必填`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber min={0} ref={inputRef} onPressEnter={save} onBlur={save} />
|
||||||
|
</Form.Item>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="editable-cell-value-wrap"
|
||||||
|
style={{
|
||||||
|
paddingRight: 24,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <td {...restProps} onClick={() => toggleEdit(title)}>{childNode}</td>;
|
||||||
|
};
|
||||||
|
const App = ({ count, tableData, setEditData }) => {
|
||||||
|
const [dataSource, setDataSource] = useState([]);
|
||||||
|
const defaultColumns = [
|
||||||
|
{
|
||||||
|
title: '预见期',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
width: '30%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '雨量',
|
||||||
|
dataIndex: 'drp',
|
||||||
|
width: '30%',
|
||||||
|
editable: true,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '水位',
|
||||||
|
dataIndex: 'rz',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const handleAdd = () => {
|
||||||
|
const newArr = Array(count).fill(0).map((item, i) => ({
|
||||||
|
key: (i + 1).toString(),
|
||||||
|
tm: `${i + 1}小时`,
|
||||||
|
drp: '',
|
||||||
|
rz: '',
|
||||||
|
}))
|
||||||
|
|
||||||
|
setDataSource([...newArr]);
|
||||||
|
setEditData([...newArr])
|
||||||
|
};
|
||||||
|
const handleSave = (row) => {
|
||||||
|
const newData = [...dataSource];
|
||||||
|
const index = newData.findIndex((item) => row.key === item.key);
|
||||||
|
const item = newData[index];
|
||||||
|
newData.splice(index, 1, {
|
||||||
|
...item,
|
||||||
|
...row,
|
||||||
|
});
|
||||||
|
console.log('newData', newData);
|
||||||
|
setDataSource(newData);
|
||||||
|
setEditData(newData)
|
||||||
|
};
|
||||||
|
const components = {
|
||||||
|
body: {
|
||||||
|
row: EditableRow,
|
||||||
|
cell: EditableCell,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const columns = defaultColumns.map((col) => {
|
||||||
|
if (!col.editable) {
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...col,
|
||||||
|
onCell: (record) => ({
|
||||||
|
record,
|
||||||
|
editable: col.editable,
|
||||||
|
dataIndex: col.dataIndex,
|
||||||
|
title: col.title,
|
||||||
|
handleSave,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (count) {
|
||||||
|
handleAdd(count)
|
||||||
|
}
|
||||||
|
}, [count])
|
||||||
|
useEffect(() => {
|
||||||
|
if (tableData.length > 0) {
|
||||||
|
setDataSource(tableData)
|
||||||
|
setEditData(tableData)
|
||||||
|
}
|
||||||
|
}, [tableData])
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
components={components}
|
||||||
|
rowClassName={() => 'editable-row'}
|
||||||
|
bordered
|
||||||
|
dataSource={dataSource}
|
||||||
|
columns={columns}
|
||||||
|
scroll={{ x: 200, y: 'calc( 100vh - 400px )' }}
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default App;
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import moment, { min } from 'moment';
|
||||||
|
|
||||||
|
export default function drpOption(predict = [], history = [], type) {
|
||||||
|
console.log("predict",predict[0]);
|
||||||
|
|
||||||
|
const LimitWater = type == 'cq' ? 112 :
|
||||||
|
type == 'sxs' ? 72.01 :
|
||||||
|
type == 'wpc' ? 112 : 0;
|
||||||
|
// 水位
|
||||||
|
const minRz = Math.floor(Math.min(...history.map(item => item.waters),LimitWater, ...predict.map(item => item.predict)))
|
||||||
|
const maxRz = Math.ceil(Math.max(...history.map(item => item.waters), LimitWater,...predict.map(item => item.predict)))
|
||||||
|
// 雨量
|
||||||
|
const mindrp = Math.floor(Math.min(...history.map(item => item.rains)))
|
||||||
|
const maxdrp = Math.ceil(Math.max(...history.map(item => item.rains)))
|
||||||
|
|
||||||
|
// // 水位
|
||||||
|
// const minRz = Math.floor(Math.min(...data.map(item => item.water),...data.map(item => item.predict)))
|
||||||
|
// const maxRz = Math.ceil(Math.max(...data.map(item => item.water),...data.map(item => item.predict)))
|
||||||
|
// // 雨量
|
||||||
|
// const mindrp = Math.floor(Math.min(...data.map(item => item.rain)))
|
||||||
|
// const maxdrp = Math.ceil(Math.max(...data.map(item => item.rain)))
|
||||||
|
console.log(minRz, maxRz);
|
||||||
|
const seen = new Map();
|
||||||
|
const limitData = [...history, ...predict].filter(item => {
|
||||||
|
const time = item.tm;
|
||||||
|
if (!time) return true;
|
||||||
|
const timeStr = moment(time).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
||||||
|
if (seen.has(timeStr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seen.set(timeStr, true);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isShowSymbol = predict.length == 1 ? true : false;
|
||||||
|
return {
|
||||||
|
grid: {
|
||||||
|
left: 0,
|
||||||
|
top: "20%",
|
||||||
|
right: "3%",
|
||||||
|
bottom: "10%",
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
right: "center",
|
||||||
|
top: "4%",
|
||||||
|
},
|
||||||
|
toolbox: {
|
||||||
|
show: true,
|
||||||
|
top: "15%",
|
||||||
|
right: "0%",
|
||||||
|
orient: 'vertical',
|
||||||
|
feature: {
|
||||||
|
dataZoom: {
|
||||||
|
show: true,
|
||||||
|
title: {
|
||||||
|
zoom: '区域缩放',
|
||||||
|
back: '区域缩放还原',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
magicType: {
|
||||||
|
show: true,
|
||||||
|
title: {
|
||||||
|
line: '切换为折线图',
|
||||||
|
bar: '切换为柱状图',
|
||||||
|
},
|
||||||
|
type: ['line', 'bar']
|
||||||
|
},
|
||||||
|
restore: {
|
||||||
|
show: true,
|
||||||
|
title: '还原'
|
||||||
|
},
|
||||||
|
saveAsImage: {
|
||||||
|
show: true,
|
||||||
|
name: '日到报率详情',
|
||||||
|
title: '保存为图片'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: "category",
|
||||||
|
// data: history.map(item => item.tm),
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: "value",
|
||||||
|
name: '水位',
|
||||||
|
min: minRz,
|
||||||
|
max: maxRz,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: "#7a869a",
|
||||||
|
formatter: '{value} m'
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "value",
|
||||||
|
name: '雨量',
|
||||||
|
min: mindrp,
|
||||||
|
max: maxdrp,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: "#7a869a",
|
||||||
|
formatter: '{value} mm'
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataZoom: [{
|
||||||
|
type: 'inside',
|
||||||
|
start: (580 / history.length) * 100,
|
||||||
|
// start:0,
|
||||||
|
end: 100
|
||||||
|
}, {
|
||||||
|
start: 0,
|
||||||
|
end: 10,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "实测雨量",
|
||||||
|
type: "bar",
|
||||||
|
barWidth:3,
|
||||||
|
color: '#F59A23',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
showSymbol: false,
|
||||||
|
data: history.map(item => [item.tm, item.rains]),
|
||||||
|
smooth: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "实测水位",
|
||||||
|
type: "line",
|
||||||
|
showSymbol: false,
|
||||||
|
data: history.map(item => [item.tm, item.waters]),
|
||||||
|
smooth: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "预测水位",
|
||||||
|
type: "line",
|
||||||
|
color: '#1fbcd2',
|
||||||
|
lineStyle: {
|
||||||
|
width: 4 // 设置线的宽度为4px
|
||||||
|
},
|
||||||
|
yAxisIndex: 0,
|
||||||
|
showSymbol: isShowSymbol,
|
||||||
|
// symbolSize: 8,
|
||||||
|
itemStyle: {
|
||||||
|
borderWidth: 5
|
||||||
|
},
|
||||||
|
data: predict.map(item => [item.tm, item.predict]),
|
||||||
|
smooth: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: LimitWater != 0 ? "汛限水位" :'',
|
||||||
|
type: "line",
|
||||||
|
yAxisIndex: 0,
|
||||||
|
color:"#85ea2d",
|
||||||
|
showSymbol: false,
|
||||||
|
data:LimitWater != 0 ? limitData.map(item => [item.tm, LimitWater]) : [],
|
||||||
|
smooth: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// name:'截断',
|
||||||
|
type: "bar",
|
||||||
|
// color: '#F59A23',
|
||||||
|
barWidth:3,
|
||||||
|
yAxisIndex: 1,
|
||||||
|
showSymbol: false,
|
||||||
|
data: [[predict[0]?.tm, maxdrp]],
|
||||||
|
tooltip: {
|
||||||
|
show:false
|
||||||
|
},
|
||||||
|
smooth: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,299 @@
|
||||||
|
import React, { Fragment, useRef, useMemo, useEffect, useState } from 'react';
|
||||||
|
import { Table, Card, Modal, Form, Spin, Button, Row, Col, Typography, message, Tabs, Image, InputNumber, Descriptions } from 'antd';
|
||||||
|
import ToolBar from './toolbar'
|
||||||
|
import ReactEcharts from 'echarts-for-react';
|
||||||
|
import './index.less'
|
||||||
|
import drpOption from './drpOption.js'
|
||||||
|
import { httppost2 } from '../../utils/request';
|
||||||
|
import TestApp from './createData.js'
|
||||||
|
import { getAllHydroBatches, responseData } from './watersTools'
|
||||||
|
import apiurl from '../../service/apiurl';
|
||||||
|
import moment from 'moment';
|
||||||
|
export default function TestLine() {
|
||||||
|
const obj = {
|
||||||
|
'hjw': '60906600',
|
||||||
|
'sxs': '60917600',
|
||||||
|
'szl': '60918000',
|
||||||
|
"wpc": 'ZH201606',
|
||||||
|
'cq':'61013270'
|
||||||
|
}
|
||||||
|
const [searchVal, setSearchVal] = useState(false)
|
||||||
|
const [historyData, setHistoryData] = useState([])
|
||||||
|
const [predictData, setPredictData] = useState([])
|
||||||
|
const [tableList, setTableList] = useState([])
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const options = useMemo(() => {
|
||||||
|
if (searchVal.code) {
|
||||||
|
return drpOption(predictData, historyData,searchVal.code)
|
||||||
|
}
|
||||||
|
}, [predictData, historyData,searchVal])
|
||||||
|
|
||||||
|
// const options = useMemo(() => {
|
||||||
|
// return drpOption(tableList)
|
||||||
|
// }, [tableList])
|
||||||
|
const handleEmptyValue = (value) => {
|
||||||
|
if (value === null || value === undefined || Number.isNaN(value)) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 测试
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '预测时间点',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
key: 'tm',
|
||||||
|
width: 175,
|
||||||
|
align: 'center',
|
||||||
|
fixed:'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '实测雨量(mm)',
|
||||||
|
dataIndex: 'rain',
|
||||||
|
key: 'rain',
|
||||||
|
width: 100,
|
||||||
|
align: 'center',
|
||||||
|
render: (v) => <span>{ handleEmptyValue(v)}</span>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '实测水位(m)',
|
||||||
|
dataIndex: 'water',
|
||||||
|
key: 'water',
|
||||||
|
width: 100,
|
||||||
|
align: 'center',
|
||||||
|
render: (v) => <span>{ handleEmptyValue(v)}</span>
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '预测水位(m)',
|
||||||
|
dataIndex: 'predict',
|
||||||
|
key: 'predict',
|
||||||
|
width: 100,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '差值(m)',
|
||||||
|
dataIndex: 'cz',
|
||||||
|
key: 'cz',
|
||||||
|
width: 90,
|
||||||
|
align: 'center',
|
||||||
|
render: (v, r) => <span>{(r.predict && r.water) ? Math.abs((r.predict - r.water)).toFixed(2) : '-'}</span>
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const getHistoryData = async (params) => {
|
||||||
|
setLoading(true)
|
||||||
|
params.stcd = obj[params.code];
|
||||||
|
try {
|
||||||
|
const result = await httppost2(apiurl.test.find1, params);
|
||||||
|
if (result.code == 200) {
|
||||||
|
const responseData = result.data.map(item => ({ ...item, tm: moment(item.tm).format('YYYY-MM-DD HH:00:00') }))
|
||||||
|
if (!responseData.length) {
|
||||||
|
setHistoryData(responseData)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setHistoryData(responseData)
|
||||||
|
const allBatches = getAllHydroBatches(responseData);
|
||||||
|
const res = await processPredictions(allBatches, params.code)
|
||||||
|
if (res.length > 0) {
|
||||||
|
setLoading(false)
|
||||||
|
setPredictData(res.map(item => ({ ...item, predict: item.predict.toFixed(2) })))
|
||||||
|
const tableData = res.map(item => {
|
||||||
|
const obj = responseData.find(it => it.tm == item.tm)
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
predict: item.predict ? item.predict.toFixed(2) : '',
|
||||||
|
water: obj?.waters,
|
||||||
|
rain:obj?.rains
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setTableList(tableData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const replaceLastItem = (arr, newValue) => {
|
||||||
|
if (!Array.isArray(arr) || arr.length === 0) {
|
||||||
|
return [newValue];
|
||||||
|
}
|
||||||
|
const newArr = [...arr];
|
||||||
|
newArr[newArr.length - 1] = newValue;
|
||||||
|
return newArr;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 处理预测结果
|
||||||
|
* @param {Array} batches - 批次数据
|
||||||
|
* @param {Array} predictions - 预测结果数组
|
||||||
|
* @returns {Array} - 返回处理后的预测结果
|
||||||
|
*/ const processPredictions = async (batches, name) => {
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const batch of batches) {
|
||||||
|
const prediction = await httppost2('http://202.96.165.23:10100/api/v1/bot/water_infer', {
|
||||||
|
rains: replaceLastItem(batch.rains,searchVal.predictRain),
|
||||||
|
waters: batch.waters,
|
||||||
|
name,
|
||||||
|
tn:searchVal.time
|
||||||
|
});
|
||||||
|
prediction?.data?.water_predicts.forEach((item,i) => {
|
||||||
|
results.push({
|
||||||
|
predict: item, // 预测水位
|
||||||
|
tm: moment(batch.lastTm).clone().add(i, 'hours').format("YYYY-MM-DD HH:00:00") // 对应的时间点
|
||||||
|
});
|
||||||
|
})
|
||||||
|
// results.push({
|
||||||
|
// predict: prediction?.data?.water_predicts[0], // 预测水位
|
||||||
|
// tm: batch.lastTm // 对应的时间点
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findMinMaxRain = (arr) => {
|
||||||
|
|
||||||
|
if (arr.length === 0) {
|
||||||
|
return { min: null, max: null }; // 如果数组为空,返回 null
|
||||||
|
}
|
||||||
|
|
||||||
|
let min = arr[0]; // 初始化最小值为第一个元素
|
||||||
|
let max = arr[0]; // 初始化最大值为第一个元素
|
||||||
|
|
||||||
|
for (let i = 1; i < arr.length; i++) {
|
||||||
|
if (arr[i].diff < min.diff) {
|
||||||
|
min = arr[i]; // 更新最小值
|
||||||
|
}
|
||||||
|
if (arr[i].diff > max.diff) {
|
||||||
|
max = arr[i]; // 更新最大值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { min, max };
|
||||||
|
}
|
||||||
|
const summaryVal = useMemo(() => {
|
||||||
|
if (tableList.length > 0) {
|
||||||
|
const sum = tableList.reduce((total, cur) => total + Math.abs((cur.predict - cur.water)), 0);
|
||||||
|
const newArr = JSON.parse(JSON.stringify(tableList));
|
||||||
|
const resultMaxOrMin = findMinMaxRain(newArr.map(item => ({ ...item, diff: Math.abs((item.predict - item.water)).toFixed(2) })))
|
||||||
|
return {
|
||||||
|
avergVal: (sum / tableList.length).toFixed(2),
|
||||||
|
maxVal: resultMaxOrMin?.max,
|
||||||
|
minVal: resultMaxOrMin?.min,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},[tableList])
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchVal) {
|
||||||
|
getHistoryData(searchVal)
|
||||||
|
}
|
||||||
|
}, [searchVal])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='content-root clearFloat xybm' style={{ paddingBottom: "0" }}>
|
||||||
|
<div className='lf' style={{ width: "100%", overflowY: "auto" }}>
|
||||||
|
<Card className='nonebox'>
|
||||||
|
<ToolBar
|
||||||
|
setSearchVal={setSearchVal}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
{
|
||||||
|
historyData.length ? !loading ?
|
||||||
|
<div className="ant-card-body" style={{ padding: "20px 0 0 0"}}>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', }}>
|
||||||
|
<div style={{ flex: 1, height: 'calc( 100vh - 400px )', padding: 10 }}>
|
||||||
|
<ReactEcharts option={options} style={{ width: "100%", height: '100%' }} notMerge={true} />
|
||||||
|
</div>
|
||||||
|
<div style={{ width: 600, marginRight: 30 }}>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={tableList}
|
||||||
|
rowKey='tm'
|
||||||
|
pagination={true}
|
||||||
|
scroll={{ x: 600, y: 'calc(100vh - 300px)' }}
|
||||||
|
summary={() => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Table.Summary.Row>
|
||||||
|
<Table.Summary.Cell index={12} align='center' colSpan={1} >差值最大值时间点</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={1} align='center' colSpan={2}>{summaryVal?.maxVal?.tm || '-'}</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={2} align='center' colSpan={1}>差值最大值</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={3} align='center' colSpan={1}>{summaryVal?.maxVal?.diff !='NaN'?summaryVal?.maxVal?.diff:"-"}</Table.Summary.Cell>
|
||||||
|
</Table.Summary.Row>
|
||||||
|
{/* <Table.Summary.Row>
|
||||||
|
<Table.Summary.Cell index={4} align='center' colSpan={1} >差值最小值时间点</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={5} align='center' colSpan={2}>{summaryVal?.minVal?.tm}</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={6} align='center' colSpan={1}>差值最小值</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={7} align='center' colSpan={1}>{summaryVal?.minVal?.diff}</Table.Summary.Cell>
|
||||||
|
</Table.Summary.Row> */}
|
||||||
|
<Table.Summary.Row>
|
||||||
|
<Table.Summary.Cell index={4} align='center' colSpan={1} >差值平均值</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={5} align='center' colSpan={4}>{summaryVal?.avergVal!='NaN'?summaryVal?.avergVal: '-'}</Table.Summary.Cell>
|
||||||
|
</Table.Summary.Row>
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> : <div style={{ width: '100%', height: 'calc(100vh - 60px)', display: 'flex', justifyContent: 'center', alignItems: 'center' }}><Spin size="large" /></div> :
|
||||||
|
<div style={{ width: '100%', height: 'calc(100vh - 60px)', display: 'flex', justifyContent: 'center', alignItems: 'center' }}> <img alt='' src={`${process.env.PUBLIC_URL}/assets/noData.png`} /></div>}
|
||||||
|
|
||||||
|
|
||||||
|
{/* {!loading?
|
||||||
|
<div className="ant-card-body" style={{ padding: "20px 0 0 0", height: 'calc( 100vh - 400px )' }}>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', }}>
|
||||||
|
<div style={{ flex: 1, height: 'calc( 100vh - 400px )', padding: 10 }}>
|
||||||
|
<ReactEcharts option={options} style={{ width: "100%", height: '100%' }} notMerge={true} />
|
||||||
|
</div>
|
||||||
|
<div style={{ width: 600, marginRight: 30 }}>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={tableList}
|
||||||
|
rowKey='tm'
|
||||||
|
pagination={true}
|
||||||
|
scroll={{x:600,y:'calc(100vh - 300px)'}}
|
||||||
|
summary={() => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Table.Summary.Row>
|
||||||
|
<Table.Summary.Cell index={12} align='center'colSpan={1} >差值最大值时间点</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={1} align='center' colSpan={2}>{summaryVal?.maxVal?.tm}</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={2} align='center' colSpan={1}>差值最大值</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={3} align='center' colSpan={1}>{summaryVal?.maxVal?.diff}</Table.Summary.Cell>
|
||||||
|
</Table.Summary.Row>
|
||||||
|
<Table.Summary.Row>
|
||||||
|
<Table.Summary.Cell index={4} align='center'colSpan={1} >差值最小值时间点</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={5} align='center' colSpan={2}>{summaryVal?.minVal?.tm}</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={6} align='center' colSpan={1}>差值最小值</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={7} align='center' colSpan={1}>{summaryVal?.minVal?.diff}</Table.Summary.Cell>
|
||||||
|
</Table.Summary.Row>
|
||||||
|
<Table.Summary.Row>
|
||||||
|
<Table.Summary.Cell index={4} align='center'colSpan={1} >差值平均值</Table.Summary.Cell>
|
||||||
|
<Table.Summary.Cell index={5} align='center' colSpan={4}>{summaryVal?.avergVal}</Table.Summary.Cell>
|
||||||
|
</Table.Summary.Row>
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> : <div style={{ width: '100%', height: 'calc(100vh - 60px)', display: 'flex', justifyContent: 'center', alignItems: 'center' }}><Spin size="large" /></div>
|
||||||
|
} */}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
.line-box{
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.ant-table-summary {
|
||||||
|
background-color: #f2f9fd !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Form, Input, Button, DatePicker, InputNumber, message } from 'antd';
|
||||||
|
import NormalSelect from '../../components/Form/NormalSelect';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import moment from 'moment';
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
const ToolBar = ({ setSearchVal, setType, save, form1 }) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const types = [
|
||||||
|
// {
|
||||||
|
// label: "黄家湾水库",
|
||||||
|
// value: "hjw"
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
label: "车桥水库",
|
||||||
|
value: "cq"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "三星寺水库",
|
||||||
|
value: "sxs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "石子岭水库",
|
||||||
|
value: "szl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "乌盆冲",
|
||||||
|
value: "wpc"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const timeType = [
|
||||||
|
{
|
||||||
|
label: "1h",
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "3h",
|
||||||
|
value: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "6h",
|
||||||
|
value: 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "12h",
|
||||||
|
value: 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "24h",
|
||||||
|
value: 24
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const onFinish = (values) => {
|
||||||
|
let dateTimeRangeSo;
|
||||||
|
if (values.tm) {
|
||||||
|
dateTimeRangeSo = {
|
||||||
|
// start: moment(values.tm[0]).format('YYYY-MM-DD HH:00:00'),
|
||||||
|
end: moment(values.tm).format('YYYY-MM-DD HH:00:00')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.error('请选择时间范围')
|
||||||
|
}
|
||||||
|
delete values.tm
|
||||||
|
setSearchVal({...values,etm: dateTimeRangeSo.end});
|
||||||
|
}
|
||||||
|
const disabledFutureDate = (current) => {
|
||||||
|
const maxDate = moment('2025-02-28');
|
||||||
|
return current && (current > maxDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
const jump = () => {
|
||||||
|
navigate('/');
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
const stm = moment('2024-01-01').format('YYYY-MM-DD 00:00:00')
|
||||||
|
const etm = moment('2024-07-28 11:00:00').format('YYYY-MM-DD 00:00:00')
|
||||||
|
const params = {
|
||||||
|
code: "cq",
|
||||||
|
time: 24,
|
||||||
|
predictRain:11,
|
||||||
|
// stm,
|
||||||
|
etm
|
||||||
|
}
|
||||||
|
form.setFieldValue('code', params.code);
|
||||||
|
form.setFieldValue('tm',moment('2024-07-28 11:00:00'));
|
||||||
|
form.setFieldValue('time', params.time);
|
||||||
|
form.setFieldValue('predictRain', params.predictRain);
|
||||||
|
setSearchVal(params)
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<Form form={form} className='toolbarBox' layout="inline" onFinish={onFinish} >
|
||||||
|
<Form.Item label="水库" name="code">
|
||||||
|
<NormalSelect allowClear style={{ width: '150px' }} options={types} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="预测时段" name="time">
|
||||||
|
<NormalSelect allowClear style={{ width: '150px' }} options={timeType} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="预测降雨量" name="predictRain">
|
||||||
|
<InputNumber min={0} style={{ width: '150px' }}/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="时间" name="tm"
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
showTime
|
||||||
|
disabledDate={disabledFutureDate}
|
||||||
|
style={{ width: "250px" }}
|
||||||
|
format="YYYY-MM-DD HH:00:00"
|
||||||
|
// allowClear={false}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit">查询</Button>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" onClick={jump}>时段预测</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToolBar;
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import moment from "moment";
|
||||||
|
/**
|
||||||
|
* 使用滑动窗口获取水文数据批次
|
||||||
|
* @param {Array} data - 后端返回的数据数组
|
||||||
|
* @returns {Array} - 返回数组,每个元素包含600个点的数据
|
||||||
|
*/
|
||||||
|
export const getAllHydroBatches = (data) => {
|
||||||
|
if (!Array.isArray(data) || data.length < 600) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const batches = [];
|
||||||
|
let startIndex = 0;
|
||||||
|
|
||||||
|
// 当剩余数据量大于等于600时继续处理
|
||||||
|
while (startIndex + 600 <= 600) {
|
||||||
|
const slicedData = data.slice(startIndex, startIndex + 600);
|
||||||
|
batches.push({
|
||||||
|
rains: slicedData.map(item => item.rains),
|
||||||
|
waters: slicedData.map(item => item.waters),
|
||||||
|
// lastTm: slicedData[599].tm // 记录第600个点的时间
|
||||||
|
lastTm:moment(slicedData[599].tm).clone().add(1, 'hours').format("YYYY-MM-DD HH:00:00")
|
||||||
|
});
|
||||||
|
|
||||||
|
// 每次向后移动1个位置
|
||||||
|
startIndex += 1;
|
||||||
|
}
|
||||||
|
return batches;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const responseData = new Array(800).fill(0).map((item,index) => ({
|
||||||
|
rains: (Math.random() * 1).toFixed(2),
|
||||||
|
waters: (Math.random() * 100).toFixed(2),
|
||||||
|
tm:moment().clone().add(index, 'hours').format("YYYY-MM-DD HH:00:00")
|
||||||
|
}))
|
||||||
Loading…
Reference in New Issue