Compare commits

...

57 Commits

Author SHA1 Message Date
街角小林
eff4cd0e77 打包0.11.2 2024-09-25 10:52:21 +08:00
街角小林
82c2d848a9 Feat:增加是否阻止mousedown事件默认事件的实例化选项 2024-09-25 10:31:39 +08:00
街角小林
0344599411 Fix:优化公式插件,适配创建多个实例的情况 2024-09-24 18:21:48 +08:00
街角小林
19fa0af6c0 Fix:修复创建多个思维导图实例时调用addPlugin添加同一个插件只有第一个实例会生效的问题 2024-09-24 17:55:52 +08:00
街角小林
bca1a073f7 Fix:修复创建多个思维导图实例时公式插件会多次扩展Quill的问题 2024-09-24 17:25:06 +08:00
街角小林
98fb23bf7c Feat:主题支持配置各个层级节点的内边距 2024-09-23 17:48:11 +08:00
街角小林
1c9c399b76 Feat:主题新增节点高亮框的圆角配置 2024-09-23 17:19:47 +08:00
wanglin2
bc43fedd87 Feat:新增自定义判断wheel事件是否来自触控板的实例化选项;优化代码 2024-09-21 19:54:43 +08:00
街角小林
156054ed93 Merge pull request #886 from Tarrency/feature-to-the-feature
feat: 配置平移步长和扩缩最值,解决触控板灵敏度问题
2024-09-21 15:24:58 +08:00
wangqi01
937f7d2969 fix: 限值平移步长比例生效只在鼠标/触控板滚动行为内 2024-09-20 18:03:18 +08:00
街角小林
e56a6d36cb Demo:优化节点图片添加了无法访问的图片的展示样式 2024-09-20 17:33:16 +08:00
街角小林
9f19061010 Fix:修复节点富文本编辑能粘贴图片的问题 2024-09-20 16:58:06 +08:00
街角小林
0d465f28f3 Feat:注释掉非https情况下的粘贴逻辑 2024-09-20 16:37:07 +08:00
街角小林
b4fdcd81b0 Fix:修复自定义主题节点渐变色方向无效的问题 2024-09-20 09:32:12 +08:00
Tarrency
38c0fe2e39 feat: 配置平移步长和扩缩最值,解决触控板灵敏度问题 2024-09-19 20:35:46 +08:00
街角小林
29ddbba9b9 Demo:新增一键展开某个节点所有下级节点的右键菜单 2024-09-19 19:52:34 +08:00
街角小林
9f9ed1e84f Fix:修复思维导图非常大的情况下导出图片失败的问题 2024-09-19 19:36:10 +08:00
街角小林
9ebc416167 Demo:修复右键菜单中的二级菜单会超出边界的问题 2024-09-19 18:19:57 +08:00
街角小林
5d49d985c0 优化代码 2024-09-19 18:14:52 +08:00
街角小林
c36338a794 Merge pull request #876 from BlackEyeBear/main
处理非https下navigator.clipboard方法无法获取,导致无法复制黏贴外部文本
2024-09-19 18:05:36 +08:00
wanglin2
c21ee4960e Fix:修复存在概要时切换主题会报错的问题 2024-09-18 21:52:11 +08:00
BlackEyeBear
5090f21b0e 处理非https下navigator.clipboard方法无法获取,导致无法复制黏贴外部文本 2024-09-16 22:08:55 +08:00
panda
3d9d172fa0 '处理非https下navigator.clipboard方法无法获取,导致无法复制黏贴外部文本' 2024-09-16 21:32:52 +08:00
街角小林
766ce310d0 打包0.11.1 2024-09-11 20:21:10 +08:00
街角小林
075bf54d28 Merge branch 'feature' into main 2024-09-11 20:02:17 +08:00
街角小林
14ebd7a239 Merge pull request #839 from PeterDaveHelloKitchen/zh_tw
Add Traditional Chinese(zh_TW) translation
2024-09-11 20:01:33 +08:00
街角小林
ac13aa8bc9 Doc: update 2024-09-11 19:59:59 +08:00
街角小林
f4d84aeb55 Demo:支持导入导出Excel 2024-09-11 17:41:57 +08:00
街角小林
9b7305de1e Demo:新增删除节点图片前的二次提示 2024-09-09 18:04:56 +08:00
街角小林
ef526fe302 Feat:新增拦截删除节点图片的实例化选项 2024-09-09 18:04:37 +08:00
街角小林
007a5f2815 Feat:组织结构图支持曲线连线 2024-09-09 17:47:17 +08:00
街角小林
e04b680cdc Feat:非富文本模式下文本编辑支持粘贴带换行的文本 2024-09-06 17:21:55 +08:00
街角小林
156c866bc1 Feat:1.去除highlightNodeBoxStyle选项;2.highlightNode方法新增参数;3.概要区间高亮框的颜色由主题的hoverRectColor选项和hoverRectColor实例化选项确定 2024-09-06 16:56:22 +08:00
街角小林
62b734890b Fix:修复切换主题时概要节点的样式没有更新的问题 2024-09-06 09:36:02 +08:00
街角小林
c7f3dd4d7e Feat:主题支持设置节点hover和激活时矩形框的颜色 2024-09-06 09:35:10 +08:00
街角小林
5014a2feb7 Feat:展开所有和收起所有的命令支持指定节点的uid 2024-09-05 19:14:18 +08:00
街角小林
8f8c6c9d95 Feat:expandBtnNumHandler选项新增节点实例的回调参数 2024-09-05 18:21:38 +08:00
街角小林
c8d5a34640 Demo:支持导入和导出FreeMind文件 2024-09-05 09:40:41 +08:00
街角小林
bd0fc37f03 Demo:删除无用文件 2024-09-04 09:07:21 +08:00
Peter Dave Hello
c05d947fa3 Add Traditional Chinese(zh_TW) translation 2024-09-03 21:44:43 +08:00
街角小林
1f303145c6 Fix:修复富文本模式下即使未修改文本也会添加历史记录的问题 2024-09-03 17:20:49 +08:00
街角小林
453e7311b8 Feat:公式插件:去除将公式富文本转换为公式源码时的特殊字符转义逻辑,避免双重转换导致报错 2024-08-30 17:38:39 +08:00
街角小林
c12b7f6dae Feat:更新节点非样式字段列表 2024-08-30 17:02:48 +08:00
街角小林
7ba11be42b Fix:修复公式中存在<>符号时导出svg报错的问题 2024-08-30 14:01:59 +08:00
wanglin2
ce49fcb511 Feat:修改更新节点当前应用样式的逻辑 2024-08-29 22:01:05 +08:00
街角小林
06fb6245b7 Demo:支持设置节点背景渐变方向 2024-08-29 18:29:04 +08:00
街角小林
fa8a80792d Feat:主题支持配置背景渐变的方向 2024-08-29 17:48:03 +08:00
街角小林
570bbb1b16 Feat:新增开启节点文本编辑实时更新节点大小和位置的实例化选项 2024-08-29 15:33:38 +08:00
街角小林
4e327c3a48 Feat:格式刷支持刷节点所有生效的样式,包括来自主题的和自定义的 2024-08-29 10:30:52 +08:00
街角小林
89d89f4dd8 update 2024-08-28 17:41:52 +08:00
街角小林
5ae998f304 Merge branch 'feature' of https://github.com/wanglin2/mind-map into feature 2024-08-28 16:49:51 +08:00
街角小林
428c4fd93b Fix:修复默认主题配置中的normal单词拼写错误的问题 2024-08-28 16:41:59 +08:00
街角小林
2bcc4a7c18 Demo:支持点击画布取消缩放输入框的聚焦状态 2024-08-28 16:39:35 +08:00
街角小林
9229f13172 Fix:调整hide_text_edit事件触发时机,防止一些情况下的死循环问题 2024-08-28 16:20:49 +08:00
wanglin2
afdb557a49 打包demo 2024-08-26 21:16:02 +08:00
wanglin2
62e25cdf86 Merge branch 'feature' of https://github.com/wanglin2/mind-map into feature 2024-08-26 21:10:09 +08:00
wanglin2
d2562f35bd Feat:优化mac触控板双指拖动画布的体验 2024-08-26 07:33:04 +08:00
93 changed files with 2324 additions and 495 deletions

View File

@@ -44,7 +44,7 @@ Github[releases](https://github.com/wanglin2/mind-map/releases)。百度云
官方提供了如下插件,可根据需求按需引入(某个功能不生效大概率是因为你没有引入对应的插件),具体使用方式请查看文档:
> RichText节点富文本插件、Select鼠标多选节点插件、Drag节点拖拽插件、AssociativeLine关联线插件、Export导出插件、KeyboardNavigation键盘导航插件、MiniMap小地图插件、Watermark水印插件、TouchEvent移动端触摸事件支持插件、NodeImgAdjust拖拽调整节点图片大小插件、Search搜索插件、Painter节点格式刷插件、Scrollbar滚动条插件、Formula数学公式插件、Cooperate协同编辑插件、RainbowLines彩虹线条插件、Demonstrate演示模式插件、OuterFrame外框插件、HandDrawnLikeStyle手绘风格插件[收费]、Notation节点标记插件[收费]、Numbers节点编号插件[收费]
> RichText节点富文本插件、Select鼠标多选节点插件、Drag节点拖拽插件、AssociativeLine关联线插件、Export导出插件、KeyboardNavigation键盘导航插件、MiniMap小地图插件、Watermark水印插件、TouchEvent移动端触摸事件支持插件、NodeImgAdjust拖拽调整节点图片大小插件、Search搜索插件、Painter节点格式刷插件、Scrollbar滚动条插件、Formula数学公式插件、Cooperate协同编辑插件、RainbowLines彩虹线条插件、Demonstrate演示模式插件、OuterFrame外框插件、HandDrawnLikeStyle手绘风格插件[收费]、Notation节点标记插件[收费]、Numbers节点编号插件[收费]、FreemindFreemind格式导入导出插件[收费]、ExcelExcel格式导入导出插件[收费]
本项目不会实现的特性:
@@ -457,4 +457,24 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/炫.jpg" style="width: 50px;height: 50px;" />
<span>炫</span>
</span>
<span>
<img src="./web/src/assets/avatar/Lawliet.jpg" style="width: 50px;height: 50px;" />
<span>Lawliet</span>
</span>
<span>
<img src="./web/src/assets/avatar/一叶孤舟.jpg" style="width: 50px;height: 50px;" />
<span>一叶孤舟</span>
</span>
<span>
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>晏江</span>
</span>
<span>
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>Eric</span>
</span>
<span>
<img src="./web/src/assets/avatar/Joe.jpg" style="width: 50px;height: 50px;" />
<span>Joe</span>
</span>
</p>

View File

@@ -13,4 +13,4 @@ if (fs.existsSync(src)) {
fs.unlinkSync(src)
}
console.warn('请检查手绘风格、标记插件、编号插件是否启用!!!')
console.warn('请检查付费插件是否启用!!!')

2
dist/css/app.css vendored
View File

@@ -1 +1 @@
*{margin:0;padding:0;box-sizing:border-box}#app{font-family:Avenir,Helvetica,Arial,sans-serif;color:#2c3e50}@font-face{font-family:iconfont;src:url(../fonts/iconfont.woff2) format("woff2"),url(../fonts/iconfont.woff) format("woff"),url(../fonts/iconfont.ttf) format("truetype")}#app,.iconfont{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal}.iconwaikuang:before{content:"\e640"}.iconhighlight:before{content:"\e6b8"}.iconyanshibofang:before{content:"\e648"}.iconfujian:before{content:"\e88a"}.icongeshihua:before{content:"\e7a3"}.iconyuanma:before{content:"\e658"}.icongundongtiao:before{content:"\e670"}.iconxietongwendang:before{content:"\e60d"}.iconTXT:before{content:"\e6e1"}.iconwenjian1:before{content:"\e69f"}.icondodeparent:before{content:"\e70f"}.icongongshi:before{content:"\e617"}.icontouming:before{content:"\e60c"}.iconlieri:before{content:"\e60b"}.iconmoon_line:before{content:"\e745"}.iconsousuo:before{content:"\e693"}.iconjiantouyou:before{content:"\e62d"}.iconbianji1:before{content:"\e60a"}.icondaohang1:before{content:"\e632"}.iconyanjing:before{content:"\e8bf"}.iconwangzhan:before{content:"\e628"}.iconcsdn:before{content:"\e608"}.iconshejiaotubiao-10:before{content:"\e644"}.iconstar:before{content:"\e7df"}.iconfork:before{content:"\e641"}.iconxiazai:before{content:"\e613"}.iconteamwork:before{content:"\e870"}.iconshuiyin:before{content:"\e67a"}.iconxmind:before{content:"\ea57"}.iconmouseR:before{content:"\e6bd"}.iconmouseL:before{content:"\e6c0"}.iconwenjian:before{content:"\e607"}.iconpdf:before{content:"\e740"}.iconPNG:before{content:"\ec18"}.iconSVG:before{content:"\e621"}.iconmarkdown:before{content:"\ec04"}.iconjson:before{content:"\ea42"}.iconlianjiexian:before{content:"\e75b"}.iconbangzhu:before{content:"\e620"}.iconshezhi:before{content:"\e8b7"}.iconwushuju:before{content:"\e643"}.iconzuijinliulan:before{content:"\e62f"}.icon3zuidahua-3:before{content:"\e692"}.iconzuixiaohua:before{content:"\e650"}.iconzuidahua:before{content:"\e651"}.iconguanbi:before{content:"\e652"}.icondiannao:before{content:"\eac0"}.iconzhuye:before{content:"\e65c"}.iconbendi1x:before{content:"\e606"}.iconbeijingyanse:before{content:"\e6f8"}.iconqingchu:before{content:"\e605"}.iconcase:before{content:"\e6c6"}.iconxingzhuang-wenzi:before{content:"\eb99"}.iconzitijiacu:before{content:"\ec83"}.iconzitixiahuaxian:before{content:"\ec85"}.iconzitixieti:before{content:"\ec86"}.iconshanchuxian:before{content:"\e612"}.iconzitiyanse:before{content:"\e854"}.icongithub:before{content:"\e64f"}.iconchoose1:before{content:"\e6c5"}.iconzhuti:before{content:"\e7aa"}.icondaochu1:before{content:"\e63e"}.iconlingcunwei:before{content:"\e657"}.iconexport:before{content:"\e642"}.icondakai:before{content:"\ebdf"}.iconxinjian:before{content:"\e64e"}.iconjianqie:before{content:"\e601"}.iconzhengli:before{content:"\e83b"}.iconfuzhi:before{content:"\e604"}.iconniantie:before{content:"\e63f"}.iconshangyi:before{content:"\e6be"}.iconxiayi:before{content:"\e6bf"}.icongaikuozonglan:before{content:"\e609"}.iconquanxuan:before{content:"\f199"}.icondaoru:before{content:"\e6a3"}.iconhoutui-shi:before{content:"\e656"}.iconqianjin1:before{content:"\e654"}.iconwithdraw:before{content:"\e603"}.iconqianjin:before{content:"\e600"}.iconhuifumoren:before{content:"\e60e"}.iconhuanhang:before{content:"\e61e"}.iconsuoxiao:before{content:"\ec13"}.iconbianji:before{content:"\e626"}.iconfangda:before{content:"\e663"}.iconquanping1:before{content:"\e664"}.icondingwei:before{content:"\e616"}.icondaohang:before{content:"\e611"}.iconjianpan:before{content:"\e64d"}.iconquanping:before{content:"\e602"}.icondaochu:before{content:"\e63d"}.iconbiaoqian:before{content:"\e63c"}.iconflow-Mark:before{content:"\e65b"}.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"}
*{margin:0;padding:0;box-sizing:border-box}#app{font-family:Avenir,Helvetica,Arial,sans-serif;color:#2c3e50}@font-face{font-family:iconfont;src:url(../fonts/iconfont.woff2) format("woff2"),url(../fonts/iconfont.woff) format("woff"),url(../fonts/iconfont.ttf) format("truetype")}#app,.iconfont{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal}.iconfile-excel:before{content:"\e7b7"}.iconfreemind:before{content:"\e97d"}.iconwaikuang:before{content:"\e640"}.iconhighlight:before{content:"\e6b8"}.iconyanshibofang:before{content:"\e648"}.iconfujian:before{content:"\e88a"}.icongeshihua:before{content:"\e7a3"}.iconyuanma:before{content:"\e658"}.icongundongtiao:before{content:"\e670"}.iconxietongwendang:before{content:"\e60d"}.iconTXT:before{content:"\e6e1"}.iconwenjian1:before{content:"\e69f"}.icondodeparent:before{content:"\e70f"}.icongongshi:before{content:"\e617"}.icontouming:before{content:"\e60c"}.iconlieri:before{content:"\e60b"}.iconmoon_line:before{content:"\e745"}.iconsousuo:before{content:"\e693"}.iconjiantouyou:before{content:"\e62d"}.iconbianji1:before{content:"\e60a"}.icondaohang1:before{content:"\e632"}.iconyanjing:before{content:"\e8bf"}.iconwangzhan:before{content:"\e628"}.iconcsdn:before{content:"\e608"}.iconshejiaotubiao-10:before{content:"\e644"}.iconstar:before{content:"\e7df"}.iconfork:before{content:"\e641"}.iconxiazai:before{content:"\e613"}.iconteamwork:before{content:"\e870"}.iconshuiyin:before{content:"\e67a"}.iconxmind:before{content:"\ea57"}.iconmouseR:before{content:"\e6bd"}.iconmouseL:before{content:"\e6c0"}.iconwenjian:before{content:"\e607"}.iconpdf:before{content:"\e740"}.iconPNG:before{content:"\ec18"}.iconSVG:before{content:"\e621"}.iconmarkdown:before{content:"\ec04"}.iconjson:before{content:"\ea42"}.iconlianjiexian:before{content:"\e75b"}.iconbangzhu:before{content:"\e620"}.iconshezhi:before{content:"\e8b7"}.iconwushuju:before{content:"\e643"}.iconzuijinliulan:before{content:"\e62f"}.icon3zuidahua-3:before{content:"\e692"}.iconzuixiaohua:before{content:"\e650"}.iconzuidahua:before{content:"\e651"}.iconguanbi:before{content:"\e652"}.icondiannao:before{content:"\eac0"}.iconzhuye:before{content:"\e65c"}.iconbendi1x:before{content:"\e606"}.iconbeijingyanse:before{content:"\e6f8"}.iconqingchu:before{content:"\e605"}.iconcase:before{content:"\e6c6"}.iconxingzhuang-wenzi:before{content:"\eb99"}.iconzitijiacu:before{content:"\ec83"}.iconzitixiahuaxian:before{content:"\ec85"}.iconzitixieti:before{content:"\ec86"}.iconshanchuxian:before{content:"\e612"}.iconzitiyanse:before{content:"\e854"}.icongithub:before{content:"\e64f"}.iconchoose1:before{content:"\e6c5"}.iconzhuti:before{content:"\e7aa"}.icondaochu1:before{content:"\e63e"}.iconlingcunwei:before{content:"\e657"}.iconexport:before{content:"\e642"}.icondakai:before{content:"\ebdf"}.iconxinjian:before{content:"\e64e"}.iconjianqie:before{content:"\e601"}.iconzhengli:before{content:"\e83b"}.iconfuzhi:before{content:"\e604"}.iconniantie:before{content:"\e63f"}.iconshangyi:before{content:"\e6be"}.iconxiayi:before{content:"\e6bf"}.icongaikuozonglan:before{content:"\e609"}.iconquanxuan:before{content:"\f199"}.icondaoru:before{content:"\e6a3"}.iconhoutui-shi:before{content:"\e656"}.iconqianjin1:before{content:"\e654"}.iconwithdraw:before{content:"\e603"}.iconqianjin:before{content:"\e600"}.iconhuifumoren:before{content:"\e60e"}.iconhuanhang:before{content:"\e61e"}.iconsuoxiao:before{content:"\ec13"}.iconbianji:before{content:"\e626"}.iconfangda:before{content:"\e663"}.iconquanping1:before{content:"\e664"}.icondingwei:before{content:"\e616"}.icondaohang:before{content:"\e611"}.iconjianpan:before{content:"\e64d"}.iconquanping:before{content:"\e602"}.icondaochu:before{content:"\e63d"}.iconbiaoqian:before{content:"\e63c"}.iconflow-Mark:before{content:"\e65b"}.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

Binary file not shown.

Binary file not shown.

Binary file not shown.

2
dist/js/app.js vendored

File diff suppressed because one or more lines are too long

69
dist/js/chunk-4cff5316.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -9,7 +9,7 @@
})
} catch (error) {
console.log(error)
}</script><link href="dist/css/chunk-vendors.css?1c8f9269e64b9476f0c7" rel="stylesheet"><link href="dist/css/app.css?1c8f9269e64b9476f0c7" 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>const getDataFromBackend = () => {
}</script><link href="dist/css/chunk-vendors.css?9c8ee1f3de5ddc8a450e" rel="stylesheet"><link href="dist/css/app.css?9c8ee1f3de5ddc8a450e" 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>const getDataFromBackend = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
@@ -74,4 +74,4 @@
// 可以通过window.$bus.$on()来监听应用的一些事件
// 实例化页面
window.initApp()
}</script><script src="dist/js/chunk-vendors.js?1c8f9269e64b9476f0c7"></script><script src="dist/js/app.js?1c8f9269e64b9476f0c7"></script></body></html>
}</script><script src="dist/js/chunk-vendors.js?9c8ee1f3de5ddc8a450e"></script><script src="dist/js/app.js?9c8ee1f3de5ddc8a450e"></script></body></html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -31,7 +31,7 @@ MindMap.iconList = icons.nodeIconList
MindMap.constants = constants
MindMap.themes = themes
MindMap.defaultTheme = defaultTheme
MindMap.version = '0.11.0'
MindMap.version = '0.11.2'
MindMap.usePlugin(MiniMap)
.usePlugin(Watermark)

View File

@@ -19,7 +19,8 @@ import {
getObjectChangedProps,
isUndef,
handleGetSvgDataExtraContent,
getNodeTreeBoundingRect
getNodeTreeBoundingRect,
mergeTheme
} from './src/utils'
import defaultTheme, {
checkIsNodeSizeIndependenceConfig
@@ -34,6 +35,7 @@ class MindMap {
* @param {defaultOpt} opt
*/
constructor(opt = {}) {
MindMap.instanceCount++
// 合并选项
this.opt = this.handleOpt(merge(defaultOpt, opt))
// 预处理节点数据
@@ -252,7 +254,7 @@ class MindMap {
// 设置主题
initTheme() {
// 合并主题配置
this.themeConfig = merge(theme[this.opt.theme], this.opt.themeConfig)
this.themeConfig = mergeTheme(theme[this.opt.theme], this.opt.themeConfig)
// 设置背景样式
Style.setBackgroundStyle(this.el, this.themeConfig)
}
@@ -563,8 +565,8 @@ class MindMap {
let index = MindMap.hasPlugin(plugin)
if (index === -1) {
MindMap.usePlugin(plugin, opt)
this.initPlugin(plugin)
}
this.initPlugin(plugin)
}
// 移除插件
@@ -583,6 +585,7 @@ class MindMap {
// 实例化插件
initPlugin(plugin) {
if (this[plugin.instanceName]) return
this[plugin.instanceName] = new plugin({
mindMap: this,
pluginOpt: plugin.pluginOpt
@@ -616,6 +619,7 @@ class MindMap {
this.el.innerHTML = ''
this.el = null
this.removeCss()
MindMap.instanceCount--
}
}
@@ -632,13 +636,14 @@ MindMap.hasPlugin = plugin => {
return item === plugin
})
}
MindMap.instanceCount = 0
// 定义新主题
MindMap.defineTheme = (name, config = {}) => {
if (theme[name]) {
return new Error('该主题名称已存在')
}
theme[name] = merge(defaultTheme, config)
theme[name] = mergeTheme(defaultTheme, config)
}
export default MindMap

View File

@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
"version": "0.11.0",
"version": "0.11.2",
"description": "一个简单的web在线思维导图",
"authors": [
{

View File

@@ -328,7 +328,9 @@ export const nodeDataNoStylePropList = [
'notation',
'outerFrame',
'number',
'range'
'range',
'customLeft',
'customTop'
]
// 错误类型

View File

@@ -19,6 +19,16 @@ export const defaultOpt = {
themeConfig: {},
// 放大缩小的增量比例
scaleRatio: 0.2,
// 平移的步长比例,只在鼠标滚轮和触控板触发的平移中应用
translateRatio: 1,
// 最小缩小值百分数最小为0该选项只会影响view.narrow方法影响的行为为Ctrl+-快捷键、鼠标滚轮及触控板不会影响其他方法比如view.setScale所以需要你自行限制大小
minZoomRatio: 20,
// 最大放大值,百分数,传-1代表不限制否则传0以上数字该选项只会影响view.enlarge方法
maxZoomRatio: 400,
// 自定义判断wheel事件是否来自电脑的触控板
// 默认是通过判断e.deltaY的值是否小于10显然这种方法是不准确的当鼠标滚动的很慢或者触摸移动的很快时判断就失效了如果你有更好的方法欢迎提交issue
// 如果你希望自己来判断那么传递一个函数接收一个参数e事件对象需要返回true或false代表是否是来自触控板
customCheckIsTouchPad: null,
// 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点
mouseScaleCenterUseMousePosition: true,
// 最多显示几个标签
@@ -67,9 +77,7 @@ export const defaultOpt = {
close: ''
},
// 处理收起节点数量
expandBtnNumHandler: num => {
return num
},
expandBtnNumHandler: null,
// 是否显示带数量的收起按钮
isShowExpandNum: true,
// 是否只有当鼠标在画布内才响应快捷键事件
@@ -176,11 +184,6 @@ export const defaultOpt = {
addHistoryTime: 100,
// 是否禁止拖动画布
isDisableDrag: false,
// 鼠标移入概要高亮所属节点时的高亮框样式
highlightNodeBoxStyle: {
stroke: 'rgb(94, 200, 248)',
fill: 'transparent'
},
// 创建新节点时的行为
/*
DEFAULT :默认会激活新创建的节点,并且进入编辑模式。如果同时创建了多个新节点,那么只会激活而不会进入编辑模式
@@ -238,6 +241,12 @@ export const defaultOpt = {
padding: 100, // 超出画布四周指定范围内依旧渲染节点
removeNodeWhenOutCanvas: true // 节点移除画布可视区域后从画布删除
},
// 如果节点文本为空,那么为了避免空白节点高度塌陷,会用该字段指定的文本测量一个高度
emptyTextMeasureHeightText: 'abc123我和你',
// 是否在进行节点文本编辑时实时更新节点大小和节点位置,开启后当节点数量比较多时可能会造成卡顿
openRealtimeRenderOnNodeTextEdit: false,
// 默认会给容器元素el绑定mousedown事件并且会阻止其默认事件这会带来一定问题比如你聚焦在思维导图外的其他输入框点击画布就不会触发其失焦可以通过该选项关闭阻止。关闭后也会带来一定问题比如鼠标框选节点时可能会选中节点文字看你如何取舍
mousedownEventPreventDefault: true,
// 【Select插件】
// 多选节点时鼠标移动到边缘时的画布移动偏移量
@@ -321,6 +330,8 @@ export const defaultOpt = {
// 导出png、svg、pdf时会获取画布上的svg数据进行克隆然后通过该克隆的元素进行导出如果你想对该克隆元素做一些处理比如新增、替换、修改其中的一些元素那么可以通过该参数传递一个处理函数接收svg元素对象处理后需要返回原svg元素对象。
// 需要注意的是svg对象指的是@svgdotjs/svg.js库的元素对象所以你需要阅读该库的文档来操作该对象
handleBeingExportSvg: null,
// 导出图片或pdf都是通过canvas将svg绘制出来再导出所以如果思维导图特别大宽高可能会超出canvas支持的上限所以会进行缩放这个上限可以通过该参数设置代表canvas宽和高的最大宽度
maxCanvasSize: 16384,
// 【AssociativeLine插件】
// 关联线默认文字
@@ -409,5 +420,13 @@ export const defaultOpt = {
// 【OuterFrame】插件
outerFramePaddingX: 10,
outerFramePaddingY: 10
outerFramePaddingY: 10,
// 【Painter】插件
// 是否只格式刷节点手动设置的样式,不考虑节点通过主题的应用的样式
onlyPainterNodeCustomStyles: false,
// 【NodeImgAdjust】插件
// 拦截节点图片的删除点击节点图片上的删除按钮删除图片前会调用该函数如果函数返回true则取消删除
beforeDeleteNodeImg: null
}

View File

@@ -156,8 +156,14 @@ class Event extends EventEmitter {
// 判断是否是触控板
let isTouchPad = false
// mac、windows
if (e.wheelDeltaY === e.deltaY * -3 || Math.abs(e.wheelDeltaY) <= 10) {
isTouchPad = true
// if (e.wheelDeltaY === e.deltaY * -3 || Math.abs(e.wheelDeltaY) <= 10) {
// isTouchPad = true
// }
const { customCheckIsTouchPad } = this.mindMap.opt
if (typeof customCheckIsTouchPad === 'function') {
isTouchPad = customCheckIsTouchPad(e)
} else {
isTouchPad = Math.abs(e.deltaY) <= 10
}
this.emit('mousewheel', e, dirs, this, isTouchPad)
}

View File

@@ -97,8 +97,10 @@ class Render {
this.beingPasteText = ''
this.beingPasteImgSize = 0
this.currentBeingPasteType = ''
this.pasteData = { text: null, img: null }
// 节点高亮框
this.highlightBoxNode = null
this.highlightBoxNodeStyle = null
// 上一次节点激活数据
this.lastActiveNode = null
this.lastActiveNodeList = []
@@ -147,6 +149,28 @@ class Render {
})
// 性能模式
this.performanceMode()
// 实时渲染当节点文本编辑时
if (this.mindMap.opt.openRealtimeRenderOnNodeTextEdit) {
this.mindMap.on('node_text_edit_change', ({ node, text }) => {
node._textData = node.createTextNode(text)
const { width, height } = node.getNodeRect()
node.width = width
node.height = height
node.layout()
this.mindMap.render(() => {
this.textEdit.updateTextEditNode()
})
})
}
// 处理非https下的复制黏贴问题
// 暂时不启用,因为给页面的其他输入框(比如节点文本编辑框)粘贴内容也会触发,冲突问题暂时没有想到好的解决方法,不可能要求所有输入框都阻止冒泡
// if (!navigator.clipboard) {
// this.handlePaste = this.handlePaste.bind(this)
// window.addEventListener('paste', this.handlePaste)
// this.mindMap.on('beforeDestroy', () => {
// window.removeEventListener('paste', this.handlePaste)
// })
// }
}
// 性能模式,懒加载节点
@@ -391,7 +415,7 @@ class Render {
this.mindMap.keyCommand.addShortcut('Control+Down', () => {
this.mindMap.execCommand('DOWN_NODE')
})
// 复制节点
// 复制节点
this.mindMap.keyCommand.addShortcut('Control+c', () => {
this.copy()
})
@@ -401,7 +425,7 @@ class Render {
})
// 粘贴节点
this.mindMap.keyCommand.addShortcut('Control+v', () => {
this.paste()
if (navigator.clipboard) this.paste()
})
// 根节点居中显示
this.mindMap.keyCommand.addShortcut('Control+Enter', () => {
@@ -1104,6 +1128,28 @@ class Render {
})
}
// 非https下复制黏贴获取内容方法
handlePaste(event) {
const { disabledClipboard } = this.mindMap.opt
if (disabledClipboard) return
const clipboardData =
event.clipboardData || event.originalEvent.clipboardData
const items = clipboardData.items
let img = null
let text = ''
Array.from(items).forEach(item => {
if (item.type.indexOf('image') > -1) {
img = item.getAsFile()
}
if (item.type.indexOf('text') > -1) {
text = clipboardData.getData('text')
}
})
this.pasteData.img = img
this.pasteData.text = text
this.paste()
}
// 粘贴
async paste() {
const {
@@ -1117,7 +1163,9 @@ class Render {
let img = null
if (!disabledClipboard) {
try {
const res = await getDataFromClipboard()
const res = navigator.clipboard
? await getDataFromClipboard()
: this.pasteData
text = res.text || ''
img = res.img || null
} catch (error) {
@@ -1593,40 +1641,53 @@ class Render {
}
// 展开所有
expandAllNode() {
expandAllNode(uid = '') {
if (!this.renderTree) return
walk(
this.renderTree,
null,
node => {
if (!node.data.expand) {
node.data.expand = true
}
},
null,
true,
0,
0
)
const _walk = (node, enableExpand) => {
// 如果该节点为目标节点,那么修改允许展开的标志
if (!enableExpand && node.data.uid === uid) {
enableExpand = true
}
if (enableExpand && !node.data.expand) {
node.data.expand = true
}
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
_walk(child, enableExpand)
})
}
}
_walk(this.renderTree, !uid)
this.mindMap.render()
}
// 收起所有
unexpandAllNode(isSetRootNodeCenter = true) {
unexpandAllNode(isSetRootNodeCenter = true, uid = '') {
if (!this.renderTree) return
walk(
this.renderTree,
null,
(node, parent, isRoot) => {
if (!isRoot && node.children && node.children.length > 0) {
node.data.expand = false
}
},
null,
true,
0,
0
)
const _walk = (node, isRoot, enableUnExpand) => {
// 如果该节点为目标节点,那么修改允许展开的标志
if (!enableUnExpand && node.data.uid === uid) {
enableUnExpand = true
}
if (
enableUnExpand &&
!isRoot &&
node.children &&
node.children.length > 0
) {
node.data.expand = false
}
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
_walk(child, false, enableUnExpand)
})
}
}
_walk(this.renderTree, true, !uid)
this.mindMap.render(() => {
if (isSetRootNodeCenter) {
this.setRootNodeCenter()
@@ -2035,19 +2096,39 @@ class Render {
}
// 高亮节点或子节点
highlightNode(node, range) {
highlightNode(node, range, style) {
// 如果当前正在渲染,那么不进行高亮,因为节点位置可能不正确
if (this.isRendering) return
const { highlightNodeBoxStyle = {} } = this.mindMap.opt
style = {
stroke: 'rgb(94, 200, 248)',
fill: 'transparent',
...(style || {})
}
// 尚未创建
if (!this.highlightBoxNode) {
this.highlightBoxNode = new Polygon()
.stroke({
color: highlightNodeBoxStyle.stroke || 'transparent'
color: style.stroke || 'transparent'
})
.fill({
color: highlightNodeBoxStyle.fill || 'transparent'
color: style.fill || 'transparent'
})
} else if (this.highlightBoxNodeStyle) {
// 样式更新了
if (
this.highlightBoxNodeStyle.stroke !== style.stroke ||
this.highlightBoxNodeStyle.fill !== style.fill
) {
this.highlightBoxNode
.stroke({
color: style.stroke || 'transparent'
})
.fill({
color: style.fill || 'transparent'
})
}
}
this.highlightBoxNodeStyle = { ...style }
let minx = Infinity,
miny = Infinity,
maxx = -Infinity,

View File

@@ -25,6 +25,8 @@ export default class TextEdit {
// 如果编辑过程中缩放画布了,那么缓存当前编辑的内容
this.cacheEditingText = ''
this.hasBodyMousedown = false
this.textNodePaddingX = 5
this.textNodePaddingY = 3
this.bindEvent()
}
@@ -214,7 +216,7 @@ export default class TextEdit {
this.registerTmpShortcut()
if (!this.textEditNode) {
this.textEditNode = document.createElement('div')
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: ${this.textNodePaddingY}px ${this.textNodePaddingX}px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
this.textEditNode.setAttribute('contenteditable', true)
this.textEditNode.addEventListener('keyup', e => {
e.stopPropagation()
@@ -240,6 +242,13 @@ export default class TextEdit {
handleInputPasteText(e)
}
})
this.textEditNode.addEventListener('input', () => {
this.mindMap.emit('node_text_edit_change', {
node: this.currentNode,
text: this.getEditText(),
richText: false
})
})
const targetNode =
this.mindMap.opt.customInnerElsAppendTo || document.body
targetNode.appendChild(this.textEditNode)
@@ -256,8 +265,10 @@ export default class TextEdit {
node.style.domText(this.textEditNode, scale, isMultiLine)
this.textEditNode.style.zIndex = nodeTextEditZIndex
this.textEditNode.innerHTML = textLines.join('<br>')
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
this.textEditNode.style.minWidth =
rect.width + this.textNodePaddingX * 2 + 'px'
this.textEditNode.style.minHeight =
rect.height + this.textNodePaddingY * 2 + 'px'
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
this.textEditNode.style.display = 'block'
@@ -280,6 +291,24 @@ export default class TextEdit {
this.cacheEditingText = ''
}
// 更新文本编辑框的大小和位置
updateTextEditNode() {
if (this.mindMap.richText) {
this.mindMap.richText.updateTextEditNode()
return
}
if (!this.showTextEdit || !this.currentNode) {
return
}
const rect = this.currentNode._textData.node.node.getBoundingClientRect()
this.textEditNode.style.minWidth =
rect.width + this.textNodePaddingX * 2 + 'px'
this.textEditNode.style.minHeight =
rect.height + this.textNodePaddingY * 2 + 'px'
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
}
// 删除文本编辑元素
removeTextEditEl() {
if (this.mindMap.richText) {
@@ -313,12 +342,7 @@ export default class TextEdit {
}
this.mindMap.render()
})
this.mindMap.emit(
'hide_text_edit',
this.textEditNode,
this.renderer.activeNodeList,
this.currentNode
)
const currentNode = this.currentNode
this.currentNode = null
this.textEditNode.style.display = 'none'
this.textEditNode.innerHTML = ''
@@ -327,6 +351,12 @@ export default class TextEdit {
this.textEditNode.style.fontWeight = 'normal'
this.textEditNode.style.transform = 'translateY(0)'
this.showTextEdit = false
this.mindMap.emit(
'hide_text_edit',
this.textEditNode,
this.renderer.activeNodeList,
currentNode
)
}
// 获取当前正在编辑中的节点实例

View File

@@ -34,6 +34,8 @@ class MindMapNode {
this.lineDraw = this.mindMap.lineDraw
// 样式实例
this.style = new Style(this)
// 节点当前生效的全部样式
this.effectiveStyles = {}
// 形状实例
this.shapeInstance = new Shape(this)
this.shapePadding = {
@@ -1178,10 +1180,9 @@ class MindMapNode {
// 获取padding值
getPaddingVale() {
let { isActive } = this.getData()
return {
paddingX: this.getStyle('paddingX', true, isActive),
paddingY: this.getStyle('paddingY', true, isActive)
paddingX: this.getStyle('paddingX'),
paddingY: this.getStyle('paddingY')
}
}

View File

@@ -1,6 +1,5 @@
import { checkIsNodeStyleDataKey } from '../../../utils/index'
const rootProp = ['paddingX', 'paddingY']
const backgroundStyleProps = [
'backgroundColor',
'backgroundImage',
@@ -62,10 +61,11 @@ class Style {
// 合并样式
merge(prop, root) {
let themeConfig = this.ctx.mindMap.themeConfig
// 三级及以下节点
let defaultConfig = themeConfig.node
if (root || rootProp.includes(prop)) {
// 直接使用最外层样式
let defaultConfig = null
let useRoot = false
if (root) {
// 使用最外层样式
useRoot = true
defaultConfig = themeConfig
} else if (this.ctx.isGeneralization) {
// 概要节点
@@ -76,11 +76,27 @@ class Style {
} else if (this.ctx.layerIndex === 1) {
// 二级节点
defaultConfig = themeConfig.second
} else {
// 三级及以下节点
defaultConfig = themeConfig.node
}
let value = ''
// 优先使用节点本身的样式
return this.getSelfStyle(prop) !== undefined
? this.getSelfStyle(prop)
: defaultConfig[prop]
if (this.getSelfStyle(prop) !== undefined) {
value = this.getSelfStyle(prop)
} else if (defaultConfig[prop] !== undefined) {
// 否则使用对应层级的样式
value = defaultConfig[prop]
} else {
// 否则使用最外层样式
value = themeConfig[prop]
}
if (!useRoot) {
this.addToEffectiveStyles({
[prop]: value
})
}
return value
}
// 获取某个样式值
@@ -93,6 +109,14 @@ class Style {
return this.ctx.getData(prop)
}
// 更新当前节点生效的样式数据
addToEffectiveStyles(styles) {
this.ctx.effectiveStyles = {
...this.ctx.effectiveStyles,
...styles
}
}
// 矩形
rect(node) {
this.shape(node)
@@ -101,18 +125,30 @@ class Style {
// 形状
shape(node) {
if (this.merge('gradientStyle')) {
const styles = {
gradientStyle: this.merge('gradientStyle'),
startColor: this.merge('startColor'),
endColor: this.merge('endColor'),
startDir: this.merge('startDir'),
endDir: this.merge('endDir'),
fillColor: this.merge('fillColor'),
borderColor: this.merge('borderColor'),
borderWidth: this.merge('borderWidth'),
borderDasharray: this.merge('borderDasharray')
}
if (styles.gradientStyle) {
if (!this._gradient) {
this._gradient = this.ctx.nodeDraw.gradient('linear')
}
this._gradient.update(add => {
add.stop(0, this.merge('startColor'))
add.stop(1, this.merge('endColor'))
add.stop(0, styles.startColor)
add.stop(1, styles.endColor)
})
this._gradient.from(...styles.startDir).to(...styles.endDir)
node.fill(this._gradient)
} else {
node.fill({
color: this.merge('fillColor')
color: styles.fillColor
})
}
// 节点使用横线样式,不需要渲染非激活状态的边框样式
@@ -125,56 +161,89 @@ class Style {
// return
// }
node.stroke({
color: this.merge('borderColor'),
width: this.merge('borderWidth'),
dasharray: this.merge('borderDasharray')
color: styles.borderColor,
width: styles.borderWidth,
dasharray: styles.borderDasharray
})
}
// 文字
text(node) {
const styles = {
color: this.merge('color'),
fontFamily: this.merge('fontFamily'),
fontSize: this.merge('fontSize'),
fontWeight: this.merge('fontWeight'),
fontStyle: this.merge('fontStyle'),
textDecoration: this.merge('textDecoration')
}
node
.fill({
color: this.merge('color')
color: styles.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')
'font-family': styles.fontFamily,
'font-size': styles.fontSize,
'font-weight': styles.fontWeight,
'font-style': styles.fontStyle,
'text-decoration': styles.textDecoration
})
}
// 生成内联样式
createStyleText() {
const styles = {
color: this.merge('color'),
fontFamily: this.merge('fontFamily'),
fontSize: this.merge('fontSize'),
fontWeight: this.merge('fontWeight'),
fontStyle: this.merge('fontStyle'),
textDecoration: this.merge('textDecoration')
}
return `
color: ${this.merge('color')};
font-family: ${this.merge('fontFamily')};
font-size: ${this.merge('fontSize') + 'px'};
font-weight: ${this.merge('fontWeight')};
font-style: ${this.merge('fontStyle')};
text-decoration: ${this.merge('textDecoration')}
color: ${styles.color};
font-family: ${styles.fontFamily};
font-size: ${styles.fontSize + 'px'};
font-weight: ${styles.fontWeight};
font-style: ${styles.fontStyle};
text-decoration: ${styles.textDecoration}
`
}
// 获取文本样式
getTextFontStyle() {
return {
italic: this.merge('fontStyle') === 'italic',
bold: this.merge('fontWeight'),
const styles = {
color: this.merge('color'),
fontFamily: this.merge('fontFamily'),
fontSize: this.merge('fontSize'),
fontFamily: this.merge('fontFamily')
fontWeight: this.merge('fontWeight'),
fontStyle: this.merge('fontStyle'),
textDecoration: this.merge('textDecoration')
}
return {
italic: styles.fontStyle === 'italic',
bold: styles.fontWeight,
fontSize: styles.fontSize,
fontFamily: styles.fontFamily
}
}
// html文字节点
domText(node, fontSizeScale = 1, isMultiLine) {
node.style.fontFamily = this.merge('fontFamily')
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
node.style.fontWeight = this.merge('fontWeight') || 'normal'
node.style.lineHeight = !isMultiLine ? 'normal' : this.merge('lineHeight')
node.style.fontStyle = this.merge('fontStyle')
const styles = {
color: this.merge('color'),
fontFamily: this.merge('fontFamily'),
fontSize: this.merge('fontSize'),
fontWeight: this.merge('fontWeight'),
fontStyle: this.merge('fontStyle'),
textDecoration: this.merge('textDecoration'),
lineHeight: this.merge('lineHeight')
}
node.style.fontFamily = styles.fontFamily
node.style.fontSize = styles.fontSize * fontSizeScale + 'px'
node.style.fontWeight = styles.fontWeight || 'normal'
node.style.lineHeight = !isMultiLine ? 'normal' : styles.lineHeight
node.style.fontStyle = styles.fontStyle
}
// 标签文字
@@ -286,8 +355,10 @@ class Style {
// hover和激活节点
hoverNode(node) {
const { hoverRectColor } = this.ctx.mindMap.opt
node.radius(5).fill('none').stroke({
const hoverRectColor =
this.merge('hoverRectColor') || this.ctx.mindMap.opt.hoverRectColor
const hoverRectRadius = this.merge('hoverRectRadius')
node.radius(hoverRectRadius).fill('none').stroke({
color: hoverRectColor
})
}

View File

@@ -114,8 +114,10 @@ function createIconNode() {
}
// 创建富文本节点
function createRichTextNode() {
const { textAutoWrapWidth } = this.mindMap.opt
function createRichTextNode(specifyText) {
let text =
typeof specifyText === 'string' ? specifyText : this.getData('text')
const { textAutoWrapWidth, emptyTextMeasureHeightText } = this.mindMap.opt
let g = new G()
// 重新设置富文本节点内容
let recoverText = false
@@ -129,7 +131,6 @@ function createRichTextNode() {
recoverText = true
}
}
let text = this.getData('text')
if (recoverText && !isUndef(text)) {
// 判断节点内容是否是富文本
let isRichText = checkIsRichText(text)
@@ -153,7 +154,7 @@ function createRichTextNode() {
text: text
})
}
let html = `<div>${this.getData('text')}</div>`
let html = `<div>${text}</div>`
if (!this.mindMap.commonCaches.measureRichtextNodeTextSizeEl) {
this.mindMap.commonCaches.measureRichtextNodeTextSizeEl =
document.createElement('div')
@@ -174,7 +175,7 @@ function createRichTextNode() {
let { width, height } = el.getBoundingClientRect()
// 如果文本为空,那么需要计算一个默认高度
if (height <= 0) {
div.innerHTML = '<p>abc123我和你</p>'
div.innerHTML = `<p>${emptyTextMeasureHeightText}</p>`
let elTmp = div.children[0]
elTmp.classList.add('smm-richtext-node-wrap')
height = elTmp.getBoundingClientRect().height
@@ -199,10 +200,12 @@ function createRichTextNode() {
}
// 创建文本节点
function createTextNode() {
function createTextNode(specifyText) {
if (this.getData('richText')) {
return this.createRichTextNode()
return this.createRichTextNode(specifyText)
}
const text =
typeof specifyText === 'string' ? specifyText : this.getData('text')
if (this.getData('resetRichText')) {
delete this.nodeData.data.resetRichText
}
@@ -212,10 +215,11 @@ function createTextNode() {
// 文本超长自动换行
let textStyle = this.style.getTextFontStyle()
let textArr = []
if (!isUndef(this.getData('text'))) {
textArr = String(this.getData('text')).split(/\n/gim)
if (!isUndef(text)) {
textArr = String(text).split(/\n/gim)
}
let maxWidth = this.mindMap.opt.textAutoWrapWidth
const { textAutoWrapWidth: maxWidth, emptyTextMeasureHeightText } =
this.mindMap.opt
let isMultiLine = false
textArr.forEach((item, index) => {
let arr = item.split('')
@@ -247,6 +251,13 @@ function createTextNode() {
g.add(node)
})
let { width, height } = g.bbox()
// 如果文本为空,那么需要计算一个默认高度
if (height <= 0) {
const tmpNode = new Text().text(emptyTextMeasureHeightText)
this.style.text(tmpNode)
const tmpBbox = tmpNode.bbox()
height = tmpBbox.height
}
width = Math.min(Math.ceil(width), maxWidth)
height = Math.ceil(height)
g.attr('data-width', width)

View File

@@ -1,5 +1,6 @@
import btnsSvg from '../../../svg/btns'
import { SVG, Circle, G, Text } from '@svgdotjs/svg.js'
import { isUndef } from '../../../utils'
// 创建展开收起按钮的内容节点
function createExpandNodeContent() {
@@ -78,7 +79,12 @@ function updateExpandBtnNode() {
})
// 计算子节点数量
let count = this.sumNode(this.nodeData.children)
count = expandBtnNumHandler(count)
if (typeof expandBtnNumHandler === 'function') {
const res = expandBtnNumHandler(count, this)
if (!isUndef(res)) {
count = res
}
}
node.text(String(count))
} else {
this._fillExpandNode.stroke('none')

View File

@@ -186,15 +186,29 @@ function handleGeneralizationMouseenter() {
const list = belongNode.formatGetGeneralization()
const index = belongNode.getGeneralizationNodeIndex(this)
const generalizationData = list[index]
// 如果主题中设置了hoverRectColor颜色那么使用该颜色
// 否则使用hoverRectColor实例化选项的颜色
// 兜底使用highlightNode方法的默认颜色
const hoverRectColor = this.getStyle('hoverRectColor')
const color = hoverRectColor || this.mindMap.opt.hoverRectColor
const style = color
? {
stroke: color
}
: null
// 区间概要,框子节点
if (
Array.isArray(generalizationData.range) &&
generalizationData.range.length > 0
) {
this.mindMap.renderer.highlightNode(belongNode, generalizationData.range)
this.mindMap.renderer.highlightNode(
belongNode,
generalizationData.range,
style
)
} else {
// 否则框自己
this.mindMap.renderer.highlightNode(belongNode)
this.mindMap.renderer.highlightNode(belongNode, null, style)
}
}

View File

@@ -30,8 +30,11 @@ class View {
})
// 拖动视图
this.mindMap.event.on('mousedown', e => {
if (this.mindMap.opt.isDisableDrag) return
e.preventDefault()
const { isDisableDrag, mousedownEventPreventDefault } = this.mindMap.opt
if (isDisableDrag) return
if (mousedownEventPreventDefault) {
e.preventDefault()
}
this.sx = this.x
this.sy = this.y
})
@@ -63,7 +66,8 @@ class View {
mouseScaleCenterUseMousePosition,
mousewheelMoveStep,
mousewheelZoomActionReverse,
disableMouseWheelZoom
disableMouseWheelZoom,
translateRatio
} = this.mindMap.opt
// 是否自定义鼠标滚轮事件
if (
@@ -111,26 +115,34 @@ class View {
}
} else {
// 2.鼠标滚轮事件控制画布移动
const step = isTouchPad ? 10 : mousewheelMoveStep
let stepX = 0
let stepY = 0
if (isTouchPad) {
// 如果是触控板,那么直接使用触控板滑动距离
stepX = Math.abs(e.wheelDeltaX)
stepY = Math.abs(e.wheelDeltaY)
} else {
stepX = stepY = mousewheelMoveStep
}
let mx = 0
let my = 0
// 上移
if (dirs.includes(CONSTANTS.DIR.DOWN)) {
my = -step
my = -stepY
}
// 下移
if (dirs.includes(CONSTANTS.DIR.UP)) {
my = step
my = stepY
}
// 右移
if (dirs.includes(CONSTANTS.DIR.LEFT)) {
mx = step
mx = stepX
}
// 左移
if (dirs.includes(CONSTANTS.DIR.RIGHT)) {
mx = -step
mx = -stepX
}
this.translateXY(mx, my)
this.translateXY(mx * translateRatio, my * translateRatio)
}
})
this.mindMap.on('resize', () => {
@@ -238,8 +250,9 @@ class View {
// 缩小
narrow(cx, cy, isTouchPad) {
const scaleRatio = this.mindMap.opt.scaleRatio / (isTouchPad ? 5 : 1)
const scale = Math.max(this.scale - scaleRatio, 0.1)
let { scaleRatio, minZoomRatio } = this.mindMap.opt
scaleRatio = scaleRatio / (isTouchPad ? 5 : 1)
const scale = Math.max(this.scale - scaleRatio, minZoomRatio / 100)
this.scaleInCenter(scale, cx, cy)
this.transform()
this.emitEvent('scale')
@@ -247,8 +260,14 @@ class View {
// 放大
enlarge(cx, cy, isTouchPad) {
const scaleRatio = this.mindMap.opt.scaleRatio / (isTouchPad ? 5 : 1)
const scale = this.scale + scaleRatio
let { scaleRatio, maxZoomRatio } = this.mindMap.opt
scaleRatio = scaleRatio / (isTouchPad ? 5 : 1)
let scale = 0
if (maxZoomRatio === -1) {
scale = this.scale + scaleRatio
} else {
scale = Math.min(this.scale + scaleRatio, maxZoomRatio / 100)
}
this.scaleInCenter(scale, cx, cy)
this.transform()
this.emitEvent('scale')

View File

@@ -128,8 +128,9 @@ class Base {
)
}
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本等情况需要重新计算节点大小和布局
const isNeedResizeSources = this.checkIsNeedResizeSources()
if (
this.checkIsNeedResizeSources() ||
isNeedResizeSources ||
isLayerTypeChange ||
newNode.getData('resetRichText') ||
isNumberChange
@@ -137,7 +138,7 @@ class Base {
newNode.getSize()
newNode.needLayout = true
}
this.checkGetGeneralizationChange(newNode)
this.checkGetGeneralizationChange(newNode, isNeedResizeSources)
} else if (
(this.lru.has(uid) || this.renderer.lastNodeCache[uid]) &&
!this.renderer.reRender
@@ -186,7 +187,7 @@ class Base {
newNode.getSize()
newNode.needLayout = true
}
this.checkGetGeneralizationChange(newNode)
this.checkGetGeneralizationChange(newNode, isResizeSource)
} else {
// 创建新节点
const newUid = uid || createUid()
@@ -228,7 +229,7 @@ class Base {
}
// 检查概要节点是否需要更新
checkGetGeneralizationChange(node) {
checkGetGeneralizationChange(node, isResizeSource) {
const generalizationList = node.getData('generalization')
if (
generalizationList &&
@@ -239,8 +240,13 @@ class Base {
const gNode = item.generalizationNode
const oldData = gNode.getData()
const newData = generalizationList[index]
if (newData && JSON.stringify(oldData) !== JSON.stringify(newData)) {
gNode.nodeData.data = newData
if (
isResizeSource ||
(newData && JSON.stringify(oldData) !== JSON.stringify(newData))
) {
if (newData) {
gNode.nodeData.data = newData
}
gNode.getSize()
gNode.needLayout = true
}
@@ -371,18 +377,32 @@ class Base {
}
// 二次贝塞尔曲线
quadraticCurvePath(x1, y1, x2, y2) {
let cx = x1 + (x2 - x1) * 0.2
let cy = y1 + (y2 - y1) * 0.8
quadraticCurvePath(x1, y1, x2, y2, v = false) {
let cx, cy
if (v) {
cx = x1 + (x2 - x1) * 0.8
cy = y1 + (y2 - y1) * 0.2
} else {
cx = x1 + (x2 - x1) * 0.2
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
cubicBezierPath(x1, y1, x2, y2, v = false) {
let cx1, cy1, cx2, cy2
if (v) {
cx1 = x1
cy1 = y1 + (y2 - y1) / 2
cx2 = x2
cy2 = cy1
} else {
cx1 = x1 + (x2 - x1) / 2
cy1 = y1
cx2 = cx1
cy2 = y2
}
return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`
}

View File

@@ -34,7 +34,14 @@ class OrganizationStructure extends Base {
this.renderer.renderTree,
null,
(cur, parent, isRoot, layerIndex, index, ancestors) => {
let newNode = this.createNode(cur, parent, isRoot, layerIndex, index, ancestors)
let newNode = this.createNode(
cur,
parent,
isRoot,
layerIndex,
index,
ancestors
)
// 根节点定位在画布中心位置
if (isRoot) {
this.setNodeCenter(newNode)
@@ -148,13 +155,56 @@ class OrganizationStructure extends Base {
// 绘制连线,连接该节点到其子节点
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'direct') {
if (lineStyle === 'curve') {
this.renderLineCurve(node, lines, style)
} else if (lineStyle === 'direct') {
this.renderLineDirect(node, lines, style)
} else {
this.renderLineStraight(node, lines, style)
}
}
// 曲线风格连线
renderLineCurve(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
const { alwaysShowExpandBtn, notShowExpandBtn } = this.mindMap.opt
if (!alwaysShowExpandBtn || notShowExpandBtn) {
expandBtnSize = 0
}
const {
nodeUseLineStyle,
rootLineStartPositionKeepSameInCurve,
rootLineKeepSameInCurve
} = this.mindMap.themeConfig
node.children.forEach((item, index) => {
if (node.layerIndex === 0) {
expandBtnSize = 0
}
let x1 = left + width / 2
let y1 =
node.layerIndex === 0 && !rootLineStartPositionKeepSameInCurve
? top + height / 2
: top + height + expandBtnSize
let x2 = item.left + item.width / 2
let y2 = item.top
let path = ''
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = nodeUseLineStyle
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
: ''
if (node.isRoot && !rootLineKeepSameInCurve) {
path =
this.quadraticCurvePath(x1, y1, x2, y2, true) + nodeUseLineStylePath
} else {
path = this.cubicBezierPath(x1, y1, x2, y2, true) + nodeUseLineStylePath
}
this.setLineStyle(style, lines[index], path, item)
})
}
// 直连风格
renderLineDirect(node, lines, style) {
if (node.children.length <= 0) {

View File

@@ -129,6 +129,7 @@ class Export {
// svg转png
svgToPng(svgSrc, transparent, clipData = null) {
const { maxCanvasSize, minExportImgCanvasScale } = this.mindMap.opt
return new Promise((resolve, reject) => {
const img = new Image()
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
@@ -136,10 +137,7 @@ class Export {
img.onload = async () => {
try {
const canvas = document.createElement('canvas')
const dpr = Math.max(
window.devicePixelRatio,
this.mindMap.opt.minExportImgCanvasScale
)
const dpr = Math.max(window.devicePixelRatio, minExportImgCanvasScale)
let imgWidth = img.width
let imgHeight = img.height
// 如果是裁减操作的话,那么需要手动添加内边距,及调整图片大小为实际的裁减区域的大小,不要忘了内边距哦
@@ -152,29 +150,40 @@ class Export {
imgHeight = clipData.height + paddingY * 2
}
// 检查是否超出canvas支持的像素上限
const maxSize = 16384 / dpr
const maxArea = maxSize * maxSize
if (imgWidth * imgHeight > maxArea) {
// canvas大小需要乘以dpr
let canvasWidth = imgWidth * dpr
let canvasHeight = imgHeight * dpr
if (canvasWidth > maxCanvasSize || canvasHeight > maxCanvasSize) {
let newWidth = null
let newHeight = null
if (imgWidth > maxSize) {
newWidth = maxArea / imgHeight
} else if (imgHeight > maxSize) {
newHeight = maxArea / imgWidth
if (canvasWidth > maxCanvasSize) {
// 如果宽度超出限制,那么调整为上限值
newWidth = maxCanvasSize
} else if (canvasHeight > maxCanvasSize) {
// 高度同理
newHeight = maxCanvasSize
}
const res = resizeImgSize(imgWidth, imgHeight, newWidth, newHeight)
imgWidth = res[0]
imgHeight = res[1]
// 计算缩放后的宽高
const res = resizeImgSize(
canvasWidth,
canvasHeight,
newWidth,
newHeight
)
canvasWidth = res[0]
canvasHeight = res[1]
}
canvas.width = imgWidth * dpr
canvas.height = imgHeight * dpr
canvas.style.width = imgWidth + 'px'
canvas.style.height = imgHeight + 'px'
canvas.width = canvasWidth
canvas.height = canvasHeight
const styleWidth = canvasWidth / dpr
const styleHeight = canvasHeight / dpr
canvas.style.width = styleWidth + 'px'
canvas.style.height = styleHeight + 'px'
const ctx = canvas.getContext('2d')
ctx.scale(dpr, dpr)
// 绘制背景
if (!transparent) {
await this.drawBackgroundToCanvas(ctx, imgWidth, imgHeight)
await this.drawBackgroundToCanvas(ctx, styleWidth, styleHeight)
}
// 图片绘制到canvas里
// 如果有裁减数据,那么需要进行裁减
@@ -191,7 +200,7 @@ class Export {
clipData.height
)
} else {
ctx.drawImage(img, 0, 0, imgWidth, imgHeight)
ctx.drawImage(img, 0, 0, styleWidth, styleHeight)
}
resolve(canvas.toDataURL())
} catch (error) {

View File

@@ -1,8 +1,11 @@
import katex from 'katex'
import Quill from 'quill'
import { getChromeVersion } from '../utils/index'
import { getChromeVersion, htmlEscape } from '../utils/index'
import { getBaseStyleText, getFontStyleText } from './FormulaStyle'
let extended = false
const QuillFormula = Quill.import('formats/formula')
// 数学公式支持插件
// 该插件在富文本模式下可用
class Formula {
@@ -16,6 +19,18 @@ class Formula {
this.cssEl = null
this.addStyle()
this.extendQuill()
this.onDestroy = this.onDestroy.bind(this)
this.mindMap.on('beforeDestroy', this.onDestroy)
}
onDestroy() {
const instanceCount = Object.getPrototypeOf(this.mindMap).constructor
.instanceCount
// 如果思维导图实例数量变成0了那么就恢复成默认的
if (instanceCount <= 1) {
extended = false
Quill.register('formats/formula', QuillFormula, true)
}
}
init() {
@@ -50,7 +65,9 @@ class Formula {
// 修改formula格式工具
extendQuill() {
const QuillFormula = Quill.import('formats/formula')
if (extended) return
extended = true
const self = this
class CustomFormulaBlot extends QuillFormula {
@@ -58,7 +75,7 @@ class Formula {
let node = super.create(value)
if (typeof value === 'string') {
katex.render(value, node, self.config)
node.setAttribute('data-value', value)
node.setAttribute('data-value', htmlEscape(value))
}
return node
}
@@ -110,11 +127,7 @@ class Formula {
for (const el of els)
nodeText = nodeText.replace(
el.outerHTML,
`\$${el
.getAttribute('data-value')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')}\$`
`\$${el.getAttribute('data-value')}\$`
)
}
return nodeText
@@ -172,11 +185,13 @@ class Formula {
// 插件被移除前做的事情
beforePluginRemove() {
this.removeStyle()
this.mindMap.off('beforeDestroy', this.onDestroy)
}
// 插件被卸载前做的事情
beforePluginDestroy() {
this.removeStyle()
this.mindMap.off('beforeDestroy', this.onDestroy)
}
}

View File

@@ -192,8 +192,14 @@ class NodeImgAdjust {
if (this.isMousedown) return
this.hideHandleEl()
})
btnRemove.addEventListener('click', e => {
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, { url: null })
btnRemove.addEventListener('click', async e => {
let stop = false
if (typeof this.mindMap.opt.beforeDeleteNodeImg === 'function') {
stop = await this.mindMap.opt.beforeDeleteNodeImg(this.node)
}
if (!stop) {
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, { url: null })
}
})
// 添加元素到页面
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body

View File

@@ -53,17 +53,22 @@ class Painter {
node.uid === this.painterNode.uid
)
return
const style = {}
let style = {}
// 格式刷节点所有生效的样式
if (!this.mindMap.opt.onlyPainterNodeCustomStyles) {
style = {
...this.painterNode.effectiveStyles
}
}
const painterNodeData = this.painterNode.getData()
Object.keys(painterNodeData).forEach(key => {
if (checkIsNodeStyleDataKey(key)) {
style[key] = painterNodeData[key]
}
})
// 先去除目标节点的样式
this.mindMap.renderer._handleRemoveCustomStyles(node.getData())
node.setStyles(style)
if (painterNodeData.activeStyle) {
node.setStyles(painterNodeData.activeStyle, true)
}
}
// 插件被移除前做的事情

View File

@@ -57,6 +57,8 @@ class RichText {
this.cacheEditingText = ''
this.lostStyle = false
this.isCompositing = false
this.textNodePaddingX = 6
this.textNodePaddingY = 4
this.initOpt()
this.extendQuill()
this.appendCss()
@@ -71,14 +73,17 @@ class RichText {
// 绑定事件
bindEvent() {
this.onCompositionStart = this.onCompositionStart.bind(this)
this.onCompositionUpdate = this.onCompositionUpdate.bind(this)
this.onCompositionEnd = this.onCompositionEnd.bind(this)
window.addEventListener('compositionstart', this.onCompositionStart)
window.addEventListener('compositionupdate', this.onCompositionUpdate)
window.addEventListener('compositionend', this.onCompositionEnd)
}
// 解绑事件
unbindEvent() {
window.removeEventListener('compositionstart', this.onCompositionStart)
window.removeEventListener('compositionupdate', this.onCompositionUpdate)
window.removeEventListener('compositionend', this.onCompositionEnd)
}
@@ -198,8 +203,8 @@ class RichText {
let scaleX = rect.width / originWidth
let scaleY = rect.height / originHeight
// 内边距
let paddingX = 6
let paddingY = 4
let paddingX = this.textNodePaddingX
let paddingY = this.textNodePaddingY
if (richTextEditFakeInPlace) {
let paddingValue = node.getPaddingVale()
paddingX = paddingValue.paddingX
@@ -287,6 +292,20 @@ class RichText {
this.cacheEditingText = ''
}
// 更新文本编辑框的大小和位置
updateTextEditNode() {
if (!this.node) return
const rect = this.node._textData.node.node.getBoundingClientRect()
const g = this.node._textData.node
const originWidth = g.attr('data-width')
const originHeight = g.attr('data-height')
this.textEditNode.style.minWidth =
originWidth + this.textNodePaddingX * 2 + 'px'
this.textEditNode.style.minHeight = originHeight + 'px'
this.textEditNode.style.left = rect.left + 'px'
this.textEditNode.style.top = rect.top + 'px'
}
// 删除文本编辑框元素
removeTextEditEl() {
if (!this.textEditNode) return
@@ -340,6 +359,18 @@ class RichText {
return html.replace(/<p><br><\/p>$/, '')
}
// 给html字符串中的节点样式按样式名首字母排序
sortHtmlNodeStyles(html) {
return html.replace(/(<[^<>]+\s+style=")([^"]+)("\s*>)/g, (_, a, b, c) => {
let arr = b.match(/[^:]+:[^:]+;/g) || []
arr = arr.map(item => {
return item.trim()
})
arr.sort()
return a + arr.join('') + c
})
}
// 隐藏文本编辑控件,即完成编辑
hideEditText(nodes) {
if (!this.showTextEdit) {
@@ -350,6 +381,7 @@ class RichText {
beforeHideRichTextEdit(this)
}
let html = this.getEditText()
html = this.sortHtmlNodeStyles(html)
let list =
nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
list.forEach(node => {
@@ -360,12 +392,13 @@ class RichText {
// }
this.mindMap.render()
})
this.mindMap.emit('hide_text_edit', this.textEditNode, list, this.node)
const node = this.node
this.textEditNode.style.display = 'none'
this.showTextEdit = false
this.mindMap.emit('rich_text_selection_change', false)
this.node = null
this.isInserting = false
this.mindMap.emit('hide_text_edit', this.textEditNode, list, node)
}
// 初始化Quill富文本编辑器
@@ -490,6 +523,11 @@ class RichText {
this.setTextStyleIfNotRichText(this.node)
this.lostStyle = false
}
this.mindMap.emit('node_text_edit_change', {
node: this.node,
text: this.getEditText(),
richText: true
})
})
// 拦截粘贴,只允许粘贴纯文本
// this.quill.clipboard.addMatcher(Node.TEXT_NODE, node => {
@@ -511,6 +549,20 @@ class RichText {
delta.ops = ops
return delta
})
// 拦截图片的粘贴
this.quill.root.addEventListener(
'paste',
e => {
if (
e.clipboardData &&
e.clipboardData.files &&
e.clipboardData.files.length
) {
e.preventDefault()
}
},
true
)
}
// 获取粘贴的文本的样式
@@ -544,6 +596,16 @@ class RichText {
this.isCompositing = true
}
// 中文输入中
onCompositionUpdate() {
if (!this.showTextEdit || !this.node) return
this.mindMap.emit('node_text_edit_change', {
node: this.node,
text: this.getEditText(),
richText: true
})
}
// 中文输入结束
onCompositionEnd() {
if (!this.showTextEdit) {

View File

@@ -41,7 +41,8 @@ class Select {
// 鼠标按下
onMousedown(e) {
if (this.mindMap.opt.readonly) {
const { readonly, mousedownEventPreventDefault } = this.mindMap.opt
if (readonly) {
return
}
let { useLeftKeySelectionRightKeyDrag } = this.mindMap.opt
@@ -51,7 +52,9 @@ class Select {
) {
return
}
e.preventDefault()
if (mousedownEventPreventDefault) {
e.preventDefault()
}
this.isMousedown = true
this.cacheActiveList = [...this.mindMap.renderer.activeNodeList]
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 秋天
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 背景颜色
backgroundColor: '#fff2df',
// 连线的颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 牛油果
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 背景颜色
backgroundColor: '#e6f1de',
// 连线的颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 黑金
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 背景颜色
backgroundColor: 'rgb(18, 20, 20)',
// 连线的颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 黑色幽默
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 背景颜色
backgroundColor: 'rgb(27, 31, 34)',
// 连线的颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 天空蓝
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(115, 161, 191)',
// 背景颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 脑残粉
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 115, 148)',
// 背景颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 脑图经典
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: '#fff',
// 连线的粗细

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 经典2
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',
// 连线的粗细

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 经典3
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(94, 202, 110)',
// 连线的粗细

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 经典4
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(30, 53, 86)',
// 连线的粗细

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 经典蓝
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',
// 连线的粗细

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 经典绿
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(123, 199, 120)',
// 背景颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 咖啡
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(173, 123, 91)',
lineWidth: 4,

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 课程绿
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(113, 195, 169)',
lineWidth: 3,

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 暗色
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(17, 68, 23)',
// 连线的粗细

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 暗色2
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(75, 81, 78)',
lineWidth: 3,

View File

@@ -82,8 +82,16 @@ export default {
gradientStyle: false,
startColor: '#549688',
endColor: '#fff',
startDir: [0, 0],
endDir: [1, 0],
// 连线标记的位置start头部、end尾部该配置在showLineMarker配置为true时生效
lineMarkerDir: 'end'
lineMarkerDir: 'end',
// 节点鼠标hover和激活时显示的矩形边框的颜色主题里不设置默认会取hoverRectColor实例化选项的值
hoverRectColor: '',
// 点鼠标hover和激活时显示的矩形边框的圆角大小
hoverRectRadius: 5
// paddingX: 15,
// paddingY: 5
},
// 二级节点样式
second: {
@@ -94,7 +102,7 @@ export default {
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#565656',
fontSize: 16,
fontWeight: 'noraml',
fontWeight: 'normal',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: '#549688',
@@ -105,7 +113,13 @@ export default {
gradientStyle: false,
startColor: '#549688',
endColor: '#fff',
lineMarkerDir: 'end'
startDir: [0, 0],
endDir: [1, 0],
lineMarkerDir: 'end',
hoverRectColor: '',
hoverRectRadius: 5
// paddingX: 15,
// paddingY: 5
},
// 三级及以下节点样式
node: {
@@ -116,7 +130,7 @@ export default {
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#6a6d6c',
fontSize: 14,
fontWeight: 'noraml',
fontWeight: 'normal',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: 'transparent',
@@ -127,7 +141,13 @@ export default {
gradientStyle: false,
startColor: '#549688',
endColor: '#fff',
lineMarkerDir: 'end'
startDir: [0, 0],
endDir: [1, 0],
lineMarkerDir: 'end',
hoverRectColor: '',
hoverRectRadius: 5
// paddingX: 15,
// paddingY: 5
},
// 概要节点样式
generalization: {
@@ -138,7 +158,7 @@ export default {
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#565656',
fontSize: 16,
fontWeight: 'noraml',
fontWeight: 'normal',
fontStyle: 'normal',
lineHeight: 1.5,
borderColor: '#549688',
@@ -148,7 +168,13 @@ export default {
textDecoration: 'none',
gradientStyle: false,
startColor: '#549688',
endColor: '#fff'
endColor: '#fff',
startDir: [0, 0],
endDir: [1, 0],
hoverRectColor: '',
hoverRectRadius: 5
// paddingX: 15,
// paddingY: 5
}
}
@@ -179,7 +205,11 @@ const nodeSizeIndependenceList = [
'gradientStyle',
'lineRadius',
'startColor',
'endColor'
'endColor',
'startDir',
'endDir',
'hoverRectColor',
'hoverRectRadius'
]
export const checkIsNodeSizeIndependenceConfig = config => {
let keys = Object.keys(config)

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 泥土黄
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 147, 115)',
// 背景颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 清新绿
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: '#333',
// 背景颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 清新红
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 115, 115)',
// 背景颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 金色vip
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 56, 62)',
lineWidth: 3,

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 绿叶
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(40, 193, 84)',
lineWidth: 3,

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 深夜办公室
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 背景颜色
backgroundColor: 'rgb(32, 37, 49)',
// 连线的颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 小黄人
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',
lineWidth: 3,

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 薄荷
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(104, 204, 202)',
lineWidth: 3,

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 橙汁
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 背景颜色
backgroundColor: '#070616',
// 连线的颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 粉红葡萄
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(166, 101, 106)',
lineWidth: 3,

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 红色精神
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 背景颜色
backgroundColor: 'rgb(255, 238, 228)',
// 连线的颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 浪漫紫
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(123, 115, 191)',
// 背景颜色

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 简约黑
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(34, 34, 34)',
lineWidth: 4,

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 天清绿
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: '#fff',
lineWidth: 3,

View File

@@ -1,8 +1,8 @@
import defaultTheme from './default'
import merge from 'deepmerge'
import { mergeTheme } from '../utils'
// 活力橙
export default merge(defaultTheme, {
export default mergeTheme(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(254, 146, 0)',
lineWidth: 3,

View File

@@ -5,6 +5,7 @@ import {
} from '../constants/constant'
import MersenneTwister from './mersenneTwister'
import { ForeignObject } from '@svgdotjs/svg.js'
import merge from 'deepmerge'
// 深度优先遍历树
export const walk = (
@@ -1184,9 +1185,18 @@ export const handleInputPasteText = (e, text) => {
// 去除格式
text = getTextFromHtml(text)
// 去除换行
text = text.replaceAll(/\n/g, '')
const node = document.createTextNode(text)
selection.getRangeAt(0).insertNode(node)
// text = text.replaceAll(/\n/g, '')
const textArr = text.split(/\n/g)
const fragment = document.createDocumentFragment()
textArr.forEach((item, index) => {
const node = document.createTextNode(item)
fragment.appendChild(node)
if (index < textArr.length - 1) {
const br = document.createElement('br')
fragment.appendChild(br)
}
})
selection.getRangeAt(0).insertNode(fragment)
selection.collapseToEnd()
}
@@ -1601,3 +1611,12 @@ export const sortNodeList = nodeList => {
})
return nodeList
}
// 合并主题配置
export const mergeTheme = (dest, source) => {
return merge(dest, source, {
arrayMerge: (destinationArray, sourceArray) => {
return sourceArray
}
})
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2479351 */
src: url('iconfont.woff2?t=1719815803051') format('woff2'),
url('iconfont.woff?t=1719815803051') format('woff'),
url('iconfont.ttf?t=1719815803051') format('truetype');
src: url('iconfont.woff2?t=1726022313538') format('woff2'),
url('iconfont.woff?t=1726022313538') format('woff'),
url('iconfont.ttf?t=1726022313538') format('truetype');
}
.iconfont {
@@ -13,6 +13,14 @@
-moz-osx-font-smoothing: grayscale;
}
.iconfile-excel:before {
content: "\e7b7";
}
.iconfreemind:before {
content: "\e97d";
}
.iconwaikuang:before {
content: "\e640";
}

View File

@@ -85,12 +85,14 @@ export const formulaList = [
'\\begin{cases}3x + 5y + z \\\\7x - 2y + 4z \\\\-6x + 3y + 2z\\end{cases}'
]
// 支持某种连线类型的结构
export const supportLineStyleLayoutsMap = {
curve: [
'logicalStructure',
'logicalStructureLeft',
'mindMap',
'verticalTimeline'
'verticalTimeline',
'organizationStructure'
],
direct: [
'logicalStructure',
@@ -101,6 +103,7 @@ export const supportLineStyleLayoutsMap = {
]
}
// 直线模式支持设置圆角的结构
export const supportLineRadiusLayouts = [
'logicalStructure',
'logicalStructureLeft',
@@ -108,6 +111,7 @@ export const supportLineRadiusLayouts = [
'verticalTimeline'
]
// 支持只显示底边直线风格的结构
export const supportNodeUseLineStyleLayouts = [
'logicalStructure',
'logicalStructureLeft',
@@ -116,10 +120,12 @@ export const supportNodeUseLineStyleLayouts = [
'organizationStructure'
]
// 支持曲线模式下,根节点样式和其他节点样式保持一致的结构
export const supportRootLineKeepSameInCurveLayouts = [
'logicalStructure',
'logicalStructureLeft',
'mindMap'
'mindMap',
'organizationStructure'
]
// 彩虹线条配置

View File

@@ -495,6 +495,18 @@ export const downTypeList = [
type: 'txt',
icon: 'iconTXT',
desc: 'Plain text file'
},
{
name: 'FreeMind',
type: 'mm',
icon: 'iconfreemind',
desc: 'FreeMind software format'
},
{
name: 'Excel',
type: 'xlsx',
icon: 'iconfile-excel',
desc: 'Excel software format'
}
]
@@ -557,3 +569,55 @@ export const numberLevelList = [
value: 0
}
]
// 背景渐变方向
export const linearGradientDirList = [
{
name: 'Left to right',
value: '1',
start: [0, 0],
end: [1, 0]
},
{
name: 'Right to left',
value: '2',
start: [1, 0],
end: [0, 0]
},
{
name: 'Top to bottom',
value: '3',
start: [0, 0],
end: [0, 1]
},
{
name: 'Bottom to top',
value: '4',
start: [0, 1],
end: [0, 0]
},
{
name: 'Left top to right bottom',
value: '5',
start: [0, 0],
end: [1, 1]
},
{
name: 'Left bottom to right top',
value: '6',
start: [0, 1],
end: [1, 0]
},
{
name: 'Right top to left bottom',
value: '7',
start: [1, 0],
end: [0, 1]
},
{
name: 'Right bottom to left top',
value: '8',
start: [1, 1],
end: [0, 0]
}
]

View File

@@ -21,7 +21,8 @@ import {
shapeListMap as shapeListMapZh,
lineStyleMap as lineStyleMapZh,
numberTypeList as numberTypeListZh,
numberLevelList as numberLevelListZh
numberLevelList as numberLevelListZh,
linearGradientDirList as linearGradientDirListZh
} from './zh'
import {
fontFamilyList as fontFamilyListEn,
@@ -36,82 +37,120 @@ import {
backgroundSizeList as backgroundSizeListEn,
downTypeList as downTypeListEn,
numberTypeList as numberTypeListEn,
numberLevelList as numberLevelListEn
numberLevelList as numberLevelListEn,
linearGradientDirList as linearGradientDirListEn
} from './en'
import {
fontFamilyList as fontFamilyListZhtw,
borderDasharrayList as borderDasharrayListZhtw,
lineStyleList as lineStyleListZhtw,
rootLineKeepSameInCurveList as rootLineKeepSameInCurveListZhtw,
backgroundRepeatList as backgroundRepeatListZhtw,
backgroundPositionList as backgroundPositionListZhtw,
shortcutKeyList as shortcutKeyListZhtw,
shapeList as shapeListZhtw,
sidebarTriggerList as sidebarTriggerListZhtw,
backgroundSizeList as backgroundSizeListZhtw,
downTypeList as downTypeListZhtw,
numberTypeList as numberTypeListZhtw,
numberLevelList as numberLevelListZhtw,
linearGradientDirList as linearGradientDirListZhtw
} from './zhtw'
const fontFamilyList = {
zh: fontFamilyListZh,
en: fontFamilyListEn
en: fontFamilyListEn,
zhtw: fontFamilyListZhtw
}
const borderDasharrayList = {
zh: borderDasharrayListZh,
en: borderDasharrayListEn
en: borderDasharrayListEn,
zhtw: borderDasharrayListZhtw
}
const lineStyleList = {
zh: lineStyleListZh,
en: lineStyleListEn
en: lineStyleListEn,
zhtw: lineStyleListZhtw
}
const lineStyleMap = {
zh: lineStyleMapZh,
en: lineStyleMapZh
en: lineStyleMapZh,
zhtw: lineStyleMapZh
}
const rootLineKeepSameInCurveList = {
zh: rootLineKeepSameInCurveListZh,
en: rootLineKeepSameInCurveListEn
en: rootLineKeepSameInCurveListEn,
zhtw: rootLineKeepSameInCurveListZhtw
}
const backgroundRepeatList = {
zh: backgroundRepeatListZh,
en: backgroundRepeatListEn
en: backgroundRepeatListEn,
zhtw: backgroundRepeatListZhtw
}
const backgroundPositionList = {
zh: backgroundPositionListZh,
en: backgroundPositionListEn
en: backgroundPositionListEn,
zhtw: backgroundPositionListZhtw
}
const backgroundSizeList = {
zh: backgroundSizeListZh,
en: backgroundSizeListEn
en: backgroundSizeListEn,
zhtw: backgroundSizeListZhtw
}
const shortcutKeyList = {
zh: shortcutKeyListZh,
en: shortcutKeyListEn
en: shortcutKeyListEn,
zhtw: shortcutKeyListZhtw
}
const shapeList = {
zh: shapeListZh,
en: shapeListEn
en: shapeListEn,
zhtw: shapeListZhtw
}
const shapeListMap = {
zh: shapeListMapZh,
en: shapeListMapZh
en: shapeListMapZh,
zhtw: shapeListMapZh
}
const sidebarTriggerList = {
zh: sidebarTriggerListZh,
en: sidebarTriggerListEn
en: sidebarTriggerListEn,
zhtw: sidebarTriggerListZhtw
}
const downTypeList = {
zh: downTypeListZh,
en: downTypeListEn
en: downTypeListEn,
zhtw: downTypeListZhtw
}
const numberTypeList = {
zh: numberTypeListZh,
en: numberTypeListEn
en: numberTypeListEn,
zhtw: numberTypeListZhtw
}
const numberLevelList = {
zh: numberLevelListZh,
en: numberLevelListEn
en: numberLevelListEn,
zhtw: numberLevelListZhtw
}
const linearGradientDirList = {
zh: linearGradientDirListZh,
en: linearGradientDirListEn,
zhtw: linearGradientDirListZhtw
}
export {
@@ -137,5 +176,6 @@ export {
sidebarTriggerList,
downTypeList,
numberTypeList,
numberLevelList
numberLevelList,
linearGradientDirList
}

View File

@@ -500,6 +500,10 @@ export const langList = [
value: 'zh',
name: '简体中文'
},
{
value: 'zhtw',
name: '繁體中文'
},
{
value: 'en',
name: 'English'
@@ -589,6 +593,18 @@ export const downTypeList = [
type: 'txt',
icon: 'iconTXT',
desc: '纯文本文件'
},
{
name: 'FreeMind',
type: 'mm',
icon: 'iconfreemind',
desc: 'FreeMind软件格式'
},
{
name: 'Excel',
type: 'xlsx',
icon: 'iconfile-excel',
desc: 'Excel软件格式'
}
]
@@ -651,3 +667,55 @@ export const numberLevelList = [
value: 0
}
]
// 背景渐变方向
export const linearGradientDirList = [
{
name: '从左到右',
value: '1',
start: [0, 0],
end: [1, 0]
},
{
name: '从右到左',
value: '2',
start: [1, 0],
end: [0, 0]
},
{
name: '从上到下',
value: '3',
start: [0, 0],
end: [0, 1]
},
{
name: '从下到上',
value: '4',
start: [0, 1],
end: [0, 0]
},
{
name: '从左上到右下',
value: '5',
start: [0, 0],
end: [1, 1]
},
{
name: '从左下到右上',
value: '6',
start: [0, 1],
end: [1, 0]
},
{
name: '从右上到左下',
value: '7',
start: [1, 0],
end: [0, 1]
},
{
name: '从右下到左上',
value: '8',
start: [1, 1],
end: [0, 0]
}
]

623
web/src/config/zhtw.js Normal file
View File

@@ -0,0 +1,623 @@
// 字型列表
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 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'
},
{
name: '虛線 7',
value: '6, 4'
}
]
// 連線樣式
export const lineStyleList = [
{
name: '直線',
value: 'straight'
},
{
name: '曲線',
value: 'curve'
},
{
name: '直接連線',
value: 'direct'
}
]
// 曲線樣式中,根節點樣式是否和其他節點保持一致
export const rootLineKeepSameInCurveList = [
{
name: '括號',
value: false
},
{
name: '大括號',
value: true
}
]
// 圖片重複方式
export const backgroundRepeatList = [
{
name: '不重複',
value: 'no-repeat'
},
{
name: '重複',
value: 'repeat'
},
{
name: '水平重複',
value: 'repeat-x'
},
{
name: '垂直重複',
value: 'repeat-y'
}
]
// 背景圖片位置
export const backgroundPositionList = [
{
name: '預設',
value: '0% 0%'
},
{
name: '左上',
value: 'left top'
},
{
name: '左中',
value: 'left center'
},
{
name: '左下',
value: 'left bottom'
},
{
name: '右上',
value: 'right top'
},
{
name: '右中',
value: 'right center'
},
{
name: '右下',
value: 'right bottom'
},
{
name: '中上',
value: 'center top'
},
{
name: '置中',
value: 'center center'
},
{
name: '中下',
value: 'center bottom'
}
]
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
const ctrl = isMac ? '⌘' : 'Ctrl'
const enter = isMac ? 'Return' : 'Enter'
const macFn = isMac ? 'fn + ' : ''
// 背景圖片大小
export const backgroundSizeList = [
{
name: '自動',
value: 'auto'
},
{
name: '覆蓋',
value: 'cover'
},
{
name: '包含',
value: 'contain'
}
]
// 快捷鍵列表
export const shortcutKeyList = [
{
type: '節點操作',
list: [
{
icon: 'icontianjiazijiedian',
name: '插入子節點',
value: 'Tab | Insert'
},
{
icon: 'iconjiedian',
name: '插入同層節點',
value: enter
},
{
icon: 'icondodeparent',
name: '插入父節點',
value: 'Shift + Tab'
},
{
icon: 'iconshangyi',
name: '上移節點',
value: `${ctrl} + ↑`
},
{
icon: 'iconxiayi',
name: '下移節點',
value: `${ctrl} + ↓`
},
{
icon: 'icongaikuozonglan',
name: '插入摘要',
value: `${ctrl} + G`
},
{
icon: 'iconzhankai',
name: '展開/收合節點',
value: '/'
},
{
icon: 'iconshanchu',
name: '刪除節點',
value: 'Delete | Backspace'
},
{
icon: 'iconshanchu',
name: '僅刪除目前節點',
value: 'Shift + Backspace'
},
{
icon: 'iconfuzhi',
name: '複製節點',
value: `${ctrl} + C`
},
{
icon: 'iconjianqie',
name: '剪下節點',
value: `${ctrl} + X`
},
{
icon: 'iconniantie',
name: '貼上節點',
value: `${ctrl} + V`
},
{
icon: 'iconbianji',
name: '編輯節點',
value: macFn + 'F2'
},
{
icon: 'iconhuanhang',
name: '文字換行',
value: `Shift + ${enter}`
},
{
icon: 'iconhoutui-shi',
name: '復原',
value: `${ctrl} + Z`
},
{
icon: 'iconqianjin1',
name: '重做',
value: `${ctrl} + Y`
},
{
icon: 'iconquanxuan',
name: '全選',
value: `${ctrl} + A`
},
{
icon: 'iconquanxuan',
name: '多重選擇',
value: `右鍵 / ${ctrl} + 左鍵`
},
{
icon: 'iconzhengli',
name: '一鍵整理版面配置',
value: `${ctrl} + L`
},
{
icon: 'iconsousuo',
name: '搜尋與取代',
value: `${ctrl} + F`
}
]
},
{
type: '畫布操作',
list: [
{
icon: 'iconfangda',
name: '放大',
value: `${ctrl} + +`
},
{
icon: 'iconsuoxiao',
name: '縮小',
value: `${ctrl} + -`
},
{
icon: 'iconfangda',
name: '放大/縮小',
value: `${ctrl} + 滑鼠滾輪`
},
{
icon: 'icondingwei',
name: '回到根節點',
value: `${ctrl} + ${enter}`
},
{
icon: 'iconquanping1',
name: '適應畫布',
value: `${ctrl} + i`
}
]
},
{
type: '大綱操作',
list: [
{
icon: 'iconhuanhang',
name: '文字換行',
value: `Shift + ${enter}`
},
{
icon: 'iconshanchu',
name: '刪除節點',
value: 'Delete'
},
{
icon: 'icontianjiazijiedian',
name: '插入子節點',
value: 'Tab'
},
{
icon: 'iconjiedian',
name: '插入同層節點',
value: enter
},
{
icon: 'icondodeparent',
name: '上移一層',
value: 'Shift + Tab'
}
]
}
]
// 形狀列表
export const shapeList = [
{
name: '矩形',
value: 'rectangle'
},
{
name: '菱形',
value: 'diamond'
},
{
name: '平行四邊形',
value: 'parallelogram'
},
{
name: '圓角矩形',
value: 'roundedRectangle'
},
{
name: '八角矩形',
value: 'octagonalRectangle'
},
{
name: '外三角矩形',
value: 'outerTriangularRectangle'
},
{
name: '內三角矩形',
value: 'innerTriangularRectangle'
},
{
name: '橢圓形',
value: 'ellipse'
},
{
name: '圓形',
value: 'circle'
}
]
// 側邊欄列表
export const sidebarTriggerList = [
{
name: '節點樣式',
value: 'nodeStyle',
icon: 'iconzhuti'
},
{
name: '基礎樣式',
value: 'baseStyle',
icon: 'iconyangshi'
},
{
name: '主題',
value: 'theme',
icon: 'iconjingzi'
},
{
name: '結構',
value: 'structure',
icon: 'iconjiegou'
},
{
name: '大綱',
value: 'outline',
icon: 'iconfuhao-dagangshu'
},
{
name: '快捷鍵',
value: 'shortcutKey',
icon: 'iconjianpan'
}
]
// 下載類型列表
export const downTypeList = [
{
name: '專用檔案',
type: 'smm',
icon: 'iconwenjian',
desc: '可用於匯入'
},
{
name: 'JSON',
type: 'json',
icon: 'iconjson',
desc: '常見的資料交換格式,可用於匯入'
},
{
name: '圖片',
type: 'png',
icon: 'iconPNG',
desc: '適合檢視與分享'
},
{
name: 'SVG',
type: 'svg',
icon: 'iconSVG',
desc: '可縮放向量圖形'
},
{
name: 'PDF',
type: 'pdf',
icon: 'iconpdf',
desc: '適合列印'
},
{
name: 'Markdown',
type: 'md',
icon: 'iconmarkdown',
desc: '方便其他軟體開啟'
},
{
name: 'XMind',
type: 'xmind',
icon: 'iconxmind',
desc: 'XMind 檔案'
},
{
name: 'Txt',
type: 'txt',
icon: 'iconTXT',
desc: '純文字檔案'
},
{
name: 'FreeMind',
type: 'mm',
icon: 'iconfreemind',
desc: 'FreeMind軟體格式'
},
{
name: 'Excel',
type: 'xlsx',
icon: 'iconfile-excel',
desc: 'Excel軟體格式'
}
]
// 編號類型列表
export const numberTypeList = [
{
name: '無編號',
value: ''
},
{
name: '1, 2, 3',
value: 1
},
{
name: '1., 2., 3.',
value: 2
},
{
name: '(1), (2), (3)',
value: 3
},
{
name: 'a., b., c.',
value: 4
},
{
name: 'A., B., C.',
value: 5
},
{
name: 'i., ii., iii.',
value: 6
},
{
name: 'I., II., III.',
value: 7
},
{
name: '一、, 二、, 三、',
value: 8
}
]
// 編號層級列表
export const numberLevelList = [
{
name: '編號第一層',
value: 1
},
{
name: '編號前兩層',
value: 2
},
{
name: '編號前三層',
value: 3
},
{
name: '編號每一層',
value: 0
}
]
// 背景渐变方向
export const linearGradientDirList = [
{
name: '从左到右',
value: '1',
start: [0, 0],
end: [1, 0]
},
{
name: '从右到左',
value: '2',
start: [1, 0],
end: [0, 0]
},
{
name: '从上到下',
value: '3',
start: [0, 0],
end: [0, 1]
},
{
name: '从下到上',
value: '4',
start: [0, 1],
end: [0, 0]
},
{
name: '从左上到右下',
value: '5',
start: [0, 0],
end: [1, 1]
},
{
name: '从左下到右上',
value: '6',
start: [0, 1],
end: [1, 0]
},
{
name: '从右上到左下',
value: '7',
start: [1, 0],
end: [0, 1]
},
{
name: '从右下到左上',
value: '8',
start: [1, 1],
end: [0, 0]
}
]

View File

@@ -111,7 +111,8 @@ export default {
copyToPng: 'Png',
copySuccess: 'Copy success',
copyFail: 'Copy fail',
number: 'Number child nodes'
number: 'Number child nodes',
expandNodeChild: 'Expand all sub nodes'
},
count: {
words: 'Words',
@@ -157,8 +158,9 @@ export default {
import: {
title: 'Import',
selectFile: 'Select file',
supportFile: 'Support .smm、.json、.xmind、.xlsx、.md file',
enableFileTip: 'Please select .smm、.json、.xmind、.xlsx、.md file',
support: 'Support',
file: 'file',
pleaseSelect: 'Please select',
maxFileNum: 'At most one file can be selected',
notSelectTip: 'Please select the file to import',
fileContentError: 'The file content is incorrect',
@@ -238,7 +240,8 @@ export default {
endColor: 'End',
arrowDir: 'Arrow dir',
arrowDirStart: 'Start',
arrowDirEnd: 'End'
arrowDirEnd: 'End',
direction: 'Direction'
},
theme: {
title: 'Theme',
@@ -305,7 +308,8 @@ export default {
yes: 'Yes',
no: 'No',
exportError: 'Export failed',
dragTip: 'Release here to import the file'
dragTip: 'Release here to import the file',
deleteNodeImgTip: 'Are you sure to delete the node image?'
},
mouseAction: {
tip1:

View File

@@ -1,7 +1,9 @@
import en from './en_us'
import zh from './zh_cn'
import zhtw from './zh_tw'
export default {
zh,
zhtw,
en
}

View File

@@ -111,7 +111,8 @@ export default {
copyToPng: '图片',
copySuccess: '复制成功',
copyFail: '复制失败',
number: '编号其子节点'
number: '编号其子节点',
expandNodeChild: '展开所有下级节点'
},
count: {
words: '字数',
@@ -155,8 +156,9 @@ export default {
import: {
title: '导入',
selectFile: '选取文件',
supportFile: '支持.smm、.json、.xmind、.xlsx、.md文件',
enableFileTip: '请选择.smm、.json、.xmind、.xlsx、.md文件',
support: '支持',
file: '文件',
pleaseSelect: '请选择',
maxFileNum: '最多只能选择一个文件',
notSelectTip: '请选择要导入的文件',
fileContentError: '文件内容有误',
@@ -236,7 +238,8 @@ export default {
endColor: '结束',
arrowDir: '箭头位置',
arrowDirStart: '头部',
arrowDirEnd: '尾部'
arrowDirEnd: '尾部',
direction: '方向'
},
theme: {
title: '主题',
@@ -299,7 +302,8 @@ export default {
yes: '是',
no: '否',
exportError: '导出失败',
dragTip: '在此释放以导入该文件'
dragTip: '在此释放以导入该文件',
deleteNodeImgTip: '是否确认删除该节点图片?'
},
mouseAction: {
tip1: '当前:左键拖动画布,右键框选节点',

379
web/src/lang/zh_tw.js Normal file
View File

@@ -0,0 +1,379 @@
export default {
baseStyle: {
title: '基本樣式',
background: '背景',
color: '顏色',
image: '圖片',
imageRepeat: '圖片重複',
imagePosition: '圖片位置',
imageSize: '圖片大小',
line: '連線',
width: '寬度',
style: '樣式',
lineRadius: '圓角半徑',
lineOfOutline: '概要連線',
showArrow: '顯示箭頭',
nodePadding: '節點內距',
nodeMargin: '節點外距',
horizontal: '水平',
vertical: '垂直',
maximumWidth: '最大寬度',
maximumHeight: '最大高度',
icon: '圖示',
size: '大小',
level2Node: '第二層節點',
belowLevel2Node: '第三層及以下節點',
nodeBorderType: '節點邊框樣式',
nodeUseLineStyle: '僅使用底邊框樣式',
otherConfig: '其他設定',
enableFreeDrag: '啟用節點自由拖曳 (Beta)',
openPerformance: '啟用效能模式',
watermark: '浮水印',
showWatermark: '顯示浮水印',
onlyExport: '僅在匯出時顯示',
watermarkDefaultText: '浮水印文字',
watermarkText: '浮水印文字',
watermarkTextColor: '文字顏色',
watermarkLineSpacing: '行距',
watermarkTextSpacing: '文字間距',
watermarkAngle: '旋轉角度',
watermarkTextOpacity: '文字透明度',
watermarkTextFontSize: '字型大小',
belowNode: '顯示在節點下方',
isEnableNodeRichText: '啟用節點豐富文字編輯',
mousewheelAction: '滑鼠滾輪行為',
zoomView: '縮放檢視',
moveViewUpDown: '上下移動檢視',
associativeLine: '關聯線',
associativeLineWidth: '寬度',
associativeLineColor: '顏色',
associativeLineActiveWidth: '啟用時寬度',
associativeLineActiveColor: '啟用時顏色',
mousewheelZoomActionReverse: '滑鼠滾輪縮放',
mousewheelZoomActionReverse1: '向前縮小,向後放大',
mousewheelZoomActionReverse2: '向前放大,向後縮小',
createNewNodeBehavior: '建立新節點行為',
default: '啟用新節點並進入編輯',
notActive: '不啟用新節點',
activeOnly: '僅啟用新節點,不進入編輯',
rootStyle: '根節點',
associativeLineText: '關聯線文字',
fontFamily: '字型',
fontSize: '字型大小',
isShowScrollbar: '顯示捲軸',
isUseHandDrawnLikeStyle: '使用手繪風格',
rootLineStartPos: '根節點連線起始位置',
center: '中心',
edge: '邊緣',
rainbowLines: '彩虹線條',
notUseRainbowLines: '不使用彩虹線條',
outerFramePadding: '外框內距'
},
color: {
moreColor: '更多顏色'
},
contextmenu: {
insertSiblingNode: '插入同層節點',
insertChildNode: '插入子節點',
insertParentNode: '插入父節點',
insertSummary: '插入概要',
moveUpNode: '上移節點',
moveDownNode: '下移節點',
deleteNode: '刪除節點',
deleteCurrentNode: '僅刪除目前節點',
copyNode: '複製節點',
cutNode: '剪下節點',
pasteNode: '貼上節點',
backCenter: '回到根節點',
expandAll: '展開全部',
unExpandAll: '收合全部',
expandTo: '展開至',
arrangeLayout: '一鍵整理版面',
level1: '第一層主題',
level2: '第二層主題',
level3: '第三層主題',
level4: '第四層主題',
level5: '第五層主題',
level6: '第六層主題',
zenMode: '禪模式',
fitCanvas: '適應畫布',
removeImage: '移除圖片',
removeHyperlink: '移除超連結',
removeNote: '移除備註',
removeCustomStyles: '一鍵移除自訂樣式',
removeAllNodeCustomStyles: '一鍵移除所有節點自訂樣式',
exportNodeToPng: '匯出此節點為圖片',
copyToClipboard: '複製到剪貼簿',
copyToSmm: 'SMM',
copyToJson: 'JSON',
copyToMarkdown: 'Markdown',
copyToTxt: 'Txt',
copyToPng: '圖片',
copySuccess: '複製成功',
copyFail: '複製失敗',
number: '將其子節點編號',
expandNodeChild: '展開所有下級節點'
},
count: {
words: '字數',
nodes: '節點數'
},
dialog: {
cancel: '取消',
confirm: '確定'
},
export: {
title: '匯出',
filename: '檔案名稱',
include: '包含主題、結構等設定資料',
dedicatedFile: '專用檔案',
jsonFile: 'JSON 檔案',
imageFile: '圖片檔案',
svgFile: 'SVG 檔案',
pdfFile: 'PDF 檔案',
markdownFile: 'Markdown 檔案',
tips: '提示:.smm 和 .json 檔案可以匯入',
isTransparent: '背景透明',
pngTips: '提示:在豐富文字模式下匯出圖片非常耗時,建議匯出為 SVG 格式',
svgTips: '提示:在豐富文字模式下匯出圖片非常耗時',
transformingDomToImages: '正在轉換節點:',
notifyTitle: '訊息',
notifyMessage: '如果沒有觸發下載,請檢查是否被瀏覽器封鎖',
paddingX: '水平內距',
paddingY: '垂直內距',
useMultiPageExport: '多頁匯出',
defaultFileName: '心智圖',
addFooterText: '在底部新增文字',
addFooterTextPlaceholder: '例如:來自 simple-mind-map'
},
fullscreen: {
fullscreenShow: '全螢幕檢視',
fullscreenEdit: '全螢幕編輯'
},
demonstrate: {
demonstrate: '進入展示模式'
},
import: {
title: '匯入',
selectFile: '選擇檔案',
support: '支援',
file: '檔案',
pleaseSelect: '請選擇',
maxFileNum: '最多只能選擇一個檔案',
notSelectTip: '請選擇要匯入的檔案',
fileContentError: '檔案內容有誤',
importSuccess: '匯入成功',
fileParsingFailed: '檔案解析失敗',
xmindCanvasSelectDialogTitle: '選擇要匯入的畫布'
},
navigatorToolbar: {
openMiniMap: '開啟小地圖',
closeMiniMap: '關閉小地圖',
readonly: '切換為唯讀模式',
edit: '切換為編輯模式',
backToRoot: '回到根節點',
changeSourceCodeEdit: '切換為原始碼編輯模式'
},
nodeHyperlink: {
title: '超連結',
link: '連結',
name: '名稱'
},
nodeIcon: {
title: '圖示'
},
nodeImage: {
title: '圖片',
imgTitle: '圖片標題'
},
nodeNote: {
title: '備註'
},
nodeTag: {
title: '標籤',
addTip: '請按 Enter 鍵新增'
},
outline: {
title: '大綱',
nodeDefaultText: '分支節點'
},
scale: {
zoomIn: '放大',
zoomOut: '縮小'
},
shortcutKey: {
title: '快速鍵'
},
strusture: {
title: '結構'
},
style: {
title: '節點樣式',
normal: '常態',
active: '選取狀態',
text: '文字',
fontFamily: '字型',
fontSize: '字型大小',
lineHeight: '行高',
color: '顏色',
addFontWeight: '粗體',
italic: '斜體',
textDecoration: '文字裝飾',
none: '無',
underline: '底線',
lineThrough: '刪除線',
overline: '上劃線',
border: '邊框',
style: '樣式',
width: '寬度',
borderRadius: '圓角',
background: '背景',
shape: '形狀',
line: '線條',
nodePadding: '節點內距',
horizontal: '水平',
vertical: '垂直',
gradientStyle: '漸層',
startColor: '起始',
endColor: '結束',
arrowDir: '箭頭位置',
arrowDirStart: '頭部',
arrowDirEnd: '尾部'
},
theme: {
title: '主題',
classics: '經典',
dark: '深色',
simple: '簡約',
coverTip: '您目前已自訂過基本樣式,是否要覆蓋?',
tip: '提示',
cover: '覆蓋',
reserve: '保留'
},
toolbar: {
undo: '復原',
redo: '重做',
insertSiblingNode: '同層節點',
insertChildNode: '子節點',
deleteNode: '刪除節點',
image: '圖片',
icon: '圖示',
link: '超連結',
note: '備註',
tag: '標籤',
summary: '摘要',
displayOutline: '顯示大綱',
baseStyle: '基本樣式',
theme: '主題',
strusture: '結構',
newFile: '新增檔案',
openFile: '開啟檔案',
saveAs: '另存新檔',
import: '匯入',
export: '匯出',
shortcutKey: '快速鍵',
associativeLine: '關聯線',
painter: '格式刷',
formula: '公式',
attachment: '附件',
outerFrame: '外框',
more: '更多',
selectFileTip: '請選擇檔案',
notSupportTip: '您的瀏覽器不支援此功能,或者目前頁面非 HTTPS 協定',
tip: '提示',
editingLocalFileTipFront: '目前正在編輯您電腦上的【',
editingLocalFileTipEnd: '】檔案',
fileContentError: '檔案內容有誤',
fileOpenFailed: '檔案開啟失敗',
defaultFileName: '心智圖',
creatingTip: '正在建立檔案',
directory: '目錄',
newFileTip: '新增檔案前,請先匯出目前編輯的檔案,以免內容遺失',
openFileTip: '開啟檔案前,請先匯出目前編輯的檔案,以免內容遺失'
},
edit: {
newFeatureNoticeTitle: '新功能提醒',
newFeatureNoticeMessage:
'本次更新支援了節點豐富文字編輯,但存在一些缺陷,最主要的影響是匯出為圖片的時間與節點數量成正比,所以如果比較依賴匯出功能,可以透過【基本樣式】-【其他設定】-【是否啟用節點豐富文字編輯】設定關閉豐富文字編輯模式。',
root: '根節點',
splitByWrap: '是否根據換行自動分割節點?',
tip: '提示',
yes: '是',
no: '否',
exportError: '匯出失敗',
dragTip: '在此釋放以匯入檔案'
},
mouseAction: {
tip1: '目前:左鍵拖曳畫布,右鍵框選節點',
tip2: '目前:左鍵框選節點,右鍵拖曳畫布'
},
search: {
searchPlaceholder: '請輸入搜尋內容',
replacePlaceholder: '請輸入取代內容',
replace: '取代',
replaceAll: '全部取代',
cancel: '取消',
noResult: '查無結果'
},
nodeIconSidebar: {
title: '圖示/貼圖',
icon: '圖示',
sticker: '貼圖'
},
formulaSidebar: {
title: '公式',
placeholder: '請輸入 LaTeX 語法',
confirm: '完成',
common: '常用公式',
tip: '僅在豐富文字模式下支援插入公式'
},
richTextToolbar: {
bold: '粗體',
italic: '斜體',
underline: '底線',
strike: '刪除線',
fontFamily: '字型',
fontSize: '字型大小',
color: '字型顏色',
backgroundColor: '背景顏色',
removeFormat: '清除樣式'
},
other: {
loading: '載入中,請稍候...'
},
sourceCodeEdit: {
sourceCodeTip:
'不建議在豐富文字模式下修改樣式,因為需要同步修改資料和 HTML 結構。',
format: '格式化',
copy: '複製',
confirm: '完成',
close: '關閉',
formatErrorTip: 'JSON 格式錯誤,請檢查後重試',
copyTip: '已複製到剪貼簿',
formatTip: '格式化完成'
},
attachment: {
deleteAttachment: '刪除附件',
tip: '附件功能僅在用戶端可用'
},
annotation: {
mark: '標記',
show: '顯示標記',
type: '類型',
color: '顏色',
lineWidth: '線寬',
padding: '內距',
animate: '動畫'
},
nodeOuterFrame: {
outerFrameSetting: '外框設定',
deleteOuterFrame: '刪除外框',
boxStyle: '邊框樣式',
boxColor: '邊框顏色',
fillColor: '填充顏色'
},
nodeTagStyle: {
placeholder: '請輸入標籤內容',
delete: '刪除此標籤'
}
}

View File

@@ -56,6 +56,9 @@
<span class="name">{{ $t('contextmenu.moveDownNode') }}</span>
<span class="desc">Ctrl + </span>
</div>
<div class="item" @click="exec('EXPAND_ALL')">
<span class="name">{{ $t('contextmenu.expandNodeChild') }}</span>
</div>
<div class="item" v-if="supportNumbers">
<span class="name">{{ $t('contextmenu.number') }}</span>
<span class="el-icon-arrow-right"></span>
@@ -344,12 +347,11 @@ export default {
// 计算右键菜单元素的显示位置
getShowPosition(x, y) {
this.subItemsShowLeft = false
const rect = this.$refs.contextmenuRef.getBoundingClientRect()
if (x + rect.width > window.innerWidth) {
x = x - rect.width - 20
this.subItemsShowLeft = true
}
this.subItemsShowLeft = x + rect.width + 150 > window.innerWidth
if (y + rect.height > window.innerHeight) {
y = window.innerHeight - rect.height - 10
}
@@ -462,6 +464,9 @@ export default {
this.node
)
break
case 'EXPAND_ALL':
this.$bus.$emit('execCommand', key, this.node.uid)
break
default:
this.$bus.$emit('execCommand', key, ...args)
break

View File

@@ -79,6 +79,11 @@ import OuterFrame from 'simple-mind-map/src/plugins/OuterFrame.js'
// import Notation from 'simple-mind-map-plugin-notation'
// 编号插件,该插件为付费插件,详情请查看开发文档
// import Numbers from 'simple-mind-map-plugin-numbers'
// Freemind软件格式导入导出插件该插件为付费插件详情请查看开发文档
// import Freemind from 'simple-mind-map-plugin-freemind'
// Excel软件格式导入导出插件该插件为付费插件详情请查看开发文档
// import Excel from 'simple-mind-map-plugin-excel'
// npm link simple-mind-map-plugin-excel simple-mind-map-plugin-freemind simple-mind-map-plugin-numbers simple-mind-map-plugin-notation simple-mind-map-plugin-handdrawnlikestyle simple-mind-map
import OutlineSidebar from './OutlineSidebar'
import Style from './Style'
import BaseStyle from './BaseStyle'
@@ -418,6 +423,25 @@ export default {
},
expandBtnNumHandler: num => {
return num >= 100 ? '…' : num
},
beforeDeleteNodeImg: node => {
return new Promise(resolve => {
this.$confirm(
this.$t('edit.deleteNodeImgTip'),
this.$t('edit.tip'),
{
confirmButtonText: this.$t('edit.yes'),
cancelButtonText: this.$t('edit.no'),
type: 'warning'
}
)
.then(() => {
resolve(false)
})
.catch(() => {
resolve(true)
})
})
}
// createNodePrefixContent: (node) => {
// const el = document.createElement('div')
@@ -544,6 +568,16 @@ export default {
this.mindMap.addPlugin(Numbers)
this.$store.commit('setSupportNumbers', true)
}
if (typeof Freemind !== 'undefined') {
this.mindMap.addPlugin(Freemind)
this.$store.commit('setSupportFreemind', true)
Vue.prototype.Freemind = Freemind
}
if (typeof Excel !== 'undefined') {
this.mindMap.addPlugin(Excel)
this.$store.commit('setSupportExcel', true)
Vue.prototype.Excel = Excel
}
this.mindMap.keyCommand.addShortcut('Control+s', () => {
this.manualSave()
})

View File

@@ -8,7 +8,7 @@
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
:width="isMobile ? '90%' : '50%'"
:top="isMobile? '20px' : '15vh'"
:top="isMobile ? '20px' : '15vh'"
>
<div class="exportContainer" :class="{ isDark: isDark }">
<div class="nameInputBox">
@@ -98,12 +98,14 @@
import { mapState, mapMutations } from 'vuex'
import { downTypeList } from '@/config'
import { isMobile } from 'simple-mind-map/src/utils/index'
import MarkdownIt from 'markdown-it'
/**
* @Author: 王林
* @Date: 2021-06-24 22:53:54
* @Desc: 导出
*/
let md = null
export default {
name: 'Export',
data() {
@@ -124,11 +126,23 @@ export default {
computed: {
...mapState({
openNodeRichText: state => state.localConfig.openNodeRichText,
isDark: state => state.localConfig.isDark
isDark: state => state.localConfig.isDark,
supportFreemind: state => state.supportFreemind,
supportExcel: state => state.supportExcel
}),
downTypeList() {
return downTypeList[this.$i18n.locale] || downTypeList.zh
const list = downTypeList[this.$i18n.locale] || downTypeList.zh
return list.filter(item => {
if (item.type === 'mm') {
return this.supportFreemind
}
if (item.type === 'xlsx') {
return this.supportExcel
} else {
return true
}
})
}
},
created() {
@@ -203,6 +217,22 @@ export default {
this.fileName,
this.isTransparent
)
} else if (this.exportType === 'mm') {
this.$bus.$emit('export', this.exportType, true, this.fileName, {
transformNote: note => {
if (!md) {
md = new MarkdownIt()
}
return md.render(note)
},
transformImage: img => {
if (/^https?:\/\//.test(img)) {
return img
} else {
return ''
}
}
})
} else {
this.$bus.$emit('export', this.exportType, true, this.fileName)
}

View File

@@ -9,7 +9,7 @@
<el-upload
ref="upload"
action="x"
accept=".smm,.json,.xmind,.xlsx,.md"
:accept="supportFileStr"
:file-list="fileList"
:auto-upload="false"
:multiple="false"
@@ -22,7 +22,7 @@
$t('import.selectFile')
}}</el-button>
<div slot="tip" class="el-upload__tip">
{{ $t('import.supportFile') }}
{{ $t('import.support') }}{{ supportFileStr }}{{ $t('import.file') }}
</div>
</el-upload>
<span slot="footer" class="dialog-footer">
@@ -40,9 +40,12 @@
:show-close="false"
>
<el-radio-group v-model="selectCanvas" class="canvasList">
<el-radio v-for="(item, index) in canvasList" :key="index" :label="index">{{
item.title
}}</el-radio>
<el-radio
v-for="(item, index) in canvasList"
:key="index"
:label="index"
>{{ item.title }}</el-radio
>
</el-radio-group>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="confirmSelect">{{
@@ -56,9 +59,8 @@
<script>
import xmind from 'simple-mind-map/src/parse/xmind.js'
import markdown from 'simple-mind-map/src/parse/markdown.js'
import { fileToBuffer } from '@/utils'
import { read, utils } from 'xlsx'
import { mapMutations } from 'vuex'
import { mapMutations, mapState } from 'vuex'
import Vue from 'vue'
/**
* @Author: 王林
@@ -77,6 +79,22 @@ export default {
canvasList: []
}
},
computed: {
...mapState({
supportFreemind: state => state.supportFreemind,
supportExcel: state => state.supportExcel
}),
supportFileStr() {
let res = '.smm,.json,.xmind,.md'
if (this.supportFreemind) {
res += ',.mm'
}
if (this.supportExcel) {
res += ',.xlsx'
}
return res
}
},
watch: {
dialogVisible(val, oldVal) {
if (!val && oldVal) {
@@ -101,12 +119,20 @@ export default {
this.dialogVisible = true
},
getRegexp() {
return new RegExp(
`\.(smm|json|xmind|md${this.supportFreemind ? '|mm' : ''}${
this.supportExcel ? '|xlsx' : ''
})$`
)
},
// 检查url中是否操作需要打开的文件
async handleFileURL() {
try {
const fileURL = this.$route.query.fileURL
if (!fileURL) return
const macth = /\.(smm|json|xmind|md|xlsx)$/.exec(fileURL)
const macth = this.getRegexp().exec(fileURL)
if (!macth) {
return
}
@@ -124,6 +150,8 @@ export default {
this.handleExcel(data)
} else if (type === 'md') {
this.handleMd(data)
} else if (type === 'mm') {
this.handleMm(data)
}
} catch (error) {
console.log(error)
@@ -132,9 +160,12 @@ export default {
// 文件选择
onChange(file) {
let reg = /\.(smm|xmind|json|xlsx|md)$/
if (!reg.test(file.name)) {
this.$message.error(this.$t('import.enableFileTip'))
if (!this.getRegexp().test(file.name)) {
this.$message.error(
this.$t('import.pleaseSelect') +
this.supportFileStr +
this.$t('import.file')
)
this.fileList = []
} else {
this.fileList.push(file)
@@ -171,6 +202,8 @@ export default {
this.handleExcel(file)
} else if (/\.md$/.test(file.name)) {
this.handleMd(file)
} else if (/\.mm$/.test(file.name)) {
this.handleMm(file)
}
this.cancel()
this.setActiveSidebar(null)
@@ -212,6 +245,36 @@ export default {
}
},
// 处理Freemind格式
handleMm(file) {
const fileReader = new FileReader()
fileReader.readAsText(file.raw)
fileReader.onload = async evt => {
try {
const data = await Vue.prototype.Freemind.freemindToSmm(
evt.target.result,
{
// withStyle: true,
transformImg: image => {
return new Promise(resolve => {
if (/^https?:\/\//.test(image)) {
resolve({ url: image })
} else {
resolve(null)
}
})
}
}
)
this.$bus.$emit('setData', data)
this.$message.success(this.$t('import.importSuccess'))
} catch (error) {
console.log(error)
this.$message.error(this.$t('import.fileParsingFailed'))
}
}
},
// 显示xmind文件的多个画布选择弹窗
showSelectXmindCanvasDialog(content) {
this.canvasList = content
@@ -230,59 +293,8 @@ export default {
// 处理.xlsx文件
async handleExcel(file) {
try {
const wb = read(await fileToBuffer(file.raw))
const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], {
header: 1
})
if (data.length <= 0) {
return
}
let max = 0
data.forEach(arr => {
if (arr.length > max) {
max = arr.length
}
})
let layers = []
let walk = layer => {
if (!layers[layer]) {
layers[layer] = []
}
for (let i = 0; i < data.length; i++) {
if (data[i][layer]) {
let node = {
data: {
text: data[i][layer]
},
children: [],
_row: i
}
layers[layer].push(node)
}
}
if (layer < max - 1) {
walk(layer + 1)
}
}
walk(0)
let getParent = (arr, row) => {
for (let i = arr.length - 1; i >= 0; i--) {
if (row >= arr[i]._row) {
return arr[i]
}
}
}
for (let i = 1; i < layers.length; i++) {
let arr = layers[i]
for (let j = 0; j < arr.length; j++) {
let item = arr[j]
let parent = getParent(layers[i - 1], item._row)
if (parent) {
parent.children.push(item)
}
}
}
this.$bus.$emit('setData', layers[0][0])
const res = await Vue.prototype.Excel.excelTo(file.raw)
this.$bus.$emit('setData', res)
this.$message.success(this.$t('import.importSuccess'))
} catch (error) {
console.log(error)
@@ -296,7 +308,7 @@ export default {
fileReader.readAsText(file.raw)
fileReader.onload = async evt => {
try {
let data = await markdown.transformMarkdownTo(evt.target.result)
let data = markdown.transformMarkdownTo(evt.target.result)
this.$bus.$emit('setData', data)
this.$message.success(this.$t('import.importSuccess'))
} catch (error) {

View File

@@ -124,8 +124,8 @@ export default {
node.setImage({
url: img || 'none',
title: this.imgTitle,
width: res.width,
height: res.height
width: res.width || 100,
height: res.height || 100
})
})
this.cancel()

View File

@@ -10,6 +10,7 @@
</el-tooltip>
<div class="scaleInfo">
<input
ref="inputRef"
type="text"
v-model="scaleNum"
@input="onScaleNumInput"
@@ -55,13 +56,16 @@ export default {
watch: {
mindMap(val, oldVal) {
if (val && !oldVal) {
this.mindMap.on('scale', scale => {
this.scaleNum = this.toPer(scale)
})
this.mindMap.on('scale', this.onScale)
this.mindMap.on('draw_click', this.onDrawClick)
this.scaleNum = this.toPer(this.mindMap.view.scale)
}
}
},
beforeDestroy() {
this.mindMap.off('scale', this.onScale)
this.mindMap.off('draw_click', this.onDrawClick)
},
methods: {
// 转换成百分数
toPer(scale) {
@@ -98,6 +102,14 @@ export default {
const cy = this.mindMap.height / 2
this.mindMap.view.setScale(this.scaleNum / 100, cx, cy)
}
},
onScale(scale) {
this.scaleNum = this.toPer(scale)
},
onDrawClick() {
if (this.$refs.inputRef) this.$refs.inputRef.blur()
}
}
}

View File

@@ -247,16 +247,16 @@
<el-popover ref="popover4" placement="bottom" trigger="hover">
<Color :color="style.fillColor" @change="changeFillColor"></Color>
</el-popover>
</div>
</div>
<div class="row">
<div class="rowItem">
<span class="name">{{ $t('style.gradientStyle') }}</span>
<span class="name" style="margin-left: 20px;">{{
$t('style.gradientStyle')
}}</span>
<el-checkbox
v-model="style.gradientStyle"
@change="update('gradientStyle')"
></el-checkbox>
</div>
</div>
<div class="row" v-if="style.gradientStyle">
<div class="rowItem">
<span class="name">{{ $t('style.startColor') }}</span>
<span
@@ -282,6 +282,24 @@
<Color :color="style.endColor" @change="changeEndColor"></Color>
</el-popover>
</div>
<div class="rowItem">
<span class="name">{{ $t('style.direction') }}</span>
<el-select
size="mini"
style="width: 80px"
v-model="style.linearGradientDir"
placeholder=""
@change="update('linearGradientDir')"
>
<el-option
v-for="item in linearGradientDirList"
:key="item.value"
:label="item.name"
:value="item.value"
>
</el-option>
</el-select>
</div>
</div>
<!-- 形状 -->
<div class="title">{{ $t('style.shape') }}</div>
@@ -458,7 +476,8 @@ import {
borderRadiusList,
lineHeightList,
shapeList,
shapeListMap
shapeListMap,
linearGradientDirList
} from '@/config'
import { mapState } from 'vuex'
@@ -502,7 +521,8 @@ export default {
lineMarkerDir: '',
gradientStyle: false,
startColor: '',
endColor: ''
endColor: '',
linearGradientDir: ''
}
}
},
@@ -522,6 +542,11 @@ export default {
},
shapeListMap() {
return shapeListMap[this.$i18n.locale] || shapeListMap.zh
},
linearGradientDirList() {
return (
linearGradientDirList[this.$i18n.locale] || linearGradientDirList.zh
)
}
},
watch: {
@@ -587,6 +612,24 @@ export default {
].forEach(item => {
this.style[item] = this.activeNodes[0].getStyle(item, false)
})
this.initLinearGradientDir()
},
// 初始化渐变方向样式
initLinearGradientDir() {
const startDir = this.activeNodes[0].getStyle('startDir', false)
const endDir = this.activeNodes[0].getStyle('endDir', false)
const target = this.linearGradientDirList.find(item => {
return (
item.start[0] === startDir[0] &&
item.start[1] === startDir[1] &&
item.end[0] === endDir[0] &&
item.end[1] === endDir[1]
)
})
if (target) {
this.style.linearGradientDir = target.value
}
},
/**
@@ -595,9 +638,21 @@ export default {
* @Desc: 修改样式
*/
update(prop) {
this.activeNodes.forEach(node => {
node.setStyle(prop, this.style[prop])
})
if (prop === 'linearGradientDir') {
const target = this.linearGradientDirList.find(item => {
return item.value === this.style.linearGradientDir
})
this.activeNodes.forEach(node => {
node.setStyles({
startDir: [...target.start],
endDir: [...target.end]
})
})
} else {
this.activeNodes.forEach(node => {
node.setStyle(prop, this.style[prop])
})
}
},
/**

View File

@@ -31,6 +31,8 @@ const store = new Vuex.Store({
supportHandDrawnLikeStyle: false, // 是否支持设置手绘风格
supportMark: false, // 是否支持标记
supportNumbers: false, // 是否支持编号
supportFreemind: false, // 是否支持Freemind插件
supportExcel: false, // 是否支持Excel插件
isDragOutlineTreeNode: false // 当前是否正在拖拽大纲树的节点
},
mutations: {
@@ -93,6 +95,16 @@ const store = new Vuex.Store({
state.supportNumbers = data
},
// 设置是否支持Freemind插件
setSupportFreemind(state, data) {
state.supportFreemind = data
},
// 设置是否支持Excel插件
setSupportExcel(state, data) {
state.supportExcel = data
},
// 设置树节点拖拽
setIsDragOutlineTreeNode(state, data) {
state.isDragOutlineTreeNode = data