综合案例:购物车
数据渲染
构建cart购物车模块
准备后端接口服务环境
请求数据存入vuex
cart.js
// 新建购物车模块
import axios from 'axios'
export default {
namespaced: true,
state () {
return {
// 购物车数据 [{},{}]
list: []
}
},
mutations: {
updateList (state, newList) {
state.list = newList
}
},
actions: {
// 请求方式:get
// 请求地址:http://localhost:3000/cart
async getList (context) {
const res = await axios.get('http://localhost:3000/cart')
context.commit('updateList', res.data)
}
},
getters: {}
}
App.vue
<template>
<div class="app-container">
<!-- Header 区域 -->
<cart-header></cart-header>
<!-- 商品 Item 项组件 -->
<cart-item v-for="item in list" :key="item.id" :item="item"></cart-item>
<!-- Foote 区域 -->
<cart-footer></cart-footer>
</div>
</template>
<script>
import CartHeader from '@/components/cart-header.vue'
import CartFooter from '@/components/cart-footer.vue'
import CartItem from '@/components/cart-item.vue'
import { mapState } from 'vuex'
export default {
name: 'App',
created () {
this.$store.dispatch('cart/getList')
},
computed: {
...mapState('cart', ['list'])
},
components: {
CartHeader,
CartFooter,
CartItem
}
}
</script>
<style lang="less" scoped>
.app-container {
padding: 50px 0;
font-size: 14px;
}
</style>
cart-item.vue
<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 class="btn btn-light">-</button>
<span class="count">{{ item.count }}</span>
<button class="btn btn-light">+</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CartItem',
methods: {
},
props: {
item: {
type: Object,
require: true
}
}
}
</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>
修改数量功能的实现
cart.js
// 新建购物车模块
import axios from 'axios'
export default {
namespaced: true,
state () {
return {
// 购物车数据 [{},{}]
list: []
}
},
mutations: {
updateList (state, newList) {
state.list = newList
},
// obj: {id:xxx, newCount:xxx}
updateCount (state, obj) {
// 根据 id 找到对应的对象,更新count属性即可
const goods = state.list.find(item => item.id === obj.id)
goods.count = obj.newCount
}
},
actions: {
// 请求方式:get
// 请求地址:http://localhost:3000/cart
async getList (context) {
const res = await axios.get('http://localhost:3000/cart')
context.commit('updateList', res.data)
},
// 请求方式: patch
// 请求地址: http://localhost:3000/cart/:id值 表示修改的是哪个对象
// 请求参数:
// {
// name:'新值',【可选】
// price:'新值',【可选】
// count:'新值',【可选】
// thumb:'新值',【可选】
// }
async updateCountAsync (context, obj) {
// 将修改更新同步到后台服务器
await axios.patch(`http://localhost:3000/cart/${obj.id}`, {
count: obj.newCount
})
// 将修改更新同步到 vuex
context.commit('updateCount', {
id: obj.id,
newCount: obj.newCount
})
}
},
getters: {}
}
cart-item.vue
<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 class="btn btn-light" @click="btnClick(-1)">-</button>
<span class="count">{{ item.count }}</span>
<button class="btn btn-light" @click="btnClick(1)">+</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CartItem',
methods: {
btnClick (step) {
const newCount = this.item.count + step
const id = this.item.id
if (newCount < 1) return
this.$store.dispatch('cart/updateCountAsync', {
id,
newCount
})
}
},
props: {
item: {
type: Object,
require: true
}
}
}
</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>
底部 getters 统计
cart.js
// 新建购物车模块
import axios from 'axios'
export default {
namespaced: true,
state () {
return {
// 购物车数据 [{},{}]
list: []
}
},
mutations: {
updateList (state, newList) {
state.list = newList
},
// obj: {id:xxx, newCount:xxx}
updateCount (state, obj) {
// 根据 id 找到对应的对象,更新count属性即可
const goods = state.list.find(item => item.id === obj.id)
goods.count = obj.newCount
}
},
actions: {
// 请求方式:get
// 请求地址:http://localhost:3000/cart
async getList (context) {
const res = await axios.get('http://localhost:3000/cart')
context.commit('updateList', res.data)
},
// 请求方式: patch
// 请求地址: http://localhost:3000/cart/:id值 表示修改的是哪个对象
// 请求参数:
// {
// name:'新值',【可选】
// price:'新值',【可选】
// count:'新值',【可选】
// thumb:'新值',【可选】
// }
async updateCountAsync (context, obj) {
// 将修改更新同步到后台服务器
await axios.patch(`http://localhost:3000/cart/${obj.id}`, {
count: obj.newCount
})
// 将修改更新同步到 vuex
context.commit('updateCount', {
id: obj.id,
newCount: obj.newCount
})
}
},
getters: {
// 商品总数 sum + item.count
total (state) {
return state.list.reduce((sum, item) => sum + item.count, 0)
},
// 商品总结个 sum + item.count * item.price
totalPrice (state) {
return state.list.reduce((sum, item) => sum + item.count * item.price, 0)
}
}
}
cart-footer.vue
<template>
<div class="footer-container">
<!-- 中间的合计 -->
<div>
<span>共 {{ total }} 件商品,合计:</span>
<span class="price">¥{{ totalPrice }}</span>
</div>
<!-- 右侧结算按钮 -->
<button class="btn btn-success btn-settle">结算</button>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'CartFooter',
computed: {
...mapGetters('cart', ['total', 'totalPrice'])
}
}
</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>