Private
Public Access
1
0

新增:版本更新管理功能,优化代码架构

This commit is contained in:
2026-01-25 18:06:16 +08:00
parent 652f5e5d60
commit cc50de0323
11 changed files with 1887 additions and 42 deletions

View File

@@ -9,6 +9,13 @@
<a-tab-pane key="device" title="设备调用测试"/>
</a-tabs>
<div class="header-actions">
<a-tooltip content="版本更新">
<a-button type="text" @click="showUpdateModal = true">
<template #icon>
<icon-sync />
</template>
</a-button>
</a-tooltip>
<ThemeToggle />
</div>
</div>
@@ -83,6 +90,16 @@
<!-- 设备调用测试页面 -->
<DeviceTest v-if="activeTab === 'device'"/>
</a-layout-content>
<!-- 版本更新模态框 -->
<a-modal
v-model:visible="showUpdateModal"
title="版本更新"
width="800px"
:footer="false"
>
<UpdatePanel />
</a-modal>
</a-layout>
</template>
@@ -92,8 +109,10 @@ import {Message} from '@arco-design/web-vue'
import DeviceTest from './components/DeviceTest.vue'
import DbCli from './views/db-cli/index.vue'
import ThemeToggle from './components/ThemeToggle.vue'
import UpdatePanel from './components/UpdatePanel.vue'
const activeTab = ref('db-cli')
const showUpdateModal = ref(false)
const loading = ref(false)
const formModel = ref({
keyword: '',

View File

@@ -53,10 +53,12 @@
<a-card class="test-card" title="文件系统操作">
<a-space direction="vertical" :size="16" style="width: 100%">
<a-input-group>
<a-input
<a-auto-complete
v-model="filePath"
:data="pathHistory"
placeholder="输入文件或目录路径"
style="flex: 1"
@select="onPathSelect"
/>
<a-button @click="browseDirectory">浏览</a-button>
<a-button type="primary" @click="listDirectory">列出目录</a-button>
@@ -124,8 +126,8 @@
</template>
<script setup>
import {computed, onMounted, ref} from 'vue'
import {Message} from '@arco-design/web-vue'
import {computed, onMounted, ref, watch} from 'vue'
import {Message, Modal} from '@arco-design/web-vue'
import {
getSystemInfo,
getCPUInfo,
@@ -138,6 +140,14 @@ import {
getEnvVars
} from '@/api'
// localStorage 键名
const STORAGE_KEYS = {
FILE_PATH: 'device-test-file-path',
FILE_LIST: 'device-test-file-list',
FILE_CONTENT: 'device-test-file-content',
PATH_HISTORY: 'device-test-path-history'
}
const systemInfo = ref(null)
const cpuInfo = ref(null)
const memoryInfo = ref(null)
@@ -148,6 +158,7 @@ const fileList = ref([])
const fileLoading = ref(false)
const envVars = ref(null)
const envLoading = ref(false)
const pathHistory = ref([]) // 路径历史记录
const diskColumns = [
{title: '设备', dataIndex: 'device', width: 120},
@@ -188,6 +199,10 @@ const listDirectory = async () => {
Message.error('请输入目录路径')
return
}
// 添加到历史记录
addToHistory(filePath.value)
fileLoading.value = true
try {
fileList.value = await listDir(filePath.value)
@@ -199,11 +214,21 @@ const listDirectory = async () => {
}
}
// 自动完成选择处理
const onPathSelect = (value) => {
filePath.value = value
listDirectory()
}
const readFile = async () => {
if (!filePath.value) {
Message.error('请输入文件路径')
return
}
// 添加到历史记录
addToHistory(filePath.value)
fileLoading.value = true
try {
fileContent.value = await readFileApi(filePath.value)
@@ -237,26 +262,31 @@ const deleteFile = async () => {
Message.error('请输入文件路径')
return
}
if (!confirm('确定要删除 ' + filePath.value + ' 吗?')) {
return
}
fileLoading.value = true
try {
await deletePath(filePath.value)
Message.success('删除成功')
filePath.value = ''
fileContent.value = ''
fileList.value = []
} catch (error) {
console.error('删除失败:', error)
Message.error('删除失败: ' + (error.message || error))
} finally {
fileLoading.value = false
}
Modal.confirm({
title: '确认删除',
content: `确定要删除 ${filePath.value} 吗?`,
onOk: async () => {
fileLoading.value = true
try {
await deletePath(filePath.value)
Message.success('删除成功')
filePath.value = ''
fileContent.value = ''
fileList.value = []
} catch (error) {
console.error('删除失败:', error)
Message.error('删除失败: ' + (error.message || error))
} finally {
fileLoading.value = false
}
}
})
}
const selectFile = (path) => {
filePath.value = path
addToHistory(path)
}
const browseDirectory = () => {
@@ -287,7 +317,72 @@ const formatBytes = (bytes) => {
return (bytes / Math.pow(unit, exp)).toFixed(2) + ' ' + 'KMGTPE'[exp - 1] + 'B'
}
// 从 localStorage 加载数据
const loadFromStorage = () => {
try {
const savedPath = localStorage.getItem(STORAGE_KEYS.FILE_PATH)
const savedFileList = localStorage.getItem(STORAGE_KEYS.FILE_LIST)
const savedFileContent = localStorage.getItem(STORAGE_KEYS.FILE_CONTENT)
const savedHistory = localStorage.getItem(STORAGE_KEYS.PATH_HISTORY)
if (savedPath) filePath.value = savedPath
if (savedFileList) fileList.value = JSON.parse(savedFileList)
if (savedFileContent) fileContent.value = savedFileContent
if (savedHistory) pathHistory.value = JSON.parse(savedHistory)
} catch (error) {
console.error('从 localStorage 加载数据失败:', error)
}
}
// 保存到 localStorage
const saveToStorage = (key, value) => {
try {
if (typeof value === 'string') {
localStorage.setItem(key, value)
} else {
localStorage.setItem(key, JSON.stringify(value))
}
} catch (error) {
console.error('保存到 localStorage 失败:', error)
}
}
// 添加到历史记录
const addToHistory = (path) => {
if (!path || path.trim() === '') return
// 移除重复项
const index = pathHistory.value.indexOf(path)
if (index > -1) {
pathHistory.value.splice(index, 1)
}
// 添加到开头
pathHistory.value.unshift(path)
// 限制历史记录数量
if (pathHistory.value.length > 20) {
pathHistory.value = pathHistory.value.slice(0, 20)
}
saveToStorage(STORAGE_KEYS.PATH_HISTORY, pathHistory.value)
}
// 监听数据变化自动保存
watch(filePath, (newPath) => {
saveToStorage(STORAGE_KEYS.FILE_PATH, newPath)
})
watch(fileContent, (newContent) => {
saveToStorage(STORAGE_KEYS.FILE_CONTENT, newContent)
})
watch(fileList, (newList) => {
saveToStorage(STORAGE_KEYS.FILE_LIST, newList)
}, { deep: true })
onMounted(() => {
loadFromStorage()
refreshSystemInfo()
})
</script>

View 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>