使用Vuex实现购物车功能
2023-08-20 本文已影响0人
h2coder
效果展示
image.png依赖版本
- vue2:2.6.14
- vuex:3.6.2
- axios:1.4.0
- less-loader:11.1.3
准备数据
购物车列表
- 服务端接口返回的购车列表数据
{
"cart": [
{
"id": 100001,
"name": "低帮城市休闲户外鞋天然牛皮COOLMAX纤维",
"price": 128,
"count": 2,
"thumb": "https://yanxuan-item.nosdn.127.net/3a56a913e687dc2279473e325ea770a9.jpg"
},
{
"id": 100002,
"name": "网易味央黑猪猪肘330g*1袋",
"price": 39,
"count": 2,
"thumb": "https://yanxuan-item.nosdn.127.net/d0a56474a8443cf6abd5afc539aa2476.jpg"
},
{
"id": 100003,
"name": "KENROLL男女简洁多彩一片式室外拖",
"price": 128,
"count": 2,
"thumb": "https://yanxuan-item.nosdn.127.net/eb1556fcc59e2fd98d9b0bc201dd4409.jpg"
},
{
"id": 100004,
"name": "云音乐定制IN系列intar民谣木吉他",
"price": 589,
"count": 1,
"thumb": "https://yanxuan-item.nosdn.127.net/4d825431a3587edb63cb165166f8fc76.jpg"
}
]
}
页面分析
- 页面标题栏
- 固定显示“购物车”的文字
- 购物车列表
- 列表显示购物车数据,有商品图片、商品名称、商品价格、商品数量,以及数量的加、减按钮
- 页面底部栏
- 所有商品数量、商品总价、结算按钮
业务分析
- 列表项
- 点击加号,单项商品的数量 + 1
- 点击减号,单项商品的数量 - 1,但数量不能少于1,少于1时,禁用按钮
- 底部栏
- 统计多少件商品,取数据数组的长度即可
- 单项商品的价格 = 数量 * 单价
- 统计商品总价,所有单项商品的价格累加和
Vuex配置
- Vuex主要有6个配置部分,分别为
- state,状态,也就是要共享的数据
- mutations,同步修改state状态的函数
- actions,异步修改state状态的函数,其实就是异步结束后,调用mutations的函数,实现修改state数据
- getters,类似Vue组件的计算属性,可以对state状态数据进行统计、合并多个state为一个新的响应式变量
- modules,模块,Vuex支持定义模块,每个模块都有自己的state、mutations、actions、getters
开始配置
- src目录下,建立store目录,新建
index.js
- 建立modules目录,购物车使用分模块,单独建立一个
cart
模块,封装在cart.js
中
/* 封装Vuex相关 */
import Vue from 'vue'
import Vuex from 'vuex'
// 导入购物车模块的store
import cart from '@/store/modules/cart'
// 使用Vuex
Vue.use(Vuex)
// 创建vuex仓库实例
const vuex = new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
// 导入购物车模块的store
cart
}
})
// 默认导出
export default vuex
注册Vuex
import Vue from 'vue'
import App from './App.vue'
// 导入vuex实例
import store from './store'
Vue.config.productionTip = false
new Vue({
// 注册vuex
store,
render: h => h(App)
}).$mount('#app')
购物车模块Vuex
-
state(状态)
- list,购物车数据,将用于渲染购物车项
-
mutations(同步修改state的函数)
- updateCartList,更新购物车数据,给list状态变量赋值
- updateCartCount,更新指定id的购物车数量
-
actions(异步修改state的函数)
- getCartAsync,调用接口,获取购物车列表数据,提交mutation,调用updateCartList函数
- updateAsyncCartCount,调用接口,更新某个id的购物车项的数量,提交mutation,调用updateCartCount函数
-
getters(相当于计算属性)
- totalPrice,总价格,统计state中的list中所有的商品的价格(商品价格 * 数量,再累加起来)
- totalCount,总数量,统计state中的list中所有的商品的个数
-
注意:必须添加
namespaced
属性,否则调用时,会找不到模块
/* 购物车模块的store */
// 引入axios
import axios from "axios"
// 状态响应式变量
const state = () => ({
// 购物车数据列表
list: []
})
// vuex的计算属性
const getters = {
// 总价格
totalPrice(state) {
return state.list.reduce((prev, current) => {
return prev + (current.count * current.price)
}, 0)
},
// 总数量
totalCount(state) {
return state.list.reduce((prev, current) => {
return prev + current.count
}, 0)
}
}
// 同步修改state变量
const mutations = {
// 更新购物车数据
updateCartList(state, list) {
state.list = list
},
// 更新指定Id的购物车商品的数量
updateCartCount(state, { id, count }) {
const cartItem = state.list.find(item => item.id === id)
cartItem.count = count
}
}
// 异步修改state变量
const actions = {
// 发送请求,获取购物车列表
async getCartAsync(context) {
const result = await axios({
url: 'http://localhost:3000/cart',
method: 'GET'
})
console.log(result);
// 提交mutation
const list = result.data
context.commit("updateCartList", list)
},
// 修改购物车的商品数量
async updateAsyncCartCount(context, { id, count }) {
// 发送请求,修改数量
const result = await axios({
url: `http://localhost:3000/cart/${id}`,
method: 'PATCH',
data: {
count: count
}
})
console.log(result);
// 提交mutation,修改内存中购物车商品的数量
context.commit("updateCartCount", { id, count })
}
}
// 默认导出
export default {
// 命名空间,设置为true,才能用使用vuex的辅助函数
namespaced: true,
state,
getters,
mutations,
actions
}
编写组件
按页面分析,拆分为3个组件,分别为顶部栏组件、列表项组件、底部栏组件
顶部栏组件
<template>
<div class="header-container">{{ title }}</div>
</template>
<script>
export default {
name: "CartHeader",
props: {
// 标题文字
title: {
type: String,
required: true
}
}
};
</script>
<style lang="less" scoped>
.header-container {
height: 50px;
line-height: 50px;
font-size: 16px;
background-color: #42b983;
text-align: center;
color: white;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
}
</style>
底部栏组件
- 组件需要传入2个参数,分别是:
- 总价格
- 总数量
<template>
<div class="footer-container">
<!-- 中间的合计 -->
<div>
<span>共 {{ totalCount }} 件商品,合计:</span>
<span class="price">¥{{ totalPrice }}</span>
</div>
<!-- 右侧结算按钮 -->
<button class="btn btn-success btn-settle">结算</button>
</div>
</template>
<script>
export default {
name: "CartFooter",
props: {
// 总价格
totalPrice: {
type: Number,
required: true,
},
// 总数量
totalCount: {
type: Number,
required: true,
},
},
};
</script>
<style lang="less" scoped>
.footer-container {
background-color: white;
height: 50px;
border-top: 1px solid #f8f8f8;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 0 10px;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
z-index: 999;
}
.price {
color: red;
font-size: 13px;
font-weight: bold;
margin-right: 10px;
}
.btn-settle {
height: 30px;
min-width: 80px;
margin-right: 20px;
border-radius: 20px;
background: #42b983;
border: none;
color: white;
}
</style>
购物车项组件
- 组件需要传入单个购物车项数据,组件中使用数据渲染,商品图片、商品名称、单价、数量等
- 通过
mapActions
,将vuex中的actions函数,映射到methods
中 - 定义
updateCartCount
函数,当点击+
号和-
号时,调用该函数,计算商品新的数量,再调用vuex中的updateAsyncCartCount
函数,通知后端更新商品数量 - 计算数量,定义
isDisable
函数,判断商品小于1时,为禁用状态
<template>
<div class="goods-container">
<!-- 左侧图片区域 -->
<div class="left">
<img :src="item.thumb" class="avatar" alt="" />
</div>
<!-- 右侧商品区域 -->
<div class="right">
<!-- 标题 -->
<div class="title">{{ item.name }}</div>
<div class="info">
<!-- 单价 -->
<span class="price">¥{{ item.price }}</span>
<div class="btns">
<!-- 按钮区域 -->
<button
:disabled="isDisable"
@click="updateCartCount(item.id, -1)"
class="btn btn-light"
>
-
</button>
<span class="count">{{ item.count }}</span>
<button @click="updateCartCount(item.id, 1)" class="btn btn-light">
+
</button>
</div>
</div>
</div>
</div>
</template>
<script>
// 引入vuex的辅助函数
import { mapActions } from "vuex";
export default {
name: "CartItem",
// 子组件参数声明
props: {
item: {
type: Object,
required: true,
},
},
methods: {
// 更新购物车的商品数量
updateCartCount(id, unit) {
// 计算新商品数量
const count = this.item.count + unit;
// 派发action,修改数量
this.updateAsyncCartCount({
id: id,
count: count,
});
},
// 引入异步action
...mapActions("cart", ["updateAsyncCartCount"]),
},
computed: {
// 当购物车的商品数量小于等于1时,为禁用
isDisable() {
return this.item.count <= 1;
},
},
};
</script>
<style lang="less" scoped>
.goods-container {
display: flex;
padding: 10px;
+ .goods-container {
border-top: 1px solid #f8f8f8;
}
.left {
.avatar {
width: 100px;
height: 100px;
}
margin-right: 10px;
}
.right {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
.title {
font-weight: bold;
}
.info {
display: flex;
justify-content: space-between;
align-items: center;
.price {
color: red;
font-weight: bold;
}
.btns {
.count {
display: inline-block;
width: 30px;
text-align: center;
}
}
}
}
}
.custom-control-label::before,
.custom-control-label::after {
top: 3.6rem;
}
</style>
根组件
- 通过
mapState
映射函数,将购物车状态数据list
,映射到computed
计算属性中 - 通过
mapActions
映射函数,将获取购物车数据函数getCartAsync
,映射到methods
中 - 通过
mapGetters
映射函数,将购物车totalPrice
商品总价、totalCount
商品总数量,映射到computed
计算属性中 - 通过v-for,循环渲染购物车项组件,并将单个购物车项数据传入给组件
- 将购物车
totalPrice
商品总价、totalCount
商品总数量,传给底部栏组件进行渲染
<template>
<div class="app-container">
<!-- Header 区域 -->
<cart-header title="购物车"></cart-header>
<!-- 商品 Item 项组件 -->
<cart-item v-for="item in list" :key="item.id" :item="item"></cart-item>
<!-- Foote 区域 -->
<cart-footer
:totalPrice="totalPrice"
:totalCount="totalCount"
></cart-footer>
</div>
</template>
<script>
// 标题栏
import CartHeader from "@/components/cart-header.vue";
// 购物车条目
import CartItem from "@/components/cart-item.vue";
// 底部栏
import CartFooter from "@/components/cart-footer.vue";
// 引入vuex的辅助函数
import { mapState, mapActions, mapGetters } from "vuex";
export default {
name: "App",
// 注册组件
components: {
CartHeader,
CartFooter,
CartItem,
},
created() {
// 一进入页面,就派发action,获取购物车列表数据
this.getCartAsync();
},
methods: {
// 引入异步action
...mapActions("cart", ["getCartAsync"]),
},
computed: {
// 引入状态响应式变量
...mapState("cart", ["list"]),
// 引入vuex的计算属性
...mapGetters("cart", ["totalPrice", "totalCount"]),
},
};
</script>
<style lang="less" scoped>
.app-container {
padding: 50px 0;
font-size: 14px;
}
</style>