"use client"
import { type ReactNode } from "react"
import { Checkbox } from "@/shared/components/ui/checkbox"
import { Label } from "@/shared/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/shared/components/ui/radio-group"
import { Textarea } from "@/shared/components/ui/textarea"
import { useTranslations } from "next-intl"
import {
extractAnswerValue,
getOptions,
getQuestionText,
type QuestionOption,
type QuestionType,
} from "../lib/question-content-utils"
import { isRecord } from "@/shared/lib/type-guards"
/**
* 题目渲染模式
* - `take`: 学生作答交互
* - `review`: 学生查看批改结果(只读 + 正确答案高亮)
* - `grade`: 教师批改(只读学生答案 + 正确答案 + 评分面板 slot)
*/
export type QuestionRenderMode = "take" | "review" | "grade"
export interface QuestionRendererProps {
questionId: string
questionType: QuestionType
questionContent: unknown
maxScore: number
index: number
mode: QuestionRenderMode
/** 学生答案(take 模式下为当前值,review/grade 模式下为已提交值) */
value?: unknown
/** take 模式下的禁用状态 */
disabled?: boolean
/** take 模式下答案变更回调 */
onChange?: (answer: unknown) => void
/** review/grade 模式下是否显示正确答案 */
showCorrectAnswer?: boolean
/** review/grade 模式下是否显示批改反馈 */
feedback?: string | null
/** 题目头部右侧额外内容(如分数 Badge) */
headerExtra?: ReactNode
/** 题目底部额外内容(如批改面板) */
footerExtra?: ReactNode
/** 题目卡片额外 className */
className?: string
}
/**
* 题目渲染器(只读展示 + 答案输入组合)
*
* 通过 `mode` 切换交互行为:
* - `take`: 渲染可编辑的作答输入控件
* - `review`: 渲染只读答案 + 正确答案高亮
* - `grade`: 渲染只读学生答案 + 正确答案(由父组件通过 `footerExtra` 注入批改面板)
*/
export function QuestionRenderer({
questionId,
questionType,
questionContent,
maxScore,
index,
mode,
value,
disabled,
onChange,
showCorrectAnswer,
feedback,
headerExtra,
footerExtra,
className,
}: QuestionRendererProps) {
const t = useTranslations("examHomework")
const text = getQuestionText(questionContent)
const options = getOptions(questionContent)
const isReadOnly = mode !== "take"
const showFeedback = isReadOnly && Boolean(feedback)
return (
{text || "—"}
{showFeedback && (
{t("homework.take.teacherFeedback")}
{feedback}
)}
{footerExtra}
)
}
interface QuestionAnswerInputProps {
questionId: string
questionType: QuestionType
options: QuestionOption[]
value: unknown
disabled?: boolean
readOnly?: boolean
onChange?: (answer: unknown) => void
showCorrectAnswer?: boolean
questionContent: unknown
}
function QuestionAnswerInput({
questionId,
questionType,
options,
value,
disabled,
readOnly,
onChange,
showCorrectAnswer,
questionContent,
}: QuestionAnswerInputProps) {
const t = useTranslations("examHomework")
if (questionType === "text") {
if (readOnly) {
const textValue = typeof value === "string" ? value : ""
return (
{textValue || (
{t("homework.review.noAnswer")}
)}
{showCorrectAnswer && (
)}
)
}
return (
)
}
if (questionType === "judgment") {
const boolValue = typeof value === "boolean" ? (value ? "true" : "false") : ""
return (
onChange?.(v === "true")}
disabled={disabled || readOnly}
className="flex flex-col gap-2"
>
{showCorrectAnswer && readOnly && (
)}
)
}
if (questionType === "single_choice") {
const strValue = typeof value === "string" ? value : ""
return (
onChange?.(v)}
disabled={disabled || readOnly}
className="flex flex-col gap-2"
>
{options.map((o) => {
const isCorrectOption = showCorrectAnswer && o.isCorrect === true
return (
)
})}
)
}
if (questionType === "multiple_choice") {
const selectedIds = Array.isArray(value)
? value.filter((x): x is string => typeof x === "string")
: []
return (
{options.map((o) => {
const selected = selectedIds.includes(o.id)
const isCorrectOption = showCorrectAnswer && o.isCorrect === true
return (
{
if (readOnly) return
const isChecked = checked === true
const next = isChecked
? Array.from(new Set([...selectedIds, o.id]))
: selectedIds.filter((x) => x !== o.id)
onChange?.(next)
}}
disabled={disabled || readOnly}
/>
)
})}
)
}
return (
{t("homework.take.unsupportedType")}
)
}
/**
* 正确答案展示(review/grade 模式)
*/
function CorrectAnswerDisplay({
questionType,
questionContent,
}: {
questionType: QuestionType
questionContent: unknown
}) {
const t = useTranslations("examHomework")
if (questionType === "text") {
const correctTexts = (() => {
if (!isRecord(questionContent)) return []
const raw = questionContent.correctAnswer
if (typeof raw === "string") return [raw]
if (Array.isArray(raw)) return raw.filter((x): x is string => typeof x === "string")
return []
})()
if (correctTexts.length === 0) return null
return (
{t("homework.review.correctAnswer")}
{correctTexts.join(" / ")}
)
}
if (questionType === "judgment") {
const correct = (() => {
if (!isRecord(questionContent)) return null
return typeof questionContent.correctAnswer === "boolean"
? questionContent.correctAnswer
: null
})()
if (correct === null) return null
return (
{t("homework.review.correctAnswer")}:
{correct ? t("homework.take.true") : t("homework.take.false")}
)
}
return null
}
/**
* 提取学生答案值(便于父组件格式化展示)
*/
export { extractAnswerValue }