Private
Public Access
1
0
Files
u-desk/web/src/components/UpdatePanel.vue

428 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="update-panel">
<a-card title="版本更新">
<a-space direction="vertical" style="width: 100%" :size="16">
<!-- 当前版本信息 -->
<a-descriptions :column="3" bordered>
<a-descriptions-item label="当前版本">{{ currentVersion }}</a-descriptions-item>
<a-descriptions-item label="最后检查">{{ lastCheckTime }}</a-descriptions-item>
<a-descriptions-item label="自动检查">
<a-tag :color="config.auto_check_enabled ? 'green' : 'gray'">
{{ config.auto_check_enabled ? '已开启' : '已关闭' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
<!-- 检查更新 -->
<a-space>
<a-button type="primary" @click="handleCheckUpdate" :loading="checking">
<template #icon>
<icon-search />
</template>
检查更新
</a-button>
<a-button @click="showConfig = true">
<template #icon>
<icon-settings />
</template>
更新设置
</a-button>
</a-space>
<!-- 更新信息 -->
<a-alert
v-if="updateInfo"
:type="updateInfo.has_update ? 'info' : 'success'"
style="margin-top: 16px"
>
<template #title>
{{ updateInfo.has_update ? '发现新版本' : '已是最新版本' }}
</template>
<div v-if="updateInfo.has_update">
<p><strong>最新版本</strong>{{ updateInfo.latest_version }}</p>
<p><strong>当前版本</strong>{{ updateInfo.current_version }}</p>
<p v-if="updateInfo.changelog"><strong>更新日志</strong></p>
<div v-if="updateInfo.changelog" class="changelog">{{ updateInfo.changelog }}</div>
<p><strong>发布日期</strong>{{ updateInfo.release_date }}</p>
<a-space style="margin-top: 12px">
<a-button
type="primary"
@click="handleDownload"
:loading="downloading"
:disabled="!!downloadProgress"
>
<template #icon>
<icon-download />
</template>
{{ downloadProgress > 0 ? '下载中...' : '下载更新' }}
</a-button>
<a-button
v-if="downloadedFile"
type="primary"
status="success"
@click="handleInstall"
:loading="installing"
>
<template #icon>
<icon-check-circle />
</template>
立即安装
</a-button>
</a-space>
</div>
</a-alert>
<!-- 下载进度 -->
<div v-if="downloadProgress > 0 || downloading" class="download-progress">
<a-progress
:percent="downloadProgress"
:status="downloadStatus"
/>
<div class="progress-info">
<span>{{ formatFileSize(progressInfo.downloaded) }} / {{ formatFileSize(progressInfo.total) }}</span>
<span v-if="progressInfo.speed > 0">{{ formatSpeed(progressInfo.speed) }}</span>
</div>
</div>
<!-- 安装结果 -->
<a-alert
v-if="installResult"
:type="installResult.success ? 'success' : 'error'"
style="margin-top: 16px"
>
<template #title>{{ installResult.success ? '安装成功' : '安装失败' }}</template>
<p>{{ installResult.message }}</p>
</a-alert>
</a-space>
</a-card>
<!-- 更新设置对话框 -->
<a-modal
v-model:visible="showConfig"
title="更新设置"
@ok="handleSaveConfig"
@cancel="handleCancelConfig"
:confirm-loading="saving"
>
<a-form :model="config" layout="vertical">
<a-form-item label="自动检查更新" field="auto_check_enabled">
<a-switch v-model="config.auto_check_enabled" />
<span style="margin-left: 8px">{{ config.auto_check_enabled ? '开启' : '关闭' }}</span>
</a-form-item>
<a-form-item label="检查间隔(分钟)" field="check_interval_minutes">
<a-input-number
v-model="config.check_interval_minutes"
:min="1"
:max="1440"
style="width: 200px"
/>
</a-form-item>
<a-form-item label="更新检查地址" field="check_url">
<a-input
v-model="config.check_url"
placeholder="https://example.com/version.json"
/>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
// 工具函数:解析事件数据
const parseEventData = (event) => {
try {
return typeof event === 'string' ? JSON.parse(event) : event
} catch {
return {}
}
}
// 状态
const currentVersion = ref('-')
const lastCheckTime = ref('-')
const checking = ref(false)
const downloading = ref(false)
const installing = ref(false)
const saving = ref(false)
const updateInfo = ref(null)
const downloadedFile = ref(null)
const installResult = ref(null)
const showConfig = ref(false)
const downloadProgress = ref(0)
const downloadStatus = ref('active')
// 配置
const config = ref({
auto_check_enabled: true,
check_interval_minutes: 60,
check_url: ''
})
// 下载进度信息
const progressInfo = ref({
progress: 0,
speed: 0,
downloaded: 0,
total: 0
})
// 格式化文件大小
const formatFileSize = (bytes) => {
if (!bytes || 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 Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
}
// 格式化速度
const formatSpeed = (bytesPerSecond) => {
return formatFileSize(bytesPerSecond) + '/s'
}
// 加载当前版本
const loadCurrentVersion = async () => {
try {
const result = await window.go.main.App.GetCurrentVersion()
if (result.success) {
currentVersion.value = result.data?.version || '-'
}
} catch (error) {
console.error('获取版本失败:', error)
}
}
// 加载配置
const loadConfig = async () => {
try {
const result = await window.go.main.App.GetUpdateConfig()
if (result.success) {
config.value = {
auto_check_enabled: result.data.auto_check_enabled || false,
check_interval_minutes: result.data.check_interval_minutes || 60,
check_url: result.data.check_url || ''
}
lastCheckTime.value = result.data.last_check_time || '-'
}
} catch (error) {
console.error('加载配置失败:', error)
}
}
// 检查更新
const handleCheckUpdate = async () => {
checking.value = true
updateInfo.value = null
installResult.value = null
try {
const result = await window.go.main.App.CheckUpdate()
if (result.success) {
updateInfo.value = result.data
if (result.data.has_update) {
Message.success('发现新版本!')
} else {
Message.success('已是最新版本')
}
} else {
Message.error(result.message || '检查更新失败')
}
} catch (error) {
console.error('检查更新失败:', error)
Message.error('检查更新失败:' + (error.message || error))
} finally {
checking.value = false
// 刷新最后检查时间
await loadConfig()
}
}
// 下载更新
const handleDownload = async () => {
if (!updateInfo.value?.download_url) {
Message.warning('下载地址不存在')
return
}
downloading.value = true
downloadProgress.value = 0
downloadStatus.value = 'active'
progressInfo.value = { progress: 0, speed: 0, downloaded: 0, total: 0 }
installResult.value = null
try {
const result = await window.go.main.App.DownloadUpdate(updateInfo.value.download_url)
if (result.success) {
Message.success('下载请求已发送')
} else {
downloadStatus.value = 'exception'
Message.error(result.message || '下载启动失败')
downloading.value = false
}
} catch (error) {
console.error('下载失败:', error)
downloadStatus.value = 'exception'
Message.error('下载失败:' + (error.message || error))
downloading.value = false
}
}
// 安装更新
const handleInstall = async () => {
if (!downloadedFile.value) {
Message.warning('请先下载更新包')
return
}
// 确认对话框
Modal.confirm({
title: '确认安装',
content: '安装更新后应用将自动重启,是否继续?',
onOk: async () => {
installing.value = true
installResult.value = null
try {
const result = await window.go.main.App.InstallUpdate(
downloadedFile.value,
true // 自动重启
)
installResult.value = result.data || result
if (result.success || result.data?.success) {
Message.success({
content: '安装成功!应用将在几秒后重启...',
duration: 3000
})
} else {
Message.error(result.message || '安装失败')
}
} catch (error) {
console.error('安装失败:', error)
installResult.value = {
success: false,
message: '安装失败:' + (error.message || error)
}
Message.error('安装失败:' + (error.message || error))
} finally {
installing.value = false
}
}
})
}
// 保存配置
const handleSaveConfig = async () => {
saving.value = true
try {
const result = await window.go.main.App.SetUpdateConfig(
config.value.auto_check_enabled,
config.value.check_interval_minutes,
config.value.check_url
)
if (result.success) {
Message.success('配置保存成功')
showConfig.value = false
await loadConfig()
} else {
Message.error(result.message || '保存配置失败')
}
} catch (error) {
console.error('保存配置失败:', error)
Message.error('保存配置失败:' + (error.message || error))
} finally {
saving.value = false
}
}
// 取消配置
const handleCancelConfig = () => {
showConfig.value = false
// 恢复原始配置
loadConfig()
}
// 监听下载进度事件
const onDownloadProgress = (event) => {
const data = parseEventData(event)
progressInfo.value = {
progress: data.progress || 0,
speed: data.speed || 0,
downloaded: data.downloaded || 0,
total: data.total || 0
}
downloadProgress.value = Math.round(data.progress || 0)
}
// 监听下载完成事件
const onDownloadComplete = (event) => {
downloading.value = false
const data = parseEventData(event)
if (data.error) {
downloadStatus.value = 'exception'
Message.error('下载失败:' + data.error)
} else if (data.success) {
downloadStatus.value = 'success'
downloadProgress.value = 100
downloadedFile.value = data.file_path
Message.success('下载完成!文件已保存到:' + data.file_path)
}
}
onMounted(async () => {
await loadCurrentVersion()
await loadConfig()
// 监听下载进度事件
window.EventsOn('download-progress', onDownloadProgress)
window.EventsOn('download-complete', onDownloadComplete)
})
onUnmounted(() => {
// 取消事件监听
window.EventsOff('download-progress')
window.EventsOff('download-complete')
})
</script>
<style scoped>
.update-panel {
max-width: 900px;
margin: 0 auto;
}
.changelog {
background: var(--color-fill-2);
padding: 12px;
border-radius: 4px;
white-space: pre-wrap;
margin: 8px 0;
max-height: 200px;
overflow-y: auto;
}
.download-progress {
margin-top: 16px;
padding: 16px;
background: var(--color-fill-1);
border-radius: 4px;
}
.progress-info {
display: flex;
justify-content: space-between;
margin-top: 8px;
font-size: 12px;
color: var(--color-text-3);
}
</style>