<!-- 昨日供水管网概况 -->
|
<template>
|
<div class="w-full">
|
<div class="flex mb-4 flex-wrap">
|
<!-- TimeRange v-model 跟 @change 中的值会不一样,以@change 中为准 -->
|
<template v-if="visibleParams && visibleParams.length > 0">
|
<component
|
class="flex-0 m-1"
|
v-model="paramsValueList[index].value"
|
v-for="(item, index) in visibleParams as any"
|
:key="item.id"
|
:id="item.id"
|
:is="recordSetMapCom[item.type]"
|
:data="item"
|
:originData="originData"
|
@change="(val) => handleQueryChange(val, item)"
|
:disabled="chartLoading"
|
></component>
|
</template>
|
<slot> </slot>
|
|
<YRange v-model="yRange" @input="yRangeInput" />
|
<el-checkbox class="m-1" v-model="isMultiCompare" label="多日对比" @change="multiCompareChange"></el-checkbox>
|
</div>
|
<div :style="{ height: chartHeight }" v-resize="chartContainerResize" v-loading="chartLoading">
|
<div ref="chartRef"></div>
|
</div>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import type * as echarts from 'echarts';
|
import _ from 'lodash';
|
import moment from 'moment';
|
import type { PropType } from 'vue';
|
import { computed, ref } from 'vue';
|
import { SCATTER_SYMBOL_SIZE, getChatChartOption } from '../../../common';
|
import { useDrawChatChart } from '../../../hooks/useDrawChatChart';
|
import YRange from './components/YRange.vue';
|
import type { RecordSet, RecordSetParamsItem } from './types';
|
import { RecordSetParamsType, recordSetMapCom } from './types';
|
import { filterQuery } from '/@/api/ai/chat';
|
import { deepClone } from '/@/utils/other';
|
import { debounce } from '/@/utils/util';
|
import { ChartTypeEnum } from '../../../types';
|
|
const chartRef = ref<HTMLDivElement>(null);
|
const defaultDisplayType = 'line';
|
const yRange = ref({
|
min: null as number,
|
max: null as number,
|
});
|
// const props = defineProps({
|
// data: {
|
// type: Object as PropType<RecordSet>,
|
// },
|
// });
|
|
const props = defineProps({
|
data: {
|
type: Object as PropType<any>,
|
},
|
originData: {
|
type: Object as PropType<any>,
|
},
|
summaryIndex: {
|
type: Number,
|
},
|
chartHeight: {
|
type: String,
|
default: '20rem',
|
},
|
}) as {
|
data: RecordSet;
|
};
|
const chartLoading = ref(false);
|
|
const visibleParams = computed(() => {
|
const visibleList = props.data?.params?.filter((item) => !item?.hide) ?? [];
|
|
const newList: RecordSetParamsItem[] = [];
|
let nextMatchIndex = null;
|
for (let index = 0; index < visibleList.length; index++) {
|
if (nextMatchIndex === index) continue;
|
const current = visibleList[index];
|
const currentAny = current as any;
|
if (index !== visibleList.length - 1 && currentAny.type === RecordSetParamsType.StartTime) {
|
const next = visibleList[index + 1];
|
const nextAny = next as any;
|
|
if (nextAny.group === currentAny.group && nextAny.type === RecordSetParamsType.EndTime) {
|
newList.push({
|
type: RecordSetParamsType.TimeRange,
|
value: [currentAny.value, nextAny.value],
|
group: currentAny.group,
|
range: [currentAny, nextAny],
|
} as any);
|
nextMatchIndex = index + 1;
|
} else {
|
newList.push(current);
|
}
|
} else {
|
newList.push(current);
|
}
|
}
|
|
return newList;
|
});
|
const paramsValueList = ref(deepClone(visibleParams.value));
|
let groupedValues = null;
|
let timeIndex = undefined;
|
let valueIndex = undefined;
|
let nameIndex = undefined;
|
|
let timeCol = null;
|
let valueCol = null;
|
|
let preData = null;
|
|
let activeChartType: ChartTypeEnum = props.data.chart_type ?? ChartTypeEnum.Line;
|
|
const getChartTypeSeriesOption = (type: ChartTypeEnum) => {
|
let result = {};
|
switch (type) {
|
case ChartTypeEnum.Bar:
|
result = {
|
type: 'bar',
|
symbol: 'none',
|
};
|
|
break;
|
case ChartTypeEnum.Line:
|
result = {
|
type: 'line',
|
symbol: 'none',
|
smooth: true,
|
};
|
|
break;
|
|
case ChartTypeEnum.Scatter:
|
result = {
|
type: 'scatter',
|
symbol: 'circle',
|
symbolSize: SCATTER_SYMBOL_SIZE,
|
};
|
|
break;
|
default:
|
break;
|
}
|
|
return result;
|
};
|
|
const setNewOption = (series?: any[], extraOption: echarts.EChartsOption = {}) => {
|
const isEmpty = !series || series.length === 0;
|
if (isEmpty) {
|
series = Object.keys(groupedValues).map((item) => {
|
const values = groupedValues[item];
|
return {
|
name: item === 'default' ? '' : item,
|
data: values.map((item) => [item[timeIndex], item[valueIndex]]),
|
...getChartTypeSeriesOption(activeChartType)
|
|
};
|
});
|
}
|
const combineOption = _.defaultsDeep(extraOption, getChatChartOption(), {
|
grid: {
|
bottom: 20,
|
},
|
legend: {
|
top: 19,
|
show: series?.length > 1,
|
type: 'scroll',
|
},
|
toolbox: {
|
show: true,
|
feature: {
|
myBar: {
|
onclick: () => {
|
activeChartType = ChartTypeEnum.Bar;
|
chartInstance.value.setOption({
|
series: series.map((item) => ({
|
...item,
|
...getChartTypeSeriesOption(activeChartType),
|
})),
|
});
|
},
|
},
|
|
myScatter: {
|
onclick: () => {
|
activeChartType = ChartTypeEnum.Scatter;
|
|
chartInstance.value.setOption({
|
series: series.map((item) => ({
|
...item,
|
...getChartTypeSeriesOption(activeChartType),
|
})),
|
});
|
},
|
},
|
myLine: {
|
onclick: () => {
|
activeChartType = ChartTypeEnum.Line;
|
chartInstance.value.setOption({
|
series: series.map((item) => ({
|
...item,
|
...getChartTypeSeriesOption(activeChartType),
|
})),
|
});
|
},
|
},
|
},
|
},
|
|
title: {
|
text: preData?.title,
|
},
|
xAxis: {
|
name: timeCol?.title,
|
},
|
yAxis: {
|
name: valueCol?.title,
|
/** @description 不强制保留0 */
|
scale: true,
|
},
|
series: series,
|
} as echarts.EChartsOption);
|
chartInstance.value.setOption(combineOption, {
|
notMerge: true,
|
});
|
};
|
|
const handleData = () => {
|
const data = props.data;
|
if (!data || !data.cols || !data.values) {
|
return;
|
}
|
preData = data;
|
const xType = 'time';
|
timeIndex = data.cols.findIndex((item) => item.type === 'time');
|
if (timeIndex === -1) {
|
timeIndex = 0;
|
}
|
timeCol = data.cols[timeIndex];
|
|
valueIndex = data.cols.findIndex((item) => item.type === 'value');
|
if (valueIndex === -1) {
|
valueIndex = 2;
|
}
|
valueCol = data.cols[valueIndex];
|
|
let nameCol = null;
|
groupedValues = null;
|
if (data.chart === 'muli_line') {
|
nameIndex = data.cols.findIndex((item) => item.type === 'name');
|
if (nameIndex === -1) {
|
nameIndex = 1;
|
}
|
nameCol = data.cols[nameIndex];
|
groupedValues = _.groupBy(data.values, (item) => item[nameIndex]);
|
} else if (data.chart === 'single_line') {
|
groupedValues = {
|
default: data.values,
|
};
|
} else {
|
// 默认都当muli_line
|
let nameIndex = data.cols.findIndex((item) => item.type === 'name');
|
if (nameIndex === -1) {
|
nameIndex = 1;
|
}
|
nameCol = data.cols[nameIndex];
|
groupedValues = _.groupBy(data.values, (item) => item[nameIndex]);
|
}
|
};
|
|
const drawChart = () => {
|
const data = props.data;
|
if (!data || !data.cols || !data.values) {
|
return;
|
}
|
handleData();
|
setNewOption();
|
};
|
const { chartContainerResize, chartInstance } = useDrawChatChart({ chartRef, drawChart });
|
|
// 更换列表
|
const changeMap = new Map<string, string>(null);
|
|
const handleQueryChange = async (val: any, item: RecordSetParamsItem) => {
|
if (!val) return;
|
|
const historyId = (props as any).originData.historyId;
|
const summaryIndex = (props as any).summaryIndex;
|
let res = null;
|
|
try {
|
if (item.type === RecordSetParamsType.TimeRange) {
|
changeMap.set(item.range[0].id, val[0]), changeMap.set(item.range[1].id, val[1]);
|
} else {
|
changeMap.set(item.id, val);
|
}
|
const paramsObj = {};
|
for (const [key, value] of changeMap) {
|
paramsObj[key] = value;
|
}
|
const params = {
|
history_id: historyId,
|
query_index: summaryIndex,
|
param_json: JSON.stringify(paramsObj),
|
};
|
res = await filterQuery(params);
|
chartLoading.value = true;
|
} finally {
|
chartLoading.value = false;
|
}
|
|
const title = res?.values?.title;
|
const values = res?.values?.values ?? [];
|
groupedValues = _.groupBy(values, (item) => item[nameIndex]);
|
|
if (isMultiCompare.value) {
|
handleMultiCompare();
|
} else {
|
chartInstance.value.setOption({
|
title: {
|
text: title,
|
},
|
series:
|
groupedValues &&
|
Object.keys(groupedValues).map((item) => {
|
const values = groupedValues[item];
|
return {
|
data: values.map((item) => [item[timeIndex], item[valueIndex]]),
|
};
|
}),
|
});
|
}
|
};
|
|
const getSingleDayOption = (day=COMMON_DAY) => ({
|
tooltip: {
|
show: true,
|
trigger: 'axis',
|
formatter(params) {
|
const itemList = params.map((item, index) => {
|
return `<div style="margin: ${index === 0 ? 0 : 10}px 0 0; line-height: 1">
|
<div style="margin: 0px 0 0; line-height: 1">
|
${item.marker}<span style="font-size: 14px; color: #666; font-weight: 400; margin-left: 2px"
|
>${item.seriesName}</span
|
><span style="float: right; margin-left: 20px; font-size: 14px; color: #666; font-weight: 900">${item.data[1]}</span>
|
<div style="clear: both"></div>
|
</div>
|
<div style="clear: both"></div>
|
</div>`;
|
});
|
|
const result = `<div style="margin: 0px 0 0; line-height: 1">
|
<div style="margin: 0px 0 0; line-height: 1">
|
<div style="font-size: 14px; color: #666; font-weight: 400; line-height: 1">${params?.[0]?.data[0]?.slice(10, 16)}</div>
|
<div style="margin: 10px 0 0; line-height: 1">
|
${itemList.join('')}
|
<div style="clear: both"></div>
|
</div>
|
<div style="clear: both"></div>
|
</div>
|
<div style="clear: both"></div>
|
</div>`;
|
return result;
|
},
|
},
|
xAxis: {
|
min: day + ' 00:00:00',
|
max: day + ' 23:59:59',
|
splitNumber: 10,
|
axisLabel: {
|
formatter: (val) => {
|
const newVal = moment(val).format('HH:mm');
|
return newVal;
|
},
|
showMaxLabel: true,
|
},
|
},
|
} as echarts.EChartsOption);
|
//#region ====================== 设置Y范围 ======================
|
const debounceSetYRange = debounce((val) => {
|
chartInstance.value.setOption({
|
yAxis: {
|
min: val.min,
|
max: val.max,
|
},
|
});
|
});
|
|
const yRangeInput = (val) => {
|
debounceSetYRange(val);
|
};
|
|
//#endregion
|
|
//#region ====================== 多日对比 ======================
|
// 多日对比基准时间
|
const COMMON_DAY = '2024-07-26';
|
|
const isMultiCompare = ref(false);
|
const handleMultiCompare = () => {
|
if (!isMultiCompare.value) return;
|
const cloneData = deepClone(groupedValues);
|
const seriesData = Object.keys(cloneData).reduce((preVal, curVal, curIndex, arr) => {
|
const values = cloneData[curVal];
|
const isMulti = arr.length > 1;
|
const groupByDateValues = _.groupBy(values, (item) => moment(item[timeIndex]).format('YYYY-MM-DD'));
|
for (const key in groupByDateValues) {
|
if (Object.prototype.hasOwnProperty.call(groupByDateValues, key)) {
|
const val = groupByDateValues[key];
|
|
const newVal = val.map((item) => {
|
// 新名称
|
item[nameIndex] = isMulti ? `${curVal}_${key}` : `${key}`;
|
item[timeIndex] = COMMON_DAY + ' ' + moment(item[timeIndex]).format('HH:mm:ss');
|
return item;
|
});
|
|
preVal.push(newVal);
|
}
|
}
|
return preVal;
|
}, []);
|
const series = seriesData.map<echarts.SeriesOption>((item) => ({
|
name: item[0]?.[nameIndex],
|
data: item.map((item) => [item[timeIndex], item[valueIndex]]),
|
...getChartTypeSeriesOption(activeChartType),
|
}));
|
setNewOption(series, getSingleDayOption());
|
};
|
const multiCompareChange = (val) => {
|
if (!groupedValues) return;
|
if (val) {
|
handleMultiCompare();
|
} else {
|
setNewOption();
|
}
|
};
|
//#endregion
|
|
defineExpose({
|
drawChart,
|
isMultiCompare,
|
handleMultiCompare,
|
handleData,
|
});
|
</script>
|
<style scoped lang="scss"></style>
|