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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
<template>
<view class="registration-records-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,8 +20,8 @@
</scroll-view>
<view v-if="providerUnavailable" class="empty-panel tn-padding-lg">
<view class="empty-title">{{ t('providerUnavailableTitle') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ t('providerUnavailableDesc') }}</view>
<view class="empty-title">{{ $tr('当前无法获取微信身份') }}</view>
<view class="empty-desc tn-margin-top-sm">{{ $tr('请在微信小程序中打开后,再查看当前微信账号提交的注册记录。') }}</view>
</view>
<view v-else-if="list.length">
@@ -33,7 +33,7 @@
>
<view class="card-head tn-flex tn-flex-row-between tn-flex-col-center">
<view class="card-title-wrap">
<view class="card-title">{{ item.name || item.account || t('registrationFallbackTitle') }}</view>
<view class="card-title">{{ item.name || item.account || $tr('人员注册申请') }}</view>
<view class="card-code tn-margin-top-xs">{{ item.code || '--' }}</view>
</view>
<view :class="['status-badge', 'status-' + resolveStatusType(item.approvalStatus)]">
@@ -43,50 +43,50 @@
<view class="card-body tn-margin-top">
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('accountLabel') }}</text>
<text class="info-label">{{ $tr('注册账号') }}</text>
<text class="info-value">{{ item.account || '--' }}</text>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('phoneLabel') }}</text>
<text class="info-label">{{ $tr('手机号') }}</text>
<text class="info-value">{{ item.phone || '--' }}</text>
</view>
<view class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('idNumberLabel') }}</text>
<text class="info-label">{{ $tr('证件号') }}</text>
<text class="info-value">{{ item.idCard || item.passportNumber || '--' }}</text>
</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.approveTime" class="info-row tn-flex tn-flex-row-between">
<text class="info-label">{{ t('approvedAtLabel') }}</text>
<text class="info-label">{{ $tr('审批时间') }}</text>
<text class="info-value">{{ formatDateTime(item.approveTime) }}</text>
</view>
<view v-if="item.approveReason" class="remark-panel tn-margin-top">
<view class="remark-label">{{ item.approvalStatus === -1 ? t('rejectReasonLabel') : t('approvalRemarkLabel') }}</view>
<view class="remark-label">{{ item.approvalStatus === -1 ? $tr('驳回原因') : $tr('审批意见') }}</view>
<view class="remark-text tn-margin-top-xs">{{ item.approveReason }}</view>
</view>
</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="goToRegister">
<text class="tn-text-bold">{{ t('goRegister') }}</text>
<text class="tn-text-bold">{{ $tr('去提交注册申请') }}</text>
</view>
</view>
<view v-if="loading && !list.length && !providerUnavailable" 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>
@@ -152,15 +152,12 @@ export default {
}
},
methods: {
t(key, params) {
return this.$tm('pkgAccount.registration_records', key, params)
},
syncStatusOptions() {
this.statusOptions = [
{ label: this.t('statusAll'), value: '' },
{ label: this.t('statusPending'), value: 0 },
{ label: this.t('statusApproved'), value: 1 },
{ label: this.t('statusRejected'), value: -1 }
{ label: this.$tr('全部'), value: '' },
{ label: this.$tr('审核中'), value: 0 },
{ label: this.$tr('已通过'), value: 1 },
{ label: this.$tr('已驳回'), value: -1 }
]
},
syncNavigationTitle() {
@@ -187,11 +184,11 @@ export default {
},
getStatusText(status) {
const map = {
0: this.t('statusPending'),
1: this.t('statusApproved'),
'-1': this.t('statusRejected')
0: this.$tr('审核中'),
1: this.$tr('已通过'),
'-1': this.$tr('已驳回')
}
return map[status] || this.t('statusProcessing')
return map[status] || this.$tr('处理中')
},
getDisplayStatusText(item = {}) {
if (this.language !== 'en' && item.approvalStatusDesc) {
@@ -301,7 +298,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'
})
})