chore: 更新依赖版本并添加uuid库
更新了esbuild、rollup、vite及其相关插件的版本,并在package.json中添加了uuid库。同时,新增了多个API视图文件和相关逻辑处理文件。
Showing
10 changed files
with
706 additions
and
3 deletions
| ... | @@ -20,6 +20,7 @@ | ... | @@ -20,6 +20,7 @@ |
| 20 | "lodash-es": "^4.17.21", | 20 | "lodash-es": "^4.17.21", |
| 21 | "postcss": "^8.5.3", | 21 | "postcss": "^8.5.3", |
| 22 | "tailwindcss": "^4.0.12", | 22 | "tailwindcss": "^4.0.12", |
| 23 | + "uuid": "^11.1.0", | ||
| 23 | "vue": "^3.5.13", | 24 | "vue": "^3.5.13", |
| 24 | "vue-router": "^4.5.0" | 25 | "vue-router": "^4.5.0" |
| 25 | }, | 26 | }, | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2025-03-10 13:07:05 | 2 | * @Date: 2025-03-10 13:07:05 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-03-12 23:35:54 | 4 | + * @LastEditTime: 2025-03-17 15:19:41 |
| 5 | * @FilePath: /logic-flow2/src/main.js | 5 | * @FilePath: /logic-flow2/src/main.js |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| ... | @@ -16,13 +16,14 @@ import ElementPlus from 'element-plus' | ... | @@ -16,13 +16,14 @@ import ElementPlus from 'element-plus' |
| 16 | import 'element-plus/dist/index.css' | 16 | import 'element-plus/dist/index.css' |
| 17 | 17 | ||
| 18 | import LogicFlow from '@logicflow/core'; | 18 | import LogicFlow from '@logicflow/core'; |
| 19 | -import { Menu, DndPanel, SelectionSelect, Control, InsertNodeInPolyline, Highlight } from "@logicflow/extension"; | 19 | +import { Menu, DndPanel, SelectionSelect, Control, InsertNodeInPolyline, Highlight, Label } from "@logicflow/extension"; |
| 20 | LogicFlow.use(Menu) // 右键菜单 | 20 | LogicFlow.use(Menu) // 右键菜单 |
| 21 | LogicFlow.use(DndPanel) // 拖拽面板 | 21 | LogicFlow.use(DndPanel) // 拖拽面板 |
| 22 | LogicFlow.use(SelectionSelect) // 选中元素 | 22 | LogicFlow.use(SelectionSelect) // 选中元素 |
| 23 | // LogicFlow.use(Control) // 控制面板 | 23 | // LogicFlow.use(Control) // 控制面板 |
| 24 | LogicFlow.use(InsertNodeInPolyline) // 边上插入节点 | 24 | LogicFlow.use(InsertNodeInPolyline) // 边上插入节点 |
| 25 | // LogicFlow.use(Highlight) // 高亮 | 25 | // LogicFlow.use(Highlight) // 高亮 |
| 26 | +// LogicFlow.use(Label) // Label标签 | ||
| 26 | 27 | ||
| 27 | const app = createApp(App) | 28 | const app = createApp(App) |
| 28 | app.use(ElementPlus) | 29 | app.use(ElementPlus) | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2025-03-13 18:34:16 | 2 | * @Date: 2025-03-13 18:34:16 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-03-16 00:21:53 | 4 | + * @LastEditTime: 2025-03-19 00:34:12 |
| 5 | * @FilePath: /logic-flow2/src/router/index.js | 5 | * @FilePath: /logic-flow2/src/router/index.js |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| ... | @@ -125,6 +125,26 @@ const router = createRouter({ | ... | @@ -125,6 +125,26 @@ const router = createRouter({ |
| 125 | name: 'api-graphModel', | 125 | name: 'api-graphModel', |
| 126 | component: () => import('../views/api/graphModel.vue') | 126 | component: () => import('../views/api/graphModel.vue') |
| 127 | }, | 127 | }, |
| 128 | + { | ||
| 129 | + path: '/api-nodeModel', | ||
| 130 | + name: 'api-nodeModel', | ||
| 131 | + component: () => import('../views/api/nodeModel.vue') | ||
| 132 | + }, | ||
| 133 | + { | ||
| 134 | + path: '/api-scalable-node', | ||
| 135 | + name: 'api-scalable-node', | ||
| 136 | + component: () => import('../views/api/scalable-node.vue') | ||
| 137 | + }, | ||
| 138 | + { | ||
| 139 | + path: '/api-edge-model', | ||
| 140 | + name: 'api-edge-model', | ||
| 141 | + component: () => import('../views/api/edgeModel.vue') | ||
| 142 | + }, | ||
| 143 | + { | ||
| 144 | + path: '/api-transform-model', | ||
| 145 | + name: 'api-transform-model', | ||
| 146 | + component: () => import('../views/api/transformModel.vue') | ||
| 147 | + }, | ||
| 128 | ] | 148 | ] |
| 129 | }) | 149 | }) |
| 130 | 150 | ... | ... |
src/views/api/draggable-text-node.js
0 → 100644
| 1 | +/* | ||
| 2 | + * @Date: 2025-03-17 15:40:00 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-18 17:17:47 | ||
| 5 | + * @FilePath: /logic-flow2/src/views/api/draggable-text-node.js | ||
| 6 | + * @Description: 可拖动文本的矩形节点 | ||
| 7 | + */ | ||
| 8 | +import { RectNode, RectNodeModel } from "@logicflow/core"; | ||
| 9 | +import { v4 as uuidv4 } from "uuid"; | ||
| 10 | + | ||
| 11 | +const useNodeBehavior = () => { | ||
| 12 | + const isTextDraggable = ref(true); | ||
| 13 | + const isTextEditable = ref(false); | ||
| 14 | + | ||
| 15 | + const behavior = { | ||
| 16 | + nodeStyle: ref({ | ||
| 17 | + stroke: '#1E90FF', | ||
| 18 | + fill: '#F0F8FF', | ||
| 19 | + strokeWidth: 1 | ||
| 20 | + }), | ||
| 21 | + selectedStyle: ref({ | ||
| 22 | + stroke: '#ff7f0e', | ||
| 23 | + strokeWidth: 2 | ||
| 24 | + }), | ||
| 25 | + textStyle: ref({ | ||
| 26 | + cursor: 'move' | ||
| 27 | + }) | ||
| 28 | + }; | ||
| 29 | + | ||
| 30 | + return { | ||
| 31 | + isTextDraggable, | ||
| 32 | + isTextEditable, | ||
| 33 | + behavior | ||
| 34 | + }; | ||
| 35 | +}; | ||
| 36 | + | ||
| 37 | +class DraggableTextNodeModel extends RectNodeModel { | ||
| 38 | + initNodeData(data) { | ||
| 39 | + // 确保 data.x 和 data.y 有默认值 | ||
| 40 | + const nodeX = data.x || 0; | ||
| 41 | + const nodeY = data.y || 0; | ||
| 42 | + | ||
| 43 | + // 处理文本数据 | ||
| 44 | + if (!data.text || typeof data.text === "string") { | ||
| 45 | + data.text = { | ||
| 46 | + value: data.text || "", | ||
| 47 | + x: nodeX, | ||
| 48 | + y: nodeY + 80, | ||
| 49 | + }; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + super.initNodeData(data); | ||
| 53 | + | ||
| 54 | + const { behavior } = useNodeBehavior(); | ||
| 55 | + this.behavior = behavior; | ||
| 56 | + | ||
| 57 | + this.text.draggable = true; | ||
| 58 | + this.text.editable = false; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + getNodeStyle() { | ||
| 62 | + /** | ||
| 63 | + * - 合并基础样式 | ||
| 64 | + * - 根据选中状态添加额外样式 | ||
| 65 | + */ | ||
| 66 | + return { | ||
| 67 | + ...this.behavior.nodeStyle.value, | ||
| 68 | + ...(this.isSelected ? this.behavior.selectedStyle.value : {}) | ||
| 69 | + }; | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + getTextStyle() { | ||
| 73 | + /** | ||
| 74 | + * - 扩展原有文本样式 | ||
| 75 | + * - 添加自定义样式(如移动光标) | ||
| 76 | + */ | ||
| 77 | + const style = super.getTextStyle(); | ||
| 78 | + return { | ||
| 79 | + ...style, | ||
| 80 | + ...this.behavior.textStyle.value | ||
| 81 | + }; | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + getAnchorStyle() { | ||
| 85 | + const style = super.getAnchorStyle(); | ||
| 86 | + style.stroke = "rgb(24, 125, 255)"; | ||
| 87 | + style.r = 3; | ||
| 88 | + style.hover.r = 8; | ||
| 89 | + style.hover.fill = "rgb(24, 125, 255)"; | ||
| 90 | + style.hover.stroke = "rgb(24, 125, 255)"; | ||
| 91 | + return style; | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + getAnchorLineStyle() { | ||
| 95 | + const style = super.getAnchorLineStyle(); | ||
| 96 | + style.stroke = "rgb(24, 125, 255)"; | ||
| 97 | + return style; | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + getOutlineStyle() { | ||
| 101 | + const style = super.getOutlineStyle(); | ||
| 102 | + style.stroke = "red"; | ||
| 103 | + style.hover.stroke = "yellow"; | ||
| 104 | + return style; | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + createId() { | ||
| 108 | + return `custom-rect-${uuidv4()}`; | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + // 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。 | ||
| 112 | + getDefaultAnchor() { | ||
| 113 | + const { width, height, x, y, id } = this; | ||
| 114 | + return [ | ||
| 115 | + { | ||
| 116 | + x: x - width / 2, | ||
| 117 | + y, | ||
| 118 | + name: 'left', | ||
| 119 | + id: `${id}_0` | ||
| 120 | + }, | ||
| 121 | + { | ||
| 122 | + x: x + width / 2, | ||
| 123 | + y, | ||
| 124 | + name: 'right', | ||
| 125 | + id: `${id}_1`, | ||
| 126 | + // edgeAddable: false | ||
| 127 | + }, | ||
| 128 | + ] | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + // getConnectedSourceRules(){ | ||
| 132 | + // const rules = super.getConnectedSourceRules(); | ||
| 133 | + // const getWayOnlyAsTarget = { | ||
| 134 | + // message: "结束节点只能连入,不能连出!", | ||
| 135 | + // validate: ( source, target, sourceAnchor, targetAnchor ) => { | ||
| 136 | + // let isValid = true; | ||
| 137 | + // if (source) { | ||
| 138 | + // isValid = false; | ||
| 139 | + // } | ||
| 140 | + // return isValid; | ||
| 141 | + // }, | ||
| 142 | + // }; | ||
| 143 | + | ||
| 144 | + // rules.push(getWayOnlyAsTarget); | ||
| 145 | + // return rules; | ||
| 146 | + // } | ||
| 147 | + | ||
| 148 | + // getConnectedTargetRules() { | ||
| 149 | + // const rules = super.getConnectedTargetRules(); | ||
| 150 | + // const notAsTarget = { | ||
| 151 | + // message: "起始节点不能作为边的终点", | ||
| 152 | + // validate: () => false, | ||
| 153 | + // }; | ||
| 154 | + | ||
| 155 | + // rules.push(notAsTarget); | ||
| 156 | + // return rules; | ||
| 157 | + // } | ||
| 158 | + | ||
| 159 | + isAllowMoveNode(deltaX, deltaY) { | ||
| 160 | + let newX = this.x + deltaX | ||
| 161 | + let newY = this.y + deltaY | ||
| 162 | + let isAllowMoveX = true | ||
| 163 | + let isAllowMoveY = true | ||
| 164 | + // 处理 | ||
| 165 | + return { | ||
| 166 | + x: isAllowMoveX, | ||
| 167 | + y: isAllowMoveY, | ||
| 168 | + } | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + // isAllowConnectedAsSource(target, sourceAnchor, targetAnchor) { | ||
| 172 | + // // 根据节点类型判断是否允许连线 | ||
| 173 | + // if (this.properties.nodeType === 'source') { | ||
| 174 | + // if (target.properties.nodeType === 'target') { | ||
| 175 | + // return false | ||
| 176 | + // } | ||
| 177 | + // return true | ||
| 178 | + // } | ||
| 179 | + // } | ||
| 180 | + | ||
| 181 | + // isAllowConnectedAsTarget(source, sourceAnchor, targetAnchor) { | ||
| 182 | + // // 根据节点类型判断是否允许连线 | ||
| 183 | + // if (this.properties.nodeType ==='target') { | ||
| 184 | + // if (source.properties.nodeType === 'source') { | ||
| 185 | + // return false | ||
| 186 | + // } | ||
| 187 | + // return true | ||
| 188 | + // } | ||
| 189 | + // } | ||
| 190 | +} | ||
| 191 | + | ||
| 192 | +class DraggableTextNode extends RectNode {} | ||
| 193 | + | ||
| 194 | +export default { | ||
| 195 | + type: "custom-rect", | ||
| 196 | + view: DraggableTextNode, | ||
| 197 | + model: DraggableTextNodeModel, | ||
| 198 | +}; |
src/views/api/edgeModel.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-10 16:52:35 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-18 23:51:14 | ||
| 5 | + * @FilePath: /logic-flow2/src/views/api/edgeModel.vue | ||
| 6 | + * @Description: 拖拽面板 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <div class="container"> | ||
| 10 | + <div ref="container" class="flow-container"></div> | ||
| 11 | + </div> | ||
| 12 | +</template> | ||
| 13 | + | ||
| 14 | +<script setup> | ||
| 15 | +import LogicFlow from '@logicflow/core'; | ||
| 16 | +import { PolylineEdge, PolylineEdgeModel } from '@logicflow/core'; | ||
| 17 | + | ||
| 18 | +class CustomEdgeModel extends PolylineEdgeModel { | ||
| 19 | + getEdgeStyle() { | ||
| 20 | + const style = super.getEdgeStyle(); | ||
| 21 | + style.stroke = "blue"; | ||
| 22 | + style.strokeDasharray = "3 3"; | ||
| 23 | + return style; | ||
| 24 | + } | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +const container = ref(null); | ||
| 28 | +let lf = null; | ||
| 29 | + | ||
| 30 | +onMounted(() => { | ||
| 31 | + lf = new LogicFlow({ | ||
| 32 | + container: container.value, | ||
| 33 | + grid: true, | ||
| 34 | + }); | ||
| 35 | + | ||
| 36 | + // 注册自定义边 | ||
| 37 | + lf.register({ | ||
| 38 | + type: 'custom-edge', | ||
| 39 | + view: PolylineEdge, | ||
| 40 | + model: CustomEdgeModel | ||
| 41 | + }); | ||
| 42 | + | ||
| 43 | + lf.setDefaultEdgeType('custom-edge'); | ||
| 44 | + | ||
| 45 | + lf.render({ | ||
| 46 | + nodes: [ | ||
| 47 | + { id: 'node1', type: 'rect', x: 200, y: 100 }, | ||
| 48 | + { id: 'node2', type: 'circle', x: 400, y: 100 }, | ||
| 49 | + ], | ||
| 50 | + edges: [{ | ||
| 51 | + id: 'edge1', | ||
| 52 | + type: 'custom-edge', | ||
| 53 | + sourceNodeId: 'node1', | ||
| 54 | + targetNodeId: 'node2' | ||
| 55 | + }], | ||
| 56 | + }); | ||
| 57 | +}); | ||
| 58 | +</script> | ||
| 59 | + | ||
| 60 | + | ||
| 61 | +<style scoped> | ||
| 62 | +.container { | ||
| 63 | + width: 100vw; | ||
| 64 | + height: 100vh; | ||
| 65 | + display: flex; | ||
| 66 | + flex-direction: column; | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +.flow-container { | ||
| 70 | + flex: 1; | ||
| 71 | + width: 100%; | ||
| 72 | + height: 100%; | ||
| 73 | +} | ||
| 74 | +</style> |
src/views/api/nodeModel.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-10 16:52:35 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-18 17:18:57 | ||
| 5 | + * @FilePath: /logic-flow2/src/views/api/nodeModel.vue | ||
| 6 | + * @Description: 拖拽面板 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <div class="container"> | ||
| 10 | + <div ref="container" class="flow-container"></div> | ||
| 11 | + </div> | ||
| 12 | +</template> | ||
| 13 | + | ||
| 14 | +<script setup> | ||
| 15 | +import LogicFlow from "@logicflow/core"; | ||
| 16 | +import "@logicflow/extension/lib/style/index.css"; | ||
| 17 | +import CustomNode from "./draggable-text-node"; | ||
| 18 | + | ||
| 19 | +const container = ref(null); | ||
| 20 | +let lf = null; | ||
| 21 | + | ||
| 22 | +/** | ||
| 23 | + * 先必须初始化的时把配置项打开,之后再进行单独的数据设置 | ||
| 24 | + * 比如你如果要文本移动,那么你必须先打开文本移动的配置项,然后再进行单独的数据设置 | ||
| 25 | + */ | ||
| 26 | + | ||
| 27 | +onMounted(() => { | ||
| 28 | + lf = new LogicFlow({ | ||
| 29 | + container: container.value, | ||
| 30 | + grid: true, | ||
| 31 | + // plugins: [Label], // 引入 Label 插件 | ||
| 32 | + nodeTextDraggable: true, // 开启节点文本拖拽 | ||
| 33 | + }); | ||
| 34 | + | ||
| 35 | + // 注册自定义节点 | ||
| 36 | + lf.register(CustomNode); | ||
| 37 | + | ||
| 38 | + // 设置拖拽面板配置 | ||
| 39 | + lf.setPatternItems([ | ||
| 40 | + { | ||
| 41 | + type: "custom-rect", | ||
| 42 | + text: "自定义节点", | ||
| 43 | + label: "拖拽生成节点", | ||
| 44 | + className: "custom-node", | ||
| 45 | + }, | ||
| 46 | + ]); | ||
| 47 | + | ||
| 48 | + // 监听节点创建事件,自定义ID规则 | ||
| 49 | + lf.on("node:dnd-add", ({ data }) => { | ||
| 50 | + const prefix = "custom_node_"; | ||
| 51 | + const timestamp = Date.now(); | ||
| 52 | + data.id = `${prefix}${timestamp}`; | ||
| 53 | + console.log("新创建的节点ID:", data.id); | ||
| 54 | + }); | ||
| 55 | + | ||
| 56 | + // 添加连线事件监听 | ||
| 57 | + lf.on('edge:connect', ({ data }) => { | ||
| 58 | + console.log('连线成功', data); | ||
| 59 | + }); | ||
| 60 | + | ||
| 61 | + lf.on('connection:not-allowed', (data) => { | ||
| 62 | + console.log('连线被阻止', data); | ||
| 63 | + }); | ||
| 64 | + | ||
| 65 | + lf.render({ | ||
| 66 | + nodes: [ | ||
| 67 | + { | ||
| 68 | + id: "node1", | ||
| 69 | + type: "custom-rect", | ||
| 70 | + x: 200, | ||
| 71 | + y: 100, | ||
| 72 | + // text: { | ||
| 73 | + // x: 250, | ||
| 74 | + // y: 150, | ||
| 75 | + // value: 'Node 1', | ||
| 76 | + // }, | ||
| 77 | + text: "Node 1", | ||
| 78 | + }, | ||
| 79 | + { | ||
| 80 | + id: "node2", | ||
| 81 | + type: "circle", | ||
| 82 | + x: 400, | ||
| 83 | + y: 100, | ||
| 84 | + text: { | ||
| 85 | + x: 450, | ||
| 86 | + y: 150, | ||
| 87 | + value: "Node 2", | ||
| 88 | + draggable: false, | ||
| 89 | + }, | ||
| 90 | + }, | ||
| 91 | + ], | ||
| 92 | + edges: [{ id: "edge1", sourceNodeId: "node1", targetNodeId: "node2" }], | ||
| 93 | + }); | ||
| 94 | + | ||
| 95 | + lf.translateCenter(); | ||
| 96 | + | ||
| 97 | + lf.on("node:click", ({data}) => { | ||
| 98 | + lf.getNodeModelById(data.id).setProperties({ | ||
| 99 | + disabled: !data.properties.disabled, | ||
| 100 | + scale: 2, | ||
| 101 | + }); | ||
| 102 | + console.warn(lf.getGraphData()); | ||
| 103 | + | ||
| 104 | + }); | ||
| 105 | + | ||
| 106 | + const nodeModel = lf.getNodeModelById("node1"); | ||
| 107 | + // const { anchors } = nodeModel; | ||
| 108 | + // nodeModel.setIsShowAnchor(true) | ||
| 109 | + console.warn("nodeModel", nodeModel.getConnectedTargetRules()); | ||
| 110 | + | ||
| 111 | + | ||
| 112 | +}); | ||
| 113 | +</script> | ||
| 114 | + | ||
| 115 | +<style scoped> | ||
| 116 | +.container { | ||
| 117 | + width: 100vw; | ||
| 118 | + height: 100vh; | ||
| 119 | + display: flex; | ||
| 120 | + flex-direction: column; | ||
| 121 | +} | ||
| 122 | + | ||
| 123 | +.flow-container { | ||
| 124 | + flex: 1; | ||
| 125 | + width: 100%; | ||
| 126 | + height: 100%; | ||
| 127 | +} | ||
| 128 | +</style> |
src/views/api/scalable-node.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-10 16:52:35 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-17 23:33:06 | ||
| 5 | + * @FilePath: /logic-flow2/src/views/api/scalable-node.vue | ||
| 6 | + * @Description: 可缩放节点示例 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <div class="container"> | ||
| 10 | + <div ref="container" class="flow-container"></div> | ||
| 11 | + </div> | ||
| 12 | +</template> | ||
| 13 | + | ||
| 14 | +<script setup> | ||
| 15 | +import LogicFlow from '@logicflow/core'; | ||
| 16 | +import ScalableRectNode from './scalable-rect-node'; | ||
| 17 | + | ||
| 18 | +const container = ref(null); | ||
| 19 | +let lf = null; | ||
| 20 | + | ||
| 21 | +onMounted(() => { | ||
| 22 | + lf = new LogicFlow({ | ||
| 23 | + container: container.value, | ||
| 24 | + grid: true, | ||
| 25 | + }); | ||
| 26 | + | ||
| 27 | + // 注册自定义节点 | ||
| 28 | + lf.register(ScalableRectNode); | ||
| 29 | + | ||
| 30 | + lf.render({ | ||
| 31 | + nodes: [ | ||
| 32 | + { | ||
| 33 | + id: 'node1', | ||
| 34 | + type: 'scalable-rect', | ||
| 35 | + x: 300, | ||
| 36 | + y: 200, | ||
| 37 | + text: '可缩放节点', | ||
| 38 | + properties: { | ||
| 39 | + scale: 1, // 初始缩放比例 | ||
| 40 | + }, | ||
| 41 | + }, | ||
| 42 | + ], | ||
| 43 | + }); | ||
| 44 | + | ||
| 45 | + lf.translateCenter(); | ||
| 46 | +}); | ||
| 47 | +</script> | ||
| 48 | + | ||
| 49 | +<style scoped> | ||
| 50 | +.container { | ||
| 51 | + width: 100vw; | ||
| 52 | + height: 100vh; | ||
| 53 | + display: flex; | ||
| 54 | + flex-direction: column; | ||
| 55 | +} | ||
| 56 | + | ||
| 57 | +.flow-container { | ||
| 58 | + flex: 1; | ||
| 59 | + width: 100%; | ||
| 60 | + height: 100%; | ||
| 61 | +} | ||
| 62 | +</style> |
src/views/api/scalable-rect-node.js
0 → 100644
| 1 | +/* | ||
| 2 | + * @Date: 2025-03-17 23:23:33 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-17 23:35:02 | ||
| 5 | + * @FilePath: /logic-flow2/src/views/api/scalable-rect-node.js | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | + */ | ||
| 8 | +import { RectResize } from "@logicflow/extension"; | ||
| 9 | + | ||
| 10 | +class CustomNode extends RectResize.view { | ||
| 11 | +} | ||
| 12 | + | ||
| 13 | +class ScalableRectNode extends RectResize.model { | ||
| 14 | + | ||
| 15 | + initNodeData(data) { | ||
| 16 | + super.initNodeData(data); | ||
| 17 | + this.width = 80; | ||
| 18 | + this.height = 40; | ||
| 19 | + this.maxWidth = 300; | ||
| 20 | + this.maxHeight = 300; | ||
| 21 | + this.text.draggable = true; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + // setAttributes() { | ||
| 25 | + // const size = this.properties.scale || 1; | ||
| 26 | + // this.width = 100 * size; | ||
| 27 | + // this.height = 80 * size; | ||
| 28 | + // } | ||
| 29 | + | ||
| 30 | + getDefaultAnchor() { | ||
| 31 | + const { width, height, x, y, id } = this; | ||
| 32 | + return [ | ||
| 33 | + { x: x - width / 2, y, id: `${id}_0` }, | ||
| 34 | + { x: x + width / 2, y, id: `${id}_1` }, | ||
| 35 | + { x, y: y - height / 2, id: `${id}_2` }, | ||
| 36 | + { x, y: y + height / 2, id: `${id}_3` }, | ||
| 37 | + ]; | ||
| 38 | + } | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +export default { | ||
| 42 | + type: 'scalable-rect', | ||
| 43 | + view: CustomNode, | ||
| 44 | + model: ScalableRectNode, | ||
| 45 | +}; |
src/views/api/transformModel.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-10 16:52:35 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-19 00:57:55 | ||
| 5 | + * @FilePath: /logic-flow2/src/views/api/transformModel.vue | ||
| 6 | + * @Description: 拖拽面板 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <div class="container"> | ||
| 10 | + <div ref="container" class="flow-container"></div> | ||
| 11 | + <div class="control-panel"> | ||
| 12 | + <button @click="zoomIn">放大</button> | ||
| 13 | + <button @click="zoomOut">缩小</button> | ||
| 14 | + <button @click="moveLeft">左移</button> | ||
| 15 | + <button @click="moveRight">右移</button> | ||
| 16 | + <button @click="centerView">居中</button> | ||
| 17 | + </div> | ||
| 18 | + </div> | ||
| 19 | +</template> | ||
| 20 | + | ||
| 21 | +<script setup> | ||
| 22 | +import LogicFlow from "@logicflow/core"; | ||
| 23 | + | ||
| 24 | +const container = ref(null); | ||
| 25 | +let lf = null; | ||
| 26 | + | ||
| 27 | +// 缩放画布 | ||
| 28 | +const zoomIn = () => { | ||
| 29 | + const { transformModel } = lf.graphModel; | ||
| 30 | + const currentZoom = transformModel.ZOOM; | ||
| 31 | + transformModel.zoom(currentZoom + 0.1); | ||
| 32 | +}; | ||
| 33 | + | ||
| 34 | +const zoomOut = () => { | ||
| 35 | + const { transformModel } = lf.graphModel; | ||
| 36 | + const currentZoom = transformModel.ZOOM; | ||
| 37 | + transformModel.zoom(currentZoom - 0.1); | ||
| 38 | +}; | ||
| 39 | + | ||
| 40 | +// 平移画布 | ||
| 41 | +const moveLeft = () => { | ||
| 42 | + const { transformModel } = lf.graphModel; | ||
| 43 | + const [x, y] = transformModel.getTranslate(); | ||
| 44 | + transformModel.translate(x - 50, y); | ||
| 45 | +}; | ||
| 46 | + | ||
| 47 | +const moveRight = () => { | ||
| 48 | + const { transformModel } = lf.graphModel; | ||
| 49 | + const [x, y] = transformModel.getTranslate(); | ||
| 50 | + transformModel.translate(x + 50, y); | ||
| 51 | +}; | ||
| 52 | + | ||
| 53 | +// 居中显示 | ||
| 54 | +const centerView = () => { | ||
| 55 | + const { transformModel } = lf; | ||
| 56 | + // 直接从 lf 实例获取节点 | ||
| 57 | + | ||
| 58 | + const nodes = lf.graphModel.nodes; | ||
| 59 | + if (nodes.length === 0) return; | ||
| 60 | + | ||
| 61 | + // 计算所有节点的边界框 | ||
| 62 | + const bounds = nodes.reduce((acc, node) => { | ||
| 63 | + const { x, y } = node; | ||
| 64 | + acc.minX = Math.min(acc.minX, x); | ||
| 65 | + acc.maxX = Math.max(acc.maxX, x); | ||
| 66 | + acc.minY = Math.min(acc.minY, y); | ||
| 67 | + acc.maxY = Math.max(acc.maxY, y); | ||
| 68 | + return acc; | ||
| 69 | + }, { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity }); | ||
| 70 | + | ||
| 71 | + // 计算中心点和范围 | ||
| 72 | + const centerX = (bounds.minX + bounds.maxX) / 2; | ||
| 73 | + const centerY = (bounds.minY + bounds.maxY) / 2; | ||
| 74 | + const width = bounds.maxX - bounds.minX + 200; // 添加边距 | ||
| 75 | + const height = bounds.maxY - bounds.minY + 100; | ||
| 76 | + | ||
| 77 | + // 居中显示 | ||
| 78 | + transformModel.focusOn(centerX, centerY, width, height); | ||
| 79 | +}; | ||
| 80 | + | ||
| 81 | +onMounted(() => { | ||
| 82 | + lf = new LogicFlow({ | ||
| 83 | + container: container.value, | ||
| 84 | + grid: true, | ||
| 85 | + }); | ||
| 86 | + | ||
| 87 | + // 监听点击画布功能 | ||
| 88 | + lf.on("canvas:click", (e) => { | ||
| 89 | + console.log("Canvas clicked at:", e.x, e.y); | ||
| 90 | + }); | ||
| 91 | + | ||
| 92 | + lf.on('node:click', (e) => { | ||
| 93 | + console.log(e); | ||
| 94 | + // 示例:HTML坐标转换为画布坐标 | ||
| 95 | + console.warn(e); | ||
| 96 | + | ||
| 97 | + | ||
| 98 | + const htmlPoint = { x: 100, y: 100 }; | ||
| 99 | + const { transformModel } = lf.graphModel; | ||
| 100 | + const canvasPoint = transformModel.HtmlPointToCanvasPoint(htmlPoint); | ||
| 101 | + console.log("Canvas coordinates:", canvasPoint); | ||
| 102 | + }); | ||
| 103 | + | ||
| 104 | + lf.render({ | ||
| 105 | + nodes: [ | ||
| 106 | + { id: "node1", type: "rect", x: 200, y: 100 }, | ||
| 107 | + { id: "node2", type: "circle", x: 400, y: 100 }, | ||
| 108 | + ], | ||
| 109 | + edges: [{ id: "edge1", sourceNodeId: "node1", targetNodeId: "node2" }], | ||
| 110 | + }); | ||
| 111 | + | ||
| 112 | + // 初始化时居中显示 | ||
| 113 | + setTimeout(() => { | ||
| 114 | + const { transformModel } = lf; | ||
| 115 | + // 这里也需要修改 | ||
| 116 | + const nodes = lf.graphModel.nodes; | ||
| 117 | + if (nodes.length === 0) return; | ||
| 118 | + | ||
| 119 | + const bounds = nodes.reduce((acc, node) => { | ||
| 120 | + const { x, y } = node; | ||
| 121 | + acc.minX = Math.min(acc.minX, x); | ||
| 122 | + acc.maxX = Math.max(acc.maxX, x); | ||
| 123 | + acc.minY = Math.min(acc.minY, y); | ||
| 124 | + acc.maxY = Math.max(acc.maxY, y); | ||
| 125 | + return acc; | ||
| 126 | + }, { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity }); | ||
| 127 | + | ||
| 128 | + const centerX = (bounds.minX + bounds.maxX) / 2; | ||
| 129 | + const centerY = (bounds.minY + bounds.maxY) / 2; | ||
| 130 | + const width = bounds.maxX - bounds.minX + 200; | ||
| 131 | + const height = bounds.maxY - bounds.minY + 100; | ||
| 132 | + | ||
| 133 | + // transformModel.focusOn(centerX, centerY, width, height); | ||
| 134 | + }, 0); | ||
| 135 | + | ||
| 136 | + | ||
| 137 | +}); | ||
| 138 | +</script> | ||
| 139 | + | ||
| 140 | +<style scoped> | ||
| 141 | +.container { | ||
| 142 | + width: 100vw; | ||
| 143 | + height: 100vh; | ||
| 144 | + display: flex; | ||
| 145 | + flex-direction: column; | ||
| 146 | +} | ||
| 147 | + | ||
| 148 | +.flow-container { | ||
| 149 | + flex: 1; | ||
| 150 | + width: 100%; | ||
| 151 | + height: 100%; | ||
| 152 | +} | ||
| 153 | + | ||
| 154 | +.control-panel { | ||
| 155 | + position: fixed; | ||
| 156 | + top: 20px; | ||
| 157 | + left: 20px; | ||
| 158 | + display: flex; | ||
| 159 | + gap: 10px; | ||
| 160 | +} | ||
| 161 | + | ||
| 162 | +button { | ||
| 163 | + padding: 8px 16px; | ||
| 164 | + border: none; | ||
| 165 | + border-radius: 4px; | ||
| 166 | + background-color: #4a90e2; | ||
| 167 | + color: white; | ||
| 168 | + cursor: pointer; | ||
| 169 | +} | ||
| 170 | + | ||
| 171 | +button:hover { | ||
| 172 | + background-color: #357abd; | ||
| 173 | +} | ||
| 174 | +</style> |
This diff is collapsed. Click to expand it.
-
Please register or login to post a comment