wujingjing
2024-11-13 17fe923ffb4e05f4fe64d78fd50be5f007f674c3
src/views/project/yw/systemManage/agentGraph/AgentGraph.vue
@@ -1,10 +1,5 @@
<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
@@ -34,46 +29,192 @@
</template>
<script setup lang="ts">
import { DagreLayout } from '@antv/layout';
import { Graph } from '@antv/x6';
import { Graph, GraphOptions, treeToGraphData } from '@antv/g6';
import { defaultsDeep } from 'lodash';
import { onMounted, ref } from 'vue';
import { routeMap } from './routeMap';
import { circleShape, edgeShape, ellipseShape, rectShape } from './shape';
import { getMetricAgentListByPost, getMetricNameListByPost } from '/@/api/metrics';
import router from '/@/router';
import { travelTree } from '/@/utils/util';
import { DeepPartial } from '/@/utils/types';
const graphRef = ref<HTMLDivElement>(null);
let graph: Graph;
const firstLoading = ref(false);
const agentId = router.currentRoute.value.query.id as string;
type OrgTreeItem = {
   treeId: string;
   logicId: string;
   model: any;
   type: 'agent' | 'metrics' | 'dimension';
   label: string;
   level: number;
   children?: OrgTreeItem[];
};
const initGraph = () => {
   // 初始化画布
   graph = new Graph({
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 enum CustomLayout {
   /** @description 紧凑树布局 n <= 30*/
   NormalTree,
   /** @description 垂直紧凑树,根节点在中间 30 < n <= 50*/
   Vertical,
   /** @description 辐射紧凑树,> 50 */
   Radial,
}
const getLayoutType = (maxLeafLen: number) => {
   if (maxLeafLen > 50) {
      return CustomLayout.Radial;
   } else if (30 < maxLeafLen) {
      return CustomLayout.Vertical;
   } else {
      return CustomLayout.NormalTree;
   }
};
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 initGraph = async (orgTreeData: OrgTreeItem, layoutType: CustomLayout) => {
   const treeNodeData = convertOrgTreeToTreeNode(orgTreeData);
   const commonOption: GraphOptions = {
      container: graphRef.value,
      async: true,
      interacting: false,
      connecting: {
         anchor: 'orth',
         connector: 'rounded',
         connectionPoint: 'boundary',
         router: {
            name: 'er',
            args: {
               offset: 24,
               direction: 'H',
      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',
            },
         },
      },
      mousewheel: {
         enabled: true,
      },
      panning: {
         enabled: true,
      },
   });
      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);
   window.graph = graph;
   graph.render();
};
const getLeaf = (item) => {
@@ -86,85 +227,16 @@
};
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: [],
   };
   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();
   // graph.on('node:click', ({ cell, e, node, view }) => {
   // });
};
const getFirstOrgTreeList = async () => {
   // const res = await GetSMCenterFirstOrgTreeList();
   /** @description 维度数量 */
   let dimensionCount = 0;
   /** @description 指标数量 */
   let metricsCount = 0;
   const allAgentRes = getMetricAgentListByPost();
   const metricsRes = getMetricNameListByPost({
      agent_id: agentId,
@@ -174,54 +246,60 @@
   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,
               };
            }),
            // .filter((item, index) => index === 0),
         };
         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();
      graph.fitView();
   }, 100);
};
@@ -255,25 +333,25 @@
   },
   {
      icon: 'zishiying',
      label: '重置',
      label: '自适应',
      type: ToolBarType.Reset,
   },
];
const searchIsShow = ref(false);
const searchValue = ref('');
const ZOOM_OFFSET = 0.05;
const ZOOM_OFFSET = 0.15;
const toolBarItemClick = (item: ToolBarItem) => {
   switch (item.type) {
      case ToolBarType.Search:
         searchIsShow.value = true;
         break;
      case ToolBarType.ZoomIn:
         graph.zoom(ZOOM_OFFSET);
         graph.zoomBy(1 + ZOOM_OFFSET);
         break;
      case ToolBarType.ZoomOut:
         graph.zoom(-ZOOM_OFFSET);
         graph.zoomBy(1 - ZOOM_OFFSET);
         break;
@@ -293,13 +371,15 @@
   if (!agentId) return;
   firstLoading.value = true;
   const orgTreeList = await getFirstOrgTreeList().catch(() => {
   const [orgTreeData, maxLevelNodeCount] = await (getFirstOrgTreeList() as any).catch(() => {
      firstLoading.value = false;
   });
   setTimeout(() => {
      initGraph();
      initEvent();
      loadData(orgTreeList as any);
      const layoutType = getLayoutType(maxLevelNodeCount);
      initGraph(orgTreeData as OrgTreeItem, layoutType);
      // initEvent();
      // loadData(orgTreeList as any);
      firstLoading.value = false;
   }, 100);
});