当前位置: 首页 > news >正文

智慧商城项目4-购物车功能

加入购物车

唤起弹层

按需导入 van-action-sheet

x import { ActionSheet } from 'vant'
Vue.use(ActionSheet)

在prodetail/index.js的模板下面加入

    <!-- 加入购物车的弹窗底层 --><van-action-sheet v-model="showPannel" :title="mode === 'cart' ? '加入购物车' : '立刻购买'"><div class="product"><div class="product-title"><div class="left"><img :src="detail.goods_image" alt=""></div><div class="right"><div class="price"><span>¥</span><span class="nowprice">{{ detail.goods_price_min }}</span></div><div class="count"><span>库存</span><span>{{ detail.stock_total }}</span></div></div></div><div class="num-box"><span>数量</span><!-- v-model本质上是v-bind:value和v-on:input/@input的语法糖 --><!-- 这样有个好处就是子组件直接this.$emit,如组件不需要再手动去触发input事件了 --><CountBox v-model="addCount"></CountBox></div><div class="showbtn" v-if="detail.stock_total>0"><div class="btn" v-if="mode==='cart'" @click="addCart">加入购物车</div><div class="btn now" v-else @click="goBuyNow">立刻购买</div></div><div class="btn-none" v-else>该商品已抢完</div></div></van-action-sheet>

 然后注册点击事件

<div class="btn-add" @click="addFn">加入购物车</div>
<div class="btn-buy" @click="buyFn">立刻购买</div>addFn () {this.mode = 'cart'this.showPannel = true
},
buyFn () {this.mode = 'buyNow'this.showPannel = true
}

之后加入样式

.product {.product-title {display: flex;.left {img {width: 90px;height: 90px;}margin: 10px;}.right {flex: 1;padding: 10px;.price {font-size: 14px;color: #fe560a;.nowprice {font-size: 24px;margin: 0 5px;}}}}.num-box {display: flex;justify-content: space-between;padding: 10px;align-items: center;}.btn, .btn-none {height: 40px;line-height: 40px;margin: 20px;border-radius: 20px;text-align: center;color: rgb(255, 255, 255);background-color: rgb(255, 148, 2);}.btn.now {background-color: #fe5630;}.btn-none {background-color: #cccccc;}
}

封装数字框组件

然后封装封装组件 components/CountBox.vue

<template><div class="count-box"><button @click="handleSub" class="minus">-</button><input :value="value" @change="handleChange" class="inp" type="text"><button @click="handleAdd" class="add">+</button></div>
</template><script>
export default {props: {value: {type: Number,default: 1}},methods: {handleSub () {if (this.value <= 1) {return}this.$emit('input', this.value - 1)},handleAdd () {this.$emit('input', this.value + 1)},handleChange (e) {// console.log(e.target.value)const num = +e.target.value // 转数字处理 (1) 数字 (2) NaN// 输入了不合法的文本 或 输入了负值,回退成原来的 value 值if (isNaN(num) || num < 1) {e.target.value = this.valuereturn}this.$emit('input', num)}}
}
</script><style lang="less" scoped>
.count-box {width: 110px;display: flex;.add, .minus {width: 30px;height: 30px;outline: none;border: none;background-color: #efefef;}.inp {width: 40px;height: 30px;outline: none;border: none;margin: 0 5px;background-color: #efefef;text-align: center;}
}
</style>

最后我们需要去使用我们封装好的组件,先导入,然后给一个默认值,之后在模板中去使用

import CountBox from '@/components/CountBox.vue'export default {name: 'ProDetail',components: {CountBox},data () {return {addCount: 1...}},
}<div class="num-box"><span>数量</span><CountBox v-model="addCount"></CountBox>
</div>

 判断token登录提示

购物车模块

基本静态布局

首先复制以下基本结构

<template><div class="cart"><van-nav-bar title="购物车" fixed /><!-- 购物车开头 --><div class="cart-title"><span class="all">共<i>4</i>件商品</span><span class="edit"><van-icon name="edit" />编辑</span></div><!-- 购物车列表 --><div class="cart-list"><div class="cart-item" v-for="item in 10" :key="item"><van-checkbox></van-checkbox><div class="show"><img src="http://cba.itlike.com/public/uploads/10001/20230321/a072ef0eef1648a5c4eae81fad1b7583.jpg" alt=""></div><div class="info"><span class="tit text-ellipsis-2">新Pad 14英寸 12+128 远峰蓝 M6平板电脑 智能安卓娱乐十核游戏学习二合一 低蓝光护眼超清4K全面三星屏5GWIFI全网通 蓝魔快本平板</span><span class="bottom"><div class="price">¥ <span>1247.04</span></div><div class="count-box"><button class="minus">-</button><input class="inp" :value="4" type="text" readonly><button class="add">+</button></div></span></div></div></div><div class="footer-fixed"><div  class="all-check"><van-checkbox  icon-size="18"></van-checkbox>全选</div><div class="all-total"><div class="price"><span>合计:</span><span>¥ <i class="totalPrice">99.99</i></span></div><div v-if="true" class="goPay">结算(5)</div><div v-else class="delete">删除</div></div></div></div>
</template><script>
export default {name: 'CartPage'
}
</script><style lang="less" scoped>
// 主题 padding
.cart {padding-top: 46px;padding-bottom: 100px;background-color: #f5f5f5;min-height: 100vh;.cart-title {height: 40px;display: flex;justify-content: space-between;align-items: center;padding: 0 10px;font-size: 14px;.all {i {font-style: normal;margin: 0 2px;color: #fa2209;font-size: 16px;}}.edit {.van-icon {font-size: 18px;}}}.cart-item {margin: 0 10px 10px 10px;padding: 10px;display: flex;justify-content: space-between;background-color: #ffffff;border-radius: 5px;.show img {width: 100px;height: 100px;}.info {width: 210px;padding: 10px 5px;font-size: 14px;display: flex;flex-direction: column;justify-content: space-between;.bottom {display: flex;justify-content: space-between;.price {display: flex;align-items: flex-end;color: #fa2209;font-size: 12px;span {font-size: 16px;}}.count-box {display: flex;width: 110px;.add,.minus {width: 30px;height: 30px;outline: none;border: none;}.inp {width: 40px;height: 30px;outline: none;border: none;background-color: #efefef;text-align: center;margin: 0 5px;}}}}}
}.footer-fixed {position: fixed;left: 0;bottom: 50px;height: 50px;width: 100%;border-bottom: 1px solid #ccc;background-color: #fff;display: flex;justify-content: space-between;align-items: center;padding: 0 10px;.all-check {display: flex;align-items: center;.van-checkbox {margin-right: 5px;}}.all-total {display: flex;line-height: 36px;.price {font-size: 14px;margin-right: 10px;.totalPrice {color: #fa2209;font-size: 18px;font-style: normal;}}.goPay, .delete {min-width: 100px;height: 36px;line-height: 36px;text-align: center;background-color: #fa2f21;color: #fff;border-radius: 18px;&.disabled {background-color: #ff9779;}}}}
</style>

 不要忘了去vant-ui.js去导入使用

import { Checkbox } from 'vant'
Vue.use(Checkbox)

构建vuex模块

  1. 新建 modules/cart.js 模块

export default {namespaced: true,state () {return {cartList: []}},mutations: {},actions: {},getters: {}
}
  1. 挂载到 store 上面

  去api/cart.js封装接口

// 获取购物车列表数据
export const getCartList = () => {return request.get('/cart/list')
}

之后去封装mutations和actions

mutations: {setCartList (state, newList) {state.cartList = newList},
},
actions: {async getCartAction (context) {const { data } = await getCartList()data.list.forEach(item => {item.isChecked = true})context.commit('setCartList', data.list)}
},

 然后在页面dispatch使用

computed: {isLogin () {return this.$store.getters.token}
},
created () {// 必须是登录过的用户if (this.isLogin) {this.$store.dispatch('cart/getCartAction')}
},

mapState渲染购物车列表

mapState去映射,然后去动态的渲染

import { mapState } from 'vuex'computed: {...mapState('cart', ['cartList'])
}
<!-- 购物车列表 -->
<div class="cart-list"><div class="cart-item" v-for="item in cartList" :key="item.goods_id"><van-checkbox icon-size="18" :value="item.isChecked"></van-checkbox><div class="show" @click="$router.push(`/prodetail/${item.goods_id}`)"><img :src="item.goods.goods_image" alt=""></div><div class="info"><span class="tit text-ellipsis-2">{{ item.goods.goods_name }}</span><span class="bottom"><div class="price">¥ <span>{{ item.goods.goods_price_min }}</span></div><CountBox :value="item.goods_num"></CountBox></span></div></div>
</div>

封装getters动态计算展示

getters: {cartTotal (state) {return state.cartList.length},selCartList (state) {return state.cartList.filter(item => item.isChecked)},selCount (state, getters) {return getters.selCartList.reduce((sum, item, index) => sum + item.goods_num, 0)},selPrice (state, getters) {return getters.selCartList.reduce((sum, item, index) => {return sum + item.goods_num * item.goods.goods_price_min}, 0).toFixed(2)}
}

页面中在computed去映射

computed: {...mapGetters('cart', ['cartTotal', 'selCount', 'selPrice']),
},<!-- 购物车开头 -->
<div class="cart-title"><span class="all">共<i>{{ cartTotal || 0 }}</i>件商品</span><span class="edit"><van-icon name="edit"  />编辑</span>
</div><div class="footer-fixed"><div  class="all-check"><van-checkbox  icon-size="18"></van-checkbox>全选</div><div class="all-total"><div class="price"><span>合计:</span><span>¥ <i class="totalPrice">{{ selPrice }}</i></span></div><div v-if="true" :class="{ disabled: selCount === 0 }" class="goPay">结算({{ selCount }})</div><div v-else  :class="{ disabled: selCount === 0 }" class="delete">删除({{ selCount }})</div></div>
</div>

全选反选

全选getters

getters: {isAllChecked (state) {return state.cartList.every(item => item.isChecked)}
}...mapGetters('cart', ['isAllChecked']),<div class="all-check"><van-checkbox :value="isAllChecked" icon-size="18"></van-checkbox>全选
</div>

点击小轩,修改状态

<van-checkbox @click="toggleCheck(item.goods_id)" ...></van-checkbox>toggleCheck (goodsId) {this.$store.commit('cart/toggleCheck', goodsId)
},mutations: {toggleCheck (state, goodsId) {const goods = state.cartList.find(item => item.goods_id === goodsId)goods.isChecked = !goods.isChecked},
}

 点击全选,重置状态

<div @click="toggleAllCheck" class="all-check"><van-checkbox :value="isAllChecked" icon-size="18"></van-checkbox>全选
</div>toggleAllCheck () {this.$store.commit('cart/toggleAllCheck', !this.isAllChecked)
},mutations: {toggleAllCheck (state, flag) {state.cartList.forEach(item => {item.isChecked = flag})},
}

数字卡修改数量

先封装接口

// 更新购物车商品数量
export const changeCount = (goodsId, goodsNum, goodsSkuId) => {return request.post('/cart/update', {goodsId,goodsNum,goodsSkuId})
}

页面中去注册点击事件

<CountBox :value="item.goods_num" @input="value => changeCount(value, item.goods_id, item.goods_sku_id)"></CountBox>changeCount (value, goodsId, skuId) {this.$store.dispatch('cart/changeCountAction', {value,goodsId,skuId})
},

发送请求,并且本地同步更新

mutations: {changeCount (state, { goodsId, value }) {const obj = state.cartList.find(item => item.goods_id === goodsId)obj.goods_num = value}
},
actions: {async changeCountAction (context, obj) {const { goodsId, value, skuId } = objcontext.commit('changeCount', {goodsId,value})await changeCount(goodsId, value, skuId)},
}

编辑、删除、空购物车处理

data 提供数据, 定义是否在编辑删除的状态

  1. data () {return {isEdit: false}
    },

2. 注册点击事件,修改状态

<span class="edit" @click="isEdit = !isEdit"><van-icon name="edit"  />编辑
</span>

 3.底下按钮根据状态去变化

<div v-if="!isEdit" :class="{ disabled: selCount === 0 }" class="goPay">去结算({{ selCount }})
</div>
<div v-else :class="{ disabled: selCount === 0 }" class="delete">删除</div>

4.监视编辑器状态,动态传值去控制复选框状态

watch: {isEdit (value) {if (value) {this.$store.commit('cart/toggleAllCheck', false)} else {this.$store.commit('cart/toggleAllCheck', true)}}
}

 接下来完成删除功能,首先封装接口

// 删除购物车
export const delSelect = (cartIds) => {return request.post('/cart/clear', {cartIds})
}

注册点击事件

<div v-else :class="{ disabled: selCount === 0 }" @click="handleDel" class="delete">删除({{ selCount }})
</div>async handleDel () {if (this.selCount === 0) returnawait this.$store.dispatch('cart/delSelect')this.isEdit = false
},

 然后提供异步请求去清空数据,注意删除了之后得再去重新去调用获取最新的数据

actions: {// 删除购物车数据async delSelect (context) {const selCartList = context.getters.selCartListconst cartIds = selCartList.map(item => item.id)await delSelect(cartIds)Toast('删除成功')// 重新拉取最新的购物车数据 (重新渲染)context.dispatch('getCartAction')}
},

当购物车是空的时候呢或者没登陆的时候,页面得展示成空的图片,那么我们可以用v-if去判断 

<div class="cart-box" v-if="isLogin && cartList.length > 0"><!-- 购物车开头 --><div class="cart-title">...</div><!-- 购物车列表 --><div class="cart-list">...</div><div class="footer-fixed">...</div>
</div><div class="empty-cart" v-else><img src="@/assets/empty.png" alt=""><div class="tips">您的购物车是空的, 快去逛逛吧</div><div class="btn" @click="$router.push('/')">去逛逛</div>
</div>

然后填写相关的样式,最终,这个购物车的功能就完成了

后面就是要完成订单结算台的功能以及打包了,即将截止,期待大家的关注~~你们的赞与评论是我更新的动力!!

 


http://www.mrgr.cn/news/58360.html

相关文章:

  • 从0到1学习node.js(path模块以及HTTP协议)
  • InnoDB 存储引擎的底层逻辑架构白话-必知必会
  • 操作系统期末|考研复习知识点汇总 - 持续更新
  • 一座数智工厂,看见汽车制造的诗与远方
  • AnaTraf | TCP重传的工作原理与优化方法
  • 奈氏定理和香农定理
  • Django配置路由后,为什么输入http://127.0.0.1:8000/ 网址后报错了?
  • 【逆向基础】十七、PE文件格式(二)
  • 16 使用宏定义定义常量
  • OFFER攻略 08| 130+个offer背后:AIGC产品经理成长之路,零基础入门到精通,收藏这一篇就够了
  • 汇编教程 最终:文件管理与内存管理
  • Jvm中的堆和栈
  • Docker容器的基础镜像:构建现代应用程序的基石
  • 讲一讲AOP的原理,AOP在哪些场景下会失效?
  • openresty安装
  • Ubuntu 下安装 Nginx
  • NativeCrash 率从万分位降到十万分位,我做了这几件事...
  • 对比两个el-table,差异数据突显标记
  • springboot仓库管理系统-计算机毕业设计源码19585
  • 集群分发脚本
  • WUP-MY-POS-PRINTER 旻佑热敏打印机票据打印uniapp插件使用说明
  • 被面试官怼了,对nacos的原理都不理解,还多年的微服务工作经验?
  • CTF-RE 从0到N 1-1-1 开始之前-c函数手册
  • 一年四起供应链投毒事件的幕后黑手
  • 储能蓝海:技术革新与成本骤降引爆市场
  • python_删除二维列表的制定列