Vue2电商平台(五)、加入购物车,购物车页面
文章目录
- 一、加入购物车
- 1. 添加到购物车的接口
- 2. 点击按钮的回调函数
- 3. 请求成功后进行路由跳转
- (1)、创建路由并配置路由规则
- (2)、路由跳转并传参(本地存储)
- 二、购物车页面的业务
- 1. uuid生成用户id
- 2. 获取购物车数据
- 3. 计算打勾商品总价
- 4. 全选与商品打勾
- (1)、商品全部打勾,自动勾全选(every方法)
- (2)、打勾单个商品,更新数据
- (3)、点击全选框,对每个商品的单选框进行操作
- 5. 删除购物车
- (1)、删除单个商品
- (2)、删除选中的商品
- 6. 处理购物车的产品数量(难)
一、加入购物车
思路分析:点击加入购物车按钮后,需要进行如下的几个操作
(1)、向服务器发送请求,将产品信息(产品Id,购买的产品数量)发给服务器进行存储。
(2)、服务器存储成功,进行路由跳转,跳转到加入购物车成功的界面。
(3)、服务器存储失败,给用户提示。
1. 添加到购物车的接口
写发送请求的接口:api/index.js
// 添加到购物车(对已有物品进行数量改动) url: /api/cart/addToCart/{ skuId }/{ skuNum } 请求方式post
export const reqAddOrUpdateShopCart = (skuId, skuNum) => {return requests({ url: `/cart/addToCart/${skuId}/${skuNum} `, method: 'post' })
}
2. 点击按钮的回调函数
<!-- 加入购物车 --><div class="add"><a href="javascript:" @click="addShopCar">加入购物车</a></div><script>addShopCar () {// 1. 派发action请求,将数据给服务器进行存储this.$store.dispatch('detail/addOrUpdateShopCart', { skuId: this.$route.params.goodsId, skuNum: this.skuNum })// 2. 服务器存储成功---进行路由跳转并携带参数 // 3. 服务器存储失败----给用户提示}</script>
这里需要注意的是
(1)、这里发请求不需要服务器返回数据,所以拿到服务器的返回结果时并不需要存储。服务器的返回结果只会告知请求是发送成功or发送失败。
(2)、问题: 请求的返回结果在仓库的actions里,组件中如何拿到这个返回结果以判断是请求成功还是请求失败。
解决:async函数返回的是一个Promise对象,组件中派发请求相当于调用了这个async函数,应该得到一个Promise对象。那我们就在这个async函数里判断请求成功或失败来实例化一个Promise对象,返回给组件。
// detail小仓库里:async addOrUpdateShopCart (context, { skuId, skuNum }) {let result = await reqAddOrUpdateShopCart(skuId, skuNum)if (result.code == 200) {// 加入购物车成功return 'ok'} else {// 加入购物车失败return Promise.reject(new Error('faile'))}}
回调函数中接收到这个
addShopCar () {// 1. 派发action请求,将数据给服务器进行存储:this.$store.dispatch('detail/addOrUpdateShopCart')就是在调用这个函数addOrUpdateShopCarttry {this.$store.dispatch('detail/addOrUpdateShopCart', { skuId: this.$route.params.goodsId, skuNum: this.skuNum })// 2. 服务器存储成功---进行路由跳转并携带参数this.$router.push({ name: 'addCartSuccess', })} catch (error) {// 3. 存储失败----给用户提示alert(error.message)}
}
3. 请求成功后进行路由跳转
(1)、创建路由并配置路由规则
1. 创建路由
将路由组件添加到pages文件夹

2. 配置路由规则

(2)、路由跳转并传参(本地存储)
如果路由跳转的时候不携带产品信息参数,则需要在跳转页面之后根据产品Id再次发送请求。这样做没必要。直接携带就好了。但是之前路由跳转传参都是简单的数字之类的。这里的产品信息(产品名称,产品属性)涉及到对象,还要传递购买的产品数量。
二、购物车页面的业务
1. uuid生成用户id
进入购物车页面时,应将用户的id传给后端,后端根据用户id检索出该用户的购物车商品信息,传递给前端用于展示及进行其他操作。在这个系统中,我们用uuid库生成一个用户Id,在向后台发送请求时传递给后端。
1、创建文件utils/uuid_token.js:
需要注意:一个用户只能有一个Id,所以这里采用本地存储来存储uuid生成的用户id。每次先读取本地存储中是否有用户id,有的话直接用,没有就生成一个新的。
// 用于生成用户的临时身份Id
import { v4 as uuidv4 } from 'uuid'
/*
- 先检查本地存储中有没有这个用户Id,
- 有就直接返回,
- 没有则生成新的
*/
export const getUUID = () => {let uuid_token = localStorage.getItem('UUID_TOKEN')// 如果没有if (!uuid_token) {// 生成用户iduuid_token = uuidv4();// 存储到localStorage.setItem('UUID_TOKEN', uuid_token)}return uuid_token
}
2、在仓库里调用函数,得到userTempId.

3、将userTempId配置在请求头里
查看文档里的接口会发现:请求中并没有让携带参数,所以除了通过参数能将数据传递给后台,还能用什么方式呢?答:请求头。可以将用户Id放在请求头中。

2. 获取购物车数据
1、写接口
api/index.js
// 获取购物车列表 url: /api/cart/cartList 请求方式 get
export const reqShopCart = () => {return requests({ url: '/cart/cartList ', method: 'get' })
}
2、vuex三连环

3、组件发请求
因为在购物车页面,其他的操作也会重新向服务器发请求,所以这里将放请求的操作包装成一个函数。

根据获取到的数据渲染界面

3. 计算打勾商品总价
computed: {...mapState('shopCart', ['cartInfoList']),// 计算总价totalPrice () {let sum = 0this.cartInfoList.forEach((el) => {if (el.isChecked === 1) { // 商品是否被选中sum += (el.skuPrice * el.skuNum)}})return sum},
}
4. 全选与商品打勾
(1)、商品全部打勾,自动勾全选(every方法)
<div class="select-all"><input class="chooseAll" type="checkbox" :checked="isAllChecked"/><span>全选</span>
</div>
<script>
computed:{isAllChecked () {// 统计checked的数量,是否等于数组长度let flag = this.cartInfoList.every((item) => {return item.isChecked == 1})return flag}
}
</script>
关于every方法,回顾博客:
(2)、打勾单个商品,更新数据
选中某个商品后,商品的isChecked属性发生变化,需要将更新后的商品信息发给后台保存。(我感觉这个业务在实际中没必要,每次勾选或取消勾选都发送请求,服务器压力增大,没必要。但是这里是为了练习一些知识点)。

接口
// 切换商品选中状态 url: /api/cart/checkCart/{skuID}/{isChecked} 请求方式 get
export const changeIsChecked = (skuID, isChecked) => {return requests({ url: `/cart/checkCart/${skuID}/${isChecked}`, method: 'get' })
}
仓库Vuex:
// 改变商品选中的状态async changeChecked (context, { skuID, isChecked }) {let res = await changeIsChecked(skuID, isChecked)if (res.code === 200) {return 'ok'} else {return Promise.reject(new Error('faile'))}},
组件:
<!-- 选择框 --><li class="cart-list-con1"><input type="checkbox" name="chk_list" :checked="good.isChecked == 1"@change="handleChange(good.skuId, $event)" /></li><script>// 切换商品选中的状态async handleChange (skuId, e) {try {// 接口中的参数是1或0,不是true 或falselet isChecked = e.target.checked ? 1 : 0await this.$store.dispatch('shopCart/changeChecked', { skuID: skuId, isChecked: isChecked })this.getCartData()// 成功重新发请求} catch (e) {alert(e.message)}}</script>
(3)、点击全选框,对每个商品的单选框进行操作
由于没有修改多个商品的接口,所以思路是多次调用打勾单个商品的接口,把每个商品的勾选状态改为当前全选框的状态。
此处需要注意在actions里,如何通过dispatch调用其他actions的函数
// 改变商品选中的状态async changeChecked (context, { skuID, isChecked }) {let res = await changeIsChecked(skuID, isChecked)if (res.code === 200) {return 'ok'} else {return Promise.reject(new Error('faile'))}},// 改变所有商品的状态// 第一个参数是context,包含:state,dispatch,getters登,通过解构将需要的解构出来async changeAllState ({ dispatch, state }, stateFlag) {let promiseAll = []state.cartInfoList.forEach(el => {if (el.isChecked !== stateFlag) {// 通过dispatch调用其他的actions函数let promise = dispatch('changeChecked', { skuID: el.skuId, isChecked: stateFlag })promiseAll.push(promise)}});return Promise.all(promiseAll)},

给组件添加的全选点击事件
<div class="select-all">
<!--&& cartInfoList.length > 0 是考虑到处于当商品全部被删除时,全选框不应该处于选中状态--><input class="chooseAll" type="checkbox":checked="isAllChecked && cartInfoList.length > 0"@change="handleAllChange" /><span>全选</span>
</div>
<script>async handleAllChange (e) {try {// e.target.checked获取全选框的值,let stateFlag = e.target.checked ? 1 : 0await this.$store.dispatch('shopCart/changeAllState', stateFlag)// 成功则重新发送请求this.getCartData()} catch (error) {alert(error.message);}}
</script>
5. 删除购物车
文档接口:

(1)、删除单个商品
接口:
// 删除购物车商品 url:/api/cart/deleteCart/{skuId} 请求方式:delete
export const reqDeleteGoodById = (skuId) => {return requests({ url: `/cart/deleteCart/${skuId}`, method: 'delete' })
}
Vuex (这里也不需要三连环)
// 删除商品async deleteOneGood (context, skuId) {let res = await reqDeleteGoodById(skuId)if (res.code === 200) {return 'ok'} else {return Promise.reject(new Error('failed'))}}
组件:
<a class="sindelet" @click="deleteOneGood(good.skuId)">删除</a>
<script>// 删除一个商品async deleteOneGood (skuId) {try {await this.$store.dispatch('shopCart/deleteOneGood', skuId)this.getCartData() // 重新发送请求 } catch (error) {alert(error.message)}}
</script>
注意这里有一个bug!
(2)、删除选中的商品
没有删除多个商品的接口,所以思路是多次调用删除单个商品的接口,将选中的商品全都删除。
组件:
// 删除已选中的商品;async deleteAllChecked () {try {await this.$store.dispatch('shopCart/deleteAllSelected')// 重新发请求获取购物车列表this.getCartData()} catch (error) {alert(error.message)}}
重点是Vuex中:
如果有一个删除失败,则这个操作就是失败了。采用Promise.all()方法给组件反馈成功或失败
actions:{// 删除全部商品deleteAllSelected ({ dispatch, state }) {let promiseAll = []// 获取购物车的全部商品state.cartInfoList.forEach((el) => {// 如果被该商品被勾选if (el.isChecked === 1) {let promise = dispatch('deleteOneGood', el.skuId)// 将每一次返回的Promise添加到数组当中promiseAll.push(promise)}})//Promise.all([p1,p2,p3]) p1,p2,p3都是Promise对象,其中有一个失败则全失败return Promise.all(promiseAll)}
}
6. 处理购物车的产品数量(难)
这里用到的接口之前写过,注意参数的描述!

根据这个文档描述,猜测这个skuNum的值是商品数量的变化量。
这个接口可以用在两个地方,添加到购物车和对已有物品进行数量改动。在添加到购物车的场景中,skuNum的值一直都是正的值。
HTML结构:
<!--修改产品数量 --><li class="cart-list-con5"><!-----减号------><a class="mins" @click="handler('sub', -1, good)">-</a><!-----文本框输入,注意事件是失去焦点事件blur。$event.target.value * 1 是因为 非数值字符串*1 的值为NaN ------><input autocomplete="off"type="text":value="good.skuNum"minnum="1"class="itxt"@blur="handler('input', $event.target.value * 1, good)"/><!----加号------><a class="plus" @click="handler('add', 1, good)">+</a></li>
回调函数:
/*type:操作的类型 add sub inputdisNum: 1,-1,输入框的最终数good:产品*/async handler (type, disNum, good) {// 判断不同的情况switch (type) {case 'add': // 加disNum = 1;break;case 'sub': // 减if (good.skuNum > 1) {disNum = -1} else {disNum = 0}break;case 'input':if (isNaN(disNum) || disNum < 0) {disNum = 0} else {// 增量disNum = parseInt(disNum) - good.skuNum}break;}// 发送请求 try {await this.$store.dispatch('detail/addOrUpdateShopCart', { skuId: good.skuId, skuNum: disNum })this.getCartData() // 请求成功,重新获取数据} catch (e) {alert(e.message)}}
但是这样做,有个小bug。当用户频繁点击-是,数量可能会出现负值

这是因为连续快速点击,请求还来不及发送,数据没改,所以每次disnum都是-1。解决办法就是节流,给服务器一些缓冲的时间,防止数据不同步出现上述bug。
// 按需引入
import throttle from 'lodash/throttle'
handler: throttle(async function (type, disNum, good) {// 判断不同的情况switch (type) { ... }try {await this.$store.dispatch('detail/addOrUpdateShopCart', { skuId: good.skuId, skuNum: disNum })this.getCartData()} catch (e) {alert(e.message)}
}, 800)
最后分析一下,这个功能为什么要设计的这么麻烦,
(1) 为什么每+或-或输入一次都要发送一次请求,
(2) 为什么不能直接把输入框的商品最终数量传给后端,还要计算差值。
答:
(1) 每+或-或输入一次都是对数据进行了修改。购物车的商品数量改变后,刷新页面时,数量应该保持修改后的样子。所以每次操作完都要保存一下数据。
(2) 为什么要计算差值?因为这个接口还用来实现将商品添加到购物车。
我用淘宝试了一下,这个业务逻辑是当在页面详情页,多次重复的将商品添加到购物车时。购物车里该商品的数量应该是这几次重复添加的数量的和。而不是最后一次添加时选择的商品数量。
所以如果只将输入框里的最终数量传过去的话,商品数量只会是最后一次将该商品添加到购物车里时的选择的数量。
相关文章:
Vue2电商平台(五)、加入购物车,购物车页面
文章目录 一、加入购物车1. 添加到购物车的接口2. 点击按钮的回调函数3. 请求成功后进行路由跳转(1)、创建路由并配置路由规则(2)、路由跳转并传参(本地存储) 二、购物车页面的业务1. uuid生成用户id2. 获取购物车数据3. 计算打勾商品总价4. 全选与商品打勾(1)、商品全部打勾&a…...
众数信科 AI智能体政务服务解决方案——寻知智能笔录系统
政务服务解决方案 寻知智能笔录方案 融合民警口供录入与笔录生成需求 2分钟内生成笔录并提醒错漏 助办案人员二次询问 提升笔录质量和效率 寻知智能笔录系统 众数信科AI智能体 产品亮点 分析、理解行业知识和校验规则 AI实时提醒用户文书需注意部分 全文校验格式、内容…...
Redis篇(面试题 - 连环16炮)(持续更新迭代)
目录 目录 目录 (第一炮)一、Redis?常用数据结构? 1. 项目里面到了Redis,为什么选用Redis? 2. Redis 是什么? 3. Redis和关系型数据库的本质区别有哪些? 4. Redis 的线程模型…...
selenium元素定位
find_element和find_elements 元素定位有两个表达式,分别为find_element()和find_elements(),它们的不同点如下: find_element():找出的为单个元素,若有多个元素为同一表达式,则默认定位第一个元素&#…...
美畅物联丨视频汇聚从“设”开始:海康威视摄像机设置详解
在运用畅联云平台进行视频汇聚与监控管理时,海康威视的安防摄像机凭借其卓越的性能与广泛的应用兼容性,成为了众多用户的首选产品。海康威视摄像机参数设置与调试对于实现高效的安防监控至关重要。今天,让我们一同深入学习海康摄像机的参数设…...
聊天机器人羲和的代码04
进一步完善和优化聊天机器人GUI,使其更加丰富和美观,采取了以下措施: 添加图标:为应用程序添加一个图标。 调整布局:进一步优化布局,使其更加美观。 增加样式:使用更多的样式和主题来提升视觉效果。 添加动画:增加加载动画以提高用户体验。 优化控件:使用更现代的控件…...
Linux安装配置Jupyter Lab并开机自启
文章目录 1、安装配置jupyter lab首先需要使用pip3安装:生成配置文件和密码: 2、设置开机自启首先通过which jupyter查询到可执行文件路径:设置自启服务: 1、安装配置jupyter lab 首先需要使用pip3安装: pip3 instal…...
Java基础——`UUID.randomUUID()` 方法详细介绍
这里写自定义目录标题 UUID.randomUUID() 方法详细介绍1. 概述2. UUID 的结构与格式UUID 的 128 位结构划分: 3. UUID.randomUUID() 方法详解3.1 方法签名3.2 使用示例3.3 生成原理3.4 随机数生成的范围3.5 随机字符的取值范围 4. UUID 的版本与特性4.1 UUID 版本 4…...
前端面试常见手写代码题【详细篇】
文章目录 前言:防抖节流函数柯里化函数组合instanceof 实现实现new操作符的行为深拷贝继承实现:手写Promise数组中常见函数的实现 前言: 在前端面试中,经常会遇到要求手写的代码的题目,主要是考察我们的编程能力、和对…...
当代最厉害的哲学家改名大师颜廷利:北京、上海、广州和深圳房价精准预测
在2024年国庆节期间,北京、上海、广州和深圳的房地产市场异常活跃。作为山东济南籍的国际易学权威颜廷利教授,连续收到了这些大城市客户的感谢信和电话。 来自北京的王先生在信中写道:“非常感谢颜廷利教授这几年来对我们的鼓励和支持。在经历…...
MySQL常用指令码
本文精心挑选了一系列MySQL指令码,助你提升资料库效率、解决常见问题,让你的资料储存体验更加高效、可靠。 常用功能指令码 1.汇出整个资料库 mysqldump - u 使用者名称- p – default - character - set latin1 资料库名>汇出的档名(资料库预设编…...
OpenHarmony(鸿蒙南向开发)——轻量系统内核(LiteOS-M)【扩展组件】
往期知识点记录: 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~ 持续更新中…… C支持 基本概念 C作为目前使用最广泛的编程语言之一,…...
官方ROM 免费下载! 王者归来! 华为秘盒media Q M310(续)
最近在捣鼓电视盒子, 前帖讨论了如何拯救华为华为秘盒media Q M310, 详情请点击这里! https://blog.csdn.net/weixin_62598385/article/details/142658048 CSDN上有精简版的M310 ROM下载, 但是我点不进去, 要收年费&am…...
【Docker】05-Docker部署前端项目
1. nginx.conf worker_processes 1;events {worker_connections 1024; }http {include mime.types;default_type application/json;sendfile on;keepalive_timeout 65;server {listen 18080;# 指定前端项目所在的位置location / {root /usr/share/nginx…...
SQL进阶技巧:如何优化NULL值引发的数据倾斜问题?
目录 0 场景描述 1 问题分析 1.1 问题剖析 1.2 解决方案 2 小结 0 场景描述 实际业务中有些大量的null值或者一些无意义的数据参与到计算作业中,表中有大量的null值,如果表之间进行join操作,就会有shuffle产生,这样所有的null值都会被分配到一个reduce中,必然产生数…...
【09】纯血鸿蒙HarmonyOS NEXT星河版开发0基础学习笔记-Class类基础全解(属性、方法、继承复用、判断)
序言: 本文详细讲解了关于我们在程序设计中所用到的class类的各种参数及语法。 笔者也是跟着B站黑马的课程一步步学习,学习的过程中添加部分自己的想法整理为笔记分享出来,如有代码错误或笔误,欢迎指正。 B站黑马的课程链接&am…...
快速提升波段交易技能:4种实用策略分享
每个交易员的交易偏好是各不相同的,有人偏爱短线交易的迅速反应,有人钟情于中长线的稳健布局,还有人则热衷于波段交易的灵活操作。我们经常探讨短线与中长线的策略,但你了解波段交易的策略吗? 波段交易是什么…...
LeetCode 11 Container with Most Water 解题思路和python代码
题目: You are given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the ith line are (i, 0) and (i, height[i]). Find two lines that together with the x-axis form a container, such that the co…...
【深度学习】损失函数
损失函数(Loss Function)是机器学习和深度学习模型中的一个核心概念,它用于衡量模型的预测输出与真实标签之间的差异。通过优化(最小化)损失函数,模型可以不断调整其内部参数,提升预测性能。不同…...
力扣 中等 46.全排列
文章目录 题目介绍题解 题目介绍 题解 代码如下: class Solution {List<List<Integer>> res new ArrayList<>();// 存放符合条件结果的集合List<Integer> path new ArrayList<>();// 用来存放符合条件结果boolean[] used; // 标记…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
