day83:luffy:添加购物车&导航栏购物车数字显示&购物车页面展示
目录
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:添加购物车&导航栏购物车数字显示&购物车页面展示的更多相关文章
- 添加底部导航栏tabbar
效果图: 如果要添加底部导航栏,最少2个,最多5个. app.json { "pages": [ "pages/index/index", "page ...
- JavaScript网站设计实践(二)实现导航栏当前所选页面的菜单项高亮显示
一.(一)中的代码还可以修改的地方. 在(一)中,如果是运行在服务器下,如apache等,可以把head和navigation的div抽取出来,放置在另一个html文件里,然后在页面中,include ...
- CI框架后台添加左侧导航栏出现的一系列问题
- Vue导航栏在特定的页面不显示~
最近写vue项目遇到一些问题,我把导航栏组件放在了app.vue中,让他在每个页面都能显示了,但遇到了一个问题,在登录以及注册页面导航栏是不合理不允许存在的 解决方法: 公共模块的内容可以放在App. ...
- 使用fragment添加底部导航栏
切记:fragment一定要放在framlayout中,不然不会被替换完全(就是切换之后原来的fagment可能还会存在) main.xml <LinearLayout xmlns:androi ...
- 微信小程序添加底部导航栏
修改 app.json 文件即可 "tabBar": { "selectedColor": "#1296db", "list&qu ...
- iOS 导航栏返回到指定页面的方法和理解
关于ios中 viewcontroller的跳转问题,其中有一种方式是采用navigationController pushViewController 的方法,比如我从主页面跳转到了一级页面,又从一 ...
- 首页 导航栏隐藏 下一级页面显示,pop回来遇到的问题
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.navigationContr ...
- Android——Fragment实例精讲——底部导航栏+ViewPager滑动切换页面
说明: 实现效果: 1- 用ViewPager实现Fragmen之间的切换 2- 底部用RadioGroup实现,更方便的实现图片和字体颜色的改变,更方便的通过RadioButton的点击事件来控制页 ...
随机推荐
- VS2013 c++ 生成和调用DLL动态链接库(.def 方法已验证OK)
转载:https://blog.csdn.net/zhunianguo/article/details/52294339 .def 方法 创建动态库方法: 创建动态库是生成 .dll .lib 两个个 ...
- 【题解】[USACO19DEC]Milk Visits G
题目戳我 \(\text{Solution:}\) 这题不要把思想局限到线段树上--这题大意就是求路径经过的值中\(x\)的出现性问题. 最开始的想法是值域线段树--看了题解发现直接\(vector\ ...
- HarmonyOS 润和 HiSpark开发套件 免费领!
让人期盼已久的HarmonyOS 2.0终于在9月10日正式上线啦! 这是一件让众多开发者关注的大事件! 相信不少开发者都已经迫不及待的想上手实操了, 为了满足大家的好奇心, 也希望能有更多开发者了解 ...
- ASP。NET Core Blazor CRUD使用实体框架和Web API
下载source code - 1.7 MB 介绍 *请查看我的Youtube视频链接来学习ASP.NET Core Blazor CRUD使用实体框架和Web API. 在本文中,我们将了解如何为A ...
- 多测师讲解python_模块间的调用_高级讲师肖sir
案例1: 在aaa.py 文件A类中定义一个函数sadp: 在bbb.py文件中导入aaa模块,导入类 ,调用函数 案例2: aaa模块中定义一个A类, 在定义一个sadp的函数, 在bbb模块中导 ...
- MeteoInfoLab脚本示例:计算水平螺旋度
尝试了用MeteoInfoLab编写计算水平螺旋度的脚本,结果未经验证.脚本程序: print 'Open data files...' f_uwnd = addfile('D:/Temp/nc/uw ...
- XUEXI0.4
1.堆是一种内存管理方式,堆和栈是没有关联的.由于内存的容量很大,并且内存需求在时间和空间上没有规律,所以对操作系统来说,管理内存是非常复杂的. 2.堆这种内存管理方式特点是自由.堆内存是由操作系统划 ...
- docker查看ip
docker查看容器的网络ip docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' conta ...
- zoookeeper集群和kafka集群启动快速启动脚本
kafka.sh port=9092 # 根据端口号去查询对应的PID pid=$(netstat -nlp | grep :$port | awk '{print $7}' | awk -F&quo ...
- Model实体类
Model又叫实体类,这个东西,大家可能觉得不好分层.包括我以前在内,是这样理解的:UI<-->Model<-->BLL<-->Model<-->DAL ...