前面已经将一些课程加入购物车中,并保存到了后端的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. 《nginx - 基本操作/配置》

    一:基本操作 - 开启 Nginx nginx -c nginx.conf -  Nginx 的平滑重启 kill -HUP nginx主进程号(平滑重启) -  停止 Nginx * Kill -Q ...

  2. shell脚本----周期压缩备份日志文件

    一.日志文件样式 二.目标 1.备份压缩.log结尾&&时间样式为“date +%Y%m%d”的日志文件(如:20170912.20160311等) 2.可指定压缩范围(N天前至当天) ...

  3. springmvc.xml 中 <url-pattern></url-pattern>节点详解

    1.  先来上段常见的代码 <!-- MVC Servlet --> <servlet> <servlet-name>springServlet</servl ...

  4. PyQt5信号、定时器及多线程

    信号 信号是用于界面自动变化的一个工具,原理是信号绑定了一个函数,当信号被触发时函数即被调用 举个例子 from PyQt5 import QtWidgets,QtCore from untitled ...

  5. python基础(4)-元组&字典&字符串&队列

    元组(tuple) #元组相对列表来说不同之处是只读不可写 读操作和列表一致 letter_tuple = ('a','b','c','d'); print(letter_tuple[0]);#res ...

  6. openshift 容器云从入门到崩溃之九《容器监控-报警》

    容器状态监控 主要是监控POD的状态包括重启.不健康等等这些k8s api 状态本身会报出来,在配合zabbix报警 导入zabbix模板关联上oc master主机 <?xml version ...

  7. python常用函数总结

    原文地址https://www.cnblogs.com/nice107/p/8118876.html 我们在学习python的时候,接触最多的往往则是那些函数,对于python函数,在这里为大家总结归 ...

  8. python_的面向对象编程

    废话不多说,先弄个对象来看看 class Student(object): def __init__(self, name, score): self.name = name self.score = ...

  9. 关于table的td和ul元素li隔行变色的功能实现

    table元素的td和ul元素li隔行变色的功能实现 利用css控制二者的样式轻松实现隔行换色: 例如:table的css样式控制: table tr td{   background-color:颜 ...

  10. poj3155 最大密度子图

    求最大密度子图 记得在最后一次寻找的时候记得将进入的边放大那么一点点,这样有利于当每条边都满流的情况下会选择点 #include <iostream> #include <algor ...