2522 lines
80 KiB
Vue
2522 lines
80 KiB
Vue
<template>
|
||
<view class="register-page tn-safe-area-inset-bottom">
|
||
<tn-nav-bar :isBack="true" :bottomShadow="false" :backgroundColor="navBackgroundColor">
|
||
<view class="nav-title">{{ $tr('注册账号') }}</view>
|
||
</tn-nav-bar>
|
||
|
||
<view class="register-content" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
|
||
<view class="ambient-orb orb-a"></view>
|
||
<view class="ambient-orb orb-b"></view>
|
||
<view class="ambient-grid"></view>
|
||
|
||
<view class="register-intro animation-fade-in">
|
||
<view class="intro-kicker-row">
|
||
<view class="hero-kicker">{{ $tr('绿居公寓登记入口') }}</view>
|
||
</view>
|
||
<view class="page-title">{{ $tr('注册并提交实名资料') }}</view>
|
||
<view class="draft-tip-bar">
|
||
<text class="tn-icon-safe-fill draft-tip-icon"></text>
|
||
<text class="draft-tip-text">{{ draftTipText }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="progress-strip animation-fade-in" style="animation-delay: 0.08s;">
|
||
<view
|
||
v-for="step in stepList"
|
||
:key="step.index"
|
||
class="progress-node"
|
||
:class="{
|
||
'is-active': currentStep === step.index,
|
||
'is-completed': currentStep > step.index
|
||
}"
|
||
>
|
||
<text class="progress-node-index">{{ step.code }}</text>
|
||
<text class="progress-node-text">{{ step.shortTitle }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="currentStep === 1" class="form-card animation-fade-in" style="animation-delay: 0.12s;">
|
||
<view class="section-head">
|
||
<view class="section-index">01</view>
|
||
<view class="section-copy">
|
||
<view class="section-title">{{ $tr('账号信息') }}</view>
|
||
<view class="section-desc">{{ $tr('用于登录和接收后续通知') }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section-body">
|
||
<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>
|
||
|
||
<view v-if="currentStep === 2" class="form-card animation-fade-in" style="animation-delay: 0.12s;">
|
||
<view class="section-head">
|
||
<view class="section-index">02</view>
|
||
<view class="section-copy">
|
||
<view class="section-title">{{ $tr('基础实名信息') }}</view>
|
||
<view class="section-desc">{{ $tr('根据身份展示对应的证件和字段') }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section-body">
|
||
<view class="identity-panel">
|
||
<view class="identity-panel-label">{{ $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="identity-panel-tip">{{ $tr('切换后会同步调整国籍、证件类型和上传要求') }}</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('手机号') }}</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>
|
||
<view v-else class="form-input picker-input" @click="openCountrySelector">
|
||
<view class="picker-text-wrap">
|
||
<text :class="form.country ? 'text-black' : 'text-gray'">{{ form.country || $tr('请选择国籍') }}</text>
|
||
<text v-if="form.country" class="picker-subtext">{{ $tr('支持模糊搜索') }}</text>
|
||
</view>
|
||
<view class="picker-action-wrap">
|
||
<text v-if="form.country" class="picker-action-text">{{ $tr('重新选择') }}</text>
|
||
<text class="tn-icon-right picker-arrow"></text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="currentStep === 3" class="form-card animation-fade-in" style="animation-delay: 0.12s;">
|
||
<view class="section-head">
|
||
<view class="section-index">03</view>
|
||
<view class="section-copy">
|
||
<view class="section-title">{{ $tr('认证档案') }}</view>
|
||
<view class="section-desc">{{ $tr('上传证件和补充资料,便于审核') }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section-body">
|
||
<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="upload-text">{{ $tr('点击上传入学通知书/在职证明') }}</view>
|
||
<view class="upload-subtext">{{ $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">{{ $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 picker-arrow"></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('生日') }}</view>
|
||
<picker mode="date" :value="form.birthday" @change="onBirthdayChange">
|
||
<view class="form-input picker-input">
|
||
<text :class="form.birthday ? 'text-black' : 'text-gray'">{{ form.birthday || $tr('请选择生日') }}</text>
|
||
<text class="tn-icon-calendar picker-arrow"></text>
|
||
</view>
|
||
</picker>
|
||
</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="upload-text">{{ $tr('点击上传身份证正面') }}</view>
|
||
<view class="upload-subtext">{{ $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">{{ $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="upload-text">{{ $tr('点击上传身份证反面') }}</view>
|
||
<view class="upload-subtext">{{ $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">{{ $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">{{ $tr('生日') }}</view>
|
||
<picker mode="date" :value="form.birthday" @change="onBirthdayChange">
|
||
<view class="form-input picker-input">
|
||
<text :class="form.birthday ? 'text-black' : 'text-gray'">{{ form.birthday || $tr('请选择生日') }}</text>
|
||
<text class="tn-icon-calendar picker-arrow"></text>
|
||
</view>
|
||
</picker>
|
||
</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 picker-arrow"></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 class="upload-placeholder">
|
||
<view class="upload-icon-wrap"><text class="tn-icon-upload"></text></view>
|
||
<view class="upload-text">{{ foreignDocumentUploadText }}</view>
|
||
<view class="upload-subtext">{{ $tr('优先上传正面完整清晰的图片') }}</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">
|
||
{{ 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>
|
||
|
||
<view v-if="currentStep === 4" class="form-card animation-fade-in" style="animation-delay: 0.12s;">
|
||
<view class="section-head">
|
||
<view class="section-index">04</view>
|
||
<view class="section-copy">
|
||
<view class="section-title">{{ $tr('偏好信息') }}</view>
|
||
<view class="section-desc">{{ $tr('选填项,用于更好地匹配入住生活偏好') }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section-body">
|
||
<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 picker-arrow"></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 picker-arrow"></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 picker-arrow"></text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="bottom-btn-area animation-fade-in" style="animation-delay: 0.44s;">
|
||
<view class="action-dock">
|
||
<view class="step-action-row" :class="{ 'single-action': currentStep === 1 }">
|
||
<view v-if="currentStep > 1" class="custom-btn btn-secondary step-btn" @click="goPrevStep">
|
||
<text class="step-btn-text">{{ $tr('上一步') }}</text>
|
||
</view>
|
||
<view class="custom-btn btn-primary submit-btn step-btn" @click="handlePrimaryAction" :class="{ 'btn-disabled': isSubmitting }">
|
||
<text class="submit-btn-text">{{ primaryActionText }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<tn-popup v-model="showCountrySelector" mode="bottom" length="78%" :borderRadius="32" :closeBtn="true" @close="closeCountrySelector">
|
||
<view class="country-selector-popup">
|
||
<view class="country-selector-header">
|
||
<view class="country-selector-title">{{ $tr('选择国籍') }}</view>
|
||
<view class="country-selector-subtitle">{{ $tr('可按中文、英文或国家代码搜索') }}</view>
|
||
</view>
|
||
<view class="country-search-box">
|
||
<text class="tn-icon-search country-search-icon"></text>
|
||
<input
|
||
v-model.trim="countrySearchKeyword"
|
||
class="country-search-input"
|
||
:placeholder="$tr('搜索国家、地区或代码')"
|
||
placeholder-class="input-placeholder"
|
||
confirm-type="search"
|
||
/>
|
||
<view v-if="countrySearchKeyword" class="country-search-clear" @click="clearCountrySearch">{{ $tr('清空') }}</view>
|
||
</view>
|
||
<scroll-view scroll-y class="country-selector-list">
|
||
<view
|
||
v-for="(item, index) in filteredCountryOptions"
|
||
:key="index"
|
||
class="country-option"
|
||
:class="{ 'is-active': form.country === item.value }"
|
||
@click="selectCountryOptionByIndex(index)"
|
||
>
|
||
<view class="country-option-main">
|
||
<view class="country-option-name">{{ item.value }}</view>
|
||
<view v-if="item.secondaryText" class="country-option-meta">{{ item.secondaryText }}</view>
|
||
</view>
|
||
<text v-if="form.country === item.value" class="tn-icon-success-circle-fill country-option-check"></text>
|
||
</view>
|
||
<view v-if="!filteredCountryOptions.length" class="country-selector-empty">
|
||
{{ $tr('没有找到匹配的国家或地区') }}
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</tn-popup>
|
||
|
||
<!-- #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'
|
||
import test from '../../tuniao-ui/libs/function/test.js'
|
||
|
||
function createAttachmentState() {
|
||
return {
|
||
name: '',
|
||
serverValue: '',
|
||
localPath: ''
|
||
}
|
||
}
|
||
|
||
function createDefaultForm() {
|
||
return {
|
||
identityType: 'ForeignStudents',
|
||
account: '',
|
||
password: '',
|
||
email: '',
|
||
emailVerifyCode: '',
|
||
name: '',
|
||
gender: 1,
|
||
phone: '',
|
||
country: '',
|
||
idCardType: 2,
|
||
idCard: '',
|
||
birthday: '',
|
||
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)
|
||
}
|
||
}
|
||
|
||
function normalizeRegistrationDraftStep(draft) {
|
||
const step = Number(draft && draft.currentStep)
|
||
if (step >= 1 && step <= 4) {
|
||
return step
|
||
}
|
||
return 1
|
||
}
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
navBgOpacity: 0,
|
||
currentStep: 1,
|
||
isSubmitting: false,
|
||
dictLoading: false,
|
||
hasPromptedFailedDraft: false,
|
||
draftPersistTimer: null,
|
||
draftTipText: '',
|
||
lastForeignCountry: '',
|
||
lastForeignIdCardType: 2,
|
||
showCountrySelector: false,
|
||
countrySearchKeyword: '',
|
||
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('当前暂无可选项')]
|
||
},
|
||
filteredCountryOptions() {
|
||
if (!this.countryOptions.length) {
|
||
return []
|
||
}
|
||
const keyword = this.normalizeSearchText(this.countrySearchKeyword)
|
||
if (!keyword) {
|
||
return this.countryOptions
|
||
}
|
||
const compactKeyword = keyword.replace(/\s+/g, '')
|
||
return this.countryOptions.filter((item) => {
|
||
const searchText = this.normalizeSearchText(item.searchText || '')
|
||
const compactSearchText = searchText.replace(/\s+/g, '')
|
||
return searchText.includes(keyword) || compactSearchText.includes(compactKeyword)
|
||
})
|
||
},
|
||
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('证件附件仅支持图片,请重新上传')
|
||
},
|
||
stepList() {
|
||
return [
|
||
{
|
||
index: 1,
|
||
code: '01',
|
||
shortTitle: this.$tr('账号'),
|
||
title: this.$tr('账号信息'),
|
||
desc: this.$tr('先完成登录账号、密码和邮箱信息。')
|
||
},
|
||
{
|
||
index: 2,
|
||
code: '02',
|
||
shortTitle: this.$tr('实名'),
|
||
title: this.$tr('基础实名信息'),
|
||
desc: this.$tr('填写姓名、身份、手机号和国籍等基础资料。')
|
||
},
|
||
{
|
||
index: 3,
|
||
code: '03',
|
||
shortTitle: this.$tr('档案'),
|
||
title: this.$tr('认证档案'),
|
||
desc: this.$tr('补充证件、附件和审核所需资料。')
|
||
},
|
||
{
|
||
index: 4,
|
||
code: '04',
|
||
shortTitle: this.$tr('偏好'),
|
||
title: this.$tr('偏好信息'),
|
||
desc: this.$tr('最后补充选填偏好信息,然后完成提交。')
|
||
}
|
||
]
|
||
},
|
||
totalSteps() {
|
||
return this.stepList.length
|
||
},
|
||
currentStepMeta() {
|
||
return this.stepList.find(step => step.index === this.currentStep) || this.stepList[0]
|
||
},
|
||
isLastStep() {
|
||
return this.currentStep === this.totalSteps
|
||
},
|
||
primaryActionText() {
|
||
if (this.isLastStep) {
|
||
return this.isSubmitting ? this.$tr('提交中...') : this.$tr('完成并提交')
|
||
}
|
||
return this.$tr('下一步')
|
||
},
|
||
genderOptions() {
|
||
return [
|
||
{ key: 1, value: this.$tr('男') },
|
||
{ key: 0, value: this.$tr('女') }
|
||
]
|
||
}
|
||
},
|
||
watch: {
|
||
language() {
|
||
this.rebuildLocalizedOptions()
|
||
this.draftTipText = this.$tr('未完成内容会自动保存,可随时返回继续填写')
|
||
},
|
||
'form.idCard'(value) {
|
||
this.syncBirthdayFromIdCard(value)
|
||
},
|
||
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)
|
||
this.currentStep = normalizeRegistrationDraftStep(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',
|
||
'birthday',
|
||
'country',
|
||
'idCard',
|
||
'workUnit',
|
||
'dietaryHabit',
|
||
'hobby',
|
||
'passportNumber',
|
||
'passportExpiry',
|
||
'school',
|
||
'religion'
|
||
]
|
||
|
||
if (this.form.identityType !== 'ForeignStudents') {
|
||
return true
|
||
}
|
||
if (this.form.gender !== 1 || 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(),
|
||
currentStep: this.currentStep,
|
||
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
|
||
if (type === 'DomesticStudents') {
|
||
this.closeCountrySelector()
|
||
}
|
||
this.applyIdentityDocumentTypeRule()
|
||
this.applyIdentityCountryRule()
|
||
this.syncBirthdayFromIdCard(this.form.idCard)
|
||
},
|
||
onBirthdayChange(e) {
|
||
this.form.birthday = e.detail.value
|
||
},
|
||
normalizeIdCardValue(value = '') {
|
||
return String(value || '').trim().toUpperCase()
|
||
},
|
||
isValidBirthdayValue(value = '') {
|
||
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
||
return false
|
||
}
|
||
const [year, month, day] = value.split('-').map(item => Number(item))
|
||
const date = new Date(year, month - 1, day)
|
||
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day
|
||
},
|
||
extractBirthdayFromIdCard(idCard = '') {
|
||
const normalizedIdCard = this.normalizeIdCardValue(idCard)
|
||
if (!test.idCard(normalizedIdCard)) {
|
||
return ''
|
||
}
|
||
const birthday = `${normalizedIdCard.slice(6, 10)}-${normalizedIdCard.slice(10, 12)}-${normalizedIdCard.slice(12, 14)}`
|
||
return this.isValidBirthdayValue(birthday) ? birthday : ''
|
||
},
|
||
syncBirthdayFromIdCard(idCard = '') {
|
||
if (!this.isDomesticIdentity) {
|
||
return
|
||
}
|
||
const birthday = this.extractBirthdayFromIdCard(idCard)
|
||
if (birthday) {
|
||
this.form.birthday = birthday
|
||
}
|
||
},
|
||
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 || ''
|
||
},
|
||
normalizeSearchText(value = '') {
|
||
return String(value || '').trim().toLowerCase().replace(/\s+/g, ' ')
|
||
},
|
||
getCountrySecondaryText(item = {}, localizedValue = '') {
|
||
const isEn = this.language === 'en'
|
||
const alternateCandidates = isEn
|
||
? [item.cname, item.nameZh, item.labelZh, item.valueZh, item.description]
|
||
: [item.ename, item.nameEn, item.labelEn, item.valueEn, item.descriptionEn]
|
||
const alternateText = alternateCandidates
|
||
.map(value => String(value || '').trim())
|
||
.find(value => value && value !== localizedValue)
|
||
const code = String(item.code || item.key || '').trim().toUpperCase()
|
||
return [alternateText, code].filter(Boolean).join(' · ')
|
||
},
|
||
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) => {
|
||
const value = this.getLocalizedOptionText(item)
|
||
const searchCandidates = this.getOptionTextCandidates(item)
|
||
return {
|
||
key: item.code || item.key || item.cname || '',
|
||
value,
|
||
secondaryText: this.getCountrySecondaryText(item, value),
|
||
searchText: searchCandidates.join(' ')
|
||
}
|
||
})
|
||
.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 || ''
|
||
}
|
||
},
|
||
openCountrySelector() {
|
||
if (this.isDomesticIdentity) {
|
||
return
|
||
}
|
||
if (!this.countryOptions.length) {
|
||
uni.showToast({
|
||
title: this.$tr('当前暂无可选国家'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
this.countrySearchKeyword = ''
|
||
this.showCountrySelector = true
|
||
},
|
||
closeCountrySelector() {
|
||
this.showCountrySelector = false
|
||
this.countrySearchKeyword = ''
|
||
},
|
||
clearCountrySearch() {
|
||
this.countrySearchKeyword = ''
|
||
},
|
||
selectCountryOptionByIndex(index) {
|
||
const option = (this.filteredCountryOptions || [])[Number(index)]
|
||
this.selectCountryOption(option)
|
||
},
|
||
selectCountryOption(option) {
|
||
if (!option || !option.value) {
|
||
return
|
||
}
|
||
this.form.country = option.value
|
||
this.lastForeignCountry = this.form.country || ''
|
||
this.closeCountrySelector()
|
||
},
|
||
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
|
||
}
|
||
},
|
||
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() : ''
|
||
},
|
||
scrollToTop() {
|
||
uni.pageScrollTo({
|
||
scrollTop: 0,
|
||
duration: 220
|
||
})
|
||
},
|
||
validateStep(step = this.currentStep) {
|
||
if (step === 1) {
|
||
if (!this.form.account || !this.form.password || !this.form.email) {
|
||
return this.$tr('请先填写账号、密码和邮箱')
|
||
}
|
||
if (this.form.password.length < 6) {
|
||
return this.$tr('登录密码至少 6 位')
|
||
}
|
||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.form.email)) {
|
||
return this.$tr('请输入正确的邮箱地址')
|
||
}
|
||
return ''
|
||
}
|
||
|
||
if (step === 2) {
|
||
if (!this.form.name) {
|
||
return this.$tr('请填写真实姓名')
|
||
}
|
||
if (this.form.gender === null || !this.form.country) {
|
||
return this.$tr('请完善性别和国籍')
|
||
}
|
||
return ''
|
||
}
|
||
|
||
if (step === 3) {
|
||
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 ''
|
||
},
|
||
goPrevStep() {
|
||
if (this.currentStep <= 1) {
|
||
return
|
||
}
|
||
this.currentStep -= 1
|
||
this.scrollToTop()
|
||
},
|
||
goNextStep() {
|
||
const errorMessage = this.validateStep(this.currentStep)
|
||
if (errorMessage) {
|
||
uni.showToast({
|
||
title: errorMessage,
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
if (this.currentStep >= this.totalSteps) {
|
||
return
|
||
}
|
||
this.currentStep += 1
|
||
this.scrollToTop()
|
||
},
|
||
handlePrimaryAction() {
|
||
if (this.isLastStep) {
|
||
this.submitForm()
|
||
return
|
||
}
|
||
this.goNextStep()
|
||
},
|
||
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) => {
|
||
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() {
|
||
for (let step = 1; step <= 3; step += 1) {
|
||
const errorMessage = this.validateStep(step)
|
||
if (errorMessage) {
|
||
this.currentStep = step
|
||
this.scrollToTop()
|
||
return errorMessage
|
||
}
|
||
}
|
||
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.normalizeIdCardValue(this.form.idCard) : '',
|
||
birthday: this.form.identityType === 'DomesticStudents'
|
||
? this.extractBirthdayFromIdCard(this.form.idCard) || this.form.birthday || ''
|
||
: this.form.birthday || '',
|
||
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 || null,
|
||
country: profile.country,
|
||
idCardType: profile.idCardType || null,
|
||
IdCardType: profile.idCardType || null,
|
||
idCard: profile.idCard || null,
|
||
birthday: profile.birthday || 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">
|
||
$brand: $brand-primary;
|
||
$brand-soft: rgba($brand-primary, 0.12);
|
||
$brand-ink: $brand-primary;
|
||
$bg-base: #FFFFFF;
|
||
$surface: rgba(255, 255, 255, 0.82);
|
||
$surface-solid: #FFFFFF;
|
||
$text-main: #1A2233;
|
||
$text-sub: #6A7A99;
|
||
$text-light: #9AA8C0;
|
||
$line-soft: rgba($brand-primary, 0.12);
|
||
$shadow-soft: 0 8rpx 20rpx rgba($brand-primary, 0.05);
|
||
|
||
.register-page {
|
||
min-height: 100vh;
|
||
position: relative;
|
||
background: #FFFFFF;
|
||
background-image:
|
||
radial-gradient(circle at 100% 0%, rgba($brand-primary, 0.15) 0%, transparent 40%),
|
||
radial-gradient(circle at 0% 100%, rgba($brand-primary, 0.05) 0%, transparent 40%);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.register-page::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background:
|
||
radial-gradient(circle at 14% 18%, rgba($brand-primary, 0.05) 0, rgba($brand-primary, 0) 28%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.nav-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
letter-spacing: 1rpx;
|
||
color: $text-main;
|
||
}
|
||
|
||
.register-content {
|
||
position: relative;
|
||
z-index: 1;
|
||
padding: 0 28rpx 232rpx;
|
||
}
|
||
|
||
.ambient-orb {
|
||
position: absolute;
|
||
border-radius: 50%;
|
||
filter: blur(16rpx);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.orb-a {
|
||
width: 280rpx;
|
||
height: 280rpx;
|
||
top: 90rpx;
|
||
right: -80rpx;
|
||
background: rgba($brand-primary, 0.1);
|
||
}
|
||
|
||
.orb-b {
|
||
width: 220rpx;
|
||
height: 220rpx;
|
||
top: 520rpx;
|
||
left: -100rpx;
|
||
background: rgba($brand-primary, 0.06);
|
||
}
|
||
|
||
.ambient-grid {
|
||
position: absolute;
|
||
top: 140rpx;
|
||
right: 20rpx;
|
||
width: 200rpx;
|
||
height: 220rpx;
|
||
opacity: 0.24;
|
||
background-image:
|
||
linear-gradient(rgba($brand-primary, 0.12) 1px, transparent 1px),
|
||
linear-gradient(90deg, rgba($brand-primary, 0.12) 1px, transparent 1px);
|
||
background-size: 26rpx 26rpx;
|
||
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 1), transparent);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.register-intro {
|
||
position: relative;
|
||
margin-top: 34rpx;
|
||
padding: 8rpx 4rpx 0;
|
||
}
|
||
|
||
.intro-kicker-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.hero-kicker {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
height: 48rpx;
|
||
padding: 0 20rpx;
|
||
border-radius: 999rpx;
|
||
background: #FFFFFF;
|
||
border: 1rpx solid rgba($brand-primary, 0.12);
|
||
color: $brand-ink;
|
||
font-size: 22rpx;
|
||
letter-spacing: 1rpx;
|
||
}
|
||
|
||
.page-title {
|
||
margin-top: 22rpx;
|
||
font-size: 48rpx;
|
||
line-height: 1.24;
|
||
font-weight: 600;
|
||
letter-spacing: 0;
|
||
color: $text-main;
|
||
}
|
||
|
||
.draft-tip-bar {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
margin-top: 22rpx;
|
||
padding: 12rpx 20rpx;
|
||
border-radius: 999rpx;
|
||
background: #FFFFFF;
|
||
border: 1rpx solid rgba($brand-primary, 0.12);
|
||
box-shadow: 0 8rpx 20rpx rgba($brand-primary, 0.05);
|
||
}
|
||
|
||
.draft-tip-icon {
|
||
margin-right: 12rpx;
|
||
font-size: 24rpx;
|
||
color: $brand;
|
||
}
|
||
|
||
.draft-tip-text {
|
||
font-size: 22rpx;
|
||
font-weight: 500;
|
||
color: $brand-ink;
|
||
}
|
||
|
||
.progress-strip {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 14rpx;
|
||
margin: 28rpx 0 22rpx;
|
||
border-radius: 32rpx;
|
||
background: #FFFFFF;
|
||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||
box-shadow: $shadow-soft;
|
||
}
|
||
|
||
.progress-node {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 18rpx 8rpx;
|
||
border-radius: 24rpx;
|
||
color: $text-sub;
|
||
}
|
||
|
||
.progress-node.is-active {
|
||
background: #FFFFFF;
|
||
color: $brand-primary;
|
||
border: 1rpx solid rgba($brand-primary, 0.16);
|
||
box-shadow: 0 8rpx 20rpx rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.progress-node.is-completed {
|
||
background: rgba($brand-primary, 0.08);
|
||
color: $brand-primary;
|
||
}
|
||
|
||
.progress-node-index {
|
||
margin-bottom: 8rpx;
|
||
font-size: 20rpx;
|
||
font-weight: 600;
|
||
letter-spacing: 2rpx;
|
||
}
|
||
|
||
.progress-node-text {
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-card {
|
||
position: relative;
|
||
margin-bottom: 28rpx;
|
||
padding: 34rpx 26rpx 26rpx;
|
||
border-radius: 36rpx;
|
||
background: #FFFFFF;
|
||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||
box-shadow: $shadow-soft;
|
||
}
|
||
|
||
.section-head {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 26rpx;
|
||
}
|
||
|
||
.section-index {
|
||
margin-right: 22rpx;
|
||
width: 88rpx;
|
||
height: 88rpx;
|
||
border-radius: 28rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba($brand-primary, 0.1);
|
||
color: $brand-ink;
|
||
font-size: 24rpx;
|
||
font-weight: 700;
|
||
letter-spacing: 2rpx;
|
||
}
|
||
|
||
.section-copy {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 34rpx;
|
||
font-weight: 600;
|
||
color: $text-main;
|
||
}
|
||
|
||
.section-desc {
|
||
margin-top: 10rpx;
|
||
font-size: 24rpx;
|
||
line-height: 1.6;
|
||
color: $text-sub;
|
||
}
|
||
|
||
.section-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.section-body > .identity-panel + .form-item,
|
||
.section-body > .form-item + .form-item,
|
||
.section-body > .form-item + .identity-panel,
|
||
.section-body > .identity-panel + .identity-panel {
|
||
margin-top: 18rpx;
|
||
}
|
||
|
||
.identity-panel,
|
||
.form-item {
|
||
padding: 26rpx 24rpx 24rpx;
|
||
border-radius: 28rpx;
|
||
background: #FFFFFF;
|
||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.03);
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.identity-panel-label {
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
color: $text-light;
|
||
}
|
||
|
||
.identity-panel-tip {
|
||
margin-top: 16rpx;
|
||
font-size: 22rpx;
|
||
line-height: 1.6;
|
||
color: $text-sub;
|
||
}
|
||
|
||
.form-label {
|
||
margin-bottom: 16rpx;
|
||
font-size: 25rpx;
|
||
font-weight: 600;
|
||
color: $text-main;
|
||
}
|
||
|
||
.required-mark {
|
||
margin-left: 6rpx;
|
||
color: #F06E53;
|
||
}
|
||
|
||
.form-input {
|
||
height: 96rpx;
|
||
padding: 0 28rpx;
|
||
border-radius: 24rpx;
|
||
box-sizing: border-box;
|
||
background: $surface-solid;
|
||
border: 1px solid $line-soft;
|
||
color: $text-main;
|
||
font-size: 28rpx;
|
||
transition: all 0.25s ease;
|
||
}
|
||
|
||
.form-input:focus {
|
||
border-color: $brand-primary;
|
||
box-shadow: 0 4rpx 16rpx rgba($brand-primary, 0.15);
|
||
}
|
||
|
||
.input-placeholder {
|
||
color: #B2BECC;
|
||
}
|
||
|
||
.picker-input {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.picker-text-wrap {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
}
|
||
|
||
.picker-subtext {
|
||
margin-top: 6rpx;
|
||
font-size: 20rpx;
|
||
color: $text-light;
|
||
}
|
||
|
||
.picker-action-wrap {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
margin-left: 20rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.picker-action-text {
|
||
margin-right: 10rpx;
|
||
font-size: 22rpx;
|
||
color: $brand-ink;
|
||
}
|
||
|
||
.picker-input-disabled {
|
||
background: rgba($brand-primary, 0.05);
|
||
}
|
||
|
||
.picker-fixed-text,
|
||
.text-gray {
|
||
color: $text-light;
|
||
}
|
||
|
||
.text-black {
|
||
color: $text-main;
|
||
}
|
||
|
||
.picker-arrow {
|
||
font-size: 28rpx;
|
||
color: #9AA7B6;
|
||
}
|
||
|
||
.role-switch-wrap {
|
||
margin-top: 18rpx;
|
||
}
|
||
|
||
.role-switch {
|
||
padding: 10rpx;
|
||
border-radius: 24rpx;
|
||
background: rgba($brand-primary, 0.06);
|
||
}
|
||
|
||
.role-tab {
|
||
padding: 22rpx 0 20rpx;
|
||
border-radius: 18rpx;
|
||
text-align: center;
|
||
color: $text-sub;
|
||
font-size: 28rpx;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.role-tab-subtitle {
|
||
margin-top: 6rpx;
|
||
font-size: 20rpx;
|
||
color: $text-light;
|
||
}
|
||
|
||
.role-active {
|
||
background: #FFFFFF;
|
||
color: $brand-ink;
|
||
font-weight: 600;
|
||
border: 1rpx solid rgba($brand-primary, 0.16);
|
||
box-shadow: 0 8rpx 20rpx rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.role-active .role-tab-subtitle {
|
||
color: rgba($brand-primary, 0.7);
|
||
}
|
||
|
||
.gender-btn {
|
||
height: 96rpx;
|
||
line-height: 96rpx;
|
||
border-radius: 24rpx;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
background: #FFFFFF;
|
||
color: $text-sub;
|
||
border: 1px solid $line-soft;
|
||
}
|
||
|
||
.gender-active {
|
||
color: $brand-ink;
|
||
border-color: rgba($brand-primary, 0.3);
|
||
background: rgba($brand-primary, 0.08);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.tag-item {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 120rpx;
|
||
height: 72rpx;
|
||
padding: 0 34rpx;
|
||
margin-right: 18rpx;
|
||
margin-bottom: 10rpx;
|
||
border-radius: 999rpx;
|
||
background: #FFFFFF;
|
||
border: 1px solid $line-soft;
|
||
color: $text-sub;
|
||
font-size: 26rpx;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.tag-active {
|
||
background: rgba($brand-primary, 0.08);
|
||
border-color: rgba($brand-primary, 0.28);
|
||
color: $brand-ink;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.upload-box {
|
||
min-height: 260rpx;
|
||
padding: 40rpx 32rpx;
|
||
border-radius: 28rpx;
|
||
border: 1rpx dashed rgba($brand-primary, 0.24);
|
||
background:
|
||
linear-gradient(180deg, rgba($brand-primary, 0.03) 0%, #FFFFFF 100%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.upload-box:active {
|
||
transform: scale(0.995);
|
||
}
|
||
|
||
.upload-placeholder {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
}
|
||
|
||
.upload-icon-wrap {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
border-radius: 32rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba($brand-primary, 0.1);
|
||
color: $brand;
|
||
font-size: 42rpx;
|
||
}
|
||
|
||
.upload-text {
|
||
margin-top: 22rpx;
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: $text-main;
|
||
}
|
||
|
||
.upload-subtext {
|
||
margin-top: 10rpx;
|
||
font-size: 22rpx;
|
||
line-height: 1.5;
|
||
color: $text-sub;
|
||
}
|
||
|
||
.attachment-file-card {
|
||
overflow: hidden;
|
||
border-radius: 28rpx;
|
||
background: #FFFFFF;
|
||
border: 1rpx solid rgba($brand-primary, 0.1);
|
||
box-shadow: 0 8rpx 20rpx rgba($brand-primary, 0.05);
|
||
}
|
||
|
||
.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(180deg, rgba($brand-primary, 0.05) 0%, #FFFFFF 100%);
|
||
}
|
||
|
||
.attachment-file-cover.is-unsupported {
|
||
color: $brand-ink;
|
||
}
|
||
|
||
.attachment-file-ext {
|
||
font-size: 54rpx;
|
||
font-weight: 700;
|
||
letter-spacing: 4rpx;
|
||
}
|
||
|
||
.attachment-file-info {
|
||
padding: 24rpx 24rpx 18rpx;
|
||
}
|
||
|
||
.attachment-name {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: $text-main;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.attachment-type {
|
||
margin-top: 8rpx;
|
||
font-size: 24rpx;
|
||
line-height: 1.5;
|
||
color: $text-sub;
|
||
}
|
||
|
||
.attachment-action-row {
|
||
display: flex;
|
||
padding: 0 24rpx 24rpx;
|
||
}
|
||
|
||
.attachment-action-btn {
|
||
flex: 1;
|
||
height: 76rpx;
|
||
border-radius: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba($brand-primary, 0.04);
|
||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||
color: $text-main;
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.attachment-action-btn + .attachment-action-btn {
|
||
margin-left: 16rpx;
|
||
}
|
||
|
||
.attachment-action-btn.is-primary {
|
||
color: #FFFFFF;
|
||
background: linear-gradient(135deg, rgba($brand-primary, 0.9) 0%, $brand-primary 100%);
|
||
border-color: transparent;
|
||
}
|
||
|
||
.bottom-btn-area {
|
||
position: fixed;
|
||
left: 20rpx;
|
||
right: 20rpx;
|
||
bottom: 14rpx;
|
||
bottom: calc(env(safe-area-inset-bottom) + 14rpx);
|
||
z-index: 20;
|
||
}
|
||
|
||
.action-dock {
|
||
padding: 0;
|
||
border-radius: 26rpx;
|
||
background: transparent;
|
||
border: 0;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.step-action-row {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
width: 100%;
|
||
}
|
||
|
||
.step-action-row.single-action .step-btn {
|
||
width: 100%;
|
||
}
|
||
|
||
.custom-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 999rpx;
|
||
transition: transform 0.2s ease, opacity 0.2s ease;
|
||
}
|
||
|
||
.custom-btn:active {
|
||
transform: scale(0.992);
|
||
}
|
||
|
||
.submit-btn {
|
||
height: 96rpx;
|
||
flex: 1;
|
||
}
|
||
|
||
.step-btn {
|
||
min-width: 0;
|
||
}
|
||
|
||
.btn-secondary {
|
||
min-width: 196rpx;
|
||
background: #FFFFFF;
|
||
color: $brand-ink;
|
||
border: 1rpx solid rgba($brand-primary, 0.18);
|
||
}
|
||
|
||
.btn-primary {
|
||
background: linear-gradient(135deg, rgba($brand-primary, 0.9) 0%, $brand-primary 100%);
|
||
color: #FFFFFF;
|
||
box-shadow: 0 12rpx 32rpx rgba($brand-primary, 0.25);
|
||
}
|
||
|
||
.submit-btn-text {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
letter-spacing: 1rpx;
|
||
}
|
||
|
||
.step-btn-text {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.btn-disabled {
|
||
opacity: 0.56;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.animation-fade-in {
|
||
opacity: 0;
|
||
animation: fadeIn 0.55s ease forwards;
|
||
}
|
||
|
||
.country-selector-popup {
|
||
padding: 28rpx 24rpx 32rpx;
|
||
background: #F8FAFF;
|
||
}
|
||
|
||
.country-selector-header {
|
||
padding: 8rpx 12rpx 0;
|
||
}
|
||
|
||
.country-selector-title {
|
||
font-size: 34rpx;
|
||
font-weight: 600;
|
||
color: $text-main;
|
||
}
|
||
|
||
.country-selector-subtitle {
|
||
margin-top: 10rpx;
|
||
font-size: 24rpx;
|
||
color: $text-sub;
|
||
}
|
||
|
||
.country-search-box {
|
||
display: flex;
|
||
align-items: center;
|
||
height: 92rpx;
|
||
margin-top: 24rpx;
|
||
padding: 0 24rpx;
|
||
border-radius: 24rpx;
|
||
background: #FFFFFF;
|
||
border: 1rpx solid rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.country-search-icon {
|
||
font-size: 28rpx;
|
||
color: $text-light;
|
||
}
|
||
|
||
.country-search-input {
|
||
flex: 1;
|
||
min-width: 0;
|
||
height: 92rpx;
|
||
margin-left: 16rpx;
|
||
color: $text-main;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.country-search-clear {
|
||
margin-left: 16rpx;
|
||
font-size: 24rpx;
|
||
color: $brand-ink;
|
||
}
|
||
|
||
.country-selector-list {
|
||
max-height: 760rpx;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.country-option {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 24rpx 20rpx;
|
||
border-radius: 24rpx;
|
||
background: #FFFFFF;
|
||
border: 1rpx solid rgba($brand-primary, 0.06);
|
||
}
|
||
|
||
.country-option + .country-option {
|
||
margin-top: 14rpx;
|
||
}
|
||
|
||
.country-option.is-active {
|
||
border-color: rgba($brand-primary, 0.22);
|
||
box-shadow: 0 8rpx 20rpx rgba($brand-primary, 0.08);
|
||
}
|
||
|
||
.country-option-main {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.country-option-name {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: $text-main;
|
||
}
|
||
|
||
.country-option-meta {
|
||
margin-top: 8rpx;
|
||
font-size: 22rpx;
|
||
color: $text-sub;
|
||
}
|
||
|
||
.country-option-check {
|
||
margin-left: 20rpx;
|
||
font-size: 30rpx;
|
||
color: $brand;
|
||
}
|
||
|
||
.country-selector-empty {
|
||
padding: 72rpx 24rpx 96rpx;
|
||
font-size: 26rpx;
|
||
text-align: center;
|
||
color: $text-sub;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(18rpx);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.privacy-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 999;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40rpx;
|
||
background: rgba(18, 32, 51, 0.32);
|
||
backdrop-filter: blur(8rpx);
|
||
}
|
||
|
||
.privacy-dialog {
|
||
width: 100%;
|
||
max-width: 620rpx;
|
||
padding: 48rpx 40rpx 40rpx;
|
||
border-radius: 34rpx;
|
||
background: rgba(255, 255, 255, 0.96);
|
||
box-shadow: 0 26rpx 70rpx rgba(72, 108, 164, 0.18);
|
||
}
|
||
|
||
.privacy-title {
|
||
font-size: 34rpx;
|
||
font-weight: 600;
|
||
text-align: center;
|
||
color: $text-main;
|
||
}
|
||
|
||
.privacy-content {
|
||
margin-top: 28rpx;
|
||
font-size: 28rpx;
|
||
line-height: 1.72;
|
||
color: $text-sub;
|
||
}
|
||
|
||
.privacy-link {
|
||
color: $brand;
|
||
}
|
||
|
||
.privacy-action-row {
|
||
display: flex;
|
||
margin-top: 44rpx;
|
||
}
|
||
|
||
.privacy-action-btn {
|
||
flex: 1;
|
||
height: 90rpx;
|
||
border-radius: 999rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: $text-main;
|
||
background: #FFFFFF;
|
||
border: 1rpx solid rgba($brand-primary, 0.12);
|
||
}
|
||
|
||
.privacy-action-btn + .privacy-action-btn {
|
||
margin-left: 18rpx;
|
||
}
|
||
|
||
button.privacy-action-btn {
|
||
padding: 0;
|
||
line-height: 90rpx;
|
||
}
|
||
|
||
button.privacy-action-btn::after {
|
||
border: none;
|
||
}
|
||
|
||
.privacy-action-btn.is-primary {
|
||
color: #FFFFFF;
|
||
background: linear-gradient(135deg, rgba($brand-primary, 0.9) 0%, $brand-primary 100%);
|
||
border-color: transparent;
|
||
}
|
||
</style>
|