| | |
| | | <!-- 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" |
| | |
| | | ></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> |
| | |
| | | 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>, |
| | |
| | | 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; |
| | |
| | | |
| | | 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) { |
| | |
| | | }; |
| | | |
| | | break; |
| | | case ChartTypeEnum.Score: |
| | | result = { |
| | | type: 'bar', |
| | | symbol: 'none', |
| | | }; |
| | | |
| | | break; |
| | | |
| | | default: |
| | | break; |
| | | } |
| | |
| | | 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, |
| | | }); |
| | |
| | | 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; |
| | |
| | | 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"> |
| | |
| | | </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) => { |
| | |
| | | } |
| | | }; |
| | | //#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, |