2025-05-22 20:42:10 +08:00

210 lines
6.6 KiB
TypeScript

"use client"
import { useEffect, useState } from "react"
import { Card, CardContent, CardFooter } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { useToast } from "@/components/ui/use-toast"
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
import Link from "next/link"
import { ShoppingCart } from "lucide-react"
import { useCart } from "@/context/cart-context"
import { fetchWithAuth } from "@/lib/api"
interface Book {
bookId: number
title: string
isbn: string
price: number
stock: number
publishDate: string
publisherName: string
description: string
coverImage: string
author: string[]
}
interface BooksResponse {
pageNum: number
pageSize: number
total: number
data: Book[]
}
export function BookList() {
const [books, setBooks] = useState<BooksResponse | null>(null)
const [loading, setLoading] = useState(true)
const [currentPage, setCurrentPage] = useState(1)
const { toast } = useToast()
const { addToCart } = useCart()
useEffect(() => {
const fetchBooks = async () => {
try {
const response = await fetchWithAuth(`book/all?pageNum=${currentPage}&pageSize=8`)
const result = await response.json()
if (result.code === 0) {
setBooks(result.data)
} else {
toast({
variant: "destructive",
title: "获取图书失败",
description: result.msg || "无法获取图书信息",
})
}
} catch (error) {
toast({
variant: "destructive",
title: "获取图书失败",
description: "服务器连接错误,请稍后再试",
})
} finally {
setLoading(false)
}
}
fetchBooks()
}, [currentPage, toast])
const handlePageChange = (page: number) => {
setCurrentPage(page)
}
const handleAddToCart = (book: Book) => {
addToCart({
id: book.bookId,
title: book.title,
price: book.price,
quantity: 1,
coverImage: book.coverImage || "/placeholder.svg?height=100&width=80",
})
toast({
title: "已添加到购物车",
description: book.title,
})
}
if (loading) {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 py-6">
{Array.from({ length: 8 }).map((_, index) => (
<Card key={index} className="overflow-hidden">
<div className="aspect-[3/4] bg-muted animate-pulse" />
<CardContent className="p-4">
<div className="h-4 bg-muted animate-pulse rounded mb-2" />
<div className="h-4 bg-muted animate-pulse rounded w-2/3" />
</CardContent>
<CardFooter className="p-4 pt-0 flex justify-between">
<div className="h-4 bg-muted animate-pulse rounded w-1/4" />
<div className="h-8 bg-muted animate-pulse rounded w-1/3" />
</CardFooter>
</Card>
))}
</div>
)
}
if (!books || books.data.length === 0) {
return (
<div className="flex justify-center items-center py-12">
<p className="text-muted-foreground"></p>
</div>
)
}
return (
<div className="space-y-6 py-6">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{books.data.map((book) => (
<Card key={book.bookId} className="overflow-hidden flex flex-col">
<Link href={`/books/${book.bookId}`}>
<div className="aspect-[3/4] relative overflow-hidden">
<img
src={book.coverImage || "/placeholder.svg?height=400&width=300"}
alt={book.title}
className="object-cover w-full h-full transition-transform hover:scale-105"
/>
</div>
</Link>
<CardContent className="p-4 flex-grow">
<Link href={`/books/${book.bookId}`}>
<h3 className="font-semibold hover:underline line-clamp-2">{book.title}</h3>
</Link>
<p className="text-sm text-muted-foreground mt-1">{book.author.join(", ")}</p>
</CardContent>
<CardFooter className="p-4 pt-0 flex justify-between items-center">
<div className="font-bold">¥{book.price.toFixed(2)}</div>
<Button size="sm" onClick={() => handleAddToCart(book)} disabled={book.stock <= 0}>
<ShoppingCart className="h-4 w-4 mr-2" />
{book.stock > 0 ? "加入购物车" : "缺货"}
</Button>
</CardFooter>
</Card>
))}
</div>
{books.total > books.pageSize && (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault()
if (currentPage > 1) handlePageChange(currentPage - 1)
}}
className={currentPage === 1 ? "pointer-events-none opacity-50" : ""}
/>
</PaginationItem>
{Array.from({ length: Math.min(5, Math.ceil(books.total / books.pageSize)) }).map((_, index) => {
let pageNumber = currentPage - 2 + index
if (pageNumber < 1) pageNumber = index + 1
if (pageNumber > Math.ceil(books.total / books.pageSize))
pageNumber = Math.ceil(books.total / books.pageSize) - (4 - index)
return (
<PaginationItem key={index}>
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault()
handlePageChange(pageNumber)
}}
isActive={currentPage === pageNumber}
>
{pageNumber}
</PaginationLink>
</PaginationItem>
)
})}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault()
if (currentPage < Math.ceil(books.total / books.pageSize)) {
handlePageChange(currentPage + 1)
}
}}
className={
currentPage >= Math.ceil(books.total / books.pageSize) ? "pointer-events-none opacity-50" : ""
}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
)}
</div>
)
}