Private
Public Access
1
0

重构:Wails v3 迁移 + 前端目录规范化 + Sidebar滚动优化

- web/ → frontend/ 目录重命名(Wails v3 标准结构)
- main.go: Middleware 修复 custom.js 404 + DevTools 延迟启动
- Sidebar: 收藏夹内部独立滚动 + 帮助区块固定底部
- useFavorites.ts: longPressTimer const→let 修复 TypeError
- App.vue: Arco Tabs padding-top 覆盖
- build: config.yml / Taskfile.yml 对齐官方模板,devtools build tag
- 新增 v3 bindings、vite.config.js、跨平台构建配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 11:03:53 +08:00
parent 44847e0d40
commit f54bf1c28d
185 changed files with 7768 additions and 914 deletions

View File

@@ -0,0 +1,360 @@
<template>
<div class="update-panel">
<a-space direction="vertical" style="width: 100%" :size="20">
<!-- 当前版本信息 -->
<a-card title="版本信息" :bordered="false">
<template #extra>
<a-button type="text" size="small" @click="$emit('open-version-history')">
<template #icon><icon-history /></template>
版本历史
</a-button>
</template>
<a-row :gutter="16">
<a-col :span="12">
<div class="info-item">
<div class="info-label">当前版本</div>
<div class="info-value">{{ currentVersion }}</div>
</div>
</a-col>
<a-col :span="12">
<div class="info-item">
<div class="info-label">最后检查</div>
<div class="info-value">{{ lastCheckTime }}</div>
</div>
</a-col>
</a-row>
<!-- 更新说明 -->
<div v-if="updateInfo && updateInfo.changelog" class="changelog-section">
<div class="changelog-title">
{{ updateInfo.has_update ? '最新版本更新内容' : '当前版本更新内容' }}
</div>
<div class="changelog" v-html="renderChangelog(updateInfo.changelog)" />
</div>
</a-card>
<!-- 检查更新 -->
<a-card title="检查更新" :bordered="false">
<a-space>
<a-button type="primary" @click="handleCheckUpdate" :loading="checking">
<template #icon>
<icon-search />
</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.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>{{ updateStore.formatFileSize(progressInfo.downloaded) }} / {{ updateStore.formatFileSize(progressInfo.total) }}</span>
<span v-if="progressInfo.speed > 0">{{ updateStore.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-card>
</a-space>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
import { storeToRefs } from 'pinia'
import { IconHistory } from '@arco-design/web-vue/es/icon'
import { useUpdateStore } from '../stores/update'
import { marked } from '../utils/markedExtensions'
import { sanitizeHtml } from '@/utils/fileUtils'
import { GetCurrentVersion, GetUpdateConfig, InstallUpdate } from '../wailsjs/v3-bindings/u-desk/app'
import { On, Off } from '@wailsio/events'
// Emits
defineEmits(['open-version-history'])
// 使用更新管理 store
const updateStore = useUpdateStore()
// 使用 storeToRefs 解构以保持响应性
const { checking, downloading, installing, downloadProgress, downloadStatus, progressInfo, updateInfo } = storeToRefs(updateStore)
// 本地状态
const currentVersion = ref('-')
const lastCheckTime = ref('-')
const installResult = ref(null)
const downloadedFile = ref(null)
/** 渲染 changelogMarkdown → HTML */
function renderChangelog(text: string): string {
if (!text) return ''
try { return sanitizeHtml(marked.parse(text) as string) } catch { return text }
}
// 加载当前版本
const loadCurrentVersion = async () => {
try {
const result = await GetCurrentVersion()
if (!result.success) return
currentVersion.value = result.data?.version || '-'
} catch (error) {
console.error('获取版本失败:', error)
}
}
// 加载配置
const loadConfig = async () => {
try {
const result = await GetUpdateConfig()
if (!result.success) return
const { last_check_time = '-' } = result.data || {}
lastCheckTime.value = last_check_time
} catch (error) {
console.error('加载配置失败:', error)
}
}
// 检查更新
const handleCheckUpdate = async () => {
installResult.value = null
// 使用 store 的检查方法(非静默模式,显示消息)
await updateStore.checkForUpdates(false)
// 刷新最后检查时间
await loadConfig()
}
// 下载更新
const handleDownload = async () => {
// 使用 store 的下载方法,会自动管理状态和事件监听
await updateStore.downloadUpdate()
}
// 安装更新
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 InstallUpdate(downloadedFile.value, true)
installResult.value = result.data || result
const success = result.success || result.data?.success
if (!success) {
Message.error(result.message || '安装失败')
return
}
Message.success({
content: '安装成功!应用将在几秒后重启...',
duration: 3000
})
} catch (error) {
console.error('安装失败:', error)
const errorMsg = '安装失败:' + (error.message || error)
installResult.value = { success: false, message: errorMsg }
Message.error(errorMsg)
} finally {
installing.value = false
}
}
})
}
// 监听下载完成事件(本地覆盖:记录下载文件路径)
const onDownloadComplete = (event) => {
let data: any
try {
data = typeof event === 'string' ? JSON.parse(event) : event
} catch {
return
}
if (data.success && data.file_path) {
downloadedFile.value = data.file_path
}
}
onMounted(async () => {
await loadCurrentVersion()
await loadConfig()
// 监听下载完成事件(仅用于记录文件路径)
On('download-complete', onDownloadComplete)
})
onUnmounted(() => {
Off('download-complete')
})
</script>
<style scoped>
.update-panel {
max-width: 900px;
margin: 0 auto;
}
.info-item {
padding: 12px;
background: var(--color-fill-1);
border-radius: 6px;
text-align: center;
}
.info-label {
font-size: 12px;
color: var(--color-text-3);
margin-bottom: 8px;
}
.info-value {
font-size: 16px;
font-weight: 500;
color: var(--color-text-1);
}
.changelog-section {
margin-top: 12px;
}
.changelog-title {
font-size: 13px;
font-weight: 500;
color: var(--color-text-2);
margin-bottom: 6px;
}
.changelog {
background: var(--color-fill-1);
padding: 10px 12px;
border-radius: 4px;
margin: 0;
max-height: 280px;
overflow-y: auto;
font-size: 12px;
line-height: 1.65;
color: var(--color-text-2);
}
.changelog :deep(h4) {
font-size: 12px;
font-weight: 600;
color: var(--color-text-1);
margin: 8px 0 3px;
}
.changelog :deep(h4:first-child) {
margin-top: 0;
}
.changelog :deep(ul) {
list-style: none;
padding: 0;
margin: 1px 0;
}
.changelog :deep(li) {
position: relative;
padding-left: 14px;
margin: 1px 0;
}
.changelog :deep(li::before) {
content: '·';
position: absolute;
left: 4px;
color: var(--color-text-4);
font-weight: bold;
}
.changelog :deep(code) {
background: var(--color-fill-3);
padding: 0 4px;
border-radius: 3px;
font-size: 11px;
}
.changelog :deep(p) {
margin: 2px 0;
}
.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>