在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
为了方便记录一些个人随笔,我最近用
接下来我就带大家来一步步来实现这个功能。 Vue CLI 搭建Vue项目在选择的目录下执行 选择自定义的模板(可以选择默认的Vue 3 模板) 选择Vue3 和 TypeScript, 其他的选项基于自身项目决定是否选择 执行 Vue项目改造为markdown编辑器执行 添加TypeScript类型定义文件
declare module "*.vue" { import type { DefineComponent } from "vue"; const component: DefineComponent<{}, {}, any>; export default component; } <!-- 添加的内容 --> declare module "@kangc/v-md-editor/lib/theme/vuepress.js"; declare module "@kangc/v-md-editor/lib/plugins/copy-code/index"; declare module "@kangc/v-md-editor/lib/plugins/line-number/index"; declare module "@kangc/v-md-editor"; declare module "prismjs"; 改造App.vue <template> <div> <v-md-editor v-model="content" height="100vh"></v-md-editor> </div> </template> <script lang="ts"> // 编辑器 import VMdEditor from "@kangc/v-md-editor"; import "@kangc/v-md-editor/lib/style/base-editor.css"; import vuepress from "@kangc/v-md-editor/lib/theme/vuepress.js"; import "@kangc/v-md-editor/lib/theme/style/vuepress.css"; // 高亮显示 import Prism from "prismjs"; import "prismjs/components/prism-json"; import "prismjs/components/prism-dart"; import "prismjs/components/prism-c"; import "prismjs/components/prism-swift"; import "prismjs/components/prism-kotlin"; import "prismjs/components/prism-java"; // 快捷复制代码 import createCopyCodePlugin from "@kangc/v-md-editor/lib/plugins/copy-code/index"; import "@kangc/v-md-editor/lib/plugins/copy-code/copy-code.css"; // 行号 import createLineNumbertPlugin from "@kangc/v-md-editor/lib/plugins/line-number/index"; VMdEditor.use(vuepress, { Prism, }) .use(createCopyCodePlugin()) .use(createLineNumbertPlugin()); import { defineComponent, ref } from "vue"; export default defineComponent({ name: "App", components: { VMdEditor }, setup() { const content = ref(""); return { content }; }, }); </script> <style> /* 去掉一些按钮 */ .v-md-icon-save, .v-md-icon-fullscreen { display: none; } </style>
效果如下 Vue CLI Plugin Electron Builder我尝试过用Vite 2.0去搭建Electron项目,但是没有找到类似的Vite和Electron结合好使的工具,所以放弃了Vite 2.0的诱惑。如果有小伙伴有推荐可以分享下。 使用
执行
优化功能启动全屏显示引入screen import { screen } from "electron"; 创建窗口的时候设置为screen大小 <!-- background.ts --> async function createWindow() { const { width, height } = screen.getPrimaryDisplay().workAreaSize; const win = new BrowserWindow({ width, height, // 省略... }); // 省略... }
修改菜单栏定义菜单栏 <!-- background.ts --> const template: Array<MenuItemConstructorOptions> = [ { label: "MarkDown", submenu: [ { label: "关于", accelerator: "CmdOrCtrl+W", role: "about", }, { label: "退出程序", accelerator: "CmdOrCtrl+Q", role: "quit", }, ], }, { label: "文件", submenu: [ { label: "打开文件", accelerator: "CmdOrCtrl+O", click: ( item: MenuItem, focusedWindow: BrowserWindow | undefined, _event: KeyboardEvent ) => { // TODO: 打开文件 }, }, { label: "存储", accelerator: "CmdOrCtrl+S", click: ( item: MenuItem, focusedWindow: BrowserWindow | undefined, _event: KeyboardEvent ) => { // TODO: 存储内容 }, }, ], }, { label: "编辑", submenu: [ { label: "撤销", accelerator: "CmdOrCtrl+Z", role: "undo", }, { label: "重做", accelerator: "Shift+CmdOrCtrl+Z", role: "redo", }, { type: "separator", }, { label: "剪切", accelerator: "CmdOrCtrl+X", role: "cut", }, { label: "复制", accelerator: "CmdOrCtrl+C", role: "copy", }, { label: "粘贴", accelerator: "CmdOrCtrl+V", role: "paste", }, ], }, { label: "窗口", role: "window", submenu: [ { label: "最小化", accelerator: "CmdOrCtrl+M", role: "minimize", }, { label: "最大化", accelerator: "CmdOrCtrl+M", click: ( item: MenuItem, focusedWindow: BrowserWindow | undefined, _event: KeyboardEvent ) => { if (focusedWindow) { focusedWindow.maximize(); } }, }, { type: "separator", }, { label: "切换全屏", accelerator: (function () { if (process.platform === "darwin") { return "Ctrl+Command+F"; } else { return "F11"; } })(), click: ( item: MenuItem, focusedWindow: BrowserWindow | undefined, // eslint-disable-next-line @typescript-eslint/no-unused-vars _event: KeyboardEvent ) => { if (focusedWindow) { focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); } }, }, ], }, { label: "帮助", role: "help", submenu: [ { label: "学习更多", click: function () { shell.openExternal("http://electron.atom.io"); }, }, ], }, ];
设置菜单栏 import { Menu } from "electron"; app.on("ready", async () => { // 省略... // 创建菜单 Menu.setApplicationMenu(Menu.buildFromTemplate(template)); });
效果 编辑器打开markdonw文件的内容主线程选择文件,将文件路径传给渲染线程 <!-- background.ts --> dialog .showOpenDialog({ properties: ["openFile"], filters: [{ name: "Custom File Type", extensions: ["md"] }], }) .then((res) => { if (res && res["filePaths"].length > 0) { const filePath = res["filePaths"][0]; // 将文件传给渲染线程 if (focusedWindow) { focusedWindow.webContents.send("open-file-path", filePath); } } }) .catch((err) => { console.log(err); });
渲染线程取到文件路径,读取文件内容,赋值给markdown编辑器 <!-- App.vue --> import { ipcRenderer } from "electron"; import { readFileSync } from "fs"; export default defineComponent({ // 省略... setup() { const content = ref(""); onMounted(() => { // 1. ipcRenderer.on("open-file-path", (e, filePath: string) => { if (filePath && filePath.length > 0) { // 2. content.value = readFileSync(filePath).toString(); } }); }); return { content }; }, }); vue添加node支持 <!-- vue.config.js --> module.exports = { pluginOptions: { electronBuilder: { nodeIntegration: true, }, }, }; 效果 markdonw的内容存入文件主线程发起向渲染线程获取编辑器内容的请求 <!-- background.js --> if (focusedWindow) { focusedWindow.webContents.send("get-content", ""); } 渲染线程主线程向返回编辑器的内容 <!-- App.vue --> onMounted(() => { ipcRenderer.on("get-content", () => { ipcRenderer.send("save-content", content.value); }); }); 主线程收到内容然后存入文件 <!-- background.ts --> // 存储文件 ipcMain.on("save-content", (event: unknown, content: string) => { if (openedFile.length > 0) { // 直接存储到文件中去 try { writeFileSync(openedFile, content); console.log("保存成功"); } catch (error) { console.log("保存失败"); } } else { const options = { title: "保存文件", defaultPath: "new.md", filters: [{ name: "Custom File Type", extensions: ["md"] }], }; const focusedWindow = BrowserWindow.getFocusedWindow(); if (focusedWindow) { dialog .showSaveDialog(focusedWindow, options) .then((result: Electron.SaveDialogReturnValue) => { if (result.filePath) { try { writeFileSync(result.filePath, content); console.log("保存成功"); openedFile = result.filePath; } catch (error) { console.log("保存失败"); } } }) .catch((error) => { console.log(error); }); } } }); 效果 打包设置应用的名字和图片 <!-- vue.config.js --> module.exports = { pluginOptions: { electronBuilder: { nodeIntegration: true, // 添加的设置 builderOptions: { appId: "com.johnny.markdown", productName: "JJMarkDown", // 应用的名字 copyright: "Copyright © 2021", //版权声明 mac: { icon: "./public/icon.icns", // icon }, }, }, }, }; icon.icns生成 准备一个1024*1024的图片,同级目录下创建一个为 创建各种不同尺寸要求的图片文件 sips -z 16 16 icon.png -o icons.iconset/icon_16x16.png sips -z 32 32 icon.png -o icons.iconset/[email protected] sips -z 32 32 icon.png -o icons.iconset/icon_32x32.png sips -z 64 64 icon.png -o icons.iconset/[email protected] sips -z 128 128 icon.png -o icons.iconset/icon_128x128.png sips -z 256 256 icon.png -o icons.iconset/[email protected] sips -z 256 256 icon.png -o icons.iconset/icon_256x256.png sips -z 512 512 icon.png -o icons.iconset/[email protected] sips -z 512 512 icon.png -o icons.iconset/icon_512x512.png sips -z 1024 1024 icon.png -o icons.iconset/[email protected] 获得名为icon.icns的图标文件 iconutil -c icns icons.iconset -o icon.icns 打包 npm run electron:build 结果 获得的dmg文件就可以直接安装使用了。 代码 <!-- background.ts --> "use strict"; import { app, protocol, BrowserWindow, screen, Menu, MenuItem, shell, dialog, ipcMain, } from "electron"; import { KeyboardEvent, MenuItemConstructorOptions } from "electron/main"; import { createProtocol } from "vue-cli-plugin-electron-builder/lib"; import installExtension, { VUEJS3_DEVTOOLS } from "electron-devtools-installer"; const isDevelopment = process.env.NODE_ENV !== "production"; import { writeFileSync } from "fs"; let openedFile = ""; // 存储文件 ipcMain.on("save-content", (event: unknown, content: string) => { if (openedFile.length > 0) { // 直接存储到文件中去 try { writeFileSync(openedFile, content); console.log("保存成功"); } catch (error) { console.log("保存失败"); } } else { const options = { title: "保存文件", defaultPath: "new.md", filters: [{ name: "Custom File Type", extensions: ["md"] }], }; const focusedWindow = BrowserWindow.getFocusedWindow(); if (focusedWindow) { dialog .showSaveDialog(focusedWindow, options) .then((result: Electron.SaveDialogReturnValue) => { if (result.filePath) { try { writeFileSync(result.filePath, content); console.log("保存成功"); openedFile = result.filePath; } catch (error) { console.log("保存失败"); } } }) .catch((error) => { console.log(error); }); } } }); const template: Array<MenuItemConstructorOptions> = [ { label: "MarkDown", submenu: [ { label: "关于", accelerator: "CmdOrCtrl+W", role: "about", }, { label: "退出程序", accelerator: "CmdOrCtrl+Q", role: "quit", }, ], }, { label: "文件", submenu: [ { label: "打开文件", accelerator: "CmdOrCtrl+O", click: ( item: MenuItem, focusedWindow: BrowserWindow | undefined, // eslint-disable-next-line @typescript-eslint/no-unused-vars _event: KeyboardEvent ) => { dialog .showOpenDialog({ properties: ["openFile"], filters: [{ name: "Custom File Type", extensions: ["md"] }], }) .then((res) => { if (res && res["filePaths"].length > 0) { const filePath = res["filePaths"][0]; // 将文件传给渲染线程 if (focusedWindow) { focusedWindow.webContents.send("open-file-path", filePath); openedFile = filePath; } } }) .catch((err) => { console.log(err); }); }, }, { label: "存储", accelerator: "CmdOrCtrl+S", click: ( item: MenuItem, focusedWindow: BrowserWindow | undefined, // eslint-disable-next-line @typescript-eslint/no-unused-vars _event: KeyboardEvent ) => { if (focusedWindow) { focusedWindow.webContents.send("get-content", ""); } }, }, ], }, { label: "编辑", submenu: [ { label: "撤销", accelerator: "CmdOrCtrl+Z", role: "undo", }, { label: "重做", accelerator: "Shift+CmdOrCtrl+Z", role: "redo", }, { type: "separator", }, { label: "剪切", accelerator: "CmdOrCtrl+X", role: "cut", }, { label: "复制", accelerator: "CmdOrCtrl+C", role: "copy", }, { label: "粘贴", accelerator: "CmdOrCtrl+V", role: "paste", }, ], }, { label: "窗口", role: "window", submenu: [ { label: "最小化", accelerator: "CmdOrCtrl+M", role: "minimize", }, { label: "最大化", accelerator: "CmdOrCtrl+M", click: ( item: MenuItem, focusedWindow: BrowserWindow | undefined, // eslint-disable-next-line @typescript-eslint/no-unused-vars _event: KeyboardEvent ) => { if (focusedWindow) { focusedWindow.maximize(); } }, }, { type: "separator", }, { label: "切换全屏", accelerator: (function () { if (process.platform === "darwin") { return "Ctrl+Command+F"; } else { return "F11"; } })(), click: ( item: MenuItem, focusedWindow: BrowserWindow | undefined, // eslint-disable-next-line @typescript-eslint/no-unused-vars _event: KeyboardEvent ) => { if (focusedWindow) { focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); } }, }, ], }, { label: "帮助", role: "help", submenu: [ { label: "学习更多", click: function () { shell.openExternal("http://electron.atom.io"); }, }, ], }, ]; protocol.registerSchemesAsPrivileged([ { scheme: "app", privileges: { secure: true, standard: true } }, ]); async function createWindow() { const { width, height } = screen.getPrimaryDisplay().workAreaSize; const win = new BrowserWindow({ width, height, webPreferences: { nodeIntegration: true, contextIsolation: false, }, }); 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 as string); if (!process.env.IS_TEST) win.webContents.openDevTools(); } else { createProtocol("app"); // Load the index.html when not in development win.loadURL("app://./index.html"); } } // 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 if (process.platform !== "darwin") { app.quit(); } }); 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(); }); // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on("ready", async () => { if (isDevelopment && !process.env.IS_TEST) { // Install Vue Devtools try { await installExtension(VUEJS3_DEVTOOLS); } catch (e) { console.error("Vue Devtools failed to install:", e.toString()); } } createWindow(); // 创建菜单 Menu.setApplicationMenu(Menu.buildFromTemplate(template)); }); // Exit cleanly on request from parent process in development mode. if (isDevelopment) { if (process.platform === "win32") { process.on("message", (data) => { if (data === "graceful-exit") { app.quit(); } }); } else { process.on("SIGTERM", () => { app.quit(); }); } } 到此这篇关于Vue3和Electron实现桌面端应用详解的文章就介绍到这了,更多相关Vue3 Electron 桌面端应用内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论