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',
downloadFailed: 'Download failed',
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: '下载完成',
downloadFailed: '下载失败',
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 class="favorite-header" :class="setAnimationClass('animate__fadeInLeft')">
<div class="favorite-header-left">
@@ -6,6 +6,26 @@
<div class="favorite-count">{{ t('favorite.count', { count: favoriteList.length }) }}</div>
</div>
<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
v-if="!isSelecting"
secondary
@@ -80,60 +100,60 @@
</n-scrollbar>
</div>
</div>
</template>
</template>
<script setup lang="ts">
import { cloneDeep } from 'lodash';
import { useMessage } from 'naive-ui';
import { computed, onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
<script setup lang="ts">
import { cloneDeep } from 'lodash';
import { useMessage } from 'naive-ui';
import { computed, onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { getMusicDetail } from '@/api/music';
import { getBilibiliProxyUrl, getBilibiliVideoDetail } from '@/api/bilibili';
import SongItem from '@/components/common/SongItem.vue';
import { getSongUrl } from '@/hooks/MusicListHook';
import { usePlayerStore } from '@/store';
import type { SongResult } from '@/type/music';
import { isElectron, setAnimationClass, setAnimationDelay } from '@/utils';
import { getMusicDetail } from '@/api/music';
import { getBilibiliProxyUrl, getBilibiliVideoDetail } from '@/api/bilibili';
import SongItem from '@/components/common/SongItem.vue';
import { getSongUrl } from '@/hooks/MusicListHook';
import { usePlayerStore } from '@/store';
import type { SongResult } from '@/type/music';
import { isElectron, setAnimationClass, setAnimationDelay } from '@/utils';
const { t } = useI18n();
const playerStore = usePlayerStore();
const message = useMessage();
const favoriteList = computed(() => playerStore.favoriteList);
const favoriteSongs = ref<SongResult[]>([]);
const loading = ref(false);
const noMore = ref(false);
const scrollbarRef = ref();
const { t } = useI18n();
const playerStore = usePlayerStore();
const message = useMessage();
const favoriteList = computed(() => playerStore.favoriteList);
const favoriteSongs = ref<SongResult[]>([]);
const loading = ref(false);
const noMore = ref(false);
const scrollbarRef = ref();
// 多选相关
const isSelecting = ref(false);
const selectedSongs = ref<number[]>([]);
const isDownloading = ref(false);
// 多选相关
const isSelecting = ref(false);
const selectedSongs = ref<number[]>([]);
const isDownloading = ref(false);
// 开始多选
const startSelect = () => {
// 开始多选
const startSelect = () => {
isSelecting.value = true;
selectedSongs.value = [];
};
};
// 取消多选
const cancelSelect = () => {
// 取消多选
const cancelSelect = () => {
isSelecting.value = false;
selectedSongs.value = [];
};
};
// 处理选择
const handleSelect = (songId: number, selected: boolean) => {
// 处理选择
const handleSelect = (songId: number, selected: boolean) => {
if (selected) {
selectedSongs.value.push(songId);
} else {
selectedSongs.value = selectedSongs.value.filter((id) => id !== songId);
}
};
};
// 批量下载
const handleBatchDownload = async () => {
// 批量下载
const handleBatchDownload = async () => {
if (isDownloading.value) {
message.warning(t('favorite.downloading'));
return;
@@ -215,29 +235,49 @@ const handleBatchDownload = async () => {
message.destroyAll();
message.error(t('favorite.downloadFailed'));
}
};
};
// 无限滚动相关
const pageSize = 100;
const currentPage = ref(1);
// 排序相关
const isDescending = ref(true); // 默认倒序显示
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: {
type: Boolean,
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 endIndex = startIndex + pageSize;
// 返回原始ID,不进行类型转换
return favoriteList.value.slice(startIndex, endIndex);
};
return ids.slice(startIndex, endIndex);
};
// 获取收藏歌曲详情
const getFavoriteSongs = async () => {
// 获取收藏歌曲详情
const getFavoriteSongs = async () => {
if (favoriteList.value.length === 0) {
favoriteSongs.value = [];
return;
@@ -366,10 +406,10 @@ const getFavoriteSongs = async () => {
} finally {
loading.value = false;
}
};
};
// 处理滚动事件
const handleScroll = (e: any) => {
// 处理滚动事件
const handleScroll = (e: any) => {
const { scrollTop, scrollHeight, offsetHeight } = e.target;
const threshold = 100; // 距离底部多少像素时加载更多
@@ -377,15 +417,15 @@ const handleScroll = (e: any) => {
currentPage.value++;
getFavoriteSongs();
}
};
};
onMounted(async () => {
onMounted(async () => {
await playerStore.initializeFavoriteList();
await getFavoriteSongs();
});
});
// 监听收藏列表变化
watch(
// 监听收藏列表变化
watch(
favoriteList,
() => {
currentPage.value = 1;
@@ -393,44 +433,44 @@ watch(
getFavoriteSongs();
},
{ deep: true, immediate: true }
);
);
const handlePlay = () => {
const handlePlay = () => {
playerStore.setPlayList(favoriteSongs.value);
};
};
const getItemAnimationDelay = (index: number) => {
const getItemAnimationDelay = (index: number) => {
return setAnimationDelay(index, 30);
};
};
const router = useRouter();
const handleMore = () => {
const router = useRouter();
const handleMore = () => {
router.push('/history');
};
};
// 全选相关
const isAllSelected = computed(() => {
// 全选相关
const isAllSelected = computed(() => {
return (
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;
});
});
// 处理全选/取消全选
const handleSelectAll = (checked: boolean) => {
// 处理全选/取消全选
const handleSelectAll = (checked: boolean) => {
if (checked) {
selectedSongs.value = favoriteSongs.value.map((song) => song.id as number);
} else {
selectedSongs.value = [];
}
};
</script>
};
</script>
<style lang="scss" scoped>
.favorite-page {
<style lang="scss" scoped>
.favorite-page {
@apply h-full flex flex-col pt-2;
@apply bg-light dark:bg-black;
@@ -451,8 +491,40 @@ const handleSelectAll = (checked: boolean) => {
}
&-right {
@apply flex items-center gap-3;
.sort-controls {
@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 {
@apply rounded-full px-4 h-8;
@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;
}
}
.no-more-tip {
.no-more-tip {
@apply text-center py-4 text-sm;
@apply text-gray-500 dark:text-gray-400;
}
}
.mobile {
.mobile {
.favorite-page {
@apply p-4;
@@ -547,5 +619,5 @@ const handleSelectAll = (checked: boolean) => {
}
}
}
}
</style>
}
</style>