night_购物车结算有效期相关

 

2. 实现课程详情页倒计时功能

模型返回当前课程优惠的剩余时间戳

# courses/models.py中,Course模型新增计算剩余时间的方法,代码:
from ckeditor_uploader.fields import RichTextUploadingField
class Course(BaseModel):
    """
    专题课程
    """
    。。。

    def has_time(self):
        """计算活动的剩余时间"""
        now = datetime.now()
        try:
            course_prices = self.prices.get(start_time__lte=now, end_time__gte=now, is_delete=False, is_show=True)
            # 把 活动结束时间 - 当前时间 = 剩余时间
            return int( course_prices.end_time.timestamp() - now.timestamp() )
        except:
            print("---活动过期了----")

        return 0

# 序列化器,新增返回字段
class CourseDetailModelSerializer(serializers.ModelSerializer):
    """课程详情页的序列化器"""
    teacher = TeacherDetailModelSerializer()
    class Meta:
        model = Course
        # fields = ("id","name", "video", "course_img", "students","lessons","pub_lessons","price","teacher","course_level","brief")
        fields = ("id","name", "course_img", "students","lessons","pub_lessons","price","teacher","course_level","brief","get_course_price","get_course_discount_type","has_time")

 

# 前端使用定时器setInterval完成倒计时功能
<template>
    <div class="detail">
      <Header/>
      <div class="main">
        <div class="course-info">
          <div class="wrap-left">
            。。。。
            <div class="sale-time">
              <p class="sale-type">{{course.get_course_discount_type}}</p>
              <p class="expire">距离结束:仅剩 {{Math.floor(course.has_time/86400)}}天 {{Math.floor(course.has_time%86400/3600)}}小时 {{Math.floor(course.has_time%86400%3600/60)}}分 <span class="second">{{Math.floor(course.has_time%86400%3600%60)}}</span> 秒</p>
            </div>
            <p class="course-price">
              <span>活动价</span>
              <span class="discount">¥{{course.get_course_price}}</span>
              <span class="original">¥{{course.price}}</span>
            </p>
            </div>
            <div v-else>
            <div class="sale-time">
              <p class="sale-type">价格: ¥{{course.price}}</p>
            </div>
            </div>
            。。。。

</template>
<script>
import Header from "./common/Header"
import Footer from "./common/Footer"

import {videoPlayer} from 'vue-video-player';

export default {
    name: "Detail",
    。。。。
    created(){
      // 获取当前课程ID
      this.course_id = this.$route.query.id - 0;
      // 判断ID基本有效性
      let _this = this;
      if( isNaN(this.course_id) || this.course_id < 1 ){
        _this.$alert("无效的课程ID!","错误",{
          callback(){
            _this.$router.go(-1);
          }});
      }
      // 发送请求获取后端课程数据
      this.$axios.get(this.$settings.Host+`/courses/detail/${this.course_id}/`).then(response=>{
        this.course = response.data;
        // 修改视频中的封面图片
        this.playerOptions.poster = this.course.course_img;
        // 倒计时
        if(this.course.has_time > 1){

          let timer = setInterval(()=>{
            if( this.course.has_time > 1 ){
              this.course.has_time-=1;
            }else{
              clearInterval(timer);
              location.reload();
            }
          },1000);

        }

      }).catch(error=>{
        console.log(error.response)
      });
    },
    。。。。
}
</script>

 

3. 根据课程有效期调整价格

 

后端实现提供课程有效期的API

# 模型代码:
"""课程有效期"""
class CourseTime(BaseModel):
    """课程有效期表"""
    timer = models.IntegerField(verbose_name="购买周期",default=30,help_text="单位:天<br>建议按月书写,例如:1个月,则为30.")
    title = models.CharField(max_length=150, null=True, blank=True, verbose_name="购买周期的文本提示", default="1个月有效", help_text="要根据上面的购买周期,<br>声明对应的提示内容,<br>展示在购物车商品列表中")
    course = models.ForeignKey("Course", on_delete=models.CASCADE, related_name="prices", verbose_name="课程")
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价", default=0)

    class Meta:
        db_table = "ly_course_time"
        verbose_name = "课程有效期表"
        verbose_name_plural = "课程有效期表"

    def __str__(self):
        return "课程:%s,周期:%s,价格:%s" % (self.course, self.timer, self.price)

# 数据建议
python manage.py makemigrations
python manage.py migrate

# 把模型注册到xadmin中
from .models import CourseTime
class CourseTimeModelAdmin(object):
    """课程与价格优惠关系模型管理类"""
    list_display = ["course","title","timer","price"]
xadmin.site.register(CourseTime, CourseTimeModelAdmin)

 

添加测试数据

 

# 购物车视图中,返回购买课程的周期列表,cart/views.py,代码
class CartAPIView(APIView):
    permission_classes = [IsAuthenticated]
    """购物车视图"""
    def get(self,request):
        """获取购物车商品课程列表"""
        # 获取当前用户ID
        # user_id = 1
        user_id = request.user.id
        # 通过用户ID获取购物车中的商品信息
        redis = get_redis_connection("cart")
        cart_goods_list = redis.hgetall("cart_%s" % user_id ) # 商品课程列表
        cart_goods_selects = redis.smembers("cart_selected_%s" % user_id)
        # redis里面的所有数据最终都是以bytes类型的字符串保存的
        # print( cart_goods_selects ) # 格式: {b'7', b'3', b'5'}
        # print( cart_goods_list ) # 格式: {b'7': b'-1', b'5': b'-1'}
        # 遍历购物车中的商品课程到数据库获取课程的价格, 标题, 图片
        data_list = []
        try:
            for course_id_bytes,expire_bytes in cart_goods_list.items():
                course_id = int( course_id_bytes.decode() )
                expire    = expire_bytes.decode()
                course = Course.objects.get(pk=course_id)

                # 获取购买的课程的周期价格列表
                expires = course.coursetimes.all()
                # 默认具有永久价格
                expire_list = [{
                    "title": "永久有效",
                    "timer": -1,
                    "price": course.price
                }]
                for item in expires:
                    expire_list.append({
                        "title":item.title,
                        "timer":item.timer,
                        "price":item.price,
                    })

                data_list.append({
                    "id": course_id,
                    "expire":expire,
                    "course_img": course.course_img.url,
                    "name": course.name,
                    "price": course.get_course_price(),
                    "is_select": course_id_bytes in cart_goods_selects,
                    "expire_list": expire_list,
                })
        except:
            return Response(data_list,status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        # print(data_list)
        # 返回查询结果
        return Response(data_list,status=status.HTTP_200_OK)

 

# 前端展示购物车中每一个商品课程的购买周期,代码:
<template>
    。。。。
            <el-table-column label="有效期" width="216">
              <template slot-scope="scope">
                <el-select @change="ChangeExpire(scope.row)" v-model="scope.row.expire" placeholder="请选择">
                  <el-option v-for="item in scope.row.expire_list" :key="item.timer" :label="item.title" :value="item.timer"></el-option>
                </el-select>
              </template>
            </el-table-column>
            。。。。
    </div>
</template>

<script>
import Header from "./common/Header"
import Footer from "./common/Footer"
export default {
    name: "Cart",
    data(){
      return{
        // 注释掉原来的有效周期测试数据
        // expire:3,
        // expire_list:[]
        courseData:[], // 购物车中的商品信息
        selection:[],  // 购物车中被勾选的商品信息
        total_price:0.00,
      }
    },
    。。。。
    created(){
      // 判断是否登录
      this.token = sessionStorage.token || localStorage.token;
      if( !this.token ){
        this.$confirm("对不起,您尚未登录!请登录",'提示').then(() => {
          this.$router.push("/login");
        }).catch(()=>{
          this.$router.go(-1);
        });
      }else{
        // 获取购物车商品数据
        this.$axios.get(this.$settings.Host+"/carts/course/",{
          headers:{
            // 注意下方的空格!!!
            "Authorization":"jwt " + this.token
          }
        }).then(response=>{

          this.courseData = response.data;
          // 更新在vuex里面的数据
          this.$store.state.cart.count = response.data.length;

          // 调整因为ajax数据请求导致勾选状态人没有出现的原因,使用定时器进行延时调用
          setTimeout(()=>{
            let expire_data = [];
            this.courseData.forEach(course=>{
              course.expire_list.forEach(row=>{
                expire_data[row.timer] = row.title;
              });
            });

            // row 就是字典数据[json]
            this.courseData.forEach(row => {
              // 设置商品课程的选中状态
              if(row.is_select){
                this.$refs.multipleTable.toggleRowSelection(row);
              }
              // 调整有效期选项中数值变成文本内容
              row.expire = expire_data[row.expire];
            });
          },0)

        }).catch(error=>{
          let status = error.response.status;
          if( status == 401 ){
            this.token = null;
            sessionStorage.removeItem("token");
            localStorage.removeItem("token");
            let _this = this;
            this.$alert("您尚未登录或登录超时!请重新登录","警告",{
              callback(){
                _this.$router.push("/login");
              }
            });
          }
        })

      }
    },
    
    。。。
}
</script>

 

4. 当切换课程周期时,后端重新计算价格并返回

模型中计算真实价格时,增加一个原价字段,通过原价字段,判断本次计算是计算周期还是计算永久有效。

from ckeditor_uploader.fields import RichTextUploadingField
class Course(BaseModel):
    """
    专题课程
    """
    。。。。

    def get_course_price(self,price=0):
        # 获取当前课程的真实价格

        self.price = price if price != 0 else self.price # 判断调用当前方法时,是否定义了价格
        
        。。。。

 

# 视图中修改patch方法,在用户切换购买课程周期时,重新计算真实课程价格,代码:
def patch(self,request):
        """更新购物城中的商品信息[切换课程有效期]"""
        # 获取当前登录的用户ID
        # user_id = 1
        user_id = request.user.id

        # 获取当前操作的课程ID
        course_id = request.data.get("course_id")

        # 获取新的有效期
        expire = request.data.get("expire")

        # 获取redis链接
        redis = get_redis_connection("cart")

        # 更新购物中商品课程的有效期
        redis.hset("cart_%s" % user_id,course_id, expire)

        # 根据新的课程有效期获取新的课程原价
        try:
            coursetime = CourseTime.objects.get(course=course_id, timer=expire)
            # 根据新的课程价格,计算真实课程价格
            price = coursetime.course.get_course_price(coursetime.price)
        except:
            # 这里给price设置一个默认值,当值-1,则前段不许要对价格进行调整
            course = Course.objects.get(pk=course_id)
            price = course.get_course_price()


        return Response({
            "price": price,
            "message": "修改购物车信息成功!"
        }, status=status.HTTP_200_OK)

 

5. 前端获取课程有效期列表

      // 更新课程的有效期
      ChangeExpire(course){
        // 获取课程ID和有效期
        let course_id = course.id;
        let expire    = course.expire;

        // 发送patch请求更新有效期
        this.$axios.patch(this.$settings.Host+"/carts/course/",{
          course_id,
          expire,  // 这里是简写,相当于 expire:expire,
        },{
          headers:{
            // 注意下方的空格!!!
            "Authorization":"jwt " + this.token
          },
        }).then(response=>{
          // 更新购买的商品课程的价格
          course.price = response.data.price;
          this.$message(response.data.message,"提示");
        });
      },

# 完成上面的步骤以后,切换购买周期时,价格就发生了变化,但是购物车页面刷新时,发现价格还原成"永久有效"的价格。所以我们需要在后端的购物车商品列表api接口中针对价格的购买周期进行判断。

# carts/views.py的CartAPIView
    def get(self,request):
        """获取购物车商品课程列表"""
        # 获取当前用户ID
        # user_id = 1
        user_id = request.user.id
        # 通过用户ID获取购物车中的商品信息
        redis = get_redis_connection("cart")
        cart_goods_list = redis.hgetall("cart_%s" % user_id ) # 商品课程列表
        cart_goods_selects = redis.smembers("cart_selected_%s" % user_id)
        # redis里面的所有数据最终都是以bytes类型的字符串保存的
        # print( cart_goods_selects ) # 格式: {b'7', b'3', b'5'}
        # print( cart_goods_list ) # 格式: {b'7': b'-1', b'5': b'-1'}
        # 遍历购物车中的商品课程到数据库获取课程的价格, 标题, 图片
        data_list = []
        try:
            for course_id_bytes,expire_bytes in cart_goods_list.items():
                course_id = int( course_id_bytes.decode() )
                expire    = expire_bytes.decode()
                course = Course.objects.get(pk=course_id)

                # 获取购买的课程的周期价格列表
                expires = course.coursetimes.all()
                # 默认具有永久价格
                expire_list = [{
                    "title": "永久有效",
                    "timer": -1,
                    "price": course.price
                }]
                for item in expires:
                    expire_list.append({
                        "title":item.title,
                        "timer":item.timer,
                        "price":item.price,
                    })

                try:
                    # 根据课程有效期传入课程原价
                    coursetime = CourseTime.objects.get(course=course_id, timer=expire)
                    # 根据新的课程价格,计算真实课程价格
                    price= coursetime.price
                except:
                    price = 0

                data_list.append({
                    "id": course_id,
                    "expire":expire,
                    "course_img": course.course_img.url,
                    "name": course.name,
                    "price": course.get_course_price(price),
                    "is_select": course_id_bytes in cart_goods_selects,
                    "expire_list": expire_list,
                })
        except:
            return Response(data_list,status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        # print(data_list)
        # 返回查询结果
        return Response(data_list,status=status.HTTP_200_OK)

 

6. 最后,修复在用户换购买周期时,前端需要重新计算购物车中所有商品的总价格

// 更新课程的有效期
      ChangeExpire(course){
        // 获取课程ID和有效期
        let course_id = course.id;
        let expire    = course.expire;

        // 发送patch请求更新有效期
        this.$axios.patch(this.$settings.Host+"/carts/course/",{
          course_id,
          expire,  // 这里是简写,相当于 expire:expire,
        },{
          headers:{
            // 注意下方的空格!!!
            "Authorization":"jwt " + this.token
          },
        }).then(response=>{
          // 更新购买的商品课程的价格
          course.price = response.data.price;
          // 重新计算购物车中的商品总价
          this.getTotalPrice();
          this.$message(response.data.message,"提示");
        });
      },

 

posted @ 2019-05-21 20:24  pythonernoob  阅读(148)  评论(0)    收藏  举报