210 lines
9.1 KiB
Vue
210 lines
9.1 KiB
Vue
<template>
|
||
<view class="page">
|
||
<view class="nav-bar">
|
||
<view class="nav-back" @click="goBack">
|
||
<u-icon name="arrow-left" color="#1a1818" size="40"></u-icon>
|
||
</view>
|
||
<text class="nav-title">确认订单</text>
|
||
</view>
|
||
|
||
<!-- 券面 -->
|
||
<view class="ticket">
|
||
<view class="ticket-amt">满200减8 <text class="ticket-amt-sub">汽油电子券</text></view>
|
||
<view class="ticket-dsc">{{ voucherName }} | 收银台扫码核销</view>
|
||
<text class="ticket-wm">90天</text>
|
||
</view>
|
||
|
||
<!-- 订单要素 -->
|
||
<view class="card">
|
||
<view class="kv"><text class="k">券名称</text><text class="v">{{ voucherName }}</text></view>
|
||
<view class="kv"><text class="k">使用规则</text><text class="v">{{ voucherRule }} | 单日限用 1 张</text></view>
|
||
<view class="kv"><text class="k">有效期</text><text class="v">购买日起 90 天</text></view>
|
||
<view class="kv"><text class="k">购买数量</text><text class="v">1 张</text></view>
|
||
<view class="kv"><text class="k">售价</text><text class="v">¥{{ priceText }}</text></view>
|
||
<view class="kv"><text class="k">购买账户</text><text class="v">{{ maskedPhone }}</text></view>
|
||
</view>
|
||
|
||
<!-- 支付方式 -->
|
||
<view class="card">
|
||
<view class="card-title">支付方式</view>
|
||
<view class="payrow">
|
||
<image class="pay-logo" src="https://zhihuiyou.oss-cn-shenzhen.aliyuncs.com/hhrs/hhrs_ysf_logo.png" mode="aspectFit" />
|
||
<text class="pay-name">云闪付 App 安全支付</text>
|
||
<view class="pay-radio"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="foot-note">券数量有限,售完即止;兑换后不可退换。</view>
|
||
|
||
<view class="footbar">
|
||
<view class="foot-price"><text class="foot-price-label">合计 </text>¥{{ priceText }}</view>
|
||
<view class="foot-btn" :class="{ disabled: submitting }" @click="onConfirmPay">{{ submitting ? '支付中…' : '确认支付' }}</view>
|
||
</view>
|
||
|
||
<!-- xuchao: 非云闪付环境拦截弹窗,必须点「我知道了」关闭(决策 11) -->
|
||
<view class="modal-mask" v-if="guideModal" @click.stop>
|
||
<view class="modal-box">
|
||
<view class="modal-text">{{ guideText }}</view>
|
||
<view class="modal-ok" @click="guideModal = false">我知道了</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
VOUCHER_NAME, VOUCHER_RULE, VOUCHER_PRICE_TEXT, VOUCHER_GUIDE_TEXT,
|
||
VOUCHER_PAY_WHITELIST_PHONE, isUnionPayApp, bizOk, bizMsg, bizData
|
||
} from '@/common/petrolVoucher.js'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
phone: '',
|
||
orderNo: '',
|
||
submitting: false,
|
||
guideModal: false,
|
||
guideText: VOUCHER_GUIDE_TEXT,
|
||
voucherName: VOUCHER_NAME,
|
||
voucherRule: VOUCHER_RULE,
|
||
priceText: VOUCHER_PRICE_TEXT
|
||
}
|
||
},
|
||
computed: {
|
||
maskedPhone() {
|
||
if (!this.phone || this.phone.length !== 11) return this.phone || ''
|
||
return this.phone.slice(0, 3) + '****' + this.phone.slice(7)
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
this.phone = uni.getStorageSync('phone') || ''
|
||
if (!this.phone) {
|
||
// xuchao: 未登录先看登录,回跳本流程从门店页重新进入(决策 15)
|
||
uni.redirectTo({ url: '/pages/index/login?redirect=' + encodeURIComponent('/pages/venue/detail?id=851219003520709&type=2') })
|
||
return
|
||
}
|
||
if (options.orderNo) this.orderNo = options.orderNo
|
||
this.initFromServer()
|
||
},
|
||
methods: {
|
||
// xuchao: 价格展示以服务端 latest 为准(测试期 0.01);已有待支付单自动续用;已发券直接跳券详情
|
||
initFromServer() {
|
||
this.$u.api.voucherLatest({ phone: this.phone }).then(res => {
|
||
if (!bizOk(res)) return
|
||
const d = bizData(res)
|
||
if (d.price) this.priceText = (d.price / 100).toFixed(2)
|
||
if (!this.orderNo && d.statusCode === 0 && d.orderNo) {
|
||
this.orderNo = d.orderNo
|
||
if (d.amount) this.priceText = (d.amount / 100).toFixed(2)
|
||
}
|
||
if (d.statusCode === 2) {
|
||
uni.redirectTo({ url: '/pages/petrol/voucherDetail?orderNo=' + (d.orderNo || '') })
|
||
}
|
||
}).catch(() => {})
|
||
},
|
||
onConfirmPay() {
|
||
if (this.submitting) return
|
||
// xuchao: 容器拦截——非云闪付且非联调白名单号,弹窗不发起支付(决策 11/13)
|
||
if (!isUnionPayApp() && this.phone !== VOUCHER_PAY_WHITELIST_PHONE) {
|
||
this.guideModal = true
|
||
return
|
||
}
|
||
this.submitting = true
|
||
this.doPay()
|
||
},
|
||
doPay() {
|
||
const step = this.orderNo
|
||
? Promise.resolve({ orderNo: this.orderNo })
|
||
: this.$u.api.voucherCreate({ phone: this.phone }).then(res => {
|
||
if (!bizOk(res)) throw new Error(bizMsg(res) || '下单失败')
|
||
return bizData(res)
|
||
})
|
||
step.then(d => {
|
||
this.orderNo = d.orderNo
|
||
return this.$u.api.voucherPrepay({ orderNo: this.orderNo, phone: this.phone })
|
||
}).then(res => {
|
||
if (!bizOk(res)) throw new Error(bizMsg(res) || '发起支付失败')
|
||
const pre = bizData(res)
|
||
const tn = pre.extendMap && pre.extendMap.resData
|
||
if (!tn) throw new Error('未获取到支付凭证')
|
||
this.launchUnionPay(tn)
|
||
}).catch(err => {
|
||
this.submitting = false
|
||
uni.showToast({ title: (err && err.message) || '操作失败,请稍后再试', icon: 'none' })
|
||
})
|
||
},
|
||
// xuchao: 云闪付控件唤起,交互与湘工惠商城一致(xghmall ordersure.vue:195-229 同型)
|
||
launchUnionPay(tn) {
|
||
if (typeof window === 'undefined' || !window.upsdk) {
|
||
this.submitting = false
|
||
uni.showToast({ title: '支付组件未就绪,请在云闪付内重进页面', icon: 'none' })
|
||
return
|
||
}
|
||
const orderNo = this.orderNo
|
||
window.upsdk.pluginReady(() => {
|
||
window.upsdk.pay({
|
||
tn: tn,
|
||
success: () => {
|
||
uni.redirectTo({ url: '/pages/petrol/voucherDetail?orderNo=' + orderNo })
|
||
},
|
||
fail: () => {
|
||
this.submitting = false
|
||
uni.showToast({ title: '支付未完成,可在门店页继续支付', icon: 'none' })
|
||
}
|
||
})
|
||
})
|
||
// 控件无异步结果时兜底:3 秒后恢复按钮(支付成功路径以回跳/轮询为准)
|
||
setTimeout(() => { this.submitting = false }, 3000)
|
||
},
|
||
goBack() {
|
||
const pages = getCurrentPages()
|
||
if (pages.length > 1) {
|
||
uni.navigateBack()
|
||
} else {
|
||
uni.reLaunch({ url: '/pages/index/index' })
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.page { min-height: 100vh; background-color: #f2f2f7; padding-bottom: 80px; }
|
||
|
||
.nav-bar { display: flex; align-items: center; height: 56px; background-color: #ffffff; border-bottom: 1px solid #f0f0f0; position: relative; }
|
||
.nav-back { position: absolute; left: 12px; top: 0; bottom: 0; display: flex; align-items: center; }
|
||
.nav-title { flex: 1; text-align: center; font-size: 17px; font-weight: 600; color: #1a1818; padding: 0 56px; }
|
||
|
||
.ticket { position: relative; overflow: hidden; background: linear-gradient(135deg, #0d6fe0, #2f9bff); border-radius: 8px; margin: 12px; padding: 16px; color: #ffffff; }
|
||
.ticket-amt { font-size: 26px; font-weight: 700; }
|
||
.ticket-amt-sub { font-size: 13px; font-weight: 400; opacity: 0.9; margin-left: 4px; }
|
||
.ticket-dsc { font-size: 12px; opacity: 0.9; margin-top: 6px; }
|
||
.ticket-wm { position: absolute; right: 4px; bottom: -14px; font-size: 60px; font-weight: 800; opacity: 0.1; }
|
||
|
||
.card { background-color: #ffffff; border-radius: 8px; margin: 12px; padding: 4px 16px 8px; }
|
||
.card-title { font-size: 15px; font-weight: 600; color: #1a1818; padding: 12px 0 4px; }
|
||
.kv { display: flex; justify-content: space-between; align-items: center; font-size: 13px; padding: 11px 0; border-bottom: 1px solid #f2f2f7; }
|
||
.kv:last-child { border-bottom: none; }
|
||
.k { color: #999999; flex-shrink: 0; margin-right: 12px; }
|
||
.v { color: #1a1818; text-align: right; word-break: break-all; }
|
||
|
||
.payrow { display: flex; align-items: center; padding: 6px 0 12px; }
|
||
.pay-logo { width: 26px; height: 26px; border-radius: 5px; margin-right: 10px; flex-shrink: 0; }
|
||
.pay-name { flex: 1; font-size: 14px; color: #1a1818; }
|
||
.pay-radio { width: 18px; height: 18px; border-radius: 50%; background-color: #0088ff; position: relative; }
|
||
.pay-radio::after { content: ''; position: absolute; left: 5px; top: 5px; width: 8px; height: 8px; border-radius: 50%; background-color: #ffffff; }
|
||
|
||
.foot-note { font-size: 12px; color: #999999; margin: 4px 16px; line-height: 1.6; }
|
||
|
||
.footbar { position: fixed; left: 0; right: 0; bottom: 0; height: 64px; background-color: #ffffff; border-top: 1px solid #f0f0f0; display: flex; align-items: center; padding: 0 16px; z-index: 5; }
|
||
.foot-price { font-size: 20px; font-weight: 600; color: #ff4d4f; }
|
||
.foot-price-label { font-size: 12px; font-weight: 400; color: #666666; }
|
||
.foot-btn { flex: 1; margin-left: 14px; height: 44px; border-radius: 8px; background-color: #0088ff; color: #ffffff; font-size: 16px; display: flex; align-items: center; justify-content: center; }
|
||
.foot-btn.disabled { background-color: #c8cdd4; }
|
||
|
||
.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; z-index: 99; }
|
||
.modal-box { width: 300px; background-color: #ffffff; border-radius: 12px; overflow: hidden; }
|
||
.modal-text { padding: 24px 20px 20px; font-size: 14px; color: #1a1818; line-height: 1.7; text-align: center; }
|
||
.modal-ok { height: 48px; border-top: 1px solid #f2f2f7; color: #0088ff; font-size: 16px; font-weight: 600; display: flex; align-items: center; justify-content: center; }
|
||
</style>
|