书接上文用Vuex做一个购物车的状态管理(一) 上文只是简单的做了两个子仓库,分别为购物车和商品仓库。
如下就是这个购物车的示意图。
里面的功能就是添加商品和减小商品,在商品增加以后添加一个到购物车和减少一个到库存,在商品减少以后减少一个到购物车和增加一个到库存,计算购物车的总价格。
接下来看看是如何实现它的吧。
技术栈
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做的一个购物车功能。
如果这对你有帮助,点个免费的赞吧。有什么不懂,随时问。