Vue电商项目实战(二)

一、轮播图

1.1 基本用法

资料地址:https://didi.github.io/cube-ui/#/zh-CN/docs/slide

基本用法:

<cube-slide :data="items" :interval="5000">cube-slide>

:data 代表轮播图的数据;
:interval 代表滚动时间间隔,以毫秒为单位;

1.2 定义轮播图

打开Home.vue文件,添加轮播图组件。

<cube-slide :data="slider" :interval="5000">
  <cube-slide-item v-for="item in slider" :key="item.id">
    <router-link :to="`/detail/${item.id}`">
      <img :src="item.img" class="slider"/>
    router-link>
  cube-slide-item>
cube-slide>

轮播图样式:


从后台获取slider轮播图数据。

async created() {
  const ret = await this.$http.get('/api/goods');
  this.slider = ret.data.slider;
},
data() {
  return {
    slider: [],
  }
},

1.3 后台商品数据

下面是获取商品接口的代码:

// 获取商品数据
app.get("/api/goods", function(req, res) {
	res.json({
		code: 0,
		slider: [
			{
				id: 21,
				img: "/img/01.jpg"
			},
			{
				id: 22,
				img: "/img/02.jpg"
			},
			{
				id: 23,
				img: "/img/03.jpg"
			},
			{
				id: 24,
				img: "/img/04.jpg"
			}
		],
	});
});

运行效果:
Vue电商项目实战(二)_第1张图片

二、Tab导航

2.1 自定义插槽

资料地址:https://didi.github.io/cube-ui/#/zh-CN/docs/tab-bar

修改App.vue文件:

<cube-tab-bar show-slider
	v-model="selectedLabel"
	@change="changeHandler">
	
	<cube-tab v-for="(item, index) in tabs" :key="index"
			:icon="item.icon" :label="item.label" :value="item.value">
		<span>{{item.label}}span>
	cube-tab>
cube-tab-bar>

2.2 定义页签

定义selectedLabel和tabs属性,selectedLabel属性表示默认选定的页签;tabs属性提供了所有页签的数据。

export default {
  computed: {
    ...mapGetters(['isLogin'])
  },
  data () {
    return {
      selectedLabel: '/',  // 默认选定页签
      tabs: [ // 页签数据
        {label: 'Home', value: '/', icon: 'cubeic-home'},
        {label: 'Cart', value: '/cart', icon: 'cubeic-mall'},
        {label: 'Me', value: '/login', icon: 'cubeic-person'},
      ]
    }
  },
  watch: { 
    $route(route) { 
      this.selectedLabel = route.path;
    }
  }
}

上面在watch中监控路由状态的变化。当路由发生变化,同步tabs选中状态。showBadge方法用于控制购物车商品数量的显示。只有当页签为购物车,并且购物车商品数量大于0的时候,才会显示数量。

2.3 页签显示效果优化

/* 页签样式 */
.cube-tab-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: #edf0f4;
}

/* 页签滚动条样式 */
.cube-tab-bar-slider {
  top: 0; 
}

/* 动画设置 */
.route-move-enter { /* 入场前 */
  transform: translate3d(-100%, 0, 0);
}

.route-move-leave-to { /* 出场后 */
  transform: translate3d(100%, 0, 0);
}

.route-move-enter, 
.route-move-leave-active { /* 播放动画过程中 */
  transition: transform 0.3s; /* 完成动画需要的时间 */
}

.child-view {
  width: 100%;
  position: absolute;
  left: 0;
  top: 0;
  padding-bottom: 36px;
}

span.badge {
  display: inline-block;
  background: #de3529;
  color: white;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
}

运行效果:
Vue电商项目实战(二)_第2张图片

三、显示商品列表

3.1 构件商品列表页

在components目录下新建GoodsList.vue文件,购物车商品数据从外部传入。

<template>
    <div>
        <div v-for="item in goods" :key="item.id" class="item">
            <router-link :to="`/detail/{item.id}`">
                <div class="left">
                    <img :src="item.img" @click.stop.prevent="imgPreview(item.img)"/>
                div>
                <div class="right">
                    <div class="title">{{item.title}}div>
                    <div class="info">
                        <i class="cubeic-add" @click.stop.prevent="addCart(item)">+i>
                        <span>{{item.count}}人购买span>
                    div>
                div>
            router-link>
        div>
    div>
template>

<script>
    export default {
        props: ['goods'],
        methods: {
            // 添加购物车
            addCart(item) {
                console.log('添加购物车');
            },
            // 图片预览
            imgPreview(img) {
                console.log('图片预览');
            },
        },
    }
script>

<style lang="stylus" scope>
.item {
  padding: 10px;
  overflow: hidden;
  .left {
    width: 100px;
    float: left;
    img {
      width: 100%;
    }
  }
  .right {
    margin-left: 120px;
    text-align: left;
    .title {
      line-height: 30px;
    }
    .cubeic-add {
      font-size: 22px;
    }
  }
}
style>

3.2 定义商品列表

在Home.vue文件中导入GoodsList。

import GoodsList from '@/components/GoodsList.vue'

export default {
  name: 'home',
  components: {
    GoodsList,
  },
  ...
}

在轮播图下定义商品列表。


<goods-list :goods="goods">goods-list>

3.3 定义商品

async created() {
	const ret = await this.$http.get('/api/goods');
	this.slider = ret.data.slider;
	this.goods = ret.data.data;
},
data() {
	return {
		slider: [],
		goods: [],
	}
},

3.4 后台商品数据

获取商品数据的接口如下所示:

// 获取商品数据
app.get("/api/goods", function(req, res) {
	res.json({
		code: 0,
		slider: [
			{
				id: 21,
				img: "/img/01.jpg"
			},
			{
				id: 22,
				img: "/img/02.jpg"
			},
			{
				id: 23,
				img: "/img/03.jpg"
			},
			{
				id: 24,
				img: "/img/04.jpg"
			}
		],
		data: {
			fe: [
				{
					id: 1,
					title: "Vue2.x实战",
					price: "100",
					img: "/img/01.jpg",
					count: 100
				},
				{
					id: 2,
					title: "React16.x实战",
					price: "120",
					img: "/img/03.jpg",
					count: 100
				},
				{
					id: 3,
					title: "nodejs实战",
					price: "80",
					img: "/img/02.jpg",
					count: 100
				},
				{
					id: 4,
					title: "前端工程化",
					price: "110",
					img: "/img/04.jpg",
					count: 100
				},
				{
					id: 5,
					title: "面试",
					price: "200",
					img: "/img/02.jpg",
					count: 100
				},
				{
					id: 6,
					title: "前端安全",
					price: "30",
					img: "/img/05.jpg",
					count: 100
				}
			],
			python: [
				{
					id: 7,
					title: "Python基础语法",
					price: "120",
					img: "/img/03.jpg",
					count: 101
				},
				{
					id: 8,
					title: "Flask实战",
					price: "80",
					img: "/img/02.jpg",
					count: 100
				},
				{
					id: 9,
					title: "Django实战",
					price: "110",
					img: "/img/01.jpg",
					count: 100
				},
				{
					id: 10,
					title: "Python语法进阶",
					price: "200",
					img: "/img/04.jpg",
					count: 100
				}
			],
			java: [
				{
					id: 11,
					title: "java入门实战",
					price: "80",
					img: "/img/02.jpg",
					count: 100
				},
				{
					id: 12,
					title: "spring boot实战",
					price: "110",
					img: "/img/01.jpg",
					count: 100
				},
				{
					id: 13,
					title: "Java高并发",
					price: "30",
					img: "/img/04.jpg",
					count: 100
				}
			],
			bigdata: [
				{
					id: 14,
					title: "大数据实战",
					price: "200",
					img: "/img/01.jpg",
					count: 100
				},
				{
					id: 15,
					title: "Hadoop实战",
					price: "120",
					img: "/img/03.jpg",
					count: 100
				},
				{
					id: 16,
					title: "Kafka平台",
					price: "80",
					img: "/img/02.jpg",
					count: 100
				}
			],
			ai: [
				{
					id: 17,
					title: "算法实战",
					price: "100",
					img: "/img/01.jpg",
					count: 100
				},
				{
					id: 18,
					title: "个性化推荐",
					price: "120",
					img: "/img/03.jpg",
					count: 100
				},
				{
					id: 19,
					title: "机器学习",
					price: "80",
					img: "/img/02.jpg",
					count: 100
				},
				{
					id: 20,
					title: "AI实战",
					price: "110",
					img: "/img/05.jpg",
					count: 100
				}
			]
		},
		keys: ["fe", "python", "java", "bigdata", "ai"]
	});
});

data属性封装了商品分类以及各个分类的商品信息。keys属性封装了所有分类的名称。

运行效果:
Vue电商项目实战(二)_第3张图片

四、添加购物车

4.1 添加购物方法实现

修改GoodsList.vue文件,实现addCart方法。

// 添加购物车
addCart(item) {
    // 把添加购物车的商品放在state中
    this.$store.commit('addCart', item);
},

在state中定义cart属性,该属性存储了购物车的商品。

state: {
  token: localStorage.getItem('token') || '', // 令牌
  cart: JSON.parse(localStorage.getItem('cart')) || [], // 购物车商品
},

在mutations中定义addCart属性。

// 添加购物车
addCart(state, item) { 
	const good = state.cart.find(v => v.id === item.id);
	if (good) {
		good.count += 1;
	} else {
		state.cart.push({
			...item,
			count: 1,
		});
	}
},

当购物车中存在指定商品,则该商品的count属性加1。如果购物车中不存在该商品,则添加商品。

4.2 订阅mutations

添加购物车后,把购物车商品保存在localStorage中。

store.subscribe((mutation, state) => {
  switch (mutation.type) {
    case 'setToken':
      localStorage.setItem('token', JSON.stringify(state.token));
      break;
    case 'addCart':
        localStorage.setItem('cart', JSON.stringify(state.cart));
        break;
  }
});

五、图片预览

图片预览可以通过cube-ui提供了$createImagePreview全局方法来实现。

// 图片预览
imgPreview(img) {
	// 使用ImagePreview组件显示图片
	this.$createImagePreview({
		imgs: [img],
	}).show();
},

运行效果:
Vue电商项目实战(二)_第4张图片

六、显示分类

drawer组件的使用:https://didi.github.io/cube-ui/#/zh-CN/docs/drawer

6.1 基本用法

<cube-button @click="showDrawer">Show Drawercube-button>
<cube-drawer
  ref="drawer"
  title="请选择"
  :data="data"
  @select="selectHandler">cube-drawer>

ref :当前drawer组件的引用
title: 标题
:data 代表分类列表数据,可以是一维数组,也可以是多维数组
@Select:选择事件处理函数

6.2 定义分类列表

在Home页中定义分类列表:


<cube-drawer ref="drawer" title="请选择分类" 
      :data="[drawerList]" @select="selectHandler">cube-drawer>
      

<cube-button @click="showCategory">选择分类cube-button>

定义keys和selectedKeys属性。keys代表分类名称,selectedKeys代表默认的分类。

async created() {
	const ret = await this.$http.get('/api/goods');
	...
	this.keys = ret.data.keys;
	this.selectedKeys = this.keys; // 默认选中全部分类
},
data() {
	return {
		...
		keys: [], // 分类
		selectedKeys: [], // 选中的分类
	}
},
computed: {
	...
	// 分类列表
	drawerList() { 
		return this.keys.map(v => {
			return {
				text: labels[v],
				value: v,
			}
		});
	}
},

我们在computed中构建分类列表的数据模型。text代表分类名称,value代表分类的值。

6.3 选择分类

定义selectHandler方法,该方法传入一个参数,代表分类的值。该值可能是单个值,也可能是包含多个值的数组。

// 选择分类
selectHandler(val) { // val代表选中分类的值,
	this.selectedKeys = [...val];
}

6.4 显示分类列表

showCategory() {
	this.$refs.drawer.show(); // 显示drawer组件
},

运行效果:
Vue电商项目实战(二)_第5张图片

6.5 商品列表改造

之前商品列表是显示所有商品,现在我们要修改为只显示指定分类的商品。

修改商品列表组件的:goods属性:

<goods-list :goods="filterGoods">goods-list>

在computed属性中加入filterGoods属性,该属性函数中获取选定分类的商品。

filterGoods() {
  let ret = [];
  this.selectedKeys.forEach(v => {
    ret = ret.concat(this.goods[v]);
  });
  return ret;
},

七、购物车

7.1 搭建购物车页面

修改Cart.vue文件:

<template>
  <div>
    <div class="good" v-for="(item,index) in cart" :key="item.id">
      {{item.title}}
      <div class="right">
        <i class="cubeic-remove" @click="countMinus(index)">i>
        <span>{{item.count}}span>
        <i class="cubeic-add" @click="countAdd(index)">i>
      div>
    div>
    <div>总价 {{total}}div>

    <cube-button :disabled="true" v-if="total">还差{{minTotal-total}}可以购买cube-button>
    <cube-button v-else>
      下单
      <span v-if="!token">(需要登录)span>
    cube-button>
  div>
template>
<script>
import { mapState, mapGetters } from "vuex";
export default {
  data() {
    return {
      minTotal: 1000
    };
  },
  computed: {
    ...mapState({
      cart: state => state.cart,
      token: state => state.token
    }),
    ...mapGetters({
      total: "total"
    })
  },
  methods: {
    countAdd(index) {
      this.$store.commit("countAdd", index);
    },
    countMinus(index) {
      this.$store.commit("countMinus", index);
    }
  }
};
script>


<style lang="stylus">
.good {
  padding: 10px;
  text-align: left;
  .right {
    float: right;
  }
  i {
    font-size: 18px;
  }
}
style>

上面minTotal属性的值为1000,代表购物车里面的商品总价必须要大于1000才能够下单。

7.2 定义state

在getters中定义total属性,该属性返回购物车商品总价。

total(state) {
 	return state.cart.reduce((num, v) => num += v.count * v.price, 0);
},

在mutations中定义countMinus和countAdd方法。

// 减少商品
countMinus(state, index) {
	const item = state.cart[index];
	if (item.count > 1) {
		item.count -= 1;
	} else {
		state.cart.splice(index, 1);
	}
},
// 增加商品
countAdd(state, index) {
	state.cart[index].count += 1; 
}

7.3 显示购物车数量

在购物车页签中显示购物车数量:

<cube-tab-bar show-slider
	v-model="selectedLabel"
	@change="changeHandler">
	
	<cube-tab v-for="(item, index) in tabs" :key="index"
			:icon="item.icon" :label="item.label" :value="item.value">
		<span>{{item.label}}span>
		<span class="badge" v-if="showBadge(item.label)">{{cartTotal}}span>
	cube-tab>
cube-tab-bar>

上面在cube-tab中定义了,用于显示购物车商品数量。

样式设置:

span.badge {
  display: inline-block;
  background: #de3529;
  color: white;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
}

定义showBadge方法,该方法判断labe是否为Cart,并且

computed: {
  ...mapGetters(['isLogin', 'cartTotal'])
},

methods: {
	...
	showBadge(label) {
		return label === 'Cart' && this.cartTotal > 0;
	}
}

修改state,在getters方法中定义cartTotal属性,该属性返回购物车商品数量。

// 计算购物车中商品的总数量
cartTotal(state) {
	let num = 0;
	state.cart.forEach(v => {
		num += v.count;
	});
	return num;
},

运行效果:
Vue电商项目实战(二)_第6张图片

你可能感兴趣的:(前端)