feat(练习): 添加进度缓存和用户体验优化
- 使用 localStorage 缓存答题进度,支持切换页面后继续 - 修复暗色主题下输入框文字不可见问题 - 添加"想不起来"提示按钮,引导用户查看答案 - 添加清除进度按钮,方便重新开始练习 via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
@@ -128,6 +128,7 @@ export function InputArea({
|
||||
'w-10 h-12 text-center text-2xl font-medium',
|
||||
'bg-transparent border-b-2 transition-all duration-200',
|
||||
'focus:outline-none',
|
||||
'text-gray-900 dark:text-gray-100',
|
||||
isComposing && 'border-gray-300 dark:border-gray-600 opacity-70',
|
||||
!isComposing && !char && 'border-gray-400 dark:border-gray-500',
|
||||
!isComposing && isCorrect && 'border-green-500',
|
||||
|
||||
@@ -16,12 +16,32 @@ export default function ProcessPracticePage() {
|
||||
// 生成格子顺序
|
||||
const [cellSequence] = useState<CellInfo[]>(() => generateCellSequence())
|
||||
|
||||
// 从 localStorage 加载答题进度
|
||||
const loadProgress = useCallback(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem('practice-progress')
|
||||
if (saved) {
|
||||
const data = JSON.parse(saved)
|
||||
return {
|
||||
answeredCells: new Map<string, boolean>(data.answeredCells || []),
|
||||
currentCellId: data.currentCellId || cellSequence[0]?.id || null,
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载进度失败:', e)
|
||||
}
|
||||
return {
|
||||
answeredCells: new Map<string, boolean>(),
|
||||
currentCellId: cellSequence[0]?.id || null,
|
||||
}
|
||||
}, [cellSequence])
|
||||
|
||||
// 答题状态
|
||||
const [answeredCells, setAnsweredCells] = useState<Map<string, boolean>>(
|
||||
new Map()
|
||||
() => loadProgress().answeredCells
|
||||
)
|
||||
const [currentCellId, setCurrentCellId] = useState<string | null>(
|
||||
cellSequence[0]?.id || null
|
||||
() => loadProgress().currentCellId
|
||||
)
|
||||
|
||||
// 输入状态
|
||||
@@ -55,6 +75,21 @@ export default function ProcessPracticePage() {
|
||||
latestInputRef.current = userInput
|
||||
}, [userInput])
|
||||
|
||||
// 保存答题进度到 localStorage
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
'practice-progress',
|
||||
JSON.stringify({
|
||||
answeredCells: Array.from(answeredCells.entries()),
|
||||
currentCellId,
|
||||
})
|
||||
)
|
||||
} catch (e) {
|
||||
console.error('保存进度失败:', e)
|
||||
}
|
||||
}, [answeredCells, currentCellId])
|
||||
|
||||
// 切换到指定格子
|
||||
const switchToCell = useCallback(
|
||||
(cell: CellInfo) => {
|
||||
@@ -283,6 +318,15 @@ export default function ProcessPracticePage() {
|
||||
const answeredCount = answeredCells.size
|
||||
const totalCount = cellSequence.length
|
||||
|
||||
// 清除进度
|
||||
const handleClearProgress = useCallback(() => {
|
||||
if (confirm('确定要清除所有答题进度吗?')) {
|
||||
setAnsweredCells(new Map())
|
||||
setCurrentCellId(cellSequence[0]?.id || null)
|
||||
localStorage.removeItem('practice-progress')
|
||||
}
|
||||
}, [cellSequence])
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
{/* 顶部进度条 */}
|
||||
@@ -292,9 +336,17 @@ export default function ProcessPracticePage() {
|
||||
<h1 className="text-xl font-bold text-gray-900 dark:text-gray-100">
|
||||
过程背诵练习
|
||||
</h1>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
进度:{answeredCount} / {totalCount}
|
||||
</div>
|
||||
<button
|
||||
onClick={handleClearProgress}
|
||||
className="text-xs px-2 py-1 text-gray-500 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400 transition-colors"
|
||||
>
|
||||
清除进度
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<motion.div
|
||||
@@ -341,6 +393,16 @@ export default function ProcessPracticePage() {
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
onPaste={handlePaste}
|
||||
/>
|
||||
|
||||
{/* 提示按钮 */}
|
||||
<div className="flex justify-center mt-3">
|
||||
<button
|
||||
onClick={() => currentCellId && handleLongPress(currentCellId)}
|
||||
className="px-4 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
|
||||
>
|
||||
想不起来?长按格子或点击此处查看答案
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 辅助信息区域 */}
|
||||
|
||||
Reference in New Issue
Block a user