"use client"; /** * 题目详情对话框(ARCHITECTURE.md §5.4 / §9.4 / §10 P5) * * 数据契约: * - adminQuestion(id):❌ schema 无 → MSW 兜底(@contract-pending) * * 适配 portal-shell: * - 用原生轻量模态(fixed inset-0 + bg-black/50 + 卡片)替代 shadcn Dialog * - 数据通过 useAdminQuestion hook 拉取 * - 错误处理走 notify.error() * * 关联:ARCHITECTURE.md §5.3 契约纪律 / §9.4 / §11.3 DoD */ import { useEffect } from "react"; import { useTranslations } from "next-intl"; import { BookOpen, Calendar, FileText, Hash, HelpCircle, Lightbulb, RefreshCw, Target, User, } from "lucide-react"; import { useAdminQuestion } from "@/lib/api"; import { notify } from "@/shared/lib/notify"; import { Badge } from "@/shared/components/ui/badge"; import { Button } from "@/shared/components/ui/button"; import { Separator } from "@/shared/components/ui/separator"; import { difficultyToColorClass, formatDifficulty, formatQuestionDate, formatQuestionStatus, formatQuestionType, questionStatusToBadgeClass, } from "@/features/admin/questions/transformations"; export interface QuestionDetailDialogProps { /** 当前选中的题目 id,为空时关闭对话框 */ questionId: string | null; onOpenChange: (open: boolean) => void; } /** * 题目详情对话框。questionId 非空时打开,按 id 拉取详情。 */ export function QuestionDetailDialog({ questionId, onOpenChange, }: QuestionDetailDialogProps): React.ReactElement { const t = useTranslations("admin.questions.detailDialog"); const tCommon = useTranslations("common"); const open = questionId !== null && questionId.length > 0; const { data, loading, error, refetch } = useAdminQuestion(questionId ?? "", { enabled: open, }); // 查询失败时通知用户(§11.3 DoD #8:catch 块必须包含 notify.error()) useEffect(() => { if (error) { notify.error(tCommon("error.loadFailed", { message: String(error) })); } }, [error, tCommon]); // ESC 键关闭 useEffect(() => { if (!open) return; const handleKeyDown = (e: KeyboardEvent): void => { if (e.key === "Escape") onOpenChange(false); }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [open, onOpenChange]); if (!open) return <>; return (
onOpenChange(false)} >
e.stopPropagation()} > {/* 头部 */}

{t("title")}

{data ? (
{formatQuestionType(data.type)} {formatQuestionStatus(data.status)} {data.subjectName ? ( {data.subjectName} ) : null}
) : null}
{/* 内容区 */}
{loading ? (
) : error ? (

{tCommon("error.loadFailed", { message: String(error) })}

) : data ? (
{/* 元信息 */}
} label={t("type")} value={formatQuestionType(data.type)} /> } label={t("difficulty")} value={ {formatDifficulty(data.difficulty)} } /> } label={t("status")} value={ {formatQuestionStatus(data.status)} } /> } label={t("subject")} value={data.subjectName || data.subjectId || "--"} /> } label={t("textbook")} value={data.textbookTitle || data.textbookId || "--"} /> } label={t("knowledgePoint")} value={ data.knowledgePointTitle || data.knowledgePointId || "--" } />
{/* 题目内容 */}

{t("content")}

                    {data.content || t("noContent")}
                  
{/* 正确答案 */} {data.answer ? (

{t("answer")}

                      {data.answer}
                    
) : null} {/* 解析 */} {data.explanation ? (

{t("explanation")}

                      {data.explanation}
                    
) : null} {/* 来源 */} {data.source ? (

{t("source")}

{data.source}

) : null} {/* 元信息 */}
{t("createdBy")}:{data.createdBy || "--"} {t("createdAt")}:{formatQuestionDate(data.createdAt)} {t("updatedAt")}:{formatQuestionDate(data.updatedAt)}
) : (
{t("noData")}
)}
{/* 底部 */}
); } /** 信息项(图标 + 标签 + 值)。 */ function InfoItem({ icon, label, value, }: { icon: React.ReactNode; label: string; value: React.ReactNode; }): React.ReactElement { return (
{icon}

{label}

{value || "--"}

); }