Compare commits

...

56 Commits

Author SHA1 Message Date
wanglin2
f66297ff99 打包0.4.0 2023-02-25 10:11:16 +08:00
wanglin2
3113bf2e1f 完善文档 2023-02-24 17:14:48 +08:00
wanglin2
8c114dac02 Feature:支持节点富文本编辑 2023-02-24 17:14:24 +08:00
wanglin2
06dba79272 新增图标 2023-02-24 17:13:07 +08:00
wanglin2
8441986ca7 修改文档 2023-02-20 19:25:59 +08:00
wanglin2
c8b50908e1 Fix:修复底边风格的情况下,节点高度过高会和其他节点重叠的问题 2023-02-20 15:10:27 +08:00
wanglin2
7e11fde892 打包0.3.4 2023-02-20 11:30:15 +08:00
wanglin2
3d9f3bd7a8 Fix:修复批量删除的节点中如果存在根节点会出现删除异常的问题 2023-02-20 11:26:26 +08:00
wanglin2
46e11649b0 优化节点文本编辑 2023-02-20 11:02:04 +08:00
wanglin2
161ef7590c feature:节点文本增加自动换行功能 2023-02-20 10:14:34 +08:00
wanglin2
ca660a3c74 打包0.3.3 2023-02-14 09:38:38 +08:00
wanglin2
7a24872331 Fix:修复根节点无法黄的问题 2023-02-14 09:35:56 +08:00
wanglin2
eb4ea9fb3a 打包0.3.2 2023-02-03 11:13:58 +08:00
wanglin2
64228c02ff 修复当思维导图实际内容大于屏幕宽高时,导出的时候超出的部分没有绘制水印的问题 2023-02-03 11:09:43 +08:00
wanglin2
f184a5d948 修复二级节点拖拽到其他节点或其他节点拖拽到二级节点时节点样式没有更新的问题 2023-02-03 10:40:06 +08:00
wanglin2
0bf5b2d6f7 打包0.3.1 2023-02-01 16:30:29 +08:00
wanglin2
74a52ea386 导出d的图片支持background-size/position/repeat属性效果 2023-02-01 16:25:57 +08:00
wanglin2
9914eef5b9 修改文档 2023-01-31 15:06:24 +08:00
wanglin2
f547f741f2 1.修复删除背景图片不生效的问题;2.背景图片展示增加位置和大小设置;3.修复节点拖拽到根节点时连接线跑到根节点上方的问题 2023-01-31 15:04:38 +08:00
wanglin2
c26149fa42 修复demo设置了水印页面刷新后生效的问题 2023-01-31 13:35:46 +08:00
wanglin2
b2aa3648eb 支持当修改文档后自动重新编译生成vue组件 2023-01-31 11:15:34 +08:00
wanglin2
b997604ab2 打包0.3.0 2023-01-17 17:39:37 +08:00
wanglin2
c6929b82ad 插件化架构基本升级完成 2023-01-17 16:53:28 +08:00
wanglin2
b40da53aef 只读模式下禁止添加历史记录、禁止响应前进后退操作 2023-01-17 15:49:23 +08:00
wanglin2
1e5dfd97e1 提取多选节点Select类作为插件 2023-01-17 15:26:35 +08:00
wanglin2
97bcc22abd 提取导出类作为插件 2023-01-17 15:21:02 +08:00
wanglin2
bd655839cb 提取出键盘导航类作为插件 2023-01-17 14:39:43 +08:00
wanglin2
a53a4e8e1d 提取拖拽类Drag作为插件 2023-01-17 14:30:13 +08:00
wanglin2
eb01646747 提取水印类作为插件 2023-01-17 14:11:07 +08:00
wanglin2
d27eee0fae 开始进行插件化转换,即提取非核心的类作为插件,不再内置;抽取出校地图类插件 2023-01-17 13:56:57 +08:00
wanglin2
92ee241ed8 将xmind解析方法从MindMap类上移除,改为按需引入方式使用 2023-01-17 11:19:59 +08:00
wanglin2
660906e4c5 【春节快乐】修改文档 2023-01-17 10:55:28 +08:00
wanglin2
971f0d3446 打包0.2.24 2023-01-17 09:24:33 +08:00
wanglin2
63eccede7f 完成水印功能、新增水印文档 2023-01-17 09:20:37 +08:00
wanglin2
5bd6adb488 节点自由拖拽支持配置是否开启 2023-01-14 16:26:51 +08:00
wanglin2
fbdc5e0f39 完善文档 2023-01-14 14:44:53 +08:00
wanglin2
5e994322fe 全新文档 2023-01-14 11:33:05 +08:00
wanglin2
fd0a594b56 打包0.2.23 2023-01-12 15:50:10 +08:00
wanglin2
d7b93ba0a0 新增支持注册新主题 2023-01-12 15:45:41 +08:00
wanglin2
c14fb5ad9d 修改文档 2023-01-12 09:07:10 +08:00
wanglin2
4d82dfdf9e 修复文档错误 2023-01-11 18:01:56 +08:00
wanglin2
7d3f08bed0 去除文档中的当前版本信息 2023-01-11 16:59:52 +08:00
wanglin2
60597616ec 取消内置在simple-mind-map包内的主题和结构图片 2023-01-11 16:55:44 +08:00
wanglin2
6d758a988c 打包 2023-01-11 14:23:27 +08:00
wanglin2
503506dfd4 区分全屏查看和全屏编辑 2023-01-11 14:12:42 +08:00
wanglin2
df32655321 文档新增changelog 2023-01-11 10:25:24 +08:00
wanglin2
1c7edf47e3 文档新增常见错误解决方案 2023-01-11 09:21:50 +08:00
wanglin2
4f5f9543f7 上传package-lock.json文件 2023-01-11 09:16:45 +08:00
wanglin2
0d3c1b7417 优化注释,去掉冗余信息 2023-01-10 11:08:55 +08:00
街角小林
e634fee753 Merge pull request #56 from emircanerkul/main
Readme Translation
2023-01-10 10:05:23 +08:00
Emircan ERKUL
81c17dc643 Merge branch 'main' of github.com:emircanerkul/mind-map 2023-01-09 13:53:40 +03:00
Emircan ERKUL
cb106c7fac translate readme documentation 2023-01-09 13:48:36 +03:00
wanglin2
5a9189b7fe 修改文档 2023-01-09 14:36:30 +08:00
wanglin2
160ed0d0e4 修改文档 2023-01-05 17:12:50 +08:00
wanglin25
ed92286178 打包 2023-01-05 16:16:39 +08:00
wanglin25
a837a6fb64 新增支持节点横线风格 2023-01-05 16:12:31 +08:00
249 changed files with 44824 additions and 3685 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,2 @@
node_modules
.DS_Store
package-lock.json
.DS_Store

1034
README.md

File diff suppressed because it is too large Load Diff

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/js/chunk-2d20ec02.10aa67e3.js" rel="prefetch"><link href="dist/js/chunk-2d216b67.2d06497a.js" rel="prefetch"><link href="dist/js/chunk-5397ae43.e756f28b.js" rel="prefetch"><link href="dist/css/app.f735ed26.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.f631d5ff.css" rel="preload" as="style"><link href="dist/js/app.2fa527aa.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.68a34765.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.f631d5ff.css" rel="stylesheet"><link href="dist/css/app.f735ed26.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.68a34765.js"></script><script src="dist/js/app.2fa527aa.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"><link rel="icon" href="./dist/logo.png"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d0a3179.3b27bed0.js" rel="prefetch"><link href="dist/js/chunk-2d0aa579.82a73ddc.js" rel="prefetch"><link href="dist/js/chunk-2d0aa978.84fc06da.js" rel="prefetch"><link href="dist/js/chunk-2d0ab10b.a0694d8e.js" rel="prefetch"><link href="dist/js/chunk-2d0abe0f.a9abff6a.js" rel="prefetch"><link href="dist/js/chunk-2d0b361e.d657e190.js" rel="prefetch"><link href="dist/js/chunk-2d0b91e5.433fdc5c.js" rel="prefetch"><link href="dist/js/chunk-2d0b92c3.9d7f8382.js" rel="prefetch"><link href="dist/js/chunk-2d0ba309.3542b9be.js" rel="prefetch"><link href="dist/js/chunk-2d0bd54e.906e86ec.js" rel="prefetch"><link href="dist/js/chunk-2d0be174.0cf53d60.js" rel="prefetch"><link href="dist/js/chunk-2d0c0a44.bdfd7cfb.js" rel="prefetch"><link href="dist/js/chunk-2d0c14fc.7163274e.js" rel="prefetch"><link href="dist/js/chunk-2d0c18d8.8456c878.js" rel="prefetch"><link href="dist/js/chunk-2d0c191e.f425dd57.js" rel="prefetch"><link href="dist/js/chunk-2d0c1a01.e37b19a2.js" rel="prefetch"><link href="dist/js/chunk-2d0c20be.76666437.js" rel="prefetch"><link href="dist/js/chunk-2d0d9fbc.22793f14.js" rel="prefetch"><link href="dist/js/chunk-2d0da701.80759043.js" rel="prefetch"><link href="dist/js/chunk-2d0dad5f.2e4d6938.js" rel="prefetch"><link href="dist/js/chunk-2d0db0f2.b5ce4946.js" rel="prefetch"><link href="dist/js/chunk-2d0dddce.3eea98de.js" rel="prefetch"><link href="dist/js/chunk-2d0ddf37.6ecb5986.js" rel="prefetch"><link href="dist/js/chunk-2d0de01b.a2a047cf.js" rel="prefetch"><link href="dist/js/chunk-2d0e2326.cfbc28b0.js" rel="prefetch"><link href="dist/js/chunk-2d0e268c.eba27ee7.js" rel="prefetch"><link href="dist/js/chunk-2d0e5089.10135360.js" rel="prefetch"><link href="dist/js/chunk-2d0e9742.9abceada.js" rel="prefetch"><link href="dist/js/chunk-2d0f026c.5f73597d.js" rel="prefetch"><link href="dist/js/chunk-2d2082b9.f52387a2.js" rel="prefetch"><link href="dist/js/chunk-2d208ffa.7f14d671.js" rel="prefetch"><link href="dist/js/chunk-2d20ec02.917aff76.js" rel="prefetch"><link href="dist/js/chunk-2d20f68f.ff46a11f.js" rel="prefetch"><link href="dist/js/chunk-2d210a7a.6a4911c4.js" rel="prefetch"><link href="dist/js/chunk-2d216004.1c1e194c.js" rel="prefetch"><link href="dist/js/chunk-2d217907.32a00939.js" rel="prefetch"><link href="dist/js/chunk-2d226d0a.6b1238d2.js" rel="prefetch"><link href="dist/js/chunk-2d2299c3.8f3151dd.js" rel="prefetch"><link href="dist/js/chunk-2d22bd06.29ee05f7.js" rel="prefetch"><link href="dist/js/chunk-2d2308b0.2797f6b4.js" rel="prefetch"><link href="dist/js/chunk-2d238428.7c9ae7c7.js" rel="prefetch"><link href="dist/js/chunk-3a2f3e67.13278516.js" rel="prefetch"><link href="dist/css/app.10014a3e.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.c097b26d.css" rel="preload" as="style"><link href="dist/js/app.4d5a4cb3.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.524ee6e1.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.c097b26d.css" rel="stylesheet"><link href="dist/css/app.10014a3e.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.524ee6e1.js"></script><script src="dist/js/app.4d5a4cb3.js"></script></body></html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -925,5 +925,6 @@ export default {
"layout": "logicalStructure",
// "layout": "mindMap",
// "layout": "catalogOrganization"
// "layout": "organizationStructure"
// "layout": "organizationStructure",
"config": {}
}

View File

@@ -62,5 +62,6 @@
"sx": 0,
"sy": 0
}
}
},
"config": {}
}

20
simple-mind-map/full.js Normal file
View File

@@ -0,0 +1,20 @@
import MindMap from './index'
import MiniMap from './src/MiniMap.js'
import Watermark from './src/Watermark.js'
import KeyboardNavigation from './src/KeyboardNavigation.js'
import Export from './src/Export.js'
import Drag from './src/Drag.js'
import Select from './src/Select.js'
import xmind from './src/parse/xmind.js'
MindMap.xmind = xmind
MindMap
.usePlugin(MiniMap)
.usePlugin(Watermark)
.usePlugin(Drag)
.usePlugin(KeyboardNavigation)
.usePlugin(Export)
.usePlugin(Select)
export default MindMap

View File

@@ -7,15 +7,10 @@ import Style from './src/Style'
import KeyCommand from './src/KeyCommand'
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 MiniMap from './src/MiniMap'
import { layoutValueList } from './src/utils/constant'
import { SVG } from '@svgdotjs/svg.js'
import xmind from './src/parse/xmind'
import { simpleDeepClone } from './src/utils'
import KeyboardNavigation from './src/KeyboardNavigation'
import defaultTheme from './src/themes/default'
// 默认选项配置
const defaultOpt = {
@@ -44,28 +39,34 @@ const defaultOpt = {
// 多选节点时鼠标移动距边缘多少距离时开始偏移
selectTranslateLimit: 20,
// 自定义节点备注内容显示
customNoteContentShow: null
customNoteContentShow: null,
/*
{
show(){},
hide(){}
}
*/
// 是否开启节点自由拖拽
enableFreeDrag: false,
// 水印配置
watermarkConfig: {
text: '',
lineSpacing: 100,
textSpacing: 100,
angle: 30,
textStyle: {
color: '#999',
opacity: 0.5,
fontSize: 14
}
},
// 达到该宽度文本自动换行
textAutoWrapWidth: 500
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 11:18:47
* @Desc: 思维导图
*/
// 思维导图
class MindMap {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 11:19:01
* @Desc: 构造函数
*/
// 构造函数
constructor(opt = {}) {
// 合并选项
this.opt = this.handleOpt(merge(defaultOpt, opt))
@@ -114,34 +115,14 @@ class MindMap {
draw: this.draw
})
// 小地图类
this.miniMap = new MiniMap({
mindMap: this
})
// 导出类
this.doExport = new Export({
mindMap: this
})
// 选择类
this.select = new Select({
mindMap: this
})
// 拖动类
this.drag = new Drag({
mindMap: this
})
// 键盘导航类
this.keyboardNavigation = new KeyboardNavigation({
mindMap: this
})
// 批量执行类
this.batchExecution = new BatchExecution()
// 注册插件
MindMap.pluginList.forEach((plugin) => {
this.initPlugin(plugin)
})
// 初始渲染
this.reRender()
setTimeout(() => {
@@ -149,11 +130,7 @@ class MindMap {
}, 0)
}
/**
* @Author: 王林
* @Date: 2021-07-01 22:15:22
* @Desc: 配置参数处理
*/
// 配置参数处理
handleOpt(opt) {
// 检查布局配置
if (!layoutValueList.includes(opt.layout)) {
@@ -164,39 +141,26 @@ class MindMap {
return opt
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 18:47:29
* @Desc: 渲染,部分渲染
*/
render() {
// 渲染,部分渲染
render(callback) {
this.batchExecution.push('render', () => {
this.initTheme()
this.renderer.reRender = false
this.renderer.render()
this.renderer.render(callback)
})
}
/**
* @Author: 王林
* @Date: 2021-07-08 22:05:11
* @Desc: 重新渲染
*/
reRender() {
// 重新渲染
reRender(callback) {
this.batchExecution.push('render', () => {
this.draw.clear()
this.initTheme()
this.renderer.reRender = true
this.renderer.render()
this.renderer.render(callback)
})
}
/**
* @Author: 王林
* @Date: 2021-07-11 21:16:52
* @Desc: 容器尺寸变化,调整尺寸
*/
// 容器尺寸变化,调整尺寸
resize() {
this.elRect = this.el.getBoundingClientRect()
this.width = this.elRect.width
@@ -204,38 +168,22 @@ class MindMap {
this.svg.size(this.width, this.height)
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:25:50
* @Desc: 监听事件
*/
// 监听事件
on(event, fn) {
this.event.on(event, fn)
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:51:35
* @Desc: 触发事件
*/
// 触发事件
emit(event, ...args) {
this.event.emit(event, ...args)
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:53:54
* @Desc: 解绑事件
*/
// 解绑事件
off(event, fn) {
this.event.off(event, fn)
}
/**
* @Author: 王林
* @Date: 2021-05-05 13:32:43
* @Desc: 设置主题
*/
// 设置主题
initTheme() {
// 合并主题配置
this.themeConfig = merge(theme[this.opt.theme], this.opt.themeConfig)
@@ -243,70 +191,50 @@ class MindMap {
Style.setBackgroundStyle(this.el, this.themeConfig)
}
/**
* @Author: 王林
* @Date: 2021-05-05 13:52:08
* @Desc: 设置主题
*/
// 设置主题
setTheme(theme) {
this.renderer.clearAllActive()
this.opt.theme = theme
this.reRender()
}
/**
* @Author: 王林
* @Date: 2021-06-25 23:52:37
* @Desc: 获取当前主题
*/
// 获取当前主题
getTheme() {
return this.opt.theme
}
/**
* @Author: 王林
* @Date: 2021-05-05 13:50:17
* @Desc: 设置主题配置
*/
// 设置主题配置
setThemeConfig(config) {
this.opt.themeConfig = config
this.reRender()
}
/**
* @Author: 王林
* @Date: 2021-08-01 10:38:34
* @Desc: 获取自定义主题配置
*/
// 获取自定义主题配置
getCustomThemeConfig() {
return this.opt.themeConfig
}
/**
* @Author: 王林
* @Date: 2021-05-05 14:01:29
* @Desc: 获取某个主题配置值
*/
// 获取某个主题配置值
getThemeConfig(prop) {
return prop === undefined ? this.themeConfig : this.themeConfig[prop]
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-13 16:17:06
* @Desc: 获取当前布局结构
*/
// 获取配置
getConfig(prop) {
return prop === undefined ? this.opt : this.opt[prop]
}
// 更新配置
updateConfig(opt = {}) {
this.opt = this.handleOpt(merge.all([defaultOpt, this.opt, opt]))
}
// 获取当前布局结构
getLayout() {
return this.opt.layout
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-13 16:17:33
* @Desc: 设置布局结构
*/
// 设置布局结构
setLayout(layout) {
// 检查布局配置
if (!layoutValueList.includes(layout)) {
@@ -317,20 +245,12 @@ class MindMap {
this.render()
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:01:00
* @Desc: 执行命令
*/
// 执行命令
execCommand(...args) {
this.command.exec(...args)
}
/**
* @Author: 王林
* @Date: 2021-08-03 22:58:12
* @Desc: 动态设置思维导图数据,纯节点数据
*/
// 动态设置思维导图数据,纯节点数据
setData(data) {
this.execCommand('CLEAR_ACTIVE_NODE')
this.command.clearHistory()
@@ -338,12 +258,7 @@ class MindMap {
this.reRender()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-21 16:39:13
* @Desc: 动态设置思维导图数据,包括节点数据、布局、主题、视图
*/
// 动态设置思维导图数据,包括节点数据、布局、主题、视图
setFullData(data) {
if (data.root) {
this.setData(data.root)
@@ -364,12 +279,7 @@ class MindMap {
}
}
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-24 14:42:07
* @Desc: 获取思维导图数据,节点树、主题、布局等
*/
// 获取思维导图数据,节点树、主题、布局等
getData(withConfig) {
let nodeData = this.command.getCopyData()
let data = {}
@@ -389,21 +299,13 @@ class MindMap {
return simpleDeepClone(data)
}
/**
* @Author: 王林
* @Date: 2021-07-01 22:06:38
* @Desc: 导出
*/
// 导出
async export(...args) {
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,
@@ -411,12 +313,7 @@ class MindMap {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-06-08 14:12:38
* @Desc: 设置只读模式、编辑模式
*/
// 设置只读模式、编辑模式
setMode(mode) {
if (!['readonly', 'edit'].includes(mode)) {
return
@@ -428,8 +325,105 @@ class MindMap {
}
this.emit('mode_change', mode)
}
// 获取svg数据
getSvgData() {
const svg = this.svg
const draw = this.draw
// 保存原始信息
const origWidth = svg.width()
const origHeight = svg.height()
const origTransform = draw.transform()
const elRect = this.el.getBoundingClientRect()
// 去除放大缩小的变换效果
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY)
// 获取变换后的位置尺寸信息其实是getBoundingClientRect方法的包装方法
const rect = draw.rbox()
// 将svg设置为实际内容的宽高
svg.size(rect.width, rect.height)
// 把实际内容变换
draw.translate(-rect.x + elRect.left, -rect.y + elRect.top)
// 克隆一份数据
let clone = svg.clone()
// 如果实际图形宽高超出了屏幕宽高,且存在水印的话需要重新绘制水印,否则会出现超出部分没有水印的问题
if ((rect.width > origWidth || rect.height > origHeight) && this.watermark && this.watermark.hasWatermark()) {
this.width = rect.width
this.height = rect.height
this.watermark.draw()
clone = svg.clone()
this.width = origWidth
this.height = origHeight
this.watermark.draw()
}
// 恢复原先的大小和变换信息
svg.size(origWidth, origHeight)
draw.transform(origTransform)
return {
svg: clone, // 思维导图图形的整体svg元素包括svg画布容器、g实际的思维导图组
svgHTML: clone.svg(), // svg字符串
rect: {
...rect, // 思维导图图形未缩放时的位置尺寸等信息
ratio: rect.width / rect.height // 思维导图图形的宽高比
},
origWidth, // 画布宽度
origHeight, // 画布高度
scaleX: origTransform.scaleX, // 思维导图图形的水平缩放值
scaleY: origTransform.scaleY // 思维导图图形的垂直缩放值
}
}
// 添加插件
addPlugin(plugin, opt) {
let index = MindMap.hasPlugin(plugin)
if (index === -1) {
MindMap.usePlugin(plugin, opt)
this.initPlugin(plugin)
}
}
// 移除插件
removePlugin(plugin) {
let index = MindMap.hasPlugin(plugin)
if (index !== -1) {
MindMap.pluginList.splice(index, 1)
if (this[plugin.instanceName]) {
if (this[plugin.instanceName].beforePluginRemove) {
this[plugin.instanceName].beforePluginRemove()
}
delete this[plugin.instanceName]
}
}
}
// 实例化插件
initPlugin(plugin) {
this[plugin.instanceName] = new plugin({
mindMap: this,
pluginOpt: plugin.pluginOpt
})
}
}
MindMap.xmind = xmind
// 插件列表
MindMap.pluginList = []
MindMap.usePlugin = (plugin, opt = {}) => {
plugin.pluginOpt = opt
MindMap.pluginList.push(plugin)
return MindMap
}
MindMap.hasPlugin = (plugin) => {
return MindMap.pluginList.findIndex((item) => {
return item === plugin
})
}
// 定义新主题
MindMap.defineTheme = (name, config = {}) => {
if (theme[name]) {
return new Error('该主题名称已存在')
}
theme[name] = merge(defaultTheme, config)
}
export default MindMap

2932
simple-mind-map/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
"version": "0.2.20",
"version": "0.4.0",
"description": "一个简单的web在线思维导图",
"authors": [
{
@@ -22,14 +22,16 @@
"format": "prettier --write ."
},
"module": "index.js",
"main": "./dist/simpleMindMap.umd.min.js",
"__main": "./dist/simpleMindMap.umd.min.js",
"dependencies": {
"@svgdotjs/svg.js": "^3.0.16",
"canvg": "^3.0.7",
"deepmerge": "^1.5.2",
"eventemitter3": "^4.0.7",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.1",
"jszip": "^3.10.1",
"quill": "^1.3.6",
"xml-js": "^1.6.11"
},
"keywords": [

View File

@@ -0,0 +1,46 @@
// 遍历所有js文件
const path = require('path')
const fs = require('fs')
const entryPath = path.resolve(__dirname, '../src')
const transform = dir => {
let dirs = fs.readdirSync(dir)
dirs.forEach(item => {
let file = path.join(dir, item)
if (fs.statSync(file).isDirectory()) {
transform(file)
} else if (/\.js$/.test(file)) {
transformFile(file)
}
})
}
const transformFile = file => {
console.log(file);
let content = fs.readFileSync(file, 'utf-8')
countCodeLines(content)
// transformComments(file, content)
}
// 统计代码行数
let totalLines = 0
const countCodeLines = (content) => {
totalLines += content.split(/\n/).length
}
// 转换注释类型
const transformComments = (file, content) => {
console.log('当前转换文件:', file)
content = content.replace(/\/\*\*[^/]+\*\//g, str => {
let res = /@Desc:([^\n]+)\n/g.exec(str)
if (res.length > 0) {
return '// ' + res[1]
}
})
fs.writeFileSync(file, content)
}
transform(entryPath)
transformFile(path.join(__dirname, '../index.js'))
console.log(totalLines);

View File

@@ -1,8 +1,4 @@
/**
* @Author: 王林
* @Date: 2021-06-27 13:16:23
* @Desc: 在下一个事件循环里执行任务
*/
// 在下一个事件循环里执行任务
const nextTick = function (fn, ctx) {
let pending = false
let timerFunc = null
@@ -33,28 +29,16 @@ const nextTick = function (fn, ctx) {
}
}
/**
* @Author: 王林
* @Date: 2021-06-26 22:40:52
* @Desc: 批量执行
*/
// 批量执行
class BatchExecution {
/**
* @Author: 王林
* @Date: 2021-06-26 22:41:41
* @Desc: 构造函数
*/
// 构造函数
constructor() {
this.has = {}
this.queue = []
this.nextTick = nextTick(this.flush, this)
}
/**
* @Author: 王林
* @Date: 2021-06-27 12:54:04
* @Desc: 添加任务
*/
// 添加任务
push(name, fn) {
if (this.has[name]) {
return
@@ -67,11 +51,7 @@ class BatchExecution {
this.nextTick()
}
/**
* @Author: 王林
* @Date: 2021-06-27 13:09:24
* @Desc: 执行队列
*/
// 执行队列
flush() {
let fns = this.queue.slice(0)
this.queue = []

View File

@@ -1,16 +1,8 @@
import { copyRenderTree, simpleDeepClone } from './utils'
/**
* @Author: 王林
* @Date: 2021-05-04 13:10:06
* @Desc: 命令类
*/
// 命令类
class Command {
/**
* @Author: 王林
* @Date: 2021-05-04 13:10:24
* @Desc: 构造函数
*/
// 构造函数
constructor(opt = {}) {
this.opt = opt
this.mindMap = opt.mindMap
@@ -21,22 +13,14 @@ class Command {
this.registerShortcutKeys()
}
/**
* @Author: 王林
* @Date: 2021-08-03 23:06:55
* @Desc: 清空历史数据
*/
// 清空历史数据
clearHistory() {
this.history = []
this.activeHistoryIndex = 0
this.mindMap.emit('back_forward', 0, 0)
}
/**
* @Author: 王林
* @Date: 2021-08-02 23:23:19
* @Desc: 注册快捷键
*/
// 注册快捷键
registerShortcutKeys() {
this.mindMap.keyCommand.addShortcut('Control+z', () => {
this.mindMap.execCommand('BACK')
@@ -46,11 +30,7 @@ class Command {
})
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:12:30
* @Desc: 执行命令
*/
// 执行命令
exec(name, ...args) {
if (this.commands[name]) {
this.commands[name].forEach(fn => {
@@ -63,11 +43,7 @@ class Command {
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:13:01
* @Desc: 添加命令
*/
// 添加命令
add(name, fn) {
if (this.commands[name]) {
this.commands[name].push(fn)
@@ -76,11 +52,7 @@ class Command {
}
}
/**
* @Author: 王林
* @Date: 2021-07-15 23:02:41
* @Desc: 移除命令
*/
// 移除命令
remove(name, fn) {
if (!this.commands[name]) {
return
@@ -98,12 +70,11 @@ class Command {
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:35:43
* @Desc: 添加回退数据
*/
// 添加回退数据
addHistory() {
if (this.mindMap.opt.readonly) {
return
}
let data = this.getCopyData()
this.history.push(simpleDeepClone(data))
this.activeHistoryIndex = this.history.length - 1
@@ -115,12 +86,11 @@ class Command {
)
}
/**
* @Author: 王林
* @Date: 2021-07-11 22:34:53
* @Desc: 回退
*/
// 回退
back(step = 1) {
if (this.mindMap.opt.readonly) {
return
}
if (this.activeHistoryIndex - step >= 0) {
this.activeHistoryIndex -= step
this.mindMap.emit(
@@ -132,13 +102,11 @@ class Command {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-12 10:45:31
* @Desc: 前进
*/
// 前进
forward(step = 1) {
if (this.mindMap.opt.readonly) {
return
}
let len = this.history.length
if (this.activeHistoryIndex + step <= len - 1) {
this.activeHistoryIndex += step
@@ -147,11 +115,7 @@ class Command {
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 15:02:58
* @Desc: 获取渲染树数据副本
*/
// 获取渲染树数据副本
getCopyData() {
return copyRenderTree({}, this.mindMap.renderer.renderTree)
}

View File

@@ -1,18 +1,11 @@
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
@@ -20,12 +13,8 @@ class Drag extends Base {
this.bindEvent()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-23 19:33:56
* @Desc: 复位
*/
// 复位
reset() {
// 当前拖拽节点
this.node = null
@@ -58,11 +47,8 @@ class Drag extends Base {
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) => {
@@ -110,12 +96,8 @@ class Drag extends Base {
this.mindMap.on('mouseup', this.onMouseup)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-23 19:38:02
* @Desc: 鼠标松开事件
*/
// 鼠标松开事件
onMouseup(e) {
if (!this.isMousedown) {
return
@@ -137,7 +119,7 @@ class Drag extends Base {
// 存在下一个相邻节点,作为其前一个兄弟节点
this.mindMap.renderer.setNodeActive(this.nextNode, false)
this.mindMap.execCommand('INSERT_BEFORE', this.node, this.nextNode)
} else if (_nodeIsDrag) {
} else if (_nodeIsDrag && this.mindMap.opt.enableFreeDrag) {
// 自定义位置
let { x, y } = this.mindMap.toPos(
e.clientX - this.offsetX,
@@ -156,12 +138,8 @@ class Drag extends Base {
this.reset()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-23 19:34:53
* @Desc: 创建克隆节点
*/
// 创建克隆节点
createCloneNode() {
if (!this.clone) {
// 节点
@@ -182,12 +160,8 @@ class Drag extends Base {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-23 19:35:16
* @Desc: 移除克隆节点
*/
// 移除克隆节点
removeCloneNode() {
if (!this.clone) {
return
@@ -197,12 +171,8 @@ class Drag extends Base {
this.placeholder.remove()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-23 18:53:47
* @Desc: 拖动中
*/
// 拖动中
onMove(x, y) {
if (!this.isMousedown) {
return
@@ -228,11 +198,8 @@ class Drag extends Base {
this.checkOverlapNode()
}
/**
* @Author: 王林
* @Date: 2021-07-11 10:20:43
* @Desc: 检测重叠节点
*/
// 检测重叠节点
checkOverlapNode() {
if (!this.drawTransform) {
return
@@ -274,7 +241,8 @@ class Drag extends Base {
}
}
// 检测兄弟节点位置
if (!this.prevNode && !this.nextNode && !node.isRoot) {// && this.node.isBrother(node)
if (!this.prevNode && !this.nextNode && !node.isRoot) {
// && this.node.isBrother(node)
if (left <= checkRight && right >= this.cloneNodeLeft) {
if (this.cloneNodeTop > bottom && this.cloneNodeTop <= bottom + 10) {
this.prevNode = node
@@ -292,4 +260,6 @@ class Drag extends Base {
}
}
Drag.instanceName = 'drag'
export default Drag

View File

@@ -1,18 +1,8 @@
import EventEmitter from 'eventemitter3'
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:53:09
* @Desc: 事件类
*/
// 事件类
class Event extends EventEmitter {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:53:25
* @Desc: 构造函数
*/
// 构造函数
constructor(opt = {}) {
super()
this.opt = opt
@@ -34,12 +24,7 @@ class Event extends EventEmitter {
this.bind()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:52:24
* @Desc: 绑定函数上下文
*/
// 绑定函数上下文
bindFn() {
this.onDrawClick = this.onDrawClick.bind(this)
this.onMousedown = this.onMousedown.bind(this)
@@ -51,12 +36,7 @@ class Event extends EventEmitter {
this.onKeyup = this.onKeyup.bind(this)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:53:43
* @Desc: 绑定事件
*/
// 绑定事件
bind() {
this.mindMap.svg.on('click', this.onDrawClick)
this.mindMap.el.addEventListener('mousedown', this.onMousedown)
@@ -73,12 +53,7 @@ class Event extends EventEmitter {
window.addEventListener('keyup', this.onKeyup)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:40:51
* @Desc: 解绑事件
*/
// 解绑事件
unbind() {
this.mindMap.svg.off('click', this.onDrawClick)
this.mindMap.el.removeEventListener('mousedown', this.onMousedown)
@@ -89,30 +64,17 @@ class Event extends EventEmitter {
window.removeEventListener('keyup', this.onKeyup)
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:19:39
* @Desc: 画布的单击事件
*/
// 画布的单击事件
onDrawClick(e) {
this.emit('draw_click', e)
}
/**
* @Author: 王林
* @Date: 2021-07-16 13:37:30
* @Desc: svg画布的鼠标按下事件
*/
// svg画布的鼠标按下事件
onSvgMousedown(e) {
this.emit('svg_mousedown', e)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:17:35
* @Desc: 鼠标按下事件
*/
// 鼠标按下事件
onMousedown(e) {
// e.preventDefault()
// 鼠标左键
@@ -124,12 +86,7 @@ class Event extends EventEmitter {
this.emit('mousedown', e, this)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:18:32
* @Desc: 鼠标移动事件
*/
// 鼠标移动事件
onMousemove(e) {
// e.preventDefault()
this.mousemovePos.x = e.clientX
@@ -142,23 +99,13 @@ class Event extends EventEmitter {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:18:57
* @Desc: 鼠标松开事件
*/
// 鼠标松开事件
onMouseup(e) {
this.isLeftMousedown = false
this.emit('mouseup', e, this)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:46:27
* @Desc: 鼠标滚动
*/
// 鼠标滚动
onMousewheel(e) {
e.stopPropagation()
e.preventDefault()
@@ -171,22 +118,13 @@ class Event extends EventEmitter {
this.emit('mousewheel', e, dir, this)
}
/**
* @Author: 王林
* @Date: 2021-07-10 22:34:13
* @Desc: 鼠标右键菜单事件
*/
// 鼠标右键菜单事件
onContextmenu(e) {
e.preventDefault()
this.emit('contextmenu', e)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 11:12:11
* @Desc: 按键松开事件
*/
// 按键松开事件
onKeyup(e) {
this.emit('keyup', e)
}

View File

@@ -1,29 +1,18 @@
import { imgToDataUrl, downloadFile } from './utils'
import JsPDF from 'jspdf'
import { SVG } from '@svgdotjs/svg.js'
import drawBackgroundImageToCanvas from './utils/simulateCSSBackgroundInCanvas'
const URL = window.URL || window.webkitURL || window
/**
* @Author: 王林
* @Date: 2021-07-01 22:05:16
* @Desc: 导出类
*/
// 导出类
class Export {
/**
* @Author: 王林
* @Date: 2021-07-01 22:05:42
* @Desc: 构造函数
*/
// 构造函数
constructor(opt) {
this.mindMap = opt.mindMap
this.exportPadding = this.mindMap.opt.exportPadding
}
/**
* @Author: 王林
* @Date: 2021-07-02 07:44:06
* @Desc: 导出
*/
// 导出
async export(type, isDownload = true, name = '思维导图', ...args) {
if (this[type]) {
let result = await this[type](name, ...args)
@@ -36,13 +25,9 @@ class Export {
}
}
/**
* @Author: 王林
* @Date: 2021-07-04 14:57:40
* @Desc: 获取svg数据
*/
async getSvgData() {
let { svg, svgHTML } = this.mindMap.miniMap.getMiniMap()
// 获取svg数据
async getSvgData(domToImage) {
let { svg, svgHTML } = this.mindMap.getSvgData()
// 把图片的url转换成data:url类型否则导出会丢失图片
let imageList = svg.find('image')
let task = imageList.map(async item => {
@@ -51,17 +36,21 @@ class Export {
item.attr('href', imgData)
})
await Promise.all(task)
// 如果开启了富文本编辑需要把svg中的dom元素转换成图片
let nodeWithDomToImg = null
if (domToImage && this.mindMap.richText) {
let res = await this.mindMap.richText.handleSvgDomElements(svg)
nodeWithDomToImg = res.svg
svgHTML = res.svgHTML
}
return {
node: svg,
str: svgHTML
str: svgHTML,
nodeWithDomToImg
}
}
/**
* @Author: 王林
* @Date: 2021-07-04 15:25:19
* @Desc: svg转png
*/
// svg转png
svgToPng(svgSrc) {
return new Promise((resolve, reject) => {
const img = new Image()
@@ -99,17 +88,15 @@ class Export {
})
}
/**
* @Author: 王林
* @Date: 2021-07-04 15:32:07
* @Desc: 在canvas上绘制思维导图背景
*/
// 在canvas上绘制思维导图背景
drawBackgroundToCanvas(ctx, width, height) {
return new Promise((resolve, reject) => {
let {
backgroundColor = '#fff',
backgroundImage,
backgroundRepeat = 'repeat'
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center center',
backgroundSize = 'cover',
} = this.mindMap.themeConfig
// 背景颜色
ctx.save()
@@ -120,34 +107,31 @@ class Export {
// 背景图片
if (backgroundImage && backgroundImage !== 'none') {
ctx.save()
let img = new Image()
img.src = backgroundImage
img.onload = () => {
let pat = ctx.createPattern(img, backgroundRepeat)
ctx.rect(0, 0, width, height)
ctx.fillStyle = pat
ctx.fill()
drawBackgroundImageToCanvas(ctx, width, height, backgroundImage, {
backgroundRepeat,
backgroundPosition,
backgroundSize
}, (err) => {
if (err) {
reject(err)
} else {
resolve()
}
ctx.restore()
resolve()
}
img.onerror = e => {
reject(e)
}
})
} else {
resolve()
}
})
}
// 导出为png
/**
* @Author: 王林
* @Date: 2021-07-01 22:09:51
* @Desc: 导出为png
* 方法1.把svg的图片都转化成data:url格式再转换
* 方法2.把svg的图片提取出来再挨个绘制到canvas里最后一起转换
*/
async png() {
let { str } = await this.getSvgData()
let { str } = await this.getSvgData(true)
// 转换成blob数据
let blob = new Blob([str], {
type: 'image/svg+xml'
@@ -160,12 +144,7 @@ class Export {
return imgDataUrl
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-08 19:23:08
* @Desc: 导出为pdf
*/
// 导出为pdf
async pdf(name) {
let img = await this.png()
let pdf = new JsPDF('', 'pt', 'a4')
@@ -197,11 +176,7 @@ class Export {
image.src = img
}
/**
* @Author: 王林
* @Date: 2021-07-04 15:32:07
* @Desc: 在svg上绘制思维导图背景
*/
// 在svg上绘制思维导图背景
drawBackgroundToSvg(svg) {
return new Promise(async resolve => {
let {
@@ -223,13 +198,22 @@ class Export {
})
}
/**
* @Author: 王林
* @Date: 2021-07-04 14:54:07
* @Desc: 导出为svg
*/
async svg(name) {
let { node } = await this.getSvgData()
// 导出为svg
// domToImage是否将svg中的dom节点转换成图片的形式
// plusCssText附加的css样式如果svg中存在dom节点想要设置一些针对节点的样式可以通过这个参数传入
async svg(name, domToImage = false, plusCssText) {
let { node, nodeWithDomToImg } = await this.getSvgData(domToImage)
// 开启了节点富文本编辑
if (this.mindMap.richText) {
if (domToImage) {
node = nodeWithDomToImg
} else if (plusCssText) {
let foreignObjectList = node.find('foreignObject')
if (foreignObjectList.length > 0) {
foreignObjectList[0].add(SVG(`<style>${plusCssText}</style>`))
}
}
}
node.first().before(SVG(`<title>${name}</title>`))
await this.drawBackgroundToSvg(node)
let str = node.svg()
@@ -240,11 +224,7 @@ class Export {
return URL.createObjectURL(blob)
}
/**
* @Author: 王林
* @Date: 2021-08-03 22:19:17
* @Desc: 导出为json
*/
// 导出为json
json(name, withConfig = true) {
let data = this.mindMap.getData(withConfig)
let str = JSON.stringify(data)
@@ -252,14 +232,12 @@ class Export {
return URL.createObjectURL(blob)
}
/**
* @Author: 王林
* @Date: 2021-08-03 22:24:24
* @Desc: 专有文件其实就是json文件
*/
// 专有文件其实就是json文件
smm(name, withConfig) {
return this.json(name, withConfig)
}
}
Export.instanceName = 'doExport'
export default Export

View File

@@ -1,15 +1,7 @@
import { keyMap } from './utils/keyMap'
/**
* @Author: 王林
* @Date: 2021-04-24 15:20:46
* @Desc: 快捷按键、命令处理类
*/
// 快捷按键、命令处理类
export default class KeyCommand {
/**
* @Author: 王林
* @Date: 2021-04-24 15:21:32
* @Desc: 构造函数
*/
// 构造函数
constructor(opt) {
this.opt = opt
this.mindMap = opt.mindMap
@@ -21,51 +13,29 @@ export default class KeyCommand {
this.bindEvent()
}
/**
* @Author: 王林
* @Date: 2022-08-14 08:57:55
* @Desc: 暂停快捷键响应
*/
// 暂停快捷键响应
pause() {
this.isPause = true
}
/**
* @Author: 王林
* @Date: 2022-08-14 08:58:43
* @Desc: 恢复快捷键响应
*/
// 恢复快捷键响应
recovery() {
this.isPause = false
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-16 16:29:01
* @Desc: 保存当前注册的快捷键数据,然后清空快捷键数据
*/
// 保存当前注册的快捷键数据,然后清空快捷键数据
save() {
this.shortcutMapCache = this.shortcutMap
this.shortcutMap = {}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-16 16:29:38
* @Desc: 恢复保存的快捷键数据,然后清空缓存数据
*/
// 恢复保存的快捷键数据,然后清空缓存数据
restore() {
this.shortcutMap = this.shortcutMapCache
this.shortcutMapCache = {}
}
/**
* @Author: 王林
* @Date: 2021-04-24 15:23:22
* @Desc: 绑定事件
*/
// 绑定事件
bindEvent() {
window.addEventListener('keydown', e => {
if (this.isPause) {
@@ -83,11 +53,7 @@ export default class KeyCommand {
})
}
/**
* @Author: 王林
* @Date: 2021-04-24 19:24:53
* @Desc: 检查键值是否符合
*/
// 检查键值是否符合
checkKey(e, key) {
let o = this.getOriginEventCodeArr(e)
let k = this.getKeyCodeArr(key)
@@ -107,11 +73,7 @@ export default class KeyCommand {
return true
}
/**
* @Author: 王林
* @Date: 2021-04-24 19:15:19
* @Desc: 获取事件对象里的键值数组
*/
// 获取事件对象里的键值数组
getOriginEventCodeArr(e) {
let arr = []
if (e.ctrlKey || e.metaKey) {
@@ -129,11 +91,7 @@ export default class KeyCommand {
return arr
}
/**
* @Author: 王林
* @Date: 2021-04-24 19:40:11
* @Desc: 获取快捷键对应的键值数组
*/
// 获取快捷键对应的键值数组
getKeyCodeArr(key) {
let keyArr = key.split(/\s*\+\s*/)
let arr = []
@@ -143,10 +101,8 @@ export default class KeyCommand {
return arr
}
// 添加快捷键命令
/**
* @Author: 王林
* @Date: 2021-04-24 15:23:00
* @Desc: 添加快捷键命令
* Enter
* Tab | Insert
* Shift + a
@@ -161,12 +117,7 @@ export default class KeyCommand {
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-27 14:06:16
* @Desc: 移除快捷键命令
*/
// 移除快捷键命令
removeShortcut(key, fn) {
key.split(/\s*\|\s*/).forEach(item => {
if (this.shortcutMap[item]) {
@@ -185,11 +136,7 @@ export default class KeyCommand {
})
}
/**
* @Author: 王林
* @Date: 2022-08-14 08:49:58
* @Desc: 获取指定快捷键的处理函数
*/
// 获取指定快捷键的处理函数
getShortcutFn(key) {
let res = []
key.split(/\s*\|\s*/).forEach(item => {

View File

@@ -1,19 +1,9 @@
import { isKey } from './utils/keyMap'
import { bfsWalk } from './utils'
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 11:06:50
* @Desc: 键盘导航类
*/
export default class KeyboardNavigation {
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 11:07:24
* @Desc: 构造函数
*/
// 键盘导航类
class KeyboardNavigation {
// 构造函数
constructor(opt) {
this.opt = opt
this.mindMap = opt.mindMap
@@ -21,12 +11,7 @@ export default class KeyboardNavigation {
this.mindMap.on('keyup', this.onKeyup)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 14:12:27
* @Desc: 处理按键事件
*/
// 处理按键事件
onKeyup(e) {
;['Left', 'Up', 'Right', 'Down'].forEach(dir => {
if (isKey(e, dir)) {
@@ -41,12 +26,7 @@ export default class KeyboardNavigation {
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 14:12:39
* @Desc: 聚焦到下一个节点
*/
// 聚焦到下一个节点
focus(dir) {
// 当前聚焦的节点
let currentActiveNode = this.mindMap.renderer.activeNodeList[0]
@@ -99,12 +79,7 @@ export default class KeyboardNavigation {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-12 16:22:54
* @Desc: 1.简单算法
*/
// 1.简单算法
getFocusNodeBySimpleAlgorithm({
currentActiveNode,
currentActiveNodeRect,
@@ -143,12 +118,7 @@ export default class KeyboardNavigation {
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-12 16:24:54
* @Desc: 2.阴影算法
*/
// 2.阴影算法
getFocusNodeByShadowAlgorithm({
currentActiveNode,
currentActiveNodeRect,
@@ -187,12 +157,7 @@ export default class KeyboardNavigation {
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-13 16:15:36
* @Desc: 3.区域算法
*/
// 3.区域算法
getFocusNodeByAreaAlgorithm({
currentActiveNode,
currentActiveNodeRect,
@@ -229,12 +194,7 @@ export default class KeyboardNavigation {
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 14:12:50
* @Desc: 获取节点的位置信息
*/
// 获取节点的位置信息
getNodeRect(node) {
let { scaleX, scaleY, translateX, translateY } =
this.mindMap.draw.transform()
@@ -247,12 +207,7 @@ export default class KeyboardNavigation {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 14:13:04
* @Desc: 获取两个节点的距离
*/
// 获取两个节点的距离
getDistance(node1Rect, node2Rect) {
let center1 = this.getCenter(node1Rect)
let center2 = this.getCenter(node2Rect)
@@ -261,12 +216,7 @@ export default class KeyboardNavigation {
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 14:13:11
* @Desc: 获取节点的中心点
*/
// 获取节点的中心点
getCenter({ left, right, top, bottom }) {
return {
x: (left + right) / 2,
@@ -274,3 +224,7 @@ export default class KeyboardNavigation {
}
}
}
KeyboardNavigation.instanceName = 'keyboardNavigation'
export default KeyboardNavigation

View File

@@ -1,11 +1,6 @@
// 小地图类
class MiniMap {
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:00:45
* @Desc: 构造函数
*/
// 构造函数
constructor(opt) {
this.mindMap = opt.mindMap
this.isMousedown = false
@@ -19,59 +14,14 @@ class MiniMap {
}
}
// 计算小地图的渲染数据
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:00:43
* @Desc: 获取小地图相关数据
*/
getMiniMap() {
const svg = this.mindMap.svg
const draw = this.mindMap.draw
// 保存原始信息
const origWidth = svg.width()
const origHeight = svg.height()
const origTransform = draw.transform()
const elRect = this.mindMap.el.getBoundingClientRect()
// 去除放大缩小的变换效果
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY)
// 获取变换后的位置尺寸信息其实是getBoundingClientRect方法的包装方法
const rect = draw.rbox()
// 将svg设置为实际内容的宽高
svg.size(rect.width, rect.height)
// 把实际内容变换
draw.translate(-rect.x + elRect.left, -rect.y + elRect.top)
// 克隆一份数据
const clone = svg.clone()
// 恢复原先的大小和变换信息
svg.size(origWidth, origHeight)
draw.transform(origTransform)
return {
svg: clone, // 思维导图图形的整体svg元素包括svg画布容器、g实际的思维导图组
svgHTML: clone.svg(), // svg字符串
rect: {
...rect, // 思维导图图形未缩放时的位置尺寸等信息
ratio: rect.width / rect.height // 思维导图图形的宽高比
},
origWidth, // 画布宽度
origHeight, // 画布高度
scaleX: origTransform.scaleX, // 思维导图图形的水平缩放值
scaleY: origTransform.scaleY // 思维导图图形的垂直缩放值
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:05:51
* @Desc: 计算小地图的渲染数据
* boxWidth小地图容器的宽度
* boxHeight小地图容器的高度
*/
calculationMiniMap(boxWidth, boxHeight) {
let { svgHTML, rect, origWidth, origHeight, scaleX, scaleY } =
this.getMiniMap()
this.mindMap.getSvgData()
// 计算数据
let boxRatio = boxWidth / boxHeight
let actWidth = 0
@@ -124,12 +74,7 @@ class MiniMap {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:22:40
* @Desc: 小地图鼠标按下事件
*/
// 小地图鼠标按下事件
onMousedown(e) {
this.isMousedown = true
this.mousedownPos = {
@@ -144,12 +89,7 @@ class MiniMap {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:22:55
* @Desc: 小地图鼠标移动事件
*/
// 小地图鼠标移动事件
onMousemove(e, sensitivityNum = 5) {
if (!this.isMousedown) {
return
@@ -161,15 +101,12 @@ class MiniMap {
this.mindMap.view.translateYTo(oy * sensitivityNum + this.startViewPos.y)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:23:01
* @Desc: 小地图鼠标松开事件
*/
// 小地图鼠标松开事件
onMouseup() {
this.isMousedown = false
}
}
MiniMap.instanceName = 'miniMap'
export default MiniMap

View File

@@ -1,23 +1,15 @@
import Style from './Style'
import Shape from './Shape'
import { resizeImgSize, asyncRun } from './utils'
import { Image, SVG, Circle, A, G, Rect, Text } from '@svgdotjs/svg.js'
import { resizeImgSize, asyncRun, measureText } from './utils'
import { Image, SVG, Circle, A, G, Rect, Text, ForeignObject } from '@svgdotjs/svg.js'
import btnsSvg from './svg/btns'
import iconsSvg from './svg/icons'
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 11:26:00
* @Desc: 节点类
*/
// 节点类
class Node {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 11:26:17
* @Desc: 构造函数
*/
// 构造函数
constructor(opt = {}) {
// 节点数据
this.nodeData = this.handleData(opt.data || {})
@@ -118,11 +110,8 @@ class Node {
this._top = val
}
/**
* @Author: 王林
* @Date: 2021-07-12 07:40:47
* @Desc: 更新主题配置
*/
// 更新主题配置
updateThemeConfig() {
// 主题配置
this.themeConfig = this.mindMap.themeConfig
@@ -130,11 +119,8 @@ class Node {
this.style.updateThemeConfig(this.themeConfig)
}
/**
* @Author: 王林
* @Date: 2021-07-05 23:11:39
* @Desc: 复位部分布局时会重新设置的数据
*/
// 复位部分布局时会重新设置的数据
reset() {
this.children = []
this.parent = null
@@ -144,11 +130,8 @@ class Node {
this.top = 0
}
/**
* @Author: 王林
* @Date: 2021-06-20 10:12:31
* @Desc: 处理数据
*/
// 处理数据
handleData(data) {
data.data.expand = data.data.expand === false ? false : true
data.data.isActive = data.data.isActive === true ? true : false
@@ -156,22 +139,14 @@ class Node {
return data
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-02 19:53:40
* @Desc: 检查节点是否存在自定义数据
*/
// 检查节点是否存在自定义数据
hasCustomPosition() {
return this.customLeft !== undefined && this.customTop !== undefined
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-04 09:06:56
* @Desc: 检查节点是否存在自定义位置的祖先节点
*/
// 检查节点是否存在自定义位置的祖先节点
ancestorHasCustomPosition() {
let node = this
while (node) {
@@ -183,21 +158,14 @@ class Node {
return false
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 15:55:04
* @Desc: 添加子节点
*/
// 添加子节点
addChildren(node) {
this.children.push(node)
}
/**
* @Author: 王林
* @Date: 2021-07-06 22:08:09
* @Desc: 创建节点的各个内容对象数据
*/
// 创建节点的各个内容对象数据
createNodeData() {
this._imgData = this.createImgNode()
this._iconData = this.createIconNode()
@@ -208,11 +176,8 @@ class Node {
this.createGeneralizationNode()
}
/**
* @Author: 王林
* @Date: 2021-07-10 09:20:02
* @Desc: 解绑所有事件
*/
// 解绑所有事件
removeAllEvent() {
if (this._noteData) {
this._noteData.node.off(['mouseover', 'mouseout'])
@@ -231,11 +196,8 @@ class Node {
}
}
/**
* @Author: 王林
* @Date: 2021-07-07 21:27:24
* @Desc: 移除节点内容
*/
// 移除节点内容
removeAllNode() {
// 节点内的内容
;[
@@ -269,12 +231,8 @@ class Node {
this.removeGeneralization()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 09:46:23
* @Desc: 计算节点的宽高
*/
// 计算节点的宽高
getSize() {
this.removeAllNode()
this.createNodeData()
@@ -286,12 +244,8 @@ class Node {
return changed
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 14:52:17
* @Desc: 计算节点尺寸信息
*/
// 计算节点尺寸信息
getNodeRect() {
// 宽高
let imgContentWidth = 0
@@ -358,12 +312,8 @@ class Node {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:06:17
* @Desc: 创建图片节点
*/
// 创建图片节点
createImgNode() {
let img = this.nodeData.data.image
if (!img) {
@@ -384,12 +334,8 @@ class Node {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 10:12:51
* @Desc: 获取图片显示宽高
*/
// 获取图片显示宽高
getImgShowSize() {
return resizeImgSize(
this.nodeData.data.imageSize.width,
@@ -399,12 +345,8 @@ class Node {
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:10:48
* @Desc: 创建icon节点
*/
// 创建icon节点
createIconNode() {
let _data = this.nodeData.data
if (!_data.icon || _data.icon.length <= 0) {
@@ -420,31 +362,29 @@ class Node {
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:08:56
* @Desc: 创建文本节点
*/
createTextNode() {
// 创建富文本节点
createRichTextNode() {
let g = new G()
let fontSize = this.getStyle(
'fontSize',
this.isRoot,
this.nodeData.data.isActive
)
let lineHeight = this.getStyle(
'lineHeight',
this.isRoot,
this.nodeData.data.isActive
)
this.nodeData.data.text.split(/\n/gim).forEach((item, index) => {
let node = new Text().text(item)
this.style.text(node)
node.y(fontSize * lineHeight * index)
g.add(node)
})
let { width, height } = g.bbox()
let html = `<div>${this.nodeData.data.text}</div>`
let div = document.createElement('div')
div.innerHTML = html
div.style.cssText = `position: fixed; left: -999999px;`
let el = div.children[0]
el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
el.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
this.mindMap.el.appendChild(div)
let { width, height } = el.getBoundingClientRect()
width = Math.ceil(width)
height = Math.ceil(height)
g.attr('data-width', width)
g.attr('data-height', height)
html = div.innerHTML
this.mindMap.el.removeChild(div)
let foreignObject = new ForeignObject()
foreignObject.width(width)
foreignObject.height(height)
foreignObject.add(SVG(html))
g.add(foreignObject)
return {
node: g,
width,
@@ -452,11 +392,65 @@ class Node {
}
}
/**
* @Author: 王林
* @Date: 2021-06-20 15:28:54
* @Desc: 创建超链接节点
*/
// 创建文本节点
createTextNode() {
if (this.nodeData.data.richText) {
return this.createRichTextNode()
}
let g = new G()
let fontSize = this.getStyle(
'fontSize',
false,
this.nodeData.data.isActive
)
let lineHeight = this.getStyle(
'lineHeight',
false,
this.nodeData.data.isActive
)
// 文本超长自动换行
let textStyle = this.style.getTextFontStyle()
let textArr = this.nodeData.data.text.split(/\n/gim)
let maxWidth = this.mindMap.opt.textAutoWrapWidth
textArr.forEach((item, index) => {
let arr = item.split('')
let lines = []
let line = []
while(arr.length) {
line.push(arr.shift())
let text = line.join('')
if (measureText(text, textStyle).width >= maxWidth) {
lines.push(text)
line = []
}
}
if (line.length > 0) {
lines.push(line.join(''))
}
textArr[index] = lines.join('\n')
})
textArr = textArr.join('\n').split(/\n/gim)
textArr.forEach((item, index) => {
let node = new Text().text(item)
this.style.text(node)
node.y(fontSize * lineHeight * index)
g.add(node)
})
let { width, height } = g.bbox()
width = Math.ceil(width)
height = Math.ceil(height)
g.attr('data-width', width)
g.attr('data-height', height)
return {
node: g,
width,
height
}
}
// 创建超链接节点
createHyperlinkNode() {
let { hyperlink, hyperlinkTitle } = this.nodeData.data
if (!hyperlink) {
@@ -486,11 +480,8 @@ class Node {
}
}
/**
* @Author: 王林
* @Date: 2021-06-20 19:49:15
* @Desc: 创建标签节点
*/
// 创建标签节点
createTagNode() {
let tagData = this.nodeData.data.tag
if (!tagData || tagData.length <= 0) {
@@ -516,11 +507,8 @@ class Node {
return nodes
}
/**
* @Author: 王林
* @Date: 2021-06-20 21:19:36
* @Desc: 创建备注节点
*/
// 创建备注节点
createNoteNode() {
if (!this.nodeData.data.note) {
return null
@@ -577,22 +565,17 @@ class Node {
}
}
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 22:02:07
* @Desc: 获取节点形状
*/
// 获取节点形状
getShape() {
return this.style.getStyle('shape', false, false)
// 节点使用功能横线风格的话不支持设置形状,直接使用默认的矩形
return this.themeConfig.nodeUseLineStyle
? 'rectangle'
: this.style.getStyle('shape', false, false)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 11:10:11
* @Desc: 定位节点内容
*/
// 定位节点内容
layout() {
let { width, textContentItemMargin } = this
let { paddingY } = this.getPaddingVale()
@@ -636,6 +619,7 @@ class Node {
}
// 文字
if (this._textData) {
this._textData.node.attr('data-offsetx', textContentOffsetX)
this._textData.node.x(textContentOffsetX).y(0)
textContentNested.add(this._textData.node)
textContentOffsetX += this._textData.width + textContentItemMargin
@@ -716,11 +700,8 @@ class Node {
})
}
/**
* @Author: 王林
* @Date: 2021-07-10 16:44:22
* @Desc: 激活节点
*/
// 激活节点
active(e) {
if (this.mindMap.opt.readonly) {
return
@@ -736,11 +717,8 @@ class Node {
this.mindMap.emit('node_active', this, this.renderer.activeNodeList)
}
/**
* @Author: 王林
* @Date: 2021-07-04 20:20:09
* @Desc: 渲染节点到画布,会移除旧的,创建新的
*/
// 渲染节点到画布,会移除旧的,创建新的
renderNode() {
// 连线
this.renderLine()
@@ -750,11 +728,8 @@ class Node {
this.layout()
}
/**
* @Author: 王林
* @Date: 2021-07-04 22:47:01
* @Desc: 更新节点
*/
// 更新节点
update(layout = false) {
if (!this.group) {
return
@@ -773,18 +748,20 @@ class Node {
if (!layout) {
this.group
.animate(300)
.translate(this.left - t.translateX, this.top - t.translateY)
.translate(
this.left - t.translateX,
this.top - t.translateY
)
} else {
this.group.translate(this.left - t.translateX, this.top - t.translateY)
this.group.translate(
this.left - t.translateX,
this.top - t.translateY
)
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 13:55:58
* @Desc: 递归渲染
*/
// 递归渲染
render(callback = () => {}) {
// 节点
if (this.initRender) {
@@ -821,15 +798,14 @@ class Node {
if (this.nodeData.inserting) {
delete this.nodeData.inserting
this.active()
this.mindMap.emit('node_dblclick', this)
setTimeout(() => {
this.mindMap.emit('node_dblclick', this)
}, 0)
}
}
/**
* @Author: 王林
* @Date: 2021-07-10 09:24:55
* @Desc: 递归删除
*/
// 递归删除
remove() {
this.initRender = true
this.removeAllEvent()
@@ -847,12 +823,8 @@ class Node {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-23 18:39:14
* @Desc: 隐藏节点
*/
// 隐藏节点
hide() {
this.group.hide()
this.hideGeneralization()
@@ -872,12 +844,8 @@ class Node {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-23 18:39:14
* @Desc: 显示节点
*/
// 显示节点
show() {
if (!this.group) {
return
@@ -900,11 +868,8 @@ class Node {
}
}
/**
* @Author: 王林
* @Date: 2021-04-10 22:01:53
* @Desc: 连线
*/
// 连线
renderLine(deep = false) {
if (this.nodeData.data.expand === false) {
return
@@ -940,12 +905,8 @@ class Node {
}
}
/**
* javascript comment
* @Author: flydreame
* @Date: 2022-09-17 12:41:29
* @Desc: 设置连线样式
*/
// 设置连线样式
styleLine(line, node) {
let width =
node.getSelfInhertStyle('lineWidth') || node.getStyle('lineWidth', true)
@@ -961,11 +922,8 @@ class Node {
})
}
/**
* @Author: 王林
* @Date: 2021-07-10 16:40:21
* @Desc: 移除连线
*/
// 移除连线
removeLine() {
this._lines.forEach(line => {
line.remove()
@@ -973,21 +931,14 @@ class Node {
this._lines = []
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-01 09:27:30
* @Desc: 检查是否存在概要
*/
// 检查是否存在概要
checkHasGeneralization() {
return !!this.nodeData.data.generalization
}
/**
* @Author: 王林
* @Date: 2022-07-31 09:41:28
* @Desc: 创建概要节点
*/
// 创建概要节点
createGeneralizationNode() {
if (this.isGeneralization || !this.checkHasGeneralization()) {
return
@@ -1015,22 +966,15 @@ class Node {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-01 15:38:52
* @Desc: 更新概要节点
*/
// 更新概要节点
updateGeneralization() {
this.removeGeneralization()
this.createGeneralizationNode()
}
/**
* @Author: 王林
* @Date: 2022-07-30 08:35:51
* @Desc: 渲染概要节点
*/
// 渲染概要节点
renderGeneralization() {
if (this.isGeneralization) {
return
@@ -1055,11 +999,8 @@ class Node {
this._generalizationNode.render()
}
/**
* @Author: 王林
* @Date: 2022-07-30 13:11:27
* @Desc: 删除概要节点
*/
// 删除概要节点
removeGeneralization() {
if (this._generalizationLine) {
this._generalizationLine.remove()
@@ -1079,12 +1020,8 @@ class Node {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-01 09:56:46
* @Desc: 隐藏概要节点
*/
// 隐藏概要节点
hideGeneralization() {
if (this._generalizationLine) {
this._generalizationLine.hide()
@@ -1094,12 +1031,8 @@ class Node {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-01 09:57:42
* @Desc: 显示概要节点
*/
// 显示概要节点
showGeneralization() {
if (this._generalizationLine) {
this._generalizationLine.show()
@@ -1109,11 +1042,8 @@ class Node {
}
}
/**
* @Author: 王林
* @Date: 2021-07-10 17:59:14
* @Desc: 创建或更新展开收缩按钮内容
*/
// 创建或更新展开收缩按钮内容
updateExpandBtnNode() {
if (this._expandBtn) {
this._expandBtn.clear()
@@ -1132,12 +1062,8 @@ class Node {
if (this._expandBtn) this._expandBtn.add(fillNode).add(node)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-12 18:18:13
* @Desc: 更新展开收缩按钮位置
*/
// 更新展开收缩按钮位置
updateExpandBtnPos() {
if (!this._expandBtn) {
return
@@ -1145,11 +1071,8 @@ class Node {
this.renderer.layout.renderExpandBtn(this, this._expandBtn)
}
/**
* @Author: 王林
* @Date: 2021-04-11 19:47:01
* @Desc: 展开收缩按钮
*/
// 展开收缩按钮
renderExpandBtn() {
if (
!this.nodeData.children ||
@@ -1186,11 +1109,8 @@ class Node {
this.updateExpandBtnPos()
}
/**
* @Author: 王林
* @Date: 2021-07-11 13:26:00
* @Desc: 移除展开收缩按钮
*/
// 移除展开收缩按钮
removeExpandBtn() {
if (this._expandBtn) {
this._expandBtn.off(['mouseover', 'mouseout', 'click'])
@@ -1200,12 +1120,8 @@ class Node {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-25 09:51:37
* @Desc: 检测当前节点是否是某个节点的祖先节点
*/
// 检测当前节点是否是某个节点的祖先节点
isParent(node) {
if (this === node) {
return false
@@ -1220,12 +1136,8 @@ class Node {
return false
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-25 10:32:34
* @Desc: 检测当前节点是否是某个节点的兄弟节点
*/
// 检测当前节点是否是某个节点的兄弟节点
isBrother(node) {
if (!this.parent || this === node) {
return false
@@ -1235,11 +1147,8 @@ class Node {
})
}
/**
* @Author: 王林
* @Date: 2021-06-20 22:51:57
* @Desc: 获取padding值
*/
// 获取padding值
getPaddingVale() {
return {
paddingX: this.getStyle('paddingX', true, this.nodeData.data.isActive),
@@ -1247,32 +1156,21 @@ class Node {
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 21:48:49
* @Desc: 获取某个样式
*/
// 获取某个样式
getStyle(prop, root, isActive) {
let v = this.style.merge(prop, root, isActive)
return v === undefined ? '' : v
}
/**
* javascript comment
* @Author: flydreame
* @Date: 2022-09-17 11:21:15
* @Desc: 获取自定义样式
*/
// 获取自定义样式
getSelfStyle(prop) {
return this.style.getSelfStyle(prop)
}
/**
* javascript comment
* @Author: flydreame
* @Date: 2022-09-17 11:21:26
* @Desc: 获取最近一个存在自身自定义样式的祖先节点的自定义样式
*/
// 获取最近一个存在自身自定义样式的祖先节点的自定义样式
getParentSelfStyle(prop) {
if (this.parent) {
return (
@@ -1282,12 +1180,8 @@ class Node {
return null
}
/**
* javascript comment
* @Author: flydreame
* @Date: 2022-09-17 12:15:30
* @Desc: 获取自身可继承的自定义样式
*/
// 获取自身可继承的自定义样式
getSelfInhertStyle(prop) {
return (
this.getSelfStyle(prop) || // 自身
@@ -1295,93 +1189,62 @@ class Node {
) // 父级
}
/**
* @Author: 王林
* @Date: 2021-05-04 22:18:07
* @Desc: 修改某个样式
*/
// 修改某个样式
setStyle(prop, value, isActive) {
this.mindMap.execCommand('SET_NODE_STYLE', this, prop, value, isActive)
}
/**
* @Author: 王林
* @Date: 2021-06-22 22:04:02
* @Desc: 获取数据
*/
// 获取数据
getData(key) {
return key ? this.nodeData.data[key] || '' : this.nodeData.data
}
/**
* @Author: 王林
* @Date: 2021-06-22 22:12:01
* @Desc: 设置数据
*/
// 设置数据
setData(data = {}) {
this.mindMap.execCommand('SET_NODE_DATA', this, data)
}
/**
* @Author: 王林
* @Date: 2021-07-10 08:41:28
* @Desc: 设置文本
*/
// 设置文本
setText(text) {
this.mindMap.execCommand('SET_NODE_TEXT', this, text)
}
/**
* @Author: 王林
* @Date: 2021-07-10 08:42:19
* @Desc: 设置图片
*/
// 设置图片
setImage(imgData) {
this.mindMap.execCommand('SET_NODE_IMAGE', this, imgData)
}
/**
* @Author: 王林
* @Date: 2021-07-10 08:47:29
* @Desc: 设置图标
*/
// 设置图标
setIcon(icons) {
this.mindMap.execCommand('SET_NODE_ICON', this, icons)
}
/**
* @Author: 王林
* @Date: 2021-07-10 08:50:41
* @Desc: 设置超链接
*/
// 设置超链接
setHyperlink(link, title) {
this.mindMap.execCommand('SET_NODE_HYPERLINK', this, link, title)
}
/**
* @Author: 王林
* @Date: 2021-07-10 08:53:24
* @Desc: 设置备注
*/
// 设置备注
setNote(note) {
this.mindMap.execCommand('SET_NODE_NOTE', this, note)
}
/**
* @Author: 王林
* @Date: 2021-07-10 08:55:08
* @Desc: 设置标签
*/
// 设置标签
setTag(tag) {
this.mindMap.execCommand('SET_NODE_TAG', this, tag)
}
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 21:47:45
* @Desc: 设置形状
*/
// 设置形状
setShape(shape) {
this.mindMap.execCommand('SET_NODE_SHAPE', this, shape)
}

View File

@@ -20,19 +20,11 @@ const layouts = {
organizationStructure: OrganizationStructure
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 16:25:07
* @Desc: 渲染
*/
// 渲染
class Render {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 16:25:32
* @Desc: 构造函数
*/
// 构造函数
constructor(opt = {}) {
this.opt = opt
this.mindMap = opt.mindMap
@@ -58,12 +50,8 @@ class Render {
this.registerShortcutKeys()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-13 16:20:07
* @Desc: 设置布局结构
*/
// 设置布局结构
setLayout() {
this.layout = new (
layouts[this.mindMap.opt.layout]
@@ -72,11 +60,8 @@ class Render {
)(this)
}
/**
* @Author: 王林
* @Date: 2021-06-20 10:34:06
* @Desc: 绑定事件
*/
// 绑定事件
bindEvent() {
// 点击事件
this.mindMap.on('draw_click', () => {
@@ -87,11 +72,8 @@ class Render {
})
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:19:06
* @Desc: 注册命令
*/
// 注册命令
registerCommands() {
// 全选
this.selectAll = this.selectAll.bind(this)
@@ -192,11 +174,8 @@ class Render {
this.mindMap.command.add('SET_NODE_SHAPE', this.setNodeShape)
}
/**
* @Author: 王林
* @Date: 2021-07-11 16:55:44
* @Desc: 注册快捷键
*/
// 注册快捷键
registerShortcutKeys() {
// 插入下级节点
this.mindMap.keyCommand.addShortcut('Tab', () => {
@@ -240,12 +219,8 @@ class Render {
// 复制节点、剪切节点、粘贴节点的快捷键需开发者自行注册实现可参考demo
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-05-09 10:43:52
* @Desc: 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突
*/
// 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突
startTextEdit() {
this.mindMap.keyCommand.save()
// this.mindMap.keyCommand.removeShortcut('Del|Backspace')
@@ -253,12 +228,8 @@ class Render {
// this.mindMap.keyCommand.removeShortcut('Enter', this.insertNodeWrap)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-05-09 10:45:11
* @Desc: 结束文字编辑,会恢复回车键和删除键相关快捷键
*/
// 结束文字编辑,会恢复回车键和删除键相关快捷键
endTextEdit() {
this.mindMap.keyCommand.restore()
// this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap)
@@ -266,13 +237,9 @@ class Render {
// this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 16:27:55
* @Desc: 渲染
*/
render() {
// 渲染
render(callback = () => {}) {
if (this.reRender) {
this.clearActive()
}
@@ -280,16 +247,14 @@ class Render {
this.root = root
this.root.render(() => {
this.mindMap.emit('node_tree_render_end')
callback()
})
})
this.mindMap.emit('node_active', null, this.activeNodeList)
}
/**
* @Author: 王林
* @Date: 2021-04-12 22:45:01
* @Desc: 清除当前激活的节点
*/
// 清除当前激活的节点
clearActive() {
this.activeNodeList.forEach(item => {
this.setNodeActive(item, false)
@@ -297,11 +262,8 @@ class Render {
this.activeNodeList = []
}
/**
* @Author: 王林
* @Date: 2021-08-03 23:14:34
* @Desc: 清除当前所有激活节点,并会触发事件
*/
// 清除当前所有激活节点,并会触发事件
clearAllActive() {
if (this.activeNodeList.length <= 0) {
return
@@ -310,11 +272,8 @@ class Render {
this.mindMap.emit('node_active', null, [])
}
/**
* @Author: 王林
* @Date: 2021-07-11 10:54:00
* @Desc: 添加节点到激活列表里
*/
// 添加节点到激活列表里
addActiveNode(node) {
let index = this.findActiveNodeIndex(node)
if (index === -1) {
@@ -322,11 +281,8 @@ class Render {
}
}
/**
* @Author: 王林
* @Date: 2021-07-10 10:04:04
* @Desc: 在激活列表里移除某个节点
*/
// 在激活列表里移除某个节点
removeActiveNode(node) {
let index = this.findActiveNodeIndex(node)
if (index === -1) {
@@ -335,22 +291,16 @@ class Render {
this.activeNodeList.splice(index, 1)
}
/**
* @Author: 王林
* @Date: 2021-07-11 10:55:23
* @Desc: 检索某个节点在激活列表里的索引
*/
// 检索某个节点在激活列表里的索引
findActiveNodeIndex(node) {
return this.activeNodeList.findIndex(item => {
return item === node
})
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:46:08
* @Desc: 获取节点在同级里的索引位置
*/
// 获取节点在同级里的索引位置
getNodeIndex(node) {
return node.parent
? node.parent.children.findIndex(item => {
@@ -359,11 +309,8 @@ class Render {
: 0
}
/**
* @Author: 王林
* @Date: 2021-08-04 23:54:52
* @Desc: 全选
*/
// 全选
selectAll() {
walk(
this.root,
@@ -384,11 +331,8 @@ class Render {
)
}
/**
* @Author: 王林
* @Date: 2021-07-11 22:34:12
* @Desc: 回退
*/
// 回退
back(step) {
this.clearAllActive()
let data = this.mindMap.command.back(step)
@@ -398,12 +342,8 @@ class Render {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-12 10:44:51
* @Desc: 前进
*/
// 前进
forward(step) {
this.clearAllActive()
let data = this.mindMap.command.forward(step)
@@ -413,11 +353,8 @@ class Render {
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:19:54
* @Desc: 插入同级节点,多个节点只会操作第一个节点
*/
// 插入同级节点,多个节点只会操作第一个节点
insertNode() {
if (this.activeNodeList.length <= 0) {
return
@@ -443,11 +380,8 @@ class Render {
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:31:02
* @Desc: 插入子节点
*/
// 插入子节点
insertChildNode() {
if (this.activeNodeList.length <= 0) {
return
@@ -477,11 +411,8 @@ class Render {
this.mindMap.render()
}
/**
* @Author: 王林
* @Date: 2021-07-14 23:34:14
* @Desc: 上移节点,多个节点只会操作第一个节点
*/
// 上移节点,多个节点只会操作第一个节点
upNode() {
if (this.activeNodeList.length <= 0) {
return
@@ -508,11 +439,8 @@ class Render {
this.mindMap.render()
}
/**
* @Author: 王林
* @Date: 2021-07-14 23:34:18
* @Desc: 下移节点,多个节点只会操作第一个节点
*/
// 下移节点,多个节点只会操作第一个节点
downNode() {
if (this.activeNodeList.length <= 0) {
return
@@ -539,16 +467,14 @@ 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 nodeLayerChanged = (node.layerIndex === 1 && exist.layerIndex !== 1) || (node.layerIndex !== 1 && exist.layerIndex === 1)
// 移动节点
let nodeParent = node.parent
let nodeBorthers = nodeParent.children
@@ -561,7 +487,6 @@ class Render {
nodeBorthers.splice(nodeIndex, 1)
nodeParent.nodeData.children.splice(nodeIndex, 1)
// 目标节点
let existParent = exist.parent
let existBorthers = existParent.children
@@ -573,19 +498,22 @@ class Render {
}
existBorthers.splice(existIndex, 0, node)
existParent.nodeData.children.splice(existIndex, 0, node.nodeData)
this.mindMap.render()
this.mindMap.render(() => {
if (nodeLayerChanged) {
node.getSize()
node.renderNode()
}
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-25 10:51:34
* @Desc: 将节点移动到另一个节点的后面
*/
// 将节点移动到另一个节点的后面
insertAfter(node, exist) {
if (node.isRoot) {
return
}
// 如果是二级节点变成了下级节点,或是下级节点变成了二级节点,节点样式需要更新
let nodeLayerChanged = (node.layerIndex === 1 && exist.layerIndex !== 1) || (node.layerIndex !== 1 && exist.layerIndex === 1)
// 移动节点
let nodeParent = node.parent
let nodeBorthers = nodeParent.children
@@ -598,7 +526,6 @@ class Render {
nodeBorthers.splice(nodeIndex, 1)
nodeParent.nodeData.children.splice(nodeIndex, 1)
// 目标节点
let existParent = exist.parent
let existBorthers = existParent.children
@@ -611,50 +538,55 @@ class Render {
existIndex++
existBorthers.splice(existIndex, 0, node)
existParent.nodeData.children.splice(existIndex, 0, node.nodeData)
this.mindMap.render()
this.mindMap.render(() => {
if (nodeLayerChanged) {
node.getSize()
node.renderNode()
}
})
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:40:39
* @Desc: 移除节点
*/
// 移除节点
removeNode() {
if (this.activeNodeList.length <= 0) {
return
}
for (let i = 0; i < this.activeNodeList.length; i++) {
let node = this.activeNodeList[i]
if (node.isGeneralization) {
// 删除概要节点
this.setNodeData(node.generalizationBelongNode, {
generalization: null
})
node.generalizationBelongNode.update()
this.removeActiveNode(node)
i--
} else if (node.isRoot) {
node.children.forEach(child => {
child.remove()
})
node.children = []
node.nodeData.children = []
break
} else {
this.removeActiveNode(node)
this.removeOneNode(node)
i--
let root = this.activeNodeList.find((node) => {
return node.isRoot
})
if (root) {
this.clearActive()
root.children.forEach(child => {
child.remove()
})
root.children = []
root.nodeData.children = []
} else {
for (let i = 0; i < this.activeNodeList.length; i++) {
let node = this.activeNodeList[i]
if (node.isGeneralization) {
// 删除概要节点
this.setNodeData(node.generalizationBelongNode, {
generalization: null
})
node.generalizationBelongNode.update()
this.removeActiveNode(node)
i--
} else {
this.removeActiveNode(node)
this.removeOneNode(node)
i--
}
}
}
this.activeNodeList = []
this.mindMap.emit('node_active', null, [])
this.mindMap.render()
}
/**
* @Author: 王林
* @Date: 2021-07-15 22:46:27
* @Desc: 移除某个指定节点
*/
// 移除某个指定节点
removeOneNode(node) {
let index = this.getNodeIndex(node)
node.remove()
@@ -662,12 +594,8 @@ class Render {
node.parent.nodeData.children.splice(index, 1)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-15 09:53:23
* @Desc: 复制节点,多个节点只会操作第一个节点
*/
// 复制节点,多个节点只会操作第一个节点
copyNode() {
if (this.activeNodeList.length <= 0) {
return
@@ -675,11 +603,8 @@ class Render {
return copyNodeTree({}, this.activeNodeList[0], true)
}
/**
* @Author: 王林
* @Date: 2021-07-15 22:36:45
* @Desc: 剪切节点,多个节点只会操作第一个节点
*/
// 剪切节点,多个节点只会操作第一个节点
cutNode(callback) {
if (this.activeNodeList.length <= 0) {
return
@@ -698,12 +623,8 @@ class Render {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-24 16:54:01
* @Desc: 移动一个节点作为另一个节点的子节点
*/
// 移动一个节点作为另一个节点的子节点
moveNodeTo(node, toNode) {
if (node.isRoot) {
return
@@ -714,13 +635,13 @@ class Render {
this.mindMap.emit('node_active', null, this.activeNodeList)
toNode.nodeData.children.push(copyData)
this.mindMap.render()
if (toNode.isRoot) {
toNode.renderNode()
}
}
/**
* @Author: 王林
* @Date: 2021-07-15 20:09:39
* @Desc: 粘贴节点到节点
*/
// 粘贴节点到节点
pasteNode(data) {
if (this.activeNodeList.length <= 0) {
return
@@ -731,11 +652,8 @@ class Render {
this.mindMap.render()
}
/**
* @Author: 王林
* @Date: 2021-07-08 21:54:30
* @Desc: 设置节点样式
*/
// 设置节点样式
setNodeStyle(node, prop, value, isActive) {
let data = {}
if (isActive) {
@@ -750,6 +668,15 @@ class Render {
[prop]: value
}
}
// 如果开启了富文本,则需要应用到富文本上
if (this.mindMap.richText) {
this.mindMap.richText.showEditText(node)
let config = this.mindMap.richText.normalStyleToRichTextStyle({
[prop]: value
})
this.mindMap.richText.formatAllText(config)
this.mindMap.richText.hideEditText()
}
this.setNodeDataRender(node, data)
// 更新了连线的样式
if (lineStyleProps.includes(prop)) {
@@ -757,11 +684,8 @@ class Render {
}
}
/**
* @Author: 王林
* @Date: 2021-07-08 22:13:03
* @Desc: 设置节点是否激活
*/
// 设置节点是否激活
setNodeActive(node, active) {
this.setNodeData(node, {
isActive: active
@@ -769,11 +693,8 @@ class Render {
node.renderNode()
}
/**
* @Author: 王林
* @Date: 2021-07-10 16:52:41
* @Desc: 设置节点是否展开
*/
// 设置节点是否展开
setNodeExpand(node, expand) {
this.setNodeData(node, {
expand
@@ -796,11 +717,8 @@ class Render {
this.mindMap.render()
}
/**
* @Author: 王林
* @Date: 2021-07-15 23:23:37
* @Desc: 展开所有
*/
// 展开所有
expandAllNode() {
walk(
this.renderTree,
@@ -818,11 +736,8 @@ class Render {
this.mindMap.reRender()
}
/**
* @Author: 王林
* @Date: 2021-07-15 23:27:14
* @Desc: 收起所有
*/
// 收起所有
unexpandAllNode() {
walk(
this.renderTree,
@@ -841,12 +756,8 @@ class Render {
this.mindMap.reRender()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-23 16:31:27
* @Desc: 展开到指定层级
*/
// 展开到指定层级
expandToLevel(level) {
walk(
this.renderTree,
@@ -863,11 +774,8 @@ class Render {
this.mindMap.reRender()
}
/**
* @Author: 王林
* @Date: 2022-08-14 09:18:40
* @Desc: 切换激活节点的展开状态
*/
// 切换激活节点的展开状态
toggleActiveExpand() {
this.activeNodeList.forEach(node => {
if (node.nodeData.children.length <= 0) {
@@ -877,11 +785,8 @@ class Render {
})
}
/**
* @Author: 王林
* @Date: 2021-07-11 17:15:33
* @Desc: 切换节点展开状态
*/
// 切换节点展开状态
toggleNodeExpand(node) {
this.mindMap.execCommand(
'SET_NODE_EXPAND',
@@ -890,22 +795,17 @@ class Render {
)
}
/**
* @Author: 王林
* @Date: 2021-07-09 22:04:19
* @Desc: 设置节点文本
*/
setNodeText(node, text) {
// 设置节点文本
setNodeText(node, text, richText) {
this.setNodeDataRender(node, {
text
text,
richText
})
}
/**
* @Author: 王林
* @Date: 2021-07-10 08:37:40
* @Desc: 设置节点图片
*/
// 设置节点图片
setNodeImage(node, { url, title, width, height }) {
this.setNodeDataRender(node, {
image: url,
@@ -917,22 +817,16 @@ class Render {
})
}
/**
* @Author: 王林
* @Date: 2021-07-10 08:44:06
* @Desc: 设置节点图标
*/
// 设置节点图标
setNodeIcon(node, icons) {
this.setNodeDataRender(node, {
icon: icons
})
}
/**
* @Author: 王林
* @Date: 2021-07-10 08:49:33
* @Desc: 设置节点超链接
*/
// 设置节点超链接
setNodeHyperlink(node, link, title = '') {
this.setNodeDataRender(node, {
hyperlink: link,
@@ -940,33 +834,24 @@ class Render {
})
}
/**
* @Author: 王林
* @Date: 2021-07-10 08:52:59
* @Desc: 设置节点备注
*/
// 设置节点备注
setNodeNote(node, note) {
this.setNodeDataRender(node, {
note
})
}
/**
* @Author: 王林
* @Date: 2021-07-10 08:54:53
* @Desc: 设置节点标签
*/
// 设置节点标签
setNodeTag(node, tag) {
this.setNodeDataRender(node, {
tag
})
}
/**
* @Author: 王林
* @Date: 2022-07-30 20:52:42
* @Desc: 添加节点概要
*/
// 添加节点概要
addGeneralization(data) {
if (this.activeNodeList.length <= 0) {
return
@@ -985,11 +870,8 @@ class Render {
this.mindMap.render()
}
/**
* @Author: 王林
* @Date: 2022-07-30 21:16:33
* @Desc: 删除节点概要
*/
// 删除节点概要
removeGeneralization() {
if (this.activeNodeList.length <= 0) {
return
@@ -1006,12 +888,8 @@ class Render {
this.mindMap.render()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-02 19:04:24
* @Desc: 设置节点自定义位置
*/
// 设置节点自定义位置
setNodeCustomPosition(node, left = undefined, top = undefined) {
let nodeList = [node] || this.activeNodeList
nodeList.forEach(item => {
@@ -1022,12 +900,8 @@ class Render {
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-02 20:02:50
* @Desc: 一键整理布局,即去除自定义位置
*/
// 一键整理布局,即去除自定义位置
resetLayout() {
walk(
this.root,
@@ -1048,12 +922,8 @@ class Render {
)
}
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 21:44:01
* @Desc: 设置节点形状
*/
// 设置节点形状
setNodeShape(node, shape) {
if (!shape || !shapeList.includes(shape)) {
return
@@ -1064,22 +934,16 @@ class Render {
})
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:19:48
* @Desc: 更新节点数据
*/
// 更新节点数据
setNodeData(node, data) {
Object.keys(data).forEach(key => {
node.nodeData.data[key] = data[key]
})
}
/**
* @Author: 王林
* @Date: 2021-07-10 08:45:48
* @Desc: 设置节点数据,并判断是否渲染
*/
// 设置节点数据,并判断是否渲染
setNodeDataRender(node, data) {
this.setNodeData(node, data)
let changed = node.getSize()
@@ -1093,12 +957,8 @@ class Render {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 11:46:57
* @Desc: 移动节点到画布中心
*/
// 移动节点到画布中心
moveNodeToCenter(node) {
let halfWidth = this.mindMap.width / 2
let halfHeight = this.mindMap.height / 2

View File

@@ -0,0 +1,428 @@
import Quill from 'quill'
import 'quill/dist/quill.snow.css'
import './css/quill.css'
import html2canvas from 'html2canvas'
import { Image as SvgImage } from '@svgdotjs/svg.js'
import { walk } from './utils'
let extended = false
// 扩展quill的字体列表
let fontFamilyList = [
'宋体, SimSun, Songti SC',
'微软雅黑, Microsoft YaHei',
'楷体, 楷体_GB2312, SimKai, STKaiti',
'黑体, SimHei, Heiti SC',
'隶书, SimLi',
'andale mono',
'arial, helvetica, sans-serif',
'arial black, avant garde',
'comic sans ms',
'impact, chicago',
'times new roman',
'sans-serif',
'serif'
]
// 扩展quill的字号列表
let fontSizeList = new Array(100).fill(0).map((_, index) => {
return index + 'px'
})
// 节点支持富文本编辑功能
class RichText {
constructor({ mindMap, pluginOpt }) {
this.mindMap = mindMap
this.pluginOpt = pluginOpt
this.textEditNode = null
this.showTextEdit = false
this.quill = null
this.range = null
this.lastRange = null
this.node = null
this.initOpt()
this.extendQuill()
}
// 处理选项参数
initOpt() {
if (
this.pluginOpt.fontFamilyList &&
Array.isArray(this.pluginOpt.fontFamilyList)
) {
fontFamilyList = this.pluginOpt.fontFamilyList
}
if (
this.pluginOpt.fontSizeList &&
Array.isArray(this.pluginOpt.fontSizeList)
) {
fontSizeList = this.pluginOpt.fontSizeList
}
}
// 扩展quill编辑器
extendQuill() {
if (extended) {
return
}
extended = true
// 扩展quill的字体列表
const FontAttributor = Quill.import('attributors/class/font')
FontAttributor.whitelist = fontFamilyList
Quill.register(FontAttributor, true)
const FontStyle = Quill.import('attributors/style/font')
FontStyle.whitelist = fontFamilyList
Quill.register(FontStyle, true)
// 扩展quill的字号列表
const SizeAttributor = Quill.import('attributors/class/size')
SizeAttributor.whitelist = fontSizeList
Quill.register(SizeAttributor, true)
const SizeStyle = Quill.import('attributors/style/size')
SizeStyle.whitelist = fontSizeList
Quill.register(SizeStyle, true)
}
// 显示文本编辑控件
showEditText(node, rect) {
if (this.showTextEdit) {
return
}
this.node = node
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
this.mindMap.emit('before_show_text_edit')
this.mindMap.renderer.textEdit.registerTmpShortcut()
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);outline: none; word-break: break-all;`
document.body.appendChild(this.textEditNode)
}
// 原始宽高
let g = node._textData.node
let originWidth = g.attr('data-width')
let originHeight = g.attr('data-height')
this.textEditNode.style.minWidth = originWidth + 'px'
this.textEditNode.style.minHeight = originHeight + 'px'
this.textEditNode.style.left =
rect.left + (rect.width - originWidth) / 2 + 'px'
this.textEditNode.style.top =
rect.top + (rect.height - originHeight) / 2 + 'px'
this.textEditNode.style.display = 'block'
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
this.textEditNode.style.transform = `scale(${rect.width / originWidth}, ${
rect.height / originHeight
})`
if (!node.nodeData.data.richText) {
// 还不是富文本的情况
let text = node.nodeData.data.text.split(/\n/gim).join('<br>')
let html = `<p>${text}</p>`
this.textEditNode.innerHTML = html
} else {
this.textEditNode.innerHTML = node.nodeData.data.text
}
this.initQuillEditor()
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
this.showTextEdit = true
this.selectAll()
if (!node.nodeData.data.richText) {
// 如果是非富文本的情况,需要手动应用文本样式
this.setTextStyleIfNotRichText(node)
}
}
// 如果是非富文本的情况,需要手动应用文本样式
setTextStyleIfNotRichText(node) {
let style = {
font: node.style.merge('fontFamily'),
color: node.style.merge('color'),
italic: node.style.merge('fontStyle') === 'italic',
bold: node.style.merge('fontWeight') === 'bold',
size: node.style.merge('fontSize') + 'px',
underline: node.style.merge('textDecoration') === 'underline',
strike: node.style.merge('textDecoration') === 'line-through'
}
this.formatText(style)
}
// 隐藏文本编辑控件,即完成编辑
hideEditText() {
if (!this.showTextEdit) {
return
}
let html = this.quill.container.firstChild.innerHTML
// 去除最后的空行
html = html.replace(/<p><br><\/p>$/, '')
this.mindMap.renderer.activeNodeList.forEach(node => {
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
if (node.isGeneralization) {
// 概要节点
node.generalizationBelongNode.updateGeneralization()
}
this.mindMap.render()
})
this.mindMap.emit(
'hide_text_edit',
this.textEditNode,
this.mindMap.renderer.activeNodeList
)
this.textEditNode.style.display = 'none'
this.showTextEdit = false
this.mindMap.emit('rich_text_selection_change', false)
this.node = null
}
// 初始化Quill富文本编辑器
initQuillEditor() {
this.quill = new Quill(this.textEditNode, {
modules: {
toolbar: false,
keyboard: {
bindings: {
enter: {
key: 13,
handler: function () {
// 覆盖默认的回车键换行
}
}
}
}
},
theme: 'snow'
})
this.quill.on('selection-change', range => {
this.lastRange = this.range
this.range = null
if (range) {
let bounds = this.quill.getBounds(range.index, range.length)
let rect = this.textEditNode.getBoundingClientRect()
let rectInfo = {
left: bounds.left + rect.left,
top: bounds.top + rect.top,
right: bounds.right + rect.left,
bottom: bounds.bottom + rect.top,
width: bounds.width
}
let formatInfo = this.quill.getFormat(range.index, range.length)
let hasRange = false
if (range.length == 0) {
hasRange = false
} else {
this.range = range
hasRange = true
}
this.mindMap.emit(
'rich_text_selection_change',
hasRange,
rectInfo,
formatInfo
)
}
})
}
// 选中全部
selectAll() {
this.quill.setSelection(0, this.quill.getLength())
}
// 格式化当前选中的文本
formatText(config = {}) {
if (!this.range && !this.lastRange) return
this.syncFormatToNodeConfig(config)
let rangeLost = !this.range
let range = rangeLost ? this.lastRange : this.range
this.quill.formatText(range.index, range.length, config)
if (rangeLost) {
this.quill.setSelection(this.lastRange.index, this.lastRange.length)
}
}
// 格式化指定范围的文本
formatRangeText(range, config = {}) {
if (!range) return
this.syncFormatToNodeConfig(config)
this.quill.formatText(range.index, range.length, config)
}
// 格式化所有文本
formatAllText(config = {}) {
this.syncFormatToNodeConfig(config)
this.quill.formatText(0, this.quill.getLength(), config)
}
// 同步格式化到节点样式配置
syncFormatToNodeConfig(config) {
if (!this.node) return
let data = this.richTextStyleToNormalStyle(config)
this.mindMap.renderer.setNodeData(this.node, data)
}
// 将普通节点样式对象转换成富文本样式对象
normalStyleToRichTextStyle(style) {
let config = {}
Object.keys(style).forEach(prop => {
let value = style[prop]
switch (prop) {
case 'fontFamily':
config.font = value
break
case 'fontSize':
config.size = value + 'px'
break
case 'fontWeight':
config.bold = value === 'bold'
break
case 'fontStyle':
config.italic = value === 'italic'
break
case 'textDecoration':
config.underline = value === 'underline'
config.strike = value === 'line-through'
break
case 'color':
config.color = value
break
default:
break
}
})
return config
}
// 将富文本样式对象转换成普通节点样式对象
richTextStyleToNormalStyle(config) {
let data = {}
Object.keys(config).forEach(prop => {
let value = config[prop]
switch (prop) {
case 'font':
data.fontFamily = value
break
case 'size':
data.fontSize = parseFloat(value)
break
case 'bold':
data.fontWeight = value ? 'bold' : 'normal'
break
case 'italic':
data.fontStyle = value ? 'italic' : 'normal'
break
case 'underline':
data.textDecoration = value ? 'underline' : 'none'
break
case 'strike':
data.textDecoration = value ? 'line-through' : 'none'
break
case 'color':
data.color = value
break
default:
break
}
})
return data
}
// 将svg中嵌入的dom元素转换成图片
async _handleSvgDomElements(svg) {
svg = svg.clone()
let foreignObjectList = svg.find('foreignObject')
let task = foreignObjectList.map(async item => {
let clone = item.first().node.cloneNode(true)
let div = document.createElement('div')
div.style.cssText = `position: fixed; left: -999999px;`
div.appendChild(clone)
this.mindMap.el.appendChild(div)
let canvas = await html2canvas(clone, {
backgroundColor: null
})
this.mindMap.el.removeChild(div)
let imgNode = new SvgImage()
.load(canvas.toDataURL())
.size(canvas.width, canvas.height)
item.replace(imgNode)
})
await Promise.all(task)
return {
svg: svg,
svgHTML: svg.svg()
}
}
// 将svg中嵌入的dom元素转换成图片
handleSvgDomElements(svg) {
return new Promise((resolve, reject) => {
svg = svg.clone()
let foreignObjectList = svg.find('foreignObject')
let index = 0
let len = foreignObjectList.length
let transform = async () => {
this.mindMap.emit('transforming-dom-to-images', index, len)
try {
let item = foreignObjectList[index++]
let parent = item.parent()
let clone = item.first().node.cloneNode(true)
let div = document.createElement('div')
div.style.cssText = `position: fixed; left: -999999px;`
div.appendChild(clone)
this.mindMap.el.appendChild(div)
let canvas = await html2canvas(clone, {
backgroundColor: null
})
this.mindMap.el.removeChild(div)
let imgNode = new SvgImage()
.load(canvas.toDataURL())
.size(canvas.width, canvas.height)
.x((parent ? parent.attr('data-offsetx') : 0) || 0)
item.replace(imgNode)
if (index <= len - 1) {
setTimeout(() => {
transform()
}, 0)
} else {
resolve({
svg: svg,
svgHTML: svg.svg()
})
}
} catch (error) {
reject(error)
}
}
if (len > 0) transform()
})
}
// 将所有节点转换成非富文本节点
transformAllNodesToNormalNode() {
let div = document.createElement('div')
walk(
this.mindMap.renderer.renderTree,
null,
node => {
if (node.data.richText) {
node.data.richText = false
div.innerHTML = node.data.text
node.data.text = div.textContent
}
},
null,
true,
0,
0
)
this.mindMap.reRender()
}
// 插件被移除前做的事情
beforePluginRemove() {
this.transformAllNodesToNormalNode()
}
}
RichText.instanceName = 'richText'
export default RichText

View File

@@ -1,17 +1,9 @@
import { bfsWalk, throttle } from './utils'
/**
* @Author: 王林
* @Date: 2021-07-10 22:34:51
* @Desc: 选择节点类
*/
// 选择节点类
class Select {
/**
* @Author: 王林
* @Date: 2021-07-10 22:35:16
* @Desc: 构造函数
*/
// 构造函数
constructor({ mindMap }) {
this.mindMap = mindMap
this.rect = null
@@ -23,11 +15,7 @@ class Select {
this.bindEvent()
}
/**
* @Author: 王林
* @Date: 2021-07-10 22:36:36
* @Desc: 绑定事件
*/
// 绑定事件
bindEvent() {
this.checkInNodes = throttle(this.checkInNodes, 500, this)
this.mindMap.on('mousedown', e => {
@@ -81,11 +69,7 @@ class Select {
})
}
/**
* @Author: 王林
* @Date: 2021-07-13 07:55:49
* @Desc: 鼠标移动事件
*/
// 鼠标移动事件
onMove(x, y) {
// 绘制矩形
this.rect.plot([
@@ -128,22 +112,14 @@ class Select {
}
}
/**
* @Author: 王林
* @Date: 2021-07-22 08:02:23
* @Desc: 开启自动移动
*/
// 开启自动移动
startAutoMove(x, y) {
this.autoMoveTimer = setTimeout(() => {
this.onMove(x, y)
}, 20)
}
/**
* @Author: 王林
* @Date: 2021-07-11 10:19:37
* @Desc: 创建矩形
*/
// 创建矩形
createRect(x, y) {
this.rect = this.mindMap.svg
.polygon()
@@ -156,11 +132,7 @@ class Select {
.plot([[x, y]])
}
/**
* @Author: 王林
* @Date: 2021-07-11 10:20:43
* @Desc: 检测在选区里的节点
*/
// 检测在选区里的节点
checkInNodes() {
let { scaleX, scaleY, translateX, translateY } =
this.mindMap.draw.transform()
@@ -195,4 +167,6 @@ class Select {
}
}
Select.instanceName = 'select'
export default Select

View File

@@ -1,18 +1,10 @@
/**
* @Author: 王林
* @Date: 2022-08-22 21:32:50
* @Desc: 节点形状类
*/
// 节点形状类
export default class Shape {
constructor(node) {
this.node = node
}
/**
* @Author: 王林
* @Date: 2022-08-17 22:32:32
* @Desc: 形状需要的padding
*/
// 形状需要的padding
getShapePadding(width, height, paddingX, paddingY) {
const shape = this.node.getShape()
const defaultPaddingX = 15
@@ -64,11 +56,7 @@ export default class Shape {
}
}
/**
* @Author: 王林
* @Date: 2022-08-17 22:22:53
* @Desc: 创建形状节点
*/
// 创建形状节点
createShape() {
const shape = this.node.getShape()
let { width, height } = this.node
@@ -104,11 +92,7 @@ export default class Shape {
return node
}
/**
* @Author: 王林
* @Date: 2022-09-04 09:08:54
* @Desc: 创建菱形
*/
// 创建菱形
createDiamond() {
let { width, height } = this.node
let halfWidth = width / 2
@@ -129,11 +113,7 @@ export default class Shape {
`)
}
/**
* @Author: 王林
* @Date: 2022-09-03 16:14:12
* @Desc: 创建平行四边形
*/
// 创建平行四边形
createParallelogram() {
let { paddingX } = this.node.getPaddingVale()
paddingX = paddingX || this.node.shapePadding.paddingX
@@ -146,11 +126,7 @@ export default class Shape {
`)
}
/**
* @Author: 王林
* @Date: 2022-09-03 16:50:23
* @Desc: 创建圆角矩形
*/
// 创建圆角矩形
createRoundedRectangle() {
let { width, height } = this.node
let halfHeight = height / 2
@@ -163,12 +139,7 @@ export default class Shape {
`)
}
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 16:14:08
* @Desc: 创建八角矩形
*/
// 创建八角矩形
createOctagonalRectangle() {
let w = 5
let { width, height } = this.node
@@ -184,12 +155,7 @@ export default class Shape {
`)
}
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 20:55:50
* @Desc: 创建外三角矩形
*/
// 创建外三角矩形
createOuterTriangularRectangle() {
let { paddingX } = this.node.getPaddingVale()
paddingX = paddingX || this.node.shapePadding.paddingX
@@ -204,12 +170,7 @@ export default class Shape {
`)
}
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 20:59:37
* @Desc: 创建内三角矩形
*/
// 创建内三角矩形
createInnerTriangularRectangle() {
let { paddingX } = this.node.getPaddingVale()
paddingX = paddingX || this.node.shapePadding.paddingX
@@ -224,12 +185,7 @@ export default class Shape {
`)
}
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 21:06:31
* @Desc: 创建椭圆
*/
// 创建椭圆
createEllipse() {
let { width, height } = this.node
let halfWidth = width / 2
@@ -242,12 +198,7 @@ export default class Shape {
`)
}
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 21:14:04
* @Desc: 创建圆
*/
// 创建圆
createCircle() {
let { width, height } = this.node
let halfWidth = width / 2

View File

@@ -1,50 +1,39 @@
import { tagColorList } from './utils/constant'
const rootProp = ['paddingX', 'paddingY']
/**
* @Author: 王林
* @Date: 2021-04-11 10:09:08
* @Desc: 样式类
*/
// 样式类
class Style {
/**
* @Author: 王林
* @Date: 2021-04-11 16:01:53
* @Desc: 设置背景样式
*/
// 设置背景样式
static setBackgroundStyle(el, themeConfig) {
let { backgroundColor, backgroundImage, backgroundRepeat } = themeConfig
let { backgroundColor, backgroundImage, backgroundRepeat, backgroundPosition, backgroundSize } = themeConfig
el.style.backgroundColor = backgroundColor
if (backgroundImage) {
el.style.backgroundImage = `url(${backgroundImage})`
el.style.backgroundRepeat = backgroundRepeat
el.style.backgroundPosition = backgroundPosition
el.style.backgroundSize = backgroundSize
} else {
el.style.backgroundImage = 'none'
}
}
/**
* @Author: 王林
* @Date: 2021-04-11 10:10:11
* @Desc: 构造函数
*/
// 构造函数
constructor(ctx, themeConfig) {
this.ctx = ctx
this.themeConfig = themeConfig
}
/**
* @Author: 王林
* @Date: 2021-07-12 07:40:14
* @Desc: 更新主题配置
*/
// 更新主题配置
updateThemeConfig(themeConfig) {
this.themeConfig = themeConfig
}
/**
* @Author: 王林
* @Date: 2021-04-11 12:02:55
* @Desc: 合并样式
*/
// 合并样式
merge(prop, root, isActive) {
// 三级及以下节点
let defaultConfig = this.themeConfig.node
@@ -78,59 +67,49 @@ class Style {
: defaultConfig[prop]
}
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 21:55:57
* @Desc: 获取某个样式值
*/
// 获取某个样式值
getStyle(prop, root, isActive) {
return this.merge(prop, root, isActive)
}
/**
* javascript comment
* @Author: flydreame
* @Date: 2022-09-17 12:09:39
* @Desc: 获取自身自定义样式
*/
// 获取自身自定义样式
getSelfStyle(prop) {
return this.ctx.nodeData.data[prop]
}
/**
* @Author: 王林
* @Date: 2021-04-11 10:12:56
* @Desc: 矩形
*/
// 矩形
rect(node) {
this.shape(node)
node.radius(this.merge('borderRadius'))
}
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 15:04:28
* @Desc: 矩形外的其他形状
*/
// 矩形外的其他形状
shape(node) {
node
.fill({
color: this.merge('fillColor')
})
.stroke({
color: this.merge('borderColor'),
width: this.merge('borderWidth'),
dasharray: this.merge('borderDasharray')
})
node.fill({
color: this.merge('fillColor')
})
// 节点使用横线样式,不需要渲染非激活状态的边框样式
if (
!this.ctx.isRoot &&
!this.ctx.isGeneralization &&
this.themeConfig.nodeUseLineStyle &&
!this.ctx.nodeData.data.isActive
) {
return
}
node.stroke({
color: this.merge('borderColor'),
width: this.merge('borderWidth'),
dasharray: this.merge('borderDasharray')
})
}
/**
* @Author: 王林
* @Date: 2021-04-11 12:07:59
* @Desc: 文字
*/
// 文字
text(node) {
node
.fill({
@@ -145,22 +124,28 @@ class Style {
})
}
/**
* @Author: 王林
* @Date: 2021-04-13 08:14:34
* @Desc: html文字节点
*/
// 获取文本样式
getTextFontStyle() {
return {
italic: this.merge('fontStyle') === 'italic',
bold: this.merge('fontWeight'),
fontSize: this.merge('fontSize'),
fontFamily: this.merge('fontFamily')
}
}
// html文字节点
domText(node, fontSizeScale = 1) {
node.style.fontFamily = this.merge('fontFamily')
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
node.style.fontWeight = this.merge('fontWeight') || 'normal'
node.style.lineHeight = this.merge('lineHeight')
node.style.fontStyle = this.merge('fontStyle')
}
/**
* @Author: 王林
* @Date: 2021-06-20 20:02:18
* @Desc: 标签文字
*/
// 标签文字
tagText(node, index) {
node
.fill({
@@ -171,42 +156,30 @@ class Style {
})
}
/**
* @Author: 王林
* @Date: 2021-06-20 21:04:11
* @Desc: 标签矩形
*/
// 标签矩形
tagRect(node, index) {
node.fill({
color: tagColorList[index].background
})
}
/**
* @Author: 王林
* @Date: 2021-07-03 22:37:19
* @Desc: 内置图标
*/
// 内置图标
iconNode(node) {
node.attr({
fill: this.merge('color')
})
}
/**
* @Author: 王林
* @Date: 2021-04-11 14:50:49
* @Desc: 连线
*/
// 连线
line(node, { width, color, dasharray } = {}) {
node.stroke({ width, color, dasharray }).fill({ color: 'none' })
}
/**
* @Author: 王林
* @Date: 2022-07-30 16:19:03
* @Desc: 概要连线
*/
// 概要连线
generalizationLine(node) {
node
.stroke({
@@ -216,11 +189,8 @@ class Style {
.fill({ color: 'none' })
}
/**
* @Author: 王林
* @Date: 2021-04-11 20:03:59
* @Desc: 按钮
*/
// 按钮
iconBtn(node, fillNode) {
node.fill({ color: '#808080' })
fillNode.fill({ color: '#fff' })

View File

@@ -1,18 +1,8 @@
import { getStrWithBrFromHtml } from './utils'
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-06-19 11:11:28
* @Desc: 节点文字编辑类
*/
// 节点文字编辑类
export default class TextEdit {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-06-19 11:22:57
* @Desc: 构造函数
*/
// 构造函数
constructor(renderer) {
this.renderer = renderer
this.mindMap = renderer.mindMap
@@ -23,11 +13,7 @@ export default class TextEdit {
this.bindEvent()
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:27:04
* @Desc: 事件
*/
// 事件
bindEvent() {
this.show = this.show.bind(this)
// 节点双击事件
@@ -54,12 +40,7 @@ export default class TextEdit {
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-16 16:27:02
* @Desc: 注册临时快捷键
*/
// 注册临时快捷键
registerTmpShortcut() {
// 注册回车快捷键
this.mindMap.keyCommand.addShortcut('Enter', () => {
@@ -67,33 +48,33 @@ export default class TextEdit {
})
}
/**
* @Author: 王林
* @Date: 2021-04-13 22:15:56
* @Desc: 显示文本编辑框
*/
// 显示文本编辑框
show(node) {
this.showEditTextBox(node, node._textData.node.node.getBoundingClientRect())
let rect = node._textData.node.node.getBoundingClientRect()
if (this.mindMap.richText) {
this.mindMap.richText.showEditText(node, rect)
return
}
this.showEditTextBox(node, rect)
}
/**
* @Author: 王林
* @Date: 2021-04-13 22:13:02
* @Desc: 显示文本编辑框
*/
// 显示文本编辑框
showEditTextBox(node, rect) {
this.mindMap.emit('before_show_text_edit')
this.registerTmpShortcut()
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
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;`
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 => {
e.stopPropagation()
})
document.body.appendChild(this.textEditNode)
}
node.style.domText(this.textEditNode, this.mindMap.view.scale)
let scale = this.mindMap.view.scale
let lineHeight = node.style.merge('lineHeight')
let fontSize = node.style.merge('fontSize')
node.style.domText(this.textEditNode, scale)
this.textEditNode.innerHTML = node.nodeData.data.text
.split(/\n/gim)
.join('<br>')
@@ -102,16 +83,14 @@ export default class TextEdit {
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
this.textEditNode.style.display = 'block'
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth * scale + 'px'
this.textEditNode.style.transform = `translateY(${-(lineHeight * fontSize - fontSize) / 2 * scale}px)`
this.showTextEdit = true
// 选中文本
this.selectNodeText()
}
/**
* @Author: 王林
* @Date: 2021-08-02 23:13:50
* @Desc: 选中文本
*/
// 选中文本
selectNodeText() {
let selection = window.getSelection()
let range = document.createRange()
@@ -120,12 +99,11 @@ export default class TextEdit {
selection.addRange(range)
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:48:16
* @Desc: 隐藏文本编辑框
*/
// 隐藏文本编辑框
hideEditTextBox() {
if (this.mindMap.richText) {
return this.mindMap.richText.hideEditText()
}
if (!this.showTextEdit) {
return
}

View File

@@ -1,16 +1,6 @@
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:45:24
* @Desc: 视图操作类
*/
// 视图操作类
class View {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:45:40
* @Desc: 构造函数
*/
// 构造函数
constructor(opt = {}) {
this.opt = opt
this.mindMap = this.opt.mindMap
@@ -24,12 +14,7 @@ class View {
this.bind()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:38:51
* @Desc: 绑定
*/
// 绑定
bind() {
// 快捷键
this.mindMap.keyCommand.addShortcut('Control+=', () => {
@@ -80,12 +65,7 @@ class View {
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-22 18:30:24
* @Desc: 获取当前变换状态数据
*/
// 获取当前变换状态数据
getTransformData() {
return {
transform: this.mindMap.draw.transform(),
@@ -99,12 +79,7 @@ class View {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-22 19:54:17
* @Desc: 动态设置变换状态数据
*/
// 动态设置变换状态数据
setTransformData(viewData) {
if (viewData) {
Object.keys(viewData.state).forEach(prop => {
@@ -118,55 +93,31 @@ class View {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-13 15:49:06
* @Desc: 平移x方向
*/
// 平移x方向
translateX(step) {
this.x += step
this.transform()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:03:53
* @Desc: 平移x方式到
*/
// 平移x方式到
translateXTo(x) {
this.x = x
this.transform()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-13 15:48:52
* @Desc: 平移y方向
*/
// 平移y方向
translateY(step) {
this.y += step
this.transform()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:04:10
* @Desc: 平移y方向到
*/
// 平移y方向到
translateYTo(y) {
this.y = y
this.transform()
}
/**
* @Author: 王林
* @Date: 2021-07-04 17:13:14
* @Desc: 应用变换
*/
// 应用变换
transform() {
this.mindMap.draw.transform({
scale: this.scale,
@@ -176,11 +127,7 @@ class View {
this.mindMap.emit('view_data_change', this.getTransformData())
}
/**
* @Author: 王林
* @Date: 2021-07-11 17:41:35
* @Desc: 恢复
*/
// 恢复
reset() {
this.scale = 1
this.x = 0
@@ -188,11 +135,7 @@ class View {
this.transform()
}
/**
* @Author: 王林
* @Date: 2021-07-04 17:10:34
* @Desc: 缩小
*/
// 缩小
narrow() {
if (this.scale - this.mindMap.opt.scaleRatio > 0.1) {
this.scale -= this.mindMap.opt.scaleRatio
@@ -203,23 +146,14 @@ class View {
this.mindMap.emit('scale', this.scale)
}
/**
* @Author: 王林
* @Date: 2021-07-04 17:10:41
* @Desc: 放大
*/
// 放大
enlarge() {
this.scale += this.mindMap.opt.scaleRatio
this.transform()
this.mindMap.emit('scale', this.scale)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 16:31:59
* @Desc: 设置缩放
*/
// 设置缩放
setScale(scale) {
this.scale = scale
this.transform()

View File

@@ -0,0 +1,120 @@
import { Text, G } from '@svgdotjs/svg.js'
import { degToRad, camelCaseToHyphen } from './utils'
import merge from 'deepmerge'
// 水印类
class Watermark {
constructor(opt = {}) {
this.mindMap = opt.mindMap
this.lineSpacing = 0 // 水印行间距
this.textSpacing = 0 // 行内水印间距
this.angle = 0 // 旋转角度
this.text = '' // 水印文字
this.textStyle = {} // 水印文字样式
this.watermarkDraw = this.mindMap.svg
.group()
.css({ 'pointer-events': 'none', 'user-select': 'none' })
this.maxLong = Math.sqrt(
Math.pow(this.mindMap.width, 2) + Math.pow(this.mindMap.height, 2)
)
this.updateWatermark(this.mindMap.opt.watermarkConfig || {})
}
// 获取是否存在水印
hasWatermark() {
return !!this.text.trim()
}
// 处理水印配置
handleConfig({ text, lineSpacing, textSpacing, angle, textStyle }) {
this.text = text === undefined ? '' : String(text).trim()
this.lineSpacing =
typeof lineSpacing === 'number' && lineSpacing > 0 ? lineSpacing : 100
this.textSpacing =
typeof textSpacing === 'number' && textSpacing > 0 ? textSpacing : 100
this.angle =
typeof angle === 'number' && angle >= 0 && angle <= 90 ? angle : 30
this.textStyle = Object.assign(this.textStyle, textStyle || {})
}
// 绘制水印
// 非精确绘制,会绘制一些超出可视区域的水印
draw() {
this.watermarkDraw.clear()
if (!this.hasWatermark()) {
return
}
let x = 0
while (x < this.mindMap.width) {
this.drawText(x)
x += this.lineSpacing / Math.sin(degToRad(this.angle))
}
let yOffset =
this.lineSpacing / Math.cos(degToRad(this.angle)) || this.lineSpacing
let y = yOffset
while (y < this.mindMap.height) {
this.drawText(0, y)
y += yOffset
}
}
// 绘制文字
drawText(x, y) {
let long = Math.min(
this.maxLong,
(this.mindMap.width - x) / Math.cos(degToRad(this.angle))
)
let g = new G()
let bbox = null
let bboxWidth = 0
let textHeight = -1
while (bboxWidth < long) {
let text = new Text().text(this.text)
g.add(text)
text.transform({
translateX: bboxWidth
})
this.setTextStyle(text)
bbox = g.bbox()
if (textHeight === -1) {
textHeight = bbox.height
}
bboxWidth = bbox.width + this.textSpacing
}
let params = {
rotate: this.angle,
origin: 'top left',
translateX: x,
translateY: textHeight
}
if (y !== undefined) {
params.translateY = y + textHeight
}
g.transform(params)
this.watermarkDraw.add(g)
}
// 给文字设置样式
setTextStyle(text) {
Object.keys(this.textStyle).forEach(item => {
let value = this.textStyle[item]
if (item === 'color') {
text.fill(value)
} else {
text.css(camelCaseToHyphen(item), value)
}
})
}
// 更新水印
updateWatermark(config) {
this.mindMap.opt.watermarkConfig = merge(this.mindMap.opt.watermarkConfig, config)
this.handleConfig(config)
this.draw()
}
}
Watermark.instanceName = 'watermark'
export default Watermark

View File

@@ -0,0 +1,9 @@
.ql-editor {
overflow: hidden;
padding: 0;
height: auto;
}
.ql-container {
height: auto;
}

View File

@@ -1,16 +1,8 @@
import Node from '../Node'
/**
* @Author: 王林
* @Date: 2021-04-12 22:24:30
* @Desc: 布局基类
*/
// 布局基类
class Base {
/**
* @Author: 王林
* @Date: 2021-04-12 22:25:16
* @Desc: 构造函数
*/
// 构造函数
constructor(renderer) {
// 渲染实例
this.renderer = renderer
@@ -22,45 +14,25 @@ class Base {
this.root = null
}
/**
* @Author: 王林
* @Date: 2021-04-12 22:39:50
* @Desc: 计算节点位置
*/
// 计算节点位置
doLayout() {
throw new Error('【computed】方法为必要方法需要子类进行重写')
}
/**
* @Author: 王林
* @Date: 2021-04-12 22:41:04
* @Desc: 连线
*/
// 连线
renderLine() {
throw new Error('【renderLine】方法为必要方法需要子类进行重写')
}
/**
* @Author: 王林
* @Date: 2021-04-12 22:42:08
* @Desc: 定位展开收缩按钮
*/
// 定位展开收缩按钮
renderExpandBtn() {
throw new Error('【renderExpandBtn】方法为必要方法需要子类进行重写')
}
/**
* @Author: 王林
* @Date: 2022-07-30 22:49:28
* @Desc: 概要节点
*/
// 概要节点
renderGeneralization() {}
/**
* @Author: 王林
* @Date: 2021-07-10 21:30:54
* @Desc: 创建节点实例
*/
// 创建节点实例
createNode(data, parent, isRoot, layerIndex) {
// 创建节点
let newNode = null
@@ -98,22 +70,13 @@ class Base {
return newNode
}
/**
* @Author: 王林
* @Date: 2021-07-16 13:48:43
* @Desc: 定位节点到画布中间
*/
// 定位节点到画布中间
setNodeCenter(node) {
node.left = (this.mindMap.width - node.width) / 2
node.top = (this.mindMap.height - node.height) / 2
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 11:25:52
* @Desc: 更新子节点属性
*/
// 更新子节点属性
updateChildren(children, prop, offset) {
children.forEach(item => {
item[prop] += offset
@@ -124,22 +87,14 @@ class Base {
})
}
/**
* @Author: 王林
* @Date: 2021-04-11 15:05:01
* @Desc: 二次贝塞尔曲线
*/
// 二次贝塞尔曲线
quadraticCurvePath(x1, y1, x2, y2) {
let cx = x1 + (x2 - x1) * 0.2
let cy = y1 + (y2 - y1) * 0.8
return `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
}
/**
* @Author: 王林
* @Date: 2021-04-11 15:05:18
* @Desc: 三次贝塞尔曲线
*/
// 三次贝塞尔曲线
cubicBezierPath(x1, y1, x2, y2) {
let cx1 = x1 + (x2 - x1) / 2
let cy1 = y1
@@ -148,33 +103,21 @@ class Base {
return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`
}
/**
* @Author: 王林
* @Date: 2021-06-27 19:00:07
* @Desc: 获取节点的marginX
*/
// 获取节点的marginX
getMarginX(layerIndex) {
return layerIndex === 1
? this.mindMap.themeConfig.second.marginX
: this.mindMap.themeConfig.node.marginX
}
/**
* @Author: 王林
* @Date: 2021-04-11 15:34:20
* @Desc: 获取节点的marginY
*/
// 获取节点的marginY
getMarginY(layerIndex) {
return layerIndex === 1
? this.mindMap.themeConfig.second.marginY
: this.mindMap.themeConfig.node.marginY
}
/**
* @Author: 王林
* @Date: 2022-07-31 20:53:12
* @Desc: 获取节点包括概要在内的宽度
*/
// 获取节点包括概要在内的宽度
getNodeWidthWithGeneralization(node) {
return Math.max(
node.width,
@@ -182,11 +125,7 @@ class Base {
)
}
/**
* @Author: 王林
* @Date: 2022-07-31 20:53:12
* @Desc: 获取节点包括概要在内的高度
*/
// 获取节点包括概要在内的高度
getNodeHeightWithGeneralization(node) {
return Math.max(
node.height,
@@ -194,10 +133,8 @@ class Base {
)
}
// 获取节点的边界值
/**
* @Author: 王林
* @Date: 2022-07-31 09:14:03
* @Desc: 获取节点的边界值
* dir生长方向h水平、v垂直
* isLeft是否向左生长
*/

View File

@@ -1,430 +1,386 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
/**
* @Author: 王林
* @Date: 2021-04-12 22:25:58
* @Desc: 目录组织图
*/
class CatalogOrganization extends Base {
/**
* @Author: 王林
* @Date: 2021-04-12 22:26:31
* @Desc: 构造函数
*/
constructor(opt = {}) {
super(opt)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 14:04:20
* @Desc: 布局
*/
doLayout(callback) {
let task = [
() => {
this.computedBaseValue()
},
() => {
this.computedLeftTopValue()
},
() => {
this.adjustLeftTopValue()
},
() => {
callback(this.root)
}
]
asyncRun(task)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:49:32
* @Desc: 遍历数据计算节点的left、width、height
*/
computedBaseValue() {
walk(
this.renderer.renderTree,
null,
(cur, parent, isRoot, layerIndex) => {
let newNode = this.createNode(cur, parent, isRoot, layerIndex)
// 根节点定位在画布中心位置
if (isRoot) {
this.setNodeCenter(newNode)
} else {
// 非根节点
if (parent._node.isRoot) {
newNode.top =
parent._node.top +
parent._node.height +
this.getMarginX(layerIndex)
}
}
if (!cur.data.expand) {
return true
}
},
(cur, parent, isRoot, layerIndex) => {
if (isRoot) {
let len = cur.data.expand === false ? 0 : cur._node.children.length
cur._node.childrenAreaWidth = len
? cur._node.children.reduce((h, item) => {
return h + item.width
}, 0) +
(len + 1) * this.getMarginX(layerIndex + 1)
: 0
}
},
true,
0
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:59:25
* @Desc: 遍历节点树计算节点的left、top
*/
computedLeftTopValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (
node.nodeData.data.expand &&
node.children &&
node.children.length
) {
let marginX = this.getMarginX(layerIndex + 1)
let marginY = this.getMarginY(layerIndex + 1)
if (isRoot) {
let left = node.left + node.width / 2 - node.childrenAreaWidth / 2
let totalLeft = left + marginX
node.children.forEach(cur => {
cur.left = totalLeft
totalLeft += cur.width + marginX
})
} else {
let totalTop = node.top + node.height + marginY + node.expandBtnSize
node.children.forEach(cur => {
cur.left = node.left + node.width * 0.5
cur.top = totalTop
totalTop += cur.height + marginY + node.expandBtnSize
})
}
}
},
null,
true
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 10:04:05
* @Desc: 调整节点left、top
*/
adjustLeftTopValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
return
}
// 调整left
if (parent && parent.isRoot) {
let areaWidth = this.getNodeAreaWidth(node)
let difference = areaWidth - node.width
if (difference > 0) {
this.updateBrothersLeft(node, difference / 2)
}
}
// 调整top
let len = node.children.length
if (parent && !parent.isRoot && len > 0) {
let marginY = this.getMarginY(layerIndex + 1)
let totalHeight =
node.children.reduce((h, item) => {
return h + item.height
}, 0) +
(len + 1) * marginY +
len * node.expandBtnSize
this.updateBrothersTop(node, totalHeight)
}
},
null,
true
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-12 18:55:03
* @Desc: 递归计算节点的宽度
*/
getNodeAreaWidth(node) {
let widthArr = []
let loop = (node, width) => {
if (node.children.length) {
width += node.width / 2
node.children.forEach(item => {
loop(item, width)
})
} else {
width += node.width
widthArr.push(width)
}
}
loop(node, 0)
return Math.max(...widthArr)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-13 11:12:51
* @Desc: 调整兄弟节点的left
*/
updateBrothersLeft(node, addWidth) {
if (node.parent) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item === node
})
// 存在大于一个节点时,第一个或最后一个节点自身也需要移动,否则两边不对称
if (
(index === 0 || index === childrenList.length - 1) &&
childrenList.length > 1
) {
let _offset = index === 0 ? -addWidth : addWidth
node.left += _offset
if (
node.children &&
node.children.length &&
!node.hasCustomPosition()
) {
this.updateChildren(node.children, 'left', _offset)
}
}
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
return
}
let _offset = 0
if (_index < index) {
// 左边的节点往左移
_offset = -addWidth
} else if (_index > index) {
// 右边的节点往右移
_offset = addWidth
}
item.left += _offset
// 同步更新子节点的位置
if (item.children && item.children.length) {
this.updateChildren(item.children, 'left', _offset)
}
})
// 更新父节点的位置
this.updateBrothersLeft(node.parent, addWidth)
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:26:03
* @Desc: 调整兄弟节点的top
*/
updateBrothersTop(node, addHeight) {
if (node.parent && !node.parent.isRoot) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item === node
})
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
return
}
let _offset = 0
// 下面的节点往下移
if (_index > index) {
_offset = addHeight
}
item.top += _offset
// 同步更新子节点的位置
if (item.children && item.children.length) {
this.updateChildren(item.children, 'top', _offset)
}
})
// 更新父节点的位置
this.updateBrothersTop(node.parent, addHeight)
}
}
/**
* @Author: 王林
* @Date: 2021-04-11 14:42:48
* @Desc: 绘制连线,连接该节点到其子节点
*/
renderLine(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
let len = node.children.length
let marginX = this.getMarginX(node.layerIndex + 1)
if (node.isRoot) {
// 根节点
let x1 = left + width / 2
let y1 = top + height
let s1 = marginX * 0.7
let minx = Infinity
let maxx = -Infinity
node.children.forEach((item, index) => {
let x2 = item.left + item.width / 2
let y2 = item.top
if (x2 < minx) {
minx = x2
}
if (x2 > maxx) {
maxx = x2
}
let path = `M ${x2},${y1 + s1} L ${x2},${
y1 + s1 > y2 ? y2 + item.height : y2
}`
// 竖线
lines[index].plot(path)
style && style(lines[index], item)
})
minx = Math.min(minx, x1)
maxx = Math.max(maxx, x1)
// 父节点的竖线
let line1 = this.draw.path()
node.style.line(line1)
line1.plot(`M ${x1},${y1} L ${x1},${y1 + s1}`)
node._lines.push(line1)
style && style(line1, node)
// 水平线
if (len > 0) {
let lin2 = this.draw.path()
node.style.line(lin2)
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
node._lines.push(lin2)
style && style(lin2, node)
}
} else {
// 非根节点
let y1 = top + height
let maxy = -Infinity
let x2 = node.left + node.width * 0.3
node.children.forEach((item, index) => {
// 为了适配自定义位置,下面做了各种位置的兼容
let y2 = item.top + item.height / 2
if (y2 > maxy) {
maxy = y2
}
// 水平线
let path = ''
let _left = item.left
let _isLeft = item.left + item.width < x2
let _isXCenter = false
if (_isLeft) {
// 水平位置在父节点左边
_left = item.left + item.width
} else if (item.left < x2 && item.left + item.width > x2) {
// 水平位置在父节点之间
_isXCenter = true
y2 = item.top
maxy = y2
}
if (y2 > top && y2 < y1) {
// 自定义位置的情况:垂直位置节点在父节点之间
path = `M ${
_isLeft ? node.left : node.left + node.width
},${y2} L ${_left},${y2}`
} else if (y2 < y1) {
// 自定义位置的情况:垂直位置节点在父节点上面
if (_isXCenter) {
y2 = item.top + item.height
_left = x2
}
path = `M ${x2},${top} L ${x2},${y2} L ${_left},${y2}`
} else {
if (_isXCenter) {
_left = x2
}
path = `M ${x2},${y2} L ${_left},${y2}`
}
lines[index].plot(path)
style && style(lines[index], item)
})
// 竖线
if (len > 0) {
let lin2 = this.draw.path()
expandBtnSize = len > 0 ? expandBtnSize : 0
node.style.line(lin2)
if (maxy < y1 + expandBtnSize) {
lin2.hide()
} else {
lin2.plot(`M ${x2},${y1 + expandBtnSize} L ${x2},${maxy}`)
lin2.show()
}
node._lines.push(lin2)
style && style(lin2, node)
}
}
}
/**
* @Author: 王林
* @Date: 2021-04-11 19:54:26
* @Desc: 渲染按钮
*/
renderExpandBtn(node, btn) {
let { width, height, expandBtnSize, isRoot } = node
if (!isRoot) {
let { translateX, translateY } = btn.transform()
btn.translate(
width * 0.3 - expandBtnSize / 2 - translateX,
height + expandBtnSize / 2 - translateY
)
}
}
/**
* @Author: 王林
* @Date: 2022-07-30 08:30:35
* @Desc: 创建概要节点
*/
renderGeneralization(node, gLine, gNode) {
let {
top,
bottom,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'h')
let x1 = right + generalizationLineMargin
let y1 = top
let x2 = right + generalizationLineMargin
let y2 = bottom
let cx = x1 + 20
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
gLine.plot(path)
gNode.left = right + generalizationNodeMargin
gNode.top = top + (bottom - top - gNode.height) / 2
}
}
export default CatalogOrganization
import Base from './Base'
import { walk, asyncRun } from '../utils'
// 目录组织图
class CatalogOrganization extends Base {
// 构造函数
constructor(opt = {}) {
super(opt)
}
// 布局
doLayout(callback) {
let task = [
() => {
this.computedBaseValue()
},
() => {
this.computedLeftTopValue()
},
() => {
this.adjustLeftTopValue()
},
() => {
callback(this.root)
}
]
asyncRun(task)
}
// 遍历数据计算节点的left、width、height
computedBaseValue() {
walk(
this.renderer.renderTree,
null,
(cur, parent, isRoot, layerIndex) => {
let newNode = this.createNode(cur, parent, isRoot, layerIndex)
// 根节点定位在画布中心位置
if (isRoot) {
this.setNodeCenter(newNode)
} else {
// 非根节点
if (parent._node.isRoot) {
newNode.top =
parent._node.top +
parent._node.height +
this.getMarginX(layerIndex)
}
}
if (!cur.data.expand) {
return true
}
},
(cur, parent, isRoot, layerIndex) => {
if (isRoot) {
let len = cur.data.expand === false ? 0 : cur._node.children.length
cur._node.childrenAreaWidth = len
? cur._node.children.reduce((h, item) => {
return h + item.width
}, 0) +
(len + 1) * this.getMarginX(layerIndex + 1)
: 0
}
},
true,
0
)
}
// 遍历节点树计算节点的left、top
computedLeftTopValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (
node.nodeData.data.expand &&
node.children &&
node.children.length
) {
let marginX = this.getMarginX(layerIndex + 1)
let marginY = this.getMarginY(layerIndex + 1)
if (isRoot) {
let left = node.left + node.width / 2 - node.childrenAreaWidth / 2
let totalLeft = left + marginX
node.children.forEach(cur => {
cur.left = totalLeft
totalLeft += cur.width + marginX
})
} else {
let totalTop = node.top + node.height + marginY + node.expandBtnSize
node.children.forEach(cur => {
cur.left = node.left + node.width * 0.5
cur.top = totalTop
totalTop += cur.height + marginY + node.expandBtnSize
})
}
}
},
null,
true
)
}
// 调整节点left、top
adjustLeftTopValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
return
}
// 调整left
if (parent && parent.isRoot) {
let areaWidth = this.getNodeAreaWidth(node)
let difference = areaWidth - node.width
if (difference > 0) {
this.updateBrothersLeft(node, difference / 2)
}
}
// 调整top
let len = node.children.length
if (parent && !parent.isRoot && len > 0) {
let marginY = this.getMarginY(layerIndex + 1)
let totalHeight =
node.children.reduce((h, item) => {
return h + item.height
}, 0) +
(len + 1) * marginY +
len * node.expandBtnSize
this.updateBrothersTop(node, totalHeight)
}
},
null,
true
)
}
// 递归计算节点的宽度
getNodeAreaWidth(node) {
let widthArr = []
let loop = (node, width) => {
if (node.children.length) {
width += node.width / 2
node.children.forEach(item => {
loop(item, width)
})
} else {
width += node.width
widthArr.push(width)
}
}
loop(node, 0)
return Math.max(...widthArr)
}
// 调整兄弟节点的left
updateBrothersLeft(node, addWidth) {
if (node.parent) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item === node
})
// 存在大于一个节点时,第一个或最后一个节点自身也需要移动,否则两边不对称
if (
(index === 0 || index === childrenList.length - 1) &&
childrenList.length > 1
) {
let _offset = index === 0 ? -addWidth : addWidth
node.left += _offset
if (
node.children &&
node.children.length &&
!node.hasCustomPosition()
) {
this.updateChildren(node.children, 'left', _offset)
}
}
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
return
}
let _offset = 0
if (_index < index) {
// 左边的节点往左移
_offset = -addWidth
} else if (_index > index) {
// 右边的节点往右移
_offset = addWidth
}
item.left += _offset
// 同步更新子节点的位置
if (item.children && item.children.length) {
this.updateChildren(item.children, 'left', _offset)
}
})
// 更新父节点的位置
this.updateBrothersLeft(node.parent, addWidth)
}
}
// 调整兄弟节点的top
updateBrothersTop(node, addHeight) {
if (node.parent && !node.parent.isRoot) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item === node
})
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
return
}
let _offset = 0
// 下面的节点往下移
if (_index > index) {
_offset = addHeight
}
item.top += _offset
// 同步更新子节点的位置
if (item.children && item.children.length) {
this.updateChildren(item.children, 'top', _offset)
}
})
// 更新父节点的位置
this.updateBrothersTop(node.parent, addHeight)
}
}
// 绘制连线,连接该节点到其子节点
renderLine(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
let len = node.children.length
let marginX = this.getMarginX(node.layerIndex + 1)
if (node.isRoot) {
// 根节点
let x1 = left + width / 2
let y1 = top + height
let s1 = marginX * 0.7
let minx = Infinity
let maxx = -Infinity
node.children.forEach((item, index) => {
let x2 = item.left + item.width / 2
let y2 = item.top
if (x2 < minx) {
minx = x2
}
if (x2 > maxx) {
maxx = x2
}
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
: ''
let path =
`M ${x2},${y1 + s1} L ${x2},${y1 + s1 > y2 ? y2 + item.height : y2}` +
nodeUseLineStylePath
// 竖线
lines[index].plot(path)
style && style(lines[index], item)
})
minx = Math.min(minx, x1)
maxx = Math.max(maxx, x1)
// 父节点的竖线
let line1 = this.draw.path()
node.style.line(line1)
line1.plot(`M ${x1},${y1} L ${x1},${y1 + s1}`)
node._lines.push(line1)
style && style(line1, node)
// 水平线
if (len > 0) {
let lin2 = this.draw.path()
node.style.line(lin2)
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
node._lines.push(lin2)
style && style(lin2, node)
}
} else {
// 非根节点
let y1 = top + height
let maxy = -Infinity
let x2 = node.left + node.width * 0.3
node.children.forEach((item, index) => {
// 为了适配自定义位置,下面做了各种位置的兼容
let y2 = item.top + item.height / 2
if (y2 > maxy) {
maxy = y2
}
// 水平线
let path = ''
let _left = item.left
let _isLeft = item.left + item.width < x2
let _isXCenter = false
if (_isLeft) {
// 水平位置在父节点左边
_left = item.left + item.width
} else if (item.left < x2 && item.left + item.width > x2) {
// 水平位置在父节点之间
_isXCenter = true
y2 = item.top
maxy = y2
}
if (y2 > top && y2 < y1) {
// 自定义位置的情况:垂直位置节点在父节点之间
path = `M ${
_isLeft ? node.left : node.left + node.width
},${y2} L ${_left},${y2}`
} else if (y2 < y1) {
// 自定义位置的情况:垂直位置节点在父节点上面
if (_isXCenter) {
y2 = item.top + item.height
_left = x2
}
path = `M ${x2},${top} L ${x2},${y2} L ${_left},${y2}`
} else {
if (_isXCenter) {
_left = x2
}
path = `M ${x2},${y2} L ${_left},${y2}`
}
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
? ` L ${_left},${y2 - item.height / 2} L ${_left},${
y2 + item.height / 2
}`
: ''
path += nodeUseLineStylePath
lines[index].plot(path)
style && style(lines[index], item)
})
// 竖线
if (len > 0) {
let lin2 = this.draw.path()
expandBtnSize = len > 0 ? expandBtnSize : 0
node.style.line(lin2)
if (maxy < y1 + expandBtnSize) {
lin2.hide()
} else {
lin2.plot(`M ${x2},${y1 + expandBtnSize} L ${x2},${maxy}`)
lin2.show()
}
node._lines.push(lin2)
style && style(lin2, node)
}
}
}
// 渲染按钮
renderExpandBtn(node, btn) {
let { width, height, expandBtnSize, isRoot } = node
if (!isRoot) {
let { translateX, translateY } = btn.transform()
btn.translate(
width * 0.3 - expandBtnSize / 2 - translateX,
height + expandBtnSize / 2 - translateY
)
}
}
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let {
top,
bottom,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'h')
let x1 = right + generalizationLineMargin
let y1 = top

View File

@@ -1,27 +1,17 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
/**
* @Author: 王林
* @Date: 2021-04-12 22:25:58
* @Desc: 逻辑结构图
*/
// 逻辑结构图
class LogicalStructure extends Base {
/**
* @Author: 王林
* @Date: 2021-04-12 22:26:31
* @Desc: 构造函数
*/
// 构造函数
constructor(opt = {}) {
super(opt)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 14:04:20
* @Desc: 布局
*/
// 布局
doLayout(callback) {
let task = [
() => {
@@ -40,12 +30,8 @@ class LogicalStructure extends Base {
asyncRun(task)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:49:32
* @Desc: 遍历数据计算节点的left、width、height
*/
// 遍历数据计算节点的left、width、height
computedBaseValue() {
walk(
this.renderer.renderTree,
@@ -80,12 +66,8 @@ class LogicalStructure extends Base {
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:59:25
* @Desc: 遍历节点树计算节点的top
*/
// 遍历节点树计算节点的top
computedTopValue() {
walk(
this.root,
@@ -111,12 +93,8 @@ class LogicalStructure extends Base {
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 10:04:05
* @Desc: 调整节点top
*/
// 调整节点top
adjustTopValue() {
walk(
this.root,
@@ -139,12 +117,8 @@ class LogicalStructure extends Base {
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:26:03
* @Desc: 更新兄弟节点的top
*/
// 更新兄弟节点的top
updateBrothers(node, addHeight) {
if (node.parent) {
let childrenList = node.parent.children
@@ -175,11 +149,8 @@ class LogicalStructure extends Base {
}
}
/**
* @Author: 王林
* @Date: 2021-04-11 14:42:48
* @Desc: 绘制连线,连接该节点到其子节点
*/
// 绘制连线,连接该节点到其子节点
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'curve') {
this.renderLineCurve(node, lines, style)
@@ -190,12 +161,8 @@ class LogicalStructure extends Base {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:17:30
* @Desc: 直线风格连线
*/
// 直线风格连线
renderLineStraight(node, lines, style) {
if (node.children.length <= 0) {
return []
@@ -203,54 +170,61 @@ class LogicalStructure extends Base {
let { left, top, width, height, expandBtnSize } = node
let marginX = this.getMarginX(node.layerIndex + 1)
let s1 = (marginX - expandBtnSize) * 0.6
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0 ? left + width : left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
let path = `M ${x1},${y1} L ${x1 + s1},${y1} L ${
x1 + s1
},${y2} L ${x2},${y2}`
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStyleOffset = nodeUseLineStyle
? item.width
: 0
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
let path = `M ${x1},${y1} L ${x1 + s1},${y1} L ${x1 + s1},${y2} L ${
x2 + nodeUseLineStyleOffset
},${y2}`
lines[index].plot(path)
style && style(lines[index], item)
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:34:41
* @Desc: 直连风格
*/
// 直连风格
renderLineDirect(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize
let y1 = top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
let path = `M ${x1},${y1} L ${x2},${y2}`
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = nodeUseLineStyle
? ` L ${item.left + item.width},${y2}`
: ''
let path = `M ${x1},${y1} L ${x2},${y2}` + nodeUseLineStylePath
lines[index].plot(path)
style && style(lines[index], item)
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:17:43
* @Desc: 曲线风格连线
*/
// 曲线风格连线
renderLineCurve(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize
@@ -258,32 +232,39 @@ class LogicalStructure extends Base {
let x2 = item.left
let y2 = item.top + item.height / 2
let path = ''
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = nodeUseLineStyle
? ` L ${item.left + item.width},${y2}`
: ''
if (node.isRoot) {
path = this.quadraticCurvePath(x1, y1, x2, y2)
path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath
} else {
path = this.cubicBezierPath(x1, y1, x2, y2)
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath
}
lines[index].plot(path)
style && style(lines[index], item)
})
}
/**
* @Author: 王林
* @Date: 2021-04-11 19:54:26
* @Desc: 渲染按钮
*/
// 渲染按钮
renderExpandBtn(node, btn) {
let { width, height } = node
let { translateX, translateY } = btn.transform()
btn.translate(width - translateX, height / 2 - translateY)
// 节点使用横线风格,需要调整展开收起按钮位置
let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle
? height / 2
: 0
btn.translate(
width - translateX,
height / 2 - translateY + nodeUseLineStyleOffset
)
}
/**
* @Author: 王林
* @Date: 2022-07-30 08:30:35
* @Desc: 创建概要节点
*/
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let {
top,

View File

@@ -1,28 +1,17 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
/**
* @Author: 王林
* @Date: 2021-04-12 22:25:58
* @Desc: 思维导图
* 在逻辑结构图的基础上增加一个变量来记录生长方向向左还是向右同时在计算left的时候根据方向来计算、调整top时只考虑同方向的节点即可
*/
// 思维导图
class MindMap extends Base {
/**
* @Author: 王林
* @Date: 2021-04-12 22:26:31
* @Desc: 构造函数
*/
// 构造函数
// 在逻辑结构图的基础上增加一个变量来记录生长方向向左还是向右同时在计算left的时候根据方向来计算、调整top时只考虑同方向的节点即可
constructor(opt = {}) {
super(opt)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 14:04:20
* @Desc: 布局
*/
// 布局
doLayout(callback) {
let task = [
() => {
@@ -41,12 +30,8 @@ class MindMap extends Base {
asyncRun(task)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:49:32
* @Desc: 遍历数据计算节点的left、width、height
*/
// 遍历数据计算节点的left、width、height
computedBaseValue() {
walk(
this.renderer.renderTree,
@@ -110,12 +95,8 @@ class MindMap extends Base {
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:59:25
* @Desc: 遍历节点树计算节点的top
*/
// 遍历节点树计算节点的top
computedTopValue() {
walk(
this.root,
@@ -147,12 +128,8 @@ class MindMap extends Base {
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 10:04:05
* @Desc: 调整节点top
*/
// 调整节点top
adjustTopValue() {
walk(
this.root,
@@ -174,12 +151,8 @@ class MindMap extends Base {
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:26:03
* @Desc: 更新兄弟节点的top
*/
// 更新兄弟节点的top
updateBrothers(node, leftAddHeight, rightAddHeight) {
if (node.parent) {
// 过滤出和自己同方向的节点
@@ -214,11 +187,8 @@ class MindMap extends Base {
}
}
/**
* @Author: 王林
* @Date: 2021-04-11 14:42:48
* @Desc: 绘制连线,连接该节点到其子节点
*/
// 绘制连线,连接该节点到其子节点
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'curve') {
this.renderLineCurve(node, lines, style)
@@ -229,12 +199,8 @@ class MindMap extends Base {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:10:47
* @Desc: 直线风格连线
*/
// 直线风格连线
renderLineStraight(node, lines, style) {
if (node.children.length <= 0) {
return []
@@ -242,12 +208,18 @@ class MindMap extends Base {
let { left, top, width, height, expandBtnSize } = node
let marginX = this.getMarginX(node.layerIndex + 1)
let s1 = (marginX - expandBtnSize) * 0.6
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
node.children.forEach((item, index) => {
let x1 = 0
let _s = 0
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStyleOffset = nodeUseLineStyle
? item.width
: 0
if (item.dir === 'left') {
_s = -s1
x1 = node.layerIndex === 0 ? left : left - expandBtnSize
nodeUseLineStyleOffset = -nodeUseLineStyleOffset
} else {
_s = s1
x1 = node.layerIndex === 0 ? left + width : left + width + expandBtnSize
@@ -255,25 +227,24 @@ class MindMap extends Base {
let y1 = top + height / 2
let x2 = item.dir === 'left' ? item.left + item.width : item.left
let y2 = item.top + item.height / 2
let path = `M ${x1},${y1} L ${x1 + _s},${y1} L ${
x1 + _s
},${y2} L ${x2},${y2}`
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
let path = `M ${x1},${y1} L ${x1 + _s},${y1} L ${x1 + _s},${y2} L ${
x2 + nodeUseLineStyleOffset
},${y2}`
lines[index].plot(path)
style && style(lines[index], item)
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:34:41
* @Desc: 直连风格
*/
// 直连风格
renderLineDirect(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0
@@ -284,23 +255,31 @@ class MindMap extends Base {
let y1 = top + height / 2
let x2 = item.dir === 'left' ? item.left + item.width : item.left
let y2 = item.top + item.height / 2
let path = `M ${x1},${y1} L ${x2},${y2}`
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = ''
if (nodeUseLineStyle) {
if (item.dir === 'left') {
nodeUseLineStylePath = ` L ${item.left},${y2}`
} else {
nodeUseLineStylePath = ` L ${item.left + item.width},${y2}`
}
}
let path = `M ${x1},${y1} L ${x2},${y2}` + nodeUseLineStylePath
lines[index].plot(path)
style && style(lines[index], item)
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:10:56
* @Desc: 曲线风格连线
*/
// 曲线风格连线
renderLineCurve(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
node.children.forEach((item, index) => {
let x1 =
node.layerIndex === 0
@@ -312,34 +291,43 @@ class MindMap extends Base {
let x2 = item.dir === 'left' ? item.left + item.width : item.left
let y2 = item.top + item.height / 2
let path = ''
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = ''
if (this.mindMap.themeConfig.nodeUseLineStyle) {
if (item.dir === 'left') {
nodeUseLineStylePath = ` L ${item.left},${y2}`
} else {
nodeUseLineStylePath = ` L ${item.left + item.width},${y2}`
}
}
if (node.isRoot) {
path = this.quadraticCurvePath(x1, y1, x2, y2)
path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath
} else {
path = this.cubicBezierPath(x1, y1, x2, y2)
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath
}
lines[index].plot(path)
style && style(lines[index], item)
})
}
/**
* @Author: 王林
* @Date: 2021-04-11 19:54:26
* @Desc: 渲染按钮
*/
// 渲染按钮
renderExpandBtn(node, btn) {
let { width, height, expandBtnSize } = node
let { translateX, translateY } = btn.transform()
// 节点使用横线风格,需要调整展开收起按钮位置
let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle
? height / 2
: 0
let x = (node.dir === 'left' ? 0 - expandBtnSize : width) - translateX
let y = height / 2 - translateY
let y = height / 2 - translateY + nodeUseLineStyleOffset
btn.translate(x, y)
}
/**
* @Author: 王林
* @Date: 2022-07-30 08:30:35
* @Desc: 创建概要节点
*/
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let isLeft = node.dir === 'left'
let {

View File

@@ -1,28 +1,17 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
/**
* @Author: 王林
* @Date: 2021-04-12 22:25:58
* @Desc: 组织结构图
* 和逻辑结构图基本一样只是方向变成向下生长所以先计算节点的top后计算节点的left、最后调整节点的left即可
*/
// 组织结构图
// 和逻辑结构图基本一样只是方向变成向下生长所以先计算节点的top后计算节点的left、最后调整节点的left即可
class OrganizationStructure extends Base {
/**
* @Author: 王林
* @Date: 2021-04-12 22:26:31
* @Desc: 构造函数
*/
// 构造函数
constructor(opt = {}) {
super(opt)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 14:04:20
* @Desc: 布局
*/
// 布局
doLayout(callback) {
let task = [
() => {
@@ -41,12 +30,8 @@ class OrganizationStructure extends Base {
asyncRun(task)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:49:32
* @Desc: 遍历数据计算节点的left、width、height
*/
// 遍历数据计算节点的left、width、height
computedBaseValue() {
walk(
this.renderer.renderTree,
@@ -81,12 +66,8 @@ class OrganizationStructure extends Base {
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:59:25
* @Desc: 遍历节点树计算节点的left
*/
// 遍历节点树计算节点的left
computedLeftValue() {
walk(
this.root,
@@ -112,12 +93,8 @@ class OrganizationStructure extends Base {
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 10:04:05
* @Desc: 调整节点left
*/
// 调整节点left
adjustLeftValue() {
walk(
this.root,
@@ -140,12 +117,8 @@ class OrganizationStructure extends Base {
)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:26:03
* @Desc: 更新兄弟节点的left
*/
// 更新兄弟节点的left
updateBrothers(node, addWidth) {
if (node.parent) {
let childrenList = node.parent.children
@@ -176,11 +149,8 @@ class OrganizationStructure extends Base {
}
}
/**
* @Author: 王林
* @Date: 2021-04-11 14:42:48
* @Desc: 绘制连线,连接该节点到其子节点
*/
// 绘制连线,连接该节点到其子节点
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'direct') {
this.renderLineDirect(node, lines, style)
@@ -189,12 +159,8 @@ class OrganizationStructure extends Base {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:34:41
* @Desc: 直连风格
*/
// 直连风格
renderLineDirect(node, lines, style) {
if (node.children.length <= 0) {
return []
@@ -205,18 +171,18 @@ class OrganizationStructure extends Base {
node.children.forEach((item, index) => {
let x2 = item.left + item.width / 2
let y2 = item.top
let path = `M ${x1},${y1} L ${x2},${y2}`
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
: ''
let path = `M ${x1},${y1} L ${x2},${y2}` + nodeUseLineStylePath
lines[index].plot(path)
style && style(lines[index], item)
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-30 14:39:07
* @Desc: 直线风格连线
*/
// 直线风格连线
renderLineStraight(node, lines, style) {
if (node.children.length <= 0) {
return []
@@ -238,7 +204,11 @@ class OrganizationStructure extends Base {
if (x2 > maxx) {
maxx = x2
}
let path = `M ${x2},${y1 + s1} L ${x2},${y2}`
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
: ''
let path = `M ${x2},${y1 + s1} L ${x2},${y2}` + nodeUseLineStylePath
lines[index].plot(path)
style && style(lines[index], item)
})
@@ -261,11 +231,8 @@ class OrganizationStructure extends Base {
}
}
/**
* @Author: 王林
* @Date: 2021-04-11 19:54:26
* @Desc: 渲染按钮
*/
// 渲染按钮
renderExpandBtn(node, btn) {
let { width, height, expandBtnSize } = node
let { translateX, translateY } = btn.transform()
@@ -275,11 +242,8 @@ class OrganizationStructure extends Base {
)
}
/**
* @Author: 王林
* @Date: 2022-07-30 08:30:35
* @Desc: 创建概要节点
*/
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let {
bottom,

View File

@@ -1,12 +1,7 @@
import JSZip from 'jszip'
import xmlConvert from 'xml-js'
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-21 14:07:47
* @Desc: 解析.xmind文件
*/
// 解析.xmind文件
const parseXmindFile = file => {
return new Promise((resolve, reject) => {
JSZip.loadAsync(file).then(
@@ -37,12 +32,7 @@ const parseXmindFile = file => {
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-21 18:57:25
* @Desc: 转换xmind数据
*/
// 转换xmind数据
const transformXmind = content => {
let data = JSON.parse(content)[0]
let nodeTree = data.rootTopic
@@ -82,12 +72,7 @@ const transformXmind = content => {
return newTree
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-23 15:51:51
* @Desc: 转换旧版xmind数据xmind8
*/
// 转换旧版xmind数据xmind8
const transformOldXmind = content => {
let data = JSON.parse(content)
let elements = data.elements

View File

@@ -1,15 +1,7 @@
/**
* @Author: 王林
* @Date: 2021-04-11 19:46:10
* @Desc: 展开按钮
*/
// 展开按钮
const open = `<svg t="1618141562310" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13476" width="200" height="200"><path d="M475.136 327.168v147.968h-147.968v74.24h147.968v147.968h74.24v-147.968h147.968v-74.24h-147.968v-147.968h-74.24z m36.864-222.208c225.28 0 407.04 181.76 407.04 407.04s-181.76 407.04-407.04 407.04-407.04-181.76-407.04-407.04 181.76-407.04 407.04-407.04z m0-74.24c-265.216 0-480.768 215.552-480.768 480.768s215.552 480.768 480.768 480.768 480.768-215.552 480.768-480.768-215.552-480.768-480.768-480.768z" p-id="13477"></path></svg>`
/**
* @Author: 王林
* @Date: 2021-04-11 19:46:23
* @Desc: 收缩按钮
*/
// 收缩按钮
const close = `<svg t="1618141589243" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13611" width="200" height="200"><path d="M512 105.472c225.28 0 407.04 181.76 407.04 407.04s-181.76 407.04-407.04 407.04-407.04-181.76-407.04-407.04 181.76-407.04 407.04-407.04z m0-74.24c-265.216 0-480.768 215.552-480.768 480.768s215.552 480.768 480.768 480.768 480.768-215.552 480.768-480.768-215.552-480.768-480.768-480.768z" p-id="13612"></path><path d="M252.928 474.624h518.144v74.24h-518.144z" p-id="13613"></path></svg>`
export default {

View File

@@ -278,11 +278,7 @@ export const nodeIconList = [
}
]
/**
* @Author: 王林
* @Date: 2021-06-23 22:36:56
* @Desc: 获取nodeIconList icon内容
*/
// 获取nodeIconList icon内容
const getNodeIconListIcon = name => {
let arr = name.split('_')
let typeData = nodeIconList.find(item => {

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 天空蓝
*/
// 天空蓝
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(115, 161, 191)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 脑残粉
*/
// 脑残粉
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 115, 148)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 脑图经典
*/
// 脑图经典
export default merge(defaultTheme, {
// 连线的颜色
lineColor: '#fff',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 经典2
*/
// 经典2
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 经典3
*/
// 经典3
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(94, 202, 110)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 经典4
*/
// 经典4
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(30, 53, 86)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 经典蓝
*/
// 经典蓝
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 经典绿
*/
// 经典绿
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(123, 199, 120)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 暗色
*/
// 暗色
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(17, 68, 23)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 暗色2
*/
// 暗色2
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(75, 81, 78)',

View File

@@ -1,8 +1,5 @@
/**
* @Author: 王林
* @Date: 2021-04-11 10:19:55
* @Desc: 默认主题
*/
// 默认主题
export default {
// 节点内边距
paddingX: 15,
@@ -35,6 +32,12 @@ export default {
backgroundImage: 'none',
// 背景重复
backgroundRepeat: 'no-repeat',
// 设置背景图像的起始位置
backgroundPosition: 'center center',
// 设置背景图片大小
backgroundSize: 'cover',
// 节点使用横线样式
nodeUseLineStyle: false,
// 根节点样式
root: {
shape: 'rectangle',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 泥土黄
*/
// 泥土黄
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 147, 115)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 清新绿
*/
// 清新绿
export default merge(defaultTheme, {
// 连线的颜色
lineColor: '#333',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 清新红
*/
// 清新红
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 115, 115)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 金色vip
*/
// 金色vip
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 56, 62)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 绿叶
*/
// 绿叶
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(40, 193, 84)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 小黄人
*/
// 小黄人
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 薄荷
*/
// 薄荷
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(104, 204, 202)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 粉红葡萄
*/
// 粉红葡萄
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(166, 101, 106)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 浪漫紫
*/
// 浪漫紫
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(123, 115, 191)',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 天清绿
*/
// 天清绿
export default merge(defaultTheme, {
// 连线的颜色
lineColor: '#fff',

View File

@@ -1,11 +1,7 @@
import defaultTheme from './default'
import merge from 'deepmerge'
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 活力橙
*/
// 活力橙
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(254, 146, 0)',

View File

@@ -1,8 +1,4 @@
/**
* @Author: 王林
* @Date: 2021-06-24 21:42:07
* @Desc: 标签颜色列表
*/
// 标签颜色列表
export const tagColorList = [
{
color: 'rgb(77, 65, 0)',
@@ -26,32 +22,23 @@ export const tagColorList = [
}
]
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-13 15:56:28
* @Desc: 布局结构列表
*/
// 布局结构列表
export const layoutList = [
{
name: '逻辑结构图',
value: 'logicalStructure',
img: require('../assets/logicalStructure.jpg')
},
{
name: '思维导图',
value: 'mindMap',
img: require('../assets/mindMap.jpg')
},
{
name: '组织结构图',
value: 'organizationStructure',
img: require('../assets/organizationStructure.jpg')
},
{
name: '目录组织图',
value: 'catalogOrganization',
img: require('../assets/catalogOrganization.jpg')
}
]
export const layoutValueList = [
@@ -61,120 +48,94 @@ export const layoutValueList = [
'organizationStructure'
]
/**
* @Author: 王林
* @Date: 2021-06-24 22:58:42
* @Desc: 主题列表
*/
// 主题列表
export const themeList = [
{
name: '默认',
value: 'default',
img: require('../assets/default.jpg')
},
{
name: '脑图经典',
value: 'classic',
img: require('../assets/classic.jpg')
},
{
name: '小黄人',
value: 'minions',
img: require('../assets/minions.jpg')
},
{
name: '粉红葡萄',
value: 'pinkGrape',
img: require('../assets/pinkGrape.jpg')
},
{
name: '薄荷',
value: 'mint',
img: require('../assets/mint.jpg')
},
{
name: '金色vip',
value: 'gold',
img: require('../assets/gold.jpg')
},
{
name: '活力橙',
value: 'vitalityOrange',
img: require('../assets/vitalityOrange.jpg')
},
{
name: '绿叶',
value: 'greenLeaf',
img: require('../assets/greenLeaf.jpg')
},
{
name: '暗色2',
value: 'dark2',
img: require('../assets/dark2.jpg')
},
{
name: '天清绿',
value: 'skyGreen',
img: require('../assets/skyGreen.jpg')
},
{
name: '脑图经典2',
value: 'classic2',
img: require('../assets/classic2.jpg')
},
{
name: '脑图经典3',
value: 'classic3',
img: require('../assets/classic3.jpg')
},
{
name: '脑图经典4',
value: 'classic4',
img: require('../assets/classic4.jpg')
},
{
name: '经典绿',
value: 'classicGreen',
img: require('../assets/classicGreen.jpg')
},
{
name: '经典蓝',
value: 'classicBlue',
img: require('../assets/classicBlue.jpg')
},
{
name: '天空蓝',
value: 'blueSky',
img: require('../assets/blueSky.jpg')
},
{
name: '脑残粉',
value: 'brainImpairedPink',
img: require('../assets/brainImpairedPink.jpg')
},
{
name: '暗色',
value: 'dark',
img: require('../assets/dark.jpg')
},
{
name: '泥土黄',
value: 'earthYellow',
img: require('../assets/earthYellow.jpg')
},
{
name: '清新绿',
value: 'freshGreen',
img: require('../assets/freshGreen.jpg')
},
{
name: '清新红',
value: 'freshRed',
img: require('../assets/freshRed.jpg')
},
{
name: '浪漫紫',
value: 'romanticPurple',
img: require('../assets/romanticPurple.jpg')
}
]

View File

@@ -1,9 +1,4 @@
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 14:13:17
* @Desc: 深度优先遍历树
*/
// 深度优先遍历树
export const walk = (
root,
parent,
@@ -34,12 +29,7 @@ export const walk = (
afterCallback && afterCallback(root, parent, isRoot, layerIndex, index)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 18:47:20
* @Desc: 广度优先遍历树
*/
// 广度优先遍历树
export const bfsWalk = (root, callback) => {
callback(root)
let stack = [root]
@@ -60,12 +50,7 @@ export const bfsWalk = (root, callback) => {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 10:44:54
* @Desc: 缩放图片尺寸
*/
// 缩放图片尺寸
export const resizeImgSize = (width, height, maxWidth, maxHeight) => {
let nRatio = width / height
let arr = []
@@ -98,12 +83,7 @@ export const resizeImgSize = (width, height, maxWidth, maxHeight) => {
return arr
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 10:18:42
* @Desc: 缩放图片
*/
// 缩放图片
export const resizeImg = (imgUrl, maxWidth, maxHeight) => {
return new Promise((resolve, reject) => {
let img = new Image()
@@ -123,11 +103,7 @@ export const resizeImg = (imgUrl, maxWidth, maxHeight) => {
})
}
/**
* @Author: 王林
* @Date: 2021-05-04 12:26:56
* @Desc: 从头html结构字符串里获取带换行符的字符串
*/
// 从头html结构字符串里获取带换行符的字符串
export const getStrWithBrFromHtml = str => {
str = str.replace(/<br>/gim, '\n')
let el = document.createElement('div')
@@ -136,11 +112,7 @@ export const getStrWithBrFromHtml = str => {
return str
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:45:39
* @Desc: 极简的深拷贝
*/
// 极简的深拷贝
export const simpleDeepClone = data => {
try {
return JSON.parse(JSON.stringify(data))
@@ -149,11 +121,7 @@ export const simpleDeepClone = data => {
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:40:11
* @Desc: 复制渲染树数据
*/
// 复制渲染树数据
export const copyRenderTree = (tree, root) => {
tree.data = simpleDeepClone(root.data)
tree.children = []
@@ -165,11 +133,7 @@ export const copyRenderTree = (tree, root) => {
return tree
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:40:11
* @Desc: 复制节点树数据
*/
// 复制节点树数据
export const copyNodeTree = (tree, root, removeActiveState = false) => {
tree.data = simpleDeepClone(root.nodeData ? root.nodeData.data : root.data)
if (removeActiveState) {
@@ -192,11 +156,7 @@ export const copyNodeTree = (tree, root, removeActiveState = false) => {
return tree
}
/**
* @Author: 王林
* @Date: 2021-07-04 09:08:43
* @Desc: 图片转成dataURL
*/
// 图片转成dataURL
export const imgToDataUrl = src => {
return new Promise((resolve, reject) => {
const img = new Image()
@@ -222,11 +182,7 @@ export const imgToDataUrl = src => {
})
}
/**
* @Author: 王林
* @Date: 2021-07-04 16:20:06
* @Desc: 下载文件
*/
// 下载文件
export const downloadFile = (file, fileName) => {
let a = document.createElement('a')
a.href = file
@@ -234,11 +190,7 @@ export const downloadFile = (file, fileName) => {
a.click()
}
/**
* @Author: 王林
* @Date: 2021-07-11 10:36:47
* @Desc: 节流函数
*/
// 节流函数
export const throttle = (fn, time = 300, ctx) => {
let timer = null
return () => {
@@ -252,12 +204,7 @@ export const throttle = (fn, time = 300, ctx) => {
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-12 10:27:36
* @Desc: 异步执行任务队列
*/
// 异步执行任务队列
export const asyncRun = (taskList, callback = () => {}) => {
let index = 0
let len = taskList.length
@@ -277,3 +224,45 @@ export const asyncRun = (taskList, callback = () => {}) => {
}
loop()
}
// 角度转弧度
export const degToRad = deg => {
return deg * (Math.PI / 180)
}
// 驼峰转连字符
export const camelCaseToHyphen = (str) => {
return str.replace(/([a-z])([A-Z])/g, (...args) => {
return args[1] + '-' + args[2].toLowerCase()
})
}
//计算节点的文本长宽
let measureTextContext = null
export const measureText = (text, { italic, bold, fontSize, fontFamily }) => {
const font = joinFontStr({
italic,
bold,
fontSize,
fontFamily
})
if (!measureTextContext) {
const canvas = document.createElement('canvas')
measureTextContext = canvas.getContext('2d')
}
measureTextContext.save()
measureTextContext.font = font
const {
width,
actualBoundingBoxAscent,
actualBoundingBoxDescent
} = measureTextContext.measureText(text)
measureTextContext.restore()
const height = actualBoundingBoxAscent + actualBoundingBoxDescent
return { width, height }
}
// 拼接font字符串
export const joinFontStr = ({ italic, bold, fontSize, fontFamily }) => {
return `${italic ? 'italic ' : ''} ${bold ? 'bold ' : ''} ${fontSize}px ${fontFamily} `
}

View File

@@ -0,0 +1,354 @@
// 将以空格分隔的字符串值转换成成数字/单位/值数组
const getNumberValueFromStr = value => {
let arr = String(value).split(/\s+/)
return arr.map(item => {
if (/^[\d.]+/.test(item)) {
// 数字+单位
let res = /^([\d.]+)(.*)$/.exec(item)
return [Number(res[1]), res[2]]
} else {
// 单个值
return item
}
})
}
// 缩放宽度
const zoomWidth = (ratio, height) => {
// w / height = ratio
return ratio * height
}
// 缩放高度
const zoomHeight = (ratio, width) => {
// width / h = ratio
return width / ratio
}
// 关键词到百分比值的映射
const keyWordToPercentageMap = {
left: 0,
top: 0,
center: 50,
bottom: 100,
right: 100
}
// 模拟background-size
const handleBackgroundSize = ({
backgroundSize,
drawOpt,
imageRatio,
canvasWidth,
canvasHeight,
canvasRatio
}) => {
if (backgroundSize) {
// 将值转换成数组
let backgroundSizeValueArr = getNumberValueFromStr(backgroundSize)
// 两个值都为auto那就相当于不设置
if (
backgroundSizeValueArr[0] === 'auto' &&
backgroundSizeValueArr[1] === 'auto'
) {
return
}
// 值为cover
if (backgroundSizeValueArr[0] === 'cover') {
if (imageRatio > canvasRatio) {
// 图片的宽高比大于canvas的宽高比那么图片高度缩放到和canvas的高度一致宽度自适应
drawOpt.height = canvasHeight
drawOpt.width = zoomWidth(imageRatio, canvasHeight)
} else {
// 否则图片宽度缩放到和canvas的宽度一致高度自适应
drawOpt.width = canvasWidth
drawOpt.height = zoomHeight(imageRatio, canvasWidth)
}
return
}
// 值为contain
if (backgroundSizeValueArr[0] === 'contain') {
if (imageRatio > canvasRatio) {
// 图片的宽高比大于canvas的宽高比那么图片宽度缩放到和canvas的宽度一致高度自适应
drawOpt.width = canvasWidth
drawOpt.height = zoomHeight(imageRatio, canvasWidth)
} else {
// 否则图片高度缩放到和canvas的高度一致宽度自适应
drawOpt.height = canvasHeight
drawOpt.width = zoomWidth(imageRatio, canvasHeight)
}
return
}
// 图片宽度
let newNumberWidth = -1
if (backgroundSizeValueArr[0]) {
if (Array.isArray(backgroundSizeValueArr[0])) {
// 数字+单位类型
if (backgroundSizeValueArr[0][1] === '%') {
// %单位
drawOpt.width = (backgroundSizeValueArr[0][0] / 100) * canvasWidth
newNumberWidth = drawOpt.width
} else {
// 其他都认为是px单位
drawOpt.width = backgroundSizeValueArr[0][0]
newNumberWidth = backgroundSizeValueArr[0][0]
}
} else if (backgroundSizeValueArr[0] === 'auto') {
// auto类型那么根据设置的新高度以图片原宽高比进行自适应
if (backgroundSizeValueArr[1]) {
if (backgroundSizeValueArr[1][1] === '%') {
// 高度为%单位
drawOpt.width = zoomWidth(
imageRatio,
(backgroundSizeValueArr[1][0] / 100) * canvasHeight
)
} else {
// 其他都认为是px单位
drawOpt.width = zoomWidth(imageRatio, backgroundSizeValueArr[1][0])
}
}
}
}
// 设置了图片高度
if (backgroundSizeValueArr[1] && Array.isArray(backgroundSizeValueArr[1])) {
// 数字+单位类型
if (backgroundSizeValueArr[1][1] === '%') {
// 高度为%单位
drawOpt.height = (backgroundSizeValueArr[1][0] / 100) * canvasHeight
} else {
// 其他都认为是px单位
drawOpt.height = backgroundSizeValueArr[1][0]
}
} else if (newNumberWidth !== -1) {
// 没有设置图片高度或者设置为auto那么根据设置的新宽度以图片原宽高比进行自适应
drawOpt.height = zoomHeight(imageRatio, newNumberWidth)
}
}
}
// 模拟background-position
const handleBackgroundPosition = ({
backgroundPosition,
drawOpt,
imgWidth,
imgHeight,
canvasWidth,
canvasHeight
}) => {
if (backgroundPosition) {
// 将值转换成数组
let backgroundPositionValueArr = getNumberValueFromStr(backgroundPosition)
// 将关键词转为百分比
backgroundPositionValueArr = backgroundPositionValueArr.map(item => {
if (typeof item === 'string') {
return keyWordToPercentageMap[item] !== undefined
? [keyWordToPercentageMap[item], '%']
: item
}
return item
})
if (Array.isArray(backgroundPositionValueArr[0])) {
if (backgroundPositionValueArr.length === 1) {
// 如果只设置了一个值第二个默认为50%
backgroundPositionValueArr.push([50, '%'])
}
// 水平位置
if (backgroundPositionValueArr[0][1] === '%') {
// 单位为%
let canvasX = (backgroundPositionValueArr[0][0] / 100) * canvasWidth
let imgX = (backgroundPositionValueArr[0][0] / 100) * imgWidth
// 计算差值
drawOpt.x = canvasX - imgX
} else {
// 其他单位默认都为px
drawOpt.x = backgroundPositionValueArr[0][0]
}
// 垂直位置
if (backgroundPositionValueArr[1][1] === '%') {
// 单位为%
let canvasY = (backgroundPositionValueArr[1][0] / 100) * canvasHeight
let imgY = (backgroundPositionValueArr[1][0] / 100) * imgHeight
// 计算差值
drawOpt.y = canvasY - imgY
} else {
// 其他单位默认都为px
drawOpt.y = backgroundPositionValueArr[1][0]
}
}
}
}
// 模拟background-repeat
const handleBackgroundRepeat = ({
ctx,
image,
backgroundRepeat,
drawOpt,
imgWidth,
imgHeight,
canvasWidth,
canvasHeight
}) => {
if (backgroundRepeat) {
// 保存在handleBackgroundPosition中计算出来的x、y
let ox = drawOpt.x
let oy = drawOpt.y
// 计算ox和oy能平铺的图片数量
let oxRepeatNum = Math.ceil(ox / imgWidth)
let oyRepeatNum = Math.ceil(oy / imgHeight)
// 计算ox和oy第一张图片的位置
let oxRepeatX = ox - oxRepeatNum * imgWidth
let oxRepeatY = oy - oyRepeatNum * imgHeight
// 将值转换成数组
let backgroundRepeatValueArr = getNumberValueFromStr(backgroundRepeat)
// 不处理
if (
backgroundRepeatValueArr[0] === 'no-repeat' ||
(imgWidth >= canvasWidth && imgHeight >= canvasHeight)
) {
return
}
// 水平平铺
if (backgroundRepeatValueArr[0] === 'repeat-x') {
if (canvasWidth > imgWidth) {
let x = oxRepeatX
while (x < canvasWidth) {
drawImage(ctx, image, {
...drawOpt,
x
})
x += imgWidth
}
return true
}
}
// 垂直平铺
if (backgroundRepeatValueArr[0] === 'repeat-y') {
if (canvasHeight > imgHeight) {
let y = oxRepeatY
while (y < canvasHeight) {
drawImage(ctx, image, {
...drawOpt,
y
})
y += imgHeight
}
return true
}
}
// 平铺
if (backgroundRepeatValueArr[0] === 'repeat') {
let x = oxRepeatX
while (x < canvasWidth) {
if (canvasHeight > imgHeight) {
let y = oxRepeatY
while (y < canvasHeight) {
drawImage(ctx, image, {
...drawOpt,
x,
y
})
y += imgHeight
}
}
x += imgWidth
}
return true
}
}
}
// 根据参数绘制图片
const drawImage = (ctx, image, drawOpt) => {
ctx.drawImage(
image,
drawOpt.sx,
drawOpt.sy,
drawOpt.swidth,
drawOpt.sheight,
drawOpt.x,
drawOpt.y,
drawOpt.width,
drawOpt.height
)
}
const drawBackgroundImageToCanvas = (
ctx,
width,
height,
img,
{ backgroundSize, backgroundPosition, backgroundRepeat },
callback = () => {}
) => {
// 画布的长宽比
let canvasRatio = width / height
// 加载图片
let image = new Image()
image.src = img
image.onload = () => {
// 图片的宽度及长宽比
let imgWidth = image.width
let imgHeight = image.height
let imageRatio = imgWidth / imgHeight
// 绘制图片
// drawImage方法的参数值
let drawOpt = {
sx: 0,
sy: 0,
swidth: imgWidth,
sheight: imgHeight,
x: 0,
y: 0,
width: imgWidth,
height: imgHeight
}
// 模拟background-size
handleBackgroundSize({
backgroundSize,
drawOpt,
imageRatio,
canvasWidth: width,
canvasHeight: height,
canvasRatio
})
// 模拟background-position
handleBackgroundPosition({
backgroundPosition,
drawOpt,
imgWidth: drawOpt.width,
imgHeight: drawOpt.height,
imageRatio,
canvasWidth: width,
canvasHeight: height,
canvasRatio
})
// 模拟background-repeat
let notNeedDraw = handleBackgroundRepeat({
ctx,
image,
backgroundRepeat,
drawOpt,
imgWidth: drawOpt.width,
imgHeight: drawOpt.height,
imageRatio,
canvasWidth: width,
canvasHeight: height,
canvasRatio
})
// 绘制图片
if (!notNeedDraw) {
drawImage(ctx, image, drawOpt)
}
callback()
}
image.onerror = e => {
callback(e)
}
}
export default drawBackgroundImageToCanvas

29188
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,13 +6,16 @@
"serve": "vue-cli-service serve",
"build": "vue-cli-service build && node ../copy.js",
"lint": "vue-cli-service lint",
"buildLibrary": "vue-cli-service build --target lib --name simpleMindMap ../simple-mind-map/index.js --dest ../simple-mind-map/dist",
"format": "prettier --write src/* src/*/* src/*/*/* src/*/*/*/*"
"buildLibrary": "vue-cli-service build --target lib --name simpleMindMap ../simple-mind-map/full.js --dest ../simple-mind-map/dist",
"format": "prettier --write src/* src/*/* src/*/*/* src/*/*/*/*",
"buildDoc": "node ./scripts/buildDoc.js",
"autoBuildDoc": "node ./scripts/autoBuildDoc.js"
},
"dependencies": {
"@toast-ui/editor": "^3.1.5",
"core-js": "^3.6.5",
"element-ui": "^2.15.1",
"highlight.js": "^10.7.3",
"v-viewer": "^1.6.4",
"vue": "^2.6.11",
"vue-i18n": "^8.27.2",
@@ -25,10 +28,13 @@
"@vue/cli-plugin-eslint": "^4.5.0",
"@vue/cli-service": "^4.5.0",
"babel-eslint": "^10.1.0",
"chokidar": "^3.5.3",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"less": "^3.12.2",
"less-loader": "^7.1.0",
"markdown-it": "^13.0.1",
"markdown-it-checkbox": "^1.1.0",
"prettier": "^1.19.1",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.44.2"

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="./dist/logo.png">
<title>一个简单的web思维导图实现</title>
</head>
<body>

BIN
web/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -0,0 +1,38 @@
const chokidar = require('chokidar')
const path = require('path')
const fs = require('fs')
const { exec } = require('node:child_process')
const { transformMdToVue } = require('./transformMdToVue')
const reBuildAll = () => {
exec(
'node ./buildDoc.js',
{
cwd: path.resolve(__dirname)
},
(error, msg) => {
console.log(error, msg)
}
)
}
const buildOne = file => {
let content = fs.readFileSync(file, 'utf-8')
let doc = transformMdToVue(content)
let destPath = path.join(path.dirname(file), './index.vue')
fs.writeFileSync(destPath, doc)
}
chokidar
.watch(path.join(__dirname, '../src/pages/Doc/'), {
ignoreInitial: true
})
.on('all', (event, file) => {
if (/\.md$/.test(file)) {
if (event === 'change') {
buildOne(file)
} else {
reBuildAll()
}
}
})

85
web/scripts/buildDoc.js Normal file
View File

@@ -0,0 +1,85 @@
// 编译文档
const path = require('path')
const fs = require('fs')
const { transformMdToVue } = require('./transformMdToVue')
// 文档语言种类
let langList = ['zh', 'en']
// 开始转换
const transform = (dir, routerList) => {
let dirs = fs.readdirSync(dir)
dirs.forEach(item => {
let cur = path.join(dir, item)
if (fs.statSync(cur).isDirectory()) {
compilerDir(cur, item, routerList)
}
})
}
// 编译某种语言下的文档
const compilerDir = (dir, dirName, routerList) => {
let files = fs.readdirSync(dir)
files.forEach(file => {
if (file.endsWith('.md')) {
compilerFile(dir, file, dirName, routerList)
}
})
}
// 编译具体的文档
const compilerFile = (dir, file, dirName, routerList) => {
let filePath = path.join(dir, file)
let destPath = path.join(dir, './index.vue')
let content = fs.readFileSync(filePath, 'utf-8')
let title = /(^|\n\r)\s*#\s+([^\n\r]+)/g.exec(content)
if (title && title[2]) {
addRouter(dirName, routerList, title[2])
}
let doc = transformMdToVue(content)
fs.writeFileSync(destPath, doc)
}
// 收集文档路由
const addRouter = (item, routerList, title) => {
routerList.push({
path: item,
title
})
}
// 创建路由
const createRouter = () => {
let content = `
export default ${JSON.stringify(
routerTypeList.map(item => {
return {
lang: item.lang,
children: item.routerList
}
})
)}
`
fs.writeFileSync(
path.join(__dirname, '../src/pages/Doc/routerList.js'),
content
)
}
// 创建目录列表
const createCatalogList = () => {}
// 开始编译
let routerTypeList = []
langList.forEach(lang => {
let dir = path.join(__dirname, '../src/pages/Doc/', `./${lang}/`)
let routerList = []
transform(dir, routerList)
routerTypeList.push({
lang,
routerList
})
})
// 创建路由
createRouter()
console.log('编译完成')

View File

@@ -0,0 +1,31 @@
const path = require('path')
const fs = require('fs')
const hljs = require('highlight.js')
const md = require('markdown-it')({
highlight: function(str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return (
'<pre class="hljs"><code>' +
hljs.highlight(str, {
language: lang,
ignoreIllegals: true
}).value +
'</code></pre>'
)
} catch (__) {}
}
return (
'<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>'
)
}
}).use(require('markdown-it-checkbox'))
const templatePath = path.join(__dirname, '../src/pages/Doc/Template.vue')
exports.transformMdToVue = (content) => {
let result = md.render(content)
let template = fs.readFileSync(templatePath, 'utf-8')
return template.replace('$$$$', result)
}

View File

@@ -54,6 +54,54 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe6c6;</span>
<div class="name">case</div>
<div class="code-name">&amp;#xe6c6;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xeb99;</span>
<div class="name">形状-文字</div>
<div class="code-name">&amp;#xeb99;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xec83;</span>
<div class="name">字体加粗</div>
<div class="code-name">&amp;#xec83;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xec85;</span>
<div class="name">字体下划线</div>
<div class="code-name">&amp;#xec85;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xec86;</span>
<div class="name">字体斜体</div>
<div class="code-name">&amp;#xec86;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe612;</span>
<div class="name">删除线</div>
<div class="code-name">&amp;#xe612;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe854;</span>
<div class="name">字体颜色</div>
<div class="code-name">&amp;#xe854;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe64f;</span>
<div class="name">github</div>
<div class="code-name">&amp;#xe64f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6c5;</span>
<div class="name">选择</div>
@@ -342,9 +390,9 @@
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1668512547595') format('woff2'),
url('iconfont.woff?t=1668512547595') format('woff'),
url('iconfont.ttf?t=1668512547595') format('truetype');
src: url('iconfont.woff2?t=1677058133223') format('woff2'),
url('iconfont.woff?t=1677058133223') format('woff'),
url('iconfont.ttf?t=1677058133223') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -370,6 +418,78 @@
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont iconcase"></span>
<div class="name">
case
</div>
<div class="code-name">.iconcase
</div>
</li>
<li class="dib">
<span class="icon iconfont iconxingzhuang-wenzi"></span>
<div class="name">
形状-文字
</div>
<div class="code-name">.iconxingzhuang-wenzi
</div>
</li>
<li class="dib">
<span class="icon iconfont iconzitijiacu"></span>
<div class="name">
字体加粗
</div>
<div class="code-name">.iconzitijiacu
</div>
</li>
<li class="dib">
<span class="icon iconfont iconzitixiahuaxian"></span>
<div class="name">
字体下划线
</div>
<div class="code-name">.iconzitixiahuaxian
</div>
</li>
<li class="dib">
<span class="icon iconfont iconzitixieti"></span>
<div class="name">
字体斜体
</div>
<div class="code-name">.iconzitixieti
</div>
</li>
<li class="dib">
<span class="icon iconfont iconshanchuxian"></span>
<div class="name">
删除线
</div>
<div class="code-name">.iconshanchuxian
</div>
</li>
<li class="dib">
<span class="icon iconfont iconzitiyanse"></span>
<div class="name">
字体颜色
</div>
<div class="code-name">.iconzitiyanse
</div>
</li>
<li class="dib">
<span class="icon iconfont icongithub"></span>
<div class="name">
github
</div>
<div class="code-name">.icongithub
</div>
</li>
<li class="dib">
<span class="icon iconfont iconchoose1"></span>
<div class="name">
@@ -802,6 +922,70 @@
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconcase"></use>
</svg>
<div class="name">case</div>
<div class="code-name">#iconcase</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconxingzhuang-wenzi"></use>
</svg>
<div class="name">形状-文字</div>
<div class="code-name">#iconxingzhuang-wenzi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconzitijiacu"></use>
</svg>
<div class="name">字体加粗</div>
<div class="code-name">#iconzitijiacu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconzitixiahuaxian"></use>
</svg>
<div class="name">字体下划线</div>
<div class="code-name">#iconzitixiahuaxian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconzitixieti"></use>
</svg>
<div class="name">字体斜体</div>
<div class="code-name">#iconzitixieti</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconshanchuxian"></use>
</svg>
<div class="name">删除线</div>
<div class="code-name">#iconshanchuxian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconzitiyanse"></use>
</svg>
<div class="name">字体颜色</div>
<div class="code-name">#iconzitiyanse</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icongithub"></use>
</svg>
<div class="name">github</div>
<div class="code-name">#icongithub</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconchoose1"></use>

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2479351 */
src: url('iconfont.woff2?t=1668512547595') format('woff2'),
url('iconfont.woff?t=1668512547595') format('woff'),
url('iconfont.ttf?t=1668512547595') format('truetype');
src: url('iconfont.woff2?t=1677058133223') format('woff2'),
url('iconfont.woff?t=1677058133223') format('woff'),
url('iconfont.ttf?t=1677058133223') format('truetype');
}
.iconfont {
@@ -13,6 +13,38 @@
-moz-osx-font-smoothing: grayscale;
}
.iconcase:before {
content: "\e6c6";
}
.iconxingzhuang-wenzi:before {
content: "\eb99";
}
.iconzitijiacu:before {
content: "\ec83";
}
.iconzitixiahuaxian:before {
content: "\ec85";
}
.iconzitixieti:before {
content: "\ec86";
}
.iconshanchuxian:before {
content: "\e612";
}
.iconzitiyanse:before {
content: "\e854";
}
.icongithub:before {
content: "\e64f";
}
.iconchoose1:before {
content: "\e6c5";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,62 @@
"css_prefix_text": "icon",
"description": "思维导图",
"glyphs": [
{
"icon_id": "586787",
"name": "case",
"font_class": "case",
"unicode": "e6c6",
"unicode_decimal": 59078
},
{
"icon_id": "4354254",
"name": "形状-文字",
"font_class": "xingzhuang-wenzi",
"unicode": "eb99",
"unicode_decimal": 60313
},
{
"icon_id": "6337466",
"name": "字体加粗",
"font_class": "zitijiacu",
"unicode": "ec83",
"unicode_decimal": 60547
},
{
"icon_id": "6337470",
"name": "字体下划线",
"font_class": "zitixiahuaxian",
"unicode": "ec85",
"unicode_decimal": 60549
},
{
"icon_id": "6337471",
"name": "字体斜体",
"font_class": "zitixieti",
"unicode": "ec86",
"unicode_decimal": 60550
},
{
"icon_id": "11975179",
"name": "删除线",
"font_class": "shanchuxian",
"unicode": "e612",
"unicode_decimal": 58898
},
{
"icon_id": "34198316",
"name": "字体颜色",
"font_class": "zitiyanse",
"unicode": "e854",
"unicode_decimal": 59476
},
{
"icon_id": "8760187",
"name": "github",
"font_class": "github",
"unicode": "e64f",
"unicode_decimal": 58959
},
{
"icon_id": "1009019",
"name": "选择",

Some files were not shown because too many files have changed in this diff Show More