mirror of
https://github.com/ILoveBingLu/CipherTalk.git
synced 2026-05-23 04:53:24 +08:00
feat:优化更新功能与UI
This commit is contained in:
+98
-16
@@ -12,14 +12,17 @@
|
||||
right: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||
gap: 14px;
|
||||
min-width: 320px;
|
||||
max-width: 420px;
|
||||
padding: 16px 18px;
|
||||
background: color-mix(in srgb, var(--bg-primary) 88%, white 12%);
|
||||
border: 1px solid color-mix(in srgb, var(--primary) 22%, var(--border-color) 78%);
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.16);
|
||||
z-index: 1000;
|
||||
animation: slideUp 0.3s ease;
|
||||
backdrop-filter: blur(18px);
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
@@ -33,15 +36,25 @@
|
||||
}
|
||||
|
||||
.update-toast-icon {
|
||||
font-size: 28px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 14px;
|
||||
flex-shrink: 0;
|
||||
font-size: 22px;
|
||||
background: color-mix(in srgb, var(--primary) 14%, transparent);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.update-toast-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.update-toast-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
@@ -49,13 +62,30 @@
|
||||
.update-toast-version {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.update-toast-meta {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
span {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--bg-tertiary) 75%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.update-toast-btn {
|
||||
padding: 8px 16px;
|
||||
padding: 0 16px;
|
||||
height: 38px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
border-radius: 10px;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
@@ -63,7 +93,12 @@
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--primary-hover);
|
||||
}
|
||||
}
|
||||
@@ -86,6 +121,30 @@
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.update-toast-progress {
|
||||
width: 88px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.update-toast-progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
background: color-mix(in srgb, var(--bg-tertiary) 82%, transparent);
|
||||
}
|
||||
|
||||
.update-toast-progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, var(--primary), color-mix(in srgb, var(--primary) 65%, white));
|
||||
transition: width 0.2s linear;
|
||||
}
|
||||
|
||||
&.is-downloading {
|
||||
min-width: 360px;
|
||||
}
|
||||
}
|
||||
|
||||
.force-update-overlay {
|
||||
@@ -374,8 +433,8 @@
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 16px;
|
||||
gap: 14px;
|
||||
padding: 10px 16px;
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
backdrop-filter: blur(8px);
|
||||
border-radius: 20px;
|
||||
@@ -392,8 +451,31 @@
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
.capsule-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
}
|
||||
|
||||
.capsule-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
small {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar-bg {
|
||||
|
||||
+124
-24
@@ -51,6 +51,25 @@ type AppUpdateInfo = {
|
||||
checkedAt: number
|
||||
updateSource: 'github' | 'custom' | 'none'
|
||||
policySource: 'github' | 'custom' | 'none'
|
||||
diagnostics?: {
|
||||
phase: 'idle' | 'checking' | 'available' | 'downloading' | 'downloaded' | 'installing' | 'failed'
|
||||
strategy: 'unknown' | 'differential' | 'full'
|
||||
fallbackToFull: boolean
|
||||
lastError?: string
|
||||
lastEvent?: string
|
||||
progressPercent?: number
|
||||
downloadedBytes?: number
|
||||
totalBytes?: number
|
||||
targetVersion?: string
|
||||
lastUpdatedAt: number
|
||||
}
|
||||
}
|
||||
|
||||
type UpdateDownloadProgressPayload = {
|
||||
percent: number
|
||||
transferred: number
|
||||
total: number
|
||||
bytesPerSecond: number
|
||||
}
|
||||
|
||||
function App() {
|
||||
@@ -71,7 +90,25 @@ function App() {
|
||||
|
||||
// 更新提示状态
|
||||
const [updateInfo, setUpdateInfo] = useState<AppUpdateInfo | null>(null)
|
||||
const [downloadProgress, setDownloadProgress] = useState<number | null>(null)
|
||||
const [downloadProgress, setDownloadProgress] = useState<UpdateDownloadProgressPayload | null>(null)
|
||||
|
||||
const formatSpeed = (bytesPerSecond: number) => {
|
||||
if (!Number.isFinite(bytesPerSecond) || bytesPerSecond <= 0) return '计算中'
|
||||
if (bytesPerSecond < 1024) return `${bytesPerSecond.toFixed(0)} B/s`
|
||||
if (bytesPerSecond < 1024 * 1024) return `${(bytesPerSecond / 1024).toFixed(1)} KB/s`
|
||||
return `${(bytesPerSecond / (1024 * 1024)).toFixed(1)} MB/s`
|
||||
}
|
||||
|
||||
const formatBytes = (bytes?: number) => {
|
||||
if (!bytes || bytes <= 0) return '0 B'
|
||||
if (bytes < 1024) return `${bytes.toFixed(0)} B`
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
||||
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
|
||||
}
|
||||
|
||||
const isUpdateDownloading = updateInfo?.diagnostics?.phase === 'downloading' || updateInfo?.diagnostics?.phase === 'installing'
|
||||
const progressPercent = downloadProgress?.percent ?? updateInfo?.diagnostics?.progressPercent ?? null
|
||||
|
||||
// 加载主题配置
|
||||
useEffect(() => {
|
||||
@@ -197,6 +234,24 @@ function App() {
|
||||
useEffect(() => {
|
||||
const removeDownloadListener = window.electronAPI.app.onDownloadProgress?.((progress) => {
|
||||
setDownloadProgress(progress)
|
||||
setUpdateInfo((current) => {
|
||||
if (!current) return current
|
||||
return {
|
||||
...current,
|
||||
diagnostics: {
|
||||
phase: 'downloading',
|
||||
strategy: current.diagnostics?.strategy || 'unknown',
|
||||
fallbackToFull: current.diagnostics?.fallbackToFull || false,
|
||||
lastError: current.diagnostics?.lastError,
|
||||
lastEvent: current.diagnostics?.lastEvent,
|
||||
progressPercent: progress.percent,
|
||||
downloadedBytes: progress.transferred,
|
||||
totalBytes: progress.total,
|
||||
targetVersion: current.version || current.diagnostics?.targetVersion,
|
||||
lastUpdatedAt: Date.now()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
return () => {
|
||||
removeDownloadListener?.()
|
||||
@@ -204,10 +259,30 @@ function App() {
|
||||
}, [])
|
||||
|
||||
const dismissUpdate = () => {
|
||||
if (updateInfo?.forceUpdate) return
|
||||
if (updateInfo?.forceUpdate || isUpdateDownloading) return
|
||||
setUpdateInfo(null)
|
||||
}
|
||||
|
||||
const handleStartUpdate = () => {
|
||||
if (isUpdateDownloading) return
|
||||
setUpdateInfo((current) => current ? {
|
||||
...current,
|
||||
diagnostics: {
|
||||
phase: 'downloading',
|
||||
strategy: current.diagnostics?.strategy || 'unknown',
|
||||
fallbackToFull: current.diagnostics?.fallbackToFull || false,
|
||||
lastError: undefined,
|
||||
lastEvent: '开始下载更新',
|
||||
progressPercent: 0,
|
||||
downloadedBytes: 0,
|
||||
totalBytes: current.diagnostics?.totalBytes,
|
||||
targetVersion: current.version || current.diagnostics?.targetVersion,
|
||||
lastUpdatedAt: Date.now()
|
||||
}
|
||||
} : current)
|
||||
window.electronAPI.app.downloadAndInstall()
|
||||
}
|
||||
|
||||
// 检查是否是独立聊天窗口
|
||||
const isChatWindow = location.pathname === '/chat-window'
|
||||
const isGroupAnalyticsWindow = location.pathname === '/group-analytics-window'
|
||||
@@ -493,22 +568,41 @@ function App() {
|
||||
<div className="app-container">
|
||||
<TitleBar />
|
||||
{updateInfo && !updateInfo.forceUpdate && (
|
||||
<div className="update-toast">
|
||||
<div className="update-toast-icon">🎉</div>
|
||||
<div className={`update-toast ${isUpdateDownloading ? 'is-downloading' : ''}`}>
|
||||
<div className="update-toast-icon">{isUpdateDownloading ? <Loader2 size={18} className="spin" /> : '🎉'}</div>
|
||||
<div className="update-toast-content">
|
||||
<div className="update-toast-title">发现新版本</div>
|
||||
<div className="update-toast-version">v{updateInfo.version} 已发布</div>
|
||||
<div className="update-toast-version">更新源:{updateInfo.updateSource === 'github' ? 'GitHub Release' : '未知'}</div>
|
||||
<div className="update-toast-title">{isUpdateDownloading ? '正在下载更新' : '发现新版本'}</div>
|
||||
<div className="update-toast-version">
|
||||
{isUpdateDownloading ? `v${updateInfo.version} ${progressPercent !== null ? `${progressPercent.toFixed(0)}%` : ''}` : `v${updateInfo.version} 已发布`}
|
||||
</div>
|
||||
<div className="update-toast-version">
|
||||
{isUpdateDownloading
|
||||
? `${formatBytes(downloadProgress?.transferred ?? updateInfo.diagnostics?.downloadedBytes)} / ${formatBytes(downloadProgress?.total ?? updateInfo.diagnostics?.totalBytes)}`
|
||||
: `更新源:${updateInfo.updateSource === 'github' ? 'GitHub Release' : '未知'}`}
|
||||
</div>
|
||||
{isUpdateDownloading && (
|
||||
<div className="update-toast-meta">
|
||||
<span>速度 {formatSpeed(downloadProgress?.bytesPerSecond ?? 0)}</span>
|
||||
{updateInfo.diagnostics?.fallbackToFull ? <span>已回退全量</span> : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<button className="update-toast-btn" onClick={() => {
|
||||
window.electronAPI.app.downloadAndInstall()
|
||||
dismissUpdate()
|
||||
}}>
|
||||
立即更新
|
||||
</button>
|
||||
<button className="update-toast-close" onClick={dismissUpdate}>
|
||||
<X size={14} />
|
||||
</button>
|
||||
{isUpdateDownloading ? (
|
||||
<div className="update-toast-progress">
|
||||
<div className="update-toast-progress-bar">
|
||||
<div className="update-toast-progress-fill" style={{ width: `${progressPercent ?? 0}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<button className="update-toast-btn" onClick={handleStartUpdate} disabled={isUpdateDownloading}>
|
||||
立即更新
|
||||
</button>
|
||||
<button className="update-toast-close" onClick={dismissUpdate}>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{updateInfo?.forceUpdate && (
|
||||
@@ -538,20 +632,20 @@ function App() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{downloadProgress !== null && (
|
||||
{progressPercent !== null && (
|
||||
<div className="force-update-progress">
|
||||
<div className="force-update-progress-label">
|
||||
<Loader2 size={16} className="spin" />
|
||||
<span>正在下载更新... {downloadProgress.toFixed(0)}%</span>
|
||||
<span>正在下载更新... {progressPercent.toFixed(0)}%</span>
|
||||
</div>
|
||||
<div className="force-update-progress-bar">
|
||||
<div className="force-update-progress-fill" style={{ width: `${downloadProgress}%` }} />
|
||||
<div className="force-update-progress-fill" style={{ width: `${progressPercent}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="force-update-actions">
|
||||
<button className="btn btn-primary" onClick={() => window.electronAPI.app.downloadAndInstall()}>
|
||||
<button className="btn btn-primary" onClick={handleStartUpdate} disabled={isUpdateDownloading}>
|
||||
立即更新
|
||||
</button>
|
||||
<button className="btn btn-secondary" onClick={() => window.electronAPI.window.close()}>
|
||||
@@ -599,12 +693,18 @@ function App() {
|
||||
</Box>
|
||||
</Box>
|
||||
<DecryptProgressOverlay />
|
||||
{downloadProgress !== null && (
|
||||
{progressPercent !== null && (
|
||||
<div className="download-progress-capsule">
|
||||
<Loader2 className="spin" size={14} />
|
||||
<span>正在下载更新... {downloadProgress.toFixed(0)}%</span>
|
||||
<div className="progress-bar-bg">
|
||||
<div className="progress-bar-fill" style={{ width: `${downloadProgress}%` }} />
|
||||
<div className="capsule-copy">
|
||||
<span>正在下载更新... {progressPercent.toFixed(0)}%</span>
|
||||
<small>{formatSpeed(downloadProgress?.bytesPerSecond ?? 0)}</small>
|
||||
</div>
|
||||
<div className="capsule-progress">
|
||||
<div className="progress-bar-bg">
|
||||
<div className="progress-bar-fill" style={{ width: `${progressPercent}%` }} />
|
||||
</div>
|
||||
<small>{formatBytes(downloadProgress?.transferred ?? updateInfo?.diagnostics?.downloadedBytes)} / {formatBytes(downloadProgress?.total ?? updateInfo?.diagnostics?.totalBytes)}</small>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+40
-18
@@ -1001,29 +1001,51 @@ to {
|
||||
|
||||
.download-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 200px;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: min(320px, 100%);
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
.progress-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--primary);
|
||||
border-radius: 3px;
|
||||
transition: width 0.2s ease;
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--primary);
|
||||
border-radius: 999px;
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
min-width: 35px;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
min-width: 35px;
|
||||
.progress-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useSearchParams, useLocation } from 'react-router-dom'
|
||||
import { useAppStore } from '../stores/appStore'
|
||||
import { useThemeStore, themes } from '../stores/themeStore'
|
||||
import { useActivationStore } from '../stores/activationStore'
|
||||
import type { UpdateDownloadProgressPayload } from '../types/electron'
|
||||
import { dialog } from '../services/ipc'
|
||||
import * as configService from '../services/config'
|
||||
import AISummarySettings from '../components/ai/AISummarySettings'
|
||||
@@ -97,6 +98,7 @@ function SettingsPage() {
|
||||
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false)
|
||||
const [isDownloading, setIsDownloading] = useState(false)
|
||||
const [downloadProgress, setDownloadProgress] = useState(0)
|
||||
const [downloadProgressDetail, setDownloadProgressDetail] = useState<UpdateDownloadProgressPayload | null>(null)
|
||||
const [appVersion, setAppVersion] = useState('')
|
||||
const [updateInfo, setUpdateInfo] = useState<{
|
||||
hasUpdate: boolean
|
||||
@@ -486,17 +488,61 @@ function SettingsPage() {
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
|
||||
}
|
||||
|
||||
const formatSpeed = (bytesPerSecond: number): string => {
|
||||
if (!Number.isFinite(bytesPerSecond) || bytesPerSecond <= 0) return '计算中'
|
||||
if (bytesPerSecond < 1024) return `${bytesPerSecond.toFixed(0)} B/s`
|
||||
if (bytesPerSecond < 1024 * 1024) return `${(bytesPerSecond / 1024).toFixed(1)} KB/s`
|
||||
return `${(bytesPerSecond / (1024 * 1024)).toFixed(1)} MB/s`
|
||||
}
|
||||
|
||||
const syncUpdateState = async () => {
|
||||
try {
|
||||
const state = await window.electronAPI.app.getUpdateState?.()
|
||||
if (!state) return
|
||||
setUpdateInfo(state)
|
||||
const phase = state.diagnostics?.phase
|
||||
setIsDownloading(phase === 'downloading' || phase === 'installing')
|
||||
if (typeof state.diagnostics?.progressPercent === 'number') {
|
||||
setDownloadProgress(state.diagnostics.progressPercent)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('同步更新状态失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听下载进度
|
||||
useEffect(() => {
|
||||
const removeListener = window.electronAPI.app.onDownloadProgress?.((progress: number) => {
|
||||
setDownloadProgress(progress)
|
||||
syncUpdateState()
|
||||
|
||||
const removeListener = window.electronAPI.app.onDownloadProgress?.((progress: UpdateDownloadProgressPayload) => {
|
||||
setDownloadProgress(progress.percent)
|
||||
setDownloadProgressDetail(progress)
|
||||
setIsDownloading(true)
|
||||
setUpdateInfo((current) => {
|
||||
if (!current) return current
|
||||
return {
|
||||
...current,
|
||||
diagnostics: {
|
||||
phase: 'downloading',
|
||||
strategy: current.diagnostics?.strategy || 'unknown',
|
||||
fallbackToFull: current.diagnostics?.fallbackToFull || false,
|
||||
lastError: current.diagnostics?.lastError,
|
||||
lastEvent: current.diagnostics?.lastEvent,
|
||||
progressPercent: progress.percent,
|
||||
downloadedBytes: progress.transferred,
|
||||
totalBytes: progress.total,
|
||||
targetVersion: current.version || current.diagnostics?.targetVersion,
|
||||
lastUpdatedAt: Date.now()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
return () => removeListener?.()
|
||||
}, [])
|
||||
|
||||
const handleCheckUpdate = async () => {
|
||||
if (isDownloading || updateInfo?.diagnostics?.phase === 'installing') return
|
||||
setIsCheckingUpdate(true)
|
||||
setUpdateInfo(null)
|
||||
try {
|
||||
const result = await window.electronAPI.app.checkForUpdates()
|
||||
if (result.hasUpdate) {
|
||||
@@ -598,14 +644,31 @@ function SettingsPage() {
|
||||
}
|
||||
|
||||
const handleUpdateNow = async () => {
|
||||
if (isDownloading) return
|
||||
setIsDownloading(true)
|
||||
setDownloadProgress(0)
|
||||
setUpdateInfo((current) => current ? {
|
||||
...current,
|
||||
diagnostics: {
|
||||
phase: 'downloading',
|
||||
strategy: current.diagnostics?.strategy || 'unknown',
|
||||
fallbackToFull: current.diagnostics?.fallbackToFull || false,
|
||||
lastError: undefined,
|
||||
lastEvent: '开始下载更新',
|
||||
progressPercent: 0,
|
||||
downloadedBytes: 0,
|
||||
totalBytes: current.diagnostics?.totalBytes,
|
||||
targetVersion: current.version || current.diagnostics?.targetVersion,
|
||||
lastUpdatedAt: Date.now()
|
||||
}
|
||||
} : current)
|
||||
try {
|
||||
showMessage('正在下载更新...', true)
|
||||
await window.electronAPI.app.downloadAndInstall()
|
||||
} catch (e) {
|
||||
showMessage(`更新失败: ${e}`, false)
|
||||
setIsDownloading(false)
|
||||
await syncUpdateState()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2678,6 +2741,13 @@ function SettingsPage() {
|
||||
useEffect(() => {
|
||||
if (location.state?.updateInfo) {
|
||||
setUpdateInfo(location.state.updateInfo)
|
||||
const phase = location.state.updateInfo.diagnostics?.phase
|
||||
setIsDownloading(phase === 'downloading' || phase === 'installing')
|
||||
if (typeof location.state.updateInfo.diagnostics?.progressPercent === 'number') {
|
||||
setDownloadProgress(location.state.updateInfo.diagnostics.progressPercent)
|
||||
}
|
||||
} else {
|
||||
syncUpdateState()
|
||||
}
|
||||
}, [location.state])
|
||||
|
||||
@@ -2709,7 +2779,7 @@ function SettingsPage() {
|
||||
{updateInfo?.hasUpdate ? (
|
||||
<>
|
||||
<p className="update-hint">
|
||||
{updateInfo.forceUpdate ? '检测到强制更新' : `新版本 v${updateInfo.version} 可用`}
|
||||
{isDownloading ? `正在下载 v${updateInfo.version}` : updateInfo.forceUpdate ? '检测到强制更新' : `新版本 v${updateInfo.version} 可用`}
|
||||
</p>
|
||||
<p className="update-hint">
|
||||
更新来源:{updateInfo.updateSource === 'github' ? 'GitHub Release' : '未知'} / 策略来源:
|
||||
@@ -2730,19 +2800,28 @@ function SettingsPage() {
|
||||
)}
|
||||
{isDownloading ? (
|
||||
<div className="download-progress">
|
||||
<div className="progress-bar">
|
||||
<div className="progress-fill" style={{ width: `${downloadProgress}%` }} />
|
||||
<div className="progress-main">
|
||||
<div className="progress-bar">
|
||||
<div className="progress-fill" style={{ width: `${downloadProgress}%` }} />
|
||||
</div>
|
||||
<span>{downloadProgress.toFixed(0)}%</span>
|
||||
</div>
|
||||
<div className="progress-meta">
|
||||
<span>
|
||||
{formatFileSize(downloadProgressDetail?.transferred ?? updateInfo.diagnostics?.downloadedBytes ?? 0)} / {formatFileSize(downloadProgressDetail?.total ?? updateInfo.diagnostics?.totalBytes ?? 0)}
|
||||
</span>
|
||||
<span>速度 {formatSpeed(downloadProgressDetail?.bytesPerSecond ?? 0)}</span>
|
||||
{updateInfo.diagnostics?.fallbackToFull ? <span>已回退全量下载</span> : null}
|
||||
</div>
|
||||
<span>{downloadProgress.toFixed(0)}%</span>
|
||||
</div>
|
||||
) : (
|
||||
<button className="btn btn-primary" onClick={handleUpdateNow}>
|
||||
<button className="btn btn-primary" onClick={handleUpdateNow} disabled={isDownloading}>
|
||||
<Download size={16} /> 立即更新
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<button className="btn btn-secondary" onClick={handleCheckUpdate} disabled={isCheckingUpdate}>
|
||||
<button className="btn btn-secondary" onClick={handleCheckUpdate} disabled={isCheckingUpdate || isDownloading}>
|
||||
<RefreshCw size={16} className={isCheckingUpdate ? 'spin' : ''} />
|
||||
{isCheckingUpdate ? '检查中...' : '检查更新'}
|
||||
</button>
|
||||
|
||||
Vendored
+8
-1
@@ -12,6 +12,13 @@ export interface ImageViewerOpenOptions {
|
||||
imageDatName?: string
|
||||
}
|
||||
|
||||
export interface UpdateDownloadProgressPayload {
|
||||
percent: number
|
||||
transferred: number
|
||||
total: number
|
||||
bytesPerSecond: number
|
||||
}
|
||||
|
||||
export interface ElectronAPI {
|
||||
window: {
|
||||
minimize: () => void
|
||||
@@ -146,7 +153,7 @@ export interface ElectronAPI {
|
||||
downloadAndInstall: () => Promise<void>
|
||||
getStartupDbConnected?: () => Promise<boolean>
|
||||
setAppIcon: (iconName: string) => Promise<void>
|
||||
onDownloadProgress: (callback: (progress: number) => void) => () => void
|
||||
onDownloadProgress: (callback: (progress: UpdateDownloadProgressPayload) => void) => () => void
|
||||
onUpdateAvailable: (callback: (info: {
|
||||
hasUpdate: boolean
|
||||
forceUpdate: boolean
|
||||
|
||||
Reference in New Issue
Block a user