RelationMap官网: 在线配置官网(可以把数据放进去,直接看效果)
VUE2 效果:左侧列表栏,点击右侧显示对应的图谱
代码:按照代码直接贴过去,直接出效果
relationMap/index.vue
<template>
<div class="graphBody">
<List :list="list" @select-Graph="getGraph" />
<Graph v-if="showGraph" :key="graphItem.rootId" :item="graphItem" />
</div>
</template>
<script lang="ts">
import { getUuid } from "@/utils/generateUuid";
import List from "./list/index.vue";
import Graph from "./graph/index.vue";
import mockInfo from "./mock.json";
export default {
components: {
List,
Graph,
},
data() {
return {
list: [],
showGraph: false,
graphItem: {},
};
},
methods: {
initListData() {
this.list = mockInfo.data;
},
getGraph(item) {
const data = {
id: getUuid(),
name: item.globalVariable.name,
DefineType: item.globalVariable.defineType,
ActualType: item.globalVariable.actualType,
children: [],
};
const graphMap = new Map();
item.callFunction.forEach((item) => {
item.call.forEach((call) => {
const fileName = call.location.split("/").pop();
const secondNode = {
id: getUuid(),
fileName: fileName,
location: call.location,
children: [],
};
const threeNode = {
id: getUuid(),
functionName: item.functionName,
children: [
{
id: getUuid(),
startRow: call.startRow,
endRow: call.endRow,
startColumn: call.startColumn,
endColumn: call.endColumn,
mode: call.mode,
},
],
};
// 如果没有相同文件
if (!graphMap.has(call.location)) {
secondNode.children.push(threeNode);
graphMap.set(call.location, secondNode);
} else {
const copy = graphMap.get(call.location);
copy.children[0].children.push({
id: getUuid(),
startRow: call.startRow,
endRow: call.endRow,
startColumn: call.startColumn,
endColumn: call.endColumn,
});
graphMap.set(call.location, copy);
}
});
const valuesIterator = graphMap.values();
const valuesArray = Array.from(valuesIterator);
data.children.push(...valuesArray);
graphMap.clear();
});
// 合并具有相同 location 属性的对象的 children 属性
const mergedData = data.children.reduce((acc, obj) => {
const existingObj = acc.find((item) => item.location === obj.location);
if (existingObj) {
existingObj.children = existingObj.children.concat(obj.children);
} else {
acc.push(obj);
}
return acc;
}, []);
const newValue = { ...data, children: mergedData };
const { nodes, edges } = this.convertToNodesAndLines(newValue);
const graphData = {
rootId: newValue.id,
nodes,
lines: edges,
};
this.graphItem = graphData;
if (this.graphItem) {
this.showGraph = true;
}
},
convertToNodesAndLines(data) {
const treeToNode = (node, parentId) => {
const result = [];
const { children, ...nodeData } = node;
result.push({
id: node.id,
text: node.id,
data: nodeData,
});
if (node.children && node.children.length > 0) {
node.children.forEach(
(item) => {
result.push(...treeToNode(item, node.id));
}
);
}
return result;
};
const nodeArray = treeToNode(data, data.id);
const treeToEdge = (node) => {
const links = [];
if (node.children && node.children.length > 0) {
node.children.forEach(
(item) => {
const to = item.id;
const from = node.id;
links.push({
id: `${to}->${from}`,
to,
from,
});
links.push(...treeToEdge(item));
}
);
}
return links;
};
const edgeArray = treeToEdge(data);
return { nodes: nodeArray, edges: edgeArray };
},
},
mounted() {
this.initListData();
},
};
</script>
<style scoped>
.graphBody {
height: 100%;
border-radius: 4px;
border: 1px solid #222529;
background: #191c1f;
display: flex;
}
::-webkit-scrollbar {
display: block;
}
::-webkit-scrollbar-thumb {
background: #393d45;
}
</style>
使用的方法util/generateUuid.js
import { v1 as uuidv1 } from 'uuid'
// 去除-携带时间戳-uuid
export function getUuid() {
// const timestamp = new Date().getTime()
// 生成UUID时去掉连字符
const uuid = uuidv1().replace(/-/g, '')
// 截取前8位作为8位UUID
const eightDigitUuid = uuid.substring(0, 12)
return `${eightDigitUuid}`
}
relationMap/graph/index.vue
<template>
<div>
<div id="relation-graph-container" class="graph-wrapper">
<RelationGraph ref="graphRef" :options="graphOptions">
<template slot="node" slot-scope="{ node }">
<div
:class="`node-container node-type`"
@click="nodeClick(node.data)"
>
<span
v-if="node.data?.DefineType || node.data?.ActualType"
class="type-style"
>
<div class="type-title">{{ node.data.name }}</div>
<div class="content word-hidden type-content">
<p>DefineType: {{ node.data.DefineType }}</p>
<p>ActualType: {{ node.data.ActualType }}</p>
</div>
</span>
<span v-if="node.data?.location" class="file-style">
<div class="file-title">
<div>{{ node.data.fileName || node.data.name }}</div>
</div>
<div>
<span class="content word-hidden file-path"
>路径: {{ node.data?.location }}</span
>
</div>
</span>
<div v-if="node.data?.functionName" class="function-style">
<div>
<span clsss="content word-hidden1"
>函数名:{{ node.data?.functionName }}</span
>
</div>
</div>
<div v-if="node.data?.startRow" class="rowRolumn-style">
<span clsss="content word-hidden"
>行号:{{ node.data?.startRow }} 列号:{{
node.data?.startColumn
}}-{{ node.data?.endColumn }}</span
><span
><span
>【{{ node.data.mode === "read" ? "读取" : "写入" }}】</span
></span
>
</div>
</div>
</template>
</RelationGraph>
</div>
</div>
</template>
<script>
import RelationGraph from "relation-graph";
import { set } from "vue";
export default {
name: "Graph",
components: {
RelationGraph,
},
props: {
item: Object,
},
data() {
return {
graphOptions: {
backgroundImageNoRepeat: true,
moveToCenterWhenRefresh: false,
zoomToFitWhenRefresh: false,
defaultNodeBorderWidth: 0,
defaultNodeShape: 1,
layouts: [
{
label: "中心",
layoutName: "tree",
from: "left",
},
],
},
};
},
mounted() {
this.$refs.graphRef.setJsonData(this.item, (graphInstance) => {});
},
};
</script>
<style scoped>
.node-container {
width: 240px;
min-height: 40px;
border-radius: 4px;
background-color: #484750;
}
.type-style {
height: 120px;
}
.type-title {
color: #4880ff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 8px 12px;
text-align: left;
border-radius: 4px;
border-bottom: 1px solid #383b3e;
}
.type-content {
text-align: left;
line-height: 22px;
padding: 3px 6px;
}
.file-style {
height: 120px;
border-radius: 4px;
}
.file-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background-color: #387dff;
padding: 8px 12px;
text-align: left;
border-radius: 4px;
}
.file-path {
text-align: left;
line-height: 22px;
padding: 3px 6px;
}
.function-style {
height: 40px;
line-height: 40px;
text-align: left;
background: #aabee3;
border-radius: 4px;
padding: 0 4px;
color: #000;
}
.rowRolumn-style {
height: 40px;
line-height: 40px;
text-align: left;
padding: 0 4px;
border-radius: 2px;
}
.content {
padding: 4px 2px 2px;
width: 240px;
align-items: left;
}
.word-hidden {
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
.word-hidden1 {
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
}
</style>
<style>
.graph-wrapper {
height: calc(100vh - 160px);
width: 1200px;
}
.c-current-zoom {
color: #999999 !important;
}
.relation-graph .rel-map {
background-color: #191c1f !important;
}
.relation-graph .rel-toolbar .c-mb-button:hover {
background-color: rgba(153, 153, 153, 0.5) !important;
}
.relation-graph .rel-node-checked {
width: 100% !important;
box-shadow: 0 0px 5px 2px #191c1f !important;
}
</style>
relationMap/list/index.vue
该页面使用了虚拟滚动RecycleScroller,需要安装一下,参考文档: 虚拟滚动
安装: npm i vue-virtual-scroller
main.ts:
// vue virtual scroller
import "vue-virtual-scroller/dist/vue-virtual-scroller.css" // 引入它的 css
import VueVirtualScroller from "vue-virtual-scroller" // 引入它
Vue.use(VueVirtualScroller) //
<template>
<RecycleScroller
class="scroller"
:items="list"
:item-size="36"
key-field="id"
v-slot="{ item }"
>
<div class="user" @click="selectGraph(item)">
<span style="margin-left: 16px"> {{ item.globalVariable.name }}</span>
</div>
</RecycleScroller>
</template>
<script>
export default {
props: {
list: Array,
},
methods: {
selectGraph(item) {
this.$emit("select-Graph", item);
},
},
};
</script>
<style scoped>
.scroller {
height: 800px;
width: 240px;
}
.user {
height: 36px;
position: relative;
display: flex;
align-items: center;
color: #fff;
overflow: hidden;
text-overflow: ellipsis;
font-family: "Source Han Sans CN";
font-size: 14px;
font-style: normal;
font-weight: 700;
line-height: 20px; /* 142.857% */
}
.user:hover {
background: #222529;
}
/* 在:hover状态下添加before伪类的样式 */
.user:hover::before {
content: ""; /* 必须有content属性才能显示 */
display: block;
width: 3px;
height: 36px;
position: absolute;
left: 0;
top: 0;
background-color: #4880ff;
}
</style>
mock.json 数据量太大了,这个页面放不下了,看这里:mock.json