feat: 初始化船长·HeiXiu 桌面应用项目(错误是svg的问题)
Electron + React 19 + TypeScript + Ant Design 6 + Tailwind CSS 4 【核心架构】 - Electron 主进程(窗口管理、平台检测、图标适配) - Vite 构建链(双产物 dist/ + dist-electron/) - 共享层 shared/(类型、常量、工具函数) - IPC 通信框架 + 安全守卫 【主题系统】 - 蓝紫渐变主题(亮色/暗色双模式) - Ant Design ConfigProvider 适配(lightAlgorithm / darkAlgorithm) - Tailwind CSS 4 @theme 指令 + CSS 变量 - 设计令牌三处同步(tokens.ts / globals.css / antd-theme.ts) 【导航栏】 - 自定义 H5 导航栏替代系统菜单 - 个人版/企业版双布局 - 未登录拦截 + 事件总线驱动登录弹窗 【登录注册】 - Modal 弹层(Portal)+ Tabs 切换 - 登录/注册表单动态字段渲染 - 记住密码 / 自动登录 / 用户协议 【更新系统】 - 自定义 API 版本检查(替代 electron-updater generic provider) - 手动下载 + SHA512 校验 + spawn NSIS 安装 - 渲染进程更新状态机 Hook(useUpdater) - 检查更新按钮 + 下载进度展示 【构建工具链】 - build.mjs 构建总管(--win/--mac/--linux --personal/--enterprise --build/--clean/--sign/--upload/--publish) - scripts/ 自动化脚本(clean / generate-update-info / upload-oss / publish-release) - electron-builder NSIS 安装器(可选路径、多版本打包) - update-info.json 自动生成(fileSize/sha512/changelog/releaseDate) 【代码质量】 - ESLint + TypeScript strict + Prettier - husky + lint-staged(提交前自动检查) - commitlint(规范提交信息) - Playwright E2E + Vitest 单元测试框架 - rollup-plugin-visualizer 打包体积分析 【文档】 - README.md(快速开始 + 目录结构 + 命令速查) - UpdateA.md(发布手册:API/签名/发版流程/数据库) - CHANGELOG.md(更新日志) - .env.example(构建环境变量模板)
This commit is contained in:
136
src/hooks/use-updater.ts
Normal file
136
src/hooks/use-updater.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
// ============================================================
|
||||
// useUpdater — 渲染进程中的更新状态管理
|
||||
//
|
||||
// 用法:
|
||||
// const { status, progress, checkForUpdates, installUpdate } = useUpdater();
|
||||
//
|
||||
// 状态机:idle → checking → (no-update | available → downloading → downloaded → idle)
|
||||
// ============================================================
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
|
||||
import { safeIpcInvoke, safeIpcOn, safeIpcOff, safeIpcSend } from '@/utils/ipc';
|
||||
|
||||
// ---------- 类型 ----------
|
||||
|
||||
export type UpdaterStatus =
|
||||
| 'idle' // 空闲
|
||||
| 'checking' // 正在检查
|
||||
| 'no-update' // 已是最新
|
||||
| 'available' // 发现新版本,准备下载
|
||||
| 'downloading' // 下载中
|
||||
| 'downloaded' // 下载完成,待安装
|
||||
| 'error'; // 出错
|
||||
|
||||
export interface UpdateInfo {
|
||||
version: string;
|
||||
releaseDate?: string;
|
||||
releaseNotes?: string;
|
||||
forceUpdate?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateProgress {
|
||||
percent: number;
|
||||
bytesPerSecond?: number;
|
||||
transferred?: number;
|
||||
total?: number;
|
||||
}
|
||||
|
||||
export interface UpdateError {
|
||||
message: string;
|
||||
}
|
||||
|
||||
// ---------- IPC 通道名(与主进程 updater.ts 保持一致)----------
|
||||
|
||||
const CH = {
|
||||
UPDATE_AVAILABLE: 'update-available',
|
||||
UPDATE_PROGRESS: 'update-progress',
|
||||
UPDATE_DOWNLOADED: 'update-downloaded',
|
||||
UPDATE_ERROR: 'update-error',
|
||||
UPDATE_NOT_AVAILABLE: 'update-not-available',
|
||||
INSTALL_UPDATE: 'install-update',
|
||||
CHECK_FOR_UPDATE: 'check-for-update',
|
||||
} as const;
|
||||
|
||||
// ---------- Hook ----------
|
||||
|
||||
export function useUpdater() {
|
||||
const [status, setStatus] = useState<UpdaterStatus>('idle');
|
||||
const [info, setInfo] = useState<UpdateInfo | null>(null);
|
||||
const [progress, setProgress] = useState<UpdateProgress>({ percent: 0 });
|
||||
const [error, setError] = useState<UpdateError | null>(null);
|
||||
|
||||
// 防止 StrictMode 双重监听
|
||||
const registered = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (registered.current) return;
|
||||
registered.current = true;
|
||||
|
||||
safeIpcOn(CH.UPDATE_AVAILABLE, (_event: unknown, ...args: unknown[]) => {
|
||||
setInfo(args[0] as UpdateInfo);
|
||||
setStatus('available');
|
||||
});
|
||||
|
||||
safeIpcOn(CH.UPDATE_PROGRESS, (_event: unknown, ...args: unknown[]) => {
|
||||
setStatus('downloading');
|
||||
setProgress(args[0] as UpdateProgress);
|
||||
});
|
||||
|
||||
safeIpcOn(CH.UPDATE_DOWNLOADED, () => {
|
||||
setStatus('downloaded');
|
||||
});
|
||||
|
||||
safeIpcOn(CH.UPDATE_ERROR, (_event: unknown, ...args: unknown[]) => {
|
||||
setError(args[0] as UpdateError);
|
||||
setStatus('error');
|
||||
});
|
||||
|
||||
safeIpcOn(CH.UPDATE_NOT_AVAILABLE, () => {
|
||||
setStatus('no-update');
|
||||
});
|
||||
|
||||
return () => {
|
||||
safeIpcOff(CH.UPDATE_AVAILABLE, () => {});
|
||||
safeIpcOff(CH.UPDATE_PROGRESS, () => {});
|
||||
safeIpcOff(CH.UPDATE_DOWNLOADED, () => {});
|
||||
safeIpcOff(CH.UPDATE_ERROR, () => {});
|
||||
safeIpcOff(CH.UPDATE_NOT_AVAILABLE, () => {});
|
||||
};
|
||||
}, []);
|
||||
|
||||
/** 手动检查更新 */
|
||||
const checkForUpdates = useCallback(async () => {
|
||||
setStatus('checking');
|
||||
setError(null);
|
||||
try {
|
||||
await safeIpcInvoke(CH.CHECK_FOR_UPDATE);
|
||||
} catch (err) {
|
||||
setStatus('error');
|
||||
setError({ message: (err as Error).message || '检查更新失败' });
|
||||
}
|
||||
}, []);
|
||||
|
||||
/** 确认安装更新(退出 → 安装 → 重启) */
|
||||
const installUpdate = useCallback(() => {
|
||||
safeIpcSend(CH.INSTALL_UPDATE);
|
||||
}, []);
|
||||
|
||||
/** 重置状态 */
|
||||
const reset = useCallback(() => {
|
||||
setStatus('idle');
|
||||
setInfo(null);
|
||||
setProgress({ percent: 0 });
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
status,
|
||||
info,
|
||||
progress,
|
||||
error,
|
||||
checkForUpdates,
|
||||
installUpdate,
|
||||
reset,
|
||||
} as const;
|
||||
}
|
||||
Reference in New Issue
Block a user