性能优化

This commit is contained in:
zhongluofeng
2026-08-06 10:33:16 +08:00
parent c7578a2e6b
commit e66c53e66d
105 changed files with 7273 additions and 5002 deletions
+56 -40
View File
@@ -9,10 +9,11 @@ import {
} from '@lucide/vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { toast } from 'vue-sonner'
import { invoke } from '@tauri-apps/api/core'
import { open as openDialog } from '@tauri-apps/plugin-dialog'
// Rust 端通过 tauri-specta 生成的命令绑定(bindings.ts
import { commands } from '@/lib/bindings'
import { useDownloaderStore, type DownloadTask, type TaskStatus, type CheckUrlResult } from '@/stores/downloaderStore'
import { useModuleTabs } from '@/lib/useModuleTabs'
import { useModuleTabs } from '@/lib/use-module-tabs'
import { useModuleTabsStore } from '@/stores/moduleTabsStore'
import { createLogger } from '@/lib/logger'
import { pendingNewDownload } from '@/lib/trayEvents'
@@ -218,13 +219,21 @@ const onRemoveOpenChange = (open: boolean) => {
}
// ===== 任务详情弹窗 =====
const detailDialogState = ref<{ open: boolean; task: DownloadTask | null }>({
// 仅存任务 id,通过 computed 实时从 store.tasks 取最新对象,
// 保证弹窗内的进度/速度/状态随下载进度事件实时刷新
const detailDialogState = ref<{ open: boolean; taskId: string | null }>({
open: false,
task: null
taskId: null
})
const detailTask = computed<DownloadTask | null>(() => {
const id = detailDialogState.value.taskId
if (!id) return null
return store.tasks.find((t) => t.id === id) ?? null
})
const handleShowDetail = (task: DownloadTask) => {
detailDialogState.value = { open: true, task }
detailDialogState.value = { open: true, taskId: task.id }
}
const handleCopyText = async (text: string, label: string) => {
@@ -498,7 +507,7 @@ const handleDialogSave = async () => {
const EXTENSION_STORE_URL = 'https://chromewebstore.google.com/'
const handleInstallExtensionOnline = async () => {
try {
await invoke('downloader_open_url', { url: EXTENSION_STORE_URL })
await commands.downloaderOpenUrl(EXTENSION_STORE_URL)
} catch (e) {
try {
await navigator.clipboard.writeText(EXTENSION_STORE_URL)
@@ -537,6 +546,13 @@ onUnmounted(() => {
const allTasks = computed<DownloadTask[]>(() => store.tasks)
// 状态栏计数:单次遍历统计各状态任务数(替代模板内 4 次 filter 全量扫描)
const statusCounts = computed(() => {
const counts: Record<TaskStatus, number> = { queued: 0, active: 0, paused: 0, complete: 0, error: 0 }
for (const t of allTasks.value) counts[t.status]++
return counts
})
// 状态筛选
const filteredByStatus = computed<DownloadTask[]>(() => {
if (statusFilter.value === 'all') return allTasks.value
@@ -635,19 +651,19 @@ const toggleSortOrder = () => {
<div v-if="running" class="flex items-center gap-2 text-xs">
<Badge variant="secondary" class="gap-1">
<Download class="h-3 w-3" />
下载中 {{ allTasks.filter(t => t.status === 'active').length }}
下载中 {{ statusCounts.active }}
</Badge>
<Badge variant="secondary" class="gap-1">
<Clock class="h-3 w-3" />
等待 {{ allTasks.filter(t => t.status === 'queued').length }}
等待 {{ statusCounts.queued }}
</Badge>
<Badge v-if="allTasks.filter(t => t.status === 'paused').length > 0" variant="secondary" class="gap-1">
<Badge v-if="statusCounts.paused > 0" variant="secondary" class="gap-1">
<Pause class="h-3 w-3" />
已暂停 {{ allTasks.filter(t => t.status === 'paused').length }}
已暂停 {{ statusCounts.paused }}
</Badge>
<Badge variant="secondary" class="gap-1">
<Check class="h-3 w-3" />
已完成 {{ allTasks.filter(t => t.status === 'complete').length }}
已完成 {{ statusCounts.complete }}
</Badge>
</div>
</div>
@@ -1457,21 +1473,21 @@ const toggleSortOrder = () => {
任务详情
</DialogTitle>
<DialogDescription class="text-xs">
任务 ID{{ detailDialogState.task?.id }}
任务 ID{{ detailTask?.id }}
</DialogDescription>
</DialogHeader>
<ScrollArea class="max-h-[55vh] pr-3">
<div v-if="detailDialogState.task" class="flex flex-col gap-3 py-2 text-sm">
<div v-if="detailTask" class="flex flex-col gap-3 py-2 text-sm">
<!-- 文件名 -->
<div class="flex items-start justify-between gap-2">
<div class="flex flex-col gap-0.5 min-w-0 flex-1">
<span class="text-xs text-muted-foreground">文件名</span>
<span class="font-medium break-all">{{ detailDialogState.task.filename }}</span>
<span class="font-medium break-all">{{ detailTask.filename }}</span>
</div>
<Tooltip>
<TooltipTrigger as-child>
<Button size="icon" variant="ghost" class="h-7 w-7 shrink-0" @click="handleCopyText(detailDialogState.task.filename, '文件名')">
<Button size="icon" variant="ghost" class="h-7 w-7 shrink-0" @click="handleCopyText(detailTask.filename, '文件名')">
<Copy class="h-3.5 w-3.5" />
</Button>
</TooltipTrigger>
@@ -1484,9 +1500,9 @@ const toggleSortOrder = () => {
<!-- 状态 -->
<div class="flex items-center justify-between">
<span class="text-xs text-muted-foreground">状态</span>
<Badge :variant="getTaskStatusBadge(detailDialogState.task).variant" class="gap-1">
<component :is="getTaskStatusBadge(detailDialogState.task).icon" class="h-3 w-3" />
{{ getTaskStatusBadge(detailDialogState.task).text }}
<Badge :variant="getTaskStatusBadge(detailTask).variant" class="gap-1">
<component :is="getTaskStatusBadge(detailTask).icon" class="h-3 w-3" />
{{ getTaskStatusBadge(detailTask).text }}
</Badge>
</div>
@@ -1494,11 +1510,11 @@ const toggleSortOrder = () => {
<div class="flex items-start justify-between gap-2">
<div class="flex flex-col gap-0.5 min-w-0 flex-1">
<span class="text-xs text-muted-foreground">下载链接</span>
<span class="font-mono text-xs break-all">{{ detailDialogState.task.url }}</span>
<span class="font-mono text-xs break-all">{{ detailTask.url }}</span>
</div>
<Tooltip>
<TooltipTrigger as-child>
<Button size="icon" variant="ghost" class="h-7 w-7 shrink-0" @click="handleCopyText(detailDialogState.task.url, '下载链接')">
<Button size="icon" variant="ghost" class="h-7 w-7 shrink-0" @click="handleCopyText(detailTask.url, '下载链接')">
<Copy class="h-3.5 w-3.5" />
</Button>
</TooltipTrigger>
@@ -1514,15 +1530,15 @@ const toggleSortOrder = () => {
<span class="text-xs text-muted-foreground">保存位置</span>
<Tooltip>
<TooltipTrigger as-child>
<span class="font-mono text-xs break-all cursor-default">{{ detailDialogState.task.dir }}</span>
<span class="font-mono text-xs break-all cursor-default">{{ detailTask.dir }}</span>
</TooltipTrigger>
<TooltipContent class="max-w-[400px] break-all">{{ detailDialogState.task.dir }}</TooltipContent>
<TooltipContent class="max-w-[400px] break-all">{{ detailTask.dir }}</TooltipContent>
</Tooltip>
<span class="font-mono text-xs text-muted-foreground break-all">{{ detailDialogState.task.filename }}</span>
<span class="font-mono text-xs text-muted-foreground break-all">{{ detailTask.filename }}</span>
</div>
<Tooltip>
<TooltipTrigger as-child>
<Button size="icon" variant="ghost" class="h-7 w-7 shrink-0" @click="handleCopyText(detailDialogState.task.dir + '\\' + detailDialogState.task.filename, '完整路径')">
<Button size="icon" variant="ghost" class="h-7 w-7 shrink-0" @click="handleCopyText(detailTask.dir + '\\' + detailTask.filename, '完整路径')">
<Copy class="h-3.5 w-3.5" />
</Button>
</TooltipTrigger>
@@ -1536,19 +1552,19 @@ const toggleSortOrder = () => {
<div class="grid grid-cols-2 gap-3">
<div class="flex flex-col gap-0.5">
<span class="text-xs text-muted-foreground">文件总大小</span>
<span>{{ formatSize(detailDialogState.task.totalSize) }}</span>
<span>{{ formatSize(detailTask.totalSize) }}</span>
</div>
<div class="flex flex-col gap-0.5">
<span class="text-xs text-muted-foreground">已下载</span>
<span>{{ formatSize(detailDialogState.task.completedSize) }}</span>
<span>{{ formatSize(detailTask.completedSize) }}</span>
</div>
<div class="flex flex-col gap-0.5">
<span class="text-xs text-muted-foreground">下载进度</span>
<span>{{ getProgress(detailDialogState.task) }}%</span>
<span>{{ getProgress(detailTask) }}%</span>
</div>
<div class="flex flex-col gap-0.5">
<span class="text-xs text-muted-foreground">当前速度</span>
<span v-if="detailDialogState.task.status === 'active'">{{ formatSpeed(detailDialogState.task.speed) }}</span>
<span v-if="detailTask.status === 'active'">{{ formatSpeed(detailTask.speed) }}</span>
<span v-else>-</span>
</div>
</div>
@@ -1559,41 +1575,41 @@ const toggleSortOrder = () => {
<div class="grid grid-cols-2 gap-3">
<div class="flex flex-col gap-0.5">
<span class="text-xs text-muted-foreground">断点续传</span>
<span>{{ detailDialogState.task.supportsResume ? '支持' : '不支持' }}</span>
<span>{{ detailTask.supportsResume ? '支持' : '不支持' }}</span>
</div>
<div class="flex flex-col gap-0.5">
<span class="text-xs text-muted-foreground">连接数 / 分片数</span>
<span>{{ detailDialogState.task.segments.length }}</span>
<span>{{ detailTask.segments.length }}</span>
</div>
<div class="flex flex-col gap-0.5">
<span class="text-xs text-muted-foreground">创建时间</span>
<span>{{ formatTime(detailDialogState.task.createdAt) }}</span>
<span>{{ formatTime(detailTask.createdAt) }}</span>
</div>
<div class="flex flex-col gap-0.5">
<span class="text-xs text-muted-foreground">剩余时间</span>
<span v-if="detailDialogState.task.status === 'active' && detailDialogState.task.speed > 0">
{{ formatEta(getEta(detailDialogState.task)) }}
<span v-if="detailTask.status === 'active' && detailTask.speed > 0">
{{ formatEta(getEta(detailTask)) }}
</span>
<span v-else>-</span>
</div>
</div>
<!-- 错误信息 -->
<template v-if="detailDialogState.task.error">
<template v-if="detailTask.error">
<Separator />
<div class="flex flex-col gap-1">
<span class="text-xs text-muted-foreground">错误信息</span>
<span class="text-sm text-destructive break-all">{{ detailDialogState.task.error }}</span>
<span class="text-sm text-destructive break-all">{{ detailTask.error }}</span>
</div>
</template>
<!-- 自定义请求头 -->
<template v-if="detailDialogState.task.headers && Object.keys(detailDialogState.task.headers).length > 0">
<template v-if="detailTask.headers && Object.keys(detailTask.headers).length > 0">
<Separator />
<div class="flex flex-col gap-1">
<span class="text-xs text-muted-foreground">自定义请求头</span>
<div class="rounded-md bg-muted p-2 text-xs font-mono space-y-0.5">
<div v-for="(value, key) in detailDialogState.task.headers" :key="key" class="flex gap-2">
<div v-for="(value, key) in detailTask.headers" :key="key" class="flex gap-2">
<span class="text-muted-foreground shrink-0">{{ key }}:</span>
<span class="break-all">{{ value }}</span>
</div>
@@ -1602,12 +1618,12 @@ const toggleSortOrder = () => {
</template>
<!-- 分段详情 -->
<template v-if="detailDialogState.task.segments.length > 1">
<template v-if="detailTask.segments.length > 1">
<Separator />
<div class="flex flex-col gap-2">
<span class="text-xs text-muted-foreground">分段详情</span>
<div class="flex flex-col gap-1.5">
<div v-for="(seg, i) in detailDialogState.task.segments" :key="i" class="flex items-center gap-2 text-xs">
<div v-for="(seg, i) in detailTask.segments" :key="i" class="flex items-center gap-2 text-xs">
<span class="w-8 text-muted-foreground shrink-0">#{{ i }}</span>
<div class="flex-1 min-w-0">
<Progress :model-value="segmentProgress(seg)" class="h-1.5" />
@@ -1626,7 +1642,7 @@ const toggleSortOrder = () => {
<DialogClose as-child>
<Button variant="outline">关闭</Button>
</DialogClose>
<Button v-if="detailDialogState.task?.dir" variant="outline" @click="handleOpenDir(detailDialogState.task)">
<Button v-if="detailTask?.dir" variant="outline" @click="handleOpenDir(detailTask)">
<FolderOpen class="h-4 w-4" />
打开目录
</Button>