Vue2电商平台(五)、加入购物车,购物车页面

news2024/12/29 22:29:34

文章目录

  • 一、加入购物车
    • 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')就是在调用这个函数addOrUpdateShopCart
  try {
    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) {
    // 生成用户id
    uuid_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 = 0
     this.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 或false
     let isChecked = e.target.checked ? 1 : 0
     await 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 : 0
     await 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 input
  disNum: 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) 为什么要计算差值?因为这个接口还用来实现将商品添加到购物车。
  我用淘宝试了一下,这个业务逻辑是当在页面详情页,多次重复的将商品添加到购物车时。购物车里该商品的数量应该是这几次重复添加的数量的和。而不是最后一次添加时选择的商品数量。
  所以如果只将输入框里的最终数量传过去的话,商品数量只会是最后一次将该商品添加到购物车里时的选择的数量。

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

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

相关文章

Nature 正刊!树木多样性促进天然林土壤碳氮的固存

本文首发于“生态学者”微信公众号&#xff01; 2023年4月26日&#xff0c;《Nature》杂志在线发表了浙江农林大学陈信力教授、Scott X. Chang教授及湖首大学Han Y. H. Chen教授等合作的最新研究成果 “Tree diversity increases decadal forest soil carbon and nitrogen acc…

深入掌握 Golang 单元测试与性能测试:从零开始打造高质量代码!

在软件开发中&#xff0c;测试是保证代码质量、减少错误的重要环节。Golang 自带了强大的测试工具&#xff0c;不仅支持单元测试&#xff0c;还支持性能测试&#xff0c;让开发者可以轻松进行代码的测试和优化。本文将详细介绍如何在 Go 中进行单元测试和性能测试&#xff0c;帮…

Codeforces Round 969 (Div. 1) B. Iris and the Tree

题目 题解&#xff1a; #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 #define ll long long #define pii pair<int, int&…

(JAVA)开始熟悉 “二叉树” 的数据结构

1. 二叉树入门 ​ 符号表的增删查改操作&#xff0c;随着元素个数N的增多&#xff0c;其耗时也是线性增多的。时间复杂度都是O(n)&#xff0c;为了提高运算效率&#xff0c;下面将学习 树 这种数据结构 1.1 树的基本定义 ​ 树是我们计算机中非常重要的一种数据结构&#xf…

C语言刷题--数数一个数的二进制里有几个‘1’

先来看一下左移、右移 左移 右移 题目解答 1是一个特殊的数&#xff0c;二进制是000000000000000000000001(32位机器) 假如要判断的数是0&#xff08;二进制里面没有1&#xff09; 000000000000000000000000 & 000000000000000000000001 结果为0&#xff1b; 假如要…

基于FPGA的多路视频缓存

对于多路视频传输的场合&#xff0c;需要正确设置同步。 uifdma_dbuf0 的写通道输出帧同步计数器直接接入 uifdma_dbuf0&#xff0c;uifdma_dbuf1, uifdma_dbuf2, uifdma_dbuf3 的写通道同步计数输入。uifdma_dbuf0 的读通道&#xff0c;延迟 1 帧于 uifdma_dbuf0 的写通道帧计…

初入网络学习第一篇

引言 不磨磨唧唧&#xff0c;跟着学就好了&#xff0c;这个是我个人整理的学习内容梳理&#xff0c;学完百分百有收获。 1、使用的网络平台:eNSP 下载方法以及内容参考这篇文章 华为 eNSP 模拟器安装教程&#xff08;内含下载地址&#xff09;_ensp下载-CSDN博客https://b…

javaScript操作元素(9个案例+代码+效果)

目录 1.innerHTML 案例:使用innerHTML修改文本内容 1.代码 2.效果 2.innerText 案例:使用innerText修改文本 1.代码 2.效果 3.textContent 案例:使用textContent修改文本 1.代码 2.效果 4.通过style属性操作样式 案例:改变小球颜色 1.代码 2.效果 5.通过className属性操作样式 …

【Iceberg分析】Spark集成Iceberg采集输出

Spark集成Iceberg采集输出 文章目录 Spark集成Iceberg采集输出Iceberg提供了两类指标和提供了两类指标输出器ScanReportCommitReport LoggingMetricsReporterRESTMetricsReporter验证示例相关环境配置结果说明 Iceberg提供了两类指标和提供了两类指标输出器 ScanReport 包含在…

基于SpringBoot+Uniapp的家庭记账本微信小程序系统设计与实现

项目运行截图 展示效果图 展示效果图 展示效果图 展示效果图 展示效果图 5. 技术框架 5.1 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更…

Three.js基础内容(二)

目录 一、模型 1.1、组对象Group和层级模型(树结构) 1.2、递归遍历模型树结构、查询具体模型节点(楼房案例) 1.3、本地(局部)坐标和世界坐标 1.4、改变模型相对局部坐标原点位置 1.5、移除对象.remove() 1.6、模型隐藏与显示 二、纹理 2.1、创建纹理贴图(TextureLoade…

005集—— 用户交互之CAD窗口选择图元实体(CAD—C#二次开发入门)

如下图&#xff1a;根据提示选择若干图形要素&#xff0c;空格或右键结束选择&#xff0c;返回图元的objectid&#xff0c;以便进一步操作图元实体。 代码如下&#xff1a; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.Aut…

嘉兴儿童自闭症寄宿学校独特教育模式探秘

自闭症&#xff08;孤独症&#xff09;儿童的教育问题一直是社会关注的焦点。如何为这些特殊的孩子提供一个安全、稳定且充满爱的成长环境&#xff0c;成为了许多家庭的期盼。在众多自闭症儿童教育机构中&#xff0c;广州的星贝育园自闭症儿童寄宿制学校以其独特的教育模式和全…

Keycloak 获取token 用户信息

进入Clients菜单&#xff0c;选择Client ID找到Access settings 》Direct access grants 将Direct access grants勾选Postmans URL输入地址&#xff1a;{IP}:{prot}/realms/{ realms }/protocol/openid-connect/token 例&#xff1a;http://10.18.11.3:31873/realms/master/pro…

Centos7 NTP客户端

目录 1. NTP客户端1.1 安装1.2 启动1.3 同步状态异常1.4 更改/etc/chrony.conf配置文件1.5 同步状态正常 1. NTP客户端 1.1 安装 如果chrony没有安装&#xff0c;可以使用以下命令安装 sudo yum install chrony1.2 启动 启动并设置开机自启 sudo systemctl start chronyd …

【Matlab学习日记】② 常用滤波以及噪声分析方法(上)

关注星标公众号&#xff0c;不错过精彩内容 作者 | 量子君 微信公众号 | 极客工作室 【Matlab学习日记】专栏目录 第一章 ① Sinmulink自动代码生成教程 第二章 ② 常用滤波以及噪声分析方法&#xff08;上&#xff09; 文章目录 前言一、使用滤波的目的二、常见的几种噪声和表…

棋牌灯控计时计费系统软件免费试用版怎么下载 佳易王计时收银管理系统操作教程

一、前言 【试用版软件下载&#xff0c;可以点击本文章最下方官网卡片】 棋牌灯控计时计费系统软件免费试用版怎么下载 佳易王计时收银管理系统操作教程 棋牌计时计费软件的应用也提升了顾客的服务体验&#xff0c;顾客可以清晰的看到自己的消费时间和费用。增加了消费的透明…

免费高可用软件

高可用软件是指那些能够提供高可用性、高可靠性的软件&#xff0c;它们在各种应用场景下都能确保系统的稳定运行。以下是四款免费的高可用软件&#xff0c;它们在不同领域都表现出色&#xff0c;能够满足各种高可用性需求。 一、PanguHA PanguHA是一款专为Windows平台设计的双…

数据分析之Spark框架介绍

文章目录 概述一、发展历程与背景二、核心特点三、生态系统与组件四、应用场景五、与其他大数据技术的比较 核心概念1. 弹性分布式数据集&#xff08;RDD, Resilient Distributed Dataset&#xff09;2. 转换&#xff08;Transformations&#xff09;和动作&#xff08;Actions…

python jpg 简单研究 1

起因&#xff0c; 目的: 就是想看看 jpg 里面有什么。 其实&#xff0c;我最开始的想法是&#xff0c;自己来写一个文件格式&#xff0c;只能我自己才能打开。 然后看了 jpg 相关的内容&#xff0c;发现太复杂&#xff0c;只能罢了。 1. jpg 的魔法头数字&#xff08;File Ma…