vue3+vue-flow制作簡單可拖拽可增刪改流程圖

實現效果

在這里插入圖片描述

實現代碼

準備工作

安裝依賴
npm install @vue-flow/core
npm install @vue-flow/minimap //小地圖
npm install @vue-flow/controls //自帶的縮放、居中、加鎖功能

我這里只用到上述三個,還有其余的可根據實際情況配合官方文檔使用。

npm install @vue-flow/background //背景
npm install @vue-flow/node-toolbar //工具欄
npm install @vue-flow/node-resizer //縮放
創建<初始元素>js文件 initial-elements.js
import { MarkerType } from '@vue-flow/core'export const initialNodes = [{id: '1',position: { x: 100, y: 250 },type: 'custom',data: {value: '需求響應',icon1: false,icon2: false,icon3: false,icon4: false,icon5: false,},},{id: '2',position: { x: 350, y: 250 },type: 'custom',data: {value: '方案制定',icon1: false,icon2: false,icon3: false,icon4: false,icon5: false,},},{id: '3',position: { x: 600, y: 250 },type: 'custom',data: {value: '實施',icon1: false,icon2: false,icon3: false,icon4: false,icon5: false,},},{id: '4',position: { x: 850, y: 250 },type: 'custom',data: {value: '效果驗證',icon1: false,icon2: false,icon3: false,icon4: false,icon5: false,},},
]export const initialEdges = [{ id: 'e1-2', source: '1', target: '2', markerEnd: MarkerType.ArrowClosed, updatable: true, EdgeMarkerType: { strokeWidth: 10 }, style: { stroke: '#999', strokeWidth: 2, strokeLinecap: 'round' } },{ id: 'e2-3', source: '2', target: '3', markerEnd: MarkerType.ArrowClosed, updatable: true, EdgeMarkerType: { strokeWidth: 10 }, style: { stroke: '#999', strokeWidth: 2, strokeLinecap: 'round' } },{ id: 'e3-4', source: '3', target: '4', markerEnd: MarkerType.ArrowClosed, updatable: true, EdgeMarkerType: { strokeWidth: 10 }, style: { stroke: '#999', strokeWidth: 2, strokeLinecap: 'round' } },
]
創建<使用拖拽>js文件 useDnD.js
import { useVueFlow } from '@vue-flow/core'
import { ref, watch } from 'vue'/*** In a real world scenario you'd want to avoid creating refs in a global scope like this as they might not be cleaned up properly.* @type {{draggedType: Ref<string|null>, isDragOver: Ref<boolean>, isDragging: Ref<boolean>}}*/
const state = {/*** The type of the node being dragged.*/draggedType: ref(null),isDragOver: ref(false),isDragging: ref(false),
}export default function useDragAndDrop() {const { draggedType, isDragOver, isDragging } = stateconst { addNodes, screenToFlowCoordinate, onNodesInitialized, updateNode } = useVueFlow()watch(isDragging, (dragging) => {document.body.style.userSelect = dragging ? 'none' : ''})function onDragStart(event, type) {console.log("onDragStart", type);if (event.dataTransfer) {event.dataTransfer.setData('application/vueflow', type)event.dataTransfer.effectAllowed = 'move'}draggedType.value = typeisDragging.value = truedocument.addEventListener('drop', onDragEnd)}/*** Handles the drag over event.** @param {DragEvent} event*/function onDragOver(event) {event.preventDefault()if (draggedType.value) {isDragOver.value = trueif (event.dataTransfer) {event.dataTransfer.dropEffect = 'move'}}}function onDragLeave() {isDragOver.value = false}function onDragEnd() {console.log("onDragEnd");isDragging.value = falseisDragOver.value = falsedraggedType.value = nulldocument.removeEventListener('drop', onDragEnd)}/*** Handles the drop event.** @param {DragEvent} event*/function onDrop(event, node) {const position = screenToFlowCoordinate({x: event.clientX,y: event.clientY,})node.position = position// /**//  * Align node position after drop, so it's centered to the mouse//  *//  * We can hook into events even in a callback, and we can remove the event listener after it's been called.//  */const { off } = onNodesInitialized(() => {updateNode(node.id, (node) => ({position: { x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2 },}))off()})addNodes(node)}return {draggedType,isDragOver,isDragging,onDragStart,onDragLeave,onDragOver,onDrop,}
}
創建<單個流程圖節點>vue文件 ValueNode.vue
<template><div class="nodeItem relative"><!-- @contextmenu="handleRightClick($event, props.id)" --><!-- 開始節點的位置 --><Handle type="source" position="right" /><el-input:id="`${id}-input`"v-model="value"placeholder="點擊添加文字"style="width: 170px; font-size: 14px"type="textarea"autosizemaxlength="20"resize="none"/><!-- 結束節點的位置 --><Handle type="target" position="left" /><el-icon:size="20"class="absolute red pointer"style="right: -10px; top: -10px"@click="handleDel($event, id)"><CircleCloseFilled/></el-icon></div>
</template><script setup>
import { computed } from "vue";
import { Handle, Position, useVueFlow } from "@vue-flow/core";
const { proxy } = getCurrentInstance();// 定義傳遞給父組件的事件
const emit = defineEmits(["updateNodes"]);
const props = defineProps(["id","data","length",
]);const { updateNodeData, removeNodes } = useVueFlow();const value = computed({get: () => props.data.value,set: (value) => {updateNodeData(props.id, { value });emit("updateNodes");},
});function handleDel(event, id) {event.preventDefault();if (props.length == 1) {proxy.$modal.msgWarning("至少保留一個節點");} else {removeNodes([id]);emit("updateNodes");}
}function handleRightClick(event, id) {console.log("右鍵被點擊了");event.preventDefault(); // 阻止默認的右鍵菜單顯示// 在這里可以添加更多邏輯,比如顯示自定義的右鍵菜單等console.log("右鍵被點擊");removeNodes([id]);
}
</script>
<style scoped lang="scss">
.nodeItem {padding: 6px 20px;background: rgba(219, 227, 247, 1);border-radius: 8px;
}
</style>

具體實現


<template><div class="w100 h100 flex1 size-15" @drop="onDrop($event, getNewNode())"><!-- <el-button @click="addNode">add</el-button> --><div class="bg-white h100 pd-16" style="width: 340px"><div class="mb-12 bold">流程圖組件</div><el-buttonstyle="width: 100%; cursor: grab"plain:draggable="true"@dragstart="onDragStart($event, 'custom')">拖轉至畫布</el-button></div><div class="flex-1 h100"><VueFlow:key="key"ref="vueFlowRef":nodes="nodes":edges="edges"auto-connect:default-viewport="{ zoom: 1.0 }":min-zoom="0.2":max-zoom="4"@edge-update="onEdgeUpdate"@connect="onConnect"@edge-update-start="onEdgeUpdateStart"@edge-update-end="onEdgeUpdateEnd"@dragover="onDragOver"@dragleave="onDragLeave"><template #node-custom="props"><ValueNode:id="props.id":data="props.data"@updateNodes="updateNodes":length="nodes.length"/></template><MiniMap /></VueFlow></div></div>
</template><script setup>
import { ref, computed, nextTick, watch } from "vue";
import { VueFlow, useVueFlow, MarkerType } from "@vue-flow/core";
import { initialEdges, initialNodes } from "./initial-elements.js";
import { MiniMap } from "@vue-flow/minimap";
import ValueNode from "./ValueNode.vue";
import "@vue-flow/core/dist/style.css";
import "@vue-flow/core/dist/theme-default.css";const { proxy } = getCurrentInstance();import useDragAndDrop from "./useDnD.js";
import { Sunny } from "@element-plus/icons-vue";const { onDragStart, onDrop, onDragOver, onDragLeave } = useDragAndDrop();
/*** `useVueFlow` provides:* 1. a set of methods to interact with the VueFlow instance (like `fitView`, `setViewport`, `addEdges`, etc)* 2. a set of event-hooks to listen to VueFlow events (like `onInit`, `onNodeDragStop`, `onConnect`, etc)* 3. the internal state of the VueFlow instance (like `nodes`, `edges`, `viewport`, etc)*/
const {onInit,onNodeDragStop,onConnect,addEdges,updateEdge,getNodes,getEdges,
} = useVueFlow();const props = defineProps({nodes: {type: Object,default: initialNodes,},edges: {type: Object,default: initialEdges,},iconShow: {type: Object,default: () => {},},
});
const nodes = ref(null);
const edges = ref(null);const abc = "需求響應";
nodes.value = props.nodes || initialNodes;
edges.value = props.edges || initialEdges;
proxy.$emit("updateList", nodes.value, edges.value);const vueFlowRef = ref(null);
const nodeOptions = ref([]);
nodeOptions.value = handleNodesOption();// 創建一個新的節點對象
function getNewNode() {return {id: new Date().getTime().toString(),type: "custom",data: {value: "",},position: { x: 50, y: 50 },};
}// 更新節點列表
function updateNodes() {nodes.value = getNodes.value;nodeOptions.value = handleNodesOption();proxy.$emit("updateList", getNodes.value, getEdges.value);
}// 處理節點下拉數據
function handleNodesOption() {return nodes.value.filter((item) => (item.data.value ?? "") !== "").map((r) => ({label: r.data.value,value: r.data.value,}));
}/*** onNodeDragStop is called when a node is done being dragged** Node drag events provide you with:* 1. the event object* 2. the nodes array (if multiple nodes are dragged)* 3. the node that initiated the drag* 4. any intersections with other nodes*/
onNodeDragStop(({ event, nodes, node }) => {console.log("Node Drag Stop", { event, nodes, node });
});function onEdgeUpdateStart(edge) {console.log("start update", edge);
}function onEdgeUpdateEnd(edge) {console.log("end update", edge);
}function onEdgeUpdate({ edge, connection }) {console.log("onEdgeUpdate", edge, connection);updateEdge(edge, connection);console.log("onEdgeUpdate", getEdges.value);
}/*** onConnect is called when a new connection is created.** You can add additional properties to your new edge (like a type or label) or block the creation altogether by not calling `addEdges`*/
onConnect((connection) => {console.log("onConnect", connection, [connection]);const newEdges = {...connection,markerEnd: MarkerType.ArrowClosed,updatable: true,style: { stroke: "#999", strokeWidth: 2, strokeLinecap: "round" },};addEdges([newEdges]);console.log("onConnect", getEdges.value);
});watchEffect(() => {nodes.value = props.nodes || initialNodes;edges.value = props.edges || initialEdges;proxy.$emit("updateList", nodes.value, edges.value);
});function multipleChange(keyArr, type) {console.log(keyArr, type);nodes.value.forEach((node) => {let item = keyArr.find((r) => r === node.data.value);node.data[type] = item ? true : false;});console.log(nodes.value);
}
const key = ref(0);
// 重新生成
const init = (nodeArr, edgeArr) => {edges.value = edgeArr || initialEdges;nodes.value = nodeArr || initialNodes;key.value++;
};// 下一步前的校驗
const checkNodesEdges = () => {console.log("checkNodesEdges", getEdges.value);let hasNoTarget = getEdges.value.length < getNodes.value.length - 1;if (hasNoTarget) {proxy.$modal.msgWarning("畫布中存在節點未連線");return false;} else {proxy.$emit("updateList", getNodes.value, getEdges.value);return true;}
};// 使用defineExpose暴露方法給父組件
defineExpose({checkNodesEdges,init,
});
</script><style scoped>
:deep(.vue-flow__handle) {width: 12px !important;height: 12px !important;border: 1px solid #666 !important;background: #fff !important;
}
</style>

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/92466.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/92466.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/92466.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

itextPdf獲取pdf文件寬高不準確

正常情況下我們通過下面方式獲取寬高PdfReader reader new PdfReader(file.getPath()); float width reader.getPageSize(1).getWidth(); float height reader.getPageSize(1).getHeight();但是這樣獲取的寬高是不準確的&#xff0c;永遠都是 寬 > 高&#xff0c;也就是橫…

NodeJs學習日志(2):windows安裝使用node.js 安裝express,suquelize,mysql,nodemon

windows安裝使用node.js 安裝express&#xff0c;suquelize&#xff0c;mysql&#xff0c;nodemon 系統是win10&#xff0c;默認已經安裝好nodejs與npm包名作用expressWeb應用框架suquelize數據庫ORMmysql數據庫nodemon代碼熱重載安裝express 添加express生成器 npm add expres…

VueCropper 圖片裁剪組件在Vue項目中的實踐應用

VueCropper 圖片裁剪組件在Vue項目中的實踐應用 1. 組件介紹 VueCropper 是一個基于 Vue.js 的圖片裁剪組件&#xff0c;它提供了豐富的圖片裁剪功能&#xff0c;包括&#xff1a; 圖片縮放、旋轉、移動固定比例裁剪高質量圖片輸出多種裁剪模式選擇 2. 安裝與引入 首先需要安裝…

給同一個wordpress網站綁定多個域名的實現方法

在WordPress網站上綁定多個域名&#xff0c;可以通過以下幾種方法實現&#xff1a; 1. 修改wp-config.php文件 在wp-config.php文件中&#xff0c;找到define(‘WP_DEBUG’, false);&#xff0c;在其下方添加以下代碼&#xff1a; define(WP_SITEURL, http:// . $_SERVER[HT…

HarmonyOS分布式開發實戰:打造跨設備協同應用

&#x1f4d6; 文章目錄 第一章&#xff1a;HarmonyOS分布式架構揭秘第二章&#xff1a;跨設備協同的核心技術第三章&#xff1a;開發環境搭建與配置第四章&#xff1a;實戰項目&#xff1a;智能家居控制系統第五章&#xff1a;數據同步與狀態管理第六章&#xff1a;性能優化與…

用 Enigma Virtual Box 把 Qt 程序壓成單文件 EXE——從編譯、收集依賴到一鍵封包

關鍵詞&#xff1a;Qt、windeployqt、Enigma Virtual Box、單文件、綠色軟件 為什么要打成單文件&#xff1f; 傳統做法&#xff1a;用 windeployqt 把依賴拷進 release 目錄&#xff0c;發給用戶一個文件夾&#xff0c;文件又多又亂。理想做法&#xff1a;把整個目錄壓成一個…

unity中實現選中人物腳下顯示圓形標識且完美貼合復雜地形(如彈坑) 的效果

要實現人物腳下圓形 完美貼合復雜地形&#xff08;如彈坑&#xff09; 的效果&#xff0c;核心思路是 「動態生成貼合地面的 Mesh」 —— 即根據地面的高度場實時計算環形頂點的 Y 坐標&#xff0c;讓每個頂點都 “貼” 在地面上。核心邏輯&#xff1a;確定環形范圍&#xff1a…

引領GameFi 2.0新范式:D.Plan攜手頂級財經媒體啟動“龍珠創意秀”

在GameFi賽道尋求新突破的今天&#xff0c;一個名為Dragonverse Plan&#xff08;D.Plan&#xff09;的項目正以其獨特的經濟模型和宏大愿景&#xff0c;吸引著整個Web3社區的目光。據悉&#xff0c;D.Plan即將聯合中文區頂級加密媒體金色財經與非小號&#xff08;Feixiaohao&a…

通信算法之307:fpga之時序圖繪制

時序圖繪制軟件 一. 序言 在FPGA設計過程中&#xff0c;經常需要編寫設計文檔&#xff0c;其中&#xff0c;不可缺少的就是波形圖的繪制&#xff0c;可以直接截取Vivado或者Modelsim平臺實際仿真波形&#xff0c;但是往往由于信號雜亂無法凸顯重點。因此&#xff0c;通過相應軟…

計網學習筆記第3章 數據鏈路層(灰灰題庫)

題目 11 單選題 下列說法正確的是______。 A. 路由器具有路由選擇功能&#xff0c;交換機沒有路由選擇功能 B. 三層交換機具有路由選擇功能&#xff0c;二層交換機沒有路由選擇功能 C. 三層交換機適合異構網絡&#xff0c;二層交換機不適合異構網絡 D. 路由器適合異構網絡&…

SQL的LEFT JOIN優化

原sql&#xff0c;一個base表a,LEFT JOIN三個表抽數 SELECT ccu.*, ctr.*, om.*, of.* FROM ods.a ccu LEFT JOIN ods.b ctr ON ccu.coupon_code ctr.coupon_code AND ctr.is_deleted 0 LEFT JOIN ods.c om ON ctr.bill_code om.order_id AND om.deleted 0 LEFT JOIN ods.…

Redis 核心概念、命令詳解與應用實踐:從基礎到分布式集成

目錄 1. 認識 Redis 2. Redis 特性 2.1 操作內存 2.2 速度快 2.3 豐富的功能 2.4 簡單穩定 2.5 客戶端語言多 2.6 持久化 2.7 主從復制 2.8 高可用 和 分布式 2.9 單線程架構 2.9.1 引出單線程模型 2.9.2 單線程快的原因 2.10 Redis 和 MySQL 的特性對比 2.11 R…

【Day 18】Linux-DNS解析

目錄 一、DNS概念 1、概念和作用 2、域名解析類型 3、 軟件與服務 4、DNS核心概念 區域 記錄 5、查詢類型 6、分層結構 二、DNS操作 配置本機為DNS內網解析服務器 &#xff08;1&#xff09;修改主配置文件 &#xff08;2&#xff09;添加區域 正向解析區域&#xff1a; …

Python 中 OpenCV (cv2) 安裝與使用介紹

Python 中 OpenCV (cv2) 安裝與使用詳細指南 OpenCV (Open Source Computer Vision Library) 是計算機視覺領域最流行的庫之一。Python 通過 cv2 模塊提供 OpenCV 的接口。 一、安裝 OpenCV 方法 1&#xff1a;基礎安裝&#xff08;推薦&#xff09; # 安裝核心包&#xff0…

微軟WSUS替代方案

微軟WSUS事件回顧2025年7月10日&#xff0c;微軟最新確認Windows Server Update Services&#xff08;WSUS&#xff09;出現了問題&#xff0c;導致IT管理員無法正常同步和部署Windows更新。WSUS是允許管理員根據策略配置&#xff0c;將更新推送到特定計算機&#xff0c;并優化…

Minio 分布式集群安裝配置

目錄創建 mkdir -p /opt/minio/run && mkdir -p /etc/minio && mkdir -p /indata/disk_0/minio/datarun&#xff1a;啟動腳本及二進制文件目錄/etc/minio&#xff1a;配置文件目錄data&#xff1a;數據存儲目錄下載 minio wget https://dl.min.io/server/minio…

Spring Boot + ShardingSphere 實現分庫分表 + 讀寫分離實戰

&#x1f680; Spring Boot ShardingSphere 實現分庫分表 讀寫分離&#xff08;涵蓋99%真實場景&#xff09; &#x1f3f7;? 標簽&#xff1a;ShardingSphere、分庫分表、讀寫分離、MySQL 主從、Spring Boot 實戰 分庫分表 vs 讀寫分離 vs 主從配置與數據庫高可用架構區別 …

將普通用戶添加到 Docker 用戶組

這樣可以避免每次使用 Docker 命令時都需要 sudo。以下是具體步驟&#xff1a;1. 創建 Docker 用戶組&#xff08;如果尚未存在&#xff09; 默認情況下&#xff0c;安裝 Docker 時會自動創建 docker 用戶組。可以通過以下命令檢查&#xff1a; groupadd docker&#xff08;如果…

Scrapy(一):輕松爬取圖片網站內容?

目錄 一、CrawlSpider 簡介? 二、實戰案例&#xff1a;圖片網站爬取? 三、代碼解析&#xff1a;核心組件詳解? 類定義&#xff1a; 2.核心屬性&#xff1a;? 3.爬取規則&#xff08;Rules&#xff09;&#xff1a;? 4.數據提取方法&#xff08;parse_item&#xff09;…

使用 systemd 的原生功能來實現 Redis 的自動監控和重啟,而不是依賴額外的腳本最佳實踐方案

使用 systemd 的原生功能來實現 Redis 的自動監控和重啟&#xff0c;而不是依賴額外的腳本最佳實踐方案方案 1&#xff1a;配置 systemd 服務文件&#xff08;推薦&#xff09;1. 檢查/創建 Redis 的 systemd 服務文件2. 配置關鍵參數&#xff08;覆蓋配置示例&#xff09;3. 重…