You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
498 lines
16 KiB
498 lines
16 KiB
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>忘记密码 - 青核 Youthome</title>
|
|
<link rel="stylesheet" href="../css/design-system.css">
|
|
<style>
|
|
.auth-page {
|
|
width: 1920px;
|
|
min-height: 1080px;
|
|
margin: 0 auto;
|
|
background: var(--bg-page);
|
|
display: flex;
|
|
align-items: stretch;
|
|
justify-content: center;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.auth-page::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -200px;
|
|
right: -200px;
|
|
width: 600px;
|
|
height: 600px;
|
|
border-radius: 50%;
|
|
background: radial-gradient(circle, rgba(23, 176, 207, 0.08) 0%, transparent 70%);
|
|
}
|
|
|
|
/* 左右分栏容器 */
|
|
.auth-layout {
|
|
width: 100%;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
position: relative;
|
|
z-index: 1;
|
|
box-sizing: border-box;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* 左侧品牌区 */
|
|
.auth-brand-side {
|
|
position: relative;
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 50%, #BCEAF3 100%);
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
padding: 80px 64px;
|
|
overflow: hidden;
|
|
}
|
|
.auth-brand-side::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -120px; left: -120px;
|
|
width: 400px; height: 400px;
|
|
border-radius: 50%;
|
|
background: rgba(255,255,255,0.1);
|
|
}
|
|
.auth-brand-side::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: -150px; right: -100px;
|
|
width: 500px; height: 500px;
|
|
border-radius: 50%;
|
|
background: rgba(255,255,255,0.08);
|
|
}
|
|
.auth-brand-top { position: relative; z-index: 2; }
|
|
.auth-brand-logo {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
color: #fff;
|
|
margin-bottom: 80px;
|
|
}
|
|
.auth-brand-logo-icon {
|
|
width: 48px; height: 48px;
|
|
border-radius: 12px;
|
|
background: rgba(255,255,255,0.2);
|
|
backdrop-filter: blur(10px);
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 22px; font-weight: 700;
|
|
border: 1px solid rgba(255,255,255,0.3);
|
|
}
|
|
.auth-brand-logo-text { font-size: 20px; font-weight: 700; }
|
|
|
|
.auth-brand-hero { color: #fff; }
|
|
.auth-brand-hero h1 {
|
|
font-size: 42px;
|
|
font-weight: 700;
|
|
line-height: 1.25;
|
|
margin-bottom: 16px;
|
|
}
|
|
.auth-brand-hero p {
|
|
font-size: 16px;
|
|
line-height: 1.7;
|
|
opacity: 0.9;
|
|
max-width: 420px;
|
|
}
|
|
|
|
.auth-brand-features {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 18px;
|
|
position: relative;
|
|
z-index: 2;
|
|
color: #fff;
|
|
}
|
|
.auth-brand-feature {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
}
|
|
.auth-brand-feature-icon {
|
|
width: 40px; height: 40px;
|
|
border-radius: 10px;
|
|
background: rgba(255,255,255,0.2);
|
|
backdrop-filter: blur(8px);
|
|
display: flex; align-items: center; justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.auth-brand-feature-icon svg { width: 20px; height: 20px; color: #fff; }
|
|
.auth-brand-feature-text h4 { font-size: 15px; font-weight: 600; margin-bottom: 2px; }
|
|
.auth-brand-feature-text p { font-size: 12px; opacity: 0.8; }
|
|
|
|
/* 右侧表单区 */
|
|
.auth-form-side {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 40px;
|
|
}
|
|
|
|
.auth-card {
|
|
width: 420px;
|
|
background: var(--bg-card);
|
|
border-radius: 20px;
|
|
padding: 48px 40px;
|
|
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.06);
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.back-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: 13px;
|
|
color: var(--text-tertiary);
|
|
cursor: pointer;
|
|
margin-bottom: 20px;
|
|
transition: color var(--transition-fast);
|
|
}
|
|
|
|
.back-link:hover { color: var(--primary); }
|
|
.back-link svg { width: 16px; height: 16px; }
|
|
|
|
.auth-header { margin-bottom: 28px; }
|
|
|
|
.auth-header h2 {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.auth-header p { font-size: 13px; color: var(--text-tertiary); }
|
|
|
|
.form-group { margin-bottom: 16px; }
|
|
|
|
.form-label {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 8px;
|
|
display: block;
|
|
}
|
|
|
|
.step-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
margin-bottom: 28px;
|
|
}
|
|
|
|
.step-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.step-dot {
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 50%;
|
|
background: var(--bg-hover);
|
|
color: var(--text-tertiary);
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.step-item.active .step-dot {
|
|
background: var(--primary);
|
|
color: #fff;
|
|
box-shadow: 0 2px 8px rgba(23, 176, 207, 0.3);
|
|
}
|
|
|
|
.step-item.done .step-dot {
|
|
background: var(--success-bg);
|
|
color: var(--success);
|
|
}
|
|
|
|
.step-label {
|
|
font-size: 12px;
|
|
color: var(--text-tertiary);
|
|
}
|
|
|
|
.step-item.active .step-label { color: var(--primary); font-weight: 500; }
|
|
|
|
.step-line {
|
|
width: 32px;
|
|
height: 1px;
|
|
background: var(--border);
|
|
}
|
|
|
|
.step-item.done + .step-line { background: var(--success); }
|
|
|
|
.success-icon {
|
|
width: 64px;
|
|
height: 64px;
|
|
border-radius: 50%;
|
|
background: var(--success-bg);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin: 0 auto 20px;
|
|
}
|
|
|
|
.success-icon svg {
|
|
width: 32px;
|
|
height: 32px;
|
|
color: var(--success);
|
|
}
|
|
|
|
.success-text {
|
|
text-align: center;
|
|
margin-bottom: 28px;
|
|
}
|
|
|
|
.success-text h3 {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.success-text p {
|
|
font-size: 13px;
|
|
color: var(--text-tertiary);
|
|
}
|
|
|
|
.auth-footer {
|
|
text-align: center;
|
|
margin-top: 20px;
|
|
font-size: 13px;
|
|
color: var(--text-tertiary);
|
|
}
|
|
|
|
.auth-footer a { color: var(--primary); font-weight: 500; }
|
|
</style>
|
|
<link rel="stylesheet" href="../css/web-refresh.css?v=20261020-brand-color">
|
|
</head>
|
|
<body>
|
|
<div class="auth-page">
|
|
<div class="auth-layout">
|
|
<!-- 左侧品牌区 -->
|
|
<div class="auth-brand-side">
|
|
<div class="auth-brand-top">
|
|
<div class="auth-brand-logo">
|
|
<div class="auth-brand-logo-icon">青</div>
|
|
<div class="auth-brand-logo-text">青核 Youthome</div>
|
|
</div>
|
|
<div class="auth-brand-hero">
|
|
<h1>找回密码<br>安全便捷的账号恢复</h1>
|
|
<p>通过手机号验证快速重置你的密码,我们严格保障你的账号安全与隐私。</p>
|
|
</div>
|
|
</div>
|
|
<div class="auth-brand-features">
|
|
<div class="auth-brand-feature">
|
|
<div class="auth-brand-feature-icon">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
|
|
</div>
|
|
<div class="auth-brand-feature-text">
|
|
<h4>安全验证</h4>
|
|
<p>多重身份验证,确保你的账号安全</p>
|
|
</div>
|
|
</div>
|
|
<div class="auth-brand-feature">
|
|
<div class="auth-brand-feature-icon">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
|
|
</div>
|
|
<div class="auth-brand-feature-text">
|
|
<h4>快速找回</h4>
|
|
<p>手机验证码一键重置,简单快捷</p>
|
|
</div>
|
|
</div>
|
|
<div class="auth-brand-feature">
|
|
<div class="auth-brand-feature-icon">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path><circle cx="12" cy="16" r="1"></circle></svg>
|
|
</div>
|
|
<div class="auth-brand-feature-text">
|
|
<h4>隐私保护</h4>
|
|
<p>全程加密传输,严格保护个人信息</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右侧表单区 -->
|
|
<div class="auth-form-side">
|
|
<div class="auth-card">
|
|
<div class="back-link" onclick="goLogin()">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<polyline points="15 18 9 12 15 6"></polyline>
|
|
</svg>
|
|
返回登录
|
|
</div>
|
|
|
|
<div class="auth-header">
|
|
<h2>找回密码</h2>
|
|
<p>通过手机号验证重置你的密码</p>
|
|
</div>
|
|
|
|
<!-- 步骤指示器 -->
|
|
|
|
|
|
<!-- Step 1: 验证身份 -->
|
|
<div id="panel-step1">
|
|
<div class="form-group">
|
|
<label class="form-label">手机号</label>
|
|
<div class="input-wrap">
|
|
<input type="tel" class="input input-icon-left" placeholder="请输入注册手机号">
|
|
<svg class="input-icon left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">验证码</label>
|
|
<div class="input-wrap">
|
|
<input type="text" class="input input-icon-left" placeholder="请输入6位验证码" style="padding-right: 110px;">
|
|
<svg class="input-icon left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M9 12l2 2 4-4"></path>
|
|
<path d="M21 12c0 4.97-4.03 9-9 9s-9-4.03-9-9 4.03-9 9-9c2.05 0 3.94.69 5.5 1.86"></path>
|
|
</svg>
|
|
<button class="input-suffix-btn" onclick="sendCode(this)">获取验证码</button>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="btn btn-primary btn-block btn-lg" style="margin-top: 8px;" onclick="goStep(2)">下一步</button>
|
|
</div>
|
|
|
|
<!-- Step 2: 重置密码 -->
|
|
<div id="panel-step2" style="display:none;">
|
|
<div class="form-group">
|
|
<label class="form-label">新密码</label>
|
|
<div class="input-wrap">
|
|
<input type="password" class="input input-icon-left" placeholder="8-20位,包含字母和数字">
|
|
<svg class="input-icon left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
|
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">确认新密码</label>
|
|
<div class="input-wrap">
|
|
<input type="password" class="input input-icon-left" placeholder="请再次输入新密码">
|
|
<svg class="input-icon left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
|
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 12px; margin-top: 20px;">
|
|
<button class="btn btn-secondary btn-lg" style="flex:1;" onclick="goStep(1)">上一步</button>
|
|
<button class="btn btn-primary btn-lg" style="flex:1;" onclick="goStep(3)">确认重置</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 3: 完成 -->
|
|
<div id="panel-step3" style="display:none;">
|
|
<div class="success-icon">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
<polyline points="20 6 9 17 4 12"></polyline>
|
|
</svg>
|
|
</div>
|
|
<div class="success-text">
|
|
<h3>密码重置成功</h3>
|
|
<p>请使用新密码重新登录你的账号</p>
|
|
</div>
|
|
<button class="btn btn-primary btn-block btn-lg" onclick="goLogin()">返回登录</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let countdown = 0;
|
|
let resetting = false;
|
|
function resetToast(message) {
|
|
document.querySelector('.web-toast')?.remove();
|
|
const node = document.createElement('div');
|
|
node.className = 'web-toast is-error';
|
|
node.setAttribute('role', 'status');
|
|
node.textContent = message;
|
|
document.body.appendChild(node);
|
|
setTimeout(() => node.remove(), 3500);
|
|
}
|
|
async function sendCode(btn) {
|
|
if (countdown > 0) return;
|
|
const phone = document.querySelector('#panel-step1 input[type="tel"]')?.value.trim();
|
|
if (!/^1\d{10}$/.test(phone || '')) { resetToast('请输入正确的手机号'); return; }
|
|
btn.disabled = true;
|
|
try {
|
|
await window.YouthomeApi.request('/auth/captcha?phone=' + encodeURIComponent(phone));
|
|
} catch (error) {
|
|
btn.disabled = false;
|
|
resetToast(error.message || '验证码发送失败');
|
|
return;
|
|
}
|
|
countdown = 60;
|
|
btn.textContent = countdown + 's';
|
|
const timer = setInterval(() => {
|
|
countdown--;
|
|
if (countdown <= 0) {
|
|
clearInterval(timer);
|
|
btn.disabled = false;
|
|
btn.textContent = '获取验证码';
|
|
} else {
|
|
btn.textContent = countdown + 's';
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
async function goStep(step) {
|
|
if (step === 2) {
|
|
const phone = document.querySelector('#panel-step1 input[type="tel"]')?.value.trim();
|
|
const captcha = document.querySelector('#panel-step1 input[placeholder*="验证码"]')?.value.trim();
|
|
if (!/^1\d{10}$/.test(phone || '')) { resetToast('请输入正确的手机号'); return; }
|
|
if (!/^\d{6}$/.test(captcha || '')) { resetToast('请输入6位验证码'); return; }
|
|
}
|
|
if (step === 3) {
|
|
if (resetting) return;
|
|
const phone = document.querySelector('#panel-step1 input[type="tel"]')?.value.trim();
|
|
const captcha = document.querySelector('#panel-step1 input[placeholder*="验证码"]')?.value.trim();
|
|
const newPassword = document.querySelector('#panel-step2 input[type="password"]')?.value || '';
|
|
const confirmPassword = document.querySelectorAll('#panel-step2 input[type="password"]')[1]?.value || '';
|
|
if (!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,20}$/.test(newPassword)) { resetToast('密码需为8-20位,包含字母和数字'); return; }
|
|
if (newPassword !== confirmPassword) { resetToast('两次输入的密码不一致'); return; }
|
|
resetting = true;
|
|
try {
|
|
await window.YouthomeApi.request('/auth/reset-password', {
|
|
method: 'POST', body: JSON.stringify({ phone, captcha, newPassword })
|
|
});
|
|
} catch (error) {
|
|
resetToast(error.message || '密码重置失败');
|
|
resetting = false;
|
|
return;
|
|
}
|
|
resetting = false;
|
|
}
|
|
document.getElementById('panel-step1').style.display = 'none';
|
|
document.getElementById('panel-step2').style.display = 'none';
|
|
document.getElementById('panel-step3').style.display = 'none';
|
|
document.getElementById('panel-step' + step).style.display = 'block';
|
|
|
|
}
|
|
|
|
function goLogin() {
|
|
window.location.href = 'login.html';
|
|
}
|
|
</script>
|
|
<script src="../js/web-local-data.js?v=20261016-local" defer></script>
|
|
<script src="../js/web-api.js?v=20261020-api" defer></script>
|
|
<script src="../js/web-shell.js" defer></script>
|
|
</body>
|
|
</html>
|
|
|