Vue3和Electron实现桌面端应用

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

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

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

Vue CLI 搭建Vue项目

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

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

    vue create

  • 选择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文件。简单介绍下,这个文件执行在主线程,其他的页面都是在渲染线程。渲染线程有很多限制的,有些功能只能在主线程执行,这里就不具体展开了。

  • 执行vue-cli-service 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");
        },
      },
    ],
  },
];
  1. 具体如何定义参阅Electron Menu

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

  • 设置菜单栏
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);
  });
  1. showOpenDialog是打开文件的方法,我们这里指定了只打开md文件;

  2. 获得文件路径后,通过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生成
  1. 准备一个1024*1024的图片,同级目录下创建一个为icons.iconset的文件夹;
  2. 创建各种不同尺寸要求的图片文件
sips -z 16 16 icon.png -o icons.iconset/icon_16x16.png
sips -z 32 32 icon.png -o icons.iconset/icon_16x16@2x.png
sips -z 32 32 icon.png -o icons.iconset/icon_32x32.png
sips -z 64 64 icon.png -o icons.iconset/icon_32x32@2x.png
sips -z 128 128 icon.png -o icons.iconset/icon_128x128.png
sips -z 256 256 icon.png -o icons.iconset/icon_128x128@2x.png
sips -z 256 256 icon.png -o icons.iconset/icon_256x256.png
sips -z 512 512 icon.png -o icons.iconset/icon_256x256@2x.png
sips -z 512 512 icon.png -o icons.iconset/icon_512x512.png
sips -z 1024 1024 icon.png -o icons.iconset/icon_512x512@2x.png
  1. 获得名为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();
    });
  }
}

原文地址:https://blog.csdn.net/lcl130/article/details/119059901

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要讲解了“electron打包中的坑如何解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“electron...
这篇文章主要介绍“electron打包的坑如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“electron打包的坑如何...
这篇文章主要为大家分析了VSCode中如何调试Electron应用的主进程代码的相关知识点,内容详细易懂,操作细节合理,具有一定参考价值。如果感兴趣的话,不妨跟着跟...
这篇“如何在VSCode上调试Electron应用的主进程代码”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价
vue-cli+electron一种新的脚手架(vue-electron)vue-electron主要业务逻辑都放在src下的renderer文件夹内,和之前的vue-cli搭建项目流程没有任何区别 GIT地址:https://github.com/SimulatedGREG/electron-vue 搭建项目:1.全局安装脚手架:npminstall--globalvue-cli
1、首先成功安装Node.js。2、配置好环境变量path,参加上一篇博客《NodeJs安装》3、全局安装electron,并测试。如下图
相关代码:https://github.com/WozHuang/Barrage-helper/blob/master/src/main/index.dev.js在SPA逐渐成为构建优秀交互体验应用的主流方式后,使用Electron开发跨平台的软件是一个优秀的解决方案。下面简单介绍一下Electron-vue安装vue-devtool的方式。安装步骤下载vue-de
前言本人是做java开发的(菜鸟),做web项目的朋友们基本上都会遇到同样一个,永远不知道客户会怎么样使用,或者说永远不知道客户会用什么浏览器打开我们做出来的应用,就算你跟他说明了一定得用某某某浏览器打开,还是有人会用别的浏览器打开,这种情况通常我们会去做适配(前端),最近公司有需求
electron-builder是将electron做的桌面应用打包成安装包的插件。一、安装使用yarn安装,使用npm安装的有问题(没有尝试),先安装yarn工具。npminstall-gyarn安装electron-builderyarnaddelectron-builder--save-dev二、配置在package.json 中配置"build":{
来源:https:/ewsn.net/say/electron-asar.html 在electron中,asar是个特殊的代码格式。asar包里面包含了程序猿编写的代码逻辑。默认情况下,这些代码逻辑,是放置在resource/app目录下面的,明文可见,这样的话,也就有了代码加密(asar打包)的需求 asar如何解密加密?electron的asar的
 字体图标丢失问题解决方案 重新打包文件npmrunbuild再次运行electron 
<img:src="item.headUrl"alt=""class="contact-head":onerror="morenImage">data(){return{morenImage:'this.src="static/image/head.png"',//默认头像}}
在electron-vue中使用了字体图标,但是打包成.exe文件后图标不显示,路劲问题把字体图标放到static目录下就可以了,静态图片也一样我原来放在其它地方不行改到static目录就可以了
//设置登录cookiesetCookie(name,value){varDays=30;varexp=newDate();vardate=Math.round(exp.getTime()/1000)+Days*24*60*60;constcookie={url:this.webApi,name:name,value:value,e
vue部分cnpminstall-gvue-clivueinitsimulatedgreg/electron-vuemy-projectelectron下载失败解决办法:单独设置electron为淘宝镜像,npmconfigsetelectron_mirrorhttps:/pm.taobao.org/mirrors/electron/yarnconfigsetelectron_mirrorhttps:/pm.taobao.org
原始的方式打包下载对应的版本号的ReleaseElectron然后把对应的项目方便整理成这样的目录结构(Windows下)node_modules重新安装,不然可能启动失败把整文件夹给别人就可以了如果想改名子可以用改名工具rcedit应用程序打包成一个文件为了缓解windows路径名过长的问题(就
窗口间通信的问题electron窗口通信比nwjs要麻烦的多electron分主进程和渲染进程,渲染进程又分主窗口的渲染进程和子窗口的渲染进程主窗口的渲染进程给子窗口的渲染进程发消息1234567891011subWin.webContents.on('dom-ready', () => {    subWin.webCo
按照上一个问题here,我有一个使用Electron平台和Javascript的桌面应用程序,我使用以下方法将HTML5画布转换为JPEG:<aid="download"download="Path.jpg">DownloadJPG</a>然后,functiondownload(){vardt=canvas.toDataURL('image/jpeg');this.href
一.什么是Electron?它和NW.js的区别是什么?Electron是GitHub开发的桌面应用开发框架,它支持使用HTML、CSS、JavaScript来构建跨平台的桌面应用。Electron和NW.js(NW.js是什么可以百度下)的区别是:1.整合Chromium和Node.js的方式不同。在NW.js中,Chromium是直接被打补丁打进去
1.打开父子模态创建,<button@click="showModalHandler">父子模态窗口</button>/enderer渲染器中主注册事件showModalHandler(){ipcRenderer.send("child-down-modal");}//主进程中触发事件/***父子模态窗口*/letchildDownModal;ipcMain.on(&#