|
@@ -1,5 +1,7 @@
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
|
|
|
+import { Spin } from 'ant-design-vue'
|
|
|
|
|
+import { CheckCircleOutlined, InfoCircleOutlined, CloseCircleOutlined } from '@ant-design/icons-vue'
|
|
|
|
|
|
|
|
type UpdateState = 'idle' | 'checking' | 'available' | 'not-available' | 'downloading' | 'downloaded' | 'error'
|
|
type UpdateState = 'idle' | 'checking' | 'available' | 'not-available' | 'downloading' | 'downloaded' | 'error'
|
|
|
|
|
|
|
@@ -9,8 +11,14 @@ const percent = ref(0)
|
|
|
const errorMsg = ref('')
|
|
const errorMsg = ref('')
|
|
|
const visible = ref(false)
|
|
const visible = ref(false)
|
|
|
|
|
|
|
|
|
|
+let notAvailableTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
|
|
+
|
|
|
function onStatus(_event: any, data: any) {
|
|
function onStatus(_event: any, data: any) {
|
|
|
state.value = data.status
|
|
state.value = data.status
|
|
|
|
|
+ if (notAvailableTimer) {
|
|
|
|
|
+ clearTimeout(notAvailableTimer)
|
|
|
|
|
+ notAvailableTimer = null
|
|
|
|
|
+ }
|
|
|
if (data.status === 'available') {
|
|
if (data.status === 'available') {
|
|
|
version.value = data.version || ''
|
|
version.value = data.version || ''
|
|
|
visible.value = true
|
|
visible.value = true
|
|
@@ -23,9 +31,12 @@ function onStatus(_event: any, data: any) {
|
|
|
errorMsg.value = data.message || '更新失败'
|
|
errorMsg.value = data.message || '更新失败'
|
|
|
visible.value = true
|
|
visible.value = true
|
|
|
} else if (data.status === 'not-available') {
|
|
} else if (data.status === 'not-available') {
|
|
|
- visible.value = false
|
|
|
|
|
|
|
+ visible.value = true
|
|
|
|
|
+ notAvailableTimer = setTimeout(() => {
|
|
|
|
|
+ visible.value = false
|
|
|
|
|
+ }, 5000)
|
|
|
} else if (data.status === 'checking') {
|
|
} else if (data.status === 'checking') {
|
|
|
- visible.value = false
|
|
|
|
|
|
|
+ visible.value = true
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -51,33 +62,49 @@ onUnmounted(() => {
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
<template>
|
|
|
- <div v-if="visible" class="update-status">
|
|
|
|
|
- <!-- 有新版本可用 -->
|
|
|
|
|
- <template v-if="state === 'available'">
|
|
|
|
|
- <span class="update-text">新版本 v{{ version }} 可用</span>
|
|
|
|
|
- <button class="update-btn" @click="startDownload">下载</button>
|
|
|
|
|
- <button class="update-btn dismiss" @click="dismiss">忽略</button>
|
|
|
|
|
- </template>
|
|
|
|
|
- <!-- 下载中 -->
|
|
|
|
|
- <template v-else-if="state === 'downloading'">
|
|
|
|
|
- <span class="update-text">下载中 {{ percent }}%</span>
|
|
|
|
|
- <div class="progress-bar">
|
|
|
|
|
- <div class="progress-fill" :style="{ width: percent + '%' }"></div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- <!-- 下载完成 -->
|
|
|
|
|
- <template v-else-if="state === 'downloaded'">
|
|
|
|
|
- <span class="update-text">下载完成,重启以安装</span>
|
|
|
|
|
- <button class="update-btn" @click="installNow">立即重启</button>
|
|
|
|
|
- <button class="update-btn dismiss" @click="dismiss">稍后</button>
|
|
|
|
|
- </template>
|
|
|
|
|
- <!-- 错误 -->
|
|
|
|
|
- <template v-else-if="state === 'error'">
|
|
|
|
|
- <span class="update-text error">更新失败: {{ errorMsg }}</span>
|
|
|
|
|
- <button class="update-btn" @click="checkUpdate">重试</button>
|
|
|
|
|
- <button class="update-btn dismiss" @click="dismiss">关闭</button>
|
|
|
|
|
- </template>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <Transition name="update-slide">
|
|
|
|
|
+ <div v-if="visible" class="update-status">
|
|
|
|
|
+ <!-- 正在检查 -->
|
|
|
|
|
+ <template v-if="state === 'checking'">
|
|
|
|
|
+ <Spin size="small" />
|
|
|
|
|
+ <span class="update-text">正在检查更新</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 已是最新版 -->
|
|
|
|
|
+ <template v-else-if="state === 'not-available'">
|
|
|
|
|
+ <CheckCircleOutlined />
|
|
|
|
|
+ <span class="update-text">已经是最新版</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 有新版本可用 -->
|
|
|
|
|
+ <template v-if="state === 'available'">
|
|
|
|
|
+ <InfoCircleOutlined />
|
|
|
|
|
+ <span class="update-text">新版本 v{{ version }} 可用</span>
|
|
|
|
|
+ <button class="update-btn" @click="startDownload">下载</button>
|
|
|
|
|
+ <button class="update-btn dismiss" @click="dismiss">忽略</button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 下载中 -->
|
|
|
|
|
+ <template v-else-if="state === 'downloading'">
|
|
|
|
|
+ <Spin size="small" />
|
|
|
|
|
+ <span class="update-text">下载中 {{ percent }}%</span>
|
|
|
|
|
+ <div class="progress-bar">
|
|
|
|
|
+ <div class="progress-fill" :style="{ width: percent + '%' }"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 下载完成 -->
|
|
|
|
|
+ <template v-else-if="state === 'downloaded'">
|
|
|
|
|
+ <InfoCircleOutlined />
|
|
|
|
|
+ <span class="update-text">更新下载完成,重启以安装</span>
|
|
|
|
|
+ <button class="update-btn" @click="installNow">立即重启</button>
|
|
|
|
|
+ <button class="update-btn dismiss" @click="dismiss">稍后</button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 错误 -->
|
|
|
|
|
+ <template v-else-if="state === 'error'">
|
|
|
|
|
+ <CloseCircleOutlined />
|
|
|
|
|
+ <span class="update-text error">更新失败: {{ errorMsg }}</span>
|
|
|
|
|
+ <button class="update-btn" @click="checkUpdate">重试</button>
|
|
|
|
|
+ <button class="update-btn dismiss" @click="dismiss">关闭</button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Transition>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
<style scoped lang="scss">
|
|
@@ -87,13 +114,14 @@ onUnmounted(() => {
|
|
|
gap: 8px;
|
|
gap: 8px;
|
|
|
padding: 8px 12px;
|
|
padding: 8px 12px;
|
|
|
margin: 0 12px 8px;
|
|
margin: 0 12px 8px;
|
|
|
- background: #1a3a5c;
|
|
|
|
|
|
|
+ background: #ebebeb;
|
|
|
border-radius: 6px;
|
|
border-radius: 6px;
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
|
flex-wrap: wrap;
|
|
flex-wrap: wrap;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
}
|
|
}
|
|
|
.update-text {
|
|
.update-text {
|
|
|
- color: #e0e0e0;
|
|
|
|
|
|
|
+ color: #3b3b3b;
|
|
|
&.error {
|
|
&.error {
|
|
|
color: #ff6b6b;
|
|
color: #ff6b6b;
|
|
|
}
|
|
}
|
|
@@ -130,4 +158,23 @@ onUnmounted(() => {
|
|
|
border-radius: 3px;
|
|
border-radius: 3px;
|
|
|
transition: width 0.3s;
|
|
transition: width 0.3s;
|
|
|
}
|
|
}
|
|
|
|
|
+.update-slide-enter-active,
|
|
|
|
|
+.update-slide-leave-active {
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+.update-slide-enter-from,
|
|
|
|
|
+.update-slide-leave-to {
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+ max-height: 0;
|
|
|
|
|
+ margin-top: 0;
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+ padding-top: 0;
|
|
|
|
|
+ padding-bottom: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.update-slide-enter-to,
|
|
|
|
|
+.update-slide-leave-from {
|
|
|
|
|
+ opacity: 1;
|
|
|
|
|
+ max-height: 60px;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|