feat: 更新依赖和配置,增强开发体验

- 在 electron.vite.config.ts 中启用 Vue DevTools 插件
- 更新 package.json 中多个依赖版本,确保兼容性和性能
- 调整 tsconfig.node.json 的配置,优化模块解析
- 删除不再使用的组件 PlaylistType.vue、RecommendAlbum.vue、RecommendSinger.vue 和 RecommendSonglist.vue
- 在请求处理逻辑中改进错误日志输出,使用 console.error 替代 console.log
- 在首页视图中替换推荐歌手组件为顶部横幅组件

这些更改旨在提升开发效率和用户体验,确保项目的稳定性和可维护性。
This commit is contained in:
algerkong
2025-03-19 21:25:32 +08:00
parent df9a1370c3
commit 4fa5ed0ca6
14 changed files with 293 additions and 229 deletions
@@ -0,0 +1,166 @@
<template>
<!-- 歌单分类列表 -->
<div class="play-list-type">
<div class="title" :class="setAnimationClass('animate__fadeInLeft')">
{{ t('comp.playlistType.title') }}
</div>
<div>
<template v-for="(item, index) in playlistCategory?.sub" :key="item.name">
<span
v-show="isShowAllPlaylistCategory || index <= 19 || isHiding"
class="play-list-type-item"
:class="
setAnimationClass(
index <= 19
? 'animate__bounceIn'
: !isShowAllPlaylistCategory
? 'animate__backOutLeft'
: 'animate__bounceIn'
) +
' ' +
'type-item-' +
index
"
:style="getAnimationDelay(index)"
@click="handleClickPlaylistType(item.name)"
>{{ item.name }}</span
>
</template>
<div
class="play-list-type-showall"
:class="setAnimationClass('animate__bounceIn')"
:style="
setAnimationDelay(
!isShowAllPlaylistCategory ? 25 : playlistCategory?.sub.length || 100 + 30
)
"
@click="handleToggleShowAllPlaylistCategory"
>
{{
!isShowAllPlaylistCategory ? t('comp.playlistType.showAll') : t('comp.playlistType.hide')
}}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { getPlaylistCategory } from '@/api/home';
import type { IPlayListSort } from '@/type/playlist';
import { setAnimationClass, setAnimationDelay } from '@/utils';
const { t } = useI18n();
// 歌单分类
const playlistCategory = ref<IPlayListSort>();
// 是否显示全部歌单分类
const isShowAllPlaylistCategory = ref<boolean>(false);
const DELAY_TIME = 40;
const getAnimationDelay = computed(() => {
return (index: number) => {
if (index <= 19) {
return setAnimationDelay(index, DELAY_TIME);
}
if (!isShowAllPlaylistCategory.value) {
const nowIndex = (playlistCategory.value?.sub.length || 0) - index;
return setAnimationDelay(nowIndex, DELAY_TIME);
}
return setAnimationDelay(index - 19, DELAY_TIME);
};
});
watch(isShowAllPlaylistCategory, (newVal) => {
if (!newVal) {
const elements = playlistCategory.value?.sub.map((_, index) =>
document.querySelector(`.type-item-${index}`)
) as HTMLElement[];
elements
.slice(20)
.reverse()
.forEach((element, index) => {
if (element) {
setTimeout(
() => {
(element as HTMLElement).style.position = 'absolute';
},
index * DELAY_TIME + 400
);
}
});
setTimeout(
() => {
isHiding.value = false;
document.querySelectorAll('.play-list-type-item').forEach((element) => {
if (element) {
console.log('element', element);
(element as HTMLElement).style.position = 'none';
}
});
},
(playlistCategory.value?.sub.length || 0 - 19) * DELAY_TIME
);
} else {
document.querySelectorAll('.play-list-type-item').forEach((element) => {
if (element) {
(element as HTMLElement).style.position = 'none';
}
});
}
});
// 加载歌单分类
const loadPlaylistCategory = async () => {
const { data } = await getPlaylistCategory();
playlistCategory.value = data;
};
const router = useRouter();
const handleClickPlaylistType = (type: string) => {
router.push({
path: '/list',
query: {
type
}
});
};
const isHiding = ref<boolean>(false);
const handleToggleShowAllPlaylistCategory = () => {
isShowAllPlaylistCategory.value = !isShowAllPlaylistCategory.value;
if (!isShowAllPlaylistCategory.value) {
isHiding.value = true;
}
};
// 页面初始化
onMounted(() => {
loadPlaylistCategory();
});
</script>
<style lang="scss" scoped>
.title {
@apply text-lg font-bold mb-4 text-gray-900 dark:text-white;
}
.play-list-type {
width: 250px;
@apply mr-4;
&-item,
&-showall {
@apply bg-light dark:bg-black text-gray-900 dark:text-white;
@apply py-2 px-3 mr-3 mb-3 inline-block border border-gray-200 dark:border-gray-700 rounded-xl cursor-pointer hover:bg-green-600 hover:text-white transition;
}
&-showall {
@apply block text-center;
}
}
.mobile {
.play-list-type {
@apply mx-0 w-full;
}
}
</style>
@@ -0,0 +1,111 @@
<template>
<div class="recommend-album">
<div class="title" :class="setAnimationClass('animate__fadeInRight')">
{{ t('comp.recommendAlbum.title') }}
</div>
<div class="recommend-album-list">
<template v-for="(item, index) in albumData?.albums" :key="item.id">
<div
v-if="index < 6"
class="recommend-album-list-item"
:class="setAnimationClass('animate__backInUp')"
:style="setAnimationDelay(index, 100)"
@click="handleClick(item)"
>
<n-image
class="recommend-album-list-item-img"
:src="getImgUrl(item.blurPicUrl, '200y200')"
lazy
preview-disabled
/>
<div class="recommend-album-list-item-content">{{ item.name }}</div>
</div>
</template>
</div>
<music-list
v-model:show="showMusic"
:name="albumName"
:song-list="songList"
:cover="false"
:loading="loadingList"
:list-info="albumInfo"
/>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { getNewAlbum } from '@/api/home';
import { getAlbum } from '@/api/list';
import MusicList from '@/components/MusicList.vue';
import type { IAlbumNew } from '@/type/album';
import { getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils';
const { t } = useI18n();
const albumData = ref<IAlbumNew>();
const loadAlbumList = async () => {
const { data } = await getNewAlbum();
albumData.value = data;
};
const showMusic = ref(false);
const songList = ref([]);
const albumName = ref('');
const loadingList = ref(false);
const albumInfo = ref<any>({});
const handleClick = async (item: any) => {
songList.value = [];
albumInfo.value = {};
albumName.value = item.name;
loadingList.value = true;
showMusic.value = true;
const res = await getAlbum(item.id);
songList.value = res.data.songs.map((song: any) => {
song.al.picUrl = song.al.picUrl || item.picUrl;
return song;
});
albumInfo.value = {
...res.data.album,
creator: {
avatarUrl: res.data.album.artist.img1v1Url,
nickname: `${res.data.album.artist.name} - ${res.data.album.company}`
},
description: res.data.album.description
};
loadingList.value = false;
};
onMounted(() => {
loadAlbumList();
});
</script>
<style lang="scss" scoped>
.recommend-album {
@apply flex-1 mx-5;
.title {
@apply text-lg font-bold mb-4 text-gray-900 dark:text-white;
}
.recommend-album-list {
@apply grid grid-cols-2 grid-rows-3 gap-2;
&-item {
@apply rounded-xl overflow-hidden relative;
&-img {
@apply rounded-xl transition w-full h-full;
}
&:hover img {
filter: brightness(50%);
}
&-content {
@apply w-full h-full opacity-0 transition absolute z-10 top-0 left-0 p-4 text-xl text-white bg-opacity-60 bg-black dark:bg-opacity-60 dark:bg-black;
}
&-content:hover {
opacity: 1;
}
}
}
}
</style>
@@ -0,0 +1,71 @@
<template>
<div class="recommend-music">
<div class="title" :class="setAnimationClass('animate__fadeInLeft')">
{{ t('comp.recommendSonglist.title') }}
</div>
<div
v-show="recommendMusic?.result"
v-loading="loading"
class="recommend-music-list"
:class="setAnimationClass('animate__bounceInUp')"
>
<!-- 推荐音乐列表 -->
<template v-for="(item, index) in recommendMusic?.result" :key="item.id">
<div
:class="setAnimationClass('animate__bounceInUp')"
:style="setAnimationDelay(index, 100)"
>
<song-item :item="item" @play="handlePlay" />
</div>
</template>
</div>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from 'vue-i18n';
import { useStore } from 'vuex';
import { getRecommendMusic } from '@/api/home';
import SongItem from '@/components/common/SongItem.vue';
import type { IRecommendMusic } from '@/type/music';
import { setAnimationClass, setAnimationDelay } from '@/utils';
const { t } = useI18n();
const store = useStore();
// 推荐歌曲
const recommendMusic = ref<IRecommendMusic>();
const loading = ref(false);
// 加载推荐歌曲
const loadRecommendMusic = async () => {
loading.value = true;
const { data } = await getRecommendMusic({ limit: 10 });
recommendMusic.value = data;
loading.value = false;
};
// 页面初始化
onMounted(() => {
loadRecommendMusic();
});
const handlePlay = () => {
store.commit('setPlayList', recommendMusic.value?.result);
};
</script>
<style lang="scss" scoped>
.title {
@apply text-lg font-bold mb-4 text-gray-900 dark:text-white;
}
.recommend-music {
@apply flex-auto;
.text-ellipsis {
width: 100%;
}
&-list {
@apply rounded-3xl p-2 w-full border border-gray-200 dark:border-gray-700 bg-light dark:bg-black;
}
}
</style>
+236
View File
@@ -0,0 +1,236 @@
<template>
<div class="recommend-singer">
<div class="recommend-singer-list">
<n-carousel
slides-per-view="auto"
:show-dots="false"
:space-between="20"
draggable
show-arrow
:autoplay="false"
>
<n-carousel-item :class="setAnimationClass('animate__backInRight')" :style="setAnimationDelay(0, 100)" style="width: calc((100% / 5) - 16px)">
<div
v-if="dayRecommendData"
class="recommend-singer-item relative"
>
<div
:style="
setBackgroundImg(getImgUrl(dayRecommendData?.dailySongs[0].al.picUrl, '500y500'))
"
class="recommend-singer-item-bg"
></div>
<div
class="recommend-singer-item-count p-2 text-base text-gray-200 z-10 cursor-pointer"
@click="showMusic = true"
>
<div class="font-bold text-lg">
{{ t('comp.recommendSinger.title') }}
</div>
<div class="mt-2">
<p
v-for="item in dayRecommendData?.dailySongs.slice(0, 5)"
:key="item.id"
class="text-el"
>
{{ item.name }}
<br />
</p>
</div>
</div>
</div>
</n-carousel-item>
<n-carousel-item :class="setAnimationClass('animate__backInRight')"
:style="setAnimationDelay(1, 100)"
style="width: calc(((100% / 5) - 16px) * 3)">
<div class="user-play">
<div class="user-play-title">
{{ store.state.user?.nickname }}
</div>
<div class="user-play-item" v-for="item in userPlaylist" :key="item.id">
<div class="user-play-item-img">
<img :src="getImgUrl(item.coverImgUrl, '200y200')" alt="">
</div>
<div class="user-play-item-info">
<div class="user-play-item-info-name text- overflow-hidden">{{ item.name }}</div>
<div class="user-play-item-info-count">{{ t('common.songCount', { count: item.trackCount }) }}</div>
</div>
</div>
</div>
</n-carousel-item>
<n-carousel-item
v-for="(item, index) in hotSingerData?.artists"
:key="item.id"
:class="setAnimationClass('animate__backInRight')"
:style="setAnimationDelay(index + 1, 100)"
style="width: calc((100% / 5) - 16px)"
>
<div
class="recommend-singer-item relative"
:class="setAnimationClass('animate__backInRight')"
:style="setAnimationDelay(index + 2, 100)"
>
<div
:style="setBackgroundImg(getImgUrl(item.picUrl, '500y500'))"
class="recommend-singer-item-bg"
></div>
<div class="recommend-singer-item-count p-2 text-base text-gray-200 z-10">
{{ t('common.songCount', { count: item.musicSize }) }}
</div>
<div class="recommend-singer-item-info z-10">
<div class="recommend-singer-item-info-play" @click="toSearchSinger(item.name)">
<i class="iconfont icon-playfill text-xl"></i>
</div>
<div class="ml-4">
<div class="recommend-singer-item-info-name text-el">{{ item.name }}</div>
</div>
</div>
</div>
</n-carousel-item>
</n-carousel>
</div>
<music-list
v-if="dayRecommendData?.dailySongs.length"
v-model:show="showMusic"
:name="t('comp.recommendSinger.songlist')"
:song-list="dayRecommendData?.dailySongs"
:cover="false"
/>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStore } from 'vuex';
import { getDayRecommend, getHotSinger } from '@/api/home';
import MusicList from '@/components/MusicList.vue';
import router from '@/router';
import { IDayRecommend } from '@/type/day_recommend';
import type { IHotSinger } from '@/type/singer';
import { getImgUrl, setAnimationClass, setAnimationDelay, setBackgroundImg } from '@/utils';
import { getUserPlaylist } from '@/api/user';
import { Playlist } from '@/type/list';
const store = useStore();
const { t } = useI18n();
// 歌手信息
const hotSingerData = ref<IHotSinger>();
const dayRecommendData = ref<IDayRecommend>();
const showMusic = ref(false);
const userPlaylist = ref<Playlist[]>([]);
onMounted(async () => {
await loadData();
});
const loadData = async () => {
try {
// 第一个请求:获取热门歌手
const { data: singerData } = await getHotSinger({ offset: 0, limit: 5 });
// 第二个请求:获取每日推荐
try {
const {
data: { data: dayRecommend }
} = await getDayRecommend();
dayRecommendData.value = dayRecommend as unknown as IDayRecommend;
} catch (error) {
console.error('error', error);
}
hotSingerData.value = singerData;
if(store.state.user){
const { data: playlistData } = await getUserPlaylist(store.state.user?.userId);
userPlaylist.value = (playlistData.playlist as Playlist[]).sort((a, b) => b.playCount - a.playCount).slice(0, 3);
}
} catch (error) {
console.error('error', error);
}
};
const toSearchSinger = (keyword: string) => {
router.push({
path: '/search',
query: {
keyword
}
});
};
// 监听登录状态
watchEffect(() => {
if (store.state.user) {
loadData();
}
});
</script>
<style lang="scss" scoped>
.recommend-singer {
&-list {
@apply flex;
height: 280px;
margin-right: 20px;
}
&-item {
@apply flex-1 h-full rounded-3xl p-5 flex flex-col justify-between overflow-hidden;
&-bg {
@apply bg-gray-900 dark:bg-gray-800 bg-no-repeat bg-cover bg-center rounded-3xl absolute w-full h-full top-0 left-0 z-0;
filter: brightness(60%);
}
&-info {
@apply flex items-center p-2;
&-play {
@apply w-12 h-12 bg-green-500 rounded-full flex justify-center items-center hover:bg-green-600 cursor-pointer text-white;
}
&-name {
@apply text-gray-100 dark:text-gray-100;
}
}
&-count {
@apply text-gray-100 dark:text-gray-100;
}
}
}
.user-play{
@apply flex bg-light-100 dark:bg-dark rounded-3xl p-4 gap-4;
&-item{
@apply bg-light dark:bg-dark-100 rounded-3xl overflow-hidden w-28;
&-img{
@apply w-28 h-28 rounded-3xl overflow-hidden;
img{
@apply w-full h-full object-cover;
}
}
&-info{
@apply flex-1;
&-name{
@apply text-gray-900 dark:text-gray-100 line-clamp-1;
}
&-count{
@apply text-gray-900 dark:text-gray-100;
}
}
}
}
.mobile .recommend-singer {
&-list {
height: 180px;
@apply ml-4;
}
&-item {
@apply p-4 rounded-xl;
&-bg {
@apply rounded-xl;
}
}
}
</style>