diff --git a/static/css/style.css b/static/css/style.css index 8a6fc2a..48b3850 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -430,102 +430,117 @@ body { height: 320px; } -/* ===== 读书排行 ===== */ -.top-books-section { - padding: 60px 0; - background: var(--accent-bg); - transition: background-color 0.3s ease; +/* ===== 标签页切换 ===== */ +.tab-panel { + display: none; + animation: fadeInTab 0.3s ease; } -.top-books-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - gap: 20px; +.tab-panel.active { + display: block; } -.top-book-card { +@keyframes fadeInTab { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + +/* ===== 读书排行(竖向列表) ===== */ +.top-books-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.top-book-row { + display: flex; + align-items: center; + gap: 16px; background: var(--bg-card); border-radius: var(--radius); - overflow: hidden; + padding: 12px 16px; box-shadow: var(--shadow); - transition: transform 0.2s, box-shadow 0.3s, background-color 0.3s, border-color 0.3s; border: 1px solid var(--border); cursor: pointer; + transition: transform 0.2s, box-shadow 0.3s, background-color 0.3s, border-color 0.3s; } -.top-book-card:hover { - transform: translateY(-4px); +.top-book-row:hover { + transform: translateX(4px); box-shadow: var(--shadow-hover); + border-color: var(--accent); } -.top-book-rank { - position: absolute; - top: 8px; - left: 8px; - background: var(--rank-bg); - color: var(--rank-text); - width: 28px; - height: 28px; +.top-book-rank-num { + flex-shrink: 0; + width: 36px; + height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; - font-size: 14px; - font-weight: 700; - z-index: 5; + font-size: 16px; + font-weight: 800; + background: var(--accent-bg); + color: var(--accent-dark); } -.top-book-cover-wrapper { - position: relative; - width: 100%; - aspect-ratio: 3 / 4; - border-radius: var(--radius-sm) var(--radius-sm) 0 0; - overflow: hidden; +.top-book-row:nth-child(1) .top-book-rank-num { + background: linear-gradient(135deg, #ffd700, #ffa500); + color: #fff; +} + +.top-book-row:nth-child(2) .top-book-rank-num { + background: linear-gradient(135deg, #c0c0c0, #a8a8a8); + color: #fff; +} + +.top-book-row:nth-child(3) .top-book-rank-num { + background: linear-gradient(135deg, #cd7f32, #b87333); + color: #fff; +} + +.top-book-mini-cover { + flex-shrink: 0; + width: 48px; + height: 64px; + border-radius: 6px; + object-fit: cover; + box-shadow: var(--shadow); background: var(--bg-primary); } -.top-book-cover { - width: 100%; - height: 100%; - object-fit: cover; - transition: transform 0.3s; +.top-book-row-info { + flex: 1; + min-width: 0; } -.top-book-card:hover .top-book-cover { - transform: scale(1.05); -} - -.top-book-info { - padding: 14px 16px; -} - -.top-book-title { - font-size: 14px; +.top-book-row-title { + font-size: 15px; font-weight: 600; color: var(--text-primary); - margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.top-book-author { +.top-book-row-meta { + display: flex; + align-items: center; + gap: 12px; + margin-top: 4px; font-size: 12px; color: var(--text-light); - margin-bottom: 8px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } -.top-book-time { +.top-book-row-time { font-size: 13px; color: var(--accent); font-weight: 600; + flex-shrink: 0; } -.top-book-tags { - margin-top: 6px; +.top-book-row-tags { display: flex; gap: 4px; flex-wrap: wrap; @@ -1354,6 +1369,324 @@ body { font-size: 14px; } +/* ===== 想法详情独立页面 ===== */ +.thoughts-page { + min-height: 100vh; + background: var(--bg-primary); +} + +.thoughts-page-header { + background: var(--bg-card); + border-bottom: 1px solid var(--border); + box-shadow: var(--shadow); + padding: 16px 0 20px; + position: sticky; + top: 60px; + z-index: 50; +} + +.back-btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 16px; + border: 1px solid var(--border); + border-radius: 20px; + background: var(--bg-card); + color: var(--text-secondary); + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.2s; + margin-bottom: 16px; +} + +.back-btn:hover { + border-color: var(--accent); + color: var(--accent); + background: var(--accent-bg); +} + +.thoughts-book-header { + display: flex; + gap: 16px; + align-items: center; +} + +.thoughts-book-cover { + width: 64px; + height: 85px; + border-radius: 8px; + object-fit: cover; + flex-shrink: 0; + box-shadow: var(--shadow); +} + +.thoughts-book-title { + font-size: 20px; + font-weight: 700; + color: var(--text-primary); +} + +.thoughts-book-author { + font-size: 13px; + color: var(--text-light); + margin-top: 4px; +} + +.thoughts-book-stats { + display: flex; + gap: 16px; + margin-top: 8px; + font-size: 13px; + color: var(--text-secondary); +} + +.thoughts-book-stats strong { + color: var(--accent-dark); +} + +/* 想法页面分类筛选 */ +.thoughts-filters { + display: flex; + gap: 8px; + margin: 24px 0 20px; + flex-wrap: wrap; +} + +.thought-filter-btn { + padding: 6px 18px; + border: 1px solid var(--border); + border-radius: 20px; + background: var(--bg-card); + color: var(--text-secondary); + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.2s; + display: flex; + align-items: center; + gap: 6px; +} + +.thought-filter-btn .filter-count { + font-size: 12px; + background: var(--accent-bg); + color: var(--accent-dark); + padding: 1px 8px; + border-radius: 10px; +} + +.thought-filter-btn.active { + background: var(--accent); + color: #fff; + border-color: var(--accent); +} + +.thought-filter-btn.active .filter-count { + background: rgba(255,255,255,0.25); + color: #fff; +} + +.thought-filter-btn:hover:not(.active) { + border-color: var(--accent); + color: var(--accent); +} + +/* 分类区块标题 */ +.thoughts-section-title { + font-size: 16px; + font-weight: 700; + color: var(--text-primary); + margin: 28px 0 16px; + padding-bottom: 10px; + border-bottom: 2px solid var(--border); + display: flex; + align-items: center; + gap: 8px; +} + +.thoughts-section-title .section-badge { + font-size: 12px; + font-weight: 600; + padding: 2px 10px; + border-radius: 10px; +} + +.badge-highlight { + background: var(--accent-bg-hover); + color: var(--accent-dark); +} + +.badge-review { + background: var(--rating-bg); + color: var(--rating-color); +} + +.badge-thought { + background: rgba(76, 175, 80, 0.12); + color: #4caf50; +} + +[data-theme="dark"] .badge-thought { + background: rgba(76, 175, 80, 0.15); + color: #66bb6a; +} + +/* 划线条目 */ +.thought-highlight-item { + padding: 14px 18px; + background: var(--chart-highlight-bg); + border-radius: var(--radius-sm); + margin-bottom: 10px; + font-size: 15px; + color: var(--text-primary); + line-height: 1.8; + border-left: 4px solid var(--accent-light); + transition: background-color 0.3s; +} + +.thought-highlight-meta { + font-size: 12px; + color: var(--text-light); + margin-top: 8px; + display: flex; + gap: 12px; +} + +/* 书评条目 */ +.thought-review-item { + padding: 16px 20px; + background: var(--chart-review-bg); + border-radius: var(--radius-sm); + margin-bottom: 10px; + font-size: 15px; + color: var(--text-primary); + line-height: 1.8; + border-left: 4px solid var(--rating-star); + transition: background-color 0.3s; +} + +.thought-review-stars { + color: var(--rating-star); + font-size: 14px; + margin-bottom: 6px; +} + +.thought-review-meta { + font-size: 12px; + color: var(--text-light); + margin-top: 8px; + display: flex; + gap: 12px; + flex-wrap: wrap; +} + +/* 想法条目 */ +.thought-thought-item { + padding: 16px 20px; + background: var(--bg-card); + border-radius: var(--radius-sm); + margin-bottom: 10px; + border-left: 4px solid #4caf50; + transition: background-color 0.3s, border-color 0.3s; +} + +[data-theme="dark"] .thought-thought-item { + border-left-color: #66bb6a; +} + +.thought-thought-abstract { + font-size: 13px; + color: var(--text-light); + background: var(--bg-secondary); + padding: 8px 12px; + border-radius: 6px; + margin-bottom: 10px; + line-height: 1.6; + border-left: 3px solid var(--border); +} + +.thought-thought-content { + font-size: 15px; + color: var(--text-primary); + line-height: 1.8; +} + +.thought-thought-meta { + font-size: 12px; + color: var(--text-light); + margin-top: 8px; + display: flex; + gap: 12px; + flex-wrap: wrap; +} + +.thoughts-empty { + text-align: center; + padding: 60px 0; + color: var(--text-light); + font-size: 15px; +} + +/* 章节分组 */ +.thoughts-chapter-group { + margin-bottom: 24px; +} + +.thoughts-chapter-title { + font-size: 14px; + font-weight: 600; + color: var(--accent-dark); + margin-bottom: 10px; + padding: 6px 0 6px 12px; + border-left: 4px solid var(--accent); +} + +/* ===== 回到顶部按钮 ===== */ +.back-to-top { + position: fixed; + bottom: 32px; + right: 32px; + width: 48px; + height: 48px; + border-radius: 50%; + background: var(--accent); + color: #fff; + border: none; + cursor: pointer; + font-size: 22px; + line-height: 1; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + z-index: 90; + opacity: 0; + visibility: hidden; + transform: translateY(20px); + transition: opacity 0.3s, visibility 0.3s, transform 0.3s, background-color 0.3s; +} + +.back-to-top.visible { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.back-to-top:hover { + background: var(--accent-dark); + transform: translateY(-2px); +} + +/* 书籍详情弹窗 - 查看想法按钮 */ +.detail-action-thoughts { + background: var(--bg-card); + border: 1px solid var(--accent); + color: var(--accent); +} + +.detail-action-thoughts:hover { + background: var(--accent-bg); +} + /* ===== 响应式 ===== */ @media (max-width: 768px) { .nav-links { @@ -1378,10 +1711,40 @@ body { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 12px; } - .top-books-grid { - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + .top-books-list { + gap: 8px; + } + .top-book-row { + padding: 8px 12px; + gap: 10px; + } + .top-book-mini-cover { + width: 36px; + height: 48px; + } + .top-book-rank-num { + width: 28px; + height: 28px; + font-size: 13px; } .nav-update { display: none; } + .back-to-top { + bottom: 20px; + right: 20px; + width: 40px; + height: 40px; + font-size: 18px; + } + .thoughts-page-header { + top: 0; + } + .thoughts-book-cover { + width: 48px; + height: 64px; + } + .thoughts-book-title { + font-size: 16px; + } } diff --git a/static/js/app.js b/static/js/app.js index 4985cf9..bb73ef7 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -4,18 +4,10 @@ const API_BASE = ""; // ===== 主题管理 ===== -/** - * 获取当前主题。 - * @returns {"light"|"dark"} - */ function getCurrentTheme() { return document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "light"; } -/** - * 切换明亮/黑暗主题。 - * 持久化到 localStorage,并触发图表重绘。 - */ function toggleTheme() { const newTheme = getCurrentTheme() === "dark" ? "light" : "dark"; document.documentElement.setAttribute("data-theme", newTheme); @@ -23,9 +15,6 @@ function toggleTheme() { refreshChartsForTheme(); } -/** - * 从 CSS 变量读取当前主题的颜色值,供 ECharts 使用。 - */ function getChartColors() { const cs = getComputedStyle(document.documentElement); return { @@ -45,13 +34,6 @@ function getChartColors() { // ===== 工具函数 ===== -/** - * 升级微信读书封面图 URL 到高清晰度版本。 - * 微信读书 CDN 的封面图通过文件名前缀区分分辨率: - * t6_ = 低清缩略图(~150px,Retina 屏模糊) - * t8_ = 高清版(~500px,适合 2x/3x 屏幕) - * 如果 URL 不含 t6_ 前缀或已是高清则原样返回。 - */ function hdCover(url) { if (!url) return ""; return url.replace("/t6_", "/t8_"); @@ -68,55 +50,47 @@ function formatHours(seconds) { return (seconds / 3600).toFixed(1); } +function escapeHtml(text) { + if (!text) return ""; + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; +} + // ===== 数据库健康检查 ===== -// 存储所有 ECharts 实例,用于主题切换时重绘 const chartInstances = {}; +let chartsLoaded = false; +let currentThoughtsData = null; -/** - * 主题切换后重绘所有 ECharts 图表。 - */ function refreshChartsForTheme() { + if (!chartsLoaded) return; Object.values(chartInstances).forEach((chart) => { - if (chart && !chart.isDisposed()) { - chart.dispose(); - } + if (chart && !chart.isDisposed()) chart.dispose(); }); - // 重新加载所有图表 loadYearlyTrend(); loadTimeDistribution(); loadCategories(); loadAuthors(); } -/** - * 页面加载时检查 MySQL 数据库连接状态。 - * 如果连接失败,显示告警横幅并阻止后续数据加载。 - */ async function checkDBHealth() { try { const resp = await fetch(API_BASE + "/api/health"); const data = await resp.json(); if (resp.ok && data.database === "connected") { - // 数据库连接正常,隐藏告警横幅 document.getElementById("dbAlertBanner").style.display = "none"; return true; } else { - // 数据库连接异常,显示告警横幅 showDBAlert(data.message || "数据库连接失败"); return false; } } catch (error) { - // 健康检查接口本身无法访问 showDBAlert("无法连接到服务器,请检查后端服务是否运行。"); return false; } } -/** - * 显示数据库连接告警横幅。 - * @param {string} message - 告警详情信息 - */ function showDBAlert(message) { const banner = document.getElementById("dbAlertBanner"); const detail = document.getElementById("dbAlertDetail"); @@ -124,61 +98,78 @@ function showDBAlert(message) { if (detail) detail.textContent = message; } -// ===== 初始化 ===== -document.addEventListener("DOMContentLoaded", async () => { - // 首先检查数据库连接状态 - const isHealthy = await checkDBHealth(); - if (!isHealthy) { - // 数据库连接失败,不加载后续数据 - return; +// ===== 标签页切换 ===== + +function switchTab(tabName) { + document.querySelectorAll(".tab-panel").forEach((panel) => { + panel.classList.remove("active"); + }); + document.querySelectorAll(".nav-link").forEach((link) => { + link.classList.toggle("active", link.dataset.tab === tabName); + }); + + const target = document.getElementById(`tab-${tabName}`); + if (target) { + target.classList.add("active"); } - // 数据库连接正常,加载所有数据模块 + window.scrollTo({ top: 0, behavior: "smooth" }); + + // 懒加载图表 + if (tabName === "charts" && !chartsLoaded) { + chartsLoaded = true; + loadYearlyTrend(); + loadTimeDistribution(); + loadCategories(); + loadAuthors(); + } + + // 切换到图表时重绘尺寸 + if (tabName === "charts" && chartsLoaded) { + setTimeout(() => { + Object.values(chartInstances).forEach((chart) => { + if (chart && !chart.isDisposed()) chart.resize(); + }); + }, 100); + } +} + +// ===== 回到顶部按钮 ===== + +function initBackToTop() { + const btn = document.getElementById("backToTop"); + window.addEventListener("scroll", () => { + btn.classList.toggle("visible", window.scrollY > 400); + }); +} + +function scrollToTop() { + window.scrollTo({ top: 0, behavior: "smooth" }); +} + +// ===== 初始化 ===== + +document.addEventListener("DOMContentLoaded", async () => { + const isHealthy = await checkDBHealth(); + if (!isHealthy) return; + loadOverview(); - loadYearlyTrend(); - loadTimeDistribution(); - loadCategories(); - loadAuthors(); loadTopBooks(); loadShelf(1); loadNotebooks(); loadRecommend(0); - initNavScroll(); + initBackToTop(); + + // 搜索和筛选事件 + document.getElementById("shelfSearch").addEventListener("input", debounce(() => loadShelf(1), 300)); + document.getElementById("finishedOnly").addEventListener("change", () => loadShelf(1)); }); -// ===== 导航栏滚动高亮 ===== -function initNavScroll() { - const links = document.querySelectorAll(".nav-link"); - const sections = document.querySelectorAll("section[id]"); - - window.addEventListener("scroll", () => { - let current = ""; - sections.forEach((sec) => { - const top = sec.offsetTop - 100; - if (window.scrollY >= top) current = sec.id; - }); - links.forEach((link) => { - link.classList.toggle("active", link.getAttribute("href") === `#${current}`); - }); - }); - - links.forEach((link) => { - link.addEventListener("click", (e) => { - e.preventDefault(); - const target = document.querySelector(link.getAttribute("href")); - if (target) { - window.scrollTo({ top: target.offsetTop - 60, behavior: "smooth" }); - } - }); - }); -} - // ===== 总览数据 ===== async function loadOverview() { try { const data = await fetchAPI("/api/overview"); - // Hero 统计 const heroStats = document.getElementById("heroStats"); heroStats.innerHTML = `