结算&支付
结算-页面布局
落的代码:
- 定义组件基础解构和配置路由
src/views/member/pay/checkout.vue
<template>
<div class="xtx-pay-checkout-page">
<div class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem to="/cart">购物车</XtxBreadItem>
<XtxBreadItem >填写订单</XtxBreadItem>
</XtxBread>
<div class="wrapper">
<!-- 收货地址 -->
<!-- 商品信息 -->
<!-- 配送时间 -->
<!-- 支付方式 -->
<!-- 金额明细 -->
<!-- 提交订单 -->
</div>
</div>
</div>
</template>
<script>
export default {
name: 'XtxPayCheckoutPage'
}
</script>
<style scoped lang="less">
.xtx-pay-checkout-page {
.wrapper {
background: #fff;
}
}
</style>
- 配置路由
src/router/index.js
const PayCheckout = () => import('@/views/member/pay/checkout')
{ path: '/cart', component: Cart },
+ { path: '/member/checkout', component: PayCheckout }
- 完成页面布局效果
<template>
<div class="xtx-pay-checkout-page">
<div class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem to="/cart">购物车</XtxBreadItem>
<XtxBreadItem >填写订单</XtxBreadItem>
</XtxBread>
<div class="wrapper">
<!-- 收货地址 -->
<h3 class="box-title">收货地址</h3>
<div class="box-body">
<div class="address">
<div class="text">
<!-- <div class="none">您需要先添加收货地址才可提交订单。</div> -->
<ul>
<li><span>收<i/>货<i/>人:</span>朱超</li>
<li><span>联系方式:</span>132****2222</li>
<li><span>收货地址:</span>海南省三亚市解放路108号物质大厦1003室</li>
</ul>
<a href="javascript:;">修改地址</a>
</div>
<div class="action">
<XtxButton class="btn">切换地址</XtxButton>
<XtxButton class="btn">添加地址</XtxButton>
</div>
</div>
</div>
<!-- 商品信息 -->
<h3 class="box-title">商品信息</h3>
<div class="box-body">
<table class="goods">
<thead>
<tr>
<th width="520">商品信息</th>
<th width="170">单价</th>
<th width="170">数量</th>
<th width="170">小计</th>
<th width="170">实付</th>
</tr>
</thead>
<tbody>
<tr v-for="i in 4" :key="i">
<td>
<a href="javascript:;" class="info">
<img src="https://yanxuan-item.nosdn.127.net/cd9b2550cde8bdf98c9d083d807474ce.png" alt="">
<div class="right">
<p>轻巧多用锅雪平锅 麦饭石不粘小奶锅煮锅</p>
<p>颜色:白色 尺寸:10cm 产地:日本</p>
</div>
</a>
</td>
<td>¥100.00</td>
<td>2</td>
<td>¥200.00</td>
<td>¥200.00</td>
</tr>
</tbody>
</table>
</div>
<!-- 配送时间 -->
<h3 class="box-title">配送时间</h3>
<div class="box-body">
<a class="my-btn active" href="javascript:;">不限送货时间:周一至周日</a>
<a class="my-btn" href="javascript:;">工作日送货:周一至周五</a>
<a class="my-btn" href="javascript:;">双休日、假日送货:周六至周日</a>
</div>
<!-- 支付方式 -->
<h3 class="box-title">支付方式</h3>
<div class="box-body">
<a class="my-btn active" href="javascript:;">在线支付</a>
<a class="my-btn" href="javascript:;">货到付款</a>
<span style="color:#999">货到付款需付5元手续费</span>
</div>
<!-- 金额明细 -->
<h3 class="box-title">金额明细</h3>
<div class="box-body">
<div class="total">
<dl><dt>商品件数:</dt><dd>5件</dd></dl>
<dl><dt>商品总价:</dt><dd>¥5697.00</dd></dl>
<dl><dt>运<i></i>费:</dt><dd>¥0.00</dd></dl>
<dl><dt>应付总额:</dt><dd class="price">¥5697.00</dd></dl>
</div>
</div>
<!-- 提交订单 -->
<div class="submit">
<XtxButton type="primary">提交订单</XtxButton>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'XtxPayCheckoutPage'
}
</script>
<style scoped lang="less">
.xtx-pay-checkout-page {
.wrapper {
background: #fff;
padding: 0 20px;
.box-title {
font-size: 16px;
font-weight: normal;
padding-left: 10px;
line-height: 70px;
border-bottom: 1px solid #f5f5f5;
}
.box-body {
padding: 20px 0;
}
}
}
.address {
border: 1px solid #f5f5f5;
display: flex;
align-items: center;
.text {
flex: 1;
min-height: 90px;
display: flex;
align-items: center;
.none {
line-height: 90px;
color: #999;
text-align: center;
width: 100%;
}
> ul {
flex: 1;
padding: 20px;
li {
line-height: 30px;
span {
color: #999;
margin-right: 5px;
> i {
width: 0.5em;
display: inline-block;
}
}
}
}
> a {
color: @xtxColor;
width: 160px;
text-align: center;
height: 90px;
line-height: 90px;
border-right: 1px solid #f5f5f5;
}
}
.action {
width: 420px;
text-align: center;
.btn {
width: 140px;
height: 46px;
line-height: 44px;
font-size: 14px;
&:first-child {
margin-right: 10px;
}
}
}
}
.goods {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
.info {
display: flex;
text-align: left;
img {
width: 70px;
height: 70px;
margin-right: 20px;
}
.right {
line-height: 24px;
p {
&:last-child {
color: #999;
}
}
}
}
tr {
th {
background: #f5f5f5;
font-weight: normal;
}
td,th {
text-align: center;
padding: 20px;
border-bottom: 1px solid #f5f5f5;
&:first-child {
border-left: 1px solid #f5f5f5;
}
&:last-child {
border-right: 1px solid #f5f5f5;
}
}
}
}
.my-btn {
width: 228px;
height: 50px;
border: 1px solid #e4e4e4;
text-align: center;
line-height: 48px;
margin-right: 25px;
color: #666666;
display: inline-block;
&.active,&:hover {
border-color: @xtxColor;
}
}
.total {
dl {
display: flex;
justify-content: flex-end;
line-height: 50px;
dt {
i {
display: inline-block;
width: 2em;
}
}
dd {
width: 240px;
text-align: right;
padding-right: 70px;
&.price {
font-size: 20px;
color: @priceColor;
}
}
}
}
.submit {
text-align: right;
padding: 60px;
border-top: 1px solid #f5f5f5;
}
</style>
结算-渲染页面
目的:分离收货地址组件,渲染页面默认内容。
大致步骤:
- 分离收货地址组件
- 定义获结算信息API接口
- 页面组件获取数据,传入地址组件,渲染剩余内容
- 渲染地址组件
落的代码:
- 分离收货地址组件
src/member/pay/components/checkout-address.vue
<template>
<div class="checkout-address">
<div class="text">
<!-- <div class="none">您需要先添加收货地址才可提交订单。</div> -->
<ul>
<li><span>收<i/>货<i/>人:</span>朱超</li>
<li><span>联系方式:</span>132****2222</li>
<li><span>收货地址:</span>海南省三亚市解放路108号物质大厦1003室</li>
</ul>
<a href="javascript:;">修改地址</a>
</div>
<div class="action">
<XtxButton class="btn">切换地址</XtxButton>
<XtxButton class="btn">添加地址</XtxButton>
</div>
</div>
</template>
<script>
export default {
name: 'CheckoutAddress'
}
</script>
<style scoped lang="less">
.checkout-address {
border: 1px solid #f5f5f5;
display: flex;
align-items: center;
.text {
flex: 1;
min-height: 90px;
display: flex;
align-items: center;
.none {
line-height: 90px;
color: #999;
text-align: center;
width: 100%;
}
> ul {
flex: 1;
padding: 20px;
li {
line-height: 30px;
span {
color: #999;
margin-right: 5px;
> i {
width: 0.5em;
display: inline-block;
}
}
}
}
> a {
color: @xtxColor;
width: 160px;
text-align: center;
height: 90px;
line-height: 90px;
border-right: 1px solid #f5f5f5;
}
}
.action {
width: 420px;
text-align: center;
.btn {
width: 140px;
height: 46px;
line-height: 44px;
font-size: 14px;
&:first-child {
margin-right: 10px;
}
}
}
}
</style>
- 使用组件
src/views/member/pay/checkout.vue
+import CheckoutAddress from './components/checkout-address'
export default {
name: 'XtxPayCheckoutPage',
+ components: { CheckoutAddress }
}
<!-- 收货地址 -->
<h3 class="box-title">收货地址</h3>
<div class="box-body">
+ <CheckoutAddress />
</div>
- 定义获结算信息API接口
src/api/order.js
定义接口
import request from '@/utils/request'
/**
* 获取结算信息
*/
export const findCheckoutInfo = () => {
return request({
method: 'get',
url: '/member/order/pre'
})
}
- 页面组件获取数据,传入地址组件,渲染剩余内容
src/views/member/pay/checkout.vue
+import { findCheckoutInfo } from '@/api/order'
export default {
name: 'XtxPayCheckoutPage',
components: { CheckoutAddress },
setup () {
+ const checkoutInfo = ref(null)
+ findCheckoutInfo().then(data => {
+ checkoutInfo.value = data.result
+ })
+ return { checkoutInfo }
}
}
- 传入地址组件,地址列表
src/views/member/pay/checkout.vue
+ <div class="wrapper" v-if="checkoutInfo">
<!-- 收货地址 -->
<h3 class="box-title">收货地址</h3>
<div class="box-body">
+ <CheckoutAddress :list="checkoutInfo.userAddresses" />
</div>
- 渲染剩余内容
src/views/member/pay/checkout.vue
<tbody>
<tr v-for="item in checkoutInfo.goods" :key="item.id">
<td>
<a href="javascript:;" class="info">
<img :src="item.picture" alt="">
<div class="right">
<p>{{item.name}}</p>
<p>{{item.attrsText}}</p>
</div>
</a>
</td>
<td>¥{{item.payPrice}}</td>
<td>{{item.count}}</td>
<td>¥{{item.totalPrice}}</td>
<td>¥{{item.totalPayPrice}}</td>
</tr>
</tbody>
<div class="total">
<dl><dt>商品件数:</dt><dd>{{checkoutInfo.summary.goodsCount}}件</dd></dl>
<dl><dt>商品总价:</dt><dd>¥{{checkoutInfo.summary.totalPrice}}</dd></dl>
<dl><dt>运<i></i>费:</dt><dd>¥{{checkoutInfo.summary.postFee}}</dd></dl>
<dl><dt>应付总额:</dt><dd class="price">¥{{checkoutInfo.summary.totalPayPrice}}</dd></dl>
</div>
- 渲染地址组件
src/member/pay/components/checkout-address.vue
- 接收数据
props: {
list: {
type: Array,
default: () => []
}
},
- 得到默认显示地址
setup (props) {
// 显示的地址
const showAddress = ref(null)
if (props.list.length) {
const defaultAddress = props.list.find(item => item.isDefault === 1)
if (defaultAddress) {
showAddress.value = defaultAddress
} else {
// eslint-disable-next-line vue/no-setup-props-destructure
showAddress.value = props.list[0]
}
}
return { showAddress }
}
- 渲染组件
<div class="text">
<div v-if="!showAddress" class="none">您需要先添加收货地址才可提交订单。</div>
<ul v-if="showAddress">
<li><span>收<i/>货<i/>人:</span>{{showAddress.receiver}}</li>
<li><span>联系方式:</span>{{showAddress.contact}}</li>
<li><span>收货地址:</span>{{showAddress.fullLocation.replace(/ /g,'')+showAddress.address}}</li>
</ul>
<a v-if="showAddress" href="javascript:;">修改地址</a>
</div>
结算-提交订单
目的:汇总提交订单需要的数据,进行提交。
大致步骤:
- 定义需要提交的数据对象
- 绑定提交订单点击事件,进行提交即可
落的代码:
- 定义需要提交的数据对象
src/views/member/pay/checkout.vue
setup () {
const checkoutInfo = ref(null)
// 需要提交的字段
const requestParams = reactive({
addressId: null,
+ deliveryTimeType: 1,
+ payType: 1,
+ buyerMessage: '',
+ goods: []
})
findCheckoutInfo().then(data => {
checkoutInfo.value = data.result
+ // 设置提交时候的商品
+ requestParams.goods = checkoutInfo.value.goods.map(item => {
+ return {
+ skuId: item.skuId,
+ count: item.count
+ }
+ })
})
- 绑定提交订单点击事件,进行提交即可
src/api/order.js
提交订单API函数
/**
* 提交订单
* @param {Object} order - 订单信息对象
*/
export const createOrder = (order) => {
return request({
method: 'post',
url: '/member/order',
data: order
})
}
- 提交订单
src/views/member/pay/checkout.vue
<!-- 提交订单 -->
<div class="submit">
<XtxButton @click="submitOrder" type="primary">提交订单</XtxButton>
</div>
// 提交订单
const router = useRouter()
const submitOrder = () => {
if (!requestParams.addressId) return Message({ text: '请选择收货地址' })
createOrder(requestParams).then(data => {
router.push({ path: '/member/pay', query: { id: data.result.id } })
})
}
return { checkoutInfo, changeAddress, submitOrder }
支付-支付页面-基础布局
目的:配置路由和支付页面基础布局。
- 支付页面基本布局
src/views/member/pay/index.vue
<template>
<div class="xtx-pay-page">
<div class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem to="/cart">购物车</XtxBreadItem>
<XtxBreadItem>支付订单</XtxBreadItem>
</XtxBread>
<!-- 付款信息 -->
<div class="pay-info">
<span class="icon iconfont icon-queren2"></span>
<div class="tip">
<p>订单提交成功!请尽快完成支付。</p>
<p>支付还剩 <span>24分59秒</span>, 超时后将取消订单</p>
</div>
<div class="amount">
<span>应付总额:</span>
<span>¥5673.00</span>
</div>
</div>
<!-- 付款方式 -->
<div class="pay-type">
<p class="head">选择以下支付方式付款</p>
<div class="item">
<p>支付平台</p>
<a class="btn wx" href="javascript:;"></a>
<a class="btn alipay" href="javascript:;"></a>
</div>
<div class="item">
<p>支付方式</p>
<a class="btn" href="javascript:;">招商银行</a>
<a class="btn" href="javascript:;">工商银行</a>
<a class="btn" href="javascript:;">建设银行</a>
<a class="btn" href="javascript:;">农业银行</a>
<a class="btn" href="javascript:;">交通银行</a>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'XtxPayPage'
}
</script>
<style scoped lang="less">
.pay-info {
background: #fff;
display: flex;
align-items: center;
height: 240px;
padding: 0 80px;
.icon {
font-size: 80px;
color: #1dc779;
}
.tip {
padding-left: 10px;
flex: 1;
p {
&:first-child {
font-size: 20px;
margin-bottom: 5px;
}
&:last-child {
color: #999;
font-size: 16px;
}
}
}
.amount {
span {
&:first-child {
font-size: 16px;
color: #999;
}
&:last-child {
color: @priceColor;
font-size: 20px;
}
}
}
}
.pay-type {
margin-top: 20px;
background-color: #fff;
padding-bottom: 70px;
p {
line-height: 70px;
height: 70px;
padding-left: 30px;
font-size: 16px;
&.head {
border-bottom: 1px solid #f5f5f5;
}
}
.btn {
width: 150px;
height: 50px;
border: 1px solid #e4e4e4;
text-align: center;
line-height: 48px;
margin-left: 30px;
color: #666666;
display: inline-block;
&.active,
&:hover {
border-color: @xtxColor;
}
&.alipay {
background: url(https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7b6b02396368c9314528c0bbd85a2e06.png) no-repeat center / contain;
}
&.wx {
background: url(https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/c66f98cff8649bd5ba722c2e8067c6ca.jpg) no-repeat center / contain;
}
}
}
</style>
- 路由配置
src/router/index.js
const PayIndex = () => import('@/views/member/pay/index')
{ path: '/member/checkout', component: PayCheckout },
+ { path: '/member/pay', component: PayIndex }
支付-支付页面-信息展示
目的:展示支付的订单相关信息。
大致步骤:
- 准备API接口函数获取订单详情
- 在组件获取数据渲染
落的代码:
- 准备API接口函数获取订单详情
src/api/order.js
/**
* 获取订单详情
* @param {String} id - 订单ID
*/
export const findOrder = (id) => {
return request({
method: 'get',
url: '/member/order/' + id
})
}
- 在组件获取数据渲染
src/views/component/pay/index.vue
import { ref } from 'vue'
import { findOrder } from '@/api/order'
import { useRoute } from 'vue-router'
export default {
name: 'XtxPayPage',
setup () {
// 订单
const order = ref(null)
// 路由信息
const route = useRoute()
// 查询订单
findOrder(route.query.id).then(data => {
// 设置订单
order.value = data.result
})
return { order }
}
}
+ <div class="pay-info" v-if="order">
<div class="amount">
<span>应付总额:</span>
+ <span>¥{{order.payMoney}}</span>
</div>
支付-支付流程
目的:知道小兔鲜支付流程。
总结:
- PC前台点击支付按钮,新开标签页打开后台提供的支付链接带上订单ID还有回跳地址
- 后台服务发起支付,等待支付结果,修改订单状态,回跳PC前台结果页
- PC前台在结果页获取回跳URL参数订单ID查询支付状态,展示支付结果
# 支付地址回调地址(可变)
http://www.corho.com:8080/#/pay/callback
测试:如果使用客户端需要下载 沙箱支付宝
开放平台扫码下载。
买家账号jfjbwb4477@sandbox.com
登录密码111111
支付密码111111
支付-跳转支付
目的:支付打开新页,当前页打开提示框。
- 准备支付跳转链接
src/utils/request.js
export const baseURL = 'http://pcapi-xiaotuxian-front-devtest.itheima.net/'
- 拼接回跳地址链接
src/views/member/pay/index.vue
// 支付地址
// const payUrl = '后台服务基准地址+支付页面地址+订单ID+回跳地址'
const redirect = encodeURIComponent('http://www.corho.com:8080/#/pay/callback')
const payUrl = `${baseURL}pay/aliPay?orderId=${route.query.orderId}&redirect=${redirect}`
return { order, countdownText, payUrl }
<a class="btn alipay" :href="payUrl" target="_blank"></a>2.
支付-结果展示
目的:准备一个支付完成的回调页面,展示支付后订单状态。
大致步骤:
- 准备一个基础页面
- 根据地址订单ID查询订单状态进行展示,或者是地址栏支付结果。
落的代码:
- 准备一个基础页面
<template>
<div class="xtx-pay-page">
<div class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem to="/cart">购物车</XtxBreadItem>
<XtxBreadItem>支付结果</XtxBreadItem>
</XtxBread>
<!-- 支付结果 -->
<div class="pay-result">
<span class="iconfont icon-queren2 green"></span>
<!-- <span class="iconfont icon-shanchu red" ></span> -->
<p class="tit">订单支付成功</p>
<p class="tip">我们将尽快为您发货,收货期间请保持手机畅通</p>
<p>支付方式:<span>微信支付</span></p>
<p>支付金额:<span>¥1899.00</span></p>
<div class="btn">
<XtxButton type="primary" style="margin-right:20px">查看订单</XtxButton>
<XtxButton type="gray">进入首页</XtxButton>
</div>
<p class="alert">
<span class="iconfont icon-tip"></span>
温馨提示:小兔鲜儿不会以订单异常、系统升级为由要求您点击任何网址链接进行退款操作,保护资产、谨慎操作。
</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'XtxPayResultPage'
}
</script>
<style scoped lang="less">
.pay-result {
padding: 100px 0;
background: #fff;
text-align: center;
> .iconfont {
font-size: 100px;
}
.green {
color: #1dc779;
}
.red {
color: @priceColor;
}
.tit {
font-size: 24px;
}
.tip {
color: #999;
}
p {
line-height: 40px;
font-size: 16px;
}
.btn {
margin-top: 50px;
}
.alert {
font-size: 12px;
color: #999;
margin-top: 50px;
}
}
</style>
- 根据地址订单ID查询订单状态进行展示
<template>
<div class="xtx-pay-page">
<div class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem to="/cart">购物车</XtxBreadItem>
<XtxBreadItem>支付结果</XtxBreadItem>
</XtxBread>
<!-- 支付结果 -->
<div class="pay-result" v-if="order">
<span v-if="$route.query.payResult" class="iconfont icon-queren2 green"></span>
<span v-else class="iconfont icon-shanchu red" ></span>
<p class="tit">订单支付{{$route.query.payResult?'成功':'失败'}}</p>
<p class="tip">我们将尽快为您发货,收货期间请保持手机畅通</p>
<p>支付方式:<span>支付宝支付</span></p>
<p>支付金额:<span class="red">¥{{order.payMoney}}</span></p>
<div class="btn">
<XtxButton @click="$router.push('/member/order')" type="primary" style="margin-right:20px">查看订单</XtxButton>
<XtxButton @click="$router.push('/')" type="gray">进入首页</XtxButton>
</div>
<p class="alert">
<span class="iconfont icon-tip"></span>
温馨提示:小兔鲜儿不会以订单异常、系统升级为由要求您点击任何网址链接进行退款操作,保护资产、谨慎操作。
</p>
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import { findOrderDetail } from '@/api/order'
import { useRoute } from 'vue-router'
export default {
name: 'XtxPayResultPage',
setup () {
const order = ref(null)
const route = useRoute()
findOrderDetail(route.query.orderId).then(data => {
order.value = data.result
})
return { order }
}
}
</script>
<style scoped lang="less">
.pay-result {
padding: 100px 0;
background: #fff;
text-align: center;
> .iconfont {
font-size: 100px;
}
.green {
color: #1dc779;
}
.red {
color: @priceColor;
}
.tit {
font-size: 24px;
}
.tip {
color: #999;
}
p {
line-height: 40px;
font-size: 16px;
}
.btn {
margin-top: 50px;
}
.alert {
font-size: 12px;
color: #999;
margin-top: 50px;
}
}
</style>