用Vuex做一个购物车的状态管理(二)-完结篇

545 阅读4分钟

书接上文用Vuex做一个购物车的状态管理(一) 上文只是简单的做了两个子仓库,分别为购物车和商品仓库。

如下就是这个购物车的示意图。

屏幕录制 2024-08-14 2347 -original-original.gif

里面的功能就是添加商品和减小商品,在商品增加以后添加一个到购物车和减少一个到库存,在商品减少以后减少一个到购物车和增加一个到库存,计算购物车的总价格。

接下来看看是如何实现它的吧。

技术栈

Vue3 + Vitev5.4.0 + Vuex + Tailwindcss

用vite搭建Vue3的项目 下载 vuex 和配置tailwind。

如果不知道tailwind的话 官网看怎么配置Tailwind CSS 或者我写了一篇文章TailwindCSS也能知道如何配置。

实在配置不出来,可以自己写样式。

分了两个组件 分别为商品列表和购物车ProductList和ShoppingCart

ProductList

<template>
    <div class="container w-full max-w-4xl h-80 overflow-auto p-4 bg-gray-100">
        <ul>
            <li v-for="(item, index) in products" :key="index" class="mb-4 p-4 border rounded bg-white">
                {{ item.title }} - {{ item.price }}
                <br>
                <button :class="[
                    'px-4 py-2 border rounded',
                    getProductInventory(item) ? 'bg-blue-500 text-white hover:bg-blue-600' : 'bg-gray-300 text-gray-500 cursor-not-allowed'
                ]" :disabled="!getProductInventory(item)" @click="addProductToCart(item)">
                    添加一个
                </button>
                <button :class="[
                    'px-4 py-2 border rounded ml-2',
                    getProductQuantity(item) > 0 ? 'bg-red-500 text-white hover:bg-red-600' : 'bg-gray-300 text-gray-500 cursor-not-allowed'
                ]" :disabled="getProductQuantity(item) === 0" @click="deleteProductToCart(item)">
                    减小一个
                </button>
            </li>
        </ul>
    </div>
</template>

<script setup>
import { computed,onMounted } from 'vue';
import { useStore } from 'vuex'


const store = useStore()
const products = computed(() => store.state.products.all)
const cartProducts = computed(() =>store.getters['cart/cartProducts'])

onMounted(() => {
    store.dispatch('products/getAllProducts');
})

const addProductToCart = (product) =>{
    //修改inventory -1 库存减一 cart + 1
    //修改 dispatch action -> commit mutation
    store.dispatch('cart/addProductToCart',product)
}

const deleteProductToCart = (product) => {
    store.dispatch('cart/deleteProductToCart',product)
}
const getProductQuantity = (product) => {
    const cartProduct = cartProducts.value.find(p => p.id === product.id)
    return cartProduct ? cartProduct.quantity : 0
}

const getProductInventory = (product) => {
    return product.inventory > 0
}
</script>

<style lang="scss" scoped>

</style>
  • 组件上定义四个方法分别对应相关操作;

  • 利用钩子函数去先让主页面挂载后再去渲染子组件中数据也就是onMounted();

  • computed()计算属性让整个页面数据都变成响应式的数据;

  • 禁用按钮在没有库存或者购物车没有数量的时候;

  • dispatch去提交修改数据的操作,不能直接操作数据

ShoppingCart

<template>
    <div class="cart bg-white shadow-md rounded-lg p-6 max-w-lg mx-auto h-96 overflow-y-auto">
        <h2 class="text-2xl font-semibold mb-4">Your Cart</h2>
        <p v-show="!products.length" class="text-gray-500 italic">
            <i>Please add some products to cart</i>
        </p>
        <ul class="list-disc pl-5 mb-4">
            <li v-for="(item, index) in products" :key="index" class="mb-2 border-b border-gray-200 pb-2">
                <span class="font-medium text-gray-800">{{ item.title }}</span>
                <span class="text-gray-600"> - {{ item.quantity }}</span>
            </li>
        </ul>
        <p class="font-bold text-lg">
            Total: <span class="text-green-600">{{ total }}</span>
        </p>
    </div>
</template>

<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';

const store = useStore();
const products = computed(() => store.getters['cart/cartProducts']);
const total = computed(() => store.getters['cart/cartTotalPrice']);
</script>
  • cart子仓库定义好了 getters直接获取数据;

  • 计算属性直接变响应式变量

App.vue

<template>
  <div id="app" >
    <h1 class="text-center">Shopping Cart Example</h1>
    <hr />
    <h2 >Product</h2>
    <ProductList></ProductList>
    <hr />
    <h3 >Carting</h3>
    <shoppingCart></shoppingCart>
  </div>
</template>

<script setup>
import ProductList from './components/productList.vue'
import shoppingCart from './components/shoppingCart.vue';

</script>

<style lang="less" scoped>

</style>

分了两个子仓库去管理各自的数据

cart


const state = {
    items: []
}

const getters = {
    cartProducts:(state,getters,rootState) => {
        return state.items.map(({id,quantity}) => {
            const product = rootState.products.all.find(product => product.id === id)
            return {
                id:product.id,
                title:product.title,
                price:product.price,
                quantity
            }
        })
    },
    cartTotalPrice:(state,getters) => {
        //reduce()将数组消灭 累加和
        return getters.cartProducts.reduce((total,product) => {
            return total + product.price * product.quantity
        },0)
    }
}

const actions = {
    addProductToCart({commit,state},product){
        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)
            }
            commit('products/decrementProductInventory',{id:product.id},{root:true})
        }
    },
    deleteProductToCart({commit,state},product){
        const cartItem = state.items.find(item => item.id === product.id)
        if(cartItem){
            commit('decrementItemQuantity',cartItem)
            commit('products/incrementProductInventory',{id:product.id},{root:true})
        }
    }
}
//比pinia更复杂
const mutations = {
    pushProductToCart(state,{ id }){
        state.items.push({
            id,
            quantity:1
        })
    },
    incrementItemQuantity(state,cartItem){
        // const cartItem = state.items.find(item => item.id === id)
        cartItem.quantity ++
    },
    decrementItemQuantity(state,cartItem){
        cartItem.quantity --
    }
}
// store.product.all
export default {
    namespaced:true,
    state,
    getters,
    actions,
    mutations
}


product

import  API  from '../../api/shop'
const state = {
    all: []
}

const getters = {

}

const actions = {
    // api请求 -> 提交mutations
    // commit ? vuex给actions 可以commit mutations 的API
    getAllProducts({commit}){
        API.getProducts((products) =>{
            console.log(products);
            commit('setProducts',products)
            
        })
    }
}
//比pinia更复杂
const mutations = {
    //vuex mutations api 第一个参数是state 第二个执行mutations 的commit
    setProducts(state,products){
        state.all = products
    },
    decrementProductInventory(state, { id }) {
        const product = state.all.find(product => product.id === id)
        product.inventory--
    },
    incrementProductInventory(state,{ id }) {
        const product = state.all.find(product => product.id === id)
        product.inventory++
    }
}
// store.product.all
export default {
    namespaced:true,
    state,
    getters,
    actions,
    mutations
}

中央仓库

import { createStore, } from 'vuex'

import products from './module/products.js'
import cart from './module/cart.js'
// vuex pinia 复杂 ,中央仓库的概念 store 单例 单一状态树

//仓库
//分子仓库
export default createStore({
    //全局状态
    state:{
        count:0
    },
    //模块化状态
    modules:{
        cart,//购物车状态
        products//商品状态
    }

})

模拟后端的假数据

//假数据

const _products = [
    { 'id': 1, 'title': 'iPad 4 Mini', 'price': 500.01, 'inventory': 2 },
    { 'id': 2, 'title': 'H&M T-Shirt White', 'price': 10.99, 'inventory': 10 },
    { 'id': 3, 'title': 'Charli XCX - Sucker CD', 'price': 19.99, 'inventory': 5 },
    { 'id': 4, 'title': 'Samsung Galaxy S21', 'price': 799.99, 'inventory': 8 },
    { 'id': 5, 'title': 'Sony WH-1000XM4', 'price': 349.99, 'inventory': 15 },
    { 'id': 6, 'title': 'MacBook Pro 16"', 'price': 2399.00, 'inventory': 3 },
    { 'id': 7, 'title': 'Bose SoundLink Mini', 'price': 149.99, 'inventory': 20 },
    { 'id': 8, 'title': 'Nike Air Max 270', 'price': 149.99, 'inventory': 12 },
    { 'id': 9, 'title': 'Canon EOS R5', 'price': 3899.00, 'inventory': 7 },
    { 'id': 10, 'title': 'Dell XPS 13', 'price': 1199.99, 'inventory': 5 },
    { 'id': 11, 'title': 'JBL Flip 5', 'price': 89.95, 'inventory': 25 },
    { 'id': 12, 'title': 'Apple Watch Series 6', 'price': 749.00, 'inventory': 10 },
    { 'id': 13, 'title': 'GoPro HERO 9 Black', 'price': 399.99, 'inventory': 6 },
    { 'id': 14, 'title': 'Fitbit Charge 4', 'price': 149.95, 'inventory': 18 },
    { 'id': 15, 'title': 'Nintendo Switch', 'price': 299.99, 'inventory': 14 },
    { 'id': 16, 'title': 'Acer Predator Helios 300', 'price': 1399.99, 'inventory': 4 },
    { 'id': 17, 'title': 'Logitech MX Master 3', 'price': 99.99, 'inventory': 22 },
    { 'id': 18, 'title': 'iPhone 12 Pro Max', 'price': 1099.99, 'inventory': 6 },
    { 'id': 19, 'title': 'Samsung QLED TV 55"', 'price': 799.99, 'inventory': 2 },
    { 'id': 20, 'title': 'Apple AirPods Pro', 'price': 249.00, 'inventory': 12 }
];



export default {
    // cb callback 回调函数解决异步问题
    getProducts(cb){
        setTimeout(() => cb(_products),100)
    }
}

当然最后别忘了把store在main.js中使用掉。

这样你也得到了一个简易的vuex做的一个购物车功能。

如果这对你有帮助,点个免费的赞吧。有什么不懂,随时问。