From 6c659a4dd414309fcd7153db2a189f31adf94e61 Mon Sep 17 00:00:00 2001 From: grtsinry43 Date: Thu, 22 May 2025 20:42:10 +0800 Subject: [PATCH] Initial Commit --- .gitignore | 27 + app/admin/books/add/page.tsx | 451 +++ app/admin/books/edit/[id]/page.tsx | 554 +++ app/admin/books/loading.tsx | 3 + app/admin/books/page.tsx | 354 ++ app/admin/orders/loading.tsx | 3 + app/admin/orders/page.tsx | 284 ++ app/admin/page.tsx | 239 ++ app/admin/publishers/loading.tsx | 3 + app/admin/publishers/page.tsx | 473 +++ app/admin/users/loading.tsx | 3 + app/admin/users/page.tsx | 329 ++ app/books/[id]/loading.tsx | 3 + app/books/[id]/page.tsx | 332 ++ app/books/loading.tsx | 3 + app/books/page.tsx | 423 +++ app/cart/loading.tsx | 3 + app/cart/page.tsx | 211 ++ app/globals.css | 283 ++ app/layout.tsx | 43 + app/loading.tsx | 3 + app/login/page.tsx | 185 + app/orders/[id]/loading.tsx | 3 + app/orders/[id]/page.tsx | 245 ++ app/orders/loading.tsx | 3 + app/orders/page.tsx | 263 ++ app/page.tsx | 76 + app/providers.tsx | 8 + app/register/page.tsx | 181 + components.json | 21 + components/admin-nav.tsx | 72 + components/book-list.tsx | 209 ++ components/main-nav.tsx | 61 + components/notification-provider.tsx | 84 + components/search-bar.tsx | 189 ++ components/shopping-cart.tsx | 159 + components/theme-provider.tsx | 11 + components/theme-toggle.tsx | 27 + components/ui/accordion.tsx | 58 + components/ui/alert-dialog.tsx | 141 + components/ui/alert.tsx | 59 + components/ui/aspect-ratio.tsx | 7 + components/ui/avatar.tsx | 50 + components/ui/badge.tsx | 36 + components/ui/breadcrumb.tsx | 115 + components/ui/button.tsx | 56 + components/ui/calendar.tsx | 66 + components/ui/card.tsx | 79 + components/ui/carousel.tsx | 262 ++ components/ui/chart.tsx | 365 ++ components/ui/checkbox.tsx | 30 + components/ui/collapsible.tsx | 11 + components/ui/combobox.tsx | 54 + components/ui/command.tsx | 153 + components/ui/context-menu.tsx | 200 ++ components/ui/dialog.tsx | 122 + components/ui/drawer.tsx | 118 + components/ui/dropdown-menu.tsx | 200 ++ components/ui/form.tsx | 178 + components/ui/hover-card.tsx | 29 + components/ui/input-otp.tsx | 71 + components/ui/input.tsx | 22 + components/ui/label.tsx | 26 + components/ui/menubar.tsx | 236 ++ components/ui/navigation-menu.tsx | 128 + components/ui/pagination.tsx | 117 + components/ui/popover.tsx | 31 + components/ui/progress.tsx | 28 + components/ui/radio-group.tsx | 44 + components/ui/resizable.tsx | 45 + components/ui/scroll-area.tsx | 48 + components/ui/select.tsx | 160 + components/ui/separator.tsx | 31 + components/ui/sheet.tsx | 140 + components/ui/sidebar.tsx | 763 +++++ components/ui/skeleton.tsx | 15 + components/ui/slider.tsx | 28 + components/ui/sonner.tsx | 31 + components/ui/switch.tsx | 29 + components/ui/table.tsx | 117 + components/ui/tabs.tsx | 55 + components/ui/textarea.tsx | 22 + components/ui/toast.tsx | 129 + components/ui/toaster.tsx | 35 + components/ui/toggle-group.tsx | 61 + components/ui/toggle.tsx | 45 + components/ui/tooltip.tsx | 30 + components/ui/use-mobile.tsx | 19 + components/ui/use-toast.ts | 194 ++ components/user-nav.tsx | 74 + context/auth-context.tsx | 85 + context/cart-context.tsx | 87 + hooks/use-mobile.tsx | 19 + hooks/use-toast.ts | 194 ++ lib/api.ts | 135 + lib/event-bus.ts | 78 + lib/utils.ts | 6 + next.config.mjs | 24 + package.json | 78 + pnpm-lock.yaml | 4718 ++++++++++++++++++++++++++ pnpm-workspace.yaml | 2 + postcss.config.mjs | 8 + public/placeholder-logo.png | Bin 0 -> 958 bytes public/placeholder-logo.svg | 1 + public/placeholder-user.jpg | Bin 0 -> 2615 bytes public/placeholder.jpg | Bin 0 -> 1596 bytes public/placeholder.svg | 1 + styles/globals.css | 94 + tailwind.config.ts | 102 + tsconfig.json | 27 + 110 files changed, 16646 insertions(+) create mode 100644 .gitignore create mode 100644 app/admin/books/add/page.tsx create mode 100644 app/admin/books/edit/[id]/page.tsx create mode 100644 app/admin/books/loading.tsx create mode 100644 app/admin/books/page.tsx create mode 100644 app/admin/orders/loading.tsx create mode 100644 app/admin/orders/page.tsx create mode 100644 app/admin/page.tsx create mode 100644 app/admin/publishers/loading.tsx create mode 100644 app/admin/publishers/page.tsx create mode 100644 app/admin/users/loading.tsx create mode 100644 app/admin/users/page.tsx create mode 100644 app/books/[id]/loading.tsx create mode 100644 app/books/[id]/page.tsx create mode 100644 app/books/loading.tsx create mode 100644 app/books/page.tsx create mode 100644 app/cart/loading.tsx create mode 100644 app/cart/page.tsx create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/loading.tsx create mode 100644 app/login/page.tsx create mode 100644 app/orders/[id]/loading.tsx create mode 100644 app/orders/[id]/page.tsx create mode 100644 app/orders/loading.tsx create mode 100644 app/orders/page.tsx create mode 100644 app/page.tsx create mode 100644 app/providers.tsx create mode 100644 app/register/page.tsx create mode 100644 components.json create mode 100644 components/admin-nav.tsx create mode 100644 components/book-list.tsx create mode 100644 components/main-nav.tsx create mode 100644 components/notification-provider.tsx create mode 100644 components/search-bar.tsx create mode 100644 components/shopping-cart.tsx create mode 100644 components/theme-provider.tsx create mode 100644 components/theme-toggle.tsx create mode 100644 components/ui/accordion.tsx create mode 100644 components/ui/alert-dialog.tsx create mode 100644 components/ui/alert.tsx create mode 100644 components/ui/aspect-ratio.tsx create mode 100644 components/ui/avatar.tsx create mode 100644 components/ui/badge.tsx create mode 100644 components/ui/breadcrumb.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/calendar.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/carousel.tsx create mode 100644 components/ui/chart.tsx create mode 100644 components/ui/checkbox.tsx create mode 100644 components/ui/collapsible.tsx create mode 100644 components/ui/combobox.tsx create mode 100644 components/ui/command.tsx create mode 100644 components/ui/context-menu.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/drawer.tsx create mode 100644 components/ui/dropdown-menu.tsx create mode 100644 components/ui/form.tsx create mode 100644 components/ui/hover-card.tsx create mode 100644 components/ui/input-otp.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/menubar.tsx create mode 100644 components/ui/navigation-menu.tsx create mode 100644 components/ui/pagination.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/ui/progress.tsx create mode 100644 components/ui/radio-group.tsx create mode 100644 components/ui/resizable.tsx create mode 100644 components/ui/scroll-area.tsx create mode 100644 components/ui/select.tsx create mode 100644 components/ui/separator.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 components/ui/sidebar.tsx create mode 100644 components/ui/skeleton.tsx create mode 100644 components/ui/slider.tsx create mode 100644 components/ui/sonner.tsx create mode 100644 components/ui/switch.tsx create mode 100644 components/ui/table.tsx create mode 100644 components/ui/tabs.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 components/ui/toast.tsx create mode 100644 components/ui/toaster.tsx create mode 100644 components/ui/toggle-group.tsx create mode 100644 components/ui/toggle.tsx create mode 100644 components/ui/tooltip.tsx create mode 100644 components/ui/use-mobile.tsx create mode 100644 components/ui/use-toast.ts create mode 100644 components/user-nav.tsx create mode 100644 context/auth-context.tsx create mode 100644 context/cart-context.tsx create mode 100644 hooks/use-mobile.tsx create mode 100644 hooks/use-toast.ts create mode 100644 lib/api.ts create mode 100644 lib/event-bus.ts create mode 100644 lib/utils.ts create mode 100644 next.config.mjs create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 postcss.config.mjs create mode 100644 public/placeholder-logo.png create mode 100644 public/placeholder-logo.svg create mode 100644 public/placeholder-user.jpg create mode 100644 public/placeholder.jpg create mode 100644 public/placeholder.svg create mode 100644 styles/globals.css create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f650315 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# next.js +/.next/ +/out/ + +# production +/build + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/app/admin/books/add/page.tsx b/app/admin/books/add/page.tsx new file mode 100644 index 0000000..7aafbae --- /dev/null +++ b/app/admin/books/add/page.tsx @@ -0,0 +1,451 @@ +"use client" + +import { AdminNav } from "@/components/admin-nav" +import { ThemeToggle } from "@/components/theme-toggle" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Combobox } from "@/components/ui/combobox" +import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" +import { Textarea } from "@/components/ui/textarea" +import { useToast } from "@/components/ui/use-toast" +import { useAuth } from "@/context/auth-context" +import { zodResolver } from "@hookform/resolvers/zod" +import { format } from "date-fns" +import { ArrowLeft, BookOpen, CalendarIcon, ImageIcon, Loader2, Plus, Save, X } from "lucide-react" +import { useRouter } from "next/navigation" +import { useEffect, useState } from "react" +import { useFieldArray, useForm } from "react-hook-form" +import * as z from "zod" +import { fetchWithAuth } from "@/lib/api" + +interface Publisher { + publisherId: number + name: string + address: string +} + +const formSchema = z.object({ + title: z.string().min(1, "标题不能为空"), + isbn: z.string().min(1, "ISBN不能为空"), + price: z.coerce.number().min(0, "价格不能为负数"), + stock: z.coerce.number().min(0, "库存不能为负数").int("库存必须是整数"), + publishDate: z.date(), + publisherId: z.coerce.number(), + description: z.string().optional(), + coverImage: z.string().optional(), + authors: z + .array( + z.object({ + name: z.string().min(1, "作者名不能为空"), + }), + ) + .min(1, "至少需要一位作者"), +}) + +export default function AddBookPage() { + const { user } = useAuth() + const router = useRouter() + const { toast } = useToast() + const [publishers, setPublishers] = useState([]) + const [loading, setLoading] = useState(false) + const [coverPreview, setCoverPreview] = useState(null) + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + title: "", + isbn: "", + price: 0, + stock: 0, + publishDate: new Date(), + publisherId: 0, + description: "", + coverImage: "", + authors: [{ name: "" }], + }, + }) + + const { fields, append, remove } = useFieldArray({ + control: form.control, + name: "authors", + }) + + // 监听封面图片URL变化,更新预览 + const coverImageValue = form.watch("coverImage") + useEffect(() => { + if (coverImageValue) { + setCoverPreview(coverImageValue) + } + }, [coverImageValue]) + + useEffect(() => { + // 检查用户是否登录且是管理员 + if (!user) { + toast({ + title: "请先登录", + description: "您需要登录后才能访问管理页面", + variant: "destructive", + }) + router.push("/login") + return + } + + if (!user.isAdmin) { + toast({ + title: "权限不足", + description: "您没有管理员权限", + variant: "destructive", + }) + router.push("/") + return + } + + // 获取出版社列表 + const fetchPublishers = async () => { + try { + const response = await fetchWithAuth("publisher/all") + const result = await response.json() + + if (result.code === 0) { + setPublishers(result.data) + } else { + toast({ + variant: "destructive", + title: "获取出版社失败", + description: result.msg || "无法获取出版社信息", + }) + } + } catch (error) { + toast({ + variant: "destructive", + title: "获取出版社失败", + description: "服务器连接错误,请稍后再试", + }) + } + } + + fetchPublishers() + }, [user, router, toast]) + + const onSubmit = async (values: z.infer) => { + setLoading(true) + try { + const bookData = { + ...values, + authors: values.authors.map((author) => author.name), + } + + const response = await fetchWithAuth("book/admin/add", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(bookData), + }) + + const result = await response.json() + + if (result.code === 0) { + toast({ + title: "添加成功", + description: "图书已成功添加", + }) + router.push("/admin/books") + } else { + toast({ + variant: "destructive", + title: "添加失败", + description: result.msg || "无法添加图书", + }) + } + } catch (error) { + toast({ + variant: "destructive", + title: "添加失败", + description: "服务器连接错误,请稍后再试", + }) + } finally { + setLoading(false) + } + } + + if (!user || !user.isAdmin) { + return null + } + + return ( +
+
+
+
+ + 图书管理系统 - 管理后台 +
+
+ + +
+
+
+
+ +
+
+
+ +

添加图书

+
+ +
+ +
+ + + 基本信息 + 填写图书的基本信息 + + + ( + + 标题 + + + + + + )} + /> + + ( + + ISBN + + + + + + )} + /> + +
+ ( + + 价格 + + + + + + )} + /> + + ( + + 库存 + + + + + + )} + /> +
+ + ( + + 出版日期 + + + + + + + + + + + + + )} + /> + + ( + + 出版社 + ({ + label: publisher.name, + value: publisher.publisherId.toString(), + }))} + value={field.value.toString()} + onChange={(value) => field.onChange(Number.parseInt(value))} + placeholder="选择出版社" + /> + + + )} + /> +
+
+ + + + 封面与描述 + 填写图书的封面和描述信息 + + + ( + + 封面图片URL + + + + 输入图书封面的URL地址 + + + )} + /> + +
+

封面预览

+
+ {coverPreview ? ( + 封面预览 setCoverPreview(null)} + /> + ) : ( +
+ +

无封面图片

+
+ )} +
+
+ + ( + + 描述 + +