diff --git a/README.md b/README.md index 314429a0..d9e18fcd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,23 @@ -[TOC] - # web思维导图的简单实现 +## 特性 + +- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图四种结构 + +- [x] 内置多种主题,允许高度自定义样式 + +- [x] 支持快捷键 + +- [x] 节点内容支持图片、图标、超链接、备注、标签 + +- [x] 支持前进后退 + +- [x] 支持拖动、缩放 + +- [x] 支持右键多选 + +- [x] 支持节点拖拽 + ## 目录介绍 1.simple-mind-map @@ -44,6 +60,10 @@ npm run build ``` 会自动把`index.html`移动到根目录。 +## 相关文章 + +[Web思维导图实现的技术点分析](https://juejin.cn/post/6987711560521089061) + # 安装 ```bash @@ -193,31 +213,34 @@ const mindMap = new MindMap({ 执行命令,每执行一个命令就会在历史堆栈里添加一条记录用于回退或前进。所有命令如下: -| 命令名称 | 描述 | 参数 | -| ------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | -| SELECT_ALL | 全选 | | -| BACK | 回退指定的步数 | step(要回退的步数,默认为1) | -| FORWARD | 前进指定的步数 | step(要前进的步数,默认为1) | -| INSERT_NODE | 插入同级节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效 | | -| INSERT_CHILD_NODE | 插入子节点,操作节点为当前激活的节点 | | -| UP_NODE | 上移节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点或在列表里的第一个节点使用无效 | | -| DOWN_NODE | 操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点或在列表里的最后一个节点使用无效 | | -| REMOVE_NODE | 删除节点,操作节点为当前激活的节点 | | -| PASTE_NODE | 粘贴节点到节点,操作节点为当前激活的节点 | data(要粘贴的节点数据,一般通过`renderer.copyNode()`方法和`renderer.cutNode()`方法获取) | -| CUT_NODE | 剪切节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点使用无效 | callback(回调函数,剪切的节点数据会通过调用该函数并通过参数返回) | -| SET_NODE_STYLE | 修改节点样式 | node(要设置样式的节点)、prop(样式属性)、value(样式属性值)、isActive(布尔值,是否设置的是激活状态的样式) | -| SET_NODE_ACTIVE | 设置节点是否激活 | node(要设置的节点)、active(布尔值,是否激活) | -| CLEAR_ACTIVE_NODE | 清除当前已激活节点的激活状态,操作节点为当前激活的节点 | | -| SET_NODE_EXPAND | 设置节点是否展开 | node(要设置的节点)、expand(布尔值,是否展开) | -| EXPAND_ALL | 展开所有节点 | | -| UNEXPAND_ALL | 收起所有节点 | | -| SET_NODE_DATA | 更新节点数据,即更新节点数据对象里`data`对象的数据 | node(要设置的节点)、data(对象,要更新的数据,如`{expand: true}`) | -| SET_NODE_TEXT | 设置节点文本 | node(要设置的节点)、text(要设置的文本字符串,换行可以使用`\n`) | -| SET_NODE_IMAGE | 设置节点图片 | node(要设置的节点)、imgData(对象,图片信息,结构为:`{url, title, width, height}`,图片的宽高必须要传) | -| SET_NODE_ICON | 设置节点图标 | node(要设置的节点)、icons(数组,预定义的图片名称组成的数组,可用图标可在[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js)文件里的`nodeIconList`列表里获取到,图标名称为`type_name`,如`['priority_1']`) | -| SET_NODE_HYPERLINK | 设置节点超链接 | node(要设置的节点)、link(超链接地址)、title(超链接名称,可选) | -| SET_NODE_NOTE | 设置节点备注 | node(要设置的节点)、note(备注文字) | -| SET_NODE_TAG | 设置节点标签 | node(要设置的节点)、tag(字符串数组,内置颜色信息可在[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/utils/constant.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/utils/constant.js)里获取到) | +| 命令名称 | 描述 | 参数 | +| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| SELECT_ALL | 全选 | | +| BACK | 回退指定的步数 | step(要回退的步数,默认为1) | +| FORWARD | 前进指定的步数 | step(要前进的步数,默认为1) | +| INSERT_NODE | 插入同级节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效 | | +| INSERT_CHILD_NODE | 插入子节点,操作节点为当前激活的节点 | | +| UP_NODE | 上移节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点或在列表里的第一个节点使用无效 | | +| DOWN_NODE | 操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点或在列表里的最后一个节点使用无效 | | +| REMOVE_NODE | 删除节点,操作节点为当前激活的节点 | | +| PASTE_NODE | 粘贴节点到节点,操作节点为当前激活的节点 | data(要粘贴的节点数据,一般通过`renderer.copyNode()`方法和`renderer.cutNode()`方法获取) | +| CUT_NODE | 剪切节点,操作节点为当前激活的节点,如果有多个激活节点,只会对第一个有效,对根节点使用无效 | callback(回调函数,剪切的节点数据会通过调用该函数并通过参数返回) | +| SET_NODE_STYLE | 修改节点样式 | node(要设置样式的节点)、prop(样式属性)、value(样式属性值)、isActive(布尔值,是否设置的是激活状态的样式) | +| SET_NODE_ACTIVE | 设置节点是否激活 | node(要设置的节点)、active(布尔值,是否激活) | +| CLEAR_ACTIVE_NODE | 清除当前已激活节点的激活状态,操作节点为当前激活的节点 | | +| SET_NODE_EXPAND | 设置节点是否展开 | node(要设置的节点)、expand(布尔值,是否展开) | +| EXPAND_ALL | 展开所有节点 | | +| UNEXPAND_ALL | 收起所有节点 | | +| SET_NODE_DATA | 更新节点数据,即更新节点数据对象里`data`对象的数据 | node(要设置的节点)、data(对象,要更新的数据,如`{expand: true}`) | +| SET_NODE_TEXT | 设置节点文本 | node(要设置的节点)、text(要设置的文本字符串,换行可以使用`\n`) | +| SET_NODE_IMAGE | 设置节点图片 | node(要设置的节点)、imgData(对象,图片信息,结构为:`{url, title, width, height}`,图片的宽高必须要传) | +| SET_NODE_ICON | 设置节点图标 | node(要设置的节点)、icons(数组,预定义的图片名称组成的数组,可用图标可在[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js)文件里的`nodeIconList`列表里获取到,图标名称为`type_name`,如`['priority_1']`) | +| SET_NODE_HYPERLINK | 设置节点超链接 | node(要设置的节点)、link(超链接地址)、title(超链接名称,可选) | +| SET_NODE_NOTE | 设置节点备注 | node(要设置的节点)、note(备注文字) | +| SET_NODE_TAG | 设置节点标签 | node(要设置的节点)、tag(字符串数组,内置颜色信息可在[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/utils/constant.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/utils/constant.js)里获取到) | +| INSERT_AFTER(v0.1.5+) | 将节点移动到另一个节点的后面 | node(要移动的节点)、 exist(目标节点) | +| INSERT_BEFORE(v0.1.5+) | 将节点移动到另一个节点的前面 | node(要移动的节点)、 exist(目标节点) | +| MOVE_NODE_TO(v0.1.5+) | 移动一个节点作为另一个节点的子节点 | node(要移动的节点)、 toNode(目标节点) | #### setData(data) @@ -235,7 +258,11 @@ const mindMap = new MindMap({ `isDownload`:是否需要直接触发下载,布尔值,默认为`false` +#### toPos(x, y) +v0.1.5+ + +将浏览器可视窗口的坐标转换成相对于画布的坐标 ## render实例 @@ -301,6 +328,30 @@ const mindMap = new MindMap({ +#### moveNodeTo(node, toNode) + +v0.1.5+ + +移动一个节点作为另一个节点的子节点 + + + +#### insertBefore(node, exist) + +v0.1.5+ + +将节点移动到另一个节点的前面 + + + +#### insertAfter(node, exist) + +v0.1.5+ + +将节点移动到另一个节点的后面 + + + ## keyCommand实例 `keyCommand`实例负责快捷键的添加及触发,内置了一些快捷键,也可以自行添加。可通过`mindMap.keyCommand`获取到该实例。 @@ -558,6 +609,14 @@ v0.1.1+ +#### isDrag + +v0.1.5+ + +节点是否正在拖拽中 + + + ### 方法 #### addChildren(node) @@ -680,6 +739,38 @@ v0.1.1+ +#### hide() + +v0.1.5+ + +隐藏节点及其下级节点 + + + +#### show() + +v0.1.5+ + +显示节点及其下级节点 + + + +#### isParent(node) + +v0.1.5+ + +检测当前节点是否是某个节点的祖先节点 + + + +#### isBrother(node) + +v0.1.5+ + +检测当前节点是否是某个节点的兄弟节点 + + + ## 内置工具方法 引用: diff --git a/index.html b/index.html index 8347f032..a359b737 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -一个简单的web思维导图实现
\ No newline at end of file +一个简单的web思维导图实现
\ No newline at end of file diff --git a/simple-mind-map/READMD.md b/simple-mind-map/READMD.md new file mode 100644 index 00000000..c170d570 --- /dev/null +++ b/simple-mind-map/READMD.md @@ -0,0 +1,3 @@ +# 一个web思维导图的简单实现 + +详细文档见:[https://github.com/wanglin2/mind-map](https://github.com/wanglin2/mind-map) \ No newline at end of file diff --git a/simple-mind-map/example/exampleData.js b/simple-mind-map/example/exampleData.js index 6299ca96..f0fab061 100644 --- a/simple-mind-map/example/exampleData.js +++ b/simple-mind-map/example/exampleData.js @@ -876,9 +876,9 @@ const rootData = { export default { // ...data1, // ...data2, - // ...data3, + ...data3, // ...data4, - ...rootData, + // ...rootData, "theme": { "template": "minions", "config": { diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js index 014051d9..8d163859 100644 --- a/simple-mind-map/index.js +++ b/simple-mind-map/index.js @@ -9,6 +9,7 @@ import Command from './src/Command' import BatchExecution from './src/BatchExecution' import Export from './src/Export' import Select from './src/Select' +import Drag from './src/Drag' import { layoutValueList } from './src/utils/constant' @@ -113,6 +114,11 @@ class MindMap { mindMap: this }) + // 拖动类 + this.drag = new Drag({ + mindMap: this + }) + // 批量执行类 this.batchExecution = new BatchExecution() @@ -321,6 +327,18 @@ class MindMap { let result = await this.doExport.export(...args) return result } + + /** + * @Author: 王林 + * @Date: 2021-07-11 09:20:03 + * @Desc: 转换位置 + */ + toPos(x, y) { + return { + x: x - this.elRect.left, + y: y - this.elRect.top + } + } } export default MindMap \ No newline at end of file diff --git a/simple-mind-map/src/Drag.js b/simple-mind-map/src/Drag.js new file mode 100644 index 00000000..2b48dd58 --- /dev/null +++ b/simple-mind-map/src/Drag.js @@ -0,0 +1,288 @@ +import { + bfsWalk, + throttle +} from './utils' +import Base from './layouts/Base' + +/** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-23 17:38:55 + * @Desc: 节点拖动类 + */ +class Drag extends Base { + /** + * @Author: 王林 + * @Date: 2021-07-10 22:35:16 + * @Desc: 构造函数 + */ + constructor({ + mindMap + }) { + super(mindMap.renderer) + this.mindMap = mindMap + this.reset() + this.bindEvent() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-23 19:33:56 + * @Desc: 复位 + */ + reset() { + // 当前拖拽节点 + this.node = null + // 当前重叠节点 + this.overlapNode = null + // 当前上一个同级节点 + this.prevNode = null + // 当前下一个同级节点 + this.nextNode = null + // 画布的变换数据 + this.drawTransform = null + // 克隆节点 + this.clone = null + // 连接线 + this.line = null + // 同级位置占位符 + this.placeholder = null + // 鼠标按下位置和节点左上角的偏移量 + this.offsetX = 0 + this.offsetY = 0 + // 克隆节点左上角的坐标 + this.cloneNodeLeft = 0 + this.cloneNodeTop = 0 + // 当前鼠标是否按下 + this.isMousedown = false + // 拖拽的鼠标位置变量 + this.mouseDownX = 0 + this.mouseDownY = 0 + this.mouseMoveX = 0 + this.mouseMoveY = 0 + } + + /** + * @Author: 王林 + * @Date: 2021-07-10 22:36:36 + * @Desc: 绑定事件 + */ + bindEvent() { + this.checkOverlapNode = throttle(this.checkOverlapNode, 300, this) + this.mindMap.on('node_mousedown', (node, e) => { + if (e.which !== 1 || node.isRoot) { + return + } + // 计算鼠标按下的位置距离节点左上角的距离 + this.drawTransform = this.mindMap.draw.transform() + let { + scaleX, + scaleY, + translateX, + translateY + } = this.drawTransform + this.offsetX = e.clientX - (node.left * scaleX + translateX) + this.offsetY = e.clientY - (node.top * scaleY + translateY) + // + this.node = node + this.isMousedown = true + let { + x, + y + } = this.mindMap.toPos(e.clientX, e.clientY) + this.mouseDownX = x + this.mouseDownY = y + }) + this.mindMap.on('mousemove', (e) => { + if (!this.isMousedown) { + return + } + let { + x, + y + } = this.mindMap.toPos(e.clientX, e.clientY) + this.mouseMoveX = x + this.mouseMoveY = y + if ((Math.abs(x - this.mouseDownX) <= 10 && Math.abs(y - this.mouseDownY) <= 10) && !this.node.isDrag) { + return + } + this.mindMap.renderer.clearAllActive() + this.onMove(x, y) + }) + this.onMouseup = this.onMouseup.bind(this) + this.mindMap.on('node_mouseup', this.onMouseup) + this.mindMap.on('mouseup', this.onMouseup) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-23 19:38:02 + * @Desc: 鼠标松开事件 + */ + onMouseup() { + if (!this.isMousedown) { + return; + } + this.isMousedown = false + this.node.isDrag = false + this.node.show() + this.removeCloneNode() + // 存在重叠子节点,则移动作为其子节点 + if (this.overlapNode) { + this.mindMap.renderer.setNodeActive(this.overlapNode, false) + this.mindMap.execCommand('MOVE_NODE_TO', this.node, this.overlapNode) + } else if (this.prevNode) { // 存在前一个相邻节点,作为其下一个兄弟节点 + this.mindMap.renderer.setNodeActive(this.prevNode, false) + this.mindMap.execCommand('INSERT_AFTER', this.node, this.prevNode) + } else if (this.nextNode) { // 存在下一个相邻节点,作为其前一个兄弟节点 + this.mindMap.renderer.setNodeActive(this.nextNode, false) + this.mindMap.execCommand('INSERT_BEFORE', this.node, this.nextNode) + } + this.reset() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-23 19:34:53 + * @Desc: 创建克隆节点 + */ + createCloneNode() { + if (!this.clone) { + // 节点 + this.clone = this.node.group.clone() + this.clone.opacity(0.5) + this.clone.css('z-index', 99999) + this.node.isDrag = true + this.node.hide() + // 连接线 + this.line = this.draw.path() + this.line.opacity(0.5) + this.node.style.line(this.line) + // 同级位置占位符 + this.placeholder = this.draw.rect().fill({ + color: this.node.style.merge('lineColor', true) + }) + this.mindMap.draw.add(this.clone) + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-23 19:35:16 + * @Desc: 移除克隆节点 + */ + removeCloneNode() { + if (!this.clone) { + return + } + this.clone.remove() + this.line.remove() + this.placeholder.remove() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-23 18:53:47 + * @Desc: 拖动中 + */ + onMove(x, y) { + if (!this.isMousedown) { + return; + } + this.createCloneNode() + let { + scaleX, + scaleY, + translateX, + translateY + } = this.drawTransform + this.cloneNodeLeft = x - this.offsetX + this.cloneNodeTop = y - this.offsetY + x = (this.cloneNodeLeft - translateX) / scaleX + y = (this.cloneNodeTop - translateY) / scaleY + let t = this.clone.transform() + this.clone.translate(x - t.translateX, y - t.translateY) + // 连接线 + let parent = this.node.parent + this.line.plot(this.quadraticCurvePath(parent.left + parent.width / 2, parent.top + parent.height / 2, x + this.node.width / 2, y + this.node.height / 2)) + this.checkOverlapNode() + } + + /** + * @Author: 王林 + * @Date: 2021-07-11 10:20:43 + * @Desc: 检测重叠节点 + */ + checkOverlapNode() { + if (!this.drawTransform) { + return + } + let { + scaleX, + scaleY, + translateX, + translateY + } = this.drawTransform + let checkRight = this.cloneNodeLeft + this.node.width * scaleX + let checkBottom = this.cloneNodeTop + this.node.height * scaleX + this.overlapNode = null + this.prevNode = null + this.nextNode = null + this.placeholder.size(0, 0) + bfsWalk(this.mindMap.renderer.root, (node) => { + if (node.nodeData.data.isActive) { + this.mindMap.renderer.setNodeActive(node, false) + } + if (node === this.node || this.node.isParent(node)) { + return + } + if (this.overlapNode || this.prevNode && this.nextNode) { + return + } + let { + left, + top, + width, + height + } = node + let _left = left + let _top = top + let _bottom = top + height + let right = (left + width) * scaleX + translateX + let bottom = (top + height) * scaleY + translateY + left = left * scaleX + translateX + top = top * scaleY + translateY + // 检测是否重叠 + if (!this.overlapNode) { + if ( + left <= checkRight && right >= this.cloneNodeLeft && + top <= checkBottom && bottom >= this.cloneNodeTop + ) { + this.overlapNode = node + } + } + // 检测兄弟节点位置 + if (!this.prevNode && !this.nextNode && this.node.isBrother(node)) { + if (left <= checkRight && right >= this.cloneNodeLeft) { + if (this.cloneNodeTop > bottom && this.cloneNodeTop <= bottom + 10) { + this.prevNode = node + this.placeholder.size(node.width, 10).move(_left, _bottom) + } else if (checkBottom < top && checkBottom >= top - 10) { + this.nextNode = node + this.placeholder.size(node.width, 10).move(_left, _top - 10) + } + } + } + }) + if (this.overlapNode) { + this.mindMap.renderer.setNodeActive(this.overlapNode, true) + } + } +} + +export default Drag \ No newline at end of file diff --git a/simple-mind-map/src/Node.js b/simple-mind-map/src/Node.js index 36823dcc..900d2651 100644 --- a/simple-mind-map/src/Node.js +++ b/simple-mind-map/src/Node.js @@ -55,6 +55,8 @@ class Node { this.left = opt.left || 0 // top this.top = opt.top || 0 + // 是否正在拖拽中 + this.isDrag = false // 父节点 this.parent = opt.parent || null // 子节点 @@ -676,6 +678,50 @@ class Node { } } + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-23 18:39:14 + * @Desc: 隐藏节点 + */ + hide() { + this.group.hide() + if (this.parent) { + let index = this.parent.children.indexOf(this) + this.parent._lines[index].hide() + } + // 子节点 + if (this.children && this.children.length) { + asyncRun(this.children.map((item) => { + return () =>{ + item.hide() + } + })) + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-23 18:39:14 + * @Desc: 显示节点 + */ + show() { + this.group.show() + if (this.parent) { + let index = this.parent.children.indexOf(this) + this.parent._lines[index].show() + } + // 子节点 + if (this.children && this.children.length) { + asyncRun(this.children.map((item) => { + return () =>{ + item.show() + } + })) + } + } + /** * @Author: 王林 * @Date: 2021-04-10 22:01:53 @@ -801,6 +847,42 @@ class Node { } } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-25 09:51:37 + * @Desc: 检测当前节点是否是某个节点的祖先节点 + */ + isParent(node) { + if (this === node) { + return false + } + let parent = node.parent + while(parent) { + if (this === parent) { + return true + } + parent = parent.parent + } + return false + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-25 10:32:34 + * @Desc: 检测当前节点是否是某个节点的兄弟节点 + */ + isBrother(node) { + if (!this.parent || this === node) { + return false + } + return this.parent.children.find((item) => { + return item === node + }) + } + /** * @Author: 王林 * @Date: 2021-06-20 22:51:57 diff --git a/simple-mind-map/src/Render.js b/simple-mind-map/src/Render.js index 2600a51a..526e58e1 100644 --- a/simple-mind-map/src/Render.js +++ b/simple-mind-map/src/Render.js @@ -77,7 +77,6 @@ class Render { // 清除激活状态 if (this.activeNodeList.length > 0) { this.mindMap.execCommand('CLEAR_ACTIVE_NODE') - this.mindMap.emit('node_active', null, []) } }) } @@ -109,6 +108,13 @@ class Render { // 下移节点 this.downNode = this.downNode.bind(this) this.mindMap.command.add('DOWN_NODE', this.downNode) + // 移动节点 + this.insertAfter = this.insertAfter.bind(this) + this.mindMap.command.add('INSERT_AFTER', this.insertAfter) + this.insertBefore = this.insertBefore.bind(this) + this.mindMap.command.add('INSERT_BEFORE', this.insertBefore) + this.moveNodeTo = this.moveNodeTo.bind(this) + this.mindMap.command.add('MOVE_NODE_TO', this.moveNodeTo) // 删除节点 this.removeNode = this.removeNode.bind(this) this.mindMap.command.add('REMOVE_NODE', this.removeNode) @@ -240,6 +246,9 @@ class Render { * @Desc: 清除当前所有激活节点,并会触发事件 */ clearAllActive() { + if (this.activeNodeList.length <= 0) { + return + } this.clearActive() this.mindMap.emit('node_active', null, []) } @@ -457,6 +466,88 @@ class Render { this.mindMap.render() } + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-25 10:51:34 + * @Desc: 将节点移动到另一个节点的前面 + */ + insertBefore(node, exist) { + if (node.isRoot) { + return + } + let parent = node.parent + let childList = parent.children + // 要移动节点的索引 + let index = childList.findIndex((item) => { + return item === node + }) + if (index === -1) { + return + } + // 目标节点的索引 + let existIndex = childList.findIndex((item) => { + return item === exist + }) + if (existIndex === -1) { + return + } + // 当前节点在目标节点前面 + if (index < existIndex) { + existIndex = existIndex - 1 + } else { + existIndex = existIndex + } + // 节点实例 + childList.splice(index, 1) + childList.splice(existIndex, 0, node) + // 节点数据 + parent.nodeData.children.splice(index, 1) + parent.nodeData.children.splice(existIndex, 0, node.nodeData) + this.mindMap.render() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-25 10:51:34 + * @Desc: 将节点移动到另一个节点的后面 + */ + insertAfter(node, exist) { + if (node.isRoot) { + return + } + let parent = node.parent + let childList = parent.children + // 要移动节点的索引 + let index = childList.findIndex((item) => { + return item === node + }) + if (index === -1) { + return + } + // 目标节点的索引 + let existIndex = childList.findIndex((item) => { + return item === exist + }) + if (existIndex === -1) { + return + } + // 当前节点在目标节点前面 + if (index < existIndex) { + existIndex = existIndex + } else { + existIndex = existIndex + 1 + } + // 节点实例 + childList.splice(index, 1) + childList.splice(existIndex, 0, node) + // 节点数据 + parent.nodeData.children.splice(index, 1) + parent.nodeData.children.splice(existIndex, 0, node.nodeData) + this.mindMap.render() + } + /** * @Author: 王林 * @Date: 2021-05-04 13:40:39 @@ -533,6 +624,24 @@ class Render { } } + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-11-24 16:54:01 + * @Desc: 移动一个节点作为另一个节点的子节点 + */ + moveNodeTo(node, toNode) { + if (node.isRoot) { + return + } + let copyData = copyNodeTree({}, node) + this.removeActiveNode(node) + this.removeOneNode(node) + this.mindMap.emit('node_active', null, this.activeNodeList) + toNode.nodeData.children.push(copyData) + this.mindMap.render() + } + /** * @Author: 王林 * @Date: 2021-07-15 20:09:39 diff --git a/simple-mind-map/src/Select.js b/simple-mind-map/src/Select.js index a8de6bcc..bdfefbb1 100644 --- a/simple-mind-map/src/Select.js +++ b/simple-mind-map/src/Select.js @@ -35,7 +35,7 @@ class Select { return } this.isMousedown = true - let { x, y } = this.toPos(e.clientX, e.clientY) + let { x, y } = this.mindMap.toPos(e.clientX, e.clientY) this.mouseDownX = x this.mouseDownY = y this.createRect(x, y) @@ -44,7 +44,7 @@ class Select { if (!this.isMousedown) { return } - let { x, y } = this.toPos(e.clientX, e.clientY) + let { x, y } = this.mindMap.toPos(e.clientX, e.clientY) this.mouseMoveX = x this.mouseMoveY = y if (Math.abs(x - this.mouseDownX) <= 10 && Math.abs(y - this.mouseDownY) <= 10) { @@ -136,18 +136,6 @@ class Select { }).plot([[x, y]]) } - /** - * @Author: 王林 - * @Date: 2021-07-11 09:20:03 - * @Desc: 转换位置 - */ - toPos(x, y) { - return { - x: x - this.mindMap.elRect.left, - y: y - this.mindMap.elRect.top - } - } - /** * @Author: 王林 * @Date: 2021-07-11 10:20:43 @@ -175,7 +163,7 @@ class Select { if (node.nodeData.data.isActive) { return ; } - this.mindMap.execCommand('SET_NODE_ACTIVE', node, true) + this.mindMap.renderer.setNodeActive(node, true) this.mindMap.renderer.addActiveNode(node) }) } else if (node.nodeData.data.isActive) { @@ -183,7 +171,7 @@ class Select { if (!node.nodeData.data.isActive) { return ; } - this.mindMap.execCommand('SET_NODE_ACTIVE', node, false) + this.mindMap.renderer.setNodeActive(node, false) this.mindMap.renderer.removeActiveNode(node) }) } diff --git a/simple-mind-map/src/Style.js b/simple-mind-map/src/Style.js index 83f27b04..3d47b892 100644 --- a/simple-mind-map/src/Style.js +++ b/simple-mind-map/src/Style.js @@ -104,9 +104,9 @@ class Style { * @Date: 2021-04-13 08:14:34 * @Desc: html文字节点 */ - domText(node) { + domText(node, fontSizeScale = 1) { node.style.fontFamily = this.merge('fontFamily') - node.style.fontSize = this.merge('fontSize') + 'px' + node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px' node.style.fontWeight = this.merge('fontWeight') || 'normal' } diff --git a/simple-mind-map/src/TextEdit.js b/simple-mind-map/src/TextEdit.js index 4e77de64..f7523eba 100644 --- a/simple-mind-map/src/TextEdit.js +++ b/simple-mind-map/src/TextEdit.js @@ -82,7 +82,7 @@ export default class TextEdit { this.textEditNode.setAttribute('contenteditable', true) document.body.appendChild(this.textEditNode) } - node.style.domText(this.textEditNode) + node.style.domText(this.textEditNode, this.mindMap.view.scale) this.textEditNode.innerHTML = node.nodeData.data.text.split(/\n/img).join('
') this.textEditNode.style.minWidth = rect.width + 10 + 'px' this.textEditNode.style.minHeight = rect.height + 6 + 'px' diff --git a/simple-mind-map/src/View.js b/simple-mind-map/src/View.js index 0abc166c..afb07a6f 100644 --- a/simple-mind-map/src/View.js +++ b/simple-mind-map/src/View.js @@ -19,6 +19,7 @@ class View { this.sy = 0 this.x = 0 this.y = 0 + this.firstDrag = true this.setTransformData(this.mindMap.opt.viewData) this.bind() } @@ -49,10 +50,18 @@ class View { this.sy = this.y }) this.mindMap.event.on('drag', (e, event) => { + if (this.firstDrag) { + this.firstDrag = false + // 清除激活节点 + this.mindMap.execCommand('CLEAR_ACTIVE_NODE') + } this.x = this.sx + event.mousemoveOffset.x this.y = this.sy + event.mousemoveOffset.y this.transform() }) + this.mindMap.event.on('mouseup', () => { + this.firstDrag = true + }) // 放大缩小视图 this.mindMap.event.on('mousewheel', (e, dir) => { // // 放大 @@ -97,6 +106,8 @@ class View { this.mindMap.draw.transform({ ...viewData.transform }) + this.mindMap.emit('view_data_change', this.getTransformData()) + this.mindMap.emit('scale', this.scale) } } @@ -130,7 +141,7 @@ class View { transform() { this.mindMap.draw.transform({ scale: this.scale, - origin: 'left center', + // origin: 'center center', translate: [this.x, this.y], }) this.mindMap.emit('view_data_change', this.getTransformData()) @@ -142,7 +153,6 @@ class View { * @Desc: 恢复 */ reset() { - // let t = this.mindMap.draw.transform() this.scale = 1 this.x = 0 this.y = 0 diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index 68b1cef7..a5adb6db 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -27,12 +27,18 @@ export const walk = (root, parent, beforeCallback, afterCallback, isRoot, layerI export const bfsWalk = (root, callback) => { callback(root) let stack = [root] + let isStop = false while (stack.length) { + if (isStop) { + break + } let cur = stack.shift() if (cur.children && cur.children.length) { cur.children.forEach((item) => { stack.push(item) - callback(item) + if(callback(item) === 'stop') { + isStop = true + } }) } } diff --git a/web/src/pages/Edit/components/Edit.vue b/web/src/pages/Edit/components/Edit.vue index 29437ea5..d355c30d 100644 --- a/web/src/pages/Edit/components/Edit.vue +++ b/web/src/pages/Edit/components/Edit.vue @@ -130,7 +130,6 @@ export default { storeData(data) }) this.$bus.$on('view_data_change', (data) => { - console.log(JSON.stringify(data)) storeConfig({ view: data, }) diff --git a/web/src/pages/Edit/components/Scale.vue b/web/src/pages/Edit/components/Scale.vue index 26518063..6c0edc64 100644 --- a/web/src/pages/Edit/components/Scale.vue +++ b/web/src/pages/Edit/components/Scale.vue @@ -32,12 +32,22 @@ export default { mindMap(val, oldVal) { if (val && !oldVal) { this.mindMap.on("scale", (scale) => { - this.scaleNum = (scale * 100).toFixed(0); + this.scaleNum = this.toPer(scale); }); + this.scaleNum = this.toPer(this.mindMap.view.scale) } }, }, methods: { + /** + * @Author: 王林25 + * @Date: 2021-11-25 14:20:16 + * @Desc: 转换成百分数 + */ + toPer(scale) { + return (scale * 100).toFixed(0) + }, + /** * @Author: 王林 * @Date: 2021-07-04 17:10:34