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