当前位置: 首页 > news >正文

Vue-Flow绘制流程图(Vue3+ElementPlus+TS)简单案例

本文是vue3+Elementplus+ts框架编写的简单可拖拽绘制案例。

1.效果图:

2.Index.vue主代码:

<script lang="ts" setup>
import { ref, markRaw } from "vue";
import {VueFlow,useVueFlow,MarkerType,type Node,type Edge
} from "@vue-flow/core";
import { Background } from "@vue-flow/background";
import { Controls } from "@vue-flow/controls";
import { MiniMap } from "@vue-flow/minimap";
import "@vue-flow/core/dist/style.css";
import "@vue-flow/core/dist/theme-default.css";
import CustomNode from "./components/CusInfoNode.vue";
import {ElMessageBox,ElNotification,ElButton,ElRow,ElCol,ElScrollbar,ElInput,ElSelect,ElOption
} from "element-plus";const {onInit,onNodeDragStop,onConnect,addEdges,getNodes,getEdges,setEdges,setNodes,screenToFlowCoordinate,onNodesInitialized,updateNode,addNodes
} = useVueFlow();const defaultEdgeOptions = {type: "smoothstep", // 默认边类型animated: true, // 是否启用动画markerEnd: {type: MarkerType.ArrowClosed, // 默认箭头样式color: "black"}
};// 节点
const nodes = ref<Node[]>([{id: "5",type: "input",data: { label: "开始" },position: { x: 235, y: 100 },class: "round-start"},{id: "6",type: "custom", // 使用自定义类型data: { label: "工位:流程1" },position: { x: 200, y: 200 },class: "light"},{id: "7",type: "output",data: { label: "结束" },position: { x: 235, y: 300 },class: "round-stop"}
]);const nodeTypes = ref({custom: markRaw(CustomNode) // 注册自定义节点类型
});// 线
const edges = ref<Edge[]>([{id: "e4-5",type: "straight",source: "5",target: "6",sourceHandle: "top-6",label: "测试1",markerEnd: {type: MarkerType.ArrowClosed, // 使用闭合箭头color: "black"}},{id: "e4-6",type: "straight",source: "6",target: "7",sourceHandle: "bottom-6",label: "测试2",markerEnd: {type: MarkerType.ArrowClosed, // 使用闭合箭头color: "black"}}
]);onInit(vueFlowInstance => {vueFlowInstance.fitView();
});onNodeDragStop(({ event, nodes, node }) => {console.log("Node Drag Stop", { event, nodes, node });
});onConnect(connection => {addEdges(connection);
});const pointsList = ref([{ name: "测试1" }, { name: "测试2" }]);
const updateState = ref("");
const selectedEdge = ref<{id: string;type?: string;label?: string;animated?: boolean;
}>({ id: "", type: undefined, label: undefined, animated: undefined });const onEdgeClick = ({ event, edge }) => {selectedEdge.value = edge; // 选中边updateState.value = "edge";console.log("选中的边:", selectedEdge.value);
};function updateEdge() {// 获取当前所有的边const allEdges = getEdges.value;// 切换边类型:根据当前类型来切换const newType =selectedEdge.value.type === "smoothstep" ? null : "smoothstep";// 更新选中边的类型setEdges([...allEdges.filter(e => e.id !== selectedEdge.value.id), // 移除旧的边{...selectedEdge.value,type: selectedEdge.value.type,label: selectedEdge.value.label} as Edge // 更新边的类型]);
}function removeEdge() {ElMessageBox.confirm("是否要删除该连线?", "删除连线", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning"}).then(() => {const allEdges = getEdges.value;setEdges(allEdges.filter(e => e.id !== selectedEdge.value.id));ElNotification({type: "success",message: "连线删除成功"});updateState.value = null;selectedEdge.value = { id: "", type: undefined, label: undefined };});
}const selectedNode = ref<{id: string;data: { label: string };type: string;position: { x: number; y: number };class: string;
}>({id: "",data: { label: "" },type: "",position: { x: 0, y: 0 },class: ""
});const onNodeClick = ({ event, node }) => {selectedNode.value = node; // 更新选中的节点updateState.value = "node";console.log("选中的节点:", node);
};function removeNode() {ElMessageBox.confirm("是否要删除该点位?", "删除点位", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning"}).then(() => {const allNodes = getNodes.value;setNodes(allNodes.filter(e => e.id !== selectedNode.value.id));const allEdges = getEdges.value;setEdges(allEdges.filter(e =>e.source !== selectedNode.value.id &&e.target !== selectedNode.value.id));ElNotification({type: "success",message: "点位删除成功"});updateState.value = null;selectedNode.value = {id: "",data: { label: "" },type: "",position: { x: 0, y: 0 },class: ""};});
}const dragItem = ref<Node>(null);// 拖拽开始时设置拖拽的元素
function onDragStart(event, state) {dragItem.value = {id: `node-${Date.now()}`, // 动态生成唯一 iddata: {label:state === "开始" ? "开始" : state === "结束" ? "结束" : "工位:" + state},type: state === "开始" ? "input" : state === "结束" ? "output" : "custom",position: { x: event.clientX, y: event.clientY },class:state === "开始"? "round-start": state === "结束"? "round-stop": "light"};
}// 拖拽结束时清除状态
function onDragEnd() {dragItem.value = null;
}// 拖拽目标画布区域时允许放置
function onDragOver(event) {console.log("onDragOver事件:", event);event.preventDefault();
}function onDrop(event) {console.log("onDrop事件:", event);const position = screenToFlowCoordinate({x: event.clientX,y: event.clientY});const newNode = {...dragItem.value,position};const { off } = onNodesInitialized(() => {updateNode(dragItem.value?.id, node => ({position: {x: node.position.x - node.dimensions.width / 2,y: node.position.y - node.dimensions.height / 2}}));off();});// 更新节点数据dragItem.value = null;addNodes(newNode); //这里是画布上增加updateNodeData(newNode); //更新后端数据console.log("新节点:", newNode);console.log("新节点后List", nodes.value);
}const saveFlow = () => {console.log("保存数据nodes:", nodes.value);console.log("保存数据edges", edges.value);
};function updateNodeData(node: Node) {//更新后端数据console.log("更新后端数据:", node);nodes.value.push(node);
}
</script><template><div class="flow-container"><VueFlow:nodes="nodes":edges="edges":default-viewport="{ zoom: 1 }":min-zoom="0.2":max-zoom="4"@node-click="onNodeClick"@edge-click="onEdgeClick"@drop="onDrop"@dragover="onDragOver":node-types="nodeTypes":default-edge-options="defaultEdgeOptions":connect-on-click="true"><Background pattern-color="#aaa" :gap="16" /><MiniMap /></VueFlow><div class="top-container"><Controls class="controls" /><div class="save-btn"><ElButton type="primary" class="mr-2" @click="saveFlow">保存</ElButton></div></div><div class="left-panel"><div class="drag-items"><ElRow :gutter="10"><ElCol :span="12"><divclass="drag-item start-node"draggable="true"@dragstart="onDragStart($event, '开始')"@dragend="onDragEnd"><span>开始</span></div></ElCol><ElCol :span="12"><divclass="drag-item end-node"draggable="true"@dragstart="onDragStart($event, '结束')"@dragend="onDragEnd"><span>结束</span></div></ElCol></ElRow><ElScrollbar height="75%"><divclass="drag-item custom-node"draggable="true"@dragstart="onDragStart($event, item.name)"@dragend="onDragEnd"v-for="(item, index) in pointsList":key="index"><span>{{ item.name }}</span></div></ElScrollbar></div></div><div class="right-panel" v-if="updateState"><div class="panel-header"><span>{{updateState === "edge" ? "连接线规则配置" : "点位规则配置"}}</span><ElButton circle class="close-btn" @click="updateState = ''">×</ElButton></div><div class="panel-content" v-if="updateState === 'edge'"><ElInput v-model="selectedEdge.label" placeholder="线名称" clearable /><ElSelect v-model="selectedEdge.type" placeholder="线类型"><ElOption label="折线" value="smoothstep" /><ElOption label="曲线" value="default" /><ElOption label="直线" value="straight" /></ElSelect><ElSelect v-model="selectedEdge.animated" placeholder="线动画"><ElOption label="开启" :value="true" /><ElOption label="关闭" :value="false" /></ElSelect><ElButton type="primary" @click="updateEdge">修改</ElButton><ElButton type="danger" @click="removeEdge">删除</ElButton></div><div class="panel-content" v-else><ElInputv-model="selectedNode.data.label"placeholder="点位名称"clearable/><ElButton type="danger" @click="removeNode">删除</ElButton></div></div></div>
</template><style scoped>
.flow-container {position: relative;height: 100vh;
}.top-container {position: absolute;top: 0;width: 100%;display: flex;justify-content: space-between;padding: 10px;border-bottom: 1px solid #e4e7ed;
}.left-panel {position: absolute;left: 0;top: 120px;width: 200px;padding: 10px;background: rgba(245, 247, 250, 0.9);border-right: 1px solid #e4e7ed;
}.right-panel {position: absolute;right: 0;top: 60px;width: 200px;padding: 10px;background: rgba(245, 247, 250, 0.9);border-left: 1px solid #e4e7ed;
}.drag-item {padding: 8px;margin: 5px 0;border-radius: 4px;text-align: center;cursor: move;
}.start-node {background-color: rgba(103, 194, 58, 0.8);color: white;
}.end-node {background-color: rgba(245, 108, 108, 0.8);color: white;
}.custom-node {background-color: rgba(64, 158, 255, 0.8);color: white;
}.panel-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 15px;
}.panel-content {display: grid;gap: 10px;
}.controls {position: relative;top: -4px;left: -10px;
}
</style>

3. CusInfoNode.vue自定义客户Node

<script setup lang="ts">
import { defineProps } from "vue";
import { Handle, Position } from "@vue-flow/core";defineProps({id: String,data: Object
});
</script><template><div class="custom-node"><div class="node-header">{{ data.label }}</div><!-- Handle 定义 --><Handletype="source":position="Position.Top":id="'top-' + id":style="{ background: '#4a5568' }"/><Handletype="source":position="Position.Left":id="'left-' + id":style="{ background: '#4a5568' }"/><Handletype="source":position="Position.Right":id="'right-' + id":style="{ background: '#4a5568' }"/><Handletype="source":position="Position.Bottom":id="'bottom-' + id":style="{ background: '#4a5568' }"/></div>
</template><style scoped>
.custom-node {width: 120px;height: 40px;border-radius: 3px;background-color: #4a5568;color: white;position: relative;display: flex;justify-content: center;align-items: center;
}.node-header {font-size: 14px;font-weight: bold;
}
</style>

相关文章:

Vue-Flow绘制流程图(Vue3+ElementPlus+TS)简单案例

本文是vue3Elementplusts框架编写的简单可拖拽绘制案例。 1.效果图&#xff1a; 2.Index.vue主代码&#xff1a; <script lang"ts" setup> import { ref, markRaw } from "vue"; import {VueFlow,useVueFlow,MarkerType,type Node,type Edge } fro…...

CNN:卷积网络中设计1×1夹在主要卷积核如3×3前后的作用

话不多说直接上图举例&#xff1a; 像在 ResNet 的 Bottleneck 结构 中&#xff0c;1x1 卷积 被放置在 3x3 卷积 的前后&#xff0c;这种设计有以下几个关键作用和优势&#xff1a; 1. 降低计算复杂度 问题&#xff1a;直接使用 3x3 卷积计算量较大&#xff0c;尤其是当输入和…...

esp8266 rtos sdk开发环境搭建

1. 安装必要的工具 1.1 安装 Git Git 用于从远程仓库克隆代码&#xff0c;你可以从Git 官方网站下载 Windows 版本的安装程序。安装过程中可保持默认设置&#xff0c;安装完成后&#xff0c;在命令提示符&#xff08;CMD&#xff09;或 PowerShell 中输入git --version&#…...

【深度学习】矩阵的核心问题解析

一、基础问题 1. 如何实现两个矩阵的乘法&#xff1f; 问题描述&#xff1a;给定两个矩阵 A A A和 B B B&#xff0c;编写代码实现矩阵乘法。 解法&#xff1a; 使用三重循环实现标准矩阵乘法。 或者使用 NumPy 的 dot 方法进行高效计算。 def matrix_multiply(A, B):m, n …...

DeepSeek模型昇腾部署优秀实践

2024年12月26日&#xff0c;DeepSeek-V3横空出世&#xff0c;以其卓越性能备受瞩目。该模型发布即支持昇腾&#xff0c;用户可在昇腾硬件和MindIE推理引擎上实现高效推理&#xff0c;但在实际操作中&#xff0c;部署流程与常见问题困扰着不少开发者。本文将为你详细阐述昇腾 De…...

从 Spring Boot 2 升级到 Spring Boot 3 的终极指南

一、升级前的核心准备 1. JDK 版本升级 Spring Boot 3 强制要求 Java 17 及以上版本。若当前项目使用 Java 8 或 11&#xff0c;需按以下步骤操作&#xff1a; 安装 JDK 17&#xff1a;从 Oracle 或 OpenJDK 官网下载&#xff0c;配置环境变量&#xff08;如 JAVA_HOME&…...

mysql架构查询执行流程(图解+描述)

目录 mysql架构查询执行流程 图解 描述 mysql架构查询执行流程 图解 描述 用户连接到数据库后&#xff0c;由连接器处理 连接器负责跟客户端建立连接、获取权限、维持和管理连接 客户端发送一条查询给服务器 服务器先检查查询缓存&#xff0c;如果命中缓存&#xff0c;则立…...

20分钟 Bash 上手指南

文章目录 bash 概念与学习目的第一个 bash 脚本bash 语法变量的使用位置参数管道符号&#xff08;过滤条件&#xff09;重定向符号条件测试命令条件语句case 条件分支Arrayfor 循环函数exit 关键字 bash 脚本记录历史命令查询文件分发内容 bash 概念与学习目的 bash&#xff0…...

事故02分析报告:慢查询+逻辑耦合导致订单无法生成

一、事故背景与现象 时间范围 2022年2月3日 18:11~18:43&#xff08;历时32分钟&#xff09; 受影响系统 系统名称角色影响范围dc3订单数据库主库订单生成、事务回滚dc4订单数据库从库数据同步、容灾切换 业务影响 核心业务&#xff1a;手机点餐、C扫B支付订单无法推送至…...

vant2 vue2 两个输入框联动验证遇到的问题

需求是两个输入框&#xff0c;一个输上限A&#xff0c;一个输下限B <van-fieldv-model"formData.upperLimit"name"upperLimit"type"number"label"上限"required:formatter"formatter"/><van-fieldv-model"for…...

硬件工程师入门教程

1.欧姆定律 测电压并联使用万用表测电流串联使用万用表&#xff0c;红入黑出 2.电阻的阻值识别 直插电阻 贴片电阻 3.电阻的功率 4.电阻的限流作用 限流电阻阻值的计算 单位换算关系 5.电阻的分流功能 6.电阻的分压功能 7.电容 电容简单来说是两块不连通的导体加上中间的绝…...

如何使用Docker搭建哪吒监控面板程序

哪吒监控(Nezha Monitoring)是一款自托管、轻量级的服务器和网站监控及运维工具,旨在为用户提供实时性能监控、故障告警及自动化运维能力。 文档地址:https://nezha.wiki/ 本章教程,使用Docker方式安装哪吒监控面板,在此之前,你需要提前安装好Docker. 我当前使用的操作系…...

python-leetcode 45.二叉树转换为链表

题目&#xff1a; 给定二叉树的根节点root,请将它展开为一个单链表&#xff1a; 展开后的单链表应该使用同样的TreeNode,其中right子指针指向链表中的下一个节点&#xff0c;而左子指针始终为空 展开后的单链表应该与二叉树先序遍历顺序相同 方法一&#xff1a;二叉树的前序…...

uni小程序wx.switchTab有时候跳转错误tab问题,解决办法

在一个子页面里面使用uni.switchTab或者wx.switchTab跳转到tab菜单的时候&#xff0c;先发送了一个请求&#xff0c;然后执行跳转到tab菜单&#xff0c;但是这个时候&#xff0c;出错了........也是非常的奇怪&#xff0c;不加请求就没问题......但是业务逻辑就是要先执行某个请…...

【一起学Rust | 框架篇 | Tauri2.0框架】在Tauri应用中设置Http头(Headers)

文章目录 前言一、配置准备1. 检查版本2. 使用条件3. 支持的请求头&#xff08;并不是全部支持&#xff09; 二、使用步骤1. 如何配置header2. 框架集成1. 对于Vite系列、Nuxt、Next.js这种前端框架Vite系列框架Angular系列框架Nuxt系列框架Next.js系列框架 2. 对于Yew和Leptos…...

STM32G473VET6 在 Keil MDK 下手动移植 FreeRTOS 指南

下面将详细介绍如何在 Keil MDK 环境下将 FreeRTOS 手动移植到 STM32G473VET6 微控制器上。内容涵盖工程创建、获取源码、文件组织、移植层适配、测试任务编写以及编译调试等步骤。 1. 工程搭建&#xff08;Keil 项目创建&#xff09; 创建基础工程&#xff1a;首先准备一个基…...

波导阵列天线 学习笔记11双极化全金属垂直公共馈电平板波导槽阵列天线

摘要&#xff1a; 本communicaition提出了一种双极化全金属垂直公共馈电平板波导槽阵列天线。最初提出了一种公共馈电的单层槽平板波导来实现双极化阵列。此设计消除了传统背腔公共馈电的复杂腔体边缘的必要性&#xff0c;提供了一种更简单的天线结构。在2x2子阵列种发展了宽十…...

DeepSeek-R1自写CUDA内核跑分屠榜:开启GPU编程自动化新时代

引言 在AI领域&#xff0c;深度学习模型的性能优化一直是研究者们关注的核心。最近&#xff0c;斯坦福和普林斯顿的研究团队发现&#xff0c;DeepSeek-R1生成的自定义CUDA内核不仅超越了OpenAI的o1和Claude 3.5 Sonnet&#xff0c;还在KernelBench框架中取得了总排名第一的好成…...

001 Kafka入门及安装

Kafka入门及安装 文章目录 Kafka入门及安装1.介绍Kafka的基本概念和核心组件 2.安装1.docker快速安装zookeeper安装kafka安装 添加topic删除topickafka-ui安装 2.Docker安装&#xff08;SASL/PLAIN认证配置-用户名密码&#xff09; 来源参考的deepseek&#xff0c;如有侵权联系…...

2024 年出现的 11 大数据收集趋势

数据收集趋势的出现是对技术进步、企业需求和市场波动的回应&#xff0c;我们对 2025 年的预测涵盖了所有方面。物联网和人工智能等前沿技术将改变组织收集和处理数据的方式&#xff0c;法规将促使它们更加细致地对待数据&#xff0c;而消费者对增强现实和虚拟现实的兴趣将为数…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

Yolov8 目标检测蒸馏学习记录

yolov8系列模型蒸馏基本流程&#xff0c;代码下载&#xff1a;这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中&#xff0c;**知识蒸馏&#xff08;Knowledge Distillation&#xff09;**被广泛应用&#xff0c;作为提升模型…...

嵌入式常见 CPU 架构

架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集&#xff0c;单周期执行&#xff1b;低功耗、CIP 独立外设&#xff1b;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel&#xff08;原始…...

Kafka主题运维全指南:从基础配置到故障处理

#作者&#xff1a;张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1&#xff1a;主题删除失败。常见错误2&#xff1a;__consumer_offsets占用太多的磁盘。 主题日常管理 …...

前端中slice和splic的区别

1. slice slice 用于从数组中提取一部分元素&#xff0c;返回一个新的数组。 特点&#xff1a; 不修改原数组&#xff1a;slice 不会改变原数组&#xff0c;而是返回一个新的数组。提取数组的部分&#xff1a;slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...