Skip to content

Commit 1221101

Browse files
committed
feat: 列表添加多选下载功能,支持批量选择和下载音乐
1 parent 3ac3159 commit 1221101

File tree

3 files changed

+93
-2
lines changed

3 files changed

+93
-2
lines changed

src/renderer/components/common/songItemCom/CompactSongItem.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ const artists = computed(() => baseItem.value?.artists || []);
126126
// 包装方法,避免直接访问可能为undefined的ref
127127
const onToggleSelect = () => {
128128
baseItem.value?.toggleSelect();
129-
emit('select', props.item.id, !props.selected);
130129
};
131130
const onArtistClick = (id: number) => baseItem.value?.handleArtistClick(id);
132131
const onToggleFavorite = (event: Event) => {

src/renderer/components/common/songItemCom/StandardSongItem.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ const artists = computed(() => baseItem.value?.artists || []);
131131
// 包装方法,避免直接访问可能为undefined的ref
132132
const onToggleSelect = () => {
133133
baseItem.value?.toggleSelect();
134-
emit('select', props.item.id, !props.selected);
135134
};
136135
const onImageLoad = (event: Event) => baseItem.value?.imageLoad(event);
137136
const onArtistClick = (id: number) => baseItem.value?.handleArtistClick(id);

src/renderer/views/music/MusicListPage.vue

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,48 @@
3636
</template>
3737
{{ t('comp.musicList.addToPlaylist') }}
3838
</n-tooltip>
39+
40+
<!-- 多选/下载操作 -->
41+
<div v-if="filteredSongs.length > 0" class="flex items-center gap-2">
42+
<n-tooltip v-if="!isSelecting" placement="bottom" trigger="hover">
43+
<template #trigger>
44+
<div class="action-button hover-green" @click="startSelect">
45+
<i class="icon iconfont ri-checkbox-multiple-line"></i>
46+
</div>
47+
</template>
48+
{{ t('favorite.batchDownload')}}
49+
</n-tooltip>
50+
<div v-else class="flex items-center gap-2">
51+
<n-checkbox
52+
:checked="isAllSelected"
53+
:indeterminate="isIndeterminate"
54+
@update:checked="handleSelectAll"
55+
>
56+
{{ t('common.selectAll') }}
57+
</n-checkbox>
58+
<n-tooltip placement="bottom" trigger="hover">
59+
<template #trigger>
60+
<div
61+
class="action-button hover-green"
62+
:class="{ 'opacity-50 pointer-events-none': selectedSongs.length === 0 || isDownloading }"
63+
@click="selectedSongs.length && !isDownloading && handleBatchDownload()"
64+
>
65+
<i class="icon iconfont ri-download-line" :class="{ 'animate-spin': isDownloading }"></i>
66+
</div>
67+
</template>
68+
{{ t('favorite.download', { count: selectedSongs.length }) }}
69+
</n-tooltip>
70+
<n-tooltip placement="bottom" trigger="hover">
71+
<template #trigger>
72+
<div class="action-button" @click="cancelSelect">
73+
<i class="icon iconfont ri-close-line"></i>
74+
</div>
75+
</template>
76+
{{ t('common.cancel') }}
77+
</n-tooltip>
78+
</div>
79+
</div>
80+
3981
<!-- 布局切换按钮 -->
4082
<div class="layout-toggle" v-if="!isMobile">
4183
<n-tooltip placement="bottom" trigger="hover">
@@ -132,8 +174,11 @@
132174
:compact="isCompactLayout"
133175
:item="formatSong(item)"
134176
:can-remove="canRemove"
177+
:selectable="isSelecting"
178+
:selected="selectedSongs.includes(item.id as number)"
135179
@play="handlePlay"
136180
@remove-song="handleRemoveSong"
181+
@select="(id, selected) => handleSelect(id, selected)"
137182
/>
138183
</div>
139184
</template>
@@ -166,6 +211,7 @@ import PlayBottom from '@/components/common/PlayBottom.vue';
166211
import { useMusicStore, usePlayerStore } from '@/store';
167212
import { SongResult } from '@/type/music';
168213
import { getImgUrl, isMobile, setAnimationClass } from '@/utils';
214+
import { useDownload } from '@/hooks/useDownload';
169215
170216
const { t } = useI18n();
171217
const route = useRoute();
@@ -838,6 +884,53 @@ const addToPlaylist = () => {
838884
839885
message.success(t('comp.musicList.addToPlaylistSuccess', { count: newSongs.length }));
840886
};
887+
888+
// 多选下载相关状态和方法
889+
const isSelecting = ref(false);
890+
const selectedSongs = ref<number[]>([]);
891+
const { isDownloading, batchDownloadMusic } = useDownload();
892+
893+
const startSelect = () => {
894+
isSelecting.value = true;
895+
selectedSongs.value = [];
896+
};
897+
const cancelSelect = () => {
898+
isSelecting.value = false;
899+
selectedSongs.value = [];
900+
};
901+
const handleSelect = (songId: number, selected: boolean) => {
902+
if (selected) {
903+
selectedSongs.value.push(songId);
904+
} else {
905+
selectedSongs.value = selectedSongs.value.filter((id) => id !== songId);
906+
}
907+
};
908+
const isAllSelected = computed(() => {
909+
return (
910+
filteredSongs.value.length > 0 &&
911+
selectedSongs.value.length === filteredSongs.value.length
912+
);
913+
});
914+
const isIndeterminate = computed(() => {
915+
return (
916+
selectedSongs.value.length > 0 &&
917+
selectedSongs.value.length < filteredSongs.value.length
918+
);
919+
});
920+
const handleSelectAll = (checked: boolean) => {
921+
if (checked) {
922+
selectedSongs.value = filteredSongs.value.map((song) => song.id as number);
923+
} else {
924+
selectedSongs.value = [];
925+
}
926+
};
927+
const handleBatchDownload = async () => {
928+
const selectedSongsList = selectedSongs.value
929+
.map((songId) => filteredSongs.value.find((s) => s.id === songId))
930+
.filter((song) => song) as SongResult[];
931+
await batchDownloadMusic(selectedSongsList);
932+
cancelSelect();
933+
};
841934
</script>
842935

843936
<style scoped lang="scss">

0 commit comments

Comments
 (0)