Files
hhrs/pages/petrol/voucherDetail.vue
T

241 lines
9.9 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="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>
<!-- 发券中(statusCode=1) -->
<view class="issuing" v-if="status === 1">
<view class="spinner"></view>
<view class="issuing-t">支付成功,券发放中…</view>
<view class="issuing-d">通常 10 秒内到账,请稍后刷新查看券码</view>
<view class="btn-line" @click="refresh()">手动刷新</view>
</view>
<!-- 已发券(statusCode=2):二维码 + 订单信息 -->
<block v-else-if="status === 2">
<view class="qrbox">
<view class="qr-badge">有效</view>
<!-- 石油返回图片URL时直接展示 -->
<image v-if="isQrImage" class="qr-img" :src="voucher.voucherCode" mode="widthFix" />
<!-- 返回码字符串时用 qrcodejs 渲染 -->
<view v-else id="petrol-qr" class="qr-canvas"></view>
<view class="qr-tip">到店出示此二维码,收银员以“中石油汽油券”通道扫码核销</view>
</view>
<view class="card">
<view class="card-title">订单信息</view>
<view class="kv"><text class="k">券名称</text><text class="v">{{ voucherName }}</text></view>
<view class="kv"><text class="k">券规则</text><text class="v rule-hl">{{ voucherRule }} | 无门槛</text></view>
<view class="kv"><text class="k">有效期至</text><text class="v">{{ voucher.validUntil || '-' }}</text></view>
<view class="kv"><text class="k">实付金额</text><text class="v">¥{{ amountText }}</text></view>
<view class="kv"><text class="k">支付时间</text><text class="v">{{ fmtTime(voucher.payTime) }}</text></view>
<view class="kv"><text class="k">订单号</text><text class="v">{{ voucher.orderNo }}</text></view>
</view>
</block>
<!-- 待支付(0):续付入口 -->
<view class="state-card" v-else-if="status === 0">
<view class="state-t">订单待支付</view>
<view class="state-d">实付金额 ¥{{ amountText }},订单将在下单 30 分钟后自动关闭</view>
<view class="btn-main" @click="goConfirm">继续支付</view>
</view>
<!-- 已退款(3/4) -->
<view class="state-card" v-else-if="status === 3 || status === 4">
<view class="state-t">{{ status === 4 ? '退款处理中' : '已退款' }}</view>
<view class="state-d">很抱歉,本券发放失败,支付金额已原路退回{{ status === 4 ? '处理中,到账请稍后查询' : '' }}。如有疑问请致电客服。</view>
<view class="btn-main" @click="goConfirm">重新购买</view>
</view>
<!-- 已关闭(5) -->
<view class="state-card" v-else-if="status === 5">
<view class="state-t">订单已关闭</view>
<view class="state-d">该待支付订单已超时关闭,可重新下单购买。</view>
<view class="btn-main" @click="goConfirm">重新购买</view>
</view>
<!-- 加载中 / 无单 -->
<view class="state-card" v-else>
<view class="state-t">{{ status === null ? '加载中…' : '暂无券订单' }}</view>
<view class="state-d" v-if="status === null">正在获取订单状态</view>
<view class="state-d" v-else>您还没有购买汽油券,可返回门店页购买</view>
<view class="btn-line" v-if="status !== null" @click="refresh()">刷新</view>
</view>
<!-- 使用说明(有券或发券中都展示) -->
<block v-if="status === 2 || status === 1">
<view class="card">
<view class="card-title">使用规则</view>
<view class="ol-item" v-for="(t, i) in useRules" :key="'r' + i">{{ i + 1 }}、{{ t }}</view>
</view>
<view class="card">
<view class="card-title">使用流程</view>
<view class="ol-item" v-for="(t, i) in useFlow" :key="'f' + i">{{ i + 1 }}、{{ t }}</view>
</view>
</block>
</view>
</template>
<script>
import {
VOUCHER_NAME, VOUCHER_RULE, VOUCHER_USE_RULES, VOUCHER_USE_FLOW,
bizOk, bizMsg, bizData
} from '@/common/petrolVoucher.js'
export default {
data() {
return {
orderNo: '',
phone: '',
voucher: {},
status: null, // null=加载中;含义对齐后端 statusCode
pollTimer: null,
pollLeft: 3,
voucherName: VOUCHER_NAME,
voucherRule: VOUCHER_RULE,
useRules: VOUCHER_USE_RULES,
useFlow: VOUCHER_USE_FLOW
}
},
computed: {
amountText() {
const f = this.voucher.amount
return f ? (f / 100).toFixed(2) : ''
},
isQrImage() {
return typeof this.voucher.voucherCode === 'string' && /^https?:\/\//i.test(this.voucher.voucherCode)
}
},
onLoad(options) {
this.phone = uni.getStorageSync('phone') || ''
this.orderNo = options.orderNo || ''
if (!this.phone) {
uni.redirectTo({ url: '/pages/index/login?redirect=' + encodeURIComponent('/pages/petrol/voucherDetail?orderNo=' + this.orderNo) })
return
}
this.refresh(true)
},
onUnload() {
this.stopPoll()
},
methods: {
refresh(first) {
this.$u.api.voucherLatest({ phone: this.phone }).then(res => {
if (!bizOk(res)) {
uni.showToast({ title: bizMsg(res) || '查询失败', icon: 'none' })
return
}
const d = bizData(res)
if (!d || d.statusCode === undefined || d.statusCode === -1) {
this.status = -1
this.voucher = {}
return
}
this.voucher = d
this.status = d.statusCode
if (this.status === 1) {
// xuchao: 发券中轮询(支付回跳后 5 秒 ×3 次;手动刷新不限量)
if (first && this.pollLeft > 0) this.startPoll()
} else {
this.stopPoll()
}
if (this.status === 2) this.$nextTick(() => this.renderQr())
}).catch(() => {
if (first) uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' })
})
},
startPoll() {
this.stopPoll()
this.pollTimer = setInterval(() => {
if (this.pollLeft <= 0) { this.stopPoll(); return }
this.pollLeft -= 1
this.refresh(false)
}, 5000)
},
stopPoll() {
if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null }
},
// xuchao: 码字符串→二维码渲染;qrcode.min.js 为本地静态资源(static/js/),动态注入一次
renderQr() {
if (this.isQrImage) return
const draw = () => {
if (typeof document === 'undefined' || typeof window.QRCode === 'undefined') return
const el = document.getElementById('petrol-qr')
if (!el || el.hasChildNodes()) {
if (el && this.voucher.voucherCode) { el.innerHTML = ''; } else { return }
}
// eslint-disable-next-line no-new
new window.QRCode(el, {
text: this.voucher.voucherCode,
width: 200, height: 200, colorDark: '#1a1818', colorLight: '#ffffff',
correctLevel: window.QRCode.CorrectLevel.H
})
}
if (typeof window !== 'undefined' && window.QRCode) { draw(); return }
if (typeof document === 'undefined') return
const s = document.createElement('script')
s.src = '/static/js/qrcode.min.js'
s.onload = draw
document.head.appendChild(s)
},
goConfirm() {
uni.navigateTo({ url: '/pages/petrol/orderConfirm' + (this.status === 0 ? '?orderNo=' + this.voucher.orderNo : '') })
},
fmtTime(t) {
if (!t) return '-'
return String(t).replace('T', ' ').slice(0, 16)
},
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: 24px; }
.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; }
.qrbox { position: relative; background-color: #ffffff; border-radius: 8px; margin: 12px; padding: 28px 20px 18px; text-align: center; }
.qr-badge { position: absolute; top: 14px; left: 14px; font-size: 11px; color: #2BB879; border: 1px solid #b6e6cf; border-radius: 3px; padding: 2px 8px; background-color: #f2fbf7; }
.qr-canvas { display: inline-block; }
.qr-canvas img, .qr-canvas canvas { display: block; }
.qr-img { width: 200px; }
.qr-tip { font-size: 12px; color: #999999; margin-top: 12px; }
.card { background-color: #ffffff; border-radius: 8px; margin: 12px; padding: 4px 16px 12px; }
.card-title { font-size: 15px; font-weight: 600; color: #1a1818; padding: 14px 0 2px; }
.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; }
.rule-hl { color: #ff4d4f; font-weight: 600; }
.ol-item { font-size: 13px; color: #666666; line-height: 1.8; }
.issuing { background-color: #ffffff; border-radius: 8px; margin: 12px; padding: 46px 20px 26px; text-align: center; }
.spinner { width: 44px; height: 44px; border: 3px solid #dbe9ff; border-top-color: #0088ff; border-radius: 50%; margin: 0 auto 16px; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.issuing-t { font-size: 16px; color: #1a1818; font-weight: 600; margin-bottom: 6px; }
.issuing-d { font-size: 12px; color: #999999; line-height: 1.7; margin-bottom: 18px; }
.state-card { background-color: #ffffff; border-radius: 8px; margin: 12px; padding: 30px 20px; text-align: center; }
.state-t { font-size: 17px; font-weight: 600; color: #1a1818; margin-bottom: 8px; }
.state-d { font-size: 13px; color: #999999; line-height: 1.7; margin-bottom: 20px; }
.btn-main { height: 44px; border-radius: 8px; background-color: #0088ff; color: #ffffff; font-size: 15px; display: flex; align-items: center; justify-content: center; }
.btn-line { height: 42px; border-radius: 8px; border: 1px solid #0088ff; color: #0088ff; font-size: 15px; display: flex; align-items: center; justify-content: center; background-color: #ffffff; margin: 0 30px; }
.issuing .btn-line { margin: 0; }
</style>