mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
新的提交
This commit is contained in:
703
electron/main.ts
Normal file
703
electron/main.ts
Normal file
@@ -0,0 +1,703 @@
|
||||
import { app, BrowserWindow, ipcMain, nativeTheme } from 'electron'
|
||||
import { Worker } from 'worker_threads'
|
||||
import { join } from 'path'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { readFile, writeFile, mkdir } from 'fs/promises'
|
||||
import { existsSync } from 'fs'
|
||||
import { ConfigService } from './services/config'
|
||||
import { dbPathService } from './services/dbPathService'
|
||||
import { wcdbService } from './services/wcdbService'
|
||||
import { chatService } from './services/chatService'
|
||||
import { imageDecryptService } from './services/imageDecryptService'
|
||||
import { imagePreloadService } from './services/imagePreloadService'
|
||||
import { analyticsService } from './services/analyticsService'
|
||||
import { groupAnalyticsService } from './services/groupAnalyticsService'
|
||||
import { annualReportService } from './services/annualReportService'
|
||||
import { exportService, ExportOptions } from './services/exportService'
|
||||
import { KeyService } from './services/keyService'
|
||||
|
||||
// 配置自动更新
|
||||
autoUpdater.autoDownload = false
|
||||
autoUpdater.autoInstallOnAppQuit = true
|
||||
autoUpdater.disableDifferentialDownload = true // 禁用差分更新,强制全量下载
|
||||
const AUTO_UPDATE_ENABLED =
|
||||
process.env.AUTO_UPDATE_ENABLED === 'true' ||
|
||||
process.env.AUTO_UPDATE_ENABLED === '1' ||
|
||||
(process.env.AUTO_UPDATE_ENABLED == null && !process.env.VITE_DEV_SERVER_URL)
|
||||
|
||||
// 单例服务
|
||||
let configService: ConfigService | null = null
|
||||
|
||||
// 协议窗口实例
|
||||
let agreementWindow: BrowserWindow | null = null
|
||||
let onboardingWindow: BrowserWindow | null = null
|
||||
const keyService = new KeyService()
|
||||
|
||||
let mainWindowReady = false
|
||||
let shouldShowMain = true
|
||||
|
||||
function createWindow(options: { autoShow?: boolean } = {}) {
|
||||
// 获取图标路径 - 打包后在 resources 目录
|
||||
const { autoShow = true } = options
|
||||
const isDev = !!process.env.VITE_DEV_SERVER_URL
|
||||
const iconPath = isDev
|
||||
? join(__dirname, '../public/icon.ico')
|
||||
: join(process.resourcesPath, 'icon.ico')
|
||||
|
||||
const win = new BrowserWindow({
|
||||
width: 1400,
|
||||
height: 900,
|
||||
minWidth: 1000,
|
||||
minHeight: 700,
|
||||
icon: iconPath,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, 'preload.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false
|
||||
},
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: {
|
||||
color: '#00000000',
|
||||
symbolColor: '#1a1a1a',
|
||||
height: 40
|
||||
},
|
||||
show: false
|
||||
})
|
||||
|
||||
// 窗口准备好后显示
|
||||
win.once('ready-to-show', () => {
|
||||
mainWindowReady = true
|
||||
if (autoShow || shouldShowMain) {
|
||||
win.show()
|
||||
}
|
||||
})
|
||||
|
||||
// 开发环境加载 vite 服务器
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
win.loadURL(process.env.VITE_DEV_SERVER_URL)
|
||||
|
||||
// 开发环境下按 F12 或 Ctrl+Shift+I 打开开发者工具
|
||||
win.webContents.on('before-input-event', (event, input) => {
|
||||
if (input.key === 'F12' || (input.control && input.shift && input.key === 'I')) {
|
||||
if (win.webContents.isDevToolsOpened()) {
|
||||
win.webContents.closeDevTools()
|
||||
} else {
|
||||
win.webContents.openDevTools()
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
win.loadFile(join(__dirname, '../dist/index.html'))
|
||||
}
|
||||
|
||||
return win
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建用户协议窗口
|
||||
*/
|
||||
function createAgreementWindow() {
|
||||
// 如果已存在,聚焦
|
||||
if (agreementWindow && !agreementWindow.isDestroyed()) {
|
||||
agreementWindow.focus()
|
||||
return agreementWindow
|
||||
}
|
||||
|
||||
const isDev = !!process.env.VITE_DEV_SERVER_URL
|
||||
const iconPath = isDev
|
||||
? join(__dirname, '../public/icon.ico')
|
||||
: join(process.resourcesPath, 'icon.ico')
|
||||
|
||||
const isDark = nativeTheme.shouldUseDarkColors
|
||||
|
||||
agreementWindow = new BrowserWindow({
|
||||
width: 700,
|
||||
height: 600,
|
||||
minWidth: 500,
|
||||
minHeight: 400,
|
||||
icon: iconPath,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, 'preload.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false
|
||||
},
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: {
|
||||
color: '#00000000',
|
||||
symbolColor: isDark ? '#FFFFFF' : '#333333',
|
||||
height: 32
|
||||
},
|
||||
show: false,
|
||||
backgroundColor: isDark ? '#1A1A1A' : '#FFFFFF'
|
||||
})
|
||||
|
||||
agreementWindow.once('ready-to-show', () => {
|
||||
agreementWindow?.show()
|
||||
})
|
||||
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
agreementWindow.loadURL(`${process.env.VITE_DEV_SERVER_URL}#/agreement-window`)
|
||||
} else {
|
||||
agreementWindow.loadFile(join(__dirname, '../dist/index.html'), { hash: '/agreement-window' })
|
||||
}
|
||||
|
||||
agreementWindow.on('closed', () => {
|
||||
agreementWindow = null
|
||||
})
|
||||
|
||||
return agreementWindow
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建首次引导窗口
|
||||
*/
|
||||
function createOnboardingWindow() {
|
||||
if (onboardingWindow && !onboardingWindow.isDestroyed()) {
|
||||
onboardingWindow.focus()
|
||||
return onboardingWindow
|
||||
}
|
||||
|
||||
const isDev = !!process.env.VITE_DEV_SERVER_URL
|
||||
const iconPath = isDev
|
||||
? join(__dirname, '../public/icon.ico')
|
||||
: join(process.resourcesPath, 'icon.ico')
|
||||
|
||||
onboardingWindow = new BrowserWindow({
|
||||
width: 1100,
|
||||
height: 720,
|
||||
minWidth: 900,
|
||||
minHeight: 600,
|
||||
frame: false,
|
||||
transparent: true,
|
||||
backgroundColor: '#00000000',
|
||||
hasShadow: false,
|
||||
icon: iconPath,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, 'preload.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false
|
||||
},
|
||||
show: false
|
||||
})
|
||||
|
||||
onboardingWindow.once('ready-to-show', () => {
|
||||
onboardingWindow?.show()
|
||||
})
|
||||
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
onboardingWindow.loadURL(`${process.env.VITE_DEV_SERVER_URL}#/onboarding-window`)
|
||||
} else {
|
||||
onboardingWindow.loadFile(join(__dirname, '../dist/index.html'), { hash: '/onboarding-window' })
|
||||
}
|
||||
|
||||
onboardingWindow.on('closed', () => {
|
||||
onboardingWindow = null
|
||||
})
|
||||
|
||||
return onboardingWindow
|
||||
}
|
||||
|
||||
function showMainWindow() {
|
||||
shouldShowMain = true
|
||||
if (mainWindowReady) {
|
||||
mainWindow?.show()
|
||||
}
|
||||
}
|
||||
|
||||
// 注册 IPC 处理器
|
||||
function registerIpcHandlers() {
|
||||
// 配置相关
|
||||
ipcMain.handle('config:get', async (_, key: string) => {
|
||||
return configService?.get(key as any)
|
||||
})
|
||||
|
||||
ipcMain.handle('config:set', async (_, key: string, value: any) => {
|
||||
return configService?.set(key as any, value)
|
||||
})
|
||||
|
||||
ipcMain.handle('config:clear', async () => {
|
||||
configService?.clear()
|
||||
return true
|
||||
})
|
||||
|
||||
// 文件对话框
|
||||
ipcMain.handle('dialog:openFile', async (_, options) => {
|
||||
const { dialog } = await import('electron')
|
||||
return dialog.showOpenDialog(options)
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:openDirectory', async (_, options) => {
|
||||
const { dialog } = await import('electron')
|
||||
return dialog.showOpenDialog({
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
...options
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.handle('dialog:saveFile', async (_, options) => {
|
||||
const { dialog } = await import('electron')
|
||||
return dialog.showSaveDialog(options)
|
||||
})
|
||||
|
||||
ipcMain.handle('shell:openPath', async (_, path: string) => {
|
||||
const { shell } = await import('electron')
|
||||
return shell.openPath(path)
|
||||
})
|
||||
|
||||
ipcMain.handle('shell:openExternal', async (_, url: string) => {
|
||||
const { shell } = await import('electron')
|
||||
return shell.openExternal(url)
|
||||
})
|
||||
|
||||
ipcMain.handle('app:getDownloadsPath', async () => {
|
||||
return app.getPath('downloads')
|
||||
})
|
||||
|
||||
ipcMain.handle('app:getVersion', async () => {
|
||||
return app.getVersion()
|
||||
})
|
||||
|
||||
ipcMain.handle('log:getPath', async () => {
|
||||
return join(app.getPath('userData'), 'logs', 'wcdb.log')
|
||||
})
|
||||
|
||||
ipcMain.handle('log:read', async () => {
|
||||
try {
|
||||
const logPath = join(app.getPath('userData'), 'logs', 'wcdb.log')
|
||||
const content = await readFile(logPath, 'utf8')
|
||||
return { success: true, content }
|
||||
} catch (e) {
|
||||
return { success: false, error: String(e) }
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('app:checkForUpdates', async () => {
|
||||
if (!AUTO_UPDATE_ENABLED) {
|
||||
return { hasUpdate: false }
|
||||
}
|
||||
try {
|
||||
const result = await autoUpdater.checkForUpdates()
|
||||
if (result && result.updateInfo) {
|
||||
const currentVersion = app.getVersion()
|
||||
const latestVersion = result.updateInfo.version
|
||||
if (latestVersion !== currentVersion) {
|
||||
return {
|
||||
hasUpdate: true,
|
||||
version: latestVersion,
|
||||
releaseNotes: result.updateInfo.releaseNotes as string || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
return { hasUpdate: false }
|
||||
} catch (error) {
|
||||
console.error('检查更新失败:', error)
|
||||
return { hasUpdate: false }
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('app:downloadAndInstall', async (event) => {
|
||||
if (!AUTO_UPDATE_ENABLED) {
|
||||
throw new Error('自动更新已暂时禁用')
|
||||
}
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
|
||||
// 监听下载进度
|
||||
autoUpdater.on('download-progress', (progress) => {
|
||||
win?.webContents.send('app:downloadProgress', progress.percent)
|
||||
})
|
||||
|
||||
// 下载完成后自动安装
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
autoUpdater.quitAndInstall(false, true)
|
||||
})
|
||||
|
||||
try {
|
||||
await autoUpdater.downloadUpdate()
|
||||
} catch (error) {
|
||||
console.error('下载更新失败:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
// 窗口控制
|
||||
ipcMain.on('window:minimize', (event) => {
|
||||
BrowserWindow.fromWebContents(event.sender)?.minimize()
|
||||
})
|
||||
|
||||
ipcMain.on('window:maximize', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
if (win?.isMaximized()) {
|
||||
win.unmaximize()
|
||||
} else {
|
||||
win?.maximize()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('window:close', (event) => {
|
||||
BrowserWindow.fromWebContents(event.sender)?.close()
|
||||
})
|
||||
|
||||
// 更新窗口控件主题色
|
||||
ipcMain.on('window:setTitleBarOverlay', (event, options: { symbolColor: string }) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
if (win) {
|
||||
try {
|
||||
win.setTitleBarOverlay({
|
||||
color: '#00000000',
|
||||
symbolColor: options.symbolColor,
|
||||
height: 40
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn('TitleBarOverlay not enabled for this window:', error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 数据库路径相关
|
||||
ipcMain.handle('dbpath:autoDetect', async () => {
|
||||
return dbPathService.autoDetect()
|
||||
})
|
||||
|
||||
ipcMain.handle('dbpath:scanWxids', async (_, rootPath: string) => {
|
||||
return dbPathService.scanWxids(rootPath)
|
||||
})
|
||||
|
||||
ipcMain.handle('dbpath:getDefault', async () => {
|
||||
return dbPathService.getDefaultPath()
|
||||
})
|
||||
|
||||
// WCDB 数据库相关
|
||||
ipcMain.handle('wcdb:testConnection', async (_, dbPath: string, hexKey: string, wxid: string) => {
|
||||
return wcdbService.testConnection(dbPath, hexKey, wxid)
|
||||
})
|
||||
|
||||
ipcMain.handle('wcdb:open', async (_, dbPath: string, hexKey: string, wxid: string) => {
|
||||
return wcdbService.open(dbPath, hexKey, wxid)
|
||||
})
|
||||
|
||||
ipcMain.handle('wcdb:close', async () => {
|
||||
wcdbService.close()
|
||||
return true
|
||||
})
|
||||
|
||||
// 聊天相关
|
||||
ipcMain.handle('chat:connect', async () => {
|
||||
return chatService.connect()
|
||||
})
|
||||
|
||||
ipcMain.handle('chat:getSessions', async () => {
|
||||
return chatService.getSessions()
|
||||
})
|
||||
|
||||
ipcMain.handle('chat:getMessages', async (_, sessionId: string, offset?: number, limit?: number) => {
|
||||
return chatService.getMessages(sessionId, offset, limit)
|
||||
})
|
||||
|
||||
ipcMain.handle('chat:getLatestMessages', async (_, sessionId: string, limit?: number) => {
|
||||
return chatService.getLatestMessages(sessionId, limit)
|
||||
})
|
||||
|
||||
ipcMain.handle('chat:getContact', async (_, username: string) => {
|
||||
return chatService.getContact(username)
|
||||
})
|
||||
|
||||
ipcMain.handle('chat:getContactAvatar', async (_, username: string) => {
|
||||
return chatService.getContactAvatar(username)
|
||||
})
|
||||
|
||||
ipcMain.handle('chat:getMyAvatarUrl', async () => {
|
||||
return chatService.getMyAvatarUrl()
|
||||
})
|
||||
|
||||
ipcMain.handle('chat:downloadEmoji', async (_, cdnUrl: string, md5?: string) => {
|
||||
return chatService.downloadEmoji(cdnUrl, md5)
|
||||
})
|
||||
|
||||
ipcMain.handle('chat:close', async () => {
|
||||
chatService.close()
|
||||
return true
|
||||
})
|
||||
|
||||
ipcMain.handle('chat:getSessionDetail', async (_, sessionId: string) => {
|
||||
return chatService.getSessionDetail(sessionId)
|
||||
})
|
||||
|
||||
ipcMain.handle('chat:getImageData', async (_, sessionId: string, msgId: string) => {
|
||||
return chatService.getImageData(sessionId, msgId)
|
||||
})
|
||||
|
||||
ipcMain.handle('chat:getVoiceData', async (_, sessionId: string, msgId: string) => {
|
||||
return chatService.getVoiceData(sessionId, msgId)
|
||||
})
|
||||
|
||||
ipcMain.handle('chat:getMessageById', async (_, sessionId: string, localId: number) => {
|
||||
return chatService.getMessageById(sessionId, localId)
|
||||
})
|
||||
|
||||
ipcMain.handle('image:decrypt', async (_, payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; force?: boolean }) => {
|
||||
return imageDecryptService.decryptImage(payload)
|
||||
})
|
||||
ipcMain.handle('image:resolveCache', async (_, payload: { sessionId?: string; imageMd5?: string; imageDatName?: string }) => {
|
||||
return imageDecryptService.resolveCachedImage(payload)
|
||||
})
|
||||
ipcMain.handle('image:preload', async (_, payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>) => {
|
||||
imagePreloadService.enqueue(payloads || [])
|
||||
return true
|
||||
})
|
||||
|
||||
// 导出相关
|
||||
ipcMain.handle('export:exportSessions', async (_, sessionIds: string[], outputDir: string, options: ExportOptions) => {
|
||||
return exportService.exportSessions(sessionIds, outputDir, options)
|
||||
})
|
||||
|
||||
ipcMain.handle('export:exportSession', async (_, sessionId: string, outputPath: string, options: ExportOptions) => {
|
||||
return exportService.exportSessionToChatLab(sessionId, outputPath, options)
|
||||
})
|
||||
|
||||
// 数据分析相关
|
||||
ipcMain.handle('analytics:getOverallStatistics', async () => {
|
||||
return analyticsService.getOverallStatistics()
|
||||
})
|
||||
|
||||
ipcMain.handle('analytics:getContactRankings', async (_, limit?: number) => {
|
||||
return analyticsService.getContactRankings(limit)
|
||||
})
|
||||
|
||||
ipcMain.handle('analytics:getTimeDistribution', async () => {
|
||||
return analyticsService.getTimeDistribution()
|
||||
})
|
||||
|
||||
// 群聊分析相关
|
||||
ipcMain.handle('groupAnalytics:getGroupChats', async () => {
|
||||
return groupAnalyticsService.getGroupChats()
|
||||
})
|
||||
|
||||
ipcMain.handle('groupAnalytics:getGroupMembers', async (_, chatroomId: string) => {
|
||||
return groupAnalyticsService.getGroupMembers(chatroomId)
|
||||
})
|
||||
|
||||
ipcMain.handle('groupAnalytics:getGroupMessageRanking', async (_, chatroomId: string, limit?: number, startTime?: number, endTime?: number) => {
|
||||
return groupAnalyticsService.getGroupMessageRanking(chatroomId, limit, startTime, endTime)
|
||||
})
|
||||
|
||||
ipcMain.handle('groupAnalytics:getGroupActiveHours', async (_, chatroomId: string, startTime?: number, endTime?: number) => {
|
||||
return groupAnalyticsService.getGroupActiveHours(chatroomId, startTime, endTime)
|
||||
})
|
||||
|
||||
ipcMain.handle('groupAnalytics:getGroupMediaStats', async (_, chatroomId: string, startTime?: number, endTime?: number) => {
|
||||
return groupAnalyticsService.getGroupMediaStats(chatroomId, startTime, endTime)
|
||||
})
|
||||
|
||||
// 打开协议窗口
|
||||
ipcMain.handle('window:openAgreementWindow', async () => {
|
||||
createAgreementWindow()
|
||||
return true
|
||||
})
|
||||
|
||||
// 完成引导,关闭引导窗口并显示主窗口
|
||||
ipcMain.handle('window:completeOnboarding', async () => {
|
||||
try {
|
||||
configService?.set('onboardingDone', true)
|
||||
} catch (e) {
|
||||
console.error('保存引导完成状态失败:', e)
|
||||
}
|
||||
|
||||
if (onboardingWindow && !onboardingWindow.isDestroyed()) {
|
||||
onboardingWindow.close()
|
||||
}
|
||||
showMainWindow()
|
||||
return true
|
||||
})
|
||||
|
||||
// 重新打开首次引导窗口,并隐藏主窗口
|
||||
ipcMain.handle('window:openOnboardingWindow', async () => {
|
||||
shouldShowMain = false
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.hide()
|
||||
}
|
||||
createOnboardingWindow()
|
||||
return true
|
||||
})
|
||||
|
||||
// 年度报告相关
|
||||
ipcMain.handle('annualReport:getAvailableYears', async () => {
|
||||
const cfg = configService || new ConfigService()
|
||||
configService = cfg
|
||||
return annualReportService.getAvailableYears({
|
||||
dbPath: cfg.get('dbPath'),
|
||||
decryptKey: cfg.get('decryptKey'),
|
||||
wxid: cfg.get('myWxid')
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.handle('annualReport:generateReport', async (_, year: number) => {
|
||||
const cfg = configService || new ConfigService()
|
||||
configService = cfg
|
||||
|
||||
const dbPath = cfg.get('dbPath')
|
||||
const decryptKey = cfg.get('decryptKey')
|
||||
const wxid = cfg.get('myWxid')
|
||||
const logEnabled = cfg.get('logEnabled')
|
||||
|
||||
const resourcesPath = app.isPackaged
|
||||
? join(process.resourcesPath, 'resources')
|
||||
: join(app.getAppPath(), 'resources')
|
||||
const userDataPath = app.getPath('userData')
|
||||
|
||||
const workerPath = join(__dirname, 'annualReportWorker.js')
|
||||
|
||||
return await new Promise((resolve) => {
|
||||
const worker = new Worker(workerPath, {
|
||||
workerData: { year, dbPath, decryptKey, myWxid: wxid, resourcesPath, userDataPath, logEnabled }
|
||||
})
|
||||
|
||||
const cleanup = () => {
|
||||
worker.removeAllListeners()
|
||||
}
|
||||
|
||||
worker.on('message', (msg: any) => {
|
||||
if (msg && msg.type === 'annualReport:progress') {
|
||||
for (const win of BrowserWindow.getAllWindows()) {
|
||||
if (!win.isDestroyed()) {
|
||||
win.webContents.send('annualReport:progress', msg.data)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if (msg && (msg.type === 'annualReport:result' || msg.type === 'done')) {
|
||||
cleanup()
|
||||
void worker.terminate()
|
||||
resolve(msg.data ?? msg.result)
|
||||
return
|
||||
}
|
||||
if (msg && (msg.type === 'annualReport:error' || msg.type === 'error')) {
|
||||
cleanup()
|
||||
void worker.terminate()
|
||||
resolve({ success: false, error: msg.error || '年度报告生成失败' })
|
||||
}
|
||||
})
|
||||
|
||||
worker.on('error', (err) => {
|
||||
cleanup()
|
||||
resolve({ success: false, error: String(err) })
|
||||
})
|
||||
|
||||
worker.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
cleanup()
|
||||
resolve({ success: false, error: `年度报告线程异常退出: ${code}` })
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.handle('annualReport:exportImages', async (_, payload: { baseDir: string; folderName: string; images: Array<{ name: string; dataUrl: string }> }) => {
|
||||
try {
|
||||
const { baseDir, folderName, images } = payload
|
||||
if (!baseDir || !folderName || !Array.isArray(images) || images.length === 0) {
|
||||
return { success: false, error: '导出参数无效' }
|
||||
}
|
||||
|
||||
let targetDir = join(baseDir, folderName)
|
||||
if (existsSync(targetDir)) {
|
||||
let idx = 2
|
||||
while (existsSync(`${targetDir}_${idx}`)) idx++
|
||||
targetDir = `${targetDir}_${idx}`
|
||||
}
|
||||
|
||||
await mkdir(targetDir, { recursive: true })
|
||||
|
||||
for (const img of images) {
|
||||
const dataUrl = img.dataUrl || ''
|
||||
const commaIndex = dataUrl.indexOf(',')
|
||||
if (commaIndex <= 0) continue
|
||||
const base64 = dataUrl.slice(commaIndex + 1)
|
||||
const buffer = Buffer.from(base64, 'base64')
|
||||
const filePath = join(targetDir, img.name)
|
||||
await writeFile(filePath, buffer)
|
||||
}
|
||||
|
||||
return { success: true, dir: targetDir }
|
||||
} catch (e) {
|
||||
return { success: false, error: String(e) }
|
||||
}
|
||||
})
|
||||
|
||||
// 密钥获取
|
||||
ipcMain.handle('key:autoGetDbKey', async (event) => {
|
||||
return keyService.autoGetDbKey(60_000, (message, level) => {
|
||||
event.sender.send('key:dbKeyStatus', { message, level })
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.handle('key:autoGetImageKey', async (event, manualDir?: string) => {
|
||||
return keyService.autoGetImageKey(manualDir, (message) => {
|
||||
event.sender.send('key:imageKeyStatus', { message })
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 主窗口引用
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
// 启动时自动检测更新
|
||||
function checkForUpdatesOnStartup() {
|
||||
if (!AUTO_UPDATE_ENABLED) return
|
||||
// 开发环境不检测更新
|
||||
if (process.env.VITE_DEV_SERVER_URL) return
|
||||
|
||||
// 延迟3秒检测,等待窗口完全加载
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const result = await autoUpdater.checkForUpdates()
|
||||
if (result && result.updateInfo) {
|
||||
const currentVersion = app.getVersion()
|
||||
const latestVersion = result.updateInfo.version
|
||||
if (latestVersion !== currentVersion && mainWindow) {
|
||||
// 通知渲染进程有新版本
|
||||
mainWindow.webContents.send('app:updateAvailable', {
|
||||
version: latestVersion,
|
||||
releaseNotes: result.updateInfo.releaseNotes || ''
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('启动时检查更新失败:', error)
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
configService = new ConfigService()
|
||||
const resourcesPath = app.isPackaged
|
||||
? join(process.resourcesPath, 'resources')
|
||||
: join(app.getAppPath(), 'resources')
|
||||
const userDataPath = app.getPath('userData')
|
||||
wcdbService.setPaths(resourcesPath, userDataPath)
|
||||
wcdbService.setLogEnabled(configService.get('logEnabled') === true)
|
||||
registerIpcHandlers()
|
||||
const onboardingDone = configService.get('onboardingDone')
|
||||
shouldShowMain = onboardingDone === true
|
||||
mainWindow = createWindow({ autoShow: shouldShowMain })
|
||||
|
||||
if (!onboardingDone) {
|
||||
createOnboardingWindow()
|
||||
}
|
||||
|
||||
// 启动时检测更新
|
||||
checkForUpdatesOnStartup()
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
mainWindow = createWindow()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user