新增h5qrcode依赖

This commit is contained in:
2026-02-05 17:38:19 +08:00
parent 5a13803743
commit f476cee76d
16 changed files with 2036 additions and 559 deletions
+201 -231
View File
@@ -1,19 +1,23 @@
<template>
<view class="payment-container">
<!-- 订单状态 -->
<view class="status-card">
<view class="status-icon-wrapper">
<view class="status-icon" :class="orderStatus.class">
<text class="icon-text">💳</text>
</view>
<!-- 地点信息卡片 -->
<view class="location-card">
<view class="location-header">
<!-- <view class="location-icon">📍</view> -->
<image src="@/static/device_location.png" mode="aspectFit" class="location-icon"></image>
<text class="location-name">{{ locationName }}</text>
<view class="status-badge">{{ $t('device.available') }}</view>
</view>
<view class="device-info">
<text class="device-label">{{ $t('order.deviceNo') }}</text>
<text class="device-value">{{ orderInfo.deviceNo || '-' }}</text>
</view>
<view class="status-text">{{ orderStatus.text }}</view>
<view class="status-desc">{{ orderStatus.desc }}</view>
</view>
<!-- 订单信息 -->
<!-- 订单信息和费用信息 -->
<view class="order-card">
<view class="card-header">
<view class="card-title-bar"></view>
<view class="card-title">{{ $t('payment.orderInfo') }}</view>
</view>
<view class="info-item">
@@ -28,52 +32,35 @@
<text class="label">{{ $t('payment.createTime') }}</text>
<text class="value">{{ orderInfo.createTime || '-' }}</text>
</view>
</view>
<!-- 费用信息 -->
<view class="price-card">
<view class="card-header">
<!-- 费用信息部分 -->
<view class="card-header" style="margin-top: 32rpx;">
<view class="card-title-bar"></view>
<view class="card-title">{{ $t('payment.feeInfo') }}</view>
</view>
<view class="price-item">
<text class="label">{{ $t('payment.deposit') }}</text>
<text class="value">{{ orderInfo.deposit || '99.00' }}</text>
<text class="value">¥ {{ orderInfo.deposit || '90' }}</text>
</view>
<!-- <view class="price-divider"></view> -->
<!-- <view class="price-item">
<text class="label">{{ $t('payment.package') }}</text>
<text class="value">{{ packageText }}</text>
</view> -->
<view class="price-item total">
<text class="label">{{ $t('payment.total') }}</text>
<text class="value">{{ totalAmount }}</text>
</view>
</view>
<!-- 支付方式选择 -->
<view class="payment-methods" v-if="paymentMethods.length > 0">
<view class="card-header">
<view class="card-title">支付方式</view>
</view>
<view class="method-item" v-for="method in paymentMethods" :key="method?.paymentMethodType"
:class="{ active: selectedPaymentMethod === method?.paymentMethodType }"
@click="selectPaymentMethod(method?.paymentMethodType)">
<view class="method-info">
<view class="method-icon">
<image :src="method?.logo?.logoUrl" mode="aspectFit" style="width: 48rpx; height: 48rpx;">
</image>
</view>
<text class="method-name">{{ method?.logo?.logoName }}</text>
</view>
<view class="method-radio" :class="{ checked: selectedPaymentMethod === method?.paymentMethodType }">
<view class="total-value">
<text class="currency">¥</text>
<text class="amount">{{ totalAmount }}</text>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="total-amount">
<text class="label-text">{{ $t('payment.total') }}</text>
<text class="amount">{{ totalAmount }}</text>
</view>
<view class="pay-btn" @click="handlePayment">
<text>{{ $t('payment.payNow') }}</text>
<text class="currency-small">¥</text>
<text class="amount-large">{{ totalAmount }}</text>
<text class="pay-text">{{ $t('payment.payNow') }}</text>
</view>
</view>
</view>
@@ -116,10 +103,23 @@
const passedTotalAmount = ref(null)
const passedDepositAmount = ref(null)
// 倒计时相关
const countdown = ref(15 * 60) // 15分钟 = 900秒
let countdownTimer = null
// 支付方式相关
const paymentMethods = ref([])
const selectedPaymentMethod = ref('ALIPAY') // 默认选择支付宝
// 地点名称(可以从设备信息中获取,这里先用默认值)
const locationName = ref('澎创办公室')
// 套餐文本(可以从设备信息中获取,这里先用默认值)
const packageText = computed(() => {
// 这里可以根据实际的设备信息动态生成
return '2元/小时'
})
const orderStatus = reactive({
get text() {
return t('payment.waitingForPayment')
@@ -191,11 +191,17 @@
deviceNo: orderData.deviceNo,
createTime: formattedTime,
deposit: passedDepositAmount.value || orderData.depositAmount || '99.00',
orderStatus:orderData.orderStatus
}
deviceNo.value = orderData.deviceNo;
await loadDeviceInfo();
await loadPaymentMethods();
// #ifdef H5
if(orderInfo.value.orderStatus=='waiting_for_payment'){
startPaymentStatusPolling();
}
// #endif
} else {
throw new Error(t('order.getOrderFailed'))
}
@@ -322,12 +328,12 @@
uni.hideLoading();
// #ifdef H5
uni.setStorageSync('pendingPaymentNo', orderId.value);
// #endif
// 跳转到支付页面
uni.navigateTo({
url: `/pages/webview/index?url=${encodeURIComponent(paymentUrl)}&title=支付`
});
// uni.navigateTo({
// url: `/pages/webview/index?url=${encodeURIComponent(paymentUrl)}&title=支付`
// });
window.open(paymentUrl);
// #endif
// 开始轮询支付状态
startPaymentStatusPolling();
@@ -409,12 +415,56 @@
}, 5000); // 每5秒查询一次
}
// 更新导航栏倒计时
const updateNavBarCountdown = () => {
const minutes = Math.floor(countdown.value / 60).toString().padStart(2, '0')
const seconds = (countdown.value % 60).toString().padStart(2, '0')
uni.setNavigationBarTitle({
title: `待支付 ${minutes}:${seconds}`
})
}
// 开始倒计时
const startCountdown = () => {
// 清除之前的定时器
if (countdownTimer) {
clearInterval(countdownTimer)
}
// 立即更新一次
updateNavBarCountdown()
// 每秒更新
countdownTimer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(countdownTimer)
uni.showToast({
title: t('order.paymentFailedRetry'),
icon: 'none'
})
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
})
}, 1500)
return
}
updateNavBarCountdown()
}, 1000)
}
// 页面卸载时清除定时器
onMounted(() => {
return () => {
if (pollingTimer) {
clearInterval(pollingTimer);
}
if (countdownTimer) {
clearInterval(countdownTimer);
}
};
});
@@ -430,9 +480,8 @@
}
onLoad((options) => {
uni.setNavigationBarTitle({
title: t('payment.orderPayment')
})
// 启动倒计时
startCountdown()
// 优先从 options 中获取 orderId
if (options && options.orderId) {
@@ -470,7 +519,7 @@
}
}
// #endif
loadOrderInfo()
// 调用 loadOrderInfo,内部会再次检查 orderId 是否存在
})
@@ -479,78 +528,81 @@
<style lang="scss" scoped>
.payment-container {
min-height: 100vh;
background: #f7f8fa;
padding: 30rpx;
padding-bottom: 180rpx;
background: #F5F5F5;
padding: 24rpx;
padding-bottom: 200rpx;
box-sizing: border-box;
.status-card {
.location-card {
background: #fff;
border-radius: 20rpx;
padding: 40rpx 30rpx;
margin-bottom: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
border-radius: 24rpx;
padding: 32rpx;
margin-bottom: 24rpx;
.status-icon-wrapper {
margin-bottom: 20rpx;
.location-header {
display: flex;
align-items: center;
margin-bottom: 24rpx;
.status-icon {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
.location-icon {
font-size: 40rpx;
width: 40rpx;
height: 40rpx;
margin-right: 12rpx;
}
.icon-text {
font-size: 60rpx;
}
.location-name {
font-size: 36rpx;
font-weight: 600;
color: #333;
flex: 1;
}
&.waiting {
background: #FFF9C4;
}
&.success {
background: #E8F5E9;
}
&.failed {
background: #FFEBEE;
}
.status-badge {
background: #D4F4DD;
color: #52C41A;
font-size: 24rpx;
padding: 8rpx 20rpx;
border-radius: 8rpx;
}
}
.status-text {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.status-desc {
.device-info {
font-size: 28rpx;
color: #999;
color: #666;
.device-label {
color: #999;
}
.device-value {
color: #333;
}
}
}
.order-card,
.price-card,
.payment-methods {
.order-card {
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
border-radius: 24rpx;
padding: 32rpx;
margin-bottom: 24rpx;
.card-header {
margin-bottom: 24rpx;
display: flex;
align-items: center;
margin-bottom: 32rpx;
.card-title-bar {
width: 8rpx;
height: 32rpx;
background: #52C41A;
border-radius: 4rpx;
margin-right: 12rpx;
}
.card-title {
font-size: 32rpx;
font-weight: bold;
font-weight: 600;
color: #333;
}
}
@@ -560,16 +612,11 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
padding: 24rpx 0;
.label {
font-size: 28rpx;
color: #999;
color: #666;
}
.value {
@@ -579,108 +626,34 @@
}
}
.price-divider {
height: 1rpx;
background: #f0f0f0;
margin: 10rpx 0;
}
.price-item.total {
margin-top: 10rpx;
padding-top: 20rpx;
border-top: none;
padding-top: 32rpx;
justify-content: flex-end !important;
// border-top: 1rpx solid #F0F0F0;
margin-top: 16rpx;
.label {
font-size: 28rpx;
color: #999;
color: #666;
margin-right: 10rpx;
}
.value {
font-size: 48rpx;
font-weight: bold;
color: #ff6b6b;
}
}
}
.payment-methods {
.method-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 20rpx;
margin-bottom: 16rpx;
background: #f7f8fa;
border-radius: 12rpx;
border: 2rpx solid transparent;
&:last-child {
margin-bottom: 0;
}
&.active {
background: #E8F5E9;
border-color: #07c160;
}
.method-info {
.total-value {
display: flex;
align-items: center;
align-items: baseline;
.method-icon {
width: 48rpx;
height: 48rpx;
margin-right: 16rpx;
border-radius: 8rpx;
&.alipay {
background: #1677FF;
}
&.wechat {
background: #07C160;
}
&.default {
background: #999;
}
.currency {
font-size: 22rpx;
color: #52C41A;
margin-right: 4rpx;
}
.method-name {
font-size: 30rpx;
color: #333;
font-weight: 500;
.amount {
font-size: 32rpx;
font-weight: 700;
color: #52C41A;
}
}
.method-radio {
width: 40rpx;
height: 40rpx;
border: 2rpx solid #ddd;
border-radius: 50%;
position: relative;
&.checked {
border-color: #07c160;
background: #07c160;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 16rpx;
height: 16rpx;
background: #fff;
border-radius: 50%;
}
}
}
&:active {
opacity: 0.8;
}
}
}
@@ -690,47 +663,44 @@
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);
z-index: 10;
gap: 20rpx;
.total-amount {
display: flex;
align-items: baseline;
.label-text {
font-size: 28rpx;
color: #666;
}
.amount {
font-size: 36rpx;
font-weight: bold;
color: #ff6b6b;
margin-left: 8rpx;
}
}
padding: 24rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
z-index: 100;
.pay-btn {
height: 88rpx;
width: 100%;
padding: 20rpx 0;
// height: 96rpx;
background: linear-gradient(135deg, #52C41A 0%, #73D13D 100%);
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
background: #07c160;
color: #fff;
font-size: 30rpx;
font-weight: 600;
padding: 0 60rpx;
border-radius: 44rpx;
border: none;
box-shadow: 0 8rpx 24rpx rgba(82, 196, 26, 0.3);
.currency-small {
font-size: 32rpx;
font-weight: 600;
margin-right: 4rpx;
// text-align: ;
}
.amount-large {
font-size: 52rpx;
font-weight: 700;
margin-right: 16rpx;
}
.pay-text {
font-size: 32rpx;
font-weight: 600;
}
&:active {
opacity: 0.8;
opacity: 0.9;
transform: scale(0.98);
}
}
}
+145 -96
View File
@@ -1,30 +1,36 @@
<template>
<view class="success-container">
<!-- 支付成功状态 -->
<view class="status-card">
<view class="status-icon success"></view>
<view class="status-text">{{ $t('success.paymentSuccess') }}</view>
<view class="status-desc">{{ $t('success.paymentSuccessDesc') }}</view>
</view>
<!-- 支付成功状态和订单信息 -->
<view class="status-order-card">
<!-- 支付成功状态 -->
<view class="status-section">
<view class="status-icon success"></view>
<view class="status-text">{{ $t('success.paymentSuccess') }}</view>
<view class="status-desc">{{ $t('success.paymentSuccessDesc') }}</view>
</view>
<!-- 订单信息 -->
<view class="order-card">
<view class="card-title">{{ $t('success.orderInfo') }}</view>
<view class="info-item">
<text class="label">{{ $t('order.orderNo') }}</text>
<text class="value">{{ orderInfo.orderNo || '-' }}</text>
</view>
<view class="info-item">
<text class="label">{{ $t('order.deviceNo') }}</text>
<text class="value">{{ orderInfo.deviceNo || '-' }}</text>
</view>
<view class="info-item">
<text class="label">{{ $t('success.paymentAmount') }}</text>
<text class="value">{{ orderInfo.amount || '0.00' }}</text>
</view>
<view class="info-item">
<text class="label">{{ $t('success.paymentTime') }}</text>
<text class="value">{{ orderInfo.payTime || '-' }}</text>
<!-- 分割线 -->
<view class="section-divider"></view>
<!-- 订单信息 -->
<view class="order-section">
<view class="card-title">{{ $t('success.orderInfo') }}</view>
<view class="info-item">
<text class="label">{{ $t('order.orderNo') }}</text>
<text class="value">{{ orderInfo.orderNo || '-' }}</text>
</view>
<view class="info-item">
<text class="label">{{ $t('order.deviceNo') }}</text>
<text class="value">{{ orderInfo.deviceNo || '-' }}</text>
</view>
<view class="info-item">
<text class="label">{{ $t('success.paymentAmount') }}</text>
<text class="value">{{ orderInfo.amount || '0.00' }}</text>
</view>
<view class="info-item">
<text class="label">{{ $t('success.paymentTime') }}</text>
<text class="value">{{ orderInfo.payTime || '-' }}</text>
</view>
</view>
</view>
@@ -38,8 +44,8 @@
<!-- 操作按钮 -->
<view class="button-group">
<button class="primary-btn" @click="goToHome">{{ $t('success.backToHome') }}</button>
<button class="secondary-btn" @click="goToOrderList">{{ $t('success.viewOrder') }}</button>
<view class="secondary-btn" @click="goToHome">{{ $t('success.backToHome') }}</view>
<view class="primary-btn" @click="goToOrderList">{{ $t('success.viewOrder') }}</view>
</view>
</view>
</template>
@@ -196,81 +202,109 @@
<style lang="scss" scoped>
.success-container {
padding: 20px;
padding-bottom: 180rpx;
background-color: #f5f5f5;
min-height: 100vh;
box-sizing: border-box;
}
.status-card {
.status-order-card {
background-color: #fff;
border-radius: 12px;
padding: 30px;
text-align: center;
margin-bottom: 20px;
overflow: hidden;
.status-icon {
width: 60px;
height: 60px;
margin: 0 auto 16px;
background-color: #07c160;
border-radius: 50%;
position: relative;
.status-section {
padding: 30px;
text-align: center;
&::after {
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 30px;
height: 20px;
border: 3px solid #fff;
border-top: none;
border-right: none;
transform-origin: center;
transform: translate(-50%, -70%) rotate(-45deg);
.status-icon {
width: 60px;
height: 60px;
margin: 0 auto 16px;
background-color: #07c160;
border-radius: 50%;
position: relative;
&::after {
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 30px;
height: 20px;
border: 3px solid #fff;
border-top: none;
border-right: none;
transform-origin: center;
transform: translate(-50%, -70%) rotate(-45deg);
}
}
}
.status-text {
font-size: 24px;
font-weight: bold;
color: #07c160;
margin-bottom: 8px;
}
.status-text {
font-size: 24px;
font-weight: bold;
color: #07c160;
margin-bottom: 8px;
}
.status-desc {
font-size: 14px;
color: #666;
}
}
.order-card {
background-color: #fff;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
.card-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
color: #333;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
.label {
.status-desc {
font-size: 14px;
color: #666;
font-size: 14px;
}
}
.section-divider {
height: 1px;
background-color: #f0f0f0;
margin: 0 20px;
}
.order-section {
padding: 20px;
.card-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
color: #333;
padding-left: 12px;
position: relative;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background-color: #07c160;
border-radius: 2px;
}
}
.value {
color: #333;
font-size: 14px;
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
.label {
color: #666;
font-size: 14px;
}
.value {
color: #333;
font-size: 14px;
font-weight: 500;
}
}
}
}
@@ -316,18 +350,30 @@
}
.button-group {
margin-top: 30px;
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
background: #fff;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
z-index: 10;
display: flex;
// flex-direction: column;
gap: 16px;
justify-content: flex-end;
align-items: center;
gap: 20rpx;
.primary-btn {
background-color: #07c160;
color: #fff;
border: none;
border-radius: 24px;
padding: 12px;
font-size: 16px;
border-radius: 32rpx;
padding: 0 32rpx;
font-size: 24rpx;
height: 64rpx;
line-height: 64rpx;
white-space: nowrap;
&:active {
opacity: 0.8;
@@ -337,10 +383,13 @@
.secondary-btn {
background-color: #fff;
color: #07c160;
border: 1px solid #07c160;
border-radius: 24px;
padding: 12px;
font-size: 16px;
border: 2rpx solid #07c160;
border-radius: 32rpx;
padding: 0 32rpx;
font-size: 24rpx;
height: 64rpx;
line-height: 64rpx;
white-space: nowrap;
&:active {
background-color: #f5f5f5;