最近在写后台管理系统时,遇到一个需求,就是要实现关系图:
如下图所示:
在前年写天眼查功能时,我也遇到过这种需求,不过当时不知道可以用插件来实现,因此功能并未完全开发:
npm i relation-graph
yarn add relation-graph
<template>
<div>
<div style="height:calc(100vh - 50px);">
<RelationGraph ref="seeksRelationGraph" :options="graphOptions" :on-node-click="onNodeClick" :on-line-click="onLineClick" />
</div>
</div>
</template>
<script>
import RelationGraph from 'relation-graph'
export default {
name: 'Demo',
components: { RelationGraph },
data() {
return {
graphOptions: {
allowSwitchLineShape: true,
allowSwitchJunctionPoint: true,
defaultJunctionPoint: 'border'
// 这里可以参考"Graph 图谱"中的参数进行设置:http://relation-graph.com/#/docs/graph
}
}
},
mounted() {
this.showSeeksGraph()
},
methods: {
showSeeksGraph() {
var __graph_json_data = {
rootId: 'a',
nodes: [
// node配置选项:http://relation-graph.com/#/docs/node
// node支持通过插槽slot完全自定义,示例:http://relation-graph.com/#/demo/adv-slot
{ id: 'a', text: 'A', borderColor: 'yellow' },
{ id: 'b', text: 'B', color: '#43a2f1', fontColor: 'yellow' },
{ id: 'c', text: 'C', nodeShape: 1, width: 80, height: 60 },
{ id: 'e', text: 'E', nodeShape: 0, width: 150, height: 150 }
],
lines: [
// link配置选项:http://relation-graph.com/#/docs/link
{ from: 'a', to: 'b', text: '关系1', color: '#43a2f1' },
{ from: 'a', to: 'c', text: '关系2' },
{ from: 'a', to: 'e', text: '关系3' },
{ from: 'b', to: 'e', color: '#67C23A' }
]
}
this.$refs.seeksRelationGraph.setJsonData(__graph_json_data, (seeksRGGraph) => {
// Called when the relation-graph is completed
})
},
onNodeClick(nodeObject, $event) {
console.log('onNodeClick:', nodeObject)
},
onLineClick(linkObject, $event) {
console.log('onLineClick:', linkObject)
}
}
}
</script>
<template>
<a-spin :spinning="loading">
<div
style="height: calc(100vh - 130px); border: 1px solid #ebebeb"
ref="myPage"
>
<RelationGraph
ref="seeksRelationGraph"
:options="graphOptions"
:on-node-click="onNodeClick"
:on-line-click="onLineClick"
>
<div
slot="node"
slot-scope="{ node }"
style="height: 100%"
@mouseover="showNodeTips(node, $event)"
@mouseout="hideNodeTips(node, $event)"
>
<div
style="
border-radius: 50%;
cursor: pointer;
word-break: break-all;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
height: 100%;
font-size: 12px;
overflow: hidden;
"
>
{{
node.text &&
node.text
.replace('https://www.', '')
.replace('http://www.', '')
.replace('.com', '')
.replace('.html', '')
.split('/').length > 1
? node.text
.replace('https://www.', '')
.replace('http://www.', '')
.replace('.com', '')
.replace('.html', '')
.split('/')[
node.text
.replace('https://www.', '')
.replace('http://www.', '')
.replace('.com', '')
.replace('.html', '')
.split('/').length - 1
]||node.text
.replace('https://www.', '')
.replace('http://www.', '')
.replace('.com', '')
.replace('.html', '')
.split('/')[
node.text
.replace('https://www.', '')
.replace('http://www.', '')
.replace('.com', '')
.replace('.html', '')
.split('/').length - 2
]
: node.text
}}
</div>
</div>
<!-- <div
slot="bottomPanel"
style="
border-top: #efefef solid 1px;
height: 60px;
line-height: 60px;
text-align: center;
font-size: 18px;
background-color: #ffffff;
"
>
这里是底部插槽 slot="bottomPanel",可以自定义这里的内容
</div> -->
</RelationGraph>
</div>
<div
v-if="isShowNodeTipsPanel"
:style="{
left: nodeMenuPanelPosition.x + 'px',
top: nodeMenuPanelPosition.y + 'px',
}"
style="
z-index: 999;
padding: 10px;
background-color: #ffffff;
border: #eeeeee solid 1px;
box-shadow: 0px 0px 8px #cccccc;
position: absolute;
"
>
<div
style="
line-height: 25px;
padding-left: 10px;
color: #888888;
font-size: 12px;
"
>
节点名称:{{ currentNode.text }}
</div>
</div>
</a-spin>
</template>
<script>
import RelationGraph from 'relation-graph';
import { getRelationship } from '@/services/statistics';
export default {
components: { RelationGraph },
data() {
return {
loading: false,
data: [],
activeKey: '',
src: '',
isShowCodePanel: false,
isShowNodeTipsPanel: false,
nodeMenuPanelPosition: { x: 0, y: 0 },
currentNode: {},
graphOptions: {
allowSwitchLineShape: true,
allowSwitchJunctionPoint: true,
layouts: [
{
label: '中心',
layoutName: 'force', //布局方式(tree树状布局/center中心布局/force自动布局)
layoutClassName: 'seeks-layout-center', //当使用这个布局时,会将此样式添加到图谱上
defaultJunctionPoint: 'border', //默认的连线与节点接触的方式
defaultNodeShape: 0, //默认的节点形状,0:圆形;1:矩形
defaultLineShape: 1, //默认的线条样式(1:直线/2:样式2/3:样式3/4:折线/5:样式5/6:样式6)
centerOffset_y: 130, //根节点y坐标偏移量(针对项目配置单位为px)
min_per_width: 150, //节点距离限制:节点之间横向距离最小值
min_per_height: 180, //节点距离限制:节点之间纵向距离最小值
},
],
defaultNodeShape: 0, //默认的节点形状,0:圆形;1:矩形
defaultExpandHolderPosition: 'bottom', //节点展开关闭的按钮位置
defaultLineShape: 1, //默认的线条样式(1:直线/2:样式2/3:样式3/4:折线/5:样式5/6:样式6)
defaultJunctionPoint: 'tb', //默认的连线与节点接触的方式(border:边缘/ltrb:上下左右/tb:上下/lr:左右)当布局为树状布局时应使用tb或者lr,这样才会好看
defaultNodeBorderWidth: 0.2, //节点边框粗细
defaultcolor: 'rgba(0, 186, 189, 1)', //默认的线条颜色
defaultNodeColor: 'rgba(0, 206, 209, 1)', //默认的节点背景颜色
defaultNodeWidth: '80', //节点宽度
defaultNodeHeight: '80', //节点高度
defaultFocusRootNode: false, //默认为根节点添加一个被选中的样式
moveToCenterWhenResize: true, //当图谱的大小发生变化时,是否重新让图谱的内容看起来居中
// 这里可以参考"Graph 图谱"中的参数进行设置
},
};
},
activated() {
this.showSeeksGraph();
},
methods: {
showNodeTips(nodeObject, $event) {
this.currentNode = nodeObject;
const _base_position = this.$refs.myPage.getBoundingClientRect();
this.isShowNodeTipsPanel = true;
this.nodeMenuPanelPosition.x = $event.clientX - _base_position.x + 10;
this.nodeMenuPanelPosition.y = $event.clientY - _base_position.y + 10;
},
hideNodeTips(nodeObject, $event) {
this.isShowNodeTipsPanel = false;
},
callback(val) {
this.activeKey = val;
this.showSeeksGraph();
},
showSeeksGraph() {
getRelationship().then((res) => {
let nodes = res.node_list || [];
let links = res.edge_list || [];
var __graph_json_data = {
rootId: '0',
nodes: nodes,
links: links,
};
// 以上数据中的node和link可以参考"Node节点"和"Link关系"中的参数进行配置
this.$refs.seeksRelationGraph.setJsonData(
__graph_json_data,
(graphInstance) => {
// Called when the relation-graph is completed
setTimeout(() => {
graphInstance.stopAutoLayout();
}, 1000);
}
);
});
},
onNodeClick(nodeObject, $event) {
const allLinks = this.$refs.seeksRelationGraph.getLinks();
allLinks.forEach((link) => {
// 还原所有样式
link.relations.forEach((line) => {
if (line.data.orignColor) {
line.color = line.data.orignColor;
}
if (line.data.orignFontColor) {
line.fontColor = line.data.orignColor;
}
if (line.data.orignLineWidth) {
line.lineWidth = line.data.orignLineWidth;
}
});
});
// 让与{nodeObject}相关的所有连线高亮
allLinks
.filter(
(link) => link.fromNode === nodeObject || link.toNode === nodeObject
)
.forEach((link) => {
link.relations.forEach((line) => {
line.data.orignColor = line.color;
line.data.orignFontColor = line.fontColor || line.color;
line.data.orignLineWidth = line.lineWidth || 1;
line.color = '#ff0000';
line.fontColor = '#ff0000';
line.lineWidth = 3;
});
});
// 有时候更改一些属性后,并不能马上同步到视图,这需要以下方法让视图强制根据数据同步到最新
this.$refs.seeksRelationGraph.getInstance().dataUpdated();
},
onLineClick(lineObject, $event) {
console.log('onLineClick:', lineObject);
// this.$notify({
// title: '点击连线:',
// type: 'success',
// message: '点击了线:' + linkObject.fromNode.text + ' to ' + linkObject.toNode.text
// });
},
},
};
</script>
<style lang="less" scoped>
.c-my-node2 {
border: none;
background-position: center center;
background-size: 100%;
height: 74px;
width: 74px;
border-radius: 40px;
}
.c-node-name2 {
width: 160px;
margin-left: -40px;
text-align: center;
margin-top: 85px;
position: absolute;
}
.c-node-menu-item {
line-height: 30px;
padding-left: 10px;
cursor: pointer;
color: #444444;
font-size: 14px;
border-top: #efefef solid 1px;
}
.c-node-menu-item:hover {
background-color: rgba(66, 187, 66, 0.2);
}
</style>