feat(updater): 优化更新体验与 UI
- ui: UpdateBadge 使用 Tailwind 内置过渡,支持点击打开设置,保留图标动画 - updater: 新增 UpdateContext 首启延迟检查,忽略版本键名命名空间化(含旧键迁移),并发保护 - settings: 去除版本硬编码回退;检测到更新时复用 updateHandle 下载并安装,并新增常显“更新日志”入口 - a11y: 更新徽标支持键盘触达(Enter/Space) - refactor: 移除未使用的 runUpdateFlow 导出 - chore: 类型检查通过,整体行为与权限边界未改变
This commit is contained in:
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@@ -559,7 +559,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc-switch"
|
name = "cc-switch"
|
||||||
version = "3.1.1"
|
version = "3.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"log",
|
"log",
|
||||||
|
|||||||
18
src/App.tsx
18
src/App.tsx
@@ -7,6 +7,7 @@ import EditProviderModal from "./components/EditProviderModal";
|
|||||||
import { ConfirmDialog } from "./components/ConfirmDialog";
|
import { ConfirmDialog } from "./components/ConfirmDialog";
|
||||||
import { AppSwitcher } from "./components/AppSwitcher";
|
import { AppSwitcher } from "./components/AppSwitcher";
|
||||||
import SettingsModal from "./components/SettingsModal";
|
import SettingsModal from "./components/SettingsModal";
|
||||||
|
import { UpdateBadge } from "./components/UpdateBadge";
|
||||||
import { Plus, Settings, Moon, Sun } from "lucide-react";
|
import { Plus, Settings, Moon, Sun } from "lucide-react";
|
||||||
import { buttonStyles } from "./lib/styles";
|
import { buttonStyles } from "./lib/styles";
|
||||||
import { useDarkMode } from "./hooks/useDarkMode";
|
import { useDarkMode } from "./hooks/useDarkMode";
|
||||||
@@ -220,13 +221,16 @@ function App() {
|
|||||||
>
|
>
|
||||||
{isDarkMode ? <Sun size={18} /> : <Moon size={18} />}
|
{isDarkMode ? <Sun size={18} /> : <Moon size={18} />}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<div className="flex items-center gap-2">
|
||||||
onClick={() => setIsSettingsOpen(true)}
|
<button
|
||||||
className={buttonStyles.icon}
|
onClick={() => setIsSettingsOpen(true)}
|
||||||
title="设置"
|
className={buttonStyles.icon}
|
||||||
>
|
title="设置"
|
||||||
<Settings size={18} />
|
>
|
||||||
</button>
|
<Settings size={18} />
|
||||||
|
</button>
|
||||||
|
<UpdateBadge onClick={() => setIsSettingsOpen(true)} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
|||||||
@@ -419,14 +419,12 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
|
|
||||||
// 初始时从配置中同步 API Key(编辑模式)
|
// 初始时从配置中同步 API Key(编辑模式)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialData) {
|
if (!initialData) return;
|
||||||
const parsedKey = getApiKeyFromConfig(
|
const parsedKey = getApiKeyFromConfig(
|
||||||
JSON.stringify(initialData.settingsConfig),
|
JSON.stringify(initialData.settingsConfig),
|
||||||
);
|
);
|
||||||
if (parsedKey) setApiKey(parsedKey);
|
if (parsedKey) setApiKey(parsedKey);
|
||||||
}
|
}, [initialData]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 支持按下 ESC 关闭弹窗
|
// 支持按下 ESC 关闭弹窗
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { X, Info, RefreshCw, FolderOpen } from "lucide-react";
|
import { X, Info, RefreshCw, FolderOpen, Download, ExternalLink } from "lucide-react";
|
||||||
import { getVersion } from "@tauri-apps/api/app";
|
import { getVersion } from "@tauri-apps/api/app";
|
||||||
import "../lib/tauri-api";
|
import "../lib/tauri-api";
|
||||||
import { runUpdateFlow } from "../lib/updater";
|
import { relaunchApp } from "../lib/updater";
|
||||||
|
import { useUpdate } from "../contexts/UpdateContext";
|
||||||
import type { Settings } from "../types";
|
import type { Settings } from "../types";
|
||||||
|
|
||||||
interface SettingsModalProps {
|
interface SettingsModalProps {
|
||||||
@@ -16,6 +17,8 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
const [configPath, setConfigPath] = useState<string>("");
|
const [configPath, setConfigPath] = useState<string>("");
|
||||||
const [version, setVersion] = useState<string>("");
|
const [version, setVersion] = useState<string>("");
|
||||||
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false);
|
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false);
|
||||||
|
const [isDownloading, setIsDownloading] = useState(false);
|
||||||
|
const { hasUpdate, updateInfo, updateHandle, checkUpdate, resetDismiss } = useUpdate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
@@ -29,7 +32,8 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
setVersion(appVersion);
|
setVersion(appVersion);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取版本信息失败:", error);
|
console.error("获取版本信息失败:", error);
|
||||||
setVersion("3.1.1"); // 降级使用默认版本
|
// 失败时不硬编码版本号,显示为未知
|
||||||
|
setVersion("未知");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,15 +69,32 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCheckUpdate = async () => {
|
const handleCheckUpdate = async () => {
|
||||||
setIsCheckingUpdate(true);
|
if (hasUpdate && updateHandle) {
|
||||||
try {
|
// 已检测到更新:直接复用 updateHandle 下载并安装,避免重复检查
|
||||||
// 优先使用 Tauri Updater 流程;失败时回退到打开 Releases 页面
|
setIsDownloading(true);
|
||||||
await runUpdateFlow({ timeout: 30000 });
|
try {
|
||||||
} catch (error) {
|
resetDismiss();
|
||||||
console.error("检查更新失败,回退到 Releases 页面:", error);
|
await updateHandle.downloadAndInstall();
|
||||||
await window.api.checkForUpdates();
|
await relaunchApp();
|
||||||
} finally {
|
} catch (error) {
|
||||||
setIsCheckingUpdate(false);
|
console.error("更新失败:", error);
|
||||||
|
// 更新失败时回退到打开 Releases 页面
|
||||||
|
await window.api.checkForUpdates();
|
||||||
|
} finally {
|
||||||
|
setIsDownloading(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 尚未检测到更新:先检查
|
||||||
|
setIsCheckingUpdate(true);
|
||||||
|
try {
|
||||||
|
await checkUpdate();
|
||||||
|
// 检查后若有更新,让用户再次点击执行
|
||||||
|
} catch (error) {
|
||||||
|
console.error("检查更新失败,回退到 Releases 页面:", error);
|
||||||
|
await window.api.checkForUpdates();
|
||||||
|
} finally {
|
||||||
|
setIsCheckingUpdate(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -85,6 +106,23 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenReleaseNotes = async () => {
|
||||||
|
try {
|
||||||
|
const targetVersion = updateInfo?.availableVersion || version;
|
||||||
|
// 如果未知或为空,回退到 releases 首页
|
||||||
|
if (!targetVersion || targetVersion === "未知") {
|
||||||
|
await window.api.openExternal("https://github.com/farion1231/cc-switch/releases");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tag = targetVersion.startsWith("v") ? targetVersion : `v${targetVersion}`;
|
||||||
|
await window.api.openExternal(
|
||||||
|
`https://github.com/farion1231/cc-switch/releases/tag/${tag}`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("打开更新日志失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black/50 dark:bg-black/70 flex items-center justify-center z-50">
|
<div className="fixed inset-0 bg-black/50 dark:bg-black/70 flex items-center justify-center z-50">
|
||||||
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-2xl w-[500px] overflow-hidden">
|
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-2xl w-[500px] overflow-hidden">
|
||||||
@@ -168,24 +206,48 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<div className="flex items-center gap-2">
|
||||||
onClick={handleCheckUpdate}
|
<button
|
||||||
disabled={isCheckingUpdate}
|
onClick={handleOpenReleaseNotes}
|
||||||
className={`px-3 py-1.5 text-xs font-medium rounded-lg transition-all ${
|
className="px-2 py-1 text-xs font-medium text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 rounded-lg hover:bg-blue-500/10 transition-colors"
|
||||||
isCheckingUpdate
|
title={hasUpdate ? "查看该版本更新日志" : "查看当前版本更新日志"}
|
||||||
? "bg-white dark:bg-gray-700 text-gray-400 dark:text-gray-500"
|
>
|
||||||
: "bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 text-blue-500 dark:text-blue-400"
|
<span className="inline-flex items-center gap-1">
|
||||||
}`}
|
<ExternalLink size={12} />
|
||||||
>
|
更新日志
|
||||||
{isCheckingUpdate ? (
|
|
||||||
<span className="flex items-center gap-1">
|
|
||||||
<RefreshCw size={12} className="animate-spin" />
|
|
||||||
检查中...
|
|
||||||
</span>
|
</span>
|
||||||
) : (
|
</button>
|
||||||
"检查更新"
|
<button
|
||||||
)}
|
onClick={handleCheckUpdate}
|
||||||
</button>
|
disabled={isCheckingUpdate || isDownloading}
|
||||||
|
className={`px-3 py-1.5 text-xs font-medium rounded-lg transition-all ${
|
||||||
|
isCheckingUpdate || isDownloading
|
||||||
|
? "bg-white dark:bg-gray-700 text-gray-400 dark:text-gray-500"
|
||||||
|
: hasUpdate
|
||||||
|
? "bg-gradient-to-r from-blue-500 to-purple-500 hover:from-blue-600 hover:to-purple-600 text-white"
|
||||||
|
: "bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 text-blue-500 dark:text-blue-400"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isDownloading ? (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Download size={12} className="animate-pulse" />
|
||||||
|
更新中...
|
||||||
|
</span>
|
||||||
|
) : isCheckingUpdate ? (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<RefreshCw size={12} className="animate-spin" />
|
||||||
|
检查中...
|
||||||
|
</span>
|
||||||
|
) : hasUpdate ? (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Download size={12} />
|
||||||
|
点击更新 {updateInfo?.availableVersion}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
"检查更新"
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
59
src/components/UpdateBadge.tsx
Normal file
59
src/components/UpdateBadge.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { X, Sparkles } from "lucide-react";
|
||||||
|
import { useUpdate } from "../contexts/UpdateContext";
|
||||||
|
|
||||||
|
interface UpdateBadgeProps {
|
||||||
|
className?: string;
|
||||||
|
onClick?: () => void; // 点击徽标的回调(例如打开设置)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UpdateBadge({ className = "", onClick }: UpdateBadgeProps) {
|
||||||
|
const { hasUpdate, updateInfo, isDismissed, dismissUpdate } = useUpdate();
|
||||||
|
|
||||||
|
// 如果没有更新或已关闭,不显示
|
||||||
|
if (!hasUpdate || isDismissed || !updateInfo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
flex items-center gap-2 px-3 py-1.5
|
||||||
|
bg-gradient-to-r from-blue-500/20 to-purple-500/20
|
||||||
|
border border-blue-500/30
|
||||||
|
rounded-full text-xs
|
||||||
|
transition-all duration-200
|
||||||
|
${onClick ? "cursor-pointer hover:border-blue-400/50" : ""}
|
||||||
|
${className}
|
||||||
|
`}
|
||||||
|
role={onClick ? "button" : undefined}
|
||||||
|
tabIndex={onClick ? 0 : -1}
|
||||||
|
onClick={onClick}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (!onClick) return;
|
||||||
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
|
e.preventDefault();
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Sparkles className="w-3 h-3 text-blue-400 animate-pulse" />
|
||||||
|
<span className="text-gray-200 font-medium">
|
||||||
|
新版本 {updateInfo.availableVersion}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
dismissUpdate();
|
||||||
|
}}
|
||||||
|
className="
|
||||||
|
-mr-1 p-0.5 rounded-full
|
||||||
|
hover:bg-white/10 transition-colors
|
||||||
|
focus:outline-none focus:ring-2 focus:ring-blue-500/50
|
||||||
|
"
|
||||||
|
aria-label="关闭更新提醒"
|
||||||
|
>
|
||||||
|
<X className="w-3 h-3 text-gray-400 hover:text-gray-200" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
147
src/contexts/UpdateContext.tsx
Normal file
147
src/contexts/UpdateContext.tsx
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import React, { createContext, useContext, useState, useEffect, useCallback, useRef } from "react";
|
||||||
|
import type { UpdateInfo, UpdateHandle } from "../lib/updater";
|
||||||
|
import { checkForUpdate } from "../lib/updater";
|
||||||
|
|
||||||
|
interface UpdateContextValue {
|
||||||
|
// 更新状态
|
||||||
|
hasUpdate: boolean;
|
||||||
|
updateInfo: UpdateInfo | null;
|
||||||
|
updateHandle: UpdateHandle | null;
|
||||||
|
isChecking: boolean;
|
||||||
|
error: string | null;
|
||||||
|
|
||||||
|
// 提示状态
|
||||||
|
isDismissed: boolean;
|
||||||
|
dismissUpdate: () => void;
|
||||||
|
|
||||||
|
// 操作方法
|
||||||
|
checkUpdate: () => Promise<void>;
|
||||||
|
resetDismiss: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateContext = createContext<UpdateContextValue | undefined>(undefined);
|
||||||
|
|
||||||
|
export function UpdateProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const DISMISSED_VERSION_KEY = "ccswitch:update:dismissedVersion";
|
||||||
|
const LEGACY_DISMISSED_KEY = "dismissedUpdateVersion"; // 兼容旧键
|
||||||
|
|
||||||
|
const [hasUpdate, setHasUpdate] = useState(false);
|
||||||
|
const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);
|
||||||
|
const [updateHandle, setUpdateHandle] = useState<UpdateHandle | null>(null);
|
||||||
|
const [isChecking, setIsChecking] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [isDismissed, setIsDismissed] = useState(false);
|
||||||
|
|
||||||
|
// 从 localStorage 读取已关闭的版本
|
||||||
|
useEffect(() => {
|
||||||
|
const current = updateInfo?.availableVersion;
|
||||||
|
if (!current) return;
|
||||||
|
|
||||||
|
// 读取新键;若不存在,尝试迁移旧键
|
||||||
|
let dismissedVersion = localStorage.getItem(DISMISSED_VERSION_KEY);
|
||||||
|
if (!dismissedVersion) {
|
||||||
|
const legacy = localStorage.getItem(LEGACY_DISMISSED_KEY);
|
||||||
|
if (legacy) {
|
||||||
|
localStorage.setItem(DISMISSED_VERSION_KEY, legacy);
|
||||||
|
localStorage.removeItem(LEGACY_DISMISSED_KEY);
|
||||||
|
dismissedVersion = legacy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsDismissed(dismissedVersion === current);
|
||||||
|
}, [updateInfo?.availableVersion]);
|
||||||
|
|
||||||
|
const isCheckingRef = useRef(false);
|
||||||
|
|
||||||
|
const checkUpdate = useCallback(async () => {
|
||||||
|
if (isCheckingRef.current) return;
|
||||||
|
isCheckingRef.current = true;
|
||||||
|
setIsChecking(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await checkForUpdate({ timeout: 30000 });
|
||||||
|
|
||||||
|
if (result.status === "available") {
|
||||||
|
setHasUpdate(true);
|
||||||
|
setUpdateInfo(result.info);
|
||||||
|
setUpdateHandle(result.update);
|
||||||
|
|
||||||
|
// 检查是否已经关闭过这个版本的提醒
|
||||||
|
let dismissedVersion = localStorage.getItem(DISMISSED_VERSION_KEY);
|
||||||
|
if (!dismissedVersion) {
|
||||||
|
const legacy = localStorage.getItem(LEGACY_DISMISSED_KEY);
|
||||||
|
if (legacy) {
|
||||||
|
localStorage.setItem(DISMISSED_VERSION_KEY, legacy);
|
||||||
|
localStorage.removeItem(LEGACY_DISMISSED_KEY);
|
||||||
|
dismissedVersion = legacy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setIsDismissed(dismissedVersion === result.info.availableVersion);
|
||||||
|
} else {
|
||||||
|
setHasUpdate(false);
|
||||||
|
setUpdateInfo(null);
|
||||||
|
setUpdateHandle(null);
|
||||||
|
setIsDismissed(false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("检查更新失败:", err);
|
||||||
|
setError(err instanceof Error ? err.message : "检查更新失败");
|
||||||
|
setHasUpdate(false);
|
||||||
|
} finally {
|
||||||
|
setIsChecking(false);
|
||||||
|
isCheckingRef.current = false;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const dismissUpdate = useCallback(() => {
|
||||||
|
setIsDismissed(true);
|
||||||
|
if (updateInfo?.availableVersion) {
|
||||||
|
localStorage.setItem(DISMISSED_VERSION_KEY, updateInfo.availableVersion);
|
||||||
|
// 清理旧键
|
||||||
|
localStorage.removeItem(LEGACY_DISMISSED_KEY);
|
||||||
|
}
|
||||||
|
}, [updateInfo?.availableVersion]);
|
||||||
|
|
||||||
|
const resetDismiss = useCallback(() => {
|
||||||
|
setIsDismissed(false);
|
||||||
|
localStorage.removeItem(DISMISSED_VERSION_KEY);
|
||||||
|
localStorage.removeItem(LEGACY_DISMISSED_KEY);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 应用启动时自动检查更新
|
||||||
|
useEffect(() => {
|
||||||
|
// 延迟1秒后检查,避免影响启动体验
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
checkUpdate().catch(console.error);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [checkUpdate]);
|
||||||
|
|
||||||
|
const value: UpdateContextValue = {
|
||||||
|
hasUpdate,
|
||||||
|
updateInfo,
|
||||||
|
updateHandle,
|
||||||
|
isChecking,
|
||||||
|
error,
|
||||||
|
isDismissed,
|
||||||
|
dismissUpdate,
|
||||||
|
checkUpdate,
|
||||||
|
resetDismiss,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UpdateContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</UpdateContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUpdate() {
|
||||||
|
const context = useContext(UpdateContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useUpdate must be used within UpdateProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -122,25 +122,5 @@ export async function relaunchApp(): Promise<void> {
|
|||||||
await relaunch();
|
await relaunch();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runUpdateFlow(
|
// 旧的聚合更新流程已由调用方直接使用 updateHandle 取代
|
||||||
opts: CheckOptions = {},
|
// 如需单函数封装,可在需要时基于 checkForUpdate + updateHandle 复合调用
|
||||||
): Promise<{ status: "up-to-date" | "done" }> {
|
|
||||||
const result = await checkForUpdate(opts);
|
|
||||||
if (result.status === "up-to-date") return result;
|
|
||||||
|
|
||||||
let downloaded = 0;
|
|
||||||
let total = 0;
|
|
||||||
await result.update.downloadAndInstall((e) => {
|
|
||||||
if (e.event === "Started") {
|
|
||||||
total = e.total ?? 0;
|
|
||||||
downloaded = 0;
|
|
||||||
} else if (e.event === "Progress") {
|
|
||||||
downloaded += e.downloaded ?? 0;
|
|
||||||
// 调用方可监听此处并更新 UI(目前设置页仅显示加载态)
|
|
||||||
console.debug("update progress", { downloaded, total });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await relaunchApp();
|
|
||||||
return { status: "done" };
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
import { UpdateProvider } from "./contexts/UpdateContext";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
// 导入 Tauri API(自动绑定到 window.api)
|
// 导入 Tauri API(自动绑定到 window.api)
|
||||||
import "./lib/tauri-api";
|
import "./lib/tauri-api";
|
||||||
@@ -19,6 +20,8 @@ try {
|
|||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<UpdateProvider>
|
||||||
|
<App />
|
||||||
|
</UpdateProvider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user