From 749a4d0e81d9709662aa1d5b0bbed181dbc8b3c1 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Wed, 21 Jun 2023 14:57:22 +0800 Subject: [PATCH] =?UTF-8?q?Feat=EF=BC=9A=E6=94=AF=E6=8C=81=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E8=8A=82=E7=82=B9=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/index.js | 122 +---------------- .../src/constants/defaultOptions.js | 126 ++++++++++++++++++ simple-mind-map/src/core/render/TextEdit.js | 9 +- simple-mind-map/src/core/render/node/Node.js | 29 +++- .../core/render/node/nodeCreateContents.js | 30 ++++- simple-mind-map/src/utils/index.js | 11 ++ 6 files changed, 201 insertions(+), 126 deletions(-) create mode 100644 simple-mind-map/src/constants/defaultOptions.js diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js index 3802b389..90ab5156 100644 --- a/simple-mind-map/index.js +++ b/simple-mind-map/index.js @@ -11,127 +11,7 @@ import { layoutValueList, CONSTANTS } from './src/constants/constant' import { SVG } from '@svgdotjs/svg.js' import { simpleDeepClone } from './src/utils' import defaultTheme, { checkIsNodeSizeIndependenceConfig } from './src/themes/default' - -// 默认选项配置 -const defaultOpt = { - // 是否只读 - readonly: false, - // 布局 - layout: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE, - // 如果结构为鱼骨图,那么可以通过该选项控制倾斜角度 - fishboneDeg: 45, - // 主题 - theme: 'default', // 内置主题:default(默认主题) - // 主题配置,会和所选择的主题进行合并 - themeConfig: {}, - // 放大缩小的增量比例 - scaleRatio: 0.1, - // 最多显示几个标签 - maxTag: 5, - // 导出图片时的内边距 - exportPadding: 20, - // 展开收缩按钮尺寸 - expandBtnSize: 20, - // 节点里图片和文字的间距 - imgTextMargin: 5, - // 节点里各种文字信息的间距,如图标和文字的间距 - textContentMargin: 2, - // 多选节点时鼠标移动到边缘时的画布移动偏移量 - selectTranslateStep: 3, - // 多选节点时鼠标移动距边缘多少距离时开始偏移 - selectTranslateLimit: 20, - // 自定义节点备注内容显示 - customNoteContentShow: null, - /* - { - show(){}, - hide(){} - } - */ - // 是否开启节点自由拖拽 - enableFreeDrag: false, - // 水印配置 - watermarkConfig: { - text: '', - lineSpacing: 100, - textSpacing: 100, - angle: 30, - textStyle: { - color: '#999', - opacity: 0.5, - fontSize: 14 - } - }, - // 达到该宽度文本自动换行 - textAutoWrapWidth: 500, - // 自定义鼠标滚轮事件处理 - // 可以传一个函数,回调参数为事件对象 - customHandleMousewheel: null, - // 鼠标滚动的行为,如果customHandleMousewheel传了自定义函数,这个属性不生效 - mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM,// zoom(放大缩小)、move(上下移动) - // 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px - mousewheelMoveStep: 100, - // 默认插入的二级节点的文字 - defaultInsertSecondLevelNodeText: '二级节点', - // 默认插入的二级以下节点的文字 - defaultInsertBelowSecondLevelNodeText: '分支主题', - // 展开收起按钮的颜色 - expandBtnStyle: { - color: '#808080', - fill: '#fff' - }, - // 自定义展开收起按钮的图标 - expandBtnIcon: { - open: '',// svg字符串 - close: '' - }, - // 是否只有当鼠标在画布内才响应快捷键事件 - enableShortcutOnlyWhenMouseInSvg: true, - // 是否开启节点动画过渡 - enableNodeTransitionMove: true, - // 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms - nodeTransitionMoveDuration: 300, - // 初始根节点的位置 - initRootNodePosition: null, - // 导出png、svg、pdf时的图形内边距 - exportPaddingX: 10, - exportPaddingY: 10, - // 节点文本编辑框的z-index - nodeTextEditZIndex: 3000, - // 节点备注浮层的z-index - nodeNoteTooltipZIndex: 3000, - // 是否在点击了画布外的区域时结束节点文本的编辑状态 - isEndNodeTextEditOnClickOuter: true, - // 最大历史记录数 - maxHistoryCount: 1000, - // 是否一直显示节点的展开收起按钮,默认为鼠标移上去和激活时才显示 - alwaysShowExpandBtn: false, - // 扩展节点可插入的图标 - iconList: [ - // { - // name: '',// 分组名称 - // type: '',// 分组的值 - // list: [// 分组下的图标列表 - // { - // name: '',// 图标名称 - // icon:''// 图标,可以传svg或图片 - // } - // ] - // } - ], - // 节点最大缓存数量 - maxNodeCacheCount: 1000, - // 关联线默认文字 - defaultAssociativeLineText: '关联', - // 思维导图适应画布大小时的内边距 - fitPadding: 50, - // 是否开启按住ctrl键多选节点功能 - enableCtrlKeyNodeSelection: true, - // 设置为左键多选节点,右键拖动画布 - useLeftKeySelectionRightKeyDrag: false, - // 节点即将进入编辑前的回调方法,如果该方法返回true以外的值,那么将取消编辑,函数可以返回一个值,或一个Promise,回调参数为节点实例 - beforeTextEdit: null -} +import { defaultOpt } from './src/constants/defaultOptions' // 思维导图 class MindMap { diff --git a/simple-mind-map/src/constants/defaultOptions.js b/simple-mind-map/src/constants/defaultOptions.js new file mode 100644 index 00000000..51d68aea --- /dev/null +++ b/simple-mind-map/src/constants/defaultOptions.js @@ -0,0 +1,126 @@ +import { CONSTANTS } from './constant' + +// 默认选项配置 +export const defaultOpt = { + // 是否只读 + readonly: false, + // 布局 + layout: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE, + // 如果结构为鱼骨图,那么可以通过该选项控制倾斜角度 + fishboneDeg: 45, + // 主题 + theme: 'default', // 内置主题:default(默认主题) + // 主题配置,会和所选择的主题进行合并 + themeConfig: {}, + // 放大缩小的增量比例 + scaleRatio: 0.1, + // 最多显示几个标签 + maxTag: 5, + // 导出图片时的内边距 + exportPadding: 20, + // 展开收缩按钮尺寸 + expandBtnSize: 20, + // 节点里图片和文字的间距 + imgTextMargin: 5, + // 节点里各种文字信息的间距,如图标和文字的间距 + textContentMargin: 2, + // 多选节点时鼠标移动到边缘时的画布移动偏移量 + selectTranslateStep: 3, + // 多选节点时鼠标移动距边缘多少距离时开始偏移 + selectTranslateLimit: 20, + // 自定义节点备注内容显示 + customNoteContentShow: null, + /* + { + show(){}, + hide(){} + } + */ + // 是否开启节点自由拖拽 + enableFreeDrag: false, + // 水印配置 + watermarkConfig: { + text: '', + lineSpacing: 100, + textSpacing: 100, + angle: 30, + textStyle: { + color: '#999', + opacity: 0.5, + fontSize: 14 + } + }, + // 达到该宽度文本自动换行 + textAutoWrapWidth: 500, + // 自定义鼠标滚轮事件处理 + // 可以传一个函数,回调参数为事件对象 + customHandleMousewheel: null, + // 鼠标滚动的行为,如果customHandleMousewheel传了自定义函数,这个属性不生效 + mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM, // zoom(放大缩小)、move(上下移动) + // 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px + mousewheelMoveStep: 100, + // 默认插入的二级节点的文字 + defaultInsertSecondLevelNodeText: '二级节点', + // 默认插入的二级以下节点的文字 + defaultInsertBelowSecondLevelNodeText: '分支主题', + // 展开收起按钮的颜色 + expandBtnStyle: { + color: '#808080', + fill: '#fff' + }, + // 自定义展开收起按钮的图标 + expandBtnIcon: { + open: '', // svg字符串 + close: '' + }, + // 是否只有当鼠标在画布内才响应快捷键事件 + enableShortcutOnlyWhenMouseInSvg: true, + // 是否开启节点动画过渡 + enableNodeTransitionMove: true, + // 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms + nodeTransitionMoveDuration: 300, + // 初始根节点的位置 + initRootNodePosition: null, + // 导出png、svg、pdf时的图形内边距 + exportPaddingX: 10, + exportPaddingY: 10, + // 节点文本编辑框的z-index + nodeTextEditZIndex: 3000, + // 节点备注浮层的z-index + nodeNoteTooltipZIndex: 3000, + // 是否在点击了画布外的区域时结束节点文本的编辑状态 + isEndNodeTextEditOnClickOuter: true, + // 最大历史记录数 + maxHistoryCount: 1000, + // 是否一直显示节点的展开收起按钮,默认为鼠标移上去和激活时才显示 + alwaysShowExpandBtn: false, + // 扩展节点可插入的图标 + iconList: [ + // { + // name: '',// 分组名称 + // type: '',// 分组的值 + // list: [// 分组下的图标列表 + // { + // name: '',// 图标名称 + // icon:''// 图标,可以传svg或图片 + // } + // ] + // } + ], + // 节点最大缓存数量 + maxNodeCacheCount: 1000, + // 关联线默认文字 + defaultAssociativeLineText: '关联', + // 思维导图适应画布大小时的内边距 + fitPadding: 50, + // 是否开启按住ctrl键多选节点功能 + enableCtrlKeyNodeSelection: true, + // 设置为左键多选节点,右键拖动画布 + useLeftKeySelectionRightKeyDrag: false, + // 节点即将进入编辑前的回调方法,如果该方法返回true以外的值,那么将取消编辑,函数可以返回一个值,或一个Promise,回调参数为节点实例 + beforeTextEdit: null, + // 是否开启自定义节点内容 + isUseCustomNodeContent: false, + // 自定义返回节点内容的方法 + customCreateNodeContent: null +} diff --git a/simple-mind-map/src/core/render/TextEdit.js b/simple-mind-map/src/core/render/TextEdit.js index 888ef7e3..56efc9f6 100644 --- a/simple-mind-map/src/core/render/TextEdit.js +++ b/simple-mind-map/src/core/render/TextEdit.js @@ -66,10 +66,15 @@ export default class TextEdit { // 显示文本编辑框 async show(node) { - if (typeof this.mindMap.opt.beforeTextEdit === 'function') { + // 使用了自定义节点内容那么不响应编辑事件 + if (node.isUseCustomNodeContent()) { + return + } + let { beforeTextEdit } = this.mindMap.opt + if (typeof beforeTextEdit === 'function') { let isShow = false try { - isShow = await this.mindMap.opt.beforeTextEdit(node) + isShow = await beforeTextEdit(node) } catch (error) { isShow = false } diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js index 3d2bab6f..7934a6e7 100644 --- a/simple-mind-map/src/core/render/node/Node.js +++ b/simple-mind-map/src/core/render/node/Node.js @@ -1,7 +1,7 @@ import Style from './Style' import Shape from './Shape' -import { asyncRun } from '../../../utils' -import { G, Rect } from '@svgdotjs/svg.js' +import { asyncRun, nodeToHTML } from '../../../utils' +import { G, Rect, ForeignObject, SVG } from '@svgdotjs/svg.js' import nodeGeneralizationMethods from './nodeGeneralization' import nodeExpandBtnMethods from './nodeExpandBtn' import nodeCommandWrapsMethods from './nodeCommandWraps' @@ -59,6 +59,7 @@ class Node { this.group = null this.shapeNode = null // 节点形状节点 // 节点内容对象 + this._customNodeContent = null this._imgData = null this._iconData = null this._textData = null @@ -154,6 +155,13 @@ class Node { // 创建节点的各个内容对象数据 createNodeData() { + // 自定义节点内容 + let { isUseCustomNodeContent, customCreateNodeContent } = this.mindMap.opt + if (isUseCustomNodeContent && customCreateNodeContent) { + this._customNodeContent = customCreateNodeContent(this) + } + // 如果没有返回内容,那么还是使用内置的节点内容 + if (this._customNodeContent) return this._imgData = this.createImgNode() this._iconData = this.createIconNode() this._textData = this.createTextNode() @@ -176,6 +184,14 @@ class Node { // 计算节点尺寸信息 getNodeRect() { + // 自定义节点内容 + if (this.isUseCustomNodeContent()) { + let rect = this.measureCustomNodeContentSize(this._customNodeContent) + return { + width: rect.width, + height: rect.height + } + } // 宽高 let imgContentWidth = 0 let imgContentHeight = 0 @@ -266,6 +282,15 @@ class Node { if (this.isGeneralization && this.generalizationBelongNode) { this.group.addClass('generalization_' + this.generalizationBelongNode.uid) } + // 如果存在自定义节点内容,那么使用自定义节点内容 + if (this.isUseCustomNodeContent()) { + let foreignObject = new ForeignObject() + foreignObject.width(width) + foreignObject.height(height) + foreignObject.add(SVG(this._customNodeContent)) + this.group.add(foreignObject) + return + } // 图片节点 let imgHeight = 0 if (this._imgData) { diff --git a/simple-mind-map/src/core/render/node/nodeCreateContents.js b/simple-mind-map/src/core/render/node/nodeCreateContents.js index e175455f..2cdd4ce4 100644 --- a/simple-mind-map/src/core/render/node/nodeCreateContents.js +++ b/simple-mind-map/src/core/render/node/nodeCreateContents.js @@ -280,6 +280,32 @@ function createNoteNode() { } } +// 测量自定义节点内容元素的宽高 +let warpEl = null +function measureCustomNodeContentSize (content) { + if (!warpEl) { + warpEl = document.createElement('div') + warpEl.style.cssText = ` + position: fixed; + left: -99999px; + top: -99999px; + ` + this.mindMap.el.appendChild(warpEl) + } + warpEl.innerHTML = '' + warpEl.appendChild(content) + let rect = warpEl.getBoundingClientRect() + return { + width: rect.width, + height: rect.height + } +} + +// 是否使用的是自定义节点内容 +function isUseCustomNodeContent() { + return !!this._customNodeContent +} + export default { createImgNode, getImgShowSize, @@ -288,5 +314,7 @@ export default { createTextNode, createHyperlinkNode, createTagNode, - createNoteNode + createNoteNode, + measureCustomNodeContentSize, + isUseCustomNodeContent } \ No newline at end of file diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index d8e95df2..66a4778e 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -356,4 +356,15 @@ export const readBlob = (blob) => { } reader.readAsDataURL(blob) }) +} + +// 将dom节点转换成html字符串 +let nodeToHTMLWrapEl = null +export const nodeToHTML = (node) => { + if (!nodeToHTMLWrapEl) { + nodeToHTMLWrapEl = document.createElement('div') + } + nodeToHTMLWrapEl.innerHTML = '' + nodeToHTMLWrapEl.appendChild(node) + return nodeToHTMLWrapEl.innerHTML } \ No newline at end of file