g6版本
"@antv/g6": "^4.6.15"
数据
// 列表数据
public class DataBack {
private String userId;
private String operateId;
private String abilityType;
private String abilityId;
private String abilityName;
private String modeType;
}
// 流程数据
public class DataBackShow {
private String id;
private String operateId;
private String abilityId;
private String depAbilityIds;
private String status;
private String abilityType;
private String abilityName;
private String currentX;
private String currentY;
}
组件调用
根据用户,运营分类
<!--user-id:用户id; operate-id:运营id; drawingListBackKey:刷新组件;
refreshListBack: 刷新数据,刷新组件方法; drawing-list: 节点和连线数据 -->
<draggable-back
:user-id="user.userId"
:operate-id="operateId"
:key="drawingListBackKey"
@refresh="refreshListBack"
:drawing-list="drawingListBack"
@dragover.prevent
/>
// 刷新数据方法
refreshListBack(drawingList) {
this.drawingListBack = drawingList
this.drawingListBackKey = Date.now()
},
模型组件
<template>
<el-row style="height: 100%;">
<el-col style="border-left: 2px solid #DCDFE6;height: 100%;">
<div id="container" style="height: 100%;position: relative;border: 1px solid black;">
</div>
</el-col>
</el-row>
</template>
<script>
import G6 from '@antv/g6'
export default {
name: 'DraggableBack',
props: [
'userId',
'drawingList',
'operateId',
],
data() {
return {
// 图上节点
data: { nodes: [], edges: []},
// 节点数据
graph: undefined
}
},
mounted() {
this.initData() // 初始化数据
this.initComponent() // 初始化图
},
methods: {
// 边缘:增删; 删除节点 之后,回填值
saveBackForm(drawingList = this.drawingList) {
this.$emit('refresh', drawingList)
},
nodeDragend(model, currentX, currentY) {// 移动节点结束后,更新一下数据
let { id } = model
for (let i = 0; i < this.drawingList.length; i++) {
let item = this.drawingList[i]
if (item.id === id) {
item.currentX = currentX.toFixed(1)
item.currentY = currentY.toFixed(1)
}
}
this.saveBackForm()
},
// 删除边
delEdge(edge) {
this.$confirm('请确认是否删除连线?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let source = edge.getSource().getID()
let target = edge.getTarget().getID()
for (let i = 0; i < this.drawingList.length; i++) {
let item = this.drawingList[i]
if (item.id === target) {
// 转成数组过滤删除后,转成字符串
let dep = item.depAbilityIds.split(",").filter(Boolean)
item.depAbilityIds = dep.filter(item => item !== source).toString()
}
}
// this.graph.removeItem(edge) // 仅去除图上的线
this.saveBackForm()
}).catch(() => {
});
},
// 新增边
addEdge(source, target) {
// 自己不能连自己
if (source !== target) {
for (let i = 0; i < this.drawingList.length; i++) {
let item = this.drawingList[i]
if (item.id === target) {
// 转成数组添加后,转成字符串
let dep = item.depAbilityIds.split(",").filter(Boolean)
dep.push(source)
item.depAbilityIds = dep.toString()
}
}
}
this.saveBackForm()
},
// 删除节点
delModel(model) {
this.$confirm('请确认是否删除节点?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let { id } = model
// 去掉连接线: 循环查找,节点来源是否存在于其他节点依赖中,存在则去除
for (let i = 0; i < this.drawingList.length; i++) {
let item = this.drawingList[i]
let dep = item.depAbilityIds.split(",").filter(Boolean) // 节点来源数组化
if (dep.indexOf(id) > -1) { // 来源是否存在于这个数组
// 存在,则过滤删除后,转成字符串
item.depAbilityIds = dep.filter(item => item !== id).toString()
}
}
// 去掉节点:
let drawingList = this.drawingList.filter(item => item.id !== id)
// this.graph.removeItem(model) // 仅去除图上的节点
this.saveBackForm(drawingList)
}).catch(() => {
});
},
// 初始化数据
initData(){
for (let i = 0; i < this.drawingList.length; i++) {
let item = this.drawingList[i]
this.data.nodes.push({
id: item.id,
label: this.fittingString(item.abilityName, 80, 12),
type: 'service',
x: Number(item.currentX),
y: Number(item.currentY),
})
let dep = item.depAbilityIds.split(",").filter(Boolean)
for (let j = 0; j < dep.length; j++) {
this.data.edges.push({ source: dep[j], target: item.id })
}
}
},
// 初始化图
initComponent() {
let container = document.getElementById('container')
const width = container.scrollWidth
const height = container.scrollHeight
// 自定义节点
G6.registerNode(
'service',
{
// 绘制后的附加操作
afterDraw(cfg, group) {
// 添加删除按钮
group.addShape('circle', {
attrs: {
x: 50,
y: -20,
r: 10,
fill: 'red',
cursor: 'pointer',
},
visible: false,
name: 'custom-circle-shape',
})
group.addShape('text', { // 删除的X号
attrs: {
text: 'X',
x: 45.5,
y: -11.5,
fontSize: 14,
fill: '#fff',
cursor: 'pointer',
},
visible: false,
name: 'custom-text-shape',
})
// 添加锚点
const bbox = group.getBBox();
const anchorPoints = this.getAnchorPoints(cfg)
anchorPoints.forEach((anchorPos, i) => {
group.addShape('circle', {
attrs: {
r: 5,
x: bbox.x + bbox.width * anchorPos[0],
y: bbox.y + bbox.height * anchorPos[1],
fill: '#fff',
stroke: '#5F95FF'
},
name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point')
anchorPointIdx: i, // flag the idx of the anchor-point circle
links: 0, // cache the number of edges connected to this shape
visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state
draggable: true // allow to catch the drag events on this shape
})
})
},
// 锚点的位置
getAnchorPoints(cfg) {
return cfg.anchorPoints || [[0, 0.5], [0.5, 1], [1, 0.5], [0.5, 0]];
},
// 触发事件
setState(name, value, item) {
if (name === 'show') {
// 删除按钮
const custom = item.getContainer().findAll(ele => ele.get('name').indexOf('custom') > -1)
custom.forEach(point => {
if (value) point.show()
else point.hide()
})
// 锚点是否展示
// const anchorPoints = item.getContainer().findAll(ele => ele.get('name') === 'anchor-point');
// anchorPoints.forEach(point => {
// if (value || point.get('links') > 0) point.show()
// else point.hide()
// })
}
// 点击节点高亮
if (name === 'selected') {
const service = item.getContainer().findAll(ele => ele.get('name') === 'service-keyShape')[0]
if (value) {
service.attr('stroke', '#5400fc');
} else {
service.attr('stroke', '');
}
}
}
}, 'rect')
const SnapLine = new G6.SnapLine(); // G6插件:对齐辅助线
// 没有的时候新增
if (this.graph === undefined) {
this.graph = new G6.Graph({
container: 'container',
width,
height,
fitCenter: false, // 开启后图将会被平移,图的中心将对齐到画布中心
autoPaint: false,
// layout: {
// // type: 'dagre',
// },
modes: {
default: ['drag-node', {
type: 'create-edge',
trigger: 'click',
shouldBegin: e => {
this.graph.startNode = e.item
return e.target && !(e.target.get('name').indexOf('custom') > -1)
},
shouldEnd(e) {
if (e.item === this.graph.startNode) {
return false;
}
this.graph.startNode = null;
return true;
},
}],
snap: ['grid'] // 防止节点移动的时候,轻微偏移
},
grid: { // 防止节点移动的时候,轻微偏移
forceAlign: true, // 强制节点位置对齐到网格
cell: 20 // 网格大小
},
defaultNode: {
type: 'rect',
style: {
width: 150,
height: 50,
radius: 10,
stroke: '',
fill: 'l(90) 0:#fff 1:#f00' // 渐变色
},
labelCfg: {
style: {
fontSize: 12,
}
}
},
defaultEdge: {
color: '#00ffff',
style: {
endArrow: {
path: 'M 0,0 L 8,4 L 8,-4 Z',
fill: '#00ffff',
},
cursor: 'pointer',
lineWidth: 2,
},
},
plugins: [SnapLine],
})
}
this.graph.data(this.data) // 填充数据
this.graph.render() // 渲染
this.graphTriggerEvent() // 触发事件
},
fittingString(str, maxWidth, fontSize) {// 字符串换行
let currentWidth = 0
let res = ''
let lineNum = []
const pattern = new RegExp('[\u4E00-\u9FA5]+')
str.split('').forEach((letter, i) => {// 根据字符串长度进行分割获取分割点
if (pattern.test(letter)) {
currentWidth += fontSize
} else {
currentWidth += G6.Util.getLetterWidth(letter, fontSize)
}
if (currentWidth > maxWidth) {
lineNum.push(i)
currentWidth = 0
}
})
let length = lineNum.length
if (length > 0) {// 超过长度循环切割,最后补上最后str
for (let i = 0; i < length; i++) {
res += str.substring(0, lineNum[i]) + '\n';
str = str.substring(lineNum[i]);
}
}
return res += str
},
// 图的触发事件
graphTriggerEvent() {
// 删除点击节点
this.graph.on('custom-circle-shape:click', (evt) => {
this.delModel(evt.item.getModel())
})
this.graph.on('custom-text-shape:click', (evt) => {
this.delModel(evt.item.getModel())
})
// 移入边时高亮
this.graph.on('edge:mouseenter', (e) => {
const edge = e.item;
this.graph.updateItem(edge, {
style: {
endArrow: {
path: 'M 0,0 L 8,4 L 8,-4 Z',
fill: 'red',
},
stroke: 'red',
},
});
});
// 移出边时还原
this.graph.on('edge:mouseleave', (e) => {
const edge = e.item;
this.graph.updateItem(edge, {
style: {
endArrow: {
path: 'M 0,0 L 8,4 L 8,-4 Z',
fill: '#00ffff'
},
stroke: '#00ffff',
},
});
});
// 删除点击时触发边
this.graph.on('edge:click', (e) => {
this.delEdge(e.item)
})
// 点击新增边,更新一下样式
this.graph.on('node:click', (e) => {
this.graph.setItemState(e.item, 'selected', true)
})
this.graph.on('canvas:click', (e) => {
let selected = this.graph.findAllByState('node', 'selected')[0]
if (selected) {
this.graph.setItemState(selected, 'selected', false)
}
})
// 点击新增边,还原样式
this.graph.on('aftercreateedge', (e) => {
const { edge } = e;
this.addEdge(edge.getSource().getID(), edge.getTarget().getID())
})
// 锚点和删除的展示
this.graph.on('node:mouseenter', e => {
this.graph.setItemState(e.item, 'show', true)
})
this.graph.on('node:mouseleave', e => {
this.graph.setItemState(e.item, 'show', false)
})
// 拖拽节点时,保存节点位置
this.graph.on('node:dragend', e => {
const node = e.item;
const model = node.getModel();
const { x, y } = model;
this.nodeDragend(model, x, y)
})
},
}
}
</script>
<style scoped>
</style>