开发中

This commit is contained in:
wanglin2 2023-05-04 17:54:31 +08:00
parent c2c9de1c03
commit 26e3158bd8
12 changed files with 2228 additions and 2533 deletions

File diff suppressed because one or more lines are too long

View File

@ -2,6 +2,12 @@
"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",
@ -19,9 +25,11 @@
"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",

View File

@ -93,7 +93,7 @@
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("const { contextBridge, ipcRenderer } = __webpack_require__(/*! electron */ \"electron\")\n\ncontextBridge.exposeInMainWorld('platform', process.platform)\ncontextBridge.exposeInMainWorld('IS_ELECTRON', true)\n\ncontextBridge.exposeInMainWorld('electronAPI', {\n minimize: () => ipcRenderer.send('minimize'),\n maximize: () => ipcRenderer.send('maximize'),\n unmaximize: () => ipcRenderer.send('unmaximize'),\n close: () => ipcRenderer.send('close'),\n create: (id) => ipcRenderer.send('create', id),\n save: (id, data) => ipcRenderer.send('save', id, data),\n})\n\n//# sourceURL=webpack:///./src/electron/preload.js?");
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 create: (id) => ipcRenderer.send('create', id),\r\n save: (id, data) => ipcRenderer.invoke('save', id, data),\r\n rename: (id, name) => ipcRenderer.invoke('rename', id, name),\r\n openUrl: (url) => ipcRenderer.send('openUrl', url),\r\n getRecentFileList: () => ipcRenderer.invoke('getRecentFileList'),\r\n openFileInDir: (file) => ipcRenderer.send('openFileInDir', file),\r\n deleteFile: (file) => ipcRenderer.invoke('deleteFile', file),\r\n onRefreshRecentFileList: (callback) => ipcRenderer.on('refreshRecentFileList', callback)\r\n})\n\n//# sourceURL=webpack:///./src/electron/preload.js?");
/***/ }),
@ -104,7 +104,7 @@ eval("const { contextBridge, ipcRenderer } = __webpack_require__(/*! electron */
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("module.exports = __webpack_require__(/*! /Users/lisa/wanglin/github/mind-map/web/src/electron/preload.js */\"./src/electron/preload.js\");\n\n\n//# sourceURL=webpack:///multi_./src/electron/preload.js?");
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?");
/***/ }),

3721
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,12 @@
"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",
@ -19,9 +25,11 @@
"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",

View File

@ -1,20 +1,58 @@
'use strict'
import { app, protocol, BrowserWindow, ipcMain, BrowserView, dialog } from 'electron'
import {
app,
protocol,
BrowserWindow,
ipcMain,
BrowserView,
dialog,
shell
} from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import path from 'path'
import open from 'open'
import storage from 'electron-json-storage'
const fs = require('fs-extra')
const isDevelopment = process.env.NODE_ENV !== 'production'
// 保存到最近文件
const RECENT_FILE_LIST = 'recentFileList'
const saveToRecent = file => {
let list = removeFileInRecent()
list.push(file)
storage.set(RECENT_FILE_LIST, list)
}
const getRecent = () => {
let res = storage.getSync(RECENT_FILE_LIST)
return Array.isArray(res) ? res : []
}
const clearRecent = () => {
storage.remove(RECENT_FILE_LIST)
}
const removeFileInRecent = (file) => {
let list = getRecent()
let index = list.find(item => {
return item === file
})
if (index !== -1) {
list.splice(index, 1)
}
storage.set(RECENT_FILE_LIST, list)
return list
}
// clearRecent()
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
async function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
// 创建主页面
let mainWindow = null
async function createMainWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
frame: false,
@ -28,8 +66,28 @@ async function createWindow() {
}
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await mainWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL + '/#/workbenche')
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol('app')
// Load the index.html when not in development
mainWindow.loadURL('app://./index.html/#/workbenche')
}
}
// 通知主页面刷新最近文件列表
const notifyMainWindowRefreshRecentFileList = () => {
mainWindow.webContents.send('refreshRecentFileList')
}
// 监听事件
const bindEvent = () => {
// 新建编辑页面
const openIds = []
ipcMain.on('create', async (event, id) => {
openIds.push(id)
const win = new BrowserWindow({
width: 1200,
height: 800,
@ -43,38 +101,64 @@ async function createWindow() {
preload: path.join(__dirname, 'preload.js')
}
})
win.on('closed', () => {
let index = openIds.find(item => {
return item === id
})
if (index !== -1) {
openIds.splice(index, 1)
}
})
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()
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)
}
})
// 保存
// 保存文件
const idToFilePath = {}
ipcMain.on('save', async (event, id, data) => {
ipcMain.handle('save', async (event, id, data) => {
if (!idToFilePath[id]) {
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
const res = dialog.showSaveDialogSync(win, {
title: '保存',
defaultPath: '未命名.smm',
filters: [
{ name: '思维导图', extensions: ['smm'] }
]
filters: [{ name: '思维导图', extensions: ['smm'] }]
})
if (res) {
idToFilePath[id] = res
fs.writeFile(res, data)
saveToRecent(res)
notifyMainWindowRefreshRecentFileList()
return path.parse(idToFilePath[id]).name
}
return
} else {
fs.writeFile(idToFilePath[id], data)
}
fs.writeFile(idToFilePath[id], data)
})
// 重命名文件
ipcMain.handle('rename', async (event, id, name) => {
if (!idToFilePath[id]) {
return
}
let oldPath = idToFilePath[id]
let { base, ...oldPathData } = path.parse(oldPath)
oldPathData.name = name
let newPath = path.format(oldPathData)
idToFilePath[id] = newPath
await fs.rename(oldPath, newPath)
notifyMainWindowRefreshRecentFileList()
})
// 处理缩放事件
;['minimize', 'maximize', 'unmaximize', 'close'].forEach(item => {
ipcMain.on(item, event => {
const webContents = event.sender
@ -83,18 +167,55 @@ async function createWindow() {
})
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL + '/#/workbenche')
// if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol('app')
// Load the index.html when not in development
win.loadURL('app://./index.html/#/workbenche')
}
// 使用默认浏览器打开指定url
ipcMain.on('openUrl', (event, url) => {
open(url)
})
// 获取最近文件列表
ipcMain.handle('getRecentFileList', () => {
return getRecent().map(item => {
let data = path.parse(item)
return {
url: item,
dir: data.dir,
name: data.name
}
})
})
// 打开指定目录
ipcMain.on('openFileInDir', (event, file) => {
shell.showItemInFolder(file)
})
// 删除指定文件
ipcMain.handle('deleteFile', (event, file) => {
let res = ''
let id = Object.keys(idToFilePath).find(item => {
console.log(item, idToFilePath[item])
return idToFilePath[item] === file
})
let index = -1
if (id) {
index = openIds.findIndex(item => {
return item === id
})
}
console.log(file, id, index)
if (index === -1) {
res = fs.rmSync(file)
if (res) {
removeFileInRecent(file)
}
} else {
res = '该文件正在编辑,请关闭后再试'
}
return res
})
}
// Quit when all windows are closed.
// 关闭所有窗口后退出
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
@ -106,11 +227,15 @@ app.on('window-all-closed', () => {
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
if (BrowserWindow.getAllWindows().length === 0) {
createMainWindow()
bindEvent()
}
})
app.on('ready', async () => {
createWindow()
createMainWindow()
bindEvent()
})
// Exit cleanly on request from parent process in development mode.

View File

@ -9,5 +9,11 @@ contextBridge.exposeInMainWorld('electronAPI', {
unmaximize: () => ipcRenderer.send('unmaximize'),
close: () => ipcRenderer.send('close'),
create: (id) => ipcRenderer.send('create', id),
save: (id, data) => ipcRenderer.send('save', id, data),
save: (id, data) => ipcRenderer.invoke('save', id, data),
rename: (id, name) => ipcRenderer.invoke('rename', id, name),
openUrl: (url) => ipcRenderer.send('openUrl', url),
getRecentFileList: () => ipcRenderer.invoke('getRecentFileList'),
openFileInDir: (file) => ipcRenderer.send('openFileInDir', file),
deleteFile: (file) => ipcRenderer.invoke('deleteFile', file),
onRefreshRecentFileList: (callback) => ipcRenderer.on('refreshRecentFileList', callback)
})

View File

@ -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'
@ -98,6 +98,7 @@ export default {
},
computed: {
...mapState({
fileName: state => state.fileName,
isZenMode: state => state.localConfig.isZenMode,
openNodeRichText: state => state.localConfig.openNodeRichText,
})
@ -141,6 +142,8 @@ export default {
}
},
methods: {
...mapMutations(['setFileName', 'setIsUnSave']),
/**
* @Author: 王林25
* @Date: 2021-11-22 19:39:28
@ -245,9 +248,11 @@ export default {
return
}
this.$bus.$on('data_change', data => {
this.setIsUnSave(true)
storeData(data)
})
this.$bus.$on('view_data_change', data => {
this.setIsUnSave(true)
storeConfig({
view: data
})
@ -295,9 +300,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',
@ -414,11 +419,16 @@ export default {
this.mindMap.removePlugin(RichText)
},
saveToLocal() {
//
async saveToLocal() {
let id = this.$route.params.id
let data = this.mindMap.getData(true)
console.log('保存', id, data)
window.electronAPI.save(id, JSON.stringify(data))
let res = await window.electronAPI.save(id, JSON.stringify(data))
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

@ -2,7 +2,34 @@
<div class="workbencheFileListContainer">
<div class="title">最近</div>
<div class="fileListBox">
<Empty></Empty>
<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"></el-button>
<el-button
icon="el-icon-document-copy"
circle
size="mini"
></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>
@ -13,6 +40,37 @@ import Empty from '../components/Empty.vue'
export default {
components: {
Empty
},
data() {
return {
list: []
}
},
created() {
this.getRecentFileList()
window.electronAPI.onRefreshRecentFileList((_event, value) => {
console.log(1);
this.getRecentFileList()
})
},
methods: {
async getRecentFileList() {
this.list = await window.electronAPI.getRecentFileList()
console.log(this.list)
},
openFileInDir(file) {
window.electronAPI.openFileInDir(file)
},
async deleteFile(file, index) {
let res = await window.electronAPI.deleteFile(file)
console.log(res)
if (res) {
} else {
this.list.splice(index, 1)
}
}
}
}
</script>

View File

@ -3,6 +3,12 @@
<div class="workbencheEditHeader">
<MacControl></MacControl>
<WinControl></WinControl>
<div class="inputBox">
<el-input v-model="name" size="mini" placeholder=""></el-input>
<div class="modifyDotBox">
<div class="modifyDot" v-show="isUnSave"></div>
</div>
</div>
</div>
<Edit></Edit>
</div>
@ -12,6 +18,7 @@
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: {
@ -19,12 +26,35 @@ export default {
MacControl,
WinControl
},
data () {
return {
name: ''
}
},
computed: {
...mapState(['fileName', 'isUnSave'])
},
watch: {
fileName(val) {
this.name = val
},
name(val) {
if (!val.trim()) return
this.setFileName(val)
let id = this.$route.params.id
window.electronAPI.rename(id, val.trim())
}
},
methods: {
...mapMutations(['setFileName'])
}
}
</script>
<style lang="less" scoped>
.workbencheEditContainer {
.workbencheEditHeader {
position: relative;
width: 100%;
height: 40px;
background-color: #ebeef1;
@ -32,6 +62,30 @@ export default {
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

@ -7,6 +7,8 @@ Vue.use(Vuex)
const store = new Vuex.Store({
state: {
fileName: '',// 本地的文件名
isUnSave: false,// 当前操作是否未保存
mindMapData: null, // 思维导图数据
isHandleLocalFile: false, // 是否操作的是本地文件
localConfig: {
@ -19,6 +21,16 @@ const store = new Vuex.Store({
localEditList: []// 客户端中正在编辑的思维导图列表
},
mutations: {
// 设置本地文件名
setFileName(state, data) {
state.fileName = data
},
// 设置当前操作是否未保存
setIsUnSave(state, data) {
state.isUnSave = data
},
/**
* @Author: 王林
* @Date: 2021-04-10 14:50:01