Vue 3 + jsPlumb 打造可视化流程图组件(支持图标与颜色配置)

文章目录

一、项目简介

本文基于 Vue 3 + jsPlumb 实现一个完整的流程图组件,具备以下功能:

  • 支持节点拖拽添加
  • 支持节点之间连接与删除
  • 节点支持自定义图标与颜色选择
  • 支持导入导出 JSON 数据
  • 支持视图缩放与自动居中
  • 支持快捷键删除、右键菜单操作
  • 支持自动布局拓展
  • 具备组件封装能力,可复用

二、流程图基础结构搭建

2.1 安装依赖

bash 复制代码
npm install jsplumb

2.2 基础组件结构

javascript 复制代码
<template>
  <div class="flow-container" ref="container">
    <div
      v-for="node in nodes"
      :key="node.id"
      class="flow-node"
      :id="node.id"
      :style="getNodeStyle(node)"
      @contextmenu.prevent="showContextMenu(node, $event)"
    >
      <div class="icon">{{ node.icon }}</div>
      <div class="label">{{ node.label }}</div>
    </div>

    <context-menu
      v-if="context.visible"
      :node="context.node"
      :x="context.x"
      :y="context.y"
      @update="updateNode"
      @delete="deleteNode"
    />
  </div>
</template>

三、节点拖拽与连接逻辑

3.1 拖拽添加节点

javascript 复制代码
function addNode(label = '新节点') {
  const id = 'node-' + Date.now();
  nodes.value.push({
    id,
    label,
    icon: '📦',
    color: '#409EFF',
    left: 200,
    top: 150
  });
  nextTick(() => {
    instance.value.draggable(id, { containment: 'parent' });
    initEndpoints(id);
  });
}

3.2 jsPlumb 初始化与连接规则

javascript 复制代码
onMounted(() => {
  instance.value = jsPlumb.getInstance();

  instance.value.bind('connection', info => {
    connections.value.push({
      source: info.sourceId,
      target: info.targetId
    });
  });

  nodes.value.forEach(node => {
    instance.value.draggable(node.id, { containment: 'parent' });
    initEndpoints(node.id);
  });
});

function initEndpoints(id) {
  instance.value.addEndpoint(id, {
    anchor: 'Right',
    isSource: true,
    maxConnections: -1
  }, commonStyle);

  instance.value.addEndpoint(id, {
    anchor: 'Left',
    isTarget: true,
    maxConnections: -1
  }, commonStyle);
}

四、节点图标与颜色选择功能

4.1 节点数据结构

javascript 复制代码
{
  id: 'node-1',
  label: '开始',
  icon: '🟢',
  color: '#67C23A',
  left: 100,
  top: 100
}

4.2 图标与颜色右键菜单

javascript 复制代码
<!-- ContextMenu.vue -->
<template>
  <div class="context-menu" :style="{ left: x + 'px', top: y + 'px' }">
    <label>名称:
      <input v-model="node.label" />
    </label>
    <label>图标:
      <select v-model="node.icon">
        <option>📦</option>
        <option>🟢</option>
        <option>📄</option>
        <option>🔴</option>
      </select>
    </label>
    <label>颜色:
      <input type="color" v-model="node.color" />
    </label>
    <button @click="$emit('delete', node)">删除节点</button>
  </div>
</template>

4.3 渲染节点样式

javascript 复制代码
function getNodeStyle(node) {
  return {
    left: node.left + 'px',
    top: node.top + 'px',
    backgroundColor: node.color
  };
}

五、节点与连线删除功能

5.1 快捷键删除

javascript 复制代码
window.addEventListener('keydown', e => {
  if (e.key === 'Delete' && selectedNode.value) {
    deleteNode(selectedNode.value);
  }
});

5.2 删除连线

javascript 复制代码
instance.value.bind('click', conn => {
  instance.value.deleteConnection(conn);
  connections.value = connections.value.filter(
    c => !(c.source === conn.sourceId && c.target === conn.targetId)
  );
});

六、导入导出 JSON 数据

6.1 导出结构

javascript 复制代码
function exportFlow() {
  const data = {
    nodes: nodes.value,
    connections: connections.value
  };
  const json = JSON.stringify(data, null, 2);
  console.log(json);
}

6.2 导入结构

javascript 复制代码
function importFlow(json) {
  const data = JSON.parse(json);
  nodes.value = data.nodes;
  connections.value = data.connections;

  nextTick(() => {
    nodes.value.forEach(n => {
      instance.value.draggable(n.id, { containment: 'parent' });
      initEndpoints(n.id);
    });

    connections.value.forEach(conn => {
      instance.value.connect({
        source: conn.source,
        target: conn.target
      });
    });
  });
}

七、视图缩放与自动居中

javascript 复制代码
function zoomView(scale) {
  const container = document.querySelector('.flow-container');
  container.style.transform = `scale(${scale})`;
  container.style.transformOrigin = '0 0';
}

function centerView() {
  const container = document.querySelector('.flow-container');
  container.scrollIntoView({ behavior: 'smooth', block: 'center' });
}

八、自动布局拓展方向

可借助 dagre.js 或 elkjs 实现自动布局:

  • 自动计算节点位置
  • 优化连接路径
  • 支持有向图/流程图布局模式
bash 复制代码
npm install dagre

九、组件封装建议

  • 封装为 <FlowEditor /> 组件
  • 支持 v-model:data 传入节点与连线数据
  • 提供 @save@connect@delete 等事件钩子
  • 可组合使用多个视图与工具栏

到这里,这篇文章就和大家说再见啦!我的主页里还藏着很多 篇 前端 实战干货,感兴趣的话可以点击头像看看,说不定能找到你需要的解决方案~

创作这篇内容花了很多的功夫。如果它帮你解决了问题,或者带来了启发,欢迎:

点个赞❤️ 让更多人看到优质内容

关注「前端极客探险家」🚀 每周解锁新技巧

收藏文章⭐️ 方便随时查阅

📢 特别提醒:

转载请注明原文链接,商业合作请私信联系

感谢你的阅读!我们下篇文章再见~ 💕

相关推荐
疯狂的沙粒14 分钟前
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
前端·uni-app·html
程序员秘密基地15 分钟前
基于vscode,idea,java,html,css,vue,echart,maven,springboot,mysql数据库,在线考试系统
java·vue.js·spring boot·spring·web app
小妖66617 分钟前
html 滚动条滚动过快会留下边框线
前端·html
heroboyluck32 分钟前
Svelte 核心语法详解:Vue/React 开发者如何快速上手?
前端·svelte
海的诗篇_33 分钟前
前端开发面试题总结-JavaScript篇(二)
开发语言·前端·javascript·typescript
琹箐43 分钟前
ant-design4.xx实现数字输入框; 某些输入法数字需要连续输入两次才显示
前端·javascript·anti-design-vue
程序员-小李44 分钟前
VuePress完美整合Toast消息提示
前端·javascript·vue.js
Uyker2 小时前
从零开始制作小程序简单概述
前端·微信小程序·小程序
EndingCoder6 小时前
React从基础入门到高级实战:React 实战项目 - 项目三:实时聊天应用
前端·react.js·架构·前端框架
阿阳微客7 小时前
Steam 搬砖项目深度拆解:从抵触到真香的转型之路
前端·笔记·学习·游戏