From 732b6b50b09e64cd2691dc5904cf6b7e5d497042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A1=97=E8=A7=92=E5=B0=8F=E6=9E=97?= <1013335014@qq.com> Date: Wed, 11 Dec 2024 17:53:08 +0800 Subject: [PATCH] =?UTF-8?q?Feat=EF=BC=9A1.=E6=94=AF=E6=8C=81=E5=8D=95?= =?UTF-8?q?=E7=8B=AC=E5=AE=9A=E4=B9=89=E6=AF=8F=E6=9D=A1=E5=85=B3=E8=81=94?= =?UTF-8?q?=E7=BA=BF=E7=9A=84=E6=A0=B7=E5=BC=8F=EF=BC=9B2.=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=8F=96=E6=B6=88=E6=BF=80=E6=B4=BB=E5=85=B3=E8=81=94?= =?UTF-8?q?=E7=BA=BF=E7=9A=84=E4=BA=8B=E4=BB=B6=EF=BC=9B3.=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=85=B3=E8=81=94=E7=BA=BF=E6=96=87=E6=9C=AC=E5=AD=98?= =?UTF-8?q?=E5=9C=A8=E7=A9=BA=E8=A1=8C=E6=97=B6=E7=BC=96=E8=BE=91=E6=A1=86?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E5=BC=82=E5=B8=B8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/constants/defaultOptions.js | 2 +- .../src/plugins/AssociativeLine.js | 148 +++++++++++++++--- .../associativeLineControls.js | 16 +- .../associativeLine/associativeLineText.js | 33 ++-- simple-mind-map/src/theme/default.js | 1 + 5 files changed, 159 insertions(+), 41 deletions(-) diff --git a/simple-mind-map/src/constants/defaultOptions.js b/simple-mind-map/src/constants/defaultOptions.js index c145f33c..8c001a79 100644 --- a/simple-mind-map/src/constants/defaultOptions.js +++ b/simple-mind-map/src/constants/defaultOptions.js @@ -85,7 +85,7 @@ export const defaultOpt = { // 是否只有当鼠标在画布内才响应快捷键事件 enableShortcutOnlyWhenMouseInSvg: true, // 自定义判断是否响应快捷键事件,优先级比enableShortcutOnlyWhenMouseInSvg选项高 - // 可以传递一个函数,接收事件对象e为参数,需要返回true或false,返回true代表允许响应快捷键事件,反之不允许,库默认当事件目标为body,或为文本编辑框元素时响应快捷键,其他不响应 + // 可以传递一个函数,接收事件对象e为参数,需要返回true或false,返回true代表允许响应快捷键事件,反之不允许,库默认当事件目标为body,或为文本编辑框元素(普通文本编辑框、富文本编辑框、关联线文本编辑框)时响应快捷键,其他不响应 customCheckEnableShortcut: null, // 初始根节点的位置 initRootNodePosition: null, diff --git a/simple-mind-map/src/plugins/AssociativeLine.js b/simple-mind-map/src/plugins/AssociativeLine.js index 27694200..3bab80f2 100644 --- a/simple-mind-map/src/plugins/AssociativeLine.js +++ b/simple-mind-map/src/plugins/AssociativeLine.js @@ -11,11 +11,25 @@ import { import associativeLineControlsMethods from './associativeLine/associativeLineControls' import associativeLineTextMethods from './associativeLine/associativeLineText' +const styleProps = [ + 'associativeLineWidth', + 'associativeLineColor', + 'associativeLineActiveWidth', + 'associativeLineActiveColor', + 'associativeLineDasharray', + 'associativeLineTextColor', + 'associativeLineTextFontSize', + 'associativeLineTextLineHeight', + 'associativeLineTextFontFamily' +] + // 关联线插件 class AssociativeLine { constructor(opt = {}) { this.mindMap = opt.mindMap this.associativeLineDraw = this.mindMap.associativeLineDraw + // 本次不要重新渲染连线 + this.isNotRenderAllLines = false // 当前所有连接线 this.lineList = [] // 当前激活的连接线 @@ -27,9 +41,6 @@ class AssociativeLine { this.overlapNode = null // 创建过程中的目标节点 // 是否有节点正在被拖拽 this.isNodeDragging = false - // 箭头图标 - this.markerPath = null - this.marker = this.createMarker() // 控制点 this.controlLine1 = null this.controlLine2 = null @@ -112,6 +123,25 @@ class AssociativeLine { this.mindMap.off('beforeDestroy', this.onBeforeDestroy) } + // 获取关联线的样式配置 + // 优先级:关联线自定义样式、节点自定义样式、主题的节点层级样式、主题的最外层样式 + getStyleConfig(node, toNode) { + let lineStyle = {} + if (toNode) { + const associativeLineStyle = node.getData('associativeLineStyle') || {} + lineStyle = associativeLineStyle[toNode.getData('uid')] || {} + } + const res = {} + styleProps.forEach(prop => { + if (typeof lineStyle[prop] !== 'undefined') { + res[prop] = lineStyle[prop] + } else { + res[prop] = node.getStyle(prop) + } + }) + return res + } + // 实例销毁时清除关联线文字编辑框 onBeforeDestroy() { this.hideEditTextBox() @@ -140,12 +170,12 @@ class AssociativeLine { } // 创建箭头 - createMarker() { + createMarker(callback = () => {}) { return this.associativeLineDraw.marker(20, 20, add => { add.ref(12, 5) add.size(10, 10) add.attr('orient', 'auto-start-reverse') - this.markerPath = add.path('M0,0 L2,5 L0,10 L10,5 Z') + callback(add.path('M0,0 L2,5 L0,10 L10,5 Z')) }) } @@ -172,6 +202,10 @@ class AssociativeLine { // 渲染所有连线 renderAllLines() { + if (this.isNotRenderAllLines) { + this.isNotRenderAllLines = false + return + } // 先移除 this.removeAllLines() this.removeControls() @@ -223,11 +257,14 @@ class AssociativeLine { associativeLineWidth, associativeLineColor, associativeLineActiveWidth, - associativeLineActiveColor, associativeLineDasharray - } = this.mindMap.themeConfig + } = this.getStyleConfig(node, toNode) // 箭头 - this.markerPath + let markerPath = null + const marker = this.createMarker(p => { + markerPath = p + }) + markerPath .stroke({ color: associativeLineColor }) .fill({ color: associativeLineColor }) // 路径 @@ -247,7 +284,7 @@ class AssociativeLine { }) .fill({ color: 'none' }) path.plot(pathStr) - path.marker('end', this.marker) + path.marker('end', marker) // 不可见的点击线 let clickPath = this.associativeLineDraw.path() clickPath @@ -258,6 +295,7 @@ class AssociativeLine { let text = this.createText({ path, clickPath, + markerPath, node, toNode, startPoint, @@ -270,6 +308,7 @@ class AssociativeLine { this.setActiveLine({ path, clickPath, + markerPath, text, node, toNode, @@ -284,14 +323,60 @@ class AssociativeLine { this.showEditTextBox(text) }) // 渲染关联线文字 - this.renderText(this.getText(node, toNode), path, text) + this.renderText(this.getText(node, toNode), path, text, node, toNode) this.lineList.push([path, clickPath, text, node, toNode]) } + // 更新当前激活连线的样式,一般在自定义了节点关联线的样式后调用 + // 直接调用node.setStyle方法更新样式会直接触发关联线更新,但是关联线的激活状态会丢失 + // 所以可以调用node.setData方法更新数据,然后再调用该方法更新样式,这样关联线激活状态不会丢失 + updateActiveLineStyle() { + if (!this.activeLine) return + this.isNotRenderAllLines = true + const [path, clickPath, text, node, toNode, markerPath] = this.activeLine + const { + associativeLineWidth, + associativeLineColor, + associativeLineDasharray, + associativeLineActiveWidth, + associativeLineActiveColor, + associativeLineTextColor, + associativeLineTextFontFamily, + associativeLineTextFontSize + } = this.getStyleConfig(node, toNode) + path + .stroke({ + width: associativeLineWidth, + color: associativeLineColor, + dasharray: associativeLineDasharray || [6, 4] + }) + .fill({ color: 'none' }) + clickPath + .stroke({ + width: associativeLineActiveWidth, + color: associativeLineActiveColor + }) + .fill({ color: 'none' }) + markerPath + .stroke({ color: associativeLineColor }) + .fill({ color: associativeLineColor }) + text.find('text').forEach(textNode => { + textNode + .fill({ + color: associativeLineTextColor + }) + .css({ + 'font-family': associativeLineTextFontFamily, + 'font-size': associativeLineTextFontSize + 'px' + }) + }) + } + // 激活某根关联线 setActiveLine({ path, clickPath, + markerPath, text, node, toNode, @@ -299,25 +384,33 @@ class AssociativeLine { endPoint, controlPoints }) { - let { associativeLineActiveColor } = this.mindMap.themeConfig + let { associativeLineActiveColor } = this.getStyleConfig(node, toNode) // 如果当前存在激活节点,那么取消激活节点 this.mindMap.execCommand('CLEAR_ACTIVE_NODE') // 否则清除当前的关联线的激活状态,如果有的话 this.clearActiveLine() // 保存当前激活的关联线信息 - this.activeLine = [path, clickPath, text, node, toNode] + this.activeLine = [path, clickPath, text, node, toNode, markerPath] // 让不可见的点击线显示 clickPath.stroke({ color: associativeLineActiveColor }) // 如果没有输入过关联线文字,那么显示默认文字 if (!this.getText(node, toNode)) { - this.renderText(this.mindMap.opt.defaultAssociativeLineText, path, text) + this.renderText( + this.mindMap.opt.defaultAssociativeLineText, + path, + text, + node, + toNode + ) } // 渲染控制点和连线 this.renderControls( startPoint, endPoint, controlPoints[0], - controlPoints[1] + controlPoints[1], + node, + toNode ) this.mindMap.emit('associative_line_click', path, clickPath, node, toNode) this.front() @@ -346,7 +439,7 @@ class AssociativeLine { associativeLineWidth, associativeLineColor, associativeLineDasharray - } = this.mindMap.themeConfig + } = this.getStyleConfig(fromNode) if (this.isCreatingLine || !fromNode) return this.front() this.isCreatingLine = true @@ -360,10 +453,14 @@ class AssociativeLine { }) .fill({ color: 'none' }) // 箭头 - this.markerPath + let markerPath = null + const marker = this.createMarker(p => { + markerPath = p + }) + markerPath .stroke({ color: associativeLineColor }) .fill({ color: associativeLineColor }) - this.creatingLine.marker('end', this.marker) + this.creatingLine.marker('end', marker) } // 取消创建关联线 @@ -529,7 +626,8 @@ class AssociativeLine { associativeLineTargets, associativeLinePoint, associativeLineTargetControlOffsets, - associativeLineText + associativeLineText, + associativeLineStyle } = node.getData() associativeLinePoint = associativeLinePoint || [] let targetIndex = getAssociativeLineTargetIndex(node, toNode) @@ -542,6 +640,15 @@ class AssociativeLine { } }) } + // 更新关联线样式数据 + let newAssociativeLineStyle = {} + if (associativeLineStyle) { + Object.keys(associativeLineStyle).forEach(item => { + if (item !== toNode.getData('uid')) { + newAssociativeLineStyle[item] = associativeLineStyle[item] + } + }) + } this.mindMap.execCommand('SET_NODE_DATA', node, { // 目标 associativeLineTargets: associativeLineTargets.filter((_, index) => { @@ -558,7 +665,9 @@ class AssociativeLine { }) : [], // 文本 - associativeLineText: newAssociativeLineText + associativeLineText: newAssociativeLineText, + // 样式 + associativeLineStyle: newAssociativeLineStyle }) } @@ -578,6 +687,7 @@ class AssociativeLine { this.activeLine = null this.removeControls() this.back() + this.mindMap.emit('associative_line_deactivate') } } diff --git a/simple-mind-map/src/plugins/associativeLine/associativeLineControls.js b/simple-mind-map/src/plugins/associativeLine/associativeLineControls.js index c1b52756..146e1a61 100644 --- a/simple-mind-map/src/plugins/associativeLine/associativeLineControls.js +++ b/simple-mind-map/src/plugins/associativeLine/associativeLineControls.js @@ -6,8 +6,8 @@ import { } from './associativeLineUtils' // 创建控制点、连线节点 -function createControlNodes() { - let { associativeLineActiveColor } = this.mindMap.themeConfig +function createControlNodes(node, toNode) { + let { associativeLineActiveColor } = this.getStyleConfig(node, toNode) // 连线 this.controlLine1 = this.associativeLineDraw .line() @@ -16,13 +16,13 @@ function createControlNodes() { .line() .stroke({ color: associativeLineActiveColor, width: 2 }) // 控制点 - this.controlPoint1 = this.createOneControlNode('controlPoint1') - this.controlPoint2 = this.createOneControlNode('controlPoint2') + this.controlPoint1 = this.createOneControlNode('controlPoint1', node, toNode) + this.controlPoint2 = this.createOneControlNode('controlPoint2', node, toNode) } // 创建控制点 -function createOneControlNode(pointKey) { - let { associativeLineActiveColor } = this.mindMap.themeConfig +function createOneControlNode(pointKey, node, toNode) { + let { associativeLineActiveColor } = this.getStyleConfig(node, toNode) return this.associativeLineDraw .circle(this.controlPointDiameter) .stroke({ color: associativeLineActiveColor }) @@ -221,10 +221,10 @@ function resetControlPoint() { } // 渲染控制点 -function renderControls(startPoint, endPoint, point1, point2) { +function renderControls(startPoint, endPoint, point1, point2, node, toNode) { if (!this.mindMap.opt.enableAdjustAssociativeLinePoints) return if (!this.controlLine1) { - this.createControlNodes() + this.createControlNodes(node, toNode) } let radius = this.controlPointDiameter / 2 // 控制点和起终点的连线 diff --git a/simple-mind-map/src/plugins/associativeLine/associativeLineText.js b/simple-mind-map/src/plugins/associativeLine/associativeLineText.js index 3fdcfced..a2147d16 100644 --- a/simple-mind-map/src/plugins/associativeLine/associativeLineText.js +++ b/simple-mind-map/src/plugins/associativeLine/associativeLineText.js @@ -43,6 +43,7 @@ function showEditTextBox(g) { // 输入框元素没有创建过,则先创建 if (!this.textEditNode) { this.textEditNode = document.createElement('div') + this.textEditNode.className = 'associative-line-text-edit-warp' this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;` this.textEditNode.setAttribute('contenteditable', true) this.textEditNode.addEventListener('keyup', e => { @@ -54,14 +55,14 @@ function showEditTextBox(g) { const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body targetNode.appendChild(this.textEditNode) } + let [, , , node, toNode] = this.activeLine let { associativeLineTextFontSize, associativeLineTextFontFamily, associativeLineTextLineHeight - } = this.mindMap.themeConfig + } = this.getStyleConfig(node, toNode) let { defaultAssociativeLineText, nodeTextEditZIndex } = this.mindMap.opt let scale = this.mindMap.view.scale - let [, , , node, toNode] = this.activeLine let text = this.getText(node, toNode) let textLines = (text || defaultAssociativeLineText).split(/\n/gim) this.textEditNode.style.fontFamily = associativeLineTextFontFamily @@ -124,7 +125,7 @@ function hideEditTextBox() { this.textEditNode.style.display = 'none' this.textEditNode.innerHTML = '' this.showTextEdit = false - this.renderText(str, path, text) + this.renderText(str, path, text, node, toNode) this.mindMap.emit('hide_text_edit') } @@ -138,29 +139,35 @@ function getText(node, toNode) { } // 渲染关联线文字 -function renderText(str, path, text) { +function renderText(str, path, text, node, toNode) { if (!str) return let { associativeLineTextFontSize, associativeLineTextLineHeight } = - this.mindMap.themeConfig + this.getStyleConfig(node, toNode) text.clear() - let textArr = str.split(/\n/gim) + let textArr = str.replace(/\n$/g, '').split(/\n/gim) textArr.forEach((item, index) => { - let node = new Text().text(item) - node.y(associativeLineTextFontSize * associativeLineTextLineHeight * index) - this.styleText(node) - text.add(node) + // 避免尾部的空行不占宽度,导致文本编辑框定位异常的问题 + if (item === '') { + item = '' + } + let textNode = new Text().text(item) + textNode.y( + associativeLineTextFontSize * associativeLineTextLineHeight * index + ) + this.styleText(textNode, node, toNode) + text.add(textNode) }) updateTextPos(path, text) } // 给文本设置样式 -function styleText(node) { +function styleText(textNode, node, toNode) { let { associativeLineTextColor, associativeLineTextFontSize, associativeLineTextFontFamily - } = this.mindMap.themeConfig - node + } = this.getStyleConfig(node, toNode) + textNode .fill({ color: associativeLineTextColor }) diff --git a/simple-mind-map/src/theme/default.js b/simple-mind-map/src/theme/default.js index 519ecc95..38ecb5ac 100644 --- a/simple-mind-map/src/theme/default.js +++ b/simple-mind-map/src/theme/default.js @@ -103,6 +103,7 @@ export default { // lineFlow, // lineFlowDuration, // lineFlowForward + // 关联线的所有样式 }, // 二级节点样式 second: {