zoukankan      html  css  js  c++  java
  • day83:luffy:添加购物车&导航栏购物车数字显示&购物车页面展示

    目录

    1.添加购物车+验证登录状态

    2.右上方购物车图标的小红圆圈数字

    3.Vuex

    4.购物车页面展示-后端接口

    5.购物车页面展示-前端

    6.解决一个购物车数量显示混乱的bug

    1.添加购物车+验证登录状态

    1.添加购物车的整体思想

    购物车数据要存到redis中的:要存用户id,课程id

    用户在课程详情页面点击了加入购物车:

    拿到当前课程的课程id 到数据库把课程id所对应的信息(需要在购物车显示的)加工成一个字典,然后json序列化成字符串保存到redis中

    如何实现点击添加购物车,将购物车数据添加到redis中??????

    2.添加购物车-后端接口

    1.创建一个cart应用,并配置INSTALLAPP

    python3 ../../ manage.py startapp cart

    2.总路由中添加cart

    # lyapi/urls.py
    path('cart/', include("cart.urls") ),

    3.cart/urls.py

    from django.urls import path,re_path
    from . import views
    
    urlpatterns = [
        path('add_cart/', views.AddCartView.as_view({'post':'add'}))
    
    ]

    4.cart/views.py

    from rest_framework.viewsets import Viewset
    from django_redis import get_redis_connection
    from course import models
    from rest_framework.response import Response
    
    
    class AddCartView(ViewSet):
        
        def add(self,request):
            course_id = request.data.get('course_id')
            user_id = 1 # 先把用户id写死
            
            # 去redis里存数据
            conn = get_redis_connection('cart')
            
            # 校验一下课程id是否合法
            try:
                models.Course.objects.get(id=course_id)
            except:
                return Response({'msg':'课程不存在'},status=400)
          
            '''选择用集合的数据类型去存储'''
            conn.sadd('cart_%s' % user_id,course_id)
            
            # vheader右方的购物车小红数字显示
            cart_length = conn.scard('cart_%s' % user_id) # 获取商品数量
            return Response({'msg':'添加成功''cart_length',cart_length})

    5.单独给购物车使用一个redis库

    # dev.py
    CACHES = {
        ......
        "cart":{
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": "redis://127.0.0.1:6379/3",
            "OPTIONS": {
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
            },
        }
    }

    3.添加购物车-前端

    前端点击添加购物车,向后端发送请求

    <!-- html -->
    <div class="add-cart" @click="addCart"><img src="/static/img/cart-yellow.svg" alt="">加入购物车</div>

    注意:添加购物车要验证用户是否已经登录

    axios发送的是异步请求,setting.js里的check_login()函数和Detail.vue中的addCart是同步执行的,但是addCart添加购物车时需要验证登录状态的,

    会出现:我这边还没有验证token呢,添加购物车那边就已经执行到检测token的代码位置了。

    所以现在我们需要将两个请求变为同步请求,让token验证之后再进行别的操作

    异步改成同步还是比较麻烦的,所以我们直接将验证token的操作写在addCart添加购物车方法里

    // js     
    addCart(){
    
            // 获取前端存储的token值
            let token = localStorage.token || sessionStorage.token;
           
            // 如果token值存在
            if (token){
              
              // 验证token
              this.$axios.post(`${this.$settings.Host}/users/verify/`,{
                  token:token,
                }).then((res)=>{
    
              // 验证通过,可以添加购物车
                this.$axios.post(`${this.$settings.Host}/cart/add_cart/`,{
                    // 获取课程id
                    course_id:this.course_id,
                  }).then((res)=>{
                    // 添加购物车成功,打印添加成功的信息
                    this.$message.success(res.data.msg);
                  })
               // 验证没有通过(token错误或者token过期) 提示用户让用户去登录
                }).catch((error)=>{
    
                  this.$confirm('您还没有登录!!!?', '31s', {
                    confirmButtonText: '去登录',
                    cancelButtonText: '取消',
                    type: 'warning'
                  }).then(() => {
                    this.$router.push('/user/login');
                  })
                  
                  // 将过期的token清理掉
                  sessionStorage.removeItem('token');
                  sessionStorage.removeItem('username');
                  sessionStorage.removeItem('id');
                  localStorage.removeItem('token');
                  localStorage.removeItem('username');
                  localStorage.removeItem('id');
                })
    
    
            }
            // token获取不到
            else {
              this.$confirm('您还没有登录!!!?', '31s', {
                    confirmButtonText: '去登录',
                    cancelButtonText: '取消',
                    type: 'warning'
                  }).then(() => {
                    this.$router.push('/user/login');
                  })
            }
    
    
    
          },

    2.右上方购物车图标的小红圆圈数字

    我们在后端已经将购物车的长度返回了,在前端我们就可以拿到购物车的长度

    // Detail.vue 
    this.$axios.post(`${this.$settings.Host}/cart/add_cart/`,{
                    // 获取课程id
                    course_id:this.course_id,
                  }).then((res)=>{
                    // 添加购物车成功,打印添加成功的信息
                    this.$message.success(res.data.msg);
          
                     // 获取到后端发送过来的购物车长度
                     this.cart_length = res.data.cart_length
                  })

    现在就会有一个问题,我们这个cart_length数据属性是在Detail组件里面的,但是那个右上方购物车小红圆圈是在vheader组件里面的。不同组件之间的数据不是互通的

    第一种思路:vheader组件是detail组件的子组件,我们可以通过vue的父子传值来实现。

    那问题就来了

    如果我们访问实战课页面 也就是/course,在course组件是不也要显示那个购物车小红圆圈?

    但是在course组件我们根本就没有去获取购物车的长度。所以红圆圈数字根本显示不出来。所以父子传值这个思路行不通。

    3.Vuex

    因为对于一些数据,需要在多个组件中即时共享,所以根据上述的问题,我们引出vuex

    1.安装Vuex

    npm install -S vuex

    2.把vuex注册到vue中

    在src目录下创建store目录,并在store目录下创建一个index.js文件,index.js文件代码

    // store/index.js
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    
    Vue.use(Vuex)
    
      export default new Vuex.Store({
      state: { // 数据仓库,类似vue里面的data
        cart_length: 0 // 购物车数据
      },
       
      mutations: { // 数据操作方法,类似vue里面的methods
        add_cart (state, cart_length) {
          state.cart_length = cart_length; // 修改购物车的商品总数
        }
      }
    })

    3.挂载store对象

    把上面index.js中创建的store对象注册到main.js的vue中。

    // main.js
    
    import Vue from 'vue'
    import App from './App'
    import router from './router'
    import store from './store';  // 引入
    
    
    new Vue({
      el: '#app',
      router,
      store,    // 挂载
      components: { App },
      template: '<App/>'
    })

    4.Vheader组件读取store的数据(读取购物车长度)

    在Vheader.vue头部组件中,直接就可以读取store里面的数据

    <router-link to="/">
        <b>{{$store.state.cart_length}}</b>
        <img src="@/assets/shopcart.png" alt="">
        <span>购物车 </span>
    </router-link>

    5.Detail组件修改store的数据(修改购物车长度)

    当用户点击添加购物车时,触发addCart中的post方法,将购物车的长度进行修改

    this.$axios.post(`${this.$settings.Host}/cart/add_cart/`,{
        course_id:this.course_id,
    }).then((res)=>{
        this.$message.success(res.data.msg);
        // 从后端获取到的购物车长度不存在当前组件的数据属性中了,而是存到vuex中
        this.$store.commit('add_cart', res.data.cart_length) ; // commit用来触发mutation中声明的方法
    })

    6.关于页面刷新,vuex数据丢失问题

    问题:vuex中的数据是存放在内存中的,页面一刷新,vuex中的数据就没有了

    解决方式:用户点击刷新时,我们可以监听用户刷新的这个动作,可以在刷新之前对页面做一些动作。

    当点击刷新时,我们先把数据存到sessionStorage或localStorage中,

    页面刷新完成之后,再把数据取回来放到vuex中。这样的话就可以做到页面刷新了,数据也没有丢。

    1.点击刷新,将数据存到sessionStorage中

    // app.vue
    <script>
    export default {
      name: 'App',
      created() {
        // 页面刷新之前把cart_length数据存到了sessionStorage中
        window.addEventListener('beforeunload',()=>{
          console.log('页面要刷新啦!!!,赶紧保存数据!!!!');
          sessionStorage.setItem('cart_length',this.$store.state.cart_length);
    
        })
      }
    }
    </script>

    2..页面刷新完成之后,将数据从sessionStorage取出来放到vuex中

    // vheader.vue
     created() {
    
    
        if (this.$store.state.cart_length === 0) {
          let cart_length = sessionStorage.getItem('cart_length');
          this.$store.commit('add_cart', cart_length);
        }
      },

    7.关于redis的异常捕获

    为了保证系统的日志记录可以跟进redis部分的,我们还可以在之前自定义异常处理中增加关于 redis的异常捕获

    # utils/exceptions.py
    from rest_framework.views import exception_handler
    
    from django.db import DatabaseError
    from rest_framework.response import Response
    from rest_framework import status
    
    from redis import RedisError # 引入redis异常
    
    import logging
    logger = logging.getLogger('django')
    
    
    def custom_exception_handler(exc, context):
        """
        自定义异常处理
        :param exc: 异常类
        :param context: 抛出异常的上下文
        :return: Response响应对象
        """
        # 调用drf框架原生的异常处理方法
        response = exception_handler(exc, context)
    
        if response is None:
            view = context['view']  # 错误出现的那个函数或者方法
            if isinstance(exc, DatabaseError) or isinstance(exc, RedisError):
                # 数据库异常/redis异常
                logger.error('[%s] %s' % (view, exc))
                response = Response({'message': '服务器内部错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
    
        return response
    redis的异常捕获以及记录错误日志

    4.购物车页面展示-后端接口

    1.添加购物车-课程有效期

    1.在后端设置一个默认有效期

    2.添加购物车时将有效期也存到redis中:之前的redis数据存储结构是集合,但是现在集合已经满足不了我们的需求了。要使用哈希数据类型存储

    哈希数据类型结构如下所示:

    '''
            user_id:{
                course_id:expire,
                course_id:expire,
            }
            
    '''
    # cart/views.py
    
    
    class AddCartView(ViewSet):
    
        def add(self,request):
           ......
    
            expire = 0  # 有效期:表示永久有效
            '''存用户对应的课程id和有效期'''
            conn.hset('cart_%s' % user_id,course_id,expire)
            '''存放用户的购物车长度'''
            cart_length = conn.hlen('cart_%s' % user_id)
    
              ......

    在上面的代码中,我们可以看到一共建立了两次conn连接,这样并不是很好,所以我们借助一个redis的管道pipe

    pipe = conn.pipeline() # 创建管道
    pipe.multi()
    
    # 将下面两个指令放到管道里面
    '''存用户对应的课程id和有效期'''
    pipe.hset('cart_%s' % user_id,course_id,expire)
    '''存放用户的购物车长度'''
    cart_length = pipe.hlen('cart_%s' % user_id)
    
    pipe.execute() # 执行上面两条指令

    2.购物车列表-后端接口

    class AddCartView(ViewSet):
    
        def cart_list(self,request):
            user_id = 1  # 用户id先写死
            conn = get_redis_connection('cart') # 获取cart对应的redis库对象
            
            # 将当前用户所对应的课程id从redis中取出来
            ret = conn.hgetall('cart_%s' % user_id)  # 封装成了字典{课程id,有效期},dict {b'1': b'0', b'2': b'0'}
            cart_data_list = []
            try:
                for cid, eid in ret.items():# cid:课程id eid:有效期
                    '''redis中存的是字节 所以要解码'''
                    course_id = cid.decode('utf-8') 
                    expire_id = eid.decode('utf-8')
    
                    course_obj = models.Course.objects.get(id=course_id)
                   '''
                   前端所需要的购物车数据包括
                   1.课程名称
                   2.课程封面图
                   3.课程价格
                   4.课程有效期
                   so 我们自己创建一个数据结构去存储前端所需要的内容
                   '''
                    cart_data_list.append({
                        'name':course_obj.name,
                        'course_img':contains.SERVER_ADDR + course_obj.course_img.url , # 图片路径是相对路径,我们将其变为绝对路径
                        'price':course_obj.price,
                        'expire_id':expire_id
                    })
            except Exception:
                logger.error('获取购物车数据失败')
                return Response({'msg':'后台数据库出问题了,请联系管理员'},status=status.HTTP_507_INSUFFICIENT_STORAGE)
    
           # 将数据响应给前端
            return Response({'msg':'xxx','cart_data_list':cart_data_list})

    5.购物车页面展示-前端

    1.购物车前端的初始界面

    ...

    2.将cart组件注册到路由上

    import Vue from 'vue'
    import Cart from '@/components/Cart'
    
    Vue.use(Router)
    
    export default new Router({
      mode:'history',
      routes: [
       
        {
          path: '/cart/',   
          component: Cart
        },
    
      ]
    })

    3.关于cart组件和cartitem组件

    在购物车页面中,整个购物车是一个组件(cart组件),然后要展示的每条购物车数据又是一个子组件(cartitem组件)

    <!-- cart.vue html部分 -->
    <div class="cart_course_list">
        <CartItem v-for="(value,index) in cart_data_list" :key="index" :cart="value"></CartItem> <!-- 001 :cart 父组件往子组件传值 -->
    </div>
    // cart.vue js部分
    <script>
    import CartItem from "./common/CartItem"
    export default {
        name: "Cart",
        data(){
          return {
            cart_data_list:[],
          }
        },
        methods:{
    
        },
      created() {
        let token = sessionStorage.token || localStorage.token;
        if (token){
    
          this.$axios.get(`${this.$settings.Host}/cart/add_cart/`) // 获取购物车数据
          .then((res)=>{
            this.cart_data_list = res.data.cart_data_list
          })
          .catch((error)=>{
            this.$message.error(error.response.data.msg);
          })
    
        }else {
          this.$router.push('/user/login');
        }
    
    
      },
      components:{
    
          CartItem,
        }
    }
    </script>

    父组件拿着自己的值 cart_data_list 传递给每个子组件进行渲染(父组件往子组件传值)

    // cartitem.vue
    <template>
        <div class="cart_item">
          <div class="cart_column column_1">
            <el-checkbox class="my_el_checkbox" v-model="checked"></el-checkbox>
          </div>
          <div class="cart_column column_2">
            <img :src="cart.course_img" alt="">
            <span><router-link to="/course/detail/1">{{cart.name}}</router-link></span>
          </div>
          <div class="cart_column column_3">
            <el-select v-model="cart.expire_id" size="mini" placeholder="请选择购买有效期" class="my_el_select">
              <el-option label="1个月有效" value="30" key="30"></el-option>
              <el-option label="2个月有效" value="60" key="60"></el-option>
              <el-option label="3个月有效" value="90" key="90"></el-option>
              <el-option label="永久有效" value="0" key="0"></el-option>
            </el-select>
          </div>
          <div class="cart_column column_4">¥{{cart.price}}</div>
          <div class="cart_column column_4">删除</div>
        </div>
    </template>
    
    <script>
    export default {
        name: "CartItem",
        data(){
          return {
            checked:false,
    
          }
        },
        props:['cart', ] // 002:子组件接受父组件传过来的值
    }
    </script>

    6.解决一个购物车数量显示混乱的bug

    1.页面刷新导致的vuex数据重置

    解决方法:页面刷新前将数据存到SessionStorage

    // App.vue
    <script>
    export default {
      name: 'App',
      created() {
        window.addEventListener('beforeunload',()=>{
      sessionStorage.setItem('cart_length',this.$store.state.cart_length);
        })
      }
    }
    </script>

    2.不同页面显示的购物车小红圆圈数量不一致

    在组件加载的时候,会执行vheader中的created方法,拿到sessionStorage的值

    let cart_length = sessionStorage.getItem('cart_length');
    this.$store.commit('add_cart',cart_length);

    其中有一点:只有页面刷新的时候,才会拿到sessionStorage的值存放到vuex中,

    如果页面不刷新,用户点击添加购物车(此时vuex存的购物车长度因为添加购物操作已经发生了变化),

    我们组件再加载时,如果拿的是sessionStorage的值,其实拿的还是原来的那个值。

    我们应该把拿值操作放到刷新页面之后

    created(){
        if (this.$store.state.cart_length === 0){ // 如果购物车没有数据
          let cart_length = sessionStorage.getItem('cart_length'); // 就去sessionStorage中拿数据
          this.$store.commit('add_cart',cart_length); // 并将数据存放到vuex中
        }
      },
  • 相关阅读:
    请多指教
    第九周作业总结
    win10系统开机输入密码黑屏解决方法
    第八周作业总结
    第七周作业总结
    第六周作业总结
    第五周作业总结
    第四周作业
    第三周作业
    2019年春季学期第二周作业
  • 原文地址:https://www.cnblogs.com/libolun/p/13945509.html
Copyright © 2011-2022 走看看