• Luffy之购物车页面搭建


    前面已经将一些课程加入购物车中,并保存到了后端的redis数据库中,此时做购物车页面时,我们需要将在前端向后端发送请求,用来获取数据数据

    购物车页面

    1.首先后端要将数据构建好,后端视图函数如下代码:

    (post请求是将加入购物车的课程信息加入到redis中,其中对于价格在存储的时候要计算折扣后的价格,而get请求则是redis中取出数据到发送前端页面中)

    cart/view:

    
    
    from django.conf import settings
    from rest_framework import status
    from rest_framework.response import Response
    from django_redis import get_redis_connection
    from rest_framework.views import APIView
    from courses.models import Course
    from rest_framework.permissions import IsAuthenticated
    from .utils import get_course_real_price
    class CartAPIView(APIView):
        permission_classes = [IsAuthenticated]
        def post(self,request):
            """添加课程到购物车"""
            # 接受客户端提交过来的课程ID
            course_id = request.data.get("course_id")
            try:
                course = models.Course.objects.get(pk=course_id, status=0)
            except:
                return Response({"message": "当前课程不存在或者已经下架了"}, status=status.HTTP_400_BAD_REQUEST)
          
         # 计算课程的真实价格
         price = get_course_real_price(course)
    # 把课程id和课程价格保存到购物车中redis中
            # redis中使用hash类型保存数据
            redis = get_redis_connection("cart")
            # 获取当前登陆用户的ID,并写入redis中
            user_id = request.user.id
            pl = redis.pipeline()
            pl.multi()
            pl.hset("cart_%s" % user_id, course_id, str(course.price))
            # 把当前课程默认为勾选状态[勾选状态也要保存到redis中]
            # redis中使用set类型保存数据
            pl.sadd("cart_select_%s" % user_id, course_id)
            pl.execute()
    
            # 返回响应操作
            return Response({"message": "success"}, status=status.HTTP_200_OK)
    
    
        def get(self,request):
            # 获取当前登陆用户
            user_id = request.user.id
            # 从redis中获取所有的课程信息和勾选状态
            redis = get_redis_connection("cart")
            course_list = redis.hgetall("cart_%s" % user_id)
            selected_list = redis.smembers("cart_select_%s" % user_id)
    
            # 构造数据返回给前端
            data = []
            for course_id, price in course_list.items():
                course_id = course_id.decode()
                price = price.decode()
    
                try:
                    course_info = models.Course.objects.get(pk=course_id)
                except:
                    return Response({"message": "请求有误,请联系客服"}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
                data.append({
                    "id": course_id,
                    "price": price,
                    "selected": course_id.encode() in selected_list,
                    "course_img": HOST+course_info.course_img.url,
                    "name": course_info.name,
                })
    
            # 返回给客户端
            return Response(data, status=status.HTTP_200_OK)

    其中关于计算折扣的详细方法如下:

    cart/utils:

     1 from decimal import Decimal
     2 
     3 def get_course_real_price(course):
     4     price = course.price
     5     st = course.price_service_type  # 价格服务类型
     6     if st is not None:
     7         all_services = st.priceservices.all()  # 当前价格类型的所有服务策略
     8         if st != None and len(all_services) > 0:
     9             if all_services[0].condition > 0:  # 是否有设置了价格服务,没有设置价格服务的课程,服务为值None
    10                 # 1. 优惠条件值大于0,则属于满减
    11                 service_list = all_services
    12                 # 进行满减价格计算
    13                 real_sale = 0  # 保存满足条件的优惠值
    14                 for item in service_list:
    15                     item.condition = int(item.condition)
    16                     item.sale = int(item.sale)
    17                     if course.price > item.condition and real_sale <= item.sale:
    18                         real_sale = item.sale
    19                 price = course.price - real_sale
    20 
    21             else:  # 优惠条件值为0,则表示是限时折扣或者限时免费
    22                 if all_services[0].sale == "-1":
    23                     # 2. 限时免费
    24                     price = 0
    25                 else:
    26                     # 3. 限时折扣
    27                     # 把优惠值sale中的*去掉
    28                     sale = all_services[0].sale[1:]
    29                     price = course.price * Decimal(sale)
    30         else:
    31             # 原价
    32             price = course.price
    33 
    34     return "%.2f" % price

    2.关于设置勾选的商品发送到后端保存以及按钮删除购物车课程

    后端代码:

    cart/view:(由于此时前端发送过来的数据含有数字,另外开一个类(CartCourseAPIView)处理此次请求)

    post:前端携带相应的取消或添加勾选购物车内课程的选项,后端根据携带值得真假,做相应的增加或删除勾选项

    delete:用于处理前端按钮删除某个购物车课程的处理,需要在购物车课程列表中删除对应键值对,并在勾选集合中删掉对应的id值

     1 class CartCourseAPIView(APIView):
     2     permission_classes = [IsAuthenticated]
     3 
     4     def post(self,request,pk):
     5         user = request.user
     6         print("user_id",user.id)
     7         try:
     8             course = models.Course.objects.get(pk=pk)
     9         except models.Course.DoesNotExist:
    10             return Response({"message": "0"}, status=status.HTTP_400_BAD_REQUEST)
    11 
    12         #获取勾选状态
    13         is_selected = request.data.get("is_select")
    14 
    15         #引入redis
    16         redis = get_redis_connection("cart")
    17         if is_selected:
    18             #  redis中增加当前课程id到勾选集合中
    19             redis.sadd("cart_select_%s" % user.id, pk)
    20         else:
    21             # redis中删除当前课程id
    22             redis.srem("cart_select_%s" % user.id, pk)
    23 
    24         return Response({"message": "1"}, status=status.HTTP_200_OK)
    25 
    26     def delete(self,request,pk):
    27 
    28         user = request.user
    29         try:
    30             course = models.Course.objects.get(pk=pk)
    31         except models.Course.DoesNotExist:
    32             return Response({"message": "无效的课程标号"}, status=status.HTTP_400_BAD_REQUEST)
    33 
    34         # 从购物车和勾选集合中删除指定的数据
    35         redis = get_redis_connection("cart")
    36         pl = redis.pipeline()
    37         pl.multi()
    38         pl.hdel("cart_%s" % user.id, pk)
    39         pl.srem("cart_select_%s" % user.id, pk)
    40         pl.execute()
    41 
    42         return Response({"message": "删除成功!"}, status=status.HTTP_200_OK)

    前端页面要做的一些功能:

    加载数据时,从后端拿数据,发送请求:

    cart.vue

     1   //计算各种折算后,购物车的总价
     2       total_price(){
     3         // 计算总价格
     4         let cl = this.course_list;
     5         let total = 0;
     6         for(let i = 0;i<cl.length;i++){
     7           if(cl[i].selected){
     8             total+=parseFloat(cl[i].price);
     9           }
    10         }
    11         total = total.toFixed(2);
    12         this.total = total;
    13       },
    14     },
    15     created() {
    16        let _this = this;
    17       // 发起请求获取购物车中的商品信息
    18       _this.$axios.get("http://127.0.0.1:8000/cart/",{
    19           headers: {
    20               'Authorization': 'JWT ' + _this.token
    21           },
    22           responseType: 'json',
    23         }).then(response=>{
    24           console.log("response.data",response.data)
    25           _this.course_list = response.data;
    26           this.total_price()
    27         })
    28 
    29     },

    勾选购物车内课程选项时:

    1.在每次用户点击选项框时,向后台发送请求,更新后端redis中的勾选项集合(采用监视的方法,只要选项的值变动,便发送请求),

    2.在发送请求成功后,需要通知父组件更新,结算的总结各

    Template:
    <el-col :span="2" class="checkbox"><el-checkbox label="" v-model="course.selected" name="type"></el-checkbox></el-col>
    
    script标签内:
    
     watch:{
          "course.selected":function(){
            let _this = this;
            _this.$axios.post(`http://127.0.0.1:8000/cart/${this.course.id}`,{
              is_select: _this.course.selected
            },{
              headers:{
                // 附带已经登录用户的jwt token 提供给后端,一定不能疏忽这个空格
                'Authorization':'JWT '+_this.token
              },
              responseType:"json",
            }).then(response=>{

          //通知父组件更改价格 _this.$emit(
    "change_select"); }).catch(error=>{ console.log( error.response ); }) } },

     

    2.按钮删除购物车课程,需要做的有:

    1.用delete请求向后端发送携带要删除课程id的值 

    2.在点击该删除按钮时,同时告知父组件应该删除该项课程(涉及到子传父的数据交互问题)

    3.在点击该删除按钮时,应该刷新所勾选的购物车的课程结算金额,因为实在父组件中展示的总价,也要对父组件发送更新总价的通知

    cartitem.vue中(cart的子组件)

     1 template内:
     2   <el-col :span="4" class="course-delete"><span @click="delete_course(course.id)">删除</span></el-col>
     3 
     4 script内:
     5 
     6  props:["course","course_key"], //父组件传递过来的数据
     7 methods:{
     8       //按删除键删除购物车的课程
     9       delete_course(course_id){
    10         let _this = this;
    11         this.$axios.delete(`http://127.0.0.1:8000/cart/${this.course.id}`, {
    12           headers: {
    13             // 附带已经登录用户的jwt token 提供给后端,一定不能疏忽这个空格
    14             'Authorization': 'JWT ' + _this.token
    15           },
    16           responseType: "json",
    17         }).then(response => {
    18 
    19           // 发送信息给父组件,通过父组件删除当前子组件
    20           _this.$emit("delete_course",_this.course_key);
    21         }).catch(error => {
    22           console.log(error.response);
    23         });
    24     },
    25  },

    cart.vue中(cartitem的父组件):

     1 template:
     2   <CartItem v-for="item,course_key in course_list" :course="item" @change_select="total_price"  @delete_course="del_course" :course_key="course_key" />
     3 
     4 script:
    5 export default { 6 name:"Cart", 7 data(){ 8 return { 9 total:0, 10 course_list:[], 11 token: localStorage.token || sessionStorage.token, 12 } 13 }, 14 15 components:{ 16 Header, 17 Footer, 18 CartItem, 19 }, 20 methods:{ 21 del_course(course_key) { 22 //course_key是通过字传父传回来,用于删除已删除的的购物车的课程 23 this.course_list.splice(course_key, 1); 24 // 重新计算总价格 25 this.total_price(); 26 }, 27 //计算各种折算后,购物车的总价 28 total_price(){ 29 // 计算总价格 30 let cl = this.course_list; 31 let total = 0; 32 for(let i = 0;i<cl.length;i++){ 33 if(cl[i].selected){ 34 total+=parseFloat(cl[i].price); 35 } 36 } 37 total = total.toFixed(2); 38 this.total = total; 39 }, 40 }, 41 created() { 42 let _this = this; 43 // 发起请求获取购物车中的商品信息 44 _this.$axios.get("http://127.0.0.1:8000/cart/",{ 45 headers: { 46 'Authorization': 'JWT ' + _this.token 47 }, 48 responseType: 'json', 49 }).then(response=>{ 50 console.log("response.data",response.data) 51 _this.course_list = response.data; 52 this.total_price() //在加载数据的时候也要对总价做出计算 53 }) 54 55 }, 56 }

    详细的完整代码如下:

    后端试图cart/view

    from django.shortcuts import render
    
    # Create your views here.
    from django_redis import get_redis_connection
    from rest_framework import status
    from rest_framework.permissions import IsAuthenticated
    from rest_framework.response import Response
    from rest_framework.views import APIView
    
    from luffy.apps.cart.utils import get_course_real_price
    from luffy.apps.courses import models
    from luffy.settings import HOST
    
    
    class CartAPIView(APIView):
        permission_classes = [IsAuthenticated]
        def post(self,request):
            """添加课程到购物车"""
            # 接受客户端提交过来的课程ID
            course_id = request.data.get("course_id")
            try:
                course = models.Course.objects.get(pk=course_id, status=0)
            except:
                return Response({"message": "当前课程不存在或者已经下架了"}, status=status.HTTP_400_BAD_REQUEST)
    
            # 计算课程的真实价格,调用写好的的在utils的计算折扣的方法
            price = get_course_real_price(course)
    
            # 把课程id和课程价格保存到购物车中redis中
            # redis中使用hash类型保存数据
            redis = get_redis_connection("cart")
            # 获取当前登陆用户的ID,并写入redis中
            user_id = request.user.id
            pl = redis.pipeline()
            pl.multi()
            pl.hset("cart_%s" % user_id, course_id, price)
            # 把当前课程默认为勾选状态[勾选状态也要保存到redis中]
            # redis中使用set类型保存数据
            pl.sadd("cart_select_%s" % user_id, course_id)
            pl.execute()
    
            # 返回响应操作
            return Response({"message": "success"}, status=status.HTTP_200_OK)
    
    
        def get(self,request):
            # 获取当前登陆用户
            user_id = request.user.id
            # 从redis中获取所有的课程信息和勾选状态
            redis = get_redis_connection("cart")
            course_list = redis.hgetall("cart_%s" % user_id)
            selected_list = redis.smembers("cart_select_%s" % user_id)
    
            # 构造数据返回给前端
            data = []
            for course_id, price in course_list.items():
                course_id = course_id.decode()
                price = price.decode()
    
                try:
                    course_info = models.Course.objects.get(pk=course_id)
                except:
                    return Response({"message": "请求有误,请联系客服"}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
                data.append({
                    "id": course_id,
                    "price": price,
                    "selected": course_id.encode() in selected_list,
                    "course_img": HOST+course_info.course_img.url,
                    "name": course_info.name,
                })
    
            # 返回给客户端
            return Response(data, status=status.HTTP_200_OK)
    
    class CartCourseAPIView(APIView):
        permission_classes = [IsAuthenticated]
    
        def post(self,request,pk):
            user = request.user
            print("user_id",user.id)
            try:
                course = models.Course.objects.get(pk=pk)
            except models.Course.DoesNotExist:
                return Response({"message": "0"}, status=status.HTTP_400_BAD_REQUEST)
    
            #获取勾选状态
            is_selected = request.data.get("is_select")
    
            #引入redis
            redis = get_redis_connection("cart")
            if is_selected:
                #  redis中增加当前课程id到勾选集合中
                redis.sadd("cart_select_%s" % user.id, pk)
            else:
                # redis中删除当前课程id
                redis.srem("cart_select_%s" % user.id, pk)
    
            return Response({"message": "1"}, status=status.HTTP_200_OK)
    
        def delete(self,request,pk):
    
            user = request.user
            try:
                course = models.Course.objects.get(pk=pk)
            except models.Course.DoesNotExist:
                return Response({"message": "无效的课程标号"}, status=status.HTTP_400_BAD_REQUEST)
    
            # 从购物车和勾选集合中删除指定的数据
            redis = get_redis_connection("cart")
            pl = redis.pipeline()
            pl.multi()
            pl.hdel("cart_%s" % user.id, pk)
            pl.srem("cart_select_%s" % user.id, pk)
            pl.execute()
    
            return Response({"message": "删除成功!"}, status=status.HTTP_200_OK)
    View Code

    前端cart.vue(父组件):

      1 <template>
      2   <div class="cart">
      3     <Header/>
      4     <div class="cart-info">
      5         <h3 class="cart-top">我的购物车 <span>共1门课程</span></h3>
      6         <div class="cart-title">
      7            <el-row>
      8              <el-col :span="2">&nbsp;</el-col>
      9              <el-col :span="10">课程</el-col>
     10              <el-col :span="4">有效期</el-col>
     11              <el-col :span="4">单价</el-col>
     12              <el-col :span="4">操作</el-col>
     13            </el-row>
     14         </div>
     15         <CartItem v-for="item in course_list" :course="item" @change_select="total_price"  @delete_course="del_course" :course_key="course_key" />
     16         <div class="calc">
     17             <el-row>
     18               <el-col :span="2">&nbsp;</el-col>
     19               <el-col :span="3">
     20                   <el-checkbox label="全选" name="type"></el-checkbox></el-col>
     21               <el-col :span="2" class="del"><i class="el-icon-delete"></i>删除</el-col>
     22               <el-col :span="12" class="count">总计:¥{{total}}</el-col>
     23               <el-col :span="3" class="cart-calc">去结算</el-col>
     24             </el-row>
     25         </div>
     26     </div>
     27     <Footer/>
     28   </div>
     29 </template>
     30 
     31 <script>
     32   import Header from "./common/Header"
     33   import Footer from "./common/Footer"
     34   import CartItem from "./common/CartItem"
     35   export default {
     36     name:"Cart",
     37     data(){
     38       return {
     39           total:0,
     40           course_list:[],
     41           token: localStorage.token || sessionStorage.token,
     42       }
     43     },
     44 
     45     components:{
     46       Header,
     47       Footer,
     48       CartItem,
     49     },
     50     methods:{
     51       del_course(course_key) {
     52         //course_key是通过字传父传回来,用于删除已删除的的购物车的课程
     53         this.course_list.splice(course_key, 1);
     54         // 重新计算总价格
     55         this.total_price();
     56       },
     57       //计算各种折算后,购物车的总价
     58       total_price(){
     59         // 计算总价格
     60         let cl = this.course_list;
     61         let total = 0;
     62         for(let i = 0;i<cl.length;i++){
     63           if(cl[i].selected){
     64             total+=parseFloat(cl[i].price);
     65           }
     66         }
     67         total = total.toFixed(2);
     68         this.total = total;
     69       },
     70     },
     71     created() {
     72        let _this = this;
     73       // 发起请求获取购物车中的商品信息
     74       _this.$axios.get("http://127.0.0.1:8000/cart/",{
     75           headers: {
     76               'Authorization': 'JWT ' + _this.token
     77           },
     78           responseType: 'json',
     79         }).then(response=>{
     80           console.log("response.data",response.data)
     81           _this.course_list = response.data;
     82           this.total_price()
     83         })
     84 
     85     },
     86   }
     87 </script>
     88 
     89 <style scoped>
     90 .cart{
     91   margin-top: 80px;
     92 }
     93 .cart-info{
     94   overflow: hidden;
     95    1200px;
     96   margin: auto;
     97 }
     98 .cart-top{
     99   font-size: 18px;
    100   color: #666;
    101   margin: 25px 0;
    102   font-weight: normal;
    103 }
    104 .cart-top span{
    105     font-size: 12px;
    106     color: #d0d0d0;
    107     display: inline-block;
    108 }
    109 .cart-title{
    110     background: #F7F7F7;
    111 }
    112 .cart-title .el-row,.cart-title .el-col{
    113     height: 80px;
    114     font-size: 14px;
    115     color: #333;
    116     line-height: 80px;
    117 }
    118 .calc .el-col{
    119   height: 80px;
    120   line-height: 80px;
    121 }
    122 .calc .el-row span{
    123   font-size: 18px!important;
    124 }
    125 .calc .el-row{
    126     font-size: 18px;
    127     color: #666;
    128     margin-bottom: 300px;
    129     margin-top: 50px;
    130     background: #F7F7F7;
    131 }
    132 .calc .del{
    133 
    134 }
    135 .calc .el-icon-delete{
    136   margin-right: 15px;
    137   font-size: 20px;
    138 }
    139 .calc .count{
    140   text-align: right;
    141   margin-right:62px;
    142 }
    143 .calc .cart-calc{
    144      159px;
    145     height: 80px;
    146     border: none;
    147     background: #ffc210;
    148     font-size: 18px;
    149     color: #fff;
    150     text-align: center;
    151     cursor: pointer;
    152 }
    153 </style>
    View Code

    前端cartitem.vue(子组件):

      1 <template>
      2   <div class="cart-item">
      3           <el-row>
      4              <el-col :span="2" class="checkbox"><el-checkbox label="" v-model="course.selected" name="type"></el-checkbox></el-col>
      5              <el-col :span="10" class="course-info">
      6                <img :src="course.course_img" alt="">
      7                 <span>{{course.name}}</span>
      8              </el-col>
      9              <el-col :span="4">
     10                  <el-select  v-model="duration">
     11                     <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
     12                   </el-select>
     13              </el-col>
     14              <el-col :span="4" class="course-price">¥{{course.price}}</el-col>
     15              <el-col :span="4" class="course-delete"><span @click="delete_course(course.id)">删除</span></el-col>
     16            </el-row>
     17   </div>
     18 </template>
     19 
     20 <script>
     21   export default {
     22     name:"CartItem",
     23 
     24     props:["course","course_key"],
     25 
     26     data(){
     27       return {
     28         token: localStorage.token || sessionStorage.token,
     29         duration: 60,
     30         options:[
     31           {value:30,label:"一个月有效"},
     32           {value:60,label:"二个月有效"},
     33           {value:90,label:"三个月有效"},
     34           {value:-1,label:"永久有效"},
     35         ],
     36 
     37       }
     38     },
     39     mounted(){
     40 
     41     },
     42 
     43  methods:{
     44       //按删除键删除购物车的课程
     45       delete_course(course_id){
     46         let _this = this;
     47         this.$axios.delete(`http://127.0.0.1:8000/cart/${this.course.id}`, {
     48           headers: {
     49             // 附带已经登录用户的jwt token 提供给后端,一定不能疏忽这个空格
     50             'Authorization': 'JWT ' + _this.token
     51           },
     52           responseType: "json",
     53         }).then(response => {
     54 
     55           // 发送信息给父组件,通过父组件删除当前子组件
     56           _this.$emit("delete_course",_this.course_key);
     57         }).catch(error => {
     58           console.log(error.response);
     59         });
     60     },
     61  },
     62      watch:{
     63       "course.selected":function(){
     64         let _this = this;
     65         _this.$axios.post(`http://127.0.0.1:8000/cart/${this.course.id}`,{
     66           is_select: _this.course.selected
     67         },{
     68           headers:{
     69             // 附带已经登录用户的jwt token 提供给后端,一定不能疏忽这个空格
     70             'Authorization':'JWT '+_this.token
     71           },
     72           responseType:"json",
     73         }).then(response=>{
     74           _this.$emit("change_select");
     75         }).catch(error=>{
     76           console.log( error.response );
     77         })
     78       }
     79     },
     80   }
     81 </script>
     82 
     83 <style scoped>
     84 .cart-item{
     85   height: 250px;
     86 }
     87 .cart-item .el-row{
     88   height: 100%;
     89 }
     90 .course-delete{
     91     font-size: 14px;
     92     color: #ffc210;
     93     cursor: pointer;
     94 }
     95 .el-checkbox,.el-select,.course-price,.course-delete{
     96     display: flex;
     97     align-items: center;
     98     justify-content: center;
     99     height: 100%;
    100 }
    101 .el-checkbox{
    102     padding-top: 55px;
    103 }
    104 .el-select{
    105     padding-top: 45px;
    106      118px;
    107     height: 28px;
    108     font-size: 12px;
    109     color: #666;
    110     line-height: 18px;
    111 }
    112 .course-info img{
    113      175px;
    114     height: 115px;
    115     margin-right: 35px;
    116     vertical-align: middle;
    117 }
    118 .cart-item .el-col{
    119     padding: 67px 10px;
    120     vertical-align: middle!important;
    121 }
    122 .course-info{
    123 
    124 }
    125 </style>
    View Code
  • 相关阅读:
    机器学习
    Python
    sublime的推荐插件
    C语言编程
    将生成logo图片导入到Altium Designer中
    基于MDK的stm32实践过程中,debug的总结
    LCD12864使用总结
    c语言使用技巧
    LCD12864显示中文乱码
    在Keil中做stm32的软件仿真,查看输出PWM波形时,在逻辑分析仪中规定IO口signal,出现"unknow signal"
  • 原文地址:https://www.cnblogs.com/Mixtea/p/10639277.html
Copyright © 2020-2023  润新知