用vue框架制做购物车

1.在商品详细页的添加到购物车按钮内添加触发事件

2.在商品详情页接收这个事件

3.在状态管理内进行数据处理

        3.1 index.js导入action.js和getters.js

import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import actions from './actions'

Vue.use(Vuex)

const state = {
    cartList: []
}

const store = new Vuex.Store({
    state,
    // mutations,
    getters,
    actions
})

export default store

        3.2 在actions内处理数据和异步处理

export default {
  addCart(context, payload) {
    //进行异步操作
    return new Promise(((resolve, reject) => {
      //定义变量oldProduct,在cartList数组内根据item查找是否item.iid和payload.iid相同
      let oldProduct = context.state.cartList.find(item => item.iid === payload.iid)
      if (oldProduct) {
        //如果有则定义下标为state内类型为oldProduct的cartList
        //然后该下标的数量加1
        //并且弹出'商品新添加一件'
        const index = context.state.cartList.indexOf(oldProduct)
        context.state.cartList[index].count++
        resolve('商品新添加一件')
      } else {
        //如果没有,则给payload的数量赋值为1
        //然后购物车内补选中
        //并且把payload的内容加到carList内
        //最后弹出'商品添加成功'
        payload.count = 1
        payload.checked = false
        context.state.cartList.push(payload)
        resolve('商品添加成功')
      }
    }))
  }
}

        3.3 在getters内把对象转成计算熟悉

const getters = {
	cartLength(state) {
		//在state内返回购物车的长度
		return state.cartList.length
	},
	cartList(state) {
		//返回在state内购物车的数据
		return state.cartList
	}
}

export default getters

4.添加成功弹出窗口提醒

        4.1配置$toast

toast.vue

<template>
  <div v-show="isShow">
    <div class="toast">
      {{message}}
    </div>
  </div>
</template>

<script>
  export default {
    name: "Toast",
    data() {
      return {
        message: '',
        isShow: false
      }
    },
    methods: {
      show(message, duration=1000) {
        this.isShow = true
        this.message = message
        setTimeout(() => {
          this.isShow = false
          this.message = ''
        }, duration)
      }
    }
  }
</script>

<style scoped>
  .toast {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    border-radius: 5px;
    background-color: rgba(0, 0, 0, .75);
    color: #fff;
    padding: 8px 10px;
    z-index: 999;
  }
</style>

index.js

import Toast from './Toast'
let obj = {}

obj.install = function (Vue) {
  // 1. 创建组件构造器
  const toastConstuctor = Vue.extend(Toast)
  // 2. new的方式,根据组件构造器,可以创建出来一个组件对象
  const toast = new toastConstuctor()
  // 3. 将组件对象,手动挂载到某一个元素上
  toast.$mount(document.createElement('div'))
  // 4. toast.$el 对应的就是div
  document.body.appendChild(toast.$el)

  Vue.prototype.$toast = toast
}

export default obj

        4.2 从main.js内导入

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from "./store";
import toast from  './components/common/toast'


Vue.config.productionTip = false
Vue.prototype.$bus = new Vue()
Vue.use(toast)

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

        4.4 在处理完窗口后运用

4.添加数据到购物车

5.购物车数据加载

<template>
  <div id="shop-item">
    <div class="item-selector">
      <CheckButton :is-checked="itemInfo.checked" @click.native="checkClick" />
    </div>
    <div class="item-img">
      <img :src="itemInfo.imgURL" alt="商品图片" @load="imgLoad">
    </div>
    <div class="item-info">
      <div class="item-title">{{itemInfo.title}}</div>
      <div class="item-desc">{{itemInfo.desc}}</div>
      <div class="info-bottom">
        <div class="item-price left">¥{{itemInfo.price}}</div>
        <div class="item-count right">x{{itemInfo.count}}</div>
      </div>
    </div>
  </div>
</template>

<script>
  import CheckButton from './CheckButton'

  export default {
    name: "ShopCartItem",
    props: {
      //商品对象
      itemInfo: Object
    },
    components: {
      CheckButton
    },
    methods: {
      checkClick: function () {
        //响应是否有选中itemInfo,并且改变checke值使CheckButton的样式改变
        this.itemInfo.checked = !this.itemInfo.checked;
      },
      imgLoad() {
        // 判断, 所有的图片都加载完了, 那么进行一次回调就可以了.
        if (++this.counter === this.imagesLength) {
          this.$emit('imageLoad');
        }
      }
    }
  }
</script>

<style scoped>
  #shop-item {
    width: 100%;
    display: flex;
    font-size: 0;
    padding: 5px;
    border-bottom: 1px solid #ccc;
  }

  .item-selector {
    width: 20px;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .item-title, .item-desc {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .item-img {
    padding: 5px;
    /*border: 1px solid #ccc;*/
  }

  .item-img img {
    width: 80px;
    height: 100px;
    display: block;
    border-radius: 5px;
  }

  .item-info {
    font-size: 17px;
    color: #333;
    padding: 5px 10px;
    position: relative;
    overflow: hidden;
  }

  .item-info .item-desc {
    font-size: 14px;
    color: #666;
    margin-top: 15px;
  }

  .info-bottom {
    margin-top: 10px;
    position: absolute;
    bottom: 10px;
    left: 10px;
    right: 10px;
  }

  .info-bottom .item-price {
    color: orangered;
  }
</style>

6.购物车选择和全选按键

按钮代码:

<template>
    <div>
      <div class="icon-selector" :class="{'check': isChecked}" @click="selectItem">
        <img src="../../../assets/img/cart/tick.svg" alt="">
      </div>
    </div>
</template>

<script>
	export default {
		name: "CheckButton",
    props: {
		  isChecked: {
		    type: Boolean,
        default: false
      }
    },
    data: function () {
		  return {
		    //checked等于这个dom变量
		    checked: this.value
      }
    },
    methods: {
      selectItem: function () {
        //发送事件
        this.$emit('checkBtnClick')
      }
    },
    watch: {
		  //监听value是否有改变,如果改变则checked的值特相应改变
		  value: function (newValue) {
        this.checked = newValue;
      }
    }
	}
</script>

<style scoped>
  .icon-selector {
    position: relative;
    margin: 0;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 2px solid #aaa;
    cursor: pointer;
  }

  .check {
    background-color: #ff8198;
    border-color: #ff8198;
  }
</style>

全选按钮代码:

<template>
  <div class="bottom-menu">
    <CheckButton class="select-all"
                 :is-checked="isSelectorAll"
                 @click.native="checkClick">

    </CheckButton>
    <span>全选</span>
    <span class="total-price">合计: ¥{{totalPrice}}</span>
    <span class="buy-product" @click="calcClick">去结算({{cartLength}})</span>
  </div>
</template>

<script>
  import CheckButton from './CheckButton'
  import { mapGetters } from 'vuex'

	export default {
		name: "BottomBar",
    components: {
		  CheckButton
    },
    computed: {
      ...mapGetters([
      	'cartList',
        'cartLength'
      ]),
		  totalPrice() {
        //计算总和
        //用filter来判断item.checked的布尔值,true存入cartList
        //用reduce函数对数组中的所有内容进行数量乘上价格记总,并保留两位小数
        const cartList = this.cartList;
        return cartList.filter(item => {
          return item.checked
        }).reduce((preValue, item) => {
          return preValue + item.count * item.price
        }, 0).toFixed(2)
      },
      checkLength() {
        //监听组件
        //返回Vuex内getters内的cartList查找item中的checked属性,和长度
        return this.$store.getters.cartList.filter(item => item.checked).length
      },
      isSelectorAll() {
        //判断是否全部选中
        if (this.$store.getters.cartList.length === 0) return false
        return !this.$store.getters.cartList.find(item => !item.checked)
      }
    },
    methods: {
      checkClick() {
        //如果全部选中则全部取消选择,如果没有则部分或全部选中
        if(this.isSelectorAll) {
          this.$store.getters.cartList.forEach(item => item.checked = false)
        } else {
          this.$store.getters.cartList.forEach(item => item.checked = true)
        }
      },
      calcClick() {
        //如果没有全部选中则弹出'请选择购买的商品'
        if(!this.isSelectorAll) {
          this.$toast.show('请选择购买的商品')

        }
      }
    }
	}
</script>

<style scoped>
  .bottom-menu {
    width: 100%;
    height: 44px;
    background-color: #eee;
    position: fixed;
    bottom: 49px;
    left: 0;
    box-shadow: 0 -2px 3px rgba(0, 0, 0, .2);
    font-size: 14px;
    color: #888;
    line-height: 44px;
    padding-left: 35px;
    box-sizing: border-box;
  }

  .bottom-menu .select-all {
    position: absolute;
    line-height: 0;
    left: 12px;
    top: 13px;
  }

  .bottom-menu .total-price {
    margin-left: 15px;
    font-size: 16px;
    color: #666;
  }

  .bottom-menu .buy-product {
    background-color: orangered;
    color: #fff;
    width: 100px;
    height: 44px;
    text-align: center;
    line-height: 44px;
    float: right;
  }
</style>

选择按钮代码:

<template>
  <div id="shop-item">
    <div class="item-selector">
      <CheckButton :is-checked="itemInfo.checked" @click.native="checkClick" />
    </div>
    <div class="item-img">
      <img :src="itemInfo.imgURL" alt="商品图片" @load="imgLoad">
    </div>
    <div class="item-info">
      <div class="item-title">{{itemInfo.title}}</div>
      <div class="item-desc">{{itemInfo.desc}}</div>
      <div class="info-bottom">
        <div class="item-price left">¥{{itemInfo.price}}</div>
        <div class="item-count right">x{{itemInfo.count}}</div>
      </div>
    </div>
  </div>
</template>

<script>
  import CheckButton from './CheckButton'

  export default {
    name: "ShopCartItem",
    props: {
      //商品对象
      itemInfo: Object
    },
    components: {
      CheckButton
    },
    methods: {
      checkClick: function () {
        //响应是否有选中itemInfo,并且改变checke值使CheckButton的样式改变
        this.itemInfo.checked = !this.itemInfo.checked;
      },
      imgLoad() {
        // 判断, 所有的图片都加载完了, 那么进行一次回调就可以了.
        if (++this.counter === this.imagesLength) {
          this.$emit('imageLoad');
        }
      }
    }
  }
</script>

<style scoped>
  #shop-item {
    width: 100%;
    display: flex;
    font-size: 0;
    padding: 5px;
    border-bottom: 1px solid #ccc;
  }

  .item-selector {
    width: 20px;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .item-title, .item-desc {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .item-img {
    padding: 5px;
    /*border: 1px solid #ccc;*/
  }

  .item-img img {
    width: 80px;
    height: 100px;
    display: block;
    border-radius: 5px;
  }

  .item-info {
    font-size: 17px;
    color: #333;
    padding: 5px 10px;
    position: relative;
    overflow: hidden;
  }

  .item-info .item-desc {
    font-size: 14px;
    color: #666;
    margin-top: 15px;
  }

  .info-bottom {
    margin-top: 10px;
    position: absolute;
    bottom: 10px;
    left: 10px;
    right: 10px;
  }

  .info-bottom .item-price {
    color: orangered;
  }
</style>

6.我遇到的问题

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值