mirror of
				https://github.com/veops/cmdb.git
				synced 2025-10-31 11:09:21 +08:00 
			
		
		
		
	fix:topo图相同节点出现两次的bug
This commit is contained in:
		| @@ -1,163 +1,168 @@ | ||||
| <template> | ||||
|   <div | ||||
|     id="ci-detail-relation-topo" | ||||
|     class="ci-detail-relation-topo" | ||||
|     :style="{ width: '100%', marginTop: '20px', height: 'calc(100vh - 136px)' }" | ||||
|   ></div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import _ from 'lodash' | ||||
| import { TreeCanvas } from 'butterfly-dag' | ||||
| import { searchCIRelation } from '@/modules/cmdb/api/CIRelation' | ||||
| import Node from './node.js' | ||||
|  | ||||
| import 'butterfly-dag/dist/index.css' | ||||
| import './index.less' | ||||
|  | ||||
| export default { | ||||
|   name: 'CiDetailRelationTopo', | ||||
|   data() { | ||||
|     return { | ||||
|       topoData: {}, | ||||
|     } | ||||
|   }, | ||||
|   inject: ['ci_types'], | ||||
|   mounted() {}, | ||||
|   methods: { | ||||
|     init() { | ||||
|       const root = document.getElementById('ci-detail-relation-topo') | ||||
|       this.canvas = new TreeCanvas({ | ||||
|         root: root, | ||||
|         disLinkable: false, // 可删除连线 | ||||
|         linkable: false, // 可连线 | ||||
|         draggable: true, // 可拖动 | ||||
|         zoomable: true, // 可放大 | ||||
|         moveable: true, // 可平移 | ||||
|         theme: { | ||||
|           edge: { | ||||
|             shapeType: 'AdvancedBezier', | ||||
|             arrow: true, | ||||
|             arrowPosition: 1, | ||||
|           }, | ||||
|         }, | ||||
|         layout: { | ||||
|           type: 'mindmap', | ||||
|           options: { | ||||
|             direction: 'H', | ||||
|             getSide(d) { | ||||
|               return d.data.side || 'right' | ||||
|             }, | ||||
|             getHeight(d) { | ||||
|               return 10 | ||||
|             }, | ||||
|             getWidth(d) { | ||||
|               return 40 | ||||
|             }, | ||||
|             getHGap(d) { | ||||
|               return 80 | ||||
|             }, | ||||
|             getVGap(d) { | ||||
|               return 40 | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }) | ||||
|       this.canvas.setZoomable(true, true) | ||||
|       this.canvas.on('events', ({ type, data }) => { | ||||
|         const sourceNode = data?.id || null | ||||
|         if (type === 'custom:clickLeft') { | ||||
|           searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=1&&count=10000`).then((res) => { | ||||
|             this.redrawData(res, sourceNode, 'left') | ||||
|           }) | ||||
|         } | ||||
|         if (type === 'custom:clickRight') { | ||||
|           searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=0&&count=10000`).then((res) => { | ||||
|             this.redrawData(res, sourceNode, 'right') | ||||
|           }) | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     setTopoData(data) { | ||||
|       this.canvas = null | ||||
|       this.init() | ||||
|       this.topoData = _.cloneDeep(data) | ||||
|       this.canvas.draw(data, {}, () => { | ||||
|         this.canvas.focusCenterWithAnimate() | ||||
|       }) | ||||
|     }, | ||||
|     redrawData(res, sourceNode, side) { | ||||
|       const newNodes = [] | ||||
|       const newEdges = [] | ||||
|       if (!res.result.length) { | ||||
|         this.$message.info('无层级关系!') | ||||
|         return | ||||
|       } | ||||
|       const ci_types_list = this.ci_types() | ||||
|       res.result.forEach((r) => { | ||||
|         const _findCiType = ci_types_list.find((item) => item.id === r._type) | ||||
|         newNodes.push({ | ||||
|           id: `${r._id}`, | ||||
|           Class: Node, | ||||
|           title: r.ci_type_alias || r.ci_type, | ||||
|           name: r.ci_type, | ||||
|           side: side, | ||||
|           unique_alias: r.unique_alias, | ||||
|           unique_name: r.unique, | ||||
|           unique_value: r[r.unique], | ||||
|           children: [], | ||||
|           icon: _findCiType?.icon || '', | ||||
|           endpoints: [ | ||||
|             { | ||||
|               id: 'left', | ||||
|               orientation: [-1, 0], | ||||
|               pos: [0, 0.5], | ||||
|             }, | ||||
|             { | ||||
|               id: 'right', | ||||
|               orientation: [1, 0], | ||||
|               pos: [0, 0.5], | ||||
|             }, | ||||
|           ], | ||||
|         }) | ||||
|         newEdges.push({ | ||||
|           id: `${r._id}`, | ||||
|           source: 'right', | ||||
|           target: 'left', | ||||
|           sourceNode: side === 'right' ? sourceNode : `${r._id}`, | ||||
|           targetNode: side === 'right' ? `${r._id}` : sourceNode, | ||||
|           type: 'endpoint', | ||||
|         }) | ||||
|       }) | ||||
|       const { nodes, edges } = this.canvas.getDataMap() | ||||
|       // 删除原节点和边 | ||||
|       this.canvas.removeNodes(nodes.map((node) => node.id)) | ||||
|       this.canvas.removeEdges(edges) | ||||
|  | ||||
|       const _topoData = _.cloneDeep(this.topoData) | ||||
|       let result | ||||
|       const getTreeItem = (data, id) => { | ||||
|         for (let i = 0; i < data.length; i++) { | ||||
|           if (data[i].id === id) { | ||||
|             result = data[i] // 结果赋值 | ||||
|             break | ||||
|           } else { | ||||
|             if (data[i].children && data[i].children.length) { | ||||
|               getTreeItem(data[i].children, id) | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       getTreeItem(_topoData.nodes.children, sourceNode) | ||||
|       result.children.push(...newNodes) | ||||
|       _topoData.edges.push(...newEdges) | ||||
|  | ||||
|       this.topoData = _topoData | ||||
|       this.canvas.draw(_topoData, {}, () => {}) | ||||
|     }, | ||||
|   }, | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style></style> | ||||
| <template> | ||||
|   <div | ||||
|     id="ci-detail-relation-topo" | ||||
|     class="ci-detail-relation-topo" | ||||
|     :style="{ width: '100%', marginTop: '20px', height: 'calc(100vh - 136px)' }" | ||||
|   ></div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import _ from 'lodash' | ||||
| import { TreeCanvas } from 'butterfly-dag' | ||||
| import { searchCIRelation } from '@/modules/cmdb/api/CIRelation' | ||||
| import Node from './node.js' | ||||
|  | ||||
| import 'butterfly-dag/dist/index.css' | ||||
| import './index.less' | ||||
|  | ||||
| export default { | ||||
|   name: 'CiDetailRelationTopo', | ||||
|   data() { | ||||
|     return { | ||||
|       topoData: {}, | ||||
|       exsited_ci: [], | ||||
|     } | ||||
|   }, | ||||
|   inject: ['ci_types'], | ||||
|   mounted() {}, | ||||
|   methods: { | ||||
|     init() { | ||||
|       const root = document.getElementById('ci-detail-relation-topo') | ||||
|       this.canvas = new TreeCanvas({ | ||||
|         root: root, | ||||
|         disLinkable: false, // 可删除连线 | ||||
|         linkable: false, // 可连线 | ||||
|         draggable: true, // 可拖动 | ||||
|         zoomable: true, // 可放大 | ||||
|         moveable: true, // 可平移 | ||||
|         theme: { | ||||
|           edge: { | ||||
|             shapeType: 'AdvancedBezier', | ||||
|             arrow: true, | ||||
|             arrowPosition: 1, | ||||
|           }, | ||||
|         }, | ||||
|         layout: { | ||||
|           type: 'mindmap', | ||||
|           options: { | ||||
|             direction: 'H', | ||||
|             getSide(d) { | ||||
|               return d.data.side || 'right' | ||||
|             }, | ||||
|             getHeight(d) { | ||||
|               return 10 | ||||
|             }, | ||||
|             getWidth(d) { | ||||
|               return 40 | ||||
|             }, | ||||
|             getHGap(d) { | ||||
|               return 80 | ||||
|             }, | ||||
|             getVGap(d) { | ||||
|               return 40 | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }) | ||||
|       this.canvas.setZoomable(true, true) | ||||
|       this.canvas.on('events', ({ type, data }) => { | ||||
|         const sourceNode = data?.id || null | ||||
|         if (type === 'custom:clickLeft') { | ||||
|           searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=1&&count=10000`).then((res) => { | ||||
|             this.redrawData(res, sourceNode, 'left') | ||||
|           }) | ||||
|         } | ||||
|         if (type === 'custom:clickRight') { | ||||
|           searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=0&&count=10000`).then((res) => { | ||||
|             this.redrawData(res, sourceNode, 'right') | ||||
|           }) | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     setTopoData(data) { | ||||
|       this.canvas = null | ||||
|       this.init() | ||||
|       this.topoData = _.cloneDeep(data) | ||||
|       this.canvas.draw(data, {}, () => { | ||||
|         this.canvas.focusCenterWithAnimate() | ||||
|       }) | ||||
|     }, | ||||
|     redrawData(res, sourceNode, side) { | ||||
|       const newNodes = [] | ||||
|       const newEdges = [] | ||||
|       if (!res.result.length) { | ||||
|         this.$message.info('无层级关系!') | ||||
|         return | ||||
|       } | ||||
|       const ci_types_list = this.ci_types() | ||||
|       res.result.forEach((r) => { | ||||
|         if (!this.exsited_ci.includes(r._id)) { | ||||
|           const _findCiType = ci_types_list.find((item) => item.id === r._type) | ||||
|           newNodes.push({ | ||||
|             id: `${r._id}`, | ||||
|             Class: Node, | ||||
|             title: r.ci_type_alias || r.ci_type, | ||||
|             name: r.ci_type, | ||||
|             side: side, | ||||
|             unique_alias: r.unique_alias, | ||||
|             unique_name: r.unique, | ||||
|             unique_value: r[r.unique], | ||||
|             children: [], | ||||
|             icon: _findCiType?.icon || '', | ||||
|             endpoints: [ | ||||
|               { | ||||
|                 id: 'left', | ||||
|                 orientation: [-1, 0], | ||||
|                 pos: [0, 0.5], | ||||
|               }, | ||||
|               { | ||||
|                 id: 'right', | ||||
|                 orientation: [1, 0], | ||||
|                 pos: [0, 0.5], | ||||
|               }, | ||||
|             ], | ||||
|           }) | ||||
|         } | ||||
|         newEdges.push({ | ||||
|           id: `${r._id}`, | ||||
|           source: 'right', | ||||
|           target: 'left', | ||||
|           sourceNode: side === 'right' ? sourceNode : `${r._id}`, | ||||
|           targetNode: side === 'right' ? `${r._id}` : sourceNode, | ||||
|           type: 'endpoint', | ||||
|         }) | ||||
|       }) | ||||
|       const { nodes, edges } = this.canvas.getDataMap() | ||||
|       // 删除原节点和边 | ||||
|       this.canvas.removeNodes(nodes.map((node) => node.id)) | ||||
|       this.canvas.removeEdges(edges) | ||||
|  | ||||
|       const _topoData = _.cloneDeep(this.topoData) | ||||
|       _topoData.edges.push(...newEdges) | ||||
|       let result | ||||
|       const getTreeItem = (data, id) => { | ||||
|         for (let i = 0; i < data.length; i++) { | ||||
|           if (data[i].id === id) { | ||||
|             result = data[i] // 结果赋值 | ||||
|             result.edges = _topoData.edges | ||||
|             break | ||||
|           } else { | ||||
|             if (data[i].children && data[i].children.length) { | ||||
|               getTreeItem(data[i].children, id) | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       getTreeItem(_topoData.nodes.children, sourceNode) | ||||
|       result.children.push(...newNodes) | ||||
|  | ||||
|       this.topoData = _topoData | ||||
|       this.canvas.draw(_topoData, {}, () => {}) | ||||
|       this.exsited_ci = [...new Set([...this.exsited_ci, ...res.result.map((r) => r._id)])] | ||||
|     }, | ||||
|   }, | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style></style> | ||||
|   | ||||
| @@ -1,56 +1,56 @@ | ||||
| /* eslint-disable no-useless-constructor */ | ||||
| import { TreeNode } from 'butterfly-dag' | ||||
|  | ||||
| import $ from 'jquery' | ||||
|  | ||||
| class BaseNode extends TreeNode { | ||||
|     constructor(opts) { | ||||
|         super(opts) | ||||
|     } | ||||
|  | ||||
|     draw = (opts) => { | ||||
|         const container = $(`<div class="${opts.id.startsWith('Root') ? 'root' : ''} ci-detail-relation-topo-node"></div>`) | ||||
|             .css('top', opts.top) | ||||
|             .css('left', opts.left) | ||||
|             .attr('id', opts.id) | ||||
|         let icon | ||||
|         if (opts.options.icon) { | ||||
|             if (opts.options.icon.split('$$')[2]) { | ||||
|                 icon = $(`<img style="max-width:16px;max-height:16px;" src="/api/common-setting/v1/file/${opts.options.icon.split('$$')[3]}" />`) | ||||
|             } else { | ||||
|                 icon = $(`<svg class="icon" style="color:${opts.options.icon.split('$$')[1]}" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><use data-v-5bd421da="" xlink:href="#${opts.options.icon.split('$$')[0]}"></use></svg>`) | ||||
|             } | ||||
|         } else { | ||||
|             icon = $(`<span class="icon icon-default">${opts.options.name[0].toUpperCase()}</span>`) | ||||
|         } | ||||
|  | ||||
|         const titleContent = $(`<div title=${opts.options.title} class="title">${opts.options.title}</div>`) | ||||
|         const uniqueDom = $(`<div class="unique">${opts.options.unique_alias || opts.options.unique_name}:${opts.options.unique_value}<div>`) | ||||
|         container.append(icon) | ||||
|         container.append(titleContent) | ||||
|         container.append(uniqueDom) | ||||
|  | ||||
|         if (opts.options.side && !opts.options.children.length) { | ||||
|             const addIcon = $(`<i aria-label="图标: plus-square" class="anticon anticon-plus-square add-icon-${opts.options.side}"><svg viewBox="64 64 896 896" data-icon="plus-square" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M328 544h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z"></path><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z"></path></svg></i>`) | ||||
|             container.append(addIcon) | ||||
|             addIcon.on('click', () => { | ||||
|                 if (opts.options.side === 'left') { | ||||
|                     this.emit('events', { | ||||
|                         type: 'custom:clickLeft', | ||||
|                         data: { ...this } | ||||
|                     }) | ||||
|                 } | ||||
|                 if (opts.options.side === 'right') { | ||||
|                     this.emit('events', { | ||||
|                         type: 'custom:clickRight', | ||||
|                         data: { ...this } | ||||
|                     }) | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|  | ||||
|         return container[0] | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default BaseNode | ||||
| /* eslint-disable no-useless-constructor */ | ||||
| import { TreeNode } from 'butterfly-dag' | ||||
|  | ||||
| import $ from 'jquery' | ||||
|  | ||||
| class BaseNode extends TreeNode { | ||||
|     constructor(opts) { | ||||
|         super(opts) | ||||
|     } | ||||
|  | ||||
|     draw = (opts) => { | ||||
|         const container = $(`<div class="${opts.id.startsWith('Root') ? 'root' : ''} ci-detail-relation-topo-node"></div>`) | ||||
|             .css('top', opts.top) | ||||
|             .css('left', opts.left) | ||||
|             .attr('id', opts.id) | ||||
|         let icon | ||||
|         if (opts.options.icon) { | ||||
|             if (opts.options.icon.split('$$')[2]) { | ||||
|                 icon = $(`<img style="max-width:16px;max-height:16px;" src="/api/common-setting/v1/file/${opts.options.icon.split('$$')[3]}" />`) | ||||
|             } else { | ||||
|                 icon = $(`<svg class="icon" style="color:${opts.options.icon.split('$$')[1]}" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><use data-v-5bd421da="" xlink:href="#${opts.options.icon.split('$$')[0]}"></use></svg>`) | ||||
|             } | ||||
|         } else { | ||||
|             icon = $(`<span class="icon icon-default">${opts.options.name[0].toUpperCase()}</span>`) | ||||
|         } | ||||
|  | ||||
|         const titleContent = $(`<div title=${opts.options.title} class="title">${opts.options.title}</div>`) | ||||
|         const uniqueDom = $(`<div class="unique">${opts.options.unique_alias || opts.options.unique_name}:${opts.options.unique_value}<div>`) | ||||
|         container.append(icon) | ||||
|         container.append(titleContent) | ||||
|         container.append(uniqueDom) | ||||
|  | ||||
|         if (opts.options.side && (!opts.options.children.length && !(opts.options.edges && opts.options.edges.length && opts.options.edges.find(e => e.source === opts.options.side && e.sourceNode === opts.options.id)))) { | ||||
|             const addIcon = $(`<i aria-label="图标: plus-square" class="anticon anticon-plus-square add-icon-${opts.options.side}"><svg viewBox="64 64 896 896" data-icon="plus-square" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M328 544h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z"></path><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z"></path></svg></i>`) | ||||
|             container.append(addIcon) | ||||
|             addIcon.on('click', () => { | ||||
|                 if (opts.options.side === 'left') { | ||||
|                     this.emit('events', { | ||||
|                         type: 'custom:clickLeft', | ||||
|                         data: { ...this } | ||||
|                     }) | ||||
|                 } | ||||
|                 if (opts.options.side === 'right') { | ||||
|                     this.emit('events', { | ||||
|                         type: 'custom:clickRight', | ||||
|                         data: { ...this } | ||||
|                     }) | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|  | ||||
|         return container[0] | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default BaseNode | ||||
|   | ||||
		Reference in New Issue
	
	Block a user