diff --git a/src/service/apiurl.js b/src/service/apiurl.js index 79606fb..6eb2a34 100644 --- a/src/service/apiurl.js +++ b/src/service/apiurl.js @@ -7,7 +7,8 @@ const service_test = '/gunshiApp/dcpj' const apiurl = { test: { - find: service_test + '/stPRHisData/getHisData' + find: service_test + '/stPRHisData/getHisData', + find1: service_test + '/stPRHisData/getHisDataV2' }, setMenu: service_fxdd + '/visitMenuLog/insert', setPassword: service_fxdd + '/user/updateSecretKey', diff --git a/src/views/AppRouters.tsx b/src/views/AppRouters.tsx index 3ff22ba..1d354a8 100644 --- a/src/views/AppRouters.tsx +++ b/src/views/AppRouters.tsx @@ -24,6 +24,7 @@ import Dept from './SystemMangant/dept' import Role from './SystemMangant/role' import MenuM from './SystemMangant/menuM' import TestLine from './TestLine' +import TestLine2 from './TestLine2' // const HomePage = lazy(() => import('./Home')) @@ -41,6 +42,7 @@ const AppRouters: React.FC = () => { let element = useRoutes([ // { path: '/', element: }, { path: '/', element: }, + { path: '/test2', element: }, { path: '/home', element: }, { path: '/mgr', @@ -74,6 +76,7 @@ const AppRouters: React.FC = () => { // 测试曲线 { path: 'testLine/testLine', element: }, + { path: 'testLine/testLine2', element: }, ], }, diff --git a/src/views/TestLine/drpOption.js b/src/views/TestLine/drpOption.js index 558ea1e..77300b8 100644 --- a/src/views/TestLine/drpOption.js +++ b/src/views/TestLine/drpOption.js @@ -129,7 +129,7 @@ export default function drpOption(predict=[],history=[],type) { }], series: [ { - name: "实时雨量", + name: "实测雨量", type: "bar", color: '#F59A23', yAxisIndex: 1, @@ -138,7 +138,7 @@ export default function drpOption(predict=[],history=[],type) { smooth: true, }, { - name: "实时水位", + name: "实测水位", type: "line", showSymbol: false, data: history.map(item => [item.tm, item.waters]), diff --git a/src/views/TestLine/index.js b/src/views/TestLine/index.js index 5a1f279..c8c1154 100644 --- a/src/views/TestLine/index.js +++ b/src/views/TestLine/index.js @@ -44,14 +44,14 @@ export default function TestLine() { fixed:'left' }, { - title: '实际雨量(mm)', + title: '实测雨量(mm)', dataIndex: 'rain', key: 'rain', width: 100, align: 'center' }, { - title: '实际水位(m)', + title: '实测水位(m)', dataIndex: 'water', key: 'water', width: 100, diff --git a/src/views/TestLine/toolbar.js b/src/views/TestLine/toolbar.js index 9d840df..2e549c1 100644 --- a/src/views/TestLine/toolbar.js +++ b/src/views/TestLine/toolbar.js @@ -1,10 +1,11 @@ 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 = [ // { @@ -135,17 +136,19 @@ const ToolBar = ({ setSearchVal, setType, save, form1 }) => { // form1.resetFields() } } + const jump = () => { + navigate('/test2'); + } useEffect(() => { - const stm = moment('2024-01-01').format('YYYY-MM-DD 00:00:00') - const etm = moment('2024-01-10').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-07-31 16:00:00').format('YYYY-MM-DD 00:00:00') const params = { code: "cq", - time: '1h', stm, etm } 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) }, []) @@ -191,6 +194,9 @@ const ToolBar = ({ setSearchVal, setType, save, form1 }) => { + + + diff --git a/src/views/TestLine2/createData.js b/src/views/TestLine2/createData.js new file mode 100644 index 0000000..1249eb9 --- /dev/null +++ b/src/views/TestLine2/createData.js @@ -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 ( +
+ + + +
+ ); +}; +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 ? ( + + + + ) : ( +
+ {children} +
+ ); + } + return toggleEdit(title)}>{childNode}; +}; +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 ( +
+ 'editable-row'} + bordered + dataSource={dataSource} + columns={columns} + scroll={{ x: 200, y: 'calc( 100vh - 400px )' }} + pagination={false} + /> + + ); +}; +export default App; \ No newline at end of file diff --git a/src/views/TestLine2/drpOption.js b/src/views/TestLine2/drpOption.js new file mode 100644 index 0000000..5d3bad3 --- /dev/null +++ b/src/views/TestLine2/drpOption.js @@ -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, + }, + ], + } +} \ No newline at end of file diff --git a/src/views/TestLine2/index.js b/src/views/TestLine2/index.js new file mode 100644 index 0000000..2791f8d --- /dev/null +++ b/src/views/TestLine2/index.js @@ -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) => { handleEmptyValue(v)} + }, + { + title: '实测水位(m)', + dataIndex: 'water', + key: 'water', + width: 100, + align: 'center', + render: (v) => { handleEmptyValue(v)} + + }, + { + title: '预测水位(m)', + dataIndex: 'predict', + key: 'predict', + width: 100, + align: 'center' + }, + { + title: '差值(m)', + dataIndex: 'cz', + key: 'cz', + width: 90, + align: 'center', + render: (v, r) => {(r.predict && r.water) ? Math.abs((r.predict - r.water)).toFixed(2) : '-'} + }, + ] + 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 ( + <> +
+
+ + + + { + historyData.length ? !loading ? +
+ +
+
+ +
+
+
{ + return ( + <> + + 差值最大值时间点 + {summaryVal?.maxVal?.tm || '-'} + 差值最大值 + {summaryVal?.maxVal?.diff !='NaN'?summaryVal?.maxVal?.diff:"-"} + + {/* + 差值最小值时间点 + {summaryVal?.minVal?.tm} + 差值最小值 + {summaryVal?.minVal?.diff} + */} + + 差值平均值 + {summaryVal?.avergVal!='NaN'?summaryVal?.avergVal: '-'} + + + + ) + }} + /> + + + :
: +
} + + + {/* {!loading? +
+ +
+
+ +
+
+
{ + return ( + <> + + 差值最大值时间点 + {summaryVal?.maxVal?.tm} + 差值最大值 + {summaryVal?.maxVal?.diff} + + + 差值最小值时间点 + {summaryVal?.minVal?.tm} + 差值最小值 + {summaryVal?.minVal?.diff} + + + 差值平均值 + {summaryVal?.avergVal} + + + + ) + }} + /> + + + :
+ } */} + + + + + ) +} diff --git a/src/views/TestLine2/index.less b/src/views/TestLine2/index.less new file mode 100644 index 0000000..bc85522 --- /dev/null +++ b/src/views/TestLine2/index.less @@ -0,0 +1,7 @@ +.line-box{ + background-color: #fff; +} +.ant-table-summary { + background-color: #f2f9fd !important; + + } \ No newline at end of file diff --git a/src/views/TestLine2/toolbar.js b/src/views/TestLine2/toolbar.js new file mode 100644 index 0000000..4181cb1 --- /dev/null +++ b/src/views/TestLine2/toolbar.js @@ -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 ( + <> +
+
+ + + + + + + + + + + + + + + + + + + + +
+ + ); +} + +export default ToolBar; \ No newline at end of file diff --git a/src/views/TestLine2/watersTools.js b/src/views/TestLine2/watersTools.js new file mode 100644 index 0000000..7052931 --- /dev/null +++ b/src/views/TestLine2/watersTools.js @@ -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") +})) \ No newline at end of file