refactor: 国际化文本迁移至全局 locales/messages.js

将组件级 i18n 对象重构为统一由全局 locales/messages.js 管理,确保中英文语言切换时正确显示对应文本。

- 新增 utils/i18n.js 国际化工具函数
- 更新 store/$t.mixin.js 翻译混入逻辑
- 迁移 pkgService、pkgBooking、pkgAccount、pkgExplore、pages 等共 58 个组件的中英文文本至全局消息表
This commit is contained in:
liangjian
2026-07-24 13:43:41 +08:00
parent 445bd850f4
commit 6e721ccc7e
58 changed files with 1810 additions and 4065 deletions

View File

@@ -1,12 +1,12 @@
<template>
<view class="message-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="false" :bottomShadow="false" backgroundColor="transparent" :customBackStyle="{ transition: 'all 0.3s ease' }">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('title') }}</view>
<view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('消息中心') }}</view>
</tn-nav-bar>
<view class="message-content tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
<view class="page-header tn-margin-bottom-xl">
<view class="tn-text-bold tn-color-black page-title">{{ t('title') }}</view>
<view class="tn-text-bold tn-color-black page-title">{{ $tr('消息中心') }}</view>
<view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ subtitleText }}</view>
</view>
@@ -16,31 +16,31 @@
<text class="tn-icon-notice-fill tn-text-xxl"></text>
<view v-if="stats.systemUnread" class="badge tn-flex tn-flex-col-center tn-flex-row-center">{{ stats.systemUnread }}</view>
</view>
<view class="tn-text-sm tn-margin-top-sm tn-text-bold tn-color-black">{{ t('system') }}</view>
<view class="tn-text-sm tn-margin-top-sm tn-text-bold tn-color-black">{{ $tr('系统通知') }}</view>
</view>
<view class="category-item tn-flex tn-flex-direction-column tn-flex-col-center">
<view class="category-icon-box normal-box">
<text class="tn-icon-coupon-fill tn-text-xxl"></text>
<view v-if="stats.paymentUnread" class="badge tn-flex tn-flex-col-center tn-flex-row-center">{{ stats.paymentUnread }}</view>
</view>
<view class="tn-text-sm tn-margin-top-sm tn-text-bold tn-color-gray">{{ t('payment') }}</view>
<view class="tn-text-sm tn-margin-top-sm tn-text-bold tn-color-gray">{{ $tr('缴费账单') }}</view>
</view>
<view class="category-item tn-flex tn-flex-direction-column tn-flex-col-center">
<view class="category-icon-box normal-box">
<text class="tn-icon-service-fill tn-text-xxl"></text>
<view v-if="stats.butlerUnread" class="badge tn-flex tn-flex-col-center tn-flex-row-center">{{ stats.butlerUnread }}</view>
</view>
<view class="tn-text-sm tn-margin-top-sm tn-text-bold tn-color-gray">{{ t('butler') }}</view>
<view class="tn-text-sm tn-margin-top-sm tn-text-bold tn-color-gray">{{ $tr('管家消息') }}</view>
</view>
</view>
<view v-if="inspectionList.length" class="section-card tn-margin-bottom">
<view class="section-head">
<view>
<view class="section-title">{{ t('inspectionTitle') }}</view>
<view class="section-desc">{{ t('inspectionDesc') }}</view>
<view class="section-title">{{ $tr('巡查提醒') }}</view>
<view class="section-desc">{{ $tr('根据当前入住订单自动汇总巡查提醒记录') }}</view>
</view>
<view class="section-chip is-primary">{{ inspectionReminderCount }} {{ t('inspectionCountUnit') }}</view>
<view class="section-chip is-primary">{{ inspectionReminderCount }} {{ $tr('条提醒') }}</view>
</view>
<view
@@ -56,7 +56,7 @@
</view>
<view class="inspection-main">
<view class="inspection-title">{{ record.typeText }}</view>
<view class="inspection-meta">{{ record.roomCode || t('unassignedRoom') }} · {{ record.timeText }}</view>
<view class="inspection-meta">{{ record.roomCode || $tr('未关联房间') }} · {{ record.timeText }}</view>
</view>
</view>
<view class="inspection-chips">
@@ -66,23 +66,23 @@
</view>
<view class="inspection-desc">{{ record.summaryText }}</view>
<view class="inspection-footer">
<view class="inspection-footer-text">{{ t('checkerLabel') }}{{ record.checkerUserCode || '--' }}</view>
<view class="inspection-action">{{ t('viewDetail') }}</view>
<view class="inspection-footer-text">{{ $tr('检查人:') }}{{ record.checkerUserCode || '--' }}</view>
<view class="inspection-action">{{ $tr('查看详情') }}</view>
</view>
</view>
</view>
<view class="tn-flex tn-flex-row-between tn-flex-col-bottom tn-margin-bottom">
<view class="tn-text-bold tn-text-lg tn-color-black">{{ t('recentMessages') }}</view>
<view class="tn-text-bold tn-text-lg tn-color-black">{{ $tr('近期消息') }}</view>
<view class="tn-text-sm tn-color-gray" @click="handleReadAll">
<text class="tn-icon-delete tn-margin-right-xs"></text>{{ t('markNotificationRead') }}
<text class="tn-icon-delete tn-margin-right-xs"></text>{{ $tr('通知已读') }}
</view>
</view>
<view class="msg-list">
<view v-if="loading" class="empty-card">
<view class="tn-text-bold tn-text-lg tn-color-black">{{ t('loadingTitle') }}</view>
<view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ t('loadingDesc') }}</view>
<view class="tn-text-bold tn-text-lg tn-color-black">{{ $tr('消息加载中') }}</view>
<view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ $tr('正在同步系统通知和巡查提醒,请稍候。') }}</view>
</view>
<view
v-for="(msg, index) in messages"
@@ -103,8 +103,8 @@
</view>
</view>
<view v-if="!loading && !messages.length && !inspectionList.length" class="empty-card">
<view class="tn-text-bold tn-text-lg tn-color-black">{{ t('emptyTitle') }}</view>
<view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ t('emptyDesc') }}</view>
<view class="tn-text-bold tn-text-lg tn-color-black">{{ $tr('暂无消息') }}</view>
<view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ $tr('系统通知和巡查提醒会在这里显示。') }}</view>
</view>
</view>
@@ -117,7 +117,7 @@
<view class="inspect-popup-head">
<view>
<view class="inspect-popup-title">{{ activeInspectionRecord.typeText }}</view>
<view class="inspect-popup-subtitle">{{ activeInspectionRecord.roomCode || t('unassignedRoom') }} · {{ activeInspectionRecord.timeText }}</view>
<view class="inspect-popup-subtitle">{{ activeInspectionRecord.roomCode || $tr('未关联房间') }} · {{ activeInspectionRecord.timeText }}</view>
</view>
<view class="inspect-popup-close" @click="closeInspectionPopup">
<text class="tn-icon-close"></text>
@@ -127,39 +127,39 @@
<scroll-view scroll-y class="inspect-popup-body">
<view class="inspect-detail-card">
<view class="inspect-detail-row">
<text class="inspect-detail-label">{{ t('inspectionChecker') }}</text>
<text class="inspect-detail-label">{{ $tr('检查人') }}</text>
<text class="inspect-detail-value">{{ activeInspectionRecord.checkerUserCode || '--' }}</text>
</view>
<view class="inspect-detail-row">
<text class="inspect-detail-label">{{ t('inspectionResult') }}</text>
<text class="inspect-detail-label">{{ $tr('检查结果') }}</text>
<text class="inspect-detail-value">{{ activeInspectionRecord.resultText }}</text>
</view>
<view class="inspect-detail-row">
<text class="inspect-detail-label">{{ t('inspectionStatus') }}</text>
<text class="inspect-detail-label">{{ $tr('当前状态') }}</text>
<text class="inspect-detail-value">{{ activeInspectionRecord.statusText }}</text>
</view>
</view>
<view v-if="activeInspectionRecord.remark" class="inspect-block">
<view class="inspect-block-title">{{ t('inspectionRemarkTitle') }}</view>
<view class="inspect-block-title">{{ $tr('检查备注') }}</view>
<view class="inspect-block-content">{{ activeInspectionRecord.remark }}</view>
</view>
<view v-if="activeInspectionRecord.items.length" class="inspect-block">
<view class="inspect-block-title">{{ t('inspectionItemsTitle') }}</view>
<view class="inspect-block-title">{{ $tr('检查项明细') }}</view>
<view
v-for="(detail, index) in activeInspectionRecord.items"
:key="detail.code || index"
class="inspect-item"
>
<view class="inspect-item-head">
<view class="inspect-item-title">{{ t('inspectionItemPrefix', { index: index + 1 }) }}</view>
<view class="inspect-item-title">{{ $tr('检查项 {index}', { index: index + 1 }) }}</view>
<view class="inspect-item-tag">{{ getCheckTypeLabel(detail.checkType) }}</view>
</view>
<view v-if="detail.problemDescription" class="inspect-item-text">{{ t('problemLabel') }}{{ detail.problemDescription }}</view>
<view v-if="detail.remark" class="inspect-item-text">{{ t('remarkLabel') }}{{ detail.remark }}</view>
<view v-if="detail.problemDescription" class="inspect-item-text">{{ $tr('问题:') }}{{ detail.problemDescription }}</view>
<view v-if="detail.remark" class="inspect-item-text">{{ $tr('备注:') }}{{ detail.remark }}</view>
<view v-if="detail.compensationAmount !== '' && detail.compensationAmount !== null && detail.compensationAmount !== undefined" class="inspect-item-text">
{{ t('compensationLabel') }}{{ detail.compensationAmount }}
{{ $tr('赔付金额:') }}{{ detail.compensationAmount }}
</view>
<view v-if="detail.imageUrls.length" class="inspect-image-list">
<image
@@ -176,7 +176,7 @@
</scroll-view>
<view class="inspect-popup-foot">
<view class="inspect-popup-btn" @click="closeInspectionPopup">{{ t('acknowledged') }}</view>
<view class="inspect-popup-btn" @click="closeInspectionPopup">{{ $tr('我知道了') }}</view>
</view>
</view>
</view>
@@ -214,104 +214,6 @@ export default {
paymentUnread: 0,
butlerUnread: 0
},
i18n: {
zh: {
title: '消息中心',
subtitle: '你有 {count} 条未读消息待处理',
system: '系统通知',
payment: '缴费账单',
butler: '管家消息',
inspectionTitle: '巡查提醒',
inspectionDesc: '根据当前入住订单自动汇总巡查提醒记录',
inspectionCountUnit: '条提醒',
recentMessages: '近期消息',
markNotificationRead: '通知已读',
loadingTitle: '消息加载中',
loadingDesc: '正在同步系统通知和巡查提醒,请稍候。',
emptyTitle: '暂无消息',
emptyDesc: '系统通知和巡查提醒会在这里显示。',
noUnreadNotification: '暂无未读通知',
inspectionNeedManualHandle: '巡查提醒需单独查看处理',
remoteMarkedRead: '系统通知已全部标记为已读',
remoteMarkedReadWithInspection: '系统通知已标记,巡查提醒需单独处理',
unassignedRoom: '未关联房间',
checkerLabel: '检查人:',
viewDetail: '查看详情',
inspectionChecker: '检查人',
inspectionResult: '检查结果',
inspectionStatus: '当前状态',
inspectionRemarkTitle: '检查备注',
inspectionItemsTitle: '检查项明细',
inspectionItemPrefix: '检查项 {index}',
problemLabel: '问题:',
remarkLabel: '备注:',
compensationLabel: '赔付金额:',
acknowledged: '我知道了',
notificationFallbackTitle: '系统通知',
notificationFallbackDesc: '您有一条新的系统消息',
inspectionSummaryFallback: '巡查提醒,请查看详情',
checkTypeRoutine: '日常检查',
checkTypeCheckout: '退房检查',
checkTypeSpecial: '专项检查',
checkTypeDefault: '检查记录',
checkStatusPending: '待确认',
checkStatusProcessing: '处理中',
checkStatusDone: '已完成',
checkStatusDefault: '状态待定',
checkResultNormal: '正常',
checkResultFix: '需整改',
checkResultReview: '待复核',
checkResultDefault: '检查结果'
},
en: {
title: 'Messages',
subtitle: 'You have {count} unread messages',
system: 'System',
payment: 'Payment',
butler: 'Butler',
inspectionTitle: 'Inspection',
inspectionDesc: 'Inspection reminders are summarized automatically.',
inspectionCountUnit: 'alerts',
recentMessages: 'Recent',
markNotificationRead: 'Read Notifications',
loadingTitle: 'Loading Messages',
loadingDesc: 'Syncing notifications and inspections.',
emptyTitle: 'No Messages',
emptyDesc: 'Notifications and inspection reminders will appear here.',
noUnreadNotification: 'No unread notifications',
inspectionNeedManualHandle: 'Inspection reminders need manual handling',
remoteMarkedRead: 'Notifications marked as read',
remoteMarkedReadWithInspection: 'Notifications marked as read, inspection reminders still need handling',
unassignedRoom: 'Room Unlinked',
checkerLabel: 'Inspector: ',
viewDetail: 'Details',
inspectionChecker: 'Inspector',
inspectionResult: 'Result',
inspectionStatus: 'Status',
inspectionRemarkTitle: 'Inspection Remark',
inspectionItemsTitle: 'Inspection Items',
inspectionItemPrefix: 'Item {index}',
problemLabel: 'Problem: ',
remarkLabel: 'Remark: ',
compensationLabel: 'Compensation: ',
acknowledged: 'Got It',
notificationFallbackTitle: 'System Notification',
notificationFallbackDesc: 'You have a new system message',
inspectionSummaryFallback: 'Inspection reminder, tap to view details.',
checkTypeRoutine: 'Routine Check',
checkTypeCheckout: 'Checkout Check',
checkTypeSpecial: 'Special Check',
checkTypeDefault: 'Inspection Record',
checkStatusPending: 'Pending',
checkStatusProcessing: 'Processing',
checkStatusDone: 'Completed',
checkStatusDefault: 'Status Pending',
checkResultNormal: 'Normal',
checkResultFix: 'Needs Fix',
checkResultReview: 'Pending Review',
checkResultDefault: 'Inspection Result'
}
}
}
},
computed: {
@@ -319,7 +221,7 @@ export default {
return this.$getLanguage()
},
subtitleText() {
const text = this.t('subtitle').replace('{count}', this.stats.totalUnread)
const text = this.$tr('你有 {count} 条未读消息待处理').replace('{count}', this.stats.totalUnread)
return this.language === 'en' ? compactEnglishText(text, 48) : text
},
inspectionReminderCount() {
@@ -338,9 +240,6 @@ export default {
load(force = false) {
return this.refreshMessages(force)
},
t(key, params) {
return this.$tm('pages.index.component.MessageCenterTab', key, params)
},
getStoreUser() {
return (this.$store && this.$store.state && this.$store.state.vuex_user) || {}
},
@@ -471,15 +370,15 @@ export default {
id: item.code || item.id || `remote-${Date.now()}`,
code: item.code || '',
category: this.resolveRemoteCategory(item, title, desc),
titleZh: rawTitleZh || this.t('notificationFallbackTitle'),
titleEn: rawTitleEn || this.t('notificationFallbackTitle'),
descZh: rawDescZh || this.t('notificationFallbackDesc'),
descEn: rawDescEn || this.t('notificationFallbackDesc'),
contentZh: rawDescZh || this.t('notificationFallbackDesc'),
contentEn: rawDescEn || this.t('notificationFallbackDesc'),
title: title || this.t('notificationFallbackTitle'),
desc: desc || this.t('notificationFallbackDesc'),
content: desc || this.t('notificationFallbackDesc'),
titleZh: rawTitleZh || this.$tr('系统通知'),
titleEn: rawTitleEn || this.$tr('系统通知'),
descZh: rawDescZh || this.$tr('您有一条新的系统消息'),
descEn: rawDescEn || this.$tr('您有一条新的系统消息'),
contentZh: rawDescZh || this.$tr('您有一条新的系统消息'),
contentEn: rawDescEn || this.$tr('您有一条新的系统消息'),
title: title || this.$tr('系统通知'),
desc: desc || this.$tr('您有一条新的系统消息'),
content: desc || this.$tr('您有一条新的系统消息'),
time: formatMessageDateTime(item.createTime || item.updateTime || ''),
rawTime: item.createTime || item.updateTime || '',
unread: item.isRead !== true,
@@ -501,7 +400,7 @@ export default {
}))
: []
const issueTexts = items.map(item => item.problemDescription || item.remark || '').filter(Boolean)
const summaryText = record.remark || issueTexts[0] || this.t('inspectionSummaryFallback')
const summaryText = record.remark || issueTexts[0] || this.$tr('巡查提醒,请查看详情')
return {
code: record.code || '',
roomCode: record.roomCode || '',
@@ -545,27 +444,27 @@ export default {
},
getCheckTypeLabel(value) {
const map = {
0: this.t('checkTypeRoutine'),
1: this.t('checkTypeCheckout'),
2: this.t('checkTypeSpecial')
0: this.$tr('日常检查'),
1: this.$tr('退房检查'),
2: this.$tr('专项检查')
}
return map[Number(value)] || this.t('checkTypeDefault')
return map[Number(value)] || this.$tr('检查记录')
},
getCheckRecordStatusLabel(value) {
const map = {
0: this.t('checkStatusPending'),
1: this.t('checkStatusProcessing'),
2: this.t('checkStatusDone')
0: this.$tr('待确认'),
1: this.$tr('处理中'),
2: this.$tr('已完成')
}
return map[Number(value)] || this.t('checkStatusDefault')
return map[Number(value)] || this.$tr('状态待定')
},
getCheckResultStatusLabel(value) {
const map = {
0: this.t('checkResultNormal'),
1: this.t('checkResultFix'),
2: this.t('checkResultReview')
0: this.$tr('正常'),
1: this.$tr('需整改'),
2: this.$tr('待复核')
}
return map[Number(value)] || this.t('checkResultDefault')
return map[Number(value)] || this.$tr('检查结果')
},
getCheckResultStatusClass(value) {
const map = {
@@ -618,8 +517,8 @@ export default {
if (!unreadRemoteCodes.length) {
uni.showToast({
title: this.inspectionReminderCount
? this.t('inspectionNeedManualHandle')
: this.t('noUnreadNotification'),
? this.$tr('巡查提醒需单独查看处理')
: this.$tr('暂无未读通知'),
icon: 'none'
})
return
@@ -642,8 +541,8 @@ export default {
this.buildMessageView()
uni.showToast({
title: this.inspectionReminderCount
? this.t('remoteMarkedReadWithInspection')
: this.t('remoteMarkedRead'),
? this.$tr('系统通知已标记,巡查提醒需单独处理')
: this.$tr('系统通知已全部标记为已读'),
icon: 'none'
})
},

View File

@@ -1,19 +1,19 @@
<template>
<view class="my-application-page tn-flex tn-flex-direction-column" :style="{ height: 'calc(100vh - 100rpx - env(safe-area-inset-bottom))', boxSizing: 'border-box', paddingTop: (vuex_custom_bar_height) + 'px' }">
<view class="page-header tn-padding">
<view class="tn-text-bold tn-text-xl tn-color-black page-title">{{ t('pageTitle') }}</view>
<view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ t('pageDesc') }}</view>
<view class="tn-text-bold tn-text-xl tn-color-black page-title">{{ $tr('我的申请') }}</view>
<view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ $tr('您的入住和预约看房申请记录') }}</view>
</view>
<scroll-view v-if="reservationOrders.length || list.length" scroll-y class="list-scroll-view tn-padding" @scrolltolower="loadMore">
<view v-if="reservationOrders.length" class="reservation-flow-panel tn-margin-bottom-lg" @click.stop>
<view class="reservation-flow-panel__head">
<view class="reservation-flow-panel__title-wrap">
<view class="reservation-flow-panel__title">{{ t('reservationFlowTitle') }}</view>
<view class="reservation-flow-panel__desc">{{ t('reservationFlowDesc') }}</view>
<view class="reservation-flow-panel__title">{{ $tr('预定流程') }}</view>
<view class="reservation-flow-panel__desc">{{ $tr('当前预定单的流程进度') }}</view>
</view>
<view class="reservation-flow-panel__refresh" @click.stop="refreshStatus">
<text class="tn-text-bold">{{ t('refreshFlowAction') }}</text>
<text class="tn-text-bold">{{ $tr('刷新流程') }}</text>
</view>
</view>
@@ -26,26 +26,26 @@
:class="{ 'reservation-order-chip--active': activeReservationOrderNo === item.orderNo }"
@click.stop="switchReservationOrder(item)"
>
<view class="reservation-order-chip__title">{{ item.roomTypeText || item.roomTypeName || t('reservationOrderFallback') }}</view>
<view class="reservation-order-chip__title">{{ item.roomTypeText || item.roomTypeName || $tr('预定单') }}</view>
</view>
</view>
</scroll-view>
<view v-if="activeReservationOrder" class="reservation-grid-info tn-margin-top-lg">
<view class="grid-info-item">
<view class="grid-info-label">{{ t('apartmentLabel') }}</view>
<view class="grid-info-label">{{ $tr('所属公寓') }}</view>
<view class="grid-info-value">{{ activeReservationOrder.areaText || activeReservationOrder.areaName || '--' }}</view>
</view>
<view class="grid-info-item">
<view class="grid-info-label">{{ t('reservationRoomLabel') }}</view>
<view class="grid-info-label">{{ $tr('房型') }}</view>
<view class="grid-info-value">{{ activeReservationOrder.roomTypeText || activeReservationOrder.roomTypeName || '--' }}</view>
</view>
<view class="grid-info-item">
<view class="grid-info-label">{{ t('roomNoLabel') }}</view>
<view class="grid-info-label">{{ $tr('房间号') }}</view>
<view class="grid-info-value tn-font-mono">{{ activeReservationOrder.roomNoText || activeReservationOrder.roomNo || '--' }}</view>
</view>
<view class="grid-info-item">
<view class="grid-info-label">{{ t('reservationStatusLabel') }}</view>
<view class="grid-info-label">{{ $tr('订单状态') }}</view>
<view class="grid-info-value">{{ activeReservationOrder.orderStatusDesc || activeReservationOrder.statusText || '--' }}</view>
</view>
</view>
@@ -67,7 +67,7 @@
<view class="v-step-content">
<view class="v-step-header">
<view class="v-step-title">{{ node.stepDesc || getReservationStepLabel(node.step) }}</view>
<view v-if="node.isCurrent" class="v-step-current-tag">{{ t('currentStepTag') }}</view>
<view v-if="node.isCurrent" class="v-step-current-tag">{{ $tr('当前') }}</view>
</view>
<view
@@ -75,11 +75,11 @@
class="v-step-bill-card tn-margin-top-sm"
>
<view class="v-step-bill-row">
<text class="v-step-bill-label">{{ t('pendingPaymentDescLabel') }}</text>
<text class="v-step-bill-label">{{ $tr('支付说明') }}</text>
<text class="v-step-bill-value">{{ getReservationBillDisplayTitle(getActiveReservationBillDetail()) }}</text>
</view>
<view class="v-step-bill-row">
<text class="v-step-bill-label">{{ t('pendingPaymentAmountLabel') }}</text>
<text class="v-step-bill-label">{{ $tr('订单金额') }}</text>
<text class="v-step-bill-value v-step-bill-value--price">¥{{ formatMoney(getReservationBillDisplayAmount(getActiveReservationBillDetail())) }}</text>
</view>
</view>
@@ -96,7 +96,7 @@
class="v-step-action tn-margin-top-sm"
>
<view class="step-refresh-btn" @click.stop="refreshStatus">
<text class="tn-text-bold">{{ t('refreshAction') }}</text>
<text class="tn-text-bold">{{ $tr('刷新') }}</text>
</view>
</view>
@@ -132,57 +132,57 @@
<view class="card-body tn-margin-top-lg">
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('apartmentLabel') }}</text>
<text class="info-label">{{ $tr('所属公寓') }}</text>
<text class="info-value">{{ item.intendedAreaName || '--' }}</text>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('occupantLabel') }}</text>
<text class="info-label">{{ $tr('入住人') }}</text>
<text class="info-value">{{ item.userName || item.phone || '--' }}</text>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ item.isViewingApplication ? t('viewingTimeLabel') : t('leaseTermLabel') }}</text>
<text class="info-label">{{ item.isViewingApplication ? $tr('预约时间') : $tr('预计租期') }}</text>
<text class="info-value">{{ formatDateRange(item.start, item.end) }}</text>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('submittedAtLabel') }}</text>
<text class="info-label">{{ $tr('提交时间') }}</text>
<text class="info-value">{{ formatDateTime(item.createTime) }}</text>
</view>
</view>
<view v-if="item.remark" class="remark-panel tn-margin-top-lg">
<view class="remark-line">
<text class="remark-label">{{ t('remarkLabel') }}</text>
<text class="remark-label">{{ $tr('补充说明') }}</text>
<text class="remark-text">{{ item.remark }}</text>
</view>
</view>
<view v-if="getStatusReminderText(item)" class="reminder-panel tn-margin-top-lg" @click.stop>
<view class="reminder-head">
<view class="reminder-title">{{ t('statusReminderTitle') }}</view>
<view class="reminder-title">{{ $tr('当前进度提醒') }}</view>
<view class="reminder-refresh-btn" @click.stop="refreshStatus">
<text class="tn-text-bold">{{ t('refreshAction') }}</text>
<text class="tn-text-bold">{{ $tr('刷新') }}</text>
</view>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('statusReminderTitle') }}</text>
<text class="info-label">{{ $tr('当前进度提醒') }}</text>
<text class="info-value">{{ getStatusReminderText(item) }}</text>
</view>
</view>
<view v-if="!isPaidApplication(item) && getApplicationPendingPaymentOrder(item)" class="pending-payment-panel tn-margin-top-lg" @click.stop>
<view class="pending-payment-title">{{ t('pendingPaymentTitle') }}</view>
<view class="pending-payment-title">{{ $tr('未完成支付订单') }}</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('pendingPaymentAmountLabel') }}</text>
<text class="info-label">{{ $tr('订单金额') }}</text>
<text class="info-value info-value--highlight">¥{{ formatMoney(getApplicationPendingPaymentAmount(item)) }}</text>
</view>
<view v-if="getApplicationPendingPaymentExpireText(item)" class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('pendingPaymentExpireLabel') }}</text>
<text class="info-label">{{ $tr('剩余支付时间') }}</text>
<text class="info-value" :class="{ 'info-value--expired': isApplicationPendingPaymentExpired(item) }">
{{ getApplicationPendingPaymentExpireText(item) }}
</text>
</view>
<view v-if="getApplicationPendingPaymentDescription(item)" class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('pendingPaymentDescLabel') }}</text>
<text class="info-label">{{ $tr('支付说明') }}</text>
<text class="info-value">{{ getApplicationPendingPaymentDescription(item) }}</text>
</view>
</view>
@@ -223,36 +223,36 @@
:data-reference-type-no="resolveReferenceTypeNo(item)"
@click.stop="payDepositAction"
>
<text class="tn-text-bold">{{ t('payDeposit') }}</text>
<text class="tn-text-bold">{{ $tr('支付定金') }}</text>
</view>
</view>
</view>
<view v-if="hasMore || loadingMore" class="load-more-item tn-flex tn-flex-direction-column tn-flex-col-center tn-flex-row-center tn-padding-bottom">
<tn-loading v-if="loadingMore" mode="circle" color="#00BBE8"></tn-loading>
<text v-else-if="hasMore" class="tn-color-gray">{{ t('pullUpMore') }}</text>
<text v-else class="tn-color-gray">{{ t('noMore') }}</text>
<text v-else-if="hasMore" class="tn-color-gray">{{ $tr('上拉加载更多') }}</text>
<text v-else class="tn-color-gray">{{ $tr('没有更多申请了') }}</text>
</view>
</scroll-view>
<view v-else-if="!loading" class="empty-panel tn-flex tn-flex-direction-column tn-flex-col-center tn-flex-row-center animation-fade-in tn-padding-left tn-padding-right">
<tn-empty icon="order" :text="t('emptyTitle')"></tn-empty>
<view class="empty-desc tn-margin-top tn-text-center">{{ t('emptyDesc') }}</view>
<tn-empty icon="order" :text="$tr('暂无申请记录')"></tn-empty>
<view class="empty-desc tn-margin-top tn-text-center">{{ $tr('您还没有提交任何申请,去选房后可发起入住申请或预约看房。') }}</view>
<view class="empty-action tn-margin-top-xl" @click="goSelectRoom">
<text class="tn-text-bold">{{ t('goSelectRoom') }}</text>
<text class="tn-text-bold">{{ $tr('去选房') }}</text>
</view>
</view>
<view v-if="loading && !list.length" class="empty-panel tn-flex tn-flex-direction-column tn-flex-col-center tn-flex-row-center tn-padding-left tn-padding-right">
<tn-loading mode="circle" color="#00BBE8" size="60"></tn-loading>
<view class="empty-desc tn-margin-top-lg">{{ t('loadingDesc') }}</view>
<view class="empty-desc tn-margin-top-lg">{{ $tr('正在同步您的入住申请记录,请稍候。') }}</view>
</view>
<!-- 支付定金须知弹窗 -->
<tn-popup v-model="showDepositNotice" mode="bottom" length="80%" :borderRadius="32" :closeBtn="true" @close="closeDepositNotice">
<view class="deposit-notice-popup">
<view class="popup-header">
<view class="popup-title">{{ t('depositNoticeTitle') }}</view>
<view class="popup-title">{{ $tr('租房定金须知') }}</view>
</view>
<scroll-view scroll-y class="popup-body" @scrolltolower="onNoticeScrollToBottom">
<view class="notice-content">
@@ -288,8 +288,8 @@
:class="{ 'action-btn--disabled': !hasReadNotice }"
@click="confirmAndPayDeposit"
>
<text class="tn-text-bold">{{ t('agreeAndPay') }}</text>
<text v-if="!hasReadNotice" class="tn-margin-left-xs tn-text-sm">({{ t('scrollToRead') }})</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>
@@ -408,156 +408,6 @@ function padCountdownUnit(value) {
}
export default {
i18n: {
zh: {
pageTitle: '我的申请',
pageDesc: '您的入住和预约看房申请记录',
toolbarSummary: '共 {total} 条申请',
refreshing: '刷新中...',
refresh: '刷新状态',
apartmentLabel: '所属公寓',
occupantLabel: '入住人',
viewingTimeLabel: '预约时间',
leaseTermLabel: '预计租期',
submittedAtLabel: '提交时间',
remarkLabel: '补充说明',
payDeposit: '支付定金',
continuePay: '继续支付',
pendingPaymentTitle: '未完成支付订单',
pendingPaymentOrderNoLabel: '支付单号',
pendingPaymentAmountLabel: '订单金额',
pendingPaymentDescLabel: '支付说明',
pendingPaymentExpireLabel: '剩余支付时间',
pendingPaymentExpired: '已失效',
noMore: '没有更多申请了',
pullUpMore: '上拉加载更多',
emptyTitle: '暂无申请记录',
emptyDesc: '您还没有提交任何申请,去选房后可发起入住申请或预约看房。',
goSelectRoom: '去选房',
loadingDesc: '正在同步您的入住申请记录,请稍候。',
statusPending: '待审核',
statusApproved: '已通过',
statusRejected: '已拒绝',
statusFinished: '已结束',
statusProcessing: '处理中',
statusPendingPayment: '请支付定金',
statusWaitingRoomAssign: '等待预订中心分配房间',
statusReminderTitle: '当前进度提醒',
refreshEmptySuccess: '未查到申请单,已同步预订单',
refreshSuccess: '申请状态已刷新',
loadError: '申请记录加载失败',
cancelOrder: '取消订单',
cancelOrderLoading: '取消中...',
cancelOrderTitle: '取消订单',
cancelOrderContent: '取消后将关闭当前未支付的定金订单,是否继续?',
cancelOrderSuccess: '订单已取消',
cancelOrderFailed: '取消订单失败',
viewingApplicationTitle: '预约看房申请',
stayApplicationTitle: '入住申请',
missingBillCode: '申请单编号缺失',
missingContractNo: '合同编号缺失',
missingApplicationCode: '申请编号缺失',
dateRangeJoiner: ' 至 ',
depositNoticeTitle: '租房定金须知',
agreeAndPay: '同意并支付',
scrollToRead: '请滑动阅读至底部',
fetchPaymentParamsFailed: '获取支付参数失败',
fetchWechatLoginCodeFailed: '获取微信登录凭证失败',
loadingBill: '正在获取账单...',
noPendingDepositBill: '暂无待支付定金账单',
loadingPayment: '正在拉起支付...',
depositPaymentDesc: '支付定金',
paymentSuccess: '支付成功',
paymentCanceled: '已取消支付',
paymentFailed: '支付失败',
reservationFlowTitle: '预定流程',
reservationFlowDesc: '当前预定单的流程进度',
reservationOrderFallback: '预定单',
reservationOrderNoLabel: '预定单号',
roomNoLabel: '房间号',
reservationRoomLabel: '房型',
reservationStatusLabel: '订单状态',
currentStepTag: '当前',
contractPendingReviewHint: '合同已签,等待宿管审核',
checkinPendingHint: '宿管正在办理入住,请耐心等待',
refreshAction: '刷新',
refreshFlowAction: '刷新流程'
},
en: {
pageTitle: 'My Applications',
pageDesc: 'Your stay and viewing application records',
toolbarSummary: '{total} applications',
refreshing: 'Refreshing...',
refresh: 'Refresh Status',
apartmentLabel: 'Apartment',
occupantLabel: 'Resident',
viewingTimeLabel: 'Viewing Time',
leaseTermLabel: 'Lease Term',
submittedAtLabel: 'Submitted At',
remarkLabel: 'Remarks',
payDeposit: 'Pay Deposit',
continuePay: 'Continue Pay',
pendingPaymentTitle: 'Unfinished Payment',
pendingPaymentOrderNoLabel: 'Payment Order No.',
pendingPaymentAmountLabel: 'Order Amount',
pendingPaymentDescLabel: 'Description',
pendingPaymentExpireLabel: 'Time Left',
pendingPaymentExpired: 'Expired',
noMore: 'No more applications',
pullUpMore: 'Pull up to load more',
emptyTitle: 'No applications yet',
emptyDesc: 'You have not submitted any application yet. Go select a room to start a stay or viewing application.',
goSelectRoom: 'Select Room',
loadingDesc: 'Syncing your application records. Please wait.',
statusPending: 'Pending',
statusApproved: 'Approved',
statusRejected: 'Rejected',
statusFinished: 'Closed',
statusProcessing: 'Processing',
statusPendingPayment: 'Please Pay Deposit',
statusWaitingRoomAssign: 'Waiting for Room Assignment',
statusReminderTitle: 'Current Progress',
refreshEmptySuccess: 'No application found. Reservation records synced.',
refreshSuccess: 'Application status refreshed',
loadError: 'Failed to load applications',
cancelOrder: 'Cancel Order',
cancelOrderLoading: 'Canceling...',
cancelOrderTitle: 'Cancel Order',
cancelOrderContent: 'This will close the current unpaid deposit order. Continue?',
cancelOrderSuccess: 'Order canceled',
cancelOrderFailed: 'Failed to cancel order',
viewingApplicationTitle: 'Viewing Request',
stayApplicationTitle: 'Stay Application',
missingBillCode: 'Missing application bill number',
missingContractNo: 'Missing contract number',
missingApplicationCode: 'Missing application number',
dateRangeJoiner: ' to ',
depositNoticeTitle: 'Rental Deposit Notice',
agreeAndPay: 'Agree and Pay',
scrollToRead: 'Scroll to bottom',
fetchPaymentParamsFailed: 'Failed to get payment parameters',
fetchWechatLoginCodeFailed: 'Failed to get WeChat login code',
loadingBill: 'Loading bill...',
noPendingDepositBill: 'No pending deposit bill available',
loadingPayment: 'Opening payment...',
depositPaymentDesc: 'Deposit Payment',
paymentSuccess: 'Payment succeeded',
paymentCanceled: 'Payment canceled',
paymentFailed: 'Payment failed',
reservationFlowTitle: 'Reservation Flow',
reservationFlowDesc: 'Progress of the current reservation order',
reservationOrderFallback: 'Reservation',
reservationOrderNoLabel: 'Order No.',
roomNoLabel: 'Room No.',
reservationRoomLabel: 'Room Type',
reservationStatusLabel: 'Order Status',
currentStepTag: 'Current',
contractPendingReviewHint: 'Contract signed, waiting for review',
checkinPendingHint: 'Move-in is being processed, please wait',
refreshAction: 'Refresh',
refreshFlowAction: 'Refresh Flow'
}
},
data() {
return {
currentIndex: 0,
@@ -616,9 +466,6 @@ export default {
this.clearCountdownTimer()
},
methods: {
t(key, params) {
return this.$tm('pages.index.component.MyApplicationTab', key, params)
},
load(force = false) {
return this.reloadList(force)
},
@@ -637,12 +484,12 @@ export default {
},
getStatusText(status) {
const map = {
0: this.t('statusPending'),
10: this.t('statusApproved'),
20: this.t('statusRejected'),
99: this.t('statusFinished')
0: this.$tr('待审核'),
10: this.$tr('已通过'),
20: this.$tr('已拒绝'),
99: this.$tr('已结束')
}
return map[status] || this.t('statusProcessing')
return map[status] || this.$tr('处理中')
},
getDisplayStatusText(item = {}) {
if (this.$getLanguage() !== 'en' && item.statusDesc) {
@@ -685,17 +532,17 @@ export default {
return contractDetail.queryStatusDesc || contractDetail.approvalStatusDesc || contractDetail.statusDesc || getReservationCurrentStepText(item, this.language)
}
if (this.isReservationInspectionPendingReview(item)) {
return this.t('inspectionPendingReviewHint')
return this.$tr('验房已完成,等待宿管审核')
}
const reservationStepText = getReservationCurrentStepText(item, this.language)
if (reservationStepText) {
return reservationStepText
}
if (this.shouldPayDeposit(item)) {
return this.t('statusPendingPayment')
return this.$tr('请支付定金')
}
if (this.isWaitingRoomAssignment(item)) {
return this.t('statusWaitingRoomAssign')
return this.$tr('等待预订中心分配房间')
}
return ''
},
@@ -720,10 +567,10 @@ export default {
const startText = this.formatDateTime(start)
const endText = this.formatDateTime(end)
if (String(start).includes('T') || String(end).includes('T')) {
return `${startText}${this.t('dateRangeJoiner')}${endText}`
return `${startText}${this.$tr(' 至 ')}${endText}`
}
}
return `${this.formatDate(start)}${this.t('dateRangeJoiner')}${this.formatDate(end)}`
return `${this.formatDate(start)}${this.$tr(' 至 ')}${this.formatDate(end)}`
},
formatMoney(value) {
const amount = Number(value || 0)
@@ -826,7 +673,7 @@ export default {
requestWechatPayment(paymentInfo) {
const payOptions = this.normalizeWechatPayOptions(paymentInfo)
if (!payOptions.timeStamp || !payOptions.nonceStr || !payOptions.package || !payOptions.paySign) {
return Promise.reject(new Error(this.t('fetchPaymentParamsFailed')))
return Promise.reject(new Error(this.$tr('获取支付参数失败')))
}
return new Promise((resolve, reject) => {
uni.requestPayment({
@@ -842,7 +689,7 @@ export default {
provider: 'weixin',
success: (res) => {
if (!res || !res.code) {
reject(new Error(this.t('fetchWechatLoginCodeFailed')))
reject(new Error(this.$tr('获取微信登录凭证失败')))
return
}
resolve(res.code)
@@ -992,12 +839,12 @@ export default {
return ''
}
if (expireTimestamp <= this.countdownNow) {
return this.t('pendingPaymentExpired')
return this.$tr('已失效')
}
return this.formatPendingPaymentCountdown(this.getPendingPaymentRemainingSeconds(order))
},
getPendingPaymentActionText(item = {}) {
return this.isApplicationPendingPaymentExpired(item) ? this.t('payDeposit') : this.t('continuePay')
return this.isApplicationPendingPaymentExpired(item) ? this.$tr('支付定金') : this.$tr('继续支付')
},
getReservationOrderNoFromItem(item = {}) {
return getReservationOrderNo(item)
@@ -1077,14 +924,14 @@ export default {
},
getReservationCurrentStepHint(order = {}) {
if (this.isReservationInspectionPendingReview(order)) {
return this.t('inspectionPendingReviewHint')
return this.$tr('验房已完成,等待宿管审核')
}
if (this.isReservationContractAwaitingReview(order)) {
const detail = this.getReservationContractDetail(order) || {}
return String(detail.queryStatusDesc || detail.approvalStatusDesc || '').trim() || this.t('contractPendingReviewHint')
return String(detail.queryStatusDesc || detail.approvalStatusDesc || '').trim() || this.$tr('合同已签,等待宿管审核')
}
if (this.isReservationCheckinProcessing(order)) {
return this.t('checkinPendingHint')
return this.$tr('宿管正在办理入住,请耐心等待')
}
return ''
},
@@ -1127,7 +974,7 @@ export default {
|| bill.referenceTypeDesc
|| bill.billName
|| bill.name
|| this.t('depositPaymentDesc')
|| this.$tr('支付定金')
).trim()
},
getReservationBillDisplayAmount(bill = {}) {
@@ -1411,14 +1258,14 @@ export default {
if (this.total === 0 && !this.reservationOrders.length) {
this.$emit('refresh-shell-state')
uni.showToast({
title: this.t('refreshEmptySuccess'),
title: this.$tr('未查到申请单,已同步预订单'),
icon: 'none'
})
return
}
if (this.list.length || this.reservationOrders.length) {
uni.showToast({
title: this.t('refreshSuccess'),
title: this.$tr('申请状态已刷新'),
icon: 'none'
})
}
@@ -1507,7 +1354,7 @@ export default {
this.pageIndex = Math.max(1, this.pageIndex - 1)
}
uni.showToast({
title: (error && (error.message || error.msg)) || this.t('loadError'),
title: (error && (error.message || error.msg)) || this.$tr('申请记录加载失败'),
icon: 'none'
})
})
@@ -1531,9 +1378,9 @@ export default {
},
getApplicationTitle(item = {}) {
if (item.isViewingApplication) {
return this.t('viewingApplicationTitle')
return this.$tr('预约看房申请')
}
return item.intendedRoomTypeName || this.t('stayApplicationTitle')
return item.intendedRoomTypeName || this.$tr('入住申请')
},
resolveReferenceTypeNo(item = {}) {
return item.detailCode || ''
@@ -1549,14 +1396,14 @@ export default {
},
getCancelButtonText(item = {}) {
return this.cancelingReferenceTypeNo === this.resolveReferenceTypeNo(item)
? this.t('cancelOrderLoading')
: this.t('cancelOrder')
? this.$tr('取消中...')
: this.$tr('取消订单')
},
confirmCancelOrder() {
return new Promise((resolve) => {
uni.showModal({
title: this.t('cancelOrderTitle'),
content: this.t('cancelOrderContent'),
title: this.$tr('取消订单'),
content: this.$tr('取消后将关闭当前未支付的定金订单,是否继续?'),
confirmColor: '#ef4444',
success: (res) => resolve(!!(res && res.confirm)),
fail: () => resolve(false)
@@ -1575,7 +1422,7 @@ export default {
return {
...item,
status: 99,
statusDesc: this.t('statusFinished'),
statusDesc: this.$tr('已结束'),
pendingDepositBill: null,
pendingPaymentOrder: null
}
@@ -1611,14 +1458,14 @@ export default {
await this.reloadList(true)
this.$emit('refresh-shell-state')
uni.showToast({
title: this.t('cancelOrderSuccess'),
title: this.$tr('订单已取消'),
icon: 'success'
})
})
})
.catch((error) => {
uni.showToast({
title: (error && (error.message || error.msg)) || this.t('cancelOrderFailed'),
title: (error && (error.message || error.msg)) || this.$tr('取消订单失败'),
icon: 'none'
})
})
@@ -1633,7 +1480,7 @@ export default {
: ''
if (!referenceTypeNo) {
uni.showToast({
title: this.t('missingBillCode'),
title: this.$tr('申请单编号缺失'),
icon: 'none'
})
return
@@ -1659,7 +1506,7 @@ export default {
: ''
if (!referenceTypeNo) {
uni.showToast({
title: this.t('missingBillCode'),
title: this.$tr('申请单编号缺失'),
icon: 'none'
})
return
@@ -1732,7 +1579,7 @@ export default {
referenceTypeNo: pickFirst(candidateReferenceTypeNos),
billNo: pickFirst(candidateBillNos),
paymentNo: pickFirst(candidatePaymentNos),
description: String(currentNode.stepDesc || fallbackContext.description || '').trim() || this.t('depositPaymentDesc')
description: String(currentNode.stepDesc || fallbackContext.description || '').trim() || this.$tr('支付定金')
}
},
resolvePendingPaymentContextByPaymentNo(paymentNo = '') {
@@ -1788,11 +1635,11 @@ export default {
let referenceTypeNo = String(context.referenceTypeNo || '').trim()
let billNo = String(context.billNo || '').trim()
const paymentNo = String(context.paymentNo || '').trim()
const description = String(context.description || '').trim() || this.t('depositPaymentDesc')
const description = String(context.description || '').trim() || this.$tr('支付定金')
if (!referenceTypeNo && !billNo && !paymentNo) {
uni.showToast({
title: this.t('missingBillCode'),
title: this.$tr('申请单编号缺失'),
icon: 'none'
})
return
@@ -1802,7 +1649,7 @@ export default {
saveActiveApplicationCode(referenceTypeNo)
}
uni.showLoading({ title: this.t('loadingBill') })
uni.showLoading({ title: this.$tr('正在获取账单...') })
try {
let bill = null
let pendingPaymentOrder = billNo ? await this.getPendingPaymentOrder(billNo).catch(() => null) : null
@@ -1850,7 +1697,7 @@ export default {
billName: description
}
} else {
uni.showToast({ title: this.t('noPendingDepositBill'), icon: 'none' })
uni.showToast({ title: this.$tr('暂无待支付定金账单'), icon: 'none' })
return
}
} else {
@@ -1872,11 +1719,11 @@ export default {
}
if (!billNo) {
uni.showToast({ title: this.t('noPendingDepositBill'), icon: 'none' })
uni.showToast({ title: this.$tr('暂无待支付定金账单'), icon: 'none' })
return
}
uni.showLoading({ title: this.t('loadingPayment') })
uni.showLoading({ title: this.$tr('正在拉起支付...') })
const reusablePendingPaymentOrder = pendingPaymentOrder && !this.isApplicationPendingPaymentExpired({
pendingPaymentOrder
@@ -1888,7 +1735,7 @@ export default {
await this.requestWechatPayment(reusablePendingPaymentOrder.cashier.paymentInfo)
} else {
if (!referenceTypeNo || !bill) {
throw new Error(this.t('fetchPaymentParamsFailed'))
throw new Error(this.$tr('获取支付参数失败'))
}
const wechatCode = await this.getWechatLoginCode()
const payRes = await paymentBillPay({
@@ -1905,11 +1752,11 @@ export default {
await this.requestWechatPayment(cashier.paymentInfo)
}
uni.showToast({ title: this.t('paymentSuccess'), icon: 'success' })
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.t('paymentFailed')
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.t('paymentCanceled'), icon: 'none' })
uni.showToast({ title: this.$tr('已取消支付'), icon: 'none' })
} else {
uni.showToast({ title: errMsg, icon: 'none' })
}
@@ -1946,7 +1793,7 @@ export default {
if (step === RESERVATION_FLOW_STEP.BILL) {
if (!referenceTypeNo) {
uni.showToast({
title: this.t('missingBillCode'),
title: this.$tr('申请单编号缺失'),
icon: 'none'
})
return
@@ -1964,7 +1811,7 @@ export default {
}
if (!contractNo) {
uni.showToast({
title: this.t('missingContractNo'),
title: this.$tr('合同编号缺失'),
icon: 'none'
})
return
@@ -2010,7 +1857,7 @@ export default {
const contractNo = String(order && order.reservationCurrentNode && order.reservationCurrentNode.refNo || '').trim()
if (!contractNo) {
uni.showToast({
title: this.t('missingContractNo'),
title: this.$tr('合同编号缺失'),
icon: 'none'
})
return

View File

@@ -1,4 +1,4 @@
<template>
<template>
<view class="profile-page tn-safe-area-inset-bottom">
<!-- 头部背景与用户信息 (悬浮质感设计) -->
@@ -43,7 +43,7 @@
</view>
<view class="tn-margin-left-md">
<view class="user-name clamp-text-1">
{{ t('guest') }}
{{ $tr('游客') }}
</view>
<view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ guestHeaderDesc }}</view>
</view>
@@ -62,17 +62,17 @@
<view class="stats-row tn-flex tn-flex-row-between">
<view class="stat-item">
<view class="stat-value">{{ getStayDaysText() }}</view>
<view class="stat-label">{{ t('daysIn') }}</view>
<view class="stat-label">{{ $tr('入住天数') }}</view>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<view class="stat-value">{{ getActivityCountText() }}</view>
<view class="stat-label">{{ t('activities') }}</view>
<view class="stat-label">{{ $tr('参与活动') }}</view>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<view class="stat-value">{{ getPointsText() }}</view>
<view class="stat-label">{{ t('points') }}</view>
<view class="stat-label">{{ $tr('积分') }}</view>
</view>
</view>
</view>
@@ -84,40 +84,40 @@
<view class="guest-registration-card tn-padding-lg tn-margin-bottom-lg" v-if="!isLogin">
<view class="tn-flex tn-flex-row-between tn-flex-col-center">
<view>
<view class="tn-text-bold tn-text-lg tn-color-black">{{ t('registrationRecordTitle') }}</view>
<view class="tn-text-bold tn-text-lg tn-color-black">{{ $tr('注册记录') }}</view>
<view class="tn-margin-top-sm tn-text-sm tn-color-gray">
{{ registrationApplication ? t('registrationRecordSubmittedDesc') : t('registrationRecordGuestDesc') }}
{{ registrationApplication ? $tr('当前微信账号已提交注册申请,可查看历史记录与审批状态。') : $tr('未登录也可查看当前微信账号提交的注册申请记录。') }}
</view>
</view>
<view class="custom-btn btn-primary guest-registration-btn" @click="goToRegistrationRecords">
<text class="tn-text-sm tn-text-bold">{{ t('viewNow') }}</text>
<text class="tn-text-sm tn-text-bold">{{ $tr('立即查看') }}</text>
</view>
</view>
</view>
<view class="application-status-card tn-padding-lg tn-margin-bottom-lg" v-if="isLogin && pendingContractCount">
<view class="tn-flex tn-flex-row-between tn-flex-col-center">
<view class="tn-text-bold tn-text-lg tn-color-black">{{ t('contractStatusTitle') }}</view>
<view class="tn-text-bold tn-text-lg tn-color-black">{{ $tr('合同状态') }}</view>
<view class="status-tag status-pending">
{{ t('contractPendingTag') + ' ' + pendingContractCount }}
{{ $tr('待签约') + ' ' + pendingContractCount }}
</view>
</view>
<view class="tn-margin-top-sm tn-color-gray tn-text-sm">
{{ t('contractPendingDesc') }}
{{ $tr('您当前有待签署合同,请尽快查看并完成签约。') }}
</view>
<view class="tn-margin-top tn-flex tn-flex-row-between tn-flex-col-center">
<view class="tn-color-gray tn-text-xs">
{{ t('pendingCountText') + ':' + pendingContractCount }}
{{ $tr('待签合同') + ':' + pendingContractCount }}
</view>
<view class="custom-btn btn-primary" style="padding: 8rpx 28rpx;" @click="goToContractList">
<text class="tn-text-sm tn-text-bold">{{ t('viewDetail') }}</text>
<text class="tn-text-sm tn-text-bold">{{ $tr('查看详情') }}</text>
</view>
</view>
</view>
<!-- 核心服务 (宫格) -->
<view class="core-services-card tn-padding-lg tn-margin-bottom-lg" v-if="isLogin">
<view class="tn-text-bold tn-text-lg tn-color-black tn-margin-bottom">{{ t('coreServices') }}</view>
<view class="tn-text-bold tn-text-lg tn-color-black tn-margin-bottom">{{ $tr('我的服务') }}</view>
<view class="tn-flex tn-flex-row-between">
<view
class="core-service-item tn-flex tn-flex-direction-column tn-flex-col-center"
@@ -128,7 +128,7 @@
<view class="service-icon-box tn-flex tn-flex-col-center tn-flex-row-center">
<text :class="item.icon" class="service-icon-symbol"></text>
</view>
<text class="tn-margin-top-sm tn-text-sm tn-color-black">{{ t(item.i18nKey) }}</text>
<text class="tn-margin-top-sm tn-text-sm tn-color-black">{{ $tr(item.i18nKey) }}</text>
</view>
</view>
</view>
@@ -146,16 +146,16 @@
<text class="tn-margin-left-sm tn-text-md tn-color-black tn-text-bold">{{ getMenuLabel(item) }}</text>
</view>
<view class="tn-flex tn-flex-col-center">
<view v-if="item.available === false" class="menu-coming-soon">{{ t('comingSoon') }}</view>
<view v-if="item.available === false" class="menu-coming-soon">{{ $tr('建设中') }}</view>
<text class="tn-icon-right menu-arrow"></text>
</view>
</view>
<view class="menu-item tn-flex tn-flex-row-between tn-flex-col-center" @click="handleLanguageToggle">
<view class="tn-flex tn-flex-col-center">
<view class="menu-icon"><text class="tn-icon-set menu-icon-symbol"></text></view>
<text class="tn-margin-left-sm tn-text-md tn-color-black tn-text-bold">{{ t('languageSwitch') }}</text>
<text class="tn-margin-left-sm tn-text-md tn-color-black tn-text-bold">{{ $tr('语言切换') }}</text>
</view>
<text class="tn-text-sm tn-color-gray">{{ t('languageToggleLabel') }}</text>
<text class="tn-text-sm tn-color-gray">{{ $tr('EN') }}</text>
</view>
</view>
@@ -163,7 +163,7 @@
<!-- 退出登录 -->
<view class="tn-margin-top-xl tn-text-center" v-if="isLogin">
<view class="custom-btn btn-danger" style="width: 80%; height: 90rpx;" @click="handleLogout">
<text class="tn-text-bold">{{ t('logout') }}</text>
<text class="tn-text-bold">{{ $tr('退出登录') }}</text>
</view>
</view>
@@ -192,7 +192,7 @@ export default {
return typeof this.vuex_custom_bar_height === 'number' ? this.vuex_custom_bar_height : 0
},
displayUserName() {
return this.userInfo.name || this.userInfo.account || this.userInfo.username || this.userInfo.mobile || this.t('userFallback')
return this.userInfo.name || this.userInfo.account || this.userInfo.username || this.userInfo.mobile || this.$tr('用户')
},
displayUserInitial() {
const firstChar = this.userInfo.firstChar
@@ -217,21 +217,21 @@ export default {
},
guestHeaderDesc() {
return this.registrationApplication
? `${this.t('registrationStatusTitle')}:${this.getRegistrationStatusText(this.registrationStatus)}`
: this.t('clickToLogin')
? `${this.$tr('注册申请进度')}:${this.getRegistrationStatusText(this.registrationStatus)}`
: this.$tr('点击登录')
},
registrationStatusDesc() {
if (!this.registrationApplication) return this.t('clickToLogin')
if (!this.registrationApplication) return this.$tr('点击登录')
if (this.registrationStatus === 'pending') {
return this.t('registrationPendingDesc')
return this.$tr('注册申请审核中,审批通过后才可登录')
}
if (this.registrationStatus === 'approved') {
return this.t('registrationApprovedDesc')
return this.$tr('注册申请已通过,可前往登录')
}
if (this.registrationStatus === 'rejected') {
return this.registrationApplication.approveReason || this.t('registrationRejectedDesc')
return this.registrationApplication.approveReason || this.$tr('注册申请已驳回,请查看原因后重新提交')
}
return this.t('registrationEmptyDesc')
return this.$tr('暂未提交注册申请')
},
currentBookingCard() {
if (!this.isLogin) {
@@ -272,119 +272,11 @@ export default {
{ key: 'butler', label: '联系管家', labelEn: 'Contact Butler', icon: 'tn-icon-service-fill', title: '联系管家', titleEn: 'Contact Butler', desc: '联系专属管家,沟通入住和住中服务事项。', descEn: 'Contact your butler about move-in and in-stay services.', available: true }
],
coreServices: [
{ key: 'application', i18nKey: 'application', icon: 'tn-icon-edit-form' },
{ key: 'bookingList', i18nKey: 'bookingList', icon: 'tn-icon-order-fill' },
{ key: 'contract', i18nKey: 'contract', icon: 'tn-icon-copy' },
{ key: 'bill', i18nKey: 'bill', icon: 'tn-icon-money' }
{ key: 'application', i18nKey: '入住申请', icon: 'tn-icon-edit-form' },
{ key: 'bookingList', i18nKey: '预订记录', icon: 'tn-icon-order-fill' },
{ key: 'contract', i18nKey: '我的合同', icon: 'tn-icon-copy' },
{ key: 'bill', i18nKey: '账单', icon: 'tn-icon-money' }
],
i18n: {
zh: {
edit: '编辑资料',
guest: '游客',
clickToLogin: '点击登录',
daysIn: '入住天数',
activities: '参与活动',
points: '积分',
applicationStatus: '申请进度',
registrationStatusTitle: '注册申请进度',
notSubmittedYet: '暂未提交注册申请',
registrationPendingDesc: '注册申请审核中,审批通过后才可登录',
registrationApprovedDesc: '注册申请已通过,可前往登录',
registrationRejectedDesc: '注册申请已驳回,请查看原因后重新提交',
registrationEmptyDesc: '暂未提交注册申请',
registrationRecordTitle: '注册记录',
registrationRecordSubmittedDesc: '当前微信账号已提交注册申请,可查看历史记录与审批状态。',
registrationRecordGuestDesc: '未登录也可查看当前微信账号提交的注册申请记录。',
viewNow: '立即查看',
statusPending: '审核中',
statusApproved: '已通过',
statusRejected: '已拒绝',
remind: '催一下',
done: '已完成',
viewDetail: '查看详情',
exclusiveButler: '专属管家',
onlineService: '24小时在线服务',
contact: '联系',
application: '入住申请',
bookingList: '预订记录',
bookingStatusTitle: '预订审核进度',
contractStatusTitle: '合同状态',
bookingNo: '预定单号',
bookingPlaceholder: '支付后生成的预定单会显示在这里',
bookingProgressDesc: '支付完成后会进入审核,审核通过后分配管家并等待分房号。',
contractPendingTag: '待签约',
contractPendingDesc: '您当前有待签署合同,请尽快查看并完成签约。',
pendingCountText: '待签合同',
contract: '我的合同',
bill: '账单',
coreServices: '我的服务',
comingSoon: '建设中',
settings: '偏好设置',
languageSwitch: '语言切换',
languageToggleLabel: 'EN',
logout: '退出登录',
logoutConfirmTitle: '提示',
logoutConfirmContent: '确定要退出登录吗?',
logoutSuccess: '已退出',
userFallback: '用户',
submitRequest: '提交申请',
completeFormBeforeSubmit: '请完善表单信息后提交。',
comingSoonDesc: '{title} 功能正在建设中,后续开放后会在这里提供正式入口和数据。'
},
en: {
edit: 'Edit',
guest: 'Guest',
clickToLogin: 'Click to Sign In',
daysIn: 'Days Stayed',
activities: 'Activities',
points: 'Points',
applicationStatus: 'Application Status',
registrationStatusTitle: 'Registration Status',
notSubmittedYet: 'No registration submitted',
registrationPendingDesc: 'Registration is under review',
registrationApprovedDesc: 'Registration approved, ready to sign in',
registrationRejectedDesc: 'Registration rejected, please review the reason',
registrationEmptyDesc: 'No registration submitted',
registrationRecordTitle: 'Registration Records',
registrationRecordSubmittedDesc: 'A registration has been submitted with this Wechat account. You can review its history and approval status.',
registrationRecordGuestDesc: 'You can still review registration records submitted with this Wechat account before signing in.',
viewNow: 'View Now',
statusPending: 'Under Review',
statusApproved: 'Approved',
statusRejected: 'Rejected',
remind: 'Remind',
done: 'Done',
viewDetail: 'Details',
exclusiveButler: 'Exclusive Butler',
onlineService: '24/7 Online Service',
contact: 'Contact',
application: 'Applications',
bookingList: 'Bookings',
bookingStatusTitle: 'Booking Progress',
contractStatusTitle: 'Contract Status',
bookingNo: 'Booking No.',
bookingPlaceholder: 'Your booking will appear here after payment',
bookingProgressDesc: 'Booking will be reviewed after payment. Butler will be assigned upon approval.',
contractPendingTag: 'Pending',
contractPendingDesc: 'You have contracts waiting for signature. Please review them soon.',
pendingCountText: 'Pending Contracts',
contract: 'My Contract',
bill: 'Billing',
coreServices: 'My Services',
comingSoon: 'Soon',
settings: 'Preferences',
languageSwitch: 'Language',
languageToggleLabel: '中文',
logout: 'Log Out',
logoutConfirmTitle: 'Notice',
logoutConfirmContent: 'Are you sure to log out?',
logoutSuccess: 'Logged out',
userFallback: 'User',
submitRequest: 'Submit Request',
completeFormBeforeSubmit: 'Please complete the form before submitting.',
comingSoonDesc: '{title} is under development. The formal entry and data will be available here later.'
}
}
}
},
async mounted() {
@@ -398,9 +290,6 @@ export default {
else if (key === 'contract') this.goToContractList();
else if (key === 'bill') this.goToUtilityRecharge();
},
t(key, params) {
return this.$tm('pages.index.component.ProfileCenterTab', key, params)
},
getMenuLabel(item = {}) {
return this.language === 'en'
? (item.labelEnShort || item.labelEn || item.label || '')
@@ -511,14 +400,14 @@ export default {
},
handleLogout() {
uni.showModal({
title: this.t('logoutConfirmTitle'),
content: this.t('logoutConfirmContent'),
title: this.$tr('提示'),
content: this.$tr('确定要退出登录吗?'),
success: (res) => {
if (res.confirm) {
clearLoginSession()
this.checkLoginStatus();
this.$parent.changeTabbar && this.$parent.changeTabbar(0, 'select-room');
uni.showToast({ title: this.t('logoutSuccess'), icon: 'success' });
uni.showToast({ title: this.$tr('已退出'), icon: 'success' });
}
}
});
@@ -531,13 +420,13 @@ export default {
},
getRegistrationStatusText(status) {
if (this.registrationApplication && typeof this.registrationApplication.approvalStatus === 'number') {
return getApprovalStatusLabel(this.registrationApplication.approvalStatus, this.t('notSubmittedYet'))
return getApprovalStatusLabel(this.registrationApplication.approvalStatus, this.$tr('暂未提交注册申请'))
}
const map = {
unsubmitted: this.t('notSubmittedYet'),
pending: this.t('statusPending'),
approved: this.t('statusApproved'),
rejected: this.t('statusRejected')
unsubmitted: this.$tr('暂未提交注册申请'),
pending: this.$tr('审核中'),
approved: this.$tr('已通过'),
rejected: this.$tr('已拒绝')
}
return map[status] || map.unsubmitted
},
@@ -581,7 +470,7 @@ export default {
},
goToSubmitRequest(title, desc, payload = {}, titleZh = '', titleEn = '', descZh = '', descEn = '') {
uni.navigateTo({
url: `/pkgService/submit_request/submit_request?title=${encodeURIComponent(title || this.t('submitRequest'))}&titleZh=${encodeURIComponent(titleZh || title || this.t('submitRequest'))}&titleEn=${encodeURIComponent(titleEn || title || this.t('submitRequest'))}&desc=${encodeURIComponent(desc || this.t('completeFormBeforeSubmit'))}&descZh=${encodeURIComponent(descZh || desc || this.t('completeFormBeforeSubmit'))}&descEn=${encodeURIComponent(descEn || desc || this.t('completeFormBeforeSubmit'))}&payload=${encodeURIComponent(JSON.stringify(payload || {}))}`
url: `/pkgService/submit_request/submit_request?title=${encodeURIComponent(title || this.$tr('提交申请'))}&titleZh=${encodeURIComponent(titleZh || title || this.$tr('提交申请'))}&titleEn=${encodeURIComponent(titleEn || title || this.$tr('提交申请'))}&desc=${encodeURIComponent(desc || this.$tr('请完善表单信息后提交。'))}&descZh=${encodeURIComponent(descZh || desc || this.$tr('请完善表单信息后提交。'))}&descEn=${encodeURIComponent(descEn || desc || this.$tr('请完善表单信息后提交。'))}&payload=${encodeURIComponent(JSON.stringify(payload || {}))}`
})
},
handleApplicationEntry() {
@@ -627,9 +516,9 @@ export default {
}, itemTitleZh, itemTitleEn, itemDescZh, itemDescEn)
return
}
const comingSoonDesc = this.t('comingSoonDesc', { title: itemTitle })
const comingSoonDescZh = this.$tm('pages.index.component.ProfileCenterTab', 'comingSoonDesc', { title: itemTitleZh || itemTitle })
const comingSoonDescEn = this.$tm('pages.index.component.ProfileCenterTab', 'comingSoonDesc', { title: itemTitleEn || itemTitle })
const comingSoonDesc = this.$tr('{title} 功能正在建设中,后续开放后会在这里提供正式入口和数据。', { title: itemTitle })
const comingSoonDescZh = this.$tr('{title} 功能正在建设中,后续开放后会在这里提供正式入口和数据。', { title: itemTitleZh || itemTitle })
const comingSoonDescEn = this.$tr('{title} 功能正在建设中,后续开放后会在这里提供正式入口和数据。', { title: itemTitleEn || itemTitle })
uni.navigateTo({
url: `/pkgService/service_stub/service_stub?title=${encodeURIComponent(itemTitle)}&titleZh=${encodeURIComponent(itemTitleZh || itemTitle)}&titleEn=${encodeURIComponent(itemTitleEn || itemTitle)}&desc=${encodeURIComponent(comingSoonDesc)}&descZh=${encodeURIComponent(comingSoonDescZh)}&descEn=${encodeURIComponent(comingSoonDescEn)}&key=${item.key}`
});

View File

@@ -28,7 +28,7 @@
</view>
<view class="search-box tn-flex tn-flex-col-center">
<text class="tn-icon-search search-icon"></text>
<text class="search-placeholder">{{ t('searchApartment') }}</text>
<text class="search-placeholder">{{ $tr('搜索区域、公寓或房型') }}</text>
</view>
<view
class="sort-trigger tn-flex tn-flex-col-center tn-flex-row-center"
@@ -44,7 +44,7 @@
<view class="quick-filter-head tn-flex tn-flex-row-between tn-flex-col-center">
<view class="quick-filter-title-box tn-flex tn-flex-col-center">
<view class="quick-filter-dot"></view>
<text class="quick-filter-title">{{ t('tagFilter') }}</text>
<text class="quick-filter-title">{{ $tr('标签筛选') }}</text>
</view>
<text class="quick-filter-current">{{ displayTag(currentTagLabel) }}</text>
</view>
@@ -57,7 +57,7 @@
:class="{ 'quick-tag--active': !selectedTag }"
@click="$emit('select-tag', '')"
>
{{ t('all') }}
{{ $tr('全部') }}
</view>
<view
v-for="tag in availableFilterTags"
@@ -81,17 +81,17 @@
class="room-status-card tn-margin-bottom-lg"
>
<view class="room-status-card__title">
{{ isAreaLoading ? t('loadingAreaStatus') : t('areaStatus') }}
{{ isAreaLoading ? $tr('区域加载中') : $tr('区域状态') }}
</view>
<view class="room-status-card__desc">
{{ roomLoadError || areaLoadError || t('areaStatusDesc') }}
{{ roomLoadError || areaLoadError || $tr('暂无符合筛选条件的区域') }}
</view>
</view>
<view v-if="!isAreaLoading && !displayAreaSections.length" class="empty-state-card tn-margin-bottom-lg">
<view class="empty-state-card__title">{{ bannerList.length ? t('emptyAreaTitle') : t('emptyBannerTitle') }}</view>
<view class="empty-state-card__title">{{ bannerList.length ? $tr('暂无匹配房源') : $tr('暂无轮播图') }}</view>
<view class="empty-state-card__desc">
{{ bannerList.length ? t('emptyAreaDesc') : t('emptyBannerDesc') }}
{{ bannerList.length ? $tr('当前筛选条件下暂无可用房源') : $tr('暂无可展示的轮播图') }}
</view>
</view>
@@ -102,7 +102,7 @@
<view class="store-title-bar tn-flex tn-flex-row-between tn-flex-col-center">
<view class="store-title tn-text-ellipsis">{{ getAreaName(area) }}</view>
<view class="store-detail-btn tn-flex tn-flex-col-center">
<text class="btn-text">{{ t('viewCommunity') }}</text>
<text class="btn-text">{{ $tr('查看详情') }}</text>
<text class="tn-icon-right btn-icon"></text>
</view>
</view>
@@ -118,7 +118,7 @@
</view>
<text class="address-text">{{ getAreaAddress(area) || defaultAreaAddress(getAreaName(area)) }}</text>
</view>
<view class="store-distance">{{ t('distancePlaceholder') }}</view>
<view class="store-distance">{{ $tr('附近') }}</view>
</view>
</view>
@@ -145,8 +145,8 @@
<view class="room-price-row tn-flex tn-flex-row-between tn-flex-col-bottom">
<view class="price-content">
<text class="price-symbol">¥</text>
<text class="price-val">{{ formatDisplayPrice(roomType.price) || t('pricePending') }}</text>
<text class="price-unit">{{ t('priceUnit') }}</text>
<text class="price-val">{{ formatDisplayPrice(roomType.price) || $tr('待定') }}</text>
<text class="price-unit">{{ $tr('元/月起') }}</text>
</view>
<view class="room-go-icon tn-flex tn-flex-col-center tn-flex-row-center">
<text class="tn-icon-right"></text>
@@ -272,9 +272,6 @@ export default {
}
return localizeHousingTag(text, this.language)
},
t(key) {
return this.$tm('pages.index.component.SelectRoomTab', key)
},
formatRoomTypeName(name) {
const normalizedName = String(name || '')
return normalizedName.replace(/^[^·]+·/, '')
@@ -321,7 +318,7 @@ export default {
return [...new Set(roomTypeTags)].slice(0, 4)
},
showTagActionSheet() {
const tagOptions = [this.t('allTags'), ...this.availableFilterTags]
const tagOptions = [this.$tr('全部标签'), ...this.availableFilterTags]
if (!tagOptions.length) {
return
}
@@ -335,10 +332,10 @@ export default {
},
showSortActionSheet() {
const sortOptions = [
{ label: this.t('sortDefault'), value: 'default' },
{ label: this.t('sortPriceAsc'), value: 'price-asc' },
{ label: this.t('sortPriceDesc'), value: 'price-desc' },
{ label: this.t('sortNameAsc'), value: 'name-asc' }
{ label: this.$tr('默认排序'), value: 'default' },
{ label: this.$tr('价格从低到高'), value: 'price-asc' },
{ label: this.$tr('价格从高到低'), value: 'price-desc' },
{ label: this.$tr('名称排序'), value: 'name-asc' }
]
uni.showActionSheet({
itemList: sortOptions.map((item) => item.label),
@@ -360,9 +357,9 @@ export default {
},
defaultAreaAddress(areaName = '') {
if (this.language === 'en') {
return `${this.t('defaultAreaAddressPrefix')}${areaName} CBD`
return `${this.$tr('位于')}${areaName} CBD`
}
return `${this.t('defaultAreaAddressPrefix')}${areaName}核心商圈`
return `${this.$tr('位于')}${areaName}核心商圈`
}
}
}

View File

@@ -57,12 +57,12 @@
<!-- 底部操作区 -->
<view class="hero-bottom tn-flex tn-flex-direction-column tn-flex-col-center">
<view class="hero-actions tn-flex tn-flex-row-between" :class="{ 'hero-actions--en': language === 'en' }">
<view class="action-btn outline-btn" :class="{ 'action-btn--en': language === 'en' }" @click="$emit('consult')">{{ t('apartmentDetail') }}</view>
<view class="action-btn primary-btn" :class="{ 'action-btn--en': language === 'en' }" @click="$emit('submit')">{{ t('bookViewing') }}</view>
<view class="action-btn outline-btn" :class="{ 'action-btn--en': language === 'en' }" @click="$emit('consult')">{{ $tr('公寓详情') }}</view>
<view class="action-btn primary-btn" :class="{ 'action-btn--en': language === 'en' }" @click="$emit('submit')">{{ $tr('预约看房') }}</view>
</view>
<view class="scroll-hint tn-flex tn-flex-direction-column tn-flex-col-center" @click="$emit('collapse')">
<text class="scroll-hint__text">{{ t('learnMore') }}</text>
<text class="scroll-hint__text">{{ $tr('了解更多') }}</text>
<view class="scroll-hint__double-icon">
<text class="tn-icon-down scroll-hint__icon"></text>
<text class="tn-icon-down scroll-hint__icon scroll-hint__icon--second"></text>
@@ -136,9 +136,6 @@ export default {
displayTabSubtitle(item = {}) {
return this.getBannerDisplayText(item, 'tab')
},
t(key) {
return this.$tm('pages.index.component.SelectRoomTab', key)
},
formatTabTitle(title) {
const normalizedTitle = String(title || '')
return normalizedTitle

View File

@@ -114,64 +114,6 @@ export default {
SelectRoomHero,
SelectRoomAreaPanel
},
i18n: {
zh: {
shareTitle: '绿居公寓 - 国际化青年社区',
all: '全部',
allAreas: '全部区域',
allTags: '全部标签',
apartmentDetail: '公寓详情',
areaStatus: '区域状态',
areaStatusDesc: '暂无符合筛选条件的区域',
areaUnavailableTitle: '该区域暂未开放',
bookViewing: '预约看房',
defaultAreaAddressPrefix: '位于',
distancePlaceholder: '附近',
emptyAreaDesc: '当前筛选条件下暂无可用房源',
emptyAreaTitle: "暂无匹配房源",
emptyBannerDesc: "暂无可展示的轮播图",
emptyBannerTitle: "暂无轮播图",
learnMore: "了解更多",
loadingAreaStatus: "区域加载中",
pricePending: "待定",
priceUnit: "元/月起",
searchApartment: "搜索区域、公寓或房型",
sortDefault: "默认排序",
sortNameAsc: "名称排序",
sortPriceAsc: "价格从低到高",
sortPriceDesc: "价格从高到低",
tagFilter: "标签筛选",
viewCommunity: "查看详情"
},
en: {
shareTitle: 'Luju Apartments - International Youth Community',
all: "All",
allAreas: "All Areas",
allTags: "All Tags",
apartmentDetail: "Apartment Detail",
areaStatus: "Area Status",
areaStatusDesc: "No areas matched the current filter.",
areaUnavailableTitle: "Area information unavailable",
bookViewing: "Book Viewing",
defaultAreaAddressPrefix: "Located at ",
distancePlaceholder: "Nearby",
emptyAreaDesc: "No rooms available under the current filters.",
emptyAreaTitle: "No matched rooms",
emptyBannerDesc: "No active banners available.",
emptyBannerTitle: "No banners",
learnMore: "Learn More",
loadingAreaStatus: "Loading areas",
pricePending: "TBD",
priceUnit: "/month",
searchApartment: "Search by area, apartment or room type",
sortDefault: "Default",
sortNameAsc: "Name",
sortPriceAsc: "Price: Low to High",
sortPriceDesc: "Price: High to Low",
tagFilter: "Tags",
viewCommunity: "View Details"
}
},
data() {
return {
isLogin: false,
@@ -213,12 +155,12 @@ export default {
return this.getLocalizedAreaText(matchedArea)
}
if (this.currentLang === 'en') {
return this.currentBanner.areaLabelEnDisplay || this.currentBanner.areaLabelEn || this.currentBanner.areaLabel || this.t('allAreas')
return this.currentBanner.areaLabelEnDisplay || this.currentBanner.areaLabelEn || this.currentBanner.areaLabel || this.$tr('全部区域')
}
return this.currentBanner.areaLabel || this.t('allAreas')
return this.currentBanner.areaLabel || this.$tr('全部区域')
},
currentTagLabel() {
return this.selectedTag || this.t('allTags')
return this.selectedTag || this.$tr('全部标签')
},
availableFilterTags() {
const preferredOrder = ['押一付一', '生活便利', '独立厨卫', '商圈环绕', '民水民电', '拎包入住', '近地铁', '智能家居']
@@ -262,9 +204,6 @@ export default {
await Promise.all([this.loadBannerList(), this.loadAreaList()])
},
methods: {
t(key) {
return this.$tm('pages.index.component.SelectRoomTab', key)
},
getLocalizedBannerText(banner = {}, field = '') {
const key = String(field || '').trim()
if (!key) {
@@ -512,7 +451,7 @@ export default {
} else {
// 容错处理
uni.showToast({
title: this.t('areaUnavailableTitle'),
title: this.$tr('该区域暂未开放'),
icon: 'none'
})
}
@@ -547,13 +486,13 @@ export default {
,
onShareAppMessage() {
return {
title: this.t('shareTitle'),
title: this.$tr('绿居公寓 - 国际化青年社区'),
path: '/pages/index/index'
}
},
onShareTimeline() {
return {
title: this.t('shareTitle')
title: this.$tr('绿居公寓 - 国际化青年社区')
}
}
}

View File

@@ -1,7 +1,7 @@
<template>
<view class="service-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="false" :bottomShadow="false" :backgroundColor="navBarBackgroundColor" :customBackStyle="{ transition: 'all 0.3s ease' }">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('title') }}</view>
<view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('我的入住') }}</view>
</tn-nav-bar>
<scroll-view class="service-scroll" scroll-y :style="{paddingTop: vuex_custom_bar_height + 'px'}" @scroll="handleScroll">
@@ -13,12 +13,12 @@
<view class="icon-wrap tn-margin-bottom">
<text class="tn-icon-home-smile tn-color-white"></text>
</view>
<view class="tn-text-bold tn-color-white" style="font-size: 40rpx;">{{ t('notCheckedInTitle') }}</view>
<view class="tn-text-bold tn-color-white" style="font-size: 40rpx;">{{ $tr('您还没有在租房间') }}</view>
<view class="tn-color-white tn-margin-top-sm tn-text-center" style="opacity: 0.8; line-height: 1.6;">
{{ t('notCheckedInDesc') }}
{{ $tr('长租公寓为海外青年提供高端社区服务\n请先前往选房大厅挑选心仪套间') }}
</view>
<view class="custom-btn btn-glass tn-margin-top-xl" @click="goSelectRoom" style="width: 80%;">
<text class="tn-text-bold">{{ t('goSelectRoom') }}</text>
<text class="tn-text-bold">{{ $tr('去选房') }}</text>
</view>
</view>
</view>
@@ -49,7 +49,7 @@
<view class="ticket-header tn-flex tn-flex-row-between tn-flex-col-center">
<view class="tn-flex tn-flex-col-center">
<view class="ticket-icon-wrap"><text class="tn-icon-order-fill"></text></view>
<text class="tn-text-bold tn-text-lg tn-color-black tn-margin-left-sm">{{ t('bookingInfo') }}</text>
<text class="tn-text-bold tn-text-lg tn-color-black tn-margin-left-sm">{{ $tr('预定信息') }}</text>
</view>
<view class="ticket-status-badge">{{ bookingStatusText }}</view>
</view>
@@ -73,7 +73,7 @@
<view class="process-track tn-margin-top-xl">
<view class="track-item active">
<view class="track-dot"><text class="tn-icon-success"></text></view>
<text class="track-text">{{ t('submitBookingStep') }}</text>
<text class="track-text">{{ $tr('提交预定') }}</text>
</view>
<view class="track-line active-line"></view>
<view class="track-item" :class="isContractSigningStep ? 'active' : 'current'">
@@ -82,7 +82,7 @@
<view v-if="!isContractSigningStep" class="dot-inner"></view>
<text v-if="isContractSigningStep" class="tn-icon-success"></text>
</view>
<text class="track-text">{{ t('reviewStep') }}</text>
<text class="track-text">{{ $tr('管家审核') }}</text>
</view>
<view class="track-line" :class="{ 'active-line': isContractSigningStep }"></view>
<view class="track-item" :class="{ active: hasSignedContract, current: isContractSigningStep && !hasSignedContract }">
@@ -91,12 +91,12 @@
<view v-if="isContractSigningStep && !hasSignedContract" class="dot-inner"></view>
<text v-if="hasSignedContract" class="tn-icon-success"></text>
</view>
<text class="track-text">{{ t('contractStep') }}</text>
<text class="track-text">{{ $tr('签订合同') }}</text>
</view>
<view class="track-line" :class="{ 'active-line': hasSignedContract }"></view>
<view class="track-item">
<view class="track-dot"></view>
<text class="track-text">{{ t('checkinStep') }}</text>
<text class="track-text">{{ $tr('办理入住') }}</text>
</view>
</view>
@@ -127,11 +127,11 @@
<view class="tn-flex tn-flex-row-between tn-flex-col-center">
<view class="irc-room-no">{{ roomDisplayText }}</view>
<view class="irc-status-badge">
<text class="tn-icon-vip-fill tn-margin-right-xs"></text>{{ t('activeStatus') }}
<text class="tn-icon-vip-fill tn-margin-right-xs"></text>{{ $tr('使用中') }}
</view>
</view>
<view class="irc-room-title clamp-text-1 tn-margin-top-xs">{{ roomTitleText }}</view>
<view class="irc-lease tn-margin-top-sm">{{ t('leaseTerm') }}:{{ leaseTermText }}</view>
<view class="irc-lease tn-margin-top-sm">{{ $tr('租期') }}:{{ leaseTermText }}</view>
</view>
<!-- 环境与客控区 -->
@@ -139,7 +139,7 @@
<view class="irc-env-row tn-flex tn-flex-row-between tn-flex-col-center">
<view class="irc-env-text">{{ smartSummaryText }}</view>
<view class="irc-smart-btn" @click="goSmartControl">
{{ t('smartControl') }} <text class="tn-icon-right tn-margin-left-xs"></text>
{{ $tr('全屋智能') }} <text class="tn-icon-right tn-margin-left-xs"></text>
</view>
</view>
@@ -158,7 +158,7 @@
</view>
<view v-else class="irc-qc-empty" @click.stop="goSmartControl">
<text class="tn-icon-add tn-margin-right-xs"></text>
{{ t('addSmartDevice') }}
{{ $tr('添加智能设备') }}
</view>
</scroll-view>
</view>
@@ -166,7 +166,7 @@
</view>
<view class="tn-margin-top tn-margin-left tn-margin-right">
<view class="tn-text-bold tn-text tn-color-black tn-margin-bottom">{{ t('stayService') }}</view>
<view class="tn-text-bold tn-text tn-color-black tn-margin-bottom">{{ $tr('住中申请') }}</view>
<view class="tn-flex tn-flex-wrap tn-flex-row-between">
<view class="service-item tn-margin-bottom-sm" v-for="item in stayActions" :key="item.key" @click="openServicePage(item)">
@@ -193,8 +193,8 @@
<view class="tn-margin-top-sm tn-margin-left tn-margin-right">
<view class="tn-flex tn-flex-row-between tn-flex-col-bottom tn-margin-bottom">
<view class="tn-text-bold tn-text tn-color-black">{{ t('publicService') }}</view>
<view class="tn-text-sm tn-color-gray">{{ t('viewAll') }} <text class="tn-icon-right section-arrow-icon"></text></view>
<view class="tn-text-bold tn-text tn-color-black">{{ $tr('公寓精选服务') }}</view>
<view class="tn-text-sm tn-color-gray">{{ $tr('全部') }} <text class="tn-icon-right section-arrow-icon"></text></view>
</view>
<view class="tn-flex tn-flex-wrap tn-flex-row-between">
@@ -222,28 +222,28 @@
<view class="bill-section tn-margin-top tn-margin-left tn-margin-right tn-padding-lg">
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-bottom">
<view class="tn-text-bold tn-text tn-color-black">{{ t('myBill') }}</view>
<view class="status-tag tn-text-xs" :class="unpaidBillCount ? 'tag-busy' : 'tag-free'">{{ unpaidBillCount ? t('unpaid') : t('paidOff') }}</view>
<view class="tn-text-bold tn-text tn-color-black">{{ $tr('本月账单') }}</view>
<view class="status-tag tn-text-xs" :class="unpaidBillCount ? 'tag-busy' : 'tag-free'">{{ unpaidBillCount ? $tr('待支付') : $tr('已结清') }}</view>
</view>
<view class="tn-flex tn-flex-row-between tn-flex-col-bottom tn-margin-bottom-sm">
<view class="tn-text-bold tn-color-black" style="font-size: 60rpx;">
<text class="tn-text-lg tn-margin-right-xs">¥</text>{{ formatMoney(unpaidBillTotal) }}
</view>
<view class="custom-btn btn-primary" style="padding: 10rpx 30rpx;" @click.stop="goUtilityRecharge">
<text class="tn-text-sm tn-text-bold">{{ unpaidBillCount ? t('payBill') : t('viewBill') }}</text>
<text class="tn-text-sm tn-text-bold">{{ unpaidBillCount ? $tr('去支付') : $tr('查看账单') }}</text>
</view>
</view>
<view class="tn-flex tn-flex-row-between tn-margin-top">
<view class="tn-flex tn-flex-direction-column">
<text class="tn-color-gray tn-text-xs">{{ t('billType') }}</text>
<text class="tn-color-gray tn-text-xs">{{ $tr('账单类型') }}</text>
<text class="tn-text-bold tn-text-sm tn-margin-top-xs tn-color-black clamp-text-1">{{ unpaidBillPrimaryType }}</text>
</view>
<view class="tn-flex tn-flex-direction-column">
<text class="tn-color-gray tn-text-xs">{{ t('billCount') }}</text>
<text class="tn-text-bold tn-text-sm tn-margin-top-xs tn-color-black">{{ unpaidBillCount }} {{ t('billUnit') }}</text>
<text class="tn-color-gray tn-text-xs">{{ $tr('账单数量') }}</text>
<text class="tn-text-bold tn-text-sm tn-margin-top-xs tn-color-black">{{ unpaidBillCount }} {{ $tr('笔') }}</text>
</view>
<view class="tn-flex tn-flex-direction-column">
<text class="tn-color-gray tn-text-xs">{{ t('lastCreateTime') }}</text>
<text class="tn-color-gray tn-text-xs">{{ $tr('最近生成') }}</text>
<text class="tn-text-bold tn-text-sm tn-margin-top-xs tn-color-black">{{ latestBillTimeText }}</text>
</view>
</view>
@@ -251,7 +251,7 @@
<!-- 宿管与管理员列表 -->
<view class="butler-card-new tn-margin-top tn-margin-left tn-margin-right">
<view class="tn-text-bold tn-text tn-color-black tn-margin-bottom">{{ t('managementContacts') }}</view>
<view class="tn-text-bold tn-text tn-color-black tn-margin-bottom">{{ $tr('宿管与管理员') }}</view>
<view class="butler-card-new__inner">
<view v-if="managerContacts.length" class="butler-card-new__list">
<view
@@ -271,10 +271,10 @@
<view class="butler-member-new__name">
{{ getManagerName(contact) }}
<text class="butler-member-new__tag butler-member-new__tag--type" :class="contact.contactType === 'counselor' ? 'tag-counselor' : 'tag-dormitory'">
{{ contact.contactType === 'counselor' ? t('counselorTag') : t('dormitoryManagerTag') }}
{{ contact.contactType === 'counselor' ? $tr('管理员') : $tr('宿管') }}
</text>
</view>
<view class="butler-member-new__phone">{{ getManagerPhone(contact) || t('noContactPhone') }}</view>
<view class="butler-member-new__phone">{{ getManagerPhone(contact) || $tr('暂无联系电话') }}</view>
</view>
</view>
<view
@@ -295,8 +295,8 @@
</view>
</view>
<view class="butler-member-new__info">
<view class="butler-member-new__name">{{ t('managementContacts') }}</view>
<view class="butler-member-new__phone">{{ isManagementContactsLoading ? t('loadingButlerInfo') : t('noManagerContacts') }}</view>
<view class="butler-member-new__name">{{ $tr('宿管与管理员') }}</view>
<view class="butler-member-new__phone">{{ isManagementContactsLoading ? $tr('正在同步管家信息...') : $tr('暂无管理联系人') }}</view>
</view>
</view>
</view>
@@ -562,176 +562,6 @@ export default {
descEn: 'Submit lost or found item details so the community can help.'
}
],
i18n: {
zh: {
title: '我的入住',
bookingInfo: '预定信息',
submitBookingStep: '提交预定',
reviewStep: '管家审核',
contractStep: '签订合同',
checkinStep: '办理入住',
notCheckedInTitle: '您还没有在租房间',
notCheckedInDesc: '长租公寓为海外青年提供高端社区服务\n请先前往选房大厅挑选心仪套间',
bookingPendingTitle: '您的预订正在审核中',
bookingPendingDesc: '定金已支付,预定单正在审批流中。审批通过后将分配管家并等待分房号。',
goSelectRoom: '去选房',
stayService: '住中申请',
butler: '专属社区管家',
online: '当前在线 · 响应时间 < 5分钟',
callNow: '拨打',
publicService: '公寓精选服务',
viewAll: '全部',
leaseTerm: '租期',
activeStatus: '使用中',
myBill: '本月账单',
unpaid: '待支付',
paidOff: '已结清',
payBill: '去支付',
viewBill: '查看账单',
smartControl: '全屋智能',
addSmartDevice: '添加智能设备',
smartAvailableAfterCheckin: '入住后可使用智能设备',
syncingSmartDevices: '正在同步智能设备...',
smartDeviceConnected: '已接入 {count} 台智能设备',
smartDeviceSyncFailed: '智能设备同步失败',
smartDeviceEmpty: '当前房间暂无智能设备',
roomDeviceUnconfigured: '房间智能设备未配置',
smartDeviceLoadFailed: '智能设备加载失败',
primaryButlerTag: '主责',
noContactPhone: '暂无联系电话',
loadingButlerInfo: '正在同步管家信息...',
noButlerInfo: '暂未获取到管家信息',
managementContacts: '宿管与管理员',
dormitoryManagerTag: '宿管',
counselorTag: '管理员',
noManagerContacts: '暂无管理联系人',
managerContactSummary: '当前区域共有 {count} 位管家可联系',
contactPhonePrefix: '联系电话 ',
loadingShort: '同步中',
onlineSingle: '1人在线',
pendingSync: '待同步',
billEmpty: '暂无待支付账单',
billRecordFallback: '账单记录',
pendingContractFound: '发现待签合同',
statusUpdated: '状态已刷新',
refreshFailed: '刷新失败,请稍后重试',
managerWechatCopied: '管家微信已复制',
noManagerWechat: '暂无管家微信',
loadingManagerInfo: '正在加载管家信息',
managerInitialFallback: '管',
billCount: '账单数量',
billUnit: '笔',
billType: '账单类型',
lastCreateTime: '最近生成',
communityEvent: '近期活动',
joinEvent: '立即报名',
signContractNow: '请尽快签订合同',
signContractDesc: '系统已生成待签合同,当前流程已进入签订合同阶段,完成签约后即可继续后续入住流程。',
contractSignedTitle: '合同已签署,等待办理入住',
contractSignedDesc: '您的住宿合同已经完成签署,管家会尽快为您安排办理入住流程。',
contractPendingNote: '检测到待签合同后,流程将停留在签订合同节点,请先完成签名。',
contractSignedNote: '合同签署完成,请等待管家通知办理入住。',
bookingPendingNote: '订单已提交,管家审核通过后会同步更多入住信息。',
contractRoom: '合同房型',
bookedRoomType: '预定房型',
refreshing: '刷新中...',
signContract: '去签约',
refreshStatus: '刷新状态',
pendingAssignment: '待审核分配',
currentStay: '当前入住房间',
controllable: '可控制',
deviceFallback: '设备 {index}',
bedPrefix: '床位 {label}',
multiButlerName: '{name} 等{count}位管家',
onDutyCount: '{count}人值班',
submitRequest: '提交申请',
completeFormBeforeSubmit: '请完善表单信息后提交。'
},
en: {
title: 'My Stay',
bookingInfo: 'Booking Info',
submitBookingStep: 'Submit',
reviewStep: 'Review',
contractStep: 'Contract',
checkinStep: 'Check-in',
notCheckedInTitle: 'No Leased Room Yet',
notCheckedInDesc: 'We provide premium co-living services.\nPlease explore and find your suite first.',
bookingPendingTitle: 'Your booking is under review',
bookingPendingDesc: 'Deposit has been paid. The booking is waiting for approval and butler assignment.',
goSelectRoom: 'Explore Rooms',
stayService: 'Service Request',
butler: 'Community Butler',
online: 'Online · Response < 5 mins',
callNow: 'Call',
publicService: 'Premium Services',
viewAll: 'View All',
leaseTerm: 'Lease',
activeStatus: 'Active',
myBill: 'Monthly Bill',
unpaid: 'Unpaid',
paidOff: 'Paid Off',
payBill: 'Pay Now',
viewBill: 'View Bills',
smartControl: 'Smart Control',
addSmartDevice: 'Add Smart Device',
smartAvailableAfterCheckin: 'Smart devices are available after check-in',
syncingSmartDevices: 'Syncing smart devices...',
smartDeviceConnected: '{count} smart devices connected',
smartDeviceSyncFailed: 'Smart device sync failed',
smartDeviceEmpty: 'No smart devices in this room',
roomDeviceUnconfigured: 'Smart devices are not configured for this room',
smartDeviceLoadFailed: 'Failed to load smart devices',
primaryButlerTag: 'Lead',
noContactPhone: 'No contact number',
loadingButlerInfo: 'Syncing butler info...',
noButlerInfo: 'No butler information yet',
managementContacts: 'Dorm Managers & Admins',
dormitoryManagerTag: 'Dorm Mgr',
counselorTag: 'Admin',
noManagerContacts: 'No management contacts',
managerContactSummary: '{count} butlers are available in this area',
contactPhonePrefix: 'Phone ',
loadingShort: 'Syncing',
onlineSingle: '1 online',
pendingSync: 'Pending',
billEmpty: 'No unpaid bills',
billRecordFallback: 'Bill',
pendingContractFound: 'Pending contract found',
statusUpdated: 'Status updated',
refreshFailed: 'Refresh failed, please try again later',
managerWechatCopied: 'Butler Wechat copied',
noManagerWechat: 'No butler Wechat',
loadingManagerInfo: 'Loading butler info',
managerInitialFallback: 'B',
billCount: 'Bills',
billUnit: '',
billType: 'Type',
lastCreateTime: 'Latest',
communityEvent: 'Community Events',
joinEvent: 'Join Now',
signContractNow: 'Please sign your contract',
signContractDesc: 'A contract is ready for signature. You are now at the contract signing step.',
contractSignedTitle: 'Contract Signed',
contractSignedDesc: 'Your stay contract has been signed. The butler will arrange check-in for you shortly.',
contractPendingNote: 'The flow stays at the contract step until the contract is signed.',
contractSignedNote: 'Contract signed successfully. Awaiting check-in arrangement.',
bookingPendingNote: 'Your booking has been submitted. More stay details will appear after review.',
contractRoom: 'Contract Room',
bookedRoomType: 'Room Type',
refreshing: 'Refreshing...',
signContract: 'Sign Contract',
refreshStatus: 'Refresh Status',
pendingAssignment: 'Pending assignment',
currentStay: 'Current Stay',
controllable: 'Controllable',
deviceFallback: 'Device {index}',
bedPrefix: 'Bed {label}',
multiButlerName: '{name} +{count}',
onDutyCount: '{count} on duty',
submitRequest: 'Submit Request',
completeFormBeforeSubmit: 'Please complete the form before submitting.'
}
}
}
},
computed: {
@@ -780,42 +610,42 @@ export default {
},
pendingStageTitle() {
if (this.hasSignedContract) {
return this.t('contractSignedTitle')
return this.$tr('合同已签署,等待办理入住')
}
if (this.isContractSigningStep) {
return this.t('signContractNow')
return this.$tr('请尽快签订合同')
}
return this.t('bookingPendingTitle')
return this.$tr('您的预订正在审核中')
},
pendingStageDesc() {
if (this.hasSignedContract) {
return this.t('contractSignedDesc')
return this.$tr('您的住宿合同已经完成签署,管家会尽快为您安排办理入住流程。')
}
if (this.isContractSigningStep) {
return this.t('signContractDesc')
return this.$tr('系统已生成待签合同,当前流程已进入签订合同阶段,完成签约后即可继续后续入住流程。')
}
return this.t('bookingPendingDesc')
return this.$tr('定金已支付,预定单正在审批流中。审批通过后将分配管家并等待分房号。')
},
pendingInfoLabel() {
return this.hasSignedContract || this.isContractSigningStep ? this.t('contractRoom') : this.t('bookedRoomType')
return this.hasSignedContract || this.isContractSigningStep ? this.$tr('合同房型') : this.$tr('预定房型')
},
pendingStageNote() {
if (this.hasSignedContract) {
return this.t('contractSignedNote')
return this.$tr('合同签署完成,请等待管家通知办理入住。')
}
if (this.isContractSigningStep) {
return this.t('contractPendingNote')
return this.$tr('检测到待签合同后,流程将停留在签订合同节点,请先完成签名。')
}
return this.t('bookingPendingNote')
return this.$tr('订单已提交,管家审核通过后会同步更多入住信息。')
},
pendingPrimaryActionText() {
if (this.isPendingStatusRefreshing) {
return this.t('refreshing')
return this.$tr('刷新中...')
}
if (this.isContractSigningStep && !this.hasSignedContract) {
return this.t('signContract')
return this.$tr('去签约')
}
return this.t('refreshStatus')
return this.$tr('刷新状态')
},
bookingRoomText() {
if (!this.currentBooking) {
@@ -824,18 +654,18 @@ export default {
return this.currentBooking.roomName
|| this.currentBooking.roomTypeName
|| this.currentBooking.roomTypeNameEn
|| this.t('pendingAssignment')
|| this.$tr('待审核分配')
},
roomCardCover() {
return this.myRoomData && this.myRoomData.image ? this.myRoomData.image : ''
},
roomTitleText() {
if (!this.myRoomData) {
return this.t('currentStay')
return this.$tr('当前入住房间')
}
return this.language === 'zh'
? (pickReadableValue(this.myRoomData.titleZh, this.myRoomData.roomTypeName, this.myRoomData.roomName, this.myRoomData.titleEn) || this.t('currentStay'))
: (pickReadableValue(this.myRoomData.titleEn, this.myRoomData.roomTypeNameEn, this.myRoomData.roomTypeName, this.myRoomData.titleZh) || this.t('currentStay'))
? (pickReadableValue(this.myRoomData.titleZh, this.myRoomData.roomTypeName, this.myRoomData.roomName, this.myRoomData.titleEn) || this.$tr('当前入住房间'))
: (pickReadableValue(this.myRoomData.titleEn, this.myRoomData.roomTypeNameEn, this.myRoomData.roomTypeName, this.myRoomData.titleZh) || this.$tr('当前入住房间'))
},
roomDisplayText() {
if (!this.myRoomData) {
@@ -866,10 +696,10 @@ export default {
},
unpaidBillPrimaryType() {
if (!this.unpaidBillList.length) {
return this.t('billEmpty')
return this.$tr('暂无待支付账单')
}
const firstBill = this.unpaidBillList[0] || {}
return firstBill.referenceTypeDesc || getAccountReferenceTypeLabel(firstBill.referenceType, this.t('billRecordFallback'))
return firstBill.referenceTypeDesc || getAccountReferenceTypeLabel(firstBill.referenceType, this.$tr('账单记录'))
},
latestBillTimeText() {
if (!this.unpaidBillList.length || !this.unpaidBillList[0].createTime) {
@@ -879,25 +709,25 @@ export default {
},
smartSummaryText() {
if (!this.hasCheckedIn) {
return this.t('smartAvailableAfterCheckin')
return this.$tr('入住后可使用智能设备')
}
if (this.isSmartDevicesLoading) {
return this.t('syncingSmartDevices')
return this.$tr('正在同步智能设备...')
}
if (this.smartDevices.length) {
return this.t('smartDeviceConnected').replace('{count}', this.smartDevices.length)
return this.$tr('已接入 {count} 台智能设备').replace('{count}', this.smartDevices.length)
}
if (this.smartDeviceError) {
return this.t('smartDeviceSyncFailed')
return this.$tr('智能设备同步失败')
}
return this.t('smartDeviceEmpty')
return this.$tr('当前房间暂无智能设备')
},
smartQuickDeviceCards() {
return this.smartDevices.map((device, index) => ({
...device,
key: device.renderKey || device.deviceId || device.deviceNo || `smart-device-${index}`,
name: device.deviceName || this.t('deviceFallback').replace('{index}', index + 1),
stateText: device.stateText || this.t('controllable')
name: device.deviceName || this.$tr('设备 {index}').replace('{index}', index + 1),
stateText: device.stateText || this.$tr('可控制')
}))
},
stayLocationText() {
@@ -910,7 +740,7 @@ export default {
this.myRoomData.buildingName || '',
this.myRoomData.buildingSectionName || '',
this.myRoomData.floorName || this.myRoomData.floor || '',
bedLabel ? this.t('bedPrefix').replace('{label}', bedLabel) : ''
bedLabel ? this.$tr('床位 {label}').replace('{label}', bedLabel) : ''
].filter(Boolean).join(' · ')
},
butlerAvatar() {
@@ -930,10 +760,10 @@ export default {
const manager = this.primaryAreaManager || {}
const managerName = manager.displayName || manager.name || manager.userName || manager.nickname || manager.userCode || ''
if (!managerName) {
return this.t('butler')
return this.$tr('专属社区管家')
}
if (this.areaManagers.length > 1) {
return this.t('multiButlerName')
return this.$tr('{name} 等{count}位管家')
.replace('{name}', managerName)
.replace('{count}', this.language === 'zh' ? this.areaManagers.length : this.areaManagers.length - 1)
}
@@ -941,29 +771,29 @@ export default {
},
butlerSubtitle() {
if (this.isAreaManagerLoading) {
return this.t('loadingButlerInfo')
return this.$tr('正在同步管家信息...')
}
if (this.areaManagers.length > 1) {
return this.t('managerContactSummary').replace('{count}', this.areaManagers.length)
return this.$tr('当前区域共有 {count} 位管家可联系').replace('{count}', this.areaManagers.length)
}
const manager = this.primaryAreaManager || {}
const phone = this.getManagerPhone(manager)
if (phone) {
return `${this.t('contactPhonePrefix')}${phone}`
return `${this.$tr('联系电话 ')}${phone}`
}
return this.t('online')
return this.$tr('当前在线 · 响应时间 < 5分钟')
},
butlerCountText() {
if (this.isAreaManagerLoading) {
return this.t('loadingShort')
return this.$tr('同步中')
}
if (this.areaManagers.length > 1) {
return this.t('onDutyCount').replace('{count}', this.areaManagers.length)
return this.$tr('{count}人值班').replace('{count}', this.areaManagers.length)
}
if (this.areaManagers.length === 1) {
return this.t('onlineSingle')
return this.$tr('1人在线')
}
return this.t('pendingSync')
return this.$tr('待同步')
},
butlerPhoneNumber() {
const manager = this.primaryAreaManager || {}
@@ -995,9 +825,6 @@ export default {
this.loadUnpaidBills()
},
methods: {
t(key, params) {
return this.$tm('pages.index.component.StayServiceTab', key, params)
},
pickItemText(item = {}, zhField, enField, fallback = '') {
if (this.language === 'en') {
const shortField = `${enField}Short`
@@ -1139,7 +966,7 @@ export default {
const roomCode = this.getCurrentSmartRoomCode()
if (!brandId || !roomCode) {
this.resetSmartDevicesState()
this.smartDeviceError = this.t('roomDeviceUnconfigured')
this.smartDeviceError = this.$tr('房间智能设备未配置')
return Promise.resolve()
}
@@ -1162,7 +989,7 @@ export default {
.catch((error) => {
console.error('load stay smart devices failed', error)
this.smartDevices = []
this.smartDeviceError = (error && (error.message || error.msg)) || this.t('smartDeviceLoadFailed')
this.smartDeviceError = (error && (error.message || error.msg)) || this.$tr('智能设备加载失败')
})
.finally(() => {
this.isSmartDevicesLoading = false
@@ -1397,15 +1224,15 @@ export default {
})
uni.showToast({
title: pendingContracts.length
? this.t('pendingContractFound')
: this.t('statusUpdated'),
? this.$tr('发现待签合同')
: this.$tr('状态已刷新'),
icon: 'success'
})
})
.catch((error) => {
console.error('refresh pending status failed', error)
uni.showToast({
title: this.t('refreshFailed'),
title: this.$tr('刷新失败,请稍后重试'),
icon: 'none'
})
})
@@ -1430,10 +1257,10 @@ export default {
},
buildServiceSubmitUrl(item = {}) {
const roomData = this.myRoomData || {}
const titleZh = item.title || item.titleEn || item.nameZh || item.nameEn || this.t('submitRequest')
const titleEn = item.titleEnShort || item.titleEn || item.title || item.nameEn || item.nameZh || this.t('submitRequest')
const descZh = item.desc || item.descEn || this.t('completeFormBeforeSubmit')
const descEn = compactEnglishText(item.descEn || item.desc || '', 68) || item.descEn || item.desc || this.t('completeFormBeforeSubmit')
const titleZh = item.title || item.titleEn || item.nameZh || item.nameEn || this.$tr('提交申请')
const titleEn = item.titleEnShort || item.titleEn || item.title || item.nameEn || item.nameZh || this.$tr('提交申请')
const descZh = item.desc || item.descEn || this.$tr('请完善表单信息后提交。')
const descEn = compactEnglishText(item.descEn || item.desc || '', 68) || item.descEn || item.desc || this.$tr('请完善表单信息后提交。')
const title = this.language === 'en'
? titleEn
: titleZh
@@ -1451,7 +1278,7 @@ export default {
roomTypeName: item.key === 'change-room' ? (roomData.roomTypeName || roomData.titleZh || '') : '',
roomName: roomData.roomLabel || ''
}
return `/pkgService/submit_request/submit_request?title=${encodeURIComponent(title || this.t('submitRequest'))}&titleZh=${encodeURIComponent(titleZh)}&titleEn=${encodeURIComponent(titleEn)}&desc=${encodeURIComponent(desc || this.t('completeFormBeforeSubmit'))}&descZh=${encodeURIComponent(descZh)}&descEn=${encodeURIComponent(descEn)}&payload=${encodeURIComponent(JSON.stringify(payload))}`
return `/pkgService/submit_request/submit_request?title=${encodeURIComponent(title || this.$tr('提交申请'))}&titleZh=${encodeURIComponent(titleZh)}&titleEn=${encodeURIComponent(titleEn)}&desc=${encodeURIComponent(desc || this.$tr('请完善表单信息后提交。'))}&descZh=${encodeURIComponent(descZh)}&descEn=${encodeURIComponent(descEn)}&payload=${encodeURIComponent(JSON.stringify(payload))}`
},
buildTicketWorkbenchUrl(item = {}) {
const serviceTypeMap = {
@@ -1506,7 +1333,7 @@ export default {
fail: (error) => {
console.error('跳转送餐小程序失败', error)
uni.showToast({
title: this.t('mealDeliveryJumpFailed'),
title: this.$tr('跳转送餐小程序失败'),
icon: 'none'
})
}
@@ -1523,7 +1350,7 @@ export default {
},
getManagerInitial(manager = {}) {
const name = this.getManagerName(manager)
return String(name).trim().slice(0, 1) || this.t('managerInitialFallback')
return String(name).trim().slice(0, 1) || this.$tr('管')
},
handleButlerWechat() {
const manager = this.primaryAreaManager || {}
@@ -1533,7 +1360,7 @@ export default {
data: String(wechat),
success: () => {
uni.showToast({
title: this.t('managerWechatCopied'),
title: this.$tr('管家微信已复制'),
icon: 'success'
})
}
@@ -1541,7 +1368,7 @@ export default {
return
}
uni.showToast({
title: this.t('noManagerWechat'),
title: this.$tr('暂无管家微信'),
icon: 'none'
})
},
@@ -1549,7 +1376,7 @@ export default {
const phone = this.getManagerPhone(manager)
if (!phone) {
uni.showToast({
title: this.t('noContactPhone'),
title: this.$tr('暂无联系电话'),
icon: 'none'
})
return
@@ -1561,7 +1388,7 @@ export default {
handleButlerCall() {
if (!this.butlerPhoneNumber) {
uni.showToast({
title: this.isAreaManagerLoading ? this.t('loadingManagerInfo') : this.t('noContactPhone'),
title: this.isAreaManagerLoading ? this.$tr('正在加载管家信息') : this.$tr('暂无联系电话'),
icon: 'none'
})
return

View File

@@ -6,7 +6,7 @@
<view class="loading-logo">
<text class="tn-icon-moon tn-color-white" style="font-size: 80rpx; transform: rotate(-45deg); display: block;"></text>
</view>
<view class="loading-desc tn-margin-top-lg" style="color: rgba(255,255,255,0.8); letter-spacing: 4rpx; font-size: 32rpx; font-weight: bold;">{{ t('loadingBrand') }}</view>
<view class="loading-desc tn-margin-top-lg" style="color: rgba(255,255,255,0.8); letter-spacing: 4rpx; font-size: 32rpx; font-weight: bold;">{{ $tr('绿居公寓') }}</view>
<view class="loading-spinner tn-margin-top-xl">
<tn-loading mode="circle" color="#00BBE8" size="60"></tn-loading>
</view>
@@ -15,21 +15,21 @@
<!-- #ifdef MP-WEIXIN -->
<view v-if="showPrivacyPopup" class="privacy-mask">
<view class="privacy-dialog">
<view class="privacy-title">{{ t('privacyTitle') }}</view>
<view class="privacy-title">{{ $tr('隐私授权提示') }}</view>
<view class="privacy-content">
{{ t('privacyContentPrefix') }}
<text class="privacy-link" @click="openPrivacyContract">{{ t('privacyContractName') }}</text>
{{ t('privacyContentSuffix') }}
{{ $tr('进入小程序前,请先阅读并同意') }}
<text class="privacy-link" @click="openPrivacyContract">{{ $tr('《用户隐私保护指引》') }}</text>
{{ $tr(',同意后才能继续使用相关功能。') }}
</view>
<view class="privacy-action-row">
<view class="privacy-action-btn" @click="handlePrivacyDisagree">{{ t('privacyDisagree') }}</view>
<view class="privacy-action-btn" @click="handlePrivacyDisagree">{{ $tr('暂不授权') }}</view>
<button
id="index-privacy-agree-btn"
class="privacy-action-btn is-primary"
open-type="agreePrivacyAuthorization"
@agreeprivacyauthorization="handleAgreePrivacyAuthorization"
>
{{ t('privacyAgree') }}
{{ $tr('同意并继续') }}
</button>
</view>
</view>
@@ -156,50 +156,18 @@
pendingContracts: []
},
tabDefinitions: [
{ id: 'select-room', labelKey: 'tabSelectRoom', icon: 'tn-icon-home-fill', image: '/static/tabbar/tn-tabbar0.png', component: 'SelectRoomTab', ref: 'selectRoomTab', refreshMethod: 'checkLoginStatus' },
{ id: 'stay-service', labelKey: 'tabStayService', icon: 'tn-icon-safe', image: '/static/tabbar/tn-tabbar1.png', component: 'StayServiceTab', ref: 'stayServiceTab', refreshMethod: 'checkLocalStatus' },
{ id: 'my-application', labelKey: 'tabMyApplication', icon: 'tn-icon-order', image: '/static/tabbar/tn-tabbar2.png', component: 'MyApplicationTab', ref: 'myApplicationTab', refreshMethod: 'load' },
{ id: 'message-center', labelKey: 'tabMessageCenter', icon: 'tn-icon-message', image: '/static/tabbar/tn-tabbar3.png', component: 'MessageCenterTab', ref: 'messageCenterTab', refreshMethod: 'load' },
{ id: 'profile-center', labelKey: 'tabProfileCenter', icon: 'tn-icon-my-fill', image: '/static/tabbar/tn-tabbar3.png', component: 'ProfileCenterTab', ref: 'profileCenterTab', refreshMethod: 'checkLoginStatus' }
{ id: 'select-room', labelKey: '选 房', icon: 'tn-icon-home-fill', image: '/static/tabbar/tn-tabbar0.png', component: 'SelectRoomTab', ref: 'selectRoomTab', refreshMethod: 'checkLoginStatus' },
{ id: 'stay-service', labelKey: '入 住', icon: 'tn-icon-safe', image: '/static/tabbar/tn-tabbar1.png', component: 'StayServiceTab', ref: 'stayServiceTab', refreshMethod: 'checkLocalStatus' },
{ id: 'my-application', labelKey: '申请单', icon: 'tn-icon-order', image: '/static/tabbar/tn-tabbar2.png', component: 'MyApplicationTab', ref: 'myApplicationTab', refreshMethod: 'load' },
{ id: 'message-center', labelKey: '消 息', icon: 'tn-icon-message', image: '/static/tabbar/tn-tabbar3.png', component: 'MessageCenterTab', ref: 'messageCenterTab', refreshMethod: 'load' },
{ id: 'profile-center', labelKey: '我 的', icon: 'tn-icon-my-fill', image: '/static/tabbar/tn-tabbar3.png', component: 'ProfileCenterTab', ref: 'profileCenterTab', refreshMethod: 'checkLoginStatus' }
],
loadedPages: ['select-room'],
isAppInitLoading: true,
isTabSwitching: false,
showPrivacyPopup: false,
resolvePrivacyAuthorization: null,
isCheckingPrivacyAuthorization: false,
i18n: {
zh: {
loadingBrand: '绿居公寓',
shareTitle: '绿居公寓 - 国际化青年社区',
tabSelectRoom: '选 房',
tabStayService: '入 住',
tabMyApplication: '申请单',
tabMessageCenter: '消 息',
tabProfileCenter: '我 的',
privacyTitle: '隐私授权提示',
privacyContentPrefix: '进入小程序前,请先阅读并同意',
privacyContractName: '《用户隐私保护指引》',
privacyContentSuffix: ',同意后才能继续使用相关功能。',
privacyAgree: '同意并继续',
privacyDisagree: '暂不授权'
},
en: {
loadingBrand: 'Luju Apartments',
shareTitle: 'Luju Apartments - International Youth Community',
tabSelectRoom: 'Explore',
tabStayService: 'Stay',
tabMyApplication: 'Applications',
tabMessageCenter: 'Messages',
tabProfileCenter: 'Profile',
privacyTitle: 'Privacy Authorization',
privacyContentPrefix: 'Before entering the mini program, please read and agree to the ',
privacyContractName: 'Privacy Protection Guide',
privacyContentSuffix: ' to continue using related features.',
privacyAgree: 'Agree and Continue',
privacyDisagree: 'Not Now'
}
}
isCheckingPrivacyAuthorization: false
}
},
computed: {
@@ -231,7 +199,7 @@
.filter(item => this.visibleTabIds.includes(item.id))
.map(item => ({
...item,
name: this.t(item.labelKey)
name: this.$tr(item.labelKey)
}))
},
currentTabbarIndex() {
@@ -262,9 +230,6 @@
})
},
methods: {
t(key) {
return this.$tm('pages.index.index', key)
},
initPrivacyAuthorization() {
// #ifdef MP-WEIXIN
if (typeof wx === 'undefined' || !wx.onNeedPrivacyAuthorization) {
@@ -782,14 +747,14 @@
// 分享到朋友
onShareAppMessage() {
return {
title: this.t('shareTitle'),
title: this.$tr('绿居公寓 - 国际化青年社区'),
path: '/pages/index/index'
}
},
// 分享到朋友圈
onShareTimeline() {
return {
title: this.t('shareTitle')
title: this.$tr('绿居公寓 - 国际化青年社区')
}
}
}