目录

产品预览

准备工作

项目开始

页面结构和根组件 app.vue

编写底部导航栏

编写路由

编写Home页面

顶部标题栏

轮播图


产品预览

准备工作

src目录下创建项目

  • api
  • assets(fonts, img, js, scss)
  • base
  • components
  • pages
  • router

1、引入assets下的字体文件,然后配置scss。(_variables.scss、_mixins.scss、_reset.scss、_icons.scss、_base.scss、index.scss  加入下划线是为了不被编译为css,统一在index中引入 )

//引入 main.js
import 'assets/scss/index.scss';

2、配置mian.js  引入index.scss完成基础配置。sass引入无法识别、需要引入两个插件sass-loader、node-sass

cnpm install sass-loader@7.3.1 --save-dev
cnpm install --save-dev node-sass

3、安装一些包 babel-polyfill   fastclick、  然后在mian.js引入

安装
cnpm install --save-dev babel-polyfill 
cnpm install --save-dev fastclick

引入  main.js
import 'babel-polyfill'
import fastclick from 'fastclick'

fastclick.attach(document.body);

4、路由下的@符号是别名的意思,我们可以去build下的webpack.base.conf.js文件去修改。修改后路由下就可以用别名作为路径。

//原来别名
alias: {
  '@': resolve('src'),
  'abc': resolve('src'),
}

//修改后
alias: {
  'api': resolve('src/api'),
  'assets': resolve('src/assets'),
  'base': resolve('src/base'),
  'components': resolve('src/components'),
  'pages': resolve('src/pages')
}

项目开始

页面结构和根组件 app.vue

<template>
  <div id="app" class="g-container">
    <div class="g-view-container">
      <router-view/>
    </div>
    <div class="g-footer-container">

    </div>
  </div>
</template>

<script>
  export default {
    name: 'App'
  }
</script>
/*_container.scss*/
@import "mixins";
.g-container {
  overflow: hidden;
  position: relative;
  width: 100%;
  max-width: 640px;
  min-width: 320px;
  height: 100%;
  margin: 0 auto;
}

.g-view-container {
  height: 100%;
  padding-bottom: $tabbar-height;
}
.g-content-container {
  height: 100%;
  padding-top: $navbar-height;
}

.g-header-container {
  position: absolute;
  left: 0;
  top: 0;
  z-index: $navbar-z-index;
  width: 100%;
}

.g-footer-container {
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: $tabbar-z-index;
  width: 100%;
  box-shadow: 0 0 10px 0 hsla(0, 6%, 58%, 0.6);
}


.g-backtop-container {
  position: absolute;
  z-index: $backtop-z-index;
  right: 10px;
  bottom: $tabbar-height + 10px;
}


如果想要保持占满全屏  不溢出需要全局设置body和html  宽高、溢出隐藏

html, body {
  overflow: hidden;
  width: 100%;
  height: 100%;
}

编写底部导航栏

//app.vue 界面引入自定义底部导航组件
<template>
  <div id="app" class="g-container">
    <div class="g-view-container">
      <router-view/>
    </div>
    <div class="g-footer-container">
      <c-tabber/>
    </div>
  </div>
</template>

<script>
  import CTabber from './components/tabber';

  export default {
    name: 'App',
    components: {
      CTabber
    }
  }
</script>
//commponents下tabber  index.vue
<template>
    <div class="g-tabbar">
        <router-link class="g-tabbar-item" to="/home">
            <i class="iconfont icon-home"></i>
            <span>首页</span>
        </router-link>

        <router-link class="g-tabbar-item" to="/category">
            <i class="iconfont icon-category"></i>
            <span>分类页</span>
        </router-link>

        <router-link class="g-tabbar-item" to="/cart">
            <i class="iconfont icon-cart"></i>
            <span>购物车</span>
        </router-link>

        <router-link class="g-tabbar-item" to="/personal">
            <i class="iconfont icon-personal"></i>
            <span>个人中心</span>
        </router-link>
    </div>
</template>

<script>
export default {
    name: 'CTabber'
}
</script>

<style lang='scss' scoped>
    // 这里引入一定要加波浪线  不然报错
    @import "~assets/scss/mixins";

    .router-link-active{
        color: $link-active-color;
    }
</style>
/*_tabber.scss */
@import "mixins";

.g-tabbar {
  display: flex;
  width: 100%;
  height: $tabbar-height;
  background-color: #fff;

  &-item {
    flex: 1;
    @include flex-center(column);

    .iconfont {
      margin-bottom: 4px;
      font-size: $icon-font-size;
    }
  }
}
//_mixins
@import "variables";

// flex-center
@mixin flex-center($direction: row) {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: $direction;
}

效果

编写路由

创建页面并配置路由,home、product、category、cart、personal、seach

注意:*通配符  如果上面没有匹配到就导航到home、() => import('pages/home')路由懒加载,只在显示是加载页面信息。

//router配置
import Vue from 'vue'
import Router from 'vue-router'
// import Home from 'pages/home'
// import Category from 'pages/category'
// import Cart from 'pages/cart'
// import Personal from 'pages/personal'
// import Product from 'pages/product'
// import Search from 'pages/search'

Vue.use(Router)

const routes = [
    {
      name: 'home',
      path: '/home',
      component: () => import('pages/home'),
      children: [
        {
          name: 'home-product',
          path: 'product/:id',
          component: () => import('pages/product')
        }
      ]
    },
    {
      name: 'category',
      path: '/category',
      component: () => import('pages/category')
    },
    {
      name: 'cart',
      path: '/cart',
      component: () => import('pages/cart')
    },
    {
      name: 'personal',
      path: '/personal',
      component: () => import('pages/personal')
    },
    {
      name: 'search',
      path: '/search',
      component: () => import('pages/search')
    },
    {
      path: '*',
      redirect: '/home'
    }
]

export default new Router({
  routes
})

编写Home页面

顶部标题栏

//home下  index.vue
<template>
    <div class="home">
        <header class="g-header-container">
            <home-header></home-header>
        </header>

        <div></div>

        <div class="g-backtop-container"></div>

        <router-view></router-view>
    </div>
</template>

<script>
    import HomeHeader from './header';
    
    export default {
        name: 'Home',
        components: {
            HomeHeader
        }
    }
</script>

<style lang="scss" scoped>
    @import '~assets/scss/mixins';
    .home{
        overflow: hidden;
        width: 100%;
        height: 100%;
        background-color: $bgc-theme;
    }
</style>
//home下 header.vue
<template>
    <me-navbar class="header" title='titl'>
        <i class="iconfont icon-scan" slot="left"></i>
        <div slot="center">搜索框</div>

        <i class="iconfont icon-msg" slot="right"></i>
    </me-navbar>
</template>

<script>
    import MeNavbar from 'base/navbar';

    export default {
        name: 'HomeHeader',
        components: {
            MeNavbar
        }
    }
</script>

<style lang="scss" scoped>
    @import "~assets/scss/mixins";
    .header{
        &.mine-navbar{
            //透明背景
            background-color: transparent;
            // background-color: $header-bgc-translucent;
        }
        

        .iconfont{
            color: $icon-color-default;
            font-size: $icon-font-size;
        }
    }
</style>
//base下navbar
<template>
    <div class="mine-navbar">
        <div class="mine-navbar-left" v-if="$slots.left">
            <slot name="left"></slot>
        </div>
        <div class="mine-navbar-center" v-if="$slots.center">
            <slot name="center"></slot>
        </div>
        <div class="mine-navbar-right" v-if="$slots.right">
            <slot name="right"></slot>
        </div>
        <h1 class="mine-navbar-title" v-if="title">
            <span class="mine-navbar-text" v-text="title"></span>
        </h1>
    </div>
</template>

<script>


    export default {
        name: 'nav',
        props: {
            title: {
                type: String,
                default: ''
            }
        }
    }

</script>

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

    .mine-navbar{
        position: relative;
        @include flex-between();
        height: 50px;
        background-color: #fff;

        &-left{
            margin-left: 10px;

            ~ .mine-navbar-right{
                position: static;
            }
        }

        &-center{
            flex: 1;
            margin: 0 10px;

             ~ .mine-navbar-right{
                position: static;
            }
        }

        &-right{
            position: absolute;
            right: 0;
            @include flex-center();
            height: 100%;
            margin-right: 10px;

        }

        &-title{
            position: absolute;
            top: 0;
            bottom: 0;
            left: 20%;
            right: 20%;
            @include flex-center();
            //解决span中  jqjqjqajq   被切掉j前面一段和q下面一段问题   主要因为设置了溢出隐藏
            text-align: center;
        }

        &-text{
            font-size: 18px;
            @include ellipsis();
            //解决span中  jqjqjqajq   被切掉j前面一段和q下面一段问题   主要因为设置了溢出隐藏
            line-height: 1.5;
            width: 100%;

        }
    }

</style>

轮播图

安装vue-awesome-swiper

cnpm install --save-dev vue-awesome-swiper@3.1.3

//main.js 全局引入css
import 'swiper/dist/css/swiper.css'
//home 下 index.vue
<template>
    <div class="home">
        <header class="g-header-container">
            <home-header></home-header>
        </header>
        <div>
            <home-slider></home-slider>
        </div>
        <div class="g-backtop-container"></div>
        <router-view></router-view>
    </div>
</template>

<script>
    import HomeHeader from './header';
    import HomeSlider from './slider';
    
    export default {
        name: 'Home',
        components: {
            HomeHeader,
            HomeSlider
        }
    }
</script>

<style lang="scss" scoped>
    @import '~assets/scss/mixins';
    .home{
        overflow: hidden;
        width: 100%;
        height: 100%;
        background-color: $bgc-theme;
    }
</style>
//header.vue
<template>
    <me-navbar class="header">
        <i class="iconfont icon-scan" slot="left"></i>
        <div slot="center">搜索框</div>

        <i class="iconfont icon-msg" slot="right"></i>
    </me-navbar>
</template>

<script>
    import MeNavbar from 'base/navbar';

    export default {
        name: 'HomeHeader',
        components: {
            MeNavbar
        }
    }
</script>

<style lang="scss" scoped>
    @import "~assets/scss/mixins";
    .header{
        &.mine-navbar{
            //透明背景
            background-color: transparent;
            // background-color: $header-bgc-translucent;
        }

        .iconfont{
            color: $icon-color-default;
            font-size: $icon-font-size;
        }
    }
</style>
//base下创建 slider 下创建index.vue
<template>
    <swiper :options="swiperOption">
        <slot></slot>
        <div class="swiper-pagination" v-if="pagination" slot="pagination"></div>
    </swiper>
</template>

<script>
    import { swiper } from 'vue-awesome-swiper'

    export default {
        name: 'MeSlider',
        components: {
            swiper
        },
        props: {
            direction: {
                type: String,
                default: 'horizontal',
                validator(value){
                    return [
                        'horizontal',
                        'vertical'
                    ].indexOf(value) > -1
                }
            },
            interval:{
                type: Number,
                default: 3000,
                validator(value){
                    return value >= 0
                }
            },
            loop:{
                type:Boolean,
                default: true
            },
            pagination:{
                type:Boolean,
                default: true
            }
        },
        data () {
            return {
                swiperOption: {
                    watchOverflow: true,
                    direction: this.direction,
                    autoplay: this.interval ? {
                        delay: this.interval,
                        disableOnInteraction: false
                    } : false,
                    slidesPerView: 1,
                    loop: this.loop,
                    pagination: {
                        el: this.pagination ? '.swiper-pagination' : null
                    }
                }
            };
        }
    };

</script>

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

    .swiper-container{
        height: 100%;
        width: 100%;
    }

</style>
//config.js
export const sliderOptions = {
    direction: 'horizontal',
    loop: true,
    interval: 0,
    pagination: true,
}

网络请求轮播图地址

安装axios插件,

cnpm install --save-dev axios 
//home 下的 index.vue
<template>
    <div class="slider-wrapper">
        <me-slider
        :direction="direction"
        :loop="loop"
        :interval="interval"
        :pagination="pagination"
        v-if="sliders.length" 
        >
            <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>
        </me-slider>
    </div>
    
</template>

<script>
    import MeSlider from 'base/slider';
    import { swiperSlide } from 'vue-awesome-swiper';
    import { sliderOptions } from './config';
    import { getHomeSlider } from 'api/home';
    export default {
        name: 'HomeSlider',
        components: {
            MeSlider,
            swiperSlide
        },
        data () {
            return{
                direction: sliderOptions.direction,
                loop: sliderOptions.loop,
                interval: sliderOptions.interval,
                pagination: sliderOptions.pagination,
                sliders: []
                //用v-if之后  可以让组件在加载完数据后  渲染        
            }
        },
        created () {
            this.getSliders();
        },
        methods: {
            getSliders() {
                getHomeSlider().then(data => {
                    console.log(data)
                    this.sliders = data
                })
            }
        }
    }
</script>

<style lang="scss" scoped>
    .slider-wrapper{
        height: 183px;
    }

    .slider-link{
        display: block;
    }

    .slider-link, .slider-img{
        width: 100%;
        height: 100%;
    }
</style>
//api下的  home.js
import axios from 'axios';
import { SUCC_CODE, TIMEOUT } from './config';

// 获取幻灯片数据 -- ajax
export const getHomeSlider = () => {
    return axios.get('http://www.imooc.com/api/home/slider', {
        //这里是为了延迟10s 让网络超时  执行catch
        // timeout: TIMEOUT
    }).then(res => {
        console.log(res)
        if(res.data.code === SUCC_CODE){
            return res.data.slider
        }

        throw new Error('没有成功获取到数据!')
    }).catch(err => {
        if (err){
            console.log(err);
        }

        return [
            {
                linkUrl: 'https://www.imooc.com',
                picUrl: require('assets/img/404.png')
            }
        ]
    }).then(data => {
        // 这里是延迟1s返回  为了加  正在加载效果
        return new Promise(resolve => {
            setTimeout(() => {
                resolve(data);
            }, 1000);
        });
    })
};
//api 下的 config.js
export const SUCC_CODE = 0

export const TIMEOUT = 10000;

正在加载组件

//home 下  slider.vue  引入正在加载组件并根据轮播图是否有数据显示
<template>
    <div class="slider-wrapper">
        <me-loading v-if="!sliders.length"></me-loading>
        <me-slider
        :direction="direction"
        :loop="loop"
        :interval="interval"
        :pagination="pagination"
        v-if="sliders.length" 
        >
            <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>
        </me-slider>
    </div>
    
</template>

<script>
    import MeSlider from 'base/slider';
    import { swiperSlide } from 'vue-awesome-swiper';
    import { sliderOptions } from './config';
    import { getHomeSlider } from 'api/home';
    import MeLoading from 'base/loading';
    export default {
        name: 'HomeSlider',
        components: {
            MeSlider,
            MeLoading,
            swiperSlide
        },
        data () {
            return{
                direction: sliderOptions.direction,
                loop: sliderOptions.loop,
                interval: sliderOptions.interval,
                pagination: sliderOptions.pagination,
                sliders: []
                //用v-if之后  可以让组件在加载完数据后  渲染        
            }
        },
        created () {
            this.getSliders();
        },
        methods: {
            getSliders() {
                getHomeSlider().then(data => {
                    console.log(data)
                    this.sliders = data
                })
            }
        }
    }
</script>

<style lang="scss" scoped>
    .slider-wrapper{
        height: 183px;
    }

    .slider-link{
        display: block;
    }

    .slider-link, .slider-img{
        width: 100%;
        height: 100%;
    }
</style>
//base下 slider组件  实现正在加载  
<template>
    <div class="mine-loading" :class="{'mine-loading-inline' : inline}">
        <span class="mine-loading-indicator" v-if="indicator === 'on'">
            <slot><img src="./loading.gif" alt=""></slot>
        </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{
        overflow: hidden;
        width: 100%;
        height: 100%;
        @include flex-center(column);

        &.mine-loading-inline {
            flex-direction: row;

            .mine-loading-indicator ~ .mine-loading-text{
                margin-top: 0px;
                margin-left: 6px;
            }
        }
    }

    .mine-loading-indicator ~ .mine-loading-text{
        margin-top: 6px;
    }
</style>

滚动条组件

//home内引入 scroll组件
<template>
    <div class="home">
        <header class="g-header-container">
            <home-header></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-backtop-container"></div>

        <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: {
            MeScroll,
            HomeHeader,
            HomeSlider
        }
    }
</script>

<style lang="scss" scoped>
    @import '~assets/scss/mixins';
    .home{
        overflow: hidden;
        width: 100%;
        height: 100%;
        background-color: $bgc-theme;
    }
</style>
//scroll组件
<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: 'scroll',
        components: {
            swiperSlide,
            swiper
        },
        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>
    .swiper-container{
        overflow: hidden;
        width: 100%;
        height: 100%;
    }

    .swiper-slide{
        height: auto;
    }

</style>

导航面板

在home引入  nav.vue

//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" class="nav-pic">
          <span>{{item.text}}</span>
        </a>
      </li>
    </ul>
  </nav>
</template>

<script>
    import {navItems} from './config';
    export default {
        name: 'HomeNav',
        created() {
            this.navs = navItems;
        }
    };
</script>

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

  .nav {
    width: 100%;
    padding-top: 15px;
    background-color: #fff;

    &-list {
      display: flex;
      flex-wrap: wrap;
    }

    &-item {
      width: 20%;
      margin-bottom: 15px;
    }

    &-link {
      @include flex-center(column);
    }

    &-pic {
      width: 60%;
      margin-bottom: 8px;
    }
  }

</style>
//config.js
export const sliderOptions = {
    direction: 'horizontal',
    loop: true,
    interval: 0,
    pagination: true,
}

export const navItems = [
    // 原nav.uve的数据
    {
      linkUrl: 'https://www.imooc.com',
      picUrl: require('./img/nav-item-1.png'),
      text: '拍卖'
    },
    {
      linkUrl: 'https://www.imooc.com',
      picUrl: require('./img/nav-item-2.png'),
      text: '拍卖'
    },
    {
      linkUrl: 'https://www.imooc.com',
      picUrl: require('./img/nav-item-3.png'),
      text: '拍卖'
    },
    {
      linkUrl: 'https://www.imooc.com',
      picUrl: require('./img/nav-item-4.png'),
      text: '拍卖'
    },
    {
      linkUrl: 'https://www.imooc.com',
      picUrl: require('./img/nav-item-5.png'),
      text: '拍卖'
    },
    {
      linkUrl: 'https://www.imooc.com',
      picUrl: require('./img/nav-item-6.png'),
      text: '拍卖'
    },
    {
      linkUrl: 'https://www.imooc.com',
      picUrl: require('./img/nav-item-7.png'),
      text: '拍卖'
    },
    {
      linkUrl: 'https://www.imooc.com',
      picUrl: require('./img/nav-item-8.png'),
      text: '拍卖'
    },
    {
      linkUrl: 'https://www.imooc.com',
      picUrl: require('./img/nav-item-9.png'),
      text: '拍卖'
    },
    {
      linkUrl: 'https://www.imooc.com',
      picUrl: require('./img/nav-item-10.png'),
      text: '拍卖'
    }
  ];
  

热卖推荐组件

安装jsonp、安装vue-lazyload

cnpm install --save-dev jsonp
cnpm install vue-lazyload --save-dev

main.js引入
import VueLazyload from 'vue-lazyload';

Vue.use(VueLazyload, {
  preLoad: 1,
  error: require('assets/img/error.png'),
  loading: require('assets/img/loading.gif'),
  attempt: 1
});
//home下index.vue 注意:对组件封装  并且数据加载完成后注意刷新
<template>
    <div class="home">
        <header class="g-header-container">
            <home-header></home-header>
        </header>
        <me-scroll :data="recommends">
            <home-slider></home-slider>

            <home-nav></home-nav>

            <home-recommend @loaded="getRecommends"></home-recommend>
        </me-scroll>

        <div class="g-backtop-container"></div>

        <router-view></router-view>
    </div>
</template>

<script>
    import MeScroll from 'base/scroll';
    import HomeHeader from './header';
    import HomeSlider from './slider';
    import HomeNav from './nav';
    import HomeRecommend from './recommend'
    
    export default {
        name: 'Home',
        components: {
            MeScroll,
            HomeHeader,
            HomeSlider,
            HomeNav,
            HomeRecommend
        },
        data(){
            return {
                recommends: []
            }
        },
        methods: {
            updateScroll() {

            },
            getRecommends(recommends) {
                this.recommends = recommends
            }
        }
    }
</script>

<style lang="scss" scoped>
    @import '~assets/scss/mixins';
    .home{
        overflow: hidden;
        width: 100%;
        height: 100%;
        background-color: $bgc-theme;
    }
</style>
//home下recommend  封装热卖商品组件 
<template>
    <div class="recommend">
        <h3 class="recommend-title">热卖推荐</h3>
        <div class="loading-container" v-if="!recommends.length">
            <me-loading inline/>
        </div>
        <ul class="recommend-list" v-else>
            <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" v-lazy="item.baseinfo.picUrlNew"></p>
                    <p class="recommend-name">{{item.name.shortName}}</p>
                    <p class="recommend-origPrice"><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',
        components: {
            MeLoading
        },
        data(){
            return {
                recommends: [],
                curPage: 1,
                totalPage: 1
            }
        },
        created() {
            this.getRecommend()
        },
        methods: {
            getRecommend(){
                if(this.curPage > this.totalPage){
                    return;
                }
                getHomeRecommend(this.curPage).then(data => {
                    if(data){
                        this.curPage++;
                        this.totalPage = data.totalPage
                        this.recommends = this.recommends.concat(data.itemList);
                        this.$emit('loaded', this.recommends);
                    }
                })
            }
        }  
    }
</script>

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

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

      &:before,
      &:after {
        content: '';
        position: absolute;
        top: 50%;
        width: 40%;
        height: 1px;
        background-color: #ddd;
      }
      &:before {
        left: 0;
      }
      &:after {
        right: 0;
      }
    }

    &-list {
      @include flex-between();
      flex-wrap: wrap;
    }

    &-item {
      width: 49%;
      background-color: #fff;
      box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.12);
      margin-bottom: 8px;
    }

    &-link {
      display: block;
    }

    &-pic {
      position: relative;
      width: 100%;
      padding-top: 100%;
      margin-bottom: 5px;
    }

    &-img {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }

    &-name {
      height: 36px;
      padding: 0 5px;
      margin-bottom: 8px;
      line-height: 1.5;
      @include multiline-ellipsis();
    }

    &-origPrice {
      padding: 0 5px;
      margin-bottom: 8px;
      color: #ccc;
    }

    &-info {
      @include flex-between();
      padding: 0 5px;
      margin-bottom: 8px;
    }

    &-price {
      color: #e61414;
    }

    &-price-num {
      font-size: 20px;
    }

    &-count {
      color: #999;
    }
  }

  .loading-container {
    padding-top: 100px;
  }
</style>
//base下  封装scroll组件
<template>
    <swiper :options="swiperOption" ref="swiper">
        <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: 'scroll',
        components: {
            swiperSlide,
            swiper
        },
        props: {
            scrollbar: {
                type: Boolean,
                default: true
            },
            data: {
                type: [Array, Object]
            }
        },
        watch: {
            data() {
                this.update();
            }
        },
        methods: {
            update(){
                console.log(this.$refs.swiper)
                this.$refs.swiper && this.$refs.swiper.swiper.update();
            }
        },
        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>
    .swiper-container{
        overflow: hidden;
        width: 100%;
        height: 100%;
    }

    .swiper-slide{
        height: auto;
    }

</style>
//base下loading组件
<template>
    <div class="mine-loading" :class="{'mine-loading-inline' : inline}">
        <span class="mine-loading-indicator" v-if="indicator === 'on'">
            <slot><img src="./loading.gif" alt=""></slot>
        </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{
        overflow: hidden;
        width: 100%;
        height: 100%;
        @include flex-center(column);

        &.mine-loading-inline {
            flex-direction: row;

            .mine-loading-indicator ~ .mine-loading-text{
                margin-top: 0px;
                margin-left: 6px;
            }
        }
    }

    .mine-loading-indicator ~ .mine-loading-text{
        margin-top: 6px;
    }
</style>

jsonp请求

//api下封装  home.js  请求jsonp   
import axios from 'axios';
import jsonp from 'assets/js/jsonp';
import {SUCC_CODE, TIMEOUT, HOME_RECOMMEND_PAGE_SIZE, jsonpOptions} from './config';

// 获取幻灯片数据 -- ajax
export const getHomeSlider = () => {
    return axios.get('http://www.imooc.com/api/home/slider', {
        //这里是为了延迟10s 让网络超时  执行catch
        // timeout: TIMEOUT
    }).then(res => {
        // console.log(res)
        if(res.data.code === SUCC_CODE){
            return res.data.slider
        }

        throw new Error('没有成功获取到数据!')
    }).catch(err => {
        if (err){
            console.log(err);
        }

        return [
            {
                linkUrl: 'https://www.imooc.com',
                picUrl: require('assets/img/404.png')
            }
        ]
    }).then(data => {
        // 这里是延迟1s返回  为了加  正在加载效果
        return new Promise(resolve => {
            setTimeout(() => {
                resolve(data);
            }, 1000);
        });
    })
};

// 获取热门推荐数据--jsonp
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: ''
    };
  
    return jsonp(url, params, jsonpOptions).then(res => {
        console.log('jsonp', 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);
      });
    });
  };
//home下 config.js
export const SUCC_CODE = 0;

export const TIMEOUT = 10000;

export const HOME_RECOMMEND_PAGE_SIZE = 20;

export const jsonpOptions = {
  param: 'callback',
  timeout: TIMEOUT
};

封装基本jsonp

//assets 下面 js  jsonp.js 封装基本jsonp
import jsonp from 'jsonp';

const parseParam = param => {
    let params = [];
  
    for (const key in param) {
      params.push([key, encodeURIComponent(param[key])]);
    }
    // [[page, 1], [pszie, 20]]
    return params.map(value => value.join('=')).join('&');
    // [[page, 1], [pszie, 20]]
    // [page=1, psize=20]
    // page=1&psize=20
  };

export default (url, data, options) => {
    url += (url.indexOf('?') < 0 ? '?' : '&') + parseParam(data)

    // jsonp(url, opti)
    return new Promise((resolve, reject) => {
        jsonp(url, options, (err, data) => {
            if(err) {
                reject(err)
            } else {
                resolve(data)
            }
        });
    })
}

下拉刷新、上拉加载

下拉提示文字变化、松手刷新、刷新幻灯片。上拉加载更多。

注意:下拉刷新轮播图后需要更新轮播图,但是loop没有改变,拉到最后一页有bug。解决方法:查找重新加载api,但是官方未提供。那么我们采用给组件变换KEY来达到重新加载,这个是VUE特性。

资源地址

 

 

 

Logo

前往低代码交流专区

更多推荐