330 lines
7.4 KiB
Vue
330 lines
7.4 KiB
Vue
<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>
|