/* ===== 微信读书数据展示 - 前端逻辑 ===== */ const API_BASE = ""; // ===== 工具函数 ===== 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); } // ===== 初始化 ===== document.addEventListener("DOMContentLoaded", () => { loadOverview(); loadYearlyTrend(); loadTimeDistribution(); loadCategories(); loadAuthors(); loadTopBooks(); loadShelf(1); loadNotebooks(); initNavScroll(); }); // ===== 导航栏滚动高亮 ===== 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 = `
${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 chart = echarts.init(document.getElementById("chart-yearly")); const years = data.map((d) => d.year); const hours = data.map((d) => d.hours); chart.setOption({ tooltip: { trigger: "axis", 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: "#6b5d4f" }, axisLine: { lineStyle: { color: "#e8e0d4" } }, }, yAxis: { type: "value", name: "小时", axisLabel: { color: "#6b5d4f" }, splitLine: { lineStyle: { color: "#f0ebe3" } }, }, 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: "#e8a87c" }, { offset: 1, color: "#c17b4a" }, ], }, }, label: { show: true, position: "top", color: "#6b5d4f", 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 chart = echarts.init(document.getElementById("chart-hours")); const hours = data.map((d) => `${d.hour}时`); const minutes = data.map((d) => d.duration_min); chart.setOption({ tooltip: { trigger: "axis", 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: "#6b5d4f", fontSize: 10, interval: 2, }, axisLine: { lineStyle: { color: "#e8e0d4" } }, }, yAxis: { type: "value", name: "分钟", axisLabel: { color: "#6b5d4f" }, splitLine: { lineStyle: { color: "#f0ebe3" } }, }, series: [ { data: minutes, type: "line", smooth: true, symbol: "circle", symbolSize: 4, lineStyle: { width: 2, color: "#c17b4a" }, itemStyle: { color: "#c17b4a" }, areaStyle: { color: { type: "linear", x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: "rgba(193,123,74,0.3)" }, { offset: 1, color: "rgba(193,123,74,0.02)" }, ], }, }, }, ], }); window.addEventListener("resize", () => chart.resize()); } catch (e) { console.error("加载时段分布失败:", e); } } // ===== 偏好分类 ===== async function loadCategories() { try { const data = await fetchAPI("/api/categories"); const chart = echarts.init(document.getElementById("chart-categories")); const items = data.slice(0, 8).map((d) => ({ name: d.category_title, value: d.reading_time, })); chart.setOption({ tooltip: { trigger: "item", 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: "#6b5d4f", fontSize: 11 }, }, series: [ { type: "pie", radius: ["40%", "70%"], center: ["35%", "50%"], avoidLabelOverlap: false, itemStyle: { borderRadius: 6, borderColor: "#fff", borderWidth: 2, }, label: { show: false }, emphasis: { label: { show: true, fontSize: 14, fontWeight: "bold", }, }, 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 chart = echarts.init(document.getElementById("chart-authors")); 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" }, 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: "#6b5d4f" }, splitLine: { lineStyle: { color: "#f0ebe3" } }, }, yAxis: { type: "category", data: names.reverse(), axisLabel: { color: "#6b5d4f", fontSize: 12 }, axisLine: { lineStyle: { color: "#e8e0d4" } }, }, 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: "#e8a87c" }, { offset: 1, color: "#c17b4a" }, ], }, }, label: { show: true, position: "right", color: "#6b5d4f", 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 grid = document.getElementById("topBooksGrid"); grid.innerHTML = data .map((book, i) => { const tagsHtml = book.tags .map((t) => `${t}`) .join(""); return `
${i + 1} ${book.title}
${book.title}
${book.author}
⏱ ${book.read_time_str}
${tagsHtml ? `
${tagsHtml}
` : ""}
`; }) .join(""); } catch (e) { console.error("加载排行失败:", e); } } // ===== 书架 ===== let shelfCurrentPage = 1; async function loadShelf(page) { shelfCurrentPage = page; const search = document.getElementById("shelfSearch").value; const finished = document.getElementById("finishedOnly").checked ? "1" : "0"; const params = new URLSearchParams({ page: page, per_page: 60, category: search, finished: finished, }); 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) => { const badges = []; if (book.is_top) badges.push(`置顶`); if (book.finish_reading) badges.push(`已读完`); return `
${book.title} ${badges.join("")}
${book.title}
${book.author}
`; }) .join(""); // 分页 renderPagination(page, data.total_pages); } catch (e) { console.error("加载书架失败:", e); } } 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; } // 搜索和筛选事件 document.getElementById("shelfSearch").addEventListener("input", debounce(() => loadShelf(1), 300)); document.getElementById("finishedOnly").addEventListener("change", () => loadShelf(1)); 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) { window.open(`weread://reading?bId=${bookId}`, "_blank"); } }