将组件级 i18n 对象重构为统一由全局 locales/messages.js 管理,确保中英文语言切换时正确显示对应文本。 - 新增 utils/i18n.js 国际化工具函数 - 更新 store/$t.mixin.js 翻译混入逻辑 - 迁移 pkgService、pkgBooking、pkgAccount、pkgExplore、pages 等共 58 个组件的中英文文本至全局消息表
1972 lines
61 KiB
Vue
1972 lines
61 KiB
Vue
<template>
|
||
<view class="bill-panel">
|
||
<view class="hero-panel tn-margin-bottom-lg">
|
||
<view class="hero-title tn-margin-top">{{ $tr('待支付账单') }}</view>
|
||
<view class="hero-desc tn-margin-top-sm">{{ heroDesc }}</view>
|
||
<view v-if="currentReferenceTypeNo" class="source-tag tn-margin-top-sm">{{ $tr('申请单号:') }}{{ currentReferenceTypeNo }}</view>
|
||
<view class="hero-summary tn-margin-top-lg">
|
||
<view class="summary-card">
|
||
<view class="summary-label">{{ $tr('待支付数量') }}</view>
|
||
<view class="summary-value tn-margin-top-sm">{{ billList.length }}</view>
|
||
</view>
|
||
<view class="summary-card">
|
||
<view class="summary-label">{{ $tr('待支付总额') }}</view>
|
||
<view class="summary-value tn-margin-top-sm">¥{{ formatMoney(totalRemainingAmount) }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="loading || resolvingReference" class="empty-panel tn-padding-lg">
|
||
<view class="empty-title">{{ $tr('账单加载中') }}</view>
|
||
<view class="empty-desc tn-margin-top-sm">{{ $tr('正在同步您的待支付账单,请稍候。') }}</view>
|
||
</view>
|
||
|
||
<view v-else-if="billList.length">
|
||
<view
|
||
v-for="(item, index) in billList"
|
||
:key="getBillKey(item)"
|
||
class="bill-card tn-margin-bottom-lg"
|
||
>
|
||
<view class="card-head tn-flex tn-flex-row-between tn-flex-col-center">
|
||
<view class="card-title-wrap">
|
||
<view class="card-title">{{ getReferenceTypeText(item) }}</view>
|
||
<view class="card-code tn-margin-top-xs">{{ getBillNo(item) }}</view>
|
||
</view>
|
||
<view :class="['status-badge', 'status-' + resolvePaymentStatusType(getBillPaymentStatus(item))]">
|
||
{{ getBillPaymentStatusText(item) }}
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card-body tn-margin-top">
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('关联业务编号') }}</text>
|
||
<text class="info-value">{{ getBillReferenceTypeNo(item) || '--' }}</text>
|
||
</view>
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('应收合计') }}</text>
|
||
<text class="info-value">¥{{ formatMoney(getBillTotalCharge(item)) }}</text>
|
||
</view>
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('已付金额') }}</text>
|
||
<text class="info-value">¥{{ formatMoney(getBillPaidAmount(item)) }}</text>
|
||
</view>
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('待付金额') }}</text>
|
||
<text class="info-value info-value--highlight">¥{{ formatMoney(getBillRemainingAmount(item)) }}</text>
|
||
</view>
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('退款状态') }}</text>
|
||
<text class="info-value">{{ getRefundStatusText(getBillRefundStatus(item)) }}</text>
|
||
</view>
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('创建时间') }}</text>
|
||
<text class="info-value">{{ formatDateTime(getBillCreateTime(item)) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="getBillRemark(item) || getBillResult(item)" class="remark-panel tn-margin-top">
|
||
<view v-if="getBillRemark(item)" class="remark-line">
|
||
<text class="remark-label">{{ $tr('备注') }}</text>
|
||
<text class="remark-text">{{ getBillRemark(item) }}</text>
|
||
</view>
|
||
<view v-if="getBillResult(item)" class="remark-line tn-margin-top-sm">
|
||
<text class="remark-label">{{ $tr('处理结果') }}</text>
|
||
<text class="remark-text">{{ getBillResult(item) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="getPendingPaymentOrder(item)" class="pending-payment-panel tn-margin-top">
|
||
<view class="pending-payment-title">{{ $tr('未完成支付订单') }}</view>
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('支付单号') }}</text>
|
||
<text class="info-value">{{ getPendingPaymentOrderNo(item) }}</text>
|
||
</view>
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('订单金额') }}</text>
|
||
<text class="info-value info-value--highlight">¥{{ formatMoney(getPendingPaymentAmount(item)) }}</text>
|
||
</view>
|
||
<view v-if="getPendingPaymentExpireText(item)" class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('剩余支付时间') }}</text>
|
||
<text class="info-value" :class="{ 'info-value--expired': isPendingPaymentExpired(item) }">
|
||
{{ getPendingPaymentExpireText(item) }}
|
||
</text>
|
||
</view>
|
||
<view v-if="getPendingPaymentDescription(item)" class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('支付说明') }}</text>
|
||
<text class="info-value">{{ getPendingPaymentDescription(item) }}</text>
|
||
</view>
|
||
<view v-if="getPendingPaymentChannel(item)" class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('支付渠道') }}</text>
|
||
<text class="info-value">{{ getPendingPaymentChannel(item) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card-footer tn-margin-top-lg">
|
||
<view
|
||
v-if="canCancelOrder(item)"
|
||
class="cancel-btn"
|
||
:data-bill-index="index"
|
||
:class="{ 'cancel-btn--loading': cancelingBillNo === getBillNo(item) }"
|
||
@tap.stop="handleCancelOrder"
|
||
>
|
||
<text class="tn-text-bold">{{ getCancelButtonText(item) }}</text>
|
||
</view>
|
||
<view
|
||
class="pay-btn"
|
||
:data-bill-index="index"
|
||
:class="{ 'pay-btn--loading': payingBillNo === getBillNo(item) }"
|
||
@tap.stop="handleWechatPay"
|
||
>
|
||
<text class="tn-text-bold">{{ getPayButtonText(item) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-else class="empty-panel tn-padding-lg">
|
||
<view class="empty-title">{{ currentReferenceTypeNo ? $tr('暂无待支付账单') : $tr('暂无可查询申请单') }}</view>
|
||
<view class="empty-desc tn-margin-top-sm">{{ currentReferenceTypeNo ? $tr('当前申请单下没有查询到待支付账单,后续有新账单时会在这里展示。') : $tr('当前未找到可关联的申请单,请先前往“我的申请”选择具体申请后查看账单。') }}</view>
|
||
<view v-if="!currentReferenceTypeNo" class="empty-action tn-margin-top-lg" @tap="goToMyApplication">
|
||
<text class="tn-text-bold">{{ $tr('前往我的申请') }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<tn-popup v-model="showPaymentNotice" mode="bottom" length="72%" :borderRadius="32" :closeBtn="true" @close="handlePaymentNoticeClose">
|
||
<view class="payment-notice-popup">
|
||
<view class="payment-notice-popup__header">
|
||
<view class="payment-notice-popup__title">{{ paymentNoticeContext.title }}</view>
|
||
</view>
|
||
|
||
<scroll-view scroll-y class="payment-notice-popup__body">
|
||
<view class="payment-notice-popup__summary">
|
||
<view class="payment-notice-popup__summary-row">
|
||
<text class="payment-notice-popup__summary-label">{{ paymentNoticeContext.billLabel }}</text>
|
||
<text class="payment-notice-popup__summary-value">{{ paymentNoticeContext.billNo }}</text>
|
||
</view>
|
||
<view class="payment-notice-popup__summary-row">
|
||
<text class="payment-notice-popup__summary-label">{{ paymentNoticeContext.itemLabel }}</text>
|
||
<text class="payment-notice-popup__summary-value">{{ paymentNoticeContext.itemTitle }}</text>
|
||
</view>
|
||
<view class="payment-notice-popup__summary-row">
|
||
<text class="payment-notice-popup__summary-label">{{ paymentNoticeContext.amountLabel }}</text>
|
||
<text class="payment-notice-popup__summary-value payment-notice-popup__summary-value--price">{{ paymentNoticeContext.amountText }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="payment-notice-popup__content">
|
||
<view
|
||
v-for="(line, index) in paymentNoticeContext.lines"
|
||
:key="`payment-notice-line-${index}`"
|
||
class="payment-notice-popup__line"
|
||
>
|
||
{{ index + 1 }}. {{ line }}
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view class="payment-notice-popup__footer">
|
||
<view class="payment-notice-popup__action payment-notice-popup__action--ghost" @tap="closePaymentNotice(false)">
|
||
<text class="tn-text-bold">{{ paymentNoticeContext.cancelText }}</text>
|
||
</view>
|
||
<view class="payment-notice-popup__action payment-notice-popup__action--primary" @tap="closePaymentNotice(true)">
|
||
<text class="tn-text-bold">{{ paymentNoticeContext.confirmText }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</tn-popup>
|
||
|
||
<view v-if="showPaymentFeedbackPanel" class="payment-waiting-mask">
|
||
<view :class="['payment-waiting-panel', 'payment-waiting-panel--' + paymentStageVisualType]">
|
||
<view class="payment-waiting-glow payment-waiting-glow--one"></view>
|
||
<view class="payment-waiting-glow payment-waiting-glow--two"></view>
|
||
|
||
<view :class="['payment-waiting-badge', 'payment-waiting-badge--' + paymentStageVisualType]">
|
||
<text :class="[paymentBadgeIconClass, 'payment-waiting-badge__icon']"></text>
|
||
<text>{{ paymentStageLabel }}</text>
|
||
</view>
|
||
|
||
<view class="payment-waiting-icon-wrap">
|
||
<view v-if="isPaymentWaitingStage" class="payment-waiting-icon-ring"></view>
|
||
<view :class="['payment-waiting-icon-core', 'payment-waiting-icon-core--' + paymentStageVisualType]">
|
||
<text :class="[paymentFeedbackIconClass, 'payment-waiting-icon', { 'payment-waiting-icon--spin': isPaymentWaitingStage }]"></text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="payment-waiting-title">{{ paymentWaitingTitle }}</view>
|
||
<view class="payment-waiting-desc">{{ paymentWaitingDesc }}</view>
|
||
|
||
<view v-if="isPaymentWaitingStage" class="payment-progress-card">
|
||
<view class="payment-progress-head tn-flex tn-flex-row-between tn-flex-col-center">
|
||
<text class="payment-progress-label">{{ $tr('结果确认进度') }}</text>
|
||
<text class="payment-progress-value">{{ paymentProgressText }}</text>
|
||
</view>
|
||
<view class="payment-progress-track">
|
||
<view class="payment-progress-bar" :style="{ width: paymentProgressPercent + '%' }"></view>
|
||
</view>
|
||
<view class="payment-progress-note">{{ paymentStatusHintText }}</view>
|
||
</view>
|
||
|
||
<view class="payment-summary-grid">
|
||
<view class="payment-summary-item">
|
||
<view class="payment-summary-label">{{ $tr('账单号') }}</view>
|
||
<view class="payment-summary-value payment-summary-value--mono">{{ paymentWaitingBillNo || '--' }}</view>
|
||
</view>
|
||
<view class="payment-summary-item">
|
||
<view class="payment-summary-label">{{ $tr('本次支付') }}</view>
|
||
<view class="payment-summary-value payment-summary-value--price">¥{{ paymentWaitingAmountText }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="payment-tips-card">
|
||
<view class="payment-tips-title">{{ paymentTipsTitle }}</view>
|
||
<view v-for="(tip, index) in paymentTips" :key="`${paymentStage}-${index}`" class="payment-tip-line">{{ tip }}</view>
|
||
</view>
|
||
|
||
<view v-if="showPaymentActionButton" class="payment-action-row">
|
||
<view class="payment-action-btn" @tap="handlePaymentResultAction">
|
||
<text class="tn-text-bold">{{ paymentActionText }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { accountQueryBills, applicationFormMePage, paymentBillPay, paymentPayingBills, paymentStatusByBillNo, userDetailCancel } from '@/api/index.js'
|
||
import {
|
||
ACCOUNT_BILL_PAYMENT_STATUS,
|
||
ACCOUNT_BILL_STATUS,
|
||
getAccountBillPaymentStatusLabel,
|
||
getAccountBillRefundStatusLabel,
|
||
getAccountReferenceTypeLabel
|
||
} from '@/utils/enum-store.js'
|
||
import { getActiveApplicationCode, getMyApplicationList, saveActiveApplicationCode, saveMyApplicationList } from '@/utils/app-state'
|
||
|
||
const VIEWING_APPLICATION_PATTERN = /预约看房申请|看房时间:|意向房型:|意向区域:|viewing request|viewing time|preferred room type|preferred area/i
|
||
|
||
function isViewingApplicationRecord(item = {}) {
|
||
if (!item || typeof item !== 'object') {
|
||
return false
|
||
}
|
||
|
||
const typeText = [
|
||
item.businessType,
|
||
item.applicationType,
|
||
item.applicationTypeDesc,
|
||
item.formType,
|
||
item.bizType,
|
||
item.type,
|
||
item.typeDesc
|
||
]
|
||
.filter(value => value !== null && typeof value !== 'undefined' && String(value).trim())
|
||
.join(' ')
|
||
|
||
if (/viewing|看房|带看/i.test(typeText)) {
|
||
return true
|
||
}
|
||
|
||
if ([item.appointmentViewingTime, item.viewingTime, item.bookViewingTime].some(value => String(value || '').trim())) {
|
||
return true
|
||
}
|
||
|
||
const remark = item && item.remark ? String(item.remark) : ''
|
||
return VIEWING_APPLICATION_PATTERN.test(remark)
|
||
}
|
||
|
||
function normalizeBillList(response) {
|
||
const payload = response && response.data ? response.data : response
|
||
if (Array.isArray(payload)) {
|
||
return payload.filter(item => item && typeof item === 'object')
|
||
}
|
||
if (payload && Array.isArray(payload.data)) {
|
||
return payload.data.filter(item => item && typeof item === 'object')
|
||
}
|
||
return []
|
||
}
|
||
|
||
function normalizeApplicationList(list = []) {
|
||
if (!Array.isArray(list)) {
|
||
return []
|
||
}
|
||
return list
|
||
.filter(item => item && typeof item === 'object')
|
||
.map((item) => {
|
||
return {
|
||
...item,
|
||
isViewingApplication: isViewingApplicationRecord(item)
|
||
}
|
||
})
|
||
}
|
||
|
||
function parseExpireTimeToTimestamp(value) {
|
||
if (value === null || typeof value === 'undefined' || value === '') {
|
||
return 0
|
||
}
|
||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||
return value > 1e12 ? value : value * 1000
|
||
}
|
||
const text = String(value).trim()
|
||
if (!text) {
|
||
return 0
|
||
}
|
||
if (/^\d+$/.test(text)) {
|
||
const numericValue = Number(text)
|
||
if (!Number.isFinite(numericValue)) {
|
||
return 0
|
||
}
|
||
return numericValue > 1e12 ? numericValue : numericValue * 1000
|
||
}
|
||
const normalizedText = text.includes('T') ? text : text.replace(/-/g, '/')
|
||
const timestamp = Date.parse(normalizedText)
|
||
return Number.isFinite(timestamp) ? timestamp : 0
|
||
}
|
||
|
||
function padCountdownUnit(value) {
|
||
return String(Math.max(0, Number(value) || 0)).padStart(2, '0')
|
||
}
|
||
|
||
const PAYMENT_QUERY_STATUS = {
|
||
PENDING: 'pending',
|
||
SUCCESS: 'success',
|
||
FAILED: 'failed'
|
||
}
|
||
|
||
const PAYMENT_ORDER_STATUS = {
|
||
SUCCESS: 10,
|
||
FAILED: 99
|
||
}
|
||
|
||
const PAYMENT_QUERY_MAX_ATTEMPTS = 8
|
||
const PAYMENT_QUERY_INTERVAL = 1800
|
||
|
||
export default {
|
||
name: 'CurrentApplicationBillPanel',
|
||
props: {
|
||
referenceTypeNo: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
autoResolveReference: {
|
||
type: Boolean,
|
||
default: true
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
billList: [],
|
||
pendingPaymentOrderMap: {},
|
||
payingBillNo: '',
|
||
cancelingBillNo: '',
|
||
currentReferenceTypeNo: '',
|
||
countdownNow: Date.now(),
|
||
resolvingReference: false,
|
||
paymentPollingBillNo: '',
|
||
paymentPollingAttempt: 0,
|
||
paymentStage: '',
|
||
paymentStatusHint: '',
|
||
paymentSession: null,
|
||
showPaymentNotice: false,
|
||
paymentNoticeResolver: null,
|
||
paymentNoticeContext: {
|
||
title: '',
|
||
billLabel: '',
|
||
billNo: '--',
|
||
itemLabel: '',
|
||
itemTitle: '',
|
||
amountLabel: '',
|
||
amountText: '',
|
||
lines: [],
|
||
confirmText: '',
|
||
cancelText: ''
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
language() {
|
||
return this.$getLanguage()
|
||
},
|
||
totalRemainingAmount() {
|
||
return this.billList.reduce((sum, item) => {
|
||
return sum + Number(item && item.remainingAmount ? item.remainingAmount : 0)
|
||
}, 0)
|
||
},
|
||
heroDesc() {
|
||
if (this.currentReferenceTypeNo) {
|
||
return this.$tr('当前页面已按申请单编号查询其关联的待支付账单记录。')
|
||
}
|
||
return this.$tr('当前将优先读取最近申请单的编号来查询关联账单。')
|
||
},
|
||
showPaymentFeedbackPanel() {
|
||
return Boolean(this.paymentStage)
|
||
},
|
||
isPaymentWaitingStage() {
|
||
return this.paymentStage === 'launching' || this.paymentStage === 'polling'
|
||
},
|
||
isPaymentSuccessStage() {
|
||
return this.paymentStage === 'success'
|
||
},
|
||
isPaymentFailedStage() {
|
||
return this.paymentStage === 'failed'
|
||
},
|
||
paymentStageVisualType() {
|
||
if (this.isPaymentSuccessStage) {
|
||
return 'success'
|
||
}
|
||
if (this.isPaymentFailedStage) {
|
||
return 'failed'
|
||
}
|
||
return 'waiting'
|
||
},
|
||
paymentStageLabel() {
|
||
if (this.paymentStage === 'launching') {
|
||
return this.$tr('正在唤起微信收银台')
|
||
}
|
||
if (this.paymentStage === 'polling') {
|
||
return this.$tr('正在同步支付结果')
|
||
}
|
||
if (this.paymentStage === 'success') {
|
||
return this.$tr('支付结果已确认')
|
||
}
|
||
if (this.paymentStage === 'failed') {
|
||
return this.$tr('支付未完成')
|
||
}
|
||
return this.$tr('支付处理中')
|
||
},
|
||
paymentBadgeIconClass() {
|
||
if (this.isPaymentSuccessStage) {
|
||
return 'tn-icon-success-circle-fill'
|
||
}
|
||
if (this.isPaymentFailedStage) {
|
||
return 'tn-icon-warning-fill'
|
||
}
|
||
return 'tn-icon-secure'
|
||
},
|
||
paymentFeedbackIconClass() {
|
||
if (this.isPaymentSuccessStage) {
|
||
return 'tn-icon-success'
|
||
}
|
||
if (this.isPaymentFailedStage) {
|
||
return 'tn-icon-close'
|
||
}
|
||
return 'tn-icon-loading'
|
||
},
|
||
paymentWaitingTitle() {
|
||
if (this.paymentStage === 'launching') {
|
||
return this.$tr('请完成微信支付')
|
||
}
|
||
if (this.paymentStage === 'polling') {
|
||
return this.$tr('正在确认是否支付成功')
|
||
}
|
||
if (this.paymentStage === 'success') {
|
||
return this.$tr('支付成功')
|
||
}
|
||
if (this.paymentStage === 'failed') {
|
||
return this.$tr('支付结果确认失败')
|
||
}
|
||
return this.$tr('支付处理中')
|
||
},
|
||
paymentWaitingDesc() {
|
||
if (this.paymentStage === 'launching') {
|
||
return this.$tr('系统正在调起微信支付,请在微信收银台中完成付款后返回。')
|
||
}
|
||
if (this.paymentStage === 'polling') {
|
||
return this.$tr('支付完成后,系统会自动向后台查询支付状态,无需手动刷新。')
|
||
}
|
||
if (this.paymentStage === 'success') {
|
||
return this.$tr('本次支付已同步完成,账单状态已更新。')
|
||
}
|
||
if (this.paymentStage === 'failed') {
|
||
return this.$tr('系统暂时未能确认支付成功,您可以查看提示后稍后重试。')
|
||
}
|
||
return ''
|
||
},
|
||
paymentWaitingBillNo() {
|
||
return this.paymentPollingBillNo || this.payingBillNo || (this.paymentSession && this.paymentSession.billNo) || ''
|
||
},
|
||
paymentWaitingAmountText() {
|
||
const amount = this.paymentSession && Number(this.paymentSession.amount)
|
||
return Number.isFinite(amount) ? amount.toFixed(2) : '0.00'
|
||
},
|
||
paymentProgressPercent() {
|
||
if (this.paymentStage !== 'polling') {
|
||
return 16
|
||
}
|
||
const current = this.paymentPollingAttempt > 0 ? this.paymentPollingAttempt : 1
|
||
return Math.min(100, Math.max(18, Math.round((current / PAYMENT_QUERY_MAX_ATTEMPTS) * 100)))
|
||
},
|
||
paymentProgressText() {
|
||
if (this.paymentStage !== 'polling') {
|
||
return this.$tr('等待支付完成')
|
||
}
|
||
const current = this.paymentPollingAttempt > 0 ? this.paymentPollingAttempt : 1
|
||
return `${current}/${PAYMENT_QUERY_MAX_ATTEMPTS}`
|
||
},
|
||
paymentStatusHintText() {
|
||
if (this.paymentStatusHint) {
|
||
return this.paymentStatusHint
|
||
}
|
||
if (this.paymentStage === 'launching') {
|
||
return this.$tr('已为您准备好支付信息,等待微信支付完成。')
|
||
}
|
||
return this.$tr('正在与后台核对支付状态,请稍候。')
|
||
},
|
||
paymentTipsTitle() {
|
||
if (this.isPaymentFailedStage) {
|
||
return this.$tr('处理建议')
|
||
}
|
||
if (this.isPaymentSuccessStage) {
|
||
return this.$tr('完成说明')
|
||
}
|
||
return this.$tr('温馨提示')
|
||
},
|
||
paymentTips() {
|
||
if (this.isPaymentFailedStage) {
|
||
return [
|
||
this.$tr('若您已完成付款,请稍后刷新账单,避免重复支付。'),
|
||
this.$tr('若微信侧已扣款但页面未更新,请联系管家或客服协助核实。'),
|
||
this.$tr('若未扣款,可返回账单列表后重新发起支付。')
|
||
]
|
||
}
|
||
if (this.isPaymentSuccessStage) {
|
||
return [
|
||
this.$tr('若当前申请单还有其他待付账单,可继续在当前页完成支付。'),
|
||
this.$tr('若当前账单已全部支付完成,系统会自动返回首页。'),
|
||
this.$tr('支付记录同步可能略有延迟,刷新后即可看到最新状态。')
|
||
]
|
||
}
|
||
return [
|
||
this.$tr('支付成功后,系统可能需要几秒钟同步账单状态。'),
|
||
this.$tr('请不要重复点击支付,也不要提前关闭当前页面。'),
|
||
this.$tr('如果超时未完成,系统会自动再校验一次账单状态。')
|
||
]
|
||
},
|
||
showPaymentActionButton() {
|
||
return this.isPaymentFailedStage
|
||
},
|
||
paymentActionText() {
|
||
return this.isPaymentFailedStage ? this.$tr('我知道了') : ''
|
||
}
|
||
},
|
||
watch: {
|
||
referenceTypeNo: {
|
||
immediate: true,
|
||
handler(value) {
|
||
const nextReferenceTypeNo = value ? String(value).trim() : ''
|
||
if (nextReferenceTypeNo === this.currentReferenceTypeNo) {
|
||
return
|
||
}
|
||
this.currentReferenceTypeNo = nextReferenceTypeNo
|
||
if (this.currentReferenceTypeNo) {
|
||
saveActiveApplicationCode(this.currentReferenceTypeNo)
|
||
}
|
||
if (this._panelReady) {
|
||
this.refreshBillList()
|
||
}
|
||
}
|
||
}
|
||
},
|
||
mounted() {
|
||
this._panelReady = true
|
||
this.startCountdownTimer()
|
||
this.refreshBillList()
|
||
},
|
||
beforeDestroy() {
|
||
this.clearCountdownTimer()
|
||
},
|
||
methods: {
|
||
getBillNo(item = {}) {
|
||
return item.no || item.billNo || ''
|
||
},
|
||
getBillKey(item = {}) {
|
||
return this.getBillNo(item) || this.getBillReferenceTypeNo(item) || this.getBillCreateTime(item) || 'bill-item'
|
||
},
|
||
getBillReferenceTypeNo(item = {}) {
|
||
return item.referenceTypeNo || ''
|
||
},
|
||
getBillPaymentStatus(item = {}) {
|
||
return item.paymentStatus
|
||
},
|
||
getBillPaymentStatusText(item = {}) {
|
||
const translatedStatus = this.getPaymentStatusText(this.getBillPaymentStatus(item))
|
||
return translatedStatus || item.paymentStatusDesc || this.$tr('处理中')
|
||
},
|
||
getBillRefundStatus(item = {}) {
|
||
return item.refundStatus
|
||
},
|
||
getBillCreateTime(item = {}) {
|
||
return item.createTime || ''
|
||
},
|
||
getBillTotalCharge(item = {}) {
|
||
return item.totalCharge
|
||
},
|
||
getBillPaidAmount(item = {}) {
|
||
return item.paidAmount
|
||
},
|
||
getBillRemainingAmount(item = {}) {
|
||
return item.remainingAmount
|
||
},
|
||
getBillRemark(item = {}) {
|
||
return item.remark || ''
|
||
},
|
||
getBillResult(item = {}) {
|
||
return item.result || ''
|
||
},
|
||
getPendingPaymentOrder(item = {}) {
|
||
const billNo = this.getBillNo(item)
|
||
return billNo ? (this.pendingPaymentOrderMap[billNo] || null) : null
|
||
},
|
||
getPendingPaymentOrderNo(item = {}) {
|
||
const order = this.getPendingPaymentOrder(item)
|
||
return order ? (order.payRequestNo || order.payNo || order.paymentNo || '--') : '--'
|
||
},
|
||
getPendingPaymentAmount(item = {}) {
|
||
const order = this.getPendingPaymentOrder(item)
|
||
if (order && (order.amount || order.amount === 0)) {
|
||
return order.amount
|
||
}
|
||
return this.getBillRemainingAmount(item)
|
||
},
|
||
getPendingPaymentDescription(item = {}) {
|
||
const order = this.getPendingPaymentOrder(item)
|
||
return order ? (order.description || '') : ''
|
||
},
|
||
getPendingPaymentChannel(item = {}) {
|
||
const order = this.getPendingPaymentOrder(item)
|
||
return order && order.cashier ? (order.cashier.payChannel || '') : ''
|
||
},
|
||
startCountdownTimer() {
|
||
if (this._countdownTimer) {
|
||
return
|
||
}
|
||
this._countdownTimer = setInterval(() => {
|
||
this.countdownNow = Date.now()
|
||
}, 1000)
|
||
},
|
||
clearCountdownTimer() {
|
||
if (this._countdownTimer) {
|
||
clearInterval(this._countdownTimer)
|
||
this._countdownTimer = null
|
||
}
|
||
},
|
||
getPendingPaymentExpireTimestamp(order = {}) {
|
||
return parseExpireTimeToTimestamp(order.expireTime || (order.cashier && order.cashier.expireTime) || '')
|
||
},
|
||
getPendingPaymentRemainingSeconds(item = {}) {
|
||
const order = this.getPendingPaymentOrder(item)
|
||
if (!order) {
|
||
return 0
|
||
}
|
||
const expireTimestamp = this.getPendingPaymentExpireTimestamp(order)
|
||
if (!expireTimestamp) {
|
||
return 0
|
||
}
|
||
return Math.max(0, Math.ceil((expireTimestamp - this.countdownNow) / 1000))
|
||
},
|
||
formatPendingPaymentCountdown(seconds = 0) {
|
||
const totalSeconds = Math.max(0, Number(seconds) || 0)
|
||
const day = Math.floor(totalSeconds / 86400)
|
||
const hour = Math.floor((totalSeconds % 86400) / 3600)
|
||
const minute = Math.floor((totalSeconds % 3600) / 60)
|
||
const second = totalSeconds % 60
|
||
const timeText = `${padCountdownUnit(hour)}:${padCountdownUnit(minute)}:${padCountdownUnit(second)}`
|
||
return day > 0
|
||
? (this.language === 'en' ? `${day}d ${timeText}` : `${day}天 ${timeText}`)
|
||
: timeText
|
||
},
|
||
isPendingPaymentExpired(item = {}) {
|
||
const order = this.getPendingPaymentOrder(item)
|
||
if (!order) {
|
||
return false
|
||
}
|
||
const expireTimestamp = this.getPendingPaymentExpireTimestamp(order)
|
||
if (!expireTimestamp) {
|
||
return false
|
||
}
|
||
return expireTimestamp <= this.countdownNow
|
||
},
|
||
getPendingPaymentExpireText(item = {}) {
|
||
const order = this.getPendingPaymentOrder(item)
|
||
if (!order) {
|
||
return ''
|
||
}
|
||
const expireTimestamp = this.getPendingPaymentExpireTimestamp(order)
|
||
if (!expireTimestamp) {
|
||
return ''
|
||
}
|
||
if (expireTimestamp <= this.countdownNow) {
|
||
return this.$tr('已失效')
|
||
}
|
||
return this.formatPendingPaymentCountdown(this.getPendingPaymentRemainingSeconds(item))
|
||
},
|
||
getPayButtonText(item = {}) {
|
||
const billNo = this.getBillNo(item)
|
||
if (this.paymentPollingBillNo && this.paymentPollingBillNo === billNo) {
|
||
if (!this.paymentPollingAttempt) {
|
||
return this.$tr('确认结果中...')
|
||
}
|
||
return this.$tr('查询结果中({current}/{total})', {
|
||
current: this.paymentPollingAttempt,
|
||
total: PAYMENT_QUERY_MAX_ATTEMPTS
|
||
})
|
||
}
|
||
if (this.payingBillNo === billNo) {
|
||
return this.paymentStage === 'launching' ? this.$tr('拉起支付中...') : this.$tr('确认结果中...')
|
||
}
|
||
if (this.getPendingPaymentOrder(item) && !this.isPendingPaymentExpired(item)) {
|
||
return this.$tr('继续支付')
|
||
}
|
||
return this.$tr('微信支付')
|
||
},
|
||
canCancelOrder(item = {}) {
|
||
if (!this.currentReferenceTypeNo) {
|
||
return false
|
||
}
|
||
const remainingAmount = Number(this.getBillRemainingAmount(item) || 0)
|
||
const paymentStatus = Number(this.getBillPaymentStatus(item))
|
||
return remainingAmount > 0 || paymentStatus !== ACCOUNT_BILL_PAYMENT_STATUS.PAID
|
||
},
|
||
getCancelButtonText(item = {}) {
|
||
return this.cancelingBillNo === this.getBillNo(item)
|
||
? this.$tr('取消中...')
|
||
: this.$tr('取消订单')
|
||
},
|
||
resolveReferenceTypeNo(item = {}) {
|
||
return item.code || item.detailCode || ''
|
||
},
|
||
resolvePreferredReferenceTypeNo(list = []) {
|
||
const sortedList = normalizeApplicationList(list)
|
||
.filter(item => !item.isViewingApplication && this.resolveReferenceTypeNo(item))
|
||
.sort((a, b) => new Date(b.createTime || 0).getTime() - new Date(a.createTime || 0).getTime())
|
||
|
||
if (!sortedList.length) {
|
||
return ''
|
||
}
|
||
|
||
const preferredItem = sortedList.find(item => Number(item.status) !== 99) || sortedList[0]
|
||
return this.resolveReferenceTypeNo(preferredItem)
|
||
},
|
||
ensureReferenceTypeNo() {
|
||
if (this.currentReferenceTypeNo) {
|
||
return Promise.resolve(this.currentReferenceTypeNo)
|
||
}
|
||
if (!this.autoResolveReference) {
|
||
return Promise.resolve('')
|
||
}
|
||
|
||
const cachedActiveCode = getActiveApplicationCode()
|
||
if (cachedActiveCode) {
|
||
this.currentReferenceTypeNo = cachedActiveCode
|
||
return Promise.resolve(this.currentReferenceTypeNo)
|
||
}
|
||
|
||
const cachedApplicationList = normalizeApplicationList(getMyApplicationList())
|
||
const cachedCode = this.resolvePreferredReferenceTypeNo(cachedApplicationList)
|
||
if (cachedCode) {
|
||
this.currentReferenceTypeNo = cachedCode
|
||
saveActiveApplicationCode(cachedCode)
|
||
return Promise.resolve(this.currentReferenceTypeNo)
|
||
}
|
||
|
||
this.resolvingReference = true
|
||
return applicationFormMePage({
|
||
PageIndex: 1,
|
||
PageSize: 20
|
||
})
|
||
.then((response) => {
|
||
const payload = response && response.data ? response.data : response
|
||
const list = normalizeApplicationList(payload && payload.data)
|
||
saveMyApplicationList(list)
|
||
const nextCode = this.resolvePreferredReferenceTypeNo(list)
|
||
this.currentReferenceTypeNo = nextCode
|
||
if (nextCode) {
|
||
saveActiveApplicationCode(nextCode)
|
||
}
|
||
return nextCode
|
||
})
|
||
.catch(() => '')
|
||
.finally(() => {
|
||
this.resolvingReference = false
|
||
})
|
||
},
|
||
formatMoney(value) {
|
||
const amount = Number(value || 0)
|
||
return amount.toFixed(2)
|
||
},
|
||
formatDateTime(value) {
|
||
if (!value) {
|
||
return '--'
|
||
}
|
||
const text = String(value).replace('T', ' ')
|
||
return text.length > 16 ? text.slice(0, 16) : text
|
||
},
|
||
getPaymentStatusText(status) {
|
||
return getAccountBillPaymentStatusLabel(status, this.$tr('处理中'))
|
||
},
|
||
resolvePaymentStatusType(status) {
|
||
if (Number(status) === ACCOUNT_BILL_PAYMENT_STATUS.PAID) {
|
||
return 'paid'
|
||
}
|
||
if (Number(status) === ACCOUNT_BILL_PAYMENT_STATUS.PARTIAL) {
|
||
return 'partial'
|
||
}
|
||
return 'pending'
|
||
},
|
||
getRefundStatusText(status) {
|
||
return getAccountBillRefundStatusLabel(status, '--')
|
||
},
|
||
getReferenceTypeText(item = {}) {
|
||
const translatedType = getAccountReferenceTypeLabel(item.referenceType, '')
|
||
return translatedType || item.referenceTypeDesc || this.$tr('账单记录')
|
||
},
|
||
parsePaymentInfo(paymentInfo) {
|
||
if (!paymentInfo) {
|
||
return {}
|
||
}
|
||
if (typeof paymentInfo === 'object') {
|
||
return paymentInfo
|
||
}
|
||
const text = String(paymentInfo).trim()
|
||
if (!text) {
|
||
return {}
|
||
}
|
||
try {
|
||
return JSON.parse(text)
|
||
} catch (error) {
|
||
return {}
|
||
}
|
||
},
|
||
normalizeWechatPayOptions(paymentInfo) {
|
||
const source = this.parsePaymentInfo(paymentInfo)
|
||
return {
|
||
provider: 'wxpay',
|
||
timeStamp: String(source.timeStamp || source.timestamp || source.time_stamp || ''),
|
||
nonceStr: source.nonceStr || source.noncestr || source.nonce_str || '',
|
||
package: source.package || source.packageValue || source.package_value || '',
|
||
signType: source.signType || source.sign_type || 'MD5',
|
||
paySign: source.paySign || source.paysign || source.pay_sign || ''
|
||
}
|
||
},
|
||
requestWechatPayment(paymentInfo) {
|
||
const payOptions = this.normalizeWechatPayOptions(paymentInfo)
|
||
if (!payOptions.timeStamp || !payOptions.nonceStr || !payOptions.package || !payOptions.paySign) {
|
||
return Promise.reject(new Error(this.$tr('微信支付参数不完整')))
|
||
}
|
||
|
||
return new Promise((resolve, reject) => {
|
||
uni.requestPayment({
|
||
...payOptions,
|
||
success: (res) => resolve(res),
|
||
fail: (error) => reject(error)
|
||
})
|
||
})
|
||
},
|
||
normalizePendingPaymentOrders(response) {
|
||
const payload = response && response.data ? response.data : response
|
||
if (Array.isArray(payload)) {
|
||
return payload.filter(item => item && typeof item === 'object')
|
||
}
|
||
if (payload && Array.isArray(payload.data)) {
|
||
return payload.data.filter(item => item && typeof item === 'object')
|
||
}
|
||
return []
|
||
},
|
||
syncPendingPaymentOrders(list = []) {
|
||
const billNos = [...new Set(
|
||
(Array.isArray(list) ? list : [])
|
||
.map(item => this.getBillNo(item))
|
||
.filter(Boolean)
|
||
)]
|
||
|
||
if (!billNos.length) {
|
||
this.pendingPaymentOrderMap = {}
|
||
return Promise.resolve({})
|
||
}
|
||
|
||
const currentReferenceTypeNo = this.currentReferenceTypeNo
|
||
return Promise.all(
|
||
billNos.map((billNo) => {
|
||
return paymentPayingBills({ BillNo: billNo })
|
||
.then((response) => {
|
||
const orderList = this.normalizePendingPaymentOrders(response)
|
||
return {
|
||
billNo,
|
||
order: orderList.length ? orderList[0] : null
|
||
}
|
||
})
|
||
.catch(() => ({
|
||
billNo,
|
||
order: null
|
||
}))
|
||
})
|
||
).then((resultList) => {
|
||
if (currentReferenceTypeNo !== this.currentReferenceTypeNo) {
|
||
return this.pendingPaymentOrderMap
|
||
}
|
||
const nextMap = resultList.reduce((result, item) => {
|
||
if (item && item.billNo && item.order) {
|
||
result[item.billNo] = item.order
|
||
}
|
||
return result
|
||
}, {})
|
||
this.pendingPaymentOrderMap = nextMap
|
||
return nextMap
|
||
})
|
||
},
|
||
getWechatLoginCode() {
|
||
return new Promise((resolve, reject) => {
|
||
uni.login({
|
||
provider: 'weixin',
|
||
success: (res) => {
|
||
const code = res && res.code ? res.code : ''
|
||
if (!code) {
|
||
reject(new Error(this.$tr('获取微信登录凭证失败')))
|
||
return
|
||
}
|
||
resolve(code)
|
||
},
|
||
fail: (error) => {
|
||
reject(error || new Error(this.$tr('获取微信登录凭证失败')))
|
||
}
|
||
})
|
||
})
|
||
},
|
||
buildBillPayPayload(item = {}, wechatCode = '') {
|
||
const payload = {
|
||
referenceTypeNo: this.currentReferenceTypeNo,
|
||
billNo: this.getBillNo(item),
|
||
payMethod: 'Wechat',
|
||
amount: Number(this.getBillRemainingAmount(item) || 0),
|
||
description: this.getReferenceTypeText(item) || this.$tr('账单支付')
|
||
}
|
||
if (wechatCode) {
|
||
payload.ExtraInfo = wechatCode
|
||
}
|
||
return payload
|
||
},
|
||
resolveNoticeText(key, zhText, enText) {
|
||
return this.language === 'en' ? enText : zhText
|
||
},
|
||
resetPaymentNoticeContext() {
|
||
this.paymentNoticeContext = {
|
||
title: '',
|
||
billLabel: '',
|
||
billNo: '--',
|
||
itemLabel: '',
|
||
itemTitle: '',
|
||
amountLabel: '',
|
||
amountText: '',
|
||
lines: [],
|
||
confirmText: '',
|
||
cancelText: ''
|
||
}
|
||
},
|
||
closePaymentNotice(confirmed = false) {
|
||
const resolver = this.paymentNoticeResolver
|
||
this.paymentNoticeResolver = null
|
||
this.showPaymentNotice = false
|
||
this.resetPaymentNoticeContext()
|
||
if (typeof resolver === 'function') {
|
||
resolver(!!confirmed)
|
||
}
|
||
},
|
||
handlePaymentNoticeClose() {
|
||
if (this.paymentNoticeResolver) {
|
||
this.closePaymentNotice(false)
|
||
} else {
|
||
this.showPaymentNotice = false
|
||
this.resetPaymentNoticeContext()
|
||
}
|
||
},
|
||
confirmPaymentNotice(item = {}) {
|
||
const billNo = this.getBillNo(item) || '--'
|
||
const amountText = `¥${this.formatMoney(this.getBillRemainingAmount(item))}`
|
||
const modalTitle = this.resolveNoticeText('paymentNoticeTitle', '支付须知', 'Notice')
|
||
const itemLabel = this.resolveNoticeText('paymentNoticeItemLabel', '费用项目', 'Item')
|
||
const billLabel = this.resolveNoticeText('paymentNoticeBillLabel', '账单号', 'Bill')
|
||
const amountLabel = this.resolveNoticeText('paymentNoticeAmountLabel', '支付金额', 'Amount')
|
||
const line1 = this.resolveNoticeText('paymentNoticeLine1', '请确认本次支付账单和金额无误后再继续付款。', 'Please confirm the bill and amount before payment.')
|
||
const line2 = this.resolveNoticeText('paymentNoticeLine2', '支付完成后,账单状态可能需要几秒同步,请勿重复支付。', 'Bill status may sync a few seconds later. Do not pay twice.')
|
||
const line3 = this.resolveNoticeText('paymentNoticeLine3', '若支付中断或页面关闭,可稍后返回账单页查看最新结果。', 'If interrupted, return later to check the latest result.')
|
||
const itemTitle = this.getReferenceTypeText(item) || this.resolveNoticeText('billPaymentDesc', '账单支付', 'Bill Payment')
|
||
|
||
return new Promise((resolve) => {
|
||
this.paymentNoticeResolver = resolve
|
||
this.paymentNoticeContext = {
|
||
title: modalTitle,
|
||
billLabel,
|
||
billNo,
|
||
itemLabel,
|
||
itemTitle,
|
||
amountLabel,
|
||
amountText,
|
||
lines: [line1, line2, line3],
|
||
confirmText: this.resolveNoticeText('paymentNoticeConfirm', '继续支付', 'Continue'),
|
||
cancelText: this.resolveNoticeText('paymentNoticeCancel', '稍后再付', 'Later')
|
||
}
|
||
this.showPaymentNotice = true
|
||
})
|
||
},
|
||
confirmCancelOrder() {
|
||
return new Promise((resolve) => {
|
||
uni.showModal({
|
||
title: this.$tr('取消订单'),
|
||
content: this.$tr('取消后将关闭当前未支付订单,是否继续?'),
|
||
confirmColor: '#ef4444',
|
||
success: (res) => resolve(!!(res && res.confirm)),
|
||
fail: () => resolve(false)
|
||
})
|
||
})
|
||
},
|
||
buildCancelOrderPayload(item = {}) {
|
||
const pendingOrder = this.getPendingPaymentOrder(item) || {}
|
||
const billNo = this.getBillNo(item)
|
||
const paymentNo = pendingOrder.paymentNo || pendingOrder.payNo || pendingOrder.payRequestNo || ''
|
||
return {
|
||
detailCode: this.currentReferenceTypeNo,
|
||
code: this.currentReferenceTypeNo,
|
||
referenceTypeNo: this.currentReferenceTypeNo,
|
||
billNo,
|
||
BillNo: billNo,
|
||
paymentNo,
|
||
payNo: paymentNo,
|
||
payRequestNo: pendingOrder.payRequestNo || ''
|
||
}
|
||
},
|
||
createPaymentSession(item = {}) {
|
||
this.paymentSession = {
|
||
billNo: this.getBillNo(item),
|
||
amount: Number(this.getBillRemainingAmount(item) || 0),
|
||
title: this.getReferenceTypeText(item) || this.$tr('账单支付')
|
||
}
|
||
},
|
||
resetPaymentWaitingState() {
|
||
this.payingBillNo = ''
|
||
this.paymentPollingBillNo = ''
|
||
this.paymentPollingAttempt = 0
|
||
this.paymentStage = ''
|
||
this.paymentStatusHint = ''
|
||
this.paymentSession = null
|
||
},
|
||
openPaymentFailedState(message = '') {
|
||
this.paymentStage = 'failed'
|
||
this.paymentStatusHint = message || this.$tr('暂未查询到支付成功结果,请稍后刷新账单确认。')
|
||
this.payingBillNo = ''
|
||
this.paymentPollingBillNo = this.paymentWaitingBillNo
|
||
},
|
||
handlePaymentResultAction() {
|
||
if (this.isPaymentFailedStage) {
|
||
this.resetPaymentWaitingState()
|
||
}
|
||
},
|
||
wait(ms = 0) {
|
||
return new Promise((resolve) => {
|
||
setTimeout(resolve, ms)
|
||
})
|
||
},
|
||
buildPaymentQueryContext(item = {}, cashier = {}) {
|
||
const paymentInfo = this.parsePaymentInfo(cashier.paymentInfo)
|
||
return {
|
||
referenceTypeNo: this.currentReferenceTypeNo,
|
||
billNo: this.getBillNo(item),
|
||
paymentNo: cashier.paymentNo || cashier.cashierNo || cashier.orderNo || '',
|
||
tradeNo: cashier.tradeNo || cashier.outTradeNo || paymentInfo.outTradeNo || ''
|
||
}
|
||
},
|
||
queryPaymentResult(queryContext = {}) {
|
||
return paymentStatusByBillNo({
|
||
BillNo: queryContext.billNo
|
||
})
|
||
.then((response) => {
|
||
const payload = response && response.data ? response.data : response
|
||
const payStatus = Number(payload && payload.payStatus)
|
||
const statusDesc = this.language !== 'en' && payload && payload.statusDesc ? payload.statusDesc : ''
|
||
|
||
if (payStatus === PAYMENT_ORDER_STATUS.SUCCESS) {
|
||
return {
|
||
status: PAYMENT_QUERY_STATUS.SUCCESS,
|
||
message: statusDesc || this.$tr('后台确认支付成功'),
|
||
data: payload
|
||
}
|
||
}
|
||
if (payStatus === PAYMENT_ORDER_STATUS.FAILED) {
|
||
return {
|
||
status: PAYMENT_QUERY_STATUS.FAILED,
|
||
message: statusDesc || this.$tr('后台返回支付失败'),
|
||
data: payload
|
||
}
|
||
}
|
||
return {
|
||
status: PAYMENT_QUERY_STATUS.PENDING,
|
||
message: statusDesc || this.$tr('后台仍在处理中'),
|
||
data: payload
|
||
}
|
||
})
|
||
},
|
||
pollPaymentUntilSettled(item = {}, cashier = {}) {
|
||
const queryContext = this.buildPaymentQueryContext(item, cashier)
|
||
let lastResult = null
|
||
|
||
this.paymentStage = 'polling'
|
||
this.paymentPollingBillNo = queryContext.billNo
|
||
this.paymentPollingAttempt = 0
|
||
this.paymentStatusHint = this.$tr('已支付成功,正在等待后台确认账单状态。')
|
||
|
||
const executePolling = async () => {
|
||
for (let attempt = 1; attempt <= PAYMENT_QUERY_MAX_ATTEMPTS; attempt += 1) {
|
||
if (attempt > 1) {
|
||
await this.wait(PAYMENT_QUERY_INTERVAL)
|
||
}
|
||
this.paymentPollingAttempt = attempt
|
||
lastResult = await this.queryPaymentResult(queryContext)
|
||
this.paymentStatusHint = lastResult && lastResult.message ? lastResult.message : this.$tr('正在与后台核对支付状态,请稍候。')
|
||
if (lastResult && lastResult.status === PAYMENT_QUERY_STATUS.SUCCESS) {
|
||
return lastResult
|
||
}
|
||
if (lastResult && lastResult.status === PAYMENT_QUERY_STATUS.FAILED) {
|
||
throw new Error(lastResult.message || this.$tr('支付失败'))
|
||
}
|
||
}
|
||
throw new Error((lastResult && lastResult.message) || this.$tr('支付结果确认超时,请稍后刷新账单确认'))
|
||
}
|
||
|
||
return executePolling()
|
||
},
|
||
emitPaymentSuccess() {
|
||
const hasPendingBills = this.billList.length > 0
|
||
this.paymentStage = 'success'
|
||
this.paymentStatusHint = hasPendingBills
|
||
? this.$tr('支付结果已确认,当前申请单仍有其他待支付账单。')
|
||
: this.$tr('支付结果已确认,当前申请单账单已全部完成。')
|
||
this.payingBillNo = ''
|
||
this.paymentPollingBillNo = this.paymentWaitingBillNo
|
||
return this.wait(1200)
|
||
.then(() => {
|
||
this.$emit('payment-success', {
|
||
referenceTypeNo: this.currentReferenceTypeNo,
|
||
hasPendingBills,
|
||
billList: this.billList
|
||
})
|
||
if (!hasPendingBills) {
|
||
this.$emit('all-paid', {
|
||
referenceTypeNo: this.currentReferenceTypeNo
|
||
})
|
||
return
|
||
}
|
||
this.resetPaymentWaitingState()
|
||
})
|
||
},
|
||
reloadListAfterPayment() {
|
||
return this.reloadList()
|
||
.catch(() => [])
|
||
.then(() => {
|
||
this.emitPaymentSuccess()
|
||
})
|
||
},
|
||
reconcilePaymentResult(billNo = '') {
|
||
return this.reloadList()
|
||
.catch(() => [])
|
||
.then(() => {
|
||
const billStillPending = this.billList.some(item => this.getBillNo(item) === billNo)
|
||
if (!billStillPending) {
|
||
this.emitPaymentSuccess()
|
||
return true
|
||
}
|
||
return false
|
||
})
|
||
},
|
||
handleWechatPay(event) {
|
||
const dataset = event && event.currentTarget ? event.currentTarget.dataset || {} : {}
|
||
const billIndex = Number(dataset.billIndex)
|
||
const item = Number.isInteger(billIndex) && billIndex >= 0 ? (this.billList[billIndex] || {}) : {}
|
||
const billNo = this.getBillNo(item)
|
||
const pendingPaymentOrder = this.getPendingPaymentOrder(item)
|
||
const reusablePendingPaymentOrder = pendingPaymentOrder && !this.isPendingPaymentExpired(item)
|
||
? pendingPaymentOrder
|
||
: null
|
||
if (!billNo || !this.currentReferenceTypeNo) {
|
||
uni.showToast({
|
||
title: this.$tr('账单信息不完整'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
if (this.payingBillNo) {
|
||
return
|
||
}
|
||
let cashier = {}
|
||
Promise.resolve()
|
||
.then(() => this.confirmPaymentNotice(item))
|
||
.then((confirmed) => {
|
||
if (!confirmed) {
|
||
throw new Error('PAYMENT_NOTICE_CANCELED')
|
||
}
|
||
this.payingBillNo = billNo
|
||
this.paymentStage = 'launching'
|
||
this.paymentStatusHint = this.$tr('正在唤起微信支付,请在微信内完成付款。')
|
||
this.createPaymentSession(item)
|
||
})
|
||
.then(() => {
|
||
if (reusablePendingPaymentOrder && reusablePendingPaymentOrder.cashier && reusablePendingPaymentOrder.cashier.paymentInfo) {
|
||
cashier = reusablePendingPaymentOrder.cashier
|
||
return cashier
|
||
}
|
||
return this.getWechatLoginCode()
|
||
.then((wechatCode) => paymentBillPay(this.buildBillPayPayload(item, wechatCode)))
|
||
.then((response) => {
|
||
const payload = response && response.data ? response.data : response
|
||
cashier = payload && payload.cashier ? payload.cashier : {}
|
||
return cashier
|
||
})
|
||
})
|
||
.then(() => {
|
||
return this.requestWechatPayment(cashier.paymentInfo)
|
||
})
|
||
.then(() => {
|
||
return this.pollPaymentUntilSettled(item, cashier)
|
||
})
|
||
.then(() => {
|
||
return this.reloadListAfterPayment()
|
||
})
|
||
.catch((error) => {
|
||
const errMsg = error && (error.errMsg || error.message || error.msg) ? (error.errMsg || error.message || error.msg) : ''
|
||
if (errMsg === 'PAYMENT_NOTICE_CANCELED') {
|
||
this.resetPaymentWaitingState()
|
||
return
|
||
}
|
||
if (/cancel/i.test(errMsg)) {
|
||
this.resetPaymentWaitingState()
|
||
uni.showToast({
|
||
title: this.$tr('已取消支付'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
return this.reconcilePaymentResult(billNo)
|
||
.then((hasConfirmedSuccess) => {
|
||
if (hasConfirmedSuccess) {
|
||
return
|
||
}
|
||
this.openPaymentFailedState(errMsg || this.$tr('支付结果确认失败,请稍后刷新账单确认'))
|
||
})
|
||
})
|
||
.finally(() => {
|
||
if (this.isPaymentWaitingStage) {
|
||
this.resetPaymentWaitingState()
|
||
}
|
||
})
|
||
},
|
||
handleCancelOrder(event) {
|
||
const dataset = event && event.currentTarget ? event.currentTarget.dataset || {} : {}
|
||
const billIndex = Number(dataset.billIndex)
|
||
const item = Number.isInteger(billIndex) && billIndex >= 0 ? (this.billList[billIndex] || {}) : {}
|
||
const billNo = this.getBillNo(item)
|
||
if (!billNo || !this.canCancelOrder(item) || this.cancelingBillNo || this.payingBillNo) {
|
||
return
|
||
}
|
||
this.confirmCancelOrder()
|
||
.then((confirmed) => {
|
||
if (!confirmed) {
|
||
return
|
||
}
|
||
this.cancelingBillNo = billNo
|
||
return userDetailCancel(this.buildCancelOrderPayload(item), this.language)
|
||
.then(() => {
|
||
uni.showToast({
|
||
title: this.$tr('订单已取消'),
|
||
icon: 'success'
|
||
})
|
||
return this.refreshBillList()
|
||
})
|
||
})
|
||
.catch((error) => {
|
||
uni.showToast({
|
||
title: (error && (error.message || error.msg)) || this.$tr('取消订单失败'),
|
||
icon: 'none'
|
||
})
|
||
})
|
||
.finally(() => {
|
||
this.cancelingBillNo = ''
|
||
})
|
||
},
|
||
refreshBillList() {
|
||
return this.ensureReferenceTypeNo()
|
||
.then((referenceTypeNo) => {
|
||
if (!referenceTypeNo) {
|
||
this.billList = []
|
||
this.pendingPaymentOrderMap = {}
|
||
return []
|
||
}
|
||
this.$emit('reference-resolved', referenceTypeNo)
|
||
return this.reloadList()
|
||
})
|
||
},
|
||
reloadList() {
|
||
if (!this.currentReferenceTypeNo) {
|
||
this.billList = []
|
||
this.pendingPaymentOrderMap = {}
|
||
return Promise.resolve([])
|
||
}
|
||
const requestKey = this.currentReferenceTypeNo
|
||
if (this._reloadListPromise && this._reloadListKey === requestKey) {
|
||
return this._reloadListPromise
|
||
}
|
||
this.loading = true
|
||
this._reloadListKey = requestKey
|
||
this._reloadListPromise = accountQueryBills({
|
||
referenceTypeNo: this.currentReferenceTypeNo,
|
||
status: ACCOUNT_BILL_STATUS.ACTIVE,
|
||
paymentStatus: ACCOUNT_BILL_PAYMENT_STATUS.UNPAID
|
||
})
|
||
.then((response) => {
|
||
this.billList = normalizeBillList(response)
|
||
return this.syncPendingPaymentOrders(this.billList)
|
||
.then(() => this.billList)
|
||
})
|
||
.catch((error) => {
|
||
this.billList = []
|
||
this.pendingPaymentOrderMap = {}
|
||
uni.showToast({
|
||
title: (error && (error.message || error.msg)) || this.$tr('账单加载失败'),
|
||
icon: 'none'
|
||
})
|
||
return []
|
||
})
|
||
.finally(() => {
|
||
this.loading = false
|
||
this._reloadListPromise = null
|
||
})
|
||
return this._reloadListPromise
|
||
},
|
||
goToMyApplication() {
|
||
uni.navigateTo({
|
||
url: '/pkgService/my_application/my_application'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.hero-panel,
|
||
.bill-card,
|
||
.empty-panel {
|
||
background: #ffffff;
|
||
border-radius: 24rpx;
|
||
padding: 32rpx;
|
||
box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.04);
|
||
border: 1rpx solid rgba($brand-primary, 0.05);
|
||
}
|
||
|
||
.hero-title,
|
||
.empty-title,
|
||
.card-title {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #1A2233;
|
||
}
|
||
|
||
.hero-desc,
|
||
.empty-desc,
|
||
.card-code,
|
||
.info-label,
|
||
.remark-label {
|
||
color: #6A7A99;
|
||
}
|
||
|
||
.hero-summary {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.source-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 8rpx 18rpx;
|
||
border-radius: 999rpx;
|
||
font-size: 22rpx;
|
||
color: $brand-primary;
|
||
background: rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.summary-card {
|
||
flex: 1;
|
||
padding: 24rpx;
|
||
border-radius: 20rpx;
|
||
background: linear-gradient(135deg, rgba($brand-primary, 0.1) 0%, rgba(59, 130, 246, 0.05) 100%);
|
||
border: 1rpx solid rgba($brand-primary, 0.1);
|
||
}
|
||
|
||
.summary-label {
|
||
font-size: 22rpx;
|
||
color: #6A7A99;
|
||
}
|
||
|
||
.summary-value {
|
||
font-size: 40rpx;
|
||
font-weight: 700;
|
||
color: #1A2233;
|
||
}
|
||
|
||
.card-code,
|
||
.info-label,
|
||
.info-value,
|
||
.remark-text {
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.status-badge {
|
||
padding: 8rpx 20rpx;
|
||
border-radius: 999rpx;
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.status-pending {
|
||
color: #b45309;
|
||
background: rgba(245, 158, 11, 0.12);
|
||
}
|
||
|
||
.status-partial {
|
||
color: $brand-primary;
|
||
background: rgba($brand-primary, 0.12);
|
||
}
|
||
|
||
.status-paid {
|
||
color: #166534;
|
||
background: rgba(34, 197, 94, 0.12);
|
||
}
|
||
|
||
.card-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0;
|
||
}
|
||
|
||
.info-row {
|
||
padding: 16rpx 0;
|
||
border-bottom: 1px dashed rgba($brand-primary, 0.15);
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.info-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.info-label {
|
||
flex: 0 0 170rpx;
|
||
color: #6A7A99;
|
||
}
|
||
|
||
.info-value {
|
||
flex: 1;
|
||
color: #1A2233;
|
||
text-align: right;
|
||
word-break: break-all;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.info-value--highlight {
|
||
color: #c2410c;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.info-value--expired {
|
||
color: #dc2626;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.remark-panel {
|
||
padding: 24rpx;
|
||
border-radius: 20rpx;
|
||
background: rgba($brand-primary, 0.02);
|
||
border: 1rpx solid rgba($brand-primary, 0.1);
|
||
}
|
||
|
||
.pending-payment-panel {
|
||
padding: 24rpx;
|
||
border-radius: 20rpx;
|
||
background: linear-gradient(180deg, rgba(255, 247, 237, 0.9) 0%, rgba(255, 251, 245, 0.95) 100%);
|
||
border: 1rpx solid rgba(249, 115, 22, 0.16);
|
||
}
|
||
|
||
.pending-payment-title {
|
||
margin-bottom: 12rpx;
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #c2410c;
|
||
}
|
||
|
||
.remark-line {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.remark-label {
|
||
flex: 0 0 96rpx;
|
||
font-size: 22rpx;
|
||
color: #6A7A99;
|
||
}
|
||
|
||
.remark-text {
|
||
flex: 1;
|
||
color: #1A2233;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.card-footer {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.cancel-btn,
|
||
.pay-btn {
|
||
min-width: 220rpx;
|
||
height: 84rpx;
|
||
padding: 0 36rpx;
|
||
border-radius: 999rpx;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(135deg, rgba($brand-primary, 0.9) 0%, $brand-primary 100%);
|
||
color: #ffffff;
|
||
box-shadow: 0 8rpx 20rpx rgba($brand-primary, 0.25);
|
||
font-size: 26rpx;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.cancel-btn {
|
||
background: #ffffff;
|
||
color: #ef4444;
|
||
border: 1rpx solid rgba(239, 68, 68, 0.3);
|
||
box-shadow: 0 8rpx 20rpx rgba(239, 68, 68, 0.08);
|
||
}
|
||
|
||
.pay-btn:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.cancel-btn:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.cancel-btn--loading,
|
||
.pay-btn--loading {
|
||
opacity: 0.72;
|
||
}
|
||
|
||
.empty-action {
|
||
width: 100%;
|
||
height: 96rpx;
|
||
border-radius: 999rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(135deg, rgba($brand-primary, 0.9) 0%, $brand-primary 100%);
|
||
color: #ffffff;
|
||
box-shadow: 0 12rpx 32rpx rgba($brand-primary, 0.25);
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.empty-action:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.payment-notice-popup {
|
||
background: #ffffff;
|
||
border-radius: 32rpx 32rpx 0 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 72vh;
|
||
}
|
||
|
||
.payment-notice-popup__header {
|
||
padding: 32rpx;
|
||
text-align: center;
|
||
border-bottom: 1px solid #eef2f7;
|
||
}
|
||
|
||
.payment-notice-popup__title {
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
color: #172033;
|
||
}
|
||
|
||
.payment-notice-popup__body {
|
||
flex: 1;
|
||
padding: 28rpx 32rpx 32rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.payment-notice-popup__summary {
|
||
padding: 28rpx;
|
||
border-radius: 24rpx;
|
||
background: linear-gradient(180deg, rgba(248, 251, 255, 0.98) 0%, rgba(238, 246, 255, 0.98) 100%);
|
||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.payment-notice-popup__summary-row {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.payment-notice-popup__summary-row + .payment-notice-popup__summary-row {
|
||
margin-top: 18rpx;
|
||
}
|
||
|
||
.payment-notice-popup__summary-label {
|
||
flex: 0 0 140rpx;
|
||
font-size: 24rpx;
|
||
color: #6a7a99;
|
||
}
|
||
|
||
.payment-notice-popup__summary-value {
|
||
flex: 1;
|
||
text-align: right;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #172033;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.payment-notice-popup__summary-value--price {
|
||
color: #d9480f;
|
||
}
|
||
|
||
.payment-notice-popup__content {
|
||
margin-top: 24rpx;
|
||
padding: 28rpx;
|
||
border-radius: 24rpx;
|
||
background: #f8fafc;
|
||
}
|
||
|
||
.payment-notice-popup__line {
|
||
font-size: 25rpx;
|
||
line-height: 1.75;
|
||
color: #475569;
|
||
}
|
||
|
||
.payment-notice-popup__line + .payment-notice-popup__line {
|
||
margin-top: 18rpx;
|
||
}
|
||
|
||
.payment-notice-popup__footer {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
|
||
border-top: 1px solid #eef2f7;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.payment-notice-popup__action {
|
||
flex: 1;
|
||
height: 92rpx;
|
||
border-radius: 999rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 28rpx;
|
||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||
}
|
||
|
||
.payment-notice-popup__action:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.payment-notice-popup__action--ghost {
|
||
color: #475569;
|
||
background: #f1f5f9;
|
||
}
|
||
|
||
.payment-notice-popup__action--primary {
|
||
color: #ffffff;
|
||
background: linear-gradient(135deg, rgba($brand-primary, 0.9) 0%, $brand-primary 100%);
|
||
box-shadow: 0 16rpx 34rpx rgba($brand-primary, 0.24);
|
||
}
|
||
|
||
.payment-waiting-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 999;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 32rpx;
|
||
background: rgba(8, 15, 35, 0.46);
|
||
backdrop-filter: blur(12rpx);
|
||
-webkit-backdrop-filter: blur(12rpx);
|
||
}
|
||
|
||
.payment-waiting-panel {
|
||
position: relative;
|
||
width: 100%;
|
||
max-width: 640rpx;
|
||
padding: 40rpx 32rpx 34rpx;
|
||
border-radius: 36rpx;
|
||
overflow: hidden;
|
||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(244, 249, 255, 0.98) 100%);
|
||
box-shadow: 0 30rpx 80rpx rgba(15, 23, 42, 0.18);
|
||
}
|
||
|
||
.payment-waiting-panel--success {
|
||
background: linear-gradient(180deg, rgba(248, 255, 251, 0.98) 0%, rgba(240, 250, 245, 0.98) 100%);
|
||
}
|
||
|
||
.payment-waiting-panel--failed {
|
||
background: linear-gradient(180deg, rgba(255, 250, 250, 0.98) 0%, rgba(255, 244, 244, 0.98) 100%);
|
||
}
|
||
|
||
.payment-waiting-glow {
|
||
position: absolute;
|
||
border-radius: 50%;
|
||
filter: blur(12rpx);
|
||
opacity: 0.8;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.payment-waiting-glow--one {
|
||
top: -56rpx;
|
||
right: -30rpx;
|
||
width: 220rpx;
|
||
height: 220rpx;
|
||
background: radial-gradient(circle, rgba(64, 158, 255, 0.24) 0%, rgba(64, 158, 255, 0) 72%);
|
||
}
|
||
|
||
.payment-waiting-glow--two {
|
||
left: -60rpx;
|
||
bottom: -80rpx;
|
||
width: 260rpx;
|
||
height: 260rpx;
|
||
background: radial-gradient(circle, rgba($brand-primary, 0.18) 0%, rgba($brand-primary, 0) 74%);
|
||
}
|
||
|
||
.payment-waiting-badge {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 10rpx 20rpx;
|
||
border-radius: 999rpx;
|
||
color: $brand-primary;
|
||
background: rgba($brand-primary, 0.08);
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.payment-waiting-badge--success {
|
||
color: #15803d;
|
||
background: rgba(34, 197, 94, 0.12);
|
||
}
|
||
|
||
.payment-waiting-badge--failed {
|
||
color: #c2410c;
|
||
background: rgba(249, 115, 22, 0.12);
|
||
}
|
||
|
||
.payment-waiting-badge__icon {
|
||
margin-right: 8rpx;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.payment-waiting-icon-wrap {
|
||
position: relative;
|
||
z-index: 1;
|
||
width: 180rpx;
|
||
height: 180rpx;
|
||
margin: 28rpx auto 18rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.payment-waiting-icon-ring {
|
||
position: absolute;
|
||
inset: 0;
|
||
border-radius: 50%;
|
||
border: 2rpx solid rgba($brand-primary, 0.12);
|
||
border-top-color: rgba($brand-primary, 0.4);
|
||
border-bottom-color: rgba(76, 190, 255, 0.4);
|
||
animation: paymentRotate 1.8s linear infinite;
|
||
}
|
||
|
||
.payment-waiting-icon-core {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #ffffff;
|
||
background: linear-gradient(135deg, $brand-primary 0%, #68B6FF 100%);
|
||
box-shadow: 0 18rpx 40rpx rgba($brand-primary, 0.28);
|
||
}
|
||
|
||
.payment-waiting-icon-core--success {
|
||
background: linear-gradient(135deg, #22c55e 0%, #34d399 100%);
|
||
box-shadow: 0 18rpx 40rpx rgba(34, 197, 94, 0.24);
|
||
}
|
||
|
||
.payment-waiting-icon-core--failed {
|
||
background: linear-gradient(135deg, #f97316 0%, #fb7185 100%);
|
||
box-shadow: 0 18rpx 40rpx rgba(249, 115, 22, 0.24);
|
||
}
|
||
|
||
.payment-waiting-icon {
|
||
font-size: 52rpx;
|
||
}
|
||
|
||
.payment-waiting-icon--spin {
|
||
animation: paymentRotate 1.1s linear infinite;
|
||
}
|
||
|
||
.payment-waiting-title {
|
||
position: relative;
|
||
z-index: 1;
|
||
text-align: center;
|
||
font-size: 38rpx;
|
||
font-weight: 700;
|
||
color: #172033;
|
||
}
|
||
|
||
.payment-waiting-desc {
|
||
position: relative;
|
||
z-index: 1;
|
||
margin-top: 14rpx;
|
||
text-align: center;
|
||
font-size: 25rpx;
|
||
line-height: 1.7;
|
||
color: #66758f;
|
||
}
|
||
|
||
.payment-progress-card,
|
||
.payment-tips-card {
|
||
position: relative;
|
||
z-index: 1;
|
||
margin-top: 28rpx;
|
||
padding: 24rpx;
|
||
border-radius: 24rpx;
|
||
background: rgba(255, 255, 255, 0.76);
|
||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.payment-progress-head {
|
||
margin-bottom: 18rpx;
|
||
}
|
||
|
||
.payment-progress-label,
|
||
.payment-summary-label,
|
||
.payment-tips-title {
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
color: #5d6c87;
|
||
}
|
||
|
||
.payment-progress-value {
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
color: $brand-primary;
|
||
}
|
||
|
||
.payment-progress-track {
|
||
width: 100%;
|
||
height: 14rpx;
|
||
border-radius: 999rpx;
|
||
overflow: hidden;
|
||
background: rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.payment-progress-bar {
|
||
height: 100%;
|
||
border-radius: inherit;
|
||
background: linear-gradient(90deg, $brand-primary 0%, #78BEFF 100%);
|
||
box-shadow: 0 6rpx 18rpx rgba($brand-primary, 0.22);
|
||
transition: width 0.35s ease;
|
||
}
|
||
|
||
.payment-progress-note {
|
||
margin-top: 16rpx;
|
||
font-size: 24rpx;
|
||
color: #23314d;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.payment-summary-grid {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
gap: 18rpx;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.payment-summary-item {
|
||
flex: 1;
|
||
min-width: 0;
|
||
padding: 24rpx;
|
||
border-radius: 24rpx;
|
||
background: linear-gradient(180deg, rgba(248, 251, 255, 0.96) 0%, rgba(238, 246, 255, 0.96) 100%);
|
||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.payment-summary-value {
|
||
margin-top: 12rpx;
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #1A2233;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.payment-summary-value--mono {
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.payment-summary-value--price {
|
||
color: #d9480f;
|
||
}
|
||
|
||
.payment-tip-line {
|
||
margin-top: 14rpx;
|
||
font-size: 23rpx;
|
||
line-height: 1.7;
|
||
color: #5f6e88;
|
||
}
|
||
|
||
.payment-action-row {
|
||
position: relative;
|
||
z-index: 1;
|
||
margin-top: 28rpx;
|
||
}
|
||
|
||
.payment-action-btn {
|
||
width: 100%;
|
||
height: 92rpx;
|
||
border-radius: 999rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #ffffff;
|
||
background: linear-gradient(135deg, #f97316 0%, #fb7185 100%);
|
||
box-shadow: 0 16rpx 34rpx rgba(249, 115, 22, 0.24);
|
||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||
}
|
||
|
||
.payment-action-btn:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
@keyframes paymentRotate {
|
||
from {
|
||
transform: rotate(0deg);
|
||
}
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
</style>
|