mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-21 18:37:43 +08:00
update
This commit is contained in:
commit
bcffecab40
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,4 +2,5 @@ node_modules
|
||||
.DS_Store
|
||||
dist_electron
|
||||
simple-mind-map/dist
|
||||
simple-mind-map/types
|
||||
simple-mind-map/types
|
||||
utools/dist
|
||||
32
README.md
32
README.md
@ -7,9 +7,9 @@
|
||||
[](https://github.com/wanglin2/mind-map/stargazers)
|
||||
[](https://github.com/wanglin2/mind-map/network/members)
|
||||
|
||||
> 中文名:思绪思维导图。一个简单&强大的 Web 思维导图。
|
||||
> 中文名:思绪思维导图。一个简单&强大的 Web 思维导图库和思维导图软件。
|
||||
|
||||
本项目包含两部分:
|
||||
本项目主要包含以下内容:
|
||||
|
||||
1.一个 js 思维导图库,不依赖任何框架,可以使用它来快速完成 Web 思维导图产品的开发。
|
||||
|
||||
@ -19,13 +19,15 @@
|
||||
|
||||
在线地址:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)。
|
||||
|
||||
此外也提供了客户端可供下载使用,支持`Windows`、`Mac`及`Linux`,下载地址:
|
||||
3.此外也支持以客户端的方式使用,现已上架[uTools](https://www.u.tools/)插件应用市场,强烈建议通过`uTools`来体验。
|
||||
|
||||
Github:[releases](https://github.com/wanglin2/mind-map/releases)。百度云盘:[地址](https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3)。
|
||||
可直接在`uTools`插件应用市场中搜索`思绪`进行安装,也可以直接访问该地址:[主页](https://www.u-tools.cn/plugins/detail/%E6%80%9D%E7%BB%AA%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE/),点击右侧的【启动】按钮进行安装。
|
||||
|
||||
> 客户端版本会落后于在线版本,尝试最新功能请优先使用在线版。
|
||||
> 独立客户端下载:Github:[releases](https://github.com/wanglin2/mind-map/releases)。百度云盘:[地址](https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3)。
|
||||
>
|
||||
> 后续不会投入太多精力在独立客户端上,建议通过`uTools`来使用,功能更强,体验更好。
|
||||
|
||||
【云存储版本】如果你需要带后端的云存储版本,可以尝试我们开发的另一个项目[理想文档](https://github.com/wanglin2/lx-doc)。
|
||||
4.【云存储版本】如果你需要带后端的云存储版本,可以尝试我们开发的另一个项目[理想文档](https://github.com/wanglin2/lx-doc)。
|
||||
|
||||
# 特性
|
||||
|
||||
@ -44,7 +46,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(外框插件)、MindMapLayoutPro(思维导图布局插件)、HandDrawnLikeStyle(手绘风格插件)[收费]、Notation(节点标记插件)[收费]、Numbers(节点编号插件)[收费]、Freemind(Freemind格式导入导出插件)[收费]、Excel(Excel格式导入导出插件)[收费]、Checkbox(待办插件)[收费]、Lineflow(节点连线流动插件)[收费]
|
||||
> 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(节点连线流动插件)[收费]、Momentum(动量效果插件)[收费]
|
||||
|
||||
本项目不会实现的特性:
|
||||
|
||||
@ -103,6 +105,8 @@ const mindMap = new MindMap({
|
||||
|
||||
微信添加`wanglinguanfang`拉你入群。根据过往的经验,大部分问题都可以通过查看issue列表或文档解决,所以提问前请确保你已经阅读完了所有文档,文档里没有的可在群里提问,不必私聊作者,如果你一定要私聊,请先发红包(¥9.9+每次)。
|
||||
|
||||
如果你在杭州,也欢迎来找我面基。
|
||||
|
||||
# star
|
||||
|
||||
如果喜欢本项目,欢迎点个 star,这对我们很重要。
|
||||
@ -153,6 +157,13 @@ const mindMap = new MindMap({
|
||||
<sub style="font-size:14px"><b>黄智彪@一米一栗科技</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" style="word-wrap: break-word; width: 75.0; height: 75.0">
|
||||
<a href="#">
|
||||
<img src="./web/src/assets/avatar/沨沄.jpg" width="50;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px"/>
|
||||
<br />
|
||||
<sub style="font-size:14px"><b>沨沄</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@ -929,5 +940,12 @@ const mindMap = new MindMap({
|
||||
<sub style="font-size:14px"><b>好好先生Ervin</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" style="word-wrap: break-word; width: 75.0; height: 75.0">
|
||||
<a href="#">
|
||||
<img src="./web/src/assets/avatar/胡永刚.jpg" width="50;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px"/>
|
||||
<br />
|
||||
<sub style="font-size:14px"><b>胡永刚</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
2
dist/css/app.css
vendored
2
dist/css/app.css
vendored
@ -1 +1 @@
|
||||
*{margin:0;padding:0;box-sizing:border-box}#app{font-family:Avenir,Helvetica,Arial,sans-serif;color:#2c3e50}@font-face{font-family:iconfont;src:url(../fonts/iconfont.woff2) format("woff2"),url(../fonts/iconfont.woff) format("woff"),url(../fonts/iconfont.ttf) format("truetype")}#app,.iconfont{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal}.iconcontentleft:before{content:"\e8c9"}.iconjuzhongduiqi:before{content:"\ec80"}.iconfile-excel:before{content:"\e7b7"}.iconfreemind:before{content:"\e97d"}.iconwaikuang:before{content:"\e640"}.iconhighlight:before{content:"\e6b8"}.iconyanshibofang:before{content:"\e648"}.iconfujian:before{content:"\e88a"}.icongeshihua:before{content:"\e7a3"}.iconyuanma:before{content:"\e658"}.icongundongtiao:before{content:"\e670"}.iconxietongwendang:before{content:"\e60d"}.iconTXT:before{content:"\e6e1"}.iconwenjian1:before{content:"\e69f"}.icondodeparent:before{content:"\e70f"}.icongongshi:before{content:"\e617"}.icontouming:before{content:"\e60c"}.iconlieri:before{content:"\e60b"}.iconmoon_line:before{content:"\e745"}.iconsousuo:before{content:"\e693"}.iconjiantouyou:before{content:"\e62d"}.iconbianji1:before{content:"\e60a"}.icondaohang1:before{content:"\e632"}.iconyanjing:before{content:"\e8bf"}.iconwangzhan:before{content:"\e628"}.iconcsdn:before{content:"\e608"}.iconshejiaotubiao-10:before{content:"\e644"}.iconstar:before{content:"\e7df"}.iconfork:before{content:"\e641"}.iconxiazai:before{content:"\e613"}.iconteamwork:before{content:"\e870"}.iconshuiyin:before{content:"\e67a"}.iconxmind:before{content:"\ea57"}.iconmouseR:before{content:"\e6bd"}.iconmouseL:before{content:"\e6c0"}.iconwenjian:before{content:"\e607"}.iconpdf:before{content:"\e740"}.iconPNG:before{content:"\ec18"}.iconSVG:before{content:"\e621"}.iconmarkdown:before{content:"\ec04"}.iconjson:before{content:"\ea42"}.iconlianjiexian:before{content:"\e75b"}.iconbangzhu:before{content:"\e620"}.iconshezhi:before{content:"\e8b7"}.iconwushuju:before{content:"\e643"}.iconzuijinliulan:before{content:"\e62f"}.icon3zuidahua-3:before{content:"\e692"}.iconzuixiaohua:before{content:"\e650"}.iconzuidahua:before{content:"\e651"}.iconguanbi:before{content:"\e652"}.icondiannao:before{content:"\eac0"}.iconzhuye:before{content:"\e65c"}.iconbendi1x:before{content:"\e606"}.iconbeijingyanse:before{content:"\e6f8"}.iconqingchu:before{content:"\e605"}.iconcase:before{content:"\e6c6"}.iconxingzhuang-wenzi:before{content:"\eb99"}.iconzitijiacu:before{content:"\ec83"}.iconzitixiahuaxian:before{content:"\ec85"}.iconzitixieti:before{content:"\ec86"}.iconshanchuxian:before{content:"\e612"}.iconzitiyanse:before{content:"\e854"}.icongithub:before{content:"\e64f"}.iconchoose1:before{content:"\e6c5"}.iconzhuti:before{content:"\e7aa"}.icondaochu1:before{content:"\e63e"}.iconlingcunwei:before{content:"\e657"}.iconexport:before{content:"\e642"}.icondakai:before{content:"\ebdf"}.iconxinjian:before{content:"\e64e"}.iconjianqie:before{content:"\e601"}.iconzhengli:before{content:"\e83b"}.iconfuzhi:before{content:"\e604"}.iconniantie:before{content:"\e63f"}.iconshangyi:before{content:"\e6be"}.iconxiayi:before{content:"\e6bf"}.icongaikuozonglan:before{content:"\e609"}.iconquanxuan:before{content:"\f199"}.icondaoru:before{content:"\e6a3"}.iconhoutui-shi:before{content:"\e656"}.iconqianjin1:before{content:"\e654"}.iconwithdraw:before{content:"\e603"}.iconqianjin:before{content:"\e600"}.iconhuifumoren:before{content:"\e60e"}.iconhuanhang:before{content:"\e61e"}.iconsuoxiao:before{content:"\ec13"}.iconbianji:before{content:"\e626"}.iconfangda:before{content:"\e663"}.iconquanping1:before{content:"\e664"}.icondingwei:before{content:"\e616"}.icondaohang:before{content:"\e611"}.iconjianpan:before{content:"\e64d"}.iconquanping:before{content:"\e602"}.icondaochu:before{content:"\e63d"}.iconbiaoqian:before{content:"\e63c"}.iconflow-Mark:before{content:"\e65b"}.iconchaolianjie:before{content:"\e6f4"}.iconjingzi:before{content:"\e610"}.iconxiaolian:before{content:"\e60f"}.iconimage:before{content:"\e629"}.iconjiegou:before{content:"\e61d"}.iconyangshi:before{content:"\e631"}.iconfuhao-dagangshu:before{content:"\e71f"}.icontianjiazijiedian:before{content:"\e622"}.iconjiedian:before{content:"\e655"}.iconshanchu:before{content:"\e696"}.iconzhankai:before{content:"\e64c"}.iconzhankai1:before{content:"\e673"}
|
||||
*{margin:0;padding:0;box-sizing:border-box}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#2c3e50}.customScrollbar{&::-webkit-scrollbar{width:7px;height:7px}&::-webkit-scrollbar-thumb{border-radius:7px;background-color:rgba(0,0,0,.3);cursor:pointer}&::-webkit-scrollbar-track{box-shadow:none;background:transparent;display:none}}@font-face{font-family:iconfont;src:url(../fonts/iconfont.woff2) format("woff2"),url(../fonts/iconfont.woff) format("woff"),url(../fonts/iconfont.ttf) format("truetype")}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.iconAIshengcheng:before{content:"\e6b5"}.iconprinting:before{content:"\ea28"}.iconwenjianjia:before{content:"\e614"}.iconcontentleft:before{content:"\e8c9"}.iconjuzhongduiqi:before{content:"\ec80"}.iconfile-excel:before{content:"\e7b7"}.iconfreemind:before{content:"\e97d"}.iconwaikuang:before{content:"\e640"}.iconhighlight:before{content:"\e6b8"}.iconyanshibofang:before{content:"\e648"}.iconfujian:before{content:"\e88a"}.icongeshihua:before{content:"\e7a3"}.iconyuanma:before{content:"\e658"}.icongundongtiao:before{content:"\e670"}.iconxietongwendang:before{content:"\e60d"}.iconTXT:before{content:"\e6e1"}.iconwenjian1:before{content:"\e69f"}.icondodeparent:before{content:"\e70f"}.icongongshi:before{content:"\e617"}.icontouming:before{content:"\e60c"}.iconlieri:before{content:"\e60b"}.iconmoon_line:before{content:"\e745"}.iconsousuo:before{content:"\e693"}.iconjiantouyou:before{content:"\e62d"}.iconbianji1:before{content:"\e60a"}.icondaohang1:before{content:"\e632"}.iconyanjing:before{content:"\e8bf"}.iconwangzhan:before{content:"\e628"}.iconcsdn:before{content:"\e608"}.iconshejiaotubiao-10:before{content:"\e644"}.iconstar:before{content:"\e7df"}.iconfork:before{content:"\e641"}.iconxiazai:before{content:"\e613"}.iconteamwork:before{content:"\e870"}.iconshuiyin:before{content:"\e67a"}.iconxmind:before{content:"\ea57"}.iconmouseR:before{content:"\e6bd"}.iconmouseL:before{content:"\e6c0"}.iconwenjian:before{content:"\e607"}.iconpdf:before{content:"\e740"}.iconPNG:before{content:"\ec18"}.iconSVG:before{content:"\e621"}.iconmarkdown:before{content:"\ec04"}.iconjson:before{content:"\ea42"}.iconlianjiexian:before{content:"\e75b"}.iconbangzhu:before{content:"\e620"}.iconshezhi:before{content:"\e8b7"}.iconwushuju:before{content:"\e643"}.iconzuijinliulan:before{content:"\e62f"}.icon3zuidahua-3:before{content:"\e692"}.iconzuixiaohua:before{content:"\e650"}.iconzuidahua:before{content:"\e651"}.iconguanbi:before{content:"\e652"}.icondiannao:before{content:"\eac0"}.iconzhuye:before{content:"\e65c"}.iconbendi1x:before{content:"\e606"}.iconbeijingyanse:before{content:"\e6f8"}.iconqingchu:before{content:"\e605"}.iconcase:before{content:"\e6c6"}.iconxingzhuang-wenzi:before{content:"\eb99"}.iconzitijiacu:before{content:"\ec83"}.iconzitixiahuaxian:before{content:"\ec85"}.iconzitixieti:before{content:"\ec86"}.iconshanchuxian:before{content:"\e612"}.iconzitiyanse:before{content:"\e854"}.icongithub:before{content:"\e64f"}.iconchoose1:before{content:"\e6c5"}.iconzhuti:before{content:"\e7aa"}.icondaochu1:before{content:"\e63e"}.iconlingcunwei:before{content:"\e657"}.iconexport:before{content:"\e642"}.icondakai:before{content:"\ebdf"}.iconxinjian:before{content:"\e64e"}.iconjianqie:before{content:"\e601"}.iconzhengli:before{content:"\e83b"}.iconfuzhi:before{content:"\e604"}.iconniantie:before{content:"\e63f"}.iconshangyi:before{content:"\e6be"}.iconxiayi:before{content:"\e6bf"}.icongaikuozonglan:before{content:"\e609"}.iconquanxuan:before{content:"\f199"}.icondaoru:before{content:"\e6a3"}.iconhoutui-shi:before{content:"\e656"}.iconqianjin1:before{content:"\e654"}.iconwithdraw:before{content:"\e603"}.iconqianjin:before{content:"\e600"}.iconhuifumoren:before{content:"\e60e"}.iconhuanhang:before{content:"\e61e"}.iconsuoxiao:before{content:"\ec13"}.iconbianji:before{content:"\e626"}.iconfangda:before{content:"\e663"}.iconquanping1:before{content:"\e664"}.icondingwei:before{content:"\e616"}.icondaohang:before{content:"\e611"}.iconjianpan:before{content:"\e64d"}.iconquanping:before{content:"\e602"}.icondaochu:before{content:"\e63d"}.iconbiaoqian:before{content:"\e63c"}.iconflow-Mark:before{content:"\e65b"}.iconchaolianjie:before{content:"\e6f4"}.iconjingzi:before{content:"\e610"}.iconxiaolian:before{content:"\e60f"}.iconimage:before{content:"\e629"}.iconjiegou:before{content:"\e61d"}.iconyangshi:before{content:"\e631"}.iconfuhao-dagangshu:before{content:"\e71f"}.icontianjiazijiedian:before{content:"\e622"}.iconjiedian:before{content:"\e655"}.iconshanchu:before{content:"\e696"}.iconzhankai:before{content:"\e64c"}.iconzhankai1:before{content:"\e673"}
|
||||
File diff suppressed because one or more lines are too long
BIN
dist/fonts/iconfont.ttf
vendored
BIN
dist/fonts/iconfont.ttf
vendored
Binary file not shown.
BIN
dist/fonts/iconfont.woff
vendored
BIN
dist/fonts/iconfont.woff
vendored
Binary file not shown.
BIN
dist/fonts/iconfont.woff2
vendored
BIN
dist/fonts/iconfont.woff2
vendored
Binary file not shown.
2
dist/js/app.js
vendored
2
dist/js/app.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
14
dist/js/chunk-vendors.js
vendored
14
dist/js/chunk-vendors.js
vendored
File diff suppressed because one or more lines are too long
@ -9,7 +9,7 @@
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}</script><link href="dist/css/chunk-vendors.css?74531016fcc3dfd0eba4" rel="stylesheet"><link href="dist/css/app.css?74531016fcc3dfd0eba4" 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?137a23e103fd70ffd3cb" rel="stylesheet"><link href="dist/css/app.css?137a23e103fd70ffd3cb" 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?fc8e52cca177f49cac0d"></script><script src="dist/js/app.js?fc8e52cca177f49cac0d"></script></body></html>
|
||||
}</script><script src="dist/js/chunk-vendors.js?137a23e103fd70ffd3cb"></script><script src="dist/js/app.js?137a23e103fd70ffd3cb"></script></body></html>
|
||||
|
||||
@ -30,7 +30,7 @@ MindMap.markdown = markdown
|
||||
MindMap.iconList = icons.nodeIconList
|
||||
MindMap.constants = constants
|
||||
MindMap.defaultTheme = defaultTheme
|
||||
MindMap.version = '0.13.0'
|
||||
MindMap.version = '0.13.1-fix.1'
|
||||
|
||||
MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(Watermark)
|
||||
|
||||
@ -20,7 +20,8 @@ import {
|
||||
isUndef,
|
||||
handleGetSvgDataExtraContent,
|
||||
getNodeTreeBoundingRect,
|
||||
mergeTheme
|
||||
mergeTheme,
|
||||
createUidForAppointNodes
|
||||
} from './src/utils'
|
||||
import defaultTheme, {
|
||||
checkIsNodeSizeIndependenceConfig
|
||||
@ -149,6 +150,8 @@ class MindMap {
|
||||
if (data.data && !data.data.expand) {
|
||||
data.data.expand = true
|
||||
}
|
||||
// 给没有uid的节点添加uid
|
||||
createUidForAppointNodes([data], false, null, true)
|
||||
return data
|
||||
}
|
||||
|
||||
@ -395,6 +398,7 @@ class MindMap {
|
||||
|
||||
// 更新画布数据,如果新的数据是在当前画布节点数据基础上增删改查后形成的,那么可以使用该方法来更新画布数据
|
||||
updateData(data) {
|
||||
data = this.handleData(data)
|
||||
this.emit('before_update_data', data)
|
||||
this.renderer.setData(data)
|
||||
this.render()
|
||||
@ -583,7 +587,7 @@ class MindMap {
|
||||
this.watermark.isInExport = false
|
||||
}
|
||||
// 添加必要的样式
|
||||
[this.joinCss(), ...cssTextList].forEach(s => {
|
||||
;[this.joinCss(), ...cssTextList].forEach(s => {
|
||||
clone.add(SVG(`<style>${s}</style>`))
|
||||
})
|
||||
// 附加内容
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.13.0",
|
||||
"version": "0.13.1-fix.1",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@ -302,6 +302,27 @@ export const defaultOpt = {
|
||||
},
|
||||
// 自定义快捷创建子节点按钮的点击操作,
|
||||
customQuickCreateChildBtnClick: null,
|
||||
// 添加自定义的节点内容
|
||||
// 可传递一个对象,格式如下:
|
||||
/*
|
||||
{
|
||||
// 返回要添加的DOM元素详细
|
||||
create: (node) => {
|
||||
return {
|
||||
el, // DOM节点
|
||||
width: 20, // 宽高
|
||||
height: 20
|
||||
}
|
||||
},
|
||||
// 处理生成的@svgdotjs/svg.js库的ForeignObject节点实例,可以设置其在节点内的位置
|
||||
handle: ({ content, element, node }) => {
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
addCustomContentToNode: null,
|
||||
// 节点连线样式是否允许继承祖先的连线样式
|
||||
enableInheritAncestorLineStyle: true,
|
||||
|
||||
// 【Select插件】
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
@ -492,5 +513,10 @@ export const defaultOpt = {
|
||||
maxImgResizeWidthInheritTheme: false,
|
||||
// 最大允许缩放的尺寸,maxImgResizeWidthInheritTheme选项设置为false时生效,不限制最大值可传递Infinity
|
||||
maxImgResizeWidth: Infinity,
|
||||
maxImgResizeHeight: Infinity
|
||||
maxImgResizeHeight: Infinity,
|
||||
// 自定义删除按钮和尺寸调整按钮的内容
|
||||
// 默认为内置图标,你可以传递一个svg字符串,或者其他的html字符串
|
||||
// 整体大小请使用上面的minImgResizeWidth和minImgResizeHeight选项设置
|
||||
customDeleteBtnInnerHTML: '',
|
||||
customResizeBtnInnerHTML: ''
|
||||
}
|
||||
|
||||
@ -99,6 +99,7 @@ class MindMapNode {
|
||||
this._generalizationList = []
|
||||
this._unVisibleRectRegionNode = null
|
||||
this._isMouseenter = false
|
||||
this._customContentAddToNodeAdd = null
|
||||
// 尺寸信息
|
||||
this._rectInfo = {
|
||||
textContentWidth: 0,
|
||||
@ -216,7 +217,8 @@ class MindMapNode {
|
||||
isUseCustomNodeContent,
|
||||
customCreateNodeContent,
|
||||
createNodePrefixContent,
|
||||
createNodePostfixContent
|
||||
createNodePostfixContent,
|
||||
addCustomContentToNode
|
||||
} = this.mindMap.opt
|
||||
// 需要创建的内容类型
|
||||
const typeList = [
|
||||
@ -289,6 +291,18 @@ class MindMapNode {
|
||||
addXmlns(this._postfixData.el)
|
||||
}
|
||||
}
|
||||
if (
|
||||
addCustomContentToNode &&
|
||||
typeof addCustomContentToNode.create === 'function'
|
||||
) {
|
||||
this._customContentAddToNodeAdd = addCustomContentToNode.create(this)
|
||||
if (
|
||||
this._customContentAddToNodeAdd &&
|
||||
this._customContentAddToNodeAdd.el
|
||||
) {
|
||||
addXmlns(this._customContentAddToNodeAdd.el)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 计算节点的宽高
|
||||
@ -872,15 +886,18 @@ class MindMapNode {
|
||||
|
||||
// 设置连线样式
|
||||
styleLine(line, childNode, enableMarker) {
|
||||
const { enableInheritAncestorLineStyle } = this.mindMap.opt
|
||||
const getName = enableInheritAncestorLineStyle
|
||||
? 'getSelfInhertStyle'
|
||||
: 'getSelfStyle'
|
||||
const width =
|
||||
childNode.getSelfInhertStyle('lineWidth') ||
|
||||
childNode.getStyle('lineWidth', true)
|
||||
childNode[getName]('lineWidth') || childNode.getStyle('lineWidth', true)
|
||||
const color =
|
||||
childNode.getSelfInhertStyle('lineColor') ||
|
||||
childNode[getName]('lineColor') ||
|
||||
this.getRainbowLineColor(childNode) ||
|
||||
childNode.getStyle('lineColor', true)
|
||||
const dasharray =
|
||||
childNode.getSelfInhertStyle('lineDasharray') ||
|
||||
childNode[getName]('lineDasharray') ||
|
||||
childNode.getStyle('lineDasharray', true)
|
||||
this.style.line(
|
||||
line,
|
||||
|
||||
@ -177,10 +177,15 @@ function layout() {
|
||||
const {
|
||||
hoverRectPadding,
|
||||
openRealtimeRenderOnNodeTextEdit,
|
||||
textContentMargin
|
||||
textContentMargin,
|
||||
addCustomContentToNode
|
||||
} = this.mindMap.opt
|
||||
// 避免编辑过程中展开收起按钮闪烁的问题
|
||||
if (openRealtimeRenderOnNodeTextEdit && this._expandBtn) {
|
||||
if (
|
||||
openRealtimeRenderOnNodeTextEdit &&
|
||||
this._expandBtn &&
|
||||
this.getChildrenLength() > 0
|
||||
) {
|
||||
this.group.add(this._expandBtn)
|
||||
}
|
||||
const { width, height } = this
|
||||
@ -428,6 +433,22 @@ function layout() {
|
||||
}
|
||||
textContentNested.translate(translateX, translateY)
|
||||
addHoverNode()
|
||||
if (this._customContentAddToNodeAdd && this._customContentAddToNodeAdd.el) {
|
||||
const foreignObject = createForeignObjectNode(
|
||||
this._customContentAddToNodeAdd
|
||||
)
|
||||
this.group.add(foreignObject)
|
||||
if (
|
||||
addCustomContentToNode &&
|
||||
typeof addCustomContentToNode.handle === 'function'
|
||||
) {
|
||||
addCustomContentToNode.handle({
|
||||
content: this._customContentAddToNodeAdd,
|
||||
element: foreignObject,
|
||||
node: this
|
||||
})
|
||||
}
|
||||
}
|
||||
this.mindMap.emit('node_layout_end', this)
|
||||
}
|
||||
|
||||
|
||||
@ -2,13 +2,14 @@ import btnsSvg from '../../../svg/btns'
|
||||
import { SVG, Circle, G } from '@svgdotjs/svg.js'
|
||||
|
||||
function initQuickCreateChildBtn() {
|
||||
if (this.isGeneralization) return
|
||||
this._quickCreateChildBtn = null
|
||||
this._showQuickCreateChildBtn = false
|
||||
}
|
||||
|
||||
// 显示按钮
|
||||
function showQuickCreateChildBtn() {
|
||||
if (this.getChildrenLength() > 0) return
|
||||
if (this.isGeneralization || this.getChildrenLength() > 0) return
|
||||
// 创建按钮
|
||||
if (this._quickCreateChildBtn) {
|
||||
this.group.add(this._quickCreateChildBtn)
|
||||
@ -63,6 +64,7 @@ function showQuickCreateChildBtn() {
|
||||
|
||||
// 移除按钮
|
||||
function removeQuickCreateChildBtn() {
|
||||
if (this.isGeneralization) return
|
||||
if (this._quickCreateChildBtn && this._showQuickCreateChildBtn) {
|
||||
this._quickCreateChildBtn.remove()
|
||||
this._showQuickCreateChildBtn = false
|
||||
@ -71,6 +73,7 @@ function removeQuickCreateChildBtn() {
|
||||
|
||||
// 隐藏按钮
|
||||
function hideQuickCreateChildBtn() {
|
||||
if (this.isGeneralization) return
|
||||
const { isActive } = this.getData()
|
||||
if (!isActive) {
|
||||
this.removeQuickCreateChildBtn()
|
||||
|
||||
@ -253,7 +253,9 @@ const transformToXmind = async (data, name) => {
|
||||
}
|
||||
// 标签
|
||||
if (node.data.tag !== undefined) {
|
||||
newData.labels = node.data.tag || []
|
||||
newData.labels = (node.data.tag || []).map(item => {
|
||||
return typeof item === 'object' && item !== null ? item.text : item
|
||||
})
|
||||
}
|
||||
// 图片
|
||||
handleNodeImageToXmind(node, newNode, waitLoadImageList, imageList)
|
||||
|
||||
@ -122,7 +122,11 @@ class NodeImgAdjust {
|
||||
|
||||
// 创建调整按钮元素
|
||||
createResizeBtnEl() {
|
||||
const { imgResizeBtnSize } = this.mindMap.opt
|
||||
const {
|
||||
imgResizeBtnSize,
|
||||
customResizeBtnInnerHTML,
|
||||
customDeleteBtnInnerHTML
|
||||
} = this.mindMap.opt
|
||||
// 容器元素
|
||||
this.handleEl = document.createElement('div')
|
||||
this.handleEl.style.cssText = `
|
||||
@ -134,7 +138,7 @@ class NodeImgAdjust {
|
||||
this.handleEl.className = 'node-img-handle'
|
||||
// 调整按钮元素
|
||||
const btnEl = document.createElement('div')
|
||||
btnEl.innerHTML = btnsSvg.imgAdjust
|
||||
btnEl.innerHTML = customResizeBtnInnerHTML || btnsSvg.imgAdjust
|
||||
btnEl.style.cssText = `
|
||||
position: absolute;
|
||||
right: 0;
|
||||
@ -179,7 +183,7 @@ class NodeImgAdjust {
|
||||
const btnRemove = document.createElement('div')
|
||||
this.handleEl.prepend(btnRemove)
|
||||
btnRemove.className = 'node-image-remove'
|
||||
btnRemove.innerHTML = btnsSvg.remove
|
||||
btnRemove.innerHTML = customDeleteBtnInnerHTML || btnsSvg.remove
|
||||
btnRemove.style.cssText = `
|
||||
position: absolute;
|
||||
right: 0;top:0;color:#fff;
|
||||
|
||||
@ -820,6 +820,7 @@ class RichText {
|
||||
|
||||
// 处理导入数据
|
||||
handleSetData(data) {
|
||||
if (!data) return
|
||||
// 短期处理,为了兼容老数据,长期会去除
|
||||
const isOldRichTextVersion =
|
||||
!data.smmVersion || compareVersion(data.smmVersion, '0.13.0') === '<'
|
||||
|
||||
@ -182,6 +182,10 @@ class Search {
|
||||
const uid = this.isNodeInstance(currentNode)
|
||||
? currentNode.getData('uid')
|
||||
: currentNode.data.uid
|
||||
if (!uid) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
const targetNode = this.mindMap.renderer.findNodeByUid(uid)
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', uid, node => {
|
||||
if (!this.isNodeInstance(currentNode)) {
|
||||
|
||||
@ -95,7 +95,7 @@ export default {
|
||||
// 点鼠标hover和激活时显示的矩形边框的圆角大小
|
||||
hoverRectRadius: 5,
|
||||
// 文本对齐
|
||||
align: 'left',
|
||||
textAlign: 'left',// right、center、justify、left
|
||||
// 图片放置位置,相对于整个文本内容
|
||||
imgPlacement: 'top', // left、right、bottom、top
|
||||
// 标签放置位置
|
||||
|
||||
@ -398,7 +398,7 @@ export const nextTick = function (fn, ctx) {
|
||||
}
|
||||
|
||||
// 检查节点是否超出画布
|
||||
export const checkNodeOuter = (mindMap, node) => {
|
||||
export const checkNodeOuter = (mindMap, node, offsetX = 0, offsetY = 0) => {
|
||||
let elRect = mindMap.elRect
|
||||
let { scaleX, scaleY, translateX, translateY } = mindMap.draw.transform()
|
||||
let { left, top, width, height } = node
|
||||
@ -408,17 +408,17 @@ export const checkNodeOuter = (mindMap, node) => {
|
||||
top = top * scaleY + translateY
|
||||
let offsetLeft = 0
|
||||
let offsetTop = 0
|
||||
if (left < 0) {
|
||||
offsetLeft = -left
|
||||
if (left < 0 + offsetX) {
|
||||
offsetLeft = -left + offsetX
|
||||
}
|
||||
if (right > elRect.width) {
|
||||
offsetLeft = -(right - elRect.width)
|
||||
if (right > elRect.width - offsetX) {
|
||||
offsetLeft = -(right - elRect.width) - offsetX
|
||||
}
|
||||
if (top < 0) {
|
||||
offsetTop = -top
|
||||
if (top < 0 + offsetY) {
|
||||
offsetTop = -top + offsetY
|
||||
}
|
||||
if (bottom > elRect.height) {
|
||||
offsetTop = -(bottom - elRect.height)
|
||||
if (bottom > elRect.height - offsetY) {
|
||||
offsetTop = -(bottom - elRect.height) - offsetY
|
||||
}
|
||||
return {
|
||||
isOuter: offsetLeft !== 0 || offsetTop !== 0,
|
||||
@ -1002,7 +1002,8 @@ export const addDataToAppointNodes = (appointNodes, data = {}) => {
|
||||
export const createUidForAppointNodes = (
|
||||
appointNodes,
|
||||
createNewId = false,
|
||||
handle = null
|
||||
handle = null,
|
||||
handleGeneralization = false
|
||||
) => {
|
||||
const walk = list => {
|
||||
list.forEach(node => {
|
||||
@ -1012,6 +1013,14 @@ export const createUidForAppointNodes = (
|
||||
if (createNewId || isUndef(node.data.uid)) {
|
||||
node.data.uid = createUid()
|
||||
}
|
||||
if (handleGeneralization) {
|
||||
const generalizationList = formatGetNodeGeneralization(node.data)
|
||||
generalizationList.forEach(gNode => {
|
||||
if (createNewId || isUndef(gNode.uid)) {
|
||||
gNode.uid = createUid()
|
||||
}
|
||||
})
|
||||
}
|
||||
handle && handle(node)
|
||||
if (node.children && node.children.length > 0) {
|
||||
walk(node.children)
|
||||
|
||||
491
web/package-lock.json
generated
491
web/package-lock.json
generated
@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "thoughts",
|
||||
"version": "0.11.2",
|
||||
"version": "0.12.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "thoughts",
|
||||
"version": "0.11.2",
|
||||
"version": "0.12.1",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@toast-ui/editor": "^3.1.5",
|
||||
"axios": "^1.7.9",
|
||||
"codemirror": "^5.65.16",
|
||||
"core-js": "^3.6.5",
|
||||
"electron-json-storage": "^4.6.0",
|
||||
@ -37,6 +38,7 @@
|
||||
"esbuild": "^0.17.15",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"express": "^4.21.2",
|
||||
"hasown": "^2.0.2",
|
||||
"less": "^3.12.2",
|
||||
"less-loader": "^7.1.0",
|
||||
@ -4152,8 +4154,7 @@
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/at-least-node": {
|
||||
"version": "1.0.0",
|
||||
@ -4227,6 +4228,30 @@
|
||||
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/form-data": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-eslint": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
|
||||
@ -4443,9 +4468,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
||||
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
@ -4456,7 +4481,7 @@
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.11.0",
|
||||
"qs": "6.13.0",
|
||||
"raw-body": "2.5.2",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
@ -4482,12 +4507,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/body-parser/node_modules/qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
"side-channel": "^1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
@ -5212,6 +5237,18 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-me-maybe": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
|
||||
@ -5790,7 +5827,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
@ -5995,9 +6031,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@ -7087,7 +7123,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
@ -7435,6 +7470,19 @@
|
||||
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/duplexer": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
|
||||
@ -8008,9 +8056,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
@ -8164,13 +8212,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@ -8179,7 +8223,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@ -8188,7 +8231,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
|
||||
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
@ -8197,14 +8239,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
|
||||
"integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
|
||||
"dev": true,
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.1"
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -8753,37 +8795,37 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
|
||||
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.2",
|
||||
"body-parser": "1.20.3",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.6.0",
|
||||
"cookie": "0.7.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.2.0",
|
||||
"finalhandler": "1.3.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"merge-descriptors": "1.0.3",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"path-to-regexp": "0.1.12",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.11.0",
|
||||
"qs": "6.13.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.18.0",
|
||||
"serve-static": "1.15.0",
|
||||
"send": "0.19.0",
|
||||
"serve-static": "1.16.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
@ -8792,6 +8834,10 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/debug": {
|
||||
@ -8810,12 +8856,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/express/node_modules/qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
"side-channel": "^1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
@ -9159,13 +9205,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
@ -9264,7 +9310,6 @@
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
@ -9525,8 +9570,7 @@
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
|
||||
},
|
||||
"node_modules/function.prototype.name": {
|
||||
"version": "1.1.6",
|
||||
@ -9574,16 +9618,32 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"dev": true,
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
|
||||
"integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.0",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -9777,7 +9837,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
}
|
||||
@ -9920,10 +9979,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"dev": true,
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@ -9932,7 +9990,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
@ -10021,7 +10078,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
@ -12101,6 +12157,14 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@ -12144,10 +12208,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
|
||||
"dev": true
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-source-map": {
|
||||
"version": "1.1.0",
|
||||
@ -12323,7 +12390,6 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@ -12332,7 +12398,6 @@
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
@ -13676,9 +13741,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
@ -14683,6 +14748,11 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/prr": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||
@ -15582,9 +15652,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
@ -15620,6 +15690,15 @@
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/send/node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@ -15737,15 +15816,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.18.0"
|
||||
"send": "0.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
@ -23640,8 +23719,7 @@
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
@ -23699,6 +23777,29 @@
|
||||
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-eslint": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
|
||||
@ -23898,9 +23999,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
||||
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bytes": "3.1.2",
|
||||
@ -23911,7 +24012,7 @@
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.11.0",
|
||||
"qs": "6.13.0",
|
||||
"raw-body": "2.5.2",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
@ -23933,12 +24034,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"side-channel": "^1.0.4"
|
||||
"side-channel": "^1.0.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24562,6 +24663,15 @@
|
||||
"set-function-length": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"call-me-maybe": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
|
||||
@ -25032,7 +25142,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
@ -25210,9 +25319,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"dev": true
|
||||
},
|
||||
"cookie-signature": {
|
||||
@ -26098,8 +26207,7 @@
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||
},
|
||||
"depd": {
|
||||
"version": "2.0.0",
|
||||
@ -26394,6 +26502,16 @@
|
||||
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
|
||||
"dev": true
|
||||
},
|
||||
"dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"duplexer": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
|
||||
@ -26869,9 +26987,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"dev": true
|
||||
},
|
||||
"end-of-stream": {
|
||||
@ -27006,38 +27124,32 @@
|
||||
"dev": true
|
||||
},
|
||||
"es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
}
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
|
||||
},
|
||||
"es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
|
||||
},
|
||||
"es-object-atoms": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
|
||||
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"es-set-tostringtag": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
|
||||
"integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
|
||||
"dev": true,
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.1"
|
||||
"hasown": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"es-to-primitive": {
|
||||
@ -27474,37 +27586,37 @@
|
||||
}
|
||||
},
|
||||
"express": {
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
|
||||
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.2",
|
||||
"body-parser": "1.20.3",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.6.0",
|
||||
"cookie": "0.7.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.2.0",
|
||||
"finalhandler": "1.3.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"merge-descriptors": "1.0.3",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"path-to-regexp": "0.1.12",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.11.0",
|
||||
"qs": "6.13.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.18.0",
|
||||
"serve-static": "1.15.0",
|
||||
"send": "0.19.0",
|
||||
"serve-static": "1.16.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
@ -27528,12 +27640,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"side-channel": "^1.0.4"
|
||||
"side-channel": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
@ -27823,13 +27935,13 @@
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
@ -27916,8 +28028,7 @@
|
||||
"follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
|
||||
},
|
||||
"for-each": {
|
||||
"version": "0.3.3",
|
||||
@ -28119,8 +28230,7 @@
|
||||
"function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
|
||||
},
|
||||
"function.prototype.name": {
|
||||
"version": "1.1.6",
|
||||
@ -28159,16 +28269,29 @@
|
||||
"dev": true
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"dev": true,
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
|
||||
"integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.0",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"requires": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"get-stream": {
|
||||
@ -28322,7 +28445,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
}
|
||||
@ -28440,16 +28562,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"dev": true
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
|
||||
},
|
||||
"has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.3"
|
||||
}
|
||||
@ -28522,7 +28642,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
@ -30208,6 +30327,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@ -30248,9 +30372,9 @@
|
||||
}
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||
"dev": true
|
||||
},
|
||||
"merge-source-map": {
|
||||
@ -30398,14 +30522,12 @@
|
||||
"mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mime-db": "1.52.0"
|
||||
}
|
||||
@ -31523,9 +31645,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"path-type": {
|
||||
@ -32399,6 +32521,11 @@
|
||||
"ipaddr.js": "1.9.1"
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"prr": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||
@ -33170,9 +33297,9 @@
|
||||
}
|
||||
},
|
||||
"send": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
@ -33207,6 +33334,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@ -33312,15 +33445,15 @@
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.18.0"
|
||||
"send": "0.19.0"
|
||||
}
|
||||
},
|
||||
"set-blocking": {
|
||||
|
||||
@ -18,11 +18,13 @@
|
||||
"format": "prettier --write src/* src/*/* src/*/*/* src/*/*/*/*",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"postuninstall": "electron-builder install-app-deps",
|
||||
"createNodeImageList": "node ./scripts/createNodeImageList.js"
|
||||
"createNodeImageList": "node ./scripts/createNodeImageList.js",
|
||||
"ai:serve": "node ./scripts/ai.js"
|
||||
},
|
||||
"main": "background.js",
|
||||
"dependencies": {
|
||||
"@toast-ui/editor": "^3.1.5",
|
||||
"axios": "^1.7.9",
|
||||
"codemirror": "^5.65.16",
|
||||
"core-js": "^3.6.5",
|
||||
"electron-json-storage": "^4.6.0",
|
||||
@ -50,6 +52,7 @@
|
||||
"esbuild": "^0.17.15",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"express": "^4.21.2",
|
||||
"hasown": "^2.0.2",
|
||||
"less": "^3.12.2",
|
||||
"less-loader": "^7.1.0",
|
||||
|
||||
@ -15,22 +15,6 @@
|
||||
// 接管应用
|
||||
window.takeOverApp = false
|
||||
</script>
|
||||
<script
|
||||
charset="UTF-8"
|
||||
id="LA_COLLECT"
|
||||
src="//sdk.51.la/js-sdk-pro.min.js"
|
||||
></script>
|
||||
<script>
|
||||
try {
|
||||
LA.init({
|
||||
id: 'KRO0WxK8GT66tYCQ',
|
||||
ck: 'KRO0WxK8GT66tYCQ',
|
||||
autoTrack: false
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
|
||||
55
web/scripts/ai.js
Normal file
55
web/scripts/ai.js
Normal file
@ -0,0 +1,55 @@
|
||||
const express = require('express')
|
||||
let axios = require('axios')
|
||||
|
||||
axios = typeof axios === 'function' ? axios : axios.default
|
||||
|
||||
const port = 3456
|
||||
|
||||
// 起个服务
|
||||
const app = express()
|
||||
app.use(express.json())
|
||||
app.use(express.urlencoded({ extended: true }))
|
||||
|
||||
// 允许跨域
|
||||
app.use((req, res, next) => {
|
||||
res.header('Access-Control-Allow-Origin', '*') // 允许所有来源的跨域请求,或者指定一个域名
|
||||
res.header('Access-Control-Allow-Methods', '*') // 允许的方法
|
||||
res.header('Access-Control-Allow-Headers', '*') // 允许的头部信息
|
||||
next()
|
||||
})
|
||||
|
||||
// 监听对话请求
|
||||
app.get('/ai/test', (req, res) => {
|
||||
res
|
||||
.json({
|
||||
code: 0,
|
||||
data: null,
|
||||
msg: '连接成功'
|
||||
})
|
||||
.end()
|
||||
})
|
||||
app.post('/ai/chat', async (req, res, next) => {
|
||||
// 设置SSE响应头
|
||||
res.setHeader('Content-Type', 'text/event-stream')
|
||||
res.setHeader('Cache-Control', 'no-cache')
|
||||
res.setHeader('Connection', 'keep-alive')
|
||||
|
||||
const { api, method = 'POST', headers = {}, data } = req.body
|
||||
|
||||
try {
|
||||
const response = await axios({
|
||||
url: api,
|
||||
method,
|
||||
headers,
|
||||
data,
|
||||
responseType: 'stream'
|
||||
})
|
||||
response.data.pipe(res)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
})
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`app listening on port ${port}`)
|
||||
})
|
||||
@ -11,7 +11,7 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="less">
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@ -23,4 +23,24 @@ export default {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.customScrollbar,
|
||||
.el-table--scrollable-y .el-table__body-wrapper {
|
||||
&::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 7px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
BIN
web/src/assets/avatar/沨沄.jpg
Normal file
BIN
web/src/assets/avatar/沨沄.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
BIN
web/src/assets/avatar/胡永刚.jpg
Normal file
BIN
web/src/assets/avatar/胡永刚.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2479351 */
|
||||
src: url('iconfont.woff2?t=1737722825571') format('woff2'),
|
||||
url('iconfont.woff?t=1737722825571') format('woff'),
|
||||
url('iconfont.ttf?t=1737722825571') format('truetype');
|
||||
src: url('iconfont.woff2?t=1739843331607') format('woff2'),
|
||||
url('iconfont.woff?t=1739843331607') format('woff'),
|
||||
url('iconfont.ttf?t=1739843331607') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@ -13,6 +13,18 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconAIshengcheng:before {
|
||||
content: "\e6b5";
|
||||
}
|
||||
|
||||
.iconprinting:before {
|
||||
content: "\ea28";
|
||||
}
|
||||
|
||||
.iconwenjianjia:before {
|
||||
content: "\e614";
|
||||
}
|
||||
|
||||
.iconcontentleft:before {
|
||||
content: "\e8c9";
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -5,6 +5,7 @@ import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
|
||||
import path from 'path'
|
||||
import { bindFileHandleEvent } from './electron/fileHandle'
|
||||
import { bindOtherHandleEvent } from './electron/otherHandle'
|
||||
import '../scripts/ai'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
|
||||
@ -444,6 +444,11 @@ export const sidebarTriggerList = [
|
||||
value: 'setting',
|
||||
icon: 'iconshezhi'
|
||||
},
|
||||
{
|
||||
name: 'AI',
|
||||
value: 'ai',
|
||||
icon: 'iconAIshengcheng'
|
||||
},
|
||||
{
|
||||
name: 'ShortcutKey',
|
||||
value: 'shortcutKey',
|
||||
@ -457,19 +462,20 @@ export const downTypeList = [
|
||||
name: 'Dedicated file',
|
||||
type: 'smm',
|
||||
icon: 'iconwenjian',
|
||||
desc: 'Available for import'
|
||||
desc:
|
||||
'SimpleMindMap private format, can be used for re import, and the client can directly edit it'
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
type: 'json',
|
||||
icon: 'iconjson',
|
||||
desc: 'Popular data exchange formats, Available for import'
|
||||
desc: 'Popular data exchange format that can be used for re importing'
|
||||
},
|
||||
{
|
||||
name: 'Image',
|
||||
type: 'png',
|
||||
icon: 'iconPNG',
|
||||
desc: 'Suitable for viewing and sharing'
|
||||
desc: 'Common image formats, suitable for viewing and sharing'
|
||||
},
|
||||
{
|
||||
name: 'SVG',
|
||||
@ -481,19 +487,19 @@ export const downTypeList = [
|
||||
name: 'PDF',
|
||||
type: 'pdf',
|
||||
icon: 'iconpdf',
|
||||
desc: 'Suitable for printing'
|
||||
desc: 'Suitable for viewing, browsing, and printing'
|
||||
},
|
||||
{
|
||||
name: 'Markdown',
|
||||
type: 'md',
|
||||
icon: 'iconmarkdown',
|
||||
desc: 'Easy for other software to open'
|
||||
desc: 'MD text format, easy for other software to open'
|
||||
},
|
||||
{
|
||||
name: 'XMind',
|
||||
type: 'xmind',
|
||||
icon: 'iconxmind',
|
||||
desc: 'XMind file'
|
||||
desc: 'XMind software file'
|
||||
},
|
||||
{
|
||||
name: 'Txt',
|
||||
@ -511,7 +517,7 @@ export const downTypeList = [
|
||||
name: 'Excel',
|
||||
type: 'xlsx',
|
||||
icon: 'iconfile-excel',
|
||||
desc: 'Excel software format'
|
||||
desc: 'Table text format, editable with Excel software'
|
||||
}
|
||||
]
|
||||
|
||||
@ -641,4 +647,4 @@ export const alignList = [
|
||||
name: 'Align right',
|
||||
value: 'right'
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@ -534,6 +534,11 @@ export const sidebarTriggerList = [
|
||||
value: 'outline',
|
||||
icon: 'iconfuhao-dagangshu'
|
||||
},
|
||||
{
|
||||
name: 'AI',
|
||||
value: 'ai',
|
||||
icon: 'iconAIshengcheng'
|
||||
},
|
||||
{
|
||||
name: '设置',
|
||||
value: 'setting',
|
||||
@ -552,19 +557,19 @@ export const downTypeList = [
|
||||
name: '专有文件',
|
||||
type: 'smm',
|
||||
icon: 'iconwenjian',
|
||||
desc: '可用于导入'
|
||||
desc: 'SimpleMindMap私有格式,可用于再次导入,客户端可直接编辑'
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
type: 'json',
|
||||
icon: 'iconjson',
|
||||
desc: '流行的数据交换格式,可用于导入'
|
||||
desc: '流行的数据交换格式,可用于再次导入'
|
||||
},
|
||||
{
|
||||
name: '图片',
|
||||
type: 'png',
|
||||
icon: 'iconPNG',
|
||||
desc: '适合查看分享'
|
||||
desc: '常用图片格式,适合查看分享'
|
||||
},
|
||||
{
|
||||
name: 'SVG',
|
||||
@ -576,19 +581,19 @@ export const downTypeList = [
|
||||
name: 'PDF',
|
||||
type: 'pdf',
|
||||
icon: 'iconpdf',
|
||||
desc: '适合打印'
|
||||
desc: '适合查看浏览和打印'
|
||||
},
|
||||
{
|
||||
name: 'Markdown',
|
||||
type: 'md',
|
||||
icon: 'iconmarkdown',
|
||||
desc: '便于其他软件打开'
|
||||
desc: 'md文本格式,便于其他软件打开'
|
||||
},
|
||||
{
|
||||
name: 'XMind',
|
||||
type: 'xmind',
|
||||
icon: 'iconxmind',
|
||||
desc: 'XMind格式'
|
||||
desc: 'XMind软件格式'
|
||||
},
|
||||
{
|
||||
name: 'Txt',
|
||||
@ -606,7 +611,7 @@ export const downTypeList = [
|
||||
name: 'Excel',
|
||||
type: 'xlsx',
|
||||
icon: 'iconfile-excel',
|
||||
desc: 'Excel软件格式'
|
||||
desc: '表格文本形式,可用Excel软件编辑'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@ -439,6 +439,11 @@ export const sidebarTriggerList = [
|
||||
value: 'outline',
|
||||
icon: 'iconfuhao-dagangshu'
|
||||
},
|
||||
{
|
||||
name: 'AI',
|
||||
value: 'ai',
|
||||
icon: 'iconAIshengcheng'
|
||||
},
|
||||
{
|
||||
name: '設置',
|
||||
value: 'setting',
|
||||
@ -457,61 +462,61 @@ export const downTypeList = [
|
||||
name: '專用檔案',
|
||||
type: 'smm',
|
||||
icon: 'iconwenjian',
|
||||
desc: '可用於匯入'
|
||||
desc: 'SimpleMindMap私有格式,可用于再次導入,客戶端可直接編輯'
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
type: 'json',
|
||||
icon: 'iconjson',
|
||||
desc: '常見的資料交換格式,可用於匯入'
|
||||
desc: '流行的數據交換格式,可用于再次導入'
|
||||
},
|
||||
{
|
||||
name: '圖片',
|
||||
type: 'png',
|
||||
icon: 'iconPNG',
|
||||
desc: '適合檢視與分享'
|
||||
desc: '常用圖片格式,適合查看分享'
|
||||
},
|
||||
{
|
||||
name: 'SVG',
|
||||
type: 'svg',
|
||||
icon: 'iconSVG',
|
||||
desc: '可縮放向量圖形'
|
||||
desc: '可縮放矢量圖形'
|
||||
},
|
||||
{
|
||||
name: 'PDF',
|
||||
type: 'pdf',
|
||||
icon: 'iconpdf',
|
||||
desc: '適合列印'
|
||||
desc: '適合查看浏覽和打印'
|
||||
},
|
||||
{
|
||||
name: 'Markdown',
|
||||
type: 'md',
|
||||
icon: 'iconmarkdown',
|
||||
desc: '方便其他軟體開啟'
|
||||
desc: 'md文本格式,便于其他軟件打開'
|
||||
},
|
||||
{
|
||||
name: 'XMind',
|
||||
type: 'xmind',
|
||||
icon: 'iconxmind',
|
||||
desc: 'XMind 檔案'
|
||||
desc: 'XMind軟件格式'
|
||||
},
|
||||
{
|
||||
name: 'Txt',
|
||||
type: 'txt',
|
||||
icon: 'iconTXT',
|
||||
desc: '純文字檔案'
|
||||
desc: '純文本文件'
|
||||
},
|
||||
{
|
||||
name: 'FreeMind',
|
||||
type: 'mm',
|
||||
icon: 'iconfreemind',
|
||||
desc: 'FreeMind軟體格式'
|
||||
desc: 'FreeMind軟件格式'
|
||||
},
|
||||
{
|
||||
name: 'Excel',
|
||||
type: 'xlsx',
|
||||
icon: 'iconfile-excel',
|
||||
desc: 'Excel軟體格式'
|
||||
desc: '表格文本形式,可用Excel軟件編輯'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
saveToRecent,
|
||||
clearRecent,
|
||||
removeFileInRecent,
|
||||
removeMultiFileInRecent,
|
||||
replaceFileInRecent,
|
||||
getRecent,
|
||||
saveFileListToRecent,
|
||||
@ -104,27 +105,31 @@ export const bindFileHandleEvent = ({ mainWindow }) => {
|
||||
|
||||
// 保存文件
|
||||
const idToFilePath = {}
|
||||
ipcMain.handle('save', async (event, id, data, fileName = '未命名') => {
|
||||
if (!idToFilePath[id]) {
|
||||
const webContents = event.sender
|
||||
const win = BrowserWindow.fromWebContents(webContents)
|
||||
const res = dialog.showSaveDialogSync(win, {
|
||||
title: '保存',
|
||||
defaultPath: fileName + '.smm',
|
||||
filters: [{ name: '思维导图', extensions: ['smm'] }]
|
||||
})
|
||||
if (res) {
|
||||
idToFilePath[id] = res
|
||||
fs.writeFile(res, data)
|
||||
saveToRecent(res).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
ipcMain.handle(
|
||||
'save',
|
||||
async (event, id, data, fileName = '未命名', defaultPath = '') => {
|
||||
if (!idToFilePath[id]) {
|
||||
const webContents = event.sender
|
||||
const win = BrowserWindow.fromWebContents(webContents)
|
||||
const res = dialog.showSaveDialogSync(win, {
|
||||
title: '保存',
|
||||
defaultPath:
|
||||
(defaultPath ? defaultPath + '/' : '') + fileName + '.smm',
|
||||
filters: [{ name: '思维导图', extensions: ['smm'] }]
|
||||
})
|
||||
return path.parse(idToFilePath[id]).name
|
||||
if (res) {
|
||||
idToFilePath[id] = res
|
||||
fs.writeFile(res, data)
|
||||
saveToRecent(res).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
})
|
||||
return path.parse(idToFilePath[id]).name
|
||||
}
|
||||
} else {
|
||||
fs.writeFile(idToFilePath[id], data)
|
||||
}
|
||||
} else {
|
||||
fs.writeFile(idToFilePath[id], data)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 打开文件
|
||||
const openFile = (event, file) => {
|
||||
@ -157,16 +162,49 @@ export const bindFileHandleEvent = ({ mainWindow }) => {
|
||||
ipcMain.handle('openFile', openFile)
|
||||
|
||||
// 选择打开本地文件
|
||||
ipcMain.on('selectOpenFile', event => {
|
||||
ipcMain.handle('selectOpenFile', event => {
|
||||
const res = dialog.showOpenDialogSync({
|
||||
title: '选择',
|
||||
filters: [{ name: '思维导图', extensions: ['smm'] }]
|
||||
})
|
||||
if (res && res[0]) {
|
||||
openFile(null, res[0])
|
||||
return res[0]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
// 选择目录
|
||||
ipcMain.handle('selectOpenFolder', event => {
|
||||
const res = dialog.showOpenDialogSync({
|
||||
title: '选择',
|
||||
properties: ['openDirectory']
|
||||
})
|
||||
if (res && res[0]) {
|
||||
return res[0]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
// 获取指定目录下指定类型的文件列表
|
||||
ipcMain.handle('getFilesInDir', (event, dir, ext) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readdir(dir, (err, files) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
const reg = new RegExp(ext + '$')
|
||||
files = files.filter(item => {
|
||||
return reg.test(item)
|
||||
})
|
||||
resolve(files)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 选择本地文件
|
||||
ipcMain.handle(
|
||||
'selectFile',
|
||||
@ -266,6 +304,30 @@ export const bindFileHandleEvent = ({ mainWindow }) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 从最近文件列表中删除指定文件
|
||||
ipcMain.handle('removeFileInRecent', async (event, file) => {
|
||||
try {
|
||||
removeFileInRecent(file).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
})
|
||||
return ''
|
||||
} catch (error) {
|
||||
return '清空失败'
|
||||
}
|
||||
})
|
||||
|
||||
// 从最近文件列表中删除指定的多个文件
|
||||
ipcMain.handle('removeMultiFileInRecent', async (event, fileList) => {
|
||||
try {
|
||||
removeMultiFileInRecent(fileList).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
})
|
||||
return ''
|
||||
} catch (error) {
|
||||
return '删除失败'
|
||||
}
|
||||
})
|
||||
|
||||
// 添加到最近文件列表
|
||||
ipcMain.handle('addRecentFileList', async (event, fileList) => {
|
||||
try {
|
||||
@ -288,6 +350,19 @@ export const bindFileHandleEvent = ({ mainWindow }) => {
|
||||
shell.showItemInFolder(file)
|
||||
})
|
||||
|
||||
// 检查文件是否存在
|
||||
ipcMain.handle('checkFileExist', (event, filePath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.access(filePath, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 打开指定文件
|
||||
ipcMain.handle('openPath', (event, file, relativePath = '') => {
|
||||
if (!path.isAbsolute(file) && relativePath) {
|
||||
@ -301,7 +376,7 @@ export const bindFileHandleEvent = ({ mainWindow }) => {
|
||||
})
|
||||
|
||||
// 删除指定文件
|
||||
ipcMain.handle('deleteFile', (event, file) => {
|
||||
const deleteFile = async (event, file) => {
|
||||
let res = ''
|
||||
let id = Object.keys(idToFilePath).find(item => {
|
||||
return idToFilePath[item] === file
|
||||
@ -316,11 +391,31 @@ export const bindFileHandleEvent = ({ mainWindow }) => {
|
||||
try {
|
||||
fs.rmSync(file)
|
||||
} catch (error) {}
|
||||
removeFileInRecent(file)
|
||||
await removeFileInRecent(file)
|
||||
} else {
|
||||
res = '该文件正在编辑,请关闭后再试'
|
||||
}
|
||||
return res
|
||||
}
|
||||
ipcMain.handle('deleteFile', deleteFile)
|
||||
|
||||
// 删除指定的多个文件
|
||||
ipcMain.handle('deleteMultiFile', (event, fileList) => {
|
||||
return new Promise(resolve => {
|
||||
const total = fileList.length
|
||||
let count = 0
|
||||
const failList = []
|
||||
fileList.forEach(item => {
|
||||
const error = deleteFile(null, item)
|
||||
count++
|
||||
if (error) {
|
||||
failList.push(item)
|
||||
}
|
||||
if (count >= total) {
|
||||
resolve(failList)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 复制文件
|
||||
|
||||
@ -12,19 +12,24 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
create: id => ipcRenderer.send('create', id),
|
||||
getFileContent: id => ipcRenderer.invoke('getFileContent', id),
|
||||
getFilePath: id => ipcRenderer.invoke('getFilePath', id),
|
||||
save: (id, data, fileName) => ipcRenderer.invoke('save', id, data, fileName),
|
||||
save: (id, data, fileName, defaultPath) =>
|
||||
ipcRenderer.invoke('save', id, data, fileName, defaultPath),
|
||||
rename: (id, name) => ipcRenderer.invoke('rename', id, name),
|
||||
openUrl: url => ipcRenderer.send('openUrl', url),
|
||||
addRecentFileList: fileList =>
|
||||
ipcRenderer.invoke('addRecentFileList', fileList),
|
||||
getRecentFileList: () => ipcRenderer.invoke('getRecentFileList'),
|
||||
clearRecentFileList: () => ipcRenderer.invoke('clearRecentFileList'),
|
||||
removeFileInRecent: file => ipcRenderer.invoke('removeFileInRecent', file),
|
||||
removeMultiFileInRecent: fileList =>
|
||||
ipcRenderer.invoke('removeMultiFileInRecent', fileList),
|
||||
openFileInDir: file => ipcRenderer.invoke('openFileInDir', file),
|
||||
deleteFile: file => ipcRenderer.invoke('deleteFile', file),
|
||||
deleteMultiFile: fileList => ipcRenderer.invoke('deleteMultiFile', fileList),
|
||||
onRefreshRecentFileList: callback =>
|
||||
ipcRenderer.on('refreshRecentFileList', callback),
|
||||
openFile: file => ipcRenderer.invoke('openFile', file),
|
||||
selectOpenFile: () => ipcRenderer.send('selectOpenFile'),
|
||||
selectOpenFile: () => ipcRenderer.invoke('selectOpenFile'),
|
||||
copyFile: file => ipcRenderer.invoke('copyFile', file),
|
||||
selectFile: (openDirectory, relativePath) =>
|
||||
ipcRenderer.invoke('selectFile', openDirectory, relativePath),
|
||||
@ -32,5 +37,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
ipcRenderer.invoke('openPath', path, relativePath),
|
||||
saveClientConfig: config => ipcRenderer.invoke('saveClientConfig', config),
|
||||
getClientConfig: () => ipcRenderer.invoke('getClientConfig'),
|
||||
getIsMaximize: id => ipcRenderer.invoke('getIsMaximize', id)
|
||||
getIsMaximize: id => ipcRenderer.invoke('getIsMaximize', id),
|
||||
selectOpenFolder: () => ipcRenderer.invoke('selectOpenFolder'),
|
||||
getFilesInDir: (dir, ext) => ipcRenderer.invoke('getFilesInDir', dir, ext),
|
||||
checkFileExist: filePath => ipcRenderer.invoke('checkFileExist', filePath)
|
||||
})
|
||||
|
||||
@ -89,6 +89,23 @@ export const removeFileInRecent = file => {
|
||||
})
|
||||
}
|
||||
|
||||
// 从最近文件列表中移除指定的多个文件
|
||||
export const removeMultiFileInRecent = fileList => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let list = getRecent()
|
||||
list = list.filter(item => {
|
||||
return !fileList.includes(item)
|
||||
})
|
||||
storage.set(RECENT_FILE_LIST, list, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 替换指定文件
|
||||
export const replaceFileInRecent = (oldFile, newFile) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@ -61,6 +61,7 @@ export default {
|
||||
'Enable real-time rendering effect for text editing',
|
||||
isShowScrollbar: 'Is show scrollbar',
|
||||
isUseHandDrawnLikeStyle: 'Is use hand drawn like style',
|
||||
isUseMomentum: 'Is open drag momentum',
|
||||
watermark: 'Watermark',
|
||||
showWatermark: 'Is show watermark',
|
||||
onlyExport: 'Only export',
|
||||
@ -84,7 +85,8 @@ export default {
|
||||
changeRichTextTip3: 'Do you want to switch to non rich text mode?',
|
||||
enableDragImport: 'Is it allowed to directly drag and drop files to the page for import',
|
||||
imgTextMargin: 'Node image and text margin',
|
||||
textContentMargin: 'Node contents margin'
|
||||
textContentMargin: 'Node contents margin',
|
||||
enableInheritAncestorLineStyle: 'Node connection style inherits the style of ancestor nodes',
|
||||
},
|
||||
color: {
|
||||
moreColor: 'More color'
|
||||
@ -133,7 +135,8 @@ export default {
|
||||
expandNodeChild: 'Expand all sub nodes',
|
||||
unExpandNodeChild: 'Un expand all sub nodes',
|
||||
addToDo: 'Add toDo',
|
||||
removeToDo: 'Remove toDo'
|
||||
removeToDo: 'Remove toDo',
|
||||
aiCreate: 'AI Continuation'
|
||||
},
|
||||
count: {
|
||||
words: 'Words',
|
||||
@ -153,11 +156,7 @@ export default {
|
||||
svgFile: 'svg file',
|
||||
pdfFile: 'pdf file',
|
||||
markdownFile: 'markdown file',
|
||||
tips: 'tips: .smm and .json file can be import',
|
||||
isTransparent: 'Background is transparent',
|
||||
pngTips:
|
||||
'tips: Exporting pictures in rich text mode is time-consuming. It is recommended to export to svg format',
|
||||
svgTips: 'tips: Exporting pictures in rich text mode is time-consuming',
|
||||
transformingDomToImages: 'Converting nodes: ',
|
||||
notifyTitle: 'Info',
|
||||
notifyMessage:
|
||||
@ -167,7 +166,9 @@ export default {
|
||||
useMultiPageExport: 'Export multi page',
|
||||
defaultFileName: 'Mind map',
|
||||
addFooterTextPlaceholder: 'For example: From simple-mind-map',
|
||||
addFooterText: 'Add text at the footer'
|
||||
addFooterText: 'Add text at the footer',
|
||||
desc: 'Desc',
|
||||
options: 'Options'
|
||||
},
|
||||
fullscreen: {
|
||||
fullscreenShow: 'Full screen show',
|
||||
@ -218,7 +219,9 @@ export default {
|
||||
},
|
||||
outline: {
|
||||
title: 'Outline',
|
||||
nodeDefaultText: 'Branch node'
|
||||
nodeDefaultText: 'Branch node',
|
||||
print: 'Print',
|
||||
fullscreen: 'Fullscreen'
|
||||
},
|
||||
scale: {
|
||||
zoomIn: 'Zoom in',
|
||||
@ -331,7 +334,8 @@ export default {
|
||||
openFileTip:
|
||||
'Please export the currently edited file before opening it, Beware of content loss',
|
||||
isRelative: 'Relative path',
|
||||
selectFolder: 'Select folder'
|
||||
selectFolder: 'Select folder',
|
||||
ai: 'AI'
|
||||
},
|
||||
edit: {
|
||||
newFeatureNoticeTitle: 'New feature reminder',
|
||||
@ -424,5 +428,58 @@ export default {
|
||||
nodeTagStyle: {
|
||||
placeholder: 'Please enter the tag content',
|
||||
delete: 'Delete this tag'
|
||||
},
|
||||
ai: {
|
||||
chatTitle: 'AI dialogue',
|
||||
clearRecords: 'Clear records',
|
||||
connectFailedTitle: 'Client connection failure prompt',
|
||||
connectFailedTip: 'Client connection failed, please check:',
|
||||
connectFailedCheckTip1:
|
||||
'1. Have you installed the mind mapping client? If not, please click here to install:',
|
||||
connectFailedCheckTip2: '2. If the client is installed, please confirm if the client is opened.',
|
||||
connectFailedCheckTip3:
|
||||
'If it has already been installed and started, you can try closing and restarting it.',
|
||||
connectFailedCheckTip4: 'After completing the above steps, you can click on:',
|
||||
baiduNetdisk: 'Baidu Netdisk',
|
||||
createMindMapTitle: 'One click generation of mind maps',
|
||||
createTip:
|
||||
'Please enter a theme, and AI will generate a mind map based on your theme, such as: Hangzhou weekend travel plan.',
|
||||
importantTip: 'Important note: One click generation will overwrite existing data. It is recommended to export the current data first.',
|
||||
wantModifyAiConfigTip: 'Do you want to modify the AI configuration? Please click on:',
|
||||
modifyAIConfiguration: 'Modify AI configuration',
|
||||
chatInputPlaceholder: 'Enter to send, Shift+Enter to wrap.',
|
||||
send: 'Send',
|
||||
stopGenerating: 'Stop generating',
|
||||
generationFailed: 'Generation failed',
|
||||
aiGenerationSuccess: 'AI generation completed',
|
||||
stoppedGenerating: 'Stopped generating',
|
||||
AIConfiguration: 'AI configuration',
|
||||
VolcanoArkLargeModelConfiguration: 'Volcano Ark Large Model Configuration:',
|
||||
configTip: 'At present, only the Volcano Ark model is supported, and you need to obtain the key yourself. For detailed operation steps, please refer to:',
|
||||
course: 'Course',
|
||||
inferenceAccessPoint: 'Inference access point',
|
||||
mindMappingClientConfiguration: 'Mind mapping client configuration:',
|
||||
port: 'Port',
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Confirm',
|
||||
close: 'Close',
|
||||
configSaveSuccessTip: 'Configuration saved successfully',
|
||||
apiValidateTip: 'Please enter the interface',
|
||||
keyValidateTip: 'Please enter the API Key',
|
||||
modelValidateTip: 'Please enter the inference access point',
|
||||
portValidateTip: 'Please enter the port',
|
||||
methodValidateTip: 'Please select the request method',
|
||||
noInputTip: 'Please enter the content',
|
||||
connectSuccessful: 'Connection successful',
|
||||
connectFailed: 'connection failed',
|
||||
connectionDetection: 'Connection detection',
|
||||
configurationMissing: 'Configuration missing',
|
||||
aiCreateMsgPrefix: 'Help me write one【',
|
||||
aiCreateMsgPostfix:
|
||||
'】. It needs to be returned in Markdown format and can only use two syntax: Markdown title and unordered list. It can support multiple layers of nesting. Just return the content.',
|
||||
aiCreatePartMsgPrefix: 'I have a theme for【',
|
||||
aiCreatePartMsgCenter: '】Can you help me continue writing one of the contents of the mind map【',
|
||||
aiCreatePartMsgPostfix:
|
||||
'】The subordinate content of the node needs to be returned in Markdown format and can only use two syntax: Markdown title and unordered list. It can support multi-level nesting. Just return the content.'
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,6 +59,7 @@ export default {
|
||||
openRealtimeRenderOnNodeTextEdit: '开启文本编辑实时渲染效果',
|
||||
isShowScrollbar: '是否显示滚动条',
|
||||
isUseHandDrawnLikeStyle: '是否开启手绘风格',
|
||||
isUseMomentum: '是否开启拖动画布的动量效果',
|
||||
watermark: '水印',
|
||||
showWatermark: '是否显示水印',
|
||||
watermarkDefaultText: '水印文字',
|
||||
@ -75,9 +76,11 @@ export default {
|
||||
tagPositionBottom: '文本下面',
|
||||
alwaysShowExpandBtn: '是否一直显示展开收起按钮',
|
||||
enableAutoEnterTextEditWhenKeydown: '键盘输入时自动进入文本编辑',
|
||||
enableInheritAncestorLineStyle: '节点连线样式继承祖先节点的样式',
|
||||
confirm: '确定',
|
||||
cancel: '取消',
|
||||
changeRichTextTip: '该操作会清空所有历史修改记录,并且修改思维导图数据,是否继续?',
|
||||
changeRichTextTip:
|
||||
'该操作会清空所有历史修改记录,并且修改思维导图数据,是否继续?',
|
||||
changeRichTextTip2: '是否切换为富文本模式?',
|
||||
changeRichTextTip3: '是否切换为非富文本模式?',
|
||||
enableDragImport: '是否允许直接拖拽文件到页面进行导入',
|
||||
@ -131,7 +134,8 @@ export default {
|
||||
expandNodeChild: '展开所有下级节点',
|
||||
unExpandNodeChild: '收起所有下级节点',
|
||||
addToDo: '添加待办',
|
||||
removeToDo: '删除待办'
|
||||
removeToDo: '删除待办',
|
||||
aiCreate: 'AI续写'
|
||||
},
|
||||
count: {
|
||||
words: '字数',
|
||||
@ -151,10 +155,7 @@ export default {
|
||||
svgFile: 'svg文件',
|
||||
pdfFile: 'pdf文件',
|
||||
markdownFile: 'markdown文件',
|
||||
tips: 'tips:.smm和.json文件可用于导入',
|
||||
isTransparent: '背景是否透明',
|
||||
pngTips: 'tips:富文本模式导出图片非常耗时,建议导出为svg格式',
|
||||
svgTips: 'tips:富文本模式导出图片非常耗时',
|
||||
transformingDomToImages: '正在转换节点:',
|
||||
notifyTitle: '消息',
|
||||
notifyMessage: '如果没有触发下载,请检查是否被浏览器拦截了',
|
||||
@ -163,7 +164,9 @@ export default {
|
||||
useMultiPageExport: '是否多页导出',
|
||||
defaultFileName: '思维导图',
|
||||
addFooterText: '底部添加文字',
|
||||
addFooterTextPlaceholder: '比如:来自simple-mind-map'
|
||||
addFooterTextPlaceholder: '比如:来自simple-mind-map',
|
||||
desc: '说明',
|
||||
options: '选项'
|
||||
},
|
||||
fullscreen: {
|
||||
fullscreenShow: '全屏查看',
|
||||
@ -214,7 +217,9 @@ export default {
|
||||
},
|
||||
outline: {
|
||||
title: '大纲',
|
||||
nodeDefaultText: '分支节点'
|
||||
nodeDefaultText: '分支节点',
|
||||
print: '打印',
|
||||
fullscreen: '全屏'
|
||||
},
|
||||
scale: {
|
||||
zoomIn: '放大',
|
||||
@ -269,7 +274,7 @@ export default {
|
||||
bottom: '下',
|
||||
left: '左',
|
||||
right: '右',
|
||||
tag: '标签',
|
||||
tag: '标签'
|
||||
},
|
||||
theme: {
|
||||
title: '主题',
|
||||
@ -323,7 +328,8 @@ export default {
|
||||
newFileTip: '新建文件前请先导出当前编辑的文件,谨防内容丢失',
|
||||
openFileTip: '打开文件前请先导出当前编辑的文件,谨防内容丢失',
|
||||
isRelative: '相对路径',
|
||||
selectFolder: '选择文件夹'
|
||||
selectFolder: '选择文件夹',
|
||||
ai: 'AI'
|
||||
},
|
||||
edit: {
|
||||
newFeatureNoticeTitle: '新特性提醒',
|
||||
@ -414,5 +420,58 @@ export default {
|
||||
nodeTagStyle: {
|
||||
placeholder: '请输入标签内容',
|
||||
delete: '删除此标签'
|
||||
},
|
||||
ai: {
|
||||
chatTitle: 'AI对话',
|
||||
clearRecords: '清空记录',
|
||||
connectFailedTitle: '客户端连接失败提示',
|
||||
connectFailedTip: '客户端连接失败,请检查:',
|
||||
connectFailedCheckTip1:
|
||||
'1.是否安装了思绪思维导图客户端,如果没有请点此安装:',
|
||||
connectFailedCheckTip2: '2.如果安装了客户端,请确认是否打开了客户端。',
|
||||
connectFailedCheckTip3:
|
||||
'3.如果已经安装并启动了,那么可以尝试关闭然后重新启动。',
|
||||
connectFailedCheckTip4: '完成以上步骤后可点击:',
|
||||
baiduNetdisk: '百度网盘',
|
||||
createMindMapTitle: '一键生成思维导图',
|
||||
createTip:
|
||||
'请输入一个主题,AI会根据你的主题生成思维导图,如:杭州周末出游计划。',
|
||||
importantTip: '重要提示:一键生成会覆盖现有数据,建议先导出当前数据。',
|
||||
wantModifyAiConfigTip: '想要修改AI配置?请点击:',
|
||||
modifyAIConfiguration: '修改AI配置',
|
||||
chatInputPlaceholder: 'Enter 发送,Shift + Enter 换行。',
|
||||
send: '发送',
|
||||
stopGenerating: '停止生成',
|
||||
generationFailed: '生成失败',
|
||||
aiGenerationSuccess: 'AI生成完成',
|
||||
stoppedGenerating: '已停止生成',
|
||||
AIConfiguration: 'AI配置',
|
||||
VolcanoArkLargeModelConfiguration: '火山方舟大模型配置:',
|
||||
configTip: '目前仅支持火山方舟大模型,需要自行去获取key,详细操作步骤见:',
|
||||
course: '教程',
|
||||
inferenceAccessPoint: '推理接入点',
|
||||
mindMappingClientConfiguration: '思绪思维导图客户端配置:',
|
||||
port: '端口',
|
||||
cancel: '取消',
|
||||
confirm: '确认',
|
||||
close: '关闭',
|
||||
configSaveSuccessTip: '配置保存成功',
|
||||
apiValidateTip: '请输入接口',
|
||||
keyValidateTip: '请输入API Key',
|
||||
modelValidateTip: '请输入推理接入点',
|
||||
portValidateTip: '请输入端口',
|
||||
methodValidateTip: '请选择请求方式',
|
||||
noInputTip: '请输入内容',
|
||||
connectSuccessful: '连接成功',
|
||||
connectFailed: '连接失败',
|
||||
connectionDetection: '连接检测',
|
||||
configurationMissing: '配置缺失',
|
||||
aiCreateMsgPrefix: '帮我写一个【',
|
||||
aiCreateMsgPostfix:
|
||||
'】,需要以Markdown格式返回,并且只能使用Markdown的标题和无序列表两种语法,可以支持多层嵌套。只需返回内容即可。',
|
||||
aiCreatePartMsgPrefix: '我有一个主题为【',
|
||||
aiCreatePartMsgCenter: '】的思维导图,帮我续写其中一个内容为【',
|
||||
aiCreatePartMsgPostfix:
|
||||
'】的节点的下级内容,需要以Markdown格式返回,并且只能使用Markdown的标题和无序列表两种语法,可以支持多层嵌套。只需返回内容即可。'
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,9 +42,7 @@ export default {
|
||||
notUseRainbowLines: '不使用彩虹線條',
|
||||
outerFramePadding: '外框內距',
|
||||
tagPositionRight: '文本右側',
|
||||
tagPositionBottom: '文本下面',
|
||||
alwaysShowExpandBtn: '是否壹直顯示展開收起按鈕',
|
||||
enableAutoEnterTextEditWhenKeydown: '鍵盤輸入時自動進入文本編輯'
|
||||
tagPositionBottom: '文本下面'
|
||||
},
|
||||
setting: {
|
||||
title: '設置',
|
||||
@ -64,6 +62,7 @@ export default {
|
||||
openRealtimeRenderOnNodeTextEdit: '開啟文本編輯實時渲染效果',
|
||||
isShowScrollbar: '顯示捲軸',
|
||||
isUseHandDrawnLikeStyle: '使用手繪風格',
|
||||
isUseMomentum: '是否開啓拖動畫布的動量效果',
|
||||
watermark: '浮水印',
|
||||
showWatermark: '顯示浮水印',
|
||||
onlyExport: '僅在匯出時顯示',
|
||||
@ -78,12 +77,16 @@ export default {
|
||||
belowNode: '顯示在節點下方',
|
||||
confirm: '確定',
|
||||
cancel: '取消',
|
||||
changeRichTextTip: '該操作會清空所有曆史修改記錄,並且修改思維導圖數據,是否繼續?',
|
||||
changeRichTextTip:
|
||||
'該操作會清空所有曆史修改記錄,並且修改思維導圖數據,是否繼續?',
|
||||
changeRichTextTip2: '是否切換爲富文本模式?',
|
||||
changeRichTextTip3: '是否切換爲非富文本模式?',
|
||||
enableDragImport: '是否允許直接拖拽文件到頁面進行導入',
|
||||
imgTextMargin: '節點圖片和文本間隔',
|
||||
textContentMargin: '節點各種內容間隔'
|
||||
textContentMargin: '節點各種內容間隔',
|
||||
enableAutoEnterTextEditWhenKeydown: '鍵盤輸入時自動進入文本編輯',
|
||||
enableInheritAncestorLineStyle: '節點連線樣式繼承祖先節點的樣式',
|
||||
alwaysShowExpandBtn: '是否壹直顯示展開收起按鈕'
|
||||
},
|
||||
color: {
|
||||
moreColor: '更多顏色'
|
||||
@ -131,7 +134,8 @@ export default {
|
||||
expandNodeChild: '展開所有下級節點',
|
||||
unExpandNodeChild: '收起所有下級節點',
|
||||
addToDo: '添加待辦',
|
||||
removeToDo: '刪除待辦'
|
||||
removeToDo: '刪除待辦',
|
||||
aiCreate: 'AI續寫'
|
||||
},
|
||||
count: {
|
||||
words: '字數',
|
||||
@ -151,10 +155,7 @@ export default {
|
||||
svgFile: 'SVG 檔案',
|
||||
pdfFile: 'PDF 檔案',
|
||||
markdownFile: 'Markdown 檔案',
|
||||
tips: '提示:.smm 和 .json 檔案可以匯入',
|
||||
isTransparent: '背景透明',
|
||||
pngTips: '提示:在豐富文字模式下匯出圖片非常耗時,建議匯出為 SVG 格式',
|
||||
svgTips: '提示:在豐富文字模式下匯出圖片非常耗時',
|
||||
transformingDomToImages: '正在轉換節點:',
|
||||
notifyTitle: '訊息',
|
||||
notifyMessage: '如果沒有觸發下載,請檢查是否被瀏覽器封鎖',
|
||||
@ -163,7 +164,9 @@ export default {
|
||||
useMultiPageExport: '多頁匯出',
|
||||
defaultFileName: '心智圖',
|
||||
addFooterText: '在底部新增文字',
|
||||
addFooterTextPlaceholder: '例如:來自 simple-mind-map'
|
||||
addFooterTextPlaceholder: '例如:來自 simple-mind-map',
|
||||
desc: '說明',
|
||||
options: '選項'
|
||||
},
|
||||
fullscreen: {
|
||||
fullscreenShow: '全螢幕檢視',
|
||||
@ -214,7 +217,9 @@ export default {
|
||||
},
|
||||
outline: {
|
||||
title: '大綱',
|
||||
nodeDefaultText: '分支節點'
|
||||
nodeDefaultText: '分支節點',
|
||||
print: '打印',
|
||||
fullscreen: '全屏'
|
||||
},
|
||||
scale: {
|
||||
zoomIn: '放大',
|
||||
@ -268,7 +273,7 @@ export default {
|
||||
bottom: '下',
|
||||
left: '左',
|
||||
right: '右',
|
||||
tag: '標簽',
|
||||
tag: '標簽'
|
||||
},
|
||||
theme: {
|
||||
title: '主題',
|
||||
@ -319,7 +324,8 @@ export default {
|
||||
creatingTip: '正在建立檔案',
|
||||
directory: '目錄',
|
||||
newFileTip: '新增檔案前,請先匯出目前編輯的檔案,以免內容遺失',
|
||||
openFileTip: '開啟檔案前,請先匯出目前編輯的檔案,以免內容遺失'
|
||||
openFileTip: '開啟檔案前,請先匯出目前編輯的檔案,以免內容遺失',
|
||||
ai: 'AI'
|
||||
},
|
||||
edit: {
|
||||
newFeatureNoticeTitle: '新功能提醒',
|
||||
@ -407,5 +413,58 @@ export default {
|
||||
nodeTagStyle: {
|
||||
placeholder: '請輸入標籤內容',
|
||||
delete: '刪除此標籤'
|
||||
},
|
||||
ai: {
|
||||
chatTitle: 'AI對話',
|
||||
clearRecords: '清空記錄',
|
||||
connectFailedTitle: '客戶端連接失敗提示',
|
||||
connectFailedTip: '客戶端連接失敗,請檢查:',
|
||||
connectFailedCheckTip1:
|
||||
'1.是否安裝了思緒思維導圖客戶端,如果沒有請點此安裝:',
|
||||
connectFailedCheckTip2: '2.如果安裝了客戶端,請確認是否打開了客戶端。',
|
||||
connectFailedCheckTip3:
|
||||
'3.如果已經安裝並啓動了,那麽可以嘗試關閉然後重新啓動。',
|
||||
connectFailedCheckTip4: '完成以上步驟後可點擊:',
|
||||
baiduNetdisk: '百度網盤',
|
||||
createMindMapTitle: '一鍵生成思維導圖',
|
||||
createTip:
|
||||
'請輸入一個主題,AI會根據你的主題生成思維導圖,如:杭州周末出遊計劃。',
|
||||
importantTip: '重要提示:一鍵生成會覆蓋現有數據,建議先導出當前數據。',
|
||||
wantModifyAiConfigTip: '想要修改AI配置?請點擊:',
|
||||
modifyAIConfiguration: '修改AI配置',
|
||||
chatInputPlaceholder: 'Enter 發送,Shift Enter 換行。',
|
||||
send: '發送',
|
||||
stopGenerating: '停止生成',
|
||||
generationFailed: '生成失敗',
|
||||
aiGenerationSuccess: 'AI生成完成',
|
||||
stoppedGenerating: '已停止生成',
|
||||
AIConfiguration: 'AI配置',
|
||||
VolcanoArkLargeModelConfiguration: '火山方舟大模型配置:',
|
||||
configTip: '目前僅支持火山方舟大模型,需要自行去獲取key,詳細操作步驟見:',
|
||||
course: '教程',
|
||||
inferenceAccessPoint: '推理接入點',
|
||||
mindMappingClientConfiguration: '思緒思維導圖客戶端配置:',
|
||||
port: '端口',
|
||||
cancel: '取消',
|
||||
confirm: '確認',
|
||||
close: '關閉',
|
||||
configSaveSuccessTip: '配置保存成功',
|
||||
apiValidateTip: '請輸入接口',
|
||||
keyValidateTip: '請輸入API Key',
|
||||
modelValidateTip: '請輸入推理接入點',
|
||||
portValidateTip: '請輸入端口',
|
||||
methodValidateTip: '請選擇請求方式',
|
||||
noInputTip: '請輸入內容',
|
||||
connectSuccessful: '連接成功',
|
||||
connectFailed: '連接失敗',
|
||||
connectionDetection: '連接檢測',
|
||||
configurationMissing: '配置缺失',
|
||||
aiCreateMsgPrefix: '幫我寫一個【',
|
||||
aiCreateMsgPostfix:
|
||||
'】,需要以Markdown格式返回,並且只能使用Markdown的標題和無序列表兩種語法,可以支持多層嵌套。只需返回內容即可。',
|
||||
aiCreatePartMsgPrefix: '我有一個主題爲【',
|
||||
aiCreatePartMsgCenter: '】的思維導圖,幫我續寫其中一個內容爲【',
|
||||
aiCreatePartMsgPostfix:
|
||||
'】的節點的下級內容,需要以Markdown格式返回,並且只能使用Markdown的標題和無序列表兩種語法,可以支持多層嵌套。只需返回內容即可。'
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,4 +46,3 @@ if (window.takeOverApp) {
|
||||
} else {
|
||||
initApp()
|
||||
}
|
||||
|
||||
|
||||
340
web/src/pages/Edit/components/AiChat.vue
Normal file
340
web/src/pages/Edit/components/AiChat.vue
Normal file
@ -0,0 +1,340 @@
|
||||
<template>
|
||||
<Sidebar ref="sidebar" :title="$t('ai.chatTitle')">
|
||||
<div class="aiChatBox" :class="{ isDark: isDark }">
|
||||
<div class="chatHeader">
|
||||
<el-button size="mini" @click="clear">
|
||||
<span class="el-icon-delete"></span>
|
||||
{{ $t('ai.clearRecords') }}
|
||||
</el-button>
|
||||
<el-button size="mini" @click="modifyAiConfig">
|
||||
<span class="el-icon-edit"></span>
|
||||
{{ $t('ai.modifyAIConfiguration') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="chatResBox customScrollbar" ref="chatResBoxRef">
|
||||
<div
|
||||
class="chatItem"
|
||||
v-for="item in chatList"
|
||||
:key="item.id"
|
||||
:class="[item.type]"
|
||||
>
|
||||
<div class="chatItemInner" v-if="item.type === 'user'">
|
||||
<div class="avatar">
|
||||
<span class="icon el-icon-user"></span>
|
||||
</div>
|
||||
<div class="content">{{ item.content }}</div>
|
||||
</div>
|
||||
<div class="chatItemInner" v-else-if="item.type === 'ai'">
|
||||
<div class="avatar">
|
||||
<span class="icon iconfont iconAIshengcheng"></span>
|
||||
</div>
|
||||
<div class="content" v-html="item.content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chatInputBox">
|
||||
<textarea
|
||||
v-model="text"
|
||||
class="customScrollbar"
|
||||
:placeholder="$t('ai.chatInputPlaceholder')"
|
||||
@keydown="onKeydown"
|
||||
></textarea>
|
||||
<el-button class="btn" size="mini" @click="send" :loading="isCreating">
|
||||
{{ $t('ai.send') }}
|
||||
<span class="el-icon-position"></span>
|
||||
</el-button>
|
||||
<el-button
|
||||
class="stop"
|
||||
size="mini"
|
||||
type="warning"
|
||||
@click="stop"
|
||||
v-show="isCreating"
|
||||
>
|
||||
{{ $t('ai.stopGenerating') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</Sidebar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sidebar from './Sidebar'
|
||||
import { mapState } from 'vuex'
|
||||
import { createUid } from 'simple-mind-map/src/utils'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
|
||||
let md = null
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Sidebar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
text: '',
|
||||
chatList: [],
|
||||
isCreating: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isDark: state => state.localConfig.isDark,
|
||||
activeSidebar: state => state.activeSidebar
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
activeSidebar(val) {
|
||||
if (val === 'ai') {
|
||||
this.$refs.sidebar.show = true
|
||||
} else {
|
||||
this.$refs.sidebar.show = false
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
beforeDestroy() {},
|
||||
methods: {
|
||||
onKeydown(e) {
|
||||
if (e.keyCode === 13) {
|
||||
if (!e.shiftKey) {
|
||||
e.preventDefault()
|
||||
this.send()
|
||||
} else {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
send() {
|
||||
if (this.isCreating) return
|
||||
const text = this.text.trim()
|
||||
if (!text) {
|
||||
return
|
||||
}
|
||||
this.text = ''
|
||||
const historyUserMsgList = this.chatList
|
||||
.filter(item => {
|
||||
return item.type === 'user'
|
||||
})
|
||||
.map(item => {
|
||||
return item.content
|
||||
})
|
||||
this.chatList.push({
|
||||
id: createUid(),
|
||||
type: 'user',
|
||||
content: text
|
||||
})
|
||||
this.chatList.push({
|
||||
id: createUid(),
|
||||
type: 'ai',
|
||||
content: ''
|
||||
})
|
||||
this.isCreating = true
|
||||
const textList = [...historyUserMsgList, text]
|
||||
this.$bus.$emit(
|
||||
'ai_chat',
|
||||
textList,
|
||||
res => {
|
||||
if (!md) {
|
||||
md = new MarkdownIt()
|
||||
}
|
||||
this.chatList[this.chatList.length - 1].content = md.render(res)
|
||||
this.$refs.chatResBoxRef.scrollTop = this.$refs.chatResBoxRef.scrollHeight
|
||||
},
|
||||
() => {
|
||||
this.isCreating = false
|
||||
},
|
||||
() => {
|
||||
this.isCreating = false
|
||||
this.$message.error(this.$t('ai.generationFailed'))
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
stop() {
|
||||
this.$bus.$emit('ai_chat_stop')
|
||||
this.isCreating = false
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.chatList = []
|
||||
},
|
||||
|
||||
modifyAiConfig() {
|
||||
this.$bus.$emit('showAiConfigDialog')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.aiChatBox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.isDark {
|
||||
}
|
||||
|
||||
.chatHeader {
|
||||
height: 50px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.chatResBox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
margin: 12px 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
.chatItem {
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.ai {
|
||||
border-color: #409eff;
|
||||
|
||||
.chatItemInner {
|
||||
.avatar {
|
||||
border-color: #409eff;
|
||||
left: -12px;
|
||||
top: -12px;
|
||||
|
||||
.icon {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.user {
|
||||
border-color: #f56c6c;
|
||||
|
||||
.chatItemInner {
|
||||
.avatar {
|
||||
border-color: #f56c6c;
|
||||
right: -12px;
|
||||
top: -12px;
|
||||
|
||||
.icon {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chatItemInner {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
||||
.avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
|
||||
.icon {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .content {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
color: #3f4a54;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
|
||||
p {
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
white-space: break-spaces;
|
||||
background-color: rgba(175, 184, 193, 0.2);
|
||||
border-radius: 6px;
|
||||
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
|
||||
Liberation Mono, monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 12px;
|
||||
background-color: rgba(175, 184, 193, 0.2);
|
||||
|
||||
code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chatInputBox {
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
position: relative;
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
}
|
||||
|
||||
.stop {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
top: -30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
186
web/src/pages/Edit/components/AiConfigDialog.vue
Normal file
186
web/src/pages/Edit/components/AiConfigDialog.vue
Normal file
@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="aiConfigDialog"
|
||||
:title="$t('ai.AIConfiguration')"
|
||||
:visible.sync="aiConfigDialogVisible"
|
||||
width="550px"
|
||||
append-to-body
|
||||
>
|
||||
<div class="aiConfigBox">
|
||||
<el-form
|
||||
:model="ruleForm"
|
||||
:rules="rules"
|
||||
ref="ruleFormRef"
|
||||
label-width="100px"
|
||||
>
|
||||
<p class="title">{{ $t('ai.VolcanoArkLargeModelConfiguration') }}</p>
|
||||
<p class="desc">
|
||||
{{ $t('ai.configTip') }}<a href="">{{ $t('ai.course') }}</a
|
||||
>。
|
||||
</p>
|
||||
<el-form-item label="API Key" prop="key">
|
||||
<el-input v-model="ruleForm.key"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('ai.inferenceAccessPoint')" prop="model">
|
||||
<el-input v-model="ruleForm.model"></el-input>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="接口" prop="api">
|
||||
<el-input v-model="ruleForm.api"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="请求方式" prop="method">
|
||||
<el-select v-model="ruleForm.method" placeholder="请选择">
|
||||
<el-option key="POST" label="POST" value="POST"></el-option>
|
||||
<el-option key="GET" label="GET" value="GET"></el-option>
|
||||
</el-select>
|
||||
</el-form-item> -->
|
||||
<p class="title">{{ $t('ai.mindMappingClientConfiguration') }}</p>
|
||||
<el-form-item :label="$t('ai.port')" prop="port">
|
||||
<el-input v-model="ruleForm.port"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="cancel">{{ $t('ai.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="confirm">{{
|
||||
$t('ai.confirm')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
model: {
|
||||
prop: 'visible',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
aiConfigDialogVisible: false,
|
||||
ruleForm: {
|
||||
api: '',
|
||||
key: '',
|
||||
model: '',
|
||||
port: '',
|
||||
method: ''
|
||||
},
|
||||
rules: {
|
||||
api: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('ai.apiValidateTip'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('ai.keyValidateTip'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
model: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('ai.modelValidateTip'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
port: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('ai.portValidateTip'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
method: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('ai.methodValidateTip'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['aiConfig'])
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
this.aiConfigDialogVisible = val
|
||||
},
|
||||
aiConfigDialogVisible(val, oldVal) {
|
||||
if (!val && oldVal) {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initFormData()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setLocalConfig']),
|
||||
|
||||
close() {
|
||||
this.$emit('change', false)
|
||||
},
|
||||
|
||||
initFormData() {
|
||||
Object.keys(this.aiConfig).forEach(key => {
|
||||
this.ruleForm[key] = this.aiConfig[key]
|
||||
})
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.close()
|
||||
this.initFormData()
|
||||
},
|
||||
|
||||
confirm() {
|
||||
this.$refs.ruleFormRef.validate(valid => {
|
||||
if (valid) {
|
||||
this.close()
|
||||
this.setLocalConfig({
|
||||
...this.ruleForm
|
||||
})
|
||||
this.$message.success(this.$t('ai.configSaveSuccessTip'))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.aiConfigDialog {
|
||||
/deep/ .el-dialog__body {
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
.aiConfigBox {
|
||||
a {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-bottom: 12px;
|
||||
padding-left: 12px;
|
||||
border-left: 5px solid #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
587
web/src/pages/Edit/components/AiCreate.vue
Normal file
587
web/src/pages/Edit/components/AiCreate.vue
Normal file
@ -0,0 +1,587 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 客户端连接失败提示弹窗 -->
|
||||
<el-dialog
|
||||
class="clientTipDialog"
|
||||
:title="$t('ai.connectFailedTitle')"
|
||||
:visible.sync="clientTipDialogVisible"
|
||||
width="400px"
|
||||
append-to-body
|
||||
>
|
||||
<div class="tipBox">
|
||||
<p>{{ $t('ai.connectFailedTip') }}</p>
|
||||
<p>
|
||||
{{ $t('ai.connectFailedCheckTip1')
|
||||
}}<a
|
||||
href="https://pan.baidu.com/s/1huasEbKsGNH2Af68dvWiOg?pwd=3bp3"
|
||||
>{{ $t('ai.baiduNetdisk') }}</a
|
||||
>、<a href="https://github.com/wanglin2/mind-map/releases">Github</a>
|
||||
</p>
|
||||
<p>{{ $t('ai.connectFailedCheckTip2') }}</p>
|
||||
<P>{{ $t('ai.connectFailedCheckTip3') }}</P>
|
||||
<p>
|
||||
{{ $t('ai.connectFailedCheckTip4')
|
||||
}}<el-button size="small" @click="testConnect">{{
|
||||
$t('ai.connectionDetection')
|
||||
}}</el-button>
|
||||
</p>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="clientTipDialogVisible = false">{{
|
||||
$t('ai.close')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<!-- ai内容输入弹窗 -->
|
||||
<el-dialog
|
||||
class="createDialog"
|
||||
:title="$t('ai.createMindMapTitle')"
|
||||
:visible.sync="createDialogVisible"
|
||||
width="450px"
|
||||
append-to-body
|
||||
>
|
||||
<div class="inputBox">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
:placeholder="$t('ai.createTip')"
|
||||
v-model="aiInput"
|
||||
>
|
||||
</el-input>
|
||||
<div class="tip warning">
|
||||
{{ $t('ai.importantTip') }}
|
||||
</div>
|
||||
<div class="tip">
|
||||
{{ $t('ai.wantModifyAiConfigTip')
|
||||
}}<el-button size="small" @click="showAiConfigDialog">{{
|
||||
$t('ai.modifyAIConfiguration')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="closeAiCreateDialog">{{
|
||||
$t('ai.cancel')
|
||||
}}</el-button>
|
||||
<el-button type="primary" @click="doAiCreate">{{
|
||||
$t('ai.confirm')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<!-- ai生成中添加一个透明层,防止期间用户进行操作 -->
|
||||
<div
|
||||
class="aiCreatingMask"
|
||||
ref="aiCreatingMaskRef"
|
||||
v-show="aiCreatingMaskVisible"
|
||||
>
|
||||
<el-button type="warning" class="btn" @click="stopCreate">{{
|
||||
$t('ai.stopGenerating')
|
||||
}}</el-button>
|
||||
</div>
|
||||
<AiConfigDialog v-model="aiConfigDialogVisible"></AiConfigDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Ai from '@/utils/ai'
|
||||
import { transformMarkdownTo } from 'simple-mind-map/src/parse/markdownTo'
|
||||
import {
|
||||
createUid,
|
||||
isUndef,
|
||||
checkNodeOuter,
|
||||
getStrWithBrFromHtml
|
||||
} from 'simple-mind-map/src/utils'
|
||||
import { mapState } from 'vuex'
|
||||
import AiConfigDialog from './AiConfigDialog.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AiConfigDialog
|
||||
},
|
||||
props: {
|
||||
mindMap: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
aiInstance: null,
|
||||
isAiCreating: false,
|
||||
aiCreatingContent: '',
|
||||
|
||||
isLoopRendering: false,
|
||||
uidMap: {},
|
||||
latestUid: '',
|
||||
|
||||
clientTipDialogVisible: false,
|
||||
createDialogVisible: false,
|
||||
aiInput: '',
|
||||
aiCreatingMaskVisible: false,
|
||||
aiConfigDialogVisible: false,
|
||||
|
||||
mindMapDataCache: '',
|
||||
beingAiCreateNodeUid: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['aiConfig'])
|
||||
},
|
||||
created() {
|
||||
this.$bus.$on('ai_create_all', this.aiCrateAll)
|
||||
this.$bus.$on('ai_create_part', this.aiCreatePart)
|
||||
this.$bus.$on('ai_chat', this.aiChat)
|
||||
this.$bus.$on('ai_chat_stop', this.aiChatStop)
|
||||
this.$bus.$on('showAiConfigDialog', this.showAiConfigDialog)
|
||||
},
|
||||
mounted() {
|
||||
document.body.appendChild(this.$refs.aiCreatingMaskRef)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$bus.$off('ai_create_all', this.aiCrateAll)
|
||||
this.$bus.$off('ai_create_part', this.aiCreatePart)
|
||||
this.$bus.$off('ai_chat', this.aiChat)
|
||||
this.$bus.$off('ai_chat_stop', this.aiChatStop)
|
||||
this.$bus.$off('showAiConfigDialog', this.showAiConfigDialog)
|
||||
},
|
||||
methods: {
|
||||
// 显示AI配置修改弹窗
|
||||
showAiConfigDialog() {
|
||||
this.aiConfigDialogVisible = true
|
||||
},
|
||||
|
||||
// 客户端连接检测
|
||||
async testConnect() {
|
||||
try {
|
||||
await fetch(`http://localhost:${this.aiConfig.port}/ai/test`, {
|
||||
method: 'GET'
|
||||
})
|
||||
this.$message.success(this.$t('ai.connectSuccessful'))
|
||||
this.clientTipDialogVisible = false
|
||||
this.createDialogVisible = true
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.$message.error(this.$t('ai.connectFailed'))
|
||||
}
|
||||
},
|
||||
|
||||
// 检测ai是否可用
|
||||
async aiTest() {
|
||||
// 检查配置
|
||||
if (
|
||||
!(
|
||||
this.aiConfig.api &&
|
||||
this.aiConfig.key &&
|
||||
this.aiConfig.model &&
|
||||
this.aiConfig.port
|
||||
)
|
||||
) {
|
||||
this.showAiConfigDialog()
|
||||
throw new Error(this.$t('ai.configurationMissing'))
|
||||
}
|
||||
// 检查连接
|
||||
let isConnect = false
|
||||
try {
|
||||
await fetch(`http://localhost:${this.aiConfig.port}/ai/test`, {
|
||||
method: 'GET'
|
||||
})
|
||||
isConnect = true
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.clientTipDialogVisible = true
|
||||
}
|
||||
if (!isConnect) {
|
||||
throw new Error(this.$t('ai.connectFailed'))
|
||||
}
|
||||
},
|
||||
|
||||
// AI生成整体
|
||||
async aiCrateAll() {
|
||||
try {
|
||||
await this.aiTest()
|
||||
this.createDialogVisible = true
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
|
||||
// 关闭ai内容输入弹窗
|
||||
closeAiCreateDialog() {
|
||||
this.createDialogVisible = false
|
||||
this.aiInput = ''
|
||||
},
|
||||
|
||||
// 确认生成
|
||||
doAiCreate() {
|
||||
const aiInputText = this.aiInput.trim()
|
||||
if (!aiInputText) {
|
||||
this.$message.warning(this.$t('ai.noInputTip'))
|
||||
return
|
||||
}
|
||||
this.closeAiCreateDialog()
|
||||
this.aiCreatingMaskVisible = true
|
||||
// 发起请求
|
||||
this.isAiCreating = true
|
||||
this.aiInstance = new Ai({
|
||||
port: this.aiConfig.port
|
||||
})
|
||||
this.aiInstance.init('huoshan', this.aiConfig)
|
||||
this.mindMap.renderer.setRootNodeCenter()
|
||||
this.mindMap.setData(null)
|
||||
this.aiInstance.request(
|
||||
{
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: `${this.$t(
|
||||
'ai.aiCreateMsgPrefix'
|
||||
)}${aiInputText}${this.$t('ai.aiCreateMsgPostfix')}`
|
||||
}
|
||||
]
|
||||
},
|
||||
content => {
|
||||
if (content && /\n$/.test(content)) {
|
||||
this.aiCreatingContent = content
|
||||
}
|
||||
this.loopRenderOnAiCreating()
|
||||
},
|
||||
content => {
|
||||
this.aiCreatingContent = content
|
||||
this.resetOnAiCreatingStop()
|
||||
this.$message.success(this.$t('ai.aiGenerationSuccess'))
|
||||
},
|
||||
() => {
|
||||
this.resetOnAiCreatingStop()
|
||||
this.resetOnRenderEnd()
|
||||
this.$message.error(this.$t('ai.generationFailed'))
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
// AI请求完成或出错后需要复位的数据
|
||||
resetOnAiCreatingStop() {
|
||||
this.aiCreatingMaskVisible = false
|
||||
this.isAiCreating = false
|
||||
this.aiInstance = null
|
||||
},
|
||||
|
||||
// 渲染结束后需要复位的数据
|
||||
resetOnRenderEnd() {
|
||||
this.isLoopRendering = false
|
||||
this.uidMap = {}
|
||||
this.aiCreatingContent = ''
|
||||
this.mindMapDataCache = ''
|
||||
this.beingAiCreateNodeUid = ''
|
||||
},
|
||||
|
||||
// 停止生成
|
||||
stopCreate() {
|
||||
this.aiInstance.stop()
|
||||
this.isAiCreating = false
|
||||
this.aiCreatingMaskVisible = false
|
||||
this.$message.success(this.$t('ai.stoppedGenerating'))
|
||||
},
|
||||
|
||||
// 轮询进行渲染
|
||||
loopRenderOnAiCreating() {
|
||||
if (!this.aiCreatingContent.trim() || this.isLoopRendering) return
|
||||
this.isLoopRendering = true
|
||||
const treeData = transformMarkdownTo(this.aiCreatingContent)
|
||||
this.addUid(treeData)
|
||||
let lastTreeData = JSON.stringify(treeData)
|
||||
|
||||
// 在当前渲染完成时再进行下一次渲染
|
||||
const onRenderEnd = () => {
|
||||
// 处理超出画布的节点
|
||||
this.checkNodeOuter()
|
||||
|
||||
// 如果生成结束数据渲染完毕,那么解绑事件
|
||||
if (!this.isAiCreating && !this.aiCreatingContent) {
|
||||
this.mindMap.off('node_tree_render_end', onRenderEnd)
|
||||
this.latestUid = ''
|
||||
return
|
||||
}
|
||||
|
||||
const treeData = transformMarkdownTo(this.aiCreatingContent)
|
||||
this.addUid(treeData)
|
||||
// 正在生成中
|
||||
if (this.isAiCreating) {
|
||||
// 如果和上次数据一样则不触发重新渲染
|
||||
const curTreeData = JSON.stringify(treeData)
|
||||
if (curTreeData === lastTreeData) {
|
||||
setTimeout(() => {
|
||||
onRenderEnd()
|
||||
}, 500)
|
||||
return
|
||||
}
|
||||
lastTreeData = curTreeData
|
||||
this.mindMap.updateData(treeData)
|
||||
} else {
|
||||
// 已经生成结束
|
||||
// 还要触发一遍渲染,否则会丢失数据
|
||||
this.mindMap.updateData(treeData)
|
||||
this.resetOnRenderEnd()
|
||||
}
|
||||
}
|
||||
this.mindMap.on('node_tree_render_end', onRenderEnd)
|
||||
|
||||
this.mindMap.setData(treeData)
|
||||
},
|
||||
|
||||
// 处理超出画布的节点
|
||||
checkNodeOuter() {
|
||||
if (this.latestUid) {
|
||||
const latestNode = this.mindMap.renderer.findNodeByUid(this.latestUid)
|
||||
if (latestNode) {
|
||||
const { isOuter, offsetLeft, offsetTop } = checkNodeOuter(
|
||||
this.mindMap,
|
||||
latestNode,
|
||||
100,
|
||||
100
|
||||
)
|
||||
if (isOuter) {
|
||||
this.mindMap.view.translateXY(offsetLeft, offsetTop)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 给AI生成的数据添加uid
|
||||
addUid(data) {
|
||||
const checkRepeatUidMap = {}
|
||||
const walk = (node, pUid = '') => {
|
||||
if (!node.data) {
|
||||
node.data = {}
|
||||
}
|
||||
if (isUndef(node.data.uid)) {
|
||||
// 根据pUid+文本内容来复用上一次生成数据的uid
|
||||
const key = pUid + '-' + node.data.text
|
||||
node.data.uid = this.uidMap[key] || createUid()
|
||||
// 当前uid和之前的重复,那么重新生成一个。这种情况很少,但是以防万一
|
||||
if (checkRepeatUidMap[node.data.uid]) {
|
||||
node.data.uid = createUid()
|
||||
}
|
||||
this.latestUid = this.uidMap[key] = node.data.uid
|
||||
checkRepeatUidMap[node.data.uid] = true
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.children.forEach(child => {
|
||||
walk(child, node.data.uid)
|
||||
})
|
||||
}
|
||||
}
|
||||
walk(data)
|
||||
},
|
||||
|
||||
// AI生成部分
|
||||
async aiCreatePart(node) {
|
||||
try {
|
||||
await this.aiTest()
|
||||
this.beingAiCreateNodeUid = node.getData('uid')
|
||||
const currentMindMapData = this.mindMap.getData()
|
||||
this.mindMapDataCache = JSON.stringify(currentMindMapData)
|
||||
this.aiCreatingMaskVisible = true
|
||||
// 发起请求
|
||||
this.isAiCreating = true
|
||||
this.aiInstance = new Ai({
|
||||
port: this.aiConfig.port
|
||||
})
|
||||
this.aiInstance.init('huoshan', this.aiConfig)
|
||||
this.aiInstance.request(
|
||||
{
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: `${this.$t(
|
||||
'ai.aiCreatePartMsgPrefix'
|
||||
)}${getStrWithBrFromHtml(
|
||||
currentMindMapData.data.text
|
||||
)}${this.$t('ai.aiCreatePartMsgCenter')}${getStrWithBrFromHtml(
|
||||
node.getData('text')
|
||||
)}${this.$t('ai.aiCreatePartMsgPostfix')}`
|
||||
}
|
||||
]
|
||||
},
|
||||
content => {
|
||||
if (content && /\n$/.test(content)) {
|
||||
this.aiCreatingContent = content
|
||||
}
|
||||
this.loopRenderOnAiCreatingPart()
|
||||
},
|
||||
content => {
|
||||
this.aiCreatingContent = content
|
||||
this.resetOnAiCreatingStop()
|
||||
this.$message.success(this.$t('ai.aiGenerationSuccess'))
|
||||
},
|
||||
() => {
|
||||
this.resetOnAiCreatingStop()
|
||||
this.resetOnRenderEnd()
|
||||
this.$message.error(this.$t('ai.generationFailed'))
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
|
||||
// 将生成的数据添加到指定节点上
|
||||
addToTargetNode(newChildren = []) {
|
||||
const initData = JSON.parse(this.mindMapDataCache)
|
||||
const walk = node => {
|
||||
if (node.data.uid === this.beingAiCreateNodeUid) {
|
||||
if (!node.children) {
|
||||
node.children = []
|
||||
}
|
||||
node.children.push(...newChildren)
|
||||
return
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.children.forEach(child => {
|
||||
walk(child)
|
||||
})
|
||||
}
|
||||
}
|
||||
walk(initData)
|
||||
return initData
|
||||
},
|
||||
|
||||
// 轮询进行部分渲染
|
||||
loopRenderOnAiCreatingPart() {
|
||||
if (!this.aiCreatingContent.trim() || this.isLoopRendering) return
|
||||
this.isLoopRendering = true
|
||||
const partData = transformMarkdownTo(this.aiCreatingContent)
|
||||
this.addUid(partData)
|
||||
let lastPartData = JSON.stringify(partData)
|
||||
const treeData = this.addToTargetNode(partData.children || [])
|
||||
|
||||
// 在当前渲染完成时再进行下一次渲染
|
||||
const onRenderEnd = () => {
|
||||
// 处理超出画布的节点
|
||||
this.checkNodeOuter()
|
||||
|
||||
// 如果生成结束数据渲染完毕,那么解绑事件
|
||||
if (!this.isAiCreating && !this.aiCreatingContent) {
|
||||
this.mindMap.off('node_tree_render_end', onRenderEnd)
|
||||
this.latestUid = ''
|
||||
return
|
||||
}
|
||||
|
||||
const partData = transformMarkdownTo(this.aiCreatingContent)
|
||||
this.addUid(partData)
|
||||
const treeData = this.addToTargetNode(partData.children || [])
|
||||
|
||||
if (this.isAiCreating) {
|
||||
// 如果和上次数据一样则不触发重新渲染
|
||||
const curPartData = JSON.stringify(partData)
|
||||
if (curPartData === lastPartData) {
|
||||
setTimeout(() => {
|
||||
onRenderEnd()
|
||||
}, 500)
|
||||
return
|
||||
}
|
||||
lastPartData = curPartData
|
||||
this.mindMap.updateData(treeData)
|
||||
} else {
|
||||
this.mindMap.updateData(treeData)
|
||||
this.resetOnRenderEnd()
|
||||
}
|
||||
}
|
||||
this.mindMap.on('node_tree_render_end', onRenderEnd)
|
||||
// 因为是续写,所以首次也直接使用updateData方法渲染
|
||||
this.mindMap.updateData(treeData)
|
||||
},
|
||||
|
||||
// AI对话
|
||||
async aiChat(
|
||||
messageList = [],
|
||||
progress = () => {},
|
||||
end = () => {},
|
||||
err = () => {}
|
||||
) {
|
||||
try {
|
||||
await this.aiTest()
|
||||
// 发起请求
|
||||
this.isAiCreating = true
|
||||
this.aiInstance = new Ai({
|
||||
port: this.aiConfig.port
|
||||
})
|
||||
this.aiInstance.init('huoshan', this.aiConfig)
|
||||
this.aiInstance.request(
|
||||
{
|
||||
messages: messageList.map(msg => {
|
||||
return {
|
||||
role: 'user',
|
||||
content: msg
|
||||
}
|
||||
})
|
||||
},
|
||||
content => {
|
||||
progress(content)
|
||||
},
|
||||
content => {
|
||||
end(content)
|
||||
},
|
||||
error => {
|
||||
err(error)
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
|
||||
// AI对话停止
|
||||
aiChatStop() {
|
||||
if (this.aiInstance) {
|
||||
this.aiInstance.stop()
|
||||
this.isAiCreating = false
|
||||
this.aiInstance = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.clientTipDialog,
|
||||
.createDialog {
|
||||
/deep/ .el-dialog__body {
|
||||
padding: 12px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.tipBox {
|
||||
p {
|
||||
margin-bottom: 12px;
|
||||
|
||||
a {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inputBox {
|
||||
.tip {
|
||||
margin-top: 12px;
|
||||
|
||||
&.warning {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.aiCreatingMask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 99999;
|
||||
background-color: transparent;
|
||||
|
||||
.btn {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 100px;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Sidebar ref="sidebar" :title="$t('baseStyle.title')">
|
||||
<div class="sidebarContent" :class="{ isDark: isDark }" v-if="data">
|
||||
<div class="sidebarContent customScrollbar" :class="{ isDark: isDark }" v-if="data">
|
||||
<!-- 背景 -->
|
||||
<div class="title noTop">{{ $t('baseStyle.background') }}</div>
|
||||
<div class="row">
|
||||
@ -995,8 +995,6 @@ export default {
|
||||
this.$bus.$off('setData', this.onSetData)
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setLocalConfig']),
|
||||
|
||||
onSetData() {
|
||||
if (this.activeSidebar !== 'baseStyle') return
|
||||
setTimeout(() => {
|
||||
|
||||
@ -140,6 +140,10 @@
|
||||
<div class="item" @click="exec('EXPORT_CUR_NODE_TO_PNG')">
|
||||
<span class="name">{{ $t('contextmenu.exportNodeToPng') }}</span>
|
||||
</div>
|
||||
<div class="splitLine" v-if="enableAi"></div>
|
||||
<div class="item" @click="aiCreate" v-if="enableAi">
|
||||
<span class="name">{{ $t('contextmenu.aiCreate') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="type === 'svg'">
|
||||
<div class="item" @click="exec('RETURN_CENTER')">
|
||||
@ -257,7 +261,8 @@ export default {
|
||||
isZenMode: state => state.localConfig.isZenMode,
|
||||
isDark: state => state.localConfig.isDark,
|
||||
supportNumbers: state => state.supportNumbers,
|
||||
supportCheckbox: state => state.supportCheckbox
|
||||
supportCheckbox: state => state.supportCheckbox,
|
||||
enableAi: state => state.enableAi
|
||||
}),
|
||||
expandList() {
|
||||
return [
|
||||
@ -578,6 +583,12 @@ export default {
|
||||
console.log(error)
|
||||
this.$message.error(this.$t('contextmenu.copyFail'))
|
||||
}
|
||||
},
|
||||
|
||||
// AI续写
|
||||
aiCreate() {
|
||||
this.$bus.$emit('ai_create_part', this.node)
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 740px) {
|
||||
@media screen and (max-width: 900px) {
|
||||
.countContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -48,6 +48,8 @@
|
||||
v-if="mindMap"
|
||||
:mindMap="mindMap"
|
||||
></NodeImgPlacementToolbar>
|
||||
<AiCreate v-if="mindMap && enableAi" :mindMap="mindMap"></AiCreate>
|
||||
<AiChat v-if="enableAi"></AiChat>
|
||||
<div
|
||||
class="dragMask"
|
||||
v-if="showDragMask"
|
||||
@ -90,7 +92,7 @@ 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软件格式导入导出插件、待办插件、节点连线流动效果插件
|
||||
// 以下插件为付费插件,详情请查看开发文档。依次为:手绘风格插件、标记插件、编号插件、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'
|
||||
@ -98,7 +100,8 @@ import Themes from 'simple-mind-map-plugin-themes'
|
||||
// import Excel from 'simple-mind-map-plugin-excel'
|
||||
// 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 Momentum from 'simple-mind-map-plugin-momentum'
|
||||
// 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 simple-mind-map-plugin-momentum
|
||||
import OutlineSidebar from './OutlineSidebar'
|
||||
import Style from './Style'
|
||||
import BaseStyle from './BaseStyle'
|
||||
@ -142,6 +145,8 @@ import NodeTagStyle from './NodeTagStyle.vue'
|
||||
import Setting from './Setting.vue'
|
||||
import AssociativeLineStyle from './AssociativeLineStyle.vue'
|
||||
import NodeImgPlacementToolbar from './NodeImgPlacementToolbar.vue'
|
||||
import AiCreate from './AiCreate.vue'
|
||||
import AiChat from './AiChat.vue'
|
||||
|
||||
// 注册插件
|
||||
MindMap.usePlugin(MiniMap)
|
||||
@ -196,7 +201,9 @@ export default {
|
||||
NodeTagStyle,
|
||||
Setting,
|
||||
AssociativeLineStyle,
|
||||
NodeImgPlacementToolbar
|
||||
NodeImgPlacementToolbar,
|
||||
AiCreate,
|
||||
AiChat
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -224,8 +231,11 @@ export default {
|
||||
state.localConfig.useLeftKeySelectionRightKeyDrag,
|
||||
isUseHandDrawnLikeStyle: state =>
|
||||
state.localConfig.isUseHandDrawnLikeStyle,
|
||||
isUseMomentum: state => state.localConfig.isUseMomentum,
|
||||
extraTextOnExport: state => state.extraTextOnExport,
|
||||
isDragOutlineTreeNode: state => state.isDragOutlineTreeNode
|
||||
isDragOutlineTreeNode: state => state.isDragOutlineTreeNode,
|
||||
enableAi: state => state.enableAi,
|
||||
isDark: state => state.localConfig.isDark
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
@ -249,6 +259,18 @@ export default {
|
||||
} else {
|
||||
this.removeHandDrawnLikeStylePlugin()
|
||||
}
|
||||
},
|
||||
isUseMomentum() {
|
||||
if (this.isUseMomentum) {
|
||||
this.addMomentumPlugin()
|
||||
} else {
|
||||
this.removeMomentumPlugin()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.$route.query && this.$route.query.ai) {
|
||||
this.setEnableAi(true)
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@ -288,7 +310,7 @@ export default {
|
||||
this.mindMap.destroy()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setFileName', 'setIsUnSave']),
|
||||
...mapMutations(['setFileName', 'setIsUnSave', 'setEnableAi']),
|
||||
|
||||
handleStartTextEdit() {
|
||||
this.mindMap.renderer.startTextEdit()
|
||||
@ -376,7 +398,8 @@ export default {
|
||||
})
|
||||
this.$bus.$on('view_data_change', data => {
|
||||
if (
|
||||
(!this.clientConfig || !this.clientConfig.viewTranslateChangeTriggerAutoSave) &&
|
||||
(!this.clientConfig ||
|
||||
!this.clientConfig.viewTranslateChangeTriggerAutoSave) &&
|
||||
this.lastViewData.transform.scaleX === data.transform.scaleX &&
|
||||
this.lastViewData.transform.scaleY === data.transform.scaleY
|
||||
) {
|
||||
@ -465,7 +488,8 @@ export default {
|
||||
{
|
||||
confirmButtonText: this.$t('edit.yes'),
|
||||
cancelButtonText: this.$t('edit.no'),
|
||||
type: 'warning'
|
||||
type: 'warning',
|
||||
customClass: this.isDark ? 'darkElMessageBox' : ''
|
||||
}
|
||||
)
|
||||
},
|
||||
@ -513,7 +537,8 @@ export default {
|
||||
{
|
||||
confirmButtonText: this.$t('edit.yes'),
|
||||
cancelButtonText: this.$t('edit.no'),
|
||||
type: 'warning'
|
||||
type: 'warning',
|
||||
customClass: this.isDark ? 'darkElMessageBox' : ''
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
@ -639,38 +664,7 @@ export default {
|
||||
// }
|
||||
})
|
||||
this.lastViewData = simpleDeepClone(this.mindMap.view.getTransformData())
|
||||
if (this.openNodeRichText) this.addRichTextPlugin()
|
||||
if (this.isShowScrollbar) this.addScrollbarPlugin()
|
||||
if (this.isUseHandDrawnLikeStyle) this.addHandDrawnLikeStylePlugin()
|
||||
if (typeof HandDrawnLikeStyle !== 'undefined') {
|
||||
this.$store.commit('setSupportHandDrawnLikeStyle', true)
|
||||
}
|
||||
if (typeof Notation !== 'undefined') {
|
||||
this.mindMap.addPlugin(Notation)
|
||||
this.$store.commit('setSupportMark', true)
|
||||
}
|
||||
if (typeof Numbers !== 'undefined') {
|
||||
this.mindMap.addPlugin(Numbers)
|
||||
this.$store.commit('setSupportNumbers', true)
|
||||
}
|
||||
if (typeof Freemind !== 'undefined') {
|
||||
this.mindMap.addPlugin(Freemind)
|
||||
this.$store.commit('setSupportFreemind', true)
|
||||
Vue.prototype.Freemind = Freemind
|
||||
}
|
||||
if (typeof Excel !== 'undefined') {
|
||||
this.mindMap.addPlugin(Excel)
|
||||
this.$store.commit('setSupportExcel', true)
|
||||
Vue.prototype.Excel = Excel
|
||||
}
|
||||
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.loadPlugins()
|
||||
this.mindMap.keyCommand.addShortcut('Control+s', () => {
|
||||
this.manualSave()
|
||||
})
|
||||
@ -742,6 +736,46 @@ export default {
|
||||
// }, 5000)
|
||||
},
|
||||
|
||||
// 加载相关插件
|
||||
loadPlugins() {
|
||||
if (this.openNodeRichText) this.addRichTextPlugin()
|
||||
if (this.isShowScrollbar) this.addScrollbarPlugin()
|
||||
if (typeof HandDrawnLikeStyle !== 'undefined') {
|
||||
this.$store.commit('setSupportHandDrawnLikeStyle', true)
|
||||
if (this.isUseHandDrawnLikeStyle) this.addHandDrawnLikeStylePlugin()
|
||||
}
|
||||
if (typeof Momentum !== 'undefined') {
|
||||
this.$store.commit('setSupportMomentum', true)
|
||||
if (this.isUseMomentum) this.addMomentumPlugin()
|
||||
}
|
||||
if (typeof Notation !== 'undefined') {
|
||||
this.mindMap.addPlugin(Notation)
|
||||
this.$store.commit('setSupportMark', true)
|
||||
}
|
||||
if (typeof Numbers !== 'undefined') {
|
||||
this.mindMap.addPlugin(Numbers)
|
||||
this.$store.commit('setSupportNumbers', true)
|
||||
}
|
||||
if (typeof Freemind !== 'undefined') {
|
||||
this.mindMap.addPlugin(Freemind)
|
||||
this.$store.commit('setSupportFreemind', true)
|
||||
Vue.prototype.Freemind = Freemind
|
||||
}
|
||||
if (typeof Excel !== 'undefined') {
|
||||
this.mindMap.addPlugin(Excel)
|
||||
this.$store.commit('setSupportExcel', true)
|
||||
Vue.prototype.Excel = Excel
|
||||
}
|
||||
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)
|
||||
}
|
||||
},
|
||||
|
||||
// url中是否存在要打开的文件
|
||||
hasFileURL() {
|
||||
const fileURL = this.$route.query.fileURL
|
||||
@ -841,10 +875,12 @@ export default {
|
||||
let id = this.$route.params.id
|
||||
let data = this.mindMap.getData(true)
|
||||
removeMindMapNodeStickerProtocol(data.root)
|
||||
const currentFolder = localStorage.getItem('currentFolder')
|
||||
let res = await window.electronAPI.save(
|
||||
id,
|
||||
JSON.stringify(data),
|
||||
this.fileName
|
||||
this.fileName,
|
||||
currentFolder || ''
|
||||
)
|
||||
if (res) {
|
||||
this.isNewFile = false
|
||||
@ -885,6 +921,25 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// 加载动量效果插件
|
||||
addMomentumPlugin() {
|
||||
try {
|
||||
if (!this.mindMap) return
|
||||
this.mindMap.addPlugin(Momentum)
|
||||
} catch (error) {
|
||||
console.log('动量效果插件不存在')
|
||||
}
|
||||
},
|
||||
|
||||
// 移除动量效果插件
|
||||
removeMomentumPlugin() {
|
||||
try {
|
||||
this.mindMap.removePlugin(Momentum)
|
||||
} catch (error) {
|
||||
console.log('动量效果插件不存在')
|
||||
}
|
||||
},
|
||||
|
||||
// 测试动态插入节点
|
||||
testDynamicCreateNodes() {
|
||||
// return
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="nodeExportDialog"
|
||||
:class="{ isMobile: isMobile, isDark: isDark }"
|
||||
:title="$t('export.title')"
|
||||
:visible.sync="dialogVisible"
|
||||
v-loading.fullscreen.lock="loading"
|
||||
:element-loading-text="loadingText"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
element-loading-background="rgba(0, 0, 0, 0.8)"
|
||||
:width="isMobile ? '90%' : '50%'"
|
||||
:width="isMobile ? '90%' : '800px'"
|
||||
:top="isMobile ? '20px' : '15vh'"
|
||||
>
|
||||
<div class="exportContainer" :class="{ isDark: isDark }">
|
||||
<!-- 文件名称输入 -->
|
||||
<div class="nameInputBox">
|
||||
<span class="name">{{ $t('export.filename') }}</span>
|
||||
<el-input
|
||||
@ -19,71 +21,90 @@
|
||||
size="mini"
|
||||
@keydown.native.stop
|
||||
></el-input>
|
||||
<el-checkbox
|
||||
v-show="['smm', 'json'].includes(exportType)"
|
||||
v-model="widthConfig"
|
||||
style="margin-left: 12px"
|
||||
>{{ $t('export.include') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="paddingInputBox"
|
||||
v-show="['svg', 'png', 'pdf'].includes(exportType)"
|
||||
>
|
||||
<div class="paddingInputGroup">
|
||||
<span class="name">{{ $t('export.paddingX') }}</span>
|
||||
<el-input
|
||||
style="max-width: 100px"
|
||||
v-model="paddingX"
|
||||
size="mini"
|
||||
@change="onPaddingChange"
|
||||
@keydown.native.stop
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="paddingInputGroup">
|
||||
<span class="name">{{ $t('export.paddingY') }}</span>
|
||||
<el-input
|
||||
style="width: 100px"
|
||||
v-model="paddingY"
|
||||
size="mini"
|
||||
@change="onPaddingChange"
|
||||
@keydown.native.stop
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="paddingInputGroup">
|
||||
<span class="name">{{ this.$t('export.addFooterText') }}</span>
|
||||
<el-input
|
||||
style="width: 200px"
|
||||
v-model="extraText"
|
||||
size="mini"
|
||||
:placeholder="$t('export.addFooterTextPlaceholder')"
|
||||
@keydown.native.stop
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="paddingInputGroup">
|
||||
<el-checkbox
|
||||
v-show="['png', 'pdf'].includes(exportType)"
|
||||
v-model="isTransparent"
|
||||
>{{ $t('export.isTransparent') }}</el-checkbox
|
||||
<!-- 导出类型选择 -->
|
||||
<div class="downloadTypeSelectBox">
|
||||
<!-- 类型列表 -->
|
||||
<div class="downloadTypeList customScrollbar">
|
||||
<div
|
||||
class="downloadTypeItem"
|
||||
v-for="item in downTypeList"
|
||||
:key="item.type"
|
||||
:class="{ active: exportType === item.type }"
|
||||
@click="exportType = item.type"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="downloadTypeList">
|
||||
<div
|
||||
class="downloadTypeItem"
|
||||
v-for="item in downTypeList"
|
||||
:key="item.type"
|
||||
:class="{ active: exportType === item.type }"
|
||||
@click="exportType = item.type"
|
||||
>
|
||||
<div class="icon iconfont" :class="[item.icon, item.type]"></div>
|
||||
<div class="info">
|
||||
<div class="icon iconfont" :class="[item.icon, item.type]"></div>
|
||||
<div class="name">{{ item.name }}</div>
|
||||
<div class="desc">{{ item.desc }}</div>
|
||||
<div class="icon checked el-icon-check"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 类型内容 -->
|
||||
<div class="downloadTypeContent customScrollbar">
|
||||
<div class="contentRow">
|
||||
<div class="contentName">{{ $t('export.desc') }}</div>
|
||||
<div class="contentValue">
|
||||
{{ currentTypeData ? currentTypeData.desc : '' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="contentRow">
|
||||
<div class="contentName">{{ $t('export.options') }}</div>
|
||||
<div class="contentValue">
|
||||
<div
|
||||
class="valueItem"
|
||||
v-show="['smm', 'json'].includes(exportType)"
|
||||
>
|
||||
<el-checkbox v-model="widthConfig">{{
|
||||
$t('export.include')
|
||||
}}</el-checkbox>
|
||||
</div>
|
||||
<div
|
||||
class="valueItem"
|
||||
v-show="['svg', 'png', 'pdf'].includes(exportType)"
|
||||
>
|
||||
<div class="valueSubItem">
|
||||
<span class="name">{{ $t('export.paddingX') }}</span>
|
||||
<el-input
|
||||
style="width: 200px"
|
||||
v-model="paddingX"
|
||||
size="mini"
|
||||
@change="onPaddingChange"
|
||||
@keydown.native.stop
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="valueSubItem">
|
||||
<span class="name">{{ $t('export.paddingY') }}</span>
|
||||
<el-input
|
||||
style="width: 200px"
|
||||
v-model="paddingY"
|
||||
size="mini"
|
||||
@change="onPaddingChange"
|
||||
@keydown.native.stop
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="valueSubItem">
|
||||
<span class="name">{{
|
||||
this.$t('export.addFooterText')
|
||||
}}</span>
|
||||
<el-input
|
||||
style="width: 200px"
|
||||
v-model="extraText"
|
||||
size="mini"
|
||||
:placeholder="$t('export.addFooterTextPlaceholder')"
|
||||
@keydown.native.stop
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="valueSubItem">
|
||||
<el-checkbox
|
||||
v-show="['png', 'pdf'].includes(exportType)"
|
||||
v-model="isTransparent"
|
||||
>{{ $t('export.isTransparent') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tip">{{ $t('export.tips') }}</div>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="cancel">{{ $t('dialog.cancel') }}</el-button>
|
||||
@ -144,6 +165,13 @@ export default {
|
||||
return true
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
currentTypeData() {
|
||||
const cur = this.downTypeList.find(item => {
|
||||
return item.type === this.exportType
|
||||
})
|
||||
return cur
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@ -249,15 +277,39 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.exportContainer {
|
||||
&.isDark {
|
||||
.downloadTypeList {
|
||||
.downloadTypeItem {
|
||||
background-color: #363b3f;
|
||||
.nodeExportDialog {
|
||||
.exportContainer {
|
||||
&.isDark {
|
||||
.nameInputBox {
|
||||
.name {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
.name {
|
||||
color: hsla(0, 0%, 100%, 0.9);
|
||||
.downloadTypeSelectBox {
|
||||
.downloadTypeList {
|
||||
.downloadTypeItem {
|
||||
background-color: #363b3f;
|
||||
|
||||
&.active {
|
||||
background-color: #262a2e;
|
||||
}
|
||||
|
||||
.name {
|
||||
color: hsla(0, 0%, 100%, 0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.downloadTypeContent {
|
||||
.contentRow {
|
||||
.contentName {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
||||
.contentValue {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -266,125 +318,228 @@ export default {
|
||||
}
|
||||
|
||||
.nodeExportDialog {
|
||||
/deep/ .el-dialog__body {
|
||||
background-color: #f2f4f7;
|
||||
}
|
||||
|
||||
.nameInputBox {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.name {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.paddingInputBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.paddingInputGroup {
|
||||
margin-right: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-right: 0;
|
||||
&.isDark {
|
||||
/deep/ .el-dialog__body {
|
||||
.el-checkbox {
|
||||
.el-checkbox__label {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-right: 10px;
|
||||
/deep/ .el-dialog__body {
|
||||
padding: 0;
|
||||
border-top: 1px solid #f2f4f7;
|
||||
border-bottom: 1px solid #f2f4f7;
|
||||
|
||||
.el-checkbox__input.is-checked + .el-checkbox__label {
|
||||
color: #409eff !important;
|
||||
}
|
||||
|
||||
.el-checkbox {
|
||||
.el-checkbox__label {
|
||||
color: #1a1a1a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
margin-top: 10px;
|
||||
&.isMobile {
|
||||
.exportContainer {
|
||||
.downloadTypeSelectBox {
|
||||
flex-direction: column;
|
||||
|
||||
&.warning {
|
||||
color: #f56c6c;
|
||||
.downloadTypeList {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
height: 60px;
|
||||
|
||||
.downloadTypeItem {
|
||||
width: 100px;
|
||||
flex-shrink: 0;
|
||||
padding-left: 10px;
|
||||
|
||||
.icon {
|
||||
margin-right: 5px;
|
||||
|
||||
&.checked {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.downloadTypeContent {
|
||||
.contentRow {
|
||||
flex-direction: column;
|
||||
|
||||
.contentName {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.contentValue {
|
||||
.valueItem {
|
||||
.valueSubItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.name {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.downloadTypeList {
|
||||
.exportContainer {
|
||||
width: 100%;
|
||||
height: 450px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.downloadTypeItem {
|
||||
width: 200px;
|
||||
height: 88px;
|
||||
padding: 22px;
|
||||
overflow: hidden;
|
||||
margin: 10px;
|
||||
border-radius: 11px;
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.02);
|
||||
background-color: #fff;
|
||||
flex-direction: column;
|
||||
|
||||
.nameInputBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
height: 50px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #f2f4f7;
|
||||
|
||||
&.active {
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 30px;
|
||||
.name {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.downloadTypeSelectBox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
|
||||
.downloadTypeList {
|
||||
width: 210px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background-color: #f2f4f7;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.png {
|
||||
color: #ffc038;
|
||||
}
|
||||
.downloadTypeItem {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
padding-left: 28px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
&.pdf {
|
||||
color: #ff6c4d;
|
||||
}
|
||||
&.active {
|
||||
background-color: #fff;
|
||||
|
||||
&.md {
|
||||
color: #2b2b2b;
|
||||
}
|
||||
.icon {
|
||||
&.checked {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.json {
|
||||
color: #12c87e;
|
||||
}
|
||||
.icon {
|
||||
font-size: 25px;
|
||||
margin-right: 15px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.svg {
|
||||
color: #4380ff;
|
||||
}
|
||||
&.png {
|
||||
color: #ffc038;
|
||||
}
|
||||
|
||||
&.smm {
|
||||
color: #409eff;
|
||||
}
|
||||
&.pdf {
|
||||
color: #ff6c4d;
|
||||
}
|
||||
|
||||
&.xmind {
|
||||
color: #f55e5e;
|
||||
}
|
||||
&.md {
|
||||
color: #2b2b2b;
|
||||
}
|
||||
|
||||
&.txt {
|
||||
color: #70798e;
|
||||
&.json {
|
||||
color: #12c87e;
|
||||
}
|
||||
|
||||
&.svg {
|
||||
color: #4380ff;
|
||||
}
|
||||
|
||||
&.smm {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
&.xmind {
|
||||
color: #f55e5e;
|
||||
}
|
||||
|
||||
&.txt {
|
||||
color: #70798e;
|
||||
}
|
||||
|
||||
&.checked {
|
||||
color: #409eff;
|
||||
font-size: 20px;
|
||||
margin-left: auto;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
color: #1a1a1a;
|
||||
font-size: 15px;
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
.downloadTypeContent {
|
||||
padding: 30px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
.name {
|
||||
color: #1a1a1a;
|
||||
font-size: 15px;
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.contentRow {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.desc {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
display: -webkit-box; /* 必须设置display属性为-webkit-box */
|
||||
overflow: hidden; /* 超出部分隐藏 */
|
||||
text-overflow: ellipsis; /* 显示省略号 */
|
||||
-webkit-line-clamp: 2; /* 限制显示两行 */
|
||||
-webkit-box-orient: vertical; /* 垂直方向上的换行 */
|
||||
.contentName {
|
||||
width: 80px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.contentValue {
|
||||
color: #1a1a1a;
|
||||
|
||||
.valueItem {
|
||||
.valueSubItem {
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
>
|
||||
</div>
|
||||
<div class="title">{{ $t('formulaSidebar.common') }}</div>
|
||||
<div class="formulaList">
|
||||
<div class="formulaList customScrollbar">
|
||||
<div class="formulaItem" v-for="(item, index) in list" :key="index">
|
||||
<div class="overview" v-html="item.overview"></div>
|
||||
<div class="text" @click="formulaText = item.text">
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="navigatorContainer" :class="{ isDark: isDark }">
|
||||
<div class="navigatorContainer customScrollbar" :class="{ isDark: isDark }">
|
||||
<div class="item">
|
||||
<el-select
|
||||
v-model="lang"
|
||||
@ -197,7 +197,8 @@ export default {
|
||||
url = 'https://wanglin2.github.io/mind-map-docs/help/help1.html'
|
||||
break
|
||||
case 'devDoc':
|
||||
url = 'https://wanglin2.github.io/mind-map-docs/start/introduction.html'
|
||||
url =
|
||||
'https://wanglin2.github.io/mind-map-docs/start/introduction.html'
|
||||
break
|
||||
case 'site':
|
||||
url = 'https://wanglin2.github.io/mind-map-docs/'
|
||||
@ -276,7 +277,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 590px) {
|
||||
@media screen and (max-width: 700px) {
|
||||
.navigatorContainer {
|
||||
left: 20px;
|
||||
overflow-x: auto;
|
||||
|
||||
@ -91,8 +91,10 @@
|
||||
slot="reference"
|
||||
class="toolbarBtn"
|
||||
:style="{
|
||||
marginLeft: dir === 'v' ? '0px' : '20px',
|
||||
marginTop: dir === 'v' ? '10px' : '0px'
|
||||
marginLeft: dir === 'v' || rightHasBtn ? '0px' : '20px',
|
||||
marginTop: dir === 'v' ? '10px' : '0px',
|
||||
marginRight: rightHasBtn ? '20px' : '0px',
|
||||
marginBottom: dir === 'v' && rightHasBtn ? '10px' : '0px'
|
||||
}"
|
||||
:class="{
|
||||
disabled: activeNodes.length <= 0 || hasGeneralization
|
||||
@ -128,6 +130,10 @@ export default {
|
||||
dir: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
rightHasBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="noteContentViewer"
|
||||
class="noteContentViewer customScrollbar"
|
||||
ref="noteContentViewer"
|
||||
:style="{
|
||||
left: this.left + 'px',
|
||||
@ -124,22 +124,5 @@ export default {
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 2px 16px 0 rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 7px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -6,10 +6,26 @@
|
||||
:style="{top: IS_ELECTRON ? '40px' : 0}"
|
||||
v-if="isOutlineEdit"
|
||||
>
|
||||
<div class="closeBtn" @click="onClose">
|
||||
<span class="icon iconfont iconguanbi"></span>
|
||||
<div class="btnList">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="$t('outline.print')"
|
||||
placement="top"
|
||||
>
|
||||
<div class="btn" @click="onPrint">
|
||||
<span class="icon iconfont iconprinting"></span>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<div class="btn" @click="onClose">
|
||||
<span class="icon iconfont iconguanbi"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="outlineEditBox" ref="outlineEditBox">
|
||||
<div
|
||||
class="outlineEditBox"
|
||||
id="fullScreenOutlineEditBox"
|
||||
ref="outlineEditBox"
|
||||
>
|
||||
<div class="outlineEdit">
|
||||
<el-tree
|
||||
ref="tree"
|
||||
@ -59,6 +75,7 @@ import {
|
||||
handleInputPasteText
|
||||
} from 'simple-mind-map/src/utils'
|
||||
import { storeData } from '@/api'
|
||||
import { printOutline } from '@/utils'
|
||||
|
||||
// 大纲侧边栏
|
||||
export default {
|
||||
@ -226,6 +243,11 @@ export default {
|
||||
return Math.random()
|
||||
},
|
||||
|
||||
// 打印
|
||||
onPrint() {
|
||||
printOutline(this.$refs.outlineEditBox)
|
||||
},
|
||||
|
||||
// 关闭
|
||||
onClose() {
|
||||
this.setIsOutlineEdit(false)
|
||||
@ -275,28 +297,36 @@ export default {
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
z-index: 1999;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
&.isDark {
|
||||
background-color: #262a2e;
|
||||
|
||||
.closeBtn {
|
||||
.icon {
|
||||
color: #fff;
|
||||
.btnList {
|
||||
.btn {
|
||||
.icon {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.closeBtn {
|
||||
.btnList {
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
top: 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
font-size: 28px;
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
margin-left: 12px;
|
||||
|
||||
.icon {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,16 +1,36 @@
|
||||
<template>
|
||||
<Sidebar ref="sidebar" :title="$t('outline.title')">
|
||||
<div
|
||||
class="changeBtn"
|
||||
:class="{ isDark: isDark }"
|
||||
@click="onChangeToOutlineEdit"
|
||||
>
|
||||
<span class="icon iconfont iconquanping1"></span>
|
||||
<div class="btnList">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="$t('outline.print')"
|
||||
placement="top"
|
||||
>
|
||||
<div class="btn" @click="onPrint">
|
||||
<span class="icon iconfont iconprinting"></span>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="$t('outline.fullscreen')"
|
||||
placement="top"
|
||||
>
|
||||
<div
|
||||
class="btn"
|
||||
:class="{ isDark: isDark }"
|
||||
@click="onChangeToOutlineEdit"
|
||||
>
|
||||
<span class="icon iconfont iconquanping1"></span>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<Outline
|
||||
:mindMap="mindMap"
|
||||
v-if="activeSidebar === 'outline'"
|
||||
@scrollTo="onScrollTo"
|
||||
ref="outlineRef"
|
||||
></Outline>
|
||||
</Sidebar>
|
||||
</template>
|
||||
@ -19,6 +39,7 @@
|
||||
import Sidebar from './Sidebar'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import Outline from './Outline.vue'
|
||||
import { printOutline } from '@/utils'
|
||||
|
||||
// 大纲侧边栏
|
||||
export default {
|
||||
@ -62,20 +83,31 @@ export default {
|
||||
if (y > top + height) {
|
||||
container.scrollTo(0, y - height / 2)
|
||||
}
|
||||
},
|
||||
|
||||
// 打印
|
||||
onPrint() {
|
||||
printOutline(this.$refs.outlineRef.$el)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.changeBtn {
|
||||
.btnList {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
top: 12px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.isDark {
|
||||
color: #fff;
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
margin-left: 12px;
|
||||
|
||||
&.isDark {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -125,6 +125,7 @@ export default {
|
||||
)
|
||||
this.mindMap.keyCommand.addShortcut('Control+f', this.showSearch)
|
||||
window.addEventListener('resize', this.setSearchResultListHeight)
|
||||
this.$bus.$on('setData', this.close)
|
||||
},
|
||||
mounted() {
|
||||
this.setSearchResultListHeight()
|
||||
@ -141,6 +142,7 @@ export default {
|
||||
)
|
||||
this.mindMap.keyCommand.removeShortcut('Control+f', this.showSearch)
|
||||
window.removeEventListener('resize', this.setSearchResultListHeight)
|
||||
this.$bus.$off('setData', this.close)
|
||||
},
|
||||
methods: {
|
||||
isUndef,
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<Sidebar ref="sidebar" :title="$t('setting.title')">
|
||||
<div class="sidebarContent" :class="{ isDark: isDark }" v-if="data">
|
||||
<div
|
||||
class="sidebarContent customScrollbar"
|
||||
:class="{ isDark: isDark }"
|
||||
v-if="data"
|
||||
>
|
||||
<!-- 水印 -->
|
||||
<div class="row">
|
||||
<!-- 是否显示水印 -->
|
||||
@ -230,6 +234,18 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 节点连线样式是否允许继承祖先的连线样式 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="config.enableInheritAncestorLineStyle"
|
||||
@change="
|
||||
updateOtherConfig('enableInheritAncestorLineStyle', $event)
|
||||
"
|
||||
>{{ $t('setting.enableInheritAncestorLineStyle') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否开启手绘风格 -->
|
||||
<div class="row" v-if="supportHandDrawnLikeStyle">
|
||||
<div class="rowItem">
|
||||
@ -240,6 +256,16 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否开启动量效果 -->
|
||||
<div class="row" v-if="supportMomentum">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="localConfigs.isUseMomentum"
|
||||
@change="updateLocalConfig('isUseMomentum', $event)"
|
||||
>{{ $t('setting.isUseMomentum') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置鼠标滚轮行为 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
@ -387,7 +413,8 @@ export default {
|
||||
alwaysShowExpandBtn: false,
|
||||
enableAutoEnterTextEditWhenKeydown: true,
|
||||
imgTextMargin: 0,
|
||||
textContentMargin: 0
|
||||
textContentMargin: 0,
|
||||
enableInheritAncestorLineStyle: false
|
||||
},
|
||||
watermarkConfig: {
|
||||
show: false,
|
||||
@ -407,6 +434,7 @@ export default {
|
||||
localConfigs: {
|
||||
isShowScrollbar: false,
|
||||
isUseHandDrawnLikeStyle: false,
|
||||
isUseMomentum: false,
|
||||
enableDragImport: false
|
||||
}
|
||||
}
|
||||
@ -416,7 +444,8 @@ export default {
|
||||
activeSidebar: state => state.activeSidebar,
|
||||
localConfig: state => state.localConfig,
|
||||
isDark: state => state.localConfig.isDark,
|
||||
supportHandDrawnLikeStyle: state => state.supportHandDrawnLikeStyle
|
||||
supportHandDrawnLikeStyle: state => state.supportHandDrawnLikeStyle,
|
||||
supportMomentum: state => state.supportMomentum
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
@ -479,7 +508,14 @@ export default {
|
||||
storeConfig({
|
||||
config: this.data.config
|
||||
})
|
||||
if (['alwaysShowExpandBtn', 'imgTextMargin', 'textContentMargin'].includes(key)) {
|
||||
if (
|
||||
[
|
||||
'alwaysShowExpandBtn',
|
||||
'imgTextMargin',
|
||||
'textContentMargin',
|
||||
'enableInheritAncestorLineStyle'
|
||||
].includes(key)
|
||||
) {
|
||||
this.mindMap.reRender()
|
||||
}
|
||||
},
|
||||
@ -524,7 +560,8 @@ export default {
|
||||
{
|
||||
confirmButtonText: this.$t('setting.confirm'),
|
||||
cancelButtonText: this.$t('setting.cancel'),
|
||||
type: 'warning'
|
||||
type: 'warning',
|
||||
customClass: this.isDark ? 'darkElMessageBox' : ''
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<div class="sidebarHeader" v-if="title">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="sidebarContent" ref="sidebarContent">
|
||||
<div class="sidebarContent customScrollbar" ref="sidebarContent">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<div
|
||||
class="sidebarTriggerContainer"
|
||||
class="sidebarTriggerContainer "
|
||||
@click.stop
|
||||
:class="{ hasActive: show && activeSidebar, show: show, isDark: isDark }"
|
||||
:style="{ maxHeight: maxHeight + 'px' }"
|
||||
>
|
||||
<div class="toggleShowBtn" :class="{ hide: !show }" @click="show = !show">
|
||||
<span class="iconfont iconjiantouyou"></span>
|
||||
</div>
|
||||
<div class="trigger">
|
||||
<div class="trigger customScrollbar">
|
||||
<div
|
||||
class="triggerItem"
|
||||
v-for="item in triggerList"
|
||||
@ -35,14 +36,16 @@ export default {
|
||||
name: 'SidebarTrigger',
|
||||
data() {
|
||||
return {
|
||||
show: true
|
||||
show: true,
|
||||
maxHeight: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isDark: state => state.localConfig.isDark,
|
||||
activeSidebar: state => state.activeSidebar,
|
||||
isReadonly: state => state.isReadonly
|
||||
isReadonly: state => state.isReadonly,
|
||||
enableAi: state => state.enableAi
|
||||
}),
|
||||
|
||||
triggerList() {
|
||||
@ -52,6 +55,11 @@ export default {
|
||||
return ['outline', 'shortcutKey'].includes(item.value)
|
||||
})
|
||||
}
|
||||
if (!this.enableAi) {
|
||||
list = list.filter(item => {
|
||||
return item.value !== 'ai'
|
||||
})
|
||||
}
|
||||
return list
|
||||
}
|
||||
},
|
||||
@ -62,11 +70,28 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('resize', this.onResize)
|
||||
this.updateSize()
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.onResize)
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setActiveSidebar']),
|
||||
|
||||
trigger(item) {
|
||||
this.setActiveSidebar(item.value)
|
||||
},
|
||||
|
||||
onResize() {
|
||||
this.updateSize()
|
||||
},
|
||||
|
||||
updateSize() {
|
||||
const topMargin = 110
|
||||
const bottomMargin = 80
|
||||
this.maxHeight = window.innerHeight - topMargin - bottomMargin
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -75,11 +100,13 @@ export default {
|
||||
<style lang="less" scoped>
|
||||
.sidebarTriggerContainer {
|
||||
position: fixed;
|
||||
top: 110px;
|
||||
bottom: 80px;
|
||||
right: -60px;
|
||||
margin-top: 110px;
|
||||
transition: all 0.3s;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
&.isDark {
|
||||
.trigger {
|
||||
@ -145,7 +172,9 @@ export default {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 16px 0 rgba(0, 0, 0, 0.06);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
.triggerItem {
|
||||
height: 60px;
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
:class="{ isDark: isDark }"
|
||||
v-if="activeNodes.length > 0"
|
||||
>
|
||||
<div class="sidebarContent">
|
||||
<div class="sidebarContent customScrollbar">
|
||||
<!-- 文字 -->
|
||||
<div class="title noTop">{{ $t('style.text') }}</div>
|
||||
<div class="row">
|
||||
|
||||
@ -162,6 +162,7 @@ export default {
|
||||
cancelButtonText: this.$t('theme.reserve'),
|
||||
type: 'warning',
|
||||
distinguishCancelAndClose: true,
|
||||
customClass: this.isDark ? 'darkElMessageBox' : '',
|
||||
callback: action => {
|
||||
if (action === 'confirm') {
|
||||
this.mindMap.setThemeConfig({}, true)
|
||||
|
||||
@ -185,6 +185,27 @@ import { throttle, isMobile } from 'simple-mind-map/src/utils/index'
|
||||
* @Desc: 工具栏
|
||||
*/
|
||||
let fileHandle = null
|
||||
const defaultBtnList = [
|
||||
'back',
|
||||
'forward',
|
||||
'painter',
|
||||
'siblingNode',
|
||||
'childNode',
|
||||
'deleteNode',
|
||||
'image',
|
||||
'icon',
|
||||
'link',
|
||||
'note',
|
||||
'tag',
|
||||
'summary',
|
||||
'associativeLine',
|
||||
'formula',
|
||||
'attachment',
|
||||
'outerFrame',
|
||||
'annotation',
|
||||
'ai'
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'Toolbar',
|
||||
components: {
|
||||
@ -200,25 +221,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
isMobile: isMobile(),
|
||||
list: [
|
||||
'back',
|
||||
'forward',
|
||||
'painter',
|
||||
'siblingNode',
|
||||
'childNode',
|
||||
'deleteNode',
|
||||
'image',
|
||||
'icon',
|
||||
'link',
|
||||
'note',
|
||||
'tag',
|
||||
'summary',
|
||||
'associativeLine',
|
||||
'formula',
|
||||
'attachment',
|
||||
'outerFrame',
|
||||
'annotation'
|
||||
],
|
||||
horizontalList: [],
|
||||
verticalList: [],
|
||||
showMoreBtn: true,
|
||||
@ -238,8 +240,24 @@ export default {
|
||||
...mapState({
|
||||
isDark: state => state.localConfig.isDark,
|
||||
isHandleLocalFile: state => state.isHandleLocalFile,
|
||||
openNodeRichText: state => state.localConfig.openNodeRichText
|
||||
})
|
||||
openNodeRichText: state => state.localConfig.openNodeRichText,
|
||||
enableAi: state => state.enableAi
|
||||
}),
|
||||
|
||||
btnLit() {
|
||||
let res = [...defaultBtnList]
|
||||
if (!this.openNodeRichText) {
|
||||
res = res.filter(item => {
|
||||
return item !== 'formula'
|
||||
})
|
||||
}
|
||||
if (!this.enableAi) {
|
||||
res = res.filter(item => {
|
||||
return item !== 'ai'
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isHandleLocalFile(val) {
|
||||
@ -247,21 +265,9 @@ export default {
|
||||
Notification.closeAll()
|
||||
}
|
||||
},
|
||||
openNodeRichText: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
const index = this.list.findIndex(item => {
|
||||
return item === 'formula'
|
||||
})
|
||||
if (val) {
|
||||
if (index === -1) {
|
||||
this.list.splice(13, 0, 'formula')
|
||||
}
|
||||
} else {
|
||||
if (index !== -1) {
|
||||
this.list.splice(index, 1)
|
||||
}
|
||||
}
|
||||
btnLit: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.computeToolbarShow()
|
||||
}
|
||||
}
|
||||
@ -289,7 +295,7 @@ export default {
|
||||
computeToolbarShow() {
|
||||
if (!this.$refs.toolbarRef) return
|
||||
const windowWidth = window.innerWidth - 40
|
||||
const all = [...this.list]
|
||||
const all = [...this.btnLit]
|
||||
let index = 1
|
||||
const loopCheck = () => {
|
||||
if (index > all.length) return done()
|
||||
|
||||
@ -192,8 +192,20 @@
|
||||
v-if="item === 'annotation' && supportMark"
|
||||
:isDark="isDark"
|
||||
:dir="dir"
|
||||
:rightHasBtn="annotationRightHasBtn"
|
||||
@setAnnotation="onSetAnnotation"
|
||||
></NodeAnnotationBtn>
|
||||
<div
|
||||
v-if="item === 'ai'"
|
||||
class="toolbarBtn"
|
||||
:class="{
|
||||
disabled: hasGeneralization
|
||||
}"
|
||||
@click="aiCrate"
|
||||
>
|
||||
<span class="icon iconfont iconAIshengcheng"></span>
|
||||
<span class="text">{{ $t('toolbar.ai') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@ -246,6 +258,12 @@ export default {
|
||||
return node.isGeneralization
|
||||
}) !== -1
|
||||
)
|
||||
},
|
||||
annotationRightHasBtn() {
|
||||
const index = this.list.findIndex(item => {
|
||||
return item === 'annotation'
|
||||
})
|
||||
return index !== -1 && index < this.list.length - 1
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@ -313,6 +331,11 @@ export default {
|
||||
// 设置标记
|
||||
onSetAnnotation(...args) {
|
||||
this.$bus.$emit('execCommand', 'SET_NOTATION', this.activeNodes, ...args)
|
||||
},
|
||||
|
||||
// AI生成整体
|
||||
aiCrate() {
|
||||
this.$bus.$emit('ai_create_all')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -405,6 +428,7 @@ export default {
|
||||
|
||||
.text {
|
||||
margin-top: 3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subToolbar {
|
||||
|
||||
@ -1,32 +1,24 @@
|
||||
<template>
|
||||
<div class="workbencheContainer">
|
||||
<div class="workbencheContent">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workbencheContainer">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Workbenche',
|
||||
created () {
|
||||
document.title = '思绪思维导图'
|
||||
}
|
||||
name: 'Workbenche',
|
||||
created() {
|
||||
document.title = '思绪思维导图'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workbencheContainer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.workbencheContent {
|
||||
flex-grow: 1;
|
||||
}
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
width="480px"
|
||||
@close="onClose"
|
||||
>
|
||||
<div class="aboutBox">
|
||||
<div class="aboutBox" :class="{ isDark: isDark }">
|
||||
<img src="../../../assets/img/icon.png" alt="" />
|
||||
<h2>思绪思维导图</h2>
|
||||
<p>版本:{{ version }}</p>
|
||||
@ -29,6 +29,7 @@
|
||||
|
||||
<script>
|
||||
import pkg from '../../../../package.json'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
model: {
|
||||
@ -47,6 +48,11 @@ export default {
|
||||
version: pkg.version
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isDark: state => state.localConfig.isDark
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
value(val, oldVal) {
|
||||
this.dialogVisible = val
|
||||
@ -95,6 +101,13 @@ export default {
|
||||
justify-content: center;
|
||||
padding-bottom: 30px;
|
||||
|
||||
&.isDark {
|
||||
.h2,
|
||||
p {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
|
||||
@ -1,39 +1,76 @@
|
||||
<template>
|
||||
<div class="workbencheFileListContainer">
|
||||
<div class="title">
|
||||
<span>最近</span>
|
||||
<span class="clearBtn" @click="clear">清空</span>
|
||||
<div class="workbencheFileListContainer" :class="{ isDark: isDark }">
|
||||
<div class="header">
|
||||
<div class="headerLeft">
|
||||
<span class="title">{{ currentFolderName }}</span>
|
||||
</div>
|
||||
<div class="headerRight">
|
||||
<span
|
||||
class="textBtn"
|
||||
@click="deleteMultiFile"
|
||||
v-if="multipleSelection.length > 0"
|
||||
>删除文件</span
|
||||
>
|
||||
<template v-if="this.isRecent">
|
||||
<span
|
||||
class="textBtn"
|
||||
@click="deleteMultiRecord"
|
||||
v-if="multipleSelection.length > 0"
|
||||
>删除记录</span
|
||||
>
|
||||
<span class="textBtn" @click="clearRecent">清空记录</span>
|
||||
</template>
|
||||
<template v-if="!this.isRecent">
|
||||
<span
|
||||
class="textBtn"
|
||||
@click="deleteMultiFromList"
|
||||
v-if="multipleSelection.length > 0"
|
||||
>从列表删除</span
|
||||
>
|
||||
<span class="textBtn" @click="closeList">关闭文件夹</span>
|
||||
<span class="textBtn" @click="refreshList(true)">刷新</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fileListBox">
|
||||
<Empty v-if="list.length <= 0"></Empty>
|
||||
<el-table v-else :data="list" style="width: 100%">
|
||||
<el-table
|
||||
v-else
|
||||
:data="list"
|
||||
style="width: 100%"
|
||||
height="100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55"> </el-table-column>
|
||||
<el-table-column label="名称">
|
||||
<template slot-scope="scope">
|
||||
<span class="textBtn" @click="openFile(scope.row.url)">{{
|
||||
scope.row.name
|
||||
}}</span>
|
||||
<span
|
||||
class="textBtn"
|
||||
@click="openFile(scope.row.url, scope.$index)"
|
||||
>{{ scope.row.name }}</span
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="url" label="文件路径"> </el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<el-table-column label="操作" width="170">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip effect="light" content="编辑" placement="top">
|
||||
<el-tooltip effect="dark" content="编辑" placement="top">
|
||||
<el-button
|
||||
icon="el-icon-edit"
|
||||
circle
|
||||
size="mini"
|
||||
@click="openFile(scope.row.url)"
|
||||
@click="openFile(scope.row.url, scope.$index)"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="复制" placement="top">
|
||||
<el-tooltip effect="dark" content="复制" placement="top">
|
||||
<el-button
|
||||
icon="el-icon-document-copy"
|
||||
circle
|
||||
size="mini"
|
||||
@click="copyFile(scope.row.url)"
|
||||
@click="copyFile(scope.row.url, scope.$index)"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="删除" placement="top">
|
||||
<el-tooltip effect="dark" content="删除" placement="top">
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
@ -43,7 +80,7 @@
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
effect="dark"
|
||||
content="打开文件所在目录"
|
||||
placement="top"
|
||||
>
|
||||
@ -51,7 +88,7 @@
|
||||
icon="el-icon-folder-opened"
|
||||
circle
|
||||
size="mini"
|
||||
@click="openFileInDir(scope.row.url)"
|
||||
@click="openFileInDir(scope.row.url, scope.$index)"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
@ -63,6 +100,8 @@
|
||||
|
||||
<script>
|
||||
import Empty from '../components/Empty.vue'
|
||||
import { getFileName } from '@/utils'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -70,7 +109,20 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: []
|
||||
currentFolder: 'recent',
|
||||
currentFolderName: '最近',
|
||||
list: [],
|
||||
recentList: [],
|
||||
multipleSelection: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isDark: state => state.localConfig.isDark
|
||||
}),
|
||||
|
||||
isRecent() {
|
||||
return this.currentFolder === 'recent'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@ -78,20 +130,59 @@ export default {
|
||||
window.electronAPI.onRefreshRecentFileList(() => {
|
||||
this.getRecentFileList()
|
||||
})
|
||||
this.$bus.$on('changeFolder', this.onChangeFolder)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$bus.$off('changeFolder', this.onChangeFolder)
|
||||
},
|
||||
methods: {
|
||||
// 切换文件夹
|
||||
onChangeFolder(folder, files) {
|
||||
this.multipleSelection = []
|
||||
this.currentFolder = folder
|
||||
if (folder === 'recent') {
|
||||
this.currentFolderName = '最近'
|
||||
this.list = this.recentList
|
||||
} else {
|
||||
const arr = folder.split(/[\/\\]/g)
|
||||
this.currentFolderName = arr[arr.length - 1]
|
||||
this.list = files
|
||||
}
|
||||
},
|
||||
|
||||
// 表格多选
|
||||
handleSelectionChange(val) {
|
||||
this.multipleSelection = val
|
||||
},
|
||||
|
||||
// 获取最近文件列表
|
||||
async getRecentFileList() {
|
||||
this.multipleSelection = []
|
||||
let list = await window.electronAPI.getRecentFileList()
|
||||
this.list = list.reverse()
|
||||
this.recentList = list.reverse()
|
||||
if (this.isRecent) {
|
||||
this.list = this.recentList
|
||||
}
|
||||
},
|
||||
|
||||
// 如果文件不存在则从列表删除,否则执行回调函数
|
||||
async checkExist(file, index, cb = () => {}) {
|
||||
try {
|
||||
await window.electronAPI.checkFileExist(file)
|
||||
cb()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.list.splice(index, 1)
|
||||
window.electronAPI.removeFileInRecent(file)
|
||||
this.$message.error('文件不存在')
|
||||
}
|
||||
},
|
||||
|
||||
// 在文件夹里打开文件
|
||||
async openFileInDir(file) {
|
||||
const res = await window.electronAPI.openFileInDir(file)
|
||||
if (res) {
|
||||
this.$message.error(res)
|
||||
}
|
||||
openFileInDir(file, index) {
|
||||
this.checkExist(file, index, () => {
|
||||
window.electronAPI.openFileInDir(file)
|
||||
})
|
||||
},
|
||||
|
||||
// 删除文件
|
||||
@ -99,59 +190,180 @@ export default {
|
||||
this.$confirm('确定删除该文件?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
type: 'warning',
|
||||
customClass: this.isDark ? 'darkElMessageBox' : ''
|
||||
})
|
||||
.then(() => {
|
||||
this.checkExist(file, index, async () => {
|
||||
try {
|
||||
const error = await window.electronAPI.deleteFile(file)
|
||||
if (error) {
|
||||
this.$message.error(error || '删除失败')
|
||||
} else {
|
||||
this.list.splice(index, 1)
|
||||
this.$message.success('删除成功')
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error(error || '删除失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
// 编辑文件
|
||||
openFile(file, index) {
|
||||
this.checkExist(file, index, async () => {
|
||||
const res = await window.electronAPI.openFile(file)
|
||||
if (res) {
|
||||
this.$message.error(res)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 复制文件
|
||||
copyFile(file, index) {
|
||||
this.checkExist(file, index, async () => {
|
||||
try {
|
||||
const error = await window.electronAPI.copyFile(file)
|
||||
if (error) {
|
||||
this.$message.error(error || '复制失败')
|
||||
} else {
|
||||
this.$message.success('复制成功')
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.$message.error('复制失败')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 同时删除多个文件
|
||||
deleteMultiFile() {
|
||||
if (this.multipleSelection.length <= 0) {
|
||||
return
|
||||
}
|
||||
this.$confirm('是否确定删除所选文件?会删除源文件。', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
customClass: this.isDark ? 'darkElMessageBox' : ''
|
||||
})
|
||||
.then(async () => {
|
||||
let res = await window.electronAPI.deleteFile(file)
|
||||
if (res) {
|
||||
this.$message.error(res || '删除失败')
|
||||
const fileList = this.multipleSelection.map(item => {
|
||||
return item.url
|
||||
})
|
||||
const failList = await window.electronAPI.deleteMultiFile(fileList)
|
||||
const succList = fileList.filter(item => {
|
||||
return !failList.includes(item)
|
||||
})
|
||||
window.electronAPI.removeMultiFileInRecent(succList)
|
||||
if (!this.isRecent) {
|
||||
this.list = this.list.filter(item => {
|
||||
return !succList.includes(item.url)
|
||||
})
|
||||
}
|
||||
if (failList.length > 0) {
|
||||
this.$message.error('部分文件删除失败')
|
||||
} else {
|
||||
this.list.splice(index, 1)
|
||||
this.$message.success('删除成功')
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
// 编辑文件
|
||||
async openFile(file) {
|
||||
const res = await window.electronAPI.openFile(file)
|
||||
if (res) {
|
||||
this.$message.error(res)
|
||||
// 同时删除多个最近记录
|
||||
deleteMultiRecord() {
|
||||
if (this.multipleSelection.length <= 0) {
|
||||
return
|
||||
}
|
||||
this.$confirm('是否确定删除所选记录?不会删除源文件。', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
customClass: this.isDark ? 'darkElMessageBox' : ''
|
||||
})
|
||||
.then(async () => {
|
||||
const error = await window.electronAPI.removeMultiFileInRecent(
|
||||
this.multipleSelection.map(item => {
|
||||
return item.url
|
||||
})
|
||||
)
|
||||
if (error) {
|
||||
this.$message.error(error || '删除失败')
|
||||
} else {
|
||||
this.$message.success('删除成功')
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error || '删除失败')
|
||||
})
|
||||
},
|
||||
|
||||
// 清空最近文件列表
|
||||
clear() {
|
||||
this.$confirm('确定清空最近文件?', '提示', {
|
||||
clearRecent() {
|
||||
this.$confirm('确定清空最近文件?不会删除源文件。', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
type: 'warning',
|
||||
customClass: this.isDark ? 'darkElMessageBox' : ''
|
||||
})
|
||||
.then(async () => {
|
||||
let res = await window.electronAPI.clearRecentFileList()
|
||||
if (res) {
|
||||
this.$message.error('清空失败')
|
||||
const error = await window.electronAPI.clearRecentFileList()
|
||||
if (error) {
|
||||
this.$message.error(error || '清空失败')
|
||||
} else {
|
||||
this.list = []
|
||||
this.recentList = []
|
||||
this.$message.success('清空成功')
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error || '清空失败')
|
||||
})
|
||||
},
|
||||
|
||||
// 从列表里删除多个文件
|
||||
deleteMultiFromList() {
|
||||
if (this.multipleSelection.length <= 0) {
|
||||
return
|
||||
}
|
||||
const urlList = this.multipleSelection.map(item => {
|
||||
return item.url
|
||||
})
|
||||
this.$confirm('是否确定从列表中删除所选文件?不会删除源文件。', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
customClass: this.isDark ? 'darkElMessageBox' : ''
|
||||
})
|
||||
.then(() => {
|
||||
this.list = this.list.filter(item => {
|
||||
return !urlList.includes(item.url)
|
||||
})
|
||||
this.$message.success('删除成功')
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
// 复制文件
|
||||
async copyFile(file) {
|
||||
try {
|
||||
const res = await window.electronAPI.copyFile(file)
|
||||
if (res) {
|
||||
this.$message.error(res)
|
||||
} else {
|
||||
this.$message.success('复制成功')
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('复制失败')
|
||||
}
|
||||
// 关闭文件夹
|
||||
closeList() {
|
||||
this.$confirm('是否确定关闭该文件夹?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
customClass: this.isDark ? 'darkElMessageBox' : ''
|
||||
})
|
||||
.then(() => {
|
||||
this.$bus.$emit('closeFolder', this.currentFolder)
|
||||
this.$message.success('关闭成功')
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
// 刷新
|
||||
refreshList(showTip = true) {
|
||||
this.$bus.$emit('refreshFolder', this.currentFolder, showTip)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -159,7 +371,7 @@ export default {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workbencheFileListContainer {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
@ -167,30 +379,89 @@ export default {
|
||||
padding-top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
height: 65px;
|
||||
flex-shrink: 0;
|
||||
&.isDark {
|
||||
background-color: rgb(55, 59, 63);
|
||||
|
||||
.header {
|
||||
border-bottom: 1px solid hsla(0, 0%, 100%, 0.1);
|
||||
|
||||
.headerLeft {
|
||||
.title {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .el-table {
|
||||
background-color: rgb(55, 59, 63);
|
||||
|
||||
.cell {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
||||
th.el-table__cell.is-leaf,
|
||||
td.el-table__cell {
|
||||
border-bottom: 1px solid hsla(0, 0%, 100%, 0.1);
|
||||
}
|
||||
|
||||
td.el-table__cell,
|
||||
th.el-table__cell,
|
||||
.el-table--border::after,
|
||||
.el-table--group::after,
|
||||
&::before {
|
||||
background-color: rgb(55, 59, 63);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 65px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
|
||||
.clearBtn {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #409eff;
|
||||
.headerLeft {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tip {
|
||||
font-size: 12px;
|
||||
margin-left: 12px;
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
.headerRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.textBtn {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #409eff;
|
||||
margin-left: 12px;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fileListBox {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.textBtn {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
width="480px"
|
||||
@close="onClose"
|
||||
>
|
||||
<div class="settingBox">
|
||||
<div class="settingBox" :class="{ isDark: isDark }">
|
||||
<div class="row">
|
||||
<div class="label">默认结构</div>
|
||||
<el-select
|
||||
@ -58,6 +58,13 @@
|
||||
@change="onChange"
|
||||
></el-checkbox>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label">暗黑模式</div>
|
||||
<el-checkbox
|
||||
v-model="otherConfig.isDark"
|
||||
@change="toggleDark"
|
||||
></el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
@ -67,6 +74,7 @@ import { layoutList } from 'simple-mind-map/src/constants/constant'
|
||||
import { layoutImgMap } from '@/config/constant.js'
|
||||
import themeList from 'simple-mind-map-plugin-themes/themeList'
|
||||
import themeImgMap from 'simple-mind-map-plugin-themes/themeImgMap'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
model: {
|
||||
@ -98,14 +106,23 @@ export default {
|
||||
theme: '',
|
||||
viewTranslateChangeTriggerAutoSave: false
|
||||
},
|
||||
clientConfig: null
|
||||
clientConfig: null,
|
||||
otherConfig: {
|
||||
isDark: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isDark: state => state.localConfig.isDark
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
value(val, oldVal) {
|
||||
this.dialogVisible = val
|
||||
if (val && !oldVal) {
|
||||
this.getConfig()
|
||||
this.getOtherConfig()
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -113,6 +130,8 @@ export default {
|
||||
this.onClose()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setLocalConfig']),
|
||||
|
||||
onClose() {
|
||||
this.$emit('change', false)
|
||||
},
|
||||
@ -130,6 +149,16 @@ export default {
|
||||
...this.clientConfig,
|
||||
...this.config
|
||||
})
|
||||
},
|
||||
|
||||
getOtherConfig() {
|
||||
this.otherConfig.isDark = this.isDark
|
||||
},
|
||||
|
||||
toggleDark(val) {
|
||||
this.setLocalConfig({
|
||||
isDark: val
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,6 +173,14 @@ export default {
|
||||
.settingBox {
|
||||
padding: 20px;
|
||||
|
||||
&.isDark {
|
||||
.row {
|
||||
.label {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -1,27 +1,153 @@
|
||||
<template>
|
||||
<div class="workbencheSidebarContainer">
|
||||
<div class="workbencheSidebarContainer" :class="{ isDark: isDark }">
|
||||
<div class="createBtn" @click="create">开始新建</div>
|
||||
<div class="line"></div>
|
||||
<div class="btn" @click="openLocalFile">
|
||||
<span class="icon iconfont icondakai"></span>
|
||||
<span class="icon iconfont iconwenjian1"></span>
|
||||
<span class="text">打开本地文件</span>
|
||||
</div>
|
||||
<div class="btn active">
|
||||
<div class="btn" @click="openLocalFolder">
|
||||
<span class="icon iconfont icondakai"></span>
|
||||
<span class="text">打开本地文件夹</span>
|
||||
</div>
|
||||
<div
|
||||
class="btn"
|
||||
:class="{ active: currentActive == 'recent' }"
|
||||
@click="changeTab('recent')"
|
||||
>
|
||||
<span class="icon iconfont iconzuijinliulan"></span>
|
||||
<span class="text">最近文件</span>
|
||||
</div>
|
||||
<div class="folderList">
|
||||
<div
|
||||
class="folderItem"
|
||||
v-for="item in dirList"
|
||||
:class="{ active: currentActive == item.dir }"
|
||||
@click="changeTab(item.dir)"
|
||||
>
|
||||
<span class="icon iconfont iconwenjianjia"></span>
|
||||
<span class="text"> {{ item.dirName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { create } from '../utils'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dirList: [],
|
||||
currentActive: 'recent'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isDark: state => state.localConfig.isDark
|
||||
})
|
||||
},
|
||||
created() {
|
||||
this.$bus.$on('closeFolder', this.onCloseFolder)
|
||||
this.$bus.$on('refreshFolder', this.onRefreshFolder)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$bus.$off('closeFolder', this.onCloseFolder)
|
||||
this.$bus.$off('refreshFolder', this.onRefreshFolder)
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setCurrentFolder']),
|
||||
|
||||
create,
|
||||
|
||||
openLocalFile() {
|
||||
window.electronAPI.selectOpenFile()
|
||||
// 打开文件
|
||||
async openLocalFile() {
|
||||
const file = await window.electronAPI.selectOpenFile()
|
||||
if (file) {
|
||||
this.changeTab('recent')
|
||||
}
|
||||
},
|
||||
|
||||
// 打开文件夹
|
||||
async openLocalFolder() {
|
||||
const dir = await window.electronAPI.selectOpenFolder()
|
||||
if (dir) {
|
||||
const files = await window.electronAPI.getFilesInDir(dir, '.smm')
|
||||
this.addDirFileList({
|
||||
dir,
|
||||
files
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 关闭文件夹
|
||||
onCloseFolder(folder) {
|
||||
const index = this.dirList.findIndex(item => {
|
||||
return item.dir === folder
|
||||
})
|
||||
if (index !== -1) {
|
||||
const newFolder = index > 0 ? this.dirList[index - 1].dir : 'recent'
|
||||
this.dirList.splice(index, 1)
|
||||
this.changeTab(newFolder)
|
||||
}
|
||||
},
|
||||
|
||||
// 添加文件夹到文件夹列表
|
||||
addDirFileList({ dir, files }, force) {
|
||||
const sep = this.IS_WIN ? '\\' : '/'
|
||||
files = files.map(name => {
|
||||
return {
|
||||
url: (dir[dir.length - 1] === sep ? dir : dir + sep) + name,
|
||||
name
|
||||
}
|
||||
})
|
||||
const exist = this.dirList.find(item => {
|
||||
return item.dir === dir
|
||||
})
|
||||
if (exist) {
|
||||
exist.files = files
|
||||
} else {
|
||||
const arr = dir.split(/[\/\\]/g)
|
||||
this.dirList.push({
|
||||
dir,
|
||||
dirName: arr[arr.length - 1],
|
||||
files
|
||||
})
|
||||
}
|
||||
this.changeTab(dir, force)
|
||||
},
|
||||
|
||||
// 切换文件夹
|
||||
changeTab(tab, force) {
|
||||
if (this.currentActive === tab && !force) return
|
||||
this.currentActive = tab
|
||||
let files = []
|
||||
if (tab !== 'recent') {
|
||||
const folder = this.dirList.find(item => {
|
||||
return item.dir === tab
|
||||
})
|
||||
if (folder) {
|
||||
files = folder.files
|
||||
}
|
||||
}
|
||||
this.setCurrentFolder(tab === 'recent' ? '' : tab)
|
||||
this.$bus.$emit('changeFolder', tab, files)
|
||||
},
|
||||
|
||||
// 刷新指定文件夹
|
||||
async onRefreshFolder(folder, showTip = true) {
|
||||
const files = await window.electronAPI.getFilesInDir(folder, '.smm')
|
||||
this.addDirFileList(
|
||||
{
|
||||
dir: folder,
|
||||
files
|
||||
},
|
||||
true
|
||||
)
|
||||
if (showTip) {
|
||||
this.$message.success('刷新成功')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -36,6 +162,24 @@ export default {
|
||||
border-radius: 10px;
|
||||
margin-right: 20px;
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
&.isDark {
|
||||
background-color: rgb(55, 59, 63);
|
||||
|
||||
.line {
|
||||
background-color: hsla(0, 0%, 100%, 0.1);
|
||||
}
|
||||
|
||||
.btn,
|
||||
.folderItem {
|
||||
background-color: rgb(39, 42, 46);
|
||||
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.createBtn {
|
||||
width: 100%;
|
||||
@ -49,6 +193,7 @@ export default {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
@ -60,9 +205,11 @@ export default {
|
||||
height: 1px;
|
||||
background-color: #e4e7ed;
|
||||
margin: 20px 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
.btn,
|
||||
.folderItem {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background-color: #fff;
|
||||
@ -74,7 +221,8 @@ export default {
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 8px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
@ -86,5 +234,11 @@ export default {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.folderList {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div class="winControl noDrag" v-if="IS_WIN || IS_LINUX">
|
||||
<div
|
||||
class="winControl noDrag"
|
||||
:class="{ isDark: isDark }"
|
||||
v-if="IS_WIN || IS_LINUX"
|
||||
>
|
||||
<div class="winControlBtn iconfont iconzuixiaohua" @click="minimize"></div>
|
||||
<div
|
||||
class="winControlBtn iconfont"
|
||||
@ -11,12 +15,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isMaximize: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isDark: state => state.localConfig.isDark
|
||||
})
|
||||
},
|
||||
async created() {
|
||||
try {
|
||||
this.isMaximize = await window.electronAPI.getIsMaximize(
|
||||
@ -54,6 +65,17 @@ export default {
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
|
||||
&.isDark {
|
||||
.winControlBtn {
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: #373b3f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.winControlBtn {
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
|
||||
@ -9,7 +9,9 @@
|
||||
placeholder=""
|
||||
@blur="rename"
|
||||
@keyup.enter="rename"
|
||||
></el-input>
|
||||
>
|
||||
<template slot="append">.smm</template>
|
||||
</el-input>
|
||||
<div class="modifyDotBox">
|
||||
<div class="modifyDot" v-show="isUnSave"></div>
|
||||
</div>
|
||||
@ -83,7 +85,8 @@ export default {
|
||||
this.$confirm('有操作尚未保存,是否确认关闭?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
type: 'warning',
|
||||
customClass: this.isDark ? 'darkElMessageBox' : ''
|
||||
})
|
||||
.then(async () => {
|
||||
resolve()
|
||||
@ -99,7 +102,6 @@ export default {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workbencheEditContainer {
|
||||
|
||||
&.isDark {
|
||||
.workbencheEditHeader {
|
||||
background-color: #262a2e;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="workbencheHomeContainer"
|
||||
:class="{ isDark: isDark }"
|
||||
@drop="onDrop"
|
||||
@dragenter="onDragenter"
|
||||
@dragover="onDragover"
|
||||
@ -40,6 +41,8 @@ import FileList from '../components/FileList.vue'
|
||||
import AboutDialog from '../components/AboutDialog.vue'
|
||||
import SponsorDialog from '../components/SponsorDialog.vue'
|
||||
import SettingDialog from '../components/SettingDialog.vue'
|
||||
import { getLocalConfig } from '@/api'
|
||||
import { mapState, mapActions, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -58,7 +61,33 @@ export default {
|
||||
showSettingDialog: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isDark: state => state.localConfig.isDark
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
isDark() {
|
||||
this.setBodyDark()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initLocalConfig()
|
||||
this.setBodyDark()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setLocalConfig']),
|
||||
|
||||
initLocalConfig() {
|
||||
let config = getLocalConfig()
|
||||
if (config) {
|
||||
this.setLocalConfig({
|
||||
...this.$store.state.localConfig,
|
||||
...config
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
handleCommand(command) {
|
||||
switch (command) {
|
||||
case 'about':
|
||||
@ -85,6 +114,12 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
setBodyDark() {
|
||||
this.isDark
|
||||
? document.body.classList.add('isDark')
|
||||
: document.body.classList.remove('isDark')
|
||||
},
|
||||
|
||||
onDrop(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@ -141,6 +176,21 @@ export default {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
&.isDark {
|
||||
background: rgb(39, 42, 46);
|
||||
|
||||
.workbencheHomeHeader {
|
||||
background-color: #262a2e;
|
||||
|
||||
.rightBar {
|
||||
.settingBtn {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workbencheHomeHeader {
|
||||
position: relative;
|
||||
@ -169,7 +219,8 @@ export default {
|
||||
}
|
||||
|
||||
.workbencheHomeContent {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
|
||||
@ -7,8 +7,8 @@ Vue.use(Vuex)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
fileName: '',// 本地的文件名
|
||||
isUnSave: false,// 当前操作是否未保存
|
||||
fileName: '', // 本地的文件名
|
||||
isUnSave: false, // 当前操作是否未保存
|
||||
mindMapData: null, // 思维导图数据
|
||||
isHandleLocalFile: false, // 是否操作的是本地文件
|
||||
localConfig: {
|
||||
@ -22,12 +22,14 @@ const store = new Vuex.Store({
|
||||
isShowScrollbar: false,
|
||||
// 是否开启手绘风格
|
||||
isUseHandDrawnLikeStyle: false,
|
||||
// 是否开启动量效果
|
||||
isUseMomentum: true,
|
||||
// 是否是暗黑模式
|
||||
isDark: false
|
||||
},
|
||||
activeSidebar: '', // 当前显示的侧边栏
|
||||
localEditList: [],// 客户端中正在编辑的思维导图列表
|
||||
isOutlineEdit: false,// 是否是大纲编辑模式
|
||||
localEditList: [], // 客户端中正在编辑的思维导图列表
|
||||
isOutlineEdit: false, // 是否是大纲编辑模式
|
||||
isReadonly: false, // 是否只读
|
||||
isSourceCodeEdit: false, // 是否是源码编辑模式
|
||||
extraTextOnExport: '', // 导出时底部添加的文字
|
||||
@ -38,7 +40,17 @@ const store = new Vuex.Store({
|
||||
supportExcel: false, // 是否支持Excel插件
|
||||
supportCheckbox: false, // 是否支持Checkbox插件
|
||||
supportLineFlow: false, // 是否支持LineFlow插件
|
||||
isDragOutlineTreeNode: false // 当前是否正在拖拽大纲树的节点
|
||||
supportMomentum: false, // 是否支持Momentum插件
|
||||
isDragOutlineTreeNode: false, // 当前是否正在拖拽大纲树的节点
|
||||
aiConfig: {
|
||||
api: 'http://ark.cn-beijing.volces.com/api/v3/chat/completions',
|
||||
key: '',
|
||||
model: '',
|
||||
port: 3456,
|
||||
method: 'POST'
|
||||
},
|
||||
enableAi: false, // 是否开启AI功能
|
||||
currentFolder: '' // 当前打开的目录
|
||||
},
|
||||
mutations: {
|
||||
// 设置本地文件名
|
||||
@ -51,6 +63,11 @@ const store = new Vuex.Store({
|
||||
state.isUnSave = data
|
||||
},
|
||||
|
||||
setCurrentFolder(state, data) {
|
||||
localStorage.setItem('currentFolder', data)
|
||||
state.currentFolder = data
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-10 14:50:01
|
||||
@ -67,11 +84,18 @@ const store = new Vuex.Store({
|
||||
|
||||
// 设置本地配置
|
||||
setLocalConfig(state, data) {
|
||||
state.localConfig = {
|
||||
const aiConfigKeys = Object.keys(state.aiConfig)
|
||||
Object.keys(data).forEach(key => {
|
||||
if (aiConfigKeys.includes(key)) {
|
||||
state.aiConfig[key] = data[key]
|
||||
} else {
|
||||
state.localConfig[key] = data[key]
|
||||
}
|
||||
})
|
||||
storeLocalConfig({
|
||||
...state.localConfig,
|
||||
...data
|
||||
}
|
||||
storeLocalConfig(state.localConfig)
|
||||
...state.aiConfig
|
||||
})
|
||||
},
|
||||
|
||||
// 设置当前显示的侧边栏
|
||||
@ -139,9 +163,19 @@ const store = new Vuex.Store({
|
||||
state.supportLineFlow = data
|
||||
},
|
||||
|
||||
// 设置是否支持Momentum插件
|
||||
setSupportMomentum(state, data) {
|
||||
state.supportMomentum = data
|
||||
},
|
||||
|
||||
// 设置树节点拖拽
|
||||
setIsDragOutlineTreeNode(state, data) {
|
||||
state.isDragOutlineTreeNode = data
|
||||
},
|
||||
|
||||
// 设置是否启用AI功能
|
||||
setEnableAi(state, data) {
|
||||
state.enableAi = data
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
||||
120
web/src/utils/ai.js
Normal file
120
web/src/utils/ai.js
Normal file
@ -0,0 +1,120 @@
|
||||
class Ai {
|
||||
constructor(options = {}) {
|
||||
this.options = options
|
||||
|
||||
this.baseData = {}
|
||||
this.controller = null
|
||||
this.currentChunk = ''
|
||||
this.content = ''
|
||||
}
|
||||
|
||||
init(type = 'huoshan', options = {}) {
|
||||
// 火山引擎接口
|
||||
if (type === 'huoshan') {
|
||||
this.baseData = {
|
||||
api: options.api,
|
||||
method: options.method,
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + options.key
|
||||
},
|
||||
data: {
|
||||
model: options.model,
|
||||
stream: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async request(data, progress = () => {}, end = () => {}, err = () => {}) {
|
||||
try {
|
||||
const res = await this.postMsg(data)
|
||||
const decoder = new TextDecoder()
|
||||
while (1) {
|
||||
const { done, value } = await res.read()
|
||||
if (done) {
|
||||
return
|
||||
}
|
||||
// 拿到当前切片的数据
|
||||
const text = decoder.decode(value)
|
||||
// 处理切片数据
|
||||
let chunk = this.handleChunkData(text)
|
||||
// 判断是否有不完整切片,如果有,合并下一次处理,没有则获取数据
|
||||
if (this.currentChunk) continue
|
||||
let isEnd = false
|
||||
const list = chunk
|
||||
.split('\n')
|
||||
.filter(item => {
|
||||
isEnd = item.includes('[DONE]')
|
||||
return !!item && !isEnd
|
||||
})
|
||||
.map(item => {
|
||||
return JSON.parse(item.replace(/^data:/, ''))
|
||||
})
|
||||
list.forEach(item => {
|
||||
this.content += item.choices
|
||||
.map(item2 => {
|
||||
return item2.delta.content
|
||||
})
|
||||
.join('')
|
||||
})
|
||||
progress(this.content)
|
||||
if (isEnd) {
|
||||
end(this.content)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
// 手动停止请求不需要触发错误回调
|
||||
if (!(error && error.name === 'AbortError')) {
|
||||
err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async postMsg(data) {
|
||||
this.controller = new AbortController()
|
||||
const res = await fetch(`http://localhost:${this.options.port}/ai/chat`, {
|
||||
signal: this.controller.signal,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...this.baseData,
|
||||
data: {
|
||||
...this.baseData.data,
|
||||
...data
|
||||
}
|
||||
})
|
||||
})
|
||||
if (res.status && res.status !== 200) {
|
||||
return false
|
||||
}
|
||||
return res.body.getReader()
|
||||
}
|
||||
|
||||
handleChunkData(chunk) {
|
||||
chunk = chunk.trim()
|
||||
// 如果存在上一个切片
|
||||
if (this.currentChunk) {
|
||||
chunk = this.currentChunk + chunk
|
||||
this.currentChunk = ''
|
||||
}
|
||||
// 如果存在done,认为是完整切片且是最后一个切片
|
||||
if (chunk.includes('[DONE]')) {
|
||||
return chunk
|
||||
}
|
||||
// 最后一个字符串不为},则默认切片不完整,保存与下次拼接使用(这种方法不严谨,但已经能解决大部分场景的问题)
|
||||
if (chunk[chunk.length - 1] !== '}') {
|
||||
this.currentChunk = chunk
|
||||
}
|
||||
return chunk
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.controller.abort()
|
||||
this.controller = new AbortController()
|
||||
}
|
||||
}
|
||||
|
||||
export default Ai
|
||||
@ -118,3 +118,35 @@ export const setImgToClipboard = img => {
|
||||
navigator.clipboard.write(data)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取一个文件路径中的文件名
|
||||
export const getFileName = (filePath) => {
|
||||
const res = filePath.match(/([^/]+)\.smm$/)
|
||||
if (res && res[1]) {
|
||||
return res[1]
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// 打印大纲
|
||||
export const printOutline = el => {
|
||||
const printContent = el.outerHTML
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.setAttribute('style', 'position: absolute; width: 0; height: 0;')
|
||||
document.body.appendChild(iframe)
|
||||
const iframeDoc = iframe.contentWindow.document
|
||||
// 将当前页面的所有样式添加到iframe中
|
||||
const styleList = document.querySelectorAll('style')
|
||||
Array.from(styleList).forEach(el => {
|
||||
iframeDoc.write(el.outerHTML)
|
||||
})
|
||||
// 设置打印展示方式 - 纵向展示
|
||||
iframeDoc.write('<style media="print">@page {size: portrait;}</style>')
|
||||
// 写入内容
|
||||
iframeDoc.write('<div>' + printContent + '</div>')
|
||||
setTimeout(function() {
|
||||
iframe.contentWindow?.print()
|
||||
document.body.removeChild(iframe)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user