feat: 播放列表重构 & fix: 修复一些样式问题

This commit is contained in:
imsyy
2023-04-20 16:48:28 +08:00
parent 5425288e16
commit 415cf3b3c9
13 changed files with 372 additions and 240 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "splayer",
"version": "1.1.2",
"version": "1.1.3",
"author": "imsyy",
"home": "https://imsyy.top",
"github": "https://github.com/imsyy/SPlayer",

View File

@@ -10,7 +10,12 @@
:native-scrollbar="false"
embedded
>
<main ref="mainContent" class="main">
<main
ref="mainContent"
class="main"
id="main"
:class="[music.showPlayList ? 'playlist' : null]"
>
<n-back-top
:bottom="music.getPlaylists[0] && music.showPlayBar ? 100 : 40"
style="transition: all 0.3s"
@@ -223,6 +228,14 @@ onMounted(() => {
.main {
max-width: 1400px;
margin: 0 auto;
div:nth-of-type(2) {
transition: all 0.3s;
}
&.playlist {
div:nth-of-type(2) {
transform: scale(0.98);
}
}
}
}

View File

@@ -14,7 +14,12 @@
<img
class="musicPackage"
v-if="commentData.user.vipRights?.redVipAnnualCount > 0"
:src="commentData.user.vipRights.musicPackage.iconUrl"
:src="
commentData.user.vipRights.musicPackage.iconUrl.replace(
/^http:/,
'https:'
)
"
alt="redVipAnnualCount"
title="网易音乐人"
/>
@@ -25,7 +30,12 @@
>
<img
v-if="commentData.user.vipRights.associator"
:src="commentData.user.vipRights.associator.iconUrl"
:src="
commentData.user.vipRights.associator.iconUrl.replace(
/^http:/,
'https:'
)
"
alt="associator"
title="黑胶会员"
/>

View File

@@ -1,178 +0,0 @@
<template>
<CollapseTransition easing="ease-in-out">
<n-card
title="播放列表"
closable
class="playlistModel"
v-show="music.showPlayList && music.getPlaylists.length"
:header-style="{
padding: '12px 16px',
fontSize: '16px',
backgroundColor: 'var(--n-border-color)',
borderRadius: '8px',
}"
:content-style="{
padding: '0',
display: 'flex',
flexDirection: 'column',
}"
@close="music.showPlayList = false"
@click.stop
>
<n-scrollbar>
<n-card
hoverable
:class="
index == music.persistData.playSongIndex ? 'songs play' : 'songs'
"
:id="'playlist' + index"
:content-style="{
padding: '8px',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
}"
v-for="(item, index) in music.getPlaylists"
:key="item"
@click="changeIndex(index)"
>
<div class="left">
<div class="num">{{ index + 1 }}</div>
</div>
<div class="right">
<div class="name text-hidden">{{ item.name }}</div>
<AllArtists class="text-hidden" :artistsData="item.artist" />
<n-icon
class="remove"
size="18"
:component="DeleteFour"
@click.stop="music.removeSong(index)"
/>
</div>
</n-card>
</n-scrollbar>
</n-card>
</CollapseTransition>
</template>
<script setup>
import { musicStore } from "@/store";
import { DeleteFour } from "@icon-park/vue-next";
import AllArtists from "./AllArtists.vue";
import CollapseTransition from "@ivanv/vue-collapse-transition/src/CollapseTransition.vue";
const music = musicStore();
onMounted(() => {
// 点击外部区域关闭播放列表
document.addEventListener("click", () => {
music.showPlayList = false;
});
});
// 改变播放索引
const changeIndex = (index) => {
music.persistData.playSongIndex = index;
music.setPlayState(true);
};
</script>
<style lang="scss" scoped>
.playlistModel {
position: absolute;
bottom: 76px;
min-width: 300px;
right: 0;
border-radius: 8px;
border-top: none;
box-shadow: var(--n-box-shadow);
:deep(.n-card__content) {
.n-scrollbar {
max-height: 70vh;
.n-scrollbar-content {
padding: 12px;
.songs {
border-radius: 8px;
cursor: pointer;
margin-bottom: 12px;
transition: all 0.3s;
&:nth-last-of-type(1) {
margin-bottom: 0;
}
&:active {
transform: scale(0.98);
}
&:hover {
.n-card__content {
.right {
.remove {
opacity: 1;
}
}
}
}
&.play {
background-color: $mainSecondaryColor;
border-color: $mainColor;
a,
span,
div,
.n-icon {
color: $mainColor;
}
.right {
.remove {
color: $mainColor;
&:hover {
background-color: var(--n-action-color);
}
}
}
}
.left {
width: 30px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.right {
flex: 1;
position: relative;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding-right: 42px;
.name {
pointer-events: none;
}
.artists {
opacity: 0.8;
font-size: 13px;
pointer-events: none;
}
.remove {
position: absolute;
border-radius: 8px;
right: 0;
opacity: 0;
transition: all 0.3s;
color: #999;
padding: 6px;
&:hover {
color: $mainColor;
background-color: var(--n-border-color);
}
}
}
}
}
}
.n-scrollbar-rail {
width: 4px;
}
}
}
</style>

View File

@@ -0,0 +1,223 @@
<template>
<n-drawer
class="playlist-drawer"
v-model:show="playListShow"
:z-index="2000"
:width="400"
:trap-focus="false"
:block-scroll="false"
placement="right"
to="#main"
@after-leave="music.showPlayList = false"
@mask-click="music.showPlayList = false"
>
<n-drawer-content title="播放列表" :native-scrollbar="false" closable>
<n-card
hoverable
:class="
index === music.persistData.playSongIndex ? 'songs play' : 'songs'
"
:id="'playlist' + index"
:content-style="{
padding: '8px',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
}"
v-for="(item, index) in music.getPlaylists"
:key="item"
@click="changeIndex(index)"
>
<div class="left">
<div v-if="index !== music.persistData.playSongIndex" class="num">
{{ index + 1 }}
</div>
<div v-else class="bar">
<div
v-for="item in 3"
:key="item"
class="line"
:style="{
animationDelay: `0.${item * item}s`,
animationPlayState: music.getPlayState ? 'running' : 'paused',
height: `${Math.floor(Math.random() * 7) + 10}px`,
}"
/>
</div>
</div>
<div class="right">
<div class="name text-hidden">{{ item.name }}</div>
<AllArtists class="text-hidden" :artistsData="item.artist" />
<n-icon
class="remove"
size="18"
:component="DeleteFour"
@click.stop="music.removeSong(index)"
/>
</div>
</n-card>
</n-drawer-content>
</n-drawer>
</template>
<script setup>
import { musicStore } from "@/store";
import { DeleteFour } from "@icon-park/vue-next";
import AllArtists from "@/components/DataList/AllArtists.vue";
const music = musicStore();
// 播放列表显隐
const playListShow = ref(false);
// 改变播放索引
const changeIndex = (index) => {
music.persistData.playSongIndex = index;
music.setPlayState(true);
};
// 监听播放列表显隐
const timeOut = ref(null);
watch(
() => music.showPlayList,
(val) => {
playListShow.value = val;
nextTick(() => {
if (val && music.getPlaylists[0]) {
const el = document.getElementById(
`playlist${music.persistData.playSongIndex}`
);
if (el) {
timeOut.value = setTimeout(() => {
el.scrollIntoView({
behavior: "smooth",
block: "center",
});
}, 300);
}
} else {
clearTimeout(timeOut.value);
}
});
}
);
onMounted(() => {
playListShow.value = music.showPlayList;
});
onBeforeUnmount(() => {
clearTimeout(timeOut.value);
});
</script>
<style lang="scss" scoped>
.playlist-drawer {
.songs {
border-radius: 8px;
cursor: pointer;
margin-bottom: 12px;
transition: all 0.3s;
&:nth-last-of-type(1) {
margin-bottom: 0;
}
&:active {
transform: scale(0.98);
}
&:hover {
.n-card__content {
.right {
.remove {
opacity: 1;
}
}
}
}
&.play {
background-color: $mainSecondaryColor;
border-color: $mainColor;
a,
span,
div,
.n-icon {
color: $mainColor;
}
:deep(span) {
color: $mainColor;
}
.right {
.remove {
color: $mainColor;
&:hover {
background-color: var(--n-action-color);
}
}
}
}
.left {
width: 30px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
.bar {
display: flex;
justify-content: space-evenly;
align-items: flex-end;
width: 20px;
height: 20px;
.line {
width: 3px;
height: 16px;
background-color: $mainColor;
border-radius: 4px;
transition: all 0.3s;
animation: lineMove 1s ease-in-out infinite;
}
@keyframes lineMove {
0% {
height: 16px;
}
50% {
height: 10px;
}
100% {
height: 16px;
}
}
}
}
.right {
flex: 1;
position: relative;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding-right: 42px;
.name {
pointer-events: none;
}
.artists {
opacity: 0.8;
font-size: 13px;
pointer-events: none;
}
.remove {
position: absolute;
border-radius: 8px;
right: 0;
opacity: 0;
transition: all 0.3s;
color: #999;
padding: 6px;
&:hover {
color: $mainColor;
background-color: var(--n-border-color);
}
}
}
}
}
</style>

View File

@@ -18,11 +18,7 @@
>
<router-link class="link" to="/discover">发现</router-link>
</n-dropdown>
<n-dropdown
trigger="hover"
:options="userOptions"
@select="menuSelect"
>
<n-dropdown trigger="hover" :options="userOptions" @select="menuSelect">
<router-link class="link" to="/user">我的</router-link>
</n-dropdown>
</div>
@@ -44,7 +40,8 @@
size="small"
:src="
user.getUserData.avatarUrl
? user.getUserData.avatarUrl
? user.getUserData.avatarUrl.replace(/^http:/, 'https:') +
'?param=60y60'
: '/images/ico/user-filling.svg'
"
:img-props="{ class: 'avatarImg' }"
@@ -109,7 +106,8 @@ const userDataRender = () => {
round: true,
style: "margin-right: 12px",
src: user.userLogin
? user.getUserData.avatarUrl
? user.getUserData.avatarUrl.replace(/^http:/, "https:") +
"?param=60y60"
: "/images/ico/user-filling.svg",
fallbackSrc: "/images/ico/user-filling.svg",
}),

View File

@@ -181,6 +181,7 @@ const lrcAllLeave = () => {
};
// 全屏切换
const timeOut = ref(null);
const screenfullIcon = shallowRef(FullscreenRound);
const screenfullChange = () => {
if (screenfull.isEnabled) {
@@ -189,7 +190,7 @@ const screenfullChange = () => {
? FullscreenRound
: FullscreenExitRound;
// 延迟一段时间执行列表滚动
setTimeout(() => {
timeOut.value = setTimeout(() => {
lrcMouseStatus.value = false;
lyricsScroll(music.getPlaySongLyricIndex);
}, 500);
@@ -248,6 +249,10 @@ onMounted(() => {
});
});
onBeforeUnmount(() => {
clearTimeout(timeOut.value);
});
// 监听页面是否打开
watch(
() => music.showBigPlayer,
@@ -256,6 +261,7 @@ watch(
console.log("开启播放器", music.getPlaySongLyricIndex);
nextTick(() => {
lyricsScroll(music.getPlaySongLyricIndex);
music.showPlayList = false;
});
}
}

View File

@@ -3,7 +3,11 @@
<div
class="countdown"
:style="{ animationPlayState: music.getPlayState ? 'running' : 'paused' }"
v-if="remainingPoint <= 2 && totalDuration > 3"
v-if="
remainingPoint <= 2 &&
totalDuration > 3 &&
music.getPlaySongLyric.lrc[0]
"
>
<span class="point" :class="remainingPoint > 2 ? 'hidden' : null">●</span>
<span class="point" :class="remainingPoint > 1 ? 'hidden' : null">●</span>
@@ -30,21 +34,25 @@ const totalDuration = ref(
watch(
() => music.getPlaySongTime.currentTime,
(val) => {
if (music.getPlaySongLyric.lrc[0]) {
const remainingTime = totalDuration.value - val - 0.5;
const progress = 1 - remainingTime / totalDuration.value;
remainingPoint.value = Number(Math.floor(3 * progress));
}
}
);
// 监听歌曲改变
watch(
() => music.getPlaySongLyric?.lrc,
(val) => {
if (music.getPlaySongLyric.lrc[0]) {
totalDuration.value = music.getPlaySongLyric.hasYrc
? music.getPlaySongLyric?.yrc[0].time
: val[0].time;
remainingPoint.value = 0;
}
}
);
</script>

View File

@@ -168,10 +168,8 @@
@click="music.setPlaySongMode()"
/>
</div>
<div class="playlist">
<PlayList />
<div :class="music.showPlayList ? 'playlist open' : 'playlist'">
<n-icon
class="next"
size="30"
:component="PlaylistPlayRound"
@click.stop="music.showPlayList = !music.showPlayList"
@@ -215,7 +213,11 @@
:src="music.getPlaySongLink"
></audio>
</n-card>
<!-- 播放列表 -->
<PlayListDrawer ref="PlayListDrawerRef" />
<!-- 添加到歌单 -->
<AddPlaylist ref="addPlayListRef" />
<!-- 播放器 -->
<BigPlayer />
</template>
@@ -249,8 +251,8 @@ import { storeToRefs } from "pinia";
import { musicStore, settingStore } from "@/store";
import { useRouter } from "vue-router";
import AddPlaylist from "@/components/DataModel/AddPlaylist.vue";
import PlayListDrawer from "@/components/DataModel/PlayListDrawer.vue";
import AllArtists from "@/components/DataList/AllArtists.vue";
import PlayList from "@/components/DataList/PlayList.vue";
import BigPlayer from "./BigPlayer.vue";
import debounce from "@/utils/debounce";
@@ -259,6 +261,7 @@ const setting = settingStore();
const music = musicStore();
const { persistData } = storeToRefs(music);
const addPlayListRef = ref(null);
const PlayListDrawerRef = ref(null);
// 重试次数
const testNumber = ref(0);
@@ -562,7 +565,7 @@ watch(
bottom: -90px;
left: 0;
transition: all 0.3s;
z-index: 2;
z-index: 2004;
&.show {
bottom: 0;
}
@@ -606,23 +609,6 @@ watch(
align-items: center;
max-width: 1400px;
margin: 0 auto;
@media (max-width: 620px) {
display: flex;
flex-direction: row;
justify-content: space-between;
.data {
.time {
display: none;
}
}
.control {
margin-left: auto;
.prev,
.next {
display: none;
}
}
}
.data {
display: flex;
flex-direction: row;
@@ -794,6 +780,12 @@ watch(
display: flex;
align-items: center;
justify-content: center;
&.open {
.n-icon {
background-color: $mainColor;
color: var(--n-color-embedded);
}
}
}
.volume {
display: flex;
@@ -810,6 +802,26 @@ watch(
}
}
}
@media (max-width: 620px) {
display: flex;
flex-direction: row;
justify-content: space-between;
.data {
.time {
display: none;
}
}
.control {
margin-left: auto;
.prev,
.next {
display: none;
}
.play-state {
margin: 0;
}
}
}
}
}
</style>

View File

@@ -328,6 +328,14 @@ watch(
}
}
);
// 监听播放列表显隐
watch(
() => music.showPlayList,
(val) => {
if (val) inputActive.value = false;
}
);
</script>
<style lang="scss" scoped>
@@ -339,11 +347,17 @@ watch(
.input {
width: 200px;
transition: all 0.3s;
@media (max-width: 450px) {
width: 40px;
}
&.focus {
width: 280px;
:deep(input) {
color: $mainColor;
}
@media (max-width: 450px) {
width: 60vw;
}
}
}
.list {
@@ -353,10 +367,34 @@ watch(
border-radius: 8px;
width: 280px;
z-index: 3;
@media (max-width: 450px) {
padding-top: 12px;
position: fixed;
width: 100%;
top: 58px;
right: 0;
left: 0;
border-radius: 0 0 8px 8px;
&::after {
position: absolute;
right: 0;
top: 0;
content: "收起";
padding: 4px 12px;
font-size: 12px;
background-color: #efefef;
border-radius: 0 0 0 14px;
}
}
:deep(.n-scrollbar) {
max-height: 80vh;
@media (max-width: 450px) {
max-height: calc(100vh - 130px);
min-height: calc(100vh - 130px);
}
.n-scrollbar-rail {
width: 0;
width: 4px;
}
.n-scrollbar-content {
padding: 12px;

View File

@@ -47,21 +47,7 @@ body,
}
.n-card-header {
.n-card-header__main {
// font-weight: bold;
font-size: 18px;
// position: relative;
// padding-left: 6px;
// display: flex;
// align-items: center;
// &::before {
// content: "";
// position: absolute;
// left: -4px;
// width: 4px;
// height: 80%;
// border-radius: 4px;
// background-color: $mainColor;
// }
}
}
.n-card__content {
@@ -73,12 +59,15 @@ body,
line-height: 32px;
}
}
.n-modal-container {
z-index: 2006 !important;
.n-modal-body-wrapper {
.n-modal-mask {
-webkit-backdrop-filter: blur(16px);
backdrop-filter: blur(16px);
}
}
}
// Nscrollbar
.n-scrollbar {
@@ -112,6 +101,18 @@ body,
border-radius: 8px !important;
}
// Drawer
.n-drawer-container {
.n-drawer {
border-radius: 8px 0 0 8px;
transition: all 0.3s;
@media (max-width: 450px) {
width: 100% !important;
border-radius: 0;
}
}
}
// 文本超出隐藏
.text-hidden {
display: -webkit-box !important;

View File

@@ -49,6 +49,7 @@ watch(
onMounted(() => {
$setSiteTitle("全局设置");
if ($mainContent) $mainContent.scrollIntoView({ behavior: "smooth" });
});
</script>

View File

@@ -6,7 +6,7 @@
round
:src="
user.getUserData.avatarUrl
? user.getUserData.avatarUrl
? user.getUserData.avatarUrl.replace(/^http:/, 'https:')
: '/images/ico/user-filling.svg'
"
fallback-src="/images/ico/user-filling.svg"