Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d0310f675 | ||
|
|
4ef99958b6 | ||
|
|
ec677c781e | ||
|
|
2822dcc99a | ||
|
|
46e3d85b5f | ||
|
|
024271ec54 | ||
|
|
da9fd4c36d | ||
|
|
5a291b4a5f | ||
|
|
3f002ce2ee | ||
|
|
78a242faff | ||
|
|
be229a0c04 | ||
|
|
9a36cd4478 | ||
|
|
732b6b50b0 | ||
|
|
ade7a95f3c | ||
|
|
322975528e | ||
|
|
2a76f5a0bc | ||
|
|
2a7eaefac5 | ||
|
|
68784f3e4d | ||
|
|
c29477ed55 | ||
|
|
88a6442539 | ||
|
|
24363d55a4 | ||
|
|
7fd4c7504d | ||
|
|
efb4dcf236 | ||
|
|
53c2af0bc0 | ||
|
|
74d302639a | ||
|
|
0a2e4e7c14 | ||
|
|
7d399b436b | ||
|
|
f02098f697 | ||
|
|
fbb3b47b7d | ||
|
|
508d8fe357 | ||
|
|
7258ed9ea7 | ||
|
|
6ae5d244f1 | ||
|
|
7213348c12 | ||
|
|
93092db49f | ||
|
|
b8df51eb02 | ||
|
|
6cdc2ff526 | ||
|
|
b1303ce7a5 | ||
|
|
ac5bb1d684 | ||
|
|
ad8cf74bba | ||
|
|
4e6688e4e0 | ||
|
|
15859e76b6 | ||
|
|
eb89bd71e1 | ||
|
|
d8a88f94d7 | ||
|
|
fa7761b5a3 | ||
|
|
e769c9602b | ||
|
|
2078d38092 | ||
|
|
97834086d0 | ||
|
|
8fe0a53ba1 | ||
|
|
20fb9c3067 | ||
|
|
1573141f2c | ||
|
|
dba711c9ef | ||
|
|
be3faa0aef | ||
|
|
d93511eca4 | ||
|
|
1d75e8519d | ||
|
|
02460c0642 | ||
|
|
a43ad7aa06 | ||
|
|
0041be9892 | ||
|
|
7ebab0298b | ||
|
|
079b963ae3 | ||
|
|
fb6fcd6bd3 | ||
|
|
59950b2ba0 | ||
|
|
1f23257917 | ||
|
|
4e1db01f44 | ||
|
|
38769c3b55 | ||
|
|
a4ef09779d | ||
|
|
4821dd6052 | ||
|
|
f33c886d6a | ||
|
|
b5b8c2be60 | ||
|
|
4a7485c58e | ||
|
|
c097d20748 | ||
|
|
eeeae7d0e2 | ||
|
|
dd3e169946 | ||
|
|
b895a58194 | ||
|
|
2b42b9fafa | ||
|
|
c2125b07ca | ||
|
|
eb342bf69b | ||
|
|
a7eb66a6c9 | ||
|
|
e24fd9bdbb | ||
|
|
34d7c6fed2 | ||
|
|
a0f88031c1 | ||
|
|
889ec13dbf | ||
|
|
4aa5a8c48b | ||
|
|
c6a8ec257c | ||
|
|
0ec20b8fa0 | ||
|
|
f3285cf4e6 |
230
README.md
@@ -44,7 +44,7 @@ Github:[releases](https://github.com/wanglin2/mind-map/releases)。百度云
|
||||
|
||||
官方提供了如下插件,可根据需求按需引入(某个功能不生效大概率是因为你没有引入对应的插件),具体使用方式请查看文档:
|
||||
|
||||
> RichText(节点富文本插件)、Select(鼠标多选节点插件)、Drag(节点拖拽插件)、AssociativeLine(关联线插件)、Export(导出插件)、KeyboardNavigation(键盘导航插件)、MiniMap(小地图插件)、Watermark(水印插件)、TouchEvent(移动端触摸事件支持插件)、NodeImgAdjust(拖拽调整节点图片大小插件)、Search(搜索插件)、Painter(节点格式刷插件)、Scrollbar(滚动条插件)、Formula(数学公式插件)、Cooperate(协同编辑插件)、RainbowLines(彩虹线条插件)、Demonstrate(演示模式插件)、OuterFrame(外框插件)、HandDrawnLikeStyle(手绘风格插件)[收费]、Notation(节点标记插件)[收费]、Numbers(节点编号插件)[收费]、Freemind(Freemind格式导入导出插件)[收费]、Excel(Excel格式导入导出插件)[收费]
|
||||
> RichText(节点富文本插件)、Select(鼠标多选节点插件)、Drag(节点拖拽插件)、AssociativeLine(关联线插件)、Export(导出插件)、KeyboardNavigation(键盘导航插件)、MiniMap(小地图插件)、Watermark(水印插件)、TouchEvent(移动端触摸事件支持插件)、NodeImgAdjust(拖拽调整节点图片大小插件)、Search(搜索插件)、Painter(节点格式刷插件)、Scrollbar(滚动条插件)、Formula(数学公式插件)、Cooperate(协同编辑插件)、RainbowLines(彩虹线条插件)、Demonstrate(演示模式插件)、OuterFrame(外框插件)、MindMapLayoutPro(思维导图布局插件)、HandDrawnLikeStyle(手绘风格插件)[收费]、Notation(节点标记插件)[收费]、Numbers(节点编号插件)[收费]、Freemind(Freemind格式导入导出插件)[收费]、Excel(Excel格式导入导出插件)[收费]、Checkbox(待办插件)[收费]、Lineflow(节点连线流动插件)[收费]
|
||||
|
||||
本项目不会实现的特性:
|
||||
|
||||
@@ -93,17 +93,15 @@ const mindMap = new MindMap({
|
||||
});
|
||||
```
|
||||
|
||||
即可得到一个思维导图。
|
||||
|
||||
想要实现更多功能?可以查看[开发文档](https://wanglin2.github.io/mind-map-docs/)。
|
||||
即可得到一个思维导图。想要实现更多功能?可以查看[开发文档](https://wanglin2.github.io/mind-map-docs/)。
|
||||
|
||||
# License
|
||||
|
||||
[MIT](./LICENSE)。保留`mind-map`版权声明的情况下可随意商用。如不想保留可联系作者。
|
||||
[MIT](./LICENSE)。保留`mind-map`版权声明的情况下可随意商用,如不想保留可联系作者。
|
||||
|
||||
# 微信交流群
|
||||
|
||||
微信添加`wanglinguanfang`拉你入群。根据过往的经验,大部分问题都可以通过查看issue列表或文档解决,所以提问前请确保你已经阅读完了所有文档,文档里没有的可在群里提问,不必私聊作者。
|
||||
微信添加`wanglinguanfang`拉你入群。根据过往的经验,大部分问题都可以通过查看issue列表或文档解决,所以提问前请确保你已经阅读完了所有文档,文档里没有的可在群里提问,不必私聊作者,如果你一定要私聊,请先发红包(¥9.9+每次)。
|
||||
|
||||
# star
|
||||
|
||||
@@ -117,17 +115,119 @@ const mindMap = new MindMap({
|
||||
|
||||
# 请作者喝杯咖啡
|
||||
|
||||
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡,你的支持是开发者持续维护的最大动力~
|
||||
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡~你的赞助对项目的可持续发展非常重要,是作者持续维护的最大动力。
|
||||
|
||||
> 推荐使用支付宝,微信获取不到头像。转账请备注【思维导图】。
|
||||
>
|
||||
> 也可以通过购买付费插件来支持我们:[付费插件](https://wanglin2.github.io/mind-map-docs/plugins/about.html)。
|
||||
>
|
||||
> 赞助等级:最强王者(¥500+)、星耀赞助(¥300+)、钻石赞助(¥150+)、黄金赞助(¥50+)、青铜赞助
|
||||
|
||||
<p>
|
||||
<img src="./web/src/assets/img/alipay.jpg" style="width: 300px" />
|
||||
<img src="./web/src/assets/img/wechat.jpg" style="width: 300px" />
|
||||
</p>
|
||||
|
||||
## 钻石赞助
|
||||
|
||||
<p>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/黄智彪@一米一栗科技.png" style="width: 50px;height: 50px;" />
|
||||
<span>黄智彪@一米一栗科技</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
## 黄金赞助
|
||||
|
||||
<p>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/小土渣的宇宙.jpeg" style="width: 50px;height: 50px;" />
|
||||
<span>小土渣的宇宙</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/Chris.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>Chris</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>LiuJL</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/Kyle.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>Kyle</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/ccccs.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>ccccs</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/千帆.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/达仁科技.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/俊奇.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>Matt</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>
|
||||
</p>
|
||||
|
||||
## 青铜赞助
|
||||
|
||||
<p>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/Think.jpg" style="width: 50px;height: 50px;" />
|
||||
@@ -137,10 +237,6 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/志斌.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>志斌</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/小土渣的宇宙.jpeg" style="width: 50px;height: 50px;" />
|
||||
<span>小土渣的宇宙</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/qp.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>qp</span>
|
||||
@@ -157,22 +253,10 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/suka.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>suka</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/Chris.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>Chris</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/千帆.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>千帆</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/才镇.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>才镇</span>
|
||||
@@ -189,10 +273,6 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>Luke</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>
|
||||
@@ -209,10 +289,6 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/敏.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>敏</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/沐风牧草.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>沐风牧草</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/有希.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>有希</span>
|
||||
@@ -221,10 +297,6 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/樊笼.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>樊笼</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/达仁科技.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>达仁科技</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/小逗比.png" style="width: 50px;height: 50px;" />
|
||||
<span>小逗比</span>
|
||||
@@ -301,10 +373,6 @@ const mindMap = new MindMap({
|
||||
<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>
|
||||
@@ -317,10 +385,6 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/皇登攀.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>皇登攀</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/风格.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>
|
||||
@@ -329,10 +393,6 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/逆水行舟.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>逆水行舟</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/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>
|
||||
@@ -357,10 +417,6 @@ 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/Alex.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>Alex</span>
|
||||
@@ -377,18 +433,10 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/最多5个字.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>最多5个字</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/ZX.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>ZX</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>
|
||||
@@ -401,18 +449,10 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/好名字.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>好名字</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/Kyle.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>Kyle</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/lsytyrt.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>lsytyrt</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/buddy.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>buddy</span>
|
||||
@@ -433,14 +473,6 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/晴空.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>晴空</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>黄泳</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/ccccs.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>ccccs</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/。.png" style="width: 50px;height: 50px;" />
|
||||
<span>。</span>
|
||||
@@ -453,10 +485,6 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/张文建.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>张文建</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/炫.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>炫</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/Lawliet.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>Lawliet</span>
|
||||
@@ -465,10 +493,6 @@ const mindMap = new MindMap({
|
||||
<img src="./web/src/assets/avatar/一叶孤舟.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>一叶孤舟</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>晏江</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
|
||||
<span>Eric</span>
|
||||
@@ -481,12 +505,44 @@ 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/海云.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/h.r.w.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>h.r.w</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/xbkkjbs0246658.png" style="width: 50px;height: 50px;" />
|
||||
<span>xbkkjbs0246658</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/4399行星元帅.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>4399行星元帅</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/Xavier.png" style="width: 50px;height: 50px;" />
|
||||
<span>Xavier</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/冒号括号.png" style="width: 50px;height: 50px;" />
|
||||
<span>:)</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
2
dist/js/app.js
vendored
69
dist/js/chunk-46ea317e.js
vendored
69
dist/js/chunk-9a7bee60.js
vendored
Normal file
@@ -9,7 +9,7 @@
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}</script><link href="dist/css/chunk-vendors.css?63f5fae2a1a38e74b08f" rel="stylesheet"><link href="dist/css/app.css?63f5fae2a1a38e74b08f" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
|
||||
}</script><link href="dist/css/chunk-vendors.css?f5839763ea0d5c47d80a" rel="stylesheet"><link href="dist/css/app.css?f5839763ea0d5c47d80a" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
@@ -74,4 +74,4 @@
|
||||
// 可以通过window.$bus.$on()来监听应用的一些事件
|
||||
// 实例化页面
|
||||
window.initApp()
|
||||
}</script><script src="dist/js/chunk-vendors.js?63f5fae2a1a38e74b08f"></script><script src="dist/js/app.js?63f5fae2a1a38e74b08f"></script></body></html>
|
||||
}</script><script src="dist/js/chunk-vendors.js?f5839763ea0d5c47d80a"></script><script src="dist/js/app.js?f5839763ea0d5c47d80a"></script></body></html>
|
||||
21
simple-mind-map/bin/createPluginsTypeFiles.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const { exec } = require('child_process')
|
||||
const fs = require('fs')
|
||||
|
||||
const base = './src/plugins/'
|
||||
const list = fs.readdirSync(base)
|
||||
const files = []
|
||||
list.forEach(item => {
|
||||
const stat = fs.statSync(base + item)
|
||||
if (stat.isFile()) {
|
||||
files.push(item)
|
||||
}
|
||||
})
|
||||
const str = files
|
||||
.map(item => {
|
||||
return base + item
|
||||
})
|
||||
.join(' ')
|
||||
|
||||
exec(
|
||||
`tsc ${str} --declaration --allowJs --emitDeclarationOnly --outDir types/src/ --target es2017 --skipLibCheck `
|
||||
)
|
||||
@@ -18,6 +18,7 @@ import Formula from './src/plugins/Formula.js'
|
||||
import RainbowLines from './src/plugins/RainbowLines.js'
|
||||
import Demonstrate from './src/plugins/Demonstrate.js'
|
||||
import OuterFrame from './src/plugins/OuterFrame.js'
|
||||
import MindMapLayoutPro from './src/plugins/MindMapLayoutPro.js'
|
||||
import xmind from './src/parse/xmind.js'
|
||||
import markdown from './src/parse/markdown.js'
|
||||
import icons from './src/svg/icons.js'
|
||||
@@ -29,7 +30,7 @@ MindMap.markdown = markdown
|
||||
MindMap.iconList = icons.nodeIconList
|
||||
MindMap.constants = constants
|
||||
MindMap.defaultTheme = defaultTheme
|
||||
MindMap.version = '0.12.0'
|
||||
MindMap.version = '0.12.2'
|
||||
|
||||
MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(Watermark)
|
||||
@@ -50,5 +51,6 @@ MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(RainbowLines)
|
||||
.usePlugin(Demonstrate)
|
||||
.usePlugin(OuterFrame)
|
||||
.usePlugin(MindMapLayoutPro)
|
||||
|
||||
export default MindMap
|
||||
|
||||
@@ -56,6 +56,26 @@ class MindMap {
|
||||
this.cssEl = null
|
||||
this.cssTextMap = {} // 该样式在实例化时会动态添加到页面,同时导出为svg时也会添加到svg源码中
|
||||
|
||||
// 节点前置内容列表
|
||||
/*
|
||||
{
|
||||
name: '',// 一个唯一的类型标识
|
||||
// 创建节点的显示内容:节点元素、宽高
|
||||
createContent: (node) => {
|
||||
return {
|
||||
node: null,
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
},
|
||||
// 创建保存到节点实例的opt对象中的数据
|
||||
createNodeData: () => {},
|
||||
// 更新节点实例的opt数据,返回数据是否改变了
|
||||
updateNodeData: () => {},
|
||||
}
|
||||
*/
|
||||
this.nodeInnerPrefixList = []
|
||||
|
||||
// 画布
|
||||
this.initContainer()
|
||||
|
||||
@@ -103,9 +123,11 @@ class MindMap {
|
||||
|
||||
// 初始渲染
|
||||
this.render(this.opt.fit ? () => this.view.fit() : () => {})
|
||||
setTimeout(() => {
|
||||
if (this.opt.data) this.command.addHistory()
|
||||
}, 0)
|
||||
|
||||
// 将初始数据添加到历史记录堆栈中
|
||||
if (this.opt.addHistoryOnInit && this.opt.data) {
|
||||
this.command.addHistory()
|
||||
}
|
||||
}
|
||||
|
||||
// 配置参数处理
|
||||
@@ -316,7 +338,7 @@ class MindMap {
|
||||
this.opt.themeConfig = config
|
||||
if (!notRender) {
|
||||
// 检查改变的是否是节点大小无关的主题属性
|
||||
let res = checkIsNodeSizeIndependenceConfig(changedConfig)
|
||||
const res = checkIsNodeSizeIndependenceConfig(changedConfig)
|
||||
this.render(null, res ? '' : CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
}
|
||||
@@ -339,8 +361,11 @@ class MindMap {
|
||||
// 更新配置
|
||||
updateConfig(opt = {}) {
|
||||
this.emit('before_update_config', this.opt)
|
||||
const lastOpt = {
|
||||
...this.opt
|
||||
}
|
||||
this.opt = this.handleOpt(merge.all([defaultOpt, this.opt, opt]))
|
||||
this.emit('after_update_config', this.opt)
|
||||
this.emit('after_update_config', this.opt, lastOpt)
|
||||
}
|
||||
|
||||
// 获取当前布局结构
|
||||
@@ -370,14 +395,17 @@ class MindMap {
|
||||
|
||||
// 更新画布数据,如果新的数据是在当前画布节点数据基础上增删改查后形成的,那么可以使用该方法来更新画布数据
|
||||
updateData(data) {
|
||||
this.emit('before_update_data', data)
|
||||
this.renderer.setData(data)
|
||||
this.render()
|
||||
this.command.addHistory()
|
||||
this.emit('update_data', data)
|
||||
}
|
||||
|
||||
// 动态设置思维导图数据,纯节点数据
|
||||
setData(data) {
|
||||
data = this.handleData(data)
|
||||
this.emit('before_set_data', data)
|
||||
this.opt.data = data
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
@@ -456,11 +484,20 @@ class MindMap {
|
||||
}
|
||||
const isReadonly = mode === CONSTANTS.MODE.READONLY
|
||||
if (isReadonly === this.opt.readonly) return
|
||||
this.opt.readonly = isReadonly
|
||||
if (this.opt.readonly) {
|
||||
if (isReadonly) {
|
||||
// 如果处于编辑态,要隐藏所有的编辑框
|
||||
if (this.renderer.textEdit.isShowTextEdit()) {
|
||||
this.renderer.textEdit.hideEditTextBox()
|
||||
this.command.originAddHistory()
|
||||
}
|
||||
// 取消当前激活的元素
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
}
|
||||
this.opt.readonly = isReadonly
|
||||
// 切换为编辑模式时,如果历史记录堆栈是空的,那么进行一次入栈操作
|
||||
if (!isReadonly && this.command.history.length <= 0) {
|
||||
this.command.originAddHistory()
|
||||
}
|
||||
this.emit('mode_change', mode)
|
||||
}
|
||||
|
||||
@@ -546,7 +583,7 @@ class MindMap {
|
||||
this.watermark.isInExport = false
|
||||
}
|
||||
// 添加必要的样式
|
||||
;[this.joinCss(), ...cssTextList].forEach(s => {
|
||||
[this.joinCss(), ...cssTextList].forEach(s => {
|
||||
clone.add(SVG(`<style>${s}</style>`))
|
||||
})
|
||||
// 附加内容
|
||||
|
||||
18
simple-mind-map/package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.10.6",
|
||||
"version": "0.12.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.10.6",
|
||||
"version": "0.12.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "3.2.0",
|
||||
@@ -15,7 +15,7 @@
|
||||
"katex": "^0.16.8",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"quill": "^2.0.2",
|
||||
"quill": "^2.0.3",
|
||||
"tern": "^0.24.3",
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^7.5.9",
|
||||
@@ -1822,9 +1822,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/quill": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz",
|
||||
"integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz",
|
||||
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
@@ -3553,9 +3553,9 @@
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
|
||||
},
|
||||
"quill": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz",
|
||||
"integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz",
|
||||
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
|
||||
"requires": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.2",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -22,7 +22,7 @@
|
||||
"scripts": {
|
||||
"lint": "eslint src/",
|
||||
"format": "prettier --write .",
|
||||
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017 --skipLibCheck",
|
||||
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types --target es2017 --skipLibCheck & node ./bin/createPluginsTypeFiles.js",
|
||||
"wsServe": "node ./bin/wsServer.mjs"
|
||||
},
|
||||
"module": "index.js",
|
||||
@@ -35,7 +35,7 @@
|
||||
"katex": "^0.16.8",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"quill": "^2.0.2",
|
||||
"quill": "^2.0.3",
|
||||
"tern": "^0.24.3",
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^7.5.9",
|
||||
|
||||
@@ -75,6 +75,11 @@ export const CONSTANTS = {
|
||||
TAG_POSITION: {
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom'
|
||||
},
|
||||
EDIT_NODE_CLASS: {
|
||||
SMM_NODE_EDIT_WRAP: 'smm-node-edit-wrap',
|
||||
RICH_TEXT_EDIT_WRAP: 'ql-editor',
|
||||
ASSOCIATIVE_LINE_TEXT_EDIT_WRAP: 'associative-line-text-edit-warp'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +172,9 @@ export const nodeDataNoStylePropList = [
|
||||
'range',
|
||||
'customLeft',
|
||||
'customTop',
|
||||
'customTextWidth'
|
||||
'customTextWidth',
|
||||
'checkbox',
|
||||
'dir'
|
||||
]
|
||||
|
||||
// 错误类型
|
||||
@@ -200,6 +207,10 @@ export const cssContent = `
|
||||
opacity: 1;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.smm-text-node-wrap {
|
||||
user-select: none;
|
||||
}
|
||||
`
|
||||
|
||||
// html自闭合标签列表
|
||||
@@ -212,3 +223,6 @@ export const selfCloseTagList = [
|
||||
'meta',
|
||||
'area'
|
||||
]
|
||||
|
||||
// 非富文本模式下的节点文本行高
|
||||
export const noneRichTextNodeLineHeight = 1.2
|
||||
|
||||
@@ -84,6 +84,9 @@ export const defaultOpt = {
|
||||
isShowExpandNum: true,
|
||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||
enableShortcutOnlyWhenMouseInSvg: true,
|
||||
// 自定义判断是否响应快捷键事件,优先级比enableShortcutOnlyWhenMouseInSvg选项高
|
||||
// 可以传递一个函数,接收事件对象e为参数,需要返回true或false,返回true代表允许响应快捷键事件,反之不允许,库默认当事件目标为body,或为文本编辑框元素(普通文本编辑框、富文本编辑框、关联线文本编辑框)时响应快捷键,其他不响应
|
||||
customCheckEnableShortcut: null,
|
||||
// 初始根节点的位置
|
||||
initRootNodePosition: null,
|
||||
// 节点文本编辑框的z-index
|
||||
@@ -130,6 +133,8 @@ export const defaultOpt = {
|
||||
// 是否在存在一个激活节点时,当按下中文、英文、数字按键时自动进入文本编辑模式
|
||||
// 开启该特性后,需要给你的输入框绑定keydown事件,并禁止冒泡
|
||||
enableAutoEnterTextEditWhenKeydown: false,
|
||||
// 当enableAutoEnterTextEditWhenKeydown选项开启时生效,当通过按键进入文本编辑时是否自动清空原有文本
|
||||
autoEmptyTextWhenKeydownEnterEdit: false,
|
||||
// 自定义对剪贴板文本的处理。当按ctrl+v粘贴时会读取用户剪贴板中的文本和图片,默认只会判断文本是否是普通文本和simple-mind-map格式的节点数据,如果你想处理其他思维导图的数据,比如processon、zhixi等,那么可以传递一个函数,接受当前剪贴板中的文本为参数,返回处理后的数据,可以返回两种类型:
|
||||
/*
|
||||
1.返回一个纯文本,那么会直接以该文本创建一个子节点
|
||||
@@ -226,7 +231,7 @@ export const defaultOpt = {
|
||||
// 移动节点到画布中心、回到根节点等操作时是否将缩放层级复位为100%
|
||||
// 该选项实际影响的是render.moveNodeToCenter方法,moveNodeToCenter方法本身也存在第二个参数resetScale来设置是否复位,如果resetScale参数没有传递,那么使用resetScaleOnMoveNodeToCenter配置,否则使用resetScale配置
|
||||
resetScaleOnMoveNodeToCenter: false,
|
||||
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分
|
||||
// 添加附加的节点前置内容,前置内容指和文本同一行的区域中的前置内容,不包括节点图片部分。如果存在编号、任务勾选框内容,这里添加的前置内容会在这两者之后
|
||||
createNodePrefixContent: null,
|
||||
// 添加附加的节点后置内容,后置内容指和文本同一行的区域中的后置内容,不包括节点图片部分
|
||||
createNodePostfixContent: null,
|
||||
@@ -247,8 +252,9 @@ export const defaultOpt = {
|
||||
emptyTextMeasureHeightText: 'abc123我和你',
|
||||
// 是否在进行节点文本编辑时实时更新节点大小和节点位置,开启后当节点数量比较多时可能会造成卡顿
|
||||
openRealtimeRenderOnNodeTextEdit: false,
|
||||
// 默认会给容器元素el绑定mousedown事件,并且会阻止其默认事件,这会带来一定问题,比如你聚焦在思维导图外的其他输入框,点击画布就不会触发其失焦,可以通过该选项关闭阻止。关闭后也会带来一定问题,比如鼠标框选节点时可能会选中节点文字,看你如何取舍
|
||||
mousedownEventPreventDefault: true,
|
||||
// 默认会给容器元素el绑定mousedown事件,可通过该选项设置是否阻止其默认事件
|
||||
// 如果设置为true,会带来一定问题,比如你聚焦在思维导图外的其他输入框,点击画布就不会触发其失焦
|
||||
mousedownEventPreventDefault: false,
|
||||
// 在激活上粘贴用户剪贴板中的数据时,如果同时存在文本和图片,那么只粘贴文本,忽略图片
|
||||
onlyPasteTextWhenHasImgAndText: true,
|
||||
// 是否允许拖拽调整节点的宽度,实际上压缩的是节点里面文本内容的宽度,当节点文本内容宽度压缩到最小时无法继续压缩。如果节点存在图片,那么最小值以图片宽度和文本内容最小宽度的最大值为准(目前该特性仅在两种情况下可用:1.开启了富文本模式,即注册了RichText插件;2.自定义节点内容)
|
||||
@@ -257,6 +263,11 @@ export const defaultOpt = {
|
||||
minNodeTextModifyWidth: 20,
|
||||
// 同minNodeTextModifyWidth,最大值,传-1代表不限制
|
||||
maxNodeTextModifyWidth: -1,
|
||||
// 自定义处理节点的连线方法,可以传递一个函数,函数接收三个参数:node(节点实例)、line(节点的某条连线,@svgjs库的path对象), { width, color, dasharray },dasharray(该条连线的虚线样式,为none代表实线),你可以修改line对象来达到修改节点连线样式的效果,比如增加流动效果
|
||||
customHandleLine: null,
|
||||
// 实例化完后是否立刻进行一次历史数据入栈操作
|
||||
// 即调用mindMap.command.addHistory方法
|
||||
addHistoryOnInit: true,
|
||||
|
||||
// 【Select插件】
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
@@ -426,9 +437,6 @@ export const defaultOpt = {
|
||||
transformRichTextOnEnterEdit: null,
|
||||
// 可以传递一个函数,即将结束富文本编辑前会执行该函数,函数接收richText实例,所以你可以在此时机更新quill文档数据
|
||||
beforeHideRichTextEdit: null,
|
||||
// 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果
|
||||
// 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果
|
||||
richTextEditFakeInPlace: false,
|
||||
|
||||
// 【OuterFrame】插件
|
||||
outerFramePaddingX: 10,
|
||||
|
||||
@@ -18,6 +18,7 @@ class Command {
|
||||
this.activeHistoryIndex = 0
|
||||
// 注册快捷键
|
||||
this.registerShortcutKeys()
|
||||
this.originAddHistory = this.addHistory.bind(this)
|
||||
this.addHistory = throttle(
|
||||
this.addHistory,
|
||||
this.mindMap.opt.addHistoryTime,
|
||||
@@ -60,6 +61,7 @@ class Command {
|
||||
this.commands[name].forEach(fn => {
|
||||
fn(...args)
|
||||
})
|
||||
this.mindMap.emit('afterExecCommand', name, ...args)
|
||||
if (
|
||||
['BACK', 'FORWARD', 'SET_NODE_ACTIVE', 'CLEAR_ACTIVE_NODE'].includes(
|
||||
name
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { keyMap } from './keyMap'
|
||||
import { CONSTANTS } from '../../constants/constant'
|
||||
|
||||
// 快捷按键、命令处理类
|
||||
export default class KeyCommand {
|
||||
@@ -15,6 +16,18 @@ export default class KeyCommand {
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
// 扩展按键映射
|
||||
extendKeyMap(key, code) {
|
||||
keyMap[key] = code
|
||||
}
|
||||
|
||||
// 从按键映射中删除某个键
|
||||
removeKeyMap(key) {
|
||||
if (typeof keyMap[key] !== 'undefined') {
|
||||
delete keyMap[key]
|
||||
}
|
||||
}
|
||||
|
||||
// 暂停快捷键响应
|
||||
pause() {
|
||||
this.isPause = true
|
||||
@@ -73,10 +86,29 @@ export default class KeyCommand {
|
||||
window.removeEventListener('keydown', this.onKeydown)
|
||||
}
|
||||
|
||||
// 根据事件目标判断是否响应快捷键事件
|
||||
defaultEnableCheck(e) {
|
||||
const target = e.target
|
||||
return (
|
||||
target === document.body ||
|
||||
target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.SMM_NODE_EDIT_WRAP) ||
|
||||
target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP) ||
|
||||
target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.ASSOCIATIVE_LINE_TEXT_EDIT_WRAP)
|
||||
)
|
||||
}
|
||||
|
||||
// 按键事件
|
||||
onKeydown(e) {
|
||||
const { enableShortcutOnlyWhenMouseInSvg, beforeShortcutRun } =
|
||||
this.mindMap.opt
|
||||
const {
|
||||
enableShortcutOnlyWhenMouseInSvg,
|
||||
beforeShortcutRun,
|
||||
customCheckEnableShortcut
|
||||
} = this.mindMap.opt
|
||||
const checkFn =
|
||||
typeof customCheckEnableShortcut === 'function'
|
||||
? customCheckEnableShortcut
|
||||
: this.defaultEnableCheck
|
||||
if (!checkFn(e)) return
|
||||
if (this.isPause || (enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@ import {
|
||||
removeRichTextStyes,
|
||||
formatGetNodeGeneralization,
|
||||
sortNodeList,
|
||||
throttle
|
||||
throttle,
|
||||
checkClipboardReadEnable,
|
||||
isNodeNotNeedRenderData
|
||||
} from '../../utils'
|
||||
import { shapeList } from './node/Shape'
|
||||
import { lineStyleProps } from '../../theme/default'
|
||||
@@ -92,12 +94,7 @@ class Render {
|
||||
// 文本编辑框,需要再bindEvent之前实例化,否则单击事件只能触发隐藏文本编辑框,而无法保存文本修改
|
||||
this.textEdit = new TextEdit(this)
|
||||
// 当前复制的数据
|
||||
this.lastBeingCopyData = null
|
||||
this.beingCopyData = null
|
||||
this.beingPasteText = ''
|
||||
this.beingPasteImgSize = 0
|
||||
this.currentBeingPasteType = ''
|
||||
this.pasteData = { text: null, img: null }
|
||||
// 节点高亮框
|
||||
this.highlightBoxNode = null
|
||||
this.highlightBoxNodeStyle = null
|
||||
@@ -115,24 +112,26 @@ class Render {
|
||||
|
||||
// 设置布局结构
|
||||
setLayout() {
|
||||
const { layout } = this.mindMap.opt
|
||||
this.layout = new (
|
||||
layouts[this.mindMap.opt.layout]
|
||||
? layouts[this.mindMap.opt.layout]
|
||||
layouts[layout]
|
||||
? layouts[layout]
|
||||
: layouts[CONSTANTS.LAYOUT.LOGICAL_STRUCTURE]
|
||||
)(this, this.mindMap.opt.layout)
|
||||
)(this, layout)
|
||||
}
|
||||
|
||||
// 重新设置思维导图数据
|
||||
setData(data) {
|
||||
if (this.hasRichTextPlugin()) {
|
||||
this.renderTree = data ? this.mindMap.richText.handleSetData(data) : null
|
||||
} else {
|
||||
this.renderTree = data
|
||||
}
|
||||
this.renderTree = data || null
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
bindEvent() {
|
||||
const {
|
||||
openPerformance,
|
||||
performanceConfig,
|
||||
openRealtimeRenderOnNodeTextEdit
|
||||
} = this.mindMap.opt
|
||||
// 画布点击事件清除当前激活节点列表
|
||||
this.mindMap.on('draw_click', e => {
|
||||
this.clearActiveNodeListOnDrawClick(e, 'click')
|
||||
@@ -147,34 +146,6 @@ class Render {
|
||||
this.setRootNodeCenter()
|
||||
})
|
||||
// 性能模式
|
||||
this.performanceMode()
|
||||
// 实时渲染当节点文本编辑时
|
||||
if (this.mindMap.opt.openRealtimeRenderOnNodeTextEdit) {
|
||||
this.mindMap.on('node_text_edit_change', ({ node, text }) => {
|
||||
node._textData = node.createTextNode(text)
|
||||
const { width, height } = node.getNodeRect()
|
||||
node.width = width
|
||||
node.height = height
|
||||
node.layout()
|
||||
this.mindMap.render(() => {
|
||||
this.textEdit.updateTextEditNode()
|
||||
})
|
||||
})
|
||||
}
|
||||
// 处理非https下的复制黏贴问题
|
||||
// 暂时不启用,因为给页面的其他输入框(比如节点文本编辑框)粘贴内容也会触发,冲突问题暂时没有想到好的解决方法,不可能要求所有输入框都阻止冒泡
|
||||
// if (!navigator.clipboard) {
|
||||
// this.handlePaste = this.handlePaste.bind(this)
|
||||
// window.addEventListener('paste', this.handlePaste)
|
||||
// this.mindMap.on('beforeDestroy', () => {
|
||||
// window.removeEventListener('paste', this.handlePaste)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
// 性能模式,懒加载节点
|
||||
performanceMode() {
|
||||
const { openPerformance, performanceConfig } = this.mindMap.opt
|
||||
const onViewDataChange = throttle(() => {
|
||||
if (this.root) {
|
||||
this.mindMap.emit('node_tree_render_start')
|
||||
@@ -187,24 +158,56 @@ class Render {
|
||||
)
|
||||
}
|
||||
}, performanceConfig.time)
|
||||
let lastOpen = false
|
||||
this.mindMap.on('before_update_config', opt => {
|
||||
lastOpen = opt.openPerformance
|
||||
})
|
||||
this.mindMap.on('after_update_config', opt => {
|
||||
if (opt.openPerformance && !lastOpen) {
|
||||
// 动态开启性能模式
|
||||
this.mindMap.on('view_data_change', onViewDataChange)
|
||||
if (openPerformance) {
|
||||
this.mindMap.on('view_data_change', onViewDataChange)
|
||||
}
|
||||
// 文本编辑时实时更新节点大小
|
||||
this.onNodeTextEditChange = this.onNodeTextEditChange.bind(this)
|
||||
if (openRealtimeRenderOnNodeTextEdit) {
|
||||
this.mindMap.on('node_text_edit_change', this.onNodeTextEditChange)
|
||||
}
|
||||
// 监听配置改变事件
|
||||
this.mindMap.on('after_update_config', (opt, lastOpt) => {
|
||||
// 更新openPerformance配置
|
||||
if (opt.openPerformance !== lastOpt.openPerformance) {
|
||||
this.mindMap[opt.openPerformance ? 'on' : 'off'](
|
||||
'view_data_change',
|
||||
onViewDataChange
|
||||
)
|
||||
this.forceLoadNode()
|
||||
}
|
||||
if (!opt.openPerformance && lastOpen) {
|
||||
// 动态关闭性能模式
|
||||
this.mindMap.off('view_data_change', onViewDataChange)
|
||||
this.forceLoadNode()
|
||||
// 更新openRealtimeRenderOnNodeTextEdit配置
|
||||
if (
|
||||
opt.openRealtimeRenderOnNodeTextEdit !==
|
||||
lastOpt.openRealtimeRenderOnNodeTextEdit
|
||||
) {
|
||||
this.mindMap[opt.openRealtimeRenderOnNodeTextEdit ? 'on' : 'off'](
|
||||
'node_text_edit_change',
|
||||
this.onNodeTextEditChange
|
||||
)
|
||||
}
|
||||
})
|
||||
if (!openPerformance) return
|
||||
this.mindMap.on('view_data_change', onViewDataChange)
|
||||
// 处理非https下的复制黏贴问题
|
||||
// 暂时不启用,因为给页面的其他输入框(比如节点文本编辑框)粘贴内容也会触发,冲突问题暂时没有想到好的解决方法,不可能要求所有输入框都阻止冒泡
|
||||
// if (!checkClipboardReadEnable()) {
|
||||
// this.handlePaste = this.handlePaste.bind(this)
|
||||
// window.addEventListener('paste', this.handlePaste)
|
||||
// this.mindMap.on('beforeDestroy', () => {
|
||||
// window.removeEventListener('paste', this.handlePaste)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
// 监听文本编辑事件,实时更新节点大小
|
||||
onNodeTextEditChange({ node, text }) {
|
||||
node._textData = node.createTextNode(text)
|
||||
const { width, height } = node.getNodeRect()
|
||||
node.width = width
|
||||
node.height = height
|
||||
node.layout()
|
||||
this.mindMap.render(() => {
|
||||
this.textEdit.updateTextEditNode()
|
||||
})
|
||||
}
|
||||
|
||||
// 强制渲染节点,不考虑是否在画布可视区域内
|
||||
@@ -424,7 +427,7 @@ class Render {
|
||||
})
|
||||
// 粘贴节点
|
||||
this.mindMap.keyCommand.addShortcut('Control+v', () => {
|
||||
if (navigator.clipboard) this.paste()
|
||||
this.paste()
|
||||
})
|
||||
// 根节点居中显示
|
||||
this.mindMap.keyCommand.addShortcut('Control+Enter', () => {
|
||||
@@ -981,11 +984,12 @@ class Render {
|
||||
}
|
||||
|
||||
// 上移节点,多个节点只会操作第一个节点
|
||||
upNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
upNode(appointNode) {
|
||||
if (this.activeNodeList.length <= 0 && !appointNode) {
|
||||
return
|
||||
}
|
||||
let node = this.activeNodeList[0]
|
||||
const list = appointNode ? [appointNode] : this.activeNodeList
|
||||
const node = list[0]
|
||||
if (node.isRoot) {
|
||||
return
|
||||
}
|
||||
@@ -1006,11 +1010,12 @@ class Render {
|
||||
}
|
||||
|
||||
// 下移节点,多个节点只会操作第一个节点
|
||||
downNode() {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
downNode(appointNode) {
|
||||
if (this.activeNodeList.length <= 0 && !appointNode) {
|
||||
return
|
||||
}
|
||||
let node = this.activeNodeList[0]
|
||||
const list = appointNode ? [appointNode] : this.activeNodeList
|
||||
const node = list[0]
|
||||
if (node.isRoot) {
|
||||
return
|
||||
}
|
||||
@@ -1144,8 +1149,6 @@ class Render {
|
||||
text = clipboardData.getData('text')
|
||||
}
|
||||
})
|
||||
this.pasteData.img = img
|
||||
this.pasteData.text = text
|
||||
this.paste()
|
||||
}
|
||||
|
||||
@@ -1158,134 +1161,121 @@ class Render {
|
||||
disabledClipboard,
|
||||
onlyPasteTextWhenHasImgAndText
|
||||
} = this.mindMap.opt
|
||||
// 读取剪贴板的文字和图片
|
||||
let text = ''
|
||||
let img = null
|
||||
if (!disabledClipboard) {
|
||||
// 如果支持剪贴板操作,那么以剪贴板数据为准
|
||||
if (!disabledClipboard && checkClipboardReadEnable()) {
|
||||
try {
|
||||
const res = navigator.clipboard
|
||||
? await getDataFromClipboard()
|
||||
: this.pasteData
|
||||
text = res.text || ''
|
||||
img = res.img || null
|
||||
const res = await getDataFromClipboard()
|
||||
let text = res.text || ''
|
||||
let img = res.img || null
|
||||
// 存在文本,则创建子节点
|
||||
if (text) {
|
||||
// 判断粘贴的是否是simple-mind-map的数据
|
||||
let smmData = null
|
||||
let useDefault = true
|
||||
// 用户自定义处理
|
||||
if (this.mindMap.opt.customHandleClipboardText) {
|
||||
try {
|
||||
const res = await this.mindMap.opt.customHandleClipboardText(text)
|
||||
if (!isUndef(res)) {
|
||||
useDefault = false
|
||||
const checkRes = checkSmmFormatData(res)
|
||||
if (checkRes.isSmm) {
|
||||
smmData = checkRes.data
|
||||
} else {
|
||||
text = checkRes.data
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(
|
||||
ERROR_TYPES.CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR,
|
||||
error
|
||||
)
|
||||
}
|
||||
}
|
||||
// 默认处理
|
||||
if (useDefault) {
|
||||
const checkRes = checkSmmFormatData(text)
|
||||
if (checkRes.isSmm) {
|
||||
smmData = checkRes.data
|
||||
} else {
|
||||
text = checkRes.data
|
||||
}
|
||||
}
|
||||
if (smmData) {
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_MULTI_CHILD_NODE',
|
||||
[],
|
||||
Array.isArray(smmData) ? smmData : [smmData]
|
||||
)
|
||||
} else {
|
||||
text = htmlEscape(text)
|
||||
const textArr = text
|
||||
.split(new RegExp('\r?\n|(?<!\n)\r', 'g'))
|
||||
.filter(item => {
|
||||
return !!item
|
||||
})
|
||||
// 判断是否需要根据换行自动分割节点
|
||||
if (textArr.length > 1 && handleIsSplitByWrapOnPasteCreateNewNode) {
|
||||
handleIsSplitByWrapOnPasteCreateNewNode()
|
||||
.then(() => {
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_MULTI_CHILD_NODE',
|
||||
[],
|
||||
textArr.map(item => {
|
||||
return {
|
||||
data: {
|
||||
text: item
|
||||
},
|
||||
children: []
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
.catch(() => {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
text
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
text
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// 存在图片,则添加到当前激活节点
|
||||
if (img && (!text || !onlyPasteTextWhenHasImgAndText)) {
|
||||
try {
|
||||
let imgData = null
|
||||
// 自定义图片处理函数
|
||||
if (
|
||||
handleNodePasteImg &&
|
||||
typeof handleNodePasteImg === 'function'
|
||||
) {
|
||||
imgData = await handleNodePasteImg(img)
|
||||
} else {
|
||||
imgData = await loadImage(img)
|
||||
}
|
||||
if (this.activeNodeList.length > 0) {
|
||||
this.activeNodeList.forEach(node => {
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', node, {
|
||||
url: imgData.url,
|
||||
title: '',
|
||||
width: imgData.size.width,
|
||||
height: imgData.size.height
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.LOAD_CLIPBOARD_IMAGE_ERROR, error)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.READ_CLIPBOARD_ERROR, error)
|
||||
}
|
||||
}
|
||||
// 检查剪切板数据是否有变化
|
||||
// 通过图片大小来判断图片是否发生变化,可能是不准确的,但是目前没有其他好方法
|
||||
const imgSize = img ? img.size : 0
|
||||
if (this.beingPasteText !== text || this.beingPasteImgSize !== imgSize) {
|
||||
this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CLIP_BOARD
|
||||
this.beingPasteText = text
|
||||
this.beingPasteImgSize = imgSize
|
||||
}
|
||||
// 检查要粘贴的节点数据是否有变化,节点优先级高于剪切板
|
||||
if (this.lastBeingCopyData !== this.beingCopyData) {
|
||||
this.lastBeingCopyData = this.beingCopyData
|
||||
this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CANVAS
|
||||
}
|
||||
// 粘贴剪切板的数据
|
||||
if (this.currentBeingPasteType === CONSTANTS.PASTE_TYPE.CLIP_BOARD) {
|
||||
// 存在文本,则创建子节点
|
||||
if (text) {
|
||||
// 判断粘贴的是否是simple-mind-map的数据
|
||||
let smmData = null
|
||||
let useDefault = true
|
||||
// 用户自定义处理
|
||||
if (this.mindMap.opt.customHandleClipboardText) {
|
||||
try {
|
||||
const res = await this.mindMap.opt.customHandleClipboardText(text)
|
||||
if (!isUndef(res)) {
|
||||
useDefault = false
|
||||
const checkRes = checkSmmFormatData(res)
|
||||
if (checkRes.isSmm) {
|
||||
smmData = checkRes.data
|
||||
} else {
|
||||
text = checkRes.data
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.CUSTOM_HANDLE_CLIPBOARD_TEXT_ERROR, error)
|
||||
}
|
||||
}
|
||||
// 默认处理
|
||||
if (useDefault) {
|
||||
const checkRes = checkSmmFormatData(text)
|
||||
if (checkRes.isSmm) {
|
||||
smmData = checkRes.data
|
||||
} else {
|
||||
text = checkRes.data
|
||||
}
|
||||
}
|
||||
if (smmData) {
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_MULTI_CHILD_NODE',
|
||||
[],
|
||||
Array.isArray(smmData) ? smmData : [smmData]
|
||||
)
|
||||
} else {
|
||||
text = htmlEscape(text)
|
||||
const textArr = text
|
||||
.split(new RegExp('\r?\n|(?<!\n)\r', 'g'))
|
||||
.filter(item => {
|
||||
return !!item
|
||||
})
|
||||
// 判断是否需要根据换行自动分割节点
|
||||
if (textArr.length > 1 && handleIsSplitByWrapOnPasteCreateNewNode) {
|
||||
handleIsSplitByWrapOnPasteCreateNewNode()
|
||||
.then(() => {
|
||||
this.mindMap.execCommand(
|
||||
'INSERT_MULTI_CHILD_NODE',
|
||||
[],
|
||||
textArr.map(item => {
|
||||
return {
|
||||
data: {
|
||||
text: item
|
||||
},
|
||||
children: []
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
.catch(() => {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
text
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
text
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// 存在图片,则添加到当前激活节点
|
||||
if (img && (!text || !onlyPasteTextWhenHasImgAndText)) {
|
||||
try {
|
||||
let imgData = null
|
||||
// 自定义图片处理函数
|
||||
if (handleNodePasteImg && typeof handleNodePasteImg === 'function') {
|
||||
imgData = await handleNodePasteImg(img)
|
||||
} else {
|
||||
imgData = await loadImage(img)
|
||||
}
|
||||
if (this.activeNodeList.length > 0) {
|
||||
this.activeNodeList.forEach(node => {
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', node, {
|
||||
url: imgData.url,
|
||||
title: '',
|
||||
width: imgData.size.width,
|
||||
height: imgData.size.height
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
errorHandler(ERROR_TYPES.LOAD_CLIPBOARD_IMAGE_ERROR, error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 粘贴节点数据
|
||||
// 禁用剪贴板或不支持剪贴板时
|
||||
// 粘贴画布内的节点数据
|
||||
if (this.beingCopyData) {
|
||||
this.mindMap.execCommand('PASTE_NODE', this.beingCopyData)
|
||||
}
|
||||
@@ -1560,6 +1550,8 @@ class Render {
|
||||
return
|
||||
}
|
||||
this.activeNodeList.forEach(node => {
|
||||
// 概要节点不允许添加下级节点
|
||||
if (node.isGeneralization) return
|
||||
node.setData({
|
||||
expand: true
|
||||
})
|
||||
@@ -1587,28 +1579,32 @@ class Render {
|
||||
|
||||
// 设置节点样式
|
||||
setNodeStyle(node, prop, value) {
|
||||
let data = {
|
||||
const data = {
|
||||
[prop]: value
|
||||
}
|
||||
// 如果开启了富文本,则需要应用到富文本上
|
||||
if (this.hasRichTextPlugin()) {
|
||||
this.mindMap.richText.setNotActiveNodeStyle(node, {
|
||||
[prop]: value
|
||||
})
|
||||
if (
|
||||
this.hasRichTextPlugin() &&
|
||||
this.mindMap.richText.isHasRichTextStyle(data)
|
||||
) {
|
||||
data.resetRichText = true
|
||||
}
|
||||
this.setNodeDataRender(node, data)
|
||||
// 更新了连线的样式
|
||||
if (lineStyleProps.includes(prop)) {
|
||||
;(node.parent || node).renderLine(true)
|
||||
(node.parent || node).renderLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置节点多个样式
|
||||
setNodeStyles(node, style) {
|
||||
let data = { ...style }
|
||||
const data = { ...style }
|
||||
// 如果开启了富文本,则需要应用到富文本上
|
||||
if (this.hasRichTextPlugin()) {
|
||||
this.mindMap.richText.setNotActiveNodeStyle(node, style)
|
||||
if (
|
||||
this.hasRichTextPlugin() &&
|
||||
this.mindMap.richText.isHasRichTextStyle(data)
|
||||
) {
|
||||
data.resetRichText = true
|
||||
}
|
||||
this.setNodeDataRender(node, data)
|
||||
// 更新了连线的样式
|
||||
@@ -1620,7 +1616,7 @@ class Render {
|
||||
}
|
||||
})
|
||||
if (hasLineStyleProps) {
|
||||
;(node.parent || node).renderLine(true)
|
||||
(node.parent || node).renderLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1971,6 +1967,10 @@ class Render {
|
||||
// 设置节点数据,并判断是否渲染
|
||||
setNodeDataRender(node, data, notRender = false) {
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, data)
|
||||
if (isNodeNotNeedRenderData(data)) {
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
return
|
||||
}
|
||||
this.reRenderNodeCheckChange(node, notRender)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,15 @@ import {
|
||||
htmlEscape,
|
||||
handleInputPasteText,
|
||||
checkSmmFormatData,
|
||||
getTextFromHtml
|
||||
getTextFromHtml,
|
||||
isWhite,
|
||||
getVisibleColorFromTheme
|
||||
} from '../../utils'
|
||||
import { ERROR_TYPES, CONSTANTS } from '../../constants/constant'
|
||||
import {
|
||||
ERROR_TYPES,
|
||||
CONSTANTS,
|
||||
noneRichTextNodeLineHeight
|
||||
} from '../../constants/constant'
|
||||
|
||||
// 节点文字编辑类
|
||||
export default class TextEdit {
|
||||
@@ -92,6 +98,32 @@ export default class TextEdit {
|
||||
this.mindMap.on('beforeDestroy', () => {
|
||||
this.unBindEvent()
|
||||
})
|
||||
this.mindMap.on('after_update_config', (opt, lastOpt) => {
|
||||
if (
|
||||
opt.openRealtimeRenderOnNodeTextEdit !==
|
||||
lastOpt.openRealtimeRenderOnNodeTextEdit
|
||||
) {
|
||||
if (this.mindMap.richText) {
|
||||
this.mindMap.richText.onOpenRealtimeRenderOnNodeTextEditConfigUpdate(
|
||||
opt.openRealtimeRenderOnNodeTextEdit
|
||||
)
|
||||
} else {
|
||||
this.onOpenRealtimeRenderOnNodeTextEditConfigUpdate(
|
||||
opt.openRealtimeRenderOnNodeTextEdit
|
||||
)
|
||||
}
|
||||
}
|
||||
if (
|
||||
opt.enableAutoEnterTextEditWhenKeydown !==
|
||||
lastOpt.enableAutoEnterTextEditWhenKeydown
|
||||
) {
|
||||
window[
|
||||
opt.enableAutoEnterTextEditWhenKeydown
|
||||
? 'addEventListener'
|
||||
: 'removeEventListener'
|
||||
]('keydown', this.onKeydown)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
@@ -101,6 +133,7 @@ export default class TextEdit {
|
||||
|
||||
// 按键事件
|
||||
onKeydown(e) {
|
||||
if (e.target !== document.body) return
|
||||
const activeNodeList = this.mindMap.renderer.activeNodeList
|
||||
if (activeNodeList.length <= 0 || activeNodeList.length > 1) return
|
||||
const node = activeNodeList[0]
|
||||
@@ -158,7 +191,8 @@ export default class TextEdit {
|
||||
if (node.isUseCustomNodeContent()) {
|
||||
return
|
||||
}
|
||||
const { beforeTextEdit } = this.mindMap.opt
|
||||
const { beforeTextEdit, openRealtimeRenderOnNodeTextEdit } =
|
||||
this.mindMap.opt
|
||||
if (typeof beforeTextEdit === 'function') {
|
||||
let isShow = false
|
||||
try {
|
||||
@@ -172,7 +206,12 @@ export default class TextEdit {
|
||||
this.currentNode = node
|
||||
const { offsetLeft, offsetTop } = checkNodeOuter(this.mindMap, node)
|
||||
this.mindMap.view.translateXY(offsetLeft, offsetTop)
|
||||
const rect = node._textData.node.node.getBoundingClientRect()
|
||||
const g = node._textData.node
|
||||
const rect = g.node.getBoundingClientRect()
|
||||
// 如果开启了大小实时更新,那么直接隐藏节点原文本
|
||||
if (openRealtimeRenderOnNodeTextEdit) {
|
||||
g.hide()
|
||||
}
|
||||
const params = {
|
||||
node,
|
||||
rect,
|
||||
@@ -187,6 +226,21 @@ export default class TextEdit {
|
||||
this.showEditTextBox(params)
|
||||
}
|
||||
|
||||
// 当openRealtimeRenderOnNodeTextEdit配置更新后需要更新编辑框样式
|
||||
onOpenRealtimeRenderOnNodeTextEditConfigUpdate(
|
||||
openRealtimeRenderOnNodeTextEdit
|
||||
) {
|
||||
if (!this.textEditNode) return
|
||||
this.textEditNode.style.background = openRealtimeRenderOnNodeTextEdit
|
||||
? 'transparent'
|
||||
: this.currentNode
|
||||
? this.getBackground(this.currentNode)
|
||||
: ''
|
||||
this.textEditNode.style.boxShadow = openRealtimeRenderOnNodeTextEdit
|
||||
? 'none'
|
||||
: '0 0 20px rgba(0,0,0,.5)'
|
||||
}
|
||||
|
||||
// 处理画布缩放
|
||||
onScale() {
|
||||
const node = this.getCurrentEditNode()
|
||||
@@ -208,16 +262,37 @@ export default class TextEdit {
|
||||
// 显示文本编辑框
|
||||
showEditTextBox({ node, rect, isInserting, isFromKeyDown, isFromScale }) {
|
||||
if (this.showTextEdit) return
|
||||
const { nodeTextEditZIndex, textAutoWrapWidth, selectTextOnEnterEditText } =
|
||||
this.mindMap.opt
|
||||
const {
|
||||
nodeTextEditZIndex,
|
||||
textAutoWrapWidth,
|
||||
selectTextOnEnterEditText,
|
||||
openRealtimeRenderOnNodeTextEdit,
|
||||
autoEmptyTextWhenKeydownEnterEdit
|
||||
} = this.mindMap.opt
|
||||
if (!isFromScale) {
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
}
|
||||
this.registerTmpShortcut()
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.classList.add('smm-node-edit-wrap')
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: ${this.textNodePaddingY}px ${this.textNodePaddingX}px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
|
||||
this.textEditNode.classList.add(
|
||||
CONSTANTS.EDIT_NODE_CLASS.SMM_NODE_EDIT_WRAP
|
||||
)
|
||||
this.textEditNode.style.cssText = `
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
${
|
||||
openRealtimeRenderOnNodeTextEdit
|
||||
? ''
|
||||
: `box-shadow: 0 0 20px rgba(0,0,0,.5);`
|
||||
}
|
||||
padding: ${this.textNodePaddingY}px ${this.textNodePaddingX}px;
|
||||
margin-left: -${this.textNodePaddingX}px;
|
||||
margin-top: -${this.textNodePaddingY}px;
|
||||
outline: none;
|
||||
word-break: break-all;
|
||||
line-break: anywhere;
|
||||
`
|
||||
this.textEditNode.setAttribute('contenteditable', true)
|
||||
this.textEditNode.addEventListener('keyup', e => {
|
||||
e.stopPropagation()
|
||||
@@ -254,30 +329,38 @@ export default class TextEdit {
|
||||
this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.textEditNode)
|
||||
}
|
||||
let scale = this.mindMap.view.scale
|
||||
let lineHeight = node.style.merge('lineHeight')
|
||||
let fontSize = node.style.merge('fontSize')
|
||||
let textLines = (this.cacheEditingText || node.getData('text'))
|
||||
const scale = this.mindMap.view.scale
|
||||
const fontSize = node.style.merge('fontSize')
|
||||
const textLines = (this.cacheEditingText || node.getData('text'))
|
||||
.split(/\n/gim)
|
||||
.map(item => {
|
||||
return htmlEscape(item)
|
||||
})
|
||||
let isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
|
||||
node.style.domText(this.textEditNode, scale, isMultiLine)
|
||||
const isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
|
||||
node.style.domText(this.textEditNode, scale)
|
||||
if (!openRealtimeRenderOnNodeTextEdit) {
|
||||
this.textEditNode.style.background = this.getBackground(node)
|
||||
}
|
||||
this.textEditNode.style.zIndex = nodeTextEditZIndex
|
||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||
if (isFromKeyDown && autoEmptyTextWhenKeydownEnterEdit) {
|
||||
this.textEditNode.innerHTML = ''
|
||||
} else {
|
||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||
}
|
||||
this.textEditNode.style.minWidth =
|
||||
rect.width + this.textNodePaddingX * 2 + 'px'
|
||||
this.textEditNode.style.minHeight =
|
||||
rect.height + this.textNodePaddingY * 2 + 'px'
|
||||
this.textEditNode.style.minHeight = rect.height + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
this.textEditNode.style.display = 'block'
|
||||
this.textEditNode.style.maxWidth = textAutoWrapWidth * scale + 'px'
|
||||
if (isMultiLine && lineHeight !== 1) {
|
||||
if (isMultiLine) {
|
||||
this.textEditNode.style.lineHeight = noneRichTextNodeLineHeight
|
||||
this.textEditNode.style.transform = `translateY(${
|
||||
-((lineHeight * fontSize - fontSize) / 2) * scale
|
||||
(((noneRichTextNodeLineHeight - 1) * fontSize) / 2) * scale
|
||||
}px)`
|
||||
} else {
|
||||
this.textEditNode.style.lineHeight = 'normal'
|
||||
}
|
||||
this.showTextEdit = true
|
||||
// 选中文本
|
||||
@@ -310,6 +393,27 @@ export default class TextEdit {
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
}
|
||||
|
||||
// 获取编辑区域的背景填充
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文本编辑元素
|
||||
removeTextEditEl() {
|
||||
if (this.mindMap.richText) {
|
||||
@@ -334,17 +438,8 @@ export default class TextEdit {
|
||||
if (!this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
this.mindMap.execCommand(
|
||||
'SET_NODE_TEXT',
|
||||
this.currentNode,
|
||||
this.getEditText()
|
||||
)
|
||||
if (this.currentNode.isGeneralization) {
|
||||
// 概要节点
|
||||
this.currentNode.generalizationBelongNode.updateGeneralization()
|
||||
}
|
||||
this.mindMap.render()
|
||||
const currentNode = this.currentNode
|
||||
const text = this.getEditText()
|
||||
this.currentNode = null
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.textEditNode.innerHTML = ''
|
||||
@@ -353,6 +448,12 @@ export default class TextEdit {
|
||||
this.textEditNode.style.fontWeight = 'normal'
|
||||
this.textEditNode.style.transform = 'translateY(0)'
|
||||
this.showTextEdit = false
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', currentNode, text)
|
||||
// if (currentNode.isGeneralization) {
|
||||
// // 概要节点
|
||||
// currentNode.generalizationBelongNode.updateGeneralization()
|
||||
// }
|
||||
this.mindMap.render()
|
||||
this.mindMap.emit(
|
||||
'hide_text_edit',
|
||||
this.textEditNode,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Style from './Style'
|
||||
import Shape from './Shape'
|
||||
import { G, Rect, Text } from '@svgdotjs/svg.js'
|
||||
import { G, Rect, Text, SVG } from '@svgdotjs/svg.js'
|
||||
import nodeGeneralizationMethods from './nodeGeneralization'
|
||||
import nodeExpandBtnMethods from './nodeExpandBtn'
|
||||
import nodeCommandWrapsMethods from './nodeCommandWraps'
|
||||
@@ -89,7 +89,6 @@ class MindMapNode {
|
||||
this.noteEl = null
|
||||
this.noteContentIsShow = false
|
||||
this._attachmentData = null
|
||||
this._numberData = null
|
||||
this._prefixData = null
|
||||
this._postfixData = null
|
||||
this._expandBtn = null
|
||||
@@ -113,8 +112,6 @@ class MindMapNode {
|
||||
// 概要节点的宽高
|
||||
this._generalizationNodeWidth = 0
|
||||
this._generalizationNodeHeight = 0
|
||||
// 编号字符
|
||||
this.number = opt.number || ''
|
||||
// 各种文字信息的间距
|
||||
this.textContentItemMargin = this.mindMap.opt.textContentMargin
|
||||
// 图片和文字节点的间距
|
||||
@@ -207,10 +204,10 @@ class MindMapNode {
|
||||
}
|
||||
|
||||
// 创建节点的各个内容对象数据
|
||||
// recreateTypes:[] custom、image、icon、text、hyperlink、tag、note、attachment、numbers、prefix、postfix
|
||||
// recreateTypes:[] custom、image、icon、text、hyperlink、tag、note、attachment、numbers、prefix、postfix、checkbox
|
||||
createNodeData(recreateTypes) {
|
||||
// 自定义节点内容
|
||||
let {
|
||||
const {
|
||||
isUseCustomNodeContent,
|
||||
customCreateNodeContent,
|
||||
createNodePrefixContent,
|
||||
@@ -226,9 +223,11 @@ class MindMapNode {
|
||||
'tag',
|
||||
'note',
|
||||
'attachment',
|
||||
'numbers',
|
||||
'prefix',
|
||||
'postfix'
|
||||
'postfix',
|
||||
...this.mindMap.nodeInnerPrefixList.map(item => {
|
||||
return item.name
|
||||
})
|
||||
]
|
||||
const createTypes = {}
|
||||
if (Array.isArray(recreateTypes)) {
|
||||
@@ -264,9 +263,11 @@ class MindMapNode {
|
||||
if (createTypes.note) this._noteData = this.createNoteNode()
|
||||
if (createTypes.attachment)
|
||||
this._attachmentData = this.createAttachmentNode()
|
||||
if (this.mindMap.numbers && createTypes.numbers) {
|
||||
this._numberData = this.mindMap.numbers.createNumberContent(this)
|
||||
}
|
||||
this.mindMap.nodeInnerPrefixList.forEach(item => {
|
||||
if (createTypes[item.name]) {
|
||||
this[`_${item.name}Data`] = item.createContent(this)
|
||||
}
|
||||
})
|
||||
if (createTypes.prefix) {
|
||||
this._prefixData = createNodePrefixContent
|
||||
? createNodePrefixContent(this)
|
||||
@@ -286,15 +287,19 @@ class MindMapNode {
|
||||
}
|
||||
|
||||
// 计算节点的宽高
|
||||
getSize(recreateTypes) {
|
||||
getSize(recreateTypes, opt = {}) {
|
||||
const ignoreUpdateCustomTextWidth = opt.ignoreUpdateCustomTextWidth || false
|
||||
if (!ignoreUpdateCustomTextWidth) {
|
||||
this.customTextWidth = this.getData('customTextWidth') || undefined
|
||||
}
|
||||
this.customLeft = this.getData('customLeft') || undefined
|
||||
this.customTop = this.getData('customTop') || undefined
|
||||
// 这里不要更新概要,不然即使概要没修改,每次也会重新渲染
|
||||
// this.updateGeneralization()
|
||||
this.createNodeData(recreateTypes)
|
||||
let { width, height } = this.getNodeRect()
|
||||
const { width, height } = this.getNodeRect()
|
||||
// 判断节点尺寸是否有变化
|
||||
let changed = this.width !== width || this.height !== height
|
||||
const changed = this.width !== width || this.height !== height
|
||||
this.width = width
|
||||
this.height = height
|
||||
return changed
|
||||
@@ -304,7 +309,7 @@ class MindMapNode {
|
||||
getNodeRect() {
|
||||
// 自定义节点内容
|
||||
if (this.isUseCustomNodeContent()) {
|
||||
let rect = this.measureCustomNodeContentSize(this._customNodeContent)
|
||||
const rect = this.measureCustomNodeContentSize(this._customNodeContent)
|
||||
return {
|
||||
width: this.hasCustomWidth() ? this.customTextWidth : rect.width,
|
||||
height: rect.height
|
||||
@@ -324,11 +329,14 @@ class MindMapNode {
|
||||
this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width
|
||||
this._rectInfo.imgContentHeight = imgContentHeight = this._imgData.height
|
||||
}
|
||||
// 编号内容
|
||||
if (this._numberData) {
|
||||
textContentWidth += this._numberData.width
|
||||
textContentHeight = Math.max(textContentHeight, this._numberData.height)
|
||||
}
|
||||
// 库前置内容
|
||||
this.mindMap.nodeInnerPrefixList.forEach(item => {
|
||||
const itemData = this[`_${item.name}Data`]
|
||||
if (itemData) {
|
||||
textContentWidth += itemData.width
|
||||
textContentHeight = Math.max(textContentHeight, itemData.height)
|
||||
}
|
||||
})
|
||||
// 自定义前置内容
|
||||
if (this._prefixData) {
|
||||
textContentWidth += this._prefixData.width
|
||||
@@ -397,7 +405,7 @@ class MindMapNode {
|
||||
imgContentHeight > 0 && textContentHeight > 0
|
||||
? this.blockContentMargin
|
||||
: 0
|
||||
let { paddingX, paddingY } = this.getPaddingVale()
|
||||
const { paddingX, paddingY } = this.getPaddingVale()
|
||||
// 纯内容宽高
|
||||
let _width = Math.max(imgContentWidth, textContentWidth)
|
||||
let _height = imgContentHeight + textContentHeight
|
||||
@@ -411,7 +419,7 @@ class MindMapNode {
|
||||
_height += tagContentHeight
|
||||
}
|
||||
// 计算节点形状需要的附加内边距
|
||||
let { paddingX: shapePaddingX, paddingY: shapePaddingY } =
|
||||
const { paddingX: shapePaddingX, paddingY: shapePaddingY } =
|
||||
this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY)
|
||||
this.shapePadding.paddingX = shapePaddingX
|
||||
this.shapePadding.paddingY = shapePaddingY
|
||||
@@ -428,7 +436,8 @@ class MindMapNode {
|
||||
if (!this.group) return
|
||||
// 清除之前的内容
|
||||
this.group.clear()
|
||||
const { hoverRectPadding, tagPosition } = this.mindMap.opt
|
||||
const { hoverRectPadding, tagPosition, openRealtimeRenderOnNodeTextEdit } =
|
||||
this.mindMap.opt
|
||||
let { width, height, textContentItemMargin } = this
|
||||
let { paddingY } = this.getPaddingVale()
|
||||
const halfBorderWidth = this.getBorderWidth() / 2
|
||||
@@ -480,14 +489,17 @@ class MindMapNode {
|
||||
// 内容节点
|
||||
let textContentNested = new G()
|
||||
let textContentOffsetX = 0
|
||||
// 编号内容
|
||||
if (this._numberData) {
|
||||
this._numberData.node
|
||||
.x(textContentOffsetX)
|
||||
.y((textContentHeight - this._numberData.height) / 2)
|
||||
textContentNested.add(this._numberData.node)
|
||||
textContentOffsetX += this._numberData.width + textContentItemMargin
|
||||
}
|
||||
// 库前置内容
|
||||
this.mindMap.nodeInnerPrefixList.forEach(item => {
|
||||
const itemData = this[`_${item.name}Data`]
|
||||
if (itemData) {
|
||||
itemData.node
|
||||
.x(textContentOffsetX)
|
||||
.y((textContentHeight - itemData.height) / 2)
|
||||
textContentNested.add(itemData.node)
|
||||
textContentOffsetX += itemData.width + textContentItemMargin
|
||||
}
|
||||
})
|
||||
// 自定义前置内容
|
||||
if (this._prefixData) {
|
||||
const foreignObject = createForeignObjectNode({
|
||||
@@ -524,6 +536,12 @@ class MindMapNode {
|
||||
.x(-oldX) // 修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题
|
||||
.x(textContentOffsetX)
|
||||
.y((textContentHeight - this._textData.height) / 2)
|
||||
// 如果开启了文本编辑实时渲染,需要判断当前渲染的节点是否是正在编辑的节点,是的话将透明度设置为0不显示
|
||||
if (openRealtimeRenderOnNodeTextEdit) {
|
||||
this._textData.node.opacity(
|
||||
this.mindMap.renderer.textEdit.getCurrentEditNode() === this ? 0 : 1
|
||||
)
|
||||
}
|
||||
textContentNested.add(this._textData.node)
|
||||
textContentOffsetX += this._textData.width + textContentItemMargin
|
||||
}
|
||||
@@ -656,7 +674,7 @@ class MindMapNode {
|
||||
// 多选和取消多选
|
||||
if (!readonly && (e.ctrlKey || e.metaKey) && enableCtrlKeyNodeSelection) {
|
||||
this.isMultipleChoice = true
|
||||
let isActive = this.getData('isActive')
|
||||
const isActive = this.getData('isActive')
|
||||
if (!isActive)
|
||||
this.mindMap.emit(
|
||||
'before_node_active',
|
||||
@@ -775,7 +793,7 @@ class MindMapNode {
|
||||
this.renderExpandBtn()
|
||||
}
|
||||
} else {
|
||||
let { isActive, expand } = this.getData()
|
||||
const { isActive, expand } = this.getData()
|
||||
// 展开状态且非激活状态,且当前鼠标不在它上面,才隐藏
|
||||
if (childrenLength <= 0) {
|
||||
this.removeExpandBtn()
|
||||
@@ -793,7 +811,7 @@ class MindMapNode {
|
||||
// 更新协同头像
|
||||
if (this.updateUserListNode) this.updateUserListNode()
|
||||
// 更新节点位置
|
||||
let t = this.group.transform()
|
||||
const t = this.group.transform()
|
||||
// 保存一份当前节点数据快照
|
||||
this.nodeDataSnapshot = JSON.stringify(this.getData())
|
||||
// 节点位置变化才更新,因为即使值没有变化属性设置操作也是耗时的
|
||||
@@ -804,10 +822,10 @@ class MindMapNode {
|
||||
|
||||
// 获取节点相当于画布的位置
|
||||
getNodePosInClient(_left, _top) {
|
||||
let drawTransform = this.mindMap.draw.transform()
|
||||
let { scaleX, scaleY, translateX, translateY } = drawTransform
|
||||
let left = _left * scaleX + translateX
|
||||
let top = _top * scaleY + translateY
|
||||
const drawTransform = this.mindMap.draw.transform()
|
||||
const { scaleX, scaleY, translateX, translateY } = drawTransform
|
||||
const left = _left * scaleX + translateX
|
||||
const top = _top * scaleY + translateY
|
||||
return {
|
||||
left,
|
||||
top
|
||||
@@ -826,8 +844,8 @@ class MindMapNode {
|
||||
}
|
||||
|
||||
// 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置
|
||||
reRender(recreateTypes) {
|
||||
let sizeChange = this.getSize(recreateTypes)
|
||||
reRender(recreateTypes, opt) {
|
||||
const sizeChange = this.getSize(recreateTypes, opt)
|
||||
this.layout()
|
||||
this.update()
|
||||
return sizeChange
|
||||
@@ -977,7 +995,7 @@ class MindMapNode {
|
||||
if (this.group) this.group.hide()
|
||||
this.hideGeneralization()
|
||||
if (this.parent) {
|
||||
let index = this.parent.children.indexOf(this)
|
||||
const index = this.parent.children.indexOf(this)
|
||||
this.parent._lines[index] && this.parent._lines[index].hide()
|
||||
this._lines.forEach(item => {
|
||||
item.hide()
|
||||
@@ -999,7 +1017,7 @@ class MindMapNode {
|
||||
this.group.show()
|
||||
this.showGeneralization()
|
||||
if (this.parent) {
|
||||
let index = this.parent.children.indexOf(this)
|
||||
const index = this.parent.children.indexOf(this)
|
||||
this.parent._lines[index] && this.parent._lines[index].show()
|
||||
this._lines.forEach(item => {
|
||||
item.show()
|
||||
@@ -1245,7 +1263,7 @@ class MindMapNode {
|
||||
|
||||
// 获取某个样式
|
||||
getStyle(prop, root) {
|
||||
let v = this.style.merge(prop, root)
|
||||
const v = this.style.merge(prop, root)
|
||||
return v === undefined ? '' : v
|
||||
}
|
||||
|
||||
@@ -1310,11 +1328,11 @@ class MindMapNode {
|
||||
|
||||
// 获取节点的尺寸和位置信息,宽高是应用了缩放效果后的实际宽高,位置信息相对于画布
|
||||
getRectInSvg() {
|
||||
let { scaleX, scaleY, translateX, translateY } =
|
||||
const { scaleX, scaleY, translateX, translateY } =
|
||||
this.mindMap.draw.transform()
|
||||
let { left, top, width, height } = this
|
||||
let right = (left + width) * scaleX + translateX
|
||||
let bottom = (top + height) * scaleY + translateY
|
||||
const right = (left + width) * scaleX + translateX
|
||||
const bottom = (top + height) * scaleY + translateY
|
||||
left = left * scaleX + translateX
|
||||
top = top * scaleY + translateY
|
||||
return {
|
||||
@@ -1355,6 +1373,15 @@ class MindMapNode {
|
||||
return new Text().text(text)
|
||||
}
|
||||
|
||||
// 获取SVG.js库的一些对象
|
||||
getSvgObjects() {
|
||||
return {
|
||||
SVG,
|
||||
G,
|
||||
Rect
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否支持拖拽调整宽度
|
||||
// 1.富文本模式
|
||||
// 2.自定义节点内容
|
||||
|
||||
@@ -12,6 +12,7 @@ const backgroundStyleProps = [
|
||||
class Style {
|
||||
// 设置背景样式
|
||||
static setBackgroundStyle(el, themeConfig) {
|
||||
if (!el) return
|
||||
// 缓存容器元素原本的样式
|
||||
if (!Style.cacheStyle) {
|
||||
Style.cacheStyle = {}
|
||||
@@ -183,7 +184,7 @@ class Style {
|
||||
})
|
||||
.css({
|
||||
'font-family': styles.fontFamily,
|
||||
'font-size': styles.fontSize,
|
||||
'font-size': styles.fontSize + 'px',
|
||||
'font-weight': styles.fontWeight,
|
||||
'font-style': styles.fontStyle,
|
||||
'text-decoration': styles.textDecoration
|
||||
@@ -191,14 +192,15 @@ class Style {
|
||||
}
|
||||
|
||||
// 生成内联样式
|
||||
createStyleText() {
|
||||
createStyleText(customStyle = {}) {
|
||||
const styles = {
|
||||
color: this.merge('color'),
|
||||
fontFamily: this.merge('fontFamily'),
|
||||
fontSize: this.merge('fontSize'),
|
||||
fontWeight: this.merge('fontWeight'),
|
||||
fontStyle: this.merge('fontStyle'),
|
||||
textDecoration: this.merge('textDecoration')
|
||||
textDecoration: this.merge('textDecoration'),
|
||||
...customStyle
|
||||
}
|
||||
return `
|
||||
color: ${styles.color};
|
||||
@@ -229,20 +231,20 @@ class Style {
|
||||
}
|
||||
|
||||
// html文字节点
|
||||
domText(node, fontSizeScale = 1, isMultiLine) {
|
||||
domText(node, fontSizeScale = 1) {
|
||||
const styles = {
|
||||
color: this.merge('color'),
|
||||
fontFamily: this.merge('fontFamily'),
|
||||
fontSize: this.merge('fontSize'),
|
||||
fontWeight: this.merge('fontWeight'),
|
||||
fontStyle: this.merge('fontStyle'),
|
||||
textDecoration: this.merge('textDecoration'),
|
||||
lineHeight: this.merge('lineHeight')
|
||||
textDecoration: this.merge('textDecoration')
|
||||
}
|
||||
node.style.color = styles.color
|
||||
node.style.textDecoration = styles.textDecoration
|
||||
node.style.fontFamily = styles.fontFamily
|
||||
node.style.fontSize = styles.fontSize * fontSizeScale + 'px'
|
||||
node.style.fontWeight = styles.fontWeight || 'normal'
|
||||
node.style.lineHeight = !isMultiLine ? 'normal' : styles.lineHeight
|
||||
node.style.fontStyle = styles.fontStyle
|
||||
}
|
||||
|
||||
@@ -276,6 +278,10 @@ class Style {
|
||||
|
||||
// 连线
|
||||
line(line, { width, color, dasharray } = {}, enableMarker, childNode) {
|
||||
const { customHandleLine } = this.ctx.mindMap.opt
|
||||
if (typeof customHandleLine === 'function') {
|
||||
customHandleLine(this.ctx, line, { width, color, dasharray })
|
||||
}
|
||||
line.stroke({ color, dasharray, width }).fill({ color: 'none' })
|
||||
// 可以显示箭头
|
||||
if (enableMarker) {
|
||||
@@ -338,7 +344,7 @@ class Style {
|
||||
node2.fill({ color: color })
|
||||
fillNode.fill({ color: fill })
|
||||
if (this.ctx.mindMap.opt.isShowExpandNum) {
|
||||
node.attr({ 'font-size': fontSize, 'font-color': fontColor })
|
||||
node.attr({ 'font-size': fontSize + 'px', 'font-color': fontColor })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,6 +359,17 @@ class Style {
|
||||
return res
|
||||
}
|
||||
|
||||
// 获取自定义的样式
|
||||
getCustomStyle() {
|
||||
const customStyle = {}
|
||||
Object.keys(this.ctx.getData()).forEach(item => {
|
||||
if (checkIsNodeStyleDataKey(item)) {
|
||||
customStyle[item] = this.ctx.getData(item)
|
||||
}
|
||||
})
|
||||
return customStyle
|
||||
}
|
||||
|
||||
// hover和激活节点
|
||||
hoverNode(node) {
|
||||
const hoverRectColor =
|
||||
|
||||
@@ -28,7 +28,7 @@ function createTextAvatar(item) {
|
||||
color: '#fff'
|
||||
})
|
||||
.css({
|
||||
'font-size': fontSize
|
||||
'font-size': fontSize + 'px'
|
||||
})
|
||||
.dx(-fontSize / 2)
|
||||
.dy((avatarSize - fontSize) / 2)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
measureText,
|
||||
resizeImgSize,
|
||||
removeHtmlStyle,
|
||||
addHtmlStyle,
|
||||
@@ -11,7 +10,19 @@ import {
|
||||
} from '../../../utils'
|
||||
import { Image as SVGImage, SVG, A, G, Rect, Text } from '@svgdotjs/svg.js'
|
||||
import iconsSvg from '../../../svg/icons'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
import {
|
||||
CONSTANTS,
|
||||
noneRichTextNodeLineHeight
|
||||
} from '../../../constants/constant'
|
||||
|
||||
// 测量svg文本宽高
|
||||
const measureText = (text, style) => {
|
||||
const g = new G()
|
||||
const node = new Text().text(text)
|
||||
style.text(node)
|
||||
g.add(node)
|
||||
return g.bbox()
|
||||
}
|
||||
|
||||
// 标签默认的样式
|
||||
const defaultTagStyle = {
|
||||
@@ -113,6 +124,20 @@ function createIconNode() {
|
||||
})
|
||||
}
|
||||
|
||||
// 尝试给html指定标签添加内联样式
|
||||
function tryAddHtmlStyle(text, style) {
|
||||
const tagList = ['span', 'strong', 's', 'em', 'u']
|
||||
// let _text = text
|
||||
// for (let i = 0; i < tagList.length; i++) {
|
||||
// text = addHtmlStyle(text, tagList[i], style)
|
||||
// if (text !== _text) {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// return text
|
||||
return addHtmlStyle(text, tagList, style)
|
||||
}
|
||||
|
||||
// 创建富文本节点
|
||||
function createRichTextNode(specifyText) {
|
||||
const hasCustomWidth = this.hasCustomWidth()
|
||||
@@ -129,25 +154,22 @@ function createRichTextNode(specifyText) {
|
||||
}
|
||||
if ([CONSTANTS.CHANGE_THEME].includes(this.mindMap.renderer.renderSource)) {
|
||||
// 如果自定义过样式则不允许覆盖
|
||||
if (!this.hasCustomStyle()) {
|
||||
recoverText = true
|
||||
}
|
||||
// if (!this.hasCustomStyle() ) {
|
||||
recoverText = true
|
||||
// }
|
||||
}
|
||||
if (recoverText && !isUndef(text)) {
|
||||
// 判断节点内容是否是富文本
|
||||
let isRichText = checkIsRichText(text)
|
||||
const isRichText = checkIsRichText(text)
|
||||
// 获取自定义样式
|
||||
const customStyle = this.style.getCustomStyle()
|
||||
// 样式字符串
|
||||
let style = this.style.createStyleText()
|
||||
const style = this.style.createStyleText(customStyle)
|
||||
if (isRichText) {
|
||||
// 如果是富文本那么线移除内联样式
|
||||
text = removeHtmlStyle(text)
|
||||
// 再添加新的内联样式
|
||||
let _text = text
|
||||
text = addHtmlStyle(text, 'span', style)
|
||||
// 给span添加样式没有成功,则尝试给strong标签添加样式
|
||||
if (text === _text) {
|
||||
text = addHtmlStyle(text, 'strong', style)
|
||||
}
|
||||
text = this.tryAddHtmlStyle(text, style)
|
||||
} else {
|
||||
// 非富文本
|
||||
text = `<p><span style="${style}">${text}</span></p>`
|
||||
@@ -218,16 +240,14 @@ function createTextNode(specifyText) {
|
||||
}
|
||||
let g = new G()
|
||||
let fontSize = this.getStyle('fontSize', false)
|
||||
let lineHeight = this.getStyle('lineHeight', false)
|
||||
// 文本超长自动换行
|
||||
let textStyle = this.style.getTextFontStyle()
|
||||
let textArr = []
|
||||
if (!isUndef(text)) {
|
||||
textArr = String(text).split(/\n/gim)
|
||||
}
|
||||
const { textAutoWrapWidth: maxWidth, emptyTextMeasureHeightText } =
|
||||
this.mindMap.opt
|
||||
let isMultiLine = false
|
||||
let isMultiLine = textArr.length > 1
|
||||
textArr.forEach((item, index) => {
|
||||
let arr = item.split('')
|
||||
let lines = []
|
||||
@@ -235,7 +255,7 @@ function createTextNode(specifyText) {
|
||||
while (arr.length) {
|
||||
let str = arr.shift()
|
||||
let text = [...line, str].join('')
|
||||
if (measureText(text, textStyle).width <= maxWidth) {
|
||||
if (measureText(text, this.style).width <= maxWidth) {
|
||||
line.push(str)
|
||||
} else {
|
||||
lines.push(line.join(''))
|
||||
@@ -250,11 +270,20 @@ function createTextNode(specifyText) {
|
||||
}
|
||||
textArr[index] = lines.join('\n')
|
||||
})
|
||||
textArr = textArr.join('\n').split(/\n/gim)
|
||||
textArr = textArr.join('\n').replace(/\n$/g, '').split(/\n/gim)
|
||||
textArr.forEach((item, index) => {
|
||||
let node = new Text().text(item)
|
||||
// 避免尾部的空行不占宽度
|
||||
// 同时解决该问题:https://github.com/wanglin2/mind-map/issues/1037
|
||||
if (item === '') {
|
||||
item = ''
|
||||
}
|
||||
const node = new Text().text(item)
|
||||
node.addClass('smm-text-node-wrap')
|
||||
this.style.text(node)
|
||||
node.y(fontSize * lineHeight * index)
|
||||
node.y(
|
||||
fontSize * noneRichTextNodeLineHeight * index +
|
||||
((noneRichTextNodeLineHeight - 1) * fontSize) / 2
|
||||
)
|
||||
g.add(node)
|
||||
})
|
||||
let { width, height } = g.bbox()
|
||||
@@ -441,6 +470,9 @@ function createNoteNode() {
|
||||
node.on('click', e => {
|
||||
this.mindMap.emit('node_note_click', this, e, node)
|
||||
})
|
||||
node.on('dblclick', e => {
|
||||
this.mindMap.emit('node_note_dblclick', this, e, node)
|
||||
})
|
||||
return {
|
||||
node,
|
||||
width: iconSize,
|
||||
@@ -524,6 +556,7 @@ export default {
|
||||
createImgNode,
|
||||
getImgShowSize,
|
||||
createIconNode,
|
||||
tryAddHtmlStyle,
|
||||
createRichTextNode,
|
||||
createTextNode,
|
||||
createHyperlinkNode,
|
||||
|
||||
@@ -8,7 +8,7 @@ function initDragHandle() {
|
||||
// 拖拽手柄元素
|
||||
this._dragHandleNodes = null
|
||||
// 手柄元素的宽度
|
||||
this.dragHandleWidth = 2
|
||||
this.dragHandleWidth = 4
|
||||
// 鼠标按下时的x坐标
|
||||
this.dragHandleMousedownX = 0
|
||||
// 鼠标是否处于按下状态
|
||||
@@ -71,7 +71,9 @@ function onDragMousemoveHandle(e) {
|
||||
this.left = this.dragHandleMousedownLeft + ox / scaleX
|
||||
}
|
||||
// 自定义内容不重新渲染,交给开发者
|
||||
this.reRender(useCustomContent ? [] : ['text'])
|
||||
this.reRender(useCustomContent ? [] : ['text'], {
|
||||
ignoreUpdateCustomTextWidth: true
|
||||
})
|
||||
}
|
||||
|
||||
// 鼠标松开事件
|
||||
|
||||
@@ -354,6 +354,10 @@ class View {
|
||||
|
||||
// 判断是否需要将思维导图限制在画布内
|
||||
checkNeedMindMapInCanvas() {
|
||||
// 如果当前在演示模式,那么不需要限制
|
||||
if (this.mindMap.demonstrate && this.mindMap.demonstrate.isInDemonstrate) {
|
||||
return false
|
||||
}
|
||||
const { isLimitMindMapInCanvasWhenHasScrollbar, isLimitMindMapInCanvas } =
|
||||
this.mindMap.opt
|
||||
// 如果注册了滚动条插件,那么使用isLimitMindMapInCanvasWhenHasScrollbar配置
|
||||
|
||||
@@ -69,28 +69,6 @@ class Base {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取节点编号信息
|
||||
getNumberInfo({ parent, ancestors, layerIndex, index }) {
|
||||
// 编号
|
||||
const hasNumberPlugin = !!this.mindMap.numbers
|
||||
const parentNumberStr =
|
||||
hasNumberPlugin && parent && parent._node.number
|
||||
? parent._node.number
|
||||
: ''
|
||||
const newNumberStr = hasNumberPlugin
|
||||
? this.mindMap.numbers.getNodeNumberStr({
|
||||
ancestors,
|
||||
layerIndex,
|
||||
num: index + 1,
|
||||
parentNumberStr
|
||||
})
|
||||
: ''
|
||||
return {
|
||||
hasNumberPlugin,
|
||||
newNumberStr
|
||||
}
|
||||
}
|
||||
|
||||
// 节点节点数据是否发生了改变
|
||||
checkIsNodeDataChange(lastData, curData) {
|
||||
if (lastData) {
|
||||
@@ -105,14 +83,21 @@ class Base {
|
||||
|
||||
// 创建节点实例
|
||||
createNode(data, parent, isRoot, layerIndex, index, ancestors) {
|
||||
// 编号
|
||||
const { hasNumberPlugin, newNumberStr } = this.getNumberInfo({
|
||||
parent,
|
||||
ancestors,
|
||||
layerIndex,
|
||||
index
|
||||
})
|
||||
// 创建节点
|
||||
// 库前置内容数据
|
||||
const nodeInnerPrefixData = {}
|
||||
this.mindMap.nodeInnerPrefixList.forEach(item => {
|
||||
if (item.createNodeData) {
|
||||
const [key, value] = item.createNodeData({
|
||||
data,
|
||||
parent,
|
||||
ancestors,
|
||||
layerIndex,
|
||||
index
|
||||
})
|
||||
nodeInnerPrefixData[key] = value
|
||||
}
|
||||
})
|
||||
const uid = data.data.uid
|
||||
let newNode = null
|
||||
// 数据上保存了节点引用,那么直接复用节点
|
||||
@@ -132,14 +117,16 @@ class Base {
|
||||
}
|
||||
this.cacheNode(data._node.uid, newNode)
|
||||
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
|
||||
// 判断编号是否改变
|
||||
let isNumberChange = false
|
||||
if (hasNumberPlugin) {
|
||||
isNumberChange = this.mindMap.numbers.updateNumber(
|
||||
newNode,
|
||||
newNumberStr
|
||||
)
|
||||
}
|
||||
// 库前置内容是否改变了
|
||||
let isNodeInnerPrefixChange = false
|
||||
this.mindMap.nodeInnerPrefixList.forEach(item => {
|
||||
if (item.updateNodeData) {
|
||||
const isChange = item.updateNodeData(newNode, nodeInnerPrefixData)
|
||||
if (isChange) {
|
||||
isNodeInnerPrefixChange = isChange
|
||||
}
|
||||
}
|
||||
})
|
||||
// 主题或主题配置改变了
|
||||
const isResizeSource = this.checkIsNeedResizeSources()
|
||||
// 节点数据改变了
|
||||
@@ -153,7 +140,7 @@ class Base {
|
||||
isNodeDataChange ||
|
||||
isLayerTypeChange ||
|
||||
newNode.getData('resetRichText') ||
|
||||
isNumberChange
|
||||
isNodeInnerPrefixChange
|
||||
) {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
@@ -190,21 +177,23 @@ class Base {
|
||||
const isResizeSource = this.checkIsNeedResizeSources()
|
||||
// 点数据改变了
|
||||
const isNodeDataChange = this.checkIsNodeDataChange(lastData, data.data)
|
||||
// 判断编号是否改变
|
||||
let isNumberChange = false
|
||||
if (hasNumberPlugin) {
|
||||
isNumberChange = this.mindMap.numbers.updateNumber(
|
||||
newNode,
|
||||
newNumberStr
|
||||
)
|
||||
}
|
||||
// 库前置内容是否改变了
|
||||
let isNodeInnerPrefixChange = false
|
||||
this.mindMap.nodeInnerPrefixList.forEach(item => {
|
||||
if (item.updateNodeData) {
|
||||
const isChange = item.updateNodeData(newNode, nodeInnerPrefixData)
|
||||
if (isChange) {
|
||||
isNodeInnerPrefixChange = isChange
|
||||
}
|
||||
}
|
||||
})
|
||||
// 重新计算节点大小和布局
|
||||
if (
|
||||
isResizeSource ||
|
||||
isNodeDataChange ||
|
||||
isLayerTypeChange ||
|
||||
newNode.getData('resetRichText') ||
|
||||
isNumberChange
|
||||
isNodeInnerPrefixChange
|
||||
) {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
@@ -222,7 +211,7 @@ class Base {
|
||||
layerIndex,
|
||||
isRoot,
|
||||
parent: !isRoot ? parent._node : null,
|
||||
number: newNumberStr
|
||||
...nodeInnerPrefixData
|
||||
})
|
||||
// uid保存到数据上,为了节点复用
|
||||
data.data.uid = newUid
|
||||
@@ -463,8 +452,10 @@ class Base {
|
||||
const end = list[len - 1]
|
||||
// 如果三点在一条直线,那么不用处理
|
||||
const isOneLine =
|
||||
(start[0] === center[0] && center[0] === end[0]) ||
|
||||
(start[1] === center[1] && center[1] === end[1])
|
||||
(start[0].toFixed(0) === center[0].toFixed(0) &&
|
||||
center[0].toFixed(0) === end[0].toFixed(0)) ||
|
||||
(start[1].toFixed(0) === center[1].toFixed(0) &&
|
||||
center[1].toFixed(0) === end[1].toFixed(0))
|
||||
if (!isOneLine) {
|
||||
const cStart = this.computeNewPoint(start, center, lineRadius)
|
||||
const cEnd = this.computeNewPoint(end, center, lineRadius)
|
||||
|
||||
@@ -35,7 +35,14 @@ class MindMap extends Base {
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(cur, parent, isRoot, layerIndex, index, ancestors) => {
|
||||
let newNode = this.createNode(cur, parent, isRoot, layerIndex, index, ancestors)
|
||||
let newNode = this.createNode(
|
||||
cur,
|
||||
parent,
|
||||
isRoot,
|
||||
layerIndex,
|
||||
index,
|
||||
ancestors
|
||||
)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
@@ -47,9 +54,10 @@ class MindMap extends Base {
|
||||
} else {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
newNode.getData('dir') ||
|
||||
(index % 2 === 0
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.LEFT)
|
||||
}
|
||||
// 根据生长方向定位到父节点的左侧或右侧
|
||||
newNode.left =
|
||||
|
||||
@@ -11,11 +11,25 @@ import {
|
||||
import associativeLineControlsMethods from './associativeLine/associativeLineControls'
|
||||
import associativeLineTextMethods from './associativeLine/associativeLineText'
|
||||
|
||||
const styleProps = [
|
||||
'associativeLineWidth',
|
||||
'associativeLineColor',
|
||||
'associativeLineActiveWidth',
|
||||
'associativeLineActiveColor',
|
||||
'associativeLineDasharray',
|
||||
'associativeLineTextColor',
|
||||
'associativeLineTextFontSize',
|
||||
'associativeLineTextLineHeight',
|
||||
'associativeLineTextFontFamily'
|
||||
]
|
||||
|
||||
// 关联线插件
|
||||
class AssociativeLine {
|
||||
constructor(opt = {}) {
|
||||
this.mindMap = opt.mindMap
|
||||
this.associativeLineDraw = this.mindMap.associativeLineDraw
|
||||
// 本次不要重新渲染连线
|
||||
this.isNotRenderAllLines = false
|
||||
// 当前所有连接线
|
||||
this.lineList = []
|
||||
// 当前激活的连接线
|
||||
@@ -27,9 +41,6 @@ class AssociativeLine {
|
||||
this.overlapNode = null // 创建过程中的目标节点
|
||||
// 是否有节点正在被拖拽
|
||||
this.isNodeDragging = false
|
||||
// 箭头图标
|
||||
this.markerPath = null
|
||||
this.marker = this.createMarker()
|
||||
// 控制点
|
||||
this.controlLine1 = null
|
||||
this.controlLine2 = null
|
||||
@@ -112,6 +123,25 @@ class AssociativeLine {
|
||||
this.mindMap.off('beforeDestroy', this.onBeforeDestroy)
|
||||
}
|
||||
|
||||
// 获取关联线的样式配置
|
||||
// 优先级:关联线自定义样式、节点自定义样式、主题的节点层级样式、主题的最外层样式
|
||||
getStyleConfig(node, toNode) {
|
||||
let lineStyle = {}
|
||||
if (toNode) {
|
||||
const associativeLineStyle = node.getData('associativeLineStyle') || {}
|
||||
lineStyle = associativeLineStyle[toNode.getData('uid')] || {}
|
||||
}
|
||||
const res = {}
|
||||
styleProps.forEach(prop => {
|
||||
if (typeof lineStyle[prop] !== 'undefined') {
|
||||
res[prop] = lineStyle[prop]
|
||||
} else {
|
||||
res[prop] = node.getStyle(prop)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// 实例销毁时清除关联线文字编辑框
|
||||
onBeforeDestroy() {
|
||||
this.hideEditTextBox()
|
||||
@@ -140,12 +170,12 @@ class AssociativeLine {
|
||||
}
|
||||
|
||||
// 创建箭头
|
||||
createMarker() {
|
||||
createMarker(callback = () => {}) {
|
||||
return this.associativeLineDraw.marker(20, 20, add => {
|
||||
add.ref(12, 5)
|
||||
add.size(10, 10)
|
||||
add.attr('orient', 'auto-start-reverse')
|
||||
this.markerPath = add.path('M0,0 L2,5 L0,10 L10,5 Z')
|
||||
callback(add.path('M0,0 L2,5 L0,10 L10,5 Z'))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -172,6 +202,10 @@ class AssociativeLine {
|
||||
|
||||
// 渲染所有连线
|
||||
renderAllLines() {
|
||||
if (this.isNotRenderAllLines) {
|
||||
this.isNotRenderAllLines = false
|
||||
return
|
||||
}
|
||||
// 先移除
|
||||
this.removeAllLines()
|
||||
this.removeControls()
|
||||
@@ -223,11 +257,14 @@ class AssociativeLine {
|
||||
associativeLineWidth,
|
||||
associativeLineColor,
|
||||
associativeLineActiveWidth,
|
||||
associativeLineActiveColor,
|
||||
associativeLineDasharray
|
||||
} = this.mindMap.themeConfig
|
||||
} = this.getStyleConfig(node, toNode)
|
||||
// 箭头
|
||||
this.markerPath
|
||||
let markerPath = null
|
||||
const marker = this.createMarker(p => {
|
||||
markerPath = p
|
||||
})
|
||||
markerPath
|
||||
.stroke({ color: associativeLineColor })
|
||||
.fill({ color: associativeLineColor })
|
||||
// 路径
|
||||
@@ -247,7 +284,7 @@ class AssociativeLine {
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
path.plot(pathStr)
|
||||
path.marker('end', this.marker)
|
||||
path.marker('end', marker)
|
||||
// 不可见的点击线
|
||||
let clickPath = this.associativeLineDraw.path()
|
||||
clickPath
|
||||
@@ -258,6 +295,7 @@ class AssociativeLine {
|
||||
let text = this.createText({
|
||||
path,
|
||||
clickPath,
|
||||
markerPath,
|
||||
node,
|
||||
toNode,
|
||||
startPoint,
|
||||
@@ -270,6 +308,7 @@ class AssociativeLine {
|
||||
this.setActiveLine({
|
||||
path,
|
||||
clickPath,
|
||||
markerPath,
|
||||
text,
|
||||
node,
|
||||
toNode,
|
||||
@@ -284,14 +323,72 @@ class AssociativeLine {
|
||||
this.showEditTextBox(text)
|
||||
})
|
||||
// 渲染关联线文字
|
||||
this.renderText(this.getText(node, toNode), path, text)
|
||||
this.renderText(this.getText(node, toNode), path, text, node, toNode)
|
||||
this.lineList.push([path, clickPath, text, node, toNode])
|
||||
}
|
||||
|
||||
// 更新当前激活连线的样式,一般在自定义了节点关联线的样式后调用
|
||||
// 直接调用node.setStyle方法更新样式会直接触发关联线更新,但是关联线的激活状态会丢失
|
||||
// 所以可以调用node.setData方法更新数据,然后再调用该方法更新样式,这样关联线激活状态不会丢失
|
||||
updateActiveLineStyle() {
|
||||
if (!this.activeLine) return
|
||||
this.isNotRenderAllLines = true
|
||||
const [path, clickPath, text, node, toNode, markerPath] = this.activeLine
|
||||
const {
|
||||
associativeLineWidth,
|
||||
associativeLineColor,
|
||||
associativeLineDasharray,
|
||||
associativeLineActiveWidth,
|
||||
associativeLineActiveColor,
|
||||
associativeLineTextColor,
|
||||
associativeLineTextFontFamily,
|
||||
associativeLineTextFontSize
|
||||
} = this.getStyleConfig(node, toNode)
|
||||
path
|
||||
.stroke({
|
||||
width: associativeLineWidth,
|
||||
color: associativeLineColor,
|
||||
dasharray: associativeLineDasharray || [6, 4]
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
clickPath
|
||||
.stroke({
|
||||
width: associativeLineActiveWidth,
|
||||
color: associativeLineActiveColor
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
markerPath
|
||||
.stroke({ color: associativeLineColor })
|
||||
.fill({ color: associativeLineColor })
|
||||
text.find('text').forEach(textNode => {
|
||||
textNode
|
||||
.fill({
|
||||
color: associativeLineTextColor
|
||||
})
|
||||
.css({
|
||||
'font-family': associativeLineTextFontFamily,
|
||||
'font-size': associativeLineTextFontSize + 'px'
|
||||
})
|
||||
})
|
||||
if (this.controlLine1) {
|
||||
this.controlLine1.stroke({ color: associativeLineActiveColor })
|
||||
}
|
||||
if (this.controlLine2) {
|
||||
this.controlLine2.stroke({ color: associativeLineActiveColor })
|
||||
}
|
||||
if (this.controlPoint1) {
|
||||
this.controlPoint1.stroke({ color: associativeLineActiveColor })
|
||||
}
|
||||
if (this.controlPoint2) {
|
||||
this.controlPoint2.stroke({ color: associativeLineActiveColor })
|
||||
}
|
||||
}
|
||||
|
||||
// 激活某根关联线
|
||||
setActiveLine({
|
||||
path,
|
||||
clickPath,
|
||||
markerPath,
|
||||
text,
|
||||
node,
|
||||
toNode,
|
||||
@@ -299,25 +396,33 @@ class AssociativeLine {
|
||||
endPoint,
|
||||
controlPoints
|
||||
}) {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
let { associativeLineActiveColor } = this.getStyleConfig(node, toNode)
|
||||
// 如果当前存在激活节点,那么取消激活节点
|
||||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||||
// 否则清除当前的关联线的激活状态,如果有的话
|
||||
this.clearActiveLine()
|
||||
// 保存当前激活的关联线信息
|
||||
this.activeLine = [path, clickPath, text, node, toNode]
|
||||
this.activeLine = [path, clickPath, text, node, toNode, markerPath]
|
||||
// 让不可见的点击线显示
|
||||
clickPath.stroke({ color: associativeLineActiveColor })
|
||||
// 如果没有输入过关联线文字,那么显示默认文字
|
||||
if (!this.getText(node, toNode)) {
|
||||
this.renderText(this.mindMap.opt.defaultAssociativeLineText, path, text)
|
||||
this.renderText(
|
||||
this.mindMap.opt.defaultAssociativeLineText,
|
||||
path,
|
||||
text,
|
||||
node,
|
||||
toNode
|
||||
)
|
||||
}
|
||||
// 渲染控制点和连线
|
||||
this.renderControls(
|
||||
startPoint,
|
||||
endPoint,
|
||||
controlPoints[0],
|
||||
controlPoints[1]
|
||||
controlPoints[1],
|
||||
node,
|
||||
toNode
|
||||
)
|
||||
this.mindMap.emit('associative_line_click', path, clickPath, node, toNode)
|
||||
this.front()
|
||||
@@ -346,7 +451,7 @@ class AssociativeLine {
|
||||
associativeLineWidth,
|
||||
associativeLineColor,
|
||||
associativeLineDasharray
|
||||
} = this.mindMap.themeConfig
|
||||
} = this.getStyleConfig(fromNode)
|
||||
if (this.isCreatingLine || !fromNode) return
|
||||
this.front()
|
||||
this.isCreatingLine = true
|
||||
@@ -360,10 +465,14 @@ class AssociativeLine {
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
// 箭头
|
||||
this.markerPath
|
||||
let markerPath = null
|
||||
const marker = this.createMarker(p => {
|
||||
markerPath = p
|
||||
})
|
||||
markerPath
|
||||
.stroke({ color: associativeLineColor })
|
||||
.fill({ color: associativeLineColor })
|
||||
this.creatingLine.marker('end', this.marker)
|
||||
this.creatingLine.marker('end', marker)
|
||||
}
|
||||
|
||||
// 取消创建关联线
|
||||
@@ -529,7 +638,8 @@ class AssociativeLine {
|
||||
associativeLineTargets,
|
||||
associativeLinePoint,
|
||||
associativeLineTargetControlOffsets,
|
||||
associativeLineText
|
||||
associativeLineText,
|
||||
associativeLineStyle
|
||||
} = node.getData()
|
||||
associativeLinePoint = associativeLinePoint || []
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
@@ -542,6 +652,15 @@ class AssociativeLine {
|
||||
}
|
||||
})
|
||||
}
|
||||
// 更新关联线样式数据
|
||||
let newAssociativeLineStyle = {}
|
||||
if (associativeLineStyle) {
|
||||
Object.keys(associativeLineStyle).forEach(item => {
|
||||
if (item !== toNode.getData('uid')) {
|
||||
newAssociativeLineStyle[item] = associativeLineStyle[item]
|
||||
}
|
||||
})
|
||||
}
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
// 目标
|
||||
associativeLineTargets: associativeLineTargets.filter((_, index) => {
|
||||
@@ -558,7 +677,9 @@ class AssociativeLine {
|
||||
})
|
||||
: [],
|
||||
// 文本
|
||||
associativeLineText: newAssociativeLineText
|
||||
associativeLineText: newAssociativeLineText,
|
||||
// 样式
|
||||
associativeLineStyle: newAssociativeLineStyle
|
||||
})
|
||||
}
|
||||
|
||||
@@ -578,6 +699,7 @@ class AssociativeLine {
|
||||
this.activeLine = null
|
||||
this.removeControls()
|
||||
this.back()
|
||||
this.mindMap.emit('associative_line_deactivate')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
117
simple-mind-map/src/plugins/MindMapLayoutPro.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
// 该插件会向节点数据的data中添加dir字段
|
||||
/*
|
||||
需要更新数据的情况:
|
||||
|
||||
1.实例化时的数据
|
||||
2.调用setData和updateData方法
|
||||
3.执行完命令
|
||||
4.切换结构
|
||||
*/
|
||||
|
||||
class MindMapLayoutPro {
|
||||
constructor(opt) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
this.updateNodeTree = this.updateNodeTree.bind(this)
|
||||
this.afterExecCommand = this.afterExecCommand.bind(this)
|
||||
this.layoutChange = this.layoutChange.bind(this)
|
||||
|
||||
// 处理实例化时传入的数据
|
||||
if (this.mindMap.opt.data && this.isMindMapLayout()) {
|
||||
this.updateNodeTree(this.mindMap.opt.data)
|
||||
}
|
||||
|
||||
this.mindMap.on('layout_change', this.layoutChange)
|
||||
this.mindMap.on('afterExecCommand', this.afterExecCommand)
|
||||
this.mindMap.on('before_update_data', this.updateNodeTree)
|
||||
this.mindMap.on('before_set_data', this.updateNodeTree)
|
||||
}
|
||||
|
||||
restore() {
|
||||
this.mindMap.off('layout_change', this.layoutChange)
|
||||
this.mindMap.off('afterExecCommand', this.afterExecCommand)
|
||||
this.mindMap.off('before_update_data', this.updateNodeTree)
|
||||
this.mindMap.off('before_set_data', this.updateNodeTree)
|
||||
}
|
||||
|
||||
// 监听命令执行后的事件
|
||||
afterExecCommand(name) {
|
||||
if (!this.isMindMapLayout()) return
|
||||
if (
|
||||
![
|
||||
'BACK',
|
||||
'FORWARD',
|
||||
'INSERT_NODE',
|
||||
'INSERT_MULTI_NODE',
|
||||
'INSERT_CHILD_NODE',
|
||||
'INSERT_MULTI_CHILD_NODE',
|
||||
'INSERT_PARENT_NODE',
|
||||
'UP_NODE',
|
||||
'DOWN_NODE',
|
||||
'MOVE_UP_ONE_LEVEL',
|
||||
'INSERT_AFTER',
|
||||
'INSERT_BEFORE',
|
||||
'MOVE_NODE_TO',
|
||||
'REMOVE_NODE',
|
||||
'REMOVE_CURRENT_NODE',
|
||||
'PASTE_NODE',
|
||||
'CUT_NODE'
|
||||
].includes(name)
|
||||
)
|
||||
return
|
||||
this.updateRenderTree()
|
||||
}
|
||||
|
||||
// 更新布局结构
|
||||
layoutChange(layout) {
|
||||
if (layout === CONSTANTS.LAYOUT.MIND_MAP) {
|
||||
this.updateRenderTree()
|
||||
}
|
||||
}
|
||||
|
||||
// 更新当前的渲染树
|
||||
updateRenderTree() {
|
||||
this.updateNodeTree(this.mindMap.renderer.renderTree)
|
||||
}
|
||||
|
||||
// 更新节点树,修改二级节点的排列位置
|
||||
updateNodeTree(tree) {
|
||||
if (!this.isMindMapLayout()) return
|
||||
const root = tree
|
||||
const childrenLength = root.children.length
|
||||
if (childrenLength <= 0) return
|
||||
const center = Math.ceil(childrenLength / 2)
|
||||
root.children.forEach((item, index) => {
|
||||
if (index + 1 <= center) {
|
||||
item.data.dir = CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
} else {
|
||||
item.data.dir = CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 判断当前是否是思维导图布局结构
|
||||
isMindMapLayout() {
|
||||
return this.mindMap.opt.layout === CONSTANTS.LAYOUT.MIND_MAP
|
||||
}
|
||||
|
||||
// 插件被移除前做的事情
|
||||
beforePluginRemove() {
|
||||
this.restore()
|
||||
}
|
||||
|
||||
// 插件被卸载前做的事情
|
||||
beforePluginDestroy() {
|
||||
this.restore()
|
||||
}
|
||||
}
|
||||
|
||||
MindMapLayoutPro.instanceName = 'mindMapLayoutPro'
|
||||
|
||||
export default MindMapLayoutPro
|
||||
@@ -289,11 +289,14 @@ class NodeImgAdjust {
|
||||
// 隐藏自定义元素
|
||||
this.hideHandleEl()
|
||||
// 更新节点图片为新的大小
|
||||
const { image, imageTitle, imageSize } = this.node.getData()
|
||||
const { image, imageTitle } = this.node.getData()
|
||||
const { scaleX, scaleY } = this.mousedownDrawTransform
|
||||
const newWidth = this.currentImgWidth / scaleX
|
||||
const newHeight = this.currentImgHeight / scaleY
|
||||
if (newWidth !== imageSize.width || newHeight !== imageSize.height) {
|
||||
if (
|
||||
Math.abs(newWidth - this.rect.width) > 1 ||
|
||||
Math.abs(newHeight - this.rect.height) > 1
|
||||
) {
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, {
|
||||
url: image,
|
||||
title: imageTitle,
|
||||
|
||||
@@ -4,8 +4,6 @@ import 'quill/dist/quill.snow.css'
|
||||
import {
|
||||
walk,
|
||||
getTextFromHtml,
|
||||
isWhite,
|
||||
getVisibleColorFromTheme,
|
||||
isUndef,
|
||||
checkSmmFormatData,
|
||||
removeHtmlNodeByClass,
|
||||
@@ -59,6 +57,14 @@ class RichText {
|
||||
this.isCompositing = false
|
||||
this.textNodePaddingX = 6
|
||||
this.textNodePaddingY = 4
|
||||
this.supportStyleProps = [
|
||||
'fontFamily',
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textDecoration',
|
||||
'color'
|
||||
]
|
||||
this.initOpt()
|
||||
this.extendQuill()
|
||||
this.appendCss()
|
||||
@@ -75,9 +81,12 @@ class RichText {
|
||||
this.onCompositionStart = this.onCompositionStart.bind(this)
|
||||
this.onCompositionUpdate = this.onCompositionUpdate.bind(this)
|
||||
this.onCompositionEnd = this.onCompositionEnd.bind(this)
|
||||
this.handleSetData = this.handleSetData.bind(this)
|
||||
window.addEventListener('compositionstart', this.onCompositionStart)
|
||||
window.addEventListener('compositionupdate', this.onCompositionUpdate)
|
||||
window.addEventListener('compositionend', this.onCompositionEnd)
|
||||
this.mindMap.on('before_update_data', this.handleSetData)
|
||||
this.mindMap.on('before_set_data', this.handleSetData)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
@@ -85,6 +94,8 @@ class RichText {
|
||||
window.removeEventListener('compositionstart', this.onCompositionStart)
|
||||
window.removeEventListener('compositionupdate', this.onCompositionUpdate)
|
||||
window.removeEventListener('compositionend', this.onCompositionEnd)
|
||||
this.mindMap.off('before_update_data', this.handleSetData)
|
||||
this.mindMap.off('before_set_data', this.handleSetData)
|
||||
}
|
||||
|
||||
// 插入样式
|
||||
@@ -94,6 +105,7 @@ class RichText {
|
||||
`
|
||||
.smm-richtext-node-wrap {
|
||||
word-break: break-all;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.smm-richtext-node-wrap p {
|
||||
@@ -102,7 +114,7 @@ class RichText {
|
||||
`
|
||||
)
|
||||
let cssText = `
|
||||
.ql-editor {
|
||||
.${CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP} {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
@@ -184,12 +196,13 @@ class RichText {
|
||||
return
|
||||
}
|
||||
let {
|
||||
richTextEditFakeInPlace,
|
||||
customInnerElsAppendTo,
|
||||
nodeTextEditZIndex,
|
||||
textAutoWrapWidth,
|
||||
selectTextOnEnterEditText,
|
||||
transformRichTextOnEnterEdit
|
||||
transformRichTextOnEnterEdit,
|
||||
openRealtimeRenderOnNodeTextEdit,
|
||||
autoEmptyTextWhenKeydownEnterEdit
|
||||
} = this.mindMap.opt
|
||||
textAutoWrapWidth = node.hasCustomWidth()
|
||||
? node.customTextWidth
|
||||
@@ -206,26 +219,24 @@ class RichText {
|
||||
let originWidth = g.attr('data-width')
|
||||
let originHeight = g.attr('data-height')
|
||||
// 缩放值
|
||||
let scaleX = rect.width / originWidth
|
||||
let scaleY = rect.height / originHeight
|
||||
const scaleX = Math.ceil(rect.width) / originWidth
|
||||
const scaleY = Math.ceil(rect.height) / originHeight
|
||||
// 内边距
|
||||
let paddingX = this.textNodePaddingX
|
||||
let paddingY = this.textNodePaddingY
|
||||
if (richTextEditFakeInPlace) {
|
||||
let paddingValue = node.getPaddingVale()
|
||||
paddingX = paddingValue.paddingX
|
||||
paddingY = paddingValue.paddingY
|
||||
}
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.classList.add('smm-richtext-node-edit-wrap')
|
||||
this.textEditNode.style.cssText = `
|
||||
position:fixed;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 20px rgba(0,0,0,.5);
|
||||
outline: none;
|
||||
word-break:
|
||||
break-all;
|
||||
position:fixed;
|
||||
box-sizing: border-box;
|
||||
${
|
||||
openRealtimeRenderOnNodeTextEdit
|
||||
? ''
|
||||
: 'box-shadow: 0 0 20px rgba(0,0,0,.5);'
|
||||
}
|
||||
outline: none;
|
||||
word-break: break-all;
|
||||
padding: ${paddingY}px ${paddingX}px;
|
||||
`
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
@@ -245,7 +256,10 @@ class RichText {
|
||||
this.textEditNode.style.marginLeft = `-${paddingX * scaleX}px`
|
||||
this.textEditNode.style.marginTop = `-${paddingY * scaleY}px`
|
||||
this.textEditNode.style.zIndex = nodeTextEditZIndex
|
||||
this.textEditNode.style.background = this.getBackground(node)
|
||||
if (!openRealtimeRenderOnNodeTextEdit) {
|
||||
this.textEditNode.style.background =
|
||||
this.mindMap.renderer.textEdit.getBackground(node)
|
||||
}
|
||||
this.textEditNode.style.minWidth = originWidth + paddingX * 2 + 'px'
|
||||
this.textEditNode.style.minHeight = originHeight + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
@@ -254,13 +268,6 @@ class RichText {
|
||||
this.textEditNode.style.maxWidth = textAutoWrapWidth + paddingX * 2 + 'px'
|
||||
this.textEditNode.style.transform = `scale(${scaleX}, ${scaleY})`
|
||||
this.textEditNode.style.transformOrigin = 'left top'
|
||||
if (richTextEditFakeInPlace) {
|
||||
this.textEditNode.style.borderRadius =
|
||||
(node.style.merge('borderRadius') || 5) + 'px'
|
||||
if (node.style.merge('shape') == 'roundedRectangle') {
|
||||
this.textEditNode.style.borderRadius = (node.height || 50) + 'px'
|
||||
}
|
||||
}
|
||||
// 节点文本内容
|
||||
let nodeText = node.getData('text')
|
||||
if (typeof transformRichTextOnEnterEdit === 'function') {
|
||||
@@ -274,7 +281,10 @@ class RichText {
|
||||
if (isEmptyText) {
|
||||
this.lostStyle = true
|
||||
}
|
||||
if (noneEmptyNoneRichText) {
|
||||
if (isFromKeyDown && autoEmptyTextWhenKeydownEnterEdit) {
|
||||
this.textEditNode.innerHTML = ''
|
||||
this.lostStyle = true
|
||||
} else if (noneEmptyNoneRichText) {
|
||||
// 还不是富文本
|
||||
let text = String(nodeText).split(/\n/gim).join('<br>')
|
||||
let html = `<p>${text}</p>`
|
||||
@@ -284,7 +294,9 @@ class RichText {
|
||||
this.textEditNode.innerHTML = this.cacheEditingText || nodeText
|
||||
}
|
||||
this.initQuillEditor()
|
||||
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
|
||||
document.querySelector(
|
||||
'.' + CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP
|
||||
).style.minHeight = originHeight + 'px'
|
||||
this.showTextEdit = true
|
||||
// 如果是刚创建的节点,那么默认全选,否则普通激活不全选,除非selectTextOnEnterEditText配置为true
|
||||
// 在selectTextOnEnterEditText时,如果是在keydown事件进入的节点编辑,也不需要全选
|
||||
@@ -298,11 +310,26 @@ class RichText {
|
||||
this.cacheEditingText = ''
|
||||
}
|
||||
|
||||
// 当openRealtimeRenderOnNodeTextEdit配置更新后需要更新编辑框样式
|
||||
onOpenRealtimeRenderOnNodeTextEditConfigUpdate(
|
||||
openRealtimeRenderOnNodeTextEdit
|
||||
) {
|
||||
if (!this.textEditNode) return
|
||||
this.textEditNode.style.background = openRealtimeRenderOnNodeTextEdit
|
||||
? 'transparent'
|
||||
: this.node
|
||||
? this.mindMap.renderer.textEdit.getBackground(this.node)
|
||||
: ''
|
||||
this.textEditNode.style.boxShadow = openRealtimeRenderOnNodeTextEdit
|
||||
? 'none'
|
||||
: '0 0 20px rgba(0,0,0,.5)'
|
||||
}
|
||||
|
||||
// 更新文本编辑框的大小和位置
|
||||
updateTextEditNode() {
|
||||
if (!this.node) return
|
||||
const rect = this.node._textData.node.node.getBoundingClientRect()
|
||||
const g = this.node._textData.node
|
||||
const rect = g.node.getBoundingClientRect()
|
||||
const originWidth = g.attr('data-width')
|
||||
const originHeight = g.attr('data-height')
|
||||
this.textEditNode.style.minWidth =
|
||||
@@ -319,27 +346,6 @@ class RichText {
|
||||
targetNode.removeChild(this.textEditNode)
|
||||
}
|
||||
|
||||
// 获取编辑区域的背景填充
|
||||
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 = {
|
||||
@@ -356,13 +362,16 @@ class RichText {
|
||||
|
||||
// 获取当前正在编辑的内容
|
||||
getEditText() {
|
||||
let html = this.quill.container.firstChild.innerHTML
|
||||
// https://github.com/slab/quill/issues/4509
|
||||
return this.quill.container.firstChild.innerHTML.replaceAll(/ +/g, match =>
|
||||
' '.repeat(match.length)
|
||||
)
|
||||
// 去除ql-cursor节点
|
||||
// https://github.com/wanglin2/mind-map/commit/138cc4b3e824671143f0bf70e5c46796f48520d0
|
||||
// https://github.com/wanglin2/mind-map/commit/0760500cebe8ec4e8ad84ab63f877b8b2a193aa1
|
||||
// html = removeHtmlNodeByClass(html, '.ql-cursor')
|
||||
// 去除最后的空行
|
||||
return html.replace(/<p><br><\/p>$/, '')
|
||||
// return html.replace(/<p><br><\/p>$/, '')
|
||||
}
|
||||
|
||||
// 给html字符串中的节点样式按样式名首字母排序
|
||||
@@ -389,6 +398,12 @@ class RichText {
|
||||
let html = this.getEditText()
|
||||
html = this.sortHtmlNodeStyles(html)
|
||||
const list = nodes && nodes.length > 0 ? nodes : [this.node]
|
||||
const node = this.node
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.showTextEdit = false
|
||||
this.mindMap.emit('rich_text_selection_change', false)
|
||||
this.node = null
|
||||
this.isInserting = false
|
||||
list.forEach(node => {
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', node, html, true)
|
||||
// if (node.isGeneralization) {
|
||||
@@ -397,12 +412,6 @@ class RichText {
|
||||
// }
|
||||
this.mindMap.render()
|
||||
})
|
||||
const node = this.node
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.showTextEdit = false
|
||||
this.mindMap.emit('rich_text_selection_change', false)
|
||||
this.node = null
|
||||
this.isInserting = false
|
||||
this.mindMap.emit('hide_text_edit', this.textEditNode, list, node)
|
||||
}
|
||||
|
||||
@@ -464,6 +473,17 @@ class RichText {
|
||||
}
|
||||
}
|
||||
},
|
||||
formats: [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strike',
|
||||
'color',
|
||||
'background',
|
||||
'font',
|
||||
'size',
|
||||
'formula'
|
||||
], // 明确指定允许的格式,不包含有序列表,无序列表等
|
||||
theme: 'snow'
|
||||
})
|
||||
// 拦截复制事件,即Ctrl + c,去除多余的空行
|
||||
@@ -656,14 +676,7 @@ class RichText {
|
||||
// 再将样式恢复为当前主题改节点的默认样式
|
||||
const style = {}
|
||||
if (this.node) {
|
||||
;[
|
||||
'fontFamily',
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textDecoration',
|
||||
'color'
|
||||
].forEach(key => {
|
||||
this.supportStyleProps.forEach(key => {
|
||||
style[key] = this.node.style.merge(key)
|
||||
})
|
||||
}
|
||||
@@ -694,14 +707,7 @@ class RichText {
|
||||
if (!this.node) return
|
||||
if (clear) {
|
||||
// 清除文本样式
|
||||
;[
|
||||
'fontFamily',
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textDecoration',
|
||||
'color'
|
||||
].forEach(prop => {
|
||||
this.supportStyleProps.forEach(prop => {
|
||||
delete this.node.nodeData.data[prop]
|
||||
})
|
||||
} else {
|
||||
@@ -776,6 +782,18 @@ class RichText {
|
||||
return data
|
||||
}
|
||||
|
||||
// 判断一个对象是否包含了富文本支持的样式字段
|
||||
isHasRichTextStyle(obj) {
|
||||
const keys = Object.keys(obj)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
if (this.supportStyleProps.includes(key)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 给未激活的节点设置富文本样式
|
||||
setNotActiveNodeStyle(node, style) {
|
||||
const config = this.normalStyleToRichTextStyle(style)
|
||||
@@ -788,17 +806,9 @@ class RichText {
|
||||
|
||||
// 检查指定节点是否存在自定义的富文本样式
|
||||
checkNodeHasCustomRichTextStyle(node) {
|
||||
const list = [
|
||||
'fontFamily',
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'textDecoration',
|
||||
'color'
|
||||
]
|
||||
const nodeData = node instanceof MindMapNode ? node.getData() : node
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (nodeData[list[i]] !== undefined) {
|
||||
for (let i = 0; i < this.supportStyleProps.length; i++) {
|
||||
if (nodeData[this.supportStyleProps[i]] !== undefined) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +257,7 @@ class Search {
|
||||
replaceText = String(replaceText)
|
||||
// 如果当前搜索文本是替换文本的子串,那么该节点还是符合搜索结果的
|
||||
const keep = replaceText.includes(this.searchText)
|
||||
this.notResetSearchText = true
|
||||
const hasRichTextPlugin = this.mindMap.renderer.hasRichTextPlugin()
|
||||
this.matchNodeList.forEach(node => {
|
||||
const text = this.getReplacedText(node, this.searchText, replaceText)
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
} from './associativeLineUtils'
|
||||
|
||||
// 创建控制点、连线节点
|
||||
function createControlNodes() {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
function createControlNodes(node, toNode) {
|
||||
let { associativeLineActiveColor } = this.getStyleConfig(node, toNode)
|
||||
// 连线
|
||||
this.controlLine1 = this.associativeLineDraw
|
||||
.line()
|
||||
@@ -16,13 +16,13 @@ function createControlNodes() {
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
// 控制点
|
||||
this.controlPoint1 = this.createOneControlNode('controlPoint1')
|
||||
this.controlPoint2 = this.createOneControlNode('controlPoint2')
|
||||
this.controlPoint1 = this.createOneControlNode('controlPoint1', node, toNode)
|
||||
this.controlPoint2 = this.createOneControlNode('controlPoint2', node, toNode)
|
||||
}
|
||||
|
||||
// 创建控制点
|
||||
function createOneControlNode(pointKey) {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
function createOneControlNode(pointKey, node, toNode) {
|
||||
let { associativeLineActiveColor } = this.getStyleConfig(node, toNode)
|
||||
return this.associativeLineDraw
|
||||
.circle(this.controlPointDiameter)
|
||||
.stroke({ color: associativeLineActiveColor })
|
||||
@@ -202,6 +202,7 @@ function onControlPointMouseup(e) {
|
||||
associativeLineTargetControlOffsets: offsetList,
|
||||
associativeLinePoint
|
||||
})
|
||||
this.isNotRenderAllLines = true
|
||||
// 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚,所以重置isControlPointMousedown需要等draw_click事件触发完以后
|
||||
setTimeout(() => {
|
||||
this.resetControlPoint()
|
||||
@@ -221,10 +222,10 @@ function resetControlPoint() {
|
||||
}
|
||||
|
||||
// 渲染控制点
|
||||
function renderControls(startPoint, endPoint, point1, point2) {
|
||||
function renderControls(startPoint, endPoint, point1, point2, node, toNode) {
|
||||
if (!this.mindMap.opt.enableAdjustAssociativeLinePoints) return
|
||||
if (!this.controlLine1) {
|
||||
this.createControlNodes()
|
||||
this.createControlNodes(node, toNode)
|
||||
}
|
||||
let radius = this.controlPointDiameter / 2
|
||||
// 控制点和起终点的连线
|
||||
|
||||
@@ -43,6 +43,7 @@ function showEditTextBox(g) {
|
||||
// 输入框元素没有创建过,则先创建
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.className = 'associative-line-text-edit-warp'
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
|
||||
this.textEditNode.setAttribute('contenteditable', true)
|
||||
this.textEditNode.addEventListener('keyup', e => {
|
||||
@@ -54,14 +55,14 @@ function showEditTextBox(g) {
|
||||
const targetNode = this.mindMap.opt.customInnerElsAppendTo || document.body
|
||||
targetNode.appendChild(this.textEditNode)
|
||||
}
|
||||
let [, , , node, toNode] = this.activeLine
|
||||
let {
|
||||
associativeLineTextFontSize,
|
||||
associativeLineTextFontFamily,
|
||||
associativeLineTextLineHeight
|
||||
} = this.mindMap.themeConfig
|
||||
} = this.getStyleConfig(node, toNode)
|
||||
let { defaultAssociativeLineText, nodeTextEditZIndex } = this.mindMap.opt
|
||||
let scale = this.mindMap.view.scale
|
||||
let [, , , node, toNode] = this.activeLine
|
||||
let text = this.getText(node, toNode)
|
||||
let textLines = (text || defaultAssociativeLineText).split(/\n/gim)
|
||||
this.textEditNode.style.fontFamily = associativeLineTextFontFamily
|
||||
@@ -124,7 +125,7 @@ function hideEditTextBox() {
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.textEditNode.innerHTML = ''
|
||||
this.showTextEdit = false
|
||||
this.renderText(str, path, text)
|
||||
this.renderText(str, path, text, node, toNode)
|
||||
this.mindMap.emit('hide_text_edit')
|
||||
}
|
||||
|
||||
@@ -138,35 +139,41 @@ function getText(node, toNode) {
|
||||
}
|
||||
|
||||
// 渲染关联线文字
|
||||
function renderText(str, path, text) {
|
||||
function renderText(str, path, text, node, toNode) {
|
||||
if (!str) return
|
||||
let { associativeLineTextFontSize, associativeLineTextLineHeight } =
|
||||
this.mindMap.themeConfig
|
||||
this.getStyleConfig(node, toNode)
|
||||
text.clear()
|
||||
let textArr = str.split(/\n/gim)
|
||||
let textArr = str.replace(/\n$/g, '').split(/\n/gim)
|
||||
textArr.forEach((item, index) => {
|
||||
let node = new Text().text(item)
|
||||
node.y(associativeLineTextFontSize * associativeLineTextLineHeight * index)
|
||||
this.styleText(node)
|
||||
text.add(node)
|
||||
// 避免尾部的空行不占宽度,导致文本编辑框定位异常的问题
|
||||
if (item === '') {
|
||||
item = ''
|
||||
}
|
||||
let textNode = new Text().text(item)
|
||||
textNode.y(
|
||||
associativeLineTextFontSize * associativeLineTextLineHeight * index
|
||||
)
|
||||
this.styleText(textNode, node, toNode)
|
||||
text.add(textNode)
|
||||
})
|
||||
updateTextPos(path, text)
|
||||
}
|
||||
|
||||
// 给文本设置样式
|
||||
function styleText(node) {
|
||||
function styleText(textNode, node, toNode) {
|
||||
let {
|
||||
associativeLineTextColor,
|
||||
associativeLineTextFontSize,
|
||||
associativeLineTextFontFamily
|
||||
} = this.mindMap.themeConfig
|
||||
node
|
||||
} = this.getStyleConfig(node, toNode)
|
||||
textNode
|
||||
.fill({
|
||||
color: associativeLineTextColor
|
||||
})
|
||||
.css({
|
||||
'font-family': associativeLineTextFontFamily,
|
||||
'font-size': associativeLineTextFontSize
|
||||
'font-size': associativeLineTextFontSize + 'px'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ export default {
|
||||
lineColor: '#549688',
|
||||
// 连线样式
|
||||
lineDasharray: 'none',
|
||||
// 连线是否开启流动效果,仅在虚线时有效(需要注册LineFlow插件)
|
||||
lineFlow: false,
|
||||
// 流动效果一个周期的时间,单位:s
|
||||
lineFlowDuration: 1,
|
||||
// 流动方向是否是从父节点到子节点
|
||||
lineFlowForward: true,
|
||||
// 连线风格
|
||||
lineStyle: 'straight', // 曲线(curve)【仅支持logicalStructure、mindMap、verticalTimeline三种结构】、直线(straight)、直连(direct)【仅支持logicalStructure、mindMap、organizationStructure、verticalTimeline四种结构】
|
||||
// 曲线连接时,根节点和其他节点的连接线样式保持统一,默认根节点为 ( 型,其他节点为 { 型,设为true后,都为 { 型。仅支持logicalStructure、mindMap两种结构
|
||||
@@ -72,7 +78,6 @@ export default {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
borderColor: 'transparent',
|
||||
borderWidth: 0,
|
||||
borderDasharray: 'none',
|
||||
@@ -89,8 +94,16 @@ export default {
|
||||
hoverRectColor: '',
|
||||
// 点鼠标hover和激活时显示的矩形边框的圆角大小
|
||||
hoverRectRadius: 5
|
||||
// paddingX: 15,
|
||||
// paddingY: 5
|
||||
// 下列样式也支持给节点设置,用于覆盖最外层的设置
|
||||
// paddingX,
|
||||
// paddingY,
|
||||
// lineWidth,
|
||||
// lineColor,
|
||||
// lineDasharray,
|
||||
// lineFlow,
|
||||
// lineFlowDuration,
|
||||
// lineFlowForward
|
||||
// 关联线的所有样式
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
@@ -103,7 +116,6 @@ export default {
|
||||
fontSize: 16,
|
||||
fontWeight: 'normal',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
borderColor: '#549688',
|
||||
borderWidth: 1,
|
||||
borderDasharray: 'none',
|
||||
@@ -117,8 +129,6 @@ export default {
|
||||
lineMarkerDir: 'end',
|
||||
hoverRectColor: '',
|
||||
hoverRectRadius: 5
|
||||
// paddingX: 15,
|
||||
// paddingY: 5
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
@@ -131,7 +141,6 @@ export default {
|
||||
fontSize: 14,
|
||||
fontWeight: 'normal',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
borderColor: 'transparent',
|
||||
borderWidth: 0,
|
||||
borderRadius: 5,
|
||||
@@ -145,8 +154,6 @@ export default {
|
||||
lineMarkerDir: 'end',
|
||||
hoverRectColor: '',
|
||||
hoverRectRadius: 5
|
||||
// paddingX: 15,
|
||||
// paddingY: 5
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
@@ -159,7 +166,6 @@ export default {
|
||||
fontSize: 16,
|
||||
fontWeight: 'normal',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 1.5,
|
||||
borderColor: '#549688',
|
||||
borderWidth: 1,
|
||||
borderDasharray: 'none',
|
||||
@@ -172,8 +178,6 @@ export default {
|
||||
endDir: [1, 0],
|
||||
hoverRectColor: '',
|
||||
hoverRectRadius: 5
|
||||
// paddingX: 15,
|
||||
// paddingY: 5
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,14 +205,12 @@ const nodeSizeIndependenceList = [
|
||||
'rootLineKeepSameInCurve',
|
||||
'rootLineStartPositionKeepSameInCurve',
|
||||
'showLineMarker',
|
||||
'gradientStyle',
|
||||
'lineRadius',
|
||||
'startColor',
|
||||
'endColor',
|
||||
'startDir',
|
||||
'endDir',
|
||||
'hoverRectColor',
|
||||
'hoverRectRadius'
|
||||
'hoverRectRadius',
|
||||
'lineFlow',
|
||||
'lineFlowDuration',
|
||||
'lineFlowForward'
|
||||
]
|
||||
export const checkIsNodeSizeIndependenceConfig = config => {
|
||||
let keys = Object.keys(config)
|
||||
@@ -224,9 +226,13 @@ export const checkIsNodeSizeIndependenceConfig = config => {
|
||||
return true
|
||||
}
|
||||
|
||||
// 连线的样式
|
||||
export const lineStyleProps = [
|
||||
'lineColor',
|
||||
'lineDasharray',
|
||||
'lineWidth',
|
||||
'lineMarkerDir'
|
||||
'lineMarkerDir',
|
||||
'lineFlow',
|
||||
'lineFlowDuration',
|
||||
'lineFlowForward'
|
||||
]
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import MersenneTwister from './mersenneTwister'
|
||||
import { ForeignObject } from '@svgdotjs/svg.js'
|
||||
import merge from 'deepmerge'
|
||||
import { lineStyleProps } from '../theme/default'
|
||||
|
||||
// 深度优先遍历树
|
||||
export const walk = (
|
||||
@@ -507,13 +508,14 @@ export const addHtmlStyle = (html, tag, style) => {
|
||||
if (!addHtmlStyleEl) {
|
||||
addHtmlStyleEl = document.createElement('div')
|
||||
}
|
||||
const tags = Array.isArray(tag) ? tag : [tag]
|
||||
addHtmlStyleEl.innerHTML = html
|
||||
let walk = root => {
|
||||
let childNodes = root.childNodes
|
||||
childNodes.forEach(node => {
|
||||
if (node.nodeType === 1) {
|
||||
// 元素节点
|
||||
if (node.tagName.toLowerCase() === tag) {
|
||||
if (tags.includes(node.tagName.toLowerCase())) {
|
||||
node.style.cssText = style
|
||||
} else {
|
||||
walk(node)
|
||||
@@ -790,6 +792,18 @@ export const checkIsNodeStyleDataKey = key => {
|
||||
return false
|
||||
}
|
||||
|
||||
// 判断一个对象是否不需要触发节点重新创建
|
||||
export const isNodeNotNeedRenderData = config => {
|
||||
const list = [...lineStyleProps] // 节点连线样式
|
||||
const keys = Object.keys(config)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (!list.includes(keys[i])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 合并图标数组
|
||||
// const data = [
|
||||
// { type: 'priority', name: '优先级图标', list: [{ name: '1', icon: 'a' }, { name: 2, icon: 'b' }] },
|
||||
@@ -1069,9 +1083,14 @@ export const isSameObject = (a, b) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查navigator.clipboard对象的读取是否可用
|
||||
export const checkClipboardReadEnable = () => {
|
||||
return navigator.clipboard && typeof navigator.clipboard.read === 'function'
|
||||
}
|
||||
|
||||
// 将数据设置到用户剪切板中
|
||||
export const setDataToClipboard = data => {
|
||||
if (navigator.clipboard) {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(JSON.stringify(data))
|
||||
}
|
||||
}
|
||||
@@ -1080,7 +1099,7 @@ export const setDataToClipboard = data => {
|
||||
export const getDataFromClipboard = async () => {
|
||||
let text = null
|
||||
let img = null
|
||||
if (navigator.clipboard) {
|
||||
if (checkClipboardReadEnable()) {
|
||||
const items = await navigator.clipboard.read()
|
||||
if (items && items.length > 0) {
|
||||
for (const clipboardItem of items) {
|
||||
|
||||
BIN
web/src/assets/avatar/4399行星元帅.jpg
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
web/src/assets/avatar/Xavier.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
web/src/assets/avatar/h.r.w.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
web/src/assets/avatar/xbkkjbs0246658.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
web/src/assets/avatar/一亩三.jpg
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
web/src/assets/avatar/冒号括号.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
web/src/assets/avatar/广兴.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
web/src/assets/avatar/时光匆匆.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
web/src/assets/avatar/皮老板.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
web/src/assets/avatar/黄智彪@一米一栗科技.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
@@ -439,6 +439,11 @@ export const sidebarTriggerList = [
|
||||
value: 'outline',
|
||||
icon: 'iconfuhao-dagangshu'
|
||||
},
|
||||
{
|
||||
name: 'Setting',
|
||||
value: 'setting',
|
||||
icon: 'iconshezhi'
|
||||
},
|
||||
{
|
||||
name: 'ShortcutKey',
|
||||
value: 'shortcutKey',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
fontSizeList,
|
||||
lineHeightList,
|
||||
colorList,
|
||||
borderWidthList,
|
||||
borderRadiusList,
|
||||
@@ -155,7 +154,6 @@ const linearGradientDirList = {
|
||||
|
||||
export {
|
||||
fontSizeList,
|
||||
lineHeightList,
|
||||
borderWidthList,
|
||||
borderRadiusList,
|
||||
lineWidthList,
|
||||
|
||||
@@ -57,9 +57,6 @@ export const fontFamilyList = [
|
||||
// 字号
|
||||
export const fontSizeList = [10, 12, 16, 18, 24, 32, 48]
|
||||
|
||||
// 行高
|
||||
export const lineHeightList = [1, 1.5, 2, 2.5, 3]
|
||||
|
||||
// 颜色
|
||||
export const colorList = [
|
||||
'#4D4D4D',
|
||||
@@ -537,6 +534,11 @@ export const sidebarTriggerList = [
|
||||
value: 'outline',
|
||||
icon: 'iconfuhao-dagangshu'
|
||||
},
|
||||
{
|
||||
name: '设置',
|
||||
value: 'setting',
|
||||
icon: 'iconshezhi'
|
||||
},
|
||||
{
|
||||
name: '快捷键',
|
||||
value: 'shortcutKey',
|
||||
|
||||
@@ -439,6 +439,11 @@ export const sidebarTriggerList = [
|
||||
value: 'outline',
|
||||
icon: 'iconfuhao-dagangshu'
|
||||
},
|
||||
{
|
||||
name: '設置',
|
||||
value: 'setting',
|
||||
icon: 'iconshezhi'
|
||||
},
|
||||
{
|
||||
name: '快捷鍵',
|
||||
value: 'shortcutKey',
|
||||
|
||||
@@ -26,8 +26,41 @@ export default {
|
||||
nodeBorderType: 'Node border style',
|
||||
nodeUseLineStyle: 'Use only has bottom border style',
|
||||
otherConfig: 'Other config',
|
||||
enableFreeDrag: 'Enable node free drag(Beta)',
|
||||
associativeLine: 'Associative line',
|
||||
associativeLineWidth: 'Width',
|
||||
associativeLineColor: 'Color',
|
||||
associativeLineActiveWidth: 'Active width',
|
||||
associativeLineActiveColor: 'Active color',
|
||||
rootStyle: 'Root Node',
|
||||
associativeLineText: 'Associative line text',
|
||||
fontFamily: 'Font family',
|
||||
fontSize: 'Font size',
|
||||
rootLineStartPos: 'Root line start pos',
|
||||
center: 'Center',
|
||||
edge: 'Edge',
|
||||
rainbowLines: 'Rainbow lines',
|
||||
notUseRainbowLines: 'Not use rainbow lines',
|
||||
outerFramePadding: 'Outer frame padding'
|
||||
},
|
||||
setting: {
|
||||
title: 'Setting',
|
||||
openPerformance: 'Enable performance mode',
|
||||
enableFreeDrag: 'Enable node free drag(Beta)',
|
||||
isEnableNodeRichText: 'Enable node rich text editing',
|
||||
mousewheelAction: 'Mouse wheel behavior',
|
||||
zoomView: 'Zoom view',
|
||||
moveViewUpDown: 'Move view up and down',
|
||||
mousewheelZoomActionReverse: 'Mouse Wheel Zoom',
|
||||
mousewheelZoomActionReverse1: 'Zoom out forward and zoom in back',
|
||||
mousewheelZoomActionReverse2: 'Zoom in forward and zoom out back',
|
||||
createNewNodeBehavior: 'Behavior of creating new node',
|
||||
default: 'Active new node and editing',
|
||||
notActive: 'Not active new node',
|
||||
activeOnly: 'Only active new node but not editing',
|
||||
openRealtimeRenderOnNodeTextEdit:
|
||||
'Enable real-time rendering effect for text editing',
|
||||
isShowScrollbar: 'Is show scrollbar',
|
||||
isUseHandDrawnLikeStyle: 'Is use hand drawn like style',
|
||||
watermark: 'Watermark',
|
||||
showWatermark: 'Is show watermark',
|
||||
onlyExport: 'Only export',
|
||||
@@ -40,34 +73,11 @@ export default {
|
||||
watermarkTextOpacity: 'Text opacity',
|
||||
watermarkTextFontSize: 'Font size',
|
||||
belowNode: 'Display below nodes',
|
||||
isEnableNodeRichText: 'Enable node rich text editing',
|
||||
mousewheelAction: 'Mouse wheel behavior',
|
||||
zoomView: 'Zoom view',
|
||||
moveViewUpDown: 'Move view up and down',
|
||||
associativeLine: 'Associative line',
|
||||
associativeLineWidth: 'Width',
|
||||
associativeLineColor: 'Color',
|
||||
associativeLineActiveWidth: 'Active width',
|
||||
associativeLineActiveColor: 'Active color',
|
||||
mousewheelZoomActionReverse: 'Mouse Wheel Zoom',
|
||||
mousewheelZoomActionReverse1: 'Zoom out forward and zoom in back',
|
||||
mousewheelZoomActionReverse2: 'Zoom in forward and zoom out back',
|
||||
createNewNodeBehavior: 'Behavior of creating new node',
|
||||
default: 'Active new node and editing',
|
||||
notActive: 'Not active new node',
|
||||
activeOnly: 'Only active new node but not editing',
|
||||
rootStyle: 'Root Node',
|
||||
associativeLineText: 'Associative line text',
|
||||
fontFamily: 'Font family',
|
||||
fontSize: 'Font size',
|
||||
isShowScrollbar: 'Is show scrollbar',
|
||||
isUseHandDrawnLikeStyle: 'Is use hand drawn like style',
|
||||
rootLineStartPos: 'Root line start pos',
|
||||
center: 'Center',
|
||||
edge: 'Edge',
|
||||
rainbowLines: 'Rainbow lines',
|
||||
notUseRainbowLines: 'Not use rainbow lines',
|
||||
outerFramePadding: 'Outer frame padding'
|
||||
tagPosition: 'Node tag position',
|
||||
tagPositionRight: 'Text right',
|
||||
tagPositionBottom: 'Text bottom',
|
||||
alwaysShowExpandBtn: 'Always show expand btn',
|
||||
enableAutoEnterTextEditWhenKeydown: 'Auto enter text edit when keydown'
|
||||
},
|
||||
color: {
|
||||
moreColor: 'More color'
|
||||
@@ -112,7 +122,10 @@ export default {
|
||||
copySuccess: 'Copy success',
|
||||
copyFail: 'Copy fail',
|
||||
number: 'Number child nodes',
|
||||
expandNodeChild: 'Expand all sub nodes'
|
||||
expandNodeChild: 'Expand all sub nodes',
|
||||
unExpandNodeChild: 'Un expand all sub nodes',
|
||||
addToDo: 'Add toDo',
|
||||
removeToDo: 'Remove toDo'
|
||||
},
|
||||
count: {
|
||||
words: 'Words',
|
||||
@@ -216,7 +229,6 @@ export default {
|
||||
text: 'Text',
|
||||
fontFamily: 'Font family',
|
||||
fontSize: 'Font size',
|
||||
lineHeight: 'Line height',
|
||||
color: 'color',
|
||||
addFontWeight: 'add font weight',
|
||||
italic: 'Italic',
|
||||
@@ -241,7 +253,12 @@ export default {
|
||||
arrowDir: 'Arrow dir',
|
||||
arrowDirStart: 'Start',
|
||||
arrowDirEnd: 'End',
|
||||
direction: 'Direction'
|
||||
direction: 'Direction',
|
||||
selectNodeTip: 'Please select a node',
|
||||
openLineFlow: 'Open line flow',
|
||||
lineFlowDuration: 'Line flow duration',
|
||||
forward: 'Forward',
|
||||
reverse: 'Reverse'
|
||||
},
|
||||
theme: {
|
||||
title: 'Theme',
|
||||
|
||||
@@ -25,9 +25,40 @@ export default {
|
||||
belowLevel2Node: '三级及以下节点',
|
||||
nodeBorderType: '节点边框风格',
|
||||
nodeUseLineStyle: '是否使用只有底边框的风格',
|
||||
otherConfig: '其他配置',
|
||||
enableFreeDrag: '是否开启节点自由拖拽',
|
||||
associativeLine: '关联线',
|
||||
associativeLineWidth: '粗细',
|
||||
associativeLineColor: '颜色',
|
||||
associativeLineActiveWidth: '激活粗细',
|
||||
associativeLineActiveColor: '激活颜色',
|
||||
rootStyle: '根节点',
|
||||
associativeLineText: '关联线文字',
|
||||
fontFamily: '字体',
|
||||
fontSize: '字号',
|
||||
rootLineStartPos: '根节点连线起始位置',
|
||||
center: '中心',
|
||||
edge: '边缘',
|
||||
rainbowLines: '彩虹线条',
|
||||
notUseRainbowLines: '不使用彩虹线条',
|
||||
outerFramePadding: '外框内边距'
|
||||
},
|
||||
setting: {
|
||||
title: '设置',
|
||||
openPerformance: '开启性能模式(Beta)',
|
||||
enableFreeDrag: '是否开启节点自由拖拽',
|
||||
isEnableNodeRichText: '是否开启节点富文本编辑',
|
||||
mousewheelAction: '鼠标滚轮行为',
|
||||
zoomView: '缩放视图',
|
||||
moveViewUpDown: '上下移动视图',
|
||||
mousewheelZoomActionReverse: '鼠标滚轮缩放',
|
||||
mousewheelZoomActionReverse1: '向前缩小向后放大',
|
||||
mousewheelZoomActionReverse2: '向前放大向后缩小',
|
||||
createNewNodeBehavior: '创建新节点的行为',
|
||||
default: '激活新节点及进入编辑',
|
||||
notActive: '不激活新节点',
|
||||
activeOnly: '只激活新节点,不进入编辑',
|
||||
openRealtimeRenderOnNodeTextEdit: '开启文本编辑实时渲染效果',
|
||||
isShowScrollbar: '是否显示滚动条',
|
||||
isUseHandDrawnLikeStyle: '是否开启手绘风格',
|
||||
watermark: '水印',
|
||||
showWatermark: '是否显示水印',
|
||||
watermarkDefaultText: '水印文字',
|
||||
@@ -40,34 +71,11 @@ export default {
|
||||
watermarkTextOpacity: '文字透明度',
|
||||
watermarkTextFontSize: '文字字号',
|
||||
belowNode: '显示在节点下方',
|
||||
isEnableNodeRichText: '是否开启节点富文本编辑',
|
||||
mousewheelAction: '鼠标滚轮行为',
|
||||
zoomView: '缩放视图',
|
||||
moveViewUpDown: '上下移动视图',
|
||||
associativeLine: '关联线',
|
||||
associativeLineWidth: '粗细',
|
||||
associativeLineColor: '颜色',
|
||||
associativeLineActiveWidth: '激活粗细',
|
||||
associativeLineActiveColor: '激活颜色',
|
||||
mousewheelZoomActionReverse: '鼠标滚轮缩放',
|
||||
mousewheelZoomActionReverse1: '向前缩小向后放大',
|
||||
mousewheelZoomActionReverse2: '向前放大向后缩小',
|
||||
createNewNodeBehavior: '创建新节点的行为',
|
||||
default: '激活新节点及进入编辑',
|
||||
notActive: '不激活新节点',
|
||||
activeOnly: '只激活新节点,不进入编辑',
|
||||
rootStyle: '根节点',
|
||||
associativeLineText: '关联线文字',
|
||||
fontFamily: '字体',
|
||||
fontSize: '字号',
|
||||
isShowScrollbar: '是否显示滚动条',
|
||||
isUseHandDrawnLikeStyle: '是否开启手绘风格',
|
||||
rootLineStartPos: '根节点连线起始位置',
|
||||
center: '中心',
|
||||
edge: '边缘',
|
||||
rainbowLines: '彩虹线条',
|
||||
notUseRainbowLines: '不使用彩虹线条',
|
||||
outerFramePadding: '外框内边距'
|
||||
tagPosition: '节点标签显示的位置',
|
||||
tagPositionRight: '文本右侧',
|
||||
tagPositionBottom: '文本下面',
|
||||
alwaysShowExpandBtn: '是否一直显示展开收起按钮',
|
||||
enableAutoEnterTextEditWhenKeydown: '键盘输入时自动进入文本编辑'
|
||||
},
|
||||
color: {
|
||||
moreColor: '更多颜色'
|
||||
@@ -112,7 +120,10 @@ export default {
|
||||
copySuccess: '复制成功',
|
||||
copyFail: '复制失败',
|
||||
number: '编号其子节点',
|
||||
expandNodeChild: '展开所有下级节点'
|
||||
expandNodeChild: '展开所有下级节点',
|
||||
unExpandNodeChild: '收起所有下级节点',
|
||||
addToDo: '添加待办',
|
||||
removeToDo: '删除待办'
|
||||
},
|
||||
count: {
|
||||
words: '字数',
|
||||
@@ -214,7 +225,6 @@ export default {
|
||||
text: '文字',
|
||||
fontFamily: '字体',
|
||||
fontSize: '字号',
|
||||
lineHeight: '行高',
|
||||
color: '颜色',
|
||||
addFontWeight: '加粗',
|
||||
italic: '斜体',
|
||||
@@ -239,7 +249,12 @@ export default {
|
||||
arrowDir: '箭头位置',
|
||||
arrowDirStart: '头部',
|
||||
arrowDirEnd: '尾部',
|
||||
direction: '方向'
|
||||
direction: '方向',
|
||||
selectNodeTip: '请选择一个节点',
|
||||
openLineFlow: '开启流动效果',
|
||||
lineFlowDuration: '一个流动周期的时间',
|
||||
forward: '正向',
|
||||
reverse: '反向'
|
||||
},
|
||||
theme: {
|
||||
title: '主题',
|
||||
|
||||
@@ -26,8 +26,45 @@ export default {
|
||||
nodeBorderType: '節點邊框樣式',
|
||||
nodeUseLineStyle: '僅使用底邊框樣式',
|
||||
otherConfig: '其他設定',
|
||||
enableFreeDrag: '啟用節點自由拖曳 (Beta)',
|
||||
associativeLine: '關聯線',
|
||||
associativeLineWidth: '寬度',
|
||||
associativeLineColor: '顏色',
|
||||
associativeLineActiveWidth: '啟用時寬度',
|
||||
associativeLineActiveColor: '啟用時顏色',
|
||||
rootStyle: '根節點',
|
||||
associativeLineText: '關聯線文字',
|
||||
fontFamily: '字型',
|
||||
fontSize: '字型大小',
|
||||
rootLineStartPos: '根節點連線起始位置',
|
||||
center: '中心',
|
||||
edge: '邊緣',
|
||||
rainbowLines: '彩虹線條',
|
||||
notUseRainbowLines: '不使用彩虹線條',
|
||||
outerFramePadding: '外框內距',
|
||||
tagPosition: '節點標簽顯示的位置',
|
||||
tagPositionRight: '文本右側',
|
||||
tagPositionBottom: '文本下面',
|
||||
alwaysShowExpandBtn: '是否壹直顯示展開收起按鈕',
|
||||
enableAutoEnterTextEditWhenKeydown: '鍵盤輸入時自動進入文本編輯'
|
||||
},
|
||||
setting: {
|
||||
title: '設置',
|
||||
openPerformance: '啟用效能模式',
|
||||
enableFreeDrag: '啟用節點自由拖曳 (Beta)',
|
||||
isEnableNodeRichText: '啟用節點豐富文字編輯',
|
||||
mousewheelAction: '滑鼠滾輪行為',
|
||||
zoomView: '縮放檢視',
|
||||
moveViewUpDown: '上下移動檢視',
|
||||
mousewheelZoomActionReverse: '滑鼠滾輪縮放',
|
||||
mousewheelZoomActionReverse1: '向前縮小,向後放大',
|
||||
mousewheelZoomActionReverse2: '向前放大,向後縮小',
|
||||
createNewNodeBehavior: '建立新節點行為',
|
||||
default: '啟用新節點並進入編輯',
|
||||
notActive: '不啟用新節點',
|
||||
activeOnly: '僅啟用新節點,不進入編輯',
|
||||
openRealtimeRenderOnNodeTextEdit: '開啟文本編輯實時渲染效果',
|
||||
isShowScrollbar: '顯示捲軸',
|
||||
isUseHandDrawnLikeStyle: '使用手繪風格',
|
||||
watermark: '浮水印',
|
||||
showWatermark: '顯示浮水印',
|
||||
onlyExport: '僅在匯出時顯示',
|
||||
@@ -39,35 +76,7 @@ export default {
|
||||
watermarkAngle: '旋轉角度',
|
||||
watermarkTextOpacity: '文字透明度',
|
||||
watermarkTextFontSize: '字型大小',
|
||||
belowNode: '顯示在節點下方',
|
||||
isEnableNodeRichText: '啟用節點豐富文字編輯',
|
||||
mousewheelAction: '滑鼠滾輪行為',
|
||||
zoomView: '縮放檢視',
|
||||
moveViewUpDown: '上下移動檢視',
|
||||
associativeLine: '關聯線',
|
||||
associativeLineWidth: '寬度',
|
||||
associativeLineColor: '顏色',
|
||||
associativeLineActiveWidth: '啟用時寬度',
|
||||
associativeLineActiveColor: '啟用時顏色',
|
||||
mousewheelZoomActionReverse: '滑鼠滾輪縮放',
|
||||
mousewheelZoomActionReverse1: '向前縮小,向後放大',
|
||||
mousewheelZoomActionReverse2: '向前放大,向後縮小',
|
||||
createNewNodeBehavior: '建立新節點行為',
|
||||
default: '啟用新節點並進入編輯',
|
||||
notActive: '不啟用新節點',
|
||||
activeOnly: '僅啟用新節點,不進入編輯',
|
||||
rootStyle: '根節點',
|
||||
associativeLineText: '關聯線文字',
|
||||
fontFamily: '字型',
|
||||
fontSize: '字型大小',
|
||||
isShowScrollbar: '顯示捲軸',
|
||||
isUseHandDrawnLikeStyle: '使用手繪風格',
|
||||
rootLineStartPos: '根節點連線起始位置',
|
||||
center: '中心',
|
||||
edge: '邊緣',
|
||||
rainbowLines: '彩虹線條',
|
||||
notUseRainbowLines: '不使用彩虹線條',
|
||||
outerFramePadding: '外框內距'
|
||||
belowNode: '顯示在節點下方'
|
||||
},
|
||||
color: {
|
||||
moreColor: '更多顏色'
|
||||
@@ -112,7 +121,10 @@ export default {
|
||||
copySuccess: '複製成功',
|
||||
copyFail: '複製失敗',
|
||||
number: '將其子節點編號',
|
||||
expandNodeChild: '展開所有下級節點'
|
||||
expandNodeChild: '展開所有下級節點',
|
||||
unExpandNodeChild: '收起所有下級節點',
|
||||
addToDo: '添加待辦',
|
||||
removeToDo: '刪除待辦'
|
||||
},
|
||||
count: {
|
||||
words: '字數',
|
||||
@@ -214,7 +226,6 @@ export default {
|
||||
text: '文字',
|
||||
fontFamily: '字型',
|
||||
fontSize: '字型大小',
|
||||
lineHeight: '行高',
|
||||
color: '顏色',
|
||||
addFontWeight: '粗體',
|
||||
italic: '斜體',
|
||||
@@ -238,7 +249,12 @@ export default {
|
||||
endColor: '結束',
|
||||
arrowDir: '箭頭位置',
|
||||
arrowDirStart: '頭部',
|
||||
arrowDirEnd: '尾部'
|
||||
arrowDirEnd: '尾部',
|
||||
selectNodeTip: '請選擇壹個節點',
|
||||
openLineFlow: '開啓流動效果',
|
||||
lineFlowDuration: '一個流動周期的時間',
|
||||
forward: '正向',
|
||||
reverse: '反向'
|
||||
},
|
||||
theme: {
|
||||
title: '主題',
|
||||
|
||||
420
web/src/pages/Edit/components/AssociativeLineStyle.vue
Normal file
@@ -0,0 +1,420 @@
|
||||
<template>
|
||||
<Sidebar ref="sidebar" :title="'关联线样式'">
|
||||
<div class="sidebarContent" :class="{ isDark: isDark }">
|
||||
<div class="title noTop">{{ $t('baseStyle.associativeLine') }}</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.associativeLineColor') }}</span>
|
||||
<span
|
||||
class="block"
|
||||
v-popover:popover4
|
||||
:style="{ backgroundColor: style.associativeLineColor }"
|
||||
></span>
|
||||
<el-popover ref="popover4" placement="bottom" trigger="click">
|
||||
<Color
|
||||
:color="style.associativeLineColor"
|
||||
@change="
|
||||
color => {
|
||||
update('associativeLineColor', color)
|
||||
}
|
||||
"
|
||||
></Color>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.associativeLineWidth') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.associativeLineWidth"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
update('associativeLineWidth', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in lineWidthList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
>
|
||||
<span
|
||||
v-if="item > 0"
|
||||
class="borderLine"
|
||||
:class="{ isDark: isDark }"
|
||||
:style="{ height: item + 'px' }"
|
||||
></span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{
|
||||
$t('baseStyle.associativeLineActiveColor')
|
||||
}}</span>
|
||||
<span
|
||||
class="block"
|
||||
v-popover:popover5
|
||||
:style="{ backgroundColor: style.associativeLineActiveColor }"
|
||||
></span>
|
||||
<el-popover ref="popover5" placement="bottom" trigger="click">
|
||||
<Color
|
||||
:color="style.associativeLineActiveColor"
|
||||
@change="
|
||||
color => {
|
||||
update('associativeLineActiveColor', color)
|
||||
}
|
||||
"
|
||||
></Color>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">{{
|
||||
$t('baseStyle.associativeLineActiveWidth')
|
||||
}}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.associativeLineActiveWidth"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
update('associativeLineActiveWidth', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in lineWidthList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
>
|
||||
<span
|
||||
v-if="item > 0"
|
||||
class="borderLine"
|
||||
:class="{ isDark: isDark }"
|
||||
:style="{ height: item + 'px' }"
|
||||
></span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('style.style') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.associativeLineDasharray"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
update('associativeLineDasharray', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in borderDasharrayList"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
>
|
||||
<svg width="120" height="34">
|
||||
<line
|
||||
x1="10"
|
||||
y1="17"
|
||||
x2="110"
|
||||
y2="17"
|
||||
stroke-width="2"
|
||||
:stroke="
|
||||
style.associativeLineDasharray === item.value
|
||||
? '#409eff'
|
||||
: isDark
|
||||
? '#fff'
|
||||
: '#000'
|
||||
"
|
||||
:stroke-dasharray="item.value"
|
||||
></line>
|
||||
</svg>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 关联线文字 -->
|
||||
<div class="title noTop">{{ $t('baseStyle.associativeLineText') }}</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.fontFamily') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
v-model="style.associativeLineTextFontFamily"
|
||||
placeholder=""
|
||||
@change="update('associativeLineTextFontFamily', $event)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in fontFamilyList"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
:style="{ fontFamily: item.value }"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.color') }}</span>
|
||||
<span
|
||||
class="block"
|
||||
v-popover:popover6
|
||||
:style="{ backgroundColor: style.associativeLineTextColor }"
|
||||
></span>
|
||||
<el-popover ref="popover6" placement="bottom" trigger="click">
|
||||
<Color
|
||||
:color="style.associativeLineTextColor"
|
||||
@change="
|
||||
color => {
|
||||
update('associativeLineTextColor', color)
|
||||
}
|
||||
"
|
||||
></Color>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.fontSize') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.associativeLineTextFontSize"
|
||||
placeholder=""
|
||||
@change="update('associativeLineTextFontSize', $event)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in fontSizeList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
:style="{ fontSize: item + 'px' }"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Sidebar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sidebar from './Sidebar'
|
||||
import Color from './Color'
|
||||
import {
|
||||
lineWidthList,
|
||||
lineStyleList,
|
||||
backgroundRepeatList,
|
||||
backgroundPositionList,
|
||||
backgroundSizeList,
|
||||
fontFamilyList,
|
||||
fontSizeList,
|
||||
rootLineKeepSameInCurveList,
|
||||
lineStyleMap,
|
||||
borderDasharrayList
|
||||
} from '@/config'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import {
|
||||
supportLineStyleLayoutsMap,
|
||||
supportLineRadiusLayouts,
|
||||
supportNodeUseLineStyleLayouts,
|
||||
supportRootLineKeepSameInCurveLayouts,
|
||||
rainbowLinesOptions
|
||||
} from '@/config/constant'
|
||||
|
||||
const defaultStyle = {
|
||||
associativeLineColor: '',
|
||||
associativeLineWidth: 0,
|
||||
associativeLineActiveWidth: 0,
|
||||
associativeLineDasharray: '',
|
||||
associativeLineActiveColor: '',
|
||||
associativeLineTextFontSize: 0,
|
||||
associativeLineTextColor: '',
|
||||
associativeLineTextFontFamily: ''
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'BaseStyle',
|
||||
components: {
|
||||
Sidebar,
|
||||
Color
|
||||
},
|
||||
props: {
|
||||
mindMap: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
lineWidthList,
|
||||
fontSizeList,
|
||||
activeLineNode: null,
|
||||
activeLineToNode: null,
|
||||
style: {
|
||||
...defaultStyle
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
activeSidebar: state => state.activeSidebar,
|
||||
isDark: state => state.localConfig.isDark
|
||||
}),
|
||||
|
||||
fontFamilyList() {
|
||||
return fontFamilyList[this.$i18n.locale] || fontFamilyList.zh
|
||||
},
|
||||
|
||||
borderDasharrayList() {
|
||||
return borderDasharrayList[this.$i18n.locale] || borderDasharrayList.zh
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeSidebar(val) {
|
||||
if (val === 'associativeLineStyle') {
|
||||
this.$refs.sidebar.show = true
|
||||
} else {
|
||||
this.$refs.sidebar.show = false
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.mindMap.on('associative_line_click', this.onAssociativeLineClick)
|
||||
this.mindMap.on(
|
||||
'associative_line_deactivate',
|
||||
this.associativeLineDeactivate
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setActiveSidebar']),
|
||||
|
||||
onAssociativeLineClick(a, b, node, toNode) {
|
||||
this.activeLineNode = node
|
||||
this.activeLineToNode = toNode
|
||||
const styleConfig = this.mindMap.associativeLine.getStyleConfig(
|
||||
node,
|
||||
toNode
|
||||
)
|
||||
Object.keys(this.style).forEach(item => {
|
||||
this.style[item] = styleConfig[item]
|
||||
})
|
||||
this.setActiveSidebar('associativeLineStyle')
|
||||
},
|
||||
|
||||
associativeLineDeactivate() {
|
||||
this.setActiveSidebar('')
|
||||
this.activeLineNode = null
|
||||
this.activeLineToNode = null
|
||||
this.style = {
|
||||
...defaultStyle
|
||||
}
|
||||
},
|
||||
|
||||
update(prop, value) {
|
||||
this.style[prop] = value
|
||||
const associativeLineStyle =
|
||||
this.activeLineNode.getData('associativeLineStyle') || {}
|
||||
const toNodeUid = this.activeLineToNode.getData('uid')
|
||||
const lineStyle = associativeLineStyle[toNodeUid] || {}
|
||||
this.activeLineNode.setData({
|
||||
associativeLineStyle: {
|
||||
...associativeLineStyle,
|
||||
[toNodeUid]: {
|
||||
...lineStyle,
|
||||
...this.style
|
||||
}
|
||||
}
|
||||
})
|
||||
this.mindMap.associativeLine.updateActiveLineStyle()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sidebarContent {
|
||||
padding: 20px;
|
||||
padding-top: 10px;
|
||||
|
||||
&.isDark {
|
||||
.title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.row {
|
||||
.rowItem {
|
||||
.name {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-family: PingFangSC-Medium, PingFang SC;
|
||||
font-weight: 500;
|
||||
color: rgba(26, 26, 26, 0.9);
|
||||
margin-bottom: 10px;
|
||||
margin-top: 20px;
|
||||
|
||||
&.noTop {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.rowItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.name {
|
||||
font-size: 12px;
|
||||
margin-right: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.borderLine {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
background-color: #000;
|
||||
|
||||
&.isDark {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.el-select-dropdown__item.selected {
|
||||
.borderLine {
|
||||
background-color: #409eff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -273,6 +273,61 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 流动效果 -->
|
||||
<div class="row" v-if="supportLineFlow">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('style.openLineFlow') }}</span>
|
||||
<el-checkbox
|
||||
v-model="style.lineFlow"
|
||||
@change="
|
||||
value => {
|
||||
update('lineFlow', value)
|
||||
}
|
||||
"
|
||||
></el-checkbox>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('style.direction') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.lineFlowForward"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
update('lineFlowForward', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
key="1"
|
||||
:label="$t('style.forward')"
|
||||
:value="true"
|
||||
></el-option>
|
||||
<el-option
|
||||
key="2"
|
||||
:label="$t('style.reverse')"
|
||||
:value="false"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="supportLineFlow">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('style.lineFlowDuration') }}</span>
|
||||
<el-input-number
|
||||
v-model="style.lineFlowDuration"
|
||||
@change="
|
||||
value => {
|
||||
update('lineFlowDuration', value)
|
||||
}
|
||||
"
|
||||
:min="0.1"
|
||||
size="mini"
|
||||
:step="0.5"
|
||||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 彩虹线条 -->
|
||||
<div class="title noTop">{{ $t('baseStyle.rainbowLines') }}</div>
|
||||
<div class="row">
|
||||
@@ -714,146 +769,6 @@
|
||||
></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印 -->
|
||||
<div class="title noTop">{{ $t('baseStyle.watermark') }}</div>
|
||||
<div class="row">
|
||||
<!-- 是否显示水印 -->
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="watermarkConfig.show"
|
||||
@change="watermarkShowChange"
|
||||
>{{ $t('baseStyle.showWatermark') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="watermarkConfig.show">
|
||||
<!-- 是否仅在导出时显示 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="watermarkConfig.onlyExport"
|
||||
@change="updateWatermarkConfig"
|
||||
>{{ $t('baseStyle.onlyExport') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否在节点下方 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="watermarkConfig.belowNode"
|
||||
@change="updateWatermarkConfig"
|
||||
>{{ $t('baseStyle.belowNode') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.watermarkText') }}</span>
|
||||
<el-input
|
||||
v-model="watermarkConfig.text"
|
||||
size="small"
|
||||
@change="updateWatermarkConfig"
|
||||
@keydown.native.stop
|
||||
></el-input>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字颜色 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.watermarkTextColor') }}</span>
|
||||
<span
|
||||
class="block"
|
||||
v-popover:popover3
|
||||
:style="{ backgroundColor: watermarkConfig.textStyle.color }"
|
||||
></span>
|
||||
<el-popover ref="popover3" placement="bottom" trigger="click">
|
||||
<Color
|
||||
:color="watermarkConfig.textStyle.color"
|
||||
@change="
|
||||
value => {
|
||||
watermarkConfig.textStyle.color = value
|
||||
updateWatermarkConfig()
|
||||
}
|
||||
"
|
||||
></Color>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字透明度 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.watermarkTextOpacity') }}</span>
|
||||
<el-slider
|
||||
v-model="watermarkConfig.textStyle.opacity"
|
||||
style="width: 170px"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.1"
|
||||
@change="updateWatermarkConfig"
|
||||
></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字字号 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{
|
||||
$t('baseStyle.watermarkTextFontSize')
|
||||
}}</span>
|
||||
<el-input-number
|
||||
v-model="watermarkConfig.textStyle.fontSize"
|
||||
size="small"
|
||||
:min="0"
|
||||
:max="50"
|
||||
:step="1"
|
||||
@change="updateWatermarkConfig"
|
||||
@keydown.native.stop
|
||||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 旋转角度 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.watermarkAngle') }}</span>
|
||||
<el-input-number
|
||||
v-model="watermarkConfig.angle"
|
||||
size="small"
|
||||
:min="0"
|
||||
:max="90"
|
||||
:step="10"
|
||||
@change="updateWatermarkConfig"
|
||||
@keydown.native.stop
|
||||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印行间距 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.watermarkLineSpacing') }}</span>
|
||||
<el-input-number
|
||||
v-model="watermarkConfig.lineSpacing"
|
||||
size="small"
|
||||
:step="10"
|
||||
@change="updateWatermarkConfig"
|
||||
@keydown.native.stop
|
||||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字间距 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.watermarkTextSpacing') }}</span>
|
||||
<el-input-number
|
||||
v-model="watermarkConfig.textSpacing"
|
||||
size="small"
|
||||
:step="10"
|
||||
@change="updateWatermarkConfig"
|
||||
@keydown.native.stop
|
||||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 外框内边距 -->
|
||||
<div class="title noTop">{{ $t('baseStyle.outerFramePadding') }}</div>
|
||||
<div class="row">
|
||||
@@ -884,150 +799,6 @@
|
||||
></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 其他配置 -->
|
||||
<div class="title noTop">{{ $t('baseStyle.otherConfig') }}</div>
|
||||
<!-- 配置性能模式 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="config.openPerformance"
|
||||
@change="
|
||||
value => {
|
||||
updateOtherConfig('openPerformance', value)
|
||||
}
|
||||
"
|
||||
>{{ $t('baseStyle.openPerformance') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置开启自由拖拽 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="config.enableFreeDrag"
|
||||
@change="
|
||||
value => {
|
||||
updateOtherConfig('enableFreeDrag', value)
|
||||
}
|
||||
"
|
||||
>{{ $t('baseStyle.enableFreeDrag') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置是否启用富文本编辑 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="enableNodeRichText"
|
||||
@change="enableNodeRichTextChange"
|
||||
>{{ $t('baseStyle.isEnableNodeRichText') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置鼠标滚轮行为 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.mousewheelAction') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 120px"
|
||||
v-model="config.mousewheelAction"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
updateOtherConfig('mousewheelAction', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
:label="$t('baseStyle.zoomView')"
|
||||
value="zoom"
|
||||
></el-option>
|
||||
<el-option
|
||||
:label="$t('baseStyle.moveViewUpDown')"
|
||||
value="move"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置鼠标缩放行为 -->
|
||||
<div class="row" v-if="config.mousewheelAction === 'zoom'">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{
|
||||
$t('baseStyle.mousewheelZoomActionReverse')
|
||||
}}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 120px"
|
||||
v-model="config.mousewheelZoomActionReverse"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
updateOtherConfig('mousewheelZoomActionReverse', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
:label="$t('baseStyle.mousewheelZoomActionReverse1')"
|
||||
:value="false"
|
||||
></el-option>
|
||||
<el-option
|
||||
:label="$t('baseStyle.mousewheelZoomActionReverse2')"
|
||||
:value="true"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置创建新节点时的行为 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.createNewNodeBehavior') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 120px"
|
||||
v-model="config.createNewNodeBehavior"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
updateOtherConfig('createNewNodeBehavior', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
:label="$t('baseStyle.default')"
|
||||
value="default"
|
||||
></el-option>
|
||||
<el-option
|
||||
:label="$t('baseStyle.notActive')"
|
||||
value="notActive"
|
||||
></el-option>
|
||||
<el-option
|
||||
:label="$t('baseStyle.activeOnly')"
|
||||
value="activeOnly"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否显示滚动条 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="localConfigs.isShowScrollbar"
|
||||
@change="updateLocalConfig('isShowScrollbar', $event)"
|
||||
>{{ $t('baseStyle.isShowScrollbar') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否开启手绘风格 -->
|
||||
<div class="row" v-if="supportHandDrawnLikeStyle">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="localConfigs.isUseHandDrawnLikeStyle"
|
||||
@change="updateLocalConfig('isUseHandDrawnLikeStyle', $event)"
|
||||
>{{ $t('baseStyle.isUseHandDrawnLikeStyle') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Sidebar>
|
||||
</template>
|
||||
@@ -1095,6 +866,9 @@ export default {
|
||||
rootLineKeepSameInCurve: '',
|
||||
rootLineStartPositionKeepSameInCurve: '',
|
||||
lineRadius: 0,
|
||||
lineFlow: false,
|
||||
lineFlowForward: true,
|
||||
lineFlowDuration: 1,
|
||||
generalizationLineWidth: '',
|
||||
generalizationLineColor: '',
|
||||
associativeLineColor: '',
|
||||
@@ -1118,34 +892,8 @@ export default {
|
||||
marginY: 0,
|
||||
nodeUseLineStyle: false
|
||||
},
|
||||
config: {
|
||||
openPerformance: false,
|
||||
enableFreeDrag: false,
|
||||
mousewheelAction: 'zoom',
|
||||
mousewheelZoomActionReverse: false,
|
||||
createNewNodeBehavior: 'default'
|
||||
},
|
||||
watermarkConfig: {
|
||||
show: false,
|
||||
onlyExport: false,
|
||||
text: '',
|
||||
lineSpacing: 100,
|
||||
textSpacing: 100,
|
||||
angle: 30,
|
||||
textStyle: {
|
||||
color: '',
|
||||
opacity: 0,
|
||||
fontSize: 1
|
||||
}
|
||||
},
|
||||
rainbowLinesPopoverVisible: false,
|
||||
curRainbowLineColorList: null,
|
||||
updateWatermarkTimer: null,
|
||||
enableNodeRichText: true,
|
||||
localConfigs: {
|
||||
isShowScrollbar: false,
|
||||
isUseHandDrawnLikeStyle: false
|
||||
},
|
||||
currentLayout: '', // 当前结构
|
||||
outerFramePadding: {
|
||||
outerFramePaddingX: 0,
|
||||
@@ -1158,7 +906,7 @@ export default {
|
||||
activeSidebar: state => state.activeSidebar,
|
||||
localConfig: state => state.localConfig,
|
||||
isDark: state => state.localConfig.isDark,
|
||||
supportHandDrawnLikeStyle: state => state.supportHandDrawnLikeStyle
|
||||
supportLineFlow: state => state.supportLineFlow
|
||||
}),
|
||||
lineStyleList() {
|
||||
return lineStyleList[this.$i18n.locale] || lineStyleList.zh
|
||||
@@ -1221,8 +969,6 @@ export default {
|
||||
if (val === 'baseStyle') {
|
||||
this.$refs.sidebar.show = true
|
||||
this.initStyle()
|
||||
this.initConfig()
|
||||
this.initWatermark()
|
||||
this.initRainbowLines()
|
||||
this.initOuterFramePadding()
|
||||
this.currentLayout = this.mindMap.getLayout()
|
||||
@@ -1243,7 +989,6 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initLoacalConfig()
|
||||
this.$bus.$on('setData', this.onSetData)
|
||||
},
|
||||
beforeDestroy() {
|
||||
@@ -1259,42 +1004,9 @@ export default {
|
||||
}, 0)
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 14:02:12
|
||||
* @Desc: 初始样式
|
||||
*/
|
||||
// 初始样式
|
||||
initStyle() {
|
||||
;[
|
||||
'backgroundColor',
|
||||
'lineWidth',
|
||||
'lineStyle',
|
||||
'showLineMarker',
|
||||
'rootLineKeepSameInCurve',
|
||||
'rootLineStartPositionKeepSameInCurve',
|
||||
'lineRadius',
|
||||
'lineColor',
|
||||
'generalizationLineWidth',
|
||||
'generalizationLineColor',
|
||||
'associativeLineColor',
|
||||
'associativeLineWidth',
|
||||
'associativeLineActiveWidth',
|
||||
'associativeLineDasharray',
|
||||
'associativeLineActiveColor',
|
||||
'associativeLineTextFontSize',
|
||||
'associativeLineTextColor',
|
||||
'associativeLineTextFontFamily',
|
||||
'paddingX',
|
||||
'paddingY',
|
||||
'imgMaxWidth',
|
||||
'imgMaxHeight',
|
||||
'iconSize',
|
||||
'backgroundImage',
|
||||
'backgroundRepeat',
|
||||
'backgroundPosition',
|
||||
'backgroundSize',
|
||||
'nodeUseLineStyle'
|
||||
].forEach(key => {
|
||||
Object.keys(this.style).forEach(key => {
|
||||
this.style[key] = this.mindMap.getThemeConfig(key)
|
||||
if (key === 'backgroundImage' && this.style[key] === 'none') {
|
||||
this.style[key] = ''
|
||||
@@ -1303,41 +1015,6 @@ export default {
|
||||
this.initMarginStyle()
|
||||
},
|
||||
|
||||
// 初始化其他配置
|
||||
initConfig() {
|
||||
;[
|
||||
'openPerformance',
|
||||
'enableFreeDrag',
|
||||
'mousewheelAction',
|
||||
'mousewheelZoomActionReverse',
|
||||
'createNewNodeBehavior'
|
||||
].forEach(key => {
|
||||
this.config[key] = this.mindMap.getConfig(key)
|
||||
})
|
||||
},
|
||||
|
||||
// 初始化本地配置
|
||||
initLoacalConfig() {
|
||||
this.enableNodeRichText = this.localConfig.openNodeRichText
|
||||
this.mousewheelAction = this.localConfig.mousewheelAction
|
||||
this.mousewheelZoomActionReverse = this.localConfig.mousewheelZoomActionReverse
|
||||
;['isShowScrollbar', 'isUseHandDrawnLikeStyle'].forEach(key => {
|
||||
this.localConfigs[key] = this.localConfig[key]
|
||||
})
|
||||
},
|
||||
|
||||
// 初始化水印配置
|
||||
initWatermark() {
|
||||
let config = this.mindMap.getConfig('watermarkConfig')
|
||||
;['text', 'lineSpacing', 'textSpacing', 'angle', 'onlyExport'].forEach(
|
||||
key => {
|
||||
this.watermarkConfig[key] = config[key]
|
||||
}
|
||||
)
|
||||
this.watermarkConfig.show = !!config.text
|
||||
this.watermarkConfig.textStyle = { ...config.textStyle }
|
||||
},
|
||||
|
||||
// 初始化彩虹线条配置
|
||||
initRainbowLines() {
|
||||
const config = this.mindMap.getConfig('rainbowLinesConfig') || {}
|
||||
@@ -1358,11 +1035,7 @@ export default {
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-03 22:27:32
|
||||
* @Desc: margin初始值
|
||||
*/
|
||||
// margin初始值
|
||||
initMarginStyle() {
|
||||
;['marginX', 'marginY'].forEach(key => {
|
||||
this.style[key] = this.mindMap.getThemeConfig()[this.marginActiveTab][
|
||||
@@ -1371,11 +1044,7 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 14:05:40
|
||||
* @Desc: 更新配置
|
||||
*/
|
||||
// 更新配置
|
||||
update(key, value) {
|
||||
if (key === 'backgroundImage' && value === 'none') {
|
||||
this.style[key] = ''
|
||||
@@ -1393,36 +1062,6 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
// 更新其他配置
|
||||
updateOtherConfig(key, value) {
|
||||
this.mindMap.updateConfig({
|
||||
[key]: value
|
||||
})
|
||||
this.data.config = this.data.config || {}
|
||||
this.data.config[key] = value
|
||||
storeConfig({
|
||||
config: this.data.config
|
||||
})
|
||||
},
|
||||
|
||||
// 更新水印配置
|
||||
updateWatermarkConfig() {
|
||||
clearTimeout(this.updateWatermarkTimer)
|
||||
this.updateWatermarkTimer = setTimeout(() => {
|
||||
let { show, ...config } = this.watermarkConfig
|
||||
this.mindMap.watermark.updateWatermark({
|
||||
...config
|
||||
})
|
||||
this.data.config = this.data.config || {}
|
||||
this.data.config.watermarkConfig = this.mindMap.getConfig(
|
||||
'watermarkConfig'
|
||||
)
|
||||
storeConfig({
|
||||
config: this.data.config
|
||||
})
|
||||
}, 300)
|
||||
},
|
||||
|
||||
// 更新彩虹线条配置
|
||||
updateRainbowLinesConfig(item) {
|
||||
this.rainbowLinesPopoverVisible = false
|
||||
@@ -1474,33 +1113,6 @@ export default {
|
||||
config: this.data.theme.config
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 切换显示水印与否
|
||||
watermarkShowChange(value) {
|
||||
if (value) {
|
||||
let text =
|
||||
this.watermarkConfig.text || this.$t('baseStyle.watermarkDefaultText')
|
||||
this.watermarkConfig.text = text
|
||||
} else {
|
||||
this.watermarkConfig.text = ''
|
||||
}
|
||||
this.updateWatermarkConfig()
|
||||
},
|
||||
|
||||
// 切换是否开启节点富文本编辑
|
||||
enableNodeRichTextChange(e) {
|
||||
this.mindMap.renderer.textEdit.hideEditTextBox()
|
||||
this.setLocalConfig({
|
||||
openNodeRichText: e
|
||||
})
|
||||
},
|
||||
|
||||
// 本地配置
|
||||
updateLocalConfig(key, value) {
|
||||
this.setLocalConfig({
|
||||
[key]: value
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@
|
||||
<span class="name">{{ $t('contextmenu.moveDownNode') }}</span>
|
||||
<span class="desc">Ctrl + ↓</span>
|
||||
</div>
|
||||
<div class="item" @click="exec('UNEXPAND_ALL')">
|
||||
<span class="name">{{ $t('contextmenu.unExpandNodeChild') }}</span>
|
||||
</div>
|
||||
<div class="item" @click="exec('EXPAND_ALL')">
|
||||
<span class="name">{{ $t('contextmenu.expandNodeChild') }}</span>
|
||||
</div>
|
||||
@@ -89,6 +92,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item" @click="setCheckbox" v-if="supportCheckbox">
|
||||
<span class="name">{{
|
||||
hasCheckbox ? $t('contextmenu.removeToDo') : $t('contextmenu.addToDo')
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="splitLine"></div>
|
||||
<div class="item danger" @click="exec('REMOVE_NODE')">
|
||||
<span class="name">{{ $t('contextmenu.deleteNode') }}</span>
|
||||
@@ -244,7 +252,8 @@ export default {
|
||||
...mapState({
|
||||
isZenMode: state => state.localConfig.isZenMode,
|
||||
isDark: state => state.localConfig.isDark,
|
||||
supportNumbers: state => state.supportNumbers
|
||||
supportNumbers: state => state.supportNumbers,
|
||||
supportCheckbox: state => state.supportCheckbox
|
||||
}),
|
||||
expandList() {
|
||||
return [
|
||||
@@ -322,6 +331,9 @@ export default {
|
||||
},
|
||||
numberLevelList() {
|
||||
return numberLevelList[this.$i18n.locale] || numberLevelList.zh
|
||||
},
|
||||
hasCheckbox() {
|
||||
return !!this.node.getData('checkbox')
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -464,8 +476,12 @@ export default {
|
||||
this.node
|
||||
)
|
||||
break
|
||||
case 'UNEXPAND_ALL':
|
||||
const uid = this.node ? this.node.uid : ''
|
||||
this.$bus.$emit('execCommand', key, !uid, uid)
|
||||
break
|
||||
case 'EXPAND_ALL':
|
||||
this.$bus.$emit('execCommand', key, this.node.uid)
|
||||
this.$bus.$emit('execCommand', key, this.node ? this.node.uid : '')
|
||||
break
|
||||
default:
|
||||
this.$bus.$emit('execCommand', key, ...args)
|
||||
@@ -496,6 +512,21 @@ export default {
|
||||
this.mindMap.execCommand('SET_NUMBER', [], {
|
||||
[prop]: value
|
||||
})
|
||||
this.hide()
|
||||
},
|
||||
|
||||
// 设置待办
|
||||
setCheckbox() {
|
||||
this.mindMap.execCommand(
|
||||
'SET_CHECKBOX',
|
||||
[],
|
||||
this.hasCheckbox
|
||||
? null
|
||||
: {
|
||||
done: false
|
||||
}
|
||||
)
|
||||
this.hide()
|
||||
},
|
||||
|
||||
// 复制到剪贴板
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
<OutlineSidebar :mindMap="mindMap"></OutlineSidebar>
|
||||
<Style v-if="!isZenMode"></Style>
|
||||
<BaseStyle :data="mindMapData" :mindMap="mindMap"></BaseStyle>
|
||||
<AssociativeLineStyle
|
||||
v-if="mindMap"
|
||||
:mindMap="mindMap"
|
||||
></AssociativeLineStyle>
|
||||
<Theme v-if="mindMap" :data="mindMapData" :mindMap="mindMap"></Theme>
|
||||
<Structure :mindMap="mindMap"></Structure>
|
||||
<ShortcutKey></ShortcutKey>
|
||||
@@ -38,6 +42,7 @@
|
||||
<SourceCodeEdit v-if="mindMap" :mindMap="mindMap"></SourceCodeEdit>
|
||||
<NodeOuterFrame v-if="mindMap" :mindMap="mindMap"></NodeOuterFrame>
|
||||
<NodeTagStyle v-if="mindMap" :mindMap="mindMap"></NodeTagStyle>
|
||||
<Setting :data="mindMapData" :mindMap="mindMap"></Setting>
|
||||
<div
|
||||
class="dragMask"
|
||||
v-if="showDragMask"
|
||||
@@ -71,20 +76,19 @@ import Formula from 'simple-mind-map/src/plugins/Formula.js'
|
||||
import RainbowLines from 'simple-mind-map/src/plugins/RainbowLines.js'
|
||||
import Demonstrate from 'simple-mind-map/src/plugins/Demonstrate.js'
|
||||
import OuterFrame from 'simple-mind-map/src/plugins/OuterFrame.js'
|
||||
import MindMapLayoutPro from 'simple-mind-map/src/plugins/MindMapLayoutPro.js'
|
||||
import Themes from 'simple-mind-map-plugin-themes'
|
||||
// 协同编辑插件
|
||||
// import Cooperate from 'simple-mind-map/src/plugins/Cooperate.js'
|
||||
// 手绘风格插件,该插件为付费插件,详情请查看开发文档
|
||||
// 以下插件为付费插件,详情请查看开发文档。依次为:手绘风格插件、标记插件、编号插件、Freemind软件格式导入导出插件、Excel软件格式导入导出插件、待办插件、节点连线流动效果插件
|
||||
// import HandDrawnLikeStyle from 'simple-mind-map-plugin-handdrawnlikestyle'
|
||||
// 标记插件,该插件为付费插件,详情请查看开发文档
|
||||
// import Notation from 'simple-mind-map-plugin-notation'
|
||||
// 编号插件,该插件为付费插件,详情请查看开发文档
|
||||
// import Numbers from 'simple-mind-map-plugin-numbers'
|
||||
// Freemind软件格式导入导出插件,该插件为付费插件,详情请查看开发文档
|
||||
// import Freemind from 'simple-mind-map-plugin-freemind'
|
||||
// Excel软件格式导入导出插件,该插件为付费插件,详情请查看开发文档
|
||||
// import Excel from 'simple-mind-map-plugin-excel'
|
||||
// npm link simple-mind-map-plugin-excel simple-mind-map-plugin-freemind simple-mind-map-plugin-numbers simple-mind-map-plugin-notation simple-mind-map-plugin-handdrawnlikestyle simple-mind-map simple-mind-map-plugin-themes
|
||||
// import Checkbox from 'simple-mind-map-plugin-checkbox'
|
||||
// import LineFlow from 'simple-mind-map-plugin-lineflow'
|
||||
// npm link simple-mind-map-plugin-excel simple-mind-map-plugin-freemind simple-mind-map-plugin-numbers simple-mind-map-plugin-notation simple-mind-map-plugin-handdrawnlikestyle simple-mind-map-plugin-checkbox simple-mind-map simple-mind-map-plugin-themes simple-mind-map-plugin-lineflow
|
||||
import OutlineSidebar from './OutlineSidebar'
|
||||
import Style from './Style'
|
||||
import BaseStyle from './BaseStyle'
|
||||
@@ -121,6 +125,8 @@ import SourceCodeEdit from './SourceCodeEdit.vue'
|
||||
import NodeAttachment from './NodeAttachment.vue'
|
||||
import NodeOuterFrame from './NodeOuterFrame.vue'
|
||||
import NodeTagStyle from './NodeTagStyle.vue'
|
||||
import Setting from './Setting.vue'
|
||||
import AssociativeLineStyle from './AssociativeLineStyle.vue'
|
||||
|
||||
// 注册插件
|
||||
MindMap.usePlugin(MiniMap)
|
||||
@@ -140,16 +146,12 @@ MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(RainbowLines)
|
||||
.usePlugin(Demonstrate)
|
||||
.usePlugin(OuterFrame)
|
||||
.usePlugin(MindMapLayoutPro)
|
||||
// .usePlugin(Cooperate) // 协同插件
|
||||
|
||||
// 注册主题
|
||||
Themes.init(MindMap)
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-24 22:56:17
|
||||
* @Desc: 编辑区域
|
||||
*/
|
||||
export default {
|
||||
name: 'Edit',
|
||||
components: {
|
||||
@@ -176,7 +178,9 @@ export default {
|
||||
SourceCodeEdit,
|
||||
NodeAttachment,
|
||||
NodeOuterFrame,
|
||||
NodeTagStyle
|
||||
NodeTagStyle,
|
||||
Setting,
|
||||
AssociativeLineStyle
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -290,21 +294,13 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-03 22:11:37
|
||||
* @Desc: 获取思维导图数据,实际应该调接口获取
|
||||
*/
|
||||
// 获取思维导图数据,实际应该调接口获取
|
||||
getData() {
|
||||
let storeData = getData()
|
||||
this.mindMapData = storeData
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-01 10:19:07
|
||||
* @Desc: 存储数据当数据有变时
|
||||
*/
|
||||
// 存储数据当数据有变时
|
||||
bindSaveEvent() {
|
||||
this.$bus.$on('data_change', data => {
|
||||
storeData(data)
|
||||
@@ -319,21 +315,13 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-02 23:19:52
|
||||
* @Desc: 手动保存
|
||||
*/
|
||||
// 手动保存
|
||||
manualSave() {
|
||||
let data = this.mindMap.getData(true)
|
||||
storeConfig(data)
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-10 15:01:01
|
||||
* @Desc: 初始化
|
||||
*/
|
||||
// 初始化
|
||||
init() {
|
||||
let hasFileURL = this.hasFileURL()
|
||||
let { root, layout, theme, view, config } = this.mindMapData
|
||||
@@ -367,11 +355,12 @@ export default {
|
||||
// this.$bus.$emit('hideNoteContent')
|
||||
}
|
||||
},
|
||||
openRealtimeRenderOnNodeTextEdit: true,
|
||||
enableAutoEnterTextEditWhenKeydown: true,
|
||||
...(config || {}),
|
||||
iconList: [...icon],
|
||||
useLeftKeySelectionRightKeyDrag: this.useLeftKeySelectionRightKeyDrag,
|
||||
customInnerElsAppendTo: null,
|
||||
enableAutoEnterTextEditWhenKeydown: true,
|
||||
customHandleClipboardText: handleClipboardText,
|
||||
defaultNodeImage: require('../../../assets/img/图片加载失败.svg'),
|
||||
initRootNodePosition: ['center', 'center'],
|
||||
@@ -576,6 +565,14 @@ export default {
|
||||
this.$store.commit('setSupportExcel', true)
|
||||
Vue.prototype.Excel = Excel
|
||||
}
|
||||
if (typeof Checkbox !== 'undefined') {
|
||||
this.mindMap.addPlugin(Checkbox)
|
||||
this.$store.commit('setSupportCheckbox', true)
|
||||
}
|
||||
if (typeof LineFlow !== 'undefined') {
|
||||
this.mindMap.addPlugin(LineFlow)
|
||||
this.$store.commit('setSupportLineFlow', true)
|
||||
}
|
||||
this.mindMap.keyCommand.addShortcut('Control+s', () => {
|
||||
this.manualSave()
|
||||
})
|
||||
@@ -604,7 +601,8 @@ export default {
|
||||
'node_attachmentClick',
|
||||
'node_attachmentContextmenu',
|
||||
'demonstrate_jump',
|
||||
'exit_demonstrate'
|
||||
'exit_demonstrate',
|
||||
'node_note_dblclick'
|
||||
].forEach(event => {
|
||||
this.mindMap.on(event, (...args) => {
|
||||
this.$bus.$emit(event, ...args)
|
||||
@@ -653,11 +651,7 @@ export default {
|
||||
return /\.(smm|json|xmind|md|xlsx)$/.test(fileURL)
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-03 23:01:13
|
||||
* @Desc: 动态设置思维导图数据
|
||||
*/
|
||||
// 动态设置思维导图数据
|
||||
setData(data) {
|
||||
this.handleShowLoading()
|
||||
if (data.root) {
|
||||
@@ -669,29 +663,17 @@ export default {
|
||||
this.manualSave()
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 13:32:11
|
||||
* @Desc: 重新渲染
|
||||
*/
|
||||
// 重新渲染
|
||||
reRender() {
|
||||
this.mindMap.reRender()
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:08:28
|
||||
* @Desc: 执行命令
|
||||
*/
|
||||
// 执行命令
|
||||
execCommand(...args) {
|
||||
this.mindMap.execCommand(...args)
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:33:02
|
||||
* @Desc: 导出
|
||||
*/
|
||||
// 导出
|
||||
async export(...args) {
|
||||
try {
|
||||
showLoading()
|
||||
|
||||
@@ -42,7 +42,8 @@ export default {
|
||||
note: '',
|
||||
activeNodes: [],
|
||||
editor: null,
|
||||
isMobile: isMobile()
|
||||
isMobile: isMobile(),
|
||||
appointNode: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -63,6 +64,10 @@ export default {
|
||||
methods: {
|
||||
handleNodeActive(...args) {
|
||||
this.activeNodes = [...args[1]]
|
||||
this.updateNoteInfo()
|
||||
},
|
||||
|
||||
updateNoteInfo() {
|
||||
if (this.activeNodes.length > 0) {
|
||||
let firstNode = this.activeNodes[0]
|
||||
this.note = firstNode.getData('note') || ''
|
||||
@@ -71,19 +76,18 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
handleShowNodeNote() {
|
||||
handleShowNodeNote(node) {
|
||||
this.$bus.$emit('startTextEdit')
|
||||
if (node) {
|
||||
this.appointNode = node
|
||||
this.note = node.getData('note') || ''
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.initEditor()
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-05-09 11:37:05
|
||||
* @Desc: 初始化编辑器
|
||||
*/
|
||||
initEditor() {
|
||||
if (!this.editor) {
|
||||
this.editor = new Editor({
|
||||
@@ -96,25 +100,24 @@ export default {
|
||||
this.editor.setMarkdown(this.note)
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-22 22:08:11
|
||||
* @Desc: 取消
|
||||
*/
|
||||
cancel() {
|
||||
this.dialogVisible = false
|
||||
if (this.appointNode) {
|
||||
this.appointNode = null
|
||||
this.updateNoteInfo()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-06 22:28:20
|
||||
* @Desc: 确定
|
||||
*/
|
||||
confirm() {
|
||||
this.note = this.editor.getMarkdown()
|
||||
this.activeNodes.forEach(node => {
|
||||
node.setNote(this.note)
|
||||
})
|
||||
if (this.appointNode) {
|
||||
this.appointNode.setNote(this.note)
|
||||
} else {
|
||||
this.activeNodes.forEach(node => {
|
||||
node.setNote(this.note)
|
||||
})
|
||||
}
|
||||
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
574
web/src/pages/Edit/components/Setting.vue
Normal file
@@ -0,0 +1,574 @@
|
||||
<template>
|
||||
<Sidebar ref="sidebar" :title="$t('setting.title')">
|
||||
<div class="sidebarContent" :class="{ isDark: isDark }" v-if="data">
|
||||
<!-- 水印 -->
|
||||
<div class="title noTop">{{ $t('setting.watermark') }}</div>
|
||||
<div class="row">
|
||||
<!-- 是否显示水印 -->
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="watermarkConfig.show"
|
||||
@change="watermarkShowChange"
|
||||
>{{ $t('setting.showWatermark') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="watermarkConfig.show">
|
||||
<!-- 是否仅在导出时显示 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="watermarkConfig.onlyExport"
|
||||
@change="updateWatermarkConfig"
|
||||
>{{ $t('setting.onlyExport') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否在节点下方 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="watermarkConfig.belowNode"
|
||||
@change="updateWatermarkConfig"
|
||||
>{{ $t('setting.belowNode') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.watermarkText') }}</span>
|
||||
<el-input
|
||||
v-model="watermarkConfig.text"
|
||||
size="small"
|
||||
@change="updateWatermarkConfig"
|
||||
@keydown.native.stop
|
||||
></el-input>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字颜色 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.watermarkTextColor') }}</span>
|
||||
<span
|
||||
class="block"
|
||||
v-popover:popover3
|
||||
:style="{ backgroundColor: watermarkConfig.textStyle.color }"
|
||||
></span>
|
||||
<el-popover ref="popover3" placement="bottom" trigger="click">
|
||||
<Color
|
||||
:color="watermarkConfig.textStyle.color"
|
||||
@change="
|
||||
value => {
|
||||
watermarkConfig.textStyle.color = value
|
||||
updateWatermarkConfig()
|
||||
}
|
||||
"
|
||||
></Color>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字透明度 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.watermarkTextOpacity') }}</span>
|
||||
<el-slider
|
||||
v-model="watermarkConfig.textStyle.opacity"
|
||||
style="width: 170px"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.1"
|
||||
@change="updateWatermarkConfig"
|
||||
></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字字号 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.watermarkTextFontSize') }}</span>
|
||||
<el-input-number
|
||||
v-model="watermarkConfig.textStyle.fontSize"
|
||||
size="small"
|
||||
:min="0"
|
||||
:max="50"
|
||||
:step="1"
|
||||
@change="updateWatermarkConfig"
|
||||
@keydown.native.stop
|
||||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 旋转角度 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.watermarkAngle') }}</span>
|
||||
<el-input-number
|
||||
v-model="watermarkConfig.angle"
|
||||
size="small"
|
||||
:min="0"
|
||||
:max="90"
|
||||
:step="10"
|
||||
@change="updateWatermarkConfig"
|
||||
@keydown.native.stop
|
||||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印行间距 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.watermarkLineSpacing') }}</span>
|
||||
<el-input-number
|
||||
v-model="watermarkConfig.lineSpacing"
|
||||
size="small"
|
||||
:step="10"
|
||||
@change="updateWatermarkConfig"
|
||||
@keydown.native.stop
|
||||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字间距 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.watermarkTextSpacing') }}</span>
|
||||
<el-input-number
|
||||
v-model="watermarkConfig.textSpacing"
|
||||
size="small"
|
||||
:step="10"
|
||||
@change="updateWatermarkConfig"
|
||||
@keydown.native.stop
|
||||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 配置性能模式 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="config.openPerformance"
|
||||
@change="
|
||||
value => {
|
||||
updateOtherConfig('openPerformance', value)
|
||||
}
|
||||
"
|
||||
>{{ $t('setting.openPerformance') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置开启自由拖拽 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="config.enableFreeDrag"
|
||||
@change="
|
||||
value => {
|
||||
updateOtherConfig('enableFreeDrag', value)
|
||||
}
|
||||
"
|
||||
>{{ $t('setting.enableFreeDrag') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置是否启用富文本编辑 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="enableNodeRichText"
|
||||
@change="enableNodeRichTextChange"
|
||||
>{{ $t('setting.isEnableNodeRichText') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否开启文本编辑时实时更新节点大小 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="config.openRealtimeRenderOnNodeTextEdit"
|
||||
@change="
|
||||
updateOtherConfig('openRealtimeRenderOnNodeTextEdit', $event)
|
||||
"
|
||||
>{{ $t('setting.openRealtimeRenderOnNodeTextEdit') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否显示滚动条 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="localConfigs.isShowScrollbar"
|
||||
@change="updateLocalConfig('isShowScrollbar', $event)"
|
||||
>{{ $t('setting.isShowScrollbar') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否一直显示展开收起按钮 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="config.alwaysShowExpandBtn"
|
||||
@change="updateOtherConfig('alwaysShowExpandBtn', $event)"
|
||||
>{{ $t('setting.alwaysShowExpandBtn') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否在键盘输入时自动进入节点文本编辑模式 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="config.enableAutoEnterTextEditWhenKeydown"
|
||||
@change="updateOtherConfig('enableAutoEnterTextEditWhenKeydown', $event)"
|
||||
>{{ $t('setting.enableAutoEnterTextEditWhenKeydown') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否开启手绘风格 -->
|
||||
<div class="row" v-if="supportHandDrawnLikeStyle">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="localConfigs.isUseHandDrawnLikeStyle"
|
||||
@change="updateLocalConfig('isUseHandDrawnLikeStyle', $event)"
|
||||
>{{ $t('setting.isUseHandDrawnLikeStyle') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置鼠标滚轮行为 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.mousewheelAction') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 120px"
|
||||
v-model="config.mousewheelAction"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
updateOtherConfig('mousewheelAction', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option :label="$t('setting.zoomView')" value="zoom"></el-option>
|
||||
<el-option
|
||||
:label="$t('setting.moveViewUpDown')"
|
||||
value="move"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置鼠标缩放行为 -->
|
||||
<div class="row" v-if="config.mousewheelAction === 'zoom'">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{
|
||||
$t('setting.mousewheelZoomActionReverse')
|
||||
}}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 120px"
|
||||
v-model="config.mousewheelZoomActionReverse"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
updateOtherConfig('mousewheelZoomActionReverse', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
:label="$t('setting.mousewheelZoomActionReverse1')"
|
||||
:value="false"
|
||||
></el-option>
|
||||
<el-option
|
||||
:label="$t('setting.mousewheelZoomActionReverse2')"
|
||||
:value="true"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置创建新节点时的行为 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.createNewNodeBehavior') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 120px"
|
||||
v-model="config.createNewNodeBehavior"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
updateOtherConfig('createNewNodeBehavior', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
:label="$t('setting.default')"
|
||||
value="default"
|
||||
></el-option>
|
||||
<el-option
|
||||
:label="$t('setting.notActive')"
|
||||
value="notActive"
|
||||
></el-option>
|
||||
<el-option
|
||||
:label="$t('setting.activeOnly')"
|
||||
value="activeOnly"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 标签显示的位置 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.tagPosition') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 120px"
|
||||
v-model="config.tagPosition"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
updateOtherConfig('tagPosition', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
:label="$t('setting.tagPositionRight')"
|
||||
value="right"
|
||||
></el-option>
|
||||
<el-option
|
||||
:label="$t('setting.tagPositionBottom')"
|
||||
value="bottom"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Sidebar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sidebar from './Sidebar'
|
||||
import { storeConfig } from '@/api'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import Color from './Color'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Sidebar,
|
||||
Color
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: [Object, null],
|
||||
default: null
|
||||
},
|
||||
mindMap: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
config: {
|
||||
openPerformance: false,
|
||||
enableFreeDrag: false,
|
||||
mousewheelAction: 'zoom',
|
||||
mousewheelZoomActionReverse: false,
|
||||
createNewNodeBehavior: 'default',
|
||||
tagPosition: 'right',
|
||||
openRealtimeRenderOnNodeTextEdit: true,
|
||||
alwaysShowExpandBtn: false,
|
||||
enableAutoEnterTextEditWhenKeydown: true
|
||||
},
|
||||
watermarkConfig: {
|
||||
show: false,
|
||||
onlyExport: false,
|
||||
text: '',
|
||||
lineSpacing: 100,
|
||||
textSpacing: 100,
|
||||
angle: 30,
|
||||
textStyle: {
|
||||
color: '',
|
||||
opacity: 0,
|
||||
fontSize: 1
|
||||
}
|
||||
},
|
||||
updateWatermarkTimer: null,
|
||||
enableNodeRichText: true,
|
||||
localConfigs: {
|
||||
isShowScrollbar: false,
|
||||
isUseHandDrawnLikeStyle: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
activeSidebar: state => state.activeSidebar,
|
||||
localConfig: state => state.localConfig,
|
||||
isDark: state => state.localConfig.isDark,
|
||||
supportHandDrawnLikeStyle: state => state.supportHandDrawnLikeStyle
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
activeSidebar(val) {
|
||||
if (val === 'setting') {
|
||||
this.$refs.sidebar.show = true
|
||||
this.initConfig()
|
||||
this.initWatermark()
|
||||
} else {
|
||||
this.$refs.sidebar.show = false
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initLoacalConfig()
|
||||
},
|
||||
beforeDestroy() {},
|
||||
methods: {
|
||||
...mapMutations(['setLocalConfig']),
|
||||
|
||||
// 初始化其他配置
|
||||
initConfig() {
|
||||
Object.keys(this.config).forEach(key => {
|
||||
this.config[key] = this.mindMap.getConfig(key)
|
||||
})
|
||||
},
|
||||
|
||||
// 初始化本地配置
|
||||
initLoacalConfig() {
|
||||
this.enableNodeRichText = this.localConfig.openNodeRichText
|
||||
this.mousewheelAction = this.localConfig.mousewheelAction
|
||||
this.mousewheelZoomActionReverse = this.localConfig.mousewheelZoomActionReverse
|
||||
;['isShowScrollbar', 'isUseHandDrawnLikeStyle'].forEach(key => {
|
||||
this.localConfigs[key] = this.localConfig[key]
|
||||
})
|
||||
},
|
||||
|
||||
// 初始化水印配置
|
||||
initWatermark() {
|
||||
const config = this.mindMap.getConfig('watermarkConfig')
|
||||
;['text', 'lineSpacing', 'textSpacing', 'angle', 'onlyExport'].forEach(
|
||||
key => {
|
||||
this.watermarkConfig[key] = config[key]
|
||||
}
|
||||
)
|
||||
this.watermarkConfig.show = !!config.text
|
||||
this.watermarkConfig.textStyle = { ...config.textStyle }
|
||||
},
|
||||
|
||||
// 更新其他配置
|
||||
updateOtherConfig(key, value) {
|
||||
this.mindMap.updateConfig({
|
||||
[key]: value
|
||||
})
|
||||
this.data.config = this.data.config || {}
|
||||
this.data.config[key] = value
|
||||
storeConfig({
|
||||
config: this.data.config
|
||||
})
|
||||
if (['tagPosition', 'alwaysShowExpandBtn'].includes(key)) {
|
||||
this.mindMap.reRender()
|
||||
}
|
||||
},
|
||||
|
||||
// 更新水印配置
|
||||
updateWatermarkConfig() {
|
||||
clearTimeout(this.updateWatermarkTimer)
|
||||
this.updateWatermarkTimer = setTimeout(() => {
|
||||
let { show, ...config } = this.watermarkConfig
|
||||
this.mindMap.watermark.updateWatermark({
|
||||
...config
|
||||
})
|
||||
this.data.config = this.data.config || {}
|
||||
this.data.config.watermarkConfig = this.mindMap.getConfig(
|
||||
'watermarkConfig'
|
||||
)
|
||||
storeConfig({
|
||||
config: this.data.config
|
||||
})
|
||||
}, 300)
|
||||
},
|
||||
|
||||
// 切换显示水印与否
|
||||
watermarkShowChange(value) {
|
||||
if (value) {
|
||||
let text =
|
||||
this.watermarkConfig.text || this.$t('setting.watermarkDefaultText')
|
||||
this.watermarkConfig.text = text
|
||||
} else {
|
||||
this.watermarkConfig.text = ''
|
||||
}
|
||||
this.updateWatermarkConfig()
|
||||
},
|
||||
|
||||
// 切换是否开启节点富文本编辑
|
||||
enableNodeRichTextChange(e) {
|
||||
this.mindMap.renderer.textEdit.hideEditTextBox()
|
||||
this.setLocalConfig({
|
||||
openNodeRichText: e
|
||||
})
|
||||
},
|
||||
|
||||
// 本地配置
|
||||
updateLocalConfig(key, value) {
|
||||
this.setLocalConfig({
|
||||
[key]: value
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sidebarContent {
|
||||
padding: 20px;
|
||||
padding-top: 10px;
|
||||
|
||||
&.isDark {
|
||||
.title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.row {
|
||||
.rowItem {
|
||||
.name {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-family: PingFangSC-Medium, PingFang SC;
|
||||
font-weight: 500;
|
||||
color: rgba(26, 26, 26, 0.9);
|
||||
margin-bottom: 10px;
|
||||
margin-top: 20px;
|
||||
|
||||
&.noTop {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.rowItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.name {
|
||||
font-size: 12px;
|
||||
margin-right: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -13,6 +13,7 @@
|
||||
<span class="name">{{ $t('style.fontFamily') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 100px"
|
||||
v-model="style.fontFamily"
|
||||
placeholder=""
|
||||
@change="update('fontFamily')"
|
||||
@@ -27,8 +28,6 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('style.fontSize') }}</span>
|
||||
<el-select
|
||||
@@ -48,24 +47,6 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('style.lineHeight') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.lineHeight"
|
||||
placeholder=""
|
||||
@change="update('lineHeight')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in lineHeightList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="btnGroup">
|
||||
@@ -434,6 +415,49 @@
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 流动效果 -->
|
||||
<div class="row" v-if="supportLineFlow">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('style.openLineFlow') }}</span>
|
||||
<el-checkbox
|
||||
v-model="style.lineFlow"
|
||||
@change="update('lineFlow')"
|
||||
></el-checkbox>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('style.direction') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.lineFlowForward"
|
||||
placeholder=""
|
||||
@change="update('lineFlowForward')"
|
||||
>
|
||||
<el-option
|
||||
key="1"
|
||||
:label="$t('style.forward')"
|
||||
:value="true"
|
||||
></el-option>
|
||||
<el-option
|
||||
key="2"
|
||||
:label="$t('style.reverse')"
|
||||
:value="false"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="supportLineFlow">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('style.lineFlowDuration') }}</span>
|
||||
<el-input-number
|
||||
v-model="style.lineFlowDuration"
|
||||
@change="update('lineFlowDuration')"
|
||||
:min="0.1"
|
||||
size="mini"
|
||||
:step="0.5"
|
||||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 节点内边距 -->
|
||||
<div class="title noTop">{{ $t('style.nodePadding') }}</div>
|
||||
<div class="row">
|
||||
@@ -460,7 +484,7 @@
|
||||
</div>
|
||||
<div class="tipBox" v-else>
|
||||
<div class="tipIcon iconfont icontianjiazijiedian"></div>
|
||||
<div class="tipText">请选择一个节点</div>
|
||||
<div class="tipText">{{ $t('style.selectNodeTip') }}</div>
|
||||
</div>
|
||||
</Sidebar>
|
||||
</template>
|
||||
@@ -474,7 +498,6 @@ import {
|
||||
borderWidthList,
|
||||
borderDasharrayList,
|
||||
borderRadiusList,
|
||||
lineHeightList,
|
||||
shapeList,
|
||||
shapeListMap,
|
||||
linearGradientDirList
|
||||
@@ -497,7 +520,6 @@ export default {
|
||||
fontSizeList,
|
||||
borderWidthList,
|
||||
borderRadiusList,
|
||||
lineHeightList,
|
||||
activeNodes: [],
|
||||
style: {
|
||||
shape: '',
|
||||
@@ -506,7 +528,6 @@ export default {
|
||||
color: '',
|
||||
fontFamily: '',
|
||||
fontSize: '',
|
||||
lineHeight: '',
|
||||
textDecoration: '',
|
||||
fontWeight: '',
|
||||
fontStyle: '',
|
||||
@@ -522,14 +543,18 @@ export default {
|
||||
gradientStyle: false,
|
||||
startColor: '',
|
||||
endColor: '',
|
||||
linearGradientDir: ''
|
||||
linearGradientDir: '',
|
||||
lineFlow: false,
|
||||
lineFlowForward: true,
|
||||
lineFlowDuration: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isDark: state => state.localConfig.isDark,
|
||||
activeSidebar: state => state.activeSidebar
|
||||
activeSidebar: state => state.activeSidebar,
|
||||
supportLineFlow: state => state.supportLineFlow
|
||||
}),
|
||||
fontFamilyList() {
|
||||
return fontFamilyList[this.$i18n.locale] || fontFamilyList.zh
|
||||
@@ -586,30 +611,7 @@ export default {
|
||||
if (this.activeNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
;[
|
||||
'shape',
|
||||
'paddingX',
|
||||
'paddingY',
|
||||
'color',
|
||||
'fontFamily',
|
||||
'fontSize',
|
||||
'lineHeight',
|
||||
'textDecoration',
|
||||
'fontWeight',
|
||||
'fontStyle',
|
||||
'borderWidth',
|
||||
'borderColor',
|
||||
'fillColor',
|
||||
'borderDasharray',
|
||||
'borderRadius',
|
||||
'lineColor',
|
||||
'lineDasharray',
|
||||
'lineWidth',
|
||||
'lineMarkerDir',
|
||||
'gradientStyle',
|
||||
'startColor',
|
||||
'endColor'
|
||||
].forEach(item => {
|
||||
Object.keys(this.style).forEach(item => {
|
||||
this.style[item] = this.activeNodes[0].getStyle(item, false)
|
||||
})
|
||||
this.initLinearGradientDir()
|
||||
|
||||
@@ -234,12 +234,14 @@ export default {
|
||||
window.addEventListener('resize', this.computeToolbarShowThrottle)
|
||||
this.$bus.$on('lang_change', this.computeToolbarShowThrottle)
|
||||
window.addEventListener('beforeunload', this.onUnload)
|
||||
this.$bus.$on('node_note_dblclick', this.onNodeNoteDblclick)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$bus.$off('write_local_file', this.onWriteLocalFile)
|
||||
window.removeEventListener('resize', this.computeToolbarShowThrottle)
|
||||
this.$bus.$off('lang_change', this.computeToolbarShowThrottle)
|
||||
window.removeEventListener('beforeunload', this.onUnload)
|
||||
this.$bus.$off('node_note_dblclick', this.onNodeNoteDblclick)
|
||||
},
|
||||
methods: {
|
||||
// 计算工具按钮如何显示
|
||||
@@ -501,6 +503,11 @@ export default {
|
||||
}
|
||||
this.$message.warning(this.$t('toolbar.notSupportTip'))
|
||||
}
|
||||
},
|
||||
|
||||
onNodeNoteDblclick(node, e) {
|
||||
e.stopPropagation()
|
||||
this.$bus.$emit('showNodeNote', node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ const store = new Vuex.Store({
|
||||
supportNumbers: false, // 是否支持编号
|
||||
supportFreemind: false, // 是否支持Freemind插件
|
||||
supportExcel: false, // 是否支持Excel插件
|
||||
supportCheckbox: false, // 是否支持Checkbox插件
|
||||
supportLineFlow: false, // 是否支持LineFlow插件
|
||||
isDragOutlineTreeNode: false // 当前是否正在拖拽大纲树的节点
|
||||
},
|
||||
mutations: {
|
||||
@@ -105,6 +107,16 @@ const store = new Vuex.Store({
|
||||
state.supportExcel = data
|
||||
},
|
||||
|
||||
// 设置是否支持Checkbox插件
|
||||
setSupportCheckbox(state, data) {
|
||||
state.supportCheckbox = data
|
||||
},
|
||||
|
||||
// 设置是否支持Lineflow插件
|
||||
setSupportLineFlow(state, data) {
|
||||
state.supportLineFlow = data
|
||||
},
|
||||
|
||||
// 设置树节点拖拽
|
||||
setIsDragOutlineTreeNode(state, data) {
|
||||
state.isDragOutlineTreeNode = data
|
||||
|
||||
@@ -62,14 +62,14 @@ export const copy = text => {
|
||||
|
||||
// 复制文本到剪贴板
|
||||
export const setDataToClipboard = data => {
|
||||
if (navigator.clipboard) {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(data)
|
||||
}
|
||||
}
|
||||
|
||||
// 复制图片到剪贴板
|
||||
export const setImgToClipboard = img => {
|
||||
if (navigator.clipboard) {
|
||||
if (navigator.clipboard && navigator.clipboard.write) {
|
||||
const data = [new ClipboardItem({ ['image/png']: img })]
|
||||
navigator.clipboard.write(data)
|
||||
}
|
||||
|
||||