• 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










  • 相关阅读:
    软件开发 [CJOJ 1101] [NOIP 模拟]
    OI中卡常数技巧
    疫情控制 [NOIP2012]
    开车旅行 [NOIP 2012]
    观光公交 [NOIP 2011] [思维推导]
    选择客栈 [NOIP 2011]
    2016级算法期末上机-F.中等·AlvinZH's Fight with DDLs II
    2016级算法期末上机-E.中等·ModricWang's Fight with DDLs II
    2016级算法期末上机-D.简单·AlvinZH's Fight with DDLs I
    2016级算法期末上机-C.简单·Bamboo's Fight with DDLs III
  • 原文地址:https://www.cnblogs.com/wuqilang/p/12405182.html
Copyright © 2020-2023  润新知