f103148ebf
基于 Flask + MySQL + Bootstrap 5 的全栈个人资料库管理系统。 主要功能: - 管理员/普通用户双角色权限体系,全站登录保护 - 资源管理:文本、图片、音频、视频四类资源 - 三种添加方式:本地上传(拖拽)、URL 后台下载、磁力下载(aria2c) - 在线预览:文本、图片、HTML5 音视频播放器 - 安全:bcrypt 加盐密码哈希、CSRF 防护、SQLAlchemy ORM 防注入 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
123 lines
5.2 KiB
JavaScript
123 lines
5.2 KiB
JavaScript
/* ═══════════════════════════════════════════════════
|
|
个人资料库 — 主脚本
|
|
═══════════════════════════════════════════════════ */
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
// ── 侧边栏折叠/展开 ──────────────────────────────────────────────────────
|
|
const sidebar = document.getElementById('sidebar');
|
|
const toggleBtn = document.getElementById('sidebarToggle');
|
|
const overlay = document.createElement('div');
|
|
overlay.className = 'sidebar-overlay';
|
|
document.body.appendChild(overlay);
|
|
|
|
if (toggleBtn && sidebar) {
|
|
const isMobile = () => window.innerWidth <= 768;
|
|
const STORAGE_KEY = 'sidebar_collapsed';
|
|
|
|
// 恢复桌面端折叠状态
|
|
if (!isMobile() && localStorage.getItem(STORAGE_KEY) === 'true') {
|
|
sidebar.classList.add('collapsed');
|
|
}
|
|
|
|
toggleBtn.addEventListener('click', () => {
|
|
if (isMobile()) {
|
|
sidebar.classList.toggle('mobile-open');
|
|
overlay.classList.toggle('active');
|
|
} else {
|
|
sidebar.classList.toggle('collapsed');
|
|
localStorage.setItem(STORAGE_KEY,
|
|
sidebar.classList.contains('collapsed') ? 'true' : 'false');
|
|
}
|
|
});
|
|
|
|
overlay.addEventListener('click', () => {
|
|
sidebar.classList.remove('mobile-open');
|
|
overlay.classList.remove('active');
|
|
});
|
|
|
|
window.addEventListener('resize', () => {
|
|
if (!isMobile()) {
|
|
sidebar.classList.remove('mobile-open');
|
|
overlay.classList.remove('active');
|
|
}
|
|
});
|
|
}
|
|
|
|
// ── 深色/浅色主题切换 ──────────────────────────────────────────────────────
|
|
const themeToggle = document.getElementById('themeToggle');
|
|
const themeIcon = document.getElementById('themeIcon');
|
|
const html = document.documentElement;
|
|
const THEME_KEY = 'theme';
|
|
|
|
function applyTheme(theme) {
|
|
html.setAttribute('data-bs-theme', theme);
|
|
if (themeIcon) {
|
|
themeIcon.className = theme === 'dark' ? 'bi bi-moon-fill' : 'bi bi-sun-fill';
|
|
}
|
|
localStorage.setItem(THEME_KEY, theme);
|
|
}
|
|
|
|
// 初始化主题
|
|
const savedTheme = localStorage.getItem(THEME_KEY) ||
|
|
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
|
applyTheme(savedTheme);
|
|
|
|
if (themeToggle) {
|
|
themeToggle.addEventListener('click', () => {
|
|
applyTheme(html.getAttribute('data-bs-theme') === 'dark' ? 'light' : 'dark');
|
|
});
|
|
}
|
|
|
|
// ── 自动关闭 Flash 消息 ───────────────────────────────────────────────────
|
|
document.querySelectorAll('.alert.alert-success, .alert.alert-info').forEach(el => {
|
|
setTimeout(() => {
|
|
const bsAlert = bootstrap.Alert.getOrCreateInstance(el);
|
|
if (bsAlert) bsAlert.close();
|
|
}, 4000);
|
|
});
|
|
|
|
// ── 表单提交防重复点击 ────────────────────────────────────────────────────
|
|
document.querySelectorAll('form').forEach(form => {
|
|
form.addEventListener('submit', () => {
|
|
const btn = form.querySelector('[type="submit"]');
|
|
if (btn && !btn.dataset.noDisable) {
|
|
setTimeout(() => { btn.disabled = true; }, 0);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ── 密码强度指示(注册/修改密码页面)────────────────────────────────────
|
|
const pwdInputs = document.querySelectorAll('input[type="password"][id$="Pwd"],' +
|
|
'input[type="password"][id$="Password"]');
|
|
pwdInputs.forEach(input => {
|
|
const meter = document.createElement('div');
|
|
meter.className = 'password-strength mt-1';
|
|
meter.innerHTML = '<div class="strength-bar d-flex gap-1 mt-1">' +
|
|
'<div class="flex-fill rounded" style="height:4px;transition:background .3s"></div>'.repeat(4) +
|
|
'</div>';
|
|
input.parentElement.appendChild(meter);
|
|
|
|
input.addEventListener('input', () => {
|
|
const val = input.value;
|
|
let strength = 0;
|
|
if (val.length >= 8) strength++;
|
|
if (/[A-Z]/.test(val)) strength++;
|
|
if (/[0-9]/.test(val)) strength++;
|
|
if (/[^A-Za-z0-9]/.test(val)) strength++;
|
|
|
|
const colors = ['#ef4444','#f97316','#eab308','#22c55e'];
|
|
const bars = meter.querySelectorAll('.flex-fill');
|
|
bars.forEach((bar, i) => {
|
|
bar.style.background = i < strength ? colors[strength - 1] : '#e5e7eb';
|
|
});
|
|
});
|
|
});
|
|
|
|
// ── 工具提示初始化 ────────────────────────────────────────────────────────
|
|
document.querySelectorAll('[title]').forEach(el => {
|
|
new bootstrap.Tooltip(el, { trigger: 'hover', placement: 'top' });
|
|
});
|
|
|
|
});
|