内容均为《Vue2.0开发企业级移动端音乐Web App 》学习笔记
包括search-box 组件开发、热门搜索数据抓取和应用、suggest 组件开发、搜索结果保存功能实现、search-list 组件开发、confirm 组件开发。
10-1 搜索页面页面布局和功能介绍
10-2 搜索页面search-box组件开发
base\search-Box\search-Box.vue 多个页面的搜索部分是相似的,我们将它抽离出来放在base里面
<div class="search-box-wrapper">
<div class="search-box">
<i class="icon-search"></i>
<input :placeholder="placeholder" class="box" v-model="query">
<i class="icon-dismiss" v-show="query" @click="clear"></i>
</div>
</div>
复制代码
不同的页面的placeholder不一致的,所以我们需要props placeholder 。 判断query的长度,当query有值的才展示清除按钮,当点击的清除按钮,清除文本框的值。
<script>
export default {
props:{
placeholder:{type:String,default:'搜索歌曲、歌手'}
},
data(){
return{
query:''
}
},
methods:{
clear(){
this.query=''
}
}
}
</script>
复制代码
10-3 搜索页面热门搜索数据抓取和应用
在search\search.vue,引入search-box组件
热门搜索布局
<!--搜索框没有搜索关键之的展示-->
<div class="shortcut-wrapper" v-show="!query">
<div class="shortcut">
<div class="hot-key" style="pointer-events: auto;">
<h1 class="title">热门搜索</h1>
<ul >
<li class="item"><span >该死的温柔 </span></li>
<li class="item"><span >不能说的秘密 </span></li>
</ul>
</div>
</div>
</div>
复制代码
定义请求 api\search.js
import {options,commonParams} from '../api/config';
import jsonp from '../common/js/jsonp';
export function getHotKey(params) {
let url='https://c.y.qq.com/splcloud/fcgi-bin/gethotkey.fcg';
let data=Object.assign({},commonParams,{
g_tk: '1928093487',
inCharset: 'utf-8',
outCharset: 'utf-8',
notice: 0,
format: 'jsonp',
uin: 0,
needNewCode: 1,
platform:'h5',
jsonpCallback: 'jp0',
})
return jsonp(url,data,options)
}
复制代码
search\search.vue 发送请求
data() {
return {
hotKey:[]
}
},
created(){
this._getHotKey()
},
methods:{
_getHotKey(){
getHotKey().then(res=>{
if(ERR_OK==res.code){
this.hotKey=res.data.hotkey.slice(0,10);//取得前面十条数据
}
})
}
}
复制代码
渲染数据
<!-- 搜索框没有搜索关键之的展示-->
<div class="shortcut-wrapper" v-show="!query">
<div class="shortcut">
<div class="hot-key">
<h1 class="title">热门搜索</h1>
<ul v-show="hotKey">
<li class="item" v-for="item in hotKey" :key="item.n"><span >{{item.k}} </span></li>
</ul>
</div>
</div>
</div>
复制代码
当点击数据的热门搜索的列表项,我们希望在搜索框填入搜索关键字
<li class="item" v-for="item in hotKey" :key="item.n" @click="selectItem(item)"><span>{{item.k}} </span></li>
复制代码
selectItem(item){
//那么我们需要像searchBox传递我们想要搜索的值
this.$refs.search.set_query(item.k);
}
复制代码
在search-Box.vue中定义改变query的方法
set_query(query){
this.query=query
}
复制代码
一旦query发生变化的时候,我们需要发送请求,我们需要在search-Box.vue中向外派发query。search-Box.vue只负责自身的功能,当外部需要数据时,向外暴露力所能及的基本的数据,不对自身进行额外的操作(思考:为什么不在watch中监听query的变化,而是在created中watch数据的变化?)
//在create中watch query的变化
created() {
this.$watch('query', (newQuery) => {
this.$emit('query', newQuery)
})
}
复制代码
在search.vue中接收query
<search-box ref="search" @query="onQueryChange"></search-box>
复制代码
onQueryChange(query){
//根据得到的关键字
this.query=query
},
复制代码
10-4 搜索页面suggest组件开发
因为搜索的列表页 和 添加歌曲到列表 特别相似。我们将这个列表抽取。做suggest组件。 在search页面中引入suggest.vue组件,因为suggest.vue组件的结果是依赖 query 的,所以我们在获取到search-Box.vue的query的值的时候,同时也把query传给suggest.vue
<!-- 搜索框有关键字的展示 -->
<div class="search-result" v-show="query">
<suggest :query="query" :showSinger="showSinger"></suggest>
</div>
复制代码
suggest.vue 页面布局
<div class="suggest">
<ul class="suggest-list">
<li class="suggest-item" v-for="(item,index) in list" :key="index">
<div class="icon">
<i :class="getIconCls(item)"></i>
</div>
<div class="name">
<p class="text" v-html="getDisplayName(item)"></p>
</div>
</li>
</ul>
</div>
复制代码
定获取搜索列表的api
import axios from 'axios'
复制代码
//列表的搜索处理(需要传入参数 keyworld:关键字,pageIndex:当前第几页,pageSize:一页多少条数据,isShowSinger是否需要展示歌手),
export function search(keyworld,pageIndex,pageSize,isShowSinger){
const url = '/api/search'
let data=Object.assign({},commonParams,{
w: keyworld,
p: pageIndex,
perpage:pageSize,
n:pageSize,
catZhida: isShowSinger? 1 : 0,
g_tk: '1928093487',
inCharset:' utf-8',
outCharset: 'utf-8',
notice: 0,
format: 'json',
zhidaqu: 1,
t: 0,
flag: 1,
ie: 'utf-8',
sem: 1,
aggr: 0,
remoteplace: 'txt.mqq.all',
uin: 0,
needNewCode: 1,
platform: 'h5',
})
return new Promise(function(resolve,reject){
axios.get(url,{params:data}).then(res=>{
resolve(res.data)
}).catch(err=>{
reject(err)
})
})
}
复制代码
配置代理(config\index.js)
//根据关键字获取到搜索列表,做跨域处理
'/api/search':{
target:'https://c.y.qq.com/soso/fcgi-bin/search_for_qq_cp',
secure:false,
changeOrigin:true,
bypass:function(req,proxyOptions){
req.headers.referer='https://y.qq.com/m/index.html',
req.headers.origin='https://y.qq.com'
},
pathRewrite:{
'^/api/search': ''
}
}
复制代码
suggest\suggest.vue
引入search
import {search} from '../../api/search'
复制代码
定义一个常量表示页展多少条
const pageSize = 20 //抓取数据一页有多少数据
复制代码
在页面上维护一list和当前的页码
data(){
return{
pageIndex:1,
list:[]//当前的列表的数据
}
},
复制代码
需要父组件传递 搜索的值,并且需要父组件传递是否需要展示歌手
props:{
query:{
type:String,
default:''
},
showSinger:{
type:Boolean,
default:false
}
},
复制代码
监听query的变化并且发起请求
watch: {
query(newQuery) {
//监听到query的变化,发送请求
this.search(newQuery);
}
}
复制代码
const SINGER_TYPE="singer"//定义一个代表当前类型的常量,判断是歌手还是歌曲
import {filterSinger} from '../../common/js/song'//引用filterSinger方法,做文本拼接
methods:{
//发送请求
search(newQuery){
//关键字,当前页(在data中维护当前页),一页多少条,是否展示歌手(根据props传递过来)
search(newQuery,this.pageIndex,pageSize,this.showSinger).then(res=>{
if(res.code==ERR_OK){
this.list=this.getResult(res.data)
}
})
},
getResult(data){
let ret=[];
if(data.zhida && data.zhida.singerid){
//这里用展开运算符,相当于pbject.assgin
ret.push({...data.zhida,...{type:SINGER_TYPE}})
}
if(data.song){
ret=ret.concat(data.song.list);
}
return ret
},
//做图标处理
getIconCls(item){
if(item.type==SINGER_TYPE){
return 'icon-mine'
}else{
return 'icon-music'
}
},
//做文本处理
getDisplayName(item){
if(item.type === SINGER_TYPE) {
return item.singername
}else{
return `${item.songname}-${filterSinger(item.singer)}`
}
}
},
复制代码
优化 我们在做文本处理的时候用了filterSinger函数,并且将其在common/js/song中暴露了出来,但是filterSinger函数是 createSong 函数的一个内部函数,我们不应该将其暴露出来,这里我们将 getResult处理一下
import createSong from '../../common/js/song'
import {getMusicVkey} from '../../api/singer'
复制代码
getResult(data){
let ret=[];
if(result.data.zhida && result.data.zhida.singerid){
this.list.push({...result.data.zhida,...{type:SINGER_TYPE}});
}
if(data.song){
//将data.song.list转化成一个歌曲类
ret=ret.concat(this._normallizeSong(data.song.list));//将两个数组进行合并
}
return ret
},
//将data.song.list转化成一个歌曲类
_normallizeSong(list){
let ret=[];
list.forEach(item => {
if(item.songid && item.songmid){
//异步2
getMusicVkey(item.songmid).then(res=>{
if(res.code==ERR_OK){
ret.push(createSong(item,res.data.items[0].vkey));
}
})
}
});
//在这里我们出现了一个问题,我们得到了一个song类的集合,但是ret的长度却是为[]
//是因为_normallizeSong的list是异步获取的,而ret 也是经过异步获取的
console.log(ret);
return ret
},
//做图标处理
getIconCls(item){
if(item.type==SINGER_TYPE){
return 'icon-mine'
}else{
return 'icon-music'
}
},
复制代码
这里我们得到了一个song类的集合,但是集合的长度却是0,这是因为异步内使用异步 导致数组操作不能执行。歌曲数组数据是异步获取的,其中处理数据时拼接播放源url的vkey也是异步获取的,异步内部再异步导致
现在我们用async,await 来解决这个问题,当请求完成的时候才concat 原来的list(在forEach 用await是有问题的,我们用for of),现在我们去到 getResult,重写search
//发送请求
async search(newQuery){
this.pageIndex=1;
this.list=[];
//关键字,当前页(在data中维护当前页),一页多少条,是否展示歌手(根据props传递过来)
let result=await search(newQuery,this.pageIndex,PAGESIZE,this.showSinger)
if(result.code==ERR_OK){
if(result.data.zhida && result.data.zhida.albumid){
this.list.push({...result.data.zhida,...{type:SINGER_TYPE}});
}
// console.log(result.data.song.list)
if(result.data.song.list){
let res=await this._nomalizeSong(result.data.song.list);
this.list=this.list.concat(res)
this._checkMore(result.data.song);
}
}
},
//将data.song.list转化成一个歌曲类
async _nomalizeSong(list){
let ret=[];
for(let item of list){
if(item.songid && item.songmid){
let result=await getMusicVkey(item.songmid);
if(result.code==ERR_OK){
ret.push(createSong(item,result.data.items[0].vkey))
}
}
}
return ret
},
//做文本处理
getDisplayName(item){
if(item.type === SINGER_TYPE) {
return item.singername
}else{
return `${item.name}-${item.singer}`
}
},
复制代码
至此,页面列表可以正常显示了
上拉加载功能
1、扩展scroll组件,在scroll组件上接收pullup参数。当pullup 为ture的时候,派发一个scrollEnd事件。告诉外部组件,已经滚动到底部了(scroll\scroll.vue)
//是否需要上拉刷新
pullup:{
type:Boolean,
default:false
}
复制代码
2、在初始化_initScroll的时候,判断是否需要派发scrollToEnd事件
//判断是否需要上拉刷新
if(this.pullup){
this.scroll.on('scrollEnd',(pos)=>{
if(this.scroll.y<=(this.scroll.maxScrollY+50)){//当滑动到底部50px处
this.$emit('scrollToEnd');//向外暴露一个scrollEnd 事件
}
})
}
复制代码
3、引入scroll组件,替换掉根元素div
<scroll class="suggest" :data="result" :pullup="pullup" @scrollToEnd="searchMore" ref="suggest">
复制代码
4、在data中维护一个hasMore表示是否还有更多数据,定义searchMore函数和 _checkMore函数。searchMore函数用于下拉异步获取到更多的数据, _checkMore函数用于实时hasMore的值,当hasMore为false的时候,就无法继续上拉加载更多了。
async searchMore(){//加载更多
if(!this.hasMore){
return
}
this.pageIndex++;
//关键字,当前页(在data中维护当前页),一页多少条,是否展示歌手(根据props传递过来)
let result=await search(this.query,this.pageIndex,PAGESIZE,this.showSinger)
if(result.code==ERR_OK){
if(result.data.song.list){
let res=await this._nomalizeSong(result.data.song.list);
this.list=this.list.concat(res)
this._checkMore(result.data.song);
}
}
},
_checkMore(data){
// console.log("当前页:"+data.curpage,"当前条数"+data.curnum,'总数'+data.totalnum);
if(!data.list || (data.curpage-1)*PAGESIZE+data.curnum>=data.totalnum){
this.hasMore=false
}else{
this.hasMore=true
}
}
复制代码
暂无数据处理
做暂无数据展示,我们可以在base里面扩展 一个no-result的组件,在suggest\suggest.vue中引入
<!-- 展示暂无数据-->
<div v-show="!hasMore && !list.length" class="no-result-wrapper">
<noResult title="暂无数据"></noResult>
</div>
复制代码
点击歌手列
在search组件上添加router-view标签,给search组件配置子路由。
{
path:'/search',
component:search,
name:'搜索',
children:[
{
path:'/search/:id',
component:singerDetail //指向歌曲详情页
}
]
},
复制代码
当点击歌手列的时候,改变当前路由,并且修改vuex的singer状态
<li class="suggest-item" v-for="(item,index) in list" :key="index" @click="selectItem(item)">
复制代码
selectItem(item){
if(item.type===SINGER_TYPE){
//跳转到当前的路由
let singer=new Singer({
name:item.singername,
id:item.singermid
})
//改变vuex的 singer
this.set_singer(singer);
this.$router.push({
path:`/search/${singer.id}`
});
}
}
复制代码
点击歌曲列
点击歌曲列,在selectItem时,当点击的是歌曲的时候,this.insert_song(item); suggest\suggest.vue
...mapActions(['insert_song'])
复制代码
selectItem(item){
if(item.type===SINGER_TYPE){
//省略...
}else{
//需要改变vuex的playlist sequenceList currentIndex
//因为需要改变多个vuex的state。我们需要将这些mutation封装在一个actions里面
this.insert_song(item);
}
}
复制代码
在当前的播放列表中加入该歌曲,播放默认列表中加入该歌曲,改变当前的播放的索引,展示播放页面,播放歌曲。因为我们需要改变多个vuex的属性,所以在这里我们使用actions
actions.js
function findIndex(list, song) {
return list.findIndex((item) => {
return item.id === song.id
})
}
//在当前列表插入一首歌曲
export const insert_song=function({commit,state},song){
let playlist=state.playlist.slice(0);//当前的播放的列表
let sequenceList=state.sequenceList.slice(0);//当前的播放默认的列表
let currentIndex=state.currentIndex;//当前的播放歌曲的索引
let playSong=playlist[currentIndex]//当前的播放的歌曲
let findplayIndex=findIndex(playlist,song);//判断播放列表中是否有这首歌
currentIndex++;//因为长度多了一首,让当前的索引++
playlist.splice(currentIndex,0,song);//在当前的播放列表的位置插入一首歌
//当播放列表中有这首歌,做删除重复歌曲操作
if(findplayIndex >-1 ){
//当播放列表中有这首歌
if(currentIndex>findplayIndex){
playlist.splice(findplayIndex,1)
currentIndex--
}else{
playlist.splice(findplayIndex+1,1)
}
}
//需要插入的位置
let sequenceIndex=findIndex(sequenceList,playSong)+1;
//判断是否有这首歌
let findSequenceIndex=findIndex(sequenceList,song);
sequenceList.splice(sequenceIndex,0,song)//插入一首歌
if(findSequenceIndex>-1){//存在重复的歌曲
if(findSequenceIndex < sequenceIndex){
sequenceList.splice(findSequenceIndex,1);
}else{
sequenceList.splice(findSequenceIndex+1,1)
}
}
//commit提交更改
commit(types.SET_PLAYLIST,playlist)
commit(types.SET_SEQUENCELIST,sequenceList)
commit(types.SET_CURRENTINDEX,currentIndex)
commit(types.SET_PLAYING, true)
commit(types.SET_FULLSCREEN, true)
}
复制代码
节流
优化,当我们回退的时候,query在变化,就导致了页面不断地进行异步请求,在这里我们做节流处理,找到改变query的根源,做节流处理 common->js->util.js中:定义截流函数
//截流
//对一个函数做截流,就会返回新的函数,新函数是在延迟执行原函数
//如果很快的多次调用新函数,timer会被清空,不能多次调用原函数,实现截流
export function debounce(func, delay){
let timer
return function (...args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
}
复制代码
search-Box\search-Box.vue(从根本解决问题,在watch中使用节流函数)
//在create中watch query的变化
created() {
this.$watch('query',debounce((newQuery) => {
this.$emit('query', newQuery)
},300))
}
复制代码
手机端焦点处理
当我们在文本框输入内容的时候,在手机端会出现一个软键盘,而我们想要当我们滑动的时候,软键盘消失,即文本框失去焦点,所以我们需要在滚动的时候,监听到滚动的是否开始了,当滚动开始的时候,向外派发一个beforeScrollStart事件
对scroll进行扩展 scroll\scroll.vue 由外部传入的isBeforeScroll来确定是否需要向外派发beforeScrollStart事件
//一旦开始滚动列表的时候,是否需要向外部派发beforeScrollStart事件
isBeforeScroll:{
type:Boolean,
default:false
}
复制代码
在初始化方法_initScroll中做判断
//是否向外派发一个beforeScrollStart事件
if(this.isBeforeScroll){
this.scroll.on('beforeScrollStart',()=>{
this.$emit('beforeScrollStart')
})
}
复制代码
suggest\suggest.vue。传入isBeforeScroll和接收beforeScrollStart事件
<scroll class="suggest" :data="list" @scrollToEnd='searchMore' :pullup="pullup" :isBeforeScroll="isBeforeScroll" :beforeScrollStart="beforeScrollStart" >
复制代码
在data中维护isBeforeScroll
isBeforeScroll:true,//是否需要滚动的时候派发滚动开始事件
复制代码
因为suggest.vue只做自己相关的逻辑,我们不在这个组件上做 input框失去焦点的操作,所以我们得到子组件派发的beforeScrollStart之后,继续向上派发事件给search组件
beforeScrollStart(){
//一旦滚动开始的时候,让search的input失去焦点
this.$emit('beforeScrollStart');
}
复制代码
search\search.vue 在search组件上获取的到search-Box.vue组件,并且调用search-Box.vue的nputBlur()失去焦点方法
//滚动的时候让input框失去焦点
beforeScrollStart(){
this.$refs.search.inputBlur()
},
复制代码
search-Box\search-Box.vue
inputBlur(){
this.$refs.query.blur();
}
复制代码
10-5 搜索页面搜索结果保存功能实现
除了搜索页面的搜索历史在其他页面也是有搜索历史的(添加歌曲到列表页面),多个组件共用搜索历史,我们可以将搜索历史放在vuex里面做处理 store\state.js
searchHistory:[],//存搜索历史
复制代码
store\getters.js
//暴露出searchHistory
export const searchHistory=(state)=>state.searchHistory
复制代码
tore\mutation-types.js
//搜索历史
export const SET_SEARCHHISTORY='SET_SEARCHHISTORY';
复制代码
mutations.js
//改变当前的搜索历史
[types.SET_SEARCHHISTORY](state,list){
state.searchHistory=list
}
复制代码
suggest\suggest.vue
在selectItem点击事件中,派发saveSearch事件
//一旦点击任何一个列表项的时候,派发事件,将当前的搜索的关键字作为参数派发出去
this.$emit('saveSearch',this.query)
复制代码
search\search.vue
<suggest :query="query" :showSinger="showSinger" @beforeScrollStart="beforeScrollStart" @saveSearch="saveSearch"></suggest>
复制代码
...mapActions(['set_searchHistoty']),
//保存搜索关键字
saveSearch(query){
//改变vuex的searchHistory数组
//将searchHistory存入本地
//因为进行了多个操作,这里我们需要在actions里面写
this.set_searchHistoty(query)
}
复制代码
当点击搜索列表的时候,我们除了需要更改searchHistory,还需要将searchHistory存进到本地存储中,这里,我们进行了多个操作,所以我们将更改searchHistory和本地存储操作在actions里面写。
1、首先我们在 common\js\cache.js 封装本地存储的方法 用到的库:github.com/ustbhuangyi… src\common\js\cache.js
import storage from 'good-storage'
const SEARCH_KEY='_search_' //双划线避免与外部冲突
const MAXLENGTH=15 //最大的长度
//插入搜索历史的方法
//参数:当前的数组,插入的值,比较方法(目的在于获取到之前搜索过的那个val的位置),最大长度
function insertArray(arr,val,compare,maxlength){
let index=arr.findIndex(compare)//获取到之前搜索的index(要是之前搜索过了index为-1)
if(index==0){//当当前的是第一项,之前的也是在第一项,那么就不做任何添加操作
return
}
arr.unshift(val) //将我们需要插入的项插第一位
//之前有搜索过的记录
if(index>0){
arr.splice(index+1,1)//将之前的删除
}
if(maxlength && arr.length>maxlength){
arr.pop();
}
}
export function saveSearch(val) {
//取出来
let searchArr= storage.get(SEARCH_KEY,[])//获取到内存中的_search_。找不到的话就给默认值为[]
//得到要存的数据(直接对searchArr做插入操作)
insertArray(searchArr,val,(item)=>{
return item.trim()==val.trim()
},MAXLENGTH)
//存进去(得到新的searchArr,存进本地)
storage.set(SEARCH_KEY,searchArr)
//将searchArr返回
return searchArr
}
复制代码
2、store\actions.js
//引入本地缓存的方法
import {saveSearch} from '../common/js/cache'
//对搜索列表进行修改并且保存在本地
export function set_searchHistoty({commit,state},query){
commit(types.SET_SEARCHHISTORY,saveSearch(query))
}
复制代码
3、当我们刷新页面的时候,vuex的searchHistory为空数组,我们需要在state.js中将searchHistory设置为本地缓存的值 在cache.js中暴露 获取到缓存_search_的值的方法
//获取到本地存储的值
export function getSearchStorage(){
return storage.get(SEARCH_KEY,[])
}
复制代码
在state.js中使用
import {getSearchStorage} from 'common/js/cache'
复制代码
searchHistory:getSearchStorage(),//存搜索历史
复制代码
10-6 搜索页面search-list 组件功能实现
基础实现 除了搜索页面的搜索列表。添加到歌曲列表也有搜索列表,我们将这个列表抽取出来作为基础组件共享 base\search-list\search-list.vue
接收外层传入的数据
props:{
searches:{
type:Array,
default:[]
}
}
复制代码
页面遍历列表
<div class="search-list">
<ul>
<li class="search-item" v-for="(item,index) in searches" :key="index">
<span class="text">{{item}}</span>
<span class="icon"><i class="icon-delete"></i></span>
</li>
</ul>
</div>
复制代码
search\search.vue 获取到vuex的searchHistory
computed:{
...mapGetters(['searchHistory']),
},
复制代码
引入并且使用search-list组件,向组件传入searchHistory
<search-list :searches='searchHistory'></search-list>
复制代码
当点击删除当前列,当点击删除删除全部 当点击删除列或者删除全部。因为涉及到本地缓存,我们在common\js\cache.js封装删除的方法
//删除当前项的本地缓存
export function deleteSearchOne(val){
//获取到当前的
let searchArr= storage.get(SEARCH_KEY,[])//获取到内存中的_search_。找不到的话就给默认值为[]
let index=searchArr.findIndex((item)=>{
return item==val
})
searchArr.splice(index,1);
//将当前的存进去
storage.set(SEARCH_KEY,searchArr)
return searchArr;
}
复制代码
//删除所有的本地缓存
export function removeAllSearch(){
storage.remove(SEARCH_KEY)
return []
}
复制代码
store\actions.js
//根据当前的值去删除相应的缓存数据
export function delete_SearchOne({commit,state},item){
commit(types.SET_SEARCHHISTORY,deleteSearchOne(item))
}
//删除所有的缓存的数据
export function delete_SearchAll({commit,state}){
commit(types.SET_SEARCHHISTORY,removeAllSearch())
}
复制代码
点击了删除当前项 search-list\search-list.vue作为基础的组件,我们只需要给外部派发事件,不要在基础组件做vuex的处理操作,只需要告诉外部,自己被点击了 因为删除图标是li的子元素,而li本身也绑定了自己的点击事件,所以这里需要用.stop修饰符来停止事件冒泡
<li class="search-item" v-for="(item,index) in searches" :key="index" @click="addquery(item)">
<span class="text">{{item}}</span>
<span class="icon"><i class="icon-delete" @click.stop="selectItem(item)"></i></span>
</li>
复制代码
当点击的是li,需要告诉外部点击了li,外部应该要根据li的值重新搜索了 当点击的是icon,需要告诉外部点击了icon删除按钮,外部应该要删除相关的项了
selectItem(item){
this.$emit('delectOne',item)
},
addquery(item){
this.$emit('addquery',item);
}
复制代码
在search\search.vue中,得到子组件派发的事件和参数,给全部删除绑定全部删除事件delectAll
<!-- 搜索历史部分-->
<div class="search-history" v-show="searchHistory.length">
<h1 class="title">
<span class="text">搜索历史</span>
<span class="clear" @click="delectAll"><i class="icon-clear"></i></span>
</h1>
<!--这里是搜索列表组件 -->
<search-list :searches='searchHistory' @delectOne="delectOne" @addquery="addquery"></search-list>
</div>
复制代码
发起actions
...mapActions(
{'delete_SearchOne':'delete_SearchOne'},
{'delete_SearchAll':'delete_SearchAll'}
),
//删除一个
delectOne(item){
this.delete_SearchOne(item);
},
//删除全部
delectAll(){
this.delete_SearchAll();
},
addquery(query){
this.$refs.search.set_query(query);
},
复制代码
10-7搜索页面confirm 组件功能实现
在项目中,删除全部的数据是一个非常敏感的操作,当一不小心点错了,就会删除了批量数据,所以我们需要在点击全部删除的时候,友好提示客户,是否需要删除全部。 提示弹窗是一个基础组件,可能其他的组件也会频繁用到,我们把它放在base文件夹 base\confirm\confirm.vue
<transition name="confirm-fade">
<div class="confirm" v-show="isShow">
<div class="confirm-wrapper">
<div class="confirm-content">
<p class="text">
{{text}}
</p>
<div class="operate">
<div class="operate-btn left" @click="cancel">
{{cancelBtnText}}
</div>
<div class="operate-btn" @click="confirm">
{{confirmBtnText}}
</div>
</div>
</div>
</div>
</div>
</transition>
复制代码
动画效果
&.confirm-fade-enter-active
animation: confirm-fadein 0.3s
.confirm-content
animation: confirm-zoom 0.3s
@keyframes confirm-fadein
0%
opacity: 0
100%
opacity: 1
@keyframes confirm-zoom
0%
transform: scale(0)
50%
transform: scale(1.1)
100%
transform: scale(1)
复制代码
通过props可由外部传入要修改的相关文本。 confirm的显示和隐藏是有自己的内部维护的,我们在confirm组件的data维护一个isShow 当点击确顶或者取消的时候,只需要向外派发相关的事件,告诉外部,目前被点击了。不在基础组件写外部组件的相关逻辑
props:{
text:{
type:String,
default:''
},
cancelBtnText:{
type:String,
default:"取消"
},
confirmBtnText:{
type:String,
default:"确定"
}
},
data() {
return {
isShow:false
}
},
methods:{
hide(){
this.isShow=false
},
show(){
this.isShow=true
},
cancel(){
this.hide()
this.$emit('cancel')
},
confirm(){
this.hide()
this.$emit('confirm');
}
}
复制代码
通常,基础组件是不会放置和vuex相关的任何逻辑它一般是通过props,data和methods来实现自身的逻辑以及实现和外部的通讯。
现在我们来修改search\search.vue
<confirm text="您确定删除全部吗?" ref="confirm" @confirm="confirm" ></confirm>
复制代码
//删除全部
delectAll(){
this.$refs.confirm.show()//展示弹窗
},
confirm(){
this.delete_SearchAll()
},
复制代码
10-8 搜索页面剩余功能实现
1、让shortcut-wrapper (热门搜索和搜索历史)这部分可以滚动,引入scrol并使用scroll。
<scroll>里面包含多个同级元素,要在外层再嵌套一个<div>
<scroll class="shortcut" ref="shortcut" :data="shortcut">
<div>
<!--热门搜索部分 -->
<!-- 搜索历史部分-->
</div>
</scroll>
复制代码
热门搜索和搜索历史部分的数据都是动获取的,那么在scroll里面传入的data属性。以哪个为准呢?当数据发生变化的时候,让scroll refresh()一下,在这里我么用计算属性shortcut来concat, hotKey和searchHistory。
shortcut(){
return this.hotKey.concat(this.searchHistory)
}
复制代码
2、底部适配处理 shortcut-wrapper,search-result 的底部自适应 引入
import {playlistMixin} from 'common/js/mixin.js'
复制代码
注册
mixins:[playlistMixin],
复制代码
应用
//做底部自适应
handlePlayList(playlist){
let bottom= playlist.length ? 60 :0
this.$refs.shortcut_wrapper.style.bottom = `${bottom}px`
this.$refs.search_result.style.bottom = `${bottom}px`
this.$refs.shortcut.refresh()
this.$refs.suggest.refresh()//在suggest组件暴露出一个refresh的方法。当底部bottom样式变化的时候,suggest组件的scroll重刷新
}
复制代码
优化 当搜索列表页面,切换到热门搜索页面scroll不能正常滚动,这是因为切换之前热门搜索页面是一个从隐藏到展示的过程。所以子啊这里我们需要监听query的变化,当query的值为空的时候,这个时候 热门搜索页面应该是展示的,这个时候,我们就让热门搜索页面的scroll重刷新
watch: {
query(newQuery) {
if (!newQuery) {
setTimeout(() => {
this.$refs.shortcut.refresh()
}, 20)
}
}
},
复制代码
bug: 加载更多的,每一条列表的数据异步获取到播放源,用了getMusicVkey。之前我在这里用了await处理,但是这里有一个问题,就是当不断上拉加载。关掉了搜索页面,去到其他的页面,getMusicVkey的异步请求还在继续。这里暂缓一下,所以把这部分的代码注释了,后续学了axios取消请求的相关知识再补上。