Files
jiapuapp/pages/auth/a04-register.vue
T
2026-07-21 11:49:46 +08:00

462 lines
11 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>
<AuthPageShell class="auth-page register-page">
<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>
<template #overlay>
<view v-if="feedbackVisible" class="feedback-toast">
<text class="feedback-toast__copy">{{ feedbackMessage }}</text>
</view>
</template>
</AuthPageShell>
</template>
<script setup>
import { ref } from "vue";
import { onUnload } from "@dcloudio/uni-app";
import AuthPageShell from "@/components/AuthPageShell.vue";
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">
.register-content {
display: flex;
flex: 1;
flex-direction: column;
justify-content: flex-start;
box-sizing: border-box;
width: 100%;
max-width: 480px;
min-width: 0;
margin: 0 auto;
padding: clamp(10px, 2.2vh, 20px) clamp(24px, 8.5vw, 44px);
}
.page-heading {
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: auto auto auto;
justify-items: center;
min-height: 154rpx;
}
.back-button {
display: flex;
grid-row: 1 / -1;
grid-column: 1;
align-self: start;
justify-self: start;
align-items: center;
justify-content: center;
width: var(--app-touch-min);
height: var(--app-touch-min);
margin-top: 10rpx;
}
.back-button__icon {
width: 42rpx;
height: 42rpx;
transform: scaleX(-1);
}
.page-title {
grid-column: 1;
grid-row: 1;
color: #9f170f;
font-size: 58rpx;
font-weight: 700;
letter-spacing: 7rpx;
}
.page-subtitle {
grid-column: 1;
grid-row: 2;
margin-top: 12rpx;
color: #806c58;
font-size: 25rpx;
letter-spacing: 2rpx;
}
.register-divider {
grid-column: 1;
grid-row: 3;
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: var(--app-touch-min);
border-bottom: 1rpx solid rgba(182, 116, 43, 0.68);
}
.input-label {
flex: 0 0 152rpx;
color: #674b35;
font-size: 30rpx;
}
.auth-input {
flex: 1;
min-width: 0;
min-height: var(--app-touch-min);
color: #493323;
font-size: 30rpx;
}
.placeholder {
color: #9f968d;
}
.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 {
display: grid;
place-items: center;
min-height: var(--app-touch-min);
margin-top: 36rpx;
}
.register-submit__skin {
grid-area: 1 / 1;
width: 100%;
height: 100%;
}
.register-submit__content {
z-index: 1;
grid-area: 1 / 1;
color: #fffaf0;
font-size: 36rpx;
letter-spacing: 6rpx;
}
.agreement-area {
min-height: 118rpx;
margin-top: 18rpx;
}
.agreement-row {
display: flex;
align-items: center;
justify-content: center;
min-height: var(--app-touch-min);
color: #493323;
font-size: 24rpx;
line-height: 30rpx;
}
.agreement-icon {
flex: 0 0 auto;
width: 34rpx;
height: 34rpx;
margin-right: 10rpx;
}
.agreement-copy {
display: flex;
align-items: center;
}
.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;
margin-top: auto;
color: #493323;
font-size: 28rpx;
}
.login-entry__link {
display: flex;
align-items: center;
min-height: var(--app-touch-min);
margin-left: 8rpx;
color: #a7160c;
}
.tap-fade {
opacity: 0.62;
}
.button-hover {
opacity: 0.84;
}
.feedback-toast {
position: fixed;
z-index: 30;
top: calc(var(--status-bar-height, 0px) + 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 {
z-index: 1;
padding: 16rpx 36rpx;
color: #5c4330;
font-size: 25rpx;
text-align: center;
}
</style>