From fbea6ce5d0afe948f8b819e6ad0e1305e43ab430 Mon Sep 17 00:00:00 2001 From: cles <208023732@qq.com> Date: Fri, 5 Sep 2025 15:35:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=95=B4=E5=90=88?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=AE=A1=E7=90=86=E5=99=A8=E5=92=8CUI?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron-builder.yml | 2 +- package-lock.json | 76 +------ package.json | 3 +- src/main/autoUpdaterManager.js | 121 +++++++++++ src/main/index.js | 17 +- src/renderer/src/App.jsx | 2 + src/renderer/src/common/ipcEvents.js | 11 +- .../components/AutoUpdater/AutoUpdater.jsx | 189 ++++++++++++++++++ .../AutoUpdater/AutoUpdater.module.css | 47 +++++ 9 files changed, 384 insertions(+), 84 deletions(-) create mode 100644 src/main/autoUpdaterManager.js create mode 100644 src/renderer/src/components/AutoUpdater/AutoUpdater.jsx create mode 100644 src/renderer/src/components/AutoUpdater/AutoUpdater.module.css diff --git a/electron-builder.yml b/electron-builder.yml index 129b3f8..09f4593 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -41,6 +41,6 @@ appImage: npmRebuild: false publish: provider: generic - url: https://gitee.com/bocinpity/flexometer-setup/releases/download/v${version} + url: https://gitee.com/bocinpity/flexometer-setup/releases/download/latest electronDownload: mirror: https://npmmirror.com/mirrors/electron/ diff --git a/package-lock.json b/package-lock.json index faa1a99..3636c7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,17 @@ { "name": "FlexometerSetup", - "version": "1.0.0", + "version": "1.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "FlexometerSetup", - "version": "1.0.0", + "version": "1.0.5", "hasInstallScript": true, "dependencies": { "@ant-design/icons": "^5.6.1", "@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/utils": "^4.0.0", - "ahooks": "^3.9.5", "antd": "^5.27.1", "chart.js": "^4.5.0", "electron-log": "^5.4.3", @@ -2460,12 +2459,6 @@ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "license": "MIT" }, - "node_modules/@types/js-cookie": { - "version": "3.0.6", - "resolved": "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-3.0.6.tgz", - "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", - "license": "MIT" - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2661,31 +2654,6 @@ "node": ">=8" } }, - "node_modules/ahooks": { - "version": "3.9.5", - "resolved": "https://registry.npmmirror.com/ahooks/-/ahooks-3.9.5.tgz", - "integrity": "sha512-TrjXie49Q8HuHKTa84Fm9A+famMDAG1+7a9S9Gq6RQ0h90Jgqmiq3CkObuRjWT/C4d6nRZCw35Y2k2fmybb5eA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0", - "@types/js-cookie": "^3.0.6", - "dayjs": "^1.9.1", - "intersection-observer": "^0.12.0", - "js-cookie": "^3.0.5", - "lodash": "^4.17.21", - "react-fast-compare": "^3.2.2", - "resize-observer-polyfill": "^1.5.1", - "screenfull": "^5.0.0", - "tslib": "^2.4.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", @@ -6299,12 +6267,6 @@ "node": ">= 0.4" } }, - "node_modules/intersection-observer": { - "version": "0.12.2", - "resolved": "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz", - "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==", - "license": "Apache-2.0" - }, "node_modules/ip-address": { "version": "10.0.1", "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.0.1.tgz", @@ -6840,15 +6802,6 @@ "node": ">=10" } }, - "node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7081,6 +7034,7 @@ "version": "4.17.21", "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, "license": "MIT" }, "node_modules/lodash.defaults": { @@ -8914,12 +8868,6 @@ "react": "^18.3.1" } }, - "node_modules/react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", - "license": "MIT" - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", @@ -9380,18 +9328,6 @@ "loose-envify": "^1.1.0" } }, - "node_modules/screenfull": { - "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/screenfull/-/screenfull-5.2.0.tgz", - "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/scroll-into-view-if-needed": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", @@ -10181,12 +10117,6 @@ "utf8-byte-length": "^1.0.1" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 618c58f..cde6f6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "FlexometerSetup", - "version": "1.0.1", + "version": "1.0.7", "description": "An Electron application with React", "main": "./out/main/index.js", "author": "cles", @@ -21,7 +21,6 @@ "@ant-design/icons": "^5.6.1", "@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/utils": "^4.0.0", - "ahooks": "^3.9.5", "antd": "^5.27.1", "chart.js": "^4.5.0", "electron-log": "^5.4.3", diff --git a/src/main/autoUpdaterManager.js b/src/main/autoUpdaterManager.js new file mode 100644 index 0000000..235094d --- /dev/null +++ b/src/main/autoUpdaterManager.js @@ -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 diff --git a/src/main/index.js b/src/main/index.js index 3cb40ef..488dc91 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -4,8 +4,7 @@ import { electronApp, optimizer, is } from '@electron-toolkit/utils' import icon from '../../resources/icon.png?asset' import { registerIpRouter } from './ipcRouter' import log from 'electron-log' -import { autoUpdater } from 'electron-updater' - +import autoUpdaterManager from './autoUpdaterManager' // 开发环境日志写到项目根目录 logs 文件夹 if (is.dev) { log.transports.file.resolvePathFn = () => `${process.cwd()}/logs/main.log` @@ -37,6 +36,8 @@ function createWindow() { } else { mainWindow.loadFile(join(__dirname, '../renderer/index.html')) } + + return mainWindow } app.whenReady().then(() => { @@ -48,17 +49,19 @@ app.whenReady().then(() => { }) // IPC处理函数注册 registerIpRouter() - createWindow() - // 自动更新监测, 生产环境使用 - if (!is.dev) { - autoUpdater.checkForUpdatesAndNotify() - } + const mainWindow = createWindow() + + // 初始化自动更新管理器 + autoUpdaterManager.initialize(mainWindow) app.on('activate', function () { if (BrowserWindow.getAllWindows().length === 0) createWindow() }) }) app.on('window-all-closed', () => { + // 清理自动更新管理器资源 + autoUpdaterManager.cleanup() + if (process.platform !== 'darwin') { app.quit() } diff --git a/src/renderer/src/App.jsx b/src/renderer/src/App.jsx index 77c3b09..178b29a 100644 --- a/src/renderer/src/App.jsx +++ b/src/renderer/src/App.jsx @@ -2,6 +2,7 @@ import { Layout } from 'antd' import styles from './App.module.css' const { Sider, Content } = Layout import SiderHeader from './components/SiderHeader/SiderHeader' +import AutoUpdater from './components/AutoUpdater/AutoUpdater' import { FundFilled, CameraFilled } from '@ant-design/icons' import { Tabs } from 'antd' import DeflectionCollection from './components/DeflectionCollection/DeflectionCollection' @@ -48,6 +49,7 @@ const App = () => { /> + ) } diff --git a/src/renderer/src/common/ipcEvents.js b/src/renderer/src/common/ipcEvents.js index 01e7d5e..1b69864 100644 --- a/src/renderer/src/common/ipcEvents.js +++ b/src/renderer/src/common/ipcEvents.js @@ -31,5 +31,14 @@ export const IPC_EVENT = { DATA_FPS_SET: 'dataFps:set', VIDEO_FPS_SET: 'videoFps:set', THRESHOLD_SET: 'threshold:set', - INVALID_DATA_COUNT_SET: 'invalidDataCount:set' + INVALID_DATA_COUNT_SET: 'invalidDataCount:set', + + // 自动更新相关的value不要乱改,定义在这里是为了方便,这些都是标准的api事件名 + UPDATE_AVAILABLE: 'update-available', + UPDATE_NOT_AVAILABLE: 'update-not-available', + DOWNLOAD_PROGRESS: 'download-progress', + UPDATE_DOWNLOADED: 'update-downloaded', + UPDATE_ERROR: 'update-error', + UPDATE_DOWNLOAD: 'update-download', + UPDATE_INSTALL: 'update-install' } diff --git a/src/renderer/src/components/AutoUpdater/AutoUpdater.jsx b/src/renderer/src/components/AutoUpdater/AutoUpdater.jsx new file mode 100644 index 0000000..376437b --- /dev/null +++ b/src/renderer/src/components/AutoUpdater/AutoUpdater.jsx @@ -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 ( + + + 软件更新 + + } + open={isModalVisible} + onCancel={handleCancel} + footer={null} + width={480} + maskClosable={false} + > +
+ {updateInfo && ( +
+ + 发现新版本:v{updateInfo.version} + + {updateInfo.releaseNotes && ( +
+ 更新说明: +
+ {updateInfo.releaseNotes} +
+
+ )} +
+ )} + +
+ {updateStatus} +
+ + {isDownloading && ( +
+ `${percent}%`} + /> +
+ )} + +
+ + + {!isDownloaded && !isDownloading && ( + + )} + {isDownloaded && ( + + )} + +
+
+
+ ) +} + +export default AutoUpdater diff --git a/src/renderer/src/components/AutoUpdater/AutoUpdater.module.css b/src/renderer/src/components/AutoUpdater/AutoUpdater.module.css new file mode 100644 index 0000000..d01b96c --- /dev/null +++ b/src/renderer/src/components/AutoUpdater/AutoUpdater.module.css @@ -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; +}