/* ===== 微信读书数据展示 - 前端逻辑 ===== */
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;
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) {
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 shelfCategories = [];
let shelfActiveCategory = ""; // 空字符串表示"全部"
async function loadShelfCategories() {
try {
const data = await fetchAPI("/api/shelf?page=1&per_page=1");
shelfCategories = data.categories || [];
renderShelfCategories();
} catch (e) {
console.error("加载分类失败:", e);
}
}
function renderShelfCategories() {
const container = document.getElementById("shelfCategories");
if (!container) return;
let html = ``;
html += shelfCategories
.map((cat) => ``)
.join("");
container.innerHTML = html;
}
function filterByCategory(category) {
shelfActiveCategory = category;
renderShelfCategories();
loadShelf(1);
}
// ===== 初始化 =====
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)}
累计阅读 (小时)
`;
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.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);
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 `
})
${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;
}
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 = `
`;
const grid = document.getElementById("notebooksGrid");
grid.innerHTML = data
.map((nb) => {
const progress = nb.reading_progress || 0;
return `
${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.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}
${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;
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");
renderThoughtsList(category);
}
function renderThoughtsList(category) {
const data = currentThoughtsData;
if (!data) return;
const highlights = data.highlights || [];
const bookReviews = (data.reviews || []).filter((r) => r.is_finish);
const thoughts = (data.reviews || []).filter((r) => !r.is_finish);
const list = document.getElementById("thoughtsList");
let html = "";
// 划线区
if (category === "all" || category === "highlight") {
if (highlights.length > 0) {
html += `📌 划线 ${highlights.length}
`;
const grouped = data.grouped_highlights || {};
for (const [chTitle, items] of Object.entries(grouped)) {
if (category === "highlight") {
html += ``;
html += `
${chTitle}
`;
}
items.forEach((h) => {
html += `
${escapeHtml(h.mark_text)}
${h.chapter_title ? `${h.chapter_title}` : ""}
${h.create_time ? `${h.create_time}` : ""}
`;
});
if (category === "highlight") html += `
`;
}
}
}
// 书评区
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) {
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.author || ''}
${ratingHtml}${ratingLabel}${readingHtml}
${categoryHtml}
${reasonHtml}
`;
})
.join("");
}