一、大致效果
二、使用步骤
1.歌词详情页代码块
<template><view class="play"><view class="play_centent" :style="{ 'background-image': 'url(' + playInfo.siPic + ')' }"><div class="cover-mask" style="opacity: 0.9"></div></view><view style="position: absolute;z-index: 999;width: 100%;height: 100%;"><view class="headerPl"><view class="" @click="goone"><u-icon name="arrow-left" color="white" size="40" style="margin-top: 4px;"></u-icon></view><view class="header_cen">{{playInfo.name}}</view></view><view class="author">{{playInfo.nickName}}</view><view class="img-container" :style="{ transform: 'translate(-50%, -50%) rotate(' + rotate + 'deg)' }"v-show="!lyricShow"><image :src="playInfo.siPic" class="authorImg" @click="getLyric"></image></view><scroll-viewscroll-yv-if="lyricShow"@click="lyricShow = false":scroll-top="scrollTop"class="lyric-container":style="{ top: CustomBar + 35 + 'px' }"><view v-if="lyricList.length > 0"><viewclass="lyric-item":class="{ active: index == currentLyricIndex }"v-for="(item, index) in lyricList":key="index"style="text-align: center">{{ item.content }}</view></view><p v-else class="noLyric">暂无歌词</p></scroll-view><view class="bottom-control"><view class="progress"><view class="audio-number">{{ playDetailInfo.current }}</view><!-- {{playDetailInfo.current_value}}{{playDetailInfo.duration_value}} --><slider class="audio-slider" activeColor="rgb(248, 78, 81)" block-size="8":value="playDetailInfo.current_value" :max="playDetailInfo.duration_value"@change="handleChange"></slider><view class="audio-number">{{ playDetailInfo.duration }}</view></view><view class="iconList flex"><view v-if="!playComm.xunhuan" @click="playComm.xunhuan = !playComm.xunhuan"><svg t="1730535056260" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1872" width="30" height="30"><path d="M342.69 297.61c-22.96 0-41.3-18.63-41.3-41.32 0-22.67 18.34-41.03 41.3-41.03h457.64c61.35 0 117.2 25.32 157.94 65.49 40.76 40.71 65.73 96.88 65.73 158.23v146.07c0 61.65-24.97 117.52-65.73 158.23-40.73 40.17-96.58 65.46-157.94 65.46H342.69c-61.68 0-117.52-25.29-158.23-65.46-36.09-36.62-60.52-85.52-64.6-139.06h-95.4C11.09 604.22 0 592.88 0 579.52c0-6.71 2.93-13.1 7.57-17.16l67.21-67.82 67.8-67.48c9.29-9.59 24.73-9.59 34.32 0h0.56l67.5 67.48 67.21 67.82c9.58 9.29 9.58 25 0 34.91-5.24 4.65-12.22 7.57-19.23 6.95h-91.02c4.05 31.15 18.9 59.66 40.11 80.9h0.3c25.88 25.59 61.41 41.56 100.37 41.56h457.64c38.66 0 74.16-15.98 99.75-41.56C925.97 659.53 942 624.01 942 585.05V438.98c0-38.96-16.03-74.46-41.91-100.05-25.59-25.91-61.09-41.32-99.75-41.32H342.69zM187.63 555.35h47.74l-25.32-25.88v-0.27l-50.32-50.05-50.34 50.05v0.27L83.8 555.35h103.83z" fill="#ffffff" p-id="1873"></path></svg></view><text v-if="playComm.xunhuan" class="iconfont" :class="'icon-unlike'" @click="playComm.xunhuan = !playComm.xunhuan"></text><text class="iconfont icon-play-left" style="margin-left:35px" @click="handleChangePlay(-1)"></text><text class="iconfont" :class="!playComm.paused ? 'icon-play' : 'icon-pause'"style="font-size: 90rpx; margin: 0 35px" @click="playMusic"></text><text class="iconfont icon-play-right" @click="handleChangePlay(1)"></text><text class="iconfont icon-liebiao" style="margin-left: 65rpx; font-size: 56rpx"@click.stop="modelShow = true"></text></view></view></view><playListUtl v-if="modelShow" :modelShow="modelShow" @close="close"@nextSong="(item,index)=>nextSong(item,index)" @del="(item,index)=>del(item,index)"></playListUtl></view>
</template><script setup>import {reactive,ref,} from 'vue'import {useAppStore} from '@/stores/plear'import {onLoad,onPageScroll,onShow} from "@dcloudio/uni-app";import playListUtl from "@/components/playList.vue";import {useRouter,useRoute} from 'uniapp-router-next'const route = useRoute()const router = useRouter()const appStore = useAppStore()const innerAudioContext = appStore.$state.contextconst playInfo = appStore.$state.soleconst playComm = appStore.$stateconst scrollTop = ref(0)const lyricList = ref([])const CustomBar = ref(100)const lyricShow = ref(false)const currentLyricIndex = ref(false)const modelShow = ref(false)const playDetailInfo = ref({current: "00.00",current_value: "0",duration_value: "0",duration: "00.00"})onShow(()=>{initLyric()})// 追踪歌词const initLyric = ()=> {if (lyricList.length == 0) return;let timeStamp = playDetailInfo.value.current;currentLyricIndex.value = lyricList.value.findIndex((item, index) => {return item.time < timeStamp && lyricList.value[index + 1]? lyricList.value[index + 1].time > timeStamp: true;});scrollTop.value = currentLyricIndex.value * 36;}// 歌词const getLyric = (bool)=> {console.log(1245)lyricShow.value = true;let data = [];// const id = this.playInfo.id;// const res = await this.$api.getLyric({ id });data = (playInfo.lyrics || "").split("\n");let timeReg = /^\[.*\]/;let json = [];data.forEach((item) => {if (item.match(timeReg)) {let time = item.match(timeReg)[0].substr(1, 8);let minute = time.substr(0, 2);let second = time.substr(3, 2);let ms = time.substr(6, 2);json.push({time,ms:parseInt(minute) * 60 * 1000 +parseInt(second) * 1000 +parseInt(ms) * 10,content: item.substr(11),});}});lyricList.value = json;console.log(json)}// f返回const goone = ()=>{console.log(123)// router.go(-1)uni.navigateBack();}const handleChange = (val) => {console.log(val)innerAudioContext.seek(val.detail.value)}// 上一首下一首const handleChangePlay = (val) => {// if (val == 1) {playComm.tabDate.forEach((el,index) => {console.log(el.sid , playInfo.sid)if (el.sid == playInfo.sid) {if (playComm.tabDate[ val == 1 ? index + 1 : index -1]) {// innerAudioContext.src = el.souce;nextSong(playComm.tabDate[val ==1 ? index + 1 : index -1],index)throw Error();}else{nextSong(playComm.tabDate[val ==1 ? 0 : playComm.tabDate.length - 1],index)throw Error();}}})// }}// 播放暂停const playMusic = () => {console.log(123)innerAudioContext.src = playInfo.souce;// innerAudioContext.seek(this.currenttime)innerAudioContext.volume = 0.5playComm.paused = !playComm.pausedif (!playComm.paused) {// seekinnerAudioContext.seek(playComm.Time)innerAudioContext.play()} else {innerAudioContext.pause()}}innerAudioContext.onTimeUpdate(() => {// 获取当前播放的总时长,单位:秒const currentTime = innerAudioContext.currentTime;// console.log(Time.value > 0)if(currentTime > 0){playComm.Time = currentTimeplayDetailInfo.value.current = convertSecondsToMinutesAndSeconds(playComm.Time)playDetailInfo.value.current_value = playComm.Time}initLyric()// console.log('当前播放时间:', currentTime);});// 防止为播放进来onLoad(() => {if (innerAudioContext.src == "") {innerAudioContext.src = playInfo.souce;}setTimeout(() => {if (innerAudioContext.duration > 0) {playDetailInfo.value.duration = convertSecondsToMinutesAndSeconds(innerAudioContext.duration)playDetailInfo.value.duration_value = innerAudioContext.duration}if (playComm.Time > 0) {playDetailInfo.value.current = convertSecondsToMinutesAndSeconds(playComm.Time)playDetailInfo.value.current_value = playComm.Time}}, 500)// playDetailInfo.duration = convertSecondsToMinutesAndSeconds(convertSecondsToMinutesAndSeconds)})// 转换秒数const convertSecondsToMinutesAndSeconds = (seconds) => {// 取整秒数,因为分钟和秒通常不包含小数 const intSeconds = Math.floor(seconds);// 计算分钟数 const minutes = Math.floor(intSeconds / 60);// 计算剩余的秒数 const secs = intSeconds % 60;// 返回格式化后的时间字符串,确保分钟和秒都是两位数 return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;}innerAudioContext.onCanplay((el) => {// 官方bug 解决方法console.log(el)});// 播放完成innerAudioContext.onEnded(() => {console.log(123456)// innerAudioContext.loop = trueconsole.log(playComm.xunhuan)if (!playComm.xunhuan) {handleChangePlay(1)}else{const X = getRandomItem(playComm.tabDate)console.log(X)nextSong(X)}})// 随机const getRandomItem = (array)=> { // 生成一个从 0 到 array.length - 1 的随机索引 const randomIndex = Math.floor(Math.random() * array.length); // 返回数组中该索引对应的元素 return array[randomIndex]; } // 列表const del = (item, index) => {playComm.tabDate = playComm.tabDate.filter(el => el.sid != item.sid)if (item.sid == playInfo.sid) {if (playComm.tabDate.length > 0) {nextSong(playComm.tabDate[0], index)} else {playComm.Time = 0playComm.paused = trueinnerAudioContext.stop()}}}const nextSong = (item, index) => {console.log(123)playComm.Time = 0playComm.paused = trueplayInfo.souce = item.souceplayInfo.lyrics = item.lyricsplayInfo.sid = item.sidplayInfo.nickName = item.nickNameplayInfo.name = item.nameplayInfo.siPic = item.siPicsetTimeout(()=>{playDetailInfo.value.duration = convertSecondsToMinutesAndSeconds(innerAudioContext.duration)playDetailInfo.value.duration_value = innerAudioContext.duration},500)playMusic()}const close = ()=>{console.log(12)modelShow.value = false}
</script><style lang="scss">.bottom-control {position: absolute;bottom: 10%;left: 10px;right: 10px;.progress {width: 100%;display: flex;align-items: center;.audio-number {width: 120upx;font-size: 24upx;line-height: 1;color: #fff;text-align: center;}.audio-slider {flex: 1;margin: 0;}}.iconList {justify-content: center;align-items: center;margin-top: 26rpx;.iconfont {color: #fff;font-size: 48rpx;}}}.lyric-container {position: absolute;bottom: calc(10% + 100px);.lyric-item {color: #e1d7f0;height: 40px;line-height: 40px;&.active {color: rgb(248, 78, 81);}}.noLyric {position: absolute;top: 50%;left: 50%;color: #fff;transform: translate(-50%, -50%);}}.img-container {position: absolute;top: 35%;left: 50%;transform: translate(-50%, -50%);width: 450rpx;height: 450rpx;background: url(../../static/images/musicImg.png) no-repeat;background-size: 100% 100%;border-radius: 50%;border: 2px solid rgba(255, 255, 255, 0.3);transition: all 1s linear;.authorImg {border-radius: 50%;width: 315rpx;height: 315rpx;position: absolute;left: 50%;top: 50%;transform: translate(-50%, -50%);}}.author {position: absolute;left: 50%;transform: translateX(-50%);color: rgba(255, 255, 255, 0.9);font-size: 28rpx;}.header_cen {font-size: 20px;color: white;position: absolute;// width: 95%;left: 50%;transform: translateX(-50%);top: 19rpx;text-align: center;}.headerPl {// display: flex;padding: 10px;width: 100%;}.play {height: 100vh;width: 100%;border: 1px solid red;}.play_centent {height: 100vh;width: 100%;overflow: hidden;position: absolute;left: 0;top: 0;right: 0;bottom: 0;z-index: 0;background-size: cover;background-position: center;// filter: blur(8px);&:after {content: "";position: absolute;width: 130%;height: 130%;left: 0;top: 0;z-index: 1;filter: blur(15px);transform: translate(-3rem, -3rem);background: inherit;background-size: 100% 100%;}}.cover-mask {position: absolute;top: 0;bottom: 0;left: 0;z-index: 3;width: 100%;height: 100%;background-color: rgba(0, 0, 0, 0.8);}</style>
2.@/stores/plear文件
组件使用的是pinia不是vuex
import { defineStore } from 'pinia'interface AppSate {// config: Record<string, any>
}
export const useAppStore = defineStore({id: 'plear',state: (): AppSate => ({tabDate: [{siPic: "http://p1.music.126.net/9bVOooAY6U6EJLzpv1Fikw==/109951169682871673.jpg?param=300y300",sid: "197444381412",name: "我记得456",nickName: "赵雷",lyrics:"",souce: "http://m801.music.126.net/20241102095804/1d592017727f26c48e713c1308fa99a8/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/17718439149/44a7/5592/7271/b58b2979b640d886d829e1c6647f8d8a.mp3?vuutv=hJa0vveoJbaqH+QeXIhk6FsOkfY6HxkPT9oAeYVt3YYrrWwUNNeDA44/GAtQx1LPliwKogEkdEjUE6QoxQzp9vtIbDY4Seyvf54daEHmgjA="},{siPic: "http://p1.music.126.net/9bVOooAY6U6EJLzpv1Fikw==/109951169682871673.jpg?param=300y300",sid: "1974443814",name: "心墙",lyrics:"[00:00.000] 作词 : 姚若龙\n[00:00.177] 作曲 : 林俊杰\n[00:00.354] 编曲 : 陈炯顺\n[00:00.531] 制作人 : 吴剑泓/陈炯顺\n[00:00.708]我学着不去担心的太远\n[00:03.894]不计划太多反而能勇敢冒险\n[00:07.395]丰富地过每一天\n[00:09.395]快乐地看每一天\n[00:15.145]Wooh~ 第一次遇见阴天遮住你侧脸\n[00:18.644]有什么故事好想了解\n[00:22.395]我感觉 我懂你的特别\n[00:28.895]你的心有一道墙\n[00:32.645]但我发现一扇窗\n[00:37.395]偶尔透出一丝暖暖的微光\n[00:43.896]就算你有一道墙\n[00:47.646]我的爱会攀上窗台盛放\n[00:51.895]打开窗你会看到 悲伤融化\n[01:15.147]我学着不去担心的太远\n[01:18.897]不计划太多反而能勇敢冒险\n[01:22.397]丰富地过每一天\n[01:24.397]快乐地看每一天\n[01:29.898]Wooh~ 第一次遇见阴天遮住你侧脸\n[01:33.647]有什么故事好想了解\n[01:37.397]我感觉 我懂你的特别\n[01:43.898]你的心有一道墙\n[01:47.648]但我发现一扇窗\n[01:52.398]偶尔透出一丝暖暖的微光\n[01:58.898]就算你有一道墙\n[02:02.648]我的爱会攀上窗台盛放\n[02:06.898]打开窗你会看到 悲伤融化\n[02:14.149]你的心有一道墙\n[02:17.649]但我发现一扇窗\n[02:23.106]偶尔透出一丝暖暖的微光\n[02:29.106]就算你有一道墙\n[02:32.606]我的爱会攀上窗台盛放\n[02:36.856]打开窗你会看到 悲伤融化\n[02:44.108]你会闻到幸福晴朗的芬芳\n",nickName: "MD李",souce: "http://m801.music.126.net/20241102175101/34d7b7449b1a0799cc14d243db6c379e/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/8193344258/f791/6ce5/1393/89f955cf0eab540f91bed2765ce20ca9.mp3?vuutv=KxvXvGxO3vcVjhvxpRGtSLeAvFSS0UwqmyxqZ+ebfb5oQoZ9Yb5GUi3KAXNoSxdEEOrPELyZ3f+5rnNTUCWe03y0IsEmgb/hXvDq4wPTdMI="}],paused: true,Time: 0,xunhuan: false,context: uni.createInnerAudioContext(),sole: {siPic: "http://p1.music.126.net/9bVOooAY6U6EJLzpv1Fikw==/109951169682871673.jpg?param=300y300",sid: "1974443814",name: "心墙",nickName: "MD李",souce: "http://slm54lcbc.hn-bkt.clouddn.com/uploads/music/20241028/20241028225914096694612.mp3?e=1730137094&token=VffSr6dlaO_zmkIeZSULAGAgMk8FgUiatiMkF0C1:edRJLwaIuGkdf1tE22fhMYfI92I=",lyrics: "[00:00.000] 作词 : 黄家驹\n[00:01.000] 作曲 : 黄家驹\n[00:02.000] 编曲 : Beyond\n[00:03.000] 制作人 : Beyond/Gordon O'Yang\n[00:06.000]\n[00:28.650]钟声响起归家的讯号\n[00:33.036]在他生命里\n[00:36.281]仿佛带点唏嘘\n[00:41.564]黑色肌肤给他的意义\n[00:46.038]是一生奉献 肤色斗争中\n[00:54.543]年月把拥有变做失去\n[01:01.056]疲倦的双眼带着期望\n[01:07.551]今天只有残留的躯壳\n[01:11.162]迎接光辉岁月\n[01:14.391]风雨中抱紧自由\n[01:20.515]一生经过彷徨的挣扎\n[01:24.167]自信可改变未来\n[01:27.343]问谁又能做到\n[01:31.091]\n[01:43.174]可否不分肤色的界限\n[01:47.643]愿这土地里\n[01:50.774]不分你我高低\n[01:56.103]缤纷色彩闪出的美丽\n[02:00.572]是因它没有\n[02:03.764]分开每种色彩\n[02:09.060]年月把拥有变做失去\n[02:15.509]疲倦的双眼带着期望\n[02:22.001]今天只有残留的躯壳\n[02:25.740]迎接光辉岁月\n[02:28.901]风雨中抱紧自由\n[02:34.992]一生经过彷徨的挣扎\n[02:38.655]自信可改变未来\n[02:41.900]问谁又能做到\n[02:45.698]\n[03:23.643]今天只有残留的躯壳\n[03:27.339]迎接光辉岁月\n[03:30.561]风雨中抱紧自由\n[03:36.626]一生经过彷徨的挣扎\n[03:40.315]自信可改变未来\n[03:43.493]问谁又能做到\n[03:48.026]Woo\n[03:50.494]\n[03:55.305]Ah\n[03:57.239]\n[03:59.359]今天只有残留的躯壳\n[04:02.934]迎接光辉岁月\n[04:06.174]风雨中抱紧自由\n[04:12.267]一生经过彷徨的挣扎\n[04:15.855]自信可改变未来\n[04:19.047]问谁又能做到\n[04:23.624]Woo\n[04:26.040]\n[04:30.932]Ah\n[04:32.610]\n[04:35.027]今天只有残留的躯壳\n[04:38.580]迎接光辉岁月\n[04:41.808]风雨中抱紧自由\n[04:47.928]一生经过彷徨的挣扎\n[04:51.555]自信可改变未来\n[04:53.560] Synth Programming : Gordon O'Yang / 叶世荣\n[04:54.560] Mixed by Philip Kwok"
}}),getters: {},actions: {}
})
3.@/components/playList.vue
<template><u-popup v-model="props.modelShow" mode="bottom" length="55%" @close="close" border-radius="14"><view style="padding: 20px 0;height: 100%;"><view class="cu-dialog play-list-dialog" style="height: 10%;"><view style="text-align: center;">播放列表<text class="light-text">(共{{playComm.tabDate.length}}首)</text></view></view><view style="height: 90%;overflow: auto;"><view v-for="(item,index) in playComm.tabDate" :key="item.sid" :class="item.sid == playInfo.sid ? 'prolist' : 'fixed-container'"><view @click="nextSong(item,index)" class="cu-avatar playImage round" :style="'background-image:url(' + item.siPic + ')'"></view><view @click="nextSong(item,index)" class="play-center" style="width: 70%;max-width: 70%;"><view class="music-name" style="font-size: 14px;">{{ item.name }}</view><view class="music-author" style="font-size: 12px;">{{ item.nickName }}</view></view><view style="line-height: 60px;"><u-icon name="trash-fill" color="red" size="40" style="margin-top: 20px;" @click.top="del(item,index)"></u-icon></view></view></view></view></u-popup>
</template><script setup>import { reactive, ref } from 'vue'import { useAppStore } from '@/stores/plear'import { useRouter, useRoute } from 'uniapp-router-next'import { onLoad, onPageScroll } from "@dcloudio/uni-app";const emit = defineEmits(["'close'","del","nextSong"]);const route = useRoute()const router = useRouter()const appStore = useAppStore()const innerAudioContext = appStore.$state.contextconst close = ()=>{emit("close",false)}const nextSong = (item,index)=>{emit("nextSong",item,index)}const del = (item,index)=>{emit("del",item,index)}const props = defineProps({modelShow: {type: Boolean,default: false}})onLoad(()=>{console.log(1245)})// appStore.$state.nickName = 100// console.log(appStore.$state)// const cardStyle = {// background: 'linear-gradient(yellow, pink)'// }const modelShow = ref(false)const playInfo = appStore.$state.soleconst playComm = appStore.$state</script><style scoped lang="scss">.prolist{display: flex;background-image: linear-gradient(to right, rgba(247, 73, 79, 0.1), rgba(247, 73, 79, 0.05));}.prolistetde{display: flex;}.light-text{color: gray;font-size: 12px;}.fixed-container {display: flex;}.play-right {width: 92px;display: flex;justify-content: space-between;align-items: center;.play-list {font-size: 30px;margin-right: 12px;}}.play-center {flex: auto;display: flex;flex-wrap: wrap;align-content: center;max-width: calc(100% - 165px);.music-name {color: #000;font-size: 32rpx;margin-bottom: 4px;width: 100%;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}.music-author {font-size: 24rpx;color: rgba(0, 0, 0, 0.5);width: 100%;}}.inbottom {width: 100%;position: fixed;bottom: 90rpx;height: 60px;box-shadow: 0 1px 2px #001500;// line-height: 50px;padding-top: 10rpx;background: white;// border: 1px solid red;}.play-right {display: flex;}.playImage {width: 80rpx;height: 80rpx;margin: auto 15px auto 10px;border-radius: 50%;}
</style>