<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flowchart Editor</title>
<style>
#app {
display: flex;
width: 100%;
height: 100%;
}
.left {
flex: 1;
border: 1px solid #ddd;
padding: 10px;
}
.left-item {
cursor: pointer;
padding: 5px;
border: 1px solid #ddd;
margin-bottom: 5px;
}
.right {
flex: 3;
border: 1px solid #ddd;
}
/* 添加模态框的样式 */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
</style>
</head>
<body>
<div id="app">
<div class="left">
<div class="left-item" v-for="item in tasklist" @click="addNode(item)" @mouseover="handleMouseOver"
@mouseleave="cancelModal">
{{ item.name }}
</div>
</div>
<div class="right" id="graph-container"></div>
<!-- 在 #app 内部添加模态框的 HTML 结构 -->
<div v-if="showModal" class="modal" @click="showModal = false">
<p>模态框内容</p>
<button @click.stop="goToOtherPage">前往其他页面</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script src="https://gw.alipayobjects.com/os/lib/antv/g6/4.3.7/dist/g6.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
tasklist: [
{ id: 1, name: 'StartStartStartStartStart', color: 'orange' },
{ id: 2, name: 'ProcessProcessProcessProcess', color: 'green' },
{ id: 3, name: 'EndEndEndEndEndEndEndEndEnd', color: 'yellow' }
],
graph: null,
selectedNode: null,
nodeArr: [],
nodeRelation: [],
showModal: false,
modalTimeout: null,
_tooltipActive:false
},
mounted() {
this.initGraph();
},
methods: {
handleMouseOver() {
this.modalTimeout = setTimeout(() => {
this.showModal = true;
}, 2000);
},
cancelModal() {
clearTimeout(this.modalTimeout);
},
goToOtherPage() {
location.href = '/paint3'; // 替换为目标页面的 URL
},
initGraph() {
G6.registerBehavior('show-tooltip', {
getEvents() {
return {
'node:mouseenter': 'onMouseEnter',
'node:mouseleave': 'onMouseLeave',
};
},
onMouseEnter(evt) {
const node = evt.item;
const model = node.getModel();
const label = model.label; // 完整的 label 文本
// 创建提示框
const tooltip = document.createElement('div');
tooltip.innerHTML = label;
// tooltip.innerHTML = `<span>${label}</span><button @click="copyText('${label}')">复制</button>`;;
tooltip.style.position = 'absolute';
tooltip.style.backgroundColor = 'white';
tooltip.style.border = '1px solid #e2e2e2';
tooltip.style.padding = '5px';
tooltip.style.borderRadius = '4px';
tooltip.style.pointerEvents = 'auto'; // 确保提示框不会干扰其他交互
// 点击复制文本
tooltip.addEventListener('click', () => {
navigator.clipboard.writeText(label).then(() => {
// alert('复制成功');
tooltip.remove();
this.graph.set('tooltip', null);
}).catch(err => {
console.error('复制失败:', err);
});
});
tooltip.addEventListener('mouseenter', () => {
this._tooltipActive = true;
});
tooltip.addEventListener('mouseleave', () => {
this._tooltipActive = false;
tooltip.remove();
this.graph.set('tooltip', null);
});
// 保存 tooltip 元素到图形实例中,以便稍后移除
this.graph.set('tooltip', tooltip);
document.body.appendChild(tooltip);
// 定位提示框
const canvasPosition = this.graph.getCanvasByPoint(model.x, model.y);
tooltip.style.left = `${canvasPosition.x + 290}px`; //加上左边menu的宽度
tooltip.style.top = `${canvasPosition.y - 30}px`; //减去1.5倍半径
},
onMouseLeave() {
const tooltip = this.graph.get('tooltip');
if (tooltip && !this._tooltipActive) {
tooltip.remove();
this.graph.set('tooltip', null);
}
},
});
G6.registerNode('customCircleNode', {
draw(cfg, group) {
const radius = 40; // 设置圆的半径
// 创建圆形
const circle = group.addShape('circle', {
attrs: {
x: 0, // 圆心坐标
y: 0,
r: radius,
// stroke: '#5B8FF9',
fill: cfg.color, // 使用 cfg.color 设置填充颜色
},
});
// 添加文本
if (cfg.label) {
group.addShape('text', {
attrs: {
x: 0, // 居中
y: 0,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label.length > 10 ? cfg.label.slice(0, 10) + '...' : cfg.label,
fill: '#333',
},
});
}
return circle;
},
}, 'single-shape');
this.graph = new G6.Graph({
container: 'graph-container',
width: 1100,
height: 600,
fitView: true, // 确保图形适应视图
fitViewPadding: [20, 40, 50, 20], // 设置适应视图时的内边距
modes: {
default: ['drag-node', 'drag-canvas', 'zoom-canvas', 'show-tooltip']
},
layout: {
type: 'force', // 使用力导向布局
preventOverlap: true, // 防止节点重叠
linkDistance: 100, // 边的距离
// 可以根据需要配置更多布局参数
},
defaultNode: {
type: 'customCircleNode',
},
defaultEdge: {
style: {
endArrow: {
path: G6.Arrow.triangle(10, 20, 0),
fill: 'green',
offset: -1 // 将箭头向后移动一点,使其更靠近节点
},
lineWidth: 3, // 设置边的宽度
stroke: 'green'
}
},
});
this.graph.on('node:click', evt => {
const clickedNode = evt.item;
if (this.selectedNode && this.selectedNode !== clickedNode) {
const sourceId = this.selectedNode.get('id');
const targetId = clickedNode.get('id');
const sourceLabel = this.selectedNode.getModel().label;
const targetLabel = clickedNode.getModel().label;
// 检查新边是否会形成闭环
if (!this.doesEdgeCreateLoop(sourceId, targetId)) {
this.graph.addItem('edge', {
source: sourceId,
target: targetId,
style: {
endArrow: {
path: G6.Arrow.triangle(10, 20, 5),
fill: 'green'
}
}
});
// 添加边关系到 nodeRelation
this.nodeRelation.push(`${sourceLabel}>>${targetLabel}`);
console.log(this.nodeRelation, 'this.nodeRelation--add')
// 添加node到 nodeArr
if (!this.nodeArr.includes(sourceLabel)) {
this.nodeArr.push(sourceLabel);
}
if (!this.nodeArr.includes(targetLabel)) {
this.nodeArr.push(targetLabel);
}
console.log(this.nodeArr, 'this.nodeArr--add')
}
this.selectedNode = null;
} else {
this.selectedNode = clickedNode;
}
});
this.graph.on('edge:click', (evt) => {
const edge = evt.item;
const sourceLabel = edge.getSource().getModel().label;
const targetLabel = edge.getTarget().getModel().label;
// 从 nodeRelation 中删除边
const edgeIndex = this.nodeRelation.indexOf(`${sourceLabel}>>${targetLabel}`);
if (edgeIndex > -1) {
this.nodeRelation.splice(edgeIndex, 1);
}
// 从 nodeArr 中删除不再参与连线的节点
[sourceLabel, targetLabel].forEach(label => {
if (!this.nodeRelation.some(rel => rel.includes(label))) {
const nodeIndex = this.nodeArr.indexOf(label);
if (nodeIndex > -1) {
this.nodeArr.splice(nodeIndex, 1);
}
}
});
console.log(this.nodeRelation, 'this.nodeRelation--del')
console.log(this.nodeArr, 'this.nodeArr---del')
// 删除边
this.graph.removeItem(edge);
});
},
addNode(item) {
if (this.graph.findById(`node-${item.id}`)) return;
console.log(item.id, 'node-id-------')
this.cancelModal(); // 取消悬停事件的模态框显示
const model = {
id: `node-${item.id}`,
label: item.name,
color: item.color, // 将颜色传递给节点
x: Math.random() * 800,
y: Math.random() * 600,
};
this.graph.addItem('node', model);
},
doesEdgeCreateLoop(sourceId, targetId) {
const visited = new Set();
let hasLoop = false;
const dfs = (nodeId) => {
if (nodeId === sourceId) {
hasLoop = true;
return;
}
if (visited.has(nodeId)) {
return;
}
visited.add(nodeId);
const edges = this.graph.getEdges();
edges.forEach(edge => {
if (edge.getSource().getID() === nodeId) {
const nextNodeId = edge.getTarget().getID();
if (!visited.has(nextNodeId)) {
dfs(nextNodeId);
}
}
});
};
dfs(targetId);
return hasLoop;
}
}
});
</script>
</body>
</html>