Compare commits

...

264 Commits

Author SHA1 Message Date
wanglin2
9b7b41597f Merge branch 'main' of https://github.com/wanglin2/mind-map into main 2023-03-31 14:39:30 +08:00
wanglin2
e56ff8fdf2 更新群二维码 2023-03-31 14:39:09 +08:00
wanglin2
3f9da8940f 打包0.4.7 2023-03-24 21:05:12 +08:00
wanglin2
26d5f69af2 Doc:更新文档 2023-03-24 15:26:44 +08:00
wanglin2
6efe4a3fd6 Feature:1.支持配置插入节点时的初始文字;2.优化历史记录添加逻辑;3.节点插入和删除命令支持传入指定节点和初始节点数据 2023-03-24 15:26:24 +08:00
wanglin2
2b4ab4a322 Fix:修复从富文本模式切换到非富文本模式时没有触发数据更新和历史记录的问题 2023-03-24 11:34:09 +08:00
wanglin2
cca42d1f89 Fix:修复从富文本模式切换到非富文本模式时 2023-03-24 11:33:17 +08:00
wanglin2
724fef87b1 Doc:更新文档 2023-03-24 11:22:18 +08:00
wanglin2
d50c0e4cd5 优化:1.节点激活状态切换不再触发历史记录;2.短时间多次触发历史记录只会添加最后一次的数据 2023-03-24 11:22:05 +08:00
wanglin2
c18c037642 Doc:更新文档 2023-03-24 10:31:21 +08:00
wanglin2
cda1da5fd0 Demo:Feature:支持导入和导出为markdown格式,优化导出弹窗视觉 2023-03-24 10:30:53 +08:00
wanglin2
eba1aa3a37 Demo:增加图标 2023-03-24 10:28:35 +08:00
wanglin2
81018bb615 Fix:修复富文本编辑模式下当没有富文本节点时无法导出为图片的问题 2023-03-24 10:27:13 +08:00
wanglin2
5fa0ff7b5c Feature:支持导入和导出为markdown格式文件 2023-03-24 10:13:24 +08:00
wanglin2
cabe286ebb 优化:富文本编辑时默认不再全选;使用节点填充色作为编辑时的背景色 2023-03-23 09:39:00 +08:00
wanglin2
e337da088b 打包0.4.6 2023-03-22 15:14:50 +08:00
wanglin2
de97ea9e75 兼容0.4.5版本的关联线 2023-03-22 14:40:07 +08:00
wanglin2
cc331065eb 修改文档 2023-03-22 13:49:47 +08:00
wanglin2
9a8e630654 Feature:支持调整关联线的控制点 2023-03-22 13:49:34 +08:00
wanglin2
17ab977efb 优化关联线的点击逻辑 2023-03-22 13:42:31 +08:00
wanglin2
6bd10d9451 1.demo:大纲支持点击后定位节点,支持添加节点;2.完善问题 2023-03-21 09:35:34 +08:00
wanglin2
5313b9b69c 1.优化重复的历史数据;2.修复节点编辑时的方向键冲突;3.修复拖拽节点的id丢失问题 2023-03-21 09:34:47 +08:00
wanglin2
8dcfdcc44a 更新二维码 2023-03-20 09:26:46 +08:00
wanglin2
67422df3ff 打包0.4.5 2023-03-18 21:54:20 +08:00
wanglin2
2ad7536eb7 优化关联线逻辑 2023-03-18 21:51:02 +08:00
wanglin2
6f56e5c4e6 完善文档 2023-03-17 23:06:14 +08:00
wanglin2
5ae8ebe590 Feature:1.优化关联线创建,2.支持按住ctrl键多选和取消多选节点 2023-03-17 23:00:25 +08:00
wanglin2
6ecb97e4e5 Feature:按住根节点也可以拖动画布 2023-03-17 17:03:56 +08:00
wanglin2
c8f938dd3e Fix:修复创建关联线时节点激活状态未清除的问题 2023-03-17 16:57:30 +08:00
wanglin2
0d29e29162 完善文档 2023-03-17 16:24:22 +08:00
wanglin2
be1b3dffce demo支持关联线功能 2023-03-17 15:39:07 +08:00
wanglin2
3ba2dbe415 Feature:新增关联线功能 2023-03-17 15:38:38 +08:00
wanglin2
19a96c92a9 新增图标 2023-03-17 14:43:23 +08:00
wanglin2
c265e3e437 打包0.4.4 2023-03-14 09:45:27 +08:00
wanglin2
7434ac2648 Feature:支持响应鼠标的横向滚动 2023-03-08 16:04:26 +08:00
wanglin2
63d73a73aa 打包0.4.3 2023-03-08 09:43:11 +08:00
wanglin2
00f9258a4d 更新文档 2023-03-08 09:39:05 +08:00
wanglin2
7087b43d39 Fix:修复前进后退没有触发data_change事件的问题;Feature:鼠标滚轮事件支持自定义 2023-03-08 09:38:46 +08:00
wanglin2
945a78b7b1 去除docs目录,更新群二维码 2023-03-06 14:26:02 +08:00
wanglin2
55acc19ab8 打包0.4.2 2023-03-01 09:24:51 +08:00
wanglin2
410f0be05e Fetaure:Node类的setText方法增加第二个参数,以支持设置富文本内容 2023-03-01 09:17:45 +08:00
wanglin2
2d7c091071 打包0.4.1 2023-02-27 15:31:52 +08:00
wanglin2
9bf58a54ce Feature:节点富文本编辑支持清除文字样式 2023-02-27 15:19:56 +08:00
wanglin2
231adeea44 Feature:富文本支持设置背景颜色 2023-02-27 14:58:01 +08:00
wanglin2
0ea618af39 Fix:1.Mac系统触控板缩放方向相反的问题;2.设备像素比不为1时导出图片中富文本节点尺寸变大的问题.Feater:节点抛出鼠标移入和移出事件 2023-02-26 10:07:03 +08:00
wanglin2
f66297ff99 打包0.4.0 2023-02-25 10:11:16 +08:00
wanglin2
3113bf2e1f 完善文档 2023-02-24 17:14:48 +08:00
wanglin2
8c114dac02 Feature:支持节点富文本编辑 2023-02-24 17:14:24 +08:00
wanglin2
06dba79272 新增图标 2023-02-24 17:13:07 +08:00
wanglin2
8441986ca7 修改文档 2023-02-20 19:25:59 +08:00
wanglin2
c8b50908e1 Fix:修复底边风格的情况下,节点高度过高会和其他节点重叠的问题 2023-02-20 15:10:27 +08:00
wanglin2
7e11fde892 打包0.3.4 2023-02-20 11:30:15 +08:00
wanglin2
3d9f3bd7a8 Fix:修复批量删除的节点中如果存在根节点会出现删除异常的问题 2023-02-20 11:26:26 +08:00
wanglin2
46e11649b0 优化节点文本编辑 2023-02-20 11:02:04 +08:00
wanglin2
161ef7590c feature:节点文本增加自动换行功能 2023-02-20 10:14:34 +08:00
wanglin2
ca660a3c74 打包0.3.3 2023-02-14 09:38:38 +08:00
wanglin2
7a24872331 Fix:修复根节点无法黄的问题 2023-02-14 09:35:56 +08:00
wanglin2
eb4ea9fb3a 打包0.3.2 2023-02-03 11:13:58 +08:00
wanglin2
64228c02ff 修复当思维导图实际内容大于屏幕宽高时,导出的时候超出的部分没有绘制水印的问题 2023-02-03 11:09:43 +08:00
wanglin2
f184a5d948 修复二级节点拖拽到其他节点或其他节点拖拽到二级节点时节点样式没有更新的问题 2023-02-03 10:40:06 +08:00
wanglin2
0bf5b2d6f7 打包0.3.1 2023-02-01 16:30:29 +08:00
wanglin2
74a52ea386 导出d的图片支持background-size/position/repeat属性效果 2023-02-01 16:25:57 +08:00
wanglin2
9914eef5b9 修改文档 2023-01-31 15:06:24 +08:00
wanglin2
f547f741f2 1.修复删除背景图片不生效的问题;2.背景图片展示增加位置和大小设置;3.修复节点拖拽到根节点时连接线跑到根节点上方的问题 2023-01-31 15:04:38 +08:00
wanglin2
c26149fa42 修复demo设置了水印页面刷新后生效的问题 2023-01-31 13:35:46 +08:00
wanglin2
b2aa3648eb 支持当修改文档后自动重新编译生成vue组件 2023-01-31 11:15:34 +08:00
wanglin2
b997604ab2 打包0.3.0 2023-01-17 17:39:37 +08:00
wanglin2
c6929b82ad 插件化架构基本升级完成 2023-01-17 16:53:28 +08:00
wanglin2
b40da53aef 只读模式下禁止添加历史记录、禁止响应前进后退操作 2023-01-17 15:49:23 +08:00
wanglin2
1e5dfd97e1 提取多选节点Select类作为插件 2023-01-17 15:26:35 +08:00
wanglin2
97bcc22abd 提取导出类作为插件 2023-01-17 15:21:02 +08:00
wanglin2
bd655839cb 提取出键盘导航类作为插件 2023-01-17 14:39:43 +08:00
wanglin2
a53a4e8e1d 提取拖拽类Drag作为插件 2023-01-17 14:30:13 +08:00
wanglin2
eb01646747 提取水印类作为插件 2023-01-17 14:11:07 +08:00
wanglin2
d27eee0fae 开始进行插件化转换,即提取非核心的类作为插件,不再内置;抽取出校地图类插件 2023-01-17 13:56:57 +08:00
wanglin2
92ee241ed8 将xmind解析方法从MindMap类上移除,改为按需引入方式使用 2023-01-17 11:19:59 +08:00
wanglin2
660906e4c5 【春节快乐】修改文档 2023-01-17 10:55:28 +08:00
wanglin2
971f0d3446 打包0.2.24 2023-01-17 09:24:33 +08:00
wanglin2
63eccede7f 完成水印功能、新增水印文档 2023-01-17 09:20:37 +08:00
wanglin2
5bd6adb488 节点自由拖拽支持配置是否开启 2023-01-14 16:26:51 +08:00
wanglin2
fbdc5e0f39 完善文档 2023-01-14 14:44:53 +08:00
wanglin2
5e994322fe 全新文档 2023-01-14 11:33:05 +08:00
wanglin2
fd0a594b56 打包0.2.23 2023-01-12 15:50:10 +08:00
wanglin2
d7b93ba0a0 新增支持注册新主题 2023-01-12 15:45:41 +08:00
wanglin2
c14fb5ad9d 修改文档 2023-01-12 09:07:10 +08:00
wanglin2
4d82dfdf9e 修复文档错误 2023-01-11 18:01:56 +08:00
wanglin2
7d3f08bed0 去除文档中的当前版本信息 2023-01-11 16:59:52 +08:00
wanglin2
60597616ec 取消内置在simple-mind-map包内的主题和结构图片 2023-01-11 16:55:44 +08:00
wanglin2
6d758a988c 打包 2023-01-11 14:23:27 +08:00
wanglin2
503506dfd4 区分全屏查看和全屏编辑 2023-01-11 14:12:42 +08:00
wanglin2
df32655321 文档新增changelog 2023-01-11 10:25:24 +08:00
wanglin2
1c7edf47e3 文档新增常见错误解决方案 2023-01-11 09:21:50 +08:00
wanglin2
4f5f9543f7 上传package-lock.json文件 2023-01-11 09:16:45 +08:00
wanglin2
0d3c1b7417 优化注释,去掉冗余信息 2023-01-10 11:08:55 +08:00
街角小林
e634fee753 Merge pull request #56 from emircanerkul/main
Readme Translation
2023-01-10 10:05:23 +08:00
Emircan ERKUL
81c17dc643 Merge branch 'main' of github.com:emircanerkul/mind-map 2023-01-09 13:53:40 +03:00
Emircan ERKUL
cb106c7fac translate readme documentation 2023-01-09 13:48:36 +03:00
wanglin2
5a9189b7fe 修改文档 2023-01-09 14:36:30 +08:00
wanglin2
160ed0d0e4 修改文档 2023-01-05 17:12:50 +08:00
wanglin25
ed92286178 打包 2023-01-05 16:16:39 +08:00
wanglin25
a837a6fb64 新增支持节点横线风格 2023-01-05 16:12:31 +08:00
wanglin2
47328236f7 修复画布距窗口左上角不为0时节点拖拽出现偏移的问题 2022-12-30 10:52:04 +08:00
wanglin2
566147c530 修复没有激活节点时顺便按上面键都会触发自动聚焦的问题 2022-12-13 20:21:39 +08:00
wanglin2
54fad1ee08 打包 2022-12-13 20:00:03 +08:00
wanglin2
8c8e283a88 优化键盘导航寻找焦点的算法 2022-12-13 19:57:54 +08:00
wanglin2
4553bc37f5 '打包' 2022-12-09 16:56:28 +08:00
wanglin2
41b0b21354 '新增键盘导航,即通过方向键来切换激活的节点' 2022-12-09 16:53:42 +08:00
wanglin2
39b55b0cd7 支持在大纲直接编辑节点文本内容 2022-12-08 09:19:47 +08:00
wanglin2
ede01c069e 打包 2022-11-16 08:59:13 +08:00
wanglin2
aa56e53c4d 优化侧边栏显示和隐藏方式 2022-11-15 20:00:03 +08:00
wanglin2
3a723a15bf 优化备注显示 2022-11-14 19:59:21 +08:00
wanglin2
1b2da4eb72 新增禅模式 2022-11-14 19:25:25 +08:00
wanglin2
6f351d4cee 打包 2022-11-05 14:46:52 +08:00
wanglin2
dba93d4363 demo网站增加英文翻译 2022-11-05 14:42:15 +08:00
wanglin2
b46a94bd96 多语言开发中 2022-10-28 16:26:55 +08:00
wanglin2
fadd8217e8 '增加从excel导入的功能' 2022-10-24 15:16:27 +08:00
wanglin2
b0d898054e 打包 2022-10-24 10:31:35 +08:00
wanglin2
3e84892a28 修复在小屏幕下侧边栏和工具栏重叠的问题 2022-10-24 10:28:50 +08:00
wanglin2
f663f8d60a 增加eslint校验、prettier格式化 2022-10-19 16:33:09 +08:00
wanglin2
0aeee0ff28 优化小地图、优化拖拽性能 2022-10-19 09:54:38 +08:00
wanglin2
140e9cf893 '支持双击节点内图片进行大图预览' 2022-10-18 19:13:15 +08:00
wanglin2
89e426e522 优化本地文件编辑 2022-10-18 09:40:34 +08:00
wanglin2
c77062b660 打包 2022-10-17 09:25:21 +08:00
街角小林
bca2912390 Merge pull request #43 from huangyuanyin/main
优化 地图组件卸载的时候把相关事件移除
2022-10-17 09:20:38 +08:00
liuzhanghao
ed7177e29d 优化 地图组件卸载的时候把相关事件移除 2022-10-14 18:11:57 +08:00
wanglin2
12ca3ed556 Merge branch 'dev6' into main 2022-10-11 18:54:24 +08:00
wanglin2
d7de86209c 打包 2022-10-11 18:53:30 +08:00
wanglin2
e53c6cd559 优化:插入子节点时自动展开 2022-10-11 18:48:52 +08:00
wanglin2
352711c871 修复小地图关闭时报错的问题 2022-10-11 18:36:16 +08:00
wanglin2
d304ebc544 合并 2022-10-11 11:10:00 +08:00
wanglin2
27291fe5c9 更新版本 2022-10-11 11:08:58 +08:00
wanglin2
f714e47722 修改文档 2022-10-10 22:40:44 +08:00
wanglin2
ce135dd111 修改文档 2022-10-10 22:40:12 +08:00
wanglin2
fb4bfedef4 打包 2022-10-10 16:27:49 +08:00
wanglin2
5cbe5d2906 修复子节点收起状态复制时丢失的问题 2022-10-10 16:11:00 +08:00
wanglin2
b6c15164ac 更新文档 2022-10-10 15:02:22 +08:00
wanglin2
1960e96a19 打包 2022-10-10 14:39:09 +08:00
wanglin2
7a0fd5adfb 支持小地图 2022-10-10 14:31:43 +08:00
wanglin2
9ae40cff32 修改文档 2022-09-30 22:16:27 +08:00
wanglin2
0070cab9f1 打包 2022-09-30 14:48:08 +08:00
wanglin2
79ae876eeb 逻辑结构图、思维导图新增直线连接风格、直连风格 2022-09-30 14:44:40 +08:00
wanglin2
ca35b84308 逻辑结构图和思维导图支持直线连接线开发中 2022-09-25 22:07:21 +08:00
wanglin2
3b4f386900 打包 2022-09-24 20:42:53 +08:00
wanglin2
34ef1e908e 优化:手动创建节点时立即聚焦 2022-09-24 20:38:47 +08:00
wanglin2
6a3f016920 修复连线样式深度更新问题 2022-09-24 20:13:53 +08:00
wanglin2
d1ab67cd4c 支持新建、打开、 2022-09-24 17:08:11 +08:00
wanglin2
0d81f9ff9c 修改文档 2022-09-24 10:51:09 +08:00
wanglin2
471bd3c5e5 支持展开到指定层级 2022-09-24 10:49:40 +08:00
wanglin2
5d4cf8a3c3 支持展开到指定层级 2022-09-23 17:41:38 +08:00
wanglin2
5a5062ecaf 修复xmind8版本文件导入失败的问题 2022-09-23 16:16:08 +08:00
wanglin2
13c4b51f69 修改README 2022-09-21 21:02:17 +08:00
wanglin2
a3ddcc93e5 打包 2022-09-21 20:11:12 +08:00
wanglin2
365e51e2e9 支持导入.xmind文件 2022-09-21 20:07:46 +08:00
wanglin2
fd096c4444 修复根节点添加多个节点爆栈的问题 2022-09-21 09:38:41 +08:00
wanglin2
4c1786e127 导出svg增加title标签 2022-09-19 09:36:19 +08:00
wanglin2
bfeb59d43f 打包 2022-09-17 22:00:15 +08:00
wanglin2
aa998b1829 节点支持设置自定义线条样式 2022-09-17 16:18:38 +08:00
wanglin2
c8f5c94683 优化节点自定义线条样式 2022-09-17 15:09:34 +08:00
wanglin2
2aea7a3c88 节点支持自定义线条样式 2022-09-17 15:00:12 +08:00
wanglin2
9f16691cd6 修改文档 2022-09-13 22:30:45 +08:00
wanglin2
8363819933 修复节点展开收起的bug&打包 2022-09-13 10:34:20 +08:00
wanglin2
7d6149dbdf 打包 2022-09-13 09:43:18 +08:00
wanglin2
b088c40101 修改文档 2022-09-13 09:33:05 +08:00
wanglin2
5b5aab1c9e 节点支持多种形状开发完成 2022-09-12 23:07:01 +08:00
wanglin2
13a4b12ad4 打包 2022-09-01 11:11:33 +08:00
街角小林
3e59d84e6b Merge pull request #26 from huangyuanyin/main
fix 二级节点后无子节点,展开所有/收起所有操作后的报错信息
2022-09-01 11:07:38 +08:00
liuzhanghao
cd987a382a fix 2022-09-01 10:28:26 +08:00
liuzhanghao
fdada41327 fix 二级节点后无子节点,展开所有/收起所有操作后的报错信息 2022-09-01 10:22:01 +08:00
wanglin
46d44e6753 更新readme 2022-08-22 21:03:08 +08:00
wanglin2
d63d9873ac fix:修改右键菜单快捷键提示 2022-08-17 09:25:16 +08:00
wanglin2
7eeb7ab51b fix:修复右键菜单快捷键提示错误 2022-08-16 18:48:11 +08:00
wanglin2
7a2075df51 fix:修复编辑节点文本时快捷键冲突的问题 2022-08-16 16:47:43 +08:00
wanglin
ba5ff54b9a 修复输入字符串/和快捷键/冲突问题 2022-08-14 09:43:00 +08:00
wanglin
8af44b2d45 打包 2022-08-08 21:24:02 +08:00
街角小林
72c989ae11 Update README.md 2022-08-08 20:09:31 +08:00
wanglin2
861309d517 支持导出为pdf 2022-08-08 20:06:59 +08:00
wanglin
2f0d64cf41 打包&发布新版本 2022-08-06 09:21:18 +08:00
wanglin2
5df8a28403 支持Ctrl+左键多选 2022-08-05 17:21:21 +08:00
wanglin2
3c9940e076 Merge branch 'dev' of https://github.com/wanglin2/mind-map into dev 2022-08-05 09:30:19 +08:00
wanglin2
0b11aff105 新增库打包&修改文档 2022-08-05 09:29:59 +08:00
wanglin
3e4fe06197 增加图标 2022-08-04 20:24:57 +08:00
wanglin2
8bf23f8397 修改文档 2022-08-04 15:06:07 +08:00
wanglin2
10d04549fc 新增快捷键 2022-08-04 14:48:52 +08:00
wanglin2
b4193d50a3 支持自由拖拽、修复一些bug 2022-08-04 11:39:17 +08:00
wanglin2
0620d31d0a 修复概要的一些bug 2022-08-02 08:59:05 +08:00
wanglin2
a804a5e2fa 修复拖拽节点时为隐藏概要内容的问题 2022-08-01 09:59:43 +08:00
wanglin2
06daffbaab 修复初始渲染时激活节点没有触发页面效果的问题 2022-08-01 09:51:53 +08:00
wanglin2
b901c08156 修复概要展开收起的定位错误问题 2022-08-01 09:36:25 +08:00
wanglin
e27eb63627 新增方法 2022-07-31 22:29:40 +08:00
wanglin
990a4e5c4c 修复概要定位问题 2022-07-31 20:46:40 +08:00
wanglin
7f79d4881d 修改及新增主题 2022-07-31 14:54:32 +08:00
wanglin
2d8643015f 概要开发中 2022-07-31 10:28:13 +08:00
wanglin
30f7713af1 更新版本 2022-07-30 08:21:43 +08:00
街角小林
7ab9db4bbd Merge pull request #16 from harris2012/patch-2
fix typo
2022-07-29 21:59:53 +08:00
街角小林
fa7548afaf Merge pull request #15 from harris2012/patch-1
Update Node.js
2022-07-29 21:57:05 +08:00
Harris Zhang
676efa792a fix typo 2022-07-29 17:55:20 +08:00
Harris Zhang
9ffa1e2744 Update Node.js 2022-07-29 11:29:25 +08:00
街角小林
1108b29da0 Merge pull request #13 from harris2012/patch-1
Update index.js
2022-07-26 21:15:41 +08:00
Harris Zhang
b00d5f6adb Update index.js 2022-07-26 19:12:47 +08:00
wanglin2
c6438668f2 fix:1.节点图标不能删除的问题;2.工具按钮置灰仍然可以点击的问题 2022-06-28 19:55:31 +08:00
wanglin2
608adb21e1 增加只读模式 2022-06-08 14:30:45 +08:00
wanglin
495cb4c62a 修改README 2022-05-10 23:09:54 +08:00
wanglin
3048cba1ae 发布0.1.6版本 2022-05-10 23:08:02 +08:00
wanglin2
5b1f5f803e 打包 2022-05-09 14:31:22 +08:00
wanglin2
0d79e28ec9 节点备注支持markdown及富文本、修复不能选中文字的问题 2022-05-09 14:24:11 +08:00
wanglin2
806be0b537 修复节点标注在节点激活后无法隐藏问题 2022-05-09 11:31:05 +08:00
wanglin2
c15b9be7ef 修复超链接、备注、标签等文字编辑时返回键和回车键与思维导图快捷键冲突的问题 2022-05-09 11:03:56 +08:00
wanglin
f6ac2c80ed 增加版本号 2021-11-25 22:25:26 +08:00
wanglin2
5f19509079 新增支持节点拖拽 2021-11-25 16:06:18 +08:00
wanglin2
4677b11dfb '状态数据支持保存激活状态、视图状态(拖动位置、缩放值)' 2021-11-22 19:59:21 +08:00
wanglin
b3e9797b0e 修复存在激活节点时设置主题存在的问题 2021-08-05 22:29:02 +08:00
wanglin
f72deb0478 升级版本 2021-08-05 08:09:48 +08:00
wanglin
455e97074f 增加快捷键功能 2021-08-05 00:06:27 +08:00
wanglin
3e52fa6585 修复回退问题 2021-08-04 23:45:05 +08:00
wanglin
48172bc035 新增导出为json;优化一些细节 2021-08-04 07:26:01 +08:00
wanglin
09d1f82021 修改版本号及package.json 2021-08-01 11:58:08 +08:00
wanglin
786e183091 增加本地存储功能 2021-08-01 10:49:57 +08:00
wanglin2
0ebac39716 修复删除节点和节点编辑时的删除冲突问题 2021-07-27 14:17:58 +08:00
wanglin2
10b8a33ab7 '修改文章与打包' 2021-07-22 19:06:51 +08:00
wanglin2
72b9001b56 '打包' 2021-07-22 14:13:49 +08:00
wanglin2
1efabd44ec '打包' 2021-07-22 09:36:50 +08:00
wanglin
5e66bd29ff 优化 2021-07-22 08:03:00 +08:00
wanglin
2f1114b722 修改文章 2021-07-21 22:50:46 +08:00
wanglin2
4428028146 '完成文章' 2021-07-21 20:24:37 +08:00
wanglin2
e660a74630 文章写作中 2021-07-20 20:18:03 +08:00
wanglin
8f29d63441 文章 2021-07-20 07:48:22 +08:00
wanglin
98afa6eb5b 修改文章 2021-07-19 07:39:34 +08:00
wanglin
c06971987d 添加图片 2021-07-18 22:47:03 +08:00
wanglin
e385ecf35d 优化与文档编写 2021-07-18 22:27:34 +08:00
wanglin
62fb0b15e0 完善开发文档 2021-07-16 19:33:01 +08:00
wanglin
19da1a4ff3 优化代码 2021-07-16 13:58:48 +08:00
wanglin
a798a40fab 基本完成 2021-07-16 13:42:37 +08:00
wanglin2
7982a1373f '右键功能开发中' 2021-07-15 17:46:36 +08:00
wanglin2
a812637cf0 Merge branch 'main' of https://github.com/wanglin2/mind-map into main 2021-07-15 09:46:53 +08:00
wanglin2
53c4d3945a '优化' 2021-07-15 09:46:27 +08:00
wanglin
2e7519cf29 右键菜单开发中 2021-07-14 23:57:36 +08:00
wanglin2
acad210d57 '修改样式' 2021-07-14 09:54:45 +08:00
wanglin2
ba5807932e '打包' 2021-07-14 09:34:13 +08:00
wanglin
59d572ae18 修改配置 2021-07-14 08:13:23 +08:00
wanglin
14aa8659ee 修改配置 2021-07-14 08:09:23 +08:00
wanglin
59c8ed4ef8 打包 2021-07-14 07:38:30 +08:00
wanglin
b886a0572d 修改打包配置i 2021-07-14 07:34:06 +08:00
wanglin
42755cac8a 修改 2021-07-14 07:28:31 +08:00
wanglin
ab1306357e 上传打包文件 2021-07-13 23:29:22 +08:00
wanglin
fa506f4212 修改README 2021-07-13 23:20:41 +08:00
wanglin2
364aed858f '基本完成' 2021-07-13 16:39:50 +08:00
wanglin
58703597e3 优化 2021-07-13 08:04:47 +08:00
wanglin
5d4b42d519 新增主题 2021-07-12 22:49:22 +08:00
wanglin2
cd55db7ed7 '完成部分结构' 2021-07-12 20:12:21 +08:00
wanglin2
ca9b3678dc '优化及增加主题' 2021-07-12 14:55:50 +08:00
wanglin
395e802b6b 回退功能开发中 2021-07-12 07:56:38 +08:00
wanglin
f38b92a2e2 增加快捷键和全屏功能 2021-07-11 22:21:40 +08:00
wanglin
5c6fef71bd 完成多选操作 2021-07-11 13:33:06 +08:00
wanglin
f3780baedc 完成基本逻辑 2021-07-10 22:06:45 +08:00
wanglin
388656f6e0 日常提交 2021-07-04 22:53:36 +08:00
wanglin
df60f103cc 完成导出功能 2021-07-04 16:56:37 +08:00
wanglin
7a977d74dc 日常提交 2021-06-28 07:46:12 +08:00
wanglin
7336c57e80 提交 2021-06-26 00:03:39 +08:00
wanglin
66963ca3ac 完成节点内容设置及主题 2021-06-26 00:01:03 +08:00
wanglin2
1bb1afd1b7 Delete .DS_Store 2021-06-20 23:05:11 +08:00
wanglin2
52cfc40c24 Delete .DS_Store 2021-06-20 23:04:56 +08:00
wanglin
debb058889 完成节点内容布局 2021-06-20 23:03:23 +08:00
wanglin2
3d5e3ac9a0 修改忽略文件 2021-06-19 14:24:52 +08:00
wanglin2
a06cb2e031 重构 2021-06-19 14:04:05 +08:00
326 changed files with 62974 additions and 5233 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
node_modules
oss.js
.DS_Store
dist_electron

View File

@@ -1 +1,63 @@
开发中...
<h1 align="center">Simple mind map</h1>
[![npm-version](https://img.shields.io/npm/v/simple-mind-map)](https://www.npmjs.com/package/simple-mind-map)
![npm download](https://img.shields.io/npm/dm/simple-mind-map)
[![GitHub stars](https://img.shields.io/github/stars/wanglin2/mind-map)](https://github.com/wanglin2/mind-map/stargazers)
[![GitHub issues](https://img.shields.io/github/issues/wanglin2/mind-map)](https://github.com/wanglin2/mind-map/issues)
[![GitHub forks](https://img.shields.io/github/forks/wanglin2/mind-map)](https://github.com/wanglin2/mind-map/network/members)
![license](https://img.shields.io/npm/l/express.svg)
> 一个简单&强大的Web思维导图库
Demo[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)
文档:[https://wanglin2.github.io/mind-map/#/doc/zh/](https://wanglin2.github.io/mind-map/#/doc/zh/)
# 特性
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图四种结构
- [x] 内置多种主题,允许高度自定义样式,支持注册新主题
- [x] 支持快捷键
- [x] 节点内容支持图片、图标、超链接、备注、标签、概要
- [x] 支持前进后退
- [x] 支持拖动、缩放
- [x] 支持右键和Ctrl+左键两种多选方式
- [x] 支持节点自由拖拽、拖拽调整
- [x] 支持多种节点形状
- [x] 支持导出为`json``png``svg``pdf`,支持从`json``xmind`导入
- [x] 支持小地图
- [x] 支持关联线
# 安装
```bash
npm i simple-mind-map
```
# 使用
```html
<div id="mindMapContainer"></div>
```
```js
import MindMap from "simple-mind-map";
const mindMap = new MindMap({
el: document.getElementById('mindMapContainer'),
data: {
"data": {
"text": "根节点"
},
"children": []
}
});
```
# License
MIT
# 微信交流群
![](./qrcode.jpg)

15
copy.js Normal file
View File

@@ -0,0 +1,15 @@
const fs = require('fs')
const path = require('path')
const src = path.resolve(__dirname, './dist/index.html')
const dest = path.resolve(__dirname, './index.html')
if (fs.existsSync(dest)) {
fs.unlinkSync(dest)
}
if (fs.existsSync(src)) {
fs.copyFileSync(src, dest)
fs.unlinkSync(src)
}

1
index.html Normal file
View File

@@ -0,0 +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-2d0a514a.64cc7822.js" rel="prefetch"><link href="dist/js/chunk-2d0aa579.61afd8c1.js" rel="prefetch"><link href="dist/js/chunk-2d0aa978.84fc06da.js" rel="prefetch"><link href="dist/js/chunk-2d0ab10b.4d405645.js" rel="prefetch"><link href="dist/js/chunk-2d0abe0f.a9abff6a.js" rel="prefetch"><link href="dist/js/chunk-2d0b1c6f.b8417307.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.a3915823.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.0f9ebf1b.js" rel="prefetch"><link href="dist/js/chunk-2d0c14fc.7163274e.js" rel="prefetch"><link href="dist/js/chunk-2d0c18d8.2e6ddc70.js" rel="prefetch"><link href="dist/js/chunk-2d0c191e.cd5e6df3.js" rel="prefetch"><link href="dist/js/chunk-2d0c1a01.2e73bf0a.js" rel="prefetch"><link href="dist/js/chunk-2d0c20be.dab092ff.js" rel="prefetch"><link href="dist/js/chunk-2d0d5cb9.2e73ab77.js" rel="prefetch"><link href="dist/js/chunk-2d0d9fbc.4800807d.js" rel="prefetch"><link href="dist/js/chunk-2d0da701.80759043.js" rel="prefetch"><link href="dist/js/chunk-2d0dad5f.f670c26a.js" rel="prefetch"><link href="dist/js/chunk-2d0db0f2.b5ce4946.js" rel="prefetch"><link href="dist/js/chunk-2d0dd3b1.c4647d07.js" rel="prefetch"><link href="dist/js/chunk-2d0dddce.3eea98de.js" rel="prefetch"><link href="dist/js/chunk-2d0ddf37.d960b122.js" rel="prefetch"><link href="dist/js/chunk-2d0de01b.a2a047cf.js" rel="prefetch"><link href="dist/js/chunk-2d0e2326.5072607c.js" rel="prefetch"><link href="dist/js/chunk-2d0e268c.ed75c510.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.4757f6bb.js" rel="prefetch"><link href="dist/js/chunk-2d2082b9.f52387a2.js" rel="prefetch"><link href="dist/js/chunk-2d208ffa.766e8fa6.js" rel="prefetch"><link href="dist/js/chunk-2d20ec02.917aff76.js" rel="prefetch"><link href="dist/js/chunk-2d20f68f.6c1b14e1.js" rel="prefetch"><link href="dist/js/chunk-2d210a7a.6a4911c4.js" rel="prefetch"><link href="dist/js/chunk-2d216004.704073c5.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.9f7bbd4c.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.3b1827be.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.c097b26d.css" rel="preload" as="style"><link href="dist/js/app.e9e06d36.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.49b07a77.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.c097b26d.css" rel="stylesheet"><link href="dist/css/app.3b1827be.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.49b07a77.js"></script><script src="dist/js/app.e9e06d36.js"></script></body></html>

BIN
qrcode.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -0,0 +1,17 @@
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": "eslint:recommended",
"overrides": [
],
"parserOptions": {
"parser": 'babel-eslint',
"ecmaVersion": 12,
"sourceType": "module",
"allowImportExportEverywhere": true
},
"rules": {
}
}

View File

@@ -0,0 +1,10 @@
src/assets
*/.DS_Store
dist
example
node_modules
*.json
*.md
.eslintrc.js
.prettierignore
.prettierrc

View File

@@ -0,0 +1,5 @@
semi: false
singleQuote: true
printWidth: 80
trailingComma: 'none'
arrowParens: 'avoid'

View File

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

View File

@@ -0,0 +1,941 @@
const createFullData = () => {
return {
"image": "/enJFNMHnedQTYTESGfDkctCp2.jpeg",
"imageTitle": "图片名称",
"imageSize": {
"width": 1000,
"height": 563
},
"icon": ['priority_1'],
"tag": ["标签1", "标签2"],
"hyperlink": "http://lxqnsys.com/",
"hyperlinkTitle": "理想青年实验室",
"note": "理想青年实验室\n一个有意思的角落",
// 自定义位置
// "customLeft": 1318,
// "customTop": 374.5
};
}
/**
* @Author: 王林
* @Date: 2021-04-15 22:23:24
* @Desc: 节点较多示例数据
*/
const data1 = {
"root": {
"data": {
"text": "根节点"
},
"children": [
{
"data": {
"text": "二级节点1",
"expand": true,
},
"children": [{
"data": {
"text": "分支主题",
...createFullData()
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
...createFullData()
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}]
},
{
"data": {
"text": "二级节点2",
"expand": true,
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
},
{
"data": {
"text": "二级节点3",
"expand": true,
},
"children": [{
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}]
},
{
"data": {
"text": "二级节点4",
"expand": true,
},
"children": [{
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}]
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}, {
"data": {
"text": "分支主题",
},
"children": [{
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}, {
"data": {
"text": "分支主题",
},
}]
}]
}]
}]
},
]
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-12 13:49:43
* @Desc: 真实场景数据
*/
const data2 = {
"root": {
"data": {
"text": "一周安排"
},
"children": [
{
"data": {
"text": "生活"
},
"children": [
{
"data": {
"text": "锻炼"
},
"children": [
{
"data": {
"text": "晨跑"
},
"children": [
{
"data": {
"text": "7:00-8:00"
},
"children": []
}
]
},
{
"data": {
"text": "夜跑"
},
"children": [
{
"data": {
"text": "20:00-21:00"
},
"children": []
}
]
}
]
},
{
"data": {
"text": "饮食"
},
"children": [
{
"data": {
"text": "早餐"
},
"children": [
{
"data": {
"text": "8:30"
},
"children": []
}
]
},
{
"data": {
"text": "午餐"
},
"children": [
{
"data": {
"text": "11:30"
},
"children": []
}
]
},
{
"data": {
"text": "晚餐"
},
"children": [
{
"data": {
"text": "19:00"
},
"children": []
}
]
}
]
},
{
"data": {
"text": "休息"
},
"children": [
{
"data": {
"text": "午休"
},
"children": [
{
"data": {
"text": "12:30-13:00"
},
"children": []
}
]
},
{
"data": {
"text": "晚休"
},
"children": [
{
"data": {
"text": "23:00-6:30"
},
"children": []
}
]
}
]
}
]
},
{
"data": {
"text": "工作日\n周一至周五"
},
"children": [
{
"data": {
"text": "日常工作"
},
"children": [
{
"data": {
"text": "9:00-18:00"
},
"children": []
}
]
},
{
"data": {
"text": "工作总结"
},
"children": [
{
"data": {
"text": "21:00-22:00"
},
"children": []
}
]
}
]
},
{
"data": {
"text": "学习"
},
"children": [
{
"data": {
"text": "工作日"
},
"children": [
{
"data": {
"text": "早间新闻"
},
"children": [
{
"data": {
"text": "8:00-8:30"
},
"children": []
}
]
},
{
"data": {
"text": "阅读"
},
"children": [
{
"data": {
"text": "21:00-23:00"
},
"children": []
}
]
}
]
},
{
"data": {
"text": "休息日"
},
"children": [
{
"data": {
"text": "财务管理"
},
"children": [
{
"data": {
"text": "9:00-10:30"
},
"children": []
}
]
},
{
"data": {
"text": "职场技能"
},
"children": [
{
"data": {
"text": "14:00-15:30"
},
"children": []
}
]
},
{
"data": {
"text": "其他书籍"
},
"children": [
{
"data": {
"text": "16:00-18:00"
},
"children": []
}
]
}
]
}
]
},
{
"data": {
"text": "休闲娱乐"
},
"children": [
{
"data": {
"text": "看电影"
},
"children": [
{
"data": {
"text": "1~2部"
},
"children": []
}
]
},
{
"data": {
"text": "逛街"
},
"children": [
{
"data": {
"text": "1~2次"
},
"children": []
}
]
}
]
}
]
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-07-12 14:29:10
* @Desc: 极简数据
*/
const data3 = {
"root": {
"data": {
"text": "根节点"
},
"children": [
{
"data": {
"text": "二级节点"
},
"children": [
{
"data": {
"text": "分支主题"
},
"children": []
},
{
"data": {
"text": "分支主题"
},
"children": []
}
]
}
]
}
}
const data4 = {
"root": {
"data": {
"text": "根节点"
},
"children": [
{
"data": {
"text": "二级节点1"
},
"children": [
{
"data": {
"text": "子节点1-1"
},
"children": []
},
{
"data": {
"text": "子节点1-2"
},
"children": [
{
"data": {
"text": "子节点1-2-1"
},
"children": []
},
{
"data": {
"text": "子节点1-2-2"
},
"children": []
},
{
"data": {
"text": "子节点1-2-3"
},
"children": []
}
]
}
]
},
{
"data": {
"text": "二级节点2"
},
"children": [
{
"data": {
"text": "子节点2-1"
},
"children": [
{
"data": {
"text": "子节点2-1-1"
},
"children": [
{
"data": {
"text": "子节点2-1-1-1"
},
"children": []
},
]
}
]
},
{
"data": {
"text": "子节点2-2"
},
"children": []
}
]
}
]
}
}
// 带概要
const data5 = {
"root": {
"data": {
"text": "根节点"
},
"children": [
{
"data": {
"text": "二级节点",
"generalization": {
"text": "概要",
}
},
"children": [
{
"data": {
"text": "分支主题"
},
"children": []
},
{
"data": {
"text": "分支主题"
},
"children": []
}
]
},
]
}
}
// 富文本数据v0.4.0+需要使用RichText插件才支持富文本编辑
const richTextData = {
"root": {
"data": {
"text": "<a href='http://lxqnsys.com/' target='_blank'>理想去年实验室</a>",
"richText": true
},
"children": []
}
}
const rootData = {
"root": {
"data": {
"text": "根节点"
},
"children": []
}
}
export default {
// ...data1,
// ...data2,
// ...data3,
// ...data4,
...data5,
// ...rootData,
"theme": {
"template": "classic4",
"config": {
// 自定义配置...
}
},
"layout": "logicalStructure",
// "layout": "mindMap",
// "layout": "catalogOrganization"
// "layout": "organizationStructure",
"config": {}
}

View File

@@ -0,0 +1,73 @@
{
"layout": "logicalStructure",
"root": {
"data": {
"text": "根节点",
"expand": true,
"isActive": false
},
"children": [{
"data": {
"text": "二级节点",
"generalization": {
"text": "概要",
"expand": true,
"isActive": false
},
"expand": true,
"isActive": false
},
"children": [{
"data": {
"text": "分支主题",
"expand": true,
"isActive": false
},
"children": []
}, {
"data": {
"text": "分支主题",
"expand": true,
"isActive": false
},
"children": []
}, {
"data": {
"text": "<a href='http://lxqnsys.com/' target='_blank'>理想去年实验室</a>",
"richText": true
},
"children": []
}]
}]
},
"theme": {
"template": "classic4",
"config": {}
},
"view": {
"transform": {
"scaleX": 1,
"scaleY": 1,
"shear": 0,
"rotate": 0,
"translateX": 0,
"translateY": 0,
"originX": 0,
"originY": 0,
"a": 1,
"b": 0,
"c": 0,
"d": 1,
"e": 0,
"f": 0
},
"state": {
"scale": 1,
"x": 0,
"y": 0,
"sx": 0,
"sy": 0
}
},
"config": {}
}

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

@@ -0,0 +1,26 @@
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 AssociativeLine from './src/AssociativeLine'
import RichText from './src/RichText'
import xmind from './src/parse/xmind.js'
import markdown from './src/parse/markdown.js'
MindMap.xmind = xmind
MindMap.markdown = markdown
MindMap
.usePlugin(MiniMap)
.usePlugin(Watermark)
.usePlugin(Drag)
.usePlugin(KeyboardNavigation)
.usePlugin(Export)
.usePlugin(Select)
.usePlugin(AssociativeLine)
.usePlugin(RichText)
export default MindMap

440
simple-mind-map/index.js Normal file
View File

@@ -0,0 +1,440 @@
import View from './src/View'
import Event from './src/Event'
import Render from './src/Render'
import merge from 'deepmerge'
import theme from './src/themes'
import Style from './src/Style'
import KeyCommand from './src/KeyCommand'
import Command from './src/Command'
import BatchExecution from './src/BatchExecution'
import { layoutValueList } from './src/utils/constant'
import { SVG } from '@svgdotjs/svg.js'
import { simpleDeepClone } from './src/utils'
import defaultTheme from './src/themes/default'
// 默认选项配置
const defaultOpt = {
// 是否只读
readonly: false,
// 布局
layout: 'logicalStructure',
// 主题
theme: 'default', // 内置主题default默认主题
// 主题配置,会和所选择的主题进行合并
themeConfig: {},
// 放大缩小的增量比例
scaleRatio: 0.1,
// 最多显示几个标签
maxTag: 5,
// 导出图片时的内边距
exportPadding: 20,
// 展开收缩按钮尺寸
expandBtnSize: 20,
// 节点里图片和文字的间距
imgTextMargin: 5,
// 节点里各种文字信息的间距,如图标和文字的间距
textContentMargin: 2,
// 多选节点时鼠标移动到边缘时的画布移动偏移量
selectTranslateStep: 3,
// 多选节点时鼠标移动距边缘多少距离时开始偏移
selectTranslateLimit: 20,
// 自定义节点备注内容显示
customNoteContentShow: null,
/*
{
show(){},
hide(){}
}
*/
// 是否开启节点自由拖拽
enableFreeDrag: false,
// 水印配置
watermarkConfig: {
text: '',
lineSpacing: 100,
textSpacing: 100,
angle: 30,
textStyle: {
color: '#999',
opacity: 0.5,
fontSize: 14
}
},
// 达到该宽度文本自动换行
textAutoWrapWidth: 500,
// 自定义鼠标滚轮事件处理
// 可以传一个函数,回调参数为事件对象
customHandleMousewheel: null,
// 鼠标滚动的行为如果customHandleMousewheel传了自定义函数这个属性不生效
mousewheelAction: 'zoom',// zoom放大缩小、move上下移动
// 当mousewheelAction设为move时可以通过该属性控制鼠标滚动一下视图移动的步长单位px
mousewheelMoveStep: 100,
// 默认插入的二级节点的文字
defaultInsertSecondLevelNodeText: '二级节点',
// 默认插入的二级以下节点的文字
defaultInsertBelowSecondLevelNodeText: '分支主题'
}
// 思维导图
class MindMap {
// 构造函数
constructor(opt = {}) {
// 合并选项
this.opt = this.handleOpt(merge(defaultOpt, opt))
// 容器元素
this.el = this.opt.el
this.elRect = this.el.getBoundingClientRect()
// 画布宽高
this.width = this.elRect.width
this.height = this.elRect.height
// 画布
this.svg = SVG().addTo(this.el).size(this.width, this.height)
this.draw = this.svg.group()
// 节点id
this.uid = 0
// 初始化主题
this.initTheme()
// 事件类
this.event = new Event({
mindMap: this
})
// 按键类
this.keyCommand = new KeyCommand({
mindMap: this
})
// 命令类
this.command = new Command({
mindMap: this
})
// 渲染类
this.renderer = new Render({
mindMap: this
})
// 视图操作类
this.view = new View({
mindMap: this,
draw: this.draw
})
// 批量执行类
this.batchExecution = new BatchExecution()
// 注册插件
MindMap.pluginList.forEach((plugin) => {
this.initPlugin(plugin)
})
// 初始渲染
this.reRender()
setTimeout(() => {
this.command.addHistory()
}, 0)
}
// 配置参数处理
handleOpt(opt) {
// 检查布局配置
if (!layoutValueList.includes(opt.layout)) {
opt.layout = 'logicalStructure'
}
// 检查主题配置
opt.theme = opt.theme && theme[opt.theme] ? opt.theme : 'default'
return opt
}
// 渲染,部分渲染
render(callback) {
this.batchExecution.push('render', () => {
this.initTheme()
this.renderer.reRender = false
this.renderer.render(callback)
})
}
// 重新渲染
reRender(callback) {
this.batchExecution.push('render', () => {
this.draw.clear()
this.initTheme()
this.renderer.reRender = true
this.renderer.render(callback)
})
}
// 容器尺寸变化,调整尺寸
resize() {
this.elRect = this.el.getBoundingClientRect()
this.width = this.elRect.width
this.height = this.elRect.height
this.svg.size(this.width, this.height)
}
// 监听事件
on(event, fn) {
this.event.on(event, fn)
}
// 触发事件
emit(event, ...args) {
this.event.emit(event, ...args)
}
// 解绑事件
off(event, fn) {
this.event.off(event, fn)
}
// 设置主题
initTheme() {
// 合并主题配置
this.themeConfig = merge(theme[this.opt.theme], this.opt.themeConfig)
// 设置背景样式
Style.setBackgroundStyle(this.el, this.themeConfig)
}
// 设置主题
setTheme(theme) {
this.renderer.clearAllActive()
this.opt.theme = theme
this.reRender()
}
// 获取当前主题
getTheme() {
return this.opt.theme
}
// 设置主题配置
setThemeConfig(config) {
this.opt.themeConfig = config
this.reRender()
}
// 获取自定义主题配置
getCustomThemeConfig() {
return this.opt.themeConfig
}
// 获取某个主题配置值
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]))
}
// 获取当前布局结构
getLayout() {
return this.opt.layout
}
// 设置布局结构
setLayout(layout) {
// 检查布局配置
if (!layoutValueList.includes(layout)) {
layout = 'logicalStructure'
}
this.opt.layout = layout
this.renderer.setLayout()
this.render()
}
// 执行命令
execCommand(...args) {
this.command.exec(...args)
}
// 动态设置思维导图数据,纯节点数据
setData(data) {
this.execCommand('CLEAR_ACTIVE_NODE')
this.command.clearHistory()
this.renderer.renderTree = data
this.reRender()
}
// 动态设置思维导图数据,包括节点数据、布局、主题、视图
setFullData(data) {
if (data.root) {
this.setData(data.root)
}
if (data.layout) {
this.setLayout(data.layout)
}
if (data.theme) {
if (data.theme.template) {
this.setTheme(data.theme.template)
}
if (data.theme.config) {
this.setThemeConfig(data.theme.config)
}
}
if (data.view) {
this.view.setTransformData(data.view)
}
}
// 获取思维导图数据,节点树、主题、布局等
getData(withConfig) {
let nodeData = this.command.getCopyData()
let data = {}
if (withConfig) {
data = {
layout: this.getLayout(),
root: nodeData,
theme: {
template: this.getTheme(),
config: this.getCustomThemeConfig()
},
view: this.view.getTransformData()
}
} else {
data = nodeData
}
return simpleDeepClone(data)
}
// 导出
async export(...args) {
let result = await this.doExport.export(...args)
return result
}
// 转换位置
toPos(x, y) {
return {
x: x - this.elRect.left,
y: y - this.elRect.top
}
}
// 设置只读模式、编辑模式
setMode(mode) {
if (!['readonly', 'edit'].includes(mode)) {
return
}
this.opt.readonly = mode === 'readonly'
if (this.opt.readonly) {
// 取消当前激活的元素
this.renderer.clearAllActive()
}
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)
}
export default MindMap

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
{
"name": "simple-mind-map",
"version": "0.4.7",
"description": "一个简单的web在线思维导图",
"authors": [
{
"name": "街角小林",
"email": "1013335014@qq.com"
},
{
"name": "理想青年实验室",
"url": "http://lxqnsys.com/"
}
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/wanglin2/mind-map"
},
"scripts": {
"lint": "eslint src/",
"format": "prettier --write ."
},
"module": "index.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",
"mdast-util-from-markdown": "^1.3.0",
"quill": "^1.3.6",
"uuid": "^9.0.0",
"xml-js": "^1.6.11"
},
"keywords": [
"javascript",
"svg",
"mind-map",
"mindMap",
"MindMap"
],
"devDependencies": {
"eslint": "^8.25.0",
"prettier": "^2.7.1"
}
}

View File

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

View File

@@ -0,0 +1,627 @@
import { walk, bfsWalk, throttle } from './utils/'
import { v4 as uuid } from 'uuid'
import {
getAssociativeLineTargetIndex,
computeCubicBezierPathPoints,
joinCubicBezierPath,
cubicBezierPath,
getNodePoint,
computeNodePoints,
getNodeLinePath,
getDefaultControlPointOffsets
} from './utils/associativeLineUtils'
// 关联线类
class AssociativeLine {
constructor(opt = {}) {
this.mindMap = opt.mindMap
this.draw = this.mindMap.draw
// 当前所有连接线
this.lineList = []
// 当前激活的连接线
this.activeLine = null
// 当前正在创建连接线
this.isCreatingLine = false // 是否正在创建连接线中
this.creatingStartNode = null // 起始节点
this.creatingLine = null // 创建过程中的连接线
this.overlapNode = null // 创建过程中的目标节点
// 是否有节点正在被拖拽
this.isNodeDragging = false
// 箭头图标
this.markerPath = null
this.marker = this.createMarker()
// 控制点
this.controlLine1 = null
this.controlLine2 = null
this.controlPoint1 = null
this.controlPoint2 = null
this.controlPointDiameter = 10
this.isControlPointMousedown = false
this.mousedownControlPointKey = ''
this.controlPointMousemoveState = {
pos: null,
startPoint: null,
endPoint: null,
targetIndex: ''
}
// 节流一下,不然很卡
this.checkOverlapNode = throttle(this.checkOverlapNode, 100, this)
this.bindEvent()
}
// 监听事件
bindEvent() {
// 节点树渲染完毕后渲染连接线
this.renderAllLines = this.renderAllLines.bind(this)
this.mindMap.on('node_tree_render_end', this.renderAllLines)
// 状态改变后重新渲染连接线
this.mindMap.on('data_change', this.renderAllLines)
// 监听画布和节点点击事件,用于清除当前激活的连接线
this.mindMap.on('draw_click', () => {
if (this.isControlPointMousedown) {
return
}
this.clearActiveLine()
})
this.mindMap.on('node_click', node => {
if (this.isCreatingLine) {
this.completeCreateLine(node)
} else {
this.clearActiveLine()
}
})
// 注册删除快捷键
this.mindMap.keyCommand.addShortcut(
'Del|Backspace',
this.removeLine.bind(this)
)
// 注册添加连接线的命令
this.mindMap.command.add('ADD_ASSOCIATIVE_LINE', this.addLine.bind(this))
// 监听鼠标移动事件
this.mindMap.on('mousemove', this.onMousemove.bind(this))
// 节点拖拽事件
this.mindMap.on('node_dragging', this.onNodeDragging.bind(this))
this.mindMap.on('node_dragend', this.onNodeDragend.bind(this))
// 拖拽控制点
window.addEventListener('mousemove', e => {
this.onControlPointMousemove(e)
})
window.addEventListener('mouseup', e => {
this.onControlPointMouseup(e)
})
}
// 创建箭头
createMarker() {
return this.draw.marker(20, 20, add => {
add.ref(2, 5)
add.size(10, 10)
add.attr('orient', 'auto-start-reverse')
this.markerPath = add.path('M0,0 L2,5 L0,10 L10,5 Z')
})
}
// 渲染所有连线
renderAllLines() {
// 先移除
this.removeAllLines()
this.removeControls()
this.clearActiveLine()
let tree = this.mindMap.renderer.root
if (!tree) return
let idToNode = new Map()
let nodeToIds = new Map()
walk(
tree,
null,
cur => {
if (!cur) return
let data = cur.nodeData.data
if (
data.associativeLineTargets &&
data.associativeLineTargets.length > 0
) {
nodeToIds.set(cur, data.associativeLineTargets)
}
if (data.id) {
idToNode.set(data.id, cur)
}
},
() => {},
true,
0
)
nodeToIds.forEach((ids, node) => {
ids.forEach(id => {
let toNode = idToNode.get(id)
if (!node || !toNode) return
let [startPoint, endPoint] = computeNodePoints(node, toNode)
this.drawLine(startPoint, endPoint, node, toNode)
})
})
}
// 绘制连接线
drawLine(startPoint, endPoint, node, toNode) {
let {
associativeLineWidth,
associativeLineColor,
associativeLineActiveWidth,
associativeLineActiveColor
} = this.mindMap.themeConfig
// 箭头
this.markerPath
.stroke({ color: associativeLineColor })
.fill({ color: associativeLineColor })
// 路径
let { path: pathStr, controlPoints } = getNodeLinePath(
startPoint,
endPoint,
node,
toNode
)
// 虚线
let path = this.draw.path()
path
.stroke({
width: associativeLineWidth,
color: associativeLineColor,
dasharray: [6, 4]
})
.fill({ color: 'none' })
path.plot(pathStr)
path.marker('end', this.marker)
// 不可见的点击线
let clickPath = this.draw.path()
clickPath
.stroke({ width: associativeLineActiveWidth, color: 'transparent' })
.fill({ color: 'none' })
clickPath.plot(pathStr)
// 点击事件
clickPath.click(e => {
e.stopPropagation()
// 如果当前存在激活节点,那么取消激活节点
if (this.mindMap.renderer.activeNodeList.length > 0) {
this.clearActiveNodes()
} else {
// 否则清除当前的关联线的激活状态,如果有的话
this.clearActiveLine()
// 保存当前激活的关联线信息
this.activeLine = [path, clickPath, node, toNode]
// 让不可见的点击线显示
clickPath.stroke({ color: associativeLineActiveColor })
// 渲染控制点和连线
this.renderControls(
startPoint,
endPoint,
controlPoints[0],
controlPoints[1]
)
this.mindMap.emit(
'associative_line_click',
path,
clickPath,
node,
toNode
)
}
})
this.lineList.push([path, clickPath, node, toNode])
}
// 移除所有连接线
removeAllLines() {
this.lineList.forEach(line => {
line[0].remove()
line[1].remove()
})
this.lineList = []
}
// 从当前激活节点开始创建连接线
createLineFromActiveNode() {
if (this.mindMap.renderer.activeNodeList.length <= 0) return
let node = this.mindMap.renderer.activeNodeList[0]
this.createLine(node)
}
// 创建连接线
createLine(fromNode) {
let { associativeLineWidth, associativeLineColor } =
this.mindMap.themeConfig
if (this.isCreatingLine || !fromNode) return
this.isCreatingLine = true
this.creatingStartNode = fromNode
this.creatingLine = this.draw.path()
this.creatingLine
.stroke({
width: associativeLineWidth,
color: associativeLineColor,
dasharray: [6, 4]
})
.fill({ color: 'none' })
this.creatingLine.marker('end', this.marker)
}
// 鼠标移动事件
onMousemove(e) {
if (!this.isCreatingLine) return
this.updateCreatingLine(e)
}
// 更新创建过程中的连接线
updateCreatingLine(e) {
let { x, y } = this.getTransformedEventPos(e)
let startPoint = getNodePoint(this.creatingStartNode)
let pathStr = cubicBezierPath(startPoint.x, startPoint.y, x, y)
this.creatingLine.plot(pathStr)
this.checkOverlapNode(x, y)
}
// 获取转换后的鼠标事件对象的坐标
getTransformedEventPos(e) {
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
let { scaleX, scaleY, translateX, translateY } =
this.mindMap.draw.transform()
return {
x: (x - translateX) / scaleX,
y: (y - translateY) / scaleY
}
}
// 检测当前移动到的目标节点
checkOverlapNode(x, y) {
this.overlapNode = null
bfsWalk(this.mindMap.renderer.root, node => {
if (node.nodeData.data.isActive) {
this.mindMap.renderer.setNodeActive(node, false)
}
if (node === this.creatingStartNode || this.overlapNode) {
return
}
let { left, top, width, height } = node
let right = left + width
let bottom = top + height
if (x >= left && x <= right && y >= top && y <= bottom) {
this.overlapNode = node
}
})
if (this.overlapNode && !this.overlapNode.nodeData.data.isActive) {
this.mindMap.renderer.setNodeActive(this.overlapNode, true)
}
}
// 完成创建连接线
completeCreateLine(node) {
if (this.creatingStartNode === node) return
this.addLine(this.creatingStartNode, node)
if (this.overlapNode && this.overlapNode.nodeData.data.isActive) {
this.mindMap.renderer.setNodeActive(this.overlapNode, false)
}
this.isCreatingLine = false
this.creatingStartNode = null
this.creatingLine.remove()
this.creatingLine = null
this.overlapNode = null
}
// 添加连接线
addLine(fromNode, toNode) {
if (!fromNode || !toNode) return
// 目标节点如果没有id则生成一个id
let id = toNode.nodeData.data.id
if (!id) {
id = uuid()
this.mindMap.execCommand('SET_NODE_DATA', toNode, {
id
})
}
// 将目标节点id保存起来
let list = fromNode.nodeData.data.associativeLineTargets || []
list.push(id)
// 保存控制点
let [startPoint, endPoint] = computeNodePoints(fromNode, toNode)
let controlPoints = computeCubicBezierPathPoints(
startPoint.x,
startPoint.y,
endPoint.x,
endPoint.y
)
let offsetList =
fromNode.nodeData.data.associativeLineTargetControlOffsets || []
// 保存的实际是控制点和端点的差值,否则当节点位置改变了,控制点还是原来的位置,连线就不对了
offsetList[list.length - 1] = [
{
x: controlPoints[0].x - startPoint.x,
y: controlPoints[0].y - startPoint.y
},
{
x: controlPoints[1].x - endPoint.x,
y: controlPoints[1].y - endPoint.y
}
]
this.mindMap.execCommand('SET_NODE_DATA', fromNode, {
associativeLineTargets: list,
associativeLineTargetControlOffsets: offsetList
})
}
// 删除连接线
removeLine() {
if (!this.activeLine) return
let [, , node, toNode] = this.activeLine
this.removeControls()
let { associativeLineTargets, associativeLineTargetControlOffsets } =
node.nodeData.data
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
this.mindMap.execCommand('SET_NODE_DATA', node, {
associativeLineTargets: associativeLineTargets.filter((_, index) => {
return index !== targetIndex
}),
associativeLineTargetControlOffsets: associativeLineTargetControlOffsets
? associativeLineTargetControlOffsets.filter((_, index) => {
return index !== targetIndex
})
: []
})
}
// 清除当前激活的节点
clearActiveNodes() {
if (this.mindMap.renderer.activeNodeList.length > 0) {
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
}
}
// 清除激活的线
clearActiveLine() {
if (this.activeLine) {
this.activeLine[1].stroke({
color: 'transparent'
})
this.activeLine = null
this.removeControls()
}
}
// 处理节点正在拖拽事件
onNodeDragging() {
if (this.isNodeDragging) return
this.isNodeDragging = true
this.lineList.forEach(line => {
line[0].hide()
line[1].hide()
})
this.hideControls()
}
// 处理节点拖拽完成事件
onNodeDragend() {
if (!this.isNodeDragging) return
this.lineList.forEach(line => {
line[0].show()
line[1].show()
})
this.showControls()
this.isNodeDragging = false
}
// 创建控制点、连线节点
createControlNodes() {
let { associativeLineActiveColor } = this.mindMap.themeConfig
// 连线
this.controlLine1 = this.draw
.line()
.stroke({ color: associativeLineActiveColor, width: 2 })
this.controlLine2 = this.draw
.line()
.stroke({ color: associativeLineActiveColor, width: 2 })
// 控制点
this.controlPoint1 = this.createOneControlNode('controlPoint1')
this.controlPoint2 = this.createOneControlNode('controlPoint2')
}
// 创建控制点
createOneControlNode(pointKey) {
let { associativeLineActiveColor } = this.mindMap.themeConfig
return this.draw
.circle(this.controlPointDiameter)
.stroke({ color: associativeLineActiveColor })
.fill({ color: '#fff' })
.click(e => {
e.stopPropagation()
})
.mousedown(e => {
this.onControlPointMousedown(e, pointKey)
})
}
// 控制点的鼠标按下事件
onControlPointMousedown(e, pointKey) {
e.stopPropagation()
this.isControlPointMousedown = true
this.mousedownControlPointKey = pointKey
}
// 控制点的鼠标移动事件
onControlPointMousemove(e) {
if (
!this.isControlPointMousedown ||
!this.mousedownControlPointKey ||
!this[this.mousedownControlPointKey]
)
return
e.stopPropagation()
e.preventDefault()
let radius = this.controlPointDiameter / 2
// 转换鼠标当前的位置
let { x, y } = this.getTransformedEventPos(e)
this.controlPointMousemoveState.pos = {
x,
y
}
// 更新当前拖拽的控制点的位置
this[this.mousedownControlPointKey].x(x - radius).y(y - radius)
let [path, clickPath, node, toNode] = this.activeLine
let [startPoint, endPoint] = computeNodePoints(node, toNode)
this.controlPointMousemoveState.startPoint = startPoint
this.controlPointMousemoveState.endPoint = endPoint
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
this.controlPointMousemoveState.targetIndex = targetIndex
let offsets = []
let associativeLineTargetControlOffsets = node.nodeData.data.associativeLineTargetControlOffsets
if (!associativeLineTargetControlOffsets) {
// 兼容0.4.5版本没有associativeLineTargetControlOffsets的情况
offsets = getDefaultControlPointOffsets(startPoint, endPoint)
} else {
offsets = associativeLineTargetControlOffsets[targetIndex]
}
let point1 = null
let point2 = null
// 拖拽的是控制点1
if (this.mousedownControlPointKey === 'controlPoint1') {
point1 = {
x,
y
}
point2 = {
x: endPoint.x + offsets[1].x,
y: endPoint.y + offsets[1].y
}
// 更新控制点1的连线
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
} else {
// 拖拽的是控制点2
point1 = {
x: startPoint.x + offsets[0].x,
y: startPoint.y + offsets[0].y
}
point2 = {
x,
y
}
// 更新控制点2的连线
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
}
// 更新关联线
let pathStr = joinCubicBezierPath(startPoint, endPoint, point1, point2)
path.plot(pathStr)
clickPath.plot(pathStr)
}
// 控制点的鼠标移动事件
onControlPointMouseup(e) {
if (!this.isControlPointMousedown) return
e.stopPropagation()
e.preventDefault()
let { pos, startPoint, endPoint, targetIndex } =
this.controlPointMousemoveState
let [, , node] = this.activeLine
let offsetList = []
let associativeLineTargetControlOffsets = node.nodeData.data.associativeLineTargetControlOffsets
if (!associativeLineTargetControlOffsets) {
// 兼容0.4.5版本没有associativeLineTargetControlOffsets的情况
offsetList[targetIndex] = getDefaultControlPointOffsets(startPoint, endPoint)
} else {
offsetList = associativeLineTargetControlOffsets
}
let offset1 = null
let offset2 = null
if (this.mousedownControlPointKey === 'controlPoint1') {
// 更新控制点1数据
offset1 = {
x: pos.x - startPoint.x,
y: pos.y - startPoint.y
}
offset2 = offsetList[targetIndex][1]
} else {
// 更新控制点2数据
offset1 = offsetList[targetIndex][0]
offset2 = {
x: pos.x - endPoint.x,
y: pos.y - endPoint.y
}
}
offsetList[targetIndex] = [offset1, offset2]
this.mindMap.execCommand('SET_NODE_DATA', node, {
associativeLineTargetControlOffsets: offsetList
})
// 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚所以重置isControlPointMousedown需要等draw_click事件触发完以后
setTimeout(() => {
this.resetControlPoint()
}, 0)
}
// 复位控制点移动
resetControlPoint() {
this.isControlPointMousedown = false
this.mousedownControlPointKey = ''
this.controlPointMousemoveState = {
pos: null,
startPoint: null,
endPoint: null,
targetIndex: ''
}
}
// 渲染控制点
renderControls(startPoint, endPoint, point1, point2) {
if (!this.controlLine1) {
this.createControlNodes()
}
let radius = this.controlPointDiameter / 2
// 控制点和起终点的连线
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
// 控制点
this.controlPoint1.x(point1.x - radius).y(point1.y - radius)
this.controlPoint2.x(point2.x - radius).y(point2.y - radius)
}
// 删除控制点
removeControls() {
if (!this.controlLine1) return
;[
this.controlLine1,
this.controlLine2,
this.controlPoint1,
this.controlPoint2
].forEach(item => {
item.remove()
})
this.controlLine1 = null
this.controlLine2 = null
this.controlPoint1 = null
this.controlPoint2 = null
}
// 隐藏控制点
hideControls() {
if (!this.controlLine1) return
;[
this.controlLine1,
this.controlLine2,
this.controlPoint1,
this.controlPoint2
].forEach(item => {
item.hide()
})
}
// 显示控制点
showControls() {
if (!this.controlLine1) return
;[
this.controlLine1,
this.controlLine2,
this.controlPoint1,
this.controlPoint2
].forEach(item => {
item.show()
})
}
}
AssociativeLine.instanceName = 'associativeLine'
export default AssociativeLine

View File

@@ -0,0 +1,36 @@
import { nextTick } from './utils'
// 批量执行
class BatchExecution {
// 构造函数
constructor() {
this.has = {}
this.queue = []
this.nextTick = nextTick(this.flush, this)
}
// 添加任务
push(name, fn) {
if (this.has[name]) {
return
}
this.has[name] = true
this.queue.push({
name,
fn
})
this.nextTick()
}
// 执行队列
flush() {
let fns = this.queue.slice(0)
this.queue = []
fns.forEach(({ name, fn }) => {
this.has[name] = false
fn()
})
}
}
export default BatchExecution

View File

@@ -0,0 +1,135 @@
import { copyRenderTree, simpleDeepClone, nextTick } from './utils'
// 命令类
class Command {
// 构造函数
constructor(opt = {}) {
this.opt = opt
this.mindMap = opt.mindMap
this.commands = {}
this.history = []
this.activeHistoryIndex = 0
// 注册快捷键
this.registerShortcutKeys()
this.addHistory = nextTick(this.addHistory, this)
}
// 清空历史数据
clearHistory() {
this.history = []
this.activeHistoryIndex = 0
this.mindMap.emit('back_forward', 0, 0)
}
// 注册快捷键
registerShortcutKeys() {
this.mindMap.keyCommand.addShortcut('Control+z', () => {
this.mindMap.execCommand('BACK')
})
this.mindMap.keyCommand.addShortcut('Control+y', () => {
this.mindMap.execCommand('FORWARD')
})
}
// 执行命令
exec(name, ...args) {
if (this.commands[name]) {
this.commands[name].forEach(fn => {
fn(...args)
})
if (['BACK', 'FORWARD', 'SET_NODE_ACTIVE', 'CLEAR_ACTIVE_NODE'].includes(name)) {
return
}
this.addHistory()
}
}
// 添加命令
add(name, fn) {
if (this.commands[name]) {
this.commands[name].push(fn)
} else {
this.commands[name] = [fn]
}
}
// 移除命令
remove(name, fn) {
if (!this.commands[name]) {
return
}
if (!fn) {
this.commands[name] = []
delete this.commands[name]
} else {
let index = this.commands[name].find(item => {
return item === fn
})
if (index !== -1) {
this.commands[name].splice(index, 1)
}
}
}
// 添加回退数据
addHistory() {
if (this.mindMap.opt.readonly) {
return
}
let data = this.getCopyData()
// 此次数据和上次一样则不重复添加
if (this.history.length > 0 && JSON.stringify(this.history[this.history.length - 1]) === JSON.stringify(data)) {
return
}
// 删除当前历史指针后面的数据
this.history = this.history.slice(0, this.activeHistoryIndex + 1)
this.history.push(simpleDeepClone(data))
this.activeHistoryIndex = this.history.length - 1
this.mindMap.emit('data_change', data)
this.mindMap.emit(
'back_forward',
this.activeHistoryIndex,
this.history.length
)
}
// 回退
back(step = 1) {
if (this.mindMap.opt.readonly) {
return
}
if (this.activeHistoryIndex - step >= 0) {
this.activeHistoryIndex -= step
this.mindMap.emit(
'back_forward',
this.activeHistoryIndex,
this.history.length
)
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
this.mindMap.emit('data_change', data)
return data
}
}
// 前进
forward(step = 1) {
if (this.mindMap.opt.readonly) {
return
}
let len = this.history.length
if (this.activeHistoryIndex + step <= len - 1) {
this.activeHistoryIndex += step
this.mindMap.emit('back_forward', this.activeHistoryIndex)
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
this.mindMap.emit('data_change', data)
return data
}
}
// 获取渲染树数据副本
getCopyData() {
return copyRenderTree({}, this.mindMap.renderer.renderTree, true)
}
}
export default Command

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

@@ -0,0 +1,267 @@
import { bfsWalk, throttle } from './utils'
import Base from './layouts/Base'
// 节点拖动类
class Drag extends Base {
// 构造函数
constructor({ mindMap }) {
super(mindMap.renderer)
this.mindMap = mindMap
this.reset()
this.bindEvent()
}
// 复位
reset() {
// 当前拖拽节点
this.node = null
// 当前重叠节点
this.overlapNode = null
// 当前上一个同级节点
this.prevNode = null
// 当前下一个同级节点
this.nextNode = null
// 画布的变换数据
this.drawTransform = null
// 克隆节点
this.clone = null
// 连接线
this.line = null
// 同级位置占位符
this.placeholder = null
// 鼠标按下位置和节点左上角的偏移量
this.offsetX = 0
this.offsetY = 0
// 克隆节点左上角的坐标
this.cloneNodeLeft = 0
this.cloneNodeTop = 0
// 当前鼠标是否按下
this.isMousedown = false
// 拖拽的鼠标位置变量
this.mouseDownX = 0
this.mouseDownY = 0
this.mouseMoveX = 0
this.mouseMoveY = 0
}
// 绑定事件
bindEvent() {
this.checkOverlapNode = throttle(this.checkOverlapNode, 300, this)
this.mindMap.on('node_mousedown', (node, e) => {
if (this.mindMap.opt.readonly || node.isGeneralization) {
return
}
if (e.which !== 1 || node.isRoot) {
return
}
e.preventDefault()
// 计算鼠标按下的位置距离节点左上角的距离
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.node = node
this.isMousedown = true
this.mouseDownX = x
this.mouseDownY = y
})
this.mindMap.on('mousemove', e => {
if (this.mindMap.opt.readonly) {
return
}
if (!this.isMousedown) {
return
}
this.mindMap.emit('node_dragging', this.node)
e.preventDefault()
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
this.mouseMoveX = x
this.mouseMoveY = y
if (
Math.abs(x - this.mouseDownX) <= 10 &&
Math.abs(y - this.mouseDownY) <= 10 &&
!this.node.isDrag
) {
return
}
this.mindMap.renderer.clearAllActive()
this.onMove(x, y)
})
this.onMouseup = this.onMouseup.bind(this)
this.mindMap.on('node_mouseup', this.onMouseup)
this.mindMap.on('mouseup', this.onMouseup)
}
// 鼠标松开事件
onMouseup(e) {
if (!this.isMousedown) {
return
}
this.isMousedown = false
let _nodeIsDrag = this.node.isDrag
this.node.isDrag = false
this.node.show()
this.removeCloneNode()
// 存在重叠子节点,则移动作为其子节点
if (this.overlapNode) {
this.mindMap.renderer.setNodeActive(this.overlapNode, false)
this.mindMap.execCommand('MOVE_NODE_TO', this.node, this.overlapNode)
} else if (this.prevNode) {
// 存在前一个相邻节点,作为其下一个兄弟节点
this.mindMap.renderer.setNodeActive(this.prevNode, false)
this.mindMap.execCommand('INSERT_AFTER', this.node, this.prevNode)
} else if (this.nextNode) {
// 存在下一个相邻节点,作为其前一个兄弟节点
this.mindMap.renderer.setNodeActive(this.nextNode, false)
this.mindMap.execCommand('INSERT_BEFORE', this.node, this.nextNode)
} else if (_nodeIsDrag && this.mindMap.opt.enableFreeDrag) {
// 自定义位置
let { x, y } = this.mindMap.toPos(
e.clientX - this.offsetX,
e.clientY - this.offsetY
)
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
x = (x - translateX) / scaleX
y = (y - translateY) / scaleY
this.node.left = x
this.node.top = y
this.node.customLeft = x
this.node.customTop = y
this.mindMap.execCommand('SET_NODE_CUSTOM_POSITION', this.node, x, y)
this.mindMap.render()
}
this.reset()
this.mindMap.emit('node_dragend')
}
// 创建克隆节点
createCloneNode() {
if (!this.clone) {
// 节点
this.clone = this.node.group.clone()
this.clone.opacity(0.5)
this.clone.css('z-index', 99999)
this.node.isDrag = true
this.node.hide()
// 连接线
this.line = this.draw.path()
this.line.opacity(0.5)
this.node.styleLine(this.line, this.node)
// 同级位置占位符
this.placeholder = this.draw.rect().fill({
color: this.node.style.merge('lineColor', true)
})
this.mindMap.draw.add(this.clone)
}
}
// 移除克隆节点
removeCloneNode() {
if (!this.clone) {
return
}
this.clone.remove()
this.line.remove()
this.placeholder.remove()
}
// 拖动中
onMove(x, y) {
if (!this.isMousedown) {
return
}
this.createCloneNode()
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
this.cloneNodeLeft = x - this.offsetX
this.cloneNodeTop = y - this.offsetY
x = (this.cloneNodeLeft - translateX) / scaleX
y = (this.cloneNodeTop - translateY) / scaleY
let t = this.clone.transform()
this.clone.translate(x - t.translateX, y - t.translateY)
// 连接线
let parent = this.node.parent
this.line.plot(
this.quadraticCurvePath(
parent.left + parent.width / 2,
parent.top + parent.height / 2,
x + this.node.width / 2,
y + this.node.height / 2
)
)
this.checkOverlapNode()
}
// 检测重叠节点
checkOverlapNode() {
if (!this.drawTransform) {
return
}
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
let checkRight = this.cloneNodeLeft + this.node.width * scaleX
let checkBottom = this.cloneNodeTop + this.node.height * scaleX
this.overlapNode = null
this.prevNode = null
this.nextNode = null
this.placeholder.size(0, 0)
bfsWalk(this.mindMap.renderer.root, node => {
if (node.nodeData.data.isActive) {
this.mindMap.renderer.setNodeActive(node, false)
}
if (node === this.node || this.node.isParent(node)) {
return
}
if (this.overlapNode || (this.prevNode && this.nextNode)) {
return
}
let { left, top, width, height } = node
let _left = left
let _top = top
let _bottom = top + height
let right = (left + width) * scaleX + translateX
let bottom = (top + height) * scaleY + translateY
left = left * scaleX + translateX
top = top * scaleY + translateY
// 检测是否重叠
if (!this.overlapNode) {
if (
left <= checkRight &&
right >= this.cloneNodeLeft &&
top <= checkBottom &&
bottom >= this.cloneNodeTop
) {
this.overlapNode = node
}
}
// 检测兄弟节点位置
if (!this.prevNode && !this.nextNode && !node.isRoot) {
// && this.node.isBrother(node)
if (left <= checkRight && right >= this.cloneNodeLeft) {
if (this.cloneNodeTop > bottom && this.cloneNodeTop <= bottom + 10) {
this.prevNode = node
this.placeholder.size(node.width, 10).move(_left, _bottom)
} else if (checkBottom < top && checkBottom >= top - 10) {
this.nextNode = node
this.placeholder.size(node.width, 10).move(_left, _top - 10)
}
}
}
})
if (this.overlapNode) {
this.mindMap.renderer.setNodeActive(this.overlapNode, true)
}
}
}
Drag.instanceName = 'drag'
export default Drag

View File

@@ -0,0 +1,135 @@
import EventEmitter from 'eventemitter3'
// 事件类
class Event extends EventEmitter {
// 构造函数
constructor(opt = {}) {
super()
this.opt = opt
this.mindMap = opt.mindMap
this.isLeftMousedown = false
this.mousedownPos = {
x: 0,
y: 0
}
this.mousemovePos = {
x: 0,
y: 0
}
this.mousemoveOffset = {
x: 0,
y: 0
}
this.bindFn()
this.bind()
}
// 绑定函数上下文
bindFn() {
this.onDrawClick = this.onDrawClick.bind(this)
this.onMousedown = this.onMousedown.bind(this)
this.onMousemove = this.onMousemove.bind(this)
this.onMouseup = this.onMouseup.bind(this)
this.onMousewheel = this.onMousewheel.bind(this)
this.onContextmenu = this.onContextmenu.bind(this)
this.onSvgMousedown = this.onSvgMousedown.bind(this)
this.onKeyup = this.onKeyup.bind(this)
}
// 绑定事件
bind() {
this.mindMap.svg.on('click', this.onDrawClick)
this.mindMap.el.addEventListener('mousedown', this.onMousedown)
this.mindMap.svg.on('mousedown', this.onSvgMousedown)
window.addEventListener('mousemove', this.onMousemove)
window.addEventListener('mouseup', this.onMouseup)
this.mindMap.el.addEventListener('wheel', this.onMousewheel)
this.mindMap.svg.on('contextmenu', this.onContextmenu)
window.addEventListener('keyup', this.onKeyup)
}
// 解绑事件
unbind() {
this.mindMap.svg.off('click', this.onDrawClick)
this.mindMap.el.removeEventListener('mousedown', this.onMousedown)
window.removeEventListener('mousemove', this.onMousemove)
window.removeEventListener('mouseup', this.onMouseup)
this.mindMap.el.removeEventListener('wheel', this.onMousewheel)
this.mindMap.svg.off('contextmenu', this.onContextmenu)
window.removeEventListener('keyup', this.onKeyup)
}
// 画布的单击事件
onDrawClick(e) {
this.emit('draw_click', e)
}
// svg画布的鼠标按下事件
onSvgMousedown(e) {
this.emit('svg_mousedown', e)
}
// 鼠标按下事件
onMousedown(e) {
// e.preventDefault()
// 鼠标左键
if (e.which === 1) {
this.isLeftMousedown = true
}
this.mousedownPos.x = e.clientX
this.mousedownPos.y = e.clientY
this.emit('mousedown', e, this)
}
// 鼠标移动事件
onMousemove(e) {
// e.preventDefault()
this.mousemovePos.x = e.clientX
this.mousemovePos.y = e.clientY
this.mousemoveOffset.x = e.clientX - this.mousedownPos.x
this.mousemoveOffset.y = e.clientY - this.mousedownPos.y
this.emit('mousemove', e, this)
if (this.isLeftMousedown) {
this.emit('drag', e, this)
}
}
// 鼠标松开事件
onMouseup(e) {
this.isLeftMousedown = false
this.emit('mouseup', e, this)
}
// 鼠标滚动
onMousewheel(e) {
e.stopPropagation()
e.preventDefault()
let dir
// 解决mac触控板双指缩放方向相反的问题
if (e.ctrlKey) {
if (e.deltaY > 0) dir = 'up'
if (e.deltaY < 0) dir = 'down'
if (e.deltaX > 0) dir = 'left'
if (e.deltaX < 0) dir = 'right'
} else {
if ((e.wheelDeltaY || e.detail) > 0) dir = 'up'
if ((e.wheelDeltaY || e.detail) < 0) dir = 'down'
if ((e.wheelDeltaX || e.detail) > 0) dir = 'left'
if ((e.wheelDeltaX || e.detail) < 0) dir = 'right'
}
this.emit('mousewheel', e, dir, this)
}
// 鼠标右键菜单事件
onContextmenu(e) {
e.preventDefault()
this.emit('contextmenu', e)
}
// 按键松开事件
onKeyup(e) {
this.emit('keyup', e)
}
}
export default Event

View File

@@ -0,0 +1,254 @@
import { imgToDataUrl, downloadFile } from './utils'
import JsPDF from 'jspdf'
import { SVG } from '@svgdotjs/svg.js'
import drawBackgroundImageToCanvas from './utils/simulateCSSBackgroundInCanvas'
import { transformToMarkdown } from './parse/toMarkdown'
const URL = window.URL || window.webkitURL || window
// 导出类
class Export {
// 构造函数
constructor(opt) {
this.mindMap = opt.mindMap
this.exportPadding = this.mindMap.opt.exportPadding
}
// 导出
async export(type, isDownload = true, name = '思维导图', ...args) {
if (this[type]) {
let result = await this[type](name, ...args)
if (isDownload && type !== 'pdf') {
downloadFile(result, name + '.' + type)
}
return result
} else {
return null
}
}
// 获取svg数据
async getSvgData(domToImage) {
let { svg, svgHTML } = this.mindMap.getSvgData()
// 把图片的url转换成data:url类型否则导出会丢失图片
let imageList = svg.find('image')
let task = imageList.map(async item => {
let imgUlr = item.attr('href') || item.attr('xlink:href')
let imgData = await imgToDataUrl(imgUlr)
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)
if (res) {
nodeWithDomToImg = res.svg
svgHTML = res.svgHTML
}
}
return {
node: svg,
str: svgHTML,
nodeWithDomToImg
}
}
// svg转png
svgToPng(svgSrc) {
return new Promise((resolve, reject) => {
const img = new Image()
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
img.setAttribute('crossOrigin', 'anonymous')
img.onload = async () => {
try {
let canvas = document.createElement('canvas')
canvas.width = img.width + this.exportPadding * 2
canvas.height = img.height + this.exportPadding * 2
let ctx = canvas.getContext('2d')
// 绘制背景
await this.drawBackgroundToCanvas(ctx, canvas.width, canvas.height)
// 图片绘制到canvas里
ctx.drawImage(
img,
0,
0,
img.width,
img.height,
this.exportPadding,
this.exportPadding,
img.width,
img.height
)
resolve(canvas.toDataURL())
} catch (error) {
reject(error)
}
}
img.onerror = e => {
reject(e)
}
img.src = svgSrc
})
}
// 在canvas上绘制思维导图背景
drawBackgroundToCanvas(ctx, width, height) {
return new Promise((resolve, reject) => {
let {
backgroundColor = '#fff',
backgroundImage,
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center center',
backgroundSize = 'cover',
} = this.mindMap.themeConfig
// 背景颜色
ctx.save()
ctx.rect(0, 0, width, height)
ctx.fillStyle = backgroundColor
ctx.fill()
ctx.restore()
// 背景图片
if (backgroundImage && backgroundImage !== 'none') {
ctx.save()
drawBackgroundImageToCanvas(ctx, width, height, backgroundImage, {
backgroundRepeat,
backgroundPosition,
backgroundSize
}, (err) => {
if (err) {
reject(err)
} else {
resolve()
}
ctx.restore()
})
} else {
resolve()
}
})
}
// 导出为png
/**
* 方法1.把svg的图片都转化成data:url格式再转换
* 方法2.把svg的图片提取出来再挨个绘制到canvas里最后一起转换
*/
async png() {
let { str } = await this.getSvgData(true)
// 转换成blob数据
let blob = new Blob([str], {
type: 'image/svg+xml'
})
// 转换成data:url数据
let svgUrl = URL.createObjectURL(blob)
// 绘制到canvas上
let imgDataUrl = await this.svgToPng(svgUrl)
URL.revokeObjectURL(svgUrl)
return imgDataUrl
}
// 导出为pdf
async pdf(name) {
let img = await this.png()
let pdf = new JsPDF('', 'pt', 'a4')
let a4Width = 595
let a4Height = 841
let a4Ratio = a4Width / a4Height
let image = new Image()
image.onload = () => {
let imageWidth = image.width
let imageHeight = image.height
let imageRatio = imageWidth / imageHeight
let w, h
if (imageWidth <= a4Width && imageHeight <= a4Height) {
// 使用图片原始宽高
w = imageWidth
h = imageHeight
} else if (a4Ratio > imageRatio) {
// 以a4Height为高度缩放图片宽度
w = imageRatio * a4Height
h = a4Height
} else {
// 以a4Width为宽度缩放图片高度
w = a4Width
h = a4Width / imageRatio
}
pdf.addImage(img, 'PNG', (a4Width - w) / 2, (a4Height - h) / 2, w, h)
pdf.save(name)
}
image.src = img
}
// 在svg上绘制思维导图背景
drawBackgroundToSvg(svg) {
return new Promise(async resolve => {
let {
backgroundColor = '#fff',
backgroundImage,
backgroundRepeat = 'repeat'
} = this.mindMap.themeConfig
// 背景颜色
svg.css('background-color', backgroundColor)
// 背景图片
if (backgroundImage && backgroundImage !== 'none') {
let imgDataUrl = await imgToDataUrl(backgroundImage)
svg.css('background-image', `url(${imgDataUrl})`)
svg.css('background-repeat', backgroundRepeat)
resolve()
} else {
resolve()
}
})
}
// 导出为svg
// domToImage是否将svg中的dom节点转换成图片的形式
// plusCssText附加的css样式如果svg中存在dom节点想要设置一些针对节点的样式可以通过这个参数传入
async svg(name, domToImage = false, plusCssText) {
let { node, nodeWithDomToImg } = await this.getSvgData(domToImage)
// 开启了节点富文本编辑
if (this.mindMap.richText) {
if (domToImage) {
node = nodeWithDomToImg
} else if (plusCssText) {
let foreignObjectList = node.find('foreignObject')
if (foreignObjectList.length > 0) {
foreignObjectList[0].add(SVG(`<style>${plusCssText}</style>`))
}
}
}
node.first().before(SVG(`<title>${name}</title>`))
await this.drawBackgroundToSvg(node)
let str = node.svg()
// 转换成blob数据
let blob = new Blob([str], {
type: 'image/svg+xml'
})
return URL.createObjectURL(blob)
}
// 导出为json
json(name, withConfig = true) {
let data = this.mindMap.getData(withConfig)
let str = JSON.stringify(data)
let blob = new Blob([str])
return URL.createObjectURL(blob)
}
// 专有文件其实就是json文件
smm(name, withConfig) {
return this.json(name, withConfig)
}
// markdown文件
md() {
let data = this.mindMap.getData()
let content = transformToMarkdown(data)
let blob = new Blob([content])
return URL.createObjectURL(blob)
}
}
Export.instanceName = 'doExport'
export default Export

View File

@@ -0,0 +1,147 @@
import { keyMap } from './utils/keyMap'
// 快捷按键、命令处理类
export default class KeyCommand {
// 构造函数
constructor(opt) {
this.opt = opt
this.mindMap = opt.mindMap
this.shortcutMap = {
//Enter: [fn]
}
this.shortcutMapCache = {}
this.isPause = false
this.bindEvent()
}
// 暂停快捷键响应
pause() {
this.isPause = true
}
// 恢复快捷键响应
recovery() {
this.isPause = false
}
// 保存当前注册的快捷键数据,然后清空快捷键数据
save() {
this.shortcutMapCache = this.shortcutMap
this.shortcutMap = {}
}
// 恢复保存的快捷键数据,然后清空缓存数据
restore() {
this.shortcutMap = this.shortcutMapCache
this.shortcutMapCache = {}
}
// 绑定事件
bindEvent() {
window.addEventListener('keydown', e => {
if (this.isPause) {
return
}
Object.keys(this.shortcutMap).forEach(key => {
if (this.checkKey(e, key)) {
e.stopPropagation()
e.preventDefault()
this.shortcutMap[key].forEach(fn => {
fn()
})
}
})
})
}
// 检查键值是否符合
checkKey(e, key) {
let o = this.getOriginEventCodeArr(e)
let k = this.getKeyCodeArr(key)
if (o.length !== k.length) {
return false
}
for (let i = 0; i < o.length; i++) {
let index = k.findIndex(item => {
return item === o[i]
})
if (index === -1) {
return false
} else {
k.splice(index, 1)
}
}
return true
}
// 获取事件对象里的键值数组
getOriginEventCodeArr(e) {
let arr = []
if (e.ctrlKey || e.metaKey) {
arr.push(keyMap['Control'])
}
if (e.altKey) {
arr.push(keyMap['Alt'])
}
if (e.shiftKey) {
arr.push(keyMap['Shift'])
}
if (!arr.includes(e.keyCode)) {
arr.push(e.keyCode)
}
return arr
}
// 获取快捷键对应的键值数组
getKeyCodeArr(key) {
let keyArr = key.split(/\s*\+\s*/)
let arr = []
keyArr.forEach(item => {
arr.push(keyMap[item])
})
return arr
}
// 添加快捷键命令
/**
* Enter
* Tab | Insert
* Shift + a
*/
addShortcut(key, fn) {
key.split(/\s*\|\s*/).forEach(item => {
if (this.shortcutMap[item]) {
this.shortcutMap[item].push(fn)
} else {
this.shortcutMap[item] = [fn]
}
})
}
// 移除快捷键命令
removeShortcut(key, fn) {
key.split(/\s*\|\s*/).forEach(item => {
if (this.shortcutMap[item]) {
if (fn) {
let index = this.shortcutMap[item].findIndex(f => {
return f === fn
})
if (index !== -1) {
this.shortcutMap[item].splice(index, 1)
}
} else {
this.shortcutMap[item] = []
delete this.shortcutMap[item]
}
}
})
}
// 获取指定快捷键的处理函数
getShortcutFn(key) {
let res = []
key.split(/\s*\|\s*/).forEach(item => {
res = this.shortcutMap[item] || []
})
return res
}
}

View File

@@ -0,0 +1,236 @@
import { bfsWalk } from './utils'
// 键盘导航类
class KeyboardNavigation {
// 构造函数
constructor(opt) {
this.opt = opt
this.mindMap = opt.mindMap
this.onKeyup = this.onKeyup.bind(this)
this.mindMap.keyCommand.addShortcut('Left', () => {
this.onKeyup('Left')
})
this.mindMap.keyCommand.addShortcut('Up', () => {
this.onKeyup('Up')
})
this.mindMap.keyCommand.addShortcut('Right', () => {
this.onKeyup('Right')
})
this.mindMap.keyCommand.addShortcut('Down', () => {
this.onKeyup('Down')
})
}
// 处理按键事件
onKeyup(dir) {
if (this.mindMap.renderer.activeNodeList.length > 0) {
this.focus(dir)
} else {
let root = this.mindMap.renderer.root
this.mindMap.renderer.moveNodeToCenter(root)
root.active()
}
}
// 聚焦到下一个节点
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) {
targetNode = node
targetDis = dis
}
}
// 第一优先级:阴影算法
this.getFocusNodeByShadowAlgorithm({
currentActiveNode,
currentActiveNodeRect,
dir,
checkNodeDis
})
// 第二优先级:区域算法
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)
}
})
}
// 获取节点的位置信息
getNodeRect(node) {
let { scaleX, scaleY, translateX, translateY } =
this.mindMap.draw.transform()
let { left, top, width, height } = node
return {
right: (left + width) * scaleX + translateX,
bottom: (top + height) * scaleY + translateY,
left: left * scaleX + translateX,
top: top * scaleY + translateY
}
}
// 获取两个节点的距离
getDistance(node1Rect, node2Rect) {
let center1 = this.getCenter(node1Rect)
let center2 = this.getCenter(node2Rect)
return Math.sqrt(
Math.pow(center1.x - center2.x, 2) + Math.pow(center1.y - center2.y, 2)
)
}
// 获取节点的中心点
getCenter({ left, right, top, bottom }) {
return {
x: (left + right) / 2,
y: (top + bottom) / 2
}
}
}
KeyboardNavigation.instanceName = 'keyboardNavigation'
export default KeyboardNavigation

View File

@@ -0,0 +1,112 @@
// 小地图类
class MiniMap {
// 构造函数
constructor(opt) {
this.mindMap = opt.mindMap
this.isMousedown = false
this.mousedownPos = {
x: 0,
y: 0
}
this.startViewPos = {
x: 0,
y: 0
}
}
// 计算小地图的渲染数据
/**
* boxWidth小地图容器的宽度
* boxHeight小地图容器的高度
*/
calculationMiniMap(boxWidth, boxHeight) {
let { svgHTML, rect, origWidth, origHeight, scaleX, scaleY } =
this.mindMap.getSvgData()
// 计算数据
let boxRatio = boxWidth / boxHeight
let actWidth = 0
let actHeight = 0
if (boxRatio > rect.ratio) {
// 高度以box为准缩放宽度
actHeight = boxHeight
actWidth = rect.ratio * actHeight
} else {
// 宽度以box为准缩放高度
actWidth = boxWidth
actHeight = actWidth / rect.ratio
}
// svg图形的缩放及位置
let miniMapBoxScale = actWidth / rect.width
let miniMapBoxLeft = (boxWidth - actWidth) / 2
let miniMapBoxTop = (boxHeight - actHeight) / 2
// 视口框大小及位置
let _rectX = rect.x - (rect.width * scaleX - rect.width) / 2
let _rectX2 = rect.x2 + (rect.width * scaleX - rect.width) / 2
let _rectY = rect.y - (rect.height * scaleY - rect.height) / 2
let _rectY2 = rect.y2 + (rect.height * scaleY - rect.height) / 2
let _rectWidth = rect.width * scaleX
let _rectHeight = rect.height * scaleY
let viewBoxStyle = {
left: 0,
top: 0,
right: 0,
bottom: 0
}
viewBoxStyle.left =
Math.max(0, (-_rectX / _rectWidth) * actWidth) + miniMapBoxLeft + 'px'
viewBoxStyle.right =
Math.max(0, ((_rectX2 - origWidth) / _rectWidth) * actWidth) +
miniMapBoxLeft +
'px'
viewBoxStyle.top =
Math.max(0, (-_rectY / _rectHeight) * actHeight) + miniMapBoxTop + 'px'
viewBoxStyle.bottom =
Math.max(0, ((_rectY2 - origHeight) / _rectHeight) * actHeight) +
miniMapBoxTop +
'px'
return {
svgHTML, // 小地图html
viewBoxStyle, // 视图框的位置信息
miniMapBoxScale, // 视图框的缩放值
miniMapBoxLeft, // 视图框的left值
miniMapBoxTop // 视图框的top值
}
}
// 小地图鼠标按下事件
onMousedown(e) {
this.isMousedown = true
this.mousedownPos = {
x: e.clientX,
y: e.clientY
}
// 保存视图当前的偏移量
let transformData = this.mindMap.view.getTransformData()
this.startViewPos = {
x: transformData.state.x,
y: transformData.state.y
}
}
// 小地图鼠标移动事件
onMousemove(e, sensitivityNum = 5) {
if (!this.isMousedown) {
return
}
let ox = e.clientX - this.mousedownPos.x
let oy = e.clientY - this.mousedownPos.y
// 在视图最初偏移量上累加更新量
this.mindMap.view.translateXTo(ox * sensitivityNum + this.startViewPos.x)
this.mindMap.view.translateYTo(oy * sensitivityNum + this.startViewPos.y)
}
// 小地图鼠标松开事件
onMouseup() {
this.isMousedown = false
}
}
MiniMap.instanceName = 'miniMap'
export default MiniMap

1277
simple-mind-map/src/Node.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,996 @@
import merge from 'deepmerge'
import LogicalStructure from './layouts/LogicalStructure'
import MindMap from './layouts/MindMap'
import CatalogOrganization from './layouts/CatalogOrganization'
import OrganizationStructure from './layouts/OrganizationStructure'
import TextEdit from './TextEdit'
import { copyNodeTree, simpleDeepClone, walk } from './utils'
import { shapeList } from './Shape'
import { lineStyleProps } from './themes/default'
// 布局列表
const layouts = {
// 逻辑结构图
logicalStructure: LogicalStructure,
// 思维导图
mindMap: MindMap,
// 目录组织图
catalogOrganization: CatalogOrganization,
// 组织结构图
organizationStructure: OrganizationStructure
}
// 渲染
class Render {
// 构造函数
constructor(opt = {}) {
this.opt = opt
this.mindMap = opt.mindMap
this.themeConfig = this.mindMap.themeConfig
this.draw = this.mindMap.draw
// 渲染树,操作过程中修改的都是这里的数据
this.renderTree = merge({}, this.mindMap.opt.data || {})
// 是否重新渲染
this.reRender = false
// 当前激活的节点列表
this.activeNodeList = []
// 根节点
this.root = null
// 文本编辑框需要再bindEvent之前实例化否则单击事件只能触发隐藏文本编辑框而无法保存文本修改
this.textEdit = new TextEdit(this)
// 布局
this.setLayout()
// 绑定事件
this.bindEvent()
// 注册命令
this.registerCommands()
// 注册快捷键
this.registerShortcutKeys()
}
// 设置布局结构
setLayout() {
this.layout = new (
layouts[this.mindMap.opt.layout]
? layouts[this.mindMap.opt.layout]
: layouts.logicalStructure
)(this)
}
// 绑定事件
bindEvent() {
// 点击事件
this.mindMap.on('draw_click', () => {
// 清除激活状态
if (this.activeNodeList.length > 0) {
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
}
})
}
// 注册命令
registerCommands() {
// 全选
this.selectAll = this.selectAll.bind(this)
this.mindMap.command.add('SELECT_ALL', this.selectAll)
// 回退
this.back = this.back.bind(this)
this.mindMap.command.add('BACK', this.back)
// 前进
this.forward = this.forward.bind(this)
this.mindMap.command.add('FORWARD', this.forward)
// 插入同级节点
this.insertNode = this.insertNode.bind(this)
this.mindMap.command.add('INSERT_NODE', this.insertNode)
// 插入子节点
this.insertChildNode = this.insertChildNode.bind(this)
this.mindMap.command.add('INSERT_CHILD_NODE', this.insertChildNode)
// 上移节点
this.upNode = this.upNode.bind(this)
this.mindMap.command.add('UP_NODE', this.upNode)
// 下移节点
this.downNode = this.downNode.bind(this)
this.mindMap.command.add('DOWN_NODE', this.downNode)
// 移动节点
this.insertAfter = this.insertAfter.bind(this)
this.mindMap.command.add('INSERT_AFTER', this.insertAfter)
this.insertBefore = this.insertBefore.bind(this)
this.mindMap.command.add('INSERT_BEFORE', this.insertBefore)
this.moveNodeTo = this.moveNodeTo.bind(this)
this.mindMap.command.add('MOVE_NODE_TO', this.moveNodeTo)
// 删除节点
this.removeNode = this.removeNode.bind(this)
this.mindMap.command.add('REMOVE_NODE', this.removeNode)
// 粘贴节点
this.pasteNode = this.pasteNode.bind(this)
this.mindMap.command.add('PASTE_NODE', this.pasteNode)
// 剪切节点
this.cutNode = this.cutNode.bind(this)
this.mindMap.command.add('CUT_NODE', this.cutNode)
// 修改节点样式
this.setNodeStyle = this.setNodeStyle.bind(this)
this.mindMap.command.add('SET_NODE_STYLE', this.setNodeStyle)
// 切换节点是否激活
this.setNodeActive = this.setNodeActive.bind(this)
this.mindMap.command.add('SET_NODE_ACTIVE', this.setNodeActive)
// 清除所有激活节点
this.clearAllActive = this.clearAllActive.bind(this)
this.mindMap.command.add('CLEAR_ACTIVE_NODE', this.clearAllActive)
// 切换节点是否展开
this.setNodeExpand = this.setNodeExpand.bind(this)
this.mindMap.command.add('SET_NODE_EXPAND', this.setNodeExpand)
// 展开所有节点
this.expandAllNode = this.expandAllNode.bind(this)
this.mindMap.command.add('EXPAND_ALL', this.expandAllNode)
// 收起所有节点
this.unexpandAllNode = this.unexpandAllNode.bind(this)
this.mindMap.command.add('UNEXPAND_ALL', this.unexpandAllNode)
// 展开到指定层级
this.expandToLevel = this.expandToLevel.bind(this)
this.mindMap.command.add('UNEXPAND_TO_LEVEL', this.expandToLevel)
// 设置节点数据
this.setNodeData = this.setNodeData.bind(this)
this.mindMap.command.add('SET_NODE_DATA', this.setNodeData)
// 设置节点文本
this.setNodeText = this.setNodeText.bind(this)
this.mindMap.command.add('SET_NODE_TEXT', this.setNodeText)
// 设置节点图片
this.setNodeImage = this.setNodeImage.bind(this)
this.mindMap.command.add('SET_NODE_IMAGE', this.setNodeImage)
// 设置节点图标
this.setNodeIcon = this.setNodeIcon.bind(this)
this.mindMap.command.add('SET_NODE_ICON', this.setNodeIcon)
// 设置节点超链接
this.setNodeHyperlink = this.setNodeHyperlink.bind(this)
this.mindMap.command.add('SET_NODE_HYPERLINK', this.setNodeHyperlink)
// 设置节点备注
this.setNodeNote = this.setNodeNote.bind(this)
this.mindMap.command.add('SET_NODE_NOTE', this.setNodeNote)
// 设置节点标签
this.setNodeTag = this.setNodeTag.bind(this)
this.mindMap.command.add('SET_NODE_TAG', this.setNodeTag)
// 添加节点概要
this.addGeneralization = this.addGeneralization.bind(this)
this.mindMap.command.add('ADD_GENERALIZATION', this.addGeneralization)
// 删除节点概要
this.removeGeneralization = this.removeGeneralization.bind(this)
this.mindMap.command.add('REMOVE_GENERALIZATION', this.removeGeneralization)
// 设置节点自定义位置
this.setNodeCustomPosition = this.setNodeCustomPosition.bind(this)
this.mindMap.command.add(
'SET_NODE_CUSTOM_POSITION',
this.setNodeCustomPosition
)
// 一键整理布局
this.resetLayout = this.resetLayout.bind(this)
this.mindMap.command.add('RESET_LAYOUT', this.resetLayout)
// 设置节点形状
this.setNodeShape = this.setNodeShape.bind(this)
this.mindMap.command.add('SET_NODE_SHAPE', this.setNodeShape)
}
// 注册快捷键
registerShortcutKeys() {
// 插入下级节点
this.mindMap.keyCommand.addShortcut('Tab', () => {
this.mindMap.execCommand('INSERT_CHILD_NODE')
})
// 插入同级节点
this.insertNodeWrap = () => {
if (this.textEdit.showTextEdit) {
return
}
this.mindMap.execCommand('INSERT_NODE')
}
this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap)
// 插入概要
this.mindMap.keyCommand.addShortcut('Control+s', this.addGeneralization)
// 展开/收起节点
this.toggleActiveExpand = this.toggleActiveExpand.bind(this)
this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand)
// 删除节点
this.removeNodeWrap = () => {
this.mindMap.execCommand('REMOVE_NODE')
}
this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap)
// 节点编辑时某些快捷键会存在冲突,需要暂时去除
this.mindMap.on('before_show_text_edit', () => {
this.startTextEdit()
})
this.mindMap.on('hide_text_edit', () => {
this.endTextEdit()
})
// 全选
this.mindMap.keyCommand.addShortcut('Control+a', () => {
this.mindMap.execCommand('SELECT_ALL')
})
// 一键整理布局
this.mindMap.keyCommand.addShortcut('Control+l', this.resetLayout)
// 上移节点
this.mindMap.keyCommand.addShortcut('Control+Up', this.upNode)
// 下移节点
this.mindMap.keyCommand.addShortcut('Control+Down', this.downNode)
// 复制节点、剪切节点、粘贴节点的快捷键需开发者自行注册实现可参考demo
}
// 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突
startTextEdit() {
this.mindMap.keyCommand.save()
// this.mindMap.keyCommand.removeShortcut('Del|Backspace')
// this.mindMap.keyCommand.removeShortcut('/')
// this.mindMap.keyCommand.removeShortcut('Enter', this.insertNodeWrap)
}
// 结束文字编辑,会恢复回车键和删除键相关快捷键
endTextEdit() {
this.mindMap.keyCommand.restore()
// this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap)
// this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand)
// this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap)
}
// 渲染
render(callback = () => {}) {
if (this.reRender) {
this.clearActive()
}
this.layout.doLayout(root => {
this.root = root
this.root.render(() => {
this.mindMap.emit('node_tree_render_end')
callback()
})
})
this.mindMap.emit('node_active', null, this.activeNodeList)
}
// 清除当前激活的节点
clearActive() {
this.activeNodeList.forEach(item => {
this.setNodeActive(item, false)
})
this.activeNodeList = []
}
// 清除当前所有激活节点,并会触发事件
clearAllActive() {
if (this.activeNodeList.length <= 0) {
return
}
this.clearActive()
this.mindMap.emit('node_active', null, [])
}
// 添加节点到激活列表里
addActiveNode(node) {
let index = this.findActiveNodeIndex(node)
if (index === -1) {
this.activeNodeList.push(node)
}
}
// 在激活列表里移除某个节点
removeActiveNode(node) {
let index = this.findActiveNodeIndex(node)
if (index === -1) {
return
}
this.activeNodeList.splice(index, 1)
}
// 检索某个节点在激活列表里的索引
findActiveNodeIndex(node) {
return this.activeNodeList.findIndex(item => {
return item === node
})
}
// 获取节点在同级里的索引位置
getNodeIndex(node) {
return node.parent
? node.parent.children.findIndex(item => {
return item === node
})
: 0
}
// 全选
selectAll() {
walk(
this.root,
null,
node => {
if (!node.nodeData.data.isActive) {
node.nodeData.data.isActive = true
this.addActiveNode(node)
setTimeout(() => {
node.renderNode()
}, 0)
}
},
null,
true,
0,
0
)
}
// 回退
back(step) {
this.clearAllActive()
let data = this.mindMap.command.back(step)
if (data) {
this.renderTree = data
this.mindMap.reRender()
}
}
// 前进
forward(step) {
this.clearAllActive()
let data = this.mindMap.command.forward(step)
if (data) {
this.renderTree = data
this.mindMap.reRender()
}
}
// 规范指定节点数据
formatAppointNodes(appointNodes) {
if (!appointNodes) return []
return Array.isArray(appointNodes) ? appointNodes: [appointNodes]
}
// 插入同级节点,多个节点只会操作第一个节点
insertNode(openEdit = true, appointNodes = [], appointData = null) {
appointNodes = this.formatAppointNodes(appointNodes)
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
return
}
let { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt
let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
let first = list[0]
if (first.isRoot) {
this.insertChildNode(openEdit, appointNodes, appointData)
} else {
let text = first.layerIndex === 1 ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText
if (first.layerIndex === 1) {
first.parent.initRender = true
}
let index = this.getNodeIndex(first)
first.parent.nodeData.children.splice(index + 1, 0, {
inserting: openEdit,
data: {
text: text,
expand: true,
...(appointData || {})
},
children: []
})
this.mindMap.render()
}
}
// 插入子节点
insertChildNode(openEdit = true, appointNodes = [], appointData = null) {
appointNodes = this.formatAppointNodes(appointNodes)
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
return
}
let { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt
let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
list.forEach(node => {
if (!node.nodeData.children) {
node.nodeData.children = []
}
let text = node.isRoot ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText
node.nodeData.children.push({
inserting: openEdit,
data: {
text: text,
expand: true,
...(appointData || {})
},
children: []
})
// 插入子节点时自动展开子节点
node.nodeData.data.expand = true
if (node.isRoot) {
node.initRender = true
// this.mindMap.batchExecution.push('renderNode' + index, () => {
// node.renderNode()
// })
}
})
this.mindMap.render()
}
// 上移节点,多个节点只会操作第一个节点
upNode() {
if (this.activeNodeList.length <= 0) {
return
}
let node = this.activeNodeList[0]
if (node.isRoot) {
return
}
let parent = node.parent
let childList = parent.children
let index = childList.findIndex(item => {
return item === node
})
if (index === -1 || index === 0) {
return
}
let insertIndex = index - 1
// 节点实例
childList.splice(index, 1)
childList.splice(insertIndex, 0, node)
// 节点数据
parent.nodeData.children.splice(index, 1)
parent.nodeData.children.splice(insertIndex, 0, node.nodeData)
this.mindMap.render()
}
// 下移节点,多个节点只会操作第一个节点
downNode() {
if (this.activeNodeList.length <= 0) {
return
}
let node = this.activeNodeList[0]
if (node.isRoot) {
return
}
let parent = node.parent
let childList = parent.children
let index = childList.findIndex(item => {
return item === node
})
if (index === -1 || index === childList.length - 1) {
return
}
let insertIndex = index + 1
// 节点实例
childList.splice(index, 1)
childList.splice(insertIndex, 0, node)
// 节点数据
parent.nodeData.children.splice(index, 1)
parent.nodeData.children.splice(insertIndex, 0, node.nodeData)
this.mindMap.render()
}
// 将节点移动到另一个节点的前面
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 => {
return item === node
})
if (nodeIndex === -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 => {
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()
}
})
}
// 将节点移动到另一个节点的后面
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 => {
return item === node
})
if (nodeIndex === -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 => {
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()
}
})
}
// 移除节点
removeNode(appointNodes = []) {
appointNodes = this.formatAppointNodes(appointNodes)
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
return
}
let isAppointNodes = appointNodes.length > 0
let list = isAppointNodes ? appointNodes : this.activeNodeList
let root = list.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 < list.length; i++) {
let node = list[i]
if (isAppointNodes) list.splice(i, 1)
if (node.isGeneralization) {
// 删除概要节点
this.setNodeData(node.generalizationBelongNode, {
generalization: null
})
node.generalizationBelongNode.update()
this.removeActiveNode(node)
i--
} else {
this.removeActiveNode(node)
this.removeOneNode(node)
i--
}
}
}
this.mindMap.emit('node_active', null, this.activeNodeList)
this.mindMap.render()
}
// 移除某个指定节点
removeOneNode(node) {
let index = this.getNodeIndex(node)
node.remove()
node.parent.children.splice(index, 1)
node.parent.nodeData.children.splice(index, 1)
}
// 复制节点,多个节点只会操作第一个节点
copyNode() {
if (this.activeNodeList.length <= 0) {
return
}
return copyNodeTree({}, this.activeNodeList[0], true)
}
// 剪切节点,多个节点只会操作第一个节点
cutNode(callback) {
if (this.activeNodeList.length <= 0) {
return
}
let node = this.activeNodeList[0]
if (node.isRoot) {
return null
}
let copyData = copyNodeTree({}, node, true)
this.removeActiveNode(node)
this.removeOneNode(node)
this.mindMap.emit('node_active', null, this.activeNodeList)
this.mindMap.render()
if (callback && typeof callback === 'function') {
callback(copyData)
}
}
// 移动一个节点作为另一个节点的子节点
moveNodeTo(node, toNode) {
if (node.isRoot) {
return
}
let copyData = copyNodeTree({}, node, false, true)
this.removeActiveNode(node)
this.removeOneNode(node)
this.mindMap.emit('node_active', null, this.activeNodeList)
toNode.nodeData.children.push(copyData)
this.mindMap.render()
if (toNode.isRoot) {
toNode.renderNode()
}
}
// 粘贴节点到节点
pasteNode(data) {
if (this.activeNodeList.length <= 0) {
return
}
this.activeNodeList.forEach(item => {
item.nodeData.children.push(simpleDeepClone(data))
})
this.mindMap.render()
}
// 设置节点样式
setNodeStyle(node, prop, value, isActive) {
let data = {}
if (isActive) {
data = {
activeStyle: {
...(node.nodeData.data.activeStyle || {}),
[prop]: value
}
}
} else {
data = {
[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)) {
;(node.parent || node).renderLine(true)
}
}
// 设置节点是否激活
setNodeActive(node, active) {
this.setNodeData(node, {
isActive: active
})
node.renderNode()
}
// 设置节点是否展开
setNodeExpand(node, expand) {
this.setNodeData(node, {
expand
})
if (expand) {
// 展开
node.children.forEach(item => {
item.render()
})
node.renderLine()
node.updateExpandBtnNode()
} else {
// 收缩
node.children.forEach(item => {
item.remove()
})
node.removeLine()
node.updateExpandBtnNode()
}
this.mindMap.render()
}
// 展开所有
expandAllNode() {
walk(
this.renderTree,
null,
node => {
if (!node.data.expand) {
node.data.expand = true
}
},
null,
true,
0,
0
)
this.mindMap.reRender()
}
// 收起所有
unexpandAllNode() {
walk(
this.renderTree,
null,
(node, parent, isRoot) => {
node._node = null
if (!isRoot) {
node.data.expand = false
}
},
null,
true,
0,
0
)
this.mindMap.reRender()
}
// 展开到指定层级
expandToLevel(level) {
walk(
this.renderTree,
null,
(node, parent, isRoot, layerIndex) => {
node._node = null
node.data.expand = layerIndex < level
},
null,
true,
0,
0
)
this.mindMap.reRender()
}
// 切换激活节点的展开状态
toggleActiveExpand() {
this.activeNodeList.forEach(node => {
if (node.nodeData.children.length <= 0) {
return
}
this.toggleNodeExpand(node)
})
}
// 切换节点展开状态
toggleNodeExpand(node) {
this.mindMap.execCommand(
'SET_NODE_EXPAND',
node,
!node.nodeData.data.expand
)
}
// 设置节点文本
setNodeText(node, text, richText) {
this.setNodeDataRender(node, {
text,
richText
})
}
// 设置节点图片
setNodeImage(node, { url, title, width, height }) {
this.setNodeDataRender(node, {
image: url,
imageTitle: title || '',
imageSize: {
width,
height
}
})
}
// 设置节点图标
setNodeIcon(node, icons) {
this.setNodeDataRender(node, {
icon: icons
})
}
// 设置节点超链接
setNodeHyperlink(node, link, title = '') {
this.setNodeDataRender(node, {
hyperlink: link,
hyperlinkTitle: title
})
}
// 设置节点备注
setNodeNote(node, note) {
this.setNodeDataRender(node, {
note
})
}
// 设置节点标签
setNodeTag(node, tag) {
this.setNodeDataRender(node, {
tag
})
}
// 添加节点概要
addGeneralization(data) {
if (this.activeNodeList.length <= 0) {
return
}
this.activeNodeList.forEach(node => {
if (node.nodeData.data.generalization || node.isRoot) {
return
}
this.setNodeData(node, {
generalization: data || {
text: '概要'
}
})
node.update()
})
this.mindMap.render()
}
// 删除节点概要
removeGeneralization() {
if (this.activeNodeList.length <= 0) {
return
}
this.activeNodeList.forEach(node => {
if (!node.nodeData.data.generalization) {
return
}
this.setNodeData(node, {
generalization: null
})
node.update()
})
this.mindMap.render()
}
// 设置节点自定义位置
setNodeCustomPosition(node, left = undefined, top = undefined) {
let nodeList = [node] || this.activeNodeList
nodeList.forEach(item => {
this.setNodeData(item, {
customLeft: left,
customTop: top
})
})
}
// 一键整理布局,即去除自定义位置
resetLayout() {
walk(
this.root,
null,
node => {
node.customLeft = undefined
node.customTop = undefined
this.setNodeData(node, {
customLeft: undefined,
customTop: undefined
})
this.mindMap.render()
},
null,
true,
0,
0
)
}
// 设置节点形状
setNodeShape(node, shape) {
if (!shape || !shapeList.includes(shape)) {
return
}
let nodeList = [node] || this.activeNodeList
nodeList.forEach(item => {
this.setNodeStyle(item, 'shape', shape)
})
}
// 更新节点数据
setNodeData(node, data) {
Object.keys(data).forEach(key => {
node.nodeData.data[key] = data[key]
})
}
// 设置节点数据,并判断是否渲染
setNodeDataRender(node, data) {
this.setNodeData(node, data)
let changed = node.getSize()
node.renderNode()
if (changed) {
if (node.isGeneralization) {
// 概要节点
node.generalizationBelongNode.updateGeneralization()
}
this.mindMap.render()
}
}
// 移动节点到画布中心
moveNodeToCenter(node) {
let halfWidth = this.mindMap.width / 2
let halfHeight = this.mindMap.height / 2
let { left, top, width, height } = node
let nodeCenterX = left + width / 2
let nodeCenterY = top + height / 2
let { state } = this.mindMap.view.getTransformData()
let targetX = halfWidth - state.x
let targetY = halfHeight - state.y
let offsetX = targetX - nodeCenterX
let offsetY = targetY - nodeCenterY
this.mindMap.view.translateX(offsetX)
this.mindMap.view.translateY(offsetY)
this.mindMap.view.setScale(1)
}
}
export default Render

View File

@@ -0,0 +1,459 @@
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;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')
// 使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
let bgColor = node.style.merge('fillColor')
this.textEditNode.style.backgroundColor = bgColor === 'transparent' ? '#fff' : bgColor
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.focus()
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())
}
// 聚焦
focus() {
let len = this.quill.getLength()
this.quill.setSelection(len, len)
}
// 格式化当前选中的文本
formatText(config = {}, clear = false) {
if (!this.range && !this.lastRange) return
this.syncFormatToNodeConfig(config, clear)
let rangeLost = !this.range
let range = rangeLost ? this.lastRange : this.range
clear ? this.quill.removeFormat(range.index, range.length) : this.quill.formatText(range.index, range.length, config)
if (rangeLost) {
this.quill.setSelection(this.lastRange.index, this.lastRange.length)
}
}
// 清除当前选中文本的样式
removeFormat() {
this.formatText({}, true)
}
// 格式化指定范围的文本
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, clear) {
if (!this.node) return
if (clear) {
// 清除文本样式
['fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'textDecoration', 'color'].forEach((prop) => {
delete this.node.nodeData.data[prop]
})
} else {
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
})
// 优先使用原始宽高因为当设备的window.devicePixelRatio不为1时html2canvas输出的图片会更大
let imgNodeWidth = parent.attr('data-width') || canvas.width
let imgNodeHeight = parent.attr('data-height') || canvas.height
this.mindMap.el.removeChild(div)
let imgNode = new SvgImage()
.load(canvas.toDataURL())
.size(imgNodeWidth, imgNodeHeight)
.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()
} else {
resolve(null)
}
})
}
// 将所有节点转换成非富文本节点
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.command.clearHistory()
this.mindMap.command.addHistory()
this.mindMap.reRender()
}
// 插件被移除前做的事情
beforePluginRemove() {
this.transformAllNodesToNormalNode()
}
}
RichText.instanceName = 'richText'
export default RichText

View File

@@ -0,0 +1,172 @@
import { bfsWalk, throttle } from './utils'
// 选择节点类
class Select {
// 构造函数
constructor({ mindMap }) {
this.mindMap = mindMap
this.rect = null
this.isMousedown = false
this.mouseDownX = 0
this.mouseDownY = 0
this.mouseMoveX = 0
this.mouseMoveY = 0
this.bindEvent()
}
// 绑定事件
bindEvent() {
this.checkInNodes = throttle(this.checkInNodes, 500, this)
this.mindMap.on('mousedown', e => {
if (this.mindMap.opt.readonly) {
return
}
if (!e.ctrlKey && e.which !== 3) {
return
}
this.isMousedown = true
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
this.mouseDownX = x
this.mouseDownY = y
this.createRect(x, y)
})
this.mindMap.on('mousemove', e => {
if (this.mindMap.opt.readonly) {
return
}
if (!this.isMousedown) {
return
}
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
this.mouseMoveX = x
this.mouseMoveY = y
if (
Math.abs(x - this.mouseDownX) <= 10 &&
Math.abs(y - this.mouseDownY) <= 10
) {
return
}
clearTimeout(this.autoMoveTimer)
this.onMove(x, y)
})
this.mindMap.on('mouseup', () => {
if (this.mindMap.opt.readonly) {
return
}
if (!this.isMousedown) {
return
}
this.mindMap.emit(
'node_active',
null,
this.mindMap.renderer.activeNodeList
)
clearTimeout(this.autoMoveTimer)
this.isMousedown = false
if (this.rect) this.rect.remove()
this.rect = null
})
}
// 鼠标移动事件
onMove(x, y) {
// 绘制矩形
this.rect.plot([
[this.mouseDownX, this.mouseDownY],
[this.mouseMoveX, this.mouseDownY],
[this.mouseMoveX, this.mouseMoveY],
[this.mouseDownX, this.mouseMoveY]
])
this.checkInNodes()
// 检测边缘移动
let step = this.mindMap.opt.selectTranslateStep
let limit = this.mindMap.opt.selectTranslateLimit
let count = 0
// 左边缘
if (x <= this.mindMap.elRect.left + limit) {
this.mouseDownX += step
this.mindMap.view.translateX(step)
count++
}
// 右边缘
if (x >= this.mindMap.elRect.right - limit) {
this.mouseDownX -= step
this.mindMap.view.translateX(-step)
count++
}
// 上边缘
if (y <= this.mindMap.elRect.top + limit) {
this.mouseDownY += step
this.mindMap.view.translateY(step)
count++
}
// 下边缘
if (y >= this.mindMap.elRect.bottom - limit) {
this.mouseDownY -= step
this.mindMap.view.translateY(-step)
count++
}
if (count > 0) {
this.startAutoMove(x, y)
}
}
// 开启自动移动
startAutoMove(x, y) {
this.autoMoveTimer = setTimeout(() => {
this.onMove(x, y)
}, 20)
}
// 创建矩形
createRect(x, y) {
this.rect = this.mindMap.svg
.polygon()
.stroke({
color: '#0984e3'
})
.fill({
color: 'rgba(9,132,227,0.3)'
})
.plot([[x, y]])
}
// 检测在选区里的节点
checkInNodes() {
let { scaleX, scaleY, translateX, translateY } =
this.mindMap.draw.transform()
let minx = Math.min(this.mouseDownX, this.mouseMoveX)
let miny = Math.min(this.mouseDownY, this.mouseMoveY)
let maxx = Math.max(this.mouseDownX, this.mouseMoveX)
let maxy = Math.max(this.mouseDownY, this.mouseMoveY)
bfsWalk(this.mindMap.renderer.root, node => {
let { left, top, width, height } = node
let right = (left + width) * scaleX + translateX
let bottom = (top + height) * scaleY + translateY
left = left * scaleX + translateX
top = top * scaleY + translateY
if (left >= minx && right <= maxx && top >= miny && bottom <= maxy) {
this.mindMap.batchExecution.push('activeNode' + node.uid, () => {
if (node.nodeData.data.isActive) {
return
}
this.mindMap.renderer.setNodeActive(node, true)
this.mindMap.renderer.addActiveNode(node)
})
} else if (node.nodeData.data.isActive) {
this.mindMap.batchExecution.push('activeNode' + node.uid, () => {
if (!node.nodeData.data.isActive) {
return
}
this.mindMap.renderer.setNodeActive(node, false)
this.mindMap.renderer.removeActiveNode(node)
})
}
})
}
}
Select.instanceName = 'select'
export default Select

View File

@@ -0,0 +1,226 @@
// 节点形状类
export default class Shape {
constructor(node) {
this.node = node
}
// 形状需要的padding
getShapePadding(width, height, paddingX, paddingY) {
const shape = this.node.getShape()
const defaultPaddingX = 15
const defaultPaddingY = 5
const actWidth = width + paddingX * 2
const actHeight = height + paddingY * 2
const actOffset = Math.abs(actWidth - actHeight)
switch (shape) {
case 'roundedRectangle':
return {
paddingX: height > width ? (height - width) / 2 : 0,
paddingY: 0
}
case 'diamond':
return {
paddingX: width / 2,
paddingY: height / 2
}
case 'parallelogram':
return {
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
paddingY: 0
}
case 'outerTriangularRectangle':
return {
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
paddingY: 0
}
case 'innerTriangularRectangle':
return {
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
paddingY: 0
}
case 'ellipse':
return {
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
paddingY: paddingY <= 0 ? defaultPaddingY : 0
}
case 'circle':
return {
paddingX: actHeight > actWidth ? actOffset / 2 : 0,
paddingY: actHeight < actWidth ? actOffset / 2 : 0
}
default:
return {
paddingX: 0,
paddingY: 0
}
}
}
// 创建形状节点
createShape() {
const shape = this.node.getShape()
let { width, height } = this.node
let node = null
// 矩形
if (shape === 'rectangle') {
node = this.node.group.rect(width, height)
} else if (shape === 'diamond') {
// 菱形
node = this.createDiamond()
} else if (shape === 'parallelogram') {
// 平行四边形
node = this.createParallelogram()
} else if (shape === 'roundedRectangle') {
// 圆角矩形
node = this.createRoundedRectangle()
} else if (shape === 'octagonalRectangle') {
// 八角矩形
node = this.createOctagonalRectangle()
} else if (shape === 'outerTriangularRectangle') {
// 外三角矩形
node = this.createOuterTriangularRectangle()
} else if (shape === 'innerTriangularRectangle') {
// 内三角矩形
node = this.createInnerTriangularRectangle()
} else if (shape === 'ellipse') {
// 椭圆
node = this.createEllipse()
} else if (shape === 'circle') {
// 圆
node = this.createCircle()
}
return node
}
// 创建菱形
createDiamond() {
let { width, height } = this.node
let halfWidth = width / 2
let halfHeight = height / 2
let topX = halfWidth
let topY = 0
let rightX = width
let rightY = halfHeight
let bottomX = halfWidth
let bottomY = height
let leftX = 0
let leftY = halfHeight
return this.node.group.polygon(`
${topX}, ${topY}
${rightX}, ${rightY}
${bottomX}, ${bottomY}
${leftX}, ${leftY}
`)
}
// 创建平行四边形
createParallelogram() {
let { paddingX } = this.node.getPaddingVale()
paddingX = paddingX || this.node.shapePadding.paddingX
let { width, height } = this.node
return this.node.group.polygon(`
${paddingX}, ${0}
${width}, ${0}
${width - paddingX}, ${height}
${0}, ${height}
`)
}
// 创建圆角矩形
createRoundedRectangle() {
let { width, height } = this.node
let halfHeight = height / 2
return this.node.group.path(`
M${halfHeight},0
L${width - halfHeight},0
A${height / 2},${height / 2} 0 0,1 ${width - halfHeight},${height}
L${halfHeight},${height}
A${height / 2},${height / 2} 0 0,1 ${halfHeight},${0}
`)
}
// 创建八角矩形
createOctagonalRectangle() {
let w = 5
let { width, height } = this.node
return this.node.group.polygon(`
${0}, ${w}
${w}, ${0}
${width - w}, ${0}
${width}, ${w}
${width}, ${height - w}
${width - w}, ${height}
${w}, ${height}
${0}, ${height - w}
`)
}
// 创建外三角矩形
createOuterTriangularRectangle() {
let { paddingX } = this.node.getPaddingVale()
paddingX = paddingX || this.node.shapePadding.paddingX
let { width, height } = this.node
return this.node.group.polygon(`
${paddingX}, ${0}
${width - paddingX}, ${0}
${width}, ${height / 2}
${width - paddingX}, ${height}
${paddingX}, ${height}
${0}, ${height / 2}
`)
}
// 创建内三角矩形
createInnerTriangularRectangle() {
let { paddingX } = this.node.getPaddingVale()
paddingX = paddingX || this.node.shapePadding.paddingX
let { width, height } = this.node
return this.node.group.polygon(`
${0}, ${0}
${width}, ${0}
${width - paddingX / 2}, ${height / 2}
${width}, ${height}
${0}, ${height}
${paddingX / 2}, ${height / 2}
`)
}
// 创建椭圆
createEllipse() {
let { width, height } = this.node
let halfWidth = width / 2
let halfHeight = height / 2
return this.node.group.path(`
M${halfWidth},0
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
M${halfWidth},${height}
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
`)
}
// 创建圆
createCircle() {
let { width, height } = this.node
let halfWidth = width / 2
let halfHeight = height / 2
return this.node.group.path(`
M${halfWidth},0
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
M${halfWidth},${height}
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
`)
}
}
// 形状列表
export const shapeList = [
'rectangle',
'diamond',
'parallelogram',
'roundedRectangle',
'octagonalRectangle',
'outerTriangularRectangle',
'innerTriangularRectangle',
'ellipse',
'circle'
]

View File

@@ -0,0 +1,200 @@
import { tagColorList } from './utils/constant'
const rootProp = ['paddingX', 'paddingY']
// 样式类
class Style {
// 设置背景样式
static setBackgroundStyle(el, themeConfig) {
let { backgroundColor, backgroundImage, backgroundRepeat, backgroundPosition, backgroundSize } = themeConfig
el.style.backgroundColor = backgroundColor
if (backgroundImage) {
el.style.backgroundImage = `url(${backgroundImage})`
el.style.backgroundRepeat = backgroundRepeat
el.style.backgroundPosition = backgroundPosition
el.style.backgroundSize = backgroundSize
} else {
el.style.backgroundImage = 'none'
}
}
// 构造函数
constructor(ctx, themeConfig) {
this.ctx = ctx
this.themeConfig = themeConfig
}
// 更新主题配置
updateThemeConfig(themeConfig) {
this.themeConfig = themeConfig
}
// 合并样式
merge(prop, root, isActive) {
// 三级及以下节点
let defaultConfig = this.themeConfig.node
if (root || rootProp.includes(prop)) {
// 直接使用最外层样式
defaultConfig = this.themeConfig
} else if (this.ctx.isGeneralization) {
// 概要节点
defaultConfig = this.themeConfig.generalization
} else if (this.ctx.layerIndex === 0) {
// 根节点
defaultConfig = this.themeConfig.root
} else if (this.ctx.layerIndex === 1) {
// 二级节点
defaultConfig = this.themeConfig.second
}
// 激活状态
if (isActive !== undefined ? isActive : this.ctx.nodeData.data.isActive) {
if (
this.ctx.nodeData.data.activeStyle &&
this.ctx.nodeData.data.activeStyle[prop] !== undefined
) {
return this.ctx.nodeData.data.activeStyle[prop]
} else if (defaultConfig.active && defaultConfig.active[prop]) {
return defaultConfig.active[prop]
}
}
// 优先使用节点本身的样式
return this.getSelfStyle(prop) !== undefined
? this.getSelfStyle(prop)
: defaultConfig[prop]
}
// 获取某个样式值
getStyle(prop, root, isActive) {
return this.merge(prop, root, isActive)
}
// 获取自身自定义样式
getSelfStyle(prop) {
return this.ctx.nodeData.data[prop]
}
// 矩形
rect(node) {
this.shape(node)
node.radius(this.merge('borderRadius'))
}
// 矩形外的其他形状
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')
})
}
// 文字
text(node) {
node
.fill({
color: this.merge('color')
})
.css({
'font-family': this.merge('fontFamily'),
'font-size': this.merge('fontSize'),
'font-weight': this.merge('fontWeight'),
'font-style': this.merge('fontStyle'),
'text-decoration': this.merge('textDecoration')
})
}
// 获取文本样式
getTextFontStyle() {
return {
italic: this.merge('fontStyle') === 'italic',
bold: this.merge('fontWeight'),
fontSize: this.merge('fontSize'),
fontFamily: this.merge('fontFamily')
}
}
// html文字节点
domText(node, fontSizeScale = 1) {
node.style.fontFamily = this.merge('fontFamily')
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
node.style.fontWeight = this.merge('fontWeight') || 'normal'
node.style.lineHeight = this.merge('lineHeight')
node.style.fontStyle = this.merge('fontStyle')
}
// 标签文字
tagText(node, index) {
node
.fill({
color: tagColorList[index].color
})
.css({
'font-size': '12px'
})
}
// 标签矩形
tagRect(node, index) {
node.fill({
color: tagColorList[index].background
})
}
// 内置图标
iconNode(node) {
node.attr({
fill: this.merge('color')
})
}
// 连线
line(node, { width, color, dasharray } = {}) {
node.stroke({ width, color, dasharray }).fill({ color: 'none' })
}
// 概要连线
generalizationLine(node) {
node
.stroke({
width: this.merge('generalizationLineWidth', true),
color: this.merge('generalizationLineColor', true)
})
.fill({ color: 'none' })
}
// 按钮
iconBtn(node, fillNode) {
node.fill({ color: '#808080' })
fillNode.fill({ color: '#fff' })
}
}
export default Style

View File

@@ -0,0 +1,131 @@
import { getStrWithBrFromHtml } from './utils'
// 节点文字编辑类
export default class TextEdit {
// 构造函数
constructor(renderer) {
this.renderer = renderer
this.mindMap = renderer.mindMap
// 文本编辑框
this.textEditNode = null
// 文本编辑框是否显示
this.showTextEdit = false
this.bindEvent()
}
// 事件
bindEvent() {
this.show = this.show.bind(this)
// 节点双击事件
this.mindMap.on('node_dblclick', this.show)
// 点击事件
this.mindMap.on('draw_click', () => {
// 隐藏文本编辑框
this.hideEditTextBox()
})
// 展开收缩按钮点击事件
this.mindMap.on('expand_btn_click', () => {
this.hideEditTextBox()
})
// 节点激活前事件
this.mindMap.on('before_node_active', () => {
this.hideEditTextBox()
})
// 注册编辑快捷键
this.mindMap.keyCommand.addShortcut('F2', () => {
if (this.renderer.activeNodeList.length <= 0) {
return
}
this.show(this.renderer.activeNodeList[0])
})
}
// 注册临时快捷键
registerTmpShortcut() {
// 注册回车快捷键
this.mindMap.keyCommand.addShortcut('Enter', () => {
this.hideEditTextBox()
})
}
// 显示文本编辑框
show(node) {
let rect = node._textData.node.node.getBoundingClientRect()
if (this.mindMap.richText) {
this.mindMap.richText.showEditText(node, rect)
return
}
this.showEditTextBox(node, rect)
}
// 显示文本编辑框
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.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)
this.textEditNode.innerHTML = node.nodeData.data.text
.split(/\n/gim)
.join('<br>')
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
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()
}
// 选中文本
selectNodeText() {
let selection = window.getSelection()
let range = document.createRange()
range.selectNodeContents(this.textEditNode)
selection.removeAllRanges()
selection.addRange(range)
}
// 隐藏文本编辑框
hideEditTextBox() {
if (this.mindMap.richText) {
return this.mindMap.richText.hideEditText()
}
if (!this.showTextEdit) {
return
}
this.renderer.activeNodeList.forEach(node => {
let str = getStrWithBrFromHtml(this.textEditNode.innerHTML)
this.mindMap.execCommand('SET_NODE_TEXT', node, str)
if (node.isGeneralization) {
// 概要节点
node.generalizationBelongNode.updateGeneralization()
}
this.mindMap.render()
})
this.mindMap.emit(
'hide_text_edit',
this.textEditNode,
this.renderer.activeNodeList
)
this.textEditNode.style.display = 'none'
this.textEditNode.innerHTML = ''
this.textEditNode.style.fontFamily = 'inherit'
this.textEditNode.style.fontSize = 'inherit'
this.textEditNode.style.fontWeight = 'normal'
this.showTextEdit = false
}
}

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

@@ -0,0 +1,193 @@
// 视图操作类
class View {
// 构造函数
constructor(opt = {}) {
this.opt = opt
this.mindMap = this.opt.mindMap
this.scale = 1
this.sx = 0
this.sy = 0
this.x = 0
this.y = 0
this.firstDrag = true
this.setTransformData(this.mindMap.opt.viewData)
this.bind()
}
// 绑定
bind() {
// 快捷键
this.mindMap.keyCommand.addShortcut('Control+=', () => {
this.enlarge()
})
this.mindMap.keyCommand.addShortcut('Control+-', () => {
this.narrow()
})
this.mindMap.keyCommand.addShortcut('Control+Enter', () => {
this.reset()
})
this.mindMap.svg.on('dblclick', () => {
this.reset()
})
// 拖动视图
this.mindMap.event.on('mousedown', () => {
this.sx = this.x
this.sy = this.y
})
this.mindMap.event.on('drag', (e, event) => {
if (e.ctrlKey) {
// 按住ctrl键拖动为多选
return
}
if (this.firstDrag) {
this.firstDrag = false
// 清除激活节点
if (this.mindMap.renderer.activeNodeList.length > 0) {
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
}
}
this.x = this.sx + event.mousemoveOffset.x
this.y = this.sy + event.mousemoveOffset.y
this.transform()
})
this.mindMap.event.on('mouseup', () => {
this.firstDrag = true
})
// 放大缩小视图
this.mindMap.event.on('mousewheel', (e, dir) => {
if (this.mindMap.opt.customHandleMousewheel && typeof this.mindMap.opt.customHandleMousewheel === 'function') {
return this.mindMap.opt.customHandleMousewheel(e)
}
if (this.mindMap.opt.mousewheelAction === 'zoom') {
switch (dir) {
// 鼠标滚轮,向上和向左,都是缩小
case 'up':
case 'left':
this.narrow()
break
// 鼠标滚轮,向下和向右,都是放大
case 'down':
case 'right':
this.enlarge()
break
}
} else {
switch (dir){
// 上移
case 'down':
this.translateY(-this.mindMap.opt.mousewheelMoveStep)
break
// 下移
case 'up':
this.translateY(this.mindMap.opt.mousewheelMoveStep)
break
// 右移
case 'left':
this.translateX(-this.mindMap.opt.mousewheelMoveStep)
break
// 左移
case 'right':
this.translateX(this.mindMap.opt.mousewheelMoveStep)
break
}
}
})
}
// 获取当前变换状态数据
getTransformData() {
return {
transform: this.mindMap.draw.transform(),
state: {
scale: this.scale,
x: this.x,
y: this.y,
sx: this.sx,
sy: this.sy
}
}
}
// 动态设置变换状态数据
setTransformData(viewData) {
if (viewData) {
Object.keys(viewData.state).forEach(prop => {
this[prop] = viewData.state[prop]
})
this.mindMap.draw.transform({
...viewData.transform
})
this.mindMap.emit('view_data_change', this.getTransformData())
this.mindMap.emit('scale', this.scale)
}
}
// 平移x方向
translateX(step) {
this.x += step
this.transform()
}
// 平移x方式到
translateXTo(x) {
this.x = x
this.transform()
}
// 平移y方向
translateY(step) {
this.y += step
this.transform()
}
// 平移y方向到
translateYTo(y) {
this.y = y
this.transform()
}
// 应用变换
transform() {
this.mindMap.draw.transform({
scale: this.scale,
// origin: 'center center',
translate: [this.x, this.y]
})
this.mindMap.emit('view_data_change', this.getTransformData())
}
// 恢复
reset() {
this.scale = 1
this.x = 0
this.y = 0
this.transform()
}
// 缩小
narrow() {
if (this.scale - this.mindMap.opt.scaleRatio > 0.1) {
this.scale -= this.mindMap.opt.scaleRatio
} else {
this.scale = 0.1
}
this.transform()
this.mindMap.emit('scale', this.scale)
}
// 放大
enlarge() {
this.scale += this.mindMap.opt.scaleRatio
this.transform()
this.mindMap.emit('scale', this.scale)
}
// 设置缩放
setScale(scale) {
this.scale = scale
this.transform()
this.mindMap.emit('scale', this.scale)
}
}
export default View

View File

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

View File

@@ -0,0 +1,10 @@
.ql-editor {
overflow: hidden;
padding: 0;
height: auto;
}
.ql-container {
height: auto;
font-size: inherit;
}

View File

@@ -0,0 +1,201 @@
import Node from '../Node'
// 布局基类
class Base {
// 构造函数
constructor(renderer) {
// 渲染实例
this.renderer = renderer
// 控制实例
this.mindMap = renderer.mindMap
// 绘图对象
this.draw = this.mindMap.draw
// 根节点
this.root = null
}
// 计算节点位置
doLayout() {
throw new Error('【computed】方法为必要方法需要子类进行重写')
}
// 连线
renderLine() {
throw new Error('【renderLine】方法为必要方法需要子类进行重写')
}
// 定位展开收缩按钮
renderExpandBtn() {
throw new Error('【renderExpandBtn】方法为必要方法需要子类进行重写')
}
// 概要节点
renderGeneralization() {}
// 创建节点实例
createNode(data, parent, isRoot, layerIndex) {
// 创建节点
let newNode = null
// 复用节点
if (data && data._node && !this.renderer.reRender) {
newNode = data._node
newNode.reset()
newNode.layerIndex = layerIndex
} else {
// 创建新节点
newNode = new Node({
data,
uid: this.mindMap.uid++,
renderer: this.renderer,
mindMap: this.mindMap,
draw: this.draw,
layerIndex
})
newNode.getSize()
// 数据关联实际节点
data._node = newNode
if (data.data.isActive) {
this.renderer.addActiveNode(newNode)
}
}
// 根节点
if (isRoot) {
newNode.isRoot = true
this.root = newNode
} else {
// 互相收集
newNode.parent = parent._node
parent._node.addChildren(newNode)
}
return newNode
}
// 定位节点到画布中间
setNodeCenter(node) {
node.left = (this.mindMap.width - node.width) / 2
node.top = (this.mindMap.height - node.height) / 2
}
// 更新子节点属性
updateChildren(children, prop, offset) {
children.forEach(item => {
item[prop] += offset
if (item.children && item.children.length && !item.hasCustomPosition()) {
// 适配自定义位置
this.updateChildren(item.children, prop, offset)
}
})
}
// 二次贝塞尔曲线
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}`
}
// 三次贝塞尔曲线
cubicBezierPath(x1, y1, x2, y2) {
let cx1 = x1 + (x2 - x1) / 2
let cy1 = y1
let cx2 = cx1
let cy2 = y2
return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`
}
// 获取节点的marginX
getMarginX(layerIndex) {
return layerIndex === 1
? this.mindMap.themeConfig.second.marginX
: this.mindMap.themeConfig.node.marginX
}
// 获取节点的marginY
getMarginY(layerIndex) {
return layerIndex === 1
? this.mindMap.themeConfig.second.marginY
: this.mindMap.themeConfig.node.marginY
}
// 获取节点包括概要在内的宽度
getNodeWidthWithGeneralization(node) {
return Math.max(
node.width,
node.checkHasGeneralization() ? node._generalizationNodeWidth : 0
)
}
// 获取节点包括概要在内的高度
getNodeHeightWithGeneralization(node) {
return Math.max(
node.height,
node.checkHasGeneralization() ? node._generalizationNodeHeight : 0
)
}
// 获取节点的边界值
/**
* dir生长方向h水平、v垂直
* isLeft是否向左生长
*/
getNodeBoundaries(node, dir) {
let { generalizationLineMargin, generalizationNodeMargin } =
this.mindMap.themeConfig
let walk = root => {
let _left = Infinity
let _right = -Infinity
let _top = Infinity
let _bottom = -Infinity
if (root.children && root.children.length > 0) {
root.children.forEach(child => {
let { left, right, top, bottom } = walk(child)
// 概要内容的宽度
let generalizationWidth =
child.checkHasGeneralization() && child.nodeData.data.expand
? child._generalizationNodeWidth + generalizationNodeMargin
: 0
// 概要内容的高度
let generalizationHeight =
child.checkHasGeneralization() && child.nodeData.data.expand
? child._generalizationNodeHeight + generalizationNodeMargin
: 0
if (left - (dir === 'h' ? generalizationWidth : 0) < _left) {
_left = left - (dir === 'h' ? generalizationWidth : 0)
}
if (right + (dir === 'h' ? generalizationWidth : 0) > _right) {
_right = right + (dir === 'h' ? generalizationWidth : 0)
}
if (top < _top) {
_top = top
}
if (bottom + (dir === 'v' ? generalizationHeight : 0) > _bottom) {
_bottom = bottom + (dir === 'v' ? generalizationHeight : 0)
}
})
}
let cur = {
left: root.left,
right: root.left + root.width,
top: root.top,
bottom: root.top + root.height
}
return {
left: cur.left < _left ? cur.left : _left,
right: cur.right > _right ? cur.right : _right,
top: cur.top < _top ? cur.top : _top,
bottom: cur.bottom > _bottom ? cur.bottom : _bottom
}
}
let { left, right, top, bottom } = walk(node)
return {
left,
right,
top,
bottom,
generalizationLineMargin,
generalizationNodeMargin
}
}
}
export default Base

View File

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

View File

@@ -0,0 +1,289 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
// 逻辑结构图
class LogicalStructure extends Base {
// 构造函数
constructor(opt = {}) {
super(opt)
}
// 布局
doLayout(callback) {
let task = [
() => {
this.computedBaseValue()
},
() => {
this.computedTopValue()
},
() => {
this.adjustTopValue()
},
() => {
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 {
// 非根节点
// 定位到父节点右侧
newNode.left =
parent._node.left + parent._node.width + this.getMarginX(layerIndex)
}
if (!cur.data.expand) {
return true
}
},
(cur, parent, isRoot, layerIndex) => {
// 返回时计算节点的areaHeight也就是子节点所占的高度之和包括外边距
let len = cur.data.expand === false ? 0 : cur._node.children.length
cur._node.childrenAreaHeight = len
? cur._node.children.reduce((h, item) => {
return h + item.height
}, 0) +
(len + 1) * this.getMarginY(layerIndex + 1)
: 0
},
true,
0
)
}
// 遍历节点树计算节点的top
computedTopValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (
node.nodeData.data.expand &&
node.children &&
node.children.length
) {
let marginY = this.getMarginY(layerIndex + 1)
// 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半
let top = node.top + node.height / 2 - node.childrenAreaHeight / 2
let totalTop = top + marginY
node.children.forEach(cur => {
cur.top = totalTop
totalTop += cur.height + marginY
})
}
},
null,
true
)
}
// 调整节点top
adjustTopValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
return
}
// 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置
let difference =
node.childrenAreaHeight -
this.getMarginY(layerIndex + 1) * 2 -
node.height
if (difference > 0) {
this.updateBrothers(node, difference / 2)
}
},
null,
true
)
}
// 更新兄弟节点的top
updateBrothers(node, addHeight) {
if (node.parent) {
let childrenList = node.parent.children
let index = childrenList.findIndex(item => {
return item === node
})
childrenList.forEach((item, _index) => {
if (item === node || item.hasCustomPosition()) {
// 适配自定义位置
return
}
let _offset = 0
// 上面的节点往上移
if (_index < index) {
_offset = -addHeight
} else if (_index > index) {
// 下面的节点往下移
_offset = addHeight
}
item.top += _offset
// 同步更新子节点的位置
if (item.children && item.children.length) {
this.updateChildren(item.children, 'top', _offset)
}
})
// 更新父节点的位置
this.updateBrothers(node.parent, addHeight)
}
}
// 绘制连线,连接该节点到其子节点
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'curve') {
this.renderLineCurve(node, lines, style)
} else if (lineStyle === 'direct') {
this.renderLineDirect(node, lines, style)
} else {
this.renderLineStraight(node, lines, style)
}
}
// 直线风格连线
renderLineStraight(node, lines, style) {
if (node.children.length <= 0) {
return []
}
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}`
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 直连风格
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
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 曲线风格连线
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
let y1 = top + height / 2
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
} else {
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath
}
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 渲染按钮
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
)
}
// 创建概要节点
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 LogicalStructure

View File

@@ -0,0 +1,360 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
// 思维导图
class MindMap extends Base {
// 构造函数
// 在逻辑结构图的基础上增加一个变量来记录生长方向向左还是向右同时在计算left的时候根据方向来计算、调整top时只考虑同方向的节点即可
constructor(opt = {}) {
super(opt)
}
// 布局
doLayout(callback) {
let task = [
() => {
this.computedBaseValue()
},
() => {
this.computedTopValue()
},
() => {
this.adjustTopValue()
},
() => {
callback(this.root)
}
]
asyncRun(task)
}
// 遍历数据计算节点的left、width、height
computedBaseValue() {
walk(
this.renderer.renderTree,
null,
(cur, parent, isRoot, layerIndex, index) => {
let newNode = this.createNode(cur, parent, isRoot, layerIndex)
// 根节点定位在画布中心位置
if (isRoot) {
this.setNodeCenter(newNode)
} else {
// 非根节点
// 三级及以下节点以上级为准
if (parent._node.dir) {
newNode.dir = parent._node.dir
} else {
// 节点生长方向
newNode.dir = index % 2 === 0 ? 'right' : 'left'
}
// 根据生长方向定位到父节点的左侧或右侧
newNode.left =
newNode.dir === 'right'
? parent._node.left +
parent._node.width +
this.getMarginX(layerIndex)
: parent._node.left - this.getMarginX(layerIndex) - newNode.width
}
if (!cur.data.expand) {
return true
}
},
(cur, parent, isRoot, layerIndex) => {
// 返回时计算节点的leftChildrenAreaHeight和rightChildrenAreaHeight也就是左侧和右侧子节点所占的高度之和包括外边距
if (!cur.data.expand) {
cur._node.leftChildrenAreaHeight = 0
cur._node.rightChildrenAreaHeight = 0
return
}
// 理论上只有根节点是存在两个方向的子节点的,其他节点的子节点一定全都是同方向,但是为了逻辑统一,就不按特殊处理的方式来写了
let leftLen = 0
let rightLen = 0
let leftChildrenAreaHeight = 0
let rightChildrenAreaHeight = 0
cur._node.children.forEach(item => {
if (item.dir === 'left') {
leftLen++
leftChildrenAreaHeight += item.height
} else {
rightLen++
rightChildrenAreaHeight += item.height
}
})
cur._node.leftChildrenAreaHeight =
leftChildrenAreaHeight +
(leftLen + 1) * this.getMarginY(layerIndex + 1)
cur._node.rightChildrenAreaHeight =
rightChildrenAreaHeight +
(rightLen + 1) * this.getMarginY(layerIndex + 1)
},
true,
0
)
}
// 遍历节点树计算节点的top
computedTopValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (
node.nodeData.data.expand &&
node.children &&
node.children.length
) {
let marginY = this.getMarginY(layerIndex + 1)
let baseTop = node.top + node.height / 2 + marginY
// 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半
let leftTotalTop = baseTop - node.leftChildrenAreaHeight / 2
let rightTotalTop = baseTop - node.rightChildrenAreaHeight / 2
node.children.forEach(cur => {
if (cur.dir === 'left') {
cur.top = leftTotalTop
leftTotalTop += cur.height + marginY
} else {
cur.top = rightTotalTop
rightTotalTop += cur.height + marginY
}
})
}
},
null,
true
)
}
// 调整节点top
adjustTopValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
return
}
// 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置
let base = this.getMarginY(layerIndex + 1) * 2 + node.height
let leftDifference = node.leftChildrenAreaHeight - base
let rightDifference = node.rightChildrenAreaHeight - base
if (leftDifference > 0 || rightDifference > 0) {
this.updateBrothers(node, leftDifference / 2, rightDifference / 2)
}
},
null,
true
)
}
// 更新兄弟节点的top
updateBrothers(node, leftAddHeight, rightAddHeight) {
if (node.parent) {
// 过滤出和自己同方向的节点
let childrenList = node.parent.children.filter(item => {
return item.dir === node.dir
})
let index = childrenList.findIndex(item => {
return item === node
})
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
return
}
let _offset = 0
let addHeight = item.dir === 'left' ? leftAddHeight : rightAddHeight
// 上面的节点往上移
if (_index < index) {
_offset = -addHeight
} else if (_index > index) {
// 下面的节点往下移
_offset = addHeight
}
item.top += _offset
// 同步更新子节点的位置
if (item.children && item.children.length) {
this.updateChildren(item.children, 'top', _offset)
}
})
// 更新父节点的位置
this.updateBrothers(node.parent, leftAddHeight, rightAddHeight)
}
}
// 绘制连线,连接该节点到其子节点
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'curve') {
this.renderLineCurve(node, lines, style)
} else if (lineStyle === 'direct') {
this.renderLineDirect(node, lines, style)
} else {
this.renderLineStraight(node, lines, style)
}
}
// 直线风格连线
renderLineStraight(node, lines, style) {
if (node.children.length <= 0) {
return []
}
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
}
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}`
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 直连风格
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
: item.dir === 'left'
? left - expandBtnSize
: left + width + expandBtnSize
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
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 曲线风格连线
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
: item.dir === 'left'
? left - expandBtnSize
: left + width + 20
let y1 = top + height / 2
let x2 = item.dir === 'left' ? item.left + item.width : item.left
let y2 = item.top + item.height / 2
let path = ''
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
} else {
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath
}
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 渲染按钮
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
btn.translate(x, y)
}
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let isLeft = node.dir === 'left'
let {
top,
bottom,
left,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'h', isLeft)
let x = isLeft
? left - generalizationLineMargin
: right + generalizationLineMargin
let x1 = x
let y1 = top
let x2 = x
let y2 = bottom
let cx = x1 + (isLeft ? -20 : 20)
let cy = y1 + (y2 - y1) / 2
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
gLine.plot(path)
gNode.left =
x +
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -
(isLeft ? gNode.width : 0)
gNode.top = top + (bottom - top - gNode.height) / 2
}
}
export default MindMap

View File

@@ -0,0 +1,268 @@
import Base from './Base'
import { walk, asyncRun } from '../utils'
// 组织结构图
// 和逻辑结构图基本一样只是方向变成向下生长所以先计算节点的top后计算节点的left、最后调整节点的left即可
class OrganizationStructure extends Base {
// 构造函数
constructor(opt = {}) {
super(opt)
}
// 布局
doLayout(callback) {
let task = [
() => {
this.computedBaseValue()
},
() => {
this.computedLeftValue()
},
() => {
this.adjustLeftValue()
},
() => {
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 {
// 非根节点
// 定位到父节点下方
newNode.top =
parent._node.top + parent._node.height + this.getMarginX(layerIndex)
}
if (!cur.data.expand) {
return true
}
},
(cur, parent, isRoot, layerIndex) => {
// 返回时计算节点的areaWidth也就是子节点所占的宽度之和包括外边距
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.getMarginY(layerIndex + 1)
: 0
},
true,
0
)
}
// 遍历节点树计算节点的left
computedLeftValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (
node.nodeData.data.expand &&
node.children &&
node.children.length
) {
let marginX = this.getMarginY(layerIndex + 1)
// 第一个子节点的left值 = 该节点中心的left值 - 子节点的宽度之和的一半
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
})
}
},
null,
true
)
}
// 调整节点left
adjustLeftValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.nodeData.data.expand) {
return
}
// 判断子节点所占的宽度之和是否大于该节点自身,大于则需要调整位置
let difference =
node.childrenAreaWidth -
this.getMarginY(layerIndex + 1) * 2 -
node.width
if (difference > 0) {
this.updateBrothers(node, difference / 2)
}
},
null,
true
)
}
// 更新兄弟节点的left
updateBrothers(node, addWidth) {
if (node.parent) {
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 = -addWidth
} else if (_index > index) {
// 下面的节点往下移
_offset = addWidth
}
item.left += _offset
// 同步更新子节点的位置
if (item.children && item.children.length) {
this.updateChildren(item.children, 'left', _offset)
}
})
// 更新父节点的位置
this.updateBrothers(node.parent, addWidth)
}
}
// 绘制连线,连接该节点到其子节点
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'direct') {
this.renderLineDirect(node, lines, style)
} else {
this.renderLineStraight(node, lines, style)
}
}
// 直连风格
renderLineDirect(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height } = node
let x1 = left + width / 2
let y1 = top + height
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
lines[index].plot(path)
style && style(lines[index], item)
})
}
// 直线风格连线
renderLineStraight(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize, isRoot } = node
let x1 = left + width / 2
let y1 = top + height
let marginX = this.getMarginX(node.layerIndex + 1)
let s1 = marginX * 0.7
let minx = Infinity
let maxx = -Infinity
let len = node.children.length
node.children.forEach((item, index) => {
let x2 = item.left + item.width / 2
let y2 = y1 + s1 > item.top ? item.top + item.height : 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},${y2}` + nodeUseLineStylePath
lines[index].plot(path)
style && style(lines[index], item)
})
minx = Math.min(x1, minx)
maxx = Math.max(x1, maxx)
// 父节点的竖线
let line1 = this.draw.path()
node.style.line(line1)
expandBtnSize = len > 0 && !isRoot ? expandBtnSize : 0
line1.plot(`M ${x1},${y1 + expandBtnSize} 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)
}
}
// 渲染按钮
renderExpandBtn(node, btn) {
let { width, height, expandBtnSize } = node
let { translateX, translateY } = btn.transform()
btn.translate(
width / 2 - expandBtnSize / 2 - translateX,
height + expandBtnSize / 2 - translateY
)
}
// 创建概要节点
renderGeneralization(node, gLine, gNode) {
let {
bottom,
left,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeBoundaries(node, 'v')
let x1 = left
let y1 = bottom + generalizationLineMargin
let x2 = right
let y2 = bottom + generalizationLineMargin
let cx = x1 + (x2 - x1) / 2
let cy = y1 + 20
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
gLine.plot(path)
gNode.top = bottom + generalizationNodeMargin
gNode.left = left + (right - left - gNode.width) / 2
}
}
export default OrganizationStructure

View File

@@ -0,0 +1,7 @@
import { transformToMarkdown } from './toMarkdown'
import { transformMarkdownTo } from './markdownTo'
export default {
transformToMarkdown,
transformMarkdownTo
}

View File

@@ -0,0 +1,98 @@
import { fromMarkdown } from 'mdast-util-from-markdown'
// 处理list的情况
const handleList = node => {
let list = []
let walk = (arr, newArr) => {
for (let i = 0; i < arr.length; i++) {
let cur = arr[i]
let node = {}
node.data = {
// 节点内容
text: cur.children[0].children[0].value
}
node.children = []
newArr.push(node)
if (cur.children.length > 1) {
for (let j = 1; j < cur.children.length; j++) {
let cur2 = cur.children[j]
if (cur2.type === 'list') {
walk(cur2.children, node.children)
}
}
}
}
}
walk(node.children, list)
return list
}
// 将markdown转换成节点树
export const transformMarkdownTo = async md => {
const tree = fromMarkdown(md)
let root = {
children: []
}
let childrenQueue = [root.children]
let currentChildren = root.children
let depthQueue = [-1]
let currentDepth = -1
for (let i = 0; i < tree.children.length; i++) {
let cur = tree.children[i]
if (cur.type === 'heading') {
if (!cur.children[0]) continue
// 创建新节点
let node = {}
node.data = {
// 节点内容
text: cur.children[0].value
}
node.children = []
// 如果当前的层级大于上一个节点的层级,那么是其子节点
if (cur.depth > currentDepth) {
// 添加到上一个节点的子节点列表里
currentChildren.push(node)
// 更新当前栈和数据
childrenQueue.push(node.children)
currentChildren = node.children
depthQueue.push(cur.depth)
currentDepth = cur.depth
} else if (cur.depth === currentDepth) {
// 如果当前层级等于上一个节点的层级,说明它们是同级节点
// 将上一个节点出栈
childrenQueue.pop()
currentChildren = childrenQueue[childrenQueue.length - 1]
depthQueue.pop()
currentDepth = depthQueue[depthQueue.length - 1]
// 追加到上上个节点的子节点列表里
currentChildren.push(node)
// 更新当前栈和数据
childrenQueue.push(node.children)
currentChildren = node.children
depthQueue.push(cur.depth)
currentDepth = cur.depth
} else {
// 如果当前层级小于上一个节点的层级,那么一直出栈,直到遇到比当前层级小的节点
while (depthQueue.length) {
childrenQueue.pop()
currentChildren = childrenQueue[childrenQueue.length - 1]
depthQueue.pop()
currentDepth = depthQueue[depthQueue.length - 1]
if (currentDepth < cur.depth) {
// 追加到该节点的子节点列表里
currentChildren.push(node)
// 更新当前栈和数据
childrenQueue.push(node.children)
currentChildren = node.children
depthQueue.push(cur.depth)
currentDepth = cur.depth
break
}
}
}
} else if (cur.type === 'list') {
currentChildren.push(...handleList(cur))
}
}
return root.children[0]
}

View File

@@ -0,0 +1,53 @@
import { walk } from '../utils'
let el = null
const getText = str => {
if (!el) {
el = document.createElement('div')
}
el.innerHTML = str
return el.textContent
}
const getTitleMark = level => {
return new Array(level).fill('#').join('')
}
const getIndentMark = level => {
return new Array(level - 6).fill(' ').join('') + '*'
}
// 转换成markdown格式
export const transformToMarkdown = root => {
let content = ''
walk(
root,
null,
(node, parent, isRoot, layerIndex) => {
let level = layerIndex + 1
let text = node.data.richText ? getText(node.data.text) : node.data.text
if (level <= 6) {
content += getTitleMark(level)
} else {
content += getIndentMark(level)
}
content += ' ' + text
// 概要
let generalization = node.data.generalization
if (generalization && generalization.text) {
let generalizationText = generalization.richText
? getText(generalization.text)
: generalization.text
content += `[${generalizationText}]`
}
content += '\n\n'
// 备注
if (node.data.note) {
content += node.data.note + '\n\n'
}
},
() => {},
true
)
return content
}

View File

@@ -0,0 +1,164 @@
import JSZip from 'jszip'
import xmlConvert from 'xml-js'
// 解析.xmind文件
const parseXmindFile = file => {
return new Promise((resolve, reject) => {
JSZip.loadAsync(file).then(
async zip => {
try {
let content = ''
if (zip.files['content.json']) {
let json = await zip.files['content.json'].async('string')
content = transformXmind(json)
} else if (zip.files['content.xml']) {
let xml = await zip.files['content.xml'].async('string')
let json = xmlConvert.xml2json(xml)
content = transformOldXmind(json)
}
if (content) {
resolve(content)
} else {
reject(new Error('解析失败'))
}
} catch (error) {
reject(error)
}
},
e => {
reject(e)
}
)
})
}
// 转换xmind数据
const transformXmind = content => {
let data = JSON.parse(content)[0]
let nodeTree = data.rootTopic
let newTree = {}
let walk = (node, newNode) => {
newNode.data = {
// 节点内容
text: node.title
}
// 节点备注
if (node.notes) {
newNode.data.note = (node.notes.realHTML || node.notes.plain).content
}
// 超链接
if (node.href && /^https?:\/\//.test(node.href)) {
newNode.data.hyperlink = node.href
}
// 标签
if (node.labels && node.labels.length > 0) {
newNode.data.tag = node.labels
}
// 子节点
newNode.children = []
if (
node.children &&
node.children.attached &&
node.children.attached.length > 0
) {
node.children.attached.forEach(item => {
let newChild = {}
newNode.children.push(newChild)
walk(item, newChild)
})
}
}
walk(nodeTree, newTree)
return newTree
}
// 转换旧版xmind数据xmind8
const transformOldXmind = content => {
let data = JSON.parse(content)
let elements = data.elements
let root = null
let getRoot = arr => {
for (let i = 0; i < arr.length; i++) {
if (!root && arr[i].name === 'topic') {
root = arr[i]
return
}
}
arr.forEach(item => {
getRoot(item.elements)
})
}
getRoot(elements)
let newTree = {}
let getItemByName = (arr, name) => {
return arr.find(item => {
return item.name === name
})
}
let walk = (node, newNode) => {
let nodeElements = node.elements
newNode.data = {
// 节点内容
text: getItemByName(nodeElements, 'title').elements[0].text
}
try {
// 节点备注
let notesElement = getItemByName(nodeElements, 'notes')
if (notesElement) {
newNode.data.note =
notesElement.elements[0].elements[0].elements[0].text
}
} catch (error) {
console.log(error)
}
try {
// 超链接
if (
node.attributes &&
node.attributes['xlink:href'] &&
/^https?:\/\//.test(node.attributes['xlink:href'])
) {
newNode.data.hyperlink = node.attributes['xlink:href']
}
} catch (error) {
console.log(error)
}
try {
// 标签
let labelsElement = getItemByName(nodeElements, 'labels')
if (labelsElement) {
newNode.data.tag = labelsElement.elements.map(item => {
return item.elements[0].text
})
}
} catch (error) {
console.log(error)
}
// 子节点
newNode.children = []
let _children = getItemByName(nodeElements, 'children')
if (_children && _children.elements && _children.elements.length > 0) {
_children.elements.forEach(item => {
if (item.name === 'topics') {
item.elements.forEach(item2 => {
let newChild = {}
newNode.children.push(newChild)
walk(item2, newChild)
})
} else {
let newChild = {}
newNode.children.push(newChild)
walk(item, newChild)
}
})
}
}
walk(root, newTree)
return newTree
}
export default {
parseXmindFile,
transformXmind,
transformOldXmind
}

View File

@@ -1,18 +1,10 @@
/**
* @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 {
open,
close
}
open,
close
}

View File

@@ -0,0 +1,297 @@
// 超链接图标
const hyperlink =
'<svg t="1624174958075" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7982" ><path d="M435.484444 251.733333v68.892445L295.822222 320.682667a168.504889 168.504889 0 0 0-2.844444 336.952889h142.506666v68.892444H295.822222a237.397333 237.397333 0 0 1 0-474.794667h139.662222z m248.945778 0a237.397333 237.397333 0 0 1 0 474.851556H544.654222v-69.006222l139.776 0.056889a168.504889 168.504889 0 0 0 2.844445-336.952889H544.597333V251.676444h139.776z m-25.827555 203.946667a34.474667 34.474667 0 0 1 0 68.892444H321.649778a34.474667 34.474667 0 0 1 0-68.892444h336.952889z" p-id="7983"></path></svg>'
// 备注图标
const note =
'<svg t="1624195132675" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8792" ><path d="M152.768 985.984 152.768 49.856l434.56 0 66.816 0 234.048 267.392 0 66.816 0 601.92L152.768 985.984 152.768 985.984zM654.144 193.088l0 124.16 108.736 0L654.144 193.088 654.144 193.088zM821.312 384.064l-167.168 0L587.328 384.064 587.328 317.312 587.328 116.736 219.584 116.736 219.584 919.04l601.728 0L821.312 384.064 821.312 384.064zM386.688 517.888 319.808 517.888 319.808 450.944l66.816 0L386.624 517.888 386.688 517.888zM386.688 651.584 319.808 651.584 319.808 584.704l66.816 0L386.624 651.584 386.688 651.584zM386.688 785.344 319.808 785.344l0-66.88 66.816 0L386.624 785.344 386.688 785.344zM721.024 517.888 453.632 517.888 453.632 450.944l267.392 0L721.024 517.888 721.024 517.888zM654.144 651.584 453.632 651.584 453.632 584.704l200.512 0L654.144 651.584 654.144 651.584zM620.672 785.344l-167.04 0 0-66.88 167.04 0L620.672 785.344 620.672 785.344z" p-id="8793"></path></svg>'
// 节点icon
export const nodeIconList = [
{
name: '优先级图标',
type: 'priority',
list: [
{
name: '1',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512.042667 1024C229.248 1024 0 794.794667 0 511.957333 0 229.205333 229.248 0 512.042667 0 794.752 0 1024 229.205333 1024 511.957333 1024 794.794667 794.752 1024 512.042667 1024z" fill="#E93B30"></path><path d="M580.309333 256h-75.52c-10.666667 29.824-30.165333 55.765333-58.709333 78.165333-28.416 22.314667-54.869333 37.418667-79.146667 45.397334v84.608a320 320 0 0 0 120.234667-70.698667v352.085333H580.266667V256z" fill="#FFFFFF"></path></svg>`
},
{
name: '2',
icon: `<svg viewBox="0 0 1024 1024"><path d="M511.957333 1024C229.248 1024 0 794.752 0 512S229.248 0 511.957333 0C794.752 0 1024 229.248 1024 512s-229.248 512-512.042667 512z" fill="#FA8D2E"></path><path d="M667.946667 658.602667h-185.301334c4.864-8.533333 11.178667-17.066667 19.072-25.984 7.808-8.874667 26.453333-26.837333 55.936-53.888 29.525333-27.008 49.877333-47.786667 61.226667-62.165334 16.981333-21.717333 29.44-42.453333 37.290667-62.293333 7.808-19.84 11.776-40.746667 11.776-62.677333 0-38.570667-13.738667-70.741333-41.088-96.725334C599.466667 268.928 561.706667 256 513.834667 256c-43.690667 0-80.128 11.136-109.354667 33.578667-29.098667 22.4-46.506667 59.306667-52.010667 110.805333l93.184 9.301333c1.792-27.349333 8.405333-46.890667 19.754667-58.624 11.434667-11.776 26.837333-17.664 46.165333-17.664 19.541333 0 34.858667 5.589333 45.909334 16.768 11.136 11.264 16.682667 27.221333 16.682666 48.042667 0 18.858667-6.4 37.930667-19.242666 57.258667-9.472 14.037333-35.157333 40.533333-77.098667 79.872-52.096 48.554667-87.04 87.509333-104.704 116.821333A226.688 226.688 0 0 0 341.333333 745.429333h326.613334v-86.826666z" fill="#FFFFFF"></path></svg>`
},
{
name: '3',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0z" fill="#2E66FA"></path><path d="M627.754667 731.733333c-29.354667 25.088-66.901333 37.632-112.725334 37.632-44.928 0-81.792-11.52-110.592-34.773333-33.066667-26.538667-49.877333-64.469333-50.304-114.133333h92.16c0.426667 21.76 7.552 38.314667 21.333334 49.664 12.288 10.88 28.117333 16.341333 47.402666 16.341333 20.309333 0 36.778667-6.101333 49.322667-18.432 12.544-12.330667 18.773333-29.568 18.773333-51.797333 0-21.290667-6.229333-38.186667-18.773333-50.773334-12.544-12.501333-29.866667-18.773333-52.138667-18.773333h-13.525333v-80.042667H512c42.112 0 63.274667-21.034667 63.274667-63.146666 0-20.309333-5.888-36.096-17.706667-47.445334a60.757333 60.757333 0 0 0-43.818667-17.066666c-17.493333 0-32 5.504-43.434666 16.298666-11.562667 10.88-17.792 25.728-18.773334 44.714667H359.68c0.981333-43.946667 16.042667-78.976 45.397333-104.96 29.354667-25.941333 65.706667-39.04 109.226667-39.04 44.928 0 81.792 13.525333 110.592 40.490667 28.8 26.922667 43.306667 61.610667 43.306667 104.149333 0 48.213333-19.413333 82.688-58.154667 103.552 43.52 23.125333 65.28 61.44 65.28 114.858667 0 48.128-15.957333 85.76-47.573333 112.682666z" fill="#FFFFFF"></path></svg>`
},
{
name: '4',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512.042667 1024C229.248 1024 0 794.794667 0 512.042667 0 229.205333 229.248 0 512.042667 0 794.752 0 1024 229.205333 1024 512.042667 1024 794.794667 794.752 1024 512.042667 1024z" fill="#6D768D"></path><path d="M600.96 256v309.802667h60.117333v81.536h-60.16v98.218666h-90.154666v-98.218666H311.466667v-81.237334L522.666667 256h78.293333zM510.72 399.104l-112.042667 166.698667h112.042667V399.104z" fill="#FFFFFF"></path></svg>`
},
{
name: '5',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512.042667 1024C229.248 1024 0 794.794667 0 512.042667 0 229.205333 229.248 0 512.042667 0 794.752 0 1024 229.205333 1024 512.042667 1024 794.794667 794.752 1024 512.042667 1024z" fill="#6D768D"></path><path d="M470.912 343.552h175.786667V256H400.256l-47.786667 253.952 75.434667 10.837333c21.205333-23.552 45.269333-35.413333 72.021333-35.413333 21.546667 0 38.997333 7.509333 52.437334 22.4 13.312 15.018667 20.053333 37.418667 20.053333 67.328 0 31.872-6.741333 55.765333-20.181333 71.552-13.397333 15.872-29.866667 23.765333-49.237334 23.765333-17.066667 0-32.085333-6.186667-45.013333-18.432-13.013333-12.373333-20.821333-29.013333-23.466667-50.133333L341.333333 611.498667c5.546667 40.874667 22.485333 73.429333 50.730667 97.621333 28.330667 24.32 64.938667 36.437333 109.866667 36.437333 56.149333 0 100.053333-21.546667 131.754666-64.554666a176.64 176.64 0 0 0 34.816-107.52c0-48.042667-14.378667-87.210667-43.221333-117.333334-28.8-30.208-63.957333-45.312-105.514667-45.312-21.674667 0-42.922667 5.248-63.829333 15.616l14.976-82.901333z" fill="#FFFFFF"></path></svg>`
},
{
name: '6',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 1024C229.248 1024 0 794.794667 0 512.042667 0 229.205333 229.248 0 512 0c282.88 0 512 229.205333 512 512.042667C1024 794.794667 794.88 1024 512 1024z" fill="#6D768D"></path><path d="M519.210667 256c36.992 0 67.626667 10.368 91.776 31.189333 24.192 20.821333 39.68 51.029333 46.293333 90.709334l-90.197333 9.984c-2.176-18.56-7.978667-32.298667-17.28-41.173334-9.258667-8.874667-21.418667-13.226667-36.224-13.226666-19.754667 0-36.437333 8.789333-50.048 26.453333-13.696 17.664-22.314667 54.613333-25.856 110.549333 23.296-27.52 52.138667-41.258667 86.656-41.258666 38.997333 0 72.362667 14.805333 100.181333 44.544 27.733333 29.696 41.685333 68.010667 41.685333 114.858666 0 49.877333-14.634667 89.856-43.818666 119.936-29.226667 30.208-66.730667 45.226667-112.554667 45.226667-49.066667 0-89.429333-19.072-121.130667-57.344C357.12 658.218667 341.333333 595.541333 341.333333 508.416c0-89.344 16.469333-153.813333 49.493334-193.194667C423.722667 275.754667 466.56 256 519.168 256z m-9.472 241.834667c-17.962667 0-33.066667 6.997333-45.525334 21.12-12.330667 14.037333-18.56 34.858667-18.56 62.293333 0 30.421333 6.912 53.76 20.906667 70.4 13.952 16.469333 29.866667 24.746667 47.786667 24.746667 17.28 0 31.701333-6.826667 43.178666-20.309334 11.52-13.525333 17.237333-35.669333 17.237334-66.56 0-31.658667-6.186667-54.869333-18.517334-69.546666a58.197333 58.197333 0 0 0-46.506666-22.144z" fill="#FFFFFF"></path></svg>`
},
{
name: '7',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512.042667 1024C229.248 1024 0 794.752 0 512S229.248 0 512.042667 0C794.752 0 1024 229.248 1024 512s-229.248 512-511.957333 512z" fill="#6D768D"></path><path d="M673.024 273.066667H354.133333v86.869333h212.224a691.2 691.2 0 0 0-104.746666 187.989333c-26.026667 70.101333-39.978667 138.88-41.429334 206.293334h89.6c-0.298667-42.922667 6.698667-91.776 21.034667-146.474667a654.72 654.72 0 0 1 62.08-154.965333c27.136-48.554667 53.888-85.76 80.128-111.701334V273.066667z" fill="#FFFFFF"></path></svg>`
},
{
name: '8',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 1024C229.248 1024 0 794.752 0 512S229.248 0 512 0s512 229.248 512 512-229.248 512-512 512z" fill="#6D768D"></path><path d="M512.426667 256c46.208 0 82.048 11.861333 107.605333 35.541333 25.6 23.68 38.314667 53.674667 38.314667 89.898667 0 22.613333-5.802667 42.666667-17.578667 60.330667a111.445333 111.445333 0 0 1-49.450667 40.277333c26.965333 10.837333 47.36 26.752 61.312 47.658667 13.994667 20.906667 21.034667 45.013333 21.034667 72.362666 0 45.098667-14.336 81.834667-42.965333 109.952-28.586667 28.245333-66.602667 42.368-114.090667 42.368-44.245333 0-81.066667-11.648-110.464-34.986666-34.645333-27.52-52.010667-65.28-52.010667-113.365334 0-26.368 6.528-50.645333 19.626667-72.746666 13.056-22.144 33.578667-39.210667 61.696-51.242667-24.064-10.154667-41.557333-24.192-52.48-41.941333a109.824 109.824 0 0 1-16.512-58.666667c0-36.224 12.757333-66.218667 37.973333-89.898667 25.386667-23.68 61.354667-35.541333 108.032-35.541333z m1.28 265.429333c-22.784 0-39.722667 7.978667-50.901334 23.893334-11.136 15.786667-16.64 33.066667-16.64 51.498666 0 25.984 6.485333 46.208 19.712 60.714667 13.098667 14.506667 29.525333 21.802667 49.152 21.802667 19.242667 0 35.157333-6.997333 47.786667-20.992 12.629333-13.909333 18.858667-34.048 18.858667-60.416 0-23.082667-6.314667-41.557333-19.2-55.466667a63.274667 63.274667 0 0 0-48.725334-21.034667z m-0.341334-191.488c-17.792 0-32 5.333333-42.581333 16-10.538667 10.666667-15.872 24.746667-15.872 42.325334 0 18.645333 5.248 33.152 15.701333 43.648 10.453333 10.453333 24.362667 15.658667 41.770667 15.658666 17.664 0 31.658667-5.290667 42.24-15.872 10.538667-10.581333 15.872-25.173333 15.872-43.818666 0-17.493333-5.248-31.573333-15.701333-42.154667s-24.277333-15.786667-41.429334-15.786667z" fill="#FFFFFF"></path></svg>`
},
{
name: '9',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 1024C229.248 1024 0 794.794667 0 512.042667 0 229.333333 229.248 0 512 0c282.88 0 512 229.333333 512 512.042667C1024 794.794667 794.88 1024 512 1024z" fill="#6D768D"></path><path d="M497.28 256c49.365333 0 89.856 19.157333 121.429333 57.429333 31.701333 38.229333 47.488 101.205333 47.488 188.842667 0 89.173333-16.384 153.386667-49.365333 192.853333-32.853333 39.594667-75.605333 59.264-128.426667 59.264-37.888 0-68.608-10.154667-91.989333-30.506666s-38.4-50.816-45.013333-91.306667l90.112-9.984c2.261333 18.474667 8.021333 32.085333 17.28 41.088 9.173333 8.874667 21.418667 13.312 36.608 13.312 19.2 0 35.541333-8.874667 48.981333-26.752 13.44-17.749333 22.016-54.613333 25.770667-110.549333-23.466667 27.264-52.821333 40.874667-88.064 40.874666-38.314667 0-71.253333-14.72-99.114667-44.330666C355.242667 506.709333 341.333333 468.224 341.333333 420.864c0-49.493333 14.592-89.258667 43.946667-119.466667C414.549333 271.104 451.925333 256 497.237333 256z m-4.352 77.482667c-17.237333 0-31.658667 6.826667-43.008 20.437333-11.477333 13.653333-17.194667 35.84-17.194667 66.816 0 31.402667 6.229333 54.485333 18.645334 69.205333 12.458667 14.72 27.946667 22.101333 46.592 22.101334 18.005333 0 33.066667-7.082667 45.44-21.205334 12.330667-14.208 18.432-35.029333 18.432-62.506666 0-29.994667-6.912-53.376-20.821334-69.973334-13.824-16.597333-29.866667-24.874667-48.085333-24.874666z" fill="#FFFFFF"></path></svg>`
},
{
name: '10',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512.042667 1024C229.248 1024 0 794.794667 0 511.957333 0 229.205333 229.248 0 512.042667 0 794.752 0 1024 229.205333 1024 511.957333 1024 794.794667 794.752 1024 512.042667 1024z" fill="#6D768D"></path><path d="M619.946667 273.066667c46.976 0 83.754667 16.042667 110.250666 48.042666 31.573333 37.973333 47.36 100.864 47.36 188.672 0 87.722667-15.829333 150.698667-47.658666 189.056-26.325333 31.616-62.976 47.36-109.952 47.36-47.274667 0-85.418667-17.237333-114.346667-51.968-28.885333-34.602667-43.392-96.426667-43.392-185.386666 0-87.168 15.872-150.016 47.701333-188.416 26.282667-31.488 62.933333-47.36 110.037334-47.36z m-207.488 12.8v452.266666H325.504V411.690667A299.904 299.904 0 0 1 213.333333 476.373333V398.933333c22.656-7.296 47.36-21.12 73.856-41.514666 26.624-20.522667 44.842667-44.288 54.784-71.552h70.485334z m207.488 60.842666c-11.306667 0-21.461333 3.413333-30.336 10.24-8.874667 6.826667-15.786667 19.157333-20.693334 36.864-6.4 22.997333-9.642667 61.653333-9.642666 115.968 0 54.442667 2.944 91.733333 8.661333 112.128 5.802667 20.352 13.098667 33.877333 21.845333 40.618667 8.789333 6.741333 18.858667 10.154667 30.165334 10.154667 11.349333 0 21.376-3.498667 30.250666-10.325334 8.874667-6.826667 15.786667-19.157333 20.693334-36.778666 6.4-22.826667 9.642667-61.354667 9.642666-115.797334 0-54.314667-2.858667-91.648-8.661333-112.042666-5.802667-20.352-13.013333-33.962667-21.76-40.789334a47.616 47.616 0 0 0-30.165333-10.24z" fill="#FFFFFF"></path></svg>`
}
]
},
{
name: '进度图标',
type: 'progress',
list: [
{
name: '1',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0z" fill="#12BB37"></path><path d="M512 928c-229.76 0-416-186.24-416-416S282.24 96 512 96V512l294.144-294.144A414.72 414.72 0 0 1 928 512c0 229.76-186.24 416-416 416z" fill="#FFFFFF"></path></svg>`
},
{
name: '2',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0z" fill="#12BB37"></path><path d="M512 928c-229.76 0-416-186.24-416-416S282.24 96 512 96V512h416c0 229.76-186.24 416-416 416z" fill="#FFFFFF"></path></svg>`
},
{
name: '3',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0z" fill="#12BB37"></path><path d="M512 928c-229.76 0-416-186.24-416-416S282.24 96 512 96V512l294.144 294.144A414.72 414.72 0 0 1 512 928z" fill="#FFFFFF"></path></svg>`
},
{
name: '4',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0z" fill="#12BB37"></path><path d="M512 928c-229.76 0-416-186.24-416-416S282.24 96 512 96v832z" fill="#FFFFFF"></path></svg>`
},
{
name: '5',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0z" fill="#12BB37"></path><path d="M512 512l-294.144 294.144A414.72 414.72 0 0 1 96 512c0-229.76 186.24-416 416-416V512z" fill="#FFFFFF"></path></svg>`
},
{
name: '6',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0z" fill="#12BB37"></path><path d="M512 512H96c0-229.76 186.24-416 416-416V512z" fill="#FFFFFF"></path></svg>`
},
{
name: '7',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0z" fill="#12BB37"></path><path d="M512 512L217.856 217.856A414.72 414.72 0 0 1 512 96V512z" fill="#FFFFFF"></path></svg>`
},
{
name: '8',
icon: `<svg viewBox="0 0 1024 1024"><path d="M0 512c0 282.752 229.248 512 512 512s512-229.248 512-512S794.752 0 512 0 0 229.248 0 512z" fill="#12BB37"></path><path d="M716.629333 341.333333h-51.328a35.072 35.072 0 0 0-28.330666 14.293334l-171.989334 233.984-77.909333-106.026667a35.2 35.2 0 0 0-28.330667-14.293333H307.413333c-7.082667 0-11.264 7.936-7.082666 13.653333l136.32 185.472a35.2 35.2 0 0 0 56.533333 0l230.4-313.429333a8.533333 8.533333 0 0 0-6.954667-13.653334z" fill="#FFFFFF"></path></svg>`
}
]
},
{
name: '表情图标',
type: 'expression',
list: [
{
name: '1',
icon: `<svg t="1624457751393" class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12255"><path d="M1.097856 1.097642h1021.804717v1021.804716H1.097856z" fill="#F09495" p-id="12256"></path><path d="M1024.000214 1024H0.000214V0h1024v1024z m-1021.804716-2.195284h1019.609433V2.195284H2.195498v1019.609432z" fill="#FFFFFF" p-id="12257"></path><path d="M234.695985 335.179887m-27.341259 0a27.341259 27.341259 0 1 0 54.682518 0 27.341259 27.341259 0 1 0-54.682518 0Z" fill="#040000" p-id="12258"></path><path d="M234.695985 363.519002c-15.666342 0-28.339115-12.772559-28.339115-28.339115 0-15.666342 12.772559-28.339115 28.339115-28.339115s28.339115 12.772559 28.339115 28.339115c0.099786 15.666342-12.672773 28.339115-28.339115 28.339115z m0-54.582732c-14.468914 0-26.243617 11.774703-26.243617 26.243617s11.774703 26.243617 26.243617 26.243617 26.243617-11.774703 26.243617-26.243617-11.774703-26.243617-26.243617-26.243617z" fill="#FFFFFF" p-id="12259"></path><path d="M776.232528 335.179887m-27.341259 0a27.341259 27.341259 0 1 0 54.682518 0 27.341259 27.341259 0 1 0-54.682518 0Z" fill="#040000" p-id="12260"></path><path d="M776.232528 363.519002c-15.666342 0-28.339115-12.772559-28.339115-28.339115 0-15.666342 12.772559-28.339115 28.339115-28.339115 15.666342 0 28.339115 12.772559 28.339115 28.339115 0 15.666342-12.772559 28.339115-28.339115 28.339115z m0-54.582732c-14.468914 0-26.243617 11.774703-26.243617 26.243617s11.774703 26.243617 26.243617 26.243617 26.243617-11.774703 26.243617-26.243617c-0.099786-14.468914-11.874488-26.243617-26.243617-26.243617z" fill="#FFFFFF" p-id="12261"></path><path d="M512.000214 671.656987c-52.58702 0-105.872539-17.961411-105.872539-52.387449S459.413194 566.882089 512.000214 566.882089s105.872539 17.961411 105.87254 52.387449S564.587234 671.656987 512.000214 671.656987z m0-74.240499c-21.952836 0-43.207172 3.592282-58.2748 9.77899-13.870201 5.68778-17.06334 11.275775-17.06334 12.07406s3.19314 6.386279 17.06334 12.07406c15.067628 6.186708 36.321965 9.77899 58.2748 9.77899s43.207172-3.592282 58.274801-9.77899c13.870201-5.68778 17.06334-11.275775 17.06334-12.07406s-3.19314-6.386279-17.06334-12.07406c-15.067628-6.286494-36.321965-9.77899-58.274801-9.77899z" fill="#040000" p-id="12262"></path></svg>`
},
{
name: '2',
icon: `<svg t="1624457767572" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1048"><path d="M0 0h1024v1024H0z" fill="#E6A6C9" p-id="1049"></path><path d="M315.1 368.1c-23.9 0-43.3-19.4-43.3-43.3s19.4-43.3 43.3-43.3 43.3 19.4 43.3 43.3-19.4 43.3-43.3 43.3z m0-74.7c-17.3 0-31.3 14.1-31.3 31.3 0 17.3 14.1 31.3 31.3 31.3 17.3 0 31.3-14.1 31.3-31.3 0-17.2-14-31.3-31.3-31.3zM738.7 368.1c-23.9 0-43.3-19.4-43.3-43.3s19.4-43.3 43.3-43.3 43.3 19.4 43.3 43.3-19.4 43.3-43.3 43.3z m0-74.7c-17.3 0-31.3 14.1-31.3 31.3 0 17.3 14.1 31.3 31.3 31.3 17.3 0 31.3-14.1 31.3-31.3 0-17.2-14-31.3-31.3-31.3zM293.5 698.8l-14.5-1.3c0.1-0.6 1.5-14.6 15.1-27.9 17.2-16.7 45-24.8 82.7-24 4.9-0.1 10.9-10.5 16.1-19.6 8.4-14.7 19-33.1 37.9-34.3 19.4-1.2 42.2 16.4 71.5 55.4 9.9 5.2 16.5 11.2 21.8 16.1 8.4 7.7 13.1 11.9 25.1 10.8 14.9-1.4 38.9-11.1 77.5-31.4 26.8-28.4 56.4-41.4 83.5-36.6 27.9 4.9 50.6 27.6 67.5 67.5l-13.4 5.7c-14.7-34.5-34.3-54.9-56.7-58.8-22.3-3.9-47.6 7.8-71.2 33.1l-0.8 0.9-1.1 0.6c-85.6 45.1-99.4 38-120.2 19.1-5.5-5-11.2-10.2-20.1-14.7l-1.5-0.8-1-1.4c-32.2-43.2-50.4-51.6-60-51-11.1 0.7-18.8 14-26.2 27-7.6 13.2-15.4 26.9-28.8 26.9h-0.2c-78.4-1.6-83 38.3-83 38.7z" fill="#040000" p-id="1050"></path></svg>`
},
{
name: '3',
icon: `<svg t="1624457776082" class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1204" ><path d="M1.1 1.097642h1021.804716v1021.804716H1.1z" fill="#F7E983" p-id="1205"></path><path d="M1024.002358 1024H0.002358V0h1024v1024z m-1021.804716-2.195284h1019.609433V2.195284H2.197642v1019.609432z" fill="#FFFFFF" p-id="1206"></path><path d="M329.174412 344.491728a38.118106 10.277919 57.6 1 0 17.355867-11.014369 38.118106 10.277919 57.6 1 0-17.355867 11.014369Z" fill="#040000" p-id="1207"></path><path d="M644.769475 355.956059a11.175989 36.321965 30 1 0 36.321965-62.911488 11.175989 36.321965 30 1 0-36.321965 62.911488Z" fill="#040000" p-id="1208"></path><path d="M569.678445 671.158059c-26.343403 0-51.190021-5.288638-70.049503-14.967843-20.755408-10.577275-32.230754-25.445332-32.230755-41.710388 0-16.265056 11.475346-31.133112 32.230755-41.710387 18.859482-9.579419 43.805886-14.967843 70.049503-14.967843s51.190021 5.288638 70.049503 14.967843c20.755408 10.577275 32.230754 25.445332 32.230754 41.710387 0 16.265056-11.475346 31.133112-32.230754 41.710388-18.859482 9.679205-43.805886 14.967843-70.049503 14.967843z m0-95.095693c-49.693237 0-84.318846 20.356266-84.318846 38.517248s34.625609 38.517248 84.318846 38.517248 84.318846-20.356266 84.318846-38.517248-34.725395-38.517248-84.318846-38.517248z" fill="#040000" p-id="1209"></path></svg>`
},
{
name: '4',
icon: `<svg t="1624457781889" class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1363" ><path d="M1.1 1.097642h1021.804716v1021.804716H1.1z" fill="#A6D9E2" p-id="1364"></path><path d="M1024.002358 1024H0.002358V0h1024v1024z m-1021.804716-2.195284h1019.609433V2.195284H2.197642v1019.609432z" fill="#FFFFFF" p-id="1365"></path><path d="M376.194134 348.950302m-23.44962 0a23.44962 23.44962 0 1 0 46.89924 0 23.44962 23.44962 0 1 0-46.89924 0Z" fill="#040000" p-id="1366"></path><path d="M629.150672 348.950302m-24.647047 0a24.647047 24.647047 0 1 0 49.294095 0 24.647047 24.647047 0 1 0-49.294095 0Z" fill="#040000" p-id="1367"></path><path d="M397.847613 603.503411c13.471058 8.282206 28.738258 14.468914 43.7061 19.458195 29.835899 9.978562 62.266225 14.169558 93.299551 7.483921 21.054765-4.490353 40.213604-14.369129 56.778016-28.039758 6.785422-5.587995-2.893783-15.167414-9.579419-9.579419-46.999026 38.916391-112.258819 31.033327-163.847983 6.086922-4.590138-2.195284-9.080491-4.490353-13.371272-7.184564-7.583707-4.590138-14.468914 7.184564-6.984993 11.774703z" fill="#040000" p-id="1368"></path><path d="M627.753674 534.052621c-31.033327 24.048334-58.474371 68.253362-37.419607 106.970182 10.577275 19.35841 29.835899 32.629897 48.795167 42.708244 7.982849 4.190996 15.067628-7.883064 7.084779-12.07406-25.245761-13.271487-53.485091-35.324108-49.094524-66.557006 2.793997-20.156695 15.766127-37.319821 29.736114-51.190022 3.392711-3.392711 6.984993-6.785422 10.776847-9.77899 2.993569-2.295069 2.394855-7.483921 0-9.878776-2.893783-3.19314-6.885208-2.49464-9.878776-0.199572z" fill="#040000" p-id="1369"></path></svg>`
},
{
name: '5',
icon: `<svg t="1624457787809" class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1523" ><path d="M1.1 1.097642h1021.804716v1021.804716H1.1z" fill="#AD6F59" p-id="1524"></path><path d="M1024.002358 1024H0.002358V0h1024v1024z m-1021.804716-2.195284h1019.609433V2.195284H2.197642v1019.609432z" fill="#FFFFFF" p-id="1525"></path><path d="M411.829832 330.730879a38.118106 10.277919 57.6 1 0 17.355867-11.014368 38.118106 10.277919 57.6 1 0-17.355867 11.014368Z" fill="#040000" p-id="1526"></path><path d="M480.669675 609.989476c11.774703-25.844475 27.740401-51.788735 44.60417-73.342429 13.770415-17.462483 29.237186-33.92711 47.897096-44.803742 17.262912-10.078347 35.324108-13.67063 54.283376-6.58585 11.974274 4.390567 23.948548 14.468914 33.128825 24.547261 14.369129 15.865913 25.145975 34.625609 34.725394 53.684662 4.290782 8.581563 17.262912 0.997856 12.972131-7.583707-15.167414-30.334828-35.224323-63.763009-66.157864-80.327421-21.054765-11.37556-44.504385-11.475346-66.157864-1.895927-21.054765 9.280062-38.617034 25.644904-53.485091 42.907815-14.468914 16.863769-27.041902 35.324108-38.217891 54.582733-5.887351 10.178133-11.674917 20.555837-16.464627 31.232898-1.696355 3.692068-0.997856 7.982849 2.694212 10.277918 3.19314 1.895927 8.581563 0.898071 10.178133-2.694211z" fill="#040000" p-id="1527"></path><path d="M663.863649 338.091735a14.468914 33.727538 30 1 0 33.727538-58.417811 14.468914 33.727538 30 1 0-33.727538 58.417811Z" fill="#040000" p-id="1528"></path></svg>`
},
{
name: '6',
icon: `<svg t="1624457794933" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1680" ><path d="M762.9 77.4H261.1L10.2 512l250.9 434.6h501.8L1013.8 512z" fill="#83CEE3" p-id="1681"></path><path d="M369 375.8m-34.6 0a34.6 34.6 0 1 0 69.2 0 34.6 34.6 0 1 0-69.2 0Z" fill="#040000" p-id="1682"></path><path d="M369 411.7c-19.8 0-36-16.1-36-36s16.1-36 36-36 36 16.1 36 36-16.1 36-36 36z m0-69.1c-18.3 0-33.2 14.9-33.2 33.2S350.7 409 369 409s33.2-14.9 33.2-33.2-14.9-33.2-33.2-33.2z" fill="#FFFFFF" p-id="1683"></path><path d="M672.2 333.6c-15.1 7.6-30.2 15.6-44.3 25-5.9 3.9-17 10.4-14.6 19.1 1.8 6.5 12 11.2 17.3 14.3 15.7 9.3 32.1 17.6 48.3 25.9 8.6 4.4 16.2-8.5 7.6-13-14.1-7.3-28.3-14.5-42.1-22.3-3.9-2.2-7.9-4.5-11.7-6.9-1.2-0.8-2.4-1.5-3.5-2.4-0.6-0.4-1.1-0.8-1.6-1.2 2.2 1.7-0.3-0.3-0.3-0.3-0.9 0.1-1.5-3.2-0.2 0.5 0.9 2.4 1.1 3.8 0.3 5.8 0.6-1.5-0.9 0.8-0.1 0 0.5-0.5 1-1.1 1.6-1.6 0.5-0.5 1-0.9 1.6-1.3 0.6-0.5 0 0 1.2-0.9 1.7-1.3 3.5-2.5 5.3-3.6 8.4-5.5 17.2-10.4 26-15.2 5.6-3 11.2-6 16.8-8.9 8.6-4.4 1-17.3-7.6-13zM578.2 720.9c-12.5-96.7-33.3-154.7-55.6-155.6-8.8 3.9-22.3 17.5-37.7 60.1-10.8 29.8-18.4 62.2-23 81.6-1.2 5.1-2.1 9.1-2.9 11.8l-9.3-2.4c0.7-2.6 1.6-6.6 2.8-11.6 14.9-63 36-136.8 67.5-148.8l0.8-0.3h0.8c18.2-0.4 33.2 19.5 45.8 60.8 10.2 33.3 16.7 74.6 20.5 103.3l-9.7 1.1z" fill="#040000" p-id="1684"></path></svg>`
},
{
name: '7',
icon: `<svg t="1624457802025" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1838" ><path d="M762.9 77.4H261.1L10.2 512l250.9 434.6h501.8L1013.8 512z" fill="#8CC66D" p-id="1839"></path><path d="M375.778679 404.47473a14.5 33.8 30 1 0 33.8-58.543317 14.5 33.8 30 1 0-33.8 58.543317Z" fill="#040000" p-id="1840"></path><path d="M627.220263 374.211388a43.1 11.6 57.6 1 0 19.588408-12.431182 43.1 11.6 57.6 1 0-19.588408 12.431182Z" fill="#040000" p-id="1841"></path><path d="M451.1 548.5c17.6-9.3 63.9-30 105.3-16.2 17 20.3 32.7 98.8 28.8 138.1-27.5 10.2-82.5 10.2-106.1 5.8-8.3-10.5-32.7-81.8-35.3-114.6-0.4-5.5 2.5-10.6 7.3-13.1z" fill="#040000" p-id="1842"></path></svg>`
},
{
name: '8',
icon: `<svg t="1624457816632" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1996" ><path d="M762.9 77.4H261.1L10.2 512l250.9 434.6h501.8L1013.8 512z" fill="#5A74B8" p-id="1997"></path><path d="M357.7 400m-34.6 0a34.6 34.6 0 1 0 69.2 0 34.6 34.6 0 1 0-69.2 0Z" fill="#040000" p-id="1998"></path><path d="M357.7 436c-19.8 0-36-16.1-36-36s16.1-36 36-36 36 16.1 36 36-16.2 36-36 36z m0-69.2c-18.3 0-33.2 14.9-33.2 33.2s14.9 33.2 33.2 33.2 33.2-14.9 33.2-33.2-14.9-33.2-33.2-33.2z" fill="#FFFFFF" p-id="1999"></path><path d="M676 400m-34.6 0a34.6 34.6 0 1 0 69.2 0 34.6 34.6 0 1 0-69.2 0Z" fill="#040000" p-id="2000"></path><path d="M676 436c-19.8 0-36-16.1-36-36s16.1-36 36-36 36 16.1 36 36-16.2 36-36 36z m0-69.2c-18.3 0-33.2 14.9-33.2 33.2s14.9 33.2 33.2 33.2c18.3 0 33.2-14.9 33.2-33.2s-14.9-33.2-33.2-33.2z" fill="#FFFFFF" p-id="2001"></path><path d="M347.6 684.1c0.3-0.9 0.6-1.7 0.9-2.6 0.2-0.5 1.4-3.2 0.3-0.8 0.6-1.4 1.3-2.9 2-4.3 3.2-6.3 6-10.7 10.9-15.3 4.3-4 10.8-7.5 17.1-6.1 3.9 0.9 7.9 4.9 11.1 7.2 3.1 2.2 6.3 4.5 9.7 6.2 7.5 3.8 15.3 4.4 23.4 1.9 4.7-1.5 9.2-3.6 13.6-5.9 5-2.6 10.7-5 14.2-9.5 4.5-5.7 6.1-8.5 11.4-14.1 1-1 2-2 3.1-3 0.2-0.2 2.2-1.7 0.6-0.5 0.6-0.4 1.2-0.9 1.8-1.3 1-0.6 2.1-1.3 3.2-1.7-2 0.8 0.2 0 0.6-0.1 2.3-0.7-0.3-0.2 1.2-0.3 2.8-0.1 3.6 0 5.5 1 3.8 1.9 6.6 4.7 9.5 7.8 4.5 5 7.5 11.1 11.7 16.2 1.8 2.2 3.7 4.3 5.4 6.5 8.1 10.3 17.7 22.2 32.2 22 8.8-0.1 16.6-5.2 22.6-11.2 4.2-4.1 7.7-8.9 11-13.7 2.9-4.2 4.6-9.9 6.2-13.5 3.2-7.1 7.2-13.1 13-18.1 4.8-4.2 11.1-6.5 16.7-5.3 10.5 2.4 17.2 12.1 23.1 20.2 4.7 6.5 9.8 13 16 18.2 7.8 6.4 17.1 11.4 27.5 11.1 14.1-0.4 25.5-9.5 34.2-19.9 3-3.6 3.6-8.8 0-12.4-3.1-3.1-9.4-3.7-12.4 0-6.3 7.6-14.7 15.9-24.9 14.7-2.2-0.3-5.3-1.5-7.9-3.1-3.5-2.1-6.1-4.4-9.1-7.5-4.9-5.1-6.8-8.1-10.9-13.8-7.3-10.1-16.1-19.6-28.2-23.7-18.5-6.3-35.7 5.6-46 20.1-2.4 3.3-4.4 6.9-6.1 10.6-1.8 3.9-2.7 8.5-5.2 11.9-3.1 4.4-6.2 8.8-10.2 12.5-3 2.8-5.7 4.4-8.6 5.1-0.4 0.1-1.7 0.1 0.1 0h-2.2c2.1 0.1 0 0-0.5-0.1-0.7-0.2-1.4-0.4-2-0.6 1.8 0.7-1.8-1.1-2.4-1.5l-1.2-0.9c1.5 1.2-0.9-0.9-1.2-1.1-4.7-4.3-8.4-9.5-12.3-14.4-10.9-13.6-20.9-34-41-34.9-14.2-0.6-24.5 10.6-32.4 20.8-1.2 1.6-2.5 3.2-3.7 4.8-1.5 1.9 1.1-1.4-0.4 0.5-0.4 0.5-0.8 1.2-1.3 1.6-1.7 1.4-4.6 2.6-6.6 3.6-2.9 1.6-5.9 3.2-9 4.5-1.6 0.7-3.4 1.2-5.1 1.7-2.2 0.6-0.7 0.5-2.8 0.4-2.8 0-3.9-0.4-6.6-1.9-3.9-2.2-7.5-4.9-11.1-7.5-5.6-4-10-6.9-17-7.5-10.5-0.9-20.3 3.2-28.2 9.9-9.4 8.1-16.4 20.2-20.1 32-3.6 11.2 13.3 15.8 16.8 5.1z" fill="#040000" p-id="2002"></path></svg>`
},
{
name: '9',
icon: `<svg t="1624457826949" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2156" ><path d="M762.9 77.4H261.1L10.2 512l250.9 434.6h501.8L1013.8 512z" fill="#F0884F" p-id="2157"></path><path d="M287.2 382c6.4 2.3 11.6-3.7 15.4-7.9 5.1-5.5 10.2-11 16-15.9 0.8-0.7 1.7-1.4 2.5-2.1 1.2-0.9-1.7 1.3 0.2-0.2l1.2-0.9c2.1-1.5 4.3-2.9 6.5-4.3 2-1.2 4-2.2 6.1-3.2 0.6-0.3 1.2-0.6 1.9-0.9-0.3 0.2-1.5 0.6 0.2-0.1 1.3-0.5 2.6-1 4-1.5 11.2-3.7 21.8-4 33.4-1.1 19.5 4.9 36.4 17 51.2 30.2 8.6 7.7 21.4-5 12.7-12.7-25.2-22.6-57.1-42.1-92.2-36.2-20.4 3.4-37.7 16.1-51.6 30.9-2.3 2.4-4.5 5-6.8 7.4-0.7 0.7-1.9 1.5-2.4 2.4-0.5 0.8 2.3-1.5 0.8-0.7 1.3-0.7 3.9-1.4 5.8-0.7-11.1-3.7-15.8 13.7-4.9 17.5zM598 382c6.4 2.3 11.6-3.7 15.4-7.9 5.1-5.5 10.2-11 16-15.9 0.8-0.7 1.7-1.4 2.5-2.1 1.2-0.9-1.7 1.3 0.2-0.2l1.2-0.9c2.1-1.5 4.3-2.9 6.5-4.3 2-1.2 4-2.2 6.1-3.2 0.6-0.3 1.2-0.6 1.9-0.9-0.3 0.2-1.5 0.6 0.2-0.1 1.3-0.5 2.6-1 4-1.5 11.2-3.7 21.8-4 33.4-1.1 19.5 4.9 36.4 17 51.2 30.2 8.6 7.7 21.4-5 12.7-12.7-25.2-22.6-57.1-42.1-92.2-36.2-20.4 3.4-37.7 16.1-51.6 30.9-2.3 2.4-4.5 5-6.8 7.4-0.7 0.7-1.9 1.5-2.4 2.4-0.5 0.8 2.3-1.5 0.8-0.7 1.3-0.7 3.9-1.4 5.8-0.7-11.1-3.7-15.8 13.7-4.9 17.5zM505.9 527.1c3.4 0.7 6.8 1.7 10.2 2.8 6.7 2.2 10.4 3.5 16.6 7.7 1.6 1.1-0.5-0.5 0.6 0.5 0.6 0.5 1.1 1.1 1.7 1.6 1.5 1.4-0.1-0.4 0.5 0.6 0.4 0.6 0.7 1.2 1 1.8-1-2 0.1 0 0 0.5 0.1-2-0.1 0-0.1 0-0.1 0.8 0 0.7 0.1-0.5-0.1 0.4-0.1 0.7-0.3 1.1-0.6 1 0.7-0.9-0.4 1-1.6 2.5-4.6 5.4-8.1 7.8-6.8 4.6-14.4 8.2-22 11.4-7 3-7.4 11.9 0 14.8 7.4 2.8 15 5.3 22.4 8.1 3.1 1.1 4.2 1.5 6.9 2.9 1.1 0.6 2.1 1.2 3.2 1.8 1.2 0.8-0.7-0.5 0.1 0 0.4 0.3 0.8 0.7 1.1 1.1 0.6 0.8-1.1-1.2-0.2-0.2 0.8 0.9-0.3-1.4-0.1-0.2 0.1 0.9 0.2-1.9 0-0.9-0.1 0.5-0.8 1.8 0 0.2-0.2 0.5-0.5 1-0.8 1.4-0.3 0.3-0.9 1.3-0.3 0.5-0.5 0.7-1.1 1.3-1.7 1.9-6.9 7.3-15.9 12.8-24.4 18.1-8.3 5.3-0.6 18.5 7.7 13.2 9.9-6.3 20.9-12.8 28.6-21.8 4.8-5.5 8.1-12.9 4.2-19.9-3.4-6-10.5-8.9-16.6-11.4-8.6-3.5-17.5-6.2-26.2-9.5v14.8c14.4-6.1 47.2-18.8 41.2-40.3-3.5-12.9-19.4-18.9-30.8-22.6-3.4-1.1-6.9-2.1-10.5-2.9-9.1-2.2-13.3 12.5-3.6 14.6z" fill="#040000" p-id="2158"></path></svg>`
},
{
name: '10',
icon: `<svg t="1624457835383" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2312" ><path d="M762.9 77.4H261.1L10.2 512l250.9 434.6h501.8L1013.8 512z" fill="#F6F180" p-id="2313"></path><path d="M342.9 400.6m-29.5 0a29.5 29.5 0 1 0 59 0 29.5 29.5 0 1 0-59 0Z" fill="#040000" p-id="2314"></path><path d="M342.9 431.3c-16.9 0-30.7-13.8-30.7-30.7s13.8-30.7 30.7-30.7 30.7 13.8 30.7 30.7-13.7 30.7-30.7 30.7z m0-59c-15.6 0-28.3 12.7-28.3 28.3s12.7 28.3 28.3 28.3 28.3-12.7 28.3-28.3-12.6-28.3-28.3-28.3z" fill="#FFFFFF" p-id="2315"></path><path d="M702 400.6m-29.5 0a29.5 29.5 0 1 0 59 0 29.5 29.5 0 1 0-59 0Z" fill="#040000" p-id="2316"></path><path d="M702 431.3c-16.9 0-30.7-13.8-30.7-30.7s13.8-30.7 30.7-30.7 30.7 13.8 30.7 30.7-13.8 30.7-30.7 30.7z m0-59c-15.6 0-28.3 12.7-28.3 28.3s12.7 28.3 28.3 28.3 28.3-12.7 28.3-28.3-12.7-28.3-28.3-28.3z" fill="#FFFFFF" p-id="2317"></path><path d="M358.7 519.9c20 22 45.5 40.4 71.3 54.8 51.2 28.5 111.7 39.9 168 19.5 44.3-16.1 80.7-47.8 110.2-83.9 3-3.7 3.6-8.9 0-12.5-3.1-3.1-9.5-3.7-12.5 0-25.5 31.4-56.2 59.7-93.7 76-27.1 11.7-56.6 15.7-85.8 12.2-24.7-2.9-49.5-11.8-71.5-23.4-18.7-9.8-36.6-22.2-51.1-34.3-7.8-6.5-15.5-13.3-22.4-20.9-7.7-8.5-20.1 4.1-12.5 12.5z" p-id="2318"></path></svg>`
},
{
name: '11',
icon: `<svg t="1624457841751" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2472" ><path d="M48.2 844.9c-68.5-210.6 186-782.1 409.1-795.4 6.3-0.4 12.5 0.2 18.6 1.6C665.1 94.6 985.4 515 987.1 821.3c0.1 20-12.9 37.9-22.4 43.1-162.7 89.8-605.8 179.7-884.4 30.9-15-7.9-24.2-26.1-32.1-50.4z" fill="#F0884F" p-id="2473"></path><path d="M401 352.1m-52.4 0a52.4 52.4 0 1 0 104.8 0 52.4 52.4 0 1 0-104.8 0Z" fill="#FFFFFF" p-id="2474"></path><path d="M408.7 329m-29.3 0a29.3 29.3 0 1 0 58.6 0 29.3 29.3 0 1 0-58.6 0Z" fill="#040000" p-id="2475"></path><path d="M527.5 352.1m-52.4 0a52.4 52.4 0 1 0 104.8 0 52.4 52.4 0 1 0-104.8 0Z" fill="#FFFFFF" p-id="2476"></path><path d="M527.5 329m-29.3 0a29.3 29.3 0 1 0 58.6 0 29.3 29.3 0 1 0-58.6 0Z" fill="#040000" p-id="2477"></path><path d="M450.7 517c1.1-8.2 3.2-16.4 6.1-24.1 0.1-0.3 1-2.5 0.5-1.4s0.3-0.7 0.5-1c0.7-1.4 1.4-2.8 2.2-4.1 0.4-0.8 2.8-3.9 1.3-2.1 0.8-1 1.7-1.9 2.6-2.8 1-1-1.5 1 0.1 0 0.5-0.3 1-0.6 1.5-0.8-1.3 0.7-1.2 0.3 0 0.1 1.9-0.3-1.8 0.3 0.1 0 1.2-0.2 1.5 0.3 0-0.1 0.6 0.2 1.3 0.3 1.9 0.5 0.3 0.1-1.3-0.7 0.2 0.1 0.8 0.5 1.6 0.9 2.4 1.4 1.4 1 0-0.1 1.4 1.1 0.9 0.8 1.8 1.7 2.6 2.6 1.8 1.9 3.5 3.9 5 6.1 5.1 7.1 9.3 14.8 13.2 22.6 3.5 6.9 13.7 4.7 15.8-2.1 2.6-8.7 4.8-17.4 7.4-26.1 0.9-3.2 1.9-6.4 3.2-9.4-0.7 1.6 0.8-1.6 1.2-2.2l0.9-1.5c0.7-1.2-1.4 0.7 0.1-0.1 1.7-0.9-1.2 0.3-0.3 0.1 0.8-0.2 1-1.2 0.3-0.3-0.6 0.8 0.6 0-0.5 0.2-2 0.3 2.4 0.5-1.1 0 0.5 0.1 1.2 0.2 1.6 0.4-1.1-0.8-0.8-0.4 0.2 0.2 0.7 0.4 3.4 2.3 2.7 1.8 8.9 7.1 15.9 16.9 22.5 26 2.8 3.8 7.5 5.6 11.8 3.1 3.7-2.2 5.9-8 3.1-11.8-8.2-11.1-16.6-23-27.7-31.4-6.3-4.7-14.5-7.6-21.7-3-6.7 4.2-9.6 12.5-11.9 19.6-3.2 9.9-5.5 20-8.6 29.9 5.3-0.7 10.5-1.4 15.8-2.1-7.8-15.5-24.8-50.1-48-41.7-14.1 5.1-19.7 23-22.9 36.2-0.9 3.8-1.8 7.7-2.3 11.6-0.6 4.6 1.1 9.3 6 10.6 4.2 1 10.2-1.5 10.8-6.1z" fill="#040000" p-id="2478"></path></svg>`
},
{
name: '12',
icon: `<svg t="1624457847424" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2632" ><path d="M485.538528 993.072489a362.00362 481.804818 3.149 1 0 52.933731-962.15464 362.00362 481.804818 3.149 1 0-52.933731 962.15464Z" fill="#AADCF0" p-id="2633"></path><path d="M688.2 334.1c-15.1 7.6-30.2 15.6-44.3 25-5.9 3.9-17 10.4-14.6 19.1 1.8 6.5 12 11.2 17.3 14.3 15.7 9.3 32.1 17.6 48.3 25.9 8.6 4.4 16.2-8.5 7.6-13-14.1-7.3-28.3-14.5-42.1-22.3-3.9-2.2-7.9-4.5-11.7-6.9-1.2-0.8-2.4-1.5-3.5-2.4-0.6-0.4-1.1-0.8-1.6-1.2 2.2 1.7-0.3-0.3-0.3-0.3-0.9 0.1-1.5-3.2-0.2 0.5 0.9 2.4 1.1 3.8 0.3 5.8 0.6-1.5-0.9 0.8-0.1 0 0.5-0.5 1-1.1 1.6-1.6 0.5-0.5 1-0.9 1.6-1.3 0.6-0.5 0 0 1.2-0.9 1.7-1.3 3.5-2.5 5.3-3.6 8.4-5.5 17.2-10.4 26-15.2 5.6-3 11.2-6 16.8-8.9 8.6-4.4 1-17.4-7.6-13zM375.8 347c13.4 6.8 26.7 14 39.5 21.9 1.8 1.2 3.7 2.3 5.5 3.5 0.9 0.6 1.7 1.2 2.6 1.8 0.9 0.6 1.9 1.4 1.6 1.1 1.1 0.9 2.1 1.9 3.1 2.8 1.2 1 0-0.3 0.1 0 0-0.2-0.8-2.4-0.3-4.1 1.5-5.5 2.3-2.7 0.8-2-0.4 0.2-0.9 0.8-1.3 1.1 1.7-1.4-1.6 1.1-2.3 1.6-3.4 2.3-6.9 4.4-10.4 6.4-14.9 8.6-30.3 16.4-45.6 24.3-8.6 4.4-1 17.4 7.6 13 15-7.7 30.1-15.4 44.8-23.8 6.2-3.6 13.8-7.3 18.7-12.7 7.6-8.3-3.8-16.6-9.9-20.9-8.7-6.1-18-11.3-27.3-16.4-6.5-3.6-13-7.1-19.6-10.4-8.6-4.5-16.3 8.5-7.6 12.8zM412.8 570.9c13.5 7.7 28.5 13.3 43.3 17.9 29.8 9.2 61.7 13.1 92.6 7.3 20.6-3.9 40-12.5 56.6-25.2 2.8-2.2 4.3-5.6 2.3-9-1.6-2.8-6.2-4.5-9-2.3-48.3 36.9-113.3 30-165.6 6.7-4.6-2.1-9.2-4.2-13.7-6.7-7.3-4.2-13.9 7.2-6.5 11.3z" fill="#040000" p-id="2634"></path><path d="M644.6 505.2c-30.1 21.5-60.6 62.5-39.1 99.8 10.7 18.6 30.3 30.9 49.1 40.1 7.8 3.8 14.6-7.9 6.8-11.7-23.6-11.5-53.7-31.4-49.4-60.9 2.8-18.9 15.8-34.6 29.5-47.2 2.5-2.3 5.1-4.6 7.8-6.7 0.5-0.4 0.9-0.7 1.4-1.1-0.4 0.3-1.2 0.9-0.1 0.1l0.9-0.6c6.9-5.1 0.2-16.8-6.9-11.8z" fill="#040000" p-id="2635"></path></svg>`
},
{
name: '13',
icon: `<svg t="1624457855182" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2789" ><path d="M235.1 76.9c75.6-26.5 297.3-90.1 514.2-16.6 16.3 5.5 29.8 17.4 37.1 33 57.5 122.4 127.1 602.1 62.1 785.6a62.58 62.58 0 0 1-32.5 35.8c-109.5 51.8-428.1 136.7-609.3 37.2-14.4-7.9-25-21.3-29.7-37.1-41.9-140.6-37-627.7 19.1-798 6.1-18.7 20.5-33.4 39-39.9z" fill="#F9DABD" p-id="2790"></path><path d="M392.2 360.2m-35.2 0a35.2 35.2 0 1 0 70.4 0 35.2 35.2 0 1 0-70.4 0Z" fill="#040000" p-id="2791"></path><path d="M618.6 360.2m-35.2 0a35.2 35.2 0 1 0 70.4 0 35.2 35.2 0 1 0-70.4 0Z" fill="#040000" p-id="2792"></path><path d="M512 562.6c-36 0-65.3-29.3-65.3-65.3S476 432 512 432s65.3 29.3 65.3 65.3-29.3 65.3-65.3 65.3z m0-122.9c-31.7 0-57.6 25.8-57.6 57.6s25.8 57.6 57.6 57.6c31.7 0 57.6-25.8 57.6-57.6s-25.9-57.6-57.6-57.6z" fill="#040000" p-id="2793"></path></svg>`
},
{
name: '14',
icon: `<svg t="1624457863444" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2947" ><path d="M178.1 971.5c38.1 15.9 98.7 26.6 171.3-12.3 3.7-2 8.4-1.6 11.6 1.1 43.3 35.9 123.3 80.8 236 10.9 3.8-2.4 8.7-2.4 12.6-0.2 41.8 23.9 191.6 58.2 246.6 14.2 4.4-3.5 9.1-6.6 14.5-8.5C1065 909.5 678.2-652 194.3 351c-37.5 77.8-38.4 94.1-71.9 211.3-27.6 96.3-29.1 231.3 1.4 348.1 7.2 27.3 27.3 49.9 54.3 61.1z" fill="#ABAAAA" p-id="2948"></path><path d="M468.9 349H418c-6.1 0-11.1-5-11.1-11.1V336c0-6.1 5-11.1 11.1-11.1h50.9c6.1 0 11.1 5 11.1 11.1v1.9c0 6.1-5 11.1-11.1 11.1zM643 471.9H390c-6.6 0-12-5.4-12-12s5.4-12 12-12h253c6.6 0 12 5.4 12 12s-5.4 12-12 12zM609 349h-61.2c-6 0-11-4.9-11-11v-2.1c0-6 4.9-11 11-11H609c6 0 11 4.9 11 11v2.1c0 6.1-4.9 11-11 11z" fill="#040000" p-id="2949"></path></svg>`
},
{
name: '15',
icon: `<svg t="1624457870536" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3103" ><path d="M673.1 318.7c3.7-17.5 5.6-35.7 5.6-54.4 0-137.9-105.5-249.7-235.6-249.7S207.4 126.4 207.4 264.3c0 55.4 17.1 106.7 45.9 148.1-55.2 63.3-88.6 145.9-88.6 236.3 0 199.2 162.1 360.6 362.1 360.6 200 0 362.1-161.5 362.1-360.6 0.1-147.3-88.7-274-215.8-330z" fill="#4F8A54" p-id="3104"></path><path d="M392 246.2m-47.1 0a47.1 47.1 0 1 0 94.2 0 47.1 47.1 0 1 0-94.2 0Z" fill="#FFFFFF" p-id="3105"></path><path d="M386 252.8m-26.4 0a26.4 26.4 0 1 0 52.8 0 26.4 26.4 0 1 0-52.8 0Z" fill="#040000" p-id="3106"></path><path d="M505.6 246.2m-47.1 0a47.1 47.1 0 1 0 94.2 0 47.1 47.1 0 1 0-94.2 0Z" fill="#FFFFFF" p-id="3107"></path><path d="M501.4 252.8m-26.4 0a26.4 26.4 0 1 0 52.8 0 26.4 26.4 0 1 0-52.8 0Z" fill="#040000" p-id="3108"></path><path d="M474.3 364.8h-50.9c-6.1 0-11.1-5-11.1-11.1v-1.9c0-6.1 5-11.1 11.1-11.1h50.9c6.1 0 11.1 5 11.1 11.1v1.9c0 6.2-5 11.1-11.1 11.1z" fill="#040000" p-id="3109"></path></svg>`
},
{
name: '16',
icon: `<svg t="1624457876371" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3263" ><path d="M246.4 227.6c-166.9 101.1-461.9 344 87 564.1 1.5 0.6 2.9 1.1 4.4 1.6 80.7 27.7 392.8 165.4 641-198.1 40-58.6 38.5-136.2-3.7-193.3C892 289.5 727 201.1 429.1 182.7c-64.1-4-127.8 11.6-182.7 44.9z" fill="#CF92BE" p-id="3264"></path><path d="M617.1 393.4c-17.4 8.8-34.9 18.1-51.2 28.9-6.9 4.6-20.3 12.3-17.4 22.6 1.2 4.3 5.6 7 9 9.5 3.7 2.7 7.6 5 11.5 7.3 18.2 10.8 37.1 20.3 55.9 30 10 5.1 18.9-10 8.8-15.1-16.4-8.4-32.9-16.9-49-26-4.5-2.6-9.1-5.2-13.5-8l-4.5-3c-0.7-0.5-1.3-1-2-1.5 1.6 1.2 0.7 0.4-0.2-0.2-1.3-0.9-0.3-0.9-0.5-0.3 0.2 0.2 0.4 0.5 0.6 0.7 1 1.9 1.3 3.7 0.8 5.7 0.1-0.6 0.7-1.4-0.6 1.3 0.7-1.5-0.1 0-0.2 0.1 0.6-0.6 1.2-1.3 1.9-1.9l1.8-1.5c1.8-1.6-0.6 0.3 1.2-0.9 2-1.5 4.1-2.9 6.2-4.3 10-6.5 20.4-12.4 30.9-18 6.5-3.5 13.1-7 19.7-10.4 9.6-5 0.8-20.1-9.2-15zM323.1 408.5c15.9 8.1 31.7 16.5 46.8 26 2.2 1.4 4.3 2.8 6.5 4.2 1 0.7 1.9 1.3 2.8 2 0.5 0.3 1 0.7 1.4 1.1-1.1-0.9-0.3-0.3 0.3 0.3 1.1 1 2.2 2.2 3.3 3.1 1.4 1.1-1-1.7-0.1-0.1-0.6-1.1-0.9-4.1 0.3-6.7 2.2-4.8 0.7 0.1 0-0.5 0 0-1.1 0.9-1.3 1 2.3-1.9 0 0-0.5 0.4-0.8 0.5-1.5 1.1-2.3 1.6-4 2.7-8.1 5.1-12.3 7.5-17.3 10-35.1 19.1-52.8 28.2-10 5.1-1.2 20.2 8.8 15.1 17.5-9 35-17.9 52-27.7 7.3-4.2 15.9-8.6 21.8-14.7 9.3-9.7-4.3-19.7-11.5-24.7-10.1-7.1-20.9-13.1-31.7-19-7.6-4.2-15.2-8.2-22.9-12.1-9.7-5.2-18.6 9.9-8.6 15zM513 592.1c-12.2 0-24.6-1.4-36.3-4.3-8-2-13.9-8.2-15.4-16.2s1.7-15.8 8.4-20.5c23.2-16.3 60.5-31.9 106.2-13 6.4 2.6 11 8.3 12.3 15.1 1.3 6.7-0.8 13.6-5.7 18.3-13.5 13.1-40.9 20.6-69.5 20.6z m-37.4-32.5c-3.4 2.4-4.9 6.2-4.2 10.2 0.8 4.1 3.6 7.1 7.7 8.1 39.1 9.7 81.2 0.7 96.1-13.7 2.4-2.3 3.4-5.6 2.7-8.9-0.7-3.4-2.9-6.2-6.1-7.5-41.2-17.2-75.1-3.1-96.2 11.8z" fill="#040000" p-id="3265"></path></svg>`
},
{
name: '17',
icon: `<svg t="1624457881793" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3419" ><path d="M1008.6 465.7c0-124.9-95.5-226.2-213.4-226.2-12 0-23.8 1.1-35.2 3.1v-3.1c0-124.9-95.5-226.2-213.4-226.2S333.4 114.6 333.4 239.5c0 2.4 0 4.8 0.1 7.2-17.1-4.7-35-7.2-53.4-7.2-117.8 0-213.4 101.3-213.4 226.2 0 92.1 51.9 171.3 126.3 206.6-13.7 29.9-21.4 63.4-21.4 98.8 0 124.9 95.5 226.2 213.4 226.2 68.8 0 130-34.5 169-88.1 39 53.6 100.2 88.1 169 88.1 117.8 0 213.4-101.3 213.4-226.2 0-41.2-10.4-79.9-28.6-113.1 60.5-39.9 100.8-111.1 100.8-192.3z" fill="#8CC66D" p-id="3420"></path><path d="M437.8 400.7m-24.7 0a24.7 24.7 0 1 0 49.4 0 24.7 24.7 0 1 0-49.4 0Z" fill="#040000" p-id="3421"></path><path d="M649.7 400.7m-24.7 0a24.7 24.7 0 1 0 49.4 0 24.7 24.7 0 1 0-49.4 0Z" fill="#040000" p-id="3422"></path><path d="M527.3 625.9c6.3-14.2 13.1-28.3 17.9-43 6.2-19 8.3-38.6 10.5-58.3l2.1-19.2c0.7-6.2-9-6.1-9.7 0-1.7 16.3-2.8 32.8-5.7 48.9-4.2 23.7-13.8 45-23.5 66.7-2.5 5.6 5.9 10.5 8.4 4.9z" fill="#252525" p-id="3423"></path><path d="M447.7 522.3c20.3-0.1 40.6-0.2 61-0.4l96.6-0.6c7.5 0 14.9-0.1 22.4-0.1 16.6-0.1 16.7-25.9 0-25.8-20.3 0.1-40.6 0.2-61 0.4l-96.6 0.6c-7.5 0-14.9 0.1-22.4 0.1-16.6 0.1-16.7 25.9 0 25.8z" fill="#040000" p-id="3424"></path><path d="M495.4 508.2c-10.3 3.8-9.2 20.9-9.2 29.5 0.1 16 2.1 32.3 6.1 47.8 3.5 13.7 8.7 29.9 20.6 38.7 12.9 9.5 27.6 2.1 37.6-7.9 10.2-10.3 17.8-23 24.7-35.6 11.6-21.3 20.9-43.8 29.7-66.4 3-7.8-9.5-11.1-12.5-3.4-7.4 19.1-15.3 38.1-24.7 56.4-5.9 11.5-12.2 23-20.3 33.1-2.8 3.5-5.8 6.9-9.2 9.8-1.9 1.7-1.4 1.3-3.3 2.5-1.3 0.8-2.6 1.6-3.9 2.2-0.7 0.3 1-0.2-0.8 0.3-0.6 0.2-1.2 0.3-1.8 0.5-1.1 0.3-1.2 0.2-0.5 0.1-0.6 0-1.3 0-1.9 0.1-2.2 0.1 0.6 0.5-1.8-0.2l-1.8-0.6c1.5 0.5 0.2 0.1-0.5-0.3-0.8-0.5-2.9-2.1-1.7-1.1-1-0.9-2-1.7-2.8-2.7-0.4-0.5-0.9-1-1.3-1.5 0.4 0.5 0.1 0.2-0.5-0.7-0.8-1.3-1.7-2.5-2.4-3.9-0.7-1.3-1.4-2.5-2-3.8-0.4-0.8-0.8-1.6-1.1-2.4-0.1-0.2-0.5-1.1 0 0l-0.6-1.5a86.8 86.8 0 0 1-3.3-9.8c-4.4-14.9-6.2-27.9-6.8-42.8-0.3-6.6-0.3-13.1 0.4-19.7 0.2-1.5-0.3 1.5 0.1-0.5l0.3-1.8c0.2-0.9 0.5-1.8 0.7-2.8 0.4-1.9-0.7 1.1 0.3-0.7 0.5-1-1.3 1.2-0.3 0.5-0.3 0.3-1.1 0.8-2 1.1 7.7-2.9 4.3-15.4-3.5-12.5z" fill="#040000" p-id="3425"></path></svg>`
},
{
name: '18',
icon: `<svg t="1624457899440" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3579" ><path d="M75.4 739.8c-78.7-134.4-194-455.7 401.4-579.6 9.8-2 19.2-6.2 29.2-7.5C656.8 133 947.3 205 1000.1 578.4c42.6 223.8 29.7 392.1-822 233.6-43.1-8-80.6-34.4-102.7-72.2z" fill="#F09495" p-id="3580"></path><path d="M704.6 875.4c-129 0-301.8-20.5-526.6-62.3-43.5-8.1-81.2-34.6-103.5-72.7-19.3-32.9-44.8-84.3-57.1-142.5-13.9-65.1-8.8-125.3 15.1-179.2 54.3-122.3 203.7-209.6 444-259.6 4.1-0.9 8.3-2.1 12.3-3.4 5.5-1.7 11.1-3.4 16.9-4.2 29-3.8 75.7-5.9 133.8 5.7 54.5 10.9 105.3 31 150.8 59.9C843.7 251 888.2 296 922.7 351c39.7 63.1 66.1 139.6 78.5 227.3 8.1 42.4 15.2 87.3 12.5 127.9-2.8 42.6-16.4 75.5-41.5 100.7-42.5 42.7-120.3 65-237.8 68.1-9.6 0.2-19.6 0.4-29.8 0.4zM76.3 739.3c22 37.6 59.2 63.7 102.1 71.7 242.5 45.1 424.4 65.3 556.1 61.9 116.9-3.1 194.1-25.2 236.3-67.5 55.4-55.6 44.4-142.5 28.3-226.7C976 415.8 903.4 291.5 789.2 219c-124-78.7-248.1-69.9-283.2-65.3-5.6 0.7-11.2 2.4-16.6 4.1-4.1 1.2-8.3 2.5-12.5 3.4C237.3 211.1 88.5 298 34.5 419.6c-54.6 122.8 2.8 253 41.8 319.7z" fill="#FFFFFF" p-id="3581"></path><path d="M424.1 442.5m-24.7 0a24.7 24.7 0 1 0 49.4 0 24.7 24.7 0 1 0-49.4 0Z" fill="#040000" p-id="3582"></path><path d="M635.9 442.5m-24.7 0a24.7 24.7 0 1 0 49.4 0 24.7 24.7 0 1 0-49.4 0Z" fill="#040000" p-id="3583"></path><path d="M426.2 543.3c17.1 7.9 36.6 26 25.5 46.1-6.9 12.5-19.8 21.2-31.7 28.4-4.5 2.7-0.4 9.8 4.1 7.1 17.4-10.5 41.6-27.6 39-51.1-1.6-14-12.4-24.8-23.5-32.3-3-2-6.1-3.9-9.3-5.4-4.8-2.1-8.9 5-4.1 7.2zM629.5 535.4c-21.8 11.7-40.6 37-25.7 61.3 8.2 13.4 22.2 22.7 35.7 30.3 4.7 2.7 8.9-4.6 4.2-7.2-15.5-8.7-39.9-23.9-36.9-45.2 1.6-11.4 10.7-20.7 19.6-27.2 2.4-1.7 4.8-3.4 7.4-4.8 4.7-2.5 0.4-9.8-4.3-7.2z" fill="#040000" p-id="3584"></path><path d="M457.2 584.6c25.6 25.6 66.7 41 101.8 28.3 18.2-6.6 33.2-19.1 45.5-33.8 4.2-5.1-3-12.4-7.3-7.3-18.5 22-43.3 38.1-73 35-18.6-1.9-36.2-10.8-50.9-22-2.9-2.2-6.1-4.8-8.8-7.5-4.7-4.7-12 2.6-7.3 7.3z" fill="#040000" p-id="3585"></path></svg>`
},
{
name: '19',
icon: `<svg t="1624457904464" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3739" ><path d="M915.9 510.5c8.4-19 13.1-39.8 13.1-61.7 0-90-78.9-162.9-176.2-162.9-3.2 0-6.3 0.1-9.5 0.2v-0.2c0-94.8-116.2-171.6-259.6-171.6S224 191.2 224 286v2c-96.2 0-174.1 72-174.1 160.9 0 38 14.3 73 38.2 100.5-41.8 29.4-68.8 75.9-68.8 128.2 0 88.9 78 160.9 174.1 160.9 17.1 0 33.6-2.3 49.3-6.5 28.9 46.1 88.7 77.7 157.6 77.7 49.4 0 94-16.2 126-42.3 32 26.1 76.6 42.3 126 42.3 77.3 0 143-39.7 166.7-95 3.1 0.2 6.3 0.2 9.5 0.2 97.3 0 176.2-72.9 176.2-162.9 0-60.6-35.7-113.4-88.8-141.5z" fill="#5A74B8" p-id="3740"></path><path d="M357.6 449.5a46.6 73.2 0 1 0 93.2 0 46.6 73.2 0 1 0-93.2 0Z" fill="#FEFEFD" p-id="3741"></path><path d="M357.5 449.5a25.1 39.4 0 1 0 50.2 0 25.1 39.4 0 1 0-50.2 0Z" fill="#040000" p-id="3742"></path><path d="M531.3 449.5a46.6 73.2 0 1 0 93.2 0 46.6 73.2 0 1 0-93.2 0Z" fill="#FEFEFD" p-id="3743"></path><path d="M531.2 449.5a25.1 39.4 0 1 0 50.2 0 25.1 39.4 0 1 0-50.2 0Z" fill="#040000" p-id="3744"></path><path d="M426.7 574.6c20.9 29.9 59.7 52.2 96.2 38.6 19.2-7.2 34.7-21.2 47.6-36.9 2.8-3.5 3.4-8.3 0-11.7-2.9-2.9-8.9-3.5-11.7 0-16.5 20.2-40.9 40.9-68.1 35.5-17.3-3.4-31-13.2-42.9-25.9-2-2.2-3.9-4.4-5.8-6.7-1.6-1.9 1.1 1.5-0.4-0.6-0.2-0.2-0.3-0.5-0.5-0.7-6.2-8.7-20.6-0.4-14.4 8.4z" fill="#040000" p-id="3745"></path></svg>`
},
{
name: '20',
icon: `<svg t="1624457910321" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3899" ><path d="M792.8 301.4c-8.2 0-16.2 0.4-24.2 1.3-12.3-81.8-129.2-145.9-271.8-145.9-137.1 0-250.5 59.3-269.9 136.6C105.3 295.5 7.4 391.2 7.4 508.9c0 119.1 100.2 215.6 223.7 215.6 5.3 0 10.6-0.2 15.8-0.5 14.4 80.5 130.4 143.2 271.3 143.2 135.9 0 248.6-58.3 269.4-134.6 1.7 0 3.4 0.1 5.1 0.1 123.6 0 223.7-96.5 223.7-215.6s-100-215.7-223.6-215.7z" fill="#F6CD50" p-id="3900"></path><path d="M435.9 431.5m-52.2 0a52.2 52.2 0 1 0 104.4 0 52.2 52.2 0 1 0-104.4 0Z" fill="#FAFAFA" p-id="3901"></path><path d="M588.1 431.5m-52.2 0a52.2 52.2 0 1 0 104.4 0 52.2 52.2 0 1 0-104.4 0Z" fill="#FAFAFA" p-id="3902"></path><path d="M435.9 431.5m-27.8 0a27.8 27.8 0 1 0 55.6 0 27.8 27.8 0 1 0-55.6 0Z" fill="#040000" p-id="3903"></path><path d="M601.9 407.4c-5.7 2.9-11.3 5.9-16.9 9-6.8 3.8-15.3 7.8-20.5 13.8-5.6 6.5 1.6 11.1 6.7 14.4 11.2 7.1 23.3 13 35.1 19 5.7 2.9 10.8-5.7 5.1-8.6-10.9-5.6-21.9-11.1-32.4-17.4-2.4-1.4-4.6-3.1-7-4.6 1 0.6-0.4-0.4-0.4-0.4-1.9-0.3-0.5 4.2 0.5 4.1-0.1 0-0.6 0.3 0.3-0.3 0.5-0.3 1-0.9 1.5-1.3 9.7-7.9 21.9-13.5 33.1-19.2 5.7-2.7 0.6-11.4-5.1-8.5zM406.6 547.6c11.5 14.4 27 26.7 42.7 36.3 32.2 19.8 71.2 27.2 107.6 15.4 29.5-9.6 54.6-29.1 75.5-51.6 10.8-11.6-6.6-29.1-17.5-17.5-9.4 10.1-19.5 19.7-30.8 27.7-4.6 3.2-9.3 6.2-14.2 8.9-5 2.8-9.9 5.1-14.1 6.7-4.6 1.7-9.3 3.2-14.1 4.4-2.2 0.5-4.4 1-6.6 1.4-1 0.2-2 0.3-2.9 0.5 2.6-0.4-2.1 0.2-2.5 0.3-4.1 0.4-8.3 0.5-12.5 0.4-2.2-0.1-4.4-0.2-6.6-0.4-1.1-0.1-2.2-0.2-3.2-0.3-1.5-0.2-1.4-0.2 0.1 0l-2.1-0.3c-7.8-1.3-15.4-3.4-22.8-6.2-0.9-0.4-1.8-0.7-2.8-1.1-3.1-1.2 2.3 1.1-0.7-0.3-1.5-0.7-2.9-1.3-4.4-2-3.7-1.8-7.2-3.7-10.8-5.8-5.7-3.4-11.1-7.1-16.4-11.1 3 2.3-1.1-0.9-1.8-1.5-1.1-0.9-2.1-1.7-3.1-2.6-2.1-1.8-4.2-3.7-6.3-5.6-4.4-4.1-8.7-8.4-12.4-13.1-4.2-5.2-13.1-4.3-17.5 0-5 5.1-4 12.2 0.2 17.4z" fill="#040000" p-id="3904"></path></svg>`
}
]
},
{
name: '标记图标',
type: 'sign',
list: [
{
name: '1',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M809.728 429.696a18.901333 18.901333 0 0 0-15.274667-12.885333l-183.466666-26.624-81.92-166.272a18.901333 18.901333 0 0 0-34.005334 0l-81.92 166.272-183.594666 26.624a19.029333 19.029333 0 0 0-10.496 32.298666l132.693333 129.536-31.274667 182.741334a18.816 18.816 0 0 0 27.477334 19.84l164.138666-86.186667 164.096 86.058667a18.773333 18.773333 0 1 0 27.434667-19.84l-31.36-182.741334 132.693333-129.408a18.901333 18.901333 0 0 0 4.778667-19.413333z" fill="#FFFFFF"></path></svg>`
},
{
name: '2',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M644.565333 306.901333c32.128 0 65.834667-5.76 101.077334-17.237333a17.066667 17.066667 0 0 1 22.357333 16.213333v328.32c-1.109333 0.768 10.325333 27.093333-99.370667 19.84-109.653333-7.210667-181.76-45.098667-246.869333-45.098666-65.152 0-49.322667 2.688-74.154667 8.405333v168.064a24.746667 24.746667 0 0 1-24.490666 25.258667 22.528 22.528 0 0 1-17.28-7.253334 24.149333 24.149333 0 0 1-7.168-18.005333V281.258667C299.776 280.490667 328.106667 256 421.76 256s164.437333 50.901333 222.805333 50.901333z" fill="#FFFFFF"></path></svg>`
},
{
name: '3',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M524.074667 225.408l274.517333 274.517333a17.066667 17.066667 0 0 1 0 24.149334l-274.517333 274.517333a17.066667 17.066667 0 0 1-24.149334 0l-274.517333-274.517333a17.066667 17.066667 0 0 1 0-24.149334l274.517333-274.517333a17.066667 17.066667 0 0 1 24.149334 0z" fill="#FFFFFF"></path></svg>`
},
{
name: '4',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M317.866667 300.8h388.266666c9.386667 0 17.066667 7.68 17.066667 17.066667v388.266666a17.066667 17.066667 0 0 1-17.066667 17.066667h-388.266666a17.066667 17.066667 0 0 1-17.066667-17.066667v-388.266666c0-9.386667 7.68-17.066667 17.066667-17.066667z" fill="#FFFFFF"></path></svg>`
},
{
name: '5',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M498.346667 279.082667L248.789333 701.44a15.829333 15.829333 0 0 0 13.653334 23.893333h499.114666a15.829333 15.829333 0 0 0 13.653334-23.893333l-249.6-422.357333a15.829333 15.829333 0 0 0-27.264 0z" fill="#FFFFFF"></path></svg>`
},
{
name: '6',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M497.749333 798.549333l-31.445333-28.501333C313.941333 631.722667 213.333333 540.501333 213.333333 428.8a160.981333 160.981333 0 0 1 162.730667-162.730667c51.498667 0 100.906667 23.978667 133.12 61.696a177.536 177.536 0 0 1 133.162667-61.696 160.981333 160.981333 0 0 1 162.730666 162.730667c0 111.701333-100.608 202.965333-252.970666 341.333333l-31.445334 28.458667a17.066667 17.066667 0 0 1-22.912 0z" fill="#FFFFFF"></path><path d="M634.538667 487.808L555.050667 426.24 507.306667 256a201.002667 201.002667 0 0 0-23.594667 20.394667l-0.256-0.256L525.653333 426.666667l-133.290666 59.946666a14.08 14.08 0 0 0-8.021334 15.957334l28.757334 126.378666a14.208 14.208 0 0 0 27.733333-6.229333l-26.24-115.114667 126.037333-56.704 76.416 59.136a14.250667 14.250667 0 0 0 19.968-2.474666 14.08 14.08 0 0 0-2.474666-19.797334z" fill="#6D768D"></path></svg>`
},
{
name: '7',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M497.749333 798.549333l-31.445333-28.501333C313.941333 631.722667 213.333333 540.501333 213.333333 428.8a160.981333 160.981333 0 0 1 162.730667-162.730667c51.498667 0 100.906667 23.978667 133.12 61.696a177.536 177.536 0 0 1 133.162667-61.696 160.981333 160.981333 0 0 1 162.730666 162.730667c0 111.701333-100.608 202.965333-252.970666 341.333333l-31.445334 28.458667a17.066667 17.066667 0 0 1-22.912 0z" fill="#FFFFFF"></path></svg>`
},
{
name: '8',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M374.656 273.194667c5.973333 4.48 12.117333 9.6 18.346667 15.36 6.272 5.717333 11.904 12.373333 16.896 19.84 2.517333 4.010667 5.504 8.490667 9.002666 13.482666a529.493333 529.493333 0 0 1 20.266667 32.213334h155.221333a169.813333 169.813333 0 0 0 9.770667-15.744c2.474667-4.48 5.248-8.96 8.234667-13.482667a460.842667 460.842667 0 0 1 23.253333-31.829333c4.992-6.229333 12.245333-12.373333 21.76-18.346667a34.261333 34.261333 0 0 0 10.112-9.728 31.274667 31.274667 0 0 0 5.248-11.989333 18.56 18.56 0 0 0-1.536-11.605334 17.664 17.664 0 0 0-10.112-8.618666c-4.48-1.493333-8.362667-2.005333-11.605333-1.493334a46.933333 46.933333 0 0 0-9.770667 2.602667c-3.242667 1.28-6.613333 2.645333-10.112 4.138667a32.426667 32.426667 0 0 1-12.757333 2.261333 26.026667 26.026667 0 0 1-12.373334-2.645333 45.653333 45.653333 0 0 1-8.96-6.357334l-8.661333-7.850666a30.336 30.336 0 0 0-11.989333-6.4c-9.984-3.968-18.005333-4.693333-24.021334-2.218667-5.973333 2.474667-11.946667 6.485333-17.962666 11.946667a88.618667 88.618667 0 0 1-11.989334 10.496 7.338667 7.338667 0 0 1-3.754666 1.493333 46.165333 46.165333 0 0 1-8.277334-5.205333 71.808 71.808 0 0 1-7.125333-4.906667 37.973333 37.973333 0 0 1-6.4-6.357333c-3.968-3.968-9.941333-6.613333-17.92-7.850667a31.061333 31.061333 0 0 0-21.76 4.138667c-8.533333 5.461333-14.506667 10.069333-18.048 13.824a29.354667 29.354667 0 0 1-15.744 7.893333 23.978667 23.978667 0 0 1-13.098667-0.768 987.733333 987.733333 0 0 0-14.634666-4.48 80.725333 80.725333 0 0 0-14.250667-2.986667 16.768 16.768 0 0 0-11.989333 2.986667c-6.997333 5.461333-9.258667 12.074667-6.741334 19.84a34.56 34.56 0 0 0 13.482667 18.346667z" fill="#FFFFFF"></path><path d="M780.757333 545.152a219.306667 219.306667 0 0 0-19.882666-65.536 224.981333 224.981333 0 0 0-33.365334-49.792 430.336 430.336 0 0 0-37.12-37.12c-14.506667-11.946667-27.264-23.296-38.272-34.048a544.512 544.512 0 0 1-27.733333-28.842667 305.28 305.28 0 0 1-22.485333-26.197333h-168.746667c-6.485333 8.490667-13.994667 17.493333-22.485333 26.965333a360.96 360.96 0 0 1-26.24 28.074667c-10.538667 10.24-22.272 21.12-35.285334 32.597333a305.493333 305.493333 0 0 0-41.6 44.16 250.026667 250.026667 0 0 0-49.493333 117.589334 216.106667 216.106667 0 0 0 1.877333 70.4 220.586667 220.586667 0 0 0 75.349334 126.549333c21.248 18.005333 47.146667 32.597333 77.653333 43.818667 30.464 11.264 65.493333 16.853333 104.96 16.853333 38.528 0 72.874667-4.864 103.125333-14.592a265.045333 265.045333 0 0 0 78.378667-39.338667c21.973333-16.469333 39.594667-35.797333 52.864-58.026666 13.226667-22.186667 22.101333-45.824 26.624-70.784 4.992-30.421333 5.632-58.026667 1.877333-82.773334z" fill="#FFFFFF"></path><path d="M593.322667 647.509333a20.48 20.48 0 0 1-11.861334 3.2h-50.133333v14.165334c0 4.266667-1.792 8.362667-5.376 12.373333a15.914667 15.914667 0 0 1-13.952 5.333333 24.917333 24.917333 0 0 1-14.336-3.882666c-3.84-2.602667-5.973333-7.210667-6.4-13.824v-14.165334h-48.725333a17.792 17.792 0 0 1-11.818667-3.882666 10.24 10.24 0 0 1-3.968-9.6c0-4.266667 1.578667-7.68 4.693333-10.24a16.768 16.768 0 0 1 11.093334-3.925334h48.682666v-24.789333h-48.682666a15.573333 15.573333 0 0 1-11.52-4.266667 13.525333 13.525333 0 0 1-4.266667-9.941333 15.36 15.36 0 0 1 4.693333-10.624 14.72 14.72 0 0 1 11.093334-4.949333h48.682666l0.725334-14.890667a1053.568 1053.568 0 0 1-40.832-42.538667l-10.752-9.898666a41.216 41.216 0 0 1-6.442667-11.690667c-1.92-4.992-0.938667-10.069333 2.858667-15.274667a13.653333 13.653333 0 0 1 15.786666-3.84c6.186667 2.090667 11.221333 4.821333 15.018667 8.106667 1.92 2.389333 5.248 5.888 10.026667 10.666667l15.061333 14.848 19.328 19.157333 22.186667-20.565333a987.605333 987.605333 0 0 1 29.397333-25.514667 21.162667 21.162667 0 0 1 14.293333-5.674667c5.290667 0 9.557333 2.133333 12.928 6.4 6.186667 7.082667 3.84 15.36-7.168 24.789334a179.072 179.072 0 0 0-12.885333 12.373333c-5.76 5.973333-11.52 11.733333-17.194667 17.408-6.698667 7.082667-14.08 14.378667-22.186666 21.973333v13.44h46.506666c6.698667 0 11.605333 1.536 14.72 4.608a14.165333 14.165333 0 0 1 4.650667 10.282667c0 4.266667-1.450667 7.936-4.309333 11.008-2.858667 3.029333-7.637333 4.352-14.336 3.84l-46.506667 0.768-0.768 24.064h45.866667c13.354667 0 20.053333 4.992 20.053333 14.933333 0.469333 4.693333-0.853333 8.106667-3.925333 10.24z" fill="#6D768D"></path></svg>`
},
{
name: '9',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M512 213.333333l234.666667 341.333334h-128v213.333333h-213.333334v-213.333333h-128L512 213.333333z" fill="#FFFFFF"></path></svg>`
},
{
name: '10',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M533.333333 810.666667L298.666667 469.333333h128V256h213.333333v213.333333h128l-234.666667 341.333334z" fill="#FFFFFF"></path></svg>`
},
{
name: '11',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M213.333333 533.333333L554.666667 298.666667v128h213.333333v213.333333h-213.333333v128l-341.333334-234.666667z" fill="#FFFFFF"></path></svg>`
},
{
name: '12',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M810.666667 533.333333L469.333333 768v-128H256v-213.333333h213.333333V298.666667l341.333334 234.666666z" fill="#FFFFFF"></path></svg>`
},
{
name: '13',
icon: `<svg viewBox="0 0 1024 1024"><path d="M0 512c0 282.752 229.248 512 512 512s512-229.248 512-512S794.752 0 512 0 0 229.248 0 512z" fill="#6D768D"></path><path d="M571.349333 508.586667l162.389334-162.346667a44.330667 44.330667 0 1 0-62.72-62.72l-162.389334 162.389333-162.517333-162.389333a44.330667 44.330667 0 1 0-62.72 62.72l162.389333 162.389333-162.389333 162.474667a44.330667 44.330667 0 1 0 62.72 62.72l162.389333-162.346667 162.389334 162.389334a44.330667 44.330667 0 1 0 62.72-62.72l-162.261334-162.56z" fill="#FFFFFF"></path></svg>`
},
{
name: '14',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 0C233.386667 0 0 225.877333 0 512s225.877333 512 512 512 512-225.877333 512-512S790.613333 0 512 0z" fill="#6D768D"></path><path d="M726.144 311.210667l-277.333333 305.066666-124.8-124.8c-13.866667-13.866667-41.6-13.866667-55.466667 0-13.866667 13.866667-13.866667 41.6 0 55.466667l159.445333 152.533333c13.866667 13.866667 41.6 13.866667 55.466667 0l305.066667-332.8c13.866667-13.866667 13.866667-41.6 0-55.466666-20.778667-13.866667-48.512-13.866667-62.378667 0z" fill="#FFFFFF"></path></svg>`
},
{
name: '15',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M541.952 755.626667a40.618667 40.618667 0 0 1-29.824 12.373333 41.344 41.344 0 0 1-30.122667-12.373333 40.106667 40.106667 0 0 1-12.672-30.122667c0-11.605333 4.096-21.845333 12.672-30.122667a40.405333 40.405333 0 0 1 30.122667-12.714666c11.605333 0 21.546667 4.138667 29.824 12.714666a40.32 40.32 0 0 1 12.714667 30.122667c0 11.861333-4.096 21.76-12.714667 30.122667zM450.986667 241.28A77.866667 77.866667 0 0 1 512.256 213.333333c24.874667 0 45.354667 8.917333 61.354667 27.946667 15.488 18.432 23.722667 41.685333 23.722666 69.674667 0 23.765333-33.152 200.533333-44.672 329.045333h-80.128C463.146667 511.402667 426.666667 334.677333 426.666667 310.954667c0-27.392 8.277333-50.645333 24.32-69.674667z" fill="#FFFFFF"></path></svg>`
},
{
name: '16',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.794667 0 512 0z" fill="#6D768D"></path><path d="M490.666667 682.666667a64 64 0 1 1 0 128 64 64 0 0 1 0-128z m13.994666-490.752c61.397333 0 112.341333 14.634667 153.002667 43.946666 40.533333 29.269333 60.885333 72.618667 60.885333 130.133334 0 35.242667-12.373333 64.938667-29.952 89.045333-10.282667 14.677333-33.664 33.408-62.890666 56.192l-32.426667 22.357333c-15.701333 12.202667-29.696 26.453333-34.858667 42.666667-1.706667 5.546667-3.072 14.677333-3.968 24.533333-0.426667 4.949333-4.864 15.018667-15.232 15.018667h-83.328c-13.568 0-15.957333-10.581333-15.744-15.786667 1.493333-34.005333 4.608-64.213333 18.474667-80.469333 28.074667-32.896 91.904-73.813333 91.904-73.813333a104.106667 104.106667 0 0 0 23.552-24.021334c10.837333-14.933333 19.797333-31.317333 19.797333-49.237333 0-20.565333-6.016-39.338667-18.090666-56.32-12.032-16.938667-34.090667-25.386667-66.005334-25.386667-31.445333 0-53.76 10.410667-66.901333 31.274667-9.685333 15.445333-15.786667 29.610667-18.346667 45.013333-0.853333 5.461333-4.394667 16.981333-16.042666 16.981334H327.210667c-17.322667 0-21.12-11.221333-20.650667-16.64 6.272-68.138667 32.896-114.688 80-144.597334 32-20.565333 71.381333-30.890667 118.101333-30.890666z" fill="#FFFFFF"></path></svg>`
},
{
name: '17',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M336.256 410.026667H253.312a40.021333 40.021333 0 0 0-39.850667 43.264l23.296 278.101333c1.706667 20.693333 19.072 36.608 39.850667 36.608h59.648c11.050667 0 20.010667-8.96 20.010667-19.968v-318.037333a19.968 19.968 0 0 0-20.010667-19.968z m434.432 0h-178.944C653.312 182.314667 548.949333 170.666667 548.949333 170.666667c-44.288 0-35.114667 34.986667-38.442666 40.832 0 84.48-68.010667 155.093333-101.034667 184.362666a39.552 39.552 0 0 0-13.226667 29.653334v322.56c0 11.008 8.96 19.925333 20.010667 19.925333h233.728c30.378667 0 58.154667-17.152 71.68-44.373333 18.176-36.736 40.448-90.112 54.656-133.973334 13.781333-42.410667 26.24-94.976 33.578667-131.968a39.850667 39.850667 0 0 0-39.253334-47.658666z" fill="#FFFFFF"></path></svg>`
},
{
name: '18',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M796.16 413.909333c-31.146667-0.298667-115.626667-0.085333-146.858667-0.085333h-158.464c8.533333-7.68 15.914667-14.506667 23.594667-20.906667 29.781333-24.874667 25.813333-71.082667-14.208-88.874666-22.954667-10.24-44.970667-5.632-64 11.52-34.944 31.274667-69.632 62.677333-104.277333 93.994666a15.488 15.488 0 0 1-11.178667 4.437334c-11.221333-0.085333-26.88-0.128-46.933333-0.170667a17.066667 17.066667 0 0 0-17.109334 17.066667L256 719.701333a17.066667 17.066667 0 0 0 17.066667 17.152l49.578666-0.085333c3.968 0 7.466667 0.768 10.88 2.602667 15.829333 8.832 31.701333 17.493333 47.616 26.24a18.133333 18.133333 0 0 0 9.301334 2.346666h168.405333c6.186667 0 11.946667-0.981333 17.834667-2.56 29.44-7.253333 40.021333-30.293333 38.528-52.565333-0.768-9.728-4.266667-18.346667-9.984-26.24 19.626667-5.76 35.114667-16.213333 42.112-36.096 7.125333-20.394667 1.621333-38.4-12.672-53.333333 28.16-19.754667 34.858667-44.672 18.645333-75.648h140.458667c6.570667 0 13.013333-0.597333 19.370666-2.645334 31.957333-9.813333 48.810667-42.88 35.626667-71.552-10.154667-22.186667-28.629333-33.152-52.608-33.450666z" fill="#FFFFFF"></path></svg>`
},
{
name: '19',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M270.506667 413.909333c31.146667-0.298667 115.626667-0.085333 146.858666-0.085333h158.464c-8.533333-7.68-15.914667-14.506667-23.594666-20.906667-29.781333-24.874667-25.813333-71.082667 14.208-88.874666 22.954667-10.24 44.970667-5.632 64 11.52 34.944 31.274667 69.632 62.677333 104.277333 93.994666 3.413333 2.986667 6.528 4.437333 11.178667 4.437334 11.221333-0.085333 26.88-0.128 46.933333-0.170667a17.066667 17.066667 0 0 1 17.109333 17.066667l0.682667 288.853333a17.066667 17.066667 0 0 1-17.066667 17.152l-49.578666-0.085333a22.101333 22.101333 0 0 0-10.88 2.602666c-15.829333 8.832-31.701333 17.493333-47.616 26.24a18.133333 18.133333 0 0 1-9.301334 2.346667h-168.405333a68.693333 68.693333 0 0 1-17.834667-2.56c-29.44-7.253333-40.021333-30.293333-38.528-52.565333 0.768-9.728 4.266667-18.346667 9.984-26.24-19.626667-5.76-35.114667-16.213333-42.112-36.096-7.125333-20.394667-1.621333-38.4 12.672-53.333334-28.16-19.754667-34.858667-44.672-18.645333-75.648H272.853333c-6.570667 0-13.013333-0.597333-19.370666-2.645333-31.957333-9.813333-48.810667-42.88-35.626667-71.552 10.154667-22.186667 28.629333-33.152 52.608-33.450667z" fill="#FFFFFF"></path></svg>`
},
{
name: '20',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M667.733333 480.128H400v-111.36a97.706667 97.706667 0 0 1 97.621333-97.621333 97.706667 97.706667 0 0 1 97.578667 97.621333 28.885333 28.885333 0 0 0 57.813333 0A155.605333 155.605333 0 0 0 497.621333 213.333333a155.605333 155.605333 0 0 0-155.392 155.434667v111.36h-14.677333A28.885333 28.885333 0 0 0 298.666667 509.013333v292.010667a28.885333 28.885333 0 0 0 28.885333 28.885333h340.138667a28.885333 28.885333 0 0 0 28.928-28.885333V509.013333a28.885333 28.885333 0 0 0-28.928-28.885333z" fill="#FFFFFF"></path></svg>`
},
{
name: '21',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M400.042667 437.461333v-111.36a97.706667 97.706667 0 0 1 97.621333-97.621333 97.706667 97.706667 0 0 1 97.578667 97.621333 28.885333 28.885333 0 0 0 57.813333 0A155.605333 155.605333 0 0 0 497.621333 170.666667a155.605333 155.605333 0 0 0-155.392 155.434666v111.36h-14.677333A28.885333 28.885333 0 0 0 298.666667 466.346667v292.010666a28.885333 28.885333 0 0 0 28.885333 28.885334h340.138667a28.885333 28.885333 0 0 0 28.928-28.885334V466.346667a28.885333 28.885333 0 0 0-28.928-28.885334H400.042667z" fill="#FFFFFF"></path><path d="M595.242667 437.461333v-111.36a97.706667 97.706667 0 0 0-97.621334-97.621333 97.706667 97.706667 0 0 0-97.578666 97.621333 28.885333 28.885333 0 0 1-57.813334 0A155.605333 155.605333 0 0 1 497.621333 170.666667a155.605333 155.605333 0 0 1 155.434667 155.434666v111.36h14.634667c16 0 28.928 12.928 28.928 28.885334v292.010666a28.885333 28.885333 0 0 1-28.928 28.885334H327.552A28.885333 28.885333 0 0 1 298.666667 758.357333V466.346667c0-15.957333 12.928-28.885333 28.885333-28.885334h267.690667z" fill="#FFFFFF"></path></svg>`
},
{
name: '22',
icon: `<svg viewBox="0 0 1024 1024"><path d="M511.999787 512.000213m-511.999787 0a511.999787 511.999787 0 1 0 1023.999573 0 511.999787 511.999787 0 1 0-1023.999573 0Z" fill="#6D768D"></path><path d="M381.354508 364.586941c0 54.015977 29.013321 103.935957 75.946635 130.986613a152.53327 152.53327 0 0 0 151.935936 0 151.12527 151.12527 0 0 0 75.946636-130.986613A151.594604 151.594604 0 0 0 533.333111 213.333671a151.594604 151.594604 0 0 0-151.89327 151.25327zM660.479725 498.901552a185.258589 185.258589 0 0 1-127.146614 50.346646c-49.066646 0-93.866628-19.199992-127.06128-50.346646C317.141201 544.853533 255.999893 637.440161 255.999893 744.106783c0 13.183995 10.709329 23.850657 23.978657 23.850657h506.709122a23.893323 23.893323 0 0 0 23.978657-23.893323c0-106.538622-61.098641-199.25325-150.186604-245.205232z" fill="#FFFFFF"></path></svg>`
},
{
name: '23',
icon: `<svg viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#6D768D"></path><path d="M445.610667 401.578667a129.322667 129.322667 0 1 0 258.645333 0 129.322667 129.322667 0 0 0-258.645333 0z m237.568 114.901333a157.354667 157.354667 0 0 1-216.362667 0 236.373333 236.373333 0 0 0-127.957333 209.706667c0 11.264 9.130667 20.394667 20.394666 20.394666h431.402667a20.394667 20.394667 0 0 0 20.394667-20.394666 236.373333 236.373333 0 0 0-127.872-209.706667zM409.813333 401.578667c0-40.362667 14.592-77.397333 38.698667-106.112a112.725333 112.725333 0 0 0-29.013333-3.925334 112.64 112.64 0 0 0-112.426667 112.469334 112.64 112.64 0 0 0 144.853333 107.648 164.693333 164.693333 0 0 1-42.112-110.08z m-18.602666 136.704a136.533333 136.533333 0 0 1-65.706667-34.474667 205.44 205.44 0 0 0-111.232 182.4c0 9.813333 7.936 17.706667 17.706667 17.706667H303.36a273.621333 273.621333 0 0 1 87.893333-165.632z" fill="#FFFFFF"></path></svg>`
}
]
}
]
// 获取nodeIconList icon内容
const getNodeIconListIcon = name => {
let arr = name.split('_')
let typeData = nodeIconList.find(item => {
return item.type === arr[0]
})
return typeData.list.find(item => {
return item.name === arr[1]
}).icon
}
export default {
hyperlink,
note,
nodeIconList,
getNodeIconListIcon
}

View File

@@ -0,0 +1,49 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 天空蓝
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(115, 161, 191)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#333',
// 根节点样式
root: {
fillColor: 'rgb(115, 161, 191)',
active: {
borderColor: 'rgb(57, 80, 96)'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(238, 243, 246)',
color: '#333',
borderColor: 'rgb(115, 161, 191)',
borderWidth: 1,
fontSize: 14,
active: {
borderColor: 'rgb(57, 80, 96)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333',
active: {
borderColor: 'rgb(57, 80, 96)'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#333',
color: '#333',
active: {
borderColor: 'rgb(57, 80, 96)'
}
}
})

View File

@@ -0,0 +1,49 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 脑残粉
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 115, 148)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#333',
// 根节点样式
root: {
fillColor: 'rgb(191, 115, 148)',
active: {
borderColor: 'rgb(96, 57, 74)'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(246, 238, 242)',
color: '#333',
borderColor: 'rgb(191, 115, 148)',
borderWidth: 1,
fontSize: 14,
active: {
borderColor: 'rgb(96, 57, 74)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333',
active: {
borderColor: 'rgb(96, 57, 74)'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#333',
color: '#333',
active: {
borderColor: 'rgb(96, 57, 74)'
}
}
})

View File

@@ -0,0 +1,64 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 脑图经典
export default merge(defaultTheme, {
// 连线的颜色
lineColor: '#fff',
// 连线的粗细
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#fff',
// 背景颜色
backgroundColor: 'rgb(58, 65, 68)',
// 背景图片
backgroundImage:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowQzg5QTQ0NDhENzgxMUUzOENGREE4QTg0RDgzRTZDNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowQzg5QTQ0NThENzgxMUUzOENGREE4QTg0RDgzRTZDNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkMwOEQ1NDRGOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkMwOEQ1NDUwOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+e9P33AAAACVJREFUeNpisXJ0YUACTAyoAMr/+eM7EGGRZ4FQ7BycEAZAgAEAHbEGtkoQm/wAAAAASUVORK5CYII=',
// 背景重复
backgroundRepeat: 'repeat',
// 根节点样式
root: {
fillColor: 'rgb(233, 223, 152)',
color: '#333',
fontSize: 24,
borderRadius: 21,
active: {
fillColor: 'rgb(254, 219, 0)',
borderColor: 'transparent'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(164, 197, 192)',
borderColor: 'transparent',
color: '#333',
fontSize: 16,
borderRadius: 10,
active: {
fillColor: 'rgb(254, 219, 0)',
borderColor: 'transparent'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#fff',
fontWeight: 'bold',
active: {
fillColor: 'rgb(254, 219, 0)',
borderColor: 'transparent'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'transparent',
color: '#333',
active: {
fillColor: 'rgb(254, 219, 0)',
borderColor: 'transparent'
}
}
})

View File

@@ -0,0 +1,55 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 经典2
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',
// 连线的粗细
lineWidth: 2,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(51, 51, 51)',
// 背景颜色
backgroundColor: '#fff',
// 根节点样式
root: {
fillColor: 'rgb(18, 187, 55)',
color: '#fff',
fontSize: 24,
borderRadius: 10,
active: {
borderColor: 'rgb(51, 51, 51)'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(241, 242, 241)',
borderColor: 'transparent',
color: '#1a1a1a',
fontSize: 18,
borderRadius: 10,
active: {
borderColor: 'rgb(51, 51, 51)'
}
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: '#1a1a1a',
active: {
borderColor: 'rgb(51, 51, 51)'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'rgb(51, 51, 51)',
borderWidth: 2,
color: '#1a1a1a',
active: {
borderColor: 'rgb(18, 187, 55)'
}
}
})

View File

@@ -0,0 +1,58 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 经典3
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(94, 202, 110)',
// 连线的粗细
lineWidth: 2,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#1a1a1a',
// 背景颜色
backgroundColor: 'rgb(241, 241, 241)',
// 根节点样式
root: {
fillColor: 'rgb(255, 245, 214)',
color: '#1a1a1a',
fontSize: 24,
borderRadius: 10,
borderColor: 'rgb(249, 199, 84)',
borderWidth: 1,
active: {
borderColor: 'rgb(94, 202, 110)'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(255, 245, 214)',
borderColor: 'rgb(249, 199, 84)',
borderWidth: 1,
color: '#1a1a1a',
fontSize: 18,
borderRadius: 10,
active: {
borderColor: 'rgb(94, 202, 110)'
}
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: '#1a1a1a',
active: {
borderColor: 'rgb(94, 202, 110)'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#1a1a1a',
color: '#1a1a1a',
borderWidth: 2,
active: {
borderColor: 'rgb(94, 202, 110)'
}
}
})

View File

@@ -0,0 +1,61 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 经典4
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(30, 53, 86)',
// 连线的粗细
lineWidth: 2,
// 概要连线的粗细
generalizationLineWidth: 2,
// 概要连线的颜色
generalizationLineColor: 'rgb(56, 123, 233)',
// 背景颜色
backgroundColor: 'rgb(241, 241, 241)',
// 根节点样式
root: {
fillColor: 'rgb(30, 53, 86)',
color: '#fff',
fontSize: 24,
borderRadius: 10,
borderColor: 'rgb(189, 197, 201)',
borderWidth: 2,
active: {
borderColor: 'rgb(169, 218, 218)'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(169, 218, 218)',
borderColor: 'rgb(30, 53, 86)',
borderWidth: 2,
color: '#fff',
fontSize: 18,
borderRadius: 10,
active: {
borderColor: 'rgb(56, 123, 233)'
}
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(30, 53, 86)',
borderColor: 'rgb(30, 53, 86)',
borderWidth: 1,
marginY: 20,
active: {
borderColor: 'rgb(169, 218, 218)'
}
},
// 概要节点样式
generalization: {
fillColor: 'rgb(56, 123, 233)',
borderColor: 'rgb(56, 123, 233)',
color: '#fff',
borderWidth: 0,
active: {
borderColor: 'rgb(169, 218, 218)'
}
}
})

View File

@@ -0,0 +1,52 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 经典蓝
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',
// 连线的粗细
lineWidth: 2,
// 概要连线的粗细
generalizationLineWidth: 2,
// 概要连线的颜色
generalizationLineColor: 'rgb(51, 51, 51)',
// 背景颜色
backgroundColor: 'rgb(239, 248, 250)',
// 根节点样式
root: {
fillColor: 'rgb(255, 255, 255)',
color: '#222',
active: {
borderColor: 'rgb(94, 199, 248)'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(255, 255, 255)',
color: '#222',
borderColor: 'rgb(255, 255, 255)',
borderWidth: 1,
fontSize: 14,
active: {
borderColor: 'rgb(94, 199, 248)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333',
active: {
borderColor: 'rgb(94, 199, 248)'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'rgb(51, 51, 51)',
color: '#333',
active: {
borderColor: 'rgb(94, 199, 248)'
}
}
})

View File

@@ -0,0 +1,51 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 经典绿
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(123, 199, 120)',
// 背景颜色
backgroundColor: 'rgb(236, 245, 231)',
// 概要连线的粗细
generalizationLineWidth: 2,
// 概要连线的颜色
generalizationLineColor: 'rgb(123, 199, 120)',
// 根节点样式
root: {
fillColor: 'rgb(253, 244, 217)',
color: '#222',
active: {
borderColor: 'rgb(94, 199, 248)'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(253, 244, 217)',
color: '#222',
borderColor: 'rgb(242, 200, 104)',
borderWidth: 1,
fontSize: 14,
active: {
borderColor: 'rgb(94, 199, 248)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333',
active: {
borderColor: 'rgb(94, 199, 248)'
}
},
// 概要节点样式
generalization: {
fillColor: 'rgb(123, 199, 120)',
borderColor: 'transparent',
borderWidth: 2,
color: '#fff',
active: {
borderColor: 'rgb(94, 199, 248)'
}
}
})

View File

@@ -0,0 +1,54 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 暗色
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(17, 68, 23)',
// 连线的粗细
lineWidth: 2,
// 概要连线的粗细
generalizationLineWidth: 2,
// 概要连线的颜色
generalizationLineColor: '#fff',
// 背景颜色
backgroundColor: 'rgb(15, 16, 17)',
// 根节点样式
root: {
fillColor: 'rgb(28, 178, 43)',
color: '#fff',
fontSize: 24,
borderRadius: 10,
active: {
borderColor: 'rgb(17, 68, 23)'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(55, 56, 58)',
color: 'rgb(147,148,149)',
fontSize: 18,
borderRadius: 10,
borderWidth: 0,
active: {
borderColor: 'rgb(17, 68, 23)'
}
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(147, 148, 149)',
active: {
borderColor: 'rgb(17, 68, 23)'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'transparent',
color: '#333',
active: {
borderColor: 'rgb(17, 68, 23)'
}
}
})

View File

@@ -0,0 +1,56 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 暗色2
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(75, 81, 78)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(255, 119, 34)',
// 背景颜色
backgroundColor: 'rgb(27, 31, 34)',
// 根节点样式
root: {
fillColor: 'rgb(36, 179, 96)',
color: '#fff',
borderColor: '',
borderWidth: 0,
active: {
borderColor: 'rgb(254, 199, 13)',
borderWidth: 3
}
},
// 二级节点样式
second: {
fillColor: 'rgb(254, 199, 13)',
color: 'rgb(0, 0, 0)',
borderColor: '',
borderWidth: 0,
fontSize: 14,
active: {
borderColor: 'rgb(36, 179, 96)',
borderWidth: 2
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: 'rgb(204, 204, 204)',
active: {
borderColor: 'rgb(254, 199, 13)'
}
},
// 概要节点样式
generalization: {
fillColor: 'transparent',
borderColor: 'rgb(255, 119, 34)',
borderWidth: 2,
color: 'rgb(204, 204, 204)',
active: {
borderColor: 'rgb(254, 199, 13)'
}
}
})

View File

@@ -0,0 +1,155 @@
// 默认主题
export default {
// 节点内边距
paddingX: 15,
paddingY: 5,
// 图片显示的最大宽度
imgMaxWidth: 100,
// 图片显示的最大高度
imgMaxHeight: 100,
// icon的大小
iconSize: 20,
// 连线的粗细
lineWidth: 1,
// 连线的颜色
lineColor: '#549688',
// 连线样式
lineDasharray: 'none',
// 连线风格
lineStyle: 'straight', // 针对logicalStructure、mindMap两种结构。曲线curve、直线straight、直连direct
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#549688',
// 概要曲线距节点的距离
generalizationLineMargin: 0,
// 概要节点距节点的距离
generalizationNodeMargin: 20,
// 关联线默认状态的粗细
associativeLineWidth: 2,
// 关联线默认状态的颜色
associativeLineColor: 'rgb(51, 51, 51)',
// 关联线激活状态的粗细
associativeLineActiveWidth: 8,
// 关联线激活状态的颜色
associativeLineActiveColor: 'rgba(2, 167, 240, 1)',
// 背景颜色
backgroundColor: '#fafafa',
// 背景图片
backgroundImage: 'none',
// 背景重复
backgroundRepeat: 'no-repeat',
// 设置背景图像的起始位置
backgroundPosition: 'center center',
// 设置背景图片大小
backgroundSize: 'cover',
// 节点使用横线样式
nodeUseLineStyle: false,
// 根节点样式
root: {
shape: 'rectangle',
fillColor: '#549688',
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: 'transparent',
borderWidth: 0,
borderDasharray: 'none',
borderRadius: 5,
textDecoration: 'none',
active: {
borderColor: 'rgb(57, 80, 96)',
borderWidth: 3,
borderDasharray: 'none'
}
},
// 二级节点样式
second: {
shape: 'rectangle',
marginX: 100,
marginY: 40,
fillColor: '#fff',
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#565656',
fontSize: 16,
fontWeight: 'noraml',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: '#549688',
borderWidth: 1,
borderDasharray: 'none',
borderRadius: 5,
textDecoration: 'none',
active: {
borderColor: 'rgb(57, 80, 96)',
borderWidth: 3,
borderDasharray: 'none'
}
},
// 三级及以下节点样式
node: {
shape: 'rectangle',
marginX: 50,
marginY: 0,
fillColor: 'transparent',
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#6a6d6c',
fontSize: 14,
fontWeight: 'noraml',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: 'transparent',
borderWidth: 0,
borderRadius: 5,
borderDasharray: 'none',
textDecoration: 'none',
active: {
borderColor: 'rgb(57, 80, 96)',
borderWidth: 3,
borderDasharray: 'none'
}
},
// 概要节点样式
generalization: {
shape: 'rectangle',
marginX: 100,
marginY: 40,
fillColor: '#fff',
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#565656',
fontSize: 16,
fontWeight: 'noraml',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: '#549688',
borderWidth: 1,
borderDasharray: 'none',
borderRadius: 5,
textDecoration: 'none',
active: {
borderColor: 'rgb(57, 80, 96)',
borderWidth: 3,
borderDasharray: 'none'
}
}
}
// 支持激活样式的属性
// 简单来说,会改变节点大小的都不支持在激活时设置,为了性能考虑,节点切换激活态时不会重新计算节点大小
export const supportActiveStyle = [
'fillColor',
'color',
'fontWeight',
'fontStyle',
'borderColor',
'borderWidth',
'borderDasharray',
'borderRadius',
'textDecoration'
]
export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth']

View File

@@ -0,0 +1,49 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 泥土黄
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 147, 115)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#333',
// 根节点样式
root: {
fillColor: 'rgb(191, 147, 115)',
active: {
borderColor: 'rgb(96, 73, 57)'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(246, 242, 238)',
color: '#333',
borderColor: 'rgb(191, 147, 115)',
borderWidth: 1,
fontSize: 14,
active: {
borderColor: 'rgb(96, 73, 57)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333',
active: {
borderColor: 'rgb(96, 73, 57)'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#333',
color: '#333',
active: {
borderColor: 'rgb(96, 73, 57)'
}
}
})

View File

@@ -0,0 +1,36 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 清新绿
export default merge(defaultTheme, {
// 连线的颜色
lineColor: '#333',
// 背景颜色
backgroundColor: '#d1f6ec',
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#333',
// 根节点样式
root: {
fillColor: '#1fb27d'
},
// 二级节点样式
second: {
fillColor: '#fff',
color: '#565656',
borderColor: 'transparent',
borderWidth: 0
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#333',
color: '#333',
active: {
borderColor: 'rgb(57, 80, 96)',
borderWidth: 3,
borderDasharray: 'none'
}
}
})

View File

@@ -0,0 +1,49 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 清新红
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 115, 115)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#333',
// 根节点样式
root: {
fillColor: 'rgb(191, 115, 115)',
active: {
borderColor: 'rgb(96, 57, 57)'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(246, 238, 238)',
color: '#333',
borderColor: 'rgb(191, 115, 115)',
borderWidth: 1,
fontSize: 14,
active: {
borderColor: 'rgb(96, 57, 57)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333',
active: {
borderColor: 'rgb(96, 57, 57)'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#333',
color: '#333',
active: {
borderColor: 'rgb(96, 57, 57)'
}
}
})

View File

@@ -0,0 +1,55 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 金色vip
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 56, 62)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(127, 93, 64)',
// 背景颜色
backgroundColor: '#fff',
// 根节点样式
root: {
fillColor: 'rgb(51, 56, 62)',
color: 'rgb(247, 208, 160)',
borderColor: '',
borderWidth: 0,
active: {
borderColor: 'rgb(247, 208, 160)',
borderWidth: 3
}
},
// 二级节点样式
second: {
fillColor: 'rgb(239, 209, 176)',
color: 'rgb(81, 58, 42)',
borderColor: '',
borderWidth: 0,
fontSize: 14,
active: {
borderColor: 'rgb(51, 56, 62)',
borderWidth: 2
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#222',
active: {
borderColor: 'rgb(0, 192, 184)'
}
},
// 概要节点样式
generalization: {
fillColor: 'rgb(127, 93, 64)',
borderColor: 'transparent',
color: 'rgb(255, 214, 175)',
active: {
borderColor: 'rgb(51, 56, 62)'
}
}
})

View File

@@ -0,0 +1,56 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 绿叶
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(40, 193, 84)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(251, 158, 0)',
// 背景颜色
backgroundColor: 'rgb(238, 255, 243)',
// 根节点样式
root: {
fillColor: 'rgb(25, 193, 73)',
color: '#fff',
borderColor: '',
borderWidth: 0,
active: {
borderColor: '#222',
borderWidth: 3
}
},
// 二级节点样式
second: {
fillColor: '#fff',
color: 'rgb(69, 149, 96)',
borderColor: '',
borderWidth: 0,
fontSize: 14,
active: {
borderColor: 'rgb(25, 193, 73)',
borderWidth: 2
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#222',
active: {
borderColor: 'rgb(25, 193, 73)'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'rgb(251, 158, 0)',
borderWidth: 2,
color: 'rgb(51, 51, 51)',
active: {
borderColor: 'rgb(25, 193, 73)'
}
}
})

View File

@@ -0,0 +1,47 @@
import defaultTheme from './default'
import freshGreen from './freshGreen'
import blueSky from './blueSky'
import brainImpairedPink from './brainImpairedPink'
import romanticPurple from './romanticPurple'
import freshRed from './freshRed'
import earthYellow from './earthYellow'
import classic from './classic'
import classic2 from './classic2'
import classic3 from './classic3'
import classic4 from './classic4'
import dark from './dark'
import classicGreen from './classicGreen'
import classicBlue from './classicBlue'
import minions from './minions'
import pinkGrape from './pinkGrape'
import mint from './mint'
import gold from './gold'
import vitalityOrange from './vitalityOrange'
import greenLeaf from './greenLeaf'
import dark2 from './dark2'
import skyGreen from './skyGreen'
export default {
default: defaultTheme,
freshGreen,
blueSky,
brainImpairedPink,
romanticPurple,
freshRed,
earthYellow,
classic,
classic2,
classic3,
classic4,
dark,
classicGreen,
classicBlue,
minions,
pinkGrape,
mint,
gold,
vitalityOrange,
greenLeaf,
dark2,
skyGreen
}

View File

@@ -0,0 +1,52 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 小黄人
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#222',
// 背景颜色
backgroundColor: 'rgb(248, 215, 49)',
// 根节点样式
root: {
fillColor: 'rgb(55, 165, 255)',
borderColor: 'rgb(51, 51, 51)',
borderWidth: 3,
active: {
borderColor: 'rgb(255, 160, 36)'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(255, 160, 36)',
color: '#222',
borderColor: 'rgb(51, 51, 51)',
borderWidth: 3,
fontSize: 14,
active: {
borderColor: 'rgb(55, 165, 255)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#222',
active: {
borderColor: 'rgb(55, 165, 255)'
}
},
// 概要节点样式
generalization: {
borderColor: '#222',
borderWidth: 3,
color: '#222',
active: {
borderColor: 'rgb(55, 165, 255)'
}
}
})

View File

@@ -0,0 +1,53 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 薄荷
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(104, 204, 202)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(90, 206, 241)',
// 背景颜色
backgroundColor: 'rgb(239, 255, 255)',
// 根节点样式
root: {
fillColor: 'rgb(0, 192, 184)',
borderColor: '',
borderWidth: 0,
active: {
borderColor: 'rgb(255, 160, 36)',
borderWidth: 3
}
},
// 二级节点样式
second: {
fillColor: '#fff',
color: '#222',
borderColor: 'rgb(184, 235, 233)',
borderWidth: 2,
fontSize: 14,
active: {
borderColor: 'rgb(0, 192, 184)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#222',
active: {
borderColor: 'rgb(0, 192, 184)'
}
},
// 概要节点样式
generalization: {
fillColor: 'rgb(90, 206, 241)',
borderColor: 'transparent',
color: '#fff',
active: {
borderColor: 'rgb(0, 192, 184)'
}
}
})

View File

@@ -0,0 +1,55 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 粉红葡萄
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(166, 101, 106)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#fff',
// 背景颜色
backgroundColor: 'rgb(255, 208, 211)',
// 根节点样式
root: {
fillColor: 'rgb(139, 109, 225)',
borderColor: '',
borderWidth: 0,
active: {
borderColor: 'rgb(243, 104, 138)',
borderWidth: 2
}
},
// 二级节点样式
second: {
fillColor: 'rgb(243, 104, 138)',
color: '#fff',
borderColor: '',
borderWidth: 0,
fontSize: 14,
active: {
borderColor: 'rgb(139, 109, 225)',
borderWidth: 2
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#222',
active: {
borderColor: 'rgb(139, 109, 225)'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'transparent',
color: '#222',
active: {
borderColor: 'rgb(139, 109, 225)',
borderWidth: 2
}
}
})

View File

@@ -0,0 +1,49 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 浪漫紫
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(123, 115, 191)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 概要连线的粗细
generalizationLineWidth: 1,
// 概要连线的颜色
generalizationLineColor: '#333',
// 根节点样式
root: {
fillColor: 'rgb(123, 115, 191)',
active: {
borderColor: 'rgb(61, 57, 96)'
}
},
// 二级节点样式
second: {
fillColor: 'rgb(239, 238, 246)',
color: '#333',
borderColor: 'rgb(123, 115, 191)',
borderWidth: 1,
fontSize: 14,
active: {
borderColor: 'rgb(61, 57, 96)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333',
active: {
borderColor: 'rgb(61, 57, 96)'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: '#333',
color: '#333',
active: {
borderColor: 'rgb(61, 57, 96)'
}
}
})

View File

@@ -0,0 +1,55 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 天清绿
export default merge(defaultTheme, {
// 连线的颜色
lineColor: '#fff',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: '#fff',
// 背景颜色
backgroundColor: 'rgb(80, 156, 170)',
// 根节点样式
root: {
fillColor: '#fff',
borderColor: '',
borderWidth: 0,
color: 'rgb(65, 89, 158)',
active: {
borderColor: 'rgb(251, 227, 188)',
borderWidth: 3
}
},
// 二级节点样式
second: {
fillColor: 'rgb(251, 227, 188)',
color: 'rgb(65, 89, 158)',
borderColor: '',
borderWidth: 0,
fontSize: 14,
active: {
borderColor: '#fff',
borderWidth: 2
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: 'rgb(65, 89, 158)',
active: {
borderColor: 'rgb(251, 227, 188)'
}
},
// 概要节点样式
generalization: {
fillColor: '#fff',
borderColor: 'transparent',
color: 'rgb(65, 89, 158)',
active: {
borderColor: 'rgb(251, 227, 188)'
}
}
})

View File

@@ -0,0 +1,55 @@
import defaultTheme from './default'
import merge from 'deepmerge'
// 活力橙
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(254, 146, 0)',
lineWidth: 3,
// 概要连线的粗细
generalizationLineWidth: 3,
// 概要连线的颜色
generalizationLineColor: 'rgb(255, 222, 69)',
// 背景颜色
backgroundColor: 'rgb(255, 246, 243)',
// 根节点样式
root: {
fillColor: 'rgb(255, 112, 52)',
color: '#fff',
borderColor: '',
borderWidth: 0,
active: {
borderColor: 'rgb(51, 51, 51)',
borderWidth: 3
}
},
// 二级节点样式
second: {
fillColor: '#fff',
color: 'rgb(51, 51, 51)',
borderColor: '',
borderWidth: 0,
fontSize: 14,
active: {
borderColor: 'rgb(255, 112, 52)',
borderWidth: 2
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#222',
active: {
borderColor: 'rgb(255, 112, 52)'
}
},
// 概要节点样式
generalization: {
fillColor: 'rgb(255, 222, 69)',
borderColor: 'transparent',
color: 'rgb(51, 51, 51)',
active: {
borderColor: 'rgb(255, 112, 52)'
}
}
})

View File

@@ -0,0 +1,182 @@
// 获取目标节点在起始节点的目标数组中的索引
export const getAssociativeLineTargetIndex = (node, toNode) => {
return node.nodeData.data.associativeLineTargets.findIndex(item => {
return item === toNode.nodeData.data.id
})
}
// 计算贝塞尔曲线的控制点
export const computeCubicBezierPathPoints = (x1, y1, x2, y2) => {
let cx1 = x1 + (x2 - x1) / 2
let cy1 = y1
let cx2 = cx1
let cy2 = y2
if (Math.abs(x1 - x2) <= 5) {
cx1 = x1 + (y2 - y1) / 2
cx2 = cx1
}
return [
{
x: cx1,
y: cy1
},
{
x: cx2,
y: cy2
}
]
}
// 拼接贝塞尔曲线路径
export const joinCubicBezierPath = (startPoint, endPoint, point1, point2) => {
return `M ${startPoint.x},${startPoint.y} C ${point1.x},${point1.y} ${point2.x},${point2.y} ${endPoint.x},${endPoint.y}`
}
// 获取节点的位置信息
const getNodeRect = node => {
let { left, top, width, height } = node
return {
right: left + width,
bottom: top + height,
left,
top
}
}
// 三次贝塞尔曲线
export const cubicBezierPath = (x1, y1, x2, y2) => {
let points = computeCubicBezierPathPoints(x1, y1, x2, y2)
return joinCubicBezierPath(
{ x: x1, y: y1 },
{ x: x2, y: y2 },
points[0],
points[1]
)
}
// 获取节点的连接点
export const getNodePoint = (node, dir = 'right') => {
let { left, top, width, height } = node
switch (dir) {
case 'left':
return {
x: left,
y: top + height / 2
}
case 'right':
return {
x: left + width,
y: top + height / 2
}
case 'top':
return {
x: left + width / 2,
y: top
}
case 'bottom':
return {
x: left + width / 2,
y: top + height
}
default:
break
}
}
// 根据两个节点的位置计算节点的连接点
export const computeNodePoints = (fromNode, toNode) => {
let fromRect = getNodeRect(fromNode)
let fromCx = (fromRect.right + fromRect.left) / 2
let fromCy = (fromRect.bottom + fromRect.top) / 2
let toRect = getNodeRect(toNode)
let toCx = (toRect.right + toRect.left) / 2
let toCy = (toRect.bottom + toRect.top) / 2
// 中心点坐标的差值
let offsetX = toCx - fromCx
let offsetY = toCy - fromCy
if (offsetX === 0 && offsetY === 0) return
let fromDir = ''
let toDir = ''
if (offsetX <= 0 && offsetX <= offsetY && offsetX <= -offsetY) {
// left
fromDir = 'left'
toDir = 'right'
} else if (offsetX > 0 && offsetX >= -offsetY && offsetX >= offsetY) {
// right
fromDir = 'right'
toDir = 'left'
} else if (offsetY <= 0 && offsetY < offsetX && offsetY < -offsetX) {
// up
fromDir = 'top'
toDir = 'bottom'
} else if (offsetY > 0 && -offsetY < offsetX && offsetY > offsetX) {
// down
fromDir = 'bottom'
toDir = 'top'
}
return [getNodePoint(fromNode, fromDir), getNodePoint(toNode, toDir)]
}
// 获取节点的关联线路径
export const getNodeLinePath = (startPoint, endPoint, node, toNode) => {
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
// 控制点
let controlPoints = []
let associativeLineTargetControlOffsets =
node.nodeData.data.associativeLineTargetControlOffsets
if (
associativeLineTargetControlOffsets &&
associativeLineTargetControlOffsets[targetIndex]
) {
// 节点保存了控制点差值
let offsets = associativeLineTargetControlOffsets[targetIndex]
controlPoints = [
{
x: startPoint.x + offsets[0].x,
y: startPoint.y + offsets[0].y
},
{
x: endPoint.x + offsets[1].x,
y: endPoint.y + offsets[1].y
}
]
} else {
// 没有保存控制点则生成默认的
controlPoints = computeCubicBezierPathPoints(
startPoint.x,
startPoint.y,
endPoint.x,
endPoint.y
)
}
// 根据控制点拼接贝塞尔曲线路径
return {
path: joinCubicBezierPath(
startPoint,
endPoint,
controlPoints[0],
controlPoints[1]
),
controlPoints
}
}
// 获取默认的控制点差值
export const getDefaultControlPointOffsets = (startPoint, endPoint) => {
let controlPoints = computeCubicBezierPathPoints(
startPoint.x,
startPoint.y,
endPoint.x,
endPoint.y
)
return [
{
x: controlPoints[0].x - startPoint.x,
y: controlPoints[0].y - startPoint.y
},
{
x: controlPoints[1].x - endPoint.x,
y: controlPoints[1].y - endPoint.y
}
]
}

View File

@@ -0,0 +1,141 @@
// 标签颜色列表
export const tagColorList = [
{
color: 'rgb(77, 65, 0)',
background: 'rgb(255, 244, 179)'
},
{
color: 'rgb(0, 50, 77)',
background: 'rgb(179, 229, 255)'
},
{
color: 'rgb(77, 0, 73)',
background: 'rgb(255, 179, 251)'
},
{
color: 'rgb(57, 77, 0)',
background: 'rgb(236, 255, 179)'
},
{
color: 'rgb(0, 77, 47)',
background: 'rgb(179, 255, 226)'
}
]
// 布局结构列表
export const layoutList = [
{
name: '逻辑结构图',
value: 'logicalStructure',
},
{
name: '思维导图',
value: 'mindMap',
},
{
name: '组织结构图',
value: 'organizationStructure',
},
{
name: '目录组织图',
value: 'catalogOrganization',
}
]
export const layoutValueList = [
'logicalStructure',
'mindMap',
'catalogOrganization',
'organizationStructure'
]
// 主题列表
export const themeList = [
{
name: '默认',
value: 'default',
},
{
name: '脑图经典',
value: 'classic',
},
{
name: '小黄人',
value: 'minions',
},
{
name: '粉红葡萄',
value: 'pinkGrape',
},
{
name: '薄荷',
value: 'mint',
},
{
name: '金色vip',
value: 'gold',
},
{
name: '活力橙',
value: 'vitalityOrange',
},
{
name: '绿叶',
value: 'greenLeaf',
},
{
name: '暗色2',
value: 'dark2',
},
{
name: '天清绿',
value: 'skyGreen',
},
{
name: '脑图经典2',
value: 'classic2',
},
{
name: '脑图经典3',
value: 'classic3',
},
{
name: '脑图经典4',
value: 'classic4',
},
{
name: '经典绿',
value: 'classicGreen',
},
{
name: '经典蓝',
value: 'classicBlue',
},
{
name: '天空蓝',
value: 'blueSky',
},
{
name: '脑残粉',
value: 'brainImpairedPink',
},
{
name: '暗色',
value: 'dark',
},
{
name: '泥土黄',
value: 'earthYellow',
},
{
name: '清新绿',
value: 'freshGreen',
},
{
name: '清新红',
value: 'freshRed',
},
{
name: '浪漫紫',
value: 'romanticPurple',
}
]

View File

@@ -0,0 +1,304 @@
// 深度优先遍历树
export const walk = (
root,
parent,
beforeCallback,
afterCallback,
isRoot,
layerIndex = 0,
index = 0
) => {
let stop = false
if (beforeCallback) {
stop = beforeCallback(root, parent, isRoot, layerIndex, index)
}
if (!stop && root.children && root.children.length > 0) {
let _layerIndex = layerIndex + 1
root.children.forEach((node, nodeIndex) => {
walk(
node,
root,
beforeCallback,
afterCallback,
false,
_layerIndex,
nodeIndex
)
})
}
afterCallback && afterCallback(root, parent, isRoot, layerIndex, index)
}
// 广度优先遍历树
export const bfsWalk = (root, callback) => {
callback(root)
let stack = [root]
let isStop = false
while (stack.length) {
if (isStop) {
break
}
let cur = stack.shift()
if (cur.children && cur.children.length) {
cur.children.forEach(item => {
stack.push(item)
if (callback(item) === 'stop') {
isStop = true
}
})
}
}
}
// 缩放图片尺寸
export const resizeImgSize = (width, height, maxWidth, maxHeight) => {
let nRatio = width / height
let arr = []
if (maxWidth && maxHeight) {
if (width <= maxWidth && height <= maxHeight) {
arr = [width, height]
} else {
let mRatio = maxWidth / maxHeight
if (nRatio > mRatio) {
// 固定高度
arr = [nRatio * maxHeight, maxHeight]
} else {
// 固定宽度
arr = [maxWidth, maxWidth / nRatio]
}
}
} else if (maxWidth) {
if (width <= maxWidth) {
arr = [width, height]
} else {
arr = [maxWidth, maxWidth / nRatio]
}
} else if (maxHeight) {
if (height <= maxHeight) {
arr = [width, height]
} else {
arr = [nRatio * maxHeight, maxHeight]
}
}
return arr
}
// 缩放图片
export const resizeImg = (imgUrl, maxWidth, maxHeight) => {
return new Promise((resolve, reject) => {
let img = new Image()
img.src = imgUrl
img.onload = () => {
let arr = resizeImgSize(
img.naturalWidth,
img.naturalHeight,
maxWidth,
maxHeight
)
resolve(arr)
}
img.onerror = e => {
reject(e)
}
})
}
// 从头html结构字符串里获取带换行符的字符串
export const getStrWithBrFromHtml = str => {
str = str.replace(/<br>/gim, '\n')
let el = document.createElement('div')
el.innerHTML = str
str = el.textContent
return str
}
// 极简的深拷贝
export const simpleDeepClone = data => {
try {
return JSON.parse(JSON.stringify(data))
} catch (error) {
return null
}
}
// 复制渲染树数据
export const copyRenderTree = (tree, root, removeActiveState = false) => {
tree.data = simpleDeepClone(root.data)
if (removeActiveState) {
tree.data.isActive = false
}
tree.children = []
if (root.children && root.children.length > 0) {
root.children.forEach((item, index) => {
tree.children[index] = copyRenderTree({}, item, removeActiveState)
})
}
return tree
}
// 复制节点树数据
export const copyNodeTree = (tree, root, removeActiveState = false, keepId = false) => {
tree.data = simpleDeepClone(root.nodeData ? root.nodeData.data : root.data)
// 去除节点id因为节点id不能重复
if (tree.data.id && !keepId) delete tree.data.id
if (removeActiveState) {
tree.data.isActive = false
}
tree.children = []
if (root.children && root.children.length > 0) {
root.children.forEach((item, index) => {
tree.children[index] = copyNodeTree({}, item, removeActiveState)
})
} else if (
root.nodeData &&
root.nodeData.children &&
root.nodeData.children.length > 0
) {
root.nodeData.children.forEach((item, index) => {
tree.children[index] = copyNodeTree({}, item, removeActiveState)
})
}
return tree
}
// 图片转成dataURL
export const imgToDataUrl = src => {
return new Promise((resolve, reject) => {
const img = new Image()
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
img.setAttribute('crossOrigin', 'anonymous')
img.onload = () => {
try {
let canvas = document.createElement('canvas')
canvas.width = img.width
canvas.height = img.height
let ctx = canvas.getContext('2d')
// 图片绘制到canvas里
ctx.drawImage(img, 0, 0, img.width, img.height)
resolve(canvas.toDataURL())
} catch (e) {
reject(e)
}
}
img.onerror = e => {
reject(e)
}
img.src = src
})
}
// 下载文件
export const downloadFile = (file, fileName) => {
let a = document.createElement('a')
a.href = file
a.download = fileName
a.click()
}
// 节流函数
export const throttle = (fn, time = 300, ctx) => {
let timer = null
return (...args) => {
if (timer) {
return
}
timer = setTimeout(() => {
fn.call(ctx, ...args)
timer = null
}, time)
}
}
// 异步执行任务队列
export const asyncRun = (taskList, callback = () => {}) => {
let index = 0
let len = taskList.length
if (len <= 0) {
return callback()
}
let loop = () => {
if (index >= len) {
callback()
return
}
taskList[index]()
setTimeout(() => {
index++
loop()
}, 0)
}
loop()
}
// 角度转弧度
export const degToRad = deg => {
return deg * (Math.PI / 180)
}
// 驼峰转连字符
export const camelCaseToHyphen = (str) => {
return str.replace(/([a-z])([A-Z])/g, (...args) => {
return args[1] + '-' + args[2].toLowerCase()
})
}
//计算节点的文本长宽
let measureTextContext = null
export const measureText = (text, { italic, bold, fontSize, fontFamily }) => {
const font = joinFontStr({
italic,
bold,
fontSize,
fontFamily
})
if (!measureTextContext) {
const canvas = document.createElement('canvas')
measureTextContext = canvas.getContext('2d')
}
measureTextContext.save()
measureTextContext.font = font
const {
width,
actualBoundingBoxAscent,
actualBoundingBoxDescent
} = measureTextContext.measureText(text)
measureTextContext.restore()
const height = actualBoundingBoxAscent + actualBoundingBoxDescent
return { width, height }
}
// 拼接font字符串
export const joinFontStr = ({ italic, bold, fontSize, fontFamily }) => {
return `${italic ? 'italic ' : ''} ${bold ? 'bold ' : ''} ${fontSize}px ${fontFamily} `
}
// 在下一个事件循环里执行任务
export const nextTick = function (fn, ctx) {
let pending = false
let timerFunc = null
let handle = () => {
pending = false
ctx ? fn.call(ctx) : fn()
}
// 支持MutationObserver接口的话使用MutationObserver
if (typeof MutationObserver !== 'undefined') {
let counter = 1
let observer = new MutationObserver(handle)
let textNode = document.createTextNode(counter)
observer.observe(textNode, {
characterData: true // 设为 true 表示监视指定目标节点或子节点树中节点所包含的字符数据的变化
})
timerFunc = function () {
counter = (counter + 1) % 2 // counter会在0和1两者循环变化
textNode.data = counter // 节点变化会触发回调handle
}
} else {
// 否则使用定时器
timerFunc = setTimeout
}
return function () {
if (pending) return
pending = true
timerFunc(handle, 0)
}
}

View File

@@ -0,0 +1,69 @@
const map = {
Backspace: 8,
Tab: 9,
Enter: 13,
Shift: 16,
Control: 17,
Alt: 18,
CapsLock: 20,
Esc: 27,
Spacebar: 32,
PageUp: 33,
PageDown: 34,
End: 35,
Home: 36,
Insert: 45,
Left: 37,
Up: 38,
Right: 39,
Down: 40,
Del: 46,
NumLock: 144,
Cmd: 91,
CmdFF: 224,
F1: 112,
F2: 113,
F3: 114,
F4: 115,
F5: 116,
F6: 117,
F7: 118,
F8: 119,
F9: 120,
F10: 121,
F11: 122,
F12: 123,
'`': 192,
'=': 187,
'-': 189,
'/': 191,
'.': 190
}
// 数字
for (let i = 0; i <= 9; i++) {
map[i] = i + 48
}
// 字母
'abcdefghijklmnopqrstuvwxyz'.split('').forEach((n, index) => {
map[n] = index + 65
})
export const keyMap = map
export const isKey = (e, key) => {
let code = typeof e === 'object' ? e.keyCode : e
return map[key] === code
}

View File

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

BIN
src/.DS_Store vendored

Binary file not shown.

BIN
src/assets/.DS_Store vendored

Binary file not shown.

View File

@@ -1,539 +0,0 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@@ -1,464 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i2/O1CN01ZyAlrn1MwaMhqz36G_!!6000000001499-73-tps-64-64.ico" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01EYTRnJ297D6vehehJ_!!6000000008020-55-tps-64-64.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=2479351" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe6f4;</span>
<div class="name">超链接</div>
<div class="code-name">&amp;#xe6f4;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe610;</span>
<div class="name">主题</div>
<div class="code-name">&amp;#xe610;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe60f;</span>
<div class="name">笑脸</div>
<div class="code-name">&amp;#xe60f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe629;</span>
<div class="name">图 片</div>
<div class="code-name">&amp;#xe629;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe61d;</span>
<div class="name">结构</div>
<div class="code-name">&amp;#xe61d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe631;</span>
<div class="name">样式</div>
<div class="code-name">&amp;#xe631;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe71f;</span>
<div class="name">符号-大纲树</div>
<div class="code-name">&amp;#xe71f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe622;</span>
<div class="name">添加子节点</div>
<div class="code-name">&amp;#xe622;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe655;</span>
<div class="name">节点</div>
<div class="code-name">&amp;#xe655;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe696;</span>
<div class="name">删 除</div>
<div class="code-name">&amp;#xe696;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe64c;</span>
<div class="name">HTSCIT_展开</div>
<div class="code-name">&amp;#xe64c;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe673;</span>
<div class="name">HTSCIT_展开2</div>
<div class="code-name">&amp;#xe673;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1622956585729') format('woff2'),
url('iconfont.woff?t=1622956585729') format('woff'),
url('iconfont.ttf?t=1622956585729') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont iconchaolianjie"></span>
<div class="name">
超链接
</div>
<div class="code-name">.iconchaolianjie
</div>
</li>
<li class="dib">
<span class="icon iconfont iconjingzi"></span>
<div class="name">
主题
</div>
<div class="code-name">.iconjingzi
</div>
</li>
<li class="dib">
<span class="icon iconfont iconxiaolian"></span>
<div class="name">
笑脸
</div>
<div class="code-name">.iconxiaolian
</div>
</li>
<li class="dib">
<span class="icon iconfont iconimage"></span>
<div class="name">
图 片
</div>
<div class="code-name">.iconimage
</div>
</li>
<li class="dib">
<span class="icon iconfont iconjiegou"></span>
<div class="name">
结构
</div>
<div class="code-name">.iconjiegou
</div>
</li>
<li class="dib">
<span class="icon iconfont iconyangshi"></span>
<div class="name">
样式
</div>
<div class="code-name">.iconyangshi
</div>
</li>
<li class="dib">
<span class="icon iconfont iconfuhao-dagangshu"></span>
<div class="name">
符号-大纲树
</div>
<div class="code-name">.iconfuhao-dagangshu
</div>
</li>
<li class="dib">
<span class="icon iconfont icontianjiazijiedian"></span>
<div class="name">
添加子节点
</div>
<div class="code-name">.icontianjiazijiedian
</div>
</li>
<li class="dib">
<span class="icon iconfont iconjiedian"></span>
<div class="name">
节点
</div>
<div class="code-name">.iconjiedian
</div>
</li>
<li class="dib">
<span class="icon iconfont iconshanchu"></span>
<div class="name">
删 除
</div>
<div class="code-name">.iconshanchu
</div>
</li>
<li class="dib">
<span class="icon iconfont iconzhankai"></span>
<div class="name">
HTSCIT_展开
</div>
<div class="code-name">.iconzhankai
</div>
</li>
<li class="dib">
<span class="icon iconfont iconzhankai1"></span>
<div class="name">
HTSCIT_展开2
</div>
<div class="code-name">.iconzhankai1
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont iconxxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconchaolianjie"></use>
</svg>
<div class="name">超链接</div>
<div class="code-name">#iconchaolianjie</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconjingzi"></use>
</svg>
<div class="name">主题</div>
<div class="code-name">#iconjingzi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconxiaolian"></use>
</svg>
<div class="name">笑脸</div>
<div class="code-name">#iconxiaolian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconimage"></use>
</svg>
<div class="name">图 片</div>
<div class="code-name">#iconimage</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconjiegou"></use>
</svg>
<div class="name">结构</div>
<div class="code-name">#iconjiegou</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconyangshi"></use>
</svg>
<div class="name">样式</div>
<div class="code-name">#iconyangshi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconfuhao-dagangshu"></use>
</svg>
<div class="name">符号-大纲树</div>
<div class="code-name">#iconfuhao-dagangshu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icontianjiazijiedian"></use>
</svg>
<div class="name">添加子节点</div>
<div class="code-name">#icontianjiazijiedian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconjiedian"></use>
</svg>
<div class="name">节点</div>
<div class="code-name">#iconjiedian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconshanchu"></use>
</svg>
<div class="name">删 除</div>
<div class="code-name">#iconshanchu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconzhankai"></use>
</svg>
<div class="name">HTSCIT_展开</div>
<div class="code-name">#iconzhankai</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconzhankai1"></use>
</svg>
<div class="name">HTSCIT_展开2</div>
<div class="code-name">#iconzhankai1</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>

View File

@@ -1,63 +0,0 @@
@font-face {
font-family: "iconfont"; /* Project id 2479351 */
src: url('iconfont.woff2?t=1622956585729') format('woff2'),
url('iconfont.woff?t=1622956585729') format('woff'),
url('iconfont.ttf?t=1622956585729') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.iconchaolianjie:before {
content: "\e6f4";
}
.iconjingzi:before {
content: "\e610";
}
.iconxiaolian:before {
content: "\e60f";
}
.iconimage:before {
content: "\e629";
}
.iconjiegou:before {
content: "\e61d";
}
.iconyangshi:before {
content: "\e631";
}
.iconfuhao-dagangshu:before {
content: "\e71f";
}
.icontianjiazijiedian:before {
content: "\e622";
}
.iconjiedian:before {
content: "\e655";
}
.iconshanchu:before {
content: "\e696";
}
.iconzhankai:before {
content: "\e64c";
}
.iconzhankai1:before {
content: "\e673";
}

File diff suppressed because one or more lines are too long

View File

@@ -1,93 +0,0 @@
{
"id": "2479351",
"name": "思绪",
"font_family": "iconfont",
"css_prefix_text": "icon",
"description": "思维导图",
"glyphs": [
{
"icon_id": "1790486",
"name": "超链接",
"font_class": "chaolianjie",
"unicode": "e6f4",
"unicode_decimal": 59124
},
{
"icon_id": "4608986",
"name": "主题",
"font_class": "jingzi",
"unicode": "e610",
"unicode_decimal": 58896
},
{
"icon_id": "11903017",
"name": "笑脸",
"font_class": "xiaolian",
"unicode": "e60f",
"unicode_decimal": 58895
},
{
"icon_id": "19657962",
"name": "图 片",
"font_class": "image",
"unicode": "e629",
"unicode_decimal": 58921
},
{
"icon_id": "20784489",
"name": "结构",
"font_class": "jiegou",
"unicode": "e61d",
"unicode_decimal": 58909
},
{
"icon_id": "15969341",
"name": "样式",
"font_class": "yangshi",
"unicode": "e631",
"unicode_decimal": 58929
},
{
"icon_id": "2967176",
"name": "符号-大纲树",
"font_class": "fuhao-dagangshu",
"unicode": "e71f",
"unicode_decimal": 59167
},
{
"icon_id": "12316668",
"name": "添加子节点",
"font_class": "tianjiazijiedian",
"unicode": "e622",
"unicode_decimal": 58914
},
{
"icon_id": "14435368",
"name": "节点",
"font_class": "jiedian",
"unicode": "e655",
"unicode_decimal": 58965
},
{
"icon_id": "15765352",
"name": "删 除",
"font_class": "shanchu",
"unicode": "e696",
"unicode_decimal": 59030
},
{
"icon_id": "9592600",
"name": "HTSCIT_展开",
"font_class": "zhankai",
"unicode": "e64c",
"unicode_decimal": 58956
},
{
"icon_id": "9900009",
"name": "HTSCIT_展开2",
"font_class": "zhankai1",
"unicode": "e673",
"unicode_decimal": 58995
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,131 +0,0 @@
<template>
<div class="imgUploadContainer">
<div class="imgUploadPanel">
<div class="upBtn" v-if="!previewSrc">
<label
for="imgUploadInput"
class="imgUploadInputArea"
@dragenter.stop.prevent
@dragover.stop.prevent
@drop.stop.prevent="onDrop"
v-loading="loading"
element-loading-text="上传中..."
>点击此处添加图片或拖动图片到此</label
>
<input
type="file"
accept="image/*"
id="imgUploadInput"
@change="onImgUploadInputChange"
/>
</div>
<div v-if="previewSrc" class="uploadInfoBox">
<div
class="previewBox"
:style="{ backgroundImage: `url('${previewSrc}')` }"
></div>
<span class="delBtn el-icon-close" @click="deleteImg"></span>
</div>
</div>
</div>
</template>
<script>
import ossUpLoader from "@/utils/oss";
export default {
name: "ImgUpload",
data() {
return {
file: null,
previewSrc: "",
loading: false,
};
},
methods: {
/**
* @Author: 王林
* @Date: 2019-12-22 19:47:19
* @Desc: 图片选择事件
*/
onImgUploadInputChange(e) {
let file = e.target.files[0];
this.uploadImg(file);
},
/**
* @Author: 王林
* @Date: 2019-12-22 20:32:31
* @Desc: 拖动上传图片
*/
onDrop(e) {
let dt = e.dataTransfer;
let file = dt.files && dt.files[0];
this.uploadImg(file);
},
/**
* @Author: 王林
* @Date: 2021-06-06 16:56:14
* @Desc: 显示图片
*/
showImg(file) {
this.file = file;
let fr = new FileReader();
fr.readAsDataURL(file);
fr.onload = (e) => {
this.previewSrc = e.target.result;
};
},
/**
* @Author: 王林
* @Date: 2019-12-22 19:51:13
* @Desc: 上传图片
*/
async uploadImg(file) {
this.loading = true;
ossUpLoader.uploadFile(
file,
() => {},
(e) => {
this.previewSrc = e[0].res.requestUrls[0]
this.loading = false;
},
(e) => {
if (e.length > 0) {
this.loading = false;
this.$message({
message: "上传失败",
type: "warning",
});
}
}
);
},
/**
* @Author: 王林
* @Date: 2021-06-06 22:33:28
* @Desc: 获取图片url
*/
getUrl() {
return this.this.previewSrc;
},
/**
* @Author: 王林
* @Date: 2021-06-06 21:59:57
* @Desc: 删除图片
*/
deleteImg() {
this.previewSrc = "";
this.file = null;
},
},
};
</script>
<style lang="less" scoped>
@import "./style.less";
</style>

View File

@@ -1,80 +0,0 @@
.imgUploadContainer {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(255,255,255,.9);
z-index: 1000;
.imgUploadPanel {
position: relative;
width: 100%;
font-size: 22px;
white-space: nowrap;
color: #909090;
cursor: default;
user-select: none;
.title {
margin-bottom: 15px;
font-size: 22px;
font-weight: 700;
color: hsla(218,9%,51%,.8);
}
.closeBtn {
position: absolute;
right: 25px;
top: 32px;
cursor: pointer;
}
.imgUploadInputArea {
display: block;
width: 100%;
height: 200px;
font-size: 20px;
color: rgba(51,51,51,.4);
background-color: hsla(0,0%,87%,.6);
border: none;
outline: none;
cursor: pointer;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
white-space: normal;
padding: 10px;
}
#imgUploadInput {
display: none;
}
.uploadInfoBox {
position: relative;
width: 100%;
height: 200px;
background-color: hsla(0,0%,87%,.6);
.previewBox {
width: 100%;
height: 100%;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.delBtn {
position: absolute;
right: 0px;
top: 0px;
cursor: pointer;
width: 20px;
height: 20px;
background-color: #fff;
}
}
}
}

View File

@@ -1,128 +0,0 @@
// 字体列表
export const fontFamilyList = [
{
name: '宋体',
value: '宋体, SimSun, Songti SC'
}, {
name: '微软雅黑',
value: '微软雅黑, Microsoft YaHei'
}, {
name: '楷体',
value: '楷体, 楷体_GB2312, SimKai, STKaiti'
}, {
name: '黑体',
value: '黑体, SimHei, Heiti SC'
}, {
name: '隶书',
value: '隶书, SimLi'
}, {
name: 'Andale Mono',
value: 'andale mono'
}, {
name: 'Arial',
value: 'arial, helvetica, sans-serif'
}, {
name: 'arialBlack',
value: 'arial black, avant garde'
}, {
name: 'Comic Sans Ms',
value: 'comic sans ms'
}, {
name: 'Impact',
value: 'impact, chicago'
}, {
name: 'Times New Roman',
value: 'times new roman'
}, {
name: 'Sans-Serif',
value: 'sans-serif'
},
{
name: 'serif',
value: 'serif'
}
]
// 字号
export const fontSizeList = [10, 12, 16, 18, 24, 32, 48]
// 颜色
export const colorList = [
'#4D4D4D',
'#999999',
'#FFFFFF',
'#F44E3B',
'#FE9200',
'#FCDC00',
'#DBDF00',
'#A4DD00',
'#68CCCA',
'#73D8FF',
'#AEA1FF',
'#FDA1FF',
'#333333',
'#808080',
'#cccccc',
'#D33115',
'#E27300',
'#FCC400',
'#B0BC00',
'#68BC00',
'#16A5A5',
'#009CE0',
'#7B64FF',
'#FA28FF',
'#000000',
'#666666',
'#B3B3B3',
'#9F0500',
'#C45100',
'#FB9E00',
'#808900',
'#194D33',
'#0C797D',
'#0062B1',
'#653294',
'#AB149E'
]
// 边框宽度
export const borderWidthList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 边框样式
export const borderDasharrayList = [
{
name: '实线',
value: 'none'
},
{
name: '虚线1',
value: '5,5'
},
{
name: '虚线2',
value: '10,10'
},
{
name: '虚线3',
value: '20,10,5,5,5,10'
},
{
name: '虚线4',
value: '5, 5, 1, 5'
},
{
name: '虚线5',
value: '15, 10, 5, 10, 15'
},
{
name: '虚线6',
value: '1, 5'
}
]
// 圆角
export const borderRadiusList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 线宽
export const lineWidthList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

BIN
src/package/.DS_Store vendored

Binary file not shown.

View File

@@ -1,44 +0,0 @@
/**
* @Author: 王林
* @Date: 2021-04-15 22:23:24
* @Desc: 完整示例数据
*/
export default {
"root": {
"data": {
"text": "鱼骨头图",
},
"children": [{
"data": {
"text": "分支主题",
"expand": true
},
"children": [{
"data": {
"text": "分支主题",
"hyperlink": "https://naotu.baidu.com/",
"hyperlinkTitle": "百度脑图",
"image": "https://kityminder-img.gz.bcebos.com/865551aedebd1e02ac6e76d24c093231df9aafda",
"imageTitle": "图片名称",
"imageSize": {
"width": 200,
"height": 112
},
"note": "我是备注",
"resource": ["标签1", "标签2"],
"priority": 5,
"progress": 7,
// ... 其他类型的图标
},
"children": []
}]
}]
},
"theme": {
"template": "default",
"config": {
// 自定义配置...
}
},
"layout": "logicalStructure"
}

View File

@@ -1,174 +0,0 @@
import View from './src/View'
import Event from './src/Event'
import Render from './src/Render'
import merge from 'deepmerge'
import theme from './src/themes'
import Style from './src/Style'
import KeyCommand from './src/KeyCommand'
import Command from './src/Command';
import { SVG } from '@svgdotjs/svg.js'
const defaultOpt = {
// 布局
layout: 'logicalStructure',
// 放大缩小的增量比例即step = scaleRatio * width|height
scaleRatio: 0.1,
// 主题
theme: 'default',// 内置主题default默认主题
// 主题配置,会和所选择的主题进行合并
themeConfig: {}
}
/**
* 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 = merge(defaultOpt, opt)
// 容器元素
this.el = this.opt.el
let {
width,
height
} = this.el.getBoundingClientRect()
// 画布宽高
this.width = width
this.height = height
// 画笔
this.draw = SVG().addTo(this.el).size(width, height)
// 节点id
this.uid = 0
// 主题
this.initTheme()
// 事件类
this.event = new Event({
mindMap: this
})
// 按键类
this.keyCommand = new KeyCommand({
mindMap: this
})
// 命令类
this.command = new Command({
mindMap: this
})
// 渲染类
this.renderer = new Render({
mindMap: this
})
// 视图操作类
this.view = new View({
mindMap: this,
draw: this.draw
})
this.render()
setTimeout(() => {
this.command.addHistory()
}, 0);
}
/**
* @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(this.opt.theme && theme[this.opt.theme] ? theme[this.opt.theme] : theme.default, this.opt.themeConfig)
Style.setBackgroundStyle(this.el, this.themeConfig)
}
/**
* @Author: 王林
* @Date: 2021-05-05 13:52:08
* @Desc: 设置主题
*/
setTheme(theme) {
this.opt.theme = theme
this.render()
}
/**
* @Author: 王林
* @Date: 2021-05-05 13:50:17
* @Desc: 设置主题配置
*/
setThemeConfig(config) {
this.opt.themeConfig = config
this.render()
}
/**
* @Author: 王林
* @Date: 2021-05-05 14:01:29
* @Desc: 获取某个主题配置值
*/
getThemeConfig(prop) {
return prop === undefined ? this.themeConfig : this.themeConfig[prop]
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 18:47:29
* @Desc: 渲染节点
*/
render() {
this.draw.clear()
this.initTheme()
this.renderer.render()
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:01:00
* @Desc: 执行命令
*/
execCommand(...args) {
this.command.exec(...args)
}
}
export default MindMap

View File

@@ -1,71 +0,0 @@
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
this.commands = {}
this.history = []
this.activeHistoryIndex = 0
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:12:30
* @Desc: 执行命令
*/
exec(name, ...args) {
if (this.commands[name]) {
this.commands[name].forEach((fn) => {
fn(...args)
})
this.addHistory()
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:13:01
* @Desc: 添加命令
*/
add(name, fn) {
if (this.commands[name]) {
this.commands[name].push(fn)
} else[
this.commands[name] = [fn]
]
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:35:43
* @Desc: 添加回退数据
*/
addHistory() {
let data = this.getCopyData()
this.history.push(simpleDeepClone(data))
this.activeHistoryIndex++
this.mindMap.emit('data_change', data)
}
/**
* @Author: 王林
* @Date: 2021-05-04 15:02:58
* @Desc: 获取渲染树数据副本
*/
getCopyData() {
return copyRenderTree({}, this.mindMap.renderer.renderTree)
}
}
export default Command

View File

@@ -1,149 +0,0 @@
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
this.mindMap = opt.mindMap
this.isMousedown = false
this.mousedownPos = {
x: 0,
y: 0
}
this.mousemovePos = {
x: 0,
y: 0
}
this.mousemoveOffset = {
x: 0,
y: 0
}
this.bindFn()
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)
this.onMousemove = this.onMousemove.bind(this)
this.onMouseup = this.onMouseup.bind(this)
this.onMousewheel = this.onMousewheel.bind(this)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:53:43
* @Desc: 绑定事件
*/
bind() {
this.mindMap.draw.on('click', this.onDrawClick)
this.mindMap.el.addEventListener('mousedown', this.onMousedown)
window.addEventListener('mousemove', this.onMousemove)
window.addEventListener('mouseup', this.onMouseup)
this.mindMap.el.addEventListener('mousewheel', this.onMousewheel)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:40:51
* @Desc: 解绑事件
*/
unbind() {
this.mindMap.el.removeEventListener('mousedown', this.onMousedown)
window.removeEventListener('mousemove', this.onMousemove)
window.removeEventListener('mouseup', this.onMouseup)
this.mindMap.el.removeEventListener('mousewheel', this.onMousewheel)
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:19:39
* @Desc: 画布的单击事件
*/
onDrawClick(e) {
this.emit('draw_click', e)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:17:35
* @Desc: 鼠标按下事件
*/
onMousedown(e) {
e.preventDefault()
this.isMousedown = true
this.mousedownPos.x = e.clientX
this.mousedownPos.y = e.clientY
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
this.mousemovePos.y = e.clientY
this.mousemoveOffset.x = e.clientX - this.mousedownPos.x
this.mousemoveOffset.y = e.clientY - this.mousedownPos.y
this.emit('mousemove', e, this)
if (this.isMousedown) {
this.emit('drag', e, this)
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:18:57
* @Desc: 鼠标松开事件
*/
onMouseup(e) {
this.isMousedown = false
this.emit('mouseup', e, this)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:46:27
* @Desc: 鼠标滚动
*/
onMousewheel(e) {
e.stopPropagation()
e.preventDefault()
let dir
if (e.wheelDeltaY > 0) {
dir = 'up'
} else {
dir = 'down'
}
this.emit('mousewheel', e, dir, this)
}
}
export default Event

View File

@@ -1,116 +0,0 @@
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
this.shortcutMap = {
//Enter: [fn]
}
this.bindEvent()
}
/**
* @Author: 王林
* @Date: 2021-04-24 15:23:22
* @Desc: 绑定事件
*/
bindEvent() {
window.addEventListener('keydown', (e) => {
Object.keys(this.shortcutMap).forEach((key) => {
if (this.checkKey(e, key)) {
e.stopPropagation()
e.preventDefault()
this.shortcutMap[key].forEach((fn) => {
fn()
})
}
})
})
}
/**
* @Author: 王林
* @Date: 2021-04-24 19:24:53
* @Desc: 检查键值是否符合
*/
checkKey(e, key) {
let o = this.getOriginEventCodeArr(e)
let k = this.getKeyCodeArr(key)
if (o.length !== k.length) {
return false
}
for (let i = 0; i < o.length; i++) {
let index = k.findIndex((item) => {
return item === o[i];
})
if (index === -1) {
return false
} else {
k.splice(index, 1)
}
}
return true
}
/**
* @Author: 王林
* @Date: 2021-04-24 19:15:19
* @Desc: 获取事件对象里的键值数组
*/
getOriginEventCodeArr(e) {
let arr = []
if (e.ctrlKey || e.metaKey) {
arr.push(keyMap['Control'])
}
if (e.altKey) {
arr.push(keyMap['Alt'])
}
if (e.shiftKey) {
arr.push(keyMap['Shift'])
}
arr.push(e.keyCode)
return arr
}
/**
* @Author: 王林
* @Date: 2021-04-24 19:40:11
* @Desc: 获取快捷键对应的键值数组
*/
getKeyCodeArr(key) {
let keyArr = key.split(/\s*\+\s*/)
let arr = []
keyArr.forEach((item) => {
arr.push(keyMap[item])
})
return arr
}
/**
* @Author: 王林
* @Date: 2021-04-24 15:23:00
* @Desc: 添加快捷键命令
* Enter
* Tab | Insert
* Shift + a
*/
addShortcut(key, fn) {
key.split(/\s*\|\s*/).forEach((item) => {
if (this.shortcutMap[item]) {
this.shortcutMap[item].push(fn)
} else {
this.shortcutMap[item] = [fn]
}
})
}
}

View File

@@ -1,397 +0,0 @@
import Style from './Style';
import {
resizeImgSize
} from './Utils'
import {
Image,
Text,
SVG,
Circle,
Element
} from '@svgdotjs/svg.js'
import btnsSvg from './svg/btns';
/**
* 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.originData = opt.originData
// 原始数据里的数据部分
this.data = opt.data
// id
this.uid = opt.uid
// 控制实例
this.mindMap = opt.mindMap
// 渲染实例
this.renderer = opt.renderer
// 主题配置
this.themeConfig = this.mindMap.themeConfig
// 样式实例
this.style = new Style(this, this.themeConfig)
// 渲染器
this.draw = opt.draw || null
// 是否是根节点
this.isRoot = opt.isRoot === undefined ? false : opt.isRoot
// 是否激活
this.isActive = opt.isActive === undefined ? false : opt.isActive
// 是否展开
this.expand = opt.expand === undefined ? true : opt.expand
// 节点层级
this.layerIndex = opt.layerIndex === undefined ? 0 : opt.layerIndex
// 节点宽
this.width = opt.width || 0
// 节点高
this.height = opt.height || 0
// left
this.left = opt.left || 0
// top
this.top = opt.top || 0
// 父节点
this.parent = opt.parent || null
// 子节点
this.children = opt.children || []
// 全部子节点所占的高度之和
this.childrenAreaHeight = opt.childrenAreaHeight || 0
// 文本节点
this.textNode = null
// 其他数据
Object.keys(opt.data).forEach((key) => {
this[key] = opt.data[key]
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 15:55:04
* @Desc: 添加子节点
*/
addChildren(node) {
this.children.push(node)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 09:46:23
* @Desc: 刷新节点的宽高
*/
refreshSize() {
let {
width,
height
} = this.getNodeRect()
this.width = width
this.height = height
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 14:52:17
* @Desc: 计算节点尺寸信息
*/
getNodeRect() {
let width = this.themeConfig.paddingX * 2
let height = this.themeConfig.paddingY * 2
let maxWidth = 0
if (this.img) {
let img = this.createImgNode()
if (img.width > maxWidth) {
maxWidth = img.width
}
height += img.height
}
if (this.icon && this.text) {
let icon = this.createIconNode()
let text = this.createTextNode()
if (icon.width + text.width > maxWidth) {
maxWidth = icon.width + text.width
}
height += Math.max(text.height, icon.height)
} else if (this.text) {
let text = this.createTextNode()
if (text.width > maxWidth) {
maxWidth = text.width
}
height += text.height
} else if (this.icon) {
let icon = this.createIconNode()
if (icon.width > maxWidth) {
maxWidth = icon.width
}
height += icon.height
}
return {
width: width + maxWidth,
height
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:06:17
* @Desc: 创建图片节点
*/
createImgNode() {
if (!this.img) {
return
}
let imgSize = this.getImgShowSize()
return {
node: new Image().load(this.img).size(...imgSize),
width: imgSize[0],
height: imgSize[1]
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:08:56
* @Desc: 创建文本节点
*/
createTextNode() {
if (!this.text) {
return
}
let node = this.draw.text(this.text)
this.style.text(node)
let {
width,
height
} = node.bbox()
let cloneNode = node.clone()
node.remove()
return {
node: cloneNode,
width,
height
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:10:48
* @Desc: 创建icon节点
*/
createIconNode() {
if (!this.icon) {
return
}
let node = SVG('<svg t="1617947697619" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="999" width="200" height="200"><path d="M512 899.5c-213.668 0-387.5-173.832-387.5-387.5S298.332 124.5 512 124.5 899.5 298.332 899.5 512 725.668 899.5 512 899.5z" fill="#4472C4" p-id="1000"></path><path d="M512 137c-206.776 0-375 168.224-375 375s168.224 375 375 375 375-168.224 375-375-168.224-375-375-375m0-25c220.914 0 400 179.086 400 400S732.914 912 512 912 112 732.914 112 512s179.086-400 400-400z" fill="#4472C4" p-id="1001"></path><path d="M597.681 335.009c0-7.67-2.36-13.569-7.08-17.109a35.115 35.115 0 0 0-20.061-5.9c-3.54 0-6.49 1.77-10.029 4.13-3.54 2.95-6.49 5.31-8.26 7.08a75.758 75.758 0 0 0-11.211 13.569c-3.54 4.72-7.67 9.44-11.209 13.569-11.209 12.979-23.009 27.139-35.988 41.3-13.569 14.749-26.549 27.729-38.938 39.528-1.18 1.18-2.95 2.36-4.13 3.54l-4.72 2.36c-1.77 1.18-3.54 1.77-4.72 2.95l-5.31 3.54c-2.95 2.36-5.31 4.13-7.08 5.9-2.36 2.36-2.95 4.13-2.95 5.9 0 7.08 2.95 12.389 10.03 16.519 5.9 4.72 12.979 6.49 20.059 6.49a31.985 31.985 0 0 0 14.756-3.543c4.13-2.36 8.26-5.9 12.979-10.619 2.95-3.54 6.49-7.67 11.209-12.979l11.8-12.979c2.95-2.95 7.67-7.67 13.569-14.159s12.389-14.159 20.649-23.009c-1.77 9.44-3.54 20.649-4.72 33.628-2.36 12.979-4.13 25.959-5.9 40.118l-4.72 41.888c-1.18 14.159-2.36 27.729-2.95 39.528-1.18 22.419-2.36 44.838-2.95 67.257q-1.77 33.628-1.77 58.407c0 9.44 2.36 16.519 7.67 21.829 5.31 5.9 12.389 8.26 21.829 8.26a43.479 43.479 0 0 0 15.929-3.54c4.72-2.36 7.67-5.31 7.67-8.85 0-1.77-0.59-5.31-0.59-11.209a149.392 149.392 0 0 1-2.36-18.879 116.91 116.91 0 0 1-2.36-21.239 132.008 132.008 0 0 1-1.18-20.649c0-41.3 1.18-82.6 4.72-124.484 3.54-41.3 10.03-82.6 20.649-123.3a106.366 106.366 0 0 1 2.95-11.209l2.36-11.209 1.77-11.209c-0.002-3.547 0.588-7.086 0.588-11.216z" fill="#FFFFFF" p-id="1002"></path></svg>').size(this.themeConfig.iconSize, this.themeConfig.iconSize)
return {
node,
width: this.themeConfig.iconSize,
height: this.themeConfig.iconSize
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 11:10:11
* @Desc: 创建内容节点
*/
createNode() {
let {
left,
top,
width,
height
} = this
let paddingY = this.themeConfig.paddingY
// 创建组
let group = this.draw.group()
// 节点矩形
let _rectNode = group.rect(width, height).x(left).y(top)
this.style.rect(_rectNode)
// 内容节点
let imgNode = this.createImgNode()
let iconNode = this.createIconNode()
let textNode = this.createTextNode()
let imgHeight = imgNode ? imgNode.height : 0
// 图片
if (imgNode) {
group.add(imgNode.node)
imgNode.node.cx(left + width / 2).y(top + paddingY)
}
// icon
if (iconNode) {
group.add(iconNode.node)
iconNode.node.x(left + width / 2).y(top + paddingY + imgHeight + (textNode && textNode.height > iconNode.height ? (textNode.height - iconNode.height) / 2 : 0)).dx(textNode ? -textNode.width / 2 - iconNode.width / 2 : 0)
}
// 文字
if (textNode) {
this.textNode = textNode
group.add(textNode.node)
textNode.node.cx(left + width / 2).y(top + paddingY + imgHeight).dx(iconNode ? iconNode.width / 2 : 0)
}
// 单击事件
group.click((e) => {
e.stopPropagation()
if (this.isActive) {
return;
}
this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList)
this.renderer.clearActive()
this.isActive = true
this.mindMap.execCommand('UPDATE_NODE_DATA', this, {
isActive: this.isActive
})
this.renderer.activeNodeList.push(this)
this.mindMap.render()
this.mindMap.emit('node_active', this, this.renderer.activeNodeList)
})
// 双击事件
group.dblclick(() => {
this.showTextEditBox()
})
return group
}
/**
* @Author: 王林
* @Date: 2021-04-13 22:15:56
* @Desc: 显示文本编辑框
*/
showTextEditBox() {
if (!this.text) {
return;
}
this.renderer.showEditTextBox(this, this.textNode.node.node.getBoundingClientRect())
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 13:55:58
* @Desc: 渲染
*/
render() {
// 连线
this.drawLine()
// 按钮
this.drawBtn()
// 节点
this.draw.add(this.createNode())
// 子节点
if (this.children && this.children.length && this.expand) {
this.children.forEach((child) => {
child.render()
})
}
}
/**
* @Author: 王林
* @Date: 2021-04-10 22:01:53
* @Desc: 连线
*/
drawLine() {
if (!this.expand) {
return;
}
let lines = this.renderer.layout.drawLine(this)
lines.forEach((line) => {
this.style.line(line)
})
}
/**
* @Author: 王林
* @Date: 2021-04-11 19:47:01
* @Desc: 展开收缩按钮
*/
drawBtn() {
if (this.children.length <= 0 || this.isRoot) {
return;
}
let g = this.draw.group()
let iconSvg
if (this.expand) {
iconSvg = btnsSvg.close
} else {
iconSvg = btnsSvg.open
}
let node = SVG(iconSvg).size(20, 20)
let fillNode = new Circle().size(20)
this.renderer.layout.drawIcon(this, [node, fillNode])
node.dx(0).dy(-10)
fillNode.dx(0).dy(-10)
this.style.iconBtn(node, fillNode)
g.mouseover(() => {
g.css({ cursor: 'pointer' })
})
g.mouseout(() => {
g.css({ cursor: 'auto' })
})
g.click(() => {
this.expand = !this.expand
// 需要反映到实际数据上
this.mindMap.execCommand('UPDATE_NODE_DATA', this, {
expand: this.expand
})
this.mindMap.render()
this.mindMap.emit('expand_btn_click', this)
})
g.add(fillNode)
g.add(node)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 10:12:51
* @Desc: 获取图片显示宽高
*/
getImgShowSize() {
return resizeImgSize(this.imgWidth, this.imgHeight, this.themeConfig.imgMaxWidth, this.themeConfig.imgMaxHeight)
}
/**
* @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
}
/**
* @Author: 王林
* @Date: 2021-05-04 22:18:07
* @Desc: 修改某个样式
*/
setStyle(prop, value, isActive) {
if (isActive) {
this.mindMap.execCommand('UPDATE_NODE_DATA', this, {
activeStyle: {
...(this.data.activeStyle || {}),
[prop]: value
}
})
} else {
this.mindMap.execCommand('UPDATE_NODE_DATA', this, {
[prop]: value
})
}
this.mindMap.render()
}
}
export default Node

View File

@@ -1,256 +0,0 @@
import merge from 'deepmerge'
import LogicalStructure from './layouts/LogicalStructure'
import { getStrWithBrFromHtml } from './Utils'
// 布局列表
const layouts = {
logicalStructure: LogicalStructure
}
/**
* 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
this.themeConfig = this.mindMap.themeConfig
this.draw = this.mindMap.draw
// 渲染树,操作过程中修改的都是这里的数据
this.renderTree = merge({}, this.mindMap.opt.data || {})
// 当前激活的节点列表
this.activeNodeList = []
// 根节点
this.root = null
// 文本编辑框
this.textEditNode = null
// 文本编辑框是否显示
this.showTextEdit = false
// 布局
this.layout = new (layouts[this.mindMap.opt.layout] ? layouts[this.mindMap.opt.layout] : layouts.logicalStructure)({
mindMap: this.mindMap,
renderer: this,
renderTree: this.renderTree,
themeConfig: this.mindMap.themeConfig,
draw: this.mindMap.draw
})
// 绑定事件
this.bindEvent()
// 注册命令
this.registerCommands()
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:27:04
* @Desc: 事件
*/
bindEvent() {
this.mindMap.on('draw_click', () => {
// 隐藏文本编辑框
this.hideEditTextBox()
// 清除激活状态
if (this.activeNodeList.length > 0) {
this.clearActive()
this.mindMap.render()
this.mindMap.emit('node_active', null, [])
}
})
this.mindMap.on('expand_btn_click', () => {
this.hideEditTextBox()
})
this.mindMap.on('before_node_active', () => {
this.hideEditTextBox()
})
this.mindMap.keyCommand.addShortcut('Enter', () => {
this.hideEditTextBox()
})
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:19:06
* @Desc: 注册命令
*/
registerCommands() {
this.insertNode = this.insertNode.bind(this)
this.mindMap.command.add('INSERT_NODE', this.insertNode)
this.insertChildNode = this.insertChildNode.bind(this)
this.mindMap.command.add('INSERT_CHILD_NODE', this.insertChildNode)
this.removeNode = this.removeNode.bind(this)
this.mindMap.command.add('REMOVE_NODE', this.removeNode)
this.updateNodeData = this.updateNodeData.bind(this)
this.mindMap.command.add('UPDATE_NODE_DATA', this.updateNodeData)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 16:27:55
* @Desc: 渲染
*/
render() {
this.root = this.layout.doLayout()
this.root.render()
}
/**
* @Author: 王林
* @Date: 2021-04-12 22:45:01
* @Desc: 清楚当前激活的节点
*/
clearActive() {
this.activeNodeList.forEach((item) => {
this.mindMap.execCommand('UPDATE_NODE_DATA', item, {
isActive: false
})
})
this.activeNodeList = []
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:46:08
* @Desc: 获取节点在同级里的索引位置
*/
getNodeIndex(node) {
return node.parent ? node.parent.children.findIndex((item) => {
return item === node
}) : 0
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:19:54
* @Desc: 插入同级节点
*/
insertNode() {
if (this.activeNodeList.length <= 0) {
return;
}
let first = this.activeNodeList[0]
if (first.isRoot) {
this.insertChildNode()
} else {
let index = this.getNodeIndex(first)
first.parent.originData.children.splice(index + 1, 0, {
"data": {
"text": "分支主题",
"expand": true
},
"children": []
})
this.mindMap.render()
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:31:02
* @Desc: 插入子节点
*/
insertChildNode() {
if (this.activeNodeList.length <= 0) {
return;
}
let first = this.activeNodeList[0]
first.originData.children.push({
"data": {
"text": "分支主题",
"expand": true
},
"children": []
})
this.mindMap.render()
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:40:39
* @Desc: 移除节点
*/
removeNode() {
if (this.activeNodeList.length <= 0) {
return;
}
this.activeNodeList.forEach((item) => {
if (item.isRoot) {
item.children = []
item.originData.children = []
} else {
let index = this.getNodeIndex(item)
item.parent.children.splice(index, 1)
item.parent.originData.children.splice(index, 1)
}
})
this.clearActive()
this.mindMap.render()
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:19:48
* @Desc: 更新节点数据
*/
updateNodeData(node, data) {
Object.keys(data).forEach((key) => {
node.data[key] = data[key]
})
}
/**
* @Author: 王林
* @Date: 2021-04-13 22:13:02
* @Desc: 显示文本编辑框
*/
showEditTextBox(node, rect) {
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none;`
this.textEditNode.setAttribute('contenteditable', true)
document.body.appendChild(this.textEditNode)
}
node.style.domText(this.textEditNode)
this.textEditNode.innerHTML = node.data.text.split(/\n/img).join('<br>')
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
this.textEditNode.style.display = 'block'
this.showTextEdit = true
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:48:16
* @Desc: 隐藏文本编辑框
*/
hideEditTextBox() {
if (!this.showTextEdit) {
return
}
this.activeNodeList.forEach((node) => {
let str = getStrWithBrFromHtml(this.textEditNode.innerHTML)
node.data.text = str
this.mindMap.render()
})
this.mindMap.emit('hide_text_edit', this.textEditNode, this.activeNodeList)
this.textEditNode.style.display = 'none'
this.textEditNode.innerHTML = ''
this.textEditNode.style.fontFamily = 'inherit'
this.textEditNode.style.fontSize = 'inherit'
this.textEditNode.style.fontWeight = 'normal'
this.showTextEdit = false
}
}
export default Render

View File

@@ -1,122 +0,0 @@
/**
* @Author: 王林
* @Date: 2021-04-11 10:09:08
* @Desc: 样式类
*/
class Style {
/**
* @Author: 王林
* @Date: 2021-04-11 16:01:53
* @Desc: 设置背景样式
*/
static setBackgroundStyle(el, themeConfig) {
let { backgroundColor, backgroundImage, backgroundRepeat } = themeConfig
el.style.backgroundColor = backgroundColor
if (backgroundImage) {
el.style.backgroundImage = `url(${backgroundImage})`
el.style.backgroundRepeat = backgroundRepeat
}
}
/**
* @Author: 王林
* @Date: 2021-04-11 10:10:11
* @Desc: 构造函数
*/
constructor(ctx, themeConfig) {
this.ctx = ctx
this.themeConfig = themeConfig
}
/**
* @Author: 王林
* @Date: 2021-04-11 12:02:55
* @Desc: 合并样式
*/
merge(prop, root, isActive) {
// 三级及以下节点
let defaultConfig = this.themeConfig.node
if (root) {// 直接使用最外层样式
defaultConfig = this.themeConfig
} else if (this.ctx.layerIndex === 0) {// 根节点
defaultConfig = this.themeConfig.root
} else if (this.ctx.layerIndex === 1) {// 二级节点
defaultConfig = this.themeConfig.secondLevel
}
// 激活状态
if (isActive !== undefined ? isActive : this.ctx.isActive) {
if (this.ctx.activeStyle && this.ctx.activeStyle[prop] !== undefined) {
return this.ctx.activeStyle[prop];
} else if (defaultConfig.active && defaultConfig.active[prop]) {
return defaultConfig.active[prop]
}
}
// 优先使用节点本身的样式
return this.ctx[prop] !== undefined ? this.ctx[prop] : defaultConfig[prop]
}
/**
* @Author: 王林
* @Date: 2021-04-11 10:12:56
* @Desc: 矩形
*/
rect(node) {
node.fill({
color: this.merge('fillColor')
}).stroke({
color: this.merge('borderColor'),
width: this.merge('borderWidth'),
dasharray: this.merge('borderDasharray')
}).radius(this.merge('borderRadius'))
}
/**
* @Author: 王林
* @Date: 2021-04-11 12:07:59
* @Desc: 文字
*/
text(node) {
node.fill({
color: this.merge('color')
}).css({
'font-family': this.merge('fontFamily'),
'font-size': this.merge('fontSize'),
'font-weight': this.merge('fontWeight'),
'font-style': this.merge('fontStyle'),
'text-decoration': this.merge('textDecoration')
})
}
/**
* @Author: 王林
* @Date: 2021-04-13 08:14:34
* @Desc: html文字节点
*/
domText(node) {
node.style.fontFamily = this.merge('fontFamily')
node.style.fontSize = this.merge('fontSize') + 'px'
node.style.fontWeight = this.merge('fontWeight') || 'normal'
}
/**
* @Author: 王林
* @Date: 2021-04-11 14:50:49
* @Desc: 连线
*/
line(node) {
node.stroke({ width: this.merge('lineWidth', true), color: this.merge('lineColor', true) }).fill({ color: 'none' })
}
/**
* @Author: 王林
* @Date: 2021-04-11 20:03:59
* @Desc: 按钮
*/
iconBtn(node, fillNode) {
node.fill({ color: '#808080' })
fillNode.fill({ color: '#fff' })
}
}
export default Style

View File

@@ -1,134 +0,0 @@
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 14:13:17
* @Desc: 深度优先遍历树
*/
export const walk = (root, parent, beforeCallback, afterCallback, isRoot, layerIndex = 0) => {
beforeCallback && beforeCallback(root, parent, isRoot, layerIndex)
if (root.children && root.children.length > 0) {
let _layerIndex = layerIndex + 1
root.children.forEach((node) => {
walk(node, root, beforeCallback, afterCallback, false, _layerIndex)
})
}
afterCallback && afterCallback(root, parent, isRoot, layerIndex)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 18:47:20
* @Desc: 广度优先遍历树
*/
export const bfsWalk = (root, callback) => {
callback(root)
let stack = [root]
while (stack.length) {
let cur = stack.shift()
if (cur.children && cur.children.length) {
cur.children.forEach((item) => {
stack.push(item)
callback(item)
})
}
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 10:44:54
* @Desc: 缩放图片尺寸
*/
export const resizeImgSize = (width, height, maxWidth, maxHeight) => {
let nRatio = width / height
let arr = []
if (maxWidth && maxHeight) {
if (width <= maxWidth && height <= maxHeight) {
arr = [width, height]
} else {
let mRatio = maxWidth / maxHeight
if (nRatio > mRatio) { // 固定高度
arr = [nRatio * maxHeight, maxHeight]
} else { // 固定宽度
arr = [maxWidth, maxWidth / nRatio]
}
}
} else if (maxWidth) {
if (width <= maxWidth) {
arr = [width, height]
} else {
arr = [maxWidth, maxWidth / nRatio]
}
} else if (maxHeight) {
if (height <= maxHeight) {
arr = [width, height]
} else {
arr = [nRatio * maxHeight, maxHeight]
}
}
return arr
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 10:18:42
* @Desc: 缩放图片
*/
export const resizeImg = (imgUrl, maxWidth, maxHeight) => {
return new Promise((resolve, reject) => {
let img = new Image()
img.src = imgUrl
img.onload = () => {
let arr = resizeImgSize(img.naturalWidth, img.naturalHeight, maxWidth, maxHeight)
resolve(arr)
}
img.onerror = (e) => {
reject(e)
}
})
}
/**
* @Author: 王林
* @Date: 2021-05-04 12:26:56
* @Desc: 从头html结构字符串里获取带换行符的字符串
*/
export const getStrWithBrFromHtml = (str) => {
str = str.replace(/<br>/img, '\n')
let el = document.createElement('div')
el.innerHTML = str
str = el.textContent
return str;
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:45:39
* @Desc: 极简的深拷贝
*/
export const simpleDeepClone = (data) => {
try {
return JSON.parse(JSON.stringify(data))
} catch (error) {
return null
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:40:11
* @Desc: 复制渲染树数据
*/
export const copyRenderTree = (tree, root) => {
tree.data = simpleDeepClone(root.data)
tree.children = []
if (root.children.length > 0) {
root.children.forEach((item, index) => {
tree.children[index] = copyRenderTree({}, item)
})
}
return tree;
}

View File

@@ -1,87 +0,0 @@
import merge from 'deepmerge'
/**
* 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
this.viewBox = {
x: 0,
y: 0,
width: this.mindMap.width,
height: this.mindMap.height
}
this.cacheViewBox = {
x: 0,
y: 0,
width: this.mindMap.width,
height: this.mindMap.height
}
this.scale = 1
this.bind()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:38:51
* @Desc: 绑定
*/
bind() {
// 拖动视图
this.mindMap.event.on('mousedown', () => {
this.cacheViewBox = merge({}, this.viewBox)
})
this.mindMap.event.on('drag', (e, event) => {
// 视图放大缩小后拖动的距离也要相应变化
this.viewBox.x = this.cacheViewBox.x - event.mousemoveOffset.x * this.scale
this.viewBox.y = this.cacheViewBox.y - event.mousemoveOffset.y * this.scale
this.setViewBox()
})
// 放大缩小视图
this.mindMap.event.on('mousewheel', (e, dir) => {
let stepWidth = this.viewBox.width * this.mindMap.opt.scaleRatio
let stepHeight = this.viewBox.height * this.mindMap.opt.scaleRatio
// 放大
if (dir === 'down') {
this.scale += this.mindMap.opt.scaleRatio
this.viewBox.width += stepWidth
this.viewBox.height += stepHeight
} else { // 缩小
this.scale -= this.mindMap.opt.scaleRatio
this.viewBox.width -= stepWidth
this.viewBox.height -= stepHeight
}
this.setViewBox()
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:43:26
* @Desc: 设置视图
*/
setViewBox() {
let {
x,
y,
width,
height
} = this.viewBox
this.opt.draw.viewbox(x, y, width, height)
}
}
export default View

View File

@@ -1,70 +0,0 @@
/**
* @Author: 王林
* @Date: 2021-04-12 22:24:30
* @Desc: 布局基类
*/
class Base {
/**
* @Author: 王林
* @Date: 2021-04-12 22:25:16
* @Desc: 构造函数
*/
constructor(opt) {
// 控制实例
this.mindMap = opt.mindMap
// 渲染实例
this.renderer = opt.renderer
// 渲染树
this.renderTree = opt.renderTree
// 主题配置
this.themeConfig = opt.themeConfig
// 绘图对象
this.draw = opt.draw
// 根节点
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: 连线
*/
drawLine() {
throw new Error('【drawLine】方法为必要方法需要子类进行重写')
}
/**
* @Author: 王林
* @Date: 2021-04-12 22:42:08
* @Desc: 定位显示展开收缩按钮
*/
drawIcon() {
throw new Error('【drawIcon】方法为必要方法需要子类进行重写')
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 11:25:52
* @Desc: 更新子节点属性
*/
updateChildren(children, prop, offset) {
children.forEach((item) => {
item[prop] += offset
if (item.children && item.children.length) {
this.updateChildren(item.children, prop, offset)
}
})
}
}
export default Base

View File

@@ -1,240 +0,0 @@
import Base from './Base';
import {
walk
} from '../Utils'
import Node from '../Node'
/**
* @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() {
// 计算节点的left、width、height
this.computedBaseValue()
// 计算节点的top
this.computedTopValue()
// 调整节点top
this.adjustTopValue()
return this.root;
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:49:32
* @Desc: 计算节点的left、width、height
*/
computedBaseValue() {
walk(this.renderTree, null, (node, parent, isRoot, layerIndex) => {
// 遍历子节点前设置left、width、height
if (!node.data) {
node.data = {}
}
// 创建节点
let newNode = new Node({
uid: this.mindMap.uid++,
originData: node,
data: node.data,
renderer: this.renderer,
mindMap: this.mindMap,
draw: this.draw,
layerIndex
})
// 计算节点的宽高
newNode.refreshSize()
if (isRoot) {
newNode.isRoot = true
newNode.left = (this.mindMap.width - newNode.width) / 2
newNode.top = (this.mindMap.height - newNode.height) / 2
this.root = newNode
} else {
let marginX = layerIndex === 1 ? this.themeConfig.secondLevel.marginX : this.themeConfig.node.marginX
newNode.left = parent._node.left + parent._node.width + marginX,
newNode.parent = parent._node
parent._node.addChildren(newNode)
}
node._node = newNode
}, (node, parent, isRoot, layerIndex) => {
// 返回时计算节点的areaHeight也就是子节点所占的高度之和包括外边距
let len = node.expand === false ? 0 : node._node.children.length
node._node.childrenAreaHeight = len ? node._node.children.reduce((h, cur) => {
return h + cur.height
}, 0) + (len + 1) * this.getMarginY(layerIndex) : 0
}, true, 0)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:59:25
* @Desc: 计算节点的top
*/
computedTopValue() {
walk(this.root, null, (node, parent, isRoot, layerIndex) => {
if (node.children && node.children.length) {
let marginY = this.getMarginY(layerIndex)
// 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半
let top = node.top + node.height / 2 - node.childrenAreaHeight / 2
let totalTop = top + marginY
node.children.forEach((cur) => {
cur.top = totalTop
totalTop += cur.height + marginY
})
}
}, null, true)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 10:04:05
* @Desc: 调整节点top
*/
adjustTopValue() {
walk(this.root, null, (node, parent, isRoot, layerIndex) => {
// 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置
let difference = node.childrenAreaHeight - this.getMarginY(layerIndex) - node.height
if (difference > 0) {
this.updateBrothers(node, difference / 2)
}
}, null, true)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:26:03
* @Desc: 更新兄弟节点的top
*/
updateBrothers(node, addHeight) {
if (node.parent) {
let childrenList = node.parent.children
let index = childrenList.findIndex((item) => {
return item === node
})
childrenList.forEach((item, _index) => {
let _offset = 0
// 上面的节点往上移
if (_index < index) {
_offset = -addHeight
} else if (_index > index) {// 下面的节点往下移
_offset = addHeight
}
item.top += _offset
// 同步更新子节点的位置
if (item.children && item.children.length) {
this.updateChildren(item.children, 'top', _offset)
}
})
// 更新父节点的位置
this.updateBrothers(node.parent, addHeight)
}
}
/**
* @Author: 王林
* @Date: 2021-04-11 15:34:20
* @Desc: 获取节点的marginY
*/
getMarginY(layerIndex) {
return layerIndex === 1 ? this.themeConfig.secondLevel.marginY : this.themeConfig.node.marginY;
}
/**
* @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
let cx2 = x2 - (x2 - x1) / 2
let cy2 = y2
return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`
}
/**
* @Author: 王林
* @Date: 2021-04-11 14:42:48
* @Desc: 绘制连线,连接该节点到其子节点
*/
drawLine(node) {
if (node.children.length <= 0) {
return [];
}
let {
left,
top,
width,
height
} = node
let lines = []
if (!node.isRoot) {
let line = this.draw.line(left + width, top + height / 2, left + width + 20, top + height / 2)
lines.push(line)
}
node.children.forEach((item) => {
let x1 = node.layerIndex === 0 ? left + width / 2 : left + width + 20
let y1 = node.layerIndex === 0 ? top + height / 2 : top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
let path = ''
if (node.isRoot) {
path = this.quadraticCurvePath(x1, y1, x2, y2)
} else {
path = this.cubicBezierPath(x1, y1, x2, y2)
}
let line = this.draw.path(path)
lines.push(line)
})
return lines;
}
/**
* @Author: 王林
* @Date: 2021-04-11 19:54:26
* @Desc: 渲染按钮
*/
drawIcon(node, icons) {
let {
left,
top,
width,
height
} = node
icons.forEach((icon) => {
icon.x(left + width).y(top + height / 2)
})
}
}
export default LogicalStructure

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