import { useRef, useState } from "react" import { z } from "zod/v4" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Textarea } from "@/components/ui/textarea" import { Badge } from "@/components/ui/badge" import { importQuestions } from "@/lib/api/question-api" import type { QuestionFormData } from "@/types/question" const importItemSchema = z.object({ stem: z.string().min(1, "题干不能为空"), correctAnswer: z.string().min(1, "正确答案不能为空"), distractors: z.array(z.string().min(1)).min(4, "至少 4 个干扰项").max(6), categoryId: z.string().min(1, "请选择分类"), difficulty: z.number().min(1).max(5), status: z.enum(["draft", "reviewing", "published", "archived"]).optional(), knowledgeCardBasic: z.string().min(1, "请填写基础知识卡").max(100).optional(), knowledgeCardDeep: z.string().max(300).optional(), sourceRef: z.string().max(500).optional(), }) const importArraySchema = z.array(importItemSchema).min(1, "至少导入 1 道题目") type ImportStep = "input" | "preview" | "result" interface ImportQuestionsDialogProps { open: boolean onOpenChange: (open: boolean) => void onSuccess: () => void } export function ImportQuestionsDialog({ open, onOpenChange, onSuccess, }: ImportQuestionsDialogProps) { const fileInputRef = useRef(null) const [step, setStep] = useState("input") const [rawJson, setRawJson] = useState("") const [parseError, setParseError] = useState(null) const [parsedQuestions, setParsedQuestions] = useState([]) const [importing, setImporting] = useState(false) const [result, setResult] = useState<{ imported: number failed: number errors?: { index: number; error: string }[] } | null>(null) function reset() { setStep("input") setRawJson("") setParseError(null) setParsedQuestions([]) setImporting(false) setResult(null) } function handleClose(open: boolean) { if (!open) reset() onOpenChange(open) } function handleFileUpload(e: React.ChangeEvent) { const file = e.target.files?.[0] if (!file) return const reader = new FileReader() reader.onload = (event) => { const text = event.target?.result as string setRawJson(text) setParseError(null) } reader.readAsText(file) e.target.value = "" } function handleParse() { setParseError(null) let data: unknown try { data = JSON.parse(rawJson) } catch { setParseError("JSON 格式无效,请检查语法") return } const parsed = importArraySchema.safeParse(data) if (!parsed.success) { const firstError = parsed.error.issues[0] const path = firstError.path.join(".") setParseError(`第 ${path || "?"} 项: ${firstError.message}`) return } const questions: QuestionFormData[] = parsed.data.map((item) => ({ stem: item.stem, correctAnswer: item.correctAnswer, distractors: item.distractors, categoryId: item.categoryId, difficulty: item.difficulty as QuestionFormData["difficulty"], status: item.status ?? "draft", knowledgeCardBasic: item.knowledgeCardBasic ?? "", knowledgeCardDeep: item.knowledgeCardDeep, sourceRef: item.sourceRef, })) setParsedQuestions(questions) setStep("preview") } async function handleImport() { setImporting(true) try { const res = await importQuestions(parsedQuestions) setResult(res.data) setStep("result") if (res.data.imported > 0) onSuccess() } catch { setParseError("导入失败,请检查网络或联系管理员") } finally { setImporting(false) } } return ( 批量导入题目 {step === "input" && "上传 JSON 文件或粘贴 JSON 内容"} {step === "preview" && `已解析 ${parsedQuestions.length} 道题目,确认导入?`} {step === "result" && "导入完成"} {step === "input" && (
或直接粘贴 JSON