feat: 优化首页服务入口与租住业务流程
补齐首页待办跳转、押金支付引导和多类租住服务页面能力,统一租客端关键业务流程体验。
This commit is contained in:
74
api/index.js
74
api/index.js
@@ -30,6 +30,10 @@ function buildIotGatewayUrl(path = '') {
|
||||
return `${iotBaseUrl}${normalizedPath}`
|
||||
}
|
||||
|
||||
function shouldIgnoreGatewayRoom404(error = {}) {
|
||||
return Number(error && error.statusCode) === 404
|
||||
}
|
||||
|
||||
function normalizeUploadResponse(data) {
|
||||
if (typeof data !== 'string') {
|
||||
return Promise.resolve(data)
|
||||
@@ -445,14 +449,15 @@ export function orderSearchedReservationFlow(data = {}, language) {
|
||||
})
|
||||
}
|
||||
|
||||
export function roomInspectionMyPage(params = {}, language) {
|
||||
export function roomInspectionMyPage(params = {}, language, options = {}) {
|
||||
return request({
|
||||
url: '/roominspection/my/page',
|
||||
method: 'GET',
|
||||
params: params || {},
|
||||
header: {
|
||||
'X-Requested-Language': resolveLanguageHeader(language)
|
||||
}
|
||||
},
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
@@ -733,6 +738,18 @@ export function notificationRead(code, language) {
|
||||
})
|
||||
}
|
||||
|
||||
export function getMyTodoList(params = {}, language, options = {}) {
|
||||
return request({
|
||||
url: '/todo/my/list',
|
||||
method: 'GET',
|
||||
params: params || {},
|
||||
header: {
|
||||
'X-Requested-Language': resolveLanguageHeader(language)
|
||||
},
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
// --- 房源与区域 ---
|
||||
export function areaPagination(params = {}, language) {
|
||||
return request({
|
||||
@@ -746,6 +763,18 @@ export function areaPagination(params = {}, language) {
|
||||
})
|
||||
}
|
||||
|
||||
export function areaDetailPagination(params = {}, language) {
|
||||
return request({
|
||||
url: '/area/pagination',
|
||||
method: 'GET',
|
||||
params,
|
||||
header: {
|
||||
'X-Requested-Language': resolveLanguageHeader(language)
|
||||
},
|
||||
withAuth: false
|
||||
})
|
||||
}
|
||||
|
||||
export function facilityManagers(params = {}, language) {
|
||||
return request({
|
||||
url: '/facility/managers',
|
||||
@@ -855,8 +884,15 @@ export function gatewayRoomDeviceList(brandId = '', roomCode = '', language) {
|
||||
method: 'POST',
|
||||
header: {
|
||||
'X-Requested-Language': resolveLanguageHeader(language)
|
||||
}
|
||||
},
|
||||
hideErrorToast: true
|
||||
})
|
||||
.catch((error) => {
|
||||
if (shouldIgnoreGatewayRoom404(error)) {
|
||||
return []
|
||||
}
|
||||
return Promise.reject(error)
|
||||
})
|
||||
}
|
||||
|
||||
function stringifyGatewayExecuteValue(value = '') {
|
||||
@@ -959,6 +995,38 @@ export function gatewaySyncDeviceInfo(brandId = '', deviceNo = '', language) {
|
||||
})
|
||||
}
|
||||
|
||||
// --- 门锁 ---
|
||||
export function deviceGetDevicesByRoomAsync(params = {}, language) {
|
||||
const roomCode = String((params && params.roomCode) || '').trim()
|
||||
if (!roomCode) {
|
||||
return Promise.reject(new Error('roomCode 不能为空'))
|
||||
}
|
||||
|
||||
return request({
|
||||
url: '/device/getdevicesbyroomasync',
|
||||
method: 'GET',
|
||||
params: {
|
||||
roomCode
|
||||
},
|
||||
header: {
|
||||
'X-Requested-Language': resolveLanguageHeader(language)
|
||||
},
|
||||
hideErrorToast: true
|
||||
})
|
||||
}
|
||||
|
||||
export function lockHxjOpenLock(data = {}, language) {
|
||||
return createJsonPatchRequest('/LockHxj/OpenLock', data, language)
|
||||
}
|
||||
|
||||
export function lockHxjGetKeysByUserCode(data = {}, language) {
|
||||
return createJsonPatchRequest('/LockHxj/GetKeysByUserCode', data, language)
|
||||
}
|
||||
|
||||
export function lockHxjAddFingerKey(data = {}, language) {
|
||||
return createJsonPatchRequest('/LockHxj/AddFingerKey', data, language)
|
||||
}
|
||||
|
||||
// --- 内容 ---
|
||||
export function bannerAll(language) {
|
||||
return request({
|
||||
|
||||
376
components/DepositOtherPaymentPopup.vue
Normal file
376
components/DepositOtherPaymentPopup.vue
Normal file
@@ -0,0 +1,376 @@
|
||||
<template>
|
||||
<tn-popup :value="value" mode="bottom" length="84%" :borderRadius="32" :closeBtn="true" @close="close" @input="handleInput">
|
||||
<view class="deposit-other-payment-popup">
|
||||
<view class="popup-header">
|
||||
<view class="popup-handle"></view>
|
||||
<view class="popup-title">{{ getLocalizedText('其他支付方式', 'Other Payment Methods') }}</view>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="popup-body" :show-scrollbar="false">
|
||||
<view class="popup-content">
|
||||
<view v-if="hasConfiguredContent" class="hero-card">
|
||||
<view class="hero-badge">{{ getLocalizedText('线下支付说明', 'Alternative Payment') }}</view>
|
||||
<view class="hero-title">{{ getLocalizedText('定金支付备用方案', 'Deposit payment backup plan') }}</view>
|
||||
<view class="hero-desc">
|
||||
{{ getLocalizedText(
|
||||
'当线上支付渠道暂不可用时,可通过下方联系电话、二维码或转账说明继续完成定金支付。',
|
||||
'Use the configured contact, QR code, or transfer instructions below when the online payment channel is unavailable.'
|
||||
) }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="loading-card">
|
||||
<view class="loading-chip">{{ getLocalizedText('正在同步其他支付配置,请稍候...', 'Syncing alternative payment details...') }}</view>
|
||||
</view>
|
||||
|
||||
<template v-else>
|
||||
<view v-if="phone" class="contact-card">
|
||||
<view class="contact-main">
|
||||
<view class="contact-label">{{ getLocalizedText('联系电话', 'Contact Phone') }}</view>
|
||||
<view class="contact-value">{{ phone }}</view>
|
||||
</view>
|
||||
<view class="contact-action" @click="handleCallPhone">
|
||||
<text class="tn-text-bold">{{ getLocalizedText('拨打电话', 'Call') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="qrCodeUrl" class="qr-card">
|
||||
<view class="section-title">{{ getLocalizedText('支付二维码', 'Payment QR Code') }}</view>
|
||||
<image class="qr-image" :src="qrCodeUrl" mode="widthFix" @click="previewQrCode"></image>
|
||||
<view class="qr-hint">
|
||||
{{ getLocalizedText('点击二维码图片可查看大图。', 'Tap the image to preview the code in full size.') }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="richTextNodes" class="rich-text-card">
|
||||
<view class="section-title">{{ getLocalizedText('支付说明', 'Payment Instructions') }}</view>
|
||||
<rich-text class="rich-text-content" :nodes="richTextNodes"></rich-text>
|
||||
</view>
|
||||
|
||||
<view v-if="!phone && !qrCodeUrl && !richTextNodes" class="empty-card">
|
||||
<view class="empty-title">{{ getLocalizedText('暂未配置展示内容', 'Configuration Pending') }}</view>
|
||||
<view class="empty-desc">
|
||||
{{ getLocalizedText(
|
||||
'当前暂未同步到其他支付配置,请联系公寓工作人员处理。',
|
||||
'Alternative payment details are not available yet. Please contact the property staff for manual payment assistance.'
|
||||
) }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="popup-footer">
|
||||
<view class="notice-action-btn" @click="close">
|
||||
<text class="tn-text-bold">{{ getLocalizedText('我知道了', 'Got It') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</tn-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addFilePrefix } from '@/utils/common'
|
||||
import { normalizeRichTextImages } from '@/utils/rich-text'
|
||||
|
||||
function escapeHtml(value = '') {
|
||||
return String(value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
function normalizeContentToRichText(value = '') {
|
||||
const text = String(value || '').trim()
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
if (/<[a-z][\s\S]*>/i.test(text)) {
|
||||
return normalizeRichTextImages(
|
||||
text,
|
||||
'display:block;width:100%;max-width:100%;height:auto;box-sizing:border-box;border-radius:20rpx;margin:18rpx 0;vertical-align:top;'
|
||||
)
|
||||
}
|
||||
return `<div style="white-space:pre-wrap;line-height:1.9;color:#334155;font-size:28rpx;">${escapeHtml(text)}</div>`
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'DepositOtherPaymentPopup',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
language() {
|
||||
return this.$getLanguage()
|
||||
},
|
||||
phone() {
|
||||
return String((this.config && this.config.phone) || '').trim()
|
||||
},
|
||||
qrCodeUrl() {
|
||||
return addFilePrefix(String((this.config && this.config.qrCode) || '').trim())
|
||||
},
|
||||
localizedContent() {
|
||||
if (this.language === 'en') {
|
||||
return this.config.contentEn || this.config.contentZh || ''
|
||||
}
|
||||
return this.config.contentZh || this.config.contentEn || ''
|
||||
},
|
||||
hasConfiguredContent() {
|
||||
return !!(this.phone || this.qrCodeUrl || this.richTextNodes)
|
||||
},
|
||||
richTextNodes() {
|
||||
return normalizeContentToRichText(this.localizedContent)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getLocalizedText(zh, en) {
|
||||
return this.language === 'en' ? en : zh
|
||||
},
|
||||
handleInput(value) {
|
||||
this.$emit('input', value)
|
||||
this.$emit('update:modelValue', value)
|
||||
},
|
||||
close() {
|
||||
this.handleInput(false)
|
||||
},
|
||||
handleCallPhone() {
|
||||
if (!this.phone) {
|
||||
uni.showToast({
|
||||
title: this.getLocalizedText('暂无联系电话', 'No contact phone available'),
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: this.phone
|
||||
})
|
||||
},
|
||||
previewQrCode() {
|
||||
if (!this.qrCodeUrl) {
|
||||
return
|
||||
}
|
||||
uni.previewImage({
|
||||
urls: [this.qrCodeUrl]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.deposit-other-payment-popup {
|
||||
background: #ffffff;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
padding: 18rpx 88rpx 20rpx;
|
||||
text-align: center;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(251, 253, 255, 0.96) 100%);
|
||||
}
|
||||
|
||||
.popup-handle {
|
||||
width: 88rpx;
|
||||
height: 8rpx;
|
||||
margin: 0 auto 18rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(148, 163, 184, 0.35);
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 1rpx;
|
||||
color: #1a2233;
|
||||
}
|
||||
|
||||
.popup-body {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
padding: 28rpx 28rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(180deg, #f7faff 0%, #f2f5fc 100%);
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
padding-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
padding: 28rpx;
|
||||
border-radius: 32rpx;
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(43, 142, 255, 0.16) 0, rgba(43, 142, 255, 0) 38%),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(246, 250, 255, 0.96) 100%);
|
||||
border: 1rpx solid rgba(43, 142, 255, 0.1);
|
||||
box-shadow: 0 24rpx 60rpx rgba(43, 142, 255, 0.1);
|
||||
}
|
||||
|
||||
.hero-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 10rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(43, 142, 255, 0.1);
|
||||
color: #2b6fd6;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
margin-top: 18rpx;
|
||||
font-size: 36rpx;
|
||||
line-height: 1.25;
|
||||
font-weight: 700;
|
||||
color: #183b73;
|
||||
}
|
||||
|
||||
.hero-desc {
|
||||
margin-top: 12rpx;
|
||||
font-size: 25rpx;
|
||||
line-height: 1.7;
|
||||
color: #607089;
|
||||
}
|
||||
|
||||
.loading-card,
|
||||
.contact-card,
|
||||
.qr-card,
|
||||
.rich-text-card,
|
||||
.empty-card {
|
||||
padding: 24rpx 28rpx;
|
||||
border-radius: 28rpx;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
border: 1rpx solid rgba(43, 142, 255, 0.08);
|
||||
box-shadow: inset 0 1rpx 0 rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.loading-chip {
|
||||
display: inline-flex;
|
||||
padding: 12rpx 20rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(43, 142, 255, 0.08);
|
||||
color: #4c6a95;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.contact-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.contact-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.contact-label,
|
||||
.section-title {
|
||||
font-size: 24rpx;
|
||||
color: #7c8ca5;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.contact-value {
|
||||
margin-top: 10rpx;
|
||||
font-size: 32rpx;
|
||||
line-height: 1.4;
|
||||
font-weight: 700;
|
||||
color: #22324d;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.contact-action {
|
||||
padding: 0 28rpx;
|
||||
height: 76rpx;
|
||||
border-radius: 999rpx;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #2b8eff 0%, #1f6fe5 100%);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 14rpx 28rpx rgba(43, 142, 255, 0.18);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.qr-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-image {
|
||||
width: 360rpx;
|
||||
max-width: 100%;
|
||||
margin: 18rpx auto 0;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 18rpx 40rpx rgba(15, 23, 42, 0.1);
|
||||
}
|
||||
|
||||
.qr-hint {
|
||||
margin-top: 16rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.6;
|
||||
color: #7c8ca5;
|
||||
}
|
||||
|
||||
.rich-text-content {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #22324d;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
color: #7c8ca5;
|
||||
}
|
||||
|
||||
.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, #2b8eff 0%, #1f6fe5 100%);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 14rpx 28rpx rgba(43, 142, 255, 0.22);
|
||||
}
|
||||
</style>
|
||||
@@ -2,8 +2,8 @@ export const config = {
|
||||
// 根据不同环境维护接口基准地址
|
||||
// baseURL: 'http://192.168.199.182:5003/api',
|
||||
// baseURL: 'http://192.168.199.124:5111/admin/api',
|
||||
baseURL: 'https://api.xhct.yaens.com/admin/api',
|
||||
// baseURL: 'https://apartment.lvjuwuye.cn/admin/api',
|
||||
// filePrefix: 'https://apartment.lvjuwuye.cn/p'
|
||||
filePrefix: 'https://api.xhct.yaens.com/p'
|
||||
// baseURL: 'https://api.xhct.yaens.com/admin/api',
|
||||
baseURL: 'https://apartment.lvjuwuye.cn/admin/api',
|
||||
filePrefix: 'https://apartment.lvjuwuye.cn/p'
|
||||
// filePrefix: 'https://api.xhct.yaens.com/p'
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ const messages = {
|
||||
"pkgService/contract_detail/contract_detail": "Contract Detail",
|
||||
"pkgService/contract_sign/contract_sign": "Contract Signing",
|
||||
"pkgService/device_control/device_control": "Device Control",
|
||||
"pkgService/lock_control/lock_control": "Smart Lock",
|
||||
"pkgService/message_detail/message_detail": "Message Detail",
|
||||
"pkgService/my_application/my_application": "My Applications",
|
||||
"pkgService/my_contract/my_contract": "My Contracts",
|
||||
@@ -120,6 +121,11 @@ const messages = {
|
||||
loadingBill: "Loading bill...",
|
||||
loadingPayment: "Opening payment...",
|
||||
depositPaymentDesc: "Deposit Payment",
|
||||
paymentStatusLabel: "Payment Status",
|
||||
paymentStatusPending: "Pending Payment",
|
||||
paymentStatusPaid: "Paid",
|
||||
paymentStatusRefunded: "Refunded",
|
||||
paymentStatusExempt: "No Payment Needed",
|
||||
paymentSuccess: "Payment succeeded",
|
||||
paymentCanceled: "Payment canceled",
|
||||
paymentFailed: "Payment failed",
|
||||
@@ -155,7 +161,7 @@ const messages = {
|
||||
statusProcessing: "Processing",
|
||||
statusRejected: "Rejected",
|
||||
statusReminderTitle: "Current Progress",
|
||||
statusWaitingRoomAssign: "Waiting for Room Assignment",
|
||||
statusWaitingRoomAssign: "Waiting for Room Arrangement",
|
||||
stayApplicationTitle: "Stay Application",
|
||||
submittedAtLabel: "Submitted At",
|
||||
toolbarSummary: "{total} applications",
|
||||
@@ -246,6 +252,8 @@ const messages = {
|
||||
},
|
||||
StayServiceTab: {
|
||||
activeStatus: "Active",
|
||||
applicationInfoTitle: "Stay Application Info",
|
||||
applicationCanceled: "Application canceled",
|
||||
addSmartDevice: "Add Smart Device",
|
||||
bedPrefix: "Bed {label}",
|
||||
billCount: "Bills",
|
||||
@@ -261,21 +269,34 @@ const messages = {
|
||||
butler: "Community Butler",
|
||||
callNow: "Call",
|
||||
checkinStep: "Check-in",
|
||||
checkinArrangement: "Check-in Arrangement",
|
||||
communityEvent: "Community Events",
|
||||
completeFormBeforeSubmit: "Please complete the form before submitting.",
|
||||
cancelApplication: "Cancel Application",
|
||||
cancelApplicationFailed: "Failed to cancel application",
|
||||
confirmPayAction: "Confirm and Pay",
|
||||
confirmPayInfo: "Confirm Payment Info",
|
||||
confirmWechatPayHint: "WeChat Pay will be launched after confirmation",
|
||||
contactPhonePrefix: "Phone ",
|
||||
contractPendingNote: "The flow stays at the contract step until the contract is signed.",
|
||||
contractSignedPendingReview: "Contract signed, waiting for manager review",
|
||||
contractSignedPendingReviewTitle: "Contract signed, waiting for manager review",
|
||||
contractRoom: "Contract Room",
|
||||
contractStep: "Contract",
|
||||
controllable: "Controllable",
|
||||
currentStageTitle: "Current Stage",
|
||||
currentStay: "Current Stay",
|
||||
deviceFallback: "Device {index}",
|
||||
goSelectRoom: "Explore Rooms",
|
||||
inspectionSheet: "Inspection Sheet",
|
||||
inspectionSubmittedPendingReview: "Inspection submitted, waiting for manager review",
|
||||
joinEvent: "Join Now",
|
||||
lastCreateTime: "Latest",
|
||||
leaseTerm: "Lease",
|
||||
loadingApplicationDetail: "Syncing application details, please wait...",
|
||||
loadingButlerInfo: "Syncing butler info...",
|
||||
loadingManagerInfo: "Loading butler info",
|
||||
loadingPaymentPreview: "Syncing payment preview, please wait...",
|
||||
loadingShort: "Syncing",
|
||||
managerContactSummary: "{count} butlers are available in this area",
|
||||
managementContacts: "Dorm Managers & Admins",
|
||||
@@ -297,16 +318,19 @@ const messages = {
|
||||
online: "Online · Response < 5 mins",
|
||||
onlineSingle: "1 online",
|
||||
paidOff: "Paid Off",
|
||||
pendingDispatch: "Pending Dispatch",
|
||||
payBill: "Pay Now",
|
||||
pendingAssignment: "Pending assignment",
|
||||
pendingContractFound: "Pending contract found",
|
||||
pendingSync: "Pending",
|
||||
payMethodLabel: "Payment Plan",
|
||||
primaryButlerTag: "Lead",
|
||||
publicService: "Premium Services",
|
||||
refreshFailed: "Refresh failed, please try again later",
|
||||
refreshStatus: "Refresh Status",
|
||||
refreshing: "Refreshing...",
|
||||
reviewStep: "Review",
|
||||
roomTypeInfo: "Room Info",
|
||||
applicationPendingTitle: "Application submitted, waiting for review",
|
||||
applicationPendingDesc: "Your application has been submitted. It will enter the room inspection step after butler review.",
|
||||
applicationPendingNote: "Application is under review. Please wait for butler notification.",
|
||||
@@ -319,12 +343,13 @@ const messages = {
|
||||
viewingNoDepositToast: "Viewing requests do not require a deposit",
|
||||
inspectionPendingTitle: "Please schedule and complete room inspection",
|
||||
inspectionPendingDesc: "The flow has entered the room inspection step. Please complete the inspection on time.",
|
||||
inspectionPendingNote: "After the room inspection is completed, the system will automatically proceed to bill payment.",
|
||||
inspectionPendingNote: "After the room inspection is completed, the system will automatically proceed to contract signing.",
|
||||
billPendingTitle: "Bill generated, please pay as soon as possible",
|
||||
billPendingDesc: "The flow has entered the bill payment step. Complete payment to proceed to contract signing.",
|
||||
billPendingDesc: "The flow has entered the bill payment step. Complete payment to proceed to check-in.",
|
||||
billPendingNote: "Please complete the bill payment within the specified time. Overdue payment may affect your room selection.",
|
||||
signContractDoneTitle: "Contract signed, waiting for check-in",
|
||||
signContractDoneNote: "Contract signing is complete. Please wait for butler notification for check-in.",
|
||||
signContractPendingReview: "Your contract has been signed and is currently waiting for manager review. After approval, the bill payment step will begin.",
|
||||
applicationNo: "Application No.",
|
||||
billNo: "Bill No.",
|
||||
applicationStep: "Application",
|
||||
@@ -339,10 +364,11 @@ const messages = {
|
||||
pendingCheckin: "Pending Check-in",
|
||||
roomDeviceUnconfigured: "Smart devices are not configured for this room",
|
||||
signContract: "Sign Contract",
|
||||
signContractDesc: "A contract is ready for signature. You are now at the contract signing step.",
|
||||
signContractDesc: "A contract is ready for signature. You are now at the contract signing step. Complete signing to proceed to bill payment.",
|
||||
signContractNow: "Please sign your contract",
|
||||
smartAvailableAfterCheckin: "Smart devices are available after check-in",
|
||||
smartControl: "Smart Control",
|
||||
smartLockEntryDesc: "My Keys · Door Password · Fingerprint Enrollment",
|
||||
smartDeviceConnected: "{count} smart devices connected",
|
||||
smartDeviceEmpty: "No smart devices in this room",
|
||||
smartDeviceLoadFailed: "Failed to load smart devices",
|
||||
@@ -354,11 +380,24 @@ const messages = {
|
||||
syncingSmartDevices: "Syncing smart devices...",
|
||||
title: "My Stay",
|
||||
unpaid: "Unpaid",
|
||||
unknownText: "Unknown",
|
||||
viewAll: "View All",
|
||||
viewBill: "View Bills",
|
||||
currentTaskTitle: "Current Task",
|
||||
flowProgressTitle: "Flow Progress",
|
||||
nextFlowFallback: "Next Step",
|
||||
nextStepDispatchPrefix: "Waiting for manager to send",
|
||||
nextStepDispatchSuffix: ", and the next step will continue after it is sent.",
|
||||
nextStepDispatchDescPrefix: "The current flow is waiting for the manager to send",
|
||||
paymentActionLabel: "Current Payment",
|
||||
goInspection: "Book Inspection",
|
||||
profileSignedDesc: "Your accommodation contract has been signed. The manager will arrange the check-in process for you soon.",
|
||||
profileInspectionPendingDesc: "Your inspection form has been submitted successfully and is currently waiting for manager review. After approval, the contract signing step will begin.",
|
||||
profileContractPendingDesc: "Your contract has been signed and is currently waiting for manager review. After approval, the bill payment step will begin.",
|
||||
profileDepositPendingDesc: "Your deposit has been paid successfully. The current application is waiting for manager review and follow-up handling.",
|
||||
processedDepositPendingTitle: "Deposit paid, waiting for manager handling",
|
||||
statusWaitingRoomAssign: "Waiting for Room Arrangement",
|
||||
waitingRoomAssignDesc: "There is currently no pending deposit bill. The butler is arranging a room for you, please wait patiently.",
|
||||
viewDetail: "View Details"
|
||||
}
|
||||
},
|
||||
@@ -369,7 +408,37 @@ const messages = {
|
||||
tabMyApplication: "Applications",
|
||||
tabProfileCenter: "Profile",
|
||||
tabSelectRoom: "Explore",
|
||||
tabStayService: "Stay"
|
||||
tabStayService: "Stay",
|
||||
myTodo: "My Tasks",
|
||||
noTodoNow: "No pending items right now",
|
||||
pendingCount: "{count} pending",
|
||||
currentPendingCount: "{count} items pending",
|
||||
noTodos: "No to-dos",
|
||||
viewTodo: "View",
|
||||
itemUnit: "items",
|
||||
pendingContract: "Pending Contracts",
|
||||
inspectionTodo: "Inspection",
|
||||
pendingTask: "Pending Task",
|
||||
todoType: "Pending Type",
|
||||
goHandle: "Handle",
|
||||
goPay: "Pay",
|
||||
goSign: "Sign",
|
||||
goInspect: "Inspect",
|
||||
unableHandle: "Unavailable",
|
||||
todoCodeMissing: "The current pending order number is incomplete",
|
||||
todoRouteMissing: "No route is configured for this task type yet",
|
||||
todoJumpFailed: "Failed to open the pending item",
|
||||
todoEmptyType: "No actionable pending details are available for the current type",
|
||||
todoActionDetail: "Tap the order number below to view pending details",
|
||||
todoActionTicket: "Tap the order number below to identify the work order and continue processing",
|
||||
todoActionBill: "Tap the order number below to open bill processing",
|
||||
todoActionContract: "Tap the order number below to open contract signing",
|
||||
todoActionInspection: "Tap the order number below to open inspection processing",
|
||||
todoUnknownTicketType: "Work order type could not be recognized",
|
||||
pendingContractTitle: "Pending Contract",
|
||||
inspectionDraft: "Draft",
|
||||
inspectionMoveIn: "Move-in Inspection",
|
||||
inspectionInProgress: "In Progress"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -437,17 +506,30 @@ const messages = {
|
||||
admissionNoticeAttachmentLabel: "Admission Notice (Employment Certificate)",
|
||||
accountLabel: "Account",
|
||||
accountPlaceholder: "Enter account name",
|
||||
accountShortTitle: "Account",
|
||||
accountSection: "Account Information",
|
||||
accountSectionDesc: "For sign-in and follow-up notifications",
|
||||
attachmentNotImage: "The current attachment is not an image",
|
||||
basicSectionDesc: "Show matching documents and fields by identity",
|
||||
basicSection: "Basic Verification",
|
||||
chinaText: "China",
|
||||
countryFixedText: "Fixed for Chinese users",
|
||||
countryLabel: "Country",
|
||||
countryLoadFailed: "Failed to load country list",
|
||||
countrySearchHint: "Fuzzy search supported",
|
||||
countrySearchPlaceholder: "Search country, region, or code",
|
||||
countrySearchSubtitle: "Search by Chinese, English, or country code",
|
||||
countrySearchTitle: "Select Country",
|
||||
countryPlaceholder: "Select country",
|
||||
dictLoadFailed: "Failed to load dictionaries",
|
||||
dietaryLabel: "Dietary Habit",
|
||||
dietaryPlaceholder: "Select dietary habit",
|
||||
documentAttachmentLabel: "Document Attachment",
|
||||
documentExpiryLabel: "Document Expiry Date",
|
||||
documentImageOnlyTip: "Document attachment only supports images, please re-upload",
|
||||
documentTypeLabel: "Document Type",
|
||||
documentLabel: "Document Number",
|
||||
documentPlaceholder: "Enter document number",
|
||||
domesticIdentity: "Chinese",
|
||||
draftTipDefault: "Unfinished content is auto-saved and can be continued later",
|
||||
draftTipSaved: "Current content has been auto-saved",
|
||||
@@ -456,23 +538,33 @@ const messages = {
|
||||
emailLabel: "Email",
|
||||
emailPlaceholder: "Enter email",
|
||||
foreignIdentity: "Foreign",
|
||||
finishAndSubmit: "Finish and Submit",
|
||||
genderLabel: "Gender",
|
||||
genderMale: "Male",
|
||||
genderFemale: "Female",
|
||||
goLogin: "Sign In",
|
||||
hangzhouPropertyLabel: "Property in Hangzhou",
|
||||
hasAccount: "Already have an account?",
|
||||
heroKicker: "Luju Apartment Registration",
|
||||
hobbyLabel: "Hobby",
|
||||
hobbyPlaceholder: "Select hobby",
|
||||
idCardBackTip: "Make sure the expiry date and national emblem are clear",
|
||||
birthdayLabel: "Date of Birth",
|
||||
birthdayPlaceholder: "Select date of birth",
|
||||
idCardBackAttachmentLabel: "ID Card Back",
|
||||
idCardFrontAttachmentLabel: "ID Card Front",
|
||||
idCardFrontTip: "Keep all edges visible with no obstructions",
|
||||
idCardLabel: "ID Card Number",
|
||||
idCardPlaceholder: "Enter ID card number",
|
||||
imageUploadedTip: "Image uploaded, tap to preview",
|
||||
identityShortTitle: "Verification",
|
||||
identityTypeLabel: "Identity Type",
|
||||
identityTypeTip: "Switching will also update country, document type, and upload requirements",
|
||||
loading: "Loading...",
|
||||
nameLabel: "Name",
|
||||
namePlaceholder: "Enter your real name",
|
||||
navTitle: "Register",
|
||||
noMatchedCountry: "No matching country or region found",
|
||||
noSelectableCountry: "No selectable country",
|
||||
noSelectableOptions: "No selectable options",
|
||||
noText: "No",
|
||||
@@ -485,10 +577,15 @@ const messages = {
|
||||
passportPlaceholder: "Enter passport number",
|
||||
passwordLabel: "Password",
|
||||
passwordPlaceholder: "Enter at least 6 characters",
|
||||
previousStep: "Previous",
|
||||
phoneLabel: "Phone",
|
||||
phonePlaceholder: "Enter phone number",
|
||||
preferenceSectionDesc: "Optional items to better match living preferences",
|
||||
preferenceSection: "Preferences",
|
||||
preferenceShortTitle: "Preferences",
|
||||
profileSectionDesc: "Upload documents and supporting materials for review",
|
||||
profileSection: "Profile",
|
||||
profileShortTitle: "Profile",
|
||||
religionLabel: "Religion",
|
||||
religionPlaceholder: "Select religion",
|
||||
restoreDraftCancel: "No",
|
||||
@@ -497,19 +594,28 @@ const messages = {
|
||||
restoreDraftSuccess: "Previous draft restored",
|
||||
restoreDraftTitle: "Unfinished Data Found",
|
||||
reupload: "Re-upload",
|
||||
reselect: "Choose Again",
|
||||
schoolLabel: "School",
|
||||
schoolPlaceholder: "Enter school (optional)",
|
||||
stepAccountDesc: "First complete the account, password, and email information.",
|
||||
stepIdentityDesc: "Fill in the basic profile such as name, identity, phone number, and country.",
|
||||
stepProfileDesc: "Add documents, attachments, and verification materials.",
|
||||
stepPreferenceDesc: "Finally add optional preferences and complete the submission.",
|
||||
selectDate: "Select date",
|
||||
nextStep: "Next",
|
||||
submitFailed: "Submission failed, please try again later",
|
||||
submitPending: "Application submitted, pending approval",
|
||||
submitRegister: "Submit Registration",
|
||||
submitting: "Submitting...",
|
||||
uploadFailed: "Upload failed, please retry",
|
||||
uploadAdmissionNotice: "Upload admission notice or employment certificate",
|
||||
uploadClearImageTip: "Please upload a clear image",
|
||||
uploadDocument: "Upload document image",
|
||||
uploadIdBack: "Upload ID card back",
|
||||
uploadIdFront: "Upload ID card front",
|
||||
uploadImageOption: "Upload Image",
|
||||
uploadPassport: "Upload passport image",
|
||||
uploadPassportTip: "Prefer a full, clear front-side image",
|
||||
uploadSuccess: "Upload successful",
|
||||
uploading: "Uploading...",
|
||||
validateEmail: "Please enter a valid email address",
|
||||
@@ -521,13 +627,19 @@ const messages = {
|
||||
validatePassportBasic: "Please enter passport number and expiry date",
|
||||
validatePasswordLength: "Password must be at least 6 characters",
|
||||
validatePhone: "Please enter a valid phone number",
|
||||
validateProfile: "Please complete work unit and property status in Hangzhou",
|
||||
validateProfile: "Please complete work unit/school and property status in Hangzhou",
|
||||
validateRequiredStepBasic: "Please complete the account, password, and email first",
|
||||
validateRequiredBasic: "Please complete the account, password, email, and name fields first",
|
||||
validateRequiredName: "Please enter your real name",
|
||||
validateSelectDocumentType: "Please select a document type",
|
||||
validateDocumentBasic: "Please enter document number and expiry date",
|
||||
validateDocumentAttachment: "Please upload the document image",
|
||||
validateDocumentAttachmentImage: "Document attachment only supports image files",
|
||||
viewImage: "Preview",
|
||||
viewRegistrationRecords: "View Registration Records",
|
||||
viewRegistrationRecordsDesc: "If this Wechat account has submitted before, you can review the history and approval status directly.",
|
||||
workUnitLabel: "Work Unit",
|
||||
workUnitPlaceholder: "Enter work unit",
|
||||
workUnitLabel: "Work Unit/School",
|
||||
workUnitPlaceholder: "Enter work unit/school",
|
||||
yesText: "Yes"
|
||||
}
|
||||
},
|
||||
@@ -619,8 +731,8 @@ const messages = {
|
||||
submitBooking: "Submit Request",
|
||||
submitFailed: "Failed to submit the booking",
|
||||
submitHint: "A dedicated manager will contact you shortly after submission",
|
||||
submitSuccessContent: "Your viewing request has been submitted.\nThe system will forward it to the authorized manager of the selected area. After the manager confirms the viewing, the order can be completed directly or converted into a booking for payment once a room is selected.",
|
||||
submitSuccessTitle: "Booking Submitted",
|
||||
submitSuccessContent: "Your viewing request has been submitted. Please wait for the manager to contact you.",
|
||||
submitSuccessTitle: "Viewing Request Submitted",
|
||||
submitting: "Submitting...",
|
||||
today: "Today",
|
||||
tomorrow: "Tomorrow",
|
||||
@@ -893,7 +1005,7 @@ const messages = {
|
||||
statusProcessing: "Processing",
|
||||
statusRejected: "Rejected",
|
||||
statusReminderTitle: "Current Progress",
|
||||
statusWaitingRoomAssign: "Waiting for Room Assignment",
|
||||
statusWaitingRoomAssign: "Waiting for Room Arrangement",
|
||||
stayApplicationTitle: "Stay Application",
|
||||
stayApplicationType: "Stay Application",
|
||||
submittedAtLabel: "Submitted At",
|
||||
@@ -1139,7 +1251,7 @@ const messages = {
|
||||
statusProcessing: "Processing",
|
||||
statusRejected: "Rejected",
|
||||
statusReminderTitle: "Current Progress",
|
||||
statusWaitingRoomAssign: "Waiting for Room Assignment",
|
||||
statusWaitingRoomAssign: "Waiting for Room Arrangement",
|
||||
stayApplicationTitle: "Stay Application",
|
||||
submittedAtLabel: "Submitted At",
|
||||
toolbarSummary: "{total} applications",
|
||||
@@ -1201,12 +1313,12 @@ const messages = {
|
||||
defaultPageTitle: "Service Center",
|
||||
goCheckIn: "Check In",
|
||||
lockEmptyRecord: "No door lock records",
|
||||
lockNoAccessDesc: "No smart lock is assigned yet, or the lock is currently offline.\nAfter check-in, one-tap unlock and temporary password features will be available.",
|
||||
lockNoAccessDesc: "No smart lock is assigned yet, or the lock is currently offline.\nAfter check-in, door passwords, fingerprint enrollment, and temporary password delivery will be available.",
|
||||
lockNoAccessTitle: "No door lock access",
|
||||
noAccessPermission: "No access permission",
|
||||
noteDesc: "After the real capability is connected, this page will display the formal service entry, status data, and processing records.",
|
||||
noteTitle: "Notes",
|
||||
oneTapUnlock: "Unlock Now",
|
||||
oneTapUnlock: "Add Fingerprint",
|
||||
pointsAvailable: "Available Points",
|
||||
pointsEmpty: "No points record",
|
||||
pointsMall: "Points Mall",
|
||||
@@ -1219,6 +1331,51 @@ const messages = {
|
||||
visitorEmpty: "No visitor access records"
|
||||
}
|
||||
},
|
||||
lock_control: {
|
||||
lock_control: {
|
||||
addFingerprint: "Add Fingerprint",
|
||||
availableLocks: "Available Locks",
|
||||
connectingLock: "Connecting to the lock...",
|
||||
currentLock: "Current Lock",
|
||||
currentRoomLock: "Room Lock",
|
||||
currentSelectionHint: "{count} available locks found for this room. Currently selected: \"{name}\".",
|
||||
currentWillOperateRoomLock: "The current room lock will be used.",
|
||||
currentWillOperateSuiteLock: "The selected suite / common area lock will be used.",
|
||||
doorLock: "Smart Lock",
|
||||
doorPassword: "Door Password",
|
||||
fingerAdded: "Fingerprint Added",
|
||||
fingerModeFailed: "Failed to enable fingerprint mode",
|
||||
fingerStatus: "Fingerprint Status",
|
||||
fingerprintGuideDesc: "Follow the lock prompt, place your finger naturally on the sensor, and press repeatedly until enrollment is complete.",
|
||||
fingerprintGuideTip: "The system will refresh the enrollment result automatically",
|
||||
fingerprintGuideTitle: "Press Your Finger Repeatedly",
|
||||
fingerprintInstruction: "To enroll a fingerprint, tap \"Add Fingerprint\" and follow the lock prompts.",
|
||||
keySyncing: "Syncing your lock credentials...",
|
||||
keySyncFailed: "Failed to sync lock credentials",
|
||||
loadingEnroll: "Preparing Enrollment",
|
||||
lockCapabilityAfterCheckin: "Door lock features are available after check-in.",
|
||||
lockKeys: "My Keys",
|
||||
missingRoomCode: "Your current stay order is missing a room code. Please contact your butler.",
|
||||
missingUserCodeFinger: "Student/staff ID is missing, so fingerprint enrollment is unavailable.",
|
||||
missingUserCodeKeys: "Your account is missing a student/staff ID, so lock credentials cannot be synced yet.",
|
||||
noAvailableLocks: "No available locks found for the current room.",
|
||||
noneAssigned: "Not Assigned",
|
||||
notRecorded: "Not Enrolled",
|
||||
noCopyPassword: "No password available to copy",
|
||||
passwordCopied: "Password copied",
|
||||
preparingFingerDesc: "The command has been sent to the lock. Please wait a moment.",
|
||||
recorded: "Enrolled",
|
||||
roomLock: "Room Lock",
|
||||
roomOrPublicArea: "Suite / Common Area",
|
||||
roomOrPublicAreaLock: "Suite / Common Area Lock",
|
||||
selectedCount: "{count} lock(s)",
|
||||
sendingFinger: "Sending...",
|
||||
syncCurrentRoomLocks: "Syncing available locks for the current room.",
|
||||
tipsTitle: "Instructions",
|
||||
unlockWaitHint: "If the lock is still waiting, keep pressing and tap refresh to check again.",
|
||||
useRoomPasswordFinger: "My Keys · Door Password · Fingerprint Enrollment"
|
||||
}
|
||||
},
|
||||
smart_control: {
|
||||
smart_control: {
|
||||
commandFailed: "Failed to send command",
|
||||
@@ -1373,6 +1530,14 @@ const messages = {
|
||||
sourceDefaultDesc: "Fill in your stay intention, lease period, and notes.",
|
||||
stayApplyDefaultDesc: "Fill in your stay intention, preferences, and lease period before submission.",
|
||||
stayApplyDefaultTitle: "Stay Application",
|
||||
agreeAndSubmit: "Agree and Submit",
|
||||
confirmSubmitTitle: "Confirm before submit",
|
||||
confirmSubmitContent: "Confirm submitting the current application?",
|
||||
confirmSubmitAction: "Confirm submit",
|
||||
confirmSubmitCancel: "Think again",
|
||||
noDepositReminderTitle: "Application Submitted",
|
||||
noDepositReminderContent: "There is currently no pending deposit bill for this application. The butler will arrange a room for you, please wait patiently.",
|
||||
noDepositReminderConfirm: "Got It",
|
||||
submitFailed: "Failed to submit application",
|
||||
submitRoomTypeApply: "Submit Application",
|
||||
submitSuccess: "Application submitted",
|
||||
@@ -1472,9 +1637,17 @@ const messages = {
|
||||
},
|
||||
room_inspection: {
|
||||
room_inspection: {
|
||||
confirmSubmitDesc: "Add handover notes, issue background, or any extra information before submitting.",
|
||||
confirmSubmitSkip: "Skip for now",
|
||||
confirmSubmitTitle: "Confirm before submit",
|
||||
confirmSubmitAction: "Confirm submit",
|
||||
handoverInspection: "Handover Inspection",
|
||||
inspectionHint: "Please check each item. For abnormal cases, add details and upload photos.",
|
||||
pageTitle: "Inspection",
|
||||
pageDesc: "Review and submit inspection results for the current order",
|
||||
inspectionListTitle: "Inspection Records",
|
||||
inspectionSwitcher: "Switch Inspection",
|
||||
inspectionRecordUnit: "records",
|
||||
roomNoLabel: "Room No.",
|
||||
inspectionTypeLabel: "Inspection Type",
|
||||
inspectionStatusLabel: "Status",
|
||||
@@ -1483,15 +1656,23 @@ const messages = {
|
||||
inspectionRemarksPlaceholder: "Add notes for this inspection",
|
||||
inspectionItemsTitle: "Inspection Items",
|
||||
inspectionItemsDesc: "Check each item according to the on-site condition",
|
||||
inspectionItemUnit: "items",
|
||||
deviceFallback: "Unnamed Device",
|
||||
markAllNormal: "Mark All Normal",
|
||||
normalResultOptionMissing: "No normal result option found",
|
||||
allMarkedNormal: "All items marked as normal",
|
||||
requiredTag: "Required",
|
||||
expectedQuantityLabel: "Expected Qty",
|
||||
inspectionResultLabel: "Result",
|
||||
checkedQuantityLabel: "Checked Qty",
|
||||
checkedQuantityPlaceholder: "Enter quantity",
|
||||
issueDetailLabel: "Issue Details",
|
||||
problemDetailLabel: "Problem Detail",
|
||||
problemDetailHint: "Add details if anything is missing, damaged, or unusable",
|
||||
problemDetailPlaceholder: "Describe any abnormal, missing, or damaged condition",
|
||||
photosLabel: "Photos",
|
||||
photosHint: "Upload photos of key areas for follow-up confirmation",
|
||||
uploadPhoto: "Upload Photo",
|
||||
addPhoto: "Add",
|
||||
submitAction: "Submit",
|
||||
loadingDesc: "Syncing inspection data...",
|
||||
@@ -1559,6 +1740,7 @@ const messages = {
|
||||
"pkgService/contract_detail/contract_detail": "合同详情",
|
||||
"pkgService/contract_sign/contract_sign": "合同签约",
|
||||
"pkgService/device_control/device_control": "设备控制",
|
||||
"pkgService/lock_control/lock_control": "门锁",
|
||||
"pkgService/message_detail/message_detail": "消息详情",
|
||||
"pkgService/my_application/my_application": "我的申请",
|
||||
"pkgService/my_contract/my_contract": "我的合同",
|
||||
@@ -1657,6 +1839,11 @@ const messages = {
|
||||
loadingBill: "正在获取账单...",
|
||||
loadingPayment: "正在拉起支付...",
|
||||
depositPaymentDesc: "支付定金",
|
||||
paymentStatusLabel: "支付状态",
|
||||
paymentStatusPending: "待支付",
|
||||
paymentStatusPaid: "已支付",
|
||||
paymentStatusRefunded: "已退款",
|
||||
paymentStatusExempt: "无需支付",
|
||||
paymentSuccess: "支付成功",
|
||||
paymentCanceled: "已取消支付",
|
||||
paymentFailed: "支付失败",
|
||||
@@ -1692,7 +1879,7 @@ const messages = {
|
||||
statusProcessing: "处理中",
|
||||
statusRejected: "已拒绝",
|
||||
statusReminderTitle: "当前进度提醒",
|
||||
statusWaitingRoomAssign: "等待预订中心分配房间",
|
||||
statusWaitingRoomAssign: "等待管家排房",
|
||||
stayApplicationTitle: "入住申请",
|
||||
submittedAtLabel: "提交时间",
|
||||
toolbarSummary: "共 {total} 条申请",
|
||||
@@ -1783,6 +1970,8 @@ const messages = {
|
||||
},
|
||||
StayServiceTab: {
|
||||
activeStatus: "使用中",
|
||||
applicationInfoTitle: "入住申请信息",
|
||||
applicationCanceled: "申请已取消",
|
||||
addSmartDevice: "添加智能设备",
|
||||
bedPrefix: "床位 {label}",
|
||||
billCount: "账单数量",
|
||||
@@ -1798,21 +1987,34 @@ const messages = {
|
||||
butler: "专属社区管家",
|
||||
callNow: "拨打",
|
||||
checkinStep: "办理入住",
|
||||
checkinArrangement: "入住安排",
|
||||
communityEvent: "近期活动",
|
||||
completeFormBeforeSubmit: "请完善表单信息后提交。",
|
||||
cancelApplication: "取消申请",
|
||||
cancelApplicationFailed: "取消申请失败",
|
||||
confirmPayAction: "确认并支付",
|
||||
confirmPayInfo: "确认支付信息",
|
||||
confirmWechatPayHint: "确认后将发起微信支付",
|
||||
contactPhonePrefix: "联系电话 ",
|
||||
contractPendingNote: "检测到待签合同后,流程将停留在签订合同节点,请先完成签名。",
|
||||
contractSignedPendingReview: "合同已签,等待管家审核",
|
||||
contractSignedPendingReviewTitle: "合同已签署,等待管家审核",
|
||||
contractRoom: "合同房型",
|
||||
contractStep: "签订合同",
|
||||
controllable: "可控制",
|
||||
currentStageTitle: "当前阶段",
|
||||
currentStay: "当前入住房间",
|
||||
deviceFallback: "设备 {index}",
|
||||
goSelectRoom: "去选房",
|
||||
inspectionSheet: "验房单",
|
||||
inspectionSubmittedPendingReview: "验房已提交,等待管家审核",
|
||||
joinEvent: "立即报名",
|
||||
lastCreateTime: "最近生成",
|
||||
leaseTerm: "租期",
|
||||
loadingApplicationDetail: "正在同步申请详情,请稍候...",
|
||||
loadingButlerInfo: "正在同步管家信息...",
|
||||
loadingManagerInfo: "正在加载管家信息",
|
||||
loadingPaymentPreview: "正在同步支付预览,请稍候...",
|
||||
loadingShort: "同步中",
|
||||
managerContactSummary: "当前区域共有 {count} 位管家可联系",
|
||||
managementContacts: "宿管与管理员",
|
||||
@@ -1834,16 +2036,19 @@ const messages = {
|
||||
online: "当前在线 · 响应时间 < 5分钟",
|
||||
onlineSingle: "1人在线",
|
||||
paidOff: "已结清",
|
||||
pendingDispatch: "待下发",
|
||||
payBill: "去支付",
|
||||
pendingAssignment: "待审核分配",
|
||||
pendingContractFound: "发现待签合同",
|
||||
pendingSync: "待同步",
|
||||
payMethodLabel: "押付方式",
|
||||
primaryButlerTag: "主责",
|
||||
publicService: "公寓精选服务",
|
||||
refreshFailed: "刷新失败,请稍后重试",
|
||||
refreshStatus: "刷新状态",
|
||||
refreshing: "刷新中...",
|
||||
reviewStep: "管家审核",
|
||||
roomTypeInfo: "房型信息",
|
||||
applicationPendingTitle: "申请单已提交,等待审核",
|
||||
applicationPendingDesc: "您的申请单已提交,管家审核通过后将进入验房环节。",
|
||||
applicationPendingNote: "申请单审核中,请耐心等待管家通知。",
|
||||
@@ -1856,12 +2061,13 @@ const messages = {
|
||||
viewingNoDepositToast: "预约看房申请无需支付定金",
|
||||
inspectionPendingTitle: "请预约并完成验房",
|
||||
inspectionPendingDesc: "当前流程已进入验房阶段,请按预约时间完成房间验收。",
|
||||
inspectionPendingNote: "验房完成后,系统将自动进入账单支付环节。",
|
||||
inspectionPendingNote: "验房完成后,系统将自动进入合同签署环节。",
|
||||
billPendingTitle: "账单已生成,请尽快支付",
|
||||
billPendingDesc: "当前流程已进入账单支付阶段,完成支付后即可进入合同签署环节。",
|
||||
billPendingDesc: "当前流程已进入账单支付阶段,完成支付后即可进入入住办理环节。",
|
||||
billPendingNote: "请在规定时间内完成账单支付,逾期可能影响选房资格。",
|
||||
signContractDoneTitle: "合同已签署,等待办理入住",
|
||||
signContractDoneNote: "合同签署完成,请等待管家通知办理入住。",
|
||||
signContractPendingReview: "您的合同已经签署完成,当前正在等待管家审核,审核通过后将进入账单支付环节。",
|
||||
applicationNo: "申请编号",
|
||||
billNo: "账单编号",
|
||||
applicationStep: "申请单",
|
||||
@@ -1876,10 +2082,11 @@ const messages = {
|
||||
pendingCheckin: "待入住",
|
||||
roomDeviceUnconfigured: "房间智能设备未配置",
|
||||
signContract: "去签约",
|
||||
signContractDesc: "系统已生成待签合同,当前流程已进入签订合同阶段,完成签约后即可继续后续入住流程。",
|
||||
signContractDesc: "系统已生成待签合同,当前流程已进入签订合同阶段,完成签约后即可进入账单支付环节。",
|
||||
signContractNow: "请尽快签订合同",
|
||||
smartAvailableAfterCheckin: "入住后可使用智能设备",
|
||||
smartControl: "全屋智能",
|
||||
smartLockEntryDesc: "我的钥匙 · 门锁密码 · 录入指纹",
|
||||
smartDeviceConnected: "已接入 {count} 台智能设备",
|
||||
smartDeviceEmpty: "当前房间暂无智能设备",
|
||||
smartDeviceLoadFailed: "智能设备加载失败",
|
||||
@@ -1891,12 +2098,25 @@ const messages = {
|
||||
syncingSmartDevices: "正在同步智能设备...",
|
||||
title: "我的入住",
|
||||
unpaid: "待支付",
|
||||
unknownText: "未知",
|
||||
viewAll: "全部",
|
||||
viewBill: "查看账单",
|
||||
currentTaskTitle: "当前任务",
|
||||
currentStepTag: "当前",
|
||||
flowProgressTitle: "流程进度",
|
||||
nextFlowFallback: "下一步流程",
|
||||
nextStepDispatchPrefix: "等待管家发出",
|
||||
nextStepDispatchSuffix: ",下发后即可继续下一步操作。",
|
||||
nextStepDispatchDescPrefix: "当前流程正在等待管家发出",
|
||||
paymentActionLabel: "本次支付",
|
||||
goInspection: "去验房",
|
||||
profileSignedDesc: "您的住宿合同已经完成签署,管家会尽快为您安排办理入住流程。",
|
||||
profileInspectionPendingDesc: "您的验房单已提交成功,当前正在等待管家审核,审核通过后将进入合同签署环节。",
|
||||
profileContractPendingDesc: "您的合同已经签署完成,当前正在等待管家审核,审核通过后将进入账单支付环节。",
|
||||
profileDepositPendingDesc: "您的定金已支付成功,当前申请正在等待管家审核和后续处理。",
|
||||
processedDepositPendingTitle: "定金已支付,等待管家处理",
|
||||
statusWaitingRoomAssign: "等待管家排房",
|
||||
waitingRoomAssignDesc: "当前暂无待支付定金账单,管家正在为您排房,请耐心等待。",
|
||||
viewDetail: "查看详情"
|
||||
}
|
||||
},
|
||||
@@ -1907,7 +2127,37 @@ const messages = {
|
||||
tabMyApplication: "申请单",
|
||||
tabProfileCenter: "我 的",
|
||||
tabSelectRoom: "选 房",
|
||||
tabStayService: "入 住"
|
||||
tabStayService: "入 住",
|
||||
myTodo: "我的待办",
|
||||
noTodoNow: "当前暂无待办事项",
|
||||
pendingCount: "待处理 {count} 项",
|
||||
currentPendingCount: "当前共 {count} 项待处理",
|
||||
noTodos: "暂无待办",
|
||||
viewTodo: "查看",
|
||||
itemUnit: "项",
|
||||
pendingContract: "待签合同",
|
||||
inspectionTodo: "验房单",
|
||||
pendingTask: "待办事项",
|
||||
todoType: "待办类型",
|
||||
goHandle: "去处理",
|
||||
goPay: "去支付",
|
||||
goSign: "去签署",
|
||||
goInspect: "去验房",
|
||||
unableHandle: "无法处理",
|
||||
todoCodeMissing: "当前待办单号不完整",
|
||||
todoRouteMissing: "当前类型暂未配置跳转入口",
|
||||
todoJumpFailed: "待办跳转失败",
|
||||
todoEmptyType: "当前类型暂无可处理的待办明细",
|
||||
todoActionDetail: "点击下方单号查看待办详情",
|
||||
todoActionTicket: "点击下方单号识别工单并进入处理",
|
||||
todoActionBill: "点击下方单号进入账单处理",
|
||||
todoActionContract: "点击下方单号进入协议签署",
|
||||
todoActionInspection: "点击下方单号进入验房处理",
|
||||
todoUnknownTicketType: "未识别工单类型",
|
||||
pendingContractTitle: "待签合同",
|
||||
inspectionDraft: "草稿",
|
||||
inspectionMoveIn: "入住检查",
|
||||
inspectionInProgress: "进行中"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1975,17 +2225,30 @@ const messages = {
|
||||
admissionNoticeAttachmentLabel: "入学通知书(在职证明)附件",
|
||||
accountLabel: "登录账号",
|
||||
accountPlaceholder: "请输入登录账号",
|
||||
accountShortTitle: "账号",
|
||||
accountSection: "账号信息",
|
||||
accountSectionDesc: "用于登录和接收后续通知",
|
||||
attachmentNotImage: "当前附件不是图片",
|
||||
basicSectionDesc: "根据身份展示对应的证件和字段",
|
||||
basicSection: "基础实名信息",
|
||||
chinaText: "中国",
|
||||
countryFixedText: "中国籍固定",
|
||||
countryLabel: "国籍",
|
||||
countryLoadFailed: "国家列表加载失败",
|
||||
countrySearchHint: "支持模糊搜索",
|
||||
countrySearchPlaceholder: "搜索国家、地区或代码",
|
||||
countrySearchSubtitle: "可按中文、英文或国家代码搜索",
|
||||
countrySearchTitle: "选择国籍",
|
||||
countryPlaceholder: "请选择国籍",
|
||||
dictLoadFailed: "字典加载失败,稍后可重试",
|
||||
dietaryLabel: "饮食习惯",
|
||||
dietaryPlaceholder: "请选择饮食习惯",
|
||||
documentAttachmentLabel: "证件附件",
|
||||
documentExpiryLabel: "证件到期时间",
|
||||
documentImageOnlyTip: "证件附件仅支持图片,请重新上传",
|
||||
documentTypeLabel: "证件类型",
|
||||
documentLabel: "证件号",
|
||||
documentPlaceholder: "请输入证件号",
|
||||
domesticIdentity: "中国籍",
|
||||
draftTipDefault: "未完成内容会自动保存,可随时返回继续填写",
|
||||
draftTipSaved: "当前填写内容已自动保存",
|
||||
@@ -1994,23 +2257,33 @@ const messages = {
|
||||
emailLabel: "邮箱",
|
||||
emailPlaceholder: "请输入邮箱",
|
||||
foreignIdentity: "外籍人员",
|
||||
finishAndSubmit: "完成并提交",
|
||||
genderLabel: "性别",
|
||||
genderMale: "男",
|
||||
genderFemale: "女",
|
||||
goLogin: "去登录",
|
||||
hangzhouPropertyLabel: "杭州有无房产",
|
||||
hasAccount: "已有账号?",
|
||||
heroKicker: "绿居公寓登记入口",
|
||||
hobbyLabel: "兴趣爱好",
|
||||
hobbyPlaceholder: "请选择兴趣爱好",
|
||||
idCardBackTip: "请确保有效期和国徽信息清晰",
|
||||
birthdayLabel: "生日",
|
||||
birthdayPlaceholder: "请选择生日",
|
||||
idCardBackAttachmentLabel: "身份证反面附件",
|
||||
idCardFrontAttachmentLabel: "身份证正面附件",
|
||||
idCardFrontTip: "请保持边缘完整、无遮挡",
|
||||
idCardLabel: "身份证号",
|
||||
idCardPlaceholder: "请输入身份证号",
|
||||
imageUploadedTip: "已上传图片,可点击查看大图",
|
||||
identityShortTitle: "实名",
|
||||
identityTypeLabel: "身份类型",
|
||||
identityTypeTip: "切换后会同步调整国籍、证件类型和上传要求",
|
||||
loading: "加载中...",
|
||||
nameLabel: "姓名",
|
||||
namePlaceholder: "请输入真实姓名",
|
||||
navTitle: "注册账号",
|
||||
noMatchedCountry: "没有找到匹配的国家或地区",
|
||||
noSelectableCountry: "当前暂无可选国家",
|
||||
noSelectableOptions: "当前暂无可选项",
|
||||
noText: "无",
|
||||
@@ -2023,10 +2296,15 @@ const messages = {
|
||||
passportPlaceholder: "请输入护照号",
|
||||
passwordLabel: "登录密码",
|
||||
passwordPlaceholder: "请输入 6 位及以上密码",
|
||||
previousStep: "上一步",
|
||||
phoneLabel: "手机号",
|
||||
phonePlaceholder: "请输入手机号",
|
||||
preferenceSectionDesc: "选填项,用于更好地匹配入住生活偏好",
|
||||
preferenceSection: "偏好信息",
|
||||
preferenceShortTitle: "偏好",
|
||||
profileSectionDesc: "上传证件和补充资料,便于审核",
|
||||
profileSection: "认证档案",
|
||||
profileShortTitle: "档案",
|
||||
religionLabel: "宗教信仰",
|
||||
religionPlaceholder: "请选择宗教信仰",
|
||||
restoreDraftCancel: "不用",
|
||||
@@ -2035,19 +2313,28 @@ const messages = {
|
||||
restoreDraftSuccess: "已填入上次失败数据",
|
||||
restoreDraftTitle: "发现未完成的数据",
|
||||
reupload: "重新上传",
|
||||
reselect: "重新选择",
|
||||
schoolLabel: "就读学校",
|
||||
schoolPlaceholder: "请输入就读学校(选填)",
|
||||
stepAccountDesc: "先完成登录账号、密码和邮箱信息。",
|
||||
stepIdentityDesc: "填写姓名、身份、手机号和国籍等基础资料。",
|
||||
stepProfileDesc: "补充证件、附件和审核所需资料。",
|
||||
stepPreferenceDesc: "最后补充选填偏好信息,然后完成提交。",
|
||||
selectDate: "请选择日期",
|
||||
nextStep: "下一步",
|
||||
submitFailed: "提交失败,请稍后重试",
|
||||
submitPending: "申请已提交,待审批",
|
||||
submitRegister: "提交注册申请",
|
||||
submitting: "提交中...",
|
||||
uploadFailed: "上传失败,请重试",
|
||||
uploadAdmissionNotice: "点击上传入学通知书/在职证明",
|
||||
uploadClearImageTip: "建议上传清晰的图片版本",
|
||||
uploadDocument: "点击上传证件图片",
|
||||
uploadIdBack: "点击上传身份证反面",
|
||||
uploadIdFront: "点击上传身份证正面",
|
||||
uploadImageOption: "上传图片",
|
||||
uploadPassport: "点击上传护照图片",
|
||||
uploadPassportTip: "优先上传正面完整清晰的图片",
|
||||
uploadSuccess: "上传成功",
|
||||
uploading: "上传中...",
|
||||
validateEmail: "请输入正确的邮箱地址",
|
||||
@@ -2059,13 +2346,19 @@ const messages = {
|
||||
validatePassportBasic: "请填写护照号和护照到期时间",
|
||||
validatePasswordLength: "登录密码至少 6 位",
|
||||
validatePhone: "请输入正确的手机号",
|
||||
validateProfile: "请完善工作单位和杭州房产情况",
|
||||
validateProfile: "请完善工作单位/就读学校和杭州房产情况",
|
||||
validateRequiredStepBasic: "请先填写账号、密码和邮箱",
|
||||
validateRequiredBasic: "请先填写账号、密码、邮箱和姓名",
|
||||
validateRequiredName: "请填写真实姓名",
|
||||
validateSelectDocumentType: "请选择证件类型",
|
||||
validateDocumentBasic: "请填写证件号和证件到期时间",
|
||||
validateDocumentAttachment: "请上传证件图片",
|
||||
validateDocumentAttachmentImage: "证件附件仅支持图片格式",
|
||||
viewImage: "查看图片",
|
||||
viewRegistrationRecords: "查看注册记录",
|
||||
viewRegistrationRecordsDesc: "如果当前微信账号已提交过申请,可直接查看历史记录和审批状态",
|
||||
workUnitLabel: "工作单位",
|
||||
workUnitPlaceholder: "请输入工作单位",
|
||||
workUnitLabel: "工作单位/就读学校",
|
||||
workUnitPlaceholder: "请输入工作单位/就读学校",
|
||||
yesText: "有"
|
||||
}
|
||||
},
|
||||
@@ -2419,6 +2712,18 @@ const messages = {
|
||||
checkinPendingHint: "宿管正在办理入住,请耐心等待",
|
||||
refreshAction: "刷新",
|
||||
navTitle: "申请详情",
|
||||
paymentStatusLabel: "Payment Status",
|
||||
paymentStatusPending: "Pending Payment",
|
||||
paymentStatusPaid: "Paid",
|
||||
paymentStatusRefunded: "Refunded",
|
||||
paymentStatusExempt: "No Payment Needed",
|
||||
payDepositSoon: "Please complete the deposit payment soon",
|
||||
paymentMethodKey: "支付方式",
|
||||
preferredAreaKey: "意向区域",
|
||||
preferredRoomTypeKey: "意向房型",
|
||||
paymentStatusRefunded: "已退款",
|
||||
paymentStatusExempt: "无需支付",
|
||||
payDepositSoon: "请尽快完成定金支付",
|
||||
paymentMethodKey: "支付方式",
|
||||
preferredAreaKey: "意向区域",
|
||||
preferredRoomTypeKey: "意向房型",
|
||||
@@ -2431,7 +2736,7 @@ const messages = {
|
||||
statusProcessing: "处理中",
|
||||
statusRejected: "已拒绝",
|
||||
statusReminderTitle: "当前进度提醒",
|
||||
statusWaitingRoomAssign: "等待预订中心分配房间",
|
||||
statusWaitingRoomAssign: "等待管家排房",
|
||||
stayApplicationTitle: "入住申请",
|
||||
stayApplicationType: "入住申请",
|
||||
submittedAtLabel: "提交时间",
|
||||
@@ -2677,7 +2982,7 @@ const messages = {
|
||||
statusProcessing: "处理中",
|
||||
statusRejected: "已拒绝",
|
||||
statusReminderTitle: "当前进度提醒",
|
||||
statusWaitingRoomAssign: "等待预订中心分配房间",
|
||||
statusWaitingRoomAssign: "等待管家排房",
|
||||
stayApplicationTitle: "入住申请",
|
||||
submittedAtLabel: "提交时间",
|
||||
toolbarSummary: "共 {total} 条申请",
|
||||
@@ -2739,12 +3044,12 @@ const messages = {
|
||||
defaultPageTitle: "业务办理",
|
||||
goCheckIn: "去签到",
|
||||
lockEmptyRecord: "暂无门锁操作记录",
|
||||
lockNoAccessDesc: "您当前暂未分配智能门锁,或门锁暂未联网。\n入住后即可使用一键开门与临时密码下发功能。",
|
||||
lockNoAccessDesc: "您当前暂未分配智能门锁,或门锁暂未联网。\n入住后即可使用门锁密码、指纹录入与临时密码下发功能。",
|
||||
lockNoAccessTitle: "暂无门锁权限",
|
||||
noAccessPermission: "未获取通行权限",
|
||||
noteDesc: "后续接入真实能力后,这里会展示正式的业务入口、状态数据和办理记录。",
|
||||
noteTitle: "说明",
|
||||
oneTapUnlock: "一键开锁",
|
||||
oneTapUnlock: "录入指纹",
|
||||
pointsAvailable: "当前可用积分",
|
||||
pointsEmpty: "暂无积分明细",
|
||||
pointsMall: "积分商城",
|
||||
@@ -2757,6 +3062,51 @@ const messages = {
|
||||
visitorEmpty: "暂无访客通行记录"
|
||||
}
|
||||
},
|
||||
lock_control: {
|
||||
lock_control: {
|
||||
addFingerprint: "录入指纹",
|
||||
availableLocks: "可开门锁",
|
||||
connectingLock: "正在连接门锁...",
|
||||
currentLock: "当前门锁",
|
||||
currentRoomLock: "当前房间门锁",
|
||||
currentSelectionHint: "当前房间下共 {count} 个可操作门锁,当前已选“{name}”。",
|
||||
currentWillOperateRoomLock: "当前将操作当前房间门锁。",
|
||||
currentWillOperateSuiteLock: "当前将操作所选套间或公区门锁。",
|
||||
doorLock: "门锁",
|
||||
doorPassword: "门锁密码",
|
||||
fingerAdded: "指纹已录入",
|
||||
fingerModeFailed: "指纹模式下发失败",
|
||||
fingerStatus: "指纹状态",
|
||||
fingerprintGuideDesc: "请根据门锁提示,将手指自然贴合采集区并重复按压,直到门锁提示录入完成。",
|
||||
fingerprintGuideTip: "系统会自动刷新录入结果",
|
||||
fingerprintGuideTitle: "请重复按压指纹",
|
||||
fingerprintInstruction: "如需录入指纹,请先点击“录入指纹”,再按门锁提示完成采集。",
|
||||
keySyncing: "正在同步门锁钥匙...",
|
||||
keySyncFailed: "钥匙同步失败",
|
||||
loadingEnroll: "正在准备录入",
|
||||
lockCapabilityAfterCheckin: "完成入住后即可使用门锁能力。",
|
||||
lockKeys: "我的钥匙",
|
||||
missingRoomCode: "当前入住订单缺少房间编码,请联系管家配置。",
|
||||
missingUserCodeFinger: "缺少学工号,暂无法录入指纹",
|
||||
missingUserCodeKeys: "当前账号缺少学工号,暂无法同步门锁钥匙。",
|
||||
noAvailableLocks: "当前房间暂未查询到可用门锁。",
|
||||
noneAssigned: "暂未分配",
|
||||
notRecorded: "未录入",
|
||||
noCopyPassword: "暂无可复制密码",
|
||||
passwordCopied: "密码已复制",
|
||||
preparingFingerDesc: "已向门锁下发指令,请稍候片刻。",
|
||||
recorded: "已录入",
|
||||
roomLock: "房间门锁",
|
||||
roomOrPublicArea: "套间 / 公区",
|
||||
roomOrPublicAreaLock: "套间 / 公区门锁",
|
||||
selectedCount: "共 {count} 个",
|
||||
sendingFinger: "下发中...",
|
||||
syncCurrentRoomLocks: "正在同步当前房间下可操作的门锁。",
|
||||
tipsTitle: "使用说明",
|
||||
unlockWaitHint: "如门锁仍在等待,请继续按压后点刷新查看",
|
||||
useRoomPasswordFinger: "我的钥匙 · 门锁密码 · 录入指纹"
|
||||
}
|
||||
},
|
||||
smart_control: {
|
||||
smart_control: {
|
||||
commandFailed: "指令下发失败",
|
||||
@@ -2911,6 +3261,14 @@ const messages = {
|
||||
sourceDefaultDesc: "请填写您的入住意向、租期和补充说明。",
|
||||
stayApplyDefaultDesc: "请填写您的入住意向、生活偏好与预计租期后提交申请单。",
|
||||
stayApplyDefaultTitle: "入住申请",
|
||||
agreeAndSubmit: "同意并提交",
|
||||
confirmSubmitTitle: "提交前确认",
|
||||
confirmSubmitContent: "确认提交当前申请单吗?",
|
||||
confirmSubmitAction: "确认提交",
|
||||
confirmSubmitCancel: "再想想",
|
||||
noDepositReminderTitle: "申请已提交",
|
||||
noDepositReminderContent: "当前申请单暂无待支付定金账单,管家将为您排房,请耐心等待。",
|
||||
noDepositReminderConfirm: "我知道了",
|
||||
submitFailed: "申请单提交失败",
|
||||
submitRoomTypeApply: "提交房型申请",
|
||||
submitSuccess: "申请单提交成功",
|
||||
@@ -3010,9 +3368,17 @@ const messages = {
|
||||
},
|
||||
room_inspection: {
|
||||
room_inspection: {
|
||||
confirmSubmitDesc: "如有交接备注、异常背景或补充情况,可在提交前补充说明。",
|
||||
confirmSubmitSkip: "暂不填写",
|
||||
confirmSubmitTitle: "提交前确认",
|
||||
confirmSubmitAction: "确认提交",
|
||||
handoverInspection: "交付检查",
|
||||
inspectionHint: "建议逐项填写,异常情况请补充描述并上传照片。",
|
||||
pageTitle: "验房",
|
||||
pageDesc: "按当前订单查看并提交验房结果",
|
||||
inspectionListTitle: "验房记录",
|
||||
inspectionSwitcher: "验房单切换",
|
||||
inspectionRecordUnit: "份记录",
|
||||
roomNoLabel: "房间号",
|
||||
inspectionTypeLabel: "验房类型",
|
||||
inspectionStatusLabel: "当前状态",
|
||||
@@ -3021,15 +3387,23 @@ const messages = {
|
||||
inspectionRemarksPlaceholder: "可填写本次验房的补充说明",
|
||||
inspectionItemsTitle: "检查项",
|
||||
inspectionItemsDesc: "请根据现场情况逐项核对",
|
||||
inspectionItemUnit: "项",
|
||||
deviceFallback: "未命名设备",
|
||||
markAllNormal: "全部正常",
|
||||
normalResultOptionMissing: "未找到正常结果选项",
|
||||
allMarkedNormal: "已全部勾选正常",
|
||||
requiredTag: "必查",
|
||||
expectedQuantityLabel: "应有数量",
|
||||
inspectionResultLabel: "检查结果",
|
||||
checkedQuantityLabel: "实测数量",
|
||||
checkedQuantityPlaceholder: "请输入数量",
|
||||
issueDetailLabel: "异常说明",
|
||||
problemDetailLabel: "问题说明",
|
||||
problemDetailHint: "缺失、损坏或无法使用时请补充描述",
|
||||
problemDetailPlaceholder: "请描述异常、缺失或损坏情况",
|
||||
photosLabel: "现场照片",
|
||||
photosHint: "建议上传关键部位照片,便于后续确认",
|
||||
uploadPhoto: "上传照片",
|
||||
addPhoto: "添加",
|
||||
submitAction: "提交验房",
|
||||
loadingDesc: "正在同步验房数据...",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name" : "测试",
|
||||
"name" : "绿居公寓手机端",
|
||||
"appid" : "__UNI__056D753",
|
||||
"description" : "",
|
||||
"versionName" : "1.0.0",
|
||||
|
||||
@@ -156,6 +156,12 @@
|
||||
"navigationBarTitleText": "Utility Recharge"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "lock_control/lock_control",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Smart Lock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "smart_control/smart_control",
|
||||
"style": {
|
||||
|
||||
@@ -130,6 +130,15 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="shouldShowApplicationPaymentStatus(item)"
|
||||
class="payment-status-banner tn-margin-top-lg"
|
||||
:class="`payment-status-banner--${getApplicationPaymentStatusType(item)}`"
|
||||
>
|
||||
<text class="payment-status-banner__label">{{ $tr('支付状态') }}</text>
|
||||
<text class="payment-status-banner__value">{{ getApplicationPaymentStatusText(item) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="card-body tn-margin-top-lg">
|
||||
<view class="info-row tn-flex tn-flex-row-between">
|
||||
<text class="info-label">{{ $tr('所属公寓') }}</text>
|
||||
@@ -223,7 +232,7 @@
|
||||
:data-reference-type-no="resolveReferenceTypeNo(item)"
|
||||
@click.stop="payDepositAction"
|
||||
>
|
||||
<text class="tn-text-bold">{{ $tr('支付定金') }}</text>
|
||||
<text class="tn-text-bold">{{ $tr('去支付') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -296,12 +305,29 @@
|
||||
</view>
|
||||
</view>
|
||||
</tn-popup>
|
||||
|
||||
<tn-action-sheet
|
||||
v-model="showPaymentMethodSheet"
|
||||
:list="getPaymentMethodSheetOptions()"
|
||||
:tips="{ text: getPaymentMethodSheetTipText() }"
|
||||
:borderRadius="32"
|
||||
:safeAreaInsetBottom="true"
|
||||
@click="handlePaymentMethodSheetClick"
|
||||
></tn-action-sheet>
|
||||
|
||||
<deposit-other-payment-popup
|
||||
v-model="showOfflinePaymentNotice"
|
||||
:config="depositOtherPaymentConfig"
|
||||
:loading="depositOtherPaymentConfigLoading"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { applicationFormDetail, applicationFormMePage, accountQueryBill, accountQueryBillPreview, accountQueryBills, contractDetail, orderSearchedMePage, orderSearchedReservationFlow, paymentBillPay, paymentPayingBills, roomInspectionDetail, userDetailCancel } from '@/api/index.js'
|
||||
import DepositOtherPaymentPopup from '@/components/DepositOtherPaymentPopup.vue'
|
||||
import { applicationFormDetail, applicationFormMePage, accountQueryBill, accountQueryBillPreview, accountQueryBills, contractDetail, orderSearchedDetail, orderSearchedMePage, orderSearchedReservationFlow, paymentBillPay, paymentPayingBills, roomInspectionDetail, userDetailCancel } from '@/api/index.js'
|
||||
import { getMyApplicationList, saveActiveApplicationCode, saveMyApplicationList } from '@/utils/app-state'
|
||||
import { createDefaultDepositOtherPaymentConfig, loadDepositOtherPaymentConfig } from '@/utils/deposit-other-payment'
|
||||
import {
|
||||
RESERVATION_FLOW_STEP,
|
||||
attachReservationFlow,
|
||||
@@ -312,7 +338,7 @@ import {
|
||||
getReservationOrderNo,
|
||||
parseReservationFlowResponse
|
||||
} from '@/utils/reservation-flow'
|
||||
import { getOrderStatusCode, normalizeBookingList, parseOrderListResponse } from '@/utils/booking-helper'
|
||||
import { getOrderStatusCode, normalizeBookingDetail, normalizeBookingList, parseOrderDetailResponse, parseOrderListResponse } from '@/utils/booking-helper'
|
||||
import { localizePayMethodText } from '@/utils/select-room-normalizers'
|
||||
|
||||
function formatPageResponse(response) {
|
||||
@@ -410,7 +436,47 @@ function padCountdownUnit(value) {
|
||||
return String(Math.max(0, Number(value) || 0)).padStart(2, '0')
|
||||
}
|
||||
|
||||
function resolveApplicationPaymentStatusInfo(item = {}) {
|
||||
if (!item || item.isViewingApplication) {
|
||||
return {
|
||||
key: 'exempt',
|
||||
desc: ''
|
||||
}
|
||||
}
|
||||
|
||||
const desc = String(item.payStatusDesc || item.paymentStatusDesc || '').trim()
|
||||
const status = Number(
|
||||
item.payStatus !== undefined && item.payStatus !== null
|
||||
? item.payStatus
|
||||
: item.paymentStatus
|
||||
)
|
||||
|
||||
if (status === 10 || /已支付|paid/i.test(desc)) {
|
||||
return { key: 'paid', desc }
|
||||
}
|
||||
if (/退款|refunded?/i.test(desc)) {
|
||||
return { key: 'refunded', desc }
|
||||
}
|
||||
if (/失败|failed?/i.test(desc)) {
|
||||
return { key: 'failed', desc }
|
||||
}
|
||||
if (/处理中|processing/i.test(desc)) {
|
||||
return { key: 'processing', desc }
|
||||
}
|
||||
if (status === 0 || /待支付|未支付|unpaid|pending/i.test(desc)) {
|
||||
return { key: 'pending', desc }
|
||||
}
|
||||
|
||||
return {
|
||||
key: desc ? 'processing' : 'pending',
|
||||
desc
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DepositOtherPaymentPopup
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentIndex: 0,
|
||||
@@ -423,12 +489,17 @@ export default {
|
||||
loadingMore: false,
|
||||
hasMore: true,
|
||||
showDepositNotice: false,
|
||||
showPaymentMethodSheet: false,
|
||||
showOfflinePaymentNotice: false,
|
||||
pendingDepositReferenceTypeNo: '',
|
||||
pendingPaymentMethodContext: null,
|
||||
pendingDepositPaymentContext: null,
|
||||
paymentNoticeSourceItem: null,
|
||||
paymentNoticeSourceLoading: false,
|
||||
paymentNoticeBillLoading: false,
|
||||
paymentNoticeBillDetail: null,
|
||||
depositOtherPaymentConfig: createDefaultDepositOtherPaymentConfig(),
|
||||
depositOtherPaymentConfigLoading: false,
|
||||
reservationBillDetailMap: {},
|
||||
reservationContractDetailMap: {},
|
||||
reservationInspectionDetailMap: {},
|
||||
@@ -441,6 +512,7 @@ export default {
|
||||
watch: {
|
||||
language() {
|
||||
this.reloadList(true)
|
||||
this.loadDepositOtherPaymentConfig()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -464,6 +536,7 @@ export default {
|
||||
},
|
||||
onShow() {
|
||||
this.load()
|
||||
this.loadDepositOtherPaymentConfig()
|
||||
},
|
||||
mounted() {
|
||||
this.startCountdownTimer()
|
||||
@@ -522,15 +595,65 @@ export default {
|
||||
isPaidApplication(item = {}) {
|
||||
return !item.isViewingApplication && Number(item.payStatus) === 10
|
||||
},
|
||||
shouldShowApplicationPaymentStatus(item = {}) {
|
||||
return !item.isViewingApplication
|
||||
},
|
||||
getApplicationPaymentStatusText(item = {}) {
|
||||
if (this.isWaitingButlerRoomAssignment(item)) {
|
||||
return this.$tr('等待管家排房')
|
||||
}
|
||||
const statusInfo = resolveApplicationPaymentStatusInfo(item)
|
||||
if (this.$getLanguage() !== 'en' && statusInfo.desc) {
|
||||
return statusInfo.desc
|
||||
}
|
||||
const textMap = {
|
||||
exempt: this.$tr('无需支付'),
|
||||
paid: this.$tr('已支付'),
|
||||
refunded: this.$tr('已退款'),
|
||||
failed: this.$tr('支付失败'),
|
||||
processing: this.$tr('处理中'),
|
||||
pending: this.$tr('待支付')
|
||||
}
|
||||
return textMap[statusInfo.key] || statusInfo.desc || this.$tr('待支付')
|
||||
},
|
||||
getApplicationPaymentStatusType(item = {}) {
|
||||
if (this.isWaitingButlerRoomAssignment(item)) {
|
||||
return 'info'
|
||||
}
|
||||
const statusInfo = resolveApplicationPaymentStatusInfo(item)
|
||||
const typeMap = {
|
||||
exempt: 'neutral',
|
||||
paid: 'success',
|
||||
refunded: 'neutral',
|
||||
failed: 'danger',
|
||||
processing: 'info',
|
||||
pending: 'warning'
|
||||
}
|
||||
return typeMap[statusInfo.key] || 'warning'
|
||||
},
|
||||
shouldPayDeposit(item = {}) {
|
||||
return !item.isViewingApplication
|
||||
&& Number(item.status) === 0
|
||||
&& Number(item.payStatus) !== 10
|
||||
},
|
||||
shouldQueryPendingDepositBill(item = {}) {
|
||||
return this.shouldPayDeposit(item) && !!this.resolveReferenceTypeNo(item)
|
||||
},
|
||||
hasResolvedPendingDepositBill(item = {}) {
|
||||
return !!(item && item.pendingDepositBillQueried)
|
||||
},
|
||||
hasPendingDepositBill(item = {}) {
|
||||
return !!String(this.getBillNo(item && item.pendingDepositBill ? item.pendingDepositBill : {}) || '').trim()
|
||||
},
|
||||
isWaitingButlerRoomAssignment(item = {}) {
|
||||
return this.shouldQueryPendingDepositBill(item)
|
||||
&& this.hasResolvedPendingDepositBill(item)
|
||||
&& !this.hasPendingDepositBill(item)
|
||||
},
|
||||
isWaitingRoomAssignment(item = {}) {
|
||||
return !item.isViewingApplication
|
||||
&& Number(item.status) === 0
|
||||
&& Number(item.payStatus) === 10
|
||||
&& (Number(item.payStatus) === 10 || this.isWaitingButlerRoomAssignment(item))
|
||||
},
|
||||
getStatusReminderText(item = {}) {
|
||||
if (this.isReservationContractAwaitingReview(item)) {
|
||||
@@ -544,11 +667,14 @@ export default {
|
||||
if (reservationStepText) {
|
||||
return reservationStepText
|
||||
}
|
||||
if (this.isWaitingButlerRoomAssignment(item)) {
|
||||
return this.$tr('等待管家排房')
|
||||
}
|
||||
if (this.shouldPayDeposit(item)) {
|
||||
return this.$tr('请支付定金')
|
||||
}
|
||||
if (this.isWaitingRoomAssignment(item)) {
|
||||
return this.$tr('等待预订中心分配房间')
|
||||
return this.$tr('等待管家排房')
|
||||
}
|
||||
return ''
|
||||
},
|
||||
@@ -901,7 +1027,7 @@ export default {
|
||||
return this.formatPendingPaymentCountdown(this.getPendingPaymentRemainingSeconds(order))
|
||||
},
|
||||
getPendingPaymentActionText(item = {}) {
|
||||
return this.isApplicationPendingPaymentExpired(item) ? this.$tr('支付定金') : this.$tr('继续支付')
|
||||
return this.$tr('去支付')
|
||||
},
|
||||
getReservationOrderNoFromItem(item = {}) {
|
||||
return getReservationOrderNo(item)
|
||||
@@ -1270,7 +1396,7 @@ export default {
|
||||
this._pendingPaymentRequestToken = requestToken
|
||||
|
||||
await Promise.all(baseList.map(async (item, index) => {
|
||||
if (!this.canViewBill(item)) {
|
||||
if (!this.shouldQueryPendingDepositBill(item)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1289,17 +1415,6 @@ export default {
|
||||
})
|
||||
const billList = this.normalizeBillList(billRes)
|
||||
const bill = Array.isArray(billList) && billList.length ? billList[0] : null
|
||||
if (!bill || this._pendingPaymentRequestToken !== requestToken) {
|
||||
return
|
||||
}
|
||||
|
||||
const billNo = this.getBillNo(bill)
|
||||
if (!billNo) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log('[MyApplicationTab] query payment/paying-bills', billNo)
|
||||
const pendingPaymentOrder = await this.getPendingPaymentOrder(billNo).catch(() => null)
|
||||
if (this._pendingPaymentRequestToken !== requestToken) {
|
||||
return
|
||||
}
|
||||
@@ -1309,10 +1424,38 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
if (!bill) {
|
||||
this.$set(this.list, index, {
|
||||
...currentItem,
|
||||
pendingDepositBill: null,
|
||||
pendingPaymentOrder: null,
|
||||
pendingDepositBillQueried: true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const billNo = this.getBillNo(bill)
|
||||
if (!billNo) {
|
||||
this.$set(this.list, index, {
|
||||
...currentItem,
|
||||
pendingDepositBill: null,
|
||||
pendingPaymentOrder: null,
|
||||
pendingDepositBillQueried: true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
console.log('[MyApplicationTab] query payment/paying-bills', billNo)
|
||||
const pendingPaymentOrder = await this.getPendingPaymentOrder(billNo).catch(() => null)
|
||||
if (this._pendingPaymentRequestToken !== requestToken) {
|
||||
return
|
||||
}
|
||||
|
||||
const updatedItem = {
|
||||
...currentItem,
|
||||
pendingDepositBill: bill,
|
||||
pendingPaymentOrder
|
||||
pendingPaymentOrder,
|
||||
pendingDepositBillQueried: true
|
||||
}
|
||||
this.$set(this.list, index, updatedItem)
|
||||
} catch (error) {
|
||||
@@ -1404,7 +1547,8 @@ export default {
|
||||
.map(item => ({
|
||||
...item,
|
||||
pendingDepositBill: item && item.pendingDepositBill ? item.pendingDepositBill : null,
|
||||
pendingPaymentOrder: item && item.pendingPaymentOrder ? item.pendingPaymentOrder : null
|
||||
pendingPaymentOrder: item && item.pendingPaymentOrder ? item.pendingPaymentOrder : null,
|
||||
pendingDepositBillQueried: !!(item && item.pendingDepositBillQueried)
|
||||
}))
|
||||
|
||||
this.total = total
|
||||
@@ -1457,14 +1601,28 @@ export default {
|
||||
return item.detailCode || ''
|
||||
},
|
||||
canViewBill(item = {}) {
|
||||
return !item.isViewingApplication
|
||||
&& (this.shouldPayDeposit(item) || this.isReservationBillStep(item))
|
||||
&& this.resolveReferenceTypeNo(item)
|
||||
if (item.isViewingApplication || !this.resolveReferenceTypeNo(item)) {
|
||||
return false
|
||||
}
|
||||
if (this.isReservationBillStep(item)) {
|
||||
return true
|
||||
}
|
||||
if (!this.shouldQueryPendingDepositBill(item)) {
|
||||
return false
|
||||
}
|
||||
return this.hasPendingDepositBill(item)
|
||||
},
|
||||
canCancelApplication(item = {}) {
|
||||
return !!(this.resolveReferenceTypeNo(item)
|
||||
&& Number(item.status) === 0
|
||||
&& (item.isViewingApplication || Number(item.payStatus) !== 10))
|
||||
if (!this.resolveReferenceTypeNo(item) || Number(item.status) !== 0) {
|
||||
return false
|
||||
}
|
||||
if (item.isViewingApplication) {
|
||||
return true
|
||||
}
|
||||
if (Number(item.payStatus) === 10) {
|
||||
return false
|
||||
}
|
||||
return this.hasPendingDepositBill(item)
|
||||
},
|
||||
canCancelOrder(item = {}) {
|
||||
return this.canCancelApplication(item)
|
||||
@@ -1504,7 +1662,8 @@ export default {
|
||||
status: 99,
|
||||
statusDesc: this.$tr('已结束'),
|
||||
pendingDepositBill: null,
|
||||
pendingPaymentOrder: null
|
||||
pendingPaymentOrder: null,
|
||||
pendingDepositBillQueried: false
|
||||
}
|
||||
}
|
||||
this.list = normalizeApplicationList(this.list.map(updateItem))
|
||||
@@ -1566,7 +1725,15 @@ export default {
|
||||
})
|
||||
return
|
||||
}
|
||||
this.payDepositByReferenceTypeNo(referenceTypeNo)
|
||||
const currentItem = (Array.isArray(this.list) ? this.list : []).find((item) => {
|
||||
return String(this.resolveReferenceTypeNo(item)) === referenceTypeNo
|
||||
}) || {}
|
||||
this.openPaymentMethodSelector({
|
||||
referenceTypeNo,
|
||||
description: this.$tr('支付定金'),
|
||||
mode: 'deposit',
|
||||
sourceItem: currentItem
|
||||
})
|
||||
},
|
||||
resolvePreferredReferenceTypeNo(list = []) {
|
||||
const sortedList = [...list]
|
||||
@@ -1595,13 +1762,65 @@ export default {
|
||||
const currentItem = (Array.isArray(this.list) ? this.list : []).find((item) => {
|
||||
return String(this.resolveReferenceTypeNo(item)) === referenceTypeNo
|
||||
}) || {}
|
||||
this.openDepositNoticeForPayment({
|
||||
this.openPaymentMethodSelector({
|
||||
referenceTypeNo,
|
||||
description: this.$tr('支付定金'),
|
||||
mode: 'deposit',
|
||||
sourceItem: currentItem
|
||||
})
|
||||
},
|
||||
getPaymentMethodSheetOptions() {
|
||||
return [
|
||||
{
|
||||
text: this.language === 'en' ? 'WeChat Pay' : '微信支付',
|
||||
subText: this.language === 'en'
|
||||
? 'The system will continue by opening WeChat for payment.'
|
||||
: '系统将继续拉起微信完成支付'
|
||||
},
|
||||
{
|
||||
text: this.language === 'en' ? 'Other Payment Methods' : '其他支付方式',
|
||||
subText: this.language === 'en'
|
||||
? 'View the corporate bank account details when WeChat Pay is unavailable.'
|
||||
: '无法使用微信支付时查看对公账户信息'
|
||||
}
|
||||
]
|
||||
},
|
||||
getPaymentMethodSheetTipText() {
|
||||
return this.language === 'en' ? 'Please select a payment method' : '请选择支付方式'
|
||||
},
|
||||
openPaymentMethodSelector(context = {}) {
|
||||
this.pendingPaymentMethodContext = context || null
|
||||
this.showPaymentMethodSheet = true
|
||||
},
|
||||
handlePaymentMethodSheetClick(index) {
|
||||
const context = this.pendingPaymentMethodContext || {}
|
||||
this.pendingPaymentMethodContext = null
|
||||
if (Number(index) === 0) {
|
||||
this.openDepositNoticeForPayment(context)
|
||||
return
|
||||
}
|
||||
if (Number(index) === 1) {
|
||||
this.showOfflinePaymentNotice = true
|
||||
this.loadDepositOtherPaymentConfig()
|
||||
}
|
||||
},
|
||||
closeOfflinePaymentNotice() {
|
||||
this.showOfflinePaymentNotice = false
|
||||
},
|
||||
loadDepositOtherPaymentConfig() {
|
||||
this.depositOtherPaymentConfigLoading = true
|
||||
return loadDepositOtherPaymentConfig(this.language)
|
||||
.then((config) => {
|
||||
this.depositOtherPaymentConfig = config || createDefaultDepositOtherPaymentConfig()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('load deposit other payment config failed', error)
|
||||
this.depositOtherPaymentConfig = createDefaultDepositOtherPaymentConfig()
|
||||
})
|
||||
.finally(() => {
|
||||
this.depositOtherPaymentConfigLoading = false
|
||||
})
|
||||
},
|
||||
getPaymentContextMode(context = {}) {
|
||||
const explicitMode = String(context.mode || '').trim()
|
||||
if (explicitMode) {
|
||||
@@ -1802,19 +2021,18 @@ export default {
|
||||
|| ''
|
||||
).trim()
|
||||
},
|
||||
loadPaymentNoticeSourceDetail(referenceTypeNo = '', fallbackSource = null) {
|
||||
const fallbackReferenceTypeNo = fallbackSource ? this.getApplicationDetailCodeFromOrder(fallbackSource) : ''
|
||||
const normalizedReferenceTypeNo = String(referenceTypeNo || fallbackReferenceTypeNo || '').trim()
|
||||
if (!normalizedReferenceTypeNo) {
|
||||
loadPaymentNoticeSourceDetail(orderNo = '', fallbackSource = null) {
|
||||
const normalizedOrderNo = String(orderNo || this.resolvePaymentNoticeOrderNo({}, fallbackSource) || '').trim()
|
||||
if (!normalizedOrderNo) {
|
||||
this.paymentNoticeSourceLoading = false
|
||||
this.paymentNoticeSourceItem = fallbackSource || this.paymentNoticeSourceItem || null
|
||||
return Promise.resolve(this.paymentNoticeSourceItem)
|
||||
}
|
||||
|
||||
this.paymentNoticeSourceLoading = true
|
||||
return this.resolveApplicationDetailPaymentContext(normalizedReferenceTypeNo)
|
||||
.then((context) => {
|
||||
const detail = context && context.detail ? context.detail : null
|
||||
return orderSearchedDetail(normalizedOrderNo, this.language)
|
||||
.then((response) => {
|
||||
const detail = normalizeBookingDetail(parseOrderDetailResponse(response), this.language)
|
||||
this.paymentNoticeSourceItem = detail
|
||||
? {
|
||||
...(fallbackSource || {}),
|
||||
@@ -1903,16 +2121,17 @@ export default {
|
||||
).trim()
|
||||
this.pendingDepositPaymentContext.referenceTypeNo = nextReferenceTypeNo
|
||||
this.pendingDepositReferenceTypeNo = nextReferenceTypeNo
|
||||
return this.loadPaymentNoticeSourceDetail(nextReferenceTypeNo, mergedSource)
|
||||
return this.loadPaymentNoticeSourceDetail(paymentOrderNo, mergedSource)
|
||||
})
|
||||
.catch(() => {
|
||||
this.loadPaymentNoticeSourceDetail(this.pendingDepositPaymentContext.referenceTypeNo, this.paymentNoticeSourceItem)
|
||||
this.loadPaymentNoticeSourceDetail(paymentOrderNo, this.paymentNoticeSourceItem)
|
||||
})
|
||||
this.showDepositNotice = true
|
||||
},
|
||||
closeDepositNotice() {
|
||||
this.showDepositNotice = false
|
||||
this.pendingDepositReferenceTypeNo = ''
|
||||
this.pendingPaymentMethodContext = null
|
||||
this.pendingDepositPaymentContext = null
|
||||
this.paymentNoticeSourceItem = null
|
||||
this.paymentNoticeSourceLoading = false
|
||||
@@ -2225,7 +2444,7 @@ export default {
|
||||
const paymentContext = this.resolveReservationBillPaymentContext(currentItem || {}, {
|
||||
referenceTypeNo
|
||||
})
|
||||
this.openDepositNoticeForPayment(paymentContext)
|
||||
this.openPaymentMethodSelector(paymentContext)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2686,6 +2905,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 {
|
||||
color: #b45309;
|
||||
background: rgba(245, 158, 11, 0.12);
|
||||
@@ -3082,4 +3367,118 @@ export default {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.offline-payment-sheet {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.offline-payment-intro {
|
||||
padding: 24rpx 28rpx;
|
||||
border-radius: 28rpx;
|
||||
background: linear-gradient(180deg, rgba($brand-primary, 0.08) 0%, rgba(31, 111, 229, 0.04) 100%);
|
||||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||||
}
|
||||
|
||||
.offline-payment-intro__zh {
|
||||
font-size: 28rpx;
|
||||
line-height: 1.6;
|
||||
color: #22324D;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.offline-payment-intro__en {
|
||||
margin-top: 12rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.7;
|
||||
color: #5B6B86;
|
||||
}
|
||||
|
||||
.offline-payment-card {
|
||||
padding: 24rpx 28rpx;
|
||||
border-radius: 28rpx;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||||
box-shadow: 0 16rpx 36rpx rgba($brand-primary, 0.08);
|
||||
}
|
||||
|
||||
.offline-payment-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
padding: 22rpx 0;
|
||||
}
|
||||
|
||||
.offline-payment-row + .offline-payment-row {
|
||||
border-top: 1rpx solid rgba($brand-primary, 0.08);
|
||||
}
|
||||
|
||||
.offline-payment-label-group,
|
||||
.offline-payment-value-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.offline-payment-label {
|
||||
font-size: 24rpx;
|
||||
line-height: 1.5;
|
||||
color: #6B7A92;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.offline-payment-label-en {
|
||||
font-size: 20rpx;
|
||||
line-height: 1.6;
|
||||
color: #8B97AA;
|
||||
}
|
||||
|
||||
.offline-payment-value {
|
||||
font-size: 28rpx;
|
||||
line-height: 1.6;
|
||||
color: #22324D;
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.offline-payment-value-en {
|
||||
font-size: 22rpx;
|
||||
line-height: 1.7;
|
||||
color: #4F5F79;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.offline-payment-note-title {
|
||||
font-size: 28rpx;
|
||||
line-height: 1.4;
|
||||
color: #22324D;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.offline-payment-note-title-en {
|
||||
margin-top: 8rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.6;
|
||||
color: #6B7A92;
|
||||
}
|
||||
|
||||
.offline-payment-note {
|
||||
margin-top: 22rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.offline-payment-note__zh {
|
||||
font-size: 25rpx;
|
||||
line-height: 1.7;
|
||||
color: #22324D;
|
||||
}
|
||||
|
||||
.offline-payment-note__en {
|
||||
font-size: 21rpx;
|
||||
line-height: 1.7;
|
||||
color: #5B6B86;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<view class="profile-page tn-safe-area-inset-bottom">
|
||||
|
||||
<!-- 头部背景与用户信息 (悬浮质感设计) -->
|
||||
@@ -308,7 +308,7 @@ export default {
|
||||
return '-'
|
||||
}
|
||||
const currentOrder = this.getCurrentOrder() || {}
|
||||
const startText = currentOrder.startDate || currentOrder.start || currentOrder.checkinDate || ''
|
||||
const startText = currentOrder.contractStartTime || currentOrder.startDate || currentOrder.start || currentOrder.checkinDate || ''
|
||||
if (!startText) {
|
||||
return '--'
|
||||
}
|
||||
@@ -509,6 +509,12 @@ export default {
|
||||
this.goToRegistrationRecords()
|
||||
return
|
||||
}
|
||||
if (item.key === 'lock') {
|
||||
uni.navigateTo({
|
||||
url: '/pkgService/lock_control/lock_control'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (item.key === 'butler') {
|
||||
this.goToSubmitRequest(itemTitle, itemDesc, {
|
||||
businessType: 'butler-contact',
|
||||
|
||||
@@ -252,6 +252,10 @@ export default {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: 'zh'
|
||||
@@ -348,7 +352,14 @@ export default {
|
||||
})
|
||||
},
|
||||
handleBannerChange(e) {
|
||||
this.$emit('banner-change', e.detail.current)
|
||||
if (!this.isActive) {
|
||||
return
|
||||
}
|
||||
const nextIndex = Number(e && e.detail && e.detail.current)
|
||||
if (!Number.isFinite(nextIndex)) {
|
||||
return
|
||||
}
|
||||
this.$emit('banner-change', nextIndex)
|
||||
},
|
||||
openStoreDetail(area) {
|
||||
uni.navigateTo({
|
||||
|
||||
@@ -91,6 +91,10 @@ export default {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: 'zh'
|
||||
@@ -144,9 +148,19 @@ export default {
|
||||
.trim()
|
||||
},
|
||||
handleBannerChange(e) {
|
||||
this.$emit('banner-change', e.detail.current)
|
||||
if (!this.isActive) {
|
||||
return
|
||||
}
|
||||
const nextIndex = Number(e && e.detail && e.detail.current)
|
||||
if (!Number.isFinite(nextIndex)) {
|
||||
return
|
||||
}
|
||||
this.$emit('banner-change', nextIndex)
|
||||
},
|
||||
switchBanner(index) {
|
||||
if (!this.isActive) {
|
||||
return
|
||||
}
|
||||
this.$emit('banner-switch', index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
>
|
||||
<swiper-item>
|
||||
<SelectRoomHero
|
||||
:is-active="currentScreen === 0"
|
||||
:language="currentLang"
|
||||
:banner-list="bannerList"
|
||||
:current-banner="currentBanner"
|
||||
@@ -48,6 +49,7 @@
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<SelectRoomAreaPanel
|
||||
:is-active="currentScreen === 1"
|
||||
:language="currentLang"
|
||||
:banner-list="bannerList"
|
||||
:current-banner-index="currentBannerIndex"
|
||||
@@ -431,10 +433,18 @@ export default {
|
||||
})
|
||||
},
|
||||
handleBannerChange(index) {
|
||||
this.currentBannerIndex = index
|
||||
const nextIndex = Number(index)
|
||||
if (!Number.isFinite(nextIndex) || nextIndex === this.currentBannerIndex) {
|
||||
return
|
||||
}
|
||||
this.currentBannerIndex = nextIndex
|
||||
},
|
||||
switchBanner(index) {
|
||||
this.currentBannerIndex = index
|
||||
const nextIndex = Number(index)
|
||||
if (!Number.isFinite(nextIndex) || nextIndex === this.currentBannerIndex) {
|
||||
return
|
||||
}
|
||||
this.currentBannerIndex = nextIndex
|
||||
},
|
||||
collapseToTopBanner() {
|
||||
this.currentScreen = 1
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -83,7 +83,7 @@ import {
|
||||
resolveLoginPreparation
|
||||
} from '../../utils/login-helper'
|
||||
import { resolveBookingRedirectAfterLogin } from '../../utils/booking-entry'
|
||||
import { parseReservationFlowResponse, getReservationCurrentNode, hasEffectiveReservationFlow } from '../../utils/reservation-flow'
|
||||
import { parseReservationFlowResponse, getReservationCurrentFlow, getReservationCurrentNode, hasEffectiveReservationFlow } from '../../utils/reservation-flow'
|
||||
import { registrationMyorder, orderSearchedReservationFlow, userMe, usersLogin } from '@/api/index.js'
|
||||
|
||||
export default {
|
||||
@@ -250,10 +250,7 @@ export default {
|
||||
}, this.$getLanguage())
|
||||
.then(response => {
|
||||
const flowList = parseReservationFlowResponse(response)
|
||||
const currentFlow = flowList.find(item => {
|
||||
const node = getReservationCurrentNode(item)
|
||||
return node && node.isCurrent
|
||||
}) || flowList[0] || null
|
||||
const currentFlow = getReservationCurrentFlow(flowList) || flowList[0] || null
|
||||
const currentNode = currentFlow ? getReservationCurrentNode(currentFlow) : null
|
||||
const hasActiveReservationFlow = flowList.some(item => hasEffectiveReservationFlow(item))
|
||||
return {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -94,27 +94,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>
|
||||
|
||||
@@ -192,9 +192,10 @@ export default {
|
||||
`${this.$tr('支付方式')}: ${this.payMethod}`,
|
||||
`${this.$tr('支付金额')}: $500.00`,
|
||||
'',
|
||||
`1. ${this.$tr('请确认支付方式和金额无误后再继续操作。')}`,
|
||||
`2. ${this.$tr('当前页面为预支付引导页,真实支付接口暂未接入。')}`,
|
||||
`3. ${this.$tr('确认后将进入后续提交流程,由工作人员继续跟进。')}`
|
||||
`1. ${this.$tr('请在 5 分钟内支付定金或者款项,否则系统将自动取消这个订单,释放锁定的房源。')}`,
|
||||
`2. ${this.$tr('请确认支付方式和金额无误后再继续操作。')}`,
|
||||
`3. ${this.$tr('当前页面为预支付引导页,真实支付接口暂未接入。')}`,
|
||||
`4. ${this.$tr('确认后将进入后续提交流程,由工作人员继续跟进。')}`
|
||||
].join('\n')
|
||||
|
||||
return new Promise((resolve) => {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="asset-name tn-text-ellipsis">{{ item.assetName }}</text>
|
||||
<text class="asset-name tn-text-ellipsis">{{ getAssetDisplayName(item) }}</text>
|
||||
<text v-if="item.brand" class="asset-brand tn-text-ellipsis">{{ item.brand }}</text>
|
||||
<text v-else class="asset-brand-placeholder"></text>
|
||||
</view>
|
||||
@@ -53,7 +53,14 @@ export default {
|
||||
default: 'zh'
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
methods: {
|
||||
getAssetDisplayName(item = {}) {
|
||||
if (this.language === 'en') {
|
||||
return item.assetNameEn || item.assetName || ''
|
||||
}
|
||||
return item.assetName || item.assetNameEn || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -446,6 +446,7 @@ function normalizeRoomAssets(list = []) {
|
||||
return null
|
||||
}
|
||||
const assetName = normalizeText(item.assetName || item.name || item.label)
|
||||
const assetNameEn = normalizeText(item.assetNameEn || item.nameEn || item.labelEn)
|
||||
if (!assetName) {
|
||||
return null
|
||||
}
|
||||
@@ -456,6 +457,7 @@ function normalizeRoomAssets(list = []) {
|
||||
key: `${item.roomTypeCode || 'asset'}-${item.assetName || item.name || index}`,
|
||||
roomTypeCode: item.roomTypeCode || '',
|
||||
assetName,
|
||||
assetNameEn,
|
||||
brand: normalizeText(item.brand),
|
||||
icon: iconClass ? '' : addFilePrefix(rawIcon),
|
||||
iconClass,
|
||||
|
||||
@@ -98,8 +98,8 @@
|
||||
import StoreInfo from './components/StoreInfo.vue';
|
||||
import FacilityList from './components/FacilityList.vue';
|
||||
import RoomList from './components/RoomList.vue';
|
||||
import { facilityManagers } from '@/api/index.js'
|
||||
import { localizeFacilityName, localizeHousingTag } from '../../utils/select-room-normalizers'
|
||||
import { areaDetailPagination, facilityManagers } from '@/api/index.js'
|
||||
import { localizeFacilityName, localizeHousingTag, normalizeAreaItem } from '../../utils/select-room-normalizers'
|
||||
|
||||
function parseCoordinate(...values) {
|
||||
for (let i = 0; i < values.length; i += 1) {
|
||||
@@ -140,18 +140,12 @@ export default {
|
||||
if (Array.isArray(this.storeData.images) && this.storeData.images.length) {
|
||||
return this.storeData.images.filter(Boolean)
|
||||
}
|
||||
if (this.heroImage) {
|
||||
return [this.heroImage]
|
||||
}
|
||||
return []
|
||||
},
|
||||
heroImage() {
|
||||
if (this.storeData.images && this.storeData.images[0]) {
|
||||
return this.storeData.images[0]
|
||||
}
|
||||
if (this.roomTypes.length > 0 && this.roomTypes[0].images && this.roomTypes[0].images[0]) {
|
||||
return this.roomTypes[0].images[0]
|
||||
}
|
||||
return ''
|
||||
},
|
||||
storeTags() {
|
||||
@@ -284,7 +278,74 @@ export default {
|
||||
applyStoreData(data) {
|
||||
this.storeData = data && typeof data === 'object' ? data : {}
|
||||
this.roomTypes = this.storeData.roomTypes || []
|
||||
this.loadAreaManager()
|
||||
this.currentImageIndex = 0
|
||||
return this.loadAreaDetail()
|
||||
.catch((error) => {
|
||||
console.warn('查询区域详情失败', error)
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadAreaManager()
|
||||
})
|
||||
},
|
||||
pickAreaDetail(rawList = []) {
|
||||
if (!Array.isArray(rawList) || !rawList.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const areaCode = String(this.storeData.code || this.storeData.areaCode || '').trim()
|
||||
const areaName = String(this.storeData.name || '').trim()
|
||||
const areaNameEn = String(this.storeData.nameEn || '').trim()
|
||||
|
||||
if (areaCode) {
|
||||
const exactCodeItem = rawList.find((item) => String(item && item.code || '').trim() === areaCode)
|
||||
if (exactCodeItem) {
|
||||
return exactCodeItem
|
||||
}
|
||||
}
|
||||
|
||||
const exactNameItem = rawList.find((item) => {
|
||||
const name = String(item && item.name || '').trim()
|
||||
const nameEn = String(item && item.nameEn || '').trim()
|
||||
return (areaName && name === areaName) || (areaNameEn && nameEn === areaNameEn)
|
||||
})
|
||||
if (exactNameItem) {
|
||||
return exactNameItem
|
||||
}
|
||||
|
||||
return rawList[0] || null
|
||||
},
|
||||
loadAreaDetail() {
|
||||
const areaCode = String(this.storeData.code || this.storeData.areaCode || '').trim()
|
||||
const searchKeyword = areaCode || String(this.storeData.name || '').trim()
|
||||
if (!searchKeyword) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return areaDetailPagination({
|
||||
PageIndex: 1,
|
||||
PageSize: areaCode ? 20 : 50,
|
||||
Search: searchKeyword
|
||||
}, this.language)
|
||||
.then((res) => {
|
||||
const rawList = Array.isArray(res && res.data && res.data.data)
|
||||
? res.data.data
|
||||
: Array.isArray(res && res.data)
|
||||
? res.data
|
||||
: []
|
||||
const matchedArea = this.pickAreaDetail(rawList)
|
||||
if (!matchedArea) {
|
||||
return
|
||||
}
|
||||
|
||||
const normalizedArea = normalizeAreaItem(matchedArea)
|
||||
this.storeData = {
|
||||
...this.storeData,
|
||||
...normalizedArea,
|
||||
roomTypes: (this.storeData.roomTypes || []).length ? (this.storeData.roomTypes || []) : normalizedArea.roomTypes,
|
||||
tags: (this.storeData.tags || []).length ? this.storeData.tags : normalizedArea.tags,
|
||||
facilities: (this.storeData.facilities || []).length ? this.storeData.facilities : normalizedArea.facilities
|
||||
}
|
||||
})
|
||||
},
|
||||
loadAreaManager() {
|
||||
const areaCode = this.storeData.code || this.storeData.areaCode || ''
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -178,7 +178,7 @@ const store = new Vuex.Store({
|
||||
? getMyPendingSignContracts({ OrderNo: currentOrderNo, ProviderType: 'WechatMiniProgram' }, language).catch((error) => logRequestError('getMyPendingSignContracts', error))
|
||||
: Promise.resolve([]),
|
||||
currentOrderNo
|
||||
? getMyEffectiveContracts({ OrderNo: currentOrderNo }, language).catch((error) => logRequestError('getMyEffectiveContracts', error))
|
||||
? getMyEffectiveContracts({ OrderNo: currentOrderNo, ProviderType: 'WechatMiniProgram' }, language).catch((error) => logRequestError('getMyEffectiveContracts', error))
|
||||
: Promise.resolve([])
|
||||
])
|
||||
}).then(([validOrders, currentOrder, pendingContractsResponse, effectiveContractsResponse]) => {
|
||||
|
||||
@@ -77,8 +77,8 @@ function buildRoomDataFromOrder(order = {}) {
|
||||
order.roomTypeName || order.roomType || ''
|
||||
].filter(Boolean).join(' · ')
|
||||
|
||||
const start = String(order.startDate || order.start || order.checkinDate || '').slice(0, 10)
|
||||
const end = String(order.endDate || order.end || order.checkoutDate || '').slice(0, 10)
|
||||
const start = String(order.contractStartTime || order.startDate || order.start || order.checkinDate || '').slice(0, 10)
|
||||
const end = String(order.contractEndTime || order.endDate || order.end || order.checkoutDate || '').slice(0, 10)
|
||||
|
||||
return {
|
||||
...order,
|
||||
|
||||
@@ -11,12 +11,8 @@ export function resolveExistingBookingNavigationTarget(stayState = {}) {
|
||||
const currentBooking = source.currentBooking || null
|
||||
const hasCheckedIn = source.hasCheckedIn === true
|
||||
|
||||
if (currentBooking && currentBooking.orderNo) {
|
||||
return `/pkgBooking/booking_detail/booking_detail?orderNo=${encodeURIComponent(currentBooking.orderNo)}`
|
||||
}
|
||||
|
||||
if (currentBooking) {
|
||||
return '/pkgBooking/booking_detail/booking_detail'
|
||||
return '/pages/index/index?pageId=stay-service'
|
||||
}
|
||||
|
||||
if (hasCheckedIn) {
|
||||
|
||||
@@ -13,11 +13,13 @@ function getGuardText(language = 'zh') {
|
||||
return {
|
||||
loginTitle: zh ? '提示' : 'Notice',
|
||||
loginContent: zh ? '您还未登录,请先登录或注册账号。' : 'Please sign in or register an account first.',
|
||||
loginCancelText: zh ? '取消' : 'Cancel',
|
||||
loginConfirmText: zh ? '去登录' : 'Sign In',
|
||||
existingBookingTitle: zh ? '已有进行中的预订' : 'Existing Booking',
|
||||
existingBookingContent: zh
|
||||
? '您当前状态为“{status}”,请先查看预订详情。'
|
||||
: 'Your current booking status is "{status}". Please review the booking detail first.',
|
||||
existingBookingCancelText: zh ? '取消' : 'Cancel',
|
||||
viewBooking: zh ? '查看预订' : 'View Booking'
|
||||
}
|
||||
}
|
||||
@@ -69,6 +71,7 @@ export function ensureBookingAccess(options = {}) {
|
||||
uni.showModal({
|
||||
title: options.loginTitle || guardText.loginTitle,
|
||||
content: options.loginContent || guardText.loginContent,
|
||||
cancelText: options.loginCancelText || guardText.loginCancelText,
|
||||
confirmText: options.loginConfirmText || guardText.loginConfirmText,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
@@ -89,6 +92,7 @@ export function ensureBookingAccess(options = {}) {
|
||||
uni.showModal({
|
||||
title: guardText.existingBookingTitle,
|
||||
content: guardText.existingBookingContent.replace('{status}', statusText),
|
||||
cancelText: guardText.existingBookingCancelText,
|
||||
confirmText: guardText.viewBooking,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
|
||||
@@ -348,8 +348,8 @@ export function normalizeBookingOrder(order = {}, language = getCurrentLanguage(
|
||||
const statusKey = normalizeStatusKey(safeOrder)
|
||||
const statusCode = getOrderStatusCode(safeOrder)
|
||||
const checkInPeriod = safeOrder.checkInPeriod || {}
|
||||
const startDate = safeOrder.startDate || safeOrder.start || safeOrder.checkinDate || checkInPeriod.start
|
||||
const endDate = safeOrder.endDate || safeOrder.end || safeOrder.checkoutDate || checkInPeriod.end
|
||||
const startDate = safeOrder.contractStartTime || safeOrder.startDate || safeOrder.start || safeOrder.checkinDate || checkInPeriod.start
|
||||
const endDate = safeOrder.contractEndTime || safeOrder.endDate || safeOrder.end || safeOrder.checkoutDate || checkInPeriod.end
|
||||
return {
|
||||
...safeOrder,
|
||||
orderNo: getOrderNo(safeOrder),
|
||||
|
||||
112
utils/contract-display.js
Normal file
112
utils/contract-display.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import { contractDetail } from '@/api/index.js'
|
||||
|
||||
const contractNameCache = Object.create(null)
|
||||
const contractDetailCache = Object.create(null)
|
||||
|
||||
function pickFirstText(...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 parseContractDetailResponse(response) {
|
||||
const payload = response && response.data ? response.data : response
|
||||
if (payload && payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) {
|
||||
return payload.data
|
||||
}
|
||||
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
||||
return payload
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
function resolveContractNameFromSource(source = {}) {
|
||||
return pickFirstText(
|
||||
source.contractDisplayName,
|
||||
source.contractName,
|
||||
source.contractTemplateName,
|
||||
source.contractTypeName,
|
||||
source.contractType,
|
||||
source.type,
|
||||
source.typeName,
|
||||
source.templateName,
|
||||
source.title
|
||||
)
|
||||
}
|
||||
|
||||
function resolveContractNo(source = {}) {
|
||||
return pickFirstText(source.no, source.contractNo)
|
||||
}
|
||||
|
||||
export function getContractDisplayName(source = {}, fallbackText = '') {
|
||||
const directName = resolveContractNameFromSource(source)
|
||||
if (directName) return directName
|
||||
|
||||
const contractNo = resolveContractNo(source)
|
||||
if (contractNo && contractNameCache[contractNo]) return contractNameCache[contractNo]
|
||||
|
||||
return fallbackText || ''
|
||||
}
|
||||
|
||||
export async function fetchContractDisplayInfo(contractNo = '', language) {
|
||||
const normalizedContractNo = String(contractNo || '').trim()
|
||||
if (!normalizedContractNo) {
|
||||
return {
|
||||
contractDisplayName: '',
|
||||
detail: {}
|
||||
}
|
||||
}
|
||||
|
||||
if (contractDetailCache[normalizedContractNo]) {
|
||||
const cachedDetail = contractDetailCache[normalizedContractNo]
|
||||
return {
|
||||
contractDisplayName: resolveContractNameFromSource(cachedDetail) || contractNameCache[normalizedContractNo] || '',
|
||||
detail: cachedDetail
|
||||
}
|
||||
}
|
||||
|
||||
const response = await contractDetail({ contractNo: normalizedContractNo }, language)
|
||||
const detail = parseContractDetailResponse(response)
|
||||
const contractDisplayName = resolveContractNameFromSource(detail)
|
||||
|
||||
contractDetailCache[normalizedContractNo] = detail
|
||||
contractNameCache[normalizedContractNo] = contractDisplayName
|
||||
|
||||
return {
|
||||
contractDisplayName,
|
||||
detail
|
||||
}
|
||||
}
|
||||
|
||||
export async function enrichContractDisplayNames(list = [], language) {
|
||||
const sourceList = Array.isArray(list) ? list : []
|
||||
const pendingNos = [...new Set(
|
||||
sourceList
|
||||
.filter(item => !resolveContractNameFromSource(item))
|
||||
.map(item => resolveContractNo(item))
|
||||
.filter(Boolean)
|
||||
)]
|
||||
|
||||
await Promise.all(
|
||||
pendingNos.map((contractNo) =>
|
||||
fetchContractDisplayInfo(contractNo, language).catch(() => ({
|
||||
contractDisplayName: '',
|
||||
detail: {}
|
||||
}))
|
||||
)
|
||||
)
|
||||
|
||||
return sourceList.map((item) => {
|
||||
const contractNo = resolveContractNo(item)
|
||||
return {
|
||||
...item,
|
||||
contractDisplayName: getContractDisplayName(item, '')
|
||||
|| (contractNo ? contractNameCache[contractNo] : '')
|
||||
|| ''
|
||||
}
|
||||
})
|
||||
}
|
||||
90
utils/deposit-other-payment.js
Normal file
90
utils/deposit-other-payment.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import { sysdictPage } from '@/api/index.js'
|
||||
|
||||
export const MOBILE_DEPOSIT_OTHER_PAYMENT_CONFIG_TYPE = 'MobileDepositOtherPaymentConfig'
|
||||
export const MOBILE_PROVIDER_TYPE = 'WechatMiniProgram'
|
||||
export const DEPOSIT_OTHER_PAYMENT_CONFIG_KEY = 'depositOtherPayment'
|
||||
|
||||
function normalizeText(value) {
|
||||
return String(value || '').trim()
|
||||
}
|
||||
|
||||
function parseJsonStringMaybe(value) {
|
||||
const text = normalizeText(value)
|
||||
if (!text) {
|
||||
return {}
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(text)
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeRows(response) {
|
||||
const payload = response && response.data !== undefined ? 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 getProviderType(row = {}) {
|
||||
return normalizeText(row.providerType || row.ProviderType)
|
||||
}
|
||||
|
||||
export function createDefaultDepositOtherPaymentConfig() {
|
||||
return {
|
||||
code: '',
|
||||
key: DEPOSIT_OTHER_PAYMENT_CONFIG_KEY,
|
||||
phone: '',
|
||||
qrCode: '',
|
||||
contentZh: '',
|
||||
contentEn: ''
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeDepositOtherPaymentConfig(row = {}) {
|
||||
const defaults = createDefaultDepositOtherPaymentConfig()
|
||||
const extra = parseJsonStringMaybe(row.extraInfo || row.extraConfig || row.ExtraConfig)
|
||||
|
||||
return {
|
||||
code: normalizeText(row.code),
|
||||
key: normalizeText(row.key) || defaults.key,
|
||||
phone: normalizeText(extra.phone || row.description),
|
||||
qrCode: normalizeText(extra.qrCode || extra.qrCodeImage || extra.qrImage),
|
||||
contentZh: normalizeText(extra.contentZh || extra.content || defaults.contentZh),
|
||||
contentEn: normalizeText(extra.contentEn || defaults.contentEn)
|
||||
}
|
||||
}
|
||||
|
||||
function findMatchedRow(rows = []) {
|
||||
const providerRows = rows.filter(item => getProviderType(item) === MOBILE_PROVIDER_TYPE)
|
||||
const legacyRows = rows.filter(item => !getProviderType(item))
|
||||
return (
|
||||
providerRows.find(item => normalizeText(item && item.key) === DEPOSIT_OTHER_PAYMENT_CONFIG_KEY)
|
||||
|| providerRows[0]
|
||||
|| legacyRows.find(item => normalizeText(item && item.key) === DEPOSIT_OTHER_PAYMENT_CONFIG_KEY)
|
||||
|| legacyRows[0]
|
||||
|| null
|
||||
)
|
||||
}
|
||||
|
||||
export function loadDepositOtherPaymentConfig(language) {
|
||||
return sysdictPage({
|
||||
Type: MOBILE_DEPOSIT_OTHER_PAYMENT_CONFIG_TYPE,
|
||||
ProviderType: MOBILE_PROVIDER_TYPE,
|
||||
PageIndex: 1,
|
||||
PageSize: 100
|
||||
}, language).then((response) => {
|
||||
const rows = normalizeRows(response)
|
||||
const matchedRow = findMatchedRow(rows)
|
||||
return normalizeDepositOtherPaymentConfig(matchedRow || {})
|
||||
})
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
const RESERVATION_FLOW_STEP = {
|
||||
APPLICATION: 1,
|
||||
INSPECTION: 2,
|
||||
BILL: 3,
|
||||
CONTRACT: 4,
|
||||
CONTRACT: 3,
|
||||
BILL: 4,
|
||||
CHECKIN: 5
|
||||
}
|
||||
|
||||
@@ -90,7 +90,70 @@ export function getReservationCurrentNode(flow = {}) {
|
||||
return null
|
||||
}
|
||||
|
||||
return nodes.find(item => item.isCurrent) || nodes[nodes.length - 1]
|
||||
const currentNodes = nodes.filter(item => item.isCurrent)
|
||||
if (currentNodes.length) {
|
||||
const currentCheckinNode = currentNodes.find(item => (
|
||||
Number(item.step || 0) === RESERVATION_FLOW_STEP.CHECKIN
|
||||
&& String(item.refNo || '').trim()
|
||||
))
|
||||
if (currentCheckinNode) {
|
||||
return currentCheckinNode
|
||||
}
|
||||
|
||||
// 后端偶发会把前序节点也标记成 isCurrent,优先取排序最靠后的当前节点
|
||||
return currentNodes[currentNodes.length - 1]
|
||||
}
|
||||
|
||||
const checkinNode = nodes.find(item => (
|
||||
Number(item.step || 0) === RESERVATION_FLOW_STEP.CHECKIN
|
||||
&& String(item.refNo || '').trim()
|
||||
))
|
||||
if (checkinNode) {
|
||||
return checkinNode
|
||||
}
|
||||
|
||||
return nodes[nodes.length - 1]
|
||||
}
|
||||
|
||||
export function getReservationCurrentFlow(flowList = []) {
|
||||
const flows = normalizeArray(flowList)
|
||||
.filter(item => item && typeof item === 'object')
|
||||
.map(item => ({
|
||||
flow: item,
|
||||
currentNode: getReservationCurrentNode(item)
|
||||
}))
|
||||
.filter(item => item.currentNode)
|
||||
|
||||
if (!flows.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const compareNode = (target) => {
|
||||
const node = target.currentNode || {}
|
||||
const step = Number(node.step || 0)
|
||||
const sort = Number(node.sort || 0)
|
||||
const refNo = String(node.refNo || '').trim()
|
||||
const isCurrent = !!node.isCurrent
|
||||
const isCheckinWithRefNo = step === RESERVATION_FLOW_STEP.CHECKIN && !!refNo
|
||||
return [
|
||||
isCheckinWithRefNo ? 1 : 0,
|
||||
isCurrent ? 1 : 0,
|
||||
step,
|
||||
sort
|
||||
]
|
||||
}
|
||||
|
||||
return flows
|
||||
.sort((a, b) => {
|
||||
const aScore = compareNode(a)
|
||||
const bScore = compareNode(b)
|
||||
for (let index = 0; index < aScore.length; index += 1) {
|
||||
if (aScore[index] !== bScore[index]) {
|
||||
return bScore[index] - aScore[index]
|
||||
}
|
||||
}
|
||||
return 0
|
||||
})[0].flow
|
||||
}
|
||||
|
||||
export function hasEffectiveReservationFlow(flow = {}) {
|
||||
@@ -149,7 +212,7 @@ export function getReservationStepMeta(step, language = 'zh') {
|
||||
},
|
||||
[RESERVATION_FLOW_STEP.BILL]: {
|
||||
label: currentLanguage === 'en' ? 'Bill' : '账单',
|
||||
actionText: currentLanguage === 'en' ? 'Go to Bill' : '去缴费'
|
||||
actionText: currentLanguage === 'en' ? 'Go to Payment' : '去支付'
|
||||
},
|
||||
[RESERVATION_FLOW_STEP.CONTRACT]: {
|
||||
label: currentLanguage === 'en' ? 'Contract' : '合同',
|
||||
|
||||
@@ -22,6 +22,11 @@ const BANNER_TEXT_FALLBACK_MAP = {
|
||||
}
|
||||
|
||||
const VALUE_TEXT_FALLBACK_MAP = {
|
||||
apartment: {
|
||||
'绿居·和山国际公寓': 'Luju Heshan Intl. Apartments',
|
||||
'和山国际公寓': 'Heshan Intl. Apartments',
|
||||
'乾晖里': 'Qianhui Residence'
|
||||
},
|
||||
tags: {
|
||||
'押一付一': 'Monthly Deposit',
|
||||
'大型超市': 'Large Supermarket',
|
||||
@@ -148,6 +153,10 @@ export function localizeHousingTag(tag = '', language = getCurrentLanguage()) {
|
||||
return localizeKnownValue('tags', tag, language)
|
||||
}
|
||||
|
||||
export function localizeApartmentName(name = '', language = getCurrentLanguage()) {
|
||||
return localizeKnownValue('apartment', name, language)
|
||||
}
|
||||
|
||||
export function localizePayMethodText(payMethod = '', language = getCurrentLanguage()) {
|
||||
return localizeKnownValue('payMethod', payMethod, language)
|
||||
}
|
||||
@@ -330,7 +339,19 @@ export function normalizeBannerItem(item = {}, index = 0) {
|
||||
|
||||
export function normalizeImageList(item = {}) {
|
||||
const candidates = []
|
||||
const listFields = [item.images, item.imageUrls, item.pictures, item.pictureList]
|
||||
const listFields = [
|
||||
item.images,
|
||||
item.imageUrls,
|
||||
item.pictures,
|
||||
item.pictureList,
|
||||
item.bannerImages,
|
||||
item.carouselImages,
|
||||
item.imageList,
|
||||
item.photos,
|
||||
item.photoList,
|
||||
item.album,
|
||||
item.albums
|
||||
]
|
||||
|
||||
listFields.forEach((field) => {
|
||||
if (Array.isArray(field)) {
|
||||
@@ -343,7 +364,19 @@ export function normalizeImageList(item = {}) {
|
||||
}
|
||||
})
|
||||
|
||||
;[item.imageUrl, item.image, item.cover, item.picture].forEach((image) => {
|
||||
;[
|
||||
item.imageUrl,
|
||||
item.image,
|
||||
item.cover,
|
||||
item.coverUrl,
|
||||
item.picture,
|
||||
item.poster,
|
||||
item.posterUrl,
|
||||
item.banner,
|
||||
item.bannerUrl,
|
||||
item.areaImage,
|
||||
item.areaImageUrl
|
||||
].forEach((image) => {
|
||||
const normalized = normalizeBannerImage(image)
|
||||
if (normalized) {
|
||||
candidates.push(normalized)
|
||||
|
||||
@@ -131,6 +131,7 @@ function buildEffectiveContractQuery(order = {}) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
ProviderType: 'WechatMiniProgram',
|
||||
OrderNo: orderNo
|
||||
}
|
||||
}
|
||||
|
||||
64
utils/todo-route.js
Normal file
64
utils/todo-route.js
Normal file
@@ -0,0 +1,64 @@
|
||||
function ensureArray(value) {
|
||||
return Array.isArray(value) ? value : []
|
||||
}
|
||||
|
||||
function pickFirstText() {
|
||||
for (let index = 0; index < arguments.length; index += 1) {
|
||||
const value = arguments[index]
|
||||
if (value === null || value === undefined) continue
|
||||
const text = String(value).trim()
|
||||
if (text) return text
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function normalizeNumber(value, fallback = 0) {
|
||||
const num = Number(value)
|
||||
return Number.isFinite(num) ? num : fallback
|
||||
}
|
||||
|
||||
function normalizeTodoEntry(raw) {
|
||||
if (raw && typeof raw === 'object') {
|
||||
return {
|
||||
...raw,
|
||||
_todoCode: pickFirstText(raw.code, raw.no, raw.contractNo, raw.billNo, raw.orderNo, raw.referenceTypeNo)
|
||||
}
|
||||
}
|
||||
const text = pickFirstText(raw)
|
||||
return text ? { code: text, _todoCode: text } : null
|
||||
}
|
||||
|
||||
function extractTodoEntries(item) {
|
||||
const sourceList = [item && item.items, item && item.codes, item && item.list, item && item.data]
|
||||
for (let index = 0; index < sourceList.length; index += 1) {
|
||||
const current = sourceList[index]
|
||||
if (!Array.isArray(current)) continue
|
||||
return current.map(entry => normalizeTodoEntry(entry)).filter(Boolean)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export function extractTodoRows(response) {
|
||||
const payload = response && response.data !== undefined ? response.data : response
|
||||
if (Array.isArray(payload)) return payload
|
||||
if (!payload || typeof payload !== 'object') return []
|
||||
if (Array.isArray(payload.data)) return payload.data
|
||||
if (payload.data && Array.isArray(payload.data.data)) return payload.data.data
|
||||
return []
|
||||
}
|
||||
|
||||
export function normalizeTodoList(rows = []) {
|
||||
return ensureArray(rows).map((item) => {
|
||||
const entries = extractTodoEntries(item)
|
||||
const count = Math.max(normalizeNumber(item && item.count, entries.length), entries.length)
|
||||
return {
|
||||
todoType: String(item && item.todoType !== undefined ? item.todoType : ''),
|
||||
todoTypeDesc: pickFirstText(item && item.todoTypeDesc),
|
||||
count,
|
||||
entries,
|
||||
codes: entries
|
||||
.map(entry => ({ code: pickFirstText(entry && entry._todoCode) }))
|
||||
.filter(entry => entry.code)
|
||||
}
|
||||
}).filter(item => item.count > 0 || item.entries.length > 0 || item.todoTypeDesc)
|
||||
}
|
||||
Reference in New Issue
Block a user