3fecd77739
新增了订单支付页面和支付成功页面,分别用于处理用户的支付流程和展示支付结果。同时,更新了订单状态的逻辑,确保在订单列表中正确显示各个状态。调整了设备租借逻辑,优化了扫码处理流程,提升用户体验。
589 lines
15 KiB
Vue
589 lines
15 KiB
Vue
<template>
|
||
<view class="payment-container">
|
||
<!-- 订单状态 -->
|
||
<view class="status-card">
|
||
<view class="status-icon" :class="orderStatus.class"></view>
|
||
<view class="status-text">{{ orderStatus.text }}</view>
|
||
<view class="status-desc">{{ orderStatus.desc }}</view>
|
||
</view>
|
||
|
||
<!-- 订单信息 -->
|
||
<view class="order-card">
|
||
<view class="card-title">订单信息</view>
|
||
<view class="info-item">
|
||
<text class="label">订单号</text>
|
||
<text class="value">{{ orderInfo.orderNo || '-' }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="label">设备号</text>
|
||
<text class="value">{{ orderInfo.deviceNo || '-' }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="label">创建时间</text>
|
||
<text class="value">{{ orderInfo.createTime || '-' }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="label">联系电话</text>
|
||
<text class="value">{{ orderInfo.phone || '-' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 费用信息 -->
|
||
<view class="price-card">
|
||
<view class="card-title">费用信息</view>
|
||
<view class="price-item">
|
||
<text class="label">押金</text>
|
||
<text class="value">¥{{ orderInfo.deposit || '99.00' }}</text>
|
||
</view>
|
||
<view class="price-item">
|
||
<text class="label">套餐</text>
|
||
<text class="value">{{ packageInfo.time }} (¥{{ packageInfo.price }})</text>
|
||
</view>
|
||
<view class="price-item">
|
||
<text class="label">租借费用</text>
|
||
<text class="value">¥{{ orderInfo.amount || packageInfo.price }}</text>
|
||
</view>
|
||
<view class="price-item total">
|
||
<text class="label">合计</text>
|
||
<text class="value">¥{{ totalAmount }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 支付方式 -->
|
||
<view class="payment-methods">
|
||
<view class="card-title">支付方式</view>
|
||
<view
|
||
v-for="(method, index) in paymentMethods"
|
||
:key="index"
|
||
class="method-item"
|
||
:class="{ active: selectedMethod === index }"
|
||
@click="selectMethod(index)"
|
||
>
|
||
<view class="method-icon" :class="method.icon"></view>
|
||
<view class="method-name">{{ method.name }}</view>
|
||
<view class="method-check"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部操作栏 -->
|
||
<view class="bottom-bar">
|
||
<view class="total-amount">
|
||
<text>合计:</text>
|
||
<text class="amount">¥{{ totalAmount }}</text>
|
||
</view>
|
||
<button class="pay-btn" @click="handlePayment">立即支付</button>
|
||
</view>
|
||
|
||
<view class="back-btn" @click="navigateBack">
|
||
<text>返回设备详情</text>
|
||
</view>
|
||
|
||
methods: {
|
||
|
||
|
||
navigateBack() {
|
||
uni.redirectTo({
|
||
url: `/pages/device/detail?deviceId=${this.deviceId}`
|
||
})
|
||
}
|
||
}
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { queryById } from '@/config/user.js'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
orderId: null,
|
||
orderInfo: {},
|
||
packageInfo: {
|
||
time: '',
|
||
price: '0.00'
|
||
},
|
||
orderStatus: {
|
||
text: '等待支付',
|
||
desc: '请在15分钟内完成支付',
|
||
class: 'waiting'
|
||
},
|
||
paymentMethods: [
|
||
{
|
||
name: '微信支付',
|
||
icon: 'wechat'
|
||
},
|
||
{
|
||
name: '支付宝',
|
||
icon: 'alipay'
|
||
}
|
||
],
|
||
selectedMethod: 0
|
||
}
|
||
},
|
||
computed: {
|
||
totalAmount() {
|
||
const deposit = parseFloat(this.orderInfo.deposit || 99)
|
||
const amount = parseFloat(this.orderInfo.amount || this.packageInfo.price || 0)
|
||
return (deposit + amount).toFixed(2)
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
if (options && options.orderId) {
|
||
this.orderId = options.orderId
|
||
|
||
// 获取传递的套餐信息
|
||
if (options.packageTime && options.packagePrice) {
|
||
this.packageInfo = {
|
||
time: options.packageTime,
|
||
price: options.packagePrice
|
||
}
|
||
}
|
||
|
||
this.loadOrderInfo()
|
||
} else {
|
||
uni.showToast({
|
||
title: '订单信息不存在',
|
||
icon: 'none'
|
||
})
|
||
setTimeout(() => {
|
||
uni.redirectTo({
|
||
url: '/pages/index/index'
|
||
})
|
||
}, 1500)
|
||
}
|
||
},
|
||
methods: {
|
||
// 加载订单信息
|
||
async loadOrderInfo() {
|
||
try {
|
||
uni.showLoading({
|
||
title: '加载中'
|
||
})
|
||
|
||
const res = await queryById(this.orderId)
|
||
if (res.code === 200 && res.data) {
|
||
const orderData = res.data
|
||
this.orderInfo = {
|
||
orderNo: orderData.orderNo || orderData.orderId,
|
||
deviceNo: orderData.deviceNo,
|
||
createTime: orderData.createTime,
|
||
phone: orderData.phone,
|
||
deposit: '99.00', // 假设押金固定为99元
|
||
amount: orderData.amount || this.packageInfo.price || '0.00'
|
||
}
|
||
|
||
// 如果订单中没有套餐信息,但URL参数中有,则使用URL参数中的套餐信息
|
||
if (!orderData.packageTime && this.packageInfo.time) {
|
||
this.orderInfo.packageTime = this.packageInfo.time
|
||
this.orderInfo.packagePrice = this.packageInfo.price
|
||
}
|
||
} else {
|
||
throw new Error('获取订单信息失败')
|
||
}
|
||
|
||
uni.hideLoading()
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: error.message || '获取订单信息失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
|
||
// 选择支付方式
|
||
selectMethod(index) {
|
||
this.selectedMethod = index
|
||
},
|
||
|
||
// 处理支付
|
||
async handlePayment() {
|
||
try {
|
||
uni.showLoading({
|
||
title: '处理中'
|
||
})
|
||
|
||
// 调用后端创建微信支付订单接口
|
||
const res = await uni.request({
|
||
url: `${uni.getStorageSync('baseUrl') || 'http://127.0.0.1:8080'}/app/wx-payment/create/${this.orderInfo.orderNo}`,
|
||
method: 'GET',
|
||
header: {
|
||
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
||
'Clientid': uni.getStorageSync('client_id')
|
||
}
|
||
})
|
||
|
||
if (res.statusCode === 200 && res.data.code === 200) {
|
||
// 支付成功,跳转到支付成功页面
|
||
uni.hideLoading()
|
||
uni.redirectTo({
|
||
url: `/pages/order/success?orderId=${this.orderId}`
|
||
})
|
||
} else {
|
||
throw new Error(res.data.msg || '支付失败')
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: error.message || '支付失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
|
||
// 发送租借指令
|
||
async sendRentCommand() {
|
||
try {
|
||
uni.showLoading({
|
||
title: '处理中'
|
||
})
|
||
|
||
// 调用发送租借指令的接口
|
||
const res = await this.sendRentRequest()
|
||
|
||
if (res.code === 200) {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '租借成功',
|
||
icon: 'success'
|
||
})
|
||
|
||
// 跳转到订单列表页面
|
||
setTimeout(() => {
|
||
uni.redirectTo({
|
||
url: `/pages/order/index?orderId=${this.orderId}`
|
||
})
|
||
}, 1500)
|
||
} else {
|
||
throw new Error(res.msg || '租借失败')
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: error.message || '租借失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
|
||
// 发送租借请求
|
||
sendRentRequest() {
|
||
return new Promise((resolve, reject) => {
|
||
uni.request({
|
||
url: `${uni.getStorageSync('baseUrl') || 'http://127.0.0.1:8080'}/app/device/sendRentCommand`,
|
||
method: 'POST',
|
||
data: {
|
||
orderId: this.orderId
|
||
},
|
||
header: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
||
'Clientid': uni.getStorageSync('client_id')
|
||
},
|
||
success(res) {
|
||
if (res.statusCode === 200) {
|
||
resolve(res.data)
|
||
} else {
|
||
reject(new Error('请求失败'))
|
||
}
|
||
},
|
||
fail(err) {
|
||
reject(err)
|
||
}
|
||
})
|
||
})
|
||
},
|
||
|
||
// 格式化时间
|
||
formatTime(date) {
|
||
const year = date.getFullYear()
|
||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||
const day = date.getDate().toString().padStart(2, '0')
|
||
const hour = date.getHours().toString().padStart(2, '0')
|
||
const minute = date.getMinutes().toString().padStart(2, '0')
|
||
|
||
return `${year}-${month}-${day} ${hour}:${minute}`
|
||
},
|
||
|
||
// 轮询订单状态
|
||
async pollOrderStatus() {
|
||
let retryCount = 0;
|
||
const maxRetries = 10;
|
||
const interval = 1000; // 1秒间隔
|
||
|
||
const checkStatus = async () => {
|
||
try {
|
||
const res = await uni.request({
|
||
url: `${uni.getStorageSync('baseUrl') || 'http://127.0.0.1:8080'}/app/payment/status/${this.orderInfo.orderNo}`,
|
||
method: 'GET',
|
||
header: {
|
||
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
||
'Clientid': uni.getStorageSync('client_id')
|
||
}
|
||
});
|
||
|
||
if (res.statusCode === 200 && res.data.code === 200) {
|
||
const orderData = res.data.data;
|
||
if (orderData.orderStatus === 'IN_USED') {
|
||
// 支付成功,订单已开始使用
|
||
uni.showToast({
|
||
title: '支付成功',
|
||
icon: 'success'
|
||
});
|
||
setTimeout(() => {
|
||
uni.redirectTo({
|
||
url: `/pages/order/success?orderId=${this.orderId}`
|
||
});
|
||
}, 1500);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (retryCount < maxRetries) {
|
||
retryCount++;
|
||
setTimeout(checkStatus, interval);
|
||
} else {
|
||
throw new Error('订单状态查询超时');
|
||
}
|
||
} catch (error) {
|
||
uni.showToast({
|
||
title: error.message || '查询订单状态失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
};
|
||
|
||
checkStatus();
|
||
},
|
||
}
|
||
}
|
||
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.payment-container {
|
||
min-height: 100vh;
|
||
background: #f8f8f8;
|
||
padding: 30rpx;
|
||
padding-bottom: 180rpx;
|
||
box-sizing: border-box;
|
||
|
||
.status-card {
|
||
background: #fff;
|
||
border-radius: 24rpx;
|
||
padding: 40rpx 30rpx;
|
||
margin-bottom: 30rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||
|
||
.status-icon {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
border-radius: 50%;
|
||
background: #f5f5f5;
|
||
margin-bottom: 20rpx;
|
||
|
||
&.waiting {
|
||
background: #FFF9C4;
|
||
}
|
||
|
||
&.success {
|
||
background: #E8F5E9;
|
||
}
|
||
|
||
&.failed {
|
||
background: #FFEBEE;
|
||
}
|
||
}
|
||
|
||
.status-text {
|
||
font-size: 36rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.status-desc {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
.order-card, .price-card {
|
||
background: #fff;
|
||
border-radius: 24rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 30rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||
|
||
.card-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 20rpx;
|
||
position: relative;
|
||
padding-left: 20rpx;
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 8rpx;
|
||
height: 32rpx;
|
||
background: #1976D2;
|
||
border-radius: 4rpx;
|
||
}
|
||
}
|
||
|
||
.info-item, .price-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20rpx 0;
|
||
border-bottom: 1px solid #f5f5f5;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.label {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.value {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
&.total {
|
||
margin-top: 10rpx;
|
||
padding-top: 30rpx;
|
||
border-top: 1px solid #f5f5f5;
|
||
|
||
.label, .value {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.value {
|
||
color: #FF5722;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.payment-methods {
|
||
background: #fff;
|
||
border-radius: 24rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 30rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||
|
||
.method-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 30rpx 20rpx;
|
||
border-bottom: 1px solid #f5f5f5;
|
||
position: relative;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
&.active {
|
||
background: #F5F5F5;
|
||
|
||
.method-check {
|
||
background: #1976D2;
|
||
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
background: #fff;
|
||
border-radius: 50%;
|
||
}
|
||
}
|
||
}
|
||
|
||
.method-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
margin-right: 20rpx;
|
||
|
||
&.wechat {
|
||
background: url('../../static/images/wechat.svg') no-repeat center/contain;
|
||
}
|
||
|
||
&.alipay {
|
||
background: url('../../static/images/alipay.svg') no-repeat center/contain;
|
||
}
|
||
}
|
||
|
||
.method-name {
|
||
flex: 1;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.method-check {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
border-radius: 50%;
|
||
border: 2rpx solid #ddd;
|
||
position: relative;
|
||
}
|
||
}
|
||
}
|
||
|
||
.bottom-bar {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: #fff;
|
||
padding: 20rpx 30rpx;
|
||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||
|
||
.total-amount {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
|
||
.amount {
|
||
font-size: 36rpx;
|
||
font-weight: 600;
|
||
color: #FF5722;
|
||
margin-left: 10rpx;
|
||
}
|
||
}
|
||
|
||
.pay-btn {
|
||
background: #1976D2;
|
||
color: #fff;
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
padding: 20rpx 60rpx;
|
||
border-radius: 100rpx;
|
||
border: none;
|
||
|
||
&:active {
|
||
opacity: 0.9;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style> |