From fdc2097f67d75bef0f5c7efed8e67b5892a54294 Mon Sep 17 00:00:00 2001 From: ittoview Date: Mon, 11 May 2026 09:42:19 +0100 Subject: [PATCH] feat: add stacked practice cards --- src/pages/ProcessPurposePracticePage.tsx | 145 +++++++++++++++++------ 1 file changed, 112 insertions(+), 33 deletions(-) diff --git a/src/pages/ProcessPurposePracticePage.tsx b/src/pages/ProcessPurposePracticePage.tsx index 14ed1d3..43b5c11 100644 --- a/src/pages/ProcessPurposePracticePage.tsx +++ b/src/pages/ProcessPurposePracticePage.tsx @@ -92,6 +92,7 @@ export default function ProcessPurposePracticePage() { const isComposingRef = useRef(false) const latestInputRef = useRef([]) const answerTimerRef = useRef(null) + const deckScrollRef = useRef(null) const { practiceMode } = useAppStore() const itemMap = useMemo( @@ -102,6 +103,11 @@ export default function ProcessPurposePracticePage() { const totalCount = progress.order.length const completedCount = progress.completedIds.length const isFinished = completedCount >= totalCount + const visibleQuestionCards = progress.order + .slice(0, progress.currentIndex + 1) + .map((id, index) => ({ item: itemMap.get(id), index })) + .filter((card): card is { item: NonNullable; index: number } => Boolean(card.item)) + const nextPreviewItem = itemMap.get(progress.order[progress.currentIndex + 1]) const focusFirstEmptyInput = useCallback(() => { setTimeout(() => { @@ -213,6 +219,15 @@ export default function ProcessPurposePracticePage() { } }, []) + useEffect(() => { + const deck = deckScrollRef.current + if (!deck) return + + requestAnimationFrame(() => { + deck.scrollTo({ top: deck.scrollHeight, behavior: 'smooth' }) + }) + }, [progress.currentIndex]) + const moveToNextQuestion = useCallback(() => { setShowAnswer(false) setCorrectFeedback(false) @@ -413,8 +428,8 @@ export default function ProcessPurposePracticePage() { if (!currentItem) return null return ( -
-
+
+
@@ -449,40 +464,104 @@ export default function ProcessPurposePracticePage() {
-
-
+
-
- - 第 {Math.min(progress.currentIndex + 1, totalCount)} / {totalCount} 题 - - - {showAnswer ? ( - - {currentItem.name} - - ) : null} - -
-

- {currentItem.purpose} -

-
+ + {visibleQuestionCards.map(({ item, index }) => { + const isCurrent = index === progress.currentIndex + const isAnsweredHistory = index < progress.currentIndex + return ( + +
+ + 第 {index + 1} / {totalCount} 题 + + + + {isCurrent && showAnswer ? ( + + {item.name} + + ) : isAnsweredHistory ? ( + + {item.name} + + ) : null} + +
+ +

+ {item.purpose} +

+
+ ) + })} +
+
+ + {!isFinished && nextPreviewItem && ( + +
+ 第 {progress.currentIndex + 2} / {totalCount} 题 +
+

+ {nextPreviewItem.purpose} +

+
+ )} {isFinished && ( -
+
已完成本轮练习
@@ -501,7 +580,7 @@ export default function ProcessPurposePracticePage() { {!isFinished && ( -
+