第10章 搜索页面开发

内容均为《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取消请求的相关知识再补上。

github chapter10

转载于:https://juejin.im/post/5d302da3e51d454f723025bd

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值