commit 6c659a4dd414309fcd7153db2a189f31adf94e61 Author: grtsinry43 Date: Thu May 22 20:42:10 2025 +0800 Initial Commit 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)} + /> + ) : ( +
+ +

无封面图片

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