From 11738cfed9fced6927376d6de27bb8ba9077ba2f Mon Sep 17 00:00:00 2001 From: wujingjing <gersonwu@qq.com> Date: 星期四, 14 十一月 2024 11:17:09 +0800 Subject: [PATCH] 指标图谱 --- src/views/project/yw/systemManage/agentGraph/AgentGraph.vue | 353 ------------------------ src/views/project/yw/systemManage/agentGraph/types.ts | 27 - src/components/graph/treeGraph/types.ts | 28 ++ src/views/project/yw/systemManage/metricMgr/MetricDetail.vue | 70 ++++ src/components/graph/treeGraph/TreeGraph.vue | 346 ++++++++++++++++++++++++ 5 files changed, 453 insertions(+), 371 deletions(-) diff --git a/src/components/graph/treeGraph/TreeGraph.vue b/src/components/graph/treeGraph/TreeGraph.vue new file mode 100644 index 0000000..0e6e7db --- /dev/null +++ b/src/components/graph/treeGraph/TreeGraph.vue @@ -0,0 +1,346 @@ +<template> + <div class="h-full w-full bg-white relative flex flex-col"> + <!-- <div class="toolbar flex-0 border-x-0 border-b border-t-0 border-gray-200 border-solid flex-items-center"> + <span> + 鏍煎紡 + </span> + </div> --> + <div class="graph flex-auto"> + <div class="h-full" ref="graphRef"></div> + + <div + class="absolute left-4 top-4 z-10 w-16 divide-y-[1.5px] divide-solid divide-gray-100 rounded-lg" + style="box-shadow: 0 0 15px #dbdee6" + > + <div + class="flex-column content-center items-center border-x-0 py-2 hover:bg-gray-200 active:bg-gray-300 cursor-pointer" + @click="toolBarItemClick(item)" + v-for="item in toolBarFunList" + > + <span class="ywifont !text-[20px] mb-1 p-1.5" :class="[`ywicon-${item.icon}`]"></span> + <span>{{ item.label }}</span> + </div> + </div> + <div class="absolute right-2 top-4 py-7 px-5 rounded-lg w-96" style="box-shadow: 0 0 15px #dbdee6" v-if="searchIsShow"> + <span + class="absolute ywifont ywicon-guanbi right-4 top-4 cursor-pointer hover:bg-gray-200 active:bg-gray-300" + @click="closeSearch" + ></span> + <div class="flex-column"> + <span class="mb-5">鏌ユ壘</span> + <el-input v-model="searchValue" @input="debounceSearch" placeholder="杈撳叆鏌ユ壘鍐呭" v-elInputFocus></el-input> + </div> + </div> + + <div class="absolute right-2 top-4 py-7 px-5 rounded-lg w-96" style="box-shadow: 0 0 15px #dbdee6" v-if="layoutIsShow"> + <span + class="absolute ywifont ywicon-guanbi right-4 top-4 cursor-pointer hover:bg-gray-200 active:bg-gray-300" + @click="closeLayout" + ></span> + <div class="flex-column"> + <span class="mb-5">甯冨眬</span> + + <el-select v-model="activeLayout" @change="layoutChange"> + <el-option + v-for="item in Object.keys(customLayoutMap)" + :key="item" + :value="parseInt(item)" + :label="customLayoutMap[item]" + ></el-option> + </el-select> + </div> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { Graph, GraphOptions, treeToGraphData } from '@antv/g6'; +import { defaultsDeep } from 'lodash'; +import { PropType, onMounted, ref } from 'vue'; +import { CustomLayout, customLayoutMap, getLayoutType } from './types'; +import { DeepPartial } from '/@/utils/types'; + +const props = defineProps({ + maxCount: { + type: Number, + }, + data: { + type: Object as PropType<any>, + }, +}); + +const graphRef = ref<HTMLDivElement>(null); + +let graph: Graph; + +const getLayoutOption = (layoutType: CustomLayout) => { + let option: DeepPartial<GraphOptions> = {}; + switch (layoutType) { + case CustomLayout.Radial: + option = { + // autoFit: 'view', + edge: { + type: 'cubic-radial', + }, + layout: [ + { + radial: true, + direction: 'RL', + }, + ], + }; + break; + case CustomLayout.Vertical: + option = { + edge: { + type: 'line', + }, + layout: [ + { + direction: 'V', + // getHeight: () => { + // return 20; + // }, + // getWidth: () => { + // return 20; + // }, + // 鍨傜洿闂撮殭 + getVGap: () => { + return 40; + }, + // 姘村钩闂撮殭 + getHGap: () => { + return 10; + }, + }, + ], + }; + break; + default: + option = { + edge: { + type: 'line', + }, + layout: [ + { + direction: 'TB', + // 鍨傜洿闂撮殭 + getVGap: () => { + return 60; + }, + // 姘村钩闂撮殭 + getHGap: () => { + return 10; + }, + }, + ], + }; + break; + } + return option; +}; + +const graphRender = async () => { + await graph.render(); + if (activeLayout.value === CustomLayout.Radial) { + selfAdapt(); + } +}; +const rebuildGraph = async(extraOption: DeepPartial<GraphOptions>) => { + const finalOptions = defaultsDeep(extraOption, commonOption); + graph.setOptions(finalOptions); + await graphRender(); + graph.fitCenter(); +}; + +let commonOption: GraphOptions; +const initGraph = async (treeData: any, layoutType: CustomLayout) => { + commonOption = { + container: graphRef.value, + data: treeToGraphData(treeData), + node: { + style: { + size: 20, + labelText: (d) => d.label as string, + labelBackground: true, + }, + state: { + active: { + fill: '#00C9C9', + }, + }, + palette: { + field: 'type', + color: 'tableau', + }, + }, + edge: { + type: 'cubic-radial', + state: { + active: { + lineWidth: 3, + stroke: '#009999', + }, + }, + }, + layout: [ + { + type: 'compact-box', + + getHeight: () => { + return 20; + }, + getWidth: () => { + return 20; + }, + // 鍨傜洿闂撮殭 + getVGap: () => { + return 2; + }, + // 姘村钩闂撮殭 + getHGap: () => { + return 120; + }, + }, + ], + behaviors: [ + 'drag-canvas', + 'zoom-canvas', + // 'drag-element', + { + key: 'hover-activate', + type: 'hover-activate', + degree: 5, + direction: 'in', + inactiveState: 'inactive', + }, + ], + transforms: ['place-radial-labels'], + }; + const extraOption: DeepPartial<GraphOptions> = getLayoutOption(layoutType); + + const finalOption: GraphOptions = defaultsDeep(extraOption, commonOption); + graph = new Graph(finalOption); + graphRender(); +}; + +const getLeaf = (item) => { + if (item.Children && item.Children.length > 0) { + const first = item.Children[0]; + return getLeaf(first); + } else { + return item; + } +}; + +const initEvent = () => { + // graph.on('node:click', ({ cell, e, node, view }) => { + // }); +}; + +const selfAdapt = () => { + setTimeout(() => { + graph.fitView(); + }, 100); +}; + +//#region ====================== 宸ュ叿鏍� ====================== +const enum ToolBarType { + /** @description 甯冨眬 */ + Layout, + /** @description 鎼滅储 */ + Search, + /** @description 鏀惧ぇ */ + ZoomIn, + /** @description 缂╁皬 */ + ZoomOut, + /** @description 鑷�傚簲 */ + Reset, +} +type ToolBarItem = { + icon: string; + label: string; + type: ToolBarType; +}; +const toolBarFunList: ToolBarItem[] = [ + // { + // icon: 'sousuo', + // label: '鎼滅储', + // type: ToolBarType.Search, + // }, + { + icon: 'buju', + label: '甯冨眬', + type: ToolBarType.Layout, + }, + { + icon: 'fangda', + label: '鏀惧ぇ', + type: ToolBarType.ZoomIn, + }, + { + icon: 'suoxiao', + label: '缂╁皬', + type: ToolBarType.ZoomOut, + }, + { + icon: 'zishiying', + label: '鑷�傚簲', + type: ToolBarType.Reset, + }, +]; + +const searchIsShow = ref(false); +const searchValue = ref(''); +const ZOOM_OFFSET = 0.15; +const toolBarItemClick = (item: ToolBarItem) => { + switch (item.type) { + case ToolBarType.Layout: + layoutIsShow.value = true; + break; + case ToolBarType.Search: + searchIsShow.value = true; + break; + case ToolBarType.ZoomIn: + graph.zoomBy(1 + ZOOM_OFFSET); + break; + + case ToolBarType.ZoomOut: + graph.zoomBy(1 - ZOOM_OFFSET); + + break; + + case ToolBarType.Reset: + selfAdapt(); + default: + break; + } +}; + +const debounceSearch = () => {}; +const closeSearch = () => { + searchIsShow.value = false; +}; +//#endregion + +//#region ====================== 甯冨眬 ====================== +const layoutIsShow = ref(false); +const closeLayout = () => { + layoutIsShow.value = false; +}; + +const activeLayout = ref<CustomLayout>(); + +const layoutChange = (layoutType: CustomLayout) => { + const extraOption = getLayoutOption(layoutType); + rebuildGraph(extraOption); +}; +//#endregion +onMounted(() => { + const layoutType = getLayoutType(props.maxCount); + activeLayout.value = layoutType; + initGraph(props.data, layoutType); +}); +</script> +<style scoped lang="scss"></style> diff --git a/src/components/graph/treeGraph/types.ts b/src/components/graph/treeGraph/types.ts new file mode 100644 index 0000000..902175e --- /dev/null +++ b/src/components/graph/treeGraph/types.ts @@ -0,0 +1,28 @@ + +export const enum CustomLayout { + /** @description 绱у噾鏍戝竷灞� n <= 30*/ + NormalTree, + /** @description 鍨傜洿绱у噾鏍戯紝鏍硅妭鐐瑰湪涓棿 30 < n <= 50*/ + Vertical, + /** @description 杈愬皠绱у噾鏍戯紝> 50 */ + Radial, +} + +export const customLayoutMap ={ + [CustomLayout.NormalTree]:'绱у噾鏍戝竷灞�', + [CustomLayout.Vertical]:'鍨傜洿瀵圭О甯冨眬', + [CustomLayout.Radial]:'杈愬皠鐘跺竷灞�' +} + + + +export const getLayoutType = (maxLeafLen: number) => { + if (maxLeafLen > 50) { + return CustomLayout.Radial; + } else if (30 < maxLeafLen) { + return CustomLayout.Vertical; + } else { + return CustomLayout.NormalTree; + } +}; + diff --git a/src/views/project/yw/systemManage/agentGraph/AgentGraph.vue b/src/views/project/yw/systemManage/agentGraph/AgentGraph.vue index 7070cf3..b313478 100644 --- a/src/views/project/yw/systemManage/agentGraph/AgentGraph.vue +++ b/src/views/project/yw/systemManage/agentGraph/AgentGraph.vue @@ -1,74 +1,20 @@ <template> - <div v-loading="firstLoading" class="h-full w-full bg-white relative flex flex-col"> - <!-- <div class="toolbar flex-0 border-x-0 border-b border-t-0 border-gray-200 border-solid flex-items-center"> - <span> - 鏍煎紡 - </span> - </div> --> - <div class="graph flex-auto"> - <div class="h-full" ref="graphRef"></div> - - <div - class="absolute left-4 top-4 z-10 w-16 divide-y-[1.5px] divide-solid divide-gray-100 rounded-lg" - style="box-shadow: 0 0 15px #dbdee6" - > - <div - class="flex-column content-center items-center border-x-0 py-2 hover:bg-gray-200 active:bg-gray-300 cursor-pointer" - @click="toolBarItemClick(item)" - v-for="item in toolBarFunList" - > - <span class="ywifont !text-[20px] mb-1 p-1.5" :class="[`ywicon-${item.icon}`]"></span> - <span>{{ item.label }}</span> - </div> - </div> - <div class="absolute right-2 top-4 py-7 px-5 rounded-lg w-96" style="box-shadow: 0 0 15px #dbdee6" v-if="searchIsShow"> - <span - class="absolute ywifont ywicon-guanbi right-4 top-4 cursor-pointer hover:bg-gray-200 active:bg-gray-300" - @click="closeSearch" - ></span> - <div class="flex-column"> - <span class="mb-5">鏌ユ壘</span> - <el-input v-model="searchValue" @input="debounceSearch" placeholder="杈撳叆鏌ユ壘鍐呭" v-elInputFocus></el-input> - </div> - </div> - - <div class="absolute right-2 top-4 py-7 px-5 rounded-lg w-96" style="box-shadow: 0 0 15px #dbdee6" v-if="layoutIsShow"> - <span - class="absolute ywifont ywicon-guanbi right-4 top-4 cursor-pointer hover:bg-gray-200 active:bg-gray-300" - @click="closeLayout" - ></span> - <div class="flex-column"> - <span class="mb-5">甯冨眬</span> - - <el-select v-model="activeLayout" @change="layoutChange"> - <el-option - v-for="item in Object.keys(customLayoutMap)" - :key="item" - :value="parseInt(item)" - :label="customLayoutMap[item]" - ></el-option> - </el-select> - </div> - </div> - </div> + <div class="h-full" v-loading="firstLoading"> + <TreeGraph v-if="graphData" :data="graphData" class="h-full" :maxCount="maxCount" /> </div> </template> <script setup lang="ts"> -import { Graph, GraphOptions, treeToGraphData } from '@antv/g6'; -import { defaultsDeep } from 'lodash'; import { onMounted, ref } from 'vue'; -import { CustomLayout, OrgTreeItem, customLayoutMap, getLayoutType } from './types'; -import { getMetricAgentListByPost, getMetricNameListByPost } from '/@/api/metrics'; +import TreeGraph from '/@/components/graph/treeGraph/TreeGraph.vue'; import router from '/@/router'; -import { DeepPartial } from '/@/utils/types'; -import { deepClone } from '/@/utils/other'; -const graphRef = ref<HTMLDivElement>(null); - -let graph: Graph; -const firstLoading = ref(false); +import { getMetricAgentListByPost, getMetricNameListByPost } from '/@/api/metrics'; +import { OrgTreeItem } from './types'; const agentId = router.currentRoute.value.query.id as string; +const maxCount = ref(null); +const graphData = ref(null); +const firstLoading = ref(false); const convertOrgTreeToTreeNode = (orgTreeData: OrgTreeItem) => { const treeData = { id: orgTreeData.treeId, @@ -78,172 +24,6 @@ }; return treeData; }; -const getLayoutOption = (layoutType: CustomLayout) => { - let option: DeepPartial<GraphOptions> = {}; - switch (layoutType) { - case CustomLayout.Radial: - option = { - // autoFit: 'view', - edge: { - type: 'cubic-radial', - }, - layout: [ - { - radial: true, - direction: 'RL', - }, - ], - }; - break; - case CustomLayout.Vertical: - option = { - edge: { - type: 'line', - }, - layout: [ - { - direction: 'V', - // getHeight: () => { - // return 20; - // }, - // getWidth: () => { - // return 20; - // }, - // 鍨傜洿闂撮殭 - getVGap: () => { - return 40; - }, - // 姘村钩闂撮殭 - getHGap: () => { - return 10; - }, - }, - ], - }; - break; - default: - option = { - edge: { - type: 'line', - }, - layout: [ - { - direction: 'TB', - // 鍨傜洿闂撮殭 - getVGap: () => { - return 60; - }, - // 姘村钩闂撮殭 - getHGap: () => { - return 10; - }, - }, - ], - }; - break; - } - return option; -}; - -const graphRender = async() => { - await graph.render(); - if (activeLayout.value === CustomLayout.Radial) { - selfAdapt(); - } -}; -const rebuildGraph = (extraOption: DeepPartial<GraphOptions>) => { - const finalOptions = defaultsDeep(extraOption, commonOption); - graph.setOptions(finalOptions); - graphRender(); -}; - -let commonOption: GraphOptions; -const initGraph = async (orgTreeData: OrgTreeItem, layoutType: CustomLayout) => { - const treeNodeData = convertOrgTreeToTreeNode(orgTreeData); - - commonOption = { - container: graphRef.value, - data: treeToGraphData(treeNodeData), - node: { - style: { - size: 20, - labelText: (d) => d.label as string, - labelBackground: true, - }, - state: { - active: { - fill: '#00C9C9', - }, - }, - palette: { - field: 'type', - color: 'tableau', - }, - }, - edge: { - type: 'cubic-radial', - state: { - active: { - lineWidth: 3, - stroke: '#009999', - }, - }, - }, - layout: [ - { - type: 'compact-box', - - getHeight: () => { - return 20; - }, - getWidth: () => { - return 20; - }, - // 鍨傜洿闂撮殭 - getVGap: () => { - return 2; - }, - // 姘村钩闂撮殭 - getHGap: () => { - return 120; - }, - }, - ], - behaviors: [ - 'drag-canvas', - 'zoom-canvas', - // 'drag-element', - { - key: 'hover-activate', - type: 'hover-activate', - degree: 5, - direction: 'in', - inactiveState: 'inactive', - }, - ], - transforms: ['place-radial-labels'], - }; - const extraOption: DeepPartial<GraphOptions> = getLayoutOption(layoutType); - - const finalOption: GraphOptions = defaultsDeep(extraOption, commonOption); - graph = new Graph(finalOption); - graphRender(); -}; - -const getLeaf = (item) => { - if (item.Children && item.Children.length > 0) { - const first = item.Children[0]; - return getLeaf(first); - } else { - return item; - } -}; - -const initEvent = () => { - // graph.on('node:click', ({ cell, e, node, view }) => { - // }); -}; - const getFirstOrgTreeList = async () => { // const res = await GetSMCenterFirstOrgTreeList(); /** @description 缁村害鏁伴噺 */ @@ -299,7 +79,6 @@ level: 2, }; }), - // .filter((item, index) => index === 0), }; dimensionCount += metrics.children.length; @@ -310,123 +89,15 @@ const maxCount = Math.max(dimensionCount, metricsCount); return [resData, maxCount]; }; -const selfAdapt = () => { - setTimeout(() => { - graph.fitView(); - }, 100); -}; - -//#region ====================== 宸ュ叿鏍� ====================== -const enum ToolBarType { - /** @description 甯冨眬 */ - Layout, - /** @description 鎼滅储 */ - Search, - /** @description 鏀惧ぇ */ - ZoomIn, - /** @description 缂╁皬 */ - ZoomOut, - /** @description 鑷�傚簲 */ - Reset, -} -type ToolBarItem = { - icon: string; - label: string; - type: ToolBarType; -}; -const toolBarFunList: ToolBarItem[] = [ - // { - // icon: 'sousuo', - // label: '鎼滅储', - // type: ToolBarType.Search, - // }, - { - icon: 'buju', - label: '甯冨眬', - type: ToolBarType.Layout, - }, - { - icon: 'fangda', - label: '鏀惧ぇ', - type: ToolBarType.ZoomIn, - }, - { - icon: 'suoxiao', - label: '缂╁皬', - type: ToolBarType.ZoomOut, - }, - { - icon: 'zishiying', - label: '鑷�傚簲', - type: ToolBarType.Reset, - }, -]; - -const searchIsShow = ref(false); -const searchValue = ref(''); -const ZOOM_OFFSET = 0.15; -const toolBarItemClick = (item: ToolBarItem) => { - switch (item.type) { - case ToolBarType.Layout: - layoutIsShow.value = true; - break; - case ToolBarType.Search: - searchIsShow.value = true; - break; - case ToolBarType.ZoomIn: - graph.zoomBy(1 + ZOOM_OFFSET); - break; - - case ToolBarType.ZoomOut: - graph.zoomBy(1 - ZOOM_OFFSET); - - break; - - case ToolBarType.Reset: - selfAdapt(); - default: - break; - } -}; - -const debounceSearch = () => {}; -const closeSearch = () => { - searchIsShow.value = false; -}; -//#endregion - -//#region ====================== 甯冨眬 ====================== -const layoutIsShow = ref(false); -const closeLayout = () => { - layoutIsShow.value = false; -}; -const activeLayout = ref<CustomLayout>(); -const layoutChange = (layoutType: CustomLayout) => { - const extraOption = getLayoutOption(layoutType); - rebuildGraph(extraOption); -}; -//#endregion onMounted(async () => { if (!agentId) return; firstLoading.value = true; - const [orgTreeData, maxLevelNodeCount] = await (getFirstOrgTreeList() as any).catch(() => { + const [orgTreeData, maxLevelNodeCount] = await (getFirstOrgTreeList() as any).finally(() => { firstLoading.value = false; }); - setTimeout(() => { - const layoutType = getLayoutType(maxLevelNodeCount); - activeLayout.value = layoutType; - initGraph(orgTreeData as OrgTreeItem, layoutType); - - // initEvent(); - // loadData(orgTreeList as any); - firstLoading.value = false; - }, 100); + maxCount.value = maxLevelNodeCount; + graphData.value = convertOrgTreeToTreeNode(orgTreeData); }); </script> -<style scoped lang="scss"> -:deep(g[data-shape='rect'].x6-node > text:hover) { - // text-decoration: underline; - cursor: pointer; -} -</style> +<style scoped lang="scss"></style> diff --git a/src/views/project/yw/systemManage/agentGraph/types.ts b/src/views/project/yw/systemManage/agentGraph/types.ts index 320a134..5f6ee36 100644 --- a/src/views/project/yw/systemManage/agentGraph/types.ts +++ b/src/views/project/yw/systemManage/agentGraph/types.ts @@ -1,31 +1,4 @@ -export const enum CustomLayout { - /** @description 绱у噾鏍戝竷灞� n <= 30*/ - NormalTree, - /** @description 鍨傜洿绱у噾鏍戯紝鏍硅妭鐐瑰湪涓棿 30 < n <= 50*/ - Vertical, - /** @description 杈愬皠绱у噾鏍戯紝> 50 */ - Radial, -} - -export const customLayoutMap ={ - [CustomLayout.NormalTree]:'绱у噾鏍戝竷灞�', - [CustomLayout.Vertical]:'鍨傜洿瀵圭О甯冨眬', - [CustomLayout.Radial]:'杈愬皠鐘跺竷灞�' -} - - - -export const getLayoutType = (maxLeafLen: number) => { - if (maxLeafLen > 50) { - return CustomLayout.Radial; - } else if (30 < maxLeafLen) { - return CustomLayout.Vertical; - } else { - return CustomLayout.NormalTree; - } -}; - export type OrgTreeItem = { treeId: string; diff --git a/src/views/project/yw/systemManage/metricMgr/MetricDetail.vue b/src/views/project/yw/systemManage/metricMgr/MetricDetail.vue index c37c5dd..0996496 100644 --- a/src/views/project/yw/systemManage/metricMgr/MetricDetail.vue +++ b/src/views/project/yw/systemManage/metricMgr/MetricDetail.vue @@ -42,7 +42,7 @@ <el-divider /> </div> </el-tab-pane> - <el-tab-pane label="鎸囨爣鍥捐氨" name="indicatorExploration" class="h-full"> + <el-tab-pane label="鎸囨爣鎺㈢储" name="indicatorExploration" class="h-full"> <div class="h-full flex-column"> <el-form :model="dialogFormValue" class="flex-0" ref="dialogFormRef" :rules="dialogFormRules"> <el-form-item label="鏃ユ湡鍖洪棿锛�" prop="rangValue" v-if="currentMetrics?.is_time_values ?? true"> @@ -86,6 +86,10 @@ </div> </div> </el-tab-pane> + <el-tab-pane label="鎸囨爣鍥捐氨" name="metricGraph" class="h-full"> + <div class="h-full"> + <TreeGraph v-if="graphData" :data="graphData" class="h-full" :maxCount="maxCount" /></div + ></el-tab-pane> </el-tabs> </div> <div class="list_btn" @click="handleExitFlow"> @@ -199,6 +203,7 @@ import TimeRange from '/@/components/chat/chatComponents/summaryCom/components/recordSet/components/TimeRange.vue'; import { formatTime, getDefaultPeriod } from '/@/utils/istation/common.js'; import { eMetrics_Ops } from '/@/views/types/metrics'; +import TreeGraph from '/@/components/graph/treeGraph/TreeGraph.vue'; import { FormRules } from 'element-plus/es/components/form/src/types'; import _, { debounce } from 'lodash'; @@ -206,6 +211,7 @@ import SummaryCom from './components/SummaryCom.vue'; import { chatMetricsJsonByPost } from '/@/api/metrics'; import { useCompRef } from '/@/utils/types'; +import { OrgTreeItem } from '../agentGraph/types'; const defaultTime = ref<[Date, Date]>([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]); const router = useRouter(); const route = useRoute(); @@ -216,7 +222,7 @@ name: '', id: '', } as any, - activeMetricName: 'indicatorExploration', + activeMetricName: 'basicInformation', metricBasicInfo: { descriptionQuotaItems: {}, } as any, @@ -409,6 +415,11 @@ }); } state.activeMetricName = val; + if (val === 'metricGraph') { + if (!graphData.value) { + getGraphTreeData(); + } + } }; //#endregion @@ -428,7 +439,7 @@ groupDimList: [], }); -const currentMetricsId = computed(() => router.currentRoute.value.query.id); +const currentMetricsId = computed(() => router.currentRoute.value.query.id as string); const querySummaryData = ref([]); const queryLoading = ref(false); /** @@ -470,6 +481,59 @@ //#endregion +//#region ====================== 鎸囨爣鍥捐氨 ====================== + +const maxCount = ref(null); +const graphData = ref(null); +const convertOrgTreeToTreeNode = (orgTreeData: OrgTreeItem) => { + const treeData = { + id: orgTreeData.treeId, + label: orgTreeData.label, + data: orgTreeData, + children: orgTreeData.children?.length > 0 ? orgTreeData.children.map((item) => convertOrgTreeToTreeNode(item)) : [], + }; + return treeData; +}; +const getGraphTreeData = () => { + if (!currentMetricsId.value) return; + /** @description 缁村害鏁伴噺 */ + let dimensionCount = 0; + /** @description 鎸囨爣鏁伴噺 */ + let metricsCount = 1; + + const metricsTreeId = `metrics-${currentMetricsId.value}`; + const dimensionList = currentMetrics.value.dimensions ?? []; + dimensionCount = dimensionList.length; + let logicTree: OrgTreeItem = { + treeId: metricsTreeId, + logicId: currentMetricsId.value, + type: 'metrics', + + model: currentMetrics.value, + get label() { + return this.model.title; + }, + level: 0, + children: dimensionList.map((item) => { + const dimensionTreeId = `${metricsTreeId}-dimension-${item.id}`; + return { + treeId: dimensionTreeId, + logicId: item.id, + type: 'dimension', + model: item, + get label() { + return this.model.title; + }, + level: 1, + }; + }), + }; + const resData = logicTree; + maxCount.value = Math.max(dimensionCount, metricsCount); + graphData.value = convertOrgTreeToTreeNode(resData); +}; +//#endregion + onMounted(() => { const { id } = route.query; query(true); -- Gitblit v1.9.3