本项目基于B站UP主‘神奇的老黄’的教学视频‘天天生鲜Django项目’,视频讲的非常好,推荐新手观看学习
https://www.bilibili.com/video/BV1vt41147K8?p=1
购物车页面展示
分析购物车页面需要后台查询的数据有哪些,最基础的是购物车中goods对象,还有一些比如购物车中每个商品的小计金额、所有商品的总数目以及总价。虽然这些信息通过前台js代码也可以进行计算展示,但是最好是后台查询数据时一并计算一起发给前端,减轻前端的计算压力。
class CartView(LoginRequiredMixin, View): '''购物车视图类''' template_name = 'cart/cart.html' def get(self, request): '''显示购物车''' user = request.user # 获取购物车信息 connect = get_redis_connection('default') cart_key = 'cart_%d'%(user.id) cart = connect.hgetall(cart_key) # 返回给前端的goods_list goods_list = [] total_count = 0 total_amount = 0 for goods_id, count in cart.items(): goods = Goods.objects.get(id=int(goods_id)) # 小计 amount = goods.price * int(count) # 给goods对象新增属性 goods.amount = amount goods.count = int(count) # 将商品添加至列表中 goods_list.append(goods) # 合计 total_count += int(count) total_amount += amount # 组织上下文 context = { 'goods_list': goods_list, 'total_count': total_count, 'total_amount': total_amount, } return render(request, self.template_name, context)对应HTML模板:
{% extends 'base_no_cart.html' %} {% load static %} {% block title %}天天生鲜-购物车{% endblock title %} {% block infoname %}购物车{% endblock infoname %} {% block body %} <div class="total_count">全部商品<em>{{ total_count }}</em>件</div> <ul class="cart_list_th clearfix"> <li class="col01">商品名称</li> <li class="col02">商品单位</li> <li class="col03">商品价格</li> <li class="col04">数量</li> <li class="col05">小计</li> <li class="col06">操作</li> </ul> <form method="post" action="{% url 'order:place' %}"> {% csrf_token %} {% for goods in goods_list %} <ul class="cart_list_td clearfix"> <li class="col01"><input type="checkbox" name="goods_ids" value="{{ goods.id }}" checked></li> <li class="col02"><a href="{% url 'goods:detail' goods.id %}"><img src="{{ goods.image.url }}"></a></li> <li class="col03"><a href="{% url 'goods:detail' goods.id %}">{{ goods.name }}<br><em>{{ goods.price }}元/{{ goods.uom }}</em></a></li> <li class="col04">{{ goods.uom }}</li> <li class="col05">{{ goods.price }}元</li> <li class="col06"> <div class="num_add"> {% csrf_token %} <a href="javascript:;" class="add fl">+</a> <input type="text" goods_id="{{ goods.id }}" class="num_show fl" value="{{ goods.count }}"> <a href="javascript:;" class="minus fl">-</a> </div> </li> <li class="col07">{{ goods.amount }}元</li> <li class="col08"><a href="javascript:;" class="delete">删除</a></li> </ul> {% endfor %} <ul class="settlements"> <li class="col01"><input type="checkbox" name="" checked=""></li> <li class="col02">全选</li> <li class="col03">合计(不含运费):<span>¥</span><em>{{ total_amount }}</em><br>共计<b>{{ total_count }}</b>件商品</li> <li class="col04"><input type="submit" value='去结算'/></li> </ul> </form> {{ errmsg }} {% endblock body %} {% block endfiles %} <script src="{% static 'js/jquery-1.12.4.min.js'%}"></script> <script> //全选按钮 $('.settlements').find(':checkbox').change(function(){ //获取全选checkbox的全选状态 is_checked = $(this).prop('checked') //遍历设置商品的checkbox $('.cart_list_td').find(':checkbox').each(function(){ $(this).prop('checked', is_checked) }) //刷新总价格和总数量 update_page_info() }) //单个CheckBox监听 $('.cart_list_td').find(':checkbox').change(function(){ //获取全部商品的数目 all_count = $('.cart_list_td').length //获取被选中商品的数目 checked_count = $('.cart_list_td').find(':checked').length //判断两者数目是否相等,不相等则设置全选为未选中,否则设置为选中 $('.settlements').find(':checkbox').prop('checked', all_count == checked_count) //更新总数量和价格 update_page_info() }) //加号点击事件 $('.add').click(function(){ count = update_count($(this).next(), 1) goods_id = $(this).next().attr('goods_id') //发送ajax请求 send_ajax_change(goods_id, count) }) //减号点击事件 $('.minus').click(function(){ count = update_count($(this).prev(), -1) goods_id = $(this).prev().attr('goods_id') //发送ajax请求 send_ajax_change(goods_id, count) }) //手动修改数量 $('.num_show').blur(function(){ //获取数量 count = $(this).val() if(isNaN(count) || count.trim().length==0 || parseInt(count) <=0){ count = 1 $(this).val(parseInt(count)) } //提交ajax请求 goods_id = $(this).attr('goods_id') count = parseInt(count) send_ajax_change(goods_id, count) }) //删除按钮 $('.delete').click(function(){ //获取商品所在的ul元素 goods_ul = $(this).parents('.cart_list_td') //获取商品id goods_id = goods_ul.find('.num_show').attr('goods_id') send_ajax_delete(goods_id) //移除界面数据 goods_ul.remove() //刷新总数量和总价格 update_page_info() }) //数量加减 function update_count(num_show, num){ //获取原数量 count = parseInt(num_show.val()) //计算新数量 count += num if (count <= 0){ count = 1 } //重新设置数量 num_show.val(count) return count } //更新选中商品的总价格和总数量 function update_page_info(){ var total_amount = 0 var total_count = 0 $('.cart_list_td').find(':checked').parents('ul').each(function(){ count = parseInt($(this).find('.num_show').val()) amount = parseFloat($(this).find('.col07').text()) total_count += count total_amount += amount }) //更新总件数和价格 $('.settlements').find('em').text(total_amount.toFixed(2)) $('.settlements').find('b').text(total_count) } //发送ajax请求,修改购物车信息 function send_ajax_change(goods_id, count){ csrf = $('input[name="csrfmiddlewaretoken"]').val() parameter = { 'goods_id': goods_id, 'count': count, 'csrfmiddlewaretoken': csrf } $.post('change/', parameter, function(data){ //回调函数 if(data.status == 'S'){ //刷新全部商品件数 $('.total_count').children('em').text(data.total_count) //刷新小计,找到传进来的goods_id对应的那一行input,找到其父节点下的小计节点 amount = parseInt(data.amount) $('input[goods_id='+parameter.goods_id+']').parents('.cart_list_td').children('.col07').text(amount.toFixed(2)+'元')//刷新总数量和总价格 update_page_info() } else{ alert(data.errmsg) //将数量重置为改变前 $('input[goods_id='+parameter.goods_id+']').val(data.count) } }) } //发送ajax请求,删除商品 function send_ajax_delete(goods_id){ csrf = $('input[name="csrfmiddlewaretoken"]').val() parameter = { 'goods_id': goods_id, 'csrfmiddlewaretoken': csrf } $.post('delete/', parameter, function(data){ if(data.status == 'S'){ //刷新总数量 total_count = parseInt(data.total_count) $('.total_count').children('em').text(total_count) }else{ alert(data.errmsg) } }) } </script> {% endblock endfiles%}
全选框和勾选框
以下Js代码知识点基于jquery
1. 获取子标签可通过find和children,对应的获取父标签为parents和parent:
.find('selector'),遍历当前元素集合中每个元素的后代。不管是多少代后代。如find('元素名')、find('#id')、find('.class')、find(':type')等
.children(selector),遍历当前元素集合中的第一代后代,只有儿子辈。如children('元素名')、children('#id')、children('.class')、children(':type')等
2. 勾选框的勾选和取消动作对应的是change方法
3. 获取标签的属性可通过attr 与 prop获取:
在jQuery中attr
表示HTML文档节点属性,而prop
则表示JS对象属性。
例如,下面这个 HTML 元素:<input type="text" value="Name:">
拥有两个 attributes。
一旦浏览器解析该代码,HTMLInputElement 对象就会被创建,并且该对象会拥有很多 properties,如:accept、accessKey、align、alt、attributes、autofocus、baseURI、checked、childElementCount、ChildNodes、children、classList、className、clientHeight 等等。
对于某个 DOM 节点对象,properties 是该对象的所有属性,而 attributes 是该对象对应元素(标签)的属性。当一个 DOM 节点为某个 HTML 元素所创建时,其大多数 properties 与对应 attributes 拥有相同或相近的名字。
但是对于表单元素的checked
、selected
、disabled
等属性,attribute的checked、selected、disabled就是表示该属性初始状态的值,property的checked、selected、disabled才表示该属性实时状态的值(值为true或false)。因此得使用prop()
函数来设置或获取checked
、selected
、disabled
等属性
//更新选中商品的总价格和总数量 function update_page_info(){ var total_amount = 0 var total_count = 0 $('.cart_list_td').find(':checked').parents('ul').each(function(){ count = parseInt($(this).find('.num_show').val()) amount = parseFloat($(this).find('.col07').text()) total_count += count total_amount += amount }) //更新总件数和价格 $('.settlements').find('em').text(total_amount.toFixed(2)) $('.settlements').find('b').text(total_count) } //全选按钮 $('.settlements').find(':checkbox').change(function(){ //获取全选checkbox的全选状态 is_checked = $(this).prop('checked') //遍历设置商品的checkbox $('.cart_list_td').find(':checkbox').each(function(){ $(this).prop('checked', is_checked) }) //刷新总价格和总数量 update_page_info() }) //单个CheckBox监听 $('.cart_list_td').find(':checkbox').change(function(){ //获取全部商品的数目 all_count = $('.cart_list_td').length //获取被选中商品的数目 checked_count = $('.cart_list_td').find(':checked').length //判断两者数目是否相等,不相等则设置全选为未选中,否则设置为选中 $('.settlements').find(':checkbox').prop('checked', all_count == checked_count) //更新总数量和价格 update_page_info() })
实时修改购物车中商品数量
以下Js代码知识点基于jquery:
1. .next()获取下一标签,.prev()获取上一标签
2. input框的失去焦点时会调用触发.blur()方法
3. 校验数量时:
isNaN(x):如果 x 是特殊的非数字值 NaN(或者能被转换为这样的值),返回的值就是 true。如果 x 是正常的数字,则返回 false。NaN,该值表示一个非法的数字
.trim().length:去掉空格后的长度
parseInt(string, radix):将字符串解析为数字,只有字符串中的第一个数字组合会被返回。parseInt("10"); //返回 10 parseInt("10.56"); //返回 10
4. 在django中提交ajax的post请求时,也需要经过django自带的CSRF认证,可以在html模板代码中随意位置(习惯写在触发ajax请求的按钮或者链接上)加上{% CSRF_TOKEN%},加上了之后,浏览器将其解析hidden的input代码:
<input type="hidden" name="csrfmiddlewaretoken" value="9BdYDF3097s9UGgYJ6L8aGmqkL9DJCgMZehs5xT4Ebdrlvdg74KMXuEvcQpksO6C">
通过
csrf = $('input[name="csrfmiddlewaretoken"]').val()
获取值,并在post参数中加入 'csrfmiddlewaretoken': csrf,注意这里的key值'csrfmiddlewaretoken'名字固定不能更改
5. toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。
//加号点击事件 $('.add').click(function(){ count = update_count($(this).next(), 1) goods_id = $(this).next().attr('goods_id') //发送ajax请求 send_ajax_change(goods_id, count) }) //减号点击事件 $('.minus').click(function(){ count = update_count($(this).prev(), -1) goods_id = $(this).prev().attr('goods_id') //发送ajax请求 send_ajax_change(goods_id, count) }) //手动修改数量 $('.num_show').blur(function(){ //获取数量 count = $(this).val() if(isNaN(count) || count.trim().length==0 || parseInt(count) <=0){ count = 1 $(this).val(parseInt(count)) } //提交ajax请求 goods_id = $(this).attr('goods_id') count = parseInt(count) send_ajax_change(goods_id, count) }) //数量加减 function update_count(num_show, num){ //获取原数量 count = parseInt(num_show.val()) //计算新数量 count += num if (count <= 0){ count = 1 } //重新设置数量 num_show.val(count) return count } //发送ajax请求,修改购物车信息 function send_ajax_change(goods_id, count){ csrf = $('input[name="csrfmiddlewaretoken"]').val() parameter = { 'goods_id': goods_id, 'count': count, 'csrfmiddlewaretoken': csrf } $.post('change/', parameter, function(data){ //回调函数 if(data.status == 'S'){ //刷新全部商品件数 $('.total_count').children('em').text(data.total_count) //刷新小计,找到传进来的goods_id对应的那一行input,找到其父节点下的小计节点 amount = parseInt(data.amount) $('input[goods_id='+parameter.goods_id+']').parents('.cart_list_td').children('.col07').text(amount.toFixed(2)+'元')//刷新总数量和总价格 update_page_info() } else{ alert(data.errmsg) //将数量重置为改变前 $('input[goods_id='+parameter.goods_id+']').val(data.count) } }) }
新增CartChangeView变更购物车商品数量
class CartChangeView(View): '''购物车改变视图''' def post(self, request): user = request.user context = { 'status': 'E', 'errmsg': '' } # 判断是否登录 if not user.is_authenticated: context['errmsg'] = '用户未登录!' return JsonResponse(context) # 获取数据 goods_id = request.POST.get('goods_id') count = request.POST.get('count') # 校验数据 if not all([goods_id, count]): context['errmsg'] = '数据不完整!' return JsonResponse(context) # 商品是否存在 try: goods = Goods.objects.get(id=int(goods_id)) except Goods.DoesNotExist: context['errmsg'] = '商品不存在!' return JsonResponse(context) # 数量格式是否正确 try: count = int(count) except Exception as e: context['errmsg'] = '数量格式不正确!' return JsonResponse(context) # 更新redis数据库 connect = get_redis_connection('default') cart_key = 'cart_%d'%(user.id) # 校验库存 if count > goods.onhand: context['errmsg'] = '库存不足!' # 返回原数量 context['count'] = int(connect.hget(cart_key, goods_id)) return JsonResponse(context) # 更新值 connect.hset(cart_key, goods_id, count) # 重新计算总件数 total_count = 0 values = connect.hvals(cart_key) for val in values: total_count += int(val) # 上下文 context['amount'] = goods.price * count context['total_count'] = total_count context['status'] = 'S' return JsonResponse(context)
删除购物车中商品
以下Js代码知识点基于jquery:
1. .remove() 方法删除被选元素及其子元素。
//删除按钮 $('.delete').click(function(){ //获取商品所在的ul元素 goods_ul = $(this).parents('.cart_list_td') //获取商品id goods_id = goods_ul.find('.num_show').attr('goods_id') send_ajax_delete(goods_id) //移除界面数据 goods_ul.remove() //刷新总数量和总价格 update_page_info() }) //发送ajax请求,删除商品 function send_ajax_delete(goods_id){ csrf = $('input[name="csrfmiddlewaretoken"]').val() parameter = { 'goods_id': goods_id, 'csrfmiddlewaretoken': csrf } $.post('delete/', parameter, function(data){ if(data.status == 'S'){ //刷新总数量 total_count = parseInt(data.total_count) $('.total_count').children('em').text(total_count) }else{ alert(data.errmsg) } }) }
新增CartDeleteView删除购物车商品
class CartDeleteView(View): '''购物车删除视图''' def post(self, request): user = request.user context = { 'status': 'E', 'errmsg': '' } if not user.is_authenticated: context['errmsg'] = '用户未登录!' return JsonResponse(context) # 接收数据 goods_id = request.POST.get('goods_id') # 校验数据 try: goods_id = int(goods_id) goods = Goods.objects.get(id=goods_id) except Exception as e: context['errmsg'] = '商品不存在' return JsonResponse(context) # 删除购物车数据 connect = get_redis_connection('default') cart_key = 'cart_%d'%(user.id) connect.hdel(cart_key, goods_id) # 重新获取商品数量 vals = connect.hvals(cart_key) total_count = 0 for val in vals: total_count += int(val) # 上下文 context['total_count'] = total_count context['status'] = 'S' return JsonResponse(context)