Skip to content

Commit 08615a9

Browse files
committed
fix: 修复封面显示问题
1 parent 86ddb35 commit 08615a9

8 files changed

Lines changed: 243 additions & 215 deletions

File tree

apps/desktop/src/pages/Admin.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@ import { Routes, Route, Link, useLocation } from 'react-router-dom';
44
import { getApiClient, User, EbookSource, Book } from '@bookdock/api-client';
55
import { getCoverImageUrl } from '../utils/network';
66
import { Button, Card, CardHeader, CardTitle, CardContent, Input } from '@bookdock/ui';
7+
8+
function BookCover({ book, className = "" }: { book: Book; className?: string }) {
9+
const [coverError, setCoverError] = useState(false);
10+
const coverSrc = getCoverImageUrl(book.coverUrl);
11+
return (
12+
<div className={`bg-gray-100 dark:bg-gray-700 rounded overflow-hidden flex-shrink-0 ${className}`}>
13+
{coverSrc && !coverError ? (
14+
<img
15+
src={coverSrc}
16+
alt={book.title}
17+
className="w-full h-full object-cover"
18+
onError={() => setCoverError(true)}
19+
/>
20+
) : (
21+
<div className="w-full h-full flex items-center justify-center bg-gradient-to-br from-blue-400 to-purple-500">
22+
<span className="text-white font-bold">{book.title.charAt(0)}</span>
23+
</div>
24+
)}
25+
</div>
26+
);
27+
}
728
import {
829
BookOpen,
930
FileText,
@@ -786,20 +807,7 @@ function BooksManagement() {
786807
<tr key={book.id} className="hover:bg-gray-50 dark:hover:bg-gray-800/50">
787808
<td className="px-4 py-3">
788809
<div className="flex items-center gap-3">
789-
<div className="w-10 h-14 bg-gray-100 dark:bg-gray-700 rounded overflow-hidden flex-shrink-0">
790-
{book.coverUrl ? (
791-
<img
792-
src={getCoverImageUrl(book.coverUrl)}
793-
alt={book.title}
794-
className="w-full h-full object-cover"
795-
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
796-
/>
797-
) : (
798-
<div className="w-full h-full flex items-center justify-center bg-gradient-to-br from-blue-400 to-purple-500">
799-
<span className="text-white font-bold">{book.title.charAt(0)}</span>
800-
</div>
801-
)}
802-
</div>
810+
<BookCover book={book} className="w-10 h-14" />
803811
<span className="font-medium text-gray-900 dark:text-white truncate max-w-[200px]">{book.title}</span>
804812
</div>
805813
</td>

apps/desktop/src/pages/AuthorDetail.tsx

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,29 @@ import { getApiClient, type Book, type Author } from "@bookdock/api-client";
55
import { getCoverImageUrl } from "../utils/network";
66
import { ArrowLeft, BookOpen } from "lucide-react";
77

8+
function BookCover({ book, className = "" }: { book: Book; className?: string }) {
9+
const [coverError, setCoverError] = useState(false);
10+
const coverSrc = getCoverImageUrl(book.coverUrl);
11+
return (
12+
<div className={`aspect-[2/3] bg-gray-100 dark:bg-gray-700 rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-shadow ${className}`}>
13+
{coverSrc && !coverError ? (
14+
<img
15+
src={coverSrc}
16+
alt={book.title}
17+
className="w-full h-full object-cover"
18+
onError={() => setCoverError(true)}
19+
/>
20+
) : (
21+
<div className={`w-full h-full flex items-center justify-center bg-gradient-to-br ${getBookGradient(book.title)}`}>
22+
<span className="text-5xl text-white font-bold">
23+
{book.title.charAt(0)}
24+
</span>
25+
</div>
26+
)}
27+
</div>
28+
);
29+
}
30+
831
function getBookGradient(title: string): string {
932
const gradients = [
1033
"from-blue-400 to-purple-500",
@@ -146,23 +169,7 @@ export default function AuthorDetail() {
146169
className="w-36 cursor-pointer"
147170
onClick={() => handleBookSelect(book)}
148171
>
149-
<div className="aspect-[2/3] bg-gray-100 dark:bg-gray-700 rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-shadow">
150-
{book.coverUrl ? (
151-
<img
152-
src={getCoverImageUrl(book.coverUrl)}
153-
alt={book.title}
154-
className="w-full h-full object-cover"
155-
/>
156-
) : (
157-
<div
158-
className={`w-full h-full flex items-center justify-center bg-gradient-to-br ${getBookGradient(book.title)}`}
159-
>
160-
<span className="text-5xl text-white font-bold">
161-
{book.title.charAt(0)}
162-
</span>
163-
</div>
164-
)}
165-
</div>
172+
<BookCover book={book} />
166173
<p className="mt-2 text-sm font-medium text-gray-900 dark:text-white truncate">
167174
{book.title}
168175
</p>

apps/desktop/src/pages/BookDetail.tsx

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,29 @@ import { useCallback, useEffect, useMemo, useState } from "react";
1717
import { useNavigate, useParams } from "react-router-dom";
1818
import { getCoverImageUrl } from "../utils/network";
1919

20+
function BookCover({ book, className = "" }: { book: Book; className?: string }) {
21+
const [coverError, setCoverError] = useState(false);
22+
const coverSrc = getCoverImageUrl(book.coverUrl);
23+
return (
24+
<div className={`aspect-[2/3] rounded-xl overflow-hidden shadow-lg ${className}`}>
25+
{coverSrc && !coverError ? (
26+
<img
27+
src={coverSrc}
28+
alt={book.title}
29+
className="w-full h-full object-cover"
30+
onError={() => setCoverError(true)}
31+
/>
32+
) : (
33+
<div className={`w-full h-full flex items-center justify-center bg-gradient-to-br ${getBookGradient(book.title)}`}>
34+
<span className="text-6xl text-white font-bold">
35+
{book.title.charAt(0)}
36+
</span>
37+
</div>
38+
)}
39+
</div>
40+
);
41+
}
42+
2043
function getBookGradient(title: string): string {
2144
const gradients = [
2245
"from-blue-400 to-purple-500",
@@ -314,23 +337,7 @@ export default function BookDetail() {
314337
<div className="flex flex-col sm:flex-row items-start gap-4 sm:gap-6 md:gap-8 mb-8">
315338
{/* 封面 */}
316339
<div className="w-32 sm:w-44 md:w-52 flex-shrink-0 self-center sm:self-start">
317-
<div className="aspect-[2/3] rounded-xl overflow-hidden shadow-lg">
318-
{book.coverUrl ? (
319-
<img
320-
src={getCoverImageUrl(book.coverUrl)}
321-
alt={book.title}
322-
className="w-full h-full object-cover"
323-
/>
324-
) : (
325-
<div
326-
className={`w-full h-full flex items-center justify-center bg-gradient-to-br ${getBookGradient(book.title)}`}
327-
>
328-
<span className="text-6xl text-white font-bold">
329-
{book.title.charAt(0)}
330-
</span>
331-
</div>
332-
)}
333-
</div>
340+
<BookCover book={book} />
334341
</div>
335342

336343
{/* 信息 */}

apps/desktop/src/pages/CollectionDetail.tsx

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@ import { getApiClient, Book, Collection } from "@bookdock/api-client";
55
import { getCoverImageUrl } from "../utils/network";
66
import { ArrowLeft, X, Trash2 } from "lucide-react";
77

8+
function BookCover({ book, className = "", onClick }: { book: Book; className?: string; onClick?: () => void }) {
9+
const [coverError, setCoverError] = useState(false);
10+
const coverSrc = getCoverImageUrl(book.coverUrl);
11+
return (
12+
<div className={`rounded overflow-hidden flex-shrink-0 ${className}`} onClick={onClick}>
13+
{coverSrc && !coverError ? (
14+
<img
15+
src={coverSrc}
16+
alt={book.title}
17+
className="w-full h-full object-cover"
18+
onError={() => setCoverError(true)}
19+
/>
20+
) : (
21+
<div className={`w-full h-full flex items-center justify-center bg-gradient-to-br ${getBookGradient(book.title)}`}>
22+
<span className="text-xl text-white font-bold">{book.title.charAt(0)}</span>
23+
</div>
24+
)}
25+
</div>
26+
);
27+
}
28+
829
function getBookGradient(title: string): string {
930
const gradients = [
1031
"from-blue-400 to-purple-500",
@@ -107,24 +128,11 @@ export default function CollectionDetail() {
107128
key={book.id}
108129
className="flex items-center gap-3 p-3 bg-white dark:bg-gray-800 rounded-lg group"
109130
>
110-
<div
111-
className="w-12 h-18 rounded overflow-hidden flex-shrink-0 cursor-pointer"
131+
<BookCover
132+
book={book}
133+
className="w-12 h-18 cursor-pointer"
112134
onClick={() => navigate(`/book/${book.id}/detail`)}
113-
>
114-
{book.coverUrl ? (
115-
<img
116-
src={getCoverImageUrl(book.coverUrl)}
117-
alt={book.title}
118-
className="w-full h-full object-cover"
119-
/>
120-
) : (
121-
<div
122-
className={`w-full h-full flex items-center justify-center bg-gradient-to-br ${getBookGradient(book.title)}`}
123-
>
124-
<span className="text-xl text-white font-bold">{book.title.charAt(0)}</span>
125-
</div>
126-
)}
127-
</div>
135+
/>
128136
<div
129137
className="flex-1 min-w-0 cursor-pointer"
130138
onClick={() => navigate(`/book/${book.id}/detail`)}

apps/desktop/src/pages/Library.tsx

Lines changed: 70 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ const BookCard: React.FC<{ book: Book; onSelect: () => void }> = ({
8181
: progress >= 100
8282
? "bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400"
8383
: "bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400";
84+
const [coverError, setCoverError] = useState(false);
85+
const coverSrc = getCoverImageUrl(book.coverUrl);
8486

8587
return (
8688
<div
@@ -89,11 +91,12 @@ const BookCard: React.FC<{ book: Book; onSelect: () => void }> = ({
8991
>
9092
{/* Cover */}
9193
<div className="aspect-[2/3] rounded-xl overflow-hidden relative shadow-sm group-hover:shadow-md transition-shadow">
92-
{book.coverUrl ? (
94+
{coverSrc && !coverError ? (
9395
<img
94-
src={getCoverImageUrl(book.coverUrl)}
96+
src={coverSrc}
9597
alt={book.title}
9698
className="w-full h-full object-cover"
99+
onError={() => setCoverError(true)}
97100
/>
98101
) : (
99102
<div className={`w-full h-full flex items-center justify-center bg-gradient-to-br ${getBookGradient(book.title)}`}>
@@ -150,6 +153,68 @@ const BookCard: React.FC<{ book: Book; onSelect: () => void }> = ({
150153
);
151154
};
152155

156+
const BookListItem: React.FC<{ book: Book; onSelect: () => void }> = ({ book, onSelect }) => {
157+
const progress = book.readingProgress ?? 0;
158+
const statusText = progress === 0 ? "未读" : progress >= 100 ? "已读完" : "在读";
159+
const statusColor = progress === 0
160+
? "bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400"
161+
: progress >= 100
162+
? "bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400"
163+
: "bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400";
164+
const [coverError, setCoverError] = useState(false);
165+
const coverSrc = getCoverImageUrl(book.coverUrl);
166+
167+
return (
168+
<div
169+
className="flex items-center gap-4 py-4 cursor-pointer group hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors -mx-4 px-4 rounded-lg"
170+
onClick={onSelect}
171+
>
172+
{/* Cover */}
173+
<div className="w-12 h-16 rounded-lg overflow-hidden flex-shrink-0 shadow-sm">
174+
{coverSrc && !coverError ? (
175+
<img
176+
src={coverSrc}
177+
alt={book.title}
178+
className="w-full h-full object-cover"
179+
onError={() => setCoverError(true)}
180+
/>
181+
) : (
182+
<div className={`w-full h-full flex items-center justify-center bg-gradient-to-br ${getBookGradient(book.title)}`}>
183+
<span className="text-sm text-white font-bold">
184+
{book.title.charAt(0)}
185+
</span>
186+
</div>
187+
)}
188+
</div>
189+
190+
{/* Title + Author */}
191+
<div className="flex-1 min-w-0">
192+
<h3 className="font-medium text-gray-900 dark:text-white truncate">
193+
{book.title}
194+
</h3>
195+
<p className="text-sm text-gray-400 dark:text-gray-500 truncate">
196+
{book.author || "未知作者"}
197+
</p>
198+
</div>
199+
200+
{/* Format */}
201+
<span className="text-sm text-orange-500 dark:text-orange-400 flex-shrink-0">
202+
{(book.fileType || book.format || '').toUpperCase()}
203+
</span>
204+
205+
{/* Status badge */}
206+
<span className={`px-2.5 py-0.5 rounded-full text-xs font-medium flex-shrink-0 ${statusColor}`}>
207+
{statusText}
208+
</span>
209+
210+
{/* File size */}
211+
<span className="text-sm text-gray-400 dark:text-gray-500 flex-shrink-0 w-20 text-right">
212+
{formatFileSize(book.fileSize)}
213+
</span>
214+
</div>
215+
);
216+
};
217+
153218
export default function Library() {
154219
const navigate = useNavigate();
155220
const { books, isLoading, error, searchQuery, fetchBooks, setSearchQuery } =
@@ -629,65 +694,9 @@ export default function Library() {
629694
</div>
630695
) : (
631696
<div className="divide-y divide-gray-100 dark:divide-gray-800">
632-
{filteredBooks.map((book) => {
633-
const progress = book.readingProgress ?? 0;
634-
const statusText = progress === 0 ? "未读" : progress >= 100 ? "已读完" : "在读";
635-
const statusColor = progress === 0
636-
? "bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400"
637-
: progress >= 100
638-
? "bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400"
639-
: "bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400";
640-
641-
return (
642-
<div
643-
key={book.id}
644-
className="flex items-center gap-4 py-4 cursor-pointer group hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors -mx-4 px-4 rounded-lg"
645-
onClick={() => handleBookSelect(book)}
646-
>
647-
{/* Cover */}
648-
<div className="w-12 h-16 rounded-lg overflow-hidden flex-shrink-0 shadow-sm">
649-
{book.coverUrl ? (
650-
<img
651-
src={getCoverImageUrl(book.coverUrl)}
652-
alt={book.title}
653-
className="w-full h-full object-cover"
654-
/>
655-
) : (
656-
<div className={`w-full h-full flex items-center justify-center bg-gradient-to-br ${getBookGradient(book.title)}`}>
657-
<span className="text-sm text-white font-bold">
658-
{book.title.charAt(0)}
659-
</span>
660-
</div>
661-
)}
662-
</div>
663-
664-
{/* Title + Author */}
665-
<div className="flex-1 min-w-0">
666-
<h3 className="font-medium text-gray-900 dark:text-white truncate">
667-
{book.title}
668-
</h3>
669-
<p className="text-sm text-gray-400 dark:text-gray-500 truncate">
670-
{book.author || "未知作者"}
671-
</p>
672-
</div>
673-
674-
{/* Format */}
675-
<span className="text-sm text-orange-500 dark:text-orange-400 flex-shrink-0">
676-
{(book.fileType || book.format || '').toUpperCase()}
677-
</span>
678-
679-
{/* Status badge */}
680-
<span className={`px-2.5 py-0.5 rounded-full text-xs font-medium flex-shrink-0 ${statusColor}`}>
681-
{statusText}
682-
</span>
683-
684-
{/* File size */}
685-
<span className="text-sm text-gray-400 dark:text-gray-500 flex-shrink-0 w-20 text-right">
686-
{formatFileSize(book.fileSize)}
687-
</span>
688-
</div>
689-
);
690-
})}
697+
{filteredBooks.map((book) => (
698+
<BookListItem key={book.id} book={book} onSelect={() => handleBookSelect(book)} />
699+
))}
691700
</div>
692701
)}
693702
</section>

0 commit comments

Comments
 (0)