feat: 收藏列表添加升序降序排列

This commit is contained in:
alger
2025-05-14 21:18:42 +08:00
parent 54f82d384e
commit 2803d40dd1
3 changed files with 578 additions and 502 deletions
+3 -1
View File
@@ -11,5 +11,7 @@ export default {
downloadSuccess: 'Download completed', downloadSuccess: 'Download completed',
downloadFailed: 'Download failed', downloadFailed: 'Download failed',
downloading: 'Downloading, please wait...', downloading: 'Downloading, please wait...',
selectSongsFirst: 'Please select songs to download first' selectSongsFirst: 'Please select songs to download first',
descending: 'Descending',
ascending: 'Ascending'
}; };
+3 -1
View File
@@ -7,5 +7,7 @@ export default {
downloadSuccess: '下载完成', downloadSuccess: '下载完成',
downloadFailed: '下载失败', downloadFailed: '下载失败',
downloading: '正在下载中,请稍候...', downloading: '正在下载中,请稍候...',
selectSongsFirst: '请先选择要下载的歌曲' selectSongsFirst: '请先选择要下载的歌曲',
descending: '降',
ascending: '升'
}; };
+157 -85
View File
@@ -1,4 +1,4 @@
<template> <template>
<div v-if="isComponent ? favoriteSongs.length : true" class="favorite-page"> <div v-if="isComponent ? favoriteSongs.length : true" class="favorite-page">
<div class="favorite-header" :class="setAnimationClass('animate__fadeInLeft')"> <div class="favorite-header" :class="setAnimationClass('animate__fadeInLeft')">
<div class="favorite-header-left"> <div class="favorite-header-left">
@@ -6,6 +6,26 @@
<div class="favorite-count">{{ t('favorite.count', { count: favoriteList.length }) }}</div> <div class="favorite-count">{{ t('favorite.count', { count: favoriteList.length }) }}</div>
</div> </div>
<div v-if="!isComponent && isElectron" class="favorite-header-right"> <div v-if="!isComponent && isElectron" class="favorite-header-right">
<div class="sort-controls" v-if="!isSelecting">
<div class="sort-buttons">
<div
class="sort-button"
:class="{ active: isDescending }"
@click="toggleSort(true)"
>
<i class="iconfont ri-sort-desc"></i>
{{ t('favorite.descending') }}
</div>
<div
class="sort-button"
:class="{ active: !isDescending }"
@click="toggleSort(false)"
>
<i class="iconfont ri-sort-asc"></i>
{{ t('favorite.ascending') }}
</div>
</div>
</div>
<n-button <n-button
v-if="!isSelecting" v-if="!isSelecting"
secondary secondary
@@ -80,60 +100,60 @@
</n-scrollbar> </n-scrollbar>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { useMessage } from 'naive-ui'; import { useMessage } from 'naive-ui';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { getMusicDetail } from '@/api/music'; import { getMusicDetail } from '@/api/music';
import { getBilibiliProxyUrl, getBilibiliVideoDetail } from '@/api/bilibili'; import { getBilibiliProxyUrl, getBilibiliVideoDetail } from '@/api/bilibili';
import SongItem from '@/components/common/SongItem.vue'; import SongItem from '@/components/common/SongItem.vue';
import { getSongUrl } from '@/hooks/MusicListHook'; import { getSongUrl } from '@/hooks/MusicListHook';
import { usePlayerStore } from '@/store'; import { usePlayerStore } from '@/store';
import type { SongResult } from '@/type/music'; import type { SongResult } from '@/type/music';
import { isElectron, setAnimationClass, setAnimationDelay } from '@/utils'; import { isElectron, setAnimationClass, setAnimationDelay } from '@/utils';
const { t } = useI18n(); const { t } = useI18n();
const playerStore = usePlayerStore(); const playerStore = usePlayerStore();
const message = useMessage(); const message = useMessage();
const favoriteList = computed(() => playerStore.favoriteList); const favoriteList = computed(() => playerStore.favoriteList);
const favoriteSongs = ref<SongResult[]>([]); const favoriteSongs = ref<SongResult[]>([]);
const loading = ref(false); const loading = ref(false);
const noMore = ref(false); const noMore = ref(false);
const scrollbarRef = ref(); const scrollbarRef = ref();
// 多选相关 // 多选相关
const isSelecting = ref(false); const isSelecting = ref(false);
const selectedSongs = ref<number[]>([]); const selectedSongs = ref<number[]>([]);
const isDownloading = ref(false); const isDownloading = ref(false);
// 开始多选 // 开始多选
const startSelect = () => { const startSelect = () => {
isSelecting.value = true; isSelecting.value = true;
selectedSongs.value = []; selectedSongs.value = [];
}; };
// 取消多选 // 取消多选
const cancelSelect = () => { const cancelSelect = () => {
isSelecting.value = false; isSelecting.value = false;
selectedSongs.value = []; selectedSongs.value = [];
}; };
// 处理选择 // 处理选择
const handleSelect = (songId: number, selected: boolean) => { const handleSelect = (songId: number, selected: boolean) => {
if (selected) { if (selected) {
selectedSongs.value.push(songId); selectedSongs.value.push(songId);
} else { } else {
selectedSongs.value = selectedSongs.value.filter((id) => id !== songId); selectedSongs.value = selectedSongs.value.filter((id) => id !== songId);
} }
}; };
// 批量下载 // 批量下载
const handleBatchDownload = async () => { const handleBatchDownload = async () => {
if (isDownloading.value) { if (isDownloading.value) {
message.warning(t('favorite.downloading')); message.warning(t('favorite.downloading'));
return; return;
@@ -215,29 +235,49 @@ const handleBatchDownload = async () => {
message.destroyAll(); message.destroyAll();
message.error(t('favorite.downloadFailed')); message.error(t('favorite.downloadFailed'));
} }
}; };
// 无限滚动相关 // 排序相关
const pageSize = 100; const isDescending = ref(true); // 默认倒序显示
const currentPage = ref(1);
const props = defineProps({ // 切换排序方式
const toggleSort = (descending: boolean) => {
if (isDescending.value === descending) return;
isDescending.value = descending;
currentPage.value = 1;
favoriteSongs.value = [];
noMore.value = false;
getFavoriteSongs();
};
// 无限滚动相关
const pageSize = 100;
const currentPage = ref(1);
const props = defineProps({
isComponent: { isComponent: {
type: Boolean, type: Boolean,
default: false default: false
} }
}); });
// 获取当前页的收藏歌曲ID
const getCurrentPageIds = () => {
let ids = [...favoriteList.value]; // 复制一份以免修改原数组
// 根据排序方式调整顺序
if (isDescending.value) {
ids = ids.reverse(); // 倒序,最新收藏的在前面
}
// 获取当前页的收藏歌曲ID
const getCurrentPageIds = () => {
const startIndex = (currentPage.value - 1) * pageSize; const startIndex = (currentPage.value - 1) * pageSize;
const endIndex = startIndex + pageSize; const endIndex = startIndex + pageSize;
// 返回原始ID,不进行类型转换 // 返回原始ID,不进行类型转换
return favoriteList.value.slice(startIndex, endIndex); return ids.slice(startIndex, endIndex);
}; };
// 获取收藏歌曲详情 // 获取收藏歌曲详情
const getFavoriteSongs = async () => { const getFavoriteSongs = async () => {
if (favoriteList.value.length === 0) { if (favoriteList.value.length === 0) {
favoriteSongs.value = []; favoriteSongs.value = [];
return; return;
@@ -366,10 +406,10 @@ const getFavoriteSongs = async () => {
} finally { } finally {
loading.value = false; loading.value = false;
} }
}; };
// 处理滚动事件 // 处理滚动事件
const handleScroll = (e: any) => { const handleScroll = (e: any) => {
const { scrollTop, scrollHeight, offsetHeight } = e.target; const { scrollTop, scrollHeight, offsetHeight } = e.target;
const threshold = 100; // 距离底部多少像素时加载更多 const threshold = 100; // 距离底部多少像素时加载更多
@@ -377,15 +417,15 @@ const handleScroll = (e: any) => {
currentPage.value++; currentPage.value++;
getFavoriteSongs(); getFavoriteSongs();
} }
}; };
onMounted(async () => { onMounted(async () => {
await playerStore.initializeFavoriteList(); await playerStore.initializeFavoriteList();
await getFavoriteSongs(); await getFavoriteSongs();
}); });
// 监听收藏列表变化 // 监听收藏列表变化
watch( watch(
favoriteList, favoriteList,
() => { () => {
currentPage.value = 1; currentPage.value = 1;
@@ -393,44 +433,44 @@ watch(
getFavoriteSongs(); getFavoriteSongs();
}, },
{ deep: true, immediate: true } { deep: true, immediate: true }
); );
const handlePlay = () => { const handlePlay = () => {
playerStore.setPlayList(favoriteSongs.value); playerStore.setPlayList(favoriteSongs.value);
}; };
const getItemAnimationDelay = (index: number) => { const getItemAnimationDelay = (index: number) => {
return setAnimationDelay(index, 30); return setAnimationDelay(index, 30);
}; };
const router = useRouter(); const router = useRouter();
const handleMore = () => { const handleMore = () => {
router.push('/history'); router.push('/history');
}; };
// 全选相关 // 全选相关
const isAllSelected = computed(() => { const isAllSelected = computed(() => {
return ( return (
favoriteSongs.value.length > 0 && selectedSongs.value.length === favoriteSongs.value.length favoriteSongs.value.length > 0 && selectedSongs.value.length === favoriteSongs.value.length
); );
}); });
const isIndeterminate = computed(() => { const isIndeterminate = computed(() => {
return selectedSongs.value.length > 0 && selectedSongs.value.length < favoriteSongs.value.length; return selectedSongs.value.length > 0 && selectedSongs.value.length < favoriteSongs.value.length;
}); });
// 处理全选/取消全选 // 处理全选/取消全选
const handleSelectAll = (checked: boolean) => { const handleSelectAll = (checked: boolean) => {
if (checked) { if (checked) {
selectedSongs.value = favoriteSongs.value.map((song) => song.id as number); selectedSongs.value = favoriteSongs.value.map((song) => song.id as number);
} else { } else {
selectedSongs.value = []; selectedSongs.value = [];
} }
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.favorite-page { .favorite-page {
@apply h-full flex flex-col pt-2; @apply h-full flex flex-col pt-2;
@apply bg-light dark:bg-black; @apply bg-light dark:bg-black;
@@ -451,8 +491,40 @@ const handleSelectAll = (checked: boolean) => {
} }
&-right { &-right {
@apply flex items-center gap-3;
.sort-controls {
@apply flex items-center; @apply flex items-center;
.sort-buttons {
@apply flex items-center bg-gray-100 dark:bg-gray-800 rounded-full overflow-hidden;
@apply border border-gray-200 dark:border-gray-700;
.sort-button {
@apply flex items-center py-1 px-3 text-sm cursor-pointer;
@apply text-gray-600 dark:text-gray-400;
@apply transition-all duration-300;
&:first-child {
@apply border-r border-gray-200 dark:border-gray-700;
}
.iconfont {
@apply mr-1 text-base;
}
&.active {
@apply bg-green-400 text-primary dark:text-gray-200;
@apply font-medium;
}
&:hover:not(.active) {
@apply bg-gray-200 dark:bg-gray-700;
}
}
}
}
.select-btn { .select-btn {
@apply rounded-full px-4 h-8; @apply rounded-full px-4 h-8;
@apply transition-all duration-300 ease-in-out; @apply transition-all duration-300 ease-in-out;
@@ -524,18 +596,18 @@ const handleSelectAll = (checked: boolean) => {
} }
} }
} }
} }
.loading-wrapper { .loading-wrapper {
@apply flex justify-center items-center py-20; @apply flex justify-center items-center py-20;
} }
.no-more-tip { .no-more-tip {
@apply text-center py-4 text-sm; @apply text-center py-4 text-sm;
@apply text-gray-500 dark:text-gray-400; @apply text-gray-500 dark:text-gray-400;
} }
.mobile { .mobile {
.favorite-page { .favorite-page {
@apply p-4; @apply p-4;
@@ -547,5 +619,5 @@ const handleSelectAll = (checked: boolean) => {
} }
} }
} }
} }
</style> </style>