原型:https://g6-v4.antv.vision/examples/case/treeDemos/#decisionTree
<template><div class="main-content-box"><div id="container"></div></div> </template><script> import api from '@/api'; import G6 from '@antv/g6'; import insertCss from 'insert-css'; const colors = {B: '#5B8FF9',R: '#F46649',Y: '#EEBC20',G: '#5BD8A6',DI: '#A7A7A7' };export default {indexName: 'SubjectDependenceTree',components: {},props: {isTreeDialog: Boolean},data() {return {list: [], //扁平结构的数据(从接口获取)noRepeatList: [], //存放剔除重复值之后的数据 mockData: {},nodeChildren: [], //单个node子级数据childrenNode: {} //存放子节点点击的node };},mounted() {let params = { indexCode: 'POLICY_AWARDS_PUNISH_MANAGE', reportTheme: 0, isFirst: true };this.searchQueryIndexTree(params);},methods: {getInit() {let vm = this;G6.registerNode('flow-rect',{shapeType: 'flow-rect',draw(cfg, group) {const { indexName = '', indexCode, isChild, collapsed, indexCategory, status } = cfg;const grey = '#CED4D9';// background: #0092ee;// color: white;const rectConfig = {width: 202,height: 60,lineWidth: 1,fontSize: 12,fill: '#fff',radius: 4,stroke: grey,opacity: 1};const nodeOrigin = {x: -rectConfig.width / 2,y: -rectConfig.height / 2};const textConfig = {textAlign: 'left',textBaseline: 'bottom'};const rect = group.addShape('rect', {attrs: {x: nodeOrigin.x,y: nodeOrigin.y,...rectConfig}});const rectBBox = rect.getBBox();//科目编码group.addShape('text', {attrs: {...textConfig,x: 8 + nodeOrigin.x,y: 23 + nodeOrigin.y,text: indexCode.length > 23 ? indexCode.substr(0, 23) + '...' : indexCode,fontSize: 12,fill: '#000',opacity: 0.85,cursor: 'pointer'},name: 'name-shape'});// 科目名称const price = group.addShape('text', {attrs: {...textConfig,x: 12 + nodeOrigin.x,y: rectBBox.maxY - 12,text: indexName.length > 10 ? indexName.substr(0, 10) + '...' : indexName,fontSize: 12,opacity: 0.85,fill: '#000'}});// 科目类型group.addShape('text', {attrs: {...textConfig,x: price.getBBox().maxX + 5,y: rectBBox.maxY - 12,text: indexCategory == 1 ? '离线指标' : indexCategory == 2 ? '指标填报' : '自定义',fontSize: 10,fill: '#0092ee',opacity: 0.75}});// rect 展开节点( + -边框)if (cfg.isChild) {group.addShape('rect', {attrs: {x: rectConfig.width / 2 - 8,y: -8,width: 16,height: 16,stroke: 'rgba(0, 0, 0, 0.25)',cursor: 'pointer',radius: 8,fill: '#fff'},// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item typename: 'collapse-back',modelId: cfg.id});// collpase text(+ -)group.addShape('text', {attrs: {x: rectConfig.width / 2,y: 1,textAlign: 'center',textBaseline: 'middle',text: collapsed ? '+' : '-',fontSize: 16,cursor: 'pointer',fill: 'rgba(0, 0, 0, 0.25)'},// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item typename: 'collapse-text',modelId: cfg.id});}this.drawLinkPoints(cfg, group);return rect;},// * 响应节点的状态变化。// * 在需要使用动画来响应状态变化时需要被复写 setState(name, value, item) {if (name === 'collapse') {const group = item.getContainer();const collapseText = group.find((e) => e.get('name') === 'collapse-text');if (collapseText) {console.log(value);if (!value) {collapseText.attr({text: '-'});} else {collapseText.attr({text: '+'});}}}},getAnchorPoints() {return [[0, 0.5],[1, 0.5]];}},'rect');G6.registerEdge('flow-cubic',{getControlPoints(cfg) {let controlPoints = cfg.controlPoints; // 指定controlPointsif (!controlPoints || !controlPoints.length) {const { startPoint, endPoint, sourceNode, targetNode } = cfg;const { x: startX, y: startY, coefficientX, coefficientY } = sourceNode ? sourceNode.getModel() : startPoint;const { x: endX, y: endY } = targetNode ? targetNode.getModel() : endPoint;let curveStart = (endX - startX) * coefficientX;let curveEnd = (endY - startY) * coefficientY;curveStart = curveStart > 40 ? 40 : curveStart;curveEnd = curveEnd < -30 ? curveEnd : -30;controlPoints = [{ x: startPoint.x + curveStart, y: startPoint.y },{ x: endPoint.x + curveEnd, y: endPoint.y }];}return controlPoints;},getPath(points) {const path = [];path.push(['M', points[0].x, points[0].y]);path.push(['C', points[1].x, points[1].y, points[2].x, points[2].y, points[3].x, points[3].y]);return path;}},'single-line');const container = document.getElementById('container');const width = container.scrollWidth;const height = container.scrollHeight || 500;const graph = new G6.TreeGraph({container: 'container',width,height,padding: [20, 50],// defaultLevel: 3,defaultZoom: 0.3,// minZoom: 0.3,// maxZoom: 1.5, modes: {default: ['zoom-canvas', 'drag-canvas']},fitView: false, //是否启用画布自适应,适配画布大小animate: false, //是否启用全部动画 defaultNode: {type: 'flow-rect'},// 设置边的参数 defaultEdge: {type: 'cubic-horizontal',style: {stroke: '#CED4D9'}},layout: {type: 'indented',direction: 'LR',dropCap: false,preventOverlap: true, // 防止节点重叠indent: 300,getHeight: () => {return 40;}}});graph.data(this.mockData[0]);graph.render();graph.fitView(100); //适配视图,自动缩放和平移以适应内容大小,默认留白为 30pxgraph.zoom(0.6); // 设置缩放级别为50%const handleCollapse = async (e) => {this.childrenNode = [];const target = e.target;const id = target.get('modelId');const item = graph.findById(id);const nodeModel = item.getModel();const children = nodeModel.children;if (!children || children.length === 0) {this.childrenNode = nodeModel;let params = {indexCode: nodeModel.indexCode, //科目编码reportTheme: 0, //主题isFirst: false //初始化三层依赖树true,后面展开第四层或往后 false };this.addTree(params);if (!nodeModel.children) {nodeModel.children = [];}// 如果childData是一个数组,则直接赋值给parentData.children// 如果是一个对象,则使用parentData.children.push(obj)setTimeout(() => {if (this.nodeChildren && this.nodeChildren.length > 0) {nodeModel.isChild = true;nodeModel.children = this.nodeChildren;nodeModel.collapsed = false;} else {//子级存在单个重复数据nodeModel.isChild = false;nodeModel.children = [];nodeModel.collapsed = true;}item.update(nodeModel);}, 300);} else {nodeModel.collapsed = !nodeModel.collapsed;// graph.updateItem(id, nodeModel); item.update(nodeModel);}setTimeout(() => {graph.render();// graph.refresh();// graph.layout(); item.updatePosition(nodeModel);graph.focusItem(item); //将当前的节点设置为焦点}, 500);};graph.on('collapse-text:click', (e) => {handleCollapse(e);});graph.on('collapse-back:click', (e) => {handleCollapse(e);});this.$nextTick(() => {if (typeof window !== 'undefined')window.onresize = () => {if (!graph || graph.get('destroyed')) return;if (!container || !container.scrollWidth || !container.scrollHeight) return;graph.changeSize(container.scrollWidth, container.scrollHeight);};});},// 展开子结构async addTree(params) {this.list = [];this.nodeChildren = [];const res = await api.queryIndexTree(params);if (res.success) {this.list = res.data;this.nodeChildren = this.comparativeData(); //子级剔除重复数据this.noRepeatList = this.noRepeatList.concat(this.comparativeData()); //剔除重复数据} else {this.$message.error(`${res.message}`);}},// 查询结构树初始化前三层async searchQueryIndexTree(params) {this.list = [];const res = await api.queryIndexTree(params);if (res.success) {this.list = res.data;this.noRepeatList = this.noRepeatList.concat(this.comparativeData()); //剔除重复数据this.mockData = this.toTree(this.noRepeatList); //将扁平化数据转换为树结构this.$nextTick(() => {setTimeout(() => {this.getInit();}, 500);});} else {this.$message.error(`${res.message}`);}},//对比是否存在重复数据,同层级存在重复数据后点击的节点isChild=false,collapsed=false comparativeData() {let arr = [];let hasUsefulNode = false;let result = this.list.map((i) => {let one = this.noRepeatList.findIndex((k) => k.indexCode == i.indexCode);if (one < 0) {hasUsefulNode = true;arr.push({id: i.id,indexCode: i.indexCode,indexName: i.indexName,parentId: i.parentId,indexCategory: i.indexCategory, //类型collapsed: !i.isOpen, //控制是否展开isChild: i.isChild // 控制是否有子级 });}});if (!hasUsefulNode) {let one = this.noRepeatList.find((k) => k.indexCode == this.childrenNode.indexCode);if (one) {one.isChild = false;one.collapsed = true;}}return arr;},//打平数据转换为树结构 toTree(items) {const tree = []; //存放最终的树状结构const itemMap = {}; //存放每个节点数据for (const item of items) {const { indexCode } = item;itemMap[indexCode] = { ...item, children: [] }; //每个节点增加一个children属性,用来存放子节点 }// 遍历所有节点,将每个节点放到其父节点的children数组中for (const item of items) {const { indexCode, parentId } = item;// 如果是根节点,则直接放入结果数组中if (indexCode == parentId) {//if (parentId == null || parentId == 0) { tree.push(itemMap[indexCode]);} else {// 如果不是根节点,则将当前节点放入其父节点的children数组中// 子元素的parentId 等于 父节点的id itemMap[parentId] 父节点//itemMap[indexCode] 当前节点if (itemMap[parentId]) itemMap[parentId].children.push(itemMap[indexCode]);}}return tree;}} }; </script> <style lang="scss" scoped> .g6-component-tooltip {background-color: rgba(0, 0, 0, 0.65);padding: 10px;box-shadow: rgb(174, 174, 174) 0px 0px 10px;width: fit-content;color: #fff;border-radius: 4px; } </style>