vue购物车组件设计结构
购物车原型图
从功能上拆分层次
尽量让组件原子化
容器组件(只管理数据)vuex
组件拆分
该功能拆分成两个组件,顶部是商品列表,底部是购物车商品
功能上
1.点击加入购物车,底部购物车新增商品(或者是已有商品,数量加1即可)
2.点击增加按钮,数量加一,点击减少,数量减1
数据结构上
1.有两种数据,一种是商品列表数据,有商品id,商品名称,商品价格。另一种是购物车数据,有商品id,商品名称,数量
逻辑分析
1.点击添加到购物车商品,将商品id传递过去,然后在数据组件(总组件中)对数据处理,通过id来给cartlist数组添加一条新数据(没有相同id,新增,有相同id,数量加1)
2.cartlist和productionlist传递给cart组件,通过id来计算一个新数据list(因为cartlist没有商品名称),总价也可以计算出来
3.点击添加按钮,也通过id来判断数组中的数量加1, 点击减少,来判断数组中数量减1
总结;通过商品id来处理数组中的数据,利用数组中的各种方法,用到了事件总线,子向父传递数据
总组件index.vue
<template> <div> <ProductionList :list="productionList"/> <hr> <CartList :productionList="productionList" :cartList="cartList" /> </div> </template> <script> import ProductionList from './ProductionList/index' import CartList from './CartList/index' import event from './event' export default { components: { ProductionList, CartList }, data() { return { productionList: [ { id: 1, title: '商品A', price: 10 }, { id: 2, title: '商品B', price: 15 }, { id: 3, title: '商品C', price: 20 } ], cartList: [ { id: 1, quantity: 1 // 购物数量 } ] } }, methods: { // 加入购物车 addToCart(id) { // 先看购物车中是否有该商品 const prd = this.cartList.find(item => item.id === id) if (prd) { // 数量加一 prd.quantity++ return } // 购物车没有该商品 this.cartList.push({ id, quantity: 1 // 默认购物数量 1 }) }, // 从购物车删除一个(即购物数量减一) delFromCart(id) { // 从购物车中找出该商品 const prd = this.cartList.find(item => item.id === id) if (prd == null) { return } // 数量减一 prd.quantity-- // 如果数量减少到了 0 if (prd.quantity <= 0) { this.cartList = this.cartList.filter( item => item.id !== id ) } } }, mounted() { this.$on('addToCart', this.addToCart) this.$on('delFromCart', this.delFromCart) } } </script>
顶部商品列表组件production
<template> <div> <ProductionItem v-for="item in list" :key="item.id" :item="item" /> </div> </template> <script> import ProductionItem from './ProductionItem' export default { components: { ProductionItem, }, props: { list: { type: Array, default() { return [ // { // id: 1, // title: '商品A', // price: 10 // } ] } } } } </script>
<template> <div> <span>{{item.title}}</span> <span>{{item.price}}元</span> <a href="#" @click="clickHandler(item.id, $event)">加入购物车</a> </div> </template> <script> import event from '../event' export default { props: { item: { type: Object, default() { return { // id: 1, // title: '商品A', // price: 10 } } } }, methods: { clickHandler(id, e) { e.preventDefault() this.$emit('addToCart', id) } }, } </script>
底部购物车组件
<template> <div> <CartItem v-for="item in list" :key="item.id" :item="item" /> <p>总价 {{totalPrice}}</p> </div> </template> <script> import CartItem from './CartItem' export default { components: { CartItem, }, props: { productionList: { type: Array, default() { return [ // { // id: 1, // title: '商品A', // price: 10 // } ] } }, cartList: { type: Array, default() { return [ // { // id: 1, // quantity: 1 // } ] } } }, computed: { // 购物车商品列表 list() { return this.cartList.map(cartListItem => { // 找到对应的 productionItem const productionItem = this.productionList.find( prdItem => prdItem.id === cartListItem.id ) // 返回商品信息,外加购物数量 return { ...productionItem, quantity: cartListItem.quantity } // 如: // { // id: 1, // title: '商品A', // price: 10, // quantity: 1 // 购物数量 // } }) }, // 总价 totalPrice() { return this.list.reduce( (total, curItem) => total + (curItem.quantity * curItem.price), 0 ) } } } </script>
<template> <div> <span>{{item.title}}</span> <span>(数量 {{item.quantity}})</span> <a href="#" @click="addClickHandler(item.id, $event)">增加</a> <a href="#" @click="delClickHandler(item.id, $event)">减少</a> </div> </template> <script> import event from '../event' export default { props: { item: { type: Object, default() { return { // id: 1, // title: '商品A', // price: 10, // quantity: 1 // 购物数量 } } } }, methods: { addClickHandler(id, e) { e.preventDefault() this.$emit('addToCart', id) }, delClickHandler(id, e) { e.preventDefault() this.$emit('delFromCart', id) } } } </script>
vuex版本
vuex模块拆分,模块拆分,如何获取数据;参考;https://www.cnblogs.com/fsg6/p/14416502.html
vuex数据
总store
import Vue from 'vue' import Vuex from 'vuex' import cart from './modules/cart' import products from './modules/products' import createLogger from '../../../src/plugins/logger' Vue.use(Vuex) const debug = process.env.NODE_ENV !== 'production' export default new Vuex.Store({ modules: { cart, products }, strict: debug, plugins: debug ? [createLogger()] : [] })
productons.js
import shop from '../../api/shop' // initial state const state = { all: [] } // getters const getters = {} // actions —— 异步操作要放在 actions const actions = { // 加载所有商品 getAllProducts ({ commit }) { // 从 shop API 加载所有商品,模拟异步 shop.getProducts(products => { commit('setProducts', products) }) } } // mutations const mutations = { // 设置所有商品 setProducts (state, products) { state.all = products }, // 减少某一个商品的库存(够买一个,库存就相应的减少一个,合理) decrementProductInventory (state, { id }) { const product = state.all.find(product => product.id === id) product.inventory-- } } export default { namespaced: true, state, getters, actions, mutations }
cart.js
import shop from '../../api/shop' // initial state // shape: [{ id, quantity }] const state = { // 已加入购物车的商品,格式如 [{ id, quantity }, { id, quantity }] // 注意,购物车只存储 id 和数量,其他商品信息不存储 items: [], // 结账的状态 - null successful failed checkoutStatus: null } // getters const getters = { // 获取购物车商品 cartProducts: (state, getters, rootState) => { // rootState - 全局 state // 购物车 items 只有 id quantity ,没有其他商品信息。要从这里获取。 return state.items.map(({ id, quantity }) => { // 从商品列表中,根据 id 获取商品信息 const product = rootState.products.all.find(product => product.id === id) return { title: product.title, price: product.price, quantity } }) }, // 所有购物车商品的价格总和 cartTotalPrice: (state, getters) => { // reduce 的经典使用场景,求和 return getters.cartProducts.reduce((total, product) => { return total + product.price * product.quantity }, 0) } } // actions —— 异步操作要放在 actions const actions = { // 结算 checkout ({ commit, state }, products) { // 获取购物车的商品 const savedCartItems = [...state.items] // 设置结账的状态 null commit('setCheckoutStatus', null) // empty cart 清空购物车 commit('setCartItems', { items: [] }) // 请求接口 shop.buyProducts( products, () => commit('setCheckoutStatus', 'successful'), // 设置结账的状态 successful () => { commit('setCheckoutStatus', 'failed') // 设置结账的状态 failed // rollback to the cart saved before sending the request // 失败了,就要重新还原购物车的数据 commit('setCartItems', { items: savedCartItems }) } ) }, // 添加到购物车 // 【注意】这里没有异步,为何要用 actions ???—— 因为要整合多个 mutation // mutation 是原子,其中不可再进行 commit !!! addProductToCart ({ state, commit }, product) { commit('setCheckoutStatus', null) // 设置结账的状态 null // 判断库存是否足够 if (product.inventory > 0) { const cartItem = state.items.find(item => item.id === product.id) if (!cartItem) { // 初次添加到购物车 commit('pushProductToCart', { id: product.id }) } else { // 再次添加购物车,增加数量即可 commit('incrementItemQuantity', cartItem) } // remove 1 item from stock 减少库存 commit('products/decrementProductInventory', { id: product.id }, { root: true }) } } } // mutations const mutations = { // 商品初次添加到购物车 pushProductToCart (state, { id }) { state.items.push({ id, quantity: 1 }) }, // 商品再次被添加到购物车,增加商品数量 incrementItemQuantity (state, { id }) { const cartItem = state.items.find(item => item.id === id) cartItem.quantity++ }, // 设置购物车数据 setCartItems (state, { items }) { state.items = items }, // 设置结算状态 setCheckoutStatus (state, status) { state.checkoutStatus = status } } export default { namespaced: true, state, getters, actions, mutations }
总组件
<template> <div id="app"> <h1>Shopping Cart Example</h1> <hr> <h2>Products</h2> <ProductList/> <hr> <ShoppingCart/> </div> </template> <script> import ProductList from './ProductList.vue' import ShoppingCart from './ShoppingCart.vue' export default { components: { ProductList, ShoppingCart } } </script>
production组件
<template> <ul> <li v-for="product in products" :key="product.id"> {{ product.title }} - {{ product.price | currency }} (inventory: {{product.inventory}})<!-- 这里可以自己加一下显示库存 --> <br> <button :disabled="!product.inventory" @click="addProductToCart(product)"> Add to cart </button> </li> </ul> </template> <script> import { mapState, mapActions } from 'vuex' export default { computed: mapState({ // 获取所有商品 products: state => state.products.all }), methods: mapActions('cart', [ // 添加商品到购物车 'addProductToCart' ]), created () { // 加载所有商品,dispatch到模块的函数 this.$store.dispatch('products/getAllProducts') } } </script>
cart组件
<template> <div class="cart"> <h2>Your Cart</h2> <p v-show="!products.length"><i>Please add some products to cart.</i></p> <ul> <li v-for="product in products" :key="product.id"> {{ product.title }} - {{ product.price | currency }} x {{ product.quantity }} </li> </ul> <p>Total: {{ total | currency }}</p> <p><button :disabled="!products.length" @click="checkout(products)">Checkout</button></p> <p v-show="checkoutStatus">Checkout {{ checkoutStatus }}.</p> </div> </template> <script> import { mapGetters, mapState } from 'vuex' export default { computed: { ...mapState({ // 结账的状态 checkoutStatus: state => state.cart.checkoutStatus }),
//获取到模块store中数据 ...mapGetters('cart', { products: 'cartProducts', // 购物车的商品 total: 'cartTotalPrice' // 购物车商品的总价格 }) }, methods: { // 结账 checkout (products) { this.$store.dispatch('cart/checkout', products) } } } </script>