wujingjing
2024-10-09 345370831431a07d4680d0109cd1761e1e913ae9
src/components/chat/chatComponents/summaryCom/components/recordSet/RecordSet.vue
@@ -1,19 +1,28 @@
<!-- 昨日供水管网概况 -->
<template>
   <div class="w-full">
      <div class="flex space-x-2 mb-4" v-if="data?.params && data?.params.length > 0">
         <component
            v-model="paramsValueList[index].value"
            v-for="(item, index) in data?.params"
            :key="item.id"
            :id="item.id"
            :is="recordSetMapCom[item.type]"
            :data="item"
            :originData="originData"
            @change="(val) => handleQueryChange(val, item)"
            :disabled="chartLoading"
         ></component>
      <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 class="h-[20rem]" v-resize="chartContainerResize" v-loading="chartLoading">
      <div :style="{ height: chartHeight }" v-resize="chartContainerResize" v-loading="chartLoading">
         <div ref="chartRef"></div>
      </div>
   </div>
@@ -22,17 +31,25 @@
<script setup lang="ts">
import type * as echarts from 'echarts';
import _ from 'lodash';
import moment from 'moment';
import type { PropType } from 'vue';
import { ref, watch } from 'vue';
import { SCATTER_SYMBOL_SIZE, chatComProps, getChatChartOption } from '../../../common';
import { computed, ref } from 'vue';
import { SCATTER_SYMBOL_SIZE, getChatChartOption } from '../../../common';
import { useDrawChatChart } from '../../../hooks/useDrawChatChart';
import { ChartTypeEnum } from '../../../types';
import YRange from './components/YRange.vue';
import type { RecordSet, RecordSetParamsItem } from './types';
import { recordSetMapCom } from './types';
import { RecordSetParamsType, recordSetMapCom, scoreMap } from './types';
import { filterQuery } from '/@/api/ai/chat';
import { deepClone } from '/@/utils/other';
import { debounce } from '/@/utils/util';
import { axisLabelFormatter } from '/@/utils/chart';
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>,
@@ -49,34 +66,236 @@
   summaryIndex: {
      type: Number,
   },
   chartHeight: {
      type: String,
      default: '20rem',
   },
}) as {
   data: RecordSet;
};
const chartLoading = ref(false);
const paramsValueList = ref(props.data?.params);
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;
const drawChart = () => {
let nameIndex = undefined;
let timeCol = null;
let valueCol = null;
let preData = null;
let activeChartType: ChartTypeEnum = props.data?.chart_type ?? ChartTypeEnum.Line;
const originChartType = activeChartType;
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;
      case ChartTypeEnum.Score:
         result = {
            type: 'bar',
            symbol: 'none',
         };
         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 yAxisFormatter =
   originChartType === ChartTypeEnum.Score
         ? (value) => {
               return scoreMap[value];
           }
         : axisLabelFormatter;
   const tooltipValueFormatter =
   originChartType === ChartTypeEnum.Score
         ? (value) => {
               return scoreMap[value];
           }
         : undefined;
   const scoreYAxisOption: echarts.YAXisComponentOption = {
      min: 0,
      max: 4,
      interval: 1,
      axisLabel: {
         formatter: yAxisFormatter,
      },
   };
   const combineOption = _.defaultsDeep(
      {
         grid: {
            bottom: 20,
         },
         legend: {
            top: 19,
            show: true,
            type: 'scroll',
         },
         tooltip: {
            valueFormatter: tooltipValueFormatter,
         },
         toolbox: {
            show: true,
            feature: {
               myBar: {
                  onclick: () => {
                     activeChartType = originChartType === ChartTypeEnum.Score ? ChartTypeEnum.Score : 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 不强制保留 */
            scale: true,
            ...(originChartType === ChartTypeEnum.Score ? scoreYAxisOption : {}),
         },
         series: series,
      } as echarts.EChartsOption,
      extraOption,
      getChatChartOption()
   );
   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;
   }
   const timeCol = data.cols[timeIndex];
   timeCol = data.cols[timeIndex];
   valueIndex = data.cols.findIndex((item) => item.type === 'value');
   if (valueIndex === -1) {
      valueIndex = 2;
   }
   const valueCol = data.cols[valueIndex];
   valueCol = data.cols[valueIndex];
   let nameCol = null;
   groupedValues = null;
   if (data.chart === 'muli_line') {
      let nameIndex = data.cols.findIndex((item) => item.type === 'name');
      nameIndex = data.cols.findIndex((item) => item.type === 'name');
      if (nameIndex === -1) {
         nameIndex = 1;
      }
@@ -95,92 +314,36 @@
      nameCol = data.cols[nameIndex];
      groupedValues = _.groupBy(data.values, (item) => item[nameIndex]);
   }
};
   const seriesData = Object.keys(groupedValues).map((item) => {
      const values = groupedValues[item];
      return {
         name: item === 'default' ? '' : item,
         data: values.map((item) => [item[timeIndex], item[valueIndex]]),
         type: defaultDisplayType,
         symbol: 'none',
         smooth: true,
      };
   });
   const combineOption = _.defaultsDeep(getChatChartOption(), {
      grid: {
         bottom: 20,
      },
      toolbox: {
         show: true,
         feature: {
            myBar: {
               onclick: () => {
                  chartInstance.value.setOption({
                     series: seriesData.map((item) => ({
                        ...item,
                        type: 'bar',
                        symbol: 'none',
                     })),
                  });
               },
            },
            myScatter: {
               onclick: () => {
                  chartInstance.value.setOption({
                     series: seriesData.map((item) => ({
                        ...item,
                        type: 'scatter',
                        symbol: 'circle',
                        symbolSize: SCATTER_SYMBOL_SIZE,
                     })),
                  });
               },
            },
            myLine: {
               onclick: () => {
                  chartInstance.value.setOption({
                     series: seriesData.map((item) => ({
                        ...item,
                        type: 'line',
                        symbol: 'none',
                        smooth: true,
                     })),
                  });
               },
            },
         },
      },
      title: {
         text: data?.title,
      },
      xAxis: {
         name: timeCol?.title,
      },
      yAxis: {
         name: valueCol?.title,
      },
      series: seriesData,
   } as echarts.EChartsOption);
   chartInstance.value.setOption(combineOption);
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 changeMap = new Map<string, string>(null);
const handleQueryChange = async (val: string, item: RecordSetParamsItem) => {
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 {
      changeMap.set(item.id,val);
      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) {
      for (const [key, value] of changeMap) {
         paramsObj[key] = value;
      }
      const params = {
@@ -196,18 +359,140 @@
   const title = res?.values?.title;
   const values = res?.values?.values ?? [];
   chartInstance.value.setOption({
      title: {
         text: title,
      },
      series:
         groupedValues &&
         Object.keys(groupedValues).map(() => {
            return {
               data: values.map((item) => [item[timeIndex], item[valueIndex]]),
            };
         }),
   });
   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">${
                  originChartType === ChartTypeEnum.Score ? scoreMap[item.data[1]] : 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>