feat: 项目结构调整|新增依赖

This commit is contained in:
duanshuwen
2025-11-22 21:17:40 +08:00
parent 38b6a4b4a3
commit 6013c38fe7
40 changed files with 535 additions and 115 deletions

158
src/electron/main/main.ts Normal file
View File

@@ -0,0 +1,158 @@
import { app, BrowserWindow, ipcMain, shell } from "electron";
import path from "node:path";
import started from "electron-squirrel-startup";
import { TabManager } from '@modules/tab-manager'
import { logger } from '@modules/logger'
import "@modules/window-size";
if (started) {
app.quit();
}
class AppMain {
private mainWindow: BrowserWindow | null = null
private tabs: TabManager | null = null
private readonly isDev = !!MAIN_WINDOW_VITE_DEV_SERVER_URL
init() {
this.registerLifecycle()
this.registerCommonIPC()
this.registerAppIPC()
this.registerLogIPC()
}
private createWindow(options?: { frameless?: boolean; route?: string }): BrowserWindow {
const frameless = !!options?.frameless
const win = new BrowserWindow({
width: 900,
height: 670,
autoHideMenuBar: true,
frame: frameless ? false : true,
// @ts-ignore
windowButtonVisibility: frameless ? false : true,
resizable: true,
maximizable: true,
minimizable: true,
webPreferences: {
devTools: this.isDev,
nodeIntegration: false,
contextIsolation: true, // 同时启动上下文隔离
sandbox: true, // 启动沙箱模式
preload: path.join(__dirname, "preload.js"),
},
})
this.loadEntry(win, options?.route)
if (this.isDev) win.webContents.openDevTools()
this.mainWindow = win
this.initTabsIfNeeded(options)
return win
}
private loadEntry(win: BrowserWindow, route?: string) {
// @ts-ignore
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
const target = route ? `${MAIN_WINDOW_VITE_DEV_SERVER_URL}${route}` : MAIN_WINDOW_VITE_DEV_SERVER_URL
win.loadURL(target)
} else {
win.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`))
}
// 暴露安全 API 示例:通过 IPC 处理文件读取
ipcMain.handle('read-file', async (event, filePath) => {
const fs = require('fs');
return fs.promises.readFile(filePath, 'utf-8'); // 主进程处理敏感操作
});
}
private initTabsIfNeeded(options?: { frameless?: boolean; route?: string }) {
if (!this.mainWindow) return
const route = options?.route || ''
const shouldInit = !!options?.frameless || route.startsWith('/browser')
if (!shouldInit) return
this.tabs = new TabManager(this.mainWindow)
this.registerTabIPC()
this.tabs.enable?.()
this.tabs.create('about:blank')
}
private registerLifecycle() {
app.on("ready", () => {
this.createWindow({ frameless: false, route: '/login' })
})
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit()
})
app.on("activate", () => {
if (!BrowserWindow.getAllWindows().length) this.createWindow({ frameless: false, route: '/login' })
})
}
private registerCommonIPC() {
ipcMain.handle('open-baidu', () => {
this.mainWindow?.loadURL("https://www.baidu.com")
})
ipcMain.handle("external-open", (_event, url: string) => {
try {
const allowed = /^https:\/\/(www\.)?(baidu\.com|\w+[\.-]?\w+\.[a-z]{2,})(\/.*)?$/i;
if (typeof url === "string" && allowed.test(url)) {
return shell.openExternal(url);
}
throw new Error("URL not allowed");
} catch (e) {
return Promise.reject(e);
}
})
}
private registerAppIPC() {
ipcMain.handle('app:set-frameless', async (event, route?: string) => {
const old = BrowserWindow.fromWebContents(event.sender)
const win = this.createWindow({ frameless: true, route })
if (old && !old.isDestroyed()) old.close()
return true
})
}
private registerTabIPC() {
if (!this.tabs) return
const tabs = this.tabs
ipcMain.handle('tab:create', (_e, url?: string) => tabs.create(url))
ipcMain.handle('tab:list', () => tabs.list())
ipcMain.handle('tab:navigate', (_e, payload: { tabId: string; url: string }) => tabs.navigate(payload.tabId, payload.url))
ipcMain.handle('tab:reload', (_e, tabId: string) => tabs.reload(tabId))
ipcMain.handle('tab:back', (_e, tabId: string) => tabs.goBack(tabId))
ipcMain.handle('tab:forward', (_e, tabId: string) => tabs.goForward(tabId))
ipcMain.handle('tab:switch', (_e, tabId: string) => tabs.switch(tabId))
ipcMain.handle('tab:close', (_e, tabId: string) => tabs.close(tabId))
}
private registerLogIPC() {
ipcMain.handle('log-to-main', (_e, logLevel: string, message: string) => {
switch(logLevel) {
case 'trace':
logger.trace(message)
break
case 'debug':
logger.debug(message)
break
case 'info':
logger.info(message)
break
case 'warn':
logger.warn(message)
break
case 'error':
logger.error(message)
break
default:
logger.info(message)
break
}
})
}
}
new AppMain().init()