3485 lines
112 KiB
Vue
3485 lines
112 KiB
Vue
<template>
|
||
<view class="my-application-page tn-flex tn-flex-direction-column" :style="{ height: 'calc(100vh - 100rpx - env(safe-area-inset-bottom))', boxSizing: 'border-box', paddingTop: (vuex_custom_bar_height) + 'px' }">
|
||
<view class="page-header tn-padding">
|
||
<view class="tn-text-bold tn-text-xl tn-color-black page-title">{{ $tr('我的申请') }}</view>
|
||
<view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ $tr('您的入住和预约看房申请记录') }}</view>
|
||
</view>
|
||
|
||
<scroll-view v-if="reservationOrders.length || list.length" scroll-y class="list-scroll-view tn-padding" @scrolltolower="loadMore">
|
||
<view v-if="reservationOrders.length" class="reservation-flow-panel tn-margin-bottom-lg" @click.stop>
|
||
<view class="reservation-flow-panel__head">
|
||
<view class="reservation-flow-panel__title-wrap">
|
||
<view class="reservation-flow-panel__title">{{ $tr('预定流程') }}</view>
|
||
<view class="reservation-flow-panel__desc">{{ $tr('当前预定单的流程进度') }}</view>
|
||
</view>
|
||
<view class="reservation-flow-panel__refresh" @click.stop="refreshStatus">
|
||
<text class="tn-text-bold">{{ $tr('刷新流程') }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<scroll-view scroll-x class="reservation-order-scroll" :show-scrollbar="false">
|
||
<view class="reservation-order-tabs">
|
||
<view
|
||
v-for="item in reservationOrders"
|
||
:key="item.orderNo"
|
||
class="reservation-order-chip"
|
||
:class="{ 'reservation-order-chip--active': activeReservationOrderNo === item.orderNo }"
|
||
@click.stop="switchReservationOrder(item)"
|
||
>
|
||
<view class="reservation-order-chip__title">{{ item.roomTypeText || item.roomTypeName || $tr('预定单') }}</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view v-if="activeReservationOrder" class="reservation-grid-info tn-margin-top-lg">
|
||
<view class="grid-info-item">
|
||
<view class="grid-info-label">{{ $tr('所属公寓') }}</view>
|
||
<view class="grid-info-value">{{ activeReservationOrder.areaText || activeReservationOrder.areaName || '--' }}</view>
|
||
</view>
|
||
<view class="grid-info-item">
|
||
<view class="grid-info-label">{{ $tr('房型') }}</view>
|
||
<view class="grid-info-value">{{ activeReservationOrder.roomTypeText || activeReservationOrder.roomTypeName || '--' }}</view>
|
||
</view>
|
||
<view class="grid-info-item">
|
||
<view class="grid-info-label">{{ $tr('房间号') }}</view>
|
||
<view class="grid-info-value tn-font-mono">{{ activeReservationOrder.roomNoText || activeReservationOrder.roomNo || '--' }}</view>
|
||
</view>
|
||
<view class="grid-info-item">
|
||
<view class="grid-info-label">{{ $tr('订单状态') }}</view>
|
||
<view class="grid-info-value">{{ activeReservationOrder.orderStatusDesc || activeReservationOrder.statusText || '--' }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="activeReservationFlowNodes.length" class="reservation-v-steps tn-margin-top-xl">
|
||
<view
|
||
v-for="(node, index) in activeReservationFlowNodes"
|
||
:key="`${activeReservationOrderNo}-${node.step}-${node.sort}`"
|
||
class="v-step-item"
|
||
:class="{
|
||
'v-step-item--current': !!node.isCurrent,
|
||
'v-step-item--last': index === activeReservationFlowNodes.length - 1
|
||
}"
|
||
>
|
||
<view class="v-step-indicator">
|
||
<view class="v-step-badge tn-font-mono">{{ node.sort || node.step }}</view>
|
||
<view v-if="index !== activeReservationFlowNodes.length - 1" class="v-step-line"></view>
|
||
</view>
|
||
<view class="v-step-content">
|
||
<view class="v-step-header">
|
||
<view class="v-step-title">{{ node.stepDesc || getReservationStepLabel(node.step) }}</view>
|
||
<view v-if="node.isCurrent" class="v-step-current-tag">{{ $tr('当前') }}</view>
|
||
</view>
|
||
|
||
<view
|
||
v-if="node.isCurrent && isReservationBillStep(activeReservationOrder) && getActiveReservationBillDetail()"
|
||
class="v-step-bill-card tn-margin-top-sm"
|
||
>
|
||
<view class="v-step-bill-row">
|
||
<text class="v-step-bill-label">{{ $tr('支付说明') }}</text>
|
||
<text class="v-step-bill-value">{{ getReservationBillDisplayTitle(getActiveReservationBillDetail()) }}</text>
|
||
</view>
|
||
<view class="v-step-bill-row">
|
||
<text class="v-step-bill-label">{{ $tr('订单金额') }}</text>
|
||
<text class="v-step-bill-value v-step-bill-value--price">¥{{ formatMoney(getReservationBillDisplayAmount(getActiveReservationBillDetail())) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view
|
||
v-if="node.isCurrent && getReservationCurrentStepHint(activeReservationOrder)"
|
||
class="v-step-hint-card tn-margin-top-sm"
|
||
>
|
||
<text class="v-step-hint-text">{{ getReservationCurrentStepHint(activeReservationOrder) }}</text>
|
||
</view>
|
||
|
||
<view
|
||
v-if="node.isCurrent && isReservationCheckinProcessing(activeReservationOrder)"
|
||
class="v-step-action tn-margin-top-sm"
|
||
>
|
||
<view class="step-refresh-btn" @click.stop="refreshStatus">
|
||
<text class="tn-text-bold">{{ $tr('刷新') }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="node.isCurrent && activeReservationOrder && getCurrentStepActionText(activeReservationOrder)" class="v-step-action tn-margin-top-sm">
|
||
<view class="step-pay-btn" @click.stop="handleReservationOrderStepAction(activeReservationOrder)">
|
||
<text class="tn-text-bold">{{ getCurrentStepActionText(activeReservationOrder) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view
|
||
v-for="(item, index) in list"
|
||
:key="getApplicationKey(item)"
|
||
class="application-card tn-margin-bottom-lg"
|
||
@click="goToApplicationDetail(item)"
|
||
>
|
||
<view class="card-head tn-flex tn-flex-row-between tn-flex-col-center">
|
||
<view class="card-title-wrap tn-flex tn-flex-col-center">
|
||
<view class="title-icon tn-flex tn-flex-col-center tn-flex-row-center tn-margin-right-sm">
|
||
<text :class="item.isViewingApplication ? 'tn-icon-calendar' : 'tn-icon-safe'"></text>
|
||
</view>
|
||
<view class="card-title-block">
|
||
<view class="card-title">{{ getApplicationTitle(item) }}</view>
|
||
</view>
|
||
</view>
|
||
<view :class="['status-badge', 'status-' + resolveStatusType(item)]">
|
||
{{ getDisplayStatusText(item) }}
|
||
</view>
|
||
</view>
|
||
|
||
<view
|
||
v-if="shouldShowApplicationPaymentStatus(item)"
|
||
class="payment-status-banner tn-margin-top-lg"
|
||
:class="`payment-status-banner--${getApplicationPaymentStatusType(item)}`"
|
||
>
|
||
<text class="payment-status-banner__label">{{ $tr('支付状态') }}</text>
|
||
<text class="payment-status-banner__value">{{ getApplicationPaymentStatusText(item) }}</text>
|
||
</view>
|
||
|
||
<view class="card-body tn-margin-top-lg">
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('所属公寓') }}</text>
|
||
<text class="info-value">{{ item.intendedAreaName || '--' }}</text>
|
||
</view>
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('入住人') }}</text>
|
||
<text class="info-value">{{ item.userName || item.phone || '--' }}</text>
|
||
</view>
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ item.isViewingApplication ? $tr('预约时间') : $tr('预计租期') }}</text>
|
||
<text class="info-value">{{ formatDateRange(item.start, item.end) }}</text>
|
||
</view>
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('提交时间') }}</text>
|
||
<text class="info-value">{{ formatDateTime(item.createTime) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="item.remark" class="remark-panel tn-margin-top-lg">
|
||
<view class="remark-line">
|
||
<text class="remark-label">{{ $tr('补充说明') }}</text>
|
||
<text class="remark-text">{{ item.remark }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="getStatusReminderText(item)" class="reminder-panel tn-margin-top-lg" @click.stop>
|
||
<view class="reminder-head">
|
||
<view class="reminder-title">{{ $tr('当前进度提醒') }}</view>
|
||
<view class="reminder-refresh-btn" @click.stop="refreshStatus">
|
||
<text class="tn-text-bold">{{ $tr('刷新') }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('当前进度提醒') }}</text>
|
||
<text class="info-value">{{ getStatusReminderText(item) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="!isPaidApplication(item) && getApplicationPendingPaymentOrder(item)" class="pending-payment-panel tn-margin-top-lg" @click.stop>
|
||
<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 info-value--highlight">¥{{ formatMoney(getApplicationPendingPaymentAmount(item)) }}</text>
|
||
</view>
|
||
<view v-if="getApplicationPendingPaymentExpireText(item)" class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('剩余支付时间') }}</text>
|
||
<text class="info-value" :class="{ 'info-value--expired': isApplicationPendingPaymentExpired(item) }">
|
||
{{ getApplicationPendingPaymentExpireText(item) }}
|
||
</text>
|
||
</view>
|
||
<view v-if="getApplicationPendingPaymentDescription(item)" class="info-row tn-flex tn-flex-row-between">
|
||
<text class="info-label">{{ $tr('支付说明') }}</text>
|
||
<text class="info-value">{{ getApplicationPendingPaymentDescription(item) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card-footer tn-margin-top-xl" @click.stop>
|
||
<view
|
||
v-if="canCancelOrder(item)"
|
||
class="cancel-btn"
|
||
:class="{ 'cancel-btn--loading': cancelingReferenceTypeNo === resolveReferenceTypeNo(item) }"
|
||
:data-reference-type-no="resolveReferenceTypeNo(item)"
|
||
@click.stop="cancelOrderAction"
|
||
>
|
||
<text class="tn-text-bold">{{ getCancelButtonText(item) }}</text>
|
||
</view>
|
||
|
||
<view
|
||
v-if="getCurrentStepActionText(item)"
|
||
class="pay-btn"
|
||
:data-reference-type-no="resolveReferenceTypeNo(item)"
|
||
:data-order-no="getReservationOrderNoFromItem(item)"
|
||
:data-ref-no="item.reservationCurrentNode && item.reservationCurrentNode.refNo ? item.reservationCurrentNode.refNo : ''"
|
||
:data-step="getReservationCurrentStepValue(item)"
|
||
@click.stop="handleCurrentStepAction"
|
||
>
|
||
<text class="tn-text-bold">{{ getCurrentStepActionText(item) }}</text>
|
||
</view>
|
||
<view
|
||
v-else-if="!isPaidApplication(item) && getApplicationPendingPaymentOrder(item)"
|
||
class="pay-btn"
|
||
:data-reference-type-no="resolveReferenceTypeNo(item)"
|
||
@click.stop="continuePendingPaymentAction"
|
||
>
|
||
<text class="tn-text-bold">{{ getPendingPaymentActionText(item) }}</text>
|
||
</view>
|
||
<view
|
||
v-else-if="canViewBill(item)"
|
||
class="pay-btn"
|
||
:data-reference-type-no="resolveReferenceTypeNo(item)"
|
||
@click.stop="payDepositAction"
|
||
>
|
||
<text class="tn-text-bold">{{ $tr('去支付') }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="hasMore || loadingMore" class="load-more-item tn-flex tn-flex-direction-column tn-flex-col-center tn-flex-row-center tn-padding-bottom">
|
||
<tn-loading v-if="loadingMore" mode="circle" color="#00BBE8"></tn-loading>
|
||
<text v-else-if="hasMore" class="tn-color-gray">{{ $tr('上拉加载更多') }}</text>
|
||
<text v-else class="tn-color-gray">{{ $tr('没有更多申请了') }}</text>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view v-else-if="!loading" class="empty-panel tn-flex tn-flex-direction-column tn-flex-col-center tn-flex-row-center animation-fade-in tn-padding-left tn-padding-right">
|
||
<tn-empty icon="order" :text="$tr('暂无申请记录')"></tn-empty>
|
||
<view class="empty-desc tn-margin-top tn-text-center">{{ $tr('您还没有提交任何申请,去选房后可发起入住申请或预约看房。') }}</view>
|
||
<view class="empty-action tn-margin-top-xl" @click="goSelectRoom">
|
||
<text class="tn-text-bold">{{ $tr('去选房') }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="loading && !list.length" class="empty-panel tn-flex tn-flex-direction-column tn-flex-col-center tn-flex-row-center tn-padding-left tn-padding-right">
|
||
<tn-loading mode="circle" color="#00BBE8" size="60"></tn-loading>
|
||
<view class="empty-desc tn-margin-top-lg">{{ $tr('正在同步您的入住申请记录,请稍候。') }}</view>
|
||
</view>
|
||
|
||
<!-- 支付确认弹窗 -->
|
||
<tn-popup v-model="showDepositNotice" mode="bottom" length="80%" :borderRadius="32" :closeBtn="true" @close="closeDepositNotice">
|
||
<view class="deposit-notice-popup">
|
||
<view class="popup-header">
|
||
<view class="popup-handle"></view>
|
||
<view class="popup-title">{{ getPaymentNoticeTitle() }}</view>
|
||
</view>
|
||
<view class="popup-body">
|
||
<view class="notice-content">
|
||
<view class="payment-sheet">
|
||
<view class="payment-hero">
|
||
<view class="payment-hero-label">{{ $tr('本次支付') }}</view>
|
||
<view class="payment-hero-amount">{{ getPaymentNoticeBillAmountDisplay() }}</view>
|
||
<view class="payment-hero-hint">{{ $tr('确认后将发起微信支付') }}</view>
|
||
</view>
|
||
<view v-if="paymentNoticeSourceLoading || paymentNoticeBillLoading" class="payment-sync-group">
|
||
<view v-if="paymentNoticeSourceLoading" class="payment-sync-chip">
|
||
{{ $tr('正在同步申请详情,请稍候...') }}
|
||
</view>
|
||
<view v-if="paymentNoticeBillLoading" class="payment-sync-chip">
|
||
{{ $tr('正在同步支付预览,请稍候...') }}
|
||
</view>
|
||
</view>
|
||
<view v-if="getPaymentNoticeDetailRows().length" class="payment-detail-panel">
|
||
<view class="payment-detail-header">
|
||
<view class="payment-detail-title">{{ $tr('入住申请信息') }}</view>
|
||
</view>
|
||
<view class="payment-detail-list">
|
||
<view
|
||
v-for="item in getPaymentNoticeDetailRows()"
|
||
:key="item.label"
|
||
class="payment-detail-row"
|
||
>
|
||
<text class="payment-detail-label">{{ item.label }}</text>
|
||
<text class="payment-detail-value">{{ item.value }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="popup-footer">
|
||
<view class="notice-action-btn" @click="confirmAndPayDeposit">
|
||
<text class="tn-text-bold">{{ getPaymentNoticeConfirmText() }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</tn-popup>
|
||
|
||
<tn-action-sheet
|
||
v-model="showPaymentMethodSheet"
|
||
:list="getPaymentMethodSheetOptions()"
|
||
:tips="{ text: getPaymentMethodSheetTipText() }"
|
||
:borderRadius="32"
|
||
:safeAreaInsetBottom="true"
|
||
@click="handlePaymentMethodSheetClick"
|
||
></tn-action-sheet>
|
||
|
||
<deposit-other-payment-popup
|
||
v-model="showOfflinePaymentNotice"
|
||
:config="depositOtherPaymentConfig"
|
||
:loading="depositOtherPaymentConfigLoading"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import DepositOtherPaymentPopup from '@/components/DepositOtherPaymentPopup.vue'
|
||
import { applicationFormDetail, applicationFormMePage, accountQueryBill, accountQueryBillPreview, accountQueryBills, contractDetail, orderSearchedDetail, orderSearchedMePage, orderSearchedReservationFlow, paymentBillPay, paymentPayingBills, roomInspectionDetail, userDetailCancel } from '@/api/index.js'
|
||
import { getMyApplicationList, saveActiveApplicationCode, saveMyApplicationList } from '@/utils/app-state'
|
||
import { createDefaultDepositOtherPaymentConfig, loadDepositOtherPaymentConfig } from '@/utils/deposit-other-payment'
|
||
import {
|
||
RESERVATION_FLOW_STEP,
|
||
attachReservationFlow,
|
||
buildReservationFlowMap,
|
||
getReservationCurrentActionText,
|
||
getReservationCurrentStepText,
|
||
getReservationStepMeta,
|
||
getReservationOrderNo,
|
||
parseReservationFlowResponse
|
||
} from '@/utils/reservation-flow'
|
||
import { getOrderStatusCode, normalizeBookingDetail, normalizeBookingList, parseOrderDetailResponse, parseOrderListResponse } from '@/utils/booking-helper'
|
||
import { localizePayMethodText } from '@/utils/select-room-normalizers'
|
||
|
||
function formatPageResponse(response) {
|
||
const payload = response && response.data ? response.data : response
|
||
return payload && typeof payload === 'object' ? payload : {}
|
||
}
|
||
|
||
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 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 parseJsonStringMaybe(value) {
|
||
if (typeof value !== 'string') {
|
||
return value
|
||
}
|
||
const text = value.trim()
|
||
if (!text) {
|
||
return value
|
||
}
|
||
if (!((text.startsWith('{') && text.endsWith('}')) || (text.startsWith('[') && text.endsWith(']')))) {
|
||
return value
|
||
}
|
||
try {
|
||
return JSON.parse(text)
|
||
} catch (error) {
|
||
return value
|
||
}
|
||
}
|
||
|
||
function padCountdownUnit(value) {
|
||
return String(Math.max(0, Number(value) || 0)).padStart(2, '0')
|
||
}
|
||
|
||
function resolveApplicationPaymentStatusInfo(item = {}) {
|
||
if (!item || item.isViewingApplication) {
|
||
return {
|
||
key: 'exempt',
|
||
desc: ''
|
||
}
|
||
}
|
||
|
||
const desc = String(item.payStatusDesc || item.paymentStatusDesc || '').trim()
|
||
const status = Number(
|
||
item.payStatus !== undefined && item.payStatus !== null
|
||
? item.payStatus
|
||
: item.paymentStatus
|
||
)
|
||
|
||
if (status === 10 || /已支付|paid/i.test(desc)) {
|
||
return { key: 'paid', desc }
|
||
}
|
||
if (/退款|refunded?/i.test(desc)) {
|
||
return { key: 'refunded', desc }
|
||
}
|
||
if (/失败|failed?/i.test(desc)) {
|
||
return { key: 'failed', desc }
|
||
}
|
||
if (/处理中|processing/i.test(desc)) {
|
||
return { key: 'processing', desc }
|
||
}
|
||
if (status === 0 || /待支付|未支付|unpaid|pending/i.test(desc)) {
|
||
return { key: 'pending', desc }
|
||
}
|
||
|
||
return {
|
||
key: desc ? 'processing' : 'pending',
|
||
desc
|
||
}
|
||
}
|
||
|
||
export default {
|
||
components: {
|
||
DepositOtherPaymentPopup
|
||
},
|
||
data() {
|
||
return {
|
||
currentIndex: 0,
|
||
status: 0,
|
||
pageIndex: 1,
|
||
pageSize: 10,
|
||
total: 0,
|
||
list: [],
|
||
loading: false,
|
||
loadingMore: false,
|
||
hasMore: true,
|
||
showDepositNotice: false,
|
||
showPaymentMethodSheet: false,
|
||
showOfflinePaymentNotice: false,
|
||
pendingDepositReferenceTypeNo: '',
|
||
pendingPaymentMethodContext: null,
|
||
pendingDepositPaymentContext: null,
|
||
paymentNoticeSourceItem: null,
|
||
paymentNoticeSourceLoading: false,
|
||
paymentNoticeBillLoading: false,
|
||
paymentNoticeBillDetail: null,
|
||
depositOtherPaymentConfig: createDefaultDepositOtherPaymentConfig(),
|
||
depositOtherPaymentConfigLoading: false,
|
||
reservationBillDetailMap: {},
|
||
reservationContractDetailMap: {},
|
||
reservationInspectionDetailMap: {},
|
||
reservationOrders: [],
|
||
activeReservationOrderNo: '',
|
||
countdownNow: Date.now(),
|
||
cancelingReferenceTypeNo: ''
|
||
}
|
||
},
|
||
watch: {
|
||
language() {
|
||
this.reloadList(true)
|
||
this.loadDepositOtherPaymentConfig()
|
||
}
|
||
},
|
||
computed: {
|
||
language() {
|
||
return this.$getLanguage()
|
||
},
|
||
activeReservationOrder() {
|
||
const orderList = Array.isArray(this.reservationOrders) ? this.reservationOrders : []
|
||
if (!orderList.length) {
|
||
return null
|
||
}
|
||
return orderList.find(item => item.orderNo === this.activeReservationOrderNo) || orderList[0]
|
||
},
|
||
activeReservationFlowNodes() {
|
||
const order = this.activeReservationOrder
|
||
const nodes = order && order.reservationFlow && Array.isArray(order.reservationFlow.flowNodes)
|
||
? order.reservationFlow.flowNodes
|
||
: []
|
||
return [...nodes].sort((a, b) => Number(a.sort || 0) - Number(b.sort || 0))
|
||
}
|
||
},
|
||
onShow() {
|
||
this.load()
|
||
this.loadDepositOtherPaymentConfig()
|
||
},
|
||
mounted() {
|
||
this.startCountdownTimer()
|
||
},
|
||
beforeDestroy() {
|
||
this.clearCountdownTimer()
|
||
},
|
||
methods: {
|
||
load(force = false) {
|
||
return this.reloadList(force)
|
||
},
|
||
getReservationStepLabel(step) {
|
||
return getReservationStepMeta(step, this.language).label
|
||
},
|
||
switchReservationOrder(item = {}) {
|
||
const orderNo = String(item.orderNo || '').trim()
|
||
if (!orderNo) {
|
||
return
|
||
}
|
||
this.activeReservationOrderNo = orderNo
|
||
this.ensureReservationBillDetail(item)
|
||
this.ensureReservationInspectionDetail(item)
|
||
this.ensureReservationContractDetail(item)
|
||
},
|
||
getStatusText(status) {
|
||
const map = {
|
||
0: this.$tr('待审核'),
|
||
10: this.$tr('已通过'),
|
||
20: this.$tr('已拒绝'),
|
||
99: this.$tr('已结束')
|
||
}
|
||
return map[status] || this.$tr('处理中')
|
||
},
|
||
getDisplayStatusText(item = {}) {
|
||
if (this.$getLanguage() !== 'en' && item.statusDesc) {
|
||
return item.statusDesc
|
||
}
|
||
return this.getStatusText(item.status)
|
||
},
|
||
resolveStatusType(source = {}) {
|
||
const item = source && typeof source === 'object'
|
||
? source
|
||
: { status: source }
|
||
const status = Number(item.status)
|
||
if (status === 10) {
|
||
return 'approved'
|
||
}
|
||
if (status === 20) {
|
||
return 'rejected'
|
||
}
|
||
if (status === 99) {
|
||
return 'finished'
|
||
}
|
||
return 'pending'
|
||
},
|
||
isPaidApplication(item = {}) {
|
||
return !item.isViewingApplication && Number(item.payStatus) === 10
|
||
},
|
||
shouldShowApplicationPaymentStatus(item = {}) {
|
||
return !item.isViewingApplication
|
||
},
|
||
getApplicationPaymentStatusText(item = {}) {
|
||
if (this.isWaitingButlerRoomAssignment(item)) {
|
||
return this.$tr('等待管家排房')
|
||
}
|
||
const statusInfo = resolveApplicationPaymentStatusInfo(item)
|
||
if (this.$getLanguage() !== 'en' && statusInfo.desc) {
|
||
return statusInfo.desc
|
||
}
|
||
const textMap = {
|
||
exempt: this.$tr('无需支付'),
|
||
paid: this.$tr('已支付'),
|
||
refunded: this.$tr('已退款'),
|
||
failed: this.$tr('支付失败'),
|
||
processing: this.$tr('处理中'),
|
||
pending: this.$tr('待支付')
|
||
}
|
||
return textMap[statusInfo.key] || statusInfo.desc || this.$tr('待支付')
|
||
},
|
||
getApplicationPaymentStatusType(item = {}) {
|
||
if (this.isWaitingButlerRoomAssignment(item)) {
|
||
return 'info'
|
||
}
|
||
const statusInfo = resolveApplicationPaymentStatusInfo(item)
|
||
const typeMap = {
|
||
exempt: 'neutral',
|
||
paid: 'success',
|
||
refunded: 'neutral',
|
||
failed: 'danger',
|
||
processing: 'info',
|
||
pending: 'warning'
|
||
}
|
||
return typeMap[statusInfo.key] || 'warning'
|
||
},
|
||
shouldPayDeposit(item = {}) {
|
||
return !item.isViewingApplication
|
||
&& Number(item.status) === 0
|
||
&& Number(item.payStatus) !== 10
|
||
},
|
||
shouldQueryPendingDepositBill(item = {}) {
|
||
return this.shouldPayDeposit(item) && !!this.resolveReferenceTypeNo(item)
|
||
},
|
||
hasResolvedPendingDepositBill(item = {}) {
|
||
return !!(item && item.pendingDepositBillQueried)
|
||
},
|
||
hasPendingDepositBill(item = {}) {
|
||
return !!String(this.getBillNo(item && item.pendingDepositBill ? item.pendingDepositBill : {}) || '').trim()
|
||
},
|
||
isWaitingButlerRoomAssignment(item = {}) {
|
||
return this.shouldQueryPendingDepositBill(item)
|
||
&& this.hasResolvedPendingDepositBill(item)
|
||
&& !this.hasPendingDepositBill(item)
|
||
},
|
||
isWaitingRoomAssignment(item = {}) {
|
||
return !item.isViewingApplication
|
||
&& Number(item.status) === 0
|
||
&& (Number(item.payStatus) === 10 || this.isWaitingButlerRoomAssignment(item))
|
||
},
|
||
getStatusReminderText(item = {}) {
|
||
if (this.isReservationContractAwaitingReview(item)) {
|
||
const contractDetail = this.getReservationContractDetail(item) || {}
|
||
return contractDetail.queryStatusDesc || contractDetail.approvalStatusDesc || contractDetail.statusDesc || getReservationCurrentStepText(item, this.language)
|
||
}
|
||
if (this.isReservationInspectionPendingReview(item)) {
|
||
return this.$tr('验房已完成,等待宿管审核')
|
||
}
|
||
const reservationStepText = getReservationCurrentStepText(item, this.language)
|
||
if (reservationStepText) {
|
||
return reservationStepText
|
||
}
|
||
if (this.isWaitingButlerRoomAssignment(item)) {
|
||
return this.$tr('等待管家排房')
|
||
}
|
||
if (this.shouldPayDeposit(item)) {
|
||
return this.$tr('请支付定金')
|
||
}
|
||
if (this.isWaitingRoomAssignment(item)) {
|
||
return this.$tr('等待管家排房')
|
||
}
|
||
return ''
|
||
},
|
||
formatDateTime(value) {
|
||
if (!value) {
|
||
return '--'
|
||
}
|
||
const text = String(value).replace('T', ' ')
|
||
return text.length > 16 ? text.slice(0, 16) : text
|
||
},
|
||
formatDate(value) {
|
||
if (!value) {
|
||
return '--'
|
||
}
|
||
return String(value).slice(0, 10)
|
||
},
|
||
formatDateRange(start, end) {
|
||
if (!start && !end) {
|
||
return '--'
|
||
}
|
||
if (start && end) {
|
||
const startText = this.formatDateTime(start)
|
||
const endText = this.formatDateTime(end)
|
||
if (String(start).includes('T') || String(end).includes('T')) {
|
||
return `${startText}${this.$tr(' 至 ')}${endText}`
|
||
}
|
||
}
|
||
return `${this.formatDate(start)}${this.$tr(' 至 ')}${this.formatDate(end)}`
|
||
},
|
||
formatMoney(value) {
|
||
const amount = Number(value || 0)
|
||
return amount.toFixed(2)
|
||
},
|
||
getBillNo(item = {}) {
|
||
return item.no || item.billNo || ''
|
||
},
|
||
normalizeBillList(response) {
|
||
const payload = parseJsonStringMaybe(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 []
|
||
},
|
||
normalizeBillDetail(response) {
|
||
const payload = parseJsonStringMaybe(response && response.data ? response.data : response)
|
||
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
||
return payload
|
||
}
|
||
if (payload && payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) {
|
||
return payload.data
|
||
}
|
||
return {}
|
||
},
|
||
normalizeApplicationDetail(response) {
|
||
const payload = parseJsonStringMaybe(response && response.data ? response.data : response)
|
||
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
||
return payload
|
||
}
|
||
if (payload && payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) {
|
||
return payload.data
|
||
}
|
||
return {}
|
||
},
|
||
async resolveApplicationDetailPaymentContext(referenceTypeNo = '') {
|
||
const normalizedReferenceTypeNo = String(referenceTypeNo || '').trim()
|
||
if (!normalizedReferenceTypeNo) {
|
||
return null
|
||
}
|
||
|
||
const detail = this.normalizeApplicationDetail(await applicationFormDetail(normalizedReferenceTypeNo, this.language))
|
||
if (!detail || !Object.keys(detail).length) {
|
||
return null
|
||
}
|
||
|
||
const normalizedDetail = {
|
||
...detail,
|
||
isViewingApplication: isViewingApplicationRecord(detail)
|
||
}
|
||
const detailBillNo = String(normalizedDetail.billNo || normalizedDetail.BillNo || '').trim()
|
||
const detailCode = String(normalizedDetail.detailCode || normalizedReferenceTypeNo).trim()
|
||
|
||
if (Array.isArray(this.list) && this.list.length) {
|
||
const nextList = this.list.map((item) => {
|
||
if (String(this.resolveReferenceTypeNo(item)) !== normalizedReferenceTypeNo) {
|
||
return item
|
||
}
|
||
return {
|
||
...item,
|
||
...normalizedDetail,
|
||
detailCode: detailCode || item.detailCode || normalizedReferenceTypeNo,
|
||
billNo: detailBillNo || item.billNo || ''
|
||
}
|
||
})
|
||
this.list = normalizeApplicationList(nextList)
|
||
saveMyApplicationList(this.list)
|
||
}
|
||
|
||
return {
|
||
referenceTypeNo: detailCode || normalizedReferenceTypeNo,
|
||
billNo: detailBillNo,
|
||
isViewingApplication: !!normalizedDetail.isViewingApplication,
|
||
detail: normalizedDetail
|
||
}
|
||
},
|
||
normalizeContractDetail(response) {
|
||
const payload = parseJsonStringMaybe(response && response.data ? response.data : response)
|
||
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
||
return payload
|
||
}
|
||
if (payload && payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) {
|
||
return payload.data
|
||
}
|
||
return {}
|
||
},
|
||
normalizeInspectionDetail(response) {
|
||
const payload = parseJsonStringMaybe(response && response.data ? response.data : response)
|
||
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
||
return payload
|
||
}
|
||
if (payload && payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) {
|
||
return payload.data
|
||
}
|
||
return {}
|
||
},
|
||
getBillPayRequests(bill = {}) {
|
||
return (Array.isArray(bill.payRequests) ? bill.payRequests : [])
|
||
.filter(item => item && typeof item === 'object')
|
||
.sort((prev, next) => new Date(next.createTime || 0).getTime() - new Date(prev.createTime || 0).getTime())
|
||
},
|
||
buildPendingPaymentOrderFromBillDetail(bill = {}) {
|
||
const payRequest = this.getBillPayRequests(bill).find((item) => {
|
||
return !item.paidTime
|
||
})
|
||
if (!payRequest) {
|
||
return null
|
||
}
|
||
return {
|
||
amount: Number(payRequest.amount || bill.remainingAmount || bill.totalCharge || 0),
|
||
expireTime: payRequest.expireTime || '',
|
||
payRequestNo: payRequest.no || '',
|
||
paymentNo: payRequest.no || '',
|
||
cashier: {
|
||
paymentInfo: payRequest.paymentInfo || '',
|
||
expireTime: payRequest.expireTime || ''
|
||
}
|
||
}
|
||
},
|
||
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: resolve,
|
||
fail: reject
|
||
})
|
||
})
|
||
},
|
||
getWechatLoginCode() {
|
||
return new Promise((resolve, reject) => {
|
||
uni.login({
|
||
provider: 'weixin',
|
||
success: (res) => {
|
||
if (!res || !res.code) {
|
||
reject(new Error(this.$tr('获取微信登录凭证失败')))
|
||
return
|
||
}
|
||
resolve(res.code)
|
||
},
|
||
fail: reject
|
||
})
|
||
})
|
||
},
|
||
normalizePayingBills(response) {
|
||
const payload = parseJsonStringMaybe(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 []
|
||
},
|
||
async getPendingPaymentOrder(billNo = '') {
|
||
const normalizedBillNo = String(billNo || '').trim()
|
||
if (!normalizedBillNo) {
|
||
return null
|
||
}
|
||
const response = await paymentPayingBills({
|
||
BillNo: normalizedBillNo
|
||
})
|
||
const orderList = this.normalizePayingBills(response)
|
||
return orderList.length ? orderList[0] : null
|
||
},
|
||
getApplicationPendingPaymentOrder(item = {}) {
|
||
return item && item.pendingPaymentOrder ? item.pendingPaymentOrder : null
|
||
},
|
||
getPendingPaymentOrderNo(order = {}) {
|
||
const cashier = order && order.cashier ? order.cashier : {}
|
||
return String(
|
||
order.payRequestNo
|
||
|| order.payNo
|
||
|| order.paymentNo
|
||
|| cashier.payRequestNo
|
||
|| cashier.payNo
|
||
|| cashier.paymentNo
|
||
|| ''
|
||
).trim()
|
||
},
|
||
getApplicationPendingPaymentOrderNo(item = {}) {
|
||
const order = this.getApplicationPendingPaymentOrder(item)
|
||
return order ? (this.getPendingPaymentOrderNo(order) || '--') : '--'
|
||
},
|
||
getApplicationPendingPaymentAmount(item = {}) {
|
||
const order = this.getApplicationPendingPaymentOrder(item)
|
||
const bill = item && item.pendingDepositBill ? item.pendingDepositBill : {}
|
||
if (order && (order.amount || order.amount === 0)) {
|
||
return order.amount
|
||
}
|
||
return bill.remainingAmount || bill.amount || 0
|
||
},
|
||
getApplicationPendingPaymentDescription(item = {}) {
|
||
const order = this.getApplicationPendingPaymentOrder(item)
|
||
return order ? (order.description || '') : ''
|
||
},
|
||
syncReservationPendingPaymentOrders(sourceList = []) {
|
||
const baseList = Array.isArray(sourceList) ? sourceList : []
|
||
const paymentContextMap = baseList.reduce((result, item) => {
|
||
const orderNo = String(getReservationOrderNo(item) || '').trim()
|
||
const pendingPaymentOrder = this.getApplicationPendingPaymentOrder(item)
|
||
const pendingDepositBill = item && item.pendingDepositBill ? item.pendingDepositBill : null
|
||
if (!orderNo || !pendingPaymentOrder) {
|
||
return result
|
||
}
|
||
result[orderNo] = {
|
||
pendingPaymentOrder,
|
||
pendingDepositBill
|
||
}
|
||
return result
|
||
}, {})
|
||
|
||
if (!Object.keys(paymentContextMap).length || !Array.isArray(this.reservationOrders) || !this.reservationOrders.length) {
|
||
return
|
||
}
|
||
|
||
this.reservationOrders = this.reservationOrders.map((item) => {
|
||
const orderNo = String(item && item.orderNo ? item.orderNo : '').trim()
|
||
const matchedContext = orderNo ? paymentContextMap[orderNo] : null
|
||
if (!matchedContext) {
|
||
return item
|
||
}
|
||
return {
|
||
...item,
|
||
pendingPaymentOrder: matchedContext.pendingPaymentOrder || null,
|
||
pendingDepositBill: matchedContext.pendingDepositBill || null
|
||
}
|
||
})
|
||
},
|
||
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(order = {}) {
|
||
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
|
||
},
|
||
isApplicationPendingPaymentExpired(item = {}) {
|
||
const order = this.getApplicationPendingPaymentOrder(item)
|
||
if (!order) {
|
||
return false
|
||
}
|
||
const expireTimestamp = this.getPendingPaymentExpireTimestamp(order)
|
||
if (!expireTimestamp) {
|
||
return false
|
||
}
|
||
return expireTimestamp <= this.countdownNow
|
||
},
|
||
getApplicationPendingPaymentExpireText(item = {}) {
|
||
const order = this.getApplicationPendingPaymentOrder(item)
|
||
if (!order) {
|
||
return ''
|
||
}
|
||
const expireTimestamp = this.getPendingPaymentExpireTimestamp(order)
|
||
if (!expireTimestamp) {
|
||
return ''
|
||
}
|
||
if (expireTimestamp <= this.countdownNow) {
|
||
return this.$tr('已失效')
|
||
}
|
||
return this.formatPendingPaymentCountdown(this.getPendingPaymentRemainingSeconds(order))
|
||
},
|
||
getPendingPaymentActionText(item = {}) {
|
||
return this.$tr('去支付')
|
||
},
|
||
getReservationOrderNoFromItem(item = {}) {
|
||
return getReservationOrderNo(item)
|
||
},
|
||
getReservationCurrentStepValue(item = {}) {
|
||
return Number(item && item.reservationCurrentNode ? item.reservationCurrentNode.step : 0)
|
||
},
|
||
getApplicationDetailCodeFromOrder(order = {}) {
|
||
const flowNodes = order && order.reservationFlow && Array.isArray(order.reservationFlow.flowNodes)
|
||
? order.reservationFlow.flowNodes
|
||
: []
|
||
const applicationNode = [...flowNodes]
|
||
.filter(item => item && typeof item === 'object' && Number(item.step || 0) === RESERVATION_FLOW_STEP.APPLICATION)
|
||
.sort((prev, next) => Number(prev.sort || 0) - Number(next.sort || 0))[0] || null
|
||
|
||
return String(
|
||
(applicationNode && (applicationNode.detailCode || applicationNode.code || applicationNode.refNo))
|
||
|| order.detailCode
|
||
|| ''
|
||
).trim()
|
||
},
|
||
isReservationBillStep(item = {}) {
|
||
return this.getReservationCurrentStepValue(item) === RESERVATION_FLOW_STEP.BILL
|
||
},
|
||
isReservationCheckinStep(item = {}) {
|
||
return this.getReservationCurrentStepValue(item) === RESERVATION_FLOW_STEP.CHECKIN
|
||
},
|
||
isReservationInspectionStep(item = {}) {
|
||
return this.getReservationCurrentStepValue(item) === RESERVATION_FLOW_STEP.INSPECTION
|
||
},
|
||
getReservationInspectionNo(order = {}) {
|
||
const currentNode = order && order.reservationCurrentNode ? order.reservationCurrentNode : {}
|
||
if (Number(currentNode.step || 0) !== RESERVATION_FLOW_STEP.INSPECTION) {
|
||
return ''
|
||
}
|
||
return String(currentNode.refNo || '').trim()
|
||
},
|
||
getReservationContractNo(order = {}) {
|
||
const currentNode = order && order.reservationCurrentNode ? order.reservationCurrentNode : {}
|
||
if (Number(currentNode.step || 0) !== RESERVATION_FLOW_STEP.CONTRACT) {
|
||
return ''
|
||
}
|
||
return String(currentNode.refNo || '').trim()
|
||
},
|
||
getReservationBillNo(order = {}) {
|
||
const currentNode = order && order.reservationCurrentNode ? order.reservationCurrentNode : {}
|
||
if (Number(currentNode.step || 0) !== RESERVATION_FLOW_STEP.BILL) {
|
||
return ''
|
||
}
|
||
return String(
|
||
currentNode.billNo
|
||
|| currentNode.BillNo
|
||
|| currentNode.accountBillNo
|
||
|| currentNode.paymentBillNo
|
||
|| currentNode.chargeBillNo
|
||
|| currentNode.refNo
|
||
|| order.billNo
|
||
|| ''
|
||
).trim()
|
||
},
|
||
getReservationBillDetailKey(order = {}) {
|
||
return this.getReservationBillNo(order)
|
||
},
|
||
getReservationContractDetail(order = {}) {
|
||
const contractNo = this.getReservationContractNo(order)
|
||
return contractNo ? (this.reservationContractDetailMap[contractNo] || null) : null
|
||
},
|
||
getReservationInspectionDetail(order = {}) {
|
||
const inspectionNo = this.getReservationInspectionNo(order)
|
||
return inspectionNo ? (this.reservationInspectionDetailMap[inspectionNo] || null) : null
|
||
},
|
||
isReservationContractAwaitingReview(order = {}) {
|
||
const detail = this.getReservationContractDetail(order)
|
||
if (!detail) {
|
||
return false
|
||
}
|
||
const queryStatus = Number(detail.queryStatus)
|
||
const approvalStatus = Number(detail.approvalStatus)
|
||
const hasSigned = !!String(detail.signTime || detail.signImageUrl || '').trim()
|
||
return queryStatus === 1 || (hasSigned && approvalStatus === 0)
|
||
},
|
||
isReservationInspectionPendingReview(order = {}) {
|
||
if (!this.isReservationInspectionStep(order)) {
|
||
return false
|
||
}
|
||
const detail = this.getReservationInspectionDetail(order)
|
||
if (!detail) {
|
||
return false
|
||
}
|
||
return Number(detail.status) === 2
|
||
},
|
||
getReservationCurrentStepHint(order = {}) {
|
||
if (this.isReservationInspectionPendingReview(order)) {
|
||
return this.$tr('验房已完成,等待宿管审核')
|
||
}
|
||
if (this.isReservationContractAwaitingReview(order)) {
|
||
const detail = this.getReservationContractDetail(order) || {}
|
||
return String(detail.queryStatusDesc || detail.approvalStatusDesc || '').trim() || this.$tr('合同已签,等待宿管审核')
|
||
}
|
||
if (this.isReservationCheckinProcessing(order)) {
|
||
return this.$tr('宿管正在办理入住,请耐心等待')
|
||
}
|
||
return ''
|
||
},
|
||
isReservationCheckinProcessing(order = {}) {
|
||
return this.isReservationCheckinStep(order)
|
||
},
|
||
hasReservationCurrentActionRef(item = {}) {
|
||
const currentNode = item && item.reservationCurrentNode ? item.reservationCurrentNode : {}
|
||
const step = Number(currentNode.step || 0)
|
||
if (!step) {
|
||
return false
|
||
}
|
||
if (step === RESERVATION_FLOW_STEP.BILL) {
|
||
return !!this.getReservationBillNo(item)
|
||
}
|
||
if (step === RESERVATION_FLOW_STEP.INSPECTION) {
|
||
if (this.isReservationInspectionPendingReview(item)) {
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
if (step === RESERVATION_FLOW_STEP.CONTRACT) {
|
||
return !!this.getReservationContractNo(item) && !this.isReservationContractAwaitingReview(item)
|
||
}
|
||
if (step === RESERVATION_FLOW_STEP.CHECKIN) {
|
||
return false
|
||
}
|
||
return true
|
||
},
|
||
getActiveReservationBillDetail() {
|
||
const order = this.activeReservationOrder
|
||
const detailKey = this.getReservationBillDetailKey(order)
|
||
return detailKey ? (this.reservationBillDetailMap[detailKey] || null) : null
|
||
},
|
||
getReservationBillDisplayTitle(bill = {}) {
|
||
return String(
|
||
bill.paymentFeeType
|
||
|| bill.remark
|
||
|| bill.createTypeDesc
|
||
|| bill.referenceTypeDesc
|
||
|| bill.billName
|
||
|| bill.name
|
||
|| this.$tr('支付定金')
|
||
).trim()
|
||
},
|
||
getReservationBillDisplayAmount(bill = {}) {
|
||
const amount = bill.remainingAmount
|
||
if (amount !== '' && amount !== null && amount !== undefined) {
|
||
return amount
|
||
}
|
||
return bill.totalCharge || bill.amount || 0
|
||
},
|
||
async ensureReservationBillDetail(order = {}) {
|
||
const billNo = this.getReservationBillNo(order)
|
||
if (!billNo || this.reservationBillDetailMap[billNo]) {
|
||
return this.reservationBillDetailMap[billNo] || null
|
||
}
|
||
try {
|
||
const detail = this.normalizeBillDetail(await accountQueryBill({
|
||
billNo
|
||
}, this.language))
|
||
if (detail && Object.keys(detail).length) {
|
||
this.$set(this.reservationBillDetailMap, billNo, detail)
|
||
return detail
|
||
}
|
||
} catch (error) {
|
||
console.log('[MyApplicationTab] load reservation bill detail failed', billNo, error)
|
||
}
|
||
return null
|
||
},
|
||
async ensureReservationInspectionDetail(order = {}) {
|
||
const inspectionNo = this.getReservationInspectionNo(order)
|
||
if (!inspectionNo || this.reservationInspectionDetailMap[inspectionNo]) {
|
||
return this.reservationInspectionDetailMap[inspectionNo] || null
|
||
}
|
||
try {
|
||
const detail = this.normalizeInspectionDetail(await roomInspectionDetail({
|
||
inspectionNo
|
||
}, this.language))
|
||
if (detail && Object.keys(detail).length) {
|
||
this.$set(this.reservationInspectionDetailMap, inspectionNo, detail)
|
||
return detail
|
||
}
|
||
} catch (error) {
|
||
console.log('[MyApplicationTab] load reservation inspection detail failed', inspectionNo, error)
|
||
}
|
||
return null
|
||
},
|
||
async ensureReservationContractDetail(order = {}) {
|
||
const contractNo = this.getReservationContractNo(order)
|
||
if (!contractNo || this.reservationContractDetailMap[contractNo]) {
|
||
return this.reservationContractDetailMap[contractNo] || null
|
||
}
|
||
try {
|
||
const detail = this.normalizeContractDetail(await contractDetail({
|
||
contractNo
|
||
}, this.language))
|
||
if (detail && Object.keys(detail).length) {
|
||
this.$set(this.reservationContractDetailMap, contractNo, detail)
|
||
return detail
|
||
}
|
||
} catch (error) {
|
||
console.log('[MyApplicationTab] load reservation contract detail failed', contractNo, error)
|
||
}
|
||
return null
|
||
},
|
||
async preloadReservationInspectionDetails(list = []) {
|
||
const inspectionNos = [...new Set(
|
||
(Array.isArray(list) ? list : [])
|
||
.map(item => this.getReservationInspectionNo(item))
|
||
.filter(Boolean)
|
||
)]
|
||
if (!inspectionNos.length) {
|
||
return
|
||
}
|
||
await Promise.all(inspectionNos.map((inspectionNo) => {
|
||
if (this.reservationInspectionDetailMap[inspectionNo]) {
|
||
return Promise.resolve()
|
||
}
|
||
return roomInspectionDetail({
|
||
inspectionNo
|
||
}, this.language)
|
||
.then((response) => {
|
||
const detail = this.normalizeInspectionDetail(response)
|
||
if (detail && Object.keys(detail).length) {
|
||
this.$set(this.reservationInspectionDetailMap, inspectionNo, detail)
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
console.log('[MyApplicationTab] preload reservation inspection detail failed', inspectionNo, error)
|
||
})
|
||
}))
|
||
},
|
||
async preloadReservationContractDetails(list = []) {
|
||
const contractNos = [...new Set(
|
||
(Array.isArray(list) ? list : [])
|
||
.map(item => this.getReservationContractNo(item))
|
||
.filter(Boolean)
|
||
)]
|
||
if (!contractNos.length) {
|
||
return
|
||
}
|
||
await Promise.all(contractNos.map((contractNo) => {
|
||
if (this.reservationContractDetailMap[contractNo]) {
|
||
return Promise.resolve()
|
||
}
|
||
return contractDetail({
|
||
contractNo
|
||
}, this.language)
|
||
.then((response) => {
|
||
const detail = this.normalizeContractDetail(response)
|
||
if (detail && Object.keys(detail).length) {
|
||
this.$set(this.reservationContractDetailMap, contractNo, detail)
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
console.log('[MyApplicationTab] preload reservation contract detail failed', contractNo, error)
|
||
})
|
||
}))
|
||
},
|
||
getCurrentStepActionText(item = {}) {
|
||
if (item.isViewingApplication) {
|
||
return ''
|
||
}
|
||
if (!this.hasReservationCurrentActionRef(item)) {
|
||
return ''
|
||
}
|
||
return getReservationCurrentActionText(item, this.language)
|
||
},
|
||
async loadReservationFlowsForList(list = []) {
|
||
const baseList = Array.isArray(list) ? list : []
|
||
const orderNos = [...new Set(
|
||
baseList
|
||
.filter(item => item && !item.isViewingApplication)
|
||
.map(item => getReservationOrderNo(item))
|
||
.filter(Boolean)
|
||
)]
|
||
|
||
const requestToken = Date.now()
|
||
this._reservationFlowRequestToken = requestToken
|
||
|
||
const applyFlowMap = (flowMap = {}) => {
|
||
if (this._reservationFlowRequestToken !== requestToken) {
|
||
return
|
||
}
|
||
this.list = (Array.isArray(this.list) ? this.list : []).map(item => attachReservationFlow(item, flowMap))
|
||
saveMyApplicationList(this.list)
|
||
}
|
||
|
||
if (!orderNos.length) {
|
||
applyFlowMap({})
|
||
return
|
||
}
|
||
|
||
try {
|
||
const response = await orderSearchedReservationFlow({
|
||
orderNos
|
||
}, this.language)
|
||
const flowMap = buildReservationFlowMap(parseReservationFlowResponse(response))
|
||
const nextList = (Array.isArray(this.list) ? this.list : []).map(item => attachReservationFlow(item, flowMap))
|
||
await Promise.all([
|
||
this.preloadReservationContractDetails(nextList),
|
||
this.preloadReservationInspectionDetails(nextList)
|
||
])
|
||
applyFlowMap(flowMap)
|
||
} catch (error) {
|
||
console.log('[MyApplicationTab] load reservation flow failed', error)
|
||
applyFlowMap({})
|
||
}
|
||
},
|
||
async fetchReservationOrders() {
|
||
try {
|
||
const response = await orderSearchedMePage({
|
||
PageIndex: 1,
|
||
PageSize: 100,
|
||
IsExport: false
|
||
}, this.language)
|
||
const orderList = normalizeBookingList(parseOrderListResponse(response), this.language)
|
||
const reservationOrderList = orderList.filter(item => getOrderStatusCode(item) === 0)
|
||
const orderNos = [...new Set(reservationOrderList.map(item => item.orderNo).filter(Boolean))]
|
||
|
||
if (!orderNos.length) {
|
||
this.reservationOrders = []
|
||
this.activeReservationOrderNo = ''
|
||
return
|
||
}
|
||
|
||
const flowResponse = await orderSearchedReservationFlow({
|
||
orderNos
|
||
}, this.language)
|
||
const flowMap = buildReservationFlowMap(parseReservationFlowResponse(flowResponse))
|
||
const reservationOrders = reservationOrderList
|
||
.map(item => attachReservationFlow(item, flowMap))
|
||
.filter(item => !!item.reservationCurrentNode)
|
||
|
||
await Promise.all([
|
||
this.preloadReservationContractDetails(reservationOrders),
|
||
this.preloadReservationInspectionDetails(reservationOrders)
|
||
])
|
||
|
||
this.reservationOrders = reservationOrders
|
||
this.syncReservationPendingPaymentOrders(this.list)
|
||
|
||
if (!reservationOrders.length) {
|
||
this.activeReservationOrderNo = ''
|
||
return
|
||
}
|
||
|
||
const existsActiveOrder = reservationOrders.some(item => item.orderNo === this.activeReservationOrderNo)
|
||
this.activeReservationOrderNo = existsActiveOrder
|
||
? this.activeReservationOrderNo
|
||
: reservationOrders[0].orderNo
|
||
this.ensureReservationBillDetail(this.activeReservationOrder)
|
||
this.ensureReservationInspectionDetail(this.activeReservationOrder)
|
||
this.ensureReservationContractDetail(this.activeReservationOrder)
|
||
} catch (error) {
|
||
console.log('[MyApplicationTab] fetch reservation orders failed', error)
|
||
this.reservationOrders = []
|
||
this.activeReservationOrderNo = ''
|
||
}
|
||
},
|
||
async loadPendingPaymentOrdersForList(list = []) {
|
||
const baseList = Array.isArray(list) ? list : []
|
||
const requestToken = Date.now()
|
||
this._pendingPaymentRequestToken = requestToken
|
||
|
||
await Promise.all(baseList.map(async (item, index) => {
|
||
if (!this.shouldQueryPendingDepositBill(item)) {
|
||
return
|
||
}
|
||
|
||
const referenceTypeNo = this.resolveReferenceTypeNo(item)
|
||
if (!referenceTypeNo) {
|
||
return
|
||
}
|
||
|
||
try {
|
||
console.log('[MyApplicationTab] query bills for pending payment order', referenceTypeNo)
|
||
const billRes = await accountQueryBills({
|
||
referenceTypeNo,
|
||
status: 10,
|
||
paymentStatus: 0,
|
||
ProviderType: 'WechatMiniProgram'
|
||
})
|
||
const billList = this.normalizeBillList(billRes)
|
||
const bill = Array.isArray(billList) && billList.length ? billList[0] : null
|
||
if (this._pendingPaymentRequestToken !== requestToken) {
|
||
return
|
||
}
|
||
|
||
const currentItem = this.list[index]
|
||
if (!currentItem || this.getApplicationKey(currentItem) !== this.getApplicationKey(item)) {
|
||
return
|
||
}
|
||
|
||
if (!bill) {
|
||
this.$set(this.list, index, {
|
||
...currentItem,
|
||
pendingDepositBill: null,
|
||
pendingPaymentOrder: null,
|
||
pendingDepositBillQueried: true
|
||
})
|
||
return
|
||
}
|
||
|
||
const billNo = this.getBillNo(bill)
|
||
if (!billNo) {
|
||
this.$set(this.list, index, {
|
||
...currentItem,
|
||
pendingDepositBill: null,
|
||
pendingPaymentOrder: null,
|
||
pendingDepositBillQueried: true
|
||
})
|
||
return
|
||
}
|
||
|
||
console.log('[MyApplicationTab] query payment/paying-bills', billNo)
|
||
const pendingPaymentOrder = await this.getPendingPaymentOrder(billNo).catch(() => null)
|
||
if (this._pendingPaymentRequestToken !== requestToken) {
|
||
return
|
||
}
|
||
|
||
const updatedItem = {
|
||
...currentItem,
|
||
pendingDepositBill: bill,
|
||
pendingPaymentOrder,
|
||
pendingDepositBillQueried: true
|
||
}
|
||
this.$set(this.list, index, updatedItem)
|
||
} catch (error) {
|
||
console.log('[MyApplicationTab] load pending payment order failed', error)
|
||
}
|
||
}))
|
||
this.syncReservationPendingPaymentOrders(this.list)
|
||
},
|
||
async refreshStatus() {
|
||
if (this.loading || this.loadingMore) {
|
||
return
|
||
}
|
||
await this.reloadList(true)
|
||
if (this.total === 0 && !this.reservationOrders.length) {
|
||
this.$emit('refresh-shell-state')
|
||
uni.showToast({
|
||
title: this.$tr('未查到申请单,已同步预订单'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
if (this.list.length || this.reservationOrders.length) {
|
||
uni.showToast({
|
||
title: this.$tr('申请状态已刷新'),
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
async reloadList(force = false) {
|
||
const now = Date.now()
|
||
if (!force && this._lastReloadListAt && now - this._lastReloadListAt < 600) {
|
||
return Promise.resolve()
|
||
}
|
||
if (!force && this._reloadListPromise) {
|
||
return this._reloadListPromise
|
||
}
|
||
if (force) {
|
||
this.reservationBillDetailMap = {}
|
||
this.reservationContractDetailMap = {}
|
||
this.reservationInspectionDetailMap = {}
|
||
}
|
||
this._lastReloadListAt = now
|
||
this.pageIndex = 1
|
||
this.hasMore = true
|
||
this.currentIndex = 0
|
||
this._reloadListPromise = Promise.all([
|
||
this.fetchReservationOrders(),
|
||
this.fetchList(true)
|
||
]).finally(() => {
|
||
this._reloadListPromise = null
|
||
})
|
||
return this._reloadListPromise
|
||
},
|
||
onSwiperChange(e) {
|
||
this.currentIndex = e.detail.current
|
||
if (this.currentIndex >= this.list.length - 2 && this.hasMore) {
|
||
this.loadMore()
|
||
}
|
||
},
|
||
async loadMore() {
|
||
if (!this.hasMore || this.loading || this.loadingMore) {
|
||
return
|
||
}
|
||
this.pageIndex += 1
|
||
await this.fetchList(false)
|
||
},
|
||
async fetchList(isReload = false) {
|
||
if (isReload) {
|
||
this.loading = true
|
||
} else {
|
||
this.loadingMore = true
|
||
}
|
||
|
||
const params = {
|
||
PageIndex: this.pageIndex,
|
||
PageSize: this.pageSize
|
||
}
|
||
if (this.status !== '' && this.status !== null && typeof this.status !== 'undefined') {
|
||
params.Status = this.status
|
||
}
|
||
|
||
await applicationFormMePage(params, this.language)
|
||
.then(async (response) => {
|
||
const pageData = formatPageResponse(response)
|
||
console.log('pageData', pageData)
|
||
const nextList = normalizeApplicationList(pageData.data || [])
|
||
const total = Number(pageData.total || 0)
|
||
const mergedList = normalizeApplicationList(isReload ? nextList : this.list.concat(nextList))
|
||
.map(item => ({
|
||
...item,
|
||
pendingDepositBill: item && item.pendingDepositBill ? item.pendingDepositBill : null,
|
||
pendingPaymentOrder: item && item.pendingPaymentOrder ? item.pendingPaymentOrder : null,
|
||
pendingDepositBillQueried: !!(item && item.pendingDepositBillQueried)
|
||
}))
|
||
|
||
this.total = total
|
||
this.list = mergedList
|
||
this.hasMore = this.list.length < total && nextList.length > 0
|
||
|
||
const activeCode = this.resolvePreferredReferenceTypeNo(this.list)
|
||
if (activeCode) {
|
||
saveActiveApplicationCode(activeCode)
|
||
}
|
||
|
||
await this.loadReservationFlowsForList(this.list)
|
||
await this.loadPendingPaymentOrdersForList(this.list)
|
||
saveMyApplicationList(this.list)
|
||
})
|
||
.catch((error) => {
|
||
if (!isReload) {
|
||
this.pageIndex = Math.max(1, this.pageIndex - 1)
|
||
}
|
||
uni.showToast({
|
||
title: (error && (error.message || error.msg)) || this.$tr('申请记录加载失败'),
|
||
icon: 'none'
|
||
})
|
||
})
|
||
.finally(() => {
|
||
this.loading = false
|
||
this.loadingMore = false
|
||
})
|
||
},
|
||
changeStatus(value) {
|
||
if (this.status === value) {
|
||
return
|
||
}
|
||
this.status = value
|
||
this.reloadList()
|
||
},
|
||
getApplicationCode(item = {}) {
|
||
return item.detailCode || '--'
|
||
},
|
||
getApplicationKey(item = {}) {
|
||
return item.detailCode || item.formId || `${item.createTime || 'application'}-${item.phone || item.userName || 'unknown'}`
|
||
},
|
||
getApplicationTitle(item = {}) {
|
||
if (item.isViewingApplication) {
|
||
return this.$tr('预约看房申请')
|
||
}
|
||
return item.intendedRoomTypeName || this.$tr('入住申请')
|
||
},
|
||
resolveReferenceTypeNo(item = {}) {
|
||
return item.detailCode || ''
|
||
},
|
||
canViewBill(item = {}) {
|
||
if (item.isViewingApplication || !this.resolveReferenceTypeNo(item)) {
|
||
return false
|
||
}
|
||
if (this.isReservationBillStep(item)) {
|
||
return true
|
||
}
|
||
if (!this.shouldQueryPendingDepositBill(item)) {
|
||
return false
|
||
}
|
||
return this.hasPendingDepositBill(item)
|
||
},
|
||
canCancelApplication(item = {}) {
|
||
if (!this.resolveReferenceTypeNo(item) || Number(item.status) !== 0) {
|
||
return false
|
||
}
|
||
if (item.isViewingApplication) {
|
||
return true
|
||
}
|
||
if (Number(item.payStatus) === 10) {
|
||
return false
|
||
}
|
||
return this.hasPendingDepositBill(item)
|
||
},
|
||
canCancelOrder(item = {}) {
|
||
return this.canCancelApplication(item)
|
||
},
|
||
getCancelButtonText(item = {}) {
|
||
return this.cancelingReferenceTypeNo === this.resolveReferenceTypeNo(item)
|
||
? this.$tr('取消中...')
|
||
: this.$tr('取消申请')
|
||
},
|
||
getCancelOrderContent(item = {}) {
|
||
return item.isViewingApplication
|
||
? this.$tr('取消后将关闭当前预约看房申请,是否继续?')
|
||
: this.$tr('取消后将关闭当前申请单,是否继续?')
|
||
},
|
||
confirmCancelOrder(item = {}) {
|
||
return new Promise((resolve) => {
|
||
uni.showModal({
|
||
title: this.$tr('取消申请'),
|
||
content: this.getCancelOrderContent(item),
|
||
confirmColor: '#ef4444',
|
||
success: (res) => resolve(!!(res && res.confirm)),
|
||
fail: () => resolve(false)
|
||
})
|
||
})
|
||
},
|
||
syncCanceledItem(referenceTypeNo = '') {
|
||
const targetCode = String(referenceTypeNo || '')
|
||
if (!targetCode) {
|
||
return
|
||
}
|
||
const updateItem = (item = {}) => {
|
||
if (String(this.resolveReferenceTypeNo(item)) !== targetCode) {
|
||
return item
|
||
}
|
||
return {
|
||
...item,
|
||
status: 99,
|
||
statusDesc: this.$tr('已结束'),
|
||
pendingDepositBill: null,
|
||
pendingPaymentOrder: null,
|
||
pendingDepositBillQueried: false
|
||
}
|
||
}
|
||
this.list = normalizeApplicationList(this.list.map(updateItem))
|
||
saveMyApplicationList(this.list)
|
||
const cachedList = normalizeApplicationList(getMyApplicationList())
|
||
if (cachedList.length) {
|
||
saveMyApplicationList(cachedList.map(updateItem))
|
||
}
|
||
},
|
||
cancelOrderAction(event) {
|
||
const dataset = event && event.currentTarget ? event.currentTarget.dataset || {} : {}
|
||
const referenceTypeNo = dataset.referenceTypeNo || dataset.referenceTypeNo === 0
|
||
? String(dataset.referenceTypeNo)
|
||
: ''
|
||
const currentItem = (Array.isArray(this.list) ? this.list : []).find((item) => {
|
||
return String(this.resolveReferenceTypeNo(item)) === referenceTypeNo
|
||
}) || {}
|
||
if (!referenceTypeNo || this.cancelingReferenceTypeNo) {
|
||
return
|
||
}
|
||
this.confirmCancelOrder(currentItem)
|
||
.then((confirmed) => {
|
||
if (!confirmed) {
|
||
return
|
||
}
|
||
this.cancelingReferenceTypeNo = referenceTypeNo
|
||
return userDetailCancel({
|
||
detailCode: referenceTypeNo
|
||
}, this.language)
|
||
.then(async () => {
|
||
this.syncCanceledItem(referenceTypeNo)
|
||
await this.reloadList(true)
|
||
this.$emit('refresh-shell-state')
|
||
uni.showToast({
|
||
title: this.$tr('申请已取消'),
|
||
icon: 'success'
|
||
})
|
||
})
|
||
})
|
||
.catch((error) => {
|
||
uni.showToast({
|
||
title: (error && (error.message || error.msg)) || this.$tr('取消申请失败'),
|
||
icon: 'none'
|
||
})
|
||
})
|
||
.finally(() => {
|
||
this.cancelingReferenceTypeNo = ''
|
||
})
|
||
},
|
||
continuePendingPaymentAction(event) {
|
||
const dataset = event && event.currentTarget ? event.currentTarget.dataset || {} : {}
|
||
const referenceTypeNo = dataset.referenceTypeNo || dataset.referenceTypeNo === 0
|
||
? String(dataset.referenceTypeNo)
|
||
: ''
|
||
if (!referenceTypeNo) {
|
||
uni.showToast({
|
||
title: this.$tr('申请单编号缺失'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const currentItem = (Array.isArray(this.list) ? this.list : []).find((item) => {
|
||
return String(this.resolveReferenceTypeNo(item)) === referenceTypeNo
|
||
}) || {}
|
||
this.openPaymentMethodSelector({
|
||
referenceTypeNo,
|
||
description: this.$tr('支付定金'),
|
||
mode: 'deposit',
|
||
sourceItem: currentItem
|
||
})
|
||
},
|
||
resolvePreferredReferenceTypeNo(list = []) {
|
||
const sortedList = [...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)
|
||
},
|
||
async payDepositAction(event) {
|
||
const dataset = event && event.currentTarget ? event.currentTarget.dataset || {} : {}
|
||
const referenceTypeNo = dataset.referenceTypeNo || dataset.referenceTypeNo === 0
|
||
? String(dataset.referenceTypeNo)
|
||
: ''
|
||
if (!referenceTypeNo) {
|
||
uni.showToast({
|
||
title: this.$tr('申请单编号缺失'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const currentItem = (Array.isArray(this.list) ? this.list : []).find((item) => {
|
||
return String(this.resolveReferenceTypeNo(item)) === referenceTypeNo
|
||
}) || {}
|
||
this.openPaymentMethodSelector({
|
||
referenceTypeNo,
|
||
description: this.$tr('支付定金'),
|
||
mode: 'deposit',
|
||
sourceItem: currentItem
|
||
})
|
||
},
|
||
getPaymentMethodSheetOptions() {
|
||
return [
|
||
{
|
||
text: this.language === 'en' ? 'WeChat Pay' : '微信支付',
|
||
subText: this.language === 'en'
|
||
? 'The system will continue by opening WeChat for payment.'
|
||
: '系统将继续拉起微信完成支付'
|
||
},
|
||
{
|
||
text: this.language === 'en' ? 'Other Payment Methods' : '其他支付方式',
|
||
subText: this.language === 'en'
|
||
? 'View the corporate bank account details when WeChat Pay is unavailable.'
|
||
: '无法使用微信支付时查看对公账户信息'
|
||
}
|
||
]
|
||
},
|
||
getPaymentMethodSheetTipText() {
|
||
return this.language === 'en' ? 'Please select a payment method' : '请选择支付方式'
|
||
},
|
||
openPaymentMethodSelector(context = {}) {
|
||
this.pendingPaymentMethodContext = context || null
|
||
this.showPaymentMethodSheet = true
|
||
},
|
||
handlePaymentMethodSheetClick(index) {
|
||
const context = this.pendingPaymentMethodContext || {}
|
||
this.pendingPaymentMethodContext = null
|
||
if (Number(index) === 0) {
|
||
this.openDepositNoticeForPayment(context)
|
||
return
|
||
}
|
||
if (Number(index) === 1) {
|
||
this.showOfflinePaymentNotice = true
|
||
this.loadDepositOtherPaymentConfig()
|
||
}
|
||
},
|
||
closeOfflinePaymentNotice() {
|
||
this.showOfflinePaymentNotice = false
|
||
},
|
||
loadDepositOtherPaymentConfig() {
|
||
this.depositOtherPaymentConfigLoading = true
|
||
return loadDepositOtherPaymentConfig(this.language)
|
||
.then((config) => {
|
||
this.depositOtherPaymentConfig = config || createDefaultDepositOtherPaymentConfig()
|
||
})
|
||
.catch((error) => {
|
||
console.error('load deposit other payment config failed', error)
|
||
this.depositOtherPaymentConfig = createDefaultDepositOtherPaymentConfig()
|
||
})
|
||
.finally(() => {
|
||
this.depositOtherPaymentConfigLoading = false
|
||
})
|
||
},
|
||
getPaymentContextMode(context = {}) {
|
||
const explicitMode = String(context.mode || '').trim()
|
||
if (explicitMode) {
|
||
return explicitMode
|
||
}
|
||
return context.billNo ? 'bill' : 'deposit'
|
||
},
|
||
isBillPaymentNotice() {
|
||
return this.getPaymentContextMode(this.pendingDepositPaymentContext || {}) === 'bill'
|
||
},
|
||
getPaymentNoticeTitle() {
|
||
return this.$tr('确认支付信息')
|
||
},
|
||
getPaymentNoticeConfirmText() {
|
||
return this.$tr('确认并支付')
|
||
},
|
||
getPaymentNoticeBillNo() {
|
||
const context = this.pendingDepositPaymentContext || {}
|
||
const detail = this.paymentNoticeBillDetail || {}
|
||
return String(this.getBillNo(detail) || context.billNo || '').trim()
|
||
},
|
||
getPaymentNoticeBillTitle() {
|
||
const detail = this.paymentNoticeBillDetail || {}
|
||
return String(
|
||
detail.description
|
||
|| detail.paymentFeeType
|
||
|| detail.remark
|
||
|| detail.createTypeDesc
|
||
|| detail.referenceTypeDesc
|
||
|| detail.billName
|
||
|| detail.name
|
||
|| (this.pendingDepositPaymentContext && this.pendingDepositPaymentContext.description)
|
||
|| this.$tr('账单支付')
|
||
).trim()
|
||
},
|
||
getPaymentNoticeBillAmount() {
|
||
const detail = this.paymentNoticeBillDetail || {}
|
||
const amount = detail.remainingAmount
|
||
if (amount !== '' && amount !== null && amount !== undefined) {
|
||
return amount
|
||
}
|
||
const source = this.paymentNoticeSourceItem || {}
|
||
const sourceAmount = source.remainingAmount
|
||
if (sourceAmount !== '' && sourceAmount !== null && sourceAmount !== undefined) {
|
||
return sourceAmount
|
||
}
|
||
const detailTotal = detail.totalCharge !== undefined && detail.totalCharge !== null && detail.totalCharge !== ''
|
||
? detail.totalCharge
|
||
: (detail.totalAmount !== undefined && detail.totalAmount !== null && detail.totalAmount !== '' ? detail.totalAmount : detail.amount)
|
||
if (detailTotal !== '' && detailTotal !== null && detailTotal !== undefined) {
|
||
return detailTotal
|
||
}
|
||
const sourceTotal = source.totalCharge !== undefined && source.totalCharge !== null && source.totalCharge !== ''
|
||
? source.totalCharge
|
||
: (source.totalAmount !== undefined && source.totalAmount !== null && source.totalAmount !== '' ? source.totalAmount : source.amount)
|
||
if (sourceTotal !== '' && sourceTotal !== null && sourceTotal !== undefined) {
|
||
return sourceTotal
|
||
}
|
||
return ''
|
||
},
|
||
getPaymentNoticeBillAmountDisplay() {
|
||
const amount = this.getPaymentNoticeBillAmount()
|
||
if (amount === '' || amount === null || amount === undefined) {
|
||
return '--'
|
||
}
|
||
return `¥${this.formatMoney(amount)}`
|
||
},
|
||
buildPaymentNoticePayMethod(source = {}) {
|
||
const pricePlanDetail = source && source.pricePlanDetail && typeof source.pricePlanDetail === 'object'
|
||
? source.pricePlanDetail
|
||
: {}
|
||
const payMethodText = String(
|
||
source.depositPrepayDesc
|
||
|| source.depositAndPrepayDesc
|
||
|| source.depositPrepayText
|
||
|| source.payMethod
|
||
|| source.payMethodDesc
|
||
|| source.paymentMethod
|
||
|| source.paymentMethodDesc
|
||
|| pricePlanDetail.payMethod
|
||
|| ''
|
||
).trim()
|
||
if (payMethodText) {
|
||
return localizePayMethodText(payMethodText, this.language)
|
||
}
|
||
const deposit = pricePlanDetail.deposit
|
||
const prepay = pricePlanDetail.prepay
|
||
if (deposit !== '' && deposit !== null && deposit !== undefined && prepay !== '' && prepay !== null && prepay !== undefined) {
|
||
return localizePayMethodText(`押${deposit}付${prepay}`, this.language)
|
||
}
|
||
return ''
|
||
},
|
||
getPaymentNoticeDetailRows() {
|
||
const source = this.paymentNoticeSourceItem || {}
|
||
const formatShortDate = (value) => {
|
||
if (!value) {
|
||
return ''
|
||
}
|
||
return String(value).replace('T', ' ').slice(0, 10)
|
||
}
|
||
const residentName = String(
|
||
source.userName
|
||
|| source.name
|
||
|| ''
|
||
).trim()
|
||
const areaName = String(
|
||
source.areaText
|
||
|| source.intendedAreaName
|
||
|| source.areaName
|
||
|| source.campusName
|
||
|| ''
|
||
).trim()
|
||
const roomTypeName = String(
|
||
source.roomTypeText
|
||
|| source.roomTypeName
|
||
|| source.intendedRoomTypeName
|
||
|| source.roomTypeCode
|
||
|| source.roomName
|
||
|| ''
|
||
).trim()
|
||
const roomNo = String(
|
||
source.roomNoText
|
||
|| source.roomNo
|
||
|| source.roomLabel
|
||
|| ''
|
||
).trim()
|
||
const moveInDate = formatShortDate(
|
||
source.startDateText
|
||
|| source.expectedCheckInTime
|
||
|| source.startDate
|
||
|| source.start
|
||
|| source.checkinDate
|
||
|| ''
|
||
)
|
||
const leaseStart = formatShortDate(
|
||
source.startDateText
|
||
|| source.expectedCheckInTime
|
||
|| source.startDate
|
||
|| source.start
|
||
|| source.checkinDate
|
||
|| ''
|
||
)
|
||
const leaseEnd = formatShortDate(
|
||
source.endDateText
|
||
|| source.expectedCheckOutTime
|
||
|| source.endDate
|
||
|| source.end
|
||
|| source.checkoutDate
|
||
|| ''
|
||
)
|
||
const leasePeriod = leaseStart && leaseEnd
|
||
? `${leaseStart}${this.$tr(' 至 ')}${leaseEnd}`
|
||
: ''
|
||
const floorLevel = String(
|
||
source.intendedFloorLevelDesc
|
||
|| source.floorLevelDesc
|
||
|| ''
|
||
).trim()
|
||
const payMethod = this.buildPaymentNoticePayMethod(source)
|
||
return [
|
||
residentName ? { label: this.$tr('入住人'), value: residentName } : null,
|
||
areaName ? { label: this.$tr('所属公寓'), value: areaName } : null,
|
||
roomTypeName ? { label: this.$tr('房型'), value: roomTypeName } : null,
|
||
roomNo ? { label: this.$tr('房间号'), value: roomNo } : null,
|
||
floorLevel ? { label: this.$tr('意向楼层'), value: floorLevel } : null,
|
||
moveInDate ? { label: this.$tr('预计入住'), value: moveInDate } : null,
|
||
leasePeriod ? { label: this.$tr('租期'), value: leasePeriod } : null,
|
||
payMethod ? { label: this.$tr('押付方式'), value: payMethod } : null,
|
||
].filter(Boolean)
|
||
},
|
||
normalizePaymentNoticePreview(response, orderNo = '') {
|
||
const payload = parseJsonStringMaybe(response && response.data ? response.data : response)
|
||
const normalizeList = (list = []) => list.filter(item => item && typeof item === 'object')
|
||
const normalizedOrderNo = String(orderNo || '').trim()
|
||
let candidates = []
|
||
|
||
if (Array.isArray(payload)) {
|
||
candidates = normalizeList(payload)
|
||
} else if (payload && Array.isArray(payload.data)) {
|
||
candidates = normalizeList(payload.data)
|
||
} else if (payload && payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) {
|
||
candidates = [payload.data]
|
||
} else if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
||
candidates = [payload]
|
||
}
|
||
|
||
if (!candidates.length) {
|
||
return null
|
||
}
|
||
|
||
return candidates.find(item => String(item.orderNo || '').trim() === normalizedOrderNo) || candidates[0]
|
||
},
|
||
resolvePaymentNoticeOrderNo(context = {}, sourceItem = null) {
|
||
return String(
|
||
context.orderNo
|
||
|| (sourceItem && (sourceItem.orderNo || sourceItem.reservationOrderNo || getReservationOrderNo(sourceItem)))
|
||
|| (this.activeReservationOrder && this.activeReservationOrder.orderNo)
|
||
|| ''
|
||
).trim()
|
||
},
|
||
loadPaymentNoticeSourceDetail(orderNo = '', fallbackSource = null) {
|
||
const normalizedOrderNo = String(orderNo || this.resolvePaymentNoticeOrderNo({}, fallbackSource) || '').trim()
|
||
if (!normalizedOrderNo) {
|
||
this.paymentNoticeSourceLoading = false
|
||
this.paymentNoticeSourceItem = fallbackSource || this.paymentNoticeSourceItem || null
|
||
return Promise.resolve(this.paymentNoticeSourceItem)
|
||
}
|
||
|
||
this.paymentNoticeSourceLoading = true
|
||
return orderSearchedDetail(normalizedOrderNo, this.language)
|
||
.then((response) => {
|
||
const detail = normalizeBookingDetail(parseOrderDetailResponse(response), this.language)
|
||
this.paymentNoticeSourceItem = detail
|
||
? {
|
||
...(fallbackSource || {}),
|
||
...detail
|
||
}
|
||
: (fallbackSource || this.paymentNoticeSourceItem || null)
|
||
return this.paymentNoticeSourceItem
|
||
})
|
||
.catch(() => {
|
||
this.paymentNoticeSourceItem = fallbackSource || this.paymentNoticeSourceItem || null
|
||
return this.paymentNoticeSourceItem
|
||
})
|
||
.finally(() => {
|
||
this.paymentNoticeSourceLoading = false
|
||
})
|
||
},
|
||
loadPaymentNoticeBillDetail(orderNo = '') {
|
||
const normalizedOrderNo = String(orderNo || '').trim()
|
||
if (!normalizedOrderNo) {
|
||
this.paymentNoticeBillLoading = false
|
||
this.paymentNoticeBillDetail = null
|
||
return Promise.resolve(null)
|
||
}
|
||
|
||
const cacheKey = `preview:${normalizedOrderNo}`
|
||
const cached = this.reservationBillDetailMap[cacheKey] || null
|
||
if (cached) {
|
||
this.paymentNoticeBillLoading = false
|
||
this.paymentNoticeBillDetail = cached
|
||
return Promise.resolve(cached)
|
||
}
|
||
|
||
this.paymentNoticeBillLoading = true
|
||
this.paymentNoticeBillDetail = null
|
||
return accountQueryBillPreview({
|
||
OrderNo: normalizedOrderNo
|
||
}, this.language)
|
||
.then((response) => {
|
||
const detail = this.normalizePaymentNoticePreview(response, normalizedOrderNo)
|
||
const normalizedDetail = detail && Object.keys(detail).length ? detail : null
|
||
if (normalizedDetail) {
|
||
this.$set(this.reservationBillDetailMap, cacheKey, normalizedDetail)
|
||
}
|
||
this.paymentNoticeBillDetail = normalizedDetail
|
||
return normalizedDetail
|
||
})
|
||
.catch(() => {
|
||
this.paymentNoticeBillDetail = null
|
||
return null
|
||
})
|
||
.finally(() => {
|
||
this.paymentNoticeBillLoading = false
|
||
})
|
||
},
|
||
openDepositNoticeForPayment(context = {}) {
|
||
const paymentSourceItem = context.sourceItem || this.activeReservationOrder || null
|
||
const paymentReferenceTypeNo = String(
|
||
context.referenceTypeNo
|
||
|| (paymentSourceItem ? this.getApplicationDetailCodeFromOrder(paymentSourceItem) : '')
|
||
|| ''
|
||
).trim()
|
||
const paymentOrderNo = this.resolvePaymentNoticeOrderNo(context, paymentSourceItem)
|
||
this.pendingDepositPaymentContext = {
|
||
referenceTypeNo: paymentReferenceTypeNo,
|
||
orderNo: paymentOrderNo,
|
||
billNo: String(context.billNo || '').trim(),
|
||
paymentNo: String(context.paymentNo || '').trim(),
|
||
description: String(context.description || '').trim(),
|
||
mode: this.getPaymentContextMode(context)
|
||
}
|
||
this.paymentNoticeSourceItem = paymentSourceItem
|
||
this.pendingDepositReferenceTypeNo = this.pendingDepositPaymentContext.referenceTypeNo
|
||
this.loadPaymentNoticeBillDetail(paymentOrderNo)
|
||
.then((preview) => {
|
||
const mergedSource = preview
|
||
? {
|
||
...(paymentSourceItem || {}),
|
||
...preview
|
||
}
|
||
: paymentSourceItem
|
||
this.paymentNoticeSourceItem = mergedSource
|
||
const nextReferenceTypeNo = String(
|
||
(preview && (preview.applicationDetailCode || preview.detailCode))
|
||
|| this.pendingDepositPaymentContext.referenceTypeNo
|
||
|| ''
|
||
).trim()
|
||
this.pendingDepositPaymentContext.referenceTypeNo = nextReferenceTypeNo
|
||
this.pendingDepositReferenceTypeNo = nextReferenceTypeNo
|
||
return this.loadPaymentNoticeSourceDetail(paymentOrderNo, mergedSource)
|
||
})
|
||
.catch(() => {
|
||
this.loadPaymentNoticeSourceDetail(paymentOrderNo, this.paymentNoticeSourceItem)
|
||
})
|
||
this.showDepositNotice = true
|
||
},
|
||
closeDepositNotice() {
|
||
this.showDepositNotice = false
|
||
this.pendingDepositReferenceTypeNo = ''
|
||
this.pendingPaymentMethodContext = null
|
||
this.pendingDepositPaymentContext = null
|
||
this.paymentNoticeSourceItem = null
|
||
this.paymentNoticeSourceLoading = false
|
||
this.paymentNoticeBillLoading = false
|
||
this.paymentNoticeBillDetail = null
|
||
},
|
||
resolveReservationBillPaymentContext(order = {}, fallbackContext = {}) {
|
||
const currentNode = order && order.reservationCurrentNode ? order.reservationCurrentNode : {}
|
||
const isBillStep = Number(currentNode.step || 0) === RESERVATION_FLOW_STEP.BILL
|
||
const candidateReferenceTypeNos = [
|
||
fallbackContext.referenceTypeNo,
|
||
this.getApplicationDetailCodeFromOrder(order),
|
||
currentNode.referenceTypeNo,
|
||
currentNode.detailCode,
|
||
currentNode.code,
|
||
order.referenceTypeNo,
|
||
order.detailCode,
|
||
order.code
|
||
]
|
||
const candidateBillNos = [
|
||
fallbackContext.billNo,
|
||
currentNode.billNo,
|
||
currentNode.BillNo,
|
||
currentNode.accountBillNo,
|
||
currentNode.paymentBillNo,
|
||
currentNode.chargeBillNo,
|
||
isBillStep ? currentNode.refNo : '',
|
||
order.billNo,
|
||
this.getBillNo(this.getActiveReservationBillDetail() || {})
|
||
]
|
||
const candidatePaymentNos = [
|
||
fallbackContext.paymentNo,
|
||
currentNode.paymentNo,
|
||
currentNode.payNo,
|
||
currentNode.payRequestNo,
|
||
currentNode.cashierNo
|
||
]
|
||
const pickFirst = (list = []) => {
|
||
for (let index = 0; index < list.length; index += 1) {
|
||
const value = String(list[index] || '').trim()
|
||
if (value) {
|
||
return value
|
||
}
|
||
}
|
||
return ''
|
||
}
|
||
|
||
return {
|
||
referenceTypeNo: pickFirst(candidateReferenceTypeNos),
|
||
billNo: pickFirst(candidateBillNos),
|
||
paymentNo: pickFirst(candidatePaymentNos),
|
||
description: String(currentNode.stepDesc || fallbackContext.description || '').trim() || this.$tr('账单支付'),
|
||
mode: isBillStep ? 'bill' : this.getPaymentContextMode(fallbackContext),
|
||
sourceItem: order
|
||
}
|
||
},
|
||
resolvePendingPaymentContextByPaymentNo(paymentNo = '') {
|
||
const normalizedPaymentNo = String(paymentNo || '').trim()
|
||
if (!normalizedPaymentNo) {
|
||
return null
|
||
}
|
||
|
||
const matchedItem = (Array.isArray(this.list) ? this.list : []).find((item) => {
|
||
const pendingOrder = this.getApplicationPendingPaymentOrder(item)
|
||
return pendingOrder && this.getPendingPaymentOrderNo(pendingOrder) === normalizedPaymentNo
|
||
})
|
||
|
||
if (!matchedItem) {
|
||
return null
|
||
}
|
||
|
||
return {
|
||
referenceTypeNo: String(this.resolveReferenceTypeNo(matchedItem) || '').trim(),
|
||
billNo: String(this.getBillNo(matchedItem.pendingDepositBill || {}) || '').trim(),
|
||
pendingPaymentOrder: this.getApplicationPendingPaymentOrder(matchedItem),
|
||
bill: matchedItem.pendingDepositBill || null
|
||
}
|
||
},
|
||
resolveReservationPaymentContextByPaymentNo(paymentNo = '') {
|
||
const normalizedPaymentNo = String(paymentNo || '').trim()
|
||
if (!normalizedPaymentNo) {
|
||
return null
|
||
}
|
||
|
||
const matchedOrder = (Array.isArray(this.reservationOrders) ? this.reservationOrders : []).find((item) => {
|
||
const pendingOrder = item && item.pendingPaymentOrder ? item.pendingPaymentOrder : null
|
||
return pendingOrder && this.getPendingPaymentOrderNo(pendingOrder) === normalizedPaymentNo
|
||
})
|
||
|
||
if (!matchedOrder) {
|
||
return null
|
||
}
|
||
|
||
return {
|
||
referenceTypeNo: String(this.resolveReferenceTypeNo(matchedOrder) || '').trim(),
|
||
billNo: String(this.getBillNo(matchedOrder.pendingDepositBill || {}) || '').trim(),
|
||
pendingPaymentOrder: matchedOrder.pendingPaymentOrder || null,
|
||
bill: matchedOrder.pendingDepositBill || null
|
||
}
|
||
},
|
||
async payDepositByReferenceTypeNo(referenceTypeNo = '') {
|
||
return this.payByPaymentContext({
|
||
referenceTypeNo
|
||
})
|
||
},
|
||
async payByPaymentContext(context = {}) {
|
||
const paymentMode = this.getPaymentContextMode(context)
|
||
let referenceTypeNo = String(context.referenceTypeNo || '').trim()
|
||
let billNo = String(context.billNo || '').trim()
|
||
const paymentNo = String(context.paymentNo || '').trim()
|
||
const description = String(context.description || '').trim() || (paymentMode === 'bill' ? this.$tr('账单支付') : this.$tr('支付定金'))
|
||
const emptyBillMessage = paymentMode === 'bill'
|
||
? this.$tr('暂无待支付账单')
|
||
: this.$tr('暂无待支付定金账单')
|
||
const missingContextMessage = paymentMode === 'bill'
|
||
? this.$tr('账单编号缺失')
|
||
: this.$tr('申请单编号缺失')
|
||
|
||
if (!referenceTypeNo && !billNo && !paymentNo) {
|
||
uni.showToast({
|
||
title: missingContextMessage,
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (referenceTypeNo) {
|
||
saveActiveApplicationCode(referenceTypeNo)
|
||
}
|
||
|
||
uni.showLoading({ title: this.$tr('正在获取账单...') })
|
||
try {
|
||
let bill = null
|
||
if (referenceTypeNo && !billNo) {
|
||
const detailPaymentContext = await this.resolveApplicationDetailPaymentContext(referenceTypeNo).catch(() => null)
|
||
if (detailPaymentContext) {
|
||
if (detailPaymentContext.isViewingApplication) {
|
||
uni.showToast({
|
||
title: this.$tr('预约看房申请无需支付定金'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
referenceTypeNo = detailPaymentContext.referenceTypeNo || referenceTypeNo
|
||
billNo = detailPaymentContext.billNo || billNo
|
||
}
|
||
}
|
||
let pendingPaymentOrder = billNo ? await this.getPendingPaymentOrder(billNo).catch(() => null) : null
|
||
if (!pendingPaymentOrder && paymentNo) {
|
||
const matchedContext = this.resolvePendingPaymentContextByPaymentNo(paymentNo) || this.resolveReservationPaymentContextByPaymentNo(paymentNo)
|
||
if (matchedContext) {
|
||
referenceTypeNo = referenceTypeNo || matchedContext.referenceTypeNo
|
||
billNo = billNo || matchedContext.billNo
|
||
pendingPaymentOrder = matchedContext.pendingPaymentOrder || null
|
||
bill = matchedContext.bill || null
|
||
}
|
||
}
|
||
|
||
if (referenceTypeNo && !billNo) {
|
||
const detailPaymentContext = await this.resolveApplicationDetailPaymentContext(referenceTypeNo).catch(() => null)
|
||
if (detailPaymentContext) {
|
||
if (detailPaymentContext.isViewingApplication) {
|
||
uni.showToast({
|
||
title: this.$tr('预约看房申请无需支付定金'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
referenceTypeNo = detailPaymentContext.referenceTypeNo || referenceTypeNo
|
||
billNo = detailPaymentContext.billNo || billNo
|
||
}
|
||
if (!pendingPaymentOrder && billNo) {
|
||
pendingPaymentOrder = await this.getPendingPaymentOrder(billNo).catch(() => null)
|
||
}
|
||
}
|
||
|
||
if (billNo && !bill) {
|
||
const billDetail = this.normalizeBillDetail(await accountQueryBill({
|
||
billNo
|
||
}, this.language))
|
||
if (billDetail && Object.keys(billDetail).length) {
|
||
bill = billDetail
|
||
billNo = this.getBillNo(bill) || billNo
|
||
this.$set(this.reservationBillDetailMap, billNo, billDetail)
|
||
referenceTypeNo = referenceTypeNo || String(bill.referenceTypeNo || '').trim()
|
||
if (!pendingPaymentOrder) {
|
||
pendingPaymentOrder = this.buildPendingPaymentOrderFromBillDetail(bill)
|
||
}
|
||
}
|
||
}
|
||
|
||
const shouldQueryBillListByReference = !!referenceTypeNo && !(paymentMode === 'bill' && billNo)
|
||
if (shouldQueryBillListByReference) {
|
||
const billRes = await accountQueryBills({
|
||
referenceTypeNo,
|
||
status: 10,
|
||
paymentStatus: 0,
|
||
ProviderType: 'WechatMiniProgram'
|
||
})
|
||
const billList = this.normalizeBillList(billRes)
|
||
if (!Array.isArray(billList) || !billList.length) {
|
||
if (pendingPaymentOrder && billNo) {
|
||
bill = {
|
||
billNo,
|
||
no: billNo,
|
||
referenceTypeNo,
|
||
remainingAmount: Number(pendingPaymentOrder.amount || 0),
|
||
amount: Number(pendingPaymentOrder.amount || 0),
|
||
billName: description
|
||
}
|
||
} else {
|
||
uni.showToast({ title: emptyBillMessage, icon: 'none' })
|
||
return
|
||
}
|
||
} else {
|
||
bill = billNo
|
||
? (billList.find(item => String(this.getBillNo(item) || '').trim() === billNo) || null)
|
||
: null
|
||
if (!bill) {
|
||
bill = billList[0]
|
||
}
|
||
billNo = this.getBillNo(bill)
|
||
referenceTypeNo = referenceTypeNo || String(bill.referenceTypeNo || '').trim()
|
||
if (!pendingPaymentOrder && billNo) {
|
||
pendingPaymentOrder = await this.getPendingPaymentOrder(billNo).catch(() => null)
|
||
}
|
||
if (!pendingPaymentOrder) {
|
||
pendingPaymentOrder = this.buildPendingPaymentOrderFromBillDetail(bill)
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!billNo) {
|
||
uni.showToast({ title: emptyBillMessage, icon: 'none' })
|
||
return
|
||
}
|
||
|
||
uni.showLoading({ title: this.$tr('正在拉起支付...') })
|
||
|
||
const reusablePendingPaymentOrder = pendingPaymentOrder && !this.isApplicationPendingPaymentExpired({
|
||
pendingPaymentOrder
|
||
})
|
||
? pendingPaymentOrder
|
||
: null
|
||
|
||
if (reusablePendingPaymentOrder && reusablePendingPaymentOrder.cashier && reusablePendingPaymentOrder.cashier.paymentInfo) {
|
||
await this.requestWechatPayment(reusablePendingPaymentOrder.cashier.paymentInfo)
|
||
} else {
|
||
if (!referenceTypeNo || !bill) {
|
||
throw new Error(this.$tr('获取支付参数失败'))
|
||
}
|
||
const wechatCode = await this.getWechatLoginCode()
|
||
const payRes = await paymentBillPay({
|
||
referenceTypeNo,
|
||
billNo,
|
||
payMethod: 'Wechat',
|
||
amount: Number((bill && (bill.remainingAmount || bill.amount)) || 0),
|
||
description: (bill && (bill.billName || bill.name)) || description,
|
||
ExtraInfo: wechatCode
|
||
})
|
||
|
||
const payload = payRes && payRes.data ? payRes.data : payRes
|
||
const cashier = payload && payload.cashier ? payload.cashier : {}
|
||
await this.requestWechatPayment(cashier.paymentInfo)
|
||
}
|
||
|
||
uni.showToast({ title: this.$tr('支付成功'), icon: 'success' })
|
||
} catch (error) {
|
||
const errMsg = error && (error.errMsg || error.message || error.msg) ? (error.errMsg || error.message || error.msg) : this.$tr('支付失败')
|
||
if (/cancel/i.test(errMsg)) {
|
||
uni.showToast({ title: this.$tr('已取消支付'), icon: 'none' })
|
||
} else {
|
||
uni.showToast({ title: errMsg, icon: 'none' })
|
||
}
|
||
} finally {
|
||
uni.hideLoading()
|
||
setTimeout(() => {
|
||
this.refreshStatus()
|
||
}, 1500)
|
||
}
|
||
},
|
||
async confirmAndPayDeposit() {
|
||
const paymentContext = this.pendingDepositPaymentContext || {
|
||
referenceTypeNo: this.pendingDepositReferenceTypeNo
|
||
}
|
||
this.showDepositNotice = false
|
||
this.pendingDepositReferenceTypeNo = ''
|
||
this.pendingDepositPaymentContext = null
|
||
return this.payByPaymentContext(paymentContext)
|
||
},
|
||
handleCurrentStepAction(event) {
|
||
const dataset = event && event.currentTarget ? event.currentTarget.dataset || {} : {}
|
||
const step = Number(dataset.step || 0)
|
||
const referenceTypeNo = dataset.referenceTypeNo || dataset.referenceTypeNo === 0
|
||
? String(dataset.referenceTypeNo)
|
||
: ''
|
||
const contractNo = String(dataset.refNo || '').trim()
|
||
const currentItem = (Array.isArray(this.list) ? this.list : []).find(item => {
|
||
return String(this.resolveReferenceTypeNo(item)) === referenceTypeNo
|
||
})
|
||
|
||
if (step === RESERVATION_FLOW_STEP.BILL) {
|
||
if (!referenceTypeNo) {
|
||
uni.showToast({
|
||
title: this.$tr('申请单编号缺失'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const paymentContext = this.resolveReservationBillPaymentContext(currentItem || {}, {
|
||
referenceTypeNo
|
||
})
|
||
this.openPaymentMethodSelector(paymentContext)
|
||
return
|
||
}
|
||
|
||
if (step === RESERVATION_FLOW_STEP.CONTRACT) {
|
||
if (this.isReservationContractAwaitingReview(currentItem || {})) {
|
||
return
|
||
}
|
||
if (!contractNo) {
|
||
uni.showToast({
|
||
title: this.$tr('合同编号缺失'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pkgService/contract_sign/contract_sign?no=${encodeURIComponent(contractNo)}`
|
||
})
|
||
return
|
||
}
|
||
|
||
if (step === RESERVATION_FLOW_STEP.CHECKIN) {
|
||
uni.reLaunch({
|
||
url: '/pages/index/index?pageId=stay-service'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (step === RESERVATION_FLOW_STEP.INSPECTION) {
|
||
if (this.isReservationInspectionPendingReview(currentItem || {})) {
|
||
return
|
||
}
|
||
const orderNo = String(dataset.orderNo || '').trim()
|
||
uni.navigateTo({
|
||
url: orderNo
|
||
? `/pkgService/room_inspection/room_inspection?orderNo=${encodeURIComponent(orderNo)}`
|
||
: '/pkgService/room_inspection/room_inspection'
|
||
})
|
||
}
|
||
},
|
||
handleReservationOrderStepAction(order = {}) {
|
||
const step = this.getReservationCurrentStepValue(order)
|
||
|
||
if (step === RESERVATION_FLOW_STEP.BILL) {
|
||
const paymentContext = this.resolveReservationBillPaymentContext(order)
|
||
this.openDepositNoticeForPayment(paymentContext)
|
||
return
|
||
}
|
||
|
||
if (step === RESERVATION_FLOW_STEP.CONTRACT) {
|
||
if (this.isReservationContractAwaitingReview(order)) {
|
||
return
|
||
}
|
||
const contractNo = String(order && order.reservationCurrentNode && order.reservationCurrentNode.refNo || '').trim()
|
||
if (!contractNo) {
|
||
uni.showToast({
|
||
title: this.$tr('合同编号缺失'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pkgService/contract_sign/contract_sign?no=${encodeURIComponent(contractNo)}`
|
||
})
|
||
return
|
||
}
|
||
|
||
if (step === RESERVATION_FLOW_STEP.CHECKIN) {
|
||
uni.reLaunch({
|
||
url: '/pages/index/index?pageId=stay-service'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (step === RESERVATION_FLOW_STEP.INSPECTION) {
|
||
if (this.isReservationInspectionPendingReview(order)) {
|
||
return
|
||
}
|
||
const orderNo = getReservationOrderNo(order)
|
||
uni.navigateTo({
|
||
url: orderNo
|
||
? `/pkgService/room_inspection/room_inspection?orderNo=${encodeURIComponent(orderNo)}`
|
||
: '/pkgService/room_inspection/room_inspection'
|
||
})
|
||
}
|
||
},
|
||
goToApplicationDetail(item = {}) {
|
||
const code = item.detailCode || ''
|
||
if (!code) {
|
||
return
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pkgService/application_detail/application_detail?code=${encodeURIComponent(code)}&payload=${encodeURIComponent(JSON.stringify(item))}`
|
||
})
|
||
},
|
||
goSelectRoom() {
|
||
uni.reLaunch({
|
||
url: '/pages/index/index?index=0&pageId=select-room'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.my-application-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: linear-gradient(180deg, #F0F6FF 0%, #F8FAFF 100%);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.page-header {
|
||
flex-shrink: 0;
|
||
padding-bottom: 20rpx;
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 44rpx;
|
||
font-weight: 800;
|
||
letter-spacing: 1rpx;
|
||
}
|
||
|
||
.list-scroll-view {
|
||
flex: 1;
|
||
height: 0;
|
||
}
|
||
|
||
.application-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);
|
||
}
|
||
|
||
.reservation-flow-panel {
|
||
background: #ffffff;
|
||
border-radius: 32rpx;
|
||
padding: 32rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.02), 0 0 2rpx rgba(0, 0, 0, 0.05);
|
||
border: none;
|
||
}
|
||
|
||
.reservation-flow-panel__head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 20rpx;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.reservation-flow-panel__title-wrap {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.reservation-flow-panel__title {
|
||
font-size: 32rpx;
|
||
font-weight: 800;
|
||
color: #0F172A;
|
||
letter-spacing: 0.5rpx;
|
||
}
|
||
|
||
.reservation-flow-panel__desc {
|
||
margin-top: 6rpx;
|
||
font-size: 22rpx;
|
||
color: #64748B;
|
||
}
|
||
|
||
.reservation-flow-panel__refresh {
|
||
flex-shrink: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 132rpx;
|
||
height: 56rpx;
|
||
padding: 0 20rpx;
|
||
border-radius: 16rpx;
|
||
background: rgba($brand-primary, 0.08);
|
||
color: $brand-primary;
|
||
border: 1rpx solid rgba($brand-primary, 0.16);
|
||
font-size: 22rpx;
|
||
}
|
||
|
||
.reservation-order-scroll {
|
||
width: 100%;
|
||
white-space: nowrap;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.reservation-order-tabs {
|
||
display: inline-flex;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.reservation-order-chip {
|
||
padding: 16rpx 24rpx;
|
||
border-radius: 16rpx;
|
||
background: #F8FAFC;
|
||
border: 1rpx solid #E2E8F0;
|
||
transition: all 0.2s ease;
|
||
min-width: 200rpx;
|
||
}
|
||
|
||
.reservation-order-chip--active {
|
||
background: rgba($brand-primary, 0.08);
|
||
border-color: rgba($brand-primary, 0.3);
|
||
}
|
||
|
||
.reservation-order-chip__title {
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #334155;
|
||
}
|
||
|
||
.reservation-order-chip--active .reservation-order-chip__title {
|
||
color: $brand-primary;
|
||
}
|
||
|
||
.reservation-grid-info {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 20rpx;
|
||
padding: 24rpx;
|
||
background: #F8FAFC;
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
.grid-info-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6rpx;
|
||
}
|
||
|
||
.grid-info-label {
|
||
font-size: 20rpx;
|
||
color: #64748B;
|
||
letter-spacing: 0.5rpx;
|
||
}
|
||
|
||
.grid-info-value {
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #0F172A;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.tn-font-mono {
|
||
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
|
||
}
|
||
|
||
.reservation-v-steps {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-left: 8rpx;
|
||
}
|
||
|
||
.v-step-item {
|
||
display: flex;
|
||
position: relative;
|
||
min-height: 100rpx;
|
||
}
|
||
|
||
.v-step-indicator {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-right: 24rpx;
|
||
width: 48rpx;
|
||
}
|
||
|
||
.v-step-badge {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
border-radius: 16rpx;
|
||
background: #F1F5F9;
|
||
color: #64748B;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 20rpx;
|
||
font-weight: 700;
|
||
z-index: 2;
|
||
}
|
||
|
||
.v-step-item--current .v-step-badge {
|
||
background: $brand-primary;
|
||
color: #FFFFFF;
|
||
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.2);
|
||
}
|
||
|
||
.v-step-line {
|
||
flex: 1;
|
||
width: 2rpx;
|
||
background: #E2E8F0;
|
||
margin-top: 8rpx;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.v-step-content {
|
||
flex: 1;
|
||
padding-bottom: 40rpx;
|
||
padding-top: 4rpx;
|
||
}
|
||
|
||
.v-step-item--last .v-step-content {
|
||
padding-bottom: 0;
|
||
}
|
||
|
||
.v-step-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.v-step-title {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #334155;
|
||
}
|
||
|
||
.v-step-item--current .v-step-title {
|
||
color: $brand-primary;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.v-step-bill-card {
|
||
padding: 18rpx 20rpx;
|
||
background: rgba($brand-primary, 0.06);
|
||
border: 1rpx solid rgba($brand-primary, 0.14);
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
.v-step-bill-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.v-step-bill-row + .v-step-bill-row {
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
.v-step-bill-label {
|
||
flex-shrink: 0;
|
||
font-size: 22rpx;
|
||
color: #64748B;
|
||
}
|
||
|
||
.v-step-bill-value {
|
||
font-size: 24rpx;
|
||
color: #334155;
|
||
text-align: right;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.v-step-bill-value--price {
|
||
color: $brand-primary;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.v-step-hint-card {
|
||
padding: 16rpx 20rpx;
|
||
background: rgba($brand-primary, 0.06);
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
.v-step-hint-text {
|
||
font-size: 22rpx;
|
||
line-height: 1.6;
|
||
color: #64748B;
|
||
}
|
||
|
||
.v-step-current-tag {
|
||
padding: 4rpx 16rpx;
|
||
background: #FEF3C7;
|
||
color: #C2410C;
|
||
font-size: 20rpx;
|
||
font-weight: 700;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.step-pay-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0 32rpx;
|
||
height: 64rpx;
|
||
background: $brand-primary;
|
||
color: #FFFFFF;
|
||
border-radius: 16rpx;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
transition: opacity 0.2s;
|
||
}
|
||
|
||
.step-pay-btn:active {
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.step-refresh-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0 32rpx;
|
||
height: 64rpx;
|
||
background: rgba($brand-primary, 0.08);
|
||
color: $brand-primary;
|
||
border: 1rpx solid rgba($brand-primary, 0.18);
|
||
border-radius: 16rpx;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.step-refresh-btn:active {
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.card-title-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.title-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
border-radius: 12rpx;
|
||
background: rgba($brand-primary, 0.08);
|
||
color: $brand-primary;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #1A2233;
|
||
}
|
||
|
||
.card-code,
|
||
.info-label,
|
||
.remark-label {
|
||
color: #6A7A99;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.payment-status-banner {
|
||
padding: 20rpx 24rpx;
|
||
border-radius: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16rpx;
|
||
border: 1rpx solid transparent;
|
||
}
|
||
|
||
.payment-status-banner__label {
|
||
font-size: 24rpx;
|
||
color: #64748B;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.payment-status-banner__value {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.payment-status-banner--warning {
|
||
background: linear-gradient(180deg, rgba(255, 247, 237, 0.96) 0%, rgba(255, 251, 245, 0.98) 100%);
|
||
border-color: rgba(249, 115, 22, 0.16);
|
||
}
|
||
|
||
.payment-status-banner--warning .payment-status-banner__value {
|
||
color: #C2410C;
|
||
}
|
||
|
||
.payment-status-banner--success {
|
||
background: linear-gradient(180deg, rgba(236, 253, 245, 0.96) 0%, rgba(240, 253, 250, 0.98) 100%);
|
||
border-color: rgba(16, 185, 129, 0.16);
|
||
}
|
||
|
||
.payment-status-banner--success .payment-status-banner__value {
|
||
color: #047857;
|
||
}
|
||
|
||
.payment-status-banner--info {
|
||
background: linear-gradient(180deg, rgba(239, 246, 255, 0.96) 0%, rgba(248, 250, 252, 0.98) 100%);
|
||
border-color: rgba(59, 130, 246, 0.14);
|
||
}
|
||
|
||
.payment-status-banner--info .payment-status-banner__value {
|
||
color: #1D4ED8;
|
||
}
|
||
|
||
.payment-status-banner--danger {
|
||
background: linear-gradient(180deg, rgba(254, 242, 242, 0.96) 0%, rgba(255, 245, 245, 0.98) 100%);
|
||
border-color: rgba(239, 68, 68, 0.16);
|
||
}
|
||
|
||
.payment-status-banner--danger .payment-status-banner__value {
|
||
color: #B91C1C;
|
||
}
|
||
|
||
.payment-status-banner--neutral {
|
||
background: linear-gradient(180deg, rgba(241, 245, 249, 0.96) 0%, rgba(248, 250, 252, 0.98) 100%);
|
||
border-color: rgba(148, 163, 184, 0.16);
|
||
}
|
||
|
||
.payment-status-banner--neutral .payment-status-banner__value {
|
||
color: #475569;
|
||
}
|
||
|
||
.status-pending {
|
||
color: #b45309;
|
||
background: rgba(245, 158, 11, 0.12);
|
||
}
|
||
|
||
.status-approved {
|
||
color: $brand-primary;
|
||
background: rgba($brand-primary, 0.12);
|
||
}
|
||
|
||
.status-rejected {
|
||
color: #DC2626;
|
||
background: rgba(220, 38, 38, 0.12);
|
||
}
|
||
|
||
.status-finished {
|
||
color: #64748B;
|
||
background: #F1F5F9;
|
||
}
|
||
|
||
.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,
|
||
.reminder-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,
|
||
.reminder-title {
|
||
margin-bottom: 12rpx;
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #c2410c;
|
||
}
|
||
|
||
.reminder-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16rpx;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.reminder-title {
|
||
margin-bottom: 0;
|
||
color: $brand-primary;
|
||
}
|
||
|
||
.reminder-refresh-btn {
|
||
flex-shrink: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 112rpx;
|
||
height: 52rpx;
|
||
padding: 0 18rpx;
|
||
border-radius: 14rpx;
|
||
background: rgba($brand-primary, 0.08);
|
||
border: 1rpx solid rgba($brand-primary, 0.16);
|
||
color: $brand-primary;
|
||
font-size: 22rpx;
|
||
}
|
||
|
||
.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,
|
||
.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);
|
||
}
|
||
|
||
.empty-panel .empty-action {
|
||
width: 80%;
|
||
}
|
||
|
||
.empty-desc {
|
||
color: #94A3B8;
|
||
font-size: 28rpx;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.animation-fade-in {
|
||
animation: fadeIn 0.4s ease forwards;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(20rpx);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
/* 隐藏 scroll-view 滚动条 */
|
||
::-webkit-scrollbar {
|
||
display: none;
|
||
width: 0 !important;
|
||
height: 0 !important;
|
||
-webkit-appearance: none;
|
||
background: transparent;
|
||
}
|
||
|
||
.deposit-notice-popup {
|
||
background: #FFFFFF;
|
||
border-radius: 32rpx 32rpx 0 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 80vh;
|
||
}
|
||
|
||
.popup-header {
|
||
padding: 18rpx 88rpx 20rpx;
|
||
text-align: center;
|
||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(251, 253, 255, 0.96) 100%);
|
||
}
|
||
|
||
.popup-handle {
|
||
width: 88rpx;
|
||
height: 8rpx;
|
||
margin: 0 auto 18rpx;
|
||
border-radius: 999rpx;
|
||
background: rgba(148, 163, 184, 0.35);
|
||
}
|
||
|
||
.popup-title {
|
||
font-size: 34rpx;
|
||
font-weight: 600;
|
||
line-height: 1.2;
|
||
letter-spacing: 1rpx;
|
||
color: #1A2233;
|
||
}
|
||
|
||
.popup-body {
|
||
flex: 1;
|
||
padding: 28rpx 28rpx 24rpx;
|
||
overflow-y: auto;
|
||
background: linear-gradient(180deg, #F7FAFF 0%, #F2F5FC 100%);
|
||
}
|
||
|
||
.notice-content {
|
||
padding-bottom: 24rpx;
|
||
}
|
||
|
||
.payment-sheet {
|
||
position: relative;
|
||
padding: 28rpx;
|
||
border-radius: 32rpx;
|
||
background:
|
||
radial-gradient(circle at top right, rgba($brand-primary, 0.16) 0, rgba($brand-primary, 0) 38%),
|
||
linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(246, 250, 255, 0.96) 100%);
|
||
border: 1rpx solid rgba($brand-primary, 0.1);
|
||
box-shadow: 0 24rpx 60rpx rgba($brand-primary, 0.1);
|
||
}
|
||
|
||
.payment-hero {
|
||
padding: 18rpx 6rpx 14rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.payment-hero-label {
|
||
font-size: 24rpx;
|
||
color: #7C8CA5;
|
||
letter-spacing: 2rpx;
|
||
}
|
||
|
||
.payment-hero-amount {
|
||
margin-top: 14rpx;
|
||
font-size: 68rpx;
|
||
line-height: 1;
|
||
font-weight: 700;
|
||
color: #183B73;
|
||
}
|
||
|
||
.payment-hero-hint {
|
||
margin-top: 16rpx;
|
||
font-size: 24rpx;
|
||
color: #6F809C;
|
||
}
|
||
|
||
.payment-sync-group {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 14rpx;
|
||
margin-top: 18rpx;
|
||
}
|
||
|
||
.payment-sync-chip {
|
||
padding: 10rpx 18rpx;
|
||
border-radius: 999rpx;
|
||
background: rgba($brand-primary, 0.08);
|
||
color: #4C6A95;
|
||
font-size: 22rpx;
|
||
}
|
||
|
||
.payment-detail-panel {
|
||
margin-top: 26rpx;
|
||
padding: 24rpx;
|
||
border-radius: 26rpx;
|
||
background: rgba(255, 255, 255, 0.82);
|
||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||
box-shadow: inset 0 1rpx 0 rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.payment-detail-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.payment-detail-title {
|
||
font-size: 28rpx;
|
||
line-height: 1.2;
|
||
color: #22324D;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.payment-detail-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.payment-detail-row {
|
||
padding: 22rpx 4rpx;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 28rpx;
|
||
}
|
||
|
||
.payment-detail-row + .payment-detail-row {
|
||
border-top: 1rpx solid rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.payment-detail-label {
|
||
flex: 0 0 148rpx;
|
||
font-size: 22rpx;
|
||
color: #7C8CA5;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.payment-detail-value {
|
||
flex: 1;
|
||
font-size: 26rpx;
|
||
line-height: 1.4;
|
||
color: #22324D;
|
||
word-break: break-all;
|
||
font-weight: 600;
|
||
text-align: right;
|
||
}
|
||
|
||
.popup-footer {
|
||
padding: 18rpx 28rpx calc(20rpx + env(safe-area-inset-bottom));
|
||
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.92) 26%, #FFFFFF 100%);
|
||
display: flex;
|
||
justify-content: center;
|
||
backdrop-filter: blur(8rpx);
|
||
}
|
||
|
||
.notice-action-btn {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
padding: 0 40rpx;
|
||
border-radius: 999rpx;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
background: linear-gradient(135deg, $brand-primary 0%, #1F6FE5 100%);
|
||
color: #ffffff;
|
||
box-shadow: 0 14rpx 28rpx rgba($brand-primary, 0.22);
|
||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||
}
|
||
|
||
.notice-action-btn:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.offline-payment-sheet {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.offline-payment-intro {
|
||
padding: 24rpx 28rpx;
|
||
border-radius: 28rpx;
|
||
background: linear-gradient(180deg, rgba($brand-primary, 0.08) 0%, rgba(31, 111, 229, 0.04) 100%);
|
||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.offline-payment-intro__zh {
|
||
font-size: 28rpx;
|
||
line-height: 1.6;
|
||
color: #22324D;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.offline-payment-intro__en {
|
||
margin-top: 12rpx;
|
||
font-size: 22rpx;
|
||
line-height: 1.7;
|
||
color: #5B6B86;
|
||
}
|
||
|
||
.offline-payment-card {
|
||
padding: 24rpx 28rpx;
|
||
border-radius: 28rpx;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||
box-shadow: 0 16rpx 36rpx rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.offline-payment-row {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
padding: 22rpx 0;
|
||
}
|
||
|
||
.offline-payment-row + .offline-payment-row {
|
||
border-top: 1rpx solid rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.offline-payment-label-group,
|
||
.offline-payment-value-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.offline-payment-label {
|
||
font-size: 24rpx;
|
||
line-height: 1.5;
|
||
color: #6B7A92;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.offline-payment-label-en {
|
||
font-size: 20rpx;
|
||
line-height: 1.6;
|
||
color: #8B97AA;
|
||
}
|
||
|
||
.offline-payment-value {
|
||
font-size: 28rpx;
|
||
line-height: 1.6;
|
||
color: #22324D;
|
||
font-weight: 600;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.offline-payment-value-en {
|
||
font-size: 22rpx;
|
||
line-height: 1.7;
|
||
color: #4F5F79;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.offline-payment-note-title {
|
||
font-size: 28rpx;
|
||
line-height: 1.4;
|
||
color: #22324D;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.offline-payment-note-title-en {
|
||
margin-top: 8rpx;
|
||
font-size: 22rpx;
|
||
line-height: 1.6;
|
||
color: #6B7A92;
|
||
}
|
||
|
||
.offline-payment-note {
|
||
margin-top: 22rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.offline-payment-note__zh {
|
||
font-size: 25rpx;
|
||
line-height: 1.7;
|
||
color: #22324D;
|
||
}
|
||
|
||
.offline-payment-note__en {
|
||
font-size: 21rpx;
|
||
line-height: 1.7;
|
||
color: #5B6B86;
|
||
}
|
||
|
||
</style>
|