前面已经将一些课程加入购物车中,并保存到了后端的redis数据库中,此时做购物车页面时,我们需要将在前端向后端发送请求,用来获取数据数据

购物车页面

1.首先后端要将数据构建好,后端视图函数如下代码:

(post请求是将加入购物车的课程信息加入到redis中,其中对于价格在存储的时候要计算折扣后的价格,而get请求则是redis中取出数据到发送前端页面中)

cart/view:


from django.conf import settings
from rest_framework import status
from rest_framework.response import Response
from django_redis import get_redis_connection
from rest_framework.views import APIView
from courses.models import Course
from rest_framework.permissions import IsAuthenticated
from .utils import get_course_real_price
class CartAPIView(APIView):
permission_classes = [IsAuthenticated]
def post(self,request):
"""添加课程到购物车"""
# 接受客户端提交过来的课程ID
course_id = request.data.get("course_id")
try:
course = models.Course.objects.get(pk=course_id, status=0)
except:
return Response({"message": "当前课程不存在或者已经下架了"}, status=status.HTTP_400_BAD_REQUEST)
      
     # 计算课程的真实价格
     price = get_course_real_price(course)
# 把课程id和课程价格保存到购物车中redis中
# redis中使用hash类型保存数据
redis = get_redis_connection("cart")
# 获取当前登陆用户的ID,并写入redis中
user_id = request.user.id
pl = redis.pipeline()
pl.multi()
pl.hset("cart_%s" % user_id, course_id, str(course.price))
# 把当前课程默认为勾选状态[勾选状态也要保存到redis中]
# redis中使用set类型保存数据
pl.sadd("cart_select_%s" % user_id, course_id)
pl.execute() # 返回响应操作
return Response({"message": "success"}, status=status.HTTP_200_OK) def get(self,request):
# 获取当前登陆用户
user_id = request.user.id
# 从redis中获取所有的课程信息和勾选状态
redis = get_redis_connection("cart")
course_list = redis.hgetall("cart_%s" % user_id)
selected_list = redis.smembers("cart_select_%s" % user_id) # 构造数据返回给前端
data = []
for course_id, price in course_list.items():
course_id = course_id.decode()
price = price.decode() try:
course_info = models.Course.objects.get(pk=course_id)
except:
return Response({"message": "请求有误,请联系客服"}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
data.append({
"id": course_id,
"price": price,
"selected": course_id.encode() in selected_list,
"course_img": HOST+course_info.course_img.url,
"name": course_info.name,
}) # 返回给客户端
return Response(data, status=status.HTTP_200_OK)

其中关于计算折扣的详细方法如下:

cart/utils:

 from decimal import Decimal

 def get_course_real_price(course):
price = course.price
st = course.price_service_type # 价格服务类型
if st is not None:
all_services = st.priceservices.all() # 当前价格类型的所有服务策略
if st != None and len(all_services) > 0:
if all_services[0].condition > 0: # 是否有设置了价格服务,没有设置价格服务的课程,服务为值None
# 1. 优惠条件值大于0,则属于满减
service_list = all_services
# 进行满减价格计算
real_sale = 0 # 保存满足条件的优惠值
for item in service_list:
item.condition = int(item.condition)
item.sale = int(item.sale)
if course.price > item.condition and real_sale <= item.sale:
real_sale = item.sale
price = course.price - real_sale else: # 优惠条件值为0,则表示是限时折扣或者限时免费
if all_services[0].sale == "-1":
# 2. 限时免费
price = 0
else:
# 3. 限时折扣
# 把优惠值sale中的*去掉
sale = all_services[0].sale[1:]
price = course.price * Decimal(sale)
else:
# 原价
price = course.price return "%.2f" % price

2.关于设置勾选的商品发送到后端保存以及按钮删除购物车课程

后端代码:

cart/view:(由于此时前端发送过来的数据含有数字,另外开一个类(CartCourseAPIView)处理此次请求)

post:前端携带相应的取消或添加勾选购物车内课程的选项,后端根据携带值得真假,做相应的增加或删除勾选项

delete:用于处理前端按钮删除某个购物车课程的处理,需要在购物车课程列表中删除对应键值对,并在勾选集合中删掉对应的id值

 class CartCourseAPIView(APIView):
permission_classes = [IsAuthenticated] def post(self,request,pk):
user = request.user
print("user_id",user.id)
try:
course = models.Course.objects.get(pk=pk)
except models.Course.DoesNotExist:
return Response({"message": ""}, status=status.HTTP_400_BAD_REQUEST) #获取勾选状态
is_selected = request.data.get("is_select") #引入redis
redis = get_redis_connection("cart")
if is_selected:
# redis中增加当前课程id到勾选集合中
redis.sadd("cart_select_%s" % user.id, pk)
else:
# redis中删除当前课程id
redis.srem("cart_select_%s" % user.id, pk) return Response({"message": ""}, status=status.HTTP_200_OK) def delete(self,request,pk): user = request.user
try:
course = models.Course.objects.get(pk=pk)
except models.Course.DoesNotExist:
return Response({"message": "无效的课程标号"}, status=status.HTTP_400_BAD_REQUEST) # 从购物车和勾选集合中删除指定的数据
redis = get_redis_connection("cart")
pl = redis.pipeline()
pl.multi()
pl.hdel("cart_%s" % user.id, pk)
pl.srem("cart_select_%s" % user.id, pk)
pl.execute() return Response({"message": "删除成功!"}, status=status.HTTP_200_OK)

前端页面要做的一些功能:

加载数据时,从后端拿数据,发送请求:

cart.vue

   //计算各种折算后,购物车的总价
total_price(){
// 计算总价格
let cl = this.course_list;
let total = 0;
for(let i = 0;i<cl.length;i++){
if(cl[i].selected){
total+=parseFloat(cl[i].price);
}
}
total = total.toFixed(2);
this.total = total;
},
},
created() {
let _this = this;
// 发起请求获取购物车中的商品信息
_this.$axios.get("http://127.0.0.1:8000/cart/",{
headers: {
'Authorization': 'JWT ' + _this.token
},
responseType: 'json',
}).then(response=>{
console.log("response.data",response.data)
_this.course_list = response.data;
this.total_price()
}) },

勾选购物车内课程选项时:

1.在每次用户点击选项框时,向后台发送请求,更新后端redis中的勾选项集合(采用监视的方法,只要选项的值变动,便发送请求),

2.在发送请求成功后,需要通知父组件更新,结算的总结各

Template:
<el-col :span="2" class="checkbox"><el-checkbox label="" v-model="course.selected" name="type"></el-checkbox></el-col> script标签内: watch:{
"course.selected":function(){
let _this = this;
_this.$axios.post(`http://127.0.0.1:8000/cart/${this.course.id}`,{
is_select: _this.course.selected
},{
headers:{
// 附带已经登录用户的jwt token 提供给后端,一定不能疏忽这个空格
'Authorization':'JWT '+_this.token
},
responseType:"json",
}).then(response=>{       //通知父组件更改价格
_this.$emit("change_select");
}).catch(error=>{
console.log( error.response );
})
}
},

2.按钮删除购物车课程,需要做的有:

1.用delete请求向后端发送携带要删除课程id的值

2.在点击该删除按钮时,同时告知父组件应该删除该项课程(涉及到子传父的数据交互问题)

3.在点击该删除按钮时,应该刷新所勾选的购物车的课程结算金额,因为实在父组件中展示的总价,也要对父组件发送更新总价的通知

cartitem.vue中(cart的子组件)

 template内:
<el-col :span="4" class="course-delete"><span @click="delete_course(course.id)">删除</span></el-col> script内: props:["course","course_key"], //父组件传递过来的数据
methods:{
//按删除键删除购物车的课程
delete_course(course_id){
let _this = this;
this.$axios.delete(`http://127.0.0.1:8000/cart/${this.course.id}`, {
headers: {
// 附带已经登录用户的jwt token 提供给后端,一定不能疏忽这个空格
'Authorization': 'JWT ' + _this.token
},
responseType: "json",
}).then(response => { // 发送信息给父组件,通过父组件删除当前子组件
_this.$emit("delete_course",_this.course_key);
}).catch(error => {
console.log(error.response);
});
},
},

cart.vue中(cartitem的父组件):

 template:
<CartItem v-for="item,course_key in course_list" :course="item" @change_select="total_price" @delete_course="del_course" :course_key="course_key" /> script:
export default {
name:"Cart",
data(){
return {
total:0,
course_list:[],
token: localStorage.token || sessionStorage.token,
}
}, components:{
Header,
Footer,
CartItem,
},
methods:{
del_course(course_key) {
//course_key是通过字传父传回来,用于删除已删除的的购物车的课程
this.course_list.splice(course_key, 1);
// 重新计算总价格
this.total_price();
},
//计算各种折算后,购物车的总价
total_price(){
// 计算总价格
let cl = this.course_list;
let total = 0;
for(let i = 0;i<cl.length;i++){
if(cl[i].selected){
total+=parseFloat(cl[i].price);
}
}
total = total.toFixed(2);
this.total = total;
},
},
created() {
let _this = this;
// 发起请求获取购物车中的商品信息
_this.$axios.get("http://127.0.0.1:8000/cart/",{
headers: {
'Authorization': 'JWT ' + _this.token
},
responseType: 'json',
}).then(response=>{
console.log("response.data",response.data)
_this.course_list = response.data;
this.total_price() //在加载数据的时候也要对总价做出计算
}) },
}

详细的完整代码如下:

后端试图cart/view

from django.shortcuts import render

# Create your views here.
from django_redis import get_redis_connection
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView from luffy.apps.cart.utils import get_course_real_price
from luffy.apps.courses import models
from luffy.settings import HOST class CartAPIView(APIView):
permission_classes = [IsAuthenticated]
def post(self,request):
"""添加课程到购物车"""
# 接受客户端提交过来的课程ID
course_id = request.data.get("course_id")
try:
course = models.Course.objects.get(pk=course_id, status=0)
except:
return Response({"message": "当前课程不存在或者已经下架了"}, status=status.HTTP_400_BAD_REQUEST) # 计算课程的真实价格,调用写好的的在utils的计算折扣的方法
price = get_course_real_price(course) # 把课程id和课程价格保存到购物车中redis中
# redis中使用hash类型保存数据
redis = get_redis_connection("cart")
# 获取当前登陆用户的ID,并写入redis中
user_id = request.user.id
pl = redis.pipeline()
pl.multi()
pl.hset("cart_%s" % user_id, course_id, price)
# 把当前课程默认为勾选状态[勾选状态也要保存到redis中]
# redis中使用set类型保存数据
pl.sadd("cart_select_%s" % user_id, course_id)
pl.execute() # 返回响应操作
return Response({"message": "success"}, status=status.HTTP_200_OK) def get(self,request):
# 获取当前登陆用户
user_id = request.user.id
# 从redis中获取所有的课程信息和勾选状态
redis = get_redis_connection("cart")
course_list = redis.hgetall("cart_%s" % user_id)
selected_list = redis.smembers("cart_select_%s" % user_id) # 构造数据返回给前端
data = []
for course_id, price in course_list.items():
course_id = course_id.decode()
price = price.decode() try:
course_info = models.Course.objects.get(pk=course_id)
except:
return Response({"message": "请求有误,请联系客服"}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
data.append({
"id": course_id,
"price": price,
"selected": course_id.encode() in selected_list,
"course_img": HOST+course_info.course_img.url,
"name": course_info.name,
}) # 返回给客户端
return Response(data, status=status.HTTP_200_OK) class CartCourseAPIView(APIView):
permission_classes = [IsAuthenticated] def post(self,request,pk):
user = request.user
print("user_id",user.id)
try:
course = models.Course.objects.get(pk=pk)
except models.Course.DoesNotExist:
return Response({"message": ""}, status=status.HTTP_400_BAD_REQUEST) #获取勾选状态
is_selected = request.data.get("is_select") #引入redis
redis = get_redis_connection("cart")
if is_selected:
# redis中增加当前课程id到勾选集合中
redis.sadd("cart_select_%s" % user.id, pk)
else:
# redis中删除当前课程id
redis.srem("cart_select_%s" % user.id, pk) return Response({"message": ""}, status=status.HTTP_200_OK) def delete(self,request,pk): user = request.user
try:
course = models.Course.objects.get(pk=pk)
except models.Course.DoesNotExist:
return Response({"message": "无效的课程标号"}, status=status.HTTP_400_BAD_REQUEST) # 从购物车和勾选集合中删除指定的数据
redis = get_redis_connection("cart")
pl = redis.pipeline()
pl.multi()
pl.hdel("cart_%s" % user.id, pk)
pl.srem("cart_select_%s" % user.id, pk)
pl.execute() return Response({"message": "删除成功!"}, status=status.HTTP_200_OK)

前端cart.vue(父组件):

 <template>
<div class="cart">
<Header/>
<div class="cart-info">
<h3 class="cart-top">我的购物车 <span>共1门课程</span></h3>
<div class="cart-title">
<el-row>
<el-col :span="">&nbsp;</el-col>
<el-col :span="">课程</el-col>
<el-col :span="">有效期</el-col>
<el-col :span="">单价</el-col>
<el-col :span="">操作</el-col>
</el-row>
</div>
<CartItem v-for="item in course_list" :course="item" @change_select="total_price" @delete_course="del_course" :course_key="course_key" />
<div class="calc">
<el-row>
<el-col :span="">&nbsp;</el-col>
<el-col :span="">
<el-checkbox label="全选" name="type"></el-checkbox></el-col>
<el-col :span="" class="del"><i class="el-icon-delete"></i>删除</el-col>
<el-col :span="" class="count">总计:¥{{total}}</el-col>
<el-col :span="" class="cart-calc">去结算</el-col>
</el-row>
</div>
</div>
<Footer/>
</div>
</template> <script>
import Header from "./common/Header"
import Footer from "./common/Footer"
import CartItem from "./common/CartItem"
export default {
name:"Cart",
data(){
return {
total:0,
course_list:[],
token: localStorage.token || sessionStorage.token,
}
}, components:{
Header,
Footer,
CartItem,
},
methods:{
del_course(course_key) {
//course_key是通过字传父传回来,用于删除已删除的的购物车的课程
this.course_list.splice(course_key, 1);
// 重新计算总价格
this.total_price();
},
//计算各种折算后,购物车的总价
total_price(){
// 计算总价格
let cl = this.course_list;
let total = 0;
for(let i = 0;i<cl.length;i++){
if(cl[i].selected){
total+=parseFloat(cl[i].price);
}
}
total = total.toFixed(2);
this.total = total;
},
},
created() {
let _this = this;
// 发起请求获取购物车中的商品信息
_this.$axios.get("http://127.0.0.1:8000/cart/",{
headers: {
'Authorization': 'JWT ' + _this.token
},
responseType: 'json',
}).then(response=>{
console.log("response.data",response.data)
_this.course_list = response.data;
this.total_price()
}) },
}
</script> <style scoped>
.cart{
margin-top: 80px;
}
.cart-info{
overflow: hidden;
width: 1200px;
margin: auto;
}
.cart-top{
font-size: 18px;
color: #666;
margin: 25px 0;
font-weight: normal;
}
.cart-top span{
font-size: 12px;
color: #d0d0d0;
display: inline-block;
}
.cart-title{
background: #F7F7F7;
}
.cart-title .el-row,.cart-title .el-col{
height: 80px;
font-size: 14px;
color: #333;
line-height: 80px;
}
.calc .el-col{
height: 80px;
line-height: 80px;
}
.calc .el-row span{
font-size: 18px!important;
}
.calc .el-row{
font-size: 18px;
color: #666;
margin-bottom: 300px;
margin-top: 50px;
background: #F7F7F7;
}
.calc .del{ }
.calc .el-icon-delete{
margin-right: 15px;
font-size: 20px;
}
.calc .count{
text-align: right;
margin-right:62px;
}
.calc .cart-calc{
width: 159px;
height: 80px;
border: none;
background: #ffc210;
font-size: 18px;
color: #fff;
text-align: center;
cursor: pointer;
}
</style>

前端cartitem.vue(子组件):

 <template>
<div class="cart-item">
<el-row>
<el-col :span="2" class="checkbox"><el-checkbox label="" v-model="course.selected" name="type"></el-checkbox></el-col>
<el-col :span="10" class="course-info">
<img :src="course.course_img" alt="">
<span>{{course.name}}</span>
</el-col>
<el-col :span="4">
<el-select v-model="duration">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-col>
<el-col :span="4" class="course-price">¥{{course.price}}</el-col>
<el-col :span="4" class="course-delete"><span @click="delete_course(course.id)">删除</span></el-col>
</el-row>
</div>
</template> <script>
export default {
name:"CartItem", props:["course","course_key"], data(){
return {
token: localStorage.token || sessionStorage.token,
duration: 60,
options:[
{value:30,label:"一个月有效"},
{value:60,label:"二个月有效"},
{value:90,label:"三个月有效"},
{value:-1,label:"永久有效"},
], }
},
mounted(){ }, methods:{
//按删除键删除购物车的课程
delete_course(course_id){
let _this = this;
this.$axios.delete(`http://127.0.0.1:8000/cart/${this.course.id}`, {
headers: {
// 附带已经登录用户的jwt token 提供给后端,一定不能疏忽这个空格
'Authorization': 'JWT ' + _this.token
},
responseType: "json",
}).then(response => { // 发送信息给父组件,通过父组件删除当前子组件
_this.$emit("delete_course",_this.course_key);
}).catch(error => {
console.log(error.response);
});
},
},
watch:{
"course.selected":function(){
let _this = this;
_this.$axios.post(`http://127.0.0.1:8000/cart/${this.course.id}`,{
is_select: _this.course.selected
},{
headers:{
// 附带已经登录用户的jwt token 提供给后端,一定不能疏忽这个空格
'Authorization':'JWT '+_this.token
},
responseType:"json",
}).then(response=>{
_this.$emit("change_select");
}).catch(error=>{
console.log( error.response );
})
}
},
}
</script> <style scoped>
.cart-item{
height: 250px;
}
.cart-item .el-row{
height: 100%;
}
.course-delete{
font-size: 14px;
color: #ffc210;
cursor: pointer;
}
.el-checkbox,.el-select,.course-price,.course-delete{
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.el-checkbox{
padding-top: 55px;
}
.el-select{
padding-top: 45px;
width: 118px;
height: 28px;
font-size: 12px;
color: #666;
line-height: 18px;
}
.course-info img{
width: 175px;
height: 115px;
margin-right: 35px;
vertical-align: middle;
}
.cart-item .el-col{
padding: 67px 10px;
vertical-align: middle!important;
}
.course-info{ }
</style>

Luffy之购物车页面搭建的更多相关文章

  1. day83:luffy:添加购物车&导航栏购物车数字显示&购物车页面展示

    目录 1.添加购物车+验证登录状态 2.右上方购物车图标的小红圆圈数字 3.Vuex 4.购物车页面展示-后端接口 5.购物车页面展示-前端 6.解决一个购物车数量显示混乱的bug 1.添加购物车+验 ...

  2. 淘宝购物车页面 智能搜索框Ajax异步加载数据

    如果有朋友对本篇文章的一些知识点不了解的话,可以先阅读此篇文章.在这篇文章中,我大概介绍了一下构建淘宝购物车页面需要的基础知识. 这篇文章主要探讨的是智能搜索框Ajax异步加载数据.jQuery的社区 ...

  3. 淘宝购物车页面 PC端和移动端实战

    最近花了半个月的时间,做了一个淘宝购物车页面的Demo.当然,为了能够更加深入的学习,不仅仅有PC端的固定宽度的布局,还实现了移动端在Media Query为768px以下(也就是实现了ipad,ip ...

  4. FineUI小技巧(1)简单的购物车页面

    起因 最初是一位 FineUI 网友对购物车功能的需求,需要根据产品单价和数量来计算所有选中商品的总价. 这个逻辑最好在前台使用JavaScript实现,如果把这个逻辑移动到后台C#实现,则会导致过多 ...

  5. html5与js关于input[type='text']文本框value改变触发事件一些属性的区别oninput,onpropertychange,onchange和文本框的value点击全选状态onclick="select();"。做购物车页面时会要用到。

    关于input[type='text']文本框value改变触发事件一些属性的区别oninput,onpropertychange,onchange和文本框的点击全选状态onclick="s ...

  6. flask-前台布局页面搭建3

    4.前台布局的搭建 由于前端知识有限,我在网上下载的人家的前台源码,附上链接 https://link.jianshu.com/?t=https://github.com/mtianyan/movie ...

  7. 仿联想商城laravel实战---3、前端页面搭建(什么情况下需要路由接参数)

    仿联想商城laravel实战---3.前端页面搭建(什么情况下需要路由接参数) 一.总结 一句话总结: 比如访问课程的时候,不同的课程(比如云知梦),比如访问不同的商品,比如访问不同的分类 //商品详 ...

  8. 仿联想商城laravel实战---2、后端页面搭建(验证码如何在页面中使用)

    仿联想商城laravel实战---2.后端页面搭建(验证码如何在页面中使用) 一.总结 一句话总结: 放在img里面,img的src就是生产验证码的控制器路径: img src="/admi ...

  9. stark组件之显示页面搭建(四)

    页面搭建包括第一如何获取前端传过来的数据,第二如何在前端渲染出对应标签. 一.后台获取数据并进行处理 在路由系统中,每一个路由都对应着一个处理函数,如下所示: def wrapper(self, fu ...

随机推荐

  1. linux c生成唯一文件名称

    linux c生成唯一文件名称可用mktemp()或mkstemp()函数

  2. daemon函数详解

    https://blog.csdn.net/xinyuan510214/article/details/50903280

  3. nvm 安装使用

    事先说明-------先安装nvm,再安装nodejs [nvm参考安装地址] nvm install 6.9.4 # 安装nodejs6.9.4版本 nvm use 6.9.4 # 使用nodejs ...

  4. java框架之Struts2(4)-拦截器&标签库

    拦截器 概述 Interceptor (拦截器):起到拦截客户端对 Action 请求的作用. Filter:过滤器,过滤客户端向服务器发送的请求. Interceptor:拦截器,拦截的是客户端对 ...

  5. ORA-00444: background process DBRM failed while starting

    SQL> startup 报错:ORA-00444: background process DBRM failed while startingORA-00020:maximum number ...

  6. node(03)--利用 HTTP 模块 URl 模块 PATH 模块 FS 模块创建一个 WEB 服务器

    Web 服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,可以向浏览器等 Web 客户端提供文档,也可以放置网站文件,让全世界浏览:可以放置数据文件,让全世界下载.目前最主流的三个 We ...

  7. Python记录13:软件开发目录规范

    软件开发目录规范 开发一个软件,一个工程项目,一般应该具备以下的几个基本的文件夹和模块,当然,这并不是一成不变的,根据项目的不同会有一定的差异,不过作为一个入门级的新手,建议暂时按照以下的规范编写: ...

  8. sql 身份证计算年龄和性别

    IdentityNumber 是身份证号 年龄: ,), GETDATE()) / 365.25) as '推荐人年龄', 15位的身份证计算年龄: case when b.IdentityNumbe ...

  9. Linux基础培训知识点汇总

    一.Linux简介1.Linux操作系统诞生于1991年10月5日,由林纳斯·托瓦兹在comp.os.minix新闻组上发布消息,正式向外宣布Linux内核的诞生.2.Linux同时也衍生了很多版本( ...

  10. py-faster-rcnn代码

    1. 对proposal层NMS的解释,很清晰 注意第18~20行是拿一个数(x1)和array(x1[ [0,2,3] ])去比: