${esc((shelfData.mp && shelfData.mp.title) || '文章收藏')}
这是书架中的收藏入口,共 ${counts.mp ? 1 : 0} 个。
书架接口不提供入口内文章列表;可在发现页按「文章 / 公众号」检索。
diff --git a/README.md b/README.md index 6c42184..6553c91 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ DEPLOY_BRANCH=master ./deploy.sh | 偏好分类 | 阅读分类占比饼图 | | 偏好作者 | 阅读最多的作者排行 | | 读书排行 | 阅读时长最久的书籍 TOP 20 | -| 书架 | 全部书架书籍展示,支持分类筛选和已读完筛选 | +| 书架 | 每页 48 本,底部页码分页,支持按最近阅读、书名或作者排序 | | 笔记概览 | 有笔记的书籍及划线/想法/书签统计 | ## 数据库表结构 diff --git a/docs/API.md b/docs/API.md index 6c51f65..b2f3399 100644 --- a/docs/API.md +++ b/docs/API.md @@ -7,7 +7,7 @@ | 数据 | 来源 | 说明 | |------|------|------| -| 书架、书目 | 微信读书 `/shelf/sync`(实时,进程内缓存 10 分钟) | 字段含书名、作者、分类、封面、读完标记、最近阅读时间 | +| 书架、书目 | 微信读书 `/shelf/sync`(实时,进程内缓存 10 分钟) | 字段含书名、作者、分类、封面、读完标记、最近阅读时间和置顶标记 | | 阅读统计(周/月/年/总) | 微信读书 `/readdata/detail`(实时,缓存 5 分钟) | 时长趋势、阅读天数、读得最多、偏好分类/作者/出版社/时段/勋章/好友排行 | | 阅读进度 | 本地 `notebooks` / `book_progress` 缓存优先,缺失时并发调用 `/book/getprogress` 补齐(缓存 10 分钟) | 用于书架卡片与详情页进度 | | 笔记本与笔记 | 本地 `notebooks` 表 + 实时 `/book/bookmarklist`、`/review/list/mine` | 划线与想法正文实时获取 | @@ -17,6 +17,8 @@ MySQL 不可用时,阅读数据接口仍可降级运行(返回 `degraded: true`)。阅读目标必须持久化,其接口返回 503,不退回浏览器保存。 +当前书架页一次读取书架电子书,在前端按状态筛选后每页显示 48 本。排序选项为最近阅读(默认)、书名升序、书名降序和作者升序;置顶书籍始终排在前面。接口仍支持 `limit` / `offset` 参数供其他调用者分页,书架页使用全量结果以供书架、阅读目标和发现功能共享。 + ## 接口清单 | 方法 | 路径 | 参数 | 说明 | diff --git a/docs/code-design.md b/docs/code-design.md index 4b09ab5..cfeddea 100644 --- a/docs/code-design.md +++ b/docs/code-design.md @@ -81,6 +81,14 @@ GitHub Actions 的 `.github/workflows/deploy.yml` 只监听 `master` 分支。 目标介绍区在宽屏上把说明与统计并排,窄屏上改为上下排列;目标卡按可用宽度自动排列,每张卡约 280 px 起步,并缩小封面预览和上下间距。例如,一个包含 6 本书、已读完 2 本的月度目标仍展示原有完成数和进度,只减少卡片占位。页面渲染、筛选、目标计算和数据流保持由现有 JavaScript 实现,CSS 不改变业务口径。 +## 正式看板书架排序与分页 + +正式看板从 `/api/reading/shelf` 读取电子书列表。前端按当前书架状态筛选后排序,每页展示 48 本;用户切换状态或排序时回到第 1 页,点击底部页码时只替换当前书籍网格。排序框右对齐显示在页码列表上方。 + +排序默认按最近阅读时间倒序;还可按书名升序、书名降序或作者升序。四种方式都先将 `is_top` 置顶书籍排在前面。接口把微信读书的 `isTop` 映射为 `is_top`,前端排序只使用筛选结果的副本,避免改变其他页面共享的书架顺序。 + +例:筛选“全部电子书”共 101 本,选择“书名升序”并打开第 2 页。页面只显示排序后第 49 至 96 本,在网格下方显示第 1、2、3 页;切换到“最近阅读”后回到第 1 页,并按最近阅读日期倒序显示前 48 本,置顶书籍仍排在该页最前面。 + ## 正式看板的阅读目标持久化 正式看板使用 `static/js/app.js`、`static/js/weapi.js` 和 `reading_api.py`。本节覆盖正式页面;前文的 Demo 存储方式不适用于正式目标。 diff --git a/docs/technical-guide.md b/docs/technical-guide.md index 9807551..d04be8f 100644 --- a/docs/technical-guide.md +++ b/docs/technical-guide.md @@ -171,6 +171,8 @@ node --check static/js/weapi.js 本次样式收紧覆盖正式看板的概览、书架和阅读目标页。顶栏、标题区、筛选区、统计卡、书架网格、右侧目标摘要、目标介绍区和目标卡片均使用覆盖规则压缩占位;书架在窄屏固定两列,避免横向溢出。本地真实数据验收覆盖 1280 px 桌面和 390 px 手机宽度:桌面顶栏 58 px,统计卡从约 200 px 开始,书架一行 7 本,目标页介绍区约 67 px 高,目标卡约 317 px 宽;手机顶栏 56 px,书架两列,目标卡约 298 px 宽,概览、书架和目标页均未出现横向溢出。 +当前书架电子书列表每页显示 48 本,页码导航位于列表下方;翻页会替换当前页内容。右侧排序框提供最近阅读、书名升序、书名降序和作者升序,置顶书籍始终优先;切换书架状态或排序会回到第 1 页。此功能使用已加载的书架数据,不需要数据库迁移。 + ## master 自动部署 `master` 分支 push 后由 `.github/workflows/deploy.yml` 触发部署。Actions 通过仓库 Secrets 登录服务器,在 `DEPLOY_PATH` 指定的目录先拉取 `origin/master`,再执行根目录 `deploy.sh`,完成加载可选 `.env`、安装 Python 依赖、执行全部数据库迁移和重启 systemd 服务。`dev` 和其他分支不会触发部署,部署脚本也会拒绝非 `master` 分支。详细配置见 [master 自动部署](deploy-master.md)。 diff --git a/reading_api.py b/reading_api.py index f3e507b..4130ba2 100644 --- a/reading_api.py +++ b/reading_api.py @@ -364,6 +364,7 @@ def make_book( author=None, category=None, finish=0, + is_top=0, read_update_time=0, cover_url=None, deep_link=None, @@ -401,6 +402,7 @@ def make_book( "read_time_str": duration_str(read_time) if read_time else "", "read_update_time": ts_to_date(read_update_time), "finish_reading": finish, + "is_top": int(is_top or 0), "deep_link": deep_link or "", } @@ -589,6 +591,7 @@ def shelf_books(conn, with_progress=False, progress_limit=30): row.get("author"), row.get("category"), finish=row.get("finishReading"), + is_top=row.get("isTop"), read_update_time=row.get("readUpdateTime"), cover_url=row.get("cover"), deep_link=row.get("deepLink"), diff --git a/static/css/styles.css b/static/css/styles.css index 0050bff..9ce8ac1 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -92,3 +92,15 @@ .stack-more{margin-left:8px;white-space:nowrap} } .login-page{min-height:100vh;display:grid;place-items:center;padding:24px}.login-shell{width:min(100%,420px)}.login-logo{justify-content:center;margin-bottom:26px}.login-logo .logo-mark{border-radius:9px}.login-logo svg{width:24px;height:24px}.login-panel{padding:30px}.login-panel h1{font-size:23px;margin:0 0 8px}.login-panel>p{margin-bottom:23px}.login-form{display:grid;gap:16px;margin-top:22px}.login-form label{display:grid;gap:7px;font-size:12px;color:var(--text)}.login-form input{width:100%;min-height:42px;border:1px solid var(--line);border-radius:7px;background:var(--bg);color:var(--text);padding:10px 12px}.login-form input:focus{border-color:var(--accent)}.login-form .primary{width:100%;min-height:42px;margin-top:4px}.login-error{margin-top:16px;padding:10px 12px;border:1px solid #e6b9b5;border-radius:7px;background:#fff5f4;color:#a8443c;font-size:12px;line-height:1.6}.login-foot{text-align:center;margin-top:20px}.top-actions form{margin:0}.logout-button{white-space:nowrap}@media(max-width:600px){.login-page{padding:18px}.login-panel{padding:24px}.top-actions{gap:9px}.logout-button{font-size:10px}} + + +/* 书架分页与排序 */ +.shelf-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 14px;flex-wrap:wrap} +.shelf-sort-control{display:flex;align-items:center;gap:8px;margin-left:auto;color:var(--muted);font-size:11px} +.shelf-sort-control select{min-width:140px} +.shelf-pagination{display:flex;justify-content:center;align-items:center;gap:6px;margin-top:18px;flex-wrap:wrap} +.shelf-pagination .page-btn{min-width:36px;padding:8px 10px;border:1px solid var(--line);border-radius:7px;background:var(--panel);color:var(--text);font:inherit;font-size:11px;cursor:pointer} +.shelf-pagination .page-btn:hover:not(:disabled){border-color:var(--accent);color:var(--accent)} +.shelf-pagination .page-btn:disabled{opacity:.45;cursor:not-allowed} +.shelf-pagination .page-btn.active{border-color:var(--accent);background:var(--accent);color:#fff} +@media(max-width:600px){.shelf-toolbar{align-items:flex-start}.shelf-pagination{gap:4px}.shelf-pagination .page-btn{min-width:32px;padding:7px 8px}} diff --git a/static/js/app.js b/static/js/app.js index 58c48e8..9a669c7 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -24,7 +24,7 @@ let searchComposing = false, discoverSearchTimer = null; let recommendOffset = 0; let noteBook = 'all', noteType = 'all', goalFilter = 'all'; let detailBook = null, bookTab = 'info', reviewFilter = 0, selectedChapter = 0; -let shelfLimit = SHELF_PAGE, chapterLimit = 30, searchScope = 'store', notesLimit = 30; +let shelfPage = 1, shelfSort = 'recent', chapterLimit = 30, searchScope = 'store', notesLimit = 30; let editingGoalId = null, draftGoalId = null, draftIds = new Set(), draftSources = [], pendingBooks = [], pendingSources = []; let toastTimer, storageWarning = '', goalBusy = false; @@ -332,6 +332,50 @@ function renderLists(lists) { return `
这是书架中的收藏入口,共 ${counts.mp ? 1 : 0} 个。
书架接口不提供入口内文章列表;可在发现页按「文章 / 公众号」检索。