Files
jiapuapp/pages/auth/a04-register.vue
T
2026-07-16 18:13:03 +08:00

385 lines
10 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/modules/auth/transparent/a01-vnext-divider-v1.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/transparent/a01-scroll-primary-v3.png" mode="aspectFit" />
<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="feedbackVisible" class="feedback-toast">
<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 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 submitRegister = () => {
const formValid = validateForm()
if (!agreed.value) agreementError.value = true
if (!formValid || !agreed.value) return
// 真实滑动验证由接口阶段的组件自带样式接管,本页不保留自定义假面板。
showFeedback('滑动验证待接口接入')
}
</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: 184rpx;
height: 221rpx;
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-size: 66rpx;
font-weight: 700;
letter-spacing: 7rpx;
}
.page-subtitle {
margin-top: 12rpx;
color: #806c58;
font-size: 25rpx;
letter-spacing: 2rpx;
}
.register-divider {
width: 300rpx;
height: 50rpx;
filter: brightness(0.68) saturate(1.5) contrast(1.2);
}
.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-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-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;
box-sizing: border-box;
width: 590rpx;
min-height: 82rpx;
border: 16rpx solid transparent;
border-width: 16rpx 74rpx;
border-image-source: url('/static/assets/foundation/transparent/a01-scroll-toast-v3.png');
border-image-slice: 58 280 fill;
border-image-width: 16rpx 74rpx;
border-image-repeat: stretch;
background: transparent;
transform: translateX(-50%);
}
.feedback-toast__copy {
position: relative;
z-index: 1;
padding: 16rpx 36rpx;
color: #5c4330;
font-size: 25rpx;
text-align: center;
}
</style>