1.11 网易云音乐-搜索-热搜关键字
目标: 完成热搜关键字铺设
搜索框 – van-search组件
api/Search.js – 热搜关键字 - 接口方法
Search/index.vue引入-获取热搜关键字 - 铺设页面
点击文字填充到输入框
- 准备搜索界面标签
<template> <div> <van-search shape="round" placeholder="请输入搜索关键词" /> <!-- 搜索下容器 --> <div class="search_wrap"> <!-- 标题 --> <p class="hot_title">热门搜索</p> <!-- 热搜关键词容器 --> <div class="hot_name_wrap"> <!-- 每个搜索关键词 --> <span class="hot_item" >热搜关键字</span > </div> </div> </div> </template> <script> export default {} </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; } /* 给单元格设置底部边框 */ .van-cell { border-bottom: 1px solid lightgray; } </style>
- api/Search.js - 定义热门搜索接口方法和搜索结果方法
import request from '@/utils/request' // 热搜关键字 export const hotSearch = () => request({ url: '/search/hot' }) // 搜索结果列表 export const searchResult = params => request({ url: '/cloudsearch', params })
- api/index.js - 导入使用并统一导出
// 统一出口 // 你也可以在逻辑页面里.vue中直接引入@/api/Home下的网络请求工具方法 // 为什么: 我们可以把api所有的方法都统一到一处. import {recommendMusic, hotMusic} from '@/api/Home' import {hotSearch, searchResult} from '@/api/Search' export const recommendMusicAPI = recommendMusic // 把网络请求方法拿过来 导出 export const hotMusicAPI = hotMusic // 把获取最新音乐的, 网络请求方法导出 export const hotSearchAPI = hotSearch // 热搜 export const searchResultAPI = searchResult // 搜索结果
- created中请求接口-拿到热搜关键词列表
<!-- 每个搜索关键词 --> <span class="hot_item" v-for="(obj, index) in hotArr" :key="index" >{{ obj.first }}</span> <script> // 目标: 铺设热搜关键字 // 1. 搜索框van-search组件, 关键词标签和样式 // 2. 找接口, api/Search.js里定义获取搜索关键词的请求方法 // 3. 引入到当前页面, 调用接口拿到数据循环铺设页面 // 4. 点击关键词把值赋予给van-search的v-model变量 import { hotSearchAPI } from "@/api"; export default { data(){ return { hotArr: [], // 热搜关键字 } }, async created() { const res = await hotSearchAPI(); console.log(res); this.hotArr = res.data.result.hots; }, } </script>
- 点击热词填充到输入框
<van-search shape="round" v-model="value" placeholder="请输入搜索关键词" /> <!-- 每个搜索关键词 --> <span class="hot_item" v-for="(obj, index) in hotArr" :key="index" @click="fn(obj.first)" >{{ obj.first }}</span > </div> <script> export default { data(){ return { value: "", hotArr: [], // 热搜关键字 } }, // ...省略了created methods: { async fn(val) { // 点击热搜关键词 this.value = val; // 选中的关键词显示到搜索框 }, } } </script>
总结: 写好标签和样式, 拿到数据循环铺设, 点击关键词填入到van-search中
1.12 网易云音乐-搜索-点击热词-搜索结果
目标: 点击热词填充到输入框-出搜索结果
api/Search.js - 搜索结果, 接口方法
Search/index.vue引入-获取搜索结果 - 铺设页面
和热搜关键字容器 – 互斥显示
点击文字填充到输入框, 请求搜索结果铺设
- 搜索结果显示区域标签+样式(直接复制/vant文档找)
<!-- 搜索结果 --> <div class="search_wrap"> <!-- 标题 --> <p class="hot_title">最佳匹配</p> <van-cell center title='结果名字' > <template #right-icon> <van-icon name="play-circle-o" size="0.6rem"/> </template> </van-cell> </div>
- 点击 - 获取搜索结果 - 循环铺设页面
<template> <div> <van-search shape="round" v-model="value" placeholder="请输入搜索关键词" /> <!-- 搜索下容器 --> <div class="search_wrap"> <!-- 标题 --> <p class="hot_title">热门搜索</p> <!-- 热搜关键词容器 --> <div class="hot_name_wrap"> <!-- 每个搜索关键词 --> <span class="hot_item" v-for="(obj, index) in hotArr" :key="index" @click="fn(obj.first)" >{{ obj.first }}</span > </div> </div> <!-- 搜索结果 --> <div class="search_wrap"> <!-- 标题 --> <p class="hot_title">最佳匹配</p> <van-cell center v-for="obj in resultList" :key="obj.id" :title="obj.name" :label="obj.ar[0].name + ' - ' + obj.name" > <template #right-icon> <van-icon name="play-circle-o" size="0.6rem"/> </template> </van-cell> </div> </div> </template> <script> // 目标: 铺设热搜关键字 // 1. 搜索框van-search组件, 关键词标签和样式 // 2. 找接口, api/Search.js里定义获取搜索关键词的请求方法 // 3. 引入到当前页面, 调用接口拿到数据循环铺设页面 // 4. 点击关键词把值赋予给van-search的v-model变量 // 目标: 铺设搜索结果 // 1. 找到搜索结果的接口 - api/Search.js定义请求方法 // 2. 再定义methods里getListFn方法(获取数据) // 3. 在点击事件方法里调用getListFn方法拿到搜索结果数据 // 4. 铺设页面(首页van-cell标签复制过来) // 5. 把数据保存到data后, 循环van-cell使用即可(切换歌手字段) // 6. 互斥显示搜索结果和热搜关键词 import { hotSearchAPI, searchResultListAPI } from "@/api"; export default { data() { return { value: "", hotArr: [], // 热搜关键字 resultList: [], // 搜索结果 }; }, async created() { const res = await hotSearchAPI(); console.log(res); this.hotArr = res.data.result.hots; }, methods: { async getListFn() { return await searchResultListAPI({ keywords: this.value, limit: 20, }); // 把搜索结果return出去 // (难点): // async修饰的函数 -> 默认返回一个全新Promise对象 // 这个Promise对象的结果就是async函数内return的值 // 拿到getListFn的返回值用await提取结果 }, async fn(val) { // 点击热搜关键词 this.value = val; // 选中的关键词显示到搜索框 const res = await this.getListFn(); console.log(res); this.resultList = res.data.result.songs; }, }, }; </script>
- 互斥显示, 热搜关键词和搜索结果列表
总结: 点击热词后, 调用接口传入关键词, 返回数据铺设
1.13 网易云音乐-输入框-搜索结果
目标: 监测输入框改变-拿到搜索结果
观察van-search组件是否支持和实现input事件
绑定@input事件和方法
在事件处理方法中获取对应的值使用
如果搜索不存在的数据-要注意接口返回字段不同
- 绑定@input事件在van-search上
<van-search shape="round" v-model="value" placeholder="请输入搜索关键词" @input="inputFn"/>
- 实现输入框改变 - 获取搜索结果铺设
async inputFn() { // 输入框值改变 if (this.value.length === 0) { // 搜索关键词如果没有, 就把搜索结果清空阻止网络请求发送(提前return) this.resultList = []; return; } const res = await this.getListFn(); console.log(res); // 如果搜索结果响应数据没有songs字段-无数据 if (res.data.result.songs === undefined) { this.resultList = []; return; } this.resultList = res.data.result.songs; },
总结: 监测输入框改变-保存新的关键词去请求结果回来铺设
1.14 网易云音乐-搜索结果-加载更多
目标: 触底后, 加载下一页数据
观察接口文档: 发现需要传入offset和分页公式
van-list组件监测触底执行onload事件
配合后台接口, 传递下一页的标识
拿到下一页数据后追加到当前数组末尾即可
- 设置van-list组件实现相应的属性和方法, 让page++去请求下页数据
<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad" > <van-cell center v-for="obj in resultList" :key="obj.id" :title="obj.name" :label="obj.ar[0].name + ' - ' + obj.name" > <template #right-icon> <van-icon name="play-circle-o" size="0.6rem" /> </template> </van-cell> </van-list> <script> // 目标: 加载更多 // 1. 集成list组件-定义相关的变量(搞懂变量的作用) -监测触底事件 // 2. 一旦触底, 自动执行onload方法 // 3. 对page++, 给后台传递offset偏移量参数-请求下一页的数据 // 4. 把当前数据和下一页新来的数据拼接起来用在当前 页面的数组里 // (切记) - 加载更多数据后,一定要把loading改成false, 保证下一次还能触发onload方法 export default { data() { return { value: "", hotArr: [], // 热搜关键字 resultList: [], // 搜索结果 loading: false, // 加载中 (状态) - 只有为false, 才能触底后自动触发onload方法 finished: false, // 未加载全部 (如果设置为true, 底部就不会再次执行onload, 代表全部加载完成) page: 1, // 当前搜索结果的页码 }; }, // ...省略其他 methods: { async getListFn() { return await searchResultListAPI({ keywords: this.value, limit: 20, offset: (this.page - 1) * 20, // 固定公式 }); // 把搜索结果return出去 // (难点): // async修饰的函数 -> 默认返回一个全新Promise对象 // 这个Promise对象的结果就是async函数内return的值 // 拿到getListFn的返回值用await提取结果 }, async onLoad() { // 触底事件(要加载下一页的数据咯), 内部会自动把loading改为true this.page++; const res = await this.getListFn(); this.resultList = [...this.resultList, ...res.data.result.songs]; this.loading = false; // 数据加载完毕-保证下一次还能触发onload }, }, }; </script>
总结: list组件负责UI层监测触底, 执行onload函数, page++, 请求下页数据, 和现在数据合并显示更多, 设置loading为false, 确保下次触底还能执行onLoad
1.15 网易云音乐-加载更多-bug修复
目标: 如果只有一页数据/无数据判断
无数据/只有一页数据, finished为true
防止list组件触底再加载更多
还要测试-按钮点击/输入框有数据情况的加载更多
正确代码
async fn(val) { // 点击热搜关键词 + this.finished = false; // 点击新关键词-可能有新的数据 this.value = val; // 选中的关键词显示到搜索框 const res = await this.getListFn(); console.log(res); this.resultList = res.data.result.songs; + this.loading = false; // 本次数据加载完毕-才能让list加载更多 }, async inputFn() { + this.finished = false // 输入框关键字改变-可能有新数据(不一定加载完成了) // 输入框值改变 if (this.value.length === 0) { // 搜索关键词如果没有, 就把搜索结果清空阻止网络请求发送(提前return) this.resultList = []; return; } const res = await this.getListFn(); console.log(res); + // 如果搜索结果响应数据没有songs字段-无数据 + if (res.data.result.songs === undefined) { + this.resultList = []; + return; + } this.resultList = res.data.result.songs; + this.loading = false; }, async onLoad() { // 触底事件(要加载下一页的数据咯), 内部会自动把loading改为true this.page++; const res = await this.getListFn(); + if (res.data.result.songs === undefined) { // 没有更多数据了 + this.finished = true; // 全部加载完成(list不会在触发onload方法) + this.loading = false; // 本次加载完成 + return; + } this.resultList = [...this.resultList, ...res.data.result.songs]; + this.loading = false; // 数据加载完毕-保证下一次还能触发onload },
总结: 在3个函数 上和下, 设置finished还未完成, 最后要把loading改成false, 判断songs字段, 对这里的值要非常熟悉才可以
1.16 网易云音乐-输入框-防抖
目标: 输入框触发频率过高
输入框输入"asdfghjkl"
接着快速的删除
每次改变-马上发送网络请求
网络请求异步耗时 – 数据回来后还是铺设到页面上
解决:
引入防抖功能
async inputFn() { // 目标: 输入框改变-逻辑代码-慢点执行 // 解决: 防抖 // 概念: 计时n秒, 最后执行一次, 如果再次触发, 重新计时 // 效果: 用户在n秒内不触发这个事件了, 才会开始执行逻辑代码 if (this.timer) clearTimeout(this.timer); this.timer = setTimeout(async () => { this.finished = false; // 输入框关键字改变-可能有新数据(不一定加载完成了) // 输入框值改变 if (this.value.length === 0) { // 搜索关键词如果没有, 就把搜索结果清空阻止网络请求发送(提前return) this.resultList = []; return; } const res = await this.getListFn(); console.log(res); // 如果搜索结果响应数据没有songs字段-无数据 if (res.data.result.songs === undefined) { this.resultList = []; return; } this.resultList = res.data.result.songs; this.loading = false; }, 900); },
总结: 降低函数执行频率
1.17 网易云音乐-页码bug修复
目标: 第一个关键词page已经+到了10, 再第二个关键词应该从1开始
加载更多时, page已经往后计数了
重新获取时, page不是从第一页获取的
点击搜索/输入框搜索时, 把page改回1
代码如下:
async fn(val) { // 点击热搜关键词 + this.page = 1; // 点击重新获取第一页数据 this.finished = false; // 点击新关键词-可能有新的数据 this.value = val; // 选中的关键词显示到搜索框 const res = await this.getListFn(); console.log(res); this.resultList = res.data.result.songs; this.loading = false; // 本次数据加载完毕-才能让list加载更多 }, async inputFn() { // 目标: 输入框改变-逻辑代码-慢点执行 // 解决: 防抖 // 概念: 计时n秒, 最后执行一次, 如果再次触发, 重新计时 // 效果: 用户在n秒内不触发这个事件了, 才会开始执行逻辑代码 if (this.timer) clearTimeout(this.timer); this.timer = setTimeout(async () => { + this.page = 1; // 点击重新获取第一页数据 this.finished = false; // 输入框关键字改变-可能有新数据(不一定加载完成了) // 输入框值改变 if (this.value.length === 0) { // 搜索关键词如果没有, 就把搜索结果清空阻止网络请求发送(提前return) this.resultList = []; return; } const res = await this.getListFn(); console.log(res); // 如果搜索结果响应数据没有songs字段-无数据 if (res.data.result.songs === undefined) { this.resultList = []; return; } this.resultList = res.data.result.songs; this.loading = false; }, 900); },
总结: 切换时, 让page页面回到1
1.18 网易云音乐-Layout边距优化
目标: 上下导航会盖住中间内容
我们的头部导航和底部导航挡住了中间内容
给中间路由页面设置上下内边距即可
在Layout/index.vue中
/* 中间内容区域 - 容器样式(留好上下导航所占位置) */ .main { padding-top: 46px; padding-bottom: 50px; }
1.19 网易云音乐-SongItem封装
目标: 把首页和搜索结果的歌曲cell封装起
创建src/components/SongItem.vue
<template> <van-cell center :title="name" :label="author + ' - ' + name"> <template #right-icon> <van-icon name="play-circle-o" size="0.6rem"/> </template> </van-cell> </template> <script> export default { props: { name: String, // 歌名 author: String, // 歌手 id: Number, // 歌曲id (标记这首歌曲-为将来跳转播放页做准备) } }; </script> <style scoped> /* 给单元格设置底部边框 */ .van-cell { border-bottom: 1px solid lightgray; } </style>
Home/index.vue - 重构
注意: author字段不同
<SongItem v-for="obj in songList" :key="obj.id" :name="obj.name" :author="obj.song.artists[0].name" :id="obj.id" ></SongItem>
Search/index.vue - 重构
注意: author字段不同
<SongItem v-for="obj in resultList" :key="obj.id" :name="obj.name" :author="obj.ar[0].name" :id="obj.id" ></SongItem>
总结: 遇到重复标签要封装
1.20 网易云音乐-播放音乐
目标: 从预习资料拿到播放的api和页面, 配置好路由规则
这个页面不用写, 直接用
组件SongItem里 – 点击事件
api/Play.js – 提前准备好 – 接口方法
跳转到Play页面 – 把歌曲id带过进去
在SongItem.vue - 点击播放字体图标
methods: { playFn(){ this.$router.push({ path: '/play', query: { id: this.id // 歌曲id, 通过路由跳转传递过去 } }) } }
总结: 准备好播放页, 点击播放传歌曲id过去, 到播放页-再请求响应数据和歌曲地址用audio标签播放
1.21 网易云音乐-vant适配
目标: 切换不同机型, 刷新后看看标签大小适配吗
- postcss – 配合webpack翻译css代码
- postcss-pxtorem – 配合webpack, 自动把px转成rem
- 新建postcss.config.js – 设置相关配置
- 重启服务器, 再次观察Vant组件是否适配
- 下载postcss和postcss-pxtorem@5.1.1
postcss作用: 是对css代码做降级处理
postcss-pxtorem: 自动把所有代码里的css样式的px, 自动转rem - src/新建postcss.config.js
module.exports = { plugins: { 'postcss-pxtorem': { // 能够把所有元素的px单位转成Rem // rootValue: 转换px的基准值。 // 例如一个元素宽是75px,则换成rem之后就是2rem。 rootValue: 37.5, propList: ['*'] } } }
以iphone6为基准, 37.5px为基准值换算rem
今日总结
- 掌握vant组件库的使用 - 找组件, 引组件, 用组件
- 能够对vant组件自带样式进行覆盖自定义
- 遇到重复的标签, 自己也封装了一个复用的组件
- 掌握查询文档和使用每个属性的方式