Compare commits

..

159 Commits

Author SHA1 Message Date
wanglin25
04001fd181 '打包' 2022-12-09 16:56:28 +08:00
wanglin25
7fc42879c7 '新增键盘导航,即通过方向键来切换激活的节点' 2022-12-09 16:53:42 +08:00
wanglin25
c4c9f9421c 支持在大纲直接编辑节点文本内容 2022-12-08 09:19:47 +08:00
wanglin25
4b246f7106 打包 2022-11-16 08:59:13 +08:00
wanglin25
621e62af4c 优化侧边栏显示和隐藏方式 2022-11-15 20:00:03 +08:00
wanglin25
3df6538c86 优化备注显示 2022-11-14 19:59:21 +08:00
wanglin25
fea7d32c97 新增禅模式 2022-11-14 19:25:25 +08:00
wanglin2
c31a67e7bd 打包 2022-11-05 14:46:52 +08:00
wanglin2
f451d37254 demo网站增加英文翻译 2022-11-05 14:42:15 +08:00
wanglin25
377e507fd9 多语言开发中 2022-10-28 16:26:55 +08:00
wanglin25
dce55f3628 '增加从excel导入的功能' 2022-10-24 15:16:27 +08:00
wanglin25
600cadc861 打包 2022-10-24 10:31:35 +08:00
wanglin25
4d69f239f0 修复在小屏幕下侧边栏和工具栏重叠的问题 2022-10-24 10:28:50 +08:00
wanglin25
897c12b8e6 增加eslint校验、prettier格式化 2022-10-19 16:33:09 +08:00
wanglin25
9103574a88 优化小地图、优化拖拽性能 2022-10-19 09:54:38 +08:00
wanglin25
7c94ec19ac '支持双击节点内图片进行大图预览' 2022-10-18 19:13:15 +08:00
wanglin25
3e3a60cb03 优化本地文件编辑 2022-10-18 09:40:34 +08:00
wanglin25
ec69d81f6a 打包 2022-10-17 09:25:21 +08:00
街角小林
b2df7330fc Merge pull request #43 from huangyuanyin/main
优化 地图组件卸载的时候把相关事件移除
2022-10-17 09:20:38 +08:00
liuzhanghao
5265ceb803 优化 地图组件卸载的时候把相关事件移除 2022-10-14 18:11:57 +08:00
wanglin25
35d6297b72 Merge branch 'dev6' into main 2022-10-11 18:54:24 +08:00
wanglin25
5afe0a8c41 打包 2022-10-11 18:53:30 +08:00
wanglin25
81f86ccb30 优化:插入子节点时自动展开 2022-10-11 18:48:52 +08:00
wanglin25
87383498c8 修复小地图关闭时报错的问题 2022-10-11 18:36:16 +08:00
wanglin25
c68608c72c 合并 2022-10-11 11:10:00 +08:00
wanglin25
9d325d99a2 更新版本 2022-10-11 11:08:58 +08:00
wanglin2
bc59fa6dc7 修改文档 2022-10-10 22:40:44 +08:00
wanglin2
2daa59679a 修改文档 2022-10-10 22:40:12 +08:00
wanglin25
da456eeb8f 打包 2022-10-10 16:27:49 +08:00
wanglin25
5cd36b57d5 修复子节点收起状态复制时丢失的问题 2022-10-10 16:11:00 +08:00
wanglin25
e2b239fcbb 更新文档 2022-10-10 15:02:22 +08:00
wanglin25
5449e79d49 打包 2022-10-10 14:39:09 +08:00
wanglin25
c2045ddedc 支持小地图 2022-10-10 14:31:43 +08:00
wanglin2
5dfa215538 修改文档 2022-09-30 22:16:27 +08:00
wanglin25
d90013da71 打包 2022-09-30 14:48:08 +08:00
wanglin25
b3b74323f7 逻辑结构图、思维导图新增直线连接风格、直连风格 2022-09-30 14:44:40 +08:00
wanglin2
f9000ea478 逻辑结构图和思维导图支持直线连接线开发中 2022-09-25 22:07:21 +08:00
wanglin2
830e7e2482 打包 2022-09-24 20:42:53 +08:00
wanglin2
13ed7f28df 优化:手动创建节点时立即聚焦 2022-09-24 20:38:47 +08:00
wanglin2
17e79a0b23 修复连线样式深度更新问题 2022-09-24 20:13:53 +08:00
wanglin2
eee310ba49 支持新建、打开、 2022-09-24 17:08:11 +08:00
wanglin2
36c8927dd0 修改文档 2022-09-24 10:51:09 +08:00
wanglin2
05333daa63 支持展开到指定层级 2022-09-24 10:49:40 +08:00
wanglin25
5aed681198 支持展开到指定层级 2022-09-23 17:41:38 +08:00
wanglin25
8a438f2906 修复xmind8版本文件导入失败的问题 2022-09-23 16:16:08 +08:00
wanglin2
260de4987d 修改README 2022-09-21 21:02:17 +08:00
wanglin25
31cc658c06 打包 2022-09-21 20:11:12 +08:00
wanglin25
be6b41d74d 支持导入.xmind文件 2022-09-21 20:07:46 +08:00
wanglin25
4beeead53d 修复根节点添加多个节点爆栈的问题 2022-09-21 09:38:41 +08:00
wanglin25
af2df6acd3 导出svg增加title标签 2022-09-19 09:36:19 +08:00
wanglin2
c08d66acf2 打包 2022-09-17 22:00:15 +08:00
wanglin2
5a9cb9ac07 节点支持设置自定义线条样式 2022-09-17 16:18:38 +08:00
wanglin2
1662cd1be7 优化节点自定义线条样式 2022-09-17 15:09:34 +08:00
wanglin2
9dd5b3d47e 节点支持自定义线条样式 2022-09-17 15:00:12 +08:00
wanglin2
ece116317b 修改文档 2022-09-13 22:30:45 +08:00
wanglin25
d24d5c8281 修复节点展开收起的bug&打包 2022-09-13 10:34:20 +08:00
wanglin25
97c01cda3a 打包 2022-09-13 09:43:18 +08:00
wanglin25
6d520ece7e 修改文档 2022-09-13 09:33:05 +08:00
wanglin2
745041deef 节点支持多种形状开发完成 2022-09-12 23:07:01 +08:00
wanglin25
ae6c10cdf9 打包 2022-09-01 11:11:33 +08:00
街角小林
f5338d62fc Merge pull request #26 from huangyuanyin/main
fix 二级节点后无子节点,展开所有/收起所有操作后的报错信息
2022-09-01 11:07:38 +08:00
liuzhanghao
19fc12ff20 fix 2022-09-01 10:28:26 +08:00
liuzhanghao
d7640bb026 fix 二级节点后无子节点,展开所有/收起所有操作后的报错信息 2022-09-01 10:22:01 +08:00
wanglin
193b293cfe 更新readme 2022-08-22 21:03:08 +08:00
wanglin25
615ff3ea25 fix:修改右键菜单快捷键提示 2022-08-17 09:25:16 +08:00
wanglin25
8b9cfd2972 fix:修复右键菜单快捷键提示错误 2022-08-16 18:48:11 +08:00
wanglin25
03f8cb9290 fix:修复编辑节点文本时快捷键冲突的问题 2022-08-16 16:47:43 +08:00
wanglin
1ea0c7e316 修复输入字符串/和快捷键/冲突问题 2022-08-14 09:43:00 +08:00
wanglin
a39c8c30e6 打包 2022-08-08 21:24:02 +08:00
街角小林
26ce08c27d Update README.md 2022-08-08 20:09:31 +08:00
wanglin25
4bfc98a96f 支持导出为pdf 2022-08-08 20:06:59 +08:00
wanglin
7560411922 打包&发布新版本 2022-08-06 09:21:18 +08:00
wanglin25
4288e44f3a 支持Ctrl+左键多选 2022-08-05 17:21:21 +08:00
wanglin25
b82c5247fa Merge branch 'dev' of https://github.com/wanglin2/mind-map into dev 2022-08-05 09:30:19 +08:00
wanglin25
c7d2082944 新增库打包&修改文档 2022-08-05 09:29:59 +08:00
wanglin
29458ade9c 增加图标 2022-08-04 20:24:57 +08:00
wanglin25
49d366628e 修改文档 2022-08-04 15:06:07 +08:00
wanglin25
b093153262 新增快捷键 2022-08-04 14:48:52 +08:00
wanglin25
02f276bc2a 支持自由拖拽、修复一些bug 2022-08-04 11:39:17 +08:00
wanglin25
69cb961cc1 修复概要的一些bug 2022-08-02 08:59:05 +08:00
wanglin25
7096391f3b 修复拖拽节点时为隐藏概要内容的问题 2022-08-01 09:59:43 +08:00
wanglin25
280ffcf01d 修复初始渲染时激活节点没有触发页面效果的问题 2022-08-01 09:51:53 +08:00
wanglin25
db3c2b71f5 修复概要展开收起的定位错误问题 2022-08-01 09:36:25 +08:00
wanglin
f11f364d00 新增方法 2022-07-31 22:29:40 +08:00
wanglin
0e8c50d430 修复概要定位问题 2022-07-31 20:46:40 +08:00
wanglin
dd8d250857 修改及新增主题 2022-07-31 14:54:32 +08:00
wanglin
693ead6b49 概要开发中 2022-07-31 10:28:13 +08:00
wanglin
dc3c91270c 更新版本 2022-07-30 08:21:43 +08:00
街角小林
71ac739964 Merge pull request #16 from harris2012/patch-2
fix typo
2022-07-29 21:59:53 +08:00
街角小林
eae5dc5854 Merge pull request #15 from harris2012/patch-1
Update Node.js
2022-07-29 21:57:05 +08:00
Harris Zhang
c45ceac7dc fix typo 2022-07-29 17:55:20 +08:00
Harris Zhang
3d8702be8a Update Node.js 2022-07-29 11:29:25 +08:00
街角小林
084dd9fd84 Merge pull request #13 from harris2012/patch-1
Update index.js
2022-07-26 21:15:41 +08:00
Harris Zhang
0c6c68820f Update index.js 2022-07-26 19:12:47 +08:00
wanglin25
f5ff479f47 fix:1.节点图标不能删除的问题;2.工具按钮置灰仍然可以点击的问题 2022-06-28 19:55:31 +08:00
wanglin25
e9722efe93 增加只读模式 2022-06-08 14:30:45 +08:00
wanglin
db1f9c04c1 修改README 2022-05-10 23:09:54 +08:00
wanglin
b3e6412dbc 发布0.1.6版本 2022-05-10 23:08:02 +08:00
wanglin25
f58828469c 打包 2022-05-09 14:31:22 +08:00
wanglin25
cf7a92d9c7 节点备注支持markdown及富文本、修复不能选中文字的问题 2022-05-09 14:24:11 +08:00
wanglin25
ec0d021e92 修复节点标注在节点激活后无法隐藏问题 2022-05-09 11:31:05 +08:00
wanglin25
25a2d919fb 修复超链接、备注、标签等文字编辑时返回键和回车键与思维导图快捷键冲突的问题 2022-05-09 11:03:56 +08:00
wanglin
c3f4e2b797 增加版本号 2021-11-25 22:25:26 +08:00
wanglin25
a0c07522f9 新增支持节点拖拽 2021-11-25 16:06:18 +08:00
wanglin25
ca0cbdf009 '状态数据支持保存激活状态、视图状态(拖动位置、缩放值)' 2021-11-22 19:59:21 +08:00
wanglin
6263a49903 修复存在激活节点时设置主题存在的问题 2021-08-05 22:29:02 +08:00
wanglin
dce133f8f0 升级版本 2021-08-05 08:09:48 +08:00
wanglin
7f4f4e2fe0 增加快捷键功能 2021-08-05 00:06:27 +08:00
wanglin
4e4ade5c31 修复回退问题 2021-08-04 23:45:05 +08:00
wanglin
5423b42e9d 新增导出为json;优化一些细节 2021-08-04 07:26:01 +08:00
wanglin
9555375907 修改版本号及package.json 2021-08-01 11:58:08 +08:00
wanglin
cc19ce168b 增加本地存储功能 2021-08-01 10:49:57 +08:00
wanglin25
e324813eae 修复删除节点和节点编辑时的删除冲突问题 2021-07-27 14:17:58 +08:00
wanglin25
08a7ba380a '修改文章与打包' 2021-07-22 19:06:51 +08:00
wanglin25
dd2bfbcf93 '打包' 2021-07-22 14:13:49 +08:00
wanglin25
4ae5c914ca '打包' 2021-07-22 09:36:50 +08:00
wanglin
6f49577794 优化 2021-07-22 08:03:00 +08:00
wanglin
43a3dd97c7 修改文章 2021-07-21 22:50:46 +08:00
wanglin25
7226e40a6c '完成文章' 2021-07-21 20:24:37 +08:00
wanglin25
9f090a4474 文章写作中 2021-07-20 20:18:03 +08:00
wanglin
bf5298ab2f 文章 2021-07-20 07:48:22 +08:00
wanglin
ffe4fedbc3 修改文章 2021-07-19 07:39:34 +08:00
wanglin
07ad92e93a 添加图片 2021-07-18 22:47:03 +08:00
wanglin
5a004e40de 优化与文档编写 2021-07-18 22:27:34 +08:00
wanglin
8664bcc00b 完善开发文档 2021-07-16 19:33:01 +08:00
wanglin
d00f56c7f1 优化代码 2021-07-16 13:58:48 +08:00
wanglin
4ddce22076 基本完成 2021-07-16 13:42:37 +08:00
wanglin25
fdf4d51a1e '右键功能开发中' 2021-07-15 17:46:36 +08:00
wanglin25
a9ebe50fff Merge branch 'main' of https://github.com/wanglin2/mind-map into main 2021-07-15 09:46:53 +08:00
wanglin25
f8638088f3 '优化' 2021-07-15 09:46:27 +08:00
wanglin
081797e83b 右键菜单开发中 2021-07-14 23:57:36 +08:00
wanglin25
8adf5a7fb8 '修改样式' 2021-07-14 09:54:45 +08:00
wanglin25
194a920efa '打包' 2021-07-14 09:34:13 +08:00
wanglin
6b3741741c 修改配置 2021-07-14 08:13:23 +08:00
wanglin
80a7e334e4 修改配置 2021-07-14 08:09:23 +08:00
wanglin
5d45d7accf 打包 2021-07-14 07:38:30 +08:00
wanglin
b893776f82 修改打包配置i 2021-07-14 07:34:06 +08:00
wanglin
115a0ac480 修改 2021-07-14 07:28:31 +08:00
wanglin
cb43292aa5 上传打包文件 2021-07-13 23:29:22 +08:00
wanglin
7b773bd9ad 修改README 2021-07-13 23:20:41 +08:00
wanglin25
38a1e25cc2 '基本完成' 2021-07-13 16:39:50 +08:00
wanglin
fd0c471f00 优化 2021-07-13 08:04:47 +08:00
wanglin
97f84933f8 新增主题 2021-07-12 22:49:22 +08:00
wanglin25
321fc69798 '完成部分结构' 2021-07-12 20:12:21 +08:00
wanglin25
af2910bec8 '优化及增加主题' 2021-07-12 14:55:50 +08:00
wanglin
9deb20f1ca 回退功能开发中 2021-07-12 07:56:38 +08:00
wanglin
0f4d614c4e 增加快捷键和全屏功能 2021-07-11 22:21:40 +08:00
wanglin
eeba3153ef 完成多选操作 2021-07-11 13:33:06 +08:00
wanglin
554dab56d3 完成基本逻辑 2021-07-10 22:06:45 +08:00
wanglin
da28f89c52 日常提交 2021-07-04 22:53:36 +08:00
wanglin
8b0c62430e 完成导出功能 2021-07-04 16:56:37 +08:00
wanglin
adcf77f60d 日常提交 2021-06-28 07:46:12 +08:00
wanglin
54ae4b3e26 提交 2021-06-26 00:03:39 +08:00
wanglin
b0929da054 完成节点内容设置及主题 2021-06-26 00:01:03 +08:00
wanglin2
1528c3d64b Delete .DS_Store 2021-06-20 23:05:11 +08:00
wanglin2
ecc3ba8784 Delete .DS_Store 2021-06-20 23:04:56 +08:00
wanglin
947c4b2b44 完成节点内容布局 2021-06-20 23:03:23 +08:00
wanglin25
08c7768b18 修改忽略文件 2021-06-19 14:24:52 +08:00
wanglin25
efebe3f094 重构 2021-06-19 14:04:05 +08:00
249 changed files with 3719 additions and 45000 deletions

3
.gitignore vendored
View File

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

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"><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>
<!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.81d632f4.js" rel="prefetch"><link href="dist/js/chunk-2d216b67.228f2009.js" rel="prefetch"><link href="dist/js/chunk-35b0a040.cb76da7d.js" rel="prefetch"><link href="dist/css/app.2784db23.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.faba1249.css" rel="preload" as="style"><link href="dist/js/app.77dbe506.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.013a6cea.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.faba1249.css" rel="stylesheet"><link href="dist/css/app.2784db23.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.013a6cea.js"></script><script src="dist/js/app.77dbe506.js"></script></body></html>

View File

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

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

View File

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

View File

@@ -1,20 +0,0 @@
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,10 +7,15 @@ 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 defaultTheme from './src/themes/default'
import KeyboardNavigation from './src/KeyboardNavigation'
// 默认选项配置
const defaultOpt = {
@@ -39,34 +44,28 @@ 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))
@@ -115,14 +114,34 @@ 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(() => {
@@ -130,7 +149,11 @@ class MindMap {
}, 0)
}
// 配置参数处理
/**
* @Author: 王林
* @Date: 2021-07-01 22:15:22
* @Desc: 配置参数处理
*/
handleOpt(opt) {
// 检查布局配置
if (!layoutValueList.includes(opt.layout)) {
@@ -141,26 +164,39 @@ class MindMap {
return opt
}
// 渲染,部分渲染
render(callback) {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 18:47:29
* @Desc: 渲染,部分渲染
*/
render() {
this.batchExecution.push('render', () => {
this.initTheme()
this.renderer.reRender = false
this.renderer.render(callback)
this.renderer.render()
})
}
// 重新渲染
reRender(callback) {
/**
* @Author: 王林
* @Date: 2021-07-08 22:05:11
* @Desc: 重新渲染
*/
reRender() {
this.batchExecution.push('render', () => {
this.draw.clear()
this.initTheme()
this.renderer.reRender = true
this.renderer.render(callback)
this.renderer.render()
})
}
// 容器尺寸变化,调整尺寸
/**
* @Author: 王林
* @Date: 2021-07-11 21:16:52
* @Desc: 容器尺寸变化,调整尺寸
*/
resize() {
this.elRect = this.el.getBoundingClientRect()
this.width = this.elRect.width
@@ -168,22 +204,38 @@ 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)
@@ -191,50 +243,70 @@ 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]
}
// 获取配置
getConfig(prop) {
return prop === undefined ? this.opt : this.opt[prop]
}
// 更新配置
updateConfig(opt = {}) {
this.opt = this.handleOpt(merge.all([defaultOpt, this.opt, opt]))
}
// 获取当前布局结构
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-13 16:17:06
* @Desc: 获取当前布局结构
*/
getLayout() {
return this.opt.layout
}
// 设置布局结构
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-13 16:17:33
* @Desc: 设置布局结构
*/
setLayout(layout) {
// 检查布局配置
if (!layoutValueList.includes(layout)) {
@@ -245,12 +317,20 @@ 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()
@@ -258,7 +338,12 @@ 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)
@@ -279,7 +364,12 @@ class MindMap {
}
}
// 获取思维导图数据,节点树、主题、布局等
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-24 14:42:07
* @Desc: 获取思维导图数据,节点树、主题、布局等
*/
getData(withConfig) {
let nodeData = this.command.getCopyData()
let data = {}
@@ -299,13 +389,21 @@ 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,
@@ -313,7 +411,12 @@ class MindMap {
}
}
// 设置只读模式、编辑模式
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-06-08 14:12:38
* @Desc: 设置只读模式、编辑模式
*/
setMode(mode) {
if (!['readonly', 'edit'].includes(mode)) {
return
@@ -325,105 +428,8 @@ 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.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)
}
MindMap.xmind = xmind
export default MindMap

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
"version": "0.4.0",
"version": "0.2.17",
"description": "一个简单的web在线思维导图",
"authors": [
{
@@ -22,16 +22,14 @@
"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

@@ -1,46 +0,0 @@
// 遍历所有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,4 +1,8 @@
// 在下一个事件循环里执行任务
/**
* @Author: 王林
* @Date: 2021-06-27 13:16:23
* @Desc: 在下一个事件循环里执行任务
*/
const nextTick = function (fn, ctx) {
let pending = false
let timerFunc = null
@@ -29,16 +33,28 @@ 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
@@ -51,7 +67,11 @@ 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,8 +1,16 @@
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
@@ -13,14 +21,22 @@ 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')
@@ -30,7 +46,11 @@ class Command {
})
}
// 执行命令
/**
* @Author: 王林
* @Date: 2021-05-04 13:12:30
* @Desc: 执行命令
*/
exec(name, ...args) {
if (this.commands[name]) {
this.commands[name].forEach(fn => {
@@ -43,7 +63,11 @@ class Command {
}
}
// 添加命令
/**
* @Author: 王林
* @Date: 2021-05-04 13:13:01
* @Desc: 添加命令
*/
add(name, fn) {
if (this.commands[name]) {
this.commands[name].push(fn)
@@ -52,7 +76,11 @@ class Command {
}
}
// 移除命令
/**
* @Author: 王林
* @Date: 2021-07-15 23:02:41
* @Desc: 移除命令
*/
remove(name, fn) {
if (!this.commands[name]) {
return
@@ -70,11 +98,12 @@ 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
@@ -86,11 +115,12 @@ 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(
@@ -102,11 +132,13 @@ 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
@@ -115,7 +147,11 @@ class Command {
}
}
// 获取渲染树数据副本
/**
* @Author: 王林
* @Date: 2021-05-04 15:02:58
* @Desc: 获取渲染树数据副本
*/
getCopyData() {
return copyRenderTree({}, this.mindMap.renderer.renderTree)
}

View File

@@ -1,11 +1,18 @@
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
@@ -13,8 +20,12 @@ class Drag extends Base {
this.bindEvent()
}
// 复位
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-23 19:33:56
* @Desc: 复位
*/
reset() {
// 当前拖拽节点
this.node = null
@@ -47,8 +58,11 @@ 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) => {
@@ -62,11 +76,12 @@ class Drag extends Base {
// 计算鼠标按下的位置距离节点左上角的距离
this.drawTransform = this.mindMap.draw.transform()
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
this.offsetX = x - (node.left * scaleX + translateX)
this.offsetY = y - (node.top * scaleY + translateY)
this.offsetX = e.clientX - (node.left * scaleX + translateX)
this.offsetY = e.clientY - (node.top * scaleY + translateY)
//
this.node = node
this.isMousedown = true
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
this.mouseDownX = x
this.mouseDownY = y
})
@@ -96,8 +111,12 @@ 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
@@ -119,7 +138,7 @@ class Drag extends Base {
// 存在下一个相邻节点,作为其前一个兄弟节点
this.mindMap.renderer.setNodeActive(this.nextNode, false)
this.mindMap.execCommand('INSERT_BEFORE', this.node, this.nextNode)
} else if (_nodeIsDrag && this.mindMap.opt.enableFreeDrag) {
} else if (_nodeIsDrag) {
// 自定义位置
let { x, y } = this.mindMap.toPos(
e.clientX - this.offsetX,
@@ -138,8 +157,12 @@ class Drag extends Base {
this.reset()
}
// 创建克隆节点
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-23 19:34:53
* @Desc: 创建克隆节点
*/
createCloneNode() {
if (!this.clone) {
// 节点
@@ -160,8 +183,12 @@ class Drag extends Base {
}
}
// 移除克隆节点
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-23 19:35:16
* @Desc: 移除克隆节点
*/
removeCloneNode() {
if (!this.clone) {
return
@@ -171,8 +198,12 @@ 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
@@ -198,8 +229,11 @@ class Drag extends Base {
this.checkOverlapNode()
}
// 检测重叠节点
/**
* @Author: 王林
* @Date: 2021-07-11 10:20:43
* @Desc: 检测重叠节点
*/
checkOverlapNode() {
if (!this.drawTransform) {
return
@@ -241,8 +275,7 @@ class Drag extends Base {
}
}
// 检测兄弟节点位置
if (!this.prevNode && !this.nextNode && !node.isRoot) {
// && this.node.isBrother(node)
if (!this.prevNode && !this.nextNode && this.node.isBrother(node)) {
if (left <= checkRight && right >= this.cloneNodeLeft) {
if (this.cloneNodeTop > bottom && this.cloneNodeTop <= bottom + 10) {
this.prevNode = node
@@ -260,6 +293,4 @@ class Drag extends Base {
}
}
Drag.instanceName = 'drag'
export default Drag

View File

@@ -1,8 +1,18 @@
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
@@ -24,7 +34,12 @@ 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)
@@ -36,7 +51,12 @@ 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)
@@ -53,7 +73,12 @@ 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)
@@ -64,17 +89,30 @@ 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)
}
// svg画布的鼠标按下事件
/**
* @Author: 王林
* @Date: 2021-07-16 13:37:30
* @Desc: svg画布的鼠标按下事件
*/
onSvgMousedown(e) {
this.emit('svg_mousedown', e)
}
// 鼠标按下事件
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:17:35
* @Desc: 鼠标按下事件
*/
onMousedown(e) {
// e.preventDefault()
// 鼠标左键
@@ -86,7 +124,12 @@ 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
@@ -99,13 +142,23 @@ 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()
@@ -118,13 +171,22 @@ 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,18 +1,29 @@
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)
@@ -25,9 +36,13 @@ class Export {
}
}
// 获取svg数据
async getSvgData(domToImage) {
let { svg, svgHTML } = this.mindMap.getSvgData()
/**
* @Author: 王林
* @Date: 2021-07-04 14:57:40
* @Desc: 获取svg数据
*/
async getSvgData() {
let { svg, svgHTML } = this.mindMap.miniMap.getMiniMap()
// 把图片的url转换成data:url类型否则导出会丢失图片
let imageList = svg.find('image')
let task = imageList.map(async item => {
@@ -36,21 +51,17 @@ 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,
nodeWithDomToImg
str: svgHTML
}
}
// svg转png
/**
* @Author: 王林
* @Date: 2021-07-04 15:25:19
* @Desc: svg转png
*/
svgToPng(svgSrc) {
return new Promise((resolve, reject) => {
const img = new Image()
@@ -88,15 +99,17 @@ class Export {
})
}
// 在canvas上绘制思维导图背景
/**
* @Author: 王林
* @Date: 2021-07-04 15:32:07
* @Desc: 在canvas上绘制思维导图背景
*/
drawBackgroundToCanvas(ctx, width, height) {
return new Promise((resolve, reject) => {
let {
backgroundColor = '#fff',
backgroundImage,
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center center',
backgroundSize = 'cover',
backgroundRepeat = 'repeat'
} = this.mindMap.themeConfig
// 背景颜色
ctx.save()
@@ -107,31 +120,34 @@ class Export {
// 背景图片
if (backgroundImage && backgroundImage !== 'none') {
ctx.save()
drawBackgroundImageToCanvas(ctx, width, height, backgroundImage, {
backgroundRepeat,
backgroundPosition,
backgroundSize
}, (err) => {
if (err) {
reject(err)
} else {
resolve()
}
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()
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(true)
let { str } = await this.getSvgData()
// 转换成blob数据
let blob = new Blob([str], {
type: 'image/svg+xml'
@@ -144,7 +160,12 @@ class Export {
return imgDataUrl
}
// 导出为pdf
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-08 19:23:08
* @Desc: 导出为pdf
*/
async pdf(name) {
let img = await this.png()
let pdf = new JsPDF('', 'pt', 'a4')
@@ -176,7 +197,11 @@ class Export {
image.src = img
}
// 在svg上绘制思维导图背景
/**
* @Author: 王林
* @Date: 2021-07-04 15:32:07
* @Desc: 在svg上绘制思维导图背景
*/
drawBackgroundToSvg(svg) {
return new Promise(async resolve => {
let {
@@ -198,22 +223,13 @@ class Export {
})
}
// 导出为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>`))
}
}
}
/**
* @Author: 王林
* @Date: 2021-07-04 14:54:07
* @Desc: 导出为svg
*/
async svg(name) {
let { node } = await this.getSvgData()
node.first().before(SVG(`<title>${name}</title>`))
await this.drawBackgroundToSvg(node)
let str = node.svg()
@@ -224,7 +240,11 @@ class Export {
return URL.createObjectURL(blob)
}
// 导出为json
/**
* @Author: 王林
* @Date: 2021-08-03 22:19:17
* @Desc: 导出为json
*/
json(name, withConfig = true) {
let data = this.mindMap.getData(withConfig)
let str = JSON.stringify(data)
@@ -232,12 +252,14 @@ class Export {
return URL.createObjectURL(blob)
}
// 专有文件其实就是json文件
/**
* @Author: 王林
* @Date: 2021-08-03 22:24:24
* @Desc: 专有文件其实就是json文件
*/
smm(name, withConfig) {
return this.json(name, withConfig)
}
}
Export.instanceName = 'doExport'
export default Export

View File

@@ -1,7 +1,15 @@
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
@@ -13,29 +21,51 @@ 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) {
@@ -53,7 +83,11 @@ 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)
@@ -73,7 +107,11 @@ export default class KeyCommand {
return true
}
// 获取事件对象里的键值数组
/**
* @Author: 王林
* @Date: 2021-04-24 19:15:19
* @Desc: 获取事件对象里的键值数组
*/
getOriginEventCodeArr(e) {
let arr = []
if (e.ctrlKey || e.metaKey) {
@@ -91,7 +129,11 @@ 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 = []
@@ -101,8 +143,10 @@ export default class KeyCommand {
return arr
}
// 添加快捷键命令
/**
* @Author: 王林
* @Date: 2021-04-24 15:23:00
* @Desc: 添加快捷键命令
* Enter
* Tab | Insert
* Shift + a
@@ -117,7 +161,12 @@ 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]) {
@@ -136,7 +185,11 @@ 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,9 +1,19 @@
import { isKey } from './utils/keyMap'
import { bfsWalk } from './utils'
// 键盘导航类
class KeyboardNavigation {
// 构造函数
/**
* 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: 构造函数
*/
constructor(opt) {
this.opt = opt
this.mindMap = opt.mindMap
@@ -11,31 +21,37 @@ 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)) {
if (this.mindMap.renderer.activeNodeList.length > 0) {
if (this.mindMap.renderer.activeNodeList.length > 0) {
;['Left', 'Up', 'Right', 'Down'].forEach(dir => {
if (isKey(e, dir)) {
this.focus(dir)
} else {
let root = this.mindMap.renderer.root
this.mindMap.renderer.moveNodeToCenter(root)
root.active()
}
}
})
})
} else {
let root = this.mindMap.renderer.root
this.mindMap.renderer.moveNodeToCenter(root)
root.active()
}
}
// 聚焦到下一个节点
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 14:12:39
* @Desc: 聚焦到下一个节点
*/
focus(dir) {
// 当前聚焦的节点
let currentActiveNode = this.mindMap.renderer.activeNodeList[0]
// 当前聚焦节点的位置信息
let currentActiveNodeRect = this.getNodeRect(currentActiveNode)
// 寻找的下一个聚焦节点
let targetNode = null
let targetDis = Infinity
// 保存并维护距离最近的节点
let checkNodeDis = (rect, node) => {
let dis = this.getDistance(currentActiveNodeRect, rect)
if (dis < targetDis) {
@@ -43,158 +59,39 @@ class KeyboardNavigation {
targetDis = dis
}
}
// 第一优先级:阴影算法
this.getFocusNodeByShadowAlgorithm({
currentActiveNode,
currentActiveNodeRect,
dir,
checkNodeDis
bfsWalk(this.mindMap.renderer.root, node => {
let rect = this.getNodeRect(node)
let { left, top, right, bottom } = rect
if (dir === 'Right') {
if (left >= currentActiveNodeRect.right) {
checkNodeDis(rect, node)
}
} else if (dir === 'Left') {
if (right <= currentActiveNodeRect.left) {
checkNodeDis(rect, node)
}
} else if (dir === 'Up') {
if (bottom <= currentActiveNodeRect.top) {
checkNodeDis(rect, node)
}
} else if (dir === 'Down') {
if (top >= currentActiveNodeRect.bottom) {
checkNodeDis(rect, node)
}
}
})
// 第二优先级:区域算法
if (!targetNode) {
this.getFocusNodeByAreaAlgorithm({
currentActiveNode,
currentActiveNodeRect,
dir,
checkNodeDis
})
}
// 第三优先级:简单算法
if (!targetNode) {
this.getFocusNodeBySimpleAlgorithm({
currentActiveNode,
currentActiveNodeRect,
dir,
checkNodeDis
})
}
// 找到了则让目标节点聚焦
if (targetNode) {
this.mindMap.renderer.moveNodeToCenter(targetNode)
targetNode.active()
}
}
// 1.简单算法
getFocusNodeBySimpleAlgorithm({
currentActiveNode,
currentActiveNodeRect,
dir,
checkNodeDis
}) {
// 遍历节点树
bfsWalk(this.mindMap.renderer.root, node => {
// 跳过当前聚焦的节点
if (node === currentActiveNode) return
// 当前遍历到的节点的位置信息
let rect = this.getNodeRect(node)
let { left, top, right, bottom } = rect
let match = false
// 按下了左方向键
if (dir === 'Left') {
// 判断节点是否在当前节点的左侧
match = right <= currentActiveNodeRect.left
// 按下了右方向键
} else if (dir === 'Right') {
// 判断节点是否在当前节点的右侧
match = left >= currentActiveNodeRect.right
// 按下了上方向键
} else if (dir === 'Up') {
// 判断节点是否在当前节点的上面
match = bottom <= currentActiveNodeRect.top
// 按下了下方向键
} else if (dir === 'Down') {
// 判断节点是否在当前节点的下面
match = top >= currentActiveNodeRect.bottom
}
// 符合要求,判断是否是最近的节点
if (match) {
checkNodeDis(rect, node)
}
})
}
// 2.阴影算法
getFocusNodeByShadowAlgorithm({
currentActiveNode,
currentActiveNodeRect,
dir,
checkNodeDis
}) {
bfsWalk(this.mindMap.renderer.root, node => {
if (node === currentActiveNode) return
let rect = this.getNodeRect(node)
let { left, top, right, bottom } = rect
let match = false
if (dir === 'Left') {
match =
left < currentActiveNodeRect.left &&
top < currentActiveNodeRect.bottom &&
bottom > currentActiveNodeRect.top
} else if (dir === 'Right') {
match =
right > currentActiveNodeRect.right &&
top < currentActiveNodeRect.bottom &&
bottom > currentActiveNodeRect.top
} else if (dir === 'Up') {
match =
top < currentActiveNodeRect.top &&
left < currentActiveNodeRect.right &&
right > currentActiveNodeRect.left
} else if (dir === 'Down') {
match =
bottom > currentActiveNodeRect.bottom &&
left < currentActiveNodeRect.right &&
right > currentActiveNodeRect.left
}
if (match) {
checkNodeDis(rect, node)
}
})
}
// 3.区域算法
getFocusNodeByAreaAlgorithm({
currentActiveNode,
currentActiveNodeRect,
dir,
checkNodeDis
}) {
// 当前聚焦节点的中心点
let cX = (currentActiveNodeRect.right + currentActiveNodeRect.left) / 2
let cY = (currentActiveNodeRect.bottom + currentActiveNodeRect.top) / 2
bfsWalk(this.mindMap.renderer.root, node => {
if (node === currentActiveNode) return
let rect = this.getNodeRect(node)
let { left, top, right, bottom } = rect
// 遍历到的节点的中心点
let ccX = (right + left) / 2
let ccY = (bottom + top) / 2
// 节点的中心点坐标和当前聚焦节点的中心点坐标的差值
let offsetX = ccX - cX
let offsetY = ccY - cY
if (offsetX === 0 && offsetY === 0) return
let match = false
if (dir === 'Left') {
match = offsetX <= 0 && offsetX <= offsetY && offsetX <= -offsetY
} else if (dir === 'Right') {
match = offsetX > 0 && offsetX >= -offsetY && offsetX >= offsetY
} else if (dir === 'Up') {
match = offsetY <= 0 && offsetY < offsetX && offsetY < -offsetX
} else if (dir === 'Down') {
match = offsetY > 0 && -offsetY < offsetX && offsetY > offsetX
}
if (match) {
checkNodeDis(rect, node)
}
})
}
// 获取节点的位置信息
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 14:12:50
* @Desc: 获取节点的位置信息
*/
getNodeRect(node) {
let { scaleX, scaleY, translateX, translateY } =
this.mindMap.draw.transform()
@@ -207,7 +104,12 @@ 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)
@@ -216,7 +118,12 @@ class KeyboardNavigation {
)
}
// 获取节点的中心点
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-12-09 14:13:11
* @Desc: 获取节点的中心点
*/
getCenter({ left, right, top, bottom }) {
return {
x: (left + right) / 2,
@@ -224,7 +131,3 @@ class KeyboardNavigation {
}
}
}
KeyboardNavigation.instanceName = 'keyboardNavigation'
export default KeyboardNavigation

View File

@@ -1,6 +1,11 @@
// 小地图类
class MiniMap {
// 构造函数
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:00:45
* @Desc: 构造函数
*/
constructor(opt) {
this.mindMap = opt.mindMap
this.isMousedown = false
@@ -14,14 +19,59 @@ 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.mindMap.getSvgData()
this.getMiniMap()
// 计算数据
let boxRatio = boxWidth / boxHeight
let actWidth = 0
@@ -74,7 +124,12 @@ class MiniMap {
}
}
// 小地图鼠标按下事件
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:22:40
* @Desc: 小地图鼠标按下事件
*/
onMousedown(e) {
this.isMousedown = true
this.mousedownPos = {
@@ -89,7 +144,12 @@ class MiniMap {
}
}
// 小地图鼠标移动事件
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:22:55
* @Desc: 小地图鼠标移动事件
*/
onMousemove(e, sensitivityNum = 5) {
if (!this.isMousedown) {
return
@@ -101,12 +161,15 @@ 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,15 +1,23 @@
import Style from './Style'
import Shape from './Shape'
import { resizeImgSize, asyncRun, measureText } from './utils'
import { Image, SVG, Circle, A, G, Rect, Text, ForeignObject } from '@svgdotjs/svg.js'
import { resizeImgSize, asyncRun } from './utils'
import { Image, SVG, Circle, A, G, Rect, Text } 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 || {})
@@ -110,8 +118,11 @@ class Node {
this._top = val
}
// 更新主题配置
/**
* @Author: 王林
* @Date: 2021-07-12 07:40:47
* @Desc: 更新主题配置
*/
updateThemeConfig() {
// 主题配置
this.themeConfig = this.mindMap.themeConfig
@@ -119,8 +130,11 @@ class Node {
this.style.updateThemeConfig(this.themeConfig)
}
// 复位部分布局时会重新设置的数据
/**
* @Author: 王林
* @Date: 2021-07-05 23:11:39
* @Desc: 复位部分布局时会重新设置的数据
*/
reset() {
this.children = []
this.parent = null
@@ -130,8 +144,11 @@ 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
@@ -139,14 +156,22 @@ 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) {
@@ -158,14 +183,21 @@ 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()
@@ -176,8 +208,11 @@ class Node {
this.createGeneralizationNode()
}
// 解绑所有事件
/**
* @Author: 王林
* @Date: 2021-07-10 09:20:02
* @Desc: 解绑所有事件
*/
removeAllEvent() {
if (this._noteData) {
this._noteData.node.off(['mouseover', 'mouseout'])
@@ -196,8 +231,11 @@ class Node {
}
}
// 移除节点内容
/**
* @Author: 王林
* @Date: 2021-07-07 21:27:24
* @Desc: 移除节点内容
*/
removeAllNode() {
// 节点内的内容
;[
@@ -231,8 +269,12 @@ class Node {
this.removeGeneralization()
}
// 计算节点的宽高
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 09:46:23
* @Desc: 计算节点的宽高
*/
getSize() {
this.removeAllNode()
this.createNodeData()
@@ -244,8 +286,12 @@ class Node {
return changed
}
// 计算节点尺寸信息
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 14:52:17
* @Desc: 计算节点尺寸信息
*/
getNodeRect() {
// 宽高
let imgContentWidth = 0
@@ -312,8 +358,12 @@ class Node {
}
}
// 创建图片节点
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:06:17
* @Desc: 创建图片节点
*/
createImgNode() {
let img = this.nodeData.data.image
if (!img) {
@@ -334,8 +384,12 @@ class Node {
}
}
// 获取图片显示宽高
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 10:12:51
* @Desc: 获取图片显示宽高
*/
getImgShowSize() {
return resizeImgSize(
this.nodeData.data.imageSize.width,
@@ -345,8 +399,12 @@ class Node {
)
}
// 创建icon节点
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:10:48
* @Desc: 创建icon节点
*/
createIconNode() {
let _data = this.nodeData.data
if (!_data.icon || _data.icon.length <= 0) {
@@ -362,86 +420,31 @@ class Node {
})
}
// 创建富文本节点
createRichTextNode() {
let g = new G()
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,
height
}
}
// 创建文本节点
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:08:56
* @Desc: 创建文本节点
*/
createTextNode() {
if (this.nodeData.data.richText) {
return this.createRichTextNode()
}
let g = new G()
let fontSize = this.getStyle(
'fontSize',
false,
this.isRoot,
this.nodeData.data.isActive
)
let lineHeight = this.getStyle(
'lineHeight',
false,
this.isRoot,
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) => {
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()
width = Math.ceil(width)
height = Math.ceil(height)
g.attr('data-width', width)
g.attr('data-height', height)
return {
node: g,
width,
@@ -449,8 +452,11 @@ class Node {
}
}
// 创建超链接节点
/**
* @Author: 王林
* @Date: 2021-06-20 15:28:54
* @Desc: 创建超链接节点
*/
createHyperlinkNode() {
let { hyperlink, hyperlinkTitle } = this.nodeData.data
if (!hyperlink) {
@@ -480,8 +486,11 @@ class Node {
}
}
// 创建标签节点
/**
* @Author: 王林
* @Date: 2021-06-20 19:49:15
* @Desc: 创建标签节点
*/
createTagNode() {
let tagData = this.nodeData.data.tag
if (!tagData || tagData.length <= 0) {
@@ -507,8 +516,11 @@ class Node {
return nodes
}
// 创建备注节点
/**
* @Author: 王林
* @Date: 2021-06-20 21:19:36
* @Desc: 创建备注节点
*/
createNoteNode() {
if (!this.nodeData.data.note) {
return null
@@ -565,17 +577,22 @@ class Node {
}
}
// 获取节点形状
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 22:02:07
* @Desc: 获取节点形状
*/
getShape() {
// 节点使用功能横线风格的话不支持设置形状,直接使用默认的矩形
return this.themeConfig.nodeUseLineStyle
? 'rectangle'
: this.style.getStyle('shape', false, false)
return 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()
@@ -619,7 +636,6 @@ 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
@@ -700,8 +716,11 @@ class Node {
})
}
// 激活节点
/**
* @Author: 王林
* @Date: 2021-07-10 16:44:22
* @Desc: 激活节点
*/
active(e) {
if (this.mindMap.opt.readonly) {
return
@@ -717,8 +736,11 @@ class Node {
this.mindMap.emit('node_active', this, this.renderer.activeNodeList)
}
// 渲染节点到画布,会移除旧的,创建新的
/**
* @Author: 王林
* @Date: 2021-07-04 20:20:09
* @Desc: 渲染节点到画布,会移除旧的,创建新的
*/
renderNode() {
// 连线
this.renderLine()
@@ -728,8 +750,11 @@ class Node {
this.layout()
}
// 更新节点
/**
* @Author: 王林
* @Date: 2021-07-04 22:47:01
* @Desc: 更新节点
*/
update(layout = false) {
if (!this.group) {
return
@@ -748,20 +773,18 @@ 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) {
@@ -798,14 +821,15 @@ class Node {
if (this.nodeData.inserting) {
delete this.nodeData.inserting
this.active()
setTimeout(() => {
this.mindMap.emit('node_dblclick', this)
}, 0)
this.mindMap.emit('node_dblclick', this)
}
}
// 递归删除
/**
* @Author: 王林
* @Date: 2021-07-10 09:24:55
* @Desc: 递归删除
*/
remove() {
this.initRender = true
this.removeAllEvent()
@@ -823,8 +847,12 @@ class Node {
}
}
// 隐藏节点
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-23 18:39:14
* @Desc: 隐藏节点
*/
hide() {
this.group.hide()
this.hideGeneralization()
@@ -844,8 +872,12 @@ class Node {
}
}
// 显示节点
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-23 18:39:14
* @Desc: 显示节点
*/
show() {
if (!this.group) {
return
@@ -868,8 +900,11 @@ class Node {
}
}
// 连线
/**
* @Author: 王林
* @Date: 2021-04-10 22:01:53
* @Desc: 连线
*/
renderLine(deep = false) {
if (this.nodeData.data.expand === false) {
return
@@ -905,8 +940,12 @@ 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)
@@ -922,8 +961,11 @@ class Node {
})
}
// 移除连线
/**
* @Author: 王林
* @Date: 2021-07-10 16:40:21
* @Desc: 移除连线
*/
removeLine() {
this._lines.forEach(line => {
line.remove()
@@ -931,14 +973,21 @@ 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
@@ -966,15 +1015,22 @@ 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
@@ -999,8 +1055,11 @@ class Node {
this._generalizationNode.render()
}
// 删除概要节点
/**
* @Author: 王林
* @Date: 2022-07-30 13:11:27
* @Desc: 删除概要节点
*/
removeGeneralization() {
if (this._generalizationLine) {
this._generalizationLine.remove()
@@ -1020,8 +1079,12 @@ class Node {
}
}
// 隐藏概要节点
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-01 09:56:46
* @Desc: 隐藏概要节点
*/
hideGeneralization() {
if (this._generalizationLine) {
this._generalizationLine.hide()
@@ -1031,8 +1094,12 @@ class Node {
}
}
// 显示概要节点
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-01 09:57:42
* @Desc: 显示概要节点
*/
showGeneralization() {
if (this._generalizationLine) {
this._generalizationLine.show()
@@ -1042,8 +1109,11 @@ class Node {
}
}
// 创建或更新展开收缩按钮内容
/**
* @Author: 王林
* @Date: 2021-07-10 17:59:14
* @Desc: 创建或更新展开收缩按钮内容
*/
updateExpandBtnNode() {
if (this._expandBtn) {
this._expandBtn.clear()
@@ -1062,8 +1132,12 @@ 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
@@ -1071,8 +1145,11 @@ class Node {
this.renderer.layout.renderExpandBtn(this, this._expandBtn)
}
// 展开收缩按钮
/**
* @Author: 王林
* @Date: 2021-04-11 19:47:01
* @Desc: 展开收缩按钮
*/
renderExpandBtn() {
if (
!this.nodeData.children ||
@@ -1109,8 +1186,11 @@ class Node {
this.updateExpandBtnPos()
}
// 移除展开收缩按钮
/**
* @Author: 王林
* @Date: 2021-07-11 13:26:00
* @Desc: 移除展开收缩按钮
*/
removeExpandBtn() {
if (this._expandBtn) {
this._expandBtn.off(['mouseover', 'mouseout', 'click'])
@@ -1120,8 +1200,12 @@ class Node {
}
}
// 检测当前节点是否是某个节点的祖先节点
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-25 09:51:37
* @Desc: 检测当前节点是否是某个节点的祖先节点
*/
isParent(node) {
if (this === node) {
return false
@@ -1136,8 +1220,12 @@ 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
@@ -1147,8 +1235,11 @@ class Node {
})
}
// 获取padding值
/**
* @Author: 王林
* @Date: 2021-06-20 22:51:57
* @Desc: 获取padding值
*/
getPaddingVale() {
return {
paddingX: this.getStyle('paddingX', true, this.nodeData.data.isActive),
@@ -1156,21 +1247,32 @@ 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 (
@@ -1180,8 +1282,12 @@ class Node {
return null
}
// 获取自身可继承的自定义样式
/**
* javascript comment
* @Author: flydreame
* @Date: 2022-09-17 12:15:30
* @Desc: 获取自身可继承的自定义样式
*/
getSelfInhertStyle(prop) {
return (
this.getSelfStyle(prop) || // 自身
@@ -1189,62 +1295,93 @@ 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,11 +20,19 @@ 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
@@ -50,8 +58,12 @@ 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]
@@ -60,8 +72,11 @@ class Render {
)(this)
}
// 绑定事件
/**
* @Author: 王林
* @Date: 2021-06-20 10:34:06
* @Desc: 绑定事件
*/
bindEvent() {
// 点击事件
this.mindMap.on('draw_click', () => {
@@ -72,8 +87,11 @@ class Render {
})
}
// 注册命令
/**
* @Author: 王林
* @Date: 2021-05-04 13:19:06
* @Desc: 注册命令
*/
registerCommands() {
// 全选
this.selectAll = this.selectAll.bind(this)
@@ -174,8 +192,11 @@ 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', () => {
@@ -219,8 +240,12 @@ 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')
@@ -228,8 +253,12 @@ 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)
@@ -237,9 +266,13 @@ class Render {
// this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap)
}
// 渲染
render(callback = () => {}) {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 16:27:55
* @Desc: 渲染
*/
render() {
if (this.reRender) {
this.clearActive()
}
@@ -247,14 +280,16 @@ 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)
@@ -262,8 +297,11 @@ class Render {
this.activeNodeList = []
}
// 清除当前所有激活节点,并会触发事件
/**
* @Author: 王林
* @Date: 2021-08-03 23:14:34
* @Desc: 清除当前所有激活节点,并会触发事件
*/
clearAllActive() {
if (this.activeNodeList.length <= 0) {
return
@@ -272,8 +310,11 @@ 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) {
@@ -281,8 +322,11 @@ class Render {
}
}
// 在激活列表里移除某个节点
/**
* @Author: 王林
* @Date: 2021-07-10 10:04:04
* @Desc: 在激活列表里移除某个节点
*/
removeActiveNode(node) {
let index = this.findActiveNodeIndex(node)
if (index === -1) {
@@ -291,16 +335,22 @@ 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 => {
@@ -309,8 +359,11 @@ class Render {
: 0
}
// 全选
/**
* @Author: 王林
* @Date: 2021-08-04 23:54:52
* @Desc: 全选
*/
selectAll() {
walk(
this.root,
@@ -331,8 +384,11 @@ class Render {
)
}
// 回退
/**
* @Author: 王林
* @Date: 2021-07-11 22:34:12
* @Desc: 回退
*/
back(step) {
this.clearAllActive()
let data = this.mindMap.command.back(step)
@@ -342,8 +398,12 @@ 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)
@@ -353,8 +413,11 @@ class Render {
}
}
// 插入同级节点,多个节点只会操作第一个节点
/**
* @Author: 王林
* @Date: 2021-05-04 13:19:54
* @Desc: 插入同级节点,多个节点只会操作第一个节点
*/
insertNode() {
if (this.activeNodeList.length <= 0) {
return
@@ -380,8 +443,11 @@ class Render {
}
}
// 插入子节点
/**
* @Author: 王林
* @Date: 2021-05-04 13:31:02
* @Desc: 插入子节点
*/
insertChildNode() {
if (this.activeNodeList.length <= 0) {
return
@@ -411,8 +477,11 @@ class Render {
this.mindMap.render()
}
// 上移节点,多个节点只会操作第一个节点
/**
* @Author: 王林
* @Date: 2021-07-14 23:34:14
* @Desc: 上移节点,多个节点只会操作第一个节点
*/
upNode() {
if (this.activeNodeList.length <= 0) {
return
@@ -439,8 +508,11 @@ class Render {
this.mindMap.render()
}
// 下移节点,多个节点只会操作第一个节点
/**
* @Author: 王林
* @Date: 2021-07-14 23:34:18
* @Desc: 下移节点,多个节点只会操作第一个节点
*/
downNode() {
if (this.activeNodeList.length <= 0) {
return
@@ -467,126 +539,127 @@ 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
let nodeIndex = nodeBorthers.findIndex(item => {
let parent = node.parent
let childList = parent.children
// 移动节点的索引
let index = childList.findIndex(item => {
return item === node
})
if (nodeIndex === -1) {
if (index === -1) {
return
}
nodeBorthers.splice(nodeIndex, 1)
nodeParent.nodeData.children.splice(nodeIndex, 1)
// 目标节点
let existParent = exist.parent
let existBorthers = existParent.children
let existIndex = existBorthers.findIndex(item => {
// 目标节点的索引
let existIndex = childList.findIndex(item => {
return item === exist
})
if (existIndex === -1) {
return
}
existBorthers.splice(existIndex, 0, node)
existParent.nodeData.children.splice(existIndex, 0, node.nodeData)
this.mindMap.render(() => {
if (nodeLayerChanged) {
node.getSize()
node.renderNode()
}
})
// 当前节点在目标节点前面
if (index < existIndex) {
existIndex = existIndex - 1
}
// 节点实例
childList.splice(index, 1)
childList.splice(existIndex, 0, node)
// 节点数据
parent.nodeData.children.splice(index, 1)
parent.nodeData.children.splice(existIndex, 0, node.nodeData)
this.mindMap.render()
}
// 将节点移动到另一个节点的后面
/**
* 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
let nodeIndex = nodeBorthers.findIndex(item => {
let parent = node.parent
let childList = parent.children
// 移动节点的索引
let index = childList.findIndex(item => {
return item === node
})
if (nodeIndex === -1) {
if (index === -1) {
return
}
nodeBorthers.splice(nodeIndex, 1)
nodeParent.nodeData.children.splice(nodeIndex, 1)
// 目标节点
let existParent = exist.parent
let existBorthers = existParent.children
let existIndex = existBorthers.findIndex(item => {
// 目标节点的索引
let existIndex = childList.findIndex(item => {
return item === exist
})
if (existIndex === -1) {
return
}
existIndex++
existBorthers.splice(existIndex, 0, node)
existParent.nodeData.children.splice(existIndex, 0, node.nodeData)
this.mindMap.render(() => {
if (nodeLayerChanged) {
node.getSize()
node.renderNode()
}
})
// 当前节点在目标节点前面
if (index < existIndex) {
// do nothing
} else {
existIndex = existIndex + 1
}
// 节点实例
childList.splice(index, 1)
childList.splice(existIndex, 0, node)
// 节点数据
parent.nodeData.children.splice(index, 1)
parent.nodeData.children.splice(existIndex, 0, node.nodeData)
this.mindMap.render()
}
// 移除节点
/**
* @Author: 王林
* @Date: 2021-05-04 13:40:39
* @Desc: 移除节点
*/
removeNode() {
if (this.activeNodeList.length <= 0) {
return
}
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--
}
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--
}
}
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()
@@ -594,8 +667,12 @@ 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
@@ -603,8 +680,11 @@ 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
@@ -623,8 +703,12 @@ class Render {
}
}
// 移动一个节点作为另一个节点的子节点
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-24 16:54:01
* @Desc: 移动一个节点作为另一个节点的子节点
*/
moveNodeTo(node, toNode) {
if (node.isRoot) {
return
@@ -635,13 +719,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
@@ -652,8 +736,11 @@ class Render {
this.mindMap.render()
}
// 设置节点样式
/**
* @Author: 王林
* @Date: 2021-07-08 21:54:30
* @Desc: 设置节点样式
*/
setNodeStyle(node, prop, value, isActive) {
let data = {}
if (isActive) {
@@ -668,15 +755,6 @@ 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)) {
@@ -684,8 +762,11 @@ class Render {
}
}
// 设置节点是否激活
/**
* @Author: 王林
* @Date: 2021-07-08 22:13:03
* @Desc: 设置节点是否激活
*/
setNodeActive(node, active) {
this.setNodeData(node, {
isActive: active
@@ -693,8 +774,11 @@ class Render {
node.renderNode()
}
// 设置节点是否展开
/**
* @Author: 王林
* @Date: 2021-07-10 16:52:41
* @Desc: 设置节点是否展开
*/
setNodeExpand(node, expand) {
this.setNodeData(node, {
expand
@@ -717,8 +801,11 @@ class Render {
this.mindMap.render()
}
// 展开所有
/**
* @Author: 王林
* @Date: 2021-07-15 23:23:37
* @Desc: 展开所有
*/
expandAllNode() {
walk(
this.renderTree,
@@ -736,8 +823,11 @@ class Render {
this.mindMap.reRender()
}
// 收起所有
/**
* @Author: 王林
* @Date: 2021-07-15 23:27:14
* @Desc: 收起所有
*/
unexpandAllNode() {
walk(
this.renderTree,
@@ -756,8 +846,12 @@ class Render {
this.mindMap.reRender()
}
// 展开到指定层级
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-09-23 16:31:27
* @Desc: 展开到指定层级
*/
expandToLevel(level) {
walk(
this.renderTree,
@@ -774,8 +868,11 @@ 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) {
@@ -785,8 +882,11 @@ class Render {
})
}
// 切换节点展开状态
/**
* @Author: 王林
* @Date: 2021-07-11 17:15:33
* @Desc: 切换节点展开状态
*/
toggleNodeExpand(node) {
this.mindMap.execCommand(
'SET_NODE_EXPAND',
@@ -795,17 +895,22 @@ class Render {
)
}
// 设置节点文本
setNodeText(node, text, richText) {
/**
* @Author: 王林
* @Date: 2021-07-09 22:04:19
* @Desc: 设置节点文本
*/
setNodeText(node, text) {
this.setNodeDataRender(node, {
text,
richText
text
})
}
// 设置节点图片
/**
* @Author: 王林
* @Date: 2021-07-10 08:37:40
* @Desc: 设置节点图片
*/
setNodeImage(node, { url, title, width, height }) {
this.setNodeDataRender(node, {
image: url,
@@ -817,16 +922,22 @@ 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,
@@ -834,24 +945,33 @@ 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
@@ -870,8 +990,11 @@ class Render {
this.mindMap.render()
}
// 删除节点概要
/**
* @Author: 王林
* @Date: 2022-07-30 21:16:33
* @Desc: 删除节点概要
*/
removeGeneralization() {
if (this.activeNodeList.length <= 0) {
return
@@ -888,8 +1011,12 @@ 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 => {
@@ -900,8 +1027,12 @@ class Render {
})
}
// 一键整理布局,即去除自定义位置
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-02 20:02:50
* @Desc: 一键整理布局,即去除自定义位置
*/
resetLayout() {
walk(
this.root,
@@ -922,8 +1053,12 @@ class Render {
)
}
// 设置节点形状
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 21:44:01
* @Desc: 设置节点形状
*/
setNodeShape(node, shape) {
if (!shape || !shapeList.includes(shape)) {
return
@@ -934,16 +1069,22 @@ 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()
@@ -957,8 +1098,12 @@ 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

@@ -1,428 +0,0 @@
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,9 +1,17 @@
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
@@ -15,7 +23,11 @@ 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 => {
@@ -69,7 +81,11 @@ class Select {
})
}
// 鼠标移动事件
/**
* @Author: 王林
* @Date: 2021-07-13 07:55:49
* @Desc: 鼠标移动事件
*/
onMove(x, y) {
// 绘制矩形
this.rect.plot([
@@ -112,14 +128,22 @@ 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()
@@ -132,7 +156,11 @@ class Select {
.plot([[x, y]])
}
// 检测在选区里的节点
/**
* @Author: 王林
* @Date: 2021-07-11 10:20:43
* @Desc: 检测在选区里的节点
*/
checkInNodes() {
let { scaleX, scaleY, translateX, translateY } =
this.mindMap.draw.transform()
@@ -167,6 +195,4 @@ class Select {
}
}
Select.instanceName = 'select'
export default Select

View File

@@ -1,10 +1,18 @@
// 节点形状类
/**
* @Author: 王林
* @Date: 2022-08-22 21:32:50
* @Desc: 节点形状类
*/
export default class Shape {
constructor(node) {
this.node = node
}
// 形状需要的padding
/**
* @Author: 王林
* @Date: 2022-08-17 22:32:32
* @Desc: 形状需要的padding
*/
getShapePadding(width, height, paddingX, paddingY) {
const shape = this.node.getShape()
const defaultPaddingX = 15
@@ -56,7 +64,11 @@ export default class Shape {
}
}
// 创建形状节点
/**
* @Author: 王林
* @Date: 2022-08-17 22:22:53
* @Desc: 创建形状节点
*/
createShape() {
const shape = this.node.getShape()
let { width, height } = this.node
@@ -92,7 +104,11 @@ 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
@@ -113,7 +129,11 @@ 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
@@ -126,7 +146,11 @@ export default class Shape {
`)
}
// 创建圆角矩形
/**
* @Author: 王林
* @Date: 2022-09-03 16:50:23
* @Desc: 创建圆角矩形
*/
createRoundedRectangle() {
let { width, height } = this.node
let halfHeight = height / 2
@@ -139,7 +163,12 @@ export default class Shape {
`)
}
// 创建八角矩形
/**
* javascript comment
* @Author: 王林
* @Date: 2022-09-12 16:14:08
* @Desc: 创建八角矩形
*/
createOctagonalRectangle() {
let w = 5
let { width, height } = this.node
@@ -155,7 +184,12 @@ 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
@@ -170,7 +204,12 @@ 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
@@ -185,7 +224,12 @@ 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
@@ -198,7 +242,12 @@ 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,39 +1,50 @@
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, backgroundPosition, backgroundSize } = themeConfig
let { backgroundColor, backgroundImage, backgroundRepeat } = 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
@@ -67,49 +78,59 @@ 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')
})
// 节点使用横线样式,不需要渲染非激活状态的边框样式
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')
})
node
.fill({
color: this.merge('fillColor')
})
.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({
@@ -124,28 +145,22 @@ class Style {
})
}
// 获取文本样式
getTextFontStyle() {
return {
italic: this.merge('fontStyle') === 'italic',
bold: this.merge('fontWeight'),
fontSize: this.merge('fontSize'),
fontFamily: this.merge('fontFamily')
}
}
// html文字节点
/**
* @Author: 王林
* @Date: 2021-04-13 08:14:34
* @Desc: 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({
@@ -156,30 +171,42 @@ 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({
@@ -189,8 +216,11 @@ 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,8 +1,18 @@
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
@@ -13,7 +23,11 @@ export default class TextEdit {
this.bindEvent()
}
// 事件
/**
* @Author: 王林
* @Date: 2021-04-24 13:27:04
* @Desc: 事件
*/
bindEvent() {
this.show = this.show.bind(this)
// 节点双击事件
@@ -40,7 +54,12 @@ export default class TextEdit {
})
}
// 注册临时快捷键
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-08-16 16:27:02
* @Desc: 注册临时快捷键
*/
registerTmpShortcut() {
// 注册回车快捷键
this.mindMap.keyCommand.addShortcut('Enter', () => {
@@ -48,33 +67,33 @@ export default class TextEdit {
})
}
// 显示文本编辑框
/**
* @Author: 王林
* @Date: 2021-04-13 22:15:56
* @Desc: 显示文本编辑框
*/
show(node) {
let rect = node._textData.node.node.getBoundingClientRect()
if (this.mindMap.richText) {
this.mindMap.richText.showEditText(node, rect)
return
}
this.showEditTextBox(node, rect)
this.showEditTextBox(node, node._textData.node.node.getBoundingClientRect())
}
// 显示文本编辑框
/**
* @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; word-break: break-all;`
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.setAttribute('contenteditable', true)
this.textEditNode.addEventListener('keyup', e => {
e.stopPropagation()
})
document.body.appendChild(this.textEditNode)
}
let scale = this.mindMap.view.scale
let lineHeight = node.style.merge('lineHeight')
let fontSize = node.style.merge('fontSize')
node.style.domText(this.textEditNode, scale)
node.style.domText(this.textEditNode, this.mindMap.view.scale)
this.textEditNode.innerHTML = node.nodeData.data.text
.split(/\n/gim)
.join('<br>')
@@ -83,14 +102,16 @@ 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()
@@ -99,11 +120,12 @@ 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,6 +1,16 @@
// 视图操作类
/**
* 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
@@ -14,7 +24,12 @@ class View {
this.bind()
}
// 绑定
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:38:51
* @Desc: 绑定
*/
bind() {
// 快捷键
this.mindMap.keyCommand.addShortcut('Control+=', () => {
@@ -65,7 +80,12 @@ class View {
})
}
// 获取当前变换状态数据
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-22 18:30:24
* @Desc: 获取当前变换状态数据
*/
getTransformData() {
return {
transform: this.mindMap.draw.transform(),
@@ -79,7 +99,12 @@ class View {
}
}
// 动态设置变换状态数据
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-11-22 19:54:17
* @Desc: 动态设置变换状态数据
*/
setTransformData(viewData) {
if (viewData) {
Object.keys(viewData.state).forEach(prop => {
@@ -93,31 +118,55 @@ class View {
}
}
// 平移x方向
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-13 15:49:06
* @Desc: 平移x方向
*/
translateX(step) {
this.x += step
this.transform()
}
// 平移x方式到
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:03:53
* @Desc: 平移x方式到
*/
translateXTo(x) {
this.x = x
this.transform()
}
// 平移y方向
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-13 15:48:52
* @Desc: 平移y方向
*/
translateY(step) {
this.y += step
this.transform()
}
// 平移y方向到
/**
* javascript comment
* @Author: 王林25
* @Date: 2022-10-10 14:04:10
* @Desc: 平移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,
@@ -127,7 +176,11 @@ 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
@@ -135,7 +188,11 @@ 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
@@ -146,14 +203,23 @@ 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

@@ -1,120 +0,0 @@
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

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

View File

@@ -1,8 +1,16 @@
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
@@ -14,25 +22,45 @@ 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
@@ -70,13 +98,22 @@ 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
@@ -87,14 +124,22 @@ 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
@@ -103,21 +148,33 @@ class Base {
return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`
}
// 获取节点的marginX
/**
* @Author: 王林
* @Date: 2021-06-27 19:00:07
* @Desc: 获取节点的marginX
*/
getMarginX(layerIndex) {
return layerIndex === 1
? this.mindMap.themeConfig.second.marginX
: this.mindMap.themeConfig.node.marginX
}
// 获取节点的marginY
/**
* @Author: 王林
* @Date: 2021-04-11 15:34:20
* @Desc: 获取节点的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,
@@ -125,7 +182,11 @@ class Base {
)
}
// 获取节点包括概要在内的高度
/**
* @Author: 王林
* @Date: 2022-07-31 20:53:12
* @Desc: 获取节点包括概要在内的高度
*/
getNodeHeightWithGeneralization(node) {
return Math.max(
node.height,
@@ -133,8 +194,10 @@ class Base {
)
}
// 获取节点的边界值
/**
* @Author: 王林
* @Date: 2022-07-31 09:14:03
* @Desc: 获取节点的边界值
* dir生长方向h水平、v垂直
* isLeft是否向左生长
*/

View File

@@ -1,386 +1,430 @@
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
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

View File

@@ -1,17 +1,27 @@
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 = [
() => {
@@ -30,8 +40,12 @@ class LogicalStructure extends Base {
asyncRun(task)
}
// 遍历数据计算节点的left、width、height
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:49:32
* @Desc: 遍历数据计算节点的left、width、height
*/
computedBaseValue() {
walk(
this.renderer.renderTree,
@@ -66,8 +80,12 @@ class LogicalStructure extends Base {
)
}
// 遍历节点树计算节点的top
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:59:25
* @Desc: 遍历节点树计算节点的top
*/
computedTopValue() {
walk(
this.root,
@@ -93,8 +111,12 @@ class LogicalStructure extends Base {
)
}
// 调整节点top
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 10:04:05
* @Desc: 调整节点top
*/
adjustTopValue() {
walk(
this.root,
@@ -117,8 +139,12 @@ class LogicalStructure extends Base {
)
}
// 更新兄弟节点的top
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:26:03
* @Desc: 更新兄弟节点的top
*/
updateBrothers(node, addHeight) {
if (node.parent) {
let childrenList = node.parent.children
@@ -149,8 +175,11 @@ 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)
@@ -161,8 +190,12 @@ 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 []
@@ -170,61 +203,54 @@ 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 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}`
let path = `M ${x1},${y1} L ${x1 + s1},${y1} L ${
x1 + s1
},${y2} L ${x2},${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
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
let path = `M ${x1},${y1} L ${x2},${y2}`
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
@@ -232,39 +258,32 @@ 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) + nodeUseLineStylePath
path = this.quadraticCurvePath(x1, y1, x2, y2)
} else {
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath
path = this.cubicBezierPath(x1, y1, x2, y2)
}
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()
// 节点使用横线风格,需要调整展开收起按钮位置
let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle
? height / 2
: 0
btn.translate(
width - translateX,
height / 2 - translateY + nodeUseLineStyleOffset
)
btn.translate(width - translateX, height / 2 - translateY)
}
// 创建概要节点
/**
* @Author: 王林
* @Date: 2022-07-30 08:30:35
* @Desc: 创建概要节点
*/
renderGeneralization(node, gLine, gNode) {
let {
top,

View File

@@ -1,17 +1,28 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
// 思维导图
/**
* @Author: 王林
* @Date: 2021-04-12 22:25:58
* @Desc: 思维导图
* 在逻辑结构图的基础上增加一个变量来记录生长方向向左还是向右同时在计算left的时候根据方向来计算、调整top时只考虑同方向的节点即可
*/
class MindMap extends Base {
// 构造函数
// 在逻辑结构图的基础上增加一个变量来记录生长方向向左还是向右同时在计算left的时候根据方向来计算、调整top时只考虑同方向的节点即可
/**
* @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 = [
() => {
@@ -30,8 +41,12 @@ class MindMap extends Base {
asyncRun(task)
}
// 遍历数据计算节点的left、width、height
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:49:32
* @Desc: 遍历数据计算节点的left、width、height
*/
computedBaseValue() {
walk(
this.renderer.renderTree,
@@ -95,8 +110,12 @@ class MindMap extends Base {
)
}
// 遍历节点树计算节点的top
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:59:25
* @Desc: 遍历节点树计算节点的top
*/
computedTopValue() {
walk(
this.root,
@@ -128,8 +147,12 @@ class MindMap extends Base {
)
}
// 调整节点top
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 10:04:05
* @Desc: 调整节点top
*/
adjustTopValue() {
walk(
this.root,
@@ -151,8 +174,12 @@ class MindMap extends Base {
)
}
// 更新兄弟节点的top
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:26:03
* @Desc: 更新兄弟节点的top
*/
updateBrothers(node, leftAddHeight, rightAddHeight) {
if (node.parent) {
// 过滤出和自己同方向的节点
@@ -187,8 +214,11 @@ 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)
@@ -199,8 +229,12 @@ 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 []
@@ -208,18 +242,12 @@ 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
@@ -227,24 +255,25 @@ 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
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}`
let path = `M ${x1},${y1} L ${x1 + _s},${y1} L ${
x1 + _s
},${y2} L ${x2},${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
@@ -255,31 +284,23 @@ 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
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
let path = `M ${x1},${y1} L ${x2},${y2}`
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
@@ -291,43 +312,34 @@ 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) + nodeUseLineStylePath
path = this.quadraticCurvePath(x1, y1, x2, y2)
} else {
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath
path = this.cubicBezierPath(x1, y1, x2, y2)
}
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 + nodeUseLineStyleOffset
let y = height / 2 - translateY
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,17 +1,28 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
// 组织结构图
// 和逻辑结构图基本一样只是方向变成向下生长所以先计算节点的top后计算节点的left、最后调整节点的left即可
/**
* @Author: 王林
* @Date: 2021-04-12 22:25:58
* @Desc: 组织结构图
* 和逻辑结构图基本一样只是方向变成向下生长所以先计算节点的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 = [
() => {
@@ -30,8 +41,12 @@ class OrganizationStructure extends Base {
asyncRun(task)
}
// 遍历数据计算节点的left、width、height
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:49:32
* @Desc: 遍历数据计算节点的left、width、height
*/
computedBaseValue() {
walk(
this.renderer.renderTree,
@@ -66,8 +81,12 @@ class OrganizationStructure extends Base {
)
}
// 遍历节点树计算节点的left
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:59:25
* @Desc: 遍历节点树计算节点的left
*/
computedLeftValue() {
walk(
this.root,
@@ -93,8 +112,12 @@ class OrganizationStructure extends Base {
)
}
// 调整节点left
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 10:04:05
* @Desc: 调整节点left
*/
adjustLeftValue() {
walk(
this.root,
@@ -117,8 +140,12 @@ class OrganizationStructure extends Base {
)
}
// 更新兄弟节点的left
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:26:03
* @Desc: 更新兄弟节点的left
*/
updateBrothers(node, addWidth) {
if (node.parent) {
let childrenList = node.parent.children
@@ -149,8 +176,11 @@ 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)
@@ -159,8 +189,12 @@ 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 []
@@ -171,18 +205,18 @@ class OrganizationStructure extends Base {
node.children.forEach((item, index) => {
let x2 = item.left + item.width / 2
let y2 = item.top
// 节点使用横线风格,需要额外渲染横线
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
let path = `M ${x1},${y1} L ${x2},${y2}`
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 []
@@ -204,11 +238,7 @@ class OrganizationStructure extends Base {
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},${y2}` + nodeUseLineStylePath
let path = `M ${x2},${y1 + s1} L ${x2},${y2}`
lines[index].plot(path)
style && style(lines[index], item)
})
@@ -231,8 +261,11 @@ 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()
@@ -242,8 +275,11 @@ class OrganizationStructure extends Base {
)
}
// 创建概要节点
/**
* @Author: 王林
* @Date: 2022-07-30 08:30:35
* @Desc: 创建概要节点
*/
renderGeneralization(node, gLine, gNode) {
let {
bottom,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,11 @@
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)',

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