Compare commits
123 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d36ff55335 | ||
|
|
1ca6a34edf | ||
|
|
c6f8f38648 | ||
|
|
614aa1ec30 | ||
|
|
f0c08c7953 | ||
|
|
b2d5a626c7 | ||
|
|
102cbeb821 | ||
|
|
bff683cb5c | ||
|
|
bf9cb99441 | ||
|
|
e966e5d57c | ||
|
|
740c898bb1 | ||
|
|
3243e366b0 | ||
|
|
7c6b67e8fb | ||
|
|
3b4195acc5 | ||
|
|
8b68b1fc48 | ||
|
|
4614a87bdd | ||
|
|
c87c169dab | ||
|
|
bc6bf2f8f9 | ||
|
|
2e5d17de16 | ||
|
|
a12e72117e | ||
|
|
b3d16a60b8 | ||
|
|
2f91ea7199 | ||
|
|
1085473463 | ||
|
|
4c7dafe94e | ||
|
|
f52e39eb16 | ||
|
|
27d3e977db | ||
|
|
3e59fa6ade | ||
|
|
c80916d0f2 | ||
|
|
7bd73ba157 | ||
|
|
6055a04ec5 | ||
|
|
a114631a66 | ||
|
|
e22f67a831 | ||
|
|
792811f39e | ||
|
|
ea29dad6fd | ||
|
|
660ec00ca7 | ||
|
|
2baa500c17 | ||
|
|
70b6b0052f | ||
|
|
798591f6f9 | ||
|
|
f0b73d635e | ||
|
|
0b049c5294 | ||
|
|
4bf43ff338 | ||
|
|
58a3faae74 | ||
|
|
f3fe2dbc7b | ||
|
|
a72d2e6748 | ||
|
|
8c07209cea | ||
|
|
280afa6a73 | ||
|
|
fd85085cb7 | ||
|
|
95d7a3ac41 | ||
|
|
a295d257d7 | ||
|
|
460d4ea558 | ||
|
|
8b90557f70 | ||
|
|
c0fea992a9 | ||
|
|
8bdb59c3ea | ||
|
|
c4a846a195 | ||
|
|
7e3a1e405e | ||
|
|
952472a977 | ||
|
|
403aae4b3d | ||
|
|
7999b5c260 | ||
|
|
cdc5c7aa81 | ||
|
|
9a8cd1dd24 | ||
|
|
c308cc7d44 | ||
|
|
1c0fe5ac8d | ||
|
|
44413b00fd | ||
|
|
8487e148ea | ||
|
|
3effff95fa | ||
|
|
dd52873106 | ||
|
|
a2cd6e0864 | ||
|
|
4a5980f993 | ||
|
|
fa8ad5c0d0 | ||
|
|
a37fe66e60 | ||
|
|
af622793d8 | ||
|
|
679330663a | ||
|
|
32e027529f | ||
|
|
5be2f561e7 | ||
|
|
3da8070820 | ||
|
|
12c89e6d37 | ||
|
|
fdb292d9b1 | ||
|
|
1083138d8c | ||
|
|
6a4e87af7b | ||
|
|
7354bec8fd | ||
|
|
3405fb7e8a | ||
|
|
138cc4b3e8 | ||
|
|
e6ede72169 | ||
|
|
edddbbd1d6 | ||
|
|
304e76e4af | ||
|
|
b4ceb88d18 | ||
|
|
635fdf4806 | ||
|
|
77d376210e | ||
|
|
12f9e03f63 | ||
|
|
a4f83437c9 | ||
|
|
a5b3efd272 | ||
|
|
7bd467a330 | ||
|
|
59b2506884 | ||
|
|
cf87333910 | ||
|
|
95b957d37e | ||
|
|
7d18f98a33 | ||
|
|
6baf388d95 | ||
|
|
d63d01647c | ||
|
|
70c6a26de0 | ||
|
|
8241bcbbb4 | ||
|
|
89fd59adec | ||
|
|
9b1f26f6e9 | ||
|
|
e590161f0a | ||
|
|
2fe804880f | ||
|
|
bbb21d4e76 | ||
|
|
3f9c3e9fb1 | ||
|
|
925c5d6d3c | ||
|
|
bb223b080c | ||
|
|
c3652331ea | ||
|
|
62c61b6e53 | ||
|
|
88db910c68 | ||
|
|
7a8b83b9b4 | ||
|
|
74b1a082fe | ||
|
|
ac930daa11 | ||
|
|
836a335d75 | ||
|
|
b5cfca848a | ||
|
|
cd7936a50b | ||
|
|
ecc15ea572 | ||
|
|
7c6c6341e8 | ||
|
|
d7bd57ffac | ||
|
|
69264e3a9d | ||
|
|
d8fdc37684 | ||
|
|
b52497b3f6 |
92
README.md
@@ -2,33 +2,29 @@
|
||||
|
||||
[](https://www.npmjs.com/package/simple-mind-map)
|
||||

|
||||
[](https://github.com/wanglin2/mind-map/stargazers)
|
||||
[](https://github.com/wanglin2/mind-map/issues)
|
||||
[](https://github.com/wanglin2/mind-map/network/members)
|
||||

|
||||
[](https://github.com/wanglin2/mind-map/stargazers)
|
||||
[](https://github.com/wanglin2/mind-map/network/members)
|
||||
|
||||
> 一个简单&强大的 Web 思维导图
|
||||
> 中文名:思绪思维导图。一个简单&强大的 Web 思维导图。
|
||||
|
||||
本项目包含两部分:
|
||||
|
||||
1.一个 js 思维导图库,不依赖任何框架,你可以使用它来快速完成 Web 思维导图产品的开发。
|
||||
1.一个 js 思维导图库,不依赖任何框架,可以使用它来快速完成 Web 思维导图产品的开发。
|
||||
|
||||
开发文档:[https://wanglin2.github.io/mind-map/#/doc/zh/](https://wanglin2.github.io/mind-map/#/doc/zh/)。
|
||||
|
||||
2.一个 Web 思维导图,基于思维导图库、Vue2.x、ElementUI 开发,可以操作电脑本地文件,所以你可以直接把它当做一个在线版思维导图应用使用,如果觉得 github 的响应速度慢,你也可以部署到你的服务器上。
|
||||
2.一个 Web 思维导图,基于思维导图库、Vue2.x、ElementUI 开发,可以操作电脑本地文件,可以当做一个在线版思维导图应用使用,也可以自部署和二次开发。
|
||||
|
||||
在线地址:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)。
|
||||
|
||||
另外也提供了客户端可供下载使用,支持`Windows`、`Mac`及`Linux`,下载地址:
|
||||
此外也提供了客户端可供下载使用,支持`Windows`、`Mac`及`Linux`,下载地址:
|
||||
|
||||
Github:[releases](https://github.com/wanglin2/mind-map/releases)。
|
||||
|
||||
百度云盘:[地址](https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3)。
|
||||
Github:[releases](https://github.com/wanglin2/mind-map/releases)。百度云盘:[地址](https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3)。
|
||||
|
||||
> 客户端版本会落后于在线版本,尝试最新功能请优先使用在线版。
|
||||
|
||||
> 如果访问 github 比较慢,可以使用:http://lxqnsys.com/simple-mind-map/#/index
|
||||
|
||||
# 特性
|
||||
|
||||
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小打包体积
|
||||
@@ -88,18 +84,22 @@ const mindMap = new MindMap({
|
||||
|
||||
# License
|
||||
|
||||
[MIT](./LICENSE)
|
||||
[MIT](./LICENSE)。保留`mind-map`版权声明的情况下可随意商用。
|
||||
|
||||
# 微信交流群
|
||||
|
||||
群聊人数较多,无法通过二维码入群,可以微信添加`wanglinguanfang`拉你入群。
|
||||
|
||||
# star
|
||||
|
||||
如果喜欢本项目,欢迎点个star,这对我们很重要。
|
||||
|
||||
[](https://star-history.com/#wanglin2/mind-map&Date)
|
||||
|
||||
# 请作者喝杯咖啡
|
||||
|
||||
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡~
|
||||
|
||||
> 厚椰乳一盒 + 纯牛奶半盒 + 冰块 + 咖啡液 = 生椰拿铁 yyds
|
||||
|
||||
> 推荐使用支付宝,微信获取不到头像。转账请备注【思维导图】。
|
||||
|
||||
<p>
|
||||
@@ -252,4 +252,68 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>易空小易</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/汪津合.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/慕智打印-兰兰.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/旭东.png" style="width: 50px;height: 50px;" />
|
||||
<span>旭东</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/橘半.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>橘半</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/pluvet.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>pluvet</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/风格.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>风格</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>SR</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>LiuJL</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/L.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>L</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
1
copy.js
@@ -13,3 +13,4 @@ if (fs.existsSync(src)) {
|
||||
fs.unlinkSync(src)
|
||||
}
|
||||
|
||||
console.warn('请检查手绘风格选项是否开启!!!')
|
||||
2
dist/css/app.css
vendored
6
dist/css/chunk-vendors.css
vendored
BIN
dist/fonts/iconfont.ttf
vendored
BIN
dist/fonts/iconfont.woff
vendored
BIN
dist/fonts/iconfont.woff2
vendored
BIN
dist/img/L.jpg
vendored
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
dist/img/classic6.jpg
vendored
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
dist/img/classic7.jpg
vendored
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
dist/img/pluvet.jpg
vendored
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
dist/img/俊奇.jpg
vendored
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
dist/img/国发.jpg
vendored
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
dist/img/慕智打印-兰兰.jpg
vendored
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
dist/img/手绘风格.png
vendored
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
dist/img/橘半.jpg
vendored
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
dist/img/汪津合.jpg
vendored
Normal file
|
After Width: | Height: | Size: 347 KiB |
BIN
dist/img/皇登攀.jpg
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
dist/img/逆水行舟.jpg
vendored
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
dist/img/风格.jpg
vendored
Normal file
|
After Width: | Height: | Size: 35 KiB |
2
dist/js/app.js
vendored
83
dist/js/chunk-1066cf58.js
vendored
1
dist/js/chunk-1c3bec15.js
vendored
Normal file
2
dist/js/chunk-2d0aa978.js
vendored
2
dist/js/chunk-2d0ab10b.js
vendored
1
dist/js/chunk-2d0b1be7.js
vendored
Normal file
2
dist/js/chunk-2d0ba309.js
vendored
2
dist/js/chunk-2d0c09f6.js
vendored
2
dist/js/chunk-2d0c0a44.js
vendored
2
dist/js/chunk-2d0c191e.js
vendored
2
dist/js/chunk-2d0c20be.js
vendored
1
dist/js/chunk-2d0c213a.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0c213a"],{4987:function(s,n,a){"use strict";a.r(n);var i=function(){var s=this;s._self._c;return s._m(0)},t=[function(){var s=this,n=s._self._c;return n("div",[n("h1",[s._v("RainbowLines插件")]),n("blockquote",[n("p",[s._v("v0.9.9+")])]),n("p",[s._v("该插件用于实现彩虹线条。")]),n("p",[s._v("开启彩虹线条及自定义颜色可以通过实例化选项"),n("code",[s._v("rainbowLinesConfig")]),s._v("设置。")]),n("p",[s._v("默认的颜色列表如下:")]),n("pre",{staticClass:"hljs"},[n("code",[s._v("[\n 'rgb(255, 213, 73)',\n 'rgb(255, 136, 126)',\n 'rgb(107, 225, 141)',\n 'rgb(151, 171, 255)',\n 'rgb(129, 220, 242)',\n 'rgb(255, 163, 125)',\n 'rgb(152, 132, 234)'\n]\n")])]),n("h2",[s._v("注册")]),n("pre",{staticClass:"hljs"},[n("code",[n("span",{staticClass:"hljs-keyword"},[s._v("import")]),s._v(" MindMap "),n("span",{staticClass:"hljs-keyword"},[s._v("from")]),s._v(" "),n("span",{staticClass:"hljs-string"},[s._v("'simple-mind-map'")]),s._v("\n"),n("span",{staticClass:"hljs-keyword"},[s._v("import")]),s._v(" RainbowLines "),n("span",{staticClass:"hljs-keyword"},[s._v("from")]),s._v(" "),n("span",{staticClass:"hljs-string"},[s._v("'simple-mind-map/src/plugins/RainbowLines.js'")]),s._v("\nMindMap.usePlugin(RainbowLines)\n")])]),n("p",[s._v("注册完且实例化"),n("code",[s._v("MindMap")]),s._v("后可通过"),n("code",[s._v("mindMap.rainbowLines")]),s._v("获取到该实例。")]),n("h2",[s._v("方法")]),n("h3",[s._v("updateRainLinesConfig(config = {})")]),n("p",[s._v("如果你在通过实例化选项"),n("code",[s._v("rainbowLinesConfig")]),s._v("设置了彩虹线条后想修改,那么可以使用该方法,参数"),n("code",[s._v("config")]),s._v("同"),n("code",[s._v("rainbowLinesConfig")]),s._v("。")]),n("pre",{staticClass:"hljs"},[n("code",[s._v("{\n "),n("span",{staticClass:"hljs-attr"},[s._v("open")]),s._v(": "),n("span",{staticClass:"hljs-literal"},[s._v("false")]),s._v(","),n("span",{staticClass:"hljs-comment"},[s._v("// 是否开启彩虹线条")]),s._v("\n "),n("span",{staticClass:"hljs-attr"},[s._v("colorsList")]),s._v(": []"),n("span",{staticClass:"hljs-comment"},[s._v("// 自定义彩虹线条的颜色列表,如果不设置,会使用默认颜色列表")]),s._v("\n}\n")])]),n("h3",[s._v("getColorsList()")]),n("p",[s._v("获取当前使用的彩虹线条颜色列表。")]),n("h3",[s._v("getNodeColor(node)")]),n("p",[s._v("获取指定的节点实例对应的彩虹线条颜色。")]),n("h3",[s._v("getSecondLayerAncestor(node)")]),n("p",[s._v("获取一个节点实例的第二层级的祖先节点实例。")])])}],v={},_=v,o=a("2877"),e=Object(o["a"])(_,i,t,!1,null,null,null);n["default"]=e.exports}}]);
|
||||
2
dist/js/chunk-2d0c5538.js
vendored
2
dist/js/chunk-2d0d9fbc.js
vendored
2
dist/js/chunk-2d0dad5f.js
vendored
1
dist/js/chunk-2d0dd7d2.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0dd7d2"],{8235:function(n,s,t){"use strict";t.r(s);var i=function(){var n=this;n._self._c;return n._m(0)},e=[function(){var n=this,s=n._self._c;return s("div",[s("h1",[n._v("RainbowLines plugin")]),s("blockquote",[s("p",[n._v("v0.9.9+")])]),s("p",[n._v("This plugin is used to implement rainbow lines.")]),s("p",[n._v("Enabling rainbow lines and custom colors can be set through the instantiation option 'rainbowLinesConfig'.")]),s("p",[n._v("The default color list is as follows:")]),s("pre",{staticClass:"hljs"},[s("code",[n._v("[\n 'rgb(255, 213, 73)',\n 'rgb(255, 136, 126)',\n 'rgb(107, 225, 141)',\n 'rgb(151, 171, 255)',\n 'rgb(129, 220, 242)',\n 'rgb(255, 163, 125)',\n 'rgb(152, 132, 234)'\n]\n")])]),s("h2",[n._v("Register")]),s("pre",{staticClass:"hljs"},[s("code",[s("span",{staticClass:"hljs-keyword"},[n._v("import")]),n._v(" MindMap "),s("span",{staticClass:"hljs-keyword"},[n._v("from")]),n._v(" "),s("span",{staticClass:"hljs-string"},[n._v("'simple-mind-map'")]),n._v("\n"),s("span",{staticClass:"hljs-keyword"},[n._v("import")]),n._v(" RainbowLines "),s("span",{staticClass:"hljs-keyword"},[n._v("from")]),n._v(" "),s("span",{staticClass:"hljs-string"},[n._v("'simple-mind-map/src/plugins/RainbowLines.js'")]),n._v("\nMindMap.usePlugin(RainbowLines)\n")])]),s("p",[n._v("After registration and instantiation of "),s("code",[n._v("MindMap")]),n._v(", the instance can be obtained through "),s("code",[n._v("mindMap.rainbowLines")]),n._v(".")]),s("h2",[n._v("Method")]),s("h3",[n._v("updateRainLinesConfig(config = {})")]),s("p",[n._v("If you want to modify the rainbow lines after setting them through the instantiation option 'rainbowLinesConfig', you can use this method, option "),s("code",[n._v("config")]),n._v(" is same with "),s("code",[n._v("rainbowLinesConfig")]),n._v("。")]),s("pre",{staticClass:"hljs"},[s("code",[n._v("{\n "),s("span",{staticClass:"hljs-attr"},[n._v("open")]),n._v(": "),s("span",{staticClass:"hljs-literal"},[n._v("false")]),n._v(","),s("span",{staticClass:"hljs-comment"},[n._v("// Is turn on rainbow lines")]),n._v("\n "),s("span",{staticClass:"hljs-attr"},[n._v("colorsList")]),n._v(": []"),s("span",{staticClass:"hljs-comment"},[n._v("// Customize the color list for rainbow lines. If not set, the default color list will be used")]),n._v("\n}\n")])]),s("h3",[n._v("getColorsList()")]),s("p",[n._v("Get a list of currently used rainbow line colors.")]),s("h3",[n._v("getNodeColor(node)")]),s("p",[n._v("Retrieve the rainbow line color corresponding to the specified node instance.")]),s("h3",[n._v("getSecondLayerAncestor(node)")]),s("p",[n._v("Retrieve the second level ancestor node instance of a node instance.")])])}],o={},a=o,l=t("2877"),r=Object(l["a"])(a,i,e,!1,null,null,null);s["default"]=r.exports}}]);
|
||||
2
dist/js/chunk-2d0e2326.js
vendored
2
dist/js/chunk-2d0f026c.js
vendored
2
dist/js/chunk-2d0f0784.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0f0784"],{"9d03":function(e,t,n){"use strict";n.r(t);var i=function(){var e=this;e._self._c;return e._m(0)},d=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("TextEdit instance")]),t("p",[e._v("Node text editing instance. It can be obtained through "),t("code",[e._v("mindMap.renderer.textEdit")]),e._v(".")]),t("h2",[e._v("Methods")]),t("h3",[e._v("isShowTextEdit()")]),t("p",[e._v("Get whether the current text editing box is in a display state, that is, whether it is in a text editing state.")]),t("h3",[e._v("hideEditTextBox()")]),t("p",[e._v("Hiding the text editing box will set the content of the current text editing box as node text.")]),t("h3",[e._v("registerTmpShortcut()")]),t("p",[e._v("Register temporary shortcut keys, which means editing can be completed through the Enter and Tab keys.")]),t("h3",[e._v("show({ node})")]),t("ul",[t("li",[t("code",[e._v("node")]),e._v(":Node instance to enter for editing")])]),t("p",[e._v("Manually enable node editing. By default, it will enter node editing when double clicking or pressing F2 on the node.")])])}],o={},h=o,r=n("2877"),s=Object(r["a"])(h,i,d,!1,null,null,null);t["default"]=s.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0f0784"],{"9d03":function(e,t,n){"use strict";n.r(t);var i=function(){var e=this;e._self._c;return e._m(0)},d=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("TextEdit instance")]),t("p",[e._v("Node text editing instance. It can be obtained through "),t("code",[e._v("mindMap.renderer.textEdit")]),e._v(".")]),t("h2",[e._v("Methods")]),t("h3",[e._v("isShowTextEdit()")]),t("p",[e._v("Get whether the current text editing box is in a display state, that is, whether it is in a text editing state.")]),t("h3",[e._v("hideEditTextBox()")]),t("p",[e._v("Hiding the text editing box will set the content of the current text editing box as node text.")]),t("h3",[e._v("registerTmpShortcut()")]),t("p",[e._v("Register temporary shortcut keys, which means editing can be completed through the Enter and Tab keys.")]),t("h3",[e._v("show({ node})")]),t("ul",[t("li",[t("code",[e._v("node")]),e._v(":Node instance to enter for editing")])]),t("p",[e._v("Manually enable node editing. By default, it will enter node editing when double clicking or pressing F2 on the node.")]),t("h3",[e._v("getCurrentEditNode()")]),t("blockquote",[t("p",[e._v("v0.9.8+")])]),t("p",[e._v("Get the node instance currently being edited.")])])}],o={},r=o,h=n("2877"),s=Object(h["a"])(r,i,d,!1,null,null,null);t["default"]=s.exports}}]);
|
||||
2
dist/js/chunk-2d208ffa.js
vendored
2
dist/js/chunk-2d20f68f.js
vendored
2
dist/js/chunk-2d216004.js
vendored
2
dist/js/chunk-2d216f87.js
vendored
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d216f87"],{c576:function(e,t,n){"use strict";n.r(t);var _=function(){var e=this;e._self._c;return e._m(0)},v=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("TextEdit 实例")]),t("p",[e._v("节点文本编辑实例。可以通过"),t("code",[e._v("mindMap.renderer.textEdit")]),e._v("获取到。")]),t("h2",[e._v("方法")]),t("h3",[e._v("isShowTextEdit()")]),t("p",[e._v("获取当前文本编辑框是否处于显示状态,也就是是否处在文本编辑状态。")]),t("h3",[e._v("hideEditTextBox()")]),t("p",[e._v("隐藏文本编辑框,会将当前文本编辑框中的内容设置为节点文本。")]),t("h3",[e._v("registerTmpShortcut()")]),t("p",[e._v("注册临时快捷键,也就是可以通过 Enter 键和 Tab 键完成编辑。")]),t("h3",[e._v("show({ node})")]),t("ul",[t("li",[t("code",[e._v("node")]),e._v(":要进入编辑的节点实例")])]),t("p",[e._v("手动开启节点编辑。默认会在节点双击、按 F2 时进入节点编辑。")])])}],i={},r=i,d=n("2877"),o=Object(d["a"])(r,_,v,!1,null,null,null);t["default"]=o.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d216f87"],{c576:function(e,t,v){"use strict";v.r(t);var _=function(){var e=this;e._self._c;return e._m(0)},n=[function(){var e=this,t=e._self._c;return t("div",[t("h1",[e._v("TextEdit 实例")]),t("p",[e._v("节点文本编辑实例。可以通过"),t("code",[e._v("mindMap.renderer.textEdit")]),e._v("获取到。")]),t("h2",[e._v("方法")]),t("h3",[e._v("isShowTextEdit()")]),t("p",[e._v("获取当前文本编辑框是否处于显示状态,也就是是否处在文本编辑状态。")]),t("h3",[e._v("hideEditTextBox()")]),t("p",[e._v("隐藏文本编辑框,会将当前文本编辑框中的内容设置为节点文本。")]),t("h3",[e._v("registerTmpShortcut()")]),t("p",[e._v("注册临时快捷键,也就是可以通过 Enter 键和 Tab 键完成编辑。")]),t("h3",[e._v("show({ node})")]),t("ul",[t("li",[t("code",[e._v("node")]),e._v(":要进入编辑的节点实例")])]),t("p",[e._v("手动开启节点编辑。默认会在节点双击、按 F2 时进入节点编辑。")]),t("h3",[e._v("getCurrentEditNode()")]),t("blockquote",[t("p",[e._v("v0.9.8+")])]),t("p",[e._v("获取当前正在编辑中的节点实例。")])])}],i={},o=i,r=v("2877"),d=Object(r["a"])(o,_,n,!1,null,null,null);t["default"]=d.exports}}]);
|
||||
2
dist/js/chunk-2d217907.js
vendored
1
dist/js/chunk-4c82605f.js
vendored
Normal file
1
dist/js/chunk-5ecd9693.js
vendored
Normal file
1
dist/js/chunk-66b27c16.js
vendored
Normal file
1
dist/js/chunk-69cd0684.js
vendored
1
dist/js/chunk-76a82238.js
vendored
1
dist/js/chunk-7babbe51.js
vendored
1
dist/js/chunk-9d289278.js
vendored
Normal file
76
dist/js/chunk-cae1ba8a.js
vendored
Normal file
36
dist/js/chunk-vendors.js
vendored
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link rel="icon" href="dist/logo.ico"><title>思绪思维导图</title><script>// 自定义静态资源的路径
|
||||
window.externalPublicPath = './dist/'
|
||||
// 接管应用
|
||||
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?b288d0464b34253181aa" rel="stylesheet"><link href="dist/css/app.css?b288d0464b34253181aa" 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 = () => {
|
||||
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?fda56af9523032588417" rel="stylesheet"><link href="dist/css/app.css?fda56af9523032588417" 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({
|
||||
@@ -66,4 +66,4 @@
|
||||
// 可以通过window.$bus.$on()来监听应用的一些事件
|
||||
// 实例化页面
|
||||
window.initApp()
|
||||
}</script><script src="dist/js/chunk-vendors.js?b288d0464b34253181aa"></script><script src="dist/js/app.js?b288d0464b34253181aa"></script></body></html>
|
||||
}</script><script src="dist/js/chunk-vendors.js?fda56af9523032588417"></script><script src="dist/js/app.js?fda56af9523032588417"></script></body></html>
|
||||
@@ -15,6 +15,7 @@ import Search from './src/plugins/Search.js'
|
||||
import Painter from './src/plugins/Painter.js'
|
||||
import Scrollbar from './src/plugins/Scrollbar.js'
|
||||
import Formula from './src/plugins/Formula.js'
|
||||
import RainbowLines from './src/plugins/RainbowLines.js'
|
||||
import xmind from './src/parse/xmind.js'
|
||||
import markdown from './src/parse/markdown.js'
|
||||
import icons from './src/svg/icons.js'
|
||||
@@ -28,7 +29,7 @@ MindMap.iconList = icons.nodeIconList
|
||||
MindMap.constants = constants
|
||||
MindMap.themes = themes
|
||||
MindMap.defaultTheme = defaultTheme
|
||||
MindMap.version = '0.9.3'
|
||||
MindMap.version = '0.9.9'
|
||||
|
||||
MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(Watermark)
|
||||
@@ -46,5 +47,6 @@ MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(Painter)
|
||||
.usePlugin(Scrollbar)
|
||||
.usePlugin(Formula)
|
||||
.usePlugin(RainbowLines)
|
||||
|
||||
export default MindMap
|
||||
|
||||
@@ -15,7 +15,13 @@ import {
|
||||
cssContent
|
||||
} from './src/constants/constant'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import { simpleDeepClone, getType, getObjectChangedProps } from './src/utils'
|
||||
import {
|
||||
simpleDeepClone,
|
||||
getType,
|
||||
getObjectChangedProps,
|
||||
isUndef,
|
||||
handleGetSvgDataExtraContent
|
||||
} from './src/utils'
|
||||
import defaultTheme, {
|
||||
checkIsNodeSizeIndependenceConfig
|
||||
} from './src/themes/default'
|
||||
@@ -31,6 +37,8 @@ class MindMap {
|
||||
constructor(opt = {}) {
|
||||
// 合并选项
|
||||
this.opt = this.handleOpt(merge(defaultOpt, opt))
|
||||
// 预处理节点数据
|
||||
this.opt.data = this.handleData(this.opt.data)
|
||||
|
||||
// 容器元素
|
||||
this.el = this.opt.el
|
||||
@@ -39,6 +47,10 @@ class MindMap {
|
||||
// 获取容器尺寸位置信息
|
||||
this.getElRectInfo()
|
||||
|
||||
// 画布初始大小
|
||||
this.initWidth = this.width
|
||||
this.initHeight = this.height
|
||||
|
||||
// 添加css
|
||||
this.cssEl = null
|
||||
this.addCss()
|
||||
@@ -88,14 +100,12 @@ class MindMap {
|
||||
// 初始渲染
|
||||
this.render(this.opt.fit ? () => this.view.fit() : () => {})
|
||||
setTimeout(() => {
|
||||
this.command.addHistory()
|
||||
if (this.opt.data) this.command.addHistory()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 配置参数处理
|
||||
handleOpt(opt) {
|
||||
// 深拷贝一份节点数据
|
||||
opt.data = simpleDeepClone(opt.data || {})
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(opt.layout)) {
|
||||
opt.layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
@@ -105,6 +115,17 @@ class MindMap {
|
||||
return opt
|
||||
}
|
||||
|
||||
// 预处理节点数据
|
||||
handleData(data) {
|
||||
if (isUndef(data) || Object.keys(data).length <= 0) return null
|
||||
data = simpleDeepClone(data || {})
|
||||
// 根节点不能收起
|
||||
if (data.data && !data.data.expand) {
|
||||
data.data.expand = true
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 创建容器元素
|
||||
initContainer() {
|
||||
const { associativeLineIsAlwaysAboveNode } = this.opt
|
||||
@@ -294,6 +315,7 @@ class MindMap {
|
||||
if (!notRender) {
|
||||
this.render(null, CONSTANTS.CHANGE_LAYOUT)
|
||||
}
|
||||
this.emit('layout_change', layout)
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
@@ -301,9 +323,17 @@ class MindMap {
|
||||
this.command.exec(...args)
|
||||
}
|
||||
|
||||
// 更新画布数据,如果新的数据是在当前画布节点数据基础上增删改查后形成的,那么可以使用该方法来更新画布数据
|
||||
updateData(data) {
|
||||
this.renderer.setData(data)
|
||||
this.render()
|
||||
this.command.addHistory()
|
||||
}
|
||||
|
||||
// 动态设置思维导图数据,纯节点数据
|
||||
setData(data) {
|
||||
data = simpleDeepClone(data || {})
|
||||
data = this.handleData(data)
|
||||
this.opt.data = data
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
this.command.addHistory()
|
||||
@@ -385,7 +415,18 @@ class MindMap {
|
||||
}
|
||||
|
||||
// 获取svg数据
|
||||
getSvgData({ paddingX = 0, paddingY = 0, ignoreWatermark = false } = {}) {
|
||||
getSvgData({
|
||||
paddingX = 0,
|
||||
paddingY = 0,
|
||||
ignoreWatermark = false,
|
||||
addContentToHeader,
|
||||
addContentToFooter
|
||||
} = {}) {
|
||||
const { cssTextList, header, headerHeight, footer, footerHeight } =
|
||||
handleGetSvgDataExtraContent({
|
||||
addContentToHeader,
|
||||
addContentToFooter
|
||||
})
|
||||
const svg = this.svg
|
||||
const draw = this.draw
|
||||
// 保存原始信息
|
||||
@@ -398,8 +439,9 @@ class MindMap {
|
||||
// 获取变换后的位置尺寸信息,其实是getBoundingClientRect方法的包装方法
|
||||
const rect = draw.rbox()
|
||||
// 内边距
|
||||
const fixHeight = 0
|
||||
rect.width += paddingX * 2
|
||||
rect.height += paddingY * 2
|
||||
rect.height += paddingY * 2 + fixHeight + headerHeight + footerHeight
|
||||
draw.translate(paddingX, paddingY)
|
||||
// 将svg设置为实际内容的宽高
|
||||
svg.size(rect.width, rect.height)
|
||||
@@ -437,7 +479,21 @@ class MindMap {
|
||||
this.watermark.isInExport = false
|
||||
}
|
||||
// 添加必要的样式
|
||||
clone.add(SVG(`<style>${cssContent}</style>`))
|
||||
;[cssContent, ...cssTextList].forEach(s => {
|
||||
clone.add(SVG(`<style>${s}</style>`))
|
||||
})
|
||||
// 附加内容
|
||||
if (header && headerHeight > 0) {
|
||||
clone.findOne('.smm-container').translate(0, headerHeight)
|
||||
header.width(rect.width)
|
||||
header.y(paddingY)
|
||||
clone.add(header, 0)
|
||||
}
|
||||
if (footer && footerHeight > 0) {
|
||||
footer.width(rect.width)
|
||||
footer.y(rect.height - paddingY - footerHeight)
|
||||
clone.add(footer)
|
||||
}
|
||||
// 修正defs里定义的元素的id,因为clone时defs里的元素的id会继续递增,导致和内容中引用的id对不上
|
||||
const defs = svg.find('defs')
|
||||
const defs2 = clone.find('defs')
|
||||
|
||||
1421
simple-mind-map/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.9.3",
|
||||
"version": "0.9.9",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -38,6 +38,7 @@
|
||||
"quill": "^1.3.6",
|
||||
"tern": "^0.24.3",
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^7.5.9",
|
||||
"xml-js": "^1.6.11",
|
||||
"y-webrtc": "^10.2.5",
|
||||
"yjs": "^13.6.8"
|
||||
|
||||
@@ -329,7 +329,8 @@ export const ERROR_TYPES = {
|
||||
LOAD_CLIPBOARD_IMAGE_ERROR: 'load_clipboard_image_error',
|
||||
BEFORE_TEXT_EDIT_ERROR: 'before_text_edit_error',
|
||||
EXPORT_ERROR: 'export_error',
|
||||
EXPORT_LOAD_IMAGE_ERROR: 'export_load_image_error'
|
||||
EXPORT_LOAD_IMAGE_ERROR: 'export_load_image_error',
|
||||
DATA_CHANGE_DETAIL_EVENT_ERROR: 'data_change_detail_event_error'
|
||||
}
|
||||
|
||||
// css
|
||||
@@ -345,7 +346,7 @@ export const cssContent = `
|
||||
display: block;
|
||||
}
|
||||
|
||||
.smm-node.active .smm-hover-node{
|
||||
.smm-node.active .smm-hover-node, .smm-node-highlight .smm-hover-node{
|
||||
display: block;
|
||||
opacity: 1;
|
||||
stroke-width: 2;
|
||||
|
||||
@@ -40,7 +40,7 @@ export const defaultOpt = {
|
||||
enableFreeDrag: false,
|
||||
// 水印配置
|
||||
watermarkConfig: {
|
||||
onlyExport: false,// 是否仅在导出时添加水印
|
||||
onlyExport: false, // 是否仅在导出时添加水印
|
||||
text: '',
|
||||
lineSpacing: 100,
|
||||
textSpacing: 100,
|
||||
@@ -256,5 +256,67 @@ export const defaultOpt = {
|
||||
}
|
||||
}
|
||||
*/
|
||||
handleNodePasteImg: null
|
||||
handleNodePasteImg: null,
|
||||
// 默认情况下,新创建的关联线两个端点的位置是根据两个节点中心点的相对位置来计算的,如果你想固定位置,可以通过这个属性来配置
|
||||
// from和to都不传,则都自动计算,如果只传一个,另一个则会自动计算
|
||||
associativeLineInitPointsPosition: {
|
||||
// from和to可选值:left、top、bottom、right
|
||||
from: '', // 关联线起始节点上端点的位置
|
||||
to: '' // 关联线目标节点上端点的位置
|
||||
},
|
||||
// 是否允许调整关联线两个端点的位置
|
||||
enableAdjustAssociativeLinePoints: true,
|
||||
// 自定义创建节点形状的方法,可以传一个函数,均接收一个参数
|
||||
// 矩形、圆角矩形、椭圆、圆等形状会调用该方法
|
||||
// 接收svg path字符串,返回svg节点
|
||||
customCreateNodePath: null,
|
||||
// 菱形、平行四边形、八角矩形、外三角矩形、内三角矩形等形状会调用该方法
|
||||
// 接收points数组点位,返回svg节点
|
||||
customCreateNodePolygon: null,
|
||||
// 自定义转换节点连线路径的方法
|
||||
// 接收svg path字符串,返回转换后的svg path字符串
|
||||
customTransformNodeLinePath: null,
|
||||
// 是否仅搜索当前渲染的节点,被收起的节点不会被搜索到
|
||||
isOnlySearchCurrentRenderNodes: false,
|
||||
// 协同编辑时,同一个节点不能同时被多人选中
|
||||
onlyOneEnableActiveNodeOnCooperate: false,
|
||||
// 协同编辑时,节点操作即将更新到其他客户端前的生命周期函数
|
||||
// 函数接收一个对象作为参数:
|
||||
/*
|
||||
{
|
||||
type: createOrUpdate(创建节点或更新节点)、delete(删除节点)
|
||||
data: 1.当type=createOrUpdate时,代表被创建或被更新的节点数据,即将同步到其他客户端,所以你可以修改该数据;2.当type=delete时,代表被删除的节点数据
|
||||
}
|
||||
*/
|
||||
beforeCooperateUpdate: null,
|
||||
// 快捷键操作即将执行前的生命周期函数,返回true可以阻止操作执行
|
||||
// 函数接收两个参数:key(快捷键)、activeNodeList(当前激活的节点列表)
|
||||
beforeShortcutRun: null,
|
||||
// 彩虹线条配置,需要先注册RainbowLines插件
|
||||
rainbowLinesConfig: {
|
||||
open: false, // 是否开启彩虹线条
|
||||
colorsList: [] // 自定义彩虹线条的颜色列表,如果不设置,会使用默认颜色列表
|
||||
/*
|
||||
[
|
||||
'rgb(255, 213, 73)',
|
||||
'rgb(255, 136, 126)',
|
||||
'rgb(107, 225, 141)',
|
||||
'rgb(151, 171, 255)',
|
||||
'rgb(129, 220, 242)',
|
||||
'rgb(255, 163, 125)',
|
||||
'rgb(152, 132, 234)'
|
||||
]
|
||||
*/
|
||||
},
|
||||
// 导出png、svg、pdf时在头部和尾部添加自定义内容
|
||||
// 可传递一个函数,这个函数可以返回null代表不添加内容,也可以返回如下数据:
|
||||
/*
|
||||
{
|
||||
el,// 要追加的自定义DOM节点,样式可内联
|
||||
cssText,// 可选,如果样式不想内联,可以传递该值,一个css字符串
|
||||
height: 50// 返回的DOM节点的高度,必须传递
|
||||
}
|
||||
*/
|
||||
addContentToHeader: null,
|
||||
addContentToFooter: null
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
isSameObject,
|
||||
transformTreeDataToObject
|
||||
} from '../../utils'
|
||||
import { ERROR_TYPES } from '../../constants/constant'
|
||||
|
||||
// 命令类
|
||||
class Command {
|
||||
@@ -94,6 +95,7 @@ class Command {
|
||||
this.history.length > 0 ? this.history[this.history.length - 1] : null
|
||||
const data = this.getCopyData()
|
||||
// 此次数据和上次一样则不重复添加
|
||||
if (lastData === data) return
|
||||
if (lastData && JSON.stringify(lastData) === JSON.stringify(data)) {
|
||||
return
|
||||
}
|
||||
@@ -157,6 +159,7 @@ class Command {
|
||||
|
||||
// 获取渲染树数据副本
|
||||
getCopyData() {
|
||||
if (!this.mindMap.renderer.renderTree) return null
|
||||
return copyRenderTree({}, this.mindMap.renderer.renderTree, true)
|
||||
}
|
||||
|
||||
@@ -177,48 +180,58 @@ class Command {
|
||||
|
||||
// 派发思维导图更新明细事件
|
||||
emitDataUpdatesEvent(lastData, data) {
|
||||
// 如果data_change_detail没有监听者,那么不进行计算,节省性能
|
||||
const eventName = 'data_change_detail'
|
||||
const count = this.mindMap.event.listenerCount(eventName)
|
||||
if (count > 0 && lastData && data) {
|
||||
const lastDataObj = simpleDeepClone(transformTreeDataToObject(lastData))
|
||||
const dataObj = simpleDeepClone(transformTreeDataToObject(data))
|
||||
const res = []
|
||||
const walkReplace = (root, obj) => {
|
||||
if (root.children && root.children.length > 0) {
|
||||
root.children.forEach((childUid, index) => {
|
||||
root.children[index] = obj[childUid]
|
||||
walkReplace(root.children[index], obj)
|
||||
})
|
||||
try {
|
||||
// 如果data_change_detail没有监听者,那么不进行计算,节省性能
|
||||
const eventName = 'data_change_detail'
|
||||
const count = this.mindMap.event.listenerCount(eventName)
|
||||
if (count > 0 && lastData && data) {
|
||||
const lastDataObj = simpleDeepClone(transformTreeDataToObject(lastData))
|
||||
const dataObj = simpleDeepClone(transformTreeDataToObject(data))
|
||||
const res = []
|
||||
const walkReplace = (root, obj) => {
|
||||
if (root.children && root.children.length > 0) {
|
||||
root.children.forEach((childUid, index) => {
|
||||
root.children[index] =
|
||||
typeof childUid === 'string'
|
||||
? obj[childUid]
|
||||
: obj[childUid.data.uid]
|
||||
walkReplace(root.children[index], obj)
|
||||
})
|
||||
}
|
||||
return root
|
||||
}
|
||||
return root
|
||||
// 找出新增的或修改的
|
||||
Object.keys(dataObj).forEach(uid => {
|
||||
// 新增的或已经存在的,如果数据发生了改变
|
||||
if (!lastDataObj[uid]) {
|
||||
res.push({
|
||||
action: 'create',
|
||||
data: walkReplace(dataObj[uid], dataObj)
|
||||
})
|
||||
} else if (!isSameObject(lastDataObj[uid], dataObj[uid])) {
|
||||
res.push({
|
||||
action: 'update',
|
||||
oldData: walkReplace(lastDataObj[uid], lastDataObj),
|
||||
data: walkReplace(dataObj[uid], dataObj)
|
||||
})
|
||||
}
|
||||
})
|
||||
// 找出删除的
|
||||
Object.keys(lastDataObj).forEach(uid => {
|
||||
if (!dataObj[uid]) {
|
||||
res.push({
|
||||
action: 'delete',
|
||||
data: walkReplace(lastDataObj[uid], lastDataObj)
|
||||
})
|
||||
}
|
||||
})
|
||||
this.mindMap.emit(eventName, res)
|
||||
}
|
||||
// 找出新增的或修改的
|
||||
Object.keys(dataObj).forEach(uid => {
|
||||
// 新增的或已经存在的,如果数据发生了改变
|
||||
if (!lastDataObj[uid]) {
|
||||
res.push({
|
||||
action: 'create',
|
||||
data: walkReplace(dataObj[uid], dataObj)
|
||||
})
|
||||
} else if (!isSameObject(lastDataObj[uid], dataObj[uid])) {
|
||||
res.push({
|
||||
action: 'update',
|
||||
oldData: walkReplace(lastDataObj[uid], lastDataObj),
|
||||
data: walkReplace(dataObj[uid], dataObj)
|
||||
})
|
||||
}
|
||||
})
|
||||
// 找出删除的
|
||||
Object.keys(lastDataObj).forEach(uid => {
|
||||
if (!dataObj[uid]) {
|
||||
res.push({
|
||||
action: 'delete',
|
||||
data: walkReplace(lastDataObj[uid], lastDataObj)
|
||||
})
|
||||
}
|
||||
})
|
||||
this.mindMap.emit(eventName, res)
|
||||
} catch (error) {
|
||||
this.mindMap.opt.errorHandler(
|
||||
ERROR_TYPES.DATA_CHANGE_DETAIL_EVENT_ERROR,
|
||||
error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +67,10 @@ export default class KeyCommand {
|
||||
|
||||
// 按键事件
|
||||
onKeydown(e) {
|
||||
const { enableShortcutOnlyWhenMouseInSvg, beforeShortcutRun } = this.mindMap.opt
|
||||
if (
|
||||
this.isPause ||
|
||||
(this.mindMap.opt.enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)
|
||||
(enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -80,6 +81,10 @@ export default class KeyCommand {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
if (typeof beforeShortcutRun === 'function') {
|
||||
const isStop = beforeShortcutRun(key, [...this.mindMap.renderer.activeNodeList])
|
||||
if (isStop) return
|
||||
}
|
||||
this.shortcutMap[key].forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
@@ -165,6 +165,8 @@ class Event extends EventEmitter {
|
||||
// 鼠标右键菜单事件
|
||||
onContextmenu(e) {
|
||||
e.preventDefault()
|
||||
// Mac上按住ctrl键点击鼠标左键不知为何触发的是contextmenu事件
|
||||
if (e.ctrlKey) return
|
||||
this.emit('contextmenu', e)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,9 @@ import {
|
||||
parseAddGeneralizationNodeList,
|
||||
checkNodeListIsEqual,
|
||||
createSmmFormatData,
|
||||
checkSmmFormatData
|
||||
checkSmmFormatData,
|
||||
checkIsNodeStyleDataKey,
|
||||
removeRichTextStyes
|
||||
} from '../../utils'
|
||||
import { shapeList } from './node/Shape'
|
||||
import { lineStyleProps } from '../../themes/default'
|
||||
@@ -63,7 +65,7 @@ class Render {
|
||||
this.mindMap = opt.mindMap
|
||||
this.themeConfig = this.mindMap.themeConfig
|
||||
// 渲染树,操作过程中修改的都是这里的数据
|
||||
this.renderTree = merge({}, this.mindMap.opt.data || {})
|
||||
this.renderTree = this.mindMap.opt.data ? merge({}, this.mindMap.opt.data) : null
|
||||
// 是否重新渲染
|
||||
this.reRender = false
|
||||
// 是否正在渲染中
|
||||
@@ -115,7 +117,7 @@ class Render {
|
||||
// 重新设置思维导图数据
|
||||
setData(data) {
|
||||
if (this.mindMap.richText) {
|
||||
this.renderTree = this.mindMap.richText.handleSetData(data)
|
||||
this.renderTree = data ? this.mindMap.richText.handleSetData(data) : null
|
||||
} else {
|
||||
this.renderTree = data
|
||||
}
|
||||
@@ -180,6 +182,9 @@ class Render {
|
||||
// 下移节点
|
||||
this.downNode = this.downNode.bind(this)
|
||||
this.mindMap.command.add('DOWN_NODE', this.downNode)
|
||||
// 将一个节点上移一个层级
|
||||
this.moveUpOneLevel = this.moveUpOneLevel.bind(this)
|
||||
this.mindMap.command.add('MOVE_UP_ONE_LEVEL', this.moveUpOneLevel)
|
||||
// 移动节点
|
||||
this.insertAfter = this.insertAfter.bind(this)
|
||||
this.mindMap.command.add('INSERT_AFTER', this.insertAfter)
|
||||
@@ -268,6 +273,15 @@ class Render {
|
||||
// 定位节点
|
||||
this.goTargetNode = this.goTargetNode.bind(this)
|
||||
this.mindMap.command.add('GO_TARGET_NODE', this.goTargetNode)
|
||||
// 一键去除节点自定义样式
|
||||
this.removeCustomStyles = this.removeCustomStyles.bind(this)
|
||||
this.mindMap.command.add('REMOVE_CUSTOM_STYLES', this.removeCustomStyles)
|
||||
// 一键去除所有节点自定义样式
|
||||
this.removeAllNodeCustomStyles = this.removeAllNodeCustomStyles.bind(this)
|
||||
this.mindMap.command.add(
|
||||
'REMOVE_ALL_NODE_CUSTOM_STYLES',
|
||||
this.removeAllNodeCustomStyles
|
||||
)
|
||||
}
|
||||
|
||||
// 注册快捷键
|
||||
@@ -276,6 +290,10 @@ class Render {
|
||||
this.mindMap.keyCommand.addShortcut('Tab', () => {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE')
|
||||
})
|
||||
// 插入下级节点
|
||||
this.mindMap.keyCommand.addShortcut('Insert', () => {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE')
|
||||
})
|
||||
// 插入同级节点
|
||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||
this.mindMap.execCommand('INSERT_NODE')
|
||||
@@ -397,6 +415,10 @@ class Render {
|
||||
|
||||
// 渲染
|
||||
render(callback = () => {}, source) {
|
||||
// 切换主题时,被收起的节点需要添加样式复位的标注
|
||||
if (source === CONSTANTS.CHANGE_THEME) {
|
||||
this.resetUnExpandNodeStyle()
|
||||
}
|
||||
// 如果当前还没有渲染完毕,不再触发渲染
|
||||
if (this.isRendering) {
|
||||
// 等待当前渲染完毕后再进行一次渲染
|
||||
@@ -414,6 +436,12 @@ class Render {
|
||||
if (this.reRender) {
|
||||
this.clearActiveNodeList()
|
||||
}
|
||||
// 如果没有节点数据
|
||||
if (!this.renderTree) {
|
||||
this.isRendering = false
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
return
|
||||
}
|
||||
// 计算布局
|
||||
this.layout.doLayout(root => {
|
||||
// 删除本次渲染时不再需要的节点
|
||||
@@ -439,6 +467,7 @@ class Render {
|
||||
this.waitRenderingParams = []
|
||||
this.render(...params)
|
||||
} else {
|
||||
this.renderSource = ''
|
||||
if (this.reRender) {
|
||||
this.reRender = false
|
||||
}
|
||||
@@ -455,6 +484,19 @@ class Render {
|
||||
this.emitNodeActiveEvent()
|
||||
}
|
||||
|
||||
// 给当前被收起来的节点数据添加文本复位标志
|
||||
resetUnExpandNodeStyle() {
|
||||
if (!this.renderTree) return
|
||||
walk(this.renderTree, null, node => {
|
||||
if (!node.data.expand) {
|
||||
walk(node, null, node2 => {
|
||||
node2.data.resetRichText = true
|
||||
})
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 清除当前所有激活节点,并会触发事件
|
||||
clearActiveNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
@@ -474,6 +516,11 @@ class Render {
|
||||
|
||||
// 添加节点到激活列表里
|
||||
addNodeToActiveList(node) {
|
||||
if (
|
||||
this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate &&
|
||||
node.userList.length > 0
|
||||
)
|
||||
return
|
||||
const index = this.findActiveNodeIndex(node)
|
||||
if (index === -1) {
|
||||
this.mindMap.execCommand('SET_NODE_ACTIVE', node, true)
|
||||
@@ -618,7 +665,7 @@ class Render {
|
||||
uid: createUid(),
|
||||
...(appointData || {})
|
||||
},
|
||||
children: [...createUidForAppointNodes(appointChildren, true)]
|
||||
children: [...createUidForAppointNodes(appointChildren)]
|
||||
}
|
||||
parent.nodeData.children.splice(index + 1, 0, newNodeData)
|
||||
})
|
||||
@@ -654,10 +701,7 @@ class Render {
|
||||
const parent = node.parent
|
||||
// 计算插入位置
|
||||
const index = getNodeDataIndex(node)
|
||||
const newNodeList = createUidForAppointNodes(
|
||||
simpleDeepClone(nodeList),
|
||||
true
|
||||
)
|
||||
const newNodeList = createUidForAppointNodes(simpleDeepClone(nodeList))
|
||||
parent.nodeData.children.splice(index + 1, 0, ...newNodeList)
|
||||
})
|
||||
if (focusNewNode) {
|
||||
@@ -717,7 +761,7 @@ class Render {
|
||||
...params,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: [...createUidForAppointNodes(appointChildren, true)]
|
||||
children: [...createUidForAppointNodes(appointChildren)]
|
||||
}
|
||||
node.nodeData.children.push(newNode)
|
||||
// 插入子节点时自动展开子节点
|
||||
@@ -757,7 +801,7 @@ class Render {
|
||||
if (!node.nodeData.children) {
|
||||
node.nodeData.children = []
|
||||
}
|
||||
childList = createUidForAppointNodes(childList, true)
|
||||
childList = createUidForAppointNodes(childList)
|
||||
node.nodeData.children.push(...childList)
|
||||
// 插入子节点时自动展开子节点
|
||||
node.setData({
|
||||
@@ -874,6 +918,83 @@ class Render {
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 将节点上移一个层级,多个节点只会操作第一个节点
|
||||
moveUpOneLevel(node) {
|
||||
node = node || this.activeNodeList[0]
|
||||
if (!node || node.isRoot || node.layerIndex <= 1) {
|
||||
return
|
||||
}
|
||||
const parent = node.parent
|
||||
const grandpa = parent.parent
|
||||
const index = getNodeIndexInNodeList(node, parent.children)
|
||||
const parentIndex = getNodeIndexInNodeList(parent, grandpa.children)
|
||||
// 节点数据
|
||||
this.checkNodeLayerChange(node, parent)
|
||||
parent.nodeData.children.splice(index, 1)
|
||||
grandpa.nodeData.children.splice(parentIndex + 1, 0, node.nodeData)
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 移除节点数据的自定义样式的内部方法
|
||||
_handleRemoveCustomStyles(nodeData) {
|
||||
let hasCustomStyles = false
|
||||
Object.keys(nodeData).forEach(key => {
|
||||
if (checkIsNodeStyleDataKey(key)) {
|
||||
hasCustomStyles = true
|
||||
delete nodeData[key]
|
||||
}
|
||||
})
|
||||
// 如果是富文本,那么还要处理富文本内容
|
||||
if (hasCustomStyles && this.mindMap.richText) {
|
||||
nodeData.resetRichText = true
|
||||
nodeData.text = removeRichTextStyes(nodeData.text)
|
||||
}
|
||||
return hasCustomStyles
|
||||
}
|
||||
|
||||
// 一键去除自定义样式
|
||||
removeCustomStyles(node) {
|
||||
node = node || this.activeNodeList[0]
|
||||
if (!node) {
|
||||
return
|
||||
}
|
||||
const hasCustomStyles = this._handleRemoveCustomStyles(node.getData())
|
||||
if (hasCustomStyles) {
|
||||
this.reRenderNodeCheckChange(node)
|
||||
}
|
||||
}
|
||||
|
||||
// 一键去除所有节点自定义样式
|
||||
removeAllNodeCustomStyles(appointNodes) {
|
||||
appointNodes = formatDataToArray(appointNodes)
|
||||
let hasCustomStyles = false
|
||||
// 指定了节点列表,那么遍历该节点列表
|
||||
if (appointNodes.length > 0) {
|
||||
appointNodes.forEach(node => {
|
||||
const _hasCustomStyles = this._handleRemoveCustomStyles(node.getData())
|
||||
if (_hasCustomStyles) hasCustomStyles = true
|
||||
})
|
||||
} else {
|
||||
// 否则遍历整棵树
|
||||
if (!this.renderTree) return
|
||||
walk(this.renderTree, null, node => {
|
||||
const _hasCustomStyles = this._handleRemoveCustomStyles(node.data)
|
||||
if (_hasCustomStyles) hasCustomStyles = true
|
||||
// 不要忘记概要节点
|
||||
if (node.data.generalization && node.data.generalization.length > 0) {
|
||||
node.data.generalization.forEach(generalizationData => {
|
||||
const _hasCustomStyles =
|
||||
this._handleRemoveCustomStyles(generalizationData)
|
||||
if (_hasCustomStyles) hasCustomStyles = true
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
if (hasCustomStyles) {
|
||||
this.mindMap.reRender()
|
||||
}
|
||||
}
|
||||
|
||||
// 复制节点
|
||||
copy() {
|
||||
this.beingCopyData = this.copyNode()
|
||||
@@ -1074,11 +1195,12 @@ class Render {
|
||||
}
|
||||
|
||||
// 如果是富文本模式,那么某些层级变化需要更新样式
|
||||
checkNodeLayerChange(node, toNode) {
|
||||
checkNodeLayerChange(node, toNode, toNodeIsParent = false) {
|
||||
if (this.mindMap.richText) {
|
||||
const toIndex = toNodeIsParent ? toNode.layerIndex + 1 : toNode.layerIndex
|
||||
let nodeLayerChanged =
|
||||
(node.layerIndex === 1 && toNode.layerIndex !== 1) ||
|
||||
(node.layerIndex !== 1 && toNode.layerIndex === 1)
|
||||
(node.layerIndex === 1 && toIndex !== 1) ||
|
||||
(node.layerIndex !== 1 && toIndex === 1)
|
||||
if (nodeLayerChanged) {
|
||||
node.setData({
|
||||
resetRichText: true
|
||||
@@ -1108,7 +1230,15 @@ class Render {
|
||||
// 如果只选中了一个节点,删除后激活其兄弟节点或者父节点
|
||||
needActiveNode = this.getNextActiveNode()
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
let node = list[i]
|
||||
const node = list[i]
|
||||
const currentEditNode = this.textEdit.getCurrentEditNode()
|
||||
if (
|
||||
currentEditNode &&
|
||||
currentEditNode.getData('uid') === node.getData('uid')
|
||||
) {
|
||||
// 如果当前节点正在编辑中,那么先完成编辑
|
||||
this.textEdit.hideEditTextBox()
|
||||
}
|
||||
if (isAppointNodes) list.splice(i, 1)
|
||||
if (node.isGeneralization) {
|
||||
this.deleteNodeGeneralization(node)
|
||||
@@ -1256,7 +1386,7 @@ class Render {
|
||||
return !item.isRoot
|
||||
})
|
||||
nodeList.forEach(item => {
|
||||
this.checkNodeLayerChange(item, toNode)
|
||||
this.checkNodeLayerChange(item, toNode, true)
|
||||
this.removeNodeFromActiveList(item)
|
||||
removeFromParentNodeData(item)
|
||||
toNode.nodeData.children.push(item.nodeData)
|
||||
@@ -1340,6 +1470,7 @@ class Render {
|
||||
|
||||
// 展开所有
|
||||
expandAllNode() {
|
||||
if (!this.renderTree) return
|
||||
walk(
|
||||
this.renderTree,
|
||||
null,
|
||||
@@ -1358,6 +1489,7 @@ class Render {
|
||||
|
||||
// 收起所有
|
||||
unexpandAllNode() {
|
||||
if (!this.renderTree) return
|
||||
walk(
|
||||
this.renderTree,
|
||||
null,
|
||||
@@ -1378,6 +1510,7 @@ class Render {
|
||||
|
||||
// 展开到指定层级
|
||||
expandToLevel(level) {
|
||||
if (!this.renderTree) return
|
||||
walk(
|
||||
this.renderTree,
|
||||
null,
|
||||
@@ -1500,7 +1633,8 @@ class Render {
|
||||
...(data || {
|
||||
text: this.mindMap.opt.defaultGeneralizationText
|
||||
}),
|
||||
range: item.range || null
|
||||
range: item.range || null,
|
||||
uid: createUid()
|
||||
}
|
||||
let generalization = item.node.getData('generalization')
|
||||
if (generalization) {
|
||||
@@ -1596,7 +1730,7 @@ class Render {
|
||||
if (targetNode) {
|
||||
targetNode.active()
|
||||
this.moveNodeToCenter(targetNode)
|
||||
callback()
|
||||
callback(targetNode)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1611,6 +1745,11 @@ class Render {
|
||||
// 设置节点数据,并判断是否渲染
|
||||
setNodeDataRender(node, data, notRender = false) {
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, data)
|
||||
this.reRenderNodeCheckChange(node, notRender)
|
||||
}
|
||||
|
||||
// 重新节点某个节点,判断节点大小是否发生了改变,是的话触发重绘
|
||||
reRenderNodeCheckChange(node, notRender) {
|
||||
let changed = node.reRender()
|
||||
if (changed) {
|
||||
if (!notRender) this.mindMap.render()
|
||||
@@ -1643,6 +1782,10 @@ class Render {
|
||||
|
||||
// 展开到指定uid的节点
|
||||
expandToNodeUid(uid, callback = () => {}) {
|
||||
if (!this.renderTree) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
let parentsList = []
|
||||
const cache = {}
|
||||
bfsWalk(this.renderTree, (node, parent) => {
|
||||
|
||||
@@ -315,4 +315,12 @@ export default class TextEdit {
|
||||
this.textEditNode.style.transform = 'translateY(0)'
|
||||
this.showTextEdit = false
|
||||
}
|
||||
|
||||
// 获取当前正在编辑中的节点实例
|
||||
getCurrentEditNode() {
|
||||
if (this.mindMap.richText) {
|
||||
return this.mindMap.richText.node
|
||||
}
|
||||
return this.currentNode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +203,8 @@ class Node {
|
||||
|
||||
// 计算节点的宽高
|
||||
getSize() {
|
||||
this.customLeft = this.getData('customLeft') || undefined
|
||||
this.customTop = this.getData('customTop') || undefined
|
||||
this.updateGeneralization()
|
||||
this.createNodeData()
|
||||
let { width, height } = this.getNodeRect()
|
||||
@@ -420,6 +422,12 @@ class Node {
|
||||
this.isMultipleChoice = false
|
||||
return
|
||||
}
|
||||
if (
|
||||
this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate &&
|
||||
this.userList.length > 0
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.active(e)
|
||||
})
|
||||
this.group.on('mousedown', e => {
|
||||
@@ -486,16 +494,20 @@ class Node {
|
||||
})
|
||||
// 双击事件
|
||||
this.group.on('dblclick', e => {
|
||||
if (this.mindMap.opt.readonly || e.ctrlKey) {
|
||||
const { readonly, onlyOneEnableActiveNodeOnCooperate } = this.mindMap.opt
|
||||
if (readonly || e.ctrlKey) {
|
||||
return
|
||||
}
|
||||
e.stopPropagation()
|
||||
if (onlyOneEnableActiveNodeOnCooperate && this.userList.length > 0) {
|
||||
return
|
||||
}
|
||||
this.mindMap.emit('node_dblclick', this, e)
|
||||
})
|
||||
// 右键菜单事件
|
||||
this.group.on('contextmenu', e => {
|
||||
const { readonly, useLeftKeySelectionRightKeyDrag } = this.mindMap.opt
|
||||
// 按住ctrl键点击鼠标左键不知为何触发的是contextmenu事件
|
||||
// Mac上按住ctrl键点击鼠标左键不知为何触发的是contextmenu事件
|
||||
if (readonly || e.ctrlKey) {
|
||||
return
|
||||
}
|
||||
@@ -705,6 +717,9 @@ class Node {
|
||||
// 销毁节点,不但会从画布删除,而且原节点直接置空,后续无法再插回画布
|
||||
destroy() {
|
||||
if (!this.group) return
|
||||
if (this.emptyUser) {
|
||||
this.emptyUser()
|
||||
}
|
||||
this.resetWhenDelete()
|
||||
this.group.remove()
|
||||
this.removeGeneralization()
|
||||
@@ -901,6 +916,7 @@ class Node {
|
||||
childNode.getStyle('lineWidth', true)
|
||||
const color =
|
||||
childNode.getSelfInhertStyle('lineColor') ||
|
||||
this.getRainbowLineColor(childNode) ||
|
||||
childNode.getStyle('lineColor', true)
|
||||
const dasharray =
|
||||
childNode.getSelfInhertStyle('lineDasharray') ||
|
||||
@@ -917,6 +933,13 @@ class Node {
|
||||
)
|
||||
}
|
||||
|
||||
// 获取彩虹线条颜色
|
||||
getRainbowLineColor(node) {
|
||||
return this.mindMap.rainbowLines
|
||||
? this.mindMap.rainbowLines.getNodeColor(node)
|
||||
: ''
|
||||
}
|
||||
|
||||
// 移除连线
|
||||
removeLine() {
|
||||
this._lines.forEach(line => {
|
||||
@@ -1024,6 +1047,17 @@ class Node {
|
||||
return copyNodeTree({}, this, removeActiveState, removeId)
|
||||
}
|
||||
|
||||
// 获取祖先节点列表
|
||||
getAncestorNodes() {
|
||||
const list = []
|
||||
let parent = this.parent
|
||||
while (parent) {
|
||||
list.unshift(parent)
|
||||
parent = parent.parent
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// 是否存在自定义样式
|
||||
hasCustomStyle() {
|
||||
return this.style.hasCustomStyle()
|
||||
@@ -1052,6 +1086,16 @@ class Node {
|
||||
height: height * scaleY
|
||||
}
|
||||
}
|
||||
|
||||
// 高亮节点
|
||||
highlight() {
|
||||
if (this.group) this.group.addClass('smm-node-highlight')
|
||||
}
|
||||
|
||||
// 取消高亮节点
|
||||
closeHighlight() {
|
||||
if (this.group) this.group.removeClass('smm-node-highlight')
|
||||
}
|
||||
}
|
||||
|
||||
export default Node
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Rect, Polygon, Path } from '@svgdotjs/svg.js'
|
||||
import { Polygon, Path, SVG } from '@svgdotjs/svg.js'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
|
||||
// 节点形状类
|
||||
export default class Shape {
|
||||
constructor(node) {
|
||||
this.node = node
|
||||
this.mindMap = node.mindMap
|
||||
}
|
||||
|
||||
// 形状需要的padding
|
||||
@@ -106,11 +107,29 @@ export default class Shape {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建路径节点
|
||||
createPath(pathStr) {
|
||||
const { customCreateNodePath } = this.mindMap.opt
|
||||
if (customCreateNodePath) {
|
||||
return SVG(customCreateNodePath(pathStr))
|
||||
}
|
||||
return new Path().plot(pathStr)
|
||||
}
|
||||
|
||||
// 创建多边形节点
|
||||
createPolygon(points) {
|
||||
const { customCreateNodePolygon } = this.mindMap.opt
|
||||
if (customCreateNodePolygon) {
|
||||
return SVG(customCreateNodePolygon(points))
|
||||
}
|
||||
return new Polygon().plot(points)
|
||||
}
|
||||
|
||||
// 创建矩形
|
||||
createRect() {
|
||||
let { width, height } = this.getNodeSize()
|
||||
let borderRadius = this.node.style.merge('borderRadius')
|
||||
return new Path().plot(`
|
||||
const pathStr = `
|
||||
M${borderRadius},0
|
||||
L${width - borderRadius},0
|
||||
C${width - borderRadius},0 ${width},${0} ${width},${borderRadius}
|
||||
@@ -123,7 +142,8 @@ export default class Shape {
|
||||
L${0},${borderRadius}
|
||||
C${0},${borderRadius} ${0},${0} ${borderRadius},${0}
|
||||
Z
|
||||
`)
|
||||
`
|
||||
return this.createPath(pathStr)
|
||||
}
|
||||
|
||||
// 创建菱形
|
||||
@@ -139,12 +159,13 @@ export default class Shape {
|
||||
let bottomY = height
|
||||
let leftX = 0
|
||||
let leftY = halfHeight
|
||||
return new Polygon().plot([
|
||||
const points = [
|
||||
[topX, topY],
|
||||
[rightX, rightY],
|
||||
[bottomX, bottomY],
|
||||
[leftX, leftY]
|
||||
])
|
||||
]
|
||||
return this.createPolygon(points)
|
||||
}
|
||||
|
||||
// 创建平行四边形
|
||||
@@ -152,32 +173,34 @@ export default class Shape {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.getNodeSize()
|
||||
return new Polygon().plot([
|
||||
const points = [
|
||||
[paddingX, 0],
|
||||
[width, 0],
|
||||
[width - paddingX, height],
|
||||
[0, height]
|
||||
])
|
||||
]
|
||||
return this.createPolygon(points)
|
||||
}
|
||||
|
||||
// 创建圆角矩形
|
||||
createRoundedRectangle() {
|
||||
let { width, height } = this.getNodeSize()
|
||||
let halfHeight = height / 2
|
||||
return new Path().plot(`
|
||||
const pathStr = `
|
||||
M${halfHeight},0
|
||||
L${width - halfHeight},0
|
||||
A${height / 2},${height / 2} 0 0,1 ${width - halfHeight},${height}
|
||||
L${halfHeight},${height}
|
||||
A${height / 2},${height / 2} 0 0,1 ${halfHeight},${0}
|
||||
`)
|
||||
`
|
||||
return this.createPath(pathStr)
|
||||
}
|
||||
|
||||
// 创建八角矩形
|
||||
createOctagonalRectangle() {
|
||||
let w = 5
|
||||
let { width, height } = this.getNodeSize()
|
||||
return new Polygon().plot([
|
||||
const points = [
|
||||
[0, w],
|
||||
[w, 0],
|
||||
[width - w, 0],
|
||||
@@ -186,7 +209,8 @@ export default class Shape {
|
||||
[width - w, height],
|
||||
[w, height],
|
||||
[0, height - w]
|
||||
])
|
||||
]
|
||||
return this.createPolygon(points)
|
||||
}
|
||||
|
||||
// 创建外三角矩形
|
||||
@@ -194,14 +218,15 @@ export default class Shape {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.getNodeSize()
|
||||
return new Polygon().plot([
|
||||
const points = [
|
||||
[paddingX, 0],
|
||||
[width - paddingX, 0],
|
||||
[width, height / 2],
|
||||
[width - paddingX, height],
|
||||
[paddingX, height],
|
||||
[0, height / 2]
|
||||
])
|
||||
]
|
||||
return this.createPolygon(points)
|
||||
}
|
||||
|
||||
// 创建内三角矩形
|
||||
@@ -209,14 +234,15 @@ export default class Shape {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.getNodeSize()
|
||||
return new Polygon().plot([
|
||||
const points = [
|
||||
[0, 0],
|
||||
[width, 0],
|
||||
[width - paddingX / 2, height / 2],
|
||||
[width, height],
|
||||
[0, height],
|
||||
[paddingX / 2, height / 2]
|
||||
])
|
||||
]
|
||||
return this.createPolygon(points)
|
||||
}
|
||||
|
||||
// 创建椭圆
|
||||
@@ -224,12 +250,13 @@ export default class Shape {
|
||||
let { width, height } = this.getNodeSize()
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return new Path().plot(`
|
||||
const pathStr = `
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
`
|
||||
return this.createPath(pathStr)
|
||||
}
|
||||
|
||||
// 创建圆
|
||||
@@ -237,12 +264,13 @@ export default class Shape {
|
||||
let { width, height } = this.getNodeSize()
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return new Path().plot(`
|
||||
const pathStr = `
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
`
|
||||
return this.createPath(pathStr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -220,9 +220,14 @@ class Style {
|
||||
childNodeStyle._marker || childNodeStyle.createMarker()
|
||||
// 设置样式
|
||||
childNodeStyle._markerPath.stroke({ color }).fill({ color })
|
||||
line.marker('end', childNodeStyle._marker)
|
||||
// 箭头位置可能会发生改变,所以需要先删除
|
||||
line.attr('marker-start', '')
|
||||
line.attr('marker-end', '')
|
||||
const dir = childNodeStyle.merge('lineMarkerDir')
|
||||
line.marker(dir, childNodeStyle._marker)
|
||||
} else if (childNodeStyle._marker) {
|
||||
// 不显示箭头,则删除该子节点的箭头标记
|
||||
line.attr('marker-start', '')
|
||||
line.attr('marker-end', '')
|
||||
childNodeStyle._marker.remove()
|
||||
childNodeStyle._marker = null
|
||||
|
||||
@@ -67,6 +67,15 @@ function updateUserListNode() {
|
||||
} else {
|
||||
node = this.createTextAvatar(item)
|
||||
}
|
||||
node.on('click', (e) => {
|
||||
this.mindMap.emit('node_cooperate_avatar_click', item, this, node, e)
|
||||
})
|
||||
node.on('mouseenter', (e) => {
|
||||
this.mindMap.emit('node_cooperate_avatar_mouseenter', item, this, node, e)
|
||||
})
|
||||
node.on('mouseleave', (e) => {
|
||||
this.mindMap.emit('node_cooperate_avatar_mouseleave', item, this, node, e)
|
||||
})
|
||||
node.x(index * avatarSize).cy(-avatarSize / 2)
|
||||
this._userListGroup.add(node)
|
||||
})
|
||||
@@ -94,11 +103,18 @@ function removeUser(userInfo) {
|
||||
this.updateUserListNode()
|
||||
}
|
||||
|
||||
// 清空用户
|
||||
function emptyUser() {
|
||||
this.userList = []
|
||||
this.updateUserListNode()
|
||||
}
|
||||
|
||||
export default {
|
||||
createUserListNode,
|
||||
updateUserListNode,
|
||||
createTextAvatar,
|
||||
createImageAvatar,
|
||||
addUser,
|
||||
removeUser
|
||||
removeUser,
|
||||
emptyUser
|
||||
}
|
||||
|
||||
@@ -92,7 +92,13 @@ function createIconNode() {
|
||||
}
|
||||
node.size(iconSize, iconSize)
|
||||
node.on('click', e => {
|
||||
this.mindMap.emit('node_icon_click', this, item, e)
|
||||
this.mindMap.emit('node_icon_click', this, item, e, node)
|
||||
})
|
||||
node.on('mouseenter', e => {
|
||||
this.mindMap.emit('node_icon_mouseenter', this, item, e, node)
|
||||
})
|
||||
node.on('mouseleave', e => {
|
||||
this.mindMap.emit('node_icon_mouseleave', this, item, e, node)
|
||||
})
|
||||
return {
|
||||
node,
|
||||
@@ -128,7 +134,12 @@ function createRichTextNode() {
|
||||
// 如果是富文本那么线移除内联样式
|
||||
text = removeHtmlStyle(text)
|
||||
// 再添加新的内联样式
|
||||
let _text = text
|
||||
text = addHtmlStyle(text, 'span', style)
|
||||
// 给span添加样式没有成功,则尝试给strong标签添加样式
|
||||
if (text === _text) {
|
||||
text = addHtmlStyle(text, 'strong', style)
|
||||
}
|
||||
} else {
|
||||
// 非富文本
|
||||
text = `<p><span style="${style}">${text}</span></p>`
|
||||
@@ -159,7 +170,7 @@ function createRichTextNode() {
|
||||
height = elTmp.getBoundingClientRect().height
|
||||
div.innerHTML = html
|
||||
}
|
||||
width = Math.ceil(width) + 1 // 修复getBoundingClientRect方法对实际宽度是小数的元素获取到的值是整数,导致宽度不够文本发生换行的问题
|
||||
width = Math.min(Math.ceil(width) + 1, textAutoWrapWidth) // 修复getBoundingClientRect方法对实际宽度是小数的元素获取到的值是整数,导致宽度不够文本发生换行的问题
|
||||
height = Math.ceil(height)
|
||||
g.attr('data-width', width)
|
||||
g.attr('data-height', height)
|
||||
@@ -181,6 +192,9 @@ function createTextNode() {
|
||||
if (this.getData('richText')) {
|
||||
return this.createRichTextNode()
|
||||
}
|
||||
if (this.getData('resetRichText')) {
|
||||
delete this.nodeData.data.resetRichText
|
||||
}
|
||||
let g = new G()
|
||||
let fontSize = this.getStyle('fontSize', false)
|
||||
let lineHeight = this.getStyle('lineHeight', false)
|
||||
@@ -222,7 +236,7 @@ function createTextNode() {
|
||||
g.add(node)
|
||||
})
|
||||
let { width, height } = g.bbox()
|
||||
width = Math.ceil(width)
|
||||
width = Math.min(Math.ceil(width), maxWidth)
|
||||
height = Math.ceil(height)
|
||||
g.attr('data-width', width)
|
||||
g.attr('data-height', height)
|
||||
|
||||
@@ -104,7 +104,7 @@ function renderGeneralization() {
|
||||
|
||||
// 更新节点概要数据
|
||||
function updateGeneralizationData() {
|
||||
const childrenLength = this.children.length
|
||||
const childrenLength = this.nodeData.children.length
|
||||
const list = this.formatGetGeneralization()
|
||||
const newList = []
|
||||
list.forEach(item => {
|
||||
|
||||
@@ -106,7 +106,7 @@ class View {
|
||||
}
|
||||
} else {
|
||||
// 2.鼠标滚轮事件控制画布移动
|
||||
const step = isTouchPad ? 5 : mousewheelMoveStep
|
||||
const step = isTouchPad ? 10 : mousewheelMoveStep
|
||||
let mx = 0
|
||||
let my = 0
|
||||
// 上移
|
||||
@@ -128,6 +128,10 @@ class View {
|
||||
this.translateXY(mx, my)
|
||||
}
|
||||
})
|
||||
this.mindMap.on('resize', () => {
|
||||
if (!this.checkNeedMindMapInCanvas()) return
|
||||
this.transform()
|
||||
})
|
||||
}
|
||||
|
||||
// 获取当前变换状态数据
|
||||
@@ -313,20 +317,31 @@ class View {
|
||||
this.translateXY(newX, newY)
|
||||
}
|
||||
|
||||
// 将思维导图限制在画布内
|
||||
limitMindMapInCanvas() {
|
||||
// 判断是否需要将思维导图限制在画布内
|
||||
checkNeedMindMapInCanvas() {
|
||||
const { isLimitMindMapInCanvasWhenHasScrollbar, isLimitMindMapInCanvas } =
|
||||
this.mindMap.opt
|
||||
// 如果注册了滚动条插件,那么使用isLimitMindMapInCanvasWhenHasScrollbar配置
|
||||
if (this.mindMap.scrollbar) {
|
||||
if (!isLimitMindMapInCanvasWhenHasScrollbar) return
|
||||
return isLimitMindMapInCanvasWhenHasScrollbar
|
||||
} else {
|
||||
// 否则使用isLimitMindMapInCanvas配置
|
||||
if (!isLimitMindMapInCanvas) return
|
||||
return isLimitMindMapInCanvas
|
||||
}
|
||||
}
|
||||
|
||||
// 将思维导图限制在画布内
|
||||
limitMindMapInCanvas() {
|
||||
if (!this.checkNeedMindMapInCanvas()) return
|
||||
|
||||
let { scale, left, top, right, bottom } = this.getPositionLimit()
|
||||
|
||||
// 画布宽高改变了,但是思维导图元素变换的中心点依旧是原有位置,所以需要加上中心点变化量
|
||||
const centerXChange =
|
||||
((this.mindMap.width - this.mindMap.initWidth) / 2) * scale
|
||||
const centerYChange =
|
||||
((this.mindMap.height - this.mindMap.initHeight) / 2) * scale
|
||||
|
||||
// 如果缩放值改变了
|
||||
const scaleRatio = this.scale / scale
|
||||
left *= scaleRatio
|
||||
@@ -338,10 +353,10 @@ class View {
|
||||
const centerX = this.mindMap.width / 2
|
||||
const centerY = this.mindMap.height / 2
|
||||
const scaleOffset = this.scale - 1
|
||||
left -= scaleOffset * centerX
|
||||
right -= scaleOffset * centerX
|
||||
top -= scaleOffset * centerY
|
||||
bottom -= scaleOffset * centerY
|
||||
left -= scaleOffset * centerX - centerXChange
|
||||
right -= scaleOffset * centerX - centerXChange
|
||||
top -= scaleOffset * centerY - centerYChange
|
||||
bottom -= scaleOffset * centerY - centerYChange
|
||||
|
||||
// 判断是否超出边界
|
||||
if (this.x > left) {
|
||||
|
||||
@@ -85,8 +85,12 @@ class Base {
|
||||
newNode.layerIndex = layerIndex
|
||||
this.cacheNode(data._node.uid, newNode)
|
||||
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
|
||||
// 主题或主题配置改变了需要重新计算节点大小和布局
|
||||
if (this.checkIsNeedResizeSources() || isLayerTypeChange) {
|
||||
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本等情况需要重新计算节点大小和布局
|
||||
if (
|
||||
this.checkIsNeedResizeSources() ||
|
||||
isLayerTypeChange ||
|
||||
newNode.getData('resetRichText')
|
||||
) {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
}
|
||||
@@ -113,9 +117,14 @@ class Base {
|
||||
data._node = newNode
|
||||
// 主题或主题配置改变了需要重新计算节点大小和布局
|
||||
const isResizeSource = this.checkIsNeedResizeSources()
|
||||
// 节点数据改变了需要重新计算节点大小和布局
|
||||
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本,节点数据改变了等情况需要重新计算节点大小和布局
|
||||
const isNodeDataChange = lastData !== JSON.stringify(data.data)
|
||||
if (isResizeSource || isNodeDataChange || isLayerTypeChange) {
|
||||
if (
|
||||
isResizeSource ||
|
||||
isNodeDataChange ||
|
||||
isLayerTypeChange ||
|
||||
newNode.getData('resetRichText')
|
||||
) {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
}
|
||||
@@ -295,6 +304,65 @@ class Base {
|
||||
return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`
|
||||
}
|
||||
|
||||
// 根据a,b两个点的位置,计算去除圆角大小后的新的b点
|
||||
computeNewPoint(a, b, radius = 0) {
|
||||
// x坐标相同
|
||||
if (a[0] === b[0]) {
|
||||
// b在a下方
|
||||
if (b[1] > a[1]) {
|
||||
return [b[0], b[1] - radius]
|
||||
} else {
|
||||
// b在a上方
|
||||
return [b[0], b[1] + radius]
|
||||
}
|
||||
} else if (a[1] === b[1]) {
|
||||
// y坐标相同
|
||||
// b在a右边
|
||||
if (b[0] > a[0]) {
|
||||
return [b[0] - radius, b[1]]
|
||||
} else {
|
||||
return [b[0] + radius, b[1]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建一段折线路径
|
||||
// 最后一个拐角支持圆角
|
||||
createFoldLine(list) {
|
||||
const { lineRadius } = this.mindMap.themeConfig
|
||||
const len = list.length
|
||||
let path = ''
|
||||
let radiusPath = ''
|
||||
if (len >= 3 && lineRadius > 0) {
|
||||
const start = list[len - 3]
|
||||
const center = list[len - 2]
|
||||
const end = list[len - 1]
|
||||
// 如果三点在一条直线,那么不用处理
|
||||
const isOneLine =
|
||||
(start[0] === center[0] && center[0] === end[0]) ||
|
||||
(start[1] === center[1] && center[1] === end[1])
|
||||
if (!isOneLine) {
|
||||
const cStart = this.computeNewPoint(start, center, lineRadius)
|
||||
const cEnd = this.computeNewPoint(end, center, lineRadius)
|
||||
radiusPath = `Q ${center[0]},${center[1]} ${cEnd[0]},${cEnd[1]}`
|
||||
list.splice(len - 2, 1, cStart, radiusPath)
|
||||
}
|
||||
}
|
||||
list.forEach((item, index) => {
|
||||
if (typeof item === 'string') {
|
||||
path += item
|
||||
} else {
|
||||
const [x, y] = item
|
||||
if (index === 0) {
|
||||
path += `M ${x},${y}`
|
||||
} else {
|
||||
path += `L ${x},${y}`
|
||||
}
|
||||
}
|
||||
})
|
||||
return path
|
||||
}
|
||||
|
||||
// 获取节点的marginX
|
||||
getMarginX(layerIndex) {
|
||||
const { themeConfig, opt } = this.mindMap
|
||||
@@ -446,9 +514,19 @@ class Base {
|
||||
|
||||
// 设置连线样式
|
||||
setLineStyle(style, line, path, childNode) {
|
||||
line.plot(path)
|
||||
line.plot(this.transformPath(path))
|
||||
style && style(line, childNode, true)
|
||||
}
|
||||
|
||||
// 转换路径,可以转换成特殊风格的线条样式
|
||||
transformPath(path) {
|
||||
const { customTransformNodeLinePath } = this.mindMap.opt
|
||||
if (customTransformNodeLinePath) {
|
||||
return customTransformNodeLinePath(path)
|
||||
} else {
|
||||
return path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Base
|
||||
|
||||
@@ -240,14 +240,14 @@ class CatalogOrganization extends Base {
|
||||
// 父节点的竖线
|
||||
let line1 = this.lineDraw.path()
|
||||
node.style.line(line1)
|
||||
line1.plot(`M ${x1},${y1} L ${x1},${y1 + s1}`)
|
||||
line1.plot(this.transformPath(`M ${x1},${y1} L ${x1},${y1 + s1}`))
|
||||
node._lines.push(line1)
|
||||
style && style(line1, node)
|
||||
// 水平线
|
||||
if (len > 0) {
|
||||
let lin2 = this.lineDraw.path()
|
||||
node.style.line(lin2)
|
||||
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
|
||||
lin2.plot(this.transformPath(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`))
|
||||
node._lines.push(lin2)
|
||||
style && style(lin2, node)
|
||||
}
|
||||
@@ -311,7 +311,9 @@ class CatalogOrganization extends Base {
|
||||
if (maxy < y1 + expandBtnSize) {
|
||||
lin2.hide()
|
||||
} else {
|
||||
lin2.plot(`M ${x2},${y1 + expandBtnSize} L ${x2},${maxy}`)
|
||||
lin2.plot(
|
||||
this.transformPath(`M ${x2},${y1 + expandBtnSize} L ${x2},${maxy}`)
|
||||
)
|
||||
lin2.show()
|
||||
}
|
||||
node._lines.push(lin2)
|
||||
@@ -349,7 +351,7 @@ class CatalogOrganization extends Base {
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationLine.plot(this.transformPath(path))
|
||||
item.generalizationNode.left = right + generalizationNodeMargin
|
||||
item.generalizationNode.top =
|
||||
top + (bottom - top - item.generalizationNode.height) / 2
|
||||
|
||||
@@ -253,15 +253,19 @@ class Fishbone extends Base {
|
||||
let line = this.lineDraw.path()
|
||||
if (this.checkIsTop(item)) {
|
||||
line.plot(
|
||||
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${
|
||||
item.left
|
||||
},${item.top + item.height}`
|
||||
this.transformPath(
|
||||
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${
|
||||
item.left
|
||||
},${item.top + item.height}`
|
||||
)
|
||||
)
|
||||
} else {
|
||||
line.plot(
|
||||
`M ${nodeLineX - offsetX},${item.top - offset} L ${nodeLineX},${
|
||||
item.top
|
||||
}`
|
||||
this.transformPath(
|
||||
`M ${nodeLineX - offsetX},${item.top - offset} L ${nodeLineX},${
|
||||
item.top
|
||||
}`
|
||||
)
|
||||
)
|
||||
}
|
||||
node.style.line(line)
|
||||
@@ -273,9 +277,11 @@ class Fishbone extends Base {
|
||||
let offset = node.height / 2 + this.getMarginY(node.layerIndex + 1)
|
||||
let line = this.lineDraw.path()
|
||||
line.plot(
|
||||
`M ${node.left + node.width},${nodeHalfTop} L ${
|
||||
maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
|
||||
},${nodeHalfTop}`
|
||||
this.transformPath(
|
||||
`M ${node.left + node.width},${nodeHalfTop} L ${
|
||||
maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
|
||||
},${nodeHalfTop}`
|
||||
)
|
||||
)
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
@@ -372,7 +378,7 @@ class Fishbone extends Base {
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationLine.plot(this.transformPath(path))
|
||||
item.generalizationNode.left = right + generalizationNodeMargin
|
||||
item.generalizationNode.top =
|
||||
top + (bottom - top - item.generalizationNode.height) / 2
|
||||
|
||||
@@ -178,9 +178,12 @@ class LogicalStructure extends Base {
|
||||
let nodeUseLineStyleOffset = nodeUseLineStyle ? item.width : 0
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
|
||||
let path = `M ${x1},${y1} L ${x1 + s1},${y1} L ${x1 + s1},${y2} L ${
|
||||
x2 + nodeUseLineStyleOffset
|
||||
},${y2}`
|
||||
let path = this.createFoldLine([
|
||||
[x1, y1],
|
||||
[x1 + s1, y1],
|
||||
[x1 + s1, y2],
|
||||
[x2 + nodeUseLineStyleOffset, y2]
|
||||
])
|
||||
this.setLineStyle(style, lines[index], path, item)
|
||||
})
|
||||
}
|
||||
@@ -194,10 +197,12 @@ class LogicalStructure extends Base {
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
const { nodeUseLineStyle } = this.mindMap.themeConfig
|
||||
node.children.forEach((item, index) => {
|
||||
let x1 =
|
||||
node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize
|
||||
if (node.layerIndex === 0) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let x1 = left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
@@ -221,10 +226,19 @@ class LogicalStructure extends Base {
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
const {
|
||||
nodeUseLineStyle,
|
||||
rootLineStartPositionKeepSameInCurve,
|
||||
rootLineKeepSameInCurve
|
||||
} = this.mindMap.themeConfig
|
||||
node.children.forEach((item, index) => {
|
||||
if (node.layerIndex === 0) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let x1 =
|
||||
node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize
|
||||
node.layerIndex === 0 && !rootLineStartPositionKeepSameInCurve
|
||||
? left + width / 2
|
||||
: left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
@@ -235,7 +249,7 @@ class LogicalStructure extends Base {
|
||||
let nodeUseLineStylePath = nodeUseLineStyle
|
||||
? ` L ${item.left + item.width},${y2}`
|
||||
: ''
|
||||
if (node.isRoot && !this.mindMap.themeConfig.rootLineKeepSameInCurve) {
|
||||
if (node.isRoot && !rootLineKeepSameInCurve) {
|
||||
path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath
|
||||
} else {
|
||||
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath
|
||||
|
||||
@@ -240,9 +240,12 @@ class MindMap extends Base {
|
||||
let y2 = item.top + item.height / 2
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
|
||||
let path = `M ${x1},${y1} L ${x1 + _s},${y1} L ${x1 + _s},${y2} L ${
|
||||
x2 + nodeUseLineStyleOffset
|
||||
},${y2}`
|
||||
let path = this.createFoldLine([
|
||||
[x1, y1],
|
||||
[x1 + _s, y1],
|
||||
[x1 + _s, y2],
|
||||
[x2 + nodeUseLineStyleOffset, y2]
|
||||
])
|
||||
this.setLineStyle(style, lines[index], path, item)
|
||||
})
|
||||
}
|
||||
@@ -256,12 +259,13 @@ class MindMap extends Base {
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
const { nodeUseLineStyle } = this.mindMap.themeConfig
|
||||
node.children.forEach((item, index) => {
|
||||
if (node.layerIndex === 0) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let x1 =
|
||||
node.layerIndex === 0
|
||||
? left + width / 2
|
||||
: item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? left - expandBtnSize
|
||||
: left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
@@ -295,10 +299,17 @@ class MindMap extends Base {
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let nodeUseLineStyle = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
const {
|
||||
nodeUseLineStyle,
|
||||
rootLineKeepSameInCurve,
|
||||
rootLineStartPositionKeepSameInCurve
|
||||
} = this.mindMap.themeConfig
|
||||
node.children.forEach((item, index) => {
|
||||
if (node.layerIndex === 0) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
let x1 =
|
||||
node.layerIndex === 0
|
||||
node.layerIndex === 0 && !rootLineStartPositionKeepSameInCurve
|
||||
? left + width / 2
|
||||
: item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? left - expandBtnSize
|
||||
@@ -314,14 +325,14 @@ class MindMap extends Base {
|
||||
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStylePath = ''
|
||||
if (this.mindMap.themeConfig.nodeUseLineStyle) {
|
||||
if (nodeUseLineStyle) {
|
||||
if (item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
nodeUseLineStylePath = ` L ${item.left},${y2}`
|
||||
} else {
|
||||
nodeUseLineStylePath = ` L ${item.left + item.width},${y2}`
|
||||
}
|
||||
}
|
||||
if (node.isRoot && !this.mindMap.themeConfig.rootLineKeepSameInCurve) {
|
||||
if (node.isRoot && !rootLineKeepSameInCurve) {
|
||||
path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath
|
||||
} else {
|
||||
path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath
|
||||
|
||||
@@ -161,13 +161,14 @@ class OrganizationStructure extends Base {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height } = node
|
||||
const { nodeUseLineStyle } = this.mindMap.themeConfig
|
||||
let x1 = left + width / 2
|
||||
let y1 = top + height
|
||||
node.children.forEach((item, index) => {
|
||||
let x2 = item.left + item.width / 2
|
||||
let y2 = item.top
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
|
||||
let nodeUseLineStylePath = nodeUseLineStyle
|
||||
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
|
||||
: ''
|
||||
let path = `M ${x1},${y1} L ${x2},${y2}` + nodeUseLineStylePath
|
||||
@@ -213,14 +214,16 @@ class OrganizationStructure extends Base {
|
||||
let line1 = this.lineDraw.path()
|
||||
node.style.line(line1)
|
||||
expandBtnSize = len > 0 && !isRoot ? expandBtnSize : 0
|
||||
line1.plot(`M ${x1},${y1 + expandBtnSize} L ${x1},${y1 + s1}`)
|
||||
line1.plot(
|
||||
this.transformPath(`M ${x1},${y1 + expandBtnSize} L ${x1},${y1 + s1}`)
|
||||
)
|
||||
node._lines.push(line1)
|
||||
style && style(line1, node)
|
||||
// 水平线
|
||||
if (len > 0) {
|
||||
let lin2 = this.lineDraw.path()
|
||||
node.style.line(lin2)
|
||||
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
|
||||
lin2.plot(this.transformPath(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`))
|
||||
node._lines.push(lin2)
|
||||
style && style(lin2, node)
|
||||
}
|
||||
@@ -253,7 +256,7 @@ class OrganizationStructure extends Base {
|
||||
let cx = x1 + (x2 - x1) / 2
|
||||
let cy = y1 + 20
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationLine.plot(this.transformPath(path))
|
||||
item.generalizationNode.top = bottom + generalizationNodeMargin
|
||||
item.generalizationNode.left =
|
||||
left + (right - left - item.generalizationNode.width) / 2
|
||||
|
||||
@@ -274,9 +274,13 @@ class Timeline extends Base {
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
) {
|
||||
line.plot(`M ${x},${top} L ${x},${miny}`)
|
||||
line.plot(this.transformPath(`M ${x},${top} L ${x},${miny}`))
|
||||
} else {
|
||||
line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`)
|
||||
line.plot(
|
||||
this.transformPath(
|
||||
`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`
|
||||
)
|
||||
)
|
||||
}
|
||||
node.style.line(line)
|
||||
node._lines.push(line)
|
||||
@@ -325,9 +329,10 @@ class Timeline extends Base {
|
||||
let cx = x1 + 20
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationLine.plot(this.transformPath(path))
|
||||
item.generalizationNode.left = right + generalizationNodeMargin
|
||||
item.generalizationNode.top = top + (bottom - top - item.generalizationNode.height) / 2
|
||||
item.generalizationNode.top =
|
||||
top + (bottom - top - item.generalizationNode.height) / 2
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -259,11 +259,12 @@ class VerticalTimeline extends Base {
|
||||
node.children.forEach((item, index) => {
|
||||
let itemLeft = item.left
|
||||
let itemYCenter = item.top + item.height / 2
|
||||
let path = `
|
||||
M ${nodeRight},${nodeYCenter}
|
||||
L ${nodeRight + offset},${nodeYCenter}
|
||||
L ${nodeRight + offset},${itemYCenter}
|
||||
L ${itemLeft},${itemYCenter}`
|
||||
let path = this.createFoldLine([
|
||||
[nodeRight, nodeYCenter],
|
||||
[nodeRight + offset, nodeYCenter],
|
||||
[nodeRight + offset, itemYCenter],
|
||||
[itemLeft, itemYCenter]
|
||||
])
|
||||
this.setLineStyle(style, lines[index], path, item)
|
||||
})
|
||||
} else {
|
||||
@@ -274,11 +275,12 @@ class VerticalTimeline extends Base {
|
||||
node.children.forEach((item, index) => {
|
||||
let itemRight = item.left + item.width
|
||||
let itemYCenter = item.top + item.height / 2
|
||||
let path = `
|
||||
M ${nodeLeft},${nodeYCenter}
|
||||
L ${nodeLeft - offset},${nodeYCenter}
|
||||
L ${nodeLeft - offset},${itemYCenter}
|
||||
L ${itemRight},${itemYCenter}`
|
||||
let path = this.createFoldLine([
|
||||
[nodeLeft, nodeYCenter],
|
||||
[nodeLeft - offset, nodeYCenter],
|
||||
[nodeLeft - offset, itemYCenter],
|
||||
[itemRight, itemYCenter]
|
||||
])
|
||||
this.setLineStyle(style, lines[index], path, item)
|
||||
})
|
||||
}
|
||||
@@ -396,7 +398,7 @@ class VerticalTimeline extends Base {
|
||||
let cx = x1 + (isLeft ? -20 : 20)
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
item.generalizationLine.plot(path)
|
||||
item.generalizationLine.plot(this.transformPath(path))
|
||||
item.generalizationNode.left =
|
||||
x +
|
||||
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -
|
||||
|
||||
@@ -36,12 +36,18 @@ export default {
|
||||
}) {
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
line.plot(
|
||||
`M ${x},${top} L ${x + lineLength},${
|
||||
top - Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
ctx.transformPath(
|
||||
`M ${x},${top} L ${x + lineLength},${
|
||||
top - Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
)
|
||||
)
|
||||
} else {
|
||||
line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`)
|
||||
line.plot(
|
||||
ctx.transformPath(
|
||||
`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
computedLeftTopValue({ layerIndex, node, ctx }) {
|
||||
@@ -135,14 +141,16 @@ export default {
|
||||
renderLine({ node, line, top, x, lineLength, height, miny, ctx }) {
|
||||
if (node.parent && node.parent.isRoot) {
|
||||
line.plot(
|
||||
`M ${x},${top + height} L ${x + lineLength},${
|
||||
top +
|
||||
height +
|
||||
Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
ctx.transformPath(
|
||||
`M ${x},${top + height} L ${x + lineLength},${
|
||||
top +
|
||||
height +
|
||||
Math.tan(degToRad(ctx.mindMap.opt.fishboneDeg)) * lineLength
|
||||
}`
|
||||
)
|
||||
)
|
||||
} else {
|
||||
line.plot(`M ${x},${top} L ${x},${miny}`)
|
||||
line.plot(ctx.transformPath(`M ${x},${top} L ${x},${miny}`))
|
||||
}
|
||||
},
|
||||
computedLeftTopValue({ layerIndex, node, ctx }) {
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { walk } from '../utils'
|
||||
import { walk, nodeRichTextToTextWithWrap } from '../utils'
|
||||
|
||||
let el = null
|
||||
const getText = str => {
|
||||
if (!el) {
|
||||
el = document.createElement('div')
|
||||
}
|
||||
el.innerHTML = str
|
||||
return el.textContent
|
||||
const getNodeText = data => {
|
||||
return data.richText ? nodeRichTextToTextWithWrap(data.text) : data.text
|
||||
}
|
||||
|
||||
const getTitleMark = level => {
|
||||
@@ -24,21 +19,22 @@ export const transformToMarkdown = root => {
|
||||
root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
let level = layerIndex + 1
|
||||
let text = node.data.richText ? getText(node.data.text) : node.data.text
|
||||
const level = layerIndex + 1
|
||||
if (level <= 6) {
|
||||
content += getTitleMark(level)
|
||||
} else {
|
||||
content += getIndentMark(level)
|
||||
}
|
||||
content += ' ' + text
|
||||
content += ' ' + getNodeText(node.data)
|
||||
// 概要
|
||||
let generalization = node.data.generalization
|
||||
if (generalization && generalization.text) {
|
||||
let generalizationText = generalization.richText
|
||||
? getText(generalization.text)
|
||||
: generalization.text
|
||||
content += `[${generalizationText}]`
|
||||
const generalization = node.data.generalization
|
||||
if (Array.isArray(generalization)) {
|
||||
content += generalization.map(item => {
|
||||
return ` [${getNodeText(item)}]`
|
||||
})
|
||||
} else if (generalization && generalization.text) {
|
||||
const generalizationText = getNodeText(generalization)
|
||||
content += ` [${generalizationText}]`
|
||||
}
|
||||
content += '\n\n'
|
||||
// 备注
|
||||
|
||||
35
simple-mind-map/src/parse/toTxt.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { walk, nodeRichTextToTextWithWrap } from '../utils'
|
||||
|
||||
const getNodeText = data => {
|
||||
return data.richText ? nodeRichTextToTextWithWrap(data.text) : data.text
|
||||
}
|
||||
|
||||
const getIndent = level => {
|
||||
return new Array(level).fill(' ').join('')
|
||||
}
|
||||
|
||||
// 转换成txt格式
|
||||
export const transformToTxt = root => {
|
||||
let content = ''
|
||||
walk(
|
||||
root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
content += getIndent(layerIndex)
|
||||
content += ' ' + getNodeText(node.data)
|
||||
// 概要
|
||||
const generalization = node.data.generalization
|
||||
if (Array.isArray(generalization)) {
|
||||
content += generalization.map(item => {
|
||||
return ` [${getNodeText(item)}]`
|
||||
})
|
||||
} else if (generalization && generalization.text) {
|
||||
content += ` [${getNodeText(generalization)}]`
|
||||
}
|
||||
content += '\n\n'
|
||||
},
|
||||
() => {},
|
||||
true
|
||||
)
|
||||
return content
|
||||
}
|
||||
@@ -198,7 +198,7 @@ const transformOldXmind = content => {
|
||||
childrenItem.elements.length > 0
|
||||
) {
|
||||
const children = getElementsByType(childrenItem.elements, 'attached')
|
||||
children.forEach((item, index) => {
|
||||
;(children || []).forEach((item, index) => {
|
||||
const newChild = {}
|
||||
newNode.children.push(newChild)
|
||||
if (childrenSummary[index]) {
|
||||
|
||||
@@ -339,6 +339,10 @@ class AssociativeLine {
|
||||
dasharray: [6, 4]
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
// 箭头
|
||||
this.markerPath
|
||||
.stroke({ color: associativeLineColor })
|
||||
.fill({ color: associativeLineColor })
|
||||
this.creatingLine.marker('end', this.marker)
|
||||
}
|
||||
|
||||
@@ -451,6 +455,17 @@ class AssociativeLine {
|
||||
endPoint.x,
|
||||
endPoint.y
|
||||
)
|
||||
// 检查是否存在固定位置的配置
|
||||
const { associativeLineInitPointsPosition } = this.mindMap.opt
|
||||
if (associativeLineInitPointsPosition) {
|
||||
const { from, to } = associativeLineInitPointsPosition
|
||||
if (from) {
|
||||
startPoint.dir = from
|
||||
}
|
||||
if (to) {
|
||||
endPoint.dir = to
|
||||
}
|
||||
}
|
||||
let offsetList =
|
||||
fromNode.getData('associativeLineTargetControlOffsets') || []
|
||||
// 保存的实际是控制点和端点的差值,否则当节点位置改变了,控制点还是原来的位置,连线就不对了
|
||||
|
||||
@@ -28,6 +28,8 @@ class Cooperate {
|
||||
this.currentData = null
|
||||
// 用户信息
|
||||
this.userInfo = null
|
||||
// 是否正在重新设置思维导图数据
|
||||
this.isSetData = false
|
||||
// 绑定事件
|
||||
this.bindEvent()
|
||||
// 处理实例化时传入的思维导图数据
|
||||
@@ -92,8 +94,8 @@ class Cooperate {
|
||||
this.mindMap.on('node_tree_render_end', this.onNodeTreeRenderEnd)
|
||||
|
||||
// 监听设置思维导图数据事件
|
||||
this.initData = this.initData.bind(this)
|
||||
this.mindMap.on('set_data', this.initData)
|
||||
this.onSetData = this.onSetData.bind(this)
|
||||
this.mindMap.on('set_data', this.onSetData)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
@@ -104,7 +106,7 @@ class Cooperate {
|
||||
this.mindMap.off('data_change', this.onDataChange)
|
||||
this.mindMap.off('node_active', this.onNodeActive)
|
||||
this.mindMap.off('node_tree_render_end', this.onNodeTreeRenderEnd)
|
||||
this.mindMap.off('set_data', this.initData)
|
||||
this.mindMap.off('set_data', this.onSetData)
|
||||
this.ydoc.destroy()
|
||||
}
|
||||
|
||||
@@ -118,35 +120,63 @@ class Cooperate {
|
||||
const res = transformObjectToTreeData(data)
|
||||
if (!res) return
|
||||
// 更新思维导图画布
|
||||
this.mindMap.renderer.setData(res)
|
||||
this.mindMap.render()
|
||||
this.mindMap.command.addHistory()
|
||||
this.mindMap.updateData(res)
|
||||
}
|
||||
|
||||
// 当前思维导图改变后的处理,触发同步
|
||||
onDataChange(data) {
|
||||
if (this.isSetData) {
|
||||
this.isSetData = false
|
||||
return
|
||||
}
|
||||
const res = transformTreeDataToObject(data)
|
||||
this.updateChanges(res)
|
||||
}
|
||||
|
||||
// 找出更新点
|
||||
updateChanges(data) {
|
||||
const { beforeCooperateUpdate } = this.mindMap.opt
|
||||
const oldData = this.currentData
|
||||
this.currentData = data
|
||||
this.ydoc.transact(() => {
|
||||
// 找出新增的或修改的
|
||||
const createOrUpdateList = []
|
||||
Object.keys(data).forEach(uid => {
|
||||
// 新增的或已经存在的,如果数据发生了改变
|
||||
if (!oldData[uid] || !isSameObject(oldData[uid], data[uid])) {
|
||||
this.ymap.set(uid, data[uid])
|
||||
createOrUpdateList.push({
|
||||
uid,
|
||||
data: data[uid],
|
||||
oldData: oldData[uid]
|
||||
})
|
||||
}
|
||||
})
|
||||
if (beforeCooperateUpdate && createOrUpdateList.length > 0) {
|
||||
beforeCooperateUpdate({
|
||||
type: 'createOrUpdate',
|
||||
list: createOrUpdateList,
|
||||
data
|
||||
})
|
||||
}
|
||||
createOrUpdateList.forEach(item => {
|
||||
this.ymap.set(item.uid, item.data)
|
||||
})
|
||||
// 找出删除的
|
||||
const deleteList = []
|
||||
Object.keys(oldData).forEach(uid => {
|
||||
if (!data[uid]) {
|
||||
this.ymap.delete(uid)
|
||||
deleteList.push({ uid, data: oldData[uid] })
|
||||
}
|
||||
})
|
||||
if (beforeCooperateUpdate && deleteList.length > 0) {
|
||||
beforeCooperateUpdate({
|
||||
type: 'delete',
|
||||
list: deleteList
|
||||
})
|
||||
}
|
||||
deleteList.forEach(item => {
|
||||
this.ymap.delete(item.uid)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -177,6 +207,12 @@ class Cooperate {
|
||||
this.waitNodeUidMap = {}
|
||||
}
|
||||
|
||||
// 监听思维导图数据的重新设置事件
|
||||
onSetData(data) {
|
||||
this.isSetData = true
|
||||
this.initData(data)
|
||||
}
|
||||
|
||||
// 设置用户信息
|
||||
/**
|
||||
* {
|
||||
@@ -220,6 +256,7 @@ class Cooperate {
|
||||
// 设置当前数据
|
||||
const data = Array.from(this.awareness.getStates().values())
|
||||
this.currentAwarenessData = data
|
||||
this.waitNodeUidMap = {}
|
||||
walk(data, (uid, node, userInfo) => {
|
||||
// 不显示自己
|
||||
if (userInfo.id === this.userInfo.id) return
|
||||
|
||||
@@ -10,6 +10,7 @@ import { SVG } from '@svgdotjs/svg.js'
|
||||
import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas'
|
||||
import { transformToMarkdown } from '../parse/toMarkdown'
|
||||
import { ERROR_TYPES } from '../constants/constant'
|
||||
import { transformToTxt } from '../parse/toTxt'
|
||||
|
||||
// 导出插件
|
||||
class Export {
|
||||
@@ -47,11 +48,13 @@ class Export {
|
||||
|
||||
// 获取svg数据
|
||||
async getSvgData() {
|
||||
let { exportPaddingX, exportPaddingY, errorHandler, resetCss } =
|
||||
let { exportPaddingX, exportPaddingY, errorHandler, resetCss, addContentToHeader, addContentToFooter } =
|
||||
this.mindMap.opt
|
||||
let { svg, svgHTML } = this.mindMap.getSvgData({
|
||||
paddingX: exportPaddingX,
|
||||
paddingY: exportPaddingY
|
||||
paddingY: exportPaddingY,
|
||||
addContentToHeader,
|
||||
addContentToFooter
|
||||
})
|
||||
// svg的image标签,把图片的url转换成data:url类型,否则导出会丢失图片
|
||||
const task1 = this.createTransformImgTaskList(
|
||||
@@ -294,6 +297,15 @@ class Export {
|
||||
const res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
|
||||
// txt文件
|
||||
async txt() {
|
||||
const data = this.mindMap.getData()
|
||||
const content = transformToTxt(data)
|
||||
const blob = new Blob([content])
|
||||
const res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
Export.instanceName = 'doExport'
|
||||
|
||||
89
simple-mind-map/src/plugins/RainbowLines.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import { walk, getNodeDataIndex } from '../utils/index'
|
||||
|
||||
const defaultColorsList = [
|
||||
'rgb(255, 213, 73)',
|
||||
'rgb(255, 136, 126)',
|
||||
'rgb(107, 225, 141)',
|
||||
'rgb(151, 171, 255)',
|
||||
'rgb(129, 220, 242)',
|
||||
'rgb(255, 163, 125)',
|
||||
'rgb(152, 132, 234)'
|
||||
]
|
||||
|
||||
// 彩虹线条插件
|
||||
class RainbowLines {
|
||||
constructor({ mindMap }) {
|
||||
this.mindMap = mindMap
|
||||
}
|
||||
|
||||
// 更新彩虹线条配置
|
||||
updateRainLinesConfig(config = {}) {
|
||||
const newConfig = this.mindMap.opt.rainbowLinesConfig || {}
|
||||
newConfig.open = !!config.open
|
||||
newConfig.colorsList = Array.isArray(config.colorsList)
|
||||
? config.colorsList
|
||||
: []
|
||||
// 如果开启彩虹线条,那么先移除所有节点的自定义连线颜色配置
|
||||
if (this.mindMap.opt.rainbowLinesConfig.open) {
|
||||
this.removeNodeLineColor()
|
||||
}
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 删除所有节点的连线颜色
|
||||
removeNodeLineColor() {
|
||||
const tree = this.mindMap.renderer.renderTree
|
||||
if (!tree) return
|
||||
walk(
|
||||
tree,
|
||||
null,
|
||||
cur => {
|
||||
delete cur.data.lineColor
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
this.mindMap.command.addHistory()
|
||||
}
|
||||
|
||||
// 获取一个节点的第二层级的祖先节点
|
||||
getSecondLayerAncestor(node) {
|
||||
if (node.layerIndex === 1) {
|
||||
return node
|
||||
} else {
|
||||
let res = null
|
||||
let parent = node.parent
|
||||
while (parent) {
|
||||
if (parent.layerIndex === 1) {
|
||||
return parent
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
// 获取颜色列表
|
||||
getColorsList() {
|
||||
const { rainbowLinesConfig } = this.mindMap.opt
|
||||
return rainbowLinesConfig &&
|
||||
Array.isArray(rainbowLinesConfig.colorsList) &&
|
||||
rainbowLinesConfig.colorsList.length > 0
|
||||
? rainbowLinesConfig.colorsList
|
||||
: [...defaultColorsList]
|
||||
}
|
||||
|
||||
// 获取一个节点的彩虹线条颜色
|
||||
getNodeColor(node) {
|
||||
const { rainbowLinesConfig } = this.mindMap.opt
|
||||
if (!rainbowLinesConfig || !rainbowLinesConfig.open) return ''
|
||||
const ancestor = this.getSecondLayerAncestor(node)
|
||||
const index = getNodeDataIndex(ancestor)
|
||||
const colorsList = this.getColorsList()
|
||||
return colorsList[index % colorsList.length]
|
||||
}
|
||||
}
|
||||
|
||||
RainbowLines.instanceName = 'rainbowLines'
|
||||
|
||||
export default RainbowLines
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
isWhite,
|
||||
getVisibleColorFromTheme,
|
||||
isUndef,
|
||||
checkSmmFormatData
|
||||
checkSmmFormatData,
|
||||
removeHtmlNodeByClass
|
||||
} from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
@@ -201,7 +202,8 @@ class RichText {
|
||||
box-shadow: 0 0 20px rgba(0,0,0,.5);
|
||||
outline: none;
|
||||
word-break:
|
||||
break-all;padding: ${paddingY}px ${paddingX}px;
|
||||
break-all;
|
||||
padding: ${paddingY}px ${paddingX}px;
|
||||
`
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
@@ -217,18 +219,10 @@ class RichText {
|
||||
const targetNode = customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.textEditNode)
|
||||
}
|
||||
// 使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
|
||||
let bgColor = node.style.merge('fillColor')
|
||||
let color = node.style.merge('color')
|
||||
this.textEditNode.style.marginLeft = `-${paddingX * scaleX}px`
|
||||
this.textEditNode.style.marginTop = `-${paddingY * scaleY}px`
|
||||
this.textEditNode.style.zIndex = nodeTextEditZIndex
|
||||
this.textEditNode.style.backgroundColor =
|
||||
bgColor === 'transparent'
|
||||
? isWhite(color)
|
||||
? getVisibleColorFromTheme(this.mindMap.themeConfig)
|
||||
: '#fff'
|
||||
: bgColor
|
||||
this.textEditNode.style.background = this.getBackground(node)
|
||||
this.textEditNode.style.minWidth = originWidth + paddingX * 2 + 'px'
|
||||
this.textEditNode.style.minHeight = originHeight + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
@@ -278,6 +272,27 @@ class RichText {
|
||||
this.cacheEditingText = ''
|
||||
}
|
||||
|
||||
// 获取编辑区域的背景填充
|
||||
getBackground(node) {
|
||||
const gradientStyle = node.style.merge('gradientStyle')
|
||||
// 当前使用的是渐变色背景
|
||||
if (gradientStyle) {
|
||||
const startColor = node.style.merge('startColor')
|
||||
const endColor = node.style.merge('endColor')
|
||||
return `linear-gradient(to right, ${startColor}, ${endColor})`
|
||||
} else {
|
||||
// 单色背景
|
||||
const bgColor = node.style.merge('fillColor')
|
||||
const color = node.style.merge('color')
|
||||
// 默认使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
|
||||
return bgColor === 'transparent'
|
||||
? isWhite(color)
|
||||
? getVisibleColorFromTheme(this.mindMap.themeConfig)
|
||||
: '#fff'
|
||||
: bgColor
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是非富文本的情况,需要手动应用文本样式
|
||||
setTextStyleIfNotRichText(node) {
|
||||
let style = {
|
||||
@@ -295,6 +310,8 @@ class RichText {
|
||||
// 获取当前正在编辑的内容
|
||||
getEditText() {
|
||||
let html = this.quill.container.firstChild.innerHTML
|
||||
// 去除ql-cursor节点
|
||||
html = removeHtmlNodeByClass(html, '.ql-cursor')
|
||||
// 去除最后的空行
|
||||
return html.replace(/<p><br><\/p>$/, '')
|
||||
}
|
||||
@@ -309,10 +326,10 @@ class RichText {
|
||||
nodes && nodes.length > 0 ? nodes : this.mindMap.renderer.activeNodeList
|
||||
list.forEach(node => {
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
|
||||
if (node.isGeneralization) {
|
||||
// if (node.isGeneralization) {
|
||||
// 概要节点
|
||||
node.generalizationBelongNode.updateGeneralization()
|
||||
}
|
||||
// node.generalizationBelongNode.updateGeneralization()
|
||||
// }
|
||||
this.mindMap.render()
|
||||
})
|
||||
this.mindMap.emit('hide_text_edit', this.textEditNode, list)
|
||||
@@ -472,9 +489,9 @@ class RichText {
|
||||
}
|
||||
|
||||
// 格式化当前选中的文本
|
||||
formatText(config = {}, clear = false) {
|
||||
formatText(config = {}, clear = false, pure = false) {
|
||||
if (!this.range && !this.lastRange) return
|
||||
this.syncFormatToNodeConfig(config, clear)
|
||||
if (!pure) this.syncFormatToNodeConfig(config, clear)
|
||||
let rangeLost = !this.range
|
||||
let range = rangeLost ? this.lastRange : this.range
|
||||
clear
|
||||
@@ -487,7 +504,24 @@ class RichText {
|
||||
|
||||
// 清除当前选中文本的样式
|
||||
removeFormat() {
|
||||
// 先移除全部样式
|
||||
this.formatText({}, true)
|
||||
// 再将样式恢复为当前主题改节点的默认样式
|
||||
const style = {}
|
||||
if (this.node) {
|
||||
;[
|
||||
'fontFamily',
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textDecoration',
|
||||
'color'
|
||||
].forEach(key => {
|
||||
style[key] = this.node.style.merge(key)
|
||||
})
|
||||
}
|
||||
const config = this.normalStyleToRichTextStyle(style)
|
||||
this.formatText(config, false, true)
|
||||
}
|
||||
|
||||
// 格式化指定范围的文本
|
||||
@@ -605,38 +639,9 @@ class RichText {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理导出为图片
|
||||
async handleExportPng(node) {
|
||||
let el = document.createElement('div')
|
||||
el.style.position = 'absolute'
|
||||
el.style.left = '-9999999px'
|
||||
el.appendChild(node)
|
||||
this.mindMap.el.appendChild(el)
|
||||
// 遍历所有节点,将它们的margin和padding设为0
|
||||
let walk = root => {
|
||||
root.style.margin = 0
|
||||
root.style.padding = 0
|
||||
if (root.hasChildNodes()) {
|
||||
Array.from(root.children).forEach(item => {
|
||||
walk(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
walk(node)
|
||||
|
||||
// 如果使用html2canvas
|
||||
// let canvas = await html2canvas(el, {
|
||||
// backgroundColor: null
|
||||
// })
|
||||
// return canvas.toDataURL()
|
||||
|
||||
const res = await domtoimage.toPng(el)
|
||||
this.mindMap.el.removeChild(el)
|
||||
return res
|
||||
}
|
||||
|
||||
// 将所有节点转换成非富文本节点
|
||||
transformAllNodesToNormalNode() {
|
||||
if (!this.mindMap.renderer.renderTree) return
|
||||
walk(
|
||||
this.mindMap.renderer.renderTree,
|
||||
null,
|
||||
|
||||
@@ -40,6 +40,7 @@ class Scrollbar {
|
||||
this.mindMap.on('mouseup', this.onMouseup)
|
||||
this.mindMap.on('node_tree_render_end', this.updateScrollbar)
|
||||
this.mindMap.on('view_data_change', this.updateScrollbar)
|
||||
this.mindMap.on('resize', this.updateScrollbar)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
@@ -48,6 +49,7 @@ class Scrollbar {
|
||||
this.mindMap.off('mouseup', this.onMouseup)
|
||||
this.mindMap.off('node_tree_render_end', this.updateScrollbar)
|
||||
this.mindMap.off('view_data_change', this.updateScrollbar)
|
||||
this.mindMap.off('resize', this.updateScrollbar)
|
||||
}
|
||||
|
||||
// 渲染后、数据改变需要更新滚动条
|
||||
@@ -202,7 +204,8 @@ class Scrollbar {
|
||||
yOffset -
|
||||
paddingY * t.scaleY +
|
||||
paddingY -
|
||||
rootCenterOffset.y * t.scaleY
|
||||
rootCenterOffset.y * t.scaleY +
|
||||
((this.mindMap.height - this.mindMap.initHeight) / 2) * t.scaleY // 画布宽高改变了,但是思维导图元素变换的中心点依旧是原有位置,所以需要加上中心点变化量
|
||||
this.mindMap.view.translateYTo(chartTop)
|
||||
this.emitEvent({
|
||||
horizontal: scrollbarData.horizontal,
|
||||
@@ -238,7 +241,8 @@ class Scrollbar {
|
||||
xOffset -
|
||||
paddingX * t.scaleX +
|
||||
paddingX -
|
||||
rootCenterOffset.x * t.scaleX
|
||||
rootCenterOffset.x * t.scaleX +
|
||||
((this.mindMap.width - this.mindMap.initWidth) / 2) * t.scaleX // 画布宽高改变了,但是思维导图元素变换的中心点依旧是原有位置,所以需要加上中心点变化量
|
||||
this.mindMap.view.translateXTo(chartLeft)
|
||||
this.emitEvent({
|
||||
vertical: scrollbarData.vertical,
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
isUndef,
|
||||
replaceHtmlText
|
||||
} from '../utils/index'
|
||||
import Node from '../core/render/node/Node'
|
||||
|
||||
// 搜索插件
|
||||
class Search {
|
||||
@@ -69,14 +70,14 @@ class Search {
|
||||
// 结束搜索
|
||||
endSearch() {
|
||||
if (!this.isSearching) return
|
||||
if (this.mindMap.opt.readonly && this.matchNodeList[this.currentIndex]) {
|
||||
this.matchNodeList[this.currentIndex].closeHighlight()
|
||||
}
|
||||
this.searchText = ''
|
||||
this.matchNodeList = []
|
||||
this.currentIndex = -1
|
||||
this.notResetSearchText = false
|
||||
this.isSearching = false
|
||||
if (this.mindMap.opt.readonly) {
|
||||
this.mindMap.renderer.closeHighlightNode()
|
||||
}
|
||||
this.emitEvent()
|
||||
}
|
||||
|
||||
@@ -84,8 +85,15 @@ class Search {
|
||||
doSearch() {
|
||||
this.matchNodeList = []
|
||||
this.currentIndex = -1
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
let { richText, text } = node.getData()
|
||||
const { isOnlySearchCurrentRenderNodes } = this.mindMap.opt
|
||||
const tree = isOnlySearchCurrentRenderNodes
|
||||
? this.mindMap.renderer.root
|
||||
: this.mindMap.renderer.renderTree
|
||||
if (tree) return
|
||||
bfsWalk(tree, node => {
|
||||
let { richText, text } = isOnlySearchCurrentRenderNodes
|
||||
? node.getData()
|
||||
: node.data
|
||||
if (richText) {
|
||||
text = getTextFromHtml(text)
|
||||
}
|
||||
@@ -103,14 +111,25 @@ class Search {
|
||||
} else {
|
||||
this.currentIndex = 0
|
||||
}
|
||||
let currentNode = this.matchNodeList[this.currentIndex]
|
||||
const currentNode = this.matchNodeList[this.currentIndex]
|
||||
this.notResetSearchText = true
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', currentNode, () => {
|
||||
this.notResetSearchText = false
|
||||
const uid =
|
||||
currentNode instanceof Node
|
||||
? currentNode.getData('uid')
|
||||
: currentNode.data.uid
|
||||
const targetNode = this.mindMap.renderer.findNodeByUid(uid)
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', uid, node => {
|
||||
if (!(currentNode instanceof Node)) {
|
||||
this.matchNodeList[this.currentIndex] = node
|
||||
}
|
||||
callback()
|
||||
// 只读模式下节点无法激活,所以通过高亮的方式
|
||||
if (this.mindMap.opt.readonly) {
|
||||
this.mindMap.renderer.highlightNode(currentNode)
|
||||
node.highlight()
|
||||
}
|
||||
// 如果当前节点实例已经存在,则不会触发data_change事件,那么需要手动把标志复位
|
||||
if (targetNode) {
|
||||
this.notResetSearchText = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -221,6 +221,7 @@ function resetControlPoint() {
|
||||
|
||||
// 渲染控制点
|
||||
function renderControls(startPoint, endPoint, point1, point2) {
|
||||
if (!this.mindMap.opt.enableAdjustAssociativeLinePoints) return
|
||||
if (!this.controlLine1) {
|
||||
this.createControlNodes()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { getRectRelativePosition } from '../../utils/index'
|
||||
|
||||
// 获取目标节点在起始节点的目标数组中的索引
|
||||
export const getAssociativeLineTargetIndex = (node, toNode) => {
|
||||
return node.getData('associativeLineTargets').findIndex(item => {
|
||||
@@ -7,14 +9,21 @@ export const getAssociativeLineTargetIndex = (node, toNode) => {
|
||||
|
||||
// 计算贝塞尔曲线的控制点
|
||||
export const computeCubicBezierPathPoints = (x1, y1, x2, y2) => {
|
||||
const min = 5
|
||||
let cx1 = x1 + (x2 - x1) / 2
|
||||
let cy1 = y1
|
||||
let cx2 = cx1
|
||||
let cy2 = y2
|
||||
if (Math.abs(x1 - x2) <= 5) {
|
||||
if (Math.abs(x1 - x2) <= min) {
|
||||
cx1 = x1 + (y2 - y1) / 2
|
||||
cx2 = cx1
|
||||
}
|
||||
if (Math.abs(y1 - y2) <= min) {
|
||||
cx1 = x1
|
||||
cy1 = y1 - (x2 - x1) / 2
|
||||
cx2 = x2
|
||||
cy2 = cy1
|
||||
}
|
||||
return [
|
||||
{
|
||||
x: cx1,
|
||||
@@ -39,7 +48,9 @@ const getNodeRect = node => {
|
||||
right: left + width,
|
||||
bottom: top + height,
|
||||
left,
|
||||
top
|
||||
top,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,22 +180,26 @@ export const getNodePoint = (node, dir = 'right', range = 0, e = null) => {
|
||||
case 'left':
|
||||
return {
|
||||
x: left,
|
||||
y: top + height / 2 - range
|
||||
y: top + height / 2 - range,
|
||||
dir
|
||||
}
|
||||
case 'right':
|
||||
return {
|
||||
x: left + width,
|
||||
y: top + height / 2 - range
|
||||
y: top + height / 2 - range,
|
||||
dir
|
||||
}
|
||||
case 'top':
|
||||
return {
|
||||
x: left + width / 2 - range,
|
||||
y: top
|
||||
y: top,
|
||||
dir
|
||||
}
|
||||
case 'bottom':
|
||||
return {
|
||||
x: left + width / 2 - range,
|
||||
y: top + height
|
||||
y: top + height,
|
||||
dir
|
||||
}
|
||||
default:
|
||||
break
|
||||
@@ -193,34 +208,64 @@ export const getNodePoint = (node, dir = 'right', range = 0, e = null) => {
|
||||
|
||||
// 根据两个节点的位置计算节点的连接点
|
||||
export const computeNodePoints = (fromNode, toNode) => {
|
||||
let fromRect = getNodeRect(fromNode)
|
||||
let fromCx = (fromRect.right + fromRect.left) / 2
|
||||
let fromCy = (fromRect.bottom + fromRect.top) / 2
|
||||
let toRect = getNodeRect(toNode)
|
||||
let toCx = (toRect.right + toRect.left) / 2
|
||||
let toCy = (toRect.bottom + toRect.top) / 2
|
||||
// 中心点坐标的差值
|
||||
let offsetX = toCx - fromCx
|
||||
let offsetY = toCy - fromCy
|
||||
if (offsetX === 0 && offsetY === 0) return []
|
||||
const fromRect = getNodeRect(fromNode)
|
||||
const toRect = getNodeRect(toNode)
|
||||
let fromDir = ''
|
||||
let toDir = ''
|
||||
if (offsetX <= 0 && offsetX <= offsetY && offsetX <= -offsetY) {
|
||||
// left
|
||||
fromDir = 'left'
|
||||
toDir = 'right'
|
||||
} else if (offsetX > 0 && offsetX >= -offsetY && offsetX >= offsetY) {
|
||||
// right
|
||||
fromDir = 'right'
|
||||
toDir = 'left'
|
||||
} else if (offsetY <= 0 && offsetY < offsetX && offsetY < -offsetX) {
|
||||
// up
|
||||
fromDir = 'top'
|
||||
toDir = 'bottom'
|
||||
} else if (offsetY > 0 && -offsetY < offsetX && offsetY > offsetX) {
|
||||
// down
|
||||
fromDir = 'right'
|
||||
toDir = 'right'
|
||||
const dir = getRectRelativePosition(
|
||||
{
|
||||
x: fromRect.left,
|
||||
y: fromRect.top,
|
||||
width: fromRect.width,
|
||||
height: fromRect.height
|
||||
},
|
||||
{
|
||||
x: toRect.left,
|
||||
y: toRect.top,
|
||||
width: toRect.width,
|
||||
height: toRect.height
|
||||
}
|
||||
)
|
||||
// 起始矩形在结束矩形的什么方向
|
||||
switch (dir) {
|
||||
case 'left-top':
|
||||
fromDir = 'right'
|
||||
toDir = 'top'
|
||||
break
|
||||
case 'right-top':
|
||||
fromDir = 'left'
|
||||
toDir = 'top'
|
||||
break
|
||||
case 'right-bottom':
|
||||
fromDir = 'left'
|
||||
toDir = 'bottom'
|
||||
break
|
||||
case 'left-bottom':
|
||||
fromDir = 'right'
|
||||
toDir = 'bottom'
|
||||
break
|
||||
case 'left':
|
||||
fromDir = 'right'
|
||||
toDir = 'left'
|
||||
break
|
||||
case 'right':
|
||||
fromDir = 'left'
|
||||
toDir = 'right'
|
||||
break
|
||||
case 'top':
|
||||
fromDir = 'right'
|
||||
toDir = 'right'
|
||||
break
|
||||
case 'bottom':
|
||||
fromDir = 'left'
|
||||
toDir = 'left'
|
||||
break
|
||||
case 'overlap':
|
||||
fromDir = 'right'
|
||||
toDir = 'right'
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return [getNodePoint(fromNode, fromDir), getNodePoint(toNode, toDir)]
|
||||
}
|
||||
@@ -230,8 +275,9 @@ export const getNodeLinePath = (startPoint, endPoint, node, toNode) => {
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
// 控制点
|
||||
let controlPoints = []
|
||||
let associativeLineTargetControlOffsets =
|
||||
node.getData('associativeLineTargetControlOffsets')
|
||||
let associativeLineTargetControlOffsets = node.getData(
|
||||
'associativeLineTargetControlOffsets'
|
||||
)
|
||||
if (
|
||||
associativeLineTargetControlOffsets &&
|
||||
associativeLineTargetControlOffsets[targetIndex]
|
||||
|
||||
@@ -17,10 +17,14 @@ export default {
|
||||
// 连线样式
|
||||
lineDasharray: 'none',
|
||||
// 连线风格
|
||||
lineStyle: 'straight', // 针对logicalStructure、mindMap两种结构。曲线(curve)、直线(straight)、直连(direct)
|
||||
// 曲线连接时,根节点和其他节点的连接线样式保持统一,默认根节点为 ( 型,其他节点为 { 型,设为true后,都为 { 型
|
||||
lineStyle: 'straight', // 曲线(curve)【仅支持logicalStructure、mindMap、verticalTimeline三种结构】、直线(straight)、直连(direct)【仅支持logicalStructure、mindMap、organizationStructure、verticalTimeline四种结构】
|
||||
// 曲线连接时,根节点和其他节点的连接线样式保持统一,默认根节点为 ( 型,其他节点为 { 型,设为true后,都为 { 型。仅支持logicalStructure、mindMap两种结构
|
||||
rootLineKeepSameInCurve: true,
|
||||
// 连线尾部是否显示标记,目前只支持箭头
|
||||
// 曲线连接时,根节点和其他节点的连线起始位置保持统一,默认根节点的连线起始位置在节点中心,其他节点在节点右侧,如果该配置设为true,那么根节点的连线起始位置也会在节点右侧
|
||||
rootLineStartPositionKeepSameInCurve: false,
|
||||
// 直线连接(straight)时,连线的圆角大小,设置为0代表没有圆角,仅支持logicalStructure、mindMap、verticalTimeline三种结构
|
||||
lineRadius: 5,
|
||||
// 连线是否显示标记,目前只支持箭头
|
||||
showLineMarker: false,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 1,
|
||||
@@ -56,7 +60,7 @@ export default {
|
||||
backgroundPosition: 'center center',
|
||||
// 设置背景图片大小
|
||||
backgroundSize: 'cover',
|
||||
// 节点使用横线样式
|
||||
// 节点使用只有底边横线的样式,仅支持logicalStructure、mindMap、catalogOrganization、organizationStructure四种结构
|
||||
nodeUseLineStyle: false,
|
||||
// 根节点样式
|
||||
root: {
|
||||
@@ -75,7 +79,9 @@ export default {
|
||||
textDecoration: 'none',
|
||||
gradientStyle: false,
|
||||
startColor: '#549688',
|
||||
endColor: '#fff'
|
||||
endColor: '#fff',
|
||||
// 连线标记的位置,start(头部)、end(尾部),该配置在showLineMarker配置为true时生效
|
||||
lineMarkerDir: 'end'
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
@@ -96,7 +102,8 @@ export default {
|
||||
textDecoration: 'none',
|
||||
gradientStyle: false,
|
||||
startColor: '#549688',
|
||||
endColor: '#fff'
|
||||
endColor: '#fff',
|
||||
lineMarkerDir: 'end'
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
@@ -117,7 +124,8 @@ export default {
|
||||
textDecoration: 'none',
|
||||
gradientStyle: false,
|
||||
startColor: '#549688',
|
||||
endColor: '#fff'
|
||||
endColor: '#fff',
|
||||
lineMarkerDir: 'end'
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
@@ -174,8 +182,10 @@ const nodeSizeIndependenceList = [
|
||||
'backgroundPosition',
|
||||
'backgroundSize',
|
||||
'rootLineKeepSameInCurve',
|
||||
'rootLineStartPositionKeepSameInCurve',
|
||||
'showLineMarker',
|
||||
'gradientStyle',
|
||||
'lineRadius',
|
||||
'startColor',
|
||||
'endColor'
|
||||
]
|
||||
@@ -193,4 +203,9 @@ export const checkIsNodeSizeIndependenceConfig = config => {
|
||||
return true
|
||||
}
|
||||
|
||||
export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth']
|
||||
export const lineStyleProps = [
|
||||
'lineColor',
|
||||
'lineDasharray',
|
||||
'lineWidth',
|
||||
'lineMarkerDir'
|
||||
]
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
selfCloseTagList
|
||||
} from '../constants/constant'
|
||||
import MersenneTwister from './mersenneTwister'
|
||||
import { ForeignObject } from '@svgdotjs/svg.js'
|
||||
|
||||
// 深度优先遍历树
|
||||
export const walk = (
|
||||
root,
|
||||
@@ -489,9 +491,27 @@ export const removeHtmlStyle = html => {
|
||||
}
|
||||
|
||||
// 给html标签中指定的标签添加内联样式
|
||||
let addHtmlStyleEl = null
|
||||
export const addHtmlStyle = (html, tag, style) => {
|
||||
const reg = new RegExp(`(<${tag}[^>]*)(>[^<>]*</${tag}>)`, 'g')
|
||||
return html.replaceAll(reg, `$1 style="${style}"$2`)
|
||||
if (!addHtmlStyleEl) {
|
||||
addHtmlStyleEl = document.createElement('div')
|
||||
}
|
||||
addHtmlStyleEl.innerHTML = html
|
||||
let walk = root => {
|
||||
let childNodes = root.childNodes
|
||||
childNodes.forEach(node => {
|
||||
if (node.nodeType === 1) {
|
||||
// 元素节点
|
||||
if (node.tagName.toLowerCase() === tag) {
|
||||
node.style.cssText = style
|
||||
} else {
|
||||
walk(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
walk(addHtmlStyleEl)
|
||||
return addHtmlStyleEl.innerHTML
|
||||
}
|
||||
|
||||
// 检查一个字符串是否是富文本字符
|
||||
@@ -535,6 +555,20 @@ export const replaceHtmlText = (html, searchText, replaceText) => {
|
||||
return replaceHtmlTextEl.innerHTML
|
||||
}
|
||||
|
||||
// 去除html字符串中指定选择器的节点,然后返回html字符串
|
||||
let removeHtmlNodeByClassEl = null
|
||||
export const removeHtmlNodeByClass = (html, selector) => {
|
||||
if (!removeHtmlNodeByClassEl) {
|
||||
removeHtmlNodeByClassEl = document.createElement('div')
|
||||
}
|
||||
removeHtmlNodeByClassEl.innerHTML = html
|
||||
const node = removeHtmlNodeByClassEl.querySelector(selector)
|
||||
if (node) {
|
||||
node.parentNode.removeChild(node)
|
||||
}
|
||||
return removeHtmlNodeByClassEl.innerHTML
|
||||
}
|
||||
|
||||
// 判断一个颜色是否是白色
|
||||
export const isWhite = color => {
|
||||
color = String(color).replaceAll(/\s+/g, '')
|
||||
@@ -576,7 +610,25 @@ export const getVisibleColorFromTheme = themeConfig => {
|
||||
}
|
||||
}
|
||||
|
||||
// 去掉DOM节点中的公式标签
|
||||
export const removeFormulaTags = node => {
|
||||
const walk = root => {
|
||||
const childNodes = root.childNodes
|
||||
childNodes.forEach(node => {
|
||||
if (node.nodeType === 1) {
|
||||
if (node.classList.contains('ql-formula')) {
|
||||
node.parentNode.removeChild(node)
|
||||
} else {
|
||||
walk(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
walk(node)
|
||||
}
|
||||
|
||||
// 将<p><span></span><p>形式的节点富文本内容转换成\n换行的文本
|
||||
// 会过滤掉节点中的格式节点
|
||||
let nodeRichTextToTextWithWrapEl = null
|
||||
export const nodeRichTextToTextWithWrap = html => {
|
||||
if (!nodeRichTextToTextWithWrapEl) {
|
||||
@@ -589,6 +641,7 @@ export const nodeRichTextToTextWithWrap = html => {
|
||||
const node = childNodes[i]
|
||||
if (node.nodeType === 1) {
|
||||
// 元素节点
|
||||
removeFormulaTags(node)
|
||||
if (node.tagName.toLowerCase() === 'p') {
|
||||
res += node.textContent + '\n'
|
||||
} else {
|
||||
@@ -637,6 +690,52 @@ export const textToNodeRichTextWithWrap = html => {
|
||||
.join('')
|
||||
}
|
||||
|
||||
// 去除富文本内容的样式,包括样式标签,比如strong、em、s等
|
||||
// 但要保留数学公式内容
|
||||
let removeRichTextStyesEl = null
|
||||
export const removeRichTextStyes = html => {
|
||||
if (!removeRichTextStyesEl) {
|
||||
removeRichTextStyesEl = document.createElement('div')
|
||||
}
|
||||
removeRichTextStyesEl.innerHTML = html
|
||||
// 首先用占位文本替换掉所有的公式
|
||||
const formulaList = removeRichTextStyesEl.querySelectorAll('.ql-formula')
|
||||
Array.from(formulaList).forEach(el => {
|
||||
const placeholder = document.createTextNode('$smmformula$')
|
||||
el.parentNode.replaceChild(placeholder, el)
|
||||
})
|
||||
// 然后遍历每行节点,去掉内部的所有标签,转为文本
|
||||
const childNodes = removeRichTextStyesEl.childNodes
|
||||
let list = []
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const node = childNodes[i]
|
||||
if (node.nodeType === 1) {
|
||||
// 元素节点
|
||||
list.push(node.textContent)
|
||||
} else if (node.nodeType === 3) {
|
||||
// 文本节点
|
||||
list.push(node.nodeValue)
|
||||
}
|
||||
}
|
||||
// 拼接文本
|
||||
html = list
|
||||
.map(item => {
|
||||
return `<p><span>${htmlEscape(item)}</span></p>`
|
||||
})
|
||||
.join('')
|
||||
// 将公式添加回去
|
||||
if (formulaList.length > 0) {
|
||||
html = html.replace(/\$smmformula\$/g, '<span class="smmformula"></span>')
|
||||
removeRichTextStyesEl.innerHTML = html
|
||||
const els = removeRichTextStyesEl.querySelectorAll('.smmformula')
|
||||
Array.from(els).forEach((el, index) => {
|
||||
el.parentNode.replaceChild(formulaList[index], el)
|
||||
})
|
||||
html = removeRichTextStyesEl.innerHTML
|
||||
}
|
||||
return html
|
||||
}
|
||||
|
||||
// 判断是否是移动端环境
|
||||
export const isMobile = () => {
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||
@@ -1179,3 +1278,86 @@ export const transformObjectToTreeData = data => {
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// 计算两个点的直线距离
|
||||
export const getTwoPointDistance = (x1, y1, x2, y2) => {
|
||||
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
|
||||
}
|
||||
|
||||
// 判断两个矩形的相对位置
|
||||
// 第一个矩形在第二个矩形的什么方向
|
||||
export const getRectRelativePosition = (rect1, rect2) => {
|
||||
// 获取第一个矩形的中心点坐标
|
||||
const rect1CenterX = rect1.x + rect1.width / 2
|
||||
const rect1CenterY = rect1.y + rect1.height / 2
|
||||
|
||||
// 获取第二个矩形的中心点坐标
|
||||
const rect2CenterX = rect2.x + rect2.width / 2
|
||||
const rect2CenterY = rect2.y + rect2.height / 2
|
||||
|
||||
// 判断第一个矩形在第二个矩形的哪个方向
|
||||
if (rect1CenterX < rect2CenterX && rect1CenterY < rect2CenterY) {
|
||||
return 'left-top'
|
||||
} else if (rect1CenterX > rect2CenterX && rect1CenterY < rect2CenterY) {
|
||||
return 'right-top'
|
||||
} else if (rect1CenterX > rect2CenterX && rect1CenterY > rect2CenterY) {
|
||||
return 'right-bottom'
|
||||
} else if (rect1CenterX < rect2CenterX && rect1CenterY > rect2CenterY) {
|
||||
return 'left-bottom'
|
||||
} else if (rect1CenterX < rect2CenterX && rect1CenterY === rect2CenterY) {
|
||||
return 'left'
|
||||
} else if (rect1CenterX > rect2CenterX && rect1CenterY === rect2CenterY) {
|
||||
return 'right'
|
||||
} else if (rect1CenterX === rect2CenterX && rect1CenterY < rect2CenterY) {
|
||||
return 'top'
|
||||
} else if (rect1CenterX === rect2CenterX && rect1CenterY > rect2CenterY) {
|
||||
return 'bottom'
|
||||
} else {
|
||||
return 'overlap'
|
||||
}
|
||||
}
|
||||
|
||||
// 处理获取svg内容时添加额外内容
|
||||
export const handleGetSvgDataExtraContent = ({
|
||||
addContentToHeader,
|
||||
addContentToFooter
|
||||
}) => {
|
||||
// 追加内容
|
||||
const cssTextList = []
|
||||
let header = null
|
||||
let headerHeight = 0
|
||||
let footer = null
|
||||
let footerHeight = 0
|
||||
const handle = (fn, callback) => {
|
||||
if (typeof fn === 'function') {
|
||||
const res = fn()
|
||||
if (!res) return
|
||||
const { el, cssText, height } = res
|
||||
if (el instanceof HTMLElement) {
|
||||
el.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
|
||||
const foreignObject = new ForeignObject()
|
||||
foreignObject.height(height)
|
||||
foreignObject.add(el)
|
||||
callback(foreignObject, height)
|
||||
}
|
||||
if (cssText) {
|
||||
cssTextList.push(cssText)
|
||||
}
|
||||
}
|
||||
}
|
||||
handle(addContentToHeader, (foreignObject, height) => {
|
||||
header = foreignObject
|
||||
headerHeight = height
|
||||
})
|
||||
handle(addContentToFooter, (foreignObject, height) => {
|
||||
footer = foreignObject
|
||||
footerHeight = height
|
||||
})
|
||||
return {
|
||||
cssTextList,
|
||||
header,
|
||||
headerHeight,
|
||||
footer,
|
||||
footerHeight
|
||||
}
|
||||
}
|
||||
|
||||
8545
web/package-lock.json
generated
@@ -14,9 +14,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@toast-ui/editor": "^3.1.5",
|
||||
"codemirror": "^5.65.16",
|
||||
"core-js": "^3.6.5",
|
||||
"element-ui": "^2.15.1",
|
||||
"highlight.js": "^10.7.3",
|
||||
"katex": "^0.16.9",
|
||||
"v-viewer": "^1.6.4",
|
||||
"vue": "^2.6.11",
|
||||
"vue-i18n": "^8.27.2",
|
||||
|
||||