17 changed files with 2684 additions and 9 deletions
@ -0,0 +1,316 @@ |
|||||||
|
<template> |
||||||
|
<!-- <div > --> |
||||||
|
<div ref="ChartsTimeBarRef" style="height:100%;width: 100%;" class="chart-container"></div> |
||||||
|
<!-- </div> --> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import * as echarts from "echarts"; |
||||||
|
import dayjs from 'dayjs' |
||||||
|
import { onMounted, nextTick, ref, onUnmounted } from "vue"; |
||||||
|
const props = defineProps({ |
||||||
|
legendData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
xAxisData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
seriesData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
textTitle: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
}, |
||||||
|
unit: { |
||||||
|
type: String, |
||||||
|
default: 'mm' |
||||||
|
}, |
||||||
|
echartType: { |
||||||
|
type: String, |
||||||
|
default: 'bar' |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
type: Object, |
||||||
|
default: () => { |
||||||
|
return { |
||||||
|
left: "5%", |
||||||
|
right: "5%", |
||||||
|
bottom: "16%", |
||||||
|
top: "16%", |
||||||
|
containLabel: true, |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
// Y轴name的位置 |
||||||
|
YNameLocation: { |
||||||
|
type: String, |
||||||
|
default: "middle" |
||||||
|
}, |
||||||
|
colors: { |
||||||
|
type: Array, |
||||||
|
default: () => ['#248ff7', '#6f5be8'] |
||||||
|
}, |
||||||
|
BarFormatter: { |
||||||
|
type: Boolean, |
||||||
|
default: true |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
}) |
||||||
|
const ChartsTimeBarRef = ref(null); |
||||||
|
let echartsBar = null; |
||||||
|
let resizeObserver = null; |
||||||
|
// 监听props变化,确保数据更新后重新初始化图表 |
||||||
|
watch([() => props.legendData, () => props.xAxisData, () => props.seriesData], () => { |
||||||
|
if (echartsBar) { |
||||||
|
updateChart(); |
||||||
|
} |
||||||
|
}, { deep: true }); |
||||||
|
onMounted(() => { |
||||||
|
initEcharts(); |
||||||
|
window.addEventListener('resize', handleResize); |
||||||
|
// 监听容器本身尺寸变化 |
||||||
|
if (ChartsTimeBarRef.value) { |
||||||
|
resizeObserver = new ResizeObserver(handleResize); |
||||||
|
resizeObserver.observe(ChartsTimeBarRef.value); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
window.removeEventListener('resize', handleResize); |
||||||
|
if (resizeObserver && ChartsTimeBarRef.value) { |
||||||
|
resizeObserver.unobserve(ChartsTimeBarRef.value); |
||||||
|
} |
||||||
|
if (echartsBar) { |
||||||
|
echartsBar.dispose(); |
||||||
|
} |
||||||
|
}); |
||||||
|
const echartsLoading = ref(true); |
||||||
|
const initEcharts = () => { |
||||||
|
nextTick(() => { |
||||||
|
if (ChartsTimeBarRef.value) { |
||||||
|
echartsBar = echarts.init(ChartsTimeBarRef.value, "macarons"); |
||||||
|
let option = getChartOption(); |
||||||
|
echartsBar.setOption(option); |
||||||
|
|
||||||
|
// 确保图表尺寸适应容器 |
||||||
|
const resizeChart = () => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsTimeBarRef.value.clientWidth, |
||||||
|
height: ChartsTimeBarRef.value.clientHeight |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 初始化时调整一次大小 |
||||||
|
resizeChart(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
const updateChart = () => { |
||||||
|
nextTick(() => { |
||||||
|
if (echartsBar) { |
||||||
|
let option = getChartOption(); |
||||||
|
echartsBar.setOption(option, true); // 使用notMerge:true来完全替换旧选项 |
||||||
|
resizeChart(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const resizeChart = () => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsTimeBarRef.value.clientWidth, |
||||||
|
height: ChartsTimeBarRef.value.clientHeight |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
const handleResize = () => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
// 添加防抖机制,避免频繁触发 |
||||||
|
clearTimeout(window.resizeTimer); |
||||||
|
window.resizeTimer = setTimeout(() => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsTimeBarRef.value.clientWidth, |
||||||
|
height: ChartsTimeBarRef.value.clientHeight, |
||||||
|
animation: { |
||||||
|
duration: 300 |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}, 100); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
const getChartOption = () => { |
||||||
|
const series = [ |
||||||
|
{ |
||||||
|
type: props.echartType, |
||||||
|
barWidth: "20", |
||||||
|
showSymbol: false, |
||||||
|
itemStyle: { |
||||||
|
// normal 已废弃,直接使用属性 |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: props.colors[0] }, |
||||||
|
{ offset: 1, color: props.colors[1] } |
||||||
|
]), |
||||||
|
barBorderRadius: 12, |
||||||
|
}, |
||||||
|
data: props.seriesData, |
||||||
|
} |
||||||
|
|
||||||
|
]; |
||||||
|
let option = { |
||||||
|
// backgroundColor: "#323a5e", |
||||||
|
title: { |
||||||
|
text: props.textTitle, |
||||||
|
textStyle: { |
||||||
|
align: "center", |
||||||
|
color: "#000", |
||||||
|
fontSize: 20, |
||||||
|
}, |
||||||
|
top: "3%", |
||||||
|
left: "50%", |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: "axis", |
||||||
|
axisPointer: { |
||||||
|
// 坐标轴指示器,坐标轴触发有效 |
||||||
|
type: "shadow", // 默认为直线,可选为:'line' | 'shadow' |
||||||
|
}, |
||||||
|
}, |
||||||
|
grid: props.grid, |
||||||
|
legend: { |
||||||
|
data: props.legendData, |
||||||
|
right: 10, |
||||||
|
top: 12, |
||||||
|
textStyle: { |
||||||
|
color: "#000", |
||||||
|
}, |
||||||
|
itemWidth: 12, |
||||||
|
itemHeight: 10, |
||||||
|
// itemGap: 35 |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: "category", |
||||||
|
data: props.xAxisData, |
||||||
|
axisLabel: props.echartType === 'line' || props.BarFormatter ? { |
||||||
|
// 折线图专用配置 - 时间格式化 |
||||||
|
textStyle: { |
||||||
|
fontFamily: "Microsoft YaHei", |
||||||
|
}, |
||||||
|
showMinLabel: true, // 强制显示最小值标签 |
||||||
|
showMaxLabel: true, // 强制显示最大值标签 |
||||||
|
formatter: function (value) { |
||||||
|
let date = dayjs(value) |
||||||
|
let yearMonthDay = date.format('YYYY-MM-DD') |
||||||
|
let hourMinute = date.format('HH:mm') |
||||||
|
let result = `{a|${hourMinute}}\n{b|${yearMonthDay}}`; |
||||||
|
if (props.timeType == 'day') { |
||||||
|
result = `{b|${yearMonthDay}}`; |
||||||
|
} |
||||||
|
return result |
||||||
|
}, |
||||||
|
rich: { |
||||||
|
a: { |
||||||
|
fontSize: 12, |
||||||
|
color: '#000', |
||||||
|
padding: [0, 0, 5, 0] |
||||||
|
}, |
||||||
|
b: { |
||||||
|
fontSize: 12, |
||||||
|
color: '#000' |
||||||
|
} |
||||||
|
} |
||||||
|
} : { |
||||||
|
// 柱状图专用配置 - 简单标签显示 |
||||||
|
rotate: props.xAxisData.length > 10 ? 45 : 0, |
||||||
|
interval: 0, |
||||||
|
margin: 20, |
||||||
|
textStyle: { |
||||||
|
fontFamily: "Microsoft YaHei", |
||||||
|
fontSize: 16 |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
yAxis: { |
||||||
|
type: "value", |
||||||
|
name: props.unit, |
||||||
|
nameLocation: props.YNameLocation, |
||||||
|
nameTextStyle: {//y轴上方单位的颜色 |
||||||
|
color: '#464b50' |
||||||
|
}, |
||||||
|
nameGap: 50, |
||||||
|
axisLine: { |
||||||
|
show: true, |
||||||
|
lineStyle: { |
||||||
|
color: "#ccc", |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
splitLine: { |
||||||
|
show: true, |
||||||
|
lineStyle: { |
||||||
|
color: "#ccc", |
||||||
|
}, |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
textStyle: { |
||||||
|
color: "#000000", |
||||||
|
}, |
||||||
|
// 添加formatter属性,保留两位小数 |
||||||
|
formatter: function (value) { |
||||||
|
return value.toFixed(2); |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
series: series, |
||||||
|
}; |
||||||
|
|
||||||
|
return option |
||||||
|
// var app = { |
||||||
|
// currentIndex: -1, |
||||||
|
// }; |
||||||
|
// setInterval(function () { |
||||||
|
// var dataLen = option.series[0].data.length; |
||||||
|
|
||||||
|
// // 取消之前高亮的图形 |
||||||
|
// myChart.dispatchAction({ |
||||||
|
// type: "downplay", |
||||||
|
// seriesIndex: 0, |
||||||
|
// dataIndex: app.currentIndex, |
||||||
|
// }); |
||||||
|
// app.currentIndex = (app.currentIndex + 1) % dataLen; |
||||||
|
// //console.log(app.currentIndex); |
||||||
|
// // 高亮当前图形 |
||||||
|
// myChart.dispatchAction({ |
||||||
|
// type: "highlight", |
||||||
|
// seriesIndex: 0, |
||||||
|
// dataIndex: app.currentIndex, |
||||||
|
// }); |
||||||
|
// // 显示 tooltip |
||||||
|
// myChart.dispatchAction({ |
||||||
|
// type: "showTip", |
||||||
|
// seriesIndex: 0, |
||||||
|
// dataIndex: app.currentIndex, |
||||||
|
// }); |
||||||
|
// }, 1000); |
||||||
|
|
||||||
|
} |
||||||
|
</script> |
||||||
|
<style scoped> |
||||||
|
.chart-container { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
min-height: 300px; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,310 @@ |
|||||||
|
<template> |
||||||
|
<!-- <div > --> |
||||||
|
<div ref="ChartsTimeBarRef" style="height:100%;width: 100%;" class="chart-container"></div> |
||||||
|
<!-- </div> --> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import * as echarts from "echarts"; |
||||||
|
import dayjs from 'dayjs' |
||||||
|
import { onMounted, nextTick, ref, onUnmounted } from "vue"; |
||||||
|
const props = defineProps({ |
||||||
|
legendData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
xAxisData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
seriesData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
textTitle: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
}, |
||||||
|
unit: { |
||||||
|
type: String, |
||||||
|
default: 'mm' |
||||||
|
}, |
||||||
|
echartType: { |
||||||
|
type: String, |
||||||
|
default: 'bar' |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
type: Object, |
||||||
|
default: () => { |
||||||
|
return { |
||||||
|
left: "5%", |
||||||
|
right: "5%", |
||||||
|
bottom: "16%", |
||||||
|
top: "16%", |
||||||
|
containLabel: true, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
const ChartsTimeBarRef = ref(null); |
||||||
|
let echartsBar = null; |
||||||
|
let resizeObserver = null; |
||||||
|
// 监听props变化,确保数据更新后重新初始化图表 |
||||||
|
watch([() => props.legendData, () => props.xAxisData, () => props.seriesData], () => { |
||||||
|
if (echartsBar) { |
||||||
|
|
||||||
|
updateChart(); |
||||||
|
} |
||||||
|
}, { deep: true }); |
||||||
|
onMounted(() => { |
||||||
|
|
||||||
|
|
||||||
|
initEcharts(); |
||||||
|
window.addEventListener('resize', handleResize); |
||||||
|
// 监听容器本身尺寸变化 |
||||||
|
if (ChartsTimeBarRef.value) { |
||||||
|
resizeObserver = new ResizeObserver(handleResize); |
||||||
|
resizeObserver.observe(ChartsTimeBarRef.value); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
window.removeEventListener('resize', handleResize); |
||||||
|
if (resizeObserver && ChartsTimeBarRef.value) { |
||||||
|
resizeObserver.unobserve(ChartsTimeBarRef.value); |
||||||
|
} |
||||||
|
if (echartsBar) { |
||||||
|
echartsBar.dispose(); |
||||||
|
} |
||||||
|
}); |
||||||
|
const echartsLoading = ref(true); |
||||||
|
const initEcharts = () => { |
||||||
|
nextTick(() => { |
||||||
|
if (ChartsTimeBarRef.value) { |
||||||
|
echartsBar = echarts.init(ChartsTimeBarRef.value, "macarons"); |
||||||
|
let option = getChartOption(); |
||||||
|
echartsBar.setOption(option); |
||||||
|
|
||||||
|
// 确保图表尺寸适应容器 |
||||||
|
const resizeChart = () => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsTimeBarRef.value.clientWidth, |
||||||
|
height: ChartsTimeBarRef.value.clientHeight |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 初始化时调整一次大小 |
||||||
|
resizeChart(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
const updateChart = () => { |
||||||
|
nextTick(() => { |
||||||
|
if (echartsBar) { |
||||||
|
let option = getChartOption(); |
||||||
|
echartsBar.setOption(option, true); // 使用notMerge:true来完全替换旧选项 |
||||||
|
resizeChart(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const resizeChart = () => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsTimeBarRef.value.clientWidth, |
||||||
|
height: ChartsTimeBarRef.value.clientHeight |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
const handleResize = () => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
// 添加防抖机制,避免频繁触发 |
||||||
|
clearTimeout(window.resizeTimer); |
||||||
|
window.resizeTimer = setTimeout(() => { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsTimeBarRef.value.clientWidth, |
||||||
|
height: ChartsTimeBarRef.value.clientHeight, |
||||||
|
animation: { |
||||||
|
duration: 300 |
||||||
|
} |
||||||
|
}); |
||||||
|
}, 100); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
const getChartOption = () => { |
||||||
|
console.log(props.legendData, props.xAxisData, props.seriesData) |
||||||
|
const series = props.seriesData.map((item, index) => { |
||||||
|
const colors = [ |
||||||
|
["#248ff7", "#6f5be8"], |
||||||
|
["#248ff7", "#6f5be8"], |
||||||
|
|
||||||
|
]; |
||||||
|
const color = colors[index % colors.length]; |
||||||
|
|
||||||
|
return { |
||||||
|
name: props.legendData[index], |
||||||
|
type: item.type, |
||||||
|
barWidth: "20", |
||||||
|
yAxisIndex: index, |
||||||
|
showSymbol: false, |
||||||
|
itemStyle: { |
||||||
|
normal: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: color[0] }, |
||||||
|
{ offset: 1, color: color[1] } |
||||||
|
]), |
||||||
|
barBorderRadius: 12, |
||||||
|
}, |
||||||
|
}, |
||||||
|
data: item.data?.map(val => val[1]), |
||||||
|
}; |
||||||
|
}); |
||||||
|
let option = { |
||||||
|
// backgroundColor: "#323a5e", |
||||||
|
title: { |
||||||
|
text: props.textTitle, |
||||||
|
textStyle: { |
||||||
|
align: "center", |
||||||
|
color: "#000", |
||||||
|
fontSize: 20, |
||||||
|
}, |
||||||
|
top: "3%", |
||||||
|
left: "50%", |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: "axis", |
||||||
|
axisPointer: { |
||||||
|
// 坐标轴指示器,坐标轴触发有效 |
||||||
|
type: "shadow", // 默认为直线,可选为:'line' | 'shadow' |
||||||
|
}, |
||||||
|
}, |
||||||
|
grid: props.grid, |
||||||
|
legend: { |
||||||
|
data: props.legendData, |
||||||
|
right: 10, |
||||||
|
top: 12, |
||||||
|
textStyle: { |
||||||
|
color: "#000", |
||||||
|
}, |
||||||
|
itemWidth: 12, |
||||||
|
itemHeight: 10, |
||||||
|
// itemGap: 35 |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: "category", |
||||||
|
data: props.xAxisData, |
||||||
|
axisLine: { |
||||||
|
lineStyle: { |
||||||
|
color: "#ccc", |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
axisLabel: { |
||||||
|
textStyle: { |
||||||
|
fontFamily: "Microsoft YaHei", |
||||||
|
}, |
||||||
|
showMinLabel: true, // 强制显示最小值标签 |
||||||
|
showMaxLabel: true, // 强制显示最大值标签 |
||||||
|
formatter: function (value) { |
||||||
|
let date = dayjs(value) |
||||||
|
let yearMonthDay = date.format('YYYY-MM-DD') |
||||||
|
let hourMinute = date.format('HH:mm') |
||||||
|
let result = `{a|${hourMinute}}\n{b|${yearMonthDay}}`; |
||||||
|
if (props.timeType == 'day') { |
||||||
|
result = `{b|${yearMonthDay}}`; |
||||||
|
} |
||||||
|
return result |
||||||
|
}, |
||||||
|
|
||||||
|
rich: { |
||||||
|
a: { |
||||||
|
fontSize: 12, |
||||||
|
color: '#000', |
||||||
|
padding: [0, 0, 5, 0] // 上右下左的内边距,这里给上方小时分钟添加一点底部间距 |
||||||
|
}, |
||||||
|
b: { |
||||||
|
fontSize: 12, |
||||||
|
color: '#000' |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
yAxis: [ |
||||||
|
|
||||||
|
{ // 水位 - 右侧 |
||||||
|
type: 'value', |
||||||
|
name: '水位 (m)', |
||||||
|
axisLabel: { color: '#EE6666' }, |
||||||
|
nameTextStyle: { color: '#EE6666', fontWeight: 'bold' }, |
||||||
|
splitLine: { |
||||||
|
show: true, |
||||||
|
lineStyle: { |
||||||
|
color: "#ccc", |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ // 雨量 - 左侧 |
||||||
|
type: 'value', |
||||||
|
name: '雨量 (mm)', |
||||||
|
inverse: true, |
||||||
|
nameLocation: 'start', |
||||||
|
position: 'right', |
||||||
|
axisLabel: { color: '#1075FD' }, |
||||||
|
nameTextStyle: { color: '#1075FD', fontWeight: 'bold' }, |
||||||
|
splitLine: { |
||||||
|
show: true, |
||||||
|
lineStyle: { |
||||||
|
color: "#ccc", |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
} |
||||||
|
], |
||||||
|
|
||||||
|
series: series, |
||||||
|
}; |
||||||
|
|
||||||
|
return option |
||||||
|
// var app = { |
||||||
|
// currentIndex: -1, |
||||||
|
// }; |
||||||
|
// setInterval(function () { |
||||||
|
// var dataLen = option.series[0].data.length; |
||||||
|
|
||||||
|
// // 取消之前高亮的图形 |
||||||
|
// myChart.dispatchAction({ |
||||||
|
// type: "downplay", |
||||||
|
// seriesIndex: 0, |
||||||
|
// dataIndex: app.currentIndex, |
||||||
|
// }); |
||||||
|
// app.currentIndex = (app.currentIndex + 1) % dataLen; |
||||||
|
// //console.log(app.currentIndex); |
||||||
|
// // 高亮当前图形 |
||||||
|
// myChart.dispatchAction({ |
||||||
|
// type: "highlight", |
||||||
|
// seriesIndex: 0, |
||||||
|
// dataIndex: app.currentIndex, |
||||||
|
// }); |
||||||
|
// // 显示 tooltip |
||||||
|
// myChart.dispatchAction({ |
||||||
|
// type: "showTip", |
||||||
|
// seriesIndex: 0, |
||||||
|
// dataIndex: app.currentIndex, |
||||||
|
// }); |
||||||
|
// }, 1000); |
||||||
|
|
||||||
|
} |
||||||
|
</script> |
||||||
|
<style scoped> |
||||||
|
.chart-container { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
min-height: 300px; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,382 @@ |
|||||||
|
<template> |
||||||
|
<!-- <div > --> |
||||||
|
<div ref="ChartsTimeBarRef" style="height:100%;width: 100%;" class="chart-container"></div> |
||||||
|
<!-- </div> --> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import * as echarts from "echarts"; |
||||||
|
import dayjs from 'dayjs' |
||||||
|
import { onMounted, nextTick, ref, onUnmounted } from "vue"; |
||||||
|
const props = defineProps({ |
||||||
|
legendData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
xAxisData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
seriesData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
textTitle: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
}, |
||||||
|
unit: { |
||||||
|
type: String, |
||||||
|
default: 'mm' |
||||||
|
}, |
||||||
|
echartType: { |
||||||
|
type: String, |
||||||
|
default: 'bar' |
||||||
|
}, |
||||||
|
timeType: { |
||||||
|
type: String, |
||||||
|
default: 'hour' |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
const ChartsTimeBarRef = ref(null); |
||||||
|
let echartsBar = null; |
||||||
|
let resizeObserver = null; |
||||||
|
// 监听props变化,确保数据更新后重新初始化图表 |
||||||
|
watch([() => props.legendData, () => props.xAxisData, () => props.seriesData], () => { |
||||||
|
if (echartsBar) { |
||||||
|
updateChart(); |
||||||
|
} |
||||||
|
}, { deep: true }); |
||||||
|
onMounted(() => { |
||||||
|
initEcharts(); |
||||||
|
window.addEventListener('resize', handleResize); |
||||||
|
// 监听容器本身尺寸变化 |
||||||
|
if (ChartsTimeBarRef.value) { |
||||||
|
resizeObserver = new ResizeObserver(handleResize); |
||||||
|
resizeObserver.observe(ChartsTimeBarRef.value); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
window.removeEventListener('resize', handleResize); |
||||||
|
if (resizeObserver && ChartsTimeBarRef.value) { |
||||||
|
resizeObserver.unobserve(ChartsTimeBarRef.value); |
||||||
|
} |
||||||
|
if (echartsBar) { |
||||||
|
echartsBar.dispose(); |
||||||
|
} |
||||||
|
}); |
||||||
|
const echartsLoading = ref(true); |
||||||
|
const initEcharts = () => { |
||||||
|
nextTick(() => { |
||||||
|
if (ChartsTimeBarRef.value) { |
||||||
|
echartsBar = echarts.init(ChartsTimeBarRef.value, "macarons"); |
||||||
|
let option = getChartOption(); |
||||||
|
echartsBar.setOption(option); |
||||||
|
|
||||||
|
// 确保图表尺寸适应容器 |
||||||
|
const resizeChart = () => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsTimeBarRef.value.clientWidth, |
||||||
|
height: ChartsTimeBarRef.value.clientHeight |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 初始化时调整一次大小 |
||||||
|
resizeChart(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
const updateChart = () => { |
||||||
|
nextTick(() => { |
||||||
|
if (echartsBar) { |
||||||
|
let option = getChartOption(); |
||||||
|
echartsBar.setOption(option, true); // 使用notMerge:true来完全替换旧选项 |
||||||
|
resizeChart(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const resizeChart = () => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsTimeBarRef.value.clientWidth, |
||||||
|
height: ChartsTimeBarRef.value.clientHeight |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
const handleResize = () => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
// 添加防抖机制,避免频繁触发 |
||||||
|
clearTimeout(window.resizeTimer); |
||||||
|
window.resizeTimer = setTimeout(() => { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsTimeBarRef.value.clientWidth, |
||||||
|
height: ChartsTimeBarRef.value.clientHeight, |
||||||
|
animation: { |
||||||
|
duration: 300 |
||||||
|
} |
||||||
|
}); |
||||||
|
}, 100); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
const getChartOption = () => { |
||||||
|
let allData = []; |
||||||
|
props.seriesData.forEach(series => { |
||||||
|
if (series.data) { |
||||||
|
series.data.forEach(item => { |
||||||
|
// 根据数据结构获取数值,可能是 [time, value] 或 {tm: time, value: value} 格式 |
||||||
|
const value = Array.isArray(item) ? item[1] : (item.value !== undefined ? item.value : item); |
||||||
|
if (value !== undefined && value !== null && !isNaN(value)) { |
||||||
|
allData.push(Number(value)); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// 计算y轴范围 |
||||||
|
let yAxisMin = 0; |
||||||
|
let yAxisMax = 100; |
||||||
|
|
||||||
|
if (allData.length > 0) { |
||||||
|
const minVal = Math.min(...allData); |
||||||
|
const maxVal = Math.max(...allData); |
||||||
|
|
||||||
|
// 添加一些边距,使图表更美观 |
||||||
|
const range = maxVal - minVal; |
||||||
|
const margin = range > 0 ? range * 0.1 : 1; |
||||||
|
|
||||||
|
yAxisMin = Math.floor((minVal - margin) * 100) / 100; // 保留两位小数 |
||||||
|
yAxisMax = Math.ceil((maxVal + margin) * 100) / 100; // 保留两位小数 |
||||||
|
|
||||||
|
// 如果数据值都很小,确保最小值不为负数(可根据实际需求调整) |
||||||
|
if (minVal >= 0 && yAxisMin < 0) { |
||||||
|
yAxisMin = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
const series = props.seriesData.map((item, index) => { |
||||||
|
const colors = [ |
||||||
|
["#FA8072", "#B22222"], |
||||||
|
["#0fec7d", "#04793e"], |
||||||
|
["#ab36fc", "#7705a4"], |
||||||
|
["#ffbd89", "#8a5c08"], |
||||||
|
["#CD9B1D", "#8B6914"], |
||||||
|
["#ff7f50", "#a93f07"], |
||||||
|
["#EE1289", "#8B0A50"], |
||||||
|
["#ee9c9c", "#755252"], |
||||||
|
["#06e8d6", "#078075"], |
||||||
|
["#de79c2", "#cd5c5c"], |
||||||
|
["#00F5FF", "#6b8e23"], |
||||||
|
["#f50000", "#590404"], |
||||||
|
["#00CD66", "#3cb371"], |
||||||
|
["#c400fa", "#48005d"], |
||||||
|
["#eca86c", "#5e4310"], |
||||||
|
["#bebe0f", "#8B8B00"] |
||||||
|
]; |
||||||
|
const color = colors[index % colors.length]; |
||||||
|
|
||||||
|
return { |
||||||
|
name: props.legendData[index], |
||||||
|
type: props.echartType, |
||||||
|
barWidth: "20", |
||||||
|
showSymbol: false, |
||||||
|
itemStyle: { |
||||||
|
normal: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: color[0] }, |
||||||
|
{ offset: 1, color: color[1] } |
||||||
|
]), |
||||||
|
barBorderRadius: 12, |
||||||
|
}, |
||||||
|
}, |
||||||
|
data: item.data?.map(val => val[1]), |
||||||
|
}; |
||||||
|
}); |
||||||
|
let option = { |
||||||
|
// backgroundColor: "#323a5e", |
||||||
|
title: { |
||||||
|
text: props.textTitle, |
||||||
|
textStyle: { |
||||||
|
align: "center", |
||||||
|
color: "#000", |
||||||
|
fontSize: 20, |
||||||
|
}, |
||||||
|
top: "3%", |
||||||
|
left: "50%", |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: "axis", |
||||||
|
axisPointer: { |
||||||
|
// 坐标轴指示器,坐标轴触发有效 |
||||||
|
type: "shadow", // 默认为直线,可选为:'line' | 'shadow' |
||||||
|
}, |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
left: "5%", |
||||||
|
right: "5%", |
||||||
|
bottom: "16%", |
||||||
|
top: "16%", |
||||||
|
containLabel: true, |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
data: props.legendData, |
||||||
|
right: 10, |
||||||
|
top: 12, |
||||||
|
textStyle: { |
||||||
|
color: "#000", |
||||||
|
}, |
||||||
|
itemWidth: 12, |
||||||
|
itemHeight: 10, |
||||||
|
// itemGap: 35 |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: "category", |
||||||
|
data: props.xAxisData, |
||||||
|
axisLine: { |
||||||
|
lineStyle: { |
||||||
|
color: "#ccc", |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
axisLabel: { |
||||||
|
textStyle: { |
||||||
|
fontFamily: "Microsoft YaHei", |
||||||
|
}, |
||||||
|
showMinLabel: true, // 强制显示最小值标签 |
||||||
|
showMaxLabel: true, // 强制显示最大值标签 |
||||||
|
formatter: function (value) { |
||||||
|
let date = dayjs(value) |
||||||
|
let yearMonthDay = date.format('YYYY-MM-DD') |
||||||
|
let hourMinute = date.format('HH:mm') |
||||||
|
let result = `{a|${hourMinute}}\n{b|${yearMonthDay}}`; |
||||||
|
if (props.timeType == 'day') { |
||||||
|
result = `{b|${yearMonthDay}}`; |
||||||
|
} |
||||||
|
return result |
||||||
|
}, |
||||||
|
rich: { |
||||||
|
a: { |
||||||
|
fontSize: 12, |
||||||
|
color: '#000', |
||||||
|
padding: [0, 0, 5, 0] // 上右下左的内边距,这里给上方小时分钟添加一点底部间距 |
||||||
|
}, |
||||||
|
b: { |
||||||
|
fontSize: 12, |
||||||
|
color: '#000' |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
yAxis: { |
||||||
|
type: "value", |
||||||
|
min: yAxisMin, |
||||||
|
max: yAxisMax, |
||||||
|
name: props.unit, |
||||||
|
nameLocation: 'middle', |
||||||
|
nameTextStyle: {//y轴上方单位的颜色 |
||||||
|
color: '#464b50' |
||||||
|
}, |
||||||
|
nameGap: 50, |
||||||
|
axisLine: { |
||||||
|
show: true, |
||||||
|
lineStyle: { |
||||||
|
color: "#ccc", |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
splitLine: { |
||||||
|
show: true, |
||||||
|
lineStyle: { |
||||||
|
color: "#ccc", |
||||||
|
}, |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
textStyle: { |
||||||
|
color: "#000000", |
||||||
|
}, |
||||||
|
// 添加formatter属性,保留两位小数 |
||||||
|
formatter: function (value) { |
||||||
|
return value.toFixed(2); |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
dataZoom: [ |
||||||
|
{ |
||||||
|
show: true, |
||||||
|
// height: 15, |
||||||
|
xAxisIndex: [0], |
||||||
|
bottom: "8%", |
||||||
|
start: 0, |
||||||
|
end: 100, |
||||||
|
handleIcon: |
||||||
|
"path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z", |
||||||
|
handleSize: "110%", |
||||||
|
handleStyle: { |
||||||
|
color: "#d3dee5", |
||||||
|
}, |
||||||
|
textStyle: { |
||||||
|
color: "#000", |
||||||
|
}, |
||||||
|
borderColor: "#90979c", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "inside", |
||||||
|
show: true, |
||||||
|
// height: 15, |
||||||
|
start: 1, |
||||||
|
end: 35, |
||||||
|
}, |
||||||
|
// { |
||||||
|
// type: 'inside' |
||||||
|
// }, |
||||||
|
// { |
||||||
|
// type: 'slider' |
||||||
|
// } |
||||||
|
], |
||||||
|
series: series, |
||||||
|
}; |
||||||
|
|
||||||
|
return option |
||||||
|
// var app = { |
||||||
|
// currentIndex: -1, |
||||||
|
// }; |
||||||
|
// setInterval(function () { |
||||||
|
// var dataLen = option.series[0].data.length; |
||||||
|
|
||||||
|
// // 取消之前高亮的图形 |
||||||
|
// myChart.dispatchAction({ |
||||||
|
// type: "downplay", |
||||||
|
// seriesIndex: 0, |
||||||
|
// dataIndex: app.currentIndex, |
||||||
|
// }); |
||||||
|
// app.currentIndex = (app.currentIndex + 1) % dataLen; |
||||||
|
// //console.log(app.currentIndex); |
||||||
|
// // 高亮当前图形 |
||||||
|
// myChart.dispatchAction({ |
||||||
|
// type: "highlight", |
||||||
|
// seriesIndex: 0, |
||||||
|
// dataIndex: app.currentIndex, |
||||||
|
// }); |
||||||
|
// // 显示 tooltip |
||||||
|
// myChart.dispatchAction({ |
||||||
|
// type: "showTip", |
||||||
|
// seriesIndex: 0, |
||||||
|
// dataIndex: app.currentIndex, |
||||||
|
// }); |
||||||
|
// }, 1000); |
||||||
|
|
||||||
|
} |
||||||
|
</script> |
||||||
|
<style scoped> |
||||||
|
.chart-container { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
min-height: 300px; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,369 @@ |
|||||||
|
<template> |
||||||
|
<!-- <div > --> |
||||||
|
<div ref="ChartsTimeBarRef" style="height:100%;width: 100%;" class="chart-container"></div> |
||||||
|
<!-- </div> --> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import * as echarts from "echarts"; |
||||||
|
import dayjs from 'dayjs' |
||||||
|
import { onMounted, nextTick, ref, onUnmounted } from "vue"; |
||||||
|
const props = defineProps({ |
||||||
|
legendData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
xAxisData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
seriesData: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
textTitle: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
}, |
||||||
|
unit: { |
||||||
|
type: String, |
||||||
|
default: 'mm' |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
type: Object, |
||||||
|
default: () => { |
||||||
|
return { |
||||||
|
left: "5%", |
||||||
|
right: "5%", |
||||||
|
bottom: "16%", |
||||||
|
top: "16%", |
||||||
|
containLabel: true, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
const ChartsTimeBarRef = ref(null); |
||||||
|
let echartsBar = null; |
||||||
|
let resizeObserver = null; |
||||||
|
// 监听props变化,确保数据更新后重新初始化图表 |
||||||
|
watch([() => props.legendData, () => props.xAxisData, () => props.seriesData], () => { |
||||||
|
if (echartsBar) { |
||||||
|
updateChart(); |
||||||
|
} |
||||||
|
}, { deep: true }); |
||||||
|
onMounted(() => { |
||||||
|
console.log(props.xAxisData, '======xAxisData') |
||||||
|
initEcharts(); |
||||||
|
window.addEventListener('resize', handleResize); |
||||||
|
// 监听容器本身尺寸变化 |
||||||
|
if (ChartsTimeBarRef.value) { |
||||||
|
resizeObserver = new ResizeObserver(handleResize); |
||||||
|
resizeObserver.observe(ChartsTimeBarRef.value); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
window.removeEventListener('resize', handleResize); |
||||||
|
if (resizeObserver && ChartsTimeBarRef.value) { |
||||||
|
resizeObserver.unobserve(ChartsTimeBarRef.value); |
||||||
|
} |
||||||
|
if (echartsBar) { |
||||||
|
echartsBar.dispose(); |
||||||
|
} |
||||||
|
}); |
||||||
|
const echartsLoading = ref(true); |
||||||
|
const initEcharts = () => { |
||||||
|
nextTick(() => { |
||||||
|
if (ChartsTimeBarRef.value) { |
||||||
|
echartsBar = echarts.init(ChartsTimeBarRef.value, "macarons"); |
||||||
|
let option = getChartOption(); |
||||||
|
echartsBar.setOption(option); |
||||||
|
|
||||||
|
// 确保图表尺寸适应容器 |
||||||
|
const resizeChart = () => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsTimeBarRef.value.clientWidth, |
||||||
|
height: ChartsTimeBarRef.value.clientHeight |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 初始化时调整一次大小 |
||||||
|
resizeChart(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
const updateChart = () => { |
||||||
|
nextTick(() => { |
||||||
|
if (echartsBar) { |
||||||
|
let option = getChartOption(); |
||||||
|
echartsBar.setOption(option, true); // 使用notMerge:true来完全替换旧选项 |
||||||
|
resizeChart(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const resizeChart = () => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsTimeBarRef.value.clientWidth, |
||||||
|
height: ChartsTimeBarRef.value.clientHeight |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
const handleResize = () => { |
||||||
|
if (echartsBar && ChartsTimeBarRef.value) { |
||||||
|
// 添加防抖机制,避免频繁触发 |
||||||
|
clearTimeout(window.resizeTimer); |
||||||
|
window.resizeTimer = setTimeout(() => { |
||||||
|
echartsBar.resize({ |
||||||
|
width: ChartsTimeBarRef.value.clientWidth, |
||||||
|
height: ChartsTimeBarRef.value.clientHeight, |
||||||
|
animation: { |
||||||
|
duration: 300 |
||||||
|
} |
||||||
|
}); |
||||||
|
}, 100); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
const getChartOption = () => { |
||||||
|
let allData = []; |
||||||
|
props.seriesData.forEach(series => { |
||||||
|
if (series.data) { |
||||||
|
series.data.forEach(item => { |
||||||
|
// 根据数据结构获取数值,可能是 [time, value] 或 {tm: time, value: value} 格式 |
||||||
|
const value = Array.isArray(item) ? item[1] : (item.value !== undefined ? item.value : item); |
||||||
|
if (value !== undefined && value !== null && !isNaN(value)) { |
||||||
|
allData.push(Number(value)); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// 计算y轴范围 |
||||||
|
let yAxisMin = 0; |
||||||
|
let yAxisMax = 100; |
||||||
|
|
||||||
|
if (allData.length > 0) { |
||||||
|
const minVal = Math.min(...allData); |
||||||
|
const maxVal = Math.max(...allData); |
||||||
|
|
||||||
|
// 添加一些边距,使图表更美观 |
||||||
|
const range = maxVal - minVal; |
||||||
|
const margin = range > 0 ? range * 0.1 : 1; |
||||||
|
|
||||||
|
yAxisMin = Math.floor((minVal - margin) * 100) / 100; // 保留两位小数 |
||||||
|
yAxisMax = Math.ceil((maxVal + margin) * 100) / 100; // 保留两位小数 |
||||||
|
|
||||||
|
// 如果数据值都很小,确保最小值不为负数(可根据实际需求调整) |
||||||
|
if (minVal >= 0 && yAxisMin < 0) { |
||||||
|
yAxisMin = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
const series = props.seriesData.map((item, index) => { |
||||||
|
const colors = [ |
||||||
|
["#fccb05", "#f5804d"], |
||||||
|
["#8bd46e", "#09bcb7"], |
||||||
|
["#248ff7", "#6851f1"] |
||||||
|
]; |
||||||
|
const color = colors[index % colors.length]; |
||||||
|
|
||||||
|
return { |
||||||
|
name: props.legendData[index], |
||||||
|
type: "line", |
||||||
|
barWidth: "20", |
||||||
|
showSymbol: false, |
||||||
|
itemStyle: { |
||||||
|
normal: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: color[0] }, |
||||||
|
{ offset: 1, color: color[1] } |
||||||
|
]), |
||||||
|
barBorderRadius: 12, |
||||||
|
}, |
||||||
|
}, |
||||||
|
data: item.data.map(val => val[1]), |
||||||
|
}; |
||||||
|
}); |
||||||
|
let option = { |
||||||
|
// backgroundColor: "#323a5e", |
||||||
|
title: { |
||||||
|
text: props.textTitle, |
||||||
|
textStyle: { |
||||||
|
align: "center", |
||||||
|
color: "#000", |
||||||
|
fontSize: 20, |
||||||
|
}, |
||||||
|
top: "3%", |
||||||
|
left: "50%", |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: "axis", |
||||||
|
axisPointer: { |
||||||
|
// 坐标轴指示器,坐标轴触发有效 |
||||||
|
type: "shadow", // 默认为直线,可选为:'line' | 'shadow' |
||||||
|
}, |
||||||
|
}, |
||||||
|
grid: props.grid, |
||||||
|
legend: { |
||||||
|
data: props.legendData, |
||||||
|
right: 10, |
||||||
|
top: 12, |
||||||
|
textStyle: { |
||||||
|
color: "#000", |
||||||
|
}, |
||||||
|
itemWidth: 12, |
||||||
|
itemHeight: 10, |
||||||
|
// itemGap: 35 |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: "category", |
||||||
|
data: props.xAxisData, |
||||||
|
axisLine: { |
||||||
|
lineStyle: { |
||||||
|
color: "#ccc", |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
axisLabel: { |
||||||
|
textStyle: { |
||||||
|
fontFamily: "Microsoft YaHei", |
||||||
|
}, |
||||||
|
showMinLabel: true, // 强制显示最小值标签 |
||||||
|
showMaxLabel: true, // 强制显示最大值标签 |
||||||
|
formatter: function (value) { |
||||||
|
let date = dayjs(value) |
||||||
|
let yearMonthDay = date.format('YYYY-MM-DD') |
||||||
|
let hourMinute = date.format('HH:mm') |
||||||
|
let result = `{a|${hourMinute}}\n{b|${yearMonthDay}}`; |
||||||
|
if (props.timeType == 'day') { |
||||||
|
result = `{b|${yearMonthDay}}`; |
||||||
|
} |
||||||
|
return result |
||||||
|
}, |
||||||
|
rich: { |
||||||
|
a: { |
||||||
|
fontSize: 12, |
||||||
|
color: '#000', |
||||||
|
padding: [0, 0, 5, 0] // 上右下左的内边距,这里给上方小时分钟添加一点底部间距 |
||||||
|
}, |
||||||
|
b: { |
||||||
|
fontSize: 12, |
||||||
|
color: '#000' |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
yAxis: { |
||||||
|
type: "value", |
||||||
|
min: yAxisMin, |
||||||
|
max: yAxisMax, |
||||||
|
|
||||||
|
name: 'mm', |
||||||
|
nameLocation: 'middle', |
||||||
|
|
||||||
|
nameTextStyle: {//y轴上方单位的颜色 |
||||||
|
color: '#464b50' |
||||||
|
}, |
||||||
|
nameGap: 50, |
||||||
|
axisLine: { |
||||||
|
show: true, |
||||||
|
lineStyle: { |
||||||
|
color: "#ccc", |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
splitLine: { |
||||||
|
show: true, |
||||||
|
lineStyle: { |
||||||
|
color: "#ccc", |
||||||
|
}, |
||||||
|
}, |
||||||
|
axisLabel: { |
||||||
|
textStyle: { |
||||||
|
color: "#000000", |
||||||
|
}, |
||||||
|
// 添加formatter属性,保留两位小数 |
||||||
|
formatter: function (value) { |
||||||
|
return value.toFixed(2); |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
dataZoom: [ |
||||||
|
{ |
||||||
|
show: true, |
||||||
|
// height: 15, |
||||||
|
xAxisIndex: [0], |
||||||
|
bottom: "8%", |
||||||
|
start: 0, |
||||||
|
end: 100, |
||||||
|
handleIcon: |
||||||
|
"path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z", |
||||||
|
handleSize: "110%", |
||||||
|
handleStyle: { |
||||||
|
color: "#d3dee5", |
||||||
|
}, |
||||||
|
textStyle: { |
||||||
|
color: "#000", |
||||||
|
}, |
||||||
|
borderColor: "#90979c", |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "inside", |
||||||
|
show: true, |
||||||
|
// height: 15, |
||||||
|
start: 1, |
||||||
|
end: 35, |
||||||
|
}, |
||||||
|
// { |
||||||
|
// type: 'inside' |
||||||
|
// }, |
||||||
|
// { |
||||||
|
// type: 'slider' |
||||||
|
// } |
||||||
|
], |
||||||
|
series: series, |
||||||
|
}; |
||||||
|
|
||||||
|
return option |
||||||
|
// var app = { |
||||||
|
// currentIndex: -1, |
||||||
|
// }; |
||||||
|
// setInterval(function () { |
||||||
|
// var dataLen = option.series[0].data.length; |
||||||
|
|
||||||
|
// // 取消之前高亮的图形 |
||||||
|
// myChart.dispatchAction({ |
||||||
|
// type: "downplay", |
||||||
|
// seriesIndex: 0, |
||||||
|
// dataIndex: app.currentIndex, |
||||||
|
// }); |
||||||
|
// app.currentIndex = (app.currentIndex + 1) % dataLen; |
||||||
|
// //console.log(app.currentIndex); |
||||||
|
// // 高亮当前图形 |
||||||
|
// myChart.dispatchAction({ |
||||||
|
// type: "highlight", |
||||||
|
// seriesIndex: 0, |
||||||
|
// dataIndex: app.currentIndex, |
||||||
|
// }); |
||||||
|
// // 显示 tooltip |
||||||
|
// myChart.dispatchAction({ |
||||||
|
// type: "showTip", |
||||||
|
// seriesIndex: 0, |
||||||
|
// dataIndex: app.currentIndex, |
||||||
|
// }); |
||||||
|
// }, 1000); |
||||||
|
|
||||||
|
} |
||||||
|
</script> |
||||||
|
<style scoped> |
||||||
|
.chart-container { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
min-height: 300px; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,81 @@ |
|||||||
|
<!-- |
||||||
|
* @Author: waibao2 1@qq.com |
||||||
|
* @Date: 2025-10-13 14:12:37 |
||||||
|
* @LastEditors: waibao2 1@qq.com |
||||||
|
* @LastEditTime: 2025-10-23 11:27:53 |
||||||
|
* @FilePath: \qingtian-report-ui\src\components\ESelectSingle\index.vue |
||||||
|
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE |
||||||
|
--> |
||||||
|
<template> |
||||||
|
<div class="cascader-container"> |
||||||
|
<el-select-v2 filterable v-model="stnmId" :options="options" :props="defaultProps" placeholder="请选择" @change="handleChange" style="width: 240px" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref } from 'vue' |
||||||
|
const props = defineProps({ |
||||||
|
stationType: { |
||||||
|
type: String, |
||||||
|
default: 'A' |
||||||
|
}, |
||||||
|
requestPrefix: { |
||||||
|
type: String, |
||||||
|
default: '/basic/station/queryStnmandStnmId' |
||||||
|
}, |
||||||
|
defaultProps: { |
||||||
|
type: Object, |
||||||
|
default: () => { |
||||||
|
return { |
||||||
|
label: 'stnm', |
||||||
|
value: 'stnmId', |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
type: { |
||||||
|
type: String, |
||||||
|
default: 'radio' |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
// 定义 emit 事件 |
||||||
|
const emit = defineEmits(['stationChange', 'loadingChange']) |
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance() |
||||||
|
|
||||||
|
const stnmId = ref('') |
||||||
|
|
||||||
|
const handleChange = (val) => { |
||||||
|
stnmId.value = val |
||||||
|
emit('stationChange', val, options.value, stnm.value) |
||||||
|
emit('loadingChange', false); |
||||||
|
} |
||||||
|
|
||||||
|
const options = ref([]) |
||||||
|
const stnm = ref('') |
||||||
|
const getSingleStation = async () => { |
||||||
|
emit('loadingChange', true); |
||||||
|
try { |
||||||
|
let res = await proxy.axiosGet(props.requestPrefix, { 'type': proxy.stationType, isState: '0' }); |
||||||
|
|
||||||
|
if (props.type == 'radio') { |
||||||
|
options.value = res.data; |
||||||
|
} else { |
||||||
|
options.value = res.data[0].children; |
||||||
|
} |
||||||
|
await nextTick() |
||||||
|
stnmId.value = options.value[0].stnmId |
||||||
|
stnm.value = options.value[0].stnm |
||||||
|
|
||||||
|
emit('stationChange', stnmId.value, options.value, stnm.value) |
||||||
|
|
||||||
|
} catch (error) { |
||||||
|
|
||||||
|
} finally { |
||||||
|
emit('loadingChange', false); |
||||||
|
} |
||||||
|
} |
||||||
|
onMounted(() => { |
||||||
|
getSingleStation() |
||||||
|
}) |
||||||
|
</script> |
||||||
@ -0,0 +1,138 @@ |
|||||||
|
|
||||||
|
<template> |
||||||
|
<el-radio-group class="pad10" v-model="dataType" @change="handleChange" v-if="showRadioGroup"> |
||||||
|
<el-radio v-for="item in radioGroup" :value="item.value">{{item.label}}</el-radio> |
||||||
|
</el-radio-group> |
||||||
|
<el-input v-model="filterText" clearable class="w-60 mb10" placeholder="请选择" @input="onQueryChanged" /> |
||||||
|
<el-tree-v2 :height="700" v-load='treeLoading' ref="treeRef" style="max-width: 600px;" class="filter-tree flex-tree" show-checkbox :data="treeData" :props="defaultProps" node-key="id" :default-expanded-keys="defaultExpandedKeys" :default-checked-keys="defaultCheckedKeys " :filter-method="filterMethod" @node-click="handleNodeClick" @check-change="handleCheckChange" /> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref, reactive, watch, onMounted } from 'vue' |
||||||
|
const { proxy } = getCurrentInstance() |
||||||
|
const props = defineProps({ |
||||||
|
stationType: { |
||||||
|
type: String, |
||||||
|
default: 'A' |
||||||
|
}, |
||||||
|
showRadioGroup: { |
||||||
|
type: Boolean, |
||||||
|
default: false |
||||||
|
}, |
||||||
|
radioGroup: { |
||||||
|
type: Object, |
||||||
|
default: () => ( |
||||||
|
[ |
||||||
|
{ |
||||||
|
label: "区域", |
||||||
|
value: 0 |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "流域", |
||||||
|
value: 1 |
||||||
|
} |
||||||
|
] |
||||||
|
) |
||||||
|
}, |
||||||
|
|
||||||
|
}) |
||||||
|
|
||||||
|
// 定义 emit 事件 |
||||||
|
const emit = defineEmits(['stationChange', 'loadingChange']) |
||||||
|
|
||||||
|
// 添加缺失的响应式变量 |
||||||
|
const dataType = ref(0) // 单选框绑定值 |
||||||
|
let url = '/basic/stype/getTreeStationType/' // 请求URL |
||||||
|
|
||||||
|
const treeRef = ref() |
||||||
|
const filterText = ref('') |
||||||
|
|
||||||
|
const onQueryChanged = () => { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
const filterMethod = (value, data) => { |
||||||
|
if (!value) return true |
||||||
|
// 根据 defaultProps.label 的配置来访问节点标签 |
||||||
|
return data[defaultProps.label].includes(value) |
||||||
|
} |
||||||
|
// 监听 filterText 变化并触发过滤 |
||||||
|
watch(filterText, (val) => { |
||||||
|
if (treeRef.value) { |
||||||
|
treeRef.value.filter(val) |
||||||
|
} |
||||||
|
}) |
||||||
|
const defaultProps = { |
||||||
|
children: 'children', |
||||||
|
label: 'name', |
||||||
|
} |
||||||
|
// 改变单选框数据 |
||||||
|
const handleChange = (val) => { |
||||||
|
if (val == 0) { |
||||||
|
url = '/basic/stype/getTreeAreaStationByType/'; |
||||||
|
} else { |
||||||
|
url = '/basic/stype/getTreeBasinStationByType/'; |
||||||
|
} |
||||||
|
getTreeStation(); |
||||||
|
} |
||||||
|
|
||||||
|
// 添加节点点击事件处理函数 |
||||||
|
const handleNodeClick = (data, node) => { |
||||||
|
// 点击任意节点时,清空所有展开的节点 |
||||||
|
if (treeRef.value) { |
||||||
|
treeRef.value.setExpandedKeys([]) |
||||||
|
} |
||||||
|
} |
||||||
|
// 新增处理复选框变化的函数 |
||||||
|
const handleCheckChange = (data, checked, indeterminate) => { |
||||||
|
// 获取当前所有选中的节点 |
||||||
|
const checkedNodes = treeRef.value.getCheckedNodes(false, true); |
||||||
|
// 提取选中节点的ID列表 |
||||||
|
const checkedKeys = checkedNodes.map(node => node.id); |
||||||
|
// 发送事件给父组件 |
||||||
|
emit('stationChange', checkedKeys); |
||||||
|
emit('loadingChange', true); |
||||||
|
} |
||||||
|
|
||||||
|
// 改变单选框后请求接口 |
||||||
|
let treeData = ref([]) |
||||||
|
const defaultExpandedKeys = ref([]) |
||||||
|
const defaultCheckedKeys = ref([]) |
||||||
|
const treeLoading = ref(false) |
||||||
|
const getTreeStation = async () => { |
||||||
|
treeLoading.value = true; |
||||||
|
emit('loadingChange', true); |
||||||
|
try { |
||||||
|
let res = await proxy.axiosGet(url + proxy.stationType); |
||||||
|
if (res.code == 0) { |
||||||
|
treeData.value = res.data |
||||||
|
defaultCheckedKeys.value = res.defaultOption || res.checkedList |
||||||
|
defaultExpandedKeys.value = res.exList |
||||||
|
await nextTick() |
||||||
|
if (treeRef.value) { |
||||||
|
treeRef.value.setExpandedKeys(defaultExpandedKeys.value) |
||||||
|
} |
||||||
|
emit('stationChange', defaultCheckedKeys.value) |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
|
||||||
|
} finally { |
||||||
|
emit('loadingChange', false); |
||||||
|
treeLoading.value = false; |
||||||
|
} |
||||||
|
} |
||||||
|
onMounted(() => { |
||||||
|
getTreeStation() |
||||||
|
}) |
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.demo-tabs>.el-tabs__content { |
||||||
|
padding: 32px; |
||||||
|
color: #6b778c; |
||||||
|
font-size: 32px; |
||||||
|
font-weight: 600; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,54 @@ |
|||||||
|
<template> |
||||||
|
<div class="cascader-container"> |
||||||
|
<el-cascader v-model="defaultOption" :options="options" :props="cascaderProps" @change="handleChange" filterable class="w320" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import { ref } from 'vue' |
||||||
|
const props = defineProps({ |
||||||
|
stationType: { |
||||||
|
type: String, |
||||||
|
default: 'A' |
||||||
|
}, |
||||||
|
|
||||||
|
}) |
||||||
|
// 定义 emit 事件 |
||||||
|
const emit = defineEmits(['stationChange', 'loadingChange']) |
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance() |
||||||
|
const defaultOption = ref([]) |
||||||
|
|
||||||
|
const cascaderProps = { |
||||||
|
expandTrigger: 'click', |
||||||
|
} |
||||||
|
|
||||||
|
const handleChange = (val) => { |
||||||
|
defaultOption.value = val |
||||||
|
let stnmId = defaultOption.value[2] |
||||||
|
emit('stationChange', stnmId) |
||||||
|
emit('loadingChange', true); |
||||||
|
} |
||||||
|
|
||||||
|
const options = ref([]) |
||||||
|
const getSingleStation = async () => { |
||||||
|
emit('loadingChange', true); |
||||||
|
try { |
||||||
|
let res = await proxy.axiosGet('/basic/stype/getTreeStation2New/' + proxy.stationType); |
||||||
|
if (res.code === 0) { |
||||||
|
options.value = res.data; |
||||||
|
defaultOption.value = res.defaultOption; |
||||||
|
await nextTick() |
||||||
|
let stnmId = defaultOption.value[2] |
||||||
|
emit('stationChange', stnmId) |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
|
||||||
|
} finally { |
||||||
|
emit('loadingChange', false); |
||||||
|
} |
||||||
|
} |
||||||
|
onMounted(() => { |
||||||
|
getSingleStation() |
||||||
|
}) |
||||||
|
</script> |
||||||
@ -0,0 +1,156 @@ |
|||||||
|
<template> |
||||||
|
<div class="el-tree-select"> |
||||||
|
<el-select |
||||||
|
style="width: 100%" |
||||||
|
v-model="valueId" |
||||||
|
ref="treeSelect" |
||||||
|
:filterable="true" |
||||||
|
:clearable="true" |
||||||
|
@clear="clearHandle" |
||||||
|
:filter-method="selectFilterData" |
||||||
|
:placeholder="placeholder" |
||||||
|
> |
||||||
|
<el-option :value="valueId" :label="valueTitle"> |
||||||
|
<el-tree |
||||||
|
id="tree-option" |
||||||
|
ref="selectTree" |
||||||
|
:accordion="accordion" |
||||||
|
:data="options" |
||||||
|
:props="objMap" |
||||||
|
:node-key="objMap.value" |
||||||
|
:expand-on-click-node="false" |
||||||
|
:default-expanded-keys="defaultExpandedKey" |
||||||
|
:filter-node-method="filterNode" |
||||||
|
@node-click="handleNodeClick" |
||||||
|
></el-tree> |
||||||
|
</el-option> |
||||||
|
</el-select> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance(); |
||||||
|
|
||||||
|
const props = defineProps({ |
||||||
|
/* 配置项 */ |
||||||
|
objMap: { |
||||||
|
type: Object, |
||||||
|
default: () => { |
||||||
|
return { |
||||||
|
value: 'id', // ID字段名 |
||||||
|
label: 'label', // 显示名称 |
||||||
|
children: 'children' // 子级字段名 |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
/* 自动收起 */ |
||||||
|
accordion: { |
||||||
|
type: Boolean, |
||||||
|
default: () => { |
||||||
|
return false |
||||||
|
} |
||||||
|
}, |
||||||
|
/**当前双向数据绑定的值 */ |
||||||
|
value: { |
||||||
|
type: [String, Number], |
||||||
|
default: '' |
||||||
|
}, |
||||||
|
/**当前的数据 */ |
||||||
|
options: { |
||||||
|
type: Array, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
/**输入框内部的文字 */ |
||||||
|
placeholder: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
const emit = defineEmits(['update:value']); |
||||||
|
|
||||||
|
const valueId = computed({ |
||||||
|
get: () => props.value, |
||||||
|
set: (val) => { |
||||||
|
emit('update:value', val) |
||||||
|
} |
||||||
|
}); |
||||||
|
const valueTitle = ref(''); |
||||||
|
const defaultExpandedKey = ref([]); |
||||||
|
|
||||||
|
function initHandle() { |
||||||
|
nextTick(() => { |
||||||
|
const selectedValue = valueId.value; |
||||||
|
if(selectedValue !== null && typeof (selectedValue) !== 'undefined') { |
||||||
|
const node = proxy.$refs.selectTree.getNode(selectedValue) |
||||||
|
if (node) { |
||||||
|
valueTitle.value = node.data[props.objMap.label] |
||||||
|
proxy.$refs.selectTree.setCurrentKey(selectedValue) // 设置默认选中 |
||||||
|
defaultExpandedKey.value = [selectedValue] // 设置默认展开 |
||||||
|
} |
||||||
|
} else { |
||||||
|
clearHandle() |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
function handleNodeClick(node) { |
||||||
|
valueTitle.value = node[props.objMap.label] |
||||||
|
valueId.value = node[props.objMap.value]; |
||||||
|
defaultExpandedKey.value = []; |
||||||
|
proxy.$refs.treeSelect.blur() |
||||||
|
selectFilterData('') |
||||||
|
} |
||||||
|
function selectFilterData(val) { |
||||||
|
proxy.$refs.selectTree.filter(val) |
||||||
|
} |
||||||
|
function filterNode(value, data) { |
||||||
|
if (!value) return true |
||||||
|
return data[props.objMap['label']].indexOf(value) !== -1 |
||||||
|
} |
||||||
|
function clearHandle() { |
||||||
|
valueTitle.value = '' |
||||||
|
valueId.value = '' |
||||||
|
defaultExpandedKey.value = []; |
||||||
|
clearSelected() |
||||||
|
} |
||||||
|
function clearSelected() { |
||||||
|
const allNode = document.querySelectorAll('#tree-option .el-tree-node') |
||||||
|
allNode.forEach((element) => element.classList.remove('is-current')) |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
initHandle() |
||||||
|
}) |
||||||
|
|
||||||
|
watch(valueId, () => { |
||||||
|
initHandle(); |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang='scss' scoped> |
||||||
|
@import "@/assets/styles/variables.module.scss"; |
||||||
|
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item { |
||||||
|
padding: 0; |
||||||
|
background-color: #fff; |
||||||
|
height: auto; |
||||||
|
} |
||||||
|
|
||||||
|
.el-select-dropdown__item.selected { |
||||||
|
font-weight: normal; |
||||||
|
} |
||||||
|
|
||||||
|
ul li .el-tree .el-tree-node__content { |
||||||
|
height: auto; |
||||||
|
padding: 0 20px; |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.el-tree-node__content:hover), |
||||||
|
:deep(.el-tree-node__content:active), |
||||||
|
:deep(.is-current > div:first-child), |
||||||
|
:deep(.el-tree-node__content:focus) { |
||||||
|
background-color: mix(#fff, $--color-primary, 90%); |
||||||
|
color: $--color-primary; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,244 @@ |
|||||||
|
<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" v-show="showSearch" @submit.native.prevent> |
||||||
|
<el-form-item label="测站名称" prop="stnm"> |
||||||
|
<el-input v-model="queryParams.stnm" placeholder="请输入测站名称" clearable @keyup.enter.native="handleQuery" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="开始时间"> |
||||||
|
<el-date-picker v-model="queryParams.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" placeholder="选择开始时间" :disabled-date="disabledStartDate"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="结束时间"> |
||||||
|
<el-date-picker v-model="queryParams.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择结束时间" :disabled-date="disabledEndDate"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="预警类型" prop="alarmType"> |
||||||
|
<el-select v-model="queryParams.alarmType" placeholder="请选择预警类型" style="width:100%"> |
||||||
|
<el-option v-for="dict in alarm_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="处理状态" prop="isDeal"> |
||||||
|
<el-select v-model="queryParams.isDeal" placeholder="请选择处理状态" style="width:100%"> |
||||||
|
<el-option v-for="dict in alarm_isDeals" :key="dict.value" :label="dict.label" :value="dict.value"></el-option> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="遥测类型" prop="type"> |
||||||
|
<el-select v-model="queryParams.type" placeholder="请选择遥测类型" style="width:100%"> |
||||||
|
<el-option v-for="dict in alarm_data_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> |
||||||
|
<el-button icon="Refresh" @click="resetQuery">重置</el-button> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
<el-row :gutter="10" class="mb8"> |
||||||
|
<!-- <el-col :span="1.5"> |
||||||
|
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['message:value:remove']">删除</el-button> |
||||||
|
</el-col> --> |
||||||
|
<el-col :span="1.5"> |
||||||
|
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['message:value:export']">导出</el-button> |
||||||
|
</el-col> |
||||||
|
|
||||||
|
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> |
||||||
|
</el-row> |
||||||
|
</el-card> |
||||||
|
<div class="el-card-p card-shadow carder-border mt10 pad10 "> |
||||||
|
<el-table class="table-box" v-table-height v-loading="loading" :data="alarmList" @selection-change="handleSelectionChange"> |
||||||
|
<el-table-column type="selection" width="55" :align="alignment" /> |
||||||
|
<el-table-column type="index" width="55" :align="alignment" label="序号" /> |
||||||
|
<el-table-column label="测站名称" :align="alignment" prop="stnm" sortable /> |
||||||
|
<el-table-column label="遥测类型" :align="alignment" prop="type" /> |
||||||
|
<el-table-column label="预警类型" :align="alignment" prop="alarmType" /> |
||||||
|
<el-table-column label="预警内容" :align="alignment" prop="alarmContent" /> |
||||||
|
<el-table-column label="预警时间" :align="alignment" prop="alarmTime" sortable /> |
||||||
|
<el-table-column label="预警时长" :align="alignment" prop="alarmTotalLength" sortable /> |
||||||
|
<el-table-column label="预警状态" :align="alignment" prop="flag"> |
||||||
|
<template #default="scope"> |
||||||
|
<span v-if="scope.row.flag == 0" class="red">预警中</span> |
||||||
|
<span v-if="scope.row.flag == 1" class="green">预警结束</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="短信" :align="alignment" prop="isSms"> |
||||||
|
<template #default="scope"> |
||||||
|
<span v-if="scope.row.isSms == 'n'" class="red">未生成</span> |
||||||
|
<span v-if="scope.row.isSms == 'y'" class="green">已生成</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
|
||||||
|
</el-table> |
||||||
|
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.page" v-model:limit="queryParams.limit" @pagination="getList" /> |
||||||
|
</div> |
||||||
|
|
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<script setup > |
||||||
|
import { ref, reactive, onMounted, } from 'vue' |
||||||
|
import dayjs from 'dayjs' |
||||||
|
const { proxy } = getCurrentInstance() |
||||||
|
const alignment = 'center' |
||||||
|
const showSearch = ref(true) |
||||||
|
const queryParams = reactive({ |
||||||
|
page: 1, |
||||||
|
limit: 10, |
||||||
|
stnm: '', |
||||||
|
isDeal: "", |
||||||
|
type: "", |
||||||
|
alarmType: "", |
||||||
|
startTime: dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss'), |
||||||
|
endTime: dayjs().format('YYYY-MM-DD HH:mm:ss') |
||||||
|
}) |
||||||
|
|
||||||
|
const loading = ref(false) |
||||||
|
const total = ref(0) |
||||||
|
const alarmList = ref([]) |
||||||
|
const getList = async () => { |
||||||
|
loading.value = true; |
||||||
|
try { |
||||||
|
const res = await proxy.axiosGet('/alarm/alarm/list2', queryParams) |
||||||
|
if (res.code == 0) { |
||||||
|
alarmList.value = res.rows |
||||||
|
total.value = res.count |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
|
||||||
|
} finally { |
||||||
|
loading.value = false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** 搜索按钮操作 */ |
||||||
|
const handleQuery = () => { |
||||||
|
queryParams.pageNum = 1 |
||||||
|
getList() |
||||||
|
} |
||||||
|
const resetQuery = () => { |
||||||
|
proxy.resetForm("queryForm"); |
||||||
|
handleQuery(); |
||||||
|
} |
||||||
|
|
||||||
|
// 多选框选中数据 |
||||||
|
let ids = ref([]) |
||||||
|
let names = ref([]) |
||||||
|
let single = ref(true) |
||||||
|
let multiple = ref(true) |
||||||
|
const handleSelectionChange = (selection) => { |
||||||
|
ids.value = selection.map(item => item.id) |
||||||
|
names.value = selection.map(item => item.stnm) |
||||||
|
single.value = selection.length !== 1 |
||||||
|
multiple.value = !selection.length |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** 删除按钮操作 */ |
||||||
|
const handleDelete = (row) => { |
||||||
|
let stnms = row.stnm || names.value |
||||||
|
const stnmIds = row.id || ids.value; |
||||||
|
proxy.$modal.confirm('是否确认删除测站名称为"' + stnms + '"的数据项?').then(async function () { |
||||||
|
let res = await proxy.axiosDelete('/alarm/alarm/delete/' + stnmIds); |
||||||
|
if (res.code === 200) { |
||||||
|
proxy.$modal.msgSuccess("删除成功"); |
||||||
|
getList(); |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
/** 导出按钮操作 */ |
||||||
|
const handleExport = async () => { |
||||||
|
let p = JSON.parse(JSON.stringify(queryParams)) |
||||||
|
param.page = 1; |
||||||
|
param.limit = 9999; |
||||||
|
let res = await proxy.axiosGet('/alarm/alarm/list', p); |
||||||
|
if (res.code === 0) { |
||||||
|
let table = []; |
||||||
|
table.push({ |
||||||
|
A: "测站名称", |
||||||
|
B: "数据类型", |
||||||
|
C: "预警类型", |
||||||
|
D: "预警级别", |
||||||
|
E: "预警内容", |
||||||
|
F: "预警时间", |
||||||
|
G: "预警时长(时)", |
||||||
|
H: "预警状态", |
||||||
|
I: "短信是否处理", |
||||||
|
}); |
||||||
|
res.rows.forEach(d => { |
||||||
|
let row = { |
||||||
|
A: d.stnm, |
||||||
|
B: d.type, |
||||||
|
C: d.alarmType, |
||||||
|
D: d.alarmLevel, |
||||||
|
E: d.alarmContent, |
||||||
|
F: d.alarmTime, |
||||||
|
G: (d.alarmTotalLength / 60.0 / 60.0).toFixed(1), |
||||||
|
H: d.flag === 0 ? "预警中" : "预警结束", |
||||||
|
I: d.isDeal === 'n' ? "否" : "是", |
||||||
|
}; |
||||||
|
table.push(row); |
||||||
|
}); |
||||||
|
let header = ["A", "B", "C", "D", "E", "F", "G", "H", "I"]; |
||||||
|
let fileName = "预警信息"; |
||||||
|
proxy.exportExcel(header, table, fileName); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
getList() |
||||||
|
}) |
||||||
|
const alarm_flags = ref([ |
||||||
|
{ |
||||||
|
label: "预警中", |
||||||
|
value: 0 |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "预警结束", |
||||||
|
value: 1 |
||||||
|
}, |
||||||
|
]) |
||||||
|
const alarm_isDeals = ref([ |
||||||
|
{ |
||||||
|
label: "是", |
||||||
|
value: 'y' |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "否", |
||||||
|
value: 'n' |
||||||
|
}, |
||||||
|
]) |
||||||
|
const alarm_data_type = ref([ |
||||||
|
{ |
||||||
|
value: 'A', |
||||||
|
label: '雨量' |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: 'B', |
||||||
|
label: '河道' |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: 'C', |
||||||
|
label: '水库' |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: 'D', |
||||||
|
label: '潮位' |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: 'E', |
||||||
|
label: '流量' |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: 'V', |
||||||
|
label: '电量' |
||||||
|
} |
||||||
|
]) |
||||||
|
const alarm_type = ref([ |
||||||
|
{ |
||||||
|
label: "设备故障", |
||||||
|
value: '设备故障' |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: "数据异常", |
||||||
|
value: '数据异常' |
||||||
|
}, |
||||||
|
]) |
||||||
|
</script> |
||||||
@ -0,0 +1,259 @@ |
|||||||
|
/* |
||||||
|
* @Description: 预警短信 |
||||||
|
*/ |
||||||
|
<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" v-show="showSearch" @submit.native.prevent> |
||||||
|
<el-form-item label="手机号" prop="phones"> |
||||||
|
<el-input v-model="queryParams.phones" placeholder="请输入手机号" clearable @keyup.enter.native="handleQuery" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="开始时间"> |
||||||
|
<el-date-picker v-model="queryParams.startTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择开始时间"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="结束时间"> |
||||||
|
<el-date-picker v-model="queryParams.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择结束时间"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> |
||||||
|
<el-button icon="Refresh" @click="resetQuery">重置</el-button> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
|
||||||
|
<el-row :gutter="10" class="mb8"> |
||||||
|
<el-col :span="1.5"> |
||||||
|
<el-button type="danger" plain icon="el-icon-delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['alarm:sms:remove']">删除</el-button> |
||||||
|
</el-col> |
||||||
|
<el-col :span="1.5"> |
||||||
|
<el-button type="warning" plain icon="el-icon-download" @click="handleExport" v-hasPermi="['alarm:sms:export']">导出</el-button> |
||||||
|
</el-col> |
||||||
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> |
||||||
|
</el-row> |
||||||
|
</el-card> |
||||||
|
<div class="el-card-p card-shadow carder-border mt10 pad10"> |
||||||
|
<el-table v-loading="loading" :data="smsList" @selection-change="handleSelectionChange"> |
||||||
|
<el-table-column type="selection" width="55" :align="alignment" /> |
||||||
|
<el-table-column type="index" width="55" :align="alignment" label="序号"></el-table-column> |
||||||
|
<el-table-column label="短信推送人员" :align="alignment" prop="employees" width="120" /> |
||||||
|
<el-table-column label="人员手机号" :align="alignment" prop="phones" width="120" /> |
||||||
|
<el-table-column label="告警内容" prop="smsContent" /> |
||||||
|
<el-table-column label="推送时间" :align="alignment" prop="createTime" width="180" /> |
||||||
|
<el-table-column label="发送类型" :align="alignment" prop="sendType" width="120"> |
||||||
|
<template #default="scope"> |
||||||
|
<span v-if="scope.row.sendType == 0 ">自动</span> |
||||||
|
<span v-if="scope.row.sendType == 1 ">手动</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="操作" :align="alignment" class-name="small-padding fixed-width" width="120"> |
||||||
|
<template #default="scope"> |
||||||
|
<el-button text type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['alarm:sms:remove']">删除</el-button> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
|
||||||
|
<pagination v-show="total>0" :total="total" :page.sync="queryParams.page" :limit.sync="queryParams.limit" @pagination="getList" /> |
||||||
|
</div> |
||||||
|
<!-- 添加或修改短信预警信息对话框 --> |
||||||
|
<el-dialog class="custom-dialog" :title="title" v-model="open" width="500px" append-to-body> |
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto"> |
||||||
|
<el-form-item label="预警信息" prop="alarmInfoId"> |
||||||
|
<el-input v-model="form.alarmInfoId" placeholder="请输入预警信息" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="短信推送人员" prop="employees"> |
||||||
|
<el-input v-model="form.employees" type="textarea" placeholder="请输入内容" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="手机号" prop="phones"> |
||||||
|
<el-input v-model="form.phones" type="textarea" placeholder="请输入内容" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="预警短信内容"> |
||||||
|
<editor v-model="form.smsContent" :min-height="192" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="是否推送" prop="isSend"> |
||||||
|
<el-input v-model="form.isSend" placeholder="请输入是否推送,n 未推送,y 已推送" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="短信发送时间" prop="sendTime"> |
||||||
|
<el-date-picker clearable v-model="form.sendTime" type="date" value-format="yyyy-MM-dd" placeholder="请选择短信发送时间"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
<template #footer> |
||||||
|
<div class="dialog-footer"> |
||||||
|
<el-button type="primary" @click="submitForm(formRef)" v-loading='btnLoading'>确 定</el-button> |
||||||
|
<el-button @click="cancel">取 消</el-button> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</el-dialog> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<script setup > |
||||||
|
import dayjs from 'dayjs' |
||||||
|
import { ref, reactive, onMounted } from 'vue' |
||||||
|
const { proxy } = getCurrentInstance() |
||||||
|
const alignment = 'center' |
||||||
|
const loading = ref(false) |
||||||
|
const ids = ref([]) |
||||||
|
const names = ref([]) |
||||||
|
const single = ref(true) |
||||||
|
const multiple = ref(true) |
||||||
|
const showSearch = ref(true) |
||||||
|
const total = ref(0) |
||||||
|
const smsList = ref([]) |
||||||
|
const title = ref('') |
||||||
|
const open = ref(false) |
||||||
|
const queryParams = reactive({ |
||||||
|
page: 1, |
||||||
|
limit: 10, |
||||||
|
alarmInfoId: null, |
||||||
|
employees: null, |
||||||
|
phones: null, |
||||||
|
smsContent: null, |
||||||
|
isSend: null, |
||||||
|
sendType: null, |
||||||
|
sendTime: null, |
||||||
|
stnm: null, |
||||||
|
startTime: dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss'), |
||||||
|
endTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), |
||||||
|
}) |
||||||
|
const form = reactive({}) |
||||||
|
const rules = ref({}) |
||||||
|
async function getList() { |
||||||
|
loading.value = true; |
||||||
|
try { |
||||||
|
let res = await proxy.axiosGet('/alarm/sms/list', queryParams); |
||||||
|
if (res.code === 0) { |
||||||
|
smsList.value = res.data; |
||||||
|
total.value = res.count; |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
|
||||||
|
} finally { |
||||||
|
loading.value = false; |
||||||
|
} |
||||||
|
} |
||||||
|
/** 搜索按钮操作 */ |
||||||
|
const handleQuery = () => { |
||||||
|
queryParams.page = 1; |
||||||
|
getList(); |
||||||
|
} |
||||||
|
/** 重置按钮操作 */ |
||||||
|
const resetQuery = () => { |
||||||
|
proxy.resetForm("queryForm"); |
||||||
|
handleQuery(); |
||||||
|
} |
||||||
|
// 多选框选中数据 |
||||||
|
const handleSelectionChange = (selection) => { |
||||||
|
ids.value = selection.map(item => item.id) |
||||||
|
names.value = selection.map(item => item.name) |
||||||
|
single.value = selection.length !== 1 |
||||||
|
multiple.value = !selection.length |
||||||
|
} |
||||||
|
/** 新增按钮操作 */ |
||||||
|
const handleAdd = () => { |
||||||
|
reset(); |
||||||
|
open.value = true; |
||||||
|
title.value = "添加短信预警信息"; |
||||||
|
} |
||||||
|
/** 修改按钮操作 */ |
||||||
|
const handleUpdate = async (row) => { |
||||||
|
reset(); |
||||||
|
const id = row.id || ids.value |
||||||
|
let res = await proxy.axiosGet('/alarm/sms/info/' + id); |
||||||
|
if (res.code === 0) { |
||||||
|
Object.assign(form, res.data); |
||||||
|
open.value = true; |
||||||
|
title.value = "修改短信预警信息"; |
||||||
|
} |
||||||
|
} |
||||||
|
const addMethod = async () => { |
||||||
|
let res = await proxy.axiosPost('/alarm/sms/add', form); |
||||||
|
if (res.code === 0) { |
||||||
|
proxy.$modal.msgSuccess("新增成功"); |
||||||
|
open.value = false; |
||||||
|
getList(); |
||||||
|
} |
||||||
|
} |
||||||
|
const editMethod = async () => { |
||||||
|
let res = await proxy.axiosPost('/alarm/sms/edit', form); |
||||||
|
if (res.code === 0) { |
||||||
|
proxy.$modal.msgSuccess("修改成功"); |
||||||
|
open.value = false; |
||||||
|
getList(); |
||||||
|
} |
||||||
|
} |
||||||
|
let formRef = ref(null) |
||||||
|
/** 提交按钮 */ |
||||||
|
const submitForm = async (formEl) => { |
||||||
|
if (!formEl) return |
||||||
|
await formEl.validate((valid, fields) => { |
||||||
|
if (valid) { |
||||||
|
if (form.id != null) { |
||||||
|
editMethod(); |
||||||
|
} else { |
||||||
|
addMethod(); |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/** 删除按钮操作 */ |
||||||
|
const handleDelete = async (row) => { |
||||||
|
let stnms = row.name || names.value |
||||||
|
const stnmIds = row.id || ids.value; |
||||||
|
proxy.$modal.confirm('是否确认删除站点名称为"' + stnms + '"的数据项?').then(async function () { |
||||||
|
let res = await proxy.axiosDelete('/alarm/sms/delete/' + stnmIds); |
||||||
|
if (res.code === 0) { |
||||||
|
proxy.$modal.msgSuccess("删除成功"); |
||||||
|
getList(); |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const handleExport = async () => { |
||||||
|
let param = queryParams; |
||||||
|
param.page = 1; |
||||||
|
param.limit = 9999; |
||||||
|
let res = await proxy.axiosGet('/alarm/sms/list', param); |
||||||
|
if (res.code === 0) { |
||||||
|
let table = []; |
||||||
|
table.push({ |
||||||
|
A: "短信推送人员", |
||||||
|
B: "人员手机号", |
||||||
|
C: "短信内容", |
||||||
|
D: "短信生成时间", |
||||||
|
E: "是否推送", |
||||||
|
F: "发送类型", |
||||||
|
G: "短信发送时间", |
||||||
|
}); |
||||||
|
res.data.forEach(d => { |
||||||
|
var row = { |
||||||
|
A: d.employees, |
||||||
|
B: d.phones, |
||||||
|
C: d.smsContent, |
||||||
|
D: d.createTime, |
||||||
|
E: d.isSend === 'n' ? "未推送" : "已推送", |
||||||
|
F: d.sendType === 0 ? "自动" : "手动", |
||||||
|
G: d.sendTime, |
||||||
|
}; |
||||||
|
table.push(row); |
||||||
|
}); |
||||||
|
var header = ["A", "B", "C", "D", "E", "F", "G"]; |
||||||
|
var fileName = "预警短信"; |
||||||
|
proxy.exportExcel(header, table, fileName); |
||||||
|
} |
||||||
|
} |
||||||
|
const cancel = () => { |
||||||
|
open.value = false; |
||||||
|
reset(); |
||||||
|
} |
||||||
|
const reset = () => { |
||||||
|
Object.assign(form, {}); |
||||||
|
proxy.resetForm("formRef"); |
||||||
|
} |
||||||
|
onMounted(() => { |
||||||
|
getList() |
||||||
|
|
||||||
|
}) |
||||||
|
|
||||||
|
</script> |
||||||
@ -0,0 +1,297 @@ |
|||||||
|
/* |
||||||
|
* @Description: 预警信息 |
||||||
|
*/ |
||||||
|
<template> |
||||||
|
<div class="app-container app-container-bg"> |
||||||
|
<el-card class="first-card" ref='firstCard' shadow="always"> |
||||||
|
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" @submit.native.prevent> |
||||||
|
<el-form-item label="测站名称" prop="stnm"> |
||||||
|
<el-input v-model="queryParams.stnm" placeholder="请输入测站名称" clearable @keyup.enter.native="handleQuery" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="开始时间"> |
||||||
|
<el-date-picker v-model="queryParams.startTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择开始时间" class="w200"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="结束时间"> |
||||||
|
<el-date-picker v-model="queryParams.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择结束时间" class="w200"> |
||||||
|
</el-date-picker> |
||||||
|
</el-form-item> |
||||||
|
|
||||||
|
<el-form-item label="预警状态"> |
||||||
|
<el-select clearable v-model="queryParams.flag" placeholder="请选择" @change="handleQuery"> |
||||||
|
<el-option v-for="item in flagS" :key="item.value" :label="item.name" :value="item.value"> |
||||||
|
</el-option> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="处理状态"> |
||||||
|
<el-select clearable v-model="queryParams.isDeal" placeholder="请选择" @change="handleQuery"> |
||||||
|
<el-option v-for="item in isDeals" :key="item.value" :label="item.name" :value="item.value"> |
||||||
|
</el-option> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="数据类型" prop="type"> |
||||||
|
<el-select clearable v-model="queryParams.type" placeholder="请选择数据类型" @change="handleQuery"> |
||||||
|
<el-option v-for="d in sTypes" :key="d.key" :label="d.label" :value="d.key"></el-option> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
|
||||||
|
<el-form-item label="预警类型" prop="alarmType"> |
||||||
|
<el-select clearable v-model="queryParams.alarmType" placeholder="请选择预警类型" @change="handleQuery"> |
||||||
|
<el-option v-for="d in alarmTypes" :key="d.key" :label="d.label" :value="d.key"></el-option> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
|
||||||
|
<el-form-item> |
||||||
|
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> |
||||||
|
<el-button icon="Refresh" @click="resetQuery">重置</el-button> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
|
||||||
|
<el-row :gutter="10" class="mb8"> |
||||||
|
<el-col :span="1.5"> |
||||||
|
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['alarm:alarm:remove']">删除</el-button> |
||||||
|
</el-col> |
||||||
|
<el-col :span="1.5"> |
||||||
|
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['alarm:alarm:export']">导出</el-button> |
||||||
|
</el-col> |
||||||
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> |
||||||
|
</el-row> |
||||||
|
</el-card> |
||||||
|
<div class="el-card-p card-shadow carder-border mt10 pad10"> |
||||||
|
<el-table v-loading="loading" :data="alarmList" @selection-change="handleSelectionChange"> |
||||||
|
<el-table-column type="selection" width="55" align="center" /> |
||||||
|
<el-table-column type="index" width="60" align="center" label="序号"></el-table-column> |
||||||
|
<el-table-column label="测站名称" align="center" prop="stnm" width="150" sortable /> |
||||||
|
<el-table-column label="遥测类型" align="center" prop="type" width="80" /> |
||||||
|
<el-table-column label="预警类型" align="center" prop="alarmType" width="80" /> |
||||||
|
<el-table-column label="预警内容" align="center" prop="alarmContent" /> |
||||||
|
<el-table-column label="预警时间" align="center" prop="alarmTime" width="180" sortable /> |
||||||
|
<el-table-column label="预警时长" align="center" prop="alarmTotalLength" width="130" sortable /> |
||||||
|
<el-table-column label="预警状态" align="center" prop="flag" width="80"> |
||||||
|
|
||||||
|
<template #default="scope"> |
||||||
|
<span v-if="scope.row.flag == 0 " class="red">预警中</span> |
||||||
|
<span v-if="scope.row.flag == 1 " class="green">预警结束</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="短信是否生成" align="center" prop="isSms" width="80"> |
||||||
|
<template #default="scope"> |
||||||
|
<span v-if="scope.row.isSms == 'n' " class="red">否</span> |
||||||
|
<span v-if="scope.row.isSms == 'y' " class="green">是</span> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
|
||||||
|
</el-table> |
||||||
|
|
||||||
|
<pagination v-show="total>0" :total="total" :page.sync="queryParams.page" :limit.sync="queryParams.limit" @pagination="getList" /> |
||||||
|
</div> |
||||||
|
<!-- 添加或修改预警信息对话框 --> |
||||||
|
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> |
||||||
|
|
||||||
|
</el-dialog> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<script setup> |
||||||
|
import dayjs from 'dayjs' |
||||||
|
import { ref, reactive, onMounted } from "vue"; |
||||||
|
const { proxy } = getCurrentInstance() |
||||||
|
const sTypes = ref([ |
||||||
|
{ |
||||||
|
key: 'A', |
||||||
|
label: '雨量' |
||||||
|
}, |
||||||
|
{ |
||||||
|
key: 'B', |
||||||
|
label: '河道' |
||||||
|
}, |
||||||
|
{ |
||||||
|
key: 'C', |
||||||
|
label: '水库' |
||||||
|
}, |
||||||
|
{ |
||||||
|
key: 'D', |
||||||
|
label: '潮位' |
||||||
|
}, |
||||||
|
{ |
||||||
|
key: 'E', |
||||||
|
label: '流量' |
||||||
|
}, |
||||||
|
{ |
||||||
|
key: 'V', |
||||||
|
label: '电量' |
||||||
|
} |
||||||
|
]) |
||||||
|
const alarmTypes = ref([ |
||||||
|
{ |
||||||
|
key: '设备故障', |
||||||
|
label: '设备故障' |
||||||
|
}, |
||||||
|
{ |
||||||
|
key: '数据异常', |
||||||
|
label: '数据异常' |
||||||
|
} |
||||||
|
]) |
||||||
|
const loading = ref(false) |
||||||
|
const ids = ref([]) |
||||||
|
const single = ref(true) |
||||||
|
const multiple = ref(true) |
||||||
|
const showSearch = ref(true) |
||||||
|
const total = ref(0) |
||||||
|
const alarmList = ref([]) |
||||||
|
const title = ref('') |
||||||
|
const open = ref(false) |
||||||
|
const queryParams = reactive({ |
||||||
|
page: 1, |
||||||
|
limit: 10, |
||||||
|
stnmId: null, |
||||||
|
stnm: null, |
||||||
|
stcd: null, |
||||||
|
type: null, |
||||||
|
alarmType: null, |
||||||
|
alarmLevel: null, |
||||||
|
alarmContent: null, |
||||||
|
alarmItem: null, |
||||||
|
alarmTime: null, |
||||||
|
alarmTotalLength: null, |
||||||
|
flag: null, |
||||||
|
isDeal: null, |
||||||
|
isSms: null, |
||||||
|
deleted: null, |
||||||
|
startTime: dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss'), |
||||||
|
endTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), |
||||||
|
|
||||||
|
}) |
||||||
|
const form = reactive({}) |
||||||
|
const rules = ref({}) |
||||||
|
const flagS = ref([ |
||||||
|
{ |
||||||
|
name: '预警中', |
||||||
|
value: 0 |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: '预警结束', |
||||||
|
value: 1 |
||||||
|
} |
||||||
|
]) |
||||||
|
const isDeals = ref([{ |
||||||
|
name: '是', |
||||||
|
value: 'y' |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: '否', |
||||||
|
value: 'n' |
||||||
|
}]) |
||||||
|
onMounted(() => { |
||||||
|
getList() |
||||||
|
}) |
||||||
|
const getList = async () => { |
||||||
|
loading.value = true; |
||||||
|
let res = await proxy.axiosGet('/alarm/alarm/list', queryParams); |
||||||
|
if (res.code === 0) { |
||||||
|
alarmList.value = res.data; |
||||||
|
total.value = res.count; |
||||||
|
for (var i = 0; i < alarmList.value.length; i++) { |
||||||
|
alarmList.value[i].alarmTotalLength = convertSeconds(alarmList.value[i].alarmTotalLength); |
||||||
|
} |
||||||
|
} |
||||||
|
loading.value = false; |
||||||
|
} |
||||||
|
const convertSeconds = (seconds) => { |
||||||
|
const days = Math.floor(seconds / 86400); // 1天=86400秒 |
||||||
|
let remaining = seconds % 86400; |
||||||
|
|
||||||
|
const hours = Math.floor(remaining / 3600); // 1小时=3600秒 |
||||||
|
remaining = remaining % 3600; |
||||||
|
|
||||||
|
const minutes = Math.floor(remaining / 60); // 1分钟=60秒 |
||||||
|
|
||||||
|
// 拼接非零单位 |
||||||
|
const result = []; |
||||||
|
if (days > 0) result.push(`${days}日`); |
||||||
|
if (hours > 0) result.push(`${hours}时`); |
||||||
|
if (minutes > 0) result.push(`${minutes}分`); |
||||||
|
|
||||||
|
// 处理全零情况(如输入0秒) |
||||||
|
return result.length ? result.join('') : '0分'; |
||||||
|
} |
||||||
|
const cancel = () => { |
||||||
|
open.value = false; |
||||||
|
reset(); |
||||||
|
} |
||||||
|
const reset = () => { |
||||||
|
Object.assign(form, {}); |
||||||
|
proxy.resetForm("formRef"); |
||||||
|
} |
||||||
|
/** 搜索按钮操作 */ |
||||||
|
const handleQuery = () => { |
||||||
|
queryParams.page = 1; |
||||||
|
getList(); |
||||||
|
} |
||||||
|
/** 重置按钮操作 */ |
||||||
|
const resetQuery = () => { |
||||||
|
proxy.resetForm("queryForm"); |
||||||
|
handleQuery(); |
||||||
|
} |
||||||
|
// 多选框选中数据 |
||||||
|
const handleSelectionChange = (selection) => { |
||||||
|
ids.value = selection.map(item => item.id) |
||||||
|
names.value = selection.map(item => item.stnm) |
||||||
|
single.value = selection.length !== 1 |
||||||
|
multiple.value = !selection.length |
||||||
|
} |
||||||
|
/** 新增按钮操作 */ |
||||||
|
const handleAdd = () => { |
||||||
|
reset(); |
||||||
|
open.value = true; |
||||||
|
title.value = "添加预警信息"; |
||||||
|
} |
||||||
|
/** 删除按钮操作 */ |
||||||
|
const handleDelete = async (row) => { |
||||||
|
let stnms = row.name || names.value |
||||||
|
const stnmIds = row.id || ids.value; |
||||||
|
proxy.$modal.confirm('是否确认删除站点名称为"' + stnms + '"的数据项?').then(async function () { |
||||||
|
let res = await proxy.axiosDelete('/alarm/alarm/delete/' + stnmIds); |
||||||
|
if (res.code === 0) { |
||||||
|
proxy.$modal.msgSuccess("删除成功"); |
||||||
|
getList(); |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
const handleExport = async () => { |
||||||
|
let param = queryParams; |
||||||
|
param.page = 1; |
||||||
|
param.limit = 9999; |
||||||
|
let res = await proxy.axiosGet('/alarm/sms/list', param); |
||||||
|
if (res.code === 0) { |
||||||
|
let table = []; |
||||||
|
table.push({ |
||||||
|
A: "测站名称", |
||||||
|
B: "数据类型", |
||||||
|
C: "预警类型", |
||||||
|
D: "预警级别", |
||||||
|
E: "预警内容", |
||||||
|
F: "预警时间", |
||||||
|
G: "预警时长(时)", |
||||||
|
H: "预警状态", |
||||||
|
I: "短信是否处理", |
||||||
|
}); |
||||||
|
res.data.forEach(d => { |
||||||
|
var row = { |
||||||
|
A: d.stnm, |
||||||
|
B: d.type, |
||||||
|
C: d.alarmType, |
||||||
|
D: d.alarmLevel, |
||||||
|
E: d.alarmContent, |
||||||
|
F: d.alarmTime, |
||||||
|
G: (d.alarmTotalLength / 60.0 / 60.0).toFixed(1), |
||||||
|
H: d.flag === 0 ? "预警中" : "预警结束", |
||||||
|
I: d.isDeal === 'n' ? "否" : "是", |
||||||
|
}; |
||||||
|
table.push(row); |
||||||
|
}); |
||||||
|
var header = ["A", "B", "C", "D", "E", "F", "G", "H", "I"]; |
||||||
|
var fileName = "预警信息"; |
||||||
|
proxy.exportExcel(header, table, fileName); |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
Loading…
Reference in new issue