feat: 新增多个页面及功能,优化用户体验

在项目中新增了多个页面,包括押金页面、设备详情页面、反馈页面和帮助页面。同时,更新了订单支付和归还成功页面的逻辑,确保用户在支付和归还设备时能够获得清晰的反馈。优化了扫码和订单状态处理逻辑,提升了整体用户体验。
This commit is contained in:
8vd8
2025-04-16 18:26:02 +08:00
parent f96ff2b030
commit 431ceb4bdb
117 changed files with 1854 additions and 616 deletions
+145 -193
View File
@@ -49,21 +49,8 @@
</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">
@@ -73,25 +60,14 @@
</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'
import {
URL
}from"@/config/url.js"
export default {
data() {
@@ -106,18 +82,7 @@ export default {
text: '等待支付',
desc: '请在15分钟内完成支付',
class: 'waiting'
},
paymentMethods: [
{
name: '微信支付',
icon: 'wechat'
},
{
name: '支付宝',
icon: 'alipay'
}
],
selectedMethod: 0
}
}
},
computed: {
@@ -163,10 +128,26 @@ export default {
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: orderData.createTime,
createTime: formattedTime,
phone: orderData.phone,
deposit: '99.00', // 假设押金固定为99元
amount: orderData.amount || this.packageInfo.price || '0.00'
@@ -191,11 +172,6 @@ export default {
}
},
// 选择支付方式
selectMethod(index) {
this.selectedMethod = index
},
// 处理支付
async handlePayment() {
try {
@@ -203,69 +179,35 @@ export default {
title: '处理中'
})
console.log('开始处理支付订单号:', this.orderId)
// 调用后端支付API
// 调用后端创建微信支付订单接口
const res = await uni.request({
url: `${uni.getStorageSync('baseUrl') || 'http://127.0.0.1:8080'}/app/payment/${this.orderInfo.orderNo}`,
url: `${URL || 'http://127.0.0.1:8080'}/app/wx-payment/create/${this.orderInfo.orderNo}`,
method: 'GET',
header: {
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
})
console.log('支付API返回结果:', res.data)
if (res.statusCode === 200 && res.data.code === 200) {
// 获取微信支付所需参数
const payParams = res.data.data
console.log('准备调用微信支付,参数:', payParams)
// 验证支付参数是否完整
if (!payParams.timeStamp || !payParams.nonceStr ||
!payParams.packageValue || !payParams.paySign) {
console.error('支付参数不完整:', payParams)
throw new Error('支付参数不完整,请联系客服')
}
// 调用微信支付API
uni.requestPayment({
provider: 'wxpay',
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.packageValue, // 后端返回的是packageValue字段
signType: payParams.signType || 'MD5',
paySign: payParams.paySign,
success: (payRes) => {
console.log('支付成功:', payRes)
uni.hideLoading()
// 支付成功后开始轮询订单状态
// 调用微信支付
await uni.requestPayment({
...payParams,
success: () => {
// 支付成功后轮询订单状态
this.pollOrderStatus()
},
fail: (err) => {
console.error('支付失败:', err)
uni.hideLoading()
// 用户取消支付的情况,不显示错误提示
if (err.errMsg === 'requestPayment:fail cancel') {
console.log('用户取消了支付')
return
}
uni.showToast({
title: err.errMsg || '支付失败',
icon: 'none'
})
},
complete: () => {
console.log('支付流程结束')
throw new Error('支付失败,请重试')
}
})
} else {
throw new Error(res.data?.msg || '支付请求失败')
throw new Error(res.data.msg || '创建支付订单失败')
}
} catch (error) {
console.error('支付处理错误:', error)
uni.hideLoading()
uni.showToast({
title: error.message || '支付失败',
@@ -274,35 +216,108 @@ export default {
}
},
// 发送租借指令
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 pollOrderStatus() {
let retryCount = 0;
const maxRetries = 30; // 增加最大重试次数,允许更长时间检测
const interval = 2000; // 调整为2秒间隔
const maxRetries = 10;
const interval = 1000; // 1秒间隔
const checkStatus = async () => {
try {
// 使用queryById方法直接查询订单状态
const res = await queryById(this.orderId);
console.log('轮询订单状态结果:', res);
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.code === 200 && res.data) {
const orderData = res.data;
// 检查订单是否已支付成功
if (orderData.orderStatus === 'IN_USED' ||
orderData.orderStatus === 'PAYMENT_SUCCESSFUL') {
console.log('支付成功,订单状态:', orderData.orderStatus);
// 显示弹出充电宝的提示
if (res.statusCode === 200 && res.data.code === 200) {
const orderData = res.data.data;
if (orderData.orderStatus === 'in_used') {
// 支付成功,订单已开始使用
uni.showToast({
title: '支付成功,充电宝已弹出',
icon: 'success',
duration: 2000
title: '支付成功',
icon: 'success'
});
// 延迟跳转到支付成功页面
setTimeout(() => {
uni.redirectTo({
url: `/pages/order/success?orderId=${this.orderId}`
url: `/pages/order/index?orderId=${this.orderId}`
});
}, 1500);
return;
@@ -311,39 +326,18 @@ export default {
if (retryCount < maxRetries) {
retryCount++;
console.log(`${retryCount}次轮询,等待订单状态更新...`);
setTimeout(checkStatus, interval);
} else {
console.error('轮询订单状态超时');
uni.showModal({
title: '提示',
content: '订单状态查询超时,请在"我的订单"中查看订单状态',
showCancel: false,
success: function(res) {
if (res.confirm) {
uni.redirectTo({
url: '/pages/order/index'
});
}
}
});
throw new Error('订单状态查询超时');
}
} catch (error) {
console.error('查询订单状态失败:', error);
// 出错时继续轮询,不要中断
if (retryCount < maxRetries) {
retryCount++;
setTimeout(checkStatus, interval);
} else {
uni.showToast({
title: '查询订单状态失败',
icon: 'none'
});
}
uni.showToast({
title: error.message || '查询订单状态失败',
icon: 'none'
});
}
};
// 开始轮询
checkStatus();
},
}
@@ -469,72 +463,30 @@ export default {
}
}
.payment-methods {
.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-item {
display: flex;
align-items: center;
padding: 30rpx 20rpx;
border-bottom: 1px solid #f5f5f5;
position: relative;
.method-icon {
width: 48rpx;
height: 48rpx;
margin-right: 20rpx;
&: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;
&.wechat {
background: url('../../static/images/wechat.svg') no-repeat center/contain;
}
}
.method-text {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
}
.bottom-bar {
+220 -75
View File
@@ -4,7 +4,7 @@
<view class="status-card">
<view class="status-icon success"></view>
<view class="status-text">归还成功</view>
<view class="status-desc">您的充电宝已归还押金已退回</view>
<view class="status-desc">您的充电宝已归还费用已从押金中扣除</view>
</view>
<!-- 订单信息 -->
@@ -23,8 +23,12 @@
<text class="value">{{ orderInfo.usedTime || '-' }}</text>
</view>
<view class="info-item">
<text class="label">费用</text>
<text class="value">{{ orderInfo.currentFee || '0.00' }}</text>
<text class="label">套餐时长</text>
<text class="value">{{ orderInfo.packageTime || '1小时' }}</text>
</view>
<view class="info-item">
<text class="label">超出时长</text>
<text class="value">{{ orderInfo.extraTime || '0分钟' }}</text>
</view>
<view class="info-item">
<text class="label">归还时间</text>
@@ -32,9 +36,21 @@
</view>
</view>
<!-- 退还信息 -->
<!-- 费用信息 -->
<view class="refund-card">
<view class="card-title">退还信息</view>
<view class="card-title">费用信息</view>
<view class="info-item">
<text class="label">套餐费用</text>
<text class="value">{{ orderInfo.packagePrice || '0.00' }}</text>
</view>
<view class="info-item">
<text class="label">超时费用</text>
<text class="value">{{ orderInfo.extraFee || '0.00' }}</text>
</view>
<view class="info-item">
<text class="label">总费用</text>
<text class="value">{{ orderInfo.currentFee || '0.00' }}</text>
</view>
<view class="info-item">
<text class="label">押金</text>
<text class="value">{{ orderInfo.deposit || '99.00' }}</text>
@@ -45,20 +61,33 @@
</view>
<view class="info-item">
<text class="label">退还状态</text>
<text class="value success">已退还</text>
<text class="value" :class="orderInfo.withdrawStatus || 'waiting'">{{ getWithdrawStatusText() }}</text>
</view>
</view>
<!-- 退款说明卡片 -->
<view class="notice-card">
<view class="card-title">退款说明</view>
<view class="notice-content">
<text>1. 押金剩余金额需要您手动申请提现</text>
<text>2. 提现申请提交后将在1-3个工作日内退还到原支付账户</text>
<text>3. 如有疑问请联系客服</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="button-group">
<button class="primary-btn" @click="handleWithdraw" v-if="!orderInfo.isWithdrawn && orderInfo.refundAmount > 0">申请退款</button>
<button class="primary-btn" @click="goToHome">返回首页</button>
<button class="secondary-btn" @click="goToOrderList">查看订单</button>
</view>
</view>
</template>
<script>
import { queryById } from '@/config/user.js'
import {
URL
}from"@/config/url.js"
export default {
data() {
@@ -67,95 +96,182 @@ export default {
orderInfo: {
orderNo: '',
deviceNo: '',
startTime: '',
usedTime: '',
currentFee: '0.00',
deposit: '99.00',
refundAmount: '99.00',
endTime: ''
endTime: '',
withdrawStatus: 'waiting',
isWithdrawn: false
}
}
},
onLoad(options) {
if (options && options.orderId) {
this.orderId = options.orderId
this.loadOrderInfo()
this.orderId = options.orderId;
this.loadOrderInfo();
} else {
uni.showToast({
title: '订单信息不存在',
title: '订单ID不能为空',
icon: 'none'
})
});
setTimeout(() => {
this.goToHome()
}, 1500)
this.goToHome();
}, 1500);
}
},
methods: {
// 获取退款状态文本
getWithdrawStatusText() {
const statusMap = {
'waiting': '待申请',
'processing': '处理中',
'success': '已退款',
'failed': '退款失败'
};
return statusMap[this.orderInfo.withdrawStatus] || '待申请';
},
// 加载订单信息
async loadOrderInfo() {
try {
uni.showLoading({
title: '加载中'
})
uni.showLoading({ title: '加载中' });
const res = await queryById(this.orderId)
console.log('归还成功页面获取的订单数据:', JSON.stringify(res.data))
if (res.code === 200 && res.data) {
const orderData = res.data
console.log('订单的开始时间:', orderData.startTime)
console.log('订单的创建时间:', orderData.createTime)
const result = await queryById(this.orderId);
if (result.code === 200 && result.data) {
const orderData = result.data;
// 计算退还金额 (押金 - 费用)
const deposit = 99.00
const fee = parseFloat(orderData.currentFee || orderData.actualDeviceAmount || orderData.payAmount || '0')
const refundAmount = (deposit - fee).toFixed(2)
// 从remark字段中解析使用信息
let packageMinutes = 60;
let extraMinutes = 0;
let usedMinutes = 0;
let packagePrice = '0.00';
let extraFee = '0.00';
this.orderInfo = {
orderNo: orderData.orderNo || orderData.orderId,
deviceNo: orderData.deviceNo,
startTime: orderData.startTime || orderData.createTime || '-',
usedTime: orderData.usedTime || '-',
currentFee: orderData.currentFee || orderData.actualDeviceAmount || orderData.payAmount || '0.00',
deposit: deposit.toFixed(2),
refundAmount: refundAmount,
endTime: orderData.endTime || this.formatTime(new Date())
if (orderData.remark) {
try {
// 解析remark字段
const remarkInfo = orderData.remark;
// 尝试提取使用时长
const usedTimeMatch = remarkInfo.match(/使用时长:(\d+)分钟/);
if (usedTimeMatch && usedTimeMatch[1]) {
usedMinutes = parseInt(usedTimeMatch[1]);
}
// 尝试提取套餐时长
const packageTimeMatch = remarkInfo.match(/套餐时长:(\d+)分钟/);
if (packageTimeMatch && packageTimeMatch[1]) {
packageMinutes = parseInt(packageTimeMatch[1]);
}
// 尝试提取超出时长
const extraTimeMatch = remarkInfo.match(/超出时长:(\d+)分钟/);
if (extraTimeMatch && extraTimeMatch[1]) {
extraMinutes = parseInt(extraTimeMatch[1]);
}
// 尝试提取套餐费用
const packagePriceMatch = remarkInfo.match(/套餐费用:([\d.]+)元/);
if (packagePriceMatch && packagePriceMatch[1]) {
packagePrice = packagePriceMatch[1];
}
// 尝试提取超时费用
const extraFeeMatch = remarkInfo.match(/超时费用:([\d.]+)元/);
if (extraFeeMatch && extraFeeMatch[1]) {
extraFee = extraFeeMatch[1];
}
console.log('从remark解析到的信息:', {
usedMinutes,
packageMinutes,
extraMinutes,
packagePrice,
extraFee
});
} catch (e) {
console.error('解析remark字段失败:', e);
}
}
console.log('处理后的订单信息:', JSON.stringify(this.orderInfo))
this.orderInfo = {
orderNo: orderData.orderNo || '',
deviceNo: orderData.deviceNo || '',
usedTime: usedMinutes + '分钟',
packageTime: packageMinutes + '分钟',
extraTime: extraMinutes + '分钟',
packagePrice: packagePrice,
extraFee: extraFee,
currentFee: orderData.actualDeviceAmount || '0.00',
deposit: orderData.depositAmount || '99.00',
refundAmount: orderData.residueAmount || '99.00',
endTime: orderData.endTime || '',
withdrawStatus: orderData.withdrawStatus || 'waiting',
isWithdrawn: orderData.withdrawStatus === 'success'
};
} else {
throw new Error('获取订单信息失败')
throw new Error(result.msg || '获取订单信息失败');
}
uni.hideLoading()
} catch (error) {
uni.hideLoading()
console.error('加载订单信息错误:', error);
uni.showToast({
title: error.message || '获取订单信息失败',
icon: 'none'
})
});
} finally {
uni.hideLoading();
}
},
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')
const second = date.getSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
// 申请退款
async handleWithdraw() {
try {
uni.showLoading({ title: '处理中' });
const res = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/withdraw/add/${this.orderInfo.orderNo}`,
method: 'GET',
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
})
if (res.statusCode === 200 && res.data.code === 200) {
uni.showToast({
title: '退款申请成功',
icon: 'success'
});
// 更新状态
this.orderInfo.withdrawStatus = 'processing';
this.orderInfo.isWithdrawn = true;
// 刷新订单信息
setTimeout(() => {
this.loadOrderInfo();
}, 1500);
} else {
throw new Error(res.data.msg || '退款申请失败');
}
} catch (error) {
console.error('退款申请错误:', error);
uni.showToast({
title: error.message || '退款申请失败',
icon: 'none'
});
} finally {
uni.hideLoading();
}
},
// 返回首页
goToHome() {
uni.switchTab({
uni.reLaunch({
url: '/pages/index/index'
})
},
goToOrderList() {
uni.redirectTo({
url: '/pages/order/index'
})
});
}
}
}
@@ -264,18 +380,23 @@ export default {
}
.button-group {
margin-top: 30px;
margin-top: 40rpx;
display: flex;
flex-direction: column;
gap: 16px;
justify-content: center;
gap: 20rpx;
.primary-btn, .secondary-btn {
width: 50%;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
text-align: center;
font-size: 32rpx;
}
.primary-btn {
background-color: #07c160;
background: #07c160;
color: #fff;
border: none;
border-radius: 24px;
padding: 12px;
font-size: 16px;
&:active {
opacity: 0.8;
@@ -283,16 +404,40 @@ export default {
}
.secondary-btn {
background-color: #fff;
color: #07c160;
border: 1px solid #07c160;
border-radius: 24px;
padding: 12px;
font-size: 16px;
background: #f0f0f0;
color: #333;
&:active {
background-color: #f5f5f5;
opacity: 0.8;
}
}
}
.notice-card {
background-color: #fff;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
.card-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
color: #333;
border-bottom: 1px solid #f0f0f0;
padding-bottom: 10px;
}
.notice-content {
text-align: left;
color: #666;
font-size: 14px;
}
}
.waiting {
color: #ffaa00;
font-weight: bold;
}
</style>
+4 -12
View File
@@ -45,7 +45,7 @@
</template>
<script>
import { queryById, confirmPaymentAndRent, sendRentCommand } from '@/config/user.js'
import { queryById, confirmPaymentAndRent } from '@/config/user.js'
export default {
data() {
@@ -138,17 +138,9 @@ export default {
try {
console.log(`准备触发弹出充电宝,orderId: ${this.orderId}`)
// 先尝试使用确认支付并弹出的方法
let result
try {
result = await confirmPaymentAndRent(this.orderId)
console.log('确认支付并弹出充电宝结果:', JSON.stringify(result))
} catch (error) {
console.error('确认支付并弹出失败,尝试备用方法:', error)
// 如果第一种方法失败,尝试使用备用方法直接发送租借指令
result = await sendRentCommand(this.orderId)
console.log('发送租借指令结果:', JSON.stringify(result))
}
// 用确认支付并弹出的方法
const result = await confirmPaymentAndRent(this.orderId)
console.log('确认支付并弹出充电宝结果:', JSON.stringify(result))
if (result && result.code === 200) {
this.deviceMessage = '设备已弹出,请取走您的充电宝'