Vue webAPP home page development

Keywords: Javascript Vue axios JSON

Continue with previous article https://www.cnblogs.com/chenyingying0/p/12612393.html

 

Loading components

In api--home.js, add code to delay ajax from displaying a second after it gets the data for the relay map

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

//Get slide data ajax
export const getHomeSliders=()=>{
    // es6 Use promise Replace callback
    // axios One is returned 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('Failed to get data');
    // }).catch(err=>{
    //     console.log(err);
    //     //error handling
    //     return [{       
    //         linkUrl:'www.baidu.com',
    //         picUrl:require('assets/img/404.png')
    //     }]
    // });

    //Demo timeout error
    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('Failed to get data');
    }).catch(err=>{
        console.log(err);
        //error handling
        return [{       
            linkUrl:'www.baidu.com',
            picUrl:require('assets/img/404.png')
        }]
    }).then(data=>{//Delay one second to show after getting the data of the broadcast map
        return new Promise(resolve=>{
            setTimeout(()=>{
                resolve(data);
            },1000);
        })
    });
}

 

Create the loading folder under base, which creates 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:{//Filter
       indicator:{
           type:String,
           default:'on',
           validator(value){
               return ['on','off'].indexOf(value)>-1;
           }
       },
       text:{
           type:String,
           default:'Loading...'
       },
        inline:{
            type:Boolean,
            default:false
        }
   }
}
</script>

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

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

        //When text is arranged left and right
        &.me-loading-inline{
            flex-direction: row;
           
            .mine-loading-indicator ~ .mine-loading-text{
                margin-top:0px;
                margin-left:7px;
            }
        }

        .mine-loading-indicator{

        }

        // existence.mine-loading-indicator and.mine-loading-text time
        .mine-loading-indicator ~ .mine-loading-text{
            margin-top:7px;
        }
    }
</style>

 

Place loading.gif under the base-loading folder

 

Introducing the loading component in home--slider.vue

<template>
    <div class="slider-wrapper">
        <!-- sliders Show when not loaded loading -->
        <Meloading v-if="!sliders.length"></Meloading>
        <!-- Separate passes to separate checks, so objects are not passed directly in -->
        <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:[],//This is read from the server
            //This is a static write
            // sliders:[
            //     {
            //        linkUrl:'www.baidu.com',
            //        picUrl:require('./1.jpg') //Local picture import in js must 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(){
        //Generally in created Get remote data from
        this.getSliders();
        
        
    },
    methods:{
        getSliders(){
            getHomeSliders().then(data=>{
                console.log(data);
                this.sliders=data;
            });
        }
    }
}
</script>

<style lang="scss" scoped>
    // Waves are required before introduction, otherwise errors will be made
    @import "~assets/scss/mixins";
    .slider-wrapper{
        width:100%;
        height:183px;
    }
    .slider-link{
        display:block;
    }
    .slider-link,
    .slider-img{
        width:100%;
        height:100%;
    }
    
</style>

 

The catalog is as follows:

 

 

Design sketch

 

Scrollbar Component

Create scroll directory under base directory, create new 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>
// Component initials are capitalized, otherwise errors will be reported
import {Swiper,SwiperSlide} from 'vue-awesome-swiper';

export default {
    name:"MeScroll",
    components:{
        Swiper,
        SwiperSlide
    },
    props:{//Filter
       scrollbar:{
           type:Boolean,
           default:true
       }
    },
    data(){
        return {
            swiperOption:{
                direction:'vertical',//vertical direction
                slidesPerView:'auto',//Show several at a time
                freeMode:true,//Slide any distance
                setWrapperSize:true,//Set container size according to content
                scrollbar:{
                    el:this.scrollbar?'.swiper-scrollbar':null,
                    hide:true //Scrollbar Auto Hide
                }

            }
        }
    }
}
</script>

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

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

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

 

Introducing scroll components in home--index.vue

<template>
    <div class="home">
        <header class="g-header-container">
            <!-- No content self-closing-->
            <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>
        <!-- Need to use when the current page has a secondary page 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>
    // Waves are required before introduction, otherwise errors will be made
    @import "~assets/scss/mixins";
    .home{
        overflow:hidden;
        width:100%;
        height:100%;
        background:$bgc-theme;
    }

</style>

 

So many multicast maps are added here to increase the height of the next one

 

Navigation panel

Create a new nav.vue in the home directory

<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:{//Filter
       
    },
    data(){
        return {
           
        }
    },
    created(){
        //It is not recommended to put this data in data Because data All data in will be added getter and setter,And the data here doesn't need to respond to changes in real time, put in data Inside is a waste of resources
        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>

 

Introducing nav components in index.vue

<template>
    <div class="home">
        <header class="g-header-container">
            <!-- No content self-closing-->
            <home-header/>
        </header> 
        <me-scroll>
            <home-slider />
            <home-nav></home-nav>
        </me-scroll>
        <div class="g-backup-container"></div>
        <!-- Need to use when the current page has a secondary page 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>
    // Waves are required before introduction, otherwise errors will be made
    @import "~assets/scss/mixins";
    .home{
        overflow:hidden;
        width:100%;
        height:100%;
        background:$bgc-theme;
    }

</style>

 

Data in config.js

//Expose a constant
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:'Group Purchase'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-2.png'),
        text:'Group Purchase'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-3.png'),
        text:'Group Purchase'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-4.png'),
        text:'Group Purchase'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-5.png'),
        text:'Group Purchase'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-6.png'),
        text:'Group Purchase'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-7.png'),
        text:'Group Purchase'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-8.png'),
        text:'Group Purchase'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-9.png'),
        text:'Group Purchase'
    },{
        linkUrl:'www.baidu.com',
        picUrl:require('./img/nav-item-10.png'),
        text:'Group Purchase'
    }
];

 

Design sketch

 

 

Hot Sell Recommendation--jsonp Packaging

Prepare a Taobao interface

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

Install the jsonp Library

cnpm install --save jsonp

 

 

Encapsulate jsonp method

Create jsonp.js under assets--js

import jsonp from 'jsonp';

/*data Format Case
{
    id:1,
    name:'cyy'
}
*/
const parseParam=param=>{
    /*Convert data format to
    [
        [id,1],
        [name,cyy]
    ]
    */
    let arr=[];
    for(const key in param){
        arr.push([key,param[key]]);
    }
    /*Convert data format to
    [
        id=1,
        name=cyy
    ]
    */
   /*Convert the data format to
    id=1&name=cyy
    */
    return arr.map(value=>value.join("=")).join('&');
}

export default (url,data,options)=>{
    // If so?Then url Followed by&;Add if not present?
    url+=((url.indexOf('?')<0) ? '?' : '&' ) + parseParam(data);

    return new Promise((resolve,reject)=>{
        
        //jsonp Usage, three parameters: jsonp(url,options,callback)
        jsonp(url,options,(err,data)=>{
            if(err){
                reject(err);
            }else{
                resolve(data);
            }
        })
    })
}

 

Call jsonp method in api / home.js to get data

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

//Get slide data ajax
export const getHomeSliders=()=>{
    // es6 Use promise Replace callback
    // axios One is returned 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('Failed to get data');
    // }).catch(err=>{
    //     console.log(err);
    //     //error handling
    //     return [{       
    //         linkUrl:'www.baidu.com',
    //         picUrl:require('assets/img/404.png')
    //     }]
    // });

    //Demo timeout error
    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('Failed to get data');
    }).catch(err=>{
        console.log(err);
        //error handling
        return [{       
            linkUrl:'www.baidu.com',
            picUrl:require('assets/img/404.png')
        }]
    }).then(data=>{//Delay one second to show after getting the data of the broadcast map
        return new Promise(resolve=>{
            setTimeout(()=>{
                resolve(data);
            },1000);
        })
    });
}

//Get popular recommendation data
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 and frontCatId Is added according to the given Taobao interface
    }

    //call jsonp get data
    return jsonp(url,params,JSONP_OPTIONS).then(res=>{
        if(res.code==='200'){
            return res;
        }

        throw new Error('Failed to get data');
    }).catch(err=>{
        if(err){
            console.log(err);
        }
        
    }).then(res=>{
        //Delay one second to return data
        return new Promise(resolve=>{
            setTimeout(()=>{
                resolve(res);
            },1000);
        })
    })
    
}

 

Add Constant to api / config.js

//Get a rotation map
export const SUCC_CODE=0;
export const TIMEOUT=10000;

//Get popular recommendations
export const HOME_RECOMMEND_PAGE_SIZE=20;
export const JSONP_OPTIONS={
    param:'callback',
    timeout:TIMEOUT
};

 

Add code to pages/home/recommend.vue

<template>
    <div class="recommend">
        <h3 class="recommend-title">Hot Sales Recommendations</h3>
        <div class="loading-container" v-if="!recommends.length">
            <!-- The complete notation is inline:inline ,Boolean types, however, can be written directly 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}}Parts sold</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('No more'));

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

                        // concat Merge the contents of the array, appending the data each time you get it
                        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%;// Consistent height and width can be achieved
        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>

 

Design sketch

 

 

 

Update Scrollbar

Because the hot recommendations are loaded asynchronously, and the scrollbar is loaded before the hot recommendations are loaded, the scrollbar cannot get the correct height of the hot recommendation area, causing the scrollbar effect to fail

So when the popular recommendations are loaded, the scrollbar needs to be updated again

 

1. In recommend.vue, trigger the loaded message and pass recommends data when the popular recommendation has finished loading

 

2. Receive triggered message loaded, trigger getRecommends function

 

3. Update recommends data in getRecommends function

 

4. Let the scrollbar receive recommends data

 

5. Scrollbars detect data changes and start updating scrollbars

 

6. The swiper instance is used here and needs to be obtained on the swiper element

 

7. Scrollbar effect is back!

 

Lazy loading of pictures

1. Install lazyload plug-in cnpm install --save vue-lazyload

 

2. Introducing components in main.js

 

3. In recommend.vue, change:src to v-lazy

 

Perfect for lazy loading!

Posted by forsooth on Sun, 05 Apr 2020 11:08:31 -0700