1. 网易云音乐的本地服务

通过代理的形式发起请求,中转给前端服务

  • 安装服务的依赖

    1. $ npm i
  • 启动服务

    1. $ npm run start

    2. 怎么看启动命令

  1. 先克隆仓库
  2. 安装依赖包- 有的公司喜欢搞自己的镜像库 npm cnpm ynpm ccpm bbpm ddpm
  3. 启动服务-先看package.json的scripts- 运行命令-打包命令-测试命令

    运行命令最有可能的几个值 serve-dev-start

3. 初始化项目

  1. 创建项目 选路由
  2. 安装axios 和vant ```bash $ npm i axios vant@latest-v2 -S
  1. 3. 安装按需引入的插件 babel-plugin-import
  2. ```bash
  3. $ npm i babel-plugin-import -D
  1. 配置按需打包
    1. plugins: [
    2. ['import', {
    3. libraryName: 'vant',
    4. libraryDirectory: 'es',
    5. style: true
    6. }, 'vant']
    7. ]
    在main.js中引入reset.css和flexble.js文件

4. 创建布局-首页-搜索-播放四个组件

  1. 路由组件应该建立在views中
  • Layout/index.vue
  • Home/index.vue
  • Search/index.vue
  • Play/index.vue

image.png
Layout/index.vue

  1. <template>
  2. <div>布局页面</div>
  3. </template>
  4. <script>
  5. export default {
  6. name: 'LayoutPage'
  7. }
  8. </script>
  9. <style scoped>
  10. .main {
  11. padding-top: 46px;
  12. padding-bottom: 50px;
  13. }
  14. </style>

Home/index.vue

<template>
  <div>
    首页
  </div>
</template>

<script>
export default {
    name: 'HomePage'

}
</script>

<style scoped>
/* 标题 */
.title {
  padding: 0.266667rem 0.24rem;
  margin: 0 0 0.24rem 0;
  background-color: #eee;
  color: #333;
  font-size: 15px;
}
/* 推荐歌单 - 歌名 */
.song_name {
  font-size: 0.346667rem;
  padding: 0 0.08rem;
  margin-bottom: 0.266667rem;
  word-break: break-all;
  text-overflow: ellipsis;
  display: -webkit-box; /** 对象作为伸缩盒子模型显示 **/
  -webkit-box-orient: vertical; /** 设置或检索伸缩盒对象的子元素的排列方式 **/
  -webkit-line-clamp: 2; /** 显示的行数 **/
  overflow: hidden; /** 隐藏超出的内容 **/
}
</style>

Search/index.vue

<template>
  <div>搜索页面</div>
</template>

<script>
export default {
  name: 'SearchPage'
}
</script>

<style scoped>
/* 搜索容器的样式 */
.search_wrap {
  padding: 0.266667rem;
}

/*热门搜索文字标题样式 */
.hot_title {
  font-size: 0.32rem;
  color: #666;
}

/* 热搜词_容器 */
.hot_name_wrap {
  margin: 0.266667rem 0;
}

/* 热搜词_样式 */
.hot_item {
  display: inline-block;
  height: 0.853333rem;
  margin-right: 0.213333rem;
  margin-bottom: 0.213333rem;
  padding: 0 0.373333rem;
  font-size: 0.373333rem;
  line-height: 0.853333rem;
  color: #333;
  border-color: #d3d4da;
  border-radius: 0.853333rem;
  border: 1px solid #d3d4da;
}
</style>

Play/index.vue-从项目中拷贝

5.路由系统的配置

 -layout<br />            - home<br />            - search<br />     - play

并不是所有文件都按需加载

import Vue from 'vue'
import VueRouter from 'vue-router'
import Layout from '@/views/Layout'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    redirect: '/layout'
  },
  {
    path: '/layout',
    component: Layout,
    redirect: '/layout/home', //一级路由和二级路由一起显示
    children: [{
     // path: '/layout/home' 完整写法
     path: 'home',
     component: () => import('@/views/Home')

    }, {
      // path: '/layout/home' 完整写法
      path: 'search',
      component: () => import('@/views/Search')
     }]
  },
  {
    path: '/play',
    component: null
  }
]

const router = new VueRouter({
  routes
})

export default router

layout中需要放置二级路由的容器

<template>
  <div>
    <router-view></router-view>
    <h1>布局页面</h1>
  </div>
</template>

6. 底部导航tabbar组件

  1. 查文档-拷贝示例-main.js ```javascript import { Tabbar, TabbarItem } from ‘vant’;

Vue.use(Tabbar); Vue.use(TabbarItem);

layout/index.vue
```html
<template>
  <div>
    <router-view></router-view>
    <van-tabbar route>
      <van-tabbar-item replace to="/layout/home" icon="home-o">首页</van-tabbar-item>
      <van-tabbar-item replace to="/layout/search" icon="search">搜索</van-tabbar-item>
    </van-tabbar>
  </div>
</template>

7. 使用Navbar作为显示标题-动态切换

  1. 使用vant的Navbar ```javascript import { NavBar } from ‘vant’

Vue.use(NavBar)


2. 在路由中定义meta属性
```javascript
{
    path: '/layout',
    component: Layout,
    redirect: '/layout/home', //一级路由和二级路由一起显示
    children: [{
     // path: '/layout/home' 完整写法
     path: 'home',
     component: () => import('@/views/Home'),
     meta: {
      // 路由提供我们放置自定义属性的
      title: "首页"
     }

    }, {
      // path: '/layout/home' 完整写法
      path: 'search',
      component: () => import('@/views/Search'),
      meta: {
        title: '搜索'
      }
     }]
  },
  1. 绑定layout/index.vue 中navbar的title属性 ```vue

<a name="bKSLV"></a>
## 8.封装统一的请求-建立独立模块
> axios原生的请求

1. 封装请求工具 utils/request.js
```javascript
import axios from 'axios'

// axios.defaults.baseURL = ""
const instance = axios.create({
    baseURL: 'http://localhost:3000', // 基础地址
    timeout: 5000  //超时时间
})
// 请求发起之前触发-统一处理参数
// instance.interceptors.request.use(() => {}, () => {}) // 请求拦截器
// 响应数据之后触发- axios默认加了一层data
// instance.interceptors.response.use(() => {}, () => {}) // 响应拦截器

export default instance
  1. 新建请求模块 api/home.js
  • 请求推荐歌单
  • 搜索数据 ```javascript import request from ‘@/utils/request’

// 获取推荐歌单 // 网易云的nodejs接口都是get类型 export function recommandMusic (params) { return request({ params, // 地址 url: ‘/personalized’ }) }


v-for="item in 数组"<br />v-for="item in 100"

<a name="e5ZXI"></a>
## 9.实现推荐歌单的渲染

1. 实现静态结构-row-col-image
```vue
<template>
  <div>
    <p class="title">推荐歌单</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>
  </div>
</template>

<script>
export default {
  name: 'HomePage',
  data() {
    return  {
      reList: []
    }
  }

}
</script>

不要忘记注册使用的组件
es6+

  • 异步编程的终结解决方案 async/await
  • async/await必须配套使用
  • 写了await必须父级函数声明async

    await 强制等待 await Promise()

    • await会强制等待后面的promise进行resolve

      让我们写同步那样去写异步

Home/index.vue

<script>
import { recommandMusic } from '@/api/home'
export default {
  name: 'HomePage',
  data() {
    return  {
      reList: []
    }
  },
  created() {
   this.getRecommandMusic()
  },
  methods: {
    // 获取推荐歌单的方法
   async getRecommandMusic() {
     const { data: { result } } =  await recommandMusic({ limit: 6 }) // await会等到它执行成功
     // 这里一定是执行成功
     //result 等价于 recommandMusic({ limit: 6 }).then(result)
    this.reList = result
   }
  }

}
</script>

10.获取最新歌曲

  1. 铺设页面结构

    <p class="title">最新音乐</p>
     <van-cell center v-for="item in 10" :key="item" title="标题" label="描述信息">
      <!-- 具名插槽 -->
      <template #right-icon>
         <van-icon name="play-circle-o" size="0.6rem" />
       </template>
     </van-cell>
    
  2. 封装获取数据的请求

    // 获取最新歌曲的列表
    export function getNewSongList (params) {
     return request({
       params, // 地址
       url:  '/personalized/newsong'
     })
    }
    

    3.调用赋值 ```javascript import { recommandMusic, getNewSongList } from ‘@/api/home’ export default { name: ‘HomePage’, data() { return { reList: [], newList: [] } }, created() { this.getRecommandMusic() this.getNewSongList() }, methods: { // 获取推荐歌单的方法 async getRecommandMusic() { const { data: { result } } = await recommandMusic({ limit: 6 }) // await会等到它执行成功 // 这里一定是执行成功 //result 等价于 recommandMusic({ limit: 6 }).then(result) this.reList = result }, async getNewSongList() { const { data: { result } } = await getNewSongList({ limit: 20 }) this.newList = result } }

}


<a name="Zn8qr"></a>
## 11. 搜索关键词的铺设

1. 先写静态结构
```vue
<template>
  <div>
    <van-search shape="round" placeholder="请输入搜索关键字"></van-search>
    <div class="search_wrap">
       <p class="hot_title">热搜关键词</p>
       <div class="hot_name_wrap">
        <span v-for="item in 100" :key="item" class="hot_item">周杰伦</span>
       </div>
    </div>
  </div>
</template>
  1. 封装接口 ```javascript import request from ‘@/utils/request’

export function getKeysWordResult(params) { return request({ params, url: ‘/search/hot’ }) }


3. 初始化的时候调用数据
```javascript
import { getKeysWordResult } from '@/api/search'
export default {
  name: 'SearchPage',
  data() {
    return {
      keyList: []
    }
  },
  created() {
    this.getKeysWordResult()
  },
  methods: {
    async getKeysWordResult() {
    const { data: { result: { hots } } } =  await getKeysWordResult()
    this.keyList = hots
    }
  }
}

12. 搜索关键字搜索内容

  1. 点击关键字将点击内容放入到搜索框里面

    <van-search v-model="keyWord" shape="round" placeholder="请输入搜索关键字"></van-search>
     <div class="search_wrap" v-if="!searchList.length">
       <p class="hot_title">热搜关键词</p>
       <div class="hot_name_wrap">
         <span @click="keyWord = item.first" v-for="(item, i) in keyList" :key="i" class="hot_item">
           {{ item.first }}
         </span>
       </div>
     </div>
    
  2. 封装了获取歌曲的接口

    export function getSearchResult(params) {
     return request({
         params,
         url: '/cloudsearch'
     })
    }
    
  3. 监听关键字的变化-获取数据-赋值给变量-渲染

    watch: {
     keyWord(newValue) {
       // 要去搜索
       this.getSearchResult(newValue)
     }
    },
    

    methods方法

    async getSearchResult(keywords) {
       const { data: { result: { songs } } } = await getSearchResult({ keywords, limit: 20 })
       this.searchList = songs
     }
    
  • 完整代码 ```vue


<a name="d3XRV"></a>
## 13.加载更多

1. 加载更多的需求是手机端的需求,手势往上拉,当列表滚动到底部一定距离的时候,

触发加载下一页的数据
> vant- antd都有触底事件-加载下一页的数据

- 完整代码
```vue
<template>
  <div>
    <van-search v-model="keyWord" shape="round" placeholder="请输入搜索关键字"></van-search>
    <div class="search_wrap" v-if="!searchList.length">
      <p class="hot_title">热搜关键词</p>
      <div class="hot_name_wrap">
        <span @click="keyWord = item.first" v-for="(item, i) in keyList" :key="i" class="hot_item">
          {{ item.first }}
        </span>
      </div>
    </div>
    <div class="search_wrap" v-else>
      <p class="hot_title">最佳匹配</p>
      <van-list 
      v-model="loading"
      :finished="finished"  
      finished-text="没有更多了"
      @load="onLoad"
      >
        <van-cell center v-for="item in searchList" :key="item.id" :title="item.name" :label="item.ar[0].name">
          <!-- 具名插槽 -->
          <template #right-icon>
            <van-icon name="play-circle-o" size="0.6rem" />
          </template>
        </van-cell>
      </van-list>

    </div>
  </div>
</template>

<script>
import { getKeysWordResult, getSearchResult } from '@/api/search'

export default {
  name: 'SearchPage',
  data() {
    return {
      keyList: [], // 热词列表
      keyWord: '',
      searchList: [], // 搜索的结果列表
      loading: false,       // 初始值必须为false 只有在false的情况下 才会触发加载事件
      finished: false,
      page: 1 // 页码 
      // 是不是已经将所有数据加载完毕
    }
  },
  watch: {
    keyWord(newValue) {
      // 要去搜索
      this.getSearchResult(newValue)
    }
  },
  created() {
    this.getKeysWordResult()
  },
  methods: {
    async getKeysWordResult() {
      const { data: { result: { hots } } } = await getKeysWordResult()
      this.keyList = hots
    },
    async getSearchResult(keywords) {
      const { data: { result: { songs } } } = await getSearchResult({ keywords, limit: 20, offset: (this.page - 1) * 20 })
      this.searchList.push(...songs)
    },
    // 虽然触发了onload事件 loading= true
   async onLoad() {
      this.page++
      await this.getSearchResult(this.keyWord)
      // 此时认为执行完毕
      this.loading = false
    }
  }
}
</script>

<style scoped>
/* 搜索容器的样式 */
.search_wrap {
  padding: 0.266667rem;
}

/*热门搜索文字标题样式 */
.hot_title {
  font-size: 0.32rem;
  color: #666;
}

/* 热搜词_容器 */
.hot_name_wrap {
  margin: 0.266667rem 0;
}

/* 热搜词_样式 */
.hot_item {
  display: inline-block;
  height: 0.853333rem;
  margin-right: 0.213333rem;
  margin-bottom: 0.213333rem;
  padding: 0 0.373333rem;
  font-size: 0.373333rem;
  line-height: 0.853333rem;
  color: #333;
  border-color: #d3d4da;
  border-radius: 0.853333rem;
  border: 1px solid #d3d4da;
}
</style>