新增支持节点拖拽

This commit is contained in:
wanglin2
2021-11-25 16:06:18 +08:00
parent 4677b11dfb
commit 5f19509079
15 changed files with 659 additions and 55 deletions

145
README.md
View File

@@ -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_AFTERv0.1.5+ | 将节点移动到另一个节点的后面 | node要移动的节点、 exist目标节点 |
| INSERT_BEFOREv0.1.5+ | 将节点移动到另一个节点的前面 | node要移动的节点、 exist目标节点 |
| MOVE_NODE_TOv0.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+
检测当前节点是否是某个节点的兄弟节点
## 内置工具方法
引用:

View File

@@ -1 +1 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>一个简单的web思维导图实现</title><link href="dist/css/app.d4d92b48.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.37b3d8f8.css" rel="preload" as="style"><link href="dist/js/app.a76c2c51.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.52f014f8.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.37b3d8f8.css" rel="stylesheet"><link href="dist/css/app.d4d92b48.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="dist/js/chunk-vendors.52f014f8.js"></script><script src="dist/js/app.a76c2c51.js"></script></body></html>
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>一个简单的web思维导图实现</title><link href="dist/css/app.ae564d8a.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.caacd1e0.css" rel="preload" as="style"><link href="dist/js/app.f545c734.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.523e8595.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.caacd1e0.css" rel="stylesheet"><link href="dist/css/app.ae564d8a.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="dist/js/chunk-vendors.523e8595.js"></script><script src="dist/js/app.f545c734.js"></script></body></html>

View File

@@ -0,0 +1,3 @@
# 一个web思维导图的简单实现
详细文档见:[https://github.com/wanglin2/mind-map](https://github.com/wanglin2/mind-map)

View File

@@ -876,9 +876,9 @@ const rootData = {
export default {
// ...data1,
// ...data2,
// ...data3,
...data3,
// ...data4,
...rootData,
// ...rootData,
"theme": {
"template": "minions",
"config": {

View File

@@ -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

288
simple-mind-map/src/Drag.js Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)
})
}

View File

@@ -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'
}

View File

@@ -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('<br>')
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
this.textEditNode.style.minHeight = rect.height + 6 + 'px'

View File

@@ -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

View File

@@ -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
}
})
}
}

View File

@@ -130,7 +130,6 @@ export default {
storeData(data)
})
this.$bus.$on('view_data_change', (data) => {
console.log(JSON.stringify(data))
storeConfig({
view: data,
})

View File

@@ -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