效果图
image.png拖拽进父节点
image.pngimage.png
image.png
点击滑动自动生成父节点
image.pngimage.png
组件封装 sdFlow
npm i @antv/x6 @antv/x6-plugin-stencil @antv/x6-plugin-transform @antv/x6-plugin-selection @antv/x6-plugin-snapline @antv/x6-plugin-keyboard @antv/x6-plugin-clipboard @antv/x6-plugin-history insert-css --save
<!--
* @Descripttion: 流程图自定义绘制
* @version: 1.0
* @Author: xushuaibing
* @Date: 2023年9月14日00:00:00
-->
<template>
<div id="container">
<!-- <div id="graph-container" style="min-width: 400px; min-height: 600px"></div> -->
</div>
</template>
<script>
import { Graph, Shape } from "@antv/x6";
import { Stencil } from "@antv/x6-plugin-stencil";
import { Transform } from "@antv/x6-plugin-transform";
import { Selection } from "@antv/x6-plugin-selection";
import { Snapline } from "@antv/x6-plugin-snapline";
import { Keyboard } from "@antv/x6-plugin-keyboard";
import { Clipboard } from "@antv/x6-plugin-clipboard";
import { History } from "@antv/x6-plugin-history";
import insertCss from "insert-css";
export default {
name: "SdFlow",
props: {
dataShow: {
type: Object,
default: () => {}
}
},
watch: {
// 数据回显
dataShow: {
immediate: true,
deep: true,
handler(v) {
if(v) {
this.$nextTick(() => {
this.graph.fromJSON(v)
})
}
}
}
},
data() {
return {
ports: { // 设置图形上的点
groups: {
top: {
position: "top",
attrs: {
circle: {
r: 4,
magnet: true,
stroke: "#5F95FF",
strokeWidth: 1,
fill: "#fff",
style: {
visibility: "hidden",
},
},
},
},
right: {
position: "right",
attrs: {
circle: {
r: 4,
magnet: true,
stroke: "#5F95FF",
strokeWidth: 1,
fill: "#fff",
style: {
visibility: "hidden",
},
},
},
},
bottom: {
position: "bottom",
attrs: {
circle: {
r: 4,
magnet: true,
stroke: "#5F95FF",
strokeWidth: 1,
fill: "#fff",
style: {
visibility: "hidden",
},
},
},
},
left: {
position: "left",
attrs: {
circle: {
r: 4,
magnet: true,
stroke: "#5F95FF",
strokeWidth: 1,
fill: "#fff",
style: {
visibility: "hidden",
},
},
},
},
},
items: [
{
group: "top",
},
{
group: "right",
},
{
group: "bottom",
},
{
group: "left",
},
],
},
graph: '',
parentX: '',
parentY: '',
zIndex: 0.4
};
},
methods: {
// 初始化
initGarph() {
const graph = new Graph({
container: document.getElementById("graph-container"),
grid: true,
mousewheel: {
enabled: true,
zoomAtMousePosition: true,
modifiers: "ctrl",
minScale: 0.5,
maxScale: 3,
},
// 设置父节点 组合关系
embedding: {
enabled: true,
findParent({ node }) {
const bbox = node.getBBox()
return this.getNodes().filter((node) => {
const data = node.getData()
if (data && data.parent) {
const targetBBox = node.getBBox()
return bbox.isIntersectWithRect(targetBBox)
}
return false
})
},
},
connecting: {
router: "manhattan",
connector: {
name: "rounded",
args: {
radius: 8,
},
},
anchor: "center",
connectionPoint: "anchor",
allowBlank: false,
snap: {
radius: 20,
},
createEdge() {
return new Shape.Edge({
attrs: {
line: {
stroke: "#A2B1C3",
strokeWidth: 1,
targetMarker: {
// name: "block",
// name: "circle",
width: 12,
height: 8,
},
},
},
// 双击填写关系
tools: [
{
name: 'edge-editor',
args: {
attrs: {
backgroundColor: '#fff',
},
},
},
],
zIndex: 10,
});
},
validateConnection({ targetMagnet }) {
return !!targetMagnet;
},
},
highlighting: {
embedding: {
name: "stroke",
args: {
padding: -2,
attrs: {
// fill: "#5F95FF",
stroke: "#397CD6",
strokeWidth: 1
},
},
},
},
});
this.graph = graph
// #region 使用插件
graph
.use(
new Transform({
resizing: true,
rotating: true,
})
)
.use(
new Selection({
rubberband: true,
showNodeSelectionBox: true,
})
)
.use(new Snapline())
.use(new Keyboard())
.use(new Clipboard())
.use(new History());
// #region 初始化 stencil
const stencil = new Stencil({
title: "",
target: graph,
stencilGraphWidth: '100%',
stencilGraphHeight: 60,
collapsable: false,
groups: [
{
title: "自定义规则",
name: "group1",
}
],
layoutOptions: {
columns: 12,
columnWidth: 80,
rowHeight: 55,
},
});
document.getElementById("stencil").appendChild(stencil.container);
// #region 快捷键与事件
graph.on('edge:click', ({ e, x, y, node, view }) => { // 连接边
console.log(e, x, y, node, view)
})
graph.on('blank:mousedown', ({ x, y}) => {
this.parentX = x
this.parentY = y
})
graph.on('blank:mouseup', ({x, y }) => {
if(Math.abs(x - this.parentX) > 0 && Math.abs(y - this.parentY) > 0) {
this.createParent(this.parentX, this.parentY, Math.abs(x - this.parentX), Math.abs(y - this.parentY) )
// const nodes = graph.getNodes(); // 选中节点集合
// const nodes = graph.getContentArea(); // 绘画出得节点
// 获取所画区域得节点
const nodeArea = graph.getNodesInArea(this.parentX, this.parentY, Math.abs(x - this.parentX), Math.abs(y - this.parentY))
// const arr = nodeArea.filter(item => item.data.parent)
let childrenIds = []
let parentID = ''
console.log(nodeArea)
// 获取父和子的id
nodeArea.forEach(item => {
if(item.data && item.data.parent) {
parentID = item.id
}else {
childrenIds.push(item.id)
}
})
const allCellData = this.getGraphData()
// 给数据帮定父子
allCellData.cells.forEach(item => {
if(item.id == parentID) {
item.children = childrenIds
}else if(childrenIds.includes(item.id)){
item.parent = parentID
}
})
graph.fromJSON(allCellData) // 重新渲染
}
})
// graph.on('view:mounted', ({ view }) => { // 创建节点后触发
// if(view.cell.data && view.cell.data.parent) { // 限制为父节点 为实现选中生成他们的父
// console.log(view.cell.id, view)
// }
// })
graph.bindKey(["meta+c", "ctrl+c"], () => {
const cells = graph.getSelectedCells();
if (cells.length) {
graph.copy(cells);
}
return false;
});
graph.bindKey(["meta+x", "ctrl+x"], () => {
const cells = graph.getSelectedCells();
if (cells.length) {
graph.cut(cells);
}
return false;
});
graph.bindKey(["meta+v", "ctrl+v"], () => {
if (!graph.isClipboardEmpty()) {
const cells = graph.paste({ offset: 32 });
graph.cleanSelection();
graph.select(cells);
}
return false;
});
// undo redo
graph.bindKey(["meta+z", "ctrl+z"], () => {
if (graph.canUndo()) {
graph.undo();
}
return false;
});
graph.bindKey(["meta+shift+z", "ctrl+shift+z"], () => {
if (graph.canRedo()) {
graph.redo();
}
return false;
});
// select all
graph.bindKey(["meta+a", "ctrl+a"], () => {
const nodes = graph.getNodes();
if (nodes) {
graph.select(nodes);
}
});
// delete
graph.bindKey("backspace", () => {
const cells = graph.getSelectedCells();
if (cells.length) {
graph.removeCells(cells);
}
});
// zoom
graph.bindKey(["ctrl+1", "meta+1"], () => {
const zoom = graph.zoom();
if (zoom < 1.5) {
graph.zoom(0.1);
}
});
graph.bindKey(["ctrl+2", "meta+2"], () => {
const zoom = graph.zoom();
if (zoom > 0.5) {
graph.zoom(-0.1);
}
});
// 控制连接桩显示/隐藏
const showPorts = (ports, show) => {
for (let i = 0, len = ports.length; i < len; i += 1) {
ports[i].style.visibility = show ? "visible" : "hidden";
}
};
graph.on("node:mouseenter", () => {
const container = document.getElementById("graph-container");
const ports = container.querySelectorAll(".x6-port-body");
showPorts(ports, true);
});
graph.on("node:mouseleave", () => {
const container = document.getElementById("graph-container");
const ports = container.querySelectorAll(".x6-port-body");
showPorts(ports, false);
});
Graph.registerNode(
"custom-rect",
{
inherit: "rect",
width: 66,
height: 36,
attrs: {
body: {
strokeWidth: 1,
stroke: "#5F95FF",
fill: "#EFF4FF",
},
text: {
fontSize: 12,
fill: "#262626",
},
},
ports: { ...this.ports }
},
true
);
Graph.registerNode(
"custom-polygon",
{
inherit: "polygon",
width: 66,
height: 36,
attrs: {
body: {
strokeWidth: 1,
stroke: "#5F95FF",
fill: "#EFF4FF",
},
text: {
fontSize: 12,
fill: "#262626",
},
},
ports: {
...this.ports,
items: [
{
group: "top",
},
{
group: "bottom",
},
],
},
},
true
);
Graph.registerNode(
"custom-circle",
{
inherit: "circle",
width: 45,
height: 45,
attrs: {
body: {
strokeWidth: 1,
stroke: "#5F95FF",
fill: "#EFF4FF",
},
text: {
fontSize: 12,
fill: "#262626",
},
},
ports: { ...this.ports },
},
true
);
Graph.registerNode(
"custom-image",
{
inherit: "rect",
width: 52,
height: 52,
markup: [
{
tagName: "rect",
selector: "body",
},
{
tagName: "image",
},
{
tagName: "text",
selector: "label",
},
],
attrs: {
body: {
stroke: "#5F95FF",
fill: "#5F95FF",
},
image: {
width: 26,
height: 26,
refX: 13,
refY: 16,
},
label: {
refX: 3,
refY: 2,
textAnchor: "left",
textVerticalAnchor: "top",
fontSize: 12,
fill: "#fff",
},
},
ports: { ...this.ports },
},
true
);
const r1 = graph.createNode({
shape: "custom-rect",
label: "开始",
attrs: {
body: {
rx: 20,
ry: 26,
},
},
});
const r2 = graph.createNode({
shape: "custom-rect",
label: "过程"
});
const r3 = graph.createNode({
shape: "custom-rect",
attrs: {
body: {
rx: 6,
ry: 6,
},
},
label: "可选过程",
});
const r4 = graph.createNode({
shape: "custom-polygon",
attrs: {
body: {
refPoints: "0,10 10,0 20,10 10,20",
},
},
label: "决策",
});
const r5 = graph.createNode({
shape: "custom-polygon",
attrs: {
body: {
refPoints: "10,0 40,0 30,20 0,20",
},
},
label: "数据",
});
const r6 = graph.createNode({
shape: "custom-circle",
label: "连接",
});
stencil.load([r1, r2, r3, r4, r5, r6], "group1");
},
// 创建边
createEdge(
id,
source,
target,
vertices
) {
return this.graph.addEdge({
id,
source,
target,
vertices,
label: id,
attrs: {
label: {
fontSize: 12,
},
},
})
},
// 创建父节点
createParent(x, y, width, height) {
this.graph.addNode({
x,
y,
width,
height,
zIndex: this.zIndex,
// zIndex: 0.5,
attrs: {
body: {
fill: '#F5F9FF',
stroke: '#397CD6',
strokeWidth: 1,
strokeDasharray: 5
},
label: {
fontSize: 12,
},
},
data: {
parent: true,
},
ports: { ...this.ports }
})
this.zIndex = this.zIndex - 0.01 // 防止只能父子嵌套2层,实现前者能移入后者
},
// 获取JSON对象
getGraphData() {
return this.graph.toJSON()
},
// css样式
preWork() {
// 这里协助演示的代码,在实际项目中根据实际情况进行调整
const container = document.getElementById("container");
const stencilContainer = document.createElement("div");
stencilContainer.id = "stencil";
const graphContainer = document.createElement("div");
graphContainer.id = "graph-container";
container.appendChild(stencilContainer);
container.appendChild(graphContainer);
insertCss(`
#container {
display: flex;
flex-direction: column;
border: 1px solid #dfe3e8;
height: 100%;
width: 100%;
}
#stencil {
width: 100%;
height: 80px;
position: relative;
border-right: 1px solid #dfe3e8;
}
#graph-container {
width: 100%;
height: 100%;
}
.x6-widget-stencil {
background-color: #fff;
}
.x6-widget-stencil-title {
display: none;
background-color: #fff;
}
.x6-widget-stencil-group-title {
display: none;
background-color: #fff !important;
}
.x6-widget-transform {
margin: -1px 0 0 -1px;
padding: 0px;
border: 1px solid #239edd;
}
.x6-widget-transform > div {
border: 1px solid #239edd;
}
.x6-widget-transform > div:hover {
background-color: #3dafe4;
}
.x6-widget-transform-active-handle {
background-color: #3dafe4;
}
.x6-widget-transform-resize {
border-radius: 0;
}
.x6-widget-selection-inner {
border: 1px solid #239edd;
}
.x6-widget-selection-box {
opacity: 0;
}
`);
},
},
mounted() {
this.preWork();
this.initGarph();
},
};
</script>
<style></style>
组件使用
<template>
<el-button type="primary" @click="getData">获取data</el-button>
<sdFlow :dataShow="data" ref="sdFlow"></sdFlow>
</template>
<script>
import sdFlow from '@/components/sdFlow'
export default {
components: {
sdFlow
},
data() {
return {
data: {},
}
},
methods: {
// 获取json数据
getData() {
console.log(JSON.stringify(this.$refs.sdFlow.getGraphData()))
console.log(this.$refs.sdFlow.getGraphData())
}
}
}
</script>
<style>
</style>
网友评论