mirror of https://github.com/veops/cmdb.git
fix:topo图相同节点出现两次的bug
This commit is contained in:
parent
9f5979c1b1
commit
9bb787d3f4
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue