Skip to content
Snippets Groups Projects
Commit efc4ab32 authored by 杭 羽凡's avatar 杭 羽凡
Browse files

Merge remote-tracking branch 'origin/main'

# Conflicts:
#	src/components/MusicAlbumView.vue
#	src/views/HomePage.vue
parents b4463e6d d11b16d7
No related branches found
No related tags found
No related merge requests found
......@@ -7,4 +7,5 @@ export const COMMENT_MODULE = `${API_MODULE}/comments`
export const MANAGER_MODULE = `${API_MODULE}/manager`
export const SHARE_MODULE = `${API_MODULE}/share`
export const SEARCH_MODULE = `${API_MODULE}/search`
export const RESOLVE_MODULE = `${API_MODULE}/resolve`
\ No newline at end of file
export const RESOLVE_MODULE = `${API_MODULE}/resolve`
export const ARTIST_MODULE = `${API_MODULE}/artist`
\ No newline at end of file
import {axios} from '../utils/request';
import {ARTIST_MODULE} from './_prefix';
export const getArtistInfo = (artistName) => {
const encodedName = encodeURIComponent(artistName);
return axios.get(`${ARTIST_MODULE}/search/${encodedName}`)
.then(res => res);
};
export const getSongsByArtist = (artistName) => {
const encodedName = encodeURIComponent(artistName);
return axios.get(`${ARTIST_MODULE}/songs/${encodedName}`)
.then(res => res);
};
\ No newline at end of file
......@@ -56,10 +56,17 @@ export const modifyPlaylist = (playlistModifyInfo) => {
+ playlist_id: number
+ song_id: number
*/
export const addSongToPlaylist = (songInfo) => {
return axios.post(`${PLAYLIST_MODULE}/addSong`, songInfo,
{ headers: { 'Content-Type': 'application/json' } })
.then(res => {
export const addSongToPlaylist = (addInfo) => {
return axios.post(`${PLAYLIST_MODULE}/addSong`, null, {
params: {
user_id: addInfo.user_id,
playlist_id: addInfo.playlist_id,
song_id: addInfo.song_id
},
headers: {
'Content-Type': 'application/json'
}
}).then(res => {
return res;
});
}
......
<script setup>
import {computed, nextTick, onMounted, onUnmounted, ref, watch} from "vue";
import playButton from "../icon/playButton.vue";
import pauseButton from "../icon/pauseButton.vue";
import {backgroundColor, updateBackground} from "../utils/getBackgroundColor";
import {getSongsByArtist, getArtistInfo} from "../api/artist";
import {getSongById} from "../api/resolve";
import checkMark from "../icon/checkMark.vue";
import {addSongToPlaylist, removeSongFromPlaylist} from "../api/playlist";
import {getSongsByPlaylist} from "../api/song";
import { formatTime } from '../utils/formatTime';
import { loadSongDurations } from '../utils/loadSongDurations';
const emit = defineEmits(['playSong', 'pauseSong', 'back', 'updateSongs']);
const props = defineProps({
artistName: {
type: String,
required: true
},
isPaused: {
type: Boolean,
},
currentSongId: {
type: Number,
required: true
}
});
const userToken = ref(JSON.parse(sessionStorage.getItem('user-token')));
const currentUserId = ref(userToken.value.id);
const artist = ref(null);
const hotSongs = ref([]);
const resizeObserver = ref(null);
const gradientColor = computed(() => `linear-gradient(to bottom, ${backgroundColor.value}, #1F1F1F 50%)`);
let musicHoveredIndex = ref(null);
let musicClickedIndex = ref(null);
let musicPlayIndex = ref(null);
let musicPauseIndex = ref(null);
// 添加关注状态
const isFollowing = ref(false);
// 用户第一次播放该艺人歌曲的标志
const isFirstPlay = ref(true);
// 关注/取消关注逻辑
const toggleFollow = () => {
isFollowing.value = !isFollowing.value;
// TODO: 调用后端API实现关注/取消关注
// followArtist({
// artistId: artist.value.id,
// isFollow: isFollowing.value
// });
};
// 添加喜欢歌曲的状态管理
const likedSongs = ref(new Set());
const toggleLikeSong = async (songId) => {
try {
if (!likedSongs.value.has(songId)) {
await addSongToPlaylist({
user_id: currentUserId.value,
playlist_id: 2,//我的喜欢歌单ID
song_id: songId
});
likedSongs.value.add(songId);
}else{
await removeSongFromPlaylist({
playlist_id: 2,//我的喜欢歌单ID
song_id: songId
});
likedSongs.value.delete(songId);
}
} catch (error) {
console.error("Failed to add song to favorites:", error);
}
};
// 处理窗口大小变化
const handleResize = () => {
const albums = document.querySelectorAll(".music-album-info");
const albumText = document.querySelectorAll(".album-text");
const albumContent = document.querySelector(".album-content");
// if (window.innerWidth > 0)
// 专辑隐藏
console.log(albumContent.clientWidth);
if (albumContent.clientWidth < 605) {
albums.forEach(album => {
album.style.visibility = "hidden";
});
albumText.forEach(album => {
album.style.visibility = "hidden";
});
} else {
albums.forEach(album => {
album.style.visibility = "visible";
});
albumText.forEach(album => {
album.style.visibility = "visible";
});
}
// 调整头部图片和文字大小
const artistImage = document.querySelector(".artist-image");
const artistName = document.querySelector(".artist-name");
if (albumContent.clientWidth < 420) {
artistImage.style.width = "140px";
artistImage.style.height = "140px";
artistName.style.fontSize = "60px";
} else {
artistImage.style.width = "220px";
artistImage.style.height = "220px";
artistName.style.fontSize = "80px";
}
const fixedTipArea = document.querySelector(".fixed-tips");
const fixedPlayArea = document.querySelector(".fixed-play-area");
fixedPlayArea.style.width = (albumContent.clientWidth - 20) + "px";
fixedTipArea.style.width = (albumContent.clientWidth - 16) + "px";
};
const debounce = (fn, delay) => {
let timer;
return (...args) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
};
const initializeLikedSongs = async () => {
try {
const res = await getSongsByPlaylist({
playlist_id: 2 // "我的喜欢"歌单ID
});
const likedSongIds = res.data.result.map(song => song.id);
likedSongs.value = new Set(likedSongIds);
} catch (error) {
console.error("Failed to initialize liked songs:", error);
}
};
const getRandomListeners = () => {
return Math.floor(Math.random() * (100000 - 10000 + 1)) + 10000;
};
const monthlyListeners = ref(getRandomListeners());
onMounted(() => {
getArtistInfo(props.artistName).then(res => {
artist.value = res.data.result;
updateBackground(artist.value.avatarUrl);
// 使用歌手的歌曲ID集合获取具体歌曲信息
const songPromises = artist.value.songIds.map(songId =>
getSongById({
song_id: songId
}).then(res => res.data.result)
);
// 等待所有歌曲信息获取完成
Promise.all(songPromises).then(songs => {
hotSongs.value = songs;
}).catch(error => {
console.error("Failed to fetch songs:", error);
});
});
// 初始化喜欢的歌曲集合
initializeLikedSongs();
// 设置resize观察器
resizeObserver.value = new ResizeObserver(debounce(handleResize, 50));
nextTick(() => {
const albumContent = document.querySelector(".album-content");
if (albumContent) {
resizeObserver.value.observe(albumContent);
}
});
});
onUnmounted(() => {
if (resizeObserver.value) {
resizeObserver.value.disconnect();
}
popovers.value = null;
});
const handelScroll = (event) => {
const playArea = document.querySelector(".play-area");
const fixedPlayArea = document.querySelector(".fixed-play-area");
const tipArea = document.querySelector(".tips");
const fixedTipArea = document.querySelector(".fixed-tips");
const albumContent = document.querySelector(".album-content");
const offsetHeight = albumContent.offsetTop;
const stickyPlayY = playArea.offsetTop - offsetHeight;
const stickyTipY = tipArea.offsetTop - offsetHeight;
const curOffset = offsetHeight - albumContent.getBoundingClientRect().top;
console.log(stickyPlayY, stickyTipY);
if (curOffset >= stickyPlayY) {
fixedPlayArea.style.opacity = "1";
fixedPlayArea.style.top = offsetHeight + "px";
fixedPlayArea.style.width = (albumContent.clientWidth - 20) + "px";
} else {
fixedPlayArea.style.opacity = "0";
}
if (curOffset + fixedPlayArea.scrollHeight >= stickyTipY) {
fixedTipArea.style.display = "flex";
fixedTipArea.style.top = offsetHeight + fixedPlayArea.scrollHeight + 'px';
} else {
fixedTipArea.style.display = "none";
}
}
window.onscroll = () => {
const playArea = document.querySelector(".play-area");
const fixedPlayArea = document.querySelector(".fixed-play-area");
const tipArea = document.querySelector(".tips");
const fixedTipArea = document.querySelector(".fixed-tips");
const stickyPlayY = playArea.offsetTop;
const stickyTipY = tipArea.offsetTop;
if (window.scrollY >= stickyPlayY) {
fixedPlayArea.style.opacity = "1";
} else {
fixedPlayArea.style.opacity = "0";
}
if (window.scrollY + fixedPlayArea.scrollHeight >= stickyTipY) {
fixedTipArea.style.display = "flex";
fixedTipArea.style.top = fixedPlayArea.scrollHeight + 'px';
} else {
fixedTipArea.style.display = "none";
}
}
const playFromId = (musicId) => {
if (musicId === null || musicId === undefined) {
if (hotSongs.value && hotSongs.value.length > 0) {
musicPlayIndex = hotSongs.value[0].id;
if(isFirstPlay){
isFirstPlay.value = false;
emit('updateSongs', hotSongs.value);
}
emit('playSong', hotSongs.value[0]);
}
} else {
musicPlayIndex = musicId;
const songToPlay = hotSongs.value.find(song => song.id === musicId);
if (songToPlay) {
if(isFirstPlay){
isFirstPlay.value = false;
emit('updateSongs', hotSongs.value);
}
emit('playSong', songToPlay);
}
}
musicPauseIndex = null;
};
const pauseMusic = (musicId) => {
musicPauseIndex = musicId;
// 发送暂停事件给父组件
emit('pauseSong');
};
const popovers = ref([])
const getPopoverIndex = (popover) => {
if (popover) {
popovers.value.push(popover);
}
}
const closePopover = (e) => {
popovers.value.forEach((item) => {
item.hide();
})
}
const star = (artistId) => {
alert("暂未开放");
}
const addToFavorite = (musicId) => {
}
watch(() => props.isPaused, (newValue) => {
if (newValue) {
musicPauseIndex = musicPlayIndex;
} else {
musicPauseIndex = null;
}
});
const songDurations = ref(new Map());
watch(() => hotSongs.value, (newSongs) => {
loadSongDurations(newSongs, songDurations);
}, { immediate: true });
// 监听currentSongId的变化
watch(() => props.currentSongId, (newId) => {
if (newId) {
musicPlayIndex = newId;
musicClickedIndex = newId;
musicPauseIndex = props.isPaused ? newId : null;
}
});
</script>
<template>
<div class="album-content" :style="{backgroundImage: gradientColor}" @mousewheel="handelScroll">
<div class="back-button" data-tooltip="返回" @click="$emit('back')">
<svg height="16" width="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M11.03.47a.75.75 0 0 1 0 1.06L4.56 8l6.47 6.47a.75.75 0 1 1-1.06 1.06L2.44 8 9.97.47a.75.75 0 0 1 1.06 0z"></path>
</svg>
</div>
<div class="header">
<img :src="artist?.avatarUrl" alt="" class="artist-image" @load="updateBackground"/>
<div class="header-content">
<h1 class="artist-name">{{ artist?.name }}</h1>
<div class="artist-stats">
<p>月听众 {{ monthlyListeners.toLocaleString() }}</p>
</div>
</div>
</div>
<div class="content">
<div class="play-area">
<div class="play-button">
<play-button v-if="musicPlayIndex===null||musicPauseIndex!==null"
@click="playFromId(musicPauseIndex)"
style="position: absolute; top:20%;left:24%;color: #000000"/>
<pause-button v-if="musicPlayIndex!==null&&musicPauseIndex===null"
@click="pauseMusic(musicPlayIndex)"
style="position: absolute; top:24%;left:25%;color: #000000"/>
</div>
<!-- 替换原来的el-popover为关注按钮 -->
<button class="follow-button"
:class="{ 'following': isFollowing }"
@click="toggleFollow">
{{ isFollowing ? '已关注' : '关注' }}
</button>
</div>
<div class="fixed-play-area" :style="{background :`${backgroundColor}`}">
<div class="opacity-wrapper">
<div class="play-button">
<play-button v-if="musicPlayIndex===null||musicPauseIndex!==null"
@click="playFromId(musicPauseIndex)"
style="position: absolute; top:20%;left:24%;color: #000000"/>
<pause-button v-if="musicPlayIndex!==null&&musicPauseIndex===null"
@click="pauseMusic(musicPlayIndex)"
style="position: absolute; top:24%;left:25%;color: #000000"/>
</div>
<p style="padding-left: 10px;font-weight: bold;font-size: 26px">{{ artist?.name }}</p>
</div>
</div>
<div class="tips">
<p style="position:absolute; left:45px">#</p>
<p style="position:absolute; left:140px">标题</p>
<p class="album-text" style="position:absolute; left:62%">专辑</p>
<p style="margin-left: auto; margin-right:55px">时间</p>
</div>
<div class="fixed-tips">
<p style="position:absolute; left:45px">#</p>
<p style="position:absolute; left:140px">标题</p>
<p class="album-text" style="position:absolute; left:62%">专辑</p>
<p style="margin-left: auto; margin-right:75px">时间</p>
</div>
<div class="musicList">
<div class="music-item"
v-for="music in hotSongs"
:key="music.id"
:aria-selected="musicClickedIndex === music.id ? 'True':'False'"
@mouseenter="()=>{musicHoveredIndex = music.id;}"
@mouseleave="()=>{musicHoveredIndex = -1}"
@click="musicClickedIndex=music.id"
@dblclick="playFromId(music.id)"
:style="{backgroundColor: musicClickedIndex===music.id? '#404040':
musicHoveredIndex === music.id ? 'rgba(54,54,54,0.7)' :'rgba(0,0,0,0)',
}"> <!--@click事件写在script中的函数里 无法及时触发:style中的样式!!!-->
<div
:style="{visibility: musicHoveredIndex === music.id||musicPlayIndex === music.id ? 'hidden' : 'visible' }">
{{ hotSongs.indexOf(music) + 1 }}
</div>
<play-button @click="playFromId(music.id)" style="position: absolute;left: 14px;cursor: pointer"
v-if="(musicHoveredIndex === music.id&&musicPlayIndex!==music.id)||musicPauseIndex===music.id"
:style="{color: musicPauseIndex===music.id ? '#1ed660' : 'white'}"/>
<pause-button @click="pauseMusic(music.id)"
style="color:#1ed660 ;position: absolute;left: 17px;cursor: pointer"
v-if="musicPlayIndex===music.id&&musicHoveredIndex === music.id&&musicPauseIndex!==music.id"/>
<img width="17" height="17" alt=""
style="position: absolute;left: 24px;"
v-if="musicPlayIndex===music.id&&musicHoveredIndex !== music.id&&musicPauseIndex!==music.id"
src="https://open.spotifycdn.com/cdn/images/equaliser-animated-green.f5eb96f2.gif">
<div class="music-detailed-info">
<img class="music-image"
:src="music.picPath"
alt="歌曲图片"/>
<div class="music-name-author" style="padding-left: 5px;">
<p class="music-name"
:style="{color : musicPlayIndex ===music.id? '#1ED660':''}"
:class="[musicPlayIndex === music.id ? 'music-after-click' : '']"
>{{ music.title }}</p>
<p class="music-author"
:style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}">
{{ music.artist }}</p>
</div>
</div>
<div class="music-album-info" :style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}">
{{ music.album }}
</div>
<div class="music-right-info">
<div class="like-button"
:class="{ 'liked': likedSongs.has(music.id) }"
@click="toggleLikeSong(music.id)"
:style="{ visibility: musicHoveredIndex === music.id || likedSongs.has(music.id) ? 'visible' : 'hidden' }">
<div v-if="likedSongs.has(music.id)"
class="check-mark-wrapper"
data-tooltip="从我喜欢的歌曲移除">
<check-mark class="check-mark"/>
</div>
<span v-else class="add-icon" data-tooltip="添加至我喜欢的歌曲">+</span>
</div>
<div style="margin-left: auto;margin-right: 45px; color: #b2b2b2"
:style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}"
v-show="songDurations.get(music.id) !== undefined">
{{ formatTime(songDurations.get(music.id)) }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
li {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
li:hover {
background-color: #363636;
border-radius: 12px;
}
p {
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0;
}
.header, .play-area, .tips, .musicList, .other-info {
z-index: 0;
padding: 20px;
width: 100%;
box-sizing: border-box;
user-select: none;
}
.album-content {
margin: 0;
padding: 0;
color: white;
background-color: rgb(31, 31, 31);
transition: background-color ease 0.6s;
display: flex;
flex-direction: column;
width: 100%;
overflow-x: auto; /*千万不能删,不然背景黑一半*/
}
.header {
display: flex;
padding: 40px;
align-items: center;
gap: 24px;
.artist-image {
width: 220px;
height: 220px;
border-radius: 50%;
object-fit: cover;
box-shadow: 0 4px 60px rgba(0,0,0,.5);
}
.header-content {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 20px;
.artist-name {
font-size: 80px;
font-weight: 700;
margin: 0;
line-height: 1;
}
.artist-stats {
font-size: 14px;
color: #b3b3b3;
margin-top: 15px;
}
}
}
.content {
z-index: 1;
background-color: rgba(0, 0, 0, 0.20);
}
.album-image {
border-radius: 6%;
width: 160px;
height: 160px;
margin-top: 30px;
margin-left: 15px;
margin-right: 20px;
}
.play-area {
position: relative;
}
.more-info {
font-size: 35px;
position: absolute;
z-index: 13;
top: 33px;
left: 160px;
transition: width 0.1s ease-in-out;
}
.more-info:focus {
outline: none;
}
.more-info:hover {
cursor: pointer;
transform: scale(1.15);
}
.fixed-play-area {
top: 0;
z-index: 11;
opacity: 0;
transition: opacity 0.2s ease-out;
border-radius: 12px 12px 0 0;
position: fixed; /**/
display: flex;
padding: 10px 0 10px 20px;
width: inherit;
}
.opacity-wrapper {
display: flex;
align-items: center;
width: 100%;
height: 100%;
margin: -10px 0 -10px -20px;
padding: 10px 0 10px 20px;
background-color: rgba(0, 0, 0, 0.50);
}
.play-button {
margin-left: 40px;
background-color: #1ed660;
border-radius: 50%;
width: 60px;
height: 60px;
position: relative;
align-items: center;
justify-content: center;
transition: all 0.1s;
}
.play-button:hover {
cursor: pointer;
transform: scale(1.05);
background-color: #1ed683;
}
.tips {
z-index: 0;
display: flex;
position: relative;
padding: 5px 8px 5px 8px;
}
.fixed-tips {
z-index: 11;
width: 100%;
justify-content: space-between;
display: none;
padding: 10px 8px 10px 8px;
position: fixed;
background-color: #1f1f1f;
border-bottom: 1px solid #363636;
}
.musicList, .other-info {
border-top: 1px solid #363636;
margin-top: 10px;
}
/*每行音乐的样式*/
.music-item {
position: relative;
border-radius: 10px;
display: flex;
align-items: center;
padding: 10px 0 10px 25px;
flex-grow: 1;
min-width: 0;
}
/*音乐被点击后的样式*/
.music-after-click {
color: #1ed660;
}
/*左侧信息*/
.music-detailed-info {
display: flex;
flex-direction: row;
}
.music-image {
padding-left: 30px;
height: 50px;
width: 50px; /* Adjust as needed */
border-radius: 10px;
}
.music-name {
padding-bottom: 5px;
font-size: 18px
}
.music-name:hover {
cursor: pointer;
text-decoration: underline;
}
.music-author {
color: #b2b2b2;
font-size: 15px
}
.music-author:hover {
cursor: pointer;
text-decoration: underline;
}
/*专辑信息*/
.music-album-info {
position: absolute;
left: 60%;
color: #b2b2b2;
text-overflow: ellipsis;
}
.music-album-info:hover {
cursor: pointer;
text-decoration: underline;
}
.music-time-info {
position: absolute;
left: 85%;
color: #b2b2b2;
}
/*右侧信息*/
.music-right-info {
margin-left: auto;
display: flex;
align-items: center;
flex-direction: row;
}
.like-button {
width: 20px;
height: 20px;
margin-right: 40px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
color: #b3b3b3;
}
.like-button:hover {
transform: scale(1.1);
color: white;
}
.like-button.liked {
color: #1ed760;
}
.add-icon {
font-size: 24px;
font-weight: 300;
line-height: 1;
}
.add-icon[data-tooltip]:hover::after {
content: attr(data-tooltip);
position: absolute;
top: -25px;
left: 50%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
z-index: 1000;
}
.check-mark {
width: 16px;
height: 16px;
}
.check-mark[data-tooltip]:hover::after {
display: none;
}
.music-more-info {
margin-right: 14px;
font-size: 22px;
transition: width 0.1s ease-in-out;
}
.music-more-info:focus {
outline: none;
transform: scale(1.05);
}
.music-more-info:hover {
cursor: pointer;
}
.music-dropdown-options {
border-radius: 6px;
}
ul {
background-color: #282828;
list-style-type: none;
padding: 0;
margin: 0;
border-radius: 10px;
}
li {
color: white;
padding: 15px 12px;
}
li:hover {
cursor: pointer;
text-decoration: underline;
}
.other-info {
margin-top: 20px;
}
.reco-add-button {
color: white;
margin-right: 16px;
box-sizing: border-box;
background-color: transparent;
border-radius: 9999px;
cursor: pointer;
position: relative;
text-align: center;
transition-duration: 33ms;
transition-property: background-color, border-color, color, box-shadow, filter, transform;
user-select: none;
vertical-align: middle;
transform: translate3d(0px, 0px, 0px);
padding-block: 3px;
padding-inline: 15px;
border: 1px solid #818181;
min-inline-size: 0;
min-block-size: 32px;
display: inline-flex;
align-items: center;
&:hover {
border: 1px solid #f5f5f5;
scale: 1.1;
}
}
/* new-elements */
.edit-desc {
visibility: hidden;
z-index: 1000;
background-color: rgba(0, 0, 0, .7);
bottom: 0;
display: flex;
left: 0;
position: absolute;
right: 0;
top: 0;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
overflow: hidden;
}
.main-edit-desc {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
background-color: #282828;
border-radius: 8px;
-webkit-box-shadow: 0 4px 4px rgba(0, 0, 0, .3);
box-shadow: 0 4px 4px rgba(0, 0, 0, .3);
color: #fff;
-ms-flex-direction: column;
flex-direction: column;
min-height: 384px;
width: 524px;
}
.edit-desc-header {
display: flex;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
padding: 24px;
}
.edit-desc-header-button {
align-self: end;
background-color: transparent;
border: 0;
border-radius: 32px;
color: hsla(0, 0%, 100%, .7);
grid-area: close-button;
height: 32px;
margin-top: -8px;
width: 32px;
-webkit-margin-end: -8px;
margin-inline-end: -8px;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.edit-desc-text {
display: grid;
grid-template: 32px 132px 32px auto / 180px 1fr;
grid-template-areas:
"album-image title"
"album-image description"
". save-button"
"disclaimer disclaimer";
grid-gap: 16px;
padding: 0 24px 24px;
}
.edit-desc-img {
grid-area: album-image;
height: 180px;
margin: 0;
position: relative;
/* width: 180px; */
}
.edit-desc-img-1 {
border-radius: 4px;
height: 100%;
width: 100%;
}
.edit-desc-img-1-1 {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
background-color: #282828;
color: #7f7f7f;
justify-content: center;
-webkit-box-shadow: 0 4px 60px rgba(0, 0, 0, .5);
box-shadow: 0 4px 60px rgba(0, 0, 0, .5);
&:hover {
display: none;
}
}
.large-svg {
fill: currentcolor;
width: 48px;
height: 48px;
}
.edit-desc-img-2 {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.edit-desc-img-2-1 {
height: 100%;
width: 100%;
}
.edit-desc-img-2-button {
background-color: #282828;
color: #fff;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
text-align: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
border: none;
border-radius: 4px;
justify-content: center;
opacity: 0;
padding: 0;
}
.edit-desc-img-2-1-1 {
margin-top: 16px;
-webkit-transition: opacity .2s;
transition: opacity .2s;
}
.edit-desc-img-3 {
right: 8px;
height: 32px;
position: absolute;
top: 8px;
width: 32px;
}
@media (hover: hover) {
.edit-desc-img-3-button:not([data-context-menu-open=true]) {
opacity: 0;
pointer-events: none;
position: unset;
}
}
.edit-desc-img-3-button {
background-color: rgba(0, 0, 0, .3);
border: none;
border-radius: 500px;
color: #b3b3b3;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
padding: 8px;
text-decoration: none;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
&:hover {
opacity: 0;
pointer-events: none;
position: unset;
}
}
.small-svg {
height: 16px;
width: 16px;
}
.edit-desc-input-name {
grid-area: title;
position: relative;
margin-right: 8px;
}
.edit-desc-input-name-1 {
background: hsla(0, 0%, 100%, .1);
border: 1px solid transparent;
border-radius: 4px;
color: #fff;
font-family: inherit;
font-size: 14px;
height: 40px;
padding: 0 12px;
width: 100%;
-webkit-box-shadow: inset 0 -2px #343030;
box-shadow: inset 0 -2px 0 0 #343030;
}
.edit-desc-input-desc {
grid-area: description;
margin-top: 8px;
position: relative;
}
.edit-desc-input-desc-1 {
background: hsla(0, 0%, 100%, .1);
border: 1px solid transparent;
border-radius: 4px;
color: #fff;
font-family: inherit;
font-size: 14px;
padding: 8px 8px 28px;
resize: none;
width: 100%;
height: 70%;
}
.edit-desc-button {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
grid-area: save-button;
justify-self: flex-end;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.edit-desc-button-1 {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
background-color: transparent;
border: 0;
border-radius: 9999px;
cursor: pointer;
display: inline-block;
position: relative;
text-align: center;
text-decoration: none;
text-transform: none;
touch-action: manipulation;
transition-duration: 33ms;
transition-property: background-color, border-color, color, box-shadow, filter, transform;
user-select: none;
vertical-align: middle;
transform: translate3d(0px, 0px, 0px);
padding: 0px;
min-inline-size: 0px;
align-self: center;
}
.edit-desc-button-1-1 {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
position: relative;
background-color: #ffffff;
color: #000000;
display: flex;
border-radius: 9999px;
font-size: inherit;
min-block-size: 48px;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
padding-block: 8px;
padding-inline: 32px;
transition-property: background-color, transform;
transition-duration: 33ms;
}
.encore-text {
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
color: inherit;
margin-block: 0;
font-size: 13px;
white-space: normal;
}
.encore-text-title-small {
font-size: 1.5rem;
}
.final-tip {
grid-area: disclaimer;
}
.encore-text-marginal-bold {
font-weight: 700;
}
.follow-button {
margin-left: 32px;
padding: 8px 20px;
font-size: 16px;
font-weight: bold;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 500px;
background-color: transparent;
color: white;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: scale(1.04);
border-color: white;
}
&.following {
background-color: rgba(255, 255, 255, 0.1);
&:hover {
border-color: rgba(255, 255, 255, 0.3);
}
}
}
/* 修改play-area样式以适应新的布局 */
.play-area {
display: flex;
align-items: center;
padding: 24px 32px;
}
.check-mark-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.check-mark-wrapper[data-tooltip]:hover::after {
content: attr(data-tooltip);
position: absolute;
top: -25px;
left: 50%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
z-index: 1000;
}
/* 修改返回按钮样式 */
.back-button {
position: relative;
margin: 24px 0 0 24px;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 50%;
color: #fff;
transition: all 0.2s ease;
}
.back-button:hover {
transform: scale(1.1);
background-color: rgba(0, 0, 0, .8);
}
/* 提示文字样式 */
.back-button[data-tooltip]:hover::after {
content: attr(data-tooltip);
position: absolute;
top: 38px;
left: 50%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
z-index: 1000;
pointer-events: none;
}
</style>
\ No newline at end of file
......@@ -9,23 +9,39 @@ import pauseButton from "../icon/pauseButton.vue";
import {modifyPlaylist, removePlaylist, removeSongFromPlaylist} from "../api/playlist";
import tippy from 'tippy.js';
import {addSongToPlaylist, modifyPlaylist, removePlaylist, removeSongFromPlaylist} from "../api/playlist";
import {formatTime} from "@/utils/formatTime";
import { loadSongDurations } from '../utils/loadSongDurations';
const emit = defineEmits();
/*
USER
*/
const userToken = ref(JSON.parse(sessionStorage.getItem('user-token')));
const currentUserId = ref(userToken.value.id);
const emit = defineEmits(['pauseSong']);
const props = defineProps({
albumInfo: { // 类型 :id, userid, title ,description ,picPath,createTime,updateTime,songNum
type: Object,
required: true,
},
playList:{
type: Array,
required: true,
},
musicList: {// 类型 :id ,title, artist, album,description, picPath,uploadTime
type: Object,
required: true,
},
playFromLeftBar: null,
currentSongId: Number,
albumInfo: { // 类型 :id, userid, title ,description ,picPath,createTime,updateTime,songNum
type: Object,
required: true,
},
playList: {
type: Array,
required: true,
},
musicList: {// 类型 :id ,title, artist, album,description, picPath,uploadTime
type: Object,
required: true,
},
playFromLeftBar: null,
currentSongId: {
type: Number,
required: true
},
isPaused: {
type: Boolean,
}
});
const edit_title = ref("");
......@@ -52,44 +68,50 @@ let musicPauseIndex = ref(null);
const resizeObserver = ref(null)
const gradientColor = computed(() => `linear-gradient(to bottom, ${backgroundColor.value} , #1F1F1F 50%)`)
//获取歌曲时长
const songDurations = ref(new Map());
watch(() => props.musicList, (newSongs) => {
loadSongDurations(newSongs, songDurations);
}, { immediate: true });
// 放缩时的组件处理
const handleResize = () => {
const albums = document.querySelectorAll(".music-album-info");
const albumText = document.querySelectorAll(".album-text");
const albumContent = document.querySelector(".album-content");
// if (window.innerWidth > 0)
// 专辑隐藏
console.log(albumContent.clientWidth);
if (albumContent.clientWidth < 605) {
albums.forEach(album => {
album.style.visibility = "hidden";
});
albumText.forEach(album => {
album.style.visibility = "hidden";
});
const albums = document.querySelectorAll(".music-album-info");
const albumText = document.querySelectorAll(".album-text");
const albumContent = document.querySelector(".album-content");
// if (window.innerWidth > 0)
// 专辑隐藏
console.log(albumContent.clientWidth);
if (albumContent.clientWidth < 605) {
albums.forEach(album => {
album.style.visibility = "hidden";
});
albumText.forEach(album => {
album.style.visibility = "hidden";
});
} else {
albums.forEach(album => {
album.style.visibility = "visible";
});
albumText.forEach(album => {
album.style.visibility = "visible";
});
}
const albumImage = document.querySelector(".album-image");
const headerAlbumName = document.querySelector(".header-album-name");
// 歌单图片和文字缩放
if (albumContent.clientWidth < 420) {
albumImage.style.width = "120px";
albumImage.style.height = "120px";
headerAlbumName.style.fontSize = "40px";
headerAlbumName.style.marginBottom = "20px";
} else {
albumImage.style.width = "160px";
albumImage.style.height = "160px";
headerAlbumName.style.fontSize = "80px";
headerAlbumName.style.marginBottom = "35px";
}
} else {
albums.forEach(album => {
album.style.visibility = "visible";
});
albumText.forEach(album => {
album.style.visibility = "visible";
});
}
const albumImage = document.querySelector(".album-image");
const headerAlbumName = document.querySelector(".header-album-name");
// 歌单图片和文字缩放
if (albumContent.clientWidth < 420) {
albumImage.style.width = "120px";
albumImage.style.height = "120px";
headerAlbumName.style.fontSize = "40px";
headerAlbumName.style.marginBottom = "20px";
} else {
albumImage.style.width = "160px";
albumImage.style.height = "160px";
headerAlbumName.style.fontSize = "80px";
headerAlbumName.style.marginBottom = "35px";
}
//🙏 权宜之计
const fixedTipArea = document.querySelector(".fixed-tips");
......@@ -178,10 +200,7 @@ const closePopover = (e) => {
item.hide();
})
}
// 鼠标悬停提示
tippy('#myButton', {
content: "I'm a Tippy tooltip!",
});
//TODO:
const enterPersonalSpace = () => {
......@@ -206,19 +225,30 @@ const playFromId = (musicId) => {
musicPauseIndex = null;
}
const addToFavorite = (musicId,albumId) => {
//TODO:调用接口添加歌曲到指定歌单,并设置elmessage提示信息
const addToFavorite = (musicId, albumId) => {
addSongToPlaylist({
user_id: currentUserId.value,
playlist_id: albumId,
song_id: musicId,
}).then(() => {
ElMessage({
message: "添加至: " + props.albumInfo.title,
grouping: true,
type: 'info',
offset: 16,
customClass: "reco-message",
duration: 4000,
})
})
}
watch(() => props.isPaused, (newValue) => {
if (newValue) {
musicPauseIndex = musicPlayIndex;
} else {
musicPauseIndex = null;
}
});
ElMessage({
message: "添加至: " + props.albumInfo.title,
grouping: true,
type: 'info',
offset: 16,
customClass: "reco-message",
duration: 4000,
}
)
}
const removeMusicFromAlbum = (albumId, songId) => {
removeSongFromPlaylist({
playlist_id: albumId,
......@@ -227,11 +257,13 @@ const removeMusicFromAlbum = (albumId, songId) => {
}
const enterMusicDescription = (musicId) => {
}
const enterAuthorDescription = (authorName) => {
const enterAuthorDescription = (artistName) => {
emit('switchToArtist', artistName);
}
const pauseMusic = (musicId) => {
musicPauseIndex = musicId;
emit('pauseSong');
}
const editAlbumDescription = (albumId) => {
......@@ -267,6 +299,13 @@ const addRecommendMusic = (musicId) => {
})
}
watch(() => props.currentSongId, (newId) => {
if (newId) {
musicPlayIndex = newId;
musicClickedIndex = newId;
musicPauseIndex = props.isPaused ? newId : null;
}
});
</script>
......@@ -335,7 +374,7 @@ const addRecommendMusic = (musicId) => {
<p style="position:absolute; left:45px">#</p>
<p style="position:absolute; left:140px">标题</p>
<p class="album-text" style="position:absolute; left:62%">专辑</p>
<p style="margin-left: auto; margin-right:20px">详情</p> <!--时间变为详细信息-->
<p style="margin-left: auto; margin-right:62px">时间</p>
</div>
<div class="edit-desc" @blur="quitEdit">
<div data-testid="playlist-edit-details-modal" class="main-edit-desc">
......@@ -390,10 +429,13 @@ const addRecommendMusic = (musicId) => {
</div>
</div>
<div class="edit-desc-input-name">
<input v-model="edit_title" data-testid="playlist-edit-details-name-input" id="text-input-c673a65959365e7f" type="text" class="edit-desc-input-name-1" placeholder="添加名称">
<input v-model="edit_title" data-testid="playlist-edit-details-name-input"
id="text-input-c673a65959365e7f" type="text" class="edit-desc-input-name-1"
placeholder="添加名称">
</div>
<div class="edit-desc-input-desc">
<textarea v-model="edit_description" data-testid="playlist-edit-details-description-input" class="edit-desc-input-desc-1" placeholder="添加简介"/>
<textarea v-model="edit_description" data-testid="playlist-edit-details-description-input"
class="edit-desc-input-desc-1" placeholder="添加简介"/>
</div>
<div class="edit-desc-button">
<button @click="confirmEdit(albumInfo.id)" data-testid="playlist-edit-details-save-button"
......@@ -411,7 +453,7 @@ const addRecommendMusic = (musicId) => {
<p style="position:absolute; left:45px">#</p>
<p style="position:absolute; left:140px">标题</p>
<p class="album-text" style="position:absolute; left:62%">专辑</p>
<p style="margin-left: auto; margin-right:20px">详情</p><!--时间变为详细信息-->
<p style="margin-left: auto; margin-right:20px">时间</p><!--时间变为详细信息-->
</div>
<div class="musicList">
......@@ -479,51 +521,68 @@ const addRecommendMusic = (musicId) => {
:style="{visibility: musicHoveredIndex === music.id ? 'visible' : 'hidden'}"/>
</template>
<ul @click="closePopover" style="overflow: scroll;max-height: 400px;">
<div style="padding: 6px 0 6px 10px;font-weight: bold;color:darkgrey;font-size:16px">选择歌单收藏</div>
<hr style=" border: 0;padding-top: 1px;background: linear-gradient(to right, transparent, #98989b, transparent);" >
<li class="album-to-add" @click="addToFavorite(music.id,album.id)" v-for="album in playList">
<div style="height:40px;display: flex;align-items: center;font-size: 20px;font-weight:400">
<img :src="album.picPath" style="height: 32px;width:32px;border-radius: 4px" alt=""/>
<div style="margin-left: 30px">{{album.title}} </div>
</div>
</li>
</ul>
</el-popover>
<!-- 这里原本想写歌曲时长,但是没有 只能留空-->
<div style="margin-left: auto;margin-right: 15px; color: #b2b2b2"
:style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}">{{}}
</div>
<el-popover
:ref="getPopoverIndex"
class="music-dropdown-options"
popper-class="my-popover"
:width="400"
trigger="click"
:hide-after=0
>
<template #reference>
<dots v-tippy="'歌曲详情'" class="music-more-info"/>
</template>
<ul @click="closePopover">
<li @click="removeMusicFromAlbum(music.id)">删除歌曲</li>
</ul>
</el-popover>
<ul @click="closePopover" style="overflow: scroll;max-height: 400px;">
<div style="padding: 6px 0 6px 10px;font-weight: bold;color:darkgrey;font-size:16px">
选择歌单收藏
</div>
<hr style=" border: 0;padding-top: 1px;background: linear-gradient(to right, transparent, #98989b, transparent);">
<li class="album-to-add" @click="addToFavorite(music.id,album.id)"
v-for="album in playList">
<div style="
height:40px;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 20px;
font-weight:400"
>
<div style="display: flex; flex-direction: row">
<img :src="album.picPath" style="height: 40px; width:40px; border-radius: 4px" alt=""/>
<div style="
margin-left: 10px;
font-size: 18px;
">{{ album.title }}</div>
</div>
<div style="font-size: 14px; color: #a4a4a4">{{ album.songNum }}首</div>
</div>
</li>
</ul>
</el-popover>
<div style="margin-left: auto;margin-right: 15px; color: #b2b2b2"
:style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}"
v-show="songDurations.get(music.id) !== undefined">
{{ formatTime(songDurations.get(music.id)) }}
</div>
<el-popover
:ref="getPopoverIndex"
class="music-dropdown-options"
popper-class="my-popover"
:width="400"
trigger="click"
:hide-after=0
>
<template #reference>
<dots v-tippy="'歌曲详情'" class="music-more-info"/>
</template>
<ul @click="closePopover">
<li @click="removeMusicFromAlbum(albumInfo.id, music.id)">删除歌曲</li>
</ul>
</el-popover>
</div>
</div>
</div>
</div>
</div>
</div>
<!--TODO:推荐歌曲的细节处理-->
<div class="other-info">
<div style="margin-left:20px;margin-bottom:20px;">
<div style="display: flex;text-align: left;justify-content: center;flex-direction: column">
<span style="color:white;font-size: 30px;font-weight: bolder">推荐</span>
<span style="color:grey;font-size: 20px">根据此歌单包含的内容推荐
<!--TODO:推荐歌曲的细节处理-->
<div class="other-info">
<div style="margin-left:20px;margin-bottom:20px;">
<div style="display: flex;text-align: left;justify-content: center;flex-direction: column">
<span style="color:white;font-size: 30px;font-weight: bolder">推荐</span>
<span style="color:grey;font-size: 20px">根据此歌单包含的内容推荐
</span>
</div>
</div>
......@@ -542,55 +601,56 @@ const addRecommendMusic = (musicId) => {
}">
<div
:style="{visibility: musicHoveredIndex === music.id||musicPlayIndex === music.id ? 'hidden' : 'visible' }">
{{
recMusicList.indexOf(music) + 1
}}
</div>
<play-button @click="playFromId(music.id)" style="position: absolute;left: 33px;cursor: pointer"
v-if="(musicHoveredIndex === music.id&&musicPlayIndex!==music.id)||musicPauseIndex===music.id"
:style="{color: musicPauseIndex===music.id ? '#1ed660' : 'white'}"/>
<pause-button @click="pauseMusic(music.id)"
style="color:#1ed660 ;position: absolute;left: 37px;cursor: pointer"
v-if="musicPlayIndex===music.id&&musicHoveredIndex === music.id&&musicPauseIndex!==music.id"/>
<img width="17" height="17" alt=""
style="position: absolute;left: 42px;"
v-if="musicPlayIndex===music.id&&musicHoveredIndex !== music.id&&musicPauseIndex!==music.id"
src="https://open.spotifycdn.com/cdn/images/equaliser-animated-green.f5eb96f2.gif">
<div class="music-detailed-info">
<img class="music-image"
:src="music.picPath"
alt="歌曲图片"/>
<div class="music-name-author" style="padding-left: 5px;">
<p @click="enterMusicDescription(music.id)" class="music-name"
:style="{color : musicPlayIndex ===music.id? '#1ED660':''}"
:class="[musicPlayIndex === music.id ? 'music-after-click' : '']"
>{{ music.title }}</p>
<p @click="enterAuthorDescription(music.artist)" class="music-author"
:style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}">
{{ music.artist }}</p>
</div>
</div>
<div
:style="{visibility: musicHoveredIndex === music.id||musicPlayIndex === music.id ? 'hidden' : 'visible' }">
{{
recMusicList.indexOf(music) + 1
}}
</div>
<play-button @click="playFromId(music.id)" style="position: absolute;left: 33px;cursor: pointer"
v-if="(musicHoveredIndex === music.id&&musicPlayIndex!==music.id)||musicPauseIndex===music.id"
:style="{color: musicPauseIndex===music.id ? '#1ed660' : 'white'}"/>
<pause-button @click="pauseMusic(music.id)"
style="color:#1ed660 ;position: absolute;left: 37px;cursor: pointer"
v-if="musicPlayIndex===music.id&&musicHoveredIndex === music.id&&musicPauseIndex!==music.id"/>
<img width="17" height="17" alt=""
style="position: absolute;left: 42px;"
v-if="musicPlayIndex===music.id&&musicHoveredIndex !== music.id&&musicPauseIndex!==music.id"
src="https://open.spotifycdn.com/cdn/images/equaliser-animated-green.f5eb96f2.gif">
<div class="music-detailed-info">
<img class="music-image"
:src="music.picPath"
alt="歌曲图片"/>
<div class="music-name-author" style="padding-left: 5px;">
<p @click="enterMusicDescription(music.id)" class="music-name"
:style="{color : musicPlayIndex ===music.id? '#1ED660':''}"
:class="[musicPlayIndex === music.id ? 'music-after-click' : '']"
>{{ music.title }}</p>
<p @click="enterAuthorDescription(music.artist)" class="music-author"
:style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}">
{{ music.artist }}</p>
</div>
</div>
<div class="music-album-info" :style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}">
{{ music.album }}
</div>
<div class="music-right-info">
<button class="reco-add-button" @click="addRecommendMusic(music.id)">添加</button>
<div class="music-album-info"
:style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}">
{{ music.album }}
</div>
<div class="music-right-info">
<button class="reco-add-button" @click="addRecommendMusic(music.id)">添加</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
......@@ -862,8 +922,9 @@ p {
.check-mark:focus {
outline: none;
}
.album-to-add{
padding: 8px;
.album-to-add {
padding: 8px;
}
.music-more-info {
......
export function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
return `${minutes}:${remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds}`;
export function formatTime(time) {
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
export function calculateDaysDifference(startTimestamp) {
......
export const loadSongDuration = (song, durationsMap) => {
return new Promise((resolve) => {
const audio = new Audio(song.filePath);
audio.addEventListener('loadedmetadata', () => {
durationsMap.value.set(song.id, audio.duration);
resolve();
});
audio.preload = 'metadata';
});
};
export const loadSongDurations = async (songs, durationsMap) => {
if (songs) {
for (const song of songs) {
if (!durationsMap.value.has(song.id)) {
await loadSongDuration(song, durationsMap);
}
}
}
};
\ No newline at end of file
/* eslint-disable */
<script setup>
// Vue Basics
import {computed, onMounted, ref, watch} from "vue"
import {computed, onMounted, ref} from "vue"
// Assets
import defaultBg from '../assets/pictures/Eason.png'
......@@ -15,6 +15,7 @@ import SearchView from "@/components/SearchView.vue";
import MusicAlbumView from "../components/MusicAlbumView.vue";
import MainView from "../components/MainView.vue";
import EpisodeView from "@/components/EpisodeView.vue";
import ArtistView from "../components/ArtistView.vue";
// APIs
import {getSongsByEpisode, getSongsByPlaylist} from "../api/song";
......@@ -24,6 +25,7 @@ import {getPlaylistsByUser} from "../api/playlist";
import {useTheme} from "../store/theme";
import {parseLrc} from "../utils/parseLyrics"
import {updateBackground} from "../utils/getBackgroundColor";
import { formatTime } from '../utils/formatTime';
/*
......@@ -194,6 +196,7 @@ const registerDOMs = () => {
});
song.load();
song.play();
isPaused.value = false;
theme.change(songs.value[currentSongIndex.value].picPath);
}
} catch (e) {
......@@ -336,9 +339,19 @@ const currentUserId = ref(userToken.value.id);
*/
// Playing Status
const songs = ref([]);
const volumn = ref(1);
watch(volumn, (newValue) => {
song.volume = newValue;
const volume = ref(1);
const volumePercentage = ref('100%');
// 添加音量变化处理
const updateVolumeStyle = (e) => {
const volume = e.target.value;
volumePercentage.value = (volume * 100) + '%';
};
// 监听volume变化,同步更新所有音量控制器的样式
watch(volume, (newValue) => {
song.volume = newValue;
volumePercentage.value = (newValue * 100) + '%';
});
const displayingSongs = ref([]);
const isPaused = ref(false);
......@@ -353,7 +366,6 @@ const togglePlayingPage = () => {
isPlayingPage.value = !isPlayingPage.value;
registerDOMs();
}
const switchSongs = (del) => {
console.log(playingMode.value, currentSongIndex.value, songs.value.length)
switch (playingMode.value) {
......@@ -394,6 +406,7 @@ const switchToSong = (index, isDiffPlaylist) => {
song.load();
song.play();
theme.change(songs.value[index].picPath);
isPaused.value = false;
}
}
......@@ -516,10 +529,13 @@ function receiveDataFromHeader(data) {
setMidComponents(3);
}
/*HOME*/
/*
HOME
*/
function receiveDataFromHome() {
setMidComponents(0);
}
/*
MID COMPONENTS
0 - Main View
......@@ -527,10 +543,16 @@ function receiveDataFromHome() {
2 - Comments
3 - Search Results
4 - Episodes
5 - Artist View
*/
const midComponents = ref(0);
const setMidComponents = (val) => {
midComponents.value = val;
const currentArtist = ref(null);
const setMidComponents = (val, artistName = null) => {
midComponents.value = val;
if (val === 5) {
currentArtist.value = artistName;
}
}
/*
......@@ -575,9 +597,59 @@ onMounted(() => {
}).catch(e => {
console.log("Failed to get playlists!");
});
const volumeControl = document.getElementById('volumeControl');
if (volumeControl) {
volumeControl.style.setProperty('--volume-percentage', '100%');
}
})
let playFromLeftBarAlbum = ref(null);
const playArtistSong = (songToPlay) => {
// 检查歌曲是否已经在播放列表中
const existingIndex = songs.value.findIndex(song => song.id === songToPlay.id);
if (existingIndex !== -1) {
// 如果歌曲已存在,直接播放该歌曲
currentSongIndex.value = existingIndex;
currentSongId.value = songToPlay.id;
} else {
// 如果歌曲不存在,添加到播放列表开头并播放
songs.value = [songToPlay, ...songs.value];
currentSongIndex.value = 0;
currentSongId.value = songToPlay.id;
}
if (song) {
controlIcons.forEach(controlIcon => {
controlIcon.src = PLAY;
});
song.src = songToPlay.filePath;
parseLrc(songToPlay.lyricsPath).then(res => {
lyrics.value = res;
});
song.load();
song.play();
theme.change(songToPlay.picPath);
isPaused.value = false; // 设置为播放状态
}
};
const pauseCurrentSong = () => {
if (song) {
song.pause();
controlIcons.forEach(controlIcon => {
controlIcon.src = PAUSE;
});
isPaused.value = true; // 设置为暂停状态
}
};
const updateSongs = (newSongs) => {
songs.value = newSongs;
displayingSongs.value = newSongs;
};
</script>
<template>
......@@ -597,8 +669,13 @@ let playFromLeftBarAlbum = ref(null);
<!--height: 730px -->
<div v-if="midComponents == 1" class="playlist-container"
style="overflow: scroll; border-radius: 12px">
<MusicAlbumView :album-info="displayingPlaylist" :music-list="displayingSongs"
@switchSongs="switchToPlaylist" :playFromLeftBar="playFromLeftBarAlbum" :play-list="playlists"/>
<MusicAlbumView :album-info="displayingPlaylist" :music-list="displayingSongs" :play-list="playlists"
:current-song-id="currentSongId"
@switchSongs="switchToPlaylist" @switchToArtist="(name) => setMidComponents(5, name)"
@pauseSong="pauseCurrentSong"
:playFromLeftBar="playFromLeftBarAlbum"
:is-paused="isPaused"
/>
</div>
<el-container v-if="midComponents == 2" class="playlist-container"
style="overflow: auto; height: 730px ;border-radius: 12px">
......@@ -621,6 +698,16 @@ let playFromLeftBarAlbum = ref(null);
style="overflow: scroll; border-radius: 12px">
<EpisodeView :episode-info="displayingEpisode" :music-list="displayingSongs"
@switchSongs="switchToEpisode" :playFromLeftBar="playFromLeftBarAlbum"/>
</div>
<div v-if="midComponents == 5" class="playlist-container"
style="overflow: scroll; border-radius: 12px">
<ArtistView :artist-name="currentArtist"
:is-paused="isPaused"
:current-song-id="currentSongId"
@playSong="playArtistSong"
@pauseSong="pauseCurrentSong"
@back="setMidComponents(1)"
@updateSongs="updateSongs"/>
</div>
</div>
<div v-if="showRightContent" class="right-content">
......@@ -747,9 +834,17 @@ let playFromLeftBarAlbum = ref(null);
<div class="right-controls">
<div class="volumn-control" style="display: flex; flex-direction: row; align-items: center">
<div class="volume-control" style="display: flex; flex-direction: row; align-items: center">
<h1 style="margin: 0">🔈</h1>
<input v-model="volumn" type="range" id="volumeControl" min="0" max="1" step="0.01">
<input type="range"
id="volumeControl"
min="0"
max="1"
step="0.01"
v-model="volume"
@input="updateVolumeStyle"
:style="{'--volume-percentage': volumePercentage}"
/>
</div>
<div class="feature-icon"
data-tooltip="分享"
......@@ -853,9 +948,17 @@ let playFromLeftBarAlbum = ref(null);
</div>
</div>
</div>
<div class="volumn-control-playing" style="display: flex; flex-direction: row; align-items: center">
<div class="volume-control-playing" style="display: flex; flex-direction: row; align-items: center">
<h1 style="margin: 0">🔈</h1>
<input v-model="volumn" type="range" id="volumeControl" min="0" max="1" step="0.01">
<input type="range"
id="volumeControl"
min="0"
max="1"
step="0.01"
v-model="volume"
@input="updateVolumeStyle"
:style="{'--volume-percentage': volumePercentage}"
/>
</div>
<div class="corner-buttons">
<button @click="toggleLyrics" class="corner-button">
......@@ -928,7 +1031,7 @@ h1 {
flex-direction: column;
*/
min-height: 100vh;
background-color: rgb(0, 0, 0); /* rgba(0, 0, 0, 1); */
background-color: rgb(19, 19, 19); /* rgba(0, 0, 0, 1); */
background-repeat: no-repeat;
background-size: cover;
......@@ -950,7 +1053,7 @@ h1 {
"left-sidebar main-view main-view"
"now-playing-bar now-playing-bar now-playing-bar";
grid-template-columns: auto 1fr;
grid-template-rows: 10% 80% 10%;
grid-template-rows: 10% 81% 9%;
grid-auto-rows: min-content;
column-gap: 8px;
......@@ -1239,7 +1342,7 @@ footer {
/* RIGHT CONTENT */
.right-content {
background-color: #121212;
background-color: #171717;
display: flex;
flex-direction: column;
border-radius: 12px;
......@@ -1763,7 +1866,7 @@ footer {
/* 退出搜索图标 */
.exit-search {
position: absolute;
top: 90px;
top: 120px;
right: 10px;
width: 30px;
height: 30px;
......@@ -1884,7 +1987,7 @@ html, body {
font-size: 1rem;
}
.volumn-control-playing {
.volume-control-playing {
position: absolute;
bottom: 20px;
right: 200px;
......@@ -1957,36 +2060,30 @@ html, body {
-webkit-appearance: none; /* 去掉默认样式 */
appearance: none;
width: 120px; /* 设置宽度 */
height: 10px; /* 设置高度 */
background: #ddd; /* 设置默认背景颜色 */
border-radius: 5px; /* 设置圆角 */
height: 4px; /* 设置高度 */
border-radius: 2px; /* 设置圆角 */
background: linear-gradient(to right, #1db954 var(--volume-percentage, 100%), #4d4d4d var(--volume-percentage, 100%));
outline: none; /* 去除焦点时的轮廓 */
transition: background 0.3s; /* 背景色平滑过渡 */
}
/* 设置滑条(轨道)的样式 */
#volumeControl::-webkit-slider-runnable-track {
height: 10px; /* 设置轨道高度 */
border-radius: 5px; /* 圆角 */
background: #1ed760; /* 设置轨道颜色为绿色 */
}
/* 设置滑块的样式 */
/* 设置滑块按钮样式 */
#volumeControl::-webkit-slider-thumb {
-webkit-appearance: none; /* 去掉默认样式 */
appearance: none;
width: 20px; /* 设置滑块宽度 */
height: 20px; /* 设置滑块高度 */
margin-top: -5px;
width: 12px; /* 设置滑块宽度 */
height: 12px; /* 设置滑块高度 */
border-radius: 50%; /* 圆形滑块 */
background: #fff; /* 设置滑块背景颜色为白色 */
border: 2px solid green; /* 设置滑块边框颜色为绿色 */
border: none; /* 去除边框 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
cursor: pointer; /* 设置鼠标悬停时的指针样式 */
transition: all 0.2s ease; /* 添加过渡效果 */
}
/* 鼠标悬浮时改变轨道背景颜色 */
#volumeControl:hover {
background: #ccc; /* 改变背景颜色 */
/* 滑块按钮悬停效果 */
#volumeControl::-webkit-slider-thumb:hover {
transform: scale(1.2);
}
/* 设置滑块被点击时的样式 */
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment