├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── icon │ ├── icon.icns │ └── icon.ico └── index.html ├── screen └── demo.png ├── src ├── App.vue ├── assets │ ├── electron.png │ ├── logo.png │ └── style │ │ └── public.css ├── background.ts ├── components │ └── public │ │ └── SystemInformation.vue ├── main.ts ├── router │ └── index.ts ├── shims-tsx.d.ts ├── shims-vue.d.ts ├── store │ └── index.ts ├── utils │ ├── cookie.ts │ ├── index.ts │ └── main │ │ ├── checkUpdate.ts │ │ └── windowControl.ts └── views │ └── home.vue ├── tsconfig.json └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: ['plugin:vue/essential', '@vue/typescript'], 7 | rules: { 8 | 'no-console': 'off', 9 | 'no-debugger': 'off', 10 | 'no-duplicate-case': 2, 11 | eqeqeq: [2, 'allow-null'], 12 | 'eol-last': ['error', 'always'], 13 | '@typescript-eslint/no-var-requires': 0, 14 | }, 15 | parserOptions: { 16 | parser: '@typescript-eslint/parser', 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /dist_electron 5 | /build 6 | 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | pnpm-debug.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # electron-vue-cli3(electron和vue cli3的基础开发示例) 2 | 3 | > electron+cli3+ts 4 | 5 | ## 个人博客 6 | - [Blog](https://blog.zjinh.cn/) 7 | ## 🖥 运行效果 8 | ![主界面](screen/demo.png) 9 | 10 | ### 克隆代码 11 | ```bash 12 | git clone https://github.com/zjinh/electron-vue-cli3.git 13 | ``` 14 | 15 | ### 安装依赖 16 | ```bash 17 | cd electron-vue-cli3 18 | npm i 19 | ``` 20 | ### 淘宝的npm源 21 | ```bash 22 | npm config set registry 'https://registry.npm.taobao.org' 23 | npm config set ELECTRON_MIRROR='https://npm.taobao.org/mirrors/electron/' 24 | ``` 25 | 或者使用[Yarn](https://yarnpkg.com/)安装依赖 26 | 27 | ### 运行项目 28 | ```bash 29 | npm run dev 30 | npm run dev:web 31 | ``` 32 | ### 打包项目 33 | ```bash 34 | npm run build 35 | npm run build:web 36 | ``` 37 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/cli-plugin-babel/preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-vue-cli3", 3 | "author": "ZJINH", 4 | "version": "2.0.0", 5 | "private": true, 6 | "appId": "com.zjinh.app.test", 7 | "main": "background.js", 8 | "scripts": { 9 | "lint": "vue-cli-service lint", 10 | "dev": "vue-cli-service lint && vue-cli-service electron:serve", 11 | "build": "vue-cli-service lint && vue-cli-service electron:build", 12 | "dev:web": "vue-cli-service serve", 13 | "build:web": "vue-cli-service build", 14 | "postinstall": "electron-builder install-app-deps", 15 | "postuninstall": "electron-builder install-app-deps", 16 | "electron:generate-icons": "electron-icon-builder --input=./public/icon.png --output=build --flatten" 17 | }, 18 | "dependencies": { 19 | "@types/lodash": "^4.14.173", 20 | "axios": "^0.21.1", 21 | "core-js": "^3.18.0", 22 | "electron-updater": "^4.3.9", 23 | "vue": "^2.6.11", 24 | "vue-router": "^3.2.0", 25 | "vuex": "^3.4.0" 26 | }, 27 | "devDependencies": { 28 | "@typescript-eslint/eslint-plugin": "^4.31.1", 29 | "@typescript-eslint/parser": "^4.31.1", 30 | "@vue/cli-plugin-babel": "~4.5.0", 31 | "@vue/cli-plugin-eslint": "~4.5.0", 32 | "@vue/cli-plugin-router": "~4.5.0", 33 | "@vue/cli-plugin-typescript": "^4.5.13", 34 | "@vue/cli-plugin-vuex": "~4.5.0", 35 | "@vue/cli-service": "~4.5.0", 36 | "@vue/eslint-config-typescript": "^7.0.0", 37 | "electron": "^14.0.1", 38 | "electron-icon-builder": "^2.0.1", 39 | "eslint": "^7.32.0", 40 | "eslint-plugin-vue": "^7.18.0", 41 | "node-sass": "^4.12.0", 42 | "sass-loader": "^8.0.2", 43 | "typescript": "~4.1.5", 44 | "vue-cli-plugin-electron-builder": "^1.4.6", 45 | "vue-template-compiler": "^2.6.11" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjinh/electron-vue-cli3/299146715aa6434dd24abe08b0cd81c6e7f0aee8/public/favicon.ico -------------------------------------------------------------------------------- /public/icon/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjinh/electron-vue-cli3/299146715aa6434dd24abe08b0cd81c6e7f0aee8/public/icon/icon.icns -------------------------------------------------------------------------------- /public/icon/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjinh/electron-vue-cli3/299146715aa6434dd24abe08b0cd81c6e7f0aee8/public/icon/icon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /screen/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjinh/electron-vue-cli3/299146715aa6434dd24abe08b0cd81c6e7f0aee8/screen/demo.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/assets/electron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjinh/electron-vue-cli3/299146715aa6434dd24abe08b0cd81c6e7f0aee8/src/assets/electron.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjinh/electron-vue-cli3/299146715aa6434dd24abe08b0cd81c6e7f0aee8/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/style/public.css: -------------------------------------------------------------------------------- 1 | *{ 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | font-family: '微软雅黑'; 6 | } 7 | ul,li{ 8 | list-style: none; 9 | margin: 0; 10 | padding: 0; 11 | } 12 | :focus,:active{ 13 | outline:0; 14 | } 15 | a:hover,a:link,a:visited { 16 | text-decoration:none; 17 | } 18 | input:-webkit-autofill{ 19 | -webkit-box-shadow: 0 0 0 400px #fff inset!important; 20 | -webkit-text-fill-color: #666666!important; 21 | } 22 | button,input,select,textarea { 23 | border: none; 24 | -webkit-transition:all .35s; 25 | -moz-transition:all .35s; 26 | -o-transition:all .35s; 27 | resize: none!important; 28 | } 29 | button{ 30 | cursor: pointer; 31 | } 32 | button[disabled]{ 33 | cursor: not-allowed; 34 | opacity: .5; 35 | } 36 | #app,body,html{ 37 | width: 100%; 38 | height: 100%; 39 | overflow: hidden; 40 | user-select: none; 41 | background: transparent none !important; 42 | } 43 | -------------------------------------------------------------------------------- /src/background.ts: -------------------------------------------------------------------------------- 1 | import packageInfo from '../package.json'; 2 | import { app, protocol, ipcMain, BrowserWindow } from 'electron'; 3 | import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'; 4 | import { WindowControl } from './utils/main/windowControl'; 5 | import CheckUpdate from './utils/main/CheckUpdate'; 6 | protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }]); 7 | const windowControl = new WindowControl(); 8 | const checkUpdate = new CheckUpdate(); 9 | let win: BrowserWindow | null = null; 10 | /*初始化ipc*/ 11 | function bindIpc() { 12 | /*系统操作事件*/ 13 | ipcMain.on('system', (event, type, data) => { 14 | switch (type) { 15 | case 'check-for-update' /*检查更新*/: 16 | checkUpdate.check(event, data); 17 | break; 18 | case 'update' /*安装更新*/: 19 | checkUpdate.update(); 20 | break; 21 | case 'exit': 22 | app.quit(); 23 | break; 24 | } 25 | }); 26 | } 27 | /*创建窗口*/ 28 | function createWindow() { 29 | win = windowControl.create({ 30 | frame: true, 31 | name: 'home', 32 | url: '', 33 | }); 34 | } 35 | const gotTheLock = app.requestSingleInstanceLock(); 36 | if (!gotTheLock) { 37 | app.quit(); 38 | } else { 39 | app.on('second-instance', () => { 40 | //这里是单例模式,当已经存在窗口仍然打开的处理 41 | if (win) { 42 | win.show(); 43 | } 44 | }); 45 | app.on('ready', function () { 46 | bindIpc(); //初始化ipc 47 | createProtocol('app'); 48 | app.setAppUserModelId(packageInfo.appId); 49 | app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required'); 50 | createWindow(); 51 | }); 52 | } 53 | app.on('window-all-closed', () => { 54 | if (process.platform !== 'darwin') { 55 | app.quit(); 56 | } 57 | }); 58 | app.on('activate', () => { 59 | if (win === null) { 60 | createWindow(); 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /src/components/public/SystemInformation.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 45 | 46 | 79 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import router from './router'; 4 | import store from './store'; 5 | require('./utils/index'); 6 | declare module 'vue/types/vue' { 7 | interface Vue { 8 | $ipc: any; 9 | $cookie: any; 10 | $notify: Function; 11 | $electron: any; 12 | } 13 | } 14 | Vue.config.productionTip = false; 15 | new Vue({ 16 | router, 17 | store, 18 | render: (h) => h(App), 19 | }).$mount('#app'); 20 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter, { RouteConfig } from 'vue-router'; 3 | 4 | Vue.use(VueRouter); 5 | 6 | const routes: Array = [ 7 | { 8 | path: '/', 9 | name: 'Home', 10 | component: () => import('../views/home.vue'), 11 | }, 12 | ]; 13 | 14 | const router = new VueRouter({ 15 | routes, 16 | }); 17 | 18 | export default router; 19 | -------------------------------------------------------------------------------- /src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue'; 3 | export default Vue; 4 | } 5 | declare module '*.json' { 6 | const value: any; 7 | export default value; 8 | } 9 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | Vue.use(Vuex); 5 | 6 | export default new Vuex.Store({ 7 | state: {}, 8 | mutations: {}, 9 | actions: {}, 10 | modules: {}, 11 | }); 12 | -------------------------------------------------------------------------------- /src/utils/cookie.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | get: function (name: string) { 3 | let arr; 4 | const reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); 5 | if ((arr = document.cookie.match(reg))) return decodeURIComponent(arr[2]); 6 | else return null; 7 | }, 8 | set: function (name: string, value: any, time: number) { 9 | const exp = new Date(); 10 | exp.setTime(exp.getTime() + time * 1000); 11 | document.cookie = name + '=' + encodeURI(value) + ';expires=' + exp.toUTCString() + ';path=/'; 12 | }, 13 | remove: function (name: string) { 14 | const exp = new Date(); 15 | exp.setTime(exp.getTime() - 1); 16 | if (this.get(name) != null) document.cookie = name + '=' + this.get(name) + ';expires=' + exp.toUTCString(); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | const path = require('path'); 3 | /*开始自动化引入公用组件*/ 4 | const requireComponent = require.context('../components/public', true, /\.vue$/); 5 | requireComponent.keys().forEach((fileName) => { 6 | // 获取组件配置 7 | const componentConfig = requireComponent(fileName); 8 | // 剥去文件名开头的 `./` 和`.vue`结尾的扩展名 9 | const componentName = path.basename(fileName).replace(/\.vue$/, ''); 10 | // 全局注册组件 11 | Vue.component(componentName.replace(/\//, '-'), componentConfig.default || componentConfig); 12 | }); 13 | //cookie模块 14 | Vue.prototype.$cookie = require('./cookie'); 15 | //ipc模块 16 | if (!!process.versions) { 17 | const electron = require('electron'); 18 | const ipcRenderer = electron.ipcRenderer; 19 | //引入electron接口 20 | Vue.prototype.$electron = electron; //electron 21 | Vue.prototype.$ipc = ipcRenderer; //ipc接口 22 | } 23 | //系统通知 24 | Vue.prototype.$notify = function (msg: string, title: string) { 25 | const notification = { 26 | title: title, 27 | body: msg, 28 | icon: require('../assets/logo.png'), 29 | }; 30 | return new window.Notification(notification.title, notification); 31 | }; 32 | -------------------------------------------------------------------------------- /src/utils/main/checkUpdate.ts: -------------------------------------------------------------------------------- 1 | import { autoUpdater } from 'electron-updater'; 2 | import { EventEmitter } from 'events'; 3 | 4 | class CheckUpdate extends EventEmitter { 5 | constructor() { 6 | super(); 7 | } 8 | init(event) { 9 | const message = { 10 | appName: 'CloudDisk', 11 | error: '检查更新出错, 请联系开发人员', 12 | checking: '正在检查更新……', 13 | updateAva: '检测到新版本,正在下载……', 14 | updateNotAva: '现在使用的就是最新版本,不用更新', 15 | downloaded: '最新版本已下载,点击安装进行更新', 16 | }; 17 | //当开始检查更新的时候触发 18 | autoUpdater.on('checking-for-update', function () { 19 | event.sender.send('check-for-update', message.checking); //返回一条信息 20 | }); 21 | //当发现一个可用更新的时候触发,更新包下载会自动开始 22 | autoUpdater.on('update-available', function (info) { 23 | event.sender.send('update-down-success', info); 24 | event.sender.send('check-for-update', message.updateAva); //返回一条信息 25 | }); 26 | //当没有可用更新的时候触发 27 | autoUpdater.on('update-not-available', function () { 28 | event.sender.send('check-for-update', message.updateNotAva); //返回一条信息 29 | }); 30 | autoUpdater.on('error', function () { 31 | event.sender.send('check-for-update', message.error); //返回一条信息 32 | }); 33 | // 更新下载进度事件 34 | autoUpdater.on('download-progress', (progressObj) => { 35 | event.sender.send('download-progress', progressObj); 36 | }); 37 | autoUpdater.on('update-downloaded', function () { 38 | event.sender.send('check-for-update', message.downloaded); //返回一条信息 39 | //通过main进程发送事件给renderer进程,提示更新信息 40 | }); 41 | } 42 | check(event, data: string) { 43 | autoUpdater.setFeedURL(data); 44 | this.init(event); 45 | autoUpdater.checkForUpdates(); 46 | } 47 | update() { 48 | //安装更新 49 | autoUpdater.quitAndInstall(); 50 | } 51 | } 52 | export default CheckUpdate; 53 | -------------------------------------------------------------------------------- /src/utils/main/windowControl.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow, Menu } from 'electron'; 2 | const { projectName } = require('../../../package.json'); 3 | const isDevelopment = process.env.NODE_ENV !== 'production'; 4 | import { EventEmitter } from 'events'; 5 | interface WindowControlType { 6 | getWindow: Function; 7 | create: Function; 8 | active: Function; 9 | closeAll: Function; 10 | } 11 | const port = 9020; 12 | class WindowControl extends EventEmitter { 13 | constructor() { 14 | super(); 15 | } 16 | isMacOs = process.platform === 'darwin'; 17 | winList: any = {}; 18 | create(options: any) { 19 | const name: string = options.name; 20 | const data: any = options.data; 21 | if (!name) { 22 | return; 23 | } 24 | //存在就激活窗口 25 | if (this.getWindow(name)) { 26 | this.active(name, data); 27 | return; 28 | } 29 | Menu.setApplicationMenu(null); 30 | const defaultOptions = { 31 | width: 800, 32 | height: 600, 33 | title: projectName, 34 | frame: this.isMacOs, 35 | useContentSize: false, 36 | transparent: false, 37 | minimizable: true, 38 | maximizable: true, 39 | resizable: true, 40 | alwaysOnTop: false, 41 | show: false, 42 | titleBarStyle: 'hiddenInset', 43 | webPreferences: { 44 | devTools: isDevelopment, 45 | nodeIntegration: true, 46 | webSecurity: false, 47 | scrollBounce: true, 48 | contextIsolation: false, 49 | enableRemoteModule: true, 50 | plugins: true, 51 | webviewTag: true, 52 | }, 53 | }; 54 | options = Object.assign(defaultOptions, options); 55 | this.winList[name] = new BrowserWindow(options); 56 | this.winList[name] = this.winList[name]; 57 | if (options.backgroundColor) { 58 | this.winList[name].setBackgroundColor(options.backgroundColor); 59 | } 60 | //自定义回调 61 | this.winList[name].callback = (data: any) => { 62 | this.winList[name].webContents.send('win-data', data); 63 | typeof options.callback === 'function' ? options.callback() : ''; 64 | }; 65 | //加载url 66 | this.winList[name].loadURL(this.createUrl(options.url)).then(() => { 67 | this.winList[name].setTitle(options.title); 68 | this.winList[name].callback(data); 69 | }); 70 | isDevelopment && this.winList[name].webContents.openDevTools(); 71 | //处理显示 72 | this.winList[name].on('ready-to-show', (event: Event) => { 73 | this.winList[name].show(); 74 | this.winList[name].focus(); 75 | typeof options.ready === 'function' ? options.ready(event) : ''; 76 | }); 77 | //处理关闭 78 | this.winList[name].on('closed', (event: Event) => { 79 | delete this.winList[name]; 80 | typeof options.onclose === 'function' ? options.onclose(event) : ''; 81 | }); 82 | return this.winList[name]; 83 | } 84 | getWindow(windowName: string) { 85 | return this.winList[windowName]; 86 | } 87 | active(windowName: string, data: any) { 88 | const currentWin = this.getWindow(windowName); 89 | if (currentWin) { 90 | currentWin.show(); 91 | currentWin.focus(); 92 | data && currentWin.callback(data); 93 | } 94 | } 95 | createUrl(router: string) { 96 | if (router.includes('file://') || router.includes('http')) { 97 | return router; 98 | } 99 | if (isDevelopment) { 100 | return `http://localhost:${port}/#/` + router; 101 | } 102 | return 'app://./index.html#/' + router; 103 | } 104 | closeAll(filterNotClose: Array = []) { 105 | for (const name in this.winList) { 106 | const win = this.winList[name]; 107 | if (!filterNotClose.includes(name)) { 108 | win.close(); 109 | } 110 | } 111 | } 112 | cloudWindow(options: any, callback: Function, onClose: Function) { 113 | return this.create({ 114 | ...options, 115 | onclose: () => { 116 | onClose && onClose(); 117 | }, 118 | callback: () => { 119 | callback && callback(); 120 | }, 121 | }); 122 | } 123 | } 124 | 125 | export { WindowControl, WindowControlType }; 126 | -------------------------------------------------------------------------------- /src/views/home.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 111 | 112 | 178 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": false, 13 | "baseUrl": ".", 14 | "noImplicitAny": false, 15 | "types": [ 16 | "webpack-env", 17 | "jest" 18 | ], 19 | "paths": { 20 | "@/*": [ 21 | "src/*" 22 | ] 23 | }, 24 | "lib": [ 25 | "esnext", 26 | "dom", 27 | "dom.iterable", 28 | "scripthost" 29 | ] 30 | }, 31 | "include": [ 32 | "src/**/*.ts", 33 | "src/**/*.tsx", 34 | "src/**/*.vue", 35 | "tests/**/*.ts", 36 | "tests/**/*.tsx" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | function resolve(dir) { 3 | return path.join(__dirname, dir); 4 | } 5 | const productName = 'test'; 6 | const appId = 'com.zjinh.app.' + productName; 7 | const menuCategory = 'electron-cli3'; 8 | const shortcutName = 'cli3-测试'; 9 | const port = 9020; 10 | module.exports = { 11 | /*publicPath: '/', 12 | outputDir: 'dist', 13 | assetsDir: 'static', 14 | lintOnSave: process.env.NODE_ENV === 'development', 15 | productionSourceMap: false,*/ 16 | devServer: { 17 | port: port, 18 | /* open: true, 19 | overlay: { 20 | warnings: false, 21 | errors: true 22 | }*/ 23 | }, 24 | chainWebpack(config) { 25 | // alias 26 | config.resolve.alias.set('@', resolve('src')); 27 | }, 28 | pluginOptions: { 29 | electronBuilder: { 30 | builderOptions: { 31 | productName: productName, 32 | appId: appId, 33 | directories: { 34 | output: 'build', 35 | }, 36 | win: { 37 | icon: 'public/icon/icon.ico', 38 | artifactName: '${productName}_setup_${version}.${ext}', 39 | target: ['nsis'], 40 | }, 41 | nsis: { 42 | oneClick: false, 43 | menuCategory: menuCategory, 44 | shortcutName: shortcutName, 45 | allowToChangeInstallationDirectory: true, 46 | perMachine: true, 47 | runAfterFinish: true, 48 | }, 49 | dmg: { 50 | contents: [ 51 | { 52 | x: 410, 53 | y: 150, 54 | type: 'link', 55 | path: '/Applications', 56 | }, 57 | { 58 | x: 130, 59 | y: 150, 60 | type: 'file', 61 | }, 62 | ], 63 | }, 64 | mac: { 65 | icon: 'public/icon/icon.icns', 66 | }, 67 | linux: { 68 | icon: 'public/icon/icon.ico', 69 | }, 70 | }, 71 | }, 72 | }, 73 | }; 74 | --------------------------------------------------------------------------------