Showing
5 changed files
with
510 additions
and
0 deletions
src/views/adv-node/anchor/index.less
0 → 100644
| 1 | +.sql { | ||
| 2 | + .table-container { | ||
| 3 | + box-sizing: border-box; | ||
| 4 | + padding: 10px; | ||
| 5 | + } | ||
| 6 | + | ||
| 7 | + .table-node { | ||
| 8 | + width: 100%; | ||
| 9 | + height: 100%; | ||
| 10 | + overflow: hidden; | ||
| 11 | + background: #fff; | ||
| 12 | + border-radius: 4px; | ||
| 13 | + box-shadow: 0 1px 3px rgb(0 0 0 / 30%); | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | + .table-node::before { | ||
| 17 | + display: block; | ||
| 18 | + width: 100%; | ||
| 19 | + height: 8px; | ||
| 20 | + background: #d79b00; | ||
| 21 | + content: ''; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + .table-node.table-color-1::before { | ||
| 25 | + background: #9673a6; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + .table-node.table-color-2::before { | ||
| 29 | + background: #dae8fc; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + .table-node.table-color-3::before { | ||
| 33 | + background: #82b366; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + .table-node.table-color-4::before { | ||
| 37 | + background: #f8cecc; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + .table-name { | ||
| 41 | + height: 28px; | ||
| 42 | + font-size: 14px; | ||
| 43 | + line-height: 28px; | ||
| 44 | + text-align: center; | ||
| 45 | + background: #f5f5f5; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + .table-felid { | ||
| 49 | + display: flex; | ||
| 50 | + justify-content: space-between; | ||
| 51 | + height: 24px; | ||
| 52 | + padding: 0 10px; | ||
| 53 | + font-size: 12px; | ||
| 54 | + line-height: 24px; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + .felid-type { | ||
| 58 | + color: #9f9c9f; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + /* 自定义锚点样式 */ | ||
| 62 | + | ||
| 63 | + .custom-anchor { | ||
| 64 | + cursor: crosshair; | ||
| 65 | + fill: #d9d9d9; | ||
| 66 | + stroke: #999; | ||
| 67 | + stroke-width: 1; | ||
| 68 | + rx: 3; | ||
| 69 | + ry: 3; | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + .custom-anchor:hover { | ||
| 73 | + fill: #ff7f0e; | ||
| 74 | + stroke: #ff7f0e; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + .lf-node-not-allow .custom-anchor:hover { | ||
| 78 | + cursor: not-allowed; | ||
| 79 | + fill: #d9d9d9; | ||
| 80 | + stroke: #999; | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + .incoming-anchor { | ||
| 84 | + stroke: #d79b00; | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + .outgoing-anchor { | ||
| 88 | + stroke: #82b366; | ||
| 89 | + } | ||
| 90 | +} |
src/views/adv-node/anchor/index.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-10 16:52:35 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-12 23:36:42 | ||
| 5 | + * @FilePath: /logic-flow2/src/views/adv-node/anchor/index.vue | ||
| 6 | + * @Description: 自定义锚点 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <div class="helloworld-app sql container"> | ||
| 10 | + <div ref="container" class="app-content flow-container"></div> | ||
| 11 | + <button @click="handleAddField">Users添加字段</button> | ||
| 12 | + </div> | ||
| 13 | +</template> | ||
| 14 | + | ||
| 15 | +<script setup> | ||
| 16 | +import LogicFlow from "@logicflow/core"; | ||
| 17 | +import sqlNode from "./sqlNode"; | ||
| 18 | +import sqlEdge from "./sqlEdge"; | ||
| 19 | + | ||
| 20 | +import data from "./sqlData"; | ||
| 21 | +import "./index.less"; | ||
| 22 | + | ||
| 23 | +const container = ref(null); | ||
| 24 | +let lf = null; | ||
| 25 | + | ||
| 26 | +// 添加字段的处理函数 | ||
| 27 | +const handleAddField = () => { | ||
| 28 | + const nodeModel = lf.getNodeModelById("node_id_1"); | ||
| 29 | + if (typeof nodeModel?.addField === "function") { | ||
| 30 | + nodeModel.addField({ | ||
| 31 | + key: Math.random().toString(36).substring(2, 7), | ||
| 32 | + type: ["integer", "long", "string", "boolean"][Math.floor(Math.random() * 4)], | ||
| 33 | + }); | ||
| 34 | + } | ||
| 35 | +}; | ||
| 36 | + | ||
| 37 | +onMounted(() => { | ||
| 38 | + lf = new LogicFlow({ | ||
| 39 | + container: container.value, | ||
| 40 | + grid: true, | ||
| 41 | + }); | ||
| 42 | + | ||
| 43 | + lf.register(sqlNode); | ||
| 44 | + lf.register(sqlEdge); | ||
| 45 | + | ||
| 46 | + lf.setDefaultEdgeType("sql-edge"); | ||
| 47 | + lf.setTheme({ | ||
| 48 | + bezier: { | ||
| 49 | + stroke: "#afafaf", | ||
| 50 | + strokeWidth: 1, | ||
| 51 | + }, | ||
| 52 | + }); | ||
| 53 | + | ||
| 54 | + lf.render(data); | ||
| 55 | + lf.translateCenter(); | ||
| 56 | + | ||
| 57 | + // 1.1.28新增,可以自定义锚点显示时机了 | ||
| 58 | + lf.on("anchor:dragstart", ({ data, nodeModel }) => { // 当开始拖拽某个节点的锚点时 | ||
| 59 | + console.log("dragstart", data); | ||
| 60 | + if (nodeModel.type === "sql-node") { // 如果拖拽的是 sql-node 类型的节点 | ||
| 61 | + lf.graphModel.nodes.forEach((node) => { | ||
| 62 | + // 找到其他的 sql-node 节点(排除当前正在拖拽的节点) | ||
| 63 | + console.warn(node); | ||
| 64 | + | ||
| 65 | + if (node.type === "sql-node" && nodeModel.id !== node.id) { | ||
| 66 | + node.isShowAnchor = true; // 显示这些节点的锚点 | ||
| 67 | + node.setProperties({ | ||
| 68 | + isConnection: true, // 设置连接状态属性 | ||
| 69 | + }); | ||
| 70 | + } | ||
| 71 | + }); | ||
| 72 | + } | ||
| 73 | + }); | ||
| 74 | + lf.on("anchor:dragend", ({ data, nodeModel }) => { | ||
| 75 | + console.log("dragend", data); | ||
| 76 | + if (nodeModel.type === "sql-node") { | ||
| 77 | + lf.graphModel.nodes.forEach((node) => { | ||
| 78 | + if (node.type === "sql-node" && nodeModel.id !== node.id) { | ||
| 79 | + node.isShowAnchor = false; | ||
| 80 | + lf.deleteProperty(node.id, "isConnection"); | ||
| 81 | + } | ||
| 82 | + }); | ||
| 83 | + } | ||
| 84 | + }); | ||
| 85 | +}); | ||
| 86 | +</script> | ||
| 87 | + | ||
| 88 | +<style scoped> | ||
| 89 | +.container { | ||
| 90 | + width: 100vw; | ||
| 91 | + height: 100vh; | ||
| 92 | + display: flex; | ||
| 93 | + flex-direction: column; | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +.flow-container { | ||
| 97 | + flex: 1; | ||
| 98 | + width: 100%; | ||
| 99 | + height: 100%; | ||
| 100 | +} | ||
| 101 | +</style> |
src/views/adv-node/anchor/sqlData.js
0 → 100644
| 1 | +/* | ||
| 2 | + * @Date: 2025-03-12 21:23:48 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-12 23:15:14 | ||
| 5 | + * @FilePath: /logic-flow2/src/views/adv-node/anchor/sqlData.js | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | + */ | ||
| 8 | +const data = { | ||
| 9 | + nodes: [ | ||
| 10 | + { | ||
| 11 | + id: 'node_id_1', | ||
| 12 | + type: 'sql-node', | ||
| 13 | + x: 100, | ||
| 14 | + y: 100, | ||
| 15 | + properties: { | ||
| 16 | + tableName: 'Users', | ||
| 17 | + fields: [ | ||
| 18 | + { | ||
| 19 | + key: 'id', | ||
| 20 | + type: 'string', | ||
| 21 | + }, | ||
| 22 | + { | ||
| 23 | + key: 'name', | ||
| 24 | + type: 'string', | ||
| 25 | + }, | ||
| 26 | + { | ||
| 27 | + key: 'age', | ||
| 28 | + type: 'integer', | ||
| 29 | + }, | ||
| 30 | + ], | ||
| 31 | + }, | ||
| 32 | + }, | ||
| 33 | + { | ||
| 34 | + id: 'node_id_2', | ||
| 35 | + type: 'sql-node', | ||
| 36 | + x: 400, | ||
| 37 | + y: 200, | ||
| 38 | + properties: { | ||
| 39 | + tableName: 'Settings', | ||
| 40 | + fields: [ | ||
| 41 | + { | ||
| 42 | + key: 'id', | ||
| 43 | + type: 'string', | ||
| 44 | + }, | ||
| 45 | + { | ||
| 46 | + key: 'key', | ||
| 47 | + type: 'integer', | ||
| 48 | + }, | ||
| 49 | + { | ||
| 50 | + key: 'value', | ||
| 51 | + type: 'string', | ||
| 52 | + }, | ||
| 53 | + ], | ||
| 54 | + }, | ||
| 55 | + }, | ||
| 56 | + { | ||
| 57 | + id: 'node_id_3', | ||
| 58 | + type: 'sql-node', | ||
| 59 | + x: 400, | ||
| 60 | + y: 400, | ||
| 61 | + properties: { | ||
| 62 | + tableName: 'Settings', | ||
| 63 | + fields: [ | ||
| 64 | + { | ||
| 65 | + key: 'id', | ||
| 66 | + type: 'string', | ||
| 67 | + }, | ||
| 68 | + { | ||
| 69 | + key: 'key', | ||
| 70 | + type: 'integer', | ||
| 71 | + }, | ||
| 72 | + { | ||
| 73 | + key: 'value', | ||
| 74 | + type: 'string', | ||
| 75 | + }, | ||
| 76 | + ], | ||
| 77 | + }, | ||
| 78 | + }, | ||
| 79 | + ], | ||
| 80 | + edges: [], | ||
| 81 | +}; | ||
| 82 | +export default data; |
src/views/adv-node/anchor/sqlEdge.js
0 → 100644
| 1 | +/* | ||
| 2 | + * @Date: 2025-03-12 21:20:42 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-12 23:53:01 | ||
| 5 | + * @FilePath: /logic-flow2/src/views/adv-node/anchor/sqlEdge.js | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | + */ | ||
| 8 | +import { BezierEdge, BezierEdgeModel } from '@logicflow/core'; | ||
| 9 | + | ||
| 10 | +class CustomEdge2 extends BezierEdge {} // 继承贝塞尔曲线边的视图类 | ||
| 11 | + | ||
| 12 | +class CustomEdgeModel2 extends BezierEdgeModel { // 继承贝塞尔曲线边的模型类 | ||
| 13 | + getEdgeStyle() { | ||
| 14 | + const style = super.getEdgeStyle(); | ||
| 15 | + // svg属性 | ||
| 16 | + style.strokeWidth = 1; | ||
| 17 | + style.stroke = '#ababac'; | ||
| 18 | + return style; | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * 重写此方法,使保存数据是能带上锚点数据。 | ||
| 23 | + */ | ||
| 24 | + getData() { | ||
| 25 | + const data = super.getData(); | ||
| 26 | + // 保存源节点和目标节点的锚点ID,用于后续恢复连线 | ||
| 27 | + data.sourceAnchorId = this.sourceAnchorId; | ||
| 28 | + data.targetAnchorId = this.targetAnchorId; | ||
| 29 | + return data; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 给边自定义方案,使其支持基于锚点的位置更新边的路径 | ||
| 34 | + */ | ||
| 35 | + updatePathByAnchor() { | ||
| 36 | + // 获取源节点和其锚点 | ||
| 37 | + const sourceNodeModel = this.graphModel.getNodeModelById(this.sourceNodeId); | ||
| 38 | + const sourceAnchor = sourceNodeModel | ||
| 39 | + ?.getDefaultAnchor() | ||
| 40 | + .find((anchor) => anchor.id === this.sourceAnchorId); | ||
| 41 | + | ||
| 42 | + // 获取目标节点和其锚点 | ||
| 43 | + const targetNodeModel = this.graphModel.getNodeModelById(this.targetNodeId); | ||
| 44 | + const targetAnchor = targetNodeModel | ||
| 45 | + ?.getDefaultAnchor() | ||
| 46 | + .find((anchor) => anchor.id === this.targetAnchorId); | ||
| 47 | + | ||
| 48 | + // 更新连线起点 | ||
| 49 | + if (sourceAnchor) { | ||
| 50 | + const startPoint = { | ||
| 51 | + x: sourceAnchor?.x, | ||
| 52 | + y: sourceAnchor?.y, | ||
| 53 | + }; | ||
| 54 | + this.updateStartPoint(startPoint); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + // 更新连线终点 | ||
| 58 | + if (targetAnchor) { | ||
| 59 | + const endPoint = { | ||
| 60 | + x: targetAnchor?.x, | ||
| 61 | + y: targetAnchor?.y, | ||
| 62 | + }; | ||
| 63 | + this.updateEndPoint(endPoint); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + // 这里需要将原有的pointsList设置为空,才能触发bezier的自动计算control点。 | ||
| 67 | + this.pointsList = []; | ||
| 68 | + this.initPoints(); | ||
| 69 | + } | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +export default { | ||
| 73 | + type: 'sql-edge', | ||
| 74 | + view: CustomEdge2, | ||
| 75 | + model: CustomEdgeModel2, | ||
| 76 | +}; |
src/views/adv-node/anchor/sqlNode.js
0 → 100644
| 1 | +import { HtmlNode, HtmlNodeModel, h } from '@logicflow/core' | ||
| 2 | + | ||
| 3 | +class SqlNode extends HtmlNode { | ||
| 4 | + /** | ||
| 5 | + * 1.1.7版本后支持在view中重写锚点形状。 | ||
| 6 | + * 重写锚点新增 | ||
| 7 | + */ | ||
| 8 | + // 自定义锚点形状 | ||
| 9 | + getAnchorShape(anchorData) { | ||
| 10 | + const { x, y, type } = anchorData | ||
| 11 | + // 将锚点渲染为矩形,左右锚点使用不同的样式类 | ||
| 12 | + return h('rect', { | ||
| 13 | + x: x - 5, | ||
| 14 | + y: y - 5, | ||
| 15 | + width: 10, | ||
| 16 | + height: 10, | ||
| 17 | + className: `custom-anchor ${ | ||
| 18 | + type === 'left' ? 'incoming-anchor' : 'outgoing-anchor' | ||
| 19 | + }`, | ||
| 20 | + }) | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + setHtml(rootEl) { // 渲染节点的 HTML 内容 | ||
| 24 | + rootEl.innerHTML = '' | ||
| 25 | + const { | ||
| 26 | + properties: { fields, tableName }, | ||
| 27 | + } = this.props.model | ||
| 28 | + rootEl.setAttribute('class', 'table-container') | ||
| 29 | + const container = document.createElement('div') | ||
| 30 | + container.className = `table-node table-color-${Math.ceil( | ||
| 31 | + Math.random() * 4, | ||
| 32 | + )}` | ||
| 33 | + const tableNameElement = document.createElement('div') | ||
| 34 | + tableNameElement.innerText = tableName | ||
| 35 | + tableNameElement.className = 'table-name' | ||
| 36 | + container.appendChild(tableNameElement) | ||
| 37 | + const fragment = document.createDocumentFragment() | ||
| 38 | + for (let i = 0; i < fields.length; i++) { | ||
| 39 | + const item = fields[i] | ||
| 40 | + const itemElement = document.createElement('div') | ||
| 41 | + itemElement.className = 'table-felid' | ||
| 42 | + const itemKey = document.createElement('span') | ||
| 43 | + itemKey.innerText = item.key | ||
| 44 | + const itemType = document.createElement('span') | ||
| 45 | + itemType.innerText = item.type | ||
| 46 | + itemType.className = 'felid-type' | ||
| 47 | + itemElement.appendChild(itemKey) | ||
| 48 | + itemElement.appendChild(itemType) | ||
| 49 | + fragment.appendChild(itemElement) | ||
| 50 | + } | ||
| 51 | + container.appendChild(fragment) | ||
| 52 | + rootEl.appendChild(container) | ||
| 53 | + } | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +class SqlNodeModel extends HtmlNodeModel { | ||
| 57 | + /** | ||
| 58 | + * 给model自定义添加字段方法 | ||
| 59 | + */ | ||
| 60 | + addField(item) { | ||
| 61 | + this.properties.fields.unshift(item) // 在字段列表开头添加新字段 | ||
| 62 | + this.setAttributes() // 更新节点尺寸 | ||
| 63 | + // 为了保持节点顶部位置不变,在节点变化后,对节点进行一个位移,位移距离为添加高度的一半。 | ||
| 64 | + this.move(0, 24 / 2) // 调整节点位置保持顶部对齐 | ||
| 65 | + // 更新节点连接边的path | ||
| 66 | + this.incoming.edges.forEach((edge) => { | ||
| 67 | + // 调用自定义的更新方案 | ||
| 68 | + edge.updatePathByAnchor() | ||
| 69 | + }) | ||
| 70 | + this.outgoing.edges.forEach((edge) => { | ||
| 71 | + // 调用自定义的更新方案 | ||
| 72 | + edge.updatePathByAnchor() | ||
| 73 | + }) | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + getOutlineStyle() { | ||
| 77 | + const style = super.getOutlineStyle() | ||
| 78 | + style.stroke = 'none' // 移除选中外框 | ||
| 79 | + style.hover.stroke = 'none' // 设置节点轮廓为透明 | ||
| 80 | + return style | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + // 如果不用修改锚地形状,可以重写颜色相关样式 | ||
| 84 | + getAnchorStyle(anchorInfo) { | ||
| 85 | + const style = super.getAnchorStyle() | ||
| 86 | + if (anchorInfo.type === 'left') { // 左侧锚点红色(输入) | ||
| 87 | + style.fill = 'red' | ||
| 88 | + style.hover.fill = 'transparent' | ||
| 89 | + style.hover.stroke = 'transparent' | ||
| 90 | + style.className = 'lf-hide-default' | ||
| 91 | + } else { // 右侧锚点绿色(输出) | ||
| 92 | + style.fill = 'green' | ||
| 93 | + } | ||
| 94 | + return style | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + setAttributes() { // 设置节点属性 | ||
| 98 | + this.width = 200; // 设置节点宽度为 200 | ||
| 99 | + const { | ||
| 100 | + properties: { fields }, | ||
| 101 | + } = this | ||
| 102 | + this.height = 60 + fields.length * 24; // 根据字段数量计算高度 | ||
| 103 | + // 添加连线规则: | ||
| 104 | + // 1. 只能从右侧锚点连出 | ||
| 105 | + // 2. 只能连接到左侧锚点 | ||
| 106 | + const circleOnlyAsTarget = { | ||
| 107 | + message: '只允许从右边的锚点连出', | ||
| 108 | + validate: (sourceNode, targetNode, sourceAnchor) => { | ||
| 109 | + return sourceAnchor.type === 'right' | ||
| 110 | + }, | ||
| 111 | + } | ||
| 112 | + this.sourceRules.push(circleOnlyAsTarget) | ||
| 113 | + this.targetRules.push({ | ||
| 114 | + message: '只允许连接左边的锚点', | ||
| 115 | + validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { | ||
| 116 | + return targetAnchor.type === 'left' | ||
| 117 | + }, | ||
| 118 | + }) | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + getDefaultAnchor() { // 获取默认锚点 | ||
| 122 | + const { | ||
| 123 | + id, | ||
| 124 | + x, | ||
| 125 | + y, | ||
| 126 | + width, | ||
| 127 | + height, | ||
| 128 | + isHovered, | ||
| 129 | + isSelected, | ||
| 130 | + properties: { fields, isConnection }, | ||
| 131 | + } = this | ||
| 132 | + const anchors = [] | ||
| 133 | + fields.forEach((felid, index) => { | ||
| 134 | + // 如果是连出,就不显示左边的锚点 | ||
| 135 | + if (isConnection || !(isHovered || isSelected)) { | ||
| 136 | + anchors.push({ | ||
| 137 | + x: x - width / 2 + 10, | ||
| 138 | + y: y - height / 2 + 60 + index * 24, | ||
| 139 | + id: `${id}_${felid.key}_left`, | ||
| 140 | + edgeAddable: false, // 设置左侧锚点不能作为连线起点, 自定义锚点支持设置edgeAddable属性,用于控制是否可以在此锚点手动创建连线。 | ||
| 141 | + type: 'left', | ||
| 142 | + }) | ||
| 143 | + } | ||
| 144 | + if (!isConnection) { | ||
| 145 | + anchors.push({ | ||
| 146 | + x: x + width / 2 - 10, | ||
| 147 | + y: y - height / 2 + 60 + index * 24, | ||
| 148 | + id: `${id}_${felid.key}_right`, | ||
| 149 | + type: 'right', | ||
| 150 | + }) | ||
| 151 | + } | ||
| 152 | + }) | ||
| 153 | + return anchors | ||
| 154 | + } | ||
| 155 | +} | ||
| 156 | + | ||
| 157 | +export default { | ||
| 158 | + type: 'sql-node', | ||
| 159 | + model: SqlNodeModel, | ||
| 160 | + view: SqlNode, | ||
| 161 | +} |
-
Please register or login to post a comment