<template>
|
<div class="model-binding-right-box">
|
<titleBox :title="state.selectSinalInfo?.LogicalName">
|
<template v-slot:right>
|
<div class="titleBoxRightSlot">
|
日期:
|
<el-date-picker
|
v-model="state.themeTime"
|
type="date"
|
@change="getDateTime"
|
:disabled-date="disablesDate"
|
clearable
|
value-format="YYYY-MM-DD"
|
/>
|
|
<el-button class="ml10" :disabled="queryDisabled" type="primary" @click="timeQuery(1)">查询</el-button>
|
<el-button class="ml10" :disabled="queryDisabled" type="primary" @click="timeQuery(2)">上一天</el-button>
|
<el-button class="ml10" :disabled="queryDisabled" type="primary" @click="timeQuery(3)">下一天</el-button>
|
</div>
|
</template>
|
</titleBox>
|
<div :style="`display: flex; height: ${boxHeight}`" v-loading="state.echartLoading">
|
<div id="chartMain" class="w100 h100"></div>
|
</div>
|
</div>
|
</template>
|
<script setup lang="ts">
|
import * as echarts from 'echarts';
|
import { ElMessage } from 'element-plus';
|
import { definePropType } from 'element-plus/es/utils/vue/props/runtime';
|
import moment from 'moment';
|
import { onActivated, onDeactivated, onMounted, onUnmounted, reactive, ref, shallowRef, toRefs } from 'vue';
|
import { GetDisplayParasByID, GetEChartLastRecord, GetLimitBySignalIDOfDay, GetMeasureParasByID } from '/@/api/monitor/list';
|
import titleBox from '/@/components/titleBox.vue';
|
import { calMaxMinIntervalByChartData, normalBoundGap } from '/@/utils/chart';
|
import { formatTime, getDay } from '/@/utils/istation/common.js';
|
|
const myChart = shallowRef(null);
|
const m_dataInterval = ref(null);
|
|
// 定义变量内容
|
const state = reactive({
|
themeTime: '' as any,
|
getFormatJsonInfo: {
|
FormatType: '' as any,
|
DisplayParas: {
|
DisplayItems: [],
|
},
|
} as any, //获取参数
|
chartData: [],
|
getFreshData: [], //x轴y轴的数据
|
selectSignalID: '',
|
selectSignalName: '',
|
echartTitle: '',
|
selectSinalInfo: {} as any,
|
echartLoading: false,
|
getMeasureParaInfo: {} as any,
|
piecesConfigList: [],
|
isShowVisual: false,
|
});
|
|
onMounted(() => {
|
let getTime = getDataTime();
|
state.themeTime = getTime[0];
|
selfAdaption();
|
window.addEventListener('resize', selfAdaption);
|
});
|
onActivated(async () => {
|
if (isRefreshPage.value && state.selectSinalInfo.LogicalID != null) {
|
// state.selectSinalInfo = selectDataInfo.value;
|
await getFormatJson();
|
}
|
});
|
onDeactivated(() => {
|
//清除定时器
|
eChartCloseInterval();
|
});
|
onUnmounted(() => {
|
//清除定时器
|
eChartCloseInterval();
|
});
|
//#region ====================== 图表传参 ======================
|
const props = defineProps({
|
selectDataInfo: {
|
type: definePropType<{
|
LogicalID: string;
|
LogicalName: string;
|
}>(Object),
|
},
|
boxHeight: {
|
type: String,
|
default: '100%',
|
},
|
modelValue: {
|
type: Array,
|
default: () => {
|
return [];
|
},
|
},
|
isRefreshPage: {
|
type: Boolean,
|
default: false,
|
},
|
queryDisabled: {
|
type: Boolean,
|
default: false,
|
},
|
});
|
let { selectDataInfo, queryDisabled, isRefreshPage } = toRefs(props);
|
//#endregion
|
//#region ====================== 设置时间事件 ======================
|
//默认时间选择昨天/今天/明天
|
const getDataTime = () => {
|
let today = moment().format('YYYY-MM-DD');
|
return [today];
|
};
|
//获取日期的时间
|
const getDateTime = (val) => {
|
state.themeTime = val;
|
if (!queryDisabled.value) {
|
getChartList();
|
}
|
};
|
// 时间限制
|
const disablesDate = (time) => {
|
return time.getTime() > new Date().getTime();
|
};
|
//查询时间
|
const timeQuery = (type) => {
|
let yesterday = moment(state.themeTime).subtract(1, 'day').format('YYYY-MM-DD');
|
let startDate = moment(state.themeTime).add(1, 'days').startOf('days').format('YYYY-MM-DD');
|
let today = moment().format('YYYY-MM-DD');
|
if (type == 1) {
|
getChartList();
|
}
|
if (type == 2) {
|
state.themeTime = yesterday;
|
getChartList();
|
}
|
if (type == 3) {
|
if (startDate && startDate > today) {
|
return ElMessage.warning('别点了,不能超过今天哦!');
|
}
|
state.themeTime = startDate;
|
getChartList();
|
}
|
};
|
//#endregion
|
//#region ====================== 绘图init ======================
|
const getTableData = async () => {
|
selfAdaption();
|
state.selectSinalInfo = selectDataInfo.value;
|
await getFormatJson();
|
};
|
//获取格式显示参数
|
const getFormatJson = async () => {
|
myChart.value && myChart.value.clear();
|
state.echartLoading = true;
|
await GetDisplayParasByID({
|
ID: state.selectSinalInfo.LogicalID,
|
}).then((res) => {
|
if (res.Code != 0) {
|
return ElMessage.error('测点不可选');
|
}
|
if (res.Data == null || res.Data == '') {
|
return clearEChart();
|
}
|
state.getFormatJsonInfo = res.Data;
|
state.getFormatJsonInfo.DisplayParas.DisplayItems = res.Data.DisplayParas.Items;
|
getMeasureParasByID();
|
// getChartList();
|
});
|
};
|
//获取data数据列表getDataID
|
const getChartList = async () => {
|
myChart.value && myChart.value.clear();
|
|
//清除刷新定时器
|
if (m_dataInterval.value != null) {
|
clearInterval(m_dataInterval.value);
|
m_dataInterval.value = null;
|
}
|
await GetLimitBySignalIDOfDay({
|
Limit: 10000,
|
SignalID: state.selectSinalInfo.LogicalID,
|
Day: state.themeTime,
|
}).then((res) => {
|
state.echartLoading = false;
|
if (res.Code != 0) {
|
return;
|
}
|
if (res.Data == null || res.Data == '') {
|
ElMessage.warning('该日期暂无数据');
|
return;
|
}
|
const getEchartData = [];
|
res.Data.forEach((item) => {
|
if (item.DataStatus && item.DataStatus != -1) {
|
getEchartData.push(item);
|
}
|
});
|
|
state.chartData = getEchartData;
|
if (state.getFormatJsonInfo.FormatType == 1) {
|
setTimeout(() => {
|
drawBar();
|
}, 100);
|
}
|
if (state.getFormatJsonInfo.FormatType == 2) {
|
setTimeout(() => {
|
formatBar();
|
}, 100);
|
}
|
});
|
};
|
// 初始化echarts折线图
|
const drawBar = () => {
|
// myChart.value && myChart.value.clear();
|
//清除刷新定时器
|
if (m_dataInterval.value != null) {
|
clearInterval(m_dataInterval.value);
|
m_dataInterval.value = null;
|
}
|
//先获取Dom上的实例
|
let chartDom = echarts.getInstanceByDom(document.getElementById('chartMain') as HTMLDivElement);
|
//然后判断实例是否存在,如果不存在,就创建新实例
|
if (chartDom == null) {
|
chartDom = echarts.init(document.getElementById('chartMain') as HTMLDivElement);
|
}
|
myChart.value = chartDom;
|
let x_yData = [];
|
let y_calData = []; //Y轴的数据源
|
state.chartData.forEach((item) => {
|
x_yData.push({ value: [item.DataTime, item.DataValue] });
|
state.getFormatJsonInfo.DisplayParas.UnitValue = item.UnitName;
|
y_calData.push(item.DataValue);
|
});
|
state.getFreshData = x_yData; //给刷新的赋值
|
var series_arr = [];
|
series_arr.push({
|
type: 'line',
|
showSymbol: false,
|
emphasis: {
|
scale: false,
|
},
|
data: x_yData,
|
itemStyle: {
|
color: '#4169E1',
|
},
|
lineStyle: {
|
color: '#4169E1',
|
},
|
});
|
let option = {
|
tooltip: {
|
trigger: 'axis',
|
},
|
animation: false, // 只关闭这个系列的动画
|
grid: {
|
top: 50,
|
left: 50,
|
right: 120,
|
containLabel: true,
|
},
|
xAxis: {
|
name: '时间',
|
type: 'time',
|
splitLine: {
|
show: true,
|
interval: 'auto', //坐标轴分隔线的显示间隔
|
},
|
axisLabel: {
|
showMaxLabel: true,
|
formatter: function (v, index) {
|
var date = new Date(v);
|
let text = formatTime('hh:mm', date);
|
return text;
|
},
|
},
|
minInterval: 3600 * 4 * 1000, //12
|
},
|
yAxis: {
|
name: state.getFormatJsonInfo.DisplayParas.UnitValue,
|
type: 'value',
|
splitLine: {
|
show: true,
|
},
|
axisLabel: {
|
formatter: function (val) {
|
// console.log(val);
|
var value = val;
|
if (val >= 1000 && val < 1000000) {
|
value = val / 1000 + 'k';
|
} else if (val >= 1000000) {
|
value = val / 1000000 + 'm';
|
}
|
return value;
|
},
|
},
|
// 整条y轴
|
axisLine: {
|
show: true,
|
},
|
scale: true,
|
},
|
|
dataZoom: [
|
{
|
id: 'dataZoomX',
|
type: 'slider',
|
xAxisIndex: [0],
|
start: 0,
|
end: 100,
|
bottom: 30,
|
filterMode: 'filter',
|
handleIcon:
|
'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
|
handleSize: '80%',
|
handleStyle: {
|
color: '#fff',
|
shadowBlur: 3,
|
shadowColor: 'rgba(0, 0, 0, 0.9)',
|
shadowOffsetX: 2,
|
shadowOffsetY: 2,
|
},
|
borderColor: '#999',
|
},
|
{
|
// 这个dataZoom组件,也控制x轴。
|
type: 'inside', // 这个 dataZoom 组件是 inside 型 dataZoom 组件
|
start: 0, // 左边在 24% 的位置。
|
end: 100, // 右边在 100% 的位置。
|
},
|
],
|
series: series_arr,
|
};
|
myChart.value && myChart.value.setOption(option);
|
if (m_dataInterval.value == null) {
|
startInterval();
|
}
|
// let calYAxis = calMaxMinIntervalByChartData(y_calData, 1);
|
// myChart.value &&
|
// myChart.value.setOption({
|
// yAxis: {
|
// min: calYAxis.min,
|
// max: calYAxis.max,
|
// data: calYAxis,
|
// boundaryGap: ['20%', '20%'],
|
// },
|
// });
|
const { LowerValue, LowValue, HighValue, HigherValue } = state.getMeasureParaInfo.MeasureParas;
|
let y_data = [LowerValue, LowValue, HighValue, HigherValue];
|
if (state.getMeasureParaInfo.FormatType == 1 && state.getMeasureParaInfo.MeasureType == 1 && state.isShowVisual) {
|
let customerYAxis = calMaxMinIntervalByChartData(y_data, 1);
|
myChart.value &&
|
myChart.value.setOption({
|
visualMap: {
|
type: 'piecewise',
|
top: 50,
|
right: 10,
|
pieces: state.piecesConfigList,
|
},
|
yAxis: {
|
type: 'value',
|
min: customerYAxis.min,
|
max: customerYAxis.max,
|
data: customerYAxis,
|
boundaryGap: normalBoundGap,
|
// 其他Y轴相关配置...
|
},
|
series: [
|
{
|
animation: false, // 禁用动画
|
markLine: {
|
silent: true,
|
data: [
|
{
|
yAxis: LowerValue,
|
// 单独配置每条线的样式
|
lineStyle: {
|
color: 'none',
|
},
|
},
|
{
|
yAxis: LowValue,
|
// 单独配置每条线的样式
|
lineStyle: {
|
color: 'green',
|
},
|
},
|
{
|
yAxis: HighValue,
|
lineStyle: {
|
color: '#FBDB0F',
|
},
|
},
|
{
|
yAxis: HigherValue,
|
lineStyle: {
|
color: '#FD0100',
|
},
|
},
|
],
|
},
|
},
|
],
|
});
|
}
|
selfAdaption();
|
};
|
//根据信号ID获取计量参数
|
const getMeasureParasByID = async () => {
|
await GetMeasureParasByID({ ID: state.selectSinalInfo.LogicalID }).then(async (res) => {
|
if (res.Code != 0) {
|
return ElMessage.warning(res.Message);
|
}
|
if (res.Data == null || res.Data == '') {
|
ElMessage.warning('计量参数未配置');
|
return;
|
}
|
const result = res.Data.MeasureParas;
|
state.getMeasureParaInfo = res.Data;
|
if (res.Data.FormatType == 2 && res.Data.MeasureType == 1 && result == null) {
|
getChartList();
|
return;
|
}
|
if (result.LowerValue == null && result.LowValue == null && result.HighValue == null && result.HigherValue == null) {
|
state.isShowVisual = false;
|
getChartList();
|
state.piecesConfigList = [];
|
myChart.value &&
|
myChart.value.setOption({
|
visualMap: {
|
show: false,
|
pieces: [],
|
},
|
series: [
|
{
|
markLine: {
|
data: [],
|
},
|
},
|
],
|
});
|
return;
|
}
|
|
if (res.Data.FormatType == 1 && res.Data.MeasureType == 1) {
|
state.isShowVisual = true;
|
await getChartList();
|
let pieces = [
|
// {
|
// lt: result.LowerValue,
|
// label: '异常',
|
// color: 'Purple',
|
// },
|
{
|
gt: result.LowerValue,
|
lte: result.LowValue,
|
label: '正常',
|
color: 'green',
|
},
|
{
|
gt: result.LowValue,
|
lte: result.HighValue,
|
label: '警告',
|
color: '#FBDB0F',
|
},
|
{
|
gt: result.HighValue,
|
lte: result.HigherValue,
|
label: '危险',
|
color: '#FD0100',
|
},
|
// {
|
// gte: result.HigherValue,
|
// label: '异常',
|
// color: 'Purple',
|
// },
|
] as any;
|
state.chartData.forEach((item) => {
|
if (item.DataValue && item.DataValue < result.LowerValue) {
|
let piecesOption = {
|
lt: result.LowerValue,
|
label: '异常',
|
color: 'Purple',
|
};
|
pieces.unshift(piecesOption);
|
}
|
if (item.DataValue && item.DataValue > result.HigherValue) {
|
let piecesOption = {
|
gte: result.HigherValue,
|
label: '异常',
|
color: 'Purple',
|
};
|
pieces.push(piecesOption);
|
}
|
});
|
state.piecesConfigList = pieces;
|
return;
|
}
|
});
|
};
|
//开启定时器
|
const startInterval = () => {
|
let getTime = getDataTime();
|
if (state.themeTime == getTime[0]) {
|
m_dataInterval.value = setInterval(async () => {
|
await GetEChartLastRecord({ SignalID: state.selectSinalInfo.LogicalID }).then((res) => {
|
if (res.Code != 0) {
|
return;
|
}
|
if (res?.Code == 0) {
|
let getDataTime = res.Data.DataTime;
|
let getDataValue = res.Data.DataValue;
|
let arr_xyInf = {
|
value: [getDataTime, getDataValue],
|
};
|
if (state.getFreshData && state.getFreshData.length > 0) {
|
let getLastData = state.getFreshData[state.getFreshData.length - 1];
|
if (getLastData.value[0] != arr_xyInf.value[0] && getLastData.value[1] != arr_xyInf.value[1]) {
|
let xy_common = state.getFreshData;
|
xy_common.push(arr_xyInf);
|
setTimeout(() => {
|
myChart.value &&
|
myChart.value.setOption({
|
series: [
|
{
|
data: xy_common,
|
},
|
],
|
});
|
}, 100);
|
// return ElMessage.success('实时图表刷新成功');
|
return;
|
}
|
}
|
}
|
});
|
}, 200000);
|
}
|
};
|
//#endregion
|
//#region ====================== 图表自适应 ======================
|
const eChartCloseInterval = () => {
|
if (m_dataInterval.value != null) {
|
clearInterval(m_dataInterval.value);
|
m_dataInterval.value = null;
|
}
|
myChart.value && myChart.value.clear();
|
};
|
|
// 自适应
|
const selfAdaption = () => {
|
if (!myChart.value) return;
|
myChart.value.resize();
|
};
|
const clearEChart = () => {
|
setTimeout(() => {
|
ElMessage.warning('当日暂无数据');
|
myChart.value &&
|
myChart.value.setOption({
|
xAxis: {
|
min: '',
|
max: '',
|
},
|
yAxis: [],
|
series: [{ data: [] }],
|
});
|
}, 300);
|
return;
|
};
|
const clearTableData = () => {
|
setTimeout(() => {
|
state.chartData = [];
|
myChart.value && myChart.value.clear();
|
}, 300);
|
return;
|
};
|
// 导出对象
|
defineExpose({ eChartCloseInterval, getTableData, clearEChart, clearTableData });
|
//#endregion
|
//#region ====================== 文本图 ======================
|
//枚举数据格式化
|
function getFormatData4Enum(arr) {
|
var temp = arr[0];
|
var formatData = [];
|
for (var i = 0; i < arr.length - 1; i++) {
|
//var node = arr[i];
|
if (arr[i].DataValue != arr[i + 1].DataValue) {
|
var duration = ((+new Date(arr[i + 1].DataTime) - +new Date(temp.DataTime)) / 1000 / 60 / 60).toFixed(2);
|
formatData.push({ StartTime: temp.DataTime, EndTime: arr[i + 1].DataTime, Status: temp.DataValue, Duration: duration });
|
temp = arr[i + 1];
|
}
|
if (i == arr.length - 2) {
|
var len = arr.length - 1;
|
let duration = null;
|
duration = Math.round(((+new Date(arr[len].DataTime) - +new Date(temp.DataTime)) / 1000 / 60 / 60) * 100) / 100;
|
formatData.push({ StartTime: temp.DataTime, EndTime: arr[len].DataTime, Status: temp.DataValue, Duration: duration });
|
}
|
}
|
return formatData;
|
}
|
|
//文本图
|
const formatBar = () => {
|
myChart.value && myChart.value.clear();
|
//先获取Dom上的实例
|
let chartDom = echarts.getInstanceByDom(document.getElementById('chartMain') as HTMLDivElement);
|
//然后判断实例是否存在,如果不存在,就创建新实例
|
if (chartDom == null) {
|
chartDom = echarts.init(document.getElementById('chartMain') as HTMLDivElement);
|
}
|
myChart.value = chartDom;
|
|
var formatData = getFormatData4Enum(state.chartData); //数据转换
|
const series_data_enum = [];
|
const getParamsList = state.getFormatJsonInfo.DisplayParas.DisplayItems;
|
formatData.forEach((item) => {
|
getParamsList.forEach((paramsItem) => {
|
if (paramsItem.EnumName == item.Status) {
|
series_data_enum.push({
|
name: state.selectSinalInfo.LogicalName,
|
value: [
|
0,
|
+new Date(item.StartTime) - 0,
|
+new Date(item.EndTime) - 0,
|
item.Status,
|
item.Duration,
|
{ id: state.selectSinalInfo.LogicalID, name: state.selectSinalInfo.LogicalName },
|
],
|
itemStyle: {
|
color: paramsItem.DisplayColor,
|
},
|
});
|
}
|
});
|
});
|
let sel_record_day = formatData.length > 0 ? formatData[0].StartTime : '';
|
let option = {
|
tooltip: {
|
show: true,
|
formatter: function (params) {
|
return (
|
params.marker +
|
params.name +
|
': ' +
|
params.value[4] +
|
'小时' +
|
'<br/> ' +
|
'开始时间: ' +
|
new Date(params.value[1]).getHours() +
|
':' +
|
new Date(params.value[1]).getMinutes() +
|
'<br/> ' +
|
'结束时间: ' +
|
new Date(params.value[2]).getHours() +
|
':' +
|
new Date(params.value[2]).getMinutes()
|
);
|
},
|
},
|
title: {
|
text: '',
|
left: 'center',
|
},
|
legend: {
|
data: [],
|
},
|
grid: {
|
containLabel: true,
|
x: '25',
|
y: '65',
|
x2: '60',
|
y2: '80',
|
},
|
axisPointer: {
|
link: { xAxisIndex: 'all' },
|
},
|
xAxis: [
|
{
|
name: '时间',
|
min: +new Date(getDay(0, new Date(sel_record_day)) + ' 00:00:00'),
|
max: +new Date(getDay(1, new Date(sel_record_day)) + ' 00:00:00'),
|
interval: 60 * 60 * 1000 * 4,
|
axisLabel: {
|
formatter: function (val) {
|
var date = new Date(val);
|
var texts = [date.getHours()]; //date.getMinutes()
|
return texts.join(':');
|
},
|
},
|
axisTick: {
|
alignWithLabel: true,
|
show: false,
|
},
|
axisLine: { onZero: true, show: true },
|
minorTick: {
|
show: false,
|
splitNumber: 2,
|
},
|
minorSplitLine: {
|
show: true,
|
lineStyle: {
|
color: '#aaa',
|
type: 'dashed',
|
},
|
},
|
},
|
],
|
yAxis: [
|
{
|
inverse: true,
|
triggerEvent: true,
|
data: [state.selectSinalInfo.LogicalName],
|
},
|
],
|
dataZoom: [
|
{
|
type: 'inside',
|
start: 0,
|
end: 100,
|
},
|
{
|
start: 0,
|
end: 100,
|
},
|
],
|
|
series: [
|
{
|
type: 'custom',
|
renderItem: renderItemFunc,
|
itemStyle: {
|
opacity: 0.8,
|
},
|
encode: {
|
x: [1, 2],
|
y: 0,
|
},
|
data: series_data_enum,
|
},
|
],
|
};
|
myChart.value && myChart.value.setOption(option);
|
};
|
//自定义图表方法
|
function renderItemFunc(params, api) {
|
var categoryIndex = api.value(0);
|
var start = api.coord([api.value(1), categoryIndex]);
|
var end = api.coord([api.value(2), categoryIndex]);
|
var height = api.size([0, 1])[1] * 0.6 < 40 ? api.size([0, 1])[1] * 0.6 : 40;
|
var barLength = end[0] - start[0];
|
var runtime = api.value(3) + ':' + api.value(4) + 'h';
|
var flightNumberWidth = echarts.format.getTextRect(runtime).width;
|
var text = runtime;
|
text = barLength > flightNumberWidth ? text : '';
|
|
var rectShape = echarts.graphic.clipRectByRect(
|
{
|
x: start[0],
|
y: start[1] - height / 2,
|
width: end[0] - start[0],
|
height: height,
|
},
|
{
|
x: params.coordSys.x,
|
y: params.coordSys.y,
|
width: params.coordSys.width,
|
height: params.coordSys.height,
|
}
|
);
|
|
return {
|
type: 'rect',
|
shape: rectShape,
|
style: {
|
...api.style(),
|
text: text,
|
textFill: '#fff',
|
},
|
};
|
}
|
//#endregion
|
</script>
|
<style scoped lang="scss">
|
.model-binding-right-box {
|
width: 100%;
|
height: 100%;
|
// background: #fff;
|
}
|
.titleBoxRightSlot {
|
width: 100%;
|
}
|
</style>
|