接上篇 https://www.cnblogs.com/chenyingying0/p/12612393.html

Loading组件

在api–home.js中,添加代码,使ajax获取到轮播图数据后,延迟一秒再显示

import axios from 'axios';
import {SUCC_CODE,TIMEOUT} from './config';

//获取幻灯片数据 ajax
export const getHomeSliders=()=>{
    // es6使用promise代替回调
    // axios返回的就是一个promise
    // return axios.get('http://www.imooc.com/api/home/slider').then(res=>{
    //     console.log(res);
    //     if(res.data.code===SUCC_CODE){
    //         return res.data.slider;
    //     }

    //     throw new Error('没有成功获取到数据');
    // }).catch(err=>{
    //     console.log(err);
    //     //错误处理
    //     return [{       
    //         linkUrl:'www.baidu.com',
    //         picUrl:require('assets/img/404.png')
    //     }]
    // });

    //演示超时错误
    return axios.get('http://www.imooc.com/api/home/slider',{
        timeout:TIMEOUT
    }).then(res=>{
        console.log(res);
        if(res.data.code===SUCC_CODE){
            return res.data.slider;
        }

        throw new Error('没有成功获取到数据');
    }).catch(err=>{
        console.log(err);
        //错误处理
        return [{       
            linkUrl:'www.baidu.com',
            picUrl:require('assets/img/404.png')
        }]
    }).then(data=>{//获取轮播图数据后,延迟一秒再显示
        return new Promise(resolve=>{
            setTimeout(()=>{
                resolve(data);
            },1000);
        })
    });
}

在base下创建loading文件夹,里面创建index.vue

<template>
    <div class="mine-loading" :class="{'me-loading-inline':inline}">
        <span class="mine-loading-indicator" v-if="indicator==='on'" >
            <img src="./loading.gif" alt="">
        </span>
        <span class="mine-loading-text" v-if="text">{{text}}</span>
    </div>
</template>

<script>
export default {
   name:"MeLoading",
   props:{//过滤器
       indicator:{
           type:String,
           default:'on',
           validator(value){
               return ['on','off'].indexOf(value)>-1;
           }
       },
       text:{
           type:String,
           default:'加载中...'
       },
        inline:{
            type:Boolean,
            default:false
        }
   }
}
</script>

<style lang="scss" scoped>
    @import '~assets/scss/mixins';

    .mine-loading{
        width:100%;
        height:100%;
        @include flex-center(column);

        //图文左右排列时
        &.me-loading-inline{
            flex-direction: row;
           
            .mine-loading-indicator ~ .mine-loading-text{
                margin-top:0px;
                margin-left:7px;
            }
        }

        .mine-loading-indicator{

        }

        // 存在.mine-loading-indicator和.mine-loading-text时
        .mine-loading-indicator ~ .mine-loading-text{
            margin-top:7px;
        }
    }
</style>

在base-loading文件夹下放入loadging.gif

在home–slider.vue中引入loading组件

<template>
    <div class="slider-wrapper">
        <!-- sliders没加载时显示loading -->
        <Meloading v-if="!sliders.length"></Meloading>
        <!-- 分开传才能分开校验,因此不直接传入对象 -->
        <MeSlider 
            :direction="direction"
            :loop="loop"
            :interval="interval"
            :pagination="pagination"
            v-else
        >
            <swiper-slide v-for="(item,index) in sliders" :key="index">
                <a :href="item.linkUrl" class="slider-link">
                    <img :src="item.picUrl" class="slider-img">
                </a>
            </swiper-slide>
        </MeSlider>
    </div>
</template>

<script>
import MeSlider from 'base/slider';
import { SwiperSlide } from 'vue-awesome-swiper';
import { sliderOptions } from './config';
import { getHomeSliders } from 'api/home';
import Meloading from 'base/loading';

export default {
   name:"HomeSlider",
   components:{
       MeSlider,
       SwiperSlide,
       Meloading
   },
    data(){
        return{
            direction:sliderOptions.direction,
            loop:sliderOptions.loop,
            interval:sliderOptions.interval,
            pagination:sliderOptions.pagination,
            sliders:[],//这是从服务器读取
            //这是静态写入
            // sliders:[
            //     {
            //        linkUrl:'www.baidu.com',
            //        picUrl:require('./1.jpg') //js中本地图片引入必须加require
            //     },
            //     {
            //        linkUrl:'www.baidu.com',
            //        picUrl:require('./2.jpg') 
            //     },
            //     {
            //        linkUrl:'www.baidu.com',
            //        picUrl:require('./3.jpg') 
            //     },
            //     {
            //        linkUrl:'www.baidu.com',
            //        picUrl:require('./4.jpg') 
            //     }
            // ]
        }
    },
    created(){
        //一般在created里获取远程数据
        this.getSliders();
    },
    methods:{
        getSliders(){
            getHomeSliders().then(data=>{
                console.log(data);
                this.sliders=data;
            });
        }
    }
}
</script>

<style lang="scss" scoped>
    // 引入前面需要加波浪线,否则会报错
    @import "~assets/scss/mixins";
    .slider-wrapper{
        width:100%;
        height:183px;
    }
    .slider-link{
        display:block;
    }
    .slider-link,
    .slider-img{
        width:100%;
        height:100%;
    }
    
</style>

目录如下:

 

效果图

滚动条组件

在base目录下创建scroll目录,新建index.vue

<template>
    <swiper :options="swiperOption">
        <swiper-slide>
            <slot></slot>
        </swiper-slide>
        <div class="swiper-scrollbar" v-if="scrollbar" slot="scrollbar"></div>
    </swiper>
</template>

<script>
// 组件首字母大写,否则会报错
import {Swiper,SwiperSlide} from 'vue-awesome-swiper';

export default {
    name:"MeScroll",
    components:{
        Swiper,
        SwiperSlide
    },
    props:{//过滤器
       scrollbar:{
           type:Boolean,
           default:true
       }
    },
    data(){
        return {
            swiperOption:{
                direction:'vertical',//垂直方向
                slidesPerView:'auto',//一次显示几张
                freeMode:true,//任意滑动多少距离
                setWrapperSize:true,//根据内容设置容器尺寸
                scrollbar:{
                    el:this.scrollbar?'.swiper-scrollbar':null,
                    hide:true //滚动条自动隐藏
                }

            }
        }
    }
}
</script>

<style lang="scss" scoped>
    @import '~assets/scss/mixins';

    .swiper-container{
        width:100%;
        height:100%;
        overflow:hidden;

        & .swiper-slide{
            height:auto;
        }  
    }
     
</style>

在home–index.vue中引入scroll组件

<template>
    <div class="home">
        <header class="g-header-container">
            <!-- 没有内容自闭合即可-->
            <home-header/>
        </header> 
        <me-scroll>
            <home-slider></home-slider>
            <home-slider></home-slider>
            <home-slider></home-slider>
            <home-slider></home-slider>
            <home-slider></home-slider>
            <home-slider></home-slider>
            <home-slider></home-slider>
            <home-slider></home-slider>
            <home-slider></home-slider>
            <home-slider></home-slider>
            <home-slider></home-slider>
        </me-scroll>
        <div class="g-backup-container"></div>
        <!-- 当前页面存在二级页面时需要使用router-view -->
        <router-view></router-view>
    </div>
</template>

<script>
import MeScroll from 'base/scroll';
import HomeHeader from './header';
import HomeSlider from './slider';


export default {
    name:"Home",
    components:{
        HomeHeader,
        HomeSlider,
        MeScroll
    }
}
</script>

<style lang="scss" scoped>
    // 引入前面需要加波浪线,否则会报错
    @import "~assets/scss/mixins";
    .home{
        overflow:hidden;
        width:100%;
        height:100%;
        background:$bgc-theme;
    }

</style>

这里添加这么多组轮播图是为了增高高度展示下轮播图效果

导航面板

在home目录中新建nav.vue

<template>
    <nav class="nav">
        <ul class="nav-list">
            <li class="nav-item" v-for="(item,index) in navs" :key="index">
                <a :href="item.linkUrl" class="nav-link">
                    <img :src="item.picUrl" alt="" class="nav-pic">
                    <span>{{item.text}}</span>
                </a>
            </li>
        </ul>
    </nav>
</template>

<script>
import {navItems} from './config.js';

export default {
    name:"HomeNav",
    components:{
        
    },
    props:{//过滤器
       
    },
    data(){
        return {
           
        }
    },
    created(){
        //不建议把这个数据放在data里,因为data里的数据都会添加getter和setter,而这里的数据并不需要实时响应变化,放在data里对资源是一种浪费
        this.navs=navItems;
    }
}
</script>

<style lang="scss" scoped>
    @import '~assets/scss/mixins';

    .nav{
        width:100%;
        margin-top:15px;
    }
    .nav-list{
        display:flex;
        flex-wrap:wrap;
    }
    .nav-item{
        width:20%;
    }
    .nav-link{
        @include flex-center(column);
        margin-bottom:15px;
    }
    .nav-pic{
        width:60%;
        margin-bottom:7px;
    }
     
</style>

在index.vue中引入nav组件

<template>
    <div class="home">
        <header class="g-header-container">
            <!-- 没有内容自闭合即可-->
            <home-header/>
        </header> 
        <me-scroll>
            <home-slider />
            <home-nav></home-nav>
        </me-scroll>
        <div class="g-backup-container"></div>
        <!-- 当前页面存在二级页面时需要使用router-view -->
        <router-view></router-view>
    </div>
</template>

<script>
import MeScroll from 'base/scroll';
import HomeHeader from './header';
import HomeSlider from './slider';
import HomeNav from './nav';


export default {
    name:"Home",
    components:{
        HomeHeader,
        HomeSlider,
        MeScroll,
        HomeNav
    }
}
</script>

<style lang="scss" scoped>
    // 引入前面需要加波浪线,否则会报错
    @import "~assets/scss/mixins";
    .home{
        overflow:hidden;
        width:100%;
        height:100%;
        background:$bgc-theme;
    }

</style>

数据在config.js中

//暴露一个常量
export const sliderOptions={
    direction:"horizontal",
    loop:"loop",
    interval:1000,
    pagination:"pagination"
}

export const navItems=[
    {
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-1.png'),
        text:'团购'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-2.png'),
        text:'团购'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-3.png'),
        text:'团购'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-4.png'),
        text:'团购'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-5.png'),
        text:'团购'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-6.png'),
        text:'团购'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-7.png'),
        text:'团购'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-8.png'),
        text:'团购'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-9.png'),
        text:'团购'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-10.png'),
        text:'团购'
    }
];

效果图

 

热卖推荐–jsonp封装

准备一个淘宝接口

https://ju.taobao.com/json/tg/ajaxGetItemsV2.json

安装jsonp的库

cnpm install –save jsonp

封装jsonp方法

在assets–js下创建jsonp.js

import jsonp from 'jsonp';

/*data格式案例
{
    id:1,
    name:'cyy'
}
*/
const parseParam=param=>{
    /*将data格式转换为
    [
        [id,1],
        [name,cyy]
    ]
    */
    let arr=[];
    for(const key in param){
        arr.push([key,param[key]]);
    }
    /*先将data格式转换为
    [
        id=1,
        name=cyy
    ]
    */
   /*再将data格式转换为
    id=1&name=cyy
    */
    return arr.map(value=>value.join("=")).join('&');
}

export default (url,data,options)=>{
    // 如果存在?,则url后面加&;如果不存在则加?
    url+=((url.indexOf('?')<0) ? '?' : '&' ) + parseParam(data);

    return new Promise((resolve,reject)=>{
        
        //jsonp用法,三个参数:jsonp(url,options,callback)
        jsonp(url,options,(err,data)=>{
            if(err){
                reject(err);
            }else{
                resolve(data);
            }
        })
    })
}

 

在api / home.js中调用jsonp方法获取数据

import axios from 'axios';
import {SUCC_CODE,TIMEOUT,HOME_RECOMMEND_PAGE_SIZE,JSONP_OPTIONS} from './config';
import jsonp from 'assets/js/jsonp';

//获取幻灯片数据 ajax
export const getHomeSliders=()=>{
    // es6使用promise代替回调
    // axios返回的就是一个promise
    // return axios.get('http://www.imooc.com/api/home/slider').then(res=>{
    //     console.log(res);
    //     if(res.data.code===SUCC_CODE){
    //         return res.data.slider;
    //     }

    //     throw new Error('没有成功获取到数据');
    // }).catch(err=>{
    //     console.log(err);
    //     //错误处理
    //     return [{       
    //         linkUrl:'www.baidu.com',
    //         picUrl:require('assets/img/404.png')
    //     }]
    // });

    //演示超时错误
    return axios.get('http://www.imooc.com/api/home/slider',{
        timeout:TIMEOUT
    }).then(res=>{
        //console.log(res);
        if(res.data.code===SUCC_CODE){
            return res.data.slider;
        }

        throw new Error('没有成功获取到数据');
    }).catch(err=>{
        console.log(err);
        //错误处理
        return [{       
            linkUrl:'www.baidu.com',
            picUrl:require('assets/img/404.png')
        }]
    }).then(data=>{//获取轮播图数据后,延迟一秒再显示
        return new Promise(resolve=>{
            setTimeout(()=>{
                resolve(data);
            },1000);
        })
    });
}

//获取热门推荐数据
export const getHomeRecommend=(page=1,psize=HOME_RECOMMEND_PAGE_SIZE)=>{
    const url='https://ju.taobao.com/json/tg/ajaxGetItemsV2.json';
    const params={
        page,
        psize,
        type:0,
        frontCatId:''//type和frontCatId是根据给定的淘宝接口来添加的
    }

    //调用jsonp获取数据
    return jsonp(url,params,JSONP_OPTIONS).then(res=>{
        if(res.code==='200'){
            return res;
        }

        throw new Error('没有成功获取到数据');
    }).catch(err=>{
        if(err){
            console.log(err);
        }
        
    }).then(res=>{
        //延迟一秒返回数据
        return new Promise(resolve=>{
            setTimeout(()=>{
                resolve(res);
            },1000);
        })
    })
    
}

api / config.js中添加常量

//获取轮播图
export const SUCC_CODE=0;
export const TIMEOUT=10000;

//获取热门推荐
export const HOME_RECOMMEND_PAGE_SIZE=20;
export const JSONP_OPTIONS={
    param:'callback',
    timeout:TIMEOUT
};

 

在pages/home/recommend.vue中添加代码

<template>
    <div class="recommend">
        <h3 class="recommend-title">热卖推荐</h3>
        <div class="loading-container" v-if="!recommends.length">
            <!-- 完整写法是 inline:inline ,不过布尔值类型可以直接写 inline -->
            <me-loading inline />
        </div>
        <ul class="recommend-list">
            <li class="recommend-item" v-for="(item,index) in recommends" :key="index">
                <router-link class="recommend-link" :to="{name:'home-product',params:{id:item.baseinfo.itemId}}">
                    <p class="recommend-pic"><img class="recommend-img" :src="item.baseinfo.picUrl" alt=""></p>
                    <p class="recommend-name">{{item.name.shortName}}</p>
                    <p class="recommend-oriPrice"><del>¥{{item.price.origPrice}}</del></p>
                    <p class="recommend-info">
                        <span class="recommend-price">¥<strong class="recommend-price-num">{{item.price.actPrice}}</strong></span>
                        <span class="recommend-count">{{item.remind.soldCount}}件已售</span>
                    </p>
                </router-link>
            </li>
        </ul>
    </div>
</template>

<script>
import {getHomeRecommend} from 'api/home';
import MeLoading from 'base/loading';

export default {
    name:"HomeRecommend",
    data(){
        return {
           recommends:[],
           curPage:1,
           totalPage:1
        }
    },
    components:{
        MeLoading
    },
    created(){
        this.getRecommends();        
    },
    methods:{
        getRecommends(){
            
            if(this.curPage>this.totalPage) return Promise.reject(new Error('没有更多了'));

            getHomeRecommend(this.curPage).then(data=>{
                return new Promise(resolve=>{
              
                    if(data){
                        console.log(data);
                        
                        this.curPage++;
                        this.totalPage=data.totalPage;

                        // concat合并数组内容,每次获取的数据都追加进来
                        this.recommends=this.recommends.concat(data.itemList);

                        resolve();
                    }
                })
            });
        }
    }
}
</script>

<style lang="scss" scoped>
    @import '~assets/scss/mixins';

    .recommend{
        position:relative;
        width:100%;
        padding:10px 0;
        font-size:$font-size-l;
        text-align:center;

        &:before,
        &:after{
            content:"";
            display:block;
            position:absolute;
            top:50%;
            width:40%;
            height:1px;
            background:#ddd;
        }

        &:before{
            left:0;        
        }

        &:after{
            right:0;
        }
    }
    .recommend-list{
        @include flex-between();
        flex-wrap:wrap;
    }
    .recommend-title{
        margin-bottom:8px;
    }
    .recommend-item{
        width:49%;
        background:#fff;
        box-shadow:0 1px 1px 0 rgba(0,0,0,0.12);
        margin-bottom:8px;
    }
    .recommend-link{
        display:block;
    }
    .recommend-pic{
        position:relative;
        width:100%;
        padding-top:100%;// 可以实现高度与宽度一致
        margin-bottom:5px;
    }
    .recommend-img{
        width:100%;
        position:absolute;
        top:0;
        left:0;
        height:100%;
    }
    .recommend-name{
        height:40px;
        padding:0 5px;
        margin-bottom:8px;
        line-height:1.5;
        @include multiline-ellipsis();
        text-align:left;

    }
    .recommend-oriPrice{
        padding:0 5px;
        margin-bottom:8px;
        color:#ccc;

        del{

        }
    }
    .recommend-info{
        @include flex-between();
        padding:0 5px;
        margin-bottom:8px;
    }
    .recommend-price{
        color:#e61414;

        &-num{
            font-size:20px;
        }
    }
    .recommend-count{
        color:#999;
    }
    .loading-container{
        padding-top:150px;
    }
     
</style>

 

src/pages/product.vue

<template>
    <div class="product">
        product
    </div>
</template>

<script>
export default {
   name:"Product"
}
</script>

<style lang="scss" scoped>
    @import '~assets/scss/_mixins';

    .product{
        overflow:hidden;
        position:absolute;
        top:0;
        left:0;
        width:100%;
        height:100%;
        background:#fff;
        z-index:$product-z-index;
    }
</style>

效果图

 

更新滚动条

由于热门推荐是异步加载的,热门推荐还没加载完时,滚动条已经加载完毕,因此滚动条无法获取到正确的热门推荐区域的高度,导致滚动条效果失效

因此当热门推荐加载完毕时,需要再次更新滚动条

1、recommend.vue中,热门推荐加载完成后,触发loaded消息并传递recommends数据

 

2、接收触发的消息loaded,触发getRecommends函数

3、在getRecommends函数中更新recommends数据

4、让滚动条接收到recommends数据

5、滚动条检测到数据变化,开始更新滚动条

 

6、这里用到了swiper实例,需要在swiper元素上获取到

7、滚动条效果回来啦!

图片的懒加载

1、安装lazyload插件  cnpm install –save vue-lazyload

2、在main.js中引入组件

3、在recommend.vue中将:src改为v-lazy

完美实现懒加载!

Scroll Up