Files
zyt/TUICallKit-Vue3/pages/order/order.vue
T
2026-03-16 15:08:28 +08:00

513 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>