day85:luffy:購物車根據有效期不同切換價格&購物車刪除操作&價格結算&訂單頁面前戲

Poke發表於2020-11-09

目錄

1.購物車有效期切換

2.根據有效期不同切換價格

3.購物車刪除操作

4.價格結算

5.訂單頁面-初始化

1.購物車有效期切換

1.關於有效期表結構的設計

1.course/models.py

day85:luffy:購物車根據有效期不同切換價格&購物車刪除操作&價格結算&訂單頁面前戲
class CourseExpire(BaseModel):
    """課程有效期模型"""
    # 後面可以在資料庫把course和expire_time欄位設定為聯合索引
    course = models.ForeignKey("Course", related_name='course_expire', on_delete=models.CASCADE, verbose_name="課程名稱")

    # 有效期限,天數
    expire_time = models.IntegerField(verbose_name="有效期", null=True, blank=True, help_text="有效期按天數計算")

    # 一個月有效等等
    expire_text = models.CharField(max_length=150, verbose_name="提示文字", null=True, blank=True)
    # 每個有效期的價格
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="課程價格", default=0)

    class Meta:
        db_table = "ly_course_expire"
        verbose_name = "課程有效期"
        verbose_name_plural = verbose_name

    def __str__(self):
        return "課程:%s,有效期:%s,價格:%s" % (self.course, self.expire_text, self.price)
課程有效期表結構設計

在course表中的price欄位加一個提示文字

price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="課程原價", default=0,help_text='如果填寫的價格為0,那麼表示當前課程在購買的時候,沒有永久有效的期限。')

2.插入一些測試資料

INSERT INTO `ly_course_expire`
(`id`,`orders`,`is_show`,`is_deleted`,`created_time`,`updated_time`,`expire_time`,`expire_text`,`course_id`,`price`)
VALUES
(1,1,1,0,'2019-08-19 02:05:22.368823','2019-08-19 02:05:22.368855',30,'一個月有效',1,398.00),
(2,2,1,0,'2019-08-19 02:05:37.397205','2019-08-19 02:05:37.397233',60,'2個月有效',1,588.00),
(3,3,1,0,'2019-08-19 02:05:57.029411','2019-08-19 02:05:57.029440',180,'半年內有效',1,1000.00),
(4,4,1,0,'2019-08-19 02:07:29.066617','2019-08-19 02:08:29.156730',3,'3天內有效',3,0.88),
(5,3,1,0,'2019-08-19 02:07:46.120827','2019-08-19 02:08:18.652452',30,'1個月有效',3,188.00),
(6,3,1,0,'2019-08-19 02:07:59.876421','2019-08-19 02:07:59.876454',60,'2個月有效',3,298.00);

3.xadmin註冊

course/adminx.py

from .models import CourseExpire
class CourseExpireModelAdmin(object):
    """商品有效期模型"""
    pass
xadmin.site.register(CourseExpire, CourseExpireModelAdmin)

2.課程有效期-後端介面

course/models.py

class Course:    
    # 獲取課程有效期
    def get_expire(self):
        
        # 課程表和課程有效期表時一對多的關係 反向查詢
        expire_list = self.course_expire.all() # 查詢到當前課程所擁有的有效期種類
        data = []
        for expire in expire_list:
            data.append({
                'id':expire.id,
                'expire_text':expire.expire_text, # 有效期的那個文字 比如'三個月有效'
                'price':expire.price, # 有效期對應的價格
            })

        # 當價格為0時,沒有永久有效這一項,其他的都有
        if self.price > 0:
            data.append({
                'id': 0,
                'expire_text': '永久有效',
                'price': self.price,
            })

        return data

cart/views.py

class AddCartView(ViewSet):

    def cart_list(self,request):
          
        try:
            ......
            cart_data_list.append({
               ......
                'expire_list':course_obj.get_expire(),
               ......
            })
        except Exception:
           ......
        return Response({'msg':'xxx','cart_data_list':cart_data_list})

3.課程有效期-前端

cartitem.vue

<div class="cart_column column_3">
    <el-select v-model="cart.expire_id" size="mini" placeholder="請選擇購買有效期" class="my_el_select">
        <el-option :label="expire.expire_text" :value="expire.id" :key="expire.id" v-for="(expire,expire_index) in cart.expire_list"></el-option>
    </el-select>
</div>

2.根據有效期不同切換價格

1.有效期切換更改redis中資料

cart/views.py

class AddCartView:
        def change_expire(self,request):

            user_id = request.user.id
            course_id = request.data.get('course_id')
            expire_id = request.data.get('expire_id')
            
           # 檢驗課程id是否有效
            try:
                course_obj = models.Course.objects.get(id=course_id)
            except:
                return Response({'msg':'課程不存在'},status=status.HTTP_400_BAD_REQUEST)
            # 檢驗有效期id是否有效
            try:
                if expire_id > 0:
                 expire_object = models.CourseExpire.objects.get(id=expire_id)
            except:
                return Response({'msg':'課程有效期不存在'},status=status.HTTP_400_BAD_REQUEST)

            # 計算xx課程xx有效期的真實價格
            real_price = course_obj.real_price(expire_id)

            # 更改了新的有效期,要將更改之後的有效期存放到redis中
            conn = get_redis_connection('cart')
            conn.hset('cart_%s' % user_id, course_id, expire_id)

            return Response({'msg':'切換成功!', 'real_price': real_price})

cart/urls.py

urlpatterns = [
    ......
    path('expires/', views.AddCartView.as_view({'put':'change_expire'}))
]

2.有效期切換讓頁面價格變化

course/models.py

class Course:
        def real_price(self,expire_id=0): # 增加expire_id=0引數 讓真實價格根據不同的有效期顯示不同的價格
            price = float(self.price)
            '''
            1.如果不是永久有效(expire_id>0),那就從course_expire獲取該有效期對應的價格
            2.如果是永久有效(expire_id=0),那麼真實價格就等於它原來的價格
            '''
            if expire_id > 0:
                expire_obj = self.course_expire.get(id=expire_id)
                price = float(expire_obj.price)

            r_price = price
            a = self.activity()
            if a:
                sale = a[0].discount.sale
                condition_price = a[0].discount.condition

                # 限時免費
                if not sale.strip():
                    r_price = 0

                # 限時折扣  *0.5
                elif '*' in sale.strip():
                    if price >= condition_price:
                        _, d = sale.split('*')
                        r_price = price * float(d)
                # 限時減免  -100
                elif sale.strip().startswith('-'):
                    if price >= condition_price:
                        _, d = sale.split('-')
                        r_price = price - float(d)

                elif '滿' in sale:
                    if price >= condition_price:
                        l1 = sale.split('\r\n')
                        dis_list = []  #10 50  25
                        for i in l1:
                            a, b = i[1:].split('-')

                            #400
                            if price >= float(a):
                                dis_list.append(float(b))

                        max_dis = max(dis_list)
                        r_price = price - max_dis

            return r_price

cartitem.vue

// js
 watch:{
      ......
      // 當使用者選擇的課程有效期發生變化時(在前端下拉框選擇了別的有效期)
      'cart.expire_id':function (){
        let token = localStorage.token || sessionStorage.token;

          this.$axios.put(`${this.$settings.Host}/cart/expires/`,{
            // 將課程id和課程對應的有效期id提交到後端
            course_id: this.cart.course_id,
            expire_id:this.cart.expire_id,
          },{
            headers:{
              'Authorization':'jwt ' + token
            }

      }
      ).then((res)=>{
        this.$message.success(res.data.msg);
              
        // 修改有效期成功,將真實價格返回給前端
        this.cart.real_price = res.data.real_price;
              
        // 修改有效期成功,觸發cart父元件重新計算總價格的事件
        this.$emit('change_expire_handler',) 

          }).catch((error)=>{
            this.$message.error(error.response.data.msg)
          })
    }
},

cart.vue

<div class="cart_course_list">
    <CartItem v-for="(value,index) in cart_data_list" :key="index" :cart="value" @cal_t_p="cal_total_price" @change_expire_handler="cal_total_price" @delete_course_handler="delete_c(index)"></CartItem>

</div>

3.頁面重新整理 應該重置有效期

cart/views.py

class AddCartView:
    def cart_list(self,request):

        user_id = request.user.id

        conn = get_redis_connection('cart')

        conn.delete('selected_cart_%s' % user_id)
        ret = conn.hgetall('cart_%s' % user_id)  #dict {b'1': b'0', b'2': b'0'}
        cart_data_list = []

        try:
            for cid, eid in ret.items():
                course_id = cid.decode('utf-8')
                
                # 當使用者檢視購物車頁面時->預設顯示永久有效
                conn.hset('cart_%s' % user_id, course_id, 0)
                expire_id = 0
                
                course_obj = models.Course.objects.get(id=course_id)

                cart_data_list.append({
                    'course_id':course_obj.id,
                    'name':course_obj.name,
                    'course_img':contains.SERVER_ADDR + course_obj.course_img.url ,
                    'price':course_obj.price,
                    'real_price':course_obj.real_price(),
                    'expire_id':expire_id,
                    'expire_list':course_obj.get_expire(),
                    'selected':False,  # 預設沒有勾選
                })
        except Exception:
            logger.error('獲取購物車資料失敗')
            return Response({'msg':'後臺資料庫出問題了,請聯絡管理員'},status=status.HTTP_507_INSUFFICIENT_STORAGE)

        return Response({'msg':'xxx','cart_data_list':cart_data_list})
    

3.購物車刪除操作

cartitem.vue

<div class="cart_column column_4" id="delete" @click="delete_course">刪除</div>
delete_course(){
        let token = localStorage.token || sessionStorage.token;

          this.$axios.delete(`${this.$settings.Host}/cart/add_cart/`,{
            params:{
              course_id: this.cart.course_id,  // request.query_params.get('course_id')
            },
            headers:{
              'Authorization':'jwt ' + token
            }
          })
        .then((res)=>{
          this.$message.success(res.data.msg);
              
          // 當使用者要刪除購物車中的某條課程記錄時 執行Cart父元件的刪除課程事件
          this.$emit('delete_course_handler')
        })
        .catch((error)=>{
          this.$message.error(error.response.data.msg);
        })
      }

1.後端redis刪除

cart/urls.py

urlpatterns = [
    path('add_cart/', views.AddCartView.as_view(
        {'post':'add','get':'cart_list',
         'patch':'change_select','put':'cancel_select',
         'delete':'delete_course'
         })),
    path('expires/', views.AddCartView.as_view({'put':'change_expire'}))

]

cart/views.py

class AddCartView:
    def delete_course(self,request):
        user_id = request.user.id
        course_id = request.query_params.get('course_id')
        
        conn = get_redis_connection('cart')
        pipe = conn.pipeline()
        pipe.hdel('cart_%s' % user_id, course_id)
        pipe.srem('selected_cart_%s' % user_id, course_id)
        pipe.execute()

        return Response({'msg':'刪除成功'})

2.前端同步實現刪除效果

使用者刪除一條購物車資料 後端已經刪除了 但是前端頁面也要同步刪除

Cart.vue

<div class="cart_course_list">
    <CartItem v-for="(value,index) in cart_data_list" :key="index" :cart="value" @cal_t_p="cal_total_price" @change_expire_handler="cal_total_price" @delete_course_handler="delete_c(index)"></CartItem>

</div>
delete_c(index){
        this.cart_data_list.splice(index,1)
        this.cal_total_price() // 刪除之後 重新觸發計算總價格的方法
      }

Cartitem.vue

delete_course(){
        let token = localStorage.token || sessionStorage.token;

          this.$axios.delete(`${this.$settings.Host}/cart/add_cart/`,{
            params:{
              course_id: this.cart.course_id, 
            },
            headers:{
              'Authorization':'jwt ' + token
            }
          })
        .then((res)=>{ // 刪除時 觸發Cart父元件的刪除事件
          this.$message.success(res.data.msg);
          this.$emit('delete_course_handler')
        })
        .catch((error)=>{
          this.$message.error(error.response.data.msg);
        })
      }
  }

4.價格結算

1.價格結算頁面-準備工作

1.結算初始頁面

<!-- 結算頁面初始介面 -->

2.配置路由

index.js

import Vue from 'vue'
import Order from "@/components/Order"

Vue.use(Router)
export default new Router({
  mode:'history',
  routes: [
    ......
    {
      path: '/order/',   
      component: Order
    },
  ]
})

3.點選去結算按鈕 看到頁面

cart.vue

<span class="goto_pay"><router-link to="/order/">去結算</router-link></span>          

2.價格結算頁面-後端

cart/urls.py

urlpatterns = [
    ...
    path('expires/', views.AddCartView.as_view({'get':'show_pay_info'}))

]

cart/views.py

def show_pay_info(self,request):
    
    user_id = request.user.id
    
    conn = get_redis_connection('cart')
    
    # 獲取使用者購物車選中的所有課程id
    select_list = conn.smembers('selected_cart_%s' % user_id)
    data = []

    # 獲取使用者購物車的課程id:有效期id
    ret = conn.hgetall('cart_%s' % user_id)  # dict {b'1': b'0', b'2': b'0'}

    
    for cid, eid in ret.items():
        expire_id = int(eid.decode('utf-8'))
        if cid in select_list: # 如果課程是被勾選的

            course_id = int(cid.decode('utf-8'))
            # 獲取這個'被勾選的課程'model物件
            course_obj = models.Course.objects.get(id=course_id)
           
            # 如果課程的有效期不是永久有效
            if expire_id > 0:
                expire_obj = models.CourseExpire.objects.get(id=expire_id)
                data.append({
                    'course_id':course_obj.id,
                    'name':course_obj.name,
                    'course_img':contains.SERVER_ADDR + course_obj.course_img.url ,
                    'real_price':course_obj.real_price(expire_id),
                    'expire_text':expire_obj.expire_text,
                })
                
            # 如果課程的有效期是永久有效
            else:
                data.append({
                    'course_id': course_obj.id,
                    'name': course_obj.name,
                    'course_img': contains.SERVER_ADDR + course_obj.course_img.url,
                    'real_price': course_obj.real_price(expire_id),
                    'expire_text': '永久有效',
                })


    return Response({'data':data})

3.價格結算頁面-前端

order.vue

// js
methods: {
      get_order_data(){
        let token = localStorage.token || sessionStorage.token;
        this.$axios.get(`${this.$settings.Host}/cart/expires/`,{
          headers:{
              'Authorization':'jwt ' + token
            }
        }).then((res)=>{
          this.course_list = res.data.data;
        })
      },
<!-- html --> 
<div class="cart-item" v-for="(course,index) in course_list">
          <el-row>
             <el-col :span="2" class="checkbox">&nbsp;&nbsp;</el-col>
             <el-col :span="10" class="course-info">
               <img :src="course.course_img" alt="">
                <span>{{course.name}}</span>
             </el-col>
             <el-col :span="8"><span>{{course.expire_text}}</span></el-col>
             <el-col :span="4" class="course-price">¥{{course.real_price}}</el-col>
           </el-row>
        </div>

4.切換支付方式-微信/支付寶

其實就是幾張圖片 點選顯示圖片而已

Order.vue

<el-col :span="8">
    <span class="alipay"  v-if="pay_type===1"><img src="../../static/img/alipay2.png"  alt=""></span>
    <span class="alipay" @click="pay_type=1"  v-else><img src="../../static/img/alipay.png" alt=""></span>
    <span class="alipay wechat" v-if="pay_type===2"><img src="../../static/img/wechat2.png" alt="" ></span>
    <span class="alipay wechat"  @click="pay_type=2" v-else><img src="../../static/img/wechat.png" alt=""></span>

</el-col>

5.訂單頁面-初始化

order/models.py

day85:luffy:購物車根據有效期不同切換價格&購物車刪除操作&價格結算&訂單頁面前戲
from django.db import models

# Create your models here.


from lyapi.utils.models import BaseModel
from users.models import User
from course.models import Course
class Order(BaseModel):
    """訂單模型"""
    status_choices = (
        (0, '未支付'),
        (1, '已支付'),
        (2, '已取消'),
        (3, '超時取消'),
    )
    pay_choices = (
        (0, '支付寶'),
        (1, '微信支付'),
    )
    order_title = models.CharField(max_length=150,verbose_name="訂單標題")
    total_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="訂單總價", default=0)
    real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="實付金額", default=0)
    order_number = models.CharField(max_length=64,verbose_name="訂單號")
    order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="訂單狀態")
    pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式")
    credit = models.IntegerField(default=0, verbose_name="使用的積分數量")
    coupon = models.IntegerField(null=True, verbose_name="使用者優惠券ID")
    order_desc = models.TextField(max_length=500, verbose_name="訂單描述",null=True,blank=True)
    pay_time = models.DateTimeField(null=True, verbose_name="支付時間")
    user = models.ForeignKey(User, related_name='user_orders', on_delete=models.DO_NOTHING,verbose_name="下單使用者")

    class Meta:
        db_table="ly_order"
        verbose_name= "訂單記錄"
        verbose_name_plural= "訂單記錄"

    def __str__(self):
        return "%s,總價: %s,實付: %s" % (self.order_title, self.total_price, self.real_price)


class OrderDetail(BaseModel):
    """
    訂單詳情
    """
    order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, verbose_name="訂單ID")
    course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, verbose_name="課程ID")
    expire = models.IntegerField(default='0', verbose_name="有效期週期",help_text="0表示永久有效")
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="課程原價")
    real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="課程實價")
    discount_name = models.CharField(max_length=120,default="",verbose_name="優惠型別")

    class Meta:
        db_table="ly_order_detail"
        verbose_name= "訂單詳情"
        verbose_name_plural= "訂單詳情"

    def __str__(self):
        return "%s" % (self.course.name)
訂單表結構設計

order/adminx.py

import xadmin
from .models import Order
class OrderModelAdmin(object):
    """訂單模型管理類"""
    pass

xadmin.site.register(Order, OrderModelAdmin)


from .models import OrderDetail
class OrderDetailModelAdmin(object):
    """訂單詳情模型管理類"""
    pass

xadmin.site.register(OrderDetail, OrderDetailModelAdmin)

order/__init__.py

default_app_config = "order.apps.OrderConfig"

order/app.py

from django.apps import AppConfig


class OrderConfig(AppConfig):
    name = 'order'
    verbose_name = '訂單管理'

order/urls.py

from django.urls import path,re_path
from . import views

urlpatterns = [
    path('add_money/',views.OrderView.as_view(),)

]

order/views.py

from django.shortcuts import render
from rest_framework.generics import CreateAPIView
# Create your views here.
from . import models
from .serializers import OrderModelSerializer
from rest_framework.permissions import IsAuthenticated

class OrderView(CreateAPIView):
    queryset = models.Order.objects.filter(is_deleted=False,is_show=True)
    serializer_class = OrderModelSerializer
    permission_classes = [IsAuthenticated, ]

order/serializers.py

import datetime
from rest_framework import serializers
from . import models
from django_redis import get_redis_connection
from course.models import Course
from course.models import CourseExpire
from django.db import transaction


class OrderModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Order
        fields = ['id', 'order_number', 'pay_type', 'coupon', 'credit']
        
        '''
        1.使用者在訂單頁面需要提交過來的資料:
            支付型別:微信/支付寶
            優惠券
            積分
        2.使用者提交資料成功後,前端頁面需要返回過來的資料:
            id
            訂單號
        '''
        extra_kwargs = {
            'id':{'read_only':True},
            'order_number':{'read_only':True},
            'pay_type':{'write_only':True},
            'coupon':{'write_only':True},
            'credit':{'write_only':True},
        }

    def validate(self, attrs):

        # 獲取使用者使用的支付方式
        pay_type = int(attrs.get('pay_type',0))  #
        
        # 驗證使用者使用的支付方式是否是支付寶或微信中的一種
        if pay_type not in [i[0] for i in models.Order.pay_choices]:
            raise serializers.ValidationError('支付方式不對!')

            
        # todo 優惠券校驗,看看是否過期了等等


        # todo 積分上限校驗

        return attrs

    def create(self, validated_data):
        try:
            # 生成訂單號  [日期,使用者id,自增資料]
            current_time = datetime.datetime.now()
            now = current_time.strftime('%Y%m%d%H%M%S')
            user_id = self.context['request'].user.id
            
            conn = get_redis_connection('cart')
            num = conn.incr('num')
            
            # 生成一個唯一的訂單號
            order_number = now + "%06d" % user_id + "%06d" % num

            with transaction.atomic():  # 新增事務

                # 生成訂單
                order_obj = models.Order.objects.create(**{
                    'order_title': '31期訂單',
                    'total_price': 0,
                    'real_price': 0,
                    'order_number': order_number,
                    'order_status': 0,
                    'pay_type': validated_data.get('pay_type', 0),
                    'credit': 0,
                    'coupon': 0,
                    'order_desc': '女朋友',
                    'pay_time': current_time,
                    'user_id': user_id,
                    # 'user':user_obj,
                })

                select_list = conn.smembers('selected_cart_%s' % user_id)

                ret = conn.hgetall('cart_%s' % user_id)  # dict {b'1': b'0', b'2': b'0'}

                for cid, eid in ret.items():
                    expire_id = int(eid.decode('utf-8'))
                    if cid in select_list:

                        course_id = int(cid.decode('utf-8'))
                        course_obj = Course.objects.get(id=course_id)
                        if expire_id > 0:
                            expire_text = CourseExpire.objects.get(id=expire_id).expire_text

                        # 生成訂單詳情
                        models.OrderDetail.objects.create(**{
                            'order': order_obj,
                            'course': course_obj,
                            'expire': expire_id,
                            'price': course_obj.price,
                            'real_price': course_obj.real_price(expire_id),
                            'discount_name': course_obj.discount_name(),
                        })
            # print('xxxxx')
        except Exception:
            raise models.Order.DoesNotExist

        return order_obj

 

相關文章