Vue3實(shí)現(xiàn)vueFLow流程組件的詳細(xì)指南
一、前言
使用vueFlow封裝了一個(gè)層級(jí)關(guān)系組件。
二、官網(wǎng)
三、安裝
方式一:papackage.json添加依賴后直接npm install
- @vue-flow/background@^1.3.0
- 組件名稱:背景柵格組件
- 功能:為Vue Flow提供背景支持,通常用于顯示柵格線或背景圖案,以幫助用戶更好地對(duì)齊和布局流程圖中的元素。
- 版本:^1.3.0 表示該組件的版本號(hào)至少為1.3.0,但會(huì)兼容該版本之后的任何更新(遵循語(yǔ)義化版本控制規(guī)則)。
- @vue-flow/controls@^1.1.2
- 組件名稱:控件組件
- 功能:提供用于縮放、平移和旋轉(zhuǎn)流程圖的控制元素。這些控件允許用戶以交互方式調(diào)整流程圖的視角和布局。
- 版本:^1.1.2 表示該組件的版本號(hào)至少為1.1.2,同樣遵循語(yǔ)義化版本控制規(guī)則。
- @vue-flow/core@^1.41.2
- 組件名稱:核心組件
- 功能:Vue Flow的核心功能組件,提供了創(chuàng)建和管理流程圖所需的基礎(chǔ)設(shè)施。這包括節(jié)點(diǎn)、邊(連接線)、事件處理、狀態(tài)管理等核心功能。
- 版本:^1.41.2 表示該組件的版本號(hào)至少為1.41.2,并兼容后續(xù)更新。
- @vue-flow/minimap@^1.5.0
- 組件名稱:縮略圖組件
- 功能:提供一個(gè)縮略圖視圖,用于顯示整個(gè)流程圖的概覽。用戶可以通過(guò)縮略圖快速定位到流程圖中的特定區(qū)域。
- 版本:^1.5.0 表示該組件的版本號(hào)至少為1.5.0,遵循語(yǔ)義化版本控制規(guī)則。
- @vue-flow/node-resizer@^1.4.0
- 組件名稱:節(jié)點(diǎn)調(diào)整大小組件
- 功能:允許用戶通過(guò)拖動(dòng)邊緣來(lái)調(diào)整節(jié)點(diǎn)的大小。這增加了流程圖創(chuàng)建的靈活性和用戶友好性。
- 版本:^1.4.0 表示該組件的版本號(hào)至少為1.4.0,同樣遵循語(yǔ)義化版本控制規(guī)則。
- @vue-flow/node-toolbar@^1.1.0
- 組件名稱:節(jié)點(diǎn)工具欄組件
- 功能:為節(jié)點(diǎn)提供附加的工具欄,通常包含用于編輯、刪除或配置節(jié)點(diǎn)選項(xiàng)的按鈕。這增強(qiáng)了流程圖編輯的交互性和便捷性。
- 版本:^1.1.0 表示該組件的版本號(hào)至少為1.1.0,遵循語(yǔ)義化版本控制規(guī)則。
方式二:npm install @vue-flow
四、引用
1.App.vue文件
<script setup> import { ref, toRef } from 'vue' import { MiniMap } from '@vue-flow/minimap' import { Position, VueFlow } from '@vue-flow/core' import ColorSelectorNode from './ColorSelectorNode.vue' import OutputNode from './OutputNode.vue' import { presets } from './presets.js' const nodes = ref([ { id: '1', type: 'color-selector', data: { color: presets.ayame }, position: { x: 0, y: 50 }, }, { id: '2', type: 'output', position: { x: 350, y: 114 }, targetPosition: Position.Left, }, ]) const edges = ref([ { id: 'e1a-2', source: '1', sourceHandle: 'a', target: '2', animated: true, style: { stroke: presets.ayame, }, }, ]) const colorSelectorData = toRef(() => nodes.value[0].data) // minimap stroke color functions function nodeStroke(n) { switch (n.type) { case 'input': return '#0041d0' case 'color-selector': return n.data.color case 'output': return '#ff0072' default: return '#eee' } } function nodeColor(n) { if (n.type === 'color-selector') { return n.data.color } return '#fff' } </script> <template> <VueFlow v-model:nodes="nodes" :edges="edges" class="custom-node-flow" :class="[colorSelectorData?.isGradient ? 'animated-bg-gradient' : '']" :style="{ backgroundColor: colorSelectorData?.color }" fit-view-on-init > <template #node-color-selector="props"> <ColorSelectorNode :id="props.id" :data="props.data" /> </template> <template #node-output> <OutputNode /> </template> <MiniMap :node-stroke-color="nodeStroke" :node-color="nodeColor" /> </VueFlow> </template>
2.ColorSelectorNode.vue
<script setup> import { Handle, Position, useVueFlow } from '@vue-flow/core' import { colors } from './presets.js' const props = defineProps({ id: { type: String, required: true, }, data: { type: Object, required: true, }, }) const { updateNodeData, getConnectedEdges } = useVueFlow() function onSelect(color) { updateNodeData(props.id, { color, isGradient: false }) const connectedEdges = getConnectedEdges(props.id) for (const edge of connectedEdges) { edge.style = { stroke: color, } } } function onGradient() { updateNodeData(props.id, { isGradient: true }) } </script> <template> <div>Select a color</div> <div class="color-selector nodrag nopan"> <button v-for="{ name: colorName, value: color } of colors" :key="colorName" :title="colorName" :class="{ selected: color === data.color }" :style="{ backgroundColor: color }" type="button" @click="onSelect(color)" /> <button class="animated-bg-gradient" title="gradient" type="button" @click="onGradient" /> </div> <Handle id="a" type="source" :position="Position.Right" /> </template>
3.OutputNode.vue
<script setup> import { Handle, Position, useHandleConnections, useNodesData } from '@vue-flow/core' const connections = useHandleConnections({ type: 'target', }) const nodesData = useNodesData(() => connections.value[0]?.source) </script> <template> <Handle type="target" :position="Position.Left" :style="{ height: '16px', width: '6px', backgroundColor: nodesData.data?.color, filter: 'invert(100%)' }" /> {{ nodesData.data?.isGradient ? 'GRADIENT' : nodesData.data?.color }} </template>
4.presets.js
export const presets = { sumi: '#1C1C1C', gofun: '#FFFFFB', byakuroku: '#A8D8B9', mizu: '#81C7D4', asagi: '#33A6B8', ukon: '#EFBB24', mushikuri: '#D9CD90', hiwa: '#BEC23F', ichigo: '#B5495B', kurenai: '#CB1B45', syojyohi: '#E83015', konjyo: '#113285', fuji: '#8B81C3', ayame: '#6F3381', torinoko: '#DAC9A6', kurotsurubami: '#0B1013', ohni: '#F05E1C', kokikuchinashi: '#FB9966', beniukon: '#E98B2A', sakura: '#FEDFE1', toki: '#EEA9A9', } export const colors = Object.keys(presets).map((color) => { return { name: color, value: presets[color], } })
5.main.css
@import 'https://cdn.jsdelivr.net/npm/@vue-flow/core@1.41.2/dist/style.css'; @import 'https://cdn.jsdelivr.net/npm/@vue-flow/core@1.41.2/dist/theme-default.css'; @import 'https://cdn.jsdelivr.net/npm/@vue-flow/controls@latest/dist/style.css'; @import 'https://cdn.jsdelivr.net/npm/@vue-flow/minimap@latest/dist/style.css'; @import 'https://cdn.jsdelivr.net/npm/@vue-flow/node-resizer@latest/dist/style.css'; html, body, #app { margin: 0; height: 100%; } #app { text-transform: uppercase; font-family: 'JetBrains Mono', monospace; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } .vue-flow__minimap { transform: scale(75%); transform-origin: bottom right; } .vue-flow__edges { filter:invert(100%) } .vue-flow__handle { height:24px; width:8px; border-radius:4px } .vue-flow__node-color-selector { border:1px solid #777; padding:10px; border-radius:10px; background:#f5f5f5; display:flex; flex-direction:column; justify-content:space-between; align-items:center; gap:10px; max-width:250px } .vue-flow__node-color-selector .color-selector { display:flex; flex-direction:row; flex-wrap:wrap; justify-content:center; max-width:90%; margin:auto; gap:4px } .vue-flow__node-color-selector .color-selector button { border:none; cursor:pointer; padding:5px; width:25px; height:25px; border-radius:8px; box-shadow:0 0 10px #0000004d } .vue-flow__node-color-selector .color-selector button:hover { box-shadow:0 0 0 2px #2563eb; transition:box-shadow .2s } .vue-flow__node-color-selector .color-selector button.selected { box-shadow:0 0 0 2px #2563eb } .vue-flow__node-color-selector .vue-flow__handle { background-color:#ec4899; height:24px; width:8px; border-radius:4px } .animated-bg-gradient { background:linear-gradient(122deg,#6f3381,#81c7d4,#fedfe1,#fffffb); background-size:800% 800%; -webkit-animation:gradient 4s ease infinite; -moz-animation:gradient 4s ease infinite; animation:gradient 4s ease infinite } @-webkit-keyframes gradient { 0% { background-position:0% 22% } 50% { background-position:100% 79% } to { background-position:0% 22% } } @-moz-keyframes gradient { 0% { background-position:0% 22% } 50% { background-position:100% 79% } to { background-position:0% 22% } } @keyframes gradient { 0% { background-position:0% 22% } 50% { background-position:100% 79% } to { background-position:0% 22% } }
五、預(yù)覽效果
六、個(gè)人實(shí)現(xiàn)
七、問(wèn)題記錄
vueflow每個(gè)層級(jí)節(jié)點(diǎn)的位置position無(wú)法自動(dòng)生成,所以需要自己進(jìn)行封裝。我是根據(jù)層級(jí)來(lái)進(jìn)行計(jì)算從頂部依次向下布局。
<script setup lang="ts"> import {ref} from "vue"; import {tableDetail} from "./datatable.api"; import Blood from "./compoent/blood.vue" import {MarkerType} from "@vue-flow/core"; import { gettestListIndexByTableName } from "@/views/test/common/test.api"; import {useUserStore} from "@/store/modules/user"; const userStore = useUserStore(); // 詳情抽屜 const drawerDetail = ref({}) const drawerOpenFlag = ref(false) const drawerDetailFields = ref([]) const nodes = ref([]); const edges = ref([]); /** * 查看詳情 * @param record */ const onHandleOpenDrawer = async (record) => { const detail = await tableDetail(record) let fields = [] for (let fieldName of Object.keys(detail.fields) || []) { fields.push(detail.fields[fieldName]) } drawerDetail.value = detail drawerDetailFields.value = fields drawerOpenFlag.value = true nodes.value = [] edges.value = [] // 添加節(jié)點(diǎn) addNode({ id: 'testyuan', type: 'data-source', data: { database: record.database, testyuanTable: record.tableName, fieldArr: fields }, position: { x: 0, y: 90 }, }); addNode({ id: '0', type: 'data-set', data: { database: record.database, testyuanTable: record.tableName, fieldArr: fields }, position: { x: 350, y: 70 }, }); // 添加從到集的邊 addEdge({ id: 'first', source: 'testyuan', target: '0', markerEnd: MarkerType.ArrowClosed }); // 獲取指標(biāo)列表并添加節(jié)點(diǎn)和邊 const tests = await gettestListIndexByTableName({ tableName: record.tableName }); if (tests.length > 0) { for (let i = 0; i < tests.length; i++) { const test = tests[i]; addNode({ id: test.id.toString(), type: 'index-info', position: { x: 0, y: 0 }, data: { indexNameEn: test.indexNameEn, indexNameCn: test.indexNameCn, group: getIndexClass(test.secondlevel), type: gettestType(test.indexType) }, }); addEdge({ id: test.id.toString(), source: test.parentId, target: test.id.toString(), markerEnd: MarkerType.ArrowClosed, }); } // 記錄每個(gè) level 出現(xiàn)的次數(shù) let levelCounts = {}; for (let j=2; j<nodes.value.length; j++) { const node = nodes.value[j]; const level = Number(getNodeLevel(edges.value,node.id.toString())); node.position.x = 350 * level; // 更新 level 出現(xiàn)的次數(shù) if (levelCounts[level]) { levelCounts[level]++; } else { levelCounts[level] = 1; } node.position.y = 100 * levelCounts[level]; } } else { console.warn('No tests found for table:', record.tableName); } } // 封裝獲取當(dāng)前節(jié)點(diǎn)層級(jí)的函數(shù) const getNodeLevel = (edges, nodeId) => { const levelMap = {}; const visited = new Set(); const dfs = (node, level) => { visited.add(node); levelMap[node] = level; // 遍歷邊列表,找到所有從當(dāng)前節(jié)點(diǎn)出發(fā)的邊 edges.forEach((edge) => { if (edge.source === node && !visited.has(edge.target)) { // 遞歸地更新目標(biāo)節(jié)點(diǎn)的下一層級(jí) dfs(edge.target, level + 1); } }); }; // 起始節(jié)點(diǎn)ID為"0" dfs('0', 1); return levelMap[nodeId] || 'Node not found in the graph'; }; // 封裝添加節(jié)點(diǎn)的函數(shù) const addNode = (node) => { nodes.value.push(node); }; // 封裝添加邊的函數(shù) const addEdge = (edge) => { edges.value.push(edge); }; const firstArr = ref([]); const tree = userStore.gettestDictAllTree.find(t => t.dictCode === "first"); firstArr.value = tree ? tree.dictItemList : []; // 獲取指標(biāo)分級(jí)并轉(zhuǎn)成對(duì)象 const firstMap = firstArr.value.reduce((acc, item) => { acc[item.itemValue] = item.itemText; return acc; }, {}); const getIndexClass = (indexClass) => { return firstMap[indexClass] || ''; }; // 獲取指標(biāo)分類并轉(zhuǎn)成對(duì)象 const testTypeMap = userStore.testDictAllTreeObj['testType'].reduce((acc, item) => { acc[item.itemValue] = item.itemText; return acc; }, {}); // 根據(jù)indexType獲取指標(biāo)類型 const gettestType = (indexType) => { return testTypeMap[indexType] || ''; }; </script> <template> <a-drawer v-model:open="drawerOpenFlag" title="詳情" width="700" placement="right" > <div style="font-size: 16px; font-weight: 500; color: rgba(0, 0, 0, 0.88); margin-left: 23px"> 基礎(chǔ)信息 </div> <Blood :nodes="nodes" :edges="edges"></Blood> </a-drawer> </template> <style scoped lang="less"></style>
以上就是Vue3實(shí)現(xiàn)vueFLow流程組件的詳細(xì)指南的詳細(xì)內(nèi)容,更多關(guān)于Vue3 vueFLow流程組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue開(kāi)發(fā)之封裝分頁(yè)組件與使用示例
這篇文章主要介紹了Vue開(kāi)發(fā)之封裝分頁(yè)組件與使用,結(jié)合實(shí)例形式分析了vue.js封裝分頁(yè)組件操作以及使用組件進(jìn)行分頁(yè)的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-04-04VuePress在build打包時(shí)window?document?is?not?defined問(wèn)題解決
這篇文章主要為大家介紹了VuePress在build打包時(shí)window?document?is?not?defined問(wèn)題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07vue-router vuex-oidc動(dòng)態(tài)路由實(shí)例及功能詳解
這篇文章主要為大家介紹了vue-router vuex-oidc動(dòng)態(tài)路由實(shí)例及功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11vue引用CSS樣式實(shí)現(xiàn)手機(jī)充電效果
這篇文章主要介紹了vue引用CSS樣式實(shí)現(xiàn)手機(jī)充電效果,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01Vue+Microapp實(shí)現(xiàn)微前端的示例詳解
這篇文章主要為大家詳細(xì)介紹了如何實(shí)現(xiàn)以vite+vue3+Microapp為主應(yīng)用,以vue2+element為子應(yīng)用的微前端,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)一下吧2023-06-06vue中?根據(jù)判斷條件添加一個(gè)或多個(gè)style及class的寫(xiě)法小結(jié)
這篇文章主要介紹了vue中?根據(jù)判斷條件添加一個(gè)或多個(gè)style及class的寫(xiě)法,文中給大家補(bǔ)充介紹了關(guān)于vue里:class的使用結(jié)合自己的實(shí)現(xiàn)給大家講解,需要的朋友可以參考下2023-03-03vue2 el-checkbox-group復(fù)選框無(wú)法選中問(wèn)題及解決
這篇文章主要介紹了vue2 el-checkbox-group復(fù)選框無(wú)法選中問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05Vue動(dòng)態(tài)設(shè)置img的src不生效的問(wèn)題解決
本文主要介紹了Vue動(dòng)態(tài)設(shè)置img的src不生效的問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01