提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
1.功能模块分析
- 请求动态渲染购物车,数据存Vuex
- 数字框控件修改数据
- 动态计算总价和总数量
2.脚手架新建项目(注意:勾选vuex)
vue create vue-cart-demo
3.功能实现
- 新建
store/modules/cart.js
(1) 语法一:
const state = {}
const getters = {}
const mustations = {}
const actions = {}
export default {
namespaced: true,
state,
mustations,
getters,
actions
}
(2) 语法二(更推荐):
// cart 购物车模块
export default {
namespaced: true,
state () {
return {
// 购物车数据 [{},{}]
list: []
}
},
getters: {},
mutations: {},
actions: {}
}
- 基于json-server 工具,准备后端接口服务环境
当后端接口没有准备就绪时,可以利用一些工具,例如json-server,快速帮我们生成一套增删改查的接口
(1)安装全局工具 json-server (全局工具仅需要安装一次)
yarn global add json-server 或 npm i json-server -g
(2)代码根目录新建一个 db 目录
(3)新建一个json文件,此处不用新建,直接将资料 index.json 移入 db 目录
(4)进入 db 目录,执行命令,启动后端接口服务 (使用–watch 参数 可以实时监听 json 文件的修改)
json-server --watch index.json
此处报错
解决:
- 全局安装指令改为:
npm install -g json-server
- 查看Json-server版本
json-server --version
- 创建Json文件
{
"cart": [
{
"id": 100001,
"name": "低帮城市休闲户外鞋天然牛皮COOLMAX纤维",
"price": 128,
"count": 1,
"thumb": "https://yanxuan-item.nosdn.127.net/3a56a913e687dc2279473e325ea770a9.jpg"
},
{
"id": 100002,
"name": "网易味央黑猪猪肘330g*1袋",
"price": 39,
"count": 10,
"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"
}
],
"friends":[
{ "id":1,"name":"zs","age":18},
{ "id":2,"name":"ls","age":16},
{ "id":3,"name":"ww","age":20}
]
}
- 进入JSON文件所在文件夹, (使用–watch 参数 可以实时监听 json 文件的修改)
json-server --watch index.json
可以通过: http://localhost:3000/friends 访问
http://localhost:3000/friends/1 就是访问friends下id=1的数据
http://localhost:3000/cart/100001 就是id=100001的数据
(5)安装axios
(6)代码展示
cart.js 代码
// cart 购物车模块
import axios from 'axios'
export default {
namespaced: true,
state () {
return {
// 购物车数据 [{},{}]
list: []
}
},
getters: {
goodsCount (state) {
return state.list.reduce((sum, item) => sum + item.count, 0)
},
priceAll (state) {
return state.list.reduce((sum, item) => sum + item.count * item.price, 0)
}
},
mutations: {
update (state, newList) {
state.list = newList
},
resetCount (state, newValue) {
const restItem = state.list.find(item => item.id === newValue.id)
restItem.count = newValue.count
}
},
actions: {
// 查
async asyncGetData (context, newData) {
const res = await axios.get('http://localhost:3000/cart')
console.log(res.data)
newData = res.data
context.commit('update', newData)
},
// 改
async asyncChangeData (context, obj) {
await axios.patch('http://localhost:3000/cart/' + obj.id, { count: obj.count })
// 2.更新仓库中的数据
context.commit('resetCount', obj)
}
}
}
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="onBtnClick(-1)">-</button>
<span class="count">{{item.count}}</span>
<button class="btn btn-light" @click="onBtnClick(1)">+</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CartItem',
props: {
item: Object
},
methods: {
onBtnClick (step) {
const newCount = (this.item.count + step) || 1
if (newCount < 1) return
// 发送修改数量请求
this.$store.dispatch('cart/updateCount', {
id: this.item.id,
count: newCount
})
}
}
}
</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-header.vue 代码
<template>
<div class="header-container">购物车案例</div>
</template>
<script>
export default {
name: 'CartHeader'
}
</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>
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>
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',
components: {
CartHeader,
CartFooter,
CartItem
},
created () {
this.$store.dispatch('cart/getList')
},
computed: {
...mapState('cart', ['list'])
}
}
</script>
<style lang="less" scoped>
.app-container {
padding: 50px 0;
font-size: 14px;
}
</style>