Files
jiapuapp/pages/auth/a05-reset-password.vue
T
2026-07-20 06:52:26 +08:00

565 lines
13 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-05用途通过手机号验证后重设登录密码 -->
<template>
<view class="auth-page reset-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="reset-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="reset-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.verificationCode }"
>
<view class="input-row code-row">
<text class="input-label">验证码</text>
<input
v-model.trim="verificationCode"
class="auth-input"
type="number"
maxlength="6"
placeholder="请输入验证码"
placeholder-class="placeholder"
@input="clearFieldError('verificationCode')"
/>
<view
class="get-code"
hover-class="tap-fade"
@click="prepareGetCode"
>获取验证码</view
>
</view>
<text v-if="fieldErrors.verificationCode" class="field-error">{{
fieldErrors.verificationCode
}}</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="reset-submit"
hover-class="button-hover"
@click="submitReset"
>
<image
class="reset-submit__skin"
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text class="reset-submit__content">确认重设</text>
</view>
<text class="reset-tip">重设成功后请使用新密码登录</text>
<view class="login-entry">
<text>想起密码了</text>
<view
class="login-entry__link"
hover-class="tap-fade"
@click="prepareLogin"
>返回登录</view
>
</view>
</view>
</view>
<view v-if="successVisible" class="success-layer">
<view class="success-dialog">
<image
class="success-dialog__skin"
src="/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png"
mode="aspectFit"
/>
<view class="success-dialog__content">
<text class="success-mark"></text>
<text class="success-title">密码已重设</text>
<text class="success-copy">请返回登录页使用新密码登录</text>
<view
class="success-action"
hover-class="button-hover"
@click="prepareLogin"
>
<image
class="success-action__skin"
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text class="success-action__copy">返回登录</text>
</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 verificationCode = ref("");
const password = ref("");
const confirmPassword = ref("");
const successVisible = ref(false);
const feedbackVisible = ref(false);
const feedbackMessage = ref("");
const fieldErrors = ref({
phone: "",
verificationCode: "",
password: "",
confirmPassword: "",
});
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 clearFieldError = (field) => {
fieldErrors.value[field] = "";
};
const prepareGetCode = () => {
if (!/^1\d{10}$/.test(phone.value)) {
fieldErrors.value.phone = "请输入正确手机号";
return;
}
fieldErrors.value.phone = "";
showFeedback("滑动验证待接口接入");
};
const validateForm = () => {
const nextErrors = {
phone: "",
verificationCode: "",
password: "",
confirmPassword: "",
};
if (!/^1\d{10}$/.test(phone.value)) nextErrors.phone = "请输入正确手机号";
if (!/^\d{6}$/.test(verificationCode.value))
nextErrors.verificationCode = "请输入 6 位验证码";
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 submitReset = () => {
if (!validateForm()) return;
successVisible.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: 184rpx;
height: 221rpx;
transform: translateX(-50%);
pointer-events: none;
}
.reset-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: 1rpx;
}
.reset-divider {
width: 300rpx;
height: 50rpx;
margin-top: 2rpx;
filter: brightness(0.68) saturate(1.5) contrast(1.2);
}
.form-content {
margin-top: 8rpx;
}
.field-block {
min-height: 114rpx;
}
.input-row {
display: flex;
align-items: center;
min-height: 84rpx;
border-bottom: 1rpx solid #cfaa70;
}
.input-label {
flex: 0 0 162rpx;
color: #674b35;
font-size: 29rpx;
}
.auth-input {
flex: 1;
min-width: 0;
height: 82rpx;
color: #3f2c1d;
font-size: 27rpx;
}
.placeholder {
color: #a79e94;
}
.get-code {
flex: 0 0 auto;
padding-left: 14rpx;
border-left: 1rpx solid #d7bd94;
color: #a7160c;
font-size: 23rpx;
}
.field-error {
display: block;
padding-top: 4rpx;
color: #b42318;
font-size: 21rpx;
line-height: 28rpx;
text-align: right;
}
.field-block--error .input-row {
border-bottom-color: #b42318;
}
.reset-submit {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 104rpx;
margin-top: 26rpx;
overflow: hidden;
}
.reset-submit__skin,
.success-action__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.reset-submit__content {
position: relative;
z-index: 1;
color: #fffaf0;
font-size: 39rpx;
letter-spacing: 6rpx;
}
.reset-tip {
display: block;
margin-top: 22rpx;
color: #806c58;
font-size: 22rpx;
text-align: center;
}
.login-entry {
display: flex;
align-items: center;
justify-content: center;
min-height: 78rpx;
color: #6b5a4a;
font-size: 24rpx;
}
.login-entry__link {
display: flex;
align-items: center;
min-height: 78rpx;
margin-left: 8rpx;
color: #a7160c;
}
.success-layer {
position: fixed;
z-index: 20;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 42rpx;
background: rgba(35, 18, 10, 0.62);
}
.success-dialog {
position: relative;
width: 620rpx;
height: 520rpx;
}
.success-dialog__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.success-dialog__content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
height: 100%;
padding: 64rpx 64rpx 42rpx;
text-align: center;
}
.success-title {
color: #8f160f;
font-size: 43rpx;
font-weight: 700;
letter-spacing: 4rpx;
}
.success-copy {
margin-top: 18rpx;
color: #513a28;
font-size: 26rpx;
line-height: 40rpx;
}
.success-mark {
display: flex;
align-items: center;
justify-content: center;
width: 82rpx;
height: 82rpx;
margin-bottom: 20rpx;
border: 2rpx solid #a7160c;
border-radius: 50%;
color: #a7160c;
font-size: 48rpx;
}
.success-action {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 82rpx;
margin-top: 24rpx;
overflow: hidden;
}
.success-action__copy {
position: relative;
z-index: 1;
color: #fffaf0;
font-size: 30rpx;
letter-spacing: 4rpx;
}
.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;
}
.tap-fade,
.button-hover {
opacity: 0.72;
}
@media screen and (max-width: 340px) {
.reset-content {
padding-right: 48rpx;
padding-left: 48rpx;
}
.page-title {
font-size: 60rpx;
}
.input-label {
flex-basis: 150rpx;
}
}
</style>