feat: show domain option progress

This commit is contained in:
ittoview
2026-05-13 19:08:55 +01:00
parent 6b180576b4
commit 77183cb340

View File

@@ -208,6 +208,7 @@ export default function PerformanceDomainPracticePage() {
const accuracyBase = progress.correctCount + progress.wrongCount
const accuracy = accuracyBase > 0 ? Math.round((progress.correctCount / accuracyBase) * 100) : 0
const progressPercent = progress.totalCount > 0 ? (completedCount / progress.totalCount) * 100 : 0
const completedIdSet = useMemo(() => new Set(progress.completedIds), [progress.completedIds])
useEffect(() => {
try {
@@ -311,31 +312,54 @@ export default function PerformanceDomainPracticePage() {
const isCorrectSelected = isAnswerShown && isSelected && answerState?.isCorrect
const isWrongSelected = isAnswerShown && isSelected && !answerState?.isCorrect
const shouldHighlightCorrect = isAnswerShown && isCorrectDomain
const getKindProgress = (kind: QuestionKind) => {
const ids = questionBank
.filter((question) => question.domainId === domain.id && question.kind === kind)
.map((question) => question.id)
const completed = ids.filter((id) => completedIdSet.has(id)).length
return {
completed,
total: ids.length,
percent: ids.length > 0 ? (completed / ids.length) * 100 : 0,
}
}
const expectedGoalProgress = getKindProgress('expectedGoal')
const keyPointProgress = getKindProgress('keyPoint')
const currentKindProgress = currentQuestion?.kind === 'expectedGoal'
? expectedGoalProgress
: keyPointProgress
const currentKindDone = currentKindProgress.total > 0 && currentKindProgress.completed >= currentKindProgress.total
const optionDisabled = isAnswerShown || currentKindDone
return (
<motion.button
key={domain.id}
type="button"
whileTap={!isAnswerShown ? { scale: 0.98 } : undefined}
whileTap={!optionDisabled ? { scale: 0.98 } : undefined}
onClick={() => handleSelect(domain.id)}
disabled={isAnswerShown}
disabled={optionDisabled}
className={clsx(
'relative flex min-h-[84px] items-center gap-3 rounded-xl border bg-white p-3 text-left shadow-sm transition-colors dark:bg-gray-800',
'relative flex min-h-[128px] flex-col gap-3 rounded-xl border bg-white p-3 text-left shadow-sm transition-colors dark:bg-gray-800',
'border-gray-100 hover:border-indigo-200 hover:bg-indigo-50/40 dark:border-gray-700 dark:hover:border-indigo-700 dark:hover:bg-indigo-950/20',
isAnswerShown && 'cursor-default hover:bg-white dark:hover:bg-gray-800',
optionDisabled && 'cursor-default hover:bg-white dark:hover:bg-gray-800',
currentKindDone && !isAnswerShown && 'bg-gray-50 opacity-55 dark:bg-gray-800/70',
shouldHighlightCorrect &&
'border-emerald-400 bg-emerald-50 shadow-[inset_0_0_0_2px_rgba(52,211,153,0.65)] dark:border-emerald-500 dark:bg-emerald-950/30 dark:shadow-[inset_0_0_0_2px_rgba(16,185,129,0.45)]',
isWrongSelected &&
'border-rose-300 bg-rose-50 shadow-[inset_0_0_0_2px_rgba(251,113,133,0.65)] dark:border-rose-600 dark:bg-rose-950/30 dark:shadow-[inset_0_0_0_2px_rgba(244,63,94,0.45)]'
)}
>
<div className="flex items-center gap-3">
<div
className="flex h-11 w-11 shrink-0 items-center justify-center rounded-lg text-white"
className={clsx(
'flex h-11 w-11 shrink-0 items-center justify-center rounded-lg text-white',
currentKindDone && !isAnswerShown && 'grayscale'
)}
style={{ backgroundColor: domain.color }}
>
<Icon size={20} />
</div>
<div className="min-w-0 flex-1">
<div className="min-w-0 flex-1 pr-8">
<div className="text-sm font-semibold text-gray-900 dark:text-white">
{domain.name}
</div>
@@ -343,6 +367,33 @@ export default function PerformanceDomainPracticePage() {
{domain.nameEn}
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
{[
{ label: '目标', data: expectedGoalProgress },
{ label: '要点', data: keyPointProgress },
].map((item) => {
const isFull = item.data.total > 0 && item.data.completed >= item.data.total
return (
<div key={item.label} className="min-w-0">
<div className="mb-1 flex items-center justify-between gap-1 text-[11px] text-gray-500 dark:text-gray-400">
<span>{item.label}</span>
<span>{item.data.completed}/{item.data.total}</span>
</div>
<div className="h-1.5 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-700">
<div
className={clsx(
'h-full rounded-full transition-all duration-300',
isFull ? 'bg-gray-300 dark:bg-gray-500' : 'bg-indigo-500'
)}
style={{ width: `${item.data.percent}%` }}
/>
</div>
</div>
)
})}
</div>
{isCorrectSelected && (
<CheckCircle2 className="absolute right-3 top-3 text-emerald-500" size={18} />