feat: 重构核心租住流程页面与交互体验

统一注册、看房、申请与入住等核心页面的视觉和流程衔接,补齐支付、签约与状态流转闭环,提升移动端操作连贯性。
This commit is contained in:
liangjian
2026-07-29 09:05:38 +08:00
parent da6bdc6a02
commit 1111ac148b
34 changed files with 8734 additions and 2801 deletions

View File

@@ -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>