refactor(creater): wrap Panel with PanelLayout and ScrollArea

This commit is contained in:
cfngc4594 2025-06-20 23:36:44 +08:00
parent ecaba8f48b
commit 7cda49116e
7 changed files with 480 additions and 434 deletions

View File

@ -1,14 +1,16 @@
"use client"; "use client";
import React, { useEffect, useState } from "react";
import { getProblemData } from "@/app/actions/getProblem";
import { updateProblemTemplate } from "@/components/creater/problem-maintain";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { CoreEditor } from "@/components/core-editor";
import { Language } from "@/generated/client";
import { toast } from "sonner"; import { toast } from "sonner";
import { Label } from "@/components/ui/label";
import { Language } from "@/generated/client";
import { Button } from "@/components/ui/button";
import React, { useEffect, useState } from "react";
import { CoreEditor } from "@/components/core-editor";
import { ScrollArea } from "@/components/ui/scroll-area";
import { getProblemData } from "@/app/actions/getProblem";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
import { updateProblemTemplate } from "@/components/creater/problem-maintain";
interface Template { interface Template {
language: string; language: string;
@ -67,45 +69,50 @@ export default function EditCodePanel({ problemId }: EditCodePanelProps) {
}; };
return ( return (
<Card className="w-full"> <PanelLayout>
<CardHeader> <ScrollArea className="h-full">
<CardTitle></CardTitle> <Card className="w-full rounded-none border-none bg-background">
</CardHeader> <CardHeader className="px-6 py-4">
<CardContent> <div className="flex items-center justify-between">
<div className="space-y-6"> <span></span>
<div className="space-y-2"> <Button onClick={handleSave}></Button>
<Label htmlFor="language-select"></Label>
<select
id="language-select"
className="block w-full p-2 border border-gray-300 rounded-md dark:bg-gray-800 dark:border-gray-700"
value={codeTemplate.language}
onChange={(e) => handleLanguageChange(e.target.value)}
>
{templates.map((t) => (
<option key={t.language} value={t.language}>
{t.language.toUpperCase()}
</option>
))}
</select>
</div>
<div className="space-y-2">
<Label htmlFor="code-editor"></Label>
<div className="border rounded-md h-[500px]">
<CoreEditor
language={codeTemplate.language}
value={codeTemplate.content}
path={`/${problemId}.${codeTemplate.language}`}
onChange={(value) =>
setCodeTemplate({ ...codeTemplate, content: value || "" })
}
/>
</div> </div>
</div> </CardHeader>
<CardContent>
<div className="space-y-6">
<div className="space-y-2">
<Label htmlFor="language-select"></Label>
<select
id="language-select"
className="block w-full p-2 border border-gray-300 rounded-md dark:bg-gray-800 dark:border-gray-700"
value={codeTemplate.language}
onChange={(e) => handleLanguageChange(e.target.value)}
>
{templates.map((t) => (
<option key={t.language} value={t.language}>
{t.language.toUpperCase()}
</option>
))}
</select>
</div>
<Button onClick={handleSave}></Button> <div className="space-y-2">
</div> <Label htmlFor="code-editor"></Label>
</CardContent> <div className="border rounded-md h-[500px]">
</Card> <CoreEditor
language={codeTemplate.language}
value={codeTemplate.content}
path={`/${problemId}.${codeTemplate.language}`}
onChange={(value) =>
setCodeTemplate({ ...codeTemplate, content: value || "" })
}
/>
</div>
</div>
</div>
</CardContent>
</Card>
</ScrollArea>
</PanelLayout>
); );
} }

View File

@ -1,22 +1,24 @@
"use client"; "use client";
import React, { useEffect, useState } from "react"; import { toast } from "sonner";
import { Locale } from "@/generated/client";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { CoreEditor } from "@/components/core-editor";
import MdxPreview from "@/components/mdx-preview";
import { getProblemData } from "@/app/actions/getProblem";
import { getProblemLocales } from "@/app/actions/getProblemLocales";
import { Accordion } from "@/components/ui/accordion";
import { VideoEmbed } from "@/components/content/video-embed";
import { toast } from "sonner";
import { import {
updateProblemDescription, updateProblemDescription,
updateProblemTitle, updateProblemTitle,
} from "@/components/creater/problem-maintain"; } from "@/components/creater/problem-maintain";
import { Locale } from "@/generated/client"; import { Button } from "@/components/ui/button";
import MdxPreview from "@/components/mdx-preview";
import React, { useEffect, useState } from "react";
import { Accordion } from "@/components/ui/accordion";
import { CoreEditor } from "@/components/core-editor";
import { ScrollArea } from "@/components/ui/scroll-area";
import { getProblemData } from "@/app/actions/getProblem";
import { VideoEmbed } from "@/components/content/video-embed";
import { getProblemLocales } from "@/app/actions/getProblemLocales";
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export default function EditDescriptionPanel({ export default function EditDescriptionPanel({
problemId, problemId,
@ -102,107 +104,114 @@ export default function EditDescriptionPanel({
}; };
return ( return (
<Card className="w-full"> <PanelLayout>
<CardHeader> <ScrollArea className="h-full">
<CardTitle></CardTitle> <Card className="w-full rounded-none border-none bg-background">
</CardHeader> <CardHeader>
<CardContent className="space-y-6"> <CardTitle></CardTitle>
{/* 语言切换 */} </CardHeader>
<div className="space-y-2"> <CardContent className="space-y-6">
<Label></Label> {/* 语言切换 */}
<div className="flex space-x-2"> <div className="space-y-2">
<select <Label></Label>
value={currentLocale} <div className="flex space-x-2">
onChange={(e) => setCurrentLocale(e.target.value)} <select
className="border rounded-md px-3 py-2" value={currentLocale}
> onChange={(e) => setCurrentLocale(e.target.value)}
{locales.map((locale) => ( className="border rounded-md px-3 py-2"
<option key={locale} value={locale}> >
{locale} {locales.map((locale) => (
</option> <option key={locale} value={locale}>
))} {locale}
</select> </option>
<Input ))}
placeholder="添加新语言" </select>
value={customLocale} <Input
onChange={(e) => setCustomLocale(e.target.value)} placeholder="添加新语言"
/> value={customLocale}
<Button onClick={handleAddCustomLocale}></Button> onChange={(e) => setCustomLocale(e.target.value)}
</div> />
</div> <Button onClick={handleAddCustomLocale}></Button>
</div>
</div>
{/* 标题输入 */} {/* 标题输入 */}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="description-title"></Label> <Label htmlFor="description-title"></Label>
<Input <Input
id="description-title" id="description-title"
value={description.title} value={description.title}
onChange={(e) => onChange={(e) =>
setDescription({ ...description, title: e.target.value }) setDescription({ ...description, title: e.target.value })
}
placeholder="输入题目标题"
/>
</div>
{/* 编辑/预览切换 */}
<div className="flex space-x-2">
<Button
type="button"
variant={viewMode === "edit" ? "default" : "outline"}
onClick={() => setViewMode("edit")}
>
</Button>
<Button
type="button"
variant={viewMode === "preview" ? "default" : "outline"}
onClick={() =>
setViewMode(viewMode === "preview" ? "edit" : "preview")
}
>
{viewMode === "preview" ? "取消" : "预览"}
</Button>
<Button
type="button"
variant={viewMode === "compare" ? "default" : "outline"}
onClick={() => setViewMode("compare")}
>
</Button>
</div>
{/* 编辑/预览区域 */}
<div
className={
viewMode === "compare"
? "grid grid-cols-2 gap-6"
: "flex flex-col gap-6"
}
>
{(viewMode === "edit" || viewMode === "compare") && (
<div className="relative h-[600px]">
<CoreEditor
value={description.content}
onChange={(newVal) =>
setDescription({ ...description, content: newVal || "" })
} }
language="markdown" placeholder="输入题目标题"
className="absolute inset-0 rounded-md border border-input"
/> />
</div> </div>
)}
{viewMode !== "edit" && (
<div className="prose dark:prose-invert">
<MdxPreview
source={description.content}
components={{ Accordion, VideoEmbed }}
/>
</div>
)}
</div>
<Button onClick={handleSave}></Button> {/* 编辑/预览切换 */}
</CardContent> <div className="flex items-center justify-between">
</Card> <div className="flex items-center space-x-2">
<Button
type="button"
variant={viewMode === "edit" ? "default" : "outline"}
onClick={() => setViewMode("edit")}
>
</Button>
<Button
type="button"
variant={viewMode === "preview" ? "default" : "outline"}
onClick={() =>
setViewMode(viewMode === "preview" ? "edit" : "preview")
}
>
{viewMode === "preview" ? "取消" : "预览"}
</Button>
<Button
type="button"
variant={viewMode === "compare" ? "default" : "outline"}
onClick={() => setViewMode("compare")}
>
</Button>
</div>
<div className="flex items-center">
<Button onClick={handleSave}></Button>
</div>
</div>
{/* 编辑/预览区域 */}
<div
className={
viewMode === "compare"
? "grid grid-cols-2 gap-6"
: "flex flex-col gap-6"
}
>
{(viewMode === "edit" || viewMode === "compare") && (
<div className="relative h-[600px]">
<CoreEditor
value={description.content}
onChange={(newVal) =>
setDescription({ ...description, content: newVal || "" })
}
language="markdown"
className="absolute inset-0 rounded-md border border-input"
/>
</div>
)}
{viewMode !== "edit" && (
<div className="prose dark:prose-invert">
<MdxPreview
source={description.content}
components={{ Accordion, VideoEmbed }}
/>
</div>
)}
</div>
</CardContent>
</Card>
</ScrollArea>
</PanelLayout>
); );
} }

View File

@ -1,14 +1,16 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import { toast } from "sonner";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { getProblemData } from "@/app/actions/getProblem";
import { toast } from "sonner";
import { updateProblemDetail } from "@/components/creater/problem-maintain";
import { Difficulty } from "@/generated/client"; import { Difficulty } from "@/generated/client";
import React, { useState, useEffect } from "react";
import { ScrollArea } from "@/components/ui/scroll-area";
import { getProblemData } from "@/app/actions/getProblem";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
import { updateProblemDetail } from "@/components/creater/problem-maintain";
export default function EditDetailPanel({ problemId }: { problemId: string }) { export default function EditDetailPanel({ problemId }: { problemId: string }) {
const [problemDetails, setProblemDetails] = useState({ const [problemDetails, setProblemDetails] = useState({
@ -77,84 +79,91 @@ export default function EditDetailPanel({ problemId }: { problemId: string }) {
}; };
return ( return (
<Card className="w-full"> <PanelLayout>
<CardHeader> <ScrollArea className="h-full">
<CardTitle></CardTitle> <Card className="w-full rounded-none border-none bg-background">
</CardHeader> <CardHeader className="px-6 py-4">
<CardContent> <div className="flex items-center justify-between">
<div className="space-y-6"> <span></span>
<div className="grid grid-cols-2 gap-4"> <Button type="button" onClick={handleSave}>
<div className="space-y-2">
<Label htmlFor="display-id">ID</Label> </Button>
<Input
id="display-id"
type="number"
value={problemDetails.displayId}
onChange={(e) => handleNumberInputChange(e, "displayId")}
placeholder="输入显示ID"
/>
</div> </div>
<div className="space-y-2"> </CardHeader>
<Label htmlFor="difficulty-select"></Label> <CardContent>
<select <div className="space-y-6">
id="difficulty-select" <div className="grid grid-cols-2 gap-4">
className="block w-full p-2 border border-gray-300 rounded-md dark:bg-gray-800 dark:border-gray-700" <div className="space-y-2">
value={problemDetails.difficulty} <Label htmlFor="display-id">ID</Label>
onChange={handleDifficultyChange} <Input
> id="display-id"
<option value="EASY"></option> type="number"
<option value="MEDIUM"></option> value={problemDetails.displayId}
<option value="HARD"></option> onChange={(e) => handleNumberInputChange(e, "displayId")}
</select> placeholder="输入显示ID"
</div> />
</div> </div>
<div className="space-y-2">
<Label htmlFor="difficulty-select"></Label>
<select
id="difficulty-select"
className="block w-full p-2 border border-gray-300 rounded-md dark:bg-gray-800 dark:border-gray-700"
value={problemDetails.difficulty}
onChange={handleDifficultyChange}
>
<option value="EASY"></option>
<option value="MEDIUM"></option>
<option value="HARD"></option>
</select>
</div>
</div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="time-limit"> (ms)</Label> <Label htmlFor="time-limit"> (ms)</Label>
<Input <Input
id="time-limit" id="time-limit"
type="number" type="number"
value={problemDetails.timeLimit} value={problemDetails.timeLimit}
onChange={(e) => handleNumberInputChange(e, "timeLimit")} onChange={(e) => handleNumberInputChange(e, "timeLimit")}
placeholder="输入时间限制" placeholder="输入时间限制"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="memory-limit"> ()</Label> <Label htmlFor="memory-limit"> ()</Label>
<Input <Input
id="memory-limit" id="memory-limit"
type="number" type="number"
value={problemDetails.memoryLimit} value={problemDetails.memoryLimit}
onChange={(e) => handleNumberInputChange(e, "memoryLimit")} onChange={(e) => handleNumberInputChange(e, "memoryLimit")}
placeholder="输入内存限制" placeholder="输入内存限制"
/> />
</div> </div>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center justify-between">
<input <div className="flex items-center space-x-2">
id="is-published" <input
type="checkbox" id="is-published"
checked={problemDetails.isPublished} type="checkbox"
onChange={(e) => checked={problemDetails.isPublished}
setProblemDetails({ onChange={(e) =>
...problemDetails, setProblemDetails({
isPublished: e.target.checked, ...problemDetails,
}) isPublished: e.target.checked,
} })
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 focus:ring-2" }
/> className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 focus:ring-2"
<Label htmlFor="is-published" className="text-sm font-medium"> />
<Label htmlFor="is-published" className="text-sm font-medium">
</Label>
</div> </Label>
</div>
<Button type="button" onClick={handleSave}> </div>
</div>
</Button> </CardContent>
</div> </Card>
</CardContent> </ScrollArea>
</Card> </PanelLayout>
); );
} }

View File

@ -1,19 +1,21 @@
"use client"; "use client";
import React, { useEffect, useState } from "react"; import { toast } from "sonner";
import { Locale } from "@/generated/client";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { CoreEditor } from "@/components/core-editor";
import MdxPreview from "@/components/mdx-preview"; import MdxPreview from "@/components/mdx-preview";
import { getProblemData } from "@/app/actions/getProblem"; import React, { useEffect, useState } from "react";
import { getProblemLocales } from "@/app/actions/getProblemLocales";
import { Accordion } from "@/components/ui/accordion"; import { Accordion } from "@/components/ui/accordion";
import { CoreEditor } from "@/components/core-editor";
import { ScrollArea } from "@/components/ui/scroll-area";
import { getProblemData } from "@/app/actions/getProblem";
import { VideoEmbed } from "@/components/content/video-embed"; import { VideoEmbed } from "@/components/content/video-embed";
import { toast } from "sonner"; import { getProblemLocales } from "@/app/actions/getProblemLocales";
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
import { updateProblemSolution } from "@/components/creater/problem-maintain"; import { updateProblemSolution } from "@/components/creater/problem-maintain";
import { Locale } from "@/generated/client"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export default function EditSolutionPanel({ export default function EditSolutionPanel({
problemId, problemId,
@ -93,112 +95,116 @@ export default function EditSolutionPanel({
}; };
return ( return (
<Card className="w-full"> <PanelLayout>
<CardHeader> <ScrollArea className="h-full">
<CardTitle></CardTitle> <Card className="w-full rounded-none border-none bg-background">
</CardHeader> <CardHeader>
<CardContent className="space-y-6"> <CardTitle></CardTitle>
{/* 语言切换 */} </CardHeader>
<div className="space-y-2"> <CardContent className="space-y-6">
<Label></Label> {/* 语言切换 */}
<div className="flex space-x-2"> <div className="space-y-2">
<select <Label></Label>
value={currentLocale} <div className="flex space-x-2">
onChange={(e) => setCurrentLocale(e.target.value)} <select
className="border rounded-md px-3 py-2" value={currentLocale}
> onChange={(e) => setCurrentLocale(e.target.value)}
{locales.map((locale) => ( className="border rounded-md px-3 py-2"
<option key={locale} value={locale}> >
{locale} {locales.map((locale) => (
</option> <option key={locale} value={locale}>
))} {locale}
</select> </option>
<Input ))}
placeholder="添加新语言" </select>
value={customLocale} <Input
onChange={(e) => setCustomLocale(e.target.value)} placeholder="添加新语言"
/> value={customLocale}
<Button type="button" onClick={handleAddCustomLocale}> onChange={(e) => setCustomLocale(e.target.value)}
/>
</Button> <Button type="button" onClick={handleAddCustomLocale}>
</div>
</div> </Button>
</div>
</div>
{/* 标题输入 (仅展示) */} {/* 标题输入 (仅展示) */}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="solution-title"></Label> <Label htmlFor="solution-title"></Label>
<Input <Input
id="solution-title" id="solution-title"
value={solution.title} value={solution.title}
onChange={(e) => onChange={(e) =>
setSolution({ ...solution, title: e.target.value }) setSolution({ ...solution, title: e.target.value })
}
placeholder="输入题解标题"
disabled
/>
</div>
{/* 编辑/预览切换 */}
<div className="flex space-x-2">
<Button
type="button"
variant={viewMode === "edit" ? "default" : "outline"}
onClick={() => setViewMode("edit")}
>
</Button>
<Button
type="button"
variant={viewMode === "preview" ? "default" : "outline"}
onClick={() =>
setViewMode(viewMode === "preview" ? "edit" : "preview")
}
>
{viewMode === "preview" ? "取消" : "预览"}
</Button>
<Button
type="button"
variant={viewMode === "compare" ? "default" : "outline"}
onClick={() => setViewMode("compare")}
>
</Button>
</div>
{/* 编辑/预览区域 */}
<div
className={
viewMode === "compare"
? "grid grid-cols-2 gap-6"
: "flex flex-col gap-6"
}
>
{(viewMode === "edit" || viewMode === "compare") && (
<div className="relative h-[600px]">
<CoreEditor
value={solution.content}
onChange={(val) =>
setSolution({ ...solution, content: val || "" })
} }
language="markdown" placeholder="输入题解标题"
className="absolute inset-0 rounded-md border border-input" disabled
/> />
</div> </div>
)}
{viewMode !== "edit" && (
<div className="prose dark:prose-invert">
<MdxPreview
source={solution.content}
components={{ Accordion, VideoEmbed }}
/>
</div>
)}
</div>
<Button type="button" onClick={handleSave}> {/* 编辑/预览切换 */}
<div className="flex space-x-2">
</Button> <Button
</CardContent> type="button"
</Card> variant={viewMode === "edit" ? "default" : "outline"}
onClick={() => setViewMode("edit")}
>
</Button>
<Button
type="button"
variant={viewMode === "preview" ? "default" : "outline"}
onClick={() =>
setViewMode(viewMode === "preview" ? "edit" : "preview")
}
>
{viewMode === "preview" ? "取消" : "预览"}
</Button>
<Button
type="button"
variant={viewMode === "compare" ? "default" : "outline"}
onClick={() => setViewMode("compare")}
>
</Button>
</div>
{/* 编辑/预览区域 */}
<div
className={
viewMode === "compare"
? "grid grid-cols-2 gap-6"
: "flex flex-col gap-6"
}
>
{(viewMode === "edit" || viewMode === "compare") && (
<div className="relative h-[600px]">
<CoreEditor
value={solution.content}
onChange={(val) =>
setSolution({ ...solution, content: val || "" })
}
language="markdown"
className="absolute inset-0 rounded-md border border-input"
/>
</div>
)}
{viewMode !== "edit" && (
<div className="prose dark:prose-invert">
<MdxPreview
source={solution.content}
components={{ Accordion, VideoEmbed }}
/>
</div>
)}
</div>
<Button type="button" onClick={handleSave}>
</Button>
</CardContent>
</Card>
</ScrollArea>
</PanelLayout>
); );
} }

View File

@ -1,18 +1,20 @@
"use client"; "use client";
import React, { useState, useEffect } from "react"; import { toast } from "sonner";
import { generateAITestcase } from "@/app/actions/ai-testcase"; import { Input } from "@/components/ui/input";
import { getProblemData } from "@/app/actions/getProblem"; import { Label } from "@/components/ui/label";
import { import {
addProblemTestcase, addProblemTestcase,
updateProblemTestcase, updateProblemTestcase,
deleteProblemTestcase, deleteProblemTestcase,
} from "@/components/creater/problem-maintain"; } from "@/components/creater/problem-maintain";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import React, { useState, useEffect } from "react";
import { toast } from "sonner"; import { ScrollArea } from "@/components/ui/scroll-area";
import { getProblemData } from "@/app/actions/getProblem";
import { generateAITestcase } from "@/app/actions/ai-testcase";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
interface Testcase { interface Testcase {
id: string; id: string;
@ -182,82 +184,95 @@ export default function EditTestcasePanel({
}; };
return ( return (
<Card className="w-full"> <PanelLayout>
<CardHeader> <ScrollArea className="h-full">
<CardTitle></CardTitle> <Card className="w-full rounded-none border-none bg-background">
<div className="flex items-center space-x-2"> <CardHeader className="px-6 py-4">
<Button onClick={handleAddTestcase}></Button> <div className="flex items-center justify-between">
<Button onClick={handleAITestcase} disabled={isGenerating}> <span></span>
{isGenerating ? "生成中..." : "使用AI生成测试用例"} <div className="flex items-center space-x-2">
</Button> <Button onClick={handleAITestcase} disabled={isGenerating}>
<Button variant="secondary" onClick={handleSaveAll}> {isGenerating ? "生成中..." : "AI生成"}
</Button>
</Button> <Button onClick={handleAddTestcase}></Button>
</div> <Button variant="secondary" onClick={handleSaveAll}>
</CardHeader>
<CardContent className="space-y-6"> </Button>
{testcases.map((tc, idx) => (
<div key={tc.id} className="border p-4 rounded space-y-4">
<div className="flex justify-between items-center">
<h3 className="font-medium"> {idx + 1}</h3>
<Button
variant="destructive"
onClick={() => handleRemoveTestcase(idx)}
>
</Button>
</div>
<div className="space-y-2">
<Label></Label>
<Input
value={tc.expectedOutput}
onChange={(e) =>
handleExpectedOutputChange(idx, e.target.value)
}
placeholder="输入预期输出"
/>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<Label></Label>
<Button onClick={() => handleAddInput(idx)}></Button>
</div> </div>
{tc.inputs.map((inp, iIdx) => (
<div key={iIdx} className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input
value={inp.name}
onChange={(e) =>
handleInputChange(idx, iIdx, "name", e.target.value)
}
placeholder="参数名称"
/>
</div>
<div>
<Label></Label>
<Input
value={inp.value}
onChange={(e) =>
handleInputChange(idx, iIdx, "value", e.target.value)
}
placeholder="参数值"
/>
</div>
{iIdx > 0 && (
<Button
variant="outline"
onClick={() => handleRemoveInput(idx, iIdx)}
>
</Button>
)}
</div>
))}
</div> </div>
</div> </CardHeader>
))} <CardContent className="space-y-6">
</CardContent> {testcases.map((tc, idx) => (
</Card> <div key={tc.id} className="border p-4 rounded space-y-4">
<div className="flex justify-between items-center">
<h3 className="font-medium"> {idx + 1}</h3>
<Button
variant="destructive"
onClick={() => handleRemoveTestcase(idx)}
>
</Button>
</div>
<div className="space-y-2">
<Label></Label>
<Input
value={tc.expectedOutput}
onChange={(e) =>
handleExpectedOutputChange(idx, e.target.value)
}
placeholder="输入预期输出"
/>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<Label></Label>
<Button onClick={() => handleAddInput(idx)}>
</Button>
</div>
{tc.inputs.map((inp, iIdx) => (
<div key={iIdx} className="grid grid-cols-2 gap-4">
<div>
<Label></Label>
<Input
value={inp.name}
onChange={(e) =>
handleInputChange(idx, iIdx, "name", e.target.value)
}
placeholder="参数名称"
/>
</div>
<div>
<Label></Label>
<Input
value={inp.value}
onChange={(e) =>
handleInputChange(
idx,
iIdx,
"value",
e.target.value
)
}
placeholder="参数值"
/>
</div>
{iIdx > 0 && (
<Button
variant="outline"
onClick={() => handleRemoveInput(idx, iIdx)}
>
</Button>
)}
</div>
))}
</div>
</div>
))}
</CardContent>
</Card>
</ScrollArea>
</PanelLayout>
); );
} }

View File

@ -11,9 +11,9 @@ interface ProblemEditViewProps {
export const ProblemEditView = ({ problemId }: ProblemEditViewProps) => { export const ProblemEditView = ({ problemId }: ProblemEditViewProps) => {
const components: Record<string, React.ReactNode> = { const components: Record<string, React.ReactNode> = {
detail: <EditDetailPanel problemId={problemId} />,
description: <EditDescriptionPanel problemId={problemId} />, description: <EditDescriptionPanel problemId={problemId} />,
solution: <EditSolutionPanel problemId={problemId} />, solution: <EditSolutionPanel problemId={problemId} />,
detail: <EditDetailPanel problemId={problemId} />,
code: <EditCodePanel problemId={problemId} />, code: <EditCodePanel problemId={problemId} />,
testcase: <EditTestcasePanel problemId={problemId} />, testcase: <EditTestcasePanel problemId={problemId} />,
}; };

View File

@ -144,6 +144,13 @@ const initialProblemEditFlexLayoutJsonModel: IJsonModel = {
id: "1", id: "1",
weight: 50, weight: 50,
children: [ children: [
{
type: "tab",
id: "detail",
name: "Details",
component: "detail",
enableClose: false,
},
{ {
type: "tab", type: "tab",
id: "description", id: "description",
@ -158,13 +165,6 @@ const initialProblemEditFlexLayoutJsonModel: IJsonModel = {
component: "solution", component: "solution",
enableClose: false, enableClose: false,
}, },
{
type: "tab",
id: "detail",
name: "Details",
component: "detail",
enableClose: false,
},
], ],
}, },
{ {