• 网易云音乐项目


    0.项目介绍

    1.项目后台部署

    网易云音乐 NodeJS 版 API文档

    后台项目

    网易云音乐 API

    网易云音乐 Node.js API service

    灵感来自

    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.点击列表切换歌曲

    22对切换歌曲优化以及点击底部组件左

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

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

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

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

    27.获取对应的歌词数据


    __EOF__

  • 本文作者: huashenyin
  • 本文链接: https://www.cnblogs.com/huashenyin/p/16338051.html
  • 关于博主: 欢迎交流 ;))
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    软考系统架构之案例篇(软件工程相关概念)
    【从0到1进阶Redis】哨兵模式
    Windows如何启动MySQL
    x265 传递残差计算
    git的详细使用
    小马识途:如何稀释百科词条里的负面信息?
    FPGA板卡启动以及LED灯带调试
    面试题: Spring AOP是如何实现的? 它和AspectJ有什么区别?
    数据中心容灾考题
    GIC/ITS代码分析(8)中断应用实例之SGI中断
  • 原文地址:https://www.cnblogs.com/huashenyin/p/16338051.html