/* ===== 微信读书数据展示 - 前端逻辑 ===== */ const API_BASE = ""; // ===== 主题管理 ===== function getCurrentTheme() { return document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "light"; } function toggleTheme() { const newTheme = getCurrentTheme() === "dark" ? "light" : "dark"; document.documentElement.setAttribute("data-theme", newTheme); localStorage.setItem("theme", newTheme); refreshChartsForTheme(); } function getChartColors() { const cs = getComputedStyle(document.documentElement); return { axis: cs.getPropertyValue("--chart-axis").trim(), axisLine: cs.getPropertyValue("--chart-axis-line").trim(), splitLine: cs.getPropertyValue("--chart-split-line").trim(), label: cs.getPropertyValue("--chart-label").trim(), borderColor: cs.getPropertyValue("--chart-border-color").trim(), tooltipBg: cs.getPropertyValue("--chart-tooltip-bg").trim(), tooltipBorder: cs.getPropertyValue("--chart-tooltip-border").trim(), tooltipText: cs.getPropertyValue("--chart-tooltip-text").trim(), accent: cs.getPropertyValue("--accent").trim(), accentLight: cs.getPropertyValue("--accent-light").trim(), accentDark: cs.getPropertyValue("--accent-dark").trim(), }; } // ===== 工具函数 ===== /** * 升级微信读书封面图 URL 到高清晰度版本。 * 微信读书 CDN 的封面图通过文件名前缀区分分辨率: * t6_ = 低清缩略图(~150px,Retina 屏模糊) * s_ = 超低清缩略图(~90px,搜索结果常用,更模糊) * t8_ = 高清版(~500px,适合 2x/3x 屏幕) * 将 t6_ 和 s_ 前缀统一升级为 t8_ 高清版。 */ function hdCover(url) { if (!url) return ""; return url.replace(/\/(t6_|s_)/, "/t8_"); } async function fetchAPI(path) { const resp = await fetch(API_BASE + path); if (!resp.ok) throw new Error(`API error: ${resp.status}`); return resp.json(); } function formatHours(seconds) { if (!seconds) return "0"; return (seconds / 3600).toFixed(1); } function escapeHtml(text) { if (!text) return ""; const div = document.createElement("div"); div.textContent = text; return div.innerHTML; } // ===== 数据库健康检查 ===== const chartInstances = {}; let chartsLoaded = false; let currentThoughtsData = null; let currentThoughtsCategory = "all"; let currentThoughtsSort = "time_desc"; let currentThoughtsSearch = ""; function refreshChartsForTheme() { if (!chartsLoaded) return; Object.values(chartInstances).forEach((chart) => { if (chart && !chart.isDisposed()) chart.dispose(); }); loadYearlyTrend(); loadTimeDistribution(); loadCategories(); loadAuthors(); } 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; } } function showDBAlert(message) { const banner = document.getElementById("dbAlertBanner"); const detail = document.getElementById("dbAlertDetail"); if (banner) banner.style.display = "flex"; if (detail) detail.textContent = message; } // ===== 标签页切换 ===== function switchTab(tabName) { // 如果想法详情页正在显示,先关闭它回到主视图 const thoughtsPage = document.getElementById("thoughtsPage"); if (thoughtsPage && thoughtsPage.style.display !== "none") { thoughtsPage.style.display = "none"; document.getElementById("mainView").style.display = ""; currentThoughtsData = null; } // 关闭可能打开的弹窗 closeModal("bookDetailModal"); 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" }); } /** * 在微信读书中打开书籍。 * 优先使用 API 返回的 deep_link(https://weread.qq.com/... 网页链接), * 没有则用 book_id 构造网页链接。先尝试 weread:// 深链接唤起 App, * 1.5 秒后无响应则回退到网页版。 */ function openInWeRead(bookId, deepLink) { const webUrl = deepLink || `https://weread.qq.com/#book/${bookId}`; const schemeUrl = `weread://reading?bId=${bookId}`; // 尝试唤起 App(移动端),同时设置定时器回退到网页版 let triggered = false; const fallbackTimer = setTimeout(() => { if (!triggered) window.open(webUrl, "_blank"); }, 1500); // 监听页面可见性变化(App 唤起成功会切到后台) const onVisChange = () => { if (document.hidden) { triggered = true; clearTimeout(fallbackTimer); } }; document.addEventListener("visibilitychange", onVisChange); // 尝试打开深链接 const iframe = document.createElement("iframe"); iframe.style.display = "none"; iframe.src = schemeUrl; document.body.appendChild(iframe); setTimeout(() => document.body.removeChild(iframe), 2000); // 桌面端不会有 visibilitychange,确保 fallback 一定执行 setTimeout(() => { document.removeEventListener("visibilitychange", onVisChange); }, 3000); } // ===== 全局搜索(顶部常驻搜索框) ===== function globalSearch() { const keyword = document.getElementById("globalSearchInput").value.trim(); if (!keyword) return; // 切换到推荐标签页 switchTab("discover"); // 切换到搜索子标签 switchDiscoverTab("search"); // 填入搜索词并执行搜索 document.getElementById("searchInput").value = keyword; doSearch(); } // ===== 书架分类标签(可折叠层级结构) ===== let shelfCategoryTree = {}; // { "历史": {count:114, subs:{"世界史":5,...}}, ... } let shelfActiveCategory = ""; // 完整分类名,如 "历史-中国古代" let shelfActiveParent = ""; // 父分类名,如 "历史";空表示"全部" let shelfExpandedParents = new Set(); // 当前展开的父分类 let shelfSortMode = "category"; // 始终按分类排序,前端不显示切换按钮 async function loadShelfCategories() { try { const data = await fetchAPI("/api/shelf?page=1&per_page=1"); shelfCategoryTree = data.category_tree || {}; renderShelfCategories(); } catch (e) { console.error("加载分类失败:", e); } } function renderShelfCategories() { const container = document.getElementById("shelfCategories"); if (!container) return; let html = ""; // "全部" 按钮 const allActive = !shelfActiveParent && !shelfActiveCategory; html += ``; // 父分类按钮(按书籍数量降序) const parents = Object.entries(shelfCategoryTree) .sort((a, b) => b[1].count - a[1].count); for (const [parent, info] of parents) { const isActive = shelfActiveParent === parent && !shelfActiveCategory; const isExpanded = shelfExpandedParents.has(parent); const arrow = Object.keys(info.subs).length > 0 ? `${isExpanded ? "▼" : "▶"}` : ""; html += ``; // 展开的子分类 if (isExpanded && Object.keys(info.subs).length > 0) { html += `
`; for (const [sub, count] of Object.entries(info.subs).sort((a, b) => b[1] - a[1])) { const fullCat = `${parent}-${sub}`; const subActive = shelfActiveCategory === fullCat; html += ``; } html += `
`; } } container.innerHTML = html; } function filterByParent(parent) { if (parent) { // 点击父分类:展开/折叠子分类,同时筛选该父分类下的所有书 if (shelfExpandedParents.has(parent) && shelfActiveParent === parent && !shelfActiveCategory) { // 已展开且已选中 → 折叠 shelfExpandedParents.delete(parent); shelfActiveParent = ""; shelfActiveCategory = ""; } else { // 展开 + 筛选 shelfExpandedParents.add(parent); shelfActiveParent = parent; shelfActiveCategory = ""; } } else { // 点击"全部" shelfExpandedParents.clear(); shelfActiveParent = ""; shelfActiveCategory = ""; } renderShelfCategories(); loadShelf(1); } function filterByCategory(fullCategory) { shelfActiveCategory = fullCategory; // 确保父分类已展开 const parent = fullCategory.split("-")[0]; shelfExpandedParents.add(parent); shelfActiveParent = parent; renderShelfCategories(); loadShelf(1); } // ===== 书架刷新 ===== async function refreshShelf() { const btn = document.getElementById("shelfRefreshBtn"); btn.disabled = true; btn.classList.add("loading"); btn.textContent = "↻ 刷新中..."; try { await loadShelfCategories(); await loadShelf(1); } finally { btn.disabled = false; btn.classList.remove("loading"); btn.textContent = "↻ 刷新"; } } // ===== 初始化 ===== document.addEventListener("DOMContentLoaded", async () => { const isHealthy = await checkDBHealth(); if (!isHealthy) return; loadOverview(); loadTopBooks(); loadShelfCategories(); loadShelf(1); loadNotebooks(); loadRecommend(0); initBackToTop(); // 搜索和筛选事件 document.getElementById("shelfSearch").addEventListener("input", debounce(() => loadShelf(1), 300)); document.getElementById("finishedOnly").addEventListener("change", () => loadShelf(1)); }); // ===== 总览数据 ===== async function loadOverview() { try { const data = await fetchAPI("/api/overview"); const heroStats = document.getElementById("heroStats"); heroStats.innerHTML = `
${formatHours(data.total_read_time)}
累计阅读 (小时)
${data.read_days}
阅读天数
${data.shelf_count}
书架藏书
${data.total_notes}
读书笔记
`; document.getElementById("heroSubtitle").textContent = `自 ${data.regist_time} 加入微信读书,已陪伴你 ${data.read_days} 个阅读的日子`; const statsGrid = document.getElementById("statsGrid"); statsGrid.innerHTML = `
⏱️
${data.total_read_time_str}
累计阅读时长
今年: ${data.year_read_time_str}
📅
${data.read_days}
累计阅读天数
今年: ${data.year_read_days} 天
📚
${data.shelf_count}
书架书籍
已读完: ${data.finished_count} 本
✍️
${data.total_notes}
读书笔记
来自 ${data.notebook_count} 本书
`; const timeStr = `数据更新: ${data.fetch_time}`; document.getElementById("fetchTime").textContent = timeStr; document.getElementById("footerTime").textContent = data.fetch_time; } catch (e) { console.error("加载总览失败:", e); } } // ===== 年度趋势图 ===== async function loadYearlyTrend() { try { const data = await fetchAPI("/api/yearly-trend"); const el = document.getElementById("chart-yearly"); if (chartInstances.yearly) chartInstances.yearly.dispose(); const chart = echarts.init(el); chartInstances.yearly = chart; const c = getChartColors(); const years = data.map((d) => d.year); const hours = data.map((d) => d.hours); chart.setOption({ tooltip: { trigger: "axis", backgroundColor: c.tooltipBg, borderColor: c.tooltipBorder, textStyle: { color: c.tooltipText }, formatter: (params) => { const p = params[0]; return `${p.name}年
阅读: ${p.value} 小时`; }, }, grid: { left: 50, right: 30, top: 30, bottom: 40 }, xAxis: { type: "category", data: years, axisLabel: { color: c.axis }, axisLine: { lineStyle: { color: c.axisLine } }, }, yAxis: { type: "value", name: "小时", nameTextStyle: { color: c.axis }, axisLabel: { color: c.axis }, splitLine: { lineStyle: { color: c.splitLine } }, }, series: [{ data: hours, type: "bar", barWidth: "50%", itemStyle: { borderRadius: [8, 8, 0, 0], color: { type: "linear", x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: c.accentLight }, { offset: 1, color: c.accent }, ] }, }, label: { show: true, position: "top", color: c.label, fontSize: 12, formatter: "{c}h" }, }], }); window.addEventListener("resize", () => chart.resize()); } catch (e) { console.error("加载年度趋势失败:", e); } } // ===== 24小时时段分布 ===== async function loadTimeDistribution() { try { const data = await fetchAPI("/api/time-distribution"); const el = document.getElementById("chart-hours"); if (chartInstances.hours) chartInstances.hours.dispose(); const chart = echarts.init(el); chartInstances.hours = chart; const c = getChartColors(); const hours = data.map((d) => `${d.hour}时`); const minutes = data.map((d) => d.duration_min); chart.setOption({ tooltip: { trigger: "axis", backgroundColor: c.tooltipBg, borderColor: c.tooltipBorder, textStyle: { color: c.tooltipText }, formatter: (params) => { const p = params[0]; return `${p.name}
阅读: ${p.value} 分钟`; }, }, grid: { left: 50, right: 20, top: 20, bottom: 40 }, xAxis: { type: "category", data: hours, axisLabel: { color: c.axis, fontSize: 10, interval: 2 }, axisLine: { lineStyle: { color: c.axisLine } }, }, yAxis: { type: "value", name: "分钟", nameTextStyle: { color: c.axis }, axisLabel: { color: c.axis }, splitLine: { lineStyle: { color: c.splitLine } }, }, series: [{ data: minutes, type: "line", smooth: true, symbol: "circle", symbolSize: 4, lineStyle: { width: 2, color: c.accent }, itemStyle: { color: c.accent }, areaStyle: { color: { type: "linear", x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: c.accent + "4D" }, { offset: 1, color: c.accent + "05" }, ] }, }, }], }); window.addEventListener("resize", () => chart.resize()); } catch (e) { console.error("加载时段分布失败:", e); } } // ===== 偏好分类 ===== async function loadCategories() { try { const data = await fetchAPI("/api/categories"); const el = document.getElementById("chart-categories"); if (chartInstances.categories) chartInstances.categories.dispose(); const chart = echarts.init(el); chartInstances.categories = chart; const c = getChartColors(); const items = data.slice(0, 8).map((d) => ({ name: d.category_title, value: d.reading_time, })); chart.setOption({ tooltip: { trigger: "item", backgroundColor: c.tooltipBg, borderColor: c.tooltipBorder, textStyle: { color: c.tooltipText }, formatter: (p) => { const hours = (p.value / 3600).toFixed(1); return `${p.name}
阅读: ${hours} 小时 (${p.percent}%)`; }, }, legend: { type: "scroll", orient: "vertical", right: 10, top: 20, bottom: 20, textStyle: { color: c.label, fontSize: 11 }, }, series: [{ type: "pie", radius: ["40%", "70%"], center: ["35%", "50%"], avoidLabelOverlap: false, itemStyle: { borderRadius: 6, borderColor: c.borderColor, borderWidth: 2 }, label: { show: false }, emphasis: { label: { show: true, fontSize: 14, fontWeight: "bold", color: c.tooltipText }, }, data: items, color: ["#c17b4a", "#e8a87c", "#d4a574", "#b8860b", "#cd853f", "#daa520", "#bc8f8f", "#bdb76b"], }], }); window.addEventListener("resize", () => chart.resize()); } catch (e) { console.error("加载分类失败:", e); } } // ===== 偏好作者 ===== async function loadAuthors() { try { const data = await fetchAPI("/api/authors"); const el = document.getElementById("chart-authors"); if (chartInstances.authors) chartInstances.authors.dispose(); const chart = echarts.init(el); chartInstances.authors = chart; const c = getChartColors(); const top10 = data.slice(0, 10); const names = top10.map((d) => d.name); const counts = top10.map((d) => d.count); chart.setOption({ tooltip: { trigger: "axis", axisPointer: { type: "shadow" }, backgroundColor: c.tooltipBg, borderColor: c.tooltipBorder, textStyle: { color: c.tooltipText }, formatter: (params) => { const p = params[0]; return `${p.name}
阅读: ${p.value} 本`; }, }, grid: { left: 100, right: 40, top: 20, bottom: 20 }, xAxis: { type: "value", axisLabel: { color: c.axis }, splitLine: { lineStyle: { color: c.splitLine } }, }, yAxis: { type: "category", data: names.reverse(), axisLabel: { color: c.axis, fontSize: 12 }, axisLine: { lineStyle: { color: c.axisLine } }, }, series: [{ type: "bar", data: counts.reverse(), barWidth: "50%", itemStyle: { borderRadius: [0, 6, 6, 0], color: { type: "linear", x: 0, y: 0, x2: 1, y2: 0, colorStops: [ { offset: 0, color: c.accentLight }, { offset: 1, color: c.accent }, ] }, }, label: { show: true, position: "right", color: c.label, fontSize: 12, formatter: "{c}本" }, }], }); window.addEventListener("resize", () => chart.resize()); } catch (e) { console.error("加载作者失败:", e); } } // ===== 读书排行(竖向列表) ===== async function loadTopBooks() { try { const data = await fetchAPI("/api/top-books"); const list = document.getElementById("topBooksGrid"); list.innerHTML = data .map((book, i) => { const tagsHtml = book.tags .map((t) => `${t}`) .join(""); return `
${i + 1} ${book.title}
${book.title}
${book.author} ${tagsHtml ? `
${tagsHtml}
` : ""}
⏱ ${book.read_time_str}
`; }) .join(""); } catch (e) { console.error("加载排行失败:", e); } } // ===== 书架 ===== let shelfCurrentPage = 1; async function loadShelf(page) { shelfCurrentPage = page; const keyword = document.getElementById("shelfSearch").value.trim(); const finished = document.getElementById("finishedOnly").checked ? "1" : "0"; const params = new URLSearchParams({ page: page, per_page: 60, finished: finished }); if (keyword) params.set("keyword", keyword); if (shelfActiveCategory) { params.set("category", shelfActiveCategory); } else if (shelfActiveParent) { params.set("parent_category", shelfActiveParent); } if (shelfSortMode === "category") params.set("sort", "category"); try { const data = await fetchAPI(`/api/shelf?${params}`); const grid = document.getElementById("bookGrid"); document.getElementById("shelfInfo").textContent = `共 ${data.total} 本 · 第 ${page}/${data.total_pages} 页`; grid.innerHTML = data.books.map((book) => renderBookCard(book)).join(""); renderPagination(page, data.total_pages); } catch (e) { console.error("加载书架失败:", e); } } function renderBookCard(book) { const badges = []; if (book.is_top) badges.push(`置顶`); if (book.finish_reading) badges.push(`已读完`); return `
${book.title} ${badges.join("")}
${book.title}
${book.author}
`; } function renderPagination(current, total) { const pag = document.getElementById("pagination"); if (total <= 1) { pag.innerHTML = ""; return; } let html = ``; const maxButtons = 7; let start = Math.max(1, current - 3); let end = Math.min(total, start + maxButtons - 1); start = Math.max(1, end - maxButtons + 1); if (start > 1) { html += ``; if (start > 2) html += `...`; } for (let i = start; i <= end; i++) { html += ``; } if (end < total) { if (end < total - 1) html += `...`; html += ``; } html += ``; pag.innerHTML = html; } function debounce(fn, delay) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; } // ===== 笔记概览 ===== async function loadNotebooks() { try { const data = await fetchAPI("/api/notebooks"); const summary = document.getElementById("notesSummary"); const totalHighlights = data.reduce((s, d) => s + d.note_count, 0); const totalReviews = data.reduce((s, d) => s + d.review_count, 0); const totalBookmarks = data.reduce((s, d) => s + d.bookmark_count, 0); summary.innerHTML = `
${data.length}
有笔记的书
${totalHighlights}
划线
${totalReviews}
想法/点评
${totalBookmarks}
书签
`; const grid = document.getElementById("notebooksGrid"); grid.innerHTML = data .map((nb) => { const progress = nb.reading_progress || 0; return `
${nb.title}
${nb.title}
${nb.author}
划线 ${nb.note_count} 想法 ${nb.review_count} 书签 ${nb.bookmark_count}
`; }) .join(""); } catch (e) { console.error("加载笔记失败:", e); } } // ===== 书籍详情弹窗 ===== function openBook(bookId) { if (!bookId) return; showModal("bookDetailModal"); const body = document.getElementById("bookDetailBody"); body.innerHTML = `
`; fetchAPI(`/api/book/detail?book_id=${bookId}`) .then((data) => { const p = data.progress || {}; const progress = p.progress || 0; const rating = data.new_rating > 0 ? (data.new_rating / 10).toFixed(1) : null; const tags = []; if (data.category) tags.push(data.category); if (rating) tags.push(`${rating}分`); if (data.new_rating_count) tags.push(`${data.new_rating_count}人评分`); body.innerHTML = `
${data.title}
${data.title}
${data.author || ''}${data.translator ? ' / 译: ' + data.translator : ''}
${tags.map(t => `${t}`).join('')}
${p.is_start_reading ? `
阅读进度: ${p.progress}% ${p.record_reading_time_str ? ' · 累计 ' + p.record_reading_time_str : ''} ${p.finish_time ? ' · 读完于 ' + p.finish_time : ''}
` : '
尚未开始阅读
'}
${data.intro ? `
${data.intro}
` : ''}
${data.publisher ? `
出版社: ${data.publisher}
` : ''} ${data.publish_time ? `
出版时间: ${data.publish_time}
` : ''} ${data.isbn ? `
ISBN: ${data.isbn}
` : ''} ${data.word_count ? `
字数: ${(data.word_count / 10000).toFixed(1)}万字
` : ''}
`; }) .catch((e) => { body.innerHTML = `
加载失败: ${e.message}
`; }); } // ===== 想法详情独立页面 ===== function openThoughtsPage(bookId, title, author, cover) { if (!bookId) return; // 隐藏主视图,显示想法页面 document.getElementById("mainView").style.display = "none"; document.getElementById("thoughtsPage").style.display = "block"; window.scrollTo({ top: 0, behavior: "auto" }); // 渲染头部 const header = document.getElementById("thoughtsBookHeader"); header.innerHTML = ` ${title}
${title}
${author || ''}
加载中...
`; const content = document.getElementById("thoughtsContent"); content.innerHTML = `
`; fetchAPI(`/api/book/notes?book_id=${bookId}`) .then((data) => { currentThoughtsData = data; renderThoughtsPage(data); }) .catch((e) => { content.innerHTML = `
加载失败: ${e.message}
`; }); } function closeThoughtsPage() { document.getElementById("thoughtsPage").style.display = "none"; document.getElementById("mainView").style.display = ""; currentThoughtsData = null; } function renderThoughtsPage(data) { // 更新统计 const stats = document.getElementById("thoughtsBookStats"); // 分类:划线 / 书评 / 想法 const highlights = data.highlights || []; const bookReviews = (data.reviews || []).filter((r) => r.is_finish); const thoughts = (data.reviews || []).filter((r) => !r.is_finish); stats.innerHTML = ` 划线 ${highlights.length} 书评 ${bookReviews.length} 想法 ${thoughts.length} `; const content = document.getElementById("thoughtsContent"); if (highlights.length === 0 && bookReviews.length === 0 && thoughts.length === 0) { content.innerHTML = `
暂无笔记数据
`; return; } let html = `
`; content.innerHTML = html; currentThoughtsCategory = "all"; currentThoughtsSort = "time_desc"; currentThoughtsSearch = ""; renderThoughtsList("all"); } function filterThoughts(category) { document.querySelectorAll(".thought-filter-btn").forEach((btn) => { btn.classList.remove("active"); }); event.target.closest(".thought-filter-btn").classList.add("active"); currentThoughtsCategory = category; renderThoughtsList(category); } function onThoughtsSearch(val) { currentThoughtsSearch = val.trim().toLowerCase(); renderThoughtsList(currentThoughtsCategory); } function onThoughtsSort(val) { currentThoughtsSort = val; renderThoughtsList(currentThoughtsCategory); } /** 解析日期字符串为时间戳,无效则返回 0 */ function parseDate(str) { if (!str) return 0; const d = new Date(str.replace(/-/g, "/")); return isNaN(d.getTime()) ? 0 : d.getTime(); } /** 对数组按排序模式排序,返回新数组 */ function sortByMode(arr) { const sorted = [...arr]; if (currentThoughtsSort === "time_desc") { sorted.sort((a, b) => parseDate(b.create_time) - parseDate(a.create_time)); } else if (currentThoughtsSort === "time_asc") { sorted.sort((a, b) => parseDate(a.create_time) - parseDate(b.create_time)); } else if (currentThoughtsSort === "chapter") { sorted.sort((a, b) => { const ua = parseInt(a.chapter_uid) || 0; const ub = parseInt(b.chapter_uid) || 0; if (ua !== ub) return ua - ub; // 同章节内按 range 起始位置排序 const ra = a.range ? parseInt(a.range.split("-")[0]) : 0; const rb = b.range ? parseInt(b.range.split("-")[0]) : 0; return ra - rb; }); } return sorted; } /** 过滤搜索关键词 */ function matchSearch(text) { if (!currentThoughtsSearch) return true; return (text || "").toLowerCase().includes(currentThoughtsSearch); } function renderThoughtsList(category) { const data = currentThoughtsData; if (!data) return; // 原始分类 let highlights = data.highlights || []; let bookReviews = (data.reviews || []).filter((r) => r.is_finish); let thoughts = (data.reviews || []).filter((r) => !r.is_finish); // 搜索过滤 highlights = highlights.filter((h) => matchSearch(h.mark_text)); bookReviews = bookReviews.filter((r) => matchSearch(r.content) || matchSearch(r.abstract)); thoughts = thoughts.filter((r) => matchSearch(r.content) || matchSearch(r.abstract)); // 排序 highlights = sortByMode(highlights); bookReviews = sortByMode(bookReviews); thoughts = sortByMode(thoughts); const list = document.getElementById("thoughtsList"); let html = ""; // 划线区 if (category === "all" || category === "highlight") { if (highlights.length > 0) { html += `
📌 划线 ${highlights.length}
`; if (currentThoughtsSort === "chapter") { // 按章节分组展示 const grouped = {}; highlights.forEach((h) => { const ch = h.chapter_title || "未知章节"; if (!grouped[ch]) grouped[ch] = []; grouped[ch].push(h); }); for (const [chTitle, items] of Object.entries(grouped)) { html += `
`; html += `
${chTitle}
`; items.forEach((h) => { html += `
${escapeHtml(h.mark_text)}
${h.chapter_title ? `${h.chapter_title}` : ""} ${h.create_time ? `${h.create_time}` : ""}
`; }); html += `
`; } } else { // 按时间排序,平铺展示(标注章节) highlights.forEach((h) => { html += `
${escapeHtml(h.mark_text)}
${h.chapter_title ? `${h.chapter_title}` : ""} ${h.create_time ? `${h.create_time}` : ""}
`; }); } } } // 书评区 if (category === "all" || category === "review") { if (bookReviews.length > 0) { html += `
⭐ 书评 ${bookReviews.length}
`; bookReviews.forEach((r) => { const starVal = Math.max(0, Math.min(5, parseInt(r.star) || 0)); const stars = starVal > 0 ? `
${"★".repeat(starVal)}${"☆".repeat(5 - starVal)}
` : ""; html += `
${stars} ${escapeHtml(r.content)}
${r.chapter_name ? `${r.chapter_name}` : ""} ${r.create_time || ""}
`; }); } } // 想法区 if (category === "all" || category === "thought") { if (thoughts.length > 0) { html += `
💭 想法 ${thoughts.length}
`; thoughts.forEach((r) => { const abstract = r.abstract ? `
${escapeHtml(r.abstract)}
` : ""; html += `
${abstract}
${escapeHtml(r.content)}
${r.chapter_name ? `${r.chapter_name}` : ""} ${r.create_time || ""}
`; }); } } if (!html) { if (currentThoughtsSearch) { html = `
未找到包含"${escapeHtml(currentThoughtsSearch)}"的内容
`; } else { html = `
该分类暂无内容
`; } } list.innerHTML = html; } // ===== 弹窗控制 ===== function showModal(id) { document.getElementById(id).classList.add("active"); document.body.style.overflow = "hidden"; } function closeModal(id) { document.getElementById(id).classList.remove("active"); document.body.style.overflow = ""; } document.addEventListener("keydown", (e) => { if (e.key === "Escape") { closeModal("bookDetailModal"); if (document.getElementById("thoughtsPage").style.display !== "none") { closeThoughtsPage(); } } }); // ===== 推荐发现 ===== let recommendMaxIdx = 0; function switchDiscoverTab(tab) { const recPanel = document.getElementById("recommendPanel"); const searchPanel = document.getElementById("searchPanel"); const tabRec = document.getElementById("tabRecommend"); const tabSearch = document.getElementById("tabSearch"); if (tab === "recommend") { recPanel.style.display = ""; searchPanel.style.display = "none"; tabRec.classList.add("active"); tabSearch.classList.remove("active"); } else { recPanel.style.display = "none"; searchPanel.style.display = ""; tabRec.classList.remove("active"); tabSearch.classList.add("active"); } } async function loadRecommend(maxIdx) { const grid = document.getElementById("recommendGrid"); grid.innerHTML = `
`; try { const data = await fetchAPI(`/api/recommend?maxIdx=${maxIdx}`); recommendMaxIdx = data.last_search_idx || 0; renderDiscoverGrid(grid, data.books, true); } catch (e) { grid.innerHTML = `
加载推荐失败: ${e.message}
`; } } async function loadMoreRecommend() { loadRecommend(recommendMaxIdx); } async function doSearch() { const keyword = document.getElementById("searchInput").value.trim(); if (!keyword) return; const grid = document.getElementById("searchGrid"); grid.innerHTML = `
`; try { const data = await fetchAPI(`/api/search?keyword=${encodeURIComponent(keyword)}`); if (data.books.length === 0) { grid.innerHTML = `
未找到相关书籍
`; return; } renderDiscoverGrid(grid, data.books, false); } catch (e) { grid.innerHTML = `
搜索失败: ${e.message}
`; } } function renderDiscoverGrid(grid, books, showReason) { grid.innerHTML = books .map((book) => { const rating = book.new_rating > 0 ? (book.new_rating / 10).toFixed(1) : null; const ratingLabel = book.rating_title ? `${book.rating_title}` : ""; const ratingHtml = rating ? `${rating}` : ""; const readingHtml = book.reading_count ? `${book.reading_count}人在读` : ""; const reasonHtml = (showReason && book.reason) ? `
${book.reason}
` : ""; const categoryHtml = book.category ? `
${book.category}
` : ""; return `
${book.title}
${book.title}
${book.author || ''}
${ratingHtml}${ratingLabel}${readingHtml}
${categoryHtml} ${reasonHtml}
`; }) .join(""); }