Files
ai-app-database/app/static/js/main.js
T
huty f103148ebf feat: 初始化个人资料库 Web 应用
基于 Flask + MySQL + Bootstrap 5 的全栈个人资料库管理系统。

主要功能:
- 管理员/普通用户双角色权限体系,全站登录保护
- 资源管理:文本、图片、音频、视频四类资源
- 三种添加方式:本地上传(拖拽)、URL 后台下载、磁力下载(aria2c)
- 在线预览:文本、图片、HTML5 音视频播放器
- 安全:bcrypt 加盐密码哈希、CSRF 防护、SQLAlchemy ORM 防注入

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 00:16:59 +09:00

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' });
});
});