From f41e52e3debf30558d556dc0451776f5422fb9b8 Mon Sep 17 00:00:00 2001 From: yangyin <1850366751@qq.com> Date: 星期五, 08 十一月 2024 14:20:02 +0800 Subject: [PATCH] Merge branch 'test' of http://47.103.154.90:83/r/WI/Web.V1.0 into test --- src/components/chat/chatComponents/summaryCom/components/recordSet/RecordSet.vue | 778 +++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 files changed, 696 insertions(+), 82 deletions(-) diff --git a/src/components/chat/chatComponents/summaryCom/components/recordSet/RecordSet.vue b/src/components/chat/chatComponents/summaryCom/components/recordSet/RecordSet.vue index 9899e2c..0adb614 100644 --- a/src/components/chat/chatComponents/summaryCom/components/recordSet/RecordSet.vue +++ b/src/components/chat/chatComponents/summaryCom/components/recordSet/RecordSet.vue @@ -1,39 +1,391 @@ +<!-- 鏄ㄦ棩渚涙按绠$綉姒傚喌 --> <template> - <div class="h-[20rem] w-full" v-resize="chartContainerResize"> - <div ref="chartRef"></div> + <div class="w-full flex-column"> + <div class="flex mb-4 flex-wrap flex-0"> + <!-- TimeRange v-model 璺� @change 涓殑鍊间細涓嶄竴鏍凤紝浠change 涓负鍑� --> + <template v-if="visibleParams && visibleParams.length > 0 && showFilter "> + <component + class="flex-0 m-2" + v-model="visibleParams[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 ||disabled" + ></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> + + <el-checkbox class="m-2" v-model="isMultiCompare" label="澶氭棩瀵规瘮" @change="multiCompareChange"></el-checkbox> + + <DisplayMode class="ml-auto" v-model="showMode" @change="displayModeChange" /> + </div> + + <RecordSetTable + :data="tableData" + v-if="tableIsShow" + :key="tableKey" + :tableLimitHeight="tableLimitHeight" + :class="{ 'flex-auto': chartHeight == undefined }" + /> + <div + v-show="!tableIsShow" + :style="{ height: chartHeight }" + :class="{ 'flex-auto': chartHeight == undefined }" + 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 { ref } from 'vue'; -import { SCATTER_SYMBOL_SIZE, chatComProps, getChatChartOption } from '../../../common'; +import moment from 'moment'; +import type { PropType } 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 { 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 { IS_DAY_LIST } from './components/constants'; const chartRef = ref<HTMLDivElement>(null); -const defaultDisplayType = 'line'; -const props = defineProps(chatComProps); +const showMode = ref(DisplayModeType.Chart); +// const props = defineProps({ +// data: { +// type: Object as PropType<RecordSet>, +// }, +// }); -const drawChart = () => { - const data = props.data; +const emits = defineEmits<{ + (event: 'updateQuery', res: any): void; +}>(); + +const props = defineProps({ + data: { + type: Object as PropType<any>, + }, + originData: { + type: Object as PropType<any>, + }, + summaryIndex: { + type: Number, + }, + chartHeight: { + type: String, + required: false, + }, + showFilter: { + type: Boolean, + default: true, + }, + disabled:{ + type:Boolean, + default:false, + } +}) as { + data: any; + summaryIndex: number; + showFilter: Boolean; +}; + +const tableLimitHeight = props.chartHeight == undefined ? undefined : document.body.clientHeight * 0.7; + +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 getVisibleParams = (data) => { + // const visibleList = props.data?.params?.filter((item) => !item?.hide) ?? []; + // index 浣滀负 id + const visibleList = (data?.filter ?? []).map((item, index) => { + // 涓嶄慨鏀瑰師濮嬪湴鍧� + item.id = index + ''; + + return item; + }); + const newList: RecordSetParamsItem[] = []; + for (let index = 0; index < visibleList.length; index++) { + const current = visibleList[index]; + switch (current.type) { + case RecordSetParamsType.TimeRange: + newList.push({ + id: current.id, + type: RecordSetParamsType.TimeRange, + 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.step_value, + list: stepOptions, + title: current.title, + }); + break; + default: + break; + } + Reflect.deleteProperty(current, 'id'); + } + + return newList; +}; + +// 鏇存敼 value 鍊硷紝瀹屽叏瑕嗙洊浼氫涪澶卞搷搴旀�э紝鍙慨鏀� value +const updateVisibleParams = (data) => { + const filter = data?.filter ?? []; + + filter.map((newItem, index) => { + const currentItem = visibleParams.value.find((item) => item.id === index + ''); + + if (currentItem) { + if (newItem.type === RecordSetParamsType.TimeRange) { + currentItem.value = [newItem.start_value, newItem.end_value]; + } else { + currentItem.value = newItem.step_value; + } + } + }); +}; + +const visibleParams = ref(getVisibleParams(props.data)); + +const checkIsDayTime = () => { + if (!props.showFilter) return false; + const stepFilter = visibleParams.value.find((item) => item.type === RecordSetParamsType.Step); + if (!stepFilter.origin.step_value) return false; + + return IS_DAY_LIST.includes(stepFilter.origin.step_value); +}; +// 璺ㄥ害鏄惁鏄棩鏈熷舰寮� +// const isDayTime = checkIsDayTime(); +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; +let originChartType = activeChartType; + +// 缁欒〃鏍肩敤鐨� series +const currentSeries = shallowRef<any[]>(null); + +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), + }; + }); + } + currentSeries.value = series; + 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 = (data = props.data) => { + if (!data || !data.cols || !data.values) { + return; + } + preData = data; const xType = 'time'; - let timeIndex = data.cols.findIndex((item) => item.type === 'time'); + timeIndex = data.cols.findIndex((item) => item.type === 'time'); if (timeIndex === -1) { timeIndex = 0; } - const timeCol = data.cols[timeIndex]; + timeCol = data.cols[timeIndex]; - let valueIndex = data.cols.findIndex((item) => item.type === 'value'); + 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; - let groupedValues = 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; } @@ -52,76 +404,338 @@ 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 = (data = props.data) => { + if (!data || !data.cols || !data.values) { + return; + } + handleData(); + setNewOption(); }; const { chartContainerResize, chartInstance } = useDrawChatChart({ chartRef, drawChart }); + +const updateCurrent = (res, isNew = false) => { + const title = res?.title; + const values = res?.values ?? []; + //#region ====================== 鍒锋柊褰撳墠 filter ====================== + // 鍙洿鏂� value锛屼笉鐩存帴瑕嗙洊锛岄槻姝涪澶卞搷搴旀�� + // updateVisibleParams(res); + //#endregion + groupedValues = _.groupBy(values, (item) => item[nameIndex]); + if (isMultiCompare.value) { + handleMultiCompare(); + } else { + if (isNew) { + setNewOption(); + } else { + (currentSeries.value = + groupedValues && + Object.keys(groupedValues).map((item, index) => { + const values = groupedValues[item]; + return { + name: item === 'default' ? '' : item, + data: values.map((item) => [item[timeIndex], item[valueIndex]]), + }; + })), + chartInstance.value?.setOption({ + title: { + text: title, + }, + series: currentSeries.value, + }); + } + } +}; + +const handleQueryChange = async (val: any, item: RecordSetParamsItem) => { + if (!val) return; + const historyId = (props as any).originData.historyId; + const curAgentKey = props.data.agent_key; + let res = null; + + // 鏀瑰彉鍘熷鍊� + if (item.type === RecordSetParamsType.TimeRange) { + item.origin.start_value = val[0]; + item.origin.end_value = val[1]; + } else { + item.origin.step_value = val; + } + try { + // 鐩稿悓 agent_key 涓嬫墍鏈� filter 璇锋眰鍙傛暟 + const filterList = ((props as any).originData?.content?.origin?.summary ?? []).reduce((preVal, curVal) => { + if (curVal.agent_key !== curAgentKey) return preVal; + + const filter = (curVal.filter ?? []).reduce((subPreVal, subCurVal) => { + if (subCurVal.type === RecordSetParamsType.TimeRange) { + subPreVal.push( + ...[ + { + update: subCurVal.update, + value: subCurVal.start_value, + path: subCurVal.start_path, + }, + { + update: subCurVal.update, + value: subCurVal.end_value, + path: subCurVal.end_path, + }, + ] + ); + } else { + subPreVal.push({ + update: subCurVal.update, + value: subCurVal.step_value, + path: subCurVal.step_path, + }); + } + + return subPreVal; + }, []); + + preVal = preVal.concat(filter); + + return preVal; + }, []); + const params = { + history_id: historyId, + // 鏌ヨ鍓嶅悗 agent_key 涓嶄細鍙� + agent_key: props.data.agent_key, + filter_json: JSON.stringify(filterList), + }; + res = await curveQuery(params); + chartLoading.value = true; + } finally { + chartLoading.value = false; + } + + emits('updateQuery', res); + + return; +}; + +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">${ + 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) => { + (realRange.min = val.min), (realRange.max = val.max); + chartInstance.value.setOption({ + yAxis: realRange, + }); + + currentSeries.value = currentSeries.value.concat([]); +}); + +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 +//#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 绫诲瀷锛寁alue 鍊硷紝闇�瑕佹槧灏勬垚鍙︿竴涓�� + 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) + ''; + } +); + +const updateAll = (triggerIndex, res) => { + // 褰撳墠 agent_key + const curAgentKey = props.data.agent_key; + const triggerAgentKey = (props as any).originData?.content?.origin?.summary?.[triggerIndex]?.agent_key; + if (curAgentKey !== triggerAgentKey) { + return; + } + if (!curAgentKey) { + return; + } + + // 褰撳墠椤规墍鍦ㄧ储寮� + let currentIndex = -1; + for (let index = 0; index < (props as any).originData.content.origin.summary.length; index++) { + const item = (props as any).originData.content.origin.summary[index]; + if (item.agent_key === curAgentKey) { + currentIndex++; + if (index === props.summaryIndex) { + break; + } + } + } + + const newSummary = res?.summary?.[currentIndex]; + if (!newSummary) return; + + updateCurrent(newSummary); +}; + +const updateIndexSummary = (summary) => { + updateCurrent(summary?.[props.summaryIndex], true); +}; + +defineExpose({ + drawChart, + isMultiCompare, + handleMultiCompare, + handleData, + updateAll, + + updateIndexSummary, +}); </script> <style scoped lang="scss"></style> -- Gitblit v1.9.3