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