217 lines
9.0 KiB
HTML
217 lines
9.0 KiB
HTML
|
|
<!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>
|