feat(ui): add navigation header and wire up real page routes

- Add sticky header with book management nav links and logout button
- Replace placeholder page components with real feature imports
- Add login page route to router
- Fix date-time formatting for consistent YYYY-MM-DD HH:mm output
This commit is contained in:
2026-05-24 19:28:29 +08:00
parent 1ca3417f0e
commit 4820370b6a
7 changed files with 356 additions and 19 deletions
+96
View File
@@ -0,0 +1,96 @@
import { Loader2Icon } from "lucide-react"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Skeleton } from "@/components/ui/skeleton"
import { useMyBorrows, useReturnBook } from "./hooks"
import { formatDateTime } from "@/lib/formatters"
export default function MyBorrowsPage() {
const { data: records, isLoading, error } = useMyBorrows()
const returnBook = useReturnBook()
if (isLoading) return <MyBorrowsSkeleton />
if (error) {
return (
<div className="mx-auto max-w-[1200px] px-4 py-8">
<div className="rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">
{error instanceof Error ? error.message : "未知错误"}
</div>
</div>
)
}
const list = records ?? []
if (list.length === 0) {
return (
<div className="mx-auto max-w-[1200px] px-4 py-16 text-center text-muted-foreground">
<p className="text-lg"></p>
<p className="mt-1 text-sm"></p>
</div>
)
}
return (
<div className="mx-auto max-w-[1200px] px-4 py-8">
<h1 className="mb-4 text-xl font-semibold"></h1>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="w-[100px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{list.map((r) => (
<TableRow key={r.id}>
<TableCell className="font-medium">{r.bookBorrowVo.name}</TableCell>
<TableCell>{r.bookBorrowVo.author}</TableCell>
<TableCell>{formatDateTime(r.borrowTime)}</TableCell>
<TableCell>
{r.returnTime ? formatDateTime(r.returnTime) : "-"}
</TableCell>
<TableCell>
<Badge variant={r.status === "BORROWED" ? "default" : "secondary"}>
{r.status === "BORROWED" ? "借阅中" : "已归还"}
</Badge>
</TableCell>
<TableCell>
{r.status === "BORROWED" && (
<Button
size="sm"
variant="outline"
disabled={returnBook.isPending}
onClick={() => returnBook.mutate(r.id)}
>
{returnBook.isPending && <Loader2Icon className="animate-spin" />}
</Button>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)
}
function MyBorrowsSkeleton() {
return (
<div className="mx-auto max-w-[1200px] px-4 py-8">
<Skeleton className="mb-4 h-7 w-24" />
<div className="space-y-2">
{Array.from({ length: 5 }).map((_, i) => (
<Skeleton key={i} className="h-10 w-full" />
))}
</div>
</div>
)
}
+24
View File
@@ -0,0 +1,24 @@
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { getAllMyBorrows, returnBookForMe } from "@/api/borrows"
import { toast } from "sonner"
export function useMyBorrows() {
return useQuery({
queryKey: ["myBorrows"],
queryFn: getAllMyBorrows,
})
}
export function useReturnBook() {
const qc = useQueryClient()
return useMutation({
mutationFn: returnBookForMe,
onSuccess: () => {
qc.invalidateQueries({ queryKey: ["myBorrows"] })
toast.success("归还成功")
},
onError: (err) => {
toast.error(err instanceof Error ? err.message : "归还失败")
},
})
}