diff --git a/src/views/entityExplorer/EntityGraph.vue b/src/views/entityExplorer/EntityGraph.vue index 80f3357c..cf487f61 100644 --- a/src/views/entityExplorer/EntityGraph.vue +++ b/src/views/entityExplorer/EntityGraph.vue @@ -49,7 +49,6 @@ import { useRoute } from 'vue-router' import { ref, shallowRef } from 'vue' import ForceGraph from 'force-graph' import { Algorithm } from '@antv/g6' -// import ForceGraph3D from '3d-force-graph' import * as d3 from 'd3' import Node, { nodeType } from './entityGraph/node' import Link, { linkType } from './entityGraph/link' @@ -71,9 +70,8 @@ export default { links: [], graph: shallowRef(null), defaultChargeStrength: -20, // 之前的设置-20 - defaultLinkDistance: 40, + defaultLinkDistance: 80, defaultMargin: 2, // 图像与箭头的距离 - clickNode: null, rootNode: null } }, @@ -82,110 +80,193 @@ export default { try { const initialData = await this.generateInitialData() this.initialData = _.cloneDeep(initialData) // 初始化数据 + console.info(initialData) let hoverNode = null this.graph = ForceGraph()(document.getElementById('entityGraph')) .graphData(initialData) .nodeCanvasObject((node, ctx) => { + /* + * 共有4种 nodeType,3种 entityType + * */ const nodeStyle = this.getNodeStyle(node.type, node.data.entityType) - const iconWidth = nodeStyle.iconWidth / 2 - const iconHeight = nodeStyle.iconHeight / 2 - const x = node.x - iconWidth / 2 - const y = node.y - iconHeight / 2 + switch (node.type) { + case nodeType.rootNode: { + // 如果是鼠标点击高亮的,最外层加上第三层圆环 + if (node === this.clickNode) { + ctx.beginPath() + ctx.arc(node.x, node.y, nodeStyle.selectedShadowR, 0, 2 * Math.PI, false) + ctx.closePath() + ctx.fillStyle = nodeStyle.selectedShadowColor + ctx.fill() + } - if (node.type === nodeType.rootNode) { - if (this.clickNode && this.clickNode.type === nodeType.rootNode) { + // 第二层圆环 ctx.beginPath() - ctx.lineWidth = 1 - ctx.strokeStyle = 'transparent' - ctx.arc(node.x, node.y, nodeStyle.selectedShadowR / 2, 0, Math.PI * 2) - ctx.fillStyle = nodeStyle.selectedShadowColor// 先画圆形,才能填色 + ctx.arc(node.x, node.y, nodeStyle.shadowR, 0, 2 * Math.PI, false) + ctx.closePath() + ctx.fillStyle = node === this.clickNode || node === hoverNode ? + nodeStyle.hoveredShadowColor : + nodeStyle.shadowColor + ctx.fill() + // 内部挖空 + ctx.beginPath() + ctx.arc(node.x, node.y, nodeStyle.innerR, 0, 2 * Math.PI, false) + ctx.closePath() + ctx.fillStyle = nodeStyle.fillStyle ctx.fill() - ctx.stroke() + // 第一层圆环 ctx.beginPath() + ctx.arc(node.x, node.y, nodeStyle.innerR, 0, 2 * Math.PI, false) + ctx.closePath() ctx.lineWidth = 1 - ctx.strokeStyle = 'transparent' - ctx.arc(node.x, node.y, nodeStyle.shadowR / 2, 0, Math.PI * 2) - ctx.fillStyle = nodeStyle.shadowColor - ctx.fill() - ctx.stroke() - } else if (hoverNode && hoverNode.id === node.id) { - ctx.beginPath() - ctx.lineWidth = 1 - ctx.strokeStyle = 'transparent' - ctx.arc(node.x, node.y, nodeStyle.shadowR / 2, 0, Math.PI * 2) - ctx.fillStyle = nodeStyle.hoveredShadowColor - ctx.fill() - ctx.stroke() - } else { - ctx.beginPath() - ctx.lineWidth = 1 - ctx.strokeStyle = 'transparent' - ctx.arc(node.x, node.y, nodeStyle.shadowR / 2, 0, Math.PI * 2) - ctx.fillStyle = nodeStyle.shadowColor - ctx.fill() + ctx.strokeStyle = nodeStyle.borderColor ctx.stroke() + // 图片 + ctx.drawImage(node.img, node.x - nodeStyle.iconWidth / 2, node.y - nodeStyle.iconHeight / 2, nodeStyle.iconWidth, nodeStyle.iconHeight); + // 文字 + ctx.font = nodeStyle.font + const textWidth = ctx.measureText(node.label).width + const bckgDimensions = [textWidth, 9].map(n => n + 9 * 0.2) + ctx.fillStyle = nodeStyle.fontBgColor + ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2 + 31, ...bckgDimensions) // 文字的白底 + + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + ctx.fillStyle = nodeStyle.fontColor + ctx.fillText(node.label, node.x, node.y + 30) + break } - } else if (node.type === nodeType.listNode) { - if (this.clickNode && this.clickNode.id === node.id) { + case nodeType.listNode: { + // 如果是鼠标点击或者悬停的,有一层圆环 + if (node === this.clickNode || node === hoverNode) { + ctx.beginPath() + ctx.arc(node.x, node.y, nodeStyle.shadowR, 0, 2 * Math.PI, false) + ctx.closePath() + if (node === this.currentSelectedNode) { + ctx.fillStyle = nodeStyle.selectedShadowColor + } else { + ctx.fillStyle = nodeStyle.hoveredShadowColor + } + ctx.fill() + } + + // 内部填白 ctx.beginPath() - ctx.lineWidth = 1 - ctx.strokeStyle = 'transparent' - ctx.arc(node.x, node.y, nodeStyle.shadowR / 2, 0, Math.PI * 2) - ctx.fillStyle = nodeStyle.selectedShadowColor + ctx.arc(node.x, node.y, nodeStyle.innerR, 0, 2 * Math.PI, false) + ctx.closePath() + ctx.fillStyle = nodeStyle.fillStyle ctx.fill() - ctx.stroke() - } else if (hoverNode && hoverNode.id === node.id) { + + // 第一层圆环 ctx.beginPath() + ctx.arc(node.x, node.y, nodeStyle.innerR, 0, 2 * Math.PI, false) + ctx.closePath() ctx.lineWidth = 1 - ctx.strokeStyle = 'transparent' - ctx.arc(node.x, node.y, nodeStyle.shadowR / 2, 0, Math.PI * 2) - ctx.fillStyle = nodeStyle.hoveredShadowColor - ctx.fill() + ctx.strokeStyle = nodeStyle.borderColor ctx.stroke() + // 图片 + ctx.drawImage(node.img, node.x - nodeStyle.iconWidth / 2, node.y - nodeStyle.iconHeight / 2, nodeStyle.iconWidth, nodeStyle.iconHeight) + // 文字 + ctx.font = nodeStyle.font + const textWidth = ctx.measureText(node.label).width + const bckgDimensions = [textWidth, 8].map(n => n + 8 * 0.2) + ctx.fillStyle = nodeStyle.fontBgColor + ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2 + 24, ...bckgDimensions) // 文字的白底 + + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + ctx.fillStyle = nodeStyle.fontColor + ctx.fillText(node.label, node.x, node.y + 24) + break + } + case nodeType.entityNode: { + // 先画个白底圆环,避免 link 穿过不好看 + ctx.beginPath() + ctx.arc(node.x, node.y, nodeStyle.innerR, 0, 2 * Math.PI, false) + ctx.closePath() + ctx.fillStyle = nodeStyle.fillStyle + ctx.fill() + // 如果是鼠标点击或者悬停的,有一层圆环 + if (node === this.clickNode || node === hoverNode) { + ctx.beginPath() + ctx.arc(node.x, node.y, nodeStyle.shadowR, 0, 2 * Math.PI, false) + ctx.closePath() + if (node === this.clickNode) { + ctx.fillStyle = nodeStyle.selectedShadowColor + } else { + ctx.fillStyle = nodeStyle.hoveredShadowColor + } + ctx.fill() + } + // 图片 + ctx.drawImage(node.img, node.x - nodeStyle.iconWidth / 2, node.y - nodeStyle.iconHeight / 2, nodeStyle.iconWidth, nodeStyle.iconHeight) + break + } + case nodeType.tempNode: { + // 先画个白底圆环,避免 link 穿过不好看 + ctx.beginPath() + ctx.arc(node.x, node.y, nodeStyle.innerR, 0, 2 * Math.PI, false) + ctx.closePath() + ctx.fillStyle = nodeStyle.fillStyle + ctx.fill() + // 画透明度0.7的图标 + ctx.globalAlpha = 0.7 + ctx.drawImage(node.img, node.x - nodeStyle.iconWidth / 2, node.y - nodeStyle.iconHeight / 2, nodeStyle.iconWidth, nodeStyle.iconHeight) + ctx.globalAlpha = 1 + break } } - - ctx.beginPath() - ctx.lineWidth = 0.5 - ctx.strokeStyle = nodeStyle.borderColor// 边的颜色 - ctx.arc(node.x, node.y, nodeStyle.innerR / 2, 0, Math.PI * 2) - ctx.fillStyle = nodeStyle.fill// 中间的填充色 - ctx.fill() - ctx.stroke() - - ctx.drawImage(node.img, x, y, iconWidth, iconHeight) - - if (node.type === nodeType.rootNode) { - ctx.font = nodeStyle.font - ctx.fillStyle = nodeStyle.fillStyle - ctx.textAlign = nodeStyle.textAlign - ctx.textBaseline = nodeStyle.textBaseline - ctx.fillText(node.label, node.x, node.y + nodeStyle.selectedShadowR / 2 + 5) - } else if (node.type === nodeType.listNode || node.type === nodeType.tempNode) { - ctx.beginPath() - ctx.lineWidth = 1 - ctx.strokeStyle = '#FFFFFF'// 边的颜色 - ctx.rect(node.x - 19, node.y + nodeStyle.innerR / 2 + 8 - 6, 38, 12) - ctx.fillStyle = '#FFFFFF' - ctx.fill() - ctx.stroke() - - ctx.font = nodeStyle.font - ctx.fillStyle = nodeStyle.fillStyle - ctx.textAlign = nodeStyle.textAlign - ctx.textBaseline = nodeStyle.textBaseline - ctx.fillText(node.label, node.x, node.y + nodeStyle.innerR / 2 + 8) - } - }) - .nodePointerAreaPaint((node, color, ctx) => { // 鼠标hover起作用的范围 - const nodeStyle = this.getNodeStyle(node.type, node.data.entityType) - const size = nodeStyle.innerR - ctx.fillStyle = color - ctx.fillRect(node.x - size / 2, node.y - size / 2, size, size) // draw square as pointer trap }) .linkCanvasObject((link, ctx) => { - if (link.source.x !== undefined && link.source.y !== undefined && link.target.x !== undefined && link.target.y !== undefined) { + const start = link.source + const end = link.target + const width = 1 // 线宽 + const arrowSize = 3 // 箭头大小 + const shortenedLength = 20 // link 末端缩短长度 + + // 计算箭头角度 + const dx = end.x - start.x + const dy = end.y - start.y + const angle = Math.atan2(dy, dx) // 计算与x轴的角度(弧度) + const lineEndX = end.x - shortenedLength * Math.cos(angle) + const lineEndY = end.y - shortenedLength * Math.sin(angle) + const arrowEndX = lineEndX + arrowSize * Math.cos(angle) + const arrowEndY = lineEndY + arrowSize * Math.sin(angle) + + // 绘制线 + let color + ctx.beginPath() + if (link.type === linkType.temp) { + ctx.setLineDash([2, 2]) + color = 'rgba(119,131,145,0.2)' // 虚线颜色 + } else { + ctx.setLineDash([]) + if (this.clickNode === link.source || this.clickNode === link.target) { + color = 'rgba(119,131,145,0.8)' // 高亮线颜色 + } else { + color = 'rgba(119,131,145,0.3)' // 普通线颜色 + } + } + ctx.moveTo(start.x, start.y) + ctx.lineTo(lineEndX, lineEndY) + ctx.strokeStyle = color + ctx.lineWidth = width + ctx.stroke() + + // 绘制箭头 + ctx.save() // 保存当前状态以便之后恢复 + ctx.translate(arrowEndX, arrowEndY) // 将坐标原点移动到箭头末端 + ctx.rotate(angle) // 根据链接方向旋转坐标系 + ctx.beginPath() + ctx.moveTo(0, 0) + ctx.lineTo(-arrowSize, arrowSize) // 绘制箭头的一边 + ctx.lineTo(-arrowSize, -arrowSize) // 绘制箭头的另一边 + ctx.closePath() + ctx.fillStyle = color + ctx.fill() + ctx.restore() // 恢复之前保存的状态 + /*if (link.source.x !== undefined && link.source.y !== undefined && link.target.x !== undefined && link.target.y !== undefined) { const nodeStyle = this.getNodeStyle(link.target.type, link.target.data.entityType) ctx.lineWidth = 0.5 @@ -213,7 +294,7 @@ export default { ctx.fill() ctx.closePath() ctx.stroke() - } + }*/ }) .autoPauseRedraw(false) // keep redrawing after engine has stopped如果您有依赖于画布的不断重绘的自定义动态对象,建议关闭此选项。 .onNodeHover(node => { @@ -223,6 +304,7 @@ export default { } }) .centerAt(0, 30)// 设置中心节点位置 + .zoom(1) .onNodeClick(async (node, e) => { this.clickNode = node || null if (node.type !== 'tempNode') { @@ -256,7 +338,7 @@ export default { this.getIconUrl(k, false, false) ) - const tempLink = new Link(node, tempNode, 'temp', this.defaultLinkDistance, 3) + const tempLink = new Link(node, tempNode, linkType.temp) toAddNodes.push(tempNode) toAddLinks.push(tempLink) } @@ -265,7 +347,7 @@ export default { if (toAddNodes.length || toAddLinks.length) { this.addItems(toAddNodes, toAddLinks) } - this.rightBox.node = _.cloneDeep(node) + this.rightBox.node = node this.rightBox.mode = 'detail' } catch (e) { console.error(e) @@ -274,17 +356,17 @@ export default { this.rightBox.loading = false } } else if (node.type === nodeType.listNode) { - this.rightBox.node = _.cloneDeep(node) + this.rightBox.node = node this.rightBox.mode = 'list' } else if (node.type === nodeType.rootNode) { - this.rightBox.node = _.cloneDeep(node) + this.rightBox.node = node this.rightBox.mode = 'detail' } } else { // 点击tempNode,根据source生成listNode和entityNode以及对应的edge。查完entityNode的接口再删除临时node和edge。 // 若已达第六层,则只生成listNode,不再展开entityNode const nodes = [] - const edges = [] + const links = [] const sourceNode = node.getSourceNode(this.graph.graphData()) const listNode = new Node( nodeType.listNode, @@ -299,12 +381,12 @@ export default { this.getIconUrl(node.data.entityType, false, false) ) nodes.push(listNode) - edges.push(new Link(sourceNode, listNode, null, this.defaultLinkDistance, 2)) + links.push(new Link(sourceNode, listNode, null)) // 判断listNode的sourceNode层级,若大于等于10(即第6层listNode),则不继续拓展entity node,并给用户提示。否则拓展entity node const level = this.getNodeLevel(listNode.sourceNode.id) if (level < 10) { - // this.rightBox.loading = true + this.rightBox.loading = true try { const entities = await sourceNode.queryRelatedEntities(listNode.data.entityType) sourceNode.data.relatedEntities[listNode.data.entityType].list.push(...entities.list) @@ -314,9 +396,9 @@ export default { entityName: entity.vertex, x: e.x + Math.random() * 100 - 50, y: e.y + Math.random() * 100 - 50 - }, listNode, this.defaultChargeStrength, this.getIconUrl(listNode.data.entityType, true, false)) + }, listNode, this.defaultChargeStrength, this.getIconUrl(listNode.data.entityType, false, false)) nodes.push(entityNode) - edges.push(new Link(listNode, entityNode, null, this.defaultLinkDistance, 2)) + links.push(new Link(listNode, entityNode, null)) }) } catch (e) { console.error(e) @@ -327,7 +409,7 @@ export default { } else { this.$message.warning(this.$t('tip.maxExpandDepth')) } - this.addItems(nodes, edges) + this.addItems(nodes, links) this.cleanTempItems() // this.graph.layout() @@ -361,7 +443,10 @@ export default { .d3Force('link', d3.forceLink().id(link => link.id) .distance(link => { - return link.distance + if (link.source.type === nodeType.rootNode) { + return 160 + } + return 80 }) .strength(link => { return link.strength @@ -541,27 +626,26 @@ export default { return this.getEntityNodeStyle(entityType) } case nodeType.tempNode: { - return this.getTempNodeStyle(entityType) + return this.getEntityNodeStyle(entityType) } } }, getRootNodeStyle (entityType) { const nodeStyle = { - innerR: 26, - fill: '#FFFFFF', - shadowR: 31.5, - selectedShadowR: 38.5, - font: '8px NotoSansSChineseRegular', - textAlign: 'center', - textBaseline: 'middle', - fillStyle: '#353636' + innerR: 19, + fillStyle: '#FFFFFF', + shadowR: 25, + selectedShadowR: 36, + font: '9px NotoSansSChineseRegular', + fontColor: '#353636', + fontBgColor: 'rgba(255,255,255,0.8)' } switch (entityType) { case 'ip': { return { ...nodeStyle, - iconWidth: 26, - iconHeight: 23, + iconWidth: 24, + iconHeight: 21, borderColor: 'rgba(126,159,84,1)', shadowColor: 'rgba(126,159,84,0.21)', hoveredShadowColor: 'rgba(126,159,84,0.36)', @@ -571,7 +655,7 @@ export default { case 'domain': { return { ...nodeStyle, - iconWidth: 28, + iconWidth: 24, iconHeight: 24, borderColor: 'rgba(56,172,210,1)', shadowColor: 'rgba(56,172,210,0.21)', @@ -582,8 +666,8 @@ export default { case 'app': { return { ...nodeStyle, - iconWidth: 24, - iconHeight: 26, + iconWidth: 21, + iconHeight: 24, borderColor: 'rgba(229,162,25,1)', shadowColor: 'rgba(229,162,25,0.21)', hoveredShadowColor: 'rgba(229,162,25,0.36)', @@ -593,8 +677,8 @@ export default { } return { ...nodeStyle, - iconWidth: 26, - iconHeight: 23, + iconWidth: 24, + iconHeight: 21, borderColor: 'rgba(126,159,84,1)', shadowColor: 'rgba(126,159,84,0.21)', hoveredShadowColor: 'rgba(126,159,84,0.36)', @@ -602,153 +686,68 @@ export default { } }, getListNodeStyle (entityType) { - const nodeStyle = { - innerR: 21, - shadowR: 26.5, - fill: '#FFFFFF', - font: '8px NotoSansSChineseRegular', - textAlign: 'center', - textBaseline: 'middle', - fillStyle: '#353636' - } + let iconWidth = 20 + let iconHeight = 18 switch (entityType) { case 'ip': { - return { - ...nodeStyle, - iconWidth: 24, - iconHeight: 21, - borderColor: 'rgba(119,131,145,0.6)', - selectedBorderColor: 'rgba(126,159,84,1)', - hoveredShadowColor: 'rgba(151,151,151,0.21)', - selectedShadowColor: 'rgba(126,159,84,0.21)' - } + iconWidth = 20 + iconHeight = 18 + break } case 'domain': { - return { - ...nodeStyle, - iconWidth: 24, - iconHeight: 24, - borderColor: 'rgba(119,131,145,0.6)', - selectedBorderColor: 'rgba(56,172,210,1)', - hoveredShadowColor: 'rgba(151,151,151,0.21)', - selectedShadowColor: 'rgba(56,172,210,0.21)' - } + iconWidth = 20 + iconHeight = 20 + break } case 'app': { - return { - ...nodeStyle, - iconWidth: 22, - iconHeight: 24, - borderColor: 'rgba(119,131,145,0.6)', - selectedBorderColor: 'rgba(229,162,25,1)', - hoveredShadowColor: 'rgba(151,151,151,0.21)', - selectedShadowColor: 'rgba(229,162,25,0.21)' - } + iconWidth = 18 + iconHeight = 20 + break } } return { - ...nodeStyle, - iconWidth: 24, - iconHeight: 21, - borderColor: 'rgba(119,131,145,0.6)', - selectedBorderColor: 'rgba(126,159,84,1)', + innerR: 16, + shadowR: 22, + fillStyle: '#FFFFFF', + font: '9px NotoSansSChineseRegular', + fontColor: '#353636', + fontBgColor: 'rgba(255,255,255,0.8)', + iconWidth, + iconHeight, + borderColor: 'rgba(119,131,145,0.5)', + selectedBorderColor: 'rgba(119,131,145,0.8)', hoveredShadowColor: 'rgba(151,151,151,0.21)', - selectedShadowColor: 'rgba(126,159,84,0.21)' - } - }, - getTempNodeStyle (entityType) { - const nodeStyle = { - innerR: 14, - fill: 'transparent', - font: '8px NotoSansSChineseRegular', - textAlign: 'center', - textBaseline: 'middle', - fillStyle: '#353636' - } - switch (entityType) { - case 'ip': { - return { - ...nodeStyle, - iconWidth: 24, - iconHeight: 21, - borderColor: '#FFFFFF', - selectedBorderColor: 'rgba(126,159,84,1)', - hoveredShadowColor: 'rgba(151,151,151,0.21)', - selectedShadowColor: 'rgba(126,159,84,0.21)' - } - } - case 'domain': { - return { - ...nodeStyle, - iconWidth: 24, - iconHeight: 24, - borderColor: '#FFFFFF', - selectedBorderColor: 'rgba(56,172,210,1)', - hoveredShadowColor: 'rgba(151,151,151,0.21)', - selectedShadowColor: 'rgba(56,172,210,0.21)' - } - } - case 'app': { - return { - ...nodeStyle, - iconWidth: 22, - iconHeight: 24, - borderColor: '#FFFFFF', - selectedBorderColor: 'rgba(229,162,25,1)', - hoveredShadowColor: 'rgba(151,151,151,0.21)', - selectedShadowColor: 'rgba(229,162,25,0.21)' - } - } - } - return { - ...nodeStyle, - iconWidth: 24, - iconHeight: 21, - borderColor: '#FFFFFF', - selectedBorderColor: 'rgba(126,159,84,1)', - hoveredShadowColor: 'rgba(151,151,151,0.21)', - selectedShadowColor: 'rgba(126,159,84,0.21)' + selectedShadowColor: 'rgba(151,151,151,0.4)' } }, getEntityNodeStyle (entityType) { - const nodeStyle = { - innerR: 12, - fill: 'transparent' - } + let iconWidth = 20 + let iconHeight = 18 switch (entityType) { case 'ip': { - return { - ...nodeStyle, - iconWidth: 22, - iconHeight: 20, - selectedShadowColor: 'rgba(126,159,84,0.1)' - } + iconWidth = 20 + iconHeight = 18 + break } case 'domain': { - return { - ...nodeStyle, - iconWidth: 22, - iconHeight: 22, - selectedShadowColor: 'rgba(56,172,210,0.1)' - } + iconWidth = 20 + iconHeight = 20 + break } case 'app': { - return { - ...nodeStyle, - iconWidth: 21, - iconHeight: 24, - selectedShadowColor: 'rgba(229,162,25,0.1)' - } + iconWidth = 18 + iconHeight = 20 + break } } return { - ...nodeStyle, - iconWidth: 22, - iconHeight: 20, - borderColor: 'rgba(119,131,145,1)', - selectedBorderColor: 'rgba(126,159,84,1)', - hoveredShadowColor: 'rgba(151,151,151,0.21)', - selectedShadowColor: 'rgba(126,159,84,0.1)' + shadowR: 15, + innerR: 10, + fillStyle: '#FFFFFF', + iconWidth, + iconHeight, + hoveredShadowColor: 'rgba(151,151,151,0.12)', + selectedShadowColor: 'rgba(151,151,151,0.24)' } }, /** @@ -822,7 +821,7 @@ export default { l.source = l.source.id l.target = l.target.id }) - const info = findShortestPath(g6FormatData, this.entity.entityName, id) + const info = findShortestPath(g6FormatData, this.entity.entityName + nodeType.rootNode, id) return info.length }, generateTempNodeCoordinate (sourceNode, event) { @@ -847,7 +846,7 @@ export default { cleanTempItems () { const { nodes, links } = this.graph.graphData() const newNodes = nodes.filter(n => n.type !== nodeType.tempNode) - const newLinks = links.filter(l => l.level !== 3) + const newLinks = links.filter(l => l.type !== linkType.temp) if (newNodes.length !== nodes.length || newLinks.length !== links.length) { this.graph.graphData({ nodes: newNodes, links: newLinks }) } @@ -895,7 +894,7 @@ export default { this.getIconUrl(k, false, false) ) listNodes.push(listNode) - links.push(new Link(rootNode, listNode, null, 60, 1)) + links.push(new Link(rootNode, listNode, null)) } }) // entityNode @@ -913,10 +912,10 @@ export default { }, listNode, this.defaultChargeStrength, - this.getIconUrl(listNode.data.entityType, true, false) + this.getIconUrl(listNode.data.entityType, false, false) ) entityNodes.push(entityNode) - links.push(new Link(listNode, entityNode, null, this.defaultLinkDistance, 2)) + links.push(new Link(listNode, entityNode, null)) }) } nodes.push(...listNodes, ...entityNodes) @@ -946,14 +945,14 @@ export default { const toAddNode = new Node(nodeType.entityNode, entity.vertex, { entityType: expandType, entityName: entity.vertex - }, node, this.defaultChargeStrength, this.getIconUrl(node.data.entityType, true, false)) + }, node, this.defaultChargeStrength, this.getIconUrl(node.data.entityType, false, false)) toAddNodes.push(toAddNode) - const toAddLink = new Link(node, toAddNode, null, this.defaultLinkDistance) + const toAddLink = new Link(node, toAddNode, null) toAddLinks.push(toAddLink) }) this.addItems(toAddNodes, toAddLinks) - this.rightBox.node = _.cloneDeep(node) + this.rightBox.node = node } catch (e) { console.error(e) this.$message.error(this.errorMsgHandler(e)) @@ -986,7 +985,7 @@ export default { let listNode = neighbors.targetNodes.find(n => n.data.entityType === expandType) if (!listNode) { listNode = new Node(nodeType.listNode, `${node.id}__${expandType}-list`, { entityType: expandType }, node, this.defaultChargeStrength, this.getIconUrl(expandType, false, false)) - const link = new Link(node, listNode, null, this.defaultLinkDistance) + const link = new Link(node, listNode, null) toAddNodes.push(listNode) toAddLinks.push(link) } @@ -994,9 +993,9 @@ export default { const entityNode = new Node(nodeType.entityNode, entity.vertex, { entityType: expandType, entityName: entity.vertex - }, listNode, this.defaultChargeStrength, this.getIconUrl(expandType, true, false)) + }, listNode, this.defaultChargeStrength, this.getIconUrl(expandType, false, false)) toAddNodes.push(entityNode) - toAddLinks.push(new Link(listNode, entityNode, null, this.defaultLinkDistance)) + toAddLinks.push(new Link(listNode, entityNode, null)) }) this.addItems(toAddNodes, toAddLinks) this.rightBox.node = _.cloneDeep(node) @@ -1053,10 +1052,12 @@ export default { node: null, loading: true }) + const clickNode = shallowRef(null) return { entity, rightBox, - graph + graph, + clickNode } } } diff --git a/src/views/entityExplorer/entityGraph/GraphEntityDetail.vue b/src/views/entityExplorer/entityGraph/GraphEntityDetail.vue index e5abec1b..cd848d82 100644 --- a/src/views/entityExplorer/entityGraph/GraphEntityDetail.vue +++ b/src/views/entityExplorer/entityGraph/GraphEntityDetail.vue @@ -176,6 +176,7 @@ export default { node: { deep: true, handler (n) { + console.info(n) this.handleDetailData(n) } } @@ -269,6 +270,8 @@ export default { return location || '-' }, handleDetailData (node) { + console.info(node) + console.info(_.get(node, 'data.relatedEntities.ip.list', [])) const n = node const type = _.get(n, 'data.entityType', '') switch (type) { @@ -432,6 +435,7 @@ export default { ] } } + console.info(this.relationList) } } } diff --git a/src/views/entityExplorer/entityGraph/link.js b/src/views/entityExplorer/entityGraph/link.js index c57ab6af..672c7b1c 100644 --- a/src/views/entityExplorer/entityGraph/link.js +++ b/src/views/entityExplorer/entityGraph/link.js @@ -1,17 +1,19 @@ +import { nodeType } from './node' + export default class Link { - constructor (sourceNode, targetNode, type = linkType.normal, distance, level = 1) { + constructor (sourceNode, targetNode, type) { this.source = sourceNode.id this.target = targetNode.id - this.type = type - this.distance = distance || 20// 连接中心节点的线的distance为60,其他节点为20 - this.strength = level === 1 ? 0.5 : (level === 2 ? 1 : 1)// level2:设置为0.5-1 - this.color = '#c8c8cc'// 未点击线所连接的节点时线的颜色 - this.clickColor = '#7b7b99'// 点击此线所连接的节点后,线的颜色 - // this.width = 1//未点击线所连接的节点时线的宽度 - // this.arrowColor = '#c8c8cc' - // this.clickArrowColor = '#7b7b99' - // this.clickWidth = 1//点击节点后,此节点所连接的线的宽度 - this.level = level// 1:source为中心节点的线,是直线; 2:source为listNode节点的线,是直线; 3:target为临时节点的线,是虚线 + this.type = type || linkType.normal + this.strength = strengthHandler(sourceNode, targetNode) + } +} + +function strengthHandler (sourceNode, targetNode) { + if (sourceNode.type === nodeType.rootNode) { + return 0.5 + } else { + return 1 } } diff --git a/src/views/entityExplorer/entityGraph/node.js b/src/views/entityExplorer/entityGraph/node.js index a4c4352d..7c2a8fad 100644 --- a/src/views/entityExplorer/entityGraph/node.js +++ b/src/views/entityExplorer/entityGraph/node.js @@ -16,6 +16,8 @@ export default class Node { this.type = type this.vx = 0 this.vy = 0 + this.x = null + this.y = null this.fx = sourceNode ? null : 0// 设置为0,即可固定中心节点。0为中心节点,1为中心节点的子节点,2为第三级节点 this.fy = sourceNode ? null : 0// 设置为0,即可固定中心节点。0为中心节点,1为中心节点的子节点,2为第三级节点 this.preDragX = null @@ -30,6 +32,20 @@ export default class Node { } this.label = generateLabel(type, id, data, sourceNode) this.name = builtTooltip(this) + + // val 值决定鼠标触发 hover 的半径 + switch (type) { + case nodeType.rootNode: + this.val = 19 + break + case nodeType.listNode: + this.val = 16 + break + case nodeType.entityNode: + case nodeType.tempNode: + this.val = 10 + break + } } // listNode和entityNode专用,查询实体信息 @@ -99,7 +115,6 @@ export default class Node { if (this.data.entityType === entityType.domain && targetEntityType === entityType.domain) { _targetEntityType = 'subdomain' } - console.info(this.data, targetEntityType) const url = `${api.entity.entityGraph[`${this.data.entityType}Related${_.upperFirst(_targetEntityType)}`]}?resource=${this.data.entityName}&pageSize=10&pageNo=${this.data.relatedEntities[targetEntityType].pageNo + 1}` const response = await axios.get(url).catch(e => { console.error(e)