Files
vue-driven-cloud-storage/frontend/reset-password.html
Dev Team b7b00fff48 feat: 实现Vue驱动的云存储系统初始功能
- 后端: Node.js + Express + SQLite架构
- 前端: Vue 3 + Axios实现
- 功能: 用户认证、文件上传/下载、分享链接、密码重置
- 安全: 密码加密、分享链接过期机制、缓存一致性
- 部署: Docker + Nginx容器化配置
- 测试: 完整的边界测试、并发测试和状态一致性测试
2026-01-20 23:23:51 +08:00

435 lines
11 KiB
HTML
Raw Permalink 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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>重置密码 - 玩玩云</title>
<link rel="stylesheet" href="libs/fontawesome/css/all.min.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
/* 暗色主题(默认) */
:root {
--bg-primary: #0a0a0f;
--bg-secondary: #12121a;
--bg-card: rgba(255, 255, 255, 0.03);
--glass-border: rgba(255, 255, 255, 0.08);
--text-primary: #ffffff;
--text-secondary: rgba(255, 255, 255, 0.6);
--accent-1: #667eea;
--accent-2: #764ba2;
--glow: rgba(102, 126, 234, 0.4);
}
/* 亮色主题 */
.light-theme {
--bg-primary: #f0f4f8;
--bg-secondary: #ffffff;
--bg-card: rgba(255, 255, 255, 0.8);
--glass-border: rgba(102, 126, 234, 0.15);
--text-primary: #1a1a2e;
--text-secondary: rgba(26, 26, 46, 0.7);
--accent-1: #5a67d8;
--accent-2: #6b46c1;
--glow: rgba(90, 103, 216, 0.3);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
/* 动态背景 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
background:
radial-gradient(ellipse at top right, rgba(102, 126, 234, 0.15) 0%, transparent 50%),
radial-gradient(ellipse at bottom left, rgba(118, 75, 162, 0.15) 0%, transparent 50%),
var(--bg-primary);
}
body.light-theme::before {
background:
radial-gradient(ellipse at top right, rgba(102, 126, 234, 0.12) 0%, transparent 50%),
radial-gradient(ellipse at bottom left, rgba(118, 75, 162, 0.12) 0%, transparent 50%),
linear-gradient(135deg, #e0e7ff 0%, #f0f4f8 50%, #fdf2f8 100%);
}
.container {
max-width: 450px;
width: 100%;
}
.card {
background: var(--bg-card);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: 20px;
padding: 40px;
text-align: center;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
body.light-theme .card {
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.1);
}
.logo {
font-size: 48px;
margin-bottom: 20px;
background: linear-gradient(135deg, var(--accent-1), var(--accent-2));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.title {
font-size: 24px;
font-weight: 700;
margin-bottom: 10px;
color: var(--text-primary);
}
.subtitle {
color: var(--text-secondary);
margin-bottom: 30px;
font-size: 14px;
}
.status-icon {
font-size: 64px;
margin-bottom: 20px;
}
.status-icon.loading {
color: var(--accent-1);
animation: spin 1s linear infinite;
}
.status-icon.success {
color: #10b981;
}
.status-icon.error {
color: #ef4444;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.message {
font-size: 16px;
margin-bottom: 30px;
line-height: 1.6;
}
.form-group {
margin-bottom: 20px;
text-align: left;
}
.form-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--text-secondary);
}
.form-input {
width: 100%;
padding: 14px 16px;
border-radius: 12px;
border: 1px solid var(--glass-border);
background: var(--bg-secondary);
color: var(--text-primary);
font-size: 15px;
transition: all 0.3s;
}
body.light-theme .form-input {
background: rgba(255, 255, 255, 0.8);
border-color: rgba(102, 126, 234, 0.2);
}
.form-input:focus {
outline: none;
border-color: var(--accent-1);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 14px 32px;
border-radius: 12px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
text-decoration: none;
border: none;
width: 100%;
}
.btn-primary {
background: linear-gradient(135deg, var(--accent-1), var(--accent-2));
color: white;
box-shadow: 0 4px 15px var(--glow);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px var(--glow);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.password-hint {
font-size: 12px;
color: var(--text-secondary);
margin-top: 8px;
}
.footer {
margin-top: 20px;
color: var(--text-secondary);
font-size: 13px;
}
.footer a {
color: var(--accent-1);
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
.alert {
padding: 12px 16px;
border-radius: 10px;
margin-bottom: 20px;
font-size: 14px;
}
.alert-error {
background: rgba(239, 68, 68, 0.15);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #fca5a5;
}
body.light-theme .alert-error {
color: #dc2626;
}
.alert-success {
background: rgba(16, 185, 129, 0.15);
border: 1px solid rgba(16, 185, 129, 0.3);
color: #6ee7b7;
}
body.light-theme .alert-success {
color: #059669;
}
.hidden { display: none !important; }
</style>
</head>
<body>
<div class="container">
<div class="card">
<div class="logo">
<i class="fas fa-cloud"></i>
</div>
<h1 class="title">重置密码</h1>
<p class="subtitle">设置您的新密码</p>
<!-- 加载状态 -->
<div id="loading">
<div class="status-icon loading">
<i class="fas fa-spinner"></i>
</div>
<p class="message">正在验证链接...</p>
</div>
<!-- 错误状态 -->
<div id="error" class="hidden">
<div class="status-icon error">
<i class="fas fa-times-circle"></i>
</div>
<p class="message" id="errorMessage">链接无效或已过期</p>
<a href="app.html" class="btn btn-primary">
<i class="fas fa-arrow-left"></i> 返回登录
</a>
</div>
<!-- 表单 -->
<div id="form" class="hidden">
<div id="formAlert" class="alert hidden"></div>
<form onsubmit="handleSubmit(event)">
<div class="form-group">
<label class="form-label">新密码</label>
<input type="password" id="password" class="form-input"
placeholder="请输入新密码" required minlength="6">
<div class="password-hint">密码长度至少6位</div>
</div>
<div class="form-group">
<label class="form-label">确认密码</label>
<input type="password" id="confirmPassword" class="form-input"
placeholder="请再次输入新密码" required>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary">
<i class="fas fa-check"></i> 确认重置
</button>
</form>
</div>
<!-- 成功状态 -->
<div id="success" class="hidden">
<div class="status-icon success">
<i class="fas fa-check-circle"></i>
</div>
<p class="message">密码重置成功!<br>请使用新密码登录。</p>
<a href="app.html" class="btn btn-primary">
<i class="fas fa-right-to-bracket"></i> 前往登录
</a>
</div>
</div>
<div class="footer">
<a href="index.html"><i class="fas fa-arrow-left"></i> 返回首页</a>
</div>
</div>
<script>
let resetToken = '';
// 加载全局主题
async function loadTheme() {
try {
const res = await fetch('/api/public/theme');
const data = await res.json();
if (data.success && data.theme === 'light') {
document.body.classList.add('light-theme');
}
} catch (e) {
console.warn('[主题加载] 失败,使用默认主题:', e.message);
}
}
// 获取URL参数
function getParam(name) {
const url = new URL(window.location.href);
return url.searchParams.get(name);
}
// 显示指定区块
function showSection(id) {
['loading', 'error', 'form', 'success'].forEach(s => {
document.getElementById(s).classList.add('hidden');
});
document.getElementById(id).classList.remove('hidden');
}
// 显示表单提示
function showFormAlert(type, message) {
const alert = document.getElementById('formAlert');
alert.className = `alert alert-${type}`;
alert.textContent = message;
alert.classList.remove('hidden');
}
// 验证token
async function validateToken() {
resetToken = getParam('resetToken') || getParam('token');
if (!resetToken) {
document.getElementById('errorMessage').textContent = '无效的重置链接,缺少令牌';
showSection('error');
return;
}
// Token存在显示表单
showSection('form');
}
// 提交表单
async function handleSubmit(e) {
e.preventDefault();
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
const submitBtn = document.getElementById('submitBtn');
// 验证
if (password.length < 6) {
showFormAlert('error', '密码长度至少6位');
return;
}
if (password !== confirmPassword) {
showFormAlert('error', '两次输入的密码不一致');
return;
}
// 提交
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 处理中...';
try {
const res = await fetch('/api/password/reset', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: resetToken,
new_password: password
})
});
const data = await res.json();
if (data.success) {
showSection('success');
} else {
showFormAlert('error', data.message || '重置失败,请重试');
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-check"></i> 确认重置';
}
} catch (error) {
showFormAlert('error', '网络错误,请检查网络连接后重试');
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-check"></i> 确认重置';
}
}
// 初始化
loadTheme();
validateToken();
</script>
</body>
</html>