mirror of
https://github.com/lanyeeee/bilibili-video-downloader.git
synced 2026-05-06 20:02:57 +08:00
pref: 优化下载任务进度条的性能
This commit is contained in:
@@ -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
32
pnpm-lock.yaml
generated
@@ -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))
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
10
src/store.ts
10
src/store.ts
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user