513 lines
10 KiB
Vue
513 lines
10 KiB
Vue
<template>
|
||
<view class="container">
|
||
<!-- 加载中 -->
|
||
<view v-if="loading" class="loading-wrapper">
|
||
<view class="loading-spinner"></view>
|
||
<text class="loading-text">加载中...</text>
|
||
</view>
|
||
|
||
<!-- 支付成功 -->
|
||
<view v-else-if="paySuccess" class="success-container">
|
||
<view class="success-content">
|
||
<view class="success-icon-wrapper">
|
||
<view class="success-icon-circle">
|
||
<text class="success-icon-check">✓</text>
|
||
</view>
|
||
</view>
|
||
<view class="success-title">支付成功</view>
|
||
<view class="success-amount">¥ {{ orderInfo.amount }}</view>
|
||
<view class="success-message">订单号:{{ orderInfo.order_no }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 订单不存在 / 错误 -->
|
||
<view v-else-if="errorMsg" class="error-wrapper">
|
||
<view class="error-icon">!</view>
|
||
<text class="error-text">{{ errorMsg }}</text>
|
||
<button class="retry-btn" @click="loadOrderDetail">重试</button>
|
||
</view>
|
||
|
||
<!-- 订单详情 & 付款 -->
|
||
<view v-else-if="orderInfo" class="pay-wrapper">
|
||
<!-- 商户信息 -->
|
||
<view class="merchant-info">
|
||
<text class="merchant-title">付款给甄养堂医院</text>
|
||
</view>
|
||
|
||
<!-- 金额区域 -->
|
||
<view class="amount-section">
|
||
<text class="amount-label">付款金额</text>
|
||
<view class="amount-row">
|
||
<text class="amount-symbol">¥</text>
|
||
<text class="amount-value">{{ formatAmount(orderInfo.amount) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="divider"></view>
|
||
|
||
<!-- 订单信息 -->
|
||
<view class="order-info-section">
|
||
<view class="info-row">
|
||
<text class="info-label">订单号</text>
|
||
<text class="info-value">{{ orderInfo.order_no }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">订单类型</text>
|
||
<text class="info-value">{{ orderInfo.order_type_desc }}</text>
|
||
</view>
|
||
<view class="info-row" v-if="orderInfo.remark">
|
||
<text class="info-label">备注</text>
|
||
<text class="info-value">{{ orderInfo.remark }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="divider"></view>
|
||
|
||
<!-- 订单状态 -->
|
||
<view v-if="orderInfo.status == 2" class="paid-notice">
|
||
<view class="paid-icon">✓</view>
|
||
<text class="paid-text">该订单已支付</text>
|
||
</view>
|
||
|
||
<!-- 付款按钮 -->
|
||
<view v-else class="pay-btn-wrapper">
|
||
<button
|
||
class="pay-btn"
|
||
:disabled="paying"
|
||
:loading="paying"
|
||
@click="handlePay"
|
||
>
|
||
{{ paying ? '支付中...' : '付款' }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, getCurrentInstance } from 'vue'
|
||
|
||
const { proxy } = getCurrentInstance()
|
||
|
||
const loading = ref(true)
|
||
const paying = ref(false)
|
||
const paySuccess = ref(false)
|
||
const errorMsg = ref('')
|
||
const orderInfo = ref(null)
|
||
const orderNo = ref('')
|
||
|
||
onMounted(() => {
|
||
loadOrderDetail()
|
||
})
|
||
|
||
const loadOrderDetail = async () => {
|
||
loading.value = true
|
||
errorMsg.value = ''
|
||
|
||
const pages = getCurrentPages()
|
||
const currentPage = pages[pages.length - 1]
|
||
const params = proxy.$parsePageParams(currentPage.options || {})
|
||
orderNo.value = params.order_no || ''
|
||
|
||
if (!orderNo.value) {
|
||
loading.value = false
|
||
errorMsg.value = '缺少订单号'
|
||
return
|
||
}
|
||
|
||
try {
|
||
const res = await proxy.apiUrl({
|
||
url: '/api/tcm/getOrderByNo',
|
||
method: 'GET',
|
||
data: { order_no: orderNo.value }
|
||
}, false)
|
||
|
||
if (res.code === 1) {
|
||
orderInfo.value = res.data
|
||
} else {
|
||
errorMsg.value = res.msg || '订单加载失败'
|
||
}
|
||
} catch (err) {
|
||
errorMsg.value = '网络请求失败'
|
||
console.error('加载订单失败:', err)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const formatAmount = (amount) => {
|
||
const num = parseFloat(amount)
|
||
if (isNaN(num)) return '0.00'
|
||
return num.toFixed(2)
|
||
}
|
||
|
||
const ensureLogin = () => {
|
||
return new Promise((resolve, reject) => {
|
||
const token = uni.getStorageSync('token')
|
||
if (token) {
|
||
resolve(token)
|
||
return
|
||
}
|
||
uni.login({
|
||
provider: 'weixin',
|
||
success: async (loginRes) => {
|
||
try {
|
||
const res = await proxy.apiUrl({
|
||
url: '/api/login/mnpLogin',
|
||
method: 'POST',
|
||
data: { code: loginRes.code }
|
||
}, false)
|
||
if (res.code === 1 && res.data && res.data.token) {
|
||
uni.setStorageSync('token', res.data.token)
|
||
uni.setStorageSync('userData', res.data)
|
||
resolve(res.data.token)
|
||
} else {
|
||
reject(new Error(res.msg || '登录失败'))
|
||
}
|
||
} catch (e) {
|
||
reject(e)
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
reject(err)
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
const handlePay = async () => {
|
||
if (paying.value || !orderInfo.value) return
|
||
if (orderInfo.value.status == 2) {
|
||
uni.showToast({ title: '订单已支付', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
paying.value = true
|
||
|
||
try {
|
||
await ensureLogin()
|
||
|
||
const res = await proxy.apiUrl({
|
||
url: '/api/pay/prepay',
|
||
method: 'POST',
|
||
data: {
|
||
from: 'order',
|
||
order_id: orderInfo.value.id,
|
||
pay_way: 2,
|
||
redirect: '/pages/order/order'
|
||
}
|
||
}, false)
|
||
|
||
if (res.code !== 1 || !res.data) {
|
||
uni.showToast({ title: res.msg || '发起支付失败', icon: 'none' })
|
||
paying.value = false
|
||
return
|
||
}
|
||
|
||
const payParams = res.data
|
||
wx.requestPayment({
|
||
timeStamp: payParams.timeStamp,
|
||
nonceStr: payParams.nonceStr,
|
||
package: payParams.package,
|
||
signType: payParams.signType || 'RSA',
|
||
paySign: payParams.paySign,
|
||
success: () => {
|
||
paySuccess.value = true
|
||
paying.value = false
|
||
},
|
||
fail: (err) => {
|
||
paying.value = false
|
||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||
uni.showToast({ title: '已取消支付', icon: 'none' })
|
||
} else {
|
||
uni.showToast({ title: '支付失败,请重试', icon: 'none' })
|
||
}
|
||
console.error('支付失败:', err)
|
||
}
|
||
})
|
||
} catch (err) {
|
||
paying.value = false
|
||
uni.showToast({ title: '支付异常,请重试', icon: 'none' })
|
||
console.error('支付异常:', err)
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="less">
|
||
.container {
|
||
min-height: 100vh;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.loading-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
border: 6rpx solid #e0e0e0;
|
||
border-top-color: #1989fa;
|
||
border-radius: 50%;
|
||
animation: spin 0.8s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
.loading-text {
|
||
margin-top: 20rpx;
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
|
||
/* 错误页 */
|
||
.error-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
padding: 40rpx;
|
||
}
|
||
|
||
.error-icon {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
line-height: 120rpx;
|
||
text-align: center;
|
||
font-size: 70rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
background-color: #ff4d4f;
|
||
border-radius: 50%;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.error-text {
|
||
font-size: 32rpx;
|
||
color: #666;
|
||
margin-bottom: 40rpx;
|
||
}
|
||
|
||
.retry-btn {
|
||
width: 320rpx;
|
||
height: 88rpx;
|
||
line-height: 88rpx;
|
||
text-align: center;
|
||
background-color: #1989fa;
|
||
color: #fff;
|
||
font-size: 32rpx;
|
||
border-radius: 44rpx;
|
||
border: none;
|
||
}
|
||
|
||
/* 付款主体 */
|
||
.pay-wrapper {
|
||
padding: 40rpx;
|
||
}
|
||
|
||
.merchant-info {
|
||
padding: 30rpx 0;
|
||
}
|
||
|
||
.merchant-title {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
/* 金额区域 */
|
||
.amount-section {
|
||
padding: 40rpx 0 50rpx;
|
||
}
|
||
|
||
.amount-label {
|
||
font-size: 28rpx;
|
||
color: #888;
|
||
margin-bottom: 16rpx;
|
||
display: block;
|
||
}
|
||
|
||
.amount-row {
|
||
display: flex;
|
||
align-items: baseline;
|
||
margin-top: 16rpx;
|
||
}
|
||
|
||
.amount-symbol {
|
||
font-size: 44rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.amount-value {
|
||
font-size: 80rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
line-height: 1;
|
||
}
|
||
|
||
.divider {
|
||
height: 1rpx;
|
||
background-color: #e8e8e8;
|
||
margin: 10rpx 0 30rpx;
|
||
}
|
||
|
||
/* 订单信息 */
|
||
.order-info-section {
|
||
padding: 10rpx 0 20rpx;
|
||
}
|
||
|
||
.info-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 28rpx;
|
||
color: #888;
|
||
flex-shrink: 0;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
text-align: right;
|
||
word-break: break-all;
|
||
}
|
||
|
||
/* 已支付提示 */
|
||
.paid-notice {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40rpx 0;
|
||
}
|
||
|
||
.paid-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
line-height: 48rpx;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
color: #fff;
|
||
background-color: #52c41a;
|
||
border-radius: 50%;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.paid-text {
|
||
font-size: 32rpx;
|
||
color: #52c41a;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 付款按钮 */
|
||
.pay-btn-wrapper {
|
||
padding: 60rpx 40rpx 0;
|
||
}
|
||
|
||
.pay-btn {
|
||
width: 100%;
|
||
height: 100rpx;
|
||
line-height: 100rpx;
|
||
text-align: center;
|
||
background-color: #1989fa;
|
||
color: #fff;
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
border-radius: 16rpx;
|
||
border: none;
|
||
letter-spacing: 4rpx;
|
||
|
||
&[disabled] {
|
||
background-color: #a0cfff;
|
||
}
|
||
}
|
||
|
||
/* 支付成功页 */
|
||
.success-container {
|
||
min-height: 100vh;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(135deg, #1989fa 0%, #4da6ff 100%);
|
||
padding: 40rpx;
|
||
}
|
||
|
||
.success-content {
|
||
width: 100%;
|
||
max-width: 620rpx;
|
||
background: #fff;
|
||
border-radius: 32rpx;
|
||
padding: 90rpx 50rpx 70rpx;
|
||
text-align: center;
|
||
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15);
|
||
animation: fadeIn 0.5s ease-out;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; transform: translateY(-40rpx); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
.success-icon-wrapper {
|
||
margin-bottom: 48rpx;
|
||
}
|
||
|
||
.success-icon-circle {
|
||
width: 180rpx;
|
||
height: 180rpx;
|
||
margin: 0 auto;
|
||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 12rpx 40rpx rgba(82, 196, 26, 0.35);
|
||
animation: scaleIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||
}
|
||
|
||
@keyframes scaleIn {
|
||
0% { transform: scale(0); opacity: 0; }
|
||
50% { transform: scale(1.1); }
|
||
100% { transform: scale(1); opacity: 1; }
|
||
}
|
||
|
||
.success-icon-check {
|
||
font-size: 110rpx;
|
||
color: #fff;
|
||
font-weight: bold;
|
||
line-height: 1;
|
||
}
|
||
|
||
.success-title {
|
||
font-size: 52rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.success-amount {
|
||
font-size: 56rpx;
|
||
font-weight: bold;
|
||
color: #1989fa;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.success-message {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
</style>
|