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 openLayers實(shí)現(xiàn)圖層數(shù)據(jù)切換與加載流程詳解
OpenLayers是一個(gè)用于開(kāi)發(fā)WebGIS客戶端的JavaScript包,最初基于BSD許可發(fā)行。OpenLayers是一個(gè)開(kāi)源的項(xiàng)目,其設(shè)計(jì)之意是為互聯(lián)網(wǎng)客戶端提供強(qiáng)大的地圖展示功能,包括地圖數(shù)據(jù)顯示與相關(guān)操作,并具有靈活的擴(kuò)展機(jī)制2022-09-09
在Vue3中實(shí)現(xiàn)四種全局狀態(tài)數(shù)據(jù)的統(tǒng)一管理的方法
在開(kāi)發(fā)中,通常遇到四種全局狀態(tài)數(shù)據(jù):異步數(shù)據(jù)、同步數(shù)據(jù),傳統(tǒng)的Vue3使用不同機(jī)制處理這些數(shù)據(jù),而Zova框架通過(guò)Model機(jī)制來(lái)統(tǒng)一管理,簡(jiǎn)化了數(shù)據(jù)處理流程,提高了代碼的可維護(hù)性,本文介紹在Vue3中實(shí)現(xiàn)四種全局狀態(tài)數(shù)據(jù)的統(tǒng)一管理的方法,感興趣的朋友一起看看吧2024-10-10
在Flask項(xiàng)目中集成并訪問(wèn)Vue前端項(xiàng)目的流程步驟
在現(xiàn)代?Web?開(kāi)發(fā)中,前后端分離的架構(gòu)模式越來(lái)越流行,前端通常使用?Vue、React?等框架開(kāi)發(fā),而后端則使用?Flask、Django?等框架提供?API?服務(wù),本文將詳細(xì)介紹如何在一個(gè)?Flask?項(xiàng)目中集成?Vue?前端項(xiàng)目,并確保能夠正確訪問(wèn)和運(yùn)行,需要的朋友可以參考下2025-03-03
Vue中tab欄切換的簡(jiǎn)單實(shí)現(xiàn)
本文主要介紹了Vue中tab欄切換的簡(jiǎn)單實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
element-plus中el-upload組件限制上傳文件類型的方法
?Element Plus 中,el-upload 組件可以通過(guò)設(shè)置 accept 屬性來(lái)限制上傳文件的格式,這篇文章主要介紹了element-plus中el-upload組件限制上傳文件類型,需要的朋友可以參考下2024-02-02
安裝vue3開(kāi)發(fā)者工具但控制臺(tái)沒(méi)有顯示出vue選項(xiàng)的解決
這篇文章主要介紹了安裝vue3開(kāi)發(fā)者工具但控制臺(tái)沒(méi)有顯示出vue選項(xiàng)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
Vue實(shí)現(xiàn)Echarts圖表寬高自適應(yīng)的實(shí)踐
本文主要介紹了Vue實(shí)現(xiàn)Echarts圖表寬高自適應(yīng)的實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11

