mirror of
https://github.com/lanyeeee/bilibili-video-downloader.git
synced 2026-09-08 09:06:53 +08:00
pref: 优化下载任务进度条的性能
This commit is contained in:
+2
-1
@@ -22,7 +22,8 @@
|
|||||||
"unplugin-auto-import": "^19.3.0",
|
"unplugin-auto-import": "^19.3.0",
|
||||||
"unplugin-vue-components": "^28.8.0",
|
"unplugin-vue-components": "^28.8.0",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-draggable-plus": "^0.6.0"
|
"vue-draggable-plus": "^0.6.0",
|
||||||
|
"z-vue-scan": "^0.0.35"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.30.1",
|
"@eslint/js": "^9.30.1",
|
||||||
|
|||||||
Generated
+32
@@ -47,6 +47,9 @@ importers:
|
|||||||
vue-draggable-plus:
|
vue-draggable-plus:
|
||||||
specifier: ^0.6.0
|
specifier: ^0.6.0
|
||||||
version: 0.6.0(@types/sortablejs@1.15.8)
|
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:
|
devDependencies:
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.30.1
|
specifier: ^9.30.1
|
||||||
@@ -1969,6 +1972,17 @@ packages:
|
|||||||
vscode-uri@3.1.0:
|
vscode-uri@3.1.0:
|
||||||
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
|
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:
|
vue-draggable-plus@0.6.0:
|
||||||
resolution: {integrity: sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw==}
|
resolution: {integrity: sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2035,6 +2049,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
|
resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
|
||||||
engines: {node: '>=18'}
|
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:
|
snapshots:
|
||||||
|
|
||||||
'@ampproject/remapping@2.3.0':
|
'@ampproject/remapping@2.3.0':
|
||||||
@@ -3983,6 +4006,10 @@ snapshots:
|
|||||||
|
|
||||||
vscode-uri@3.1.0: {}
|
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):
|
vue-draggable-plus@0.6.0(@types/sortablejs@1.15.8):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/sortablejs': 1.15.8
|
'@types/sortablejs': 1.15.8
|
||||||
@@ -4045,3 +4072,8 @@ snapshots:
|
|||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
yoctocolors@2.1.1: {}
|
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 'virtual:uno.css'
|
||||||
import 'lazysizes'
|
import 'lazysizes'
|
||||||
import 'lazysizes/plugins/parent-fit/ls.parent-fit'
|
import 'lazysizes/plugins/parent-fit/ls.parent-fit'
|
||||||
|
import VueScan, { type VueScanOptions } from 'z-vue-scan'
|
||||||
|
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
|
const isProduction = import.meta.env.PROD
|
||||||
|
if (!isProduction) {
|
||||||
|
app.use<VueScanOptions>(VueScan, {})
|
||||||
|
}
|
||||||
|
|
||||||
app.use(pinia).mount('#app')
|
app.use(pinia).mount('#app')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { ref, watchEffect, computed, nextTick, DeepReadonly, watch } from 'vue'
|
import { ref, watchEffect, computed, nextTick, DeepReadonly, watch, useTemplateRef } from 'vue'
|
||||||
import { SelectionArea, SelectionEvent } from '@viselect/vue'
|
import { PartialSelectionOptions, SelectionArea, SelectionEvent } from '@viselect/vue'
|
||||||
import { commands } from '../../../bindings.ts'
|
import { commands } from '../../../bindings.ts'
|
||||||
import { DropdownOption, NIcon } from 'naive-ui'
|
import { DropdownOption, NIcon } from 'naive-ui'
|
||||||
import { PhChecks, PhTrash, PhArrowClockwise } from '@phosphor-icons/vue'
|
import { PhChecks, PhTrash, PhArrowClockwise } from '@phosphor-icons/vue'
|
||||||
@@ -10,9 +10,13 @@ import { ProgressData } from '../DownloadPane.vue'
|
|||||||
|
|
||||||
const store = useStore()
|
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 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 { dropdownX, dropdownY, dropdownShowing, dropdownOptions, showDropdown } = useDropdown()
|
||||||
|
|
||||||
const completedProgresses = computed<[string, DeepReadonly<ProgressData>][]>(() =>
|
const completedProgresses = computed<[string, DeepReadonly<ProgressData>][]>(() =>
|
||||||
@@ -152,41 +156,35 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full flex flex-col overflow-auto">
|
<div class="h-full flex flex-col overflow-auto">
|
||||||
<SelectionArea
|
<SelectionArea ref="selectionAreaRef" :options="selectionOptions" @move="updateSelectedIds" @start="unselectAll" />
|
||||||
ref="selectionAreaRef"
|
<div class="completed-progresses-selection-container h-full flex flex-col px-2" @contextmenu="showDropdown">
|
||||||
class="h-full flex flex-col selection-container px-2"
|
|
||||||
:options="{ selectables: '.selectable', features: { deselectOnBlur: true } }"
|
|
||||||
@contextmenu="showDropdown"
|
|
||||||
@move="updateSelectedIds"
|
|
||||||
@start="unselectAll">
|
|
||||||
<div class="animate-pulse text-violet">左键拖动进行框选,右键打开菜单</div>
|
<div class="animate-pulse text-violet">左键拖动进行框选,右键打开菜单</div>
|
||||||
|
|
||||||
<DownloadProgress
|
<DownloadProgress
|
||||||
v-for="[taskId, p] in currentPageProgresses"
|
v-for="[taskId, p] in currentPageProgresses"
|
||||||
:key="taskId"
|
:key="taskId"
|
||||||
:p="p"
|
|
||||||
v-model:selected-ids="selectedIds"
|
|
||||||
ref="progressRefs"
|
|
||||||
:data-key="taskId"
|
:data-key="taskId"
|
||||||
:class="['selectable', selectedIds.has(taskId) ? 'selected shadow-md' : 'hover:bg-gray-1']" />
|
:p="p"
|
||||||
<n-dropdown
|
v-model:selected-ids="selectedIds" />
|
||||||
placement="bottom-start"
|
</div>
|
||||||
trigger="manual"
|
|
||||||
:x="dropdownX"
|
<n-dropdown
|
||||||
:y="dropdownY"
|
placement="bottom-start"
|
||||||
:options="dropdownOptions"
|
trigger="manual"
|
||||||
:show="dropdownShowing"
|
:x="dropdownX"
|
||||||
:on-clickoutside="() => (dropdownShowing = false)" />
|
:y="dropdownY"
|
||||||
</SelectionArea>
|
:options="dropdownOptions"
|
||||||
|
:show="dropdownShowing"
|
||||||
|
:on-clickoutside="() => (dropdownShowing = false)" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.selection-container {
|
.completed-progresses-selection-container {
|
||||||
@apply select-none overflow-auto;
|
@apply select-none overflow-auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selection-container .selected {
|
.completed-progresses-selection-container .selected {
|
||||||
@apply bg-[rgb(204,232,255)];
|
@apply bg-[rgb(204,232,255)];
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { path } from '@tauri-apps/api'
|
import { path } from '@tauri-apps/api'
|
||||||
import { commands, DownloadTaskState } from '../../../bindings.ts'
|
import { commands } from '../../../bindings.ts'
|
||||||
import { useStore } from '../../../store.ts'
|
import { useStore } from '../../../store.ts'
|
||||||
import {
|
import {
|
||||||
PhWarningCircle,
|
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') {
|
if (state === 'Completed') {
|
||||||
return 'success'
|
return 'success'
|
||||||
} else if (state === 'Paused') {
|
} else if (state === 'Paused') {
|
||||||
@@ -65,9 +67,11 @@ function stateToStatus(state: DownloadTaskState): ProgressProps['status'] {
|
|||||||
} else {
|
} else {
|
||||||
return 'default'
|
return 'default'
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
|
const colorClass = computed<string>(() => {
|
||||||
|
const state = props.p.state
|
||||||
|
|
||||||
function stateToColorClass(state: DownloadTaskState) {
|
|
||||||
if (state === 'Downloading') {
|
if (state === 'Downloading') {
|
||||||
return 'text-blue-500'
|
return 'text-blue-500'
|
||||||
} else if (state === 'Pending') {
|
} else if (state === 'Pending') {
|
||||||
@@ -81,7 +85,7 @@ function stateToColorClass(state: DownloadTaskState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
}
|
})
|
||||||
|
|
||||||
async function showMp4InFileManager(episodeDir: string, filename: string) {
|
async function showMp4InFileManager(episodeDir: string, filename: string) {
|
||||||
if (store.config === undefined) {
|
if (store.config === undefined) {
|
||||||
@@ -145,7 +149,8 @@ function handleModifyClick() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<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"
|
@contextmenu="handleProgressContextMenu"
|
||||||
@dblclick="handleProgressDoubleClick">
|
@dblclick="handleProgressDoubleClick">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
@@ -254,7 +259,7 @@ function handleModifyClick() {
|
|||||||
:up-avatar="p.up_avatar"
|
:up-avatar="p.up_avatar"
|
||||||
:up-uid="p.up_uid" />
|
: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">
|
<n-icon :size="22">
|
||||||
<PhCloudArrowDown v-if="p.state === 'Downloading'" />
|
<PhCloudArrowDown v-if="p.state === 'Downloading'" />
|
||||||
<PhClock v-else-if="p.state === 'Pending'" />
|
<PhClock v-else-if="p.state === 'Pending'" />
|
||||||
@@ -264,7 +269,7 @@ function handleModifyClick() {
|
|||||||
<span class="whitespace-nowrap mr-2">{{ p.stateIndicator }}</span>
|
<span class="whitespace-nowrap mr-2">{{ p.stateIndicator }}</span>
|
||||||
<n-progress
|
<n-progress
|
||||||
v-if="p.taskIndicator !== ''"
|
v-if="p.taskIndicator !== ''"
|
||||||
:status="stateToStatus(p.state)"
|
:status="progressStatus"
|
||||||
:percentage="p.percentage"
|
:percentage="p.percentage"
|
||||||
:processing="p.state === 'Downloading'">
|
:processing="p.state === 'Downloading'">
|
||||||
{{ p.taskIndicator }}
|
{{ p.taskIndicator }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { ref, watchEffect, computed, nextTick, DeepReadonly, watch } from 'vue'
|
import { ref, watchEffect, computed, nextTick, DeepReadonly, watch, useTemplateRef } from 'vue'
|
||||||
import { SelectionArea, SelectionEvent } from '@viselect/vue'
|
import { PartialSelectionOptions, SelectionArea, SelectionEvent } from '@viselect/vue'
|
||||||
import { commands } from '../../../bindings.ts'
|
import { commands } from '../../../bindings.ts'
|
||||||
import { DropdownOption, NIcon } from 'naive-ui'
|
import { DropdownOption, NIcon } from 'naive-ui'
|
||||||
import { PhPause, PhChecks, PhTrash, PhCaretRight, PhArrowClockwise } from '@phosphor-icons/vue'
|
import { PhPause, PhChecks, PhTrash, PhCaretRight, PhArrowClockwise } from '@phosphor-icons/vue'
|
||||||
@@ -10,9 +10,13 @@ import { ProgressData } from '../DownloadPane.vue'
|
|||||||
|
|
||||||
const store = useStore()
|
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 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 { dropdownX, dropdownY, dropdownShowing, dropdownOptions, showDropdown } = useDropdown()
|
||||||
|
|
||||||
const uncompletedProgresses = computed<[string, DeepReadonly<ProgressData>][]>(() =>
|
const uncompletedProgresses = computed<[string, DeepReadonly<ProgressData>][]>(() =>
|
||||||
@@ -210,13 +214,8 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full flex flex-col overflow-auto">
|
<div class="h-full flex flex-col overflow-auto">
|
||||||
<SelectionArea
|
<SelectionArea ref="selectionAreaRef" :options="selectionOptions" @move="updateSelectedIds" @start="unselectAll" />
|
||||||
ref="selectionAreaRef"
|
<div class="h-full flex flex-col uncompleted-progresses-selection-container px-2" @contextmenu="showDropdown">
|
||||||
class="h-full flex flex-col selection-container px-2"
|
|
||||||
:options="{ selectables: '.selectable', features: { deselectOnBlur: true } }"
|
|
||||||
@contextmenu="showDropdown"
|
|
||||||
@move="updateSelectedIds"
|
|
||||||
@start="unselectAll">
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<span class="animate-pulse text-violet">
|
<span class="animate-pulse text-violet">
|
||||||
左键拖动进行框选,右键打开菜单,双击暂停/继续,失败的任务也可以继续
|
左键拖动进行框选,右键打开菜单,双击暂停/继续,失败的任务也可以继续
|
||||||
@@ -226,12 +225,10 @@ defineExpose({
|
|||||||
<DownloadProgress
|
<DownloadProgress
|
||||||
v-for="[taskId, p] in currentPageProgresses"
|
v-for="[taskId, p] in currentPageProgresses"
|
||||||
:key="taskId"
|
:key="taskId"
|
||||||
:p="p"
|
|
||||||
v-model:selected-ids="selectedIds"
|
|
||||||
ref="progressRefs"
|
|
||||||
:data-key="taskId"
|
:data-key="taskId"
|
||||||
:class="['selectable ', selectedIds.has(taskId) ? 'selected shadow-md' : 'hover:bg-gray-1']" />
|
:p="p"
|
||||||
</SelectionArea>
|
v-model:selected-ids="selectedIds" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<n-dropdown
|
<n-dropdown
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
@@ -245,11 +242,11 @@ defineExpose({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.selection-container {
|
.uncompleted-progresses-selection-container {
|
||||||
@apply select-none overflow-auto;
|
@apply select-none overflow-auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selection-container .selected {
|
.uncompleted-progresses-selection-container .selected {
|
||||||
@apply bg-[rgb(204,232,255)];
|
@apply bg-[rgb(204,232,255)];
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+9
-1
@@ -38,8 +38,16 @@ function useProgresses() {
|
|||||||
// 将 `_progresses` 的内容更新到 `progresses` 中,并触发重新渲染
|
// 将 `_progresses` 的内容更新到 `progresses` 中,并触发重新渲染
|
||||||
const updateProgressesOnFrame = () => {
|
const updateProgressesOnFrame = () => {
|
||||||
const newProgressesMap = new Map<string, ProgressData>()
|
const newProgressesMap = new Map<string, ProgressData>()
|
||||||
|
|
||||||
for (const [key, value] of _progresses.entries()) {
|
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
|
progresses.value = newProgressesMap
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user