美化电子书馆
This commit is contained in:
@@ -1,43 +1,128 @@
|
||||
'use client';
|
||||
|
||||
import { BookmarkCheck, BookOpen, Trash2 } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { buildBookDetailPath, cacheBookShelfItem } from '@/lib/book-route-cache.client';
|
||||
import { deleteBookShelf, getAllBookShelf } from '@/lib/book.db.client';
|
||||
import { BookShelfItem } from '@/lib/book.types';
|
||||
import {
|
||||
buildBookDetailPath,
|
||||
cacheBookShelfItem,
|
||||
} from '@/lib/book-route-cache.client';
|
||||
|
||||
export default function BookShelfPage() {
|
||||
const [shelf, setShelf] = useState<Record<string, BookShelfItem>>({});
|
||||
|
||||
useEffect(() => {
|
||||
getAllBookShelf().then(setShelf).catch(() => undefined);
|
||||
getAllBookShelf()
|
||||
.then(setShelf)
|
||||
.catch(() => undefined);
|
||||
}, []);
|
||||
|
||||
const items = useMemo(() => Object.values(shelf).sort((a, b) => (b.lastReadTime || b.saveTime) - (a.lastReadTime || a.saveTime)), [shelf]);
|
||||
const items = useMemo(
|
||||
() =>
|
||||
Object.values(shelf).sort(
|
||||
(a, b) =>
|
||||
(b.lastReadTime || b.saveTime) - (a.lastReadTime || a.saveTime)
|
||||
),
|
||||
[shelf]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
<div className='text-sm text-gray-500'>共 {items.length} 本电子书</div>
|
||||
<div className='space-y-5'>
|
||||
<section className='rounded-[2rem] border border-emerald-100/80 bg-white/85 p-5 shadow-sm shadow-emerald-950/5 dark:border-emerald-500/10 dark:bg-gray-950/70'>
|
||||
<div className='flex items-center justify-between gap-4'>
|
||||
<div>
|
||||
<div className='text-sm font-medium text-emerald-600 dark:text-emerald-300'>
|
||||
我的书架
|
||||
</div>
|
||||
<div className='mt-1 text-2xl font-black tracking-tight text-slate-950 dark:text-white'>
|
||||
共 {items.length} 本电子书
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex h-12 w-12 items-center justify-center rounded-2xl bg-emerald-50 text-emerald-600 ring-1 ring-emerald-100 dark:bg-emerald-500/10 dark:text-emerald-200 dark:ring-emerald-500/20'>
|
||||
<BookmarkCheck className='h-6 w-6' />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className='grid gap-4 md:grid-cols-2 xl:grid-cols-3'>
|
||||
{items.map((item) => (
|
||||
<div key={`${item.sourceId}-${item.bookId}`} className='rounded-3xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-950'>
|
||||
<article
|
||||
key={`${item.sourceId}-${item.bookId}`}
|
||||
className='rounded-[2rem] border border-emerald-100/80 bg-white/85 p-4 shadow-sm shadow-emerald-950/5 transition-colors duration-200 hover:border-emerald-200 hover:bg-white dark:border-emerald-500/10 dark:bg-gray-950/70 dark:hover:border-emerald-500/30'
|
||||
>
|
||||
<div className='flex gap-4'>
|
||||
<div className='h-28 w-20 overflow-hidden rounded-2xl bg-gray-100 dark:bg-gray-900'>{item.cover ? <img src={item.cover} alt={item.title} className='h-full w-full object-cover' /> : null}</div>
|
||||
<div className='h-28 w-20 shrink-0 overflow-hidden rounded-2xl bg-gradient-to-br from-emerald-50 to-amber-50 ring-1 ring-emerald-100 dark:from-gray-900 dark:to-emerald-950/20 dark:ring-emerald-500/10'>
|
||||
{item.cover ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
src={item.cover}
|
||||
alt={item.title}
|
||||
className='h-full w-full object-cover'
|
||||
/>
|
||||
) : (
|
||||
<div className='flex h-full items-center justify-center text-slate-400'>
|
||||
<BookOpen className='h-7 w-7' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<div className='truncate font-medium'>{item.title}</div>
|
||||
<div className='mt-1 text-sm text-gray-500'>{item.author || item.sourceName}</div>
|
||||
<div className='mt-2 text-xs text-gray-500'>进度 {Math.round(item.progressPercent || 0)}%</div>
|
||||
<div className='truncate font-semibold text-slate-950 dark:text-white'>
|
||||
{item.title}
|
||||
</div>
|
||||
<div className='mt-1 truncate text-sm text-slate-500 dark:text-slate-400'>
|
||||
{item.author || item.sourceName}
|
||||
</div>
|
||||
<div className='mt-3 h-2 overflow-hidden rounded-full bg-emerald-50 dark:bg-gray-900'>
|
||||
<div
|
||||
className='h-full rounded-full bg-emerald-600'
|
||||
style={{
|
||||
width: `${Math.max(
|
||||
0,
|
||||
Math.min(100, Math.round(item.progressPercent || 0))
|
||||
)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-1 text-xs text-slate-500 dark:text-slate-400'>
|
||||
进度 {Math.round(item.progressPercent || 0)}%
|
||||
</div>
|
||||
<div className='mt-3 flex flex-wrap gap-2'>
|
||||
<Link href={buildBookDetailPath(item.sourceId, item.bookId)} onClick={() => cacheBookShelfItem(item)} className='rounded-2xl bg-sky-600 px-3 py-2 text-xs text-white'>详情</Link>
|
||||
<button onClick={async () => { await deleteBookShelf(item.sourceId, item.bookId); setShelf((prev) => { const next = { ...prev }; delete next[`${item.sourceId}+${item.bookId}`]; return next; }); }} className='rounded-2xl border border-gray-200 px-3 py-2 text-xs dark:border-gray-700'>移除</button>
|
||||
<Link
|
||||
href={buildBookDetailPath(item.sourceId, item.bookId)}
|
||||
onClick={() => cacheBookShelfItem(item)}
|
||||
className='inline-flex cursor-pointer items-center gap-1.5 rounded-2xl bg-emerald-600 px-3 py-2 text-xs font-semibold text-white transition-colors duration-200 hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-emerald-500'
|
||||
>
|
||||
详情
|
||||
</Link>
|
||||
<button
|
||||
type='button'
|
||||
onClick={async () => {
|
||||
await deleteBookShelf(item.sourceId, item.bookId);
|
||||
setShelf((prev) => {
|
||||
const next = { ...prev };
|
||||
delete next[`${item.sourceId}+${item.bookId}`];
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
className='inline-flex cursor-pointer items-center gap-1.5 rounded-2xl border border-emerald-100 px-3 py-2 text-xs font-semibold text-slate-600 transition-colors duration-200 hover:bg-emerald-50 hover:text-emerald-700 focus:outline-none focus:ring-2 focus:ring-emerald-500 dark:border-emerald-500/10 dark:text-slate-300 dark:hover:bg-emerald-500/10 dark:hover:text-emerald-200'
|
||||
>
|
||||
<Trash2 className='h-3.5 w-3.5' />
|
||||
移除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
{items.length === 0 ? <div className='text-sm text-gray-500'>书架还是空的</div> : null}
|
||||
{items.length === 0 ? (
|
||||
<div className='rounded-3xl border border-dashed border-emerald-200 bg-white/70 p-8 text-center text-sm text-slate-500 dark:border-emerald-500/20 dark:bg-gray-950/50 dark:text-slate-400'>
|
||||
书架还是空的
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user