vue-day12----数据优化-localStorage、v-touch的使用、Search页面-搜索关键字提示(利用watch监听触发请求,同时将请求参数带过去)、Search页面-历史记录数据渲染和删除历史记录、Search页面-第二次输入请求时页面不更新、input节流(每隔300ms执行一次)、封装Loading组件-JS组件、vue-lazyload图片懒加载
### 数据优化-localStorage
①store/homepage/index.js中拿到数据的时候,设置localStorage的homePage:
actions:{ async getHomepage({commit}){ let data=await http({ method:"get", url:api.home.homepage }); commit("getHomeData",data); localStorage.setItem("homePage",JSON.stringify(data.data)); } }
②Home/index.vue中在请求之前进行判断,如果localStorage中没有homePage才进行请求:
created() { if(!localStorage.getItem("homePage")){ this.$store.dispatch("homepage/getHomepage"); } }
③store/homepage/index.js中设置数据的初始值时,先从缓存中拿:
state:{ banner:localStorage.getItem("homePage")?JSON.parse(localStorage.getItem("homePage")).banner:[], dataList:localStorage.getItem("homePage")?JSON.parse(localStorage.getItem("homePage")).dataList:[], goodsList:localStorage.getItem("homePage")?JSON.parse(localStorage.getItem("homePage")).goodsList:[], promotionBanner:localStorage.getItem("homePage")?JSON.parse(localStorage.getItem("homePage")).promotionBanner:[], recommend:localStorage.getItem("homePage")?JSON.parse(localStorage.getItem("homePage")).recommend:[] }
注意:localStorage的有效时间:关闭浏览器时不清空,手动清除时清空。
### 首页渲染数据
①Home/index.vue中引入DataList组件和GoodsList组件:
<!-- datalist --> <DataList></DataList> <!-- GoodsList --> <GoodsList></GoodsList>
②DataList中引入组件RecommendList,并将proBannerTagUnderImage参数传递过去:
<RecommendList :recommend="item.proBannerTagUnderImage"></RecommendList>
RecommendList组件中通过props接收父组件传递过来的值:
props:{
recommend:{
type:Array
}
}
RecommendList组件中拿到recommend数组,进行页面渲染。
GoodsList中引入组件GoodsListItem,并将proBannerTagUnderImage参数传递过去:
<GoodsListItem :goodsList="item.proBannerTagUnderImage"></GoodsListItem>
GoodsListItem组件中通过props接收父组件传递过来的值:
props:{ goodsList:{ type:Array, default:[] } }
GoodsListItem组件中拿到goodsList数组,进行页面渲染。
### v-touch的使用
①安装插件:npm install vue-touch@next --save dev
②main.js:
import VueTouch from 'vue-touch' Vue.use(VueTouch, { name: 'v-touch' });// 第二个参数是将标签名定义为 v-touch
③使用(v-touch默认渲染成div):
<v-touch class="header_r" tag="div" @tap="search"> <i class="iconfont"></i> </v-touch>
### Search页面-热门搜索数据渲染
①api/index.js中添加search接口:
export default{ home:{ homepage:"/v3/homepage" }, search:{ hotsearch:"/v3/hotsearch",// http://localhost:3000/v3/hotsearch search:"/v3/search", searchList:"/v3/searchList" } }
②Search下新建index.js,请求数据:
import request from '@utils/request';
import api from "@api";
// 热门搜索 export const hotSearchFetchData=()=>{ return request({ method:"get", url:api.search.hotsearch }) }
③Search/index.js中通过async await接收数据,并存入到localStorage中:
import {hotSearchFetchData} from "./index.js"; export default { name:"Search", data(){ return{ hotSearchList:JSON.parse(localStorage.getItem("hotSearchList"))||[] } }, created() { if(!localStorage.getItem("hotSearchList")){ this.getHotSearchList(); } }, methods: { async getHotSearchList(){ let data=await hotSearchFetchData(); this.hotSearchList=data.data; localStorage.setItem("hotSearchList",JSON.stringify(data.data)); } }, };
### Search页面-搜索关键字提示(利用watch监听触发请求,同时将请求参数带过去)
①Search/index.js添加请求数据(searchKeyWordData):
// 搜索关键字提示 export const searchKeyWordData=(keyword)=>{ return request({ method:"get", url:api.search.search, data:{ keyword } }) }
②Search/index.vue中:
(1)data中添加searchKeyWord和searchKeyWordList:
data(){ return{ hotSearchList:JSON.parse(localStorage.getItem("hotSearchList"))||[], searchKeyWord:"", searchKeyWordList:[], } }
(2)输入框v-model绑定searchKeyWord:
<input type="text" placeholder="新鲜水果、生鲜" v-model="searchKeyWord"/>
(3)watch监听,当输入框有变化时自动执行searchKeyWord()函数,触发请求,将请求来的数据赋给searchKeyWordList:
watch: { async searchKeyWord(newValue){ // watch监听:每次输入框中有变化时,将输入框的值传到searchKeyWordData()中作为请求时发送的参数 let data=await searchKeyWordData(newValue); this.searchKeyWordList=data.data; } }
(4)遍历searchKeyWordList:
<div class="searchKeyWordList" v-if="searchKeyWordList.length>0"> <p v-for="(item,index) in searchKeyWordList" :key="index">{{item}}</p> </div>
### Search页面-历史记录数据渲染和删除历史记录
①当输入框中有变化时,会根据关键字提示相应的数据列表供点击跳转,定义 @tap 事件(showSearchList),将文字内容传递过去:
<div class="searchKeyWordList" v-if="searchKeyWordList.length>0"> <v-touch tag="p" v-for="(item,index) in searchKeyWordList" :key="index" @tap="showSearchList(item)">{{item}}</v-touch> </div>
②data中添加 historyList 数组:
data(){ return{ hotSearchList:JSON.parse(localStorage.getItem("hotSearchList"))||[], searchKeyWord:"", searchKeyWordList:[], historyList:JSON.parse(localStorage.getItem("historyList"))||[], } }
③渲染历史记录列表,当historyList中没有值时不显示该列表:
<div class="clear_search" v-if="historyList.length>0"> <div>历史记录</div> <div> <ul> <li v-for="(item,index) in historyList" :key="index">{{item}}</li> </ul> </div> <v-touch tag="div" @tap="clearHistory">清除搜索记录</v-touch> </div>
④添加和删除历史记录:
methods: { showSearchList(item){ /* 1、跳转到searchList页面 2、将搜索的数据保存在历史记录中 */ if(!localStorage.getItem("historyList")){ this.historyList.push(item); }else{ let arr=JSON.parse(localStorage.getItem("historyList")); let flag=arr.includes(item); if(!flag){ arr.push(item); this.historyList=arr; } } localStorage.setItem("historyList",JSON.stringify(this.historyList)); // 跳转(动态路由传值) this.$router.push("/searchList/"+item); }, clearHistory(){ this.historyList=[]; localStorage.removeItem("historyList"); } }
### Search页面-SearchHeader组件封装
①pages/Search/index.js中定义请求方法searchKeyWordData(),需要请求时传递一个keyword参数:
// 搜索关键字提示 export const searchKeyWordData=(keyword)=>{ console.log(keyword) return request({ method:"get", url:api.search.search, data:{ keyword } }) }
②pages/Search/SearchHeader.vue中引入searchKeyWordData()方法:
import {searchKeyWordData} from "./index.js";
③data中定义searchKeyWord和searchKeyWordList:
data() { return { searchKeyWord:"", searchKeyWordList:[], } }
④双数据绑定,这样每当输入框中进行操作时,会触发watch中的searchKeyWord():
<input type="text" placeholder="新鲜水果、生鲜" v-model="searchKeyWord"/>
⑤通过watch监听,当输入框中进行操作时,触发searchKeyWord(),即触发searchKeyWordData(newValue),将得到的data数据赋给searchKeyWordList数组:
watch: { async searchKeyWord(newValue){ // watch监听:每次输入框中有变化时,将输入框的值传到searchKeyWordData()中作为请求时发送的参数 let data=await searchKeyWordData(newValue); this.searchKeyWordList=data.data; } }
⑥通过searchKeyWordList数组遍历数据,为每条数据绑定showSearchList(item):
<div class="searchKeyWordList" v-if="searchKeyWordList.length>0"> <v-touch tag="p" v-for="(item,index) in searchKeyWordList" :key="index" @tap="showSearchList(item)">{{item}}</v-touch> </div>
⑦showSearchList()方法的两个作用,一是跳转到searchList页面,二是将搜索的数据保存在历史记录中:
methods: { showSearchList(item){ /* 1、跳转到searchList页面 2、将搜索的数据保存在历史记录中 */ this.$emit("handleHistory",item); this.$router.push("/searchList/"+item);// 动态路由传参----router/searchList/index.js中设置路由路径时带上参数 path:"/searchList/:keyword", }, }
⑧page/Search/index.vue中,引入SearchHeader组件并绑定自定义事件handleHistory,触发getHistory()函数,将搜索记录保存在历史记录中:
<SearchHeader @handleHistory="getHistory"></SearchHeader>
methods:{ getHistory(item){ if(!localStorage.getItem("historyList")){ this.historyList.push(item); }else{ let arr=JSON.parse(localStorage.getItem("historyList")); let flag=arr.includes(item); if(!flag){ arr.push(item); this.historyList=arr; } } localStorage.setItem("historyList",JSON.stringify(this.historyList)); } }
### Search页面-SearchList组件
①pages/Search/index.js中定义请求方法searchListData(),需要请求时传递一个keyword参数:
// 搜索列表 export const searchListData=(keyword)=>{ return request({ method:"get", url:api.search.searchList, data:{ keyword } }) }
②pages/Search/SearchList.vue中引入searchListData()方法:
import {searchListData} from "../Search/index.js";
③router/searchList/index.js中设置动态路由(path:"/searchList/:keyword")、路由解耦(props:true):
export default{ name:"searchList", path:"/searchList/:keyword", component:()=>import("@pages/SearchList"), meta:{ title:"搜索列表", flag:false }, props:true }
④pages/Search/SearchList.vue中可以通过路由解耦的方式拿到keyword:
props:["keyword"]
⑤通过watch监听keyword对象(这里注意深度监听的方式),触发searchListData(newValue),将拿到的data数据赋给list渲染页面:
watch: { // watch深度监听对象的方式:当keyword有变化时请求 searchListData,将新值作为请求参数传递过去 keyword:{ async handler(newValue){ let data=await searchListData(newValue); this.list=data.data; }, immediate:true// 立即执行 } }
### Search页面-第二次输入请求时页面不更新
问题描述:当进入Search页面,进行搜索时,第一次可以拿到数据渲染页面,此时再进行输入请求时,路由发生变化,但页面未跟新。
分析:
这种问题很容易联想到beforeRouteUpdate()的原理,如果在created()中进行请求,那么页面首次创建的时候会执行请求函数,拿到数据,而接下来页面再进行更新时,由于页面未进行创建和销毁,所以created()压根没有执行,所以就拿不到请求数据。在使用动态动态路由传参时经常会遇到这种情况,路由已经发生了变化而组件未进行更新,此时就需要用到beforeRouteUpdate(),或者用用watch监听 $route 也可以。
但是在SearchList组件中,是用watch监听触发请求函数 searchListData(newValue) 的执行,也就是说每次请求都执行到了,数据也拿到了,那为什么页面没有显示呢?因为SearchHeader组件中的 .searchKeyWordList(关键词提示div) z-index层级高,将下面的搜索结果盖住了。
解决办法:在点击提示<v-touch tag="p" v-for="(item,index) in searchKeyWordList" :key="index" @tap="showSearchList(item)">{{item}}</v-touch>的时候将 searchKeyWordList 设置为[],这样 .searchKeyWordList 就不会遮盖SearchList组件。
代码:
methods: { showSearchList(item){ /* 1、跳转到searchList页面 2、将搜索的数据保存在历史记录中 */ this.$emit("handleHistory",item); this.$router.push("/searchList/"+item); // 点击跳转的时候让searchKeyWordList为[],这样就可以显示当前路由的页面,这里不是用beforeRouterUpdate()解决。beforeRouterUpdate()是用来解决created中请求数据只在第一次渲染,后面点击时组件没有进行创建与销毁,所以只在第一次拿到数据。 this.searchKeyWordList=[]; } }
### Search页面-点击热门搜索和历史记录显示搜索列表-SearchList
①Search/index.vue中:
(1)为热门搜索(hotSearchList)的li添加 @tap事件:
<v-touch tag="li" v-for="(item,index) in hotSearchList" :key="index" @tap="showSearchList(item)">{{item}}</v-touch>
(2)为历史记录(historyList)的li添加 @tap事件:
<v-touch tag="li" v-for="(item,index) in historyList" :key="index" @tap="showSearchList(item)">{{item}}</v-touch>
②Search/index.vue中,showSearchList(item)方法将当前item动态路由传给SearchList(通过路由解耦的方式接收),并将当前item存入历史记录:
methods:{ showSearchList(item){ this.$router.push("/searchList/"+item); // 存到历史记录 this.getHistory(item); } }
### Search页面-SearchHeader组件的input节流(每隔300ms执行一次)
①*新建utils/utils.js:
export const throttle=(function(){ let firstTime=0; let timer=null; return function (callback,time=300) { let lastTime=new Date().getTime(); clearTimeout(timer); if(lastTime-firstTime>time){ callback(); firstTime=lastTime; }else{ timer=setTimeout(() => { callback(); }, time); } } })();
②SearchHeader组件引入:import {throttle} from "@utils/utils.js";
③watch监听中:
async searchKeyWord(newValue){ let data=await searchKeyWordData(newValue); this.searchKeyWordList=data.data; }
替换为:
searchKeyWord(newValue){ throttle(async ()=>{ let data=await searchKeyWordData(newValue); this.searchKeyWordList=data.data; },300);// 不写时间默认300ms,可以自定义节流时间 }
节流和防抖的区别:
节流:在input输入框中,每隔n毫秒执行一次判断。
防抖:在input输入框中,不停地输入字符,当过了n毫秒后,执行判断,如果在n毫秒中又输入了字符,则重新计算等待时间。
### 封装Loading组件-JS组件
①新建plugins/Loading/index.vue文件,在 https://codepen.io/jackrugile/pen/JddmaX 将loading组件的html和css粘贴进来:
<template> <div class="loader"> <div class="loader-inner"> <div class="loader-line-wrap"> <div class="loader-line"></div> </div> <div class="loader-line-wrap"> <div class="loader-line"></div> </div> <div class="loader-line-wrap"> <div class="loader-line"></div> </div> <div class="loader-line-wrap"> <div class="loader-line"></div> </div> <div class="loader-line-wrap"> <div class="loader-line"></div> </div> </div> </div> </template> <style> .loader { background: rgba(0,0,0,.2); bottom: 0; left: 0; overflow: hidden; position: fixed; right: 0; top: 0; z-index: 99999; } .loader-inner { bottom: 0; height: 60px; left: 0; margin: auto; position: absolute; right: 0; top: 0; width: 100px; } .loader-line-wrap { animation: spin 2000ms cubic-bezier(.175, .885, .32, 1.275) infinite ; box-sizing: border-box; height: 50px; left: 0; overflow: hidden; position: absolute; top: 0; transform-origin: 50% 100%; width: 100px; } .loader-line { border: 4px solid transparent; border-radius: 100%; box-sizing: border-box; height: 100px; left: 0; margin: 0 auto; position: absolute; right: 0; top: 0; width: 100px; } .loader-line-wrap:nth-child(1) { animation-delay: -50ms; } .loader-line-wrap:nth-child(2) { animation-delay: -100ms; } .loader-line-wrap:nth-child(3) { animation-delay: -150ms; } .loader-line-wrap:nth-child(4) { animation-delay: -200ms; } .loader-line-wrap:nth-child(5) { animation-delay: -250ms; } .loader-line-wrap:nth-child(1) .loader-line { border-color: hsl(0, 80%, 60%); height: 90px; width: 90px; top: 7px; } .loader-line-wrap:nth-child(2) .loader-line { border-color: hsl(60, 80%, 60%); height: 76px; width: 76px; top: 14px; } .loader-line-wrap:nth-child(3) .loader-line { border-color: hsl(120, 80%, 60%); height: 62px; width: 62px; top: 21px; } .loader-line-wrap:nth-child(4) .loader-line { border-color: hsl(180, 80%, 60%); height: 48px; width: 48px; top: 28px; } .loader-line-wrap:nth-child(5) .loader-line { border-color: hsl(240, 80%, 60%); height: 34px; width: 34px; top: 35px; } @keyframes spin { 0%, 15% { transform: rotate(0); } 100% { transform: rotate(360deg); } } </style>
②新建plugin/Loading/index.js文件,返回一个对象,包含显示和隐藏方法(show()、hide()):
import LoadingUI from "./index.vue"; import Vue from "vue"; export default(()=>{ let JSloading=Vue.extend(LoadingUI); let loadingVm=new JSloading({ el:document.createElement("div") }); return{ show(){ document.body.appendChild(loadingVm.$mount().$el); }, hide(){ loadingVm.$mount().$el.remove(); } } })();
③request.js中引入loading组件(JS组件):
import loading from "@plugins/Loading/index.js";
④request.js中:
在发起请求时执行loading.show();
在响应请求时执行loading.hide();
⑤如果仅在单独的组件中使用,如在Home/index.vue中使用,则在请求前后加loading.show()和loading.hide()。
注意:可以在路由表中配置一下loading的路由,这样可以方便查看样式:
{ name:"loading", path:"/loading", component:()=>import("../plugins/Loading/index.vue") // 当Loading文件夹中有index.vue和index.js时,.vue后缀名要写全 }
### vue-lazyload图片懒加载
①安装插件:npm install vue-lazyload
②main.js中引入:
import VueLazyLoad from "vue-lazyload"; Vue.use(VueLazyLoad,{ preLoad:1.3, // 预加载 error:require("@assets/404.jpg"), // 错误时显示 loading:require("@assets/logo.png"), // 加载时显示 attempt:1 // 每次加载多少张 });
③v-lazy指令:将需要图片懒加载的图片 :src 替换为 v-lazy