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

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

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

View File

@@ -1,7 +1,7 @@
<template>
<view class="application-detail-page tn-safe-area-inset-bottom">
<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>
<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>
</view>
<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>
</view>
<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">
<text class="meta-label">{{ t('applicationCodeLabel') }}</text>
<text class="meta-label">{{ $tr('申请编号') }}</text>
<view class="meta-action-wrap">
<text class="meta-value">{{ detail.detailCode || '--' }}</text>
<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 class="meta-row">
<text class="meta-label">{{ t('applicationTypeLabel') }}</text>
<text class="meta-label">{{ $tr('申请类型') }}</text>
<text class="meta-value">{{ applicationTypeText }}</text>
</view>
<view class="meta-row">
<text class="meta-label">{{ t('statusLabel') }}</text>
<text class="meta-label">{{ $tr('当前状态') }}</text>
<text class="meta-value">{{ statusText }}</text>
</view>
<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>
</view>
<view class="meta-row">
<text class="meta-label">{{ t('apartmentLabel') }}</text>
<text class="meta-label">{{ $tr('所属公寓') }}</text>
<text class="meta-value">{{ detail.intendedAreaName || '--' }}</text>
</view>
<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>
</view>
<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>
</view>
<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>
</view>
</view>
<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
v-for="item in viewingMeta"
:key="item.label"
@@ -94,7 +94,7 @@
</view>
<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
v-for="(line, index) in remarkLines"
@@ -108,7 +108,7 @@
<view class="action-row">
<view class="ghost-btn" @click="goBackList">
<text class="tn-text-bold">{{ t('backList') }}</text>
<text class="tn-text-bold">{{ $tr('返回列表') }}</text>
</view>
<view
v-if="canCancelOrder"
@@ -116,7 +116,7 @@
:class="{ 'danger-btn--loading': canceling }"
@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
v-if="currentStepActionText"
@@ -130,14 +130,14 @@
class="action-btn action-btn--ghost"
@click.stop="refreshCurrentPage"
>
<text class="tn-text-bold">{{ t('refreshAction') }}</text>
<text class="tn-text-bold">{{ $tr('刷新') }}</text>
</view>
<view
v-else-if="canViewBill"
class="action-btn"
@click.stop="goToBillPage"
>
<text class="tn-text-bold">{{ t('viewBill') }}</text>
<text class="tn-text-bold">{{ $tr('查看关联账单') }}</text>
</view>
</view>
</view>
@@ -221,112 +221,6 @@ function parseJsonStringMaybe(value) {
}
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() {
return {
detailCode: '',
@@ -347,24 +241,24 @@ export default {
},
displayTitle() {
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() {
return this.isViewingApplication ? this.t('viewingApplicationType') : this.t('stayApplicationType')
return this.isViewingApplication ? this.$tr('预约看房') : this.$tr('入住申请')
},
statusText() {
if (this.language !== 'en' && this.detail && this.detail.statusDesc) {
return this.detail.statusDesc
}
const map = {
0: this.t('statusPending'),
10: this.t('statusApproved'),
20: this.t('statusRejected'),
99: this.t('statusFinished')
0: this.$tr('待审核'),
10: this.$tr('已通过'),
20: this.$tr('已拒绝'),
99: this.$tr('已结束')
}
return map[this.detail.status] || this.t('statusProcessing')
return map[this.detail.status] || this.$tr('处理中')
},
statusType() {
if (this.detail.status === 10) {
@@ -389,14 +283,14 @@ export default {
return '--'
}
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() {
const text = this.detail && this.detail.remark ? String(this.detail.remark).trim() : ''
if (!text) {
return [this.t('emptyRemark')]
return [this.$tr('暂无补充说明')]
}
return text.split(/\r?\n/).filter(Boolean)
},
@@ -429,17 +323,17 @@ export default {
return this.getReservationContractStatusText()
}
if (this.isReservationCheckinProcessing()) {
return this.t('checkinPendingHint')
return this.$tr('宿管正在办理入住,请耐心等待')
}
const reservationStepText = getReservationCurrentStepText(this.detail, this.language)
if (reservationStepText) {
return reservationStepText
}
if (this.shouldPayDeposit(this.detail)) {
return this.t('statusPendingPayment')
return this.$tr('请支付定金')
}
if (this.isWaitingRoomAssignment(this.detail)) {
return this.t('statusWaitingRoomAssign')
return this.$tr('等待预订中心分配房间')
}
return ''
},
@@ -466,9 +360,6 @@ export default {
this.loadReservationFlow()
},
methods: {
t(key, params) {
return this.$tm('pkgService.application_detail.application_detail', key, params)
},
isPaidApplication(item = {}) {
return !item.isViewingApplication && Number(item.payStatus) === 10
},
@@ -530,7 +421,7 @@ export default {
return Number(detail.status) === 2
},
getReservationInspectionStatusText() {
return this.t('inspectionPendingReviewHint')
return this.$tr('inspectionPendingReviewHint')
},
isReservationContractAwaitingReview() {
if (this.getReservationCurrentStepValue() !== RESERVATION_FLOW_STEP.CONTRACT) {
@@ -663,13 +554,13 @@ export default {
return null
}
const localizedLabelMap = {
'看房时间': this.t('viewingTimeKey'),
'预计租住时长': this.t('leaseDurationKey'),
'租金预算': this.t('budgetKey'),
'工作/通勤地点': this.t('commuteKey'),
'意向区域': this.t('preferredAreaKey'),
'意向房型': this.t('preferredRoomTypeKey'),
'支付方式': this.t('paymentMethodKey')
'看房时间': this.$tr('看房时间'),
'预计租住时长': this.$tr('预计租住时长'),
'租金预算': this.$tr('租金预算'),
'工作/通勤地点': this.$tr('工作/通勤地点'),
'意向区域': this.$tr('意向区域'),
'意向房型': this.$tr('意向房型'),
'支付方式': this.$tr('支付方式')
}
const rawValue = line
.replace(`${matched.zh}:`, '')
@@ -704,7 +595,7 @@ export default {
data: String(this.detail.detailCode),
success: () => {
uni.showToast({
title: this.t('codeCopied'),
title: this.$tr('申请编号已复制'),
icon: 'none'
})
}
@@ -713,8 +604,8 @@ export default {
confirmCancelOrder() {
return new Promise((resolve) => {
uni.showModal({
title: this.t('cancelOrderTitle'),
content: this.t('cancelOrderContent'),
title: this.$tr('取消订单'),
content: this.$tr('取消后将关闭当前未支付的定金订单,是否继续?'),
confirmColor: '#ef4444',
success: (res) => resolve(!!(res && res.confirm)),
fail: () => resolve(false)
@@ -733,7 +624,7 @@ export default {
return {
...item,
status: 99,
statusDesc: this.t('statusFinished')
statusDesc: this.$tr('已结束')
}
})
saveMyApplicationList(nextList)
@@ -757,18 +648,18 @@ export default {
this.detail = {
...this.detail,
status: 99,
statusDesc: this.t('statusFinished')
statusDesc: this.$tr('已结束')
}
this.syncCanceledDetailToCache()
uni.showToast({
title: this.t('cancelOrderSuccess'),
title: this.$tr('订单已取消'),
icon: 'success'
})
})
})
.catch((error) => {
uni.showToast({
title: (error && (error.message || error.msg)) || this.t('cancelOrderFailed'),
title: (error && (error.message || error.msg)) || this.$tr('取消订单失败'),
icon: 'none'
})
})
@@ -782,7 +673,7 @@ export default {
goToBillPage() {
if (!this.detail.detailCode) {
uni.showToast({
title: this.t('missingBillCode'),
title: this.$tr('申请单编号缺失'),
icon: 'none'
})
return
@@ -810,7 +701,7 @@ export default {
const contractNo = String(this.detail && this.detail.reservationCurrentNode && this.detail.reservationCurrentNode.refNo || '').trim()
if (!contractNo) {
uni.showToast({
title: this.t('missingContractNo'),
title: this.$tr('合同编号缺失'),
icon: 'none'
})
return

View File

@@ -1,24 +1,24 @@
<template>
<view class="bill-panel">
<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 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="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>
<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>
</view>
</view>
<view v-if="loading || resolvingReference" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('loadingTitle') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('loadingDesc') }}</view>
<view class="empty-title">{{ $tr('账单加载中') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ $tr('正在同步您的待支付账单,请稍候。') }}</view>
</view>
<view v-else-if="billList.length">
@@ -39,64 +39,64 @@
<view class="card-body tn-margin-top">
<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>
</view>
<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>
</view>
<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>
</view>
<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>
</view>
<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>
</view>
<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>
</view>
</view>
<view v-if="getBillRemark(item) || getBillResult(item)" class="remark-panel tn-margin-top">
<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>
</view>
<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>
</view>
</view>
<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">
<text class="info-label">{{ t('pendingPaymentOrderNoLabel') }}</text>
<text class="info-label">{{ $tr('支付单号') }}</text>
<text class="info-value">{{ getPendingPaymentOrderNo(item) }}</text>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('pendingPaymentAmountLabel') }}</text>
<text class="info-label">{{ $tr('订单金额') }}</text>
<text class="info-value info-value--highlight">¥{{ formatMoney(getPendingPaymentAmount(item)) }}</text>
</view>
<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) }">
{{ getPendingPaymentExpireText(item) }}
</text>
</view>
<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>
</view>
<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>
</view>
</view>
@@ -124,10 +124,10 @@
</view>
<view v-else class="empty-panel tn-padding-lg">
<view class="empty-title">{{ currentReferenceTypeNo ? t('emptyPendingTitle') : t('emptyApplicationTitle') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ currentReferenceTypeNo ? t('emptyPendingDesc') : t('emptyApplicationDesc') }}</view>
<view class="empty-title">{{ currentReferenceTypeNo ? $tr('暂无待支付账单') : $tr('暂无可查询申请单') }}</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">
<text class="tn-text-bold">{{ t('goToMyApplication') }}</text>
<text class="tn-text-bold">{{ $tr('前往我的申请') }}</text>
</view>
</view>
@@ -197,7 +197,7 @@
<view v-if="isPaymentWaitingStage" class="payment-progress-card">
<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>
</view>
<view class="payment-progress-track">
@@ -208,11 +208,11 @@
<view class="payment-summary-grid">
<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>
<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>
</view>
@@ -380,212 +380,6 @@ export default {
lines: [],
confirmText: '',
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() {
if (this.currentReferenceTypeNo) {
return this.t('heroDescResolved')
return this.$tr('当前页面已按申请单编号查询其关联的待支付账单记录。')
}
return this.t('heroDescAutoResolve')
return this.$tr('当前将优先读取最近申请单的编号来查询关联账单。')
},
showPaymentFeedbackPanel() {
return Boolean(this.paymentStage)
@@ -627,18 +421,18 @@ export default {
},
paymentStageLabel() {
if (this.paymentStage === 'launching') {
return this.t('paymentStageLaunching')
return this.$tr('正在唤起微信收银台')
}
if (this.paymentStage === 'polling') {
return this.t('paymentStagePolling')
return this.$tr('正在同步支付结果')
}
if (this.paymentStage === 'success') {
return this.t('paymentStageSuccess')
return this.$tr('支付结果已确认')
}
if (this.paymentStage === 'failed') {
return this.t('paymentStageFailed')
return this.$tr('支付未完成')
}
return this.t('paymentStageDefault')
return this.$tr('支付处理中')
},
paymentBadgeIconClass() {
if (this.isPaymentSuccessStage) {
@@ -660,31 +454,31 @@ export default {
},
paymentWaitingTitle() {
if (this.paymentStage === 'launching') {
return this.t('paymentTitleLaunching')
return this.$tr('请完成微信支付')
}
if (this.paymentStage === 'polling') {
return this.t('paymentTitlePolling')
return this.$tr('正在确认是否支付成功')
}
if (this.paymentStage === 'success') {
return this.t('paymentTitleSuccess')
return this.$tr('支付成功')
}
if (this.paymentStage === 'failed') {
return this.t('paymentTitleFailed')
return this.$tr('支付结果确认失败')
}
return this.t('paymentTitleDefault')
return this.$tr('支付处理中')
},
paymentWaitingDesc() {
if (this.paymentStage === 'launching') {
return this.t('paymentDescLaunching')
return this.$tr('系统正在调起微信支付,请在微信收银台中完成付款后返回。')
}
if (this.paymentStage === 'polling') {
return this.t('paymentDescPolling')
return this.$tr('支付完成后,系统会自动向后台查询支付状态,无需手动刷新。')
}
if (this.paymentStage === 'success') {
return this.t('paymentDescSuccess')
return this.$tr('本次支付已同步完成,账单状态已更新。')
}
if (this.paymentStage === 'failed') {
return this.t('paymentDescFailed')
return this.$tr('系统暂时未能确认支付成功,您可以查看提示后稍后重试。')
}
return ''
},
@@ -704,7 +498,7 @@ export default {
},
paymentProgressText() {
if (this.paymentStage !== 'polling') {
return this.t('paymentProgressWaiting')
return this.$tr('等待支付完成')
}
const current = this.paymentPollingAttempt > 0 ? this.paymentPollingAttempt : 1
return `${current}/${PAYMENT_QUERY_MAX_ATTEMPTS}`
@@ -714,45 +508,45 @@ export default {
return this.paymentStatusHint
}
if (this.paymentStage === 'launching') {
return this.t('paymentHintLaunching')
return this.$tr('已为您准备好支付信息,等待微信支付完成。')
}
return this.t('paymentHintPolling')
return this.$tr('正在与后台核对支付状态,请稍候。')
},
paymentTipsTitle() {
if (this.isPaymentFailedStage) {
return this.t('paymentTipsTitleFailed')
return this.$tr('处理建议')
}
if (this.isPaymentSuccessStage) {
return this.t('paymentTipsTitleSuccess')
return this.$tr('完成说明')
}
return this.t('paymentTipsTitleDefault')
return this.$tr('温馨提示')
},
paymentTips() {
if (this.isPaymentFailedStage) {
return [
this.t('paymentTipsFailed1'),
this.t('paymentTipsFailed2'),
this.t('paymentTipsFailed3')
this.$tr('若您已完成付款,请稍后刷新账单,避免重复支付。'),
this.$tr('若微信侧已扣款但页面未更新,请联系管家或客服协助核实。'),
this.$tr('若未扣款,可返回账单列表后重新发起支付。')
]
}
if (this.isPaymentSuccessStage) {
return [
this.t('paymentTipsSuccess1'),
this.t('paymentTipsSuccess2'),
this.t('paymentTipsSuccess3')
this.$tr('若当前申请单还有其他待付账单,可继续在当前页完成支付。'),
this.$tr('若当前账单已全部支付完成,系统会自动返回首页。'),
this.$tr('支付记录同步可能略有延迟,刷新后即可看到最新状态。')
]
}
return [
this.t('paymentTipsDefault1'),
this.t('paymentTipsDefault2'),
this.t('paymentTipsDefault3')
this.$tr('支付成功后,系统可能需要几秒钟同步账单状态。'),
this.$tr('请不要重复点击支付,也不要提前关闭当前页面。'),
this.$tr('如果超时未完成,系统会自动再校验一次账单状态。')
]
},
showPaymentActionButton() {
return this.isPaymentFailedStage
},
paymentActionText() {
return this.isPaymentFailedStage ? this.t('paymentActionAcknowledge') : ''
return this.isPaymentFailedStage ? this.$tr('我知道了') : ''
}
},
watch: {
@@ -782,22 +576,6 @@ export default {
this.clearCountdownTimer()
},
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 = {}) {
return item.no || item.billNo || ''
},
@@ -812,7 +590,7 @@ export default {
},
getBillPaymentStatusText(item = {}) {
const translatedStatus = this.getPaymentStatusText(this.getBillPaymentStatus(item))
return translatedStatus || item.paymentStatusDesc || this.t('paymentStatusPending')
return translatedStatus || item.paymentStatusDesc || this.$tr('处理中')
},
getBillRefundStatus(item = {}) {
return item.refundStatus
@@ -918,7 +696,7 @@ export default {
return ''
}
if (expireTimestamp <= this.countdownNow) {
return this.t('pendingPaymentExpired')
return this.$tr('已失效')
}
return this.formatPendingPaymentCountdown(this.getPendingPaymentRemainingSeconds(item))
},
@@ -926,20 +704,20 @@ export default {
const billNo = this.getBillNo(item)
if (this.paymentPollingBillNo && this.paymentPollingBillNo === billNo) {
if (!this.paymentPollingAttempt) {
return this.t('buttonConfirming')
return this.$tr('确认结果中...')
}
return this.t('buttonPolling', {
return this.$tr('查询结果中({current}/{total})', {
current: this.paymentPollingAttempt,
total: PAYMENT_QUERY_MAX_ATTEMPTS
})
}
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)) {
return this.t('continuePayButton')
return this.$tr('继续支付')
}
return this.t('wechatPayButton')
return this.$tr('微信支付')
},
canCancelOrder(item = {}) {
if (!this.currentReferenceTypeNo) {
@@ -951,8 +729,8 @@ export default {
},
getCancelButtonText(item = {}) {
return this.cancelingBillNo === this.getBillNo(item)
? this.t('cancelOrderLoading')
: this.t('cancelOrderButton')
? this.$tr('取消中...')
: this.$tr('取消订单')
},
resolveReferenceTypeNo(item = {}) {
return item.code || item.detailCode || ''
@@ -1024,7 +802,7 @@ export default {
return text.length > 16 ? text.slice(0, 16) : text
},
getPaymentStatusText(status) {
return getAccountBillPaymentStatusLabel(status, this.t('paymentStatusPending'))
return getAccountBillPaymentStatusLabel(status, this.$tr('处理中'))
},
resolvePaymentStatusType(status) {
if (Number(status) === ACCOUNT_BILL_PAYMENT_STATUS.PAID) {
@@ -1040,7 +818,7 @@ export default {
},
getReferenceTypeText(item = {}) {
const translatedType = getAccountReferenceTypeLabel(item.referenceType, '')
return translatedType || item.referenceTypeDesc || this.t('billRecordFallback')
return translatedType || item.referenceTypeDesc || this.$tr('账单记录')
},
parsePaymentInfo(paymentInfo) {
if (!paymentInfo) {
@@ -1073,7 +851,7 @@ export default {
requestWechatPayment(paymentInfo) {
const payOptions = this.normalizeWechatPayOptions(paymentInfo)
if (!payOptions.timeStamp || !payOptions.nonceStr || !payOptions.package || !payOptions.paySign) {
return Promise.reject(new Error(this.t('paymentParamIncomplete')))
return Promise.reject(new Error(this.$tr('微信支付参数不完整')))
}
return new Promise((resolve, reject) => {
@@ -1143,13 +921,13 @@ export default {
success: (res) => {
const code = res && res.code ? res.code : ''
if (!code) {
reject(new Error(this.t('wechatCodeFailed')))
reject(new Error(this.$tr('获取微信登录凭证失败')))
return
}
resolve(code)
},
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),
payMethod: 'Wechat',
amount: Number(this.getBillRemainingAmount(item) || 0),
description: this.getReferenceTypeText(item) || this.t('billPaymentDesc')
description: this.getReferenceTypeText(item) || this.$tr('账单支付')
}
if (wechatCode) {
payload.ExtraInfo = wechatCode
@@ -1168,10 +946,6 @@ export default {
return payload
},
resolveNoticeText(key, zhText, enText) {
const translatedText = this.t(key)
if (translatedText && translatedText !== key) {
return translatedText
}
return this.language === 'en' ? enText : zhText
},
resetPaymentNoticeContext() {
@@ -1237,8 +1011,8 @@ export default {
confirmCancelOrder() {
return new Promise((resolve) => {
uni.showModal({
title: this.t('cancelOrderTitle'),
content: this.t('cancelOrderContent'),
title: this.$tr('取消订单'),
content: this.$tr('取消后将关闭当前未支付订单,是否继续?'),
confirmColor: '#ef4444',
success: (res) => resolve(!!(res && res.confirm)),
fail: () => resolve(false)
@@ -1264,7 +1038,7 @@ export default {
this.paymentSession = {
billNo: this.getBillNo(item),
amount: Number(this.getBillRemainingAmount(item) || 0),
title: this.getReferenceTypeText(item) || this.t('billPaymentDesc')
title: this.getReferenceTypeText(item) || this.$tr('账单支付')
}
},
resetPaymentWaitingState() {
@@ -1277,7 +1051,7 @@ export default {
},
openPaymentFailedState(message = '') {
this.paymentStage = 'failed'
this.paymentStatusHint = message || this.t('paymentFailedHint')
this.paymentStatusHint = message || this.$tr('暂未查询到支付成功结果,请稍后刷新账单确认。')
this.payingBillNo = ''
this.paymentPollingBillNo = this.paymentWaitingBillNo
},
@@ -1312,20 +1086,20 @@ export default {
if (payStatus === PAYMENT_ORDER_STATUS.SUCCESS) {
return {
status: PAYMENT_QUERY_STATUS.SUCCESS,
message: statusDesc || this.t('paymentResultSuccess'),
message: statusDesc || this.$tr('后台确认支付成功'),
data: payload
}
}
if (payStatus === PAYMENT_ORDER_STATUS.FAILED) {
return {
status: PAYMENT_QUERY_STATUS.FAILED,
message: statusDesc || this.t('paymentResultFailed'),
message: statusDesc || this.$tr('后台返回支付失败'),
data: payload
}
}
return {
status: PAYMENT_QUERY_STATUS.PENDING,
message: statusDesc || this.t('paymentResultPending'),
message: statusDesc || this.$tr('后台仍在处理中'),
data: payload
}
})
@@ -1337,7 +1111,7 @@ export default {
this.paymentStage = 'polling'
this.paymentPollingBillNo = queryContext.billNo
this.paymentPollingAttempt = 0
this.paymentStatusHint = this.t('paymentHintPaidWaiting')
this.paymentStatusHint = this.$tr('已支付成功,正在等待后台确认账单状态。')
const executePolling = async () => {
for (let attempt = 1; attempt <= PAYMENT_QUERY_MAX_ATTEMPTS; attempt += 1) {
@@ -1346,15 +1120,15 @@ export default {
}
this.paymentPollingAttempt = attempt
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) {
return lastResult
}
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()
@@ -1363,8 +1137,8 @@ export default {
const hasPendingBills = this.billList.length > 0
this.paymentStage = 'success'
this.paymentStatusHint = hasPendingBills
? this.t('paymentSuccessPendingBills')
: this.t('paymentSuccessAllDone')
? this.$tr('支付结果已确认,当前申请单仍有其他待支付账单。')
: this.$tr('支付结果已确认,当前申请单账单已全部完成。')
this.payingBillNo = ''
this.paymentPollingBillNo = this.paymentWaitingBillNo
return this.wait(1200)
@@ -1413,7 +1187,7 @@ export default {
: null
if (!billNo || !this.currentReferenceTypeNo) {
uni.showToast({
title: this.t('billInfoIncomplete'),
title: this.$tr('账单信息不完整'),
icon: 'none'
})
return
@@ -1430,7 +1204,7 @@ export default {
}
this.payingBillNo = billNo
this.paymentStage = 'launching'
this.paymentStatusHint = this.t('paymentHintLaunchingToast')
this.paymentStatusHint = this.$tr('正在唤起微信支付,请在微信内完成付款。')
this.createPaymentSession(item)
})
.then(() => {
@@ -1464,7 +1238,7 @@ export default {
if (/cancel/i.test(errMsg)) {
this.resetPaymentWaitingState()
uni.showToast({
title: this.t('paymentCanceled'),
title: this.$tr('已取消支付'),
icon: 'none'
})
return
@@ -1474,7 +1248,7 @@ export default {
if (hasConfirmedSuccess) {
return
}
this.openPaymentFailedState(errMsg || this.t('paymentResultFailedRetry'))
this.openPaymentFailedState(errMsg || this.$tr('支付结果确认失败,请稍后刷新账单确认'))
})
})
.finally(() => {
@@ -1500,7 +1274,7 @@ export default {
return userDetailCancel(this.buildCancelOrderPayload(item), this.language)
.then(() => {
uni.showToast({
title: this.t('cancelOrderSuccess'),
title: this.$tr('订单已取消'),
icon: 'success'
})
return this.refreshBillList()
@@ -1508,7 +1282,7 @@ export default {
})
.catch((error) => {
uni.showToast({
title: (error && (error.message || error.msg)) || this.t('cancelOrderFailed'),
title: (error && (error.message || error.msg)) || this.$tr('取消订单失败'),
icon: 'none'
})
})
@@ -1554,7 +1328,7 @@ export default {
this.billList = []
this.pendingPaymentOrderMap = {}
uni.showToast({
title: (error && (error.message || error.msg)) || this.t('billLoadFailed'),
title: (error && (error.message || error.msg)) || this.$tr('账单加载失败'),
icon: 'none'
})
return []

View File

@@ -1,15 +1,15 @@
<template>
<view class="contract-detail-page tn-safe-area-inset-bottom">
<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>
<view class="page-main" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
<view class="overview-panel">
<view class="overview-header">
<view>
<view class="overview-title">{{ t('overviewTitle') }}</view>
<view class="overview-subtitle">{{ t('contractNoLabel') }}{{ contractNo || t('notAvailable') }}</view>
<view class="overview-title">{{ $tr('合同影像') }}</view>
<view class="overview-subtitle">{{ $tr('合同编号:') }}{{ contractNo || $tr('未获取') }}</view>
</view>
<view class="overview-badge">
<text class="tn-icon-read"></text>
@@ -19,26 +19,26 @@
<view class="overview-tips">
<view class="tip-chip">
<text class="tn-icon-safe-fill"></text>
<text>{{ t('signedChip') }}</text>
<text>{{ $tr('已签合同') }}</text>
</view>
<view class="tip-chip">
<text class="tn-icon-charts-ring"></text>
<text>{{ t('zoomSupportedChip') }}</text>
<text>{{ $tr('支持缩放') }}</text>
</view>
<view class="tip-chip" v-if="imageUrl">
<text class="tn-icon-image-text-fill"></text>
<text>{{ t('imageModeChip') }}</text>
<text>{{ $tr('A4 影像模式') }}</text>
</view>
<view class="tip-chip" v-else-if="contractContent">
<text class="tn-icon-text-new"></text>
<text>{{ t('textModeChip') }}</text>
<text>{{ $tr('正文预览模式') }}</text>
</view>
</view>
</view>
<view v-if="loading" class="empty-panel">
<view class="empty-title">{{ t('loadingTitle') }}</view>
<view class="empty-desc">{{ t('loadingDesc') }}</view>
<view class="empty-title">{{ $tr('合同加载中') }}</view>
<view class="empty-desc">{{ $tr('正在获取合同影像与正文,请稍候。') }}</view>
</view>
<view v-else-if="imageUrl || contractContent" class="preview-panel">
@@ -72,8 +72,8 @@
<view class="empty-icon">
<text class="tn-icon-folder"></text>
</view>
<view class="empty-title">{{ t('emptyTitle') }}</view>
<view class="empty-desc">{{ t('emptyDesc') }}</view>
<view class="empty-title">{{ $tr('暂无合同内容') }}</view>
<view class="empty-desc">{{ $tr('页面未带入影像地址,且通过合同编号也未获取到可预览的合同内容。') }}</view>
</view>
</view>
</view>
@@ -100,36 +100,6 @@ function normalizeRichTextContent(content = '') {
}
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() {
return {
contractNo: '',
@@ -147,9 +117,6 @@ export default {
this.fetchContractDetail()
},
methods: {
t(key, params) {
return this.$tm('pkgService.contract_detail.contract_detail', key, params)
},
fetchContractDetail() {
if (!this.contractNo || (this.imageUrl && this.contractContent)) {
return Promise.resolve()

View File

@@ -1,15 +1,15 @@
<template>
<view class="contract-sign-page tn-safe-area-inset-bottom">
<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>
<view class="page-main" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
<view class="overview-panel">
<view class="overview-header">
<view>
<view class="overview-title">{{ t('overviewTitle') }}</view>
<view class="overview-subtitle">{{ t('contractNoLabel') }}{{ contractNo || t('notAvailable') }}</view>
<view class="overview-title">{{ $tr('签约前确认') }}</view>
<view class="overview-subtitle">{{ $tr('合同编号:') }}{{ contractNo || $tr('未获取') }}</view>
</view>
<view class="overview-badge">
<text class="tn-icon-edit-write-fill"></text>
@@ -18,13 +18,13 @@
</view>
<view class="overview-meta">
<text class="tn-icon-safe-fill"></text>
<text>{{ t('overviewMeta') }}</text>
<text>{{ $tr('阅读合同正文后完成签名提交,签约结果会同步到我的合同。') }}</text>
</view>
</view>
<view v-if="loading" class="empty-panel">
<view class="empty-title">{{ t('loadingTitle') }}</view>
<view class="empty-desc">{{ t('loadingDesc') }}</view>
<view class="empty-title">{{ $tr('合同加载中') }}</view>
<view class="empty-desc">{{ $tr('正在获取合同正文,请稍候。') }}</view>
</view>
<view v-else-if="contractContent" class="preview-panel">
@@ -38,7 +38,7 @@
<view class="preview-tip" @click="openFullscreenPreview">
<text class="tn-icon-fullscreen"></text>
<text>{{ t('fullscreenPreview') }}</text>
<text>{{ $tr('点击全屏查看') }}</text>
</view>
<view class="zoom-controls" @click.stop>
@@ -56,17 +56,17 @@
<view class="empty-icon">
<text class="tn-icon-folder"></text>
</view>
<view class="empty-title">{{ t('emptyTitle') }}</view>
<view class="empty-desc">{{ t('emptyDesc') }}</view>
<view class="empty-title">{{ $tr('暂无合同正文') }}</view>
<view class="empty-desc">{{ $tr('当前合同暂未返回可签约正文,请稍后再试。') }}</view>
</view>
<view v-if="contractContent" class="footer-bar">
<view class="footer-tip">
<text class="tn-icon-safe-fill"></text>
<text>{{ t('footerTip') }}</text>
<text>{{ $tr('签名前请确认您已完整阅读合同内容') }}</text>
</view>
<view class="action-btn" @click="openSignBoard">
{{ isSubmitting ? t('submitting') : t('signAction') }}
{{ isSubmitting ? $tr('提交中...') : $tr('我已阅读并同意,去签名') }}
</view>
</view>
</view>
@@ -83,9 +83,9 @@
<view class="fullscreen-header">
<view class="fullscreen-back" @click="closeFullscreenPreview">
<text class="tn-icon-left"></text>
<text>{{ t('back') }}</text>
<text>{{ $tr('返回') }}</text>
</view>
<view class="fullscreen-title">{{ t('fullscreenTitle') }}</view>
<view class="fullscreen-title">{{ $tr('合同全文') }}</view>
<view class="fullscreen-placeholder"></view>
</view>
<scroll-view scroll-y scroll-x class="fullscreen-scroll" :enable-flex="true">
@@ -138,54 +138,6 @@ function resolveUploadedFileUrl(uploadResult) {
}
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: {
ContractSignDialog
},
@@ -209,9 +161,6 @@ export default {
this.fetchContractContent()
},
methods: {
t(key, params) {
return this.$tm('pkgService.contract_sign.contract_sign', key, params)
},
fetchContractContent() {
if (!this.contractNo) {
return Promise.resolve()
@@ -238,14 +187,14 @@ export default {
}
if (!this.signerName) {
uni.showToast({
title: this.t('signerMissing'),
title: this.$tr('未获取到签名人姓名'),
icon: 'none'
})
return
}
if (!this.contractNo) {
uni.showToast({
title: this.t('contractNoMissing'),
title: this.$tr('缺少合同编号'),
icon: 'none'
})
return
@@ -307,7 +256,7 @@ export default {
this.isSubmitting = true
uni.showLoading({
title: this.t('signingLoading'),
title: this.$tr('签约提交中...'),
mask: true
})
@@ -322,7 +271,7 @@ export default {
.then((uploadResult) => {
const signImageUrl = resolveUploadedFileUrl(uploadResult)
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({
@@ -349,7 +298,7 @@ export default {
.then(() => {
uni.hideLoading()
uni.showToast({
title: this.t('signSuccess'),
title: this.$tr('签约成功'),
icon: 'success'
})
setTimeout(() => {
@@ -362,7 +311,7 @@ export default {
console.error('contract sign failed', error)
uni.hideLoading()
uni.showToast({
title: (error && (error.message || error.msg)) || this.t('signFailed'),
title: (error && (error.message || error.msg)) || this.$tr('签约失败'),
icon: 'none'
})
})

View File

@@ -30,7 +30,7 @@
<view class="sheet-handle"></view>
<view class="sheet-header">
<text class="sheet-title">{{ t('controlPanel') }}</text>
<text class="sheet-title">{{ $tr('控制面板') }}</text>
<text class="sheet-subtitle">{{ functionCountText }}</text>
</view>
@@ -46,7 +46,7 @@
<text class="tn-icon-set panel-icon"></text>
<text>{{ getFunctionLabel(func) }}</text>
</view>
<view class="status-tag" v-if="!isFunctionSupported(func)">{{ t('pendingTag') }}</view>
<view class="status-tag" v-if="!isFunctionSupported(func)">{{ $tr('待完善') }}</view>
</view>
<!-- Switch Theme -->
@@ -60,7 +60,7 @@
<block v-else-if="isSlideFunction(func)">
<view class="slider-container">
<view class="slider-header">
<text class="slider-label">{{ t('sliderLabel') }}</text>
<text class="slider-label">{{ $tr('亮度 / 数值') }}</text>
<text class="slider-value">{{ controlValue }}%</text>
</view>
<slider
@@ -76,7 +76,7 @@
@change="handleControlValueChange"
/>
<view class="slider-apply-btn" :class="{ 'is-disabled': isSubmitting }" @click="submitSlideValue(index)">
<text>{{ t('applySetting') }}</text>
<text>{{ $tr('应用设置') }}</text>
</view>
</view>
</block>
@@ -86,22 +86,22 @@
<view class="control-row curtain-row">
<view class="curtain-btn" :class="{ 'is-active': curtainAction === 'open', 'is-disabled': isSubmitting }" @click="operateCurtain(index, 'open')">
<text class="tn-icon-up"></text>
<text class="btn-label">{{ t('curtainOpen') }}</text>
<text class="btn-label">{{ $tr('打开') }}</text>
</view>
<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="btn-label">{{ t('curtainPause') }}</text>
<text class="btn-label">{{ $tr('暂停') }}</text>
</view>
<view class="curtain-btn" :class="{ 'is-active': curtainAction === 'close', 'is-disabled': isSubmitting }" @click="operateCurtain(index, 'close')">
<text class="tn-icon-down"></text>
<text class="btn-label">{{ t('curtainClose') }}</text>
<text class="btn-label">{{ $tr('关闭') }}</text>
</view>
</view>
</block>
<view v-else class="unsupported-box" @click="notifyUnsupportedFunction(index)">
<text class="tn-icon-warning"></text>
<text>{{ t('unsupportedBoxText') }}</text>
<text>{{ $tr('当前功能暂未开放使用,点击反馈') }}</text>
</view>
</view>
@@ -173,68 +173,6 @@ function resolveRefreshIntervalMs(value, fallback = 1000) {
}
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() {
return {
brandId: DEFAULT_BRAND_ID,
@@ -257,10 +195,10 @@ export default {
return this.$getLanguage()
},
deviceName() {
return this.device.deviceName || this.t('deviceDetail')
return this.device.deviceName || this.$tr('设备详情')
},
displayRoomName() {
return this.roomName || this.roomCode || this.t('currentRoom')
return this.roomName || this.roomCode || this.$tr('当前房间')
},
controlType() {
return inferDeviceControlType(this.device)
@@ -273,16 +211,16 @@ export default {
},
functionCountText() {
const count = this.deviceFunctions.length
return count ? this.t('functionCount', { count }) : this.controlTypeLabel
return count ? this.$tr('{count}项功能', { count }) : this.controlTypeLabel
},
statusText() {
return buildDeviceStateText(this.device)
},
syncHintText() {
if (this.isSubmitting) {
return this.t('commandSending')
return this.$tr('指令下发中...')
}
return this.t('syncHint')
return this.$tr('操作完成后会自动刷新设备状态')
},
deviceFunctions() {
return getDeviceFunctions(this.device)
@@ -306,7 +244,7 @@ export default {
const parsedDevice = safeParseDevice(options.device)
if (!parsedDevice) {
uni.showToast({
title: this.t('deviceMissing'),
title: this.$tr('设备信息缺失'),
icon: 'none'
})
return
@@ -327,9 +265,6 @@ export default {
this.stopDevicePolling()
},
methods: {
t(key, params) {
return this.$tm('pkgService.device_control.device_control', key, params)
},
goBack() {
uni.navigateBack()
},
@@ -613,7 +548,7 @@ export default {
? this.getDeviceFunctionByIndex(funcOrIndex)
: funcOrIndex
uni.showToast({
title: this.t('unsupportedFunction', { name: this.getFunctionLabel(func) }),
title: this.$tr('{name}操作功能未完善', { name: this.getFunctionLabel(func) }),
icon: 'none'
})
},
@@ -676,7 +611,7 @@ export default {
const fields = this.getFunctionFields(func, index)
if (!fields.length) {
uni.showToast({
title: this.t('unknownSchema'),
title: this.$tr('未识别到参数模板'),
icon: 'none'
})
return
@@ -688,11 +623,11 @@ export default {
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) {
uni.showToast({ title: this.t('deviceIdMissing'), icon: 'none' })
uni.showToast({ title: this.$tr('设备ID缺失'), icon: 'none' })
return Promise.resolve()
}
@@ -703,7 +638,7 @@ export default {
const functionCode = String((func && func.functionCode) || '').trim()
if (!functionCode) {
uni.showToast({ title: this.t('functionCodeMissing'), icon: 'none' })
uni.showToast({ title: this.$tr('功能编码缺失'), icon: 'none' })
return Promise.resolve()
}
@@ -720,7 +655,7 @@ export default {
.catch((error) => {
console.error('execute device command failed', error)
uni.showToast({
title: (error && (error.message || error.msg)) || this.t('commandFailed'),
title: (error && (error.message || error.msg)) || this.$tr('指令下发失败'),
icon: 'none'
})
})
@@ -734,14 +669,14 @@ export default {
if (this.isSubmitting) {
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) {
const func = this.getDeviceFunctionByIndex(index)
if (this.isSubmitting) {
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) {
const func = this.getDeviceFunctionByIndex(index)
@@ -751,7 +686,7 @@ export default {
this.executeCommand(
func,
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) {
@@ -765,7 +700,7 @@ export default {
this.executeCommand(
func,
this.buildSlideCommandPayload(index, this.controlValue),
this.t('sliderUpdated')
this.$tr('滑块值已更新')
)
}
}

View File

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

View File

@@ -1,7 +1,7 @@
<template>
<view class="message-detail-page tn-safe-area-inset-bottom">
<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>
<view class="page-main tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }">
@@ -12,7 +12,7 @@
<text :class="categoryIcon"></text>
</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-chip">
<text class="tn-icon-time tn-margin-right-xs"></text>
@@ -26,7 +26,7 @@
</view>
<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-tag">{{ categoryText }}</view>
<view class="article-time">{{ detail.time || '--' }}</view>
@@ -34,42 +34,42 @@
</view>
<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>
</view>
<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>
<view class="meta-panel tn-margin-bottom-lg">
<view class="meta-row">
<text class="meta-label">{{ t('sourceLabel') }}</text>
<text class="meta-label">{{ $tr('消息来源') }}</text>
<text class="meta-value">{{ sourceText }}</text>
</view>
<view class="meta-row">
<text class="meta-label">{{ t('categoryLabel') }}</text>
<text class="meta-label">{{ $tr('消息分类') }}</text>
<text class="meta-value">{{ categoryText }}</text>
</view>
<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">
<text class="meta-value">{{ detail.code }}</text>
<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 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>
</view>
</view>
<view class="action-row">
<view class="ghost-btn" @click="goBackList">
<text class="tn-text-bold">{{ t('backList') }}</text>
<text class="tn-text-bold">{{ $tr('返回消息页') }}</text>
</view>
<view v-if="detail.path" class="action-btn" @click="handleGoBusiness">
<text class="tn-text-bold">{{ actionButtonText }}</text>
@@ -111,58 +111,6 @@ function normalizeContentToRichText(value = '', emptyText = '') {
}
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() {
return {
lookupId: '',
@@ -205,14 +153,14 @@ export default {
},
categoryText() {
const map = {
system: this.t('categorySystem'),
payment: this.t('categoryPayment'),
butler: this.t('categoryButler')
system: this.$tr('系统通知'),
payment: this.$tr('缴费账单'),
butler: this.$tr('管家消息')
}
return map[this.detail.category] || this.t('messageFallbackTitle')
return map[this.detail.category] || this.$tr('消息通知')
},
contentNodes() {
return normalizeContentToRichText(this.localizedContent || this.localizedDesc || '', this.t('emptyContent'))
return normalizeContentToRichText(this.localizedContent || this.localizedDesc || '', this.$tr('暂无详细内容'))
},
categoryIcon() {
const map = {
@@ -240,14 +188,14 @@ export default {
},
actionButtonText() {
const map = {
system: this.t('actionSystem'),
payment: this.t('actionPayment'),
butler: this.t('actionButler')
system: this.$tr('前往相关页面'),
payment: this.$tr('去处理账单'),
butler: this.$tr('去查看服务')
}
return map[this.detail.category] || this.t('actionDefault')
return map[this.detail.category] || this.$tr('继续前往业务页')
},
sourceText() {
return this.detail.source === 'remote' ? this.t('remoteSource') : this.t('localSource')
return this.detail.source === 'remote' ? this.$tr('系统通知接口') : this.$tr('本地业务消息')
}
},
onLoad(options = {}) {
@@ -264,9 +212,6 @@ export default {
clearMessageDetailPayload(this.language)
},
methods: {
t(key, params) {
return this.$tm('pkgService.message_detail.message_detail', key, params)
},
restoreDetail() {
const payload = getMessageDetailPayload(this.language)
const fallbackPayload = findMessageDetailPayload(this.lookupId, this.lookupCode, this.language)
@@ -284,17 +229,17 @@ export default {
...this.detail,
id: this.lookupId || this.lookupCode || '',
code: this.lookupCode || '',
titleZh: this.t('messageFallbackTitle'),
titleEn: this.t('messageFallbackTitle'),
descZh: this.t('expiredDesc'),
descEn: this.t('expiredDesc'),
contentZh: this.t('expiredDesc'),
contentEn: this.t('expiredDesc'),
title: this.t('messageFallbackTitle'),
desc: this.t('expiredDesc'),
content: this.t('expiredDesc'),
titleZh: this.$tr('消息通知'),
titleEn: this.$tr('消息通知'),
descZh: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
descEn: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
contentZh: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
contentEn: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
title: this.$tr('消息通知'),
desc: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
content: this.$tr('当前消息详情已失效,请返回消息中心重新打开。'),
source: 'remote',
statusHint: this.t('expiredHint')
statusHint: this.$tr('消息缓存已失效')
}
},
goBackList() {
@@ -308,7 +253,7 @@ export default {
data: String(this.detail.code),
success: () => {
uni.showToast({
title: this.t('codeCopied'),
title: this.$tr('通知编号已复制'),
icon: 'success'
})
}

View File

@@ -1,7 +1,7 @@
<template>
<view class="my-application-page tn-safe-area-inset-bottom">
<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>
<view class="page-main tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }">
@@ -20,7 +20,7 @@
</scroll-view>
<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
class="refresh-btn"
:class="{ 'refresh-btn--loading': loading }"
@@ -30,7 +30,7 @@
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>
{{ loading ? t('refreshing') : t('refresh') }}
{{ loading ? $tr('刷新中...') : $tr('刷新状态') }}
</text>
</view>
</view>
@@ -54,23 +54,23 @@
<view class="card-body tn-margin-top">
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('apartmentLabel') }}</text>
<text class="info-label">{{ $tr('所属公寓') }}</text>
<text class="info-value">{{ item.intendedAreaName || '--' }}</text>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('occupantLabel') }}</text>
<text class="info-label">{{ $tr('入住人') }}</text>
<text class="info-value">{{ item.userName || item.phone || '--' }}</text>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ item.isViewingApplication ? t('viewingTimeLabel') : t('leaseTermLabel') }}</text>
<text class="info-label">{{ item.isViewingApplication ? $tr('预约时间') : $tr('预计租期') }}</text>
<text class="info-value">{{ formatDateRange(item.start, item.end) }}</text>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('submittedAtLabel') }}</text>
<text class="info-label">{{ $tr('提交时间') }}</text>
<text class="info-value">{{ formatDateTime(item.createTime) }}</text>
</view>
<view 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>
</view>
@@ -80,29 +80,29 @@
:data-reference-type-no="resolveReferenceTypeNo(item)"
@click="goToBillPage"
>
<text class="tn-text-bold">{{ t('viewBill') }}</text>
<text class="tn-text-bold">{{ $tr('查看关联账单') }}</text>
</view>
</view>
</view>
<view class="load-state">
<text v-if="loadingMore">{{ t('loadingMore') }}</text>
<text v-else-if="!hasMore">{{ t('noMore') }}</text>
<text v-else>{{ t('pullUpMore') }}</text>
<text v-if="loadingMore">{{ $tr('加载中...') }}</text>
<text v-else-if="!hasMore">{{ $tr('没有更多申请了') }}</text>
<text v-else>{{ $tr('上拉加载更多') }}</text>
</view>
</view>
<view v-else-if="!loading" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('emptyTitle') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('emptyDesc') }}</view>
<view class="empty-title">{{ $tr('暂无申请记录') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ $tr('您还没有提交任何申请,去选房后可发起入住申请或预约看房。') }}</view>
<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 v-if="loading && !list.length" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('loadingTitle') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('loadingDesc') }}</view>
<view class="empty-title">{{ $tr('申请列表加载中') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ $tr('正在同步您的入住申请记录,请稍候。') }}</view>
</view>
</view>
</view>
@@ -169,76 +169,6 @@ function normalizeApplicationList(list = []) {
}
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() {
return {
status: '',
@@ -278,26 +208,23 @@ export default {
}
},
methods: {
t(key, params) {
return this.$tm('pkgService.my_application.my_application', key, params)
},
syncStatusOptions() {
this.statusOptions = [
{ label: this.t('statusAll'), value: '' },
{ label: this.t('statusPending'), value: 0 },
{ label: this.t('statusApproved'), value: 10 },
{ label: this.t('statusRejected'), value: 20 },
{ label: this.t('statusFinished'), value: 99 }
{ label: this.$tr('全部'), value: '' },
{ label: this.$tr('待审核'), value: 0 },
{ label: this.$tr('已通过'), value: 10 },
{ label: this.$tr('已拒绝'), value: 20 },
{ label: this.$tr('已结束'), value: 99 }
]
},
getStatusText(status) {
const map = {
0: this.t('statusPending'),
10: this.t('statusApproved'),
20: this.t('statusRejected'),
99: this.t('statusFinished')
0: this.$tr('待审核'),
10: this.$tr('已通过'),
20: this.$tr('已拒绝'),
99: this.$tr('已结束')
}
return map[status] || this.t('statusProcessing')
return map[status] || this.$tr('处理中')
},
getDisplayStatusText(item = {}) {
if (this.language !== 'en' && item.statusDesc) {
@@ -338,10 +265,10 @@ export default {
const startText = this.formatDateTime(start)
const endText = this.formatDateTime(end)
if (String(start).includes('T') || String(end).includes('T')) {
return `${startText}${this.t('dateRangeJoiner')}${endText}`
return `${startText}${this.$tr(' 至 ')}${endText}`
}
}
return `${this.formatDate(start)}${this.t('dateRangeJoiner')}${this.formatDate(end)}`
return `${this.formatDate(start)}${this.$tr(' 至 ')}${this.formatDate(end)}`
},
async refreshStatus() {
if (this.loading || this.loadingMore) {
@@ -350,7 +277,7 @@ export default {
await this.reloadList()
if (this.list.length || this.total === 0) {
uni.showToast({
title: this.t('refreshSuccess'),
title: this.$tr('申请状态已刷新'),
icon: 'none'
})
}
@@ -407,7 +334,7 @@ export default {
this.pageIndex = Math.max(1, this.pageIndex - 1)
}
uni.showToast({
title: (error && (error.message || error.msg)) || this.t('loadError'),
title: (error && (error.message || error.msg)) || this.$tr('申请记录加载失败'),
icon: 'none'
})
})
@@ -431,9 +358,9 @@ export default {
},
getApplicationTitle(item = {}) {
if (item.isViewingApplication) {
return this.t('viewingApplicationTitle')
return this.$tr('预约看房申请')
}
return item.intendedRoomTypeName || this.t('stayApplicationTitle')
return item.intendedRoomTypeName || this.$tr('入住申请')
},
resolveReferenceTypeNo(item = {}) {
return item.detailCode || ''
@@ -495,7 +422,7 @@ export default {
: ''
if (!referenceTypeNo) {
uni.showToast({
title: this.t('missingBillCode'),
title: this.$tr('申请单编号缺失'),
icon: 'none'
})
return

View File

@@ -1,15 +1,15 @@
<template>
<view class="my-contract-page tn-safe-area-inset-bottom">
<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>
<view class="page-main" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
<view class="overview-panel">
<view class="overview-header">
<view>
<view class="overview-title">{{ t('overviewTitle') }}</view>
<view class="overview-subtitle">{{ t('overviewSubtitle') }}</view>
<view class="overview-title">{{ $tr('合同概览') }}</view>
<view class="overview-subtitle">{{ $tr('查看住宿合同、签约状态与有效期限') }}</view>
</view>
<view class="overview-badge">
<text class="tn-icon-edit-form"></text>
@@ -18,11 +18,11 @@
</view>
<view class="overview-metrics">
<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>
<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>
</view>
@@ -41,8 +41,8 @@
</view>
<view v-if="loading" class="empty-panel">
<view class="empty-title">{{ t('loadingTitle') }}</view>
<view class="empty-desc">{{ t('loadingDesc') }}</view>
<view class="empty-title">{{ $tr('合同记录加载中') }}</view>
<view class="empty-desc">{{ $tr('正在同步您的合同数据,请稍候。') }}</view>
</view>
<view v-else-if="displayList.length" class="list-wrapper">
@@ -58,7 +58,7 @@
<text class="tn-icon-edit-write-fill"></text>
</view>
<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>
</view>
@@ -70,26 +70,26 @@
<view class="meta-strip">
<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>
<view class="meta-chip">
<view class="meta-chip-label">{{ t('signStatusLabel') }}</view>
<view class="meta-chip-value">{{ item.signImageUrl ? t('signed') : t('unsigned') }}</view>
<view class="meta-chip-label">{{ $tr('签署状态') }}</view>
<view class="meta-chip-value">{{ item.signImageUrl ? $tr('已签名') : $tr('待签名') }}</view>
</view>
</view>
<view class="detail-panel">
<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>
</view>
<view class="info-row">
<text class="info-label">{{ t('recordTipLabel') }}</text>
<text class="info-label">{{ $tr('记录说明') }}</text>
<text class="info-value">{{ getStatusTip(item) }}</text>
</view>
<view v-if="item.signImageUrl" class="sign-row">
<text class="info-label">{{ t('mySignatureLabel') }}</text>
<text class="info-label">{{ $tr('我的签名') }}</text>
<view class="sign-preview">
<image :src="item.signImageUrl" mode="aspectFit"></image>
</view>
@@ -112,8 +112,8 @@
<view class="empty-icon">
<text class="tn-icon-folder"></text>
</view>
<view class="empty-title">{{ t('emptyTitle') }}</view>
<view class="empty-desc">{{ t('emptyDesc') }}</view>
<view class="empty-title">{{ $tr('暂无对应的合同记录') }}</view>
<view class="empty-desc">{{ $tr('切换筛选条件,或等待系统生成新的合同数据。') }}</view>
</view>
</view>
</view>
@@ -133,74 +133,6 @@ function normalizeContract(item = {}) {
}
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() {
return {
currentTab: 'all',
@@ -241,13 +173,10 @@ export default {
}
},
methods: {
t(key, params) {
return this.$tm('pkgService.my_contract.my_contract', key, params)
},
syncTabs() {
this.tabs = [
{ label: this.t('tabAll'), value: 'all' },
{ label: this.t('tabPending'), value: 'pending' }
{ label: this.$tr('全部'), value: 'all' },
{ label: this.$tr('待签约'), value: 'pending' }
]
},
getStoreUser() {
@@ -316,11 +245,11 @@ export default {
},
getStatusText(status) {
const map = {
'-1': this.t('statusRejected'),
'0': this.t('statusPending'),
'1': this.t('statusApproved')
'-1': this.$tr('已拒绝'),
'0': this.$tr('待审批'),
'1': this.$tr('已通过')
}
return map[String(status)] || this.t('statusProcessing')
return map[String(status)] || this.$tr('处理中')
},
getDisplayStatusText(item = {}) {
if (this.language !== 'en' && item.approvalStatusDesc) {
@@ -340,28 +269,28 @@ export default {
if (!start && !end) {
return '--'
}
return `${this.formatDate(start)}${this.t('dateRangeJoiner')}${this.formatDate(end)}`
return `${this.formatDate(start)}${this.$tr(' 至 ')}${this.formatDate(end)}`
},
getStatusTip(item = {}) {
if (Number(item.approvalStatus) === 1) {
return this.t('tipApproved')
return this.$tr('合同已生效,可查看签约结果。')
}
if (Number(item.approvalStatus) === 0 && !item.signImageUrl) {
return this.t('tipNeedSign')
return this.$tr('待您完成签名后进入审批流程。')
}
if (Number(item.approvalStatus) === 0) {
return this.t('tipPending')
return this.$tr('已提交签约信息,等待审批结果。')
}
if (Number(item.approvalStatus) === -1) {
return this.t('tipRejected')
return this.$tr('合同未通过审批,请联系管理员处理。')
}
return this.t('tipDefault')
return this.$tr('请关注合同最新状态。')
},
resolveActionText(item = {}) {
if (this.shouldShowSignAction(item)) {
return this.t('actionSign')
return this.$tr('去签约')
}
return this.t('actionViewDetail')
return this.$tr('查看详情')
},
shouldShowSignAction(item = {}) {
return Number(item.approvalStatus) === 0 && !item.signImageUrl

View File

@@ -7,7 +7,7 @@
<!-- Header -->
<view class="page-header" :style="{ paddingTop: vuex_custom_bar_height ? 'calc(' + vuex_custom_bar_height + 'px + 32rpx)' : '32rpx' }">
<view class="title-wrap">
<text class="title-zh">{{ t('pageTitle') }}</text>
<text class="title-zh">{{ $tr('验房') }}</text>
<text class="title-en">Inspection</text>
</view>
<view v-if="activeInspection" class="status-tag">
@@ -37,15 +37,15 @@
<!-- High Density Info Grid -->
<view class="info-grid">
<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>
</view>
<view class="info-cell">
<text class="info-label">{{ t('inspectionTypeLabel') }}</text>
<text class="info-label">{{ $tr('验房类型') }}</text>
<text class="info-value">{{ activeInspectionDetail.typeText }}</text>
</view>
<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>
</view>
</view>
@@ -55,7 +55,7 @@
<textarea
class="remarks-input"
:value="remarksDraft"
:placeholder="t('inspectionRemarksPlaceholder')"
:placeholder="$tr('可填写本次验房的补充说明')"
maxlength="300"
auto-height
@input="handleRemarksInput"
@@ -65,7 +65,7 @@
<!-- Items -->
<view v-if="activeInspectionDetail.items.length" class="items-wrap">
<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>
</view>
@@ -77,34 +77,34 @@
>
<view class="item-header">
<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">
{{ [item.assetSourceText, item.brandSnapshot].filter(Boolean).join(' / ') }}
</text>
</view>
<text v-if="item.isRequiredSnapshot" class="item-required">{{ t('requiredTag') }}</text>
<text v-if="item.isRequiredSnapshot" class="item-required">{{ $tr('必查') }}</text>
</view>
<view class="item-body">
<view class="row-flex">
<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>
</view>
<view class="col-half">
<text class="field-label">{{ t('checkedQuantityLabel') }}</text>
<text class="field-label">{{ $tr('实测数量') }}</text>
<input
class="field-input font-mono"
type="number"
:value="item.checkedQuantityDraft"
:placeholder="t('checkedQuantityPlaceholder')"
:placeholder="$tr('请输入数量')"
@input="updateItemField(index, 'checkedQuantityDraft', $event.detail.value)"
/>
</view>
</view>
<view class="field-row mt-20">
<text class="field-label">{{ t('inspectionResultLabel') }}</text>
<text class="field-label">{{ $tr('检查结果') }}</text>
<view class="radio-group">
<view
v-for="option in inspectionResultOptions"
@@ -122,7 +122,7 @@
<textarea
class="field-textarea"
:value="item.problemDetailDraft"
:placeholder="t('problemDetailPlaceholder')"
:placeholder="$tr('请描述异常、缺失或损坏情况')"
maxlength="300"
auto-height
@input="updateItemField(index, 'problemDetailDraft', $event.detail.value)"
@@ -155,25 +155,25 @@
</view>
<view v-else-if="!loading" class="empty-state">
<text class="empty-title">{{ t('emptyItemsTitle') }}</text>
<text class="empty-desc">{{ t('emptyItemsDesc') }}</text>
<text class="empty-title">{{ $tr('暂无检查项') }}</text>
<text class="empty-desc">{{ $tr('当前验房单还没有检查项明细。') }}</text>
</view>
</view>
<view v-if="!loading && !inspectionList.length" class="empty-state">
<text class="empty-title">{{ t('emptyTitle') }}</text>
<text class="empty-desc">{{ t('emptyDesc') }}</text>
<text class="empty-title">{{ $tr('暂无验房单') }}</text>
<text class="empty-desc">{{ $tr('当前订单还没有可查看的验房记录。') }}</text>
</view>
<view v-if="activeInspectionDetail" class="bottom-action">
<view class="btn-submit" @click="submitInspection">
{{ submitting ? t('submitting') : t('submitAction') }}
{{ submitting ? $tr('提交中...') : $tr('提交验房') }}
</view>
</view>
<view v-if="loading" class="global-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>
</template>
@@ -295,84 +295,6 @@ export default {
})
},
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() {
this.inspectionResultOptions = this.getEnumOptionsWithAliases([
'InspectionItemResultEnum',
@@ -561,7 +483,7 @@ export default {
const hasMissingResult = items.some(item => item.result === null || Number.isNaN(item.result))
if (hasMissingResult) {
uni.showToast({
title: this.t('resultRequired'),
title: this.$tr('请先完成所有检查项结果选择'),
icon: 'none'
})
return false
@@ -584,7 +506,7 @@ export default {
items: this.buildSubmitItems()
}, this.language)
uni.showToast({
title: this.t('submitSuccess'),
title: this.$tr('验房单已提交'),
icon: 'success'
})
setTimeout(() => {
@@ -597,7 +519,7 @@ export default {
}, 500)
} catch (error) {
uni.showToast({
title: (error && (error.message || error.msg)) || this.t('submitFailed'),
title: (error && (error.message || error.msg)) || this.$tr('提交失败'),
icon: 'none'
})
} finally {
@@ -615,7 +537,7 @@ export default {
return
}
uni.showLoading({
title: this.t('loadingDesc'),
title: this.$tr('正在同步验房数据...'),
mask: true
})
try {
@@ -631,7 +553,7 @@ export default {
)
const remoteUrl = resolveUploadedFileUrl(uploadResult)
if (!remoteUrl) {
throw new Error(this.t('uploadFailed'))
throw new Error(this.$tr('图片上传失败'))
}
uploadedUrls.push(addFilePrefix(remoteUrl))
}
@@ -643,7 +565,7 @@ export default {
this.$set(detail.items[index], 'imageUrlsDraft', nextUrls)
} catch (error) {
uni.showToast({
title: (error && (error.message || error.msg)) || this.t('uploadFailed'),
title: (error && (error.message || error.msg)) || this.$tr('图片上传失败'),
icon: 'none'
})
} finally {
@@ -652,7 +574,7 @@ export default {
},
fail: () => {
uni.showToast({
title: this.t('chooseImageFailed'),
title: this.$tr('选择图片失败'),
icon: 'none'
})
}

View File

@@ -12,22 +12,22 @@
<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>
</view>
<view class="hero-title tn-text-bold">{{ t('lockNoAccessTitle') }}</view>
<view class="hero-desc tn-margin-top-sm tn-text-center">{{ t('lockNoAccessDesc') }}</view>
<view class="hero-title tn-text-bold">{{ $tr('暂无门锁权限') }}</view>
<view class="hero-desc tn-margin-top-sm tn-text-center">{{ $tr('您当前暂未分配智能门锁,或门锁暂未联网。\n入住后即可使用一键开门与临时密码下发功能。') }}</view>
<view class="tn-flex tn-margin-top-xl tn-width-full tn-flex-row-between">
<view class="btn-light tn-flex-1 tn-margin-right-sm tn-flex tn-flex-col-center tn-flex-row-center tn-padding-sm" style="border-radius: 12rpx; opacity: 0.6;">
<text class="tn-icon-key tn-margin-right-xs"></text> {{ t('tempPassword') }}
<text class="tn-icon-key tn-margin-right-xs"></text> {{ $tr('临时密码') }}
</view>
<view class="btn-primary tn-flex-1 tn-margin-left-sm tn-flex tn-flex-col-center tn-flex-row-center tn-padding-sm" style="border-radius: 12rpx; opacity: 0.6;">
<text class="tn-icon-unlock tn-margin-right-xs"></text> {{ t('oneTapUnlock') }}
<text class="tn-icon-unlock tn-margin-right-xs"></text> {{ $tr('一键开锁') }}
</view>
</view>
</view>
<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">
<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>
@@ -36,16 +36,16 @@
<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="tn-flex tn-flex-row-between tn-flex-col-center">
<view class="tn-text-md">{{ t('pointsAvailable') }}</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-md">{{ $tr('当前可用积分') }}</view>
<view class="tn-text-sm" style="background: rgba(255,255,255,0.2); padding: 4rpx 16rpx; border-radius: 100rpx;">{{ $tr('兑换规则') }}</view>
</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-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 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>
@@ -53,20 +53,20 @@
<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;">
<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-xs tn-color-gray tn-margin-top-xs">{{ t('pointsMallDesc') }}</text>
<text class="tn-text-bold tn-color-black">{{ $tr('积分商城') }}</text>
<text class="tn-text-xs tn-color-gray tn-margin-top-xs">{{ $tr('超值好物兑换') }}</text>
</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;">
<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-xs tn-color-gray tn-margin-top-xs">{{ t('couponDesc') }}</text>
<text class="tn-text-bold tn-color-black">{{ $tr('优惠券') }}</text>
<text class="tn-text-xs tn-color-gray tn-margin-top-xs">{{ $tr('租金水电抵扣') }}</text>
</view>
</view>
<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">
<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>
@@ -74,28 +74,28 @@
<!-- 一码通管理 Mockup -->
<view v-else-if="pageKey === 'code'">
<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-xs tn-color-gray tn-margin-top-xs">{{ t('accessCodeDesc') }}</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">{{ $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-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>
</view>
<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 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 class="record-card tn-margin-top-lg">
<view class="tn-flex tn-flex-row-between tn-flex-col-center">
<view class="block-title">{{ t('visitorAccessTitle') }}</view>
<view class="tn-color-gray tn-text-sm">{{ t('visitorAccessAction') }} <text class="tn-icon-right"></text></view>
<view class="block-title">{{ $tr('访客通行') }}</view>
<view class="tn-color-gray tn-text-sm">{{ $tr('临时访客授权') }} <text class="tn-icon-right"></text></view>
</view>
<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>
@@ -103,25 +103,25 @@
<!-- 默认通用占位 -->
<view v-else>
<view class="hero-card">
<view class="hero-badge">{{ t('heroBadge') }}</view>
<view class="hero-title tn-margin-top">{{ resolvedPageTitle || t('comingSoonTitle') }}</view>
<view class="hero-badge">{{ $tr('即将开放') }}</view>
<view class="hero-title tn-margin-top">{{ resolvedPageTitle || $tr('功能建设中') }}</view>
<view class="hero-desc tn-margin-top-sm">{{ resolvedPageDesc || resolvedDefaultDesc }}</view>
</view>
<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">
{{ t('currentStatusDesc') }}
{{ $tr('该功能当前仍在建设中,暂未开放正式办理入口。你可以先返回上一页,或回到首页继续查看已开放功能。') }}
</view>
<view class="tip-card tn-margin-top-lg">
<view class="tip-title">{{ t('noteTitle') }}</view>
<view class="tip-desc tn-margin-top-xs">{{ t('noteDesc') }}</view>
<view class="tip-title">{{ $tr('说明') }}</view>
<view class="tip-desc tn-margin-top-xs">{{ $tr('后续接入真实能力后,这里会展示正式的业务入口、状态数据和办理记录。') }}</view>
</view>
<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 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>
@@ -143,78 +143,6 @@ function safeDecodeText(value, fallback = '') {
}
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() {
return {
pageTitleZh: '',
@@ -229,9 +157,9 @@ export default {
computed: {
resolvedPageTitle() {
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() {
if (this.$getLanguage() === 'en') {
@@ -240,7 +168,7 @@ export default {
return this.pageDescZh || this.pageDescEn || this.rawPageDesc || ''
},
resolvedDefaultDesc() {
return this.t('defaultPageDesc')
return this.$tr('该能力正在完善中,开放后会同步到个人中心的对应入口。')
}
},
onLoad(options) {
@@ -253,9 +181,6 @@ export default {
this.pageKey = safeDecodeText(options && options.key, '')
},
methods: {
t(key, params) {
return this.$tm('pkgService.service_stub.service_stub', key, params)
},
goBack() {
uni.navigateBack()
},

View File

@@ -1,7 +1,7 @@
<template>
<view class="smart-page">
<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>
<view class="header-section" :style="{ paddingTop: (vuex_custom_bar_height + 20) + 'px' }">
@@ -13,31 +13,31 @@
</view>
<view class="toolbar">
<view class="toolbar-tip">{{ t('toolbarTip') }}</view>
<view class="toolbar-tip">{{ $tr('点击设备进入详情并下发控制指令') }}</view>
<view class="toolbar-action" @click="reloadDevices">
<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>
<!-- Feedback States -->
<view v-if="isLoading && !devices.length" class="empty-state">
<view class="loading-ring"></view>
<view class="empty-title">{{ t('loadingTitle') }}</view>
<view class="empty-desc">{{ t('loadingDesc') }}</view>
<view class="empty-title">{{ $tr('正在连接设备') }}</view>
<view class="empty-desc">{{ $tr('正在查询当前房间的可控设备...') }}</view>
</view>
<view v-else-if="loadError && !devices.length" class="empty-state error-state">
<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="retry-btn" @click="reloadDevices">{{ t('retry') }}</view>
<view class="retry-btn" @click="reloadDevices">{{ $tr('重新加载') }}</view>
</view>
<view v-else-if="!devices.length" class="empty-state">
<text class="tn-icon-empty empty-icon"></text>
<view class="empty-title">{{ t('emptyTitle') }}</view>
<view class="empty-desc">{{ t('emptyDesc') }}</view>
<view class="empty-title">{{ $tr('暂无可控设备') }}</view>
<view class="empty-desc">{{ $tr('当前房间未绑定智能设备') }}</view>
</view>
<scroll-view v-else scroll-y class="device-scroll">
@@ -58,7 +58,7 @@
</view>
<view class="card-bottom">
<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>
@@ -126,60 +126,6 @@ function resolveDeviceListPollingInterval(deviceList = [], fallback = 1000) {
}
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() {
return {
brandId: DEFAULT_BRAND_ID,
@@ -202,19 +148,19 @@ export default {
return this.$getLanguage()
},
displayRoomName() {
return this.roomName || this.roomCode || this.t('currentRoom')
return this.roomName || this.roomCode || this.$tr('当前房间')
},
summaryText() {
if (!this.roomCode) {
return this.t('missingRoomCode')
return this.$tr('缺少房间编码')
}
if (this.isLoading) {
return this.t('syncingStatus')
return this.$tr('正在同步设备状态')
}
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) {
@@ -236,9 +182,6 @@ export default {
this.stopPolling()
},
methods: {
t(key, params) {
return this.$tm('pkgService.smart_control.smart_control', key, params)
},
resolvePageContext(options = {}) {
const session = getSessionSnapshot()
const roomData = (session && session.myRoomData) || {}
@@ -321,7 +264,7 @@ export default {
if (!this.roomCode) {
this.devices = []
this.loadError = this.t('missingRoomCode')
this.loadError = this.$tr('缺少房间编码')
if (stopRefresh) {
uni.stopPullDownRefresh()
}
@@ -361,7 +304,7 @@ export default {
this.devices = []
}
if (!silent) {
this.loadError = (error && (error.message || error.msg)) || this.t('requestFailed')
this.loadError = (error && (error.message || error.msg)) || this.$tr('设备列表请求失败')
}
})
.finally(() => {
@@ -397,7 +340,7 @@ export default {
const functions = getDeviceFunctions(item)
if (!functions || !functions.length) {
uni.showToast({ title: this.t('unsupportedQuickControl'), icon: 'none' })
uni.showToast({ title: this.$tr('设备暂不支持快捷控制'), icon: 'none' })
return
}
@@ -409,13 +352,13 @@ export default {
const category = getControlTypeCategory(targetFunc.controlType)
if (category !== 'switch') {
uni.showToast({ title: this.t('unsupportedSwitch'), icon: 'none' })
uni.showToast({ title: this.$tr('该设备不支持快捷开关'), icon: 'none' })
return
}
const functionCode = String((targetFunc && targetFunc.functionCode) || '').trim()
if (!functionCode) {
uni.showToast({ title: this.t('missingFunctionCode'), icon: 'none' })
uni.showToast({ title: this.$tr('功能编码缺失'), icon: 'none' })
return
}
@@ -433,7 +376,7 @@ export default {
}
this.isSubmitting = true
uni.showLoading({ title: this.t('sending') })
uni.showLoading({ title: this.$tr('发送中...') })
gatewayExecuteDeviceCommand(this.brandId, item.deviceNo, functionCode, commandPayload)
.then(() => {
@@ -447,11 +390,11 @@ export default {
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) => {
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(() => {
this.isSubmitting = false

View File

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

View File

@@ -8,7 +8,7 @@
</view>
</picker>
</view>
<view class="date-divider">{{ t('dateDivider') }}</view>
<view class="date-divider">{{ $tr('至') }}</view>
<view class="date-item">
<view class="date-label">{{ resolvedEndLabel }}</view>
<picker mode="date" @change="onEndChange">
@@ -23,22 +23,6 @@
<script>
export default {
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: {
start: {
type: String,
@@ -67,22 +51,19 @@ export default {
},
computed: {
resolvedStartLabel() {
return this.startLabel || this.t('startLabel')
return this.startLabel || this.$tr('预计入住')
},
resolvedEndLabel() {
return this.endLabel || this.t('endLabel')
return this.endLabel || this.$tr('预计离开')
},
resolvedStartPlaceholder() {
return this.startPlaceholder || this.t('startPlaceholder')
return this.startPlaceholder || this.$tr('请选择入住日期')
},
resolvedEndPlaceholder() {
return this.endPlaceholder || this.t('endPlaceholder')
return this.endPlaceholder || this.$tr('请选择离开日期')
}
},
methods: {
t(key) {
return this.$tm('pkgService.submit_request.components.DateRangeSelector', key)
},
onStartChange(e) {
this.$emit('update:start', e.detail.value)
},
@@ -128,7 +109,7 @@ export default {
font-size: 28rpx;
color: #1A2B4C;
font-weight: 600;
&.is-empty {
color: #CBD5E1;
font-weight: normal;

View File

@@ -15,17 +15,17 @@
<tn-popup v-model="showPopup" mode="bottom" length="72%" :borderRadius="32" :closeBtn="true">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">{{ t('popupTitle') }}</text>
<text class="popup-title">{{ $tr('选择意向楼层') }}</text>
</view>
<scroll-view scroll-y class="floor-list-scroll">
<view v-if="isLoading" class="loading-state tn-padding-xl tn-text-center">
<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 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 v-else class="floor-list">
@@ -34,7 +34,7 @@
:class="{ 'is-active': !selectedFloor }"
@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>
</view>
<view
@@ -133,7 +133,7 @@ export default {
return this.$getLanguage ? this.$getLanguage() : 'zh'
},
placeholderText() {
return this.t('selectFloor')
return this.$tr('请选择楼层')
}
},
watch: {
@@ -148,9 +148,6 @@ export default {
this.loadData()
},
methods: {
t(key, params) {
return this.$tm('pkgService.submit_request.components.FloorSelector', key, params)
},
handleOpen() {
this.showPopup = true
this.loadData()
@@ -176,7 +173,7 @@ export default {
this.floorList = []
this.selectedFloor = null
uni.showToast({
title: this.t('loadFailed'),
title: this.$tr('楼层列表加载失败'),
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="selected-info">
<view class="selected-name tn-text-ellipsis" :class="{ 'is-empty': !selectedRoomType }">
{{ selectedRoomType ? getRoomTypeDisplayName(selectedRoomType) : t('selectRoomType') }}
{{ selectedRoomType ? getRoomTypeDisplayName(selectedRoomType) : $tr('请选择意向房型') }}
</view>
<view class="selected-area tn-text-ellipsis" v-if="selectedArea">
{{ getAreaDisplayName(selectedArea) }}
@@ -15,17 +15,17 @@
<tn-popup v-model="showPopup" mode="bottom" length="80%" :borderRadius="32" :closeBtn="true">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">{{ t('popupTitle') }}</text>
<text class="popup-title">{{ $tr('选择意向房型') }}</text>
</view>
<scroll-view scroll-y class="area-list-scroll">
<view v-if="isLoading" class="loading-state tn-padding-xl tn-text-center">
<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 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 v-else class="area-group" v-for="(area, index) in displayAreaList" :key="area.code">
@@ -45,7 +45,7 @@
@click="handleSelect(area, room)"
>
<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>
@@ -118,9 +118,6 @@ export default {
this.loadData()
},
methods: {
t(key) {
return this.$translate(this.i18n, key)
},
openSelector() {
this.showPopup = true
this.loadData()
@@ -148,7 +145,7 @@ export default {
})
.catch((e) => {
console.error('load room types error', e)
uni.showToast({ title: this.t('loadFailed'), icon: 'none' })
uni.showToast({ title: this.$tr('房型列表加载失败'), icon: 'none' })
})
.finally(() => {
this.isLoading = false

View File

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

View File

@@ -44,22 +44,22 @@
</view>
<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
v-model="form.hobbiesAndInterestsTagKeys"
:options="hobbyOptions"
:loading="dictLoading"
:emptyText="t('emptyHobbyTags')"
:emptyText="$tr('暂无兴趣爱好标签')"
/>
</view>
<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
v-model="form.lifestyleHabitsTagKeys"
:options="lifestyleOptions"
:loading="dictLoading"
:emptyText="t('emptyLifestyleTags')"
:emptyText="$tr('暂无生活习惯标签')"
/>
</view>
@@ -76,12 +76,12 @@
</view>
<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>
</view>
<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>
@@ -91,7 +91,7 @@
<view v-if="showFloatingSubmitBtn" class="floating-submit-bar">
<view class="floating-submit-bar__inner">
<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>
@@ -594,81 +594,7 @@ export default {
lifestyleOptions: [],
form: createDefaultForm(),
scrollTop: 0,
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'
}
}
lastAutoRemark: ''
}
},
computed: {
@@ -709,52 +635,52 @@ export default {
pageTitle() {
if (this.isRoomTypeApply) {
return this.language === 'en'
? compactEnglishText(this.t('roomTypeApplyTitle'), 22)
: this.t('roomTypeApplyTitle')
? compactEnglishText(this.$tr('房型申请'), 22)
: this.$tr('房型申请')
}
return this.getBusinessText('titleShort')
|| this.getBusinessText('title')
|| this.getLocalizedSourceText('title')
|| this.t('stayApplyDefaultTitle')
|| this.$tr('入住申请')
},
pageDesc() {
if (this.isRoomTypeApply) {
return this.language === 'en'
? compactEnglishText(this.t('roomTypeApplyDesc'), 96)
: this.t('roomTypeApplyDesc')
? compactEnglishText(this.$tr('请确认房型、区域、偏好标签和租期信息后提交申请。'), 96)
: 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
},
formBlockTitle() {
if (this.isRoomTypeApply) {
return this.t('roomTypeApplyBlockTitle')
return this.$tr('房型申请信息')
}
return this.getBusinessText('formBlockTitle') || this.t('applicationInfo')
return this.getBusinessText('formBlockTitle') || this.$tr('申请信息')
},
formBlockDesc() {
if (this.isRoomTypeApply) {
return this.t('roomTypeApplyBlockDesc')
return this.$tr('已接入真实申请单接口,支持带出房型与区域编码后直接提交。')
}
return this.getBusinessText('formBlockDesc') || this.t('stayApplyDefaultDesc')
return this.getBusinessText('formBlockDesc') || this.$tr('请填写您的入住意向、生活偏好与预计租期后提交申请单。')
},
remarkPlaceholder() {
if (this.isRoomTypeApply) {
return this.t('applyRemarkPlaceholder')
return this.$tr('请输入您的申请补充说明')
}
return this.getBusinessText('remarkPlaceholder') || this.t('remarkRequired')
return this.getBusinessText('remarkPlaceholder') || this.$tr('请输入补充说明')
},
primaryActionText() {
if (this.isRoomTypeApply) {
return this.language === 'en'
? compactEnglishText(this.t('submitRoomTypeApply'), 24)
: this.t('submitRoomTypeApply')
? compactEnglishText(this.$tr('提交房型申请'), 24)
: 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
},
roomTypeLabel() {
return this.getBusinessText('roomTypeLabel') || this.t('applyRoomType')
return this.getBusinessText('roomTypeLabel') || this.$tr('申请房型')
},
pageBottomPadding() {
return this.showFloatingSubmitBtn ? '220rpx' : '60rpx'
@@ -763,7 +689,7 @@ export default {
return this.isRoomTypeApply
},
floorLabel() {
return this.getBusinessText('floorLabel') || this.t('floorLabel')
return this.getBusinessText('floorLabel') || this.$tr('楼层')
},
showRoomTypeSelector() {
if (this.isRoomTypeApply) {
@@ -790,19 +716,19 @@ export default {
return this.businessConfig ? this.businessConfig.showDateRange !== false : true
},
dateRangeLabel() {
return this.getBusinessText('dateRangeLabel') || this.t('leaseRange')
return this.getBusinessText('dateRangeLabel') || this.$tr('租期时间')
},
dateRangeStartLabel() {
return this.getBusinessText('dateRangeStartLabel') || this.t('expectedCheckin')
return this.getBusinessText('dateRangeStartLabel') || this.$tr('预计入住')
},
dateRangeEndLabel() {
return this.getBusinessText('dateRangeEndLabel') || this.t('expectedCheckout')
return this.getBusinessText('dateRangeEndLabel') || this.$tr('预计离开')
},
dateRangeStartPlaceholder() {
return this.getBusinessText('dateRangeStartPlaceholder') || this.t('selectCheckinDate')
return this.getBusinessText('dateRangeStartPlaceholder') || this.$tr('请选择入住日期')
},
dateRangeEndPlaceholder() {
return this.getBusinessText('dateRangeEndPlaceholder') || this.t('selectCheckoutDate')
return this.getBusinessText('dateRangeEndPlaceholder') || this.$tr('请选择离开日期')
}
},
watch: {
@@ -839,9 +765,6 @@ export default {
this.loadDictOptions()
},
methods: {
t(key, params) {
return this.$tm('pkgService.submit_request.submit_request', key, params)
},
decodeRouteText(value, fallback = '') {
if (value === undefined || value === null || value === '') {
return fallback
@@ -1000,7 +923,7 @@ export default {
.catch((error) => {
console.error('load application dict options failed', error)
uni.showToast({
title: this.t('tagLoadFailed'),
title: this.$tr('偏好标签加载失败'),
icon: 'none'
})
})
@@ -1038,20 +961,20 @@ export default {
validateForm() {
if (this.showDateRange) {
if (!this.form.start) {
return this.t('selectDateStart', { label: this.dateRangeStartLabel })
return this.$tr('请选择{label}', { label: this.dateRangeStartLabel })
}
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()) {
return this.t('dateCannotBefore', {
return this.$tr('{endLabel}不能早于{startLabel}', {
endLabel: this.dateRangeEndLabel,
startLabel: this.dateRangeStartLabel
})
}
}
if (!this.form.remark.trim()) {
return this.t('remarkRequired')
return this.$tr('请输入补充说明')
}
return ''
},
@@ -1084,7 +1007,7 @@ export default {
this.isSubmitting = true
uni.showLoading({
title: this.t('submitting')
title: this.$tr('提交中...')
})
const payload = this.buildSubmitPayload()
@@ -1096,7 +1019,7 @@ export default {
}
uni.hideLoading()
uni.showToast({
title: this.t('submitSuccess'),
title: this.$tr('申请单提交成功'),
icon: 'success'
})
@@ -1111,7 +1034,7 @@ export default {
console.error('submit application form failed', error)
uni.hideLoading()
uni.showToast({
title: (error && (error.message || error.msg)) || this.t('submitFailed'),
title: (error && (error.message || error.msg)) || this.$tr('申请单提交失败'),
icon: 'none'
})
})

View File

@@ -1,17 +1,17 @@
<template>
<view class="ticket-center-page tn-safe-area-inset-bottom">
<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>
<view class="page-content tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
<view class="hero-card tn-margin-bottom-lg">
<view class="hero-badge">
<text class="badge-dot"></text>
<text>{{ t('heroBadge') }}</text>
<text>{{ $tr('SERVICE') }}</text>
</view>
<view class="hero-title tn-margin-top">{{ t('heroTitle') }}</view>
<view class="hero-desc tn-margin-top-sm">{{ t('heroDesc') }}</view>
<view class="hero-title tn-margin-top">{{ $tr('住中工单服务') }}</view>
<view class="hero-desc tn-margin-top-sm">{{ $tr('报修、续住、退宿、调宿、送水、清洁和意见建议都统一从这里进入,提交后可继续查看流转记录。') }}</view>
</view>
<view class="service-grid">
@@ -33,20 +33,6 @@
<script>
export default {
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() {
return {
ticketServices: [
@@ -110,9 +96,6 @@ export default {
}
},
methods: {
t(key, params) {
return this.$translate(this.i18n, key, params)
},
getItemText(item, fieldZh, fieldEn) {
return this.$getLanguage && this.$getLanguage() === 'en'
? (item[fieldEn] || item[fieldZh] || '')

View File

@@ -15,7 +15,7 @@
<view class="hero-header tn-margin-bottom-xl">
<view class="hero-badge">
<text class="badge-dot"></text>
<text>{{ t('ticketKicker') }}</text>
<text>{{ $tr('WORK ORDER') }}</text>
</view>
<view class="hero-title">{{ pageTitle }}</view>
<view class="hero-desc">{{ pageDesc }}</view>
@@ -27,35 +27,35 @@
:class="{ 'segment-item--active': activeTab === 'submit' }"
@click="switchTab('submit')"
>
{{ t('submitTab') }}
{{ $tr('提交申请') }}
</view>
<view
class="segment-item"
:class="{ 'segment-item--active': activeTab === 'history' }"
@click="switchTab('history')"
>
{{ t('historyTab') }}
{{ $tr('记录查询') }}
</view>
</view>
<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="context-grid tn-margin-top">
<view class="context-item">
<view class="context-label">{{ t('roomLabel') }}</view>
<view class="context-label">{{ $tr('当前房间') }}</view>
<view class="context-value">{{ currentRoomText }}</view>
</view>
<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>
<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>
<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>
</view>
@@ -64,8 +64,8 @@
<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__desc">{{ modeBannerText }}</view>
<view v-if="fixOnholdMode" class="status-card__action" @click="cancelFixOnhold">{{ t('cancelSupplement') }}</view>
<view v-else-if="transitActionMode" class="status-card__action" @click="cancelTransitAction">{{ t('cancelProcess') }}</view>
<view v-if="fixOnholdMode" class="status-card__action" @click="cancelFixOnhold">{{ $tr('取消补充') }}</view>
<view v-else-if="transitActionMode" class="status-card__action" @click="cancelTransitAction">{{ $tr('退出处理') }}</view>
</view>
<view v-if="activeTab === 'submit'" class="form-card">
@@ -73,7 +73,7 @@
<view class="block-subtitle">{{ submitSectionDesc }}</view>
<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
v-model="switchForm.roomTypeCode"
:initial-area-code="switchForm.areaCode"
@@ -82,37 +82,37 @@
</view>
<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">
<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 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>
</view>
</view>
<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">
<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 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>
</view>
</view>
<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">
<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 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>
</view>
</view>
@@ -129,26 +129,26 @@
</view>
<view class="form-item tn-margin-top">
<view class="form-label">{{ t('remarkLabel') }}</view>
<view class="form-label">{{ $tr('补充说明') }}</view>
<textarea
class="form-textarea form-textarea--remark"
:placeholder="t('remarkPlaceholder')"
:placeholder="$tr('请输入补充说明,帮助工作人员更快处理')"
:maxlength="300"
v-model="form.remark"
></textarea>
</view>
<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">
<view class="form-picker">
{{ transitSelectedAssigneeName || t('selectAssignee') }}
{{ transitSelectedAssigneeName || $tr('请选择处理目标') }}
</view>
</picker>
</view>
<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
v-for="(item, index) in fixOnholdFields"
:key="`${item.key}-${index}`"
@@ -159,20 +159,20 @@
v-if="shouldUseTextarea(item.value)"
class="form-textarea form-textarea--small"
v-model="item.value"
:placeholder="t('supplementPlaceholder', { label: item.title })"
:placeholder="$tr('请输入{label}', { label: item.title })"
></textarea>
<input
v-else
class="form-input"
type="text"
v-model="item.value"
:placeholder="t('supplementPlaceholder', { label: item.title })"
:placeholder="$tr('请输入{label}', { label: item.title })"
/>
</view>
</view>
<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
:key="uploadInstanceKey"
:fileList="form.photos"
@@ -192,7 +192,7 @@
<template #addBtn>
<view class="upload-trigger">
<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>
</template>
</tn-image-upload>
@@ -207,13 +207,13 @@
<input
v-model="searchKeyword"
class="search-box__input"
:placeholder="t('searchPlaceholder')"
:placeholder="$tr('搜索工单标题或内容')"
confirm-type="search"
@confirm="refreshHistory"
/>
</view>
<view class="refresh-mini-btn" @click="refreshHistory">
{{ historyLoading ? t('refreshing') : t('refresh') }}
{{ historyLoading ? $tr('刷新中...') : $tr('刷新') }}
</view>
</view>
@@ -249,26 +249,26 @@
</view>
<view class="history-card__body tn-margin-top">
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('submittedAtLabel') }}</text>
<text class="info-label">{{ $tr('提交时间') }}</text>
<text class="info-value">{{ formatDateTime(item.createTime) }}</text>
</view>
<view 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>
</view>
</view>
</view>
<view class="load-state">
<text v-if="historyLoading">{{ t('loadingMore') }}</text>
<text v-else-if="!hasNext">{{ t('noMore') }}</text>
<text v-else @click="loadMoreHistory">{{ t('loadMore') }}</text>
<text v-if="historyLoading">{{ $tr('加载中...') }}</text>
<text v-else-if="!hasNext">{{ $tr('没有更多了') }}</text>
<text v-else @click="loadMoreHistory">{{ $tr('加载更多') }}</text>
</view>
</view>
<view v-else-if="!historyLoading" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('emptyHistoryTitle') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('emptyHistoryDesc') }}</view>
<view class="empty-title">{{ $tr('暂无工单记录') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ $tr('当前类型还没有工单记录。') }}</view>
</view>
</view>
</view>
@@ -278,7 +278,7 @@
<view class="floating-submit-bar__inner">
<view class="floating-submit-tip">{{ submitFooterTip }}</view>
<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>
@@ -298,10 +298,10 @@
<view class="flow-card__status">{{ getFlowStatusText(item) }}</view>
<view class="flow-card__time tn-margin-top-xs">{{ formatDateTime(item.createTime) }}</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 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">
<image
@@ -316,7 +316,7 @@
</view>
<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
v-for="(item, index) in transitionOptions"
@@ -324,14 +324,14 @@
class="flow-action-btn"
@click="startTransitAction(item, getCurrentFlowItem())"
>
{{ item.nextStatusDesc || t('continueProcess') }}
{{ item.nextStatusDesc || $tr('继续处理') }}
</view>
</view>
</view>
<view v-if="canFixOnhold(currentFlowOrder)" class="flow-actions tn-margin-top">
<view class="flow-action-btn flow-action-btn--ghost" @click="startFixOnhold(currentFlowOrder)">
{{ t('supplementAction') }}
{{ $tr('补充信息') }}
</view>
</view>
</scroll-view>
@@ -455,158 +455,6 @@ export default {
components: {
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() {
return {
serviceType: 'TicketRepairApply',
@@ -737,11 +585,11 @@ export default {
},
currentOrderStatusText() {
const status = Number(this.currentOrderDetail.orderStatus)
if (Number.isNaN(status)) return this.t('currentStayOrder')
if ([0, 1, 2].includes(status)) return this.t('currentStatusPending')
if ([3, 4, 7].includes(status)) return this.t('currentStatusProcessing')
if ([5, 6].includes(status)) return this.t('currentStatusResolved')
return this.t('currentStatusClosed')
if (Number.isNaN(status)) return this.$tr('当前入住订单')
if ([0, 1, 2].includes(status)) return this.$tr('待处理')
if ([3, 4, 7].includes(status)) return this.$tr('处理中')
if ([5, 6].includes(status)) return this.$tr('已解决')
return this.$tr('已结束')
},
contentFieldLabel() {
if (this.isChangeService) return this.language === 'en' ? 'Change Reason' : '调宿说明'
@@ -776,7 +624,7 @@ export default {
}
},
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() {
if (this.fixOnholdMode) {
@@ -785,9 +633,9 @@ export default {
if (this.transitActionMode) {
return this.transitActionOption && this.transitActionOption.nextStatusDesc
? this.transitActionOption.nextStatusDesc
: this.t('continueProcess')
: this.$tr('继续处理')
}
return this.t('submitTicket')
return this.$tr('提交工单')
},
submitSectionTitle() {
if (this.fixOnholdMode) return this.language === 'en' ? 'Supplement Information' : '补充工单信息'
@@ -795,9 +643,9 @@ export default {
return this.pageTitle
},
submitSectionDesc() {
if (this.fixOnholdMode) return this.t('submitSectionSupplementDesc')
if (this.transitActionMode) return this.t('submitSectionActionDesc')
return this.t('submitSectionDefaultDesc')
if (this.fixOnholdMode) return this.$tr('当前工单处于挂起状态,补充信息后会继续流转。')
if (this.transitActionMode) return this.$tr('当前工单正在处理中,提交后会继续流转。')
return this.$tr('以下内容将通过工单接口提交并进入后续流转。')
},
modeBannerTitle() {
if (this.fixOnholdMode) return this.language === 'en' ? 'Supplement Required' : '当前工单需要补充信息'
@@ -808,12 +656,12 @@ export default {
if (this.fixOnholdMode) {
return this.fixOnholdTicket && this.fixOnholdTicket.no
? `${this.fixOnholdTicket.no} ${this.language === 'en' ? 'needs more information before continuing.' : '需要补充信息后继续流转。'}`
: this.t('submitSectionSupplementDesc')
: this.$tr('当前工单处于挂起状态,补充信息后会继续流转。')
}
if (this.transitActionMode) {
return this.transitActionOption && this.transitActionOption.nextStatusDesc
? `${this.language === 'en' ? 'Current action:' : '当前操作:'}${this.transitActionOption.nextStatusDesc}`
: this.t('submitSectionActionDesc')
: this.$tr('当前工单正在处理中,提交后会继续流转。')
}
return ''
},
@@ -827,11 +675,11 @@ export default {
},
historyStatusOptions() {
return [
{ value: '', label: this.t('allStatus') },
{ value: '0', label: this.t('pendingStatus') },
{ value: '3', label: this.t('processingStatus') },
{ value: '5', label: this.t('resolvedStatus') },
{ value: '9', label: this.t('closedStatus') }
{ value: '', label: this.$tr('全部') },
{ value: '0', label: this.$tr('待处理') },
{ value: '3', label: this.$tr('处理中') },
{ value: '5', label: this.$tr('已解决') },
{ value: '9', label: this.$tr('已结束') }
]
},
transitAssigneePickerRange() {
@@ -868,9 +716,6 @@ export default {
this.refreshContext()
},
methods: {
t(key, params) {
return this.$translate(this.i18n, key, params)
},
handleScroll(e) {
this.scrollTop = e.detail.scrollTop
},
@@ -905,7 +750,7 @@ export default {
}
})
.catch(() => {
uni.showToast({ title: this.t('loadOrderFailed'), icon: 'none' })
uni.showToast({ title: this.$tr('入住信息加载失败'), icon: 'none' })
})
},
resetForm() {
@@ -1005,28 +850,28 @@ export default {
},
getSubmitValidationMessage() {
if (this.fixOnholdMode && !this.fixOnholdFlowNo) {
return this.t('submitFailed')
return this.$tr('工单提交失败')
}
if (this.transitActionMode && !this.transitActionFlowNo) {
return this.t('submitFailed')
return this.$tr('工单提交失败')
}
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)) {
return this.t('repairAttachmentRequired')
return this.$tr('请上传报修附件图片')
}
if (this.isChangeService && !this.switchForm.roomTypeCode) {
return this.t('changeAreaRequired')
return this.$tr('请选择意向房型')
}
if (this.isCheckOutService && !this.resolveCheckOutTime()) {
return this.t('checkoutTimeRequired')
return this.$tr('请选择退宿时间')
}
if (this.isCheckOutService && !String(this.form.content || '').trim()) {
return this.t('checkoutReasonRequired')
return this.$tr('请填写退宿原因')
}
if (this.showTransitAssigneeSelector && !this.transitSelectedAssigneeValue) {
return this.t('selectAssignee')
return this.$tr('请选择处理目标')
}
return ''
},
@@ -1278,8 +1123,8 @@ export default {
new Promise((resolve) => {
uni.showModal({
title: this.t('confirmSubmitTitle'),
content: this.t('confirmSubmitContent'),
title: this.$tr('确认提交'),
content: this.$tr('确认提交当前工单吗?'),
success: (res) => resolve(!!res.confirm),
fail: () => resolve(false)
})
@@ -1299,7 +1144,7 @@ export default {
return this.submitWithCompatibilityFallback(api, this.buildSubmitPayload())
})
.then(() => {
uni.showToast({ title: this.t('submitSuccess'), icon: 'success' })
uni.showToast({ title: this.$tr('工单提交成功'), icon: 'success' })
this.cancelFixOnhold()
this.cancelTransitAction()
this.resetForm()
@@ -1311,7 +1156,7 @@ export default {
return
}
uni.showToast({
title: this.getSubmitErrorText(error) || this.t('submitFailed'),
title: this.getSubmitErrorText(error) || this.$tr('工单提交失败'),
icon: 'none'
})
})
@@ -1360,7 +1205,7 @@ export default {
this.historyList = []
}
this.hasNext = false
uni.showToast({ title: this.t('loadHistoryFailed'), icon: 'none' })
uni.showToast({ title: this.$tr('工单记录加载失败'), icon: 'none' })
})
.finally(() => {
this.historyLoading = false
@@ -1380,10 +1225,10 @@ export default {
return 'closed'
},
statusText(status) {
if ([0, 1, 2].includes(status)) return this.t('pendingStatus')
if ([3, 4, 7].includes(status)) return this.t('processingStatus')
if ([5, 6].includes(status)) return this.t('resolvedStatus')
return this.t('closedStatus')
if ([0, 1, 2].includes(status)) return this.$tr('待处理')
if ([3, 4, 7].includes(status)) return this.$tr('处理中')
if ([5, 6].includes(status)) return this.$tr('已解决')
return this.$tr('已结束')
},
openFlows(item) {
this.currentFlowOrder = item
@@ -1398,7 +1243,7 @@ export default {
return this.loadTransitionOptions(this.getCurrentFlowItem())
})
.catch(() => {
uni.showToast({ title: this.t('loadFlowsFailed'), icon: 'none' })
uni.showToast({ title: this.$tr('工单流转加载失败'), icon: 'none' })
})
.finally(() => {
this.flowLoading = false
@@ -1419,7 +1264,7 @@ export default {
})
.catch(() => {
this.transitionOptions = []
uni.showToast({ title: this.t('loadActionsFailed'), icon: 'none' })
uni.showToast({ title: this.$tr('工单操作项加载失败'), icon: 'none' })
return []
})
},
@@ -1624,7 +1469,7 @@ export default {
this.transitAssigneeGroups = this.normalizeTransitAssigneeGroups(response)
})
.catch(() => {
uni.showToast({ title: this.t('loadAssigneeFailed'), icon: 'none' })
uni.showToast({ title: this.$tr('处理目标加载失败'), icon: 'none' })
})
},
onTransitAssigneeChange(event) {
@@ -1671,13 +1516,13 @@ export default {
this.form.attachments.splice(index, 1)
},
onUploadError() {
uni.showToast({ title: this.t('uploadImageFailed'), icon: 'none' })
uni.showToast({ title: this.$tr('图片上传失败,请重试'), icon: 'none' })
},
onUploadOversize() {
uni.showToast({ title: this.t('uploadImageOversize'), icon: 'none' })
uni.showToast({ title: this.$tr('图片不能超过 5MB'), icon: 'none' })
},
onUploadExceed() {
uni.showToast({ title: this.t('uploadImageCountExceeded'), icon: 'none' })
uni.showToast({ title: this.$tr('最多上传 6 张图片'), icon: 'none' })
},
getAttachmentPreview(attachment) {
return addFilePrefix(pickReadableValue(attachment && attachment.resourceUrl, attachment && attachment.url, attachment && attachment.path))

View File

@@ -1,7 +1,7 @@
<template>
<view class="bill-page tn-safe-area-inset-bottom">
<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>
<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'
export default {
i18n: {
zh: {
navTitle: '账单管理'
},
en: {
navTitle: 'Bill Management'
}
},
components: {
CurrentApplicationBillPanel
},
@@ -45,9 +37,6 @@ export default {
}
},
methods: {
t(key, params) {
return this.$tm('pkgService.utility_recharge.utility_recharge', key, params)
},
handleReferenceResolved(referenceTypeNo) {
this.referenceTypeNo = referenceTypeNo || ''
if (this.referenceTypeNo) {