d9e70d4eaf
完成套餐从数据库中提取
600 lines
17 KiB
Vue
600 lines
17 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.price }}元/{{ packageInfo.time }}小时</text>
|
||
</view>
|
||
<view class="price-item total">
|
||
<text class="label">合计</text>
|
||
<text class="value">¥{{ totalAmount }}</text>
|
||
</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>
|
||
</template>
|
||
|
||
<script>
|
||
import { queryById } from '@/config/user.js'
|
||
import { getDeviceInfo } from '@/config/user.js'
|
||
import { updateOrderPackage } from '@/config/user.js'
|
||
import { updateUserBalance } from '@/config/user.js'
|
||
import {
|
||
URL
|
||
}from"@/config/url.js"
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
orderId: null,
|
||
deviceNo: null,
|
||
orderInfo: {},
|
||
packageInfo: {
|
||
time: '',
|
||
price: '0.00'
|
||
},
|
||
deviceInfo: null,
|
||
passedTotalAmount: null,
|
||
passedDepositAmount: null,
|
||
orderStatus: {
|
||
text: '等待支付',
|
||
desc: '请在15分钟内完成支付',
|
||
class: 'waiting'
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
totalAmount() {
|
||
if (this.passedTotalAmount !== null) {
|
||
return parseFloat(this.passedTotalAmount).toFixed(2);
|
||
}
|
||
const deposit = parseFloat(this.orderInfo.deposit || this.passedDepositAmount || 99)
|
||
const packagePrice = parseFloat(this.packageInfo.price || 0)
|
||
return (deposit + packagePrice).toFixed(2)
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
if (options && options.orderId) {
|
||
this.orderId = options.orderId
|
||
|
||
if (options.totalAmount) {
|
||
this.passedTotalAmount = options.totalAmount;
|
||
}
|
||
|
||
if (options.depositAmount) {
|
||
this.passedDepositAmount = options.depositAmount;
|
||
}
|
||
|
||
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
|
||
|
||
// 处理创建时间,确保显示的是格式化后的时间
|
||
let formattedTime;
|
||
try {
|
||
// 如果orderData.createTime存在并且是有效的日期字符串/时间戳,则格式化它
|
||
if (orderData.createTime) {
|
||
formattedTime = this.formatTime(new Date(orderData.createTime));
|
||
} else {
|
||
// 如果createTime不存在,使用当前时间作为创建时间
|
||
formattedTime = this.formatTime(new Date());
|
||
}
|
||
} catch (e) {
|
||
console.error('时间格式化错误:', e);
|
||
formattedTime = this.formatTime(new Date());
|
||
}
|
||
|
||
this.orderInfo = {
|
||
orderNo: orderData.orderNo || orderData.orderId,
|
||
deviceNo: orderData.deviceNo,
|
||
createTime: formattedTime,
|
||
phone: orderData.phone,
|
||
deposit: this.passedDepositAmount || orderData.depositAmount || '99.00',
|
||
}
|
||
|
||
// 获取设备信息并解析套餐
|
||
this.deviceNo = orderData.deviceNo;
|
||
await this.loadDeviceInfo();
|
||
} else {
|
||
throw new Error('获取订单信息失败')
|
||
}
|
||
|
||
uni.hideLoading()
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: error.message || '获取订单信息失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
|
||
// 加载设备信息并解析套餐
|
||
async loadDeviceInfo() {
|
||
if (!this.deviceNo) return;
|
||
|
||
try {
|
||
const res = await getDeviceInfo(this.deviceNo);
|
||
if (res.code === 200 && res.data) {
|
||
this.deviceInfo = res.data.device;
|
||
|
||
// 解析feeConfig获取套餐信息
|
||
if (this.deviceInfo && this.deviceInfo.feeConfig) {
|
||
try {
|
||
const feeConfig = JSON.parse(this.deviceInfo.feeConfig);
|
||
|
||
// 检查是否为新格式 [{"Hour":1,"Price":4},{"Hour":3,"Price":10},{"Hour":5,"Price":15}]
|
||
if (feeConfig.length > 0 && 'Hour' in feeConfig[0] && 'Price' in feeConfig[0]) {
|
||
// 尝试找到对应包时长的套餐
|
||
// 默认使用6小时或最接近的套餐
|
||
const targetHours = 6;
|
||
let closestPackage = feeConfig[0];
|
||
|
||
// 找出最接近目标时长的套餐
|
||
feeConfig.forEach(pkg => {
|
||
if (Math.abs(pkg.Hour - targetHours) < Math.abs(closestPackage.Hour - targetHours)) {
|
||
closestPackage = pkg;
|
||
}
|
||
});
|
||
|
||
this.packageInfo.time = closestPackage.Hour.toString();
|
||
this.packageInfo.price = closestPackage.Price.toString();
|
||
} else {
|
||
// 旧格式处理
|
||
// 通常使用common配置
|
||
const selectedConfig = feeConfig.find(item => item.specCode === 'common') || feeConfig[0];
|
||
|
||
if (selectedConfig) {
|
||
// 套餐时间
|
||
const packageHours = 6; // 默认6小时
|
||
|
||
// 如果是按次收费,则直接使用timesPrice
|
||
if (this.deviceInfo.feeType === 'times') {
|
||
this.packageInfo.price = selectedConfig.timesPrice.toString();
|
||
this.packageInfo.time = '1次';
|
||
}
|
||
// 如果是按小时收费
|
||
else if (this.deviceInfo.feeType === 'hour') {
|
||
if (selectedConfig.hourPrice > 0) {
|
||
// 如果有设置小时价格,计算套餐价格
|
||
this.packageInfo.price = (selectedConfig.hourPrice * packageHours).toString();
|
||
} else {
|
||
// 否则使用timesPrice作为套餐价格
|
||
this.packageInfo.price = selectedConfig.timesPrice.toString();
|
||
}
|
||
this.packageInfo.time = packageHours.toString();
|
||
}
|
||
// 如果是其他收费类型,使用timesPrice
|
||
else {
|
||
this.packageInfo.price = selectedConfig.timesPrice.toString();
|
||
this.packageInfo.time = packageHours.toString();
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('解析设备费用配置失败:', e);
|
||
}
|
||
}
|
||
|
||
// 设置存款金额
|
||
if (this.deviceInfo && this.deviceInfo.depositAmount) {
|
||
this.orderInfo.deposit = this.deviceInfo.depositAmount;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('获取设备信息失败:', error);
|
||
}
|
||
},
|
||
|
||
// 处理支付
|
||
async handlePayment() {
|
||
try {
|
||
uni.showLoading({
|
||
title: '处理中'
|
||
})
|
||
|
||
// 调用后端创建微信支付订单接口
|
||
const res = await uni.request({
|
||
url: `${URL || '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) {
|
||
const payParams = res.data.data
|
||
|
||
// 调用微信支付
|
||
await uni.requestPayment({
|
||
...payParams,
|
||
success: async () => {
|
||
uni.showToast({
|
||
title: '支付成功',
|
||
icon: 'success'
|
||
});
|
||
|
||
// 更新用户余额
|
||
try {
|
||
await updateUserBalance(this.orderId);
|
||
} catch (error) {
|
||
console.warn('更新用户余额失败:', error);
|
||
}
|
||
|
||
// 支付成功后直接跳转到订单页面,不再轮询
|
||
setTimeout(() => {
|
||
uni.redirectTo({
|
||
url: `/pages/order/index?orderId=${this.orderId}`
|
||
});
|
||
}, 1500);
|
||
},
|
||
fail: (err) => {
|
||
console.error('支付失败:', err)
|
||
throw new Error('支付失败,请重试')
|
||
}
|
||
})
|
||
} 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: `${URL || '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 checkOrderStatus() {
|
||
try {
|
||
const res = await uni.request({
|
||
url: `${URL || 'http://127.0.0.1:8080'}/app/wx-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) {
|
||
return res.data.data;
|
||
} else {
|
||
throw new Error('查询订单状态失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('查询订单状态错误:', error);
|
||
return null;
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
</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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.wechat-tip {
|
||
background: #fff;
|
||
border-radius: 24rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 30rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.method-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
margin-right: 20rpx;
|
||
|
||
&.wechat {
|
||
background: url('../../static/images/wechat.svg') no-repeat center/contain;
|
||
}
|
||
}
|
||
|
||
.method-text {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.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> |