| | |
| | | <!-- 昨日供水管网概况 --> |
| | | <template> |
| | | <div class="w-full"> |
| | | <div class="flex mb-4 flex-wrap"> |
| | | <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"> |
| | | <template v-if="visibleParams && visibleParams.length > 0 && showFilter"> |
| | | <component |
| | | class="flex-0 m-1" |
| | | v-model="paramsValueList[index].value" |
| | | class="flex-0 m-2" |
| | | v-model="visibleParams[index].value" |
| | | v-for="(item, index) in visibleParams as any" |
| | | :key="item.id" |
| | | :id="item.id" |
| | |
| | | :data="item" |
| | | :originData="originData" |
| | | @change="(val) => handleQueryChange(val, item)" |
| | | :disabled="chartLoading" |
| | | :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> |
| | | |
| | | <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> |
| | | |
| | | <RecordSetTable :data="tableData" v-if="tableIsShow" :key="tableKey" /> |
| | | <div v-show="!tableIsShow" :style="{ height: chartHeight }" v-resize="chartContainerResize" v-loading="chartLoading"> |
| | | <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> |
| | |
| | | |
| | | <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, shallowRef, watch } from 'vue'; |
| | |
| | | import RecordSetTable from '../recordSetTable/RecordSetTable.vue'; |
| | | import DisplayMode from './components/DisplayMode.vue'; |
| | | import YRange from './components/YRange.vue'; |
| | | import { IS_DAY_LIST } from './components/constants'; |
| | | import { DisplayModeType } from './components/types'; |
| | | import type { RecordSetParamsItem } from './types'; |
| | | import { RecordSetParamsType, recordSetMapCom, scoreMap } from './types'; |
| | |
| | | import { axisLabelFormatter } from '/@/utils/chart'; |
| | | import { deepClone } from '/@/utils/other'; |
| | | import { debounce } from '/@/utils/util'; |
| | | import { defaultsDeep, groupBy, random } from 'lodash-es'; |
| | | const chartRef = ref<HTMLDivElement>(null); |
| | | const yRange = ref({ |
| | | min: null as number, |
| | | max: null as number, |
| | | }); |
| | | |
| | | const showMode = ref(DisplayModeType.Chart); |
| | | // const props = defineProps({ |
| | |
| | | // type: Object as PropType<RecordSet>, |
| | | // }, |
| | | // }); |
| | | |
| | | const emits = defineEmits<{ |
| | | (event: 'updateQuery', res: any): void; |
| | | }>(); |
| | | |
| | | const props = defineProps({ |
| | | data: { |
| | |
| | | }, |
| | | chartHeight: { |
| | | type: String, |
| | | default: '20rem', |
| | | 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: '1天', value: '1 days' }, |
| | | ]; |
| | | |
| | | const visibleParams = computed(() => { |
| | | const getVisibleParams = (data) => { |
| | | // const visibleList = props.data?.params?.filter((item) => !item?.hide) ?? []; |
| | | // index 作为 id |
| | | const visibleList = (props.data?.filter ?? []).map((item, index) => ({ |
| | | id: index + '', |
| | | ...item, |
| | | })); |
| | | 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]; |
| | |
| | | id: current.id, |
| | | type: RecordSetParamsType.Step, |
| | | origin: current, |
| | | value: current.value, |
| | | value: current.step_value, |
| | | list: stepOptions, |
| | | title: current.title, |
| | | }); |
| | |
| | | } |
| | | |
| | | return newList; |
| | | }); |
| | | const paramsValueList = ref(deepClone(visibleParams.value)); |
| | | }; |
| | | |
| | | // 更改 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 preData = null; |
| | | |
| | | let activeChartType: ChartTypeEnum = props.data?.chart_type ?? ChartTypeEnum.Line; |
| | | const originChartType = activeChartType; |
| | | let originChartType = activeChartType; |
| | | |
| | | // 给表格用的 series |
| | | const currentSeries = shallowRef<any[]>(null); |
| | |
| | | formatter: yAxisFormatter, |
| | | }, |
| | | }; |
| | | const combineOption = _.defaultsDeep( |
| | | const combineOption = defaultsDeep( |
| | | { |
| | | grid: { |
| | | bottom: 20, |
| | |
| | | }); |
| | | }; |
| | | |
| | | const handleData = () => { |
| | | const data = props.data; |
| | | const handleData = (data = props.data) => { |
| | | if (!data || !data.cols || !data.values) { |
| | | return; |
| | | } |
| | |
| | | nameIndex = 1; |
| | | } |
| | | nameCol = data.cols[nameIndex]; |
| | | groupedValues = _.groupBy(data.values, (item) => item[nameIndex]); |
| | | groupedValues = groupBy(data.values, (item) => item[nameIndex]); |
| | | } else if (data.chart === 'single_line') { |
| | | groupedValues = { |
| | | default: data.values, |
| | |
| | | nameIndex = 1; |
| | | } |
| | | nameCol = data.cols[nameIndex]; |
| | | groupedValues = _.groupBy(data.values, (item) => item[nameIndex]); |
| | | groupedValues = groupBy(data.values, (item) => item[nameIndex]); |
| | | } |
| | | }; |
| | | |
| | | const drawChart = () => { |
| | | const data = props.data; |
| | | const drawChart = (data = props.data) => { |
| | | if (!data || !data.cols || !data.values) { |
| | | return; |
| | | } |
| | | handleData(); |
| | | setNewOption(); |
| | | }; |
| | | const { chartContainerResize, chartInstance, initChart } = useDrawChatChart({ chartRef, drawChart }); |
| | | const { chartContainerResize, chartInstance } = useDrawChatChart({ chartRef, drawChart }); |
| | | |
| | | // 更换列表 |
| | | const changeMap = new Map<string, any>(null); |
| | | 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 getFilterList = () => { |
| | | const curAgentKey = props.data.agent_key; |
| | | |
| | | // 相同 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; |
| | | }, []); |
| | | return filterList; |
| | | }; |
| | | const handleQueryChange = async (val: any, item: RecordSetParamsItem) => { |
| | | if (!val) return; |
| | | const historyId = (props as any).originData.historyId; |
| | | let res = null; |
| | | |
| | | try { |
| | | if (item.type === RecordSetParamsType.TimeRange) { |
| | | item.origin.start_value = val[0]; |
| | | item.origin.end_value = val[1]; |
| | | changeMap.set(item.id, item.origin); |
| | | } else { |
| | | item.origin.value = val; |
| | | changeMap.set(item.id, item.origin); |
| | | } |
| | | // 改变原始值 |
| | | if (item.type === RecordSetParamsType.TimeRange) { |
| | | item.origin.start_value = val[0]; |
| | | item.origin.end_value = val[1]; |
| | | } else { |
| | | item.origin.step_value = val; |
| | | } |
| | | |
| | | const filterObj = Array.from(changeMap.values()); |
| | | const filterList = getFilterList(); |
| | | try { |
| | | const params = { |
| | | history_id: historyId, |
| | | // 查询前后 agent_key 不会变 |
| | | agent_key: props.data.agent_key, |
| | | filter_json: JSON.stringify(filterObj), |
| | | filter_json: JSON.stringify(filterList), |
| | | }; |
| | | res = await curveQuery(params); |
| | | chartLoading.value = true; |
| | |
| | | chartLoading.value = false; |
| | | } |
| | | |
| | | const title = res?.values?.title; |
| | | const values = res?.values?.values ?? []; |
| | | groupedValues = _.groupBy(values, (item) => item[nameIndex]); |
| | | emits('updateQuery', res); |
| | | |
| | | if (isMultiCompare.value) { |
| | | handleMultiCompare(); |
| | | } else { |
| | | (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, |
| | | }); |
| | | } |
| | | return; |
| | | }; |
| | | |
| | | let realRange = { |
| | | min: null, |
| | | max: null, |
| | | }; |
| | | |
| | | const getSingleDayOption = (day = COMMON_DAY) => |
| | |
| | | } 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 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')); |
| | | 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 tableData = computed(() => { |
| | | if (!currentSeries.value) return []; |
| | | // const min = yRange.value.min == null ? -Infinity : yRange.value.min; |
| | | // const max = yRange.value.max == null ? Infinity : yRange.value.max; |
| | | 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) { |
| | | const [time, value] = item; |
| | | |
| | | // if (value < min || value > max) { |
| | | // continue; |
| | | // } |
| | | 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; |
| | | } |
| | |
| | | watch( |
| | | () => currentSeries.value, |
| | | (val) => { |
| | | if (!tableIsShow.value) return; |
| | | tableKey.value = _.random(0, 100000) + ''; |
| | | 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> |