feat: 新增工单中心、房间巡查、失物招领等业务模块
- 新增工单中心(ticket_center)与工单工作台(ticket_workbench)页面 - 新增房间巡查(room_inspection)与失物招领发布(lost_found_publish)页面 - 预约看样流程新增房型/楼栋/楼层选择器组件 - 新增工具模块: booking-entry、privacy、reservation-flow - api/index.js 补充楼层列表、看样提交等接口 - locales/messages.js 补充多语言文案 - manifest.json 开启微信小程序隐私检查 - 完善登录、注册、申请详情、账单等相关页面交互
This commit is contained in:
@@ -53,10 +53,12 @@
|
||||
<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')">
|
||||
{{ t('foreignIdentity') }}
|
||||
<view>{{ t('foreignIdentity') }}</view>
|
||||
<view v-if="language !== 'en'" class="role-tab-subtitle">{{ t('foreignIdentityHint') }}</view>
|
||||
</view>
|
||||
<view class="role-tab tn-flex-1" :class="form.identityType === 'DomesticStudents' ? 'role-active' : ''" @click="switchIdentityType('DomesticStudents')">
|
||||
{{ t('domesticIdentity') }}
|
||||
<view>{{ t('domesticIdentity') }}</view>
|
||||
<view v-if="language !== 'en'" class="role-tab-subtitle">{{ t('domesticIdentityHint') }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -112,6 +114,36 @@
|
||||
<input class="form-input" :placeholder="t('workUnitPlaceholder')" placeholder-class="input-placeholder" v-model="form.workUnit" />
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">{{ t('admissionNoticeAttachmentLabel') }}</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>
|
||||
</view>
|
||||
<view v-else class="attachment-file-card">
|
||||
<image
|
||||
class="attachment-preview-image"
|
||||
:src="getAttachmentPreviewUrl(form.admissionNoticeAttachment)"
|
||||
mode="aspectFill"
|
||||
@click.stop="previewAttachment('admissionNoticeAttachment')"
|
||||
/>
|
||||
<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>
|
||||
<view class="attachment-action-row">
|
||||
<view class="attachment-action-btn" @click.stop="previewAttachment('admissionNoticeAttachment')">
|
||||
{{ t('viewImage') }}
|
||||
</view>
|
||||
<view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('admissionNoticeAttachment')">
|
||||
{{ t('reupload') }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">{{ t('hangzhouPropertyLabel') }} <text class="required-mark">*</text></view>
|
||||
<view class="tn-flex tn-flex-wrap">
|
||||
@@ -120,6 +152,20 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">{{ t('idCardTypeLabel') }} <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>
|
||||
</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="tn-icon-right tn-color-gray"></text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<template v-if="form.identityType === 'DomesticStudents'">
|
||||
<view class="form-item">
|
||||
<view class="form-label">{{ t('idCardLabel') }} <text class="required-mark">*</text></view>
|
||||
@@ -189,12 +235,12 @@
|
||||
|
||||
<template v-else>
|
||||
<view class="form-item">
|
||||
<view class="form-label">{{ t('passportLabel') }} <text class="required-mark">*</text></view>
|
||||
<input class="form-input" :placeholder="t('passportPlaceholder')" placeholder-class="input-placeholder" v-model="form.passportNumber" />
|
||||
<view class="form-label">{{ foreignDocumentNumberLabel }} <text class="required-mark">*</text></view>
|
||||
<input class="form-input" :placeholder="foreignDocumentNumberPlaceholder" placeholder-class="input-placeholder" v-model="form.passportNumber" />
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">{{ t('passportExpiryLabel') }} <text class="required-mark">*</text></view>
|
||||
<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>
|
||||
@@ -209,11 +255,11 @@
|
||||
</view>
|
||||
|
||||
<view class="form-item" style="margin-bottom: 0;">
|
||||
<view class="form-label">{{ t('passportAttachmentLabel') }} <text class="required-mark">*</text></view>
|
||||
<view class="form-label">{{ foreignDocumentAttachmentLabel }} <text class="required-mark">*</text></view>
|
||||
<view v-if="!form.passportAttachment.serverValue" class="upload-box" @click="uploadAttachment('passportAttachment')">
|
||||
<view v-if="!form.passportAttachment.serverValue" class="upload-placeholder">
|
||||
<view class="upload-icon-wrap"><text class="tn-icon-upload"></text></view>
|
||||
<view class="tn-margin-top-sm upload-text">{{ t('uploadPassport') }}</view>
|
||||
<view class="tn-margin-top-sm upload-text">{{ foreignDocumentUploadText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="attachment-file-card">
|
||||
@@ -230,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') : t('passportImageOnlyTip') }}
|
||||
{{ isImageAttachment(form.passportAttachment) ? t('imageUploadedTip') : foreignDocumentImageOnlyTip }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="attachment-action-row">
|
||||
@@ -308,6 +354,30 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view v-if="showPrivacyPopup" class="privacy-mask">
|
||||
<view class="privacy-dialog">
|
||||
<view class="privacy-title">{{ t('privacyTitle') }}</view>
|
||||
<view class="privacy-content">
|
||||
{{ t('privacyContentPrefix') }}
|
||||
<text class="privacy-link" @click="openPrivacyContract">{{ t('privacyContractName') }}</text>
|
||||
{{ t('privacyContentSuffix') }}
|
||||
</view>
|
||||
<view class="privacy-action-row">
|
||||
<view class="privacy-action-btn" @click="handlePrivacyDisagree">{{ t('privacyDisagree') }}</view>
|
||||
<button
|
||||
id="register-privacy-agree-btn"
|
||||
class="privacy-action-btn is-primary"
|
||||
open-type="agreePrivacyAuthorization"
|
||||
@agreeprivacyauthorization="handleAgreePrivacyAuthorization"
|
||||
>
|
||||
{{ t('privacyAgree') }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -320,7 +390,8 @@ import {
|
||||
} from '../../utils/app-state'
|
||||
import { getCountryList, submitRegistrationApplication, sysdictPage, uploadCommonFile, registrationMyorder } from '@/api/index.js'
|
||||
import { addFilePrefix } from '@/utils/common.js'
|
||||
import { getGenderLabel } from '../../utils/enum-store'
|
||||
import { bindPrivacyAuthorizationListener, openWechatPrivacyContract, unbindPrivacyAuthorizationListener } from '@/utils/privacy.js'
|
||||
import { fetchEnums, getEnumOptions, getGenderLabel } from '../../utils/enum-store'
|
||||
import { resolveRegistrationApplicationCode } from '../../utils/flow'
|
||||
|
||||
function createAttachmentState() {
|
||||
@@ -342,6 +413,7 @@ function createDefaultForm() {
|
||||
gender: 1,
|
||||
phone: '',
|
||||
country: '',
|
||||
idCardType: 2,
|
||||
idCard: '',
|
||||
workUnit: '',
|
||||
hasHangzhouProperty: null,
|
||||
@@ -351,6 +423,7 @@ function createDefaultForm() {
|
||||
passportExpiry: '',
|
||||
school: '',
|
||||
religion: '',
|
||||
admissionNoticeAttachment: createAttachmentState(),
|
||||
passportAttachment: createAttachmentState(),
|
||||
idCardFrontAttachment: createAttachmentState(),
|
||||
idCardBackAttachment: createAttachmentState()
|
||||
@@ -374,7 +447,9 @@ function normalizeRegistrationDraftForm(draft) {
|
||||
...createDefaultForm(),
|
||||
...source,
|
||||
gender: source.gender === 0 || source.gender === 1 ? Number(source.gender) : 1,
|
||||
idCardType: Number(source.idCardType || source.IdCardType || 0) || 2,
|
||||
hasHangzhouProperty: typeof source.hasHangzhouProperty === 'boolean' ? source.hasHangzhouProperty : null,
|
||||
admissionNoticeAttachment: normalizeAttachmentDraft(source.admissionNoticeAttachment),
|
||||
passportAttachment: normalizeAttachmentDraft(source.passportAttachment),
|
||||
idCardFrontAttachment: normalizeAttachmentDraft(source.idCardFrontAttachment),
|
||||
idCardBackAttachment: normalizeAttachmentDraft(source.idCardBackAttachment)
|
||||
@@ -391,21 +466,27 @@ export default {
|
||||
draftPersistTimer: null,
|
||||
draftTipText: '',
|
||||
lastForeignCountry: '',
|
||||
lastForeignIdCardType: 2,
|
||||
showPrivacyPopup: false,
|
||||
resolvePrivacyAuthorization: null,
|
||||
form: createDefaultForm(),
|
||||
rawCountryList: [],
|
||||
rawHobbyList: [],
|
||||
rawDietaryHabitList: [],
|
||||
rawReligionList: [],
|
||||
rawIdCardTypeList: [],
|
||||
countryOptions: [],
|
||||
hobbyOptions: [],
|
||||
dietaryHabitOptions: [],
|
||||
religionOptions: [],
|
||||
idCardTypeOptions: [],
|
||||
i18n: {
|
||||
zh: {
|
||||
navTitle: '注册账号',
|
||||
pageTitle: '注册并提交实名资料',
|
||||
pageDesc: '账号信息和实名认证资料需要一起填写完成,提交的是注册申请单,审批通过后才算注册成功。',
|
||||
accountSection: '账号信息',
|
||||
admissionNoticeAttachmentLabel: '入学通知书(在职证明)附件',
|
||||
accountLabel: '登录账号',
|
||||
accountPlaceholder: '请输入登录账号',
|
||||
passwordLabel: '登录密码',
|
||||
@@ -416,7 +497,9 @@ export default {
|
||||
emailCodePlaceholder: '请输入邮箱验证码',
|
||||
basicSection: '基础实名信息',
|
||||
foreignIdentity: '外籍人员',
|
||||
foreignIdentityHint: 'Foreign',
|
||||
domesticIdentity: '中国籍',
|
||||
domesticIdentityHint: 'Chinese',
|
||||
nameLabel: '姓名',
|
||||
namePlaceholder: '请输入真实姓名',
|
||||
genderLabel: '性别',
|
||||
@@ -434,6 +517,9 @@ export default {
|
||||
hangzhouPropertyLabel: '杭州有无房产',
|
||||
yesText: '有',
|
||||
noText: '无',
|
||||
idCardTypeLabel: '证件类型',
|
||||
idCardTypePlaceholder: '请选择证件类型',
|
||||
idCardTypeFixedText: '中国籍固定',
|
||||
idCardLabel: '身份证号',
|
||||
idCardPlaceholder: '请输入身份证号',
|
||||
idCardFrontAttachmentLabel: '身份证正面附件',
|
||||
@@ -446,12 +532,18 @@ export default {
|
||||
passportLabel: '护照号',
|
||||
passportPlaceholder: '请输入护照号',
|
||||
passportExpiryLabel: '护照到期时间',
|
||||
documentNumberLabel: '证件号',
|
||||
documentNumberPlaceholder: '请输入证件号',
|
||||
documentExpiryLabel: '证件到期时间',
|
||||
selectDate: '请选择日期',
|
||||
schoolLabel: '就读学校',
|
||||
schoolPlaceholder: '请输入就读学校(选填)',
|
||||
passportAttachmentLabel: '护照附件',
|
||||
documentAttachmentLabel: '证件附件',
|
||||
uploadPassport: '点击上传护照图片',
|
||||
uploadDocument: '点击上传证件图片',
|
||||
passportImageOnlyTip: '护照附件仅支持图片,请重新上传',
|
||||
documentImageOnlyTip: '证件附件仅支持图片,请重新上传',
|
||||
preferenceSection: '偏好信息',
|
||||
dietaryLabel: '饮食习惯',
|
||||
hobbyLabel: '兴趣爱好',
|
||||
@@ -478,20 +570,32 @@ export default {
|
||||
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: '护照附件仅支持图片格式'
|
||||
@@ -501,6 +605,7 @@ export default {
|
||||
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',
|
||||
@@ -511,7 +616,9 @@ export default {
|
||||
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',
|
||||
@@ -529,6 +636,9 @@ export default {
|
||||
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',
|
||||
@@ -541,12 +651,18 @@ export default {
|
||||
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',
|
||||
@@ -573,20 +689,32 @@ export default {
|
||||
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'
|
||||
@@ -607,6 +735,9 @@ export default {
|
||||
countryRange() {
|
||||
return this.countryOptions.length ? this.countryOptions.map(item => item.value) : [this.t('noSelectableOptions')]
|
||||
},
|
||||
idCardTypeRange() {
|
||||
return this.idCardTypeOptions.length ? this.idCardTypeOptions.map(item => item.value) : [this.t('noSelectableOptions')]
|
||||
},
|
||||
dietaryHabitRange() {
|
||||
return this.dietaryHabitOptions.length ? this.dietaryHabitOptions.map(item => item.value) : [this.t('noSelectableOptions')]
|
||||
},
|
||||
@@ -616,6 +747,31 @@ export default {
|
||||
religionRange() {
|
||||
return this.religionOptions.length ? this.religionOptions.map(item => item.value) : [this.t('noSelectableOptions')]
|
||||
},
|
||||
selectedIdCardTypeLabel() {
|
||||
const matched = this.idCardTypeOptions.find(item => String(item.key) === String(this.form.idCardType))
|
||||
return matched ? matched.value : ''
|
||||
},
|
||||
isPassportType() {
|
||||
return String(this.form.idCardType) === '2'
|
||||
},
|
||||
foreignDocumentNumberLabel() {
|
||||
return this.isPassportType ? this.t('passportLabel') : this.t('documentNumberLabel')
|
||||
},
|
||||
foreignDocumentNumberPlaceholder() {
|
||||
return this.isPassportType ? this.t('passportPlaceholder') : this.t('documentNumberPlaceholder')
|
||||
},
|
||||
foreignDocumentExpiryLabel() {
|
||||
return this.isPassportType ? this.t('passportExpiryLabel') : this.t('documentExpiryLabel')
|
||||
},
|
||||
foreignDocumentAttachmentLabel() {
|
||||
return this.isPassportType ? this.t('passportAttachmentLabel') : this.t('documentAttachmentLabel')
|
||||
},
|
||||
foreignDocumentUploadText() {
|
||||
return this.isPassportType ? this.t('uploadPassport') : this.t('uploadDocument')
|
||||
},
|
||||
foreignDocumentImageOnlyTip() {
|
||||
return this.isPassportType ? this.t('passportImageOnlyTip') : this.t('documentImageOnlyTip')
|
||||
},
|
||||
genderOptions() {
|
||||
return [
|
||||
{ key: 1, value: this.t('genderMale') },
|
||||
@@ -637,6 +793,7 @@ export default {
|
||||
},
|
||||
async onLoad(options) {
|
||||
this.draftTipText = this.t('draftTipDefault')
|
||||
this.initPrivacyAuthorization()
|
||||
const bypass = options && options.bypass === '1'
|
||||
const hasRedirected = await this.checkPendingRegistration(bypass)
|
||||
if (hasRedirected) return
|
||||
@@ -645,7 +802,6 @@ export default {
|
||||
this.loadDictOptions(),
|
||||
this.loadCountryOptions()
|
||||
])
|
||||
|
||||
this.tryRestoreFailedDraft()
|
||||
},
|
||||
onHide() {
|
||||
@@ -654,6 +810,8 @@ export default {
|
||||
onUnload() {
|
||||
this.persistRegistrationDraft()
|
||||
this.clearDraftPersistTimer()
|
||||
this.resolvePrivacyAuthorization = null
|
||||
this.releasePrivacyAuthorization()
|
||||
},
|
||||
onPageScroll(e) {
|
||||
let opacity = e.scrollTop / 80
|
||||
@@ -733,6 +891,10 @@ export default {
|
||||
if (!this.isDomesticIdentity && this.form.country && !this.isChinaCountry(this.form.country)) {
|
||||
this.lastForeignCountry = this.form.country
|
||||
}
|
||||
if (!this.isDomesticIdentity && String(this.form.idCardType || '') !== '1') {
|
||||
this.lastForeignIdCardType = Number(this.form.idCardType) || 2
|
||||
}
|
||||
this.applyIdentityDocumentTypeRule()
|
||||
this.applyIdentityCountryRule()
|
||||
uni.showToast({
|
||||
title: this.t('restoreDraftSuccess'),
|
||||
@@ -760,6 +922,7 @@ export default {
|
||||
},
|
||||
hasDraftContent() {
|
||||
const attachmentFields = [
|
||||
'admissionNoticeAttachment',
|
||||
'passportAttachment',
|
||||
'idCardFrontAttachment',
|
||||
'idCardBackAttachment'
|
||||
@@ -820,6 +983,7 @@ export default {
|
||||
this.lastForeignCountry = this.form.country
|
||||
}
|
||||
this.form.identityType = type
|
||||
this.applyIdentityDocumentTypeRule()
|
||||
this.applyIdentityCountryRule()
|
||||
},
|
||||
onPassportDateChange(e) {
|
||||
@@ -846,15 +1010,19 @@ export default {
|
||||
return Promise.all([
|
||||
sysdictPage({ Type: 'UserHobbiesAndInterestsTag', PageIndex: 1, PageSize: 100 }, this.$getLanguage()),
|
||||
sysdictPage({ Type: 'UserDietaryHabitsTag', PageIndex: 1, PageSize: 100 }, this.$getLanguage()),
|
||||
sysdictPage({ Type: 'UserReligiousBeliefTag', PageIndex: 1, PageSize: 100 }, this.$getLanguage())
|
||||
sysdictPage({ Type: 'UserReligiousBeliefTag', PageIndex: 1, PageSize: 100 }, this.$getLanguage()),
|
||||
fetchEnums(false, this.$getLanguage())
|
||||
])
|
||||
.then(([hobbyRes, dietaryRes, religionRes]) => {
|
||||
this.rawHobbyList = this.extractResponseList(hobbyRes)
|
||||
this.rawDietaryHabitList = this.extractResponseList(dietaryRes)
|
||||
this.rawReligionList = this.extractResponseList(religionRes)
|
||||
this.rawIdCardTypeList = getEnumOptions('IdCardTypeEnum', this.$getLanguage())
|
||||
this.hobbyOptions = this.normalizeDictList(this.rawHobbyList)
|
||||
this.dietaryHabitOptions = this.normalizeDictList(this.rawDietaryHabitList)
|
||||
this.religionOptions = this.normalizeDictList(this.rawReligionList)
|
||||
this.idCardTypeOptions = this.normalizeDictList(this.rawIdCardTypeList)
|
||||
this.applyIdentityDocumentTypeRule()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('load dict options failed', error)
|
||||
@@ -933,13 +1101,27 @@ export default {
|
||||
this.hobbyOptions = this.normalizeDictList(this.rawHobbyList)
|
||||
this.dietaryHabitOptions = this.normalizeDictList(this.rawDietaryHabitList)
|
||||
this.religionOptions = this.normalizeDictList(this.rawReligionList)
|
||||
this.idCardTypeOptions = this.normalizeDictList(this.rawIdCardTypeList)
|
||||
this.form.country = this.remapSelectedValue(this.form.country, this.rawCountryList)
|
||||
this.lastForeignCountry = this.remapSelectedValue(this.lastForeignCountry, this.rawCountryList)
|
||||
this.form.hobby = this.remapSelectedValue(this.form.hobby, this.rawHobbyList)
|
||||
this.form.dietaryHabit = this.remapSelectedValue(this.form.dietaryHabit, this.rawDietaryHabitList)
|
||||
this.form.religion = this.remapSelectedValue(this.form.religion, this.rawReligionList)
|
||||
this.applyIdentityDocumentTypeRule()
|
||||
this.applyIdentityCountryRule()
|
||||
},
|
||||
applyIdentityDocumentTypeRule() {
|
||||
if (this.isDomesticIdentity) {
|
||||
if (String(this.form.idCardType || '') !== '1' && String(this.form.idCardType || '')) {
|
||||
this.lastForeignIdCardType = Number(this.form.idCardType) || 2
|
||||
}
|
||||
this.form.idCardType = 1
|
||||
return
|
||||
}
|
||||
if (!this.form.idCardType || String(this.form.idCardType) === '1') {
|
||||
this.form.idCardType = this.lastForeignIdCardType || 2
|
||||
}
|
||||
},
|
||||
getChinaCountryOption() {
|
||||
return this.countryOptions.find(item => String(item.key || '').toUpperCase() === 'CHN' || this.isChinaCountry(item.value)) || null
|
||||
},
|
||||
@@ -970,6 +1152,21 @@ export default {
|
||||
const selected = options[event.detail.value]
|
||||
this.form[field] = selected ? selected.value : ''
|
||||
},
|
||||
onIdCardTypeChange(event) {
|
||||
if (!this.idCardTypeOptions.length) {
|
||||
uni.showToast({
|
||||
title: this.t('noSelectableOptions'),
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
const selected = this.idCardTypeOptions[event.detail.value]
|
||||
const nextValue = selected ? Number(selected.key) : 0
|
||||
this.form.idCardType = nextValue || this.form.idCardType
|
||||
if (!this.isDomesticIdentity && nextValue && nextValue !== 1) {
|
||||
this.lastForeignIdCardType = nextValue
|
||||
}
|
||||
},
|
||||
onCountryChange(event) {
|
||||
if (this.isDomesticIdentity) return
|
||||
if (!this.countryOptions.length) {
|
||||
@@ -983,8 +1180,106 @@ export default {
|
||||
this.form.country = selected ? selected.value : ''
|
||||
this.lastForeignCountry = this.form.country || ''
|
||||
},
|
||||
initPrivacyAuthorization() {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (typeof wx === 'undefined' || !wx.onNeedPrivacyAuthorization) {
|
||||
return
|
||||
}
|
||||
this.handlePrivacyAuthorizationRequest = (resolve) => {
|
||||
this.resolvePrivacyAuthorization = resolve
|
||||
this.showPrivacyPopup = true
|
||||
}
|
||||
bindPrivacyAuthorizationListener(this.handlePrivacyAuthorizationRequest)
|
||||
// #endif
|
||||
},
|
||||
releasePrivacyAuthorization() {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (!this.handlePrivacyAuthorizationRequest) {
|
||||
return
|
||||
}
|
||||
unbindPrivacyAuthorizationListener(this.handlePrivacyAuthorizationRequest)
|
||||
this.handlePrivacyAuthorizationRequest = null
|
||||
this.showPrivacyPopup = false
|
||||
// #endif
|
||||
},
|
||||
ensurePrivacyAuthorized() {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (typeof wx === 'undefined' || !wx.requirePrivacyAuthorize) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const triggerPrivacyAuthorize = () => {
|
||||
wx.requirePrivacyAuthorize({
|
||||
success: () => {
|
||||
this.showPrivacyPopup = false
|
||||
resolve()
|
||||
},
|
||||
fail: (error) => {
|
||||
reject({
|
||||
...error,
|
||||
privacyDenied: true
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!wx.getPrivacySetting) {
|
||||
triggerPrivacyAuthorize()
|
||||
return
|
||||
}
|
||||
|
||||
wx.getPrivacySetting({
|
||||
success: (res) => {
|
||||
if (!res.needAuthorization) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
triggerPrivacyAuthorize()
|
||||
},
|
||||
fail: () => {
|
||||
triggerPrivacyAuthorize()
|
||||
}
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
return Promise.resolve()
|
||||
// #endif
|
||||
},
|
||||
openPrivacyContract() {
|
||||
// #ifdef MP-WEIXIN
|
||||
openWechatPrivacyContract().catch((error) => {
|
||||
console.error('open privacy contract failed', error)
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
handleAgreePrivacyAuthorization() {
|
||||
this.showPrivacyPopup = false
|
||||
if (!this.resolvePrivacyAuthorization) {
|
||||
return
|
||||
}
|
||||
const resolvePrivacyAuthorization = this.resolvePrivacyAuthorization
|
||||
this.resolvePrivacyAuthorization = null
|
||||
resolvePrivacyAuthorization({
|
||||
buttonId: 'register-privacy-agree-btn',
|
||||
event: 'agree'
|
||||
})
|
||||
},
|
||||
handlePrivacyDisagree() {
|
||||
this.showPrivacyPopup = false
|
||||
if (!this.resolvePrivacyAuthorization) {
|
||||
return
|
||||
}
|
||||
const resolvePrivacyAuthorization = this.resolvePrivacyAuthorization
|
||||
this.resolvePrivacyAuthorization = null
|
||||
resolvePrivacyAuthorization({
|
||||
event: 'disagree'
|
||||
})
|
||||
},
|
||||
uploadAttachment(field) {
|
||||
return this.chooseImageFile()
|
||||
return this.ensurePrivacyAuthorized()
|
||||
.then(() => this.chooseImageFile())
|
||||
.then((file) => {
|
||||
if (!file || !file.path) return
|
||||
uni.showLoading({
|
||||
@@ -1008,6 +1303,13 @@ export default {
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error && error.cancel) return
|
||||
if (error && error.privacyDenied) {
|
||||
uni.showToast({
|
||||
title: this.t('privacyAuthorizeFailed'),
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
console.error('upload attachment failed', error)
|
||||
uni.showToast({
|
||||
title: this.t('uploadFailed'),
|
||||
@@ -1103,8 +1405,6 @@ export default {
|
||||
uni.showLoading({
|
||||
title: this.t('submitting')
|
||||
})
|
||||
|
||||
const authProfile = this.buildAuthProfile()
|
||||
|
||||
return this.getWechatCode().then((providerValue) => {
|
||||
const submitPayload = this.buildSubmitPayload(providerValue)
|
||||
@@ -1179,6 +1479,7 @@ export default {
|
||||
if (field === 'passportAttachment') return 'passport-attachment'
|
||||
if (field === 'idCardFrontAttachment') return 'id-card-front'
|
||||
if (field === 'idCardBackAttachment') return 'id-card-back'
|
||||
if (field === 'admissionNoticeAttachment') return 'admission-notice-attachment'
|
||||
return this.getFileNameFromPath(filePath)
|
||||
},
|
||||
getFileNameFromPath(filePath = '') {
|
||||
@@ -1204,6 +1505,9 @@ export default {
|
||||
if (!this.form.workUnit || this.form.hasHangzhouProperty === null) {
|
||||
return this.t('validateProfile')
|
||||
}
|
||||
if (!this.form.idCardType) {
|
||||
return this.t('validateIdCardType')
|
||||
}
|
||||
|
||||
if (this.form.identityType === 'DomesticStudents') {
|
||||
if (!this.form.idCard) {
|
||||
@@ -1216,13 +1520,13 @@ export default {
|
||||
}
|
||||
|
||||
if (!this.form.passportNumber || !this.form.passportExpiry) {
|
||||
return this.t('validatePassportBasic')
|
||||
return this.isPassportType ? this.t('validatePassportBasic') : this.t('validateDocumentBasic')
|
||||
}
|
||||
if (!this.form.passportAttachment.serverValue) {
|
||||
return this.t('validatePassportAttachment')
|
||||
return this.isPassportType ? this.t('validatePassportAttachment') : this.t('validateDocumentAttachment')
|
||||
}
|
||||
if (!this.isImageAttachment(this.form.passportAttachment)) {
|
||||
return this.t('validatePassportAttachmentImage')
|
||||
return this.isPassportType ? this.t('validatePassportAttachmentImage') : this.t('validateDocumentAttachmentImage')
|
||||
}
|
||||
return ''
|
||||
},
|
||||
@@ -1246,6 +1550,8 @@ export default {
|
||||
genderText: getGenderLabel(this.form.gender, this.form.gender === 1 ? this.t('genderMale') : this.form.gender === 0 ? this.t('genderFemale') : ''),
|
||||
phone: this.form.phone,
|
||||
country: this.form.country,
|
||||
idCardType: Number(this.form.idCardType) || null,
|
||||
idCardTypeText: this.selectedIdCardTypeLabel || '',
|
||||
idCard: this.form.identityType === 'DomesticStudents' ? this.form.idCard : '',
|
||||
workUnit: this.form.workUnit,
|
||||
hasHangzhouProperty: this.form.hasHangzhouProperty,
|
||||
@@ -1260,6 +1566,7 @@ export default {
|
||||
school: this.form.identityType === 'ForeignStudents' ? this.form.school : '',
|
||||
religion: this.form.religion,
|
||||
religionTags: this.buildSingleTag(this.form.religion, this.religionOptions),
|
||||
admissionNoticeAttachment: this.form.admissionNoticeAttachment.serverValue || '',
|
||||
passportAttachment: this.form.identityType === 'ForeignStudents' ? this.form.passportAttachment.serverValue : '',
|
||||
idCardFrontAttachment: this.form.identityType === 'DomesticStudents' ? this.form.idCardFrontAttachment.serverValue : '',
|
||||
idCardBackAttachment: this.form.identityType === 'DomesticStudents' ? this.form.idCardBackAttachment.serverValue : ''
|
||||
@@ -1278,6 +1585,8 @@ export default {
|
||||
gender: profile.gender,
|
||||
phone: profile.phone,
|
||||
country: profile.country,
|
||||
idCardType: profile.idCardType || null,
|
||||
IdCardType: profile.idCardType || null,
|
||||
idCard: profile.idCard || null,
|
||||
workUnit: profile.workUnit,
|
||||
hasHangzhouProperty: profile.hasHangzhouProperty,
|
||||
@@ -1287,6 +1596,7 @@ export default {
|
||||
passportExpiry: profile.passportExpiry || null,
|
||||
school: profile.school || null,
|
||||
religionTags: profile.religionTags.length ? profile.religionTags : null,
|
||||
admissionNoticeAttachment: profile.admissionNoticeAttachment || null,
|
||||
passportAttachment: profile.passportAttachment || null,
|
||||
idCardFrontAttachment: profile.idCardFrontAttachment || null,
|
||||
idCardBackAttachment: profile.idCardBackAttachment || null
|
||||
@@ -1469,11 +1779,21 @@ export default {
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.role-tab-subtitle {
|
||||
margin-top: 6rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: #8A99B3;
|
||||
}
|
||||
.role-active {
|
||||
background-color: #FFFFFF;
|
||||
color: $brand-primary;
|
||||
box-shadow: 0 6rpx 20rpx rgba($brand-primary, 0.15);
|
||||
}
|
||||
.role-active .role-tab-subtitle {
|
||||
color: rgba($brand-primary, 0.82);
|
||||
}
|
||||
|
||||
/* 性别按钮 */
|
||||
.gender-btn {
|
||||
@@ -1736,4 +2056,84 @@ export default {
|
||||
.login-link:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.privacy-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 999;
|
||||
background: rgba(17, 24, 39, 0.56);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.privacy-dialog {
|
||||
width: 100%;
|
||||
max-width: 640rpx;
|
||||
background: #FFFFFF;
|
||||
border-radius: 28rpx;
|
||||
padding: 40rpx 32rpx 32rpx;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 24rpx 80rpx rgba(15, 23, 42, 0.16);
|
||||
}
|
||||
|
||||
.privacy-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #1A2233;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.privacy-content {
|
||||
margin-top: 24rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.7;
|
||||
color: #5C6B8A;
|
||||
}
|
||||
|
||||
.privacy-link {
|
||||
color: $brand-primary;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.privacy-action-row {
|
||||
display: flex;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.privacy-action-btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: $brand-primary;
|
||||
background: rgba($brand-primary, 0.08);
|
||||
border: 1rpx solid rgba($brand-primary, 0.12);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.privacy-action-btn + .privacy-action-btn {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
button.privacy-action-btn {
|
||||
padding: 0;
|
||||
line-height: 88rpx;
|
||||
}
|
||||
|
||||
button.privacy-action-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.privacy-action-btn.is-primary {
|
||||
color: #FFFFFF;
|
||||
background: linear-gradient(135deg, rgba($brand-primary, 0.92) 0%, $brand-primary 100%);
|
||||
border-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user