Catalog
1. Add cart + Verify login status
2. The small red circle number on the top right of the shopping cart icon
4. Shopping cart page display - Back end interface
5. Shopping cart page display - front end
6. Solve a shopping cart number display confusion bug
1. Add cart + Verify login status
1. Add the whole idea of shopping cart
On the course details page, users click Add cart :
Get the course of the current course id Go to the database and put the course id The corresponding information ( Need to be displayed in the shopping cart ) Make it into a dictionary , then json Serialize into a string and save to redis in
How to add shopping cart by clicking , Add cart data to redis in ??????
2. Add cart - Back end interface
1. Create a cart application , And configuration INSTALLAPP
python3 ../../ manage.py startapp cart
2. Add... To the total route 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 # First put the user id Write dead # Go to redis There's data in it conn = get_redis_connection('cart') # Check the course id Is it legal try: models.Course.objects.get(id=course_id) except: return Response({'msg':' The curriculum doesn't exist '},status=400) ''' Choose to store with the data type of the collection ''' conn.sadd('cart_%s' % user_id,course_id) # vheader The small red number of the shopping cart on the right shows cart_length = conn.scard('cart_%s' % user_id) # Get the number of items return Response({'msg':' Add success ','cart_length',cart_length})
5. Use a separate one for the shopping cart redis library
# 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. Add cart - front end
Click on the front end of the shopping cart , Send a request back
<!-- html --> <div class="add-cart" @click="addCart"><img src="/static/img/cart-yellow.svg" alt=""> Add to cart </div>
Be careful : Add a shopping cart to verify that the user is logged in
So now we need to make two requests synchronous , Give Way token After verification, do other operations
It is troublesome to change asynchronous to synchronous , So we're directly Will verify token The operation of is written in addCart Add shopping cart method
// js addCart(){ // Get the front-end storage of token value let token = localStorage.token || sessionStorage.token; // If token The value is if (token){ // verification token this.$axios.post(`${this.$settings.Host}/users/verify/`,{ token:token, }).then((res)=>{ // Verification passed , You can add shopping carts this.$axios.post(`${this.$settings.Host}/cart/add_cart/`,{ // Get the course id course_id:this.course_id, }).then((res)=>{ // Shopping cart added successfully , Print the information of successful addition this.$message.success(res.data.msg); }) // The verification failed (token Wrong or token Be overdue ) Prompt the user to log in }).catch((error)=>{ this.$confirm(' You haven't signed in yet !!!?', '31s', { confirmButtonText: ' Go to login ', cancelButtonText: ' Cancel ', type: 'warning' }).then(() => { this.$router.push('/user/login'); }) // Will expire token Clean up sessionStorage.removeItem('token'); sessionStorage.removeItem('username'); sessionStorage.removeItem('id'); localStorage.removeItem('token'); localStorage.removeItem('username'); localStorage.removeItem('id'); }) } // token Can't get else { this.$confirm(' You haven't signed in yet !!!?', '31s', { confirmButtonText: ' Go to login ', cancelButtonText: ' Cancel ', type: 'warning' }).then(() => { this.$router.push('/user/login'); }) } },
We have returned the length of the shopping cart in the back end , At the front end, we can get the length of the cart
// Detail.vue this.$axios.post(`${this.$settings.Host}/cart/add_cart/`,{ // Get the course id course_id:this.course_id, }).then((res)=>{ // Shopping cart added successfully , Print the information of successful addition this.$message.success(res.data.msg); // Get the length of the shopping cart sent to the back end this.cart_length = res.data.cart_length })
The first way of thinking :vheader Components are detail Subcomponent of component , We can go through vue The parent-child pass value to achieve .
That's the question
If we visit the actual combat class page That is to say /course, stay course Component is not to display the shopping cart small red circle ?
But in course Component we didn't get the length of the cart at all . So the red circle numbers don't show up at all . So the idea of father son value passing doesn't work .
3.Vuex
because For some data , Need to be shared instantly across multiple components , So according to the above question , We lead to vuex
1. install Vuex
npm install -S vuex
2. hold vuex Sign up to vue in
stay src Create under directory store Catalog , And in store Create one in the directory index.js file ,index.js File code
// store/index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { // Data warehouse , similar vue Inside data cart_length: 0 // Shopping cart data }, mutations: { // Data manipulation method , similar vue Inside methods add_cart (state, cart_length) { state.cart_length = cart_length; // Modify the total number of items in the shopping cart } } })
3. mount store object
Top up index.js Created in the store Object registered to main.js Of vue in .
// main.js import Vue from 'vue' import App from './App' import router from './router' import store from './store'; // introduce new Vue({ el: '#app', router, store, // mount components: { App }, template: '<App/>' })
4.Vheader Component read store The data of ( Read the length of the cart )
stay Vheader.vue In the head assembly , You can read it directly store The data in it
<router-link to="/"> <b>{{$store.state.cart_length}}</b> <img src="@/assets/shopcart.png" alt=""> <span> The shopping cart </span> </router-link>
5.Detail Component modification store The data of ( Modify cart length )
When the user clicks add cart , Trigger addCart Medium post Method , Change the length of the shopping cart
this.$axios.post(`${this.$settings.Host}/cart/add_cart/`,{ course_id:this.course_id, }).then((res)=>{ this.$message.success(res.data.msg); // The length of the shopping cart obtained from the back end does not exist in the data attribute of the current component , It's stored in vuex in this.$store.commit('add_cart', res.data.cart_length) ; // commit Used to trigger mutation The method declared in })
6. About page refresh ,vuex Data loss problem
Solution : When the user clicks refresh , We can monitor the user refresh action , You can do some actions on the page before refreshing .
When you click refresh , Let's save the data to sessionStorage or localStorage in ,
After the page is refreshed , And get the data back to vuex in . In this way, the page can be refreshed , The data has not been lost .
1. Click refresh , Save the data to sessionStorage in
// app.vue <script> export default { name: 'App', created() { // Before refreshing the page cart_length The data is stored in sessionStorage in window.addEventListener('beforeunload',()=>{ console.log(' Page to refresh !!!, Save the data !!!!'); sessionStorage.setItem('cart_length',this.$store.state.cart_length); }) } } </script>
2.. After the page is refreshed , Take data from sessionStorage Take it out and put it in vuex in
// 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. About redis Exception capture
In order to ensure that the system log records can be followed up redis Part of the , We can also add information about redis Exception capture
# 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 # introduce redis abnormal import logging logger = logging.getLogger('django') def custom_exception_handler(exc, context): """ Custom exception handling :param exc: Exception class :param context: The context in which the exception was thrown :return: Response The response object """ # call drf Framework native exception handling method response = exception_handler(exc, context) if response is None: view = context['view'] # The function or method where the error occurred if isinstance(exc, DatabaseError) or isinstance(exc, RedisError): # Database exception /redis abnormal logger.error('[%s] %s' % (view, exc)) response = Response({'message': ' Server internal error '}, status=status.HTTP_507_INSUFFICIENT_STORAGE) return response
4. Shopping cart page display - Back end interface
1. Add cart - Course validity
2. When adding a shopping cart, save the validity period to redis in : Previous redis The data storage structure is a collection , But now the collection can't meet our needs . To use Hash data type storage .
The hash data type structure is as follows :
''' user_id:{ course_id:expire, course_id:expire, } '''
# cart/views.py class AddCartView(ViewSet): def add(self,request): ...... expire = 0 # The period of validity : Permanent ''' Save the user's corresponding course id And validity period ''' conn.hset('cart_%s' % user_id,course_id,expire) ''' Length of shopping cart for storing users ''' cart_length = conn.hlen('cart_%s' % user_id) ......
In the code above , We can see that it was set up twice conn Connect , This is not very good , So we use a redis The pipe pipe
pipe = conn.pipeline() # Create pipes pipe.multi() # Put the next two instructions in the pipe ''' Save the user's corresponding course id And validity period ''' pipe.hset('cart_%s' % user_id,course_id,expire) ''' Length of shopping cart for storing users ''' cart_length = pipe.hlen('cart_%s' % user_id) pipe.execute() # Execute the two instructions above
2. Shopping cart list - Back end interface
class AddCartView(ViewSet): def cart_list(self,request): user_id = 1 # user id Write death first conn = get_redis_connection('cart') # obtain cart Corresponding redis Library object # The course corresponding to the current user id from redis To remove ret = conn.hgetall('cart_%s' % user_id) # It's packaged into a dictionary { Course id, The period of validity },dict {b'1': b'0', b'2': b'0'} cart_data_list = [] try: for cid, eid in ret.items():# cid: Course id eid: The period of validity '''redis There are bytes in it So to decode ''' course_id = cid.decode('utf-8') expire_id = eid.decode('utf-8') course_obj = models.Course.objects.get(id=course_id) ''' The shopping cart data required by the front end includes 1. Course name 2. Course cover 3. Price of course 4. Course validity so We create our own data structure to store what the front end needs ''' cart_data_list.append({ 'name':course_obj.name, 'course_img':contains.SERVER_ADDR + course_obj.course_img.url , # The picture path is a relative path , We turn it into an absolute path 'price':course_obj.price, 'expire_id':expire_id }) except Exception: logger.error(' Failed to get cart data ') return Response({'msg':' There's something wrong with the background database , Please contact the Administrator '},status=status.HTTP_507_INSUFFICIENT_STORAGE) # Respond the data to the front end return Response({'msg':'xxx','cart_data_list':cart_data_list})
5. Shopping cart page display - front end
1. The initial interface of the front end of the shopping cart
...
2. take cart The component is registered on the route
import Vue from 'vue' import Cart from '@/components/Cart' Vue.use(Router) export default new Router({ mode:'history', routes: [ { path: '/cart/', component: Cart }, ] })
3. About cart Components and cartitem Components
In the shopping cart page , The whole shopping cart is a component (cart Components ), Then each shopping cart data to be displayed is a sub component (cartitem Components )
<!-- cart.vue html part --> <div class="cart_course_list"> <CartItem v-for="(value,index) in cart_data_list" :key="index" :cart="value"></CartItem> <!-- 001 :cart The parent component passes values to the child components --> </div>
// cart.vue js part <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/`) // Get shopping cart data .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>
The parent component holds its own value cart_data_list Pass to each subcomponent for rendering ( The parent component passes values to the child components )
// 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=" Please select the purchase validity period " class="my_el_select"> <el-option label="1 Months in effect " value="30" key="30"></el-option> <el-option label="2 Months in effect " value="60" key="60"></el-option> <el-option label="3 Months in effect " value="90" key="90"></el-option> <el-option label=" Permanent validity " value="0" key="0"></el-option> </el-select> </div> <div class="cart_column column_4">¥{{cart.price}}</div> <div class="cart_column column_4"> Delete </div> </div> </template> <script> export default { name: "CartItem", data(){ return { checked:false, } }, props:['cart', ] // 002: The child component accepts the value passed by the parent component } </script>
1. Page refresh caused by vuex Data reset
resolvent : Save data to before page refresh SessionStorage
// App.vue <script> export default { name: 'App', created() { window.addEventListener('beforeunload',()=>{ sessionStorage.setItem('cart_length',this.$store.state.cart_length); }) } } </script>
2. The number of small red circles in shopping cart displayed on different pages is inconsistent
When the component is loaded , Will execute vheader Medium created Method , Get sessionStorage Value
let cart_length = sessionStorage.getItem('cart_length'); this.$store.commit('add_cart',cart_length);
One of them is : Only when the page is refreshed , To get sessionStorage The value of is stored in vuex in ,
If the page doesn't refresh , Users click to add shopping cart ( here vuex The length of the stored cart has changed due to the addition of shopping operations ),
When our component reloads , If it's sessionStorage Value , In fact, it is still the original value .
We should put the value operation after refreshing the page
created(){ if (this.$store.state.cart_length === 0){ // If the shopping cart has no data let cart_length = sessionStorage.getItem('cart_length'); // Just go to sessionStorage Of the data this.$store.commit('add_cart',cart_length); // And store the data in vuex in } },