Files

217 lines
9.0 KiB
HTML
Raw Permalink Normal View History

2026-04-02 00:59:42 +08:00
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>修改密码 - 用户认证</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f0f2f5; display: flex; align-items: center; justify-content: center; min-height: 100vh; }
.container { background: #fff; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.1); padding: 40px 36px; width: 400px; }
.logo { text-align: center; margin-bottom: 28px; }
.logo h1 { font-size: 24px; color: #1a1a2e; font-weight: 600; }
.logo p { font-size: 13px; color: #888; margin-top: 4px; }
.form-group { margin-bottom: 16px; }
label { display: block; font-size: 14px; color: #333; margin-bottom: 6px; font-weight: 500; }
input { width: 100%; height: 44px; padding: 0 14px; border: 1px solid #ddd; border-radius: 8px; font-size: 15px; outline: none; transition: border-color 0.2s; }
input:focus { border-color: #4f46e5; }
.sms-row { display: flex; gap: 10px; }
.sms-row input { flex: 1; }
.btn-sms { height: 44px; padding: 0 16px; background: #e8e7ff; color: #4f46e5; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; white-space: nowrap; flex-shrink: 0; }
.btn-sms:hover { background: #dddbf0; }
.btn-sms:disabled { background: #f0f0f0; color: #aaa; cursor: not-allowed; }
.btn { width: 100%; height: 46px; background: #4f46e5; color: #fff; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: background 0.2s; margin-top: 10px; }
.btn:hover { background: #4338ca; }
.btn:disabled { background: #a5a6f6; cursor: not-allowed; }
.links { text-align: center; margin-top: 16px; font-size: 13px; }
.links a { color: #4f46e5; text-decoration: none; }
.links a:hover { text-decoration: underline; }
.msg { padding: 10px 14px; border-radius: 8px; font-size: 13px; margin-bottom: 16px; display: none; }
.msg.error { background: #fef2f2; color: #dc2626; border: 1px solid #fca5a5; display: block; }
.msg.success { background: #f0fdf4; color: #16a34a; border: 1px solid #86efac; display: block; }
.loading { display: none; text-align: center; margin-top: 10px; font-size: 13px; color: #888; }
.tip { font-size: 12px; color: #888; margin-top: 4px; }
.step-indicator { display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 24px; }
.step { width: 28px; height: 28px; border-radius: 50%; background: #e8e7ff; color: #4f46e5; font-size: 13px; font-weight: 600; display: flex; align-items: center; justify-content: center; }
.step.active { background: #4f46e5; color: #fff; }
.step.done { background: #16a34a; color: #fff; }
.step-line { width: 40px; height: 2px; background: #e8e7ff; }
.step-line.done { background: #16a34a; }
</style>
</head>
<body>
<div class="container">
<div class="logo">
<h1>修改密码</h1>
<p>通过短信验证码验证身份</p>
</div>
<div class="step-indicator">
<div class="step active" id="step1">1</div>
<div class="step-line" id="line1"></div>
<div class="step" id="step2">2</div>
<div class="step-line" id="line2"></div>
<div class="step" id="step3">3</div>
</div>
<div id="msg" class="msg"></div>
<form id="form">
<!-- Step 1: verify phone & sms -->
<div id="step1Content">
<div class="form-group">
<label for="phone">手机号</label>
<input type="tel" id="phone" placeholder="请输入注册手机号" maxlength="11" required autocomplete="tel">
</div>
<div class="form-group">
<label for="smsCode">短信验证码</label>
<div class="sms-row">
<input type="text" id="smsCode" placeholder="请输入验证码" maxlength="6" required>
<button type="button" class="btn-sms" id="sendBtn">发送验证码</button>
</div>
</div>
<button type="button" class="btn" id="verifyBtn">验证</button>
</div>
<!-- Step 2: set new password -->
<div id="step2Content" style="display:none">
<div class="form-group">
<label for="newPassword">新密码</label>
<input type="password" id="newPassword" placeholder="6位及以上密码" minlength="6" required>
<p class="tip">建议混合字母和数字</p>
</div>
<div class="form-group">
<label for="confirmPassword">确认密码</label>
<input type="password" id="confirmPassword" placeholder="再次输入新密码" minlength="6" required>
</div>
<button type="submit" class="btn" id="submitBtn">确认修改</button>
</div>
</form>
<div class="links">
<a href="login.html">返回登录</a>
</div>
<div id="loading" class="loading">处理中...</div>
</div>
<script>
const API = 'http://localhost:3000/api/auth';
let verified = false;
let smsCountdown = 0;
let smsTimer = null;
let currentPhone = '';
const msg = document.getElementById('msg');
const sendBtn = document.getElementById('sendBtn');
const verifyBtn = document.getElementById('verifyBtn');
const submitBtn = document.getElementById('submitBtn');
const form = document.getElementById('form');
const step1 = document.getElementById('step1Content');
const step2 = document.getElementById('step2Content');
const stepEls = [
document.getElementById('step1'),
document.getElementById('step2'),
document.getElementById('step3')
];
const lineEls = [document.getElementById('line1'), document.getElementById('line2')];
function showMsg(text, type) {
msg.textContent = text;
msg.className = 'msg ' + type;
msg.style.display = 'block';
}
function hideMsg() { msg.style.display = 'none'; }
function setStep(n) {
stepEls.forEach((el, i) => {
el.className = 'step' + (i < n ? ' done' : (i === n ? ' active' : ''));
});
lineEls.forEach((el, i) => {
el.className = 'step-line' + (i < n ? ' done' : '');
});
}
sendBtn.addEventListener('click', async () => {
if (smsCountdown > 0) return;
const phone = document.getElementById('phone').value.trim();
if (!/^1[3-9]\d{9}$/.test(phone)) { showMsg('请输入正确的手机号', 'error'); return; }
currentPhone = phone;
sendBtn.disabled = true;
sendBtn.textContent = '发送中...';
try {
// Send code - simplified without captcha for password change flow
const res = await fetch(API + '/send-code', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone, captchaKey: 'change-pwd', captchaCode: '0000' })
});
const data = await res.json();
if (data.code === 0) {
showMsg('验证码已发送', 'success');
smsCountdown = 60;
smsTimer = setInterval(() => {
smsCountdown--;
if (smsCountdown <= 0) {
clearInterval(smsTimer);
sendBtn.textContent = '发送验证码';
sendBtn.disabled = false;
} else {
sendBtn.textContent = smsCountdown + '秒后重试';
}
}, 1000);
} else {
showMsg(data.message || '发送失败', 'error');
sendBtn.textContent = '发送验证码';
sendBtn.disabled = false;
}
} catch {
showMsg('网络错误,请检查服务器', 'error');
sendBtn.textContent = '发送验证码';
sendBtn.disabled = false;
}
});
verifyBtn.addEventListener('click', async () => {
const phone = document.getElementById('phone').value.trim();
const code = document.getElementById('smsCode').value.trim();
if (!phone) { showMsg('请输入手机号', 'error'); return; }
if (!/^\d{4,6}$/.test(code)) { showMsg('请输入验证码', 'error'); return; }
verified = true;
currentPhone = phone;
step1.style.display = 'none';
step2.style.display = 'block';
setStep(1);
showMsg('身份验证通过,请设置新密码', 'success');
});
form.addEventListener('submit', async (e) => {
e.preventDefault();
if (!verified) return;
hideMsg();
const newPwd = document.getElementById('newPassword').value;
const confirmPwd = document.getElementById('confirmPassword').value;
if (newPwd.length < 6) { showMsg('密码至少6位', 'error'); return; }
if (newPwd !== confirmPwd) { showMsg('两次输入的密码不一致', 'error'); return; }
submitBtn.disabled = true;
submitBtn.textContent = '修改中...';
try {
const res = await fetch(API + '/change-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone: currentPhone, code: document.getElementById('smsCode').value.trim(), newPassword: newPwd })
});
const data = await res.json();
if (data.code === 0) {
setStep(2);
showMsg('密码修改成功!即将跳转到登录页...', 'success');
setTimeout(() => { window.location.href = 'login.html'; }, 1500);
} else {
showMsg(data.message || '修改失败', 'error');
}
} catch {
showMsg('网络错误,请检查服务器是否启动', 'error');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = '确认修改';
}
});
</script>
</body>
</html>