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">&#xe60b;</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










posted @ 2020-03-03 22:28  吴小明-  阅读(746)  评论(0编辑  收藏  举报