|
|
|
|
@ -1,63 +1,61 @@
@@ -1,63 +1,61 @@
|
|
|
|
|
<template> |
|
|
|
|
<div class="app-container app-container-bg"> |
|
|
|
|
<el-card class="first-card" ref='firstCard' shadow="always"> |
|
|
|
|
<el-form :model="queryParams" ref="queryForm" :inline="true" @submit.native.prevent> |
|
|
|
|
<el-card class="first-card" ref="firstCard" shadow="always"> |
|
|
|
|
<el-form :model="queryForm" ref="queryFormRef" :inline="true" @submit.native.prevent> |
|
|
|
|
<el-form-item label="测站选择:"> |
|
|
|
|
<el-select v-model="queryForm.station" placeholder="测站选择" style="width: 240px;" @change="changeStation"> |
|
|
|
|
<el-option v-for="item in stationList" :key="item.stnmId" :label="item.name" :value="item.stnmId" /> |
|
|
|
|
</el-select> |
|
|
|
|
</el-form-item> |
|
|
|
|
<el-form-item label="起止时段:"> |
|
|
|
|
<el-date-picker v-model="queryForm.dateRange" type="datetimerange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd HH:mm:00"> |
|
|
|
|
<el-date-picker v-model="queryForm.dateRange" type="datetimerange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD HH:mm:00"> |
|
|
|
|
</el-date-picker> |
|
|
|
|
</el-form-item> |
|
|
|
|
<el-form-item> |
|
|
|
|
<el-button type="primary" @click="drawChart">查询</el-button> |
|
|
|
|
</el-form-item> |
|
|
|
|
<!-- <el-button type="primary" @click="showSetting = true">设置</el-button> --> |
|
|
|
|
</el-form> |
|
|
|
|
<el-row class="info-bar"> |
|
|
|
|
<el-col :xs="24" :sm="24" :md="10" :lg="10" :xl="10"> <span>起报时间:</span><span>{{qbTime}}</span></el-col> |
|
|
|
|
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6"> <span>预报时段:</span><span>1小时</span></el-col> |
|
|
|
|
<el-col :xs="24" :sm="24" :md="10" :lg="10" :xl="10"> |
|
|
|
|
<span>起报时间:</span><span>{{ qbTime }}</span> |
|
|
|
|
</el-col> |
|
|
|
|
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6"> |
|
|
|
|
<span>预报时段:</span><span>1小时</span> |
|
|
|
|
</el-col> |
|
|
|
|
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8"> |
|
|
|
|
<el-descriptions class="margin-top" :column="2" border> |
|
|
|
|
<el-descriptions-item> |
|
|
|
|
<template slot="label"> |
|
|
|
|
<template #label> |
|
|
|
|
预测最高值 |
|
|
|
|
</template> |
|
|
|
|
{{predict.maxValue}} |
|
|
|
|
{{ predict.maxValue }} |
|
|
|
|
</el-descriptions-item> |
|
|
|
|
<el-descriptions-item> |
|
|
|
|
<template slot="label"> |
|
|
|
|
|
|
|
|
|
<template #label> |
|
|
|
|
预测最低值 |
|
|
|
|
</template> |
|
|
|
|
{{predict.minValue}} |
|
|
|
|
{{ predict.minValue }} |
|
|
|
|
</el-descriptions-item> |
|
|
|
|
<el-descriptions-item> |
|
|
|
|
<template slot="label"> |
|
|
|
|
|
|
|
|
|
<template #label> |
|
|
|
|
时间 |
|
|
|
|
</template> |
|
|
|
|
{{predict.maxTime}} |
|
|
|
|
{{ predict.maxTime }} |
|
|
|
|
</el-descriptions-item> |
|
|
|
|
<el-descriptions-item> |
|
|
|
|
<template slot="label"> |
|
|
|
|
|
|
|
|
|
<template #label> |
|
|
|
|
时间 |
|
|
|
|
</template> |
|
|
|
|
{{predict.minTime}} |
|
|
|
|
{{ predict.minTime }} |
|
|
|
|
</el-descriptions-item> |
|
|
|
|
</el-descriptions> |
|
|
|
|
|
|
|
|
|
</el-col> |
|
|
|
|
</el-row> |
|
|
|
|
</el-card> |
|
|
|
|
<splitpanes class="el-card-p card-shadow carder-border mt10 pad10 default-theme container-box"> |
|
|
|
|
<pane size="30" min-size="30" max-size="30" style="height: 100%;"> |
|
|
|
|
<el-table v-table-height :data="tableData" border v-loading="loading"> |
|
|
|
|
<el-table-column prop="date" label="日期"> |
|
|
|
|
</el-table-column> |
|
|
|
|
<el-table-column prop="date" label="日期"></el-table-column> |
|
|
|
|
<el-table-column prop="rain" label="雨量(mm)"> |
|
|
|
|
<template #default="scope"> |
|
|
|
|
<el-input v-if="isEditing" v-model="scope.row.rain" size="small" /> |
|
|
|
|
@ -73,418 +71,469 @@
@@ -73,418 +71,469 @@
|
|
|
|
|
</el-table> |
|
|
|
|
</pane> |
|
|
|
|
<pane size="70" min-size="70" max-size="70" class="ml10 pad20" v-loading="loading"> |
|
|
|
|
<div v-table-height id="main" style="width: 100%; "></div> |
|
|
|
|
<div v-table-height id="main" style="width: 100%; height: 100%;"></div> |
|
|
|
|
</pane> |
|
|
|
|
</splitpanes> |
|
|
|
|
</div> |
|
|
|
|
</template> |
|
|
|
|
<script> |
|
|
|
|
import * as echarts from "echarts"; |
|
|
|
|
import request from "@/utils/request"; |
|
|
|
|
import { |
|
|
|
|
Splitpanes, |
|
|
|
|
Pane |
|
|
|
|
} from 'splitpanes' |
|
|
|
|
import 'splitpanes/dist/splitpanes.css' |
|
|
|
|
|
|
|
|
|
<script setup name="Flood"> |
|
|
|
|
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue' |
|
|
|
|
import * as echarts from "echarts" |
|
|
|
|
import request from "@/utils/request" |
|
|
|
|
import { Splitpanes, Pane } from 'splitpanes' |
|
|
|
|
import 'splitpanes/dist/splitpanes.css' |
|
|
|
|
import dayjs from 'dayjs' |
|
|
|
|
export default { |
|
|
|
|
name: 'Flood', |
|
|
|
|
components: { |
|
|
|
|
Splitpanes, |
|
|
|
|
Pane |
|
|
|
|
}, |
|
|
|
|
data() { |
|
|
|
|
const endTime = dayjs(new Date()); |
|
|
|
|
const startTime = endTime.subtract(10, 'hour').format('YYYY-MM-DD HH:mm:00'); |
|
|
|
|
const endTimeFormatted = dayjs(new Date()).format('YYYY-MM-DD HH:mm:00'); |
|
|
|
|
|
|
|
|
|
// 响应式数据 |
|
|
|
|
const loading = ref(false) |
|
|
|
|
const showSetting = ref(false) |
|
|
|
|
const tableData = ref([]) |
|
|
|
|
const stationList = ref([]) |
|
|
|
|
const isEditing = ref(false) |
|
|
|
|
const qbTime = ref('') |
|
|
|
|
const chartInstance = ref(null) |
|
|
|
|
const firstCard = ref(null) |
|
|
|
|
const queryFormRef = ref(null) |
|
|
|
|
|
|
|
|
|
// 预测值信息 |
|
|
|
|
const predict = reactive({ |
|
|
|
|
maxValue: '', |
|
|
|
|
minValue: '', |
|
|
|
|
maxTime: '', |
|
|
|
|
minTime: '' |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
// 查询表单 |
|
|
|
|
const endTime = dayjs(new Date()) |
|
|
|
|
const startTime = endTime.subtract(10, 'hour').format('YYYY-MM-DD HH:mm:00') |
|
|
|
|
const endTimeFormatted = endTime.format('YYYY-MM-DD HH:mm:00') |
|
|
|
|
|
|
|
|
|
const queryForm = reactive({ |
|
|
|
|
station: null, |
|
|
|
|
dateRange: [startTime, endTimeFormatted] |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
// 图表数据 |
|
|
|
|
const chartData = ref({}) |
|
|
|
|
|
|
|
|
|
// resize 处理函数引用 |
|
|
|
|
let resizeHandler = null |
|
|
|
|
|
|
|
|
|
// 组件挂载时获取测站列表 |
|
|
|
|
onMounted(() => { |
|
|
|
|
getStationList() |
|
|
|
|
|
|
|
|
|
// 初始化 resize 处理函数 |
|
|
|
|
resizeHandler = () => { |
|
|
|
|
if (chartInstance.value) { |
|
|
|
|
chartInstance.value.resize() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 添加窗口大小改变监听 |
|
|
|
|
window.addEventListener('resize', resizeHandler) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
// 组件卸载时清理资源 |
|
|
|
|
onUnmounted(() => { |
|
|
|
|
// 移除窗口大小改变监听 |
|
|
|
|
if (resizeHandler) { |
|
|
|
|
window.removeEventListener('resize', resizeHandler) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 销毁 echarts 实例 |
|
|
|
|
if (chartInstance.value) { |
|
|
|
|
chartInstance.value.dispose() |
|
|
|
|
chartInstance.value = null |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
// 获取测站选择下拉框数据 |
|
|
|
|
const getStationList = async () => { |
|
|
|
|
try { |
|
|
|
|
const res = await request({ |
|
|
|
|
url: "/history/data/getStationList", |
|
|
|
|
method: 'post', |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
if (res.code == 0) { |
|
|
|
|
stationList.value = res.data |
|
|
|
|
queryForm.station = stationList.value[0].stnmId |
|
|
|
|
drawChart() |
|
|
|
|
} |
|
|
|
|
} catch (error) { |
|
|
|
|
console.error('获取测站列表失败:', error) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 绘制图表 |
|
|
|
|
const drawChart = async () => { |
|
|
|
|
loading.value = true |
|
|
|
|
|
|
|
|
|
const params = { |
|
|
|
|
startTime: queryForm.dateRange[0], |
|
|
|
|
endTime: queryForm.dateRange[1], |
|
|
|
|
stnmId: queryForm.station, |
|
|
|
|
stationType: 'A', |
|
|
|
|
stcd: '' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
const res = await request({ |
|
|
|
|
url: "/history/data/newHistorydata", |
|
|
|
|
method: 'post', |
|
|
|
|
params: params |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
if (res.code == 0) { |
|
|
|
|
chartData.value = res.data |
|
|
|
|
const rainData = res.data.A?.data || [] |
|
|
|
|
const waterData = res.data.C?.data || [] |
|
|
|
|
|
|
|
|
|
if (waterData && waterData.length > 0) { |
|
|
|
|
const actualStartTime = dayjs(waterData[0][0]).format('YYYY-MM-DD HH:mm:ss') |
|
|
|
|
const actualEndTime = dayjs(waterData[waterData.length - 1][0]).format('YYYY-MM-DD HH:mm:ss') |
|
|
|
|
queryForm.dateRange = [actualStartTime, actualEndTime] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const predictData = res.data.Z?.data || [] |
|
|
|
|
|
|
|
|
|
// 表格用的水位数据,去掉最后一个点 |
|
|
|
|
const waterDataForTable = waterData.length > 0 ? waterData.slice(0, -1) : [] |
|
|
|
|
|
|
|
|
|
// 构建表格数据(雨量 + 水位,水位不包含最后一个点) |
|
|
|
|
const tableDataValue = waterDataForTable.map(waterItem => { |
|
|
|
|
const [timestamp, water] = waterItem |
|
|
|
|
const rainItem = rainData.find(r => r[0] === timestamp) |
|
|
|
|
const date = dayjs(timestamp).format('YYYY/MM/DD HH:mm:ss') |
|
|
|
|
return { |
|
|
|
|
date, |
|
|
|
|
rain: rainItem ? rainItem[1]?.toString() : '', |
|
|
|
|
water: water?.toString() |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
tableData.value = tableDataValue |
|
|
|
|
// 使用 nextTick 确保 DOM 更新后再初始化图表 |
|
|
|
|
await nextTick() |
|
|
|
|
initEcharts() // 图表仍使用完整的 waterData 和 predictData |
|
|
|
|
} |
|
|
|
|
} catch (error) { |
|
|
|
|
console.error('获取图表数据失败:', error) |
|
|
|
|
} finally { |
|
|
|
|
loading.value = false |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 初始化图表 |
|
|
|
|
const initEcharts = () => { |
|
|
|
|
// 取出A和V数据 |
|
|
|
|
const rainArr = chartData.value?.A?.data || [] |
|
|
|
|
const waterArr = chartData.value?.C?.data || [] |
|
|
|
|
const predictArr = chartData.value?.Z?.data || [] |
|
|
|
|
|
|
|
|
|
const filteredRain = waterArr |
|
|
|
|
|
|
|
|
|
// 合并数据,去重相同时间点 |
|
|
|
|
let Xdata |
|
|
|
|
if (waterArr.length > 0 && predictArr.length > 0 && waterArr[waterArr.length - 1][0] === predictArr[0][0]) { |
|
|
|
|
Xdata = [...waterArr, ...predictArr.slice(1)] |
|
|
|
|
} else { |
|
|
|
|
Xdata = [...waterArr, ...predictArr] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 生成timeData和rainfallData |
|
|
|
|
const timeData = Xdata.map(item => { |
|
|
|
|
// 时间戳转格式 |
|
|
|
|
return dayjs(item[0]).format('MM月DD日 HH:mm') |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const timeDataLength = timeData.length |
|
|
|
|
const startIndex = Math.max(0, timeDataLength - 12) // 后12条数据的起始索引 |
|
|
|
|
qbTime.value = dayjs(Xdata[startIndex][0]).format('YYYY-MM-DD HH:mm:ss') |
|
|
|
|
|
|
|
|
|
// 雨量 |
|
|
|
|
const rainfallData = filteredRain.map(item => { |
|
|
|
|
const waterItem = rainArr.find(w => w[0] === item[0]) |
|
|
|
|
return waterItem ? waterItem[1] : null |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
// 水位 |
|
|
|
|
const rainData = filteredRain.map(item => { |
|
|
|
|
const waterItem = waterArr.find(w => w[0] === item[0]) |
|
|
|
|
return waterItem ? waterItem[1] : null |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const levelData = rainData |
|
|
|
|
|
|
|
|
|
// 预测水位 |
|
|
|
|
const waterForecast = Xdata.map(item => { |
|
|
|
|
const waterItem = predictArr.find(w => w[0] === item[0]) |
|
|
|
|
return waterItem ? waterItem[1] : null |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
// 获取预测水位的最高值和最低值及对应时间 |
|
|
|
|
const validForecast = waterForecast.map((value, index) => ({ value, index })).filter(item => item.value !== null) |
|
|
|
|
if (validForecast.length > 0) { |
|
|
|
|
const maxItem = validForecast.reduce((max, current) => |
|
|
|
|
(current.value > max.value ? current : max), validForecast[0]) |
|
|
|
|
const minItem = validForecast.reduce((min, current) => |
|
|
|
|
(current.value < min.value ? current : min), validForecast[0]) |
|
|
|
|
|
|
|
|
|
predict.maxValue = maxItem.value |
|
|
|
|
predict.minValue = minItem.value |
|
|
|
|
predict.maxTime = timeData[maxItem.index] |
|
|
|
|
predict.minTime = timeData[minItem.index] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 15条横线颜色,首尾黑色,中间灰色 |
|
|
|
|
const splitLineColors = [ |
|
|
|
|
'#000', '#ccc', '#ccc', '#ccc', '#ccc', |
|
|
|
|
'#ccc', '#ccc', '#ccc', '#ccc', '#ccc', |
|
|
|
|
'#ccc', '#ccc', '#ccc', '#ccc', '#000' |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
const ycColor = '#1EA1CE' |
|
|
|
|
|
|
|
|
|
function getSplitLine(index) { |
|
|
|
|
return { |
|
|
|
|
loading: false, |
|
|
|
|
showSetting: false, |
|
|
|
|
tableData: [], |
|
|
|
|
stationList: [], |
|
|
|
|
queryForm: { |
|
|
|
|
station: null, |
|
|
|
|
dateRange: [startTime, endTimeFormatted] |
|
|
|
|
}, |
|
|
|
|
isEditing: false, |
|
|
|
|
// 起报时间 |
|
|
|
|
qbTime: '', |
|
|
|
|
// 预测值信息 |
|
|
|
|
predict: { |
|
|
|
|
maxValue: '', |
|
|
|
|
minValue: '', |
|
|
|
|
maxTime: '', |
|
|
|
|
minTime: '' |
|
|
|
|
show: true, |
|
|
|
|
lineStyle: { |
|
|
|
|
color: splitLineColors, |
|
|
|
|
width: 1 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
created() { |
|
|
|
|
this.getStationList() |
|
|
|
|
}, |
|
|
|
|
mounted() { |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
methods: { |
|
|
|
|
// 获取测站选择下拉框数据 |
|
|
|
|
async getStationList() { |
|
|
|
|
let res = await request({ |
|
|
|
|
url: "/history/data/getStationList", |
|
|
|
|
method: 'post', |
|
|
|
|
}); |
|
|
|
|
if (res.code == 0) { |
|
|
|
|
let data = res.data; |
|
|
|
|
this.stationList = data; |
|
|
|
|
this.queryForm.station = this.stationList[0].stnmId; |
|
|
|
|
this.drawChart() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 计算y轴的最小值和最大值,用于自动设置刻度 |
|
|
|
|
const yAxisRainMin = Math.min(...rainfallData.filter(v => v !== undefined && v !== null)) |
|
|
|
|
const yAxisRainMax = Math.max(...rainfallData.filter(v => v !== undefined && v !== null)) |
|
|
|
|
const allWaterData = levelData.concat(waterForecast).filter(v => v !== undefined && v !== null) |
|
|
|
|
const yAxisWaterMin = allWaterData.length ? Math.min(...allWaterData) : 0 |
|
|
|
|
const yAxisWaterMax = allWaterData.length ? Math.max(...allWaterData) : 10 |
|
|
|
|
|
|
|
|
|
const option = { |
|
|
|
|
backgroundColor: '#fff', |
|
|
|
|
tooltip: { |
|
|
|
|
trigger: 'axis', |
|
|
|
|
axisPointer: { type: 'cross' }, |
|
|
|
|
// 在 tooltip.formatter 函数中替换以下代码 |
|
|
|
|
formatter: function (params) { |
|
|
|
|
let dataIndex = params[0].dataIndex |
|
|
|
|
let html = `<div><strong>${timeData[dataIndex]}</strong></div>` |
|
|
|
|
|
|
|
|
|
// 检查是否为预测数据区间 |
|
|
|
|
const isForecast = dataIndex >= filteredRain.length |
|
|
|
|
|
|
|
|
|
// 显示雨量(仅在非预测时段或有雨量数据时显示) |
|
|
|
|
if (!isForecast || rainfallData[dataIndex] !== undefined) { |
|
|
|
|
html += `<div style="margin-top:4px;"> |
|
|
|
|
<span style="display:inline-block;width:10px;height:10px;background:#1075FD;margin-right:4px;"></span> |
|
|
|
|
雨量: ${rainfallData[dataIndex] ?? '-'} mm |
|
|
|
|
</div>` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 显示实际水位(仅在非预测时段显示) |
|
|
|
|
if (!isForecast) { |
|
|
|
|
html += `<div> |
|
|
|
|
<span style="display:inline-block;width:10px;height:10px;background:#EE6666;margin-right:4px;"></span> |
|
|
|
|
水位: ${levelData[dataIndex] ?? '-'} m |
|
|
|
|
</div>` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 显示预测水位(在预测时段显示) |
|
|
|
|
if (isForecast || waterForecast[dataIndex] !== null) { |
|
|
|
|
html += `<div> |
|
|
|
|
<span style="display:inline-block;width:10px;height:10px;background:#6f46fd;margin-right:4px;"></span> |
|
|
|
|
预测水位: ${waterForecast[dataIndex] ?? '-'} m |
|
|
|
|
</div>` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return html |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
async drawChart() { |
|
|
|
|
this.loading = true |
|
|
|
|
let params = { |
|
|
|
|
startTime: this.queryForm.dateRange[0], |
|
|
|
|
endTime: this.queryForm.dateRange[1], |
|
|
|
|
stnmId: this.queryForm.station, |
|
|
|
|
stationType: 'A', |
|
|
|
|
stcd: '' |
|
|
|
|
} |
|
|
|
|
let res = await request({ |
|
|
|
|
url: "/history/data/newHistorydata", |
|
|
|
|
method: 'post', |
|
|
|
|
params: params |
|
|
|
|
}); |
|
|
|
|
if (res.code == 0) { |
|
|
|
|
this.chartData = res.data; |
|
|
|
|
let rainData = res.data.A.data || []; |
|
|
|
|
let waterData = res.data.C.data || []; |
|
|
|
|
if (waterData && waterData.length > 0) { |
|
|
|
|
const actualStartTime = dayjs(waterData[0][0]).format('YYYY-MM-DD HH:mm:ss'); |
|
|
|
|
const actualEndTime = dayjs(waterData[waterData.length - 1][0]).format('YYYY-MM-DD HH:mm:ss'); |
|
|
|
|
this.queryForm.dateRange = [actualStartTime, actualEndTime] |
|
|
|
|
legend: { |
|
|
|
|
data: ['雨量', '水位', '预测水位'], |
|
|
|
|
top: 20 |
|
|
|
|
}, |
|
|
|
|
toolbox: { |
|
|
|
|
show: true, |
|
|
|
|
feature: { |
|
|
|
|
saveAsImage: { show: true, title: '下载图片' }, |
|
|
|
|
}, |
|
|
|
|
right: 30, |
|
|
|
|
top: 20 |
|
|
|
|
}, |
|
|
|
|
grid: { |
|
|
|
|
left: 60, |
|
|
|
|
right: 60, |
|
|
|
|
top: '15%', |
|
|
|
|
height: '60%' // 调整高度以适应dataZoom |
|
|
|
|
}, |
|
|
|
|
xAxis: { |
|
|
|
|
type: 'category', |
|
|
|
|
data: timeData, |
|
|
|
|
axisLabel: { |
|
|
|
|
rotate: 0, |
|
|
|
|
formatter: function (value) { |
|
|
|
|
const [date, time] = value.split(' ') |
|
|
|
|
return `${time}\n${date}` |
|
|
|
|
} |
|
|
|
|
let predictData = res.data.Z.data || []; |
|
|
|
|
|
|
|
|
|
// 👇 关键:表格用的水位数据,去掉最后一个点 |
|
|
|
|
const waterDataForTable = waterData.length > 0 ? waterData.slice(0, -1) : []; |
|
|
|
|
|
|
|
|
|
// 构建表格数据(雨量 + 水位,水位不包含最后一个点) |
|
|
|
|
const tableData = waterDataForTable.map(waterItem => { |
|
|
|
|
const [timestamp, water] = waterItem; |
|
|
|
|
const rainItem = rainData.find(r => r[0] === timestamp); |
|
|
|
|
const date = dayjs(timestamp).format('YYYY/MM/DD HH:mm:ss'); |
|
|
|
|
return { |
|
|
|
|
date, |
|
|
|
|
rain: rainItem ? rainItem[1]?.toString() : '', |
|
|
|
|
water: water?.toString() |
|
|
|
|
}; |
|
|
|
|
}); |
|
|
|
|
this.tableData = tableData; |
|
|
|
|
|
|
|
|
|
this.initEcharts() // 图表仍使用完整的 waterData 和 predictData |
|
|
|
|
this.loading = false |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
initEcharts() { |
|
|
|
|
// 1. 取出A和V数据 |
|
|
|
|
const rainArr = this.chartData?.A?.data || []; |
|
|
|
|
const waterArr = this.chartData?.C?.data || []; |
|
|
|
|
const predictArr = this.chartData?.Z?.data || []; |
|
|
|
|
|
|
|
|
|
// const filteredRain = waterArr |
|
|
|
|
// let Xdata = [...filteredRain, ...predictArr] |
|
|
|
|
const filteredRain = waterArr; |
|
|
|
|
|
|
|
|
|
// 合并数据,去重相同时间点 |
|
|
|
|
let Xdata; |
|
|
|
|
if (waterArr.length > 0 && predictArr.length > 0 && waterArr[waterArr.length - 1][0] === predictArr[0][0]) { |
|
|
|
|
Xdata = [...waterArr, ...predictArr.slice(1)]; |
|
|
|
|
} else { |
|
|
|
|
Xdata = [...waterArr, ...predictArr]; |
|
|
|
|
} |
|
|
|
|
// 3. 生成timeData和rainfallData |
|
|
|
|
const timeData = Xdata.map(item => { |
|
|
|
|
// 时间戳转格式 |
|
|
|
|
return dayjs(item[0]).format('MM月DD日 HH:mm'); |
|
|
|
|
}); |
|
|
|
|
const timeDataLength = timeData.length; |
|
|
|
|
const startIndex = Math.max(0, timeDataLength - 12); // 后12条数据的起始索引 |
|
|
|
|
this.qbTime = dayjs(Xdata[startIndex][0]).format('YYYY-MM-DD HH:mm:ss') |
|
|
|
|
// const rainfallData = filteredRain.map(item => item[1]); |
|
|
|
|
// 雨量 |
|
|
|
|
const rainfallData = filteredRain.map(item => { |
|
|
|
|
const waterItem = rainArr.find(w => w[0] === item[0]); |
|
|
|
|
return waterItem ? waterItem[1] : null; |
|
|
|
|
}); |
|
|
|
|
// 水位 |
|
|
|
|
const rainData = filteredRain.map(item => { |
|
|
|
|
const waterItem = waterArr.find(w => w[0] === item[0]); |
|
|
|
|
return waterItem ? waterItem[1] : null; |
|
|
|
|
}); |
|
|
|
|
const levelData = rainData |
|
|
|
|
// 5. 预测水位 |
|
|
|
|
const waterForecast = Xdata.map(item => { |
|
|
|
|
const waterItem = predictArr.find(w => w[0] === item[0]); |
|
|
|
|
return waterItem ? waterItem[1] : null; |
|
|
|
|
}); |
|
|
|
|
// 获取预测水位的最高值和最低值及对应时间 |
|
|
|
|
const validForecast = waterForecast.map((value, index) => ({ value, index })).filter(item => item.value !== null); |
|
|
|
|
if (validForecast.length > 0) { |
|
|
|
|
const maxItem = validForecast.reduce((max, current) => |
|
|
|
|
(current.value > max.value ? current : max), validForecast[0]); |
|
|
|
|
const minItem = validForecast.reduce((min, current) => |
|
|
|
|
(current.value < min.value ? current : min), validForecast[0]); |
|
|
|
|
|
|
|
|
|
this.predict.maxValue = maxItem.value; |
|
|
|
|
this.predict.minValue = minItem.value; |
|
|
|
|
this.predict.maxTime = timeData[maxItem.index]; |
|
|
|
|
this.predict.minTime = timeData[minItem.index]; |
|
|
|
|
yAxis: [ |
|
|
|
|
{ // 雨量 - 左侧 |
|
|
|
|
type: 'value', |
|
|
|
|
name: '雨量 (mm)', |
|
|
|
|
nameLocation: 'middle', |
|
|
|
|
nameGap: 35, |
|
|
|
|
inverse: true, |
|
|
|
|
min: Math.floor(yAxisRainMin), |
|
|
|
|
max: Math.ceil(yAxisRainMax), |
|
|
|
|
axisLabel: { color: '#1075FD' }, |
|
|
|
|
nameTextStyle: { color: '#1075FD', fontWeight: 'bold' }, |
|
|
|
|
splitLine: getSplitLine(0), |
|
|
|
|
}, |
|
|
|
|
{ // 水位 - 右侧 |
|
|
|
|
type: 'value', |
|
|
|
|
name: '水位 (m)', |
|
|
|
|
nameLocation: 'middle', |
|
|
|
|
nameGap: 45, |
|
|
|
|
min: Math.floor(yAxisWaterMin), |
|
|
|
|
max: Math.ceil(yAxisWaterMax), |
|
|
|
|
position: 'right', |
|
|
|
|
axisLabel: { color: '#EE6666' }, |
|
|
|
|
nameTextStyle: { color: '#EE6666', fontWeight: 'bold' }, |
|
|
|
|
splitLine: getSplitLine(1), |
|
|
|
|
} |
|
|
|
|
// 15条横线颜色,首尾黑色,中间灰色 |
|
|
|
|
const splitLineColors = [ |
|
|
|
|
'#000', '#ccc', '#ccc', '#ccc', '#ccc', |
|
|
|
|
'#ccc', '#ccc', '#ccc', '#ccc', '#ccc', |
|
|
|
|
'#ccc', '#ccc', '#ccc', '#ccc', '#000' |
|
|
|
|
]; |
|
|
|
|
let ycColor = '#1EA1CE' |
|
|
|
|
function getSplitLine(index) { |
|
|
|
|
return { |
|
|
|
|
show: true, |
|
|
|
|
lineStyle: { |
|
|
|
|
color: splitLineColors, |
|
|
|
|
width: 1 |
|
|
|
|
} |
|
|
|
|
], |
|
|
|
|
dataZoom: [ |
|
|
|
|
{ |
|
|
|
|
type: 'inside', // 内置型数据区域缩放组件 |
|
|
|
|
xAxisIndex: [0], // 控制第一个x轴 |
|
|
|
|
start: 0, |
|
|
|
|
end: 100 |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
type: 'slider', // 滑动条型数据区域缩放组件 |
|
|
|
|
xAxisIndex: [0], |
|
|
|
|
start: 0, |
|
|
|
|
end: 100, |
|
|
|
|
bottom: 50, // 距离底部距离 |
|
|
|
|
height: 30, // 高度 |
|
|
|
|
fillerColor: 'rgba(167,183,204,0.4)', // 选中区域的背景色 |
|
|
|
|
borderColor: '#ddd', |
|
|
|
|
textStyle: { |
|
|
|
|
color: '#999' |
|
|
|
|
}, |
|
|
|
|
handleSize: '100%', // 手柄大小 |
|
|
|
|
handleStyle: { |
|
|
|
|
color: '#a7b7cc' |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// 计算y轴的最小值和最大值,用于自动设置刻度 |
|
|
|
|
const yAxisRainMin = Math.min(...rainfallData.filter(v => v !== undefined && v !== null)); |
|
|
|
|
const yAxisRainMax = Math.max(...rainfallData.filter(v => v !== undefined && v !== null)); |
|
|
|
|
const allWaterData = levelData.concat(waterForecast).filter(v => v !== undefined && v !== null); |
|
|
|
|
const yAxisWaterMin = allWaterData.length ? Math.min(...allWaterData) : 0; |
|
|
|
|
const yAxisWaterMax = allWaterData.length ? Math.max(...allWaterData) : 10; let that = this |
|
|
|
|
const option = { |
|
|
|
|
backgroundColor: '#fff', |
|
|
|
|
tooltip: { |
|
|
|
|
trigger: 'axis', |
|
|
|
|
axisPointer: { type: 'cross' }, |
|
|
|
|
// 在 tooltip.formatter 函数中替换以下代码 |
|
|
|
|
formatter: function (params) { |
|
|
|
|
let dataIndex = params[0].dataIndex; |
|
|
|
|
let html = `<div><strong>${timeData[dataIndex]}</strong></div>`; |
|
|
|
|
|
|
|
|
|
// 检查是否为预测数据区间 |
|
|
|
|
const isForecast = dataIndex >= filteredRain.length; |
|
|
|
|
|
|
|
|
|
// 显示雨量(仅在非预测时段或有雨量数据时显示) |
|
|
|
|
if (!isForecast || rainfallData[dataIndex] !== undefined) { |
|
|
|
|
html += `<div style="margin-top:4px;"> |
|
|
|
|
<span style="display:inline-block;width:10px;height:10px;background:#1075FD;margin-right:4px;"></span> |
|
|
|
|
雨量: ${rainfallData[dataIndex] ?? '-'} mm |
|
|
|
|
</div>`; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 显示实际水位(仅在非预测时段显示) |
|
|
|
|
if (!isForecast) { |
|
|
|
|
html += `<div> |
|
|
|
|
<span style="display:inline-block;width:10px;height:10px;background:#EE6666;margin-right:4px;"></span> |
|
|
|
|
水位: ${levelData[dataIndex] ?? '-'} m |
|
|
|
|
</div>`; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 显示预测水位(在预测时段显示) |
|
|
|
|
if (isForecast || waterForecast[dataIndex] !== null) { |
|
|
|
|
html += `<div> |
|
|
|
|
<span style="display:inline-block;width:10px;height:10px;background:#6f46fd;margin-right:4px;"></span> |
|
|
|
|
预测水位: ${waterForecast[dataIndex] ?? '-'} m |
|
|
|
|
</div>`; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return html; |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
legend: { |
|
|
|
|
data: ['雨量', '水位', '预测水位'], |
|
|
|
|
top: 20 |
|
|
|
|
}, |
|
|
|
|
toolbox: { |
|
|
|
|
show: true, |
|
|
|
|
feature: { |
|
|
|
|
saveAsImage: { show: true, title: '下载图片' }, |
|
|
|
|
], |
|
|
|
|
series: [ |
|
|
|
|
{ |
|
|
|
|
name: '雨量', |
|
|
|
|
type: 'bar', |
|
|
|
|
yAxisIndex: 0, // 使用左侧y轴 |
|
|
|
|
data: rainfallData, |
|
|
|
|
barWidth: 10, |
|
|
|
|
z: 10, |
|
|
|
|
lineStyle: { color: '#1075FD', width: 3 }, |
|
|
|
|
itemStyle: { color: '#1075FD' }, |
|
|
|
|
markLine: { |
|
|
|
|
symbol: 'none', |
|
|
|
|
label: { |
|
|
|
|
show: false |
|
|
|
|
}, |
|
|
|
|
right: 30, |
|
|
|
|
top: 20 |
|
|
|
|
}, |
|
|
|
|
grid: { |
|
|
|
|
left: 60, |
|
|
|
|
right: 60, |
|
|
|
|
top: '15%', |
|
|
|
|
height: '60%' // 调整高度以适应dataZoom |
|
|
|
|
}, |
|
|
|
|
xAxis: { |
|
|
|
|
type: 'category', |
|
|
|
|
data: timeData, |
|
|
|
|
axisLabel: { |
|
|
|
|
rotate: 0, |
|
|
|
|
formatter: function (value) { |
|
|
|
|
const [date, time] = value.split(' '); |
|
|
|
|
return `${time}\n${date}`; |
|
|
|
|
lineStyle: { |
|
|
|
|
color: ycColor, |
|
|
|
|
type: "dashed", |
|
|
|
|
width: 2 |
|
|
|
|
}, |
|
|
|
|
z: 20, |
|
|
|
|
data: [ |
|
|
|
|
{ |
|
|
|
|
xAxis: timeData.slice(startIndex)[0] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
yAxis: [ |
|
|
|
|
{ // 雨量 - 左侧 |
|
|
|
|
type: 'value', |
|
|
|
|
name: '雨量 (mm)', |
|
|
|
|
nameLocation: 'middle', |
|
|
|
|
nameGap: 35, |
|
|
|
|
inverse: true, |
|
|
|
|
min: Math.floor(yAxisRainMin), |
|
|
|
|
max: Math.ceil(yAxisRainMax), |
|
|
|
|
axisLabel: { color: '#1075FD' }, |
|
|
|
|
nameTextStyle: { color: '#1075FD', fontWeight: 'bold' }, |
|
|
|
|
splitLine: getSplitLine(0), |
|
|
|
|
] |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: '水位', |
|
|
|
|
type: 'line', |
|
|
|
|
yAxisIndex: 1, // 使用右侧y轴 |
|
|
|
|
data: levelData, |
|
|
|
|
symbol: 'circle', |
|
|
|
|
lineStyle: { color: '#EE6666', width: 3 }, |
|
|
|
|
itemStyle: { color: '#EE6666' }, |
|
|
|
|
markLine: { |
|
|
|
|
symbol: 'none', |
|
|
|
|
label: { |
|
|
|
|
show: false |
|
|
|
|
}, |
|
|
|
|
{ // 水位 - 右侧 |
|
|
|
|
type: 'value', |
|
|
|
|
name: '水位 (m)', |
|
|
|
|
nameLocation: 'middle', |
|
|
|
|
nameGap: 45, |
|
|
|
|
min: Math.floor(yAxisWaterMin), |
|
|
|
|
max: Math.ceil(yAxisWaterMax), |
|
|
|
|
position: 'right', |
|
|
|
|
axisLabel: { color: '#EE6666' }, |
|
|
|
|
nameTextStyle: { color: '#EE6666', fontWeight: 'bold' }, |
|
|
|
|
splitLine: getSplitLine(1), |
|
|
|
|
} |
|
|
|
|
], |
|
|
|
|
dataZoom: [ |
|
|
|
|
{ |
|
|
|
|
type: 'inside', // 内置型数据区域缩放组件 |
|
|
|
|
xAxisIndex: [0], // 控制第一个x轴 |
|
|
|
|
start: 0, |
|
|
|
|
end: 100 |
|
|
|
|
lineStyle: { |
|
|
|
|
color: ycColor, |
|
|
|
|
type: "dashed", |
|
|
|
|
width: 2 |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
type: 'slider', // 滑动条型数据区域缩放组件 |
|
|
|
|
xAxisIndex: [0], |
|
|
|
|
start: 0, |
|
|
|
|
end: 100, |
|
|
|
|
bottom: 50, // 距离底部距离 |
|
|
|
|
height: 30, // 高度 |
|
|
|
|
fillerColor: 'rgba(167,183,204,0.4)', // 选中区域的背景色 |
|
|
|
|
borderColor: '#ddd', |
|
|
|
|
textStyle: { |
|
|
|
|
color: '#999' |
|
|
|
|
}, |
|
|
|
|
handleSize: '100%', // 手柄大小 |
|
|
|
|
handleStyle: { |
|
|
|
|
color: '#a7b7cc' |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
], |
|
|
|
|
series: [ |
|
|
|
|
{ |
|
|
|
|
name: '雨量', |
|
|
|
|
type: 'bar', |
|
|
|
|
yAxisIndex: 0, // 使用左侧y轴 |
|
|
|
|
data: rainfallData, |
|
|
|
|
barWidth: 10, |
|
|
|
|
z: 10, |
|
|
|
|
lineStyle: { color: '#1075FD', width: 3 }, |
|
|
|
|
itemStyle: { color: '#1075FD' }, |
|
|
|
|
markLine: { |
|
|
|
|
symbol: 'none', |
|
|
|
|
label: { |
|
|
|
|
show: false |
|
|
|
|
}, |
|
|
|
|
lineStyle: { |
|
|
|
|
color: ycColor, |
|
|
|
|
type: "dashed", |
|
|
|
|
width: 2 |
|
|
|
|
}, |
|
|
|
|
z: 20, |
|
|
|
|
data: [ |
|
|
|
|
{ |
|
|
|
|
xAxis: timeData.slice(startIndex)[0] |
|
|
|
|
} |
|
|
|
|
] |
|
|
|
|
data: [ |
|
|
|
|
{ |
|
|
|
|
xAxis: timeData.slice(startIndex)[0] |
|
|
|
|
} |
|
|
|
|
] |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: '预测水位', |
|
|
|
|
type: 'line', |
|
|
|
|
yAxisIndex: 1, // 使用右侧y轴 |
|
|
|
|
data: waterForecast, |
|
|
|
|
symbol: 'circle', |
|
|
|
|
lineStyle: { color: '#6f46fd', width: 2 }, |
|
|
|
|
itemStyle: { color: '#6f46fd' }, |
|
|
|
|
markLine: { |
|
|
|
|
symbol: 'none', |
|
|
|
|
label: { |
|
|
|
|
show: false |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: '水位', |
|
|
|
|
type: 'line', |
|
|
|
|
yAxisIndex: 1, // 使用右侧y轴 |
|
|
|
|
data: levelData, |
|
|
|
|
symbol: 'circle', |
|
|
|
|
lineStyle: { color: '#EE6666', width: 3 }, |
|
|
|
|
itemStyle: { color: '#EE6666' }, |
|
|
|
|
markLine: { |
|
|
|
|
symbol: 'none', |
|
|
|
|
label: { |
|
|
|
|
show: false |
|
|
|
|
}, |
|
|
|
|
lineStyle: { |
|
|
|
|
color: ycColor, |
|
|
|
|
type: "dashed", |
|
|
|
|
width: 2 |
|
|
|
|
}, |
|
|
|
|
data: [ |
|
|
|
|
{ |
|
|
|
|
xAxis: timeData.slice(startIndex)[0] |
|
|
|
|
} |
|
|
|
|
] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
lineStyle: { |
|
|
|
|
color: ycColor, |
|
|
|
|
type: "dashed", |
|
|
|
|
width: 2 |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: '预测水位', |
|
|
|
|
type: 'line', |
|
|
|
|
yAxisIndex: 1, // 使用右侧y轴 |
|
|
|
|
data: waterForecast, |
|
|
|
|
symbol: 'circle', |
|
|
|
|
lineStyle: { color: '#6f46fd', width: 2 }, |
|
|
|
|
itemStyle: { color: '#6f46fd' }, |
|
|
|
|
markLine: { |
|
|
|
|
symbol: 'none', |
|
|
|
|
label: { |
|
|
|
|
show: false |
|
|
|
|
}, |
|
|
|
|
lineStyle: { |
|
|
|
|
color: ycColor, |
|
|
|
|
type: "dashed", |
|
|
|
|
width: 2 |
|
|
|
|
}, |
|
|
|
|
data: [ |
|
|
|
|
{ |
|
|
|
|
xAxis: timeData.slice(startIndex)[0] |
|
|
|
|
} |
|
|
|
|
] |
|
|
|
|
data: [ |
|
|
|
|
{ |
|
|
|
|
xAxis: timeData.slice(startIndex)[0] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
] |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const chartDom = document.getElementById('main'); |
|
|
|
|
if (!chartDom) return; |
|
|
|
|
if (this.chartInstance) { |
|
|
|
|
this.chartInstance.dispose(); |
|
|
|
|
] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
this.chartInstance = echarts.init(chartDom); |
|
|
|
|
this.chartInstance.setOption(option); |
|
|
|
|
] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 监听窗口变化 |
|
|
|
|
window.addEventListener('resize', () => { |
|
|
|
|
if (this.chartInstance) { |
|
|
|
|
this.chartInstance.resize(); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
const chartDom = document.getElementById('main') |
|
|
|
|
if (!chartDom) return |
|
|
|
|
|
|
|
|
|
// 改变下拉框数据 |
|
|
|
|
changeStation(val) { |
|
|
|
|
this.queryForm.station = val |
|
|
|
|
this.drawChart() |
|
|
|
|
}, |
|
|
|
|
// 销毁已存在的实例 |
|
|
|
|
if (chartInstance.value) { |
|
|
|
|
chartInstance.value.dispose() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 初始化新的 echarts 实例 |
|
|
|
|
chartInstance.value = echarts.init(chartDom) |
|
|
|
|
chartInstance.value.setOption(option) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 改变下拉框数据 |
|
|
|
|
const changeStation = (val) => { |
|
|
|
|
queryForm.station = val |
|
|
|
|
drawChart() |
|
|
|
|
} |
|
|
|
|
</script> |
|
|
|
|
|
|
|
|
|
<style scoped> |
|
|
|
|
/* 如果需要添加样式可以在这里添加 */ |
|
|
|
|
</style> |