<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>509-流程图</title>
<link rel="stylesheet" href="./assets/css/global.css" />
<link rel="stylesheet" href="./assets/css/task.css" />
<script src="./assets/js/vue.min.js"></script>
<script src="./assets/js/x6.js"></script>
<script src="./assets/js/config_ports.js"></script>
<script src="./assets/js/component.js"></script>
</head>
<body>
<div id="app">
<div class="head flex flex-align-c">
<div class="row flex flex-align-c">
<div class="colors">
<div class="diamond-color" :style="{background: COLOR}" @click="handlerColor"></div>
<j-config-color v-if="visible" @ok="handerOk">
</div>
<div class="input-title">颜色</div>
<j-input class="flex-one" v-model="COLOR" type="text" placeholder=""></j-input>
<j-button @click="revocation">撤销</j-button>
<j-button color="blue" @click="printNodeList">保存</j-button>
<j-button @click="recover">接口渲染</j-button>
</div>
</div>
<div class="home flex">
<!-- 左侧模块菜单 -->
<div class="menu-bar">
<!-- 模块列表 -->
<div class="title">基础图形</div>
<div class="line"></div>
<!-- 模块列表 -->
<div class="menu-list">
<div class="menu-list-li" v-for="item in moduleList" :key="item.id"
:style="{background: item.color}" draggable="true" @dragend="handleDragEnd($event, item)">
{{item.name}}
</div>
</div>
</div>
<!-- 画布部分 -->
<div class="canvas-card flex-one">
<div id="container" @dragover="dragoverDiv"></div>
<!-- 菜单 -->
<div class="menu">
<div>全选</div>
<div @click="handlerEdit">编辑</div>
<div>复制</div>
<div>剪切</div>
<div>删除</div>
</div>
</div>
<!-- 小地图 -->
<div id="minimapContainer"></div>
</div>
</div>
</body>
</html>
<script>
var ACTIONNODE = null
var app = new Vue({
el: "#app",
data() {
return {
// 颜色识别器
COLOR: 'linear-gradient(to left, #f00, #ff0, #0f0, #0ff, #00f, #f0f)',
h: 0,
visible: false,
actionNode: null,//当前节点
graph: null, // 画布实例对象
ifEXistCircle:null, // 是否存在圆球运动轨迹
curSelectNode: null, // 当前选中的节点
moduleList: [
{
id: 1,
name: "1",
color: '#fff',
shape: 'custom-rect',
attrs: {
body: {
rx: 20,
ry: 26,
},
},
},
{
id: 2,
name: "测试",
color: '#fff',
shape: 'custom-rect',
attrs: {
body: {
},
},
},
{
id: 3,
color: '#fff',
name: "3",
shape: 'custom-rect',
attrs: {
body: {
rx: 6,
ry: 6,
},
},
},
{
id: 4,
name: "4",
color: '#fff',
shape: 'custom-polygon',
attrs: {
body: {
refPoints: '0,10 10,0 20,10 10,20',
},
},
},
{
id: 5,
color: '#fff',
name: "5",
shape: 'custom-polygon',
attrs: {
body: {
refPoints: '10,0 40,0 30,20 0,20',
},
},
},
{
id: 6,
color: '#fff',
name: "6",
shape: 'custom-circle',
attrs: {
body: {}
}
},
], // 列表可拖动模块
};
},
created() {
window.addEventListener("resize", this.getViweHieth, false);
},
destroyed() {
window.removeEventListener('resize', this.getViweHieth, false)
},
mounted() {
this.getViweHieth()
this.init();
this.dblclick() // 双击改变
this.graph.on('node:contextmenu', function (event) {
this.actionNode = event.node;
ACTIONNODE = event.node
console.log(this.actionNode);
const menu = document.getElementsByClassName('menu')[0]
menu.style.display = 'block'
menu.style.left = event.x + 10 + 'px'
menu.style.top = event.y + 'px'
document.body.addEventListener('click', function () {
menu.style.display = 'none'
})
})
},
methods: {
// 初始化流程图画布
init() {
this.initGraph() // 初始化X6
this.registerNode() // 自定义节点样式
this.LinkPegShow() // 节点桩显示隐藏
this.nodeAddEvent() // 显示边框删除节点
this.shortcutKey() // 快捷键
},
initGraph() {
let container = document.getElementById("container"); // 地图
let minimapContainer = document.getElementById("minimapContainer"); //小地图
// 构建X6配置
this.graph = new X6.Graph({
history: true,
container: container, // 画布容器
width: container.offsetWidth, // 画布宽
height: container.offsetHeight, // 画布高
background: this.color, // 背景(透明)
snapline: true, // 对齐线
height: this.h,
highlighting: {
magnetAvailable: magnetAvailabilityHighlighter,
magnetAdsorbed: {
name: 'stroke',
args: {
attrs: {
fill: '#fff',
stroke: '#31d0c6',
},
},
},
},
grid: {
type: 'mesh',
size: 20, // 网格大小 10px
visible: true, // 渲染网格背景
args: {
color: '#eeeeee', // 网格线/点颜色
thickness: 2, // 网格线宽度/网格点大小
},
},
mousewheel: {
enabled: true, // 支持滚动放大缩小
},
panning: {
enabled: true, // 是否拖动
},
scroller: {
enabled: true,
},
resizing: true, // 缩放节点
rotating: true,
selecting: {
enabled: true,
rubberband: true,
showNodeSelectionBox: true,
},
snapline: true,
keyboard: true,
clipboard: true,
minimap: {
enabled: true,
container: minimapContainer,
width: 250,
height: 250,
},
connecting: {
router: {
name: 'manhattan',
args: {
padding: 1,
},
},
connector: {
name: 'rounded',
args: {
radius: 8,
},
},
anchor: 'center',
connectionPoint: 'anchor',
snap: {
radius: 20,
},
createEdge() {
return new X6.Shape.Edge({
attrs: {
line: {
stroke: '#77DDFF',
strokeWidth: 2,
targetMarker: {
name: 'block',
width: 12,
height: 8,
},
},
},
zIndex: 0,
})
},
allowBlank: false, // 是否允许连接到画布空白位置的点,默认为 true。
validateConnection(e) {
return validate(e)
},
},
})
},
// 拖动节点到画布中鼠标样式变为可拖动状态
dragoverDiv(ev) {
ev.preventDefault();
},
// 拖动后松开鼠标触发事件
handleDragEnd(e, item) {
item.width = item.shape == 'custom-polygon' ? 100 : e.target.offsetWidth
this.addHandleNode(e.pageX - 300, e.pageY - 60, item)
},
// 添加节点
addHandleNode(x, y, item) {
item.attrs.label = {
fontSize: 18,
stroke: 'black'
}
item.attrs.body.fill = item.color
item.attrs.body.stroke = 'black'
item.attrs.body.strokeWidth = 2
let node = {
shape: item.shape, // 指定使用何种图形,默认值为 'rect'
x: x,
y: y,
id: item.id, // String,可选,节点的唯一标识
width: item.width,
label: item.name,
height: 40,
type: item.type,
parentId: item.parentId,
children: item.children || [],
attrs: item.attrs,
ports
}
if (node.shape == 'custom-polygon') {
node.ports.items = node.ports.items.filter(item => item.group == 'top' || item.group == 'bottom')
}
this.graph.addNode(node)
},
LinkPegShow() {
// 控制连接桩显示/隐藏
const showPorts = (ports, show) => {
for (let i = 0, len = ports.length; i < len; i = i + 1) {
ports[i].style.visibility = show ? 'visible' : 'hidden'
}
}
this.graph.on('node:mouseenter', () => {
const container = document.getElementById('container')
const ports = container.querySelectorAll(
'.x6-port-body',
)
showPorts(ports, true)
})
this.graph.on('node:mouseleave', (e) => {
const container = document.getElementById('container')
const ports = container.querySelectorAll(
'.x6-port-body',
)
showPorts(ports, false)
})
},
// 1
nodeAddEvent() {
// 节点绑定点击事件
this.graph.on('node:click', ({ e, x, y, node, view }) => {
// 判断是否有选中过节点
if (this.curSelectNode) {
// 移除选中状态
this.curSelectNode.removeTools()
// 判断两次选中节点是否相同
if (this.curSelectNode !== node) {
node.addTools([{
name: 'boundary',
args: {
attrs: {
fill: '#16B8AA',
stroke: '#2F80EB',
strokeWidth: 1,
fillOpacity: 0.1
}
}
}, {
name: 'button-remove',
args: {
x: '100%',
y: 0,
offset: {
x: 0,
y: 0
}
}
}])
this.curSelectNode = node
} else {
this.curSelectNode = null
}
} else {
this.curSelectNode = node
node.addTools([{
name: 'boundary',
args: {
attrs: {
fill: '#16B8AA',
stroke: '#2F80EB',
strokeWidth: 1,
fillOpacity: 0.1
}
}
}, {
name: 'button-remove',
args: {
x: '100%',
y: 0,
offset: {
x: 0,
y: 0
}
}
}])
}
})
this.graph.on('cell:mouseleave', ({ cell }) => {
if (cell.shape === 'edge') {
cell.removeTools()
cell.setAttrs({
line: {
stroke: '#275da3',
},
})
cell.zIndex = 1
}
})
// 连线绑定悬浮事件
this.graph.on('cell:mouseenter', ({ cell }) => {
if (cell.shape == 'edge') {
cell.addTools([
{
name: 'button-remove',
args: {
x: '100%',
y: 0,
offset: {
x: 0,
y: 0
},
},
}
])
cell.setAttrs({
line: {
stroke: '#409EFF',
},
})
cell.zIndex = 99
}
})
},
getViweHieth() {
this.h = window.innerHeight - 60
},
registerNode() {
X6.Graph.registerNode(
'custom-rect',
{
inherit: 'rect',
width: 66,
height: 36,
attrs: {
body: {
strokeWidth: 1,
stroke: '#5F95FF',
fill: '#EFF4FF',
},
text: {
fontSize: 12,
fill: '#262626',
},
},
},
true,
)
X6.Graph.registerNode(
'custom-polygon',
{
inherit: 'polygon',
width: 66,
height: 36,
attrs: {
body: {
strokeWidth: 1,
stroke: '#5F95FF',
fill: '#EFF4FF',
},
text: {
fontSize: 12,
fill: '#262626',
},
},
},
true,
)
X6.Graph.registerNode(
'custom-rect',
{
inherit: 'rect',
width: 66,
height: 36,
attrs: {
body: {
strokeWidth: 1,
stroke: '#5F95FF',
fill: '#EFF4FF',
},
text: {
fontSize: 12,
fill: '#262626',
},
},
},
true,
)
X6.Graph.registerNode(
'custom-circle',
{
inherit: 'circle',
width: 45,
height: 45,
attrs: {
body: {
strokeWidth: 1,
stroke: '#5F95FF',
fill: '#EFF4FF',
},
text: {
fontSize: 12,
fill: '#262626',
},
},
},
true,
)
},
handerOk(e) {
this.visible = false
this.graph.drawBackground({
color: e,
})
this.COLOR = e
document.getElementsByClassName('x6-widget-minimap-viewport')[0].style.border = '2px solid ' + e
},
handlerColor() {
this.visible = true
},
dblclick() {
this.graph.on('cell:dblclick', (event) => {
this.actionNode = event.node;
this.actionNode.label = '123'
console.log(event.node.label);
})
},
// 撤销
revocation() {
this.graph.undo()
},
// 快捷键
shortcutKey() {
this.graph.bindKey(['meta+c', 'ctrl+c'], () => {
console.log('cells');
const cells = this.graph.getSelectedCells()
if (cells.length) {
this.graph.copy(cells)
}
return false
})
this.graph.bindKey(['meta+v', 'ctrl+v'], () => {
if (!this.graph.isClipboardEmpty()) {
const cells = this.graph.paste({ offset: 32 })
this.graph.cleanSelection()
this.graph.select(cells)
}
return false
})
this.graph.bindKey(['meta+x', 'ctrl+x'], () => {
const cells = this.graph.getSelectedCells()
if (cells.length) {
this.graph.cut(cells)
}
return false
})
// select all
this.graph.bindKey(['meta+a', 'ctrl+a'], () => {
const nodes = this.graph.getNodes()
if (nodes) {
this.graph.select(nodes)
}
})
this.graph.bindKey('delete', () => {
const cells = this.graph.getSelectedCells()
if (cells.length) {
this.graph.removeCells(cells)
}
})
this.graph.bindKey(['meta+z', 'ctrl+z'], () => {
if (this.graph.history.canUndo()) {
this.graph.history.undo()
}
return false
})
},
printNodeList() {
console.log(JSON.stringify(this.graph.toJSON({ diff: true })));
},
recover() {
let axios = { "cells": [{ "position": { "x": 124, "y": 82 }, "size": { "width": 100, "height": 40 }, "attrs": { "text": { "text": "测试" }, "body": { "strokeWidth": 2, "stroke": "black", "fill": "#fff" }, "label": { "fontSize": 18, "stroke": "black" } }, "shape": "custom-rect", "id": 2, "children": [], "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", "id": "8f529e62-b338-4531-8db5-7fa7d5f6253f" }, { "group": "right", "id": "e60838df-6c5c-4f5d-bed4-849089431b79" }, { "group": "bottom", "id": "162fd067-7f26-4912-a3f1-4eb8371089f6" }, { "group": "left", "id": "3ab8ef93-adce-44b2-b462-6deb738d67bf" }] }, "zIndex": 1 }] }
this.graph.fromJSON(axios)
},
handlerEdit() {
let input = document.createElement('input')
let canvas = document.getElementsByClassName('canvas-card')[0]
let { x, y } = ACTIONNODE.position()
input.className = 'ces'
input.style.border='none'
input.style.position = 'absolute'
input.style.width = '110px'
input.style.height = '50px'
input.style.left = x - 9 + 'px'
input.style.top = y - 9 + 'px'
input.placeholder = ACTIONNODE.label
canvas.appendChild(input)
this.graph.mousewheel.enabled = false
this.graph.panning.enabled = false
input.focus()
input.addEventListener('blur', function (e) {
ACTIONNODE.label = e.target.value || ACTIONNODE.label
canvas.removeChild(input)
})
}
},
});
</script>