feat: 优化首页服务入口与租住业务流程
补齐首页待办跳转、押金支付引导和多类租住服务页面能力,统一租客端关键业务流程体验。
This commit is contained in:
@@ -23,16 +23,28 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="showPaymentStatusPanel"
|
||||
class="payment-status-panel tn-margin-bottom-lg"
|
||||
:class="`payment-status-panel--${paymentStatusType}`"
|
||||
>
|
||||
<view class="payment-status-panel__label">{{ $tr('支付状态') }}</view>
|
||||
<view class="payment-status-panel__main">
|
||||
<view class="payment-status-panel__value">{{ paymentStatusText }}</view>
|
||||
<view v-if="canViewBill" class="payment-status-panel__hint">{{ $tr('请尽快完成定金支付') }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="statusReminderText"
|
||||
class="status-reminder-panel tn-margin-bottom-lg"
|
||||
:class="{
|
||||
'status-reminder-panel--warning': shouldPayDeposit(detail),
|
||||
'status-reminder-panel--warning': shouldPayDeposit(detail) && !isWaitingButlerRoomAssignment(detail),
|
||||
'status-reminder-panel--info': isWaitingRoomAssignment(detail)
|
||||
}"
|
||||
>
|
||||
<view class="status-reminder-panel__icon">
|
||||
<text :class="shouldPayDeposit(detail) ? 'tn-icon-warning-fill' : 'tn-icon-time-fill'"></text>
|
||||
<text :class="shouldPayDeposit(detail) && !isWaitingButlerRoomAssignment(detail) ? 'tn-icon-warning-fill' : 'tn-icon-time-fill'"></text>
|
||||
</view>
|
||||
<view class="status-reminder-panel__content">
|
||||
<view class="status-reminder-panel__title">{{ $tr('当前进度提醒') }}</view>
|
||||
@@ -145,7 +157,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { contractDetail, orderSearchedReservationFlow, roomInspectionDetail, userDetailCancel } from '@/api/index.js'
|
||||
import { accountQueryBills, contractDetail, orderSearchedReservationFlow, roomInspectionDetail, userDetailCancel } from '@/api/index.js'
|
||||
import { getMyApplicationList, saveActiveApplicationCode, saveMyApplicationList } from '@/utils/app-state'
|
||||
import {
|
||||
RESERVATION_FLOW_STEP,
|
||||
@@ -202,6 +214,43 @@ function normalizeApplicationList(list = []) {
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function parseJsonStringMaybe(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return value
|
||||
@@ -233,6 +282,7 @@ export default {
|
||||
watch: {
|
||||
language() {
|
||||
this.loadReservationFlow()
|
||||
this.loadPendingDepositBillState()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -278,6 +328,42 @@ export default {
|
||||
heroBadgeClass() {
|
||||
return this.isViewingApplication ? 'badge-viewing' : 'badge-stay'
|
||||
},
|
||||
showPaymentStatusPanel() {
|
||||
return !this.isViewingApplication
|
||||
},
|
||||
paymentStatusText() {
|
||||
if (this.isWaitingButlerRoomAssignment(this.detail)) {
|
||||
return this.$tr('等待管家排房')
|
||||
}
|
||||
const statusInfo = resolveApplicationPaymentStatusInfo(this.detail)
|
||||
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('待支付')
|
||||
},
|
||||
paymentStatusType() {
|
||||
if (this.isWaitingButlerRoomAssignment(this.detail)) {
|
||||
return 'info'
|
||||
}
|
||||
const statusInfo = resolveApplicationPaymentStatusInfo(this.detail)
|
||||
const typeMap = {
|
||||
exempt: 'neutral',
|
||||
paid: 'success',
|
||||
refunded: 'neutral',
|
||||
failed: 'danger',
|
||||
processing: 'info',
|
||||
pending: 'warning'
|
||||
}
|
||||
return typeMap[statusInfo.key] || 'warning'
|
||||
},
|
||||
displayDateRange() {
|
||||
if (!this.detail.start && !this.detail.end) {
|
||||
return '--'
|
||||
@@ -329,26 +415,36 @@ export default {
|
||||
if (reservationStepText) {
|
||||
return reservationStepText
|
||||
}
|
||||
if (this.isWaitingButlerRoomAssignment(this.detail)) {
|
||||
return this.$tr('等待管家排房')
|
||||
}
|
||||
if (this.shouldPayDeposit(this.detail)) {
|
||||
return this.$tr('请支付定金')
|
||||
}
|
||||
if (this.isWaitingRoomAssignment(this.detail)) {
|
||||
return this.$tr('等待预订中心分配房间')
|
||||
return this.$tr('等待管家排房')
|
||||
}
|
||||
return ''
|
||||
},
|
||||
canViewBill() {
|
||||
return this.shouldPayDeposit(this.detail)
|
||||
&& this.hasPendingDepositBill(this.detail)
|
||||
&& !this.isViewingApplication
|
||||
&& this.detail
|
||||
&& this.detail.detailCode
|
||||
&& this.statusType === 'pending'
|
||||
},
|
||||
canCancelApplication() {
|
||||
return !!(this.detail
|
||||
&& this.detail.detailCode
|
||||
&& Number(this.detail.status) === 0
|
||||
&& (this.isViewingApplication || Number(this.detail.payStatus) !== 10))
|
||||
if (!(this.detail && this.detail.detailCode && Number(this.detail.status) === 0)) {
|
||||
return false
|
||||
}
|
||||
if (this.isViewingApplication) {
|
||||
return true
|
||||
}
|
||||
if (Number(this.detail.payStatus) === 10) {
|
||||
return false
|
||||
}
|
||||
return this.hasPendingDepositBill(this.detail)
|
||||
},
|
||||
canCancelOrder() {
|
||||
return this.canCancelApplication && !this.canceling
|
||||
@@ -360,11 +456,39 @@ export default {
|
||||
},
|
||||
onShow() {
|
||||
this.loadReservationFlow()
|
||||
this.loadPendingDepositBillState()
|
||||
},
|
||||
methods: {
|
||||
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 []
|
||||
},
|
||||
isPaidApplication(item = {}) {
|
||||
return !item.isViewingApplication && Number(item.payStatus) === 10
|
||||
},
|
||||
shouldQueryPendingDepositBill(item = {}) {
|
||||
return !item.isViewingApplication
|
||||
&& Number(item.status) === 0
|
||||
&& Number(item.payStatus) !== 10
|
||||
&& !!String(item.detailCode || this.detailCode || '').trim()
|
||||
},
|
||||
hasPendingDepositBill(item = {}) {
|
||||
return !!String(this.getBillNo(item && item.pendingDepositBill ? item.pendingDepositBill : {}) || '').trim()
|
||||
},
|
||||
isWaitingButlerRoomAssignment(item = {}) {
|
||||
return this.shouldQueryPendingDepositBill(item)
|
||||
&& !!(item && item.pendingDepositBillQueried)
|
||||
&& !this.hasPendingDepositBill(item)
|
||||
},
|
||||
shouldPayDeposit(item = {}) {
|
||||
return !item.isViewingApplication
|
||||
&& Number(item.status) === 0
|
||||
@@ -373,7 +497,7 @@ export default {
|
||||
isWaitingRoomAssignment(item = {}) {
|
||||
return !item.isViewingApplication
|
||||
&& Number(item.status) === 0
|
||||
&& Number(item.payStatus) === 10
|
||||
&& (Number(item.payStatus) === 10 || this.isWaitingButlerRoomAssignment(item))
|
||||
},
|
||||
getReservationCurrentStepValue() {
|
||||
return Number(this.detail && this.detail.reservationCurrentNode ? this.detail.reservationCurrentNode.step : 0)
|
||||
@@ -493,6 +617,50 @@ export default {
|
||||
isViewingApplication: false
|
||||
}
|
||||
},
|
||||
syncDetailToCache(detail = this.detail) {
|
||||
const list = normalizeApplicationList(getMyApplicationList())
|
||||
if (!list.length || !detail || !detail.detailCode) {
|
||||
return
|
||||
}
|
||||
saveMyApplicationList(list.map((item) => {
|
||||
if (String(item.detailCode || '') !== String(detail.detailCode || '')) {
|
||||
return item
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
...detail
|
||||
}
|
||||
}))
|
||||
},
|
||||
async loadPendingDepositBillState() {
|
||||
const currentDetail = this.detail && typeof this.detail === 'object' ? this.detail : {}
|
||||
const referenceTypeNo = String(currentDetail.detailCode || this.detailCode || '').trim()
|
||||
if (!this.shouldQueryPendingDepositBill(currentDetail) || !referenceTypeNo) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await accountQueryBills({
|
||||
referenceTypeNo,
|
||||
status: 10,
|
||||
paymentStatus: 0,
|
||||
ProviderType: 'WechatMiniProgram'
|
||||
}, this.language)
|
||||
if (referenceTypeNo !== String(this.detail && this.detail.detailCode || this.detailCode || '').trim()) {
|
||||
return
|
||||
}
|
||||
const billList = this.normalizeBillList(response)
|
||||
const nextDetail = {
|
||||
...this.detail,
|
||||
pendingDepositBill: Array.isArray(billList) && billList.length ? billList[0] : null,
|
||||
pendingDepositBillQueried: true
|
||||
}
|
||||
this.detail = nextDetail
|
||||
this.syncDetailToCache(nextDetail)
|
||||
} catch (error) {
|
||||
console.error('load pending deposit bill failed', error)
|
||||
}
|
||||
},
|
||||
async loadReservationFlow() {
|
||||
if (!this.detail || this.detail.isViewingApplication) {
|
||||
return
|
||||
@@ -782,6 +950,85 @@ export default {
|
||||
border: 1px solid rgba(59, 130, 246, 0.14);
|
||||
}
|
||||
|
||||
.payment-status-panel {
|
||||
padding: 24rpx 28rpx;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24rpx;
|
||||
border: 1rpx solid transparent;
|
||||
}
|
||||
|
||||
.payment-status-panel__label {
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #64748B;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.payment-status-panel__main {
|
||||
min-width: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.payment-status-panel__value {
|
||||
font-size: 34rpx;
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.payment-status-panel__hint {
|
||||
margin-top: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: #9A3412;
|
||||
}
|
||||
|
||||
.payment-status-panel--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-panel--warning .payment-status-panel__value {
|
||||
color: #C2410C;
|
||||
}
|
||||
|
||||
.payment-status-panel--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-panel--success .payment-status-panel__value {
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
.payment-status-panel--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-panel--info .payment-status-panel__value {
|
||||
color: #1D4ED8;
|
||||
}
|
||||
|
||||
.payment-status-panel--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-panel--danger .payment-status-panel__value {
|
||||
color: #B91C1C;
|
||||
}
|
||||
|
||||
.payment-status-panel--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-panel--neutral .payment-status-panel__value {
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.status-reminder-panel__icon {
|
||||
width: 68rpx;
|
||||
height: 68rpx;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<view class="overview-panel">
|
||||
<view class="overview-header">
|
||||
<view>
|
||||
<view class="overview-title">{{ $tr('合同影像') }}</view>
|
||||
<view class="overview-title">{{ contractDisplayName || $tr('合同影像') }}</view>
|
||||
<view class="overview-subtitle">{{ $tr('合同编号:') }}{{ contractNo || $tr('未获取') }}</view>
|
||||
</view>
|
||||
<view class="overview-badge">
|
||||
@@ -83,6 +83,7 @@
|
||||
import { addFilePrefix } from '@/utils/common.js'
|
||||
import { contractPreview } from '@/api/index.js'
|
||||
import { normalizeRichTextImages } from '../../utils/rich-text'
|
||||
import { fetchContractDisplayInfo } from '../../utils/contract-display'
|
||||
|
||||
function resolvePreviewPayload(response) {
|
||||
const payload = response && response.data ? response.data : response
|
||||
@@ -103,6 +104,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
contractNo: '',
|
||||
contractDisplayName: '',
|
||||
orderNo: '',
|
||||
loading: false,
|
||||
imageUrl: '',
|
||||
@@ -114,9 +116,24 @@ export default {
|
||||
this.contractNo = decodeURIComponent(options.no || '')
|
||||
this.orderNo = decodeURIComponent(options.orderNo || '')
|
||||
this.imageUrl = addFilePrefix(decodeURIComponent(options.contentImageUrl || ''))
|
||||
this.fetchContractDisplayName()
|
||||
this.fetchContractDetail()
|
||||
},
|
||||
methods: {
|
||||
fetchContractDisplayName() {
|
||||
if (!this.contractNo) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return fetchContractDisplayInfo(this.contractNo, this.$getLanguage())
|
||||
.then(({ contractDisplayName }) => {
|
||||
this.contractDisplayName = contractDisplayName || ''
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('fetch contract display name failed', error)
|
||||
this.contractDisplayName = ''
|
||||
})
|
||||
},
|
||||
fetchContractDetail() {
|
||||
if (!this.contractNo || (this.imageUrl && this.contractContent)) {
|
||||
return Promise.resolve()
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<view class="overview-panel">
|
||||
<view class="overview-header">
|
||||
<view>
|
||||
<view class="overview-title">{{ $tr('签约前确认') }}</view>
|
||||
<view class="overview-title">{{ contractDisplayName || $tr('签约前确认') }}</view>
|
||||
<view class="overview-subtitle">{{ $tr('合同编号:') }}{{ contractNo || $tr('未获取') }}</view>
|
||||
</view>
|
||||
<view class="overview-badge">
|
||||
@@ -106,6 +106,7 @@ import { contractPreview, contractSign, uploadCommonFile } from '@/api/index.js'
|
||||
import { syncLoginStayState } from '../../utils/session-sync'
|
||||
import ContractSignDialog from '../../components/ContractSignDialog.vue'
|
||||
import { normalizeRichTextImages } from '../../utils/rich-text'
|
||||
import { fetchContractDisplayInfo } from '../../utils/contract-display'
|
||||
|
||||
function normalizeContractContent(content = '') {
|
||||
return normalizeRichTextImages(
|
||||
@@ -144,6 +145,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
contractNo: '',
|
||||
contractDisplayName: '',
|
||||
orderNo: '',
|
||||
contractContent: '',
|
||||
loading: false,
|
||||
@@ -158,9 +160,24 @@ export default {
|
||||
this.contractNo = decodeURIComponent(options.no || '')
|
||||
this.orderNo = decodeURIComponent(options.orderNo || '')
|
||||
this.signerName = this.getSignerName()
|
||||
this.fetchContractDisplayName()
|
||||
this.fetchContractContent()
|
||||
},
|
||||
methods: {
|
||||
fetchContractDisplayName() {
|
||||
if (!this.contractNo) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return fetchContractDisplayInfo(this.contractNo, this.$getLanguage())
|
||||
.then(({ contractDisplayName }) => {
|
||||
this.contractDisplayName = contractDisplayName || ''
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('fetch contract display name failed', error)
|
||||
this.contractDisplayName = ''
|
||||
})
|
||||
},
|
||||
fetchContractContent() {
|
||||
if (!this.contractNo) {
|
||||
return Promise.resolve()
|
||||
|
||||
1156
pkgService/lock_control/lock_control.vue
Normal file
1156
pkgService/lock_control/lock_control.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -52,6 +52,15 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="shouldShowApplicationPaymentStatus(item)"
|
||||
class="payment-status-banner tn-margin-top"
|
||||
: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">
|
||||
<view class="info-row tn-flex tn-flex-row-between">
|
||||
<text class="info-label">{{ $tr('所属公寓') }}</text>
|
||||
@@ -154,6 +163,43 @@ function isViewingApplicationRecord(item = {}) {
|
||||
return VIEWING_APPLICATION_PATTERN.test(remark)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeApplicationList(list = []) {
|
||||
if (!Array.isArray(list)) {
|
||||
return []
|
||||
@@ -362,6 +408,36 @@ export default {
|
||||
}
|
||||
return item.intendedRoomTypeName || this.$tr('入住申请')
|
||||
},
|
||||
shouldShowApplicationPaymentStatus(item = {}) {
|
||||
return !item.isViewingApplication
|
||||
},
|
||||
getApplicationPaymentStatusText(item = {}) {
|
||||
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 = {}) {
|
||||
const statusInfo = resolveApplicationPaymentStatusInfo(item)
|
||||
const typeMap = {
|
||||
exempt: 'neutral',
|
||||
paid: 'success',
|
||||
refunded: 'neutral',
|
||||
failed: 'danger',
|
||||
processing: 'info',
|
||||
pending: 'warning'
|
||||
}
|
||||
return typeMap[statusInfo.key] || 'warning'
|
||||
},
|
||||
resolveReferenceTypeNo(item = {}) {
|
||||
return item.detailCode || ''
|
||||
},
|
||||
@@ -555,6 +631,72 @@ export default {
|
||||
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 {
|
||||
background: rgba(245, 158, 11, 0.12);
|
||||
color: #B45309;
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<view v-else-if="displayList.length" class="list-wrapper">
|
||||
<view
|
||||
v-for="(item, index) in displayList"
|
||||
:key="item.no || item.contractNo || item.orderNo || index"
|
||||
:key="getContractKey(item, index)"
|
||||
class="contract-card"
|
||||
:style="{ animationDelay: `${index * 0.08}s` }"
|
||||
>
|
||||
@@ -58,8 +58,8 @@
|
||||
<text class="tn-icon-edit-write-fill"></text>
|
||||
</view>
|
||||
<view class="card-title-group">
|
||||
<view class="card-title">{{ $tr('住宿合同') }}</view>
|
||||
<view class="card-code">{{ item.no || item.contractNo || '--' }}</view>
|
||||
<view class="card-title">{{ item.contractDisplayName || $tr('住宿合同') }}</view>
|
||||
<view class="card-code">{{ $tr('合同编号:') }}{{ item.no || item.contractNo || '--' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view :class="['status-badge', `status-${resolveStatusType(item.approvalStatus)}`]">
|
||||
@@ -88,10 +88,10 @@
|
||||
<text class="info-label">{{ $tr('记录说明') }}</text>
|
||||
<text class="info-value">{{ getStatusTip(item) }}</text>
|
||||
</view>
|
||||
<view v-if="item.signImageUrl" class="sign-row">
|
||||
<text class="info-label">{{ $tr('我的签名') }}</text>
|
||||
<view v-if="item.contentImageUrl" class="sign-row">
|
||||
<text class="info-label">{{ $tr('合同图片') }}</text>
|
||||
<view class="sign-preview">
|
||||
<image :src="item.signImageUrl" mode="aspectFit"></image>
|
||||
<image :src="item.contentImageUrl" mode="aspectFit" @click="previewContractImage(index)"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -100,7 +100,7 @@
|
||||
<view
|
||||
class="action-btn"
|
||||
:class="{ 'action-btn--secondary': !shouldShowSignAction(item) }"
|
||||
@click="handleContractAction(item)"
|
||||
@click="handleContractAction(index)"
|
||||
>
|
||||
{{ resolveActionText(item) }}
|
||||
</view>
|
||||
@@ -121,8 +121,42 @@
|
||||
|
||||
<script>
|
||||
import { addFilePrefix } from '@/utils/common.js'
|
||||
import { getMyEffectiveContracts, getMyPendingSignContracts } from '@/api/index.js'
|
||||
import { getSessionSnapshot } from '../../utils/app-state'
|
||||
import { syncLoginStayState } from '../../utils/session-sync'
|
||||
import { enrichContractDisplayNames } from '../../utils/contract-display'
|
||||
|
||||
function pickFirstValue(...values) {
|
||||
for (let index = 0; index < values.length; index += 1) {
|
||||
const value = values[index]
|
||||
if (value === undefined || value === null) {
|
||||
continue
|
||||
}
|
||||
const text = String(value).trim()
|
||||
if (text) {
|
||||
return text
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function parseContractList(response) {
|
||||
const payload = response && response.data ? response.data : response
|
||||
|
||||
if (Array.isArray(payload)) {
|
||||
return payload
|
||||
}
|
||||
|
||||
if (payload && Array.isArray(payload.data)) {
|
||||
return payload.data
|
||||
}
|
||||
|
||||
if (payload && payload.data && Array.isArray(payload.data.data)) {
|
||||
return payload.data.data
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function normalizeContract(item = {}) {
|
||||
return {
|
||||
@@ -190,6 +224,67 @@ export default {
|
||||
const list = this.$store && this.$store.state && this.$store.state.vuex_effective_contracts
|
||||
return Array.isArray(list) ? list : []
|
||||
},
|
||||
resolveContractItem(source) {
|
||||
if (typeof source === 'number') {
|
||||
return this.displayList[source] || {}
|
||||
}
|
||||
if (source && typeof source === 'object') {
|
||||
return source
|
||||
}
|
||||
return {}
|
||||
},
|
||||
resolveCurrentOrderNo(currentOrder = null) {
|
||||
const storeCurrentOrder = this.$store && this.$store.state && this.$store.state.vuex_current_order
|
||||
const session = getSessionSnapshot()
|
||||
const sessionCurrentOrder = session && session.resolvedUser && session.resolvedUser.currentOrder
|
||||
return pickFirstValue(
|
||||
currentOrder && (currentOrder.orderNo || currentOrder.no || currentOrder.code || currentOrder.OrderNo),
|
||||
storeCurrentOrder && (storeCurrentOrder.orderNo || storeCurrentOrder.no || storeCurrentOrder.code || storeCurrentOrder.OrderNo),
|
||||
sessionCurrentOrder && (sessionCurrentOrder.orderNo || sessionCurrentOrder.no || sessionCurrentOrder.code || sessionCurrentOrder.OrderNo)
|
||||
)
|
||||
},
|
||||
fetchLatestContractsByOrderNo(orderNo = '') {
|
||||
const normalizedOrderNo = String(orderNo || '').trim()
|
||||
if (!normalizedOrderNo) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
|
||||
const query = {
|
||||
OrderNo: normalizedOrderNo,
|
||||
ProviderType: 'WechatMiniProgram'
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
getMyPendingSignContracts(query, this.language).catch(() => []),
|
||||
getMyEffectiveContracts(query, this.language).catch(() => [])
|
||||
]).then(([pendingContractsResponse, effectiveContractsResponse]) => {
|
||||
return {
|
||||
pendingContracts: parseContractList(pendingContractsResponse),
|
||||
effectiveContracts: parseContractList(effectiveContractsResponse)
|
||||
}
|
||||
})
|
||||
},
|
||||
updateContractImageUrl(targetNo = '', imageUrl = '') {
|
||||
const normalizedNo = String(targetNo || '').trim()
|
||||
const normalizedImageUrl = addFilePrefix(String(imageUrl || '').trim())
|
||||
if (!normalizedNo || !normalizedImageUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
const patchList = (list = []) => list.map((contract) => {
|
||||
const contractNo = String((contract && (contract.no || contract.contractNo)) || '').trim()
|
||||
if (contractNo !== normalizedNo) {
|
||||
return contract
|
||||
}
|
||||
return {
|
||||
...contract,
|
||||
contentImageUrl: normalizedImageUrl
|
||||
}
|
||||
})
|
||||
|
||||
this.pendingContracts = patchList(this.pendingContracts)
|
||||
this.effectiveContracts = patchList(this.effectiveContracts)
|
||||
},
|
||||
isCurrentLogin() {
|
||||
const storeUser = this.getStoreUser()
|
||||
if (storeUser && Object.keys(storeUser).length) {
|
||||
@@ -201,12 +296,23 @@ export default {
|
||||
pendingContracts = [],
|
||||
effectiveContracts = []
|
||||
} = {}) {
|
||||
this.pendingContracts = Array.isArray(pendingContracts)
|
||||
const normalizedPendingContracts = Array.isArray(pendingContracts)
|
||||
? pendingContracts.map(normalizeContract)
|
||||
: []
|
||||
this.effectiveContracts = Array.isArray(effectiveContracts)
|
||||
const normalizedEffectiveContracts = Array.isArray(effectiveContracts)
|
||||
? effectiveContracts.map(normalizeContract)
|
||||
: []
|
||||
|
||||
this.pendingContracts = normalizedPendingContracts
|
||||
this.effectiveContracts = normalizedEffectiveContracts
|
||||
|
||||
Promise.all([
|
||||
enrichContractDisplayNames(normalizedPendingContracts, this.language),
|
||||
enrichContractDisplayNames(normalizedEffectiveContracts, this.language)
|
||||
]).then(([nextPendingContracts, nextEffectiveContracts]) => {
|
||||
this.pendingContracts = nextPendingContracts
|
||||
this.effectiveContracts = nextEffectiveContracts
|
||||
})
|
||||
},
|
||||
refreshContracts() {
|
||||
if (!this.isCurrentLogin()) {
|
||||
@@ -218,20 +324,32 @@ export default {
|
||||
this.loading = true
|
||||
return syncLoginStayState({ force: true })
|
||||
.then((result = {}) => {
|
||||
this.applyContractState({
|
||||
const fallbackState = {
|
||||
pendingContracts: result.pendingContracts,
|
||||
effectiveContracts: result.effectiveContracts
|
||||
})
|
||||
}
|
||||
const currentOrderNo = this.resolveCurrentOrderNo(result.currentOrder)
|
||||
|
||||
return this.fetchLatestContractsByOrderNo(currentOrderNo)
|
||||
.then((latestState) => {
|
||||
this.applyContractState(latestState || fallbackState)
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('refresh contracts failed', error)
|
||||
const storePendingContracts = this.getStorePendingContracts()
|
||||
const storeEffectiveContracts = this.getStoreEffectiveContracts()
|
||||
const session = getSessionSnapshot()
|
||||
this.applyContractState({
|
||||
const fallbackState = {
|
||||
pendingContracts: storePendingContracts.length ? storePendingContracts : session.pendingContracts,
|
||||
effectiveContracts: storeEffectiveContracts.length ? storeEffectiveContracts : session.effectiveContracts
|
||||
})
|
||||
}
|
||||
const currentOrderNo = this.resolveCurrentOrderNo()
|
||||
|
||||
return this.fetchLatestContractsByOrderNo(currentOrderNo)
|
||||
.then((latestState) => {
|
||||
this.applyContractState(latestState || fallbackState)
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
@@ -265,6 +383,9 @@ export default {
|
||||
formatDate(value) {
|
||||
return value ? String(value).slice(0, 10) : '--'
|
||||
},
|
||||
getContractKey(item = {}, index = 0) {
|
||||
return item.no || item.contractNo || item.orderNo || `contract-${index}`
|
||||
},
|
||||
formatDateRange(start, end) {
|
||||
if (!start && !end) {
|
||||
return '--'
|
||||
@@ -290,15 +411,84 @@ export default {
|
||||
if (this.shouldShowSignAction(item)) {
|
||||
return this.$tr('去签约')
|
||||
}
|
||||
return this.$tr('查看详情')
|
||||
return this.$tr('查看合同')
|
||||
},
|
||||
shouldShowSignAction(item = {}) {
|
||||
return Number(item.approvalStatus) === 0 && !item.signImageUrl
|
||||
},
|
||||
handleContractAction(item = {}) {
|
||||
previewContractImage(source = {}) {
|
||||
const item = this.resolveContractItem(source)
|
||||
const directImageUrl = addFilePrefix(String(item.contentImageUrl || '').trim())
|
||||
if (directImageUrl) {
|
||||
uni.previewImage({
|
||||
urls: [directImageUrl],
|
||||
current: directImageUrl
|
||||
})
|
||||
return Promise.resolve(directImageUrl)
|
||||
}
|
||||
|
||||
const orderNo = String(item.orderNo || '').trim()
|
||||
const contractNo = String(item.no || item.contractNo || '').trim()
|
||||
if (!orderNo) {
|
||||
uni.showToast({
|
||||
title: this.$tr('暂无合同图片'),
|
||||
icon: 'none'
|
||||
})
|
||||
return Promise.resolve('')
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
title: this.$tr('加载中')
|
||||
})
|
||||
|
||||
return this.fetchLatestContractsByOrderNo(orderNo)
|
||||
.then((latestState) => {
|
||||
const effectiveContracts = latestState && Array.isArray(latestState.effectiveContracts)
|
||||
? latestState.effectiveContracts
|
||||
: []
|
||||
const matchedContract = effectiveContracts.find((contract) => {
|
||||
const currentNo = String((contract && (contract.no || contract.contractNo)) || '').trim()
|
||||
const currentOrderNo = String((contract && contract.orderNo) || '').trim()
|
||||
if (contractNo && currentNo) {
|
||||
return currentNo === contractNo
|
||||
}
|
||||
return currentOrderNo === orderNo
|
||||
}) || effectiveContracts[0] || null
|
||||
const imageUrl = addFilePrefix(String(matchedContract && matchedContract.contentImageUrl || '').trim())
|
||||
|
||||
if (!imageUrl) {
|
||||
uni.showToast({
|
||||
title: this.$tr('暂无合同图片'),
|
||||
icon: 'none'
|
||||
})
|
||||
return ''
|
||||
}
|
||||
|
||||
if (contractNo) {
|
||||
this.updateContractImageUrl(contractNo, imageUrl)
|
||||
}
|
||||
uni.previewImage({
|
||||
urls: [imageUrl],
|
||||
current: imageUrl
|
||||
})
|
||||
return imageUrl
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('preview contract image failed', error)
|
||||
uni.showToast({
|
||||
title: this.$tr('合同图片加载失败'),
|
||||
icon: 'none'
|
||||
})
|
||||
return ''
|
||||
})
|
||||
.finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
handleContractAction(source = {}) {
|
||||
const item = this.resolveContractItem(source)
|
||||
const contractNo = encodeURIComponent(item.no || item.contractNo || '')
|
||||
const orderNo = encodeURIComponent(item.orderNo || '')
|
||||
const contentImageUrl = encodeURIComponent(item.contentImageUrl || '')
|
||||
|
||||
if (Number(item.approvalStatus) === 0 && !item.signImageUrl) {
|
||||
uni.navigateTo({
|
||||
@@ -307,9 +497,7 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pkgService/contract_detail/contract_detail?no=${contractNo}&orderNo=${orderNo}&contentImageUrl=${contentImageUrl}`
|
||||
})
|
||||
this.previewContractImage(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
<view class="item-order">{{ String(index + 1).padStart(2, '0') }}</view>
|
||||
<view class="item-title-col">
|
||||
<view class="item-title-row">
|
||||
<text class="item-name">{{ item.deviceName || $tr('未命名设备') }}</text>
|
||||
<text class="item-name">{{ getInspectionItemDisplayName(item) || $tr('未命名设备') }}</text>
|
||||
<text v-if="item.isRequiredSnapshot" class="item-required">{{ $tr('必查') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -393,6 +393,7 @@ export default {
|
||||
.map(item => ({
|
||||
itemId: item.id,
|
||||
deviceName: item.deviceName || '',
|
||||
assetNameEn: item.assetNameEn || item.deviceNameEn || '',
|
||||
brandSnapshot: item.brandSnapshot || '',
|
||||
isRequiredSnapshot: !!item.isRequiredSnapshot,
|
||||
expectedQuantityText: item.expectedQuantity === null || item.expectedQuantity === undefined ? '--' : String(item.expectedQuantity),
|
||||
@@ -415,6 +416,12 @@ export default {
|
||||
items
|
||||
}
|
||||
},
|
||||
getInspectionItemDisplayName(item = {}) {
|
||||
if (this.language === 'en') {
|
||||
return item.assetNameEn || item.deviceName || ''
|
||||
}
|
||||
return item.deviceName || item.assetNameEn || ''
|
||||
},
|
||||
async loadPage(force = false) {
|
||||
this.loading = true
|
||||
try {
|
||||
@@ -492,14 +499,23 @@ export default {
|
||||
if (!result) {
|
||||
return 'pending'
|
||||
}
|
||||
const label = this.getInspectionItemResultLabel(result, '')
|
||||
if (/未检|待检|未查/i.test(label)) {
|
||||
const matchedOption = (Array.isArray(this.inspectionResultOptions) ? this.inspectionResultOptions : []).find((option) => {
|
||||
return String(option && option.key) === result
|
||||
}) || null
|
||||
const candidateText = [
|
||||
this.getInspectionItemResultLabel(result, ''),
|
||||
matchedOption && matchedOption.value,
|
||||
matchedOption && matchedOption.label,
|
||||
result
|
||||
].filter(value => value !== undefined && value !== null).join(' ')
|
||||
|
||||
if (/未检|待检|未查|unchecked|notchecked|pending|not checked|not inspected/i.test(candidateText)) {
|
||||
return 'pending'
|
||||
}
|
||||
if (/问题|异常|损坏|缺失|故障/i.test(label)) {
|
||||
if (/问题|异常|损坏|缺失|故障|problem|issue|abnormal|damage|damaged|missing|fault|broken|unusable/i.test(candidateText)) {
|
||||
return 'abnormal'
|
||||
}
|
||||
if (/正常|可用|完好/i.test(label)) {
|
||||
if (/正常|可用|完好|normal|available|good|intact|ok/i.test(candidateText)) {
|
||||
return 'normal'
|
||||
}
|
||||
return 'done'
|
||||
@@ -944,6 +960,7 @@ export default {
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18rpx;
|
||||
padding: 22rpx 24rpx;
|
||||
}
|
||||
@@ -1004,8 +1021,9 @@ export default {
|
||||
}
|
||||
|
||||
.item-header__right {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
max-width: 56%;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.item-body {
|
||||
@@ -1099,6 +1117,7 @@ export default {
|
||||
margin-top: 0;
|
||||
justify-content: flex-end;
|
||||
gap: 10rpx;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.radio-btn {
|
||||
@@ -1116,10 +1135,11 @@ export default {
|
||||
|
||||
.radio-btn--compact {
|
||||
min-width: auto;
|
||||
padding: 12rpx 18rpx;
|
||||
padding: 12rpx 16rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.radio-btn--active {
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
<text class="tn-icon-lock tn-color-gray" style="font-size: 80rpx;"></text>
|
||||
</view>
|
||||
<view class="hero-title tn-text-bold">{{ $tr('暂无门锁权限') }}</view>
|
||||
<view class="hero-desc tn-margin-top-sm tn-text-center">{{ $tr('您当前暂未分配智能门锁,或门锁暂未联网。\n入住后即可使用一键开门与临时密码下发功能。') }}</view>
|
||||
<view class="hero-desc tn-margin-top-sm tn-text-center">{{ $tr('您当前暂未分配智能门锁,或门锁暂未联网。\n入住后即可使用门锁密码、指纹录入与临时密码下发功能。') }}</view>
|
||||
<view class="tn-flex tn-margin-top-xl tn-width-full tn-flex-row-between">
|
||||
<view class="btn-light tn-flex-1 tn-margin-right-sm tn-flex tn-flex-col-center tn-flex-row-center tn-padding-sm" style="border-radius: 12rpx; opacity: 0.6;">
|
||||
<text class="tn-icon-key tn-margin-right-xs"></text> {{ $tr('临时密码') }}
|
||||
</view>
|
||||
<view class="btn-primary tn-flex-1 tn-margin-left-sm tn-flex tn-flex-col-center tn-flex-row-center tn-padding-sm" style="border-radius: 12rpx; opacity: 0.6;">
|
||||
<text class="tn-icon-unlock tn-margin-right-xs"></text> {{ $tr('一键开锁') }}
|
||||
<text class="tn-icon-fingerprint tn-margin-right-xs"></text> {{ $tr('录入指纹') }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -149,27 +149,31 @@
|
||||
<scroll-view scroll-y class="popup-body" @scrolltolower="onNoticeScrollToBottom">
|
||||
<view class="notice-content">
|
||||
<view class="notice-item">
|
||||
<view class="notice-item-zh">1、缴纳线上定金可锁定房源并预约专属看房,平台收取定金后该房源临时下架,不再接待其他客户看房。</view>
|
||||
<view class="notice-item-zh">1、请在 5 分钟内支付定金或者款项,否则系统将自动取消这个订单,释放锁定的房源。</view>
|
||||
<view class="notice-item-en">Please pay the deposit or full payment within 5 minutes. Otherwise, the system will automatically cancel this order and release the reserved housing unit.</view>
|
||||
</view>
|
||||
<view class="notice-item">
|
||||
<view class="notice-item-zh">2、缴纳线上定金可锁定房源并预约专属看房,平台收取定金后该房源临时下架,不再接待其他客户看房。</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-zh">3、请于约定时间完成实地看房,并在看房结束后 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-zh">4、如因租客个人原因取消看房、看房后不愿承租房屋或拒绝签署租赁合同,定金不予退还。</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-zh">5、若因我方房源信息虚假、房屋无法正常交付等我方过错,造成无法看房或签约,定金将全额按原支付路径退还。</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-zh">6、待双方正式签署租赁合同后,该定金可直接抵扣首期租金。</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-zh">7、您支付定金,即代表已完整知晓房源全部信息,并自愿接受本须知全部条款约束。</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>
|
||||
</view>
|
||||
@@ -180,18 +184,91 @@
|
||||
:class="{ 'notice-action-btn--disabled': !hasReadNotice }"
|
||||
@click="confirmAndPayDeposit"
|
||||
>
|
||||
<text class="tn-text-bold">{{ $tr('同意并支付') }}</text>
|
||||
<text class="tn-text-bold">{{ $tr('同意并继续') }}</text>
|
||||
<text v-if="!hasReadNotice" class="tn-margin-left-xs tn-text-sm">({{ $tr('请滑动阅读至底部') }})</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</tn-popup>
|
||||
|
||||
<tn-popup v-model="showPaymentMethodPopup" mode="bottom" length="72%" :borderRadius="32" :closeBtn="true" @close="closePaymentMethodPopup">
|
||||
<view class="payment-method-popup">
|
||||
<view class="payment-method-popup__header">
|
||||
<view class="payment-method-popup__eyebrow">{{ $tr('支付方式') }}</view>
|
||||
<view class="payment-method-popup__title">{{ $tr('选择定金支付方式') }}</view>
|
||||
<view class="payment-method-popup__desc">
|
||||
{{ language === 'en'
|
||||
? 'Choose the channel you want to use for the room-type application deposit.'
|
||||
: '请选择房型申请定金的支付渠道,提交后可在“我的入住”继续查看进度。' }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="payment-method-popup__summary">
|
||||
<view class="payment-method-popup__summary-label">{{ $tr('支付号') }}</view>
|
||||
<view class="payment-method-popup__summary-value">
|
||||
{{ (pendingPaymentMethodContext && pendingPaymentMethodContext.paymentNo) || $tr('提交后生成') }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="payment-method-card payment-method-card--wechat" @click="handlePaymentMethodSelect('wechat')">
|
||||
<view class="payment-method-card__main">
|
||||
<view class="payment-method-card__icon payment-method-card__icon--wechat">
|
||||
<text class="tn-icon-wechat"></text>
|
||||
</view>
|
||||
<view class="payment-method-card__copy">
|
||||
<view class="payment-method-card__title-row">
|
||||
<view class="payment-method-card__title">{{ $tr('微信支付') }}</view>
|
||||
<view class="payment-method-card__tag">{{ $tr('推荐') }}</view>
|
||||
</view>
|
||||
<view class="payment-method-card__desc">
|
||||
{{ language === 'en'
|
||||
? 'Continue with the standard online payment flow.'
|
||||
: '继续拉起线上支付,适合直接完成定金支付。' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="payment-method-card__arrow">
|
||||
<text class="tn-icon-right"></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="payment-method-card payment-method-card--offline" @click="handlePaymentMethodSelect('offline')">
|
||||
<view class="payment-method-card__main">
|
||||
<view class="payment-method-card__icon payment-method-card__icon--offline">
|
||||
<text class="tn-icon-bank-card"></text>
|
||||
</view>
|
||||
<view class="payment-method-card__copy">
|
||||
<view class="payment-method-card__title-row">
|
||||
<view class="payment-method-card__title">{{ $tr('其他支付方式') }}</view>
|
||||
</view>
|
||||
<view class="payment-method-card__desc">
|
||||
{{ language === 'en'
|
||||
? 'View the configured phone, QR code, and offline payment instructions.'
|
||||
: '查看后台配置的联系电话、二维码与线下支付说明。' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="payment-method-card__arrow">
|
||||
<text class="tn-icon-right"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</tn-popup>
|
||||
|
||||
<deposit-other-payment-popup
|
||||
v-model="showOfflinePaymentNotice"
|
||||
:config="depositOtherPaymentConfig"
|
||||
:loading="depositOtherPaymentConfigLoading"
|
||||
@input="handleOfflinePaymentPopupInput"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { accountQueryBill, accountQueryBills, applicationFormDetail, paymentBillPay, paymentPayingBills, submitApplicationForm, sysdictPage } from '@/api/index.js'
|
||||
import DepositOtherPaymentPopup from '@/components/DepositOtherPaymentPopup.vue'
|
||||
import { saveActiveApplicationCode, saveHomePageTabId } from '@/utils/app-state'
|
||||
import { createDefaultDepositOtherPaymentConfig, loadDepositOtherPaymentConfig } from '@/utils/deposit-other-payment'
|
||||
import { compactEnglishText, localizePayMethodText } from '../../utils/select-room-normalizers'
|
||||
import { getStoredUser } from '../../utils/flow'
|
||||
|
||||
@@ -725,6 +802,7 @@ const BUSINESS_TYPE_CONFIG = {
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DepositOtherPaymentPopup,
|
||||
TagSelector,
|
||||
DateRangeSelector,
|
||||
RoomTypeSelector,
|
||||
@@ -747,9 +825,15 @@ export default {
|
||||
scrollTop: 0,
|
||||
lastAutoRemark: '',
|
||||
showDepositNotice: false,
|
||||
showPaymentMethodPopup: false,
|
||||
showOfflinePaymentNotice: false,
|
||||
hasReadNotice: false,
|
||||
isClosingDepositNoticeForPayment: false,
|
||||
pendingDepositPaymentContext: null
|
||||
isSwitchingPaymentMethodPopup: false,
|
||||
pendingDepositPaymentContext: null,
|
||||
pendingPaymentMethodContext: null,
|
||||
depositOtherPaymentConfig: createDefaultDepositOtherPaymentConfig(),
|
||||
depositOtherPaymentConfigLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -882,6 +966,9 @@ export default {
|
||||
language() {
|
||||
this.syncLocalizedDraftContent()
|
||||
this.loadDictOptions()
|
||||
if (this.showOfflinePaymentNotice) {
|
||||
this.loadDepositOtherPaymentConfig()
|
||||
}
|
||||
}
|
||||
},
|
||||
onPageScroll(event) {
|
||||
@@ -1227,14 +1314,66 @@ export default {
|
||||
},
|
||||
async resolvePaymentPreview(referenceTypeNo = '') {
|
||||
const context = await this.resolveApplicationDetailPaymentContext(referenceTypeNo).catch(() => null)
|
||||
const billNo = context && context.billNo ? context.billNo : ''
|
||||
const pendingPaymentOrder = billNo ? await this.getPendingPaymentOrder(billNo).catch(() => null) : null
|
||||
return {
|
||||
referenceTypeNo: context && context.referenceTypeNo ? context.referenceTypeNo : String(referenceTypeNo || '').trim(),
|
||||
billNo,
|
||||
paymentNo: this.getPendingPaymentOrderNo(pendingPaymentOrder) || (context && context.paymentNo ? context.paymentNo : ''),
|
||||
pendingPaymentOrder
|
||||
const normalizedReferenceTypeNo = context && context.referenceTypeNo
|
||||
? context.referenceTypeNo
|
||||
: String(referenceTypeNo || '').trim()
|
||||
let billNo = context && context.billNo ? context.billNo : ''
|
||||
let pendingPaymentOrder = billNo ? await this.getPendingPaymentOrder(billNo).catch(() => null) : null
|
||||
let requiresDepositPayment = !!billNo
|
||||
|
||||
if (normalizedReferenceTypeNo) {
|
||||
const billRes = await accountQueryBills({
|
||||
referenceTypeNo: normalizedReferenceTypeNo,
|
||||
status: 10,
|
||||
paymentStatus: 0,
|
||||
ProviderType: 'WechatMiniProgram'
|
||||
}, this.language)
|
||||
const billList = this.normalizeBillList(billRes)
|
||||
const matchedBill = Array.isArray(billList) && billList.length
|
||||
? ((billNo
|
||||
? billList.find(item => String(this.getBillNo(item) || '').trim() === String(billNo || '').trim())
|
||||
: null) || billList[0])
|
||||
: null
|
||||
|
||||
if (matchedBill) {
|
||||
billNo = this.getBillNo(matchedBill)
|
||||
requiresDepositPayment = !!billNo
|
||||
if (!pendingPaymentOrder && billNo) {
|
||||
pendingPaymentOrder = await this.getPendingPaymentOrder(billNo).catch(() => null)
|
||||
}
|
||||
if (!pendingPaymentOrder) {
|
||||
pendingPaymentOrder = this.buildPendingPaymentOrderFromBillDetail(matchedBill)
|
||||
}
|
||||
} else {
|
||||
billNo = ''
|
||||
pendingPaymentOrder = null
|
||||
requiresDepositPayment = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
referenceTypeNo: normalizedReferenceTypeNo,
|
||||
billNo,
|
||||
paymentNo: requiresDepositPayment
|
||||
? (this.getPendingPaymentOrderNo(pendingPaymentOrder) || (context && context.paymentNo ? context.paymentNo : ''))
|
||||
: '',
|
||||
pendingPaymentOrder,
|
||||
requiresDepositPayment
|
||||
}
|
||||
},
|
||||
confirmSubmitRequest() {
|
||||
return new Promise((resolve) => {
|
||||
uni.showModal({
|
||||
title: this.$tr('提交前确认'),
|
||||
content: this.$tr('确认提交当前申请单吗?'),
|
||||
// WeChat Mini Program limits modal button text length.
|
||||
// Keep English actions short so the modal does not fail silently.
|
||||
confirmText: this.language === 'en' ? 'OK' : this.$tr('确认提交'),
|
||||
cancelText: this.language === 'en' ? 'Back' : this.$tr('再想想'),
|
||||
success: (res) => resolve(!!(res && res.confirm)),
|
||||
fail: () => resolve(false)
|
||||
})
|
||||
})
|
||||
},
|
||||
openDepositNotice(paymentContext = {}) {
|
||||
this.pendingDepositPaymentContext = {
|
||||
@@ -1243,6 +1382,7 @@ export default {
|
||||
paymentNo: String(paymentContext.paymentNo || '').trim(),
|
||||
pendingPaymentOrder: paymentContext.pendingPaymentOrder || null
|
||||
}
|
||||
this.pendingPaymentMethodContext = null
|
||||
this.hasReadNotice = false
|
||||
this.showDepositNotice = true
|
||||
},
|
||||
@@ -1254,11 +1394,92 @@ export default {
|
||||
this.showDepositNotice = false
|
||||
this.hasReadNotice = false
|
||||
this.pendingDepositPaymentContext = null
|
||||
this.pendingPaymentMethodContext = null
|
||||
this.goStayServiceFlow()
|
||||
},
|
||||
onNoticeScrollToBottom() {
|
||||
this.hasReadNotice = true
|
||||
},
|
||||
openPaymentMethodPopup(paymentContext = {}) {
|
||||
this.pendingPaymentMethodContext = {
|
||||
referenceTypeNo: String(paymentContext.referenceTypeNo || '').trim(),
|
||||
billNo: String(paymentContext.billNo || '').trim(),
|
||||
paymentNo: String(paymentContext.paymentNo || '').trim(),
|
||||
pendingPaymentOrder: paymentContext.pendingPaymentOrder || null
|
||||
}
|
||||
this.showPaymentMethodPopup = true
|
||||
},
|
||||
closePaymentMethodPopup() {
|
||||
if (this.isSwitchingPaymentMethodPopup) {
|
||||
this.isSwitchingPaymentMethodPopup = false
|
||||
return
|
||||
}
|
||||
this.showPaymentMethodPopup = false
|
||||
this.pendingPaymentMethodContext = null
|
||||
this.goStayServiceFlow()
|
||||
},
|
||||
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
|
||||
})
|
||||
},
|
||||
async handlePaymentMethodSelect(type = '') {
|
||||
const paymentContext = this.pendingPaymentMethodContext || {}
|
||||
this.isSwitchingPaymentMethodPopup = true
|
||||
this.showPaymentMethodPopup = false
|
||||
|
||||
if (type === 'offline') {
|
||||
setTimeout(() => {
|
||||
this.showOfflinePaymentNotice = true
|
||||
this.loadDepositOtherPaymentConfig()
|
||||
}, 40)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.payByPaymentContext({
|
||||
referenceTypeNo: paymentContext.referenceTypeNo,
|
||||
billNo: paymentContext.billNo,
|
||||
pendingPaymentOrder: paymentContext.pendingPaymentOrder,
|
||||
description: this.$tr('支付定金')
|
||||
})
|
||||
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()
|
||||
this.pendingPaymentMethodContext = null
|
||||
setTimeout(() => {
|
||||
this.goStayServiceFlow()
|
||||
}, 300)
|
||||
}
|
||||
},
|
||||
handleOfflinePaymentPopupInput(value) {
|
||||
this.showOfflinePaymentNotice = !!value
|
||||
if (value) {
|
||||
return
|
||||
}
|
||||
this.pendingPaymentMethodContext = null
|
||||
setTimeout(() => {
|
||||
this.goStayServiceFlow()
|
||||
}, 300)
|
||||
},
|
||||
async payByPaymentContext(context = {}) {
|
||||
let referenceTypeNo = String(context.referenceTypeNo || '').trim()
|
||||
let billNo = String(context.billNo || '').trim()
|
||||
@@ -1367,31 +1588,66 @@ export default {
|
||||
const paymentContext = this.pendingDepositPaymentContext || {}
|
||||
this.isClosingDepositNoticeForPayment = true
|
||||
this.showDepositNotice = false
|
||||
this.hasReadNotice = false
|
||||
this.pendingDepositPaymentContext = null
|
||||
setTimeout(() => {
|
||||
this.openPaymentMethodPopup(paymentContext)
|
||||
}, 40)
|
||||
},
|
||||
async executeSubmitRequest() {
|
||||
this.isSubmitting = true
|
||||
uni.showLoading({
|
||||
title: this.$tr('提交中...')
|
||||
})
|
||||
|
||||
try {
|
||||
await this.payByPaymentContext({
|
||||
referenceTypeNo: paymentContext.referenceTypeNo,
|
||||
billNo: paymentContext.billNo,
|
||||
pendingPaymentOrder: paymentContext.pendingPaymentOrder,
|
||||
description: this.$tr('支付定金')
|
||||
})
|
||||
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' })
|
||||
const payload = this.buildSubmitPayload()
|
||||
const response = await submitApplicationForm(payload, this.language)
|
||||
const applicationCode = extractSubmitApplicationCode(response)
|
||||
if (this.isRoomTypeApply && !applicationCode) {
|
||||
throw new Error(this.$tr('申请单编号缺失'))
|
||||
}
|
||||
if (applicationCode) {
|
||||
saveActiveApplicationCode(applicationCode)
|
||||
}
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
this.hasReadNotice = false
|
||||
this.isClosingDepositNoticeForPayment = false
|
||||
this.pendingDepositPaymentContext = null
|
||||
|
||||
if (this.isRoomTypeApply) {
|
||||
const paymentContext = await this.resolvePaymentPreview(applicationCode).catch(() => null)
|
||||
if (paymentContext && paymentContext.requiresDepositPayment === false) {
|
||||
uni.showToast({
|
||||
title: this.$tr('申请单提交成功'),
|
||||
icon: 'success'
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.goStayServiceFlow()
|
||||
}, 800)
|
||||
return
|
||||
}
|
||||
this.openDepositNotice({
|
||||
...(paymentContext || {}),
|
||||
referenceTypeNo: (paymentContext && paymentContext.referenceTypeNo) || applicationCode
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: this.$tr('申请单提交成功'),
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.goStayServiceFlow()
|
||||
}, 300)
|
||||
saveHomePageTabId('stay-service')
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index?pageId=stay-service'
|
||||
})
|
||||
}, 800)
|
||||
} catch (error) {
|
||||
console.error('submit application form failed', error)
|
||||
uni.hideLoading()
|
||||
throw error
|
||||
} finally {
|
||||
this.isSubmitting = false
|
||||
}
|
||||
},
|
||||
submitRequest() {
|
||||
@@ -1408,58 +1664,21 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
this.isSubmitting = true
|
||||
uni.showLoading({
|
||||
title: this.$tr('提交中...')
|
||||
})
|
||||
|
||||
const payload = this.buildSubmitPayload()
|
||||
return submitApplicationForm(payload, this.language)
|
||||
.then(async (response) => {
|
||||
const applicationCode = extractSubmitApplicationCode(response)
|
||||
if (this.isRoomTypeApply && !applicationCode) {
|
||||
throw new Error(this.$tr('申请单编号缺失'))
|
||||
}
|
||||
if (applicationCode) {
|
||||
saveActiveApplicationCode(applicationCode)
|
||||
}
|
||||
uni.hideLoading()
|
||||
if (this.isRoomTypeApply) {
|
||||
const paymentContext = await this.resolvePaymentPreview(applicationCode).catch(() => ({
|
||||
referenceTypeNo: applicationCode,
|
||||
billNo: '',
|
||||
paymentNo: '',
|
||||
pendingPaymentOrder: null
|
||||
}))
|
||||
this.openDepositNotice({
|
||||
...paymentContext,
|
||||
referenceTypeNo: paymentContext.referenceTypeNo || applicationCode
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: this.$tr('申请单提交成功'),
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
saveHomePageTabId('stay-service')
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index?pageId=stay-service'
|
||||
})
|
||||
}, 800)
|
||||
return this.confirmSubmitRequest()
|
||||
.then((confirmed) => {
|
||||
if (!confirmed) {
|
||||
return null
|
||||
}
|
||||
return this.executeSubmitRequest()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('submit application form failed', error)
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: (error && (error.message || error.msg)) || this.$tr('申请单提交失败'),
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.isSubmitting = false
|
||||
if (!error) {
|
||||
return
|
||||
}
|
||||
uni.showToast({
|
||||
title: (error && (error.message || error.msg)) || this.$tr('申请单提交失败'),
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1805,6 +2024,172 @@ $shadow-active: 0 8rpx 24rpx rgba($brand-primary, 0.22);
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.payment-method-popup {
|
||||
padding: 30rpx 30rpx calc(28rpx + env(safe-area-inset-bottom));
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba($brand-primary, 0.16) 0%, rgba($brand-primary, 0) 36%),
|
||||
linear-gradient(180deg, #ffffff 0%, #f7fbff 100%);
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
}
|
||||
|
||||
.payment-method-popup__header {
|
||||
padding: 8rpx 4rpx 20rpx;
|
||||
}
|
||||
|
||||
.payment-method-popup__eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba($brand-primary, 0.1);
|
||||
color: $brand-primary;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.payment-method-popup__title {
|
||||
margin-top: 18rpx;
|
||||
font-size: 40rpx;
|
||||
line-height: 1.25;
|
||||
color: $text-primary;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.payment-method-popup__desc {
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.payment-method-popup__summary {
|
||||
margin-bottom: 22rpx;
|
||||
padding: 22rpx 26rpx;
|
||||
border-radius: 24rpx;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||||
box-shadow: 0 12rpx 28rpx rgba($brand-primary, 0.08);
|
||||
}
|
||||
|
||||
.payment-method-popup__summary-label {
|
||||
font-size: 22rpx;
|
||||
color: $text-tertiary;
|
||||
}
|
||||
|
||||
.payment-method-popup__summary-value {
|
||||
margin-top: 8rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.5;
|
||||
color: $text-primary;
|
||||
font-weight: 700;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.payment-method-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
margin-top: 18rpx;
|
||||
padding: 26rpx;
|
||||
border-radius: 28rpx;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||||
box-shadow: 0 14rpx 30rpx rgba($brand-primary, 0.08);
|
||||
}
|
||||
|
||||
.payment-method-card:active {
|
||||
transform: scale(0.985);
|
||||
}
|
||||
|
||||
.payment-method-card--wechat {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(239, 253, 244, 0.96) 100%);
|
||||
border-color: rgba(34, 197, 94, 0.14);
|
||||
}
|
||||
|
||||
.payment-method-card--offline {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(247, 250, 255, 0.96) 100%);
|
||||
}
|
||||
|
||||
.payment-method-card__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.payment-method-card__icon {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 26rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 42rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.payment-method-card__icon--wechat {
|
||||
background: rgba(34, 197, 94, 0.12);
|
||||
color: #16A34A;
|
||||
}
|
||||
|
||||
.payment-method-card__icon--offline {
|
||||
background: rgba($brand-primary, 0.1);
|
||||
color: $brand-primary;
|
||||
}
|
||||
|
||||
.payment-method-card__copy {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.payment-method-card__title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.payment-method-card__title {
|
||||
font-size: 30rpx;
|
||||
line-height: 1.35;
|
||||
color: $text-primary;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.payment-method-card__tag {
|
||||
padding: 6rpx 14rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(34, 197, 94, 0.12);
|
||||
color: #15803D;
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.payment-method-card__desc {
|
||||
margin-top: 10rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.7;
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.payment-method-card__arrow {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(148, 163, 184, 0.12);
|
||||
color: #64748B;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 导航栏背景色滚动过渡 */
|
||||
.submit-request-page ::v-deep .tn-custom-nav-bar__bar {
|
||||
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<current-application-bill-panel
|
||||
ref="billPanel"
|
||||
:reference-type-no="referenceTypeNo"
|
||||
:auto-resolve-reference="!pendingBillNo || !!referenceTypeNo"
|
||||
@reference-resolved="handleReferenceResolved"
|
||||
@all-paid="handleAllPaid"
|
||||
/>
|
||||
@@ -16,6 +17,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { accountQueryBill } from '@/api/index.js'
|
||||
import { fetchEnums } from '@/utils/enum-store.js'
|
||||
import { saveActiveApplicationCode, saveHomePageTabId } from '@/utils/app-state'
|
||||
import CurrentApplicationBillPanel from '../components/CurrentApplicationBillPanel.vue'
|
||||
@@ -26,17 +28,54 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
referenceTypeNo: ''
|
||||
referenceTypeNo: '',
|
||||
pendingBillNo: ''
|
||||
}
|
||||
},
|
||||
async onLoad(options) {
|
||||
await fetchEnums(false, this.$getLanguage())
|
||||
this.referenceTypeNo = options && options.referenceTypeNo ? decodeURIComponent(options.referenceTypeNo) : ''
|
||||
this.pendingBillNo = options && options.billNo ? decodeURIComponent(options.billNo) : ''
|
||||
if (!this.referenceTypeNo && this.pendingBillNo) {
|
||||
await this.resolveReferenceTypeNoByBillNo(this.pendingBillNo)
|
||||
}
|
||||
if (this.referenceTypeNo) {
|
||||
saveActiveApplicationCode(this.referenceTypeNo)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
extractBillDetail(response) {
|
||||
const payload = response && response.data ? response.data : response
|
||||
if (payload && payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) {
|
||||
return payload.data
|
||||
}
|
||||
return payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : {}
|
||||
},
|
||||
async resolveReferenceTypeNoByBillNo(billNo = '') {
|
||||
const currentBillNo = String(billNo || '').trim()
|
||||
if (!currentBillNo) {
|
||||
return ''
|
||||
}
|
||||
try {
|
||||
const response = await accountQueryBill({ billNo: currentBillNo }, this.$getLanguage())
|
||||
const detail = this.extractBillDetail(response)
|
||||
const referenceTypeNo = String(
|
||||
detail.referenceTypeNo
|
||||
|| detail.ReferenceTypeNo
|
||||
|| detail.detailCode
|
||||
|| detail.applicationCode
|
||||
|| ''
|
||||
).trim()
|
||||
if (referenceTypeNo) {
|
||||
this.referenceTypeNo = referenceTypeNo
|
||||
saveActiveApplicationCode(referenceTypeNo)
|
||||
}
|
||||
return referenceTypeNo
|
||||
} catch (error) {
|
||||
console.error('根据账单号解析申请单号失败', error)
|
||||
return ''
|
||||
}
|
||||
},
|
||||
handleReferenceResolved(referenceTypeNo) {
|
||||
this.referenceTypeNo = referenceTypeNo || ''
|
||||
if (this.referenceTypeNo) {
|
||||
|
||||
Reference in New Issue
Block a user