mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 20:16:36 +08:00
d0b4255aa8
- 首页顶部「影视/阅读」分段切换(zustand 持久化,阅读模式直渲染书架) - /reader/* 独立全屏路由(不套影视 Layout): 书架(响应式网格 3→8 列)、搜索页(结果流+失败书源明细)、 书源管理(导入/启停/删除/快速调试日志)、详情页(目录+加书架) - 阅读器仿 legado ReadMenu:6 套内置主题(日/夜色值对齐 readConfig.json)、 CSS 多栏分页 + 平移翻页/滚动双模式、三区点击 + 键盘翻页(桌面)、 上下章、目录抽屉(Virtuoso 虚拟滚动)、字号/行距/段距调节、 进度记忆(服务端同步 + 下一章预取) - 桌面兼容:内容列 900px 限宽居中、鼠标悬停态、键盘快捷键 - 后端:详情/目录/正文接口支持 source_url 定位书源(书架链路)
115 lines
4.9 KiB
TypeScript
115 lines
4.9 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { Link, useNavigate } from 'react-router-dom'
|
|
import { BookOpen, Loader2, RefreshCw, Search, Settings2 } from 'lucide-react'
|
|
|
|
import { readerAPI, type ReaderBook } from '../../api/reader'
|
|
import { ReaderModeSwitch } from './ReaderModeSwitch'
|
|
|
|
// 首页阅读模式的书架内容(首页切换与 /reader 路由共用)。
|
|
// 结构仿 legado 书架:网格封面 + 书名 + 阅读进度,右上搜索/书源管理入口。
|
|
|
|
export function ReaderHomeContent({ embedded = false }: { embedded?: boolean }) {
|
|
const navigate = useNavigate()
|
|
const [books, setBooks] = useState<ReaderBook[] | null>(null)
|
|
const [error, setError] = useState('')
|
|
|
|
const load = () => {
|
|
setError('')
|
|
readerAPI
|
|
.listBooks()
|
|
.then(setBooks)
|
|
.catch((e) => setError(e?.response?.data?.error ?? '加载书架失败'))
|
|
}
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
useEffect(load, [])
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{!embedded && <ReaderModeSwitch />}
|
|
|
|
<div className="flex items-center justify-between gap-3">
|
|
<h1 className="font-display text-2xl text-ink-600">书架</h1>
|
|
<div className="flex items-center gap-2">
|
|
<Link
|
|
to="/reader/sources"
|
|
className="flex items-center gap-1.5 rounded-xl border border-[var(--app-border)] px-3 py-1.5 text-xs font-bold text-[var(--app-muted)] hover:text-[var(--app-text)]"
|
|
>
|
|
<Settings2 size={13} /> 书源管理
|
|
</Link>
|
|
<Link
|
|
to="/reader/search"
|
|
className="flex items-center gap-1.5 rounded-xl border border-brand-500/60 bg-brand-500/10 px-3 py-1.5 text-xs font-bold text-brand-600 hover:bg-brand-500/20"
|
|
>
|
|
<Search size={13} /> 搜索
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
{books === null && !error && (
|
|
<div className="flex items-center justify-center py-24 text-[var(--app-muted)]">
|
|
<Loader2 className="animate-spin" size={22} />
|
|
</div>
|
|
)}
|
|
|
|
{error && books === null && (
|
|
<div className="rounded-2xl border border-[var(--app-border)] bg-[var(--app-panel-soft)] p-8 text-center">
|
|
<p className="text-sm text-[var(--app-muted)]">{error}</p>
|
|
<button type="button" onClick={load} className="mt-4 inline-flex items-center gap-1.5 btn-outline text-xs">
|
|
<RefreshCw size={13} /> 重试
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{books !== null && books.length === 0 && (
|
|
<div className="rounded-2xl border border-[var(--app-border)] bg-[var(--app-panel-soft)] p-12 text-center">
|
|
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-2xl border border-[var(--app-border)] bg-[var(--app-panel)]">
|
|
<BookOpen size={20} className="text-brand-500" />
|
|
</div>
|
|
<p className="mt-4 text-sm font-bold text-[var(--app-text)]">书架空空如也</p>
|
|
<p className="mt-1 text-xs text-[var(--app-muted)]">先到「书源管理」导入书源,再搜索加入书架</p>
|
|
<div className="mt-5 flex items-center justify-center gap-3">
|
|
<Link to="/reader/sources" className="btn-outline text-xs">导入书源</Link>
|
|
<Link to="/reader/search" className="btn-primary text-xs">去搜索</Link>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{books !== null && books.length > 0 && (
|
|
<div className="grid grid-cols-3 gap-x-4 gap-y-6 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-8">
|
|
{books.map((book) => (
|
|
<button
|
|
key={book.id}
|
|
type="button"
|
|
onClick={() => navigate(`/reader/view/${book.id}`)}
|
|
className="group text-left"
|
|
>
|
|
<div className="relative overflow-hidden rounded-xl border border-[var(--app-border)] bg-[var(--app-panel-soft)] shadow-sm transition group-hover:shadow-md">
|
|
<div className="aspect-[3/4] w-full">
|
|
{book.cover_url ? (
|
|
<img
|
|
src={book.cover_url}
|
|
alt={book.name}
|
|
loading="lazy"
|
|
referrerPolicy="no-referrer"
|
|
className="h-full w-full object-cover"
|
|
/>
|
|
) : (
|
|
<div className="flex h-full w-full items-center justify-center">
|
|
<BookOpen size={22} className="text-[var(--app-muted)]" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<p className="mt-2 truncate text-xs font-bold text-[var(--app-text)]">{book.name}</p>
|
|
<p className="truncate text-2xs text-[var(--app-muted)]">
|
|
{book.dur_chapter_title ? `读到 ${book.dur_chapter_title}` : book.author || '未开始阅读'}
|
|
</p>
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|