From d64e0233660942147489aad8f15569efccf83971 Mon Sep 17 00:00:00 2001 From: zhangda <221830074@smail.nju.edu.cn> Date: Sun, 22 Dec 2024 11:48:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=89=BA=E4=BA=BA=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=20=E4=BF=AE=E5=A4=8D=E4=B8=8D=E5=90=8C=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E9=94=AE=E7=8A=B6=E6=80=81=E4=B8=8D=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=20=E6=96=B0=E5=A2=9E=E6=AD=8C?= =?UTF-8?q?=E6=9B=B2=E6=B7=BB=E5=8A=A0=E8=87=B3=E6=88=91=E5=96=9C=E6=AC=A2?= =?UTF-8?q?=E7=9A=84=E6=AD=8C=E6=9B=B2=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/_prefix.js | 3 +- src/api/artist.js | 14 + src/api/playlist.js | 19 +- src/components/ArtistView.vue | 1168 +++++++++++++ src/components/MusicAlbumView.vue | 18 +- src/views/HomePage.vue | 2643 +++++++++++++++-------------- 6 files changed, 2563 insertions(+), 1302 deletions(-) create mode 100644 src/api/artist.js create mode 100644 src/components/ArtistView.vue diff --git a/src/api/_prefix.js b/src/api/_prefix.js index 815c49a..33991c7 100644 --- a/src/api/_prefix.js +++ b/src/api/_prefix.js @@ -7,4 +7,5 @@ export const COMMENT_MODULE = `${API_MODULE}/comments` export const MANAGER_MODULE = `${API_MODULE}/manager` export const SHARE_MODULE = `${API_MODULE}/share` export const SEARCH_MODULE = `${API_MODULE}/search` -export const RESOLVE_MODULE = `${API_MODULE}/resolve` \ No newline at end of file +export const RESOLVE_MODULE = `${API_MODULE}/resolve` +export const ARTIST_MODULE = `${API_MODULE}/artist` \ No newline at end of file diff --git a/src/api/artist.js b/src/api/artist.js new file mode 100644 index 0000000..6ca0319 --- /dev/null +++ b/src/api/artist.js @@ -0,0 +1,14 @@ +import {axios} from '../utils/request'; +import {ARTIST_MODULE} from './_prefix'; + +export const getArtistInfo = (artistName) => { + const encodedName = encodeURIComponent(artistName); + return axios.get(`${ARTIST_MODULE}/search/${encodedName}`) + .then(res => res); +}; + +export const getSongsByArtist = (artistName) => { + const encodedName = encodeURIComponent(artistName); + return axios.get(`${ARTIST_MODULE}/songs/${encodedName}`) + .then(res => res); +}; \ No newline at end of file diff --git a/src/api/playlist.js b/src/api/playlist.js index bf7574d..b1faeca 100644 --- a/src/api/playlist.js +++ b/src/api/playlist.js @@ -56,12 +56,19 @@ export const modifyPlaylist = (playlistModifyInfo) => { + playlist_id: number + song_id: number */ -export const addSongToPlaylist = (songInfo) => { - return axios.post(`${PLAYLIST_MODULE}/addSong`, songInfo, - { headers: { 'Content-Type': 'application/json' } }) - .then(res => { - return res; - }); +export const addSongToPlaylist = (addInfo) => { + return axios.post(`${PLAYLIST_MODULE}/addSong`, null, { + params: { + user_id: addInfo.user_id, + playlist_id: addInfo.playlist_id, + song_id: addInfo.song_id + }, + headers: { + 'Content-Type': 'application/json' + } + }).then(res => { + return res; + }); } /* diff --git a/src/components/ArtistView.vue b/src/components/ArtistView.vue new file mode 100644 index 0000000..eda9cbc --- /dev/null +++ b/src/components/ArtistView.vue @@ -0,0 +1,1168 @@ +<script setup> +import {computed, nextTick, onMounted, onUnmounted, ref, watch} from "vue"; +import playButton from "../icon/playButton.vue"; +import pauseButton from "../icon/pauseButton.vue"; +import {backgroundColor, updateBackground} from "../utils/getBackgroundColor"; +import {getSongsByArtist, getArtistInfo} from "../api/artist"; +import {getSongById} from "../api/resolve"; +import checkMark from "../icon/checkMark.vue"; +import {addSongToPlaylist, removeSongFromPlaylist} from "../api/playlist"; +import {getSongsByPlaylist} from "../api/song"; + +const emit = defineEmits(['playSong', 'pauseSong']); +const props = defineProps({ + artistName: { + type: String, + required: true + }, + isPaused: { + type: Boolean, + } +}); + +const userToken = ref(JSON.parse(sessionStorage.getItem('user-token'))); +const currentUserId = ref(userToken.value.id); + +const artist = ref(null); +const hotSongs = ref([]); +const resizeObserver = ref(null); +const gradientColor = computed(() => `linear-gradient(to bottom, ${backgroundColor.value}, #1F1F1F 50%)`); + +let musicHoveredIndex = ref(null); +let musicClickedIndex = ref(null); +let musicPlayIndex = ref(null); +let musicPauseIndex = ref(null); + +// 娣诲姞鍏虫敞鐘舵€� +const isFollowing = ref(false); + +// 鍏虫敞/鍙栨秷鍏虫敞閫昏緫 +const toggleFollow = () => { + isFollowing.value = !isFollowing.value; + // TODO: 璋冪敤鍚庣API瀹炵幇鍏虫敞/鍙栨秷鍏虫敞 + // followArtist({ + // artistId: artist.value.id, + // isFollow: isFollowing.value + // }); +}; + +// 娣诲姞鍠滄姝屾洸鐨勭姸鎬佺鐞� +const likedSongs = ref(new Set()); + +const toggleLikeSong = async (songId) => { + try { + if (!likedSongs.value.has(songId)) { + await addSongToPlaylist({ + user_id: currentUserId.value, + playlist_id: 2,//鎴戠殑鍠滄姝屽崟ID + song_id: songId + }); + + likedSongs.value.add(songId); + }else{ + await removeSongFromPlaylist({ + playlist_id: 2,//鎴戠殑鍠滄姝屽崟ID + song_id: songId + }); + + likedSongs.value.delete(songId); + } + } catch (error) { + console.error("Failed to add song to favorites:", error); + } +}; + +// 澶勭悊绐楀彛澶у皬鍙樺寲 +const handleResize = () => { + const albums = document.querySelectorAll(".music-album-info"); + const albumText = document.querySelectorAll(".album-text"); + const albumContent = document.querySelector(".album-content"); + // if (window.innerWidth > 0) + // 涓撹緫闅愯棌 + console.log(albumContent.clientWidth); + if (albumContent.clientWidth < 605) { + albums.forEach(album => { + album.style.visibility = "hidden"; + }); + albumText.forEach(album => { + album.style.visibility = "hidden"; + }); + + } else { + albums.forEach(album => { + album.style.visibility = "visible"; + }); + albumText.forEach(album => { + album.style.visibility = "visible"; + }); + } + + // 璋冩暣澶撮儴鍥剧墖鍜屾枃瀛楀ぇ灏� + const artistImage = document.querySelector(".artist-image"); + const artistName = document.querySelector(".artist-name"); + if (albumContent.clientWidth < 420) { + artistImage.style.width = "140px"; + artistImage.style.height = "140px"; + artistName.style.fontSize = "60px"; + } else { + artistImage.style.width = "220px"; + artistImage.style.height = "220px"; + artistName.style.fontSize = "80px"; + } + + const fixedTipArea = document.querySelector(".fixed-tips"); + const fixedPlayArea = document.querySelector(".fixed-play-area"); + fixedPlayArea.style.width = (albumContent.clientWidth - 20) + "px"; + fixedTipArea.style.width = (albumContent.clientWidth - 16) + "px"; +}; + +const debounce = (fn, delay) => { + let timer; + return (...args) => { + if (timer) clearTimeout(timer); + timer = setTimeout(() => fn(...args), delay); + }; +}; + +const initializeLikedSongs = async () => { + try { + const res = await getSongsByPlaylist({ + playlist_id: 2 // "鎴戠殑鍠滄"姝屽崟ID + }); + + const likedSongIds = res.data.result.map(song => song.id); + likedSongs.value = new Set(likedSongIds); + } catch (error) { + console.error("Failed to initialize liked songs:", error); + } +}; + +const getRandomListeners = () => { + return Math.floor(Math.random() * (100000 - 10000 + 1)) + 10000; +}; + +const monthlyListeners = ref(getRandomListeners()); + +onMounted(() => { + getArtistInfo(props.artistName).then(res => { + artist.value = res.data.result; + updateBackground(artist.value.avatarUrl); + + // 浣跨敤姝屾墜鐨勬瓕鏇睮D闆嗗悎鑾峰彇鍏蜂綋姝屾洸淇℃伅 + const songPromises = artist.value.songIds.map(songId => + getSongById({ + song_id: songId + }).then(res => res.data.result) + ); + + // 绛夊緟鎵€鏈夋瓕鏇蹭俊鎭幏鍙栧畬鎴� + Promise.all(songPromises).then(songs => { + hotSongs.value = songs; + }).catch(error => { + console.error("Failed to fetch songs:", error); + }); + }); + + // 鍒濆鍖栧枩娆㈢殑姝屾洸闆嗗悎 + initializeLikedSongs(); + + // 璁剧疆resize瑙傚療鍣� + resizeObserver.value = new ResizeObserver(debounce(handleResize, 50)); + nextTick(() => { + const albumContent = document.querySelector(".album-content"); + if (albumContent) { + resizeObserver.value.observe(albumContent); + } + }); +}); + +onUnmounted(() => { + if (resizeObserver.value) { + resizeObserver.value.disconnect(); + } + popovers.value = null; +}); + +const handelScroll = (event) => { + + const playArea = document.querySelector(".play-area"); + const fixedPlayArea = document.querySelector(".fixed-play-area"); + const tipArea = document.querySelector(".tips"); + const fixedTipArea = document.querySelector(".fixed-tips"); + const albumContent = document.querySelector(".album-content"); + + const offsetHeight = albumContent.offsetTop; + const stickyPlayY = playArea.offsetTop - offsetHeight; + const stickyTipY = tipArea.offsetTop - offsetHeight; + const curOffset = offsetHeight - albumContent.getBoundingClientRect().top; + + console.log(stickyPlayY, stickyTipY); + if (curOffset >= stickyPlayY) { + fixedPlayArea.style.opacity = "1"; + fixedPlayArea.style.top = offsetHeight + "px"; + + + fixedPlayArea.style.width = (albumContent.clientWidth - 20) + "px"; + } else { + fixedPlayArea.style.opacity = "0"; + } + if (curOffset + fixedPlayArea.scrollHeight >= stickyTipY) { + fixedTipArea.style.display = "flex"; + fixedTipArea.style.top = offsetHeight + fixedPlayArea.scrollHeight + 'px'; + + } else { + fixedTipArea.style.display = "none"; + } +} + +window.onscroll = () => { + const playArea = document.querySelector(".play-area"); + const fixedPlayArea = document.querySelector(".fixed-play-area"); + const tipArea = document.querySelector(".tips"); + const fixedTipArea = document.querySelector(".fixed-tips"); + const stickyPlayY = playArea.offsetTop; + const stickyTipY = tipArea.offsetTop; + + if (window.scrollY >= stickyPlayY) { + fixedPlayArea.style.opacity = "1"; + } else { + fixedPlayArea.style.opacity = "0"; + } + if (window.scrollY + fixedPlayArea.scrollHeight >= stickyTipY) { + fixedTipArea.style.display = "flex"; + fixedTipArea.style.top = fixedPlayArea.scrollHeight + 'px'; + } else { + fixedTipArea.style.display = "none"; + } +} + + +const playFromId = (musicId) => { + if (musicId === null || musicId === undefined) { + if (hotSongs.value && hotSongs.value.length > 0) { + musicPlayIndex.value = hotSongs.value[0].id; + emit('playSong', hotSongs.value[0]); + } + } else { + musicPlayIndex.value = musicId; + const songToPlay = hotSongs.value.find(song => song.id === musicId); + if (songToPlay) { + emit('playSong', songToPlay); + } + } + musicPauseIndex.value = null; +}; + +const pauseMusic = (musicId) => { + musicPauseIndex.value = musicId; + // 鍙戦€佹殏鍋滀簨浠剁粰鐖剁粍浠� + emit('pauseSong'); +}; + +const popovers = ref([]) +const getPopoverIndex = (popover) => { + if (popover) { + popovers.value.push(popover); + } +} +const closePopover = (e) => { + popovers.value.forEach((item) => { + item.hide(); + }) +} + +const star = (artistId) => { + alert("鏆傛湭寮€鏀�"); +} + +const addToFavorite = (musicId) => { +} + +watch(() => props.isPaused, (newValue) => { + if (newValue) { + musicPauseIndex.value = musicPlayIndex.value; + } else { + musicPauseIndex.value = null; + } +}); +</script> + +<template> + <div class="album-content" :style="{backgroundImage: gradientColor}" @mousewheel="handelScroll"> + <div class="header"> + <img :src="artist?.avatarUrl" alt="" class="artist-image" @load="updateBackground"/> + <div class="header-content"> + <h1 class="artist-name">{{ artist?.name }}</h1> + <div class="artist-stats"> + <p>鏈堝惉浼� {{ monthlyListeners.toLocaleString() }}</p> + </div> + </div> + </div> + + <div class="content"> + <div class="play-area"> + <div class="play-button"> + <play-button v-if="musicPlayIndex===null||musicPauseIndex!==null" + @click="playFromId(musicPauseIndex)" + style="position: absolute; top:20%;left:24%;color: #000000"/> + <pause-button v-if="musicPlayIndex!==null&&musicPauseIndex===null" + @click="pauseMusic(musicPlayIndex)" + style="position: absolute; top:24%;left:25%;color: #000000"/> + </div> + <!-- 鏇挎崲鍘熸潵鐨別l-popover涓哄叧娉ㄦ寜閽� --> + <button class="follow-button" + :class="{ 'following': isFollowing }" + @click="toggleFollow"> + {{ isFollowing ? '宸插叧娉�' : '鍏虫敞' }} + </button> + </div> + + <div class="fixed-play-area" :style="{background :`${backgroundColor}`}"> + <div class="opacity-wrapper"> + <div class="play-button"> + <play-button v-if="musicPlayIndex===null||musicPauseIndex!==null" + @click="playFromId(musicPauseIndex)" + style="position: absolute; top:20%;left:24%;color: #000000"/> + <pause-button v-if="musicPlayIndex!==null&&musicPauseIndex===null" + @click="pauseMusic(musicPlayIndex)" + style="position: absolute; top:24%;left:25%;color: #000000"/> + </div> + <p style="padding-left: 10px;font-weight: bold;font-size: 26px">{{ artist?.name }}</p> + </div> + </div> + <div class="tips"> + <p style="position:absolute; left:45px">#</p> + <p style="position:absolute; left:140px">鏍囬</p> + <p class="album-text" style="position:absolute; left:62%">涓撹緫</p> + <p style="margin-left: auto; margin-right:55px">鏃堕棿</p> + </div> + + <div class="fixed-tips"> + <p style="position:absolute; left:45px">#</p> + <p style="position:absolute; left:140px">鏍囬</p> + <p class="album-text" style="position:absolute; left:62%">涓撹緫</p> + <p style="margin-left: auto; margin-right:75px">鏃堕棿</p> + </div> + + <div class="musicList"> + <div class="music-item" + v-for="music in hotSongs" + :key="music.id" + :aria-selected="musicClickedIndex === music.id ? 'True':'False'" + @mouseenter="()=>{musicHoveredIndex = music.id;}" + @mouseleave="()=>{musicHoveredIndex = -1}" + @click="musicClickedIndex=music.id" + @dblclick="playFromId(music.id)" + :style="{backgroundColor: musicClickedIndex===music.id? '#404040': + musicHoveredIndex === music.id ? 'rgba(54,54,54,0.7)' :'rgba(0,0,0,0)', + }"> <!--@click浜嬩欢鍐欏湪script涓殑鍑芥暟閲� 鏃犳硶鍙婃椂瑙﹀彂:style涓殑鏍峰紡!!!--> + + <div + :style="{visibility: musicHoveredIndex === music.id||musicPlayIndex === music.id ? 'hidden' : 'visible' }"> + {{ hotSongs.indexOf(music) + 1 }} + </div> + <play-button @click="playFromId(music.id)" style="position: absolute;left: 14px;cursor: pointer" + v-if="(musicHoveredIndex === music.id&&musicPlayIndex!==music.id)||musicPauseIndex===music.id" + :style="{color: musicPauseIndex===music.id ? '#1ed660' : 'white'}"/> + + <pause-button @click="pauseMusic(music.id)" + style="color:#1ed660 ;position: absolute;left: 17px;cursor: pointer" + v-if="musicPlayIndex===music.id&&musicHoveredIndex === music.id&&musicPauseIndex!==music.id"/> + <img width="17" height="17" alt="" + style="position: absolute;left: 24px;" + v-if="musicPlayIndex===music.id&&musicHoveredIndex !== music.id&&musicPauseIndex!==music.id" + src="https://open.spotifycdn.com/cdn/images/equaliser-animated-green.f5eb96f2.gif"> + + <div class="music-detailed-info"> + <img class="music-image" + :src="music.picPath" + alt="姝屾洸鍥剧墖"/> + <div class="music-name-author" style="padding-left: 5px;"> + <p class="music-name" + :style="{color : musicPlayIndex ===music.id? '#1ED660':''}" + :class="[musicPlayIndex === music.id ? 'music-after-click' : '']" + >{{ music.title }}</p> + + <p class="music-author" + :style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}"> + {{ music.artist }}</p> + </div> + </div> + + <div class="music-album-info" :style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}"> + {{ music.album }} + </div> + <div class="music-right-info"> + <div class="like-button" + :class="{ 'liked': likedSongs.has(music.id) }" + @click="toggleLikeSong(music.id)" + :style="{ visibility: musicHoveredIndex === music.id || likedSongs.has(music.id) ? 'visible' : 'hidden' }"> + <div v-if="likedSongs.has(music.id)" + class="check-mark-wrapper" + data-tooltip="浠庢垜鍠滄鐨勬瓕鏇茬Щ闄�"> + <check-mark class="check-mark"/> + </div> + <span v-else class="add-icon" data-tooltip="娣诲姞鑷虫垜鍠滄鐨勬瓕鏇�">+</span> + </div> + + <div style="margin-left: auto;margin-right: 15px; color: #b2b2b2" + :style="{color:musicHoveredIndex === music.id? 'white' : '#b2b2b2'}"> + {{ music.upload_time }} + </div> + </div> + + </div> + + </div> + + </div> + + </div> +</template> + +<style scoped> +li { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + +} + +li:hover { + background-color: #363636; + border-radius: 12px; +} + + +p { + text-align: left; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin: 0; +} + +.header, .play-area, .tips, .musicList, .other-info { + z-index: 0; + padding: 20px; + width: 100%; + box-sizing: border-box; + user-select: none; +} + +.album-content { + margin: 0; + padding: 0; + color: white; + background-color: rgb(31, 31, 31); + transition: background-color ease 0.6s; + display: flex; + flex-direction: column; + width: 100%; + overflow-x: auto; /*鍗冧竾涓嶈兘鍒狅紝涓嶇劧鑳屾櫙榛戜竴鍗�*/ +} + + +.header { + display: flex; + padding: 40px; + align-items: center; + gap: 24px; + + .artist-image { + width: 220px; + height: 220px; + border-radius: 50%; + object-fit: cover; + box-shadow: 0 4px 60px rgba(0,0,0,.5); + } + + .header-content { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 20px; + + .artist-name { + font-size: 80px; + font-weight: 700; + margin: 0; + line-height: 1; + } + + .artist-stats { + font-size: 14px; + color: #b3b3b3; + margin-top: 15px; + } + } +} + +.content { + z-index: 1; + background-color: rgba(0, 0, 0, 0.20); +} + +.album-image { + border-radius: 6%; + width: 160px; + height: 160px; + margin-top: 30px; + margin-left: 15px; + margin-right: 20px; +} + +.play-area { + position: relative; +} + +.more-info { + font-size: 35px; + position: absolute; + z-index: 13; + top: 33px; + left: 160px; + transition: width 0.1s ease-in-out; +} + +.more-info:focus { + outline: none; + +} + +.more-info:hover { + cursor: pointer; + transform: scale(1.15); +} + +.fixed-play-area { + top: 0; + z-index: 11; + opacity: 0; + transition: opacity 0.2s ease-out; + border-radius: 12px 12px 0 0; + position: fixed; /**/ + display: flex; + padding: 10px 0 10px 20px; + width: inherit; + +} + +.opacity-wrapper { + display: flex; + align-items: center; + width: 100%; + height: 100%; + margin: -10px 0 -10px -20px; + padding: 10px 0 10px 20px; + background-color: rgba(0, 0, 0, 0.50); +} + +.play-button { + margin-left: 40px; + background-color: #1ed660; + border-radius: 50%; + width: 60px; + height: 60px; + position: relative; + align-items: center; + justify-content: center; + transition: all 0.1s; +} + +.play-button:hover { + cursor: pointer; + transform: scale(1.05); + background-color: #1ed683; +} + + +.tips { + z-index: 0; + display: flex; + position: relative; + padding: 5px 8px 5px 8px; +} + +.fixed-tips { + + z-index: 11; + width: 100%; + justify-content: space-between; + display: none; + padding: 10px 8px 10px 8px; + position: fixed; + background-color: #1f1f1f; + border-bottom: 1px solid #363636; +} + +.musicList, .other-info { + border-top: 1px solid #363636; + margin-top: 10px; +} + +/*姣忚闊充箰鐨勬牱寮�*/ +.music-item { + position: relative; + border-radius: 10px; + display: flex; + align-items: center; + padding: 10px 0 10px 25px; + flex-grow: 1; + min-width: 0; +} + +/*闊充箰琚偣鍑诲悗鐨勬牱寮�*/ +.music-after-click { + color: #1ed660; +} + +/*宸︿晶淇℃伅*/ +.music-detailed-info { + display: flex; + flex-direction: row; +} + +.music-image { + padding-left: 30px; + height: 50px; + width: 50px; /* Adjust as needed */ + border-radius: 10px; +} + +.music-name { + padding-bottom: 5px; + font-size: 18px +} + +.music-name:hover { + cursor: pointer; + text-decoration: underline; +} + +.music-author { + color: #b2b2b2; + font-size: 15px +} + +.music-author:hover { + cursor: pointer; + text-decoration: underline; +} + +/*涓撹緫淇℃伅*/ +.music-album-info { + position: absolute; + left: 60%; + color: #b2b2b2; + text-overflow: ellipsis; +} + +.music-album-info:hover { + cursor: pointer; + text-decoration: underline; +} + +.music-time-info { + position: absolute; + left: 85%; + color: #b2b2b2; +} + +/*鍙充晶淇℃伅*/ +.music-right-info { + margin-left: auto; + display: flex; + align-items: center; + flex-direction: row; +} + +.like-button { + width: 20px; + height: 20px; + margin-right: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + color: #b3b3b3; +} + +.like-button:hover { + transform: scale(1.1); + color: white; +} + +.like-button.liked { + color: #1ed760; +} + +.add-icon { + font-size: 24px; + font-weight: 300; + line-height: 1; +} + +.add-icon[data-tooltip]:hover::after { + content: attr(data-tooltip); + position: absolute; + top: -25px; + left: 50%; + transform: translateX(-50%); + background-color: #282828; + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + white-space: nowrap; + z-index: 1000; +} + +.check-mark { + width: 16px; + height: 16px; +} + +.check-mark[data-tooltip]:hover::after { + display: none; +} + +.music-more-info { + margin-right: 14px; + font-size: 22px; + transition: width 0.1s ease-in-out; +} + +.music-more-info:focus { + outline: none; + transform: scale(1.05); +} + +.music-more-info:hover { + cursor: pointer; +} + +.music-dropdown-options { + border-radius: 6px; +} + +ul { + background-color: #282828; + list-style-type: none; + padding: 0; + margin: 0; + border-radius: 10px; +} + +li { + color: white; + padding: 15px 12px; +} + +li:hover { + cursor: pointer; + text-decoration: underline; +} + +.other-info { + margin-top: 20px; +} + +.reco-add-button { + color: white; + margin-right: 16px; + box-sizing: border-box; + background-color: transparent; + border-radius: 9999px; + cursor: pointer; + position: relative; + text-align: center; + transition-duration: 33ms; + transition-property: background-color, border-color, color, box-shadow, filter, transform; + user-select: none; + vertical-align: middle; + transform: translate3d(0px, 0px, 0px); + padding-block: 3px; + padding-inline: 15px; + border: 1px solid #818181; + min-inline-size: 0; + min-block-size: 32px; + display: inline-flex; + align-items: center; + + &:hover { + border: 1px solid #f5f5f5; + scale: 1.1; + } +} + +/* new-elements */ +.edit-desc { + visibility: hidden; + z-index: 1000; + background-color: rgba(0, 0, 0, .7); + bottom: 0; + display: flex; + left: 0; + position: absolute; + right: 0; + top: 0; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + overflow: hidden; +} + +.main-edit-desc { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + background-color: #282828; + border-radius: 8px; + -webkit-box-shadow: 0 4px 4px rgba(0, 0, 0, .3); + box-shadow: 0 4px 4px rgba(0, 0, 0, .3); + color: #fff; + -ms-flex-direction: column; + flex-direction: column; + min-height: 384px; + width: 524px; +} + +.edit-desc-header { + display: flex; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 24px; +} + +.edit-desc-header-button { + align-self: end; + background-color: transparent; + border: 0; + border-radius: 32px; + color: hsla(0, 0%, 100%, .7); + grid-area: close-button; + height: 32px; + margin-top: -8px; + width: 32px; + -webkit-margin-end: -8px; + margin-inline-end: -8px; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.edit-desc-text { + display: grid; + grid-template: 32px 132px 32px auto / 180px 1fr; + grid-template-areas: + "album-image title" + "album-image description" + ". save-button" + "disclaimer disclaimer"; + grid-gap: 16px; + padding: 0 24px 24px; +} + +.edit-desc-img { + grid-area: album-image; + height: 180px; + margin: 0; + position: relative; + /* width: 180px; */ +} + +.edit-desc-img-1 { + border-radius: 4px; + height: 100%; + width: 100%; +} + +.edit-desc-img-1-1 { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + background-color: #282828; + color: #7f7f7f; + justify-content: center; + -webkit-box-shadow: 0 4px 60px rgba(0, 0, 0, .5); + box-shadow: 0 4px 60px rgba(0, 0, 0, .5); + + &:hover { + display: none; + } +} + +.large-svg { + fill: currentcolor; + width: 48px; + height: 48px; +} + +.edit-desc-img-2 { + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; +} + +.edit-desc-img-2-1 { + height: 100%; + width: 100%; +} + +.edit-desc-img-2-button { + background-color: #282828; + color: #fff; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + text-align: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + border: none; + border-radius: 4px; + justify-content: center; + opacity: 0; + padding: 0; +} + +.edit-desc-img-2-1-1 { + margin-top: 16px; + -webkit-transition: opacity .2s; + transition: opacity .2s; +} + +.edit-desc-img-3 { + right: 8px; + height: 32px; + position: absolute; + top: 8px; + width: 32px; +} + +@media (hover: hover) { + .edit-desc-img-3-button:not([data-context-menu-open=true]) { + opacity: 0; + pointer-events: none; + position: unset; + } +} + +.edit-desc-img-3-button { + background-color: rgba(0, 0, 0, .3); + border: none; + border-radius: 500px; + color: #b3b3b3; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding: 8px; + text-decoration: none; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + + &:hover { + opacity: 0; + pointer-events: none; + position: unset; + } +} + +.small-svg { + height: 16px; + width: 16px; +} + +.edit-desc-input-name { + grid-area: title; + position: relative; + margin-right: 8px; +} + +.edit-desc-input-name-1 { + background: hsla(0, 0%, 100%, .1); + border: 1px solid transparent; + border-radius: 4px; + color: #fff; + font-family: inherit; + font-size: 14px; + height: 40px; + padding: 0 12px; + width: 100%; + -webkit-box-shadow: inset 0 -2px #343030; + box-shadow: inset 0 -2px 0 0 #343030; +} + +.edit-desc-input-desc { + grid-area: description; + margin-top: 8px; + position: relative; +} + +.edit-desc-input-desc-1 { + background: hsla(0, 0%, 100%, .1); + border: 1px solid transparent; + border-radius: 4px; + color: #fff; + font-family: inherit; + font-size: 14px; + padding: 8px 8px 28px; + resize: none; + width: 100%; + height: 70%; +} + +.edit-desc-button { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + grid-area: save-button; + justify-self: flex-end; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.edit-desc-button-1 { + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; + background-color: transparent; + border: 0; + border-radius: 9999px; + cursor: pointer; + display: inline-block; + position: relative; + text-align: center; + text-decoration: none; + text-transform: none; + touch-action: manipulation; + transition-duration: 33ms; + transition-property: background-color, border-color, color, box-shadow, filter, transform; + user-select: none; + vertical-align: middle; + transform: translate3d(0px, 0px, 0px); + padding: 0px; + min-inline-size: 0px; + align-self: center; +} + +.edit-desc-button-1-1 { + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; + position: relative; + background-color: #ffffff; + color: #000000; + display: flex; + border-radius: 9999px; + font-size: inherit; + min-block-size: 48px; + -webkit-box-align: center; + align-items: center; + -webkit-box-pack: center; + justify-content: center; + padding-block: 8px; + padding-inline: 32px; + transition-property: background-color, transform; + transition-duration: 33ms; +} + +.encore-text { + -webkit-box-sizing: border-box; + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; + color: inherit; + margin-block: 0; + font-size: 13px; + white-space: normal; +} + +.encore-text-title-small { + font-size: 1.5rem; +} + +.final-tip { + grid-area: disclaimer; +} + +.encore-text-marginal-bold { + font-weight: 700; +} + +.follow-button { + margin-left: 32px; + padding: 8px 20px; + font-size: 16px; + font-weight: bold; + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 500px; + background-color: transparent; + color: white; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + transform: scale(1.04); + border-color: white; + } + + &.following { + background-color: rgba(255, 255, 255, 0.1); + + &:hover { + border-color: rgba(255, 255, 255, 0.3); + } + } +} + +/* 淇敼play-area鏍峰紡浠ラ€傚簲鏂扮殑甯冨眬 */ +.play-area { + display: flex; + align-items: center; + padding: 24px 32px; +} + +.check-mark-wrapper { + display: flex; + align-items: center; + justify-content: center; +} + +.check-mark-wrapper[data-tooltip]:hover::after { + content: attr(data-tooltip); + position: absolute; + top: -25px; + left: 50%; + transform: translateX(-50%); + background-color: #282828; + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + white-space: nowrap; + z-index: 1000; +} +</style> \ No newline at end of file diff --git a/src/components/MusicAlbumView.vue b/src/components/MusicAlbumView.vue index 12a91cc..b671f80 100644 --- a/src/components/MusicAlbumView.vue +++ b/src/components/MusicAlbumView.vue @@ -8,7 +8,7 @@ import {backgroundColor, updateBackground} from "../utils/getBackgroundColor"; import pauseButton from "../icon/pauseButton.vue"; import {modifyPlaylist, removePlaylist, removeSongFromPlaylist} from "../api/playlist"; -const emit = defineEmits(); +const emit = defineEmits(['pauseSong']); const props = defineProps({ albumInfo: { // 绫诲瀷 锛歩d, userid, title ,description ,picPath,createTime,updateTime,songNum type: Object, @@ -19,7 +19,8 @@ const props = defineProps({ required: true, }, playFromLeftBar: null, - currentSongId: Number + currentSongId: Number, + isPaused: Boolean, }); const edit_title = ref(""); @@ -216,6 +217,15 @@ const playFromId = (musicId) => { emit('switchSongs', props.albumInfo, musicPlayIndex.value); musicPauseIndex = null; } + +watch(() => props.isPaused, (newValue) => { + if (newValue) { + musicPauseIndex = musicPlayIndex; + } else { + musicPauseIndex = null; + } +}); + const addToFavorite = (musicId) => { } const removeMusicFromAlbum = (albumId, songId) => { @@ -226,11 +236,13 @@ const removeMusicFromAlbum = (albumId, songId) => { } const enterMusicDescription = (musicId) => { } -const enterAuthorDescription = (authorName) => { +const enterAuthorDescription = (artistName) => { + emit('switchToArtist', artistName); } const pauseMusic = (musicId) => { musicPauseIndex = musicId; + emit('pauseSong'); } const editAlbumDescription = (albumId) => { diff --git a/src/views/HomePage.vue b/src/views/HomePage.vue index 241de08..c077249 100644 --- a/src/views/HomePage.vue +++ b/src/views/HomePage.vue @@ -15,6 +15,7 @@ import SearchView from "@/components/SearchView.vue"; import MusicAlbumView from "../components/MusicAlbumView.vue"; import MainView from "../components/MainView.vue"; import EpisodeView from "@/components/EpisodeView.vue"; +import ArtistView from "../components/ArtistView.vue"; // APIs import {getSongsByEpisode, getSongsByPlaylist} from "../api/song"; @@ -35,12 +36,12 @@ const gradientColor = computed(() => `linear-gradient(to top right, ${background const isFullScreen = ref(false); function toggleFullScreen() { - isFullScreen.value = !isFullScreen.value; - if (isFullScreen.value) { - document.documentElement.requestFullscreen(); - } else { - document.exitFullscreen(); - } + isFullScreen.value = !isFullScreen.value; + if (isFullScreen.value) { + document.documentElement.requestFullscreen(); + } else { + document.exitFullscreen(); + } } @@ -53,34 +54,34 @@ const currentLineIndex = ref(0); // 褰撳墠姝岃瘝琛岀殑绱㈠紩 const isLyricsDisplaying = ref(true); function toggleLyrics() { - isLyricsDisplaying.value = !isLyricsDisplaying.value; + isLyricsDisplaying.value = !isLyricsDisplaying.value; } function formatTime(time) { - const minutes = Math.floor(time / 60); - const seconds = Math.floor(time % 60); - return `${minutes}:${seconds.toString().padStart(2, '0')}`; + const minutes = Math.floor(time / 60); + const seconds = Math.floor(time % 60); + return `${minutes}:${seconds.toString().padStart(2, '0')}`; } function updateCurrentTime(event) { - currentTime.value = event.target.currentTime; - updateCurrentLine(); + currentTime.value = event.target.currentTime; + updateCurrentLine(); } function updateCurrentLine() { - for (let i = 0; i < lyrics.value.length; i++) { - if ( - currentTime.value >= lyrics.value[i].time && - (!lyrics.value[i + 1] || currentTime.value < lyrics.value[i + 1].time) - ) { - currentLineIndex.value = i; - break; - } - } + for (let i = 0; i < lyrics.value.length; i++) { + if ( + currentTime.value >= lyrics.value[i].time && + (!lyrics.value[i + 1] || currentTime.value < lyrics.value[i + 1].time) + ) { + currentLineIndex.value = i; + break; + } + } } setInterval(() => { - // console.log(progresses.length, controlIcons.length, playModeIcons.length); + // console.log(progresses.length, controlIcons.length, playModeIcons.length); }, 1000); @@ -89,12 +90,12 @@ const album_selected = ref(false); const showRightContent = ref(true) const selectAlbum = () => { - console.log("selectAlbum"); - album_selected.value = true; + console.log("selectAlbum"); + album_selected.value = true; } const unSelectAlbum = () => { - console.log("unSelectAlbum"); - album_selected.value = false; + console.log("unSelectAlbum"); + album_selected.value = false; } @@ -123,204 +124,204 @@ let playModeIcons; const registerDOMs = () => { - /* + /* Navs & Containers - */ - const navItems = document.querySelectorAll(".nav-item"); - const containers = document.querySelectorAll(".containers"); - - navItems.forEach((navItem) => { - navItem.addEventListener("click", () => { - navItems.forEach((item) => { - item.className = "nav-item"; - }); - navItem.className = "nav-item active"; - }); - }); - containers.forEach((container) => { - let isDragging = false; - let startX; - let scrollLeft; - - container.addEventListener("mousedown", (e) => { - isDragging = true; - startX = e.pageX - container.offsetLeft; - scrollLeft = container.scrollLeft; - }); - container.addEventListener("mousemove", (e) => { - if (!isDragging) { - return; - } - e.preventDefault(); - - const x = e.pageX - container.offsetLeft; - const step = (x - startX) * 0.6; - container.scrollLeft = scrollLeft - step; - }); - container.addEventListener("mouseup", () => { - isDragging = false; - }); - container.addEventListener("mouseleave", () => { - isDragging = false; - }); - container.addEventListener('wheel', (e) => { - container.scrollLeft += e.deltaY / 2; - }); - }); - - /* + */ + const navItems = document.querySelectorAll(".nav-item"); + const containers = document.querySelectorAll(".containers"); + + navItems.forEach((navItem) => { + navItem.addEventListener("click", () => { + navItems.forEach((item) => { + item.className = "nav-item"; + }); + navItem.className = "nav-item active"; + }); + }); + containers.forEach((container) => { + let isDragging = false; + let startX; + let scrollLeft; + + container.addEventListener("mousedown", (e) => { + isDragging = true; + startX = e.pageX - container.offsetLeft; + scrollLeft = container.scrollLeft; + }); + container.addEventListener("mousemove", (e) => { + if (!isDragging) { + return; + } + e.preventDefault(); + + const x = e.pageX - container.offsetLeft; + const step = (x - startX) * 0.6; + container.scrollLeft = scrollLeft - step; + }); + container.addEventListener("mouseup", () => { + isDragging = false; + }); + container.addEventListener("mouseleave", () => { + isDragging = false; + }); + container.addEventListener('wheel', (e) => { + container.scrollLeft += e.deltaY / 2; + }); + }); + + /* Songs Related - */ - song = document.getElementById("song"); - playPauseButtons = document.querySelectorAll(".play-pause-btn"); - forwardButtons = document.querySelectorAll(".controls button.forward"); - 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]) { - controlIcons.forEach(controlIcon => { - controlIcon.src = PLAY; - }); - song.src = songs.value[currentSongIndex.value].filePath; - parseLrc(songs.value[currentSongIndex.value].lyricsPath).then(res => { - lyrics.value = res; - }); - song.load(); - song.play(); - theme.change(songs.value[currentSongIndex.value].picPath); - } - } catch (e) { - console.log("Uncaught Error in updateSongInfo!", e); - } - } - - function shareSong() { - console.log("Hello!"); - } - - function playPause() { - isPaused.value = !isPaused.value; - try { - if (song.paused) { - song.play(); - controlIcons.forEach(controlIcon => { - controlIcon.src = PLAY; - }); - } else { - song.pause(); - controlIcons.forEach(controlIcon => { - controlIcon.src = PAUSE; - }); - } - } catch (e) { - console.log("Uncaught Error in playPause!", e); - } - } - - function switchPlayMode() { - playingMode.value = (playingMode.value + 1) % 3 - switch (playingMode.value) { - case 0: - playModeIcons.forEach(playModeIcon => { - playModeIcon.src = NORMAL_MODE; - }); - break; - case 1: - playModeIcons.forEach(playModeIcon => { - playModeIcon.src = LOOP_MODE; - }); - break; - case 2: - playModeIcons.forEach(playModeIcon => { - playModeIcon.src = RANDOM_MODE; - }); - break; - default: - break; - } - } - - song.addEventListener("loadedmetadata", function () { - progresses.forEach(progress => { - duration.value = song.duration; - progress.max = song.duration; - progress.value = song.currentTime; - }); - }); - song.addEventListener("ended", function () { - switchSongs(1); - updateSongInfo(); - }); - song.addEventListener("timeupdate", function () { - if (!song.paused) { - progresses.forEach(progress => { - progress.value = song.currentTime; - }); - } - }); - - playPauseButtons.forEach(playPauseButton => { - if (!playPauseButton._hasClickListener) { - playPauseButton.addEventListener("click", playPause); - playPauseButton._hasClickListener = true; - } - }); - playModeButtons.forEach(playModeButton => { - if (!playModeButton._hasClickListener) { - playModeButton.addEventListener("click", switchPlayMode); - playModeButton._hasClickListener = true; - } - }); - shareButtons.forEach(shareButton => { - if (!shareButton._hasClickListener) { - shareButton.addEventListener("click", shareSong); - shareButton._hasClickListener = true; - } - }); - forwardButtons.forEach(forwardButton => { - if (!forwardButton._hasClickListener) { - forwardButton.addEventListener("click", function () { - switchSongs(1); - updateSongInfo(); - }); - forwardButton._hasClickListener = true; - } - }); - backwardButtons.forEach(backwardButton => { - if (!backwardButton._hasClickListener) { - backwardButton.addEventListener("click", function () { - switchSongs(-1); - updateSongInfo(); - }); - backwardButton._hasClickListener = true; - } - }); - - progresses.forEach(progress => { - progress.addEventListener("input", function () { - if (!song.paused) { - song.currentTime = progress.value; - } - }); - progress.addEventListener("change", function () { - try { - if (song.paused) { - song.play(); - } - } catch (e) { - console.log("Uncaught Error in change!", e); - } - }); - }); - - // updateSongInfo(); + */ + song = document.getElementById("song"); + playPauseButtons = document.querySelectorAll(".play-pause-btn"); + forwardButtons = document.querySelectorAll(".controls button.forward"); + 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]) { + controlIcons.forEach(controlIcon => { + controlIcon.src = PLAY; + }); + song.src = songs.value[currentSongIndex.value].filePath; + parseLrc(songs.value[currentSongIndex.value].lyricsPath).then(res => { + lyrics.value = res; + }); + song.load(); + song.play(); + theme.change(songs.value[currentSongIndex.value].picPath); + } + } catch (e) { + console.log("Uncaught Error in updateSongInfo!", e); + } + } + + function shareSong() { + console.log("Hello!"); + } + + function playPause() { + isPaused.value = !isPaused.value; + try { + if (song.paused) { + song.play(); + controlIcons.forEach(controlIcon => { + controlIcon.src = PLAY; + }); + } else { + song.pause(); + controlIcons.forEach(controlIcon => { + controlIcon.src = PAUSE; + }); + } + } catch (e) { + console.log("Uncaught Error in playPause!", e); + } + } + + function switchPlayMode() { + playingMode.value = (playingMode.value + 1) % 3 + switch (playingMode.value) { + case 0: + playModeIcons.forEach(playModeIcon => { + playModeIcon.src = NORMAL_MODE; + }); + break; + case 1: + playModeIcons.forEach(playModeIcon => { + playModeIcon.src = LOOP_MODE; + }); + break; + case 2: + playModeIcons.forEach(playModeIcon => { + playModeIcon.src = RANDOM_MODE; + }); + break; + default: + break; + } + } + + song.addEventListener("loadedmetadata", function () { + progresses.forEach(progress => { + duration.value = song.duration; + progress.max = song.duration; + progress.value = song.currentTime; + }); + }); + song.addEventListener("ended", function () { + switchSongs(1); + updateSongInfo(); + }); + song.addEventListener("timeupdate", function () { + if (!song.paused) { + progresses.forEach(progress => { + progress.value = song.currentTime; + }); + } + }); + + playPauseButtons.forEach(playPauseButton => { + if (!playPauseButton._hasClickListener) { + playPauseButton.addEventListener("click", playPause); + playPauseButton._hasClickListener = true; + } + }); + playModeButtons.forEach(playModeButton => { + if (!playModeButton._hasClickListener) { + playModeButton.addEventListener("click", switchPlayMode); + playModeButton._hasClickListener = true; + } + }); + shareButtons.forEach(shareButton => { + if (!shareButton._hasClickListener) { + shareButton.addEventListener("click", shareSong); + shareButton._hasClickListener = true; + } + }); + forwardButtons.forEach(forwardButton => { + if (!forwardButton._hasClickListener) { + forwardButton.addEventListener("click", function () { + switchSongs(1); + updateSongInfo(); + }); + forwardButton._hasClickListener = true; + } + }); + backwardButtons.forEach(backwardButton => { + if (!backwardButton._hasClickListener) { + backwardButton.addEventListener("click", function () { + switchSongs(-1); + updateSongInfo(); + }); + backwardButton._hasClickListener = true; + } + }); + + progresses.forEach(progress => { + progress.addEventListener("input", function () { + if (!song.paused) { + song.currentTime = progress.value; + } + }); + progress.addEventListener("change", function () { + try { + if (song.paused) { + song.play(); + } + } catch (e) { + console.log("Uncaught Error in change!", e); + } + }); + }); + + // updateSongInfo(); } @@ -338,7 +339,7 @@ const currentUserId = ref(userToken.value.id); const songs = ref([]); const volumn = ref(1); watch(volumn, (newValue) => { - song.volume = newValue; + song.volume = newValue; }); const displayingSongs = ref([]); const isPaused = ref(false); @@ -350,108 +351,109 @@ const currentSongId = ref(1); const currentSongIndex = ref(0); const isPlayingPage = ref(false); const togglePlayingPage = () => { - isPlayingPage.value = !isPlayingPage.value; - registerDOMs(); + isPlayingPage.value = !isPlayingPage.value; + registerDOMs(); } const switchSongs = (del) => { - console.log(playingMode.value, currentSongIndex.value, songs.value.length) - switch (playingMode.value) { - case 0: - console.log("normal mode") - currentSongIndex.value = (currentSongIndex.value + del + songs.value.length) % songs.value.length; - break; - case 1: - console.log("loop mode") - currentSongIndex.value = currentSongIndex.value; - break; - case 2: - console.log("random mode") - currentSongIndex.value = Math.floor(Math.random() * songs.value.length); - break; - default: - break; - } - currentSongId.value = songs.value[currentSongIndex.value].id; + console.log(playingMode.value, currentSongIndex.value, songs.value.length) + switch (playingMode.value) { + case 0: + console.log("normal mode") + currentSongIndex.value = (currentSongIndex.value + del + songs.value.length) % songs.value.length; + break; + case 1: + console.log("loop mode") + currentSongIndex.value = currentSongIndex.value; + break; + case 2: + console.log("random mode") + currentSongIndex.value = Math.floor(Math.random() * songs.value.length); + break; + default: + break; + } + currentSongId.value = songs.value[currentSongIndex.value].id; } 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; - }); - song.src = songs.value[index].filePath; - parseLrc(songs.value[index].lyricsPath).then(res => { - lyrics.value = res; - }); - song.load(); - song.play(); - theme.change(songs.value[index].picPath); - } + if (index === currentSongIndex.value && !isDiffPlaylist) { + return; + } + + currentSongIndex.value = index; + currentSongId.value = songs.value[index].id; + + if (song) { + controlIcons.forEach(controlIcon => { + controlIcon.src = PLAY; + }); + song.src = songs.value[index].filePath; + parseLrc(songs.value[index].lyricsPath).then(res => { + lyrics.value = res; + }); + song.load(); + song.play(); + theme.change(songs.value[index].picPath); + isPaused.value = false; + } } 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) => { - 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 playlists!"); - }); + 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) => { + 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 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!"); - }); + 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 @@ -461,28 +463,28 @@ 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); - displayingPlaylist.value = value; - getSongsByPlaylist({ - playlist_id: value.id, - }).then((res) => { - displayingSongs.value = res.data.result; - }).catch(e => { - console.log("Failed to get songs!"); - }); + setMidComponents(1); + displayingPlaylist.value = value; + getSongsByPlaylist({ + playlist_id: value.id, + }).then((res) => { + displayingSongs.value = res.data.result; + }).catch(e => { + console.log("Failed to get songs!"); + }); }; /* @@ -493,15 +495,15 @@ 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!"); - }); + 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 @@ -510,16 +512,16 @@ const songResult = ref(); const playlistResult = ref(); function receiveDataFromHeader(data) { - songResult.value = data.songResult; - playlistResult.value = data.playlistResult; - setMidComponents(3); + songResult.value = data.songResult; + playlistResult.value = data.playlistResult; + setMidComponents(3); } /* HOME */ function receiveDataFromHome() { - setMidComponents(0); + setMidComponents(0); } /* @@ -529,10 +531,16 @@ function receiveDataFromHome() { 2 - Comments 3 - Search Results 4 - Episodes + 5 - Artist View */ const midComponents = ref(0); -const setMidComponents = (val) => { - midComponents.value = val; +const currentArtist = ref(null); + +const setMidComponents = (val, artistName = null) => { + midComponents.value = val; + if (val === 5) { + currentArtist.value = artistName; + } } /* @@ -541,125 +549,176 @@ const setMidComponents = (val) => { const isSharing = ref(false); onMounted(() => { - /* + /* DOMS & EVENTS - */ - theme.change(defaultBg); - registerDOMs(); - - /* - API - */ - console.log("L: ", currentUserId.value) - getPlaylistsByUser({ - user_id: currentUserId.value, - }).then((res) => { - playlists.value = res.data.result; - console.log("playlists: ", playlists.value); - currentPlaylist.value = playlists.value[0]; - displayingPlaylist.value = playlists.value[0]; - currentPlaylistId.value = currentPlaylist.value.id; - theme.change(currentPlaylist.value.picPath); - getSongsByPlaylist({ - playlist_id: currentPlaylistId.value, - }).then((res) => { - 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; - }); - }).catch(e => { - console.log("Failed to get songs!"); - }); - }).catch(e => { - console.log("Failed to get playlists!"); - }); - + */ + theme.change(defaultBg); + registerDOMs(); + + /* + API + */ + console.log("L: ", currentUserId.value) + getPlaylistsByUser({ + user_id: currentUserId.value, + }).then((res) => { + playlists.value = res.data.result; + console.log("playlists: ", playlists.value); + currentPlaylist.value = playlists.value[0]; + displayingPlaylist.value = playlists.value[0]; + currentPlaylistId.value = currentPlaylist.value.id; + theme.change(currentPlaylist.value.picPath); + getSongsByPlaylist({ + playlist_id: currentPlaylistId.value, + }).then((res) => { + 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; + }); + }).catch(e => { + console.log("Failed to get songs!"); + }); + }).catch(e => { + console.log("Failed to get playlists!"); + }); + }) let playFromLeftBarAlbum = ref(null); + +const playArtistSong = (songToPlay) => { + // 妫€鏌ユ瓕鏇叉槸鍚﹀凡缁忓湪鎾斁鍒楄〃涓� + const existingIndex = songs.value.findIndex(song => song.id === songToPlay.id); + + if (existingIndex !== -1) { + // 濡傛灉姝屾洸宸插瓨鍦紝鐩存帴鎾斁璇ユ瓕鏇� + currentSongIndex.value = existingIndex; + currentSongId.value = songToPlay.id; + } else { + // 濡傛灉姝屾洸涓嶅瓨鍦紝娣诲姞鍒版挱鏀惧垪琛ㄥ紑澶村苟鎾斁 + songs.value = [songToPlay, ...songs.value]; + currentSongIndex.value = 0; + currentSongId.value = songToPlay.id; + } + + if (song) { + controlIcons.forEach(controlIcon => { + controlIcon.src = PLAY; + }); + song.src = songToPlay.filePath; + parseLrc(songToPlay.lyricsPath).then(res => { + lyrics.value = res; + }); + song.load(); + song.play(); + theme.change(songToPlay.picPath); + isPaused.value = false; // 璁剧疆涓烘挱鏀剧姸鎬� + } +}; + +const pauseCurrentSong = () => { + if (song) { + song.pause(); + controlIcons.forEach(controlIcon => { + controlIcon.src = PAUSE; + }); + isPaused.value = true; // 璁剧疆涓烘殏鍋滅姸鎬� + } +}; </script> <template> - <div class="body" v-show="!isPlayingPage" @click="unSelectAlbum"> - - <!-- 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=" + <div class="body" v-show="!isPlayingPage" @click="unSelectAlbum"> + + <!-- 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" @switchToArtist="(name) => setMidComponents(5, name)" + @pauseSong="pauseCurrentSong" + :playFromLeftBar="playFromLeftBarAlbum" + :is-paused="isPaused" + /> + </div> + <el-container v-if="midComponents == 2" class="playlist-container" + style="overflow: auto; height: 730px ;border-radius: 12px"> + <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 v-if="midComponents == 5" class="playlist-container" + style="overflow: scroll; border-radius: 12px"> + <ArtistView :artist-name="currentArtist" + :is-paused="isPaused" + @playSong="playArtistSong" + @pauseSong="pauseCurrentSong"/> + </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; margin-top: 16px; ">New Song</p> - </div> - </div> - </el-container> - <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"> - <img :src="song.picPath" alt="" - :class="{ 'playing': index === currentSongIndex }" - /> - </div> - <div style="display: flex; flex-direction: column; margin-left: 10px"> - <p class="playlist-container-desc" style=" + </div> + </div> + </el-container> + <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"> + <img :src="song.picPath" alt="" + :class="{ 'playing': index === currentSongIndex }" + /> + </div> + <div style="display: flex; flex-direction: column; margin-left: 10px"> + <p class="playlist-container-desc" style=" color: white; font-size: 18px; font-family: Candara, serif; @@ -668,7 +727,7 @@ let playFromLeftBarAlbum = ref(null); width: 240px; height: 24px; ">{{ songs[index].title }}</p> - <p class="playlist-container-desc" style=" + <p class="playlist-container-desc" style=" color: #949494; font-size: 12px; text-align: left; @@ -676,22 +735,22 @@ let playFromLeftBarAlbum = ref(null); width: 240px; height: 18px ">{{ songs[index].artist }}</p> - </div> - </div> - </el-container> - </div> - </div> - </div> - - - <!-- FOOTER --> - <footer> - <div class="bottom-description bottom-component" - style="display: flex; flex-direction: row; justify-content: center;"> - <div @click="togglePlayingPage"> - <img v-if="songs[currentSongIndex] !== undefined" - :src="songs[currentSongIndex].picPath" alt="" - style=" + </div> + </div> + </el-container> + </div> + </div> + </div> + + + <!-- FOOTER --> + <footer> + <div class="bottom-description bottom-component" + style="display: flex; flex-direction: row; justify-content: center;"> + <div @click="togglePlayingPage"> + <img v-if="songs[currentSongIndex] !== undefined" + :src="songs[currentSongIndex].picPath" alt="" + style=" width: 60px; margin: 0 0 0 10px; border-radius: 5%; @@ -699,180 +758,180 @@ let playFromLeftBarAlbum = ref(null); max-width: 120px; box-shadow: 0 10px 60px rgba(200, 187, 255); "/> - <audio id="song" @timeupdate="updateCurrentTime"> - <source - v-if="songs[currentSongIndex] !== undefined" - :src="songs[currentSongIndex].filePath" - type="audio/mpeg"/> - </audio> - </div> - <div v-if="songs[currentSongIndex] !== undefined" - style="display: flex; flex-direction: column; justify-content: center;"> - <p style="font-family: Consolas, serif; color: white; font-size: 16px; text-align: left; margin-left: 5px"> - {{ songs[currentSongIndex].title }}</p> - <p style="font-family: Consolas, serif; color: white; font-size: 16px; text-align: left; margin-left: 5px"> - {{ songs[currentSongIndex].artist }}</p> - </div> - </div> - - <el-card class="bottom-controller bottom-component" style=" + <audio id="song" @timeupdate="updateCurrentTime"> + <source + v-if="songs[currentSongIndex] !== undefined" + :src="songs[currentSongIndex].filePath" + type="audio/mpeg"/> + </audio> + </div> + <div v-if="songs[currentSongIndex] !== undefined" + style="display: flex; flex-direction: column; justify-content: center;"> + <p style="font-family: Consolas, serif; color: white; font-size: 16px; text-align: left; margin-left: 5px"> + {{ songs[currentSongIndex].title }}</p> + <p style="font-family: Consolas, serif; color: white; font-size: 16px; text-align: left; margin-left: 5px"> + {{ songs[currentSongIndex].artist }}</p> + </div> + </div> + + <el-card class="bottom-controller bottom-component" style=" position: absolute; left: 50%; transform: translateX(-50%); "> - <div class="controls" style="display: flex; flex-direction: row; margin: 10px 0 0 0"> - <button class="share-btn" style="margin: 0"> - <img src="../assets/icons/controller/mute.png" alt="" style="width: 60%"> - </button> - <button class="backward" style="margin: 0 10px 0 10px"> - <img src="../assets/icons/controller/last.png" alt="" style="width: 60%"> - </button> - <button class="play-pause-btn" style="margin: 0 10px 0 10px"> - <img id="controlIcon" class="idControlIcon" src="../assets/icons/controller/play.png" alt="" - style="width: 60%"> - </button> - <button class="forward" style="margin: 0 10px 0 10px"> - <img src="../assets/icons/controller/next.png" alt="" style="width: 60%"> - </button> - <button class="play-mode-btn" style="margin: 0"> - <img id="playModeIcon" class="idPlayModeIcon" src="../assets/icons/controller/normal.png" alt="" - style="width: 60%"> - </button> - </div> - <div style="display: flex; flex-direction: row; margin-top: 10px"> - <p style="margin-right: 10px; padding-bottom: 40px; color: white">{{ formatTime(currentTime) }}</p> - <input type="range" value="0" id="progress" class="idProgress" - style="margin: 0 0 10px 0; width: 500px"/> - <p style="margin-left: 10px; padding-bottom: 40px; color: white">{{ formatTime(duration) }}</p> - </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 }" - @click="showRightContent = !showRightContent"> - <img src="../assets/icons/queue.png" alt="鎾斁闃熷垪"> - </div> - </div> - </footer> - </div> - - - <!-- PLAYING PAGE --> - <div v-show="isPlayingPage" class="playing-page"> - <div v-if="isLyricsDisplaying" class="lyrics-container"> - <div class="lyrics-lines" :style="{ transform: `translateY(${-currentLineIndex * 40}px)` }"> - <div - v-for="(line, index) in lyrics" - :key="index" - :class="{ active: index === currentLineIndex }" - class="lyrics-line" - >{{ line.text }} - </div> - <h1 v-if="lyrics.length === 0" style=" + <div class="controls" style="display: flex; flex-direction: row; margin: 10px 0 0 0"> + <button class="share-btn" style="margin: 0"> + <img src="../assets/icons/controller/mute.png" alt="" style="width: 60%"> + </button> + <button class="backward" style="margin: 0 10px 0 10px"> + <img src="../assets/icons/controller/last.png" alt="" style="width: 60%"> + </button> + <button class="play-pause-btn" style="margin: 0 10px 0 10px"> + <img id="controlIcon" class="idControlIcon" src="../assets/icons/controller/play.png" alt="" + style="width: 60%"> + </button> + <button class="forward" style="margin: 0 10px 0 10px"> + <img src="../assets/icons/controller/next.png" alt="" style="width: 60%"> + </button> + <button class="play-mode-btn" style="margin: 0"> + <img id="playModeIcon" class="idPlayModeIcon" src="../assets/icons/controller/normal.png" alt="" + style="width: 60%"> + </button> + </div> + <div style="display: flex; flex-direction: row; margin-top: 10px"> + <p style="margin-right: 10px; padding-bottom: 40px; color: white">{{ formatTime(currentTime) }}</p> + <input type="range" value="0" id="progress" class="idProgress" + style="margin: 0 0 10px 0; width: 500px"/> + <p style="margin-left: 10px; padding-bottom: 40px; color: white">{{ formatTime(duration) }}</p> + </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 }" + @click="showRightContent = !showRightContent"> + <img src="../assets/icons/queue.png" alt="鎾斁闃熷垪"> + </div> + </div> + </footer> + </div> + + + <!-- PLAYING PAGE --> + <div v-show="isPlayingPage" class="playing-page"> + <div v-if="isLyricsDisplaying" class="lyrics-container"> + <div class="lyrics-lines" :style="{ transform: `translateY(${-currentLineIndex * 40}px)` }"> + <div + v-for="(line, index) in lyrics" + :key="index" + :class="{ active: index === currentLineIndex }" + class="lyrics-line" + >{{ line.text }} + </div> + <h1 v-if="lyrics.length === 0" style=" font-size: 24px; color: #9d9d9d; margin-top: 240px; font-family: Consolas, 骞煎渾, serif; ">Ouch锛佽姝屾洸鏆傛棤姝岃瘝锛�</h1> - </div> - </div> - - <div class="player"> - <div class="background"></div> - <div class="player-content"> - <div v-if="songs[currentSongIndex] !== undefined" class="album-cover-container"> - <img :src="songs[currentSongIndex].picPath" alt="Album Cover" class="album-cover" - @load="updateBackground"/> - </div> - <div class="track-info-container"> - <div v-if="songs[currentSongIndex] !== undefined" class="music-info" - style="display: flex; flex-direction: column; justify-content: center;"> - <p style=" + </div> + </div> + + <div class="player"> + <div class="background"></div> + <div class="player-content"> + <div v-if="songs[currentSongIndex] !== undefined" class="album-cover-container"> + <img :src="songs[currentSongIndex].picPath" alt="Album Cover" class="album-cover" + @load="updateBackground"/> + </div> + <div class="track-info-container"> + <div v-if="songs[currentSongIndex] !== undefined" class="music-info" + style="display: flex; flex-direction: column; justify-content: center;"> + <p style=" font-family: Consolas, serif; color: white; font-size: 32px; text-align: left; margin: 0">{{ songs[currentSongIndex].title }}</p> - <span style=" + <span style=" font-family: Consolas, serif; color: white; font-size: 16px; text-align: left; margin: 0">{{ songs[currentSongIndex].artist }}</span> - </div> - <div class="bottom-controller bottom-component" style=" + </div> + <div class="bottom-controller bottom-component" style=" position: absolute; left: 50%; bottom: 2%; transform: translateX(-50%); "> - <div class="controls" style="display: flex; flex-direction: row; margin: 10px 0 0 0"> - <button class="share-btn" style="margin: 0"> - <img src="../assets/icons/controller/share.png" alt="" style="width: 60%"> - </button> - <button class="backward" style="margin: 0 10px 0 10px"> - <img src="../assets/icons/controller/last.png" alt="" style="width: 60%"> - </button> - <button class="play-pause-btn" style="margin: 0 10px 0 10px"> - <img id="controlIcon" class="idControlIcon" src="../assets/icons/controller/play.png" - alt="" style="width: 60%"> - </button> - <button class="forward" style="margin: 0 10px 0 10px"> - <img src="../assets/icons/controller/next.png" alt="" style="width: 60%"> - </button> - <button class="play-mode-btn" style="margin: 0"> - <img id="playModeIcon" class="idPlayModeIcon" - src="../assets/icons/controller/normal.png" alt="" style="width: 60%"> - </button> - </div> - <div v-if="songs[currentSongIndex] !== undefined" style="display: flex; flex-direction: row;"> - <p style="margin-right: 10px">{{ formatTime(currentTime) }}</p> - <input type="range" value="0" id="progress" class="idProgress" - style="margin: 20px 0 10px 0; width: 700px"/> - <p style="margin-left: 10px">{{ formatTime(duration) }}</p> - </div> - </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> - <span v-else>A</span> - </button> - <button @click="toggleFullScreen" class="corner-button"> - <span v-if="isFullScreen">鈫�</span> - <span v-else>鉀�</span> - </button> - <button @click="togglePlayingPage" class="corner-button"> - <span>鈼€</span> - </button> - </div> - </div> - </div> + <div class="controls" style="display: flex; flex-direction: row; margin: 10px 0 0 0"> + <button class="share-btn" style="margin: 0"> + <img src="../assets/icons/controller/share.png" alt="" style="width: 60%"> + </button> + <button class="backward" style="margin: 0 10px 0 10px"> + <img src="../assets/icons/controller/last.png" alt="" style="width: 60%"> + </button> + <button class="play-pause-btn" style="margin: 0 10px 0 10px"> + <img id="controlIcon" class="idControlIcon" src="../assets/icons/controller/play.png" + alt="" style="width: 60%"> + </button> + <button class="forward" style="margin: 0 10px 0 10px"> + <img src="../assets/icons/controller/next.png" alt="" style="width: 60%"> + </button> + <button class="play-mode-btn" style="margin: 0"> + <img id="playModeIcon" class="idPlayModeIcon" + src="../assets/icons/controller/normal.png" alt="" style="width: 60%"> + </button> + </div> + <div v-if="songs[currentSongIndex] !== undefined" style="display: flex; flex-direction: row;"> + <p style="margin-right: 10px">{{ formatTime(currentTime) }}</p> + <input type="range" value="0" id="progress" class="idProgress" + style="margin: 20px 0 10px 0; width: 700px"/> + <p style="margin-left: 10px">{{ formatTime(duration) }}</p> + </div> + </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> + <span v-else>A</span> + </button> + <button @click="toggleFullScreen" class="corner-button"> + <span v-if="isFullScreen">鈫�</span> + <span v-else>鉀�</span> + </button> + <button @click="togglePlayingPage" class="corner-button"> + <span>鈼€</span> + </button> + </div> + </div> + </div> </template> <style scoped> @@ -880,106 +939,106 @@ let playFromLeftBarAlbum = ref(null); *, *::before, *::after { - box-sizing: border-box; - padding: 0; - margin: 0; + box-sizing: border-box; + padding: 0; + margin: 0; } .logo { - position: absolute; - top: 0px; - left: -10px; - width: 8%; - height: 8%; - z-index: 114514; + position: absolute; + top: 0px; + left: -10px; + width: 8%; + height: 8%; + z-index: 114514; } nav { - user-select: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; } nav ul, nav ul li { - outline: 0; + outline: 0; } nav ul li a { - text-decoration: none; + text-decoration: none; } img { - width: 100%; + width: 100%; } h1 { - font-size: clamp(1.2rem, 3vw, 1.5rem); + font-size: clamp(1.2rem, 3vw, 1.5rem); } .body { - font-family: "Nunito", sans-serif; - height: 100%; - /*娌$敤鐨勬牱寮�*/ - /* - align-items: center; - justify-content: space-between; - flex-direction: column; - */ - min-height: 100vh; - background-color: rgb(19, 19, 19); /* rgba(0, 0, 0, 1); */ - background-repeat: no-repeat; - background-size: cover; - - /* 鍘熷厛main涓殑鍐呭 - height: 700px; - width: 95%; - margin: 20px 0 0 0; - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border: 1px solid rgba(255, 255, 255, 0.5); - border-radius: 15px; - box-shadow: 0 0.5px 0 1px rgba(255, 255, 255, 0.23) inset, - 0 1px 0 0 rgba(255, 255, 255, 0.6) inset, 0 4px 16px rgba(0, 0, 0, 0.12); - z-index: 10; - */ - display: grid; - grid-template-areas: + font-family: "Nunito", sans-serif; + height: 100%; + /*娌$敤鐨勬牱寮�*/ + /* + align-items: center; + justify-content: space-between; + flex-direction: column; + */ + min-height: 100vh; + background-color: rgb(19, 19, 19); /* rgba(0, 0, 0, 1); */ + background-repeat: no-repeat; + background-size: cover; + + /* 鍘熷厛main涓殑鍐呭 + height: 700px; + width: 95%; + margin: 20px 0 0 0; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.5); + border-radius: 15px; + box-shadow: 0 0.5px 0 1px rgba(255, 255, 255, 0.23) inset, + 0 1px 0 0 rgba(255, 255, 255, 0.6) inset, 0 4px 16px rgba(0, 0, 0, 0.12); + z-index: 10; + */ + display: grid; + grid-template-areas: "global-nav global-nav global-nav" "left-sidebar main-view main-view" "now-playing-bar now-playing-bar now-playing-bar"; - grid-template-columns: auto 1fr; - grid-template-rows: 10% 81% 9%; - grid-auto-rows: min-content; - - column-gap: 8px; - padding: 8px; - overflow: hidden; + grid-template-columns: auto 1fr; + grid-template-rows: 10% 81% 9%; + grid-auto-rows: min-content; + + column-gap: 8px; + padding: 8px; + overflow: hidden; } /* HEADER */ .header { - grid-area: global-nav; - z-index: 1000; + grid-area: global-nav; + z-index: 1000; } /* TEMP */ left-side-bar { - grid-area: left-sideBar; - + grid-area: left-sideBar; + } .content { - grid-area: main-view; - + grid-area: main-view; + } footer { - grid-area: now-playing-bar; - + grid-area: now-playing-bar; + } /* MAIN MENU */ @@ -999,212 +1058,212 @@ main { */ footer { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - height: 75px; - width: 100%; - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + height: 75px; + width: 100%; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); } .transparent-btn { - background-color: transparent !important; - border-color: transparent !important; - color: #333; + background-color: transparent !important; + border-color: transparent !important; + color: #333; } .main-menu { - display: flex; - flex-direction: column; - justify-content: space-between; - border-radius: 15px 0 0 15px; - border-right: 1px solid rgba(255, 255, 255, 0.5); - padding: 12px 0 20px; - overflow: hidden; - font-family: inherit; + display: flex; + flex-direction: column; + justify-content: space-between; + border-radius: 15px 0 0 15px; + border-right: 1px solid rgba(255, 255, 255, 0.5); + padding: 12px 0 20px; + overflow: hidden; + font-family: inherit; } .user-info img { - padding: 12px 24px 6px; - border-radius: 50%; + padding: 12px 24px 6px; + border-radius: 50%; } .user-info p { - color: #fff; - font-size: clamp(0.8rem, 3vw, 1rem); - font-weight: 500; - text-align: center; - line-height: 1; - padding: 0 6px 32px; + color: #fff; + font-size: clamp(0.8rem, 3vw, 1rem); + font-weight: 500; + text-align: center; + line-height: 1; + padding: 0 6px 32px; } .nav-item { - display: block; + display: block; } .nav-item a { - display: flex; - align-items: center; - justify-content: center; - color: #fff; - font-size: 1rem; - padding: 12px 0; - margin: 0 8px; - border-radius: 5px; + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 1rem; + padding: 12px 0; + margin: 0 8px; + border-radius: 5px; } .nav-item.active a { - background: rgba(106, 109, 155, 0.5); - text-decoration: none; + background: rgba(106, 109, 155, 0.5); + text-decoration: none; } .nav-icon { - width: 40px; - height: 20px; - font-size: 1.1rem; + width: 40px; + height: 20px; + font-size: 1.1rem; } /* CONTENT 鍖呭惈涓棿鍜屽彸杈规爮 鏄痝rid甯冨眬*/ .content { - height: 100%; - display: grid; - grid-template-columns: 1fr auto; - grid-template-rows: 100%; - transition: all 0.3s ease; - column-gap: 8px; - + height: 100%; + display: grid; + grid-template-columns: 1fr auto; + grid-template-rows: 100%; + transition: all 0.3s ease; + column-gap: 8px; + } .content.full-width { - grid-template-columns: 100% !important; - + grid-template-columns: 100% !important; + } /* LEFT CONTENT */ .main-view { - overflow: scroll; + overflow: scroll; } .main-view > { - display: flex; - flex-direction: column; - justify-content: center; - color: #e5e5e5; - transition: all 0.3s ease; - margin: 0; - padding: 0; + display: flex; + flex-direction: column; + justify-content: center; + color: #e5e5e5; + transition: all 0.3s ease; + margin: 0; + padding: 0; } .main-view.expanded { - - margin: 0; - padding: 0; - width: 100%; + + margin: 0; + padding: 0; + width: 100%; } .swiper-slide img { - border-radius: 20px; - height: 300px; - object-fit: cover; - border: 2px solid rgba(159, 160, 168, 0.5); + border-radius: 20px; + height: 300px; + object-fit: cover; + border: 2px solid rgba(159, 160, 168, 0.5); } /* Containers of Artist and Albums */ .containers { - display: flex; - align-items: center; - padding: 0 0 12px; - overflow-x: auto; - cursor: grab; + display: flex; + align-items: center; + padding: 0 0 12px; + overflow-x: auto; + cursor: grab; } /* ALBUMS */ .albums { - animation: fadeIn 0.5s ease-in-out; + animation: fadeIn 0.5s ease-in-out; } .playlist-item img { - inset: 0; - width: 60px; - object-fit: cover; - transition: transform 0.8s; - pointer-events: none; - aspect-ratio: 1/1; - border: 2px solid rgba(169, 150, 253, 0.5); - border-radius: 10px; - box-shadow: rgba(221, 221, 221, 0.3) 0px 8px 18px -3px, - rgba(221, 221, 221, 0.2) 0px -3px 0px inset; + inset: 0; + width: 60px; + object-fit: cover; + transition: transform 0.8s; + pointer-events: none; + aspect-ratio: 1/1; + border: 2px solid rgba(169, 150, 253, 0.5); + border-radius: 10px; + box-shadow: rgba(221, 221, 221, 0.3) 0px 8px 18px -3px, + rgba(221, 221, 221, 0.2) 0px -3px 0px inset; } .playlist-item:hover img { - transform: rotate(3deg) scale(1.14514); + transform: rotate(3deg) scale(1.14514); } .album-container { - column-gap: 24px; + column-gap: 24px; } .album { - display: grid; - grid-auto-flow: dense; - grid-template-rows: 5fr 2fr; - user-select: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - height: 240px; + display: grid; + grid-auto-flow: dense; + grid-template-rows: 5fr 2fr; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + height: 240px; } .album-frame { - position: relative; - width: 144px; - aspect-ratio: 1/1; - border: 2px solid rgba(169, 150, 253, 0.5); - border-radius: 10px; - box-shadow: rgba(221, 221, 221, 0.3) 0px 8px 18px -3px, - rgba(221, 221, 221, 0.2) 0px -3px 0px inset; - overflow: hidden; + position: relative; + width: 144px; + aspect-ratio: 1/1; + border: 2px solid rgba(169, 150, 253, 0.5); + border-radius: 10px; + box-shadow: rgba(221, 221, 221, 0.3) 0px 8px 18px -3px, + rgba(221, 221, 221, 0.2) 0px -3px 0px inset; + overflow: hidden; } .album-frame img { - position: absolute; - inset: 0; - height: 100%; - object-fit: cover; - transition: transform 0.8s; - pointer-events: none; + position: absolute; + inset: 0; + height: 100%; + object-fit: cover; + transition: transform 0.8s; + pointer-events: none; } .album-frame:hover img { - transform: rotate(3deg) scale(1.2); + transform: rotate(3deg) scale(1.2); } .album h2 { - font-size: clamp(0.9rem, 4vw, 1.1rem); - font-weight: 500; - line-height: 1.3; - display: -webkit-box; - -webkit-box-orient: vertical; - overflow: hidden; - max-width: 150px; - - @supports (-webkit-line-clamp: 2) { - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - } + font-size: clamp(0.9rem, 4vw, 1.1rem); + font-weight: 500; + line-height: 1.3; + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + max-width: 150px; + + @supports (-webkit-line-clamp: 2) { + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + } } .album p { - font-size: clamp(0.9rem, 4vw, 1rem); - opacity: 0.5; + font-size: clamp(0.9rem, 4vw, 1rem); + opacity: 0.5; } /* Containers Scrollbar Style */ @@ -1221,459 +1280,459 @@ footer { */ .album-container::-webkit-scrollbar { - height: 10px; - display: none; + height: 10px; + display: none; } .album-container::-webkit-scrollbar-track { - box-shadow: inset 0 0 0.3rem rgb(79, 78, 78); - border-radius: 40px; + box-shadow: inset 0 0 0.3rem rgb(79, 78, 78); + border-radius: 40px; } .album-container::-webkit-scrollbar-thumb { - box-shadow: inset 0 0 0.5rem rgb(116, 116, 116); - background-color: rgba(25, 43, 206, 0.2); - outline: none; - border-radius: 40px; + box-shadow: inset 0 0 0.5rem rgb(116, 116, 116); + background-color: rgba(25, 43, 206, 0.2); + outline: none; + border-radius: 40px; } /* RIGHT CONTENT */ .right-content { - background-color: #171717; - display: flex; - flex-direction: column; - border-radius: 12px; - padding: 10px 20px 20px; - color: #e5e5e5; + background-color: #171717; + display: flex; + flex-direction: column; + border-radius: 12px; + padding: 10px 20px 20px; + color: #e5e5e5; } /* SONGS */ .song-img img { - aspect-ratio: 4/3; - border-radius: inherit; - object-fit: cover; - border: 2px solid rgba(159, 160, 168, 0.5); - box-shadow: rgba(221, 221, 221, 0.3) 0px 6px 18px -3px, - rgba(221, 221, 221, 0.2) 0px -3px 0px inset; + aspect-ratio: 4/3; + border-radius: inherit; + object-fit: cover; + border: 2px solid rgba(159, 160, 168, 0.5); + box-shadow: rgba(221, 221, 221, 0.3) 0px 6px 18px -3px, + rgba(221, 221, 221, 0.2) 0px -3px 0px inset; } .song-img .overlay { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - inset: 0; - width: 100%; - height: 92%; - background-color: rgba(169, 150, 253, 0.6); - border-radius: inherit; - font-size: 1.75rem; - opacity: 0; - transition: all 0.4s linear; - cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + inset: 0; + width: 100%; + height: 92%; + background-color: rgba(169, 150, 253, 0.6); + border-radius: inherit; + font-size: 1.75rem; + opacity: 0; + transition: all 0.4s linear; + cursor: pointer; } .song-img:hover .overlay { - opacity: 1; + opacity: 1; } .song h2 { - font-size: 1rem; + font-size: 1rem; } .song p, .song span { - font-size: 0.8rem; - font-weight: 300; + font-size: 0.8rem; + font-weight: 300; } .song p { - opacity: 0.8; + opacity: 0.8; } /* MUSIC PLAYER */ .music-player { - display: flex; - align-items: center; - flex-direction: column; - color: #fff; - background: rgba(188, 184, 198, 0.2); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - box-shadow: inset 2px -2px 6px rgba(214, 214, 214, 0.2), - inset -3px 3px 3px rgba(255, 255, 255, 0.3); - border-radius: 16px; - height: 200px; + display: flex; + align-items: center; + flex-direction: column; + color: #fff; + background: rgba(188, 184, 198, 0.2); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + box-shadow: inset 2px -2px 6px rgba(214, 214, 214, 0.2), + inset -3px 3px 3px rgba(255, 255, 255, 0.3); + border-radius: 16px; + height: 200px; } .album-cover { - position: relative; + position: relative; } .album-cover img { - border-radius: 50%; - border: 2px solid rgba(222, 215, 255, 0.9); - max-width: 120px; - aspect-ratio: 1/1; - object-fit: cover; - box-shadow: 0 10px 60px rgba(200, 187, 255); - transition: transform 0.5s ease-out; - pointer-events: none; - user-select: none; + border-radius: 50%; + border: 2px solid rgba(222, 215, 255, 0.9); + max-width: 120px; + aspect-ratio: 1/1; + object-fit: cover; + box-shadow: 0 10px 60px rgba(200, 187, 255); + transition: transform 0.5s ease-out; + pointer-events: none; + user-select: none; } .point { - position: absolute; - top: 50%; - left: 50%; - -ms-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); - width: 16px; - background-color: rgba(17, 6, 58, 0.7); - border: 2px solid rgba(222, 215, 255, 0.9); - aspect-ratio: 1/1; - border-radius: 50%; - z-index: 2; + position: absolute; + top: 50%; + left: 50%; + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + width: 16px; + background-color: rgba(17, 6, 58, 0.7); + border: 2px solid rgba(222, 215, 255, 0.9); + aspect-ratio: 1/1; + border-radius: 50%; + z-index: 2; } .music-player h2 { - font-size: 1.2rem; - font-weight: 500; - margin: 8px 0 0 0; + font-size: 1.2rem; + font-weight: 500; + margin: 8px 0 0 0; } .music-player p { - font-size: 1rem; - font-weight: 300; - margin-bottom: 26px; - opacity: 0.8; + font-size: 1rem; + font-weight: 300; + margin-bottom: 26px; + opacity: 0.8; } /* Music Player Controls */ #progress { - appearance: none; - -webkit-appearance: none; - width: 100%; - height: 6px; - background: rgba(200, 187, 255, 0.6); - border-radius: 4px; - margin-bottom: 16px; - cursor: pointer; + appearance: none; + -webkit-appearance: none; + width: 100%; + height: 6px; + background: rgba(200, 187, 255, 0.6); + border-radius: 4px; + margin-bottom: 16px; + cursor: pointer; } #progress::-webkit-slider-thumb { - appearance: none; - -webkit-appearance: none; - background: rgb(77, 58, 162); - width: 20px; - aspect-ratio: 1/1; - border-radius: 50%; - border: 4px solid rgb(234, 229, 255); - box-shadow: 0 6px 10px rgba(200, 187, 255, 0.4); + appearance: none; + -webkit-appearance: none; + background: rgb(77, 58, 162); + width: 20px; + aspect-ratio: 1/1; + border-radius: 50%; + border: 4px solid rgb(234, 229, 255); + box-shadow: 0 6px 10px rgba(200, 187, 255, 0.4); } .controls { - display: flex; - justify-content: center; - align-items: center; + display: flex; + justify-content: center; + align-items: center; } .controls button { - display: flex; - align-items: center; - justify-content: center; - width: 36px; - aspect-ratio: 1/1; - margin: 20px; - background: rgba(255, 255, 255, 0.6); - border-radius: 50%; - border: 0; - outline: 0; - color: #fff; - font-size: 1.1rem; - box-shadow: 0 4px 8px rgba(200, 187, 255, 0.3); - cursor: pointer; - transition: all 0.3s linear; + display: flex; + align-items: center; + justify-content: center; + width: 36px; + aspect-ratio: 1/1; + margin: 20px; + background: rgba(255, 255, 255, 0.6); + border-radius: 50%; + border: 0; + outline: 0; + color: #fff; + font-size: 1.1rem; + box-shadow: 0 4px 8px rgba(200, 187, 255, 0.3); + cursor: pointer; + transition: all 0.3s linear; } .controls button:is(:hover, :focus-visible) { - transform: scale(0.96); + transform: scale(0.96); } .controls button:nth-child(3) { - transform: scale(1.3); + transform: scale(1.3); } .controls button:nth-child(3):is(:hover, :focus-visible) { - transform: scale(1.25); + transform: scale(1.25); } .controls button:nth-child(1) { - transform: scale(0.8); + transform: scale(0.8); } .controls button:nth-child(5) { - transform: scale(0.8); + transform: scale(0.8); } /* MEDIA QUERIES */ @media (max-width: 1500px) { - 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%; - } + 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%; + } } @media (max-width: 1310px) { - main { - grid-template-columns: 8% 92%; - margin: 30px; - } + main { + grid-template-columns: 8% 92%; + margin: 30px; + } } @media (max-width: 1250px) { - .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; - } + .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; + } } @media (max-width: 1100px) { - .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; - } + .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; + } } @media (max-width: 910px) { - main { - 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%; - } - + main { + 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; - } - + .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: + 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; - } + } + + .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; + } } @media (max-width: 580px) { - .swiper-slide { - width: 290px; - } - - .swiper-slide img { - height: 180px; - } - - .artist img { - width: 80px; - } - - .album { - grid-template-rows: 3fr 2fr; - } - - .album-frame { - width: 100px; - } - + .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) { - .user-info img { - border-radius: 50%; - padding: 6px 6px 2px; - } - + .user-info img { + border-radius: 50%; + padding: 6px 6px 2px; + } + } /* 鍔ㄧ敾锛氫笓杈戝垪琛ㄧЩ鍒伴《閮� */ .move-up { - position: absolute; - top: 20px; - left: 50%; - transform: translateX(-50%); - justify-content: center; - gap: 15px; + position: absolute; + top: 20px; + left: 50%; + transform: translateX(-50%); + justify-content: center; + gap: 15px; } /* 涓撹緫绠€浠� */ .album-details { - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - animation: fadeIn 0.5s ease-in-out; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + animation: fadeIn 0.5s ease-in-out; } .album-details img { - width: 150px; - height: 150px; - border-radius: 10px; + width: 150px; + height: 150px; + border-radius: 10px; } .details-text { - text-align: center; + text-align: center; } .details-text h2 { - margin: 10px 0; - font-size: 24px; + margin: 10px 0; + font-size: 24px; } .details-text p { - font-size: 16px; - color: #bbb; + font-size: 16px; + color: #bbb; } /* 鍔ㄧ敾锛氭贰鍏ユ晥鏋� */ @keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } } /** @@ -1699,115 +1758,115 @@ footer { */ .right-controls { - display: flex; - align-items: center; - gap: 16px; - position: absolute; - right: 80px; + display: flex; + align-items: center; + gap: 16px; + position: absolute; + right: 80px; } .feature-icon { - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - border-radius: 50%; - background: rgba(255, 255, 255, 0.1); - transition: all 0.2s ease; - position: relative; - cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.1); + transition: all 0.2s ease; + position: relative; + cursor: pointer; } .feature-icon:hover { - background: rgba(255, 255, 255, 0.2); - transform: translateY(-2px); + background: rgba(255, 255, 255, 0.2); + transform: translateY(-2px); } .feature-icon img { - width: 16px; - height: 16px; - transition: all 0.2s ease; + width: 16px; + height: 16px; + transition: all 0.2s ease; } .feature-icon:hover img { - filter: brightness(1.2); + filter: brightness(1.2); } .feature-icon[data-tooltip]:hover::after { - content: attr(data-tooltip); - position: absolute; - top: -25px; - left: 50%; - transform: translateX(-50%); - background-color: #282828; - color: white; - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - white-space: nowrap; - z-index: 1000; + content: attr(data-tooltip); + position: absolute; + top: -25px; + left: 50%; + transform: translateX(-50%); + background-color: #282828; + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + white-space: nowrap; + z-index: 1000; } .feature-icon.active { - background: #1db954; + background: #1db954; } .feature-icon.active img { - filter: brightness(0) invert(1); + filter: brightness(0) invert(1); } .feature-icon.active:hover { - background: #1ed760; + background: #1ed760; } /* 閫€鍑烘悳绱㈠浘鏍� */ .exit-search { - position: absolute; - top: 120px; - right: 10px; - width: 30px; - height: 30px; - background-color: transparent; - color: #fff; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - z-index: 999; - transition: background-color 0.3s ease; - border: 2px solid #fff; + position: absolute; + top: 120px; + right: 10px; + width: 30px; + height: 30px; + background-color: transparent; + color: #fff; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + z-index: 999; + transition: background-color 0.3s ease; + border: 2px solid #fff; } .exit-search:hover { - background-color: rgba(255, 0, 0, 0.8); + background-color: rgba(255, 0, 0, 0.8); } .exit-search::before { - content: "\2716"; - font-size: 20px; - color: #fff; + content: "\2716"; + font-size: 20px; + color: #fff; } /* 淇敼鎻愮ず鏂囧瓧鏍峰紡锛屼笌feature-icon淇濇寔涓€鑷� */ .exit-search[data-tooltip]:hover::after { - content: attr(data-tooltip); - position: absolute; - top: 35px; - left: 50%; - transform: translateX(-50%); - background-color: #282828; - color: white; - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - white-space: nowrap; - z-index: 1000; + content: attr(data-tooltip); + position: absolute; + top: 35px; + left: 50%; + transform: translateX(-50%); + background-color: #282828; + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + white-space: nowrap; + z-index: 1000; } .exit-search.adjusted-position { - right: calc(23%); + right: calc(23%); } @@ -1821,178 +1880,178 @@ html, body { }*/ .player { - position: relative; - width: 100%; - height: 100vh; - color: white; - display: flex; - flex-direction: column; - justify-content: flex-end; - align-items: center; + position: relative; + width: 100%; + height: 100vh; + color: white; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: center; } .background { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-size: cover; - background-position: center; - z-index: -1; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-size: cover; + background-position: center; + z-index: -1; } .player-content { - display: flex; - justify-content: flex-start; - align-items: center; - width: 100%; - padding: 20px; + display: flex; + justify-content: flex-start; + align-items: center; + width: 100%; + padding: 20px; } .album-cover-container { - position: relative; - width: 240px; - height: 240px; - margin: 0 0 10px 10px; - z-index: 1; + position: relative; + width: 240px; + height: 240px; + margin: 0 0 10px 10px; + z-index: 1; } .album-cover { - width: 240px; - height: 240px; - border-radius: 10px; - object-fit: cover; + width: 240px; + height: 240px; + border-radius: 10px; + object-fit: cover; } .track-info-container { - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-start; - margin-left: 20px; /* 涓撹緫灏侀潰涓庢瓕鏇蹭俊鎭箣闂寸殑闂磋窛 */ - max-width: 60%; /* 闄愬埗鍐呭鐨勬渶澶у搴� */ - width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + margin-left: 20px; /* 涓撹緫灏侀潰涓庢瓕鏇蹭俊鎭箣闂寸殑闂磋窛 */ + max-width: 60%; /* 闄愬埗鍐呭鐨勬渶澶у搴� */ + width: 100%; } .seek-bar input { - width: 100%; /* 璁╄繘搴︽潯瀹藉害濉弧鍙敤绌洪棿 */ - margin-top: 10px; + width: 100%; /* 璁╄繘搴︽潯瀹藉害濉弧鍙敤绌洪棿 */ + margin-top: 10px; } .time-info { - margin-top: 10px; - font-size: 1rem; + margin-top: 10px; + font-size: 1rem; } .volumn-control-playing { - position: absolute; - bottom: 20px; - right: 200px; + position: absolute; + bottom: 20px; + right: 200px; } .corner-buttons { - position: absolute; - bottom: 20px; - right: 20px; + position: absolute; + bottom: 20px; + right: 20px; } .corner-button { - background: none; - border: none; - color: white; - font-size: 2.4rem; - cursor: pointer; - margin-right: 15px; + background: none; + border: none; + color: white; + font-size: 2.4rem; + cursor: pointer; + margin-right: 15px; } .lyrics-container { - z-index: 10; - overflow: hidden; - height: 440px; - position: absolute; - top: 40%; /* 璺濈椤堕儴50% */ - left: 50%; /* 璺濈宸﹁竟50% */ - transform: translate(-50%, -50%); /* 鍋忕Щ鑷韩瀹介珮鐨�50%鏉ュ疄鐜板畬鍏ㄥ眳涓� */ + z-index: 10; + overflow: hidden; + height: 440px; + position: absolute; + top: 40%; /* 璺濈椤堕儴50% */ + left: 50%; /* 璺濈宸﹁竟50% */ + transform: translate(-50%, -50%); /* 鍋忕Щ鑷韩瀹介珮鐨�50%鏉ュ疄鐜板畬鍏ㄥ眳涓� */ } .lyrics-lines { - transition: transform 0.3s; + transition: transform 0.3s; } .lyrics-line { - text-align: center; - font-size: 1.2rem; - padding: 10px 0; - color: #aaa; - transition: color 0.3s; + text-align: center; + font-size: 1.2rem; + padding: 10px 0; + color: #aaa; + transition: color 0.3s; } .lyrics-line.active { - color: #30e1f1; - font-weight: bold; + color: #30e1f1; + font-weight: bold; } .playlist-item img { - transition: all 0.3s ease; + transition: all 0.3s ease; } .playlist-item img.playing { - border-color: #ddc323; - box-shadow: 0 0 10px rgba(221, 195, 35, 0.5); - transform: scale(1.05); + border-color: #ddc323; + box-shadow: 0 0 10px rgba(221, 195, 35, 0.5); + transform: scale(1.05); } .playlist-item:hover img:not(.playing) { - transform: scale(1.05); - border-color: rgba(255, 255, 255, 0.8); + transform: scale(1.05); + border-color: rgba(255, 255, 255, 0.8); } .comment-container { - position: relative; - background: transparent; + position: relative; + background: transparent; } /* 璁剧疆鏁翠釜椤甸潰鐨勮緭鍏ヨ寖鍥存粦鏉℃牱寮� */ #volumeControl { - -webkit-appearance: none; /* 鍘绘帀榛樿鏍峰紡 */ - appearance: none; - width: 120px; /* 璁剧疆瀹藉害 */ - height: 10px; /* 璁剧疆楂樺害 */ - background: #ddd; /* 璁剧疆榛樿鑳屾櫙棰滆壊 */ - border-radius: 5px; /* 璁剧疆鍦嗚 */ - outline: none; /* 鍘婚櫎鐒︾偣鏃剁殑杞粨 */ - transition: background 0.3s; /* 鑳屾櫙鑹插钩婊戣繃娓� */ + -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; /* 璁剧疆杞ㄩ亾棰滆壊涓虹豢鑹� */ + 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; /* 璁剧疆榧犳爣鎮仠鏃剁殑鎸囬拡鏍峰紡 */ + -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; /* 鏀瑰彉鑳屾櫙棰滆壊 */ + background: #ccc; /* 鏀瑰彉鑳屾櫙棰滆壊 */ } /* 璁剧疆婊戝潡琚偣鍑绘椂鐨勬牱寮� */ #volumeControl:active::-webkit-slider-thumb { - background: #8bc34a; /* 鐐瑰嚮鏃舵粦鍧楃殑鑳屾櫙棰滆壊 */ - border-color: #66bb6a; /* 鐐瑰嚮鏃舵粦鍧楄竟妗嗛鑹� */ + background: #8bc34a; /* 鐐瑰嚮鏃舵粦鍧楃殑鑳屾櫙棰滆壊 */ + border-color: #66bb6a; /* 鐐瑰嚮鏃舵粦鍧楄竟妗嗛鑹� */ } </style> -- GitLab