wujingjing
2024-10-15 f9ffa3b42a98edcb1c901c3ad1fde98c9ad8589a
src/components/chat/chatComponents/summaryCom/components/recordSet/RecordSet.vue
@@ -5,7 +5,7 @@
         <!-- TimeRange v-model 跟 @change 中的值会不一样,以@change 中为准 -->
         <template v-if="visibleParams && visibleParams.length > 0">
            <component
               class="flex-0 m-1"
               class="flex-0 m-2"
               v-model="paramsValueList[index].value"
               v-for="(item, index) in visibleParams as any"
               :key="item.id"
@@ -18,11 +18,24 @@
            ></component>
         </template>
         <slot> </slot>
         <YRange @input="yRangeInput" />
         <el-tooltip
            v-if="originChartType === ChartTypeEnum.Score"
            :content="`${Object.keys(scoreMap)
               .map((key) => `${key} 表示${scoreMap[key]}`)
               .join(', ')}`"
            placement="top-start"
         >
            <SvgIcon name="fa fa-question-circle-o" :size="15" class="ml-1 cursor-help flex-center" color="#909399" />
         </el-tooltip>
         <YRange v-model="yRange" @input="yRangeInput" />
         <el-checkbox class="m-1" v-model="isMultiCompare" label="多日对比" @change="multiCompareChange"></el-checkbox>
         <el-checkbox class="m-2" v-model="isMultiCompare" label="多日对比" @change="multiCompareChange"></el-checkbox>
         <DisplayMode class="ml-auto" v-model="showMode" @change="displayModeChange" />
      </div>
      <div :style="{ height: chartHeight }" v-resize="chartContainerResize" v-loading="chartLoading">
      <RecordSetTable :data="tableData" v-if="tableIsShow" :key="tableKey" />
      <div v-show="!tableIsShow" :style="{ height: chartHeight }" v-resize="chartContainerResize" v-loading="chartLoading">
         <div ref="chartRef"></div>
      </div>
   </div>
@@ -33,22 +46,23 @@
import _ from 'lodash';
import moment from 'moment';
import type { PropType } from 'vue';
import { computed, ref } from 'vue';
import { computed, ref, shallowRef, watch } from 'vue';
import { SCATTER_SYMBOL_SIZE, getChatChartOption } from '../../../common';
import { useDrawChatChart } from '../../../hooks/useDrawChatChart';
import { ChartTypeEnum } from '../../../types';
import RecordSetTable from '../recordSetTable/RecordSetTable.vue';
import DisplayMode from './components/DisplayMode.vue';
import YRange from './components/YRange.vue';
import type { RecordSet, RecordSetParamsItem } from './types';
import { RecordSetParamsType, recordSetMapCom } from './types';
import { filterQuery } from '/@/api/ai/chat';
import { DisplayModeType } from './components/types';
import type { RecordSetParamsItem } from './types';
import { RecordSetParamsType, recordSetMapCom, scoreMap } from './types';
import { curveQuery } from '/@/api/ai/chat';
import { axisLabelFormatter } from '/@/utils/chart';
import { deepClone } from '/@/utils/other';
import { debounce } from '/@/utils/util';
import { ChartTypeEnum } from '../../../types';
const chartRef = ref<HTMLDivElement>(null);
const yRange = ref({
   min: null as number,
   max: null as number,
});
const showMode = ref(DisplayModeType.Chart);
// const props = defineProps({
//    data: {
//       type: Object as PropType<RecordSet>,
@@ -70,37 +84,52 @@
      default: '20rem',
   },
}) as {
   data: RecordSet;
   data: any;
};
const chartLoading = ref(false);
const stepOptions = [
   { title: '5分钟', value: '5 minutes' },
   { title: '10分钟', value: '10 minutes' },
   { title: '半小时', value: '30 minutes' },
   { title: '1小时', value: '1 hours' },
   { title: '1天', value: '1 days' },
];
const visibleParams = computed(() => {
   const visibleList = props.data?.params?.filter((item) => !item?.hide) ?? [];
   // const visibleList = props.data?.params?.filter((item) => !item?.hide) ?? [];
   // index 作为 id
   const visibleList = (props.data?.filter ?? []).map((item, index) => ({
      id: index + '',
      ...item,
   }));
   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) {
      switch (current.type) {
         case RecordSetParamsType.TimeRange:
            newList.push({
               id: current.id,
               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);
               origin: current,
               value: [current.start_value, current.end_value],
               title: current.title,
            });
            break;
         case RecordSetParamsType.Step:
            newList.push({
               id: current.id,
               type: RecordSetParamsType.Step,
               origin: current,
               value: current.value,
               list: stepOptions,
               title: current.title,
            });
            break;
         default:
            break;
      }
      Reflect.deleteProperty(current, 'id');
   }
   return newList;
@@ -116,7 +145,12 @@
let preData = null;
let activeChartType: ChartTypeEnum = props.data.chart_type ?? ChartTypeEnum.Line;
let activeChartType: ChartTypeEnum = props.data?.chart_type ?? ChartTypeEnum.Line;
const originChartType = activeChartType;
// 给表格用的 series
const currentSeries = shallowRef<any[]>(null);
const getChartTypeSeriesOption = (type: ChartTypeEnum) => {
   let result = {};
   switch (type) {
@@ -144,6 +178,14 @@
         };
         break;
      case ChartTypeEnum.Score:
         result = {
            type: 'bar',
            symbol: 'none',
         };
         break;
      default:
         break;
   }
@@ -159,73 +201,104 @@
         return {
            name: item === 'default' ? '' : item,
            data: values.map((item) => [item[timeIndex], item[valueIndex]]),
            ...getChartTypeSeriesOption(activeChartType)
            ...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),
                     })),
                  });
               },
            },
   currentSeries.value = series;
   const yAxisFormatter =
      originChartType === ChartTypeEnum.Score
         ? (value) => {
               return scoreMap[value];
           }
         : axisLabelFormatter;
            myScatter: {
               onclick: () => {
                  activeChartType = ChartTypeEnum.Scatter;
   const tooltipValueFormatter =
      originChartType === ChartTypeEnum.Score
         ? (value) => {
               return scoreMap[value];
           }
         : undefined;
                  chartInstance.value.setOption({
                     series: series.map((item) => ({
                        ...item,
                        ...getChartTypeSeriesOption(activeChartType),
                     })),
                  });
   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),
                        })),
                     });
                  },
               },
            },
            myLine: {
               onclick: () => {
                  activeChartType = ChartTypeEnum.Line;
                  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);
         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,
   });
@@ -285,31 +358,29 @@
const { chartContainerResize, chartInstance } = useDrawChatChart({ chartRef, drawChart });
// 更换列表
const changeMap = new Map<string, string>(null);
const changeMap = new Map<string, any>(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]);
         item.origin.start_value = val[0];
         item.origin.end_value = val[1];
         changeMap.set(item.id, item.origin);
      } else {
         changeMap.set(item.id, val);
         item.origin.value = val;
         changeMap.set(item.id, item.origin);
      }
      const paramsObj = {};
      for (const [key, value] of changeMap) {
         paramsObj[key] = value;
      }
      const filterObj = Array.from(changeMap.values());
      const params = {
         history_id: historyId,
         query_index: summaryIndex,
         param_json: JSON.stringify(paramsObj),
         agent_key: props.data.agent_key,
         filter_json: JSON.stringify(filterObj),
      };
      res = await filterQuery(params);
      res = await curveQuery(params);
      chartLoading.value = true;
   } finally {
      chartLoading.value = false;
@@ -322,40 +393,49 @@
   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]]),
               };
            }),
      });
      (currentSeries.value =
         groupedValues &&
         Object.keys(groupedValues).map((item) => {
            const values = groupedValues[item];
            return {
               data: values.map((item) => [item[timeIndex], item[valueIndex]]),
            };
         })),
         chartInstance.value.setOption({
            title: {
               text: title,
            },
            series: currentSeries.value,
         });
   }
};
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">
let realRange = {
   min: null,
   max: null,
};
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>
               ><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">
            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">
@@ -366,30 +446,30 @@
            </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;
            return result;
         },
         showMaxLabel: true,
      },
   },
} as echarts.EChartsOption);
      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) => {
   (realRange.min = val.min), (realRange.max = val.max);
   chartInstance.value.setOption({
      yAxis: {
         min: val.min,
         max: val.max,
      },
      yAxis: realRange,
   });
   currentSeries.value = currentSeries.value.concat([]);
});
const yRangeInput = (val) => {
@@ -442,6 +522,74 @@
   }
};
//#endregion
//#region ====================== 切换显示模式 ======================
const tableIsShow = ref(false);
const displayModeChange = (val: DisplayModeType) => {
   if (val === DisplayModeType.List) {
      tableIsShow.value = true;
   } else {
      tableIsShow.value = false;
   }
};
const tableData = computed(() => {
   if (!currentSeries.value) return [];
   const min = realRange.min == null ? -Infinity : realRange.min;
   const max = realRange.max == null ? Infinity : realRange.max;
   const timeDataMap = currentSeries.value.reduce((preVal, curVal, index) => {
      for (const item of curVal.data) {
         let [time, value] = item;
         // 多日对比,只显示时分秒
         if (isMultiCompare.value) {
            time = time.slice(11);
         }
         if (value < min || value > max) {
            continue;
         }
         if (!preVal[time]) {
            preVal[time] = [];
         }
         // score 类型,value 值,需要映射成另一个值
         if (originChartType === ChartTypeEnum.Score) {
            value = scoreMap[value];
         }
         preVal[time][index] = value;
      }
      return preVal;
   }, {});
   const data = Object.keys(timeDataMap).map((item) => [item, ...timeDataMap[item]]);
   const cols = currentSeries.value.map((item, index) => ({
      title: item.name ?? `值${index + 1}`,
      type: 'text',
   }));
   cols.unshift({
      title: '时间',
      type: 'time',
   });
   const result = {
      type: 'recordset',
      chart: 'table',
      title: props.data?.title,
      max_cols: 5,
      cols: cols,
      values: data,
   };
   return result;
});
//#endregion
const tableKey = ref('-999');
watch(
   () => currentSeries.value,
   (val) => {
      tableKey.value = _.random(0, 100000) + '';
   }
);
defineExpose({
   drawChart,