一. 订单结算台
说明:所有的结算,本质上就是 跳转到"订单结算台",并且,跳转的同时,需要 携带上对应的订单相关参数
具体需要哪些参数,基于“订单结算台”的需求来定。
1. 静态布局
views / pay / index.vue
<template>
<div class="pay">
<van-nav-bar fixed title="订单结算台" left-arrow @click-left="$router.go(-1)" />
<!-- 地址相关 -->
<div class="address">
<div class="left-icon">
<van-icon name="logistics" />
</div>
<div class="info" v-if="true">
<div class="info-content">
<span class="name">小红</span>
<span class="mobile">13811112222</span>
</div>
<div class="info-address">
江苏省 无锡市 南长街 110号 504
</div>
</div>
<div class="info" v-else>
请选择配送地址
</div>
<div class="right-icon">
<van-icon name="arrow" />
</div>
</div>
<!-- 订单明细 -->
<div class="pay-list">
<div class="list">
<div class="goods-item">
<div class="left">
<img src="http://cba.itlike.com/public/uploads/10001/20230321/8f505c6c437fc3d4b4310b57b1567544.jpg" alt="" />
</div>
<div class="right">
<p class="tit text-ellipsis-2">
三星手机 SAMSUNG Galaxy S23 8GB+256GB 超视觉夜拍系统 超清夜景 悠雾紫 5G手机 游戏拍照旗舰机s23
</p>
<p class="info">
<span class="count">x3</span>
<span class="price">¥9.99</span>
</p>
</div>
</div>
</div>
<div class="flow-num-box">
<span>共 12 件商品,合计:</span>
<span class="money">¥1219.00</span>
</div>
<div class="pay-detail">
<div class="pay-cell">
<span>订单总金额:</span>
<span class="red">¥1219.00</span>
</div>
<div class="pay-cell">
<span>优惠券:</span>
<span>无优惠券可用</span>
</div>
<div class="pay-cell">
<span>配送费用:</span>
<span v-if="false">请先选择配送地址</span>
<span v-else class="red">+¥0.00</span>
</div>
</div>
<!-- 支付方式 -->
<div class="pay-way">
<span class="tit">支付方式</span>
<div class="pay-cell">
<span><van-icon name="balance-o" />余额支付(可用 ¥ 999919.00 元)</span>
<!-- <span>请先选择配送地址</span> -->
<span class="red"><van-icon name="passed" /></span>
</div>
</div>
<!-- 买家留言 -->
<div class="buytips">
<textarea placeholder="选填:买家留言(50字内)" name="" id="" cols="30" rows="10"></textarea>
</div>
</div>
<!-- 底部提交 -->
<div class="footer-fixed">
<div class="left">实付款:<span>¥999919</span></div>
<div class="tipsbtn">提交订单</div>
</div>
</div>
</template>
<script>
export default {
name: 'PayIndex',
data () {
return {
}
},
methods: {
}
}
</script>
<style lang="less" scoped>
.pay {
padding-top: 46px;
padding-bottom: 46px;
::v-deep {
.van-nav-bar__arrow {
color: #333;
}
}
}
.address {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 20px;
font-size: 14px;
color: #666;
position: relative;
background: url(@/assets/121.png) bottom repeat-x;
background-size: 60px auto;
.left-icon {
margin-right: 20px;
}
.right-icon {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-7px);
}
}
.goods-item {
height: 100px;
margin-bottom: 6px;
padding: 10px;
background-color: #fff;
display: flex;
.left {
width: 100px;
img {
display: block;
width: 80px;
margin: 10px auto;
}
}
.right {
flex: 1;
font-size: 14px;
line-height: 1.3;
padding: 10px;
padding-right: 0px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
color: #333;
.info {
margin-top: 5px;
display: flex;
justify-content: space-between;
.price {
color: #fa2209;
}
}
}
}
.flow-num-box {
display: flex;
justify-content: flex-end;
padding: 10px 10px;
font-size: 14px;
border-bottom: 1px solid #efefef;
.money {
color: #fa2209;
}
}
.pay-cell {
font-size: 14px;
padding: 10px 12px;
color: #333;
display: flex;
justify-content: space-between;
.red {
color: #fa2209;
}
}
.pay-detail {
border-bottom: 1px solid #efefef;
}
.pay-way {
font-size: 14px;
padding: 10px 12px;
border-bottom: 1px solid #efefef;
color: #333;
.tit {
line-height: 30px;
}
.pay-cell {
padding: 10px 0;
}
.van-icon {
font-size: 20px;
margin-right: 5px;
}
}
.buytips {
display: block;
textarea {
display: block;
width: 100%;
border: none;
font-size: 14px;
padding: 12px;
height: 100px;
}
}
.footer-fixed {
position: fixed;
background-color: #fff;
left: 0;
bottom: 0;
width: 100%;
height: 46px;
line-height: 46px;
border-top: 1px solid #efefef;
font-size: 14px;
display: flex;
.left {
flex: 1;
padding-left: 12px;
color: #666;
span {
color:#fa2209;
}
}
.tipsbtn {
width: 121px;
background: linear-gradient(90deg,#f9211c,#ff6335);
color: #fff;
text-align: center;
line-height: 46px;
display: block;
font-size: 14px;
}
}
</style>
二. 订单结算台-获取收货地址列表
1. 封装获取地址接口
api / address.js
import request from '@/utils/request'
// 获取地址列表
export const getAddressList = () => {
return request.get('/address/list')
}
2. 页面中调用获取地址
views / pay / index.vue
<script>
import { getAddressList } from '@/api/address'
export default {
name: 'PayIndex',
data () {
return {
addressList: [] //定义空列表
}
},
computed: {
selectAddress () {
// 这里地址管理非主线业务,直接获取第一个项作为选中的地址
return this.addressList[0] || {}
},
longAddress () {
const region = this.selectAddress.region
// 拼接详细地址
return region.province + region.city + region.region + this.selectAddress.detail
}
},
created () {
this.getAddressLists()
},
methods: {
async getAddressLists () {
const { data: { list } } = await getAddressList() // 调用地址请求接口
this.addressList = list
console.log(list)
}
}
}
</script>
3. 页面中 - 进行渲染
views / pay / index.vue
<div class="info" v-if="selectAddress.address_id">
<div class="info-content">
<span class="name">{{ selectAddress.name }}</span>
<span class="mobile">{{ selectAddress.phone }}</span>
</div>
<div class="info-address">
{{ longAddress }}
</div>
</div>
三. 订单结算台- 确认订单信息
目标:封装通用的订单信息确认接口
说明:这里的订单信息确认结算,有两种情况
(1). 购物车结算
(2). 立即购买结算
订单信息确认,可以共用同一个接口(参数不同)
1. 封装通用接口
api / order.js
import request from '@/utils/request'
// 订单结算确认
// mode: cart => obj { cartId }
// mode: buyNow => { obj goodsId goodsNum goodsSkuId }
export const checkOrder = (mode, obj) => {
return request.get('/checkout/order', {
params: {
mode, // cart buyNow
delivery: 10, // 10:快递配送 20:门店自提
couponId: 0, // 优惠券ID 传0 不使用优惠券
isUsePoints: 0, // 积分 传0 不使用积分
...obj // 将传递过来的参数对象 动态展开
}
})
}
四. 订单结算台-购物车结算
目标:购物车结算跳转,传递参数,调用接口渲染订单结算台
核心步骤:
(1). 跳转传递查询参数 mode="cart"和 cartlds
(2). 页面中 $route.query 接收参数
(3). 调用接口,获取数据
(4). 基于数据渲染
1. 跳转时,传递查询参数
views / layout / cart.vue
<template>
<div v-if="!isEdit" class="goPay" :class="{ disabled: selCount === 0 }" @click="goPay">结算({{ selCount }})</div>
</template>
<script>
methods: {
...
goPay () {
// 判断有没有选中商品
if (this.selCount > 0) {
// 有选中的 商品 才进行结算跳转
this.$router.push({
path: '/pay',
query: {
mode: 'cart',
cartIds: this.selCartList.map(item => item.id).join(',') // 'cartId,cartId,cartId'
}
})
}
}
},
</script>
2. 页面中接收参数, 调用接口,获取数据
views / pay / index.vue
<script>
import { getAddressList } from '@/api/address'
import { checkOrder } from '@/api/order'
export default {
name: 'PayIndex',
data () {
return {
addressList: [],
order: {},
personal: {}
}
},
computed: {
...
mode () {
return this.$route.query.mode
},
cartIds () {
return this.$route.query.cartIds
}
},
created () {
...
this.getOrderLists()
},
methods: {
...
async getOrderLists () {
const { data: { order, personal } } = await checkOrder(this.mode, {
cartIds: this.cartIds
})
this.order = order
this.personal = personal
}
}
}
</script>
3. 基于数据进行渲染
views / pay / index.vue
<!-- 订单明细 -->
<div class="pay-list" v-if="order.goodsList">
<div class="list">
<div class="goods-item" v-for="item in order.goodsList" :key="item.goods_id">
<div class="left">
<img :src="item.goods_image" alt="" />
</div>
<div class="right">
<p class="tit text-ellipsis-2">
{{ item.goods_name }}
</p>
<p class="info">
<span class="count">x{{ item.total_num }}</span>
<span class="price">¥{{ item.total_pay_price }}</span>
</p>
</div>
</div>
</div>
<div class="flow-num-box">
<span>共 {{ order.orderTotalNum }} 件商品,合计:</span>
<span class="money">¥{{ order.orderTotalPrice }}</span>
</div>
<div class="pay-detail">
<div class="pay-cell">
<span>订单总金额:</span>
<span class="red">¥{{ order.orderTotalPrice }}</span>
</div>
<div class="pay-cell">
<span>优惠券:</span>
<span>无优惠券可用</span>
</div>
<div class="pay-cell">
<span>配送费用:</span>
<span v-if="!selectAddress">请先选择配送地址</span>
<span v-else class="red">+¥0.00</span>
</div>
</div>
<!-- 支付方式 -->
<div class="pay-way">
<span class="tit">支付方式</span>
<div class="pay-cell">
<span><van-icon name="balance-o" />余额支付(可用 ¥ {{ personal.balance }} 元)</span>
<!-- <span>请先选择配送地址</span> -->
<span class="red"><van-icon name="passed" /></span>
</div>
</div>
<!-- 买家留言 -->
<div class="buytips">
<textarea placeholder="选填:买家留言(50字内)" name="" id="" cols="30" rows="10"></textarea>
</div>
</div>
<!-- 底部提交 -->
<div class="footer-fixed">
<div class="left">实付款:<span>¥{{ order.orderTotalPrice }}</span></div>
<div class="tipsbtn">提交订单</div>
</div>
五. 订单结算台-立即购买结算
目标: 购物车结算跳转,传递参数,调用接口渲染订单结算台
核心步骤:
(1). 跳转传递查询参数
mode="buyNow",goodsld, goodsSkuld,goodsNum
(2). 页面中 $route.query 接收参数
(3). 调用接口,获取数据
(4). 基于数据渲染
(5). 未登录时,确认框的复用(mixins混入)
1. 点击跳转传参
views / prodetail / index.vue
<template>
<div class="btn now" v-else @click="goBuyNow">立刻购买</div>
</template>
<script>
methods: {
...
goBuyNow () {
this.$router.push({
path: '/pay',
query: {
mode: 'buyNow',
goodsId: this.goodsId,
goodsSkuId: this.detail.skuList[0].goods_sku_id,
goodsNum: this.addCount
}
})
}
}
</script>
2. 计算属性处理参数
views / pay / index.vue
computed: {
...
goodsId () {
return this.$route.query.goodsId
},
goodsSkuId () {
return this.$route.query.goodsSkuId
},
goodsNum () {
return this.$route.query.goodsNum
}
},
3. 基于请求时携带参数发请求渲染
views / pay / index.vue
methods: {
...
async getOrderLists () {
// 购物车结算
if (this.mode === 'cart') {
const { data: { order, personal } } = await checkOrder(this.mode, {
cartIds: this.cartIds
})
this.order = order
this.personal = personal
}
// 立刻购买结算
if (this.mode === 'buyNow') {
const { data: { order, personal } } = await checkOrder(this.mode, {
goodsId: this.goodsId,
goodsSkuId: this.goodsSkuId,
goodsNum: this.goodsNum
})
this.order = order
this.personal = personal
}
}
}
4. mixins 复用 - 处理登录确认框的弹出
(1). 建立一个mixin文件,封装登录验证
src / mixins / loginConfirm.js
export default {
// 此处编写的就是 Vue组件实例的 配置项 通过一定语法,可以直接混入到组件内部
// data method computed 生命周期函数 ...
// 注意点:
// 1. 如果此处 和 组件内,提供了同名的 data 或 methods, 则组件内优先级更高
// 2. 如果编写了生命周期函数,则mixins中的生命周期函数 和 页面中的生命周期函数,
// 会用数组管理,统一执行
methods: {
// 根据登录状态,判断是否需要显示登录确认框
// 1. 如果未登录 => 显示确认框 返回true
// 2. 如果已登录 => 啥也不干 返回false
loginConfirm () {
// 判断 token是否存在
if (!this.$store.getters.token) {
// 弹确认框
this.$dialog.confirm({
title: '温馨提示',
message: '此时需要先登录才能继续操作哦',
confirmButtonText: '去登录',
cancelButtonText: '在逛逛'
})
.then(() => {
// 如果希望, 跳转到登录 => 登录后能回跳回来,需要在跳转去携带参数(当前的路径地址)
// this.$route.fullPath(会包含查询参数)
// replace: 跳转路由,会将上一个replace路由替换成本次replace路由
this.$router.replace({
path: '/login',
// 额外携带参数
query: {
backUrl: this.$route.fullPath
}
})
})
.catch(() => {})
return true
}
return false
}
}
}
(2). 页面中导入,混入方法
views / prodetail / index.vue
<script>
import loginConfirm from '@/mixins/loginConfirm'
export default {
name: 'ProDetail',
mixins: [loginConfirm], // 混入
...
}
</script>
(3). 页面中调用混入方法
views / prodetail / index.vue
<script>
methods:{
async addCart () {
// 1. 如果token不存在, 弹确认框
// 2. 如果token存在, 继续请求操作
if (this.loginConfirm()) {
return
}
// console.log('正常请求')
//const { data } = await addCart(this.goodsId, this.addCount, this.detail.skuList[0].goods_sku_id)
//this.cartTotal = data.cartTotal
//this.$toast('加入购物车成功')
// this.showPannel = false // 关闭弹层
//console.log(this.cartTotal)
},
goBuyNow () {
// 未登录的处理: 需要弹出一个确认框
// 您当前的操作需要登录才能继续 => 去登录 在逛逛
// 判断token 是否存在
if (this.loginConfirm()) {
return
}
//this.$router.push({
// path: '/pay',
// query: {
// mode: 'buyNow',
// goodsId: this.goodsId,
// goodsSkuId: this.detail.skuList[0].goods_sku_id,
// goodsNum: this.addCount
// }
// })
//}
}
</script>
六. 提交订单并支付
目标:封装 API 请求方法,提交订单并支付
核心步骤:
(1). 封装通用请求方法
(2). 买家留言绑定
(3). 注册事件,调用方法提交订单并支付
1. 封装API通用方法(统一余额支付)
src / api / order.js
import request from '@/utils/request'
...
// 提交订单
// mode: cart => obj { cartId, remark }
// mode: buyNow => { obj goodsId goodsNum goodsSkuId,remark }
export const submitOrder = (mode, obj) => {
return request.post('/checkout/submit', {
mode, // cart buyNow
delivery: 10, // 10:快递配送 20:门店自提
couponId: 0, // 优惠券ID 传0 不使用优惠券
isUsePoints: 0, // 积分 传0 不使用积分
payType: 10, // 余额支付
...obj // 将传递过来的参数对象 动态展开
})
}
2. 买家留言绑定
views / pay / index.vue
<template>
<!-- 买家留言 -->
<div class="buytips">
<textarea v-model="remake" placeholder="选填:买家留言(50字内)" name="" id="" cols="30" rows="10"></textarea>
</div>
</template>
<script>
data () {
return {
...
remake: '' // 备注留言
}
</script>
3. 注册点击事件,提交订单并支付
views / pay / index.vue
<template>
<div class="tipsbtn" @click="submitOrders">提交订单</div>
</template>
<script>
import { checkOrder, submitOrder } from '@/api/order'
methods: {
// 提交订单
async submitOrders () {
if (this.mode === 'cart') {
await submitOrder(this.mode, {
cartIds: this.cartIds,
remake: this.remake
})
}
if (this.mode === 'buyNow') {
await submitOrder(this.mode, {
remark: this.remark,
goodsId: this.goodsId,
goodsSkuId: this.goodsSkuId,
goodsNum: this.goodsNum
})
}
this.$toast.success('支付成功')
this.$router.replace('/myorder')
},
}
</script>