Vue2电商前台项目——完成加入购物车功能和购物车页面

Vue2电商前台项目——完成加入购物车功能和购物车页面

文章目录

  • Vue2电商前台项目——完成加入购物车功能和购物车页面
    • 一、加入购物车
      • 1、路由跳转前先发请求把商品数据给服务器
        • (1)观察接口文档
        • (2)写接口
        • (3)dispatch传数据
        • (4)判断服务器是否已经收到商品数据
      • 2、请求成功后进行路由跳转
        • (1)创建路由并配置路由规则
        • (2)路由跳转并传参(练习本地存储)
    • 二、完成购物车页面的业务
      • 1、生成游客id
      • 2、获取相应的购物车数据
      • 3、计算打勾商品总价
      • 4、全选和商品的打勾联动
        • (1)全选按钮是否选中
        • (2)修改单个产品的选中状态
        • (3)点击全选时所有商品状态跟着切换
      • 5、删除购物车数据
        • (1)删除单个商品
        • (2)删除选中的所有商品
      • 6、购物车商品数量加减改(难点)
        • (1)分析一下
        • (2)配置请求的方法

一、加入购物车

1、路由跳转前先发请求把商品数据给服务器

(1)观察接口文档

这里其实只需要把已有物品的id和数量传给后台,后台不需要返回数据

请添加图片描述

请添加图片描述

(2)写接口

又到了咱滚瓜烂熟的写接口环节:

src/api/index.js
// 将产品添加到购物车中
export const reqAddShopCart = (skuId, skuNum) => {return requests({url: `/cart/addToCart/{skuId}/{skuNum}`,method: "post",});

(3)dispatch传数据

1、给加入购物车按钮添加点击事件

请添加图片描述

2、派送actions,然后把数据以对象的形式传过去,第一个键值对是商品id(当从Search到Detail路由跳转时就传过来的params参数),第二个键值对是购物车数量(我们之前已经存到了Detail组件的data里了)

    // 加入购物车的回调函数addShopcar() {// 派发actions// 1、发请求——将产品加入到数据库(通知服务、传递参数器)this.$store.dispatch("getShopCart", {skuId: this.$route.params.skuid,skuNum: this.skuNum,});// 2、服务器存储成功——进行路由跳转// 3、失败——给用户进行提示},

3、actions这边通过解构赋值,调用接口并把数据传给服务器(后端数据库)

const actions = {......async getShopCart({ commit }, { skuId, skuNum }) {let result = await reqAddShopCart(skuId, skuNum);console.log("添加到购物车的信息" + result);},
};

(4)判断服务器是否已经收到商品数据

这里要判断请求是否已经成功,也就是服务器是否收到了要加入购物车商品的数据,若成功了就要进行路由跳转并传参,若失败了就要给用户提示。

dispatch就会调用这个actions里的函数,调用这个async函数返回一个promise对象,这个Promise对象的状态和结果值取决于这个async函数的返回值。

1、如果返回一个非Promise对象,那么就是成功,值就是返回值;
2、如果返回Promise对象,那么状态和结果值取决于该Promise
3、如果不写返回值,且await后是失败的Promise,那么就会抛出异常,既然抛出异常,那么async函数返回的就是一个失败的Promise。
4、如果不写返回值,且await后是成功的Promise,那么就会返回undefined,async函数返回的就是一个成功的Promise,值是undefined。

const actions = {......//加入购物车//异步请求,把商品id和数量发送给服务器async getShopCart({ commit }, { skuId, skuNum }) {//其实这里不用try-catch,因为那边已经try了,不写return默认返回undefined(成功的Primise)//如果await后成功则返回undefined(没写return,返回成功的Promise)//如果失败则抛出异常(返回失败的Promise)let result = await reqAddShopCart(skuId, skuNum);console.log("添加到购物车的信息" + result);//这里只是把购物车数据给服务器,但是服务器不需要返回什么东西。所以这里我们不用再三连环了,通过dispatch把数据给服务器就已经好了// if (result.code == 200) {//   commit("GETSHOPCART", result.data);// }},
};
  methods: {......// 加入购物车的回调函数async addShopcar() {// 派发actions// 1、发请求——将产品加入到数据库(通知服务、传递参数器)try {// 2、服务器存储成功——进行路由跳转this.$store.dispatch("getShopCart", {skuId: this.$route.params.skuid,skuNum: this.skuNum,});} catch (error) {// 3、失败——给用户进行提示console.log("请求失败", error.message);}},},

2、请求成功后进行路由跳转

路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。

(1)创建路由并配置路由规则

1、所以我们先创建路由

请添加图片描述

2、配置路由规则src/router/routes.js

  {name: "addcat",path: "/addcartsuccess",component: AddCartSuccess,meta: {showFooter: true,},

(2)路由跳转并传参(练习本地存储)

3、写路由跳转和传参的代码

请添加图片描述

这里要传两个值:skuInfo对象和商品数量shopCarNum(其实skuInfo根本不用传,直接去仓库读就行了)。传参的时候最好别用params和query,因为带过去的需要是skuInfo这个对象,那么对象传过去的话,地址栏可能会是乱码。这里采用的方案是:会话存储——点此复习

1、使用query传简单的商品数量shopCarNum,因为数字不会乱码
2、使用会话存储(本地存储也行)带skuInfo过去(其实直接从仓库读就行了;或者你用query也行,无非就是地址栏乱码)

async addShopcar() {// 派发actions// 1、发请求——将产品加入到数据库(通知服务、传递参数器)try {// 2、服务器存储成功——进行路由跳转await this.$store.dispatch("getShopCart", {skuId: this.$route.params.skuid,skuNum: this.skuNum,});// 进行路由跳转并传参// 一些简单的数据可以通过query形式传参// 此处的产品信息比较复杂,是一个对象,所以我们可以采用会话存储sessionStorage// 本地存储和会话存储一般存的是字符串,所以我们把对象转化成JSON字符串进行存储sessionStorage.setItem("SKUINFO", JSON.stringify(this.skuInfo));this.$router.push({ name: "addcat", query: { skuNum: this.skuNum } });} catch (error) {// 3、失败——给用户进行提示console.log("请求失败", error.message);}},

注意:

  • 本地存储 里面只能存储字符串格式 ,因此需要把对象转换为字符串JSON.stringify()
  • 获取本地存储数据,需要把里面的字符串转换为对象格式JSON.parse() 我们才能使用里面的数据。

请添加图片描述

然后把相应的数据放到页面上

请添加图片描述

请添加图片描述

二、完成购物车页面的业务

点击查看商品详情就直接跳回去就行了,数据仓库本来就有不用重新发请求

<router-link class="sui-btn btn-xlarge" :to="`/detail/${skuInfo.id}`">查看商品详情
</router-link>

接下来是点击去购物车结算,跳到购物车结算页面
把购物车部分的静态搞过来,然后注册一下路由,并写个路由跳转。

<router-link to="/shopcart">去购物车结算 > </router-link>

请添加图片描述

1、生成游客id

这里后端应该是写了个逻辑,用一个叫userTempId的请求头字段来判断你是谁,然后返回给你相应的数据。

一般来说正常的逻辑应该是每个用户有自己的token,然后点击加入购物车之后,往用户-商品这个表里添加一行数据;读购物车取数据的时候呢,应该是传用户token参数获取相应的商品列表。而这里为了模拟,后端写好了useTempId字段,刷新时我们就给他个游客id(唯一id),它就拿着这个字段直接作为本地浏览器游客,所以请求购物车数据也不用传参。

这里随机生成游客id的方法有很多,可以使用nanoid、uuid、时间戳,这里我使用的是uuid。并且需要持久存储我们可以使用localStorage。

1、进入页面的时候先随机生成一个时间戳,作为用户的id。

请添加图片描述

请添加图片描述

2、请求数据时,在请求拦截器中将该id作为请求中userTempId的值。也就是把这个id放在请求头里传给服务器。

请添加图片描述

3、这样就完成了,只要本地存储中这个id没有被手动清除,那么每次都可以获取该id的购物车数据。

2、获取相应的购物车数据

写接口

// 获取购物车列表数据
export const reqCartList = () => {return requests({url: "/cart/cartList",method: "get",});
};

发请求,三连环。

请添加图片描述

请添加图片描述

最后,把数据展示在页面上
请添加图片描述

请添加图片描述

3、计算打勾商品总价

这个好算,利用isChecked属性,只计算选中(isChecked=1)的价格,forEach循环一下就行了。

请添加图片描述

  computed: {......// 计算购买商品的总价totalPrice() {let totalPrice = 0;this.cartInfoList.forEach((element) => {if (element.isChecked == 1) {totalPrice += element.skuNum * element.skuPrice;}});return totalPrice;},

4、全选和商品的打勾联动

(1)全选按钮是否选中

全选按钮是否选中,取决于每个复选框是否都选中,也就是判断每个元素的isChecked是不是都为1

    // 判断全选框是否勾选(若每个产品都勾选了,也就是isCheck都等于一,则勾选)isAllChecked() {// every:遍历每个元素// 只要有一个不等于1就返回falsereturn this.cartInfoList.every((item) => item.isChecked == 1);},

(2)修改单个产品的选中状态

修改单个产品状态需要去发送请求修改isChecked字段,这是因为总价那里用到了这个字段去计算,我们要实现勾选的计算总价,取消勾选就不计算。
修改产品勾选状态的接口,需要传两个参数:skuId(产品id)、isChecked(产品的选中状态)

下面依旧是咱们熟悉的步骤。

写接口:

// 修改购物车产品选中状态
export const reqUpdateCheck = (skuId, isChecked) => {return requests({url: `/cart/checkCart/${skuId}/${isChecked}`,method: "get",});
};

三连环vuex调用接口:

const actions = {// 修改购物车某个产品的选中状态async updateChecked({ commit }, { skuId, isChecked }) {let result = await reqUpdateCheck(skuId, isChecked);if (result.code == 200) {return "ok";} else {return Promise.reject(new Error("fail"));}},
};

每个购物车商品按钮配置一个点击事件(或者切换事件)如果当前勾选状态为1(勾选),那么改成0(取消勾选),反之也一样。

在html添加事件:

请添加图片描述

// 修改某个产品的勾选状态async updateChecked(cart, $event) {// 带过去的isChecked原本是布尔值,但是我们需要的应该是0或者1// console.log(event.target.checked);try {// 如果修改成功,再次获取服务器数据let checked = event.target.checked ? "1" : "0";await this.$store.dispatch("updateChecked", {skuId: cart.skuId,isChecked,});this.getData();} catch (error) {alert("修改失败" + error);}},

(3)点击全选时所有商品状态跟着切换

给全选的勾选框添加点击事件。总体来说主要思路就是点击全选时派发请求,这个请求需要把每个商品的勾选状态改成当前全选框的状态。
所以需要在actions中遍历购物车数据并派发请求,(当然其实在组件中写也一样,就是这样规范点),用try-catch捕获,如果都请求成功,那么就使用Promise.all获取成功的标志

  // 修改购物车某个产品的选中状态async updateChecked({ commit }, { skuId, isChecked }) {let result = await reqUpdateCheck(skuId, isChecked);if (result.code == 200) {return "ok";} else {return Promise.reject(new Error("fail"));}},// 点击全选按钮修改所有商品的状态changeAllChecked({ dispatch, state }, isChecked) {let promiseAll = [];state.cartList[0].cartInfoList.forEach((el) => {try {let promise = dispatch("updateChecked", {skuId: el.skuId,isChecked,});promiseAll.push(promise);} catch (error) {console.log(`${el.skuNum}修改失败`, err);}});return Promise.all(promiseAll);},

然后去组件中给全选添加点击事件就行

    // 修改全部产品的选中状态async changeAllChecked() {try {let isChecked = event.target.checked ? "1" : "0";await this.$store.dispatch("changeAllChecked", isChecked);this.getData();} catch (error) {console.log("全选修改失败", error);}},

5、删除购物车数据

(1)删除单个商品

这个就比较简单了,不多说了,就是写接口——三连环——派发action

  1. 写接口
//删除购物车商品的接口
// /api/cart/deleteCart/{skuId}
export const reqDeleteGoodById = (skuId) => {return requests({url: `/cart/deleteCart/${skuId}`,method: 'delete',})
}

写接口要注意,一般带参的url要用反引号(一般处于电脑键盘Tab上面那个)而不是引号。

  1. 三连环
  // 删除购物车产品async deleteCartGood({ commit }, skuId) {let result = await reqDeleteCart(skuId);console.log("被删除的产品信息" + result);if (result.code == 200) {return "ok";} else {return Promise.reject(new Error("fail"));}},
  1. 添加点击事件并派发action重新请求并展示

先在对应位置添加点击事件:

请添加图片描述

    // 删除某个购物车产品async deleteCartById(cart) {try {// 如果删除成功,再次发请求获取数据进行展示await this.$store.dispatch("deleteCartGood", cart.skuId);this.getData();} catch (error) {console.log("删除失败", error);}},

(2)删除选中的所有商品

这个逻辑和点击全选修改每个商品的勾选状态有点像。

  1. vuex中利用遍历购物车数据,查出来哪个是选中的,然后依次发请求删除 删除成功与否的结果利用Promise.all传给组件
// 删除某个购物车产品
async deleteCartGood({ commit }, skuId) {let result = await reqDeleteCart(skuId);console.log("被删除的产品信息" + result);if (result.code == 200) {return "ok";} else {return Promise.reject(new Error("faile"));}
},
// 删除购物车所有被选中的产品
deleteAllCartGood(context) {// context身上有dispatch、commit、getters、state等数据// 获取购物车中全部的产品// console.log(context.getters.cartList.cartInfoList);let PromiseArr = [];context.getters.cartList.cartInfoList.forEach((el) => {let promise =el.isChecked==1 ? context.dispatch("deleteCartGood", el.skuId): "";// 将每一次返回的promise添加到数组中PromiseArr.push(promise);});// 只要有一个失败都返回失败的结果return Promise.all(PromiseArr);
},
  1. 点击删除选中商品按钮生效,方法中派发请求
// 删除被选中的产品
// 这个回调函数收集不到cart.skuId,因为它不在v-for内
async deleteAllCheckedCart() {try {// 派发一个actionawait this.$store.dispatch("deleteAllCartGood");this.getData();} catch (error) {alert(error.message);}
},

6、购物车商品数量加减改(难点)

(1)分析一下

请添加图片描述

这个修改商品数量主要有三个地方,加号、减号、用户输入,那么每改一次实际上都要重新发送请求。这个修改商品数量的接口和加入购物车的接口是一样的(带上要修改的产品id和数量)。

请添加图片描述

注意这里的skuNum参数类型,它并不是说直接把修改后的数量直接传到服务器,而是传与原来数量的差值。参数的规则就是传正数代表增加,传负数代表减少。

(2)配置请求的方法

首先找到修改数量的位置:

<li class="cart-list-con5"><ahref="javascript:void(0)"class="mins"@click="handler('mins', -1, cart)"  //减一>-</a><inputautocomplete="off"type="text"minnum="1"class="itxt":value="cart.skuNum"@change="handler('change', $event.target.value * 1, cart)"  //直接编辑修改/><ahref="javascript:void(0)"class="plus"@click="handler('plus', 1, cart)"  //加一>+</a>
</li>

仔细看注释:

  methods: {// 获取个人购物车getData() {this.$store.dispatch("getCartList");},// 修改某个产品数量//有个bug,如果连续点减号,那么可能会变成负数//这是因为连续快速点击,请求还来不及发送,数据没改,所以每次disnum都是-1//解决办法就是节流,给服务器一些缓冲的时间,防止数据不同步出现上述bughandler: throttle(async function (type, disNum, cart) {// type:为了区分三个元素// 目前disNum形参:+变化量(1) -变化量(-1)// cart:哪一个产品// console.log("派发action,通知服务器修改个数" + type);// 向服务器发请求,修改数量switch (type) {// 加case "plus":// 带给服务器变化的量disNum = 1;break;case "mins":// 判断产品是否大于1,大于1才能减if (cart.skuNum > 1) {disNum = -1;} else {disNum = 0;}// 上面这判断可以用三元表达式disNum=cart.skuNum>1?-1:0break;case "change":// 如果用户输入的是非数字字符,则保留原数量不变,也就是disNum=0if (isNaN(disNum) || disNum < 1) {disNum = 0;} else {// 正常情况(避免用户输入小数,转化为整数)disNum = parseInt(disNum) - cart.skuNum; //需要传的是它们的差值(接口规定)}break;}// 派发actiontry {await this.$store.dispatch("getShopCart", {skuId: cart.skuId,skuNum: disNum,});//如果成功,就再次请求数据刷新页面this.getData();} catch {alert("修改数量失败", err);}}, 800),},

至此,购物车模块的笔记也基本整理完了。下一篇笔记是登录注册模块。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/80478.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React-Hooks 和 React-Redux

注&#xff1a;Redux最新用法参考 个人React专栏 react 初级学习 Hooks基本介绍------------------------- Hooks&#xff1a;钩子、钓钩、钩住&#xff0c; Hook 就是一个特殊的函数&#xff0c;让你在函数组件中获取状态等 React 特性 &#xff0c;是 React v16.8 中的新增功…

机器学习 day34(机器学习项目的完整周期、精确度和召回率、F1)

机器学习项目的完整周期 第一步&#xff0c;决定项目是什么。第二步&#xff0c;收集数据。第三步&#xff0c;训练模型&#xff0c;进行错误分析并改进模型&#xff0c;可能会回到第二步。第四步&#xff0c;当模型足够好后&#xff0c;部署在生产环境中&#xff0c;继续监控…

【Redis7】--3.Redis持久化

Redis持久化 Redis持久化(Redis persistence)是指将数据写入持久化存储&#xff0c;如固态硬盘(SSD) Redis提供了一系列持久化选项&#xff0c;这些包括&#xff1a; RDB(redis数据库)&#xff1a;RDB持久化方式能够在指定的时间间隔对数据进行快照存储AOF(追加文件)&#x…

axios在vue3.x中的基础入门使用

-2023.05.18更新&#xff0c;修复了之前demo中存在的3个问题。现在可以无bug跑起来。 1.axios在vue3.x中的基础入门使用 在不涉及使用axios进行请求拦截以及响应拦截的场景下&#xff0c;axios的使用可以简化为以下步骤。 step1. 使用npm安装axios npm install axios step…

Linux安装包 | Git使用 | NFC搭建

dpgt使用 当谈到基于 Debian 的操作系统中的软件包管理工具时&#xff0c;dpkg 是一个重要的工具。它是 Debian 系统中用于安装、升级、配置和卸载软件包的命令行工具。以下是对 dpkg 的详细介绍&#xff1a; 软件包管理&#xff1a;dpkg 可以管理系统中的软件包。它可以安装单…

Aztec.nr:Aztec的隐私智能合约框架——用Noir扩展智能合约功能

1. 引言 前序博客有&#xff1a; Aztec的隐私抽象&#xff1a;在尊重EVM合约开发习惯的情况下实现智能合约隐私 Aztec.nr&#xff0c;为&#xff1a; 面向Aztec应用的&#xff0c;新的&#xff0c;强大的智能合约框架使得开发者可直观管理私有状态基于Noir构建&#xff0c;…

LeetCode2.两数相加

一看完题&#xff0c;我的想法是先算出这两个链表表示的数&#xff0c;然后相加&#xff0c;然后把这个数一位一位的分配给第三个数组&#xff0c;这种方法应该很简单但是要遍历三次数组&#xff0c;于是我就想直接一遍遍历&#xff0c;两个链表同时往后面遍历&#xff0c;把这…

基础篇之SDK编译

文章目录 一、 Ubuntu系统固件下载1. 固件下载2 放入SDK根目录中 二、编译SDK三、说明 一、 Ubuntu系统固件下载 1. 固件下载 在资源下载页面下载Ubuntu Rootfs固件&#xff0c;文件夹有三个文件&#xff0c;其区别如下&#xff0c;根据情况进行选择下载 资源名称作用Ubuntu2…

MySQL里的查看操作

文章目录 查看当前mysql有谁连接查看数据库或者表 查看当前mysql有谁连接 show processlist;查看数据库或者表 列出所有数据库&#xff1a; show databases;查看正在使用的数据库&#xff08;必须大写&#xff09;&#xff1a; SELECT DATABASE();列出数据库中的表&#xf…

免费开箱即用的微鳄任务管理系统

编者按&#xff1a;基于天翎低代码平台实现的微鳄365任务管理系统&#xff0c;包括有发起任务、重点关注、日程、项目管理等功能&#xff0c;支持私有化部署&#xff0c;免费开箱即用。任务管理系统是组织工作中不可或缺的工具&#xff0c;可以提高工作效率、促进协作、增强任务…

强大的JTAG边界扫描(5):FPGA边界扫描应用

文章目录 1. 获取芯片的BSDL文件2. 硬件连接3. 边界扫描测试4. 总结 上一篇文章&#xff0c;介绍了基于STM32F103的JTAG边界扫描应用&#xff0c;演示了TopJTAG Probe软件的应用&#xff0c;以及边界扫描的基本功能。本文介绍基于Xilinx FPGA的边界扫描应用&#xff0c;两者几乎…

华为云云耀云服务器 L 实例评测|配置教程 + 用 Python 简单绘图

文章目录 Part.I IntroductionChap.I 云耀云服务器 L 实例简介Chap.II 参与活动步骤 Part.II 配置Chap.I 初步配置Chap.II 配置安全组 Part.III 简单使用Chap.I VScode 远程连接华为云Chap.II 简单绘图 Reference Part.I Introduction 本篇博文是为了参与华为“【有奖征文】华…

Makefile基础

迷途小书童 读完需要 4分钟 速读仅需 2 分钟 1 引言 下面这个 C 语言的代码非常简单 #include <stdio.h>int main() {printf("Hello World!.\n");return 0; } 在 Linux 下面&#xff0c;我们使用下面的命令编译就可以 gcc hello.c -o hello 但是随着项目的变大…

【第200篇原创文章】解决低于1%概率出现的芯片VPSS模块跑飞的问题

在发布SDK内测的时候&#xff0c;我们发现在切换视频分辨率的时候有低概率出现VPSS模块跑飞的情况&#xff0c;概率低于1%&#xff0c;试个两三百次&#xff0c;能出1~2次。切换视频分辨率这个功能在安防产品上也确实存在需求&#xff0c;网络带宽不大好的地方分辨率可以适当下…

CKA真题分析-2023年度

补充信息 #补全 # apt install bash-completion source <(kubectl completion bash)# kubectl config get-contexts # cat ~/.kube/config |grep current# kubectl config current-context kubectl config use-context复制粘贴 ctrlshiftc ctrlshiftv # edit编辑时只能使…

Windows开机密码破解

Windows11以及Windows10(21H2)以上版本 先开机&#xff0c;不进行任何操作&#xff0c;静静的等待登录界面 按住Shift重启 进入“选择一个选项”界面&#xff0c;点击疑难解答 点击高级选项 点击命令提示符 输入两行命令 copy C:\windows\system32\uti1man.exe C: \Window…

竞赛 基于机器视觉的银行卡识别系统 - opencv python

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的银行卡识别算法设计 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng…

C【动态内存管理】

1. 为什么存在动态内存分配 int val 20;//在栈空间上开辟四个字节 char arr[10] {0};//在栈空间上开辟10个字节的连续空间 2. 动态内存函数的介绍 2.1 malloc&#xff1a;stdlib.h void* malloc (size_t size); int* p (int*)malloc(40); #include <stdlib.h> #incl…

Web服务(Web Service)

简介 Web服务&#xff08;Web Service&#xff09;是一种Web应用开发技术&#xff0c;用XML描述、发布、发现Web服务。它可以跨平台、进行分布式部署。 Web服务包含了一套标准&#xff0c;例如SOAP、WSDL、UDDI&#xff0c;定义了应用程序如何在Web上实现互操作。 Web服务的服…

类与对象的创建

package com.mypackage.oop.later;//学生类 //类里面只存在属性和方法 public class Student {//属性&#xff1a;字段//在类里面方法外面定义一个属性&#xff08;或者说是变量&#xff09;&#xff0c;然后在方法里面对他进行不同的实例化String name; //会有一个默认值&…