This commit is contained in:
zilong
2021-10-27 23:55:43 +08:00
parent e1edf8009e
commit 2701d0cfe7
13 changed files with 396 additions and 100 deletions

View File

@@ -0,0 +1,177 @@
<script setup>
import { ref, onUnmounted, watch } from "vue";
import { NButton, NIcon } from "naive-ui";
import { useStore } from "vuex";
import svgChevrongDown from "@/assets/svgs/ChevronDown.svg";
import pubsub from "pubsub-js";
const store = useStore();
const songInfo = ref(null);
const lyric = ref(null);
const coverAngle = ref(0);
let interval;
watch(
() => store.state.settings.playing,
(val) => {
if(val){
interval = setInterval(() => {
coverAngle.value += .5
}, 100);
} else {
clearInterval(interval)
}
},
{immediate: true}
);
//#region 处理消息订阅
const token = pubsub.subscribe("zp", (msg, data) => {
switch (msg) {
case "zp.songInfo":
console.log("SongDetail: 收到歌曲详细信息。", data);
songInfo.value = data;
break;
case "zp.lyric":
lyric.value = data;
break;
}
});
//卸载组件
onUnmounted(() => {
pubsub.unsubscribe(token);
});
//#endregion
</script>
<template>
<div id="sdTitle">
<n-button
circle
size="small"
@click="pubsub.publish('zp.toggleSongDetail')"
>
<template #icon>
<n-icon>
<svgChevrongDown />
</n-icon>
</template>
</n-button>
</div>
<div id="sdContent" v-if="songInfo">
<div class="detail">
<div class="disk">
<div class="styli">
<img
src="@/assets/images/needle.png"
:class="{ playing: store.state.settings.playing }"
/>
</div>
<div class="bg">
<img class="disk" src="@/assets/images/disk.png" />
<img
class="cover"
:src="songInfo.album.picUrl"
:style="{ transform: 'rotateZ(' + coverAngle + 'deg)' }"
/>
</div>
<div class="pic"></div>
</div>
<div class="song">
歌曲
<div class="name"></div>
<div class="others"></div>
<div class="ly"></div>
</div>
</div>
<div class="comments"></div>
</div>
</template>
<script>
export default {};
</script>
<style lang="less" scoped>
#sdTitle {
position: absolute;
left: 0;
top: 0;
right: 280px;
height: 40px;
background-color: #f9f9f9;
padding-left: 50px;
display: flex;
align-items: center;
}
#sdContent {
position: absolute;
left: 0;
top: 40px;
right: 0;
bottom: 64px;
background-color: #f6f6f6;
.styli {
position: relative;
height: 60px;
img {
width: 160px;
position: absolute;
left: 135px;
transform-origin: 13px 13px;
z-index: 100;
}
.playing {
transform: rotateZ(28deg);
}
}
.detail {
// margin: 2em;
display: flex;
justify-content: center;
.pointer {
height: 50px;
}
.disk {
--back-color: #eee;
--width-num: 340;
--padding-num: 10;
--width: calc(var(--width-num) * 1px);
--height: calc(var(--width-num) * 1px);
// position: relative;
// width: 300px;
.bg {
position: relative;
width: var(--width);
height: var(--height);
background-color: var(--back-color);
border-radius: var(--width);
padding: calc(var(--padding-num) * 1px);
img.disk {
width: calc(
(var(--width-num) - var(--padding-num) * 2) * 1px
);
}
img.cover {
width: 220px;
border-radius: 200px;
position: absolute;
top: 60px;
left: 62px;
}
}
}
.song {
position: relative;
width: 400px;
}
}
}
</style>

View File

@@ -7,17 +7,19 @@ import {
toRefs,
toRaw,
} from "vue";
import {useStore} from 'vuex'
import { NAvatar } from "naive-ui";
import { useStore } from "vuex";
import { NAvatar, NIcon } from "naive-ui";
import pubsub from "pubsub-js";
import { getSongDetial } from "@/network/song";
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
import duration from 'dayjs/plugin/duration'
import { getSongDetial, getLyric } from "@/network/song";
import dayjs from "dayjs";
import "dayjs/locale/zh-cn";
import duration from "dayjs/plugin/duration";
import ArtistsSpan from "@/components/ArtistsSpan.vue";
import svgArrowMin from "@/assets/svgs/ArrowMinimize24Regular.svg";
import svgArrowMax from "@/assets/svgs/ArrowMaximize24Regular.svg";
const store = useStore()
const showInfo = ref(false);
const store = useStore();
const showInfo = ref(true);
const info = reactive({
name: "",
artists: [],
@@ -26,7 +28,7 @@ const info = reactive({
mv: 0,
});
//#region 取得歌曲信息
//#region 取得歌曲信息、歌词
const songInfo = (id, im) => {
getSongDetial(id)
.then((res) => {
@@ -36,17 +38,37 @@ const songInfo = (id, im) => {
info.album = res.data.songs[0].al;
info.duration = res.data.songs[0].dt;
info.mv = res.data.songs[0].mv;
currTime.value = '00:00'
totalTime.value = zpTime(info.duration)
if(im){
const p = {...{
currTime.value = "00:00";
totalTime.value = zpTime(info.duration);
if (im) {
const p = {
...{
id,
date: Date.now(),
},
...toRaw(info),
};
store.commit("savePlayed", p);
store.commit("addToPlayingList", p);
console.log("savePlayed");
}
pubsub.publish("zp.songInfo", {
...{
id,
date: Date.now(),
}, ...toRaw(info)}
store.commit('savePlayed', p)
store.commit('addToPlayingList', p)
console.log('savePlayed');
}
},
...toRaw(info),
});
})
.catch((err) => {});
};
const songLyric = (id) => {
getLyric(id)
.then((res) => {
if (res.data.code == 200)
pubsub.publish("zp.lyric", res.data.lrc.lyric );
else
pubsub.publish("zp.lyric", null);
})
.catch((err) => {});
};
@@ -56,22 +78,23 @@ const songInfo = (id, im) => {
let totalTime = ref("03:13");
let currTime = ref("00:03");
dayjs.extend(duration)
dayjs.extend(duration);
function zpTime(time) {
return dayjs.duration(time).format('mm:ss')
return dayjs.duration(time).format("mm:ss");
}
// totalTime.value = zpTime(12345)
const psToken = pubsub.subscribe("zp", (msg, data) => {
switch (msg) {
case "zp.getSongInfo":
songInfo(data.id, data.im);
songLyric(data.id);
break;
case "zp.hideSongInfo":
showInfo.value = false
showInfo.value = false;
break;
case "zp.progress":
totalTime.value = zpTime(data.total * 1000)
currTime.value = zpTime(data.progress * 1000)
totalTime.value = zpTime(data.total * 1000);
currTime.value = zpTime(data.progress * 1000);
break;
}
});
@@ -80,25 +103,40 @@ onUnmounted(() => {
pubsub.unsubscribe(psToken);
});
//#endregion
const toggleSongDetail = () => {
console.log(
"显示/隐藏歌曲详细界面,包括歌词、模拟唱机以及其他操作。"
);
pubsub.publish("zp.toggleSongDetail");
};
</script>
<template>
<div id="songInfo">
<NAvatar style="border: 1px #ccc solid;"
:size="40"
v-show="showInfo"
:src="info.album.picUrl"
></NAvatar>
<div class="wp-img" v-show="showInfo" @click="toggleSongDetail">
<img :src="info.album.picUrl" width="42" class="img" />
<span class="wp-icon">
<NIcon v-if="store.state.showSongDetail"
><svgArrowMin
/></NIcon>
<NIcon v-else><svgArrowMax /></NIcon>
</span>
</div>
<div class="song" v-show="showInfo">
<div class="w-song">
<div class="song-name">{{ info.name }}</div>
<div class="song-name" @click="toggleSongDetail">
{{ info.name }}
</div>
<div class="song-author">
<ArtistsSpan :artists="info.artists" />
</div>
</div>
<div class="song-time">
<span class="played-time">{{ currTime }}</span>/<span class="total-time">{{ totalTime }}</span>
<div class="song-time" @click="toggleSongDetail">
<span class="played-time">{{ currTime }}</span
>/<span class="total-time">{{ totalTime }}</span>
</div>
</div>
</div>
@@ -116,14 +154,54 @@ export default {};
align-items: center;
margin-left: 12px;
.wp-img {
--radius: 4px;
position: relative;
border: 1px #eee solid;
border-radius: var(--radius);
cursor: pointer;
&:hover {
.wp-icon {
display: flex;
}
}
.img {
border-radius: var(--radius);
}
.wp-icon {
position: absolute;
top: 0;
left: 0;
width: 42px;
height: 42px;
color: #fff;
background-color: rgba(255, 255, 255, 0.1);
border-radius: var(--radius);
-webkit-backdrop-filter: blur(2px);
backdrop-filter: blur(2px);
display: flex;
justify-content: center;
align-items: center;
font-size: 33px;
display: none;
}
}
.song {
padding-left: 6px;
display: flex;
flex-direction: column;
cursor: pointer;
.w-song {
display: flex;
align-items: center;
.song-name{
.song-name {
// flex: 1;
.text-el-line-normal();
}