CN-1087 feat: 实体关系图完善
This commit is contained in:
@@ -24,11 +24,12 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
height: 134px;
|
height: 134px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-around;
|
justify-content: space-evenly;
|
||||||
|
|
||||||
.analysis-entry-item {
|
.analysis-entry-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
min-width: 70px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@
|
|||||||
|
|
||||||
.dividing-line {
|
.dividing-line {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
width: calc(100% - 60px);
|
width: 100%;
|
||||||
margin-top: 21px;
|
margin-top: 21px;
|
||||||
background-color: #EFF2F5;
|
background-color: #EFF2F5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ $font-size: 12px;
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.entity-graph-type {
|
.entity-graph-type {
|
||||||
font-family: NotoSansSChineseRegular;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #717171;
|
color: #717171;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -33,10 +32,13 @@ $font-size: 12px;
|
|||||||
|
|
||||||
.graph-basic-info-name {
|
.graph-basic-info-name {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
font-family: Helvetica-Bold;
|
width: 260px;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: #353636;
|
color: #353636;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.graph-basic-info-icon {
|
.graph-basic-info-icon {
|
||||||
@@ -197,7 +199,6 @@ $font-size: 12px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.graph-basic-info__block-title {
|
.graph-basic-info__block-title {
|
||||||
font-family: NotoSansHans-Medium;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #353636;
|
color: #353636;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -741,12 +741,12 @@ export function truncateText (text, limitWidth, fontSize = 12, ellipsis = '...')
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function scrollToTop (dom, toTop, duration, direction) {
|
export function scrollToTop (dom, toTop, duration, direction) {
|
||||||
|
if (toTop && duration && direction) {
|
||||||
const clientHeight = dom.clientHeight
|
const clientHeight = dom.clientHeight
|
||||||
const currentTop = dom.scrollTop
|
const currentTop = dom.scrollTop
|
||||||
const totalScrollDistance = Math.abs(currentTop - toTop)
|
const totalScrollDistance = Math.abs(currentTop - toTop)
|
||||||
let scrollY = currentTop
|
let scrollY = currentTop
|
||||||
let oldTimestamp = null
|
let oldTimestamp = null
|
||||||
|
|
||||||
function step (newTimestamp) {
|
function step (newTimestamp) {
|
||||||
if (oldTimestamp !== null) {
|
if (oldTimestamp !== null) {
|
||||||
if (direction === 'up') {
|
if (direction === 'up') {
|
||||||
@@ -769,6 +769,10 @@ export function scrollToTop (dom, toTop, duration, direction) {
|
|||||||
window.requestAnimationFrame(step)
|
window.requestAnimationFrame(step)
|
||||||
}
|
}
|
||||||
window.requestAnimationFrame(step)
|
window.requestAnimationFrame(step)
|
||||||
|
} else {
|
||||||
|
const wraps = document.querySelector('.entity-graph__detail')
|
||||||
|
wraps.scrollTop = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getChainRatio (current, prev) {
|
export function getChainRatio (current, prev) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-popover
|
<el-popover
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
:width="390"
|
:width="450"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
popper-class="analysis-popper"
|
popper-class="analysis-popper"
|
||||||
@show="analysisPopSwitch(true)"
|
@show="analysisPopSwitch(true)"
|
||||||
@@ -254,6 +254,17 @@ export default {
|
|||||||
label: i18n.global.t('dns.dnsInsights'),
|
label: i18n.global.t('dns.dnsInsights'),
|
||||||
path: '/panel/dnsServiceInsights'
|
path: '/panel/dnsServiceInsights'
|
||||||
}
|
}
|
||||||
|
const graphItem = {
|
||||||
|
icon: 'cn-icon cn-icon-graph',
|
||||||
|
label: i18n.global.t('entities.graph'),
|
||||||
|
url: resolvePath({
|
||||||
|
path: '/entityGraph',
|
||||||
|
query: {
|
||||||
|
entityType: props.entity.entityType,
|
||||||
|
entityName: props.entity.entityName
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
switch (props.entity.entityType) {
|
switch (props.entity.entityType) {
|
||||||
case 'ip': {
|
case 'ip': {
|
||||||
const queryCondition = `common_client_ip='${props.entity.entityName}' OR common_server_ip='${props.entity.entityName}'`
|
const queryCondition = `common_client_ip='${props.entity.entityName}' OR common_server_ip='${props.entity.entityName}'`
|
||||||
@@ -379,6 +390,7 @@ export default {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
analysisItems.value.push(graphItem)
|
||||||
// 底部各属性
|
// 底部各属性
|
||||||
const detailCards = ref([])
|
const detailCards = ref([])
|
||||||
switch (props.entity.entityType) {
|
switch (props.entity.entityType) {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ import { ref, shallowRef } from 'vue'
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { api } from '@/utils/api'
|
import { api } from '@/utils/api'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import G6 from '@antv/g6'
|
import G6, { Algorithm } from '@antv/g6'
|
||||||
import { entityDetailTags } from '@/utils/constants'
|
import { entityDetailTags } from '@/utils/constants'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -148,20 +148,31 @@ export default {
|
|||||||
* */
|
* */
|
||||||
let change = false // 图数据是否有变更,需要刷新
|
let change = false // 图数据是否有变更,需要刷新
|
||||||
// 清除temp node和temp edge
|
// 清除temp node和temp edge
|
||||||
|
const toDeleteTempNodeIds = []
|
||||||
|
const toDeleteTempEdgeIds = []
|
||||||
_this.graphData.nodes = _this.graphData.nodes.filter(n => {
|
_this.graphData.nodes = _this.graphData.nodes.filter(n => {
|
||||||
if (n.data && !n.data.isTemp) {
|
if (n.data && !n.data.isTemp) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
change = true
|
toDeleteTempNodeIds.push(n.id)
|
||||||
|
// change = true
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
_this.graphData.edges = _this.graphData.edges.filter(e => {
|
_this.graphData.edges = _this.graphData.edges.filter(e => {
|
||||||
if (e.data && !e.data.isTemp) {
|
if (e.data && !e.data.isTemp) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
change = true
|
toDeleteTempEdgeIds.push(e.id)
|
||||||
|
// change = true
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
toDeleteTempNodeIds.forEach(n => {
|
||||||
|
_this.graph.removeItem(n)
|
||||||
|
})
|
||||||
|
toDeleteTempEdgeIds.forEach(n => {
|
||||||
|
_this.graph.removeItem(n)
|
||||||
|
})
|
||||||
|
|
||||||
if (node.nodeType === 'root') { // 点击了root
|
if (node.nodeType === 'root') { // 点击了root
|
||||||
_this.entity = {
|
_this.entity = {
|
||||||
entityName: node.id,
|
entityName: node.id,
|
||||||
@@ -170,10 +181,11 @@ export default {
|
|||||||
tags: node.data.tags,
|
tags: node.data.tags,
|
||||||
type: node.data.type,
|
type: node.data.type,
|
||||||
detailData: node.data.data,
|
detailData: node.data.data,
|
||||||
|
relatedEntityCount: node.data.relatedEntityCount,
|
||||||
isSubdomain: node.data.isSubdomain || false,
|
isSubdomain: node.data.isSubdomain || false,
|
||||||
loading: true
|
loading: false
|
||||||
}
|
}
|
||||||
const relatedEntityCount = await _this.queryRelatedEntityCount(_this.entity.entityType, node.id)
|
/* const relatedEntityCount = await _this.queryRelatedEntityCount(_this.entity.entityType, node.id)
|
||||||
_this.entity = {
|
_this.entity = {
|
||||||
..._this.entity,
|
..._this.entity,
|
||||||
relatedEntityCount: _this.handleRelatedEntityCount(node, relatedEntityCount),
|
relatedEntityCount: _this.handleRelatedEntityCount(node, relatedEntityCount),
|
||||||
@@ -206,9 +218,21 @@ export default {
|
|||||||
}
|
}
|
||||||
_this.graph.refreshItem(n.id)
|
_this.graph.refreshItem(n.id)
|
||||||
})
|
})
|
||||||
}
|
} */
|
||||||
} else if (node.nodeType === 'primary') { // 点击了primary
|
} else if (node.nodeType === 'primary') { // 点击了primary
|
||||||
_this.entity.loading = true
|
_this.entity = {
|
||||||
|
entityType: _this.entity.entityType,
|
||||||
|
entityName: _this.entity.entityName,
|
||||||
|
listData: node.data.loaded,
|
||||||
|
count: node.data.count,
|
||||||
|
isSubdomain: node.data.isSubdomain || false,
|
||||||
|
sourceType: node.data.sourceType,
|
||||||
|
sourceName: node.data.sourceName,
|
||||||
|
type: node.data.type,
|
||||||
|
nodeId: node.id,
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
/* _this.entity.loading = true
|
||||||
const queryRelatedEntityCount = await _this.queryRelatedEntityCount(node.data.sourceType, node.data.sourceName)
|
const queryRelatedEntityCount = await _this.queryRelatedEntityCount(node.data.sourceType, node.data.sourceName)
|
||||||
const sourceNode = _this.graphData.nodes.find(n => n.id === node.data.sourceName)
|
const sourceNode = _this.graphData.nodes.find(n => n.id === node.data.sourceName)
|
||||||
_this.handleRelatedEntityCount(sourceNode, queryRelatedEntityCount)
|
_this.handleRelatedEntityCount(sourceNode, queryRelatedEntityCount)
|
||||||
@@ -246,11 +270,27 @@ export default {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_this.graph.refreshItem(node.id)
|
_this.graph.refreshItem(node.id) */
|
||||||
// TODO 高亮
|
// TODO 高亮
|
||||||
} else if (node.nodeType === 'entity') {
|
} else if (node.nodeType === 'entity') {
|
||||||
|
// 点击节点时,看是否已查询过related count,已查过则不再查
|
||||||
|
if (!node.data.childNodes) {
|
||||||
_this.entity.loading = true
|
_this.entity.loading = true
|
||||||
const relatedEntityCount = await _this.queryRelatedEntityCount(node.data.type, node.id)
|
const relatedEntityCount = await _this.queryRelatedEntityCount(node.data.type, node.id)
|
||||||
|
node.data.relatedEntityCount = _this.handleRelatedEntityCount(node, relatedEntityCount)
|
||||||
|
}
|
||||||
|
_this.entity = {
|
||||||
|
entityType: _this.entity.entityType,
|
||||||
|
entityName: _this.entity.entityName,
|
||||||
|
relatedEntityCount: node.data.relatedEntityCount,
|
||||||
|
detailData: node.data.data,
|
||||||
|
type: node.data.type,
|
||||||
|
isSubdomain: false,
|
||||||
|
name: node.id,
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
/* _this.entity.loading = true
|
||||||
|
const relatedEntityCount = await _this.queryRelatedEntityCount(node.data.type, node.id)
|
||||||
// 赋值总数和已拓展数。entity node可从其childNodes取到已拓展数
|
// 赋值总数和已拓展数。entity node可从其childNodes取到已拓展数
|
||||||
_this.entity = {
|
_this.entity = {
|
||||||
entityType: _this.entity.entityType,
|
entityType: _this.entity.entityType,
|
||||||
@@ -289,8 +329,8 @@ export default {
|
|||||||
}
|
}
|
||||||
_this.graph.refreshItem(n.id)
|
_this.graph.refreshItem(n.id)
|
||||||
})
|
})
|
||||||
}
|
} */
|
||||||
const { tempNodes, tempEdges } = _this.generateTempNodesAndEdges(node, relatedEntityCount)
|
const { tempNodes, tempEdges } = _this.generateTempNodesAndEdges(node, node.data.relatedEntityCount)
|
||||||
if (tempNodes.length > 0) {
|
if (tempNodes.length > 0) {
|
||||||
change = true
|
change = true
|
||||||
_this.addNodes(tempNodes)
|
_this.addNodes(tempNodes)
|
||||||
@@ -329,10 +369,13 @@ export default {
|
|||||||
const entityNodes = []
|
const entityNodes = []
|
||||||
const edges = []
|
const edges = []
|
||||||
if (level < 9) {
|
if (level < 9) {
|
||||||
|
_this.entity.loading = true
|
||||||
const queryEntityNodes = await _this.generateHalfLevelNodes(primaryNode)
|
const queryEntityNodes = await _this.generateHalfLevelNodes(primaryNode)
|
||||||
|
_this.entity.loading = false
|
||||||
_this.updateRelatedCount(sourceNode, [primaryNode])
|
_this.updateRelatedCount(sourceNode, [primaryNode])
|
||||||
entityNodes.push(...queryEntityNodes)
|
entityNodes.push(...queryEntityNodes)
|
||||||
edges.push(..._this.generateEdges(primaryNode))
|
edges.push(..._this.generateEdges(primaryNode))
|
||||||
|
|
||||||
// TODO 高亮
|
// TODO 高亮
|
||||||
} else {
|
} else {
|
||||||
// TODO 提示无法拓展
|
// TODO 提示无法拓展
|
||||||
@@ -499,7 +542,22 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
generateEntityNode (nodeData, data) {
|
async generateEntityNode (nodeData, data) {
|
||||||
|
const tags = await this.queryTags(nodeData.type, data.vertex)
|
||||||
|
let _tags = []
|
||||||
|
Object.keys(tags).forEach(k => {
|
||||||
|
if (k !== 'userDefinedTags' && tags[k]) {
|
||||||
|
Object.keys(tags[k]).forEach(k2 => {
|
||||||
|
const find = entityDetailTags[this.entity.entityType].find(t => t.name === k2)
|
||||||
|
if (find) {
|
||||||
|
_tags.push({ key: k2, value: this.tagValueHandler(k, k2, tags[k][k2]), type: find.type })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (_.isArray(tags.userDefinedTags)) {
|
||||||
|
_tags = _.concat(_tags, tags.userDefinedTags.map(tag => ({ value: tag.tagValue, type: 'normal' })))
|
||||||
|
}
|
||||||
let width = 0
|
let width = 0
|
||||||
let height = 0
|
let height = 0
|
||||||
switch (nodeData.type) {
|
switch (nodeData.type) {
|
||||||
@@ -539,7 +597,8 @@ export default {
|
|||||||
type: nodeData.type,
|
type: nodeData.type,
|
||||||
isSubdomain: nodeData.isSubdomain || false,
|
isSubdomain: nodeData.isSubdomain || false,
|
||||||
name: data.vertex,
|
name: data.vertex,
|
||||||
data
|
data,
|
||||||
|
tags: _tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -623,6 +682,7 @@ export default {
|
|||||||
const nodes = []
|
const nodes = []
|
||||||
if (relatedEntityCount) {
|
if (relatedEntityCount) {
|
||||||
this.entity.relatedEntityCount = this.handleRelatedEntityCount(rootNode, relatedEntityCount)
|
this.entity.relatedEntityCount = this.handleRelatedEntityCount(rootNode, relatedEntityCount)
|
||||||
|
rootNode.data.relatedEntityCount = this.entity.relatedEntityCount
|
||||||
const ipNode = this.generatePrimaryNode({
|
const ipNode = this.generatePrimaryNode({
|
||||||
id: 'ip-1',
|
id: 'ip-1',
|
||||||
parentId: rootNode.id,
|
parentId: rootNode.id,
|
||||||
@@ -738,9 +798,9 @@ export default {
|
|||||||
const data = await this.queryRelatedEntity(node.data.sourceType, node.data.sourceName, node.data.type, pageNo)
|
const data = await this.queryRelatedEntity(node.data.sourceType, node.data.sourceName, node.data.type, pageNo)
|
||||||
if (data) {
|
if (data) {
|
||||||
// 生成节点
|
// 生成节点
|
||||||
data.list.forEach(d => {
|
for (const d of data.list) {
|
||||||
newNodes2.push(this.generateEntityNode(node.data, d))
|
newNodes2.push(await this.generateEntityNode(node.data, d))
|
||||||
})
|
}
|
||||||
// 更新源节点的已拓展数和列表
|
// 更新源节点的已拓展数和列表
|
||||||
this.handleLoaded(node, data, newNodes2)
|
this.handleLoaded(node, data, newNodes2)
|
||||||
newNodes.push(...newNodes2)
|
newNodes.push(...newNodes2)
|
||||||
@@ -807,6 +867,7 @@ export default {
|
|||||||
}
|
}
|
||||||
const response = await axios.get(url).catch(e => {
|
const response = await axios.get(url).catch(e => {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
this.entity.loading = false
|
||||||
this.showError = true
|
this.showError = true
|
||||||
this.errorMsg = this.errorMsgHandler(e)
|
this.errorMsg = this.errorMsgHandler(e)
|
||||||
})
|
})
|
||||||
@@ -815,6 +876,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
console.error(response)
|
console.error(response)
|
||||||
this.showError = true
|
this.showError = true
|
||||||
|
this.entity.loading = false
|
||||||
this.errorMsg = this.errorMsgHandler(response)
|
this.errorMsg = this.errorMsgHandler(response)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -887,16 +949,16 @@ export default {
|
|||||||
const tempEdges = []
|
const tempEdges = []
|
||||||
switch (node.data.type) {
|
switch (node.data.type) {
|
||||||
case 'ip': {
|
case 'ip': {
|
||||||
if (relatedEntityCount.domainCount && !this.hasChildNodeByType(node, 'domain')) {
|
if (relatedEntityCount.domain.total && !this.hasChildNodeByType(node, 'domain')) {
|
||||||
const tempNode = this.generateTempNode('domain', node)
|
const tempNode = this.generateTempNode('domain', node)
|
||||||
tempNode.data.count = relatedEntityCount.domainCount
|
tempNode.data.count = relatedEntityCount.domain.total
|
||||||
const tempEdge = this.generateTempEdge(node, tempNode)
|
const tempEdge = this.generateTempEdge(node, tempNode)
|
||||||
tempNodes.push(tempNode)
|
tempNodes.push(tempNode)
|
||||||
tempEdges.push(tempEdge)
|
tempEdges.push(tempEdge)
|
||||||
}
|
}
|
||||||
if (relatedEntityCount.appCount && !this.hasChildNodeByType(node, 'app')) {
|
if (relatedEntityCount.app.total && !this.hasChildNodeByType(node, 'app')) {
|
||||||
const tempNode = this.generateTempNode('app', node)
|
const tempNode = this.generateTempNode('app', node)
|
||||||
tempNode.data.count = relatedEntityCount.appCount
|
tempNode.data.count = relatedEntityCount.app.total
|
||||||
const tempEdge = this.generateTempEdge(node, tempNode)
|
const tempEdge = this.generateTempEdge(node, tempNode)
|
||||||
tempNodes.push(tempNode)
|
tempNodes.push(tempNode)
|
||||||
tempEdges.push(tempEdge)
|
tempEdges.push(tempEdge)
|
||||||
@@ -904,23 +966,23 @@ export default {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'domain': {
|
case 'domain': {
|
||||||
if (relatedEntityCount.ipCount && !this.hasChildNodeByType(node, 'ip')) {
|
if (relatedEntityCount.ip.total && !this.hasChildNodeByType(node, 'ip')) {
|
||||||
const tempNode = this.generateTempNode('ip', node)
|
const tempNode = this.generateTempNode('ip', node)
|
||||||
tempNode.data.count = relatedEntityCount.ipCount
|
tempNode.data.count = relatedEntityCount.ip.total
|
||||||
const tempEdge = this.generateTempEdge(node, tempNode)
|
const tempEdge = this.generateTempEdge(node, tempNode)
|
||||||
tempNodes.push(tempNode)
|
tempNodes.push(tempNode)
|
||||||
tempEdges.push(tempEdge)
|
tempEdges.push(tempEdge)
|
||||||
}
|
}
|
||||||
if (relatedEntityCount.subDomainCount && !this.hasChildNodeByType(node, 'domain')) {
|
if (relatedEntityCount.subDomain.total && !this.hasChildNodeByType(node, 'domain')) {
|
||||||
const tempNode = this.generateTempNode('domain', node, true)
|
const tempNode = this.generateTempNode('domain', node, true)
|
||||||
tempNode.data.count = relatedEntityCount.subDomainCount
|
tempNode.data.count = relatedEntityCount.subDomain.total
|
||||||
const tempEdge = this.generateTempEdge(node, tempNode)
|
const tempEdge = this.generateTempEdge(node, tempNode)
|
||||||
tempNodes.push(tempNode)
|
tempNodes.push(tempNode)
|
||||||
tempEdges.push(tempEdge)
|
tempEdges.push(tempEdge)
|
||||||
}
|
}
|
||||||
if (relatedEntityCount.appCount && !this.hasChildNodeByType(node, 'app')) {
|
if (relatedEntityCount.app.total && !this.hasChildNodeByType(node, 'app')) {
|
||||||
const tempNode = this.generateTempNode('app', node)
|
const tempNode = this.generateTempNode('app', node)
|
||||||
tempNode.data.count = relatedEntityCount.appCount
|
tempNode.data.count = relatedEntityCount.app.total
|
||||||
const tempEdge = this.generateTempEdge(node, tempNode)
|
const tempEdge = this.generateTempEdge(node, tempNode)
|
||||||
tempNodes.push(tempNode)
|
tempNodes.push(tempNode)
|
||||||
tempEdges.push(tempEdge)
|
tempEdges.push(tempEdge)
|
||||||
@@ -928,16 +990,16 @@ export default {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'app': {
|
case 'app': {
|
||||||
if (relatedEntityCount.ipCount && !this.hasChildNodeByType(node, 'ip')) {
|
if (relatedEntityCount.ip.total && !this.hasChildNodeByType(node, 'ip')) {
|
||||||
const tempNode = this.generateTempNode('ip', node)
|
const tempNode = this.generateTempNode('ip', node)
|
||||||
tempNode.data.count = relatedEntityCount.ipCount
|
tempNode.data.count = relatedEntityCount.ip.total
|
||||||
const tempEdge = this.generateTempEdge(node, tempNode)
|
const tempEdge = this.generateTempEdge(node, tempNode)
|
||||||
tempNodes.push(tempNode)
|
tempNodes.push(tempNode)
|
||||||
tempEdges.push(tempEdge)
|
tempEdges.push(tempEdge)
|
||||||
}
|
}
|
||||||
if (relatedEntityCount.domainCount && !this.hasChildNodeByType(node, 'domain')) {
|
if (relatedEntityCount.domain.total && !this.hasChildNodeByType(node, 'domain')) {
|
||||||
const tempNode = this.generateTempNode('domain', node)
|
const tempNode = this.generateTempNode('domain', node)
|
||||||
tempNode.data.count = relatedEntityCount.domainCount
|
tempNode.data.count = relatedEntityCount.domain.total
|
||||||
const tempEdge = this.generateTempEdge(node, tempNode)
|
const tempEdge = this.generateTempEdge(node, tempNode)
|
||||||
tempNodes.push(tempNode)
|
tempNodes.push(tempNode)
|
||||||
tempEdges.push(tempEdge)
|
tempEdges.push(tempEdge)
|
||||||
@@ -1028,10 +1090,9 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
getNodeLevel (id) {
|
getNodeLevel (id) {
|
||||||
const matrix = this.graph.getShortestPathMatrix(false)
|
const { findShortestPath } = Algorithm
|
||||||
const rootPathMatrix = matrix[0]
|
const info = findShortestPath(this.graphData, this.entity.entityName, id)
|
||||||
const nodeIndex = this.graphData.nodes.findIndex(n => n.id === id)
|
return info.length
|
||||||
return rootPathMatrix[nodeIndex]
|
|
||||||
},
|
},
|
||||||
onCloseBlock () {
|
onCloseBlock () {
|
||||||
// todo 关闭右侧graph面板
|
// todo 关闭右侧graph面板
|
||||||
@@ -1085,16 +1146,42 @@ export default {
|
|||||||
<span>${_this.$t('entity.graph.expandedEntityQuantity')}: ${node.data.loaded ? node.data.loaded.length : 0}</span>
|
<span>${_this.$t('entity.graph.expandedEntityQuantity')}: ${node.data.loaded ? node.data.loaded.length : 0}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
} else {
|
} else if (node.nodeType === 'entity' || node.nodeType === 'root') {
|
||||||
// TODO 逻辑
|
if (node.data && node.data.tags && node.data.tags.length > 0) {
|
||||||
return `<div class="entity-node-tooltip">
|
return `<div class="entity-node-tooltip">
|
||||||
<div class="tooltip__header"><span class="tooltip__title">${node.id}</span></div>
|
<div class="tooltip__header">
|
||||||
|
<span class="tooltip__title">${node.id}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip__content">
|
||||||
|
<div class="content-header">
|
||||||
|
<div class="header-icon"></div>
|
||||||
|
<span>${_this.$t('entity.graph.tags')}</span>
|
||||||
|
</div>
|
||||||
|
<div class="content-tag-list">${generateTagHTML(node.data.tags)}</div>
|
||||||
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
|
} else {
|
||||||
|
return `<div style="padding: 0 6px; font-size: 15px; line-height: 15px; color: #111;">${node.id}</div>`
|
||||||
|
}
|
||||||
|
} else if (node.nodeType === 'temp') {
|
||||||
|
return node.label
|
||||||
|
}
|
||||||
|
function generateTagHTML (tags) {
|
||||||
|
let html = ''
|
||||||
|
if (tags) {
|
||||||
|
tags.forEach(t => {
|
||||||
|
html += `<div class="entity-tag entity-tag--level-two-${t.type}">
|
||||||
|
<span>${t.value}</span>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return html
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async expandList (sourceNodeId, nodeId) {
|
async expandList (sourceNodeId, nodeId) {
|
||||||
|
this.entity.loading = true
|
||||||
const sourceNode = this.graphData.nodes.find(n => n.id === sourceNodeId)
|
const sourceNode = this.graphData.nodes.find(n => n.id === sourceNodeId)
|
||||||
const node = this.graphData.nodes.find(n => n.id === nodeId)
|
const node = this.graphData.nodes.find(n => n.id === nodeId)
|
||||||
if (node && sourceNode) {
|
if (node && sourceNode) {
|
||||||
@@ -1105,8 +1192,11 @@ export default {
|
|||||||
this.entity = {
|
this.entity = {
|
||||||
...this.entity,
|
...this.entity,
|
||||||
relatedEntityCount: this.handleRelatedEntityCount(sourceNode, relatedEntityCount),
|
relatedEntityCount: this.handleRelatedEntityCount(sourceNode, relatedEntityCount),
|
||||||
listData: node.data.loaded
|
listData: node.data.loaded,
|
||||||
|
loading: false
|
||||||
}
|
}
|
||||||
|
sourceNode.data.relatedEntityCount = this.entity.relatedEntityCount
|
||||||
|
|
||||||
this.addNodes(queryEntityNodes)
|
this.addNodes(queryEntityNodes)
|
||||||
this.addEdges(this.generateEdges(node, queryEntityNodes))
|
this.addEdges(this.generateEdges(node, queryEntityNodes))
|
||||||
this.graph.changeData(this.graphData)
|
this.graph.changeData(this.graphData)
|
||||||
@@ -1117,16 +1207,13 @@ export default {
|
|||||||
},
|
},
|
||||||
async expandDetailList (expandType, currentName) {
|
async expandDetailList (expandType, currentName) {
|
||||||
const currentNode = this.graphData.nodes.find(n => n.id === currentName)
|
const currentNode = this.graphData.nodes.find(n => n.id === currentName)
|
||||||
console.info(expandType, currentName, currentNode)
|
|
||||||
// 如果存在primary node,直接拓展
|
// 如果存在primary node,直接拓展
|
||||||
const _primaryNode = currentNode.data.childNodes ? currentNode.data.childNodes.find(n => n.data.type === expandType) : null
|
const _primaryNode = currentNode.data.childNodes ? currentNode.data.childNodes.find(n => n.data.type === expandType) : null
|
||||||
console.info('primary', _primaryNode)
|
|
||||||
if (_primaryNode) {
|
if (_primaryNode) {
|
||||||
await this.expandList(currentName, _primaryNode.id)
|
await this.expandList(currentName, _primaryNode.id)
|
||||||
} else {
|
} else {
|
||||||
// 如果是temp node,删掉,新增primary node,再拓展
|
// 如果是temp node,删掉,新增primary node,再拓展
|
||||||
const tempNode = this.graphData.nodes.find(n => n.nodeType === 'temp' && n.data.sourceName === currentName && n.data.type === expandType)
|
const tempNode = this.graphData.nodes.find(n => n.nodeType === 'temp' && n.data.sourceName === currentName && n.data.type === expandType)
|
||||||
console.info('temp', tempNode)
|
|
||||||
if (tempNode) {
|
if (tempNode) {
|
||||||
// 先清除此temp node和temp edge
|
// 先清除此temp node和temp edge
|
||||||
this.graphData.nodes = this.graphData.nodes.filter(n => {
|
this.graphData.nodes = this.graphData.nodes.filter(n => {
|
||||||
@@ -1160,7 +1247,9 @@ export default {
|
|||||||
const edges = []
|
const edges = []
|
||||||
const level = this.getNodeLevel(primaryNode.id)
|
const level = this.getNodeLevel(primaryNode.id)
|
||||||
if (level < 9) {
|
if (level < 9) {
|
||||||
|
this.entity.loading = true
|
||||||
const queryEntityNodes = await this.generateHalfLevelNodes(primaryNode)
|
const queryEntityNodes = await this.generateHalfLevelNodes(primaryNode)
|
||||||
|
this.entity.loading = false
|
||||||
this.updateRelatedCount(currentNode, [primaryNode])
|
this.updateRelatedCount(currentNode, [primaryNode])
|
||||||
entityNodes.push(...queryEntityNodes)
|
entityNodes.push(...queryEntityNodes)
|
||||||
edges.push(...this.generateEdges(primaryNode))
|
edges.push(...this.generateEdges(primaryNode))
|
||||||
@@ -1170,7 +1259,6 @@ export default {
|
|||||||
}
|
}
|
||||||
this.addNodes(entityNodes)
|
this.addNodes(entityNodes)
|
||||||
this.addEdges(edges)
|
this.addEdges(edges)
|
||||||
console.info(this.graphData)
|
|
||||||
this.graph.changeData(this.graphData)
|
this.graph.changeData(this.graphData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1360,11 +1448,13 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.entity-node-tooltip {
|
.entity-node-tooltip {
|
||||||
|
width: 300px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
||||||
.tooltip__header {
|
.tooltip__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
.tooltip__title {
|
.tooltip__title {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
@@ -1372,6 +1462,60 @@ export default {
|
|||||||
color: #111;
|
color: #111;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.content-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.header-icon {
|
||||||
|
width: 3px !important;
|
||||||
|
height: 12px !important;
|
||||||
|
background: #38ACD2;
|
||||||
|
border-radius: 1px;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content-tag-list {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.entity-tag {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 0 8px;
|
||||||
|
height: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
$normal-color: #778391;
|
||||||
|
$normal-light-color: #F7F8F9;
|
||||||
|
$negative-color: #E26154;
|
||||||
|
$negative-light-color: #FEF6F5;
|
||||||
|
$positive-color: #749F4D;
|
||||||
|
$positive-light-color: #F7FAF5;
|
||||||
|
&.entity-tag--level-two-normal {
|
||||||
|
border-color: $normal-color;
|
||||||
|
color: $normal-color;
|
||||||
|
background-color: $normal-light-color;
|
||||||
|
}
|
||||||
|
&.entity-tag--level-two-negative {
|
||||||
|
border-color: $negative-color;
|
||||||
|
color: $negative-color;
|
||||||
|
background-color: $negative-light-color;
|
||||||
|
}
|
||||||
|
&.entity-tag--level-two-positive {
|
||||||
|
border-color: $positive-color;
|
||||||
|
color: $positive-color;
|
||||||
|
background-color: $positive-light-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { riskLevelMapping } from '@/utils/constants'
|
import { riskLevelMapping } from '@/utils/constants'
|
||||||
import Loading from '@/components/common/Loading'
|
import Loading from '@/components/common/Loading'
|
||||||
|
import { scrollToTop } from '@/utils/tools'
|
||||||
export default {
|
export default {
|
||||||
name: 'AppList',
|
name: 'AppList',
|
||||||
props: {
|
props: {
|
||||||
@@ -110,6 +111,11 @@ export default {
|
|||||||
this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
|
this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
scrollToTop()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { riskLevelMapping } from '@/utils/constants'
|
import { riskLevelMapping } from '@/utils/constants'
|
||||||
import Loading from '@/components/common/Loading'
|
import Loading from '@/components/common/Loading'
|
||||||
|
import { scrollToTop } from '@/utils/tools'
|
||||||
export default {
|
export default {
|
||||||
name: 'DomainList',
|
name: 'DomainList',
|
||||||
props: {
|
props: {
|
||||||
@@ -106,6 +107,11 @@ export default {
|
|||||||
this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
|
this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
scrollToTop()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="entity-graph-type">{{ entityType[entity.type || entity.entityType] }}</div>
|
<div class="entity-graph-type">{{ entityType[entity.type || entity.entityType] }}</div>
|
||||||
<div class="graph-basic-info">
|
<div class="graph-basic-info">
|
||||||
<div class="graph-basic-info-name__block">
|
<div class="graph-basic-info-name__block">
|
||||||
<div class="graph-basic-info-name" id="entityName">{{ $_.get(entity, 'detailData.vertex', '') }}</div>
|
<div class="graph-basic-info-name" :title="$_.get(entity, 'detailData.vertex', '')" id="entityName">{{ $_.get(entity, 'detailData.vertex', '') }}</div>
|
||||||
<div class="graph-basic-info-icon" @click="copyEntityName">
|
<div class="graph-basic-info-icon" @click="copyEntityName">
|
||||||
<i class="cn-icon cn-icon-copy"></i>
|
<i class="cn-icon cn-icon-copy"></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--标签-->
|
<!--标签-->
|
||||||
<div class="digital-certificate graph-basic-info__block">
|
<div v-if="entity.tags" class="digital-certificate graph-basic-info__block">
|
||||||
<div class="digital-certificate-header padding-b-10">
|
<div class="digital-certificate-header padding-b-10">
|
||||||
<div class="digital-certificate-header__icon graph-header__icon"></div>
|
<div class="digital-certificate-header__icon graph-header__icon"></div>
|
||||||
<div class="graph-basic-info__block-title">
|
<div class="graph-basic-info__block-title">
|
||||||
@@ -238,6 +238,93 @@ export default {
|
|||||||
entity: {
|
entity: {
|
||||||
deep: true,
|
deep: true,
|
||||||
handler (n) {
|
handler (n) {
|
||||||
|
this.handleDetailData(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.handleDetailData(this.entity)
|
||||||
|
},
|
||||||
|
setup (props) {
|
||||||
|
const detailCards = ref([])
|
||||||
|
const relationList = ref([])
|
||||||
|
const tagList = ref([])
|
||||||
|
|
||||||
|
return {
|
||||||
|
detailCards,
|
||||||
|
relationList,
|
||||||
|
tagList
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/** 复制实体名称 */
|
||||||
|
copyEntityName () {
|
||||||
|
selectElementText(document.getElementById('entityName'))
|
||||||
|
if (copySelectionText()) {
|
||||||
|
this.$message.success(this.$t('tip.copySuccess'))
|
||||||
|
} else {
|
||||||
|
this.$message.error('Unknown error')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 修改关系拓展图标颜色,全部拓展浅灰色,否则深灰色 */
|
||||||
|
iconColor (length, total) {
|
||||||
|
if (length < total) {
|
||||||
|
if (length === 50) {
|
||||||
|
return 'rgba(57, 57, 57, 0.5)'
|
||||||
|
} else {
|
||||||
|
return 'rgba(57, 57, 57, 1)'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 'rgba(57, 57, 57, 0.5)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 关闭右侧详情栏
|
||||||
|
closeBlock () {
|
||||||
|
this.$emit('closeBlock')
|
||||||
|
},
|
||||||
|
/** 构造地址,国-省市-市 */
|
||||||
|
handleLocation (data) {
|
||||||
|
const location = []
|
||||||
|
if (data.country) {
|
||||||
|
location.push(data.country)
|
||||||
|
}
|
||||||
|
if (data.province) {
|
||||||
|
location.push(data.province)
|
||||||
|
}
|
||||||
|
if (data.city) {
|
||||||
|
location.push(data.city)
|
||||||
|
}
|
||||||
|
return location.join(' - ')
|
||||||
|
},
|
||||||
|
/** 关系拓展 */
|
||||||
|
expandRelation (name) {
|
||||||
|
this.$emit('expandDetailList', name === 'subDomain' ? 'domain' : name, this.entity.name)
|
||||||
|
},
|
||||||
|
httpError (e) {
|
||||||
|
this.isNoData = false
|
||||||
|
this.showError = true
|
||||||
|
this.errorMsg = this.errorMsgHandler(e)
|
||||||
|
},
|
||||||
|
location (detailData) {
|
||||||
|
let location = ''
|
||||||
|
if (detailData) {
|
||||||
|
const data = detailData.detail
|
||||||
|
if (data) {
|
||||||
|
if (data.city) {
|
||||||
|
location = data.city
|
||||||
|
}
|
||||||
|
if (data.province) {
|
||||||
|
location = location ? `${data.province}, ${location}` : data.province
|
||||||
|
}
|
||||||
|
if (data.country) {
|
||||||
|
location = location ? `${data.country}, ${location}` : data.country
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return location || '-'
|
||||||
|
},
|
||||||
|
handleDetailData (entity) {
|
||||||
|
const n = entity
|
||||||
const type = n.type || n.entityType
|
const type = n.type || n.entityType
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'ip': {
|
case 'ip': {
|
||||||
@@ -402,85 +489,5 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
setup (props) {
|
|
||||||
const detailCards = ref([])
|
|
||||||
const relationList = ref([])
|
|
||||||
const tagList = ref([])
|
|
||||||
|
|
||||||
return {
|
|
||||||
detailCards,
|
|
||||||
relationList,
|
|
||||||
tagList
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/** 复制实体名称 */
|
|
||||||
copyEntityName () {
|
|
||||||
selectElementText(document.getElementById('entityName'))
|
|
||||||
if (copySelectionText()) {
|
|
||||||
this.$message.success(this.$t('tip.copySuccess'))
|
|
||||||
} else {
|
|
||||||
this.$message.error('Unknown error')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/** 修改关系拓展图标颜色,全部拓展浅灰色,否则深灰色 */
|
|
||||||
iconColor (length, total) {
|
|
||||||
if (length < total) {
|
|
||||||
if (length === 50) {
|
|
||||||
return 'rgba(57, 57, 57, 0.5)'
|
|
||||||
} else {
|
|
||||||
return 'rgba(57, 57, 57, 1)'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return 'rgba(57, 57, 57, 0.5)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 关闭右侧详情栏
|
|
||||||
closeBlock () {
|
|
||||||
this.$emit('closeBlock')
|
|
||||||
},
|
|
||||||
/** 构造地址,国-省市-市 */
|
|
||||||
handleLocation (data) {
|
|
||||||
const location = []
|
|
||||||
if (data.country) {
|
|
||||||
location.push(data.country)
|
|
||||||
}
|
|
||||||
if (data.province) {
|
|
||||||
location.push(data.province)
|
|
||||||
}
|
|
||||||
if (data.city) {
|
|
||||||
location.push(data.city)
|
|
||||||
}
|
|
||||||
return location.join(' - ')
|
|
||||||
},
|
|
||||||
/** 关系拓展 */
|
|
||||||
expandRelation (name) {
|
|
||||||
this.$emit('expandDetailList', name === 'subDomain' ? 'domain' : name, this.entity.name)
|
|
||||||
},
|
|
||||||
httpError (e) {
|
|
||||||
this.isNoData = false
|
|
||||||
this.showError = true
|
|
||||||
this.errorMsg = this.errorMsgHandler(e)
|
|
||||||
},
|
|
||||||
location (detailData) {
|
|
||||||
let location = ''
|
|
||||||
if (detailData) {
|
|
||||||
const data = detailData.detail
|
|
||||||
if (data) {
|
|
||||||
if (data.city) {
|
|
||||||
location = data.city
|
|
||||||
}
|
|
||||||
if (data.province) {
|
|
||||||
location = location ? `${data.province}, ${location}` : data.province
|
|
||||||
}
|
|
||||||
if (data.country) {
|
|
||||||
location = location ? `${data.country}, ${location}` : data.country
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return location || '-'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Loading from '@/components/common/Loading'
|
import Loading from '@/components/common/Loading'
|
||||||
|
import { scrollToTop } from '@/utils/tools'
|
||||||
export default {
|
export default {
|
||||||
name: 'IpList',
|
name: 'IpList',
|
||||||
props: {
|
props: {
|
||||||
@@ -90,6 +91,11 @@ export default {
|
|||||||
// 继续拓展ip列表,传递信息,调用接口
|
// 继续拓展ip列表,传递信息,调用接口
|
||||||
this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
|
this.$emit('expandList', this.entity.sourceName, this.entity.nodeId)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
scrollToTop()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user