将组件级 i18n 对象重构为统一由全局 locales/messages.js 管理,确保中英文语言切换时正确显示对应文本。 - 新增 utils/i18n.js 国际化工具函数 - 更新 store/$t.mixin.js 翻译混入逻辑 - 迁移 pkgService、pkgBooking、pkgAccount、pkgExplore、pages 等共 58 个组件的中英文文本至全局消息表
1897 lines
64 KiB
Vue
1897 lines
64 KiB
Vue
<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">{{ $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">{{ $tr('注册并提交实名资料') }}</view>
|
||
<view class="tn-color-gray tn-text-sm page-desc">
|
||
{{ $tr('账号信息和实名认证资料需要一起填写完成,提交的是注册申请单,审批通过后才算注册成功。') }}
|
||
</view>
|
||
<view class="draft-tip-bar tn-margin-top-sm">
|
||
<text class="tn-icon-safe-fill draft-tip-icon"></text>
|
||
<text class="draft-tip-text">{{ draftTipText }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<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>
|
||
{{ $tr('账号信息') }}
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<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">{{ $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">{{ $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">{{ $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>
|
||
{{ $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>{{ $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>{{ $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">{{ $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">{{ $tr('性别') }} <text class="required-mark">*</text></view>
|
||
<view class="tn-flex">
|
||
<view
|
||
v-for="(item, index) in genderOptions"
|
||
:key="item.key"
|
||
class="gender-btn tn-flex-1"
|
||
:class="[form.gender === Number(item.key) ? 'gender-active' : '', index > 0 ? 'tn-margin-left-sm' : '']"
|
||
@click="form.gender = Number(item.key)"
|
||
>
|
||
{{ item.value }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<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">{{ $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 || $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 || $tr('请选择国籍') }}</text>
|
||
<text class="tn-icon-right tn-color-gray"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
|
||
<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>
|
||
{{ $tr('认证档案') }}
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<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">{{ $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">{{ $tr('点击上传入学通知书/在职证明') }}</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">{{ $tr('已上传图片,可点击查看大图') }}</view>
|
||
</view>
|
||
<view class="attachment-action-row">
|
||
<view class="attachment-action-btn" @click.stop="previewAttachment('admissionNoticeAttachment')">
|
||
{{ $tr('查看图片') }}
|
||
</view>
|
||
<view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('admissionNoticeAttachment')">
|
||
{{ $tr('重新上传') }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<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">{{ $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">{{ $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">{{ $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 || $tr('请选择证件类型') }}</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">{{ $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">{{ $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">{{ $tr('点击上传身份证正面') }}</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="attachment-file-card">
|
||
<image
|
||
class="attachment-preview-image"
|
||
:src="getAttachmentPreviewUrl(form.idCardFrontAttachment)"
|
||
mode="aspectFill"
|
||
@click.stop="previewAttachment('idCardFrontAttachment')"
|
||
/>
|
||
<view class="attachment-file-info">
|
||
<view class="attachment-name">{{ form.idCardFrontAttachment.name }}</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')">
|
||
{{ $tr('查看图片') }}
|
||
</view>
|
||
<view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('idCardFrontAttachment')">
|
||
{{ $tr('重新上传') }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item" style="margin-bottom: 0;">
|
||
<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">{{ $tr('点击上传身份证反面') }}</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="attachment-file-card">
|
||
<image
|
||
class="attachment-preview-image"
|
||
:src="getAttachmentPreviewUrl(form.idCardBackAttachment)"
|
||
mode="aspectFill"
|
||
@click.stop="previewAttachment('idCardBackAttachment')"
|
||
/>
|
||
<view class="attachment-file-info">
|
||
<view class="attachment-name">{{ form.idCardBackAttachment.name }}</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')">
|
||
{{ $tr('查看图片') }}
|
||
</view>
|
||
<view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('idCardBackAttachment')">
|
||
{{ $tr('重新上传') }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<template v-else>
|
||
<view class="form-item">
|
||
<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">{{ 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 || $tr('请选择日期') }}</text>
|
||
<text class="tn-icon-calendar tn-color-gray"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<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;">
|
||
<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">{{ foreignDocumentUploadText }}</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="attachment-file-card">
|
||
<image
|
||
v-if="isImageAttachment(form.passportAttachment)"
|
||
class="attachment-preview-image"
|
||
:src="getAttachmentPreviewUrl(form.passportAttachment)"
|
||
mode="aspectFill"
|
||
@click.stop="previewAttachment('passportAttachment')"
|
||
/>
|
||
<view v-else class="attachment-file-cover is-unsupported">
|
||
<text class="attachment-file-ext">FILE</text>
|
||
</view>
|
||
<view class="attachment-file-info">
|
||
<view class="attachment-name">{{ form.passportAttachment.name }}</view>
|
||
<view class="attachment-type tn-margin-top-xs">
|
||
{{ isImageAttachment(form.passportAttachment) ? $tr('已上传图片,可点击查看大图') : foreignDocumentImageOnlyTip }}
|
||
</view>
|
||
</view>
|
||
<view class="attachment-action-row">
|
||
<view
|
||
v-if="isImageAttachment(form.passportAttachment)"
|
||
class="attachment-action-btn"
|
||
@click.stop="previewAttachment('passportAttachment')"
|
||
>
|
||
{{ $tr('查看图片') }}
|
||
</view>
|
||
<view class="attachment-action-btn is-primary" @click.stop="uploadAttachment('passportAttachment')">
|
||
{{ $tr('重新上传') }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
|
||
<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>
|
||
{{ $tr('偏好信息') }}
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<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 || $tr('请选择饮食习惯') }}</text>
|
||
<text class="tn-icon-right tn-color-gray"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<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 || $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">{{ $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 || $tr('请选择宗教信仰') }}</text>
|
||
<text class="tn-icon-right tn-color-gray"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
|
||
<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 ? $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">{{ $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">{{ $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>
|
||
|
||
<!-- #ifdef MP-WEIXIN -->
|
||
<view v-if="showPrivacyPopup" class="privacy-mask">
|
||
<view class="privacy-dialog">
|
||
<view class="privacy-title">{{ $tr('隐私授权提示') }}</view>
|
||
<view class="privacy-content">
|
||
{{ $tr('上传证件图片前,请先阅读并同意') }}
|
||
<text class="privacy-link" @click="openPrivacyContract">{{ $tr('《用户隐私保护指引》') }}</text>
|
||
{{ $tr(',同意后才能继续选择图片。') }}
|
||
</view>
|
||
<view class="privacy-action-row">
|
||
<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"
|
||
>
|
||
{{ $tr('同意并继续') }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- #endif -->
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
clearFailedRegistrationDraft,
|
||
clearLoginSession,
|
||
getFailedRegistrationDraft,
|
||
saveFailedRegistrationDraft
|
||
} from '../../utils/app-state'
|
||
import { getCountryList, submitRegistrationApplication, sysdictPage, uploadCommonFile, registrationMyorder } from '@/api/index.js'
|
||
import { addFilePrefix } from '@/utils/common.js'
|
||
import { bindPrivacyAuthorizationListener, openWechatPrivacyContract, unbindPrivacyAuthorizationListener } from '@/utils/privacy.js'
|
||
import { fetchEnums, getEnumOptions, getGenderLabel } from '../../utils/enum-store'
|
||
import { resolveRegistrationApplicationCode } from '../../utils/flow'
|
||
|
||
function createAttachmentState() {
|
||
return {
|
||
name: '',
|
||
serverValue: '',
|
||
localPath: ''
|
||
}
|
||
}
|
||
|
||
function createDefaultForm() {
|
||
return {
|
||
identityType: 'ForeignStudents',
|
||
account: '',
|
||
password: '',
|
||
email: '',
|
||
emailVerifyCode: '',
|
||
name: '',
|
||
gender: 1,
|
||
phone: '',
|
||
country: '',
|
||
idCardType: 2,
|
||
idCard: '',
|
||
workUnit: '',
|
||
hasHangzhouProperty: null,
|
||
dietaryHabit: '',
|
||
hobby: '',
|
||
passportNumber: '',
|
||
passportExpiry: '',
|
||
school: '',
|
||
religion: '',
|
||
admissionNoticeAttachment: createAttachmentState(),
|
||
passportAttachment: createAttachmentState(),
|
||
idCardFrontAttachment: createAttachmentState(),
|
||
idCardBackAttachment: createAttachmentState()
|
||
}
|
||
}
|
||
|
||
function normalizeAttachmentDraft(attachment) {
|
||
const source = attachment && typeof attachment === 'object' ? attachment : {}
|
||
return {
|
||
...createAttachmentState(),
|
||
name: source.name || '',
|
||
serverValue: source.serverValue || '',
|
||
localPath: source.localPath || ''
|
||
}
|
||
}
|
||
|
||
function normalizeRegistrationDraftForm(draft) {
|
||
const draftSource = draft && typeof draft === 'object' ? draft : {}
|
||
const source = draftSource.form && typeof draftSource.form === 'object' ? draftSource.form : draftSource
|
||
return {
|
||
...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)
|
||
}
|
||
}
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
navBgOpacity: 0,
|
||
isSubmitting: false,
|
||
dictLoading: false,
|
||
hasPromptedFailedDraft: false,
|
||
draftPersistTimer: null,
|
||
draftTipText: '',
|
||
lastForeignCountry: '',
|
||
lastForeignIdCardType: 2,
|
||
showPrivacyPopup: false,
|
||
resolvePrivacyAuthorization: null,
|
||
form: createDefaultForm(),
|
||
rawCountryList: [],
|
||
rawHobbyList: [],
|
||
rawDietaryHabitList: [],
|
||
rawReligionList: [],
|
||
rawIdCardTypeList: [],
|
||
countryOptions: [],
|
||
hobbyOptions: [],
|
||
dietaryHabitOptions: [],
|
||
religionOptions: [],
|
||
idCardTypeOptions: []
|
||
}
|
||
},
|
||
computed: {
|
||
language() {
|
||
return this.$getLanguage()
|
||
},
|
||
navBackgroundColor() {
|
||
return `rgba(255, 255, 255, ${this.navBgOpacity})`
|
||
},
|
||
hobbyRange() {
|
||
return this.hobbyOptions.length ? this.hobbyOptions.map(item => item.value) : [this.$tr('当前暂无可选项')]
|
||
},
|
||
countryRange() {
|
||
return this.countryOptions.length ? this.countryOptions.map(item => item.value) : [this.$tr('当前暂无可选项')]
|
||
},
|
||
idCardTypeRange() {
|
||
return this.idCardTypeOptions.length ? this.idCardTypeOptions.map(item => item.value) : [this.$tr('当前暂无可选项')]
|
||
},
|
||
dietaryHabitRange() {
|
||
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.$tr('当前暂无可选项')]
|
||
},
|
||
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.$tr('护照号') : this.$tr('证件号')
|
||
},
|
||
foreignDocumentNumberPlaceholder() {
|
||
return this.isPassportType ? this.$tr('请输入护照号') : this.$tr('请输入证件号')
|
||
},
|
||
foreignDocumentExpiryLabel() {
|
||
return this.isPassportType ? this.$tr('护照到期时间') : this.$tr('证件到期时间')
|
||
},
|
||
foreignDocumentAttachmentLabel() {
|
||
return this.isPassportType ? this.$tr('护照附件') : this.$tr('证件附件')
|
||
},
|
||
foreignDocumentUploadText() {
|
||
return this.isPassportType ? this.$tr('点击上传护照图片') : this.$tr('点击上传证件图片')
|
||
},
|
||
foreignDocumentImageOnlyTip() {
|
||
return this.isPassportType ? this.$tr('护照附件仅支持图片,请重新上传') : this.$tr('证件附件仅支持图片,请重新上传')
|
||
},
|
||
genderOptions() {
|
||
return [
|
||
{ key: 1, value: this.$tr('男') },
|
||
{ key: 0, value: this.$tr('女') }
|
||
]
|
||
}
|
||
},
|
||
watch: {
|
||
language() {
|
||
this.rebuildLocalizedOptions()
|
||
this.draftTipText = this.$tr('未完成内容会自动保存,可随时返回继续填写')
|
||
},
|
||
form: {
|
||
deep: true,
|
||
handler() {
|
||
this.scheduleDraftPersist()
|
||
}
|
||
}
|
||
},
|
||
async onLoad(options) {
|
||
this.draftTipText = this.$tr('未完成内容会自动保存,可随时返回继续填写')
|
||
this.initPrivacyAuthorization()
|
||
const bypass = options && options.bypass === '1'
|
||
const hasRedirected = await this.checkPendingRegistration(bypass)
|
||
if (hasRedirected) return
|
||
|
||
await Promise.all([
|
||
this.loadDictOptions(),
|
||
this.loadCountryOptions()
|
||
])
|
||
this.tryRestoreFailedDraft()
|
||
},
|
||
onHide() {
|
||
this.persistRegistrationDraft()
|
||
},
|
||
onUnload() {
|
||
this.persistRegistrationDraft()
|
||
this.clearDraftPersistTimer()
|
||
this.resolvePrivacyAuthorization = null
|
||
this.releasePrivacyAuthorization()
|
||
},
|
||
onPageScroll(e) {
|
||
let opacity = e.scrollTop / 80
|
||
if (opacity > 1) {
|
||
opacity = 1
|
||
} else if (opacity < 0) {
|
||
opacity = 0
|
||
}
|
||
this.navBgOpacity = opacity
|
||
},
|
||
methods: {
|
||
isChinaCountry(value = '') {
|
||
const text = String(value || '').trim()
|
||
return text === '中国' || text === 'China' || text === this.$tr('中国')
|
||
},
|
||
async checkPendingRegistration(bypassRedirect = false) {
|
||
uni.showLoading({ title: this.$tr('加载中...') })
|
||
try {
|
||
const providerValue = await this.getWechatCode()
|
||
if (!providerValue) return false
|
||
const response = await registrationMyorder({ providerValue }, this.language)
|
||
const rawLatest = response && response.data && typeof response.data === 'object' ? response.data : response
|
||
const latest = rawLatest.data !== undefined ? rawLatest.data : rawLatest
|
||
|
||
if (latest && typeof latest === 'object' && Object.keys(latest).length > 0) {
|
||
|
||
if (!bypassRedirect) {
|
||
uni.redirectTo({ url: '/pkgAccount/auth_result/auth_result' })
|
||
return true
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('check pending registration failed', e)
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
return false
|
||
},
|
||
goToLogin() {
|
||
uni.navigateBack()
|
||
},
|
||
goToRegistrationRecords() {
|
||
uni.navigateTo({
|
||
url: '/pkgAccount/registration_records/registration_records'
|
||
})
|
||
},
|
||
tryRestoreFailedDraft() {
|
||
if (this.hasPromptedFailedDraft) {
|
||
return
|
||
}
|
||
|
||
const cachedDraft = getFailedRegistrationDraft()
|
||
if (!cachedDraft || typeof cachedDraft !== 'object') {
|
||
return
|
||
}
|
||
|
||
this.hasPromptedFailedDraft = true
|
||
uni.showModal({
|
||
title: this.$tr('发现未完成的数据'),
|
||
content: this.$tr('是否填入上次未完成或提交失败时缓存的注册信息?'),
|
||
confirmText: this.$tr('填入'),
|
||
cancelText: this.$tr('不用'),
|
||
success: ({ confirm }) => {
|
||
if (confirm) {
|
||
this.applyFailedDraft(cachedDraft)
|
||
return
|
||
}
|
||
clearFailedRegistrationDraft()
|
||
}
|
||
})
|
||
},
|
||
applyFailedDraft(draft) {
|
||
this.form = normalizeRegistrationDraftForm(draft)
|
||
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.$tr('已填入上次失败数据'),
|
||
icon: 'success'
|
||
})
|
||
},
|
||
buildFailedDraft() {
|
||
return normalizeRegistrationDraftForm(this.form)
|
||
},
|
||
scheduleDraftPersist() {
|
||
if (this.isSubmitting) {
|
||
return
|
||
}
|
||
this.clearDraftPersistTimer()
|
||
this.draftPersistTimer = setTimeout(() => {
|
||
this.persistRegistrationDraft(true)
|
||
}, 600)
|
||
},
|
||
clearDraftPersistTimer() {
|
||
if (!this.draftPersistTimer) {
|
||
return
|
||
}
|
||
clearTimeout(this.draftPersistTimer)
|
||
this.draftPersistTimer = null
|
||
},
|
||
hasDraftContent() {
|
||
const attachmentFields = [
|
||
'admissionNoticeAttachment',
|
||
'passportAttachment',
|
||
'idCardFrontAttachment',
|
||
'idCardBackAttachment'
|
||
]
|
||
const textFields = [
|
||
'account',
|
||
'password',
|
||
'email',
|
||
'emailVerifyCode',
|
||
'name',
|
||
'phone',
|
||
'country',
|
||
'idCard',
|
||
'workUnit',
|
||
'dietaryHabit',
|
||
'hobby',
|
||
'passportNumber',
|
||
'passportExpiry',
|
||
'school',
|
||
'religion'
|
||
]
|
||
|
||
if (this.form.identityType !== 'ForeignStudents') {
|
||
return true
|
||
}
|
||
if (this.form.gender !== null || this.form.hasHangzhouProperty !== null) {
|
||
return true
|
||
}
|
||
if (textFields.some(field => String(this.form[field] || '').trim())) {
|
||
return true
|
||
}
|
||
return attachmentFields.some((field) => {
|
||
const attachment = this.form[field]
|
||
return attachment && (attachment.serverValue || attachment.localPath || attachment.name)
|
||
})
|
||
},
|
||
persistRegistrationDraft(isSilent = false) {
|
||
if (this.isSubmitting) {
|
||
return
|
||
}
|
||
this.clearDraftPersistTimer()
|
||
if (!this.hasDraftContent()) {
|
||
clearFailedRegistrationDraft()
|
||
this.draftTipText = this.$tr('未完成内容会自动保存,可随时返回继续填写')
|
||
return
|
||
}
|
||
saveFailedRegistrationDraft({
|
||
form: this.buildFailedDraft(),
|
||
updatedAt: Date.now()
|
||
})
|
||
if (!isSilent) {
|
||
this.draftTipText = this.$tr('当前填写内容已自动保存')
|
||
}
|
||
},
|
||
switchIdentityType(type) {
|
||
if (this.form.identityType === type) return
|
||
if (type === 'DomesticStudents' && this.form.country) {
|
||
this.lastForeignCountry = this.form.country
|
||
}
|
||
this.form.identityType = type
|
||
this.applyIdentityDocumentTypeRule()
|
||
this.applyIdentityCountryRule()
|
||
},
|
||
onPassportDateChange(e) {
|
||
this.form.passportExpiry = e.detail.value
|
||
},
|
||
loadCountryOptions() {
|
||
return getCountryList(this.language)
|
||
.then((countryRes) => {
|
||
const rawList = this.extractResponseList(countryRes)
|
||
this.rawCountryList = rawList
|
||
this.countryOptions = this.normalizeCountryList(rawList)
|
||
this.applyIdentityCountryRule()
|
||
})
|
||
.catch((error) => {
|
||
console.error('load country options failed', error)
|
||
uni.showToast({
|
||
title: this.$tr('国家列表加载失败'),
|
||
icon: 'none'
|
||
})
|
||
})
|
||
},
|
||
loadDictOptions() {
|
||
this.dictLoading = true
|
||
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()),
|
||
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)
|
||
uni.showToast({
|
||
title: this.$tr('字典加载失败,稍后可重试'),
|
||
icon: 'none'
|
||
})
|
||
})
|
||
.finally(() => {
|
||
this.dictLoading = false
|
||
})
|
||
},
|
||
extractResponseList(response) {
|
||
const rawList = response && response.data
|
||
? (response.data.data || response.data)
|
||
: (response && response.data ? response.data : response)
|
||
return Array.isArray(rawList) ? rawList : []
|
||
},
|
||
getOptionTextCandidates(item = {}) {
|
||
return [
|
||
item.value,
|
||
item.label,
|
||
item.description,
|
||
item.name,
|
||
item.cname,
|
||
item.nameZh,
|
||
item.labelZh,
|
||
item.valueZh,
|
||
item.descriptionZh,
|
||
item.ename,
|
||
item.nameEn,
|
||
item.labelEn,
|
||
item.valueEn,
|
||
item.descriptionEn,
|
||
item.code
|
||
].map(value => String(value || '').trim()).filter(Boolean)
|
||
},
|
||
getLocalizedOptionText(item = {}) {
|
||
const isEn = this.language === 'en'
|
||
const preferred = isEn
|
||
? [item.descriptionEn, item.ename, item.nameEn, item.labelEn, item.valueEn, item.description, item.value, item.label, item.name, item.cname, item.code]
|
||
: [item.description, item.cname, item.nameZh, item.labelZh, item.valueZh, item.value, item.label, item.name, item.code]
|
||
const matched = preferred.map(value => String(value || '').trim()).find(Boolean)
|
||
return matched || ''
|
||
},
|
||
normalizeDictList(rawList = []) {
|
||
const list = Array.isArray(rawList) ? rawList : []
|
||
return list
|
||
.map(item => ({
|
||
key: item.key || item.code || '',
|
||
value: this.getLocalizedOptionText(item)
|
||
}))
|
||
.filter(item => item.value)
|
||
},
|
||
normalizeCountryList(rawList = []) {
|
||
const list = Array.isArray(rawList) ? rawList : []
|
||
return list
|
||
.map(item => ({
|
||
key: item.code || item.key || item.cname || '',
|
||
value: this.getLocalizedOptionText(item)
|
||
}))
|
||
.filter(item => item.value)
|
||
},
|
||
remapSelectedValue(currentValue, rawList = []) {
|
||
const normalizedCurrent = String(currentValue || '').trim()
|
||
if (!normalizedCurrent) {
|
||
return ''
|
||
}
|
||
const matched = (Array.isArray(rawList) ? rawList : []).find((item) => {
|
||
return this.getOptionTextCandidates(item).includes(normalizedCurrent)
|
||
})
|
||
return matched ? this.getLocalizedOptionText(matched) : normalizedCurrent
|
||
},
|
||
rebuildLocalizedOptions() {
|
||
this.countryOptions = this.normalizeCountryList(this.rawCountryList)
|
||
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
|
||
},
|
||
applyIdentityCountryRule() {
|
||
if (this.isDomesticIdentity) {
|
||
const chinaOption = this.getChinaCountryOption()
|
||
this.form.country = chinaOption ? chinaOption.value : this.$tr('中国')
|
||
return
|
||
}
|
||
if (this.isChinaCountry(this.form.country)) {
|
||
this.form.country = this.lastForeignCountry || ''
|
||
}
|
||
},
|
||
onDictSelect(field, event) {
|
||
const map = {
|
||
dietaryHabit: this.dietaryHabitOptions,
|
||
hobby: this.hobbyOptions,
|
||
religion: this.religionOptions
|
||
}
|
||
const options = map[field] || []
|
||
if (!options.length) {
|
||
uni.showToast({
|
||
title: this.$tr('当前暂无可选项'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const selected = options[event.detail.value]
|
||
this.form[field] = selected ? selected.value : ''
|
||
},
|
||
onIdCardTypeChange(event) {
|
||
if (!this.idCardTypeOptions.length) {
|
||
uni.showToast({
|
||
title: this.$tr('当前暂无可选项'),
|
||
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) {
|
||
uni.showToast({
|
||
title: this.$tr('当前暂无可选国家'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const selected = this.countryOptions[event.detail.value]
|
||
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.ensurePrivacyAuthorized()
|
||
.then(() => this.chooseImageFile())
|
||
.then((file) => {
|
||
if (!file || !file.path) return
|
||
uni.showLoading({
|
||
title: this.$tr('上传中...')
|
||
})
|
||
|
||
return uploadCommonFile(file.path).then((uploadResult) => {
|
||
const serverValue = this.resolveUploadedValue(uploadResult, file.path)
|
||
|
||
this.form[field] = {
|
||
name: file.name || this.getDefaultAttachmentName(field, file.path),
|
||
serverValue,
|
||
localPath: file.path
|
||
}
|
||
|
||
uni.showToast({
|
||
title: this.$tr('上传成功'),
|
||
icon: 'success'
|
||
})
|
||
})
|
||
})
|
||
.catch((error) => {
|
||
if (error && error.cancel) return
|
||
if (error && error.privacyDenied) {
|
||
uni.showToast({
|
||
title: this.$tr('未完成隐私授权,暂时无法选择图片'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
console.error('upload attachment failed', error)
|
||
uni.showToast({
|
||
title: this.$tr('上传失败,请重试'),
|
||
icon: 'none'
|
||
})
|
||
})
|
||
.finally(() => {
|
||
uni.hideLoading()
|
||
})
|
||
},
|
||
chooseImageFile() {
|
||
return new Promise((resolve, reject) => {
|
||
uni.chooseImage({
|
||
count: 1,
|
||
sizeType: ['compressed'],
|
||
success: (res) => {
|
||
const filePath = res.tempFilePaths && res.tempFilePaths[0]
|
||
const tempFile = res.tempFiles && res.tempFiles[0]
|
||
resolve({
|
||
path: filePath,
|
||
name: tempFile && tempFile.name ? tempFile.name : this.getFileNameFromPath(filePath)
|
||
})
|
||
},
|
||
fail: (error) => {
|
||
if (error && /cancel/i.test(JSON.stringify(error))) {
|
||
reject({ cancel: true })
|
||
return
|
||
}
|
||
reject(error)
|
||
}
|
||
})
|
||
})
|
||
},
|
||
previewAttachment(field) {
|
||
const attachment = this.form[field]
|
||
const previewUrl = this.getAttachmentPreviewUrl(attachment)
|
||
if (!previewUrl || !this.isImageAttachment(attachment)) {
|
||
uni.showToast({
|
||
title: this.$tr('当前附件不是图片'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
uni.previewImage({
|
||
urls: [previewUrl],
|
||
current: previewUrl
|
||
})
|
||
},
|
||
getAttachmentPreviewUrl(attachment) {
|
||
if (!attachment) return ''
|
||
const candidates = [attachment.serverValue, attachment.localPath]
|
||
for (const item of candidates) {
|
||
const value = String(item || '').trim()
|
||
if (!value) continue
|
||
if (this.isLocalFileUrl(value)) {
|
||
return value
|
||
}
|
||
return addFilePrefix(value)
|
||
}
|
||
return ''
|
||
},
|
||
isImageAttachment(attachment) {
|
||
return ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'].includes(this.getAttachmentExtension(attachment))
|
||
},
|
||
isLocalFileUrl(url = '') {
|
||
return /^(wxfile:\/\/|file:\/\/|blob:|data:image|_doc\/|\/tmp\/)/i.test(String(url || '').trim())
|
||
},
|
||
getAttachmentExtension(attachment) {
|
||
const candidates = [
|
||
attachment && attachment.name,
|
||
attachment && attachment.serverValue,
|
||
attachment && attachment.localPath
|
||
]
|
||
const matched = candidates.find(item => String(item || '').trim())
|
||
if (!matched) return ''
|
||
const cleanValue = String(matched).split('?')[0].split('#')[0]
|
||
const segments = cleanValue.split('.')
|
||
return segments.length > 1 ? segments.pop().toLowerCase() : ''
|
||
},
|
||
submitForm() {
|
||
if (this.isSubmitting) return
|
||
|
||
const errorMessage = this.validateForm()
|
||
if (errorMessage) {
|
||
uni.showToast({
|
||
title: errorMessage,
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
this.isSubmitting = true
|
||
uni.showLoading({
|
||
title: this.$tr('提交中...')
|
||
})
|
||
|
||
return this.getWechatCode().then((providerValue) => {
|
||
const submitPayload = this.buildSubmitPayload(providerValue)
|
||
return submitRegistrationApplication(submitPayload)
|
||
.then((response) => {
|
||
const registrationCode = resolveRegistrationApplicationCode(response)
|
||
|
||
clearLoginSession()
|
||
clearFailedRegistrationDraft()
|
||
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: this.$tr('申请已提交,待审批'),
|
||
icon: 'success'
|
||
})
|
||
|
||
setTimeout(() => {
|
||
uni.redirectTo({
|
||
url: '/pkgAccount/auth_result/auth_result'
|
||
})
|
||
}, 800)
|
||
})
|
||
.catch((error) => {
|
||
console.error('submit registration application failed', error)
|
||
saveFailedRegistrationDraft(this.buildFailedDraft())
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: (error && (error.message || error.msg)) || this.$tr('提交失败,请稍后重试'),
|
||
icon: 'none'
|
||
})
|
||
})
|
||
.finally(() => {
|
||
this.isSubmitting = false
|
||
})
|
||
})
|
||
},
|
||
getWechatCode() {
|
||
return new Promise((resolve) => {
|
||
// #ifdef MP-WEIXIN
|
||
uni.login({
|
||
provider: 'weixin',
|
||
success: (res) => {
|
||
resolve(res.code || '')
|
||
},
|
||
fail: (err) => {
|
||
console.error('getWechatCode failed', err)
|
||
resolve('')
|
||
}
|
||
})
|
||
// #endif
|
||
// #ifndef MP-WEIXIN
|
||
resolve('')
|
||
// #endif
|
||
})
|
||
},
|
||
resolveUploadedValue(uploadResult, fallback) {
|
||
const candidates = [
|
||
uploadResult && uploadResult.data && uploadResult.data.url,
|
||
uploadResult && uploadResult.data && uploadResult.data.fileUrl,
|
||
uploadResult && uploadResult.data && uploadResult.data.path,
|
||
uploadResult && uploadResult.data && uploadResult.data.filePath,
|
||
uploadResult && uploadResult.url,
|
||
uploadResult && uploadResult.fileUrl,
|
||
uploadResult && uploadResult.path,
|
||
uploadResult && uploadResult.filePath,
|
||
typeof (uploadResult && uploadResult.data) === 'string' ? uploadResult.data : '',
|
||
typeof uploadResult === 'string' ? uploadResult : ''
|
||
]
|
||
return candidates.find(item => typeof item === 'string' && item) || fallback
|
||
},
|
||
getDefaultAttachmentName(field, filePath) {
|
||
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 = '') {
|
||
const normalizedPath = String(filePath || '').replace(/\\/g, '/')
|
||
return normalizedPath.split('/').pop() || 'attachment'
|
||
},
|
||
validateForm() {
|
||
if (!this.form.account || !this.form.password || !this.form.email || !this.form.name) {
|
||
return this.$tr('请先填写账号、密码、邮箱和姓名')
|
||
}
|
||
if (this.form.password.length < 6) {
|
||
return this.$tr('登录密码至少 6 位')
|
||
}
|
||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.form.email)) {
|
||
return this.$tr('请输入正确的邮箱地址')
|
||
}
|
||
if (this.form.gender === null || !this.form.phone || !this.form.country) {
|
||
return this.$tr('请完善性别、手机号和国籍')
|
||
}
|
||
if (!/^1\d{10}$/.test(this.form.phone)) {
|
||
return this.$tr('请输入正确的手机号')
|
||
}
|
||
if (!this.form.workUnit || this.form.hasHangzhouProperty === null) {
|
||
return this.$tr('请完善工作单位和杭州房产情况')
|
||
}
|
||
if (!this.form.idCardType) {
|
||
return this.$tr('请选择证件类型')
|
||
}
|
||
|
||
if (this.form.identityType === 'DomesticStudents') {
|
||
if (!this.form.idCard) {
|
||
return this.$tr('请填写身份证号')
|
||
}
|
||
if (!this.form.idCardFrontAttachment.serverValue || !this.form.idCardBackAttachment.serverValue) {
|
||
return this.$tr('请上传身份证正反面附件')
|
||
}
|
||
return ''
|
||
}
|
||
|
||
if (!this.form.passportNumber || !this.form.passportExpiry) {
|
||
return this.isPassportType ? this.$tr('请填写护照号和护照到期时间') : this.$tr('请填写证件号和证件到期时间')
|
||
}
|
||
if (!this.form.passportAttachment.serverValue) {
|
||
return this.isPassportType ? this.$tr('请上传护照图片') : this.$tr('请上传证件图片')
|
||
}
|
||
if (!this.isImageAttachment(this.form.passportAttachment)) {
|
||
return this.isPassportType ? this.$tr('护照附件仅支持图片格式') : this.$tr('证件附件仅支持图片格式')
|
||
}
|
||
return ''
|
||
},
|
||
buildSingleTag(text, options) {
|
||
if (!text) return []
|
||
const matched = (options || []).find(item => item.value === text)
|
||
if (matched) {
|
||
return [{ key: matched.key, value: matched.value }]
|
||
}
|
||
return [{ key: text, value: text }]
|
||
},
|
||
buildAuthProfile() {
|
||
return {
|
||
identityType: this.form.identityType,
|
||
account: this.form.account,
|
||
email: this.form.email,
|
||
emailVerifyCode: this.form.emailVerifyCode,
|
||
roleNo: this.form.identityType,
|
||
name: this.form.name,
|
||
gender: this.form.gender,
|
||
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,
|
||
idCardTypeText: this.selectedIdCardTypeLabel || '',
|
||
idCard: this.form.identityType === 'DomesticStudents' ? this.form.idCard : '',
|
||
workUnit: this.form.workUnit,
|
||
hasHangzhouProperty: this.form.hasHangzhouProperty,
|
||
dietaryHabit: this.form.dietaryHabit,
|
||
dietaryHabitTags: this.buildSingleTag(this.form.dietaryHabit, this.dietaryHabitOptions),
|
||
hobby: this.form.hobby,
|
||
hobbyTags: this.buildSingleTag(this.form.hobby, this.hobbyOptions),
|
||
passportNumber: this.form.identityType === 'ForeignStudents' ? this.form.passportNumber : '',
|
||
passportExpiry: this.form.identityType === 'ForeignStudents' && this.form.passportExpiry
|
||
? `${this.form.passportExpiry}T00:00:00`
|
||
: '',
|
||
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 : ''
|
||
}
|
||
},
|
||
buildSubmitPayload(providerValue) {
|
||
const profile = this.buildAuthProfile()
|
||
return {
|
||
providerValue: providerValue || null,
|
||
account: profile.account,
|
||
password: this.form.password,
|
||
email: profile.email,
|
||
emailVerifyCode: profile.emailVerifyCode || null,
|
||
roleNo: profile.roleNo,
|
||
name: profile.name,
|
||
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,
|
||
dietaryHabitTags: profile.dietaryHabitTags.length ? profile.dietaryHabitTags : null,
|
||
hobbyTags: profile.hobbyTags.length ? profile.hobbyTags : null,
|
||
passportNumber: profile.passportNumber || null,
|
||
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
|
||
}
|
||
},
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.register-page {
|
||
background: linear-gradient(180deg, #F0F6FF 0%, #F8FAFF 100%);
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.register-content {
|
||
padding: 0 24rpx 60rpx;
|
||
}
|
||
|
||
.page-header {
|
||
margin-top: 40rpx;
|
||
margin-bottom: 40rpx;
|
||
padding: 0 10rpx;
|
||
}
|
||
.page-title {
|
||
font-size: 56rpx;
|
||
letter-spacing: 2rpx;
|
||
color: #1A2233;
|
||
line-height: 1.25;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
.page-desc {
|
||
margin-top: 16rpx;
|
||
line-height: 1.6;
|
||
color: #6A7A99;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 3;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.draft-tip-bar {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
padding: 12rpx 18rpx;
|
||
border-radius: 999rpx;
|
||
background: rgba($brand-primary, 0.08);
|
||
border: 1rpx solid rgba($brand-primary, 0.12);
|
||
}
|
||
|
||
.draft-tip-icon {
|
||
color: $brand-primary;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.draft-tip-text {
|
||
color: #5C6B8A;
|
||
font-size: 24rpx;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.tip-text {
|
||
margin-top: 12rpx;
|
||
color: #5C6B8A;
|
||
line-height: 1.6;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
/* 卡片式表单 */
|
||
.form-card {
|
||
background-color: #FFFFFF;
|
||
border-radius: 24rpx;
|
||
padding: 40rpx 32rpx;
|
||
margin-bottom: 30rpx;
|
||
border: 1rpx solid rgba($brand-primary, 0.05);
|
||
box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.04);
|
||
}
|
||
|
||
.section-title {
|
||
margin-bottom: 36rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
color: #1A2233;
|
||
font-size: 32rpx;
|
||
}
|
||
|
||
.title-indicator {
|
||
width: 8rpx;
|
||
height: 32rpx;
|
||
background-color: $brand-primary;
|
||
border-radius: 4rpx;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 36rpx;
|
||
}
|
||
|
||
/* 表单元素 */
|
||
.form-label {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #1A2233;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.required-mark {
|
||
color: #FF4D4F;
|
||
margin-left: 6rpx;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.form-input {
|
||
background-color: #FFFFFF;
|
||
height: 96rpx;
|
||
border-radius: 20rpx;
|
||
padding: 0 32rpx;
|
||
font-size: 28rpx;
|
||
border: 1rpx solid rgba($brand-primary, 0.15);
|
||
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.05);
|
||
transition: all 0.3s ease;
|
||
box-sizing: border-box;
|
||
color: #1A2233;
|
||
}
|
||
|
||
.form-input:focus {
|
||
border: 1rpx solid $brand-primary;
|
||
background-color: #FFFFFF;
|
||
box-shadow: 0 0 0 6rpx rgba($brand-primary, 0.1);
|
||
}
|
||
|
||
.input-placeholder {
|
||
color: #A0B0C0;
|
||
}
|
||
|
||
.picker-input {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.picker-input-disabled {
|
||
background-color: #F7F9FC;
|
||
color: #6A7A99;
|
||
}
|
||
|
||
.picker-fixed-text {
|
||
color: #8A99B3;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.text-black {
|
||
color: #1A2233;
|
||
}
|
||
|
||
.text-gray {
|
||
color: #A0B0C0;
|
||
}
|
||
|
||
/* 双标签切换 */
|
||
.role-switch-wrap {
|
||
margin-bottom: 36rpx;
|
||
}
|
||
.role-switch {
|
||
background-color: rgba($brand-primary, 0.05);
|
||
border-radius: 24rpx;
|
||
padding: 10rpx;
|
||
border: 1rpx solid rgba($brand-primary, 0.1);
|
||
}
|
||
.role-tab {
|
||
border-radius: 18rpx;
|
||
color: #6A7A99;
|
||
padding: 18rpx 0;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
text-align: center;
|
||
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 {
|
||
background-color: #FFFFFF;
|
||
height: 96rpx;
|
||
line-height: 96rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 28rpx;
|
||
color: #6A7A99;
|
||
border: 1rpx solid rgba($brand-primary, 0.15);
|
||
transition: all 0.2s;
|
||
text-align: center;
|
||
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.05);
|
||
}
|
||
.gender-active {
|
||
background-color: #FFFFFF;
|
||
border-color: $brand-primary;
|
||
color: $brand-primary;
|
||
font-weight: bold;
|
||
box-shadow: 0 4rpx 16rpx rgba($brand-primary, 0.1);
|
||
}
|
||
|
||
/* 标签 */
|
||
.tag-item {
|
||
display: inline-block;
|
||
background-color: #FFFFFF;
|
||
padding: 16rpx 36rpx;
|
||
border-radius: 40rpx;
|
||
font-size: 26rpx;
|
||
color: #6A7A99;
|
||
margin-right: 20rpx;
|
||
margin-bottom: 20rpx;
|
||
transition: all 0.2s;
|
||
border: 1rpx solid rgba($brand-primary, 0.15);
|
||
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.05);
|
||
}
|
||
.tag-active {
|
||
background-color: #FFFFFF;
|
||
border: 1rpx solid $brand-primary;
|
||
color: $brand-primary;
|
||
font-weight: bold;
|
||
box-shadow: 0 4rpx 16rpx rgba($brand-primary, 0.1);
|
||
}
|
||
|
||
/* 附件上传框 */
|
||
.upload-box {
|
||
background-color: #FFFFFF;
|
||
border: 1rpx dashed rgba($brand-primary, 0.4);
|
||
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.05);
|
||
border-radius: 24rpx;
|
||
min-height: 240rpx;
|
||
padding: 32rpx;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.upload-box:active {
|
||
background-color: rgba($brand-primary, 0.02);
|
||
border: 1rpx dashed rgba($brand-primary, 0.6);
|
||
}
|
||
|
||
.upload-placeholder {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.upload-icon-wrap {
|
||
width: 88rpx;
|
||
height: 88rpx;
|
||
background-color: #FFFFFF;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 4rpx 16rpx rgba($brand-primary, 0.1);
|
||
color: $brand-primary;
|
||
font-size: 40rpx;
|
||
}
|
||
|
||
.upload-text {
|
||
color: #6A7A99;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.attachment-preview {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
}
|
||
|
||
.success-icon {
|
||
font-size: 64rpx;
|
||
color: $brand-primary;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.attachment-name {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #1A2233;
|
||
max-width: 400rpx;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.attachment-type {
|
||
color: #8A99B3;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.attachment-file-card {
|
||
background-color: #FFFFFF;
|
||
border: 1rpx solid rgba($brand-primary, 0.12);
|
||
box-shadow: 0 8rpx 24rpx rgba($brand-primary, 0.06);
|
||
border-radius: 24rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.attachment-preview-image,
|
||
.attachment-file-cover {
|
||
width: 100%;
|
||
height: 320rpx;
|
||
display: block;
|
||
}
|
||
|
||
.attachment-file-cover {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(135deg, rgba($brand-primary, 0.14) 0%, rgba($brand-primary, 0.06) 100%);
|
||
}
|
||
|
||
.attachment-file-cover.is-unsupported {
|
||
color: $brand-primary;
|
||
}
|
||
|
||
.attachment-file-ext {
|
||
font-size: 56rpx;
|
||
font-weight: bold;
|
||
letter-spacing: 4rpx;
|
||
}
|
||
|
||
.attachment-file-info {
|
||
padding: 24rpx 24rpx 12rpx;
|
||
}
|
||
|
||
.attachment-action-row {
|
||
display: flex;
|
||
padding: 0 24rpx 24rpx;
|
||
}
|
||
|
||
.attachment-action-btn {
|
||
flex: 1;
|
||
height: 76rpx;
|
||
border-radius: 18rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 26rpx;
|
||
font-weight: bold;
|
||
color: $brand-primary;
|
||
background: rgba($brand-primary, 0.08);
|
||
border: 1rpx solid rgba($brand-primary, 0.12);
|
||
}
|
||
|
||
.attachment-action-btn + .attachment-action-btn {
|
||
margin-left: 16rpx;
|
||
}
|
||
|
||
.attachment-action-btn.is-primary {
|
||
color: #FFFFFF;
|
||
background: linear-gradient(135deg, rgba($brand-primary, 0.92) 0%, $brand-primary 100%);
|
||
border-color: transparent;
|
||
}
|
||
|
||
/* 动画 */
|
||
.animation-fade-in {
|
||
opacity: 0;
|
||
animation: fadeIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; transform: translateY(30rpx); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
/* 自定义按钮样式 */
|
||
.custom-btn {
|
||
display: inline-flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
border-radius: 100rpx;
|
||
padding: 12rpx 36rpx;
|
||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
.submit-btn {
|
||
width: 100%;
|
||
height: 100rpx;
|
||
box-shadow: 0 12rpx 32rpx rgba($brand-primary, 0.25);
|
||
}
|
||
.custom-btn:active {
|
||
transform: scale(0.96);
|
||
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.2);
|
||
}
|
||
.btn-primary {
|
||
background: linear-gradient(135deg, rgba($brand-primary, 0.9) 0%, $brand-primary 100%);
|
||
color: #ffffff;
|
||
}
|
||
|
||
.btn-disabled {
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.secondary-entry-card {
|
||
background: #FFFFFF;
|
||
border-radius: 24rpx;
|
||
padding: 28rpx 32rpx;
|
||
border: 1rpx solid rgba($brand-primary, 0.12);
|
||
box-shadow: 0 8rpx 20rpx rgba($brand-primary, 0.05);
|
||
}
|
||
|
||
.secondary-entry-card:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.secondary-entry-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #1A2233;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.secondary-entry-desc {
|
||
font-size: 24rpx;
|
||
color: #6A7A99;
|
||
line-height: 1.5;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.secondary-entry-arrow {
|
||
color: $brand-primary;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.login-link {
|
||
padding: 10rpx;
|
||
}
|
||
.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>
|