Compare commits

...

16 Commits

Author SHA1 Message Date
wanglin2
c789a95e4e 支持直接双击文件打开应用进入编辑 2023-05-05 17:37:01 +08:00
wanglin2
b0532072c2 修改打包配置 2023-05-05 16:59:39 +08:00
wanglin2
7d21ae4618 打包 2023-05-05 16:04:15 +08:00
wanglin2
0e608de9da 基本完成 2023-05-05 14:55:01 +08:00
wanglin2
fbff68c635 开发中 2023-05-05 10:51:39 +08:00
wanglin2
ec9517c491 开发中 2023-05-04 22:40:44 +08:00
wanglin2
26e3158bd8 开发中 2023-05-04 17:54:31 +08:00
wanglin2
c2c9de1c03 更新 2023-05-03 22:06:52 +08:00
wanglin2
27d2238d20 更新 2023-05-03 21:26:13 +08:00
wanglin2
d6e625efa6 日常提交 2023-03-14 09:40:09 +08:00
wanglin2
fba0ece8fd 合并主分支 2023-03-08 09:47:35 +08:00
wanglin2
78ed038012 重新设计客户端架构 2023-03-07 16:39:33 +08:00
wanglin2
58f7be77ed Merge branch 'main' into electron 2023-03-06 21:56:30 +08:00
wanglin2
362160c67b 使用Vue CLI Plugin Electron Builder插件 2023-03-06 21:29:59 +08:00
wanglin2
c8a3c569e1 新增electron相关页面 2023-03-06 20:59:00 +08:00
wanglin2
6bc309e3fa 增加图标 2023-03-06 20:57:55 +08:00
37 changed files with 14415 additions and 7882 deletions

BIN
web/build/icons/icon.icns Normal file

Binary file not shown.

BIN
web/build/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
web/build/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

2023
web/dist_electron/index.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,84 @@
{
"name": "thoughts",
"version": "0.1.0",
"private": true,
"description": "一个简洁的思维导图",
"author": "街角小林<1013335014@qq.com>",
"repository": {
"type": "git",
"url": "https://github.com/wanglin2/mind-map"
},
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build && node ../copy.js",
"lint": "vue-cli-service lint",
"autoBuildDoc": "node ./scripts/autoBuildDoc.js",
"buildDoc": "node ./scripts/buildDoc.js",
"electron:build": "vue-cli-service electron:build",
"electron:build-all": "vue-cli-service electron:build -p never -mwl",
"electron:build-mac": "vue-cli-service electron:build -p never -m",
"electron:build-win": "vue-cli-service electron:build -p never -w",
"electron:build-linux": "vue-cli-service electron:build -p never -l",
"electron:serve": "vue-cli-service electron:serve",
"buildLibrary": "vue-cli-service build --target lib --name simpleMindMap ../simple-mind-map/full.js --dest ../simple-mind-map/dist && esbuild ../simple-mind-map/full.js --bundle --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.js",
"format": "prettier --write src/* src/*/* src/*/*/* src/*/*/*/*",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"main": "background.js",
"dependencies": {
"@toast-ui/editor": "^3.1.5",
"core-js": "^3.6.5",
"electron-json-storage": "^4.6.0",
"element-ui": "^2.15.1",
"fs-extra": "^7.0.1",
"highlight.js": "^10.7.3",
"open": "^6.4.0",
"uuid": "^3.4.0",
"v-viewer": "^1.6.4",
"vue": "^2.6.11",
"vue-i18n": "^8.27.2",
"vue-router": "^3.5.1",
"vuex": "^3.6.2",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.0",
"@vue/cli-plugin-eslint": "^4.5.0",
"@vue/cli-service": "^4.5.0",
"babel-eslint": "^10.1.0",
"chokidar": "^3.5.3",
"electron": "^23.1.1",
"electron-devtools-installer": "^3.1.0",
"esbuild": "^0.17.15",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"less": "^3.12.2",
"less-loader": "^7.1.0",
"markdown-it": "^13.0.1",
"markdown-it-checkbox": "^1.1.0",
"prettier": "^1.19.1",
"vue-cli-plugin-electron-builder": "~2.1.1",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.44.2"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

View File

@@ -0,0 +1,122 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ({
/***/ "./src/electron/preload.js":
/*!*********************************!*\
!*** ./src/electron/preload.js ***!
\*********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("const { contextBridge, ipcRenderer } = __webpack_require__(/*! electron */ \"electron\")\r\n\r\ncontextBridge.exposeInMainWorld('platform', process.platform)\r\ncontextBridge.exposeInMainWorld('IS_ELECTRON', true)\r\n\r\ncontextBridge.exposeInMainWorld('electronAPI', {\r\n minimize: () => ipcRenderer.send('minimize'),\r\n maximize: () => ipcRenderer.send('maximize'),\r\n unmaximize: () => ipcRenderer.send('unmaximize'),\r\n close: () => ipcRenderer.send('close'),\r\n destroy: () => ipcRenderer.send('destroy'),\r\n create: id => ipcRenderer.send('create', id),\r\n getFileContent: id => ipcRenderer.invoke('getFileContent', id),\r\n save: (id, data, fileName) => ipcRenderer.invoke('save', id, data, fileName),\r\n rename: (id, name) => ipcRenderer.invoke('rename', id, name),\r\n openUrl: url => ipcRenderer.send('openUrl', url),\r\n addRecentFileList: (fileList) => ipcRenderer.invoke('addRecentFileList', fileList),\r\n getRecentFileList: () => ipcRenderer.invoke('getRecentFileList'),\r\n clearRecentFileList: () => ipcRenderer.invoke('clearRecentFileList'),\r\n openFileInDir: file => ipcRenderer.send('openFileInDir', file),\r\n deleteFile: file => ipcRenderer.invoke('deleteFile', file),\r\n onRefreshRecentFileList: callback =>\r\n ipcRenderer.on('refreshRecentFileList', callback),\r\n openFile: file => ipcRenderer.send('openFile', file),\r\n selectOpenFile: () => ipcRenderer.send('selectOpenFile'),\r\n copyFile: file => ipcRenderer.invoke('copyFile', file)\r\n})\r\n\n\n//# sourceURL=webpack:///./src/electron/preload.js?");
/***/ }),
/***/ 0:
/*!***************************************!*\
!*** multi ./src/electron/preload.js ***!
\***************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("module.exports = __webpack_require__(/*! E:\\wanglin\\mind-map\\web\\src\\electron\\preload.js */\"./src/electron/preload.js\");\n\n\n//# sourceURL=webpack:///multi_./src/electron/preload.js?");
/***/ }),
/***/ "electron":
/*!***************************!*\
!*** external "electron" ***!
\***************************/
/*! no static exports found */
/***/ (function(module, exports) {
eval("module.exports = require(\"electron\");\n\n//# sourceURL=webpack:///external_%22electron%22?");
/***/ })
/******/ });

18423
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,20 +2,39 @@
"name": "thoughts",
"version": "0.1.0",
"private": true,
"description": "一个简洁的思维导图",
"author": "街角小林<1013335014@qq.com>",
"repository": {
"type": "git",
"url": "https://github.com/wanglin2/mind-map"
},
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build && node ../copy.js",
"lint": "vue-cli-service lint",
"autoBuildDoc": "node ./scripts/autoBuildDoc.js",
"buildDoc": "node ./scripts/buildDoc.js",
"electron:build": "vue-cli-service electron:build",
"electron:build-all": "vue-cli-service electron:build -p never -mwl",
"electron:build-mac": "vue-cli-service electron:build -p never -m",
"electron:build-win": "vue-cli-service electron:build -p never -w",
"electron:build-linux": "vue-cli-service electron:build -p never -l",
"electron:serve": "vue-cli-service electron:serve",
"buildLibrary": "vue-cli-service build --target lib --name simpleMindMap ../simple-mind-map/full.js --dest ../simple-mind-map/dist && esbuild ../simple-mind-map/full.js --bundle --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.js",
"format": "prettier --write src/* src/*/* src/*/*/* src/*/*/*/*",
"buildDoc": "node ./scripts/buildDoc.js",
"autoBuildDoc": "node ./scripts/autoBuildDoc.js"
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"main": "background.js",
"dependencies": {
"@toast-ui/editor": "^3.1.5",
"core-js": "^3.6.5",
"electron-json-storage": "^4.6.0",
"element-ui": "^2.15.1",
"fs-extra": "^7.0.1",
"highlight.js": "^10.7.3",
"open": "^6.4.0",
"uuid": "^3.4.0",
"v-viewer": "^1.6.4",
"vue": "^2.6.11",
"vue-i18n": "^8.27.2",
@@ -29,6 +48,8 @@
"@vue/cli-service": "^4.5.0",
"babel-eslint": "^10.1.0",
"chokidar": "^3.5.3",
"electron": "^23.1.1",
"electron-devtools-installer": "^3.1.0",
"esbuild": "^0.17.15",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
@@ -37,6 +58,7 @@
"markdown-it": "^13.0.1",
"markdown-it-checkbox": "^1.1.0",
"prettier": "^1.19.1",
"vue-cli-plugin-electron-builder": "~2.1.1",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.44.2"
},

BIN
web/src/.DS_Store vendored

Binary file not shown.

View File

@@ -34,7 +34,17 @@ export const getData = () => {
return simpleDeepClone(exampleData)
} else {
try {
return JSON.parse(store)
let parsedData = JSON.parse(store)
if (window.IS_ELECTRON) {
return simpleDeepClone(exampleData)
let { root, ...rest } = parsedData
return {
...rest,
root: simpleDeepClone(exampleData).root
}
} else {
return parsedData
}
} catch (error) {
return simpleDeepClone(exampleData)
}

Binary file not shown.

BIN
web/src/assets/img/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

114
web/src/background.js Normal file
View File

@@ -0,0 +1,114 @@
'use strict'
import { app, protocol, BrowserWindow } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import path from 'path'
import { bindFileHandleEvent } from './electron/fileHandle'
import { bindOtherHandleEvent } from './electron/otherHandle'
const isDevelopment = process.env.NODE_ENV !== 'production'
// 在应用程序准备就绪之前,必须注册方案
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
// 创建主页面
let mainWindow = null
async function createMainWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
frame: false,
titleBarStyle: 'hiddenInset',
webPreferences: {
webSecurity: false,
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
// 如果处于开发模式则加载开发服务器的url
await mainWindow.loadURL(
process.env.WEBPACK_DEV_SERVER_URL + '/#/workbenche'
)
// if (!process.env.IS_TEST) mainWindow.webContents.openDevTools()
} else {
createProtocol('app')
// 非开发环境时加载index.html
mainWindow.loadURL('app://./index.html/#/workbenche')
}
}
// 绑定事件
const bindEvent = () => {
bindFileHandleEvent({ mainWindow, initOpenFileQueue })
bindOtherHandleEvent()
}
// 关闭所有窗口后退出
app.on('window-all-closed', () => {
// 在macOS上应用程序及其菜单栏通常保持活动状态直到用户使用Cmd+Q明确退出
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// 在macOS上当点击dock图标且没有其他窗口打开时通常会在应用程序中重新创建一个窗口。
if (BrowserWindow.getAllWindows().length === 0) {
createMainWindow()
// bindEvent()
}
})
// Attempt to bind file opening #2
// https://stackoverflow.com/questions/62420427/how-do-i-make-my-electron-app-the-default-for-opening-files
// https://github.com/rchrd2/example-electron-file-association
// https://www.jianshu.com/p/a32542277b83
const initOpenFileQueue = []
app.on('will-finish-launching', () => {
// Event fired When someone drags files onto the icon while your app is running
if (process.platform == 'win32') {
const argv = process.argv
if (argv) {
argv.forEach(filePath => {
if (filePath.indexOf('.smm') >= 0) {
initOpenFileQueue.push(filePath)
}
})
}
} else {
app.on('open-file', (event, file) => {
if (app.isReady() === false) {
initOpenFileQueue.push(file)
} else {
console.log(file)
}
event.preventDefault()
})
}
})
app.on('ready', async () => {
createMainWindow()
bindEvent()
})
// 在开发模式下,应父进程的请求干净地退出。
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', data => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}

3
web/src/css/global.css Normal file
View File

@@ -0,0 +1,3 @@
.noDrag {
-webkit-app-region: no-drag;
}

View File

@@ -0,0 +1,251 @@
import { BrowserWindow, ipcMain, dialog, shell } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import {
saveToRecent,
clearRecent,
removeFileInRecent,
replaceFileInRecent,
getRecent,
saveFileListToRecent
} from './storage'
import { v4 as uuid } from 'uuid'
export const bindFileHandleEvent = ({ mainWindow, initOpenFileQueue }) => {
// 通知主页面刷新最近文件列表
const notifyMainWindowRefreshRecentFileList = () => {
mainWindow.webContents.send('refreshRecentFileList')
}
// 新建编辑页面
const openIds = []
const createEditWindow = async (event, id) => {
openIds.push(id)
const win = new BrowserWindow({
width: 1200,
height: 800,
frame: false,
titleBarStyle: 'hiddenInset',
webPreferences: {
webSecurity: false,
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
})
win.on('closed', () => {
// 从openIds数组中删除
let index = openIds.find(item => {
return item === id
})
if (index !== -1) {
openIds.splice(index, 1)
}
// 从idToFilePath中删除
delete idToFilePath[id]
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
win.loadURL(
process.env.WEBPACK_DEV_SERVER_URL + '/#/workbenche/edit/' + id
)
// if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
// Load the index.html when not in development
win.loadURL('app://./index.html/#/workbenche/edit/' + id)
}
}
ipcMain.on('create', createEditWindow)
// 保存文件
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()
})
return path.parse(idToFilePath[id]).name
}
} else {
fs.writeFile(idToFilePath[id], data)
}
})
// 打开文件
const openFile = (event, file) => {
let id = uuid()
idToFilePath[id] = file
saveToRecent(file).then(() => {
notifyMainWindowRefreshRecentFileList()
})
createEditWindow(null, id)
}
ipcMain.on('openFile', openFile)
// 选择打开本地文件
ipcMain.on('selectOpenFile', event => {
const res = dialog.showOpenDialogSync({
title: '选择',
filters: [{ name: '思维导图', extensions: ['smm'] }]
})
if (res && res[0]) {
openFile(null, res[0])
}
})
// 获取文件内容
ipcMain.handle('getFileContent', (event, id) => {
return new Promise(resolve => {
let file = idToFilePath[id]
if (!file) {
resolve(null)
return
}
fs.readFile(file, { encoding: 'utf-8' }, (err, data) => {
resolve({
name: path.parse(file).name,
content: JSON.parse(data)
})
})
})
})
// 重命名文件
ipcMain.handle('rename', (event, id, name) => {
return new Promise(resolve => {
if (!idToFilePath[id]) {
resolve('文件不存在')
return
}
let oldPath = idToFilePath[id]
let { base, ...oldPathData } = path.parse(oldPath)
oldPathData.name = name
let newPath = path.format(oldPathData)
idToFilePath[id] = newPath
fs.rename(oldPath, newPath, err => {
if (err) {
resolve('重命名失败')
} else {
replaceFileInRecent(oldPath, newPath).then(() => {
notifyMainWindowRefreshRecentFileList()
resolve()
})
}
})
})
})
// 获取最近文件列表
ipcMain.handle('getRecentFileList', () => {
return getRecent().map(item => {
let data = path.parse(item)
return {
url: item,
dir: data.dir,
name: data.name
}
})
})
// 清空最近文件列表
ipcMain.handle('clearRecentFileList', async () => {
try {
clearRecent()
return ''
} catch (error) {
return '清空失败'
}
})
// 添加到最近文件列表
ipcMain.handle('addRecentFileList', async (event, fileList) => {
try {
console.log(fileList);
await saveFileListToRecent(fileList)
notifyMainWindowRefreshRecentFileList()
} catch (error) {
return error
}
})
// 打开指定目录
ipcMain.on('openFileInDir', (event, file) => {
shell.showItemInFolder(file)
})
// 删除指定文件
ipcMain.handle('deleteFile', (event, file) => {
let res = ''
let id = Object.keys(idToFilePath).find(item => {
return idToFilePath[item] === file
})
let index = -1
if (id) {
index = openIds.findIndex(item => {
return item === id
})
}
if (index === -1) {
try {
fs.rmSync(file)
} catch (error) {}
removeFileInRecent(file)
} else {
res = '该文件正在编辑,请关闭后再试'
}
return res
})
// 复制文件
ipcMain.handle('copyFile', async (event, file) => {
return new Promise((resolve, reject) => {
fs.pathExists(file, (err, exists) => {
if (err) {
reject(err)
} else {
if (exists) {
let { base, ...oldPathData } = path.parse(file)
let newName = oldPathData.name + '-复制'
let index = 1
oldPathData.name = newName
let newPath = path.format(oldPathData)
// 检查新路径是否已存在
while (fs.pathExistsSync(newPath)) {
oldPathData.name = newName + index
newPath = path.format(oldPathData)
index++
}
fs.copy(file, newPath, err => {
if (err) {
reject(err)
} else {
saveToRecent(newPath).then(() => {
notifyMainWindowRefreshRecentFileList()
})
resolve()
}
})
} else {
reject('文件不存在')
}
}
})
})
})
// 直接双击文件打开应用时,需要直接打开该文件编辑
initOpenFileQueue.forEach((file) => {
openFile(null, file)
})
}

View File

@@ -0,0 +1,18 @@
import { BrowserWindow, ipcMain } from 'electron'
import open from 'open'
export const bindOtherHandleEvent = () => {
// 处理缩放事件
;['minimize', 'maximize', 'unmaximize', 'close', 'destroy'].forEach(item => {
ipcMain.on(item, event => {
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
win[item]()
})
})
// 使用默认浏览器打开指定url
ipcMain.on('openUrl', (event, url) => {
open(url)
})
}

View File

@@ -0,0 +1,27 @@
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('platform', process.platform)
contextBridge.exposeInMainWorld('IS_ELECTRON', true)
contextBridge.exposeInMainWorld('electronAPI', {
minimize: () => ipcRenderer.send('minimize'),
maximize: () => ipcRenderer.send('maximize'),
unmaximize: () => ipcRenderer.send('unmaximize'),
close: () => ipcRenderer.send('close'),
destroy: () => ipcRenderer.send('destroy'),
create: id => ipcRenderer.send('create', id),
getFileContent: id => ipcRenderer.invoke('getFileContent', id),
save: (id, data, fileName) => ipcRenderer.invoke('save', id, data, fileName),
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'),
openFileInDir: file => ipcRenderer.send('openFileInDir', file),
deleteFile: file => ipcRenderer.invoke('deleteFile', file),
onRefreshRecentFileList: callback =>
ipcRenderer.on('refreshRecentFileList', callback),
openFile: file => ipcRenderer.send('openFile', file),
selectOpenFile: () => ipcRenderer.send('selectOpenFile'),
copyFile: file => ipcRenderer.invoke('copyFile', file)
})

109
web/src/electron/storage.js Normal file
View File

@@ -0,0 +1,109 @@
import storage from 'electron-json-storage'
export const RECENT_FILE_LIST = 'recentFileList'
// 保存到最近文件
export const saveToRecent = file => {
return new Promise((resolve, reject) => {
let list = getRecent()
let index = list.findIndex(item => {
return item === file
})
if (index !== -1) {
list.splice(index, 1)
}
list.push(file)
storage.set(RECENT_FILE_LIST, list, err => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
}
// 保存到最近文件
export const saveFileListToRecent = fileList => {
return new Promise((resolve, reject) => {
let list = getRecent()
fileList.forEach(file => {
let index = list.findIndex(item => {
return item === file
})
if (index !== -1) {
list.splice(index, 1)
}
list.push(file)
})
storage.set(RECENT_FILE_LIST, list, err => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
}
// 获取最近文件列表
export const getRecent = () => {
let res = storage.getSync(RECENT_FILE_LIST)
return (Array.isArray(res) ? res : []).filter(item => {
return !!item
})
}
// 清除最近文件列表
export const clearRecent = () => {
return new Promise((resolve, reject) => {
storage.remove(RECENT_FILE_LIST, err => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
}
// 从最近文件列表中移除指定文件
export const removeFileInRecent = file => {
return new Promise((resolve, reject) => {
let list = getRecent()
let index = list.findIndex(item => {
return item === file
})
if (index !== -1) {
list.splice(index, 1)
}
storage.set(RECENT_FILE_LIST, list, err => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
}
// 替换指定文件
export const replaceFileInRecent = (oldFile, newFile) => {
return new Promise((resolve, reject) => {
let list = getRecent()
let index = list.findIndex(item => {
return item === oldFile
})
if (index !== -1) {
list.splice(index, 1)
}
list.push(newFile)
storage.set(RECENT_FILE_LIST, list, err => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
}

View File

@@ -8,11 +8,21 @@ import '@/assets/icon-font/iconfont.css'
import 'viewerjs/dist/viewer.css'
import VueViewer from 'v-viewer'
import i18n from './i18n'
import './css/global.css'
Vue.config.productionTip = false
Vue.prototype.$bus = new Vue()
Vue.use(ElementUI)
Vue.use(VueViewer)
Vue.mixin({
data () {
return {
IS_ELECTRON: window.IS_ELECTRON,
IS_MAC: window.platform === 'darwin',
IS_WIN: window.platform === 'win32'
}
}
})
new Vue({
render: h => h(App),

View File

@@ -730,7 +730,7 @@ export default {
* @Desc: 初始样式
*/
initStyle() {
;[
[
'backgroundColor',
'lineWidth',
'lineStyle',
@@ -786,7 +786,7 @@ export default {
* @Desc: margin初始值
*/
initMarginStyle() {
;['marginX', 'marginY'].forEach(key => {
['marginX', 'marginY'].forEach(key => {
this.style[key] = this.mindMap.getThemeConfig()[this.marginActiveTab][
key
]

View File

@@ -1,5 +1,5 @@
<template>
<div class="editContainer">
<div class="editContainer" :style="{top: IS_ELECTRON ? '40px' : 0}">
<div class="mindMapContainer" ref="mindMapContainer"></div>
<Count v-if="!isZenMode"></Count>
<Navigator :mindMap="mindMap"></Navigator>
@@ -46,7 +46,7 @@ import { getData, storeData, storeConfig } from '@/api'
import Navigator from './Navigator.vue'
import NodeImgPreview from './NodeImgPreview.vue'
import SidebarTrigger from './SidebarTrigger.vue'
import { mapState } from 'vuex'
import { mapState, mapMutations } from 'vuex'
import customThemeList from '@/customThemes'
import icon from '@/config/icon'
@@ -93,11 +93,13 @@ export default {
mindMap: null,
mindMapData: null,
prevImg: '',
openTest: false
openTest: false,
isFirst: true
}
},
computed: {
...mapState({
fileName: state => state.fileName,
isZenMode: state => state.localConfig.isZenMode,
openNodeRichText: state => state.localConfig.openNodeRichText,
})
@@ -111,9 +113,9 @@ export default {
}
}
},
mounted() {
async mounted() {
// this.showNewFeatureInfo()
this.getData()
await this.getData()
this.init()
this.$bus.$on('execCommand', this.execCommand)
this.$bus.$on('paddingChange', this.onPaddingChange)
@@ -136,8 +138,13 @@ export default {
this.test()
}, 5000)
}
if (window.IS_ELECTRON) {
this.mindMap.keyCommand.addShortcut('Control+s', this.saveToLocal)
}
},
methods: {
...mapMutations(['setFileName', 'setIsUnSave']),
/**
* @Author: 王林25
* @Date: 2021-11-22 19:39:28
@@ -227,8 +234,16 @@ export default {
* @Date: 2021-07-03 22:11:37
* @Desc: 获取思维导图数据,实际应该调接口获取
*/
getData() {
let storeData = getData()
async getData() {
let data = await window.electronAPI.getFileContent(this.$route.params.id)
let storeData = null
if (data) {
this.setFileName(data.name)
storeData = data.content
} else {
this.setFileName('未命名')
storeData = getData()
}
this.mindMapData = storeData
},
@@ -242,9 +257,15 @@ export default {
return
}
this.$bus.$on('data_change', data => {
if (!this.isFirst) {
this.setIsUnSave(true)
} else {
this.isFirst = false
}
storeData(data)
})
this.$bus.$on('view_data_change', data => {
this.setIsUnSave(true)
storeConfig({
view: data
})
@@ -292,9 +313,9 @@ export default {
iconList: icon
})
if (this.openNodeRichText) this.addRichTextPlugin()
this.mindMap.keyCommand.addShortcut('Control+s', () => {
this.manualSave()
})
// this.mindMap.keyCommand.addShortcut('Control+s', () => {
// this.manualSave()
// })
// 转发事件
;[
'node_active',
@@ -409,6 +430,18 @@ export default {
// 移除节点富文本编辑插件
removeRichTextPlugin() {
this.mindMap.removePlugin(RichText)
},
// 保存到本地文件
async saveToLocal() {
let id = this.$route.params.id
let data = this.mindMap.getData(true)
console.log('保存', id, data)
let res = await window.electronAPI.save(id, JSON.stringify(data), this.fileName)
if (res) {
this.setFileName(res)
}
this.setIsUnSave(false)
}
}
}

View File

@@ -36,9 +36,7 @@
<Fullscreen :mindMap="mindMap"></Fullscreen>
</div>
<div class="item">
<a href="https://github.com/wanglin2/mind-map" target="_blank">
<span class="iconfont icongithub"></span>
</a>
<span class="iconfont icongithub" @click="openGithub"></span>
</div>
</div>
</template>
@@ -89,6 +87,10 @@ export default {
onLangChange(lang) {
i18n.locale = lang
storeLang(lang)
},
openGithub() {
window.electronAPI.openUrl('https://github.com/wanglin2/mind-map')
}
}
}

View File

@@ -506,7 +506,7 @@ export default {
this.activeTab = 'normal'
return
}
;[
[
'shape',
'paddingX',
'paddingY',

View File

@@ -1,6 +1,6 @@
<template>
<div class="toolbarContainer">
<div class="toolbar">
<div class="toolbar" :style="{top: IS_ELECTRON ? '40px' : 0}">
<!-- 节点操作 -->
<div class="toolbarBlock">
<div
@@ -126,15 +126,15 @@
</div>
<!-- 导出 -->
<div class="toolbarBlock">
<div class="toolbarBtn" @click="createNewLocalFile">
<div class="toolbarBtn" @click="createNewLocalFile" v-if="!IS_ELECTRON">
<span class="icon iconfont iconxinjian"></span>
<span class="text">{{ $t('toolbar.newFile') }}</span>
</div>
<div class="toolbarBtn" @click="openLocalFile">
<div class="toolbarBtn" @click="openLocalFile" v-if="!IS_ELECTRON">
<span class="icon iconfont icondakai"></span>
<span class="text">{{ $t('toolbar.openFile') }}</span>
</div>
<div class="toolbarBtn" @click="saveLocalFile">
<div class="toolbarBtn" @click="saveLocalFile" v-if="!IS_ELECTRON">
<span class="icon iconfont iconlingcunwei"></span>
<span class="text">{{ $t('toolbar.saveAs') }}</span>
</div>

View File

@@ -0,0 +1,32 @@
<template>
<div class="workbencheContainer">
<div class="workbencheContent">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
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;
}
}
</style>

View File

@@ -0,0 +1,92 @@
<template>
<el-dialog
class="aboutDialog"
title="关于"
:visible.sync="dialogVisible"
width="480px"
@close="onClose"
>
<div class="aboutBox">
<img src="../../../assets/img/icon.png" alt="" />
<h2>思绪思维导图</h2>
<p>版本{{ version }}</p>
<p>
获取源码<a href="https://github.com/wanglin2/mind-map/tree/electron"
>mind-map</a
>
</p>
<p>
下载最新版本<a href="https://github.com/wanglin2/mind-map/releases"
>releases</a
>
</p>
</div>
</el-dialog>
</template>
<script>
import pkg from '../../../../package.json'
export default {
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
type: Boolean,
default: false
}
},
data() {
return {
dialogVisible: false,
version: pkg.version
}
},
watch: {
value(val, oldVal) {
this.dialogVisible = val
}
},
methods: {
onClose() {
this.$emit('change', false)
}
}
}
</script>
<style lang="less" scoped>
.aboutDialog {
/deep/ .el-dialog__body {
padding: 0;
}
}
.aboutBox {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-bottom: 30px;
img {
width: 100px;
height: 100px;
}
h2 {
margin-bottom: 20px;
}
p {
margin-bottom: 10px;
a {
text-decoration: none;
color: #409eff;
}
}
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<div class="workbencheEmptyContainer">
<div class="icon iconfont iconwushuju"></div>
<div class="tip">暂时没有内容</div>
<div class="desc">没有最近使用的文件记录</div>
<div class="createBtn" @click="create">立即创建</div>
</div>
</template>
<script>
import { create } from '../utils'
export default {
methods: {
create
}
}
</script>
<style lang="less" scoped>
.workbencheEmptyContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
.icon {
font-size: 100px;
margin-bottom: 20px;
}
.tip {
font-weight: bold;
margin-bottom: 10px;
}
.desc {
font-size: 12px;
margin-bottom: 20px;
}
.createBtn {
width: 100px;
height: 30px;
border-radius: 5px;
border: 1px solid #409eff;
color: #409eff;
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
user-select: none;
&:hover {
background-color: rgba(64, 158, 255, 0.3);
}
}
}
</style>

View File

@@ -0,0 +1,165 @@
<template>
<div class="workbencheFileListContainer">
<div class="title">
<span>最近</span>
<span class="clearBtn" @click="clear">清空</span>
</div>
<div class="fileListBox">
<Empty v-if="list.length <= 0"></Empty>
<el-table v-else :data="list" style="width: 100%">
<el-table-column prop="name" label="名称"> </el-table-column>
<el-table-column prop="url" label="文件路径"> </el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
icon="el-icon-edit"
circle
size="mini"
@click="openFile(scope.row.url)"
></el-button>
<el-button
icon="el-icon-document-copy"
circle
size="mini"
@click="copyFile(scope.row.url)"
></el-button>
<el-button
type="danger"
icon="el-icon-delete"
circle
size="mini"
@click="deleteFile(scope.row.url, scope.$index)"
></el-button>
<el-button
icon="el-icon-folder-opened"
circle
size="mini"
@click="openFileInDir(scope.row.url)"
></el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
import Empty from '../components/Empty.vue'
export default {
components: {
Empty
},
data() {
return {
list: []
}
},
created() {
this.getRecentFileList()
window.electronAPI.onRefreshRecentFileList(() => {
this.getRecentFileList()
})
},
methods: {
// 获取最近文件列表
async getRecentFileList() {
let list = await window.electronAPI.getRecentFileList()
this.list = list.reverse()
},
// 在文件夹里打开文件
openFileInDir(file) {
window.electronAPI.openFileInDir(file)
},
// 删除文件
deleteFile(file, index) {
this.$confirm('确定删除该文件?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
let res = await window.electronAPI.deleteFile(file)
if (res) {
this.$message.error('删除失败')
} else {
this.list.splice(index, 1)
this.$message.success('删除成功')
}
})
.catch(() => {})
},
// 编辑文件
openFile(file) {
window.electronAPI.openFile(file)
},
// 清空最近文件列表
clear() {
this.$confirm('确定清空最近文件?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
let res = await window.electronAPI.clearRecentFileList()
if (res) {
this.$message.error('清空失败')
} else {
this.list = []
this.$message.success('清空成功')
}
})
.catch(() => {})
},
// 复制文件
async copyFile(file) {
try {
window.electronAPI.copyFile(file)
this.$message.success('复制成功')
} catch (error) {
this.$message.error('复制失败')
}
}
}
}
</script>
<style lang="less" scoped>
.workbencheFileListContainer {
flex-grow: 1;
height: 100%;
background-color: #fff;
border-radius: 10px;
padding: 20px;
padding-top: 0;
display: flex;
flex-direction: column;
.title {
font-weight: bold;
font-size: 18px;
border-bottom: 1px solid #e4e7ed;
height: 65px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
.clearBtn {
cursor: pointer;
font-size: 14px;
color: #409eff;
}
}
.fileListBox {
flex-grow: 1;
overflow: hidden;
}
}
</style>

View File

@@ -0,0 +1,15 @@
<template>
<div class="macControl" v-if="IS_MAC"></div>
</template>
<script>
export default {}
</script>
<style lang="less" scoped>
.macControl {
width: 100px;
height: 100%;
flex-shrink: 0;
}
</style>

View File

@@ -0,0 +1,90 @@
<template>
<div class="workbencheSidebarContainer">
<div class="createBtn" @click="create">开始新建</div>
<div class="line"></div>
<div class="btn" @click="openLocalFile">
<span class="icon iconfont icondakai"></span>
<span class="text">打开本地文件</span>
</div>
<div class="btn active">
<span class="icon iconfont iconzuijinliulan"></span>
<span class="text">最近文件</span>
</div>
</div>
</template>
<script>
import { create } from '../utils'
export default {
methods: {
create,
openLocalFile() {
window.electronAPI.selectOpenFile()
}
}
}
</script>
<style lang="less" scoped>
.workbencheSidebarContainer {
flex-shrink: 0;
width: 200px;
height: 100%;
background-color: #fff;
border-radius: 10px;
margin-right: 20px;
padding: 15px 20px;
.createBtn {
width: 100%;
height: 30px;
background-color: #409eff;
border-radius: 5px;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
font-size: 14px;
user-select: none;
&:hover {
opacity: 0.9;
}
}
.line {
width: 100%;
height: 1px;
background-color: #e4e7ed;
margin: 20px 0;
}
.btn {
width: 100%;
height: 30px;
background-color: #fff;
border-radius: 5px;
color: #000;
display: flex;
align-items: center;
cursor: pointer;
font-size: 14px;
user-select: none;
padding: 0 10px;
margin-bottom: 10px;
&.active,
&:hover {
background-color: rgba(64, 158, 255, 0.3);
color: rgba(64, 158, 255, 1);
}
.icon {
margin-right: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,68 @@
<template>
<div class="winControl noDrag" v-if="IS_WIN">
<div class="winControlBtn iconfont iconzuixiaohua" @click="minimize"></div>
<div
class="winControlBtn iconfont"
:class="[isMaximize ? 'icon3zuidahua-3' : 'iconzuidahua']"
@click="toggleMaximize"
></div>
<div class="winControlBtn iconfont iconguanbi" @click="close"></div>
</div>
</template>
<script>
export default {
data() {
return {
isMaximize: false
}
},
methods: {
minimize() {
window.electronAPI.minimize()
},
toggleMaximize() {
if (this.isMaximize) {
this.isMaximize = false
window.electronAPI.unmaximize()
} else {
this.isMaximize = true
window.electronAPI.maximize()
}
},
close() {
window.electronAPI.close()
}
}
}
</script>
<style lang="less" scoped>
.winControl {
display: flex;
align-items: center;
flex-shrink: 0;
height: 100%;
.winControlBtn {
width: 40px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&:hover {
background-color: #ccced1;
}
&.iconguanbi {
&:hover {
background-color: #e81123;
color: #fff;
}
}
}
}
</style>

View File

@@ -0,0 +1,6 @@
import { v4 as uuid } from 'uuid'
// 打开新的编辑窗口
export const create = () => {
window.electronAPI.create(uuid())
}

View File

@@ -0,0 +1,134 @@
<template>
<div class="workbencheEditContainer">
<div class="workbencheEditHeader">
<MacControl></MacControl>
<WinControl></WinControl>
<div class="inputBox">
<el-input
v-model="name"
size="mini"
placeholder=""
@blur="rename"
@keyup.enter="rename"
></el-input>
<div class="modifyDotBox">
<div class="modifyDot" v-show="isUnSave"></div>
</div>
</div>
</div>
<Edit></Edit>
</div>
</template>
<script>
import Edit from '../../Edit/Index.vue'
import WinControl from '../components/WinControl.vue'
import MacControl from '../components/MacControl.vue'
import { mapState, mapMutations } from 'vuex'
export default {
components: {
Edit,
MacControl,
WinControl
},
data() {
return {
name: ''
}
},
computed: {
...mapState(['fileName', 'isUnSave'])
},
watch: {
fileName(val) {
this.name = val
document.title = val
},
name(val) {
if (!val.trim()) return
this.setFileName(val.trim())
}
},
created() {
window.onbeforeunload = async e => {
e.returnValue = false
// 没有未保存内容直接关闭
if (!this.isUnSave) {
window.electronAPI.destroy()
} else {
try {
// 否则询问用户是否关闭
await this.checkIsClose()
window.electronAPI.destroy()
} catch (error) {}
}
}
},
methods: {
...mapMutations(['setFileName']),
// 重命名文件
rename() {
let id = this.$route.params.id
window.electronAPI.rename(id, this.name.trim())
},
// 询问是否关闭页面
checkIsClose() {
return new Promise((resolve, reject) => {
this.$confirm('有操作尚未保存,是否确认关闭?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
resolve()
})
.catch(() => {
reject()
})
})
}
}
}
</script>
<style lang="less" scoped>
.workbencheEditContainer {
.workbencheEditHeader {
position: relative;
width: 100%;
height: 40px;
background-color: #ebeef1;
-webkit-app-region: drag;
display: flex;
align-items: center;
flex-shrink: 0;
.inputBox {
-webkit-app-region: no-drag;
position: absolute;
height: 100%;
left: 50%;
transform: translateX(-50%);
top: 0;
display: flex;
align-items: center;
.modifyDotBox {
width: 10px;
height: 10px;
margin-left: 10px;
.modifyDot {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #409eff;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,150 @@
<template>
<div
class="workbencheHomeContainer"
@drop="onDrop"
@dragenter="onDragenter"
@dragover="onDragover"
@dragleave="onDragleave"
>
<div class="workbencheHomeHeader">
<MacControl></MacControl>
<WinControl></WinControl>
<div class="rightBar">
<el-dropdown @command="handleCommand">
<span class="settingBtn el-icon-setting"></span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="about">关于软件</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<div class="workbencheHomeContent">
<Sidebar></Sidebar>
<FileList></FileList>
</div>
<AboutDialog v-model="showAboutDialog"></AboutDialog>
</div>
</template>
<script>
import WinControl from '../components/WinControl.vue'
import MacControl from '../components/MacControl.vue'
import Sidebar from '../components/Sidebar.vue'
import FileList from '../components/FileList.vue'
import AboutDialog from '../components/AboutDialog.vue'
export default {
components: {
WinControl,
MacControl,
Sidebar,
FileList,
AboutDialog
},
data() {
return {
showAboutDialog: false
}
},
methods: {
handleCommand(command) {
switch (command) {
case 'about':
this.showAboutDialog = true
break
default:
break
}
},
onDrop(e) {
e.preventDefault()
e.stopPropagation()
let df = e.dataTransfer
let dropFiles = []
if (df.items !== undefined) {
for (let i = 0; i < df.items.length; i++) {
let item = df.items[i]
if (item.kind === 'file' && item.webkitGetAsEntry().isFile) {
let file = item.getAsFile()
if (/\.smm$/.test(file.name)) {
dropFiles.push(file)
}
}
}
}
if (dropFiles.length === 1) {
// 如果只有一个文件,直接打开编辑
window.electronAPI.openFile(dropFiles[0].path)
} else if (dropFiles.length > 1) {
// 否则添加到最近文件列表
window.electronAPI.addRecentFileList(
dropFiles.map(file => {
return file.path
})
)
}
},
onDragenter(e) {
e.preventDefault()
e.stopPropagation()
},
onDragover(e) {
e.preventDefault()
e.stopPropagation()
},
onDragleave(e) {
e.preventDefault()
e.stopPropagation()
}
}
}
</script>
<style lang="less" scoped>
.workbencheHomeContainer {
background-color: #f6f8f9;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.workbencheHomeHeader {
position: relative;
width: 100%;
height: 40px;
background-color: #ebeef1;
-webkit-app-region: drag;
display: flex;
align-items: center;
flex-shrink: 0;
.rightBar {
-webkit-app-region: no-drag;
position: absolute;
right: 20px;
top: 0;
height: 100%;
display: flex;
align-items: center;
.settingBtn {
font-size: 20px;
cursor: pointer;
}
}
}
.workbencheHomeContent {
flex-grow: 1;
padding: 20px;
display: flex;
overflow: hidden;
}
}
</style>

View File

@@ -3,6 +3,9 @@ import VueRouter from 'vue-router'
import EditPage from '@/pages/Edit/Index'
import DocPage from '@/pages/Doc/Index'
import routerList from '@/pages/Doc/routerList'
import WorkbenchePage from '@/pages/Workbenche/Index'
import WorkbencheHomePage from '@/pages/Workbenche/views/Home'
import WorkbencheEditPage from '@/pages/Workbenche/views/Edit'
// 处理没有翻译的章节路由
const handleRouterList = () => {
@@ -26,21 +29,39 @@ handleRouterList()
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Edit',
component: EditPage
{
path: '/',
name: 'Edit',
component: EditPage
},
{
path: '/workbenche',
name: 'Workbenche',
component: WorkbenchePage,
redirect: '/workbenche/home',
children: [
{
path: 'home',
name: 'WorkbencheHome',
component: WorkbencheHomePage,
},
{
path: 'edit/:id',
name: 'WorkbencheEdit',
component: WorkbencheEditPage,
}
]
},
...routerList.map((item) => {
return {
path: `/doc/${item.lang}/`,
return {
path: `/doc/${item.lang}/`,
redirect: `/doc/${item.lang}/introduction/`
}
}),
...routerList.map((item) => {
return {
path: `/doc/${item.lang}/`,
component: DocPage,
return {
path: `/doc/${item.lang}/`,
component: DocPage,
children: item.children.map((child) => {
return {
path: `${child.path}/:h?`,

View File

@@ -7,6 +7,8 @@ Vue.use(Vuex)
const store = new Vuex.Store({
state: {
fileName: '',// 本地的文件名
isUnSave: false,// 当前操作是否未保存
mindMapData: null, // 思维导图数据
isHandleLocalFile: false, // 是否操作的是本地文件
localConfig: {
@@ -15,9 +17,20 @@ const store = new Vuex.Store({
// 是否开启节点富文本
openNodeRichText: true
},
activeSidebar: '' // 当前显示的侧边栏
activeSidebar: '', // 当前显示的侧边栏
localEditList: []// 客户端中正在编辑的思维导图列表
},
mutations: {
// 设置本地文件名
setFileName(state, data) {
state.fileName = data
},
// 设置当前操作是否未保存
setIsUnSave(state, data) {
state.isUnSave = data
},
/**
* @Author: 王林
* @Date: 2021-04-10 14:50:01
@@ -59,6 +72,11 @@ const store = new Vuex.Store({
*/
setActiveSidebar(state, data) {
state.activeSidebar = data
},
// 设置客户端中当前正在编辑的思维导图列表
setLocalEditList(state, list) {
state.localEditList = list
}
},
actions: {

View File

@@ -1,16 +1,119 @@
const path = require('path');
const path = require('path')
const isDev = process.env.NODE_ENV === 'development'
module.exports = {
publicPath: isDev ? '' : './dist',
outputDir: '../dist',
lintOnSave: false,
productionSourceMap: false,
configureWebpack: {
resolve: {
alias: {
'@': path.resolve(__dirname, './src/')
}
}
publicPath: isDev ? '' : './dist',
outputDir: '../dist',
lintOnSave: false,
productionSourceMap: false,
configureWebpack: {
resolve: {
alias: {
'@': path.resolve(__dirname, './src/')
}
}
}
},
pluginOptions: {
electronBuilder: {
preload: 'src/electron/preload.js',
builderOptions: {
productName: '思绪思维导图',
copyright: 'Copyright © 思绪思维导图',
// compression: "maximum", // 机器好的可以打开,配置压缩,开启后会让 .AppImage 格式的客户端启动缓慢
asar: true,
fileAssociations: [
{
ext: 'smm',
name: 'mind map file',
role: 'Editor',
icon: './build/icons/icon.ico'
}
],
publish: [
{
provider: 'github',
owner: 'wanglin2',
repo: 'mind-map',
vPrefixedTagName: true,
releaseType: 'draft'
}
],
directories: {
output: 'dist_electron'
},
mac: {
target: [
{
target: 'dmg',
arch: ['x64', 'arm64', 'universal']
}
],
artifactName: '${productName}-${os}-${version}-${arch}.${ext}',
category: 'public.app-category.utilities',
darkModeSupport: true
},
win: {
target: [
{
target: 'portable',
arch: ['x64']
},
{
target: 'nsis',
arch: ['x64']
}
],
publisherName: '思绪思维导图',
icon: 'build/icons/icon.ico',
publish: ['github']
},
linux: {
target: [
{
target: 'AppImage',
arch: ['x64']
},
{
target: 'tar.gz',
arch: ['x64', 'arm64']
},
{
target: 'deb',
arch: ['x64', 'armv7l', 'arm64']
},
{
target: 'rpm',
arch: ['x64']
},
{
target: 'snap',
arch: ['x64']
},
{
target: 'pacman',
arch: ['x64']
}
],
category: 'Utilities',
icon: './build/icon.icns'
},
dmg: {
icon: 'build/icons/icon.icns'
},
nsis: {
oneClick: false,
allowToChangeInstallationDirectory: true,
perMachine: true,
deleteAppDataOnUninstall: true
}
},
// 渲染线程的配置文件
chainWebpackRendererProcess: config => {
config.plugin('define').tap(args => {
args[0]['IS_ELECTRON'] = true
return args
})
}
}
}
}