vue2实现京东商城

简单介绍

基于vue2实现的京东商城,现展示几个比较重要的模块。
tabbar功能,首页轮播图功能、首页倒计时秒杀功能,列表页等。
vue2实现京东商城_第1张图片

vue2实现京东商城_第2张图片
vue2实现京东商城_第3张图片

Tabbar功能

在这里插入图片描述

TabBar.vue

  <template>
<div id="tab-bar">
  <slot>slot>
div>
template>

<script>
export default {
name: 'TabBar'
}
script>

<style>
  #tab-bar {
  display: flex;
  background-color: rgb(255, 255, 255);;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  box-shadow: 0 0 10px 0 hsl(0deg 6% 58% / 60%);
}
style>
TabBarItem

  <template>
  <div class="tab-bar-item" @click="itemClick">
    <div v-if="!isActive">
      <slot name="item-icon">slot>
    div>
    <div v-else>
      <slot name="item-icon-active">slot>
    div>
  div>
template>

<script>
export default {
  name: 'TabBarItem',
  props: {
    path: String,

  },
  computed: {
    isActive() {
      return this.$route.path.indexOf(this.path) !== -1
    }
  },
  methods: {
    itemClick() {
      this.$router.replace(this.path)
    }
  }
}
script>

<style>
.tab-bar-item {
  flex: 1;
  text-align: center;
  height: 49px;
  font-size: 14px;
}

.tab-bar-item img {
  width: 50%;
  height: 50%;
  margin-top: 3px;
  vertical-align: middle;
  margin-bottom: 2px;
}
style>
MainTabBar

  <template>
    <tab-bar>
      <tab-bar-item path="/home">
      <img slot="item-icon" src="~assets/img/tabbar/home.png" alt="">
      <img slot="item-icon-active" src="~assets/img/tabbar/home_active.png" alt="">
      tab-bar-item>
      <tab-bar-item path="/category">
      <img slot="item-icon" src="~assets/img/tabbar/category.png" alt="">
      <img slot="item-icon-active" src="~assets/img/tabbar/category_active.png" alt="">
      tab-bar-item>
      <tab-bar-item path="/user">
      <img slot="item-icon" src="~assets/img/tabbar/user.png" alt="">
      <img slot="item-icon-active" src="~assets/img/tabbar/user_active.png" alt="">      
      tab-bar-item>
    tab-bar>
template>

<script>
  import TabBar from '@/components/common/tabbar/TabBar.vue';
  import TabBarItem from '@/components/common/tabbar/TabBarItem.vue';
export default {
  name: 'MainTabBar',
  components: {
    TabBar,
    TabBarItem
  },
}
script>

<style>
style>
路由router

import Vue from "vue";
import VueRouter from "vue-router"

Vue.use(VueRouter)

const Home=()=>import('views/home/Home')
const Category=()=>import('views/category/Category')
const User=()=>import('views/user/User')

const routes=[
  {
    path:'',
    redirect:'/home'
  },
  {
    path:'/home',
    component:Home
  },
  {
    path:'/category',
    component:Category
  },
  {
    path:'/user',
    component:User
   },
]
const router= new VueRouter({
  routes,
  mode:'history'
})
export default router

首页轮播图功能

vue2实现京东商城_第4张图片

Swiper

<template>
    <div id="hy-swiper">
      <div class="swiper" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
        <slot>slot>
      div>
      <slot name="indicator">
      slot>
      <div class="indicator">
        <slot name="indicator" v-if="showIndicator && slideCount>1">
          <div v-for="(item, index) in slideCount" class="indi-item" :class="{active: index === currentIndex-1}" :key="index">div>
        slot>
      div>
    div>
template>

<script>
	export default {
		name: "Swiper",
    props: {
      interval: {
		    type: Number,
        default: 3000
      },
      animDuration: {
		    type: Number,
        default: 300
      },
      moveRatio: {
        type: Number,
        default: 0.25
      },
      showIndicator: {
        type: Boolean,
        default: true
      }
    },
    data: function () {
		  return {
        slideCount: 0, // 元素个数
        totalWidth: 0, // swiper的宽度
        swiperStyle: {}, // swiper样式
        currentIndex: 1, // 当前的index
        scrolling: false, // 是否正在滚动
      }
    },
    mounted: function () {
      // 1.操作DOM, 在前后添加Slide
      setTimeout(() => {
        this.handleDom();

        // 2.开启定时器
        this.startTimer();
      }, 100)
    },
    methods: {
		  /**
       * 定时器操作 
       ds* 5  
       */
      startTimer: function () {
		    this.playTimer = window.setInterval(() => {
		      this.currentIndex++;
		      this.scrollContent(-this.currentIndex * this.totalWidth);
        }, this.interval)
      },
      stopTimer: function () {
        window.clearInterval(this.playTimer);
      },

      /**
       * 滚动到正确的位置
       */
      scrollContent: function (currentPosition) {
        // 0.设置正在滚动
        this.scrolling = true;

        // 1.开始滚动动画
        this.swiperStyle.transition ='transform '+ this.animDuration + 'ms';
        this.setTransform(currentPosition);

        // 2.判断滚动到的位置
        this.checkPosition();

        // 4.滚动完成
        this.scrolling = false
      },

      /**
       * 校验正确的位置
       */
      checkPosition: function () {
        window.setTimeout(() => {
          // 1.校验正确的位置
          this.swiperStyle.transition = '0ms';
          if (this.currentIndex >= this.slideCount + 1) {
            this.currentIndex = 1;
            this.setTransform(-this.currentIndex * this.totalWidth);
          } else if (this.currentIndex <= 0) {
            this.currentIndex = this.slideCount;
            this.setTransform(-this.currentIndex * this.totalWidth);
          }

          // 2.结束移动后的回调
          this.$emit('transitionEnd', this.currentIndex-1);
        }, this.animDuration)
      },

      /**
       * 设置滚动的位置
       */
      setTransform: function (position) {
        this.swiperStyle.transform = `translate3d(${position}px, 0, 0)`;
        this.swiperStyle['-webkit-transform'] = `translate3d(${position}px), 0, 0`;
        this.swiperStyle['-ms-transform'] = `translate3d(${position}px), 0, 0`;
      },

      /**
       * 操作DOM, 在DOM前后添加Slide
       */
		  handleDom: function () {
        // 1.获取要操作的元素
        let swiperEl = document.querySelector('.swiper');
        let slidesEls = swiperEl.getElementsByClassName('slide');

        // 2.保存个数
        this.slideCount = slidesEls.length;

        // 3.如果大于1个, 那么在前后分别添加一个slide
        if (this.slideCount > 1) {
          let cloneFirst = slidesEls[0].cloneNode(true);
          let cloneLast = slidesEls[this.slideCount - 1].cloneNode(true);
          swiperEl.insertBefore(cloneLast, slidesEls[0]);
          swiperEl.appendChild(cloneFirst);
          this.totalWidth = swiperEl.offsetWidth;
          this.swiperStyle = swiperEl.style;
        }

        // 4.让swiper元素, 显示第一个(目前是显示前面添加的最后一个元素)
        this.setTransform(-this.totalWidth);
      },

      /**
       * 拖动事件的处理
       */
      touchStart: function (e) {
        // 1.如果正在滚动, 不可以拖动
        if (this.scrolling) return;

        // 2.停止定时器
        this.stopTimer();

        // 3.保存开始滚动的位置
        this.startX = e.touches[0].pageX;
      },

      touchMove: function (e) {
        // 1.计算出用户拖动的距离
        this.currentX = e.touches[0].pageX;
        this.distance = this.currentX - this.startX;
        let currentPosition = -this.currentIndex * this.totalWidth;
        let moveDistance = this.distance + currentPosition;

        // 2.设置当前的位置
        this.setTransform(moveDistance);
      },

      touchEnd: function (e) {
        // 1.获取移动的距离
        let currentMove = Math.abs(this.distance);

        // 2.判断最终的距离
        if (this.distance === 0) {
          return
        } else if (this.distance > 0 && currentMove > this.totalWidth * this.moveRatio) { // 右边移动超过0.5
          this.currentIndex--
        } else if (this.distance < 0 && currentMove > this.totalWidth * this.moveRatio) { // 向左移动超过0.5
          this.currentIndex++
        }

        // 3.移动到正确的位置
        this.scrollContent(-this.currentIndex * this.totalWidth);

        // 4.移动完成后重新开启定时器
        this.startTimer();
      },

      /**
       * 控制上一个, 下一个
       */
      previous: function () {
        this.changeItem(-1);
      },

      next: function () {
        this.changeItem(1);
      },

      changeItem: function (num) {
        // 1.移除定时器
        this.stopTimer();

        // 2.修改index和位置
        this.currentIndex += num;
        this.scrollContent(-this.currentIndex * this.totalWidth);

        // 3.添加定时器
        this.startTimer();
      }
    }
	}
script>

<style scoped>
  #hy-swiper {
    overflow: hidden;
    position: relative;
  }

  .swiper {
    display: flex;
  }

  .indicator {
    display: flex;
    justify-content: center;
    position: absolute;
    width: 100%;
    bottom: 8px;
  }

  .indi-item {
    box-sizing: border-box;
    width: 5px;
    height: 5px;
    border-radius: 4px;
    background-color: hsla(0,0%,100%,.6);
    line-height: 8px;
    text-align: center;
    font-size: 12px;
    margin: 0px 2px;
  }

  .indi-item.active {
    background-color: rgba(212,62,46,1.0);
  }
style>

SwiperItem

<template>
  <div class="slide">
    <slot>slot>
  div>
template>

<script>
	export default {
		name: "Slide"
	}
script>

<style scoped>
  .slide {
    width: 100%;
    flex-shrink: 0;
  }

  .slide img {
    width: 100%;
  }
style>
HomeSwiper

  <template>
  <swiper class="swiper-s">
    <swiper-item v-for="item in banners" class="swiper-item">
      <a href="javascript:;" class="slide-a">
      <img :src="item.image" alt="">a>
    swiper-item>
  swiper>
template>

<script>
import { Swiper,SwiperItem } from '@/components/common/swiper';
export default {
name: 'HomeSwiper',
props:{
  banners:{
    type:Array,
    default() {
      return []
    }
  }
},
components:{
  Swiper,
  SwiperItem
},

}
script>

<style>
.swiper-s {
    position: relative;
    top: 43px;
    height: 134px;
  }
.slide-a {
    display: block;
    overflow: hidden;
    width: calc(100% - 1.25rem);
    height: 134px;
    margin: 0 auto;
    /* top: 2.25rem; */
    position: relative;
    border-radius: 0.35rem;
}
.swiper-item {
  height: 100%;
  position: relative;
}
style>

京东倒计时秒杀功能

vue2实现京东商城_第5张图片

floor-container

<template>
  <div class="floor-container">
    <ul class="seckill-new-list">
      <li class="seckill-new-item" v-for="item in skgoods">
        <div class="seckill-item-img">
          <a href="javascript:;" class="seckill-new-link">
            <img :src="item.image" alt="">
          a>
        div>
        <div class="seckill-item-price"> 
          <span class="seckill-new-price">
            <em>em>
          <span class="j_init_price">{{item.price}}span>
          span>
        div>
      li>
    ul>
  div>
template>

<script>
export default {
  name: 'FloorContainer',
  props:{
    skgoods:{
      type:Array,
      default() {
      return []
    }
    }
  }
}
script>

<style>
.floor-container {
  overflow-x: scroll;
  overflow-y: hidden;
  width: calc(100% - 0.8rem);
  margin: 0 auto;
  font-size: 0;
  overflow: auto;
  padding: 0 1px;
}
.floor-container::-webkit-scrollbar{
	display: none;
}
.seckill-new-list {
  position: relative;
  background-color: #fff;
  width: 609.333px;
  overflow-y: hidden;
}
.seckill-new-item {
  margin-top: 0.62rem;
  margin-bottom: 0.62rem;
    float: left;
}
.seckill-item-img, .seckill-item-price {
    width: 100%;
    padding: 0 0.062rem;
}
.seckill-item-price {
    margin: 0 auto;
    text-align: center;
}
.seckill-new-link {
    display: block;
    position: relative;
    width: 56px;
}
.floor-container img {
    width: 100%;
    height: auto;
    overflow: hidden;
    min-height: 1px;
    min-width: 1px;
}
.seckill-item-price {
    margin: 0 auto;
    text-align: center;
    width: 100%;
    padding: 0 0.05rem;
}
.seckill-new-price {
    margin-top: 0.5rem;
    display: block;
    color: #f2270c;
    font-size: .65rem;
    line-height: .6rem;
    height: 0.6rem;
    text-align: center;
}
.seckill-new-price em {
    font-size: 11px;
    padding-right: 2px;
    font-style:normal;
}
style>
floor-title

<template>
  <div class="title-wrap">
    <a href="javascript:;" class="seckill-left-link">
      <div class="seckill-tit-img">京东秒杀div>
      <div class="seckill-timer-wrap">
        <div class="seckill-nth">
          {{time}}
        div>
        <div class="seckill_timer">
          <div class="seckill-time j_sk_h">{{hour}}div>
          <span class="seckill-time-separator">:span>
          <div class="seckill-time j_sk_m">{{minute}}div>
          <span class="seckill-time-separator">:span>
          <div class="seckill-time j_sk_s">{{second}}div>
        div>
      div>
    a>
    <a href="javascript:;" class="seckill-more-link">
      爆款轮番秒
      <i class="seckill-more-icon">i>
    a>
  div>
template>

<script>
export default {
  name: 'FloorTitle',
  data(){
    return {
      date:new Date(),
      hours:this.time-new Date().getHours()-1,
      minutes:60-new Date().getMinutes()-1,
      seconds:60-new Date().getSeconds(),
 
    }
  },
  mounted() {
    this.add();
  },
  props:{
    time:Number
  },
  methods:{
    num(n) {
      return n < 10 ? '0' + n : ''+n
    },
    add() {
      let time = window.setInterval(()=>{
        if(this.hours !== 0&&this.minutes === 0&&this.seconds === 0){
          this.hours-=1;
          this.minutes=59;
          this.seconds=59;
        }else if(this.hours === 0&&this.minutes !== 0&&this.seconds===0){
          this.minutes-=1;
          this.seconds=59;
        }else if(this.hours === 0&& this.minutes ===0 &&this.seconds===0){
          this.seconds = 0
          window.clearInterval(time)
        }else if(this.hours!== 0&&this.minutes!==0&&this.seconds===0){
          this.minutes-=1;
          this.seconds=59;
        }else {
          this.seconds-=1;
        }
      },1000)
    }
  },
  watch:{

    second:{
      handler(newVal) {
        this.num(newVal)
      }
    },
    minute:{
      handler(newVal) {
        this.num(newVal)
      }
    },
    hour: {
      handler(newVal){
        this.num(newVal)
      }
    }
  },
  computed :{
    second() {
      return this.num(this.seconds)
    },
    minute() {
      return this.num(this.minutes)
    },
    hour() {
      return this.num(this.hours)
    }
  }
}
script>

<style scoped>
.title-wrap {
  background: url(~assets/img/home/seckill/bac.png) no-repeat;
  background-size: contain;
  height: 2.1rem;
  line-height: 2.1rem;
  vertical-align: middle;
}

.seckill-left-link {
  display: inline-block;
  float: left;
}

.seckill-tit-img {
  float: left;
  display: inline-block;
  height: 1.66rem;
  margin-top: 0.5rem;
  margin-right: 0.31rem;
  margin-left: 0.6rem;
  font-family: PingFangSC-Semibold;
  font-size: 0.9rem;
  color: #333;
  letter-spacing: 0;
  line-height: 0.9rem;
}

.seckill-timer-wrap {
  display: inline-block;
  border-radius: 0.4rem;
  height: 1.1rem;
  line-height: 1.1rem;
  font-size: 0;
  float: left;
  margin-top: 0.5rem;
  vertical-align: middle;
}

.seckill-nth {
  height: 100%;
  border-radius: 0.4rem;
  font-size: .85rem;
  color: #ff2727;
  letter-spacing: 0;
  padding-right: 0.31rem;
  padding-left: 0.31rem;
  float: left;
  position: relative;
  left: -1px;
  margin-right: 0.25rem;
  line-height: 1.2;
}

.seckill-nth::after {
  height: 1.08rem;
  width: 1.27rem;
  content: "";
  display: inline-block;
  vertical-align: middle;
  background-image: url(~assets/img/home/seckill/bac2.png);
  background-repeat: no-repeat;
  background-position: 50%;
  background-size: contain;
}

.seckill_timer {
  margin-right: 0.2rem;
  height: 100%;
  float: right;
}

.seckill-time {
  width: 0.92rem;
  color: #fff;
  background-image: linear-gradient(-140deg, #ff6152, #fa2c19);
  background-color: #fa2c19;
  border-radius: 0.2rem;
  text-align: center;
  font-weight: 400;
  float: left;
  display: inline-block;
  height: 100%;
  line-height: 1.4;
  font-size: .6rem;
  font-family: PingFangSC-Regular;
  letter-spacing: 0;
}
.seckill-time-separator {
    float: left;
    display: inline-block;
    height: 100%;
    line-height: 1.4;
    font-size: .6rem;
    text-align: center;
    font-family: PingFangSC-Regular;
    color: #f2270c;
    letter-spacing: 0;
    font-weight: 700;
}
.seckill-more-link {
    display: inline-block;
    color: #f23030;
    float: right;
    font-size: .6rem;
    text-align: right;
    position: relative;
    padding-right: 1.4rem;
}
.seckill-more-icon {
    display: inline-block;
    width: 0.68rem;
    height: 0.68rem;
    background: url(~assets/img/home/seckill/title_more.png) no-repeat;
    background-size: cover;
    position: absolute;
    right: 0.491rem;
    top: 0.61rem;
}
style>

列表页

vue2实现京东商城_第6张图片

category-body

<template>
  <div class="category-viewport">
    <cate-list :catetab="catetab" @tabClick="tabClick" ref="tabControl">cate-list>
    <cate-goods :goods="showGoods"
    >cate-goods>

  div>
template>

<script>
import CateGoods from '@/components/content/category/CateGoods.vue';
import CateList from '@/components/content/category/CateList.vue';

export default {
  name: 'CateGoryBody',
  components: {
    CateGoods,
    CateList
  },
  data() {
    return {
      catetab: ['热门推荐', '手机数码', '京东超市', '家用电器',
        '电脑办公', '玩具乐器', '家居厨具', '家具家装', '男装', '女装',
        '女鞋', '美妆护肤', '医药健保', '酒水饮料', '运动户外', '汽车生活', '礼品鲜花'],
        currentNum:'0',
        goods:{
          '0':{list:[
            {
              img:require('../../../assets/img/category/0/1.jpg'),
              title:'空调'
            },
            {
              img:require('../../../assets/img/category/0/2.jpg'),
              title:'冰箱'
            },
            {
              img:require('../../../assets/img/category/0/3.png'),
              title:'电脑'
            },
            {
              img:require('../../../assets/img/category/0/4.png'),
              title:'手机'
            },
            {
              img:require('../../../assets/img/category/0/5.jpg'),
              title:'全面屏手机'
            },
            {
              img:require('../../../assets/img/category/0/6.jpg'),
              title:'保健品'
            },
            {
              img:require('../../../assets/img/category/0/7.jpg'),
              title:'游戏手机'
            },
            {
              img:require('../../../assets/img/category/0/8.jpg'),
              title:'口罩'
            },
            {
              img:require('../../../assets/img/category/0/9.jpg'),
              title:'驱蚊用品'
            },
            {
              img:require('../../../assets/img/category/0/10.jpg'),
              title:'电磁炉'
            },
            {
              img:require('../../../assets/img/category/0/11.jpg'),
              title:'电热水壶'
            },
            {
              img:require('../../../assets/img/category/0/12.jpg'),
              title:'数据线'
            },
            {
              img:require('../../../assets/img/category/0/13.jpg'),
              title:'图书'
            },
            {
              img:require('../../../assets/img/category/0/14.jpg'),
              title:'美妆护肤'
            },
            {
              img:require('../../../assets/img/category/0/15.jpg'),
              title:'除菌液'
            },
            {
              img:require('../../../assets/img/category/0/16.jpg'),
              title:'休闲零食'
            },
            {
              img:require('../../../assets/img/category/0/17.jpg'),
              title:'充电包'
            },
            {
              img:require('../../../assets/img/category/0/18.jpg'),
              title:'体温计'
            },
            {
              img:require('../../../assets/img/category/0/19.jpg'),
              title:'投影机'
            },
            {
              img:require('../../../assets/img/category/0/20.jpg'),
              title:'游戏机'
            },
          ]},
          '1':{list:[
            {
              img:require('../../../assets/img/category/0/1.jpg')
            }
          ]},
          '2':{list:[
            {
              img:require('../../../assets/img/category/0/2.jpg')
            }
          ]},
          '3':{list:[
            {
              img:require('../../../assets/img/category/0/3.png')
            }
          ]},
          '4':{list:[
            {
              img:require('../../../assets/img/category/0/4.png')
            }
          ]},
          '5':{list:[
            {
              img:require('../../../assets/img/category/0/5.jpg')
            }
          ]},
          '6':{list:[
            {
              img:require('../../../assets/img/category/0/6.jpg')
            }
          ]},
          '7':{list:[
            {
              img:require('../../../assets/img/category/0/7.jpg')
            }
          ]},
          '8':{list:[
            {
              img:require('../../../assets/img/category/0/8.jpg')
            }
          ]},
          '9':{list:[
            {
              img:require('../../../assets/img/category/0/9.jpg')
            }
          ]},
          '10':{list:[
            {
              img:require('../../../assets/img/category/0/10.jpg')
            }
          ]},
          '11':{list:[
            {
              img:require('../../../assets/img/category/0/11.jpg')
            }
          ]},
          '12':{list:[
            {
              img:require('../../../assets/img/category/0/12.jpg')
            }
          ]},
          '13':{list:[
            {
              img:require('../../../assets/img/category/0/13.jpg')
            }
          ]},
          '14':{list:[
            {
              img:require('../../../assets/img/category/0/14.jpg')
            }
          ]},
          '15':{list:[
            {
              img:require('../../../assets/img/category/0/15.jpg')
            }
          ]},
          '16':{list:[
            {
              img:require('../../../assets/img/category/0/16.jpg')
            }
          ]}
        }
    }
  },
  computed: {
    showGoods() {
      return this.goods[this.currentNum].list
    }
  },
  methods :{
    tabClick(index) {
      console.log(this.catetab.length);
      for(let i=0;i<this.catetab.length;i++){
        if(index===i){
          this.currentNum=index
          break
        }
      }
      this.$refs.tabControl.currentIndex = index;
    }
  }
}
script>

<style scoped>
.category-viewport {
  height: auto;
  min-height: 100%;
  margin-top: 45px;
}
style>

你可能感兴趣的:(前端项目,vue.js,javascript,前端)