目录

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中
}
},

day83:luffy:添加购物车&导航栏购物车数字显示&购物车页面展示的更多相关文章

  1. 添加底部导航栏tabbar

    效果图: 如果要添加底部导航栏,最少2个,最多5个. app.json { "pages": [ "pages/index/index", "page ...

  2. JavaScript网站设计实践(二)实现导航栏当前所选页面的菜单项高亮显示

    一.(一)中的代码还可以修改的地方. 在(一)中,如果是运行在服务器下,如apache等,可以把head和navigation的div抽取出来,放置在另一个html文件里,然后在页面中,include ...

  3. CI框架后台添加左侧导航栏出现的一系列问题

  4. Vue导航栏在特定的页面不显示~

    最近写vue项目遇到一些问题,我把导航栏组件放在了app.vue中,让他在每个页面都能显示了,但遇到了一个问题,在登录以及注册页面导航栏是不合理不允许存在的 解决方法: 公共模块的内容可以放在App. ...

  5. 使用fragment添加底部导航栏

    切记:fragment一定要放在framlayout中,不然不会被替换完全(就是切换之后原来的fagment可能还会存在) main.xml <LinearLayout xmlns:androi ...

  6. 微信小程序添加底部导航栏

    修改 app.json 文件即可 "tabBar": { "selectedColor": "#1296db", "list&qu ...

  7. iOS 导航栏返回到指定页面的方法和理解

    关于ios中 viewcontroller的跳转问题,其中有一种方式是采用navigationController pushViewController 的方法,比如我从主页面跳转到了一级页面,又从一 ...

  8. 首页 导航栏隐藏 下一级页面显示,pop回来遇到的问题

    - (void)viewWillAppear:(BOOL)animated {    [super viewWillAppear:animated];    [self.navigationContr ...

  9. Android——Fragment实例精讲——底部导航栏+ViewPager滑动切换页面

    说明: 实现效果: 1- 用ViewPager实现Fragmen之间的切换 2- 底部用RadioGroup实现,更方便的实现图片和字体颜色的改变,更方便的通过RadioButton的点击事件来控制页 ...

随机推荐

  1. MFC 简介

    参考:https://baike.baidu.com/item/MFC/2236974 MFC (微软基础类库) 编辑 锁定 讨论999   MFC(Microsoft Foundation Clas ...

  2. SpringBoot-06-模板引擎Thymeleaf

    6. 模板引擎 Thymeleaf Thyme leaf 英译为 百里香的叶子. 模板引擎 ​ 以前开发中使用的jsp就是一个模板引擎,但是springboot 以jar的方式,并且使用嵌入式的tom ...

  3. mac电脑上安装appium报错:Failed at the appium-chromedriver@4.25.1 postinstall script.

    mac电脑安装appium,装好node.js后,使用命令:npm install appium@1.18.0,安装appium,报如下错误 ``` ERR! errno1 ERR! appium-c ...

  4. 拉格朗日乘子法与KKT条件

    拉格朗日乘子法 \[min \quad f = 2x_1^2+3x_2^2+7x_3^2 \\s.t. \quad 2x_1+x_2 = 1 \\ \quad \quad \quad 2x_2+3x_ ...

  5. 多测师_肖sir_git _004(版本控制器)

    gitgit 是一个开源的分布式版本控制系统,用于敏捷高效的处理任何大小的项目.git是linux torvalds 为了帮助管理linux内核开发的一个开放源码的版本控制软件.git与常用的版本控制 ...

  6. ssh登录二次验证,让服务器更安全。

    码云地址 sshdTwoVerification 介绍 ssh登录二次验证 问题:现在很多人的Linux服务器可能会被攻击,只校验一次后台用户名密码登录变得不再保险. 当然大家首先要做的是修改ssh服 ...

  7. spring boot: filter/interceptor/aop在获取request/method参数上的区别(spring boot 2.3.1)

    一,filter/interceptor/aop在获取参数上有什么区别? 1,filter可以修改HttpServletRequest的参数(doFilter方法的功能), interceptor/a ...

  8. 全文检索Solr集成HanLP中文分词【转】

    以前发布过HanLP的Lucene插件,后来很多人跟我说其实Solr更流行(反正我是觉得既然Solr是Lucene的子项目,那么稍微改改配置就能支持Solr),于是就抽空做了个Solr插件出来,开源在 ...

  9. cookie和session可以参考的文章

    cookie和session可以参考的文章 cookie:http://www.lemfix.com/topics/5session:https://www.cnblogs.com/nickjiang ...

  10. mysql优化篇(基于索引)

    在上一篇文章:Mysql索引(一篇就够le) 中介绍了索引的基本使用,分类和原理,也强烈建议先读Mysql索引(一篇就够le),然后继续本文的阅读 我们也知道mysql的优化可以从很多的方面进行,比如 ...