版本管理,优化
This commit is contained in:
@@ -1,27 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { Monitor, Sun, Moon, Sparkles, Layers, LogOut, Package, GripVertical } from '@lucide/vue'
|
||||
import { Monitor, Sun, Moon, Sparkles, Layers, LogOut, Package, GripVertical, Info, RefreshCw, Download, Check, Loader2 } from '@lucide/vue'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useAppStore, type Theme, type EffectType, type ModuleInfo } from '@/stores/appStore'
|
||||
import { useSearchStore } from '@/stores/searchStore'
|
||||
import { useProcessStore } from '@/stores/processStore'
|
||||
import { getModuleIcon } from '@/modules/icons'
|
||||
import { commands, type UpdateCheckResult } from '@/lib/bindings'
|
||||
import { EVENTS } from '@/lib/constants'
|
||||
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const searchStore = useSearchStore()
|
||||
const processStore = useProcessStore()
|
||||
|
||||
// ===== 关于 / 更新 =====
|
||||
|
||||
/** 更新进度事件载荷(与 Rust UpdateProgress 对应) */
|
||||
interface UpdateProgress {
|
||||
stage: string
|
||||
percent: number
|
||||
downloadedBytes: number
|
||||
totalBytes: number | null
|
||||
message: string
|
||||
}
|
||||
|
||||
/** 当前应用版本(启动时读取) */
|
||||
const currentVersion = ref('')
|
||||
/** 检查更新的结果 */
|
||||
const updateResult = ref<UpdateCheckResult | null>(null)
|
||||
const checking = ref(false)
|
||||
/** 应用本体更新中 */
|
||||
const appUpdating = ref(false)
|
||||
/** ThingHK 内核更新中 */
|
||||
const kernelUpdating = ref(false)
|
||||
const progress = ref<UpdateProgress | null>(null)
|
||||
const thinghkExists = ref(false)
|
||||
let progressUnlisten: UnlistenFn | null = null
|
||||
|
||||
const installTypeText = computed(() =>
|
||||
updateResult.value?.installType === 'installed' ? '安装版' : '便携版',
|
||||
)
|
||||
|
||||
const loadAppInfo = async () => {
|
||||
try {
|
||||
currentVersion.value = await commands.appVersion()
|
||||
} catch { /* 忽略:后端未就绪 */ }
|
||||
try {
|
||||
const info = await invoke('monitor_kernel_info') as { exists?: boolean }
|
||||
thinghkExists.value = info?.exists ?? false
|
||||
} catch { /* 忽略:内核未就绪 */ }
|
||||
}
|
||||
|
||||
/** 检查 Gitea 最新 release */
|
||||
const checkUpdate = async () => {
|
||||
if (checking.value || appUpdating.value) return
|
||||
checking.value = true
|
||||
try {
|
||||
updateResult.value = await commands.updateCheck()
|
||||
} catch (e) {
|
||||
console.error('[updater] 检查更新失败', e)
|
||||
} finally {
|
||||
checking.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 下载并应用应用更新(便携版替换 exe / 安装版静默安装),触发应用退出重启 */
|
||||
const installUpdate = async () => {
|
||||
if (appUpdating.value) return
|
||||
appUpdating.value = true
|
||||
try {
|
||||
await commands.updateInstall()
|
||||
} catch (e) {
|
||||
console.error('[updater] 应用更新失败', e)
|
||||
appUpdating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 更新 ThingHK 内核:后端先停止监控内核再覆盖文件 */
|
||||
const updateThinghkKernel = async () => {
|
||||
if (kernelUpdating.value) return
|
||||
kernelUpdating.value = true
|
||||
try {
|
||||
await commands.updateThinghk()
|
||||
await loadAppInfo()
|
||||
} catch (e) {
|
||||
console.error('[updater] ThingHK 更新失败', e)
|
||||
} finally {
|
||||
kernelUpdating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 系统真实深浅色偏好,来自 appStore(应用启动时初始化,仅通过 onThemeChanged 更新,
|
||||
// 不受 setTheme 污染),用于"跟随系统"卡片色块。
|
||||
const systemDark = computed(() => appStore.systemDark)
|
||||
|
||||
onMounted(() => {
|
||||
loadAppInfo()
|
||||
// 监听更新进度事件(应用更新与 ThingHK 内核更新共用)
|
||||
listen<UpdateProgress>(EVENTS.updateProgress, (e) => {
|
||||
progress.value = e.payload
|
||||
if (e.payload.stage === 'done') {
|
||||
kernelUpdating.value = false
|
||||
progress.value = null
|
||||
}
|
||||
}).then((fn) => {
|
||||
progressUnlisten = fn
|
||||
})
|
||||
searchStore.registerAction('settings', 0, () => appStore.setTheme('light'))
|
||||
searchStore.registerAction('settings', 1, () => appStore.setTheme('dark'))
|
||||
searchStore.registerAction('settings', 2, () => appStore.setTheme('system'))
|
||||
@@ -29,10 +121,21 @@ onMounted(() => {
|
||||
searchStore.registerAction('settings', 4, () => appStore.setEffect('mica'))
|
||||
searchStore.registerAction('settings', 5, () => appStore.setEffect('acrylic'))
|
||||
searchStore.registerAction('settings', 6, () => appStore.toggleAutoStart())
|
||||
// 新增设置项(模块管理/关于/退出):仅定位滚动到对应卡片
|
||||
searchStore.registerAction('settings', 7, () => scrollToCard('settings-card-modules'))
|
||||
searchStore.registerAction('settings', 8, () => scrollToCard('settings-card-about'))
|
||||
searchStore.registerAction('settings', 9, () => scrollToCard('settings-card-about'))
|
||||
searchStore.registerAction('settings', 10, () => scrollToCard('settings-card-about'))
|
||||
searchStore.registerAction('settings', 11, () => scrollToCard('settings-card-quit'))
|
||||
// 主动刷新所有进程状态,确保内核 badge 显示当前真实状态(而非过期缓存)
|
||||
processStore.refreshAll().catch(() => { /* 忽略:后端可能未就绪 */ })
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
progressUnlisten?.()
|
||||
progressUnlisten = null
|
||||
})
|
||||
|
||||
const themes: Array<{ id: Theme; name: string; color: string; icon: typeof Sun }> = [
|
||||
{ id: 'light', name: '浅色模式', color: '#f8fafc', icon: Sun },
|
||||
{ id: 'dark', name: '深色模式', color: '#1e293b', icon: Moon },
|
||||
@@ -82,6 +185,25 @@ const quitApp = async () => {
|
||||
await invoke('quit_app')
|
||||
}
|
||||
|
||||
/** 滚动到指定卡片(搜索导航定位用)。
|
||||
* 模块为异步加载,若卡片尚未渲染则短暂重试,直到模块挂载完成。 */
|
||||
const scrollToCard = (id: string) => {
|
||||
const tryScroll = (): boolean => {
|
||||
const el = document.getElementById(id)
|
||||
if (!el) return false
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
return true
|
||||
}
|
||||
if (tryScroll()) return
|
||||
let attempts = 0
|
||||
const timer = window.setInterval(() => {
|
||||
attempts++
|
||||
if (tryScroll() || attempts >= 20) {
|
||||
window.clearInterval(timer)
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
/** 判断模块开关是否处于处理中状态 */
|
||||
const isModuleToggling = (moduleId: string): boolean => {
|
||||
return appStore.togglingModules.has(moduleId)
|
||||
@@ -243,7 +365,7 @@ const onDragEnd = () => {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card id="settings-card-modules">
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<Package class="size-5 text-primary" />
|
||||
@@ -313,7 +435,104 @@ const onDragEnd = () => {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card id="settings-card-about">
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<Info class="size-5 text-primary" />
|
||||
关于
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-1">
|
||||
<!-- 应用版本 + 检查更新 -->
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="space-y-1">
|
||||
<Label class="text-base font-medium">应用版本</Label>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Thing v{{ currentVersion || '…' }}
|
||||
<span v-if="updateResult" class="ml-1 text-xs px-1.5 py-0.5 rounded-full bg-muted">
|
||||
{{ installTypeText }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="checking || appUpdating"
|
||||
@click="checkUpdate"
|
||||
>
|
||||
<RefreshCw v-if="!checking" class="size-3.5 mr-1.5" />
|
||||
<Loader2 v-else class="size-3.5 mr-1.5 animate-spin" />
|
||||
{{ checking ? '检查中...' : '检查更新' }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 更新结果 -->
|
||||
<div v-if="updateResult" class="rounded-lg border border-border/50 p-3 space-y-2">
|
||||
<div v-if="updateResult.hasUpdate" class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm font-medium">
|
||||
发现新版本 v{{ updateResult.latestVersion }}
|
||||
</span>
|
||||
<span class="text-xs text-muted-foreground">当前 v{{ updateResult.currentVersion }}</span>
|
||||
</div>
|
||||
<p
|
||||
v-if="updateResult.releaseBody"
|
||||
class="text-xs text-muted-foreground whitespace-pre-wrap max-h-20 overflow-y-auto"
|
||||
>
|
||||
{{ updateResult.releaseBody }}
|
||||
</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button size="sm" :disabled="appUpdating" @click="installUpdate">
|
||||
<Download class="size-3.5 mr-1.5" />
|
||||
{{ appUpdating ? '更新中...' : '下载并更新' }}
|
||||
</Button>
|
||||
<span class="text-xs text-muted-foreground">
|
||||
{{ installTypeText === '安装版' ? '将静默安装新版并重启' : '将替换程序文件并重启' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex items-center gap-1.5 text-sm text-muted-foreground">
|
||||
<Check class="size-4 text-green-500" />
|
||||
已是最新版本
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 应用更新进度 -->
|
||||
<div v-if="appUpdating && progress" class="space-y-1.5 py-1">
|
||||
<div class="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>{{ progress.message }}</span>
|
||||
<span class="font-mono">{{ progress.percent }}%</span>
|
||||
</div>
|
||||
<Progress :model-value="progress.percent" />
|
||||
</div>
|
||||
|
||||
<!-- ThingHK 内核 -->
|
||||
<div class="flex items-center justify-between py-2 border-t border-border/50">
|
||||
<div class="space-y-1">
|
||||
<Label class="text-base font-medium">ThingHK 内核</Label>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ thinghkExists ? '已安装' : '未安装' }} · 更新前请先停用监控模块
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" :disabled="kernelUpdating" @click="updateThinghkKernel">
|
||||
<Loader2 v-if="kernelUpdating && !progress" class="size-3.5 mr-1.5 animate-spin" />
|
||||
<Package v-else class="size-3.5 mr-1.5" />
|
||||
{{ kernelUpdating ? '更新中...' : '更新内核' }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- ThingHK 更新进度 -->
|
||||
<div v-if="kernelUpdating && progress" class="space-y-1.5 py-1">
|
||||
<div class="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>{{ progress.message }}</span>
|
||||
<span class="font-mono">{{ progress.percent }}%</span>
|
||||
</div>
|
||||
<Progress :model-value="progress.percent" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card id="settings-card-quit">
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<LogOut class="size-5 text-destructive" />
|
||||
|
||||
Reference in New Issue
Block a user