feat: 重构核心租住流程页面与交互体验
统一注册、看房、申请与入住等核心页面的视觉和流程衔接,补齐支付、签约与状态流转闭环,提升移动端操作连贯性。
This commit is contained in:
@@ -248,48 +248,50 @@
|
||||
<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-title">{{ $tr('租房定金须知') }}</view>
|
||||
<view class="popup-handle"></view>
|
||||
<view class="popup-title">{{ getPaymentNoticeTitle() }}</view>
|
||||
</view>
|
||||
<scroll-view scroll-y class="popup-body" @scrolltolower="onNoticeScrollToBottom">
|
||||
<view class="popup-body">
|
||||
<view class="notice-content">
|
||||
<view class="notice-item">
|
||||
<view class="notice-item-zh">1、缴纳线上定金可锁定房源并预约专属看房,平台收取定金后该房源临时下架,不再接待其他客户看房。</view>
|
||||
<view class="notice-item-en">Payment of the online deposit reserves the housing unit and arranges exclusive on-site viewing for you. After receiving the deposit, we will temporarily remove the property from the rental list and stop arranging viewings for other clients.</view>
|
||||
</view>
|
||||
<view class="notice-item">
|
||||
<view class="notice-item-zh">2、请于约定时间完成实地看房,并在看房结束后 3 个工作日内签署租赁合同;若逾期未看房或未完成签约,视作您主动放弃房源,定金不予退还。</view>
|
||||
<view class="notice-item-en">You shall complete the on-site viewing at the agreed time and sign the rental contract within 3 working days after the viewing. Failure to complete the viewing or sign the contract within the time limit shall be deemed your voluntary waiver of the property, and the deposit will not be refunded.</view>
|
||||
</view>
|
||||
<view class="notice-item">
|
||||
<view class="notice-item-zh">3、如因租客个人原因取消看房、看房后不愿承租房屋或拒绝签署租赁合同,定金不予退还。</view>
|
||||
<view class="notice-item-en">The deposit is non-refundable if you cancel the viewing, refuse to rent the property or decline to sign the contract due to personal reasons.</view>
|
||||
</view>
|
||||
<view class="notice-item">
|
||||
<view class="notice-item-zh">4、若因我方房源信息虚假、房屋无法正常交付等我方过错,造成无法看房或签约,定金将全额按原支付路径退还。</view>
|
||||
<view class="notice-item-en">If the viewing or contract signing cannot be conducted due to our liabilities such as false property information or failure to deliver the housing normally, the full deposit will be refunded via your original payment channel.</view>
|
||||
</view>
|
||||
<view class="notice-item">
|
||||
<view class="notice-item-zh">5、待双方正式签署租赁合同后,该定金可直接抵扣首期租金。</view>
|
||||
<view class="notice-item-en">After both parties formally sign the rental contract, this deposit may be directly deducted from your initial rent.</view>
|
||||
</view>
|
||||
<view class="notice-item">
|
||||
<view class="notice-item-zh">6、您支付定金,即代表已完整知晓房源全部信息,并自愿接受本须知全部条款约束。</view>
|
||||
<view class="notice-item-en">By making the deposit payment, you confirm that you have fully reviewed all property information and voluntarily accept all terms and conditions set forth in this notice.</view>
|
||||
<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>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="popup-footer">
|
||||
<view
|
||||
class="action-btn"
|
||||
:class="{ 'action-btn--disabled': !hasReadNotice }"
|
||||
@click="confirmAndPayDeposit"
|
||||
>
|
||||
<text class="tn-text-bold">{{ $tr('同意并支付') }}</text>
|
||||
<text v-if="!hasReadNotice" class="tn-margin-left-xs tn-text-sm">({{ $tr('请滑动阅读至底部') }})</text>
|
||||
<view class="notice-action-btn" @click="confirmAndPayDeposit">
|
||||
<text class="tn-text-bold">{{ getPaymentNoticeConfirmText() }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -298,7 +300,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { applicationFormMePage, accountQueryBill, accountQueryBills, contractDetail, orderSearchedMePage, orderSearchedReservationFlow, paymentBillPay, paymentPayingBills, roomInspectionDetail, userDetailCancel } from '@/api/index.js'
|
||||
import { applicationFormDetail, applicationFormMePage, accountQueryBill, accountQueryBillPreview, accountQueryBills, contractDetail, orderSearchedMePage, orderSearchedReservationFlow, paymentBillPay, paymentPayingBills, roomInspectionDetail, userDetailCancel } from '@/api/index.js'
|
||||
import { getMyApplicationList, saveActiveApplicationCode, saveMyApplicationList } from '@/utils/app-state'
|
||||
import {
|
||||
RESERVATION_FLOW_STEP,
|
||||
@@ -311,6 +313,7 @@ import {
|
||||
parseReservationFlowResponse
|
||||
} from '@/utils/reservation-flow'
|
||||
import { getOrderStatusCode, normalizeBookingList, parseOrderListResponse } from '@/utils/booking-helper'
|
||||
import { localizePayMethodText } from '@/utils/select-room-normalizers'
|
||||
|
||||
function formatPageResponse(response) {
|
||||
const payload = response && response.data ? response.data : response
|
||||
@@ -420,9 +423,12 @@ export default {
|
||||
loadingMore: false,
|
||||
hasMore: true,
|
||||
showDepositNotice: false,
|
||||
hasReadNotice: false,
|
||||
pendingDepositReferenceTypeNo: '',
|
||||
pendingDepositPaymentContext: null,
|
||||
paymentNoticeSourceItem: null,
|
||||
paymentNoticeSourceLoading: false,
|
||||
paymentNoticeBillLoading: false,
|
||||
paymentNoticeBillDetail: null,
|
||||
reservationBillDetailMap: {},
|
||||
reservationContractDetailMap: {},
|
||||
reservationInspectionDetailMap: {},
|
||||
@@ -599,6 +605,57 @@ export default {
|
||||
}
|
||||
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)) {
|
||||
@@ -852,6 +909,20 @@ export default {
|
||||
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
|
||||
},
|
||||
@@ -1390,20 +1461,29 @@ export default {
|
||||
&& (this.shouldPayDeposit(item) || this.isReservationBillStep(item))
|
||||
&& this.resolveReferenceTypeNo(item)
|
||||
},
|
||||
canCancelApplication(item = {}) {
|
||||
return !!(this.resolveReferenceTypeNo(item)
|
||||
&& Number(item.status) === 0
|
||||
&& (item.isViewingApplication || Number(item.payStatus) !== 10))
|
||||
},
|
||||
canCancelOrder(item = {}) {
|
||||
return this.shouldPayDeposit(item)
|
||||
&& this.resolveReferenceTypeNo(item)
|
||||
return this.canCancelApplication(item)
|
||||
},
|
||||
getCancelButtonText(item = {}) {
|
||||
return this.cancelingReferenceTypeNo === this.resolveReferenceTypeNo(item)
|
||||
? this.$tr('取消中...')
|
||||
: this.$tr('取消订单')
|
||||
: this.$tr('取消申请')
|
||||
},
|
||||
confirmCancelOrder() {
|
||||
getCancelOrderContent(item = {}) {
|
||||
return item.isViewingApplication
|
||||
? this.$tr('取消后将关闭当前预约看房申请,是否继续?')
|
||||
: this.$tr('取消后将关闭当前申请单,是否继续?')
|
||||
},
|
||||
confirmCancelOrder(item = {}) {
|
||||
return new Promise((resolve) => {
|
||||
uni.showModal({
|
||||
title: this.$tr('取消订单'),
|
||||
content: this.$tr('取消后将关闭当前未支付的定金订单,是否继续?'),
|
||||
title: this.$tr('取消申请'),
|
||||
content: this.getCancelOrderContent(item),
|
||||
confirmColor: '#ef4444',
|
||||
success: (res) => resolve(!!(res && res.confirm)),
|
||||
fail: () => resolve(false)
|
||||
@@ -1439,33 +1519,34 @@ export default {
|
||||
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()
|
||||
this.confirmCancelOrder(currentItem)
|
||||
.then((confirmed) => {
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
this.cancelingReferenceTypeNo = referenceTypeNo
|
||||
return userDetailCancel({
|
||||
detailCode: referenceTypeNo,
|
||||
code: referenceTypeNo,
|
||||
referenceTypeNo
|
||||
detailCode: referenceTypeNo
|
||||
}, this.language)
|
||||
.then(async () => {
|
||||
this.syncCanceledItem(referenceTypeNo)
|
||||
await this.reloadList(true)
|
||||
this.$emit('refresh-shell-state')
|
||||
uni.showToast({
|
||||
title: this.$tr('订单已取消'),
|
||||
title: this.$tr('申请已取消'),
|
||||
icon: 'success'
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
uni.showToast({
|
||||
title: (error && (error.message || error.msg)) || this.$tr('取消订单失败'),
|
||||
title: (error && (error.message || error.msg)) || this.$tr('取消申请失败'),
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
@@ -1511,35 +1592,339 @@ export default {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.pendingDepositReferenceTypeNo = referenceTypeNo
|
||||
this.hasReadNotice = false
|
||||
this.showDepositNotice = true
|
||||
const currentItem = (Array.isArray(this.list) ? this.list : []).find((item) => {
|
||||
return String(this.resolveReferenceTypeNo(item)) === referenceTypeNo
|
||||
}) || {}
|
||||
this.openDepositNoticeForPayment({
|
||||
referenceTypeNo,
|
||||
description: this.$tr('支付定金'),
|
||||
mode: 'deposit',
|
||||
sourceItem: currentItem
|
||||
})
|
||||
},
|
||||
onNoticeScrollToBottom() {
|
||||
this.hasReadNotice = true
|
||||
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(referenceTypeNo = '', fallbackSource = null) {
|
||||
const fallbackReferenceTypeNo = fallbackSource ? this.getApplicationDetailCodeFromOrder(fallbackSource) : ''
|
||||
const normalizedReferenceTypeNo = String(referenceTypeNo || fallbackReferenceTypeNo || '').trim()
|
||||
if (!normalizedReferenceTypeNo) {
|
||||
this.paymentNoticeSourceLoading = false
|
||||
this.paymentNoticeSourceItem = fallbackSource || this.paymentNoticeSourceItem || null
|
||||
return Promise.resolve(this.paymentNoticeSourceItem)
|
||||
}
|
||||
|
||||
this.paymentNoticeSourceLoading = true
|
||||
return this.resolveApplicationDetailPaymentContext(normalizedReferenceTypeNo)
|
||||
.then((context) => {
|
||||
const detail = context && context.detail ? context.detail : null
|
||||
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: String(context.referenceTypeNo || '').trim(),
|
||||
referenceTypeNo: paymentReferenceTypeNo,
|
||||
orderNo: paymentOrderNo,
|
||||
billNo: String(context.billNo || '').trim(),
|
||||
paymentNo: String(context.paymentNo || '').trim(),
|
||||
description: String(context.description || '').trim()
|
||||
description: String(context.description || '').trim(),
|
||||
mode: this.getPaymentContextMode(context)
|
||||
}
|
||||
this.paymentNoticeSourceItem = paymentSourceItem
|
||||
this.pendingDepositReferenceTypeNo = this.pendingDepositPaymentContext.referenceTypeNo
|
||||
this.hasReadNotice = false
|
||||
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(nextReferenceTypeNo, mergedSource)
|
||||
})
|
||||
.catch(() => {
|
||||
this.loadPaymentNoticeSourceDetail(this.pendingDepositPaymentContext.referenceTypeNo, this.paymentNoticeSourceItem)
|
||||
})
|
||||
this.showDepositNotice = true
|
||||
},
|
||||
closeDepositNotice() {
|
||||
this.showDepositNotice = false
|
||||
this.pendingDepositReferenceTypeNo = ''
|
||||
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,
|
||||
@@ -1579,7 +1964,9 @@ export default {
|
||||
referenceTypeNo: pickFirst(candidateReferenceTypeNos),
|
||||
billNo: pickFirst(candidateBillNos),
|
||||
paymentNo: pickFirst(candidatePaymentNos),
|
||||
description: String(currentNode.stepDesc || fallbackContext.description || '').trim() || this.$tr('支付定金')
|
||||
description: String(currentNode.stepDesc || fallbackContext.description || '').trim() || this.$tr('账单支付'),
|
||||
mode: isBillStep ? 'bill' : this.getPaymentContextMode(fallbackContext),
|
||||
sourceItem: order
|
||||
}
|
||||
},
|
||||
resolvePendingPaymentContextByPaymentNo(paymentNo = '') {
|
||||
@@ -1632,14 +2019,21 @@ export default {
|
||||
})
|
||||
},
|
||||
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() || this.$tr('支付定金')
|
||||
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: this.$tr('申请单编号缺失'),
|
||||
title: missingContextMessage,
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
@@ -1652,6 +2046,20 @@ export default {
|
||||
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)
|
||||
@@ -1663,6 +2071,24 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1678,7 +2104,8 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
if (referenceTypeNo) {
|
||||
const shouldQueryBillListByReference = !!referenceTypeNo && !(paymentMode === 'bill' && billNo)
|
||||
if (shouldQueryBillListByReference) {
|
||||
const billRes = await accountQueryBills({
|
||||
referenceTypeNo,
|
||||
status: 10,
|
||||
@@ -1697,7 +2124,7 @@ export default {
|
||||
billName: description
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: this.$tr('暂无待支付定金账单'), icon: 'none' })
|
||||
uni.showToast({ title: emptyBillMessage, icon: 'none' })
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -1719,7 +2146,7 @@ export default {
|
||||
}
|
||||
|
||||
if (!billNo) {
|
||||
uni.showToast({ title: this.$tr('暂无待支付定金账单'), icon: 'none' })
|
||||
uni.showToast({ title: emptyBillMessage, icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1768,9 +2195,6 @@ export default {
|
||||
}
|
||||
},
|
||||
async confirmAndPayDeposit() {
|
||||
if (!this.hasReadNotice) {
|
||||
return
|
||||
}
|
||||
const paymentContext = this.pendingDepositPaymentContext || {
|
||||
referenceTypeNo: this.pendingDepositReferenceTypeNo
|
||||
}
|
||||
@@ -2491,76 +2915,171 @@ export default {
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
padding: 32rpx;
|
||||
padding: 18rpx 88rpx 20rpx;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #F1F5F9;
|
||||
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: 36rpx;
|
||||
font-weight: bold;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 1rpx;
|
||||
color: #1A2233;
|
||||
}
|
||||
|
||||
.popup-body {
|
||||
flex: 1;
|
||||
padding: 32rpx;
|
||||
padding: 28rpx 28rpx 24rpx;
|
||||
overflow-y: auto;
|
||||
background: linear-gradient(180deg, #F7FAFF 0%, #F2F5FC 100%);
|
||||
}
|
||||
|
||||
.notice-content {
|
||||
padding-bottom: 40rpx;
|
||||
padding-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.notice-item {
|
||||
margin-bottom: 32rpx;
|
||||
.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);
|
||||
}
|
||||
|
||||
.notice-item-zh {
|
||||
font-size: 28rpx;
|
||||
color: #334155;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 8rpx;
|
||||
font-weight: 500;
|
||||
.payment-hero {
|
||||
padding: 18rpx 6rpx 14rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.notice-item-en {
|
||||
.payment-hero-label {
|
||||
font-size: 24rpx;
|
||||
color: #64748B;
|
||||
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;
|
||||
}
|
||||
|
||||
.popup-footer {
|
||||
padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid #F1F5F9;
|
||||
background: #FFFFFF;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.payment-detail-value {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.4;
|
||||
color: #22324D;
|
||||
word-break: break-all;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
min-width: 420rpx;
|
||||
max-width: 100%;
|
||||
height: 92rpx;
|
||||
.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, #22c55e 0%, #16a34a 100%);
|
||||
background: linear-gradient(135deg, $brand-primary 0%, #1F6FE5 100%);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 16rpx 34rpx rgba(34, 197, 94, 0.24);
|
||||
box-shadow: 0 14rpx 28rpx rgba($brand-primary, 0.22);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
.notice-action-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.action-btn--disabled {
|
||||
opacity: 0.6;
|
||||
background: #94A3B8;
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<view class="profile-page tn-safe-area-inset-bottom">
|
||||
|
||||
<!-- 头部背景与用户信息 (悬浮质感设计) -->
|
||||
|
||||
@@ -196,12 +196,12 @@ export default {
|
||||
watch: {
|
||||
currentLang() {
|
||||
this.loadBannerList()
|
||||
this.loadAreaList()
|
||||
this.refreshSelectRoomState()
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.checkLoginStatus()
|
||||
await Promise.all([this.loadBannerList(), this.loadAreaList()])
|
||||
await this.loadBannerList()
|
||||
},
|
||||
methods: {
|
||||
getLocalizedBannerText(banner = {}, field = '') {
|
||||
@@ -236,6 +236,29 @@ export default {
|
||||
this.isLogin = session.isLogin
|
||||
this.userInfo = this.isLogin ? getStoredUser() : null
|
||||
},
|
||||
hasActiveReservationFlow() {
|
||||
const storeUser = (this.$store && this.$store.state && this.$store.state.vuex_user) || {}
|
||||
if (storeUser.hasActiveReservationFlow === true) {
|
||||
return true
|
||||
}
|
||||
if (storeUser.hasActiveReservationFlow === false) {
|
||||
return false
|
||||
}
|
||||
const session = getSessionSnapshot()
|
||||
const user = session.currentUser || session.registeredUser || session.resolvedUser || {}
|
||||
if (user.hasActiveReservationFlow === true) {
|
||||
return true
|
||||
}
|
||||
if (user.hasActiveReservationFlow === false) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
},
|
||||
refreshSelectRoomState() {
|
||||
if (!this.isLogin || !this.hasActiveReservationFlow()) {
|
||||
this.loadAreaList()
|
||||
}
|
||||
},
|
||||
loadBannerList() {
|
||||
this.isBannerLoading = true
|
||||
this.bannerLoadError = ''
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,14 +50,6 @@
|
||||
></stay-service-tab>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view v-if="hasPage('my-application')" v-show="currentPageId === 'my-application'">
|
||||
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
|
||||
<my-application-tab
|
||||
ref="myApplicationTab"
|
||||
@refresh-shell-state="handleMyApplicationRefreshState"
|
||||
></my-application-tab>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view v-if="hasPage('message-center')" v-show="currentPageId === 'message-center'">
|
||||
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
|
||||
<message-center-tab ref="messageCenterTab"></message-center-tab>
|
||||
@@ -108,27 +100,28 @@
|
||||
import StayServiceTab from './component/StayServiceTab.vue'
|
||||
import MessageCenterTab from './component/MessageCenterTab.vue'
|
||||
import ProfileCenterTab from './component/ProfileCenterTab.vue'
|
||||
import MyApplicationTab from './component/MyApplicationTab.vue'
|
||||
import ContractAlert from '../../components/contract-alert/contract-alert.vue'
|
||||
import { clearLegacyDemoCache, getHomePageTabId, getSessionSnapshot, saveHomePageTabId } from '../../utils/app-state'
|
||||
import { applicationFormMePage, checkRecordByChecked, notificationPage, orderSearchedMePage, orderSearchedReservationFlow } from '@/api/index.js'
|
||||
import { parseOrderListResponse, getOrderStatusCode } from '@/utils/booking-helper'
|
||||
import {
|
||||
clearLegacyDemoCache,
|
||||
getCurrentUser,
|
||||
getHomePageTabId,
|
||||
getSessionSnapshot,
|
||||
saveBookingList,
|
||||
saveCurrentBooking,
|
||||
saveCurrentUser,
|
||||
saveEffectiveContracts,
|
||||
saveHomePageTabId,
|
||||
savePendingContractCount,
|
||||
savePendingContracts,
|
||||
setCheckedInStatus
|
||||
} from '../../utils/app-state'
|
||||
import { checkRecordByChecked, notificationPage, orderSearchedReservationFlow, userMe } from '@/api/index.js'
|
||||
import { mergeBackendUserInfo } from '@/utils/flow'
|
||||
import { bindPrivacyAuthorizationListener, getPrivacyAuthorizationStatus, openWechatPrivacyContract, unbindPrivacyAuthorizationListener } from '@/utils/privacy.js'
|
||||
import { countPendingInspectionRecords, extractInspectionList, resolveNotificationUnreadTotal } from '../../utils/message-helper'
|
||||
import { syncLoginStayState } from '../../utils/session-sync'
|
||||
import { getReservationOrderNo, getReservationCurrentNode, parseReservationFlowResponse } from '@/utils/reservation-flow'
|
||||
import { getReservationCurrentNode, hasEffectiveReservationFlow, parseReservationFlowResponse } from '@/utils/reservation-flow'
|
||||
|
||||
function formatPageResponse(response) {
|
||||
const payload = response && response.data ? response.data : response
|
||||
return payload && typeof payload === 'object' ? payload : {}
|
||||
}
|
||||
|
||||
function normalizeApplicationList(list = []) {
|
||||
if (!Array.isArray(list)) {
|
||||
return []
|
||||
}
|
||||
return list.filter(item => item && typeof item === 'object')
|
||||
}
|
||||
const STAY_STATE_SYNC_TTL = 800
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -136,7 +129,6 @@
|
||||
StayServiceTab,
|
||||
MessageCenterTab,
|
||||
ProfileCenterTab,
|
||||
MyApplicationTab,
|
||||
ContractAlert
|
||||
},
|
||||
data() {
|
||||
@@ -150,15 +142,13 @@
|
||||
sessionState: {
|
||||
isLogin: false,
|
||||
hasCheckedIn: false,
|
||||
hasStayOrder: false,
|
||||
hasPendingApplication: false,
|
||||
hasActiveReservationFlow: false,
|
||||
pendingContracts: []
|
||||
},
|
||||
tabDefinitions: [
|
||||
{ id: 'select-room', labelKey: '选 房', icon: 'tn-icon-home-fill', image: '/static/tabbar/tn-tabbar0.png', component: 'SelectRoomTab', ref: 'selectRoomTab', refreshMethod: 'checkLoginStatus' },
|
||||
{ id: 'select-room', labelKey: '选 房', icon: 'tn-icon-home-fill', image: '/static/tabbar/tn-tabbar0.png', component: 'SelectRoomTab', ref: 'selectRoomTab', refreshMethod: 'refreshSelectRoomState' },
|
||||
{ id: 'stay-service', labelKey: '入 住', icon: 'tn-icon-safe', image: '/static/tabbar/tn-tabbar1.png', component: 'StayServiceTab', ref: 'stayServiceTab', refreshMethod: 'checkLocalStatus' },
|
||||
{ id: 'my-application', labelKey: '申请单', icon: 'tn-icon-order', image: '/static/tabbar/tn-tabbar2.png', component: 'MyApplicationTab', ref: 'myApplicationTab', refreshMethod: 'load' },
|
||||
{ id: 'message-center', labelKey: '消 息', icon: 'tn-icon-message', image: '/static/tabbar/tn-tabbar3.png', component: 'MessageCenterTab', ref: 'messageCenterTab', refreshMethod: 'load' },
|
||||
{ id: 'profile-center', labelKey: '我 的', icon: 'tn-icon-my-fill', image: '/static/tabbar/tn-tabbar3.png', component: 'ProfileCenterTab', ref: 'profileCenterTab', refreshMethod: 'checkLoginStatus' }
|
||||
],
|
||||
@@ -181,16 +171,15 @@
|
||||
return ['select-room', 'profile-center']
|
||||
}
|
||||
const tabs = []
|
||||
if (this.sessionState.hasStayOrder) {
|
||||
const shouldShowStayService = this.sessionState.hasCheckedIn
|
||||
|| this.sessionState.hasPendingApplication
|
||||
|| this.sessionState.hasActiveReservationFlow
|
||||
|| this.sessionState.pendingContracts.length > 0
|
||||
if (shouldShowStayService) {
|
||||
tabs.push('stay-service')
|
||||
} else if (this.sessionState.hasPendingApplication || this.sessionState.hasActiveReservationFlow) {
|
||||
tabs.push('my-application')
|
||||
} else {
|
||||
tabs.push('select-room')
|
||||
}
|
||||
if ((this.sessionState.hasPendingApplication || this.sessionState.hasActiveReservationFlow) && !tabs.includes('my-application')) {
|
||||
tabs.push('my-application')
|
||||
}
|
||||
tabs.push('message-center', 'profile-center')
|
||||
return tabs
|
||||
},
|
||||
@@ -316,112 +305,190 @@
|
||||
getStoreCurrentOrder() {
|
||||
return (this.$store && this.$store.state && this.$store.state.vuex_current_order) || null
|
||||
},
|
||||
getStoreCurrentBooking() {
|
||||
const user = this.getStoreUser()
|
||||
return (user && user.currentBooking) || null
|
||||
},
|
||||
getStorePendingContracts() {
|
||||
const list = this.$store && this.$store.state && this.$store.state.vuex_pending_contracts
|
||||
return Array.isArray(list) ? list : []
|
||||
},
|
||||
getCachedStayStateResult() {
|
||||
const storeUser = this.getStoreUser()
|
||||
return {
|
||||
hasCheckedIn: this.getStoreCheckinStatus(),
|
||||
hasPendingApplication: !!storeUser.hasPendingApplication,
|
||||
hasActiveReservationFlow: !!storeUser.hasActiveReservationFlow,
|
||||
flowList: Array.isArray(storeUser.reservationFlowList) ? storeUser.reservationFlowList : []
|
||||
}
|
||||
},
|
||||
syncStayState(force = false) {
|
||||
if (!this.isCurrentLogin()) {
|
||||
this.refreshSessionState()
|
||||
return Promise.resolve()
|
||||
return Promise.resolve({
|
||||
hasCheckedIn: false,
|
||||
hasPendingApplication: false,
|
||||
hasActiveReservationFlow: false,
|
||||
flowList: []
|
||||
})
|
||||
}
|
||||
|
||||
return syncLoginStayState({ force }).catch((error) => {
|
||||
console.error('sync login stay state failed', error)
|
||||
})
|
||||
},
|
||||
resolveApplicationState() {
|
||||
return applicationFormMePage({
|
||||
Status: 0,
|
||||
PageIndex: 1,
|
||||
PageSize: 1
|
||||
}, this.$getLanguage())
|
||||
const now = Date.now()
|
||||
if (this._syncStayStatePromise) {
|
||||
return this._syncStayStatePromise
|
||||
}
|
||||
if (this._lastStayStateSyncAt && now - this._lastStayStateSyncAt < STAY_STATE_SYNC_TTL) {
|
||||
return Promise.resolve(this.getCachedStayStateResult())
|
||||
}
|
||||
|
||||
const storeUser = this.getStoreUser()
|
||||
const cachedUserCode = storeUser.userCode || storeUser.code || ''
|
||||
|
||||
// 先获取用户信息(含 userCode),再获取流程
|
||||
this._syncStayStatePromise = userMe({}, this.$getLanguage())
|
||||
.then((response) => {
|
||||
const pageData = formatPageResponse(response)
|
||||
const list = normalizeApplicationList(pageData.data || [])
|
||||
const total = Number(pageData.total || 0)
|
||||
const hasPendingApplication = list.some(item => Number(item && item.status) === 0)
|
||||
return {
|
||||
hasPendingApplication: total > 0 || hasPendingApplication
|
||||
const responseData = response && response.data ? response.data : response
|
||||
const info = responseData && responseData.data ? responseData.data : responseData
|
||||
const mergedUser = mergeBackendUserInfo(storeUser, response)
|
||||
const resolvedUserCode = mergedUser.userCode || mergedUser.code || info.userCode || info.code || cachedUserCode || ''
|
||||
if (this.$store && this.$store.commit) {
|
||||
this.$store.commit('$tStore', { name: 'vuex_user', value: mergedUser })
|
||||
}
|
||||
saveCurrentUser(mergedUser)
|
||||
if (!resolvedUserCode) {
|
||||
this.refreshSessionState()
|
||||
return Promise.resolve({
|
||||
hasCheckedIn: false,
|
||||
hasPendingApplication: false,
|
||||
hasActiveReservationFlow: false,
|
||||
flowList: []
|
||||
})
|
||||
}
|
||||
return this.fetchReservationFlowWithUserCode(resolvedUserCode)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('resolve application state failed', error)
|
||||
return {
|
||||
hasPendingApplication: false
|
||||
console.error('fetch user info before flow failed', error)
|
||||
if (!cachedUserCode) {
|
||||
this.refreshSessionState()
|
||||
return Promise.resolve({
|
||||
hasCheckedIn: false,
|
||||
hasPendingApplication: false,
|
||||
hasActiveReservationFlow: false,
|
||||
flowList: []
|
||||
})
|
||||
}
|
||||
return this.fetchReservationFlowWithUserCode(cachedUserCode)
|
||||
})
|
||||
.then((result) => {
|
||||
this._lastStayStateSyncAt = Date.now()
|
||||
return result
|
||||
})
|
||||
.finally(() => {
|
||||
this._syncStayStatePromise = null
|
||||
})
|
||||
|
||||
return this._syncStayStatePromise
|
||||
},
|
||||
resolveApplicationFlowState(list = []) {
|
||||
const orderList = normalizeApplicationList(list)
|
||||
const reservationOrderList = orderList.filter(item => getOrderStatusCode(item) === 0)
|
||||
const orderNos = [...new Set(reservationOrderList.map(item => getReservationOrderNo(item)).filter(Boolean))]
|
||||
|
||||
const result = {
|
||||
hasPendingApplication: false,
|
||||
hasActiveReservationFlow: false
|
||||
}
|
||||
|
||||
if (!orderNos.length) {
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
|
||||
fetchReservationFlowWithUserCode(userCode, options = {}) {
|
||||
const { silent = true } = options
|
||||
const storeUser = this.getStoreUser()
|
||||
return orderSearchedReservationFlow({
|
||||
orderNos
|
||||
userCode
|
||||
}, this.$getLanguage())
|
||||
.then((response) => {
|
||||
const flowList = parseReservationFlowResponse(response)
|
||||
const hasActiveReservationFlow = flowList.some(item => !!getReservationCurrentNode(item))
|
||||
const currentFlow = flowList.find(item => {
|
||||
const node = getReservationCurrentNode(item)
|
||||
return node && node.isCurrent
|
||||
}) || flowList[0] || null
|
||||
const currentNode = currentFlow ? getReservationCurrentNode(currentFlow) : null
|
||||
const currentStep = currentNode ? currentNode.step : null
|
||||
const currentRefNo = currentNode ? String(currentNode.refNo || '').trim() : ''
|
||||
const hasCheckedIn = currentStep === 5
|
||||
const hasActiveReservationFlow = flowList.some(item => hasEffectiveReservationFlow(item))
|
||||
const hasPendingApplication = currentStep === 1 && !!currentRefNo
|
||||
const currentBooking = hasActiveReservationFlow && currentFlow
|
||||
? { orderNo: currentFlow.orderNo || '', refNo: currentRefNo, step: currentStep }
|
||||
: null
|
||||
|
||||
if (this.$store && this.$store.commit) {
|
||||
this.$store.commit('$tStore', { name: 'vuex_checkin_status', value: hasCheckedIn })
|
||||
this.$store.commit('$tStore', { name: 'vuex_current_order', value: null })
|
||||
this.$store.commit('$tStore', { name: 'vuex_pending_contracts', value: [] })
|
||||
this.$store.commit('$tStore', { name: 'vuex_pending_contract_count', value: 0 })
|
||||
this.$store.commit('$tStore', { name: 'vuex_effective_contracts', value: [] })
|
||||
this.$store.commit('$tStore', { name: 'vuex_valid_orders', value: [] })
|
||||
this.$store.commit('$tStore', {
|
||||
name: 'vuex_user',
|
||||
value: {
|
||||
...storeUser,
|
||||
hasCheckedIn,
|
||||
currentOrder: null,
|
||||
currentBooking,
|
||||
currentFlow,
|
||||
bookingStatus: hasCheckedIn ? 'checked_in' : 'none',
|
||||
reservationFlowList: flowList,
|
||||
currentReservationStep: currentStep,
|
||||
currentReservationRefNo: currentRefNo,
|
||||
hasActiveReservationFlow,
|
||||
hasPendingApplication
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setCheckedInStatus(hasCheckedIn)
|
||||
saveCurrentBooking(currentBooking)
|
||||
saveBookingList([])
|
||||
savePendingContracts([])
|
||||
savePendingContractCount(0)
|
||||
saveEffectiveContracts([])
|
||||
|
||||
const currentUser = getCurrentUser() || {}
|
||||
saveCurrentUser({
|
||||
...currentUser,
|
||||
hasCheckedIn,
|
||||
currentOrder: null,
|
||||
currentBooking,
|
||||
currentFlow,
|
||||
bookingStatus: hasCheckedIn ? 'checked_in' : 'none',
|
||||
reservationFlowList: flowList,
|
||||
currentReservationStep: currentStep,
|
||||
currentReservationRefNo: currentRefNo,
|
||||
hasActiveReservationFlow,
|
||||
hasPendingApplication
|
||||
})
|
||||
|
||||
this.refreshSessionState()
|
||||
|
||||
return {
|
||||
hasPendingApplication: result.hasPendingApplication || hasActiveReservationFlow,
|
||||
hasActiveReservationFlow
|
||||
hasCheckedIn,
|
||||
hasPendingApplication,
|
||||
hasActiveReservationFlow,
|
||||
flowList
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('resolve application flow state failed', error)
|
||||
return result
|
||||
console.error('sync stay state by reservation flow failed', error)
|
||||
this.refreshSessionState()
|
||||
if (!silent) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
return {
|
||||
hasCheckedIn: false,
|
||||
hasPendingApplication: false,
|
||||
hasActiveReservationFlow: false,
|
||||
flowList: []
|
||||
}
|
||||
})
|
||||
},
|
||||
refreshShellState(force = false, options = {}) {
|
||||
const { skipMessageBadge = false } = options
|
||||
const tasks = [
|
||||
this.$store && this.$store.dispatch ? this.$store.dispatch('fetchGlobalEnums').catch(() => ({})) : Promise.resolve(),
|
||||
this.syncStayState(force)
|
||||
this.syncStayState(force).then((flowState = {}) => {
|
||||
this.sessionState.hasPendingApplication = !!flowState.hasPendingApplication
|
||||
this.sessionState.hasActiveReservationFlow = !!flowState.hasActiveReservationFlow
|
||||
})
|
||||
]
|
||||
if (!skipMessageBadge) {
|
||||
tasks.push(this.refreshMessageBadgeCount())
|
||||
}
|
||||
if (this.isCurrentLogin()) {
|
||||
tasks.push(
|
||||
this.resolveApplicationState()
|
||||
.then((applicationState = {}) => {
|
||||
return orderSearchedMePage({ PageIndex: 1, PageSize: 100, IsExport: false })
|
||||
.then(res => {
|
||||
const orderList = parseOrderListResponse(res)
|
||||
return this.resolveApplicationFlowState(orderList)
|
||||
.then((flowState = {}) => ({
|
||||
hasPendingApplication: !!applicationState.hasPendingApplication,
|
||||
hasActiveReservationFlow: !!flowState.hasActiveReservationFlow
|
||||
}))
|
||||
})
|
||||
})
|
||||
.then((flowState = {}) => {
|
||||
this.sessionState.hasPendingApplication = !!flowState.hasPendingApplication
|
||||
this.sessionState.hasActiveReservationFlow = !!flowState.hasActiveReservationFlow
|
||||
})
|
||||
.catch(() => {
|
||||
this.sessionState.hasPendingApplication = false
|
||||
this.sessionState.hasActiveReservationFlow = false
|
||||
})
|
||||
)
|
||||
} else {
|
||||
this.sessionState.hasPendingApplication = false
|
||||
this.sessionState.hasActiveReservationFlow = false
|
||||
}
|
||||
return Promise.all(tasks).finally(() => {
|
||||
this.refreshSessionState()
|
||||
})
|
||||
@@ -435,6 +502,10 @@
|
||||
index: Number(options.index || 0)
|
||||
})
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.refreshTabPageState('select-room')
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.isAppInitLoading = false
|
||||
}, 1500)
|
||||
@@ -464,17 +535,17 @@
|
||||
const storePendingContracts = this.getStorePendingContracts()
|
||||
const storeUser = this.getStoreUser()
|
||||
const storeHasCheckedIn = this.getStoreCheckinStatus()
|
||||
const storeCurrentBooking = this.getStoreCurrentBooking()
|
||||
const pendingContracts = storePendingContracts.length
|
||||
? storePendingContracts
|
||||
: (storeUser.pendingContracts || (Array.isArray(session.pendingContracts) ? session.pendingContracts : []))
|
||||
const hasStayOrder = !!(storeHasCheckedIn || storeCurrentBooking || session.currentBooking)
|
||||
const hasCheckedIn = !!(storeHasCheckedIn || session.hasCheckedIn)
|
||||
const hasActiveReservationFlow = !!(storeUser.hasActiveReservationFlow || session.resolvedUser.hasActiveReservationFlow || this.sessionState.hasActiveReservationFlow)
|
||||
const hasPendingApplication = !!(storeUser.hasPendingApplication || session.resolvedUser.hasPendingApplication || this.sessionState.hasPendingApplication)
|
||||
this.sessionState = {
|
||||
isLogin: this.isCurrentLogin(),
|
||||
hasCheckedIn: storeHasCheckedIn || session.hasCheckedIn,
|
||||
hasStayOrder: hasStayOrder || pendingContracts.length > 0,
|
||||
hasPendingApplication: !!this.sessionState.hasPendingApplication,
|
||||
hasActiveReservationFlow: !!this.sessionState.hasActiveReservationFlow,
|
||||
hasCheckedIn,
|
||||
hasPendingApplication,
|
||||
hasActiveReservationFlow,
|
||||
pendingContracts
|
||||
}
|
||||
},
|
||||
@@ -551,19 +622,13 @@
|
||||
const explicitMatchedTab = list.find(item => item.id === pageId)
|
||||
if (explicitMatchedTab) return explicitMatchedTab
|
||||
}
|
||||
const preferredPageId = getHomePageTabId()
|
||||
const shouldPreferStayService = this.sessionState.isLogin && this.sessionState.hasStayOrder
|
||||
if (shouldPreferStayService && (!preferredPageId || preferredPageId === 'select-room')) {
|
||||
const shouldPreferFlowPage = this.sessionState.isLogin
|
||||
&& (this.sessionState.hasCheckedIn || this.sessionState.hasPendingApplication || this.sessionState.hasActiveReservationFlow || this.sessionState.pendingContracts.length > 0)
|
||||
if (shouldPreferFlowPage) {
|
||||
const stayServiceTab = list.find(item => item.id === 'stay-service')
|
||||
if (stayServiceTab) return stayServiceTab
|
||||
}
|
||||
const shouldPreferApplicationFlow = this.sessionState.isLogin
|
||||
&& !this.sessionState.hasStayOrder
|
||||
&& (this.sessionState.hasPendingApplication || this.sessionState.hasActiveReservationFlow)
|
||||
if (shouldPreferApplicationFlow && (!preferredPageId || preferredPageId === 'select-room')) {
|
||||
const applicationTab = list.find(item => item.id === 'my-application')
|
||||
if (applicationTab) return applicationTab
|
||||
}
|
||||
const preferredPageId = getHomePageTabId()
|
||||
if (preferredPageId) {
|
||||
const matchedTab = list.find(item => item.id === preferredPageId)
|
||||
if (matchedTab) return matchedTab
|
||||
@@ -595,28 +660,74 @@
|
||||
}, 60)
|
||||
}
|
||||
},
|
||||
handleStayServiceRefreshState() {
|
||||
this.refreshSessionState()
|
||||
refreshReservationFlowOnly() {
|
||||
if (!this.isCurrentLogin()) {
|
||||
this.refreshSessionState()
|
||||
return Promise.resolve({
|
||||
hasCheckedIn: false,
|
||||
hasPendingApplication: false,
|
||||
hasActiveReservationFlow: false,
|
||||
flowList: []
|
||||
})
|
||||
}
|
||||
|
||||
const storeUser = this.getStoreUser()
|
||||
let userCode = storeUser.userCode || storeUser.code || ''
|
||||
if (!userCode) {
|
||||
const session = getSessionSnapshot()
|
||||
const fallbackUser = session.resolvedUser || session.currentUser || session.registeredUser || {}
|
||||
userCode = fallbackUser.userCode || fallbackUser.code || ''
|
||||
}
|
||||
if (!userCode) {
|
||||
this.refreshSessionState()
|
||||
return Promise.resolve({
|
||||
hasCheckedIn: false,
|
||||
hasPendingApplication: false,
|
||||
hasActiveReservationFlow: false,
|
||||
flowList: []
|
||||
})
|
||||
}
|
||||
|
||||
return this.fetchReservationFlowWithUserCode(userCode, { silent: false })
|
||||
},
|
||||
async handleMyApplicationRefreshState() {
|
||||
await this.refreshShellState(true)
|
||||
this.syncCurrentTab()
|
||||
this.$nextTick(() => {
|
||||
this.refreshTabPageState(this.currentPageId)
|
||||
this.getTabbarItemInfo()
|
||||
})
|
||||
handleStayServiceRefreshState(event = {}) {
|
||||
const { pendingContracts } = event || {}
|
||||
this.refreshReservationFlowOnly()
|
||||
.then((flowState = {}) => {
|
||||
if (pendingContracts && Array.isArray(pendingContracts)) {
|
||||
if (this.$store && this.$store.commit) {
|
||||
this.$store.commit('$tStore', { name: 'vuex_pending_contracts', value: pendingContracts })
|
||||
}
|
||||
savePendingContracts(pendingContracts)
|
||||
}
|
||||
this.sessionState.hasPendingApplication = !!flowState.hasPendingApplication
|
||||
this.sessionState.hasActiveReservationFlow = !!flowState.hasActiveReservationFlow
|
||||
this.refreshSessionState()
|
||||
|
||||
this.$nextTick(() => {
|
||||
const stayServiceTab = this.getPageRef('stayServiceTab')
|
||||
if (stayServiceTab && typeof stayServiceTab.onFlowRefreshed === 'function') {
|
||||
stayServiceTab.onFlowRefreshed({ ...flowState, success: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('refresh reservation flow from stay service failed', error)
|
||||
this.$nextTick(() => {
|
||||
const stayServiceTab = this.getPageRef('stayServiceTab')
|
||||
if (stayServiceTab && typeof stayServiceTab.onFlowRefreshed === 'function') {
|
||||
stayServiceTab.onFlowRefreshed({ success: false })
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
async handleReservationFlowForceRefresh() {
|
||||
await this.refreshShellState(true)
|
||||
this.ensurePageLoaded('my-application')
|
||||
this.ensurePageLoaded('stay-service')
|
||||
this.syncCurrentTab()
|
||||
this.$nextTick(() => {
|
||||
const myApplicationTab = this.getPageRef('myApplicationTab')
|
||||
if (myApplicationTab && typeof myApplicationTab.load === 'function') {
|
||||
myApplicationTab.load(true)
|
||||
} else {
|
||||
this.refreshTabPageState('my-application')
|
||||
}
|
||||
if (this.currentPageId) {
|
||||
this.refreshTabPageState('stay-service')
|
||||
if (this.currentPageId && this.currentPageId !== 'stay-service') {
|
||||
this.refreshTabPageState(this.currentPageId)
|
||||
}
|
||||
this.getTabbarItemInfo()
|
||||
|
||||
Reference in New Issue
Block a user