1. package.json中添加依赖
"electron": "^29.1.1",
"electron-builder": "^24.13.3",
"vite-plugin-electron": "^0.28.4",
"vite-plugin-electron-renderer": "^0.14.5"
2. vite.config.ts中添加electron插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from "path"
import electron from 'vite-plugin-electron/simple'
import fs from 'node:fs'
import pkg from './package.json'
const isDevelopment = process.env.NODE_ENV !== 'production'
fs.rmSync('dist-electron', { recursive: true, force: true })
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), !isDevelopment && electron({
main: {
// Shortcut of `build.lib.entry`
entry: 'electron/main/index.ts',
onstart({ startup }) {
startup()
},
vite: {
build: {
sourcemap: true,
minify: true,
outDir: 'dist-electron/main',
rollupOptions: {
external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}),
},
},
},
},
preload: {
input: 'electron/preload/index.ts',
vite: {
build: {
sourcemap: 'inline',
minify: true,
outDir: 'dist-electron/preload',
rollupOptions: {
external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}),
},
},
},
},
renderer: {},
})],
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}
},
clearScreen: false,
})
根目录下创建electron目录,然后添加main与preload子目录
// electron/main/index.ts
import { app, BrowserWindow, ipcMain, dialog } from 'electron'
// import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
import os from 'node:os'
// const require = createRequire(import.meta.url)
const __dirname = path.dirname(fileURLToPath(import.meta.url))
process.env.APP_ROOT = path.join(__dirname, '../..')
export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron')
export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist')
export const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL
process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
? path.join(process.env.APP_ROOT, 'public')
: RENDERER_DIST
if (os.release().startsWith('6.1')) app.disableHardwareAcceleration()
// Set application name for Windows 10+ notifications
if (process.platform === 'win32') app.setAppUserModelId(app.getName())
if (!app.requestSingleInstanceLock()) {
app.quit()
process.exit(0)
}
let win: BrowserWindow | null = null
const preload = path.join(__dirname, '../preload/index.mjs')
const indexHtml = path.join(RENDERER_DIST, 'index.html')
async function createWindow() {
win = new BrowserWindow({
title: 'XXX软件',
icon: path.join(process.env.VITE_PUBLIC || '', 'favicon.ico'),
width: 1290,
height: 800,
webPreferences: {
preload,
},
})
win.on('close', (event) => {
if (dialog.showMessageBoxSync(win!, {
type: 'info',
buttons: ['最小化到托盘', '直接退出'],
title: '提示',
message: '确定要退出吗',
defaultId: 0,
cancelId: 1,
}) === 0) {
event.preventDefault()
win?.hide()
} else {
win?.destroy()
}
})
if (VITE_DEV_SERVER_URL) {
win.loadURL(VITE_DEV_SERVER_URL)
// Open devTool if the app is not packaged
win.webContents.openDevTools()
} else {
win.loadFile(indexHtml)
}
}
app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
win?.destroy()
// if (process.platform !== 'darwin')
app.quit()
})
app.on('second-instance', () => {
if (win) {
// Focus on the main window if the user tried to open another
if (win.isMinimized()) win.restore()
win.focus()
}
})
app.on('activate', () => {
const allWindows = BrowserWindow.getAllWindows()
if (allWindows.length) {
allWindows[0].show()
allWindows[0].focus()
} else {
createWindow()
}
})
// New window example arg: new windows url
ipcMain.handle('open-win', (_, arg) => {
const childWindow = new BrowserWindow({
webPreferences: {
preload,
nodeIntegration: true,
contextIsolation: false,
},
})
if (VITE_DEV_SERVER_URL) {
childWindow.loadURL(`${VITE_DEV_SERVER_URL}#${arg}`)
} else {
childWindow.loadFile(indexHtml, { hash: arg })
}
})
// electron/preload/index.ts
import { ipcRenderer, contextBridge } from 'electron'
// --------- Expose some API to the Renderer process ---------
contextBridge.exposeInMainWorld('ipcRenderer', {
on(...args: Parameters<typeof ipcRenderer.on>) {
const [channel, listener] = args
return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args))
},
off(...args: Parameters<typeof ipcRenderer.off>) {
const [channel, ...omit] = args
return ipcRenderer.off(channel, ...omit)
},
send(...args: Parameters<typeof ipcRenderer.send>) {
const [channel, ...omit] = args
return ipcRenderer.send(channel, ...omit)
},
invoke(...args: Parameters<typeof ipcRenderer.invoke>) {
const [channel, ...omit] = args
return ipcRenderer.invoke(channel, ...omit)
},
// You can expose other APTs you need here.
// ...
})
// --------- Preload scripts loading ---------
function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
return new Promise((resolve) => {
if (condition.includes(document.readyState)) {
resolve(true)
} else {
document.addEventListener('readystatechange', () => {
if (condition.includes(document.readyState)) {
resolve(true)
}
})
}
})
}
const safeDOM = {
append(parent: HTMLElement, child: HTMLElement) {
if (!Array.from(parent.children).find(e => e === child)) {
return parent.appendChild(child)
}
},
remove(parent: HTMLElement, child: HTMLElement) {
if (Array.from(parent.children).find(e => e === child)) {
return parent.removeChild(child)
}
},
}
/**
* https://tobiasahlin.com/spinkit
* https://connoratherton.com/loaders
* https://projects.lukehaas.me/css-loaders
* https://matejkustec.github.io/SpinThatShit
*/
function useLoading() {
const className = `loaders-css__square-spin`
const styleContent = `
@keyframes square-spin {
25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }
50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); }
75% { transform: perspective(100px) rotateX(0) rotateY(180deg); }
100% { transform: perspective(100px) rotateX(0) rotateY(0); }
}
.${className} > div {
animation-fill-mode: both;
width: 50px;
height: 50px;
background: #fff;
animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
}
.app-loading-wrap {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #282c34;
z-index: 9;
}
`
const oStyle = document.createElement('style')
const oDiv = document.createElement('div')
oStyle.id = 'app-loading-style'
oStyle.innerHTML = styleContent
oDiv.className = 'app-loading-wrap'
oDiv.innerHTML = `<div class="${className}"><div></div></div>`
return {
appendLoading() {
safeDOM.append(document.head, oStyle)
safeDOM.append(document.body, oDiv)
},
removeLoading() {
safeDOM.remove(document.head, oStyle)
safeDOM.remove(document.body, oDiv)
},
}
}
// ----------------------------------------------------------------------
const { appendLoading, removeLoading } = useLoading()
domReady().then(appendLoading)
window.onmessage = (ev) => {
ev.data.payload === 'removeLoading' && removeLoading()
}
setTimeout(removeLoading, 500)
根目录下添加electron-builder.json5配置文件
// @see https://www.electron.build/configuration/configuration
{
"$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
"appId": "fu0001892",
"asar": true,
"productName": "XXX软件",
"directories": {
"output": "release/${version}"
},
"files": [
"dist",
"dist-electron"
],
"mac": {
"target": [
"dmg"
],
"artifactName": "${productName}-Mac-${version}-Installer.${ext}"
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
],
"artifactName": "${productName}-Windows-${version}-Setup.${ext}"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
},
"linux": {
"target": [
"AppImage"
],
"artifactName": "${productName}-Linux-${version}.${ext}"
}
}
package.json添加打包命令
"eln": "vue-tsc && vite build && electron-builder"
评论列表
0/1000
共 0 评论