下单结算
客户端展示下单结算界面展示
views/Cart.vue
,代码:
<div class="li-3"><router-link to="/order" class="btn">去结算</router-link></div>
router/index.js
,代码:
// 路由列表
const routes = [
{
meta:{
title: "luffy2.0-首页",
keepAlive: true
},
path:'/', // uri访问地址
component: ()=> import("../views/Home.vue")
},{
meta:{
title: "登录",
keepAlive: true
},
path: '/login',
name: "Login", // 路由名称
component: ()=> import("../views/Login.vue"), // uri绑定的组件页面
},{
meta:{
title: "注册",
keepAlive: true
},
path: '/register',
name: "Register", // 路由名称
component: ()=> import("../views/Register.vue"), // uri绑定的组件页面
},{
meta:{
title: "项目课",
keepAlive: true
},
path: '/project',
name: "Course", // 路由名称
component: ()=> import("../views/Course.vue"), // uri绑定的组件页面
},{
meta:{
title: "项目课",
keepAlive: true
},
path: '/project/:id',
name: "Info",
component: ()=> import("../views/Info.vue"),
},{
meta:{
title: "购物车",
keepAlive: true
},
path: '/cart',
name: "Cart",
component: ()=> import("../views/Cart.vue"),
},{
meta:{
title: "确认下单",
keepAlive: true
},
path: '/order',
name: "Order",
component: ()=> import("../views/Order.vue"),
}
]
views/Order.vue
,代码:
<template>
<div class="cart">
<Header/>
<div class="cart-main">
<div class="cart-header">
<div class="cart-header-warp">
<div class="cart-title left">
<h1 class="left">确认订单</h1>
</div>
<div class="right">
<div class="">
<span class="left"><router-link class="myorder-history" to="/cart">返回购物车</router-link></span>
</div>
</div>
</div>
</div>
<div class="cart-body" id="cartBody">
<div class="cart-body-title"><p class="item-1 l">课程信息</p></div>
<div class="cart-body-table">
<div class="item">
<div class="item-2">
<a href="" class="img-box l"><img src="../assets/course-9.png"></a>
<dl class="l has-package">
<dt>【实战课程】3天Typescript精修 </dt>
<p class="package-item">减免价</p>
</dl>
</div>
<div class="item-3">
<div class="price">
<p class="discount-price"><em>¥</em><span>998.00</span></p>
<p class="original-price"><em>¥</em><span>800.00</span></p>
</div>
</div>
</div>
<div class="item">
<div class="item-2">
<a href="" class="img-box l"><img src="../assets/course-9.png"></a>
<dl class="l has-package">
<dt>【实战课程】3天Typescript精修 </dt>
<p class="package-item">减免价</p>
</dl>
</div>
<div class="item-3">
<div class="price">
<p class="discount-price"><em>¥</em><span>998.00</span></p>
<p class="original-price"><em>¥</em><span>800.00</span></p>
</div>
</div>
</div>
<div class="item">
<div class="item-2">
<a href="" class="img-box l"><img src="../assets/course-9.png"></a>
<dl class="l has-package">
<dt>【实战课程】3天Typescript精修 </dt>
<p class="package-item">减免价</p>
</dl>
</div>
<div class="item-3">
<div class="price">
<p class="discount-price"><em>¥</em><span>998.00</span></p>
<p class="original-price"><em>¥</em><span>800.00</span></p>
</div>
</div>
</div>
<div class="item">
<div class="item-2">
<a href="" class="img-box l"><img src="../assets/course-9.png"></a>
<dl class="l has-package">
<dt>【实战课程】3天Typescript精修 </dt>
<p class="package-item">减免价</p>
</dl>
</div>
<div class="item-3">
<div class="price">
<p class="discount-price"><em>¥</em><span>998.00</span></p>
<p class="original-price"><em>¥</em><span>800.00</span></p>
</div>
</div>
</div>
<div class="item">
<div class="item-2">
<a href="" class="img-box l"><img src="../assets/course-9.png"></a>
<dl class="l has-package">
<dt>【实战课程】3天Typescript精修 </dt>
<p class="package-item">减免价</p>
</dl>
</div>
<div class="item-3">
<div class="price">
<p class="discount-price"><em>¥</em><span>998.00</span></p>
<p class="original-price"><em>¥</em><span>800.00</span></p>
</div>
</div>
</div>
</div>
<div class="coupons-box">
<div class="coupon-title-box">
<p class="coupon-title">
使用优惠券/积分
<span v-if="state.use_coupon" @click="state.use_coupon=!state.use_coupon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" data-v-394d1fd8=""><path fill="currentColor" d="M831.872 340.864 512 652.672 192.128 340.864a30.592 30.592 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728 30.592 30.592 0 0 0-42.752 0z"></path></svg></span>
<span v-else @click="state.use_coupon=!state.use_coupon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" data-v-394d1fd8=""><path fill="currentColor" d="m488.832 344.32-339.84 356.672a32 32 0 0 0 0 44.16l.384.384a29.44 29.44 0 0 0 42.688 0l320-335.872 319.872 335.872a29.44 29.44 0 0 0 42.688 0l.384-.384a32 32 0 0 0 0-44.16L535.168 344.32a32 32 0 0 0-46.336 0z"></path></svg></span>
<!-- <i :class="state.use_coupon?'el-icon-arrow-up':'el-icon-arrow-down'" @click="state.use_coupon=!state.use_coupon"></i>-->
</p>
</div>
<transition name="el-zoom-in-top">
<div class="coupon-del-box" v-if="state.use_coupon">
<div class="coupon-switch-box">
<div class="switch-btn ticket" :class="{'checked': state.discount_type===0}" @click="state.discount_type=0">优惠券 (4)<em><i class="imv2-check"></i></em></div>
<div class="switch-btn code" :class="{'checked': state.discount_type===1}" @click="state.discount_type=1">积分<em><i class="imv2-check"></i></em></div>
</div>
<div class="coupon-content ticket" v-if="state.discount_type===0">
<p class="no-coupons" v-if="state.coupon_list.length<1">暂无可用优惠券</p>
<div class="coupons-box" v-else>
<div class="content-box">
<ul class="nouse-box">
<li class="l">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥100 </p>
<p class="use-inst l">满499可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
<li class="l select">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥248 </p>
<p class="use-inst l">满999可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
<li class="l wait-use">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥248 </p>
<p class="use-inst l">满999可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
<li class="l wait-use">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥248 </p>
<p class="use-inst l">满999可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
</ul>
<ul class="use-box">
<li class="l useing">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥100 </p>
<p class="use-inst l">满499可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
<li class="l">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥248 </p>
<p class="use-inst l">满999可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
</ul>
<ul class="overdue-box">
<li class="l useing">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥100 </p>
<p class="use-inst l">满499可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
<li class="l">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥248 </p>
<p class="use-inst l">满999可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="coupon-content code" v-else>
<div class="input-box">
<el-input-number placeholder="10积分=1元" v-model="state.credit" :step="1" :min="0" :max="1000"></el-input-number>
<a class="convert-btn">兑换</a>
</div>
<div class="converted-box">
<p>使用积分:<span class="code-num">200</span></p>
<p class="course-title">课程:<span class="c_name">3天JavaScript入门</span>
<span class="discount-cash">100积分抵扣:<em>10</em>元</span>
</p>
<p class="course-title">课程:<span class="c_name">3天JavaScript入门</span>
<span class="discount-cash">100积分抵扣:<em>10</em>元</span>
</p>
</div>
<p class="error-msg">本次订单最多可以使用1000积分,您当前拥有200积分。(10积分=1元)</p>
<p class="tip">说明:每笔订单只能使用一次积分,并只有在部分允许使用积分兑换的课程中才能使用。</p>
</div>
</div>
</transition>
</div>
<div class="pay-type">
<p class="title">选择支付方式</p>
<div class="list">
<img :src="state.pay_type==0?'/src/assets/alipay2.png':'/src/assets/alipay1.png'" @click="state.pay_type=0" alt="支付宝">
<img :src="state.pay_type==1?'/src/assets/wechat2.png':'/src/assets/wechat1.png'" @click="state.pay_type=1" alt="微信">
<img :src="state.pay_type==2?'/src/assets/yue2.png':'/src/assets/yue1.png'" @click="state.pay_type=2" alt="余额">
</div>
</div>
<div class="pay-box" :class="{fixed:state.fixed}">
<div class="row-bottom">
<div class="row">
<div class="goods-total-price-box">
<p class="r rw price-num"><em>¥</em><span>1811.00</span></p>
<p class="r price-text"><span>共<span>5</span>件商品,</span>商品总金额:</p>
</div>
</div>
<div class="coupons-discount-box">
<p class="r rw price-num">-<em>¥</em><span>60.00</span></p>
<p class="r price-text">优惠券/积分抵扣:</p>
</div>
<div class="pay-price-box clearfix">
<p class="r rw price"><em>¥</em><span id="js-pay-price">1751.00</span></p>
<p class="r price-text">应付:</p>
</div>
<span class="r btn btn-red submit-btn">提交订单</span>
</div>
<div class="pay-add-sign">
<ul class="clearfix">
<li>支持花呗</li>
<li>可开发票</li>
<li class="drawback">7天可退款</li>
</ul>
</div>
</div>
</div>
</div>
<Footer/>
</div>
</template>
<script setup>
import {reactive,watch} from "vue"
import Header from "../components/Header.vue"
import Footer from "../components/Footer.vue"
import {useStore} from "vuex";
let store = useStore()
let state = reactive({
course_list: [], // 购物车中的商品课程列表
total_price: 0, // 勾选商品的总价格
use_coupon: false, // 用户是否使用优惠
discount_type: 0, // 0表示优惠券,1表示积分
coupon_list:[1,2,3], // 用户拥有的可用优惠券列表
select: -1, // 当前用户选中的优惠券
credit: 0, // 当前用户选择抵扣的积分
fixed: true, // 底部订单总价是否固定浮动
pay_type: 0, // 支付方式
})
// 监听用户选择的支付方式
watch(
()=>state.pay_type,
()=>{
console.log(state.pay_type)
}
)
// 底部订单总价信息固定浮动效果
window.onscroll = ()=>{
let cart_body_table = document.querySelector(".cart-body-table")
let offsetY = window.scrollY
let maxY = cart_body_table.offsetTop+cart_body_table.offsetHeight
state.fixed = offsetY < maxY
}
</script>
<style scoped>
.cart-header {
height: 160px;
background-color: #e3e6e9;
background: url("/src/assets/cart-header-bg.jpeg") repeat-x;
background-size: 38%;
}
.cart-header .cart-header-warp {
width: 1500px;
height: 120px;
line-height: 120px;
margin-left: auto;
margin-right: auto;
font-size: 14px
}
.cart-header .cart-header-warp .myorder-history {
font-weight: 200
}
.cart-header .left {
float: left
}
.cart-header .right {
float: right
}
.cart-header .cart-title {
color: #4d555d;
font-weight: 200;
font-size: 14px
}
.cart-header .cart-title h1 {
font-size: 32px;
line-height: 115px;
margin-right: 25px;
color: #07111b;
font-weight: 200
}
.cart-header .cart-title span {
margin: 0 4px
}
.l {
float: left;
}
.r {
float: right;
}
.cart-body {
width: 1500px;
padding: 0 36px 32px;
background-color: #fff;
margin-top: -40px;
margin-left: auto;
margin-right: auto;
box-shadow: 0 8px 16px 0 rgba(7,17,27, .1);
border-radius: 8px;
box-sizing: border-box
}
.cart-body .left {
float: left!important
}
.cart-body .right {
float: right!important
}
.cart-body .cart-body-title {
min-height: 88px;
line-height: 88px;
border-bottom: 1px solid #b7bbbf;
box-sizing: border-box
}
body {
background: #f8fafc
}
.cart-body .cart-body-title span {
font-size: 14px
}
.cart-body .cart-body-title .item-1>span,
.cart-body .cart-body-title .item-2>span,
.cart-body .cart-body-title .item-3>span{
display: inline-block;
font-size: 14px;
line-height: 24px;
color: #4d555d
}
.cart-body .cart-body-title .item-1>span {
color: #93999f
}
.cart-body .cart-body-title .item-2>span {
margin-left: 40px
}
.cart-body .item {
height: 88px;
padding: 24px 0;
background: #f3f5f7;
}
.cart-body .cart-body-table {
padding-bottom: 36px;
border-bottom: 1px solid #d9dde1;
}
.cart-body .item>div {
float: left
}
.cart-body .item .item-1 {
padding-top: 34px;
position: relative;
z-index: 1
}
.cart-body .item:last-child>.item-1::after {
display: none
}
.cart-body .item-1 {
width: 120px
}
.cart-body .item-1 i {
margin-left: 12px;
margin-right: 8px;
font-size: 24px
}
.cart-body .item-2 {
width: 1020px;
position:relative;
}
.cart-body .item-2>span{
line-height: 88px;
}
.cart-body .item-2 dl {
width: 464px;
margin-left: 24px;
padding-top: 12px
}
.cart-body .item-2 dl a {
display: block;
}
.cart-body .item-2 dl.has-package {
padding-top: 4px;
}
.cart-body .item-2 dl.has-package .package-item {
display: inline-block;
padding: 0 12px;
margin-top: 4px;
font-size: 12px;
color: rgba(240,20,20, .6);
line-height: 24px;
background: rgba(240,20,20, .08);
border-radius: 12px;
cursor: pointer
}
.cart-body .item-2 dl.has-package .package-item:hover {
color: #fff;
background: rgba(240,20,20, .2)
}
.cart-body .item-2 dt {
font-size: 16px;
color: #07111b;
line-height: 24px;
margin-bottom: 4px
}
.cart-body .item-2 .img-box {
display: block;
margin-left: 42px;
}
.cart-body .item-2 .img-box img{
height: 94px;
}
.cart-body .item-2 dd {
font-size: 12px;
color: #93999f;
line-height: 24px;
font-weight: 200
}
.cart-body .item-2 dd a {
display: inline-block;
margin-left: 12px;
color: rgba(240,20,20, .4)
}
.cart-body .item-2 dd a:hover {
color: #f01414
}
.cart-body .item-3 {
width: 280px;
margin-left: 48px;
position: relative;
}
.cart-body .item-3 .price {
display: inline-block;
height: 46px;
width: 96px;
padding-top: 24px;
padding-bottom: 24px;
color: #f01414;
}
.cart-body .item-3 .price em,
.cart-body .item-3 .price span{
font-size: 18px;
}
.cart-body .item-3 .price .original-price em,
.cart-body .item-3 .price .original-price span{
font-size: 15px;
color: #aaa;
text-decoration: line-through;
}
.cart-body .cart-body-bot li {
float: left
}
.cart-body .cart-body-bot .li-1 em,
.cart-body .cart-body-bot .li-3 em {
font-style: normal;
color: red
}
.cart-body .cart-body-bot .li-2 .price {
font-size: 16px;
color: #f01414;
line-height: 24px;
font-weight: 700
}
.coupons-box::after{
display: block;
content: "";
overflow: hidden;
clear: both;
}
.coupons-box .coupon-title-box {
margin: 27px 0 0 12px
}
.coupons-box .coupon-title-box .coupon-title {
color: #07111b;
font-size: 16px;
line-height: 34px
}
.coupons-box .coupon-title-box .coupon-title svg {
position: relative;
width: 26px;
height: 26px;
top: 5px;
margin-left: 12px;
font-size: 24px;
color: #999;
cursor: pointer
}
.coupons-box .coupon-del-box {
width: 100%;
padding-top: 24px;
box-sizing: border-box
}
.coupons-box .coupon-del-box .coupon-switch-box {
margin-bottom: 16px
}
.coupons-box .coupon-del-box .coupon-switch-box .switch-btn {
position: relative;
display: inline-block;
width: 138px;
height: 58px;
line-height: 20px;
border: 1px solid #d9dde1;
border-radius: 8px;
padding: 18px 0;
color: #1c1f21;
text-align: center;
font-size: 16px;
margin-right: 16px;
box-sizing: border-box;
cursor: pointer
}
.coupons-box .coupon-del-box .coupon-switch-box .switch-btn em {
display: none;
position: absolute;
bottom: 0;
right: 0;
width: 0;
height: 0;
line-height: 54px;
border-left-width: 20px;
border-left-style: solid;
border-left-color: transparent;
border-bottom-width: 20px;
border-bottom-style: solid;
border-bottom-color: #f01414
}
.coupons-box .coupon-del-box .coupon-switch-box .switch-btn em i {
color: #fff;
position: absolute;
bottom: -20px;
right: 0;
font-size: 12px
}
.coupons-box .coupon-del-box .coupon-switch-box .switch-btn.checked {
border: 2px solid #f01414
}
.coupons-box .coupon-del-box .coupon-switch-box .switch-btn.checked em {
display: block
}
.coupons-box .coupon-del-box .coupon-content {
position: relative;
background: #f3f5f7;
border-radius: 8px;
padding: 24px
}
.coupons-box .coupon-del-box .coupon-content:before {
content: "";
display: block;
position: absolute;
top: -7px;
left: 62px;
border-left: 12px solid transparent;
border-right: 12px solid transparent;
border-bottom: 7px solid #f3f5f7
}
.coupons-box .coupon-del-box .coupon-content.ticket li {
padding-top: 8px;
box-sizing: border-box;
width: 320px;
background-color: #fff6f0;
cursor: pointer;
margin: 12px
}
.coupons-box .coupon-del-box .coupon-content.ticket li .more-del-box {
padding: 16px 22px 24px 22px;
width: 100%;
box-sizing: border-box;
background-repeat: no-repeat
}
.coupons-box .coupon-del-box .coupon-content.ticket li .price-box {
height: 32px;
line-height: 32px
}
.coupons-box .coupon-del-box .coupon-content.ticket li .price-box .price {
font-size: 30px;
margin-right: 4px
}
.coupons-box .coupon-del-box .coupon-content.ticket li .price-box .price sub {
font-size: 24px;
letter-spacing: -5px
}
.coupons-box .coupon-del-box .coupon-content.ticket li .price-box .use-inst {
font-size: 12px;
margin-top: 5px;
}
.coupons-box .coupon-del-box .coupon-content.ticket .active .price,
.coupons-box .coupon-del-box .coupon-content.ticket .active .use-inst {
color: #fff
}
.coupons-box .coupon-del-box .coupon-content.ticket .active i {
position: absolute;
top: 12px;
right: 12px;
color: #fff;
font-size: 24px
}
.coupons-box .coupon-del-box .coupon-content.ticket .no-coupons {
font-size: 14px;
color: #4d555d;
line-height: 14px
}
.coupons-box .coupon-del-box .coupon-content.code {
padding-left: 38px
}
.coupons-box .coupon-del-box .coupon-content.code:before {
left: 216px
}
.coupons-box .coupon-del-box .coupon-content.code .input-box {
position: relative;
left: -12px;
margin-top: 12px
}
.coupons-box .coupon-del-box .coupon-content.code .input-box .convert-input {
background: #fff;
border: 1px solid #9199a1;
width: 356px;
height: 48px;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
color: #07111b;
letter-spacing: 2px;
line-height: 24px;
padding: 12px 16px;
box-sizing: border-box;
vertical-align: middle
}
.coupons-box .coupon-del-box .coupon-content.code .input-box .convert-btn {
display: inline-block;
width: 124px;
height: 48px;
line-height: 22px;
font-size: 16px;
color: #fff;
padding: 12px;
background: #f01414;
border-radius: 8px;
margin-left: 24px;
box-sizing: border-box;
text-align: center;
cursor: pointer
}
.coupons-box .coupon-del-box .coupon-content.code .converted-box p {
line-height: 24px;
font-size: 16px;
color: #07111b;
margin-top: 10px;
}
.coupons-box .coupon-del-box .coupon-content.code .converted-box .c_name,
.coupons-box .coupon-del-box .coupon-content.code .converted-box .code-num {
padding-left: 8px
}
.coupons-box .coupon-del-box .coupon-content.code .converted-box .cancel-btn {
background: #fff;
border: 1px solid #d9dde1;
line-height: 20px;
padding: 2px 12px;
text-align: center;
border-radius: 4px;
color: #f01414;
font-size: 14px;
margin-left: 16px;
cursor: pointer
}
.coupons-box .coupon-del-box .coupon-content.code .converted-box .course-title {
font-size: 14px;
color: #07111b;
font-weight: 600;
margin-top: 12px
}
.coupons-box .coupon-del-box .coupon-content.code .converted-box .course-title .discount-cash {
margin-left: 12px;
color: #f01414
}
.coupons-box .coupon-del-box .coupon-content.code .error-msg {
font-size: 14px;
color: #f01414;
margin-top: 8px;
line-height: 20px;
height: 20px
}
.coupons-box .coupon-del-box .coupon-content.code .tip {
font-size: 14px;
color: #93999f;
margin-top: 8px;
line-height: 20px
}
.coupons-box .content-box ul {
width: 100%
}
.coupons-box .content-box .nouse-box::after,
.coupons-box .content-box .overdue-box::after,
.coupons-box .content-box .use-box::after {
display: block;
content: "";
overflow: hidden;
clear: both;
}
.coupons-box .content-box .nouse-box li,
.coupons-box .content-box .overdue-box li,
.coupons-box .content-box .use-box li {
position: relative;
padding: 24px 32px;
margin-right: 16px;
margin-bottom: 16px;
width: 320px;
height: 144px;
border-radius: 8px;
box-sizing: border-box;
background-color: #fff;
box-shadow: 0 8px 16px 0 rgba(7,17,27, .2);
background-repeat: no-repeat;
background-size: 320px 144px;
}
.coupons-box .content-box .nouse-box li.select{
background-color: orangered;
}
.coupons-box .content-box .nouse-box li .detail-box,
.coupons-box .content-box .overdue-box li .detail-box,
.coupons-box .content-box .use-box li .detail-box {
width: 100%;
height: 100%
}
.coupons-box .content-box .nouse-box li .detail-box .price-box,
.coupons-box .content-box .overdue-box li .detail-box .price-box,
.coupons-box .content-box .use-box li .detail-box .price-box {
margin-bottom: 8px;
height: 40px;
color: #93999f;
line-height: 40px;
font-weight: 700
}
.coupons-box .content-box .nouse-box li .detail-box .price-box .coupon-price,
.coupons-box .content-box .overdue-box li .detail-box .price-box .coupon-price,
.coupons-box .content-box .use-box li .detail-box .price-box .coupon-price {
margin-right: 12px;
font-size: 36px;
margin-top: 5px;
}
.coupons-box .content-box .nouse-box li .detail-box .price-box .use-inst,
.coupons-box .content-box .overdue-box li .detail-box .price-box .use-inst,
.coupons-box .content-box .use-box li .detail-box .price-box .use-inst {
font-size: 14px
}
.coupons-box .content-box .nouse-box li .detail-box .use-detail-box,
.coupons-box .content-box .overdue-box li .detail-box .use-detail-box,
.coupons-box .content-box .use-box li .detail-box .use-detail-box {
font-size: 12px;
color: #93999f;
line-height: 24px
}
.coupons-box .content-box .nouse-box li .detail-box .use-detail-box .use-ajust-box,
.coupons-box .content-box .overdue-box li .detail-box .use-detail-box .use-ajust-box,
.coupons-box .content-box .use-box li .detail-box .use-detail-box .use-ajust-box {
position: relative
}
.coupons-box .content-box .nouse-box li .detail-box .use-detail-box .use-ajust-box i,
.coupons-box .content-box .overdue-box li .detail-box .use-detail-box .use-ajust-box i,
.coupons-box .content-box .use-box li .detail-box .use-detail-box .use-ajust-box i {
position: relative;
top: 3px;
left: 0;
font-size: 16px;
color: #93999f;
line-height: 24px;
cursor: pointer
}
.coupons-box .content-box .nouse-box li .detail-box .use-detail-box .use-ajust-box .use-course a,
.coupons-box .content-box .overdue-box li .detail-box .use-detail-box .use-ajust-box .use-course a,
.coupons-box .content-box .use-box li .detail-box .use-detail-box .use-ajust-box .use-course a {
padding: 16px 0;
width: 100%;
display: block;
font-size: 12px;
color: #4d555d;
line-height: 20px;
border-bottom: 1px solid #d9dde1;
box-sizing: border-box
}
.coupons-box .content-box .nouse-box li .detail-box .use-detail-box .use-ajust-box .use-course a:hover,
.coupons-box .content-box .overdue-box li .detail-box .use-detail-box .use-ajust-box .use-course a:hover,
.coupons-box .content-box .use-box li .detail-box .use-detail-box .use-ajust-box .use-course a:hover {
color: #07111b
}
.coupons-box .content-box .nouse-box li .detail-box .use-detail-box .use-ajust-box .use-course a:last-child,
.coupons-box .content-box .overdue-box li .detail-box .use-detail-box .use-ajust-box .use-course a:last-child,
.coupons-box .content-box .use-box li .detail-box .use-detail-box .use-ajust-box .use-course a:last-child {
border-bottom: none
}
.coupons-box .content-box li {
background-image: url(/src/assets/coupons_bg.png)
}
.coupons-box .content-box .nouse-box li .detail-box .price-box .coupon-price {
color: #f01414
}
.coupons-box .content-box .nouse-box li .detail-box .price-box .use-inst {
color: #f01414
}
.coupons-box .content-box .nouse-box li .detail-box .use-detail-box {
color: #07111b
}
.coupons-box .content-box .nouse-box li .detail-box .use-detail-box .use-ajust-box i {
color: #4d555d
}
.coupons-box .content-box .nouse-box li.wait-use {
background-image: url(/src/assets/coupon_start_bg.png)
}
.coupons-box .content-box .use-box li {
background-image: url(/src/assets/coupons_used_bg.png)
}
.coupons-box .content-box .use-box li.useing {
background-image: url(/src/assets/coupon_useing_bg.png)
}
.coupons-box .content-box .overdue-box li {
background-image: url(/src/assets/coupons_overdue.png)
}
.tip-box ol {
margin-top: 16px;
width: 100%;
list-style: decimal;
margin-left: 14px;
box-sizing: border-box
}
.tip-box ol li {
font-size: 12px
}
.pay-box {
margin-top: 36px;
position: relative
}
.pay-box::after,
.goods-total-price-box::after,
.package-discount-box::after,
.pay-price-box::after,
.coupons-discount-box::after{
display: block;
content: "";
clear: both;
overflow: hidden;
}
.pay-box .rw {
width: 140px;
box-sizing: border-box;
text-align: right
}
.pay-box .bargain-discount-box,.pay-box .coupons-discount-box,.pay-box .goods-total-price-box,.pay-box .package-discount-box,.pay-box .redpackage-discount-box,.pay-box .student-discount-box {
margin-bottom: 12px;
line-height: 26px
}
.pay-box .bargain-discount-box .price-num,.pay-box .coupons-discount-box .price-num,.pay-box .goods-total-price-box .price-num,.pay-box .package-discount-box .price-num,.pay-box .redpackage-discount-box .price-num,.pay-box .student-discount-box .price-num {
position: relative;
font-size: 14px;
color: #07111b
}
.pay-box .bargain-discount-box .price-text,.pay-box .coupons-discount-box .price-text,.pay-box .goods-total-price-box .price-text,.pay-box .package-discount-box .price-text,.pay-box .redpackage-discount-box .price-text,.pay-box .student-discount-box .price-text {
text-align: right;
font-size: 14px;
color: #07111b
}
.pay-box .bargain-discount-box .price-text span,.pay-box .coupons-discount-box .price-text span,.pay-box .goods-total-price-box .price-text span,.pay-box .package-discount-box .price-text span,.pay-box .redpackage-discount-box .price-text span,.pay-box .student-discount-box .price-text span {
margin-left: 4px;
margin-right: 4px
}
.pay-box .pay-add-sign {
text-align: right;
position: absolute;
top: -10px
}
.pay-box .pay-add-sign li {
float: left;
padding: 0 12px;
height: 26px;
line-height: 26px;
border: 1px solid #f01414;
border-radius: 18px;
font-size: 12px;
color: #f01414;
margin-right: 15px
}
.pay-box .pay-add-sign li.drawback {
position: relative
}
.pay-box .pay-add-sign li.drawback .imv2-ques {
position: absolute;
top: -4px;
right: -2px;
background: #fff;
color: #d7dbdf;
font-size: 14px;
display: inline-block;
width: 14px;
height: 14px;
cursor: pointer
}
.pay-box .pay-add-sign li.drawback .imv2-ques:hover {
color: #f20d0d
}
.pay-box .pay-add-sign a.checkbackbtn {
display: none;
color: #fff;
font-size: 12px;
text-align: center;
border-radius: 8px;
vertical-align: top;
position: absolute;
left: 100%;
top: -12px;
background: rgba(28,31,33,.25);
width: 100px;
height: 26px;
line-height: 26px;
margin-left: 8px
}
.pay-box .pay-add-sign a.checkbackbtn i.arrow {
width: 0;
height: 0;
border-top: 5px solid transparent;
border-right: 5px solid;
border-bottom: 5px solid transparent;
position: absolute;
left: -5px;
top: 8px;
border-right-color: rgba(28,31,33,.25)
}
.pay-box .pay-price-box {
color: #07111b
}
.pay-box .pay-price-box .price {
position: relative;
color: #f01414;
font-size: 24px;
font-weight: 700;
line-height: 36px;
height: 36px;
}
.pay-box .pay-price-box .price-text{
line-height: 36px;
height: 36px;
}
.pay-box .pay-price-box .price span {
float: none;
font-weight: 700
}
.pay-box .pay-account {
font-size: 12px;
color: #93999f;
line-height: 24px;
margin-bottom: 20px;
margin-top: 15px
}
.pay-box .submit-btn {
padding: 0;
width: 140px;
height: 40px;
margin-top: 12px;
text-align: center;
font-size: 14px;
line-height: 40px;
border-radius: 24px
}
.pay-box .disabled {
background: #ccc;
cursor: not-allowed;
border: none
}
.pay-box .presale-wrap {
text-align: right
}
.pay-box .presale-wrap .submit-btn {
margin-top: 24px
}
.pay-box .presale-box {
display: inline-block;
font-size: 0;
text-align: left
}
.pay-box .presale-box .step {
width: 213px;
padding-bottom: 10px;
position: relative
}
.pay-box .presale-box .step .title {
font-size: 14px;
color: #07111b;
line-height: 26px
}
.pay-box .presale-box .step .title .price {
color: #93999f;
float: right
}
.pay-box .presale-box .step .title .price.active {
color: #f01414
}
.pay-box .presale-box .step .desc {
font-size: 12px;
color: #93999f;
line-height: 16px
}
.pay-box .presale-box .step:nth-child(3) .price {
color: #f01414;
font-size: 24px;
font-weight: 700
}
.pay-box .presale-box .step .step-line {
position: absolute;
top: 8px;
left: -16px;
width: 9px;
display: flex;
flex-direction: column;
align-items: center
}
.pay-box .presale-box .step .step-line .circle {
width: 9px;
height: 9px;
border-radius: 50%;
background: rgba(147,153,159,.3)
}
.pay-box .presale-box .step .step-line .circle.active {
background: #f01414
}
.pay-box .presale-box .step .step-line .line {
height: 43px;
border-left: 1px dashed rgba(147,153,159,.3)
}
.pay-box .presale-box .step .step-line .line.short {
height: 27px
}
.pay-box.fixed {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 80px;
line-height: 80px;
background-color: #fff;
z-index: 300;
box-shadow: 10px -2px 12px rgba(7,17,27,.2);
padding-top: 10px;
}
.pay-box.fixed .row-bottom {
max-width: 1500px;
position: relative;
margin: 0 auto;
}
.pay-box.fixed .row-bottom .row {
float: left
}
.pay-box.fixed .row-bottom .bargain-discount-box,.pay-box.fixed .row-bottom .coupons-discount-box,.pay-box.fixed .row-bottom .js-total-hide,.pay-box.fixed .row-bottom .package-discount-box {
display: none
}
.pay-box.fixed .bargain-discount-box,.pay-box.fixed .coupons-discount-box,.pay-box.fixed .goods-total-price-box,.pay-box.fixed .package-discount-box,.pay-box.fixed .pay-add-sign,.pay-box.fixed .pay-price-box,.pay-box.fixed .redpackage-discount-box {
float: left;
margin-bottom: 0
}
.pay-box.fixed .coupons-discount-box,.pay-box.fixed .package-discount-box,.pay-box.fixed .redpackage-discount-box {
margin-left: 20px
}
.pay-box.fixed .goods-total-price-box {
width: auto
}
.pay-box.fixed .rw {
text-align: left;
width: auto
}
.pay-box.fixed .price,.pay-box.fixed .price-num,.pay-box.fixed .price-text {
line-height: 80px
}
.pay-box.fixed .pay-add-sign {
position: static!important;
margin-left: 20px
}
.pay-box.fixed .pay-add-sign li {
float: left;
padding: 0 12px;
height: 26px;
line-height: 26px;
border: 1px solid #f01414;
border-radius: 18px;
font-size: 12px;
color: #f01414;
margin: 27px 20px 27px 0
}
.pay-box.fixed .pay-price-box {
width: auto;
margin-left: 20px
}
.pay-box.fixed .submit-btn {
margin-top: 16px;
width: 148px;
height: 48px;
line-height: 48px;
font-size: 16px;
border-radius: 24px
}
.pay-box.fixed .presale-wrap {
float: left;
text-align: left
}
.pay-box.fixed .presale-wrap .presale-box {
height: 80px;
display: flex;
align-items: center
}
.pay-box.fixed .presale-wrap .presale-box .step {
padding-right: 38px;
padding-bottom: 0;
width: auto;
min-width: 118px;
height: 45px
}
.pay-box.fixed .presale-wrap .presale-box .step:nth-child(3) {
height: auto
}
.pay-box.fixed .presale-wrap .presale-box .step .title {
float: none;
background: #fff
}
.pay-box.fixed .presale-wrap .presale-box .step .title .price {
line-height: 26px;
float: none
}
.pay-box.fixed .presale-wrap .presale-box .step .step-line {
flex-direction: row;
width: 100%;
left: -14px
}
.pay-box.fixed .presale-wrap .presale-box .step .step-line .line {
border-left: none;
border-top: 1px dashed rgba(147,153,159,.3);
width: 30px;
height: 1px;
position: absolute;
right: 5px
}
.pay-box.fixed .presale-wrap .presale-box .step .step-line .circle:nth-child(3) {
position: absolute;
right: -10px
}
.btn {
position: relative;
display: inline-block;
margin-bottom: 0;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
text-decoration: none;
box-sizing: border-box;
background-image: none;
-webkit-appearance: none;
white-space: nowrap;
outline: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border-style: solid;
border-width: 1px;
cursor: pointer;
transition: all .3s;
color: #545c63;
background-color: transparent;
border-color: #9199a1;
opacity: 1;
padding: 7px 16px;
font-size: 14px;
line-height: 1.42857143;
border-radius: 18px;
}
.btn-red {
border-style: solid;
border-width: 1px;
cursor: pointer;
-moz-transition: all .3s;
transition: all .3s;
color: #fff;
background-color: #f20d0d;
border-color: #f20d0d;
opacity: 1;
}
.btn-red:hover {
color: #fff;
border-color: #c20a0a;
background: #c20a0a;
opacity: 1;
}
.pay-type {
margin-top: 28px;
margin-left: 12px;
}
.pay-type .title {
margin-top: 28px;
}
.pay-type .list {
padding-top: 20px;
}
.pay-type .list img {
margin-right: 10px;
}
</style>
提交代码版本
cd /home/moluo/Desktop/luffycity
git add .
git commit -m "feature: 客户端展示下单结算页面"
git push
展示购物车勾选商品列表
服务端实现购物车勾选商品列表的api接口
cart/views
,视图,代码:
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from django_redis import get_redis_connection
from courses.models import Course
# .... 中间代码省略
class CartOrderAPIView(APIView):
"""购物车确认下单接口"""
# 保证用户必须是登录状态才能调用当前视图
permission_classes = [IsAuthenticated]
def get(self,request):
"""获取勾选商品列表"""
# 查询购物车中的商品课程ID列表
user_id = request.user.id
redis = get_redis_connection("cart")
cart_hash = redis.hgetall(f"cart_{user_id}")
"""
cart_hash = {
# b'商品课程ID': b'勾选状态',
b'2': b'1',
b'4': b'1',
b'5': b'1'
}
"""
if len(cart_hash) < 1:
return Response({"errmsg": "购物车没有任何商品。"}, status=status.HTTP_204_NO_CONTENT)
# 把redis中的购物车勾选课程ID信息转换成普通列表
cart_list = [int(course_id.decode()) for course_id, selected in cart_hash.items() if selected == b'1']
course_list = Course.objects.filter(pk__in=cart_list, is_deleted=False, is_show=True).all()
# 把course_list进行遍历,提取课程中的信息组成列表
data = []
for course in course_list:
data.append({
"id": course.id,
"name": course.name,
"course_cover": course.course_cover.url,
"price": float(course.price),
"discount": course.discount,
"course_type": course.get_course_type_display(),
})
# 返回客户端
return Response({"errmsg": "ok!", "cart": data})
cart/urls.py
,路由,代码:
from django.urls import path
from . import views
urlpatterns = [
path("", views.CartAPIView.as_view()),
path("order/", views.CartOrderAPIView.as_view()),
]
客户端获取购物车勾选商品的数据
api/cart.js
,代码:
import http from "../utils/http";
import {reactive, ref} from "vue"
const cart = reactive({
// ... 中间代码省略
select_course_list: [], // 购物车中被勾选的商品磕碜列表
// ... 中间代码省略
get_select_course(token){
// 获取购物车中被勾选的商品列表
return http.get("/cart/order/", {
headers:{
Authorization: "jwt " + token,
}
})
}
})
export default cart;
api/order.js
,代码:
import http from "../utils/http";
import {reactive} from "vue";
const order = reactive({
total_price: 0, // 勾选商品的总价格
use_coupon: false, // 用户是否使用优惠
discount_type: 0, // 0表示优惠券,1表示积分
coupon_list:[1,2,3], // 用户拥有的可用优惠券列表
select: -1, // 当前用户选中的优惠券下标,-1表示没有选择
credit: 0, // 当前用户选择抵扣的积分,0表示没有使用积分
fixed: true, // 底部订单总价是否固定浮动
pay_type: 0, // 支付方式
})
export default order;
views/Order.vue
,代码:
<template>
<div class="cart">
<Header/>
<div class="cart-main">
<div class="cart-header">
<div class="cart-header-warp">
<div class="cart-title left">
<h1 class="left">确认订单</h1>
</div>
<div class="right">
<div class="">
<span class="left"><router-link class="myorder-history" to="/cart">返回购物车</router-link></span>
</div>
</div>
</div>
</div>
<div class="cart-body" id="cartBody">
<div class="cart-body-title"><p class="item-1 l">课程信息</p></div>
<div class="cart-body-table">
<div class="item" v-for="course_info in cart.select_course_list">
<div class="item-2">
<router-link :to="`/project/${course_info.id}`" class="img-box l"><img :src="course_info.course_cover"></router-link>
<dl class="l has-package">
<dt>【{{course_info.course_type}}】{{course_info.name}} </dt>
<p class="package-item" v-if="course_info.discount.type">{{course_info.discount.type}}</p>
</dl>
</div>
<div class="item-3">
<div class="price">
<p class="discount-price" v-if="course_info.discount.price>=0"><em>¥</em><span>{{course_info.discount.price.toFixed(2)}}</span></p>
<p :class="{'original-price': course_info.discount.price>=0}"><em>¥</em><span>{{course_info.price.toFixed(2)}}</span></p>
</div>
</div>
</div>
</div>
<div class="coupons-box">
<div class="coupon-title-box">
<p class="coupon-title">
使用优惠券/积分
<span v-if="order.use_coupon" @click="order.use_coupon=!order.use_coupon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" data-v-394d1fd8=""><path fill="currentColor" d="M831.872 340.864 512 652.672 192.128 340.864a30.592 30.592 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728 30.592 30.592 0 0 0-42.752 0z"></path></svg></span>
<span v-else @click="order.use_coupon=!order.use_coupon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" data-v-394d1fd8=""><path fill="currentColor" d="m488.832 344.32-339.84 356.672a32 32 0 0 0 0 44.16l.384.384a29.44 29.44 0 0 0 42.688 0l320-335.872 319.872 335.872a29.44 29.44 0 0 0 42.688 0l.384-.384a32 32 0 0 0 0-44.16L535.168 344.32a32 32 0 0 0-46.336 0z"></path></svg></span>
<!-- <i :class="order.use_coupon?'el-icon-arrow-up':'el-icon-arrow-down'" @click="order.use_coupon=!order.use_coupon"></i>-->
</p>
</div>
<transition name="el-zoom-in-top">
<div class="coupon-del-box" v-if="order.use_coupon">
<div class="coupon-switch-box">
<div class="switch-btn ticket" :class="{'checked': order.discount_type===0}" @click="order.discount_type=0">优惠券 (4)<em><i class="imv2-check"></i></em></div>
<div class="switch-btn code" :class="{'checked': order.discount_type===1}" @click="order.discount_type=1">积分<em><i class="imv2-check"></i></em></div>
</div>
<div class="coupon-content ticket" v-if="order.discount_type===0">
<p class="no-coupons" v-if="order.coupon_list.length<1">暂无可用优惠券</p>
<div class="coupons-box" v-else>
<div class="content-box">
<ul class="nouse-box">
<li class="l">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥100 </p>
<p class="use-inst l">满499可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
<li class="l select">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥248 </p>
<p class="use-inst l">满999可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
<li class="l wait-use">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥248 </p>
<p class="use-inst l">满999可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
<li class="l wait-use">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥248 </p>
<p class="use-inst l">满999可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
</ul>
<ul class="use-box">
<li class="l useing">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥100 </p>
<p class="use-inst l">满499可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
<li class="l">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥248 </p>
<p class="use-inst l">满999可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
</ul>
<ul class="overdue-box">
<li class="l useing">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥100 </p>
<p class="use-inst l">满499可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
<li class="l">
<div class="detail-box more-del-box">
<div class="price-box">
<p class="coupon-price l"> ¥248 </p>
<p class="use-inst l">满999可用</p>
</div>
<div class="use-detail-box">
<div class="use-ajust-box">适用于:全部实战课程</div>
<div class="use-ajust-box">有效期:2021.06.01-2021.06.18</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="coupon-content code" v-else>
<div class="input-box">
<el-input-number placeholder="10积分=1元" v-model="order.credit" :step="1" :min="0" :max="1000"></el-input-number>
<a class="convert-btn">兑换</a>
</div>
<div class="converted-box">
<p>使用积分:<span class="code-num">200</span></p>
<p class="course-title">课程:<span class="c_name">3天JavaScript入门</span>
<span class="discount-cash">100积分抵扣:<em>10</em>元</span>
</p>
<p class="course-title">课程:<span class="c_name">3天JavaScript入门</span>
<span class="discount-cash">100积分抵扣:<em>10</em>元</span>
</p>
</div>
<p class="error-msg">本次订单最多可以使用1000积分,您当前拥有200积分。(10积分=1元)</p>
<p class="tip">说明:每笔订单只能使用一次积分,并只有在部分允许使用积分兑换的课程中才能使用。</p>
</div>
</div>
</transition>
</div>
<div class="pay-type">
<p class="title">选择支付方式</p>
<div class="list">
<img :src="order.pay_type==0?'/src/assets/alipay2.png':'/src/assets/alipay1.png'" @click="order.pay_type=0" alt="支付宝">
<img :src="order.pay_type==1?'/src/assets/wechat2.png':'/src/assets/wechat1.png'" @click="order.pay_type=1" alt="微信">
<img :src="order.pay_type==2?'/src/assets/yue2.png':'/src/assets/yue1.png'" @click="order.pay_type=2" alt="余额">
</div>
</div>
<div class="pay-box" :class="{fixed:order.fixed}">
<div class="row-bottom">
<div class="row">
<div class="goods-total-price-box">
<p class="r rw price-num"><em>¥</em><span>1811.00</span></p>
<p class="r price-text"><span>共<span>5</span>件商品,</span>商品总金额:</p>
</div>
</div>
<div class="coupons-discount-box">
<p class="r rw price-num">-<em>¥</em><span>60.00</span></p>
<p class="r price-text">优惠券/积分抵扣:</p>
</div>
<div class="pay-price-box clearfix">
<p class="r rw price"><em>¥</em><span id="js-pay-price">1751.00</span></p>
<p class="r price-text">应付:</p>
</div>
<span class="r btn btn-red submit-btn">提交订单</span>
</div>
<div class="pay-add-sign">
<ul class="clearfix">
<li>支持花呗</li>
<li>可开发票</li>
<li class="drawback">7天可退款</li>
</ul>
</div>
</div>
</div>
</div>
<Footer/>
</div>
</template>
<script setup>
import {reactive,watch} from "vue"
import Header from "../components/Header.vue"
import Footer from "../components/Footer.vue"
import {useStore} from "vuex";
import cart from "../api/cart"
import order from "../api/order";
// let store = useStore()
const get_select_course = ()=>{
// 获取购物车中的勾选商品列表
let token = sessionStorage.token || localStorage.token;
cart.get_select_course(token).then(response=>{
cart.select_course_list = response.data.cart
})
}
get_select_course();
// 监听用户选择的支付方式
watch(
()=>order.pay_type,
()=>{
console.log(order.pay_type)
}
)
// 底部订单总价信息固定浮动效果
window.onscroll = ()=>{
let cart_body_table = document.querySelector(".cart-body-table")
let offsetY = window.scrollY
let maxY = cart_body_table.offsetTop+cart_body_table.offsetHeight
order.fixed = offsetY < maxY
}
</script>
提交代码版本
cd /home/moluo/Desktop/luffycity
git add .
git commit -m "feature: 确认下单页面中展示购物车勾选商品列表"
git push
订单生成
创建订单子应用
完成了勾选商品列表展示以后,因为优惠券或积分属于增值业务,所以可以先把优惠券功能和积分功能延后处理,先完成主流程中的订单生成功能。同时,为了方便以后项目的代码管理和维护,我们再次创建子应用orders来完成接下来的订单功能。
# 确认前面功能已经开发完整,review代码结束,向公司申请合并分支,开发合并分支
git checkout master
git merge feature/cart
# 查看线上本地所有的分支列表,可以看到本地的feature/user分支已经删除,但是线上的依然存在。
git branch --all
git branch -d feature/cart
# 本地删除了分支以后,线上分支也要同步一下。
git push origin --delete feature/cart
# 因为属于一个较大功能的开发合并,往往项目中都会打一个标签
git tag v0.0.4
# 提交标签版本
git push --tag
# git push origin v0.0.4
# 后续的功能属于购物流程里面的订单生成部分了
git checkout -b feature/order
# 创建订单子应用
cd luffycityapi/luffycityapi/apps
python ../../manage.py startapp orders
注册子应用,settings/dev.py
,代码:
INSTALLED_APPS = [
# 子应用
。。。
'orders',
]
子路由,orders/urls.py
,代码:
from django.urls import path
from . import views
urlpatterns = [
]
总路由,luffycityapi/urls.py
,代码:
path("orders/", include("orders.urls")),
订单模型
订单相关的模型分析:
订单基本信息:订单ID,支付方式,订单状态,支付时间,订单总价格,实付价格,订单标题,订单号,用户ID等等
订单项详情(订单与商品的关系):商品ID,商品原价、商品实价,优惠方式,订单ID等等
用户课程(用户与课程的关系):用户ID,课程ID,学习总时长等等
用户学习课程的进度跟踪记录(用户与课时的关系):用户ID,课时ID,课程ID,章节ID,学习进度(视频进度),学习时间等等
优惠券:优惠券标题、优惠券面额、优惠券优惠方式、优惠类型、领取方式(用户领取,系统发放)、起用时间、过期时间等等
用户的优惠券(用户与优惠券的关系): 用户ID,优惠券ID,领取时间等等。(我们采用redis来记录)
优惠券的使用记录(用户的优惠券与订单的关系):用户ID,优惠券ID、使用状态、订单ID等等。
积分流水:操作方式、积分面值、用户ID、订单ID等等。
余额流水:操作方式、货币面值、用户ID、订单ID等等。
为什么有订单号?
原因是支付平台需要记录每一个商家的资金流水,所以需要我们这边提供一个足够复杂的流水号和支付平台保持一致。
所以订单号是支付平台那边强制要求在支付时提供给平台的。用于对账。
`orders/models.py
,订单模型,代码:
from models import BaseModel,models
from users.models import User
from courses.models import Course
# Create your models here.
class Order(BaseModel):
"""订单基本信息模型"""
status_choices = (
# 模型对象.<字段名> 获取元组的第一个成员
# 模型对象.get_<字段名>_display() 获取元组的第二个成员
(0, '未支付'),
(1, '已支付'),
(2, '已取消'),
(3, '超时取消'),
)
pay_choices = (
(0, '支付宝'),
(1, '微信'),
(2, '余额'),
)
total_price = models.DecimalField(default=0, max_digits=10, decimal_places=2, verbose_name="订单总价")
real_price = models.DecimalField(default=0, max_digits=10, decimal_places=2, verbose_name="实付金额")
order_number = models.CharField(max_length=64, verbose_name="订单号")
order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态")
pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式")
order_desc = models.TextField(null=True, blank=True, max_length=500, verbose_name="订单描述")
pay_time = models.DateTimeField(null=True, blank=True, verbose_name="支付时间")
user = models.ForeignKey(User, related_name='user_orders', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name="下单用户")
class Meta:
db_table = "ly_order"
verbose_name = "订单记录"
verbose_name_plural = verbose_name
def __str__(self):
return "%s,总价: %s,实付: %s" % (self.name, self.total_price, self.real_price)
class OrderDetail(BaseModel):
"""
订单详情
"""
order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, db_constraint=False, verbose_name="订单")
course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, db_constraint=False, verbose_name="课程")
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="课程原价")
real_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="课程实价")
discount_name = models.CharField(max_length=120,default="",verbose_name="优惠类型")
class Meta:
db_table = "ly_order_course"
verbose_name = "订单详情"
verbose_name_plural = verbose_name
def __str__(self):
return "%s" % self.course.name
数据迁移:
cd ../../
python manage.py makemigrations
python manage.py migrate
提交版本
cd /home/moluo/Desktop/luffycity
git add .
git commit -m "feature:订单子应用创建以及订单信息和订单项模型的创建"
git push --set-upstream origin feature/order
把订单子应用相关的模型注册到admin管理站点
orders/admin.py
,代码:
from django.contrib import admin
from .models import Order, OrderDetail
# class OrderDetailInLine(admin.StackedInline):
class OrderDetailInLine(admin.TabularInline):
"""订单项的内嵌类"""
model = OrderDetail
fields = ["course", "price", "real_price", "discount_name"]
# readonly_fields = ["discount_name"]
class OrderModelAdmin(admin.ModelAdmin):
"""订单信息的模型管理器"""
list_display = ["id","order_number","user","total_price","total_price","order_status"]
inlines = [OrderDetailInLine, ]
admin.site.register(Order, OrderModelAdmin)
orders/apps.py
,代码:
from django.apps import AppConfig
class OrdersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'orders'
verbose_name = "订单管理"
verbose_name_plural = verbose_name
提交版本
cd /home/moluo/Desktop/luffycity
git add .
git commit -m "feature:把订单子应用相关的模型注册到admin管理站点"
git push
服务端提供创建订单的api接口
orders/views.py
,代码:
from rest_framework.generics import CreateAPIView
from .models import Order
from .serializers import OrderModelSerializer
from rest_framework.permissions import IsAuthenticated
# Create your views here.
class OrderCreateAPIView(CreateAPIView):
"""创建订单"""
permission_classes = [IsAuthenticated]
queryset = Order.objects.all()
serializer_class = OrderModelSerializer
子路由,orders/urls.py
,代码:
from django.urls import path
from . import views
urlpatterns = [
path("", views.OrderCreateAPIView.as_view()),
]
序列化器,orders/serializers.py
,代码:
from datetime import datetime
from rest_framework import serializers
from django_redis import get_redis_connection
from .models import Order, OrderDetail, Course
class OrderModelSerializer(serializers.ModelSerializer):
pay_link = serializers.CharField(read_only=True)
class Meta:
model = Order
fields = ["pay_type", "id", "order_number", "pay_link"]
read_only_fields = ["id", "order_number"]
extra_kwargs = {
"pay_type": {"write_only": True},
}
def create(self, validated_data):
"""创建订单"""
redis = get_redis_connection("cart")
user_id = self.context["request"].user.id # 1
# 创建订单记录
order = Order.objects.create(
name="购买课程", # 订单标题
user_id=user_id, # 当前下单的用户ID
# order_number = datetime.now().strftime("%Y%m%d%H%M%S") + ("%08d" % user_id) + "%08d" % random.randint(1,99999999) # 基于随机数生成唯一订单号
order_number=datetime.now().strftime("%Y%m%d") + ("%08d" % user_id) + "%08d" % redis.incr("order_number"), # 基于redis生成分布式唯一订单号
pay_type=validated_data.get("pay_type"), # 支付方式
)
# 记录本次下单的商品列表
cart_hash = redis.hgetall(f"cart_{user_id}")
if len(cart_hash) < 1:
raise serializers.ValidationError(detail="购物车没有要下单的商品")
# 提取购物车中所有勾选状态为b'1'的商品
course_id_list = [int(key.decode()) for key, value in cart_hash.items() if value == b'1']
# 添加订单与课程的关系
course_list = Course.objects.filter(pk__in=course_id_list, is_deleted=False, is_show=True).all()
detail_list = []
total_price = 0 # 本次订单的总价格
real_price = 0 # 本次订单的实付总价
for course in course_list:
discount_price = float(course.discount.get("price", 0)) # 获取课程原价
discount_name = course.discount.get("type", "")
detail_list.append(OrderDetail(
order=order,
course=course,
name=course.name,
price=course.price,
real_price=discount_price,
discount_name=discount_name,
))
# 统计订单的总价和实付总价
total_price += float(course.price)
real_price += discount_price if discount_price > 0 else float(course.price)
# 一次性批量添加本次下单的商品记录
OrderDetail.objects.bulk_create(detail_list)
# 保存订单的总价格和实付价格
order.total_price = total_price
order.real_price = real_price
order.save()
# todo 支付链接地址[后面实现支付功能的时候,再做]
order.pay_link = ""
return order
生成订单时,在序列化器中要接收客户端用户的user_id
用户ID在序列化器中接收到视图中的数据,那么在序列化器初始化的时候,其实有3个参数可以填写:
1. instance 模型对象,数据模型,
2. data 字典,客户端提交数据,
3. context 字典,额外参数[执行上下文],如果要自定义参数,可以直接通过字典格式声明,然后到context
OrderModerSerializer(instance="模型对象",data="客户端数据", context={})
利用序列化器初始化时提供的第三个参数就可以调用到视图类的
context的属性 描述 序列化器中的调用代码
request 本次客户端的请求对象 self.context["request"]
format 本次服务器响应的数据格式 self.context["format"]
view 调用当前序列化器的视图类 self.context["view"]
因此,我们要在序列化器中提取用户的id,代码如下:
user_id = self.context["request"].user.id
提交版本
cd /home/moluo/Desktop/luffycity
git add .
git commit -m "feature:服务端提供创建订单的API接口"
git push
上面我们完成了订单信息的添加,但是下单不是一个数据记录而已,而是多张表记录的同时添加操作。所以针对这种多个记录或者多张表连贯进行的操作,为了保证数据的完整性和一致性以及原子性,我们要使用数据库的事务(Transaction)来完成,当然我们这个项目中不需要使用到数据库原生的事务语句,而是使用django的ORM提供的事务模块即可。
事务(Transaction),是以功能或业务作为逻辑单位,把一条或多条SQL语句组成一个不可分割的操作序列来执行的数据库特性。
在完成一个整体功能时,操作到了多个表数据,或者同一个表的多条记录,如果要保证这些SQL语句操作作为一个整体保存到数据库中,那么可以使用事务(transaction),保证这些操作作为不可分割的整体,要么一起成功,要么一起失败。
事务具有4个特性(ACID),5个隔离等级
四个特性:一致性,原子性,隔离性,持久性
# 隔离性:两个事务的隔离性,隔离性的修改可以通过数据库的配置文件mysqld.cnf进行修改,默认mysql是属于可重复级别
五个隔离级别(从高到低): 串行隔离,可重复读,已提交读,未提交读,没有隔离
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)[事务隔离级别->幻读,脏读, 不可重复读]
持久性(Durability)
在mysql中有专门的SQl语句来完成事务的操作,事务的代码操作一般有3个步骤:
设置事务开始 begin;
事务的处理[mysql:增删改]
redis.sadd()
事务的处理[mysql:增删改]
设置事务的回滚或者提交 rollback / commit; # 这个事务过程中,事务无法对mysql数据库以外的其他类型的数据库操作进行管理和回滚
mysql中底层的事务是如何实现事务的回滚操作:undo.log重做日志
在ORM框架一般都会实现了事务操作封装,所以我们可以直接使用ORM框架即可完成事务的操作
django框架本身就提供了2种事务操作的写法,主要都是通过 django.db.transaction模块完成的。
启用事务写法1:基于装饰器对函数或方法进行事务管理:
from django.db import transaction
from rest_framework.views import APIView
class OrderAPIView(APIView):
@transaction.atomic # 开启事务,当函数/方法执行完成以后,自动提交事务
def post(self,request): # 不一定是视图方法,也可以是其他函数方法。
.... # 在整个函数或者方法中,进行的所有SQL数据写操作[增删改],都属于同一个事务操作
启用事务写法2,基于with上下文管理器进行事务管理:
from django.db import transaction
from rest_framework.views import APIView
class OrderAPIView(APIView):
def post(self,request):
.... # 事务以外的,其他的SQL数据操作
with transation.atomic(): # 开启事务,当with语句执行完成以后,自动提交事务
# 数据库操作【DML增删改】
.... # with语句以外的其他的SQL数据操作,无法被上面事务管理
在使用事务过程中, 有时候会出现异常,当出现异常时我们需要回滚事务。
from django.db import transaction
from rest_framework.generics import CreateAPIView
class OrderCreateAPIView(CreateAPIView):
def post(self,request):
....
with transaction.atomic():
# 1、设置事务回滚的标记点【一个事物中可以设置多个回滚标记】
sid1 = transaction.savepoint()
try:
.... # 增删改等数据库操作
....
except:
transaction.savepoint_rallback(sid1)
.... # 数据库操作,注意,如果这里被执行,因为没有在with里面,所以是不会被上面的事务操作影响。
django的事务操作是支持嵌套事务的,但是mysql本身不支持嵌套事务。
from django.db import transaction
from rest_framework.generics import CreateAPIView
class OrderCreateAPIView(CreateAPIView):
def post(self,request):
....
with transaction.atomic():
# 1、设置事务回滚的标记点【一个事物中可以设置多个回滚标记】
sid1 = transaction.savepoint()
try:
.... # 增删改等数据库操作
....
with transaction.atomic():
# 2. 设置回滚点
sid2 = transaction.savepoint()
try:
.... # 其他内部数据库处理
....
except:
transaction.savepoint_rallback(sid2)
except:
transaction.savepoint_rallback(sid1)
.... # 数据库操作,注意,如果这里被执行,因为没有在with里面,所以是不会被上面的事务操作影响。
使用Django的ORM提供的mysql事务操作保证下单过程中的数据原子性
orders/serializers.py
,代码:
from datetime import datetime
from rest_framework import serializers
from django_redis import get_redis_connection
from django.db import transaction
from .models import Order, OrderDetail, Course
import logging
logger = logging.getLogger("django")
class OrderModelSerializer(serializers.ModelSerializer):
pay_link = serializers.CharField(read_only=True)
class Meta:
model = Order
fields = ["pay_type", "id", "order_number", "pay_link"]
read_only_fields = ["id", "order_number"]
extra_kwargs = {
"pay_type": {"write_only": True},
}
def create(self, validated_data):
"""创建订单"""
redis = get_redis_connection("cart")
user_id = self.context["request"].user.id # 1
# 开启事务操作,保证下单过程中的所有数据库的原子性
with transaction.atomic():
# 设置事务的回滚点标记
t1 = transaction.savepoint()
try:
# 创建订单记录
order = Order.objects.create(
name="购买课程", # 订单标题
user_id=user_id, # 当前下单的用户ID
# order_number = datetime.now().strftime("%Y%m%d%H%M%S") + ("%08d" % user_id) + "%08d" % random.randint(1,99999999) # 基于随机数生成唯一订单号
order_number=datetime.now().strftime("%Y%m%d") + ("%08d" % user_id) + "%08d" % redis.incr("order_number"), # 基于redis生成分布式唯一订单号
pay_type=validated_data.get("pay_type"), # 支付方式
)
# 记录本次下单的商品列表
cart_hash = redis.hgetall(f"cart_{user_id}")
if len(cart_hash) < 1:
raise serializers.ValidationError(detail="购物车没有要下单的商品")
# 提取购物车中所有勾选状态为b'1'的商品
course_id_list = [int(key.decode()) for key, value in cart_hash.items() if value == b'1']
# 添加订单与课程的关系
course_list = Course.objects.filter(pk__in=course_id_list, is_deleted=False, is_show=True).all()
detail_list = []
total_price = 0 # 本次订单的总价格
real_price = 0 # 本次订单的实付总价
for course in course_list:
discount_price = float(course.discount.get("price", 0)) # 获取课程原价
discount_name = course.discount.get("type", "")
detail_list.append(OrderDetail(
order=order,
course=course,
name=course.name,
price=course.price,
real_price=discount_price,
discount_name=discount_name,
))
# 统计订单的总价和实付总价
total_price += float(course.price)
real_price += discount_price if discount_price > 0 else float(course.price)
# 一次性批量添加本次下单的商品记录
OrderDetail.objects.bulk_create(detail_list)
# 保存订单的总价格和实付价格
order.total_price = total_price
order.real_price = real_price
order.save()
# todo 支付链接地址[后面实现支付功能的时候,再做]
order.pay_link = ""
return order
except Exception as e:
# 1. 记录日志
logger.error(f"订单创建失败:{e}")
# 2. 事务回滚
transaction.savepoint_rollback(t1)
# 3. 抛出异常,通知视图返回错误提示
raise serializers.ValidationError(detail="订单创建失败!")
购物车中选中的商品被记录到了订单中,那么购物车中原来的勾选商品是否要删除?
如果不删除,那么订单中的商品与购物车中就重复了,所以要删除,购物车中只需要保留没有勾选过的商品。
orders/serializers.py
,代码:
from datetime import datetime
from rest_framework import serializers
from django_redis import get_redis_connection
from django.db import transaction
from .models import Order, OrderDetail, Course
import logging
logger = logging.getLogger("django")
class OrderModelSerializer(serializers.ModelSerializer):
pay_link = serializers.CharField(read_only=True)
class Meta:
model = Order
fields = ["pay_type", "id", "order_number", "pay_link"]
read_only_fields = ["id", "order_number"]
extra_kwargs = {
"pay_type": {"write_only": True},
}
def create(self, validated_data):
"""创建订单"""
redis = get_redis_connection("cart")
user_id = self.context["request"].user.id # 1
# 开启事务操作,保证下单过程中的所有数据库的原子性
with transaction.atomic():
# 设置事务的回滚点标记
t1 = transaction.savepoint()
try:
# 创建订单记录
order = Order.objects.create(
name="购买课程", # 订单标题
user_id=user_id, # 当前下单的用户ID
# order_number = datetime.now().strftime("%Y%m%d%H%M%S") + ("%08d" % user_id) + "%08d" % random.randint(1,99999999) # 基于随机数生成唯一订单号
order_number=datetime.now().strftime("%Y%m%d") + ("%08d" % user_id) + "%08d" % redis.incr("order_number"), # 基于redis生成分布式唯一订单号
pay_type=validated_data.get("pay_type"), # 支付方式
)
# 记录本次下单的商品列表
cart_hash = redis.hgetall(f"cart_{user_id}")
if len(cart_hash) < 1:
raise serializers.ValidationError(detail="购物车没有要下单的商品")
# 提取购物车中所有勾选状态为b'1'的商品
course_id_list = [int(key.decode()) for key, value in cart_hash.items() if value == b'1']
# 添加订单与课程的关系
course_list = Course.objects.filter(pk__in=course_id_list, is_deleted=False, is_show=True).all()
detail_list = []
total_price = 0 # 本次订单的总价格
real_price = 0 # 本次订单的实付总价
for course in course_list:
discount_price = float(course.discount.get("price", 0)) # 获取课程原价
discount_name = course.discount.get("type", "")
detail_list.append(OrderDetail(
order=order,
course=course,
name=course.name,
price=course.price,
real_price=discount_price,
discount_name=discount_name,
))
# 统计订单的总价和实付总价
total_price += float(course.price)
real_price += discount_price if discount_price > 0 else float(course.price)
# 一次性批量添加本次下单的商品记录
OrderDetail.objects.bulk_create(detail_list)
# 保存订单的总价格和实付价格
order.total_price = total_price
order.real_price = real_price
order.save()
# todo 支付链接地址[后面实现支付功能的时候,再做]
order.pay_link = ""
# 删除购物车中被勾选的商品,保留没有被勾选的商品信息
cart = {key: value for key, value in cart_hash.items() if value == b'0'}
pipe = redis.pipeline()
pipe.multi()
# 删除原来的购物车
pipe.delete(f"cart_{user_id}")
# 重新把未勾选的商品记录到购物车中
pipe.hmset(f"cart_{user_id}", cart) # hset 在新版本的redis中实际上hmset已经被废弃了,改用hset替代hmset
pipe.execute()
return order
except Exception as e:
# 1. 记录日志
logger.error(f"订单创建失败:{e}")
# 2. 事务回滚
transaction.savepoint_rollback(t1)
# 3. 抛出异常,通知视图返回错误提示
raise serializers.ValidationError(detail="订单创建失败!")
提交版本
cd /home/moluo/Desktop/luffycity
git add .
git commit -m "feature:服务端基于事务保证订单生成操作的原子性"
git push
客户端请求生成订单
api/order.js
,代码:
import http from "../utils/http";
import {reactive} from "vue";
const order = reactive({
total_price: 0, // 勾选商品的总价格
use_coupon: false, // 用户是否使用优惠
discount_type: 0, // 0表示优惠券,1表示积分
coupon_list:[1,2,3], // 用户拥有的可用优惠券列表
select: -1, // 当前用户选中的优惠券下标,-1表示没有选择
credit: 0, // 当前用户选择抵扣的积分,0表示没有使用积分
fixed: true, // 底部订单总价是否固定浮动
pay_type: 0, // 支付方式
create_order(token){
// 生成订单
return http.post("/orders/",{
pay_type: this.pay_type
},{
headers:{
Authorization: "jwt " + token,
}
})
}
})
export default order;
views/Order.vue
,代码:
<span class="r btn btn-red submit-btn" @click="commit_order">提交订单</span>
<script setup>
import {reactive,watch} from "vue"
import Header from "../components/Header.vue"
import Footer from "../components/Footer.vue"
import {useStore} from "vuex";
import cart from "../api/cart"
import order from "../api/order";
import {ElMessage} from "element-plus";
import router from "../router";
// let store = useStore()
const get_select_course = ()=>{
// 获取购物车中的勾选商品列表
let token = sessionStorage.token || localStorage.token;
cart.get_select_course(token).then(response=>{
cart.select_course_list = response.data.cart
if(response.data.cart.length === 0){
ElMessage.error("当前购物车中没有下单的商品!请重新重新选择购物车中要购买的商品~");
router.back();
}
}).catch(error=>{
if(error?.response?.status===400){
ElMessage.error("登录超时!请重新登录后再继续操作~");
}
})
}
get_select_course();
const commit_order = ()=>{
// 生成订单
let token = sessionStorage.token || localStorage.token;
order.create_order(token).then(response=>{
console.log(response.data.order_number) // todo 订单号
console.log(response.data.pay_link) // todo 支付链接
// 成功提示
ElMessage.success("下单成功!马上跳转到支付页面,请稍候~")
// 扣除掉被下单的商品数量,更新购物车中的商品数量
store.commit("set_cart_total", store.state.cart_total - cart.select_course_list.length);
}).catch(error=>{
if(error?.response?.status===400){
ElMessage.success("登录超时!请重新登录后再继续操作~");
}
})
}
// 监听用户选择的支付方式
watch(
()=>order.pay_type,
()=>{
console.log(order.pay_type)
}
)
// 底部订单总价信息固定浮动效果
window.onscroll = ()=>{
let cart_body_table = document.querySelector(".cart-body-table")
let offsetY = window.scrollY
let maxY = cart_body_table.offsetTop+cart_body_table.offsetHeight
order.fixed = offsetY < maxY
}
</script>
提交版本
cd /home/moluo/Desktop/luffycity
git add .
git commit -m "feature:客户端请求生成订单"
git push