feat(student): 完成 student 模块 v4 剩余修复
- P1-4.2: 新增班级详情页 courses/[classId],展示教师/学校/教室信息与课表 - P2-2.5: 今日课表卡片高亮当前/下一节课(useMemo 实时计算) - P2-3.9: 作业作答进度网格支持点击跳转题目(scrollIntoView) - P2-3.10: 作业复习视图显示正确答案(选择/判断/文本题) - P2-4.4: 课程列表支持按班级名/教师/学校搜索 - P2-5.2: 成绩页新增趋势折线图组件 GradeTrendCard - P2-9.2/9.3: 诊断报告新增历史记录卡片与弱点练习入口 - P2-10.2: 选课列表支持搜索与选课模式筛选 - P2-11.3: 修复教材阅读页全屏溢出 - P3-1.5: 面包屑保留首个角色段作为根上下文 - P3-7.3: 课表项支持点击跳转至班级详情页(ScheduleList href)
This commit is contained in:
@@ -15,7 +15,7 @@ import { RadioGroup, RadioGroupItem } from "@/shared/components/ui/radio-group"
|
||||
|
||||
const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null
|
||||
|
||||
type Option = { id: string; text: string }
|
||||
type Option = { id: string; text: string; isCorrect?: boolean }
|
||||
|
||||
const getQuestionText = (content: unknown): string => {
|
||||
if (!isRecord(content)) return ""
|
||||
@@ -32,11 +32,29 @@ const getOptions = (content: unknown): Option[] => {
|
||||
const id = typeof item.id === "string" ? item.id : ""
|
||||
const text = typeof item.text === "string" ? item.text : ""
|
||||
if (!id || !text) continue
|
||||
out.push({ id, text })
|
||||
const isCorrect = item.isCorrect === true
|
||||
out.push({ id, text, isCorrect })
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const getChoiceCorrectIds = (content: unknown): string[] => {
|
||||
return getOptions(content).filter((o) => o.isCorrect).map((o) => o.id)
|
||||
}
|
||||
|
||||
const getJudgmentCorrectAnswer = (content: unknown): boolean | null => {
|
||||
if (!isRecord(content)) return null
|
||||
return typeof content.correctAnswer === "boolean" ? content.correctAnswer : null
|
||||
}
|
||||
|
||||
const getTextCorrectAnswers = (content: unknown): string[] => {
|
||||
if (!isRecord(content)) return []
|
||||
const raw = content.correctAnswer
|
||||
if (typeof raw === "string") return [raw]
|
||||
if (Array.isArray(raw)) return raw.filter((x): x is string => typeof x === "string")
|
||||
return []
|
||||
}
|
||||
|
||||
const toAnswerShape = (questionType: string, v: unknown) => {
|
||||
if (questionType === "text") return { answer: typeof v === "string" ? v : "" }
|
||||
if (questionType === "judgment") return { answer: typeof v === "boolean" ? v : false }
|
||||
@@ -144,6 +162,16 @@ export function HomeworkReviewView({ initialData }: HomeworkReviewViewProps) {
|
||||
<div className="rounded-md border p-3 bg-muted/20 text-sm min-h-[60px]">
|
||||
{typeof value === "string" ? value : <span className="text-muted-foreground italic">No answer provided</span>}
|
||||
</div>
|
||||
{isGraded && (() => {
|
||||
const correctTexts = getTextCorrectAnswers(q.questionContent)
|
||||
if (correctTexts.length === 0) return null
|
||||
return (
|
||||
<div className="rounded-md border border-emerald-200 bg-emerald-50 p-3 text-sm">
|
||||
<div className="font-medium text-emerald-700 mb-1">Correct Answer</div>
|
||||
<div className="text-emerald-900">{correctTexts.join(" / ")}</div>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
) : q.questionType === "judgment" ? (
|
||||
<div className="grid gap-2">
|
||||
@@ -161,6 +189,16 @@ export function HomeworkReviewView({ initialData }: HomeworkReviewViewProps) {
|
||||
<Label htmlFor={`${q.questionId}-false`} className="flex-1 font-normal">False</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
{isGraded && (() => {
|
||||
const correct = getJudgmentCorrectAnswer(q.questionContent)
|
||||
if (correct === null) return null
|
||||
return (
|
||||
<div className="rounded-md border border-emerald-200 bg-emerald-50 p-3 text-sm">
|
||||
<span className="font-medium text-emerald-700">Correct Answer: </span>
|
||||
<span className="text-emerald-900">{correct ? "True" : "False"}</span>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
) : q.questionType === "single_choice" ? (
|
||||
<div className="grid gap-2">
|
||||
@@ -169,14 +207,26 @@ export function HomeworkReviewView({ initialData }: HomeworkReviewViewProps) {
|
||||
disabled
|
||||
className="flex flex-col gap-2"
|
||||
>
|
||||
{options.map((o) => (
|
||||
<div key={o.id} className="flex items-center space-x-2 rounded-md border p-3 bg-muted/20">
|
||||
<RadioGroupItem value={o.id} id={`${q.questionId}-${o.id}`} />
|
||||
<Label htmlFor={`${q.questionId}-${o.id}`} className="flex-1 font-normal">
|
||||
{o.text}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
{options.map((o) => {
|
||||
const correctIds = isGraded ? getChoiceCorrectIds(q.questionContent) : []
|
||||
const isCorrectOption = isGraded && correctIds.includes(o.id)
|
||||
return (
|
||||
<div
|
||||
key={o.id}
|
||||
className={`flex items-center space-x-2 rounded-md border p-3 bg-muted/20 ${
|
||||
isCorrectOption ? "border-emerald-300 bg-emerald-50" : ""
|
||||
}`}
|
||||
>
|
||||
<RadioGroupItem value={o.id} id={`${q.questionId}-${o.id}`} />
|
||||
<Label htmlFor={`${q.questionId}-${o.id}`} className="flex-1 font-normal">
|
||||
{o.text}
|
||||
{isCorrectOption && (
|
||||
<span className="ml-2 text-xs font-medium text-emerald-700">✓ Correct</span>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
) : q.questionType === "multiple_choice" ? (
|
||||
@@ -184,8 +234,15 @@ export function HomeworkReviewView({ initialData }: HomeworkReviewViewProps) {
|
||||
<div className="flex flex-col gap-2">
|
||||
{options.map((o) => {
|
||||
const selected = Array.isArray(value) ? value.includes(o.id) : false
|
||||
const correctIds = isGraded ? getChoiceCorrectIds(q.questionContent) : []
|
||||
const isCorrectOption = isGraded && correctIds.includes(o.id)
|
||||
return (
|
||||
<div key={o.id} className="flex items-start space-x-2 rounded-md border p-3 bg-muted/20">
|
||||
<div
|
||||
key={o.id}
|
||||
className={`flex items-start space-x-2 rounded-md border p-3 bg-muted/20 ${
|
||||
isCorrectOption ? "border-emerald-300 bg-emerald-50" : ""
|
||||
}`}
|
||||
>
|
||||
<Checkbox
|
||||
id={`${q.questionId}-${o.id}`}
|
||||
checked={selected}
|
||||
@@ -193,6 +250,9 @@ export function HomeworkReviewView({ initialData }: HomeworkReviewViewProps) {
|
||||
/>
|
||||
<Label htmlFor={`${q.questionId}-${o.id}`} className="flex-1 font-normal leading-normal">
|
||||
{o.text}
|
||||
{isCorrectOption && (
|
||||
<span className="ml-2 text-xs font-medium text-emerald-700">✓ Correct</span>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user