22 changed files with 1945 additions and 11 deletions
@ -0,0 +1,40 @@ |
|||
import type { PluginOption } from 'vite'; |
|||
|
|||
import type { CommonPluginOptions } from '../typing'; |
|||
|
|||
import fs from 'node:fs'; |
|||
|
|||
import electron from 'vite-plugin-electron/simple'; |
|||
|
|||
export const viteElectronPlugin = ( |
|||
options: CommonPluginOptions, |
|||
): PluginOption => { |
|||
fs.rmSync('dist-electron', { force: true, recursive: true }); |
|||
|
|||
const isServe = !options.isBuild; |
|||
const isBuild = options.isBuild; |
|||
const sourcemap = isServe || !!process.env.VSCODE_DEBUG; |
|||
return electron({ |
|||
main: { |
|||
entry: 'electron/main.ts', |
|||
vite: { |
|||
build: { |
|||
minify: isBuild, |
|||
outDir: 'dist-electron/main', |
|||
sourcemap, |
|||
}, |
|||
}, |
|||
}, |
|||
preload: { |
|||
input: 'electron/preload.ts', |
|||
vite: { |
|||
build: { |
|||
minify: isBuild, |
|||
outDir: 'dist-electron/preload', |
|||
sourcemap: sourcemap ? 'inline' : undefined, // #332
|
|||
}, |
|||
}, |
|||
}, |
|||
renderer: {}, |
|||
}); |
|||
}; |
|||
@ -1,6 +1,9 @@ |
|||
{ |
|||
"$schema": "https://json.schemastore.org/tsconfig", |
|||
"extends": "@vben/tsconfig/library.json", |
|||
"compilerOptions": { |
|||
"types": ["@vben-core/typings/electron"] |
|||
}, |
|||
"include": ["src"], |
|||
"exclude": ["node_modules"] |
|||
} |
|||
|
|||
@ -0,0 +1,22 @@ |
|||
import type { IpcRendererEvent } from 'electron'; |
|||
|
|||
export type IpcRendererInvoke = 'open-win'; |
|||
|
|||
declare global { |
|||
interface Window { |
|||
ipcRenderer: { |
|||
invoke: (channel: IpcRendererInvoke, ...args: any[]) => Promise<any>; |
|||
off: ( |
|||
channel: string, |
|||
listener: (event: IpcRendererEvent, ...args: any[]) => void, |
|||
) => void; |
|||
on: ( |
|||
channel: string, |
|||
listener: (event: IpcRendererEvent, ...args: any[]) => void, |
|||
) => void; |
|||
send: (channel: string, data: any) => Promise<any>; |
|||
}; |
|||
} |
|||
} |
|||
|
|||
export {}; |
|||
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 47 KiB |
@ -0,0 +1,149 @@ |
|||
import os from 'node:os'; |
|||
import path from 'node:path'; |
|||
import process from 'node:process'; |
|||
import { fileURLToPath } from 'node:url'; |
|||
|
|||
import { |
|||
app, |
|||
BrowserWindow, |
|||
globalShortcut, |
|||
ipcMain, |
|||
Menu, |
|||
shell, |
|||
} from 'electron'; |
|||
|
|||
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; |
|||
|
|||
// Disable GPU Acceleration for Windows 7
|
|||
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(); |
|||
} |
|||
|
|||
let win: BrowserWindow | null = null; |
|||
const preload = path.join(__dirname, '../preload/preload.mjs'); |
|||
const indexHtml = path.join(RENDERER_DIST, 'index.html'); |
|||
|
|||
async function createWindow() { |
|||
win = new BrowserWindow({ |
|||
autoHideMenuBar: true, |
|||
height: 900, |
|||
icon: path.join(process.env.VITE_PUBLIC as string, 'favicon.ico'), |
|||
show: false, |
|||
title: 'Main window', |
|||
webPreferences: { |
|||
contextIsolation: true, |
|||
nodeIntegration: false, |
|||
preload, |
|||
webSecurity: true, |
|||
}, |
|||
width: 1440, |
|||
}); |
|||
|
|||
// 监听窗口准备好显示的事件
|
|||
win.once('ready-to-show', () => { |
|||
win?.maximize(); // 最大化窗口
|
|||
win?.show(); // 显示窗口
|
|||
}); |
|||
|
|||
if (VITE_DEV_SERVER_URL) { |
|||
win.loadURL(VITE_DEV_SERVER_URL); |
|||
} else { |
|||
win.loadFile(indexHtml); |
|||
} |
|||
|
|||
// Test actively push message to the Electron-Renderer
|
|||
win.webContents.on('did-finish-load', () => { |
|||
win?.webContents.send('main-process-message', new Date().toLocaleString()); |
|||
}); |
|||
|
|||
// Make all links open with the browser, not with the application
|
|||
win.webContents.setWindowOpenHandler(({ url }) => { |
|||
if (url.startsWith('https:')) shell.openExternal(url); |
|||
return { action: 'deny' }; |
|||
}); |
|||
// win.webContents.on('will-navigate', (event, url) => { }) #344
|
|||
} |
|||
Menu.setApplicationMenu(null); |
|||
app |
|||
.whenReady() |
|||
.then(createWindow) |
|||
.then(() => { |
|||
// 禁用了菜单之后,默认的快捷键也会被禁用,这里重新注册部分常用快捷键
|
|||
if (VITE_DEV_SERVER_URL) { |
|||
// 开发模式下监听快捷键来打开开发者工具
|
|||
globalShortcut.register('CmdOrCtrl+Shift+I', () => { |
|||
BrowserWindow.getFocusedWindow()?.webContents.toggleDevTools(); |
|||
}); |
|||
} |
|||
// 监听快捷键来刷新页面
|
|||
globalShortcut.registerAll(['CmdOrCtrl+R', 'CmdOrCtrl+F5'], () => { |
|||
BrowserWindow.getFocusedWindow()?.webContents.reload(); |
|||
}); |
|||
// 监听快捷键来强制刷新页面
|
|||
globalShortcut.registerAll( |
|||
['CmdOrCtrl+Shift+R', 'CmdOrCtrl+Shift+F5'], |
|||
() => { |
|||
BrowserWindow.getFocusedWindow()?.webContents.reloadIgnoringCache(); |
|||
}, |
|||
); |
|||
}); |
|||
|
|||
app.on('window-all-closed', () => { |
|||
win = null; |
|||
if (process.platform !== 'darwin') app.quit(); |
|||
}); |
|||
|
|||
app.on('will-quit', () => { |
|||
globalShortcut.unregisterAll(); |
|||
}); |
|||
|
|||
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 > 0) { |
|||
allWindows[0].focus(); |
|||
} else { |
|||
createWindow(); |
|||
} |
|||
}); |
|||
|
|||
// New window example arg: new windows url
|
|||
ipcMain.handle('open-win', (_, arg) => { |
|||
const childWindow = new BrowserWindow({ |
|||
webPreferences: { |
|||
contextIsolation: true, |
|||
nodeIntegration: true, |
|||
preload, |
|||
webviewTag: true, |
|||
}, |
|||
}); |
|||
|
|||
if (VITE_DEV_SERVER_URL) { |
|||
childWindow.loadURL(`${VITE_DEV_SERVER_URL}#${arg}`); |
|||
childWindow.webContents.openDevTools(); |
|||
} else { |
|||
childWindow.loadFile(indexHtml, { hash: arg }); |
|||
} |
|||
}); |
|||
@ -0,0 +1,114 @@ |
|||
import { contextBridge, ipcRenderer } from 'electron'; |
|||
|
|||
// --------- Expose some API to the Renderer process ---------
|
|||
contextBridge.exposeInMainWorld('ipcRenderer', { |
|||
invoke(...args: Parameters<typeof ipcRenderer.invoke>) { |
|||
const [channel, ...omit] = args; |
|||
return ipcRenderer.invoke(channel, ...omit); |
|||
}, |
|||
off(...args: Parameters<typeof ipcRenderer.off>) { |
|||
const [channel, ...omit] = args; |
|||
return ipcRenderer.off(channel, ...omit); |
|||
}, |
|||
on(...args: Parameters<typeof ipcRenderer.on>) { |
|||
const [channel, listener] = args; |
|||
return ipcRenderer.on(channel, (event, ...args) => |
|||
listener(event, ...args), |
|||
); |
|||
}, |
|||
send(...args: Parameters<typeof ipcRenderer.send>) { |
|||
const [channel, ...omit] = args; |
|||
return ipcRenderer.send(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 (![...parent.children].includes(child)) {
|
|||
// return parent.append(child);
|
|||
// }
|
|||
// },
|
|||
// remove(parent: HTMLElement, child: HTMLElement) {
|
|||
// if ([...parent.children].includes(child)) {
|
|||
// return child.remove();
|
|||
// }
|
|||
// },
|
|||
// };
|
|||
|
|||
// 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, 4999);
|
|||
File diff suppressed because it is too large
Loading…
Reference in new issue