diff --git a/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailRelationTopo/index.vue b/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailRelationTopo/index.vue index 51d7bb2..679d79a 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailRelationTopo/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailRelationTopo/index.vue @@ -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> diff --git a/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailRelationTopo/node.js b/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailRelationTopo/node.js index b2b4143..af8cd49 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailRelationTopo/node.js +++ b/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailRelationTopo/node.js @@ -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