您当前的位置:首页 > 网站建设 > javascript
| php | asp | css | H5 | javascript | Mysql | Dreamweaver | Delphi | 网站维护 | 帝国cms | React | 考试系统 | ajax |

Vue3和Electron实现桌面端应用详解

51自学网 2022-05-02 21:35:37
  javascript

为了方便记录一些个人随笔,我最近用LaravelVue 3.0撸了一个博客系统,其中使用到了一个基于markdown-it markdown 编辑器Vue组件v-md-editor。我感觉用它去编写markdown还是很方便的。后面就有了一个想法,基于此组件用Electron来实现一个markdown桌面端应用,自己平时拿来使用也是个不错的选择。

题外话:VS Code就是用Electron开发出来的桌面应用,我现在除了移动端的开发外,其他的都是使用VS Code来开发了,各种插件开发起来真的很方便。

接下来我就带大家来一步步来实现这个功能。

Vue CLI 搭建Vue项目

在选择的目录下执行vue create electron-vue3-mark-down

选择自定义的模板(可以选择默认的Vue 3 模板)

选择Vue3TypeScript, 其他的选项基于自身项目决定是否选择

vue3 + TypeScript

执行npm run serve看看效果

效果

Vue项目改造为markdown编辑器

执行npm i @kangc/v-md-editor@next -S安装v-md-editor

添加TypeScript类型定义文件

由于v-md-editor这个库没有TypeScript类型定义文件,我就直接在shims-vue.d.ts这个文件的后面添加的,当然也可以新建一个文件添加申明(tsconfig.json能找到这个文件就OK)。

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>

这个文件也很简单,整个页面就是一个编辑器<v-md-editor v-model="content" height="100vh"></v-md-editor>,这个markdown编辑器有高亮显示,代码显示行号,复制代码按钮等插件,当然更方便的是可以添加其他的插件丰富这个markdown编辑器的功能.

效果如下

编辑器效果

Vue CLI Plugin Electron Builder

我尝试过用Vite 2.0去搭建Electron项目,但是没有找到类似的ViteElectron结合好使的工具,所以放弃了Vite 2.0的诱惑。如果有小伙伴有推荐可以分享下。

使用vue add electron-builder安装,我选择的是13.0.0Electron的最新版本。

我一般是选择最高的版本,其实这个版本有坑,我后面再想想要不要介绍下这个坑,哈哈。

效果

我们看到新加了很多的依赖库,还添加了一个background.ts文件。简单介绍下,这个文件执行在主线程,其他的页面都是在渲染线程。渲染线程有很多限制的,有些功能只能在主线程执行,这里就不具体展开了。

执行npm run electron:serve看效果

效果

至此,就可以看到桌面应用的效果了,并且边修改Vue的代码,桌面应用也能实时看到修改后的效果。

优化功能

启动全屏显示

引入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");        },      },    ],  },];

具体如何定义参阅Electron Menu

打开文件存储目前还没实现,后面实现。

设置菜单栏

import { Menu } from "electron";app.on("ready", async () => {  // 省略...  // 创建菜单  Menu.setApplicationMenu(Menu.buildFromTemplate(template));});

ready钩子函数中进行设置Menu

效果

菜单效果

编辑器打开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);  });

showOpenDialog是打开文件的方法,我们这里指定了只打开md文件;

获得文件路径后,通过focusedWindow.webContents.send("open-file-path", filePath);这个方法将文件路径传给渲染线程。

渲染线程取到文件路径,读取文件内容,赋值给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的图片,同级目录下创建一个为icons.iconset的文件夹;

创建各种不同尺寸要求的图片文件

sips -z 16 16 icon.png -o icons.iconset/icon_16x16.pngsips -z 32 32 icon.png -o icons.iconset/icon_16x16@2x.pngsips -z 32 32 icon.png -o icons.iconset/icon_32x32.pngsips -z 64 64 icon.png -o icons.iconset/icon_32x32@2x.pngsips -z 128 128 icon.png -o icons.iconset/icon_128x128.pngsips -z 256 256 icon.png -o icons.iconset/icon_128x128@2x.pngsips -z 256 256 icon.png -o icons.iconset/icon_256x256.pngsips -z 512 512 icon.png -o icons.iconset/icon_256x256@2x.pngsips -z 512 512 icon.png -o icons.iconset/icon_512x512.pngsips -z 1024 1024 icon.png -o icons.iconset/icon_512x512@2x.png

获得名为icon.icns的图标文件

iconutil -c icns icons.iconset -o icon.icns

打包

npm run electron:build

结果

dmg

获得的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 桌面端应用内容请搜索wanshiok.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持wanshiok.com!


超详细的vue组件间通信总结
JavaScript实现网页版五子棋游戏
51自学网,即我要自学网,自学EXCEL、自学PS、自学CAD、自学C语言、自学css3实例,是一个通过网络自主学习工作技能的自学平台,网友喜欢的软件自学网站。
京ICP备13026421号-1