🕌
vue-flowを使ってツリー構造を描画する
本業で購買部門にいたころ、企業のレジリエンスを強化するという某スタートアップ企業のサービスを導入しましたが、2年間経ってもロクに動きませんでした。(そんなサービスに2年間も毎月数十万払い続けている弊社も弊社です)
おまけにそこの社長が何言ってもいい加減な対応ばかりで、愛想を尽かして自分でサプライチェーンを管理するシステムを作りました。
私のオリジナルアプリでは、サプライチェーンを図示するためにvue-flowを使っています。
vue-flowを使うと、こんな感じで、サプライチェーンやBOMをツリー構造で可視化できます
※ ReactバージョンのReact-flowもあります。というかReact-flowが本家です。
vue-flowは通常は組織図を表示するためのものなので、上図のようにサプライチェーンやBOMを水平方向に表示させるにはちょっとしたコツが必要です。
vue-flowのインストール
"@vue-flow/background": "^1.2.0",
"@vue-flow/controls": "^1.1.1",
"@vue-flow/core": "^1.30.1",
"@vue-flow/minimap": "^1.4.0",
"@vue-flow/node-toolbar": "^1.1.0",
データ構造
- 以下のように、それぞれのレコードで親子関係を示します。
クリックで展開
interface Construction {
親品目: string
子品目: string
}
const constructions: Construction[] = [
{ 親品目: 'root', 子品目: 'マルチビタミン30日分'},
{ 親品目: 'マルチビタミン30日分',子品目: 'マルチビタミン錠'},
{ 親品目: 'マルチビタミン30日分',子品目: 'アルミ袋'},
{ 親品目: 'ビタミンC', 子品目: 'HPC'},
{ 親品目: 'マルチビタミン30日分',子品目: '外箱'},
{ 親品目: 'マルチビタミン錠', 子品目: '還元麦芽糖水飴'},
{ 親品目: 'マルチビタミン錠', 子品目: 'ビタミンC'},
{ 親品目: 'マルチビタミン錠', 子品目: 'ビタミンB1'},
{ 親品目: 'ビタミンC', 子品目: 'アスコルビン酸'},
{ 親品目: 'ビタミンC', 子品目: 'アスコルビン酸Na'},
]
スクリプト部分
import { h, ref, computed } from 'vue'
import { Background } from '@vue-flow/background'
import { Controls } from '@vue-flow/controls'
import { MiniMap } from '@vue-flow/minimap'
import { VueFlow, useVueFlow, type Node, type Edge, Position } from '@vue-flow/core'
const { onConnect, addEdges } = useVueFlow()
const res = computed(() => {
const _nodes: Node[] = []
const _edges: Edge[] = []
let childCount = 0
function setChildren(parentNode: Node) {
const children: Construction[] = constructions.filter(item => item.親品目 === parentNode.id)
children.forEach((child,i, arr) => {
const newNode: Node = {
id: child.子品目,
type: 'custom',
label: child.子品目,
position: { x: 200, y: (childCount+i)*50 },
targetPosition: Position.Left,
sourcePosition: Position.Right,
parentNode: parentNode.id,
}
_nodes.push(newNode)
_edges.push({
id: child.子品目,
source: parentNode.id,
target: newNode.id,
type: 'step',
})
// 末子まで行ったら、次の階層のノードを作成するために、子の数を足す
if(i===arr.length-1){
childCount += arr.length-1
}
setChildren(newNode)
})
}
const topItem = constructions.find(item => item.親品目 === 'root')
const topNode:Node = {
id: topItem.子品目,
type: 'input',
label: topItem.子品目,
position: { x: 0, y: 0 },
targetPosition: Position.Left,
sourcePosition: Position.Right,
}
_nodes.push(topNode)
setChildren(topNode)
return {nodes:_nodes,edges:_edges}
})
テンプレート部分
<div class="h-[500px]">
<VueFlow
v-model:nodes="res.nodes"
v-model:edges="res.edges"
class="border"
:default-zoom="0.2"
:min-zoom="0.2"
:max-zoom="4"
:snap-to-grid="true"
:snap-grid="[10, 10]"
:fit-view-on-init="true"
>
<Background pattern-color="#aaa" :gap="16" />
<Controls />
</VueFlow>
</div>
Discussion