课程动态价格策略实现
动态价格策略设计
商城往往为了提高销量都会出现活动内容,商品因为参加了活动所以会产生价格的变动。
价格优惠活动类型名称: 限时免费, 限时折扣, 限时减免, 限时满减, 积分抵扣, 优惠券针对单个商品的动态价格策略,公式:限时免费 0限时折扣 原价*0.8限时减免 原价-减免价针对单次下单的动态价格策略,公式:限时满减 总价-(满减计算后换算价格)积分抵扣 总价-(积分计算后换算价格) ->> 积分与现金换算比率优惠券 总价-(优惠券计算后的优惠价格) ->> 优惠券
模型创建
新增4个课程优惠相关的4个模型,courses/models.py,代码:
from django.utils import timezone as datetimeclass Activity(BaseModel):start_time = models.DateTimeField(default=datetime.now, verbose_name="开始时间")end_time = models.DateTimeField(default=datetime.now, verbose_name="结束时间")description = RichTextUploadingField(blank=True, null=True, verbose_name="活动介绍")remark = models.TextField(blank=True, null=True, verbose_name="备注信息")class Meta:db_table = "fg_activity"verbose_name = "优惠活动"verbose_name_plural = verbose_namedef __str__(self):return self.nameclass DiscountType(BaseModel):remark = models.CharField(max_length=250, blank=True, null=True, verbose_name="备注信息")class Meta:db_table = "fg_discount_type"verbose_name = "优惠类型"verbose_name_plural = verbose_namedef __str__(self):return self.nameclass Discount(BaseModel):discount_type = models.ForeignKey("DiscountType", on_delete=models.CASCADE, related_name='discount_list', db_constraint=False, verbose_name="优惠类型")condition = models.IntegerField(blank=True, default=0, verbose_name="满足优惠的价格条件", help_text="设置享受优惠的价格条件,如果不填或0则没有优惠门槛")sale = models.TextField(verbose_name="优惠公式", help_text="""0表示免费;<br>*号开头表示折扣价,例如填写*0.82,则表示八二折;<br>-号开头表示减免价, 例如填写-100,则表示减免100;<br>""")class Meta:db_table = "fg_discount"verbose_name = "优惠公式"verbose_name_plural = verbose_namedef __str__(self):return "价格优惠:%s,优惠条件:%s,优惠公式: %s" % (self.discount_type.name, self.condition, self.sale)class CourseActivityPrice(BaseModel):activity = models.ForeignKey("Activity", on_delete=models.CASCADE, related_name='price_list', db_constraint=False, verbose_name="活动")course = models.ForeignKey("Course", on_delete=models.CASCADE, related_name='price_list', db_constraint=False, verbose_name="课程")discount = models.ForeignKey("Discount", on_delete=models.CASCADE, related_name='price_list', db_constraint=False, verbose_name="优惠")class Meta:db_table = "fg_course_activity_price"verbose_name = "课程参与活动的价格表"verbose_name_plural = verbose_namedef __str__(self):return "活动:%s-课程:%s-优惠公式:%s" % (self.activity.name, self.course.name, self.discount.sale)
执行数据迁移
cd luffycityapipython manage.py makemigrationspython manage.py migrate
提交代码版本
# 合并前面的course分支到mastergit checkout mastergit merge feature/coursecd /home/moluo/Desktop/luffycity/git add .git commit -m "feature: 动态价格策略模型的创建"git pushgit checkout -b feature/discount
admin站点配置活动相关模型管理器
courses/admin.py,代码:
from .models import Activity, DiscountType, Discount, CourseActivityPriceclass ActivityModelAdmin(admin.ModelAdmin):"""优惠活动的模型管理器"""list_display = ["id", "name", "start_time", "end_time", "remark"]admin.site.register(Activity, ActivityModelAdmin)class DiscountTypeModelAdmin(admin.ModelAdmin):"""优惠类型的模型管理器"""list_display = ["id", "name", "remark"]admin.site.register(DiscountType, DiscountTypeModelAdmin)class DiscountModelAdmin(admin.ModelAdmin):"""优惠公式的模型管理器"""list_display = ["id", "name","discount_type","condition","sale"]admin.site.register(Discount, DiscountModelAdmin)class CourseActivityPriceModelAdmin(admin.ModelAdmin):"""课程活动价格的模型管理器"""list_display = ["id", "activity", "course","discount"]admin.site.register(CourseActivityPrice, CourseActivityPriceModelAdmin)
因为涉及到时间的转换计算,所以此处我们需要在settings/dev.py中设置时区相关的配置信息。
LANGUAGE_CODE = 'zh-hans'TIME_ZONE = 'Asia/Shanghai'USE_I18N = TrueUSE_L10N = TrueUSE_TZ = False # 关闭时区转换以后,django会默认使用TIME_ZONE作为时区。
添加测试数据
INSERT INTO luffycity.fg_activity (id, name, orders, is_show, is_deleted, created_time, updated_time, start_time, end_time, description, remark) VALUES (1, '路飞学城-5周年庆', 1, 1, 0, '2022-02-17 10:42:54.340893', '2022-02-17 10:42:54.340933', '2022-02-17 00:00:00', '2021-08-01 00:00:00', '<p>5周年庆,各种活动促销内容展示图片</p>', '负责人:组织:外勤:');INSERT INTO luffycity.fg_discount_type (id, name, orders, is_show, is_deleted, created_time, updated_time, remark) VALUES (1, '免费', 1, 1, 0, '2022-02-17 10:43:38.546870', '2022-02-17 10:43:38.546901', null);INSERT INTO luffycity.fg_discount_type (id, name, orders, is_show, is_deleted, created_time, updated_time, remark) VALUES (2, '折扣', 1, 1, 0, '2022-02-17 10:43:49.161997', '2022-02-17 11:19:58.799363', null);INSERT INTO luffycity.fg_discount_type (id, name, orders, is_show, is_deleted, created_time, updated_time, remark) VALUES (3, '减免', 1, 1, 0, '2022-02-17 10:44:05.712935', '2022-02-17 11:41:16.504340', null);INSERT INTO luffycity.fg_discount_type (id, name, orders, is_show, is_deleted, created_time, updated_time, remark) VALUES (4, '限时免费', 1, 1, 0, '2022-02-17 10:44:23.053845', '2022-02-17 10:44:23.053925', null);INSERT INTO luffycity.fg_discount_type (id, name, orders, is_show, is_deleted, created_time, updated_time, remark) VALUES (5, '限时折扣', 1, 1, 0, '2022-02-17 10:44:31.999352', '2022-02-17 10:44:31.999382', null);INSERT INTO luffycity.fg_discount_type (id, name, orders, is_show, is_deleted, created_time, updated_time, remark) VALUES (6, '限时减免', 1, 1, 0, '2022-02-17 10:44:39.100270', '2022-02-17 10:44:39.100305', null);INSERT INTO luffycity.fg_discount (id, name, orders, is_show, is_deleted, created_time, updated_time, `condition`, sale, discount_type_id) VALUES (1, '免费购买', 1, 1, 0, '2022-02-17 10:45:54.027034', '2022-02-17 10:45:54.027079', 0, '0', 4);INSERT INTO luffycity.fg_discount (id, name, orders, is_show, is_deleted, created_time, updated_time, `condition`, sale, discount_type_id) VALUES (2, '九折折扣', 1, 1, 0, '2022-02-17 10:47:12.855454', '2022-02-17 11:32:27.148655', 1, '*0.9', 2);INSERT INTO luffycity.fg_discount (id, name, orders, is_show, is_deleted, created_time, updated_time, `condition`, sale, discount_type_id) VALUES (3, '课程减免100', 1, 1, 0, '2022-02-17 11:40:44.499026', '2022-02-17 11:40:44.499060', 300, '-100', 3);INSERT INTO luffycity.fg_course_activity_price (id, name, orders, is_show, is_deleted, created_time, updated_time, activity_id, course_id, discount_id) VALUES (1, '九折-3天Typescript', 1, 1, 0, '2022-02-17 10:48:12.600755', '2022-02-17 10:48:12.600801', 1, 2, 2);INSERT INTO luffycity.fg_course_activity_price (id, name, orders, is_show, is_deleted, created_time, updated_time, activity_id, course_id, discount_id) VALUES (2, '免费送课', 1, 1, 0, '2022-02-17 11:36:34.192896', '2022-02-17 11:36:34.192941', 1, 1, 1);INSERT INTO luffycity.fg_course_activity_price (id, name, orders, is_show, is_deleted, created_time, updated_time, activity_id, course_id, discount_id) VALUES (3, '减免课程', 1, 1, 0, '2022-02-17 11:40:49.240245', '2022-02-17 11:40:49.240276', 1, 3, 3);
提交代码版本
cd /home/moluo/Desktop/luffycity/git add .git commit -m "feature: admin站点配置活动相关的模型管理器并添加测试数据"git push --set-upstream origin feature/discount
在课程模型中计算课程优惠信息
courses/models.py,代码:
class Course(BaseModel):course_type = ((0, '付费购买'),(1, '会员专享'),(2, '学位课程'),)level_choices = ((0, '初级'),(1, '中级'),(2, '高级'),)status_choices = ((0, '上线'),(1, '下线'),(2, '预上线'),)# course_cover = models.ImageField(upload_to="course/cover", max_length=255, verbose_name="封面图片", blank=True, null=True)course_cover = StdImageField(variations={'thumb_1080x608': (1080, 608), # 高清图'thumb_540x304': (540, 304), # 中等比例,'thumb_108x61': (108, 61, True), # 小图(第三个参数表示保持图片质量),}, max_length=255, delete_orphans=True, upload_to="course/cover", null=True, verbose_name="封面图片",blank=True)course_video = models.FileField(upload_to="course/video", max_length=255, verbose_name="封面视频", blank=True, null=True)course_type = models.SmallIntegerField(choices=course_type,default=0, verbose_name="付费类型")level = models.SmallIntegerField(choices=level_choices, default=1, verbose_name="难度等级")description = RichTextUploadingField(null=True, blank=True, verbose_name="详情介绍")pub_date = models.DateField(auto_now_add=True, verbose_name="发布日期")period = models.IntegerField(default=7, verbose_name="建议学习周期(day)")attachment_path = models.FileField(max_length=1000, blank=True, null=True, verbose_name="课件路径")attachment_link = models.CharField(max_length=1000, blank=True, null=True, verbose_name="课件链接")status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="课程状态")students = models.IntegerField(default=0, verbose_name="学习人数")lessons = models.IntegerField(default=0, verbose_name="总课时数量")pub_lessons = models.IntegerField(default=0, verbose_name="已更新课时数量")price = models.DecimalField(max_digits=10,decimal_places=2, verbose_name="课程原价",default=0)recomment_home_hot = models.BooleanField(default=False, verbose_name="是否推荐到首页新课栏目")recomment_home_top = models.BooleanField(default=False, verbose_name="是否推荐到首页必学栏目")direction = models.ForeignKey("CourseDirection", related_name="course_list", on_delete=models.DO_NOTHING, null=True, blank=True, db_constraint=False, verbose_name="学习方向")category = models.ForeignKey("CourseCategory", related_name="course_list", on_delete=models.DO_NOTHING, null=True, blank=True, db_constraint=False, verbose_name="课程分类")teacher = models.ForeignKey("Teacher", related_name="course_list", on_delete=models.DO_NOTHING, null=True, blank=True, db_constraint=False, verbose_name="授课老师")class Meta:db_table = "fg_course_info"verbose_name = "课程信息"verbose_name_plural = verbose_namedef course_cover_small(self):if self.course_cover:return mark_safe(f'<img style="border-radius: 0%;" src="{self.course_cover.thumb_108x61.url}">')return ""course_cover_small.short_description = "封面图片(108x61)"course_cover_small.allow_tags = Truecourse_cover_small.admin_order_field = "course_cover"def course_cover_medium(self):if self.course_cover:return mark_safe(f'<img style="border-radius: 0%;" src="{self.course_cover.thumb_540x304.url}">')return ""course_cover_medium.short_description = "封面图片(540x304)"course_cover_medium.allow_tags = Truecourse_cover_medium.admin_order_field = "course_cover"def course_cover_large(self):if self.course_cover:return mark_safe(f'<img style="border-radius: 0%;" src="{self.course_cover.thumb_1080x608.url}">')return ""course_cover_large.short_description = "封面图片(1080x608)"course_cover_large.allow_tags = Truecourse_cover_large.admin_order_field = "course_cover"@propertydef discount(self):"""通过计算获取当前课程的折扣优惠相关的信息"""# 获取折扣优惠相关的信息now_time = datetime.now() # 活动__结束时间 > 当前时间 and 活动__开始时间 < 当前时间(29)# 获取当前课程参与的最新活动记录last_activity_log = self.price_list.filter(activity__end_time__gt=now_time,activity__start_time__lt=now_time).order_by("-id").first()type_text = "" # 优惠类型的默认值price = -1 # 优惠价格expire = 0 # 优惠剩余时间if last_activity_log:# 获取优惠类型的提示文本type_text = last_activity_log.discount.discount_type.name# 获取限时活动剩余时间戳[单位:s]expire = last_activity_log.activity.end_time.timestamp() - now_time.timestamp()# 判断当前课程的价格是否满足优惠条件course_price = float(self.price)condition_price = float(last_activity_log.discount.condition)if course_price >= condition_price:# 计算本次课程参与了优惠以后的价格sale = last_activity_log.discount.saleprint(f"{type_text}-{sale}")if sale == "0":# 免费,则最终价格为0price = 0elif sale[0] == "*":# 折扣price = course_price * float(sale[1:])elif sale[0] == "-":# 减免price = course_price - float(sale[1:])price = float(f"{price:.2f}")data = {}if type_text:data["type"] = type_textif expire > 0:data["expire"] = expireif price != -1:data["price"] = pricereturn datadef discount_json(self):# 必须转成字符串才能保存到es中。所以该方法提供给es使用的。return json.dumps(self.discount)@propertydef can_free_study(self):"""是否允许试学"""lesson_list = self.lesson_list.filter(is_delete=False, is_show=True, free_trail=True).order_by("orders").all()return len(lesson_list) > 0
给Elasticsearch重建索引
cd /home/moluo/Desktop/luffycity/luffycityapipython manage.py rebuild_index
客户端课程列表页展示课程优惠价格时增加免费的判断逻辑
views/Course.vue,代码:
<p class="two clearfix"><span class="price l red bold" v-if="course_info.discount.price>=0">¥{{parseFloat(course_info.discount.price).toFixed(2)}}</span><span class="price l red bold" v-else>¥{{parseFloat(course_info.price).toFixed(2)}}</span><span class="origin-price l delete-line" v-if="course_info.discount.price>=0">¥{{parseFloat(course_info.price).toFixed(2)}}</span><span class="add-shop-cart r"><img class="icon imv2-shopping-cart" src="../assets/cart2.svg">加购物车</span></p>
客户端课程详情页展示真实课程的价格
views/Info.vue,代码:
<p class="course-price" v-if="course.info.discount.price >= 0"><span>活动价</span><span class="discount">¥{{parseFloat(course.info.discount.price).toFixed(2)}}</span><span class="original">¥{{parseFloat(course.info.price).toFixed(2)}}</span></p>
提交代码版本
git add .git commit -m "feature: 课程优惠活动的实现"git push
