pref: 优化下载任务进度条的性能

This commit is contained in:
lanyeeee
2026-02-10 04:16:30 +08:00
parent b988e9ff15
commit e8702fb734
7 changed files with 101 additions and 54 deletions

View File

@@ -22,7 +22,8 @@
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.8.0",
"vue": "^3.5.13",
"vue-draggable-plus": "^0.6.0"
"vue-draggable-plus": "^0.6.0",
"z-vue-scan": "^0.0.35"
},
"devDependencies": {
"@eslint/js": "^9.30.1",

32
pnpm-lock.yaml generated
View File

@@ -47,6 +47,9 @@ importers:
vue-draggable-plus:
specifier: ^0.6.0
version: 0.6.0(@types/sortablejs@1.15.8)
z-vue-scan:
specifier: ^0.0.35
version: 0.0.35(vue@3.5.17(typescript@5.6.3))
devDependencies:
'@eslint/js':
specifier: ^9.30.1
@@ -1969,6 +1972,17 @@ packages:
vscode-uri@3.1.0:
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
engines: {node: '>=12'}
hasBin: true
peerDependencies:
'@vue/composition-api': ^1.0.0-rc.1
vue: ^3.0.0-0 || ^2.6.0
peerDependenciesMeta:
'@vue/composition-api':
optional: true
vue-draggable-plus@0.6.0:
resolution: {integrity: sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw==}
peerDependencies:
@@ -2035,6 +2049,15 @@ packages:
resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
engines: {node: '>=18'}
z-vue-scan@0.0.35:
resolution: {integrity: sha512-isWALsDyRFhvGJrWCKbZ8ORYXmrWp+ewvoaBxBQe0WWvilzyuoTLW8IG5gL2kORLdTWmyj2j1NvehaJpKavJcw==}
peerDependencies:
'@vue/composition-api': ^1.0.0-rc.1
vue: ^2.0.0 || >=3.0.0
peerDependenciesMeta:
'@vue/composition-api':
optional: true
snapshots:
'@ampproject/remapping@2.3.0':
@@ -3983,6 +4006,10 @@ snapshots:
vscode-uri@3.1.0: {}
vue-demi@0.14.10(vue@3.5.17(typescript@5.6.3)):
dependencies:
vue: 3.5.17(typescript@5.6.3)
vue-draggable-plus@0.6.0(@types/sortablejs@1.15.8):
dependencies:
'@types/sortablejs': 1.15.8
@@ -4045,3 +4072,8 @@ snapshots:
yocto-queue@0.1.0: {}
yoctocolors@2.1.1: {}
z-vue-scan@0.0.35(vue@3.5.17(typescript@5.6.3)):
dependencies:
vue: 3.5.17(typescript@5.6.3)
vue-demi: 0.14.10(vue@3.5.17(typescript@5.6.3))

View File

@@ -4,8 +4,14 @@ import App from './App.vue'
import 'virtual:uno.css'
import 'lazysizes'
import 'lazysizes/plugins/parent-fit/ls.parent-fit'
import VueScan, { type VueScanOptions } from 'z-vue-scan'
const pinia = createPinia()
const app = createApp(App)
const isProduction = import.meta.env.PROD
if (!isProduction) {
app.use<VueScanOptions>(VueScan, {})
}
app.use(pinia).mount('#app')

View File

@@ -1,6 +1,6 @@
<script setup lang="tsx">
import { ref, watchEffect, computed, nextTick, DeepReadonly, watch } from 'vue'
import { SelectionArea, SelectionEvent } from '@viselect/vue'
import { ref, watchEffect, computed, nextTick, DeepReadonly, watch, useTemplateRef } from 'vue'
import { PartialSelectionOptions, SelectionArea, SelectionEvent } from '@viselect/vue'
import { commands } from '../../../bindings.ts'
import { DropdownOption, NIcon } from 'naive-ui'
import { PhChecks, PhTrash, PhArrowClockwise } from '@phosphor-icons/vue'
@@ -10,9 +10,13 @@ import { ProgressData } from '../DownloadPane.vue'
const store = useStore()
const selectionOptions: PartialSelectionOptions = {
selectables: '.selectable',
features: { deselectOnBlur: true },
boundaries: '.completed-progresses-selection-container',
}
const selectionAreaRef = useTemplateRef('selectionAreaRef')
const selectedIds = ref<Set<string>>(new Set())
const selectionAreaRef = ref<InstanceType<typeof SelectionArea>>()
const progressRefs = ref<InstanceType<typeof DownloadProgress>[]>([])
const { dropdownX, dropdownY, dropdownShowing, dropdownOptions, showDropdown } = useDropdown()
const completedProgresses = computed<[string, DeepReadonly<ProgressData>][]>(() =>
@@ -152,41 +156,35 @@ defineExpose({
<template>
<div class="h-full flex flex-col overflow-auto">
<SelectionArea
ref="selectionAreaRef"
class="h-full flex flex-col selection-container px-2"
:options="{ selectables: '.selectable', features: { deselectOnBlur: true } }"
@contextmenu="showDropdown"
@move="updateSelectedIds"
@start="unselectAll">
<SelectionArea ref="selectionAreaRef" :options="selectionOptions" @move="updateSelectedIds" @start="unselectAll" />
<div class="completed-progresses-selection-container h-full flex flex-col px-2" @contextmenu="showDropdown">
<div class="animate-pulse text-violet">左键拖动进行框选右键打开菜单</div>
<DownloadProgress
v-for="[taskId, p] in currentPageProgresses"
:key="taskId"
:p="p"
v-model:selected-ids="selectedIds"
ref="progressRefs"
:data-key="taskId"
:class="['selectable', selectedIds.has(taskId) ? 'selected shadow-md' : 'hover:bg-gray-1']" />
<n-dropdown
placement="bottom-start"
trigger="manual"
:x="dropdownX"
:y="dropdownY"
:options="dropdownOptions"
:show="dropdownShowing"
:on-clickoutside="() => (dropdownShowing = false)" />
</SelectionArea>
:p="p"
v-model:selected-ids="selectedIds" />
</div>
<n-dropdown
placement="bottom-start"
trigger="manual"
:x="dropdownX"
:y="dropdownY"
:options="dropdownOptions"
:show="dropdownShowing"
:on-clickoutside="() => (dropdownShowing = false)" />
</div>
</template>
<style scoped>
.selection-container {
.completed-progresses-selection-container {
@apply select-none overflow-auto;
}
.selection-container .selected {
.completed-progresses-selection-container .selected {
@apply bg-[rgb(204,232,255)];
}
</style>

View File

@@ -1,6 +1,6 @@
<script setup lang="tsx">
import { path } from '@tauri-apps/api'
import { commands, DownloadTaskState } from '../../../bindings.ts'
import { commands } from '../../../bindings.ts'
import { useStore } from '../../../store.ts'
import {
PhWarningCircle,
@@ -55,7 +55,9 @@ async function handleProgressDoubleClick() {
}
}
function stateToStatus(state: DownloadTaskState): ProgressProps['status'] {
const progressStatus = computed<ProgressProps['status']>(() => {
const state = props.p.state
if (state === 'Completed') {
return 'success'
} else if (state === 'Paused') {
@@ -65,9 +67,11 @@ function stateToStatus(state: DownloadTaskState): ProgressProps['status'] {
} else {
return 'default'
}
}
})
const colorClass = computed<string>(() => {
const state = props.p.state
function stateToColorClass(state: DownloadTaskState) {
if (state === 'Downloading') {
return 'text-blue-500'
} else if (state === 'Pending') {
@@ -81,7 +85,7 @@ function stateToColorClass(state: DownloadTaskState) {
}
return ''
}
})
async function showMp4InFileManager(episodeDir: string, filename: string) {
if (store.config === undefined) {
@@ -145,7 +149,8 @@ function handleModifyClick() {
<template>
<div
class="p-2 mb-2 rounded-lg flex flex-col border border-solid border-gray-2"
class="selectable p-2 mb-2 rounded-lg flex flex-col border border-solid border-gray-2"
:class="selectedIds.has(p.task_id) ? 'selected shadow-md' : 'hover:bg-gray-1'"
@contextmenu="handleProgressContextMenu"
@dblclick="handleProgressDoubleClick">
<div class="flex">
@@ -254,7 +259,7 @@ function handleModifyClick() {
:up-avatar="p.up_avatar"
:up-uid="p.up_uid" />
<div v-if="p.state !== 'Completed'" :class="[stateToColorClass(p.state), 'flex items-center w-100']">
<div v-if="p.state !== 'Completed'" :class="[colorClass, 'flex items-center w-100']">
<n-icon :size="22">
<PhCloudArrowDown v-if="p.state === 'Downloading'" />
<PhClock v-else-if="p.state === 'Pending'" />
@@ -264,7 +269,7 @@ function handleModifyClick() {
<span class="whitespace-nowrap mr-2">{{ p.stateIndicator }}</span>
<n-progress
v-if="p.taskIndicator !== ''"
:status="stateToStatus(p.state)"
:status="progressStatus"
:percentage="p.percentage"
:processing="p.state === 'Downloading'">
{{ p.taskIndicator }}

View File

@@ -1,6 +1,6 @@
<script setup lang="tsx">
import { ref, watchEffect, computed, nextTick, DeepReadonly, watch } from 'vue'
import { SelectionArea, SelectionEvent } from '@viselect/vue'
import { ref, watchEffect, computed, nextTick, DeepReadonly, watch, useTemplateRef } from 'vue'
import { PartialSelectionOptions, SelectionArea, SelectionEvent } from '@viselect/vue'
import { commands } from '../../../bindings.ts'
import { DropdownOption, NIcon } from 'naive-ui'
import { PhPause, PhChecks, PhTrash, PhCaretRight, PhArrowClockwise } from '@phosphor-icons/vue'
@@ -10,9 +10,13 @@ import { ProgressData } from '../DownloadPane.vue'
const store = useStore()
const selectionOptions: PartialSelectionOptions = {
selectables: '.selectable',
features: { deselectOnBlur: true },
boundaries: '.uncompleted-progresses-selection-container',
}
const selectionAreaRef = useTemplateRef('selectionAreaRef')
const selectedIds = ref<Set<string>>(new Set())
const selectionAreaRef = ref<InstanceType<typeof SelectionArea>>()
const progressRefs = ref<InstanceType<typeof DownloadProgress>[]>([])
const { dropdownX, dropdownY, dropdownShowing, dropdownOptions, showDropdown } = useDropdown()
const uncompletedProgresses = computed<[string, DeepReadonly<ProgressData>][]>(() =>
@@ -210,13 +214,8 @@ defineExpose({
<template>
<div class="h-full flex flex-col overflow-auto">
<SelectionArea
ref="selectionAreaRef"
class="h-full flex flex-col selection-container px-2"
:options="{ selectables: '.selectable', features: { deselectOnBlur: true } }"
@contextmenu="showDropdown"
@move="updateSelectedIds"
@start="unselectAll">
<SelectionArea ref="selectionAreaRef" :options="selectionOptions" @move="updateSelectedIds" @start="unselectAll" />
<div class="h-full flex flex-col uncompleted-progresses-selection-container px-2" @contextmenu="showDropdown">
<div class="flex">
<span class="animate-pulse text-violet">
左键拖动进行框选右键打开菜单双击暂停/继续失败的任务也可以继续
@@ -226,12 +225,10 @@ defineExpose({
<DownloadProgress
v-for="[taskId, p] in currentPageProgresses"
:key="taskId"
:p="p"
v-model:selected-ids="selectedIds"
ref="progressRefs"
:data-key="taskId"
:class="['selectable ', selectedIds.has(taskId) ? 'selected shadow-md' : 'hover:bg-gray-1']" />
</SelectionArea>
:p="p"
v-model:selected-ids="selectedIds" />
</div>
<n-dropdown
placement="bottom-start"
@@ -245,11 +242,11 @@ defineExpose({
</template>
<style scoped>
.selection-container {
.uncompleted-progresses-selection-container {
@apply select-none overflow-auto;
}
.selection-container .selected {
.uncompleted-progresses-selection-container .selected {
@apply bg-[rgb(204,232,255)];
}
</style>

View File

@@ -38,8 +38,16 @@ function useProgresses() {
// 将 `_progresses` 的内容更新到 `progresses` 中,并触发重新渲染
const updateProgressesOnFrame = () => {
const newProgressesMap = new Map<string, ProgressData>()
for (const [key, value] of _progresses.entries()) {
newProgressesMap.set(key, { ...value })
const progressData = progresses.value.get(key)
if (progressData !== undefined) {
Object.assign(progressData, value)
newProgressesMap.set(key, progressData)
} else {
newProgressesMap.set(key, { ...value })
}
}
progresses.value = newProgressesMap