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

Merge remote-tracking branch 'origin/main'

# Conflicts:
#	src/components/MusicAlbumView.vue
#	src/views/HomePage.vue
parents f7a531c2 7fca645f
No related branches found
No related tags found
No related merge requests found
......@@ -18,13 +18,30 @@ export const getPlaylistsByUser = (userInfo) => {
/*
// TODO - modified
- user_name: string
playlist_name: string
- playlist_name: string
+ user_id: number
+ playlist_description: string
- playlist_description: string
*/
export const createPlaylist = (playlistCreateInfo) => {
return axios.post(`${PLAYLIST_MODULE}/create`, playlistCreateInfo,
{ headers: { 'Content-Type': 'application/json' } })
console.log(playlistCreateInfo)
return axios.post(`${PLAYLIST_MODULE}/create`, null,
{ params: playlistCreateInfo })
.then(res => {
return res;
});
}
/*
// TODO - modified
+ id: number
+ title: string
+ description: string
+ picPath: string
*/
export const modifyPlaylist = (playlistModifyInfo) => {
console.log(playlistModifyInfo)
return axios.post(`${PLAYLIST_MODULE}/modify`, playlistModifyInfo,
{headers: {'Content-Type': 'application/json'}})
.then(res => {
return res;
});
......
......@@ -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
src/assets/icons/disk.png

10.4 KiB

<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>
<script setup>
import {useRouter} from "vue-router";
import {ref} from "vue";
import {searchSongByKeyword, searchPlaylistByKeyword} from "../api/search";
import {searchPlaylistByKeyword, searchSongByKeyword} from "../api/search";
const router = useRouter();
const emit = defineEmits(['headData']);
const emit = defineEmits(['headData','home']);
const props = defineProps({
allowSearch: {
......@@ -54,7 +54,7 @@ function callSearch() {
songResult.value = [];
playlistResult.value = [];
showSearch.value = true
searchSongByKeyword({
keyword: searchInput.value
}).then(res => {
......@@ -80,6 +80,10 @@ function callSearch() {
console.log("Failed to fetch playlists!")
})
}
function callHome() {
emit('home');
}
</script>
<template>
......@@ -91,11 +95,12 @@ function callSearch() {
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="feather feather-search"
@click="callSearch">
@click="callSearch">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input type="text" v-model="searchInput" :placeholder="searchPlaceHolders[Math.floor(Math.random() * 5)]" @keyup.enter="callSearch"/>
<input type="text" v-model="searchInput" :placeholder="searchPlaceHolders[Math.floor(Math.random() * 5)]"
@keyup.enter="callSearch"/>
</div>
<div style="display: flex; flex-direction: row">
......@@ -105,14 +110,14 @@ function callSearch() {
d="M480 480V128a32 32 0 0 1 64 0v352h352a32 32 0 1 1 0 64H544v352a32 32 0 1 1-64 0V544H128a32 32 0 0 1 0-64z"></path>
</svg>
</router-link>
<router-link to="/home" class="home-btn">
<div @click="callHome" class="home-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="feather feather-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
</router-link>
</div>
<div @click="toggleIcons" class="more-btn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<path fill="currentColor"
......@@ -166,9 +171,9 @@ function callSearch() {
margin-right: 10px;
color: #fff;
text-decoration: none;
cursor: pointer;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
cursor: pointer;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
}
.home-btn svg {
......@@ -178,22 +183,22 @@ function callSearch() {
}
.home-btn:hover {
transform: translateY(-2px);
transform: translateY(-2px);
}
.home-btn:hover::after {
content: '回到主页';
position: absolute;
top: 35px;
left: 50%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
z-index: 1000;
content: '回到主页';
position: absolute;
top: 35px;
left: 50%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
z-index: 1000;
}
.manage-btn {
......@@ -202,9 +207,9 @@ function callSearch() {
margin-right: 10px;
color: #fff;
text-decoration: none;
cursor: pointer;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
cursor: pointer;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
}
.manage-btn svg {
......@@ -214,25 +219,25 @@ function callSearch() {
}
.manage-btn:hover {
transform: translateY(-2px);
transform: translateY(-2px);
}
/*
添加悬停样式
*/
.manage-btn:hover::after {
content: '增加歌曲';
position: absolute;
top: 35px;
left: 50%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
z-index: 1000;
content: '增加歌曲';
position: absolute;
top: 35px;
left: 50%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
z-index: 1000;
}
.search-box {
......@@ -279,39 +284,39 @@ input[type="text"]:focus {
}
.more-btn {
display: flex;
align-items: center;
margin-right: 10px;
color: #fff;
text-decoration: none;
cursor: pointer;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
display: flex;
align-items: center;
margin-right: 10px;
color: #fff;
text-decoration: none;
cursor: pointer;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
}
.more-btn svg {
width: 32px;
height: 32px;
margin-right: 10px;
width: 32px;
height: 32px;
margin-right: 10px;
}
.more-btn:hover {
transform: scale(1.1);
transform: scale(1.1);
}
.more-btn:hover::after {
content: '更多';
position: absolute;
top: 32px;
left: 38%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
z-index: 1000;
content: '更多';
position: absolute;
top: 32px;
left: 38%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
z-index: 1000;
}
.role-icon {
......@@ -319,9 +324,9 @@ input[type="text"]:focus {
opacity: 0;
transform: translateX(-20px); /* 初始位置稍微偏左 */
animation: slideIn 0.5s forwards;
cursor: pointer;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
cursor: pointer;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
}
.role-icon svg {
......@@ -332,95 +337,95 @@ input[type="text"]:focus {
}
.role-icon svg:hover {
transform: translateY(-2px);
transform: translateY(-2px);
}
.role-icon:hover::after {
content: '个人主页';
position: absolute;
top: 35px;
left: 50%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
z-index: 1000;
content: '个人主页';
position: absolute;
top: 35px;
left: 50%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
z-index: 1000;
}
.set-icon {
margin: 10px 0;
opacity: 0;
transform: translateX(-20px); /* 初始位置稍微偏左 */
animation: slideIn 0.5s forwards;
cursor: pointer;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
margin: 10px 0;
opacity: 0;
transform: translateX(-20px); /* 初始位置稍微偏左 */
animation: slideIn 0.5s forwards;
cursor: pointer;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
}
.set-icon svg {
color: #d5d5d5;
width: 32px;
height: 32px;
margin-right: 10px;
color: #d5d5d5;
width: 32px;
height: 32px;
margin-right: 10px;
}
.set-icon svg:hover {
transform: translateY(-2px);
transform: translateY(-2px);
}
.set-icon:hover::after {
content: '设置';
position: absolute;
top: 35px;
left: 38%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
z-index: 1000;
content: '设置';
position: absolute;
top: 35px;
left: 38%;
transform: translateX(-50%);
background-color: #282828;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
z-index: 1000;
}
.exit-icon {
margin: 10px 0;
opacity: 0;
transform: translateX(-20px); /* 初始位置稍微偏左 */
animation: slideIn 0.5s forwards;
cursor: pointer;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
margin: 10px 0;
opacity: 0;
transform: translateX(-20px); /* 初始位置稍微偏左 */
animation: slideIn 0.5s forwards;
cursor: pointer;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
}
.exit-icon svg {
color: #d5d5d5;
width: 32px;
height: 32px;
margin-right: 10px;
color: #d5d5d5;
width: 32px;
height: 32px;
margin-right: 10px;
}
.exit-icon svg:hover {
color: red;
transform: translateY(-2px);
color: red;
transform: translateY(-2px);
}
.exit-icon:hover::after {
content: '退出';
color: red;
position: absolute;
top: 35px;
left: 38%;
transform: translateX(-50%);
background-color: #282828;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
z-index: 1000;
content: '退出';
color: red;
position: absolute;
top: 35px;
left: 38%;
transform: translateX(-50%);
background-color: #282828;
padding: 4px 8px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
z-index: 1000;
}
@keyframes slideIn {
......
......@@ -5,7 +5,7 @@ import musicAlbumClosed from "../icon/musicAlbumClosed.vue";
import searchIcon from "../icon/searchIcon.vue";
import plusIcon from "../icon/plusIcon.vue";
import playButton from "../icon/playButton.vue";
import {getPlaylistsByUser} from "../api/playlist";
import {createPlaylist, getPlaylistsByUser} from "../api/playlist";
import {ElPopover} from "element-plus";
const emit = defineEmits();
......@@ -28,39 +28,50 @@ const userToken = ref(JSON.parse(sessionStorage.getItem('user-token')));
const currentUserId = ref(userToken.value.id);
function toggleSideBar() {
isSideBarOpen = !isSideBarOpen;
sideBarWidth.value = isSideBarOpen ? criticalWidth : minWidth;
isSideBarOpen = !isSideBarOpen;
sideBarWidth.value = isSideBarOpen ? criticalWidth : minWidth;
}
function startResizing(event) {
event.preventDefault();
const initialWidth = sideBarWidth.value;
const initialMouseX = event.clientX;
const onMouseMove = (moveEvent) => {
sideBarWidth.value = initialWidth + (moveEvent.clientX - initialMouseX);
// 确保宽度不小于最小值
if (sideBarWidth.value <= criticalWidth) {
isSideBarOpen = false;
sideBarWidth.value = minWidth;
}
// 确保宽度不大于最大值
else if (sideBarWidth.value >= maximumWidth) {
sideBarWidth.value = maximumWidth;
} else
isSideBarOpen = true;
};
const onMouseUp = () => {
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
};
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
event.preventDefault();
const initialWidth = sideBarWidth.value;
const initialMouseX = event.clientX;
const onMouseMove = (moveEvent) => {
sideBarWidth.value = initialWidth + (moveEvent.clientX - initialMouseX);
// 确保宽度不小于最小值
if (sideBarWidth.value <= criticalWidth) {
isSideBarOpen = false;
sideBarWidth.value = minWidth;
}
// 确保宽度不大于最大值
else if (sideBarWidth.value >= maximumWidth) {
sideBarWidth.value = maximumWidth;
} else
isSideBarOpen = true;
};
const onMouseUp = () => {
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
};
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
}
function addAlbum() {
//TODO:添加新的空歌单
console.log("Hi")
createPlaylist({
user_id: currentUserId.value,
}).then(() => {
getPlaylistsByUser({
user_id: currentUserId.value
}).then(res => {
musicAlbums.value = res.data.result || []
}).catch(e => {
})
})
}
function toggleSearchBar() {
......@@ -94,7 +105,7 @@ onMounted(() => {
const popover1 = ref(null)
const closePopover = () => {
popover1.value.hide();
popover1.value.hide();
}
defineProps({
......@@ -113,11 +124,11 @@ defineProps({
</div>
<el-popover v-if="isSideBarOpen" class="dropdown-options"
ref="popover1"
ref="popover1"
:width="200"
trigger="click"
:hide-after=0
popper-class="left-popover">
popper-class="left-popover">
<template #reference>
<div class="add-album">
<plus-icon class="plus-icon"/>
......@@ -142,16 +153,16 @@ defineProps({
@mouseenter="()=>{hoverOnAlbum=true}"
@mouseleave="()=>{hoverOnAlbum=false}"
:style="{ scrollbarWidth : hoverOnAlbum? 'auto':'none'}"
>
>
<div v-if="musicAlbums !== undefined" v-for="album in musicAlbums"
:key="album.id"
@mouseenter="()=>{albumHoveredIndex = album.id}"
@mouseleave="()=>{albumHoveredIndex = null}"
:style="{backgroundColor: albumHoveredIndex === album.id ? '#1F1F1F' : '#171717' }"
@click="emit('setCurrentPlaylist', album);"
class="musicAlbum-item">
@click="emit('setCurrentPlaylist', album);"
class="musicAlbum-item">
<img
:src="album.picPath"
alt="playlist"
......@@ -163,7 +174,8 @@ defineProps({
<div class="musicAlbum-description">
<p style="padding-bottom: 5px;font-size: 18px">{{ album.title }}</p>
<p v-if="album.title !== '我喜欢的歌曲'" style="color: #b2b2b2;font-size: 13px">歌单 • {{ userToken.username }}</p>
<p v-if="album.title !== '我喜欢的歌曲'" style="color: #b2b2b2;font-size: 13px">歌单 •
{{ userToken.username }}</p>
<p v-else style="color: #b2b2b2;font-size: 13px">默认收藏夹</p>
</div>
</div>
......@@ -194,7 +206,7 @@ ul {
li {
color: white;
padding: 10px 12px;
border-radius: 10px;
border-radius: 10px;
}
li:hover {
......
......@@ -40,7 +40,7 @@ getSongsByPlaylist({
<div v-for="song in songs"
:key="song.id"
class="song">
<img :src="song.picPath" alt="">
<img class="recommend-song-img" :src="song.picPath" alt="">
<h2>{{ song.title }}</h2>
<p>{{ song.artist }}</p>
</div>
......@@ -126,4 +126,8 @@ getSongsByPlaylist({
background-color: #fff;
color: #000;
}
.recommend-song-img:hover {
transform: rotate(3deg) scale(1.02);
}
</style>
\ No newline at end of file
......@@ -6,7 +6,7 @@ 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";
import {modifyPlaylist, removePlaylist, removeSongFromPlaylist} from "../api/playlist";
const emit = defineEmits();
const props = defineProps({
......@@ -26,8 +26,9 @@ const props = defineProps({
currentSongId: Number,
});
const gradientColor = computed(() => `linear-gradient(to bottom, ${backgroundColor.value} , #1F1F1F 50%)`)
const edit_title = ref("");
const edit_description = ref("");
const edit_cover_path = ref("");
const recMusicList = ref([
{
......@@ -47,7 +48,7 @@ let musicPlayIndex = ref(null);
let musicPauseIndex = ref(null);
const resizeObserver = ref(null)
const gradientColor = computed(() => `linear-gradient(to bottom, ${backgroundColor.value} , #1F1F1F 50%)`)
// 放缩时的组件处理
const handleResize = () => {
......@@ -121,59 +122,59 @@ onMounted(() => {
onUnmounted(() => {
if (resizeObserver.value) {
resizeObserver.value.disconnect();
}
popovers.value=null;
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";
}
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";
}
}
watch(props.playFromLeftBar, () => {
playFromId(props.playFromLeftBar)
playFromId(props.playFromLeftBar)
})
const popovers = ref([])
const getPopoverIndex= (popover) => {
if (popover) {
popovers.value.push(popover);
}
const getPopoverIndex = (popover) => {
if (popover) {
popovers.value.push(popover);
}
}
const closePopover = (e) => {
popovers.value.forEach((item) => {
item.hide();
})
popovers.value.forEach((item) => {
item.hide();
})
}
......@@ -196,8 +197,8 @@ const playFromId = (musicId) => {
} else {
musicPlayIndex.value = musicId;
}
musicPauseIndex = null;
emit('switchSongs', props.albumInfo, musicPlayIndex.value);
musicPauseIndex = null;
}
const addToFavorite = (musicId,albumId) => {
......@@ -233,6 +234,17 @@ const editAlbumDescription = (albumId) => {
editDesc.style.visibility = "visible";
}
const confirmEdit = (albumId) => {
modifyPlaylist({
id: albumId,
title: edit_title.value,
description: edit_description.value,
picPath: "",
}).then(() => {
})
}
const quitEdit = () => {
const editDesc = document.querySelector(".edit-desc");
editDesc.style.visibility = "hidden";
......@@ -272,7 +284,7 @@ const addRecommendMusic = (musicId) => {
</div>
</div>
</div>
<div class="content">
<div class="play-area">
<div class="play-button">
......@@ -300,7 +312,7 @@ const addRecommendMusic = (musicId) => {
</ul>
</el-popover>
</div>
<div class="fixed-play-area" :style="{background :`${backgroundColor}`}">
<div class="opacity-wrapper">
<div class="play-button">
......@@ -318,7 +330,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:55px">时间</p>
</div>
<div class="edit-desc" @blur="quitEdit">
<div data-testid="playlist-edit-details-modal" class="main-edit-desc">
......@@ -373,18 +385,17 @@ const addRecommendMusic = (musicId) => {
</div>
</div>
<div class="edit-desc-input-name">
<input data-testid="playlist-edit-details-name-input" id="text-input-c673a65959365e7f"
type="text"
class="edit-desc-input-name-1" placeholder="添加名称" value="我的 #9 歌单">
<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 data-testid="playlist-edit-details-description-input" class="edit-desc-input-desc-1"
placeholder="添加简介"></textarea>
<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 data-testid="playlist-edit-details-save-button" data-encore-id="buttonPrimary"
class="edit-desc-button-1 encore-text-body-medium-bold"><span
class="edit-desc-button-1-1">收藏</span></button>
<button @click="confirmEdit(albumInfo.id)" data-testid="playlist-edit-details-save-button"
data-encore-id="buttonPrimary"
class="edit-desc-button-1 encore-text-body-medium-bold">
<span class="edit-desc-button-1-1">收藏</span>
</button>
</div>
<p class="encore-text encore-text-marginal-bold final-tip" data-encore-id="text">继续下一步,则表示你已同意
Spotify 获取你选择上传的图像。请确保你有上传此图像的权利。</p>
......@@ -397,7 +408,7 @@ const addRecommendMusic = (musicId) => {
<p class="album-text" style="position:absolute; left:62%">专辑</p>
<p style="margin-left: auto; margin-right:20px">详情</p><!--时间变为详细信息-->
</div>
<div class="musicList">
<div class="music-item"
v-for="music in musicList"
......@@ -509,22 +520,23 @@ const addRecommendMusic = (musicId) => {
<span style="color:white;font-size: 30px;font-weight: bolder">推荐</span>
<span style="color:grey;font-size: 20px">根据此歌单包含的内容推荐
</span>
</div>
</div>
<div class="recMusicList">
<div class="music-item"
v-for="music in recMusicList"
:key="music.id"
:aria-selected="musicClickedIndex === music.id ? 'True':'False'"
@mouseenter="()=>{musicHoveredIndex = music.id;}"
@mouseleave="()=>{musicHoveredIndex = null}"
@click="musicClickedIndex=music.id"
@dblclick="playFromId(music.id)"
:style="{backgroundColor: musicClickedIndex===music.id? '#404040':
</div>
</div>
<div class="recMusicList">
<div class="music-item"
v-for="music in recMusicList"
:key="music.id"
:aria-selected="musicClickedIndex === music.id ? 'True':'False'"
@mouseenter="()=>{musicHoveredIndex = music.id;}"
@mouseleave="()=>{musicHoveredIndex = null}"
@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)',
}">
<div
:style="{visibility: musicHoveredIndex === music.id||musicPlayIndex === music.id ? 'hidden' : 'visible' }">
{{
......@@ -578,51 +590,50 @@ const addRecommendMusic = (musicId) => {
<style scoped>
li {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
li:hover {
background-color: #363636;
border-radius: 12px;
background-color: #363636;
border-radius: 12px;
}
p {
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0;
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;
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; /*千万不能删,不然背景黑一半*/
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;
display: flex;
flex-direction: row;
}
.content {
......@@ -631,189 +642,189 @@ p {
}
.album-image {
border-radius: 6%;
width: 160px;
height: 160px;
margin-top: 30px;
margin-left: 15px;
margin-right: 20px;
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;
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;
padding: 10px;
align-items: center;
display: flex;
flex-direction: row;
}
.header-creator {
margin: 0 6px;
cursor: pointer;
font-weight: bolder
margin: 0 6px;
cursor: pointer;
font-weight: bolder
}
.header-creator:hover {
text-decoration: underline;
text-decoration: underline;
}
.play-area {
position: relative;
position: relative;
}
.more-info {
font-size: 35px;
position: absolute;
z-index: 13;
top: 33px;
left: 160px;
transition: width 0.1s ease-in-out;
font-size: 35px;
position: absolute;
z-index: 13;
top: 33px;
left: 160px;
transition: width 0.1s ease-in-out;
}
.more-info:focus {
outline: none;
outline: none;
}
.more-info:hover {
cursor: pointer;
transform: scale(1.15);
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;
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);
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;
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;
cursor: pointer;
transform: scale(1.05);
background-color: #1ed683;
}
.tips {
z-index: 0;
display: flex;
position: relative;
padding: 5px 8px 5px 8px;
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;
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;
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;
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;
color: #1ed660;
}
/*左侧信息*/
.music-detailed-info {
display: flex;
flex-direction: row;
display: flex;
flex-direction: row;
}
.music-image {
padding-left: 30px;
height: 50px;
width: 50px; /* Adjust as needed */
border-radius: 10px;
padding-left: 30px;
height: 50px;
width: 50px; /* Adjust as needed */
border-radius: 10px;
}
.music-name {
padding-bottom: 5px;
font-size: 18px
padding-bottom: 5px;
font-size: 18px
}
.music-name:hover {
cursor: pointer;
text-decoration: underline;
cursor: pointer;
text-decoration: underline;
}
.music-author {
color: #b2b2b2;
font-size: 15px
color: #b2b2b2;
font-size: 15px
}
.music-author:hover {
cursor: pointer;
text-decoration: underline;
cursor: pointer;
text-decoration: underline;
}
/*专辑信息*/
.music-album-info {
position: absolute;
left: 60%;
color: #b2b2b2;
text-overflow: ellipsis;
position: absolute;
left: 60%;
color: #b2b2b2;
text-overflow: ellipsis;
}
.music-album-info:hover {
cursor: pointer;
text-decoration: underline;
cursor: pointer;
text-decoration: underline;
}
.music-time-info {
......@@ -824,136 +835,136 @@ p {
/*右侧信息*/
.music-right-info {
margin-left: auto;
display: flex;
align-items: center;
flex-direction: row;
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%;
width: 20px;
height: auto;
margin-right: 40px;
color: black;
font-weight: bolder;
border-radius: 50%;
}
.check-mark:hover {
cursor: pointer;
cursor: pointer;
}
.check-mark:focus {
outline: none;
outline: none;
}
.album-to-add{
padding: 8px;
}
.music-more-info {
margin-right: 14px;
font-size: 22px;
transition: width 0.1s ease-in-out;
margin-right: 14px;
font-size: 22px;
transition: width 0.1s ease-in-out;
}
.music-more-info:focus {
outline: none;
transform: scale(1.05);
outline: none;
transform: scale(1.05);
}
.music-more-info:hover {
cursor: pointer;
cursor: pointer;
}
.music-dropdown-options {
border-radius: 6px;
border-radius: 6px;
}
ul {
background-color: #282828;
list-style-type: none;
padding: 10px ;
margin: 0;
border-radius: 10px;
background-color: #282828;
list-style-type: none;
padding: 0;
margin: 0;
border-radius: 10px;
}
li {
color: white;
padding: 15px 12px;
color: white;
padding: 15px 12px;
}
li:hover {
cursor: pointer;
text-decoration: underline;
cursor: pointer;
text-decoration: underline;
}
.other-info {
margin-top: 20px;
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;
}
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;
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;
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 {
......@@ -1024,7 +1035,7 @@ li:hover {
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;
}
......@@ -1106,7 +1117,7 @@ li:hover {
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
&:hover {
opacity: 0;
pointer-events: none;
......
/* eslint-disable */
<script setup>
// Vue Basics
import {onMounted, ref, watch, computed, nextTick} from "vue"
import {router} from "../router";
import {computed, onMounted, ref} from "vue"
// Assets
import defaultBg from '../assets/pictures/Eason.png'
......@@ -15,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
......@@ -80,7 +80,7 @@ function updateCurrentLine() {
}
setInterval(() => {
// console.log(progresses.length, controlIcons.length, playModeIcons.length);
// console.log(progresses.length, controlIcons.length, playModeIcons.length);
}, 1000);
......@@ -128,7 +128,7 @@ const registerDOMs = () => {
*/
const navItems = document.querySelectorAll(".nav-item");
const containers = document.querySelectorAll(".containers");
navItems.forEach((navItem) => {
navItem.addEventListener("click", () => {
navItems.forEach((item) => {
......@@ -141,7 +141,7 @@ const registerDOMs = () => {
let isDragging = false;
let startX;
let scrollLeft;
container.addEventListener("mousedown", (e) => {
isDragging = true;
startX = e.pageX - container.offsetLeft;
......@@ -152,7 +152,7 @@ const registerDOMs = () => {
return;
}
e.preventDefault();
const x = e.pageX - container.offsetLeft;
const step = (x - startX) * 0.6;
container.scrollLeft = scrollLeft - step;
......@@ -167,7 +167,7 @@ const registerDOMs = () => {
container.scrollLeft += e.deltaY / 2;
});
});
/*
Songs Related
*/
......@@ -177,11 +177,11 @@ const registerDOMs = () => {
backwardButtons = document.querySelectorAll(".controls button.backward");
playModeButtons = document.querySelectorAll(".play-mode-btn");
shareButtons = document.querySelectorAll(".share-btn");
progresses = document.querySelectorAll('.idProgress');
controlIcons = document.querySelectorAll('.idControlIcon');
playModeIcons = document.querySelectorAll('.idPlayModeIcon');
function updateSongInfo() {
try {
if (songs.value[currentSongIndex.value]) {
......@@ -200,11 +200,11 @@ const registerDOMs = () => {
console.log("Uncaught Error in updateSongInfo!", e);
}
}
function shareSong() {
console.log("Hello!");
}
function playPause() {
isPaused.value = !isPaused.value;
try {
......@@ -223,7 +223,7 @@ const registerDOMs = () => {
console.log("Uncaught Error in playPause!", e);
}
}
function switchPlayMode() {
playingMode.value = (playingMode.value + 1) % 3
switch (playingMode.value) {
......@@ -246,7 +246,7 @@ const registerDOMs = () => {
break;
}
}
song.addEventListener("loadedmetadata", function () {
progresses.forEach(progress => {
duration.value = song.duration;
......@@ -265,7 +265,7 @@ const registerDOMs = () => {
});
}
});
playPauseButtons.forEach(playPauseButton => {
if (!playPauseButton._hasClickListener) {
playPauseButton.addEventListener("click", playPause);
......@@ -302,7 +302,7 @@ const registerDOMs = () => {
backwardButton._hasClickListener = true;
}
});
progresses.forEach(progress => {
progress.addEventListener("input", function () {
if (!song.paused) {
......@@ -319,7 +319,7 @@ const registerDOMs = () => {
}
});
});
// updateSongInfo();
}
......@@ -331,12 +331,15 @@ const userToken = ref(JSON.parse(sessionStorage.getItem('user-token')));
const currentUserId = ref(userToken.value.id);
/*
SONGS
*/
// Playing Status
const songs = ref([]);
const volumn = ref(1);
watch(volumn, (newValue) => {
song.volume = newValue;
});
const displayingSongs = ref([]);
const isPaused = ref(false);
const duration = ref(0);
......@@ -375,10 +378,10 @@ const switchToSong = (index, isDiffPlaylist) => {
if (index === currentSongIndex.value && !isDiffPlaylist) {
return;
}
currentSongIndex.value = index;
currentSongId.value = songs.value[index].id;
if (song) {
controlIcons.forEach(controlIcon => {
controlIcon.src = PLAY;
......@@ -395,12 +398,12 @@ const switchToSong = (index, isDiffPlaylist) => {
const switchToPlaylist = (playlist, songId) => {
console.log(playlist, songId)
currentPlaylist.value = playlist;
displayingPlaylist.value = playlist;
currentPlaylistId.value = playlist.id;
theme.change(currentPlaylist.value.picPath);
getSongsByPlaylist({
playlist_id: currentPlaylistId.value,
}).then((res) => {
......@@ -416,13 +419,40 @@ const switchToPlaylist = (playlist, songId) => {
break;
}
}
}).catch(e => {
console.log("Error while switching playlists!");
});
}
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
*/
......@@ -431,17 +461,17 @@ const currentPlaylist = ref(2);
const currentPlaylistId = ref(2);
const displayingPlaylist = ref(2);
const receivePlaylistId = (value) => {
console.log(value)
currentPlaylist.value = value;
currentPlaylistId.value = value.id;
console.log("Current Playlist Id:", currentPlaylistId.value)
getSongsByPlaylist({
playlist_id: currentPlaylistId.value,
}).then((res) => {
songs.value = res.data.result;
}).catch(e => {
console.log("Failed to get songs!");
});
console.log(value)
currentPlaylist.value = value;
currentPlaylistId.value = value.id;
console.log("Current Playlist Id:", currentPlaylistId.value)
getSongsByPlaylist({
playlist_id: currentPlaylistId.value,
}).then((res) => {
songs.value = res.data.result;
}).catch(e => {
console.log("Failed to get songs!");
});
};
const receiveDisplayingPlaylist = (value) => {
setMidComponents(1);
......@@ -455,7 +485,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
*/
......@@ -468,13 +515,22 @@ function receiveDataFromHeader(data) {
setMidComponents(3);
}
/*
HOME
*/
function receiveDataFromHome() {
setMidComponents(0);
}
/*
MID COMPONENTS
0 - Main View
1 - Music Albums
2 - Comments
3 - Search Results
4 - Episodes
*/
const midComponents = ref(1);
const midComponents = ref(0);
const setMidComponents = (val) => {
midComponents.value = val;
}
......@@ -490,7 +546,7 @@ onMounted(() => {
*/
theme.change(defaultBg);
registerDOMs();
/*
API
*/
......@@ -510,7 +566,7 @@ onMounted(() => {
songs.value = res.data.result;
displayingSongs.value = res.data.result;
currentSongId.value = songs.value[0].id;
// TODO: currentSongIndex != currentSongId ?
parseLrc(songs.value[currentSongIndex.value].lyricsPath).then(res => {
lyrics.value = res;
......@@ -521,63 +577,70 @@ onMounted(() => {
}).catch(e => {
console.log("Failed to get playlists!");
});
})
let playFromLeftBarAlbum = ref(null);
</script>
<template>
<div class="body" v-show="!isPlayingPage" @click="unSelectAlbum">
<!-- MAIN & RIGHT CONTENT -->
<Header class="header" @headData="receiveDataFromHeader" allow-search></Header>
<img class="logo" src="../assets/pictures/logos/logo3.png" alt="">
<left-side-bar class="left-side-bar" @playFromLeftBar="(id)=>{playFromLeftBarAlbum = id }" @setCurrentPlaylist="receiveDisplayingPlaylist"/>
<div class="content" :class="{ 'full-width': !showRightContent }">
<div class="main-view" :class="{ 'expanded': !showRightContent }">
<el-container v-if="midComponents == 0" class="playlist-container"
style="overflow: auto; height: 730px ;border-radius: 12px">
<MainView/>
</el-container>
<!--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" :play-list="playlists" :playFromLeftBar="playFromLeftBarAlbum"/>
</div>
<el-container v-if="midComponents == 2" class="playlist-container"
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>
</el-container>
<el-container v-if="midComponents == 3" class="playlist-container"
style="overflow: auto; height: 730px ;border-radius: 12px">
<el-button class="exit-search"
data-tooltip="退出"
:class="{ 'adjusted-position': showRightContent }"
@click="setMidComponents(0)"></el-button>
<SearchView :songResult="songResult" :playlistResult="playlistResult"/>
</el-container>
</div>
<div v-if="showRightContent" class="right-content">
<div v-if="songs[currentSongIndex] !== undefined" class="music-player music-info">
<div class="album-cover" @click="togglePlayingPage">
<img :src="songs[currentSongIndex].picPath" style="margin-top: 10px" id="rotatingImage" alt=""/>
<span class="point"></span>
</div>
<h2>{{ songs[currentSongIndex].title }}</h2>
<p>{{ songs[currentSongIndex].artist }}</p>
</div>
<div class="current-playlist" style="margin-top: 20px">
<el-container class="playlist-container" style="height: 64px">
<div class="playlist-item" style="display: flex; flex-direction: row">
<img src="../assets/icons/add.png" alt="" style=""/>
<div style="display: flex; flex-direction: column; align-items: center; margin-left: 10px">
<p class="playlist-container-desc" style="
<!-- MAIN & RIGHT CONTENT -->
<Header class="header" @headData="receiveDataFromHeader" @home="receiveDataFromHome" allow-search></Header>
<img class="logo" src="../assets/pictures/logos/logo3.png" alt="">
<left-side-bar class="left-side-bar" @playFromLeftBar="(id)=>{playFromLeftBarAlbum = id }"
@setCurrentPlaylist="receiveDisplayingPlaylist"/>
<div class="content" :class="{ 'full-width': !showRightContent }">
<div class="main-view" :class="{ 'expanded': !showRightContent }">
<el-container v-if="midComponents == 0" class="playlist-container"
style="overflow: auto; height: 730px ;border-radius: 12px">
<MainView/>
</el-container>
<!--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"/>
</div>
<el-container v-if="midComponents == 2" class="playlist-container"
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>
</el-container>
<el-container v-if="midComponents == 3" class="playlist-container"
style="overflow: auto; height: 730px ;border-radius: 12px">
<el-button class="exit-search"
data-tooltip="退出"
:class="{ 'adjusted-position': showRightContent }"
@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">
<div class="album-cover" @click="togglePlayingPage">
<img :src="songs[currentSongIndex].picPath" style="margin-top: 10px" id="rotatingImage" alt=""/>
<span class="point"></span>
</div>
<h2>{{ songs[currentSongIndex].title }}</h2>
<p>{{ songs[currentSongIndex].artist }}</p>
</div>
<div class="current-playlist" style="margin-top: 20px">
<el-container class="playlist-container" style="height: 64px">
<div class="playlist-item" style="display: flex; flex-direction: row">
<img src="../assets/icons/add.png" alt="" style=""/>
<div style="display: flex; flex-direction: column; align-items: center; margin-left: 10px">
<p class="playlist-container-desc" style="
color: white;
font-size: 16px;
text-align: left;
......@@ -586,7 +649,8 @@ let playFromLeftBarAlbum = ref(null);
</div>
</div>
</el-container>
<el-container class="playlist-container" style="overflow: auto; height: 384px; display: flex; flex-direction: column">
<el-container class="playlist-container"
style="overflow: auto; height: 384px; display: flex; flex-direction: column">
<div v-for="(song, index) in songs" class="playlist-item"
style="display: flex; flex-direction: row">
<div @click="switchToSong(index, false)" style="cursor: pointer">
......@@ -618,8 +682,8 @@ let playFromLeftBarAlbum = ref(null);
</div>
</div>
</div>
<!-- FOOTER -->
<footer>
<div class="bottom-description bottom-component"
......@@ -650,7 +714,7 @@ let playFromLeftBarAlbum = ref(null);
{{ songs[currentSongIndex].artist }}</p>
</div>
</div>
<el-card class="bottom-controller bottom-component" style="
position: absolute;
left: 50%;
......@@ -683,20 +747,25 @@ let playFromLeftBarAlbum = ref(null);
</div>
</el-card>
<div class="right-controls">
<div class="volumn-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">
</div>
<div class="feature-icon"
data-tooltip="分享"
:class="{ active: isSharing }">
<img src="../assets/icons/comment/share.png" alt="分享">
</div>
<div class="feature-icon"
data-tooltip="评论"
:class="{ active: midComponents === 2 }"
@click="setMidComponents(2)">
<img src="../assets/icons/comment/comment.png" alt="评论">
</div>
<div class="feature-icon"
data-tooltip="播放队列"
:class="{ active: showRightContent }"
......@@ -706,8 +775,8 @@ let playFromLeftBarAlbum = ref(null);
</div>
</footer>
</div>
<!-- PLAYING PAGE -->
<div v-show="isPlayingPage" class="playing-page">
<div v-if="isLyricsDisplaying" class="lyrics-container">
......@@ -717,7 +786,8 @@ let playFromLeftBarAlbum = ref(null);
:key="index"
:class="{ active: index === currentLineIndex }"
class="lyrics-line"
>{{ line.text }}</div>
>{{ line.text }}
</div>
<h1 v-if="lyrics.length === 0" style="
font-size: 24px;
color: #9d9d9d;
......@@ -726,8 +796,8 @@ let playFromLeftBarAlbum = ref(null);
">Ouch!该歌曲暂无歌词!</h1>
</div>
</div>
<!-- <div class="player" :style="{ backgroundImage: gradientColor }">-->
<!-- <div class="player" :style="{ backgroundImage: gradientColor }">-->
<div class="player">
<div class="background"></div>
<div class="player-content">
......@@ -785,6 +855,10 @@ let playFromLeftBarAlbum = ref(null);
</div>
</div>
</div>
<div class="volumn-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">
</div>
<div class="corner-buttons">
<button @click="toggleLyrics" class="corner-button">
<span v-if="isLyricsDisplaying" style="text-decoration: underline">A</span>
......@@ -859,7 +933,7 @@ h1 {
background-color: rgb(19, 19, 19); /* rgba(0, 0, 0, 1); */
background-repeat: no-repeat;
background-size: cover;
/* 原先main中的内容
height: 700px;
width: 95%;
......@@ -878,9 +952,9 @@ 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;
padding: 8px;
overflow: hidden;
......@@ -896,17 +970,17 @@ h1 {
left-side-bar {
grid-area: left-sideBar;
}
.content {
grid-area: main-view;
}
footer {
grid-area: now-playing-bar;
}
/* MAIN MENU */
......@@ -996,24 +1070,25 @@ footer {
/* CONTENT 包含中间和右边栏 是grid布局*/
.content {
height: 100%;
height: 100%;
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: 100%;
grid-template-rows: 100%;
transition: all 0.3s ease;
column-gap: 8px;
}
.content.full-width {
grid-template-columns: 100% !important;
}
/* LEFT CONTENT */
.main-view{
overflow: scroll;
.main-view {
overflow: scroll;
}
.main-view > {
display: flex;
flex-direction: column;
......@@ -1025,7 +1100,7 @@ footer {
}
.main-view.expanded {
margin: 0;
padding: 0;
width: 100%;
......@@ -1119,7 +1194,7 @@ footer {
-webkit-box-orient: vertical;
overflow: hidden;
max-width: 150px;
@supports (-webkit-line-clamp: 2) {
overflow: hidden;
display: -webkit-box;
......@@ -1146,7 +1221,6 @@ footer {
}
*/
.album-container::-webkit-scrollbar {
height: 10px;
display: none;
......@@ -1173,7 +1247,6 @@ footer {
border-radius: 12px;
padding: 10px 20px 20px;
color: #e5e5e5;
overflow: scroll;
}
/* SONGS */
......@@ -1354,17 +1427,17 @@ footer {
main {
grid-template-columns: 15% 85%;
}
.user-info img {
border-radius: 50%;
padding: 12px 12px 6px;
}
.nav-icon {
text-align: center;
transform: translateY(2px);
}
.content {
grid-template-columns: 70% 30%;
}
......@@ -1381,20 +1454,20 @@ footer {
.swiper-slide {
width: 500px;
}
.swiper-slide img {
border-radius: 16px;
height: 280px;
}
.album-frame {
width: 160px;
}
.song {
grid-template-columns: 29% auto 10%;
}
.controls button {
margin: 15px;
}
......@@ -1404,31 +1477,31 @@ footer {
.content:not(.full-width) {
grid-template-columns: 60% 40%;
}
.main-view {
}
.swiper-slide {
width: 410px;
}
.swiper-slide img {
height: 220px;
}
.album {
grid-template-rows: 4fr 2fr;
}
.album-frame {
width: 130px;
}
.song {
grid-template-columns: 26% auto 10%;
}
.song:nth-child(8),
.song:nth-child(9) {
display: none;
......@@ -1440,83 +1513,83 @@ footer {
grid-template-columns: 10% 90%;
margin: 20px;
}
.main-view {
/*padding: 30px 20px 20px;*/
}
.swiper-slide img {
height: 180px;
}
.album {
grid-template-rows: 3fr 2fr;
}
.album-frame {
width: 100px;
}
.right-content {
grid-template-rows: 55% 45%;
}
}
@media (max-width: 825px) {
.content:not(.full-width) {
grid-template-columns: 52% 48%;
}
.swiper-slide {
width: 280px;
}
.swiper-slide img {
height: 200px;
}
.slide-overlay button {
padding: 8px 12px;
}
}
@media (max-width: 750px) {
main {
grid-template-columns: 15% 85%;
}
.content:not(.full-width) {
grid-template-columns: 100%;
grid-template-areas:
"leftContent"
"rightContent";
}
.main-view {
grid-area: leftContent;
}
.album {
grid-template-rows: 3fr 2fr;
}
.album-frame {
width: 140px;
}
.right-content {
grid-area: rightContent;
border-left: unset;
grid-template-rows: 60% 40%;
row-gap: 16px;
}
#progress {
width: 60%;
}
.controls button {
margin: 20px;
}
......@@ -1526,23 +1599,23 @@ footer {
.swiper-slide {
width: 290px;
}
.swiper-slide img {
height: 180px;
}
.artist img {
width: 80px;
}
.album {
grid-template-rows: 3fr 2fr;
}
.album-frame {
width: 100px;
}
}
@media (max-width: 450px) {
......@@ -1550,7 +1623,7 @@ footer {
border-radius: 50%;
padding: 6px 6px 2px;
}
}
/* 动画:专辑列表移到顶部 */
......@@ -1603,6 +1676,7 @@ footer {
transform: translateY(0);
}
}
/**
.share-icon,
.queue-icon,
......@@ -1738,11 +1812,6 @@ footer {
}
/* 没必要 在app中写过了
html, body {
margin: 0;
......@@ -1817,6 +1886,12 @@ html, body {
font-size: 1rem;
}
.volumn-control-playing {
position: absolute;
bottom: 20px;
right: 200px;
}
.corner-buttons {
position: absolute;
bottom: 20px;
......@@ -1879,4 +1954,46 @@ html, body {
background: transparent;
}
/* 设置整个页面的输入范围滑条样式 */
#volumeControl {
-webkit-appearance: none; /* 去掉默认样式 */
appearance: none;
width: 120px; /* 设置宽度 */
height: 10px; /* 设置高度 */
background: #ddd; /* 设置默认背景颜色 */
border-radius: 5px; /* 设置圆角 */
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;
border-radius: 50%; /* 圆形滑块 */
background: #fff; /* 设置滑块背景颜色为白色 */
border: 2px solid green; /* 设置滑块边框颜色为绿色 */
cursor: pointer; /* 设置鼠标悬停时的指针样式 */
}
/* 鼠标悬浮时改变轨道背景颜色 */
#volumeControl:hover {
background: #ccc; /* 改变背景颜色 */
}
/* 设置滑块被点击时的样式 */
#volumeControl:active::-webkit-slider-thumb {
background: #8bc34a; /* 点击时滑块的背景颜色 */
border-color: #66bb6a; /* 点击时滑块边框颜色 */
}
</style>
<script setup>
import {defineEmits, onMounted, ref} from "vue";
import musicAlbumOpened from "../icon/musicAlbumOpened.vue";
import musicAlbumClosed from "../icon/musicAlbumClosed.vue";
import searchIcon from "../icon/searchIcon.vue";
import plusIcon from "../icon/plusIcon.vue";
import playButton from "../icon/playButton.vue";
import {getPlaylistsByUser} from "../api/playlist";
import {ElPopover} from "element-plus";
const emit = defineEmits();
const musicAlbums = ref([]);
let albumHoveredIndex = ref(null);
let hoverOnAlbum = ref(false);
let isSideBarOpen = ref(true);
let sideBarWidth = ref(250);
let user = ref("HanG");
const criticalWidth = 180;
const maximumWidth = 310;
const minWidth = 75;
/*
USER
*/
const userToken = ref(JSON.parse(sessionStorage.getItem('user-token')));
const currentUserId = ref(userToken.value.id);
function toggleSideBar() {
isSideBarOpen = !isSideBarOpen;
sideBarWidth.value = isSideBarOpen ? criticalWidth : minWidth;
}
function startResizing(event) {
event.preventDefault();
const initialWidth = sideBarWidth.value;
const initialMouseX = event.clientX;
const onMouseMove = (moveEvent) => {
sideBarWidth.value = initialWidth + (moveEvent.clientX - initialMouseX);
// 确保宽度不小于最小值
if (sideBarWidth.value <= criticalWidth) {
isSideBarOpen = false;
sideBarWidth.value = minWidth;
}
// 确保宽度不大于最大值
else if (sideBarWidth.value >= maximumWidth) {
sideBarWidth.value = maximumWidth;
} else
isSideBarOpen = true;
};
const onMouseUp = () => {
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
};
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
}
function addAlbum() {
//TODO:添加新的空歌单
}
function toggleSearchBar() {
const input = document.querySelector(".search-input");
input.classList.toggle('active'); // 切换输入框的显示状态
input.focus(); // 点击搜索图标后聚焦输入框
}
function blurSearchBar() {
const input = document.querySelector(".search-input");
if (input.classList.contains('active'))
input.classList.remove('active');
}
function searchAlbum() {
const input = document.querySelector(".search-input").value;
// TODO:提交表单
}
onMounted(() => {
getPlaylistsByUser({
user_id: currentUserId.value
}).then(res => {
musicAlbums.value = res.data.result || []
}).catch(e => {
})
})
defineProps({
callParentFunction: Function
})
</script>
<template>
<nav class="main-menu resizable-box" :style="{ width: sideBarWidth + 'px'}">
<div class="top-control">
<div class="toggle-button" @click="toggleSideBar">
<music-album-opened v-if="isSideBarOpen"/>
<music-album-closed v-if="!isSideBarOpen"/>
<p style="margin-left: 15px;font:normal small-caps bold 20px Arial, sans-serif ;" v-if="isSideBarOpen">
音乐库</p>
</div>
<el-popover v-if="isSideBarOpen" class="dropdown-options"
:width="200"
trigger="click"
:hide-after=0>
<template #reference>
<div class="add-album">
<plus-icon class="plus-icon"/>
</div>
</template>
<ul>
<li @click="addAlbum">添加歌单</li>
</ul>
</el-popover>
</div>
<div class="search-container">
<div class="search-album" v-if="isSideBarOpen" @click="toggleSearchBar">
<search-icon class="search-icon"/>
</div>
<input type="text" class="search-input" placeholder="输入搜索歌单" @keydown.enter="searchAlbum"
@blur="blurSearchBar"/>
</div>
<div class="musicAlbums"
@mouseenter="()=>{hoverOnAlbum=true}"
@mouseleave="()=>{hoverOnAlbum=false}"
:style="{ scrollbarWidth : hoverOnAlbum? 'auto':'none'}">
<div v-if="musicAlbums !== undefined" v-for="album in musicAlbums"
:key="album.id"
@mouseenter="()=>{albumHoveredIndex = album.id}"
@mouseleave="()=>{albumHoveredIndex = null}"
:style="{backgroundColor: albumHoveredIndex === album.id ? '#1F1F1F' : '#171717' }"
class="musicAlbum-item">
<img
:src="album.picPath"
alt="playlist"
class="musicAlbum-image"
:style="{opacity:albumHoveredIndex === album.id ? 0.4 :1}"
/>
<play-button @click="emit('setCurrentPlaylist', album);" v-if="albumHoveredIndex === album.id"
class="play-button"/>
<div class="musicAlbum-description">
<p style="padding-bottom: 5px;font-size: 18px">{{ album.title }}</p>
<p v-if="album.title !== '我喜欢的歌曲'" style="color: #b2b2b2;font-size: 13px">歌单 • {{ userToken.username }}</p>
<p v-else style="color: #b2b2b2;font-size: 13px">默认收藏夹</p>
</div>
</div>
</div>
<div class="resizer" @mousedown="startResizing" :style="{left:(sideBarWidth+8) +'px'}"></div>
</nav>
</template>
<style scoped>
*,
*::before,
*::after {
box-sizing: border-box;
padding: 0;
margin: 0;
}
ul {
background-color: #282828;
list-style-type: none;
margin: 0;
border-radius: 10px;
padding: 12px 8px;
}
li {
color: white;
padding: 10px 12px;
}
li:hover {
cursor: pointer;
background-color: #404040;
text-decoration: underline;
}
nav {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
}
.main-menu.resizable-box {
/* position: relative; /*使得子元素的absotute是相对于该元素 这里不设置,位置通过js控制 不然的话resizer会被gap遮蔽*/
display: flex;
flex-direction: column;
border: none;
border-radius: 12px;
padding: 12px 0 20px;
overflow: hidden;
font-family: inherit;
background-color: #171717;
}
.top-control {
margin-top: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.add-album {
color: #b2b2b2;
margin-top: 5px;
margin-right: 10px;
width: 30px;
cursor: pointer;
}
.search-container {
margin: 12px 10px;
display: flex;
align-items: center;
cursor: pointer;
}
.search-icon {
font-size: 24px;
transition: margin-right 0.3s ease;
}
.search-input {
background-color: #404040;
font-size: 15px;
color: #ffffff;
height: 35px;
border: none;
width: 0;
opacity: 0;
transition: width 0.3s ease, opacity 0.3s ease;
padding: 5px;
border-radius: 4px;
outline: none;
}
.search-input.active {
width: 70%; /* 设置输入框拉伸后的宽度 */
opacity: 1;
}
.search-album {
display: flex;
color: #b2b2b2;
width: 35px;
height: 35px;
cursor: pointer;
align-items: center;
justify-content: center;
}
.plus-icon {
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
}
.plus-icon:hover {
transform: scale(1.1);
color: #efeeee;
background-color: #404040;
}
.search-icon {
padding: 3px;
border-radius: 50%;
transition: width, color, background-color ease-in-out 0.2s;
}
.search-icon:hover {
transform: scale(1.05);
color: #efeeee;
background-color: #404040;
}
.toggle-button:hover {
color: #efeeee;
}
.toggle-button {
display: flex;
align-items: center;
width: 120px;
margin: 0 0 0 16px;
background-color: transparent;
cursor: pointer;
transition: color 0.2s ease;
color: #b2b2b2;
}
.resizer {
position: absolute;
width: 7px;
background-color: transparent;
height: 92%;
top: 85px;
cursor: ew-resize; /* 水平调整光标 */
&::before {
opacity: 0;
position: absolute;
content: '';
left: 3px;
width: 1px;
height: 100%;
background-color: #9d9d9d;
transition: opacity 0.2s ease-in-out;
}
&:hover::before {
opacity: 1;
}
}
.musicAlbums {
height: 100%;
padding: 0 10px 0 10px;
overflow: auto;
/*默认情况 滚动条消失*/
}
/*滑动条*/
.musicAlbums::-webkit-scrollbar {
width: 12px; /* 滚动条宽度 */
}
.musicAlbums::-webkit-scrollbar:window-inactive {
}
.musicAlbums::-webkit-scrollbar-track {
background: transparent; /* 滚动条背景 */
}
.musicAlbums::-webkit-scrollbar-thumb {
background: #888; /* 滚动条颜色 */
border-radius: 10px; /* 圆角 */
}
.musicAlbums::-webkit-scrollbar-thumb:hover {
background-color: #555; /* 悬停时的颜色 */
}
.musicAlbum-item {
display: flex;
position: relative;
align-items: center;
border-radius: 10px;
padding: 10px 0;
color: #f6f6f6;
}
.musicAlbum-description {
text-align: left;
display: flex;
flex-direction: column;
}
.musicAlbum-image {
width: 55px; /* 调整图片大小 */
height: 55px; /* 调整图片大小 */
margin-right: 10px;
border-radius: 10px;
}
.play-button {
position: absolute;
top: 20px;
left: 10px;
color: #f1f1f1; /* 播放键的颜色 */
cursor: pointer; /* 鼠标移动到播放键上时显示为点击手型 */
transition: transform 0.3s ease;
}
.play-button:hover {
transform: scale(1.1);
}
.musicAlbum-description {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; /* 单行显示文本 */
}
</style>
\ No newline at end of file
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