// import dagre from '@dagrejs/dagre' import dagre from 'dagre' import { Position, useVueFlow } from '@vue-flow/core' import { ref } from 'vue' /** * Composable to run the layout algorithm on the graph. * It uses the `dagre` library to calculate the layout of the nodes and edges. */ export function useLayout() { const { findNode } = useVueFlow() const graph = ref(new dagre.graphlib.Graph()) const previousDirection = ref('LR') function layout(nodes, edges, direction = 'LR') { // 验证和规范化方向参数 const validDirections = ['TB', 'BT', 'LR', 'RL'] const layoutDirection = validDirections.includes(direction) ? direction : 'LR' // we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there const dagreGraph = new dagre.graphlib.Graph() graph.value = dagreGraph dagreGraph.setDefaultEdgeLabel(() => ({})) // 根据方向判断是否为水平布局 const isHorizontal = layoutDirection === 'LR' || layoutDirection === 'RL' dagreGraph.setGraph({ rankdir: layoutDirection }) previousDirection.value = layoutDirection for (const node of nodes) { // if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type) const graphNode = findNode(node.id) dagreGraph.setNode(node.id, { width: graphNode.dimensions.width || 150, height: graphNode.dimensions.height || 50 }) } for (const edge of edges) { dagreGraph.setEdge(edge.source, edge.target) } dagre.layout(dagreGraph) // set nodes with updated positions return nodes.map((node) => { const nodeWithPosition = dagreGraph.node(node.id) // 根据方向动态计算连接点位置 let targetPosition, sourcePosition switch (layoutDirection) { case 'BT': // 从上到下 (Top to Bottom) targetPosition = Position.Bottom // 目标节点连接点在下方 sourcePosition = Position.Top // 源节点连接点在上方 break case 'TB': // 从下到上 (Bottom to Top) targetPosition = Position.Top // 目标节点连接点在上方 sourcePosition = Position.Bottom // 源节点连接点在下方 break case 'LR': // 从左到右 (Left to Right) targetPosition = Position.Left sourcePosition = Position.Right break case 'RL': // 从右到左 (Right to Left) targetPosition = Position.Right sourcePosition = Position.Left break default: targetPosition = Position.Top sourcePosition = Position.Bottom } return { ...node, targetPosition, sourcePosition, position: { x: nodeWithPosition.x, y: nodeWithPosition.y } } }) } return { graph, layout, previousDirection } } /** * 递归查找指定ID的节点并添加子节点 * @param {Array} items - 要搜索的数组 * @param {string} targetId - 要查找的节点ID * @param {Object} newChild - 要添加的新子节点 * @returns {boolean} 是否成功添加 */ export function findAndAddChild(items, targetId, newChild) { for (let i = 0; i < items.length; i++) { const item = items[i] // 如果找到目标节点 if (item.id === targetId) { // 初始化child数组(如果不存在) if (!item.children) { item.children = [] } // 添加新子节点 item.children.push(newChild) return true } // 递归搜索子节点 if (item.children && item.children.length > 0) { const found = findAndAddChild(item.children, targetId, newChild) if (found) return true } } return false } /** * 递归删除指定ID的节点 * @param {Array} items - 要搜索的数组 * @param {string} targetId - 要删除的节点ID * @returns {boolean} 是否成功删除 */ export function findAndRemoveChild(items, targetId) { for (let i = 0; i < items.length; i++) { const item = items[i] // 如果找到目标节点,从当前数组中删除 if (item.id === targetId) { items.splice(i, 1) return true } // 递归搜索子节点 if (item.children && item.children.length > 0) { const found = findAndRemoveChild(item.children, targetId) if (found) return true } } return false }