目录

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. # 095 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 03 封装总结 01 封装知识点总结

    095 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 ...

  2. 036 01 Android 零基础入门 01 Java基础语法 04 Java流程控制之选择结构 03 嵌套if结构

    036 01 Android 零基础入门 01 Java基础语法 04 Java流程控制之选择结构 03 嵌套if结构 本文知识点:Java中的嵌套if结构 什么是嵌套if结构? 概念: 嵌套if结构 ...

  3. 插头 dp

    插头dp 洛谷 黑题板子? P5056 给出n×m的方格,有些格子不能铺线,其它格子必须铺,形成一个闭合回路.问有多少种铺法? 1.轮廓线 简单地说,轮廓线就是已决策格子和未决策格子的分界线: 2,插 ...

  4. Flutter沉浸式状态栏/AppBar导航栏/仿咸鱼底部凸起导航

    Flutter中如何实现沉浸式透明Statusbar状态栏效果? 如下图:状态栏是指android手机顶部显示手机状态信息的位置.android 自4.4开始新加入透明状态栏功能,状态栏可以自定义颜色 ...

  5. 第0天 | 12天搞定Pyhon,前言

    依稀记得,在2014年的某一天,一位运营电商平台的多年好朋友,找我帮忙:一个月内,实现抓取竞争对手在某电商平台上的所有产品信息并统计每个产品的点击率. 说出来有些不好意思,那些年,参与过的产品挺多的, ...

  6. 腾讯云服务器,使用xshell ssh秘钥登录的时候报错:所选的用户密钥未在远程主机上注册

    1.背景 新买了台腾讯云服务器,在腾讯云控制台新建了SSH私钥,然后下载到本地. 在本地使用xshell工具,导入下载好的私钥进行登录,报错:所选的用户密钥未在远程主机上注册 2.解决方案 在确定选择 ...

  7. spring框架bean注入

    今天学习了spring框架,刚刚入门简单的了解了spring并学习了bean的注入IOC:IOC(Inversion of Control,控制反转)不是什么技术,而是一种设计思想.它的目的是指导我们 ...

  8. redis协议规范

    好多年前看过redis的代码,那个时候还是2.6的版本,集群和哨兵还没加入正式代码,这几年redis发展的好快.简略翻译一篇文章redis的https://redis.io/topics/protoc ...

  9. 迅雷bt种子的制作

    BT是目前最热门的下载方式之一,它的全称为"BitTorrent"简称"BT",中文全称"比特流",但很多朋友将它戏称为"变态下载 ...

  10. 基于SpringAop的鉴权功能

    什么是 AOP 首先我们先了解一下什么是AOP,AOP(Aspect Orient Programming),直译过来就是面向切面编程.AOP是一种编程思想,是面向对象编程(OOP)的一种补充.面向对 ...