Skip to content
Snippets Groups Projects
Unverified Commit 77de8419 authored by daify's avatar daify Committed by GitHub
Browse files

Merge pull request #13 from CosineSky/daify

feat:添加专辑页面
parents c9c0b1c4 ae308620
No related branches found
No related tags found
No related merge requests found
......@@ -26,4 +26,15 @@ export const removeSongFromPlaylist = (removeSongInfo) => {
.then(res => {
return res;
});
}
/*
// TODO - newly added
+ episode_id: number
*/
export const getSongsByEpisode = (episodeInfo) => {
return axios.get(`${SONG_MODULE}/fetchByEpisode`, { params: episodeInfo })
.then(res => {
return res;
});
}
\ No newline at end of file
<script setup>
import {computed, nextTick, onMounted, onUnmounted, ref, watch} from "vue";
import playButton from "../icon/playButton.vue";
import dots from "../icon/dots.vue";
import checkMark from "../icon/checkMark.vue";
import {ElMessage, ElPopover} from "element-plus";
import {backgroundColor, updateBackground} from "../utils/getBackgroundColor";
import pauseButton from "../icon/pauseButton.vue";
import {removePlaylist, removeSongFromPlaylist} from "../api/playlist";
const emit = defineEmits();
const props = defineProps({
episodeInfo: { // 类型 :id,title,artist,picPath,createTime,songNum
type: Object,
required: true,
},
musicList: { // 类型 :id,title,artist,picPath,upload_time
type: Object,
required: true,
},
playFromLeftBar: null,
currentSongId: Number
});
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 resizeObserver = ref(null)
// 放缩时的组件处理
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 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");
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)
}
}
onMounted(() => {
resizeObserver.value = new ResizeObserver(debounce(handleResize, 50));
console.log(resizeObserver.value)
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";
}
}
watch(props.playFromLeftBar, () => {
playFromId(props.playFromLeftBar)
})
const popovers = ref([])
const getPopoverIndex = (popover) => {
if (popover) {
popovers.value.push(popover);
}
}
const closePopover = (e) => {
popovers.value.forEach((item) => {
item.hide();
})
}
//TODO:
const enterArtistSpace = () => {
}
const removeAlbum = (albumId) => {
removePlaylist({
playlist_id: albumId,
}).then(res => {
console.log("Hi")
})
}
const playFromId = (musicId) => {
if (musicId === null) {
// 从头开始播放
musicPlayIndex.value = props.musicList[0].id;
} else {
musicPlayIndex.value = musicId;
}
emit('switchSongs', props.albumInfo, musicPlayIndex.value);
musicPauseIndex = null;
}
const addToFavorite = (musicId) => {
}
const removeMusicFromAlbum = (albumId, songId) => {
removeSongFromPlaylist({
playlist_id: albumId,
song_id: songId,
})
}
const enterMusicDescription = (musicId) => {
}
const enterAuthorDescription = (authorName) => {
}
const pauseMusic = (musicId) => {
musicPauseIndex = musicId;
}
const addRecommendMusic = (musicId) => {
console.log(musicId);
//TODO:添加歌曲到指定的歌单
ElMessage({
message: "添加至: " + props.albumInfo.title,
grouping: true,
type: 'info',
offset: 16,
customClass: "reco-message",
duration: 4000,
})
}
</script>
<template>
<div class="album-content" :style="{backgroundImage: gradientColor}" @mousewheel="handelScroll">
<div class="header">
<img :src="episodeInfo.picPath" alt="" class="album-image" @load="updateBackground(episodeInfo.picPath)"/>
<div class="header-content">
<p style="text-align: left;margin:20px 0 0 15px">专辑</p>
<p class="header-album-name" style="font-weight: bolder;font-size:80px;margin:10px 0 35px 10px;">
{{ episodeInfo.title }}</p>
<div class="header-content-detail">
<p class="header-creator" @click="enterArtistSpace">{{ episodeInfo.artist }}</p>
<p style="padding-right: 6px "></p>
<p v-if="episodeInfo.createTime !== undefined">
{{ episodeInfo.createTime.substring(0, 10) }} </p>
<p style="padding: 0 2px 0 6px"></p>
<p style="margin-left:6px">{{ musicList.length }} 首歌曲</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>
<!-- :ref中函数执行时,组件已经渲染好,并将本组件作为参数传入函数-->
<el-popover
style="border-radius: 10px"
:width="400"
trigger="click"
popper-class="my-popover"
:ref="getPopoverIndex"
:hide-after=0>
<template #reference>
<dots class="more-info"/>
</template>
<ul @click="closePopover">
<li @click="">删除歌单</li>
</ul>
</el-popover>
</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">{{ episodeInfo.title }}</p>
</div>
</div>
<div class="tips">
<p style="position:absolute; left:45px">#</p>
<p style="position:absolute; left:140px">标题</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 style="margin-left: auto; margin-right:75px">时间</p>
</div>
<div class="musicList">
<div class="music-item"
v-for="music in musicList"
: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' }">
{{
musicList.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 @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-right-info">
<el-popover
:ref="getPopoverIndex"
class="music-dropdown-options"
popper-class="my-popover"
:width="400"
trigger="click"
:hide-after=0
>
<template #reference>
<check-mark class="check-mark"
:style="{visibility: musicHoveredIndex === music.id ? 'visible' : 'hidden'}"/>
</template>
<ul @click="closePopover">
<!-- TODO: 这里需要所有的歌单-->
<li @click="addToFavorite(music.id)"></li>
</ul>
</el-popover>
<div style="margin-left: auto;margin-right: 15px; color: #b2b2b2"
:style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}">{{
music.upload_time
}}
<!--TODO: 解决播放时间问题-->
</div>
<el-popover
:ref="getPopoverIndex"
class="music-dropdown-options"
popper-class="my-popover"
:width="400"
trigger="click"
:hide-after=0
>
<template #reference>
<dots class="music-more-info"/>
</template>
<ul @click="closePopover">
<li @click="removeMusicFromAlbum(music.id)">删除歌曲</li>
</ul>
</el-popover>
</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 {
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;
flex-direction: row;
}
.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;
}
.header-content {
display: flex;
flex-direction: column;
height: 100%;
position: relative;
flex-grow: 1;
min-width: 0;
}
.header-content-detail {
padding: 10px;
align-items: center;
display: flex;
flex-direction: row;
}
.header-creator {
margin: 0 6px;
cursor: pointer;
font-weight: bolder
}
.header-creator:hover {
text-decoration: underline;
}
.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 {
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-right-info {
margin-left: auto;
display: flex;
align-items: center;
flex-direction: row;
}
.check-mark {
width: 20px;
height: auto;
margin-right: 40px;
color: black;
font-weight: bolder;
border-radius: 50%;
}
.check-mark:hover {
cursor: pointer;
}
.check-mark:focus {
outline: 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;
}
</style>
......@@ -14,9 +14,10 @@ import LeftSideBar from "../components/LeftSideBar";
import SearchView from "@/components/SearchView.vue";
import MusicAlbumView from "../components/MusicAlbumView.vue";
import MainView from "../components/MainView.vue";
import EpisodeView from "@/components/EpisodeView.vue";
// APIs
import {getSongsByPlaylist} from "../api/song";
import {getSongsByEpisode, getSongsByPlaylist} from "../api/song";
import {getPlaylistsByUser} from "../api/playlist";
// Others
......@@ -420,7 +421,34 @@ const switchToPlaylist = (playlist, songId) => {
});
}
const switchToEpisode = (episode, songId) => {
console.log(episode, songId)
currentEpisode.value = episode;
displayingEpisode.value = episode;
currentEpisodeId.value = episode.id;
theme.change(currentEpisode.value.picPath);
getSongsByEpisode({
episode_id: currentEpisodeId.value,
}).then((res) => {
songs.value = res.data.result;
displayingSongs.value = res.data.result;
currentSongId.value = songId;
for (let i = 0; i < songs.value.length; i++) {
if (songs.value[i].id === songId) {
switchToSong(i, true);
parseLrc(songs.value[i].lyricsPath).then(res => {
lyrics.value = res;
});
break;
}
}
}).catch(e => {
console.log("Error while switching episodes!");
});
}
/*
PLAYLISTS
*/
......@@ -453,7 +481,24 @@ const receiveDisplayingPlaylist = (value) => {
});
};
/*
EPISODES
*/
const episodes = ref([]);
const currentEpisode = ref(2);
const currentEpisodeId = ref(2);
const displayingEpisode = ref(2);
const receiveDisplayingEpisode = (value) => {
setMidComponents(4);
displayingEpisode.value = value;
getSongsByEpisode({
episode_id: value.id,
}).then((res) => {
displayingSongs.value = res.data.result;
}).catch(e => {
console.log("Failed to get songs!");
});
};
/*
SEARCH
*/
......@@ -478,6 +523,7 @@ function receiveDataFromHome() {
1 - Music Albums
2 - Comments
3 - Search Results
4 - Episodes
*/
const midComponents = ref(0);
const setMidComponents = (val) => {
......@@ -555,6 +601,7 @@ let playFromLeftBarAlbum = ref(null);
style="overflow: auto; height: 730px ;border-radius: 12px">
<el-button class="exit-search"
data-tooltip="退出"
:class="{ 'adjusted-position': showRightContent }"
@click="setMidComponents(0)"></el-button>
<Comment :song-id=currentSongId :user-id=currentUserId></Comment>
......@@ -567,6 +614,11 @@ let playFromLeftBarAlbum = ref(null);
@click="setMidComponents(0)"></el-button>
<SearchView :songResult="songResult" :playlistResult="playlistResult"/>
</el-container>
<div v-if="midComponents == 4" class="playlist-container"
style="overflow: scroll; border-radius: 12px">
<EpisodeView :episode-info="displayingEpisode" :music-list="displayingSongs"
@switchSongs="switchToEpisode" :playFromLeftBar="playFromLeftBarAlbum"/>
</div>
</div>
<div v-if="showRightContent" class="right-content">
<div v-if="songs[currentSongIndex] !== undefined" class="music-player music-info">
......
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