diff --git a/app.py b/app.py index 60dd565..1caa29a 100644 --- a/app.py +++ b/app.py @@ -498,12 +498,13 @@ def api_time_distribution(conn): def api_shelf(conn): """ 书架列表接口(分页)。 - 返回书架中的书籍列表,支持按分类筛选和只看已读完。 + 返回书架中的书籍列表,支持按书名/作者搜索、按分类筛选和只看已读完。 Query Parameters: page (int): 页码,默认 1 per_page (int): 每页数量,默认 60 - category (str): 分类筛选(模糊匹配) + keyword (str): 书名或作者搜索(模糊匹配) + category (str): 分类筛选(精确匹配分类名) finished (str): "1" 表示只看已读完 Returns: @@ -511,7 +512,8 @@ def api_shelf(conn): """ page = int(request.args.get("page", 1)) per_page = int(request.args.get("per_page", 60)) - category = request.args.get("category", "") + keyword = request.args.get("keyword", "").strip() + category = request.args.get("category", "").strip() finished_only = request.args.get("finished", "0") == "1" offset = (page - 1) * per_page @@ -520,9 +522,12 @@ def api_shelf(conn): # 构建查询条件 where_clauses = [] params = [] + if keyword: + where_clauses.append("(title LIKE %s OR author LIKE %s)") + params.extend([f"%{keyword}%", f"%{keyword}%"]) if category: - where_clauses.append("category LIKE %s") - params.append(f"%{category}%") + where_clauses.append("category = %s") + params.append(category) if finished_only: where_clauses.append("finish_reading=1") diff --git a/static/css/style.css b/static/css/style.css index 48b3850..e0a777b 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -271,6 +271,103 @@ body { [data-theme="dark"] .theme-toggle .icon-moon { display: none; } [data-theme="dark"] .theme-toggle { color: var(--toggle-icon-sun); } +/* ===== 顶部常驻搜索框 ===== */ +.global-search-bar { + position: sticky; + top: 60px; + z-index: 90; + background: var(--nav-bg); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-bottom: 1px solid var(--border); + padding: 10px 0; + transition: background-color 0.3s ease, border-color 0.3s ease; +} + +.global-search-wrapper { + display: flex; + align-items: center; + gap: 0; + max-width: 600px; + margin: 0 auto; + position: relative; +} + +.global-search-icon { + position: absolute; + left: 14px; + font-size: 15px; + opacity: 0.5; + pointer-events: none; +} + +.global-search-input { + flex: 1; + padding: 9px 16px 9px 40px; + border: 1px solid var(--border); + border-right: none; + border-radius: var(--radius-sm) 0 0 var(--radius-sm); + font-size: 14px; + background: var(--bg-card); + color: var(--text-primary); + transition: border-color 0.2s, box-shadow 0.2s; +} + +.global-search-input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-bg); +} + +.global-search-btn { + padding: 9px 20px; + border: 1px solid var(--accent); + border-radius: 0 var(--radius-sm) var(--radius-sm) 0; + background: var(--accent); + color: #fff; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background 0.2s; +} + +.global-search-btn:hover { + background: var(--accent-dark); +} + +/* ===== 书架分类标签 ===== */ +.shelf-categories { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 20px; +} + +.shelf-category-tag { + padding: 5px 14px; + border: 1px solid var(--border); + border-radius: 20px; + font-size: 13px; + color: var(--text-secondary); + background: var(--bg-card); + cursor: pointer; + transition: all 0.2s; + white-space: nowrap; +} + +.shelf-category-tag:hover { + border-color: var(--accent); + color: var(--accent); + background: var(--accent-bg); +} + +.shelf-category-tag.active { + border-color: var(--accent); + color: #fff; + background: var(--accent); + font-weight: 500; +} + /* ===== Hero 区 ===== */ .hero-section { position: relative; @@ -1730,6 +1827,19 @@ body { .nav-update { display: none; } + .global-search-wrapper { + max-width: 100%; + } + .shelf-search { + width: 140px; + } + .shelf-categories { + gap: 6px; + } + .shelf-category-tag { + font-size: 12px; + padding: 4px 10px; + } .back-to-top { bottom: 20px; right: 20px; diff --git a/static/js/app.js b/static/js/app.js index bb73ef7..555e19d 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -34,9 +34,17 @@ function getChartColors() { // ===== 工具函数 ===== +/** + * 升级微信读书封面图 URL 到高清晰度版本。 + * 微信读书 CDN 的封面图通过文件名前缀区分分辨率: + * t6_ = 低清缩略图(~150px,Retina 屏模糊) + * s_ = 超低清缩略图(~90px,搜索结果常用,更模糊) + * t8_ = 高清版(~500px,适合 2x/3x 屏幕) + * 将 t6_ 和 s_ 前缀统一升级为 t8_ 高清版。 + */ function hdCover(url) { if (!url) return ""; - return url.replace("/t6_", "/t8_"); + return url.replace(/\/(t6_|s_)/, "/t8_"); } async function fetchAPI(path) { @@ -147,6 +155,53 @@ function scrollToTop() { window.scrollTo({ top: 0, behavior: "smooth" }); } +// ===== 全局搜索(顶部常驻搜索框) ===== + +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 () => { @@ -155,6 +210,7 @@ document.addEventListener("DOMContentLoaded", async () => { loadOverview(); loadTopBooks(); + loadShelfCategories(); loadShelf(1); loadNotebooks(); loadRecommend(0); @@ -500,9 +556,11 @@ let shelfCurrentPage = 1; async function loadShelf(page) { shelfCurrentPage = page; - const search = document.getElementById("shelfSearch").value; + const keyword = document.getElementById("shelfSearch").value.trim(); const finished = document.getElementById("finishedOnly").checked ? "1" : "0"; - const params = new URLSearchParams({ page: page, per_page: 60, category: search, finished: finished }); + 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}`); diff --git a/templates/index.html b/templates/index.html index 0dedfea..4453921 100644 --- a/templates/index.html +++ b/templates/index.html @@ -51,6 +51,17 @@ + + +
@@ -110,13 +121,14 @@

📚 我的书架

- +
+