9 changed files with 384 additions and 84 deletions
@ -0,0 +1,121 @@ |
|||||
|
import { autoUpdater } from 'electron-updater' |
||||
|
import { ipcMain } from 'electron' |
||||
|
import { is } from '@electron-toolkit/utils' |
||||
|
import log from 'electron-log' |
||||
|
import { IPC_EVENT } from '../renderer/src/common/ipcEvents' |
||||
|
|
||||
|
class AutoUpdaterManager { |
||||
|
constructor() { |
||||
|
this.mainWindow = null |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 初始化自动更新管理器 |
||||
|
* @param {BrowserWindow} mainWindow - 主窗口实例 |
||||
|
*/ |
||||
|
initialize(mainWindow) { |
||||
|
this.mainWindow = mainWindow |
||||
|
|
||||
|
// 只在生产环境启用自动更新
|
||||
|
if (!is.dev) { |
||||
|
// 禁用自动下载,让用户手动选择
|
||||
|
autoUpdater.autoDownload = false |
||||
|
|
||||
|
this.setupUpdateListeners() |
||||
|
this.setupIpcHandlers() |
||||
|
this.checkForUpdates() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查更新 |
||||
|
*/ |
||||
|
checkForUpdates() { |
||||
|
try { |
||||
|
log.info('开始检查更新') |
||||
|
autoUpdater.checkForUpdates() |
||||
|
} catch (e) { |
||||
|
log.error('自动更新失败', e) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置自动更新事件监听器 |
||||
|
*/ |
||||
|
setupUpdateListeners() { |
||||
|
// 发现新版本
|
||||
|
autoUpdater.on('update-available', (info) => { |
||||
|
log.info('发现新版本,通知渲染进程') |
||||
|
this.mainWindow.webContents.send(IPC_EVENT.UPDATE_AVAILABLE, info) |
||||
|
}) |
||||
|
|
||||
|
// 没有更新
|
||||
|
autoUpdater.on('update-not-available', () => { |
||||
|
log.info('当前为最新版本') |
||||
|
this.mainWindow.webContents.send(IPC_EVENT.UPDATE_NOT_AVAILABLE) |
||||
|
}) |
||||
|
|
||||
|
// 下载进度
|
||||
|
autoUpdater.on('download-progress', (progressObj) => { |
||||
|
log.info(`下载进度: ${Math.round(progressObj.percent)}%`) |
||||
|
this.mainWindow.webContents.send(IPC_EVENT.DOWNLOAD_PROGRESS, progressObj) |
||||
|
}) |
||||
|
|
||||
|
// 更新出错
|
||||
|
autoUpdater.on('error', (err) => { |
||||
|
log.error('更新出错', err) |
||||
|
this.mainWindow.webContents.send(IPC_EVENT.UPDATE_ERROR, err.message) |
||||
|
}) |
||||
|
|
||||
|
// 下载完成
|
||||
|
autoUpdater.on('update-downloaded', () => { |
||||
|
log.info('新版本下载完成,通知渲染进程') |
||||
|
this.mainWindow.webContents.send(IPC_EVENT.UPDATE_DOWNLOADED) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置IPC处理程序 |
||||
|
*/ |
||||
|
setupIpcHandlers() { |
||||
|
// 监听渲染进程的更新下载请求
|
||||
|
ipcMain.handle(IPC_EVENT.UPDATE_DOWNLOAD, () => { |
||||
|
log.info('渲染进程请求开始下载更新') |
||||
|
autoUpdater.downloadUpdate() |
||||
|
}) |
||||
|
|
||||
|
// 监听渲染进程的更新安装请求
|
||||
|
ipcMain.handle(IPC_EVENT.UPDATE_INSTALL, () => { |
||||
|
log.info('渲染进程请求安装更新') |
||||
|
autoUpdater.quitAndInstall() |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 手动触发检查更新 |
||||
|
*/ |
||||
|
manualCheckForUpdates() { |
||||
|
if (!is.dev) { |
||||
|
this.checkForUpdates() |
||||
|
} else { |
||||
|
log.info('开发环境,跳过更新检查') |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 清理资源 |
||||
|
*/ |
||||
|
cleanup() { |
||||
|
// 移除所有监听器
|
||||
|
autoUpdater.removeAllListeners() |
||||
|
|
||||
|
// 移除IPC处理程序
|
||||
|
ipcMain.removeHandler(IPC_EVENT.UPDATE_DOWNLOAD) |
||||
|
ipcMain.removeHandler(IPC_EVENT.UPDATE_INSTALL) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 创建单例实例
|
||||
|
const autoUpdaterManager = new AutoUpdaterManager() |
||||
|
|
||||
|
export default autoUpdaterManager |
@ -0,0 +1,189 @@ |
|||||
|
import { useEffect, useState } from 'react' |
||||
|
import { Modal, Progress, Button, Space, Typography, notification } from 'antd' |
||||
|
import { DownloadOutlined, ReloadOutlined, CloseOutlined } from '@ant-design/icons' |
||||
|
import { IPC_EVENT } from '../../common/ipcEvents' |
||||
|
import styles from './AutoUpdater.module.css' |
||||
|
|
||||
|
const { Title, Text } = Typography |
||||
|
|
||||
|
function AutoUpdater() { |
||||
|
const [updateInfo, setUpdateInfo] = useState(null) |
||||
|
const [isModalVisible, setIsModalVisible] = useState(false) |
||||
|
const [updateStatus, setUpdateStatus] = useState('') |
||||
|
const [downloadProgress, setDownloadProgress] = useState(0) |
||||
|
const [isDownloading, setIsDownloading] = useState(false) |
||||
|
const [isDownloaded, setIsDownloaded] = useState(false) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
// 监听发现新版本 |
||||
|
const handleUpdateAvailable = (event, info) => { |
||||
|
console.log('收到update-available事件:', info) |
||||
|
setUpdateInfo(info) |
||||
|
setUpdateStatus('发现新版本') |
||||
|
setIsModalVisible(true) |
||||
|
setIsDownloading(false) |
||||
|
setIsDownloaded(false) |
||||
|
setDownloadProgress(0) |
||||
|
} |
||||
|
|
||||
|
// 监听下载进度 |
||||
|
const handleDownloadProgress = (event, progressObj) => { |
||||
|
console.log('下载进度:', progressObj) |
||||
|
setDownloadProgress(Math.round(progressObj.percent)) |
||||
|
setUpdateStatus(`正在下载新版本... ${Math.round(progressObj.percent)}%`) |
||||
|
} |
||||
|
|
||||
|
// 监听下载完成 |
||||
|
const handleUpdateDownloaded = () => { |
||||
|
console.log('下载完成') |
||||
|
setIsDownloading(false) |
||||
|
setIsDownloaded(true) |
||||
|
setUpdateStatus('新版本下载完成,准备安装') |
||||
|
notification.success({ |
||||
|
message: '更新下载完成', |
||||
|
description: '新版本已下载完成,点击"立即安装"重启应用以完成更新', |
||||
|
duration: 5 |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// 监听更新错误 |
||||
|
const handleUpdateError = (event, error) => { |
||||
|
console.error('更新错误:', error) |
||||
|
setIsDownloading(false) |
||||
|
setUpdateStatus(`更新失败: ${error}`) |
||||
|
notification.error({ |
||||
|
message: '更新失败', |
||||
|
description: error, |
||||
|
duration: 5 |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// 注册监听器 |
||||
|
window.electron.ipcRenderer.on(IPC_EVENT.UPDATE_AVAILABLE, handleUpdateAvailable) |
||||
|
window.electron.ipcRenderer.on(IPC_EVENT.DOWNLOAD_PROGRESS, handleDownloadProgress) |
||||
|
window.electron.ipcRenderer.on(IPC_EVENT.UPDATE_DOWNLOADED, handleUpdateDownloaded) |
||||
|
window.electron.ipcRenderer.on(IPC_EVENT.UPDATE_ERROR, handleUpdateError) |
||||
|
|
||||
|
// 清理监听器 |
||||
|
return () => { |
||||
|
window.electron.ipcRenderer.removeListener(IPC_EVENT.UPDATE_AVAILABLE, handleUpdateAvailable) |
||||
|
window.electron.ipcRenderer.removeListener( |
||||
|
IPC_EVENT.DOWNLOAD_PROGRESS, |
||||
|
handleDownloadProgress |
||||
|
) |
||||
|
window.electron.ipcRenderer.removeListener( |
||||
|
IPC_EVENT.UPDATE_DOWNLOADED, |
||||
|
handleUpdateDownloaded |
||||
|
) |
||||
|
window.electron.ipcRenderer.removeListener(IPC_EVENT.UPDATE_ERROR, handleUpdateError) |
||||
|
} |
||||
|
}, []) |
||||
|
|
||||
|
// 开始下载 |
||||
|
const handleDownload = async () => { |
||||
|
setIsDownloading(true) |
||||
|
setUpdateStatus('准备下载新版本...') |
||||
|
setDownloadProgress(0) |
||||
|
try { |
||||
|
await window.electron.ipcRenderer.invoke(IPC_EVENT.UPDATE_DOWNLOAD) |
||||
|
} catch (error) { |
||||
|
console.error('请求下载失败:', error) |
||||
|
setIsDownloading(false) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 安装更新 |
||||
|
const handleInstall = async () => { |
||||
|
try { |
||||
|
await window.electron.ipcRenderer.invoke(IPC_EVENT.UPDATE_INSTALL) |
||||
|
} catch (error) { |
||||
|
console.error('安装更新失败:', error) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 取消更新 |
||||
|
const handleCancel = () => { |
||||
|
setIsModalVisible(false) |
||||
|
setUpdateInfo(null) |
||||
|
setIsDownloading(false) |
||||
|
setIsDownloaded(false) |
||||
|
setDownloadProgress(0) |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<Modal |
||||
|
title={ |
||||
|
<Space> |
||||
|
<DownloadOutlined /> |
||||
|
<span>软件更新</span> |
||||
|
</Space> |
||||
|
} |
||||
|
open={isModalVisible} |
||||
|
onCancel={handleCancel} |
||||
|
footer={null} |
||||
|
width={480} |
||||
|
maskClosable={false} |
||||
|
> |
||||
|
<div className={styles.modalContent}> |
||||
|
{updateInfo && ( |
||||
|
<div className={styles.versionInfo}> |
||||
|
<Title level={5} className={styles.versionTitle}> |
||||
|
发现新版本:v{updateInfo.version} |
||||
|
</Title> |
||||
|
{updateInfo.releaseNotes && ( |
||||
|
<div className={styles.releaseNotesSection}> |
||||
|
<Text type="secondary">更新说明:</Text> |
||||
|
<div className={styles.releaseNotesContainer}> |
||||
|
<Text className={styles.releaseNotesText}>{updateInfo.releaseNotes}</Text> |
||||
|
</div> |
||||
|
</div> |
||||
|
)} |
||||
|
</div> |
||||
|
)} |
||||
|
|
||||
|
<div className={styles.statusSection}> |
||||
|
<Text>{updateStatus}</Text> |
||||
|
</div> |
||||
|
|
||||
|
{isDownloading && ( |
||||
|
<div className={styles.progressSection}> |
||||
|
<Progress |
||||
|
percent={downloadProgress} |
||||
|
status="active" |
||||
|
strokeColor={{ |
||||
|
'0%': '#108ee9', |
||||
|
'100%': '#87d068' |
||||
|
}} |
||||
|
format={(percent) => `${percent}%`} |
||||
|
/> |
||||
|
</div> |
||||
|
)} |
||||
|
|
||||
|
<div className={styles.actionButtons}> |
||||
|
<Space> |
||||
|
<Button icon={<CloseOutlined />} onClick={handleCancel}> |
||||
|
{isDownloaded ? '稍后安装' : '取消'} |
||||
|
</Button> |
||||
|
{!isDownloaded && !isDownloading && ( |
||||
|
<Button type="primary" icon={<DownloadOutlined />} onClick={handleDownload}> |
||||
|
立即下载 |
||||
|
</Button> |
||||
|
)} |
||||
|
{isDownloaded && ( |
||||
|
<Button |
||||
|
type="primary" |
||||
|
icon={<ReloadOutlined />} |
||||
|
onClick={handleInstall} |
||||
|
className={styles.installButton} |
||||
|
> |
||||
|
立即安装 |
||||
|
</Button> |
||||
|
)} |
||||
|
</Space> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Modal> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export default AutoUpdater |
@ -0,0 +1,47 @@ |
|||||
|
.modalContent { |
||||
|
padding: 16px 0; |
||||
|
} |
||||
|
|
||||
|
.versionInfo { |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.versionTitle { |
||||
|
margin: 0 !important; |
||||
|
margin-bottom: 8px !important; |
||||
|
} |
||||
|
|
||||
|
.releaseNotesSection { |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.releaseNotesContainer { |
||||
|
background: #f6f8fa; |
||||
|
padding: 12px; |
||||
|
border-radius: 6px; |
||||
|
margin-top: 8px; |
||||
|
max-height: 120px; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
.releaseNotesText { |
||||
|
font-size: 12px; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
.statusSection { |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.progressSection { |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.actionButtons { |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
.installButton { |
||||
|
background-color: #52c41a !important; |
||||
|
border-color: #52c41a !important; |
||||
|
} |
Loading…
Reference in new issue