feat: 优化主题效果 添加展开 menu功能 优化图片清晰度 添加随机播放功能(#20)

This commit is contained in:
alger
2024-12-29 00:43:39 +08:00
parent abdb2bcd50
commit dfdf02a17f
15 changed files with 118 additions and 130 deletions

3
components.d.ts vendored
View File

@@ -27,10 +27,7 @@ declare module 'vue' {
NLayout: typeof import('naive-ui')['NLayout']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NPagination: typeof import('naive-ui')['NPagination']
NPopover: typeof import('naive-ui')['NPopover']
NRadioButton: typeof import('naive-ui')['NRadioButton']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSlider: typeof import('naive-ui')['NSlider']
NSpin: typeof import('naive-ui')['NSpin']

View File

@@ -25,24 +25,24 @@
<div class="music-info">
<div class="music-cover">
<n-image
:src="getImgUrl(cover ? listInfo?.coverImgUrl : displayedSongs[0]?.picUrl, '300y300')"
:src="getImgUrl(cover ? listInfo?.coverImgUrl : displayedSongs[0]?.picUrl, '500y500')"
class="cover-img"
preview-disabled
:class="setAnimationClass('animate__fadeIn')"
object-fit="cover"
/>
</div>
<div class="music-detail">
<div v-if="listInfo?.creator" class="creator-info">
<n-avatar round :size="24" :src="getImgUrl(listInfo.creator.avatarUrl, '50y50')" />
<span class="creator-name">{{ listInfo.creator.nickname }}</span>
</div>
<div v-if="listInfo?.description" class="music-desc">
<n-ellipsis :line-clamp="isMobile ? 3 : 10">
{{ listInfo.description }}
</n-ellipsis>
</div>
<div v-if="listInfo?.creator" class="creator-info">
<n-avatar round :size="24" :src="getImgUrl(listInfo.creator.avatarUrl, '50y50')" />
<span class="creator-name">{{ listInfo.creator.nickname }}</span>
</div>
<n-scrollbar style="max-height: 200">
<div v-if="listInfo?.description" class="music-desc">
{{ listInfo.description }}
</div>
<play-bottom />
</n-scrollbar>
</div>
<!-- 右侧歌曲列表 -->
@@ -248,25 +248,21 @@ watch(
@apply w-[25%] flex-shrink-0 pr-8 flex flex-col;
.music-cover {
@apply w-full aspect-square rounded-2xl overflow-hidden mb-4;
@apply w-full aspect-square rounded-2xl overflow-hidden mb-4 min-h-[250px];
.cover-img {
@apply w-full h-full object-cover;
}
}
.music-detail {
@apply flex flex-col flex-grow;
.creator-info {
@apply flex items-center mb-4;
.creator-name {
@apply ml-2 text-gray-700 dark:text-gray-300;
}
.creator-info {
@apply flex items-center mb-4;
.creator-name {
@apply ml-2 text-gray-700 dark:text-gray-300;
}
}
.music-desc {
@apply text-sm text-gray-600 dark:text-gray-400 leading-relaxed;
}
.music-desc {
@apply text-sm text-gray-600 dark:text-gray-400 leading-relaxed pr-4;
}
}

View File

@@ -136,7 +136,7 @@ onMounted(() => {
}
.play-list-type {
width: 250px;
@apply mx-6;
@apply mr-4;
&-item,
&-showall {
@apply bg-light dark:bg-black text-gray-900 dark:text-white;

View File

@@ -10,7 +10,7 @@
:style="setAnimationDelay(0, 100)"
>
<div
:style="setBackgroundImg(getImgUrl(dayRecommendData?.dailySongs[0].al.picUrl, '300y300'))"
:style="setBackgroundImg(getImgUrl(dayRecommendData?.dailySongs[0].al.picUrl, '500y500'))"
class="recommend-singer-item-bg"
></div>
<div
@@ -34,7 +34,7 @@
:class="setAnimationClass('animate__backInRight')"
:style="setAnimationDelay(index + 1, 100)"
>
<div :style="setBackgroundImg(getImgUrl(item.picUrl, '300y300'))" class="recommend-singer-item-bg"></div>
<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">{{ item.musicSize }}</div>
<div class="recommend-singer-item-info z-10">
<div class="recommend-singer-item-info-play" @click="toSearchSinger(item.name)">

View File

@@ -1,73 +0,0 @@
<template>
<n-drawer :show="show" height="100vh" placement="bottom" :z-index="999999999">
<div class="mv-detail">
<video :src="url" controls autoplay></video>
<div class="mv-detail-title">
<div class="title">{{ title }}</div>
<button @click="close">
<i class="iconfont icon-xiasanjiaoxing"></i>
</button>
</div>
</div>
</n-drawer>
</template>
<script setup lang="ts">
import { useStore } from 'vuex';
import { audioService } from '@/services/audioService';
const props = defineProps<{
show: boolean;
title: string;
url: string;
}>();
const store = useStore();
watch(
() => props.show,
(val) => {
if (val) {
store.commit('setIsPlay', false);
store.commit('setPlayMusic', false);
audioService.getCurrentSound()?.pause();
}
},
);
const emit = defineEmits(['update:show']);
const close = () => {
emit('update:show', false);
};
</script>
<style scoped lang="scss">
.mv-detail {
@apply w-full h-full bg-black relative;
&-title {
@apply absolute w-full left-0 flex justify-between h-16 px-6 py-2 text-xl font-bold items-center z-50 transition-all duration-300 ease-in-out -top-24;
background: linear-gradient(0, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%);
button .icon-xiasanjiaoxing {
@apply text-3xl;
}
button:hover {
@apply text-green-400;
}
}
video {
@apply w-full h-full;
}
video:hover + .mv-detail-title {
@apply top-0;
}
.mv-detail-title:hover {
@apply top-0;
}
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="search-item" :class="item.type" @click="handleClick">
<div class="search-item-img">
<n-image :src="getImgUrl(item.picUrl, '320y180')" lazy preview-disabled />
<n-image :src="getImgUrl(item.picUrl, item.type === 'mv' ? '320y180' : '100y100')" lazy preview-disabled />
<div v-if="item.type === 'mv'" class="play">
<i class="iconfont icon icon-play"></i>
</div>
@@ -17,6 +17,7 @@
:name="item.name"
:song-list="songList"
:list-info="listInfo"
:cover="false"
/>
<mv-player v-if="item.type === 'mv'" v-model:show="showPop" :current-mv="getCurrentMv()" no-list />
</div>
@@ -64,6 +65,14 @@ const handleClick = async () => {
song.al.picUrl = song.al.picUrl || props.item.picUrl;
return song;
});
listInfo.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,
};
}
if (props.item.type === 'playlist') {
@@ -84,7 +93,7 @@ const handleClick = async () => {
<style scoped lang="scss">
.search-item {
@apply rounded-3xl p-3 flex items-center hover:bg-gray-800 transition cursor-pointer;
@apply rounded-3xl p-3 flex items-center hover:bg-light-200 dark:hover:bg-gray-800 transition cursor-pointer;
margin: 0 10px;
.search-item-img {
@apply w-12 h-12 mr-4 rounded-2xl overflow-hidden;

View File

@@ -3,7 +3,7 @@
<n-image
v-if="item.picUrl"
ref="songImg"
:src="getImgUrl(item.picUrl, '40y40')"
:src="getImgUrl(item.picUrl, '100y100')"
class="song-item-img"
preview-disabled
:img-props="{

View File

@@ -111,6 +111,22 @@ export const audioServiceOn = (audio: typeof audioService) => {
if (store.state.playMode === 1) {
// 单曲循环模式
audio.getCurrentSound()?.play();
} else if (store.state.playMode === 2) {
// 随机播放模式
const { playList } = store.state;
if (playList.length <= 1) {
// 如果播放列表只有一首歌或为空,则重新播放当前歌曲
audio.getCurrentSound()?.play();
} else {
// 随机选择一首不同的歌
let randomIndex;
do {
randomIndex = Math.floor(Math.random() * playList.length);
} while (randomIndex === store.state.playListIndex && playList.length > 1);
store.state.playListIndex = randomIndex;
store.commit('setPlay', playList[randomIndex]);
}
} else {
// 列表循环模式
store.commit('nextPlay');
@@ -229,7 +245,7 @@ export const useLyricProgress = () => {
};
};
// 设置<EFBFBD><EFBFBD><EFBFBD>前播放时间
// 设置前播放时间
export const setAudioTime = (index: number) => {
const currentSound = sound.value;
if (!currentSound) return;

View File

@@ -1,9 +1,9 @@
<template>
<div>
<!-- menu -->
<div class="app-menu">
<div class="app-menu" :class="{ 'app-menu-expanded': isText }">
<div class="app-menu-header">
<div class="app-menu-logo">
<div class="app-menu-logo" @click="isText = !isText">
<img src="/icon.png" class="w-9 h-9" alt="logo" />
</div>
</div>
@@ -11,7 +11,9 @@
<div v-for="(item, index) in menus" :key="item.path" class="app-menu-item">
<router-link class="app-menu-item-link" :to="item.path">
<i class="iconfont app-menu-item-icon" :style="iconStyle(index)" :class="item.meta.icon"></i>
<span v-if="isText" class="app-menu-item-text ml-3">{{ item.meta.title }}</span>
<span v-if="isText" class="app-menu-item-text ml-3" :class="isChecked(index) ? 'text-green-500' : ''">{{
item.meta.title
}}</span>
</router-link>
</div>
</div>
@@ -23,10 +25,6 @@
import { useRoute } from 'vue-router';
const props = defineProps({
isText: {
type: Boolean,
default: false,
},
size: {
type: String,
default: '26px',
@@ -54,24 +52,40 @@ watch(
},
);
const isChecked = (index: number) => {
return path.value === props.menus[index].path;
};
const iconStyle = (index: number) => {
const style = {
fontSize: props.size,
color: path.value === props.menus[index].path ? props.selectColor : props.color,
color: isChecked(index) ? props.selectColor : props.color,
};
return style;
};
const isText = ref(false);
</script>
<style lang="scss" scoped>
.app-menu {
@apply flex-col items-center justify-center px-6 bg-light dark:bg-black;
max-width: 100px;
@apply flex-col items-center justify-center bg-light dark:bg-black transition-all duration-300 w-[100px] px-1;
}
.app-menu-expanded {
@apply w-[160px];
.app-menu-item {
@apply hover:bg-gray-100 dark:hover:bg-gray-800 rounded mr-4;
}
}
.app-menu-item-link,
.app-menu-header {
@apply flex items-center justify-center;
@apply flex items-center w-[200px] overflow-hidden ml-2 px-5;
}
.app-menu-header {
@apply ml-1;
}
.app-menu-item-link {
@@ -82,7 +96,7 @@ const iconStyle = (index: number) => {
@apply transition-all duration-200 text-gray-500 dark:text-gray-400;
&:hover {
@apply text-green-500 scale-105;
@apply text-green-500 scale-105 !important;
}
}

View File

@@ -9,7 +9,7 @@
<div id="drawer-target">
<div class="drawer-back"></div>
<div class="music-img" :style="{ color: textColors.theme === 'dark' ? '#000000' : '#ffffff' }">
<n-image ref="PicImgRef" :src="getImgUrl(playMusic?.picUrl, '300y300')" class="img" lazy preview-disabled />
<n-image ref="PicImgRef" :src="getImgUrl(playMusic?.picUrl, '500y500')" class="img" lazy preview-disabled />
<div>
<div class="music-content-name">{{ playMusic.name }}</div>
<div class="music-content-singer">

View File

@@ -20,7 +20,7 @@
<n-slider v-model:value="timeSlider" :step="1" :max="allTime" :min="0" :format-tooltip="formatTooltip"></n-slider>
</div>
<div class="play-bar-img-wrapper" @click="setMusicFull">
<n-image :src="getImgUrl(playMusic?.picUrl, '300y300')" class="play-bar-img" lazy preview-disabled />
<n-image :src="getImgUrl(playMusic?.picUrl, '500y500')" class="play-bar-img" lazy preview-disabled />
<div class="hover-arrow">
<div class="hover-content">
<!-- <i class="ri-arrow-up-s-line text-3xl" :class="{ 'ri-arrow-down-s-line': musicFullVisible }"></i> -->
@@ -202,10 +202,28 @@ const mute = () => {
// 播放模式
const playMode = computed(() => store.state.playMode);
const playModeIcon = computed(() => {
return playMode.value === 0 ? 'ri-repeat-2-line' : 'ri-repeat-one-line';
switch (playMode.value) {
case 0:
return 'ri-repeat-2-line';
case 1:
return 'ri-repeat-one-line';
case 2:
return 'ri-shuffle-line';
default:
return 'ri-repeat-2-line';
}
});
const playModeText = computed(() => {
return playMode.value === 0 ? '列表循环' : '单曲循环';
switch (playMode.value) {
case 0:
return '列表循环';
case 1:
return '单曲循环';
case 2:
return '随机播放';
default:
return '列表循环';
}
});
// 切换播放模式

View File

@@ -59,7 +59,7 @@ const layoutRouter = [
name: 'history',
component: () => import('@/views/historyAndFavorite/index.vue'),
meta: {
title: '我的收藏历史',
title: '收藏历史',
icon: 'icon-a-TicketStar',
keepAlive: true,
},

View File

@@ -102,7 +102,7 @@ const mutations = {
localStorage.setItem('favoriteList', JSON.stringify(state.favoriteList));
},
togglePlayMode(state: State) {
state.playMode = state.playMode === 0 ? 1 : 0;
state.playMode = (state.playMode + 1) % 3;
localStorage.setItem('playMode', JSON.stringify(state.playMode));
},
toggleTheme(state: State) {

View File

@@ -2,8 +2,8 @@
<div class="list-page">
<!-- 修改歌单分类部分 -->
<div class="play-list-type">
<n-scrollbar x-scrollable>
<div class="categories-wrapper">
<n-scrollbar ref="scrollbarRef" x-scrollable>
<div class="categories-wrapper" @wheel.prevent="handleWheel">
<span
v-for="(item, index) in playlistCategory?.sub"
:key="item.name"
@@ -122,7 +122,7 @@ const loadList = async (type: string, isLoadMore = false) => {
try {
const params = {
cat: type || '',
cat: type === '每日推荐' ? '' : type,
limit: TOTAL_ITEMS,
offset: page.value * TOTAL_ITEMS,
};
@@ -181,6 +181,16 @@ const handleClickPlaylistType = (type: string) => {
loadList(type);
};
const scrollbarRef = ref();
const handleWheel = (e: WheelEvent) => {
const scrollbar = scrollbarRef.value;
if (scrollbar) {
const delta = e.deltaY || e.detail;
scrollbar.scrollBy({ left: delta });
}
};
onMounted(() => {
loadPlaylistCategory(); // 添加加载歌单分类
currentType.value = (route.query.type as string) || currentType.value;

View File

@@ -92,9 +92,10 @@ watch(selectedCategory, async () => {
await loadMvList();
});
const getAnimationDelay = computed(() => {
return (index: number) => setAnimationDelay(index, 30);
});
const getAnimationDelay = (index: number) => {
const currentPageIndex = index % limit.value;
return setAnimationDelay(currentPageIndex, 30);
};
onMounted(async () => {
await loadMvList();