文章目录
 
 
 
一、添加购物车
 
1.后端逻辑代码
 
"""
一 前后端需求分析需求
    前端需要收集: 商品id,商品数量, 选中是可选的(默认就是选中)
            如果用户登陆了则请求携带session id
            如果用户未登陆了则不请求携带session id
    后端的需求: 新增数据
二 大体流程
    接收数据
    验证数据
    数据保存
    返回相应
三 把详细思路完善一下(纯后端)
    1.接收数据   sku_id,count,
    2.验证数据
    3根据用户是否登陆来保存
    4登陆用户redis
        4.1 连接redis
        4.2 hash
        4.3 set
        4.4 返回相应
    5未登录用户cookie
        5.1  组织数据
        5.2  对数据进行base64处理
        5.3  设置cookie
        5.4 返回相应
四 确定我们请求方式和路由
    POST    carts/
"""
def post(self,request):
    
    data = json.loads(request.body.decode())
    sku_id=data.get('sku_id')
    count=data.get('count')
    
    
    if not all([sku_id,count]):
        return http.JsonResponse({'code':RETCODE.PARAMERR,'errmsg':'参数不齐'})
    
    try:
        sku = SKU.objects.get(pk=sku_id)
    except SKU.DoesNotExist:
        return http.JsonResponse({'code':RETCODE.NODATAERR,'errmsg':"暂无次数据"})
    
    try:
        count = int(count)
    except Exception as e:
        return http.JsonResponse({'code':RETCODE.PARAMERR,'errmsg':'参数错误'})
    
    if request.user.is_authenticated:
        
        
        redis_conn = get_redis_connection('carts')
        
        
        
        pl = redis_conn.pipeline()
        pl.hincrby('carts_%s'%request.user.id,sku_id,count)
        
        pl.sadd('selected_%s'%request.user.id,sku_id)
        
        pl.execute()
        
        return http.JsonResponse({'code':RETCODE.OK,'errmsg':'ok'})
    else:
        
        
        cookie_str = request.COOKIES.get('carts')
        
        if cookie_str is None:
            
            carts = {}
        else:
            
            
            de = base64.b64decode(cookie_str)
            
            carts = pickle.loads(de)
        
        if sku_id in carts:
            
            origin_count = carts[sku_id]['count']
            count += origin_count
            
        carts[sku_id]={
            'count':count,
            'selected':True
        }
        
        
        d = pickle.dumps(carts)
        
        
        en = base64.b64encode(d)
        
        response = http.JsonResponse({'code':RETCODE.OK,'errmsg':'ok'})
        
        
        response.set_cookie('carts',en.decode())
        
        return response
 
2.前台请求接口代码
 
// 加入购物车
add_cart(){
    var url = this.host + '/carts/';
    axios.post(url, {
        sku_id: parseInt(this.sku_id),
        count: this.sku_count
    }, {
        headers: {
            'X-CSRFToken': getCookie('csrftoken')
        },
        responseType: 'json',
        withCredentials: true
    })
        .then(response => {
            if (response.data.code == '0') {
                alert('添加购物车成功');
                this.cart_total_count += this.sku_count;
            } else { // 参数错误
                alert(response.data.errmsg);
            }
        })
        .catch(error => {
            console.log(error.response);
        })
},
 
3.实际效果
 

 
二、获取购物车
 
1.后端逻辑代码
 
"""
一 把需求写下来 (前端需要收集什么 后端需要做什么)
二 把大体思路写下来(后端的大体思路)
    查询数据
    展示
三 把详细思路完善一下(纯后端)
    1.获取用户信息,根据用户信息进行判断
    2.登陆用户redis查询
        2.1 连接redis
        2.2 hash   {sku_id:count}
        2.3 set     [sku_id]
        2.4 根据id查询商品详细信息
        2.5 展示
    3.未登录用户cookie查询
        3.1 读取cookie
        3.2 判断carts数据,如果有则解码数据,如果没有则初始化一个字典
            {sku_id: {count:xxx,selected:xxxx}}
        3.3 根据id查询商品信息信息
        3.4 展示
    1.获取用户信息,根据用户信息进行判断
    2.登陆用户redis查询
        2.1 连接redis
        2.2 hash   {sku_id:count}
        2.3 set     [sku_id]
    3.未登录用户cookie查询
        3.1 读取cookie
        3.2 判断carts数据,如果有则解码数据,如果没有则初始化一个字典
            {sku_id: {count:xxx,selected:xxxx}}
    4.根据id查询商品信息信息
    5.展示
四 确定我们请求方式和路由
    GET     carts/
"""
def get(self,request):
    
    user = request.user
    if user.is_authenticated:
        
        
        redis_conn = get_redis_connection('carts')
        
        sku_id_count=redis_conn.hgetall('carts_%s'%user.id)
        
        selected_ids=redis_conn.smembers('selected_%s'%user.id)
        
        
        
        carts = {}
        
        for sku_id,count in sku_id_count.items():
            
            if sku_id in selected_ids:
                selected=True
            else:
                selected=False
            
            carts[int(sku_id)]={
                'count':int(count),
                'selected':selected
            }
    else:
        
        
        cookie_str = request.COOKIES.get('carts')
        
        if cookie_str is None:
            carts = {}
        else:
            
            de = base64.b64decode(cookie_str)
            carts = pickle.loads(de)
    
    
    ids = carts.keys()
    
    
    
    skus = SKU.objects.filter(id__in=ids)
    sku_list=[]
    
    total_count=0
    
    total_amount=0
    
    for sku in skus:
        sku_list.append({
            'id':sku.id,
            'name':sku.name,
            'count': carts.get(sku.id).get('count'),
            'selected': str(carts.get(sku.id).get('selected')),  
            'default_image_url':sku.default_image.url,
            'price':str(sku.price), 
            'amount':str(sku.price * carts.get(sku.id).get('count')),
        })
        total_count += carts[sku.id]['count']
        total_amount += (sku.price * carts[sku.id]['count'])
    
    context = {
        'cart_skus': sku_list
    }
    return render(request,'cart.html',context=context)
 
2.前台页面代码
 
<ul class="cart_list_td clearfix" v-for="(cart_sku,index) in carts" v-cloak=v-cloak>
    <li class="col01"><input type="checkbox" name="" v-model="cart_sku.selected" @change="update_selected(index)" /></li>
    <li class="col02"><img src="{{ static('images/goods/goods003.jpg') }}" /></li>
    <li class="col03">[[ cart_sku.name ]]</li>
    <li class="col05">[[ cart_sku.price ]]元</li>
    <li class="col06">
        <div class="num_add">
            <a @click="on_minus(index)" class="minus fl">-</a>
            <input v-model="cart_sku.count" @blur="on_input(index)" type="text" class="num_show fl" />
            <a @click="on_add(index)" class="add fl">+</a>
        </div>
    </li>
    <li class="col07">[[ cart_sku.amount ]]元</li>
    <li class="col08"><a @click="on_delete(index)">删除</a></li>
</ul>
 
// 初始化购物车数据并渲染界面
render_carts(){
    // 渲染界面
    this.carts = JSON.parse(JSON.stringify(cart_skus));
    for (var i = 0; i < this.carts.length; i++) {
        if (this.carts[i].selected == 'True') {
            this.carts[i].selected = true;
        } else {
            this.carts[i].selected = false;
        }
    }
    // 手动记录购物车的初始值,用于更新购物车失败时还原商品数量
    this.carts_tmp = JSON.parse(JSON.stringify(cart_skus));
},
 
3.实际效果
 

 
三、更新购物车
 
1.后端逻辑代码
 
"""
    一 把需求写下来 (前端需要收集什么 后端需要做什么)
        前端: 要收集sku_id,count,selected 传递给后端
        后端: 更新数据
    二 把大体思路写下来(后端的大体思路)
        指定更新哪里的数据
        接收数据
        验证数据
        更新数据
        返回相应
    三 把详细思路完善一下(纯后端)
        1.接收数据
        2.验证数据
        3.获取用户的信息
        4.登陆用户更新redis数据
            4.1 连接redis
            4.2 hash
            4.3 set
            4.4 返回相应
        5.未登录更新cookie数据
            5.1 获取cart数据,并判断
            5.2 更新指定数据
            5.3 对字典数据进行处理,并设置cookie
            5.4 返回相应
    四 确定我们请求方式和路由
    """
    def put(self,request):
        
        data = json.loads(request.body.decode())
        
        sku_id = data.get('sku_id')
        count = data.get('count')
        selected=data.get('selected')
        
        
        if not all([sku_id, count]):
            return http.JsonResponse({'code': RETCODE.PARAMERR, 'errmsg': '参数不齐'})
        
        try:
            sku = SKU.objects.get(pk=sku_id)
        except SKU.DoesNotExist:
            return http.JsonResponse({'code': RETCODE.NODATAERR, 'errmsg': "暂无次数据"})
        
        try:
            count = int(count)
        except Exception as e:
            return http.JsonResponse({'code': RETCODE.PARAMERR, 'errmsg': '参数错误'})
        
        if request.user.is_authenticated:
            
            
            redis_conn = get_redis_connection('carts')
            
            redis_conn.hset('carts_%s'%request.user.id,sku_id,count)
            
            if selected:
                redis_conn.sadd('selected_%s'%request.user.id,sku_id)
            else:
                redis_conn.srem('selected_%s'%request.user.id,sku_id)
            
            cart_sku = {
                'id': sku_id,
                'count': count,
                'selected': selected,
                'name': sku.name,
                'default_image_url': sku.default_image.url,
                'price': sku.price,
                'amount': sku.price * count,
            }
            return http.JsonResponse({'code':RETCODE.OK,'errmsg':'ok','cart_sku':cart_sku})
        else:
            
            
            cookie_str = request.COOKIES.get('carts')
            if cookie_str is None:
                carts = {}
            else:
                carts = pickle.loads(base64.b64decode(cookie_str))
            
            
            if sku_id in carts:
                carts[sku_id]={
                    'count':count,
                    'selected':selected
                }
            
            en = base64.b64encode(pickle.dumps(carts))
            
            cart_sku = {
                'id': sku_id,
                'count': count,
                'selected': selected,
                'name': sku.name,
                'default_image_url': sku.default_image.url,
                'price': sku.price,
                'amount': sku.price * count,
            }
            respnse =  http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'ok', 'cart_sku': cart_sku})
            respnse.set_cookie('carts',en)
            return respnse
 
2.前台页面代码
 
 // 减少操作
on_minus(index){
    if (this.carts[index].count > 1) {
        var count = this.carts[index].count - 1;
        // this.carts[index].count = count; // 本地测试
        this.update_count(index, count); // 请求服务器
    }
},
// 增加操作
on_add(index){
    var count = 1;
    if (this.carts[index].count < 5) {
        count = this.carts[index].count + 1;
    } else {
        count = 5;
        alert('超过商品数量上限');
    }
    // this.carts[index].count = count; // 本地测试
    this.update_count(index, count); // 请求服务器
},
// 更新购物车
update_count(index, count){
    var url = this.host + '/carts/';
    axios.put(url, {
        sku_id: this.carts[index].id,
        count: count,
        selected: this.carts[index].selected
    }, {
        headers: {
            'X-CSRFToken': getCookie('csrftoken')
        },
        responseType: 'json',
        withCredentials: true
    })
        .then(response => {
            if (response.data.code == '0') {
                // this.carts[index].count = response.data.cart_sku.count; // 无法触发页面更新
                Vue.set(this.carts, index, response.data.cart_sku); // 触发页面更新
                // 重新计算界面的价格和数量
                this.compute_total_selected_amount_count();
                this.compute_total_count();
                // 更新成功将新的购物车再次临时保存
                this.carts_tmp = this.carts;
            } else {
                alert(response.data.errmsg);
                this.carts[index].count = this.carts_tmp[index].count;
            }
        })
        .catch(error => {
            console.log(error.response);
            this.carts[index].count = this.carts_tmp[index].count;
        })
},
// 更新购物车选中数据
update_selected(index) {
    var url = this.host + '/carts/';
    axios.put(url, {
        sku_id: this.carts[index].id,
        count: this.carts[index].count,
        selected: this.carts[index].selected
    }, {
        headers: {
            'X-CSRFToken': getCookie('csrftoken')
        },
        responseType: 'json',
        withCredentials: true
    })
        .then(response => {
            if (response.data.code == '0') {
                this.carts[index].selected = response.data.cart_sku.selected;
                // 重新计算界面的价格和数量
                this.compute_total_selected_amount_count();
                this.compute_total_count();
            } else {
                alert(response.data.errmsg);
            }
        })
        .catch(error => {
            console.log(error.response);
        })
},
 
3.实际效果
 

 
四、删除购物车
 
1.后端逻辑代码
 
"""
一 把需求写下来 (前端需要收集什么 后端需要做什么)
    前端需要把商品id传递给后端
    后端把指定的商品删除就可以
二 把大体思路写下来(后端的大体思路)
    根据id进行删除
    登陆用户删除redis
    未登陆用户删除cookie
三 把详细思路完善一下(纯后端)
     1.接收数据 sku_id
     2.根据用户信息进行判断
     3.登陆用户删除redis
        3.1 连接redis
        3.2 hash
        3.3 set
        3.4 返回相应
     4.未登陆用户删除cookie
        4.1 读取cookie中的数据,并且判断
        4.2 删除数据
        4.3 字典数据处理,并设置cookie
        4.4 返回相应
四 确定我们请求方式和路由
"""
def delete(self,request):
    
    data = json.loads(request.body.decode())
    sku_id = data.get('sku_id')
    
    if request.user.is_authenticated:
        
        
        redis_conn = get_redis_connection('carts')
        
        redis_conn.hdel('carts_%s'%request.user.id,sku_id)
        
        redis_conn.srem('selected_%s'%request.user.id,sku_id)
        
        return http.JsonResponse({'code':RETCODE.OK,'errmsg':'ok'})
    else:
        
        
        cookie_str = request.COOKIES.get('carts')
        if cookie_str is None:
            carts = {}
        else:
            carts = pickle.loads(base64.b64decode(cookie_str))
        
        if sku_id in carts:
            del carts[sku_id]
        en = base64.b64encode(pickle.dumps(carts))
        
        response = http.JsonResponse({'code':RETCODE.OK,'errmsg':'ok'})
        response.set_cookie('carts',en.decode())
        
        return response
 
2.前台页面代码
 
// 删除购物车数据
on_delete(index){
    var url = this.host + '/carts/';
    axios.delete(url, {
        data: {
            sku_id: this.carts[index].id
        },
        headers: {
            'X-CSRFToken': getCookie('csrftoken')
        },
        responseType: 'json',
        withCredentials: true
    })
        .then(response => {
            if (response.data.code == '0') {
                this.carts.splice(index, 1);
                // 重新计算界面的价格和数量
                this.compute_total_selected_amount_count();
                this.compute_total_count();
            } else {
                alert(response.data.errmsg);
            }
        })
        .catch(error => {
            console.log(error.response);
        })
},