|
|
@@ -18,7 +18,7 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
<MEmpty v-if="empty"></MEmpty>
|
|
|
- <relation-graph
|
|
|
+ <RelationGraph
|
|
|
v-show="!empty"
|
|
|
ref="graphRef"
|
|
|
:options="graphOptions"
|
|
|
@@ -26,9 +26,9 @@
|
|
|
:on-line-click="onLineClick"
|
|
|
>
|
|
|
<template #node="{node}">
|
|
|
- <v-tooltip left :color="node.color || 'primary'">
|
|
|
- <template v-slot:activator="{ on, attrs }">
|
|
|
- <div @contextmenu.prevent="handleContextmenu($event, node)" v-bind="attrs" v-on="on">
|
|
|
+ <!-- <v-tooltip left :color="node.color || 'primary'"> -->
|
|
|
+ <!-- <template v-slot:activator="{ on, attrs }"> -->
|
|
|
+ <div @contextmenu.prevent="handleContextmenu($event, node)" style="cursor: pointer;">
|
|
|
<div
|
|
|
:style="{ width: node.width + 'px', height: node.height + 'px' }"
|
|
|
:class="{ 'node-active': menu.activeId === +node.id }"
|
|
|
@@ -43,9 +43,9 @@
|
|
|
{{ node.text || '' }}
|
|
|
</div>
|
|
|
</div>
|
|
|
- </template>
|
|
|
+ <!-- </template> -->
|
|
|
<!-- 浮窗展示节点详情 -->
|
|
|
- <div class="d-flex">
|
|
|
+ <!-- <div class="d-flex">
|
|
|
<ul style="color: #fff;text-align:right;">
|
|
|
<template v-for="key in Object.keys(detailKeys)">
|
|
|
<li v-if="node.data[key]" :key="key">{{ detailKeys[key] }} :</li>
|
|
|
@@ -55,13 +55,32 @@
|
|
|
<template v-for="key in Object.keys(detailKeys)">
|
|
|
<li v-if="node.data[key]" :key="key">
|
|
|
<span v-if="key === 'labels'">{{ node.data[key].join(',') }}</span>
|
|
|
- <span v-else-if="key === 'matched_fields'">{{ node.data[key].map(item => item.field_name).join(',') }}</span>
|
|
|
+ <span v-else-if="key === 'matched_data'">
|
|
|
+ <v-simple-table dense v-if="getMatchDataArray(node.data[key]) && getMatchDataArray(node.data[key]).length">
|
|
|
+ <template v-slot:default>
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th v-for="header in getMatchDataHeaders(node.data[key])" :key="header.value" class="text-left">
|
|
|
+ {{ header.text }}
|
|
|
+ </th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <tr v-for="(item, index) in getMatchDataArray(node.data[key])" :key="index">
|
|
|
+ <td v-for="header in getMatchDataHeaders(node.data[key])" :key="header.value">
|
|
|
+ {{ item[header.value] }}
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </template>
|
|
|
+ </v-simple-table>
|
|
|
+ </span>
|
|
|
<span v-else>{{ node.data[key] }}</span>
|
|
|
</li>
|
|
|
</template>
|
|
|
</ul>
|
|
|
- </div>
|
|
|
- </v-tooltip>
|
|
|
+ </div> -->
|
|
|
+ <!-- </v-tooltip> -->
|
|
|
</template>
|
|
|
<template #graph-plug>
|
|
|
<v-menu v-model="menu.show" attach :position-x="menu.x" :position-y="menu.y" absolute offset-y min-width="200">
|
|
|
@@ -72,7 +91,84 @@
|
|
|
</v-list>
|
|
|
</v-menu>
|
|
|
</template>
|
|
|
- </relation-graph>
|
|
|
+ </RelationGraph>
|
|
|
+
|
|
|
+ <!-- 右侧抽屉展示节点详情 -->
|
|
|
+ <v-navigation-drawer
|
|
|
+ v-model="drawer.show"
|
|
|
+ fixed
|
|
|
+ temporary
|
|
|
+ right
|
|
|
+ width="50%"
|
|
|
+ style="z-index: 300;"
|
|
|
+ >
|
|
|
+ <template v-slot:prepend>
|
|
|
+ <v-toolbar color="primary" dark>
|
|
|
+ <v-toolbar-title>节点详情</v-toolbar-title>
|
|
|
+ <v-spacer></v-spacer>
|
|
|
+ <v-btn icon @click="drawer.show = false">
|
|
|
+ <v-icon>mdi-close</v-icon>
|
|
|
+ </v-btn>
|
|
|
+ </v-toolbar>
|
|
|
+ </template>
|
|
|
+ <v-container v-if="drawer.nodeData">
|
|
|
+ <div class="d-flex">
|
|
|
+ <ul class="ml-0 pl-0" style="color: #333;text-align:right; padding-right: 16px; min-width: 120px;">
|
|
|
+ <template v-for="key in Object.keys(detailKeys)">
|
|
|
+ <li v-if="drawer.nodeData[key]" :key="key" style="margin-bottom: 12px;">{{ detailKeys[key] }} :</li>
|
|
|
+ </template>
|
|
|
+ </ul>
|
|
|
+ <ul style="flex: 1; overflow-x: auto;">
|
|
|
+ <template v-for="key in Object.keys(detailKeys)">
|
|
|
+ <li v-if="drawer.nodeData[key]" :key="key" style="margin-bottom: 12px;">
|
|
|
+ <span v-if="key === 'labels'">{{ drawer.nodeData[key].join(',') }}</span>
|
|
|
+ <span v-else-if="key === 'matched_data'">
|
|
|
+ <v-simple-table fixed-header height="60vh" v-if="getMatchDataArray(drawer.nodeData[key]) && getMatchDataArray(drawer.nodeData[key]).length">
|
|
|
+ <template v-slot:default>
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th v-for="header in getMatchDataHeaders(drawer.nodeData[key])" :key="header.value" class="text-left">
|
|
|
+ {{ header.text }}
|
|
|
+ </th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <tr v-for="(item, index) in getMatchDataArray(drawer.nodeData[key])" :key="index">
|
|
|
+ <td v-for="header in getMatchDataHeaders(drawer.nodeData[key])" :key="header.value">
|
|
|
+ {{ item[header.value] }}
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </template>
|
|
|
+ </v-simple-table>
|
|
|
+ <!-- <v-data-iterator
|
|
|
+ :items="getMatchDataArray(drawer.nodeData[key])"
|
|
|
+ hide-default-footer
|
|
|
+ hide-default-header
|
|
|
+ >
|
|
|
+ <template v-slot:default="props">
|
|
|
+ <v-row>
|
|
|
+ <v-col v-for="item in props.items" :key="item.id" cols="6">
|
|
|
+ <v-card elevation="3">
|
|
|
+ <v-list dense>
|
|
|
+ <v-list-item v-for="(key, index) in Object.keys(item)" :key="index">
|
|
|
+ <v-list-item-content>{{ key }}:</v-list-item-content>
|
|
|
+ <v-list-item-content class="align-end">{{ item[key] }}</v-list-item-content>
|
|
|
+ </v-list-item>
|
|
|
+ </v-list>
|
|
|
+ </v-card>
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+ </template>
|
|
|
+ </v-data-iterator> -->
|
|
|
+ </span>
|
|
|
+ <span v-else>{{ drawer.nodeData[key] }}</span>
|
|
|
+ </li>
|
|
|
+ </template>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </v-container>
|
|
|
+ </v-navigation-drawer>
|
|
|
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -85,21 +181,6 @@ const NODES_SIZE = {
|
|
|
width: 30,
|
|
|
height: 30
|
|
|
}
|
|
|
-// const LINE_COLOR_MAP = {
|
|
|
-// 包含: '#F44336', // red
|
|
|
-// 影响: '#E91E63', // pink
|
|
|
-// 依赖: '#3F51B5', // indigo
|
|
|
-// 来源: '#4CAF50', // green
|
|
|
-// 引用: '#9C27B0', // purple
|
|
|
-// 继承: '#673AB7', // deep-purple
|
|
|
-// 标记: '#2196F3', // blue
|
|
|
-// 使用: '#03A9F4', // light-blue
|
|
|
-// 关联: '#00BCD4', // cyan
|
|
|
-// 拥有: '#009688', // teal
|
|
|
-// 下级: '#8BC34A', // light-green
|
|
|
-// 上级: '#CDDC39', // lime
|
|
|
-// 联动: '#FF9800' // orange
|
|
|
-// }
|
|
|
export default {
|
|
|
name: 'details-graph',
|
|
|
components: { RelationGraph, MEmpty },
|
|
|
@@ -121,6 +202,10 @@ export default {
|
|
|
activeId: +this.$route.params.id,
|
|
|
item: {}
|
|
|
},
|
|
|
+ drawer: {
|
|
|
+ show: false,
|
|
|
+ nodeData: null
|
|
|
+ },
|
|
|
empty: true,
|
|
|
loading: false,
|
|
|
graphOptions: {
|
|
|
@@ -233,14 +318,14 @@ export default {
|
|
|
},
|
|
|
// 节点详情
|
|
|
detailKeys: {
|
|
|
- id: '节点ID',
|
|
|
+ // id: '节点ID',
|
|
|
name_zh: '中文名称',
|
|
|
name_en: '英文名称',
|
|
|
node_type: '节点类型',
|
|
|
labels: '节点标签',
|
|
|
is_target: '是否目标节点',
|
|
|
is_source: '是否源节点',
|
|
|
- matched_fields: '匹配字段'
|
|
|
+ matched_data: '匹配数据'
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
@@ -331,11 +416,33 @@ export default {
|
|
|
this.$snackbar.error(error)
|
|
|
}
|
|
|
},
|
|
|
+ // 节点点击
|
|
|
onNodeClick (nodeObject, $event) {
|
|
|
- console.log('onNodeClick:', nodeObject)
|
|
|
+ // 设置节点数据并打开抽屉
|
|
|
+ this.drawer.nodeData = nodeObject.data
|
|
|
+ this.drawer.show = true
|
|
|
},
|
|
|
onLineClick (lineObject, $event) {
|
|
|
console.log('onLineClick:', lineObject)
|
|
|
+ },
|
|
|
+ // 获取匹配数据的表头
|
|
|
+ getMatchDataHeaders (matchData) {
|
|
|
+ if (!matchData) return []
|
|
|
+ // 统一转换为数组格式
|
|
|
+ const dataArray = Array.isArray(matchData) ? matchData : [matchData]
|
|
|
+ if (dataArray.length === 0) return []
|
|
|
+ // 从第一个对象中提取所有 key 作为表头
|
|
|
+ const firstItem = dataArray[0]
|
|
|
+ const headers = Object.keys(firstItem).map(key => ({
|
|
|
+ text: key,
|
|
|
+ value: key
|
|
|
+ }))
|
|
|
+ return headers
|
|
|
+ },
|
|
|
+ // 将匹配数据统一转换为数组格式
|
|
|
+ getMatchDataArray (matchData) {
|
|
|
+ if (!matchData) return []
|
|
|
+ return Array.isArray(matchData) ? matchData : [matchData]
|
|
|
}
|
|
|
}
|
|
|
}
|