From 1e09ea1f289f36122d79251efba9e565f2ff05a2 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Wed, 29 Apr 2026 14:42:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B9=A6=E9=A6=86=E7=BC=93=E5=AD=98=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/books/history/page.tsx | 131 ++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) diff --git a/src/app/books/history/page.tsx b/src/app/books/history/page.tsx index 146a7f3..74ffe67 100644 --- a/src/app/books/history/page.tsx +++ b/src/app/books/history/page.tsx @@ -1,17 +1,19 @@ 'use client'; +import { FolderCog, RefreshCw, Trash2, X } from 'lucide-react'; import Link from 'next/link'; import { useEffect, useMemo, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { deleteCachedBookFile, listCachedBookFiles, type CachedBookFile } from '@/lib/book-cache.client'; import { buildBookReadPath, cacheBookReadRecord, cacheBookShelfItem } from '@/lib/book-route-cache.client'; import { deleteBookReadRecord, getAllBookReadRecords, getAllBookShelf } from '@/lib/book.db.client'; import { BookReadRecord, BookShelfItem } from '@/lib/book.types'; - function looksLikeInternalHref(value?: string) { if (!value) return false; const normalized = value.trim().toLowerCase(); - return /\.(xhtml|html|htm|xml)(#.*)?$/.test(normalized) || /^nav/.test(normalized); + return /\.(xhtml|html|htm|xml)(#.*)?$/.test(normalized) || /^nav\b/.test(normalized); } function getReadableChapterLabel(item: BookReadRecord) { @@ -23,15 +25,42 @@ function getReadableChapterLabel(item: BookReadRecord) { return '定位已保存'; } +function formatBytes(size: number) { + if (size < 1024) return `${size} B`; + if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`; + return `${(size / 1024 / 1024).toFixed(1)} MB`; +} + export default function BookHistoryPage() { const [records, setRecords] = useState>({}); const [shelf, setShelf] = useState>({}); + const [cacheModalOpen, setCacheModalOpen] = useState(false); + const [cacheItems, setCacheItems] = useState([]); + const [cacheLoading, setCacheLoading] = useState(false); + const [mounted, setMounted] = useState(false); + const [confirmAction, setConfirmAction] = useState<{ type: 'delete-one' | 'clear-all'; key?: string; title?: string } | null>(null); useEffect(() => { + setMounted(true); getAllBookReadRecords().then(setRecords).catch(() => undefined); getAllBookShelf().then(setShelf).catch(() => undefined); }, []); + const loadCacheItems = async () => { + setCacheLoading(true); + try { + const items = await listCachedBookFiles(); + setCacheItems(items.sort((a, b) => b.lastOpenTime - a.lastOpenTime)); + } finally { + setCacheLoading(false); + } + }; + + useEffect(() => { + if (!cacheModalOpen) return; + void loadCacheItems(); + }, [cacheModalOpen]); + const items = useMemo(() => Object.entries(records) .map(([key, item]) => { const [fallbackSourceId = '', fallbackBookId = ''] = key.split('+'); @@ -51,8 +80,23 @@ export default function BookHistoryPage() { }) .sort((a, b) => b.saveTime - a.saveTime), [records, shelf]); + const cacheTotalSize = useMemo(() => cacheItems.reduce((sum, item) => sum + item.size, 0), [cacheItems]); + return (
+
+
共 {items.length} 条阅读历史
+ +
+ {items.map((item) => (
@@ -80,6 +124,89 @@ export default function BookHistoryPage() {
))} {items.length === 0 ?
暂无阅读历史
: null} + + {cacheModalOpen && mounted && createPortal( +
setCacheModalOpen(false)}> +
event.stopPropagation()}> +
+
+
+
缓存管理
+
已缓存 {cacheItems.length} 本 · {formatBytes(cacheTotalSize)}
+
+
+ + + +
+
+ + {cacheLoading ?
正在读取缓存…
: null} + {!cacheLoading && cacheItems.length === 0 ?
当前还没有缓存书籍
: null} + +
+ {cacheItems.map((item) => ( +
+
+
+
{item.title}
+
格式 {item.format.toUpperCase()} · 大小 {formatBytes(item.size)}
+
最近打开 {new Date(item.lastOpenTime).toLocaleString()}
+
+ +
+
+ ))} +
+
+
+
, + document.body + )} + + + {confirmAction && mounted && createPortal( +
setConfirmAction(null)}> +
event.stopPropagation()}> +
+ {confirmAction.type === 'clear-all' ? '清空全部缓存' : '删除缓存'} +
+
+ {confirmAction.type === 'clear-all' + ? '确认清空当前浏览器中的全部电子书缓存吗?此操作不可撤销。' + : `确认删除《${confirmAction.title || '该书'}》的本地缓存吗?`} +
+
+ + +
+
+
, + document.body + )}
); }