購物車原型圖
從功能上拆分層次
盡量讓組件原子化
容器組件(只管理數據)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>