shinnosuke
5 天以前 e52934f6527027bd6008c2ade61c3083159efd4a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
<template>
    <view class="page">
        <view class="page-header">
            <view class="safe-space" :style="'height:' + navTop + 'px'"></view>
            <view class="nav-row" :style="'height:' + navHeight + 'px;padding-right:' + navRight + 'px'">
                <view class="back-button" hover-class="back-button-active" @tap="goBack"><image src="/static/news-assets/arrow-left.svg" mode="aspectFit"></image></view>
                <text class="nav-title">待使用</text>
                <view class="capsule-space" :style="'width:' + navWidth + 'px'"></view>
            </view>
        </view>
 
        <scroll-view class="content" scroll-y="true" show-scrollbar="false">
            <view class="content-inner">
                <view class="qr-card">
                    <image v-if="codeImage != ''" class="code-image" :src="codeImage" mode="aspectFit" @error="handleCodeImageError"></image><text v-else class="dynamic-code-placeholder" @tap="refreshCode">{{codeError ? '核销码加载失败,点击重试' : '正在生成核销码'}}</text>
                    <text class="qr-tip" @tap="refreshCode">{{codeText}}</text>
                </view>
 
                <view class="product-card">
                    <image class="product-image" :src="productCoverUrl" mode="aspectFill" @error="handleCoverImageError"></image>
                    <view class="product-info"><text class="product-name">{{productName}}</text><text class="product-original">¥ {{originalAmount}}</text></view>
                    <view class="product-payment"><text class="payment-label">实付</text><text class="payment-price">¥ {{payAmount}}</text></view>
                </view>
 
                <view class="merchant-card">
                    <view class="merchant-info"><text class="merchant-name">{{merchantName}}</text><text class="merchant-address">{{merchantAddress}}</text></view>
                    <view class="merchant-actions">
                        <view class="merchant-action" hover-class="action-active" @tap="openNavigation"><image src="/static/merchant-assets/navigation.svg" mode="aspectFit"></image><text>导航</text></view>
                        <view class="merchant-action" hover-class="action-active" @tap="callMerchant"><image src="/static/merchant-assets/phone.svg" mode="aspectFit"></image><text>电话</text></view>
                    </view>
                </view>
 
                <view class="order-card">
                    <text class="order-title">订单信息</text>
                    <view class="order-row"><text>订单号:</text><text>{{orderId}}</text></view><view class="order-row"><text>商品金额:</text><text>{{originalAmount}}元</text></view><view class="order-row"><text>优惠金额:</text><text>-{{discountAmount}}元</text></view><view class="order-row"><text>实际支付:</text><text>{{payAmount}}元</text></view><view class="order-row"><text>下单时间:</text><text>{{createdAt}}</text></view><view class="order-row"><text>核销时间:</text><text>{{verifyTime}}</text></view>
                </view>
                <view class="content-bottom"></view>
            </view>
        </scroll-view>
 
        <view v-if="canRefund" class="refund-footer">
            <view class="refund-button" hover-class="refund-button-active" @tap="requestRefund"><text>申请退款</text></view>
        </view>
    </view>
</template>
 
<script setup lang="uts">
    import { apiEnvironment } from '../../config/api.uts'
    import { resolveApiUrl } from '../../services/http.uts'
    import { getPendingUseOrder, issueOrderVerificationCode, refundPendingUseOrder } from '../../services/portal.uts'
    import { safeBack } from '../../utils/navigation.uts'
 
    const navTop = ref(28)
    const navHeight = ref(32)
    const navWidth = ref(88)
    const navRight = ref(10)
    const isSubmittingRefund = ref(false)
    const productCoverUrl = ref('/static/common/image-placeholder.png')
    const recordId = ref('')
    const orderId = ref(''); const talentId = ref(''); const codeImage = ref(''); const codeText = ref('正在生成动态核销码'); const codeError = ref(false); const productName = ref('人才权益服务'); const originalAmount = ref('0.00'); const payAmount = ref('0.00'); const discountAmount = ref('0.00'); const merchantName = ref(''); const merchantAddress = ref(''); const merchantMobile = ref(''); const merchantLatitude = ref(0.0); const merchantLongitude = ref(0.0); const createdAt = ref(''); const verifyTime = ref('-'); const canRefund = ref(false); let refreshTimer = 0
 
    onLoad((options: any) => {
        // #ifdef MP-WEIXIN
        const capsule = uni.getMenuButtonBoundingClientRect()
        const systemInfo = uni.getSystemInfoSync()
        navTop.value = capsule.top
        navHeight.value = capsule.height
        navWidth.value = capsule.width
        navRight.value = systemInfo.windowWidth - capsule.right
        // #endif
        if (options != null && options['recordId'] != null) recordId.value = decodeURIComponent(options['recordId'] as string)
        if (options != null && options['orderId'] != null) orderId.value = options['orderId'] as string
        talentId.value = options != null && options['talentId'] != null ? options['talentId'] as string : (apiEnvironment == 'development' ? '2' : '')
        loadOrder()
    })
    onShow(() => { if (orderId.value != '') { loadOrder(); refreshCode() } })
    onHide(() => stopRefresh())
    onUnload(() => stopRefresh())
 
    const goBack = () => safeBack('/pages/order/order')
    const loadOrder = () => getPendingUseOrder(orderId.value, talentId.value, (data:any) => {
        // 已退款(含退款中)或已取消的订单不再是「待使用」:这一页不发核销码,直接进订单状态页看退款结果。
        if (Number(data.orderStatus) == 2 || data.refundStatus != null) {
            uni.redirectTo({ url: '/pages/order-cancelled/order-cancelled?recordId=' + encodeURIComponent(recordId.value) + '&orderId=' + encodeURIComponent(orderId.value) + '&talentId=' + encodeURIComponent(talentId.value) })
            return
        }
        productName.value=data.productNameSnapshot||productName.value; productCoverUrl.value=resolveApiUrl(data.productCoverUrlSnapshot||'/static/common/image-placeholder.png'); originalAmount.value=Number(data.originalAmount).toFixed(2); payAmount.value=Number(data.payAmount).toFixed(2); discountAmount.value=Number(data.discountAmount).toFixed(2); merchantName.value=data.merchantNameSnapshot||''; merchantAddress.value=data.merchantAddressSnapshot||''; merchantMobile.value=data.merchantMobileSnapshot||''; merchantLatitude.value=Number(data.merchantLatitudeSnapshot); merchantLongitude.value=Number(data.merchantLongitudeSnapshot); createdAt.value=(''+data.createdAt).replace('T',' ').substring(0,19); verifyTime.value=data.verifyTime?(''+data.verifyTime).replace('T',' ').substring(0,19):'-'; canRefund.value=data.canRefund===true }, () => {})
    const stopRefresh=()=>{if(refreshTimer!=0){clearTimeout(refreshTimer);refreshTimer=0}}
    const refreshCode=()=>{stopRefresh(); if(orderId.value=='')return; codeError.value=false; issueOrderVerificationCode(orderId.value,talentId.value,(data:any)=>{codeImage.value=resolveApiUrl(data.imagePath as string);codeText.value='核销码 '+(data.displayCode as string)+',请向商家出示';refreshTimer=setTimeout(()=>refreshCode(),Math.max(1,Number(data.refreshAfterSeconds)-3)*1000)},()=>{codeImage.value='';codeError.value=true;codeText.value='点击重新生成核销码'})}
    // @error 必须收回图片地址:二维码取不回来时页面只会剩一片空白,用户分不清是没生成、没网还是码失效。
    // 这是 7.22 遗留问题 P1-006(远程图片失败会留白)的同一类缺陷,人才码和免费券码页都已按此处理。
    const handleCodeImageError=()=>{codeImage.value='';codeError.value=true;codeText.value='核销码加载失败,点击重新生成'}
    const handleCoverImageError=()=>{productCoverUrl.value='/static/common/image-placeholder.png'}
    const openNavigation = () => { if(merchantLatitude.value==0||merchantLongitude.value==0){uni.showToast({title:'商户暂未配置导航地址',icon:'none'});return};uni.openLocation({latitude:merchantLatitude.value,longitude:merchantLongitude.value,name:merchantName.value,address:merchantAddress.value,scale:16}) }
    const callMerchant = () => { if(merchantMobile.value==''){uni.showToast({title:'商户暂未配置联系电话',icon:'none'});return};uni.makePhoneCall({phoneNumber:merchantMobile.value}) }
    const requestRefund = () => {
        if (isSubmittingRefund.value) return
        uni.showModal({
            title: '申请退款',
            content: '确认申请退款吗?提交后将由后台审核处理。',
            confirmText: '确认申请',
            confirmColor: '#FF6330',
            success: (result) => {
                if (!result.confirm) return
                if (isSubmittingRefund.value) return
                isSubmittingRefund.value = true
                refundPendingUseOrder(orderId.value,talentId.value,'用户申请退款',()=>{isSubmittingRefund.value=false;canRefund.value=false;uni.showToast({title:'退款申请已提交',icon:'success'});loadOrder()},(error)=>{isSubmittingRefund.value=false;uni.showToast({title:error.message,icon:'none'})})
            }
        })
    }
</script>
 
<style>
    .page { display: flex; width: 100%; height: 100vh; background-color: #F5F7FA; background-image: linear-gradient(180deg, #FFF0D7 0%, #FFF2DE 250rpx, #F5F7FA 520rpx); flex-direction: column; }
    .page-header { position: relative; z-index: 1; flex-shrink: 0; padding-bottom: 55rpx; }
    .safe-space { height: 28px; }
    .nav-row { display: flex; height: 32px; box-sizing: border-box; padding-left: 20rpx; padding-right: 10px; position: relative; flex-direction: row; align-items: center; justify-content: space-between; }
    .back-button { display: flex; width: 70rpx; height: 64rpx; margin-left: -4rpx; justify-content: center; align-items: center; }
    .back-button image { width: 36rpx; height: 36rpx; }
    .back-button-active { opacity: .55; }
    .nav-title { position: absolute; left: 50%; top: 50%; color: #171B22; font-size: 37rpx; line-height: 48rpx; font-weight: 700; transform: translate(-50%, -50%); white-space: nowrap; }
    .capsule-space { width: 88px; height: 100%; flex-shrink: 0; }
    .content { min-height: 0; z-index: 2; flex: 1; background-color: transparent; }
    .content-inner { position: relative; z-index: 2; padding: 0 28rpx; }
    .qr-card { display: flex; height: 348rpx; margin-top: -3rpx; border-radius: 26rpx; background-color: #FFFFFF; flex-direction: column; justify-content: center; align-items: center; box-shadow: 0 8rpx 24rpx rgba(69,79,92,.025); }
    .code-image { width: 232rpx; height: 232rpx; }
    .dynamic-code-placeholder { max-width: 360rpx; color: #606B78; font-size: 28rpx; line-height: 40rpx; font-weight: 600; text-align: center; }
    .qr-tip { margin-top: 5rpx; color: #B0B6BF; font-size: 20rpx; line-height: 28rpx; }
    .product-card { display: flex; height: 158rpx; margin-top: 27rpx; box-sizing: border-box; padding: 27rpx 28rpx 27rpx 17rpx; border-radius: 25rpx; background-color: #FFFFFF; flex-direction: row; align-items: center; }
    .product-image { width: 146rpx; height: 106rpx; flex-shrink: 0; border-radius: 13rpx; background-color: #E7EBEF; }
    .product-info { display: flex; min-width: 0; flex: 1; height: 103rpx; margin-left: 23rpx; flex-direction: column; justify-content: space-between; }
    .product-name { color: #29303A; font-size: 28rpx; line-height: 39rpx; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .product-original { color: #A7AEB8; font-size: 22rpx; line-height: 31rpx; }
    .product-payment { display: flex; height: 103rpx; margin-left: 14rpx; flex-shrink: 0; flex-direction: column; align-items: flex-end; justify-content: space-between; }
    .payment-label { color: #79818D; font-size: 21rpx; line-height: 30rpx; }
    .payment-price { color: #FF542F; font-size: 31rpx; line-height: 39rpx; font-weight: 700; }
    .merchant-card { display: flex; min-height: 174rpx; margin-top: 27rpx; box-sizing: border-box; padding: 31rpx 27rpx; border-radius: 25rpx; background-color: #FFFFFF; flex-direction: row; align-items: center; }
    .merchant-info { min-width: 0; flex: 1; }
    .merchant-name { display: block; color: #29303A; font-size: 29rpx; line-height: 41rpx; font-weight: 700; }
    .merchant-address { display: block; margin-top: 15rpx; color: #A0A8B3; font-size: 21rpx; line-height: 30rpx; }
    .merchant-actions { display: flex; width: 137rpx; margin-left: 20rpx; flex-shrink: 0; flex-direction: row; justify-content: space-between; align-items: center; }
    .merchant-action { display: flex; width: 60rpx; flex-direction: column; align-items: center; }
    .merchant-action image { width: 39rpx; height: 39rpx; }
    .merchant-action text { margin-top: 10rpx; color: #303640; font-size: 20rpx; line-height: 27rpx; font-weight: 600; }
    .action-active { opacity: .55; }
    .order-card { margin-top: 27rpx; box-sizing: border-box; padding: 31rpx 28rpx 30rpx; border-radius: 25rpx; background-color: #FFFFFF; }
    .order-title { display: block; margin-bottom: 18rpx; color: #29303A; font-size: 28rpx; line-height: 39rpx; font-weight: 700; }
    .order-row { display: flex; min-height: 49rpx; flex-direction: row; align-items: center; justify-content: space-between; color: #7E8794; font-size: 23rpx; line-height: 34rpx; }
    .order-row text:last-child { margin-left: 24rpx; color: #3F4855; text-align: right; }
    .content-bottom { height: 34rpx; }
    .refund-footer { flex-shrink: 0; padding: 20rpx 64rpx; padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); background-color: transparent; }
    .refund-button { display: flex; height: 84rpx; border-radius: 44rpx; background-image: linear-gradient(90deg, #FF4E35 0%, #FF8318 100%); justify-content: center; align-items: center; }
    .refund-button text { color: #FFFFFF; font-size: 28rpx; line-height: 40rpx; font-weight: 700; }
    .refund-button-active { opacity: .78; transform: scale(.99); }
</style>