mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-04 04:01:16 +08:00
init
This commit is contained in:
193
electron/main/index.ts
Normal file
193
electron/main/index.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import { app, shell, BrowserWindow, protocol, nativeTheme } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { optimizer, is, platform } from '@electron-toolkit/utils'
|
||||
import * as fs from 'fs/promises'
|
||||
import { checkUpdate } from './update'
|
||||
import mainIpcMain from './ipcMain'
|
||||
|
||||
class MainProcess {
|
||||
mainWindow: BrowserWindow | null
|
||||
constructor() {
|
||||
// 主窗口
|
||||
this.mainWindow = null
|
||||
|
||||
// 设置应用程序名称
|
||||
if (process.platform === 'win32') app.setAppUserModelId(app.getName())
|
||||
// 初始化
|
||||
this.checkApp().then(async (lockObtained) => {
|
||||
if (lockObtained) {
|
||||
await this.init()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 单例锁
|
||||
async checkApp() {
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit()
|
||||
// 未获得锁
|
||||
return false
|
||||
}
|
||||
// 聚焦到当前程序
|
||||
else {
|
||||
app.on('second-instance', () => {
|
||||
if (this.mainWindow) {
|
||||
this.mainWindow.show()
|
||||
if (this.mainWindow.isMinimized()) this.mainWindow.restore()
|
||||
this.mainWindow.focus()
|
||||
}
|
||||
})
|
||||
// 获得锁
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化程序
|
||||
async init() {
|
||||
// 注册应用协议
|
||||
app.setAsDefaultProtocolClient('chatlens')
|
||||
|
||||
// 应用程序准备好之前注册
|
||||
protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }])
|
||||
|
||||
// 主应用程序事件
|
||||
this.mainAppEvents()
|
||||
}
|
||||
|
||||
// 创建主窗口
|
||||
async createWindow() {
|
||||
this.mainWindow = new BrowserWindow({
|
||||
width: 1180,
|
||||
height: 720,
|
||||
minWidth: 1180,
|
||||
minHeight: 720,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
sandbox: false,
|
||||
devTools: true,
|
||||
},
|
||||
})
|
||||
|
||||
// 设置默认日间模式
|
||||
nativeTheme.themeSource = 'light'
|
||||
|
||||
this.mainWindow.once('ready-to-show', () => {
|
||||
this.mainWindow?.show()
|
||||
})
|
||||
|
||||
// 主窗口事件
|
||||
this.mainWindowEvents()
|
||||
|
||||
this.mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||
this.mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||
} else {
|
||||
this.mainWindow.loadFile(join(__dirname, '../../out/renderer/index.html'))
|
||||
}
|
||||
}
|
||||
|
||||
// 主应用程序事件
|
||||
mainAppEvents() {
|
||||
app.whenReady().then(async () => {
|
||||
// 设置Windows应用程序用户模型id
|
||||
if (process.platform === 'win32') app.setAppUserModelId(app.getName())
|
||||
|
||||
// 创建主窗口
|
||||
this.createWindow()
|
||||
// 检查更新逻辑
|
||||
checkUpdate(this.mainWindow)
|
||||
// 引入主进程ipcMain
|
||||
mainIpcMain(this.mainWindow)
|
||||
|
||||
// 开发环境下 F12 打开控制台
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
// 在 macOS 上,当单击 Dock 图标且没有其他窗口时,通常会重新创建窗口
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
this.createWindow()
|
||||
return
|
||||
}
|
||||
|
||||
if (platform.isMacOS) {
|
||||
this.mainWindow?.show()
|
||||
}
|
||||
})
|
||||
|
||||
// 监听渲染进程崩溃
|
||||
app.on('render-process-gone', (e, w, d) => {
|
||||
if (d.reason == 'crashed') {
|
||||
w.reload()
|
||||
}
|
||||
fs.appendFile(`./error-log-${+new Date()}.txt`, `${new Date()}渲染进程被杀死${d.reason}\n`)
|
||||
})
|
||||
|
||||
// 自定义协议
|
||||
app.on('open-url', (_, url) => {
|
||||
console.log('Received custom protocol URL:', url)
|
||||
})
|
||||
|
||||
// 当所有窗口都关闭时退出应用,macOS 除外
|
||||
app.on('window-all-closed', () => {
|
||||
if (!platform.isMacOS) {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
// 只有显式调用quit才退出系统,区分MAC系统程序坞退出和点击X隐藏
|
||||
app.on('before-quit', () => {
|
||||
// @ts-ignore
|
||||
app.isQuiting = true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 主窗口事件
|
||||
mainWindowEvents() {
|
||||
if (!this.mainWindow) {
|
||||
return
|
||||
}
|
||||
this.mainWindow.webContents.on('did-finish-load', () => {
|
||||
setTimeout(() => {
|
||||
this.mainWindow && this.mainWindow.webContents.send('app-started')
|
||||
}, 500)
|
||||
})
|
||||
|
||||
this.mainWindow.on('maximize', () => {
|
||||
this.mainWindow?.webContents.send('windowState', true)
|
||||
})
|
||||
|
||||
this.mainWindow.on('unmaximize', () => {
|
||||
this.mainWindow?.webContents.send('windowState', false)
|
||||
})
|
||||
|
||||
// 窗口关闭
|
||||
this.mainWindow.on('close', (event) => {
|
||||
event.preventDefault()
|
||||
// @ts-ignore
|
||||
if (!app.isQuiting) {
|
||||
this.mainWindow?.hide()
|
||||
} else {
|
||||
app.exit()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 捕获未捕获的异常
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('Uncaught Exception:', error)
|
||||
})
|
||||
|
||||
new MainProcess()
|
||||
110
electron/main/ipcMain.ts
Normal file
110
electron/main/ipcMain.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { ipcMain, app, dialog, clipboard, shell } from 'electron'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import * as fs from 'fs/promises'
|
||||
|
||||
const mainIpcMain = (win) => {
|
||||
// ==================== 窗口操作 ====================
|
||||
ipcMain.on('window-min', (ev) => {
|
||||
ev.preventDefault()
|
||||
win.minimize()
|
||||
})
|
||||
|
||||
ipcMain.on('window-maxOrRestore', (ev) => {
|
||||
const winSizeState = win.isMaximized()
|
||||
winSizeState ? win.restore() : win.maximize()
|
||||
ev.reply('windowState', win.isMaximized())
|
||||
})
|
||||
|
||||
ipcMain.on('window-restore', () => {
|
||||
win.restore()
|
||||
})
|
||||
|
||||
ipcMain.on('window-hide', () => {
|
||||
win.hide()
|
||||
})
|
||||
|
||||
ipcMain.on('window-close', () => {
|
||||
win.close()
|
||||
// @ts-ignore
|
||||
app.isQuitting = true
|
||||
app.quit()
|
||||
})
|
||||
|
||||
ipcMain.on('window-resize', (_, data) => {
|
||||
if (data.resize) {
|
||||
win.setResizable(true)
|
||||
} else {
|
||||
win.setSize(1180, 720)
|
||||
win.setResizable(false)
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('open-devtools', () => {
|
||||
win.webContents.openDevTools()
|
||||
})
|
||||
|
||||
// ==================== 更新检查 ====================
|
||||
ipcMain.on('check-update', () => {
|
||||
autoUpdater.checkForUpdates()
|
||||
})
|
||||
|
||||
// ==================== 通用工具 ====================
|
||||
ipcMain.handle('show-message', (event, args) => {
|
||||
event.sender.send('show-message', args)
|
||||
})
|
||||
|
||||
// 复制到剪贴板
|
||||
ipcMain.handle('copyData', async (_, data) => {
|
||||
try {
|
||||
clipboard.writeText(data)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('复制操作出错:', error)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// ==================== 文件系统操作 ====================
|
||||
// 选择文件夹
|
||||
ipcMain.handle('selectDir', async (_, defaultPath = '') => {
|
||||
try {
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog({
|
||||
title: '选择目录',
|
||||
defaultPath: defaultPath || app.getPath('documents'),
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
buttonLabel: '选择文件夹',
|
||||
})
|
||||
if (!canceled) {
|
||||
return filePaths[0]
|
||||
}
|
||||
return null
|
||||
} catch (err) {
|
||||
console.error('选择文件夹时发生错误:', err)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
|
||||
// 检查文件是否存在
|
||||
ipcMain.handle('checkFileExist', async (_, filePath) => {
|
||||
try {
|
||||
await fs.access(filePath)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// 在文件管理器中打开
|
||||
ipcMain.handle('openInFolder', async (_, path) => {
|
||||
try {
|
||||
await fs.access(path)
|
||||
await shell.showItemInFolder(path)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('打开目录时出错:', error)
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default mainIpcMain
|
||||
105
electron/main/update.ts
Normal file
105
electron/main/update.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { dialog, app } from 'electron'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { platform } from '@electron-toolkit/utils'
|
||||
|
||||
let isFirstShow = true
|
||||
const checkUpdate = (win) => {
|
||||
autoUpdater.autoDownload = false // 自动下载
|
||||
autoUpdater.autoInstallOnAppQuit = true // 应用退出后自动安装
|
||||
|
||||
// 绕过开发模式更新检测,模拟线上更新(Skip checkForUpdates because application is not packed and dev update config is not forced)
|
||||
// Object.defineProperty(app, 'isPackaged', {
|
||||
// get() {
|
||||
// return true
|
||||
// },
|
||||
// })
|
||||
|
||||
let showUpdateMessageBox = false
|
||||
autoUpdater.on('update-available', (info) => {
|
||||
// win.webContents.send('show-message', 'electron:发现新版本')
|
||||
if (showUpdateMessageBox) return
|
||||
showUpdateMessageBox = true
|
||||
dialog
|
||||
.showMessageBox({
|
||||
title: '发现新版本 v' + info.version,
|
||||
message: '发现新版本 v' + info.version,
|
||||
detail: '是否立即下载并安装新版本?',
|
||||
buttons: ['立即下载', '取消'],
|
||||
defaultId: 1,
|
||||
cancelId: 2,
|
||||
type: 'question',
|
||||
noLink: true,
|
||||
})
|
||||
.then((result) => {
|
||||
showUpdateMessageBox = false
|
||||
if (result.response === 0) {
|
||||
autoUpdater
|
||||
.downloadUpdate()
|
||||
.then(() => {
|
||||
console.log('wait for post download operation')
|
||||
})
|
||||
.catch((downloadError) => {
|
||||
dialog.showErrorBox('客户端下载失败', `err:${downloadError}`)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 监听下载进度事件
|
||||
autoUpdater.on('download-progress', (progressObj) => {
|
||||
console.log(`更新下载进度: ${progressObj.percent}%`)
|
||||
win.webContents.send('update-download-progress', progressObj.percent)
|
||||
})
|
||||
|
||||
// 下载完成
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
dialog
|
||||
.showMessageBox({
|
||||
title: '下载完成',
|
||||
message: '新版本已准备就绪,是否现在安装?',
|
||||
buttons: ['安装', platform.isMacOS ? '之后提醒' : '稍后(应用退出后自动安装)'],
|
||||
defaultId: 1,
|
||||
cancelId: 2,
|
||||
type: 'question',
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.response === 0) {
|
||||
win.webContents.send('begin-install')
|
||||
// @ts-ignore
|
||||
app.isQuiting = true
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
autoUpdater.quitAndInstall()
|
||||
})
|
||||
}, 100)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 不需要更新
|
||||
autoUpdater.on('update-not-available', (info) => {
|
||||
// 客户端打开会默认弹一次,用isFirstShow来控制不弹
|
||||
if (isFirstShow) {
|
||||
isFirstShow = false
|
||||
} else {
|
||||
win.webContents.send('show-message', {
|
||||
type: 'success',
|
||||
message: '已是最新版本',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 错误处理
|
||||
autoUpdater.on('error', (err, ev) => {
|
||||
// 更新出错,其中一步错误都会emit
|
||||
console.log('error事件:', err, ev)
|
||||
dialog.showErrorBox('遇到错误', `err:${err}, ev:${ev}`)
|
||||
})
|
||||
|
||||
// 等待 3 秒再检查更新,确保窗口准备完成,用户进入系统
|
||||
setTimeout(() => {
|
||||
autoUpdater.checkForUpdatesAndNotify().catch()
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
export { checkUpdate }
|
||||
Reference in New Issue
Block a user