新增:版本更新管理功能,优化代码架构
This commit is contained in:
427
web/src/components/UpdatePanel.vue
Normal file
427
web/src/components/UpdatePanel.vue
Normal file
@@ -0,0 +1,427 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user