Day90 详情页加入购物车
知识点:
1.redis数据库使用
2.vuex的使用
一.详情页添加购物车
1.后端配置
1.1创建子应用 cart
cd luffy/apps
python ../../manage.py startapp cart
INSTALLED_APPS = [ 'ckeditor', # 富文本编辑器 'ckeditor_uploader', # 富文本编辑器上传图片模块 'home', 'users', 'courses', 'cart', ]
因为购物车中的商品(课程)信息会经常被用户操作,所以为了减轻服务器的压力,可以选择把购物车信息通过redis来存储.
# 设置redis缓存 CACHES = { # 默认缓存 .... "cart":{ "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/3", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } }, }
接下来商品信息存储以下内容:
course_id 购物车中的商品ID course_expire 购物车中商品的有效期 is_selected 购物车中对应商品的勾选状态 # 上面三个数据,实际上存储到redis中,要以什么类型来存储呢? # redis一共5种数据类型,我们就应该考虑到哪种数据类型保存上面的数据最方便我们读写. cart_<user_id>: { "商品ID1":"有效期", "商品ID1":"有效期", "商品ID1":"有效期", } # 把已经勾选的商品ID记录到无序集合中 cart_selected_<user_id>:{ "商品ID1", "商品ID2", "商品ID3", }
cart/views.py视图,代码:
from django.shortcuts import render # Create your views here. from rest_framework.views import APIView from courses.models import Course from rest_framework.response import Response from django_redis import get_redis_connection from rest_framework.permissions import IsAuthenticated from rest_framework import status class CartAPIView(APIView): permission_classes = [IsAuthenticated] """购物车视图""" def post(self,request): """购物车添加商品""" # 获取客户端发送过来的课程ID course_id = request.data.get("course_id") # 验证课程ID是否有效 try: Course.objects.get(pk=course_id,is_delete=False,is_show=True) except Course.DoesNotExist: return Response({"message":"当前课程不存在!"},status=status.HTTP_400_BAD_REQUEST) # 组装基本数据[课程ID,有效期]保存到redis redis = get_redis_connection("cart") # user_id = 1 user_id = request.user.id try: # 添加一个成员到指定名称的hash数据中[如果对应名称的hash数据不存在,则自动创建] # hset(名称,键,值) redis.hset("cart_%s" % user_id, course_id, -1) # -1表示购买的课程永久有效 # 添加一个成员到制定名称的set数据中[如果对应名称的set数据不存在,则自动创建] # sadd(名称,成员) redis.sadd("cart_selected_%s" % user_id, course_id ) except: return Response({"message": "添加课程到购物车失败!请联系客服人员~"},status=status.HTTP_507_INSUFFICIENT_STORAGE) # 返回结果 return Response({"message":"成功添加课程到购物车!"},status=status.HTTP_200_OK)
1.5 配置路由
1.5.1 总路由,代码:
urlpatterns = [ ... path('cart/', include("cart.urls")), ]
1.5.2子应用路由cart/urls.py,代码:
from django.urls import path, re_path from . import views urlpatterns = [ path(r"course/",views.CartAPIView.as_view()), ]
1.5.3 默认用户添加课程到购物车就已经勾选了商品,视图代码:
from django.shortcuts import render # Create your views here. from rest_framework.views import APIView from courses.models import Course from rest_framework.response import Response from django_redis import get_redis_connection from rest_framework.permissions import IsAuthenticated from rest_framework import status class CartAPIView(APIView): permission_classes = [IsAuthenticated] """购物车视图""" def post(self,request): """购物车添加商品""" # 获取客户端发送过来的课程ID course_id = request.data.get("course_id") # 验证课程ID是否有效 try: Course.objects.get(pk=course_id,is_delete=False,is_show=True) except Course.DoesNotExist: return Response({"message":"当前课程不存在!"},status=status.HTTP_400_BAD_REQUEST) # 组装基本数据[课程ID,有效期]保存到redis redis = get_redis_connection("cart") # user_id = 1 user_id = request.user.id # transation: 事务 # 作用: 可以设置多个数据库操作看成一个整体,这个整理里面每一条数据库操作都成功了,事务才算成功, # 如果出现其中任意一个数据库操作失败,则整体一起失败! # 事务可以提供 提交事务 和 回滚事务 的功能 # 不仅mysql中存在事务,在redis中也有事务的概念,但是叫"管道 pipeline" try: # 创建事务[管道]对象 pipeline = redis.pipeline() # 开启事务 pipeline.multi() # 添加一个成员到指定名称的hash数据中[如果对应名称的hash数据不存在,则自动创建] # hset(名称,键,值) pipeline.hset("cart_%s" % user_id, course_id, -1) # -1表示购买的课程永久有效 # 添加一个成员到制定名称的set数据中[如果对应名称的set数据不存在,则自动创建] # sadd(名称,成员) pipeline.sadd("cart_selected_%s" % user_id, course_id ) # 提交事务[如果不提交,则事务会自动回滚] pipeline.execute() except: return Response({"message": "添加课程到购物车失败!请联系客服人员~"},status=status.HTTP_507_INSUFFICIENT_STORAGE) # 返回结果 return Response({"message": "成功添加课程到购物车!"}, status=status.HTTP_200_OK)
2.前端详情页添加购物车
Course.vue
//template 代码:
<div @click="cartAddHander" class="add-cart"><img src="@/assets/cart-yellow.svg" alt="">加入购物车</div> //data数据: data(){ return { token:sessionStorage.token || localStorage.token, user_id:sessionStorage.user_id || localStorage.user_id, user_name:sessionStorage.user_name || ocalStorage.user_name,} // methods 方法 // 添加商品课程到购物车 cartAddHander(){ // 1. 判断用户是否已经登录了. if(!this.token){ this.$confirm("对不起,您尚未登录!请登录",'提示').then(() => { this.$router.push("/login"); }); } // 2. 发起请求 this.$axios.post(this.$settings.Host+`/carts/course/`,{ course_id: this.course_id, },{ headers:{ // 注意:jwt后面必须有且只有一个空格!!!! "Authorization":"jwt " + this.token } }).then(response=>{ // 获取购物城中商品总数 // 添加购物车成功! this.$message(response.data.message,"提示!",{ duration: 2000, // 单位: 毫秒 }); }).catch(error=>{ console.log(error.response); }) }
# 返回结果,返回购物车中的商品数量 count = redis.hlen("cart_%s" % user_id)
4.前端展示商品课程的总数
获取商品总数是在头部组件中使用到,并展示出来,但是我们后面可以在购物车中,或者商品课程的详情页中修改购物车中商品总数,因为对于一些数据,需要在多个组件中共享,这种情况,我们可以使用本地存储来完成,但是也可以通过vuex组件来完成这个功能。
4.1 安装vuex
npm install -S vuex
4.2 把vuex注册到vue中
4.2.1在src目录下创建store目录,并在store目录下创建一个index.js文件,index.js文件代码:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); export const store = new Vuex.Store({ // 数据仓库,类似vue里面的data state: { }, // 数据操作方法,类似vue里面的methods mutations: { } });
4.2.2 把上面index.js中创建的store对象注册到main.js的vue中。
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './routers/index'; import store from './store/index'; // 手动的自定义全局配置 import settings from "./settings" Vue.prototype.$settings = settings; // elementUI 导入 import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; // 调用插件 Vue.use(ElementUI); import "../static/css/reset.css" import axios from 'axios'; // 从node_modules目录中导入包 // 允许ajax发送请求时附带cookie axios.defaults.withCredentials = true; Vue.prototype.$axios = axios; // 把对象挂载vue中 Vue.config.productionTip = false; // 导入gt极验 import '../static/js/gt.js'; // vue-video视频播放插件 require('video.js/dist/video-js.css'); require('vue-video-player/src/custom-theme.css'); import VideoPlayer from 'vue-video-player' Vue.use(VideoPlayer); /* eslint-disable no-new */ new Vue({ el: '#app', router, store, components: { App }, template: '<App/>' })
4.2.3接下来,我们就可以在组件使用到store中state里面保存的共享数据了.
先到vuex中添加数据,store/inde.js,代码
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); export default new Vuex.Store({ // 数据仓库,类似vue里面的data state: { // 购物车数据 cart:{ count:0, } }, // 数据操作方法,类似vue里面的methods mutations: { } });
4.2.4在Header.vue头部组件中,直接读取store里面的数据
<b class="goods-number">{{this.$store.state.cart.count}}</b> // this是可以省略不写。 <b class="goods-number">{{$store.state.cart.count}}</b>
4.2.5 我们可以在Detail.vue课程详情的组件中, 修改商品总数。
// 添加商品课程到购物车 cartAddHander(){ // 1. 判断用户是否已经登录了. if(!this.token){ this.$confirm("对不起,您尚未登录!请登录",'提示').then(() => { this.$router.push("/login"); }); } // 2. 发起请求 this.$axios.post(this.$settings.Host+`/carts/course/`,{ course_id: this.course_id, },{ headers:{ // 注意:jwt后面必须有且只有一个空格!!!! "Authorization":"jwt " + this.token } }).then(response=>{ // 获取购物城中商品总数 // this.$store.state.cart.count = response.data.count; this.$store.commit("addcart",response.data); // 添加购物车成功! this.$message(response.data.message,"提示!",{ duration: 2000, // 单位: 毫秒 }); }).catch(error=>{ console.log(error.response); }) }
4.3 在store/index.js中新增mutations的方法,代码:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); export default new Vuex.Store({ // 数据仓库,类似vue里面的data state: { // 购物车数据 cart:{ count: 0, // course_list: [], // 购物车里面的商品列表信息 } }, // 数据操作方法,类似vue里面的 methods mutations: { // data是调用方法,传递的购物车相关的参数 addcart(state,data){ // 修改商品课程的总数 state.cart.count = data.count; // state.cart.course_list = data.course_list; } } });