Files
jiapuapp/pages/auth/a04-register.vue
T
2026-07-16 07:58:53 +08:00

469 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 页面编号A-04用途注册账号与本页服务协议确认 -->
<template>
<view class="auth-page register-page">
<view class="page-canvas">
<image class="page-backdrop" src="/static/assets/modules/auth/opaque/a01-red-hall-ink-backdrop-v1.png" mode="scaleToFill" />
<image class="brand-seal" src="/static/assets/foundation/transparent/brand-seal.png" mode="aspectFit" />
<view class="register-content">
<view class="page-heading">
<view class="back-button" hover-class="tap-fade" @click="goBack">
<image class="back-button__icon" src="/static/assets/foundation/transparent/chevron-right.png" mode="aspectFit" />
</view>
<text class="page-title">注册账号</text>
<text class="page-subtitle">创建属于你的家谱账号</text>
<image class="register-divider" src="/static/assets/foundation/transparent/auth-divider-knot.png" mode="aspectFit" />
</view>
<view class="form-content">
<view class="field-block" :class="{ 'field-block--error': fieldErrors.phone }">
<view class="input-row">
<text class="input-label">手机号</text>
<input v-model.trim="phone" class="auth-input" type="number" maxlength="11" placeholder="请输入手机号" placeholder-class="placeholder" @input="clearFieldError('phone')" />
</view>
<text v-if="fieldErrors.phone" class="field-error">{{ fieldErrors.phone }}</text>
</view>
<view class="field-block" :class="{ 'field-block--error': fieldErrors.password }">
<view class="input-row">
<text class="input-label">设置密码</text>
<input v-model="password" class="auth-input" password maxlength="32" placeholder="请设置登录密码" placeholder-class="placeholder" @input="clearFieldError('password')" />
</view>
<text v-if="fieldErrors.password" class="field-error">{{ fieldErrors.password }}</text>
</view>
<view class="field-block" :class="{ 'field-block--error': fieldErrors.confirmPassword }">
<view class="input-row">
<text class="input-label">确认密码</text>
<input v-model="confirmPassword" class="auth-input" password maxlength="32" placeholder="请再次输入密码" placeholder-class="placeholder" @input="clearFieldError('confirmPassword')" />
</view>
<text v-if="fieldErrors.confirmPassword" class="field-error">{{ fieldErrors.confirmPassword }}</text>
</view>
</view>
<view class="register-submit" hover-class="button-hover" @click="submitRegister">
<image class="register-submit__skin" src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" />
<text class="register-submit__content">注册账号</text>
</view>
<view class="agreement-area" :class="{ 'agreement-area--error': agreementError }">
<view class="agreement-row" @click="toggleAgreement">
<image class="agreement-icon" :src="agreed ? '/static/assets/modules/auth/transparent/a02-agreement-checked.png' : '/static/assets/modules/auth/transparent/a02-agreement-unchecked.png'" mode="aspectFit" />
<view class="agreement-copy">
<text>我已阅读并同意</text>
<text class="agreement-link" @click.stop="prepareAgreement">用户协议</text>
<text></text>
<text class="agreement-link" @click.stop="prepareAgreement">隐私政策</text>
</view>
</view>
<text v-if="agreementError" class="agreement-error">请先阅读并同意用户协议与隐私政策</text>
</view>
<view class="login-entry">
<text>已有账号</text>
<view class="login-entry__link" hover-class="tap-fade" @click="prepareLogin">登录</view>
</view>
</view>
</view>
<!-- 注册短信接口未提供前完整本地校验通过后使用同系列行为验证容器 -->
<view v-if="verificationVisible" class="verification-layer" @click="closeVerification">
<view class="verification-dialog" @click.stop>
<image class="verification-dialog__skin" src="/static/assets/modules/auth/opaque/a02-login-panel.png" mode="scaleToFill" />
<view class="verification-dialog__content">
<text class="verification-title">安全验证</text>
<text class="verification-copy">注册前需要完成行为验证</text>
<text class="verification-note">验证数据将在接口接入后启用</text>
<view class="verification-actions">
<view class="verification-action" hover-class="tap-fade" @click="closeVerification">取消</view>
<view class="verification-action verification-action--primary" hover-class="tap-fade" @click="confirmVerification">知道了</view>
</view>
</view>
</view>
</view>
<view v-if="feedbackVisible" class="feedback-toast">
<image class="feedback-toast__skin" src="/static/assets/foundation/opaque/a01-secondary-button.png" mode="scaleToFill" />
<text class="feedback-toast__copy">{{ feedbackMessage }}</text>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onUnload } from '@dcloudio/uni-app'
const phone = ref('')
const password = ref('')
const confirmPassword = ref('')
const agreed = ref(false)
const agreementError = ref(false)
const fieldErrors = ref({
phone: '',
password: '',
confirmPassword: ''
})
const verificationVisible = ref(false)
const feedbackVisible = ref(false)
const feedbackMessage = ref('')
let feedbackTimer = null
onUnload(() => {
if (feedbackTimer) clearTimeout(feedbackTimer)
})
const goBack = () => {
uni.navigateBack()
}
// “已有账号”用于退出注册流程,替换当前页可避免登录/注册页面反复叠加。
const prepareLogin = () => uni.redirectTo({ url: '/pages/auth/a01-entry' })
const showFeedback = (message) => {
feedbackMessage.value = message
feedbackVisible.value = true
if (feedbackTimer) clearTimeout(feedbackTimer)
feedbackTimer = setTimeout(() => {
feedbackVisible.value = false
feedbackTimer = null
}, 2200)
}
const prepareAgreement = () => showFeedback('协议页面准备中')
const toggleAgreement = () => {
agreed.value = !agreed.value
if (agreed.value) agreementError.value = false
}
const clearFieldError = (field) => {
fieldErrors.value[field] = ''
}
const validateForm = () => {
const nextErrors = { phone: '', password: '', confirmPassword: '' }
if (!/^1\d{10}$/.test(phone.value)) nextErrors.phone = '请输入正确手机号'
if (!password.value) nextErrors.password = '请设置密码'
if (!confirmPassword.value) nextErrors.confirmPassword = '请再次输入密码'
else if (password.value !== confirmPassword.value) nextErrors.confirmPassword = '两次输入的密码不一致'
fieldErrors.value = nextErrors
return !Object.values(nextErrors).some(Boolean)
}
const closeVerification = () => {
verificationVisible.value = false
}
const confirmVerification = () => {
closeVerification()
showFeedback('注册服务待接入')
}
const submitRegister = () => {
const formValid = validateForm()
if (!agreed.value) agreementError.value = true
if (!formValid || !agreed.value) return
verificationVisible.value = true
}
</script>
<style scoped lang="scss">
.auth-page {
position: relative;
width: 100%;
height: 100dvh;
min-height: 100%;
overflow-x: hidden;
overflow-y: auto;
background: #f7f0e4;
color: #493323;
}
.page-canvas {
position: relative;
box-sizing: border-box;
width: 100%;
min-height: calc(1665rpx + env(safe-area-inset-bottom));
padding-bottom: env(safe-area-inset-bottom);
}
.page-backdrop {
position: absolute;
inset: 0 0 auto;
width: 100%;
height: 1665rpx;
pointer-events: none;
}
.brand-seal {
position: absolute;
z-index: 1;
top: 64rpx;
left: 50%;
width: 220rpx;
height: 264rpx;
transform: translateX(-50%);
pointer-events: none;
}
.register-content {
position: relative;
z-index: 2;
box-sizing: border-box;
padding: 430rpx 64rpx 46rpx;
}
.page-heading {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
min-height: 154rpx;
}
.back-button {
position: absolute;
top: 10rpx;
left: 0;
display: flex;
align-items: center;
justify-content: center;
width: 88rpx;
height: 88rpx;
}
.back-button__icon {
width: 42rpx;
height: 42rpx;
transform: scaleX(-1);
}
.page-title {
color: #9f170f;
font-family: STSong, SimSun, serif;
font-size: 66rpx;
font-weight: 700;
letter-spacing: 7rpx;
}
.page-subtitle {
margin-top: 12rpx;
color: #806c58;
font-size: 25rpx;
letter-spacing: 2rpx;
}
.register-divider {
width: 154rpx;
height: 34rpx;
margin-top: 10rpx;
}
.form-content { margin-top: 12rpx; }
.field-block { min-height: 116rpx; }
.input-row {
display: flex;
align-items: center;
min-height: 104rpx;
border-bottom: 1rpx solid rgba(182, 116, 43, 0.68);
}
.input-label {
flex: 0 0 152rpx;
color: #674b35;
font-family: STSong, SimSun, serif;
font-size: 31rpx;
}
.auth-input {
flex: 1;
min-width: 0;
height: 96rpx;
color: #3f2c1d;
font-size: 29rpx;
}
.placeholder { color: #a79e94; }
.field-error,
.agreement-error {
display: block;
color: #b42318;
font-size: 21rpx;
line-height: 32rpx;
}
.field-error { padding-top: 4rpx; text-align: right; }
.field-block--error .input-row { border-bottom-color: #b42318; }
.register-submit {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 104rpx;
margin-top: 36rpx;
overflow: hidden;
}
.register-submit__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.register-submit__content {
position: relative;
z-index: 1;
color: #fffaf0;
font-family: STSong, SimSun, serif;
font-size: 39rpx;
letter-spacing: 6rpx;
}
.agreement-area { min-height: 118rpx; margin-top: 18rpx; }
.agreement-row {
display: flex;
align-items: center;
justify-content: center;
min-height: 76rpx;
color: #58483c;
font-size: 22rpx;
line-height: 30rpx;
}
.agreement-icon {
flex: 0 0 auto;
width: 34rpx;
height: 34rpx;
margin-right: 10rpx;
}
.agreement-copy {
display: flex;
align-items: center;
white-space: nowrap;
}
.agreement-link { color: #a7160c; }
.agreement-error { text-align: center; }
.agreement-area--error { animation: agreement-shake 240ms ease-out; }
@keyframes agreement-shake {
0%, 100% { transform: translateX(0); }
33% { transform: translateX(-8rpx); }
66% { transform: translateX(8rpx); }
}
.login-entry {
display: flex;
align-items: center;
justify-content: center;
min-height: 82rpx;
color: #6b5a4a;
font-size: 25rpx;
}
.login-entry__link {
display: flex;
align-items: center;
min-height: 82rpx;
margin-left: 8rpx;
color: #a7160c;
}
.tap-fade { opacity: 0.62; }
.button-hover { opacity: 0.84; }
.feedback-toast {
position: fixed;
z-index: 30;
top: calc(env(safe-area-inset-top) + 24rpx);
left: 50%;
display: flex;
align-items: center;
justify-content: center;
width: 590rpx;
min-height: 82rpx;
transform: translateX(-50%);
}
.feedback-toast__skin,
.verification-dialog__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.feedback-toast__copy {
position: relative;
z-index: 1;
padding: 16rpx 36rpx;
color: #5c4330;
font-size: 25rpx;
text-align: center;
}
.verification-layer {
position: fixed;
z-index: 40;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
background: rgba(31, 20, 13, 0.56);
}
.verification-dialog {
position: relative;
width: 620rpx;
height: 520rpx;
}
.verification-dialog__content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
height: 100%;
padding: 90rpx 64rpx 54rpx;
}
.verification-title {
color: #6f140f;
font-family: STSong, SimSun, serif;
font-size: 42rpx;
font-weight: 700;
}
.verification-copy { margin-top: 42rpx; color: #493323; font-size: 28rpx; }
.verification-note { margin-top: 20rpx; color: #8d7a68; font-size: 23rpx; }
.verification-actions {
display: flex;
width: 100%;
margin-top: auto;
}
.verification-action {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
min-height: 88rpx;
color: #745b46;
font-size: 28rpx;
}
.verification-action--primary { color: #a9160d; font-weight: 700; }
</style>