大哥 主播 即时消息 三合一
This commit is contained in:
311
src/pages/UpdateChecker.vue
Normal file
311
src/pages/UpdateChecker.vue
Normal file
@@ -0,0 +1,311 @@
|
||||
<template>
|
||||
<!-- 非 Electron 环境不渲染 -->
|
||||
<div v-if="isElectronEnv"
|
||||
class="min-h-screen bg-gradient-to-br from-slate-100 to-slate-200 flex items-center justify-center p-6">
|
||||
<!-- 背景装饰 -->
|
||||
<div class="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div class="absolute -top-40 -right-40 w-96 h-96 bg-blue-100 rounded-full blur-3xl opacity-50" />
|
||||
<div class="absolute -bottom-40 -left-40 w-96 h-96 bg-purple-100 rounded-full blur-3xl opacity-50" />
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 w-full max-w-md">
|
||||
<!-- Logo 和标题 -->
|
||||
<div class="text-center mb-8">
|
||||
<div
|
||||
class="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-blue-500 to-blue-600 rounded-2xl flex items-center justify-center shadow-lg">
|
||||
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">应用更新检查</h1>
|
||||
<p class="text-gray-500 text-sm">当前版本: v{{ currentVersion || '...' }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 更新卡片 -->
|
||||
<div class="bg-white rounded-2xl border border-gray-100 shadow-xl p-6">
|
||||
|
||||
<!-- 检查中 -->
|
||||
<div v-if="status === 'checking' && !showTimeoutError" class="text-center py-8">
|
||||
<div
|
||||
class="w-12 h-12 mx-auto mb-4 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
||||
<p class="text-gray-900 font-medium">正在检查更新...</p>
|
||||
<p class="text-gray-500 text-sm mt-2">
|
||||
{{ retryCount > 0 ? `第 ${retryCount}/${MAX_RETRIES} 次重试...` : '请稍候' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 超时错误 -->
|
||||
<div v-if="showTimeoutError" class="space-y-6 py-4">
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="w-16 h-16 mx-auto mb-4 bg-orange-50 rounded-full flex items-center justify-center">
|
||||
<svg class="w-8 h-8 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-xl font-bold text-gray-900">检查更新超时</h2>
|
||||
<p class="text-orange-600 text-sm mt-2">无法连接到更新服务器</p>
|
||||
</div>
|
||||
|
||||
<button @click="handleRetry"
|
||||
class="w-full py-3 bg-gradient-to-r from-blue-600 to-blue-500 text-white rounded-lg font-medium hover:from-blue-700 hover:to-blue-600 transition-all shadow-sm">
|
||||
🔄 重新检查
|
||||
</button>
|
||||
|
||||
<p class="text-center text-gray-400 text-xs">
|
||||
请检查网络连接后重试
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 发现新版本 -->
|
||||
<div v-if="status === 'available' && updateInfo" class="space-y-6">
|
||||
<div class="text-center">
|
||||
<div class="w-16 h-16 mx-auto mb-4 bg-green-50 rounded-full flex items-center justify-center">
|
||||
<svg class="w-8 h-8 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-xl font-bold text-gray-900">发现新版本</h2>
|
||||
<p class="text-green-600 font-mono mt-2">v{{ updateInfo.version }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="updateInfo.releaseNotes" class="bg-gray-50 rounded-lg p-3 max-h-32 overflow-y-auto">
|
||||
<p class="text-gray-600 text-sm whitespace-pre-wrap">{{ updateInfo.releaseNotes }}</p>
|
||||
</div>
|
||||
|
||||
<button @click="downloadUpdate"
|
||||
class="w-full py-3 bg-gradient-to-r from-blue-600 to-blue-500 text-white rounded-lg font-medium hover:from-blue-700 hover:to-blue-600 transition-all shadow-sm">
|
||||
立即下载更新
|
||||
</button>
|
||||
|
||||
<p class="text-center text-gray-400 text-xs">
|
||||
必须更新后才能使用程序
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 下载中 -->
|
||||
<div v-if="status === 'downloading' && progress" class="space-y-6 py-4">
|
||||
<div class="text-center">
|
||||
<p class="text-gray-900 font-medium mb-1">正在下载更新</p>
|
||||
<p class="text-4xl font-bold text-blue-600">{{ progress.percent.toFixed(0) }}%</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="h-3 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-gradient-to-r from-blue-500 to-blue-600 transition-all duration-300 rounded-full"
|
||||
:style="{ width: `${progress.percent}%` }" />
|
||||
</div>
|
||||
<div class="flex justify-between text-xs text-gray-500">
|
||||
<span>{{ formatBytes(progress.transferred) }} / {{ formatBytes(progress.total) }}</span>
|
||||
<span>{{ formatBytes(progress.bytesPerSecond) }}/s</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-center text-gray-400 text-sm">
|
||||
请勿关闭程序...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 下载完成 - 自动重启安装 -->
|
||||
<div v-if="status === 'downloaded'" class="space-y-6 py-4">
|
||||
<div class="text-center">
|
||||
<div class="w-16 h-16 mx-auto mb-4 bg-green-50 rounded-full flex items-center justify-center">
|
||||
<svg class="w-8 h-8 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-xl font-bold text-gray-900">下载完成</h2>
|
||||
<p class="text-green-600 text-sm mt-2">
|
||||
{{ countdown > 0 ? `${countdown} 秒后自动重启安装...` : '正在重启安装...' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button @click="installUpdate"
|
||||
class="w-full py-3 bg-gradient-to-r from-green-500 to-green-600 text-white rounded-lg font-medium hover:from-green-600 hover:to-green-700 transition-all shadow-sm">
|
||||
🚀 立即重启安装
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 错误 -->
|
||||
<div v-if="status === 'error'" class="space-y-6 py-4">
|
||||
<div class="text-center">
|
||||
<div class="w-16 h-16 mx-auto mb-4 bg-red-50 rounded-full flex items-center justify-center">
|
||||
<svg class="w-8 h-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-xl font-bold text-gray-900">检查更新失败</h2>
|
||||
<p class="text-red-500 text-sm mt-2">{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button @click="startCheck"
|
||||
class="flex-1 py-3 bg-gradient-to-r from-blue-600 to-blue-500 text-white rounded-lg font-medium hover:from-blue-700 hover:to-blue-600 transition-all shadow-sm">
|
||||
🔄 重试
|
||||
</button>
|
||||
<button @click="emit('ready')"
|
||||
class="flex-1 py-3 bg-gray-100 text-gray-600 rounded-lg font-medium hover:bg-gray-200 transition-all border border-gray-200">
|
||||
跳过继续
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-center text-gray-400 text-xs">
|
||||
更新检查失败不影响正常使用
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部版权 -->
|
||||
<p class="text-center text-gray-400 text-xs mt-6">
|
||||
© 2025 Yolo
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useUpdate } from '../hooks/useUpdate'
|
||||
import { isElectron } from '../utils/electronBridge'
|
||||
|
||||
const emit = defineEmits(['ready'])
|
||||
|
||||
const CHECK_TIMEOUT = 15000 // 15秒超时
|
||||
const MAX_RETRIES = 3 // 最大重试次数
|
||||
const AUTO_INSTALL_DELAY = 3 // 自动安装倒计时秒数
|
||||
|
||||
const {
|
||||
status,
|
||||
updateInfo,
|
||||
progress,
|
||||
error,
|
||||
currentVersion,
|
||||
checkForUpdates,
|
||||
downloadUpdate,
|
||||
installUpdate
|
||||
} = useUpdate()
|
||||
|
||||
const isElectronEnv = isElectron()
|
||||
|
||||
const checkComplete = ref(false)
|
||||
const retryCount = ref(0)
|
||||
const isTimeout = ref(false)
|
||||
const showTimeoutError = ref(false)
|
||||
const countdown = ref(AUTO_INSTALL_DELAY)
|
||||
|
||||
let timeoutTimer = null
|
||||
let hasStarted = false
|
||||
let countdownTimer = null
|
||||
|
||||
// 非 Electron 环境直接 ready
|
||||
onMounted(() => {
|
||||
if (!isElectronEnv) {
|
||||
emit('ready')
|
||||
return
|
||||
}
|
||||
// 启动检查
|
||||
if (!hasStarted) {
|
||||
hasStarted = true
|
||||
startCheck()
|
||||
}
|
||||
})
|
||||
|
||||
// 监听状态
|
||||
watch(status, (newStatus) => {
|
||||
if (newStatus !== 'checking' && timeoutTimer) {
|
||||
clearTimeout(timeoutTimer)
|
||||
timeoutTimer = null
|
||||
isTimeout.value = false
|
||||
}
|
||||
|
||||
if (newStatus === 'idle' && checkComplete.value) {
|
||||
emit('ready')
|
||||
}
|
||||
|
||||
if (newStatus !== 'checking' && newStatus !== 'idle') {
|
||||
checkComplete.value = true
|
||||
}
|
||||
if (newStatus === 'idle') {
|
||||
setTimeout(() => {
|
||||
checkComplete.value = true
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// Auto install timer
|
||||
if (newStatus === 'downloaded') {
|
||||
startCountdown()
|
||||
}
|
||||
})
|
||||
|
||||
watch(isTimeout, (val) => {
|
||||
if (val) {
|
||||
if (retryCount.value >= MAX_RETRIES) {
|
||||
console.log('[UpdateChecker] 更新检查超时,显示错误')
|
||||
showTimeoutError.value = true
|
||||
} else if (retryCount.value > 0) {
|
||||
setTimeout(() => {
|
||||
startCheck()
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
watch(checkComplete, (val) => {
|
||||
if (val && status.value === 'idle') {
|
||||
emit('ready')
|
||||
}
|
||||
})
|
||||
|
||||
function startCheck() {
|
||||
if (!isElectronEnv) return
|
||||
|
||||
isTimeout.value = false
|
||||
checkForUpdates()
|
||||
|
||||
timeoutTimer = setTimeout(() => {
|
||||
if (status.value === 'checking') {
|
||||
isTimeout.value = true
|
||||
if (retryCount.value < MAX_RETRIES) {
|
||||
retryCount.value++
|
||||
}
|
||||
}
|
||||
}, CHECK_TIMEOUT)
|
||||
}
|
||||
|
||||
function handleRetry() {
|
||||
showTimeoutError.value = false
|
||||
retryCount.value = 0
|
||||
hasStarted = false
|
||||
startCheck()
|
||||
}
|
||||
|
||||
function startCountdown() {
|
||||
if (countdownTimer) clearInterval(countdownTimer)
|
||||
countdown.value = AUTO_INSTALL_DELAY
|
||||
countdownTimer = setInterval(() => {
|
||||
countdown.value--
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(countdownTimer)
|
||||
installUpdate()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeoutTimer) clearTimeout(timeoutTimer)
|
||||
if (countdownTimer) clearInterval(countdownTimer)
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user