CHAT with me on luogo!

网易云音乐项目

0.项目介绍

1.项目后台部署

网易云音乐 NodeJS 版 API文档

后台项目

网易云音乐 API

网易云音乐 Node.js API service

Version License devDependencies devDependencies

灵感来自

disoul/electron-cloud-music

darknessomi/musicbox

sqaiyan/netmusic-node

greats3an/pyncm

环境要求

需要 NodeJS 8.12+ 环境

安装

$ git clone git@github.com:Binaryify/NeteaseCloudMusicApi.git 

$ npm install

或者

$ git clone https://github.com/Binaryify/NeteaseCloudMusicApi.git

$ npm install

运行

调用前务必阅读文档的调用前须知

$ node app.js

服务器启动默认端口为 3000,若不想使用 3000 端口,可使用以下命令: Mac/Linux

$ PORT=4000 node app.js

windows 下使用 git-bash 或者 cmder 等终端执行以下命令:

$ set PORT=4000 && node app.js

Vercel 部署

v4.0.8 加入了 Vercel 配置文件,可以直接在 Vercel 下部署了,不需要自己的服务器

操作方法

  1. fork 此项目
  2. 在 Vercel 官网点击 New Project
  3. 点击 Import Git Repository 并选择你 fork 的此项目并点击import
  4. 点击 PERSONAL ACCOUNTselect
  5. 直接点Continue
  6. PROJECT NAME自己填,FRAMEWORK PRESETOther 然后直接点 Deploy 接着等部署完成即可

可以在Node.js调用

v3.31.0后支持Node.js调用,导入的方法为module内的文件名,返回内容包含statusbody,status为状态码,body为请求返回内容,参考module_example 文件夹下的 test.js

const { login_cellphone, user_cloud } = require('NeteaseCloudMusicApi')
async function main() {
  try {
    const result = await login_cellphone({
      phone: '手机号',
      password: '密码'
    })
    console.log(result)
    const result2 = await user_cloud({
      cookie: result.body.cookie // 凭证
    })
    console.log(result2.body)
      
  } catch (error) {
    console.log(error)
  }
}
main()

支持 TypeScript

// test.ts
import { banner } from 'NeteaseCloudMusicApi'
banner({ type:0 }).then(res=>{
  console.log(res)
})

使用文档

文档地址

文档地址2

文档

2.vue项目创建

安装

可以使用下列任一命令安装这个新的包:

npm install -g @vue/cli
# OR
yarn global add @vue/cli

安装之后,你就可以在命令行中访问 命令。你可以通过简单运行 ,看看是否展示出了一份所有可用命令的帮助信息,来验证它是否安装成功。vuevue

你还可以用这个命令来检查其版本是否正确:

vue --version

升级

如需升级全局的 Vue CLI 包,请运行:

npm update -g @vue/cli
# 或者
yarn global upgrade --latest @vue/cli

创建一个新项目:

vue create music163_app
运行项目
npm run serve

3.实现rem布局

所谓的适配布局,是让页面盒子的高度,宽度,内外边距,边框大小,文字的大小,定位的元素位置等能够根据屏幕宽度自动改变大小和位置,从而达到对不同的屏幕都能够做到最完美的展示,这就是rem适配布局的优秀地方。

rem.js

function remSize(){
    //获取设备的宽度
    var deviceWidth=document.documentElement.clientWidth || window.innerWidth
    if(deviceWidth>=750){
        deviceWidth=750
    }
    if(deviceWidth<=320){
        deviceWidth=320
    }
    //750px-->1rem=100px,375px-->1rem=50px
    document.documentElement.style.fontSize=(deviceWidth/7.5)+'px'
    // 设置字体大小
    document.querySelector('body').style.fontSize=0.3+"rem"
}
remSize()
// 当窗口发生变化就调用
window.onresize=function(){
    remSize()
}

在主页面引入rem布局

index.html
<script src="<%= BASE_URL %>js/rem.js"></script>

4.字体图标的引入

进入阿里图标库

创建自己的项目

第一步:拷贝项目下面生成的symbol代码:

index.html
//at.alicdn.com/t/*****.js
第二步:加入通用css代码(引入一次就行):
.home.vue(设置全局样式)

<style type="text/css">
    .icon {
       width: 1em; height: 1em;
       vertical-align: -0.15em;
       fill: currentColor;
       overflow: hidden;
    }
</style>

第三步:挑选相应图标并获取类名,应用于页面:

<svg class="icon" aria-hidden="true">
    <use xlink:href="#icon-xxx"></use>
</svg>

5.完成头部导航组件

TopNav.vue

<template>
  <div class="topNav">
    <div class="topleft">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-31liebiao"></use>
      </svg>
    </div>

    <div class="topContent">
      <span @click="$router.push('/infoUser')">我的</span>
      <span class="active">发现</span>
      <span>云村</span>
      <span>视频</span>
    </div>
    <div class="topRight">
      <svg class="icon" aria-hidden="true" @click="$router.push('/search')">
        <use xlink:href="#icon-sousuo"></use>
      </svg>
    </div>
  </div>
  
</template>
<style lang="less" scoped>
    .topNav{
        width: 100%;
        height: 1rem;
        padding: .2rem;
        display: flex;
        justify-content: space-between;
        align-items: center;
        .topContent{
            width: 65%;
            height: 100%;
            display: flex;
            justify-content: space-around;
            // align-items: center;
            font-size: .36rem;
            .active{
                font-weight: 900;
            }
        }
    }
</style>

6.在项目引入vant组件库

vant组件库

  1. 安装插件
# 通过 npm 安装
npm i vite-plugin-style-import@1.4.1 -D

# 通过 yarn 安装
yarn add vite-plugin-style-import@1.4.1 -D

# 通过 pnpm 安装
pnpm add vite-plugin-style-import@1.4.1 -D
  1. 配置插件
    安装完成后,在 vite.config.js 文件中配置插件:
import vue from '@vitejs/plugin-vue';
import styleImport, { VantResolve } from 'vite-plugin-style-import';

export default {
  plugins: [
    vue(),
    styleImport({
      resolves: [VantResolve()],
    }),
  ],
};
  1. 引入组件
    完成以上两步,就可以直接使用 Vant 组件了:

plugins>index.js

import { Swipe, SwipeItem,Button,Popup  } from 'vant';
// 放入数组中
let plugins=[
    Swipe,SwipeItem,Button,Popup 
]
export default function getVant(app){
    plugins.forEach((item)=>{
        return app.use(item)
    })
}

main.js

import getVant from './plugins'
const app=createApp(App)
getVant(app)

按需使用,插件式引入

7.首页轮播图的配置

SwpierTop.vue

<template>
  <div id="swiperTop">
    <van-swipe :autoplay="3000" lazy-render>
      <van-swipe-item v-for="image in state.images" :key="image">
        <img :src="image.pic" />
      </van-swipe-item>
    </van-swipe>
  </div>
</template>
<script>
import axios from "axios";
import { getBanner } from "@/request/api/home.js";
import { reactive, onMounted } from "vue";
export default {
  setup() {
    const state = reactive({
      images: [
        "https://img.yzcdn.cn/vant/apple-1.jpg",
        "https://img.yzcdn.cn/vant/apple-2.jpg",
      ],
    });
    onMounted(async () => {
      // axios.get('http://localhost:3000/banner?type=2').then((res)=>{
      //   console.log(res);
      //   state.images=res.data.banners
      //   console.log(state.images);
      // })
      let res = await getBanner();
      state.images=res.data.banners
      console.log(res);
    });
    return { state };
  },
};
</script>
<style lang="less">
#swiperTop {
  //需要在上面自己添加一个id
  .van-swipe {
    width: 100%;
    height: 3rem;
    .van-swipe-item {
      padding: 0 0.2rem;
      img {
        width: 100%;
        height: 100%;
        border-radius: 0.2rem;
      }
    }
    .van-swipe__indicator--active {
      background-color: rgb(219, 130, 130);
    }
  }
}
</style>

8.轮播图样式以及获取轮播图数据进行.

获取网易云接口数据
axios中文文档
安装axios
npm install axios
引入axios
import axios from "axios";
执行get请求



setup() {
    const state = reactive({
      images: [
        "https://img.yzcdn.cn/vant/apple-1.jpg",
        "https://img.yzcdn.cn/vant/apple-2.jpg",
      ],
    });
    onMounted(async () => {
      //执行 GET 请求
      axios.get('http://localhost:3000/banner?type=2').then((res)=>{
     console.log(res);
     state.images=res.data.banners
     console.log(state.images);
     })

页面渲染
<img :src="image.pic" />

9.封装axios请求

  • 创建axios 实例

request>index.js

// 创建axios 实例,把域名基础路径抽取出来
import axios from 'axios';
let service=axios.create({
    baseURL:"http://localhost:3000/",
    timeout:3000
})

export default service
  • 创建指定的配置将与实例的配置合并。

request>api>home.js


import service  from "..";
// 获取首页轮播图的数据
export function getBanner(){
    return service({
        method:"GET",
        url:"/banner?type=2",
    })
}
  • 在所需页面进行异步请求
import { getBanner } from "@/request/api/home.js";


    onMounted(async () => {
      let res = await getBanner();
      state.images=res.data.banners

10.图标组件的编写

IconList.vue

<template>
  <div class="iconList">
    <div class="iconItem">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-tuijian"></use>
      </svg>
      <span>每日推荐</span>
    </div>
    <div class="iconItem">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-zhibo"></use>
      </svg>
      <span>私人FM</span>
    </div>
    <div class="iconItem">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-gedan"></use>
      </svg>
      <span>歌单</span>
    </div>
    <div class="iconItem">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-paihangbang"></use>
      </svg>
      <span>排行榜</span>
    </div>
  </div>
</template>
<style lang="less" scoped>
    .iconList{
        width: 100%;
        height: 2rem;
        margin-top: .2rem;
        display: flex;
        justify-content: space-around;
        align-items: center;
        .iconItem{
            width: 25%;
            height: 100%;
            display: flex;
            flex-direction: column;
            align-items: center;
            .icon{
                width: 1rem;
                height: 1rem;
            }
        }
    }
</style>

11发现歌单数据的获取

获取歌单

  • 获取歌单数据

request>api

//获取发现好歌单
export function getMusicList(){
    return service({
        method:"GET",
        url:"/personalized?limit=10"
    })
}
  • 调用getMusicList方法

MusicList.vue

Vue2


  data() {
    return {
      musicList: [],//定义数组接收数据
    };
  },
  methods: {
    async getGnedan() {
      let res = await getMusicList();
      console.log(res);
      this.musicList = res.data.result;
    },
  },
  mounted() {
    this.getGnedan();
  },

vue3

MusicList.vue

  // Vue3
  setup() {
    const state = reactive({
      musicList: [],//定义数组接收数据,reactive()可以响应式修改数据
    });
    onMounted(async () => { //onMounted执行数据
      let res = await getMusicList();
      console.log(res);
      state.musicList = res.data.result;
    });
    return { state,changeCount };//返回数据
  },

12.发现好歌单的列表宣染

  • 页面渲染

MusicList.vue

        <van-swipe-item v-for="item in state.musicList" :key="item">
          <router-link :to="{path:'/itemMusic',query:{id:item.id}}">
          <img :src="item.picUrl" alt="" />
          <span class="playCount">
            <svg class="icon" aria-hidden="true">
              <use xlink:href="#icon-gl-play-copy"></use>
            </svg>
            {{ changeCount(item.playCount) }}
          </span>
          <span class="name">{{ item.name }}</span>
          </router-link>
        </van-swipe-item>
  • 组件使用:

views>Home.vue: 1.import 2.components注册,div 引用

 <div>
    <TopNav />
    <SwpierTop />
    <IconList/>
    <MusicList/>
  </div>
import MusicList from "@/components/home/MusicList.vue";
  components: {
    TopNav,
    SwpierTop,
    IconList,
    MusicList,
  },

13.歌单路由跳转并携带参数

路由跳转

配置路由

router.js

{
    path: '/itemMusic',
    name: 'ItemMusic',
    component: () => import('../views/ItemMusic.vue')
  },

使用router-link路由跳转并query传参

MusicList.vue

          <router-link :to="{path:'/itemMusic',query:{id:item.id}}">
          </router-link>

14.获取路由参数获取对应的歌单的数据

使用useRoute可以获取路由传递的参数

    onMounted(() => {
      let id = useRoute().query.id;
      console.log(id);
}
  • 获取歌单详情页数据

request>item.js

//获取歌单详情页的数据
export function getMusicItemList(data){
    return service({
        method:"GET",
        url:`/playlist/detail?id=${data}`
    })

*调用getMusicItemList方法

 setup() {
    const state = reactive({
      playlist: {}, //数组接收歌单详情页的数据
    });
    onMounted(async () => {
      let id = useRoute().query.id;////onMounted执行数据
      console.log(id);
      //   获取歌单详情页
      let res = await getMusicItemList(id);
      console.log(res);
      state.playlist = res.data.playlist;//将获取的api数据存储到数组playlist中
}
return { state };//返回数据

15.通过props进行传参宣染页面以及

  • props由父级 prop 的更新会向下流动到子组件中

父组件ItemMusic

  <ItemMusicTop :playlist="state.playlist" /> //将父组件的state.playlist传给子组件ItemMusicTop

  components: {
    ItemMusicTop,
    ItemMusicList,
  },

子组件ItemMusicTop

setup(props){
  console.log(playlist)
  }
  props: ["playlist"],//子组件接收playlist数据
  • 页面渲染

ItemMusicTop


  <template>
  <div class="itemMusicTop">
    <img :src="playlist.coverImgUrl" alt="" class="bgimg" />
    <div class="itemLeft">
      <svg class="icon" aria-hidden="true" @click="$router.go(-1)">
        <use xlink:href="#icon-zuojiantou"></use>
      </svg>
      <span>歌单</span>
    </div>
    <div class="itemRight">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-sousuo"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-31liebiao"></use>
      </svg>
    </div>
  </div>
  <div class="itemTopContent">
    <div class="contentLeft">
      <img :src="playlist.coverImgUrl" alt="" />
      <div class="palyCount">
        <svg class="icon" aria-hidden="true">
          <use xlink:href="#icon-gl-play-copy"></use>
        </svg>
        <span>{{ changeCount(playlist.playCount) }}</span>
      </div>
    </div>
    <div class="contentRight">
      <p class="rightP_one">{{ playlist.name }}</p>
      <div class="right_img">
        <img :src="playlist.creator.backgroundUrl" alt="" />
        <span>{{ playlist.creator.nickname }}</span>
        <svg class="icon" aria-hidden="true">
          <use xlink:href="#icon-youjiantou"></use>
        </svg>
      </div>
      <p class="rightP_two">
        <span>{{ playlist.description }}</span>
        <svg class="icon" aria-hidden="true">
          <use xlink:href="#icon-youjiantou"></use>
        </svg>
      </p>
    </div>
  </div>
  <div class="itemTopFooter">
    <div class="footerItem">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-iconfontzhizuobiaozhun023110"></use>
      </svg>
      <span>{{ playlist.commentCount }}</span>
    </div>
    <div class="footerItem">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-fenxiang"></use>
      </svg>
      <span>{{ playlist.shareCount }}</span>
    </div>
    <div class="footerItem">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-iconfontzhizuobiaozhun023146"></use>
      </svg>
      <span>下载</span>
    </div>
    <div class="footerItem">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-show_duoxuan"></use>
      </svg>
      <span>多选</span>
    </div>
  </div>
</template>

*背景虚化

  .bgimg {
    width: 100%;
    height: 11rem;
    position: absolute;
    z-index: -1;
    filter: blur(30px);
  }
  • 路由返回上一页面

@click="$router.go(-1)

16.对页面刷新,歌单数据丢失的处理

原因:在itemmusic中onMoonunted里数据获取都是异步的,在子组件页面渲染时数据还没有获取到。为防止数据丢失,保存本地数据

父组件itemMuisc的onMoonunted

      //   防止页面刷新,数据丢失,将数据保存到sessionStorage中,setItem存储value,getItem获取value
      sessionStorage.setItem("itemDetail", JSON.stringify(state));// 将state以json的格式保存到key为itemDetail的会话存储中

判断页面刷新后数据是否为空,空则调用sessionStorage里的数据

子组件ItemMusicTop的onMounted

    // 通过props进行传值,判断如果数据拿不到,就获取sessionStorage中的数据
    if(props.playlist.creator=""){
      props.playlist.creator = JSON.parse(sessionStorage.getItem().playlist).creator //获取sessionStorage里的value有playlist的creator,并将json格式数据转为对象数据 
    }

17.获取歌单列表的数据

*获取歌单歌曲详情数据

//获取歌单的所有歌曲
export function getItemList(data){
    return service({
        method:"GET",
        url:`/playlist/track/all?id=${data.id}&limit=${data.limit}&offset=${data.offset}`
    })
}
  • 传参

itemMusic

    const state = reactive({
      itemList: [], //1.定义数组存储歌单的歌曲数据
    });
      //2.获取歌单的歌曲
    onMounted(async () => {
      let result = await getItemList({ id, limit: 10, offset: 0 });
      console.log(result);
      state.itemList = result.data.songs
}
    return { state }; //3.返回数据
//4.将父组件的state.itemList转给子组件ItemMusicList的itemList

  <ItemMusicList
    :itemList="state.itemList"
  />
//props子组件接收数据itemList
  setup(props) {
    console.log(props);
  },
  props: ["itemList"],

18.宣染页面的注意点讲解

  • 播放列表实现左侧1-10排序
      <div class="item" v-for="(item, i) in itemList" :key="i">
          <span class="leftSpan">{{ i + 1 }}</span>
  • 判断歌曲是否有mv
          //如果有mv显示mv图标
          <svg class="icon bofang" aria-hidden="true" v-if='item.mv !=0'>
            <use xlink:href="#icon-shipin"></use>
          </svg>
  • 作者为多个的情况下,使用v-for循环
            <span v-for="(item1, index) in item.ar" :key="index">{{
              item1.name
            }}</span>

19.底部组件的制作

因为底部组件全页面都存在,所以是全局组件

app.vue

//1.import 2.components注册,div 引用
<template>
  <FooterMusic v-show="$store.state.isFooterMusic"/>
</template>
<script>
import FooterMusic from "@/components/item/FooterMusic.vue"
export default {
  components:{
    FooterMusic
  }
}

  • 播放列表(全局)存入vuex中

stroe

    playList: [{ //播放列表默认
      al: {
        id: 89039055,
        name: "雨爱抖音版",
        pic: 109951164966568500,
        picUrl: "https://p1.music.126.net/2f6UgY8Jc0Dy6jufMdIZeQ==/109951164966568495.jpg",
        pic_str: "109951164966568495"
      },
      id: 1446137141,
      name: "雨爱(抖音版)",
      ar:[{name: "灏灏灏仔"}]
    }],
    playListIndex: 0, //默认下标为0,当切换歌曲时凭下标切换

*获取vuex里的playList数据

itemMusicList
//使用vuex的内置函数mapState 获取vuex数据,解构赋值

import {  mapState } from "vuex";
  computed: {
    ...mapState(["playList", "playListIndex", "isbtnShow", "detailShow"]),
  },

*页面渲染

itemMusicList

      <img :src="playList[playListIndex].al.picUrl" alt="" />
      <div>
        <p>{{ playList[playListIndex].name }}</p>
        <span>横滑切换上下首哦</span>
      </div>

20.底部组件播放音乐功能

模板字符串(template string)是增强版的字符串,用反引号``标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量,变量名写在$()中。

ref ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件.

//实例
<div ref="test" @click="test">ref 测试</div>
mounted(){
       console.log(this.$refs.test);
    },

*播放

    <audio
      ref="audio"
      :src="`https://music.163.com/song/media/outer/url?id=${playList[playListIndex].id}.mp3`" 
    ></audio>//因为src里的id是变量,所以用模板字符串``

*添加播放方法

methods: {
  play: function () {
      this.$refs.audio.play();
    } 
}

*使用播放方法

      <svg class="icon liebiao" aria-hidden="true" @click="play" >
        <use xlink:href="#icon-weibiaoti--"></use>
      </svg>

*播放暂停切换

store

state:{
   isbtnShow: true, //暂停按钮的显示
}
mutations: {
    updateIsbtnShow: function (state, value) {
     state.isbtnShow = value //调用updateIsbtnShow()方法传value从而改变state.isbtnShow的布尔值,
    },

*播放暂停切换方法
play: function () {
// 判断音乐是否播放
if (this.\(refs.audio.paused) { this.\)refs.audio.play();
this.updateIsbtnShow(false);
} else {
this.$refs.audio.pause();
this.updateIsbtnShow(true);
}
},
*解构赋值和解构方法

  //解构赋值
  computed: {
    ...mapState(["playList", "playListIndex", "isbtnShow"]),
  },
//解构方法
...mapMutations(["updateIsbtnShow"]),

*页面播放暂停图标添加判断事件实现播放暂停图标的切换

      <svg class="icon liebiao" aria-hidden="true" @click="play" v-if="isbtnShow">
        <use xlink:href="#icon-bofanganniu"></use>
      </svg>
      <svg class="icon liebiao" aria-hidden="true" @click="play" v-else>
        <use xlink:href="#icon-weibiaoti--"></use>
      </svg>

21.点击列表切换歌曲

Store

  mutations: {
    updatePlayList: function (state, value) {
      state.playList = value
      console.log(state.playList);
    },
    updatePlayListIndex: function (state, value) {
      state.playListIndex = value
    },
}

itemMusicList

    <div class="itemLeft" @click="playMusic(i)">

FooterMusic

  watch: {
    playListIndex: function () {
      //监听如果下标发生了改变,就自动播放音乐
      this.$refs.audio.autoplay = true;
      if (this.$refs.audio.paused) {
        //如果是暂停状态,改变图标
        this.updateIsbtnShow(false);
      }
    },
}

22对切换歌曲优化以及点击底部组件左侧进入歌曲详情页

FooterMusic

watch: {
//歌单第一首歌与默认歌曲(下标都为0),不能切换歌曲
playList: function () {
      if (this.isbtnShow) {
        this.$refs.audio.autoplay = true;
        this.updateIsbtnShow(false);
      }
    },
  • 点击底部进入歌曲详情页
    引入vant弹出层组件
import { Swipe, SwipeItem,Button,Popup  } from 'vant';
// 放入数组中
let plugins=[
    Swipe,SwipeItem,Button,Popup 
]
export default function getVant(app){
    plugins.forEach((item)=>{
        return app.use(item)
    })
}

FooterMusic

    <van-popup
      v-model:show="detailShow"
      position="right"
      :style="{ height: '100%', width: '100%' }"
    >
      <MusicDetail
        :musicList="playList[playListIndex]"
        :play="play"
        :isbtnShow="isbtnShow"
        :addDuration="addDuration"
      />
    </van-popup>

23.歌曲详情页的头部数据的获取以及使用跑马灯

  components: {
    MusicDetail,
  },
  • 头部数据传值到歌曲详情页
      <MusicDetail
        :musicList="playList[playListIndex]"
        :play="play"
        :isbtnShow="isbtnShow"
        :addDuration="addDuration"
      />
>MusicDetail
  props: ["musicList", "isbtnShow", "play","addDuration"],
  • 歌曲详情页
<template>
  <img :src="musicList.al.picUrl" alt="" class="bgimg" />
  <div class="detailTop">
    <div class="detailTopLeft">
      <svg class="icon liebiao" aria-hidden="true" @click="backHome">
        <use xlink:href="#icon-zuojiantou"></use>
      </svg>
      <div class="leftMarquee">
        <Vue3Marquee style="color: #fff">{{ musicList.al.name }}</Vue3Marquee>
        <span v-for="item in musicList.ar" :key="item">
          {{ item.name }}
        </span>
        <svg class="icon liebiao" aria-hidden="true">
          <use xlink:href="#icon-youjiantou1"></use>
        </svg>
      </div>
    </div>
    <div class="detailTopRight">
      <svg class="icon liebiao" aria-hidden="true">
        <use xlink:href="#icon-fenxiang"></use>
      </svg>
    </div>
  </div>
  <div class="detailContent" v-show="!isLyricShow">
    <img
      src="@/assets/needle-ab.png"
      alt=""
      class="img_needle"
      :class="{ img_needle_active: !isbtnShow }"
    />
    <img src="@/assets/cd.png" alt="" class="img_cd" />
    <img
      :src="musicList.al.picUrl"
      alt=""
      class="img_ar"
      @click="isLyricShow = true"
      :class="{ img_ar_active: !isbtnShow, img_ar_pauesd: isbtnShow }"
    />
  </div>
  <div class="musicLyric" ref="musicLyric" v-show="isLyricShow">
    <p
      v-for="item in lyric"
      :key="item"
      :class="{
        active:
          currentTime * 1000 >= item.time && currentTime * 1000 < item.pre,
      }"
    >
      {{ item.lrc }}
    </p>
  </div>
  <div class="detailFooter">
    <div class="footerTop">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-aixin"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-iconfontzhizuobiaozhun023146"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-yinlechangpian"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-iconfontzhizuobiaozhun023110"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-liebiao-"></use>
      </svg>
    </div>
    <div class="footerContent">
      <input type="range" class="range" min="0" :max="duration" v-model="currentTime" step="0.05">
    </div>
    <div class="footer">
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-xunhuan"></use>
      </svg>
      <svg class="icon" aria-hidden="true" @click="goPlay(-1)">
        <use xlink:href="#icon-shangyishoushangyige"></use>
      </svg>
      <svg
        class="icon bofang"
        aria-hidden="true"
        v-if="isbtnShow"
        @click="play"
      >
        <use xlink:href="#icon-bofang1"></use>
      </svg>
      <svg class="icon bofang" aria-hidden="true" v-else @click="play">
        <use xlink:href="#icon-zanting"></use>
      </svg>
      <svg class="icon" aria-hidden="true" @click="goPlay(1)">
        <use xlink:href="#icon-xiayigexiayishou"></use>
      </svg>
      <svg class="icon" aria-hidden="true">
        <use xlink:href="#icon-zu"></use>
      </svg>
    </div>
  </div>
</template>
  • 背景图片虚化
<img :src="musicList.al.picUrl" alt="" class="bgimg" />
.bgimg {
  width: 100%;
  height: 100%;
  position: absolute;
  z-index: -1;
  filter: blur(70px);
}
  • 跑马灯

musicdetail

//引入组件
import { Vue3Marquee } from "vue3-marquee";
import "vue3-marquee/dist/style.css";
//注册组件
  components: {
    Vue3Marquee,
  },
//使用组件
<Vue3Marquee style="color: #fff">{{ musicList.al.name }}</Vue3Marquee>

24.歌曲详情页中间部分的实现

唱片机由两部分组成,一个是磁针,另一个是唱片

1. 先完成磁针随着播放按钮进行是否在唱片上的切换

原理:将播放暂停状态存入布尔值isbtnShow中,根据isbtnShow的值切换磁针的class。

css代码
磁针在暂停状态下是 transform: rotate(-13deg);

磁针在播放状态下是 transform: rotate(0deg);


因此添加磁针在播放状态的class
.img_needle_active { width: 2rem; height: 3rem; position: absolute; left: 46%; transform-origin: 0 0; transform: rotate(0deg); transition: all 2s; }

再判断点击播放按钮时将class从.img_needle换为.img_needle_active
为磁针添加动态class,当点击播放按钮时(即isbtnShow=1时),切换磁针的class为img_needle_active

    <!-- 当!isbtnShow时唱片自转磁针转到唱片上 -->
    <img src="@/assets/needle-ab.png" class="img_needle"  :class="{img_needle_active:!isbtnShow}">

2.图片唱片随播放按钮旋转
唱片是通过旋转z轴从0到360度的,使用css3 @keyframes方法创建动画可以实现,再将动画的状态放入class中,通过动态切换class的方法实现唱片的自转和暂停
css代码:

  .img_ar {
    width: 3.2rem;
    height: 3.2rem;
    border-radius: 50%;
    position: absolute;
    bottom: 3.14rem;
    animation: rotate_ar 10s linear infinite;
  }
  .img_ar_active{ 
    animation-play-state: running;
  }
  .img_ar_paused{
        animation-play-state: paused;
  }
  @keyframes rotate_ar {
    0%{
      transform: rotateZ(0deg);
    }
    100%{
      transform: rotateZ(360deg);
    }
    
  }

vue代码:

<!-- 当!isbtnShow时唱片自转,isbtnShow时暂停 -->
<img :src="musicList.al.picUrl" class="img_ar" :class="{img_ar_active:!isbtnShow,img_ar_paused:isbtnShow}">

结果:

25.歌曲详情页的底部组件的完成以及实.

26.完成对歌曲详情页中间部分动画样式

27.获取对应的歌词数据

posted @ 2022-06-02 17:50  qsad阿斯顿  阅读(792)  评论(0编辑  收藏  举报