vite-zmusic/src/views/common/SongDetail.vue
2021-10-28 22:58:30 +08:00

330 lines
7.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, onUnmounted, watch, toRaw, nextTick } from "vue";
import { NButton, NIcon } from "naive-ui";
import { useStore } from "vuex";
import svgChevrongDown from "@/assets/svgs/ChevronDown.svg";
import pubsub from "pubsub-js";
import ArtistsSpan from "@/components/ArtistsSpan.vue";
import AlbumSpan from "@/components/AlbumSpan.vue";
import _ from 'lodash';
const store = useStore();
const song = ref(null);
const lyric = ref(null);
const lyArr = ref([]);
const coverAngle = ref(0);
const currTime = ref(0);
const totalTime = ref(0);
const currentLyricLine = ref(false);
const currentLyricIdx = ref(-1);
const lyList = ref('')
//#region 唱机
let interval = [];
watch(
() => [store.state.settings.playing, store.state.showSongDetail],
([playing, showSongDetail]) => {
if (playing && showSongDetail) {
interval.push(
setInterval(() => {
coverAngle.value += 0.5;
}, 40)
);
} else {
interval.map((item) => clearInterval(item));
interval = [];
}
},
{ immediate: true }
);
//#endregion
//#region 歌词
//处理歌词
const handleLyric = (ly) => {
lyArr.value = [];
const lines = ly.split("\n");
const re = /^\[(\d+):(\d+(.\d+)?)\](.*)$/;
lines.forEach((line) => {
// console.log(line);
// let match = re.exec(line)
// console.log(match);
// console.log(Number(line.replace(re,'$1')))
if (re.test(line))
lyArr.value.push({
time:
Number(line.replace(re, "$1")) * 60 * 1000 +
Number(line.replace(re, "$2")) * 1000,
lyric: line.replace(re, "$4"),
origin: line,
});
});
// lyList.value.scrollTop = 0;
// console.log(lyArr.value);
};
watch(
()=> currentLyricIdx.value,
(val)=>{
if(val>0){
// let elContent = document.getElementsByClassName('ly-content')[0]
let el = document.getElementsByClassName('ly-key-lines')[val]
lyList.value.scrollTop = el.offsetTop - 100
}
},
{
immediate: true,
}
)
//#endregion
//#region 处理消息订阅
const token = pubsub.subscribe("zp", (msg, data) => {
switch (msg) {
case "zp.songInfo":
// console.log("SongDetail: 收到歌曲详细信息。", data);
song.value = data;
break;
case "zp.lyric":
lyric.value = data;
handleLyric(data);
break;
case "zp.progress":
totalTime.value = data.total * 1000;
currTime.value = data.progress * 1000;
currentLyricIdx.value = _.findLastIndex(lyArr.value,(item) => {
return item.time < currTime.value;
});
// console.log(currentLyricIdx.value);
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="song">
<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="song.album.picUrl"
:style="{ transform: 'rotateZ(' + coverAngle + 'deg)' }"
/>
</div>
<div class="pic"></div>
</div>
<div class="song">
<div class="name">{{ song.name }}</div>
<div class="alias">
{{ song.alias.length > 0 ? song.alias.join(" ") : "" }}
</div>
<div class="others">
<div class="album">
专辑
<AlbumSpan
:album="song.album"
:onLeave="
() => {
pubsub.publish('zp.toggleSongDetail');
}
"
/>
</div>
<div class="artists">
歌手
<ArtistsSpan
:artists="song.artists"
:onLeave="
() => {
pubsub.publish('zp.toggleSongDetail');
}
"
/>
</div>
</div>
<div class="ly" v-if="lyric" ref="lyList">
<div class="ly-content" >
<div
class="ly-line"
v-for="(line, idx) in lyArr"
key="idx"
:class="{ current: currentLyricIdx == idx, ['ly-key-lines']: true, }"
>
{{ line.lyric }}
</div>
</div>
<div class="ly-right"></div>
</div>
</div>
<div class="comments"></div>
</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;
overflow-y: auto;
.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: #ddd;
--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: 330px;
margin-left: 30px;
font-size: 14px;
color: #333;
.name {
font-size: 24px;
font-weight: 500;
margin-top: 10px;
}
.others {
// display: flex;
font-size: 12px;
color: #999;
.album {
flex: 1;
}
.artists {
flex: 1;
}
}
.ly {
position: relative;
margin-top: 15px;
height: 290px;
overflow-y: auto;
// background-image : linear-gradient(180deg,hsla(255,0%,100%,0),rgba(255, 255, 255, 0.521));
.ly-content {
font-size: 14px;
color: #999;
margin-bottom: 100px;
// display: flex;
// flex-direction: column;
// justify-items: center;
.ly-line {
min-height: 32px;
display: flex;
align-items: center;
}
.current {
font-size: 18px;
font-weight: 600;
color: #333;
}
}
}
}
}
}
</style>