210 lines
6.6 KiB
TypeScript
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>
|
|
)
|
|
}
|