查询购物车数据

1. 后端接口设计

请求方式 : GET /cart/

请求参数: 无

返回数据: JSON 或 表单

[
    {
        "id": 9,
        "count": 3,
        "name": "华为 HUAWEI P10 Plus 6GB+64GB 钻雕金 移动联通电信4G手机 双卡双待",
        "default_image_url": "http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRcUeAHp9pAARfIK95am88523545",
        "price": "3388.00",
        "selected": true
    },
    {
        "id": 12,
        "count": 1,
        "name": "华为 HUAWEI P10 Plus 6GB+64GB 钻雕蓝 移动联通电信4G手机 双卡双待",
        "default_image_url": "http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRdICAO_CRAAcPaeOqMpA2024091",
        "price": "3388.00",
        "selected": true
    }
]
参数 类型 是否必须 说明
id int 商品sku id
count int 数量
selected bool 是否勾选,默认勾选
name str 商品名称
default_image_url str 商品默认图片
price decimal 商品单价

2. 后端实现

在carts/serializers.py中创建序列化器

class CartSKUSerializer(serializers.ModelSerializer):
    """
    购物车商品数据序列化器
    """
    count = serializers.IntegerField(label='数量')
    selected = serializers.BooleanField(label='是否勾选')

    class Meta:
        model = SKU
        fields = ('id', 'count', 'name', 'default_image_url', 'price', 'selected')

在carts/views.py 中修改视图,增加get方法

class CartView(APIView):
    ...

    def get(self, request):
        """
        获取用户的购物车记录
        """
        # 1. 获取user
        try:
            user = request.user
        except Exception:
            user = None

        # 2. 获取用户的购物车记录
        if user:
            # 2.1 如果用户已登录,从redis中获取用户的购物车记录
            redis_conn = get_redis_connection('cart')

            # 获取用户购物车中商品的id和数目
            # {
            #     '<sku_id>': '<count>',
            #     ...
            # }
            cart_key = 'cart_%s' % user.id
            redis_cart = redis_conn.hgetall(cart_key)

            # 获取购物车被选中的商品的id
            cart_selected_key = 'cart_selected_%s' % user.id
            redis_cart_selected = redis_conn.smembers(cart_selected_key)

            # 组织数据
            # {
            #     '<sku_id>': {
            #         'count': '<count>',
            #         'selected': '<selected>'
            #     },
            #     ...
            # }
            cart_dict = {}
            for sku_id, count in redis_cart.items():
                cart_dict[int(sku_id)] = {
                    'count': int(count),
                    'selected': sku_id in redis_cart_selected
                }

        else:
            # 2.2 如果用户未登录,从cookie中获取用户的购物车记录
            cookie_cart = request.COOKIES.get('cart')

            if cookie_cart:
                cart_dict = pickle.loads(base64.b64decode(cookie_cart))
            else:
                cart_dict = {}

        # 3. 根据购物车记录获取对应商品的信息
        skus = SKU.objects.filter(id__in=cart_dict.keys())
        for sku in skus:
            sku.count = cart_dict[sku.id]['count']
            sku.selected = cart_dict[sku.id]['selected']

        # 4. 将商品数据序列化并进行返回
        serializer = CartSKUSerializer(skus, many=True)
        return Response(serializer.data)

3. 前端实现

修改cart.html文件,增加Vue变量

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>美多商城-购物车</title>
    <link rel="stylesheet" type="text/css" href="css/reset.css">
    <link rel="stylesheet" type="text/css" href="css/main.css">
    <script type="text/javascript" src="js/host.js"></script>
    <script type="text/javascript" src="js/vue-2.5.16.js"></script>
    <script type="text/javascript" src="js/axios-0.18.0.min.js"></script>
</head>
<body>
    <div id="app">
    <div class="header_con">
        <div class="header">
            <div class="welcome fl">欢迎来到美多商城!</div>
            <div class="fr">
                <div v-if="username" class="login_btn fl">
                    欢迎您:<em>{{ username }}</em>
                    <span>|</span>
                    <a @click="logout">退出</a>
                </div>
                <div v-else class="login_btn fl">
                    <a href="login.html">登录</a>
                    <span>|</span>
                    <a href="register.html">注册</a>
                </div>
                <div class="user_link fl">
                    <span>|</span>
                    <a href="user_center_info.html">用户中心</a>
                    <span>|</span>
                    <a href="cart.html">我的购物车</a>
                    <span>|</span>
                    <a href="user_center_order.html">我的订单</a>
                </div>
            </div>
        </div>        
    </div>

    <div class="search_bar clearfix">
        <a href="index.html" class="logo fl"><img src="images/logo.png"></a>
        <div class="sub_page_name fl">|&nbsp;&nbsp;&nbsp;&nbsp;购物车</div>
        <form method="get" action="/search.html" class="search_con fr mt40">
            <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
            <input type="submit" class="input_btn fr" name="" value="搜索">
        </form>
    </div>

    <div class="total_count">全部商品<em>{{total_count}}</em></div>
    <ul class="cart_list_th clearfix">
        <li class="col01">商品名称</li>
        <li class="col03">商品价格</li>
        <li class="col04">数量</li>
        <li class="col05">小计</li>
        <li class="col06">操作</li>
    </ul>
    <ul class="cart_list_td clearfix" v-for="(sku,index) in cart">
        <li class="col01"><input type="checkbox" name="" v-model="sku.selected" @change="update_selected(index)"></li>
        <li class="col02"><img :src="sku.default_image_url"></li>
        <li class="col03">{{ sku.name }}</li>
        <li class="col05">{{ sku.price }}元</li>
        <li class="col06">
            <div class="num_add">
                <a @click="on_add(index)" class="add fl">+</a>
                <input v-model="sku.count" @focus="origin_input=sku.count" @blur="on_input(index)" type="text" class="num_show fl">
                <a @click="on_minus(index)" class="minus fl">-</a>
            </div>
        </li>
        <li class="col07">{{sku.amount}}元</li>
        <li class="col08"><a @click="on_delete(index)">删除</a></li>
    </ul>

    <ul class="settlements">
        <li class="col01"><input type="checkbox" name="" @change="on_selected_all" v-model="selected_all"></li>
        <li class="col02">全选</li>
        <li class="col03">合计(不含运费):<span>¥</span><em>{{total_selected_amount}}</em><br>共计<b>{{total_selected_count}}</b>件商品</li>
        <li class="col04"><a href="place_order.html">去结算</a></li>
    </ul>

    <div class="footer">
        <div class="foot_link">
            <a href="#">关于我们</a>
            <span>|</span>
            <a href="#">联系我们</a>
            <span>|</span>
            <a href="#">招聘人才</a>
            <span>|</span>
            <a href="#">友情链接</a>        
        </div>
        <p>CopyRight © 2016 北京美多商业股份有限公司 All Rights Reserved</p>
        <p>电话:010-****888    京ICP备*******8号</p>
    </div>
    </div>
    <script type="text/javascript" src="/js/cart.js"></script>
</body>
</html>

新建cart.js文件

var vm = new Vue({
    el: '#app',
    data: {
        host,
        username: sessionStorage.username || localStorage.username,
        user_id: sessionStorage.user_id || localStorage.user_id,
        token: sessionStorage.token || localStorage.token,
        cart: [],
        total_selected_count: 0,
        origin_input: 0 // 用于记录手动输入前的值
    },
    computed: {
        total_count: function(){
            var total = 0;
            for(var i=0; i<this.cart.length; i++){
                total += parseInt(this.cart[i].count);
                this.cart[i].amount = (parseFloat(this.cart[i].price) * parseFloat(this.cart[i].count)).toFixed(2);
            }
            return total;
        },
        total_selected_amount: function(){
            var total = 0;
            this.total_selected_count = 0;
            for(var i=0; i<this.cart.length; i++){
                if(this.cart[i].selected) {
                    total += (parseFloat(this.cart[i].price) * parseFloat(this.cart[i].count));
                    this.total_selected_count += parseInt(this.cart[i].count);
                }
            }
            return total.toFixed(2);
        },
        selected_all: function(){
            var selected=true;
            for(var i=0; i<this.cart.length; i++){
                if(this.cart[i].selected==false){
                    selected=false;
                    break;
                }
            }
            return selected;
        }
    },
    mounted: function(){
        // 获取购物车数据
        axios.get(this.host+'/cart/', {
                headers: {
                    'Authorization': 'JWT ' + this.token
                },
                responseType: 'json',
                withCredentials: true
            })
            .then(response => {
                this.cart = response.data;
                for(var i=0; i<this.cart.length; i++){
                    this.cart[i].amount = (parseFloat(this.cart[i].price) * this.cart[i].count).toFixed(2);
                }
            })
            .catch(error => {
                console.log(error.response.data);
            })
    },
    methods: {
        // 退出
        logout: function(){
            sessionStorage.clear();
            localStorage.clear();
            location.href = '/login.html';
        },
        // 减少操作
        on_minus: function(index){
            if (this.cart[index].count > 1) {
                var count = this.cart[index].count - 1;
                this.update_count(index, count);
            }
        },
        on_add: function(index){
            var count = this.cart[index].count + 1;
            this.update_count(index, count);
        },
        on_selected_all: function(){
            var selected = !this.selected_all;
            for (var i=0; i<this.cart.length;i++){
                this.cart[i].selected = selected;
                this.update_count(i, this.cart[i].count);
            }
        },
        // 删除购物车数据
        on_delete: function(index){

        },
        on_input: function(index){
            var val = parseInt(this.cart[index].count);
            if (isNaN(val) || val <= 0) {
                this.cart[index].count = this.origin_input;
            } else {
                // 更新购物车数据
            }
        },
        // 更新购物车数据
        update_count: function(index, count){

        },
        // 更新购物车数据
        update_selected: function(index) {

        }
    }
});