Browse Source

docs:预警信息

master
waibao2 1 month ago
parent
commit
955857d1a4
  1. 25
      src/assets/styles/common.scss
  2. 5
      src/assets/styles/variables.module.scss
  3. 316
      src/components/ChartsBar/index.vue
  4. 310
      src/components/ChartsBarLine/index.vue
  5. 382
      src/components/ChartsTimeBar/index.vue
  6. 369
      src/components/ChartsTimeLine/index.vue
  7. 81
      src/components/ESelectSingle/index.vue
  8. 138
      src/components/ETree/index.vue
  9. 54
      src/components/SingleStation/index.vue
  10. 19
      src/components/SizeSelect/index.vue
  11. 156
      src/components/TreeSelect/index.vue
  12. 23
      src/main.js
  13. 6
      src/router/index.js
  14. 5
      src/store/modules/app.js
  15. 244
      src/views/alarm/list/index.vue
  16. 259
      src/views/alarm/sms/index.vue
  17. 297
      src/views/alarm/xinxi/index.vue

25
src/assets/styles/common.scss

@ -1,18 +1,38 @@ @@ -1,18 +1,38 @@
:root {
--first-card-height: 0px;
--el-card-border-radius: 4px;
--card-height-with-tags: calc(100vh - 50px - 34px - 31px - var(--first-card-height));
--card-height-without-tags: calc(100vh - 50px - 31px - var(--first-card-height));
--card-height-with-tags: calc(
100vh - 50px - 34px - 31px - var(--first-card-height)
);
--card-height-without-tags: calc(
100vh - 50px - 31px - var(--first-card-height)
);
--table-font-size-large: #{$table-font-size-large};
--table-font-size-default: #{$table-font-size-default};
--table-font-size-small: #{$table-font-size-small};
--table-font-size: var(--table-font-size-default); // 默认值
}
.app-container-bg {
background-color: #f2f2f2;
}
// 应用到所有el-table组件
.el-table {
font-size: var(--table-font-size);
.el-table__header th,
.el-table__body td {
font-size: var(--table-font-size);
}
}
.el-card-p {
height: var(--el-card-height, var(--card-height-without-tags)) !important;
box-sizing: border-box;
min-height: 0;
background-color: #fff;
}
.scroll-bar {
overflow-y: auto;
}
.card-shadow {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: var(--el-card-border-radius);
@ -110,7 +130,6 @@ @@ -110,7 +130,6 @@
overflow-x: hidden !important;
}
.vxe-select--panel.is--transfer {
z-index: 999999 !important;
}

5
src/assets/styles/variables.module.scss

@ -44,6 +44,11 @@ $--color-info: #909399; @@ -44,6 +44,11 @@ $--color-info: #909399;
$base-sidebar-width: 200px;
$table-font-size-large: 18px;
$table-font-size-default: 14px;
$table-font-size-small: 12px;
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {

316
src/components/ChartsBar/index.vue

@ -0,0 +1,316 @@ @@ -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,
}
}
},
// Yname
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>

310
src/components/ChartsBarLine/index.vue

@ -0,0 +1,310 @@ @@ -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>

382
src/components/ChartsTimeBar/index.vue

@ -0,0 +1,382 @@ @@ -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>

369
src/components/ChartsTimeLine/index.vue

@ -0,0 +1,369 @@ @@ -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>

81
src/components/ESelectSingle/index.vue

@ -0,0 +1,81 @@ @@ -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>

138
src/components/ETree/index.vue

@ -0,0 +1,138 @@ @@ -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>

54
src/components/SingleStation/index.vue

@ -0,0 +1,54 @@ @@ -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>

19
src/components/SizeSelect/index.vue

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
<template>
<div>
<el-dropdown trigger="click" @command="handleSetSize">
@ -34,6 +35,24 @@ @@ -34,6 +35,24 @@
appStore.setSize(size);
setTimeout("window.location.reload()", 1000);
}
function updateTableFontSize(size) {
const root = document.documentElement;
let fontSizeVar;
switch (size) {
case 'large':
fontSizeVar = 'var(--table-font-size-large)';
break;
case 'small':
fontSizeVar = 'var(--table-font-size-small)';
break;
case 'default':
default:
fontSizeVar = 'var(--table-font-size-default)';
break;
}
root.style.setProperty('--table-font-size', fontSizeVar);
}
</script>
<style lang='scss' scoped>

156
src/components/TreeSelect/index.vue

@ -0,0 +1,156 @@ @@ -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>

23
src/main.js

@ -65,7 +65,28 @@ import { @@ -65,7 +65,28 @@ import {
} from "@/utils/axiosService.js"
const app = createApp(App)
// 初始化表格字体大小
function initTableFontSize() {
const size = localStorage.getItem('size') || 'default';
let fontSizeVar;
switch (size) {
case 'large':
fontSizeVar = 'var(--table-font-size-large)';
break;
case 'small':
fontSizeVar = 'var(--table-font-size-small)';
break;
case 'default':
default:
fontSizeVar = 'var(--table-font-size-default)';
break;
}
document.documentElement.style.setProperty('--table-font-size', fontSizeVar);
}
// 在应用挂载前初始化
initTableFontSize();
// 全局方法挂载
app.config.globalProperties.useDict = useDict
app.config.globalProperties.download = download

6
src/router/index.js

@ -126,7 +126,7 @@ export const dynamicRoutes = [ @@ -126,7 +126,7 @@ export const dynamicRoutes = [
path: 'index/:ruleId(\\d+)',
component: () => import('@/views/basic/error/rules'),
name: 'Rules',
meta: { title: '设置预警规则', activeMenu: '/message/error' }
meta: { title: '预警类型设置/设置预警规则', activeMenu: '/message/error' }
}
]
},
@ -175,7 +175,7 @@ export const dynamicRoutes = [ @@ -175,7 +175,7 @@ export const dynamicRoutes = [
]
const router = createRouter({
history: createWebHistory(),
history: createWebHistory('/report/'),
routes: constantRoutes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
@ -186,4 +186,6 @@ const router = createRouter({ @@ -186,4 +186,6 @@ const router = createRouter({
},
});
export default router;

5
src/store/modules/app.js

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
// src/store/modules/app.js
import Cookies from 'js-cookie'
const useAppStore = defineStore(
@ -35,7 +36,9 @@ const useAppStore = defineStore( @@ -35,7 +36,9 @@ const useAppStore = defineStore(
},
setSize(size) {
this.size = size;
Cookies.set('size', size)
Cookies.set('size', size);
// 保存到localStorage以便在应用初始化时使用
localStorage.setItem('size', size);
},
toggleSideBarHide(status) {
this.sidebar.hide = status

244
src/views/alarm/list/index.vue

@ -0,0 +1,244 @@ @@ -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>

259
src/views/alarm/sms/index.vue

@ -0,0 +1,259 @@ @@ -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>

297
src/views/alarm/xinxi/index.vue

@ -0,0 +1,297 @@ @@ -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…
Cancel
Save