书架拆成「在读」和「书籍」两个独立页面

问题:两块内容挤在 /bookshelf/ 一页里,往下滚就从「在读」滑进「书籍」,
不像两个分类该有的样子。

拆法
- src/pages/bookshelf.astro -> src/pages/bookshelf/index.astro(书籍页)
- 新增 src/pages/bookshelf/reading.astro(在读页)
- 两页共用 bookshelf-content.astro,只多收一个 view: "reading" | "books",
  决定渲染哪一块;外壳(页眉/侧栏/页脚/样式)完全同一份,样式不会跑偏
- 在读页标题带真实本数:「在读 20 本 | 时之刃的小站」

侧栏「馆藏」改成真导航
- 在读 -> /bookshelf/reading/,数量 = 未读完本数(20)
- 书籍 -> /bookshelf/,数量 = 手工书单 + 微信读书已读完(0 + 6 = 6)
  原来这里只算 books.json 的 total,所以显示 0,正是截图里的问题
- 当前页用 aria-current="page" + 主色底高亮

页面互相不串
- /bookshelf/        : id=books、搜索框、已读完分组 6 张、无在读区块、0 条进度条
- /bookshelf/reading/: id=reading、在读卡片 20 张、20 条进度条、无书籍区块、
                       无搜索框、无已读完分组、0 张 data-weread-card

顺带更正 README 首段还写着的「读书/论文/漫画书架」。
This commit is contained in:
dsh
2026-09-15 10:58:27 +08:00
parent 8a867132de
commit 95e5111fd8
6 changed files with 62 additions and 10 deletions
+11 -2
View File
@@ -2,7 +2,7 @@
个人博客源码 —— [tianxuetao.top](https://tianxuetao.top)
一个基于 Astro 的静态博客,主题为 [VermilionVoid](https://github.com/Lapis0x0/VermilionVoid)。除了常规的文章系统,还有碎碎念、读书/论文/漫画书架、学习追踪与加密文章。
一个基于 Astro 的静态博客,主题为 [VermilionVoid](https://github.com/Lapis0x0/VermilionVoid)。除了常规的文章系统,还有碎碎念、读书页(微信读书同步)、学习追踪与加密文章。
站点配置集中在两个文件,改站名或换域名通常只需要动第一个:
@@ -275,7 +275,14 @@ prose-p:leading-[1.78] md:prose-p:leading-[1.85]
## 书架与「最近在读」
书架页(`/bookshelf/`)有两块内容:
书架是**两个独立页面**,由侧栏「馆藏」互相导航(`src/components/bookshelf-content.astro` 一个组件,靠 `view` 属性决定渲染哪一块):
| 页面 | 路径 | 内容 | 侧栏数量 |
|---|---|---|---|
| 书籍 | `/bookshelf/` | 手工书单(`books.json`)+ 微信读书已读完 | 两者相加 |
| 在读 | `/bookshelf/reading/` | 微信读书里没读完的 | 未读完本数 |
两块内容:
**1. 最近在读** —— 来自微信读书,构建时抓取,页面本身不发任何请求。
@@ -321,6 +328,8 @@ src/components/bookshelf-content.astro
**2. 书籍** —— 来自 `public/data/books.json`,由 `bookshelf-content.astro` 在构建时 import(文件必须存在)。另外两个文件 `papers.json` / `comics.json` 对应的漫画、论文分类已经移除,文件保留但不再被引用。
**两个页面共用同一个组件**,不会出现样式跑偏:`bookshelf-content.astro` 收一个 `view: "reading" | "books"`,只是决定渲染哪一块。侧栏那两行是**真链接**(带 `aria-current` 和主色高亮表示当前页),不再是页内锚点。
## 部署
主用 `./deploy.sh`,构建后 rsync 到树莓派和云服务器:
+3 -1
View File
@@ -195,7 +195,9 @@ pnpm preview # 本地预览构建产物(搜索功能只有这里才生效
## 7.5 刷新「最近在读」
书架页(`/bookshelf/`)顶部的「最近在读」是从微信读书抓来的,数据存在 `src/data/reading.json`。
书架分成两个页面:**书籍** `/bookshelf/`、**在读** `/bookshelf/reading/`,侧栏「馆藏」互相跳转。
「在读」是从微信读书抓来的,数据存在 `src/data/reading.json`。
**只想更新书单**(不动别的):
+28 -4
View File
@@ -6,6 +6,17 @@ import booksData from "../../public/data/books.json"
import ReadingCard from "@/components/reading-card.astro"
import { formatReadingDate, getReadingData } from "@/lib/reading"
/**
* view="reading" -> 在读页(/bookshelf/reading/)
* view="books" -> 书籍页(/bookshelf/)
* 两页共用这个组件和同一套外壳,只是渲染其中一块。
*/
interface Props {
view?: "reading" | "books"
}
const { view = "books" } = Astro.props
const isReadingView = view === "reading"
// 微信读书数据。数据缺失时 reading 为 null,页面退化成空态,不报错。
const reading = getReadingData()
const readingBooks = reading?.books ?? []
@@ -107,15 +118,22 @@ const recommendBadge = (status?: string) => {
</div>
<div class="text-[10px] text-muted-foreground/80 uppercase tracking-[0.18em] mb-2 pl-2">馆藏</div>
{/* 漫画和论文两个分类已移除,只剩书籍;这里改成页内锚点,不再是 tab 切换 */}
{/* 漫画和论文两个分类已移除;在读和书籍现在是两个独立页面,这里是它们的导航 */}
<nav class="flex flex-row md:flex-col gap-1 mb-6 md:mb-8 overflow-x-auto md:overflow-visible -mx-1 px-1">
{[
{ href: "#reading", label: "在读", count: readingCount, svg: "reading" },
{ href: "#books", label: "书籍", count: bookStats.total, svg: "books" },
{ href: "/bookshelf/reading/", label: "在读", count: readingCount, svg: "reading", active: isReadingView },
{ href: "/bookshelf/", label: "书籍", count: bookStats.total + finishedReading.length, svg: "books", active: !isReadingView },
].map((it) => (
<a
href={it.href}
class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition-colors whitespace-nowrap text-foreground/80 hover:bg-muted/60 hover:text-foreground"
data-astro-prefetch
aria-current={it.active ? "page" : undefined}
class={cn(
"flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition-colors whitespace-nowrap",
it.active
? "bg-primary text-primary-foreground"
: "text-foreground/80 hover:bg-muted/60 hover:text-foreground",
)}
>
<span class="inline-flex h-4 w-4 items-center justify-center shrink-0">
{it.svg === "reading" && (
@@ -145,6 +163,7 @@ const recommendBadge = (status?: string) => {
{/* ========================= 最近在读(微信读书) =========================
数据在构建期由 scripts/fetch-reading.mjs 抓到 src/data/reading.json,
这里只负责渲染,页面本身不发任何请求。 */}
{isReadingView && (
<div id="reading" class="mb-16 scroll-mt-24">
<div class="flex flex-wrap items-end justify-between gap-x-6 gap-y-2 mb-6">
<div>
@@ -172,7 +191,10 @@ const recommendBadge = (status?: string) => {
</div>
)}
</div>
)}
{!isReadingView && (
<>
{/* ---------- Header ---------- */}
<div id="books" class="mb-6 scroll-mt-24">
<div class="text-[11px] text-primary tracking-[0.22em] font-semibold uppercase mb-1.5">Collection · 02</div>
@@ -344,6 +366,8 @@ const recommendBadge = (status?: string) => {
</span>
<p class="text-muted-foreground">没有找到匹配的内容</p>
</div>
</>
)}
</section>
</div>
+1 -1
View File
@@ -1,5 +1,5 @@
{
"updatedAt": "2026-09-15T02:51:38.693Z",
"updatedAt": "2026-09-15T02:58:11.129Z",
"source": "weread",
"books": [
{
@@ -3,10 +3,10 @@ import BaseLayout from "@/layouts/BaseLayout.astro"
import BookshelfContent from "@/components/bookshelf-content.astro"
import { siteTitleSuffix } from "@/data/site"
const title = `书架 | ${siteTitleSuffix}`
const title = `书籍 | ${siteTitleSuffix}`
const description = "我的阅读记录与书籍推荐"
---
<BaseLayout {title} {description} fontProfile="interface">
<BookshelfContent />
<BookshelfContent view="books" />
</BaseLayout>
+17
View File
@@ -0,0 +1,17 @@
---
import BaseLayout from "@/layouts/BaseLayout.astro"
import BookshelfContent from "@/components/bookshelf-content.astro"
import { getReadingData } from "@/lib/reading"
import { siteTitleSuffix } from "@/data/site"
const title = `在读 | ${siteTitleSuffix}`
const description = "最近在读的书,数据来自微信读书"
// 用真实本数做标题,抓不到数据时退回通用文案
const count = getReadingData()?.books.filter((book) => !book.finished).length ?? 0
const heading = count > 0 ? `在读 ${count} 本` : "在读"
---
<BaseLayout title={`${heading} | ${siteTitleSuffix}`} {description} fontProfile="interface">
<BookshelfContent view="reading" />
</BaseLayout>