<template>
|
<view class="page" :class="isCompleted ? 'paid-page' : 'pending-page'">
|
<view class="navigation-bar">
|
<view class="back-button" @tap.stop="goBack"><image class="back-icon" src="/static/icons/lucide/back-left.svg" mode="aspectFit"></image></view>
|
</view>
|
|
<scroll-view class="detail-scroll" scroll-y="true" :show-scrollbar="false">
|
<view class="status-panel">
|
<view class="status-copy"><text class="status-title" :class="isCompleted ? 'paid-title' : ''">{{statusTitle}}</text><text class="status-desc">{{statusDesc}}</text></view>
|
<view class="status-icon-wrap" :class="isCompleted ? 'paid-icon-wrap' : ''"><image class="status-icon" :src="statusIcon" mode="aspectFit"></image></view>
|
</view>
|
<text class="section-title">订单信息</text>
|
<view class="info-card">
|
<view class="info-row"><text class="info-label">订单号</text><text class="info-value">{{orderNo}}</text></view>
|
<view class="info-row"><text class="info-label">商品金额</text><text class="info-value">{{productAmount}}</text></view>
|
<view class="info-row"><text class="info-label">人才折扣</text><text class="info-value">{{talentDiscount}}</text></view>
|
<view class="info-row"><text class="info-label">商品优惠</text><text class="info-value">{{productDiscount}}</text></view>
|
<view class="info-row"><text class="info-label">实际支付</text><text class="info-value pay-value">{{paidAmount}}</text></view>
|
<view class="info-row"><text class="info-label">补贴金额</text><text class="info-value">{{subsidyAmount}}</text></view>
|
<view class="info-row"><text class="info-label">下单时间</text><text class="info-value time-value">{{createdAt}}</text></view>
|
<view class="info-row last-row"><text class="info-label">核销时间</text><text class="info-value" :class="isCompleted ? 'time-value' : 'muted-value'">{{verificationAt}}</text></view>
|
</view>
|
|
<text class="section-title benefit-section-title">权益信息</text>
|
<view class="benefit-card">
|
<view class="benefit-glow"></view>
|
<view class="benefit-head"><text class="customer-name">{{customerName}}</text><text class="talent-tag">{{cardLevel}}</text></view>
|
<text class="customer-phone">{{customerPhone}}</text>
|
<view class="benefit-line"><text class="benefit-label">当前权益</text><text class="benefit-content">{{benefitName}}</text></view>
|
</view>
|
<view class="bottom-space"></view>
|
</scroll-view>
|
<view v-if="isCompleted && !isStoreCompleted" class="bottom-actions"><view class="cancel-verification-button" :class="revokeSubmitting ? 'cancel-verification-submitting' : (!canRevokeVerification ? 'cancel-verification-disabled' : '')" @tap="handleRevokeVerification"><text>{{revokeSubmitting ? '撤销中…' : '撤销核销'}}</text></view></view>
|
</view>
|
</template>
|
|
<script setup lang="uts">
|
import { revokeOrderVerification } from '../../services/orders.uts'
|
import { loadMerchantOrderDetail, MerchantOrderDetail } from '../../services/orders.uts'
|
import { formatErrorMessage } from '../../services/diagnostics.uts'
|
|
const orderId = ref('')
|
const isCompleted = ref(false)
|
const isStoreCompleted = ref(false)
|
const canRevokeVerification = ref(false)
|
const revokeExpiredMessage = ref('已过可撤销时间')
|
const revokeSubmitting = ref(false)
|
const statusTitle = ref('订单加载中')
|
const statusDesc = ref('正在获取订单详情。')
|
const statusIcon = ref('/static/icons/lucide/clock-3.svg')
|
const orderNo = ref('')
|
const productAmount = ref('')
|
const talentDiscount = ref('')
|
const productDiscount = ref('')
|
const paidAmount = ref('')
|
const subsidyAmount = ref('')
|
const createdAt = ref('')
|
const verificationAt = ref('')
|
const customerName = ref('')
|
const customerPhone = ref('')
|
const cardLevel = ref('人才卡')
|
const benefitName = ref('以当前订单权益规则为准')
|
const formatCents = (value: number): string => '¥' + (value / 100).toFixed(2)
|
|
const applyRemoteOrder = (detail: MerchantOrderDetail) => {
|
orderNo.value = detail.orderNo
|
productAmount.value = formatCents(detail.orderAmount)
|
talentDiscount.value = '-¥' + (detail.talentDiscountAmount / 100).toFixed(2)
|
productDiscount.value = '-¥' + (detail.couponAmount / 100).toFixed(2)
|
paidAmount.value = formatCents(detail.payAmount)
|
subsidyAmount.value = formatCents(detail.subsidyAmount)
|
createdAt.value = detail.createdAt.replace('T', ' ')
|
verificationAt.value = detail.verifyTime.length > 0 ? detail.verifyTime.replace('T', ' ') : '待核销'
|
customerName.value = detail.customerName
|
customerPhone.value = detail.customerMobile
|
cardLevel.value = detail.cardLevel.length > 0 ? detail.cardLevel : '人才卡'
|
benefitName.value = detail.benefitName.length > 0 ? detail.benefitName : '以当前订单权益规则为准'
|
isCompleted.value = detail.status == 'completed'
|
isStoreCompleted.value = detail.category == 'store' && detail.status == 'completed'
|
canRevokeVerification.value = detail.canRevoke
|
revokeExpiredMessage.value = detail.revokeReason.length > 0 ? detail.revokeReason : '已过可撤销时间'
|
statusTitle.value = isCompleted.value ? '已完成' : '待核销'
|
statusDesc.value = isCompleted.value ? '订单已完成核销,相关权益已结算。' : '订单已完成支付,请确认后即可核销。'
|
statusIcon.value = isCompleted.value ? '/static/icons/lucide/badge-check.svg' : '/static/icons/lucide/clock-3.svg'
|
}
|
|
onLoad((options: any) => {
|
orderId.value = options.id != null ? options.id as string : ''
|
orderNo.value = options.orderNo != null ? options.orderNo as string : orderNo.value
|
if (orderNo.value.length == 0) {
|
uni.showToast({ title: '订单参数缺失', icon: 'none' })
|
return
|
}
|
loadMerchantOrderDetail(orderNo.value, (detail: MerchantOrderDetail, _traceId: string) => applyRemoteOrder(detail), (error: any) => {
|
statusTitle.value = '订单加载失败'
|
statusDesc.value = error.message != null ? error.message : '请返回订单列表后重试。'
|
uni.showToast({ title: error.message != null ? error.message : '订单详情加载失败', icon: 'none' })
|
})
|
})
|
|
const goBack = () => {
|
uni.navigateBack()
|
}
|
|
const showRevokeExpired = () => {
|
uni.showModal({
|
title: '无法撤销',
|
content: revokeExpiredMessage.value,
|
showCancel: false,
|
confirmText: '我知道了',
|
confirmColor: '#FF6B2C'
|
})
|
}
|
|
const submitRevokeVerification = () => {
|
if (revokeSubmitting.value) return
|
revokeSubmitting.value = true
|
const idempotencyKey = 'revoke-verification-' + orderId.value + '-' + Date.now()
|
revokeOrderVerification(orderId.value, idempotencyKey, (result: any, traceId: string) => {
|
revokeSubmitting.value = false
|
isCompleted.value = false
|
canRevokeVerification.value = false
|
statusTitle.value = result.orderStatusText != null && result.orderStatusText.length > 0 ? result.orderStatusText as string : '待核销'
|
statusDesc.value = '核销已撤销,订单已退回待核销状态。'
|
statusIcon.value = '/static/icons/lucide/clock-3.svg'
|
verificationAt.value = '待核销'
|
uni.showToast({ title: '撤销成功', icon: 'success' })
|
}, (error: any) => {
|
revokeSubmitting.value = false
|
if (error.code == 'REVOCATION_PERIOD_EXPIRED' || error.code == 'SETTLEMENT_PERIOD_EXPIRED') {
|
canRevokeVerification.value = false
|
revokeExpiredMessage.value = '已过可撤销时间'
|
showRevokeExpired()
|
return
|
}
|
let message = formatErrorMessage(error, '撤销失败,请稍后重试')
|
if (error.code == 'API_NOT_CONFIGURED') message = '撤销核销接口尚未接入'
|
uni.showModal({
|
title: '撤销失败',
|
content: message,
|
showCancel: false,
|
confirmText: '我知道了',
|
confirmColor: '#FF6B2C'
|
})
|
})
|
}
|
|
const handleRevokeVerification = () => {
|
if (revokeSubmitting.value) return
|
if (!canRevokeVerification.value) {
|
return
|
}
|
uni.showModal({
|
title: '撤销核销',
|
content: '确认是否撤销核销?撤销后订单将退回待核销状态。',
|
cancelText: '暂不撤销',
|
confirmText: '确认撤销',
|
confirmColor: '#FF6B2C',
|
success: (result: any) => {
|
if (result.confirm) submitRevokeVerification()
|
}
|
})
|
}
|
|
</script>
|
|
<style>
|
page { background-color: #F6F5F2; }
|
.page { display: flex; flex: 1; flex-direction: column; width: 100%; height: 100vh; overflow: hidden; }
|
.pending-page { background-image: linear-gradient(180deg, #FFE3C4 0%, #FFF5EA 390rpx, #F6F5F2 760rpx); }
|
.paid-page { background-image: linear-gradient(180deg, #E8EDF4 0%, #F3F5F8 390rpx, #F6F5F2 760rpx); }
|
.navigation-bar { display: flex; height: 174rpx; flex: none; position: relative; box-sizing: border-box; }
|
.back-button { display: flex; width: 72rpx; height: 72rpx; justify-content: center; align-items: center; position: absolute; top: 82rpx; left: 28rpx; z-index: 2; }
|
.back-icon { width: 40rpx; height: 40rpx; }
|
.detail-scroll { flex: 1; height: 0; padding: 10rpx 34rpx 0; box-sizing: border-box; }
|
.status-panel { display: flex; min-height: 126rpx; flex-direction: row; justify-content: space-between; align-items: flex-start; }
|
.status-copy { display: flex; flex: 1; flex-direction: column; }
|
.status-title { color: #FF633A; font-size: 50rpx; line-height: 70rpx; font-weight: 700; }
|
.paid-title { color: #677386; }
|
.status-desc { margin-top: 11rpx; color: #97A0AE; font-size: 25rpx; line-height: 36rpx; }
|
.status-icon-wrap { display: flex; width: 60rpx; height: 60rpx; margin-top: 10rpx; justify-content: center; align-items: center; border-radius: 30rpx; background-color: #FFE9D8; box-shadow: 0 10rpx 22rpx rgba(226, 119, 59, 0.10); }
|
.paid-icon-wrap { background-color: #E8EDF3; box-shadow: 0 10rpx 22rpx rgba(89, 105, 126, 0.09); }
|
.status-icon { width: 32rpx; height: 32rpx; }
|
.section-title { display: block; margin-top: 12rpx; color: #293244; font-size: 31rpx; line-height: 44rpx; font-weight: 700; }
|
.info-card { padding: 20rpx 30rpx; margin-top: 22rpx; border-radius: 32rpx; background-color: #FFFFFF; box-shadow: 0 16rpx 38rpx rgba(69, 65, 57, 0.06); box-sizing: border-box; }
|
.info-row { display: flex; min-height: 57rpx; flex-direction: row; justify-content: space-between; align-items: center; }
|
.info-label { color: #A1A9B6; font-size: 27rpx; line-height: 38rpx; }
|
.info-value { color: #414B5E; font-size: 27rpx; line-height: 38rpx; font-weight: 600; text-align: right; }
|
.pay-value { color: #FF6439; }
|
.time-value { font-size: 25rpx; }
|
.muted-value { color: #9AA4B3; font-weight: 500; }
|
.last-row { min-height: 60rpx; }
|
.benefit-section-title { margin-top: 32rpx; }
|
.benefit-card { min-height: 238rpx; padding: 34rpx 34rpx 28rpx; margin-top: 22rpx; border-width: 1rpx; border-style: solid; border-color: #E4BE59; border-radius: 38rpx; background-image: linear-gradient(135deg, #C99D32 0%, #F7E69D 55%, #E5BD4E 100%); box-shadow: 0 18rpx 38rpx rgba(188, 139, 37, 0.20); box-sizing: border-box; position: relative; overflow: hidden; }
|
.benefit-glow { width: 200rpx; height: 200rpx; border-radius: 100rpx; position: absolute; top: -75rpx; right: -18rpx; background-color: rgba(255, 255, 255, 0.18); }
|
.benefit-head { display: flex; flex-direction: row; justify-content: space-between; align-items: center; position: relative; }
|
.customer-name { color: #FFFFFF; font-size: 39rpx; line-height: 55rpx; font-weight: 700; }
|
.talent-tag { padding: 9rpx 20rpx; border-radius: 24rpx; color: #FFFFFF; font-size: 22rpx; line-height: 31rpx; font-weight: 600; background-color: rgba(149, 104, 22, 0.46); }
|
.customer-phone { display: block; margin-top: 15rpx; color: #FFFFFF; font-size: 29rpx; line-height: 41rpx; font-weight: 600; position: relative; }
|
.benefit-line { display: flex; margin-top: 22rpx; flex-direction: row; align-items: center; position: relative; }
|
.benefit-label { color: rgba(255, 255, 255, 0.78); font-size: 23rpx; line-height: 33rpx; }
|
.benefit-content { margin-left: 18rpx; color: #FFFFFF; font-size: 23rpx; line-height: 33rpx; font-weight: 600; }
|
.bottom-space { height: 70rpx; }
|
.bottom-actions { display: flex; height: 154rpx; padding: 20rpx 96rpx 34rpx; flex: none; background-color: #F6F5F2; box-sizing: border-box; }
|
.cancel-verification-button { display: flex; flex: 1; height: 94rpx; justify-content: center; align-items: center; border-radius: 48rpx; color: #FFFFFF; font-size: 29rpx; line-height: 41rpx; font-weight: 600; background-image: linear-gradient(90deg, #FF7625 0%, #FF9630 100%); box-shadow: 0 12rpx 24rpx rgba(255, 107, 36, 0.20); }
|
.cancel-verification-submitting { opacity: 0.62; box-shadow: none; }
|
.cancel-verification-disabled { background-image: none; background-color: #C8CDD5; box-shadow: none; }
|
</style>
|