Compare commits

...

2 Commits

Author SHA1 Message Date
liangjian
da6bdc6a02 chore: 忽略 .trae IDE 状态目录 2026-07-24 13:47:17 +08:00
liangjian
6e721ccc7e refactor: 国际化文本迁移至全局 locales/messages.js
将组件级 i18n 对象重构为统一由全局 locales/messages.js 管理,确保中英文语言切换时正确显示对应文本。

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

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
.dbg/ .dbg/
.hbuilderx/ .hbuilderx/
.history/ .history/
.trae/
unpackage/ unpackage/
.DS_Store .DS_Store

View File

@@ -57,24 +57,24 @@
<view class="contract-sign-dialog__toolbar"> <view class="contract-sign-dialog__toolbar">
<view class="tool-btn tool-btn--soft" :class="{ disabled: !canUndo }" @tap="handleUndo"> <view class="tool-btn tool-btn--soft" :class="{ disabled: !canUndo }" @tap="handleUndo">
<text class="ri-play-reverse-line"></text> <text class="ri-play-reverse-line"></text>
<text>{{ t('undo') }}</text> <text>{{ $tr('撤销') }}</text>
</view> </view>
<view class="tool-btn tool-btn--soft" :class="{ disabled: !hasSignature }" @tap="handleClear"> <view class="tool-btn tool-btn--soft" :class="{ disabled: !hasSignature }" @tap="handleClear">
<text class="ri-delete-bin-line"></text> <text class="ri-delete-bin-line"></text>
<text>{{ t('rewrite') }}</text> <text>{{ $tr('重写') }}</text>
</view> </view>
<view class="tool-btn tool-btn--ghost" @tap="handleRequestClose"> <view class="tool-btn tool-btn--ghost" @tap="handleRequestClose">
<text class="ri-close-line"></text> <text class="ri-close-line"></text>
<text>{{ t('close') }}</text> <text>{{ $tr('关闭') }}</text>
</view> </view>
<view class="tool-btn tool-btn--primary" :class="{ disabled: !hasSignature }" @tap="handleSave"> <view class="tool-btn tool-btn--primary" :class="{ disabled: !hasSignature }" @tap="handleSave">
<text class="ri-check-line-circle"></text> <text class="ri-check-line-circle"></text>
{{ t('confirmSignature') }} {{ $tr('确认签名') }}
</view> </view>
</view> </view>
<view v-if="!isLandscape" class="contract-sign-dialog__footer-tip"> <view v-if="!isLandscape" class="contract-sign-dialog__footer-tip">
<text class="ri-shield-check-line-fill"></text> <text class="ri-shield-check-line-fill"></text>
<text>{{ t('footerTip') }}</text> <text>{{ $tr('签名前请确认您已完整阅读合同内容') }}</text>
</view> </view>
</view> </view>
<view <view
@@ -84,13 +84,13 @@
> >
<view class="contract-sign-dialog__confirm-mask"></view> <view class="contract-sign-dialog__confirm-mask"></view>
<view class="contract-sign-dialog__confirm-card"> <view class="contract-sign-dialog__confirm-card">
<view class="contract-sign-dialog__confirm-title">{{ t('closeBoardTitle') }}</view> <view class="contract-sign-dialog__confirm-title">{{ $tr('关闭提示') }}</view>
<view class="contract-sign-dialog__confirm-desc"> <view class="contract-sign-dialog__confirm-desc">
{{ t('closeBoardDesc') }} {{ $tr('您还未完成签名,确认要关闭吗?') }}
</view> </view>
<view class="contract-sign-dialog__confirm-actions"> <view class="contract-sign-dialog__confirm-actions">
<view class="contract-sign-dialog__confirm-btn is-cancel" @tap="cancelRequestClose">{{ t('continueSigning') }}</view> <view class="contract-sign-dialog__confirm-btn is-cancel" @tap="cancelRequestClose">{{ $tr('继续签名') }}</view>
<view class="contract-sign-dialog__confirm-btn is-confirm" @tap="confirmRequestClose">{{ t('continueClosing') }}</view> <view class="contract-sign-dialog__confirm-btn is-confirm" @tap="confirmRequestClose">{{ $tr('确认关闭') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -116,46 +116,6 @@ export default {
default: '' default: ''
} }
}, },
i18n: {
zh: {
undo: '撤销',
rewrite: '重写',
close: '关闭',
confirmSignature: '确认签名',
footerTip: '签名前请确认您已完整阅读合同内容',
closeBoardTitle: '关闭提示',
closeBoardDesc: '您还未完成签名,确认要关闭吗?',
continueSigning: '继续签名',
continueClosing: '确认关闭',
missingSignerName: '签名人缺失',
writingHintZh: '请在此处手写中文正楷签名',
writingHintEn: 'Please write your signature here',
signerNameMissingToast: '签名人信息缺失',
signatureRequiredToast: '请先完成签名',
boundsFailedToast: '获取签名范围失败',
signatureTooSmallToast: '签名范围太小,请重新签名',
exportFailedToast: '导出签名失败,请重试'
},
en: {
undo: 'Undo',
rewrite: 'Rewrite',
close: 'Close',
confirmSignature: 'Confirm',
footerTip: 'Please confirm that you have fully read the contract before signing',
closeBoardTitle: 'Notice',
closeBoardDesc: 'You have not finished signing. Are you sure to close?',
continueSigning: 'Continue Signing',
continueClosing: 'Close',
missingSignerName: 'Missing Signer',
writingHintZh: 'Please write your Chinese signature here',
writingHintEn: 'Please write your signature here',
signerNameMissingToast: 'Missing signer name',
signatureRequiredToast: 'Please sign first',
boundsFailedToast: 'Failed to get signature bounds',
signatureTooSmallToast: 'Signature is too small, please try again',
exportFailedToast: 'Failed to export signature, please try again'
}
},
data() { data() {
return { return {
canvasId: 'contract-sign-canvas', canvasId: 'contract-sign-canvas',
@@ -179,7 +139,7 @@ export default {
}, },
displaySignerName() { displaySignerName() {
const name = String(this.signerName || '').trim() const name = String(this.signerName || '').trim()
return name || this.t('missingSignerName') return name || this.$tr('签名人缺失')
}, },
hasSignerName() { hasSignerName() {
return !!String(this.signerName || '').trim() return !!String(this.signerName || '').trim()
@@ -199,7 +159,7 @@ export default {
return this.strokes.length > 0 return this.strokes.length > 0
}, },
writingHint() { writingHint() {
return this.isChineseName ? this.t('writingHintZh') : this.t('writingHintEn') return this.isChineseName ? this.$tr('请在此处手写中文正楷签名') : this.$tr('Please write your signature here')
}, },
guideFontSize() { guideFontSize() {
const length = this.displaySignerName.length const length = this.displaySignerName.length
@@ -281,9 +241,6 @@ export default {
this.ctx = null this.ctx = null
}, },
methods: { methods: {
t(key) {
return this.$translate(this.$options.i18n, key)
},
clearSignatureContent() { clearSignatureContent() {
this.strokes = [] this.strokes = []
this.currentStroke = [] this.currentStroke = []
@@ -561,14 +518,14 @@ export default {
handleSave() { handleSave() {
if (!this.hasSignerName) { if (!this.hasSignerName) {
uni.showToast({ uni.showToast({
title: this.t('signerNameMissingToast'), title: this.$tr('签名人信息缺失'),
icon: 'none' icon: 'none'
}) })
return return
} }
if (!this.hasSignature) { if (!this.hasSignature) {
uni.showToast({ uni.showToast({
title: this.t('signatureRequiredToast'), title: this.$tr('请先完成签名'),
icon: 'none' icon: 'none'
}) })
return return
@@ -576,14 +533,14 @@ export default {
const cropRect = this.getSignatureBounds() const cropRect = this.getSignatureBounds()
if (!cropRect) { if (!cropRect) {
uni.showToast({ uni.showToast({
title: this.t('boundsFailedToast'), title: this.$tr('获取签名范围失败'),
icon: 'none' icon: 'none'
}) })
return return
} }
if (!this.isSignatureQualified(cropRect)) { if (!this.isSignatureQualified(cropRect)) {
uni.showToast({ uni.showToast({
title: this.t('signatureTooSmallToast'), title: this.$tr('签名范围太小,请重新签名'),
icon: 'none' icon: 'none'
}) })
return return
@@ -601,7 +558,7 @@ export default {
.catch(error => { .catch(error => {
console.error('导出签名图片失败', error) console.error('导出签名图片失败', error)
uni.showToast({ uni.showToast({
title: this.t('exportFailedToast'), title: this.$tr('导出签名失败,请重试'),
icon: 'none' icon: 'none'
}) })
}) })

View File

@@ -36,9 +36,9 @@ export default {
return null return null
} }
return { return {
title: this.t('pendingTitle'), title: this.$tr('待签合同提醒'),
desc: this.t('pendingDesc', { count: this.pendingContracts.length }), desc: this.$tr('您有 {count} 份合同待签署', { count: this.pendingContracts.length }),
actionText: this.t('actionText') actionText: this.$tr('去签署')
} }
} }
}, },
@@ -59,9 +59,6 @@ export default {
this.clearCollapseTimer() this.clearCollapseTimer()
}, },
methods: { methods: {
t(key, params) {
return this.$translate(this.i18n, key, params)
},
clearCollapseTimer() { clearCollapseTimer() {
if (this.collapseTimer) { if (this.collapseTimer) {
clearTimeout(this.collapseTimer) clearTimeout(this.collapseTimer)

View File

@@ -945,7 +945,22 @@ const messages = {
resultLabel: "Result", resultLabel: "Result",
totalChargeLabel: "Total Charge", totalChargeLabel: "Total Charge",
wechatCodeFailed: "Failed to get Wechat login code", wechatCodeFailed: "Failed to get Wechat login code",
wechatPayButton: "Wechat Pay" wechatPayButton: "Wechat Pay",
cancelOrderButton: "Cancel Order",
cancelOrderContent: "This will close the current unpaid order. Continue?",
cancelOrderFailed: "Failed to cancel order",
cancelOrderLoading: "Canceling...",
cancelOrderSuccess: "Order canceled",
cancelOrderTitle: "Cancel Order",
paymentNoticeAmountLabel: "Amount",
paymentNoticeBillLabel: "Bill No.",
paymentNoticeCancel: "Later",
paymentNoticeConfirm: "Continue",
paymentNoticeItemLabel: "Charge Item",
paymentNoticeLine1: "Please confirm the bill and payment amount before continuing.",
paymentNoticeLine2: "After payment, the bill status may take a few seconds to sync. Please do not pay repeatedly.",
paymentNoticeLine3: "If the payment is interrupted or the page is closed, return to the bill page later to check the latest result.",
paymentNoticeTitle: "Payment Notice"
} }
}, },
contract_detail: { contract_detail: {
@@ -1257,6 +1272,14 @@ const messages = {
popupTitle: "Select Floor", popupTitle: "Select Floor",
selectFloor: "Select floor" selectFloor: "Select floor"
}, },
RoomTypeSelector: {
emptyRoomTypes: "No room types available",
loadFailed: "Failed to load room types",
loadingRoomTypes: "Loading room types...",
popupTitle: "Select Room Type",
priceUnit: "/month",
selectRoomType: "Select preferred room type"
},
TagSelector: { TagSelector: {
emptyText: "No tags available", emptyText: "No tags available",
loadingText: "Loading tags..." loadingText: "Loading tags..."
@@ -1309,6 +1332,155 @@ const messages = {
utility_recharge: { utility_recharge: {
navTitle: "Bill Management" navTitle: "Bill Management"
} }
},
ticket_center: {
ticket_center: {
navTitle: "Work Orders",
heroBadge: "SERVICE",
heroTitle: "In-stay Ticket Services",
heroDesc: "Repair, renewal, checkout, room change, water delivery, cleaning, and suggestions are all handled here."
}
},
ticket_workbench: {
ticket_workbench: {
areaLabel: "Area",
assigneeLabel: "Assignee",
attachmentsLabel: "Attachments",
cancelProcess: "Cancel Action",
cancelSupplement: "Cancel Supplement",
changeRoomTypeLabel: "Preferred Room Type",
cleaningTimeLabel: "Cleaning Time",
contentLabel: "Content",
currentFlowTag: "Current",
currentStayInfo: "Current Stay",
currentStayOrder: "Current Stay Order",
emptyHistoryDesc: "No tickets of this type yet.",
emptyHistoryTitle: "No Ticket Records",
flowActionTitle: "Available Actions",
historyTab: "History",
leaseLabel: "Lease",
loadMore: "Load More",
loadingMore: "Loading...",
loadOrderFailed: "Failed to load stay context",
noFlowContent: "No handling content",
noMore: "No more records",
refresh: "Refresh",
refreshing: "Refreshing...",
remarkLabel: "Notes",
remarkPlaceholder: "Enter notes to help the staff handle this ticket",
roomLabel: "Room",
roomTypeLabel: "Room Type",
searchPlaceholder: "Search title or content",
selectAssignee: "Select assignee",
selectDate: "Select date",
selectTime: "Select time",
submitSectionDefaultDesc: "The form below will be submitted through the ticket APIs.",
submitTab: "Submit",
submitTicket: "Submit Ticket",
submitting: "Submitting...",
supplementAction: "Supplement",
supplementLabel: "Supplementary Fields",
supplementPlaceholder: "Please enter {label}",
ticketKicker: "WORK ORDER",
uploadImage: "Upload Image",
uploadImageCountExceeded: "Up to 6 images",
uploadImageFailed: "Failed to upload image",
uploadImageOversize: "Image must be less than 5MB",
uploadTip: "Upload JPG or PNG images, up to 6 images and 5MB each.",
uploadTipRepair: "Upload JPG or PNG images, up to 6 images and 5MB each. Repair requires at least 1 image.",
checkoutTimeLabel: "Checkout Time",
renewalTimeLabel: "Renew Until",
submittedAtLabel: "Submitted At",
submitSuccess: "Ticket submitted",
submitFailed: "Failed to submit ticket",
confirmSubmitTitle: "Confirm",
confirmSubmitContent: "Confirm submitting this ticket?",
missingStayContext: "Missing current stay order or room information",
repairAttachmentRequired: "Please upload repair images",
changeAreaRequired: "Please select a room type",
checkoutTimeRequired: "Please select checkout time",
checkoutReasonRequired: "Please fill in the checkout reason",
loadHistoryFailed: "Failed to load ticket history",
loadFlowsFailed: "Failed to load ticket flows",
loadActionsFailed: "Failed to load ticket actions",
continueProcess: "Continue",
loadAssigneeFailed: "Failed to load assignees",
submitSectionActionDesc: "The ticket is in progress and will continue after submission.",
submitSectionSupplementDesc: "The ticket is on hold and will continue after supplementing the information.",
currentStatusPending: "Pending",
currentStatusProcessing: "Processing",
currentStatusResolved: "Resolved",
currentStatusClosed: "Closed",
allStatus: "All",
pendingStatus: "Pending",
processingStatus: "Processing",
resolvedStatus: "Resolved",
closedStatus: "Closed"
}
},
room_inspection: {
room_inspection: {
pageTitle: "Inspection",
pageDesc: "Review and submit inspection results for the current order",
inspectionListTitle: "Inspection Records",
roomNoLabel: "Room No.",
inspectionTypeLabel: "Inspection Type",
inspectionStatusLabel: "Status",
inspectionTimeLabel: "Created At",
inspectionRemarksLabel: "Remarks",
inspectionRemarksPlaceholder: "Add notes for this inspection",
inspectionItemsTitle: "Inspection Items",
inspectionItemsDesc: "Check each item according to the on-site condition",
deviceFallback: "Unnamed Device",
requiredTag: "Required",
expectedQuantityLabel: "Expected Qty",
inspectionResultLabel: "Result",
checkedQuantityLabel: "Checked Qty",
checkedQuantityPlaceholder: "Enter quantity",
problemDetailLabel: "Problem Detail",
problemDetailPlaceholder: "Describe any abnormal, missing, or damaged condition",
photosLabel: "Photos",
addPhoto: "Add",
submitAction: "Submit",
loadingDesc: "Syncing inspection data...",
emptyTitle: "No inspections",
emptyDesc: "No inspection record is available for the current order.",
emptyItemsTitle: "No items",
emptyItemsDesc: "No inspection item is available for this inspection.",
submitSuccess: "Inspection submitted",
submitFailed: "Failed to submit",
submitting: "Submitting...",
missingInspectionNo: "Missing inspection record",
uploadFailed: "Failed to upload image",
chooseImageFailed: "Failed to choose image",
resultRequired: "Please complete the result selection for all items first"
}
}
},
components: {
ContractSignDialog: {
undo: "Undo",
rewrite: "Rewrite",
close: "Close",
confirmSignature: "Confirm",
footerTip: "Please confirm that you have fully read the contract before signing",
closeBoardTitle: "Notice",
closeBoardDesc: "You have not finished signing. Are you sure to close?",
continueSigning: "Continue Signing",
continueClosing: "Close",
missingSignerName: "Missing Signer",
writingHintZh: "Please write your Chinese signature here",
writingHintEn: "Please write your signature here",
signerNameMissingToast: "Missing signer name",
signatureRequiredToast: "Please sign first",
boundsFailedToast: "Failed to get signature bounds",
signatureTooSmallToast: "Signature is too small, please try again",
exportFailedToast: "Failed to export signature, please try again"
},
ContractAlert: {
actionText: "Sign Now",
pendingDesc: "You have {count} contract(s) pending signature",
pendingTitle: "Contract Pending Signature"
} }
} }
}, },
@@ -2258,7 +2430,22 @@ const messages = {
resultLabel: "处理结果", resultLabel: "处理结果",
totalChargeLabel: "应收合计", totalChargeLabel: "应收合计",
wechatCodeFailed: "获取微信登录凭证失败", wechatCodeFailed: "获取微信登录凭证失败",
wechatPayButton: "微信支付" wechatPayButton: "微信支付",
cancelOrderButton: "取消订单",
cancelOrderContent: "取消后将关闭当前未支付订单,是否继续?",
cancelOrderFailed: "取消订单失败",
cancelOrderLoading: "取消中...",
cancelOrderSuccess: "订单已取消",
cancelOrderTitle: "取消订单",
paymentNoticeAmountLabel: "支付金额",
paymentNoticeBillLabel: "账单号",
paymentNoticeCancel: "稍后再付",
paymentNoticeConfirm: "继续支付",
paymentNoticeItemLabel: "费用项目",
paymentNoticeLine1: "请确认本次支付账单和金额无误后再继续付款。",
paymentNoticeLine2: "支付完成后,账单状态可能需要几秒同步,请勿重复支付。",
paymentNoticeLine3: "若支付中断或页面关闭,可稍后返回账单页查看最新结果。",
paymentNoticeTitle: "用户须知"
} }
}, },
contract_detail: { contract_detail: {
@@ -2570,6 +2757,14 @@ const messages = {
popupTitle: "选择意向楼层", popupTitle: "选择意向楼层",
selectFloor: "请选择楼层" selectFloor: "请选择楼层"
}, },
RoomTypeSelector: {
emptyRoomTypes: "暂无可选房型",
loadFailed: "房型列表加载失败",
loadingRoomTypes: "房型加载中...",
popupTitle: "选择意向房型",
priceUnit: "/月",
selectRoomType: "请选择意向房型"
},
TagSelector: { TagSelector: {
emptyText: "暂无标签", emptyText: "暂无标签",
loadingText: "标签加载中..." loadingText: "标签加载中..."
@@ -2622,6 +2817,155 @@ const messages = {
utility_recharge: { utility_recharge: {
navTitle: "账单管理" navTitle: "账单管理"
} }
},
ticket_center: {
ticket_center: {
navTitle: "工单中心",
heroBadge: "SERVICE",
heroTitle: "住中工单服务",
heroDesc: "报修、续住、退宿、调宿、送水、清洁和意见建议都统一从这里进入,提交后可继续查看流转记录。"
}
},
ticket_workbench: {
ticket_workbench: {
areaLabel: "所属区域",
assigneeLabel: "处理目标",
attachmentsLabel: "附件图片",
cancelProcess: "退出处理",
cancelSupplement: "取消补充",
changeRoomTypeLabel: "意向房型",
cleaningTimeLabel: "预约清洁时间",
contentLabel: "申请内容",
currentFlowTag: "当前节点",
currentStayInfo: "当前入住信息",
currentStayOrder: "当前入住订单",
emptyHistoryDesc: "当前类型还没有工单记录。",
emptyHistoryTitle: "暂无工单记录",
flowActionTitle: "可执行操作",
historyTab: "记录查询",
leaseLabel: "租期",
loadMore: "加载更多",
loadingMore: "加载中...",
loadOrderFailed: "入住信息加载失败",
noFlowContent: "暂无处理内容",
noMore: "没有更多了",
refresh: "刷新",
refreshing: "刷新中...",
remarkLabel: "补充说明",
remarkPlaceholder: "请输入补充说明,帮助工作人员更快处理",
roomLabel: "当前房间",
roomTypeLabel: "当前房型",
searchPlaceholder: "搜索工单标题或内容",
selectAssignee: "请选择处理目标",
selectDate: "请选择日期",
selectTime: "请选择时间",
submitSectionDefaultDesc: "以下内容将通过工单接口提交并进入后续流转。",
submitTab: "提交申请",
submitTicket: "提交工单",
submitting: "提交中...",
supplementAction: "补充信息",
supplementLabel: "补充业务信息",
supplementPlaceholder: "请输入{label}",
ticketKicker: "WORK ORDER",
uploadImage: "上传图片",
uploadImageCountExceeded: "最多上传 6 张图片",
uploadImageFailed: "图片上传失败,请重试",
uploadImageOversize: "图片不能超过 5MB",
uploadTip: "支持上传 JPG、PNG 等图片,单张不超过 5MB,最多 6 张。",
uploadTipRepair: "支持上传 JPG、PNG 等图片,单张不超过 5MB,最多 6 张。报修至少上传 1 张图片。",
checkoutTimeLabel: "预计退宿时间",
renewalTimeLabel: "续住至",
submittedAtLabel: "提交时间",
submitSuccess: "工单提交成功",
submitFailed: "工单提交失败",
confirmSubmitTitle: "确认提交",
confirmSubmitContent: "确认提交当前工单吗?",
missingStayContext: "当前缺少入住订单或房间信息,暂无法提交工单",
repairAttachmentRequired: "请上传报修附件图片",
changeAreaRequired: "请选择意向房型",
checkoutTimeRequired: "请选择退宿时间",
checkoutReasonRequired: "请填写退宿原因",
loadHistoryFailed: "工单记录加载失败",
loadFlowsFailed: "工单流转加载失败",
loadActionsFailed: "工单操作项加载失败",
continueProcess: "继续处理",
loadAssigneeFailed: "处理目标加载失败",
submitSectionActionDesc: "当前工单正在处理中,提交后会继续流转。",
submitSectionSupplementDesc: "当前工单处于挂起状态,补充信息后会继续流转。",
currentStatusPending: "待处理",
currentStatusProcessing: "处理中",
currentStatusResolved: "已解决",
currentStatusClosed: "已结束",
allStatus: "全部",
pendingStatus: "待处理",
processingStatus: "处理中",
resolvedStatus: "已解决",
closedStatus: "已结束"
}
},
room_inspection: {
room_inspection: {
pageTitle: "验房",
pageDesc: "按当前订单查看并提交验房结果",
inspectionListTitle: "验房记录",
roomNoLabel: "房间号",
inspectionTypeLabel: "验房类型",
inspectionStatusLabel: "当前状态",
inspectionTimeLabel: "发起时间",
inspectionRemarksLabel: "备注",
inspectionRemarksPlaceholder: "可填写本次验房的补充说明",
inspectionItemsTitle: "检查项",
inspectionItemsDesc: "请根据现场情况逐项核对",
deviceFallback: "未命名设备",
requiredTag: "必查",
expectedQuantityLabel: "应有数量",
inspectionResultLabel: "检查结果",
checkedQuantityLabel: "实测数量",
checkedQuantityPlaceholder: "请输入数量",
problemDetailLabel: "问题说明",
problemDetailPlaceholder: "请描述异常、缺失或损坏情况",
photosLabel: "现场照片",
addPhoto: "添加",
submitAction: "提交验房",
loadingDesc: "正在同步验房数据...",
emptyTitle: "暂无验房单",
emptyDesc: "当前订单还没有可查看的验房记录。",
emptyItemsTitle: "暂无检查项",
emptyItemsDesc: "当前验房单还没有检查项明细。",
submitSuccess: "验房单已提交",
submitFailed: "提交失败",
submitting: "提交中...",
missingInspectionNo: "未获取到验房单",
uploadFailed: "图片上传失败",
chooseImageFailed: "选择图片失败",
resultRequired: "请先完成所有检查项结果选择"
}
}
},
components: {
ContractSignDialog: {
undo: "撤销",
rewrite: "重写",
close: "关闭",
confirmSignature: "确认签名",
footerTip: "签名前请确认您已完整阅读合同内容",
closeBoardTitle: "关闭提示",
closeBoardDesc: "您还未完成签名,确认要关闭吗?",
continueSigning: "继续签名",
continueClosing: "确认关闭",
missingSignerName: "签名人缺失",
writingHintZh: "请在此处手写中文正楷签名",
writingHintEn: "Please write your signature here",
signerNameMissingToast: "签名人信息缺失",
signatureRequiredToast: "请先完成签名",
boundsFailedToast: "获取签名范围失败",
signatureTooSmallToast: "签名范围太小,请重新签名",
exportFailedToast: "导出签名失败,请重试"
},
ContractAlert: {
actionText: "去签署",
pendingDesc: "您有 {count} 份合同待签署",
pendingTitle: "待签合同提醒"
} }
} }
} }

View File

@@ -1,12 +1,12 @@
<template> <template>
<view class="message-page tn-safe-area-inset-bottom"> <view class="message-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="false" :bottomShadow="false" backgroundColor="transparent" :customBackStyle="{ transition: 'all 0.3s ease' }"> <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> </tn-nav-bar>
<view class="message-content tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px' }"> <view class="message-content tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
<view class="page-header tn-margin-bottom-xl"> <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 class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ subtitleText }}</view>
</view> </view>
@@ -16,31 +16,31 @@
<text class="tn-icon-notice-fill tn-text-xxl"></text> <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 v-if="stats.systemUnread" class="badge tn-flex tn-flex-col-center tn-flex-row-center">{{ stats.systemUnread }}</view>
</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>
<view class="category-item tn-flex tn-flex-direction-column tn-flex-col-center"> <view class="category-item tn-flex tn-flex-direction-column tn-flex-col-center">
<view class="category-icon-box normal-box"> <view class="category-icon-box normal-box">
<text class="tn-icon-coupon-fill tn-text-xxl"></text> <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 v-if="stats.paymentUnread" class="badge tn-flex tn-flex-col-center tn-flex-row-center">{{ stats.paymentUnread }}</view>
</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>
<view class="category-item tn-flex tn-flex-direction-column tn-flex-col-center"> <view class="category-item tn-flex tn-flex-direction-column tn-flex-col-center">
<view class="category-icon-box normal-box"> <view class="category-icon-box normal-box">
<text class="tn-icon-service-fill tn-text-xxl"></text> <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 v-if="stats.butlerUnread" class="badge tn-flex tn-flex-col-center tn-flex-row-center">{{ stats.butlerUnread }}</view>
</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> </view>
<view v-if="inspectionList.length" class="section-card tn-margin-bottom"> <view v-if="inspectionList.length" class="section-card tn-margin-bottom">
<view class="section-head"> <view class="section-head">
<view> <view>
<view class="section-title">{{ t('inspectionTitle') }}</view> <view class="section-title">{{ $tr('巡查提醒') }}</view>
<view class="section-desc">{{ t('inspectionDesc') }}</view> <view class="section-desc">{{ $tr('根据当前入住订单自动汇总巡查提醒记录') }}</view>
</view> </view>
<view class="section-chip is-primary">{{ inspectionReminderCount }} {{ t('inspectionCountUnit') }}</view> <view class="section-chip is-primary">{{ inspectionReminderCount }} {{ $tr('条提醒') }}</view>
</view> </view>
<view <view
@@ -56,7 +56,7 @@
</view> </view>
<view class="inspection-main"> <view class="inspection-main">
<view class="inspection-title">{{ record.typeText }}</view> <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> </view>
<view class="inspection-chips"> <view class="inspection-chips">
@@ -66,23 +66,23 @@
</view> </view>
<view class="inspection-desc">{{ record.summaryText }}</view> <view class="inspection-desc">{{ record.summaryText }}</view>
<view class="inspection-footer"> <view class="inspection-footer">
<view class="inspection-footer-text">{{ t('checkerLabel') }}{{ record.checkerUserCode || '--' }}</view> <view class="inspection-footer-text">{{ $tr('检查人:') }}{{ record.checkerUserCode || '--' }}</view>
<view class="inspection-action">{{ t('viewDetail') }}</view> <view class="inspection-action">{{ $tr('查看详情') }}</view>
</view> </view>
</view> </view>
</view> </view>
<view class="tn-flex tn-flex-row-between tn-flex-col-bottom tn-margin-bottom"> <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"> <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> </view>
<view class="msg-list"> <view class="msg-list">
<view v-if="loading" class="empty-card"> <view v-if="loading" class="empty-card">
<view class="tn-text-bold tn-text-lg tn-color-black">{{ t('loadingTitle') }}</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">{{ t('loadingDesc') }}</view> <view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ $tr('正在同步系统通知和巡查提醒,请稍候。') }}</view>
</view> </view>
<view <view
v-for="(msg, index) in messages" v-for="(msg, index) in messages"
@@ -103,8 +103,8 @@
</view> </view>
</view> </view>
<view v-if="!loading && !messages.length && !inspectionList.length" class="empty-card"> <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-text-bold tn-text-lg tn-color-black">{{ $tr('暂无消息') }}</view>
<view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ t('emptyDesc') }}</view> <view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ $tr('系统通知和巡查提醒会在这里显示。') }}</view>
</view> </view>
</view> </view>
@@ -117,7 +117,7 @@
<view class="inspect-popup-head"> <view class="inspect-popup-head">
<view> <view>
<view class="inspect-popup-title">{{ activeInspectionRecord.typeText }}</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>
<view class="inspect-popup-close" @click="closeInspectionPopup"> <view class="inspect-popup-close" @click="closeInspectionPopup">
<text class="tn-icon-close"></text> <text class="tn-icon-close"></text>
@@ -127,39 +127,39 @@
<scroll-view scroll-y class="inspect-popup-body"> <scroll-view scroll-y class="inspect-popup-body">
<view class="inspect-detail-card"> <view class="inspect-detail-card">
<view class="inspect-detail-row"> <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> <text class="inspect-detail-value">{{ activeInspectionRecord.checkerUserCode || '--' }}</text>
</view> </view>
<view class="inspect-detail-row"> <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> <text class="inspect-detail-value">{{ activeInspectionRecord.resultText }}</text>
</view> </view>
<view class="inspect-detail-row"> <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> <text class="inspect-detail-value">{{ activeInspectionRecord.statusText }}</text>
</view> </view>
</view> </view>
<view v-if="activeInspectionRecord.remark" class="inspect-block"> <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 class="inspect-block-content">{{ activeInspectionRecord.remark }}</view>
</view> </view>
<view v-if="activeInspectionRecord.items.length" class="inspect-block"> <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 <view
v-for="(detail, index) in activeInspectionRecord.items" v-for="(detail, index) in activeInspectionRecord.items"
:key="detail.code || index" :key="detail.code || index"
class="inspect-item" class="inspect-item"
> >
<view class="inspect-item-head"> <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 class="inspect-item-tag">{{ getCheckTypeLabel(detail.checkType) }}</view>
</view> </view>
<view v-if="detail.problemDescription" class="inspect-item-text">{{ t('problemLabel') }}{{ detail.problemDescription }}</view> <view v-if="detail.problemDescription" class="inspect-item-text">{{ $tr('问题:') }}{{ detail.problemDescription }}</view>
<view v-if="detail.remark" class="inspect-item-text">{{ t('remarkLabel') }}{{ detail.remark }}</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"> <view v-if="detail.compensationAmount !== '' && detail.compensationAmount !== null && detail.compensationAmount !== undefined" class="inspect-item-text">
{{ t('compensationLabel') }}{{ detail.compensationAmount }} {{ $tr('赔付金额:') }}{{ detail.compensationAmount }}
</view> </view>
<view v-if="detail.imageUrls.length" class="inspect-image-list"> <view v-if="detail.imageUrls.length" class="inspect-image-list">
<image <image
@@ -176,7 +176,7 @@
</scroll-view> </scroll-view>
<view class="inspect-popup-foot"> <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> </view>
</view> </view>
@@ -214,104 +214,6 @@ export default {
paymentUnread: 0, paymentUnread: 0,
butlerUnread: 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: { computed: {
@@ -319,7 +221,7 @@ export default {
return this.$getLanguage() return this.$getLanguage()
}, },
subtitleText() { 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 return this.language === 'en' ? compactEnglishText(text, 48) : text
}, },
inspectionReminderCount() { inspectionReminderCount() {
@@ -338,9 +240,6 @@ export default {
load(force = false) { load(force = false) {
return this.refreshMessages(force) return this.refreshMessages(force)
}, },
t(key, params) {
return this.$tm('pages.index.component.MessageCenterTab', key, params)
},
getStoreUser() { getStoreUser() {
return (this.$store && this.$store.state && this.$store.state.vuex_user) || {} 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()}`, id: item.code || item.id || `remote-${Date.now()}`,
code: item.code || '', code: item.code || '',
category: this.resolveRemoteCategory(item, title, desc), category: this.resolveRemoteCategory(item, title, desc),
titleZh: rawTitleZh || this.t('notificationFallbackTitle'), titleZh: rawTitleZh || this.$tr('系统通知'),
titleEn: rawTitleEn || this.t('notificationFallbackTitle'), titleEn: rawTitleEn || this.$tr('系统通知'),
descZh: rawDescZh || this.t('notificationFallbackDesc'), descZh: rawDescZh || this.$tr('您有一条新的系统消息'),
descEn: rawDescEn || this.t('notificationFallbackDesc'), descEn: rawDescEn || this.$tr('您有一条新的系统消息'),
contentZh: rawDescZh || this.t('notificationFallbackDesc'), contentZh: rawDescZh || this.$tr('您有一条新的系统消息'),
contentEn: rawDescEn || this.t('notificationFallbackDesc'), contentEn: rawDescEn || this.$tr('您有一条新的系统消息'),
title: title || this.t('notificationFallbackTitle'), title: title || this.$tr('系统通知'),
desc: desc || this.t('notificationFallbackDesc'), desc: desc || this.$tr('您有一条新的系统消息'),
content: desc || this.t('notificationFallbackDesc'), content: desc || this.$tr('您有一条新的系统消息'),
time: formatMessageDateTime(item.createTime || item.updateTime || ''), time: formatMessageDateTime(item.createTime || item.updateTime || ''),
rawTime: item.createTime || item.updateTime || '', rawTime: item.createTime || item.updateTime || '',
unread: item.isRead !== true, unread: item.isRead !== true,
@@ -501,7 +400,7 @@ export default {
})) }))
: [] : []
const issueTexts = items.map(item => item.problemDescription || item.remark || '').filter(Boolean) 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 { return {
code: record.code || '', code: record.code || '',
roomCode: record.roomCode || '', roomCode: record.roomCode || '',
@@ -545,27 +444,27 @@ export default {
}, },
getCheckTypeLabel(value) { getCheckTypeLabel(value) {
const map = { const map = {
0: this.t('checkTypeRoutine'), 0: this.$tr('日常检查'),
1: this.t('checkTypeCheckout'), 1: this.$tr('退房检查'),
2: this.t('checkTypeSpecial') 2: this.$tr('专项检查')
} }
return map[Number(value)] || this.t('checkTypeDefault') return map[Number(value)] || this.$tr('检查记录')
}, },
getCheckRecordStatusLabel(value) { getCheckRecordStatusLabel(value) {
const map = { const map = {
0: this.t('checkStatusPending'), 0: this.$tr('待确认'),
1: this.t('checkStatusProcessing'), 1: this.$tr('处理中'),
2: this.t('checkStatusDone') 2: this.$tr('已完成')
} }
return map[Number(value)] || this.t('checkStatusDefault') return map[Number(value)] || this.$tr('状态待定')
}, },
getCheckResultStatusLabel(value) { getCheckResultStatusLabel(value) {
const map = { const map = {
0: this.t('checkResultNormal'), 0: this.$tr('正常'),
1: this.t('checkResultFix'), 1: this.$tr('需整改'),
2: this.t('checkResultReview') 2: this.$tr('待复核')
} }
return map[Number(value)] || this.t('checkResultDefault') return map[Number(value)] || this.$tr('检查结果')
}, },
getCheckResultStatusClass(value) { getCheckResultStatusClass(value) {
const map = { const map = {
@@ -618,8 +517,8 @@ export default {
if (!unreadRemoteCodes.length) { if (!unreadRemoteCodes.length) {
uni.showToast({ uni.showToast({
title: this.inspectionReminderCount title: this.inspectionReminderCount
? this.t('inspectionNeedManualHandle') ? this.$tr('巡查提醒需单独查看处理')
: this.t('noUnreadNotification'), : this.$tr('暂无未读通知'),
icon: 'none' icon: 'none'
}) })
return return
@@ -642,8 +541,8 @@ export default {
this.buildMessageView() this.buildMessageView()
uni.showToast({ uni.showToast({
title: this.inspectionReminderCount title: this.inspectionReminderCount
? this.t('remoteMarkedReadWithInspection') ? this.$tr('系统通知已标记,巡查提醒需单独处理')
: this.t('remoteMarkedRead'), : this.$tr('系统通知已全部标记为已读'),
icon: 'none' icon: 'none'
}) })
}, },

View File

@@ -1,19 +1,19 @@
<template> <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="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="page-header tn-padding">
<view class="tn-text-bold tn-text-xl tn-color-black page-title">{{ t('pageTitle') }}</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">{{ t('pageDesc') }}</view> <view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ $tr('您的入住和预约看房申请记录') }}</view>
</view> </view>
<scroll-view v-if="reservationOrders.length || list.length" scroll-y class="list-scroll-view tn-padding" @scrolltolower="loadMore"> <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 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__head">
<view class="reservation-flow-panel__title-wrap"> <view class="reservation-flow-panel__title-wrap">
<view class="reservation-flow-panel__title">{{ t('reservationFlowTitle') }}</view> <view class="reservation-flow-panel__title">{{ $tr('预定流程') }}</view>
<view class="reservation-flow-panel__desc">{{ t('reservationFlowDesc') }}</view> <view class="reservation-flow-panel__desc">{{ $tr('当前预定单的流程进度') }}</view>
</view> </view>
<view class="reservation-flow-panel__refresh" @click.stop="refreshStatus"> <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>
</view> </view>
@@ -26,26 +26,26 @@
:class="{ 'reservation-order-chip--active': activeReservationOrderNo === item.orderNo }" :class="{ 'reservation-order-chip--active': activeReservationOrderNo === item.orderNo }"
@click.stop="switchReservationOrder(item)" @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>
</view> </view>
</scroll-view> </scroll-view>
<view v-if="activeReservationOrder" class="reservation-grid-info tn-margin-top-lg"> <view v-if="activeReservationOrder" class="reservation-grid-info tn-margin-top-lg">
<view class="grid-info-item"> <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 class="grid-info-value">{{ activeReservationOrder.areaText || activeReservationOrder.areaName || '--' }}</view>
</view> </view>
<view class="grid-info-item"> <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 class="grid-info-value">{{ activeReservationOrder.roomTypeText || activeReservationOrder.roomTypeName || '--' }}</view>
</view> </view>
<view class="grid-info-item"> <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 class="grid-info-value tn-font-mono">{{ activeReservationOrder.roomNoText || activeReservationOrder.roomNo || '--' }}</view>
</view> </view>
<view class="grid-info-item"> <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 class="grid-info-value">{{ activeReservationOrder.orderStatusDesc || activeReservationOrder.statusText || '--' }}</view>
</view> </view>
</view> </view>
@@ -67,7 +67,7 @@
<view class="v-step-content"> <view class="v-step-content">
<view class="v-step-header"> <view class="v-step-header">
<view class="v-step-title">{{ node.stepDesc || getReservationStepLabel(node.step) }}</view> <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>
<view <view
@@ -75,11 +75,11 @@
class="v-step-bill-card tn-margin-top-sm" class="v-step-bill-card tn-margin-top-sm"
> >
<view class="v-step-bill-row"> <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> <text class="v-step-bill-value">{{ getReservationBillDisplayTitle(getActiveReservationBillDetail()) }}</text>
</view> </view>
<view class="v-step-bill-row"> <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> <text class="v-step-bill-value v-step-bill-value--price">¥{{ formatMoney(getReservationBillDisplayAmount(getActiveReservationBillDetail())) }}</text>
</view> </view>
</view> </view>
@@ -96,7 +96,7 @@
class="v-step-action tn-margin-top-sm" class="v-step-action tn-margin-top-sm"
> >
<view class="step-refresh-btn" @click.stop="refreshStatus"> <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>
</view> </view>
@@ -132,57 +132,57 @@
<view class="card-body tn-margin-top-lg"> <view class="card-body tn-margin-top-lg">
<view class="info-row tn-flex tn-flex-row-between"> <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> <text class="info-value">{{ item.intendedAreaName || '--' }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <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> <text class="info-value">{{ item.userName || item.phone || '--' }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <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> <text class="info-value">{{ formatDateRange(item.start, item.end) }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <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> <text class="info-value">{{ formatDateTime(item.createTime) }}</text>
</view> </view>
</view> </view>
<view v-if="item.remark" class="remark-panel tn-margin-top-lg"> <view v-if="item.remark" class="remark-panel tn-margin-top-lg">
<view class="remark-line"> <view class="remark-line">
<text class="remark-label">{{ t('remarkLabel') }}</text> <text class="remark-label">{{ $tr('补充说明') }}</text>
<text class="remark-text">{{ item.remark }}</text> <text class="remark-text">{{ item.remark }}</text>
</view> </view>
</view> </view>
<view v-if="getStatusReminderText(item)" class="reminder-panel tn-margin-top-lg" @click.stop> <view v-if="getStatusReminderText(item)" class="reminder-panel tn-margin-top-lg" @click.stop>
<view class="reminder-head"> <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"> <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> </view>
<view class="info-row tn-flex tn-flex-row-between"> <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> <text class="info-value">{{ getStatusReminderText(item) }}</text>
</view> </view>
</view> </view>
<view v-if="!isPaidApplication(item) && getApplicationPendingPaymentOrder(item)" class="pending-payment-panel tn-margin-top-lg" @click.stop> <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"> <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> <text class="info-value info-value--highlight">¥{{ formatMoney(getApplicationPendingPaymentAmount(item)) }}</text>
</view> </view>
<view v-if="getApplicationPendingPaymentExpireText(item)" class="info-row tn-flex tn-flex-row-between"> <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) }"> <text class="info-value" :class="{ 'info-value--expired': isApplicationPendingPaymentExpired(item) }">
{{ getApplicationPendingPaymentExpireText(item) }} {{ getApplicationPendingPaymentExpireText(item) }}
</text> </text>
</view> </view>
<view v-if="getApplicationPendingPaymentDescription(item)" class="info-row tn-flex tn-flex-row-between"> <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> <text class="info-value">{{ getApplicationPendingPaymentDescription(item) }}</text>
</view> </view>
</view> </view>
@@ -223,36 +223,36 @@
:data-reference-type-no="resolveReferenceTypeNo(item)" :data-reference-type-no="resolveReferenceTypeNo(item)"
@click.stop="payDepositAction" @click.stop="payDepositAction"
> >
<text class="tn-text-bold">{{ t('payDeposit') }}</text> <text class="tn-text-bold">{{ $tr('支付定金') }}</text>
</view> </view>
</view> </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"> <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> <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-if="hasMore" class="tn-color-gray">{{ $tr('上拉加载更多') }}</text>
<text v-else class="tn-color-gray">{{ t('noMore') }}</text> <text v-else class="tn-color-gray">{{ $tr('没有更多申请了') }}</text>
</view> </view>
</scroll-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"> <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> <tn-empty icon="order" :text="$tr('暂无申请记录')"></tn-empty>
<view class="empty-desc tn-margin-top tn-text-center">{{ t('emptyDesc') }}</view> <view class="empty-desc tn-margin-top tn-text-center">{{ $tr('您还没有提交任何申请,去选房后可发起入住申请或预约看房。') }}</view>
<view class="empty-action tn-margin-top-xl" @click="goSelectRoom"> <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> </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"> <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> <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> </view>
<!-- 支付定金须知弹窗 --> <!-- 支付定金须知弹窗 -->
<tn-popup v-model="showDepositNotice" mode="bottom" length="80%" :borderRadius="32" :closeBtn="true" @close="closeDepositNotice"> <tn-popup v-model="showDepositNotice" mode="bottom" length="80%" :borderRadius="32" :closeBtn="true" @close="closeDepositNotice">
<view class="deposit-notice-popup"> <view class="deposit-notice-popup">
<view class="popup-header"> <view class="popup-header">
<view class="popup-title">{{ t('depositNoticeTitle') }}</view> <view class="popup-title">{{ $tr('租房定金须知') }}</view>
</view> </view>
<scroll-view scroll-y class="popup-body" @scrolltolower="onNoticeScrollToBottom"> <scroll-view scroll-y class="popup-body" @scrolltolower="onNoticeScrollToBottom">
<view class="notice-content"> <view class="notice-content">
@@ -288,8 +288,8 @@
:class="{ 'action-btn--disabled': !hasReadNotice }" :class="{ 'action-btn--disabled': !hasReadNotice }"
@click="confirmAndPayDeposit" @click="confirmAndPayDeposit"
> >
<text class="tn-text-bold">{{ t('agreeAndPay') }}</text> <text class="tn-text-bold">{{ $tr('同意并支付') }}</text>
<text v-if="!hasReadNotice" class="tn-margin-left-xs tn-text-sm">({{ t('scrollToRead') }})</text> <text v-if="!hasReadNotice" class="tn-margin-left-xs tn-text-sm">({{ $tr('请滑动阅读至底部') }})</text>
</view> </view>
</view> </view>
</view> </view>
@@ -408,156 +408,6 @@ function padCountdownUnit(value) {
} }
export default { 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() { data() {
return { return {
currentIndex: 0, currentIndex: 0,
@@ -616,9 +466,6 @@ export default {
this.clearCountdownTimer() this.clearCountdownTimer()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pages.index.component.MyApplicationTab', key, params)
},
load(force = false) { load(force = false) {
return this.reloadList(force) return this.reloadList(force)
}, },
@@ -637,12 +484,12 @@ export default {
}, },
getStatusText(status) { getStatusText(status) {
const map = { const map = {
0: this.t('statusPending'), 0: this.$tr('待审核'),
10: this.t('statusApproved'), 10: this.$tr('已通过'),
20: this.t('statusRejected'), 20: this.$tr('已拒绝'),
99: this.t('statusFinished') 99: this.$tr('已结束')
} }
return map[status] || this.t('statusProcessing') return map[status] || this.$tr('处理中')
}, },
getDisplayStatusText(item = {}) { getDisplayStatusText(item = {}) {
if (this.$getLanguage() !== 'en' && item.statusDesc) { if (this.$getLanguage() !== 'en' && item.statusDesc) {
@@ -685,17 +532,17 @@ export default {
return contractDetail.queryStatusDesc || contractDetail.approvalStatusDesc || contractDetail.statusDesc || getReservationCurrentStepText(item, this.language) return contractDetail.queryStatusDesc || contractDetail.approvalStatusDesc || contractDetail.statusDesc || getReservationCurrentStepText(item, this.language)
} }
if (this.isReservationInspectionPendingReview(item)) { if (this.isReservationInspectionPendingReview(item)) {
return this.t('inspectionPendingReviewHint') return this.$tr('验房已完成,等待宿管审核')
} }
const reservationStepText = getReservationCurrentStepText(item, this.language) const reservationStepText = getReservationCurrentStepText(item, this.language)
if (reservationStepText) { if (reservationStepText) {
return reservationStepText return reservationStepText
} }
if (this.shouldPayDeposit(item)) { if (this.shouldPayDeposit(item)) {
return this.t('statusPendingPayment') return this.$tr('请支付定金')
} }
if (this.isWaitingRoomAssignment(item)) { if (this.isWaitingRoomAssignment(item)) {
return this.t('statusWaitingRoomAssign') return this.$tr('等待预订中心分配房间')
} }
return '' return ''
}, },
@@ -720,10 +567,10 @@ export default {
const startText = this.formatDateTime(start) const startText = this.formatDateTime(start)
const endText = this.formatDateTime(end) const endText = this.formatDateTime(end)
if (String(start).includes('T') || String(end).includes('T')) { 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) { formatMoney(value) {
const amount = Number(value || 0) const amount = Number(value || 0)
@@ -826,7 +673,7 @@ export default {
requestWechatPayment(paymentInfo) { requestWechatPayment(paymentInfo) {
const payOptions = this.normalizeWechatPayOptions(paymentInfo) const payOptions = this.normalizeWechatPayOptions(paymentInfo)
if (!payOptions.timeStamp || !payOptions.nonceStr || !payOptions.package || !payOptions.paySign) { 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) => { return new Promise((resolve, reject) => {
uni.requestPayment({ uni.requestPayment({
@@ -842,7 +689,7 @@ export default {
provider: 'weixin', provider: 'weixin',
success: (res) => { success: (res) => {
if (!res || !res.code) { if (!res || !res.code) {
reject(new Error(this.t('fetchWechatLoginCodeFailed'))) reject(new Error(this.$tr('获取微信登录凭证失败')))
return return
} }
resolve(res.code) resolve(res.code)
@@ -992,12 +839,12 @@ export default {
return '' return ''
} }
if (expireTimestamp <= this.countdownNow) { if (expireTimestamp <= this.countdownNow) {
return this.t('pendingPaymentExpired') return this.$tr('已失效')
} }
return this.formatPendingPaymentCountdown(this.getPendingPaymentRemainingSeconds(order)) return this.formatPendingPaymentCountdown(this.getPendingPaymentRemainingSeconds(order))
}, },
getPendingPaymentActionText(item = {}) { getPendingPaymentActionText(item = {}) {
return this.isApplicationPendingPaymentExpired(item) ? this.t('payDeposit') : this.t('continuePay') return this.isApplicationPendingPaymentExpired(item) ? this.$tr('支付定金') : this.$tr('继续支付')
}, },
getReservationOrderNoFromItem(item = {}) { getReservationOrderNoFromItem(item = {}) {
return getReservationOrderNo(item) return getReservationOrderNo(item)
@@ -1077,14 +924,14 @@ export default {
}, },
getReservationCurrentStepHint(order = {}) { getReservationCurrentStepHint(order = {}) {
if (this.isReservationInspectionPendingReview(order)) { if (this.isReservationInspectionPendingReview(order)) {
return this.t('inspectionPendingReviewHint') return this.$tr('验房已完成,等待宿管审核')
} }
if (this.isReservationContractAwaitingReview(order)) { if (this.isReservationContractAwaitingReview(order)) {
const detail = this.getReservationContractDetail(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)) { if (this.isReservationCheckinProcessing(order)) {
return this.t('checkinPendingHint') return this.$tr('宿管正在办理入住,请耐心等待')
} }
return '' return ''
}, },
@@ -1127,7 +974,7 @@ export default {
|| bill.referenceTypeDesc || bill.referenceTypeDesc
|| bill.billName || bill.billName
|| bill.name || bill.name
|| this.t('depositPaymentDesc') || this.$tr('支付定金')
).trim() ).trim()
}, },
getReservationBillDisplayAmount(bill = {}) { getReservationBillDisplayAmount(bill = {}) {
@@ -1411,14 +1258,14 @@ export default {
if (this.total === 0 && !this.reservationOrders.length) { if (this.total === 0 && !this.reservationOrders.length) {
this.$emit('refresh-shell-state') this.$emit('refresh-shell-state')
uni.showToast({ uni.showToast({
title: this.t('refreshEmptySuccess'), title: this.$tr('未查到申请单,已同步预订单'),
icon: 'none' icon: 'none'
}) })
return return
} }
if (this.list.length || this.reservationOrders.length) { if (this.list.length || this.reservationOrders.length) {
uni.showToast({ uni.showToast({
title: this.t('refreshSuccess'), title: this.$tr('申请状态已刷新'),
icon: 'none' icon: 'none'
}) })
} }
@@ -1507,7 +1354,7 @@ export default {
this.pageIndex = Math.max(1, this.pageIndex - 1) this.pageIndex = Math.max(1, this.pageIndex - 1)
} }
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('loadError'), title: (error && (error.message || error.msg)) || this.$tr('申请记录加载失败'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -1531,9 +1378,9 @@ export default {
}, },
getApplicationTitle(item = {}) { getApplicationTitle(item = {}) {
if (item.isViewingApplication) { if (item.isViewingApplication) {
return this.t('viewingApplicationTitle') return this.$tr('预约看房申请')
} }
return item.intendedRoomTypeName || this.t('stayApplicationTitle') return item.intendedRoomTypeName || this.$tr('入住申请')
}, },
resolveReferenceTypeNo(item = {}) { resolveReferenceTypeNo(item = {}) {
return item.detailCode || '' return item.detailCode || ''
@@ -1549,14 +1396,14 @@ export default {
}, },
getCancelButtonText(item = {}) { getCancelButtonText(item = {}) {
return this.cancelingReferenceTypeNo === this.resolveReferenceTypeNo(item) return this.cancelingReferenceTypeNo === this.resolveReferenceTypeNo(item)
? this.t('cancelOrderLoading') ? this.$tr('取消中...')
: this.t('cancelOrder') : this.$tr('取消订单')
}, },
confirmCancelOrder() { confirmCancelOrder() {
return new Promise((resolve) => { return new Promise((resolve) => {
uni.showModal({ uni.showModal({
title: this.t('cancelOrderTitle'), title: this.$tr('取消订单'),
content: this.t('cancelOrderContent'), content: this.$tr('取消后将关闭当前未支付的定金订单,是否继续?'),
confirmColor: '#ef4444', confirmColor: '#ef4444',
success: (res) => resolve(!!(res && res.confirm)), success: (res) => resolve(!!(res && res.confirm)),
fail: () => resolve(false) fail: () => resolve(false)
@@ -1575,7 +1422,7 @@ export default {
return { return {
...item, ...item,
status: 99, status: 99,
statusDesc: this.t('statusFinished'), statusDesc: this.$tr('已结束'),
pendingDepositBill: null, pendingDepositBill: null,
pendingPaymentOrder: null pendingPaymentOrder: null
} }
@@ -1611,14 +1458,14 @@ export default {
await this.reloadList(true) await this.reloadList(true)
this.$emit('refresh-shell-state') this.$emit('refresh-shell-state')
uni.showToast({ uni.showToast({
title: this.t('cancelOrderSuccess'), title: this.$tr('订单已取消'),
icon: 'success' icon: 'success'
}) })
}) })
}) })
.catch((error) => { .catch((error) => {
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('cancelOrderFailed'), title: (error && (error.message || error.msg)) || this.$tr('取消订单失败'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -1633,7 +1480,7 @@ export default {
: '' : ''
if (!referenceTypeNo) { if (!referenceTypeNo) {
uni.showToast({ uni.showToast({
title: this.t('missingBillCode'), title: this.$tr('申请单编号缺失'),
icon: 'none' icon: 'none'
}) })
return return
@@ -1659,7 +1506,7 @@ export default {
: '' : ''
if (!referenceTypeNo) { if (!referenceTypeNo) {
uni.showToast({ uni.showToast({
title: this.t('missingBillCode'), title: this.$tr('申请单编号缺失'),
icon: 'none' icon: 'none'
}) })
return return
@@ -1732,7 +1579,7 @@ export default {
referenceTypeNo: pickFirst(candidateReferenceTypeNos), referenceTypeNo: pickFirst(candidateReferenceTypeNos),
billNo: pickFirst(candidateBillNos), billNo: pickFirst(candidateBillNos),
paymentNo: pickFirst(candidatePaymentNos), paymentNo: pickFirst(candidatePaymentNos),
description: String(currentNode.stepDesc || fallbackContext.description || '').trim() || this.t('depositPaymentDesc') description: String(currentNode.stepDesc || fallbackContext.description || '').trim() || this.$tr('支付定金')
} }
}, },
resolvePendingPaymentContextByPaymentNo(paymentNo = '') { resolvePendingPaymentContextByPaymentNo(paymentNo = '') {
@@ -1788,11 +1635,11 @@ export default {
let referenceTypeNo = String(context.referenceTypeNo || '').trim() let referenceTypeNo = String(context.referenceTypeNo || '').trim()
let billNo = String(context.billNo || '').trim() let billNo = String(context.billNo || '').trim()
const paymentNo = String(context.paymentNo || '').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) { if (!referenceTypeNo && !billNo && !paymentNo) {
uni.showToast({ uni.showToast({
title: this.t('missingBillCode'), title: this.$tr('申请单编号缺失'),
icon: 'none' icon: 'none'
}) })
return return
@@ -1802,7 +1649,7 @@ export default {
saveActiveApplicationCode(referenceTypeNo) saveActiveApplicationCode(referenceTypeNo)
} }
uni.showLoading({ title: this.t('loadingBill') }) uni.showLoading({ title: this.$tr('正在获取账单...') })
try { try {
let bill = null let bill = null
let pendingPaymentOrder = billNo ? await this.getPendingPaymentOrder(billNo).catch(() => null) : null let pendingPaymentOrder = billNo ? await this.getPendingPaymentOrder(billNo).catch(() => null) : null
@@ -1850,7 +1697,7 @@ export default {
billName: description billName: description
} }
} else { } else {
uni.showToast({ title: this.t('noPendingDepositBill'), icon: 'none' }) uni.showToast({ title: this.$tr('暂无待支付定金账单'), icon: 'none' })
return return
} }
} else { } else {
@@ -1872,11 +1719,11 @@ export default {
} }
if (!billNo) { if (!billNo) {
uni.showToast({ title: this.t('noPendingDepositBill'), icon: 'none' }) uni.showToast({ title: this.$tr('暂无待支付定金账单'), icon: 'none' })
return return
} }
uni.showLoading({ title: this.t('loadingPayment') }) uni.showLoading({ title: this.$tr('正在拉起支付...') })
const reusablePendingPaymentOrder = pendingPaymentOrder && !this.isApplicationPendingPaymentExpired({ const reusablePendingPaymentOrder = pendingPaymentOrder && !this.isApplicationPendingPaymentExpired({
pendingPaymentOrder pendingPaymentOrder
@@ -1888,7 +1735,7 @@ export default {
await this.requestWechatPayment(reusablePendingPaymentOrder.cashier.paymentInfo) await this.requestWechatPayment(reusablePendingPaymentOrder.cashier.paymentInfo)
} else { } else {
if (!referenceTypeNo || !bill) { if (!referenceTypeNo || !bill) {
throw new Error(this.t('fetchPaymentParamsFailed')) throw new Error(this.$tr('获取支付参数失败'))
} }
const wechatCode = await this.getWechatLoginCode() const wechatCode = await this.getWechatLoginCode()
const payRes = await paymentBillPay({ const payRes = await paymentBillPay({
@@ -1905,11 +1752,11 @@ export default {
await this.requestWechatPayment(cashier.paymentInfo) await this.requestWechatPayment(cashier.paymentInfo)
} }
uni.showToast({ title: this.t('paymentSuccess'), icon: 'success' }) uni.showToast({ title: this.$tr('支付成功'), icon: 'success' })
} catch (error) { } 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)) { if (/cancel/i.test(errMsg)) {
uni.showToast({ title: this.t('paymentCanceled'), icon: 'none' }) uni.showToast({ title: this.$tr('已取消支付'), icon: 'none' })
} else { } else {
uni.showToast({ title: errMsg, icon: 'none' }) uni.showToast({ title: errMsg, icon: 'none' })
} }
@@ -1946,7 +1793,7 @@ export default {
if (step === RESERVATION_FLOW_STEP.BILL) { if (step === RESERVATION_FLOW_STEP.BILL) {
if (!referenceTypeNo) { if (!referenceTypeNo) {
uni.showToast({ uni.showToast({
title: this.t('missingBillCode'), title: this.$tr('申请单编号缺失'),
icon: 'none' icon: 'none'
}) })
return return
@@ -1964,7 +1811,7 @@ export default {
} }
if (!contractNo) { if (!contractNo) {
uni.showToast({ uni.showToast({
title: this.t('missingContractNo'), title: this.$tr('合同编号缺失'),
icon: 'none' icon: 'none'
}) })
return return
@@ -2010,7 +1857,7 @@ export default {
const contractNo = String(order && order.reservationCurrentNode && order.reservationCurrentNode.refNo || '').trim() const contractNo = String(order && order.reservationCurrentNode && order.reservationCurrentNode.refNo || '').trim()
if (!contractNo) { if (!contractNo) {
uni.showToast({ uni.showToast({
title: this.t('missingContractNo'), title: this.$tr('合同编号缺失'),
icon: 'none' icon: 'none'
}) })
return return

View File

@@ -1,4 +1,4 @@
<template> <template>
<view class="profile-page tn-safe-area-inset-bottom"> <view class="profile-page tn-safe-area-inset-bottom">
<!-- 头部背景与用户信息 (悬浮质感设计) --> <!-- 头部背景与用户信息 (悬浮质感设计) -->
@@ -43,7 +43,7 @@
</view> </view>
<view class="tn-margin-left-md"> <view class="tn-margin-left-md">
<view class="user-name clamp-text-1"> <view class="user-name clamp-text-1">
{{ t('guest') }} {{ $tr('游客') }}
</view> </view>
<view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ guestHeaderDesc }}</view> <view class="tn-color-gray tn-text-sm tn-margin-top-xs">{{ guestHeaderDesc }}</view>
</view> </view>
@@ -62,17 +62,17 @@
<view class="stats-row tn-flex tn-flex-row-between"> <view class="stats-row tn-flex tn-flex-row-between">
<view class="stat-item"> <view class="stat-item">
<view class="stat-value">{{ getStayDaysText() }}</view> <view class="stat-value">{{ getStayDaysText() }}</view>
<view class="stat-label">{{ t('daysIn') }}</view> <view class="stat-label">{{ $tr('入住天数') }}</view>
</view> </view>
<view class="stat-divider"></view> <view class="stat-divider"></view>
<view class="stat-item"> <view class="stat-item">
<view class="stat-value">{{ getActivityCountText() }}</view> <view class="stat-value">{{ getActivityCountText() }}</view>
<view class="stat-label">{{ t('activities') }}</view> <view class="stat-label">{{ $tr('参与活动') }}</view>
</view> </view>
<view class="stat-divider"></view> <view class="stat-divider"></view>
<view class="stat-item"> <view class="stat-item">
<view class="stat-value">{{ getPointsText() }}</view> <view class="stat-value">{{ getPointsText() }}</view>
<view class="stat-label">{{ t('points') }}</view> <view class="stat-label">{{ $tr('积分') }}</view>
</view> </view>
</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="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 class="tn-flex tn-flex-row-between tn-flex-col-center">
<view> <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"> <view class="tn-margin-top-sm tn-text-sm tn-color-gray">
{{ registrationApplication ? t('registrationRecordSubmittedDesc') : t('registrationRecordGuestDesc') }} {{ registrationApplication ? $tr('当前微信账号已提交注册申请,可查看历史记录与审批状态。') : $tr('未登录也可查看当前微信账号提交的注册申请记录。') }}
</view> </view>
</view> </view>
<view class="custom-btn btn-primary guest-registration-btn" @click="goToRegistrationRecords"> <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>
</view> </view>
<view class="application-status-card tn-padding-lg tn-margin-bottom-lg" v-if="isLogin && pendingContractCount"> <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-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"> <view class="status-tag status-pending">
{{ t('contractPendingTag') + ' ' + pendingContractCount }} {{ $tr('待签约') + ' ' + pendingContractCount }}
</view> </view>
</view> </view>
<view class="tn-margin-top-sm tn-color-gray tn-text-sm"> <view class="tn-margin-top-sm tn-color-gray tn-text-sm">
{{ t('contractPendingDesc') }} {{ $tr('您当前有待签署合同,请尽快查看并完成签约。') }}
</view> </view>
<view class="tn-margin-top tn-flex tn-flex-row-between tn-flex-col-center"> <view class="tn-margin-top tn-flex tn-flex-row-between tn-flex-col-center">
<view class="tn-color-gray tn-text-xs"> <view class="tn-color-gray tn-text-xs">
{{ t('pendingCountText') + ':' + pendingContractCount }} {{ $tr('待签合同') + ':' + pendingContractCount }}
</view> </view>
<view class="custom-btn btn-primary" style="padding: 8rpx 28rpx;" @click="goToContractList"> <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>
</view> </view>
<!-- 核心服务 (宫格) --> <!-- 核心服务 (宫格) -->
<view class="core-services-card tn-padding-lg tn-margin-bottom-lg" v-if="isLogin"> <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="tn-flex tn-flex-row-between">
<view <view
class="core-service-item tn-flex tn-flex-direction-column tn-flex-col-center" 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"> <view class="service-icon-box tn-flex tn-flex-col-center tn-flex-row-center">
<text :class="item.icon" class="service-icon-symbol"></text> <text :class="item.icon" class="service-icon-symbol"></text>
</view> </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> </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> <text class="tn-margin-left-sm tn-text-md tn-color-black tn-text-bold">{{ getMenuLabel(item) }}</text>
</view> </view>
<view class="tn-flex tn-flex-col-center"> <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> <text class="tn-icon-right menu-arrow"></text>
</view> </view>
</view> </view>
<view class="menu-item tn-flex tn-flex-row-between tn-flex-col-center" @click="handleLanguageToggle"> <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="tn-flex tn-flex-col-center">
<view class="menu-icon"><text class="tn-icon-set menu-icon-symbol"></text></view> <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> </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>
</view> </view>
@@ -163,7 +163,7 @@
<!-- 退出登录 --> <!-- 退出登录 -->
<view class="tn-margin-top-xl tn-text-center" v-if="isLogin"> <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"> <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>
</view> </view>
@@ -192,7 +192,7 @@ export default {
return typeof this.vuex_custom_bar_height === 'number' ? this.vuex_custom_bar_height : 0 return typeof this.vuex_custom_bar_height === 'number' ? this.vuex_custom_bar_height : 0
}, },
displayUserName() { 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() { displayUserInitial() {
const firstChar = this.userInfo.firstChar const firstChar = this.userInfo.firstChar
@@ -217,21 +217,21 @@ export default {
}, },
guestHeaderDesc() { guestHeaderDesc() {
return this.registrationApplication return this.registrationApplication
? `${this.t('registrationStatusTitle')}:${this.getRegistrationStatusText(this.registrationStatus)}` ? `${this.$tr('注册申请进度')}:${this.getRegistrationStatusText(this.registrationStatus)}`
: this.t('clickToLogin') : this.$tr('点击登录')
}, },
registrationStatusDesc() { registrationStatusDesc() {
if (!this.registrationApplication) return this.t('clickToLogin') if (!this.registrationApplication) return this.$tr('点击登录')
if (this.registrationStatus === 'pending') { if (this.registrationStatus === 'pending') {
return this.t('registrationPendingDesc') return this.$tr('注册申请审核中,审批通过后才可登录')
} }
if (this.registrationStatus === 'approved') { if (this.registrationStatus === 'approved') {
return this.t('registrationApprovedDesc') return this.$tr('注册申请已通过,可前往登录')
} }
if (this.registrationStatus === 'rejected') { 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() { currentBookingCard() {
if (!this.isLogin) { 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 } { 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: [ coreServices: [
{ key: 'application', i18nKey: 'application', icon: 'tn-icon-edit-form' }, { key: 'application', i18nKey: '入住申请', icon: 'tn-icon-edit-form' },
{ key: 'bookingList', i18nKey: 'bookingList', icon: 'tn-icon-order-fill' }, { key: 'bookingList', i18nKey: '预订记录', icon: 'tn-icon-order-fill' },
{ key: 'contract', i18nKey: 'contract', icon: 'tn-icon-copy' }, { key: 'contract', i18nKey: '我的合同', icon: 'tn-icon-copy' },
{ key: 'bill', i18nKey: 'bill', icon: 'tn-icon-money' } { 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() { async mounted() {
@@ -398,9 +290,6 @@ export default {
else if (key === 'contract') this.goToContractList(); else if (key === 'contract') this.goToContractList();
else if (key === 'bill') this.goToUtilityRecharge(); else if (key === 'bill') this.goToUtilityRecharge();
}, },
t(key, params) {
return this.$tm('pages.index.component.ProfileCenterTab', key, params)
},
getMenuLabel(item = {}) { getMenuLabel(item = {}) {
return this.language === 'en' return this.language === 'en'
? (item.labelEnShort || item.labelEn || item.label || '') ? (item.labelEnShort || item.labelEn || item.label || '')
@@ -511,14 +400,14 @@ export default {
}, },
handleLogout() { handleLogout() {
uni.showModal({ uni.showModal({
title: this.t('logoutConfirmTitle'), title: this.$tr('提示'),
content: this.t('logoutConfirmContent'), content: this.$tr('确定要退出登录吗?'),
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
clearLoginSession() clearLoginSession()
this.checkLoginStatus(); this.checkLoginStatus();
this.$parent.changeTabbar && this.$parent.changeTabbar(0, 'select-room'); 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) { getRegistrationStatusText(status) {
if (this.registrationApplication && typeof this.registrationApplication.approvalStatus === 'number') { 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 = { const map = {
unsubmitted: this.t('notSubmittedYet'), unsubmitted: this.$tr('暂未提交注册申请'),
pending: this.t('statusPending'), pending: this.$tr('审核中'),
approved: this.t('statusApproved'), approved: this.$tr('已通过'),
rejected: this.t('statusRejected') rejected: this.$tr('已拒绝')
} }
return map[status] || map.unsubmitted return map[status] || map.unsubmitted
}, },
@@ -581,7 +470,7 @@ export default {
}, },
goToSubmitRequest(title, desc, payload = {}, titleZh = '', titleEn = '', descZh = '', descEn = '') { goToSubmitRequest(title, desc, payload = {}, titleZh = '', titleEn = '', descZh = '', descEn = '') {
uni.navigateTo({ 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() { handleApplicationEntry() {
@@ -627,9 +516,9 @@ export default {
}, itemTitleZh, itemTitleEn, itemDescZh, itemDescEn) }, itemTitleZh, itemTitleEn, itemDescZh, itemDescEn)
return return
} }
const comingSoonDesc = this.t('comingSoonDesc', { title: itemTitle }) const comingSoonDesc = this.$tr('{title} 功能正在建设中,后续开放后会在这里提供正式入口和数据。', { title: itemTitle })
const comingSoonDescZh = this.$tm('pages.index.component.ProfileCenterTab', 'comingSoonDesc', { title: itemTitleZh || itemTitle }) const comingSoonDescZh = this.$tr('{title} 功能正在建设中,后续开放后会在这里提供正式入口和数据。', { title: itemTitleZh || itemTitle })
const comingSoonDescEn = this.$tm('pages.index.component.ProfileCenterTab', 'comingSoonDesc', { title: itemTitleEn || itemTitle }) const comingSoonDescEn = this.$tr('{title} 功能正在建设中,后续开放后会在这里提供正式入口和数据。', { title: itemTitleEn || itemTitle })
uni.navigateTo({ 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}` 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>
<view class="search-box tn-flex tn-flex-col-center"> <view class="search-box tn-flex tn-flex-col-center">
<text class="tn-icon-search search-icon"></text> <text class="tn-icon-search search-icon"></text>
<text class="search-placeholder">{{ t('searchApartment') }}</text> <text class="search-placeholder">{{ $tr('搜索区域、公寓或房型') }}</text>
</view> </view>
<view <view
class="sort-trigger tn-flex tn-flex-col-center tn-flex-row-center" 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-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-title-box tn-flex tn-flex-col-center">
<view class="quick-filter-dot"></view> <view class="quick-filter-dot"></view>
<text class="quick-filter-title">{{ t('tagFilter') }}</text> <text class="quick-filter-title">{{ $tr('标签筛选') }}</text>
</view> </view>
<text class="quick-filter-current">{{ displayTag(currentTagLabel) }}</text> <text class="quick-filter-current">{{ displayTag(currentTagLabel) }}</text>
</view> </view>
@@ -57,7 +57,7 @@
:class="{ 'quick-tag--active': !selectedTag }" :class="{ 'quick-tag--active': !selectedTag }"
@click="$emit('select-tag', '')" @click="$emit('select-tag', '')"
> >
{{ t('all') }} {{ $tr('全部') }}
</view> </view>
<view <view
v-for="tag in availableFilterTags" v-for="tag in availableFilterTags"
@@ -81,17 +81,17 @@
class="room-status-card tn-margin-bottom-lg" class="room-status-card tn-margin-bottom-lg"
> >
<view class="room-status-card__title"> <view class="room-status-card__title">
{{ isAreaLoading ? t('loadingAreaStatus') : t('areaStatus') }} {{ isAreaLoading ? $tr('区域加载中') : $tr('区域状态') }}
</view> </view>
<view class="room-status-card__desc"> <view class="room-status-card__desc">
{{ roomLoadError || areaLoadError || t('areaStatusDesc') }} {{ roomLoadError || areaLoadError || $tr('暂无符合筛选条件的区域') }}
</view> </view>
</view> </view>
<view v-if="!isAreaLoading && !displayAreaSections.length" class="empty-state-card tn-margin-bottom-lg"> <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"> <view class="empty-state-card__desc">
{{ bannerList.length ? t('emptyAreaDesc') : t('emptyBannerDesc') }} {{ bannerList.length ? $tr('当前筛选条件下暂无可用房源') : $tr('暂无可展示的轮播图') }}
</view> </view>
</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-bar tn-flex tn-flex-row-between tn-flex-col-center">
<view class="store-title tn-text-ellipsis">{{ getAreaName(area) }}</view> <view class="store-title tn-text-ellipsis">{{ getAreaName(area) }}</view>
<view class="store-detail-btn tn-flex tn-flex-col-center"> <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> <text class="tn-icon-right btn-icon"></text>
</view> </view>
</view> </view>
@@ -118,7 +118,7 @@
</view> </view>
<text class="address-text">{{ getAreaAddress(area) || defaultAreaAddress(getAreaName(area)) }}</text> <text class="address-text">{{ getAreaAddress(area) || defaultAreaAddress(getAreaName(area)) }}</text>
</view> </view>
<view class="store-distance">{{ t('distancePlaceholder') }}</view> <view class="store-distance">{{ $tr('附近') }}</view>
</view> </view>
</view> </view>
@@ -145,8 +145,8 @@
<view class="room-price-row tn-flex tn-flex-row-between tn-flex-col-bottom"> <view class="room-price-row tn-flex tn-flex-row-between tn-flex-col-bottom">
<view class="price-content"> <view class="price-content">
<text class="price-symbol">¥</text> <text class="price-symbol">¥</text>
<text class="price-val">{{ formatDisplayPrice(roomType.price) || t('pricePending') }}</text> <text class="price-val">{{ formatDisplayPrice(roomType.price) || $tr('待定') }}</text>
<text class="price-unit">{{ t('priceUnit') }}</text> <text class="price-unit">{{ $tr('元/月起') }}</text>
</view> </view>
<view class="room-go-icon tn-flex tn-flex-col-center tn-flex-row-center"> <view class="room-go-icon tn-flex tn-flex-col-center tn-flex-row-center">
<text class="tn-icon-right"></text> <text class="tn-icon-right"></text>
@@ -272,9 +272,6 @@ export default {
} }
return localizeHousingTag(text, this.language) return localizeHousingTag(text, this.language)
}, },
t(key) {
return this.$tm('pages.index.component.SelectRoomTab', key)
},
formatRoomTypeName(name) { formatRoomTypeName(name) {
const normalizedName = String(name || '') const normalizedName = String(name || '')
return normalizedName.replace(/^[^·]+·/, '') return normalizedName.replace(/^[^·]+·/, '')
@@ -321,7 +318,7 @@ export default {
return [...new Set(roomTypeTags)].slice(0, 4) return [...new Set(roomTypeTags)].slice(0, 4)
}, },
showTagActionSheet() { showTagActionSheet() {
const tagOptions = [this.t('allTags'), ...this.availableFilterTags] const tagOptions = [this.$tr('全部标签'), ...this.availableFilterTags]
if (!tagOptions.length) { if (!tagOptions.length) {
return return
} }
@@ -335,10 +332,10 @@ export default {
}, },
showSortActionSheet() { showSortActionSheet() {
const sortOptions = [ const sortOptions = [
{ label: this.t('sortDefault'), value: 'default' }, { label: this.$tr('默认排序'), value: 'default' },
{ label: this.t('sortPriceAsc'), value: 'price-asc' }, { label: this.$tr('价格从低到高'), value: 'price-asc' },
{ label: this.t('sortPriceDesc'), value: 'price-desc' }, { label: this.$tr('价格从高到低'), value: 'price-desc' },
{ label: this.t('sortNameAsc'), value: 'name-asc' } { label: this.$tr('名称排序'), value: 'name-asc' }
] ]
uni.showActionSheet({ uni.showActionSheet({
itemList: sortOptions.map((item) => item.label), itemList: sortOptions.map((item) => item.label),
@@ -360,9 +357,9 @@ export default {
}, },
defaultAreaAddress(areaName = '') { defaultAreaAddress(areaName = '') {
if (this.language === 'en') { 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-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="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 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')">{{ t('bookViewing') }}</view> <view class="action-btn primary-btn" :class="{ 'action-btn--en': language === 'en' }" @click="$emit('submit')">{{ $tr('预约看房') }}</view>
</view> </view>
<view class="scroll-hint tn-flex tn-flex-direction-column tn-flex-col-center" @click="$emit('collapse')"> <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"> <view class="scroll-hint__double-icon">
<text class="tn-icon-down scroll-hint__icon"></text> <text class="tn-icon-down scroll-hint__icon"></text>
<text class="tn-icon-down scroll-hint__icon scroll-hint__icon--second"></text> <text class="tn-icon-down scroll-hint__icon scroll-hint__icon--second"></text>
@@ -136,9 +136,6 @@ export default {
displayTabSubtitle(item = {}) { displayTabSubtitle(item = {}) {
return this.getBannerDisplayText(item, 'tab') return this.getBannerDisplayText(item, 'tab')
}, },
t(key) {
return this.$tm('pages.index.component.SelectRoomTab', key)
},
formatTabTitle(title) { formatTabTitle(title) {
const normalizedTitle = String(title || '') const normalizedTitle = String(title || '')
return normalizedTitle return normalizedTitle

View File

@@ -114,64 +114,6 @@ export default {
SelectRoomHero, SelectRoomHero,
SelectRoomAreaPanel 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() { data() {
return { return {
isLogin: false, isLogin: false,
@@ -213,12 +155,12 @@ export default {
return this.getLocalizedAreaText(matchedArea) return this.getLocalizedAreaText(matchedArea)
} }
if (this.currentLang === 'en') { 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() { currentTagLabel() {
return this.selectedTag || this.t('allTags') return this.selectedTag || this.$tr('全部标签')
}, },
availableFilterTags() { availableFilterTags() {
const preferredOrder = ['押一付一', '生活便利', '独立厨卫', '商圈环绕', '民水民电', '拎包入住', '近地铁', '智能家居'] const preferredOrder = ['押一付一', '生活便利', '独立厨卫', '商圈环绕', '民水民电', '拎包入住', '近地铁', '智能家居']
@@ -262,9 +204,6 @@ export default {
await Promise.all([this.loadBannerList(), this.loadAreaList()]) await Promise.all([this.loadBannerList(), this.loadAreaList()])
}, },
methods: { methods: {
t(key) {
return this.$tm('pages.index.component.SelectRoomTab', key)
},
getLocalizedBannerText(banner = {}, field = '') { getLocalizedBannerText(banner = {}, field = '') {
const key = String(field || '').trim() const key = String(field || '').trim()
if (!key) { if (!key) {
@@ -512,7 +451,7 @@ export default {
} else { } else {
// 容错处理 // 容错处理
uni.showToast({ uni.showToast({
title: this.t('areaUnavailableTitle'), title: this.$tr('该区域暂未开放'),
icon: 'none' icon: 'none'
}) })
} }
@@ -547,13 +486,13 @@ export default {
, ,
onShareAppMessage() { onShareAppMessage() {
return { return {
title: this.t('shareTitle'), title: this.$tr('绿居公寓 - 国际化青年社区'),
path: '/pages/index/index' path: '/pages/index/index'
} }
}, },
onShareTimeline() { onShareTimeline() {
return { return {
title: this.t('shareTitle') title: this.$tr('绿居公寓 - 国际化青年社区')
} }
} }
} }

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="service-page tn-safe-area-inset-bottom"> <view class="service-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="false" :bottomShadow="false" :backgroundColor="navBarBackgroundColor" :customBackStyle="{ transition: 'all 0.3s ease' }"> <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> </tn-nav-bar>
<scroll-view class="service-scroll" scroll-y :style="{paddingTop: vuex_custom_bar_height + 'px'}" @scroll="handleScroll"> <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"> <view class="icon-wrap tn-margin-bottom">
<text class="tn-icon-home-smile tn-color-white"></text> <text class="tn-icon-home-smile tn-color-white"></text>
</view> </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;"> <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>
<view class="custom-btn btn-glass tn-margin-top-xl" @click="goSelectRoom" style="width: 80%;"> <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> </view>
</view> </view>
@@ -49,7 +49,7 @@
<view class="ticket-header tn-flex tn-flex-row-between tn-flex-col-center"> <view class="ticket-header tn-flex tn-flex-row-between tn-flex-col-center">
<view class="tn-flex 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> <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>
<view class="ticket-status-badge">{{ bookingStatusText }}</view> <view class="ticket-status-badge">{{ bookingStatusText }}</view>
</view> </view>
@@ -73,7 +73,7 @@
<view class="process-track tn-margin-top-xl"> <view class="process-track tn-margin-top-xl">
<view class="track-item active"> <view class="track-item active">
<view class="track-dot"><text class="tn-icon-success"></text></view> <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>
<view class="track-line active-line"></view> <view class="track-line active-line"></view>
<view class="track-item" :class="isContractSigningStep ? 'active' : 'current'"> <view class="track-item" :class="isContractSigningStep ? 'active' : 'current'">
@@ -82,7 +82,7 @@
<view v-if="!isContractSigningStep" class="dot-inner"></view> <view v-if="!isContractSigningStep" class="dot-inner"></view>
<text v-if="isContractSigningStep" class="tn-icon-success"></text> <text v-if="isContractSigningStep" class="tn-icon-success"></text>
</view> </view>
<text class="track-text">{{ t('reviewStep') }}</text> <text class="track-text">{{ $tr('管家审核') }}</text>
</view> </view>
<view class="track-line" :class="{ 'active-line': isContractSigningStep }"></view> <view class="track-line" :class="{ 'active-line': isContractSigningStep }"></view>
<view class="track-item" :class="{ active: hasSignedContract, current: isContractSigningStep && !hasSignedContract }"> <view class="track-item" :class="{ active: hasSignedContract, current: isContractSigningStep && !hasSignedContract }">
@@ -91,12 +91,12 @@
<view v-if="isContractSigningStep && !hasSignedContract" class="dot-inner"></view> <view v-if="isContractSigningStep && !hasSignedContract" class="dot-inner"></view>
<text v-if="hasSignedContract" class="tn-icon-success"></text> <text v-if="hasSignedContract" class="tn-icon-success"></text>
</view> </view>
<text class="track-text">{{ t('contractStep') }}</text> <text class="track-text">{{ $tr('签订合同') }}</text>
</view> </view>
<view class="track-line" :class="{ 'active-line': hasSignedContract }"></view> <view class="track-line" :class="{ 'active-line': hasSignedContract }"></view>
<view class="track-item"> <view class="track-item">
<view class="track-dot"></view> <view class="track-dot"></view>
<text class="track-text">{{ t('checkinStep') }}</text> <text class="track-text">{{ $tr('办理入住') }}</text>
</view> </view>
</view> </view>
@@ -127,11 +127,11 @@
<view class="tn-flex tn-flex-row-between tn-flex-col-center"> <view class="tn-flex tn-flex-row-between tn-flex-col-center">
<view class="irc-room-no">{{ roomDisplayText }}</view> <view class="irc-room-no">{{ roomDisplayText }}</view>
<view class="irc-status-badge"> <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> </view>
<view class="irc-room-title clamp-text-1 tn-margin-top-xs">{{ roomTitleText }}</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> </view>
<!-- 环境与客控区 --> <!-- 环境与客控区 -->
@@ -139,7 +139,7 @@
<view class="irc-env-row tn-flex tn-flex-row-between tn-flex-col-center"> <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-env-text">{{ smartSummaryText }}</view>
<view class="irc-smart-btn" @click="goSmartControl"> <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>
</view> </view>
@@ -158,7 +158,7 @@
</view> </view>
<view v-else class="irc-qc-empty" @click.stop="goSmartControl"> <view v-else class="irc-qc-empty" @click.stop="goSmartControl">
<text class="tn-icon-add tn-margin-right-xs"></text> <text class="tn-icon-add tn-margin-right-xs"></text>
{{ t('addSmartDevice') }} {{ $tr('添加智能设备') }}
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
@@ -166,7 +166,7 @@
</view> </view>
<view class="tn-margin-top tn-margin-left tn-margin-right"> <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="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)"> <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-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-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-bold tn-text tn-color-black">{{ $tr('公寓精选服务') }}</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-sm tn-color-gray">{{ $tr('全部') }} <text class="tn-icon-right section-arrow-icon"></text></view>
</view> </view>
<view class="tn-flex tn-flex-wrap tn-flex-row-between"> <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="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-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="tn-text-bold tn-text tn-color-black">{{ $tr('本月账单') }}</view>
<view class="status-tag tn-text-xs" :class="unpaidBillCount ? 'tag-busy' : 'tag-free'">{{ unpaidBillCount ? t('unpaid') : t('paidOff') }}</view> <view class="status-tag tn-text-xs" :class="unpaidBillCount ? 'tag-busy' : 'tag-free'">{{ unpaidBillCount ? $tr('待支付') : $tr('已结清') }}</view>
</view> </view>
<view class="tn-flex tn-flex-row-between tn-flex-col-bottom tn-margin-bottom-sm"> <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;"> <view class="tn-text-bold tn-color-black" style="font-size: 60rpx;">
<text class="tn-text-lg tn-margin-right-xs">¥</text>{{ formatMoney(unpaidBillTotal) }} <text class="tn-text-lg tn-margin-right-xs">¥</text>{{ formatMoney(unpaidBillTotal) }}
</view> </view>
<view class="custom-btn btn-primary" style="padding: 10rpx 30rpx;" @click.stop="goUtilityRecharge"> <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> </view>
<view class="tn-flex tn-flex-row-between tn-margin-top"> <view class="tn-flex tn-flex-row-between tn-margin-top">
<view class="tn-flex tn-flex-direction-column"> <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> <text class="tn-text-bold tn-text-sm tn-margin-top-xs tn-color-black clamp-text-1">{{ unpaidBillPrimaryType }}</text>
</view> </view>
<view class="tn-flex tn-flex-direction-column"> <view class="tn-flex tn-flex-direction-column">
<text class="tn-color-gray tn-text-xs">{{ t('billCount') }}</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 }} {{ t('billUnit') }}</text> <text class="tn-text-bold tn-text-sm tn-margin-top-xs tn-color-black">{{ unpaidBillCount }} {{ $tr('笔') }}</text>
</view> </view>
<view class="tn-flex tn-flex-direction-column"> <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> <text class="tn-text-bold tn-text-sm tn-margin-top-xs tn-color-black">{{ latestBillTimeText }}</text>
</view> </view>
</view> </view>
@@ -251,7 +251,7 @@
<!-- 宿管与管理员列表 --> <!-- 宿管与管理员列表 -->
<view class="butler-card-new tn-margin-top tn-margin-left tn-margin-right"> <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 class="butler-card-new__inner">
<view v-if="managerContacts.length" class="butler-card-new__list"> <view v-if="managerContacts.length" class="butler-card-new__list">
<view <view
@@ -271,10 +271,10 @@
<view class="butler-member-new__name"> <view class="butler-member-new__name">
{{ getManagerName(contact) }} {{ getManagerName(contact) }}
<text class="butler-member-new__tag butler-member-new__tag--type" :class="contact.contactType === 'counselor' ? 'tag-counselor' : 'tag-dormitory'"> <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> </text>
</view> </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> </view>
<view <view
@@ -295,8 +295,8 @@
</view> </view>
</view> </view>
<view class="butler-member-new__info"> <view class="butler-member-new__info">
<view class="butler-member-new__name">{{ t('managementContacts') }}</view> <view class="butler-member-new__name">{{ $tr('宿管与管理员') }}</view>
<view class="butler-member-new__phone">{{ isManagementContactsLoading ? t('loadingButlerInfo') : t('noManagerContacts') }}</view> <view class="butler-member-new__phone">{{ isManagementContactsLoading ? $tr('正在同步管家信息...') : $tr('暂无管理联系人') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -562,176 +562,6 @@ export default {
descEn: 'Submit lost or found item details so the community can help.' 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: { computed: {
@@ -780,42 +610,42 @@ export default {
}, },
pendingStageTitle() { pendingStageTitle() {
if (this.hasSignedContract) { if (this.hasSignedContract) {
return this.t('contractSignedTitle') return this.$tr('合同已签署,等待办理入住')
} }
if (this.isContractSigningStep) { if (this.isContractSigningStep) {
return this.t('signContractNow') return this.$tr('请尽快签订合同')
} }
return this.t('bookingPendingTitle') return this.$tr('您的预订正在审核中')
}, },
pendingStageDesc() { pendingStageDesc() {
if (this.hasSignedContract) { if (this.hasSignedContract) {
return this.t('contractSignedDesc') return this.$tr('您的住宿合同已经完成签署,管家会尽快为您安排办理入住流程。')
} }
if (this.isContractSigningStep) { if (this.isContractSigningStep) {
return this.t('signContractDesc') return this.$tr('系统已生成待签合同,当前流程已进入签订合同阶段,完成签约后即可继续后续入住流程。')
} }
return this.t('bookingPendingDesc') return this.$tr('定金已支付,预定单正在审批流中。审批通过后将分配管家并等待分房号。')
}, },
pendingInfoLabel() { pendingInfoLabel() {
return this.hasSignedContract || this.isContractSigningStep ? this.t('contractRoom') : this.t('bookedRoomType') return this.hasSignedContract || this.isContractSigningStep ? this.$tr('合同房型') : this.$tr('预定房型')
}, },
pendingStageNote() { pendingStageNote() {
if (this.hasSignedContract) { if (this.hasSignedContract) {
return this.t('contractSignedNote') return this.$tr('合同签署完成,请等待管家通知办理入住。')
} }
if (this.isContractSigningStep) { if (this.isContractSigningStep) {
return this.t('contractPendingNote') return this.$tr('检测到待签合同后,流程将停留在签订合同节点,请先完成签名。')
} }
return this.t('bookingPendingNote') return this.$tr('订单已提交,管家审核通过后会同步更多入住信息。')
}, },
pendingPrimaryActionText() { pendingPrimaryActionText() {
if (this.isPendingStatusRefreshing) { if (this.isPendingStatusRefreshing) {
return this.t('refreshing') return this.$tr('刷新中...')
} }
if (this.isContractSigningStep && !this.hasSignedContract) { if (this.isContractSigningStep && !this.hasSignedContract) {
return this.t('signContract') return this.$tr('去签约')
} }
return this.t('refreshStatus') return this.$tr('刷新状态')
}, },
bookingRoomText() { bookingRoomText() {
if (!this.currentBooking) { if (!this.currentBooking) {
@@ -824,18 +654,18 @@ export default {
return this.currentBooking.roomName return this.currentBooking.roomName
|| this.currentBooking.roomTypeName || this.currentBooking.roomTypeName
|| this.currentBooking.roomTypeNameEn || this.currentBooking.roomTypeNameEn
|| this.t('pendingAssignment') || this.$tr('待审核分配')
}, },
roomCardCover() { roomCardCover() {
return this.myRoomData && this.myRoomData.image ? this.myRoomData.image : '' return this.myRoomData && this.myRoomData.image ? this.myRoomData.image : ''
}, },
roomTitleText() { roomTitleText() {
if (!this.myRoomData) { if (!this.myRoomData) {
return this.t('currentStay') return this.$tr('当前入住房间')
} }
return this.language === 'zh' return this.language === 'zh'
? (pickReadableValue(this.myRoomData.titleZh, this.myRoomData.roomTypeName, this.myRoomData.roomName, this.myRoomData.titleEn) || 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.t('currentStay')) : (pickReadableValue(this.myRoomData.titleEn, this.myRoomData.roomTypeNameEn, this.myRoomData.roomTypeName, this.myRoomData.titleZh) || this.$tr('当前入住房间'))
}, },
roomDisplayText() { roomDisplayText() {
if (!this.myRoomData) { if (!this.myRoomData) {
@@ -866,10 +696,10 @@ export default {
}, },
unpaidBillPrimaryType() { unpaidBillPrimaryType() {
if (!this.unpaidBillList.length) { if (!this.unpaidBillList.length) {
return this.t('billEmpty') return this.$tr('暂无待支付账单')
} }
const firstBill = this.unpaidBillList[0] || {} const firstBill = this.unpaidBillList[0] || {}
return firstBill.referenceTypeDesc || getAccountReferenceTypeLabel(firstBill.referenceType, this.t('billRecordFallback')) return firstBill.referenceTypeDesc || getAccountReferenceTypeLabel(firstBill.referenceType, this.$tr('账单记录'))
}, },
latestBillTimeText() { latestBillTimeText() {
if (!this.unpaidBillList.length || !this.unpaidBillList[0].createTime) { if (!this.unpaidBillList.length || !this.unpaidBillList[0].createTime) {
@@ -879,25 +709,25 @@ export default {
}, },
smartSummaryText() { smartSummaryText() {
if (!this.hasCheckedIn) { if (!this.hasCheckedIn) {
return this.t('smartAvailableAfterCheckin') return this.$tr('入住后可使用智能设备')
} }
if (this.isSmartDevicesLoading) { if (this.isSmartDevicesLoading) {
return this.t('syncingSmartDevices') return this.$tr('正在同步智能设备...')
} }
if (this.smartDevices.length) { 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) { if (this.smartDeviceError) {
return this.t('smartDeviceSyncFailed') return this.$tr('智能设备同步失败')
} }
return this.t('smartDeviceEmpty') return this.$tr('当前房间暂无智能设备')
}, },
smartQuickDeviceCards() { smartQuickDeviceCards() {
return this.smartDevices.map((device, index) => ({ return this.smartDevices.map((device, index) => ({
...device, ...device,
key: device.renderKey || device.deviceId || device.deviceNo || `smart-device-${index}`, key: device.renderKey || device.deviceId || device.deviceNo || `smart-device-${index}`,
name: device.deviceName || this.t('deviceFallback').replace('{index}', index + 1), name: device.deviceName || this.$tr('设备 {index}').replace('{index}', index + 1),
stateText: device.stateText || this.t('controllable') stateText: device.stateText || this.$tr('可控制')
})) }))
}, },
stayLocationText() { stayLocationText() {
@@ -910,7 +740,7 @@ export default {
this.myRoomData.buildingName || '', this.myRoomData.buildingName || '',
this.myRoomData.buildingSectionName || '', this.myRoomData.buildingSectionName || '',
this.myRoomData.floorName || this.myRoomData.floor || '', this.myRoomData.floorName || this.myRoomData.floor || '',
bedLabel ? this.t('bedPrefix').replace('{label}', bedLabel) : '' bedLabel ? this.$tr('床位 {label}').replace('{label}', bedLabel) : ''
].filter(Boolean).join(' · ') ].filter(Boolean).join(' · ')
}, },
butlerAvatar() { butlerAvatar() {
@@ -930,10 +760,10 @@ export default {
const manager = this.primaryAreaManager || {} const manager = this.primaryAreaManager || {}
const managerName = manager.displayName || manager.name || manager.userName || manager.nickname || manager.userCode || '' const managerName = manager.displayName || manager.name || manager.userName || manager.nickname || manager.userCode || ''
if (!managerName) { if (!managerName) {
return this.t('butler') return this.$tr('专属社区管家')
} }
if (this.areaManagers.length > 1) { if (this.areaManagers.length > 1) {
return this.t('multiButlerName') return this.$tr('{name} 等{count}位管家')
.replace('{name}', managerName) .replace('{name}', managerName)
.replace('{count}', this.language === 'zh' ? this.areaManagers.length : this.areaManagers.length - 1) .replace('{count}', this.language === 'zh' ? this.areaManagers.length : this.areaManagers.length - 1)
} }
@@ -941,29 +771,29 @@ export default {
}, },
butlerSubtitle() { butlerSubtitle() {
if (this.isAreaManagerLoading) { if (this.isAreaManagerLoading) {
return this.t('loadingButlerInfo') return this.$tr('正在同步管家信息...')
} }
if (this.areaManagers.length > 1) { 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 manager = this.primaryAreaManager || {}
const phone = this.getManagerPhone(manager) const phone = this.getManagerPhone(manager)
if (phone) { if (phone) {
return `${this.t('contactPhonePrefix')}${phone}` return `${this.$tr('联系电话 ')}${phone}`
} }
return this.t('online') return this.$tr('当前在线 · 响应时间 < 5分钟')
}, },
butlerCountText() { butlerCountText() {
if (this.isAreaManagerLoading) { if (this.isAreaManagerLoading) {
return this.t('loadingShort') return this.$tr('同步中')
} }
if (this.areaManagers.length > 1) { 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) { if (this.areaManagers.length === 1) {
return this.t('onlineSingle') return this.$tr('1人在线')
} }
return this.t('pendingSync') return this.$tr('待同步')
}, },
butlerPhoneNumber() { butlerPhoneNumber() {
const manager = this.primaryAreaManager || {} const manager = this.primaryAreaManager || {}
@@ -995,9 +825,6 @@ export default {
this.loadUnpaidBills() this.loadUnpaidBills()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pages.index.component.StayServiceTab', key, params)
},
pickItemText(item = {}, zhField, enField, fallback = '') { pickItemText(item = {}, zhField, enField, fallback = '') {
if (this.language === 'en') { if (this.language === 'en') {
const shortField = `${enField}Short` const shortField = `${enField}Short`
@@ -1139,7 +966,7 @@ export default {
const roomCode = this.getCurrentSmartRoomCode() const roomCode = this.getCurrentSmartRoomCode()
if (!brandId || !roomCode) { if (!brandId || !roomCode) {
this.resetSmartDevicesState() this.resetSmartDevicesState()
this.smartDeviceError = this.t('roomDeviceUnconfigured') this.smartDeviceError = this.$tr('房间智能设备未配置')
return Promise.resolve() return Promise.resolve()
} }
@@ -1162,7 +989,7 @@ export default {
.catch((error) => { .catch((error) => {
console.error('load stay smart devices failed', error) console.error('load stay smart devices failed', error)
this.smartDevices = [] this.smartDevices = []
this.smartDeviceError = (error && (error.message || error.msg)) || this.t('smartDeviceLoadFailed') this.smartDeviceError = (error && (error.message || error.msg)) || this.$tr('智能设备加载失败')
}) })
.finally(() => { .finally(() => {
this.isSmartDevicesLoading = false this.isSmartDevicesLoading = false
@@ -1397,15 +1224,15 @@ export default {
}) })
uni.showToast({ uni.showToast({
title: pendingContracts.length title: pendingContracts.length
? this.t('pendingContractFound') ? this.$tr('发现待签合同')
: this.t('statusUpdated'), : this.$tr('状态已刷新'),
icon: 'success' icon: 'success'
}) })
}) })
.catch((error) => { .catch((error) => {
console.error('refresh pending status failed', error) console.error('refresh pending status failed', error)
uni.showToast({ uni.showToast({
title: this.t('refreshFailed'), title: this.$tr('刷新失败,请稍后重试'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -1430,10 +1257,10 @@ export default {
}, },
buildServiceSubmitUrl(item = {}) { buildServiceSubmitUrl(item = {}) {
const roomData = this.myRoomData || {} const roomData = this.myRoomData || {}
const titleZh = item.title || item.titleEn || item.nameZh || item.nameEn || this.t('submitRequest') 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.t('submitRequest') const titleEn = item.titleEnShort || item.titleEn || item.title || item.nameEn || item.nameZh || this.$tr('提交申请')
const descZh = item.desc || item.descEn || this.t('completeFormBeforeSubmit') const descZh = item.desc || item.descEn || this.$tr('请完善表单信息后提交。')
const descEn = compactEnglishText(item.descEn || item.desc || '', 68) || item.descEn || item.desc || this.t('completeFormBeforeSubmit') const descEn = compactEnglishText(item.descEn || item.desc || '', 68) || item.descEn || item.desc || this.$tr('请完善表单信息后提交。')
const title = this.language === 'en' const title = this.language === 'en'
? titleEn ? titleEn
: titleZh : titleZh
@@ -1451,7 +1278,7 @@ export default {
roomTypeName: item.key === 'change-room' ? (roomData.roomTypeName || roomData.titleZh || '') : '', roomTypeName: item.key === 'change-room' ? (roomData.roomTypeName || roomData.titleZh || '') : '',
roomName: roomData.roomLabel || '' 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 = {}) { buildTicketWorkbenchUrl(item = {}) {
const serviceTypeMap = { const serviceTypeMap = {
@@ -1506,7 +1333,7 @@ export default {
fail: (error) => { fail: (error) => {
console.error('跳转送餐小程序失败', error) console.error('跳转送餐小程序失败', error)
uni.showToast({ uni.showToast({
title: this.t('mealDeliveryJumpFailed'), title: this.$tr('跳转送餐小程序失败'),
icon: 'none' icon: 'none'
}) })
} }
@@ -1523,7 +1350,7 @@ export default {
}, },
getManagerInitial(manager = {}) { getManagerInitial(manager = {}) {
const name = this.getManagerName(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() { handleButlerWechat() {
const manager = this.primaryAreaManager || {} const manager = this.primaryAreaManager || {}
@@ -1533,7 +1360,7 @@ export default {
data: String(wechat), data: String(wechat),
success: () => { success: () => {
uni.showToast({ uni.showToast({
title: this.t('managerWechatCopied'), title: this.$tr('管家微信已复制'),
icon: 'success' icon: 'success'
}) })
} }
@@ -1541,7 +1368,7 @@ export default {
return return
} }
uni.showToast({ uni.showToast({
title: this.t('noManagerWechat'), title: this.$tr('暂无管家微信'),
icon: 'none' icon: 'none'
}) })
}, },
@@ -1549,7 +1376,7 @@ export default {
const phone = this.getManagerPhone(manager) const phone = this.getManagerPhone(manager)
if (!phone) { if (!phone) {
uni.showToast({ uni.showToast({
title: this.t('noContactPhone'), title: this.$tr('暂无联系电话'),
icon: 'none' icon: 'none'
}) })
return return
@@ -1561,7 +1388,7 @@ export default {
handleButlerCall() { handleButlerCall() {
if (!this.butlerPhoneNumber) { if (!this.butlerPhoneNumber) {
uni.showToast({ uni.showToast({
title: this.isAreaManagerLoading ? this.t('loadingManagerInfo') : this.t('noContactPhone'), title: this.isAreaManagerLoading ? this.$tr('正在加载管家信息') : this.$tr('暂无联系电话'),
icon: 'none' icon: 'none'
}) })
return return

View File

@@ -6,7 +6,7 @@
<view class="loading-logo"> <view class="loading-logo">
<text class="tn-icon-moon tn-color-white" style="font-size: 80rpx; transform: rotate(-45deg); display: block;"></text> <text class="tn-icon-moon tn-color-white" style="font-size: 80rpx; transform: rotate(-45deg); display: block;"></text>
</view> </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"> <view class="loading-spinner tn-margin-top-xl">
<tn-loading mode="circle" color="#00BBE8" size="60"></tn-loading> <tn-loading mode="circle" color="#00BBE8" size="60"></tn-loading>
</view> </view>
@@ -15,21 +15,21 @@
<!-- #ifdef MP-WEIXIN --> <!-- #ifdef MP-WEIXIN -->
<view v-if="showPrivacyPopup" class="privacy-mask"> <view v-if="showPrivacyPopup" class="privacy-mask">
<view class="privacy-dialog"> <view class="privacy-dialog">
<view class="privacy-title">{{ t('privacyTitle') }}</view> <view class="privacy-title">{{ $tr('隐私授权提示') }}</view>
<view class="privacy-content"> <view class="privacy-content">
{{ t('privacyContentPrefix') }} {{ $tr('进入小程序前,请先阅读并同意') }}
<text class="privacy-link" @click="openPrivacyContract">{{ t('privacyContractName') }}</text> <text class="privacy-link" @click="openPrivacyContract">{{ $tr('《用户隐私保护指引》') }}</text>
{{ t('privacyContentSuffix') }} {{ $tr(',同意后才能继续使用相关功能。') }}
</view> </view>
<view class="privacy-action-row"> <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 <button
id="index-privacy-agree-btn" id="index-privacy-agree-btn"
class="privacy-action-btn is-primary" class="privacy-action-btn is-primary"
open-type="agreePrivacyAuthorization" open-type="agreePrivacyAuthorization"
@agreeprivacyauthorization="handleAgreePrivacyAuthorization" @agreeprivacyauthorization="handleAgreePrivacyAuthorization"
> >
{{ t('privacyAgree') }} {{ $tr('同意并继续') }}
</button> </button>
</view> </view>
</view> </view>
@@ -156,50 +156,18 @@
pendingContracts: [] pendingContracts: []
}, },
tabDefinitions: [ tabDefinitions: [
{ id: 'select-room', labelKey: 'tabSelectRoom', icon: 'tn-icon-home-fill', image: '/static/tabbar/tn-tabbar0.png', component: 'SelectRoomTab', ref: 'selectRoomTab', refreshMethod: 'checkLoginStatus' }, { id: 'select-room', labelKey: '选 房', icon: 'tn-icon-home-fill', image: '/static/tabbar/tn-tabbar0.png', component: 'SelectRoomTab', ref: 'selectRoomTab', refreshMethod: 'checkLoginStatus' },
{ id: 'stay-service', labelKey: 'tabStayService', icon: 'tn-icon-safe', image: '/static/tabbar/tn-tabbar1.png', component: 'StayServiceTab', ref: 'stayServiceTab', refreshMethod: 'checkLocalStatus' }, { id: 'stay-service', labelKey: '入 住', 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: 'my-application', labelKey: '申请单', 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: 'message-center', labelKey: '消 息', 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: 'profile-center', labelKey: '我 的', icon: 'tn-icon-my-fill', image: '/static/tabbar/tn-tabbar3.png', component: 'ProfileCenterTab', ref: 'profileCenterTab', refreshMethod: 'checkLoginStatus' }
], ],
loadedPages: ['select-room'], loadedPages: ['select-room'],
isAppInitLoading: true, isAppInitLoading: true,
isTabSwitching: false, isTabSwitching: false,
showPrivacyPopup: false, showPrivacyPopup: false,
resolvePrivacyAuthorization: null, resolvePrivacyAuthorization: null,
isCheckingPrivacyAuthorization: false, 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'
}
}
} }
}, },
computed: { computed: {
@@ -231,7 +199,7 @@
.filter(item => this.visibleTabIds.includes(item.id)) .filter(item => this.visibleTabIds.includes(item.id))
.map(item => ({ .map(item => ({
...item, ...item,
name: this.t(item.labelKey) name: this.$tr(item.labelKey)
})) }))
}, },
currentTabbarIndex() { currentTabbarIndex() {
@@ -262,9 +230,6 @@
}) })
}, },
methods: { methods: {
t(key) {
return this.$tm('pages.index.index', key)
},
initPrivacyAuthorization() { initPrivacyAuthorization() {
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
if (typeof wx === 'undefined' || !wx.onNeedPrivacyAuthorization) { if (typeof wx === 'undefined' || !wx.onNeedPrivacyAuthorization) {
@@ -782,14 +747,14 @@
// 分享到朋友 // 分享到朋友
onShareAppMessage() { onShareAppMessage() {
return { return {
title: this.t('shareTitle'), title: this.$tr('绿居公寓 - 国际化青年社区'),
path: '/pages/index/index' path: '/pages/index/index'
} }
}, },
// 分享到朋友圈 // 分享到朋友圈
onShareTimeline() { onShareTimeline() {
return { return {
title: this.t('shareTitle') title: this.$tr('绿居公寓 - 国际化青年社区')
} }
} }
} }

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="auth-result-page tn-safe-area-inset-bottom"> <view class="auth-result-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('注册申请结果') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="page-content tn-padding" :style="{paddingTop: vuex_custom_bar_height + 'px'}"> <view class="page-content tn-padding" :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view class="status-card fade-in"> <view class="status-card fade-in">
@@ -33,7 +33,7 @@
<!-- 提交信息摘要 --> <!-- 提交信息摘要 -->
<view v-if="profileSummary.length" class="summary-box tn-margin-top-xl tn-margin-bottom-lg tn-margin-left-lg tn-margin-right-lg"> <view v-if="profileSummary.length" class="summary-box tn-margin-top-xl tn-margin-bottom-lg tn-margin-left-lg tn-margin-right-lg">
<view class="summary-title tn-margin-bottom-sm">{{ t('submittedInfoTitle') }}</view> <view class="summary-title tn-margin-bottom-sm">{{ $tr('已提交信息') }}</view>
<view v-for="item in profileSummary" :key="item.label" class="summary-row tn-flex tn-flex-row-between tn-padding-top-sm tn-padding-bottom-sm"> <view v-for="item in profileSummary" :key="item.label" class="summary-row tn-flex tn-flex-row-between tn-padding-top-sm tn-padding-bottom-sm">
<text class="summary-label">{{ item.label }}</text> <text class="summary-label">{{ item.label }}</text>
<text class="summary-value tn-text-ellipsis">{{ item.value }}</text> <text class="summary-value tn-text-ellipsis">{{ item.value }}</text>
@@ -44,19 +44,19 @@
<!-- 操作按钮 --> <!-- 操作按钮 -->
<view class="action-group tn-margin-top-xl fade-in-up"> <view class="action-group tn-margin-top-xl fade-in-up">
<view v-if="authStatus === 'approved'" class="custom-btn btn-primary" @click="goToLogin"> <view v-if="authStatus === 'approved'" class="custom-btn btn-primary" @click="goToLogin">
<text class="tn-text-bold">{{ t('goLogin') }}</text> <text class="tn-text-bold">{{ $tr('去登录') }}</text>
</view> </view>
<view v-else-if="authStatus === 'rejected'" class="custom-btn btn-primary" @click="resubmit"> <view v-else-if="authStatus === 'rejected'" class="custom-btn btn-primary" @click="resubmit">
<text class="tn-text-bold">{{ t('resubmitRegister') }}</text> <text class="tn-text-bold">{{ $tr('重新提交注册申请') }}</text>
</view> </view>
<view v-else class="custom-btn btn-dark" @click="refreshStatus"> <view v-else class="custom-btn btn-dark" @click="refreshStatus">
<text class="tn-text-bold tn-flex tn-flex-row-center tn-flex-col-center"> <text class="tn-text-bold tn-flex tn-flex-row-center tn-flex-col-center">
<text v-if="loading" class="tn-icon-loading tn-margin-right-xs spin"></text> <text v-if="loading" class="tn-icon-loading tn-margin-right-xs spin"></text>
{{ loading ? t('refreshing') : t('refreshStatus') }} {{ loading ? $tr('刷新中...') : $tr('刷新审批状态') }}
</text> </text>
</view> </view>
<view class="custom-btn btn-light tn-margin-top" @click="goToRegistrationRecords"> <view class="custom-btn btn-light tn-margin-top" @click="goToRegistrationRecords">
<text class="tn-text-bold">{{ t('viewRegistrationRecords') }}</text> <text class="tn-text-bold">{{ $tr('查看注册记录') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -80,81 +80,7 @@ export default {
loading: false, loading: false,
applicationInfo: null, applicationInfo: null,
targetCode: '', targetCode: '',
sourceType: '', sourceType: ''
i18n: {
zh: {
navTitle: '注册申请结果',
submittedInfoTitle: '已提交信息',
goLogin: '去登录',
resubmitRegister: '重新提交注册申请',
refreshing: '刷新中...',
refreshStatus: '刷新审批状态',
viewRegistrationRecords: '查看注册记录',
unsubmitted: '未提交',
titleUnsubmitted: '您还未提交注册申请',
titlePending: '注册申请已提交',
titleApproved: '注册审批已通过',
titleRejected: '注册审批未通过',
descUnsubmitted: '请先提交注册申请单,审批通过后才算注册成功。',
descPendingRecords: '当前展示的是该申请单的审批状态,可下拉或点击按钮刷新最新结果。',
descPendingDefault: '当前申请正在审批中,审批通过前无法登录。',
descApproved: '您的注册申请已审批通过,现在可以前往登录。',
descRejectedRecords: '当前展示的是该申请单的审批结果,请根据驳回原因修改信息后重新提交注册申请。',
descRejectedDefault: '请根据驳回原因修改信息后重新提交注册申请。',
remarkApproved: '通过备注',
remarkRejected: '驳回原因',
remarkPending: '审批备注',
roleForeign: '外籍人员',
roleDomestic: '中国籍',
labelIdentityType: '人员类型',
labelName: '姓名',
labelAccount: '注册账号',
labelCertificateNo: '证件号',
fallbackNone: '-',
latestDetailMissing: '未查到最新详情,已展示列表记录',
approvedPleaseLogin: '审批已通过,请登录',
fetchStatusFailed: '获取审批状态失败',
statusPending: '审核中',
statusApproved: '已通过',
statusRejected: '已驳回'
},
en: {
navTitle: 'Registration Result',
submittedInfoTitle: 'Submitted Information',
goLogin: 'Sign In',
resubmitRegister: 'Submit Registration Again',
refreshing: 'Refreshing...',
refreshStatus: 'Refresh Status',
viewRegistrationRecords: 'View Registration Records',
unsubmitted: 'Not Submitted',
titleUnsubmitted: 'No registration application submitted yet',
titlePending: 'Registration submitted',
titleApproved: 'Registration approved',
titleRejected: 'Registration was not approved',
descUnsubmitted: 'Please submit a registration application first. Registration is completed only after approval.',
descPendingRecords: 'This page shows the approval status of the selected application. Pull down or tap the button to refresh.',
descPendingDefault: 'Your application is under review and sign-in is unavailable before approval.',
descApproved: 'Your registration is approved and you can sign in now.',
descRejectedRecords: 'This page shows the approval result of the selected application. Please update the information and resubmit based on the rejection reason.',
descRejectedDefault: 'Please update the information and resubmit based on the rejection reason.',
remarkApproved: 'Approval Note',
remarkRejected: 'Rejection Reason',
remarkPending: 'Approval Note',
roleForeign: 'Foreign',
roleDomestic: 'Chinese',
labelIdentityType: 'Identity Type',
labelName: 'Name',
labelAccount: 'Account',
labelCertificateNo: 'Certificate No.',
fallbackNone: '-',
latestDetailMissing: 'Latest details are unavailable. Showing the list record instead.',
approvedPleaseLogin: 'Approved. Please sign in.',
fetchStatusFailed: 'Failed to fetch approval status',
statusPending: 'Under Review',
statusApproved: 'Approved',
statusRejected: 'Rejected'
}
}
} }
}, },
computed: { computed: {
@@ -173,36 +99,36 @@ export default {
if (typeof status === 'number') { if (typeof status === 'number') {
return this.getApprovalStatusLabelLocal(status) return this.getApprovalStatusLabelLocal(status)
} }
return this.t('unsubmitted') return this.$tr('未提交')
}, },
titleText() { titleText() {
const map = { const map = {
unsubmitted: this.t('titleUnsubmitted'), unsubmitted: this.$tr('您还未提交注册申请'),
pending: this.t('titlePending'), pending: this.$tr('注册申请已提交'),
approved: this.t('titleApproved'), approved: this.$tr('注册审批已通过'),
rejected: this.t('titleRejected') rejected: this.$tr('注册审批未通过')
} }
return map[this.authStatus] || map.unsubmitted return map[this.authStatus] || map.unsubmitted
}, },
descText() { descText() {
const map = { const map = {
unsubmitted: this.t('descUnsubmitted'), unsubmitted: this.$tr('请先提交注册申请单,审批通过后才算注册成功。'),
pending: this.sourceType === 'registration-records' pending: this.sourceType === 'registration-records'
? this.t('descPendingRecords') ? this.$tr('当前展示的是该申请单的审批状态,可下拉或点击按钮刷新最新结果。')
: this.t('descPendingDefault'), : this.$tr('当前申请正在审批中,审批通过前无法登录。'),
approved: this.t('descApproved'), approved: this.$tr('您的注册申请已审批通过,现在可以前往登录。'),
rejected: this.sourceType === 'registration-records' rejected: this.sourceType === 'registration-records'
? this.t('descRejectedRecords') ? this.$tr('当前展示的是该申请单的审批结果,请根据驳回原因修改信息后重新提交注册申请。')
: this.t('descRejectedDefault') : this.$tr('请根据驳回原因修改信息后重新提交注册申请。')
} }
return map[this.authStatus] || map.unsubmitted return map[this.authStatus] || map.unsubmitted
}, },
approvalRemarkTitle() { approvalRemarkTitle() {
const map = { const map = {
approved: this.t('remarkApproved'), approved: this.$tr('通过备注'),
rejected: this.t('remarkRejected'), rejected: this.$tr('驳回原因'),
pending: this.t('remarkPending'), pending: this.$tr('审批备注'),
unsubmitted: this.t('remarkPending') unsubmitted: this.$tr('审批备注')
} }
return map[this.authStatus] || map.pending return map[this.authStatus] || map.pending
}, },
@@ -223,13 +149,13 @@ export default {
profileSummary() { profileSummary() {
const profile = this.applicationInfo || {} const profile = this.applicationInfo || {}
const identityType = profile.passportNumber ? 'foreign' : 'domestic' const identityType = profile.passportNumber ? 'foreign' : 'domestic'
const roleText = identityType === 'foreign' ? this.t('roleForeign') : identityType === 'domestic' ? this.t('roleDomestic') : this.t('fallbackNone') const roleText = identityType === 'foreign' ? this.$tr('外籍人员') : identityType === 'domestic' ? this.$tr('中国籍') : this.$tr('-')
const certificateNo = profile.passportNumber || profile.idCard || this.t('fallbackNone') const certificateNo = profile.passportNumber || profile.idCard || this.$tr('-')
return [ return [
{ label: this.t('labelIdentityType'), value: roleText }, { label: this.$tr('人员类型'), value: roleText },
{ label: this.t('labelName'), value: profile.name || this.t('fallbackNone') }, { label: this.$tr('姓名'), value: profile.name || this.$tr('-') },
{ label: this.t('labelAccount'), value: profile.account || this.t('fallbackNone') }, { label: this.$tr('注册账号'), value: profile.account || this.$tr('-') },
{ label: this.t('labelCertificateNo'), value: certificateNo }, { label: this.$tr('证件号'), value: certificateNo },
// { label: '国籍', value: profile.country || profile.nationality || '-' }, // { label: '国籍', value: profile.country || profile.nationality || '-' },
// { label: '工作单位', value: profile.workUnit || '-' }, // { label: '工作单位', value: profile.workUnit || '-' },
// { label: '兴趣爱好', value: hobbyText } // { label: '兴趣爱好', value: hobbyText }
@@ -267,9 +193,6 @@ export default {
this.refreshStatus() this.refreshStatus()
}, },
methods: { methods: {
t(key) {
return this.$tm('pkgAccount.auth_result.auth_result', key)
},
getWechatCode() { getWechatCode() {
return new Promise((resolve) => { return new Promise((resolve) => {
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
@@ -324,7 +247,7 @@ export default {
if (!target || typeof target !== 'object' || !Object.keys(target).length) { if (!target || typeof target !== 'object' || !Object.keys(target).length) {
if (this.applicationInfo && this.applicationCode) { if (this.applicationInfo && this.applicationCode) {
uni.showToast({ uni.showToast({
title: this.t('latestDetailMissing'), title: this.$tr('未查到最新详情,已展示列表记录'),
icon: 'none' icon: 'none'
}) })
return return
@@ -334,7 +257,7 @@ export default {
approvalStatus: 1 approvalStatus: 1
} }
uni.showToast({ uni.showToast({
title: this.t('approvedPleaseLogin'), title: this.$tr('审批已通过,请登录'),
icon: 'success' icon: 'success'
}) })
setTimeout(() => { setTimeout(() => {
@@ -354,7 +277,7 @@ export default {
} catch (error) { } catch (error) {
console.error('refresh registration status failed', error) console.error('refresh registration status failed', error)
uni.showToast({ uni.showToast({
title: this.t('fetchStatusFailed'), title: this.$tr('获取审批状态失败'),
icon: 'none' icon: 'none'
}) })
} finally { } finally {
@@ -365,12 +288,12 @@ export default {
return getApprovalStatusLabel( return getApprovalStatusLabel(
status, status,
this.authStatus === 'pending' this.authStatus === 'pending'
? this.t('statusPending') ? this.$tr('审核中')
: this.authStatus === 'approved' : this.authStatus === 'approved'
? this.t('statusApproved') ? this.$tr('已通过')
: this.authStatus === 'rejected' : this.authStatus === 'rejected'
? this.t('statusRejected') ? this.$tr('已驳回')
: this.t('unsubmitted'), : this.$tr('未提交'),
this.language this.language
) )
}, },

View File

@@ -8,19 +8,19 @@
<!-- 页面头部大标题 --> <!-- 页面头部大标题 -->
<view class="page-header tn-margin-bottom-xl tn-margin-top-xl fade-in"> <view class="page-header tn-margin-bottom-xl tn-margin-top-xl fade-in">
<view class="tn-text-bold tn-color-black title-text">{{ t('pageTitle') }}</view> <view class="tn-text-bold tn-color-black title-text">{{ $tr('欢迎回来') }}</view>
<view class="tn-color-gray tn-text-sm tn-margin-top-sm subtitle-text">{{ t('pageDesc') }}</view> <view class="tn-color-gray tn-text-sm tn-margin-top-sm subtitle-text">{{ $tr('登录绿居公寓,探索您的国际化青年社区') }}</view>
</view> </view>
<!-- 登录表单 --> <!-- 登录表单 -->
<view class="form-area tn-margin-top-xl fade-in-up"> <view class="form-area tn-margin-top-xl fade-in-up">
<view class="form-item tn-margin-bottom-lg"> <view class="form-item tn-margin-bottom-lg">
<view class="form-label tn-text-bold tn-margin-bottom-sm">{{ t('accountLabel') }}</view> <view class="form-label tn-text-bold tn-margin-bottom-sm">{{ $tr('账号 / 邮箱') }}</view>
<view class="input-wrapper" :class="{ 'is-focused': accountFocused }"> <view class="input-wrapper" :class="{ 'is-focused': accountFocused }">
<text class="tn-icon-my input-icon"></text> <text class="tn-icon-my input-icon"></text>
<input <input
class="form-input" class="form-input"
:placeholder="t('accountPlaceholder')" :placeholder="$tr('请输入账号或邮箱')"
placeholder-class="input-placeholder" placeholder-class="input-placeholder"
v-model="form.account" v-model="form.account"
@focus="accountFocused = true" @focus="accountFocused = true"
@@ -30,13 +30,13 @@
</view> </view>
<view class="form-item tn-margin-bottom-xl"> <view class="form-item tn-margin-bottom-xl">
<view class="form-label tn-text-bold tn-margin-bottom-sm">{{ t('passwordLabel') }}</view> <view class="form-label tn-text-bold tn-margin-bottom-sm">{{ $tr('密码') }}</view>
<view class="input-wrapper" :class="{ 'is-focused': passwordFocused }"> <view class="input-wrapper" :class="{ 'is-focused': passwordFocused }">
<text class="tn-icon-lock input-icon"></text> <text class="tn-icon-lock input-icon"></text>
<input <input
class="form-input" class="form-input"
type="password" type="password"
:placeholder="t('passwordPlaceholder')" :placeholder="$tr('请输入密码')"
placeholder-class="input-placeholder" placeholder-class="input-placeholder"
v-model="form.password" v-model="form.password"
@focus="passwordFocused = true" @focus="passwordFocused = true"
@@ -50,22 +50,22 @@
<view class="action-area tn-margin-top-xxl fade-in-up" style="animation-delay: 0.2s;"> <view class="action-area tn-margin-top-xxl fade-in-up" style="animation-delay: 0.2s;">
<view class="custom-btn btn-primary" @click="handleLogin" :class="{ 'btn-disabled': isSubmitting }"> <view class="custom-btn btn-primary" @click="handleLogin" :class="{ 'btn-disabled': isSubmitting }">
<text v-if="isSubmitting" class="tn-icon-loading tn-margin-right-xs spin"></text> <text v-if="isSubmitting" class="tn-icon-loading tn-margin-right-xs spin"></text>
<text class="tn-text-bold tn-text-lg">{{ isSubmitting ? t('submitting') : t('submitLogin') }}</text> <text class="tn-text-bold tn-text-lg">{{ isSubmitting ? $tr('登录中...') : $tr('立即登录') }}</text>
</view> </view>
<view class="secondary-entry-card tn-margin-top-lg" @click="goToRegistrationRecords"> <view class="secondary-entry-card tn-margin-top-lg" @click="goToRegistrationRecords">
<view class="tn-flex tn-flex-row-between tn-flex-col-center"> <view class="tn-flex tn-flex-row-between tn-flex-col-center">
<view> <view>
<view class="secondary-entry-title">{{ t('viewRegistrationRecords') }}</view> <view class="secondary-entry-title">{{ $tr('查看注册记录') }}</view>
<view class="secondary-entry-desc tn-margin-top-xs">{{ t('viewRegistrationRecordsDesc') }}</view> <view class="secondary-entry-desc tn-margin-top-xs">{{ $tr('未登录也可查看当前微信账号提交的注册申请记录') }}</view>
</view> </view>
<text class="tn-icon-right secondary-entry-arrow"></text> <text class="tn-icon-right secondary-entry-arrow"></text>
</view> </view>
</view> </view>
<view class="tn-flex tn-flex-row-center tn-margin-top-xl"> <view class="tn-flex tn-flex-row-center tn-margin-top-xl">
<view class="tn-color-gray tn-text-sm">{{ t('noAccount') }}</view> <view class="tn-color-gray tn-text-sm">{{ $tr('还没有账号?') }}</view>
<view class="tn-color-black tn-text-bold tn-text-sm tn-margin-left-xs register-link" @click="goToRegister">{{ t('goRegister') }}</view> <view class="tn-color-black tn-text-bold tn-text-sm tn-margin-left-xs register-link" @click="goToRegister">{{ $tr('去注册') }}</view>
</view> </view>
</view> </view>
@@ -96,44 +96,6 @@ export default {
form: { form: {
account: '', account: '',
password: '' password: ''
},
i18n: {
zh: {
pageTitle: '欢迎回来',
pageDesc: '登录绿居公寓,探索您的国际化青年社区',
accountLabel: '账号 / 邮箱',
accountPlaceholder: '请输入账号或邮箱',
passwordLabel: '密码',
passwordPlaceholder: '请输入密码',
submitting: '登录中...',
submitLogin: '立即登录',
viewRegistrationRecords: '查看注册记录',
viewRegistrationRecordsDesc: '未登录也可查看当前微信账号提交的注册申请记录',
noAccount: '还没有账号?',
goRegister: '去注册',
loading: '加载中...',
validateRequired: '请输入账号和密码',
loginSuccess: '登录成功',
loginFailed: '登录失败,请稍后重试'
},
en: {
pageTitle: 'Welcome Back',
pageDesc: 'Sign in to Luju Apartments and explore your international youth community.',
accountLabel: 'Account / Email',
accountPlaceholder: 'Enter account or email',
passwordLabel: 'Password',
passwordPlaceholder: 'Enter password',
submitting: 'Signing In...',
submitLogin: 'Sign In',
viewRegistrationRecords: 'View Registration Records',
viewRegistrationRecordsDesc: 'You can review registration records submitted by this Wechat account even without signing in.',
noAccount: 'No account yet?',
goRegister: 'Register',
loading: 'Loading...',
validateRequired: 'Please enter both account and password',
loginSuccess: 'Signed in successfully',
loginFailed: 'Sign-in failed, please try again later'
}
} }
} }
}, },
@@ -143,9 +105,6 @@ export default {
await this.checkPendingRegistration(bypass) await this.checkPendingRegistration(bypass)
}, },
methods: { methods: {
t(key) {
return this.$tm('pkgAccount.login.login', key)
},
normalizeRedirectUrl(value = '') { normalizeRedirectUrl(value = '') {
if (!value) { if (!value) {
return '' return ''
@@ -192,7 +151,7 @@ export default {
}) })
}, },
async checkPendingRegistration(bypassRedirect = false) { async checkPendingRegistration(bypassRedirect = false) {
uni.showLoading({ title: this.t('loading') }) uni.showLoading({ title: this.$tr('加载中...') })
try { try {
const providerValue = await this.getWechatCode() const providerValue = await this.getWechatCode()
if (!providerValue) { if (!providerValue) {
@@ -229,7 +188,7 @@ export default {
} }
uni.showToast({ uni.showToast({
title: this.t('validateRequired'), title: this.$tr('请输入账号和密码'),
icon: 'none' icon: 'none'
}) })
return false return false
@@ -361,7 +320,7 @@ export default {
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: this.t('loginSuccess'), title: this.$tr('登录成功'),
icon: 'success' icon: 'success'
}) })
setTimeout(() => { setTimeout(() => {
@@ -375,7 +334,7 @@ export default {
} }
this.isSubmitting = true this.isSubmitting = true
uni.showLoading({ title: this.t('submitting') }) uni.showLoading({ title: this.$tr('登录中...') })
return this.resolveLoginState() return this.resolveLoginState()
.then(loginState => { .then(loginState => {
@@ -390,7 +349,7 @@ export default {
uni.hideLoading() uni.hideLoading()
this.isSubmitting = false this.isSubmitting = false
uni.showToast({ uni.showToast({
title: this.t('loginFailed'), title: this.$tr('登录失败,请稍后重试'),
icon: 'none' icon: 'none'
}) })
}) })

View File

@@ -1,14 +1,14 @@
<template> <template>
<view class="register-page tn-safe-area-inset-bottom"> <view class="register-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" :backgroundColor="navBackgroundColor"> <tn-nav-bar :isBack="true" :bottomShadow="false" :backgroundColor="navBackgroundColor">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('注册账号') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="register-content" :style="{ paddingTop: vuex_custom_bar_height + 'px' }"> <view class="register-content" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
<view class="page-header animation-fade-in"> <view class="page-header animation-fade-in">
<view class="tn-text-bold tn-color-black page-title">{{ t('pageTitle') }}</view> <view class="tn-text-bold tn-color-black page-title">{{ $tr('注册并提交实名资料') }}</view>
<view class="tn-color-gray tn-text-sm page-desc"> <view class="tn-color-gray tn-text-sm page-desc">
{{ t('pageDesc') }} {{ $tr('账号信息和实名认证资料需要一起填写完成,提交的是注册申请单,审批通过后才算注册成功。') }}
</view> </view>
<view class="draft-tip-bar tn-margin-top-sm"> <view class="draft-tip-bar tn-margin-top-sm">
<text class="tn-icon-safe-fill draft-tip-icon"></text> <text class="tn-icon-safe-fill draft-tip-icon"></text>
@@ -19,57 +19,57 @@
<view class="form-card animation-fade-in" style="animation-delay: 0.1s;"> <view class="form-card animation-fade-in" style="animation-delay: 0.1s;">
<view class="section-title tn-text-bold tn-text-lg"> <view class="section-title tn-text-bold tn-text-lg">
<view class="title-indicator"></view> <view class="title-indicator"></view>
{{ t('accountSection') }} {{ $tr('账号信息') }}
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('accountLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('登录账号') }} <text class="required-mark">*</text></view>
<input class="form-input" :placeholder="t('accountPlaceholder')" placeholder-class="input-placeholder" v-model="form.account" /> <input class="form-input" :placeholder="$tr('请输入登录账号')" placeholder-class="input-placeholder" v-model="form.account" />
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('passwordLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('登录密码') }} <text class="required-mark">*</text></view>
<input class="form-input" type="password" :placeholder="t('passwordPlaceholder')" placeholder-class="input-placeholder" v-model="form.password" /> <input class="form-input" type="password" :placeholder="$tr('请输入 6 位及以上密码')" placeholder-class="input-placeholder" v-model="form.password" />
</view> </view>
<view class="form-item" style="margin-bottom: 0;"> <view class="form-item" style="margin-bottom: 0;">
<view class="form-label">{{ t('emailLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('邮箱') }} <text class="required-mark">*</text></view>
<input class="form-input" :placeholder="t('emailPlaceholder')" placeholder-class="input-placeholder" v-model="form.email" /> <input class="form-input" :placeholder="$tr('请输入邮箱')" placeholder-class="input-placeholder" v-model="form.email" />
</view> </view>
<!-- 暂时屏蔽邮箱验证码 --> <!-- 暂时屏蔽邮箱验证码 -->
<view class="form-item" style="margin-bottom: 0;" v-if="false"> <view class="form-item" style="margin-bottom: 0;" v-if="false">
<view class="form-label">{{ t('emailCodeLabel') }}</view> <view class="form-label">{{ $tr('邮箱验证码') }}</view>
<input class="form-input" :placeholder="t('emailCodePlaceholder')" placeholder-class="input-placeholder" v-model="form.emailVerifyCode" /> <input class="form-input" :placeholder="$tr('请输入邮箱验证码')" placeholder-class="input-placeholder" v-model="form.emailVerifyCode" />
</view> </view>
</view> </view>
<view class="form-card animation-fade-in" style="animation-delay: 0.2s;"> <view class="form-card animation-fade-in" style="animation-delay: 0.2s;">
<view class="section-title tn-text-bold tn-text-lg"> <view class="section-title tn-text-bold tn-text-lg">
<view class="title-indicator"></view> <view class="title-indicator"></view>
{{ t('basicSection') }} {{ $tr('基础实名信息') }}
</view> </view>
<view class="role-switch-wrap"> <view class="role-switch-wrap">
<view class="role-switch tn-flex"> <view class="role-switch tn-flex">
<view class="role-tab tn-flex-1" :class="form.identityType === 'ForeignStudents' ? 'role-active' : ''" @click="switchIdentityType('ForeignStudents')"> <view class="role-tab tn-flex-1" :class="form.identityType === 'ForeignStudents' ? 'role-active' : ''" @click="switchIdentityType('ForeignStudents')">
<view>{{ t('foreignIdentity') }}</view> <view>{{ $tr('外籍人员') }}</view>
<view v-if="language !== 'en'" class="role-tab-subtitle">{{ t('foreignIdentityHint') }}</view> <view v-if="language !== 'en'" class="role-tab-subtitle">{{ $tr('Foreign') }}</view>
</view> </view>
<view class="role-tab tn-flex-1" :class="form.identityType === 'DomesticStudents' ? 'role-active' : ''" @click="switchIdentityType('DomesticStudents')"> <view class="role-tab tn-flex-1" :class="form.identityType === 'DomesticStudents' ? 'role-active' : ''" @click="switchIdentityType('DomesticStudents')">
<view>{{ t('domesticIdentity') }}</view> <view>{{ $tr('中国籍') }}</view>
<view v-if="language !== 'en'" class="role-tab-subtitle">{{ t('domesticIdentityHint') }}</view> <view v-if="language !== 'en'" class="role-tab-subtitle">{{ $tr('Chinese') }}</view>
</view> </view>
</view> </view>
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('nameLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('姓名') }} <text class="required-mark">*</text></view>
<input class="form-input" :placeholder="t('namePlaceholder')" placeholder-class="input-placeholder" v-model="form.name" /> <input class="form-input" :placeholder="$tr('请输入真实姓名')" placeholder-class="input-placeholder" v-model="form.name" />
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('genderLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('性别') }} <text class="required-mark">*</text></view>
<view class="tn-flex"> <view class="tn-flex">
<view <view
v-for="(item, index) in genderOptions" v-for="(item, index) in genderOptions"
@@ -84,19 +84,19 @@
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('phoneLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('手机号') }} <text class="required-mark">*</text></view>
<input class="form-input" type="number" :placeholder="t('phonePlaceholder')" placeholder-class="input-placeholder" v-model="form.phone" /> <input class="form-input" type="number" :placeholder="$tr('请输入手机号')" placeholder-class="input-placeholder" v-model="form.phone" />
</view> </view>
<view class="form-item" style="margin-bottom: 0;"> <view class="form-item" style="margin-bottom: 0;">
<view class="form-label">{{ t('countryLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('国籍') }} <text class="required-mark">*</text></view>
<view v-if="isDomesticIdentity" class="form-input picker-input picker-input-disabled"> <view v-if="isDomesticIdentity" class="form-input picker-input picker-input-disabled">
<text class="text-black">{{ form.country || t('chinaText') }}</text> <text class="text-black">{{ form.country || $tr('中国') }}</text>
<text class="picker-fixed-text">{{ t('countryFixedText') }}</text> <text class="picker-fixed-text">{{ $tr('中国籍固定') }}</text>
</view> </view>
<picker v-else mode="selector" :range="countryRange" @change="onCountryChange"> <picker v-else mode="selector" :range="countryRange" @change="onCountryChange">
<view class="form-input picker-input"> <view class="form-input picker-input">
<text :class="form.country ? 'text-black' : 'text-gray'">{{ form.country || t('countryPlaceholder') }}</text> <text :class="form.country ? 'text-black' : 'text-gray'">{{ form.country || $tr('请选择国籍') }}</text>
<text class="tn-icon-right tn-color-gray"></text> <text class="tn-icon-right tn-color-gray"></text>
</view> </view>
</picker> </picker>
@@ -106,20 +106,20 @@
<view class="form-card animation-fade-in" style="animation-delay: 0.3s;"> <view class="form-card animation-fade-in" style="animation-delay: 0.3s;">
<view class="section-title tn-text-bold tn-text-lg"> <view class="section-title tn-text-bold tn-text-lg">
<view class="title-indicator"></view> <view class="title-indicator"></view>
{{ t('profileSection') }} {{ $tr('认证档案') }}
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('workUnitLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('工作单位') }} <text class="required-mark">*</text></view>
<input class="form-input" :placeholder="t('workUnitPlaceholder')" placeholder-class="input-placeholder" v-model="form.workUnit" /> <input class="form-input" :placeholder="$tr('请输入工作单位')" placeholder-class="input-placeholder" v-model="form.workUnit" />
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('admissionNoticeAttachmentLabel') }}</view> <view class="form-label">{{ $tr('入学通知书(在职证明)附件') }}</view>
<view v-if="!form.admissionNoticeAttachment.serverValue" class="upload-box" @click="uploadAttachment('admissionNoticeAttachment')"> <view v-if="!form.admissionNoticeAttachment.serverValue" class="upload-box" @click="uploadAttachment('admissionNoticeAttachment')">
<view class="upload-placeholder"> <view class="upload-placeholder">
<view class="upload-icon-wrap"><text class="tn-icon-upload"></text></view> <view class="upload-icon-wrap"><text class="tn-icon-upload"></text></view>
<view class="tn-margin-top-sm upload-text">{{ t('uploadAdmissionNotice') }}</view> <view class="tn-margin-top-sm upload-text">{{ $tr('点击上传入学通知书/在职证明') }}</view>
</view> </view>
</view> </view>
<view v-else class="attachment-file-card"> <view v-else class="attachment-file-card">
@@ -131,36 +131,36 @@
/> />
<view class="attachment-file-info"> <view class="attachment-file-info">
<view class="attachment-name">{{ form.admissionNoticeAttachment.name }}</view> <view class="attachment-name">{{ form.admissionNoticeAttachment.name }}</view>
<view class="attachment-type tn-margin-top-xs">{{ t('imageUploadedTip') }}</view> <view class="attachment-type tn-margin-top-xs">{{ $tr('已上传图片,可点击查看大图') }}</view>
</view> </view>
<view class="attachment-action-row"> <view class="attachment-action-row">
<view class="attachment-action-btn" @click.stop="previewAttachment('admissionNoticeAttachment')"> <view class="attachment-action-btn" @click.stop="previewAttachment('admissionNoticeAttachment')">
{{ t('viewImage') }} {{ $tr('查看图片') }}
</view> </view>
<view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('admissionNoticeAttachment')"> <view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('admissionNoticeAttachment')">
{{ t('reupload') }} {{ $tr('重新上传') }}
</view> </view>
</view> </view>
</view> </view>
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('hangzhouPropertyLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('杭州有无房产') }} <text class="required-mark">*</text></view>
<view class="tn-flex tn-flex-wrap"> <view class="tn-flex tn-flex-wrap">
<view class="tag-item" :class="form.hasHangzhouProperty === true ? 'tag-active' : ''" @click="form.hasHangzhouProperty = true">{{ t('yesText') }}</view> <view class="tag-item" :class="form.hasHangzhouProperty === true ? 'tag-active' : ''" @click="form.hasHangzhouProperty = true">{{ $tr('有') }}</view>
<view class="tag-item" :class="form.hasHangzhouProperty === false ? 'tag-active' : ''" @click="form.hasHangzhouProperty = false">{{ t('noText') }}</view> <view class="tag-item" :class="form.hasHangzhouProperty === false ? 'tag-active' : ''" @click="form.hasHangzhouProperty = false">{{ $tr('无') }}</view>
</view> </view>
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('idCardTypeLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('证件类型') }} <text class="required-mark">*</text></view>
<view v-if="isDomesticIdentity" class="form-input picker-input picker-input-disabled"> <view v-if="isDomesticIdentity" class="form-input picker-input picker-input-disabled">
<text class="text-black">{{ selectedIdCardTypeLabel || (language === 'en' ? 'ID Card' : '身份证') }}</text> <text class="text-black">{{ selectedIdCardTypeLabel || (language === 'en' ? 'ID Card' : '身份证') }}</text>
<text class="picker-fixed-text">{{ t('idCardTypeFixedText') }}</text> <text class="picker-fixed-text">{{ $tr('中国籍固定') }}</text>
</view> </view>
<picker v-else mode="selector" :range="idCardTypeRange" @change="onIdCardTypeChange"> <picker v-else mode="selector" :range="idCardTypeRange" @change="onIdCardTypeChange">
<view class="form-input picker-input"> <view class="form-input picker-input">
<text :class="selectedIdCardTypeLabel ? 'text-black' : 'text-gray'">{{ selectedIdCardTypeLabel || t('idCardTypePlaceholder') }}</text> <text :class="selectedIdCardTypeLabel ? 'text-black' : 'text-gray'">{{ selectedIdCardTypeLabel || $tr('请选择证件类型') }}</text>
<text class="tn-icon-right tn-color-gray"></text> <text class="tn-icon-right tn-color-gray"></text>
</view> </view>
</picker> </picker>
@@ -168,16 +168,16 @@
<template v-if="form.identityType === 'DomesticStudents'"> <template v-if="form.identityType === 'DomesticStudents'">
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('idCardLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('身份证号') }} <text class="required-mark">*</text></view>
<input class="form-input" :placeholder="t('idCardPlaceholder')" placeholder-class="input-placeholder" v-model="form.idCard" /> <input class="form-input" :placeholder="$tr('请输入身份证号')" placeholder-class="input-placeholder" v-model="form.idCard" />
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('idCardFrontAttachmentLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('身份证正面附件') }} <text class="required-mark">*</text></view>
<view v-if="!form.idCardFrontAttachment.serverValue" class="upload-box" @click="uploadAttachment('idCardFrontAttachment')"> <view v-if="!form.idCardFrontAttachment.serverValue" class="upload-box" @click="uploadAttachment('idCardFrontAttachment')">
<view class="upload-placeholder"> <view class="upload-placeholder">
<view class="upload-icon-wrap"><text class="tn-icon-camera-fill"></text></view> <view class="upload-icon-wrap"><text class="tn-icon-camera-fill"></text></view>
<view class="tn-margin-top-sm upload-text">{{ t('uploadIdFront') }}</view> <view class="tn-margin-top-sm upload-text">{{ $tr('点击上传身份证正面') }}</view>
</view> </view>
</view> </view>
<view v-else class="attachment-file-card"> <view v-else class="attachment-file-card">
@@ -189,25 +189,25 @@
/> />
<view class="attachment-file-info"> <view class="attachment-file-info">
<view class="attachment-name">{{ form.idCardFrontAttachment.name }}</view> <view class="attachment-name">{{ form.idCardFrontAttachment.name }}</view>
<view class="attachment-type tn-margin-top-xs">{{ t('imageUploadedTip') }}</view> <view class="attachment-type tn-margin-top-xs">{{ $tr('已上传图片,可点击查看大图') }}</view>
</view> </view>
<view class="attachment-action-row"> <view class="attachment-action-row">
<view class="attachment-action-btn" @click.stop="previewAttachment('idCardFrontAttachment')"> <view class="attachment-action-btn" @click.stop="previewAttachment('idCardFrontAttachment')">
{{ t('viewImage') }} {{ $tr('查看图片') }}
</view> </view>
<view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('idCardFrontAttachment')"> <view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('idCardFrontAttachment')">
{{ t('reupload') }} {{ $tr('重新上传') }}
</view> </view>
</view> </view>
</view> </view>
</view> </view>
<view class="form-item" style="margin-bottom: 0;"> <view class="form-item" style="margin-bottom: 0;">
<view class="form-label">{{ t('idCardBackAttachmentLabel') }} <text class="required-mark">*</text></view> <view class="form-label">{{ $tr('身份证反面附件') }} <text class="required-mark">*</text></view>
<view v-if="!form.idCardBackAttachment.serverValue" class="upload-box" @click="uploadAttachment('idCardBackAttachment')"> <view v-if="!form.idCardBackAttachment.serverValue" class="upload-box" @click="uploadAttachment('idCardBackAttachment')">
<view class="upload-placeholder"> <view class="upload-placeholder">
<view class="upload-icon-wrap"><text class="tn-icon-camera-fill"></text></view> <view class="upload-icon-wrap"><text class="tn-icon-camera-fill"></text></view>
<view class="tn-margin-top-sm upload-text">{{ t('uploadIdBack') }}</view> <view class="tn-margin-top-sm upload-text">{{ $tr('点击上传身份证反面') }}</view>
</view> </view>
</view> </view>
<view v-else class="attachment-file-card"> <view v-else class="attachment-file-card">
@@ -219,14 +219,14 @@
/> />
<view class="attachment-file-info"> <view class="attachment-file-info">
<view class="attachment-name">{{ form.idCardBackAttachment.name }}</view> <view class="attachment-name">{{ form.idCardBackAttachment.name }}</view>
<view class="attachment-type tn-margin-top-xs">{{ t('imageUploadedTip') }}</view> <view class="attachment-type tn-margin-top-xs">{{ $tr('已上传图片,可点击查看大图') }}</view>
</view> </view>
<view class="attachment-action-row"> <view class="attachment-action-row">
<view class="attachment-action-btn" @click.stop="previewAttachment('idCardBackAttachment')"> <view class="attachment-action-btn" @click.stop="previewAttachment('idCardBackAttachment')">
{{ t('viewImage') }} {{ $tr('查看图片') }}
</view> </view>
<view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('idCardBackAttachment')"> <view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('idCardBackAttachment')">
{{ t('reupload') }} {{ $tr('重新上传') }}
</view> </view>
</view> </view>
</view> </view>
@@ -243,15 +243,15 @@
<view class="form-label">{{ foreignDocumentExpiryLabel }} <text class="required-mark">*</text></view> <view class="form-label">{{ foreignDocumentExpiryLabel }} <text class="required-mark">*</text></view>
<picker mode="date" @change="onPassportDateChange"> <picker mode="date" @change="onPassportDateChange">
<view class="form-input picker-input"> <view class="form-input picker-input">
<text :class="form.passportExpiry ? 'text-black' : 'text-gray'">{{ form.passportExpiry || t('selectDate') }}</text> <text :class="form.passportExpiry ? 'text-black' : 'text-gray'">{{ form.passportExpiry || $tr('请选择日期') }}</text>
<text class="tn-icon-calendar tn-color-gray"></text> <text class="tn-icon-calendar tn-color-gray"></text>
</view> </view>
</picker> </picker>
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('schoolLabel') }}</view> <view class="form-label">{{ $tr('就读学校') }}</view>
<input class="form-input" :placeholder="t('schoolPlaceholder')" placeholder-class="input-placeholder" v-model="form.school" /> <input class="form-input" :placeholder="$tr('请输入就读学校(选填)')" placeholder-class="input-placeholder" v-model="form.school" />
</view> </view>
<view class="form-item" style="margin-bottom: 0;"> <view class="form-item" style="margin-bottom: 0;">
@@ -276,7 +276,7 @@
<view class="attachment-file-info"> <view class="attachment-file-info">
<view class="attachment-name">{{ form.passportAttachment.name }}</view> <view class="attachment-name">{{ form.passportAttachment.name }}</view>
<view class="attachment-type tn-margin-top-xs"> <view class="attachment-type tn-margin-top-xs">
{{ isImageAttachment(form.passportAttachment) ? t('imageUploadedTip') : foreignDocumentImageOnlyTip }} {{ isImageAttachment(form.passportAttachment) ? $tr('已上传图片,可点击查看大图') : foreignDocumentImageOnlyTip }}
</view> </view>
</view> </view>
<view class="attachment-action-row"> <view class="attachment-action-row">
@@ -285,10 +285,10 @@
class="attachment-action-btn" class="attachment-action-btn"
@click.stop="previewAttachment('passportAttachment')" @click.stop="previewAttachment('passportAttachment')"
> >
{{ t('viewImage') }} {{ $tr('查看图片') }}
</view> </view>
<view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('passportAttachment')"> <view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('passportAttachment')">
{{ t('reupload') }} {{ $tr('重新上传') }}
</view> </view>
</view> </view>
</view> </view>
@@ -299,34 +299,34 @@
<view class="form-card animation-fade-in" style="animation-delay: 0.4s;"> <view class="form-card animation-fade-in" style="animation-delay: 0.4s;">
<view class="section-title tn-text-bold tn-text-lg"> <view class="section-title tn-text-bold tn-text-lg">
<view class="title-indicator"></view> <view class="title-indicator"></view>
{{ t('preferenceSection') }} {{ $tr('偏好信息') }}
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('dietaryLabel') }}</view> <view class="form-label">{{ $tr('饮食习惯') }}</view>
<picker mode="selector" :range="dietaryHabitRange" @change="onDictSelect('dietaryHabit', $event)"> <picker mode="selector" :range="dietaryHabitRange" @change="onDictSelect('dietaryHabit', $event)">
<view class="form-input picker-input"> <view class="form-input picker-input">
<text :class="form.dietaryHabit ? 'text-black' : 'text-gray'">{{ form.dietaryHabit || t('dietaryPlaceholder') }}</text> <text :class="form.dietaryHabit ? 'text-black' : 'text-gray'">{{ form.dietaryHabit || $tr('请选择饮食习惯') }}</text>
<text class="tn-icon-right tn-color-gray"></text> <text class="tn-icon-right tn-color-gray"></text>
</view> </view>
</picker> </picker>
</view> </view>
<view class="form-item"> <view class="form-item">
<view class="form-label">{{ t('hobbyLabel') }}</view> <view class="form-label">{{ $tr('兴趣爱好') }}</view>
<picker mode="selector" :range="hobbyRange" @change="onDictSelect('hobby', $event)"> <picker mode="selector" :range="hobbyRange" @change="onDictSelect('hobby', $event)">
<view class="form-input picker-input"> <view class="form-input picker-input">
<text :class="form.hobby ? 'text-black' : 'text-gray'">{{ form.hobby || t('hobbyPlaceholder') }}</text> <text :class="form.hobby ? 'text-black' : 'text-gray'">{{ form.hobby || $tr('请选择兴趣爱好') }}</text>
<text class="tn-icon-right tn-color-gray"></text> <text class="tn-icon-right tn-color-gray"></text>
</view> </view>
</picker> </picker>
</view> </view>
<view class="form-item" style="margin-bottom: 0;"> <view class="form-item" style="margin-bottom: 0;">
<view class="form-label">{{ t('religionLabel') }}</view> <view class="form-label">{{ $tr('宗教信仰') }}</view>
<picker mode="selector" :range="religionRange" @change="onDictSelect('religion', $event)"> <picker mode="selector" :range="religionRange" @change="onDictSelect('religion', $event)">
<view class="form-input picker-input"> <view class="form-input picker-input">
<text :class="form.religion ? 'text-black' : 'text-gray'">{{ form.religion || t('religionPlaceholder') }}</text> <text :class="form.religion ? 'text-black' : 'text-gray'">{{ form.religion || $tr('请选择宗教信仰') }}</text>
<text class="tn-icon-right tn-color-gray"></text> <text class="tn-icon-right tn-color-gray"></text>
</view> </view>
</picker> </picker>
@@ -335,22 +335,22 @@
<view class="bottom-btn-area tn-margin-top-xl animation-fade-in" style="animation-delay: 0.5s;"> <view class="bottom-btn-area tn-margin-top-xl animation-fade-in" style="animation-delay: 0.5s;">
<view class="custom-btn btn-primary submit-btn" @click="submitForm" :class="{ 'btn-disabled': isSubmitting }"> <view class="custom-btn btn-primary submit-btn" @click="submitForm" :class="{ 'btn-disabled': isSubmitting }">
<text class="tn-text-bold tn-text-lg">{{ isSubmitting ? t('submitting') : t('submitRegister') }}</text> <text class="tn-text-bold tn-text-lg">{{ isSubmitting ? $tr('提交中...') : $tr('提交注册申请') }}</text>
</view> </view>
<view class="secondary-entry-card tn-margin-top-lg" @click="goToRegistrationRecords"> <view class="secondary-entry-card tn-margin-top-lg" @click="goToRegistrationRecords">
<view class="tn-flex tn-flex-row-between tn-flex-col-center"> <view class="tn-flex tn-flex-row-between tn-flex-col-center">
<view> <view>
<view class="secondary-entry-title">{{ t('viewRegistrationRecords') }}</view> <view class="secondary-entry-title">{{ $tr('查看注册记录') }}</view>
<view class="secondary-entry-desc tn-margin-top-xs">{{ t('viewRegistrationRecordsDesc') }}</view> <view class="secondary-entry-desc tn-margin-top-xs">{{ $tr('如果当前微信账号已提交过申请,可直接查看历史记录和审批状态') }}</view>
</view> </view>
<text class="tn-icon-right secondary-entry-arrow"></text> <text class="tn-icon-right secondary-entry-arrow"></text>
</view> </view>
</view> </view>
<view class="tn-flex tn-flex-row-center tn-margin-top-lg"> <view class="tn-flex tn-flex-row-center tn-margin-top-lg">
<view class="tn-color-gray tn-text-sm">{{ t('hasAccount') }}</view> <view class="tn-color-gray tn-text-sm">{{ $tr('已有账号?') }}</view>
<view class="tn-color-black tn-text-bold tn-text-sm tn-margin-left-xs login-link" @click="goToLogin">{{ t('goLogin') }}</view> <view class="tn-color-black tn-text-bold tn-text-sm tn-margin-left-xs login-link" @click="goToLogin">{{ $tr('去登录') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -358,21 +358,21 @@
<!-- #ifdef MP-WEIXIN --> <!-- #ifdef MP-WEIXIN -->
<view v-if="showPrivacyPopup" class="privacy-mask"> <view v-if="showPrivacyPopup" class="privacy-mask">
<view class="privacy-dialog"> <view class="privacy-dialog">
<view class="privacy-title">{{ t('privacyTitle') }}</view> <view class="privacy-title">{{ $tr('隐私授权提示') }}</view>
<view class="privacy-content"> <view class="privacy-content">
{{ t('privacyContentPrefix') }} {{ $tr('上传证件图片前,请先阅读并同意') }}
<text class="privacy-link" @click="openPrivacyContract">{{ t('privacyContractName') }}</text> <text class="privacy-link" @click="openPrivacyContract">{{ $tr('《用户隐私保护指引》') }}</text>
{{ t('privacyContentSuffix') }} {{ $tr(',同意后才能继续选择图片。') }}
</view> </view>
<view class="privacy-action-row"> <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 <button
id="register-privacy-agree-btn" id="register-privacy-agree-btn"
class="privacy-action-btn is-primary" class="privacy-action-btn is-primary"
open-type="agreePrivacyAuthorization" open-type="agreePrivacyAuthorization"
@agreeprivacyauthorization="handleAgreePrivacyAuthorization" @agreeprivacyauthorization="handleAgreePrivacyAuthorization"
> >
{{ t('privacyAgree') }} {{ $tr('同意并继续') }}
</button> </button>
</view> </view>
</view> </view>
@@ -479,247 +479,7 @@ export default {
hobbyOptions: [], hobbyOptions: [],
dietaryHabitOptions: [], dietaryHabitOptions: [],
religionOptions: [], religionOptions: [],
idCardTypeOptions: [], idCardTypeOptions: []
i18n: {
zh: {
navTitle: '注册账号',
pageTitle: '注册并提交实名资料',
pageDesc: '账号信息和实名认证资料需要一起填写完成,提交的是注册申请单,审批通过后才算注册成功。',
accountSection: '账号信息',
admissionNoticeAttachmentLabel: '入学通知书(在职证明)附件',
accountLabel: '登录账号',
accountPlaceholder: '请输入登录账号',
passwordLabel: '登录密码',
passwordPlaceholder: '请输入 6 位及以上密码',
emailLabel: '邮箱',
emailPlaceholder: '请输入邮箱',
emailCodeLabel: '邮箱验证码',
emailCodePlaceholder: '请输入邮箱验证码',
basicSection: '基础实名信息',
foreignIdentity: '外籍人员',
foreignIdentityHint: 'Foreign',
domesticIdentity: '中国籍',
domesticIdentityHint: 'Chinese',
nameLabel: '姓名',
namePlaceholder: '请输入真实姓名',
genderLabel: '性别',
genderMale: '男',
genderFemale: '女',
phoneLabel: '手机号',
phonePlaceholder: '请输入手机号',
countryLabel: '国籍',
chinaText: '中国',
countryFixedText: '中国籍固定',
countryPlaceholder: '请选择国籍',
profileSection: '认证档案',
workUnitLabel: '工作单位',
workUnitPlaceholder: '请输入工作单位',
hangzhouPropertyLabel: '杭州有无房产',
yesText: '有',
noText: '无',
idCardTypeLabel: '证件类型',
idCardTypePlaceholder: '请选择证件类型',
idCardTypeFixedText: '中国籍固定',
idCardLabel: '身份证号',
idCardPlaceholder: '请输入身份证号',
idCardFrontAttachmentLabel: '身份证正面附件',
idCardBackAttachmentLabel: '身份证反面附件',
uploadIdFront: '点击上传身份证正面',
uploadIdBack: '点击上传身份证反面',
imageUploadedTip: '已上传图片,可点击查看大图',
viewImage: '查看图片',
reupload: '重新上传',
passportLabel: '护照号',
passportPlaceholder: '请输入护照号',
passportExpiryLabel: '护照到期时间',
documentNumberLabel: '证件号',
documentNumberPlaceholder: '请输入证件号',
documentExpiryLabel: '证件到期时间',
selectDate: '请选择日期',
schoolLabel: '就读学校',
schoolPlaceholder: '请输入就读学校(选填)',
passportAttachmentLabel: '护照附件',
documentAttachmentLabel: '证件附件',
uploadPassport: '点击上传护照图片',
uploadDocument: '点击上传证件图片',
passportImageOnlyTip: '护照附件仅支持图片,请重新上传',
documentImageOnlyTip: '证件附件仅支持图片,请重新上传',
preferenceSection: '偏好信息',
dietaryLabel: '饮食习惯',
hobbyLabel: '兴趣爱好',
religionLabel: '宗教信仰',
dietaryPlaceholder: '请选择饮食习惯',
hobbyPlaceholder: '请选择兴趣爱好',
religionPlaceholder: '请选择宗教信仰',
submitting: '提交中...',
submitRegister: '提交注册申请',
viewRegistrationRecords: '查看注册记录',
viewRegistrationRecordsDesc: '如果当前微信账号已提交过申请,可直接查看历史记录和审批状态',
hasAccount: '已有账号?',
goLogin: '去登录',
loading: '加载中...',
restoreDraftTitle: '发现未完成的数据',
restoreDraftContent: '是否填入上次未完成或提交失败时缓存的注册信息?',
restoreDraftConfirm: '填入',
restoreDraftCancel: '不用',
restoreDraftSuccess: '已填入上次失败数据',
countryLoadFailed: '国家列表加载失败',
dictLoadFailed: '字典加载失败,稍后可重试',
noSelectableOptions: '当前暂无可选项',
noSelectableCountry: '当前暂无可选国家',
uploading: '上传中...',
uploadSuccess: '上传成功',
uploadFailed: '上传失败,请重试',
privacyTitle: '隐私授权提示',
privacyContentPrefix: '上传证件图片前,请先阅读并同意',
privacyContractName: '《用户隐私保护指引》',
privacyContentSuffix: ',同意后才能继续选择图片。',
privacyAgree: '同意并继续',
privacyDisagree: '暂不授权',
privacyAuthorizeFailed: '未完成隐私授权,暂时无法选择图片',
attachmentNotImage: '当前附件不是图片',
uploadImageOption: '上传图片',
submitPending: '申请已提交,待审批',
submitFailed: '提交失败,请稍后重试',
draftTipDefault: '未完成内容会自动保存,可随时返回继续填写',
draftTipSaved: '当前填写内容已自动保存',
uploadAdmissionNotice: '点击上传入学通知书/在职证明',
validateRequiredBasic: '请先填写账号、密码、邮箱和姓名',
validatePasswordLength: '登录密码至少 6 位',
validateEmail: '请输入正确的邮箱地址',
validateIdentityBasic: '请完善性别、手机号和国籍',
validatePhone: '请输入正确的手机号',
validateProfile: '请完善工作单位和杭州房产情况',
validateIdCardType: '请选择证件类型',
validateIdCard: '请填写身份证号',
validateIdCardAttachments: '请上传身份证正反面附件',
validateDocumentBasic: '请填写证件号和证件到期时间',
validateDocumentAttachment: '请上传证件图片',
validateDocumentAttachmentImage: '证件附件仅支持图片格式',
validatePassportBasic: '请填写护照号和护照到期时间',
validatePassportAttachment: '请上传护照图片',
validatePassportAttachmentImage: '护照附件仅支持图片格式'
},
en: {
navTitle: 'Register',
pageTitle: 'Register and Submit Verification',
pageDesc: 'Account information and identity verification materials must be completed together. Registration succeeds only after approval.',
accountSection: 'Account Information',
admissionNoticeAttachmentLabel: 'Admission Notice (Employment Certificate)',
accountLabel: 'Account',
accountPlaceholder: 'Enter account name',
passwordLabel: 'Password',
passwordPlaceholder: 'Enter at least 6 characters',
emailLabel: 'Email',
emailPlaceholder: 'Enter email',
emailCodeLabel: 'Email Code',
emailCodePlaceholder: 'Enter email verification code',
basicSection: 'Basic Verification',
foreignIdentity: 'Foreign',
foreignIdentityHint: 'Foreign',
domesticIdentity: 'Chinese',
domesticIdentityHint: 'Chinese',
nameLabel: 'Name',
namePlaceholder: 'Enter your real name',
genderLabel: 'Gender',
genderMale: 'Male',
genderFemale: 'Female',
phoneLabel: 'Phone',
phonePlaceholder: 'Enter phone number',
countryLabel: 'Country',
chinaText: 'China',
countryFixedText: 'Fixed for Chinese users',
countryPlaceholder: 'Select country',
profileSection: 'Profile',
workUnitLabel: 'Work Unit',
workUnitPlaceholder: 'Enter work unit',
hangzhouPropertyLabel: 'Property in Hangzhou',
yesText: 'Yes',
noText: 'No',
idCardTypeLabel: 'Document Type',
idCardTypePlaceholder: 'Select document type',
idCardTypeFixedText: 'Fixed for Chinese users',
idCardLabel: 'ID Card Number',
idCardPlaceholder: 'Enter ID card number',
idCardFrontAttachmentLabel: 'ID Card Front',
idCardBackAttachmentLabel: 'ID Card Back',
uploadIdFront: 'Upload ID card front',
uploadIdBack: 'Upload ID card back',
imageUploadedTip: 'Image uploaded, tap to preview',
viewImage: 'Preview',
reupload: 'Re-upload',
passportLabel: 'Passport Number',
passportPlaceholder: 'Enter passport number',
passportExpiryLabel: 'Passport Expiry Date',
documentNumberLabel: 'Document Number',
documentNumberPlaceholder: 'Enter document number',
documentExpiryLabel: 'Document Expiry Date',
selectDate: 'Select date',
schoolLabel: 'School',
schoolPlaceholder: 'Enter school (optional)',
passportAttachmentLabel: 'Passport Attachment',
documentAttachmentLabel: 'Document Attachment',
uploadPassport: 'Upload passport image',
uploadDocument: 'Upload document image',
passportImageOnlyTip: 'Passport attachment only supports images, please re-upload',
documentImageOnlyTip: 'Document attachment only supports images, please re-upload',
preferenceSection: 'Preferences',
dietaryLabel: 'Dietary Habit',
hobbyLabel: 'Hobby',
religionLabel: 'Religion',
dietaryPlaceholder: 'Select dietary habit',
hobbyPlaceholder: 'Select hobby',
religionPlaceholder: 'Select religion',
submitting: 'Submitting...',
submitRegister: 'Submit Registration',
viewRegistrationRecords: 'View Registration Records',
viewRegistrationRecordsDesc: 'If this Wechat account has submitted before, you can review the history and approval status directly.',
hasAccount: 'Already have an account?',
goLogin: 'Sign In',
loading: 'Loading...',
restoreDraftTitle: 'Unfinished Data Found',
restoreDraftContent: 'Would you like to restore the last unfinished or failed registration data?',
restoreDraftConfirm: 'Restore',
restoreDraftCancel: 'No',
restoreDraftSuccess: 'Previous draft restored',
countryLoadFailed: 'Failed to load country list',
dictLoadFailed: 'Failed to load dictionaries',
noSelectableOptions: 'No selectable options',
noSelectableCountry: 'No selectable country',
uploading: 'Uploading...',
uploadSuccess: 'Upload successful',
uploadFailed: 'Upload failed, please retry',
privacyTitle: 'Privacy Authorization',
privacyContentPrefix: 'Before uploading document images, please read and agree to the ',
privacyContractName: 'Privacy Protection Guide',
privacyContentSuffix: ' to continue selecting images.',
privacyAgree: 'Agree and Continue',
privacyDisagree: 'Not Now',
privacyAuthorizeFailed: 'Privacy authorization is required before selecting images',
attachmentNotImage: 'The current attachment is not an image',
uploadImageOption: 'Upload Image',
submitPending: 'Application submitted, pending approval',
submitFailed: 'Submission failed, please try again later',
draftTipDefault: 'Unfinished content is auto-saved and can be continued later',
draftTipSaved: 'Current content has been auto-saved',
uploadAdmissionNotice: 'Upload admission notice or employment certificate',
validateRequiredBasic: 'Please complete the account, password, email, and name fields first',
validatePasswordLength: 'Password must be at least 6 characters',
validateEmail: 'Please enter a valid email address',
validateIdentityBasic: 'Please complete gender, phone number, and country',
validatePhone: 'Please enter a valid phone number',
validateProfile: 'Please complete work unit and property status in Hangzhou',
validateIdCardType: 'Please select a document type',
validateIdCard: 'Please enter your ID card number',
validateIdCardAttachments: 'Please upload both sides of the ID card',
validateDocumentBasic: 'Please enter the document number and expiry date',
validateDocumentAttachment: 'Please upload the document image',
validateDocumentAttachmentImage: 'Document attachment only supports image files',
validatePassportBasic: 'Please enter passport number and expiry date',
validatePassportAttachment: 'Please upload the passport image',
validatePassportAttachmentImage: 'Passport attachment only supports image files'
}
}
} }
}, },
computed: { computed: {
@@ -730,22 +490,22 @@ export default {
return `rgba(255, 255, 255, ${this.navBgOpacity})` return `rgba(255, 255, 255, ${this.navBgOpacity})`
}, },
hobbyRange() { hobbyRange() {
return this.hobbyOptions.length ? this.hobbyOptions.map(item => item.value) : [this.t('noSelectableOptions')] return this.hobbyOptions.length ? this.hobbyOptions.map(item => item.value) : [this.$tr('当前暂无可选项')]
}, },
countryRange() { countryRange() {
return this.countryOptions.length ? this.countryOptions.map(item => item.value) : [this.t('noSelectableOptions')] return this.countryOptions.length ? this.countryOptions.map(item => item.value) : [this.$tr('当前暂无可选项')]
}, },
idCardTypeRange() { idCardTypeRange() {
return this.idCardTypeOptions.length ? this.idCardTypeOptions.map(item => item.value) : [this.t('noSelectableOptions')] return this.idCardTypeOptions.length ? this.idCardTypeOptions.map(item => item.value) : [this.$tr('当前暂无可选项')]
}, },
dietaryHabitRange() { dietaryHabitRange() {
return this.dietaryHabitOptions.length ? this.dietaryHabitOptions.map(item => item.value) : [this.t('noSelectableOptions')] return this.dietaryHabitOptions.length ? this.dietaryHabitOptions.map(item => item.value) : [this.$tr('当前暂无可选项')]
}, },
isDomesticIdentity() { isDomesticIdentity() {
return this.form.identityType === 'DomesticStudents' return this.form.identityType === 'DomesticStudents'
}, },
religionRange() { religionRange() {
return this.religionOptions.length ? this.religionOptions.map(item => item.value) : [this.t('noSelectableOptions')] return this.religionOptions.length ? this.religionOptions.map(item => item.value) : [this.$tr('当前暂无可选项')]
}, },
selectedIdCardTypeLabel() { selectedIdCardTypeLabel() {
const matched = this.idCardTypeOptions.find(item => String(item.key) === String(this.form.idCardType)) const matched = this.idCardTypeOptions.find(item => String(item.key) === String(this.form.idCardType))
@@ -755,34 +515,34 @@ export default {
return String(this.form.idCardType) === '2' return String(this.form.idCardType) === '2'
}, },
foreignDocumentNumberLabel() { foreignDocumentNumberLabel() {
return this.isPassportType ? this.t('passportLabel') : this.t('documentNumberLabel') return this.isPassportType ? this.$tr('护照号') : this.$tr('证件号')
}, },
foreignDocumentNumberPlaceholder() { foreignDocumentNumberPlaceholder() {
return this.isPassportType ? this.t('passportPlaceholder') : this.t('documentNumberPlaceholder') return this.isPassportType ? this.$tr('请输入护照号') : this.$tr('请输入证件号')
}, },
foreignDocumentExpiryLabel() { foreignDocumentExpiryLabel() {
return this.isPassportType ? this.t('passportExpiryLabel') : this.t('documentExpiryLabel') return this.isPassportType ? this.$tr('护照到期时间') : this.$tr('证件到期时间')
}, },
foreignDocumentAttachmentLabel() { foreignDocumentAttachmentLabel() {
return this.isPassportType ? this.t('passportAttachmentLabel') : this.t('documentAttachmentLabel') return this.isPassportType ? this.$tr('护照附件') : this.$tr('证件附件')
}, },
foreignDocumentUploadText() { foreignDocumentUploadText() {
return this.isPassportType ? this.t('uploadPassport') : this.t('uploadDocument') return this.isPassportType ? this.$tr('点击上传护照图片') : this.$tr('点击上传证件图片')
}, },
foreignDocumentImageOnlyTip() { foreignDocumentImageOnlyTip() {
return this.isPassportType ? this.t('passportImageOnlyTip') : this.t('documentImageOnlyTip') return this.isPassportType ? this.$tr('护照附件仅支持图片,请重新上传') : this.$tr('证件附件仅支持图片,请重新上传')
}, },
genderOptions() { genderOptions() {
return [ return [
{ key: 1, value: this.t('genderMale') }, { key: 1, value: this.$tr('男') },
{ key: 0, value: this.t('genderFemale') } { key: 0, value: this.$tr('女') }
] ]
} }
}, },
watch: { watch: {
language() { language() {
this.rebuildLocalizedOptions() this.rebuildLocalizedOptions()
this.draftTipText = this.t('draftTipDefault') this.draftTipText = this.$tr('未完成内容会自动保存,可随时返回继续填写')
}, },
form: { form: {
deep: true, deep: true,
@@ -792,7 +552,7 @@ export default {
} }
}, },
async onLoad(options) { async onLoad(options) {
this.draftTipText = this.t('draftTipDefault') this.draftTipText = this.$tr('未完成内容会自动保存,可随时返回继续填写')
this.initPrivacyAuthorization() this.initPrivacyAuthorization()
const bypass = options && options.bypass === '1' const bypass = options && options.bypass === '1'
const hasRedirected = await this.checkPendingRegistration(bypass) const hasRedirected = await this.checkPendingRegistration(bypass)
@@ -823,15 +583,12 @@ export default {
this.navBgOpacity = opacity this.navBgOpacity = opacity
}, },
methods: { methods: {
t(key) {
return this.$tm('pkgAccount.register.register', key)
},
isChinaCountry(value = '') { isChinaCountry(value = '') {
const text = String(value || '').trim() const text = String(value || '').trim()
return text === '中国' || text === 'China' || text === this.t('chinaText') return text === '中国' || text === 'China' || text === this.$tr('中国')
}, },
async checkPendingRegistration(bypassRedirect = false) { async checkPendingRegistration(bypassRedirect = false) {
uni.showLoading({ title: this.t('loading') }) uni.showLoading({ title: this.$tr('加载中...') })
try { try {
const providerValue = await this.getWechatCode() const providerValue = await this.getWechatCode()
if (!providerValue) return false if (!providerValue) return false
@@ -873,10 +630,10 @@ export default {
this.hasPromptedFailedDraft = true this.hasPromptedFailedDraft = true
uni.showModal({ uni.showModal({
title: this.t('restoreDraftTitle'), title: this.$tr('发现未完成的数据'),
content: this.t('restoreDraftContent'), content: this.$tr('是否填入上次未完成或提交失败时缓存的注册信息?'),
confirmText: this.t('restoreDraftConfirm'), confirmText: this.$tr('填入'),
cancelText: this.t('restoreDraftCancel'), cancelText: this.$tr('不用'),
success: ({ confirm }) => { success: ({ confirm }) => {
if (confirm) { if (confirm) {
this.applyFailedDraft(cachedDraft) this.applyFailedDraft(cachedDraft)
@@ -897,7 +654,7 @@ export default {
this.applyIdentityDocumentTypeRule() this.applyIdentityDocumentTypeRule()
this.applyIdentityCountryRule() this.applyIdentityCountryRule()
uni.showToast({ uni.showToast({
title: this.t('restoreDraftSuccess'), title: this.$tr('已填入上次失败数据'),
icon: 'success' icon: 'success'
}) })
}, },
@@ -966,7 +723,7 @@ export default {
this.clearDraftPersistTimer() this.clearDraftPersistTimer()
if (!this.hasDraftContent()) { if (!this.hasDraftContent()) {
clearFailedRegistrationDraft() clearFailedRegistrationDraft()
this.draftTipText = this.t('draftTipDefault') this.draftTipText = this.$tr('未完成内容会自动保存,可随时返回继续填写')
return return
} }
saveFailedRegistrationDraft({ saveFailedRegistrationDraft({
@@ -974,7 +731,7 @@ export default {
updatedAt: Date.now() updatedAt: Date.now()
}) })
if (!isSilent) { if (!isSilent) {
this.draftTipText = this.t('draftTipSaved') this.draftTipText = this.$tr('当前填写内容已自动保存')
} }
}, },
switchIdentityType(type) { switchIdentityType(type) {
@@ -1000,7 +757,7 @@ export default {
.catch((error) => { .catch((error) => {
console.error('load country options failed', error) console.error('load country options failed', error)
uni.showToast({ uni.showToast({
title: this.t('countryLoadFailed'), title: this.$tr('国家列表加载失败'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -1027,7 +784,7 @@ export default {
.catch((error) => { .catch((error) => {
console.error('load dict options failed', error) console.error('load dict options failed', error)
uni.showToast({ uni.showToast({
title: this.t('dictLoadFailed'), title: this.$tr('字典加载失败,稍后可重试'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -1128,7 +885,7 @@ export default {
applyIdentityCountryRule() { applyIdentityCountryRule() {
if (this.isDomesticIdentity) { if (this.isDomesticIdentity) {
const chinaOption = this.getChinaCountryOption() const chinaOption = this.getChinaCountryOption()
this.form.country = chinaOption ? chinaOption.value : this.t('chinaText') this.form.country = chinaOption ? chinaOption.value : this.$tr('中国')
return return
} }
if (this.isChinaCountry(this.form.country)) { if (this.isChinaCountry(this.form.country)) {
@@ -1144,7 +901,7 @@ export default {
const options = map[field] || [] const options = map[field] || []
if (!options.length) { if (!options.length) {
uni.showToast({ uni.showToast({
title: this.t('noSelectableOptions'), title: this.$tr('当前暂无可选项'),
icon: 'none' icon: 'none'
}) })
return return
@@ -1155,7 +912,7 @@ export default {
onIdCardTypeChange(event) { onIdCardTypeChange(event) {
if (!this.idCardTypeOptions.length) { if (!this.idCardTypeOptions.length) {
uni.showToast({ uni.showToast({
title: this.t('noSelectableOptions'), title: this.$tr('当前暂无可选项'),
icon: 'none' icon: 'none'
}) })
return return
@@ -1171,7 +928,7 @@ export default {
if (this.isDomesticIdentity) return if (this.isDomesticIdentity) return
if (!this.countryOptions.length) { if (!this.countryOptions.length) {
uni.showToast({ uni.showToast({
title: this.t('noSelectableCountry'), title: this.$tr('当前暂无可选国家'),
icon: 'none' icon: 'none'
}) })
return return
@@ -1283,7 +1040,7 @@ export default {
.then((file) => { .then((file) => {
if (!file || !file.path) return if (!file || !file.path) return
uni.showLoading({ uni.showLoading({
title: this.t('uploading') title: this.$tr('上传中...')
}) })
return uploadCommonFile(file.path).then((uploadResult) => { return uploadCommonFile(file.path).then((uploadResult) => {
@@ -1296,7 +1053,7 @@ export default {
} }
uni.showToast({ uni.showToast({
title: this.t('uploadSuccess'), title: this.$tr('上传成功'),
icon: 'success' icon: 'success'
}) })
}) })
@@ -1305,14 +1062,14 @@ export default {
if (error && error.cancel) return if (error && error.cancel) return
if (error && error.privacyDenied) { if (error && error.privacyDenied) {
uni.showToast({ uni.showToast({
title: this.t('privacyAuthorizeFailed'), title: this.$tr('未完成隐私授权,暂时无法选择图片'),
icon: 'none' icon: 'none'
}) })
return return
} }
console.error('upload attachment failed', error) console.error('upload attachment failed', error)
uni.showToast({ uni.showToast({
title: this.t('uploadFailed'), title: this.$tr('上传失败,请重试'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -1348,7 +1105,7 @@ export default {
const previewUrl = this.getAttachmentPreviewUrl(attachment) const previewUrl = this.getAttachmentPreviewUrl(attachment)
if (!previewUrl || !this.isImageAttachment(attachment)) { if (!previewUrl || !this.isImageAttachment(attachment)) {
uni.showToast({ uni.showToast({
title: this.t('attachmentNotImage'), title: this.$tr('当前附件不是图片'),
icon: 'none' icon: 'none'
}) })
return return
@@ -1403,7 +1160,7 @@ export default {
this.isSubmitting = true this.isSubmitting = true
uni.showLoading({ uni.showLoading({
title: this.t('submitting') title: this.$tr('提交中...')
}) })
return this.getWechatCode().then((providerValue) => { return this.getWechatCode().then((providerValue) => {
@@ -1417,7 +1174,7 @@ export default {
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: this.t('submitPending'), title: this.$tr('申请已提交,待审批'),
icon: 'success' icon: 'success'
}) })
@@ -1432,7 +1189,7 @@ export default {
saveFailedRegistrationDraft(this.buildFailedDraft()) saveFailedRegistrationDraft(this.buildFailedDraft())
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('submitFailed'), title: (error && (error.message || error.msg)) || this.$tr('提交失败,请稍后重试'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -1488,45 +1245,45 @@ export default {
}, },
validateForm() { validateForm() {
if (!this.form.account || !this.form.password || !this.form.email || !this.form.name) { if (!this.form.account || !this.form.password || !this.form.email || !this.form.name) {
return this.t('validateRequiredBasic') return this.$tr('请先填写账号、密码、邮箱和姓名')
} }
if (this.form.password.length < 6) { if (this.form.password.length < 6) {
return this.t('validatePasswordLength') return this.$tr('登录密码至少 6 位')
} }
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.form.email)) { if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.form.email)) {
return this.t('validateEmail') return this.$tr('请输入正确的邮箱地址')
} }
if (this.form.gender === null || !this.form.phone || !this.form.country) { if (this.form.gender === null || !this.form.phone || !this.form.country) {
return this.t('validateIdentityBasic') return this.$tr('请完善性别、手机号和国籍')
} }
if (!/^1\d{10}$/.test(this.form.phone)) { if (!/^1\d{10}$/.test(this.form.phone)) {
return this.t('validatePhone') return this.$tr('请输入正确的手机号')
} }
if (!this.form.workUnit || this.form.hasHangzhouProperty === null) { if (!this.form.workUnit || this.form.hasHangzhouProperty === null) {
return this.t('validateProfile') return this.$tr('请完善工作单位和杭州房产情况')
} }
if (!this.form.idCardType) { if (!this.form.idCardType) {
return this.t('validateIdCardType') return this.$tr('请选择证件类型')
} }
if (this.form.identityType === 'DomesticStudents') { if (this.form.identityType === 'DomesticStudents') {
if (!this.form.idCard) { if (!this.form.idCard) {
return this.t('validateIdCard') return this.$tr('请填写身份证号')
} }
if (!this.form.idCardFrontAttachment.serverValue || !this.form.idCardBackAttachment.serverValue) { if (!this.form.idCardFrontAttachment.serverValue || !this.form.idCardBackAttachment.serverValue) {
return this.t('validateIdCardAttachments') return this.$tr('请上传身份证正反面附件')
} }
return '' return ''
} }
if (!this.form.passportNumber || !this.form.passportExpiry) { if (!this.form.passportNumber || !this.form.passportExpiry) {
return this.isPassportType ? this.t('validatePassportBasic') : this.t('validateDocumentBasic') return this.isPassportType ? this.$tr('请填写护照号和护照到期时间') : this.$tr('请填写证件号和证件到期时间')
} }
if (!this.form.passportAttachment.serverValue) { if (!this.form.passportAttachment.serverValue) {
return this.isPassportType ? this.t('validatePassportAttachment') : this.t('validateDocumentAttachment') return this.isPassportType ? this.$tr('请上传护照图片') : this.$tr('请上传证件图片')
} }
if (!this.isImageAttachment(this.form.passportAttachment)) { if (!this.isImageAttachment(this.form.passportAttachment)) {
return this.isPassportType ? this.t('validatePassportAttachmentImage') : this.t('validateDocumentAttachmentImage') return this.isPassportType ? this.$tr('护照附件仅支持图片格式') : this.$tr('证件附件仅支持图片格式')
} }
return '' return ''
}, },
@@ -1547,7 +1304,7 @@ export default {
roleNo: this.form.identityType, roleNo: this.form.identityType,
name: this.form.name, name: this.form.name,
gender: this.form.gender, gender: this.form.gender,
genderText: getGenderLabel(this.form.gender, this.form.gender === 1 ? this.t('genderMale') : this.form.gender === 0 ? this.t('genderFemale') : ''), genderText: getGenderLabel(this.form.gender, this.form.gender === 1 ? this.$tr('男') : this.form.gender === 0 ? this.$tr('女') : ''),
phone: this.form.phone, phone: this.form.phone,
country: this.form.country, country: this.form.country,
idCardType: Number(this.form.idCardType) || null, idCardType: Number(this.form.idCardType) || null,

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="registration-records-page tn-safe-area-inset-bottom"> <view class="registration-records-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('注册记录') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="page-main tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }"> <view class="page-main tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }">
@@ -20,8 +20,8 @@
</scroll-view> </scroll-view>
<view v-if="providerUnavailable" class="empty-panel tn-padding-lg"> <view v-if="providerUnavailable" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('providerUnavailableTitle') }}</view> <view class="empty-title">{{ $tr('当前无法获取微信身份') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('providerUnavailableDesc') }}</view> <view class="empty-desc tn-margin-top-sm">{{ $tr('请在微信小程序中打开后,再查看当前微信账号提交的注册记录。') }}</view>
</view> </view>
<view v-else-if="list.length"> <view v-else-if="list.length">
@@ -33,7 +33,7 @@
> >
<view class="card-head tn-flex tn-flex-row-between tn-flex-col-center"> <view class="card-head tn-flex tn-flex-row-between tn-flex-col-center">
<view class="card-title-wrap"> <view class="card-title-wrap">
<view class="card-title">{{ item.name || item.account || t('registrationFallbackTitle') }}</view> <view class="card-title">{{ item.name || item.account || $tr('人员注册申请') }}</view>
<view class="card-code tn-margin-top-xs">{{ item.code || '--' }}</view> <view class="card-code tn-margin-top-xs">{{ item.code || '--' }}</view>
</view> </view>
<view :class="['status-badge', 'status-' + resolveStatusType(item.approvalStatus)]"> <view :class="['status-badge', 'status-' + resolveStatusType(item.approvalStatus)]">
@@ -43,50 +43,50 @@
<view class="card-body tn-margin-top"> <view class="card-body tn-margin-top">
<view class="info-row tn-flex tn-flex-row-between"> <view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('accountLabel') }}</text> <text class="info-label">{{ $tr('注册账号') }}</text>
<text class="info-value">{{ item.account || '--' }}</text> <text class="info-value">{{ item.account || '--' }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('phoneLabel') }}</text> <text class="info-label">{{ $tr('手机号') }}</text>
<text class="info-value">{{ item.phone || '--' }}</text> <text class="info-value">{{ item.phone || '--' }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('idNumberLabel') }}</text> <text class="info-label">{{ $tr('证件号') }}</text>
<text class="info-value">{{ item.idCard || item.passportNumber || '--' }}</text> <text class="info-value">{{ item.idCard || item.passportNumber || '--' }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <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> <text class="info-value">{{ formatDateTime(item.createTime) }}</text>
</view> </view>
<view v-if="item.approveTime" class="info-row tn-flex tn-flex-row-between"> <view v-if="item.approveTime" class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('approvedAtLabel') }}</text> <text class="info-label">{{ $tr('审批时间') }}</text>
<text class="info-value">{{ formatDateTime(item.approveTime) }}</text> <text class="info-value">{{ formatDateTime(item.approveTime) }}</text>
</view> </view>
<view v-if="item.approveReason" class="remark-panel tn-margin-top"> <view v-if="item.approveReason" class="remark-panel tn-margin-top">
<view class="remark-label">{{ item.approvalStatus === -1 ? t('rejectReasonLabel') : t('approvalRemarkLabel') }}</view> <view class="remark-label">{{ item.approvalStatus === -1 ? $tr('驳回原因') : $tr('审批意见') }}</view>
<view class="remark-text tn-margin-top-xs">{{ item.approveReason }}</view> <view class="remark-text tn-margin-top-xs">{{ item.approveReason }}</view>
</view> </view>
</view> </view>
</view> </view>
<view class="load-state"> <view class="load-state">
<text v-if="loadingMore">{{ t('loadingMore') }}</text> <text v-if="loadingMore">{{ $tr('加载中...') }}</text>
<text v-else-if="!hasMore">{{ t('noMore') }}</text> <text v-else-if="!hasMore">{{ $tr('没有更多记录了') }}</text>
<text v-else>{{ t('pullUpMore') }}</text> <text v-else>{{ $tr('上拉加载更多') }}</text>
</view> </view>
</view> </view>
<view v-else-if="!loading" class="empty-panel tn-padding-lg"> <view v-else-if="!loading" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('emptyTitle') }}</view> <view class="empty-title">{{ $tr('暂无注册记录') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('emptyDesc') }}</view> <view class="empty-desc tn-margin-top-sm">{{ $tr('当前微信账号还没有提交过人员注册申请。') }}</view>
<view class="action-btn tn-margin-top-lg" @click="goToRegister"> <view class="action-btn tn-margin-top-lg" @click="goToRegister">
<text class="tn-text-bold">{{ t('goRegister') }}</text> <text class="tn-text-bold">{{ $tr('去提交注册申请') }}</text>
</view> </view>
</view> </view>
<view v-if="loading && !list.length && !providerUnavailable" class="empty-panel tn-padding-lg"> <view v-if="loading && !list.length && !providerUnavailable" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('loadingTitle') }}</view> <view class="empty-title">{{ $tr('注册记录加载中') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('loadingDesc') }}</view> <view class="empty-desc tn-margin-top-sm">{{ $tr('正在同步当前微信账号提交的注册申请记录,请稍候。') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -152,15 +152,12 @@ export default {
} }
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgAccount.registration_records', key, params)
},
syncStatusOptions() { syncStatusOptions() {
this.statusOptions = [ this.statusOptions = [
{ label: this.t('statusAll'), value: '' }, { label: this.$tr('全部'), value: '' },
{ label: this.t('statusPending'), value: 0 }, { label: this.$tr('审核中'), value: 0 },
{ label: this.t('statusApproved'), value: 1 }, { label: this.$tr('已通过'), value: 1 },
{ label: this.t('statusRejected'), value: -1 } { label: this.$tr('已驳回'), value: -1 }
] ]
}, },
syncNavigationTitle() { syncNavigationTitle() {
@@ -187,11 +184,11 @@ export default {
}, },
getStatusText(status) { getStatusText(status) {
const map = { const map = {
0: this.t('statusPending'), 0: this.$tr('审核中'),
1: this.t('statusApproved'), 1: this.$tr('已通过'),
'-1': this.t('statusRejected') '-1': this.$tr('已驳回')
} }
return map[status] || this.t('statusProcessing') return map[status] || this.$tr('处理中')
}, },
getDisplayStatusText(item = {}) { getDisplayStatusText(item = {}) {
if (this.language !== 'en' && item.approvalStatusDesc) { if (this.language !== 'en' && item.approvalStatusDesc) {
@@ -301,7 +298,7 @@ export default {
this.pageIndex = Math.max(1, this.pageIndex - 1) this.pageIndex = Math.max(1, this.pageIndex - 1)
} }
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('loadError'), title: (error && (error.message || error.msg)) || this.$tr('注册记录加载失败'),
icon: 'none' icon: 'none'
}) })
}) })

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="book-page tn-safe-area-inset-bottom"> <view class="book-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="#F8F9FA"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="#F8F9FA">
<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> </tn-nav-bar>
<view class="book-content tn-padding" :style="{paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '180rpx'}"> <view class="book-content tn-padding" :style="{paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '180rpx'}">
@@ -13,7 +13,7 @@
</view> </view>
<view class="preview-info tn-flex-1 tn-margin-left tn-flex tn-flex-direction-column tn-flex-row-between"> <view class="preview-info tn-flex-1 tn-margin-left tn-flex tn-flex-direction-column tn-flex-row-between">
<view> <view>
<view class="tn-text-sm tn-color-gray">{{ t('bookingFor') }}</view> <view class="tn-text-sm tn-color-gray">{{ $tr('你正在预约') }}</view>
<view class="tn-text-bold tn-text-lg tn-color-black tn-margin-top-xs clamp-text-2"> <view class="tn-text-bold tn-text-lg tn-color-black tn-margin-top-xs clamp-text-2">
{{ displayStyleName }} {{ displayStyleName }}
</view> </view>
@@ -24,7 +24,7 @@
<view class="tn-flex tn-flex-row-between tn-flex-col-bottom tn-margin-top-sm"> <view class="tn-flex tn-flex-row-between tn-flex-col-bottom tn-margin-top-sm">
<view class="room-tag tn-text-xs tn-text-bold">{{ displayLocationText }}</view> <view class="room-tag tn-text-xs tn-text-bold">{{ displayLocationText }}</view>
<view class="tn-color-black tn-text tn-text-bold"> <view class="tn-color-black tn-text tn-text-bold">
<text class="tn-text-sm tn-color-gray tn-margin-right-xs">{{ t('deposit') }}</text>$500.00 <text class="tn-text-sm tn-color-gray tn-margin-right-xs">{{ $tr('定金') }}</text>$500.00
</view> </view>
</view> </view>
</view> </view>
@@ -32,15 +32,15 @@
<!-- 填写日期表单 --> <!-- 填写日期表单 -->
<view class="form-section tn-margin-bottom-xl tn-padding-lg form-card"> <view class="form-section tn-margin-bottom-xl tn-padding-lg form-card">
<view class="section-title tn-text-bold tn-text-xl tn-margin-bottom-xs">{{ t('checkInDate') }}</view> <view class="section-title tn-text-bold tn-text-xl tn-margin-bottom-xs">{{ $tr('填写预订时间') }}</view>
<view class="tn-color-gray tn-text-sm tn-margin-bottom-lg">{{ t('checkInDesc') }}</view> <view class="tn-color-gray tn-text-sm tn-margin-bottom-lg">{{ $tr('支付后会先生成预定单并进入审核,不会直接占用房间资源。') }}</view>
<view class="form-item tn-margin-bottom-lg"> <view class="form-item tn-margin-bottom-lg">
<view class="form-label tn-text-bold">{{ t('startDate') }}</view> <view class="form-label tn-text-bold">{{ $tr('入住日期 (Check-in)') }}</view>
<picker mode="date" @change="onStartDateChange"> <picker mode="date" @change="onStartDateChange">
<view class="form-input tn-flex tn-flex-row-between tn-flex-col-center"> <view class="form-input tn-flex tn-flex-row-between tn-flex-col-center">
<text :class="form.startDate ? 'tn-color-black tn-text-bold' : 'tn-color-gray'"> <text :class="form.startDate ? 'tn-color-black tn-text-bold' : 'tn-color-gray'">
{{ form.startDate ? form.startDate : t('selectDate') }} {{ form.startDate ? form.startDate : $tr('请选择日期') }}
</text> </text>
<text class="tn-icon-calendar tn-color-black tn-text-xl"></text> <text class="tn-icon-calendar tn-color-black tn-text-xl"></text>
</view> </view>
@@ -48,11 +48,11 @@
</view> </view>
<view class="form-item tn-margin-bottom-sm"> <view class="form-item tn-margin-bottom-sm">
<view class="form-label tn-text-bold">{{ t('endDate') }}</view> <view class="form-label tn-text-bold">{{ $tr('离开日期 (Check-out)') }}</view>
<picker mode="date" @change="onEndDateChange" :start="form.startDate"> <picker mode="date" @change="onEndDateChange" :start="form.startDate">
<view class="form-input tn-flex tn-flex-row-between tn-flex-col-center"> <view class="form-input tn-flex tn-flex-row-between tn-flex-col-center">
<text :class="form.endDate ? 'tn-color-black tn-text-bold' : 'tn-color-gray'"> <text :class="form.endDate ? 'tn-color-black tn-text-bold' : 'tn-color-gray'">
{{ form.endDate ? form.endDate : t('selectDate') }} {{ form.endDate ? form.endDate : $tr('请选择日期') }}
</text> </text>
<text class="tn-icon-calendar tn-color-black tn-text-xl"></text> <text class="tn-icon-calendar tn-color-black tn-text-xl"></text>
</view> </view>
@@ -61,12 +61,12 @@
<view v-if="stayNights > 0" class="tn-margin-top-lg booking-summary"> <view v-if="stayNights > 0" class="tn-margin-top-lg booking-summary">
<view class="summary-row tn-flex tn-flex-row-between"> <view class="summary-row tn-flex tn-flex-row-between">
<text class="tn-color-gray">{{ t('stayNights') }}</text> <text class="tn-color-gray">{{ $tr('入住晚数') }}</text>
<text class="tn-text-bold tn-color-black">{{ stayNights }} {{ t('nightUnit') }}</text> <text class="tn-text-bold tn-color-black">{{ stayNights }} {{ $tr('晚') }}</text>
</view> </view>
<view class="summary-row tn-flex tn-flex-row-between tn-margin-top-sm"> <view class="summary-row tn-flex tn-flex-row-between tn-margin-top-sm">
<text class="tn-color-gray">{{ t('bookingStatus') }}</text> <text class="tn-color-gray">{{ $tr('当前状态') }}</text>
<text class="tn-text-bold tn-color-black">{{ t('pendingPayment') }}</text> <text class="tn-text-bold tn-color-black">{{ $tr('待支付定金') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -75,11 +75,11 @@
<!-- 底部固定按钮区 --> <!-- 底部固定按钮区 -->
<view class="fixed-bottom-btn-area tn-flex tn-flex-row-between tn-flex-col-center"> <view class="fixed-bottom-btn-area tn-flex tn-flex-row-between tn-flex-col-center">
<view class="tn-flex tn-flex-direction-column"> <view class="tn-flex tn-flex-direction-column">
<view class="tn-text-sm tn-color-gray">{{ t('deposit') }}</view> <view class="tn-text-sm tn-color-gray">{{ $tr('定金') }}</view>
<view class="tn-text-bold tn-text-xl" style="color: $brand-primary;">$500.00</view> <view class="tn-text-bold tn-text-xl" style="color: $brand-primary;">$500.00</view>
</view> </view>
<view class="custom-btn btn-dark" style="width: 60%; height: 100rpx;" @click="submitBooking" :class="{ 'btn-disabled': isSubmitting }"> <view class="custom-btn btn-dark" style="width: 60%; height: 100rpx;" @click="submitBooking" :class="{ 'btn-disabled': isSubmitting }">
<text class="tn-text-bold tn-text-lg">{{ isSubmitting ? (language === 'zh' ? '处理中...' : 'Processing...') : t('submitOrder') }}</text> <text class="tn-text-bold tn-text-lg">{{ isSubmitting ? (language === 'zh' ? '处理中...' : 'Processing...') : $tr('进入支付页') }}</text>
</view> </view>
</view> </view>
@@ -100,40 +100,6 @@ export default {
form: { form: {
startDate: '', startDate: '',
endDate: '' endDate: ''
},
i18n: {
zh: {
title: '预订确认',
bookingFor: '你正在预约',
deposit: '定金',
checkInDate: '填写预订时间',
checkInDesc: '支付后会先生成预定单并进入审核,不会直接占用房间资源。',
startDate: '入住日期 (Check-in)',
endDate: '离开日期 (Check-out)',
selectDate: '请选择日期',
submitOrder: '进入支付页',
stayNights: '入住晚数',
nightUnit: '晚',
bookingStatus: '当前状态',
pendingPayment: '待支付定金',
roomTypeLabel: '房型:'
},
en: {
title: 'Booking Confirm',
bookingFor: 'Booking For',
deposit: 'Deposit',
checkInDate: 'Booking Period',
checkInDesc: 'Payment will create a booking order first, without occupying room inventory.',
startDate: 'Check-in Date',
endDate: 'Check-out Date',
selectDate: 'Select Date',
submitOrder: 'Go To Payment',
stayNights: 'Stay Nights',
nightUnit: 'nights',
bookingStatus: 'Status',
pendingPayment: 'Deposit Pending',
roomTypeLabel: 'Room Type: '
}
} }
} }
}, },
@@ -159,7 +125,7 @@ export default {
const areaText = this.language === 'en' const areaText = this.language === 'en'
? (this.applyData.roomAreaEnDisplay || compactEnglishText(this.applyData.roomAreaEn || this.applyData.roomArea || '', 28)) ? (this.applyData.roomAreaEnDisplay || compactEnglishText(this.applyData.roomAreaEn || this.applyData.roomArea || '', 28))
: (this.applyData.roomArea || this.applyData.roomAreaEn || '') : (this.applyData.roomArea || this.applyData.roomAreaEn || '')
return [roomTypeText ? `${this.t('roomTypeLabel')}${roomTypeText}` : '', areaText].filter(Boolean).join(' | ') return [roomTypeText ? `${this.$tr('房型:')}${roomTypeText}` : '', areaText].filter(Boolean).join(' | ')
}, },
displayLocationText() { displayLocationText() {
if (!this.applyData) { if (!this.applyData) {
@@ -200,9 +166,6 @@ export default {
this.redirectIfBookingUnavailable() this.redirectIfBookingUnavailable()
}, },
methods: { methods: {
t(key) {
return this.$tm('pkgBooking.book.book', key)
},
redirectIfBookingUnavailable() { redirectIfBookingUnavailable() {
const session = getSessionSnapshot() const session = getSessionSnapshot()
if (!session.isLogin) { if (!session.isLogin) {

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="book-viewing-page tn-safe-area-inset-bottom"> <view class="book-viewing-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :backTitle="t('backTitle')" backgroundColor="#F0F6FF" :bottomShadow="false" :fixed="true"> <tn-nav-bar :isBack="true" :backTitle="$tr('返回')" backgroundColor="#F0F6FF" :bottomShadow="false" :fixed="true">
<view class="nav-title">{{ t('navTitle') }}</view> <view class="nav-title">{{ $tr('预约现场带看') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="content-wrapper" :style="{ paddingTop: vuex_custom_bar_height + 'px' }"> <view class="content-wrapper" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
@@ -21,8 +21,8 @@
<view class="form-card"> <view class="form-card">
<view class="card-header tn-flex tn-flex-row-between tn-flex-col-bottom"> <view class="card-header tn-flex tn-flex-row-between tn-flex-col-bottom">
<view class="header-left tn-flex tn-flex-col-bottom"> <view class="header-left tn-flex tn-flex-col-bottom">
<text class="card-title">{{ t('selectViewingTime') }}</text> <text class="card-title">{{ $tr('选择看房时间') }}</text>
<text class="card-subtitle">{{ t('selectViewingTimeDesc') }}</text> <text class="card-subtitle">{{ $tr('请选择您方便的时间段') }}</text>
</view> </view>
</view> </view>
@@ -60,13 +60,13 @@
<view class="form-card"> <view class="form-card">
<view class="card-header"> <view class="card-header">
<view class="header-left tn-flex tn-flex-col-bottom"> <view class="header-left tn-flex tn-flex-col-bottom">
<text class="card-title">{{ t('rentalNeeds') }}</text> <text class="card-title">{{ $tr('您的租住需求') }}</text>
<text class="card-subtitle">{{ t('rentalNeedsDesc') }}</text> <text class="card-subtitle">{{ $tr('帮助管家更好地为您推荐房源') }}</text>
</view> </view>
</view> </view>
<view class="field-item"> <view class="field-item">
<view class="field-label">{{ t('roomTypeLabel') }}</view> <view class="field-label">{{ $tr('意向房型') }}</view>
<room-type-selector <room-type-selector
v-model="formData.intendedRoomTypeCode" v-model="formData.intendedRoomTypeCode"
:initial-area-code="formData.intendedAreaCode" :initial-area-code="formData.intendedAreaCode"
@@ -75,7 +75,7 @@
</view> </view>
<view class="field-item"> <view class="field-item">
<view class="field-label">{{ t('durationLabel') }}</view> <view class="field-label">{{ $tr('预计租住时长') }}</view>
<view class="tag-grid"> <view class="tag-grid">
<view <view
v-for="(item, index) in durationOptions" v-for="(item, index) in durationOptions"
@@ -90,28 +90,28 @@
</view> </view>
<view class="field-item"> <view class="field-item">
<view class="field-label">{{ t('budgetLabel') }}</view> <view class="field-label">{{ $tr('租金预算 (元/月)') }}</view>
<view class="input-box tn-flex tn-flex-col-center"> <view class="input-box tn-flex tn-flex-col-center">
<text class="tn-icon-money input-icon"></text> <text class="tn-icon-money input-icon"></text>
<input <input
class="custom-input" class="custom-input"
v-model="formData.budget" v-model="formData.budget"
type="digit" type="digit"
:placeholder="t('budgetPlaceholder')" :placeholder="$tr('请输入您的心理价位')"
placeholder-style="color: #A1B0C4;" placeholder-style="color: #A1B0C4;"
/> />
</view> </view>
</view> </view>
<view class="field-item"> <view class="field-item">
<view class="field-label">{{ t('workplaceLabel') }}</view> <view class="field-label">{{ $tr('工作/通勤地点') }}</view>
<view class="input-box tn-flex tn-flex-col-center"> <view class="input-box tn-flex tn-flex-col-center">
<text class="tn-icon-location input-icon"></text> <text class="tn-icon-location input-icon"></text>
<input <input
class="custom-input" class="custom-input"
v-model="formData.workplace" v-model="formData.workplace"
type="text" type="text"
:placeholder="t('workplacePlaceholder')" :placeholder="$tr('请输入主要通勤地点')"
placeholder-style="color: #A1B0C4;" placeholder-style="color: #A1B0C4;"
/> />
</view> </view>
@@ -123,7 +123,7 @@
<!-- 底部操作区 --> <!-- 底部操作区 -->
<view class="footer-bar tn-safe-area-inset-bottom"> <view class="footer-bar tn-safe-area-inset-bottom">
<view class="submit-btn" @click="handleSubmit"> <view class="submit-btn" @click="handleSubmit">
<text>{{ t('submitBooking') }}</text> <text>{{ $tr('提交预约') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -204,7 +204,7 @@ export default {
}, },
displayInfoDesc() { displayInfoDesc() {
if (!this.sourceData) { if (!this.sourceData) {
return this.t('defaultInfoDesc') return this.$tr('专属管家为您提供一对一带看服务')
} }
const bannerCopy = this.language === 'en' const bannerCopy = this.language === 'en'
? compactEnglishText(this.sourceData.bannerSubtitleEn || this.sourceData.bannerDescEn || this.sourceData.bannerSubtitle || this.sourceData.bannerDesc || '', 48) ? compactEnglishText(this.sourceData.bannerSubtitleEn || this.sourceData.bannerDescEn || this.sourceData.bannerSubtitle || this.sourceData.bannerDesc || '', 48)
@@ -213,7 +213,7 @@ export default {
? (this.sourceData.areaLabelEnDisplay || this.sourceData.areaNameEnDisplay || compactEnglishText(this.sourceData.areaLabelEn || this.sourceData.areaLabel || this.sourceData.areaNameEn || this.sourceData.areaName || '', 26)) ? (this.sourceData.areaLabelEnDisplay || this.sourceData.areaNameEnDisplay || compactEnglishText(this.sourceData.areaLabelEn || this.sourceData.areaLabel || this.sourceData.areaNameEn || this.sourceData.areaName || '', 26))
: (this.sourceData.areaLabel || this.sourceData.areaName || this.sourceData.areaLabelEn || this.sourceData.areaNameEn || '') : (this.sourceData.areaLabel || this.sourceData.areaName || this.sourceData.areaLabelEn || this.sourceData.areaNameEn || '')
const meta = [bannerCopy, areaLabel, localizePayMethodText(this.sourceData.payMethod, this.language)].filter(Boolean).join(' · ') const meta = [bannerCopy, areaLabel, localizePayMethodText(this.sourceData.payMethod, this.language)].filter(Boolean).join(' · ')
return meta || this.t('defaultInfoDesc') return meta || this.$tr('专属管家为您提供一对一带看服务')
} }
}, },
data() { data() {
@@ -236,101 +236,7 @@ export default {
workplace: '' workplace: ''
}, },
sourceData: null, sourceData: null,
isSubmitting: false, isSubmitting: false
i18n: {
zh: {
backTitle: '返回',
navTitle: '预约现场带看',
selectViewingTime: '选择看房时间',
selectViewingTimeDesc: '请选择您方便的时间段',
rentalNeeds: '您的租住需求',
rentalNeedsDesc: '帮助管家更好地为您推荐房源',
roomTypeLabel: '意向房型',
durationLabel: '预计租住时长',
budgetLabel: '租金预算 (元/月)',
budgetPlaceholder: '请输入您的心理价位',
workplaceLabel: '工作/通勤地点',
workplacePlaceholder: '请输入主要通勤地点',
submitBooking: '提交预约',
defaultInfoDesc: '专属管家为您提供一对一带看服务',
duration1: '1-3个月',
duration2: '4-6个月',
duration3: '7-9个月',
duration4: '10-12个月',
duration5: '1年以上',
week0: '周日',
week1: '周一',
week2: '周二',
week3: '周三',
week4: '周四',
week5: '周五',
week6: '周六',
today: '今天',
tomorrow: '明天',
remarkTitle: '预约看房申请',
remarkTime: '看房时间:{date} {time}',
remarkDuration: '预计租住时长:{duration}',
remarkBudget: '租金预算:{budget} 元/月',
remarkWorkplace: '工作/通勤地点:{workplace}',
remarkArea: '意向区域:{area}',
remarkRoomType: '意向房型:{roomType}',
remarkSource: '当前来源:{room}',
remarkPayMethod: '支付方式:{payMethod}',
remarkTail: '备注:请宿管确认带看安排;若带看完成并锁定房号,可转为预定单通知租客支付。',
selectTimeRequired: '请选择看房时间段',
selectDurationRequired: '请选择预计租住时长',
submitting: '提交中...',
submitSuccessTitle: '预约成功',
submitSuccessContent: '您的预约看房申请已提交,请等待管家联系',
submitFailed: '预约提交失败'
},
en: {
backTitle: 'Back',
navTitle: 'On-site Viewing',
selectViewingTime: 'Select Viewing Time',
selectViewingTimeDesc: 'Choose a time slot that works for you',
rentalNeeds: 'Rental Needs',
rentalNeedsDesc: 'Help the manager recommend a suitable room',
roomTypeLabel: 'Preferred Room Type',
durationLabel: 'Expected Stay Duration',
budgetLabel: 'Budget (CNY/month)',
budgetPlaceholder: 'Enter your expected budget',
workplaceLabel: 'Work / Commute Location',
workplacePlaceholder: 'Enter your main commute location',
submitBooking: 'Submit Request',
defaultInfoDesc: 'A dedicated manager will arrange a one-on-one viewing for you',
duration1: '1-3 months',
duration2: '4-6 months',
duration3: '7-9 months',
duration4: '10-12 months',
duration5: 'Over 1 year',
week0: 'Sun',
week1: 'Mon',
week2: 'Tue',
week3: 'Wed',
week4: 'Thu',
week5: 'Fri',
week6: 'Sat',
today: 'Today',
tomorrow: 'Tomorrow',
remarkTitle: 'Viewing Request',
remarkTime: 'Viewing Time: {date} {time}',
remarkDuration: 'Expected Stay: {duration}',
remarkBudget: 'Budget: {budget} CNY/month',
remarkWorkplace: 'Work / Commute Location: {workplace}',
remarkArea: 'Preferred Area: {area}',
remarkRoomType: 'Preferred Room Type: {roomType}',
remarkSource: 'Current Source: {room}',
remarkPayMethod: 'Payment Method: {payMethod}',
remarkTail: 'Note: Please confirm the viewing schedule with the manager. After viewing and locking a room number, the order can be converted into a booking for payment.',
selectTimeRequired: 'Please select a viewing time slot',
selectDurationRequired: 'Please select the expected stay duration',
submitting: 'Submitting...',
submitSuccessTitle: 'Booking Submitted',
submitSuccessContent: 'Your viewing request has been submitted.\nThe system will forward it to the authorized manager of the selected area. After the manager confirms the viewing, the order can be completed directly or converted into a booking for payment once a room is selected.',
submitFailed: 'Failed to submit the booking'
}
}
} }
}, },
onLoad(options) { onLoad(options) {
@@ -348,9 +254,6 @@ export default {
this.redirectIfBookingUnavailable() this.redirectIfBookingUnavailable()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgBooking.book_viewing.book_viewing', key, params)
},
redirectIfBookingUnavailable() { redirectIfBookingUnavailable() {
const session = getSessionSnapshot() const session = getSessionSnapshot()
if (!session.isLogin) { if (!session.isLogin) {
@@ -382,24 +285,24 @@ export default {
}, },
syncDurationOptions() { syncDurationOptions() {
this.durationOptions = [ this.durationOptions = [
this.t('duration1'), this.$tr('1-3个月'),
this.t('duration2'), this.$tr('4-6个月'),
this.t('duration3'), this.$tr('7-9个月'),
this.t('duration4'), this.$tr('10-12个月'),
this.t('duration5') this.$tr('1年以上')
] ]
}, },
generateDates() { generateDates() {
const dates = [] const dates = []
const today = new Date() const today = new Date()
const weeks = [ const weeks = [
this.t('week0'), this.$tr('周日'),
this.t('week1'), this.$tr('周一'),
this.t('week2'), this.$tr('周二'),
this.t('week3'), this.$tr('周三'),
this.t('week4'), this.$tr('周四'),
this.t('week5'), this.$tr('周五'),
this.t('week6') this.$tr('周六')
] ]
for (let i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
@@ -409,8 +312,8 @@ export default {
const day = String(d.getDate()).padStart(2, '0') const day = String(d.getDate()).padStart(2, '0')
let weekStr = weeks[d.getDay()] let weekStr = weeks[d.getDay()]
if (i === 0) weekStr = this.t('today'); if (i === 0) weekStr = this.$tr('今天');
else if (i === 1) weekStr = this.t('tomorrow'); else if (i === 1) weekStr = this.$tr('明天');
dates.push({ dates.push({
date: `${d.getFullYear()}-${month}-${day}`, date: `${d.getFullYear()}-${month}-${day}`,
@@ -452,11 +355,11 @@ export default {
return return
} }
if (this.currentTimeIndex === -1) { if (this.currentTimeIndex === -1) {
uni.showToast({ title: this.t('selectTimeRequired'), icon: 'none' }) uni.showToast({ title: this.$tr('请选择看房时间段'), icon: 'none' })
return return
} }
if (!this.formData.duration) { if (!this.formData.duration) {
uni.showToast({ title: this.t('selectDurationRequired'), icon: 'none' }) uni.showToast({ title: this.$tr('请选择预计租住时长'), icon: 'none' })
return return
} }
@@ -478,13 +381,13 @@ export default {
} }
this.isSubmitting = true this.isSubmitting = true
uni.showLoading({ title: this.t('submitting'), mask: true }) uni.showLoading({ title: this.$tr('提交中...'), mask: true })
return submitViewingApplicationForm(payload) return submitViewingApplicationForm(payload)
.then(() => { .then(() => {
uni.hideLoading() uni.hideLoading()
uni.showModal({ uni.showModal({
title: this.t('submitSuccessTitle'), title: this.$tr('预约成功'),
content: this.t('submitSuccessContent'), content: this.$tr('您的预约看房申请已提交,请等待管家联系'),
showCancel: false, showCancel: false,
confirmColor: '#01BEFF', confirmColor: '#01BEFF',
success: () => { success: () => {
@@ -498,7 +401,7 @@ export default {
console.error('submit viewing application failed', error) console.error('submit viewing application failed', error)
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('submitFailed'), title: (error && (error.message || error.msg)) || this.$tr('预约提交失败'),
icon: 'none' icon: 'none'
}) })
}) })

View File

@@ -3,7 +3,7 @@
<view class="selector-header tn-flex tn-flex-row-between tn-flex-col-center" @click="openSelector"> <view class="selector-header tn-flex tn-flex-row-between tn-flex-col-center" @click="openSelector">
<view class="selected-info"> <view class="selected-info">
<view class="selected-name tn-text-ellipsis" :class="{ 'is-empty': !selectedRoomType }"> <view class="selected-name tn-text-ellipsis" :class="{ 'is-empty': !selectedRoomType }">
{{ selectedRoomType ? getRoomTypeDisplayName(selectedRoomType) : t('selectRoomType') }} {{ selectedRoomType ? getRoomTypeDisplayName(selectedRoomType) : $tr('请选择房型') }}
</view> </view>
<view class="selected-area tn-text-ellipsis" v-if="selectedArea"> <view class="selected-area tn-text-ellipsis" v-if="selectedArea">
{{ getAreaDisplayName(selectedArea) }} {{ getAreaDisplayName(selectedArea) }}
@@ -15,17 +15,17 @@
<tn-popup v-model="showPopup" mode="bottom" length="80%" :borderRadius="32" :closeBtn="true"> <tn-popup v-model="showPopup" mode="bottom" length="80%" :borderRadius="32" :closeBtn="true">
<view class="popup-content"> <view class="popup-content">
<view class="popup-header"> <view class="popup-header">
<text class="popup-title">{{ t('popupTitle') }}</text> <text class="popup-title">{{ $tr('选择意向房型') }}</text>
</view> </view>
<scroll-view scroll-y class="area-list-scroll"> <scroll-view scroll-y class="area-list-scroll">
<view v-if="isLoading" class="loading-state tn-padding-xl tn-text-center"> <view v-if="isLoading" class="loading-state tn-padding-xl tn-text-center">
<tn-loading mode="circle"></tn-loading> <tn-loading mode="circle"></tn-loading>
<view class="tn-margin-top-sm tn-color-gray">{{ t('loadingRoomTypes') }}</view> <view class="tn-margin-top-sm tn-color-gray">{{ $tr('房型加载中...') }}</view>
</view> </view>
<view v-else-if="!areaList.length" class="empty-state tn-padding-xl tn-text-center"> <view v-else-if="!areaList.length" class="empty-state tn-padding-xl tn-text-center">
<text class="tn-color-gray">{{ t('emptyRoomTypes') }}</text> <text class="tn-color-gray">{{ $tr('暂无可选房型') }}</text>
</view> </view>
<view v-else class="area-group" v-for="(area, index) in areaList" :key="area.code"> <view v-else class="area-group" v-for="(area, index) in areaList" :key="area.code">
@@ -45,7 +45,7 @@
@click="handleSelect(area, room)" @click="handleSelect(area, room)"
> >
<view class="room-name tn-text-ellipsis">{{ getRoomTypeDisplayName(room) }}</view> <view class="room-name tn-text-ellipsis">{{ getRoomTypeDisplayName(room) }}</view>
<view class="room-price" v-if="room.price">¥{{ getPriceValue(room.price) }}{{ t('priceUnit') }}</view> <view class="room-price" v-if="room.price">¥{{ getPriceValue(room.price) }}{{ $tr('/月') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -117,10 +117,6 @@ export default {
this.loadData() this.loadData()
}, },
methods: { methods: {
t(key) {
const lang = I18N[this.language] ? this.language : 'zh'
return I18N[lang][key] || I18N.zh[key] || key
},
openSelector() { openSelector() {
this.showPopup = true this.showPopup = true
this.loadData() this.loadData()
@@ -149,7 +145,7 @@ export default {
}) })
.catch((error) => { .catch((error) => {
console.error('load room types error', error) console.error('load room types error', error)
uni.showToast({ title: this.t('loadFailed'), icon: 'none' }) uni.showToast({ title: this.$tr('房型加载失败'), icon: 'none' })
}) })
.finally(() => { .finally(() => {
this.isLoading = false this.isLoading = false

View File

@@ -1,18 +1,18 @@
<template> <template>
<view class="booking-detail-page tn-safe-area-inset-bottom"> <view class="booking-detail-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="#FFFFFF"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="#FFFFFF">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('预订详情') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="page-content tn-padding" :style="{paddingTop: vuex_custom_bar_height + 'px'}"> <view class="page-content tn-padding" :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view v-if="loading" class="empty-card tn-padding-lg"> <view v-if="loading" class="empty-card tn-padding-lg">
<view class="booking-title">{{ t('loadingTitle') }}</view> <view class="booking-title">{{ $tr('订单加载中') }}</view>
<view class="demo-desc tn-margin-top-sm">{{ t('loadingDesc') }}</view> <view class="demo-desc tn-margin-top-sm">{{ $tr('正在同步该预订的真实详情,请稍候。') }}</view>
</view> </view>
<view v-else-if="booking" class="booking-card tn-padding-lg"> <view v-else-if="booking" class="booking-card tn-padding-lg">
<view class="tn-flex tn-flex-row-between tn-flex-col-center"> <view class="tn-flex tn-flex-row-between tn-flex-col-center">
<view> <view>
<view class="booking-title">{{ t('orderNoLabel') }}</view> <view class="booking-title">{{ $tr('预定单号') }}</view>
<view class="booking-order tn-margin-top-xs">{{ booking.orderNo }}</view> <view class="booking-order tn-margin-top-xs">{{ booking.orderNo }}</view>
</view> </view>
<view class="status-badge" :class="'status-' + booking.tagType">{{ booking.statusText }}</view> <view class="status-badge" :class="'status-' + booking.tagType">{{ booking.statusText }}</view>
@@ -20,48 +20,48 @@
<view class="info-box tn-margin-top"> <view class="info-box tn-margin-top">
<view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm"> <view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm">
<text class="info-label">{{ t('roomTypeLabel') }}</text> <text class="info-label">{{ $tr('风格房型') }}</text>
<text class="info-value">{{ booking.roomTypeText }}</text> <text class="info-value">{{ booking.roomTypeText }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm"> <view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm">
<text class="info-label">{{ t('buildingFloorLabel') }}</text> <text class="info-label">{{ $tr('楼栋楼层') }}</text>
<text class="info-value">{{ booking.buildingFloorText }}</text> <text class="info-value">{{ booking.buildingFloorText }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm"> <view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm">
<text class="info-label">{{ t('roomNoLabel') }}</text> <text class="info-label">{{ $tr('房间号') }}</text>
<text class="info-value">{{ booking.roomNoText }}</text> <text class="info-value">{{ booking.roomNoText }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm"> <view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm">
<text class="info-label">{{ t('areaLabel') }}</text> <text class="info-label">{{ $tr('所属区域') }}</text>
<text class="info-value">{{ booking.areaText }}</text> <text class="info-value">{{ booking.areaText }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm"> <view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm">
<text class="info-label">{{ t('stayDateLabel') }}</text> <text class="info-label">{{ $tr('入住时间') }}</text>
<text class="info-value">{{ booking.startDateText }}{{ t('dateRangeJoiner') }}{{ booking.endDateText }}</text> <text class="info-value">{{ booking.startDateText }}{{ $tr(' 至 ') }}{{ booking.endDateText }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <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">{{ booking.createdAtText }}</text> <text class="info-value">{{ booking.createdAtText }}</text>
</view> </view>
</view> </view>
<view v-if="booking.rejectReasonText" class="reason-box tn-margin-top"> <view v-if="booking.rejectReasonText" class="reason-box tn-margin-top">
<view class="reason-title">{{ t('rejectReasonTitle') }}</view> <view class="reason-title">{{ $tr('驳回原因') }}</view>
<view class="reason-content tn-margin-top-xs">{{ booking.rejectReasonText }}</view> <view class="reason-content tn-margin-top-xs">{{ booking.rejectReasonText }}</view>
</view> </view>
<view class="flow-box tn-margin-top"> <view class="flow-box tn-margin-top">
<view class="flow-title">{{ t('flowTitle') }}</view> <view class="flow-title">{{ $tr('当前流程') }}</view>
<view class="flow-step tn-margin-top-sm" :class="isStepActive('pending_review') ? 'active' : ''">{{ t('flowStep1') }}</view> <view class="flow-step tn-margin-top-sm" :class="isStepActive('pending_review') ? 'active' : ''">{{ $tr('1. 已支付,等待角色审批') }}</view>
<view class="flow-step tn-margin-top-sm" :class="isStepActive('approved_wait_assign') ? 'active' : ''">{{ t('flowStep2') }}</view> <view class="flow-step tn-margin-top-sm" :class="isStepActive('approved_wait_assign') ? 'active' : ''">{{ $tr('2. 审批通过,等待分配管家') }}</view>
<view class="flow-step tn-margin-top-sm" :class="isStepActive('assigned_wait_room') ? 'active' : ''">{{ t('flowStep3') }}</view> <view class="flow-step tn-margin-top-sm" :class="isStepActive('assigned_wait_room') ? 'active' : ''">{{ $tr('3. 已分配管家,等待分配房号') }}</view>
</view> </view>
</view> </view>
<view v-else class="empty-card tn-padding-lg"> <view v-else class="empty-card tn-padding-lg">
<view class="booking-title">{{ t('emptyTitle') }}</view> <view class="booking-title">{{ $tr('暂无预订记录') }}</view>
<view class="demo-desc tn-margin-top-sm">{{ t('emptyDesc') }}</view> <view class="demo-desc tn-margin-top-sm">{{ $tr('当前暂无可查看的预订详情,请先前往预订记录确认订单。') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -78,52 +78,6 @@ import {
} from '../../utils/booking-helper' } from '../../utils/booking-helper'
export default { export default {
i18n: {
zh: {
navTitle: '预订详情',
loadingTitle: '订单加载中',
loadingDesc: '正在同步该预订的真实详情,请稍候。',
orderNoLabel: '预定单号',
roomTypeLabel: '风格房型',
buildingFloorLabel: '楼栋楼层',
roomNoLabel: '房间号',
areaLabel: '所属区域',
stayDateLabel: '入住时间',
submittedAtLabel: '提交时间',
rejectReasonTitle: '驳回原因',
flowTitle: '当前流程',
flowStep1: '1. 已支付,等待角色审批',
flowStep2: '2. 审批通过,等待分配管家',
flowStep3: '3. 已分配管家,等待分配房号',
emptyTitle: '暂无预订记录',
emptyDesc: '当前暂无可查看的预订详情,请先前往预订记录确认订单。',
cachedDetailShown: '网络异常,已展示最近同步详情',
loadFailed: '预订详情加载失败',
dateRangeJoiner: ' 至 '
},
en: {
navTitle: 'Booking Detail',
loadingTitle: 'Loading booking',
loadingDesc: 'Syncing the latest booking details. Please wait.',
orderNoLabel: 'Booking No.',
roomTypeLabel: 'Room Type',
buildingFloorLabel: 'Building / Floor',
roomNoLabel: 'Room No.',
areaLabel: 'Area',
stayDateLabel: 'Stay Dates',
submittedAtLabel: 'Submitted At',
rejectReasonTitle: 'Rejection Reason',
flowTitle: 'Current Flow',
flowStep1: '1. Paid and waiting for approval',
flowStep2: '2. Approved and waiting for butler assignment',
flowStep3: '3. Butler assigned and waiting for room allocation',
emptyTitle: 'No booking record',
emptyDesc: 'No booking detail is available right now. Please check your booking records first.',
cachedDetailShown: 'Network issue detected. Showing the latest synced detail.',
loadFailed: 'Failed to load booking detail',
dateRangeJoiner: ' to '
}
},
data() { data() {
return { return {
orderNo: '', orderNo: '',
@@ -148,9 +102,6 @@ export default {
await this.refreshBooking() await this.refreshBooking()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgBooking.booking_detail.booking_detail', key, params)
},
getStoreUser() { getStoreUser() {
return (this.$store && this.$store.state && this.$store.state.vuex_user) || {} return (this.$store && this.$store.state && this.$store.state.vuex_user) || {}
}, },
@@ -205,8 +156,8 @@ export default {
this.booking = this.resolveBookingFromList(normalizeBookingList(cachedList, this.language)) this.booking = this.resolveBookingFromList(normalizeBookingList(cachedList, this.language))
uni.showToast({ uni.showToast({
title: this.booking title: this.booking
? this.t('cachedDetailShown') ? this.$tr('网络异常,已展示最近同步详情')
: ((error && (error.message || error.msg)) || this.t('loadFailed')), : ((error && (error.message || error.msg)) || this.$tr('预订详情加载失败')),
icon: 'none' icon: 'none'
}) })
}) })

View File

@@ -1,13 +1,13 @@
<template> <template>
<view class="booking-list-page tn-safe-area-inset-bottom"> <view class="booking-list-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('预订记录') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="page-content tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px' }"> <view class="page-content tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
<view v-if="loading" class="empty-card tn-padding-lg"> <view v-if="loading" class="empty-card tn-padding-lg">
<view class="empty-title">{{ t('loadingTitle') }}</view> <view class="empty-title">{{ $tr('订单加载中') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('loadingDesc') }}</view> <view class="empty-desc tn-margin-top-sm">{{ $tr('正在同步您的真实预订记录,请稍候。') }}</view>
</view> </view>
<view v-else-if="bookingList.length"> <view v-else-if="bookingList.length">
<view <view
@@ -18,7 +18,7 @@
> >
<view class="tn-flex tn-flex-row-between tn-flex-col-center"> <view class="tn-flex tn-flex-row-between tn-flex-col-center">
<view> <view>
<view class="booking-title">{{ item.roomTypeText || t('bookingFallbackTitle') }}</view> <view class="booking-title">{{ item.roomTypeText || $tr('房源预订') }}</view>
<view class="booking-subtitle tn-margin-top-xs">{{ item.orderNo }}</view> <view class="booking-subtitle tn-margin-top-xs">{{ item.orderNo }}</view>
</view> </view>
<view class="status-badge" :class="'status-' + item.tagType"> <view class="status-badge" :class="'status-' + item.tagType">
@@ -28,15 +28,15 @@
<view class="info-grid tn-margin-top"> <view class="info-grid tn-margin-top">
<view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm"> <view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm">
<text class="info-label">{{ t('buildingFloorLabel') }}</text> <text class="info-label">{{ $tr('楼栋楼层') }}</text>
<text class="info-value">{{ item.buildingFloorText }}</text> <text class="info-value">{{ item.buildingFloorText }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm"> <view class="info-row tn-flex tn-flex-row-between tn-margin-bottom-sm">
<text class="info-label">{{ t('stayDateLabel') }}</text> <text class="info-label">{{ $tr('入住时间') }}</text>
<text class="info-value">{{ item.startDateText }}{{ t('dateRangeJoiner') }}{{ item.endDateText }}</text> <text class="info-value">{{ item.startDateText }}{{ $tr(' 至 ') }}{{ item.endDateText }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <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">{{ item.createdAtText }}</text> <text class="info-value">{{ item.createdAtText }}</text>
</view> </view>
</view> </view>
@@ -44,10 +44,10 @@
</view> </view>
<view v-else class="empty-card tn-padding-lg"> <view v-else class="empty-card tn-padding-lg">
<view class="empty-title">{{ t('emptyTitle') }}</view> <view class="empty-title">{{ $tr('暂无预订记录') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('emptyDesc') }}</view> <view class="empty-desc tn-margin-top-sm">{{ $tr('当前暂无预订记录,完成预订后会在这里展示。') }}</view>
<view class="custom-btn btn-primary tn-margin-top-lg" @click="goSelectRoom"> <view class="custom-btn btn-primary tn-margin-top-lg" @click="goSelectRoom">
<text class="tn-text-bold">{{ t('goSelectRoom') }}</text> <text class="tn-text-bold">{{ $tr('去选房') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -60,38 +60,6 @@ import { getSessionSnapshot } from '../../utils/app-state'
import { normalizeBookingList, parseOrderListResponse } from '../../utils/booking-helper' import { normalizeBookingList, parseOrderListResponse } from '../../utils/booking-helper'
export default { export default {
i18n: {
zh: {
navTitle: '预订记录',
loadingTitle: '订单加载中',
loadingDesc: '正在同步您的真实预订记录,请稍候。',
bookingFallbackTitle: '房源预订',
buildingFloorLabel: '楼栋楼层',
stayDateLabel: '入住时间',
submittedAtLabel: '提交时间',
emptyTitle: '暂无预订记录',
emptyDesc: '当前暂无预订记录,完成预订后会在这里展示。',
goSelectRoom: '去选房',
cachedListShown: '网络异常,已展示最近同步记录',
loadFailed: '预订记录加载失败',
dateRangeJoiner: ' 至 '
},
en: {
navTitle: 'Booking Records',
loadingTitle: 'Loading bookings',
loadingDesc: 'Syncing your latest booking records. Please wait.',
bookingFallbackTitle: 'Room Booking',
buildingFloorLabel: 'Building / Floor',
stayDateLabel: 'Stay Dates',
submittedAtLabel: 'Submitted At',
emptyTitle: 'No booking records',
emptyDesc: 'No booking record is available yet. Completed bookings will appear here.',
goSelectRoom: 'Select Room',
cachedListShown: 'Network issue detected. Showing the latest synced records.',
loadFailed: 'Failed to load booking records',
dateRangeJoiner: ' to '
}
},
data() { data() {
return { return {
bookingList: [], bookingList: [],
@@ -112,9 +80,6 @@ export default {
await this.fetchBookingList() await this.fetchBookingList()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgBooking.booking_list.booking_list', key, params)
},
getStoreUser() { getStoreUser() {
return (this.$store && this.$store.state && this.$store.state.vuex_user) || {} return (this.$store && this.$store.state && this.$store.state.vuex_user) || {}
}, },
@@ -156,8 +121,8 @@ export default {
this.applyBookingList(normalizeBookingList(cachedList, this.language)) this.applyBookingList(normalizeBookingList(cachedList, this.language))
uni.showToast({ uni.showToast({
title: this.bookingList.length title: this.bookingList.length
? this.t('cachedListShown') ? this.$tr('网络异常,已展示最近同步记录')
: ((error && (error.message || error.msg)) || this.t('loadFailed')), : ((error && (error.message || error.msg)) || this.$tr('预订记录加载失败')),
icon: 'none' icon: 'none'
}) })
}) })

View File

@@ -1,14 +1,14 @@
<template> <template>
<view class="pay-page tn-safe-area-inset-bottom"> <view class="pay-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="#F8F9FA"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="#F8F9FA">
<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> </tn-nav-bar>
<view class="pay-content tn-padding" :style="{paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '200rpx'}"> <view class="pay-content tn-padding" :style="{paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '200rpx'}">
<!-- 支付金额区 --> <!-- 支付金额区 -->
<view class="amount-area tn-flex tn-flex-direction-column tn-flex-col-center tn-margin-top-xl tn-margin-bottom-xl"> <view class="amount-area tn-flex tn-flex-direction-column tn-flex-col-center tn-margin-top-xl tn-margin-bottom-xl">
<view class="tn-color-gray tn-text-md">{{ t('paymentAmount') }}</view> <view class="tn-color-gray tn-text-md">{{ $tr('应付定金') }}</view>
<view class="tn-text-bold tn-color-black tn-margin-top-sm amount-text"> <view class="tn-text-bold tn-color-black tn-margin-top-sm amount-text">
<text class="tn-text-xl tn-margin-right-xs">$</text>500.00 <text class="tn-text-xl tn-margin-right-xs">$</text>500.00
</view> </view>
@@ -21,48 +21,48 @@
<view class="icon-bg-box tn-flex tn-flex-col-center tn-flex-row-center tn-margin-right-sm"> <view class="icon-bg-box tn-flex tn-flex-col-center tn-flex-row-center tn-margin-right-sm">
<text class="tn-icon-building bill-head-icon"></text> <text class="tn-icon-building bill-head-icon"></text>
</view> </view>
<view class="tn-color-gray tn-text-md">{{ t('billTo') }}</view> <view class="tn-color-gray tn-text-md">{{ $tr('收款方') }}</view>
</view> </view>
<view class="tn-text-bold tn-text-md tn-color-black">{{ t('company') }}</view> <view class="tn-text-bold tn-text-md tn-color-black">{{ $tr('绿居物业管理有限公司') }}</view>
</view> </view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-bottom"> <view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-bottom">
<view class="tn-color-gray tn-text-md">{{ t('roomType') }}</view> <view class="tn-color-gray tn-text-md">{{ $tr('房源风格') }}</view>
<view class="tn-text-bold tn-text-md tn-color-black clamp-text-1" style="max-width: 400rpx; text-align: right;"> <view class="tn-text-bold tn-text-md tn-color-black clamp-text-1" style="max-width: 400rpx; text-align: right;">
{{ displayStyleName }} {{ displayStyleName }}
</view> </view>
</view> </view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-bottom" v-if="roomData && (roomData.roomTypeName || roomData.roomTypeNameEn)"> <view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-bottom" v-if="roomData && (roomData.roomTypeName || roomData.roomTypeNameEn)">
<view class="tn-color-gray tn-text-md">{{ t('roomTypeNameLabel') }}</view> <view class="tn-color-gray tn-text-md">{{ $tr('房型') }}</view>
<view class="tn-text-bold tn-text-md tn-color-black clamp-text-1" style="max-width: 400rpx; text-align: right;"> <view class="tn-text-bold tn-text-md tn-color-black clamp-text-1" style="max-width: 400rpx; text-align: right;">
{{ displayRoomTypeName }} {{ displayRoomTypeName }}
</view> </view>
</view> </view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-bottom"> <view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-bottom">
<view class="tn-color-gray tn-text-md">{{ t('location') }}</view> <view class="tn-color-gray tn-text-md">{{ $tr('楼栋位置') }}</view>
<view class="tn-text-bold tn-text-md tn-color-black clamp-text-1" style="max-width: 400rpx; text-align: right;">{{ displayLocationText }}</view> <view class="tn-text-bold tn-text-md tn-color-black clamp-text-1" style="max-width: 400rpx; text-align: right;">{{ displayLocationText }}</view>
</view> </view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-bottom" v-if="roomData && roomData.startDate"> <view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-bottom" v-if="roomData && roomData.startDate">
<view class="tn-color-gray tn-text-md">{{ t('bookingPeriod') }}</view> <view class="tn-color-gray tn-text-md">{{ $tr('预订时间') }}</view>
<view class="tn-text-bold tn-text-md tn-color-black clamp-text-1" style="max-width: 400rpx; text-align: right;"> <view class="tn-text-bold tn-text-md tn-color-black clamp-text-1" style="max-width: 400rpx; text-align: right;">
{{ roomData.startDate }} {{ t('toLabel') }} {{ roomData.endDate }} {{ roomData.startDate }} {{ $tr('至') }} {{ roomData.endDate }}
</view> </view>
</view> </view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-bottom" v-if="roomData && roomData.stayNights"> <view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-bottom" v-if="roomData && roomData.stayNights">
<view class="tn-color-gray tn-text-md">{{ t('stayNights') }}</view> <view class="tn-color-gray tn-text-md">{{ $tr('入住晚数') }}</view>
<view class="tn-text-bold tn-text-md tn-color-black">{{ roomData.stayNights }} {{ t('nightUnit') }}</view> <view class="tn-text-bold tn-text-md tn-color-black">{{ roomData.stayNights }} {{ $tr('晚') }}</view>
</view> </view>
<view class="bill-divider tn-margin-top-md tn-margin-bottom"></view> <view class="bill-divider tn-margin-top-md tn-margin-bottom"></view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-top-md"> <view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-top-md">
<view class="tn-color-black tn-text-bold tn-text-lg">{{ t('total') }}</view> <view class="tn-color-black tn-text-bold tn-text-lg">{{ $tr('合计') }}</view>
<view class="tn-text-bold" style="color: $brand-primary; font-size: 40rpx;">$500.00</view> <view class="tn-text-bold" style="color: $brand-primary; font-size: 40rpx;">$500.00</view>
</view> </view>
</view> </view>
<!-- 支付方式选择 --> <!-- 支付方式选择 -->
<view class="payment-methods"> <view class="payment-methods">
<view class="tn-text-bold tn-text-xl tn-color-black tn-margin-bottom-lg">{{ t('paymentMethod') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black tn-margin-bottom-lg">{{ $tr('选择支付方式') }}</view>
<view class="method-item tn-flex tn-flex-row-between tn-flex-col-center tn-padding tn-margin-bottom" <view class="method-item tn-flex tn-flex-row-between tn-flex-col-center tn-padding tn-margin-bottom"
:class="payMethod === 'Wechat' ? 'method-active' : ''" @click="payMethod = 'Wechat'"> :class="payMethod === 'Wechat' ? 'method-active' : ''" @click="payMethod = 'Wechat'">
@@ -105,10 +105,10 @@
</view> </view>
<view class="tip-panel tn-padding-lg"> <view class="tip-panel tn-padding-lg">
<view class="tn-text-bold tn-text-lg tn-color-black">{{ t('fallbackTitle') }}</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-sm">{{ t('fallbackDesc') }}</view> <view class="tn-color-gray tn-text-sm tn-margin-top-sm">{{ $tr('当前版本还未接通真实定金支付。你可以先改走已接好的入住申请提交流程,后续由工作人员跟进。') }}</view>
<view class="tip-action tn-margin-top-lg" @click="fallbackToApplication"> <view class="tip-action tn-margin-top-lg" @click="fallbackToApplication">
<text class="tn-text-bold">{{ t('fallbackAction') }}</text> <text class="tn-text-bold">{{ $tr('改为提交入住申请') }}</text>
</view> </view>
</view> </view>
@@ -117,11 +117,11 @@
<!-- 底部固定支付按钮 --> <!-- 底部固定支付按钮 -->
<view class="fixed-bottom-btn-area tn-flex tn-flex-row-between tn-flex-col-center"> <view class="fixed-bottom-btn-area tn-flex tn-flex-row-between tn-flex-col-center">
<view class="tn-flex tn-flex-direction-column"> <view class="tn-flex tn-flex-direction-column">
<view class="tn-text-sm tn-color-gray">{{ t('total') }}</view> <view class="tn-text-sm tn-color-gray">{{ $tr('合计') }}</view>
<view class="tn-text-bold tn-text-xl tn-color-black">$500.00</view> <view class="tn-text-bold tn-text-xl tn-color-black">$500.00</view>
</view> </view>
<view class="custom-btn btn-primary" style="width: 60%; height: 100rpx;" @click="handlePay" :class="{ 'btn-disabled': isSubmitting }"> <view class="custom-btn btn-primary" style="width: 60%; height: 100rpx;" @click="handlePay" :class="{ 'btn-disabled': isSubmitting }">
<text class="tn-text-bold tn-text-lg">{{ isSubmitting ? (language === 'zh' ? '支付中...' : 'Processing...') : t('payNow') }}</text> <text class="tn-text-bold tn-text-lg">{{ isSubmitting ? (language === 'zh' ? '支付中...' : 'Processing...') : $tr('确认支付并提交预订') }}</text>
</view> </view>
</view> </view>
@@ -136,77 +136,7 @@ export default {
return { return {
isSubmitting: false, isSubmitting: false,
payMethod: 'Wechat', payMethod: 'Wechat',
roomData: null, roomData: null
i18n: {
zh: {
title: '支付定金',
paymentAmount: '应付定金',
billTo: '收款方',
company: '绿居物业管理有限公司',
roomType: '房源风格',
roomTypeNameLabel: '房型',
location: '楼栋位置',
bookingPeriod: '预订时间',
toLabel: '至',
stayNights: '入住晚数',
nightUnit: '晚',
total: '合计',
paymentMethod: '选择支付方式',
payNow: '确认支付并提交预订',
fallbackTitle: '支付接口暂未接入',
fallbackDesc: '当前版本还未接通真实定金支付。你可以先改走已接好的入住申请提交流程,后续由工作人员跟进。',
fallbackAction: '改为提交入住申请',
fallbackNavigateTitle: '入住申请',
fallbackNavigateDesc: '支付接口暂未接入,先提交入住申请,由工作人员继续跟进。',
paying: '支付中...',
pendingTitle: '支付接口待接入',
paymentNoticeTitle: '用户须知',
paymentNoticeMethodLabel: '支付方式',
paymentNoticeAmountLabel: '支付金额',
paymentNoticeItemLabel: '支付项目',
paymentNoticeItemValue: '房源定金',
paymentNoticeLine1: '请确认支付方式和金额无误后再继续操作。',
paymentNoticeLine2: '当前页面为预支付引导页,真实支付接口暂未接入。',
paymentNoticeLine3: '确认后将进入后续提交流程,由工作人员继续跟进。',
paymentNoticeConfirm: '继续',
paymentNoticeCancel: '取消',
laterAction: '我知道了'
},
en: {
title: 'Deposit Payment',
paymentAmount: 'Deposit Amount',
billTo: 'Bill To',
company: 'Lüju Property Management',
roomType: 'Style',
roomTypeNameLabel: 'Room Type',
location: 'Location',
bookingPeriod: 'Booking Period',
toLabel: 'to',
stayNights: 'Stay Nights',
nightUnit: 'nights',
total: 'Total',
paymentMethod: 'Payment Method',
payNow: 'Pay and Submit',
fallbackTitle: 'Payment API Pending',
fallbackDesc: 'The real deposit payment flow is not ready yet. You can submit an application first and let staff follow up.',
fallbackAction: 'Submit Application Instead',
fallbackNavigateTitle: 'Stay Application',
fallbackNavigateDesc: 'The payment API is not ready yet. Submit a stay application first and let staff follow up.',
paying: 'Processing...',
pendingTitle: 'Payment API Pending',
paymentNoticeTitle: 'Payment Notice',
paymentNoticeMethodLabel: 'Payment Method',
paymentNoticeAmountLabel: 'Amount',
paymentNoticeItemLabel: 'Charge Item',
paymentNoticeItemValue: 'Deposit',
paymentNoticeLine1: 'Please confirm the payment method and amount before continuing.',
paymentNoticeLine2: 'This page is a pre-payment guide and the real payment API is not connected yet.',
paymentNoticeLine3: 'After confirmation, the process continues to the follow-up submission flow for staff handling.',
paymentNoticeConfirm: 'Continue',
paymentNoticeCancel: 'Cancel',
laterAction: 'Later'
}
}
} }
}, },
computed: { computed: {
@@ -256,26 +186,23 @@ export default {
} }
}, },
methods: { methods: {
t(key) {
return this.$tm('pkgBooking.pay.pay', key)
},
confirmPaymentNotice() { confirmPaymentNotice() {
const content = [ const content = [
`${this.t('paymentNoticeItemLabel')}: ${this.t('paymentNoticeItemValue')}`, `${this.$tr('支付项目')}: ${this.$tr('房源定金')}`,
`${this.t('paymentNoticeMethodLabel')}: ${this.payMethod}`, `${this.$tr('支付方式')}: ${this.payMethod}`,
`${this.t('paymentNoticeAmountLabel')}: $500.00`, `${this.$tr('支付金额')}: $500.00`,
'', '',
`1. ${this.t('paymentNoticeLine1')}`, `1. ${this.$tr('请确认支付方式和金额无误后再继续操作。')}`,
`2. ${this.t('paymentNoticeLine2')}`, `2. ${this.$tr('当前页面为预支付引导页,真实支付接口暂未接入。')}`,
`3. ${this.t('paymentNoticeLine3')}` `3. ${this.$tr('确认后将进入后续提交流程,由工作人员继续跟进。')}`
].join('\n') ].join('\n')
return new Promise((resolve) => { return new Promise((resolve) => {
uni.showModal({ uni.showModal({
title: this.t('paymentNoticeTitle'), title: this.$tr('用户须知'),
content, content,
confirmText: this.t('paymentNoticeConfirm'), confirmText: this.$tr('继续'),
cancelText: this.t('paymentNoticeCancel'), cancelText: this.$tr('取消'),
success: (res) => resolve(!!(res && res.confirm)), success: (res) => resolve(!!(res && res.confirm)),
fail: () => resolve(false) fail: () => resolve(false)
}) })
@@ -314,7 +241,7 @@ export default {
const descZh = '支付接口暂未接入,先提交入住申请,由工作人员继续跟进。' const descZh = '支付接口暂未接入,先提交入住申请,由工作人员继续跟进。'
const descEn = 'The payment API is not ready yet. Submit a stay application first and let staff follow up.' const descEn = 'The payment API is not ready yet. Submit a stay application first and let staff follow up.'
uni.navigateTo({ uni.navigateTo({
url: `/pkgService/submit_request/submit_request?title=${encodeURIComponent(this.t('fallbackNavigateTitle'))}&titleZh=${encodeURIComponent(titleZh)}&titleEn=${encodeURIComponent(titleEn)}&desc=${encodeURIComponent(this.t('fallbackNavigateDesc'))}&descZh=${encodeURIComponent(descZh)}&descEn=${encodeURIComponent(descEn)}&payload=${encodeURIComponent(JSON.stringify(payload))}` url: `/pkgService/submit_request/submit_request?title=${encodeURIComponent(this.$tr('入住申请'))}&titleZh=${encodeURIComponent(titleZh)}&titleEn=${encodeURIComponent(titleEn)}&desc=${encodeURIComponent(this.$tr('支付接口暂未接入,先提交入住申请,由工作人员继续跟进。'))}&descZh=${encodeURIComponent(descZh)}&descEn=${encodeURIComponent(descEn)}&payload=${encodeURIComponent(JSON.stringify(payload))}`
}) })
}, },
handlePay() { handlePay() {
@@ -322,16 +249,16 @@ export default {
this.confirmPaymentNotice().then((confirmed) => { this.confirmPaymentNotice().then((confirmed) => {
if (!confirmed) return if (!confirmed) return
this.isSubmitting = true this.isSubmitting = true
uni.showLoading({ title: this.t('paying') }) uni.showLoading({ title: this.$tr('支付中...') })
setTimeout(() => { setTimeout(() => {
uni.hideLoading() uni.hideLoading()
this.isSubmitting = false this.isSubmitting = false
uni.showModal({ uni.showModal({
title: this.t('pendingTitle'), title: this.$tr('支付接口待接入'),
content: this.t('fallbackDesc'), content: this.$tr('当前版本还未接通真实定金支付。你可以先改走已接好的入住申请提交流程,后续由工作人员跟进。'),
confirmText: this.t('fallbackAction'), confirmText: this.$tr('改为提交入住申请'),
cancelText: this.t('laterAction'), cancelText: this.$tr('我知道了'),
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
this.fallbackToApplication() this.fallbackToApplication()

View File

@@ -7,7 +7,7 @@
<text class='icon tn-icon-left'></text> <text class='icon tn-icon-left'></text>
</view> </view>
<view class="tn-flex tn-flex-col-center tn-flex-row-center "> <view class="tn-flex tn-flex-col-center tn-flex-row-center ">
<text class="tn-text-bold tn-text-lg tn-color-black">{{ t('navTitle') }}</text> <text class="tn-text-bold tn-text-lg tn-color-black">{{ $tr('资料图库') }}</text>
</view> </view>
</tn-nav-bar> </tn-nav-bar>
@@ -17,7 +17,7 @@
<view class="justify-content-item align-content-item" style="width: 100vw;"> <view class="justify-content-item align-content-item" style="width: 100vw;">
<view class="tn-flex tn-flex-col-center" style="border: 1rpx solid #D2D2D2;border-radius: 100rpx;padding: 10rpx 20rpx 10rpx 20rpx;width: 90%;"> <view class="tn-flex tn-flex-col-center" style="border: 1rpx solid #D2D2D2;border-radius: 100rpx;padding: 10rpx 20rpx 10rpx 20rpx;width: 90%;">
<text class="tn-icon-search justify-content-item tn-padding-right-xs tn-color-gray tn-text-lg"></text> <text class="tn-icon-search justify-content-item tn-padding-right-xs tn-color-gray tn-text-lg"></text>
<input class="justify-content-item" :placeholder="t('searchPlaceholder')" name="input" placeholder-style="color:#AAAAAA" style="width: 90%;"></input> <input class="justify-content-item" :placeholder="$tr('请输入搜索关键词')" name="input" placeholder-style="color:#AAAAAA" style="width: 90%;"></input>
</view> </view>
</view> </view>
@@ -25,7 +25,7 @@
<view class="justify-content-item tn-text-center"> <view class="justify-content-item tn-text-center">
<!-- 没输入前--> <!-- 没输入前-->
<tn-button backgroundColor="#3C97FF80" shape="round" padding="20rpx 20rpx" width="150rpx" @tap=""> <tn-button backgroundColor="#3C97FF80" shape="round" padding="20rpx 20rpx" width="150rpx" @tap="">
<text class="tn-color-white">{{ t('searchButton') }}</text> <text class="tn-color-white">{{ $tr('搜 索') }}</text>
</tn-button> </tn-button>
<!-- 输入后 --> <!-- 输入后 -->
<!-- <tn-button backgroundColor="#3C97FF" shape="round" padding="20rpx 20rpx" width="150rpx" @tap=""> <!-- <tn-button backgroundColor="#3C97FF" shape="round" padding="20rpx 20rpx" width="150rpx" @tap="">
@@ -70,22 +70,6 @@
export default { export default {
name: 'List', name: 'List',
mixins: [template_page_mixin], mixins: [template_page_mixin],
i18n: {
zh: {
navTitle: '资料图库',
searchPlaceholder: '请输入搜索关键词',
searchButton: '搜 索',
galleryImage: '游戏攻略图',
galleryImageLong: '游戏攻略图游戏攻略图游戏攻略图'
},
en: {
navTitle: 'Gallery',
searchPlaceholder: 'Enter search keywords',
searchButton: 'Search',
galleryImage: 'Guide Image',
galleryImageLong: 'Guide Image Guide Image Guide Image'
}
},
data(){ data(){
return { return {
list: [ list: [
@@ -158,13 +142,10 @@
} }
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgExplore.download.download', key, params)
},
syncList() { syncList() {
this.list = this.list.map((item, index) => ({ this.list = this.list.map((item, index) => ({
...item, ...item,
desc: index === 1 ? this.t('galleryImageLong') : this.t('galleryImage') desc: index === 1 ? this.$tr('游戏攻略图游戏攻略图游戏攻略图') : this.$tr('游戏攻略图')
})) }))
}, },
// 跳转 // 跳转

View File

@@ -7,31 +7,31 @@
<text class='icon tn-icon-left'></text> <text class='icon tn-icon-left'></text>
</view> </view>
<view class="tn-flex tn-flex-col-center tn-flex-row-center "> <view class="tn-flex tn-flex-col-center tn-flex-row-center ">
<text class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</text> <text class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('应用专区') }}</text>
</view> </view>
</tn-nav-bar> </tn-nav-bar>
<view class="top-fixed" :style="{paddingTop: vuex_custom_bar_height + 10 + 'px'}"> <view class="top-fixed" :style="{paddingTop: vuex_custom_bar_height + 10 + 'px'}">
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-padding-left-sm tn-padding-right-sm"> <view class="tn-flex tn-flex-row-between tn-flex-col-center tn-padding-left-sm tn-padding-right-sm">
<view class="tn-flex tn-flex-col-center tn-padding-sm"> <view class="tn-flex tn-flex-col-center tn-padding-sm">
<view class="tn-padding-right-xs">{{ t('sortDefault') }}</view> <view class="tn-padding-right-xs">{{ $tr('默认') }}</view>
<!-- <view class="ri-bookmark-line tn-color-gray--dark"></view> --> <!-- <view class="ri-bookmark-line tn-color-gray--dark"></view> -->
</view> </view>
<view class="tn-flex tn-flex-col-center tn-padding-sm" @tap="openTypeModal"> <view class="tn-flex tn-flex-col-center tn-padding-sm" @tap="openTypeModal">
<view class="tn-padding-right-xs">{{ t('typeLabel') }}</view> <view class="tn-padding-right-xs">{{ $tr('类型') }}</view>
<view class="tn-icon-down-fill icon-active" v-if="showTypeModal"></view> <view class="tn-icon-down-fill icon-active" v-if="showTypeModal"></view>
<view class="tn-icon-down-fill tn-color-gray--dark" v-else></view> <view class="tn-icon-down-fill tn-color-gray--dark" v-else></view>
</view> </view>
<view class="tn-flex tn-flex-col-center tn-padding-sm"> <view class="tn-flex tn-flex-col-center tn-padding-sm">
<view class="tn-padding-right-xs">{{ t('salesLabel') }}</view> <view class="tn-padding-right-xs">{{ $tr('销量') }}</view>
<view class="tn-icon-sort tn-color-gray--dark"></view> <view class="tn-icon-sort tn-color-gray--dark"></view>
</view> </view>
<view class="tn-flex tn-flex-col-center tn-padding-sm"> <view class="tn-flex tn-flex-col-center tn-padding-sm">
<view class="tn-padding-right-xs">{{ t('priceLabel') }}</view> <view class="tn-padding-right-xs">{{ $tr('价格') }}</view>
<view class="tn-icon-sort tn-color-gray--dark"></view> <view class="tn-icon-sort tn-color-gray--dark"></view>
</view> </view>
<view class="tn-flex tn-flex-col-center tn-padding-sm" @tap="openSelectModal"> <view class="tn-flex tn-flex-col-center tn-padding-sm" @tap="openSelectModal">
<view class="tn-padding-right-xs">{{ t('comprehensiveLabel') }}</view> <view class="tn-padding-right-xs">{{ $tr('综合') }}</view>
<view class="tn-icon-filter tn-color-gray--dark tn-text-sm"></view> <view class="tn-icon-filter tn-color-gray--dark tn-text-sm"></view>
</view> </view>
</view> </view>
@@ -58,7 +58,7 @@
<view class="justify-content-item tn-flex tn-flex-col-center tn-padding-left-sm tn-padding-right-sm" style="margin-left: -6rpx;color: #333333;"> <view class="justify-content-item tn-flex tn-flex-col-center tn-padding-left-sm tn-padding-right-sm" style="margin-left: -6rpx;color: #333333;">
<text class="tn-text-sm">¥</text> <text class="tn-text-sm">¥</text>
<text class="tn-padding-right-sm tn-text-lg tn-text-bold">{{ item.price }}</text> <text class="tn-padding-right-sm tn-text-lg tn-text-bold">{{ item.price }}</text>
<text class="tn-color-gray tn-text-sm" style="color: #C1CACF;"> {{ item.number }} {{ t('purchasedSuffix') }}</text> <text class="tn-color-gray tn-text-sm" style="color: #C1CACF;"> {{ item.number }} {{ $tr('人已购') }}</text>
</view> </view>
<!-- <view class="justify-content-item tn-flex tn-flex-col-center"> <!-- <view class="justify-content-item tn-flex tn-flex-col-center">
<tn-button size="sm" shape="icon" backgroundColor="#3e486400" fontColor="#333333" padding="10rpx 0 0 0"> <tn-button size="sm" shape="icon" backgroundColor="#3e486400" fontColor="#333333" padding="10rpx 0 0 0">
@@ -95,40 +95,6 @@
components: { TypeModal , SelectModal }, components: { TypeModal , SelectModal },
name: 'TemplateFilter', name: 'TemplateFilter',
mixins: [template_page_mixin], mixins: [template_page_mixin],
i18n: {
zh: {
navTitle: '应用专区',
sortDefault: '默认',
typeLabel: '类型',
salesLabel: '销量',
priceLabel: '价格',
comprehensiveLabel: '综合',
purchasedSuffix: '人已购',
product1: '蛋仔派对',
product2: '保卫萝卜',
product3: '旅行青蛙',
product4: '辐射避难所',
product5: '王国保卫战',
product6: '崩坏 · 星穹铁道',
product7: '纪念碑谷'
},
en: {
navTitle: 'App Zone',
sortDefault: 'Default',
typeLabel: 'Type',
salesLabel: 'Sales',
priceLabel: 'Price',
comprehensiveLabel: 'All',
purchasedSuffix: 'purchased',
product1: 'Eggy Party',
product2: 'Carrot Defense',
product3: 'Travel Frog',
product4: 'Fallout Shelter',
product5: 'Kingdom Rush',
product6: 'Honkai: Star Rail',
product7: 'Monument Valley'
}
},
data(){ data(){
return { return {
/* 选择弹窗*/ /* 选择弹窗*/
@@ -197,14 +163,11 @@
} }
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgExplore.filter.filter', key, params)
},
syncProducts() { syncProducts() {
const keys = ['product1', 'product2', 'product3', 'product4', 'product5', 'product6', 'product7'] const names = ['蛋仔派对', '保卫萝卜', '旅行青蛙', '辐射避难所', '王国保卫战', '崩坏 · 星穹铁道', '纪念碑谷']
this.product = this.product.map((item, index) => ({ this.product = this.product.map((item, index) => ({
...item, ...item,
desc: this.t(keys[index] || 'product1') desc: this.$tr(names[index] || '蛋仔派对')
})) }))
}, },
// 打开选择弹框 // 打开选择弹框

View File

@@ -3,9 +3,9 @@
<view class="section-header tn-flex tn-flex-row-between tn-flex-col-center"> <view class="section-header tn-flex tn-flex-row-between tn-flex-col-center">
<view class="title-wrap tn-flex tn-flex-col-center"> <view class="title-wrap tn-flex tn-flex-col-center">
<view class="title-accent"></view> <view class="title-accent"></view>
<text class="section-title">{{ t('sectionTitle') }}</text> <text class="section-title">{{ $tr('房间配置') }}</text>
</view> </view>
<text class="section-subtitle">{{ t('sectionSubtitle', { count: assetList.length }) }}</text> <text class="section-subtitle">{{ $tr('{count} 项配置', { count: assetList.length }) }}</text>
</view> </view>
<view class="asset-grid"> <view class="asset-grid">
@@ -53,11 +53,7 @@ export default {
default: 'zh' default: 'zh'
} }
}, },
methods: { methods: {}
t(key, params = {}) {
return this.$tm('pkgExplore.room_detail.room_detail', key, params)
}
}
} }
</script> </script>

View File

@@ -2,7 +2,7 @@
<view class="room-hero"> <view class="room-hero">
<view v-if="showVrPanel" class="hero-vr-panel" @click="openVR"> <view v-if="showVrPanel" class="hero-vr-panel" @click="openVR">
<image v-if="vrPoster" :src="vrPoster" mode="aspectFill" class="hero-image"></image> <image v-if="vrPoster" :src="vrPoster" mode="aspectFill" class="hero-image"></image>
<view v-else class="hero-empty tn-flex tn-flex-col-center tn-flex-row-center">{{ t('noImage') }}</view> <view v-else class="hero-empty tn-flex tn-flex-col-center tn-flex-row-center">{{ $tr('暂无图片') }}</view>
<view class="hero-vr-glow"></view> <view class="hero-vr-glow"></view>
<view class="vr-center-entry"> <view class="vr-center-entry">
<view class="vr-center-ring vr-center-ring--outer"></view> <view class="vr-center-ring vr-center-ring--outer"></view>
@@ -11,8 +11,8 @@
<text class="tn-icon-vr vr-center-core-icon"></text> <text class="tn-icon-vr vr-center-core-icon"></text>
</view> </view>
<view class="vr-center-text"> <view class="vr-center-text">
<view class="vr-center-title">{{ t('vrPreview') }}</view> <view class="vr-center-title">{{ $tr('VR预览') }}</view>
<view class="vr-center-desc">{{ t('vrPreviewDesc') }}</view> <view class="vr-center-desc">{{ $tr('切换到沉浸式看房模式。') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -30,7 +30,7 @@
</swiper-item> </swiper-item>
</swiper> </swiper>
<view v-else class="hero-empty tn-flex tn-flex-col-center tn-flex-row-center"> <view v-else class="hero-empty tn-flex tn-flex-col-center tn-flex-row-center">
{{ t('noImage') }} {{ $tr('暂无图片') }}
</view> </view>
<view class="hero-mask"></view> <view class="hero-mask"></view>
@@ -46,7 +46,7 @@
@click="switchMedia('image')" @click="switchMedia('image')"
> >
<text class="tn-icon-image media-tab-icon"></text> <text class="tn-icon-image media-tab-icon"></text>
<text>{{ t('imageTab') }}</text> <text>{{ $tr('图片') }}</text>
</view> </view>
<view <view
class="media-tab" class="media-tab"
@@ -54,7 +54,7 @@
@click="switchMedia('vr')" @click="switchMedia('vr')"
> >
<text class="tn-icon-vr media-tab-icon"></text> <text class="tn-icon-vr media-tab-icon"></text>
<text>{{ t('vrPreview') }}</text> <text>{{ $tr('VR预览') }}</text>
</view> </view>
</view> </view>
@@ -65,7 +65,7 @@
<!-- 引导下滑箭头 --> <!-- 引导下滑箭头 -->
<view class="scroll-down-hint tn-flex tn-flex-direction-column tn-flex-col-center"> <view class="scroll-down-hint tn-flex tn-flex-direction-column tn-flex-col-center">
<text class="hint-text">{{ t('swipeUpHint') }}</text> <text class="hint-text">{{ $tr('上滑查看详情') }}</text>
<text class="tn-icon-up arrow-icon"></text> <text class="tn-icon-up arrow-icon"></text>
</view> </view>
</view> </view>
@@ -113,9 +113,6 @@ export default {
} }
}, },
methods: { methods: {
t(key) {
return this.$tm('pkgExplore.room_detail.room_detail', key)
},
onSwiperChange(e) { onSwiperChange(e) {
this.currentImageIndex = e.detail.current; this.currentImageIndex = e.detail.current;
this.$emit('swiper-change', e); this.$emit('swiper-change', e);

View File

@@ -2,7 +2,7 @@
<view class="room-info-wrapper"> <view class="room-info-wrapper">
<view class="section-panel" v-if="basicInfoRows.length"> <view class="section-panel" v-if="basicInfoRows.length">
<view class="section-header"> <view class="section-header">
<text class="section-title">{{ t('basicInfo') }}</text> <text class="section-title">{{ $tr('基础信息') }}</text>
</view> </view>
<view class="info-list"> <view class="info-list">
<view v-for="item in basicInfoRows" :key="item.label" class="info-row tn-flex tn-flex-row-between tn-flex-col-center"> <view v-for="item in basicInfoRows" :key="item.label" class="info-row tn-flex tn-flex-row-between tn-flex-col-center">
@@ -16,7 +16,7 @@
<view class="section-panel" v-if="displayDescriptions.length"> <view class="section-panel" v-if="displayDescriptions.length">
<view class="section-header"> <view class="section-header">
<text class="section-title">{{ isRoomType ? t('roomTypeDesc') : t('roomDesc') }}</text> <text class="section-title">{{ isRoomType ? $tr('房型介绍') : $tr('房间介绍') }}</text>
</view> </view>
<view class="desc-wrap" :class="{ 'desc-wrap--collapsed': isDescCollapsed }"> <view class="desc-wrap" :class="{ 'desc-wrap--collapsed': isDescCollapsed }">
<view <view
@@ -32,7 +32,7 @@
class="expand-btn tn-flex tn-flex-row-center tn-flex-col-center" class="expand-btn tn-flex tn-flex-row-center tn-flex-col-center"
@click="toggleDesc" @click="toggleDesc"
> >
<text>{{ isDescCollapsed ? t('expandAll') : t('collapse') }}</text> <text>{{ isDescCollapsed ? $tr('展开全部') : $tr('收起') }}</text>
<text :class="isDescCollapsed ? 'tn-icon-down' : 'tn-icon-up'" class="expand-icon"></text> <text :class="isDescCollapsed ? 'tn-icon-down' : 'tn-icon-up'" class="expand-icon"></text>
</view> </view>
</view> </view>
@@ -69,9 +69,6 @@ export default {
} }
}, },
methods: { methods: {
t(key) {
return this.$tm('pkgExplore.room_detail.room_detail', key)
},
toggleDesc() { toggleDesc() {
this.isDescCollapsed = !this.isDescCollapsed; this.isDescCollapsed = !this.isDescCollapsed;
} }

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="section-panel" v-if="bookingRuleRows.length"> <view class="section-panel" v-if="bookingRuleRows.length">
<view class="section-header"> <view class="section-header">
<text class="section-title">{{ isRoomType ? t('bookingRules') : t('bookingInfo') }}</text> <text class="section-title">{{ isRoomType ? $tr('预订规则') : $tr('预订信息') }}</text>
</view> </view>
<view class="rule-list"> <view class="rule-list">
<view v-for="item in bookingRuleRows" :key="item.label" class="rule-item tn-flex tn-flex-row-between tn-flex-col-center"> <view v-for="item in bookingRuleRows" :key="item.label" class="rule-item tn-flex tn-flex-row-between tn-flex-col-center">
@@ -31,11 +31,7 @@ export default {
default: 'zh' default: 'zh'
} }
}, },
methods: { methods: {}
t(key) {
return this.$tm('pkgExplore.room_detail.room_detail', key)
}
}
} }
</script> </script>

View File

@@ -6,10 +6,10 @@
<template v-if="hasPrice"> <template v-if="hasPrice">
<text class="price-symbol">¥</text> <text class="price-symbol">¥</text>
<text class="price-value">{{ detailData.priceValue }}</text> <text class="price-value">{{ detailData.priceValue }}</text>
<text class="price-unit">{{ t('priceUnit') }}</text> <text class="price-unit">{{ $tr('/月') }}</text>
</template> </template>
<template v-else> <template v-else>
<text class="price-empty">{{ t('pricePending') }}</text> <text class="price-empty">{{ $tr('待定') }}</text>
</template> </template>
</view> </view>
@@ -40,7 +40,7 @@
<view class="address-info"> <view class="address-info">
<view class="address-text tn-text-ellipsis">{{ shortAddress }}</view> <view class="address-text tn-text-ellipsis">{{ shortAddress }}</view>
<view class="address-sub tn-flex tn-flex-col-center"> <view class="address-sub tn-flex tn-flex-col-center">
<text class="address-sub-text">{{ t('addressHint') }}</text> <text class="address-sub-text">{{ $tr('查看地图位置') }}</text>
<text class="tn-icon-right address-sub-icon"></text> <text class="tn-icon-right address-sub-icon"></text>
</view> </view>
</view> </view>
@@ -48,7 +48,7 @@
<view class="address-right"> <view class="address-right">
<view class="nav-btn tn-flex tn-flex-row-center tn-flex-col-center"> <view class="nav-btn tn-flex tn-flex-row-center tn-flex-col-center">
<text class="tn-icon-navigation-fill"></text> <text class="tn-icon-navigation-fill"></text>
<text class="nav-text">{{ t('navigate') }}</text> <text class="nav-text">{{ $tr('导航') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -91,19 +91,16 @@ export default {
shortAddress() { shortAddress() {
const addr = this.displayAddress; const addr = this.displayAddress;
if (!addr) { if (!addr) {
return this.t('noAddress') return this.$tr('暂无地址')
} }
if (this.language === 'en') { if (this.language === 'en') {
return addr return addr
} }
const short = addr.replace(/^(?:.*?省|.*?自治区)?(?:.*?市|.*?自治州)?/, ''); const short = addr.replace(/^(?:.*?省|.*?自治区)?(?:.*?市|.*?自治州)?/, '');
return short.trim() ? short : (addr || this.t('noAddress')); return short.trim() ? short : (addr || this.$tr('暂无地址'));
} }
}, },
methods: { methods: {
t(key) {
return this.$tm('pkgExplore.room_detail.room_detail', key)
},
handleAddressClick() { handleAddressClick() {
this.$emit('open-map', this.detailData) this.$emit('open-map', this.detailData)
} }

View File

@@ -9,7 +9,7 @@
:fixed="true" :fixed="true"
class="glass-nav-bar" class="glass-nav-bar"
> >
<view class="nav-title" :style="{ opacity: navTitleOpacity, color: navTitleColor }">{{ displayName || t('navDetail') }}</view> <view class="nav-title" :style="{ opacity: navTitleOpacity, color: navTitleColor }">{{ displayName || $tr('房型详情') }}</view>
</tn-nav-bar> </tn-nav-bar>
<swiper <swiper
@@ -50,7 +50,7 @@
<view class="compact-hero-panel"> <view class="compact-hero-panel">
<view v-if="showVrPanel" class="compact-vr-panel" @click="openVR"> <view v-if="showVrPanel" class="compact-vr-panel" @click="openVR">
<image v-if="vrPoster" :src="vrPoster" mode="aspectFill" class="compact-hero-image"></image> <image v-if="vrPoster" :src="vrPoster" mode="aspectFill" class="compact-hero-image"></image>
<view v-else class="compact-hero-empty tn-flex tn-flex-col-center tn-flex-row-center">{{ t('noImage') }}</view> <view v-else class="compact-hero-empty tn-flex tn-flex-col-center tn-flex-row-center">{{ $tr('暂无图片') }}</view>
<view class="compact-vr-glow"></view> <view class="compact-vr-glow"></view>
</view> </view>
<swiper <swiper
@@ -65,7 +65,7 @@
<image class="compact-hero-image" :src="image" mode="aspectFill"></image> <image class="compact-hero-image" :src="image" mode="aspectFill"></image>
</swiper-item> </swiper-item>
</swiper> </swiper>
<view v-else class="compact-hero-empty tn-flex tn-flex-col-center tn-flex-row-center">{{ t('noImage') }}</view> <view v-else class="compact-hero-empty tn-flex tn-flex-col-center tn-flex-row-center">{{ $tr('暂无图片') }}</view>
<view class="compact-hero-mask"></view> <view class="compact-hero-mask"></view>
<view class="compact-hero-topbar tn-flex tn-flex-row-between tn-flex-col-center"> <view class="compact-hero-topbar tn-flex tn-flex-row-between tn-flex-col-center">
<view v-if="hasVR" class="compact-media-tabs"> <view v-if="hasVR" class="compact-media-tabs">
@@ -74,17 +74,17 @@
:class="{ 'compact-media-tab--active': currentMediaType === 'image' }" :class="{ 'compact-media-tab--active': currentMediaType === 'image' }"
@click="switchMedia('image')" @click="switchMedia('image')"
> >
{{ t('imageTab') }} {{ $tr('图片') }}
</view> </view>
<view <view
class="compact-media-tab" class="compact-media-tab"
:class="{ 'compact-media-tab--active': currentMediaType === 'vr' }" :class="{ 'compact-media-tab--active': currentMediaType === 'vr' }"
@click="switchMedia('vr')" @click="switchMedia('vr')"
> >
{{ t('vrTab') }} {{ $tr('VR') }}
</view> </view>
</view> </view>
<view v-if="galleryList.length" class="compact-hero-counter">{{ t('imageCount', { count: galleryList.length }) }}</view> <view v-if="galleryList.length" class="compact-hero-counter">{{ $tr('{count} 张图片', { count: galleryList.length }) }}</view>
</view> </view>
<view class="compact-hero-action-layer"> <view class="compact-hero-action-layer">
<view v-if="showVrPanel" class="compact-hero-center-action" @click="openVR"> <view v-if="showVrPanel" class="compact-hero-center-action" @click="openVR">
@@ -94,8 +94,8 @@
<text class="tn-icon-vr compact-hero-core-icon"></text> <text class="tn-icon-vr compact-hero-core-icon"></text>
</view> </view>
<view class="compact-hero-action-text"> <view class="compact-hero-action-text">
<view class="compact-hero-action-title">{{ t('vrPreview') }}</view> <view class="compact-hero-action-title">{{ $tr('VR预览') }}</view>
<view class="compact-hero-action-desc">{{ t('vrPreviewDesc') }}</view> <view class="compact-hero-action-desc">{{ $tr('切换到沉浸式看房模式。') }}</view>
</view> </view>
</view> </view>
<view v-else class="compact-hero-center-action" @click="previewImage"> <view v-else class="compact-hero-center-action" @click="previewImage">
@@ -105,8 +105,8 @@
<text class="tn-icon-image compact-hero-core-icon"></text> <text class="tn-icon-image compact-hero-core-icon"></text>
</view> </view>
<view class="compact-hero-action-text"> <view class="compact-hero-action-text">
<view class="compact-hero-action-title">{{ t('viewLargeImage') }}</view> <view class="compact-hero-action-title">{{ $tr('查看大图') }}</view>
<view class="compact-hero-action-desc">{{ t('viewLargeImageDesc') }}</view> <view class="compact-hero-action-desc">{{ $tr('点击查看当前图片的大图预览。') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -147,7 +147,7 @@
<view class="action-left tn-flex tn-flex-col-center"> <view class="action-left tn-flex tn-flex-col-center">
<button class="action-icon-btn" open-type="share"> <button class="action-icon-btn" open-type="share">
<text class="tn-icon-share-circle action-icon-font"></text> <text class="tn-icon-share-circle action-icon-font"></text>
<text class="action-icon-text">{{ t('share') }}</text> <text class="action-icon-text">{{ $tr('分享') }}</text>
</button> </button>
</view> </view>
<view class="action-right tn-flex tn-flex-col-center"> <view class="action-right tn-flex tn-flex-col-center">
@@ -159,7 +159,7 @@
class="action-btn action-btn--primary" class="action-btn action-btn--primary"
@click="handleApplyStay" @click="handleApplyStay"
> >
<text>{{ t('applyStay') }}</text> <text>{{ $tr('申请入住') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -680,7 +680,7 @@ export default {
return this.detailData.priceValue !== '' && this.detailData.priceValue !== null return this.detailData.priceValue !== '' && this.detailData.priceValue !== null
}, },
primaryActionText() { primaryActionText() {
return this.isRoomType ? this.t('applyViewing') : this.t('bookRoom') return this.isRoomType ? this.$tr('预约看房') : this.$tr('立即预订')
}, },
summaryBadges() { summaryBadges() {
const badges = [] const badges = []
@@ -693,30 +693,30 @@ export default {
badges.push(localizePayMethodText(this.detailData.payMethod, this.language)) badges.push(localizePayMethodText(this.detailData.payMethod, this.language))
} }
if (this.isRoomType && this.detailData.needApproval === true) { if (this.isRoomType && this.detailData.needApproval === true) {
badges.push(this.t('needApproval')) badges.push(this.$tr('需审核'))
} }
if (this.isRoomType && this.detailData.needApproval === false) { if (this.isRoomType && this.detailData.needApproval === false) {
badges.push(this.t('noApprovalNeeded')) badges.push(this.$tr('无需审核'))
} }
return badges return badges
}, },
basicInfoRows() { basicInfoRows() {
const rows = this.isRoomType const rows = this.isRoomType
? [ ? [
{ label: this.t('roomTypeNameLabel'), value: this.displayName }, { label: this.$tr('房型名称'), value: this.displayName },
{ label: this.t('englishNameLabel'), value: this.showEnglishName ? (this.detailData.nameEnDisplay || this.detailData.nameEn) : '' }, { label: this.$tr('英文名称'), value: this.showEnglishName ? (this.detailData.nameEnDisplay || this.detailData.nameEn) : '' },
{ label: this.t('areaLabel'), value: this.language === 'en' ? (this.detailData.areaNameEnDisplay || this.detailData.areaNameEn || this.detailData.areaName) : (this.detailData.areaName || '') }, { label: this.$tr('所属区域'), value: this.language === 'en' ? (this.detailData.areaNameEnDisplay || this.detailData.areaNameEn || this.detailData.areaName) : (this.detailData.areaName || '') },
{ label: this.t('payMethodLabel'), value: localizePayMethodText(this.detailData.pricePlan.matchedStrategies || this.detailData.payMethod, this.language) }, { label: this.$tr('支付方式'), value: localizePayMethodText(this.detailData.pricePlan.matchedStrategies || this.detailData.payMethod, this.language) },
{ label: this.t('priceLabel'), value: this.hasPrice ? `¥${this.detailData.priceValue}` : '' } { label: this.$tr('价格'), value: this.hasPrice ? `¥${this.detailData.priceValue}` : '' }
] ]
: [ : [
{ label: this.t('roomCodeLabel'), value: this.detailData.roomCode }, { label: this.$tr('房号'), value: this.detailData.roomCode },
{ label: this.t('roomTypeLabel'), value: this.language === 'en' ? (this.detailData.roomTypeNameEnDisplay || this.detailData.roomTypeNameEn || this.detailData.roomTypeName) : (this.detailData.roomTypeName || '') }, { label: this.$tr('房型'), value: this.language === 'en' ? (this.detailData.roomTypeNameEnDisplay || this.detailData.roomTypeNameEn || this.detailData.roomTypeName) : (this.detailData.roomTypeName || '') },
{ label: this.t('buildingLabel'), value: this.language === 'en' ? (this.detailData.buildingNameEnDisplay || this.detailData.buildingNameEn || this.detailData.buildingName) : (this.detailData.buildingName || '') }, { label: this.$tr('楼栋'), value: this.language === 'en' ? (this.detailData.buildingNameEnDisplay || this.detailData.buildingNameEn || this.detailData.buildingName) : (this.detailData.buildingName || '') },
{ label: this.t('unitLabel'), value: this.language === 'en' ? (this.detailData.buildingSectionNameEnDisplay || this.detailData.buildingSectionNameEn || this.detailData.buildingSectionName) : (this.detailData.buildingSectionName || '') }, { label: this.$tr('单元'), value: this.language === 'en' ? (this.detailData.buildingSectionNameEnDisplay || this.detailData.buildingSectionNameEn || this.detailData.buildingSectionName) : (this.detailData.buildingSectionName || '') },
{ label: this.t('floorLabel'), value: this.language === 'en' ? (this.detailData.floorNameEnDisplay || this.detailData.floorNameEn || this.detailData.floorName) : (this.detailData.floorName || '') }, { label: this.$tr('楼层'), value: this.language === 'en' ? (this.detailData.floorNameEnDisplay || this.detailData.floorNameEn || this.detailData.floorName) : (this.detailData.floorName || '') },
{ label: this.t('orientationLabel'), value: this.language === 'en' ? (this.detailData.orientationEn || this.detailData.orientation) : (this.detailData.orientation || '') }, { label: this.$tr('朝向'), value: this.language === 'en' ? (this.detailData.orientationEn || this.detailData.orientation) : (this.detailData.orientation || '') },
{ label: this.t('statusLabel'), value: localizeKnownStatusText(this.detailData.statusText, this.language) } { label: this.$tr('状态'), value: localizeKnownStatusText(this.detailData.statusText, this.language) }
] ]
return rows.filter((item) => item.value) return rows.filter((item) => item.value)
@@ -798,22 +798,19 @@ export default {
}, },
onShareAppMessage() { onShareAppMessage() {
return { return {
title: this.displayName || this.t('shareTitle'), title: this.displayName || this.$tr('房型详情'),
path: this.buildSharePath(), path: this.buildSharePath(),
imageUrl: this.detailData.image || '' imageUrl: this.detailData.image || ''
} }
}, },
onShareTimeline() { onShareTimeline() {
return { return {
title: this.displayName || this.t('shareTitle'), title: this.displayName || this.$tr('房型详情'),
query: this.buildShareQuery(), query: this.buildShareQuery(),
imageUrl: this.detailData.image || '' imageUrl: this.detailData.image || ''
} }
}, },
methods: { methods: {
t(key, params = {}) {
return this.$tm('pkgExplore.room_detail.room_detail', key, params)
},
scheduleAutoSwitch() { scheduleAutoSwitch() {
if (this.hasAutoSwitched || this.currentSwiperIndex !== 0) { if (this.hasAutoSwitched || this.currentSwiperIndex !== 0) {
return return
@@ -870,7 +867,7 @@ export default {
}, },
previewImage() { previewImage() {
if (!this.galleryList.length) { if (!this.galleryList.length) {
uni.showToast({ title: this.t('noPreviewImage'), icon: 'none' }) uni.showToast({ title: this.$tr('暂无预览图片'), icon: 'none' })
return return
} }
uni.previewImage({ uni.previewImage({
@@ -892,15 +889,15 @@ export default {
this.sourceAreaData && this.sourceAreaData.lng this.sourceAreaData && this.sourceAreaData.lng
) )
if (latitude === null || longitude === null) { if (latitude === null || longitude === null) {
uni.showToast({ title: this.t('noLocation'), icon: 'none' }); uni.showToast({ title: this.$tr('暂无位置信息'), icon: 'none' });
return; return;
} }
uni.openLocation({ uni.openLocation({
latitude, latitude,
longitude, longitude,
name: this.language === 'en' name: this.language === 'en'
? (detail.buildingNameEnDisplay || detail.buildingNameEn || detail.areaNameEnDisplay || detail.areaNameEn || detail.buildingName || detail.areaName || this.displayName || this.t('apartmentName')) ? (detail.buildingNameEnDisplay || detail.buildingNameEn || detail.areaNameEnDisplay || detail.areaNameEn || detail.buildingName || detail.areaName || this.displayName || this.$tr('公寓'))
: (detail.buildingName || detail.areaName || this.displayName || this.t('apartmentName')), : (detail.buildingName || detail.areaName || this.displayName || this.$tr('公寓')),
address: this.language === 'en' address: this.language === 'en'
? (detail.addressEnDisplay || detail.addressEn || detail.address || (this.sourceAreaData && (this.sourceAreaData.addressEnDisplay || this.sourceAreaData.addressEn || this.sourceAreaData.address)) || '') ? (detail.addressEnDisplay || detail.addressEn || detail.address || (this.sourceAreaData && (this.sourceAreaData.addressEnDisplay || this.sourceAreaData.addressEn || this.sourceAreaData.address)) || '')
: (detail.address || (this.sourceAreaData && this.sourceAreaData.address) || ''), : (detail.address || (this.sourceAreaData && this.sourceAreaData.address) || ''),
@@ -908,14 +905,14 @@ export default {
}); });
}, },
openVR() { openVR() {
const titleZh = `${this.displayName || this.t('roomTypeFallback')} ${this.t('vrViewingTitle')}` const titleZh = `${this.displayName || this.$tr('房型')} ${this.$tr('VR看房')}`
const englishName = this.detailData.roomTypeNameEnDisplay const englishName = this.detailData.roomTypeNameEnDisplay
|| this.detailData.roomTypeNameEn || this.detailData.roomTypeNameEn
|| this.detailData.nameEnDisplay || this.detailData.nameEnDisplay
|| this.detailData.nameEn || this.detailData.nameEn
|| this.displayName || this.displayName
|| this.t('roomTypeFallback') || this.$tr('房型')
const titleEn = `${englishName} ${this.t('vrViewingTitle')}` const titleEn = `${englishName} ${this.$tr('VR看房')}`
uni.navigateTo({ uni.navigateTo({
url: `/pkgExplore/vr_preview/vr_preview?title=${encodeURIComponent(this.language === 'en' ? titleEn : titleZh)}&titleZh=${encodeURIComponent(titleZh)}&titleEn=${encodeURIComponent(titleEn)}&poster=${encodeURIComponent(this.vrPoster || '')}` url: `/pkgExplore/vr_preview/vr_preview?title=${encodeURIComponent(this.language === 'en' ? titleEn : titleZh)}&titleZh=${encodeURIComponent(titleZh)}&titleEn=${encodeURIComponent(titleEn)}&poster=${encodeURIComponent(this.vrPoster || '')}`
}) })
@@ -1045,10 +1042,10 @@ export default {
if (!ensureBookingAccess({ if (!ensureBookingAccess({
language: this.$getLanguage(), language: this.$getLanguage(),
loginTitle: this.t('loginTitle'), loginTitle: this.$tr('请先登录'),
loginContent: this.isRoomType loginContent: this.isRoomType
? this.t('roomTypeApplyLoginContent') ? this.$tr('请先登录后再提交该房型的看房申请。')
: this.t('allRoomsLoginContent'), : this.$tr('请先登录后再预订该房间。'),
loginRedirectUrl: targetUrl loginRedirectUrl: targetUrl
})) { })) {
return return
@@ -1068,8 +1065,8 @@ export default {
handleApplyStay() { handleApplyStay() {
const serviceEntryUrl = this.buildServiceEntryUrl({ const serviceEntryUrl = this.buildServiceEntryUrl({
path: '/pkgService/submit_request/submit_request', path: '/pkgService/submit_request/submit_request',
title: `${this.displayName || this.t('roomTypeFallback')} ${this.t('stayApplicationSuffix')}`, title: `${this.displayName || this.$tr('房型')} ${this.$tr('入住申请')}`,
desc: `${this.t('roomTypeCodePrefix')}${this.detailData.roomTypeCode || ''}.${this.hasPrice ? ` ${this.t('pricePrefix')}¥${this.detailData.priceValue}.` : ''}${this.detailData.pricePlan.matchedStrategies || this.detailData.payMethod ? ` ${this.t('payMethodPrefix')}${localizePayMethodText(this.detailData.pricePlan.matchedStrategies || this.detailData.payMethod, this.language)}.` : ''}`, desc: `${this.$tr('房型编码:')}${this.detailData.roomTypeCode || ''}.${this.hasPrice ? ` ${this.$tr('价格:')}¥${this.detailData.priceValue}.` : ''}${this.detailData.pricePlan.matchedStrategies || this.detailData.payMethod ? ` ${this.$tr('支付方式:')}${localizePayMethodText(this.detailData.pricePlan.matchedStrategies || this.detailData.payMethod, this.language)}.` : ''}`,
key: 'roomTypeSubmitList', key: 'roomTypeSubmitList',
payload: { payload: {
sourceType: 'area-room-type', sourceType: 'area-room-type',
@@ -1091,8 +1088,8 @@ export default {
if (!ensureBookingAccess({ if (!ensureBookingAccess({
language: this.$getLanguage(), language: this.$getLanguage(),
loginTitle: this.t('loginTitle'), loginTitle: this.$tr('请先登录'),
loginContent: this.t('stayLoginContent'), loginContent: this.$tr('请先登录后再提交入住申请。'),
loginRedirectUrl: serviceEntryUrl loginRedirectUrl: serviceEntryUrl
})) { })) {
return return

View File

@@ -28,10 +28,10 @@
</view> </view>
<view class="tn-margin tn-padding-top-sm" style="color: #001E50;font-size: 50rpx;"> <view class="tn-margin tn-padding-top-sm" style="color: #001E50;font-size: 50rpx;">
<text class="tn-text-bold">62,888</text> <text class="tn-text-bold">62,888</text>
<text class="tn-padding-left-xs tn-text-xs tn-color-gray">{{ t('pointsUnit') }}</text> <text class="tn-padding-left-xs tn-text-xs tn-color-gray">{{ $tr('积分') }}</text>
</view> </view>
<view class="tn-margin tn-text-sm" style="color: #001E50;"> <view class="tn-margin tn-text-sm" style="color: #001E50;">
{{ t('pointsRecord') }} <text class="tn-icon-right tn-padding-left-xs tn-color-gray"></text> {{ $tr('积分记录') }} <text class="tn-icon-right tn-padding-left-xs tn-color-gray"></text>
</view> </view>
</view> </view>
@@ -84,7 +84,7 @@
<!-- 未签到--> <!-- 未签到-->
<view class="tn-flex-1 justify-content-item tn-margin-xs tn-text-center"> <view class="tn-flex-1 justify-content-item tn-margin-xs tn-text-center">
<tn-button shape="round" backgroundColor="$brand-primary" fontColor="#FFFFFF" padding="40rpx 0" width="100%" :fontSize="30" @tap="showLandscape"> <tn-button shape="round" backgroundColor="$brand-primary" fontColor="#FFFFFF" padding="40rpx 0" width="100%" :fontSize="30" @tap="showLandscape">
<text class="">{{ t('checkInNow') }}</text> <text class="">{{ $tr('立即签到') }}</text>
</tn-button> </tn-button>
</view> </view>
@@ -96,12 +96,12 @@
</view> --> </view> -->
<view class="tn-strip-bottom-min tn-padding" style="color: #95A3A8;"> <view class="tn-strip-bottom-min tn-padding" style="color: #95A3A8;">
{{ t('checkInRewardHint') }} {{ $tr('坚持每天连续签到可以获得多重奖励哦') }}
</view> </view>
<view class="tn-padding-left tn-pright tn-padding-top tn-padding-bottom tn-text-sm" style="color: #95A3A8;"> <view class="tn-padding-left tn-pright tn-padding-top tn-padding-bottom tn-text-sm" style="color: #95A3A8;">
{{ t('warmTip') }}<br> {{ $tr('温馨提示:') }}<br>
{{ t('warmTipDesc') }} {{ $tr('每日签到可获得10积分奖励,连续完成7天签到可能获得额外奖励,连续签到满7天后将会重置签到记录。') }}
</view> </view>
</view> </view>
@@ -110,10 +110,10 @@
<view class="tn-padding-top-sm"> <view class="tn-padding-top-sm">
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin" @click="tn('')"> <view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin" @click="tn('')">
<view class="justify-content-item tn-text-bold tn-text-lg tn-padding-left-xs"> <view class="justify-content-item tn-text-bold tn-text-lg tn-padding-left-xs">
{{ t('taskSectionTitle') }} {{ $tr('做任务领金币') }}
</view> </view>
<view class="justify-content-item tn-text-df" style="color: #C1CACF;"> <view class="justify-content-item tn-text-df" style="color: #C1CACF;">
<text class="tn-padding-xs">{{ t('more') }}</text> <text class="tn-padding-xs">{{ $tr('更多') }}</text>
<text class="tn-icon-right"></text> <text class="tn-icon-right"></text>
</view> </view>
</view> </view>
@@ -130,7 +130,7 @@
</view> </view>
<view class="justify-content-item"> <view class="justify-content-item">
<tn-button shape="round" size="sm" backgroundColor="$brand-primary" fontColor="#FFFFFF" padding="30rpx 0" width="160rpx"> <tn-button shape="round" size="sm" backgroundColor="$brand-primary" fontColor="#FFFFFF" padding="30rpx 0" width="160rpx">
<text class="tn-text-bold tn-text-sm">{{ t('goComplete') }}</text> <text class="tn-text-bold tn-text-sm">{{ $tr('去完成') }}</text>
<text class="tn-icon-right-fill tn-padding-left-xs tn-text-lg"></text> <text class="tn-icon-right-fill tn-padding-left-xs tn-text-lg"></text>
</tn-button> </tn-button>
</view> </view>
@@ -156,19 +156,19 @@
<image src="https://cdn.nlark.com/yuque/0/2024/png/280373/1719936460577-assets/web-upload/697ed6f1-6126-4734-bd80-bed28296e16d.png" mode="aspectFit" style="width: 320rpx;height: 330rpx;"></image> <image src="https://cdn.nlark.com/yuque/0/2024/png/280373/1719936460577-assets/web-upload/697ed6f1-6126-4734-bd80-bed28296e16d.png" mode="aspectFit" style="width: 320rpx;height: 330rpx;"></image>
</view> </view>
<view class="tn-text-xl tn-text-bold" style="padding-top: 120rpx;"> <view class="tn-text-xl tn-text-bold" style="padding-top: 120rpx;">
{{ t('checkInSuccess') }} {{ $tr('签到成功') }}
</view> </view>
<view class="tn-padding tn-color-gray--dark"> <view class="tn-padding tn-color-gray--dark">
<text>{{ t('checkedInFor') }}</text> <text>{{ $tr('已连续签到') }}</text>
<text class="tn-color-orangered" style="padding-left: 6rpx;padding-right: 6rpx">3</text> <text class="tn-color-orangered" style="padding-left: 6rpx;padding-right: 6rpx">3</text>
<text>{{ t('daysAndEarned') }}</text> <text>{{ $tr('天,获得') }}</text>
<text class="tn-color-orangered" style="padding-left: 6rpx;padding-right: 6rpx">20</text> <text class="tn-color-orangered" style="padding-left: 6rpx;padding-right: 6rpx">20</text>
<text>{{ t('pointsUnit') }}</text> <text>{{ $tr('积分') }}</text>
</view> </view>
<view class=" tn-padding tn-color-gray"> <view class=" tn-padding tn-color-gray">
<text>{{ t('continuousCheckIn') }}</text> <text>{{ $tr('连续签到') }}</text>
<text class="tn-color-orangered" style="padding-left: 6rpx;padding-right: 6rpx;">7</text> <text class="tn-color-orangered" style="padding-left: 6rpx;padding-right: 6rpx;">7</text>
<text>{{ t('daysRewardHint') }}</text> <text>{{ $tr('天可获得额外奖励哦!') }}</text>
</view> </view>
</view> </view>
@@ -186,50 +186,6 @@
export default { export default {
name: 'Sign', name: 'Sign',
mixins: [template_page_mixin], mixins: [template_page_mixin],
i18n: {
zh: {
pointsUnit: '积分',
pointsRecord: '积分记录',
checkInNow: '立即签到',
checkInRewardHint: '坚持每天连续签到可以获得多重奖励哦',
warmTip: '温馨提示:',
warmTipDesc: '每日签到可获得10积分奖励,连续完成7天签到可能获得额外奖励,连续签到满7天后将会重置签到记录。',
taskSectionTitle: '做任务领金币',
more: '更多',
goComplete: '去完成',
checkInSuccess: '签到成功',
checkedInFor: '已连续签到',
daysAndEarned: '天,获得',
continuousCheckIn: '连续签到',
daysRewardHint: '天可获得额外奖励哦!',
notice1: '恭喜 付*其 连续7天签到,获得 300积分 奖励',
notice2: '恭喜 徐*圆 连续7天签到,获得 300积分 奖励',
notice3: '恭喜 蔡*东 连续7天签到,获得 300积分 奖励',
taskName: '任务名称',
taskDesc: '抓住那只猪吖'
},
en: {
pointsUnit: 'Points',
pointsRecord: 'Points History',
checkInNow: 'Check In Now',
checkInRewardHint: 'Daily continuous check-ins unlock multiple rewards',
warmTip: 'Tips:',
warmTipDesc: 'Daily check-in grants 10 points. Completing 7 consecutive days may unlock extra rewards, and the record resets after day 7.',
taskSectionTitle: 'Complete Tasks for Coins',
more: 'More',
goComplete: 'Go',
checkInSuccess: 'Check-in Successful',
checkedInFor: 'Checked in for',
daysAndEarned: 'days and earned',
continuousCheckIn: 'Continuous check-in',
daysRewardHint: 'days unlocks extra rewards!',
notice1: 'Congrats to Fu*qi for 7 consecutive check-ins and earning 300 points',
notice2: 'Congrats to Xu*yuan for 7 consecutive check-ins and earning 300 points',
notice3: 'Congrats to Cai*dong for 7 consecutive check-ins and earning 300 points',
taskName: 'Task Name',
taskDesc: 'Catch That Pig'
}
},
data(){ data(){
return { return {
show: true, show: true,
@@ -238,19 +194,16 @@
}, },
computed: { computed: {
noticeList() { noticeList() {
return [this.t('notice1'), this.t('notice2'), this.t('notice3')] return [this.$tr('恭喜 付*其 连续7天签到,获得 300积分 奖励'), this.$tr('恭喜 徐*圆 连续7天签到,获得 300积分 奖励'), this.$tr('恭喜 蔡*东 连续7天签到,获得 300积分 奖励')]
}, },
taskList() { taskList() {
return Array.from({ length: 6 }).map(() => ({ return Array.from({ length: 6 }).map(() => ({
title: this.t('taskName'), title: this.$tr('任务名称'),
desc: this.t('taskDesc') desc: this.$tr('抓住那只猪吖')
})) }))
} }
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgExplore.sign.sign', key, params)
},
// 跳转 // 跳转
tn(e) { tn(e) {
uni.navigateTo({ uni.navigateTo({

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="section-panel" v-if="facilityList && facilityList.length"> <view class="section-panel" v-if="facilityList && facilityList.length">
<view class="section-header"> <view class="section-header">
<text class="section-title">{{ t('sectionTitle') }}</text> <text class="section-title">{{ $tr('配套设施') }}</text>
</view> </view>
<view class="facility-grid tn-flex tn-flex-wrap"> <view class="facility-grid tn-flex tn-flex-wrap">
<view class="facility-item tn-flex tn-flex-direction-column tn-flex-col-center" v-for="(item, index) in facilityList" :key="index"> <view class="facility-item tn-flex tn-flex-direction-column tn-flex-col-center" v-for="(item, index) in facilityList" :key="index">
@@ -24,11 +24,7 @@ export default {
default: 'zh' default: 'zh'
} }
}, },
methods: { methods: {}
t(key) {
return this.$tm('pkgExplore.store_detail.store_detail', key)
}
}
} }
</script> </script>

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="section-panel room-section"> <view class="section-panel room-section">
<view class="section-header tn-flex tn-flex-row-between tn-flex-col-center"> <view class="section-header tn-flex tn-flex-row-between tn-flex-col-center">
<text class="section-title">{{ t('roomSectionTitle', { count: roomTypes.length }) }}</text> <text class="section-title">{{ $tr('在租房型({count})', { count: roomTypes.length }) }}</text>
</view> </view>
<view class="room-list-vertical"> <view class="room-list-vertical">
<view <view
@@ -28,11 +28,11 @@
<view class="room-bottom-v tn-flex tn-flex-row-between tn-flex-col-bottom"> <view class="room-bottom-v tn-flex tn-flex-row-between tn-flex-col-bottom">
<view class="room-price-v"> <view class="room-price-v">
<text class="price-symbol">¥</text> <text class="price-symbol">¥</text>
<text class="price-val">{{ formatPriceValue(room.price) || t('pricePending') }}</text> <text class="price-val">{{ formatPriceValue(room.price) || $tr('待定') }}</text>
<text class="price-unit">{{ t('priceUnit') }}</text> <text class="price-unit">{{ $tr('/月') }}</text>
</view> </view>
<view class="go-btn-v tn-flex tn-flex-col-center tn-flex-row-center"> <view class="go-btn-v tn-flex tn-flex-col-center tn-flex-row-center">
<text>{{ t('viewDetails') }}</text> <text>{{ $tr('查看详情') }}</text>
<text class="tn-icon-right"></text> <text class="tn-icon-right"></text>
</view> </view>
</view> </view>
@@ -65,9 +65,6 @@ export default {
} }
}, },
methods: { methods: {
t(key, params = {}) {
return this.$tm('pkgExplore.store_detail.store_detail', key, params)
},
formatPriceValue(priceText) { formatPriceValue(priceText) {
if (!priceText) { if (!priceText) {
return '' return ''

View File

@@ -9,7 +9,7 @@
<text class="address-title tn-text-ellipsis">{{ shortAddress }}</text> <text class="address-title tn-text-ellipsis">{{ shortAddress }}</text>
</view> </view>
<view class="address-right tn-flex tn-flex-col-center"> <view class="address-right tn-flex tn-flex-col-center">
<text class="nav-text">{{ t('navigate') }}</text> <text class="nav-text">{{ $tr('导航') }}</text>
<text class="tn-icon-right nav-arrow"></text> <text class="tn-icon-right nav-arrow"></text>
</view> </view>
</view> </view>
@@ -17,7 +17,7 @@
<!-- 小区简介 --> <!-- 小区简介 -->
<view class="section-panel" v-if="displayDescription"> <view class="section-panel" v-if="displayDescription">
<view class="section-header"> <view class="section-header">
<text class="section-title">{{ t('storeIntro') }}</text> <text class="section-title">{{ $tr('小区简介') }}</text>
</view> </view>
<view <view
class="store-desc" class="store-desc"
@@ -29,7 +29,7 @@
<text v-else>{{ displayDescription }}</text> <text v-else>{{ displayDescription }}</text>
</view> </view>
<view v-if="shouldShowToggle" class="expand-btn tn-flex tn-flex-row-center tn-flex-col-center" @click="toggleDesc"> <view v-if="shouldShowToggle" class="expand-btn tn-flex tn-flex-row-center tn-flex-col-center" @click="toggleDesc">
<text>{{ isDescCollapsed ? t('expandAll') : t('collapse') }}</text> <text>{{ isDescCollapsed ? $tr('展开全部') : $tr('收起') }}</text>
<text :class="isDescCollapsed ? 'tn-icon-down' : 'tn-icon-up'" class="expand-icon"></text> <text :class="isDescCollapsed ? 'tn-icon-down' : 'tn-icon-up'" class="expand-icon"></text>
</view> </view>
</view> </view>
@@ -104,19 +104,16 @@ export default {
? (this.storeData.addressEnDisplay || this.storeData.addressEn || this.storeData.address || '') ? (this.storeData.addressEnDisplay || this.storeData.addressEn || this.storeData.address || '')
: (this.storeData.address || this.storeData.addressEn || '') : (this.storeData.address || this.storeData.addressEn || '')
if (!addr) { if (!addr) {
return this.t('noAddress') return this.$tr('暂无地址')
} }
if (this.language === 'en') { if (this.language === 'en') {
return addr return addr
} }
const short = addr.replace(/^(?:.*?省|.*?自治区)?(?:.*?市|.*?自治州)?/, ''); const short = addr.replace(/^(?:.*?省|.*?自治区)?(?:.*?市|.*?自治州)?/, '');
return short.trim() ? short : (addr || this.t('noAddress')); return short.trim() ? short : (addr || this.$tr('暂无地址'));
} }
}, },
methods: { methods: {
t(key) {
return this.$tm('pkgExplore.store_detail.store_detail', key)
},
toggleDesc() { toggleDesc() {
this.isDescCollapsed = !this.isDescCollapsed; this.isDescCollapsed = !this.isDescCollapsed;
}, },

View File

@@ -15,7 +15,7 @@
<image class="bg-image" :src="image" mode="aspectFill"></image> <image class="bg-image" :src="image" mode="aspectFill"></image>
</swiper-item> </swiper-item>
</swiper> </swiper>
<view v-else class="bg-empty tn-flex tn-flex-col-center tn-flex-row-center">{{ t('noImage') }}</view> <view v-else class="bg-empty tn-flex tn-flex-col-center tn-flex-row-center">{{ $tr('暂无图片') }}</view>
<!-- 渐变暗化遮罩,确保上层文字卡片清晰 --> <!-- 渐变暗化遮罩,确保上层文字卡片清晰 -->
<view class="bg-mask"></view> <view class="bg-mask"></view>
</view> </view>
@@ -88,7 +88,7 @@
<view class="action-bar"> <view class="action-bar">
<button class="action-btn action-btn--glass" open-type="share"> <button class="action-btn action-btn--glass" open-type="share">
<text class="tn-icon-share action-btn__icon"></text> <text class="tn-icon-share action-btn__icon"></text>
<text>{{ t('share') }}</text> <text>{{ $tr('分享') }}</text>
</button> </button>
<button class="action-btn action-btn--primary-glass" @click="handleCall"> <button class="action-btn action-btn--primary-glass" @click="handleCall">
<text class="tn-icon-phone action-btn__icon"></text> <text class="tn-icon-phone action-btn__icon"></text>
@@ -165,9 +165,9 @@ export default {
}, },
displayStoreName() { displayStoreName() {
if (this.language === 'en') { if (this.language === 'en') {
return this.storeData.nameEnDisplay || this.storeData.nameEn || this.storeData.name || this.t('apartmentName') return this.storeData.nameEnDisplay || this.storeData.nameEn || this.storeData.name || this.$tr('公寓详情')
} }
return this.storeData.name || this.storeData.nameEn || this.t('apartmentName') return this.storeData.name || this.storeData.nameEn || this.$tr('公寓详情')
}, },
displayStoreAddress() { displayStoreAddress() {
if (this.language === 'en') { if (this.language === 'en') {
@@ -214,15 +214,15 @@ export default {
}, },
contactButtonText() { contactButtonText() {
if (this.isAreaManagerLoading) { if (this.isAreaManagerLoading) {
return this.t('loading') return this.$tr('加载中...')
} }
if (this.areaManagerOptions.length > 1) { if (this.areaManagerOptions.length > 1) {
return this.t('selectContact') return this.$tr('选择联系人')
} }
if (this.areaManagerName) { if (this.areaManagerName) {
return this.t('contactManager') return this.$tr('联系管家')
} }
return this.t('phoneConsult') return this.$tr('电话咨询')
}, },
navBarBackStyle() { navBarBackStyle() {
const isDarkBg = this.scrollTop < 100; const isDarkBg = this.scrollTop < 100;
@@ -253,29 +253,26 @@ export default {
this.applyStoreData(data) this.applyStoreData(data)
}) })
.catch((e) => { .catch((e) => {
console.error(this.t('parseStoreDataFailed'), e); console.error(this.$tr('解析小区数据失败'), e);
}) })
return return
} }
}, },
onShareAppMessage() { onShareAppMessage() {
return { return {
title: this.displayStoreName || this.t('shareTitle'), title: this.displayStoreName || this.$tr('小区详情'),
path: `/pkgExplore/store_detail/store_detail?storeData=${encodeURIComponent(JSON.stringify(this.storeData || {}))}`, path: `/pkgExplore/store_detail/store_detail?storeData=${encodeURIComponent(JSON.stringify(this.storeData || {}))}`,
imageUrl: this.heroImage || '' imageUrl: this.heroImage || ''
} }
}, },
onShareTimeline() { onShareTimeline() {
return { return {
title: this.displayStoreName || this.t('shareTitle'), title: this.displayStoreName || this.$tr('小区详情'),
query: `storeData=${encodeURIComponent(JSON.stringify(this.storeData || {}))}`, query: `storeData=${encodeURIComponent(JSON.stringify(this.storeData || {}))}`,
imageUrl: this.heroImage || '' imageUrl: this.heroImage || ''
} }
}, },
methods: { methods: {
t(key) {
return this.$tm('pkgExplore.store_detail.store_detail', key)
},
applyStoreData(data) { applyStoreData(data) {
this.storeData = data && typeof data === 'object' ? data : {} this.storeData = data && typeof data === 'object' ? data : {}
this.roomTypes = this.storeData.roomTypes || [] this.roomTypes = this.storeData.roomTypes || []
@@ -333,16 +330,16 @@ export default {
}, },
getManagerActionText(manager) { getManagerActionText(manager) {
if (!manager) { if (!manager) {
return this.t('phoneConsult') return this.$tr('电话咨询')
} }
if (manager.displayName && manager.phone) { if (manager.displayName && manager.phone) {
return `${manager.displayName}(${manager.phone})` return `${manager.displayName}(${manager.phone})`
} }
return manager.displayName || manager.phone || this.t('phoneConsult') return manager.displayName || manager.phone || this.$tr('电话咨询')
}, },
makePhoneCall(phoneNumber) { makePhoneCall(phoneNumber) {
if (!phoneNumber) { if (!phoneNumber) {
uni.showToast({ title: this.t('noServicePhone'), icon: 'none' }) uni.showToast({ title: this.$tr('暂无联系电话'), icon: 'none' })
return return
} }
uni.makePhoneCall({ uni.makePhoneCall({
@@ -360,7 +357,7 @@ export default {
const longitude = parseCoordinate(this.storeData.longitude, this.storeData.lng) const longitude = parseCoordinate(this.storeData.longitude, this.storeData.lng)
if (latitude === null || longitude === null) { if (latitude === null || longitude === null) {
uni.showToast({ uni.showToast({
title: this.t('noLocation'), title: this.$tr('暂无位置信息'),
icon: 'none' icon: 'none'
}) })
return return
@@ -368,7 +365,7 @@ export default {
uni.openLocation({ uni.openLocation({
latitude, latitude,
longitude, longitude,
name: this.displayStoreName || this.t('apartmentName'), name: this.displayStoreName || this.$tr('公寓详情'),
address: this.displayStoreAddress || '', address: this.displayStoreAddress || '',
scale: 18 scale: 18
}); });
@@ -390,7 +387,7 @@ export default {
}, },
handleCall() { handleCall() {
if (this.isAreaManagerLoading && !this.contactPhoneNumber) { if (this.isAreaManagerLoading && !this.contactPhoneNumber) {
uni.showToast({ title: this.t('loadingManagerInfo'), icon: 'none' }) uni.showToast({ title: this.$tr('正在加载管家信息'), icon: 'none' })
return return
} }

View File

@@ -12,23 +12,23 @@
<view class="vr-stage"> <view class="vr-stage">
<image v-if="poster" :src="poster" mode="aspectFill" class="vr-poster"></image> <image v-if="poster" :src="poster" mode="aspectFill" class="vr-poster"></image>
<view v-else class="vr-empty tn-flex tn-flex-col-center tn-flex-row-center">{{ t('emptyPreview') }}</view> <view v-else class="vr-empty tn-flex tn-flex-col-center tn-flex-row-center">{{ $tr('暂无VR预览画面') }}</view>
<view class="vr-mask"></view> <view class="vr-mask"></view>
<view class="vr-mode-chip">{{ t('modeChip') }}</view> <view class="vr-mode-chip">{{ $tr('VR预览模式') }}</view>
<view class="vr-center-indicator" @click="openPosterPreview"> <view class="vr-center-indicator" @click="openPosterPreview">
<view class="indicator-ring"></view> <view class="indicator-ring"></view>
<view class="indicator-core"> <view class="indicator-core">
<text class="tn-icon-vr indicator-icon"></text> <text class="tn-icon-vr indicator-icon"></text>
</view> </view>
<text class="indicator-text">{{ poster ? t('viewFullscreen') : t('waitingAsset') }}</text> <text class="indicator-text">{{ poster ? $tr('查看全屏预览') : $tr('等待素材接入') }}</text>
</view> </view>
<view class="vr-bottom-bar tn-safe-area-inset-bottom"> <view class="vr-bottom-bar tn-safe-area-inset-bottom">
<view class="vr-title-area"> <view class="vr-title-area">
<view class="vr-title">{{ resolvedPageTitle }}</view> <view class="vr-title">{{ resolvedPageTitle }}</view>
<view class="vr-subtitle">{{ poster ? t('posterSubtitle') : t('placeholderSubtitle') }}</view> <view class="vr-subtitle">{{ poster ? $tr('当前展示 VR 预览海报,可点击查看大图。') : $tr('当前房型已保留 VR 入口,后续可继续接入更完整的沉浸式资源。') }}</view>
</view> </view>
<view class="vr-action-circle" @click="openPosterPreview"> <view class="vr-action-circle" @click="openPosterPreview">
@@ -41,28 +41,6 @@
<script> <script>
export default { export default {
i18n: {
zh: {
defaultTitle: 'VR看房',
emptyPreview: '暂无VR预览画面',
modeChip: 'VR预览模式',
viewFullscreen: '查看全屏预览',
waitingAsset: '等待素材接入',
posterSubtitle: '当前展示 VR 预览海报,可点击查看大图。',
placeholderSubtitle: '当前房型已保留 VR 入口,后续可继续接入更完整的沉浸式资源。',
noAsset: '暂无VR预览素材'
},
en: {
defaultTitle: 'VR Viewing',
emptyPreview: 'No VR preview available',
modeChip: 'VR Preview',
viewFullscreen: 'View Fullscreen',
waitingAsset: 'Waiting for assets',
posterSubtitle: 'The current VR preview poster is displayed. Tap to view the full image.',
placeholderSubtitle: 'This room type keeps its VR entry reserved for richer immersive assets later.',
noAsset: 'No VR preview asset available'
}
},
data() { data() {
return { return {
pageTitleZh: '', pageTitleZh: '',
@@ -74,9 +52,9 @@ export default {
computed: { computed: {
resolvedPageTitle() { resolvedPageTitle() {
if (this.$getLanguage() === 'en') { if (this.$getLanguage() === 'en') {
return this.pageTitleEn || this.pageTitleZh || this.rawPageTitle || this.t('defaultTitle') return this.pageTitleEn || this.pageTitleZh || this.rawPageTitle || this.$tr('VR看房')
} }
return this.pageTitleZh || this.pageTitleEn || this.rawPageTitle || this.t('defaultTitle') return this.pageTitleZh || this.pageTitleEn || this.rawPageTitle || this.$tr('VR看房')
}, },
navBarBackStyle() { navBarBackStyle() {
return { return {
@@ -112,13 +90,10 @@ export default {
} }
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgExplore.vr_preview.vr_preview', key, params)
},
openPosterPreview() { openPosterPreview() {
if (!this.poster) { if (!this.poster) {
uni.showToast({ uni.showToast({
title: this.t('noAsset'), title: this.$tr('暂无VR预览素材'),
icon: 'none' icon: 'none'
}) })
return return

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="application-detail-page tn-safe-area-inset-bottom"> <view class="application-detail-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('申请详情') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="page-main tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }"> <view class="page-main tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }">
@@ -35,54 +35,54 @@
<text :class="shouldPayDeposit(detail) ? 'tn-icon-warning-fill' : 'tn-icon-time-fill'"></text> <text :class="shouldPayDeposit(detail) ? 'tn-icon-warning-fill' : 'tn-icon-time-fill'"></text>
</view> </view>
<view class="status-reminder-panel__content"> <view class="status-reminder-panel__content">
<view class="status-reminder-panel__title">{{ t('statusReminderTitle') }}</view> <view class="status-reminder-panel__title">{{ $tr('当前进度提醒') }}</view>
<view class="status-reminder-panel__text">{{ statusReminderText }}</view> <view class="status-reminder-panel__text">{{ statusReminderText }}</view>
</view> </view>
</view> </view>
<view class="content-panel tn-margin-bottom-lg"> <view class="content-panel tn-margin-bottom-lg">
<view class="panel-title">{{ t('basicInfoTitle') }}</view> <view class="panel-title">{{ $tr('基础信息') }}</view>
<view class="meta-row tn-margin-top"> <view class="meta-row tn-margin-top">
<text class="meta-label">{{ t('applicationCodeLabel') }}</text> <text class="meta-label">{{ $tr('申请编号') }}</text>
<view class="meta-action-wrap"> <view class="meta-action-wrap">
<text class="meta-value">{{ detail.detailCode || '--' }}</text> <text class="meta-value">{{ detail.detailCode || '--' }}</text>
<view v-if="detail.detailCode" class="copy-btn" @click="copyCode"> <view v-if="detail.detailCode" class="copy-btn" @click="copyCode">
<text class="tn-text-xs tn-text-bold">{{ t('copy') }}</text> <text class="tn-text-xs tn-text-bold">{{ $tr('复制') }}</text>
</view> </view>
</view> </view>
</view> </view>
<view class="meta-row"> <view class="meta-row">
<text class="meta-label">{{ t('applicationTypeLabel') }}</text> <text class="meta-label">{{ $tr('申请类型') }}</text>
<text class="meta-value">{{ applicationTypeText }}</text> <text class="meta-value">{{ applicationTypeText }}</text>
</view> </view>
<view class="meta-row"> <view class="meta-row">
<text class="meta-label">{{ t('statusLabel') }}</text> <text class="meta-label">{{ $tr('当前状态') }}</text>
<text class="meta-value">{{ statusText }}</text> <text class="meta-value">{{ statusText }}</text>
</view> </view>
<view class="meta-row"> <view class="meta-row">
<text class="meta-label">{{ t('applicantLabel') }}</text> <text class="meta-label">{{ $tr('申请人') }}</text>
<text class="meta-value">{{ detail.userName || detail.phone || '--' }}</text> <text class="meta-value">{{ detail.userName || detail.phone || '--' }}</text>
</view> </view>
<view class="meta-row"> <view class="meta-row">
<text class="meta-label">{{ t('apartmentLabel') }}</text> <text class="meta-label">{{ $tr('所属公寓') }}</text>
<text class="meta-value">{{ detail.intendedAreaName || '--' }}</text> <text class="meta-value">{{ detail.intendedAreaName || '--' }}</text>
</view> </view>
<view class="meta-row" v-if="detail.intendedRoomTypeName"> <view class="meta-row" v-if="detail.intendedRoomTypeName">
<text class="meta-label">{{ isViewingApplication ? t('intendedRoomTypeLabel') : t('appliedRoomTypeLabel') }}</text> <text class="meta-label">{{ isViewingApplication ? $tr('意向房型') : $tr('申请房型') }}</text>
<text class="meta-value">{{ detail.intendedRoomTypeName }}</text> <text class="meta-value">{{ detail.intendedRoomTypeName }}</text>
</view> </view>
<view class="meta-row"> <view class="meta-row">
<text class="meta-label">{{ isViewingApplication ? t('viewingTimeLabel') : t('leaseTermLabel') }}</text> <text class="meta-label">{{ isViewingApplication ? $tr('预约时间') : $tr('预计租期') }}</text>
<text class="meta-value">{{ displayDateRange }}</text> <text class="meta-value">{{ displayDateRange }}</text>
</view> </view>
<view class="meta-row"> <view class="meta-row">
<text class="meta-label">{{ t('submittedAtLabel') }}</text> <text class="meta-label">{{ $tr('提交时间') }}</text>
<text class="meta-value">{{ formatDateTime(detail.createTime) }}</text> <text class="meta-value">{{ formatDateTime(detail.createTime) }}</text>
</view> </view>
</view> </view>
<view class="content-panel tn-margin-bottom-lg" v-if="viewingMeta.length"> <view class="content-panel tn-margin-bottom-lg" v-if="viewingMeta.length">
<view class="panel-title">{{ t('viewingInfoTitle') }}</view> <view class="panel-title">{{ $tr('带看信息') }}</view>
<view <view
v-for="item in viewingMeta" v-for="item in viewingMeta"
:key="item.label" :key="item.label"
@@ -94,7 +94,7 @@
</view> </view>
<view class="content-panel tn-margin-bottom-lg"> <view class="content-panel tn-margin-bottom-lg">
<view class="panel-title">{{ isViewingApplication ? t('applicationRemarkTitle') : t('remarkTitle') }}</view> <view class="panel-title">{{ isViewingApplication ? $tr('申请说明') : $tr('补充说明') }}</view>
<view class="remark-panel tn-margin-top"> <view class="remark-panel tn-margin-top">
<view <view
v-for="(line, index) in remarkLines" v-for="(line, index) in remarkLines"
@@ -108,7 +108,7 @@
<view class="action-row"> <view class="action-row">
<view class="ghost-btn" @click="goBackList"> <view class="ghost-btn" @click="goBackList">
<text class="tn-text-bold">{{ t('backList') }}</text> <text class="tn-text-bold">{{ $tr('返回列表') }}</text>
</view> </view>
<view <view
v-if="canCancelOrder" v-if="canCancelOrder"
@@ -116,7 +116,7 @@
:class="{ 'danger-btn--loading': canceling }" :class="{ 'danger-btn--loading': canceling }"
@click.stop="handleCancelOrder" @click.stop="handleCancelOrder"
> >
<text class="tn-text-bold">{{ canceling ? t('cancelOrderLoading') : t('cancelOrder') }}</text> <text class="tn-text-bold">{{ canceling ? $tr('取消中...') : $tr('取消订单') }}</text>
</view> </view>
<view <view
v-if="currentStepActionText" v-if="currentStepActionText"
@@ -130,14 +130,14 @@
class="action-btn action-btn--ghost" class="action-btn action-btn--ghost"
@click.stop="refreshCurrentPage" @click.stop="refreshCurrentPage"
> >
<text class="tn-text-bold">{{ t('refreshAction') }}</text> <text class="tn-text-bold">{{ $tr('刷新') }}</text>
</view> </view>
<view <view
v-else-if="canViewBill" v-else-if="canViewBill"
class="action-btn" class="action-btn"
@click.stop="goToBillPage" @click.stop="goToBillPage"
> >
<text class="tn-text-bold">{{ t('viewBill') }}</text> <text class="tn-text-bold">{{ $tr('查看关联账单') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -221,112 +221,6 @@ function parseJsonStringMaybe(value) {
} }
export default { export default {
i18n: {
zh: {
navTitle: '申请详情',
basicInfoTitle: '基础信息',
applicationCodeLabel: '申请编号',
copy: '复制',
applicationTypeLabel: '申请类型',
statusLabel: '当前状态',
applicantLabel: '申请人',
apartmentLabel: '所属公寓',
intendedRoomTypeLabel: '意向房型',
appliedRoomTypeLabel: '申请房型',
viewingTimeLabel: '预约时间',
leaseTermLabel: '预计租期',
submittedAtLabel: '提交时间',
viewingInfoTitle: '带看信息',
applicationRemarkTitle: '申请说明',
remarkTitle: '补充说明',
backList: '返回列表',
viewBill: '查看关联账单',
viewingApplicationTitle: '预约看房申请',
stayApplicationTitle: '入住申请',
viewingApplicationType: '预约看房',
stayApplicationType: '入住申请',
statusPending: '待审核',
statusApproved: '已通过',
statusRejected: '已拒绝',
statusFinished: '已结束',
statusProcessing: '处理中',
statusPendingPayment: '请支付定金',
statusWaitingRoomAssign: '等待预订中心分配房间',
statusReminderTitle: '当前进度提醒',
emptyRemark: '暂无补充说明',
codeCopied: '申请编号已复制',
missingBillCode: '申请单编号缺失',
missingContractNo: '合同编号缺失',
checkinPendingHint: '宿管正在办理入住,请耐心等待',
refreshAction: '刷新',
cancelOrder: '取消订单',
cancelOrderLoading: '取消中...',
cancelOrderTitle: '取消订单',
cancelOrderContent: '取消后将关闭当前未支付的定金订单,是否继续?',
cancelOrderSuccess: '订单已取消',
cancelOrderFailed: '取消订单失败',
viewingTimeKey: '看房时间',
leaseDurationKey: '预计租住时长',
budgetKey: '租金预算',
commuteKey: '工作/通勤地点',
preferredAreaKey: '意向区域',
preferredRoomTypeKey: '意向房型',
paymentMethodKey: '支付方式',
dateRangeJoiner: ' 至 '
},
en: {
navTitle: 'Application Detail',
basicInfoTitle: 'Basic Information',
applicationCodeLabel: 'Application No.',
copy: 'Copy',
applicationTypeLabel: 'Application Type',
statusLabel: 'Status',
applicantLabel: 'Applicant',
apartmentLabel: 'Apartment',
intendedRoomTypeLabel: 'Preferred Room Type',
appliedRoomTypeLabel: 'Requested Room Type',
viewingTimeLabel: 'Viewing Time',
leaseTermLabel: 'Lease Term',
submittedAtLabel: 'Submitted At',
viewingInfoTitle: 'Viewing Details',
applicationRemarkTitle: 'Application Notes',
remarkTitle: 'Remarks',
backList: 'Back to List',
viewBill: 'View Related Bill',
viewingApplicationTitle: 'Viewing Request',
stayApplicationTitle: 'Stay Application',
viewingApplicationType: 'Viewing Request',
stayApplicationType: 'Stay Application',
statusPending: 'Pending',
statusApproved: 'Approved',
statusRejected: 'Rejected',
statusFinished: 'Closed',
statusProcessing: 'Processing',
statusPendingPayment: 'Please Pay Deposit',
statusWaitingRoomAssign: 'Waiting for Room Assignment',
statusReminderTitle: 'Current Progress',
emptyRemark: 'No additional remarks',
codeCopied: 'Application number copied',
missingBillCode: 'Missing application bill number',
missingContractNo: 'Missing contract number',
checkinPendingHint: 'Move-in is being processed, please wait',
refreshAction: 'Refresh',
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',
viewingTimeKey: 'Viewing Time',
leaseDurationKey: 'Expected Stay Duration',
budgetKey: 'Budget',
commuteKey: 'Work / Commute Location',
preferredAreaKey: 'Preferred Area',
preferredRoomTypeKey: 'Preferred Room Type',
paymentMethodKey: 'Payment Method',
dateRangeJoiner: ' to '
}
},
data() { data() {
return { return {
detailCode: '', detailCode: '',
@@ -347,24 +241,24 @@ export default {
}, },
displayTitle() { displayTitle() {
if (this.isViewingApplication) { if (this.isViewingApplication) {
return this.detail.intendedRoomTypeName || this.t('viewingApplicationTitle') return this.detail.intendedRoomTypeName || this.$tr('预约看房申请')
} }
return this.detail.intendedRoomTypeName || this.t('stayApplicationTitle') return this.detail.intendedRoomTypeName || this.$tr('入住申请')
}, },
applicationTypeText() { applicationTypeText() {
return this.isViewingApplication ? this.t('viewingApplicationType') : this.t('stayApplicationType') return this.isViewingApplication ? this.$tr('预约看房') : this.$tr('入住申请')
}, },
statusText() { statusText() {
if (this.language !== 'en' && this.detail && this.detail.statusDesc) { if (this.language !== 'en' && this.detail && this.detail.statusDesc) {
return this.detail.statusDesc return this.detail.statusDesc
} }
const map = { const map = {
0: this.t('statusPending'), 0: this.$tr('待审核'),
10: this.t('statusApproved'), 10: this.$tr('已通过'),
20: this.t('statusRejected'), 20: this.$tr('已拒绝'),
99: this.t('statusFinished') 99: this.$tr('已结束')
} }
return map[this.detail.status] || this.t('statusProcessing') return map[this.detail.status] || this.$tr('处理中')
}, },
statusType() { statusType() {
if (this.detail.status === 10) { if (this.detail.status === 10) {
@@ -389,14 +283,14 @@ export default {
return '--' return '--'
} }
if (this.isViewingApplication) { if (this.isViewingApplication) {
return `${this.formatDateTime(this.detail.start)}${this.t('dateRangeJoiner')}${this.formatDateTime(this.detail.end)}` return `${this.formatDateTime(this.detail.start)}${this.$tr(' 至 ')}${this.formatDateTime(this.detail.end)}`
} }
return `${this.formatDate(this.detail.start)}${this.t('dateRangeJoiner')}${this.formatDate(this.detail.end)}` return `${this.formatDate(this.detail.start)}${this.$tr(' 至 ')}${this.formatDate(this.detail.end)}`
}, },
remarkLines() { remarkLines() {
const text = this.detail && this.detail.remark ? String(this.detail.remark).trim() : '' const text = this.detail && this.detail.remark ? String(this.detail.remark).trim() : ''
if (!text) { if (!text) {
return [this.t('emptyRemark')] return [this.$tr('暂无补充说明')]
} }
return text.split(/\r?\n/).filter(Boolean) return text.split(/\r?\n/).filter(Boolean)
}, },
@@ -429,17 +323,17 @@ export default {
return this.getReservationContractStatusText() return this.getReservationContractStatusText()
} }
if (this.isReservationCheckinProcessing()) { if (this.isReservationCheckinProcessing()) {
return this.t('checkinPendingHint') return this.$tr('宿管正在办理入住,请耐心等待')
} }
const reservationStepText = getReservationCurrentStepText(this.detail, this.language) const reservationStepText = getReservationCurrentStepText(this.detail, this.language)
if (reservationStepText) { if (reservationStepText) {
return reservationStepText return reservationStepText
} }
if (this.shouldPayDeposit(this.detail)) { if (this.shouldPayDeposit(this.detail)) {
return this.t('statusPendingPayment') return this.$tr('请支付定金')
} }
if (this.isWaitingRoomAssignment(this.detail)) { if (this.isWaitingRoomAssignment(this.detail)) {
return this.t('statusWaitingRoomAssign') return this.$tr('等待预订中心分配房间')
} }
return '' return ''
}, },
@@ -466,9 +360,6 @@ export default {
this.loadReservationFlow() this.loadReservationFlow()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.application_detail.application_detail', key, params)
},
isPaidApplication(item = {}) { isPaidApplication(item = {}) {
return !item.isViewingApplication && Number(item.payStatus) === 10 return !item.isViewingApplication && Number(item.payStatus) === 10
}, },
@@ -530,7 +421,7 @@ export default {
return Number(detail.status) === 2 return Number(detail.status) === 2
}, },
getReservationInspectionStatusText() { getReservationInspectionStatusText() {
return this.t('inspectionPendingReviewHint') return this.$tr('inspectionPendingReviewHint')
}, },
isReservationContractAwaitingReview() { isReservationContractAwaitingReview() {
if (this.getReservationCurrentStepValue() !== RESERVATION_FLOW_STEP.CONTRACT) { if (this.getReservationCurrentStepValue() !== RESERVATION_FLOW_STEP.CONTRACT) {
@@ -663,13 +554,13 @@ export default {
return null return null
} }
const localizedLabelMap = { const localizedLabelMap = {
'看房时间': this.t('viewingTimeKey'), '看房时间': this.$tr('看房时间'),
'预计租住时长': this.t('leaseDurationKey'), '预计租住时长': this.$tr('预计租住时长'),
'租金预算': this.t('budgetKey'), '租金预算': this.$tr('租金预算'),
'工作/通勤地点': this.t('commuteKey'), '工作/通勤地点': this.$tr('工作/通勤地点'),
'意向区域': this.t('preferredAreaKey'), '意向区域': this.$tr('意向区域'),
'意向房型': this.t('preferredRoomTypeKey'), '意向房型': this.$tr('意向房型'),
'支付方式': this.t('paymentMethodKey') '支付方式': this.$tr('支付方式')
} }
const rawValue = line const rawValue = line
.replace(`${matched.zh}:`, '') .replace(`${matched.zh}:`, '')
@@ -704,7 +595,7 @@ export default {
data: String(this.detail.detailCode), data: String(this.detail.detailCode),
success: () => { success: () => {
uni.showToast({ uni.showToast({
title: this.t('codeCopied'), title: this.$tr('申请编号已复制'),
icon: 'none' icon: 'none'
}) })
} }
@@ -713,8 +604,8 @@ export default {
confirmCancelOrder() { confirmCancelOrder() {
return new Promise((resolve) => { return new Promise((resolve) => {
uni.showModal({ uni.showModal({
title: this.t('cancelOrderTitle'), title: this.$tr('取消订单'),
content: this.t('cancelOrderContent'), content: this.$tr('取消后将关闭当前未支付的定金订单,是否继续?'),
confirmColor: '#ef4444', confirmColor: '#ef4444',
success: (res) => resolve(!!(res && res.confirm)), success: (res) => resolve(!!(res && res.confirm)),
fail: () => resolve(false) fail: () => resolve(false)
@@ -733,7 +624,7 @@ export default {
return { return {
...item, ...item,
status: 99, status: 99,
statusDesc: this.t('statusFinished') statusDesc: this.$tr('已结束')
} }
}) })
saveMyApplicationList(nextList) saveMyApplicationList(nextList)
@@ -757,18 +648,18 @@ export default {
this.detail = { this.detail = {
...this.detail, ...this.detail,
status: 99, status: 99,
statusDesc: this.t('statusFinished') statusDesc: this.$tr('已结束')
} }
this.syncCanceledDetailToCache() this.syncCanceledDetailToCache()
uni.showToast({ uni.showToast({
title: this.t('cancelOrderSuccess'), title: this.$tr('订单已取消'),
icon: 'success' icon: 'success'
}) })
}) })
}) })
.catch((error) => { .catch((error) => {
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('cancelOrderFailed'), title: (error && (error.message || error.msg)) || this.$tr('取消订单失败'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -782,7 +673,7 @@ export default {
goToBillPage() { goToBillPage() {
if (!this.detail.detailCode) { if (!this.detail.detailCode) {
uni.showToast({ uni.showToast({
title: this.t('missingBillCode'), title: this.$tr('申请单编号缺失'),
icon: 'none' icon: 'none'
}) })
return return
@@ -810,7 +701,7 @@ export default {
const contractNo = String(this.detail && this.detail.reservationCurrentNode && this.detail.reservationCurrentNode.refNo || '').trim() const contractNo = String(this.detail && this.detail.reservationCurrentNode && this.detail.reservationCurrentNode.refNo || '').trim()
if (!contractNo) { if (!contractNo) {
uni.showToast({ uni.showToast({
title: this.t('missingContractNo'), title: this.$tr('合同编号缺失'),
icon: 'none' icon: 'none'
}) })
return return

View File

@@ -1,24 +1,24 @@
<template> <template>
<view class="bill-panel"> <view class="bill-panel">
<view class="hero-panel tn-margin-bottom-lg"> <view class="hero-panel tn-margin-bottom-lg">
<view class="hero-title tn-margin-top">{{ t('heroTitle') }}</view> <view class="hero-title tn-margin-top">{{ $tr('待支付账单') }}</view>
<view class="hero-desc tn-margin-top-sm">{{ heroDesc }}</view> <view class="hero-desc tn-margin-top-sm">{{ heroDesc }}</view>
<view v-if="currentReferenceTypeNo" class="source-tag tn-margin-top-sm">{{ t('applicationNoPrefix') }}{{ currentReferenceTypeNo }}</view> <view v-if="currentReferenceTypeNo" class="source-tag tn-margin-top-sm">{{ $tr('申请单号:') }}{{ currentReferenceTypeNo }}</view>
<view class="hero-summary tn-margin-top-lg"> <view class="hero-summary tn-margin-top-lg">
<view class="summary-card"> <view class="summary-card">
<view class="summary-label">{{ t('pendingCountLabel') }}</view> <view class="summary-label">{{ $tr('待支付数量') }}</view>
<view class="summary-value tn-margin-top-sm">{{ billList.length }}</view> <view class="summary-value tn-margin-top-sm">{{ billList.length }}</view>
</view> </view>
<view class="summary-card"> <view class="summary-card">
<view class="summary-label">{{ t('pendingAmountLabel') }}</view> <view class="summary-label">{{ $tr('待支付总额') }}</view>
<view class="summary-value tn-margin-top-sm">¥{{ formatMoney(totalRemainingAmount) }}</view> <view class="summary-value tn-margin-top-sm">¥{{ formatMoney(totalRemainingAmount) }}</view>
</view> </view>
</view> </view>
</view> </view>
<view v-if="loading || resolvingReference" class="empty-panel tn-padding-lg"> <view v-if="loading || resolvingReference" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('loadingTitle') }}</view> <view class="empty-title">{{ $tr('账单加载中') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('loadingDesc') }}</view> <view class="empty-desc tn-margin-top-sm">{{ $tr('正在同步您的待支付账单,请稍候。') }}</view>
</view> </view>
<view v-else-if="billList.length"> <view v-else-if="billList.length">
@@ -39,64 +39,64 @@
<view class="card-body tn-margin-top"> <view class="card-body tn-margin-top">
<view class="info-row tn-flex tn-flex-row-between"> <view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('relatedBusinessNoLabel') }}</text> <text class="info-label">{{ $tr('关联业务编号') }}</text>
<text class="info-value">{{ getBillReferenceTypeNo(item) || '--' }}</text> <text class="info-value">{{ getBillReferenceTypeNo(item) || '--' }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('totalChargeLabel') }}</text> <text class="info-label">{{ $tr('应收合计') }}</text>
<text class="info-value">¥{{ formatMoney(getBillTotalCharge(item)) }}</text> <text class="info-value">¥{{ formatMoney(getBillTotalCharge(item)) }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('paidAmountLabel') }}</text> <text class="info-label">{{ $tr('已付金额') }}</text>
<text class="info-value">¥{{ formatMoney(getBillPaidAmount(item)) }}</text> <text class="info-value">¥{{ formatMoney(getBillPaidAmount(item)) }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('remainingAmountLabel') }}</text> <text class="info-label">{{ $tr('待付金额') }}</text>
<text class="info-value info-value--highlight">¥{{ formatMoney(getBillRemainingAmount(item)) }}</text> <text class="info-value info-value--highlight">¥{{ formatMoney(getBillRemainingAmount(item)) }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('refundStatusLabel') }}</text> <text class="info-label">{{ $tr('退款状态') }}</text>
<text class="info-value">{{ getRefundStatusText(getBillRefundStatus(item)) }}</text> <text class="info-value">{{ getRefundStatusText(getBillRefundStatus(item)) }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('createTimeLabel') }}</text> <text class="info-label">{{ $tr('创建时间') }}</text>
<text class="info-value">{{ formatDateTime(getBillCreateTime(item)) }}</text> <text class="info-value">{{ formatDateTime(getBillCreateTime(item)) }}</text>
</view> </view>
</view> </view>
<view v-if="getBillRemark(item) || getBillResult(item)" class="remark-panel tn-margin-top"> <view v-if="getBillRemark(item) || getBillResult(item)" class="remark-panel tn-margin-top">
<view v-if="getBillRemark(item)" class="remark-line"> <view v-if="getBillRemark(item)" class="remark-line">
<text class="remark-label">{{ t('remarkLabel') }}</text> <text class="remark-label">{{ $tr('备注') }}</text>
<text class="remark-text">{{ getBillRemark(item) }}</text> <text class="remark-text">{{ getBillRemark(item) }}</text>
</view> </view>
<view v-if="getBillResult(item)" class="remark-line tn-margin-top-sm"> <view v-if="getBillResult(item)" class="remark-line tn-margin-top-sm">
<text class="remark-label">{{ t('resultLabel') }}</text> <text class="remark-label">{{ $tr('处理结果') }}</text>
<text class="remark-text">{{ getBillResult(item) }}</text> <text class="remark-text">{{ getBillResult(item) }}</text>
</view> </view>
</view> </view>
<view v-if="getPendingPaymentOrder(item)" class="pending-payment-panel tn-margin-top"> <view v-if="getPendingPaymentOrder(item)" class="pending-payment-panel tn-margin-top">
<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"> <view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('pendingPaymentOrderNoLabel') }}</text> <text class="info-label">{{ $tr('支付单号') }}</text>
<text class="info-value">{{ getPendingPaymentOrderNo(item) }}</text> <text class="info-value">{{ getPendingPaymentOrderNo(item) }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <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(getPendingPaymentAmount(item)) }}</text> <text class="info-value info-value--highlight">¥{{ formatMoney(getPendingPaymentAmount(item)) }}</text>
</view> </view>
<view v-if="getPendingPaymentExpireText(item)" class="info-row tn-flex tn-flex-row-between"> <view v-if="getPendingPaymentExpireText(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': isPendingPaymentExpired(item) }"> <text class="info-value" :class="{ 'info-value--expired': isPendingPaymentExpired(item) }">
{{ getPendingPaymentExpireText(item) }} {{ getPendingPaymentExpireText(item) }}
</text> </text>
</view> </view>
<view v-if="getPendingPaymentDescription(item)" class="info-row tn-flex tn-flex-row-between"> <view v-if="getPendingPaymentDescription(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">{{ getPendingPaymentDescription(item) }}</text> <text class="info-value">{{ getPendingPaymentDescription(item) }}</text>
</view> </view>
<view v-if="getPendingPaymentChannel(item)" class="info-row tn-flex tn-flex-row-between"> <view v-if="getPendingPaymentChannel(item)" class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('pendingPaymentChannelLabel') }}</text> <text class="info-label">{{ $tr('支付渠道') }}</text>
<text class="info-value">{{ getPendingPaymentChannel(item) }}</text> <text class="info-value">{{ getPendingPaymentChannel(item) }}</text>
</view> </view>
</view> </view>
@@ -124,10 +124,10 @@
</view> </view>
<view v-else class="empty-panel tn-padding-lg"> <view v-else class="empty-panel tn-padding-lg">
<view class="empty-title">{{ currentReferenceTypeNo ? t('emptyPendingTitle') : t('emptyApplicationTitle') }}</view> <view class="empty-title">{{ currentReferenceTypeNo ? $tr('暂无待支付账单') : $tr('暂无可查询申请单') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ currentReferenceTypeNo ? t('emptyPendingDesc') : t('emptyApplicationDesc') }}</view> <view class="empty-desc tn-margin-top-sm">{{ currentReferenceTypeNo ? $tr('当前申请单下没有查询到待支付账单,后续有新账单时会在这里展示。') : $tr('当前未找到可关联的申请单,请先前往“我的申请”选择具体申请后查看账单。') }}</view>
<view v-if="!currentReferenceTypeNo" class="empty-action tn-margin-top-lg" @tap="goToMyApplication"> <view v-if="!currentReferenceTypeNo" class="empty-action tn-margin-top-lg" @tap="goToMyApplication">
<text class="tn-text-bold">{{ t('goToMyApplication') }}</text> <text class="tn-text-bold">{{ $tr('前往我的申请') }}</text>
</view> </view>
</view> </view>
@@ -197,7 +197,7 @@
<view v-if="isPaymentWaitingStage" class="payment-progress-card"> <view v-if="isPaymentWaitingStage" class="payment-progress-card">
<view class="payment-progress-head tn-flex tn-flex-row-between tn-flex-col-center"> <view class="payment-progress-head tn-flex tn-flex-row-between tn-flex-col-center">
<text class="payment-progress-label">{{ t('paymentProgressLabel') }}</text> <text class="payment-progress-label">{{ $tr('结果确认进度') }}</text>
<text class="payment-progress-value">{{ paymentProgressText }}</text> <text class="payment-progress-value">{{ paymentProgressText }}</text>
</view> </view>
<view class="payment-progress-track"> <view class="payment-progress-track">
@@ -208,11 +208,11 @@
<view class="payment-summary-grid"> <view class="payment-summary-grid">
<view class="payment-summary-item"> <view class="payment-summary-item">
<view class="payment-summary-label">{{ t('billNoLabel') }}</view> <view class="payment-summary-label">{{ $tr('账单号') }}</view>
<view class="payment-summary-value payment-summary-value--mono">{{ paymentWaitingBillNo || '--' }}</view> <view class="payment-summary-value payment-summary-value--mono">{{ paymentWaitingBillNo || '--' }}</view>
</view> </view>
<view class="payment-summary-item"> <view class="payment-summary-item">
<view class="payment-summary-label">{{ t('currentPaymentLabel') }}</view> <view class="payment-summary-label">{{ $tr('本次支付') }}</view>
<view class="payment-summary-value payment-summary-value--price">¥{{ paymentWaitingAmountText }}</view> <view class="payment-summary-value payment-summary-value--price">¥{{ paymentWaitingAmountText }}</view>
</view> </view>
</view> </view>
@@ -380,212 +380,6 @@ export default {
lines: [], lines: [],
confirmText: '', confirmText: '',
cancelText: '' cancelText: ''
},
i18n: {
zh: {
heroTitle: '待支付账单',
applicationNoPrefix: '申请单号:',
pendingCountLabel: '待支付数量',
pendingAmountLabel: '待支付总额',
loadingTitle: '账单加载中',
loadingDesc: '正在同步您的待支付账单,请稍候。',
relatedBusinessNoLabel: '关联业务编号',
totalChargeLabel: '应收合计',
paidAmountLabel: '已付金额',
remainingAmountLabel: '待付金额',
refundStatusLabel: '退款状态',
createTimeLabel: '创建时间',
remarkLabel: '备注',
resultLabel: '处理结果',
pendingPaymentTitle: '未完成支付订单',
pendingPaymentOrderNoLabel: '支付单号',
pendingPaymentAmountLabel: '订单金额',
pendingPaymentDescLabel: '支付说明',
pendingPaymentChannelLabel: '支付渠道',
pendingPaymentExpireLabel: '剩余支付时间',
pendingPaymentExpired: '已失效',
emptyPendingTitle: '暂无待支付账单',
emptyApplicationTitle: '暂无可查询申请单',
emptyPendingDesc: '当前申请单下没有查询到待支付账单,后续有新账单时会在这里展示。',
emptyApplicationDesc: '当前未找到可关联的申请单,请先前往“我的申请”选择具体申请后查看账单。',
goToMyApplication: '前往我的申请',
paymentProgressLabel: '结果确认进度',
billNoLabel: '账单号',
currentPaymentLabel: '本次支付',
heroDescResolved: '当前页面已按申请单编号查询其关联的待支付账单记录。',
heroDescAutoResolve: '当前将优先读取最近申请单的编号来查询关联账单。',
paymentStageLaunching: '正在唤起微信收银台',
paymentStagePolling: '正在同步支付结果',
paymentStageSuccess: '支付结果已确认',
paymentStageFailed: '支付未完成',
paymentStageDefault: '支付处理中',
paymentTitleLaunching: '请完成微信支付',
paymentTitlePolling: '正在确认是否支付成功',
paymentTitleSuccess: '支付成功',
paymentTitleFailed: '支付结果确认失败',
paymentTitleDefault: '支付处理中',
paymentDescLaunching: '系统正在调起微信支付,请在微信收银台中完成付款后返回。',
paymentDescPolling: '支付完成后,系统会自动向后台查询支付状态,无需手动刷新。',
paymentDescSuccess: '本次支付已同步完成,账单状态已更新。',
paymentDescFailed: '系统暂时未能确认支付成功,您可以查看提示后稍后重试。',
paymentProgressWaiting: '等待支付完成',
paymentHintLaunching: '已为您准备好支付信息,等待微信支付完成。',
paymentHintPolling: '正在与后台核对支付状态,请稍候。',
paymentTipsTitleFailed: '处理建议',
paymentTipsTitleSuccess: '完成说明',
paymentTipsTitleDefault: '温馨提示',
paymentTipsFailed1: '若您已完成付款,请稍后刷新账单,避免重复支付。',
paymentTipsFailed2: '若微信侧已扣款但页面未更新,请联系管家或客服协助核实。',
paymentTipsFailed3: '若未扣款,可返回账单列表后重新发起支付。',
paymentTipsSuccess1: '若当前申请单还有其他待付账单,可继续在当前页完成支付。',
paymentTipsSuccess2: '若当前账单已全部支付完成,系统会自动返回首页。',
paymentTipsSuccess3: '支付记录同步可能略有延迟,刷新后即可看到最新状态。',
paymentTipsDefault1: '支付成功后,系统可能需要几秒钟同步账单状态。',
paymentTipsDefault2: '请不要重复点击支付,也不要提前关闭当前页面。',
paymentTipsDefault3: '如果超时未完成,系统会自动再校验一次账单状态。',
paymentActionAcknowledge: '我知道了',
buttonConfirming: '确认结果中...',
buttonPolling: '查询结果中({current}/{total})',
buttonLaunching: '拉起支付中...',
wechatPayButton: '微信支付',
continuePayButton: '继续支付',
paymentStatusPending: '处理中',
billRecordFallback: '账单记录',
paymentParamIncomplete: '微信支付参数不完整',
wechatCodeFailed: '获取微信登录凭证失败',
billPaymentDesc: '账单支付',
paymentFailedHint: '暂未查询到支付成功结果,请稍后刷新账单确认。',
paymentResultSuccess: '后台确认支付成功',
paymentResultFailed: '后台返回支付失败',
paymentResultPending: '后台仍在处理中',
paymentHintPaidWaiting: '已支付成功,正在等待后台确认账单状态。',
paymentFailedError: '支付失败',
paymentTimeoutError: '支付结果确认超时,请稍后刷新账单确认',
paymentSuccessPendingBills: '支付结果已确认,当前申请单仍有其他待支付账单。',
paymentSuccessAllDone: '支付结果已确认,当前申请单账单已全部完成。',
billInfoIncomplete: '账单信息不完整',
paymentNoticeTitle: '用户须知',
paymentNoticeBillLabel: '账单号',
paymentNoticeAmountLabel: '支付金额',
paymentNoticeItemLabel: '费用项目',
paymentNoticeLine1: '请确认本次支付账单和金额无误后再继续付款。',
paymentNoticeLine2: '支付完成后,账单状态可能需要几秒同步,请勿重复支付。',
paymentNoticeLine3: '若支付中断或页面关闭,可稍后返回账单页查看最新结果。',
paymentNoticeConfirm: '继续支付',
paymentNoticeCancel: '稍后再付',
paymentHintLaunchingToast: '正在唤起微信支付,请在微信内完成付款。',
paymentCanceled: '已取消支付',
paymentResultFailedRetry: '支付结果确认失败,请稍后刷新账单确认',
billLoadFailed: '账单加载失败',
cancelOrderButton: '取消订单',
cancelOrderLoading: '取消中...',
cancelOrderTitle: '取消订单',
cancelOrderContent: '取消后将关闭当前未支付订单,是否继续?',
cancelOrderSuccess: '订单已取消',
cancelOrderFailed: '取消订单失败'
},
en: {
heroTitle: 'Pending Bills',
applicationNoPrefix: 'Application No: ',
pendingCountLabel: 'Pending Count',
pendingAmountLabel: 'Pending Amount',
loadingTitle: 'Loading Bills',
loadingDesc: 'Syncing your pending bills, please wait.',
relatedBusinessNoLabel: 'Related Business No.',
totalChargeLabel: 'Total Charge',
paidAmountLabel: 'Paid Amount',
remainingAmountLabel: 'Remaining Amount',
refundStatusLabel: 'Refund Status',
createTimeLabel: 'Created At',
remarkLabel: 'Remark',
resultLabel: 'Result',
pendingPaymentTitle: 'Unfinished Payment',
pendingPaymentOrderNoLabel: 'Payment Order No.',
pendingPaymentAmountLabel: 'Order Amount',
pendingPaymentDescLabel: 'Description',
pendingPaymentChannelLabel: 'Channel',
pendingPaymentExpireLabel: 'Time Left',
pendingPaymentExpired: 'Expired',
emptyPendingTitle: 'No Pending Bills',
emptyApplicationTitle: 'No Application Available',
emptyPendingDesc: 'No pending bills are found under the current application. New bills will appear here later.',
emptyApplicationDesc: 'No related application is found. Please go to "My Applications" and select an application first.',
goToMyApplication: 'Go to My Applications',
paymentProgressLabel: 'Confirmation Progress',
billNoLabel: 'Bill No.',
currentPaymentLabel: 'This Payment',
heroDescResolved: 'This page is querying pending bills by the current application number.',
heroDescAutoResolve: 'The latest application number will be used first to query related bills.',
paymentStageLaunching: 'Launching Wechat Pay',
paymentStagePolling: 'Syncing Payment Result',
paymentStageSuccess: 'Payment Confirmed',
paymentStageFailed: 'Payment Incomplete',
paymentStageDefault: 'Processing Payment',
paymentTitleLaunching: 'Complete Wechat Pay',
paymentTitlePolling: 'Confirming Payment Result',
paymentTitleSuccess: 'Payment Successful',
paymentTitleFailed: 'Payment Confirmation Failed',
paymentTitleDefault: 'Processing Payment',
paymentDescLaunching: 'Wechat Pay is being launched. Complete the payment in Wechat and return here.',
paymentDescPolling: 'After payment, the system checks the payment status automatically. No manual refresh is needed.',
paymentDescSuccess: 'This payment has been synchronized and the bill status is updated.',
paymentDescFailed: 'The system cannot confirm the payment for now. Please review the tips and try again later.',
paymentProgressWaiting: 'Waiting for Payment',
paymentHintLaunching: 'Payment information is ready. Waiting for Wechat payment to complete.',
paymentHintPolling: 'Checking payment status with the backend, please wait.',
paymentTipsTitleFailed: 'Suggestions',
paymentTipsTitleSuccess: 'Completion Notes',
paymentTipsTitleDefault: 'Tips',
paymentTipsFailed1: 'If you have completed the payment, refresh the bill later to avoid duplicate payment.',
paymentTipsFailed2: 'If Wechat has deducted the amount but the page is not updated, contact your manager or support for verification.',
paymentTipsFailed3: 'If no deduction occurred, return to the bill list and try again.',
paymentTipsSuccess1: 'If there are other pending bills under this application, you can continue paying on this page.',
paymentTipsSuccess2: 'If all bills are paid, the system returns to the home page automatically.',
paymentTipsSuccess3: 'Payment record synchronization may be slightly delayed. Refresh to see the latest status.',
paymentTipsDefault1: 'After successful payment, the system may need a few seconds to sync the bill status.',
paymentTipsDefault2: 'Do not tap the pay button repeatedly or close this page too early.',
paymentTipsDefault3: 'If the process times out, the system checks the bill status again automatically.',
paymentActionAcknowledge: 'Got It',
buttonConfirming: 'Confirming...',
buttonPolling: 'Checking ({current}/{total})',
buttonLaunching: 'Launching...',
wechatPayButton: 'Wechat Pay',
continuePayButton: 'Continue Pay',
paymentStatusPending: 'Processing',
billRecordFallback: 'Bill Record',
paymentParamIncomplete: 'Incomplete Wechat payment parameters',
wechatCodeFailed: 'Failed to get Wechat login code',
billPaymentDesc: 'Bill Payment',
paymentFailedHint: 'Payment success is not confirmed yet. Please refresh the bill later.',
paymentResultSuccess: 'Backend confirmed payment success',
paymentResultFailed: 'Backend returned payment failure',
paymentResultPending: 'Backend is still processing',
paymentHintPaidWaiting: 'Payment succeeded. Waiting for backend confirmation of the bill status.',
paymentFailedError: 'Payment failed',
paymentTimeoutError: 'Payment confirmation timed out. Please refresh the bill later.',
paymentSuccessPendingBills: 'Payment is confirmed. There are still other unpaid bills under this application.',
paymentSuccessAllDone: 'Payment is confirmed. All bills under this application are completed.',
billInfoIncomplete: 'Incomplete bill information',
paymentNoticeTitle: 'Payment Notice',
paymentNoticeBillLabel: 'Bill No.',
paymentNoticeAmountLabel: 'Amount',
paymentNoticeItemLabel: 'Charge Item',
paymentNoticeLine1: 'Please confirm the bill and payment amount before continuing.',
paymentNoticeLine2: 'After payment, the bill status may take a few seconds to sync. Please do not pay repeatedly.',
paymentNoticeLine3: 'If the payment is interrupted or the page is closed, return to the bill page later to check the latest result.',
paymentNoticeConfirm: 'Continue',
paymentNoticeCancel: 'Later',
paymentHintLaunchingToast: 'Launching Wechat Pay. Please complete the payment in Wechat.',
paymentCanceled: 'Payment canceled',
paymentResultFailedRetry: 'Payment confirmation failed. Please refresh the bill later.',
billLoadFailed: 'Failed to load bills',
cancelOrderButton: 'Cancel Order',
cancelOrderLoading: 'Canceling...',
cancelOrderTitle: 'Cancel Order',
cancelOrderContent: 'This will close the current unpaid order. Continue?',
cancelOrderSuccess: 'Order canceled',
cancelOrderFailed: 'Failed to cancel order'
}
} }
} }
}, },
@@ -600,9 +394,9 @@ export default {
}, },
heroDesc() { heroDesc() {
if (this.currentReferenceTypeNo) { if (this.currentReferenceTypeNo) {
return this.t('heroDescResolved') return this.$tr('当前页面已按申请单编号查询其关联的待支付账单记录。')
} }
return this.t('heroDescAutoResolve') return this.$tr('当前将优先读取最近申请单的编号来查询关联账单。')
}, },
showPaymentFeedbackPanel() { showPaymentFeedbackPanel() {
return Boolean(this.paymentStage) return Boolean(this.paymentStage)
@@ -627,18 +421,18 @@ export default {
}, },
paymentStageLabel() { paymentStageLabel() {
if (this.paymentStage === 'launching') { if (this.paymentStage === 'launching') {
return this.t('paymentStageLaunching') return this.$tr('正在唤起微信收银台')
} }
if (this.paymentStage === 'polling') { if (this.paymentStage === 'polling') {
return this.t('paymentStagePolling') return this.$tr('正在同步支付结果')
} }
if (this.paymentStage === 'success') { if (this.paymentStage === 'success') {
return this.t('paymentStageSuccess') return this.$tr('支付结果已确认')
} }
if (this.paymentStage === 'failed') { if (this.paymentStage === 'failed') {
return this.t('paymentStageFailed') return this.$tr('支付未完成')
} }
return this.t('paymentStageDefault') return this.$tr('支付处理中')
}, },
paymentBadgeIconClass() { paymentBadgeIconClass() {
if (this.isPaymentSuccessStage) { if (this.isPaymentSuccessStage) {
@@ -660,31 +454,31 @@ export default {
}, },
paymentWaitingTitle() { paymentWaitingTitle() {
if (this.paymentStage === 'launching') { if (this.paymentStage === 'launching') {
return this.t('paymentTitleLaunching') return this.$tr('请完成微信支付')
} }
if (this.paymentStage === 'polling') { if (this.paymentStage === 'polling') {
return this.t('paymentTitlePolling') return this.$tr('正在确认是否支付成功')
} }
if (this.paymentStage === 'success') { if (this.paymentStage === 'success') {
return this.t('paymentTitleSuccess') return this.$tr('支付成功')
} }
if (this.paymentStage === 'failed') { if (this.paymentStage === 'failed') {
return this.t('paymentTitleFailed') return this.$tr('支付结果确认失败')
} }
return this.t('paymentTitleDefault') return this.$tr('支付处理中')
}, },
paymentWaitingDesc() { paymentWaitingDesc() {
if (this.paymentStage === 'launching') { if (this.paymentStage === 'launching') {
return this.t('paymentDescLaunching') return this.$tr('系统正在调起微信支付,请在微信收银台中完成付款后返回。')
} }
if (this.paymentStage === 'polling') { if (this.paymentStage === 'polling') {
return this.t('paymentDescPolling') return this.$tr('支付完成后,系统会自动向后台查询支付状态,无需手动刷新。')
} }
if (this.paymentStage === 'success') { if (this.paymentStage === 'success') {
return this.t('paymentDescSuccess') return this.$tr('本次支付已同步完成,账单状态已更新。')
} }
if (this.paymentStage === 'failed') { if (this.paymentStage === 'failed') {
return this.t('paymentDescFailed') return this.$tr('系统暂时未能确认支付成功,您可以查看提示后稍后重试。')
} }
return '' return ''
}, },
@@ -704,7 +498,7 @@ export default {
}, },
paymentProgressText() { paymentProgressText() {
if (this.paymentStage !== 'polling') { if (this.paymentStage !== 'polling') {
return this.t('paymentProgressWaiting') return this.$tr('等待支付完成')
} }
const current = this.paymentPollingAttempt > 0 ? this.paymentPollingAttempt : 1 const current = this.paymentPollingAttempt > 0 ? this.paymentPollingAttempt : 1
return `${current}/${PAYMENT_QUERY_MAX_ATTEMPTS}` return `${current}/${PAYMENT_QUERY_MAX_ATTEMPTS}`
@@ -714,45 +508,45 @@ export default {
return this.paymentStatusHint return this.paymentStatusHint
} }
if (this.paymentStage === 'launching') { if (this.paymentStage === 'launching') {
return this.t('paymentHintLaunching') return this.$tr('已为您准备好支付信息,等待微信支付完成。')
} }
return this.t('paymentHintPolling') return this.$tr('正在与后台核对支付状态,请稍候。')
}, },
paymentTipsTitle() { paymentTipsTitle() {
if (this.isPaymentFailedStage) { if (this.isPaymentFailedStage) {
return this.t('paymentTipsTitleFailed') return this.$tr('处理建议')
} }
if (this.isPaymentSuccessStage) { if (this.isPaymentSuccessStage) {
return this.t('paymentTipsTitleSuccess') return this.$tr('完成说明')
} }
return this.t('paymentTipsTitleDefault') return this.$tr('温馨提示')
}, },
paymentTips() { paymentTips() {
if (this.isPaymentFailedStage) { if (this.isPaymentFailedStage) {
return [ return [
this.t('paymentTipsFailed1'), this.$tr('若您已完成付款,请稍后刷新账单,避免重复支付。'),
this.t('paymentTipsFailed2'), this.$tr('若微信侧已扣款但页面未更新,请联系管家或客服协助核实。'),
this.t('paymentTipsFailed3') this.$tr('若未扣款,可返回账单列表后重新发起支付。')
] ]
} }
if (this.isPaymentSuccessStage) { if (this.isPaymentSuccessStage) {
return [ return [
this.t('paymentTipsSuccess1'), this.$tr('若当前申请单还有其他待付账单,可继续在当前页完成支付。'),
this.t('paymentTipsSuccess2'), this.$tr('若当前账单已全部支付完成,系统会自动返回首页。'),
this.t('paymentTipsSuccess3') this.$tr('支付记录同步可能略有延迟,刷新后即可看到最新状态。')
] ]
} }
return [ return [
this.t('paymentTipsDefault1'), this.$tr('支付成功后,系统可能需要几秒钟同步账单状态。'),
this.t('paymentTipsDefault2'), this.$tr('请不要重复点击支付,也不要提前关闭当前页面。'),
this.t('paymentTipsDefault3') this.$tr('如果超时未完成,系统会自动再校验一次账单状态。')
] ]
}, },
showPaymentActionButton() { showPaymentActionButton() {
return this.isPaymentFailedStage return this.isPaymentFailedStage
}, },
paymentActionText() { paymentActionText() {
return this.isPaymentFailedStage ? this.t('paymentActionAcknowledge') : '' return this.isPaymentFailedStage ? this.$tr('我知道了') : ''
} }
}, },
watch: { watch: {
@@ -782,22 +576,6 @@ export default {
this.clearCountdownTimer() this.clearCountdownTimer()
}, },
methods: { methods: {
t(key, params) {
const translatedText = this.$tm('pkgService.components.CurrentApplicationBillPanel', key, params)
if (translatedText && translatedText !== key) {
return translatedText
}
const languagePack = this.i18n && this.i18n[this.language] ? this.i18n[this.language] : this.i18n.zh
const fallbackText = languagePack && languagePack[key] ? languagePack[key] : key
if (!params || typeof fallbackText !== 'string') {
return fallbackText
}
return fallbackText.replace(/\{(\w+)\}/g, (match, token) => {
return Object.prototype.hasOwnProperty.call(params, token) ? params[token] : match
})
},
getBillNo(item = {}) { getBillNo(item = {}) {
return item.no || item.billNo || '' return item.no || item.billNo || ''
}, },
@@ -812,7 +590,7 @@ export default {
}, },
getBillPaymentStatusText(item = {}) { getBillPaymentStatusText(item = {}) {
const translatedStatus = this.getPaymentStatusText(this.getBillPaymentStatus(item)) const translatedStatus = this.getPaymentStatusText(this.getBillPaymentStatus(item))
return translatedStatus || item.paymentStatusDesc || this.t('paymentStatusPending') return translatedStatus || item.paymentStatusDesc || this.$tr('处理中')
}, },
getBillRefundStatus(item = {}) { getBillRefundStatus(item = {}) {
return item.refundStatus return item.refundStatus
@@ -918,7 +696,7 @@ export default {
return '' return ''
} }
if (expireTimestamp <= this.countdownNow) { if (expireTimestamp <= this.countdownNow) {
return this.t('pendingPaymentExpired') return this.$tr('已失效')
} }
return this.formatPendingPaymentCountdown(this.getPendingPaymentRemainingSeconds(item)) return this.formatPendingPaymentCountdown(this.getPendingPaymentRemainingSeconds(item))
}, },
@@ -926,20 +704,20 @@ export default {
const billNo = this.getBillNo(item) const billNo = this.getBillNo(item)
if (this.paymentPollingBillNo && this.paymentPollingBillNo === billNo) { if (this.paymentPollingBillNo && this.paymentPollingBillNo === billNo) {
if (!this.paymentPollingAttempt) { if (!this.paymentPollingAttempt) {
return this.t('buttonConfirming') return this.$tr('确认结果中...')
} }
return this.t('buttonPolling', { return this.$tr('查询结果中({current}/{total})', {
current: this.paymentPollingAttempt, current: this.paymentPollingAttempt,
total: PAYMENT_QUERY_MAX_ATTEMPTS total: PAYMENT_QUERY_MAX_ATTEMPTS
}) })
} }
if (this.payingBillNo === billNo) { if (this.payingBillNo === billNo) {
return this.paymentStage === 'launching' ? this.t('buttonLaunching') : this.t('buttonConfirming') return this.paymentStage === 'launching' ? this.$tr('拉起支付中...') : this.$tr('确认结果中...')
} }
if (this.getPendingPaymentOrder(item) && !this.isPendingPaymentExpired(item)) { if (this.getPendingPaymentOrder(item) && !this.isPendingPaymentExpired(item)) {
return this.t('continuePayButton') return this.$tr('继续支付')
} }
return this.t('wechatPayButton') return this.$tr('微信支付')
}, },
canCancelOrder(item = {}) { canCancelOrder(item = {}) {
if (!this.currentReferenceTypeNo) { if (!this.currentReferenceTypeNo) {
@@ -951,8 +729,8 @@ export default {
}, },
getCancelButtonText(item = {}) { getCancelButtonText(item = {}) {
return this.cancelingBillNo === this.getBillNo(item) return this.cancelingBillNo === this.getBillNo(item)
? this.t('cancelOrderLoading') ? this.$tr('取消中...')
: this.t('cancelOrderButton') : this.$tr('取消订单')
}, },
resolveReferenceTypeNo(item = {}) { resolveReferenceTypeNo(item = {}) {
return item.code || item.detailCode || '' return item.code || item.detailCode || ''
@@ -1024,7 +802,7 @@ export default {
return text.length > 16 ? text.slice(0, 16) : text return text.length > 16 ? text.slice(0, 16) : text
}, },
getPaymentStatusText(status) { getPaymentStatusText(status) {
return getAccountBillPaymentStatusLabel(status, this.t('paymentStatusPending')) return getAccountBillPaymentStatusLabel(status, this.$tr('处理中'))
}, },
resolvePaymentStatusType(status) { resolvePaymentStatusType(status) {
if (Number(status) === ACCOUNT_BILL_PAYMENT_STATUS.PAID) { if (Number(status) === ACCOUNT_BILL_PAYMENT_STATUS.PAID) {
@@ -1040,7 +818,7 @@ export default {
}, },
getReferenceTypeText(item = {}) { getReferenceTypeText(item = {}) {
const translatedType = getAccountReferenceTypeLabel(item.referenceType, '') const translatedType = getAccountReferenceTypeLabel(item.referenceType, '')
return translatedType || item.referenceTypeDesc || this.t('billRecordFallback') return translatedType || item.referenceTypeDesc || this.$tr('账单记录')
}, },
parsePaymentInfo(paymentInfo) { parsePaymentInfo(paymentInfo) {
if (!paymentInfo) { if (!paymentInfo) {
@@ -1073,7 +851,7 @@ export default {
requestWechatPayment(paymentInfo) { requestWechatPayment(paymentInfo) {
const payOptions = this.normalizeWechatPayOptions(paymentInfo) const payOptions = this.normalizeWechatPayOptions(paymentInfo)
if (!payOptions.timeStamp || !payOptions.nonceStr || !payOptions.package || !payOptions.paySign) { if (!payOptions.timeStamp || !payOptions.nonceStr || !payOptions.package || !payOptions.paySign) {
return Promise.reject(new Error(this.t('paymentParamIncomplete'))) return Promise.reject(new Error(this.$tr('微信支付参数不完整')))
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -1143,13 +921,13 @@ export default {
success: (res) => { success: (res) => {
const code = res && res.code ? res.code : '' const code = res && res.code ? res.code : ''
if (!code) { if (!code) {
reject(new Error(this.t('wechatCodeFailed'))) reject(new Error(this.$tr('获取微信登录凭证失败')))
return return
} }
resolve(code) resolve(code)
}, },
fail: (error) => { fail: (error) => {
reject(error || new Error(this.t('wechatCodeFailed'))) reject(error || new Error(this.$tr('获取微信登录凭证失败')))
} }
}) })
}) })
@@ -1160,7 +938,7 @@ export default {
billNo: this.getBillNo(item), billNo: this.getBillNo(item),
payMethod: 'Wechat', payMethod: 'Wechat',
amount: Number(this.getBillRemainingAmount(item) || 0), amount: Number(this.getBillRemainingAmount(item) || 0),
description: this.getReferenceTypeText(item) || this.t('billPaymentDesc') description: this.getReferenceTypeText(item) || this.$tr('账单支付')
} }
if (wechatCode) { if (wechatCode) {
payload.ExtraInfo = wechatCode payload.ExtraInfo = wechatCode
@@ -1168,10 +946,6 @@ export default {
return payload return payload
}, },
resolveNoticeText(key, zhText, enText) { resolveNoticeText(key, zhText, enText) {
const translatedText = this.t(key)
if (translatedText && translatedText !== key) {
return translatedText
}
return this.language === 'en' ? enText : zhText return this.language === 'en' ? enText : zhText
}, },
resetPaymentNoticeContext() { resetPaymentNoticeContext() {
@@ -1237,8 +1011,8 @@ export default {
confirmCancelOrder() { confirmCancelOrder() {
return new Promise((resolve) => { return new Promise((resolve) => {
uni.showModal({ uni.showModal({
title: this.t('cancelOrderTitle'), title: this.$tr('取消订单'),
content: this.t('cancelOrderContent'), content: this.$tr('取消后将关闭当前未支付订单,是否继续?'),
confirmColor: '#ef4444', confirmColor: '#ef4444',
success: (res) => resolve(!!(res && res.confirm)), success: (res) => resolve(!!(res && res.confirm)),
fail: () => resolve(false) fail: () => resolve(false)
@@ -1264,7 +1038,7 @@ export default {
this.paymentSession = { this.paymentSession = {
billNo: this.getBillNo(item), billNo: this.getBillNo(item),
amount: Number(this.getBillRemainingAmount(item) || 0), amount: Number(this.getBillRemainingAmount(item) || 0),
title: this.getReferenceTypeText(item) || this.t('billPaymentDesc') title: this.getReferenceTypeText(item) || this.$tr('账单支付')
} }
}, },
resetPaymentWaitingState() { resetPaymentWaitingState() {
@@ -1277,7 +1051,7 @@ export default {
}, },
openPaymentFailedState(message = '') { openPaymentFailedState(message = '') {
this.paymentStage = 'failed' this.paymentStage = 'failed'
this.paymentStatusHint = message || this.t('paymentFailedHint') this.paymentStatusHint = message || this.$tr('暂未查询到支付成功结果,请稍后刷新账单确认。')
this.payingBillNo = '' this.payingBillNo = ''
this.paymentPollingBillNo = this.paymentWaitingBillNo this.paymentPollingBillNo = this.paymentWaitingBillNo
}, },
@@ -1312,20 +1086,20 @@ export default {
if (payStatus === PAYMENT_ORDER_STATUS.SUCCESS) { if (payStatus === PAYMENT_ORDER_STATUS.SUCCESS) {
return { return {
status: PAYMENT_QUERY_STATUS.SUCCESS, status: PAYMENT_QUERY_STATUS.SUCCESS,
message: statusDesc || this.t('paymentResultSuccess'), message: statusDesc || this.$tr('后台确认支付成功'),
data: payload data: payload
} }
} }
if (payStatus === PAYMENT_ORDER_STATUS.FAILED) { if (payStatus === PAYMENT_ORDER_STATUS.FAILED) {
return { return {
status: PAYMENT_QUERY_STATUS.FAILED, status: PAYMENT_QUERY_STATUS.FAILED,
message: statusDesc || this.t('paymentResultFailed'), message: statusDesc || this.$tr('后台返回支付失败'),
data: payload data: payload
} }
} }
return { return {
status: PAYMENT_QUERY_STATUS.PENDING, status: PAYMENT_QUERY_STATUS.PENDING,
message: statusDesc || this.t('paymentResultPending'), message: statusDesc || this.$tr('后台仍在处理中'),
data: payload data: payload
} }
}) })
@@ -1337,7 +1111,7 @@ export default {
this.paymentStage = 'polling' this.paymentStage = 'polling'
this.paymentPollingBillNo = queryContext.billNo this.paymentPollingBillNo = queryContext.billNo
this.paymentPollingAttempt = 0 this.paymentPollingAttempt = 0
this.paymentStatusHint = this.t('paymentHintPaidWaiting') this.paymentStatusHint = this.$tr('已支付成功,正在等待后台确认账单状态。')
const executePolling = async () => { const executePolling = async () => {
for (let attempt = 1; attempt <= PAYMENT_QUERY_MAX_ATTEMPTS; attempt += 1) { for (let attempt = 1; attempt <= PAYMENT_QUERY_MAX_ATTEMPTS; attempt += 1) {
@@ -1346,15 +1120,15 @@ export default {
} }
this.paymentPollingAttempt = attempt this.paymentPollingAttempt = attempt
lastResult = await this.queryPaymentResult(queryContext) lastResult = await this.queryPaymentResult(queryContext)
this.paymentStatusHint = lastResult && lastResult.message ? lastResult.message : this.t('paymentHintPolling') this.paymentStatusHint = lastResult && lastResult.message ? lastResult.message : this.$tr('正在与后台核对支付状态,请稍候。')
if (lastResult && lastResult.status === PAYMENT_QUERY_STATUS.SUCCESS) { if (lastResult && lastResult.status === PAYMENT_QUERY_STATUS.SUCCESS) {
return lastResult return lastResult
} }
if (lastResult && lastResult.status === PAYMENT_QUERY_STATUS.FAILED) { if (lastResult && lastResult.status === PAYMENT_QUERY_STATUS.FAILED) {
throw new Error(lastResult.message || this.t('paymentFailedError')) throw new Error(lastResult.message || this.$tr('支付失败'))
} }
} }
throw new Error((lastResult && lastResult.message) || this.t('paymentTimeoutError')) throw new Error((lastResult && lastResult.message) || this.$tr('支付结果确认超时,请稍后刷新账单确认'))
} }
return executePolling() return executePolling()
@@ -1363,8 +1137,8 @@ export default {
const hasPendingBills = this.billList.length > 0 const hasPendingBills = this.billList.length > 0
this.paymentStage = 'success' this.paymentStage = 'success'
this.paymentStatusHint = hasPendingBills this.paymentStatusHint = hasPendingBills
? this.t('paymentSuccessPendingBills') ? this.$tr('支付结果已确认,当前申请单仍有其他待支付账单。')
: this.t('paymentSuccessAllDone') : this.$tr('支付结果已确认,当前申请单账单已全部完成。')
this.payingBillNo = '' this.payingBillNo = ''
this.paymentPollingBillNo = this.paymentWaitingBillNo this.paymentPollingBillNo = this.paymentWaitingBillNo
return this.wait(1200) return this.wait(1200)
@@ -1413,7 +1187,7 @@ export default {
: null : null
if (!billNo || !this.currentReferenceTypeNo) { if (!billNo || !this.currentReferenceTypeNo) {
uni.showToast({ uni.showToast({
title: this.t('billInfoIncomplete'), title: this.$tr('账单信息不完整'),
icon: 'none' icon: 'none'
}) })
return return
@@ -1430,7 +1204,7 @@ export default {
} }
this.payingBillNo = billNo this.payingBillNo = billNo
this.paymentStage = 'launching' this.paymentStage = 'launching'
this.paymentStatusHint = this.t('paymentHintLaunchingToast') this.paymentStatusHint = this.$tr('正在唤起微信支付,请在微信内完成付款。')
this.createPaymentSession(item) this.createPaymentSession(item)
}) })
.then(() => { .then(() => {
@@ -1464,7 +1238,7 @@ export default {
if (/cancel/i.test(errMsg)) { if (/cancel/i.test(errMsg)) {
this.resetPaymentWaitingState() this.resetPaymentWaitingState()
uni.showToast({ uni.showToast({
title: this.t('paymentCanceled'), title: this.$tr('已取消支付'),
icon: 'none' icon: 'none'
}) })
return return
@@ -1474,7 +1248,7 @@ export default {
if (hasConfirmedSuccess) { if (hasConfirmedSuccess) {
return return
} }
this.openPaymentFailedState(errMsg || this.t('paymentResultFailedRetry')) this.openPaymentFailedState(errMsg || this.$tr('支付结果确认失败,请稍后刷新账单确认'))
}) })
}) })
.finally(() => { .finally(() => {
@@ -1500,7 +1274,7 @@ export default {
return userDetailCancel(this.buildCancelOrderPayload(item), this.language) return userDetailCancel(this.buildCancelOrderPayload(item), this.language)
.then(() => { .then(() => {
uni.showToast({ uni.showToast({
title: this.t('cancelOrderSuccess'), title: this.$tr('订单已取消'),
icon: 'success' icon: 'success'
}) })
return this.refreshBillList() return this.refreshBillList()
@@ -1508,7 +1282,7 @@ export default {
}) })
.catch((error) => { .catch((error) => {
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('cancelOrderFailed'), title: (error && (error.message || error.msg)) || this.$tr('取消订单失败'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -1554,7 +1328,7 @@ export default {
this.billList = [] this.billList = []
this.pendingPaymentOrderMap = {} this.pendingPaymentOrderMap = {}
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('billLoadFailed'), title: (error && (error.message || error.msg)) || this.$tr('账单加载失败'),
icon: 'none' icon: 'none'
}) })
return [] return []

View File

@@ -1,15 +1,15 @@
<template> <template>
<view class="contract-detail-page tn-safe-area-inset-bottom"> <view class="contract-detail-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('合同详情') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="page-main" :style="{ paddingTop: vuex_custom_bar_height + 'px' }"> <view class="page-main" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
<view class="overview-panel"> <view class="overview-panel">
<view class="overview-header"> <view class="overview-header">
<view> <view>
<view class="overview-title">{{ t('overviewTitle') }}</view> <view class="overview-title">{{ $tr('合同影像') }}</view>
<view class="overview-subtitle">{{ t('contractNoLabel') }}{{ contractNo || t('notAvailable') }}</view> <view class="overview-subtitle">{{ $tr('合同编号:') }}{{ contractNo || $tr('未获取') }}</view>
</view> </view>
<view class="overview-badge"> <view class="overview-badge">
<text class="tn-icon-read"></text> <text class="tn-icon-read"></text>
@@ -19,26 +19,26 @@
<view class="overview-tips"> <view class="overview-tips">
<view class="tip-chip"> <view class="tip-chip">
<text class="tn-icon-safe-fill"></text> <text class="tn-icon-safe-fill"></text>
<text>{{ t('signedChip') }}</text> <text>{{ $tr('已签合同') }}</text>
</view> </view>
<view class="tip-chip"> <view class="tip-chip">
<text class="tn-icon-charts-ring"></text> <text class="tn-icon-charts-ring"></text>
<text>{{ t('zoomSupportedChip') }}</text> <text>{{ $tr('支持缩放') }}</text>
</view> </view>
<view class="tip-chip" v-if="imageUrl"> <view class="tip-chip" v-if="imageUrl">
<text class="tn-icon-image-text-fill"></text> <text class="tn-icon-image-text-fill"></text>
<text>{{ t('imageModeChip') }}</text> <text>{{ $tr('A4 影像模式') }}</text>
</view> </view>
<view class="tip-chip" v-else-if="contractContent"> <view class="tip-chip" v-else-if="contractContent">
<text class="tn-icon-text-new"></text> <text class="tn-icon-text-new"></text>
<text>{{ t('textModeChip') }}</text> <text>{{ $tr('正文预览模式') }}</text>
</view> </view>
</view> </view>
</view> </view>
<view v-if="loading" class="empty-panel"> <view v-if="loading" class="empty-panel">
<view class="empty-title">{{ t('loadingTitle') }}</view> <view class="empty-title">{{ $tr('合同加载中') }}</view>
<view class="empty-desc">{{ t('loadingDesc') }}</view> <view class="empty-desc">{{ $tr('正在获取合同影像与正文,请稍候。') }}</view>
</view> </view>
<view v-else-if="imageUrl || contractContent" class="preview-panel"> <view v-else-if="imageUrl || contractContent" class="preview-panel">
@@ -72,8 +72,8 @@
<view class="empty-icon"> <view class="empty-icon">
<text class="tn-icon-folder"></text> <text class="tn-icon-folder"></text>
</view> </view>
<view class="empty-title">{{ t('emptyTitle') }}</view> <view class="empty-title">{{ $tr('暂无合同内容') }}</view>
<view class="empty-desc">{{ t('emptyDesc') }}</view> <view class="empty-desc">{{ $tr('页面未带入影像地址,且通过合同编号也未获取到可预览的合同内容。') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -100,36 +100,6 @@ function normalizeRichTextContent(content = '') {
} }
export default { export default {
i18n: {
zh: {
navTitle: '合同详情',
overviewTitle: '合同影像',
contractNoLabel: '合同编号:',
notAvailable: '未获取',
signedChip: '已签合同',
zoomSupportedChip: '支持缩放',
imageModeChip: 'A4 影像模式',
textModeChip: '正文预览模式',
loadingTitle: '合同加载中',
loadingDesc: '正在获取合同影像与正文,请稍候。',
emptyTitle: '暂无合同内容',
emptyDesc: '页面未带入影像地址,且通过合同编号也未获取到可预览的合同内容。'
},
en: {
navTitle: 'Contract Detail',
overviewTitle: 'Contract Preview',
contractNoLabel: 'Contract No.: ',
notAvailable: 'N/A',
signedChip: 'Signed Contract',
zoomSupportedChip: 'Zoom Supported',
imageModeChip: 'A4 Image Mode',
textModeChip: 'Text Preview Mode',
loadingTitle: 'Loading contract',
loadingDesc: 'Fetching contract images and content. Please wait.',
emptyTitle: 'No contract content',
emptyDesc: 'No previewable contract content was provided by image URL or contract number.'
}
},
data() { data() {
return { return {
contractNo: '', contractNo: '',
@@ -147,9 +117,6 @@ export default {
this.fetchContractDetail() this.fetchContractDetail()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.contract_detail.contract_detail', key, params)
},
fetchContractDetail() { fetchContractDetail() {
if (!this.contractNo || (this.imageUrl && this.contractContent)) { if (!this.contractNo || (this.imageUrl && this.contractContent)) {
return Promise.resolve() return Promise.resolve()

View File

@@ -1,15 +1,15 @@
<template> <template>
<view class="contract-sign-page tn-safe-area-inset-bottom"> <view class="contract-sign-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('合同签约') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="page-main" :style="{ paddingTop: vuex_custom_bar_height + 'px' }"> <view class="page-main" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
<view class="overview-panel"> <view class="overview-panel">
<view class="overview-header"> <view class="overview-header">
<view> <view>
<view class="overview-title">{{ t('overviewTitle') }}</view> <view class="overview-title">{{ $tr('签约前确认') }}</view>
<view class="overview-subtitle">{{ t('contractNoLabel') }}{{ contractNo || t('notAvailable') }}</view> <view class="overview-subtitle">{{ $tr('合同编号:') }}{{ contractNo || $tr('未获取') }}</view>
</view> </view>
<view class="overview-badge"> <view class="overview-badge">
<text class="tn-icon-edit-write-fill"></text> <text class="tn-icon-edit-write-fill"></text>
@@ -18,13 +18,13 @@
</view> </view>
<view class="overview-meta"> <view class="overview-meta">
<text class="tn-icon-safe-fill"></text> <text class="tn-icon-safe-fill"></text>
<text>{{ t('overviewMeta') }}</text> <text>{{ $tr('阅读合同正文后完成签名提交,签约结果会同步到我的合同。') }}</text>
</view> </view>
</view> </view>
<view v-if="loading" class="empty-panel"> <view v-if="loading" class="empty-panel">
<view class="empty-title">{{ t('loadingTitle') }}</view> <view class="empty-title">{{ $tr('合同加载中') }}</view>
<view class="empty-desc">{{ t('loadingDesc') }}</view> <view class="empty-desc">{{ $tr('正在获取合同正文,请稍候。') }}</view>
</view> </view>
<view v-else-if="contractContent" class="preview-panel"> <view v-else-if="contractContent" class="preview-panel">
@@ -38,7 +38,7 @@
<view class="preview-tip" @click="openFullscreenPreview"> <view class="preview-tip" @click="openFullscreenPreview">
<text class="tn-icon-fullscreen"></text> <text class="tn-icon-fullscreen"></text>
<text>{{ t('fullscreenPreview') }}</text> <text>{{ $tr('点击全屏查看') }}</text>
</view> </view>
<view class="zoom-controls" @click.stop> <view class="zoom-controls" @click.stop>
@@ -56,17 +56,17 @@
<view class="empty-icon"> <view class="empty-icon">
<text class="tn-icon-folder"></text> <text class="tn-icon-folder"></text>
</view> </view>
<view class="empty-title">{{ t('emptyTitle') }}</view> <view class="empty-title">{{ $tr('暂无合同正文') }}</view>
<view class="empty-desc">{{ t('emptyDesc') }}</view> <view class="empty-desc">{{ $tr('当前合同暂未返回可签约正文,请稍后再试。') }}</view>
</view> </view>
<view v-if="contractContent" class="footer-bar"> <view v-if="contractContent" class="footer-bar">
<view class="footer-tip"> <view class="footer-tip">
<text class="tn-icon-safe-fill"></text> <text class="tn-icon-safe-fill"></text>
<text>{{ t('footerTip') }}</text> <text>{{ $tr('签名前请确认您已完整阅读合同内容') }}</text>
</view> </view>
<view class="action-btn" @click="openSignBoard"> <view class="action-btn" @click="openSignBoard">
{{ isSubmitting ? t('submitting') : t('signAction') }} {{ isSubmitting ? $tr('提交中...') : $tr('我已阅读并同意,去签名') }}
</view> </view>
</view> </view>
</view> </view>
@@ -83,9 +83,9 @@
<view class="fullscreen-header"> <view class="fullscreen-header">
<view class="fullscreen-back" @click="closeFullscreenPreview"> <view class="fullscreen-back" @click="closeFullscreenPreview">
<text class="tn-icon-left"></text> <text class="tn-icon-left"></text>
<text>{{ t('back') }}</text> <text>{{ $tr('返回') }}</text>
</view> </view>
<view class="fullscreen-title">{{ t('fullscreenTitle') }}</view> <view class="fullscreen-title">{{ $tr('合同全文') }}</view>
<view class="fullscreen-placeholder"></view> <view class="fullscreen-placeholder"></view>
</view> </view>
<scroll-view scroll-y scroll-x class="fullscreen-scroll" :enable-flex="true"> <scroll-view scroll-y scroll-x class="fullscreen-scroll" :enable-flex="true">
@@ -138,54 +138,6 @@ function resolveUploadedFileUrl(uploadResult) {
} }
export default { export default {
i18n: {
zh: {
navTitle: '合同签约',
overviewTitle: '签约前确认',
contractNoLabel: '合同编号:',
notAvailable: '未获取',
overviewMeta: '阅读合同正文后完成签名提交,签约结果会同步到我的合同。',
loadingTitle: '合同加载中',
loadingDesc: '正在获取合同正文,请稍候。',
fullscreenPreview: '点击全屏查看',
emptyTitle: '暂无合同正文',
emptyDesc: '当前合同暂未返回可签约正文,请稍后再试。',
footerTip: '签名前请确认您已完整阅读合同内容',
submitting: '提交中...',
signAction: '我已阅读并同意,去签名',
back: '返回',
fullscreenTitle: '合同全文',
signerMissing: '未获取到签名人姓名',
contractNoMissing: '缺少合同编号',
signingLoading: '签约提交中...',
uploadFailed: '签名上传失败',
signSuccess: '签约成功',
signFailed: '签约失败'
},
en: {
navTitle: 'Contract Signing',
overviewTitle: 'Before Signing',
contractNoLabel: 'Contract No.: ',
notAvailable: 'N/A',
overviewMeta: 'Read the contract content before submitting your signature. The result will sync to My Contracts.',
loadingTitle: 'Loading contract',
loadingDesc: 'Fetching contract content. Please wait.',
fullscreenPreview: 'Open Fullscreen',
emptyTitle: 'No contract content',
emptyDesc: 'No signable contract content is available at the moment. Please try again later.',
footerTip: 'Please confirm that you have fully read the contract before signing',
submitting: 'Submitting...',
signAction: 'I have read and agree. Sign now',
back: 'Back',
fullscreenTitle: 'Full Contract',
signerMissing: 'Missing signer name',
contractNoMissing: 'Missing contract number',
signingLoading: 'Submitting contract...',
uploadFailed: 'Failed to upload signature',
signSuccess: 'Contract signed successfully',
signFailed: 'Failed to sign contract'
}
},
components: { components: {
ContractSignDialog ContractSignDialog
}, },
@@ -209,9 +161,6 @@ export default {
this.fetchContractContent() this.fetchContractContent()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.contract_sign.contract_sign', key, params)
},
fetchContractContent() { fetchContractContent() {
if (!this.contractNo) { if (!this.contractNo) {
return Promise.resolve() return Promise.resolve()
@@ -238,14 +187,14 @@ export default {
} }
if (!this.signerName) { if (!this.signerName) {
uni.showToast({ uni.showToast({
title: this.t('signerMissing'), title: this.$tr('未获取到签名人姓名'),
icon: 'none' icon: 'none'
}) })
return return
} }
if (!this.contractNo) { if (!this.contractNo) {
uni.showToast({ uni.showToast({
title: this.t('contractNoMissing'), title: this.$tr('缺少合同编号'),
icon: 'none' icon: 'none'
}) })
return return
@@ -307,7 +256,7 @@ export default {
this.isSubmitting = true this.isSubmitting = true
uni.showLoading({ uni.showLoading({
title: this.t('signingLoading'), title: this.$tr('签约提交中...'),
mask: true mask: true
}) })
@@ -322,7 +271,7 @@ export default {
.then((uploadResult) => { .then((uploadResult) => {
const signImageUrl = resolveUploadedFileUrl(uploadResult) const signImageUrl = resolveUploadedFileUrl(uploadResult)
if (!signImageUrl) { if (!signImageUrl) {
return Promise.reject(new Error((uploadResult && uploadResult.msg) || this.t('uploadFailed'))) return Promise.reject(new Error((uploadResult && uploadResult.msg) || this.$tr('签名上传失败')))
} }
return contractSign({ return contractSign({
@@ -349,7 +298,7 @@ export default {
.then(() => { .then(() => {
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: this.t('signSuccess'), title: this.$tr('签约成功'),
icon: 'success' icon: 'success'
}) })
setTimeout(() => { setTimeout(() => {
@@ -362,7 +311,7 @@ export default {
console.error('contract sign failed', error) console.error('contract sign failed', error)
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('signFailed'), title: (error && (error.message || error.msg)) || this.$tr('签约失败'),
icon: 'none' icon: 'none'
}) })
}) })

View File

@@ -30,7 +30,7 @@
<view class="sheet-handle"></view> <view class="sheet-handle"></view>
<view class="sheet-header"> <view class="sheet-header">
<text class="sheet-title">{{ t('controlPanel') }}</text> <text class="sheet-title">{{ $tr('控制面板') }}</text>
<text class="sheet-subtitle">{{ functionCountText }}</text> <text class="sheet-subtitle">{{ functionCountText }}</text>
</view> </view>
@@ -46,7 +46,7 @@
<text class="tn-icon-set panel-icon"></text> <text class="tn-icon-set panel-icon"></text>
<text>{{ getFunctionLabel(func) }}</text> <text>{{ getFunctionLabel(func) }}</text>
</view> </view>
<view class="status-tag" v-if="!isFunctionSupported(func)">{{ t('pendingTag') }}</view> <view class="status-tag" v-if="!isFunctionSupported(func)">{{ $tr('待完善') }}</view>
</view> </view>
<!-- Switch Theme --> <!-- Switch Theme -->
@@ -60,7 +60,7 @@
<block v-else-if="isSlideFunction(func)"> <block v-else-if="isSlideFunction(func)">
<view class="slider-container"> <view class="slider-container">
<view class="slider-header"> <view class="slider-header">
<text class="slider-label">{{ t('sliderLabel') }}</text> <text class="slider-label">{{ $tr('亮度 / 数值') }}</text>
<text class="slider-value">{{ controlValue }}%</text> <text class="slider-value">{{ controlValue }}%</text>
</view> </view>
<slider <slider
@@ -76,7 +76,7 @@
@change="handleControlValueChange" @change="handleControlValueChange"
/> />
<view class="slider-apply-btn" :class="{ 'is-disabled': isSubmitting }" @click="submitSlideValue(index)"> <view class="slider-apply-btn" :class="{ 'is-disabled': isSubmitting }" @click="submitSlideValue(index)">
<text>{{ t('applySetting') }}</text> <text>{{ $tr('应用设置') }}</text>
</view> </view>
</view> </view>
</block> </block>
@@ -86,22 +86,22 @@
<view class="control-row curtain-row"> <view class="control-row curtain-row">
<view class="curtain-btn" :class="{ 'is-active': curtainAction === 'open', 'is-disabled': isSubmitting }" @click="operateCurtain(index, 'open')"> <view class="curtain-btn" :class="{ 'is-active': curtainAction === 'open', 'is-disabled': isSubmitting }" @click="operateCurtain(index, 'open')">
<text class="tn-icon-up"></text> <text class="tn-icon-up"></text>
<text class="btn-label">{{ t('curtainOpen') }}</text> <text class="btn-label">{{ $tr('打开') }}</text>
</view> </view>
<view class="curtain-btn stop-btn" :class="{ 'is-active': curtainAction === 'stop', 'is-disabled': isSubmitting }" @click="operateCurtain(index, 'stop')"> <view class="curtain-btn stop-btn" :class="{ 'is-active': curtainAction === 'stop', 'is-disabled': isSubmitting }" @click="operateCurtain(index, 'stop')">
<text class="tn-icon-minus"></text> <text class="tn-icon-minus"></text>
<text class="btn-label">{{ t('curtainPause') }}</text> <text class="btn-label">{{ $tr('暂停') }}</text>
</view> </view>
<view class="curtain-btn" :class="{ 'is-active': curtainAction === 'close', 'is-disabled': isSubmitting }" @click="operateCurtain(index, 'close')"> <view class="curtain-btn" :class="{ 'is-active': curtainAction === 'close', 'is-disabled': isSubmitting }" @click="operateCurtain(index, 'close')">
<text class="tn-icon-down"></text> <text class="tn-icon-down"></text>
<text class="btn-label">{{ t('curtainClose') }}</text> <text class="btn-label">{{ $tr('关闭') }}</text>
</view> </view>
</view> </view>
</block> </block>
<view v-else class="unsupported-box" @click="notifyUnsupportedFunction(index)"> <view v-else class="unsupported-box" @click="notifyUnsupportedFunction(index)">
<text class="tn-icon-warning"></text> <text class="tn-icon-warning"></text>
<text>{{ t('unsupportedBoxText') }}</text> <text>{{ $tr('当前功能暂未开放使用,点击反馈') }}</text>
</view> </view>
</view> </view>
@@ -173,68 +173,6 @@ function resolveRefreshIntervalMs(value, fallback = 1000) {
} }
export default { export default {
i18n: {
zh: {
controlPanel: '控制面板',
pendingTag: '待完善',
sliderLabel: '亮度 / 数值',
applySetting: '应用设置',
curtainOpen: '打开',
curtainPause: '暂停',
curtainClose: '关闭',
unsupportedBoxText: '当前功能暂未开放使用,点击反馈',
deviceDetail: '设备详情',
currentRoom: '当前房间',
functionCount: '{count}项功能',
commandSending: '指令下发中...',
syncHint: '操作完成后会自动刷新设备状态',
deviceMissing: '设备信息缺失',
unsupportedFunction: '{name}操作功能未完善',
unknownSchema: '未识别到参数模板',
schemaApplied: '{name}参数已下发',
actionSuccess: '操作成功',
deviceIdMissing: '设备ID缺失',
functionCodeMissing: '功能编码缺失',
commandFailed: '指令下发失败',
deviceClosed: '已关闭',
deviceEnabled: '已开启',
deviceOpened: '已打开',
curtainOpened: '窗帘已打开',
curtainClosed: '窗帘已关闭',
curtainPaused: '窗帘已暂停',
sliderUpdated: '滑块值已更新'
},
en: {
controlPanel: 'Control Panel',
pendingTag: 'Pending',
sliderLabel: 'Brightness / Value',
applySetting: 'Apply',
curtainOpen: 'Open',
curtainPause: 'Pause',
curtainClose: 'Close',
unsupportedBoxText: 'This function is not available yet. Tap to send feedback.',
deviceDetail: 'Device Details',
currentRoom: 'Current Room',
functionCount: '{count} functions',
commandSending: 'Sending command...',
syncHint: 'Device status refreshes automatically after each action',
deviceMissing: 'Missing device information',
unsupportedFunction: '{name} is not fully supported yet',
unknownSchema: 'No parameter template recognized',
schemaApplied: '{name} parameters sent',
actionSuccess: 'Action completed',
deviceIdMissing: 'Missing device ID',
functionCodeMissing: 'Missing function code',
commandFailed: 'Failed to send command',
deviceClosed: 'Closed',
deviceEnabled: 'Enabled',
deviceOpened: 'Opened',
curtainOpened: 'Curtain opened',
curtainClosed: 'Curtain closed',
curtainPaused: 'Curtain paused',
sliderUpdated: 'Slider value updated'
}
},
data() { data() {
return { return {
brandId: DEFAULT_BRAND_ID, brandId: DEFAULT_BRAND_ID,
@@ -257,10 +195,10 @@ export default {
return this.$getLanguage() return this.$getLanguage()
}, },
deviceName() { deviceName() {
return this.device.deviceName || this.t('deviceDetail') return this.device.deviceName || this.$tr('设备详情')
}, },
displayRoomName() { displayRoomName() {
return this.roomName || this.roomCode || this.t('currentRoom') return this.roomName || this.roomCode || this.$tr('当前房间')
}, },
controlType() { controlType() {
return inferDeviceControlType(this.device) return inferDeviceControlType(this.device)
@@ -273,16 +211,16 @@ export default {
}, },
functionCountText() { functionCountText() {
const count = this.deviceFunctions.length const count = this.deviceFunctions.length
return count ? this.t('functionCount', { count }) : this.controlTypeLabel return count ? this.$tr('{count}项功能', { count }) : this.controlTypeLabel
}, },
statusText() { statusText() {
return buildDeviceStateText(this.device) return buildDeviceStateText(this.device)
}, },
syncHintText() { syncHintText() {
if (this.isSubmitting) { if (this.isSubmitting) {
return this.t('commandSending') return this.$tr('指令下发中...')
} }
return this.t('syncHint') return this.$tr('操作完成后会自动刷新设备状态')
}, },
deviceFunctions() { deviceFunctions() {
return getDeviceFunctions(this.device) return getDeviceFunctions(this.device)
@@ -306,7 +244,7 @@ export default {
const parsedDevice = safeParseDevice(options.device) const parsedDevice = safeParseDevice(options.device)
if (!parsedDevice) { if (!parsedDevice) {
uni.showToast({ uni.showToast({
title: this.t('deviceMissing'), title: this.$tr('设备信息缺失'),
icon: 'none' icon: 'none'
}) })
return return
@@ -327,9 +265,6 @@ export default {
this.stopDevicePolling() this.stopDevicePolling()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.device_control.device_control', key, params)
},
goBack() { goBack() {
uni.navigateBack() uni.navigateBack()
}, },
@@ -613,7 +548,7 @@ export default {
? this.getDeviceFunctionByIndex(funcOrIndex) ? this.getDeviceFunctionByIndex(funcOrIndex)
: funcOrIndex : funcOrIndex
uni.showToast({ uni.showToast({
title: this.t('unsupportedFunction', { name: this.getFunctionLabel(func) }), title: this.$tr('{name}操作功能未完善', { name: this.getFunctionLabel(func) }),
icon: 'none' icon: 'none'
}) })
}, },
@@ -676,7 +611,7 @@ export default {
const fields = this.getFunctionFields(func, index) const fields = this.getFunctionFields(func, index)
if (!fields.length) { if (!fields.length) {
uni.showToast({ uni.showToast({
title: this.t('unknownSchema'), title: this.$tr('未识别到参数模板'),
icon: 'none' icon: 'none'
}) })
return return
@@ -688,11 +623,11 @@ export default {
return result return result
}, {}) }, {})
this.executeCommand(func, payload, this.t('schemaApplied', { name: this.getFunctionLabel(func) })) this.executeCommand(func, payload, this.$tr('{name}参数已下发', { name: this.getFunctionLabel(func) }))
}, },
executeCommand(func, command, successText = this.t('actionSuccess')) { executeCommand(func, command, successText = this.$tr('操作成功')) {
if (!this.device || !this.device.deviceNo) { if (!this.device || !this.device.deviceNo) {
uni.showToast({ title: this.t('deviceIdMissing'), icon: 'none' }) uni.showToast({ title: this.$tr('设备ID缺失'), icon: 'none' })
return Promise.resolve() return Promise.resolve()
} }
@@ -703,7 +638,7 @@ export default {
const functionCode = String((func && func.functionCode) || '').trim() const functionCode = String((func && func.functionCode) || '').trim()
if (!functionCode) { if (!functionCode) {
uni.showToast({ title: this.t('functionCodeMissing'), icon: 'none' }) uni.showToast({ title: this.$tr('功能编码缺失'), icon: 'none' })
return Promise.resolve() return Promise.resolve()
} }
@@ -720,7 +655,7 @@ export default {
.catch((error) => { .catch((error) => {
console.error('execute device command failed', error) console.error('execute device command failed', error)
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('commandFailed'), title: (error && (error.message || error.msg)) || this.$tr('指令下发失败'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -734,14 +669,14 @@ export default {
if (this.isSubmitting) { if (this.isSubmitting) {
return return
} }
this.executeCommand(func, this.buildSwitchCommandPayload(index, !this.device.active), this.device.active ? this.t('deviceClosed') : this.t('deviceEnabled')) this.executeCommand(func, this.buildSwitchCommandPayload(index, !this.device.active), this.device.active ? this.$tr('已关闭') : this.$tr('已开启'))
}, },
setSwitchState(index = 0, value) { setSwitchState(index = 0, value) {
const func = this.getDeviceFunctionByIndex(index) const func = this.getDeviceFunctionByIndex(index)
if (this.isSubmitting) { if (this.isSubmitting) {
return return
} }
this.executeCommand(func, this.buildSwitchCommandPayload(index, !!value), value ? this.t('deviceOpened') : this.t('deviceClosed')) this.executeCommand(func, this.buildSwitchCommandPayload(index, !!value), value ? this.$tr('已打开') : this.$tr('已关闭'))
}, },
operateCurtain(index = 0, action) { operateCurtain(index = 0, action) {
const func = this.getDeviceFunctionByIndex(index) const func = this.getDeviceFunctionByIndex(index)
@@ -751,7 +686,7 @@ export default {
this.executeCommand( this.executeCommand(
func, func,
this.buildCurtainCommandPayload(index, action), this.buildCurtainCommandPayload(index, action),
action === 'open' ? this.t('curtainOpened') : (action === 'close' ? this.t('curtainClosed') : this.t('curtainPaused')) action === 'open' ? this.$tr('窗帘已打开') : (action === 'close' ? this.$tr('窗帘已关闭') : this.$tr('窗帘已暂停'))
) )
}, },
handleControlValueChange(event) { handleControlValueChange(event) {
@@ -765,7 +700,7 @@ export default {
this.executeCommand( this.executeCommand(
func, func,
this.buildSlideCommandPayload(index, this.controlValue), this.buildSlideCommandPayload(index, this.controlValue),
this.t('sliderUpdated') this.$tr('滑块值已更新')
) )
} }
} }

View File

@@ -7,7 +7,7 @@
:customBackStyle="navBarBackStyle" :customBackStyle="navBarBackStyle"
:fixed="true" :fixed="true"
> >
<view class="tn-text-bold tn-text-xl tn-color-black" :style="{ opacity: navTitleOpacity }">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black" :style="{ opacity: navTitleOpacity }">{{ $tr('失物拾物') }}</view>
</tn-nav-bar> </tn-nav-bar>
<scroll-view class="page-scroll" scroll-y :show-scrollbar="false" @scroll="handleScroll"> <scroll-view class="page-scroll" scroll-y :show-scrollbar="false" @scroll="handleScroll">
@@ -15,76 +15,76 @@
<view class="hero-header tn-margin-bottom-xl"> <view class="hero-header tn-margin-bottom-xl">
<view class="hero-badge"> <view class="hero-badge">
<text class="badge-dot"></text> <text class="badge-dot"></text>
<text>{{ t('heroBadge') }}</text> <text>{{ $tr('社区服务') }}</text>
</view> </view>
<view class="hero-title">{{ t('heroTitle') }}</view> <view class="hero-title">{{ $tr('发布失物或招领信息') }}</view>
<view class="hero-desc">{{ t('heroDesc') }}</view> <view class="hero-desc">{{ $tr('将失物或拾获信息同步给社区,方便后续核实与快速匹配。') }}</view>
</view> </view>
<view class="form-card"> <view class="form-card">
<view class="block-title">{{ t('formTitle') }}</view> <view class="block-title">{{ $tr('物品信息') }}</view>
<view class="block-subtitle">{{ t('formDesc') }}</view> <view class="block-subtitle">{{ $tr('请补充关键字段,必填项越完整,越方便他人联系与认领。') }}</view>
<view class="form-item tn-margin-top"> <view class="form-item tn-margin-top">
<view class="form-label">{{ t('typeLabel') }}</view> <view class="form-label">{{ $tr('发布类型') }}</view>
<picker mode="selector" :range="typeLabels" @change="handleTypeChange"> <picker mode="selector" :range="typeLabels" @change="handleTypeChange">
<view class="form-picker"> <view class="form-picker">
{{ selectedTypeLabel || t('typePlaceholder') }} {{ selectedTypeLabel || $tr('请选择失物或招领') }}
</view> </view>
</picker> </picker>
</view> </view>
<view class="form-item tn-margin-top"> <view class="form-item tn-margin-top">
<view class="form-label">{{ t('itemTypeLabel') }}</view> <view class="form-label">{{ $tr('物品类型') }}</view>
<input v-model="form.itemType" class="form-input" :placeholder="t('itemTypePlaceholder')" maxlength="40" /> <input v-model="form.itemType" class="form-input" :placeholder="$tr('例如证件、钥匙、电子设备')" maxlength="40" />
</view> </view>
<view class="form-item tn-margin-top"> <view class="form-item tn-margin-top">
<view class="form-label">{{ t('itemNameLabel') }}</view> <view class="form-label">{{ $tr('物品名称') }}</view>
<input v-model="form.itemName" class="form-input" :placeholder="t('itemNamePlaceholder')" maxlength="60" /> <input v-model="form.itemName" class="form-input" :placeholder="$tr('请输入物品名称')" maxlength="60" />
</view> </view>
<view class="form-item tn-margin-top"> <view class="form-item tn-margin-top">
<view class="form-label">{{ t('placeLabel') }}</view> <view class="form-label">{{ $tr('地点') }}</view>
<input v-model="form.place" class="form-input" :placeholder="t('placePlaceholder')" maxlength="100" /> <input v-model="form.place" class="form-input" :placeholder="$tr('请输入丢失或拾取地点')" maxlength="100" />
</view> </view>
<view class="form-item tn-margin-top"> <view class="form-item tn-margin-top">
<view class="form-label">{{ t('whenLabel') }}</view> <view class="form-label">{{ $tr('时间') }}</view>
<view class="picker-row"> <view class="picker-row">
<picker mode="date" :value="form.whenDate" @change="handleDateChange"> <picker mode="date" :value="form.whenDate" @change="handleDateChange">
<view class="form-picker">{{ form.whenDate || t('selectDate') }}</view> <view class="form-picker">{{ form.whenDate || $tr('选择日期') }}</view>
</picker> </picker>
<picker mode="time" :value="form.whenTime" @change="handleTimeChange"> <picker mode="time" :value="form.whenTime" @change="handleTimeChange">
<view class="form-picker form-picker--time">{{ form.whenTime || t('selectTime') }}</view> <view class="form-picker form-picker--time">{{ form.whenTime || $tr('选择时间') }}</view>
</picker> </picker>
</view> </view>
<view class="field-tip">{{ t('whenTip') }}</view> <view class="field-tip">{{ $tr('可选,若已知大致时间可一并填写。') }}</view>
</view> </view>
<view class="form-item tn-margin-top"> <view class="form-item tn-margin-top">
<view class="form-label">{{ t('contractLabel') }}</view> <view class="form-label">{{ $tr('联系人') }}</view>
<input v-model="form.contract" class="form-input" :placeholder="t('contractPlaceholder')" maxlength="40" /> <input v-model="form.contract" class="form-input" :placeholder="$tr('请输入联系人')" maxlength="40" />
</view> </view>
<view class="form-item tn-margin-top"> <view class="form-item tn-margin-top">
<view class="form-label">{{ t('contractPhoneLabel') }}</view> <view class="form-label">{{ $tr('联系电话') }}</view>
<input v-model="form.contractPhone" class="form-input" :placeholder="t('contractPhonePlaceholder')" maxlength="30" /> <input v-model="form.contractPhone" class="form-input" :placeholder="$tr('请输入联系电话')" maxlength="30" />
</view> </view>
<view class="form-item tn-margin-top"> <view class="form-item tn-margin-top">
<view class="form-label">{{ t('remarkLabel') }}</view> <view class="form-label">{{ $tr('备注') }}</view>
<textarea <textarea
v-model="form.remark" v-model="form.remark"
class="form-textarea" class="form-textarea"
:placeholder="t('remarkPlaceholder')" :placeholder="$tr('可补充外观特征、认领说明或其他辅助信息')"
maxlength="300" maxlength="300"
></textarea> ></textarea>
<view class="field-counter">{{ (form.remark || '').length }}/300</view> <view class="field-counter">{{ (form.remark || '').length }}/300</view>
</view> </view>
<view class="form-item tn-margin-top"> <view class="form-item tn-margin-top">
<view class="form-label">{{ t('imagesLabel') }}</view> <view class="form-label">{{ $tr('现场图片') }}</view>
<tn-image-upload <tn-image-upload
:key="uploadInstanceKey" :key="uploadInstanceKey"
:fileList="form.photos" :fileList="form.photos"
@@ -104,11 +104,11 @@
<template #addBtn> <template #addBtn>
<view class="upload-trigger"> <view class="upload-trigger">
<text class="tn-icon-camera-fill upload-trigger__icon"></text> <text class="tn-icon-camera-fill upload-trigger__icon"></text>
<view class="upload-trigger__text">{{ t('uploadImage') }}</view> <view class="upload-trigger__text">{{ $tr('上传图片') }}</view>
</view> </view>
</template> </template>
</tn-image-upload> </tn-image-upload>
<view class="field-tip">{{ t('imagesTip') }}</view> <view class="field-tip">{{ $tr('可选,上传现场图或物品图可提升识别效率。') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -116,9 +116,9 @@
<view class="floating-submit-bar"> <view class="floating-submit-bar">
<view class="floating-submit-bar__inner"> <view class="floating-submit-bar__inner">
<view class="floating-submit-tip">{{ t('submitTip') }}</view> <view class="floating-submit-tip">{{ $tr('发布前请确认信息准确,系统会按当前语言提交接口。') }}</view>
<view class="submit-btn btn-primary" :class="{ 'submit-btn--disabled': isSubmitting }" @click="submitForm"> <view class="submit-btn btn-primary" :class="{ 'submit-btn--disabled': isSubmitting }" @click="submitForm">
<text class="tn-text-bold">{{ isSubmitting ? t('submitting') : t('submitButton') }}</text> <text class="tn-text-bold">{{ isSubmitting ? $tr('发布中...') : $tr('立即发布') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -180,8 +180,8 @@ export default {
}, },
typeOptions() { typeOptions() {
return [ return [
{ value: 1, label: this.t('typeLost'), labelEn: this.t('typeLost') }, { value: 1, label: this.$tr('失物'), labelEn: this.$tr('失物') },
{ value: 2, label: this.t('typeFound'), labelEn: this.t('typeFound') } { value: 2, label: this.$tr('招领'), labelEn: this.$tr('招领') }
] ]
}, },
typeLabels() { typeLabels() {
@@ -211,9 +211,6 @@ export default {
this.prefillContact() this.prefillContact()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.lost_found_publish.lost_found_publish', key, params)
},
createDefaultForm() { createDefaultForm() {
return { return {
type: 1, type: 1,
@@ -288,20 +285,20 @@ export default {
this.form.imageUrls.splice(index, 1) this.form.imageUrls.splice(index, 1)
}, },
onUploadError() { onUploadError() {
uni.showToast({ title: this.t('uploadFailed'), icon: 'none' }) uni.showToast({ title: this.$tr('图片上传失败'), icon: 'none' })
}, },
onUploadOversize() { onUploadOversize() {
uni.showToast({ title: this.t('uploadOversize'), icon: 'none' }) uni.showToast({ title: this.$tr('单张图片不能超过 5 MB'), icon: 'none' })
}, },
onUploadExceed() { onUploadExceed() {
uni.showToast({ title: this.t('uploadExceed'), icon: 'none' }) uni.showToast({ title: this.$tr('最多上传 6 张图片'), icon: 'none' })
}, },
getValidationMessage() { getValidationMessage() {
if (!String(this.form.itemType || '').trim()) return this.t('validateItemType') if (!String(this.form.itemType || '').trim()) return this.$tr('请输入物品类型')
if (!String(this.form.itemName || '').trim()) return this.t('validateItemName') if (!String(this.form.itemName || '').trim()) return this.$tr('请输入物品名称')
if (!String(this.form.place || '').trim()) return this.t('validatePlace') if (!String(this.form.place || '').trim()) return this.$tr('请输入地点')
if (!String(this.form.contract || '').trim()) return this.t('validateContract') if (!String(this.form.contract || '').trim()) return this.$tr('请输入联系人')
if (!String(this.form.contractPhone || '').trim()) return this.t('validateContractPhone') if (!String(this.form.contractPhone || '').trim()) return this.$tr('请输入联系电话')
return '' return ''
}, },
buildSubmitPayload() { buildSubmitPayload() {
@@ -335,7 +332,7 @@ export default {
lostAndFoundPublish(this.buildSubmitPayload(), this.language) lostAndFoundPublish(this.buildSubmitPayload(), this.language)
.then(() => { .then(() => {
uni.showToast({ uni.showToast({
title: this.t('submitSuccess'), title: this.$tr('发布成功'),
icon: 'success' icon: 'success'
}) })
this.resetForm() this.resetForm()
@@ -347,7 +344,7 @@ export default {
}) })
.catch((error) => { .catch((error) => {
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('submitFailed'), title: (error && (error.message || error.msg)) || this.$tr('发布失败'),
icon: 'none' icon: 'none'
}) })
}) })

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="message-detail-page tn-safe-area-inset-bottom"> <view class="message-detail-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('消息详情') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="page-main tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }"> <view class="page-main tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }">
@@ -12,7 +12,7 @@
<text :class="categoryIcon"></text> <text :class="categoryIcon"></text>
</view> </view>
</view> </view>
<view class="hero-title tn-margin-top">{{ localizedTitle || t('messageFallbackTitle') }}</view> <view class="hero-title tn-margin-top">{{ localizedTitle || $tr('消息通知') }}</view>
<view class="hero-meta-row tn-margin-top"> <view class="hero-meta-row tn-margin-top">
<view class="hero-meta-chip"> <view class="hero-meta-chip">
<text class="tn-icon-time tn-margin-right-xs"></text> <text class="tn-icon-time tn-margin-right-xs"></text>
@@ -26,7 +26,7 @@
</view> </view>
<view class="article-header tn-margin-bottom-lg"> <view class="article-header tn-margin-bottom-lg">
<view class="article-title">{{ localizedTitle || t('messageFallbackTitle') }}</view> <view class="article-title">{{ localizedTitle || $tr('消息通知') }}</view>
<view class="article-meta tn-margin-top"> <view class="article-meta tn-margin-top">
<view class="article-tag">{{ categoryText }}</view> <view class="article-tag">{{ categoryText }}</view>
<view class="article-time">{{ detail.time || '--' }}</view> <view class="article-time">{{ detail.time || '--' }}</view>
@@ -34,42 +34,42 @@
</view> </view>
<view class="content-panel tn-margin-bottom-lg"> <view class="content-panel tn-margin-bottom-lg">
<view class="panel-title">{{ t('contentTitle') }}</view> <view class="panel-title">{{ $tr('通知内容') }}</view>
<rich-text class="article-content tn-margin-top" :nodes="contentNodes"></rich-text> <rich-text class="article-content tn-margin-top" :nodes="contentNodes"></rich-text>
</view> </view>
<view v-if="localizedDesc && localizedDesc !== localizedContent" class="content-panel tn-margin-bottom-lg"> <view v-if="localizedDesc && localizedDesc !== localizedContent" class="content-panel tn-margin-bottom-lg">
<view class="panel-title">{{ t('summaryTitle') }}</view> <view class="panel-title">{{ $tr('摘要') }}</view>
<view class="panel-desc tn-margin-top">{{ localizedDesc }}</view> <view class="panel-desc tn-margin-top">{{ localizedDesc }}</view>
</view> </view>
<view class="meta-panel tn-margin-bottom-lg"> <view class="meta-panel tn-margin-bottom-lg">
<view class="meta-row"> <view class="meta-row">
<text class="meta-label">{{ t('sourceLabel') }}</text> <text class="meta-label">{{ $tr('消息来源') }}</text>
<text class="meta-value">{{ sourceText }}</text> <text class="meta-value">{{ sourceText }}</text>
</view> </view>
<view class="meta-row"> <view class="meta-row">
<text class="meta-label">{{ t('categoryLabel') }}</text> <text class="meta-label">{{ $tr('消息分类') }}</text>
<text class="meta-value">{{ categoryText }}</text> <text class="meta-value">{{ categoryText }}</text>
</view> </view>
<view v-if="detail.code" class="meta-row"> <view v-if="detail.code" class="meta-row">
<text class="meta-label">{{ t('codeLabel') }}</text> <text class="meta-label">{{ $tr('通知编码') }}</text>
<view class="meta-action-wrap"> <view class="meta-action-wrap">
<text class="meta-value">{{ detail.code }}</text> <text class="meta-value">{{ detail.code }}</text>
<view class="copy-btn" @click="copyCode"> <view class="copy-btn" @click="copyCode">
<text class="tn-text-xs tn-text-bold">{{ t('copy') }}</text> <text class="tn-text-xs tn-text-bold">{{ $tr('复制') }}</text>
</view> </view>
</view> </view>
</view> </view>
<view v-if="detail.statusHint" class="meta-row"> <view v-if="detail.statusHint" class="meta-row">
<text class="meta-label">{{ t('statusLabel') }}</text> <text class="meta-label">{{ $tr('当前状态') }}</text>
<text class="meta-value">{{ detail.statusHint }}</text> <text class="meta-value">{{ detail.statusHint }}</text>
</view> </view>
</view> </view>
<view class="action-row"> <view class="action-row">
<view class="ghost-btn" @click="goBackList"> <view class="ghost-btn" @click="goBackList">
<text class="tn-text-bold">{{ t('backList') }}</text> <text class="tn-text-bold">{{ $tr('返回消息页') }}</text>
</view> </view>
<view v-if="detail.path" class="action-btn" @click="handleGoBusiness"> <view v-if="detail.path" class="action-btn" @click="handleGoBusiness">
<text class="tn-text-bold">{{ actionButtonText }}</text> <text class="tn-text-bold">{{ actionButtonText }}</text>
@@ -111,58 +111,6 @@ function normalizeContentToRichText(value = '', emptyText = '') {
} }
export default { export default {
i18n: {
zh: {
navTitle: '消息详情',
messageFallbackTitle: '消息通知',
contentTitle: '通知内容',
summaryTitle: '摘要',
sourceLabel: '消息来源',
categoryLabel: '消息分类',
codeLabel: '通知编码',
copy: '复制',
statusLabel: '当前状态',
backList: '返回消息页',
categorySystem: '系统通知',
categoryPayment: '缴费账单',
categoryButler: '管家消息',
actionSystem: '前往相关页面',
actionPayment: '去处理账单',
actionButler: '去查看服务',
actionDefault: '继续前往业务页',
remoteSource: '系统通知接口',
localSource: '本地业务消息',
expiredDesc: '当前消息详情已失效,请返回消息中心重新打开。',
expiredHint: '消息缓存已失效',
codeCopied: '通知编号已复制',
emptyContent: '暂无详细内容'
},
en: {
navTitle: 'Message Detail',
messageFallbackTitle: 'Notification',
contentTitle: 'Content',
summaryTitle: 'Summary',
sourceLabel: 'Source',
categoryLabel: 'Category',
codeLabel: 'Notice Code',
copy: 'Copy',
statusLabel: 'Status',
backList: 'Back to Messages',
categorySystem: 'System Notice',
categoryPayment: 'Payment Bill',
categoryButler: 'Butler Message',
actionSystem: 'Open Related Page',
actionPayment: 'Handle Bill',
actionButler: 'View Service',
actionDefault: 'Continue to Page',
remoteSource: 'Remote Notification API',
localSource: 'Local Business Message',
expiredDesc: 'This message detail has expired. Please return to the message center and open it again.',
expiredHint: 'Message cache expired',
codeCopied: 'Notice code copied',
emptyContent: 'No detailed content available'
}
},
data() { data() {
return { return {
lookupId: '', lookupId: '',
@@ -205,14 +153,14 @@ export default {
}, },
categoryText() { categoryText() {
const map = { const map = {
system: this.t('categorySystem'), system: this.$tr('系统通知'),
payment: this.t('categoryPayment'), payment: this.$tr('缴费账单'),
butler: this.t('categoryButler') butler: this.$tr('管家消息')
} }
return map[this.detail.category] || this.t('messageFallbackTitle') return map[this.detail.category] || this.$tr('消息通知')
}, },
contentNodes() { contentNodes() {
return normalizeContentToRichText(this.localizedContent || this.localizedDesc || '', this.t('emptyContent')) return normalizeContentToRichText(this.localizedContent || this.localizedDesc || '', this.$tr('暂无详细内容'))
}, },
categoryIcon() { categoryIcon() {
const map = { const map = {
@@ -240,14 +188,14 @@ export default {
}, },
actionButtonText() { actionButtonText() {
const map = { const map = {
system: this.t('actionSystem'), system: this.$tr('前往相关页面'),
payment: this.t('actionPayment'), payment: this.$tr('去处理账单'),
butler: this.t('actionButler') butler: this.$tr('去查看服务')
} }
return map[this.detail.category] || this.t('actionDefault') return map[this.detail.category] || this.$tr('继续前往业务页')
}, },
sourceText() { sourceText() {
return this.detail.source === 'remote' ? this.t('remoteSource') : this.t('localSource') return this.detail.source === 'remote' ? this.$tr('系统通知接口') : this.$tr('本地业务消息')
} }
}, },
onLoad(options = {}) { onLoad(options = {}) {
@@ -264,9 +212,6 @@ export default {
clearMessageDetailPayload(this.language) clearMessageDetailPayload(this.language)
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.message_detail.message_detail', key, params)
},
restoreDetail() { restoreDetail() {
const payload = getMessageDetailPayload(this.language) const payload = getMessageDetailPayload(this.language)
const fallbackPayload = findMessageDetailPayload(this.lookupId, this.lookupCode, this.language) const fallbackPayload = findMessageDetailPayload(this.lookupId, this.lookupCode, this.language)
@@ -284,17 +229,17 @@ export default {
...this.detail, ...this.detail,
id: this.lookupId || this.lookupCode || '', id: this.lookupId || this.lookupCode || '',
code: this.lookupCode || '', code: this.lookupCode || '',
titleZh: this.t('messageFallbackTitle'), titleZh: this.$tr('消息通知'),
titleEn: this.t('messageFallbackTitle'), titleEn: this.$tr('消息通知'),
descZh: this.t('expiredDesc'), descZh: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
descEn: this.t('expiredDesc'), descEn: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
contentZh: this.t('expiredDesc'), contentZh: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
contentEn: this.t('expiredDesc'), contentEn: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
title: this.t('messageFallbackTitle'), title: this.$tr('消息通知'),
desc: this.t('expiredDesc'), desc: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
content: this.t('expiredDesc'), content: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
source: 'remote', source: 'remote',
statusHint: this.t('expiredHint') statusHint: this.$tr('消息缓存已失效')
} }
}, },
goBackList() { goBackList() {
@@ -308,7 +253,7 @@ export default {
data: String(this.detail.code), data: String(this.detail.code),
success: () => { success: () => {
uni.showToast({ uni.showToast({
title: this.t('codeCopied'), title: this.$tr('通知编号已复制'),
icon: 'success' icon: 'success'
}) })
} }

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="my-application-page tn-safe-area-inset-bottom"> <view class="my-application-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('我的申请') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="page-main tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }"> <view class="page-main tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }">
@@ -20,7 +20,7 @@
</scroll-view> </scroll-view>
<view class="toolbar-row tn-margin-bottom-lg"> <view class="toolbar-row tn-margin-bottom-lg">
<view class="toolbar-summary">{{ t('toolbarSummary', { total }) }}</view> <view class="toolbar-summary">{{ $tr('共 {total} 条申请', { total }) }}</view>
<view <view
class="refresh-btn" class="refresh-btn"
:class="{ 'refresh-btn--loading': loading }" :class="{ 'refresh-btn--loading': loading }"
@@ -30,7 +30,7 @@
class="tn-text-bold tn-flex tn-flex-row-center tn-flex-col-center" class="tn-text-bold tn-flex tn-flex-row-center tn-flex-col-center"
> >
<text v-if="loading" class="tn-icon-loading tn-margin-right-xs spin"></text> <text v-if="loading" class="tn-icon-loading tn-margin-right-xs spin"></text>
{{ loading ? t('refreshing') : t('refresh') }} {{ loading ? $tr('刷新中...') : $tr('刷新状态') }}
</text> </text>
</view> </view>
</view> </view>
@@ -54,23 +54,23 @@
<view class="card-body tn-margin-top"> <view class="card-body tn-margin-top">
<view class="info-row tn-flex tn-flex-row-between"> <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> <text class="info-value">{{ item.intendedAreaName || '--' }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <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> <text class="info-value">{{ item.userName || item.phone || '--' }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <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> <text class="info-value">{{ formatDateRange(item.start, item.end) }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <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> <text class="info-value">{{ formatDateTime(item.createTime) }}</text>
</view> </view>
<view v-if="item.remark" class="remark-panel tn-margin-top"> <view v-if="item.remark" class="remark-panel tn-margin-top">
<view class="remark-label">{{ t('remarkLabel') }}</view> <view class="remark-label">{{ $tr('补充说明') }}</view>
<view class="remark-text tn-margin-top-xs">{{ item.remark }}</view> <view class="remark-text tn-margin-top-xs">{{ item.remark }}</view>
</view> </view>
</view> </view>
@@ -80,29 +80,29 @@
:data-reference-type-no="resolveReferenceTypeNo(item)" :data-reference-type-no="resolveReferenceTypeNo(item)"
@click="goToBillPage" @click="goToBillPage"
> >
<text class="tn-text-bold">{{ t('viewBill') }}</text> <text class="tn-text-bold">{{ $tr('查看关联账单') }}</text>
</view> </view>
</view> </view>
</view> </view>
<view class="load-state"> <view class="load-state">
<text v-if="loadingMore">{{ t('loadingMore') }}</text> <text v-if="loadingMore">{{ $tr('加载中...') }}</text>
<text v-else-if="!hasMore">{{ t('noMore') }}</text> <text v-else-if="!hasMore">{{ $tr('没有更多申请了') }}</text>
<text v-else>{{ t('pullUpMore') }}</text> <text v-else>{{ $tr('上拉加载更多') }}</text>
</view> </view>
</view> </view>
<view v-else-if="!loading" class="empty-panel tn-padding-lg"> <view v-else-if="!loading" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('emptyTitle') }}</view> <view class="empty-title">{{ $tr('暂无申请记录') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('emptyDesc') }}</view> <view class="empty-desc tn-margin-top-sm">{{ $tr('您还没有提交任何申请,去选房后可发起入住申请或预约看房。') }}</view>
<view class="action-btn tn-margin-top-lg" @click="goSelectRoom"> <view class="action-btn tn-margin-top-lg" @click="goSelectRoom">
<text class="tn-text-bold">{{ t('goSelectRoom') }}</text> <text class="tn-text-bold">{{ $tr('去选房') }}</text>
</view> </view>
</view> </view>
<view v-if="loading && !list.length" class="empty-panel tn-padding-lg"> <view v-if="loading && !list.length" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('loadingTitle') }}</view> <view class="empty-title">{{ $tr('申请列表加载中') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('loadingDesc') }}</view> <view class="empty-desc tn-margin-top-sm">{{ $tr('正在同步您的入住申请记录,请稍候。') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -169,76 +169,6 @@ function normalizeApplicationList(list = []) {
} }
export default { export default {
i18n: {
zh: {
navTitle: '我的申请',
toolbarSummary: '共 {total} 条申请',
refreshing: '刷新中...',
refresh: '刷新状态',
apartmentLabel: '所属公寓',
occupantLabel: '入住人',
viewingTimeLabel: '预约时间',
leaseTermLabel: '预计租期',
submittedAtLabel: '提交时间',
remarkLabel: '补充说明',
viewBill: '查看关联账单',
loadingMore: '加载中...',
noMore: '没有更多申请了',
pullUpMore: '上拉加载更多',
emptyTitle: '暂无申请记录',
emptyDesc: '您还没有提交任何申请,去选房后可发起入住申请或预约看房。',
goSelectRoom: '去选房',
loadingTitle: '申请列表加载中',
loadingDesc: '正在同步您的入住申请记录,请稍候。',
statusAll: '全部',
statusPending: '待审核',
statusApproved: '已通过',
statusRejected: '已拒绝',
statusFinished: '已结束',
statusProcessing: '处理中',
refreshSuccess: '申请状态已刷新',
loadError: '申请记录加载失败',
viewingApplicationTitle: '预约看房申请',
stayApplicationTitle: '入住申请',
missingBillCode: '申请单编号缺失',
missingApplicationCode: '申请编号缺失',
dateRangeJoiner: ' 至 '
},
en: {
navTitle: 'My Applications',
toolbarSummary: '{total} applications',
refreshing: 'Refreshing...',
refresh: 'Refresh Status',
apartmentLabel: 'Apartment',
occupantLabel: 'Resident',
viewingTimeLabel: 'Viewing Time',
leaseTermLabel: 'Lease Term',
submittedAtLabel: 'Submitted At',
remarkLabel: 'Remarks',
viewBill: 'View Related Bill',
loadingMore: 'Loading...',
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',
loadingTitle: 'Loading applications',
loadingDesc: 'Syncing your application records. Please wait.',
statusAll: 'All',
statusPending: 'Pending',
statusApproved: 'Approved',
statusRejected: 'Rejected',
statusFinished: 'Closed',
statusProcessing: 'Processing',
refreshSuccess: 'Application status refreshed',
loadError: 'Failed to load applications',
viewingApplicationTitle: 'Viewing Request',
stayApplicationTitle: 'Stay Application',
missingBillCode: 'Missing application bill number',
missingApplicationCode: 'Missing application number',
dateRangeJoiner: ' to '
}
},
data() { data() {
return { return {
status: '', status: '',
@@ -278,26 +208,23 @@ export default {
} }
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.my_application.my_application', key, params)
},
syncStatusOptions() { syncStatusOptions() {
this.statusOptions = [ this.statusOptions = [
{ label: this.t('statusAll'), value: '' }, { label: this.$tr('全部'), value: '' },
{ label: this.t('statusPending'), value: 0 }, { label: this.$tr('待审核'), value: 0 },
{ label: this.t('statusApproved'), value: 10 }, { label: this.$tr('已通过'), value: 10 },
{ label: this.t('statusRejected'), value: 20 }, { label: this.$tr('已拒绝'), value: 20 },
{ label: this.t('statusFinished'), value: 99 } { label: this.$tr('已结束'), value: 99 }
] ]
}, },
getStatusText(status) { getStatusText(status) {
const map = { const map = {
0: this.t('statusPending'), 0: this.$tr('待审核'),
10: this.t('statusApproved'), 10: this.$tr('已通过'),
20: this.t('statusRejected'), 20: this.$tr('已拒绝'),
99: this.t('statusFinished') 99: this.$tr('已结束')
} }
return map[status] || this.t('statusProcessing') return map[status] || this.$tr('处理中')
}, },
getDisplayStatusText(item = {}) { getDisplayStatusText(item = {}) {
if (this.language !== 'en' && item.statusDesc) { if (this.language !== 'en' && item.statusDesc) {
@@ -338,10 +265,10 @@ export default {
const startText = this.formatDateTime(start) const startText = this.formatDateTime(start)
const endText = this.formatDateTime(end) const endText = this.formatDateTime(end)
if (String(start).includes('T') || String(end).includes('T')) { 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)}`
}, },
async refreshStatus() { async refreshStatus() {
if (this.loading || this.loadingMore) { if (this.loading || this.loadingMore) {
@@ -350,7 +277,7 @@ export default {
await this.reloadList() await this.reloadList()
if (this.list.length || this.total === 0) { if (this.list.length || this.total === 0) {
uni.showToast({ uni.showToast({
title: this.t('refreshSuccess'), title: this.$tr('申请状态已刷新'),
icon: 'none' icon: 'none'
}) })
} }
@@ -407,7 +334,7 @@ export default {
this.pageIndex = Math.max(1, this.pageIndex - 1) this.pageIndex = Math.max(1, this.pageIndex - 1)
} }
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('loadError'), title: (error && (error.message || error.msg)) || this.$tr('申请记录加载失败'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -431,9 +358,9 @@ export default {
}, },
getApplicationTitle(item = {}) { getApplicationTitle(item = {}) {
if (item.isViewingApplication) { if (item.isViewingApplication) {
return this.t('viewingApplicationTitle') return this.$tr('预约看房申请')
} }
return item.intendedRoomTypeName || this.t('stayApplicationTitle') return item.intendedRoomTypeName || this.$tr('入住申请')
}, },
resolveReferenceTypeNo(item = {}) { resolveReferenceTypeNo(item = {}) {
return item.detailCode || '' return item.detailCode || ''
@@ -495,7 +422,7 @@ export default {
: '' : ''
if (!referenceTypeNo) { if (!referenceTypeNo) {
uni.showToast({ uni.showToast({
title: this.t('missingBillCode'), title: this.$tr('申请单编号缺失'),
icon: 'none' icon: 'none'
}) })
return return

View File

@@ -1,15 +1,15 @@
<template> <template>
<view class="my-contract-page tn-safe-area-inset-bottom"> <view class="my-contract-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('我的合同') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="page-main" :style="{ paddingTop: vuex_custom_bar_height + 'px' }"> <view class="page-main" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
<view class="overview-panel"> <view class="overview-panel">
<view class="overview-header"> <view class="overview-header">
<view> <view>
<view class="overview-title">{{ t('overviewTitle') }}</view> <view class="overview-title">{{ $tr('合同概览') }}</view>
<view class="overview-subtitle">{{ t('overviewSubtitle') }}</view> <view class="overview-subtitle">{{ $tr('查看住宿合同、签约状态与有效期限') }}</view>
</view> </view>
<view class="overview-badge"> <view class="overview-badge">
<text class="tn-icon-edit-form"></text> <text class="tn-icon-edit-form"></text>
@@ -18,11 +18,11 @@
</view> </view>
<view class="overview-metrics"> <view class="overview-metrics">
<view class="metric-item"> <view class="metric-item">
<view class="metric-label">{{ t('pendingMetricLabel') }}</view> <view class="metric-label">{{ $tr('待签约/审批') }}</view>
<view class="metric-value">{{ summaryStats.pending }}</view> <view class="metric-value">{{ summaryStats.pending }}</view>
</view> </view>
<view class="metric-item is-primary"> <view class="metric-item is-primary">
<view class="metric-label">{{ t('displayMetricLabel') }}</view> <view class="metric-label">{{ $tr('当前展示') }}</view>
<view class="metric-value">{{ displayList.length }}</view> <view class="metric-value">{{ displayList.length }}</view>
</view> </view>
</view> </view>
@@ -41,8 +41,8 @@
</view> </view>
<view v-if="loading" class="empty-panel"> <view v-if="loading" class="empty-panel">
<view class="empty-title">{{ t('loadingTitle') }}</view> <view class="empty-title">{{ $tr('合同记录加载中') }}</view>
<view class="empty-desc">{{ t('loadingDesc') }}</view> <view class="empty-desc">{{ $tr('正在同步您的合同数据,请稍候。') }}</view>
</view> </view>
<view v-else-if="displayList.length" class="list-wrapper"> <view v-else-if="displayList.length" class="list-wrapper">
@@ -58,7 +58,7 @@
<text class="tn-icon-edit-write-fill"></text> <text class="tn-icon-edit-write-fill"></text>
</view> </view>
<view class="card-title-group"> <view class="card-title-group">
<view class="card-title">{{ t('contractTitle') }}</view> <view class="card-title">{{ $tr('住宿合同') }}</view>
<view class="card-code">{{ item.no || item.contractNo || '--' }}</view> <view class="card-code">{{ item.no || item.contractNo || '--' }}</view>
</view> </view>
</view> </view>
@@ -70,26 +70,26 @@
<view class="meta-strip"> <view class="meta-strip">
<view class="meta-chip"> <view class="meta-chip">
<view class="meta-chip-label">{{ t('relatedOrderLabel') }}</view> <view class="meta-chip-label">{{ $tr('关联订单') }}</view>
<view class="meta-chip-value">{{ item.orderNo || '--' }}</view> <view class="meta-chip-value">{{ item.orderNo || '--' }}</view>
</view> </view>
<view class="meta-chip"> <view class="meta-chip">
<view class="meta-chip-label">{{ t('signStatusLabel') }}</view> <view class="meta-chip-label">{{ $tr('签署状态') }}</view>
<view class="meta-chip-value">{{ item.signImageUrl ? t('signed') : t('unsigned') }}</view> <view class="meta-chip-value">{{ item.signImageUrl ? $tr('已签名') : $tr('待签名') }}</view>
</view> </view>
</view> </view>
<view class="detail-panel"> <view class="detail-panel">
<view class="info-row"> <view class="info-row">
<text class="info-label">{{ t('contractPeriodLabel') }}</text> <text class="info-label">{{ $tr('合同期限') }}</text>
<text class="info-value">{{ formatDateRange(item.start, item.end) }}</text> <text class="info-value">{{ formatDateRange(item.start, item.end) }}</text>
</view> </view>
<view class="info-row"> <view class="info-row">
<text class="info-label">{{ t('recordTipLabel') }}</text> <text class="info-label">{{ $tr('记录说明') }}</text>
<text class="info-value">{{ getStatusTip(item) }}</text> <text class="info-value">{{ getStatusTip(item) }}</text>
</view> </view>
<view v-if="item.signImageUrl" class="sign-row"> <view v-if="item.signImageUrl" class="sign-row">
<text class="info-label">{{ t('mySignatureLabel') }}</text> <text class="info-label">{{ $tr('我的签名') }}</text>
<view class="sign-preview"> <view class="sign-preview">
<image :src="item.signImageUrl" mode="aspectFit"></image> <image :src="item.signImageUrl" mode="aspectFit"></image>
</view> </view>
@@ -112,8 +112,8 @@
<view class="empty-icon"> <view class="empty-icon">
<text class="tn-icon-folder"></text> <text class="tn-icon-folder"></text>
</view> </view>
<view class="empty-title">{{ t('emptyTitle') }}</view> <view class="empty-title">{{ $tr('暂无对应的合同记录') }}</view>
<view class="empty-desc">{{ t('emptyDesc') }}</view> <view class="empty-desc">{{ $tr('切换筛选条件,或等待系统生成新的合同数据。') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -133,74 +133,6 @@ function normalizeContract(item = {}) {
} }
export default { export default {
i18n: {
zh: {
navTitle: '我的合同',
overviewTitle: '合同概览',
overviewSubtitle: '查看住宿合同、签约状态与有效期限',
pendingMetricLabel: '待签约/审批',
displayMetricLabel: '当前展示',
loadingTitle: '合同记录加载中',
loadingDesc: '正在同步您的合同数据,请稍候。',
contractTitle: '住宿合同',
relatedOrderLabel: '关联订单',
signStatusLabel: '签署状态',
signed: '已签名',
unsigned: '待签名',
contractPeriodLabel: '合同期限',
recordTipLabel: '记录说明',
mySignatureLabel: '我的签名',
emptyTitle: '暂无对应的合同记录',
emptyDesc: '切换筛选条件,或等待系统生成新的合同数据。',
tabAll: '全部',
tabPending: '待签约',
statusRejected: '已拒绝',
statusPending: '待审批',
statusApproved: '已通过',
statusProcessing: '处理中',
dateRangeJoiner: ' 至 ',
tipApproved: '合同已生效,可查看签约结果。',
tipNeedSign: '待您完成签名后进入审批流程。',
tipPending: '已提交签约信息,等待审批结果。',
tipRejected: '合同未通过审批,请联系管理员处理。',
tipDefault: '请关注合同最新状态。',
actionSign: '去签约',
actionViewDetail: '查看详情'
},
en: {
navTitle: 'My Contracts',
overviewTitle: 'Contract Overview',
overviewSubtitle: 'Check stay contracts, signing status, and effective period',
pendingMetricLabel: 'Pending Sign / Review',
displayMetricLabel: 'Displayed',
loadingTitle: 'Loading contracts',
loadingDesc: 'Syncing your contract data. Please wait.',
contractTitle: 'Stay Contract',
relatedOrderLabel: 'Related Order',
signStatusLabel: 'Signing Status',
signed: 'Signed',
unsigned: 'Unsigned',
contractPeriodLabel: 'Contract Period',
recordTipLabel: 'Notes',
mySignatureLabel: 'My Signature',
emptyTitle: 'No contract record found',
emptyDesc: 'Switch filters or wait for new contract data to be generated.',
tabAll: 'All',
tabPending: 'Pending Sign',
statusRejected: 'Rejected',
statusPending: 'Pending Review',
statusApproved: 'Approved',
statusProcessing: 'Processing',
dateRangeJoiner: ' to ',
tipApproved: 'The contract is effective now. You can view the signing result.',
tipNeedSign: 'Please complete your signature to enter the review process.',
tipPending: 'Signing information has been submitted and is waiting for review.',
tipRejected: 'The contract did not pass review. Please contact the administrator.',
tipDefault: 'Please pay attention to the latest contract status.',
actionSign: 'Sign Now',
actionViewDetail: 'View Detail'
}
},
data() { data() {
return { return {
currentTab: 'all', currentTab: 'all',
@@ -241,13 +173,10 @@ export default {
} }
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.my_contract.my_contract', key, params)
},
syncTabs() { syncTabs() {
this.tabs = [ this.tabs = [
{ label: this.t('tabAll'), value: 'all' }, { label: this.$tr('全部'), value: 'all' },
{ label: this.t('tabPending'), value: 'pending' } { label: this.$tr('待签约'), value: 'pending' }
] ]
}, },
getStoreUser() { getStoreUser() {
@@ -316,11 +245,11 @@ export default {
}, },
getStatusText(status) { getStatusText(status) {
const map = { const map = {
'-1': this.t('statusRejected'), '-1': this.$tr('已拒绝'),
'0': this.t('statusPending'), '0': this.$tr('待审批'),
'1': this.t('statusApproved') '1': this.$tr('已通过')
} }
return map[String(status)] || this.t('statusProcessing') return map[String(status)] || this.$tr('处理中')
}, },
getDisplayStatusText(item = {}) { getDisplayStatusText(item = {}) {
if (this.language !== 'en' && item.approvalStatusDesc) { if (this.language !== 'en' && item.approvalStatusDesc) {
@@ -340,28 +269,28 @@ export default {
if (!start && !end) { if (!start && !end) {
return '--' return '--'
} }
return `${this.formatDate(start)}${this.t('dateRangeJoiner')}${this.formatDate(end)}` return `${this.formatDate(start)}${this.$tr(' 至 ')}${this.formatDate(end)}`
}, },
getStatusTip(item = {}) { getStatusTip(item = {}) {
if (Number(item.approvalStatus) === 1) { if (Number(item.approvalStatus) === 1) {
return this.t('tipApproved') return this.$tr('合同已生效,可查看签约结果。')
} }
if (Number(item.approvalStatus) === 0 && !item.signImageUrl) { if (Number(item.approvalStatus) === 0 && !item.signImageUrl) {
return this.t('tipNeedSign') return this.$tr('待您完成签名后进入审批流程。')
} }
if (Number(item.approvalStatus) === 0) { if (Number(item.approvalStatus) === 0) {
return this.t('tipPending') return this.$tr('已提交签约信息,等待审批结果。')
} }
if (Number(item.approvalStatus) === -1) { if (Number(item.approvalStatus) === -1) {
return this.t('tipRejected') return this.$tr('合同未通过审批,请联系管理员处理。')
} }
return this.t('tipDefault') return this.$tr('请关注合同最新状态。')
}, },
resolveActionText(item = {}) { resolveActionText(item = {}) {
if (this.shouldShowSignAction(item)) { if (this.shouldShowSignAction(item)) {
return this.t('actionSign') return this.$tr('去签约')
} }
return this.t('actionViewDetail') return this.$tr('查看详情')
}, },
shouldShowSignAction(item = {}) { shouldShowSignAction(item = {}) {
return Number(item.approvalStatus) === 0 && !item.signImageUrl return Number(item.approvalStatus) === 0 && !item.signImageUrl

View File

@@ -7,7 +7,7 @@
<!-- Header --> <!-- Header -->
<view class="page-header" :style="{ paddingTop: vuex_custom_bar_height ? 'calc(' + vuex_custom_bar_height + 'px + 32rpx)' : '32rpx' }"> <view class="page-header" :style="{ paddingTop: vuex_custom_bar_height ? 'calc(' + vuex_custom_bar_height + 'px + 32rpx)' : '32rpx' }">
<view class="title-wrap"> <view class="title-wrap">
<text class="title-zh">{{ t('pageTitle') }}</text> <text class="title-zh">{{ $tr('验房') }}</text>
<text class="title-en">Inspection</text> <text class="title-en">Inspection</text>
</view> </view>
<view v-if="activeInspection" class="status-tag"> <view v-if="activeInspection" class="status-tag">
@@ -37,15 +37,15 @@
<!-- High Density Info Grid --> <!-- High Density Info Grid -->
<view class="info-grid"> <view class="info-grid">
<view class="info-cell"> <view class="info-cell">
<text class="info-label">{{ t('roomNoLabel') }}</text> <text class="info-label">{{ $tr('房间号') }}</text>
<text class="info-value font-mono">{{ activeInspectionDetail.roomNo || '--' }}</text> <text class="info-value font-mono">{{ activeInspectionDetail.roomNo || '--' }}</text>
</view> </view>
<view class="info-cell"> <view class="info-cell">
<text class="info-label">{{ t('inspectionTypeLabel') }}</text> <text class="info-label">{{ $tr('验房类型') }}</text>
<text class="info-value">{{ activeInspectionDetail.typeText }}</text> <text class="info-value">{{ activeInspectionDetail.typeText }}</text>
</view> </view>
<view class="info-cell full-width"> <view class="info-cell full-width">
<text class="info-label">{{ t('inspectionTimeLabel') }}</text> <text class="info-label">{{ $tr('发起时间') }}</text>
<text class="info-value font-mono">{{ activeInspectionDetail.createTimeText }}</text> <text class="info-value font-mono">{{ activeInspectionDetail.createTimeText }}</text>
</view> </view>
</view> </view>
@@ -55,7 +55,7 @@
<textarea <textarea
class="remarks-input" class="remarks-input"
:value="remarksDraft" :value="remarksDraft"
:placeholder="t('inspectionRemarksPlaceholder')" :placeholder="$tr('可填写本次验房的补充说明')"
maxlength="300" maxlength="300"
auto-height auto-height
@input="handleRemarksInput" @input="handleRemarksInput"
@@ -65,7 +65,7 @@
<!-- Items --> <!-- Items -->
<view v-if="activeInspectionDetail.items.length" class="items-wrap"> <view v-if="activeInspectionDetail.items.length" class="items-wrap">
<view class="section-title"> <view class="section-title">
<text class="section-title-zh">{{ t('inspectionItemsTitle') }}</text> <text class="section-title-zh">{{ $tr('检查项') }}</text>
<text class="section-title-en">Items</text> <text class="section-title-en">Items</text>
</view> </view>
@@ -77,34 +77,34 @@
> >
<view class="item-header"> <view class="item-header">
<view class="item-title-col"> <view class="item-title-col">
<text class="item-name">{{ item.deviceName || t('deviceFallback') }}</text> <text class="item-name">{{ item.deviceName || $tr('未命名设备') }}</text>
<text class="item-meta" v-if="item.assetSourceText || item.brandSnapshot"> <text class="item-meta" v-if="item.assetSourceText || item.brandSnapshot">
{{ [item.assetSourceText, item.brandSnapshot].filter(Boolean).join(' / ') }} {{ [item.assetSourceText, item.brandSnapshot].filter(Boolean).join(' / ') }}
</text> </text>
</view> </view>
<text v-if="item.isRequiredSnapshot" class="item-required">{{ t('requiredTag') }}</text> <text v-if="item.isRequiredSnapshot" class="item-required">{{ $tr('必查') }}</text>
</view> </view>
<view class="item-body"> <view class="item-body">
<view class="row-flex"> <view class="row-flex">
<view class="col-half"> <view class="col-half">
<text class="field-label">{{ t('expectedQuantityLabel') }}</text> <text class="field-label">{{ $tr('应有数量') }}</text>
<text class="field-value font-mono">{{ item.expectedQuantityText }}</text> <text class="field-value font-mono">{{ item.expectedQuantityText }}</text>
</view> </view>
<view class="col-half"> <view class="col-half">
<text class="field-label">{{ t('checkedQuantityLabel') }}</text> <text class="field-label">{{ $tr('实测数量') }}</text>
<input <input
class="field-input font-mono" class="field-input font-mono"
type="number" type="number"
:value="item.checkedQuantityDraft" :value="item.checkedQuantityDraft"
:placeholder="t('checkedQuantityPlaceholder')" :placeholder="$tr('请输入数量')"
@input="updateItemField(index, 'checkedQuantityDraft', $event.detail.value)" @input="updateItemField(index, 'checkedQuantityDraft', $event.detail.value)"
/> />
</view> </view>
</view> </view>
<view class="field-row mt-20"> <view class="field-row mt-20">
<text class="field-label">{{ t('inspectionResultLabel') }}</text> <text class="field-label">{{ $tr('检查结果') }}</text>
<view class="radio-group"> <view class="radio-group">
<view <view
v-for="option in inspectionResultOptions" v-for="option in inspectionResultOptions"
@@ -122,7 +122,7 @@
<textarea <textarea
class="field-textarea" class="field-textarea"
:value="item.problemDetailDraft" :value="item.problemDetailDraft"
:placeholder="t('problemDetailPlaceholder')" :placeholder="$tr('请描述异常、缺失或损坏情况')"
maxlength="300" maxlength="300"
auto-height auto-height
@input="updateItemField(index, 'problemDetailDraft', $event.detail.value)" @input="updateItemField(index, 'problemDetailDraft', $event.detail.value)"
@@ -155,25 +155,25 @@
</view> </view>
<view v-else-if="!loading" class="empty-state"> <view v-else-if="!loading" class="empty-state">
<text class="empty-title">{{ t('emptyItemsTitle') }}</text> <text class="empty-title">{{ $tr('暂无检查项') }}</text>
<text class="empty-desc">{{ t('emptyItemsDesc') }}</text> <text class="empty-desc">{{ $tr('当前验房单还没有检查项明细。') }}</text>
</view> </view>
</view> </view>
<view v-if="!loading && !inspectionList.length" class="empty-state"> <view v-if="!loading && !inspectionList.length" class="empty-state">
<text class="empty-title">{{ t('emptyTitle') }}</text> <text class="empty-title">{{ $tr('暂无验房单') }}</text>
<text class="empty-desc">{{ t('emptyDesc') }}</text> <text class="empty-desc">{{ $tr('当前订单还没有可查看的验房记录。') }}</text>
</view> </view>
<view v-if="activeInspectionDetail" class="bottom-action"> <view v-if="activeInspectionDetail" class="bottom-action">
<view class="btn-submit" @click="submitInspection"> <view class="btn-submit" @click="submitInspection">
{{ submitting ? t('submitting') : t('submitAction') }} {{ submitting ? $tr('提交中...') : $tr('提交验房') }}
</view> </view>
</view> </view>
<view v-if="loading" class="global-loading"> <view v-if="loading" class="global-loading">
<tn-loading mode="circle" color="#0F172A" size="48"></tn-loading> <tn-loading mode="circle" color="#0F172A" size="48"></tn-loading>
<text class="loading-text">{{ t('loadingDesc') }}</text> <text class="loading-text">{{ $tr('正在同步验房数据...') }}</text>
</view> </view>
</view> </view>
</template> </template>
@@ -295,84 +295,6 @@ export default {
}) })
}, },
methods: { methods: {
t(key) {
const map = {
zh: {
pageTitle: '验房',
pageDesc: '按当前订单查看并提交验房结果',
inspectionListTitle: '验房记录',
roomNoLabel: '房间号',
inspectionTypeLabel: '验房类型',
inspectionStatusLabel: '当前状态',
inspectionTimeLabel: '发起时间',
inspectionRemarksLabel: '备注',
inspectionRemarksPlaceholder: '可填写本次验房的补充说明',
inspectionItemsTitle: '检查项',
inspectionItemsDesc: '请根据现场情况逐项核对',
deviceFallback: '未命名设备',
requiredTag: '必查',
expectedQuantityLabel: '应有数量',
inspectionResultLabel: '检查结果',
checkedQuantityLabel: '实测数量',
checkedQuantityPlaceholder: '请输入数量',
problemDetailLabel: '问题说明',
problemDetailPlaceholder: '请描述异常、缺失或损坏情况',
photosLabel: '现场照片',
addPhoto: '添加',
submitAction: '提交验房',
loadingDesc: '正在同步验房数据...',
emptyTitle: '暂无验房单',
emptyDesc: '当前订单还没有可查看的验房记录。',
emptyItemsTitle: '暂无检查项',
emptyItemsDesc: '当前验房单还没有检查项明细。',
submitSuccess: '验房单已提交',
submitFailed: '提交失败',
submitting: '提交中...',
missingInspectionNo: '未获取到验房单',
uploadFailed: '图片上传失败',
chooseImageFailed: '选择图片失败',
resultRequired: '请先完成所有检查项结果选择'
},
en: {
pageTitle: 'Inspection',
pageDesc: 'Review and submit inspection results for the current order',
inspectionListTitle: 'Inspection Records',
roomNoLabel: 'Room No.',
inspectionTypeLabel: 'Inspection Type',
inspectionStatusLabel: 'Status',
inspectionTimeLabel: 'Created At',
inspectionRemarksLabel: 'Remarks',
inspectionRemarksPlaceholder: 'Add notes for this inspection',
inspectionItemsTitle: 'Inspection Items',
inspectionItemsDesc: 'Check each item according to the on-site condition',
deviceFallback: 'Unnamed Device',
requiredTag: 'Required',
expectedQuantityLabel: 'Expected Qty',
inspectionResultLabel: 'Result',
checkedQuantityLabel: 'Checked Qty',
checkedQuantityPlaceholder: 'Enter quantity',
problemDetailLabel: 'Problem Detail',
problemDetailPlaceholder: 'Describe any abnormal, missing, or damaged condition',
photosLabel: 'Photos',
addPhoto: 'Add',
submitAction: 'Submit',
loadingDesc: 'Syncing inspection data...',
emptyTitle: 'No inspections',
emptyDesc: 'No inspection record is available for the current order.',
emptyItemsTitle: 'No items',
emptyItemsDesc: 'No inspection item is available for this inspection.',
submitSuccess: 'Inspection submitted',
submitFailed: 'Failed to submit',
submitting: 'Submitting...',
missingInspectionNo: 'Missing inspection record',
uploadFailed: 'Failed to upload image',
chooseImageFailed: 'Failed to choose image',
resultRequired: 'Please complete the result selection for all items first'
}
}
const lang = this.language === 'en' ? 'en' : 'zh'
return (map[lang] && map[lang][key]) || key
},
syncInspectionResultOptions() { syncInspectionResultOptions() {
this.inspectionResultOptions = this.getEnumOptionsWithAliases([ this.inspectionResultOptions = this.getEnumOptionsWithAliases([
'InspectionItemResultEnum', 'InspectionItemResultEnum',
@@ -561,7 +483,7 @@ export default {
const hasMissingResult = items.some(item => item.result === null || Number.isNaN(item.result)) const hasMissingResult = items.some(item => item.result === null || Number.isNaN(item.result))
if (hasMissingResult) { if (hasMissingResult) {
uni.showToast({ uni.showToast({
title: this.t('resultRequired'), title: this.$tr('请先完成所有检查项结果选择'),
icon: 'none' icon: 'none'
}) })
return false return false
@@ -584,7 +506,7 @@ export default {
items: this.buildSubmitItems() items: this.buildSubmitItems()
}, this.language) }, this.language)
uni.showToast({ uni.showToast({
title: this.t('submitSuccess'), title: this.$tr('验房单已提交'),
icon: 'success' icon: 'success'
}) })
setTimeout(() => { setTimeout(() => {
@@ -597,7 +519,7 @@ export default {
}, 500) }, 500)
} catch (error) { } catch (error) {
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('submitFailed'), title: (error && (error.message || error.msg)) || this.$tr('提交失败'),
icon: 'none' icon: 'none'
}) })
} finally { } finally {
@@ -615,7 +537,7 @@ export default {
return return
} }
uni.showLoading({ uni.showLoading({
title: this.t('loadingDesc'), title: this.$tr('正在同步验房数据...'),
mask: true mask: true
}) })
try { try {
@@ -631,7 +553,7 @@ export default {
) )
const remoteUrl = resolveUploadedFileUrl(uploadResult) const remoteUrl = resolveUploadedFileUrl(uploadResult)
if (!remoteUrl) { if (!remoteUrl) {
throw new Error(this.t('uploadFailed')) throw new Error(this.$tr('图片上传失败'))
} }
uploadedUrls.push(addFilePrefix(remoteUrl)) uploadedUrls.push(addFilePrefix(remoteUrl))
} }
@@ -643,7 +565,7 @@ export default {
this.$set(detail.items[index], 'imageUrlsDraft', nextUrls) this.$set(detail.items[index], 'imageUrlsDraft', nextUrls)
} catch (error) { } catch (error) {
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('uploadFailed'), title: (error && (error.message || error.msg)) || this.$tr('图片上传失败'),
icon: 'none' icon: 'none'
}) })
} finally { } finally {
@@ -652,7 +574,7 @@ export default {
}, },
fail: () => { fail: () => {
uni.showToast({ uni.showToast({
title: this.t('chooseImageFailed'), title: this.$tr('选择图片失败'),
icon: 'none' icon: 'none'
}) })
} }

View File

@@ -12,22 +12,22 @@
<view class="tn-bg-gray--light tn-padding-xl tn-border-radius-circle tn-margin-bottom"> <view class="tn-bg-gray--light tn-padding-xl tn-border-radius-circle tn-margin-bottom">
<text class="tn-icon-lock tn-color-gray" style="font-size: 80rpx;"></text> <text class="tn-icon-lock tn-color-gray" style="font-size: 80rpx;"></text>
</view> </view>
<view class="hero-title tn-text-bold">{{ t('lockNoAccessTitle') }}</view> <view class="hero-title tn-text-bold">{{ $tr('暂无门锁权限') }}</view>
<view class="hero-desc tn-margin-top-sm tn-text-center">{{ t('lockNoAccessDesc') }}</view> <view class="hero-desc tn-margin-top-sm tn-text-center">{{ $tr('您当前暂未分配智能门锁,或门锁暂未联网。\n入住后即可使用一键开门与临时密码下发功能。') }}</view>
<view class="tn-flex tn-margin-top-xl tn-width-full tn-flex-row-between"> <view class="tn-flex tn-margin-top-xl tn-width-full tn-flex-row-between">
<view class="btn-light tn-flex-1 tn-margin-right-sm tn-flex tn-flex-col-center tn-flex-row-center tn-padding-sm" style="border-radius: 12rpx; opacity: 0.6;"> <view class="btn-light tn-flex-1 tn-margin-right-sm tn-flex tn-flex-col-center tn-flex-row-center tn-padding-sm" style="border-radius: 12rpx; opacity: 0.6;">
<text class="tn-icon-key tn-margin-right-xs"></text> {{ t('tempPassword') }} <text class="tn-icon-key tn-margin-right-xs"></text> {{ $tr('临时密码') }}
</view> </view>
<view class="btn-primary tn-flex-1 tn-margin-left-sm tn-flex tn-flex-col-center tn-flex-row-center tn-padding-sm" style="border-radius: 12rpx; opacity: 0.6;"> <view class="btn-primary tn-flex-1 tn-margin-left-sm tn-flex tn-flex-col-center tn-flex-row-center tn-padding-sm" style="border-radius: 12rpx; opacity: 0.6;">
<text class="tn-icon-unlock tn-margin-right-xs"></text> {{ t('oneTapUnlock') }} <text class="tn-icon-unlock tn-margin-right-xs"></text> {{ $tr('一键开锁') }}
</view> </view>
</view> </view>
</view> </view>
<view class="record-card tn-margin-top-lg"> <view class="record-card tn-margin-top-lg">
<view class="block-title">{{ t('authRecordTitle') }}</view> <view class="block-title">{{ $tr('授权记录') }}</view>
<view class="tn-margin-top tn-flex tn-flex-row-center tn-padding-xl"> <view class="tn-margin-top tn-flex tn-flex-row-center tn-padding-xl">
<tn-empty icon="tn-icon-empty-data" :text="t('lockEmptyRecord')"></tn-empty> <tn-empty icon="tn-icon-empty-data" :text="$tr('暂无门锁操作记录')"></tn-empty>
</view> </view>
</view> </view>
</view> </view>
@@ -36,16 +36,16 @@
<view v-else-if="pageKey === 'points'"> <view v-else-if="pageKey === 'points'">
<view class="hero-card" style="background: linear-gradient(135deg, #FFB703 0%, #FF8A00 100%); color: #fff; border: none;"> <view class="hero-card" style="background: linear-gradient(135deg, #FFB703 0%, #FF8A00 100%); color: #fff; border: none;">
<view class="tn-flex tn-flex-row-between tn-flex-col-center"> <view class="tn-flex tn-flex-row-between tn-flex-col-center">
<view class="tn-text-md">{{ t('pointsAvailable') }}</view> <view class="tn-text-md">{{ $tr('当前可用积分') }}</view>
<view class="tn-text-sm" style="background: rgba(255,255,255,0.2); padding: 4rpx 16rpx; border-radius: 100rpx;">{{ t('redeemRules') }}</view> <view class="tn-text-sm" style="background: rgba(255,255,255,0.2); padding: 4rpx 16rpx; border-radius: 100rpx;">{{ $tr('兑换规则') }}</view>
</view> </view>
<view class="tn-margin-top-lg tn-text-bold" style="font-size: 80rpx;">--</view> <view class="tn-margin-top-lg tn-text-bold" style="font-size: 80rpx;">--</view>
<view class="tn-margin-top tn-flex tn-flex-row-between"> <view class="tn-margin-top tn-flex tn-flex-row-between">
<view class="tn-flex tn-flex-col-center"> <view class="tn-flex tn-flex-col-center">
<text class="tn-icon-calendar tn-margin-right-xs"></text> {{ t('dailyPoints') }} <text class="tn-icon-calendar tn-margin-right-xs"></text> {{ $tr('每日签到领积分') }}
</view> </view>
<view class="tn-flex tn-flex-col-center" style="opacity: 0.8;"> <view class="tn-flex tn-flex-col-center" style="opacity: 0.8;">
{{ t('goCheckIn') }} <text class="tn-icon-right tn-margin-left-xs"></text> {{ $tr('去签到') }} <text class="tn-icon-right tn-margin-left-xs"></text>
</view> </view>
</view> </view>
</view> </view>
@@ -53,20 +53,20 @@
<view class="tn-flex tn-flex-row-between tn-margin-top-lg"> <view class="tn-flex tn-flex-row-between tn-margin-top-lg">
<view class="btn-light tn-flex-1 tn-margin-right-sm tn-flex tn-flex-direction-column tn-flex-col-center tn-padding-lg" style="border-radius: 20rpx;"> <view class="btn-light tn-flex-1 tn-margin-right-sm tn-flex tn-flex-direction-column tn-flex-col-center tn-padding-lg" style="border-radius: 20rpx;">
<text class="tn-icon-shop tn-color-orange tn-margin-bottom-sm" style="font-size: 60rpx;"></text> <text class="tn-icon-shop tn-color-orange tn-margin-bottom-sm" style="font-size: 60rpx;"></text>
<text class="tn-text-bold tn-color-black">{{ t('pointsMall') }}</text> <text class="tn-text-bold tn-color-black">{{ $tr('积分商城') }}</text>
<text class="tn-text-xs tn-color-gray tn-margin-top-xs">{{ t('pointsMallDesc') }}</text> <text class="tn-text-xs tn-color-gray tn-margin-top-xs">{{ $tr('超值好物兑换') }}</text>
</view> </view>
<view class="btn-light tn-flex-1 tn-margin-left-sm tn-flex tn-flex-direction-column tn-flex-col-center tn-padding-lg" style="border-radius: 20rpx;"> <view class="btn-light tn-flex-1 tn-margin-left-sm tn-flex tn-flex-direction-column tn-flex-col-center tn-padding-lg" style="border-radius: 20rpx;">
<text class="tn-icon-coupon tn-color-purplered tn-margin-bottom-sm" style="font-size: 60rpx;"></text> <text class="tn-icon-coupon tn-color-purplered tn-margin-bottom-sm" style="font-size: 60rpx;"></text>
<text class="tn-text-bold tn-color-black">{{ t('coupon') }}</text> <text class="tn-text-bold tn-color-black">{{ $tr('优惠券') }}</text>
<text class="tn-text-xs tn-color-gray tn-margin-top-xs">{{ t('couponDesc') }}</text> <text class="tn-text-xs tn-color-gray tn-margin-top-xs">{{ $tr('租金水电抵扣') }}</text>
</view> </view>
</view> </view>
<view class="record-card tn-margin-top-lg"> <view class="record-card tn-margin-top-lg">
<view class="block-title">{{ t('pointsRecordTitle') }}</view> <view class="block-title">{{ $tr('积分明细') }}</view>
<view class="tn-margin-top tn-flex tn-flex-row-center tn-padding-xl"> <view class="tn-margin-top tn-flex tn-flex-row-center tn-padding-xl">
<tn-empty icon="tn-icon-empty-data" :text="t('pointsEmpty')"></tn-empty> <tn-empty icon="tn-icon-empty-data" :text="$tr('暂无积分明细')"></tn-empty>
</view> </view>
</view> </view>
</view> </view>
@@ -74,28 +74,28 @@
<!-- 一码通管理 Mockup --> <!-- 一码通管理 Mockup -->
<view v-else-if="pageKey === 'code'"> <view v-else-if="pageKey === 'code'">
<view class="hero-card tn-flex tn-flex-direction-column tn-flex-col-center"> <view class="hero-card tn-flex tn-flex-direction-column tn-flex-col-center">
<view class="tn-text-md tn-color-black tn-text-bold">{{ t('accessCodeTitle') }}</view> <view class="tn-text-md tn-color-black tn-text-bold">{{ $tr('我的通行二维码') }}</view>
<view class="tn-text-xs tn-color-gray tn-margin-top-xs">{{ t('accessCodeDesc') }}</view> <view class="tn-text-xs tn-color-gray tn-margin-top-xs">{{ $tr('可用于社区大门、单元门及电梯通行') }}</view>
<view class="qr-code-box tn-margin-top-xl tn-margin-bottom-lg tn-flex tn-flex-col-center tn-flex-row-center"> <view class="qr-code-box tn-margin-top-xl tn-margin-bottom-lg tn-flex tn-flex-col-center tn-flex-row-center">
<view class="qr-placeholder tn-flex tn-flex-col-center tn-flex-row-center"> <view class="qr-placeholder tn-flex tn-flex-col-center tn-flex-row-center">
<text class="tn-icon-qr-code tn-color-gray" style="font-size: 200rpx; opacity: 0.2;"></text> <text class="tn-icon-qr-code tn-color-gray" style="font-size: 200rpx; opacity: 0.2;"></text>
</view> </view>
<view class="qr-mask tn-flex tn-flex-col-center tn-flex-row-center"> <view class="qr-mask tn-flex tn-flex-col-center tn-flex-row-center">
<text class="tn-color-white tn-text-bold tn-text-lg">{{ t('noAccessPermission') }}</text> <text class="tn-color-white tn-text-bold tn-text-lg">{{ $tr('未获取通行权限') }}</text>
</view> </view>
</view> </view>
<view class="tn-text-xs tn-color-gray tn-text-center tn-margin-bottom">{{ t('accessAssignDesc') }}</view> <view class="tn-text-xs tn-color-gray tn-text-center tn-margin-bottom">{{ $tr('请在完成入住签约并录入人脸/实名信息后\n系统将自动为您分配通行权限') }}</view>
</view> </view>
<view class="record-card tn-margin-top-lg"> <view class="record-card tn-margin-top-lg">
<view class="tn-flex tn-flex-row-between tn-flex-col-center"> <view class="tn-flex tn-flex-row-between tn-flex-col-center">
<view class="block-title">{{ t('visitorAccessTitle') }}</view> <view class="block-title">{{ $tr('访客通行') }}</view>
<view class="tn-color-gray tn-text-sm">{{ t('visitorAccessAction') }} <text class="tn-icon-right"></text></view> <view class="tn-color-gray tn-text-sm">{{ $tr('临时访客授权') }} <text class="tn-icon-right"></text></view>
</view> </view>
<view class="tn-margin-top tn-flex tn-flex-row-center tn-padding-xl"> <view class="tn-margin-top tn-flex tn-flex-row-center tn-padding-xl">
<tn-empty icon="tn-icon-empty-data" :text="t('visitorEmpty')"></tn-empty> <tn-empty icon="tn-icon-empty-data" :text="$tr('暂无访客通行记录')"></tn-empty>
</view> </view>
</view> </view>
</view> </view>
@@ -103,25 +103,25 @@
<!-- 默认通用占位 --> <!-- 默认通用占位 -->
<view v-else> <view v-else>
<view class="hero-card"> <view class="hero-card">
<view class="hero-badge">{{ t('heroBadge') }}</view> <view class="hero-badge">{{ $tr('即将开放') }}</view>
<view class="hero-title tn-margin-top">{{ resolvedPageTitle || t('comingSoonTitle') }}</view> <view class="hero-title tn-margin-top">{{ resolvedPageTitle || $tr('功能建设中') }}</view>
<view class="hero-desc tn-margin-top-sm">{{ resolvedPageDesc || resolvedDefaultDesc }}</view> <view class="hero-desc tn-margin-top-sm">{{ resolvedPageDesc || resolvedDefaultDesc }}</view>
</view> </view>
<view class="record-card tn-margin-top-xl"> <view class="record-card tn-margin-top-xl">
<view class="block-title">{{ t('currentStatusTitle') }}</view> <view class="block-title">{{ $tr('当前状态') }}</view>
<view class="empty-text tn-margin-top"> <view class="empty-text tn-margin-top">
{{ t('currentStatusDesc') }} {{ $tr('该功能当前仍在建设中,暂未开放正式办理入口。你可以先返回上一页,或回到首页继续查看已开放功能。') }}
</view> </view>
<view class="tip-card tn-margin-top-lg"> <view class="tip-card tn-margin-top-lg">
<view class="tip-title">{{ t('noteTitle') }}</view> <view class="tip-title">{{ $tr('说明') }}</view>
<view class="tip-desc tn-margin-top-xs">{{ t('noteDesc') }}</view> <view class="tip-desc tn-margin-top-xs">{{ $tr('后续接入真实能力后,这里会展示正式的业务入口、状态数据和办理记录。') }}</view>
</view> </view>
<view class="submit-btn btn-primary tn-margin-top-xl" @click="goBack"> <view class="submit-btn btn-primary tn-margin-top-xl" @click="goBack">
<text class="tn-text-bold">{{ t('backPrev') }}</text> <text class="tn-text-bold">{{ $tr('返回上一页') }}</text>
</view> </view>
<view class="submit-btn btn-light tn-margin-top" @click="goHome"> <view class="submit-btn btn-light tn-margin-top" @click="goHome">
<text class="tn-text-bold">{{ t('backHome') }}</text> <text class="tn-text-bold">{{ $tr('返回首页') }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -143,78 +143,6 @@ function safeDecodeText(value, fallback = '') {
} }
export default { export default {
i18n: {
zh: {
defaultPageTitle: '业务办理',
defaultPageDesc: '该能力正在完善中,开放后会同步到个人中心的对应入口。',
lockNoAccessTitle: '暂无门锁权限',
lockNoAccessDesc: '您当前暂未分配智能门锁,或门锁暂未联网。\n入住后即可使用一键开门与临时密码下发功能。',
tempPassword: '临时密码',
oneTapUnlock: '一键开锁',
authRecordTitle: '授权记录',
lockEmptyRecord: '暂无门锁操作记录',
pointsAvailable: '当前可用积分',
redeemRules: '兑换规则',
dailyPoints: '每日签到领积分',
goCheckIn: '去签到',
pointsMall: '积分商城',
pointsMallDesc: '超值好物兑换',
coupon: '优惠券',
couponDesc: '租金水电抵扣',
pointsRecordTitle: '积分明细',
pointsEmpty: '暂无积分明细',
accessCodeTitle: '我的通行二维码',
accessCodeDesc: '可用于社区大门、单元门及电梯通行',
noAccessPermission: '未获取通行权限',
accessAssignDesc: '请在完成入住签约并录入人脸/实名信息后\n系统将自动为您分配通行权限',
visitorAccessTitle: '访客通行',
visitorAccessAction: '临时访客授权',
visitorEmpty: '暂无访客通行记录',
heroBadge: '即将开放',
comingSoonTitle: '功能建设中',
currentStatusTitle: '当前状态',
currentStatusDesc: '该功能当前仍在建设中,暂未开放正式办理入口。你可以先返回上一页,或回到首页继续查看已开放功能。',
noteTitle: '说明',
noteDesc: '后续接入真实能力后,这里会展示正式的业务入口、状态数据和办理记录。',
backPrev: '返回上一页',
backHome: '返回首页'
},
en: {
defaultPageTitle: 'Service Center',
defaultPageDesc: 'This capability is being improved and will appear in the related personal center entry when available.',
lockNoAccessTitle: 'No door lock access',
lockNoAccessDesc: 'No smart lock is assigned yet, or the lock is currently offline.\nAfter check-in, one-tap unlock and temporary password features will be available.',
tempPassword: 'Temporary Code',
oneTapUnlock: 'Unlock Now',
authRecordTitle: 'Access Records',
lockEmptyRecord: 'No door lock records',
pointsAvailable: 'Available Points',
redeemRules: 'Redeem Rules',
dailyPoints: 'Daily check-in rewards',
goCheckIn: 'Check In',
pointsMall: 'Points Mall',
pointsMallDesc: 'Redeem for selected items',
coupon: 'Coupons',
couponDesc: 'Offset rent and utilities',
pointsRecordTitle: 'Points History',
pointsEmpty: 'No points record',
accessCodeTitle: 'My Access QR Code',
accessCodeDesc: 'Use it for community gates, unit doors, and elevators',
noAccessPermission: 'No access permission',
accessAssignDesc: 'After completing check-in signing and face / identity enrollment,\nthe system will automatically assign your access permission.',
visitorAccessTitle: 'Visitor Access',
visitorAccessAction: 'Temporary visitor authorization',
visitorEmpty: 'No visitor access records',
heroBadge: 'Coming Soon',
comingSoonTitle: 'Coming Soon',
currentStatusTitle: 'Current Status',
currentStatusDesc: 'This capability is still under development and is not yet officially available. You can go back or return to the home page to browse available features.',
noteTitle: 'Notes',
noteDesc: 'After the real capability is connected, this page will display the formal service entry, status data, and processing records.',
backPrev: 'Go Back',
backHome: 'Back Home'
}
},
data() { data() {
return { return {
pageTitleZh: '', pageTitleZh: '',
@@ -229,9 +157,9 @@ export default {
computed: { computed: {
resolvedPageTitle() { resolvedPageTitle() {
if (this.$getLanguage() === 'en') { if (this.$getLanguage() === 'en') {
return this.pageTitleEn || this.pageTitleZh || this.rawPageTitle || this.t('defaultPageTitle') return this.pageTitleEn || this.pageTitleZh || this.rawPageTitle || this.$tr('业务办理')
} }
return this.pageTitleZh || this.pageTitleEn || this.rawPageTitle || this.t('defaultPageTitle') return this.pageTitleZh || this.pageTitleEn || this.rawPageTitle || this.$tr('业务办理')
}, },
resolvedPageDesc() { resolvedPageDesc() {
if (this.$getLanguage() === 'en') { if (this.$getLanguage() === 'en') {
@@ -240,7 +168,7 @@ export default {
return this.pageDescZh || this.pageDescEn || this.rawPageDesc || '' return this.pageDescZh || this.pageDescEn || this.rawPageDesc || ''
}, },
resolvedDefaultDesc() { resolvedDefaultDesc() {
return this.t('defaultPageDesc') return this.$tr('该能力正在完善中,开放后会同步到个人中心的对应入口。')
} }
}, },
onLoad(options) { onLoad(options) {
@@ -253,9 +181,6 @@ export default {
this.pageKey = safeDecodeText(options && options.key, '') this.pageKey = safeDecodeText(options && options.key, '')
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.service_stub.service_stub', key, params)
},
goBack() { goBack() {
uni.navigateBack() uni.navigateBack()
}, },

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="smart-page"> <view class="smart-page">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent" fontColor="#ffffff"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent" fontColor="#ffffff">
<view class="tn-text-bold tn-text-xl">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl">{{ $tr('全屋智能') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="header-section" :style="{ paddingTop: (vuex_custom_bar_height + 20) + 'px' }"> <view class="header-section" :style="{ paddingTop: (vuex_custom_bar_height + 20) + 'px' }">
@@ -13,31 +13,31 @@
</view> </view>
<view class="toolbar"> <view class="toolbar">
<view class="toolbar-tip">{{ t('toolbarTip') }}</view> <view class="toolbar-tip">{{ $tr('点击设备进入详情并下发控制指令') }}</view>
<view class="toolbar-action" @click="reloadDevices"> <view class="toolbar-action" @click="reloadDevices">
<text class="tn-icon-refresh" :class="{'spin-anim': isLoading}" style="margin-right: 8rpx;"></text> <text class="tn-icon-refresh" :class="{'spin-anim': isLoading}" style="margin-right: 8rpx;"></text>
<text>{{ isLoading ? t('loadingShort') : t('refreshStatus') }}</text> <text>{{ isLoading ? $tr('加载中') : $tr('刷新状态') }}</text>
</view> </view>
</view> </view>
<!-- Feedback States --> <!-- Feedback States -->
<view v-if="isLoading && !devices.length" class="empty-state"> <view v-if="isLoading && !devices.length" class="empty-state">
<view class="loading-ring"></view> <view class="loading-ring"></view>
<view class="empty-title">{{ t('loadingTitle') }}</view> <view class="empty-title">{{ $tr('正在连接设备') }}</view>
<view class="empty-desc">{{ t('loadingDesc') }}</view> <view class="empty-desc">{{ $tr('正在查询当前房间的可控设备...') }}</view>
</view> </view>
<view v-else-if="loadError && !devices.length" class="empty-state error-state"> <view v-else-if="loadError && !devices.length" class="empty-state error-state">
<text class="tn-icon-warning empty-icon"></text> <text class="tn-icon-warning empty-icon"></text>
<view class="empty-title">{{ t('loadFailedTitle') }}</view> <view class="empty-title">{{ $tr('设备加载失败') }}</view>
<view class="empty-desc">{{ loadError }}</view> <view class="empty-desc">{{ loadError }}</view>
<view class="retry-btn" @click="reloadDevices">{{ t('retry') }}</view> <view class="retry-btn" @click="reloadDevices">{{ $tr('重新加载') }}</view>
</view> </view>
<view v-else-if="!devices.length" class="empty-state"> <view v-else-if="!devices.length" class="empty-state">
<text class="tn-icon-empty empty-icon"></text> <text class="tn-icon-empty empty-icon"></text>
<view class="empty-title">{{ t('emptyTitle') }}</view> <view class="empty-title">{{ $tr('暂无可控设备') }}</view>
<view class="empty-desc">{{ t('emptyDesc') }}</view> <view class="empty-desc">{{ $tr('当前房间未绑定智能设备') }}</view>
</view> </view>
<scroll-view v-else scroll-y class="device-scroll"> <scroll-view v-else scroll-y class="device-scroll">
@@ -58,7 +58,7 @@
</view> </view>
<view class="card-bottom"> <view class="card-bottom">
<view class="device-name">{{ item.deviceName }}</view> <view class="device-name">{{ item.deviceName }}</view>
<view class="device-state" :class="{'is-active': item.active}">{{ item.stateText || (item.active ? t('deviceRunning') : t('deviceOff')) }}</view> <view class="device-state" :class="{'is-active': item.active}">{{ item.stateText || (item.active ? $tr('运行中') : $tr('已关闭')) }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -126,60 +126,6 @@ function resolveDeviceListPollingInterval(deviceList = [], fallback = 1000) {
} }
export default { export default {
i18n: {
zh: {
navTitle: '全屋智能',
toolbarTip: '点击设备进入详情并下发控制指令',
loadingShort: '加载中',
refreshStatus: '刷新状态',
loadingTitle: '正在连接设备',
loadingDesc: '正在查询当前房间的可控设备...',
loadFailedTitle: '设备加载失败',
retry: '重新加载',
emptyTitle: '暂无可控设备',
emptyDesc: '当前房间未绑定智能设备',
deviceSummary: '共 {count} 台设备,可进入详情页控制',
deviceRunning: '运行中',
deviceOff: '已关闭',
currentRoom: '当前房间',
missingRoomCode: '缺少房间编码',
syncingStatus: '正在同步设备状态',
unsupportedQuickControl: '设备暂不支持快捷控制',
unsupportedSwitch: '该设备不支持快捷开关',
missingFunctionCode: '功能编码缺失',
sending: '发送中...',
enabled: '已开启',
disabled: '已关闭',
commandFailed: '指令下发失败',
requestFailed: '设备列表请求失败'
},
en: {
navTitle: 'Smart Home',
toolbarTip: 'Tap a device to open details and send control commands',
loadingShort: 'Loading',
refreshStatus: 'Refresh Status',
loadingTitle: 'Connecting devices',
loadingDesc: 'Checking controllable devices in the current room...',
loadFailedTitle: 'Failed to load devices',
retry: 'Retry',
emptyTitle: 'No controllable devices',
emptyDesc: 'No smart devices are bound to this room',
deviceSummary: '{count} devices available. Tap to control in detail view',
deviceRunning: 'Running',
deviceOff: 'Off',
currentRoom: 'Current Room',
missingRoomCode: 'Missing room code',
syncingStatus: 'Syncing device status',
unsupportedQuickControl: 'Quick control is not supported for this device',
unsupportedSwitch: 'This device does not support quick switching',
missingFunctionCode: 'Missing function code',
sending: 'Sending...',
enabled: 'Enabled',
disabled: 'Disabled',
commandFailed: 'Failed to send command',
requestFailed: 'Failed to request device list'
}
},
data() { data() {
return { return {
brandId: DEFAULT_BRAND_ID, brandId: DEFAULT_BRAND_ID,
@@ -202,19 +148,19 @@ export default {
return this.$getLanguage() return this.$getLanguage()
}, },
displayRoomName() { displayRoomName() {
return this.roomName || this.roomCode || this.t('currentRoom') return this.roomName || this.roomCode || this.$tr('当前房间')
}, },
summaryText() { summaryText() {
if (!this.roomCode) { if (!this.roomCode) {
return this.t('missingRoomCode') return this.$tr('缺少房间编码')
} }
if (this.isLoading) { if (this.isLoading) {
return this.t('syncingStatus') return this.$tr('正在同步设备状态')
} }
if (this.devices.length) { if (this.devices.length) {
return this.t('deviceSummary', { count: this.devices.length }) return this.$tr('共 {count} 台设备,可进入详情页控制', { count: this.devices.length })
} }
return this.t('emptyTitle') return this.$tr('暂无可控设备')
} }
}, },
onLoad(options) { onLoad(options) {
@@ -236,9 +182,6 @@ export default {
this.stopPolling() this.stopPolling()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.smart_control.smart_control', key, params)
},
resolvePageContext(options = {}) { resolvePageContext(options = {}) {
const session = getSessionSnapshot() const session = getSessionSnapshot()
const roomData = (session && session.myRoomData) || {} const roomData = (session && session.myRoomData) || {}
@@ -321,7 +264,7 @@ export default {
if (!this.roomCode) { if (!this.roomCode) {
this.devices = [] this.devices = []
this.loadError = this.t('missingRoomCode') this.loadError = this.$tr('缺少房间编码')
if (stopRefresh) { if (stopRefresh) {
uni.stopPullDownRefresh() uni.stopPullDownRefresh()
} }
@@ -361,7 +304,7 @@ export default {
this.devices = [] this.devices = []
} }
if (!silent) { if (!silent) {
this.loadError = (error && (error.message || error.msg)) || this.t('requestFailed') this.loadError = (error && (error.message || error.msg)) || this.$tr('设备列表请求失败')
} }
}) })
.finally(() => { .finally(() => {
@@ -397,7 +340,7 @@ export default {
const functions = getDeviceFunctions(item) const functions = getDeviceFunctions(item)
if (!functions || !functions.length) { if (!functions || !functions.length) {
uni.showToast({ title: this.t('unsupportedQuickControl'), icon: 'none' }) uni.showToast({ title: this.$tr('设备暂不支持快捷控制'), icon: 'none' })
return return
} }
@@ -409,13 +352,13 @@ export default {
const category = getControlTypeCategory(targetFunc.controlType) const category = getControlTypeCategory(targetFunc.controlType)
if (category !== 'switch') { if (category !== 'switch') {
uni.showToast({ title: this.t('unsupportedSwitch'), icon: 'none' }) uni.showToast({ title: this.$tr('该设备不支持快捷开关'), icon: 'none' })
return return
} }
const functionCode = String((targetFunc && targetFunc.functionCode) || '').trim() const functionCode = String((targetFunc && targetFunc.functionCode) || '').trim()
if (!functionCode) { if (!functionCode) {
uni.showToast({ title: this.t('missingFunctionCode'), icon: 'none' }) uni.showToast({ title: this.$tr('功能编码缺失'), icon: 'none' })
return return
} }
@@ -433,7 +376,7 @@ export default {
} }
this.isSubmitting = true this.isSubmitting = true
uni.showLoading({ title: this.t('sending') }) uni.showLoading({ title: this.$tr('发送中...') })
gatewayExecuteDeviceCommand(this.brandId, item.deviceNo, functionCode, commandPayload) gatewayExecuteDeviceCommand(this.brandId, item.deviceNo, functionCode, commandPayload)
.then(() => { .then(() => {
@@ -447,11 +390,11 @@ export default {
renderKey: this.devices[index].renderKey renderKey: this.devices[index].renderKey
}) })
} }
uni.showToast({ title: enabled ? this.t('enabled') : this.t('disabled'), icon: 'success' }) uni.showToast({ title: enabled ? this.$tr('已开启') : this.$tr('已关闭'), icon: 'success' })
}) })
.catch((error) => { .catch((error) => {
console.error('execute device command failed', error) console.error('execute device command failed', error)
uni.showToast({ title: (error && (error.message || error.msg)) || this.t('commandFailed'), icon: 'none' }) uni.showToast({ title: (error && (error.message || error.msg)) || this.$tr('指令下发失败'), icon: 'none' })
}) })
.finally(() => { .finally(() => {
this.isSubmitting = false this.isSubmitting = false

View File

@@ -16,17 +16,17 @@
<tn-popup v-model="showPopup" mode="bottom" length="72%" :borderRadius="32" :closeBtn="true"> <tn-popup v-model="showPopup" mode="bottom" length="72%" :borderRadius="32" :closeBtn="true">
<view class="popup-content"> <view class="popup-content">
<view class="popup-header"> <view class="popup-header">
<text class="popup-title">{{ t('popupTitle') }}</text> <text class="popup-title">{{ $tr('选择意向楼栋') }}</text>
</view> </view>
<scroll-view scroll-y class="building-list-scroll"> <scroll-view scroll-y class="building-list-scroll">
<view v-if="isLoading" class="loading-state tn-padding-xl tn-text-center"> <view v-if="isLoading" class="loading-state tn-padding-xl tn-text-center">
<tn-loading mode="circle"></tn-loading> <tn-loading mode="circle"></tn-loading>
<view class="tn-margin-top-sm tn-color-gray">{{ t('loadingText') }}</view> <view class="tn-margin-top-sm tn-color-gray">{{ $tr('楼栋加载中...') }}</view>
</view> </view>
<view v-else-if="!buildingList.length" class="empty-state tn-padding-xl tn-text-center"> <view v-else-if="!buildingList.length" class="empty-state tn-padding-xl tn-text-center">
<text class="tn-color-gray">{{ t('emptyText') }}</text> <text class="tn-color-gray">{{ $tr('暂无可选楼栋') }}</text>
</view> </view>
<view v-else class="building-list"> <view v-else class="building-list">
@@ -97,7 +97,7 @@ export default {
return this.$getLanguage ? this.$getLanguage() : 'zh' return this.$getLanguage ? this.$getLanguage() : 'zh'
}, },
placeholderText() { placeholderText() {
return this.areaCode ? this.t('selectBuilding') : this.t('selectAreaFirst') return this.areaCode ? this.$tr('请选择楼栋') : this.$tr('请先选择区域')
} }
}, },
watch: { watch: {
@@ -120,13 +120,10 @@ export default {
this.loadData() this.loadData()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.submit_request.components.BuildingSelector', key, params)
},
handleOpen() { handleOpen() {
if (!this.areaCode) { if (!this.areaCode) {
uni.showToast({ uni.showToast({
title: this.t('selectAreaFirst'), title: this.$tr('请先选择区域'),
icon: 'none' icon: 'none'
}) })
return return
@@ -189,7 +186,7 @@ export default {
this.buildingList = [] this.buildingList = []
this.selectedBuilding = null this.selectedBuilding = null
uni.showToast({ uni.showToast({
title: this.t('loadFailed'), title: this.$tr('楼栋列表加载失败'),
icon: 'none' icon: 'none'
}) })
}) })

View File

@@ -8,7 +8,7 @@
</view> </view>
</picker> </picker>
</view> </view>
<view class="date-divider">{{ t('dateDivider') }}</view> <view class="date-divider">{{ $tr('至') }}</view>
<view class="date-item"> <view class="date-item">
<view class="date-label">{{ resolvedEndLabel }}</view> <view class="date-label">{{ resolvedEndLabel }}</view>
<picker mode="date" @change="onEndChange"> <picker mode="date" @change="onEndChange">
@@ -23,22 +23,6 @@
<script> <script>
export default { export default {
name: 'DateRangeSelector', name: 'DateRangeSelector',
i18n: {
zh: {
startLabel: '预计入住',
endLabel: '预计离开',
startPlaceholder: '请选择入住日期',
endPlaceholder: '请选择离开日期',
dateDivider: '至'
},
en: {
startLabel: 'Expected Check-in',
endLabel: 'Expected Check-out',
startPlaceholder: 'Select check-in date',
endPlaceholder: 'Select check-out date',
dateDivider: 'to'
}
},
props: { props: {
start: { start: {
type: String, type: String,
@@ -67,22 +51,19 @@ export default {
}, },
computed: { computed: {
resolvedStartLabel() { resolvedStartLabel() {
return this.startLabel || this.t('startLabel') return this.startLabel || this.$tr('预计入住')
}, },
resolvedEndLabel() { resolvedEndLabel() {
return this.endLabel || this.t('endLabel') return this.endLabel || this.$tr('预计离开')
}, },
resolvedStartPlaceholder() { resolvedStartPlaceholder() {
return this.startPlaceholder || this.t('startPlaceholder') return this.startPlaceholder || this.$tr('请选择入住日期')
}, },
resolvedEndPlaceholder() { resolvedEndPlaceholder() {
return this.endPlaceholder || this.t('endPlaceholder') return this.endPlaceholder || this.$tr('请选择离开日期')
} }
}, },
methods: { methods: {
t(key) {
return this.$tm('pkgService.submit_request.components.DateRangeSelector', key)
},
onStartChange(e) { onStartChange(e) {
this.$emit('update:start', e.detail.value) this.$emit('update:start', e.detail.value)
}, },

View File

@@ -15,17 +15,17 @@
<tn-popup v-model="showPopup" mode="bottom" length="72%" :borderRadius="32" :closeBtn="true"> <tn-popup v-model="showPopup" mode="bottom" length="72%" :borderRadius="32" :closeBtn="true">
<view class="popup-content"> <view class="popup-content">
<view class="popup-header"> <view class="popup-header">
<text class="popup-title">{{ t('popupTitle') }}</text> <text class="popup-title">{{ $tr('选择意向楼层') }}</text>
</view> </view>
<scroll-view scroll-y class="floor-list-scroll"> <scroll-view scroll-y class="floor-list-scroll">
<view v-if="isLoading" class="loading-state tn-padding-xl tn-text-center"> <view v-if="isLoading" class="loading-state tn-padding-xl tn-text-center">
<tn-loading mode="circle"></tn-loading> <tn-loading mode="circle"></tn-loading>
<view class="tn-margin-top-sm tn-color-gray">{{ t('loadingText') }}</view> <view class="tn-margin-top-sm tn-color-gray">{{ $tr('楼层加载中...') }}</view>
</view> </view>
<view v-else-if="!floorList.length" class="empty-state tn-padding-xl tn-text-center"> <view v-else-if="!floorList.length" class="empty-state tn-padding-xl tn-text-center">
<text class="tn-color-gray">{{ t('emptyText') }}</text> <text class="tn-color-gray">{{ $tr('暂无可选楼层') }}</text>
</view> </view>
<view v-else class="floor-list"> <view v-else class="floor-list">
@@ -34,7 +34,7 @@
:class="{ 'is-active': !selectedFloor }" :class="{ 'is-active': !selectedFloor }"
@click="handleClear" @click="handleClear"
> >
<text class="floor-name">{{ t('clearSelection') }}</text> <text class="floor-name">{{ $tr('不选择') }}</text>
<text v-if="!selectedFloor" class="tn-icon-success-circle-fill floor-check"></text> <text v-if="!selectedFloor" class="tn-icon-success-circle-fill floor-check"></text>
</view> </view>
<view <view
@@ -133,7 +133,7 @@ export default {
return this.$getLanguage ? this.$getLanguage() : 'zh' return this.$getLanguage ? this.$getLanguage() : 'zh'
}, },
placeholderText() { placeholderText() {
return this.t('selectFloor') return this.$tr('请选择楼层')
} }
}, },
watch: { watch: {
@@ -148,9 +148,6 @@ export default {
this.loadData() this.loadData()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.submit_request.components.FloorSelector', key, params)
},
handleOpen() { handleOpen() {
this.showPopup = true this.showPopup = true
this.loadData() this.loadData()
@@ -176,7 +173,7 @@ export default {
this.floorList = [] this.floorList = []
this.selectedFloor = null this.selectedFloor = null
uni.showToast({ uni.showToast({
title: this.t('loadFailed'), title: this.$tr('楼层列表加载失败'),
icon: 'none' icon: 'none'
}) })
}) })

View File

@@ -3,7 +3,7 @@
<view class="selector-header tn-flex tn-flex-row-between tn-flex-col-center" @click="openSelector"> <view class="selector-header tn-flex tn-flex-row-between tn-flex-col-center" @click="openSelector">
<view class="selected-info"> <view class="selected-info">
<view class="selected-name tn-text-ellipsis" :class="{ 'is-empty': !selectedRoomType }"> <view class="selected-name tn-text-ellipsis" :class="{ 'is-empty': !selectedRoomType }">
{{ selectedRoomType ? getRoomTypeDisplayName(selectedRoomType) : t('selectRoomType') }} {{ selectedRoomType ? getRoomTypeDisplayName(selectedRoomType) : $tr('请选择意向房型') }}
</view> </view>
<view class="selected-area tn-text-ellipsis" v-if="selectedArea"> <view class="selected-area tn-text-ellipsis" v-if="selectedArea">
{{ getAreaDisplayName(selectedArea) }} {{ getAreaDisplayName(selectedArea) }}
@@ -15,17 +15,17 @@
<tn-popup v-model="showPopup" mode="bottom" length="80%" :borderRadius="32" :closeBtn="true"> <tn-popup v-model="showPopup" mode="bottom" length="80%" :borderRadius="32" :closeBtn="true">
<view class="popup-content"> <view class="popup-content">
<view class="popup-header"> <view class="popup-header">
<text class="popup-title">{{ t('popupTitle') }}</text> <text class="popup-title">{{ $tr('选择意向房型') }}</text>
</view> </view>
<scroll-view scroll-y class="area-list-scroll"> <scroll-view scroll-y class="area-list-scroll">
<view v-if="isLoading" class="loading-state tn-padding-xl tn-text-center"> <view v-if="isLoading" class="loading-state tn-padding-xl tn-text-center">
<tn-loading mode="circle"></tn-loading> <tn-loading mode="circle"></tn-loading>
<view class="tn-margin-top-sm tn-color-gray">{{ t('loadingRoomTypes') }}</view> <view class="tn-margin-top-sm tn-color-gray">{{ $tr('房型加载中...') }}</view>
</view> </view>
<view v-else-if="!displayAreaList.length" class="empty-state tn-padding-xl tn-text-center"> <view v-else-if="!displayAreaList.length" class="empty-state tn-padding-xl tn-text-center">
<text class="tn-color-gray">{{ t('emptyRoomTypes') }}</text> <text class="tn-color-gray">{{ $tr('暂无可选房型') }}</text>
</view> </view>
<view v-else class="area-group" v-for="(area, index) in displayAreaList" :key="area.code"> <view v-else class="area-group" v-for="(area, index) in displayAreaList" :key="area.code">
@@ -45,7 +45,7 @@
@click="handleSelect(area, room)" @click="handleSelect(area, room)"
> >
<view class="room-name tn-text-ellipsis">{{ getRoomTypeDisplayName(room) }}</view> <view class="room-name tn-text-ellipsis">{{ getRoomTypeDisplayName(room) }}</view>
<view class="room-price" v-if="room.price">¥{{ getPriceValue(room.price) }}{{ t('priceUnit') }}</view> <view class="room-price" v-if="room.price">¥{{ getPriceValue(room.price) }}{{ $tr('/月') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -118,9 +118,6 @@ export default {
this.loadData() this.loadData()
}, },
methods: { methods: {
t(key) {
return this.$translate(this.i18n, key)
},
openSelector() { openSelector() {
this.showPopup = true this.showPopup = true
this.loadData() this.loadData()
@@ -148,7 +145,7 @@ export default {
}) })
.catch((e) => { .catch((e) => {
console.error('load room types error', e) console.error('load room types error', e)
uni.showToast({ title: this.t('loadFailed'), icon: 'none' }) uni.showToast({ title: this.$tr('房型列表加载失败'), icon: 'none' })
}) })
.finally(() => { .finally(() => {
this.isLoading = false this.isLoading = false

View File

@@ -11,23 +11,13 @@
{{ item.value }} {{ item.value }}
</view> </view>
</view> </view>
<view v-else class="empty-tips">{{ loading ? t('loadingText') : resolvedEmptyText }}</view> <view v-else class="empty-tips">{{ loading ? $tr('标签加载中...') : resolvedEmptyText }}</view>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
name: 'TagSelector', name: 'TagSelector',
i18n: {
zh: {
loadingText: '标签加载中...',
emptyText: '暂无标签'
},
en: {
loadingText: 'Loading tags...',
emptyText: 'No tags available'
}
},
props: { props: {
value: { value: {
type: Array, type: Array,
@@ -48,13 +38,10 @@ export default {
}, },
computed: { computed: {
resolvedEmptyText() { resolvedEmptyText() {
return this.emptyText || this.t('emptyText') return this.emptyText || this.$tr('暂无标签')
} }
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.submit_request.components.TagSelector', key, params)
},
toggleTag(key) { toggleTag(key) {
const currentList = [...this.value] const currentList = [...this.value]
const index = currentList.indexOf(key) const index = currentList.indexOf(key)

View File

@@ -44,22 +44,22 @@
</view> </view>
<view v-if="showPreferenceSelectors" class="form-item tn-margin-top"> <view v-if="showPreferenceSelectors" class="form-item tn-margin-top">
<view class="form-label">{{ t('hobbyLabel') }}</view> <view class="form-label">{{ $tr('兴趣爱好') }}</view>
<tag-selector <tag-selector
v-model="form.hobbiesAndInterestsTagKeys" v-model="form.hobbiesAndInterestsTagKeys"
:options="hobbyOptions" :options="hobbyOptions"
:loading="dictLoading" :loading="dictLoading"
:emptyText="t('emptyHobbyTags')" :emptyText="$tr('暂无兴趣爱好标签')"
/> />
</view> </view>
<view v-if="showPreferenceSelectors" class="form-item tn-margin-top"> <view v-if="showPreferenceSelectors" class="form-item tn-margin-top">
<view class="form-label">{{ t('lifestyleLabel') }}</view> <view class="form-label">{{ $tr('生活习惯') }}</view>
<tag-selector <tag-selector
v-model="form.lifestyleHabitsTagKeys" v-model="form.lifestyleHabitsTagKeys"
:options="lifestyleOptions" :options="lifestyleOptions"
:loading="dictLoading" :loading="dictLoading"
:emptyText="t('emptyLifestyleTags')" :emptyText="$tr('暂无生活习惯标签')"
/> />
</view> </view>
@@ -76,12 +76,12 @@
</view> </view>
<view class="form-item tn-margin-top"> <view class="form-item tn-margin-top">
<view class="form-label">{{ t('remarkLabel') }}</view> <view class="form-label">{{ $tr('补充说明') }}</view>
<textarea class="form-textarea" :placeholder="remarkPlaceholder" v-model="form.remark"></textarea> <textarea class="form-textarea" :placeholder="remarkPlaceholder" v-model="form.remark"></textarea>
</view> </view>
<view v-if="!showFloatingSubmitBtn" class="submit-btn btn-primary tn-margin-top-xl" :class="{ 'submit-btn--disabled': isSubmitting }" @click="submitRequest"> <view v-if="!showFloatingSubmitBtn" class="submit-btn btn-primary tn-margin-top-xl" :class="{ 'submit-btn--disabled': isSubmitting }" @click="submitRequest">
<text class="tn-text-bold">{{ isSubmitting ? t('submitting') : primaryActionText }}</text> <text class="tn-text-bold">{{ isSubmitting ? $tr('提交中...') : primaryActionText }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -91,7 +91,7 @@
<view v-if="showFloatingSubmitBtn" class="floating-submit-bar"> <view v-if="showFloatingSubmitBtn" class="floating-submit-bar">
<view class="floating-submit-bar__inner"> <view class="floating-submit-bar__inner">
<view class="submit-btn btn-primary" :class="{ 'submit-btn--disabled': isSubmitting }" @click="submitRequest"> <view class="submit-btn btn-primary" :class="{ 'submit-btn--disabled': isSubmitting }" @click="submitRequest">
<text class="tn-text-bold">{{ isSubmitting ? t('submitting') : primaryActionText }}</text> <text class="tn-text-bold">{{ isSubmitting ? $tr('提交中...') : primaryActionText }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -594,81 +594,7 @@ export default {
lifestyleOptions: [], lifestyleOptions: [],
form: createDefaultForm(), form: createDefaultForm(),
scrollTop: 0, scrollTop: 0,
lastAutoRemark: '', lastAutoRemark: ''
i18n: {
zh: {
hobbyLabel: '兴趣爱好',
emptyHobbyTags: '暂无兴趣爱好标签',
lifestyleLabel: '生活习惯',
emptyLifestyleTags: '暂无生活习惯标签',
remarkLabel: '补充说明',
submitting: '提交中...',
roomTypeApplyTitle: '房型申请',
roomTypeApplyDesc: '请确认房型、区域、偏好标签和租期信息后提交申请。',
roomTypeApplyBlockTitle: '房型申请信息',
roomTypeApplyBlockDesc: '已接入真实申请单接口,支持带出房型与区域编码后直接提交。',
applyRemarkPlaceholder: '请输入您的申请补充说明',
submitRoomTypeApply: '提交房型申请',
applyRoomType: '申请房型',
leaseRange: '租期时间',
expectedCheckin: '预计入住',
expectedCheckout: '预计离开',
selectCheckinDate: '请选择入住日期',
selectCheckoutDate: '请选择离开日期',
currentRoom: '当前房间',
areaLabel: '所属区域',
referencePrice: '参考价格',
paymentMethod: '支付方式',
applicationType: '申请类型',
stayApplyDefaultTitle: '入住申请',
stayApplyDefaultDesc: '请填写您的入住意向、生活偏好与预计租期后提交申请单。',
sourceDefaultDesc: '请填写您的入住意向、租期和补充说明。',
tagLoadFailed: '偏好标签加载失败',
remarkRequired: '请输入补充说明',
applicationInfo: '申请信息',
selectDateStart: '请选择{label}',
selectDateEnd: '请选择{label}',
dateCannotBefore: '{endLabel}不能早于{startLabel}',
submitSuccess: '申请单提交成功',
submitFailed: '申请单提交失败'
},
en: {
hobbyLabel: 'Hobbies',
emptyHobbyTags: 'No hobby tags',
lifestyleLabel: 'Lifestyle Habits',
emptyLifestyleTags: 'No lifestyle tags',
remarkLabel: 'Additional Notes',
submitting: 'Submitting...',
roomTypeApplyTitle: 'Room Type Application',
roomTypeApplyDesc: 'Confirm the room type, area, preference tags, and lease period before submitting.',
roomTypeApplyBlockTitle: 'Application Details',
roomTypeApplyBlockDesc: 'The live application API is connected and can submit with room and area codes.',
applyRemarkPlaceholder: 'Enter your additional notes',
submitRoomTypeApply: 'Submit Application',
applyRoomType: 'Requested Room Type',
leaseRange: 'Lease Period',
expectedCheckin: 'Expected Check-in',
expectedCheckout: 'Expected Checkout',
selectCheckinDate: 'Select check-in date',
selectCheckoutDate: 'Select checkout date',
currentRoom: 'Current Room',
areaLabel: 'Area',
referencePrice: 'Reference Price',
paymentMethod: 'Payment Method',
applicationType: 'Application Type',
stayApplyDefaultTitle: 'Stay Application',
stayApplyDefaultDesc: 'Fill in your stay intention, preferences, and lease period before submission.',
sourceDefaultDesc: 'Fill in your stay intention, lease period, and notes.',
tagLoadFailed: 'Failed to load preference tags',
remarkRequired: 'Please enter additional notes',
applicationInfo: 'Application Details',
selectDateStart: 'Please select {label}',
selectDateEnd: 'Please select {label}',
dateCannotBefore: '{endLabel} cannot be earlier than {startLabel}',
submitSuccess: 'Application submitted',
submitFailed: 'Failed to submit application'
}
}
} }
}, },
computed: { computed: {
@@ -709,52 +635,52 @@ export default {
pageTitle() { pageTitle() {
if (this.isRoomTypeApply) { if (this.isRoomTypeApply) {
return this.language === 'en' return this.language === 'en'
? compactEnglishText(this.t('roomTypeApplyTitle'), 22) ? compactEnglishText(this.$tr('房型申请'), 22)
: this.t('roomTypeApplyTitle') : this.$tr('房型申请')
} }
return this.getBusinessText('titleShort') return this.getBusinessText('titleShort')
|| this.getBusinessText('title') || this.getBusinessText('title')
|| this.getLocalizedSourceText('title') || this.getLocalizedSourceText('title')
|| this.t('stayApplyDefaultTitle') || this.$tr('入住申请')
}, },
pageDesc() { pageDesc() {
if (this.isRoomTypeApply) { if (this.isRoomTypeApply) {
return this.language === 'en' return this.language === 'en'
? compactEnglishText(this.t('roomTypeApplyDesc'), 96) ? compactEnglishText(this.$tr('请确认房型、区域、偏好标签和租期信息后提交申请。'), 96)
: this.t('roomTypeApplyDesc') : this.$tr('请确认房型、区域、偏好标签和租期信息后提交申请。')
} }
const text = this.getBusinessText('desc') || this.getLocalizedSourceText('desc') || this.t('stayApplyDefaultDesc') const text = this.getBusinessText('desc') || this.getLocalizedSourceText('desc') || this.$tr('请填写您的入住意向、生活偏好与预计租期后提交申请单。')
return this.language === 'en' ? compactEnglishText(text, 96) : text return this.language === 'en' ? compactEnglishText(text, 96) : text
}, },
formBlockTitle() { formBlockTitle() {
if (this.isRoomTypeApply) { if (this.isRoomTypeApply) {
return this.t('roomTypeApplyBlockTitle') return this.$tr('房型申请信息')
} }
return this.getBusinessText('formBlockTitle') || this.t('applicationInfo') return this.getBusinessText('formBlockTitle') || this.$tr('申请信息')
}, },
formBlockDesc() { formBlockDesc() {
if (this.isRoomTypeApply) { if (this.isRoomTypeApply) {
return this.t('roomTypeApplyBlockDesc') return this.$tr('已接入真实申请单接口,支持带出房型与区域编码后直接提交。')
} }
return this.getBusinessText('formBlockDesc') || this.t('stayApplyDefaultDesc') return this.getBusinessText('formBlockDesc') || this.$tr('请填写您的入住意向、生活偏好与预计租期后提交申请单。')
}, },
remarkPlaceholder() { remarkPlaceholder() {
if (this.isRoomTypeApply) { if (this.isRoomTypeApply) {
return this.t('applyRemarkPlaceholder') return this.$tr('请输入您的申请补充说明')
} }
return this.getBusinessText('remarkPlaceholder') || this.t('remarkRequired') return this.getBusinessText('remarkPlaceholder') || this.$tr('请输入补充说明')
}, },
primaryActionText() { primaryActionText() {
if (this.isRoomTypeApply) { if (this.isRoomTypeApply) {
return this.language === 'en' return this.language === 'en'
? compactEnglishText(this.t('submitRoomTypeApply'), 24) ? compactEnglishText(this.$tr('提交房型申请'), 24)
: this.t('submitRoomTypeApply') : this.$tr('提交房型申请')
} }
const text = this.getBusinessText('primaryActionText') || this.t('submitApplicationFallback') const text = this.getBusinessText('primaryActionText') || this.$tr('提交申请')
return this.language === 'en' ? compactEnglishText(text, 24) : text return this.language === 'en' ? compactEnglishText(text, 24) : text
}, },
roomTypeLabel() { roomTypeLabel() {
return this.getBusinessText('roomTypeLabel') || this.t('applyRoomType') return this.getBusinessText('roomTypeLabel') || this.$tr('申请房型')
}, },
pageBottomPadding() { pageBottomPadding() {
return this.showFloatingSubmitBtn ? '220rpx' : '60rpx' return this.showFloatingSubmitBtn ? '220rpx' : '60rpx'
@@ -763,7 +689,7 @@ export default {
return this.isRoomTypeApply return this.isRoomTypeApply
}, },
floorLabel() { floorLabel() {
return this.getBusinessText('floorLabel') || this.t('floorLabel') return this.getBusinessText('floorLabel') || this.$tr('楼层')
}, },
showRoomTypeSelector() { showRoomTypeSelector() {
if (this.isRoomTypeApply) { if (this.isRoomTypeApply) {
@@ -790,19 +716,19 @@ export default {
return this.businessConfig ? this.businessConfig.showDateRange !== false : true return this.businessConfig ? this.businessConfig.showDateRange !== false : true
}, },
dateRangeLabel() { dateRangeLabel() {
return this.getBusinessText('dateRangeLabel') || this.t('leaseRange') return this.getBusinessText('dateRangeLabel') || this.$tr('租期时间')
}, },
dateRangeStartLabel() { dateRangeStartLabel() {
return this.getBusinessText('dateRangeStartLabel') || this.t('expectedCheckin') return this.getBusinessText('dateRangeStartLabel') || this.$tr('预计入住')
}, },
dateRangeEndLabel() { dateRangeEndLabel() {
return this.getBusinessText('dateRangeEndLabel') || this.t('expectedCheckout') return this.getBusinessText('dateRangeEndLabel') || this.$tr('预计离开')
}, },
dateRangeStartPlaceholder() { dateRangeStartPlaceholder() {
return this.getBusinessText('dateRangeStartPlaceholder') || this.t('selectCheckinDate') return this.getBusinessText('dateRangeStartPlaceholder') || this.$tr('请选择入住日期')
}, },
dateRangeEndPlaceholder() { dateRangeEndPlaceholder() {
return this.getBusinessText('dateRangeEndPlaceholder') || this.t('selectCheckoutDate') return this.getBusinessText('dateRangeEndPlaceholder') || this.$tr('请选择离开日期')
} }
}, },
watch: { watch: {
@@ -839,9 +765,6 @@ export default {
this.loadDictOptions() this.loadDictOptions()
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.submit_request.submit_request', key, params)
},
decodeRouteText(value, fallback = '') { decodeRouteText(value, fallback = '') {
if (value === undefined || value === null || value === '') { if (value === undefined || value === null || value === '') {
return fallback return fallback
@@ -1000,7 +923,7 @@ export default {
.catch((error) => { .catch((error) => {
console.error('load application dict options failed', error) console.error('load application dict options failed', error)
uni.showToast({ uni.showToast({
title: this.t('tagLoadFailed'), title: this.$tr('偏好标签加载失败'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -1038,20 +961,20 @@ export default {
validateForm() { validateForm() {
if (this.showDateRange) { if (this.showDateRange) {
if (!this.form.start) { if (!this.form.start) {
return this.t('selectDateStart', { label: this.dateRangeStartLabel }) return this.$tr('请选择{label}', { label: this.dateRangeStartLabel })
} }
if (!this.form.end) { if (!this.form.end) {
return this.t('selectDateEnd', { label: this.dateRangeEndLabel }) return this.$tr('请选择{label}', { label: this.dateRangeEndLabel })
} }
if (new Date(this.form.end).getTime() < new Date(this.form.start).getTime()) { if (new Date(this.form.end).getTime() < new Date(this.form.start).getTime()) {
return this.t('dateCannotBefore', { return this.$tr('{endLabel}不能早于{startLabel}', {
endLabel: this.dateRangeEndLabel, endLabel: this.dateRangeEndLabel,
startLabel: this.dateRangeStartLabel startLabel: this.dateRangeStartLabel
}) })
} }
} }
if (!this.form.remark.trim()) { if (!this.form.remark.trim()) {
return this.t('remarkRequired') return this.$tr('请输入补充说明')
} }
return '' return ''
}, },
@@ -1084,7 +1007,7 @@ export default {
this.isSubmitting = true this.isSubmitting = true
uni.showLoading({ uni.showLoading({
title: this.t('submitting') title: this.$tr('提交中...')
}) })
const payload = this.buildSubmitPayload() const payload = this.buildSubmitPayload()
@@ -1096,7 +1019,7 @@ export default {
} }
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: this.t('submitSuccess'), title: this.$tr('申请单提交成功'),
icon: 'success' icon: 'success'
}) })
@@ -1111,7 +1034,7 @@ export default {
console.error('submit application form failed', error) console.error('submit application form failed', error)
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: (error && (error.message || error.msg)) || this.t('submitFailed'), title: (error && (error.message || error.msg)) || this.$tr('申请单提交失败'),
icon: 'none' icon: 'none'
}) })
}) })

View File

@@ -1,17 +1,17 @@
<template> <template>
<view class="ticket-center-page tn-safe-area-inset-bottom"> <view class="ticket-center-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('工单中心') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="page-content tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px' }"> <view class="page-content tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
<view class="hero-card tn-margin-bottom-lg"> <view class="hero-card tn-margin-bottom-lg">
<view class="hero-badge"> <view class="hero-badge">
<text class="badge-dot"></text> <text class="badge-dot"></text>
<text>{{ t('heroBadge') }}</text> <text>{{ $tr('SERVICE') }}</text>
</view> </view>
<view class="hero-title tn-margin-top">{{ t('heroTitle') }}</view> <view class="hero-title tn-margin-top">{{ $tr('住中工单服务') }}</view>
<view class="hero-desc tn-margin-top-sm">{{ t('heroDesc') }}</view> <view class="hero-desc tn-margin-top-sm">{{ $tr('报修、续住、退宿、调宿、送水、清洁和意见建议都统一从这里进入,提交后可继续查看流转记录。') }}</view>
</view> </view>
<view class="service-grid"> <view class="service-grid">
@@ -33,20 +33,6 @@
<script> <script>
export default { export default {
name: 'TicketCenterPage', name: 'TicketCenterPage',
i18n: {
zh: {
navTitle: '工单中心',
heroBadge: 'SERVICE',
heroTitle: '住中工单服务',
heroDesc: '报修、续住、退宿、调宿、送水、清洁和意见建议都统一从这里进入,提交后可继续查看流转记录。'
},
en: {
navTitle: 'Work Orders',
heroBadge: 'SERVICE',
heroTitle: 'In-stay Ticket Services',
heroDesc: 'Repair, renewal, checkout, room change, water delivery, cleaning, and suggestions are all handled here.'
}
},
data() { data() {
return { return {
ticketServices: [ ticketServices: [
@@ -110,9 +96,6 @@ export default {
} }
}, },
methods: { methods: {
t(key, params) {
return this.$translate(this.i18n, key, params)
},
getItemText(item, fieldZh, fieldEn) { getItemText(item, fieldZh, fieldEn) {
return this.$getLanguage && this.$getLanguage() === 'en' return this.$getLanguage && this.$getLanguage() === 'en'
? (item[fieldEn] || item[fieldZh] || '') ? (item[fieldEn] || item[fieldZh] || '')

View File

@@ -15,7 +15,7 @@
<view class="hero-header tn-margin-bottom-xl"> <view class="hero-header tn-margin-bottom-xl">
<view class="hero-badge"> <view class="hero-badge">
<text class="badge-dot"></text> <text class="badge-dot"></text>
<text>{{ t('ticketKicker') }}</text> <text>{{ $tr('WORK ORDER') }}</text>
</view> </view>
<view class="hero-title">{{ pageTitle }}</view> <view class="hero-title">{{ pageTitle }}</view>
<view class="hero-desc">{{ pageDesc }}</view> <view class="hero-desc">{{ pageDesc }}</view>
@@ -27,35 +27,35 @@
:class="{ 'segment-item--active': activeTab === 'submit' }" :class="{ 'segment-item--active': activeTab === 'submit' }"
@click="switchTab('submit')" @click="switchTab('submit')"
> >
{{ t('submitTab') }} {{ $tr('提交申请') }}
</view> </view>
<view <view
class="segment-item" class="segment-item"
:class="{ 'segment-item--active': activeTab === 'history' }" :class="{ 'segment-item--active': activeTab === 'history' }"
@click="switchTab('history')" @click="switchTab('history')"
> >
{{ t('historyTab') }} {{ $tr('记录查询') }}
</view> </view>
</view> </view>
<view v-if="activeTab === 'submit'" class="context-card tn-margin-bottom-lg"> <view v-if="activeTab === 'submit'" class="context-card tn-margin-bottom-lg">
<view class="block-title">{{ t('currentStayInfo') }}</view> <view class="block-title">{{ $tr('当前入住信息') }}</view>
<view class="block-subtitle">{{ currentOrderStatusText }}</view> <view class="block-subtitle">{{ currentOrderStatusText }}</view>
<view class="context-grid tn-margin-top"> <view class="context-grid tn-margin-top">
<view class="context-item"> <view class="context-item">
<view class="context-label">{{ t('roomLabel') }}</view> <view class="context-label">{{ $tr('当前房间') }}</view>
<view class="context-value">{{ currentRoomText }}</view> <view class="context-value">{{ currentRoomText }}</view>
</view> </view>
<view v-if="currentAreaText" class="context-item"> <view v-if="currentAreaText" class="context-item">
<view class="context-label">{{ t('areaLabel') }}</view> <view class="context-label">{{ $tr('所属区域') }}</view>
<view class="context-value">{{ currentAreaText }}</view> <view class="context-value">{{ currentAreaText }}</view>
</view> </view>
<view v-if="currentLeaseText" class="context-item"> <view v-if="currentLeaseText" class="context-item">
<view class="context-label">{{ t('leaseLabel') }}</view> <view class="context-label">{{ $tr('租期') }}</view>
<view class="context-value">{{ currentLeaseText }}</view> <view class="context-value">{{ currentLeaseText }}</view>
</view> </view>
<view v-if="currentRoomTypeText" class="context-item"> <view v-if="currentRoomTypeText" class="context-item">
<view class="context-label">{{ t('roomTypeLabel') }}</view> <view class="context-label">{{ $tr('当前房型') }}</view>
<view class="context-value">{{ currentRoomTypeText }}</view> <view class="context-value">{{ currentRoomTypeText }}</view>
</view> </view>
</view> </view>
@@ -64,8 +64,8 @@
<view v-if="modeBannerText && activeTab === 'submit'" class="status-card tn-margin-bottom-lg" :class="statusCardClass"> <view v-if="modeBannerText && activeTab === 'submit'" class="status-card tn-margin-bottom-lg" :class="statusCardClass">
<view class="status-card__title">{{ modeBannerTitle }}</view> <view class="status-card__title">{{ modeBannerTitle }}</view>
<view class="status-card__desc">{{ modeBannerText }}</view> <view class="status-card__desc">{{ modeBannerText }}</view>
<view v-if="fixOnholdMode" class="status-card__action" @click="cancelFixOnhold">{{ t('cancelSupplement') }}</view> <view v-if="fixOnholdMode" class="status-card__action" @click="cancelFixOnhold">{{ $tr('取消补充') }}</view>
<view v-else-if="transitActionMode" class="status-card__action" @click="cancelTransitAction">{{ t('cancelProcess') }}</view> <view v-else-if="transitActionMode" class="status-card__action" @click="cancelTransitAction">{{ $tr('退出处理') }}</view>
</view> </view>
<view v-if="activeTab === 'submit'" class="form-card"> <view v-if="activeTab === 'submit'" class="form-card">
@@ -73,7 +73,7 @@
<view class="block-subtitle">{{ submitSectionDesc }}</view> <view class="block-subtitle">{{ submitSectionDesc }}</view>
<view v-if="isChangeService" class="form-item tn-margin-top"> <view v-if="isChangeService" class="form-item tn-margin-top">
<view class="form-label">{{ t('changeRoomTypeLabel') }}</view> <view class="form-label">{{ $tr('意向房型') }}</view>
<room-type-selector <room-type-selector
v-model="switchForm.roomTypeCode" v-model="switchForm.roomTypeCode"
:initial-area-code="switchForm.areaCode" :initial-area-code="switchForm.areaCode"
@@ -82,37 +82,37 @@
</view> </view>
<view v-if="isCheckOutService" class="form-item tn-margin-top"> <view v-if="isCheckOutService" class="form-item tn-margin-top">
<view class="form-label">{{ t('checkoutTimeLabel') }}</view> <view class="form-label">{{ $tr('预计退宿时间') }}</view>
<view class="picker-row"> <view class="picker-row">
<picker mode="date" :value="form.checkOutDate" @change="onDateChange('checkOutDate', $event)"> <picker mode="date" :value="form.checkOutDate" @change="onDateChange('checkOutDate', $event)">
<view class="form-picker">{{ form.checkOutDate || t('selectDate') }}</view> <view class="form-picker">{{ form.checkOutDate || $tr('请选择日期') }}</view>
</picker> </picker>
<picker mode="time" :value="form.checkOutTimePart" @change="onTimeChange('checkOutTimePart', $event)"> <picker mode="time" :value="form.checkOutTimePart" @change="onTimeChange('checkOutTimePart', $event)">
<view class="form-picker form-picker--time">{{ form.checkOutTimePart || t('selectTime') }}</view> <view class="form-picker form-picker--time">{{ form.checkOutTimePart || $tr('请选择时间') }}</view>
</picker> </picker>
</view> </view>
</view> </view>
<view v-if="isRenewalService" class="form-item tn-margin-top"> <view v-if="isRenewalService" class="form-item tn-margin-top">
<view class="form-label">{{ t('renewalTimeLabel') }}</view> <view class="form-label">{{ $tr('续住至') }}</view>
<view class="picker-row"> <view class="picker-row">
<picker mode="date" :value="form.renewalDate" @change="onDateChange('renewalDate', $event)"> <picker mode="date" :value="form.renewalDate" @change="onDateChange('renewalDate', $event)">
<view class="form-picker">{{ form.renewalDate || t('selectDate') }}</view> <view class="form-picker">{{ form.renewalDate || $tr('请选择日期') }}</view>
</picker> </picker>
<picker mode="time" :value="form.renewalTimePart" @change="onTimeChange('renewalTimePart', $event)"> <picker mode="time" :value="form.renewalTimePart" @change="onTimeChange('renewalTimePart', $event)">
<view class="form-picker form-picker--time">{{ form.renewalTimePart || t('selectTime') }}</view> <view class="form-picker form-picker--time">{{ form.renewalTimePart || $tr('请选择时间') }}</view>
</picker> </picker>
</view> </view>
</view> </view>
<view v-if="isCleaningService" class="form-item tn-margin-top"> <view v-if="isCleaningService" class="form-item tn-margin-top">
<view class="form-label">{{ t('cleaningTimeLabel') }}</view> <view class="form-label">{{ $tr('预约清洁时间') }}</view>
<view class="picker-row"> <view class="picker-row">
<picker mode="date" :value="form.cleaningDate" @change="onDateChange('cleaningDate', $event)"> <picker mode="date" :value="form.cleaningDate" @change="onDateChange('cleaningDate', $event)">
<view class="form-picker">{{ form.cleaningDate || t('selectDate') }}</view> <view class="form-picker">{{ form.cleaningDate || $tr('请选择日期') }}</view>
</picker> </picker>
<picker mode="time" :value="form.cleaningTimePart" @change="onTimeChange('cleaningTimePart', $event)"> <picker mode="time" :value="form.cleaningTimePart" @change="onTimeChange('cleaningTimePart', $event)">
<view class="form-picker form-picker--time">{{ form.cleaningTimePart || t('selectTime') }}</view> <view class="form-picker form-picker--time">{{ form.cleaningTimePart || $tr('请选择时间') }}</view>
</picker> </picker>
</view> </view>
</view> </view>
@@ -129,26 +129,26 @@
</view> </view>
<view class="form-item tn-margin-top"> <view class="form-item tn-margin-top">
<view class="form-label">{{ t('remarkLabel') }}</view> <view class="form-label">{{ $tr('补充说明') }}</view>
<textarea <textarea
class="form-textarea form-textarea--remark" class="form-textarea form-textarea--remark"
:placeholder="t('remarkPlaceholder')" :placeholder="$tr('请输入补充说明,帮助工作人员更快处理')"
:maxlength="300" :maxlength="300"
v-model="form.remark" v-model="form.remark"
></textarea> ></textarea>
</view> </view>
<view v-if="showTransitAssigneeSelector" class="form-item tn-margin-top"> <view v-if="showTransitAssigneeSelector" class="form-item tn-margin-top">
<view class="form-label">{{ t('assigneeLabel') }}</view> <view class="form-label">{{ $tr('处理目标') }}</view>
<picker mode="selector" :range="transitAssigneePickerLabels" @change="onTransitAssigneeChange"> <picker mode="selector" :range="transitAssigneePickerLabels" @change="onTransitAssigneeChange">
<view class="form-picker"> <view class="form-picker">
{{ transitSelectedAssigneeName || t('selectAssignee') }} {{ transitSelectedAssigneeName || $tr('请选择处理目标') }}
</view> </view>
</picker> </picker>
</view> </view>
<view v-if="fixOnholdMode && fixOnholdFields.length" class="form-item tn-margin-top"> <view v-if="fixOnholdMode && fixOnholdFields.length" class="form-item tn-margin-top">
<view class="form-label">{{ t('supplementLabel') }}</view> <view class="form-label">{{ $tr('补充业务信息') }}</view>
<view <view
v-for="(item, index) in fixOnholdFields" v-for="(item, index) in fixOnholdFields"
:key="`${item.key}-${index}`" :key="`${item.key}-${index}`"
@@ -159,20 +159,20 @@
v-if="shouldUseTextarea(item.value)" v-if="shouldUseTextarea(item.value)"
class="form-textarea form-textarea--small" class="form-textarea form-textarea--small"
v-model="item.value" v-model="item.value"
:placeholder="t('supplementPlaceholder', { label: item.title })" :placeholder="$tr('请输入{label}', { label: item.title })"
></textarea> ></textarea>
<input <input
v-else v-else
class="form-input" class="form-input"
type="text" type="text"
v-model="item.value" v-model="item.value"
:placeholder="t('supplementPlaceholder', { label: item.title })" :placeholder="$tr('请输入{label}', { label: item.title })"
/> />
</view> </view>
</view> </view>
<view class="form-item tn-margin-top"> <view class="form-item tn-margin-top">
<view class="form-label">{{ t('attachmentsLabel') }}<text v-if="isRepairService" class="required-mark"> *</text></view> <view class="form-label">{{ $tr('附件图片') }}<text v-if="isRepairService" class="required-mark"> *</text></view>
<tn-image-upload <tn-image-upload
:key="uploadInstanceKey" :key="uploadInstanceKey"
:fileList="form.photos" :fileList="form.photos"
@@ -192,7 +192,7 @@
<template #addBtn> <template #addBtn>
<view class="upload-trigger"> <view class="upload-trigger">
<text class="tn-icon-camera-fill upload-trigger__icon"></text> <text class="tn-icon-camera-fill upload-trigger__icon"></text>
<view class="upload-trigger__text">{{ t('uploadImage') }}</view> <view class="upload-trigger__text">{{ $tr('上传图片') }}</view>
</view> </view>
</template> </template>
</tn-image-upload> </tn-image-upload>
@@ -207,13 +207,13 @@
<input <input
v-model="searchKeyword" v-model="searchKeyword"
class="search-box__input" class="search-box__input"
:placeholder="t('searchPlaceholder')" :placeholder="$tr('搜索工单标题或内容')"
confirm-type="search" confirm-type="search"
@confirm="refreshHistory" @confirm="refreshHistory"
/> />
</view> </view>
<view class="refresh-mini-btn" @click="refreshHistory"> <view class="refresh-mini-btn" @click="refreshHistory">
{{ historyLoading ? t('refreshing') : t('refresh') }} {{ historyLoading ? $tr('刷新中...') : $tr('刷新') }}
</view> </view>
</view> </view>
@@ -249,26 +249,26 @@
</view> </view>
<view class="history-card__body tn-margin-top"> <view class="history-card__body tn-margin-top">
<view class="info-row tn-flex tn-flex-row-between"> <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> <text class="info-value">{{ formatDateTime(item.createTime) }}</text>
</view> </view>
<view class="info-row tn-flex tn-flex-row-between"> <view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('contentLabel') }}</text> <text class="info-label">{{ $tr('申请内容') }}</text>
<text class="info-value info-value--multiline">{{ item.content || item.remark || '--' }}</text> <text class="info-value info-value--multiline">{{ item.content || item.remark || '--' }}</text>
</view> </view>
</view> </view>
</view> </view>
<view class="load-state"> <view class="load-state">
<text v-if="historyLoading">{{ t('loadingMore') }}</text> <text v-if="historyLoading">{{ $tr('加载中...') }}</text>
<text v-else-if="!hasNext">{{ t('noMore') }}</text> <text v-else-if="!hasNext">{{ $tr('没有更多了') }}</text>
<text v-else @click="loadMoreHistory">{{ t('loadMore') }}</text> <text v-else @click="loadMoreHistory">{{ $tr('加载更多') }}</text>
</view> </view>
</view> </view>
<view v-else-if="!historyLoading" class="empty-panel tn-padding-lg"> <view v-else-if="!historyLoading" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('emptyHistoryTitle') }}</view> <view class="empty-title">{{ $tr('暂无工单记录') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('emptyHistoryDesc') }}</view> <view class="empty-desc tn-margin-top-sm">{{ $tr('当前类型还没有工单记录。') }}</view>
</view> </view>
</view> </view>
</view> </view>
@@ -278,7 +278,7 @@
<view class="floating-submit-bar__inner"> <view class="floating-submit-bar__inner">
<view class="floating-submit-tip">{{ submitFooterTip }}</view> <view class="floating-submit-tip">{{ submitFooterTip }}</view>
<view class="submit-btn btn-primary" :class="{ 'submit-btn--disabled': submitting }" @click="submitForm"> <view class="submit-btn btn-primary" :class="{ 'submit-btn--disabled': submitting }" @click="submitForm">
<text class="tn-text-bold">{{ submitting ? t('submitting') : submitButtonText }}</text> <text class="tn-text-bold">{{ submitting ? $tr('提交中...') : submitButtonText }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -298,10 +298,10 @@
<view class="flow-card__status">{{ getFlowStatusText(item) }}</view> <view class="flow-card__status">{{ getFlowStatusText(item) }}</view>
<view class="flow-card__time tn-margin-top-xs">{{ formatDateTime(item.createTime) }}</view> <view class="flow-card__time tn-margin-top-xs">{{ formatDateTime(item.createTime) }}</view>
</view> </view>
<view v-if="isCurrentFlowItem(index)" class="flow-card__tag">{{ t('currentFlowTag') }}</view> <view v-if="isCurrentFlowItem(index)" class="flow-card__tag">{{ $tr('当前节点') }}</view>
</view> </view>
<view class="flow-card__content tn-margin-top">{{ item.content || item.remark || t('noFlowContent') }}</view> <view class="flow-card__content tn-margin-top">{{ item.content || item.remark || $tr('暂无处理内容') }}</view>
<view v-if="item.attachments && item.attachments.length" class="flow-attachments tn-margin-top"> <view v-if="item.attachments && item.attachments.length" class="flow-attachments tn-margin-top">
<image <image
@@ -316,7 +316,7 @@
</view> </view>
<view v-if="transitionOptions.length" class="flow-actions"> <view v-if="transitionOptions.length" class="flow-actions">
<view class="block-title">{{ t('flowActionTitle') }}</view> <view class="block-title">{{ $tr('可执行操作') }}</view>
<view class="flow-action-list tn-margin-top"> <view class="flow-action-list tn-margin-top">
<view <view
v-for="(item, index) in transitionOptions" v-for="(item, index) in transitionOptions"
@@ -324,14 +324,14 @@
class="flow-action-btn" class="flow-action-btn"
@click="startTransitAction(item, getCurrentFlowItem())" @click="startTransitAction(item, getCurrentFlowItem())"
> >
{{ item.nextStatusDesc || t('continueProcess') }} {{ item.nextStatusDesc || $tr('继续处理') }}
</view> </view>
</view> </view>
</view> </view>
<view v-if="canFixOnhold(currentFlowOrder)" class="flow-actions tn-margin-top"> <view v-if="canFixOnhold(currentFlowOrder)" class="flow-actions tn-margin-top">
<view class="flow-action-btn flow-action-btn--ghost" @click="startFixOnhold(currentFlowOrder)"> <view class="flow-action-btn flow-action-btn--ghost" @click="startFixOnhold(currentFlowOrder)">
{{ t('supplementAction') }} {{ $tr('补充信息') }}
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
@@ -455,158 +455,6 @@ export default {
components: { components: {
RoomTypeSelector RoomTypeSelector
}, },
i18n: {
zh: {
areaLabel: '所属区域',
assigneeLabel: '处理目标',
attachmentsLabel: '附件图片',
cancelProcess: '退出处理',
cancelSupplement: '取消补充',
changeRoomTypeLabel: '意向房型',
cleaningTimeLabel: '预约清洁时间',
contentLabel: '申请内容',
currentFlowTag: '当前节点',
currentStayInfo: '当前入住信息',
currentStayOrder: '当前入住订单',
emptyHistoryDesc: '当前类型还没有工单记录。',
emptyHistoryTitle: '暂无工单记录',
flowActionTitle: '可执行操作',
historyTab: '记录查询',
leaseLabel: '租期',
loadMore: '加载更多',
loadingMore: '加载中...',
loadOrderFailed: '入住信息加载失败',
noFlowContent: '暂无处理内容',
noMore: '没有更多了',
refresh: '刷新',
refreshing: '刷新中...',
remarkLabel: '补充说明',
remarkPlaceholder: '请输入补充说明,帮助工作人员更快处理',
roomLabel: '当前房间',
roomTypeLabel: '当前房型',
searchPlaceholder: '搜索工单标题或内容',
selectAssignee: '请选择处理目标',
selectDate: '请选择日期',
selectTime: '请选择时间',
submitSectionDefaultDesc: '以下内容将通过工单接口提交并进入后续流转。',
submitTab: '提交申请',
submitTicket: '提交工单',
submitting: '提交中...',
supplementAction: '补充信息',
supplementLabel: '补充业务信息',
supplementPlaceholder: '请输入{label}',
ticketKicker: 'WORK ORDER',
uploadImage: '上传图片',
uploadImageCountExceeded: '最多上传 6 张图片',
uploadImageFailed: '图片上传失败,请重试',
uploadImageOversize: '图片不能超过 5MB',
uploadTip: '支持上传 JPG、PNG 等图片,单张不超过 5MB,最多 6 张。',
uploadTipRepair: '支持上传 JPG、PNG 等图片,单张不超过 5MB,最多 6 张。报修至少上传 1 张图片。',
checkoutTimeLabel: '预计退宿时间',
renewalTimeLabel: '续住至',
submittedAtLabel: '提交时间',
submitSuccess: '工单提交成功',
submitFailed: '工单提交失败',
confirmSubmitTitle: '确认提交',
confirmSubmitContent: '确认提交当前工单吗?',
missingStayContext: '当前缺少入住订单或房间信息,暂无法提交工单',
repairAttachmentRequired: '请上传报修附件图片',
changeAreaRequired: '请选择意向房型',
checkoutTimeRequired: '请选择退宿时间',
checkoutReasonRequired: '请填写退宿原因',
loadHistoryFailed: '工单记录加载失败',
loadFlowsFailed: '工单流转加载失败',
loadActionsFailed: '工单操作项加载失败',
continueProcess: '继续处理',
loadAssigneeFailed: '处理目标加载失败',
submitSectionActionDesc: '当前工单正在处理中,提交后会继续流转。',
submitSectionSupplementDesc: '当前工单处于挂起状态,补充信息后会继续流转。',
currentStatusPending: '待处理',
currentStatusProcessing: '处理中',
currentStatusResolved: '已解决',
currentStatusClosed: '已结束',
allStatus: '全部',
pendingStatus: '待处理',
processingStatus: '处理中',
resolvedStatus: '已解决',
closedStatus: '已结束'
},
en: {
areaLabel: 'Area',
assigneeLabel: 'Assignee',
attachmentsLabel: 'Attachments',
cancelProcess: 'Cancel Action',
cancelSupplement: 'Cancel Supplement',
changeRoomTypeLabel: 'Preferred Room Type',
cleaningTimeLabel: 'Cleaning Time',
contentLabel: 'Content',
currentFlowTag: 'Current',
currentStayInfo: 'Current Stay',
currentStayOrder: 'Current Stay Order',
emptyHistoryDesc: 'No tickets of this type yet.',
emptyHistoryTitle: 'No Ticket Records',
flowActionTitle: 'Available Actions',
historyTab: 'History',
leaseLabel: 'Lease',
loadMore: 'Load More',
loadingMore: 'Loading...',
loadOrderFailed: 'Failed to load stay context',
noFlowContent: 'No handling content',
noMore: 'No more records',
refresh: 'Refresh',
refreshing: 'Refreshing...',
remarkLabel: 'Notes',
remarkPlaceholder: 'Enter notes to help the staff handle this ticket',
roomLabel: 'Room',
roomTypeLabel: 'Room Type',
searchPlaceholder: 'Search title or content',
selectAssignee: 'Select assignee',
selectDate: 'Select date',
selectTime: 'Select time',
submitSectionDefaultDesc: 'The form below will be submitted through the ticket APIs.',
submitTab: 'Submit',
submitTicket: 'Submit Ticket',
submitting: 'Submitting...',
supplementAction: 'Supplement',
supplementLabel: 'Supplementary Fields',
supplementPlaceholder: 'Please enter {label}',
ticketKicker: 'WORK ORDER',
uploadImage: 'Upload Image',
uploadImageCountExceeded: 'Up to 6 images',
uploadImageFailed: 'Failed to upload image',
uploadImageOversize: 'Image must be less than 5MB',
uploadTip: 'Upload JPG or PNG images, up to 6 images and 5MB each.',
uploadTipRepair: 'Upload JPG or PNG images, up to 6 images and 5MB each. Repair requires at least 1 image.',
checkoutTimeLabel: 'Checkout Time',
renewalTimeLabel: 'Renew Until',
submittedAtLabel: 'Submitted At',
submitSuccess: 'Ticket submitted',
submitFailed: 'Failed to submit ticket',
confirmSubmitTitle: 'Confirm',
confirmSubmitContent: 'Confirm submitting this ticket?',
missingStayContext: 'Missing current stay order or room information',
repairAttachmentRequired: 'Please upload repair images',
changeAreaRequired: 'Please select a room type',
checkoutTimeRequired: 'Please select checkout time',
checkoutReasonRequired: 'Please fill in the checkout reason',
loadHistoryFailed: 'Failed to load ticket history',
loadFlowsFailed: 'Failed to load ticket flows',
loadActionsFailed: 'Failed to load ticket actions',
continueProcess: 'Continue',
loadAssigneeFailed: 'Failed to load assignees',
submitSectionActionDesc: 'The ticket is in progress and will continue after submission.',
submitSectionSupplementDesc: 'The ticket is on hold and will continue after supplementing the information.',
currentStatusPending: 'Pending',
currentStatusProcessing: 'Processing',
currentStatusResolved: 'Resolved',
currentStatusClosed: 'Closed',
allStatus: 'All',
pendingStatus: 'Pending',
processingStatus: 'Processing',
resolvedStatus: 'Resolved',
closedStatus: 'Closed'
}
},
data() { data() {
return { return {
serviceType: 'TicketRepairApply', serviceType: 'TicketRepairApply',
@@ -737,11 +585,11 @@ export default {
}, },
currentOrderStatusText() { currentOrderStatusText() {
const status = Number(this.currentOrderDetail.orderStatus) const status = Number(this.currentOrderDetail.orderStatus)
if (Number.isNaN(status)) return this.t('currentStayOrder') if (Number.isNaN(status)) return this.$tr('当前入住订单')
if ([0, 1, 2].includes(status)) return this.t('currentStatusPending') if ([0, 1, 2].includes(status)) return this.$tr('待处理')
if ([3, 4, 7].includes(status)) return this.t('currentStatusProcessing') if ([3, 4, 7].includes(status)) return this.$tr('处理中')
if ([5, 6].includes(status)) return this.t('currentStatusResolved') if ([5, 6].includes(status)) return this.$tr('已解决')
return this.t('currentStatusClosed') return this.$tr('已结束')
}, },
contentFieldLabel() { contentFieldLabel() {
if (this.isChangeService) return this.language === 'en' ? 'Change Reason' : '调宿说明' if (this.isChangeService) return this.language === 'en' ? 'Change Reason' : '调宿说明'
@@ -776,7 +624,7 @@ export default {
} }
}, },
uploadTipText() { uploadTipText() {
return this.isRepairService ? this.t('uploadTipRepair') : this.t('uploadTip') return this.isRepairService ? this.$tr('支持上传 JPG、PNG 等图片,单张不超过 5MB,最多 6 张。报修至少上传 1 张图片。') : this.$tr('支持上传 JPG、PNG 等图片,单张不超过 5MB,最多 6 张。')
}, },
submitButtonText() { submitButtonText() {
if (this.fixOnholdMode) { if (this.fixOnholdMode) {
@@ -785,9 +633,9 @@ export default {
if (this.transitActionMode) { if (this.transitActionMode) {
return this.transitActionOption && this.transitActionOption.nextStatusDesc return this.transitActionOption && this.transitActionOption.nextStatusDesc
? this.transitActionOption.nextStatusDesc ? this.transitActionOption.nextStatusDesc
: this.t('continueProcess') : this.$tr('继续处理')
} }
return this.t('submitTicket') return this.$tr('提交工单')
}, },
submitSectionTitle() { submitSectionTitle() {
if (this.fixOnholdMode) return this.language === 'en' ? 'Supplement Information' : '补充工单信息' if (this.fixOnholdMode) return this.language === 'en' ? 'Supplement Information' : '补充工单信息'
@@ -795,9 +643,9 @@ export default {
return this.pageTitle return this.pageTitle
}, },
submitSectionDesc() { submitSectionDesc() {
if (this.fixOnholdMode) return this.t('submitSectionSupplementDesc') if (this.fixOnholdMode) return this.$tr('当前工单处于挂起状态,补充信息后会继续流转。')
if (this.transitActionMode) return this.t('submitSectionActionDesc') if (this.transitActionMode) return this.$tr('当前工单正在处理中,提交后会继续流转。')
return this.t('submitSectionDefaultDesc') return this.$tr('以下内容将通过工单接口提交并进入后续流转。')
}, },
modeBannerTitle() { modeBannerTitle() {
if (this.fixOnholdMode) return this.language === 'en' ? 'Supplement Required' : '当前工单需要补充信息' if (this.fixOnholdMode) return this.language === 'en' ? 'Supplement Required' : '当前工单需要补充信息'
@@ -808,12 +656,12 @@ export default {
if (this.fixOnholdMode) { if (this.fixOnholdMode) {
return this.fixOnholdTicket && this.fixOnholdTicket.no return this.fixOnholdTicket && this.fixOnholdTicket.no
? `${this.fixOnholdTicket.no} ${this.language === 'en' ? 'needs more information before continuing.' : '需要补充信息后继续流转。'}` ? `${this.fixOnholdTicket.no} ${this.language === 'en' ? 'needs more information before continuing.' : '需要补充信息后继续流转。'}`
: this.t('submitSectionSupplementDesc') : this.$tr('当前工单处于挂起状态,补充信息后会继续流转。')
} }
if (this.transitActionMode) { if (this.transitActionMode) {
return this.transitActionOption && this.transitActionOption.nextStatusDesc return this.transitActionOption && this.transitActionOption.nextStatusDesc
? `${this.language === 'en' ? 'Current action:' : '当前操作:'}${this.transitActionOption.nextStatusDesc}` ? `${this.language === 'en' ? 'Current action:' : '当前操作:'}${this.transitActionOption.nextStatusDesc}`
: this.t('submitSectionActionDesc') : this.$tr('当前工单正在处理中,提交后会继续流转。')
} }
return '' return ''
}, },
@@ -827,11 +675,11 @@ export default {
}, },
historyStatusOptions() { historyStatusOptions() {
return [ return [
{ value: '', label: this.t('allStatus') }, { value: '', label: this.$tr('全部') },
{ value: '0', label: this.t('pendingStatus') }, { value: '0', label: this.$tr('待处理') },
{ value: '3', label: this.t('processingStatus') }, { value: '3', label: this.$tr('处理中') },
{ value: '5', label: this.t('resolvedStatus') }, { value: '5', label: this.$tr('已解决') },
{ value: '9', label: this.t('closedStatus') } { value: '9', label: this.$tr('已结束') }
] ]
}, },
transitAssigneePickerRange() { transitAssigneePickerRange() {
@@ -868,9 +716,6 @@ export default {
this.refreshContext() this.refreshContext()
}, },
methods: { methods: {
t(key, params) {
return this.$translate(this.i18n, key, params)
},
handleScroll(e) { handleScroll(e) {
this.scrollTop = e.detail.scrollTop this.scrollTop = e.detail.scrollTop
}, },
@@ -905,7 +750,7 @@ export default {
} }
}) })
.catch(() => { .catch(() => {
uni.showToast({ title: this.t('loadOrderFailed'), icon: 'none' }) uni.showToast({ title: this.$tr('入住信息加载失败'), icon: 'none' })
}) })
}, },
resetForm() { resetForm() {
@@ -1005,28 +850,28 @@ export default {
}, },
getSubmitValidationMessage() { getSubmitValidationMessage() {
if (this.fixOnholdMode && !this.fixOnholdFlowNo) { if (this.fixOnholdMode && !this.fixOnholdFlowNo) {
return this.t('submitFailed') return this.$tr('工单提交失败')
} }
if (this.transitActionMode && !this.transitActionFlowNo) { if (this.transitActionMode && !this.transitActionFlowNo) {
return this.t('submitFailed') return this.$tr('工单提交失败')
} }
if (!this.fixOnholdMode && !this.transitActionMode && !this.getSubmitOrderNo() && !this.getSubmitRoomCode()) { if (!this.fixOnholdMode && !this.transitActionMode && !this.getSubmitOrderNo() && !this.getSubmitRoomCode()) {
return this.t('missingStayContext') return this.$tr('当前缺少入住订单或房间信息,暂无法提交工单')
} }
if (this.isRepairService && !(Array.isArray(this.form.attachments) && this.form.attachments.length)) { if (this.isRepairService && !(Array.isArray(this.form.attachments) && this.form.attachments.length)) {
return this.t('repairAttachmentRequired') return this.$tr('请上传报修附件图片')
} }
if (this.isChangeService && !this.switchForm.roomTypeCode) { if (this.isChangeService && !this.switchForm.roomTypeCode) {
return this.t('changeAreaRequired') return this.$tr('请选择意向房型')
} }
if (this.isCheckOutService && !this.resolveCheckOutTime()) { if (this.isCheckOutService && !this.resolveCheckOutTime()) {
return this.t('checkoutTimeRequired') return this.$tr('请选择退宿时间')
} }
if (this.isCheckOutService && !String(this.form.content || '').trim()) { if (this.isCheckOutService && !String(this.form.content || '').trim()) {
return this.t('checkoutReasonRequired') return this.$tr('请填写退宿原因')
} }
if (this.showTransitAssigneeSelector && !this.transitSelectedAssigneeValue) { if (this.showTransitAssigneeSelector && !this.transitSelectedAssigneeValue) {
return this.t('selectAssignee') return this.$tr('请选择处理目标')
} }
return '' return ''
}, },
@@ -1278,8 +1123,8 @@ export default {
new Promise((resolve) => { new Promise((resolve) => {
uni.showModal({ uni.showModal({
title: this.t('confirmSubmitTitle'), title: this.$tr('确认提交'),
content: this.t('confirmSubmitContent'), content: this.$tr('确认提交当前工单吗?'),
success: (res) => resolve(!!res.confirm), success: (res) => resolve(!!res.confirm),
fail: () => resolve(false) fail: () => resolve(false)
}) })
@@ -1299,7 +1144,7 @@ export default {
return this.submitWithCompatibilityFallback(api, this.buildSubmitPayload()) return this.submitWithCompatibilityFallback(api, this.buildSubmitPayload())
}) })
.then(() => { .then(() => {
uni.showToast({ title: this.t('submitSuccess'), icon: 'success' }) uni.showToast({ title: this.$tr('工单提交成功'), icon: 'success' })
this.cancelFixOnhold() this.cancelFixOnhold()
this.cancelTransitAction() this.cancelTransitAction()
this.resetForm() this.resetForm()
@@ -1311,7 +1156,7 @@ export default {
return return
} }
uni.showToast({ uni.showToast({
title: this.getSubmitErrorText(error) || this.t('submitFailed'), title: this.getSubmitErrorText(error) || this.$tr('工单提交失败'),
icon: 'none' icon: 'none'
}) })
}) })
@@ -1360,7 +1205,7 @@ export default {
this.historyList = [] this.historyList = []
} }
this.hasNext = false this.hasNext = false
uni.showToast({ title: this.t('loadHistoryFailed'), icon: 'none' }) uni.showToast({ title: this.$tr('工单记录加载失败'), icon: 'none' })
}) })
.finally(() => { .finally(() => {
this.historyLoading = false this.historyLoading = false
@@ -1380,10 +1225,10 @@ export default {
return 'closed' return 'closed'
}, },
statusText(status) { statusText(status) {
if ([0, 1, 2].includes(status)) return this.t('pendingStatus') if ([0, 1, 2].includes(status)) return this.$tr('待处理')
if ([3, 4, 7].includes(status)) return this.t('processingStatus') if ([3, 4, 7].includes(status)) return this.$tr('处理中')
if ([5, 6].includes(status)) return this.t('resolvedStatus') if ([5, 6].includes(status)) return this.$tr('已解决')
return this.t('closedStatus') return this.$tr('已结束')
}, },
openFlows(item) { openFlows(item) {
this.currentFlowOrder = item this.currentFlowOrder = item
@@ -1398,7 +1243,7 @@ export default {
return this.loadTransitionOptions(this.getCurrentFlowItem()) return this.loadTransitionOptions(this.getCurrentFlowItem())
}) })
.catch(() => { .catch(() => {
uni.showToast({ title: this.t('loadFlowsFailed'), icon: 'none' }) uni.showToast({ title: this.$tr('工单流转加载失败'), icon: 'none' })
}) })
.finally(() => { .finally(() => {
this.flowLoading = false this.flowLoading = false
@@ -1419,7 +1264,7 @@ export default {
}) })
.catch(() => { .catch(() => {
this.transitionOptions = [] this.transitionOptions = []
uni.showToast({ title: this.t('loadActionsFailed'), icon: 'none' }) uni.showToast({ title: this.$tr('工单操作项加载失败'), icon: 'none' })
return [] return []
}) })
}, },
@@ -1624,7 +1469,7 @@ export default {
this.transitAssigneeGroups = this.normalizeTransitAssigneeGroups(response) this.transitAssigneeGroups = this.normalizeTransitAssigneeGroups(response)
}) })
.catch(() => { .catch(() => {
uni.showToast({ title: this.t('loadAssigneeFailed'), icon: 'none' }) uni.showToast({ title: this.$tr('处理目标加载失败'), icon: 'none' })
}) })
}, },
onTransitAssigneeChange(event) { onTransitAssigneeChange(event) {
@@ -1671,13 +1516,13 @@ export default {
this.form.attachments.splice(index, 1) this.form.attachments.splice(index, 1)
}, },
onUploadError() { onUploadError() {
uni.showToast({ title: this.t('uploadImageFailed'), icon: 'none' }) uni.showToast({ title: this.$tr('图片上传失败,请重试'), icon: 'none' })
}, },
onUploadOversize() { onUploadOversize() {
uni.showToast({ title: this.t('uploadImageOversize'), icon: 'none' }) uni.showToast({ title: this.$tr('图片不能超过 5MB'), icon: 'none' })
}, },
onUploadExceed() { onUploadExceed() {
uni.showToast({ title: this.t('uploadImageCountExceeded'), icon: 'none' }) uni.showToast({ title: this.$tr('最多上传 6 张图片'), icon: 'none' })
}, },
getAttachmentPreview(attachment) { getAttachmentPreview(attachment) {
return addFilePrefix(pickReadableValue(attachment && attachment.resourceUrl, attachment && attachment.url, attachment && attachment.path)) return addFilePrefix(pickReadableValue(attachment && attachment.resourceUrl, attachment && attachment.url, attachment && attachment.path))

View File

@@ -1,7 +1,7 @@
<template> <template>
<view class="bill-page tn-safe-area-inset-bottom"> <view class="bill-page tn-safe-area-inset-bottom">
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent"> <tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view> <view class="tn-text-bold tn-text-xl tn-color-black">{{ $tr('账单管理') }}</view>
</tn-nav-bar> </tn-nav-bar>
<view class="bill-body tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }"> <view class="bill-body tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }">
@@ -21,14 +21,6 @@ import { saveActiveApplicationCode, saveHomePageTabId } from '@/utils/app-state'
import CurrentApplicationBillPanel from '../components/CurrentApplicationBillPanel.vue' import CurrentApplicationBillPanel from '../components/CurrentApplicationBillPanel.vue'
export default { export default {
i18n: {
zh: {
navTitle: '账单管理'
},
en: {
navTitle: 'Bill Management'
}
},
components: { components: {
CurrentApplicationBillPanel CurrentApplicationBillPanel
}, },
@@ -45,9 +37,6 @@ export default {
} }
}, },
methods: { methods: {
t(key, params) {
return this.$tm('pkgService.utility_recharge.utility_recharge', key, params)
},
handleReferenceResolved(referenceTypeNo) { handleReferenceResolved(referenceTypeNo) {
this.referenceTypeNo = referenceTypeNo || '' this.referenceTypeNo = referenceTypeNo || ''
if (this.referenceTypeNo) { if (this.referenceTypeNo) {

View File

@@ -7,7 +7,8 @@ import {
normalizeLocale, normalizeLocale,
pickLocalizedText, pickLocalizedText,
translateGlobalMessages, translateGlobalMessages,
translateMessages translateMessages,
translateText
} from '@/utils/i18n' } from '@/utils/i18n'
import { syncNavigationBarTitle } from '@/utils/navigation-title' import { syncNavigationBarTitle } from '@/utils/navigation-title'
@@ -68,6 +69,9 @@ module.exports = {
$tm(namespace, key, params) { $tm(namespace, key, params) {
return translateGlobalMessages(namespace, this.$getLocale(), key, params) return translateGlobalMessages(namespace, this.$getLocale(), key, params)
}, },
$tr(text, params = {}) {
return translateText(text, this.$getLocale(), params)
},
$pickText(zh, en, fallback = '') { $pickText(zh, en, fallback = '') {
return pickLocalizedText({ return pickLocalizedText({
zh, zh,

View File

@@ -128,3 +128,103 @@ export function pickLocalizedText(options = {}) {
} }
return zhText || options.fallback || enText || '' return zhText || options.fallback || enText || ''
} }
// === 派生扁平字典(中文 → 英文)===
// 从 globalMessages 的 zh/en 双套结构反向派生 {'中文':'英文'} map,
// 供 translateText / $tr 使用。模块加载后懒加载、进程内缓存。
let __flatDictionary = null
let __dictConflicts = null
// 手动裁决的冲突项:键为中文,值为优先采用的英文。
// 当同一中文在 messages.js 不同位置有不同英文译法时,在此指定通用译法;
// 强上下文相关、无法单一裁决的条目,组件内保留 $tm 作为逃生口。
const DICT_OVERRIDES = {}
function buildFlatDictionary() {
if (__flatDictionary) return __flatDictionary
const dict = Object.create(null)
const conflicts = []
const seen = Object.create(null)
// 1. 优先注入手动裁决项
Object.keys(DICT_OVERRIDES).forEach((zh) => {
dict[zh] = DICT_OVERRIDES[zh]
seen[zh] = { en: DICT_OVERRIDES[zh], path: '<override>' }
})
const zhRoot = globalMessages.zh || globalMessages[ZH_LOCALE] || {}
const enRoot = globalMessages.en || globalMessages[EN_LOCALE] || {}
// 2. 平行 DFS:以 zh 叶子(中文)为 key,同路径 en 叶子为 value
function walk(zhNode, enNode, path) {
if (zhNode && typeof zhNode === 'object' && !Array.isArray(zhNode)) {
const keys = Object.keys(zhNode)
for (let i = 0; i < keys.length; i += 1) {
const k = keys[i]
walk(zhNode[k], enNode ? enNode[k] : undefined, path ? path + '.' + k : k)
}
return
}
const zhText = zhNode == null ? '' : String(zhNode)
if (!zhText) return
const enText = enNode == null ? zhText : String(enNode)
if (seen[zhText]) {
if (seen[zhText].en !== enText) {
conflicts.push({
zh: zhText,
enFirst: seen[zhText].en,
enCurrent: enText,
pathFirst: seen[zhText].path,
pathCurrent: path
})
}
// 首次写入优先:已存在则不覆盖
return
}
dict[zhText] = enText
seen[zhText] = { en: enText, path }
}
walk(zhRoot, enRoot, '')
__flatDictionary = dict
__dictConflicts = conflicts
if (process.env.NODE_ENV !== 'production' && conflicts.length) {
console.warn('[i18n] flat dictionary built with ' + conflicts.length + ' conflicts; call getDictionaryConflicts() for details')
}
return dict
}
// 中文即 key 的翻译函数:中文环境原样返回,英文环境查扁平字典,查不到回退中文。
// 支持与 $tm/$translate 一致的 {param} 占位符替换。
export function translateText(text, locale, params = {}) {
if (text === null || text === undefined) return ''
const zhText = String(text)
if (!zhText) return ''
if (normalizeLocale(locale) !== EN_LOCALE) {
return formatI18nText(zhText, params)
}
const dict = buildFlatDictionary()
const enText = dict[zhText]
if (enText === undefined) {
if (process.env.NODE_ENV !== 'production') {
console.warn('[i18n] missing dictionary entry for: ' + zhText)
}
return formatI18nText(zhText, params)
}
return formatI18nText(enText, params)
}
export function getFlatDictionary() {
return buildFlatDictionary()
}
export function getDictionaryConflicts() {
if (!__dictConflicts) buildFlatDictionary()
return __dictConflicts || []
}