| | |
| | | <template> |
| | | <div v-loading="firstLoading" class="h-full w-full bg-white relative"> |
| | | <!-- <div class="absolute right-4 top-4 z-10"> |
| | | |
| | | |
| | | <span class="ywifont ywicon-zishiying cursor-pointer" title="自适应" @click="selfAdaptClick"></span> |
| | | </div> --> |
| | | <div class="h-full w-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="h-full" v-loading="firstLoading"> |
| | | <TreeGraph v-if="graphData" :data="graphData" class="h-full" :maxCount="maxCount" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { DagreLayout } from '@antv/layout'; |
| | | import { Graph } from '@antv/x6'; |
| | | import { onMounted, ref } from 'vue'; |
| | | import { routeMap } from './routeMap'; |
| | | import { circleShape, edgeShape, ellipseShape, rectShape } from './shape'; |
| | | import { getMetricAgentListByPost, getMetricNameListByPost } from '/@/api/metrics'; |
| | | import TreeGraph from '/@/components/graph/treeGraph/TreeGraph.vue'; |
| | | import router from '/@/router'; |
| | | import { travelTree } from '/@/utils/util'; |
| | | 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 initGraph = () => { |
| | | // 初始化画布 |
| | | graph = new Graph({ |
| | | container: graphRef.value, |
| | | async: true, |
| | | interacting: false, |
| | | connecting: { |
| | | anchor: 'orth', |
| | | connector: 'rounded', |
| | | connectionPoint: 'boundary', |
| | | router: { |
| | | name: 'er', |
| | | args: { |
| | | offset: 24, |
| | | direction: 'H', |
| | | }, |
| | | }, |
| | | }, |
| | | mousewheel: { |
| | | enabled: true, |
| | | }, |
| | | panning: { |
| | | enabled: true, |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | 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 }) => { |
| | | return; |
| | | const target = e.target; |
| | | const targetParent = target.parentElement; |
| | | if (!targetParent.className?.baseVal?.includes('name')) return; |
| | | |
| | | const data = node.getData(); |
| | | const leafData = getLeaf(data); |
| | | const routeName = routeMap.get(leafData.Code); |
| | | router.push({ |
| | | name: routeName, |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const loadData = (treeData: any[]) => { |
| | | const data: any = { |
| | | nodes: [], |
| | | edges: [], |
| | | const maxCount = ref(null); |
| | | const graphData = ref(null); |
| | | const firstLoading = ref(false); |
| | | 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)) : [], |
| | | }; |
| | | travelTree( |
| | | treeData, |
| | | (item) => { |
| | | let shapeAttrs = null; |
| | | switch (item.type) { |
| | | case 'agent': |
| | | shapeAttrs = rectShape(item.treeId, item.label); |
| | | break; |
| | | case 'metrics': |
| | | shapeAttrs = ellipseShape(item.treeId, item.label); |
| | | break; |
| | | case 'dimension': |
| | | shapeAttrs = circleShape(item.treeId, item.label); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | data.nodes.push({ |
| | | id: item.treeId, |
| | | |
| | | ...shapeAttrs, |
| | | children: item.children?.map((item) => item.treeId), |
| | | data: item, |
| | | }); |
| | | if (item.children) { |
| | | for (const child of item?.children) { |
| | | const sourceId = `${item.treeId}-b`; |
| | | const targetId = `${child.treeId}-t`; |
| | | |
| | | data.edges.push({ |
| | | source: { cell: item.treeId, port: sourceId }, |
| | | target: { cell: child.treeId, port: targetId }, |
| | | ...edgeShape, |
| | | }); |
| | | } |
| | | } |
| | | }, |
| | | undefined, |
| | | undefined, |
| | | 'children' |
| | | ); |
| | | const dagreLayout = new DagreLayout({ |
| | | type: 'dagre', |
| | | rankdir: 'TB', |
| | | // align: 'UL', |
| | | // ranksep: 30, |
| | | nodesep: 30, |
| | | |
| | | controlPoints: false, |
| | | }); |
| | | let newData = dagreLayout.layout(data); |
| | | |
| | | graph.fromJSON(newData); |
| | | selfAdapt(); |
| | | return treeData; |
| | | }; |
| | | |
| | | const getFirstOrgTreeList = async () => { |
| | | // const res = await GetSMCenterFirstOrgTreeList(); |
| | | |
| | | /** @description 维度数量 */ |
| | | let dimensionCount = 0; |
| | | /** @description 指标数量 */ |
| | | let metricsCount = 0; |
| | | const allAgentRes = getMetricAgentListByPost(); |
| | | const metricsRes = getMetricNameListByPost({ |
| | | agent_id: agentId, |
| | |
| | | const allAgentList = allAgentResult?.values ?? []; |
| | | const metricsList = metricsResult?.values ?? []; |
| | | // const foundAgent = allAgentList.find(item=>item.) |
| | | |
| | | metricsCount = metricsList.length; |
| | | const foundAgent = allAgentList.find((item) => item.id === agentId); |
| | | if (!foundAgent) return []; |
| | | |
| | | const agentTreeId = `agent-${foundAgent.id}`; |
| | | let logicTree = [ |
| | | { |
| | | treeId: agentTreeId, |
| | | logicId: foundAgent.id, |
| | | model: foundAgent, |
| | | type: 'agent', |
| | | get label() { |
| | | return this.model.title; |
| | | }, |
| | | children: metricsList.map((curVal) => { |
| | | const metricsTreeId = `${agentTreeId}-metrics-${curVal.id}`; |
| | | const metrics = { |
| | | treeId: metricsTreeId, |
| | | logicId: curVal.id, |
| | | type: 'metrics', |
| | | |
| | | model: curVal, |
| | | get label() { |
| | | return this.model.title; |
| | | }, |
| | | children: (curVal.dimensions ?? []).map((item) => { |
| | | const dimensionTreeId = `${metricsTreeId}-dimension-${item.id}`; |
| | | return { |
| | | treeId: dimensionTreeId, |
| | | logicId: item.id, |
| | | type: 'dimension', |
| | | model: item, |
| | | get label() { |
| | | return this.model.title; |
| | | }, |
| | | }; |
| | | }), |
| | | }; |
| | | return metrics; |
| | | }, []), |
| | | let logicTree: OrgTreeItem = { |
| | | treeId: agentTreeId, |
| | | logicId: foundAgent.id, |
| | | model: foundAgent, |
| | | type: 'agent', |
| | | get label() { |
| | | return this.model.title; |
| | | }, |
| | | ]; |
| | | level: 0, |
| | | children: metricsList.map((curVal) => { |
| | | const metricsTreeId = `${agentTreeId}-metrics-${curVal.id}`; |
| | | const dimensionList = curVal.dimensions ?? []; |
| | | const metrics: OrgTreeItem = { |
| | | treeId: metricsTreeId, |
| | | logicId: curVal.id, |
| | | type: 'metrics', |
| | | |
| | | model: curVal, |
| | | get label() { |
| | | return this.model.title; |
| | | }, |
| | | level: 1, |
| | | 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: 2, |
| | | }; |
| | | }), |
| | | }; |
| | | dimensionCount += metrics.children.length; |
| | | |
| | | return metrics; |
| | | }, []), |
| | | }; |
| | | const resData = logicTree; |
| | | return resData; |
| | | const maxCount = Math.max(dimensionCount, metricsCount); |
| | | return [resData, maxCount]; |
| | | }; |
| | | const selfAdapt = () => { |
| | | setTimeout(() => { |
| | | graph.zoomToFit(); |
| | | }, 100); |
| | | }; |
| | | |
| | | //#region ====================== 工具栏 ====================== |
| | | const enum ToolBarType { |
| | | Search, |
| | | ZoomIn, |
| | | ZoomOut, |
| | | Reset, |
| | | } |
| | | type ToolBarItem = { |
| | | icon: string; |
| | | label: string; |
| | | type: ToolBarType; |
| | | }; |
| | | const toolBarFunList: ToolBarItem[] = [ |
| | | { |
| | | icon: 'sousuo', |
| | | label: '搜索', |
| | | type: ToolBarType.Search, |
| | | }, |
| | | { |
| | | 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.05; |
| | | const toolBarItemClick = (item: ToolBarItem) => { |
| | | switch (item.type) { |
| | | case ToolBarType.Search: |
| | | searchIsShow.value = true; |
| | | break; |
| | | case ToolBarType.ZoomIn: |
| | | graph.zoom(ZOOM_OFFSET); |
| | | break; |
| | | |
| | | case ToolBarType.ZoomOut: |
| | | graph.zoom(-ZOOM_OFFSET); |
| | | |
| | | break; |
| | | |
| | | case ToolBarType.Reset: |
| | | selfAdapt(); |
| | | default: |
| | | break; |
| | | } |
| | | }; |
| | | |
| | | const debounceSearch = () => {}; |
| | | const closeSearch = () => { |
| | | searchIsShow.value = false; |
| | | }; |
| | | //#endregion |
| | | onMounted(async () => { |
| | | if (!agentId) return; |
| | | firstLoading.value = true; |
| | | |
| | | const orgTreeList = await getFirstOrgTreeList().catch(() => { |
| | | const [orgTreeData, maxLevelNodeCount] = await (getFirstOrgTreeList() as any).finally(() => { |
| | | firstLoading.value = false; |
| | | }); |
| | | setTimeout(() => { |
| | | initGraph(); |
| | | 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> |