Review changes batch 5 of 6

This commit is contained in:
2026-07-20 06:52:26 +08:00
parent 0d0645a112
commit db97d3da27
41 changed files with 6672 additions and 1124 deletions
+374 -99
View File
@@ -2,76 +2,174 @@
<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" />
<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" />
<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" />
<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="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')" />
<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>
<text v-if="fieldErrors.phone" class="field-error">{{
fieldErrors.phone
}}</text>
</view>
<view class="field-block" :class="{ 'field-block--error': fieldErrors.verificationCode }">
<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>
<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>
<text v-if="fieldErrors.verificationCode" class="field-error">{{
fieldErrors.verificationCode
}}</text>
</view>
<view class="field-block" :class="{ 'field-block--error': fieldErrors.password }">
<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')" />
<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>
<text v-if="fieldErrors.password" class="field-error">{{
fieldErrors.password
}}</text>
</view>
<view class="field-block" :class="{ 'field-block--error': fieldErrors.confirmPassword }">
<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')" />
<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>
<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" />
<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
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" />
<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" />
<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>
@@ -85,71 +183,78 @@
</template>
<script setup>
import { ref } from 'vue'
import { onUnload } from '@dcloudio/uni-app'
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 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
phone: "",
verificationCode: "",
password: "",
confirmPassword: "",
});
let feedbackTimer = null;
onUnload(() => {
if (feedbackTimer) clearTimeout(feedbackTimer)
})
if (feedbackTimer) clearTimeout(feedbackTimer);
});
const goBack = () => uni.navigateBack()
const goBack = () => uni.navigateBack();
// 返回登录时替换当前认证页,避免登录与重设页反复叠加。
const prepareLogin = () => uni.redirectTo({ url: '/pages/auth/a01-entry' })
const prepareLogin = () => uni.redirectTo({ url: "/pages/auth/a01-entry" });
const showFeedback = (message) => {
feedbackMessage.value = message
feedbackVisible.value = true
if (feedbackTimer) clearTimeout(feedbackTimer)
feedbackMessage.value = message;
feedbackVisible.value = true;
if (feedbackTimer) clearTimeout(feedbackTimer);
feedbackTimer = setTimeout(() => {
feedbackVisible.value = false
feedbackTimer = null
}, 2200)
}
feedbackVisible.value = false;
feedbackTimer = null;
}, 2200);
};
const clearFieldError = (field) => {
fieldErrors.value[field] = ''
}
fieldErrors.value[field] = "";
};
const prepareGetCode = () => {
if (!/^1\d{10}$/.test(phone.value)) {
fieldErrors.value.phone = '请输入正确手机号'
return
fieldErrors.value.phone = "请输入正确手机号";
return;
}
fieldErrors.value.phone = ''
showFeedback('滑动验证待接口接入')
}
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 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
}
if (!validateForm()) return;
successVisible.value = true;
};
</script>
<style scoped lang="scss">
@@ -217,12 +322,35 @@ const submitReset = () => {
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; }
.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;
@@ -231,12 +359,39 @@ const submitReset = () => {
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; }
.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;
@@ -249,11 +404,41 @@ const submitReset = () => {
}
.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-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;
@@ -267,23 +452,113 @@ const submitReset = () => {
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; }
.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; }
.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; }
.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; }
.reset-content {
padding-right: 48rpx;
padding-left: 48rpx;
}
.page-title {
font-size: 60rpx;
}
.input-label {
flex-basis: 150rpx;
}
}
</style>