shinnosuke
2026-08-06 e5c6b415449d0b7a5e9027dba009e46543004dc0
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
<template>
    <view class="page">
        <view class="content">
            <view class="page-inner">
                <view class="top-panel">
                    <view class="custom-nav">
                        <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" @tap="goBack" hover-class="back-button-active">
                                <image src="/static/news-assets/arrow-left.svg"></image>
                            </view>
                            <text class="nav-title">订单支付</text>
                            <view class="capsule-space" :style="'width:' + navWidth + 'px'"></view>
                        </view>
                    </view>
 
                    <view class="product-summary">
                        <image class="product-image" :src="productImage" mode="aspectFill"></image>
                        <view class="product-copy">
                            <text class="product-name">{{productName}}</text>
                            <view class="product-price"><text class="price-symbol">¥</text><text>{{salePriceText}}</text></view>
                        </view>
                    </view>
                </view>
 
                <view class="price-card">
                    <view class="price-item"><text class="price-label">人才折扣</text><text class="talent-discount">{{productDiscount}}</text></view>
                    <view class="price-item coupon-item" @tap="chooseCoupon" hover-class="price-item-active">
                        <text class="price-label">商品优惠</text>
                        <view class="coupon-value"><text>{{couponText}}</text><text class="chevron">›</text></view>
                    </view>
                    <view class="price-item"><text class="price-label">原价</text><text class="price-value">¥ {{originalAmountText}}</text></view>
                    <view class="price-item"><text class="price-label">总优惠金额</text><text class="price-value">-¥ {{discountAmountText}}</text></view>
                    <view class="price-item"><text class="price-label">用户实付</text><text class="price-value strong">¥ {{actualPayAmountText}}</text></view>
                    <view class="price-item"><text class="price-label">补贴金额</text><text class="price-value">¥ 0.00</text></view>
                </view>
 
                <view class="actual-payment">
                    <view class="actual-price"><text class="actual-symbol">¥</text><text>{{actualPayAmountText}}</text></view>
                    <text class="actual-label">实际支付</text>
                </view>
 
                <view class="confirm-button" hover-class="confirm-button-active" @tap="confirmPayment">
                    <text>确认支付</text>
                </view>
                <view class="safe-bottom"></view>
            </view>
        </view>
    </view>
</template>
 
<script setup lang="uts">
    import { safeBack } from '../../utils/navigation.uts'
    import { createAppOrder, getOrderCouponQuote } from '../../services/portal.uts'
    import { resolveApiUrl } from '../../services/http.uts'
    import { getSession } from '../../services/session.uts'
 
    const navTop = ref(28)
    const navHeight = ref(32)
    const navWidth = ref(88)
    const navRight = ref(10)
    const isSubmitting = ref(false)
    const merchantId = ref('')
    const productId = ref('')
    const orderId = ref('')
    const couponText = ref('无')
    const couponAmount = ref(0.0)
    const productName = ref('【川味盛宴】川菜12人餐')
    const productImage = ref('/static/food-assets/food-1.jpg')
    const salePrice = ref(138.0)
    const originalAmount = ref(266.0)
    const productDiscount = ref('6折')
    const salePriceText = computed(() => salePrice.value.toFixed(2))
    const originalAmountText = computed(() => originalAmount.value.toFixed(2))
    const discountAmountText = ref('0.00')
    const actualPayAmountText = ref('0.00')
    const createOrderIdempotencyKey = ref('')
    let quoteSequence = 0
 
    const applyProductById = (id: string) => {
        if (id == 'food-set-002') { productName.value = '【川香四溢】川菜4人餐'; productImage.value = '/static/food-assets/food-2.jpg'; salePrice.value = 118.0; originalAmount.value = 198.0; productDiscount.value = '6折'; return }
        if (id == 'food-set-003') { productName.value = '【欢聚时光】招牌菜6人餐'; salePrice.value = 168.0; originalAmount.value = 248.0; productDiscount.value = '6.8折'; return }
        if (id == 'food-set-004') { productName.value = '【暖心小聚】特色菜双人餐'; productImage.value = '/static/food-assets/food-2.jpg'; salePrice.value = 88.0; originalAmount.value = 126.0; productDiscount.value = '7折'; return }
        if (id == 'food-set-005') { productName.value = '【家庭聚餐】经典川菜8人餐'; salePrice.value = 238.0; originalAmount.value = 366.0; productDiscount.value = '6.5折'; return }
        if (id == 'food-set-006') { productName.value = '【人气必点】招牌菜4人餐'; productImage.value = '/static/food-assets/food-2.jpg'; salePrice.value = 128.0; originalAmount.value = 188.0; productDiscount.value = '6.8折'; return }
        if (id == 'food-set-007') { productName.value = '【周末欢聚】特色菜10人餐'; salePrice.value = 298.0; originalAmount.value = 498.0; productDiscount.value = '6折'; return }
        if (id == 'food-set-008') { productName.value = '【一人食】暖心工作餐'; productImage.value = '/static/food-assets/food-2.jpg'; salePrice.value = 39.0; originalAmount.value = 49.0; productDiscount.value = '8折'; return }
    }
 
    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['merchantId'] != null) merchantId.value = options['merchantId'] as string
        if (options != null && options['productId'] != null) productId.value = options['productId'] as string
        if (options != null && options['orderId'] != null) orderId.value = options['orderId'] as string
        applyProductById(productId.value)
        if (options != null && options['name'] != null) productName.value = decodeURIComponent(options['name'] as string)
        if (options != null && options['image'] != null) productImage.value = decodeURIComponent(options['image'] as string)
        if (options != null && options['price'] != null) {
            const parsedPrice = parseFloat(options['price'] as string)
            if (!isNaN(parsedPrice)) salePrice.value = parsedPrice
        }
        if (options != null && options['originalPrice'] != null) {
            const parsedOriginalPrice = parseFloat(options['originalPrice'] as string)
            if (!isNaN(parsedOriginalPrice)) originalAmount.value = parsedOriginalPrice
        }
        if (options != null && options['discount'] != null) productDiscount.value = decodeURIComponent(options['discount'] as string)
        if (orderId.value == '') orderId.value = 'order-draft-' + merchantId.value + '-' + productId.value
        createOrderIdempotencyKey.value = 'create-order-' + Date.now() + '-' + Math.floor(Math.random() * 1000000)
        loadOrderQuote('')
    })
 
    onShow(() => {
        const selectedCouponId: any = uni.getStorageSync(couponStorageKey())
        loadOrderQuote(selectedCouponId != null ? '' + selectedCouponId : '')
    })
 
    const goBack = () => safeBack('/pages/benefits/benefits')
    const couponStorageKey = () : string => 'selectedOrderCouponId:' + orderId.value
    const validOrderContext = () : boolean => /^\d+$/.test(merchantId.value) && /^\d+$/.test(productId.value) && orderId.value != ''
    const toAmount = (value: any) : number => {
        const parsed = Number(value)
        return isNaN(parsed) ? 0.0 : parsed
    }
    const loadOrderQuote = (couponId: string) => {
        if (!validOrderContext()) return
        const sequence = quoteSequence + 1
        quoteSequence = sequence
        getOrderCouponQuote(merchantId.value, productId.value, orderId.value, couponId, (data: any) => {
            if (sequence != quoteSequence || data == null) return
            productName.value = data.productName != null ? data.productName as string : productName.value
            if (data.coverUrl != null && data.coverUrl != '') productImage.value = resolveApiUrl(data.coverUrl as string)
            salePrice.value = toAmount(data.productAmount)
            originalAmount.value = toAmount(data.originalAmount)
            couponAmount.value = toAmount(data.couponAmount)
            couponText.value = data.couponLabel != null ? data.couponLabel as string : '无'
            productDiscount.value = data.discountText != null ? data.discountText as string : productDiscount.value
            discountAmountText.value = toAmount(data.discountAmount).toFixed(2)
            actualPayAmountText.value = toAmount(data.payAmount).toFixed(2)
        }, (error) => {
            if (sequence != quoteSequence) return
            if (couponId != '') {
                uni.removeStorageSync(couponStorageKey())
                couponText.value = '无'
                uni.showToast({ title: error.message, icon: 'none' })
                loadOrderQuote('')
            }
        })
    }
    const chooseCoupon = () => uni.navigateTo({
        url: '/pages/coupon-select/coupon-select?source=payment-confirm'
            + '&merchantId=' + encodeURIComponent(merchantId.value)
            + '&productId=' + encodeURIComponent(productId.value)
            + '&orderId=' + encodeURIComponent(orderId.value)
    })
    const confirmPayment = () => {
        if (isSubmitting.value) return
        if (!validOrderContext()) {
            uni.showToast({ title: '订单信息不完整,请重新选择商品', icon: 'none' })
            return
        }
        const session = getSession()
        const talentId = session != null && session.talentId != null ? '' + session.talentId : ''
        if (session == null || talentId == '') {
            uni.showToast({ title: '请先登录后再支付', icon: 'none' })
            uni.navigateTo({ url: '/pages/login/login' })
            return
        }
        isSubmitting.value = true
        const selectedCouponId: any = uni.getStorageSync(couponStorageKey())
        const couponId = selectedCouponId != null ? '' + selectedCouponId : ''
        createAppOrder(merchantId.value, productId.value, couponId, createOrderIdempotencyKey.value, (data: any) => {
            isSubmitting.value = false
            uni.removeStorageSync(couponStorageKey())
            const realOrderNo = data != null && data.orderNo != null ? '' + data.orderNo : ''
            if (realOrderNo == '') {
                uni.showToast({ title: '订单创建失败,请稍后重试', icon: 'none' })
                return
            }
            if (data != null && data.paid === true) {
                uni.redirectTo({ url: '/pages/payment-success/payment-success?orderId=' + encodeURIComponent(realOrderNo) })
                return
            }
            uni.redirectTo({
                url: '/pages/pending-payment/pending-payment?orderId=' + encodeURIComponent(realOrderNo)
                    + '&talentId=' + encodeURIComponent(talentId)
            })
        }, (error) => {
            isSubmitting.value = false
            uni.showToast({ title: error.message, icon: 'none' })
        })
    }
</script>
 
<style>
    .page { width: 100%; height: 100vh; background-color: #F7F7F7; }
    .content { width: 100%; height: 100%; }
    .page-inner { min-height: 100%; background-color: #F7F7F7; }
    .top-panel { padding-bottom: 30rpx; background-image: linear-gradient(180deg, #FFF0B8 0%, #FFF8E5 45%, #F7F7F7 100%); }
    .custom-nav { width: 100%; }
    .safe-space { height: 28px; }
    .nav-row { display: flex; height: 32px; box-sizing: border-box; padding-left: 26rpx; padding-right: 10px; position: relative; flex-direction: row; align-items: center; justify-content: space-between; }
    .back-button { display: flex; width: 60rpx; height: 60rpx; margin-left: -8rpx; justify-content: center; align-items: center; }
    .back-button image { width: 38rpx; height: 38rpx; }
    .back-button-active { opacity: .55; }
    .nav-title { position: absolute; left: 50%; top: 50%; color: #181A1E; font-size: 36rpx; font-weight: 700; transform: translate(-50%, -50%); white-space: nowrap; }
    .capsule-space { width: 88px; height: 100%; flex-shrink: 0; }
    .product-summary { display: flex; margin-top: 34rpx; padding: 0 30rpx; flex-direction: row; align-items: flex-start; }
    .product-image { width: 184rpx; height: 184rpx; border-radius: 17rpx; flex-shrink: 0; background-color: #E8E8E8; }
    .product-copy { display: flex; min-width: 0; flex: 1; min-height: 184rpx; padding-left: 29rpx; flex-direction: column; }
    .product-name { color: #27282B; font-size: 31rpx; font-weight: 600; line-height: 43rpx; }
    .product-price { display: flex; margin-top: 35rpx; color: #17181B; font-size: 34rpx; font-weight: 700; flex-direction: row; align-items: baseline; }
    .price-symbol { margin-right: 3rpx; font-size: 28rpx; }
    .price-card { margin: 0 24rpx; padding: 22rpx 28rpx; border-radius: 17rpx; background-color: #FFFFFF; }
    .price-item { display: flex; min-height: 66rpx; flex-direction: row; align-items: center; justify-content: space-between; }
    .price-label { color: #6A6D73; font-size: 27rpx; }
    .price-value { color: #33363B; font-size: 27rpx; }
    .price-value.strong { color: #25272B; font-weight: 700; }
    .talent-discount { color: #FF6E21; font-size: 28rpx; }
    .coupon-item { margin: 0 -12rpx; padding: 0 12rpx; border-radius: 10rpx; }
    .price-item-active { background-color: #F7F7F7; }
    .coupon-value { display: flex; color: #A5A8AD; font-size: 26rpx; flex-direction: row; align-items: center; }
    .chevron { margin-left: 16rpx; color: #62666C; font-size: 38rpx; line-height: 32rpx; }
    .actual-payment { display: flex; margin-top: 120rpx; flex-direction: column; align-items: center; }
    .actual-price { display: flex; color: #17191D; font-size: 57rpx; font-weight: 800; line-height: 68rpx; flex-direction: row; align-items: baseline; }
    .actual-symbol { margin-right: 5rpx; font-size: 29rpx; }
    .actual-label { margin-top: 8rpx; color: #666970; font-size: 29rpx; }
    .confirm-button { display: flex; width: 514rpx; height: 86rpx; margin: 146rpx auto 0; border-radius: 45rpx; background-image: linear-gradient(90deg, #FF4C32 0%, #FF841C 100%); justify-content: center; align-items: center; }
    .confirm-button text { color: #FFFFFF; font-size: 30rpx; font-weight: 600; }
    .confirm-button-active { opacity: .82; transform: scale(.99); }
    .safe-bottom { height: 44rpx; height: calc(44rpx + env(safe-area-inset-bottom)); }
</style>