shoppe项目08----购物车

购物车数据结构

由于购物车数据量小,且数据变化比较频繁,所以采用Redis内存数据库来存储,采用的数据类型如下:

  1. 存储商品数据,采用hash结构,如cart_1:{3:5}。其中的数字部分分别代表用户id,加购的商品id,购买的该商品的数量。
  2. 存储商品的选中状态,采用set结构,如cart_selected_1: {3, 5,…}
    集合中的数字为勾选的商品id。
     
    Redis hash&set数据类型操作回顾:
$ redis-cli -h localhost -p 6379
>ping

#存储商品数据
>hset cart_1 3 5   #添加一个商品
>hmset cart_1 5 2 6 3  #添加多个商品
>hkeys cart_1  #查看所有的商品
>hvals cart_1
>hgetall cart_1 #获取所有的商品及数量
>hdel cart_1 3  #删除指定商品

#存储商品的选中状态
>sadd cart_selected_1 3
>sadd cart_selected_1 5 7 9
>srem cart_selected_1 5  #取消选中
>smembers cart_selected_1 #获取所有选中的商品

python的操作方法与以上类似

import redis
r = redis.Redis(host='localhost', port=6379, db=0)
#存储商品数据
r.hset('cart_1', key=2, value=3)
r.hget("cart_1", 2) #注意是字节数据
r.hdel("cart_1", 2) #删除
#存储集合数据
r.sadd("cart_selected_1", 2)
r.sadd("cart_selected_1", 2,3)
r.smembers("cart_selected_1")
r.srem("cart_selected_1", 2)

#关闭连接
r.close()

添加购物车

前端需求分析参考已上线的项目

  1. 未登录时点击添加购物车
  2. 已登录时,点击添加购物车
      2.1 该商品第一次加入购物车,在购物车中添加一个商品,并提示‘添加购物车成功’
      2.2 该商品已经在购物车,再次加购,提示‘该商品已在购物车,数量+1’
      2.3 达到加购上限,提示‘加购达到限购数量!’ , 同时’加入购物车‘按钮不可点击
      2.4 完成加购后,右上角显示当前购物车中的商品总数。

在这里插入图片描述
加购前端接口分析
在Details组件中的模板找到如下代码:

<div class="button">
<el-button class="shop-cart" :disabled="dis" @click="addShoppingCart">加入购物车</el-button>
<el-button class="like" @click="addCollect">喜欢</el-button>
</div>

点击’加入购物车‘会触发click事件,对应的函数如下:

addShoppingCart() { //
      // 判断是否登录,没有登录则显示登录组件
      if (!this.$store.getters.getUser) {
        this.$store.dispatch("setShowLogin", true);
        return;
      }

      // 已经登录,则请求后端将当前商品,加入自己的购物车
      console.log("@@getUser,已登录用户:",this.$store.getters.getUser)
      // {userName:'laufing',xxx...}
      this.$axios
        .post("/carts/user/addCarts/", {
          user: this.$store.getters.getUser.userName,
          product_id: this.productID
        })
        .then(res => {
          console.log("@@添加购物车res:", res)
          // 
          // 处理购物车的响应
          switch (res.data.code) {
            case 200:
              // 后端新加入购物车成功
              this.unshiftShoppingCart(res.data.shoppingCartData);
              this.notifySucceed(res.data.msg);
              break;
            case 201:
              // 该商品已经在购物车,数量+1
              this.addShoppingCartNum(this.productID);
              this.notifySucceed(res.data.msg);
              break;
            case 202:
              // 商品数量达到限购数量(即库存量)
              this.dis = true; //购物车按钮不可点击
              this.notifyError(res.data.msg);
              break;
            default:
              this.notifyError(res.data.msg);
          }
        })
        .catch(err => {
          return Promise.reject(err);
        });
    },

总结:
请求地址, /carts/user/addCarts/
请求方法,POST
提交的数据, {
    user: this.$store.getters.getUser.userName,
    productID: this.productID
}
需要的响应,
{
    “code”:200,
    “msg”:“新加入购物车成功”,
    “shoppingCartData”:{
        id: “”, // 购物车id, 如cart_1
        productID: “”, // 商品id
        productName: “”, // 商品名称
        productImg: “”, // 商品图片
        price: “”, // 商品价格
        num: “”, // 购物车中该商品数量
        maxNum: “”, // 商品限购数量,即库存
        check: false // 是否勾选
         }
}
{“code”:201, “msg”:“该商品已经在购物车,数量+1”}
{”code“:202, “msg”:“加购达到限购数量!”}
{’code‘:203, ‘msg’:‘库存不足,无法购买!’}
{“code”:204, “msg”:’其他异常‘}

加购后端思路
1. 获取前端数据{“user”:‘laufing’, “productID”:3}
2. 根据用户名查询用户对象并构造key,根据商品id查询商品对象,以便获取库存。
3. hash====> cart_uid : {good_id:count, …}
set====> cart_selected_uid: {good_id, …}
存入数据:(加购数量count=1)
a. 如果库存量stock>0 ,则允许加购:
    redis如果购物车历史中有该商品,则先获取历史总数num, 然后 num += count,最后比较num总数与库存量。若num<=stock,更新hash&set,返回响应{“code”:201, “msg”:“该商品已经在购物车,数量+1”};若num > stock,直接返回响应{”code“:202, “msg”:“加购达到限购数量!”}
    如果购物车历史没有,则新加入count个该商品&同时设置勾选状态,然后返回{
    “code”:200,
    “msg”:“新加入购物车成功”,
    “shoppingCartData”:{
        id: “”, // 购物车id
        productID: “”, // 商品id
        productName: “”, // 商品名称
        productImg: “”, // 商品图片
        price: “”, // 商品价格
        num: “”, // 加购商品数量
        maxNum: “”, // 商品限购数量
        check: false // 是否勾选
    }
}
b. 如果stock=0,则不允许加购,并返回 {’code‘:203, ‘msg’:‘库存不足,无法购买!’}
c. 其他异常,返回204响应
{“code”:204, “msg”:’用户不存在、商品不存在等其他的异常问题‘}

后端加购视图代码

# 添加购物车
path("user/addCarts/", AddCart.as_view()),

#视图
#添加购物车
class AddCart(APIView):
    """
        数据存入redis购物车
        1. hash类型,表示购物车商品数据  cart_uid:{good_id:count}
        2. set类型, 表示商品的选中状态(选中) cart_selected_uid: {good_id}
        3. 存入redis之前判断该商品的库存量、它是否在历史购物车中、最终的加购总数num是否<=库存量
        4. 新加入购物车时,响应{"code":200,'msg':xxxx,
                "shoppingCartData":{
                    "id":cart_uid,
                    "productID":xxx,
                    "productName":xxxx,
                    "productImg":xxxx,
                    "price":selling_price,
                    "num":count, 当前购物车中的数量
                    "maxNum":stock, 限购数量
                    "check": False, 是否勾选
                    }
                }
    """
    def post(self, request):
        try:
            # 获取前端数据{"user": xxxx, "productID": xxxx}
            #print("data:", request.data)   -->QueryDict
            username = request.data.get("user")
            good_id = request.data.get("productID")
            count = 1 #非幂等方式,前端点击一次,发送一次,每次+1

            #查询用户
            try:
                user = User.objects.get(username=username)
                #构造redis的key
                cart_key = "cart_%s"%user.id
                cart_selected_key = "cart_selected_%s"%user.id
            except:
                return Response({"code":204, "msg":"用户不存在!"})
            #查询商品对象
            try:
                good = Goods.objects.get(id=good_id)
            except:
                return Response({"code":204, "msg":"商品不存在!"})

            #添加商品到该用户的购物车
            redis_conn = redis.Redis(host='localhost', port=6379, db=0)
            #判断该商品的库存
            #print("xxxxx:", good.stock)
            if good.stock > 0:
                #有库存,允许加购
                #购物车历史数据中是否有该商品,有则累加, 无则新加
                num = redis_conn.hget(cart_key, good_id)
                #有则累加
                if num:
                    num = int(num.decode())
                    num += count
                    # 累加后,判断num是否<=库存
                    if num <= good.stock:
                        #允许加购
                        redis_conn.hset(cart_key, good_id, num)
                        redis_conn.sadd(cart_selected_key, good_id)
                        # 关闭redis连接
                        redis_conn.close()
                        return Response({"code":201, "msg":"该商品已经在购物车,数量+1"})
                    else:
                        #累加后num超出库存,则不允许加购
                        # 关闭redis连接
                        redis_conn.close()
                        return Response({"code":202, "msg":"加购达到限购数量!"})
                #历史购物车没有该商品,则新加入一个
                else:
                    redis_conn.hset(cart_key, good_id, count)
                    redis_conn.sadd(cart_selected_key, good_id)
                    shopping_cart_data = {
                        "id": cart_key,
                        "productID": good_id,
                        "productName": good.sku_name,
                        "productImg": good.img,
                        "price": good.selling_price,
                        "num": count,  # 该商品在购物车中的数量
                        "maxNum": good.stock,  # 限购数量
                        "check": True,  # 加购,默认是勾选状态
                    }
                    # 关闭redis连接
                    redis_conn.close()
                    return Response({"code": 200, "msg": "新加入购物车成功!", "shoppingCartData": shopping_cart_data})

            else:
                #该商品没有库存,不允许加购
                # 关闭redis连接
                redis_conn.close()
                return Response({"code":203, "msg":"库存不足,无法购买!"})
        except:
            return Response({"code":204, "msg":"服务器内部错误"})

以上在浏览器控制台可以看到对应的响应。

查看购物车

  1. 购物车商品总数前端分析
    用户登录后,在首页右上角的购物车处,显示加购商品总数。查看源代码可以发现,它是一个计算属性,如下:
    在这里插入图片描述
    getNum,从集中式状态管理中映射过来的 ,即store>getters>getNum。
    该函数统计state.shoppingCart数组中的商品数量。
getNum (state) {
      // 购物车商品总数量
      let totalNum = 0;
      for (let i = 0; i < state.shoppingCart.length; i++) {
        const temp = state.shoppingCart[i];
        totalNum += temp.num;
      }
      return totalNum;
    },
  1. 加载购物车商品数据前端分析
    那么state中的shoppingCart数据什么时候加载的?
    监视属性getUser,用户登录后state.user属性值即由” “变为"laufing", 触发对应的函数
// 管理vuex中的shoppingCart数据
getUser: function (val) {//val为登录的用户,如{"userName":'laufing'}
  if (val === "") {
    // 用户退出登录
    this.setShoppingCart([]);//置空购物车,即state.shoppingCart=[]
  } else {
    // 用户完成登录,获取该用户的购物车数据
    this.$axios
      .post("/carts/user/getShoppingCart/", {
        user: val, //当前 用户对象 {userName:'laufing'}
      })
      .then((res) => {
        console.log("@@当前用户的购物车数据:",res)

        if (res.data.code == 200) {
          // 200 为成功, 更新vuex购物车状态
          this.setShoppingCart(res.data.shoppingCartData);
        } else {
          // 提示失败信息
          this.notifyError(res.data.msg);
        }
      })
      .catch((err) => {
        return Promise.reject(err);
      });
  }
},

总结:
用户每次登陆时,state.user的值会发生变化,触发监视属性getUser对应的函数,从而向后端发送请求,加载购物车商品数据。
请求地址,/carts/user/getShoppingCart/
请求方法,POST
提交数据,{
    user: val, //当前 用户对象 {userName:‘laufing’}
}
需要的响应,{
    code:200,
    msg:‘加载购物车商品数据成功!’
    shoppingCartData:[{ },…]
}
其中的每个商品对象格式,
{
    id: “”, // 购物车id
    productID: “”, // 商品id
    productName: “”, // 商品名称
    productImg: “”, // 商品图片
    price: “”, // 商品价格
    num: “”, // 加购的商品数量
    maxNum: “”, // 商品限购数量
    check: false // 是否勾选
}

  1. 加载购物车商品数据后端视图
# 路由
path('user/getShoppingCart/', GetCart.as_view()),
#获取当前用户的购物车数据
#获取当前用户的购物车数据
class GetCart(APIView):
    """
        1. 根据用户名查询用户对象
        2. 根据用户id构造key,查询redis中的购物车数据(cart_uid、cart_selected_uid)
        3. 根据购物车里的商品id,查询商品对象,组织每一个商品字典
            {
                "id": "cart_uid",
                "productID":xxx,
                "productName":xxx,
                "productImg":xxx,
                "price":商品的selling_price,
                "num": xxx, 加购数量
                "maxNum": xxx, 限购数量
                "check": True or False, 是否勾选
            }
        4.多个商品字典 组织为一个列表[{},{},...], 然后返回响应
    """
    def post(self, request):
        try:
            #获取前端数据
            username = request.data.get("user").get("userName")

            #查询当前用户
            try:
                user = User.objects.get(username=username)
                cart_key = "cart_%s"%user.id
                cart_selected_key = "cart_selected_%s"%user.id
            except:
                return Response({"code":204, "msg":"当前用户不存在!"})

            #查询当前用户的购物车
            redis_conn = redis.Redis(host="localhost",  port=6379, db=0)
            shopping_cart_data = []
            #购物车中勾选的商品
            cart_selected = [int(i.decode()) for i in redis_conn.smembers(cart_selected_key)]
            #遍历每个商品
            for good_id, count in redis_conn.hgetall(cart_key).items():
                good_id = int(good_id.decode())
                count = int(count.decode())

                #整理一个商品的字典数据
                if good_id in cart_selected: #商品被勾选
                    one_good = get_one_good(cart_key, good_id, count, check=True)
                else:
                    one_good = get_one_good(cart_key, good_id, count, check=False)

                #将当前商品字典 追加到列表中
                shopping_cart_data.append(one_good)

            return Response({"code":200, 'msg':"获取用户的购物车数据成功!",
                             "shoppingCartData":shopping_cart_data})
        except:
            return Response({"code":204, "msg":"服务端获取购物车数据异常"})

#把购物车里的每一个商品,组织成一个字典
def get_one_good(cart_key, good_id, count, check=False):
    """
    :param cart_key: 购物车id
    :param good_id: redis购物车中一个商品的id,
    :param count: 该商品加购的数量
    :param check: 该商品是否被勾选
    :return: 一个商品的字典数据
    """
    #根据good_id查询商品数据
    try:
        good = Goods.objects.get(id=good_id)
    except:
        return {}
    return {
            "id":cart_key,
            "productID":good_id,
            "productName":good.sku_name,
            "productImg":good.img,
            "price":good.selling_price,
            "num":count, #该商品 在当前购物车中的数量
            "maxNum":good.stock, #限购数量
            "check": check, #购物车中该商品是否被勾选
            }
  1. 前端展示购物车商品
    未登录时,点击购物车(router-link),弹出登录框。
    已登录时,点击购物车,则跳转到购物车页面。
    前端代码分析,在APP组件中找到’购物车‘
<router-link to="/shoppingCart">
	<i class="el-icon-shopping-cart-full"></i> 购物车
	<span class="num">({{ getNum }})</span>
</router-link>

路由/shoppingCart
组件ShoppingCart
在路由与组件的对应关系中,有requireAuth:true,所以每次未登录时点击购物车都会弹出登录框。
用户完成登录,vuex中的user从空值改为具体的用户对象,触发监视属性getUser对应的函数,去加载用户的购物车数据。
然后跳转到购物车页面,展示加载的购物车商品。

具体参考ShoppingCart组件
页面效果:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

laufing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值