mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-05 07:20:50 +08:00
293 lines
6.9 KiB
Vue
293 lines
6.9 KiB
Vue
<template>
|
|
<div class="song-item" :class="{ 'song-mini': mini, 'song-list': list }">
|
|
<n-image
|
|
v-if="item.picUrl"
|
|
ref="songImg"
|
|
:src="getImgUrl(item.picUrl, '40y40')"
|
|
class="song-item-img"
|
|
preview-disabled
|
|
:img-props="{
|
|
crossorigin: 'anonymous',
|
|
}"
|
|
@load="imageLoad"
|
|
/>
|
|
<div class="song-item-content">
|
|
<div v-if="list" class="song-item-content-wrapper">
|
|
<n-ellipsis class="song-item-content-title text-ellipsis" line-clamp="1">{{ item.name }}</n-ellipsis>
|
|
<div class="song-item-content-divider">-</div>
|
|
<n-ellipsis class="song-item-content-name text-ellipsis" line-clamp="1">
|
|
<span v-for="(artists, artistsindex) in item.ar || item.song.artists" :key="artistsindex"
|
|
>{{ artists.name }}{{ artistsindex < (item.ar || item.song.artists).length - 1 ? ' / ' : '' }}</span
|
|
>
|
|
</n-ellipsis>
|
|
</div>
|
|
<template v-else>
|
|
<div class="song-item-content-title">
|
|
<n-ellipsis class="text-ellipsis" line-clamp="1">{{ item.name }}</n-ellipsis>
|
|
</div>
|
|
<div class="song-item-content-name">
|
|
<n-ellipsis class="text-ellipsis" line-clamp="1">
|
|
<span v-for="(artists, artistsindex) in item.ar || item.song.artists" :key="artistsindex"
|
|
>{{ artists.name }}{{ artistsindex < (item.ar || item.song.artists).length - 1 ? ' / ' : '' }}</span
|
|
>
|
|
</n-ellipsis>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<div class="song-item-operating" :class="{ 'song-item-operating-list': list }">
|
|
<div v-if="favorite" class="song-item-operating-like">
|
|
<i class="iconfont icon-likefill" :class="{ 'like-active': isFavorite }" @click.stop="toggleFavorite"></i>
|
|
</div>
|
|
<div
|
|
class="song-item-operating-play bg-gray-300 dark:bg-gray-800 animate__animated"
|
|
:class="{ 'bg-green-600': isPlaying, animate__flipInY: playLoading }"
|
|
@click="playMusicEvent(item)"
|
|
>
|
|
<i v-if="isPlaying && play" class="iconfont icon-stop"></i>
|
|
<i v-else class="iconfont icon-playfill"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, useTemplateRef } from 'vue';
|
|
import { useStore } from 'vuex';
|
|
|
|
import { audioService } from '@/services/audioService';
|
|
import type { SongResult } from '@/type/music';
|
|
import { getImgUrl } from '@/utils';
|
|
import { getImageBackground } from '@/utils/linearColor';
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
item: SongResult;
|
|
mini?: boolean;
|
|
list?: boolean;
|
|
favorite?: boolean;
|
|
}>(),
|
|
{
|
|
mini: false,
|
|
list: false,
|
|
favorite: true,
|
|
},
|
|
);
|
|
|
|
const store = useStore();
|
|
|
|
const play = computed(() => store.state.play as boolean);
|
|
|
|
const playMusic = computed(() => store.state.playMusic);
|
|
|
|
const playLoading = computed(() => playMusic.value.id === props.item.id && playMusic.value.playLoading);
|
|
|
|
// 判断是否为正在播放的音乐
|
|
const isPlaying = computed(() => {
|
|
return playMusic.value.id === props.item.id;
|
|
});
|
|
|
|
const emits = defineEmits(['play']);
|
|
|
|
const songImageRef = useTemplateRef('songImg');
|
|
|
|
const imageLoad = async () => {
|
|
if (!songImageRef.value) {
|
|
return;
|
|
}
|
|
const { backgroundColor } = await getImageBackground(
|
|
(songImageRef.value as any).imageRef as unknown as HTMLImageElement,
|
|
);
|
|
// eslint-disable-next-line vue/no-mutating-props
|
|
props.item.backgroundColor = backgroundColor;
|
|
};
|
|
|
|
// 播放音乐 设置音乐详情 打开音乐底栏
|
|
const playMusicEvent = async (item: SongResult) => {
|
|
if (playMusic.value.id === item.id) {
|
|
if (play.value) {
|
|
store.commit('setPlayMusic', false);
|
|
audioService.getCurrentSound()?.pause();
|
|
} else {
|
|
store.commit('setPlayMusic', true);
|
|
audioService.getCurrentSound()?.play();
|
|
}
|
|
return;
|
|
}
|
|
await store.commit('setPlay', item);
|
|
store.commit('setIsPlay', true);
|
|
emits('play', item);
|
|
};
|
|
|
|
// 判断是否已收藏
|
|
const isFavorite = computed(() => {
|
|
return store.state.favoriteList.includes(props.item.id);
|
|
});
|
|
|
|
// 切换收藏状态
|
|
const toggleFavorite = async (e: Event) => {
|
|
e.stopPropagation();
|
|
if (isFavorite.value) {
|
|
store.commit('removeFromFavorite', props.item.id);
|
|
} else {
|
|
store.commit('addToFavorite', props.item.id);
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
// 配置文字不可选中
|
|
.text-ellipsis {
|
|
width: 100%;
|
|
}
|
|
|
|
.song-item {
|
|
-webkit-user-select: none;
|
|
-moz-user-select: none;
|
|
-ms-user-select: none;
|
|
user-select: none;
|
|
@apply rounded-3xl p-3 flex items-center transition bg-transparent dark:text-white text-gray-900;
|
|
|
|
&:hover {
|
|
@apply bg-gray-100 dark:bg-gray-800;
|
|
}
|
|
|
|
&-img {
|
|
@apply w-12 h-12 rounded-2xl mr-4;
|
|
}
|
|
|
|
&-content {
|
|
@apply flex-1;
|
|
|
|
&-title {
|
|
@apply text-base text-gray-900 dark:text-white;
|
|
}
|
|
|
|
&-name {
|
|
@apply text-xs text-gray-500 dark:text-gray-400;
|
|
}
|
|
}
|
|
|
|
&-operating {
|
|
@apply flex items-center rounded-full ml-4 border dark:border-gray-700 border-gray-200 bg-light dark:bg-black;
|
|
|
|
.iconfont {
|
|
@apply text-xl;
|
|
}
|
|
|
|
.icon-likefill {
|
|
@apply text-xl transition text-gray-500 dark:text-gray-400 hover:text-red-500;
|
|
}
|
|
|
|
&-like {
|
|
@apply mr-2 cursor-pointer ml-4;
|
|
}
|
|
|
|
.like-active {
|
|
@apply text-red-500;
|
|
}
|
|
|
|
&-play {
|
|
@apply cursor-pointer rounded-full w-10 h-10 flex justify-center items-center transition
|
|
border dark:border-gray-700 border-gray-200 text-gray-900 dark:text-white;
|
|
|
|
&:hover,
|
|
&.bg-green-600 {
|
|
@apply bg-green-500 border-green-500 text-white;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.song-mini {
|
|
@apply p-2 rounded-2xl;
|
|
|
|
.song-item {
|
|
@apply p-0;
|
|
|
|
&-img {
|
|
@apply w-10 h-10 mr-2;
|
|
}
|
|
|
|
&-content {
|
|
@apply flex-1;
|
|
|
|
&-title {
|
|
@apply text-sm;
|
|
}
|
|
|
|
&-name {
|
|
@apply text-xs;
|
|
}
|
|
}
|
|
|
|
&-operating {
|
|
@apply pl-2;
|
|
|
|
.iconfont {
|
|
@apply text-base;
|
|
}
|
|
|
|
&-like {
|
|
@apply mr-1 ml-1;
|
|
}
|
|
|
|
&-play {
|
|
@apply w-8 h-8;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.song-list {
|
|
@apply p-2 rounded-lg mb-2 border dark:border-gray-800 border-gray-200;
|
|
|
|
&:hover {
|
|
@apply bg-gray-50 dark:bg-gray-800;
|
|
}
|
|
|
|
.song-item-img {
|
|
@apply w-10 h-10 rounded-lg mr-3;
|
|
}
|
|
|
|
.song-item-content {
|
|
@apply flex items-center flex-1;
|
|
|
|
&-wrapper {
|
|
@apply flex items-center flex-1 text-sm;
|
|
}
|
|
|
|
&-title {
|
|
@apply flex-shrink-0 max-w-[45%] text-gray-900 dark:text-white;
|
|
}
|
|
|
|
&-divider {
|
|
@apply mx-2 text-gray-500 dark:text-gray-400;
|
|
}
|
|
|
|
&-name {
|
|
@apply flex-1 min-w-0 text-gray-500 dark:text-gray-400;
|
|
}
|
|
}
|
|
|
|
.song-item-operating {
|
|
@apply flex items-center gap-2;
|
|
|
|
&-like {
|
|
@apply cursor-pointer hover:scale-110 transition-transform;
|
|
|
|
.iconfont {
|
|
@apply text-base text-gray-500 dark:text-gray-400 hover:text-red-500;
|
|
}
|
|
}
|
|
|
|
&-play {
|
|
@apply w-7 h-7 cursor-pointer hover:scale-110 transition-transform;
|
|
|
|
.iconfont {
|
|
@apply text-base;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|