Netease cloud music case

Posted by davey_b_ on Sun, 03 Oct 2021 23:53:48 +0200

Case purpose

  1. Learn how to introduce nodeJS server of Netease cloud
  2. Simple page layout is realized through some functions of Netease cloud interface
  3. Gradually become familiar with vant component library through mobile end cases

1. Local interface of Netease cloud music

Download Netease cloud music node interface project and start it locally to provide data support for our vue project. And start the service locally through Nodemon to get the data. The project download method is as follows.
https://binaryify.github.io/NeteaseCloudMusicApi/#/


2. Front end project initialization of Netease cloud music case

  • Initialize project Vue create music demo
  • Download the required third-party package Axios vant Vue router, etc
  • Download the van t auto import plug-in Babel plugin import
  • Configure relevant parameters in babel.config

plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]

  • Introduce the basic css/less/js file, make the basic layout of the page, and introduce these files into the main.js file

3. Analysis of front-end project demand page of Netease cloud music

The core idea is that Layout/index is responsible for Layout, up and down navigation, secondary route switching is mounted in the middle, and the functions of home page and search page are realized. Therefore, the Layout module is the core module.
The top of the Layout is a navigation bar, and the bottom is the tabbar switching bar. It is very convenient to switch routes with the to attribute.

<template>
  <div>
    <van-nav-bar :title="activeTitle" fixed />
    <div class="main">
      <!-- Secondary routing-Mount point -->
      <router-view></router-view>
    </div>
    <van-tabbar route>
      <van-tabbar-item replace to="/layout/home" icon="home-o"
        >home page</van-tabbar-item
      >
      <van-tabbar-item replace to="/layout/search" icon="search"
        >search</van-tabbar-item
      >
    </van-tabbar>
  </div>
</template>

The top navbar accepts the value passed from the route navigation and the value in the router/index.js file.

    {
        path: '/layout',
        component: Layout,
        redirect: '/layout/home',
        children: [{
                path: 'home',
                component: Home,
                meta: { // meta stores additional information about routing objects
                    title: "home page"
                }
            },
            {
                path: 'search',
                component: Search,
                meta: {
                    title: "search"
                }
            }
        ]
    }

The intermediate router view is responsible for mounting secondary routes, including the home page and the search page. The play page is attached to the primary route. The above is the basic business logic of the whole project. Among them, the knowledge points from design to value transfer, that is, the title value passed from the router/index.js file, I need to accept and return it to navbar. Navbar and tabbar are components in the vant component library. Therefore, the implementation of secondary routing in the middle is introduced below.

export default {
  data() {
    return {
      activeTitle: this.$route.meta.title, // The title to be displayed in the "default" top navigation (get the Title Value in the meta in the current routing object by default)
    };
  },
  // Route switching - listen for $route object changes
  watch: {
    $route() {
      this.activeTitle = this.$route.meta.title; // Extract the title display in the route information object after switching
    },
  },
}

https://vant-contrib.gitee.io/vant/#/zh-CN/nav-bar navbar official documents
https://vant-contrib.gitee.io/vant/#/zh-CN/tabbar tabbar official documents

4 network request api

  • On the Home page, recommend the song list api and the latest music api. The premise is that you have downloaded the Netease cloud project locally and started the project with node. In my project, the port number I started with node is 3000, so I can access all functions of Netease cloud music through the local 3000 port
// Network request - you can first encapsulate it in a separate js file, and then import these js files into the files in the api directory
import axios from 'axios'
axios.defaults.baseURL = "http://localhost:3000"
export default axios
// Home page - Recommended song list
export const recommendMusic = params => request({
    url: '/personalized',
    params
    // The value {limit: 20} of params may be passed in the future
})

// Home page - recommend the latest music
export const newMusic = params => request({
    url: "/personalized/newsong",
    params
})

In order to form a good habit, we usually send requests with axios, and my request is a js file that wraps the axios function.

  • In the search page, we also need to use the above method to import two interfaces, namely, hot search keywords and search results. All codes at the back end are imported from third-party files, so we need to follow the interface conditions given by the back end.
// Hot search keyword
export const hotSearch = params => request({
    url: '/search/hot',
    params
})

// search result
export const searchResultList = params => request({
    url: '/cloudsearch',
    params
})
  • The second is the play page. What we need to do is to obtain song details and lyrics. Of course, this is not the main purpose of our case. The main purpose of our case is to understand the overall vue layout of Netease cloud music.
// Play page - get song details
export const getSongById = (id) => request({
  url: `/song/detail?ids=${id}`,
  method: "GET"
})

// Play page - get lyrics
export const getLyricById = (id) => request({
  url: `/lyric?id=${id}`,
  method: "GET"
})

  • The most important thing is that I have imported so many interfaces, so how can we export these interfaces? How is it exported? And listen to me slowly.
    First, we put all the imported interface names in one js file
// Each request module JS under the api folder is uniformly sent to index.js and then exported
import {recommendMusic, newMusic} from './Home'
import {hotSearch, searchResultList} from './Search'
import {getSongById, getLyricById} from './Play'

export const recommendMusicAPI = recommendMusic // Export method of requesting recommended song list
export const newMusicAPI = newMusic // Home - Latest Music

export const hotSearchAPI = hotSearch // Search - Hot search keywords
export const searchResultListAPI = searchResultList // Search = search results

export const getSongByIdAPI = getSongById // Song - playback address
export const getLyricByIdAPI = getLyricById // Song lyrics data

Then, the api directory will be introduced wherever it is needed in the main page

import { recommendMusicAPI, newMusicAPI } from "@/api";

5 automatic adaptation of mobile vant component library.

Target: Vant component library auto fit
Automatically convert all px to rem (we can write px directly in the future) - webpack can translate css code and convert px to rem by cooperating with postcss and postcss pxorem plug-ins

  1. Download postcss postcss-pxtorem@5.1.1 (to be compatible with the current scaffold webpack)
  2. postcss.config.js - fill in the benchmark value of the plug-in transformation
  3. Be sure to restart the server and observe the effect
module.exports = {
    plugins: {
      'postcss-pxtorem': {
        // The px unit of all elements can be converted to Rem
        // rootValue: base value of converted px.
        // For example, if the width of an element is 75px, it will be 2rem after rem.
        rootValue: 37.5,
        propList: ['*']
      }
    }
  }

6 home page logic

<template>
  <div>
    <p class="title">Recommended song list</p>
    <van-row gutter="6">
      <van-col span="8" v-for="obj in reList" :key="obj.id">
        <van-image width="100%" height="3rem" fit="cover" :src="obj.picUrl" />
        <p class="song_name">{{ obj.name }}</p>
      </van-col>
    </van-row>

    <p class="title">Latest music</p>
    <SongItem v-for="obj in songList"
    :key="obj.id"
    :name="obj.name"
    :author="obj.song.artists[0].name"
    :id="obj.id"
    ></SongItem>
  </div>
</template>

There is no profound knowledge among them. Those with vue component foundation should be easy to understand. As for songItem, we will talk about it later.

//Objective: lay the recommended song list
//. van row and van col to build the outline layout
//Contents in Van col (Van image and p)
//. adjust spacing and attribute values
//. call the encapsulated api/index.js - Recommended song list api method
//. get the data and save it in the data variable - go to the loop above

Goal: laying the latest music
. Van Cell laying a set of label structure
Customize the small icon in the right slot and adjust the vertical center
api/Home.js and api/index.js - encapsulate and export methods to obtain the latest music interface
Get the data circulation page

The data bits involved, reList and SongList, are data taken from the back end

  data() {
    return {
      reList: [], // Recommended song list data
      songList: [], // Latest music data
    };
  },
  async created() {
    const res = await recommendMusicAPI({
      limit: 6,
    });
    console.log(res);
    this.reList = res.data.result;

    const res2 = await newMusicAPI({
      limit: 20
    })
    console.log(res2);
    this.songList = res2.data.result
  }

7 search page logic, the top is the search box, using vant components

//Target: lay hot search keywords
//1. Search box, van search component, keyword label and style
//2. Find the interface and define the request method for obtaining search keywords in api/Search.js
//3. Import to the current page and call the interface to get the data circulation page
//4. Click the keyword to assign the value to the v-model variable of van search

    <van-search
      shape="round"
      v-model="value"
      placeholder="Please enter a search keyword"
      @input="inputFn"
    />

Below the top is the hot search keyword. If there is no search result, the hot search keyword is displayed by default. If there is a search result, the final search result is displayed

    <!-- Search next container -->
    <div class="search_wrap" v-if="resultList.length === 0">
      <!-- title -->
      <p class="hot_title">Popular search</p>
      <!-- Hot search keyword container -->
      <div class="hot_name_wrap">
        <!-- Each search keyword -->
        <span
          class="hot_item"
          v-for="(obj, index) in hotArr"
          :key="index"
          @click="fn(obj.first)"
          >{{ obj.first }}</span
        >
      </div>
    </div>

The search results are shown in the following code

    <!-- search result -->
    <div class="search_wrap" v-else>
      <!-- title -->
      <p class="hot_title">Best match</p>
      <van-list
        v-model="loading"
        :finished="finished"
        finished-text="No more"
        @load="onLoad"
      >
        <SongItem
          v-for="obj in resultList"
          :key="obj.id"
          :name="obj.name"
          :author="obj.ar[0].name"
          :id="obj.id"
        ></SongItem>
      </van-list>
    </div>

8SongItem represents a list item for playing music

You can see that it includes the title and icon, so we use the van call component to implement it

<template>
  <van-cell center :title="name" :label="author + ' - ' + name">
    <template #right-icon>
      <van-icon name="play-circle-o" size="0.6rem" @click="playFn"/>
    </template>
  </van-cell>
</template>

And specify the parameter requirements passed in props

  props: {
    name: String, // Song title
    author: String, // singer
    id: Number, // Song id (mark this song - prepare to jump to the play page in the future)
  }

Now the basic part of the project has been built, and the most important part is the direction of data transmission and flow.

  • First, let's look at the simplest hot search keyword module, which asynchronously calls the API interface in the created life cycle and assigns the value to hotArr
  async created() {
    const res = await hotSearchAPI();
    console.log(res);
    this.hotArr = res.data.result.hots;
  }
  • Secondly, after entering the information in the input box and obtaining the background data, we need to lay the data, that is, lay it into the component style of SongItem

//Target: search results
//1. Find the interface for search results - api/Search.js defines the request method
//2. Redefine getListFn method in methods (get data)
//3. Call the getListFn method in the click event method to get the search result data
//4. Laying page (copy the van cell label on the front page)
//5. After saving the data to data, cycle the van cell to use it (switch the singer field)
//6. Mutually exclusive display of search results and hot search keywords

 async getListFn() {
      return await searchResultListAPI({
        keywords: this.value,
        limit: 20,
        offset: (this.page - 1) * 20, // Fixed formula
      }); // return the search results
      // (difficulties):
      // async decorated function - > returns a new Promise object by default
      // The result of this Promise object is the return value in the async function
      // Get the return value of getListFn and extract the result with await
    },

Get the return value of getList and extract the result with await. Now we need to understand the meaning of page.

A page may contain up to 20 groups of data. When it slides to the bottom, there may be two situations: the data is gone, or there is more data. If there is more data, the onLoad loading method will be called to transfer the data of the next page to the front end. For example, when the back-end offset=20, it means that page=2 is the second page. At this time, it means that 20 data are offset. The whole page has Limit+offset (40) data. Therefore, we need to set a variable finished to indicate whether the loading is completed, and the variable loading to indicate whether the bottom is loading.

The variables involved are, and the method involved is asynchronous inputfn

  data() {
    return {
      value: "", // Search keywords
      hotArr: [], // Hot search keyword
      resultList: [], // search result
      loading: false, // Loading (status) - only when it is false, the onload method can be triggered automatically after reaching the bottom
      finished: false, // Not all are loaded (if it is set to true, onload will not be executed again at the bottom, which means that all loading is completed)
      page: 1, // Page number of the current search results
      timer: null // Input box - anti shake timer
    };
  }

When we mention the input box, we need to pay attention to anti shake of the input box, which is simply to make the code execute slowly. Count n seconds and execute it for the last time. If it is triggered again, count again. The effect is: the user triggers this event within n seconds before we execute the logic code. The event will be triggered n seconds after the user inputs it, not immediately (this event refers to passing the value of the input box to the back end and returning the keyword result from the back end)

async inputFn() {
      if (this.timer) clearTimeout(this.timer)
      this.timer = setTimeout(async () => {
        this.page = 1; // Click to retrieve the first page of data
        this.finished = false // Input box keyword change - there may be new data (not necessarily loading completed)
        // Input box value change
        if (this.value.length === 0) {
          // If there are no search keywords, clear the search results and prevent the network request from being sent (return in advance)
          this.resultList = [];
          return;
        }
        const res = await this.getListFn();
        console.log(res);
        // If the search result response data does not have the songs field - no data
        if (res.data.result.songs === undefined) {
          this.resultList = [];return
        }
        this.resultList = res.data.result.songs;
        this.loading = false;
      }, 900)
    },

The easiest way to anti shake is to set the timer. If the timer exists, clear the timer and reset it. When we get the data, the default is to get the first page of data, and judge whether there is any back-end return result. If not, return it directly. If so, pass the data to the relevant variables.

  • Bottom load event
    async onLoad() {
      // For the bottom event (to load the data of the next page), the internal will automatically change the loading to true
      this.page++;
      const res = await this.getListFn();
      if (
        res.data.result.songs === undefined
      ) { // No more data
        this.finished = true; // All loads are completed (the list will not trigger the onload method)
        this.loading = false; // This loading is completed
        return;
      }
      this.resultList = [...this.resultList, ...res.data.result.songs];
      this.loading = false; // Data loading completed - ensure onload can be triggered next time
    }

When the bottom is reached, the getList method will be called again to get the data from the background, but the value of the Page has changed. If there is no more data, it means that all loading has been completed. Set finished to true, and setting loading to false means that the loading is completed this time. In the future, the van list component will not trigger the load method because of the limitation of finished. If there is still data, we can use the shortcut syntax of ES6 and use the assignment operation of array.

  • Hot search keyword search

As we said above, hot search keywords will be displayed at the beginning of the page. What is the implementation principle of hot search keywords?

Call fn method

    async fn(val) {
      // Click hot search keyword
      this.page = 1; // Click to retrieve the first page of data
      this.finished = false; // Click new Keyword - there may be new data
      this.value = val; // The selected keywords are displayed in the search box
      const res = await this.getListFn();
      console.log(res);
      this.resultList = res.data.result.songs;
      this.loading = false; // After loading the data this time - the list can be loaded more
    

The other methods are similar to the above. The above is the basic business logic of the whole code.

Topics: Vue Vue.js