mirror of
https://litchi.icu/ngc2207/judge4c-latest.git
synced 2025-07-13 11:34:35 +00:00
feat: add problem create page and components
This commit is contained in:
parent
6b71635ded
commit
b41dd6be26
119
src/app/(main)/problems/new/components/basic-form.tsx
Normal file
119
src/app/(main)/problems/new/components/basic-form.tsx
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "@/hooks/use-toast";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
|
const basicFormSchema = z.object({
|
||||||
|
title: z.string().min(1, { message: "题目标题不能为空" }),
|
||||||
|
difficulty: z.enum(["简单", "中等", "困难"], {
|
||||||
|
required_error: "请选择题目难度",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
type BasicFormValues = z.infer<typeof basicFormSchema>;
|
||||||
|
|
||||||
|
export function BasicForm() {
|
||||||
|
const form = useForm<BasicFormValues>({
|
||||||
|
resolver: zodResolver(basicFormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
title: "",
|
||||||
|
difficulty: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function onSubmit(data: BasicFormValues) {
|
||||||
|
toast({
|
||||||
|
title: "题目基本信息创建成功",
|
||||||
|
description: (
|
||||||
|
<div>
|
||||||
|
<p>题目标题:{data.title}</p>
|
||||||
|
<p>题目难度:{data.difficulty}</p>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="title"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>题目标题</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} placeholder="例如: 两数之和" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="difficulty"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>题目难度</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="点击此处选择" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="简单">简单</SelectItem>
|
||||||
|
<SelectItem value="中等">中等</SelectItem>
|
||||||
|
<SelectItem value="困难">困难</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
<Accordion type="single" collapsible>
|
||||||
|
<AccordionItem value="difficulty">
|
||||||
|
<AccordionTrigger>点击此处查看题目难度定义</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<p>
|
||||||
|
简单:这类题目通常涉及基本的编程概念,如变量、循环、条件语句等。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
中等:这类题目可能需要一些基本的算法知识,如排序、搜索、简单的动态规划等。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
困难:这类题目通常涉及复杂的算法和数据结构,如高级动态规划、图论、网络流等。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
61
src/app/(main)/problems/new/components/details-form.tsx
Normal file
61
src/app/(main)/problems/new/components/details-form.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { toast } from "@/hooks/use-toast";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
|
const detailsFormSchema = z.object({
|
||||||
|
description: z.string().min(1, { message: "题目描述不能为空" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
type DetailsFormValues = z.infer<typeof detailsFormSchema>;
|
||||||
|
|
||||||
|
export function DeatilsForm() {
|
||||||
|
const form = useForm<DetailsFormValues>({
|
||||||
|
resolver: zodResolver(detailsFormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function onSubmit(data: DetailsFormValues) {
|
||||||
|
toast({
|
||||||
|
title: "题目详细内容创建成功",
|
||||||
|
description: (
|
||||||
|
<div>
|
||||||
|
<p>题目描述:{data.description}</p>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>题目描述</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea placeholder="描述题目要求即可" className="h-72"{...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
43
src/app/(main)/problems/new/components/sidebar-nav.tsx
Normal file
43
src/app/(main)/problems/new/components/sidebar-nav.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { buttonVariants } from "@/components/ui/button";
|
||||||
|
|
||||||
|
interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> {
|
||||||
|
items: {
|
||||||
|
href: string;
|
||||||
|
title: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
className={cn(
|
||||||
|
"flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{items.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ variant: "ghost" }),
|
||||||
|
pathname === item.href
|
||||||
|
? "bg-muted hover:bg-muted"
|
||||||
|
: "hover:bg-transparent hover:underline",
|
||||||
|
"justify-start"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
17
src/app/(main)/problems/new/details/page.tsx
Normal file
17
src/app/(main)/problems/new/details/page.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { DeatilsForm } from "../components/details-form";
|
||||||
|
|
||||||
|
export default function DetailsFormPage() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-medium">详细内容</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
题目的详细内容,例如题目描述、示例等
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<DeatilsForm />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
35
src/app/(main)/problems/new/layout.tsx
Normal file
35
src/app/(main)/problems/new/layout.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { SidebarNav } from "./components/sidebar-nav";
|
||||||
|
|
||||||
|
const sidebarNavItems = [
|
||||||
|
{
|
||||||
|
title: "基本信息",
|
||||||
|
href: "/problems/new",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "详细内容",
|
||||||
|
href: "/problems/new/details",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface SettingsLayoutProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ProblemCreateLayout({ children }: SettingsLayoutProps) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6 px-10 pt-5 pb-16">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<h2 className="text-2xl font-bold tracking-tight">新建题目</h2>
|
||||||
|
<p className="text-muted-foreground">在此处编辑题目信息</p>
|
||||||
|
</div>
|
||||||
|
<Separator className="my-6" />
|
||||||
|
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
|
||||||
|
<aside className="-mx-4 lg:w-1/5">
|
||||||
|
<SidebarNav items={sidebarNavItems} />
|
||||||
|
</aside>
|
||||||
|
<div className="flex-1 lg:max-w-2xl">{children}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
17
src/app/(main)/problems/new/page.tsx
Normal file
17
src/app/(main)/problems/new/page.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { BasicForm } from "./components/basic-form";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
|
export default function BasicFormPage() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-medium">基本信息</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
题目的基本信息,例如标题、描述等
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<BasicForm />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -3,6 +3,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import Header from "@/components/header";
|
import Header from "@/components/header";
|
||||||
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
import { AppSidebar } from "@/components/app-sidebar";
|
import { AppSidebar } from "@/components/app-sidebar";
|
||||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
||||||
|
|
||||||
@ -26,7 +27,8 @@ export default function RootLayout({
|
|||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
<SidebarInset>
|
<SidebarInset>
|
||||||
<Header />
|
<Header />
|
||||||
{children}
|
<main>{children}</main>
|
||||||
|
<Toaster />
|
||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
</body>
|
</body>
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
export default function ProblemCreatePage() {
|
|
||||||
return <div>Problem Create Page</div>;
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export default function ProblemsetPage() {
|
|
||||||
return <div>Problemset Page</div>;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user