網易雲音樂專案

2022-06-02 18:00:08

0.專案介紹

1.專案後臺部署

網易雲音樂 NodeJS 版 API檔案

後臺專案

網易雲音樂 API

網易雲音樂 Node.js API service

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 [email protected] -D

# 通過 yarn 安裝
yarn add [email protected] -D

# 通過 pnpm 安裝
pnpm add [email protected] -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.獲取對應的歌詞資料