feat(练习): 新增过程背诵练习模块
- 实现知识领域和过程的背诵练习功能 - 矩阵布局:知识领域格子横跨5列,过程按过程组分列 - 动态输入框:根据答案长度自动调整横线数量 - 实时验证:逐字符验证,错误标红,正确后自动跳转 - 辅助信息:知识领域显示裁剪因素,过程显示主要作用 - 长按显示答案:支持触摸、鼠标和键盘(空格键) - TAB键切换:按顺序切换格子,自动跳过空单元格 - 支持输入法和批量粘贴 - 完整的无障碍支持(aria-live、tabIndex、scrollIntoView) - 进度跟踪:顶部显示答题进度条 新增文件: - src/utils/practice.ts - 工具函数 - src/hooks/useLongPress.ts - 长按 Hook - src/components/practice/ - 练习组件 - src/pages/ProcessPracticePage.tsx - 练习页面 via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run>
This commit is contained in:
84
src/components/practice/HintInfo.tsx
Normal file
84
src/components/practice/HintInfo.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { motion } from 'framer-motion'
|
||||
import type { CellInfo } from '@/utils/practice'
|
||||
import { knowledgeAreaMap, processMap } from '@/data'
|
||||
|
||||
interface HintInfoProps {
|
||||
currentCell: CellInfo | undefined
|
||||
}
|
||||
|
||||
export function HintInfo({ currentCell }: HintInfoProps) {
|
||||
if (!currentCell) return null
|
||||
|
||||
if (currentCell.type === 'knowledge-area') {
|
||||
const ka = knowledgeAreaMap.get(currentCell.knowledgeAreaId)
|
||||
if (!ka?.tailoringFactors || ka.tailoringFactors.length === 0) {
|
||||
return (
|
||||
<motion.div
|
||||
id="hint-info"
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 max-w-3xl mx-auto"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||
敏捷裁剪因素
|
||||
</h3>
|
||||
<p className="text-gray-500 dark:text-gray-400">暂无裁剪因素信息</p>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
id="hint-info"
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 max-w-3xl mx-auto"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
敏捷裁剪因素
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{ka.tailoringFactors.map((factor, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="border-l-4 border-blue-500 pl-4 py-2"
|
||||
>
|
||||
<h4 className="font-medium text-gray-900 dark:text-gray-100 mb-1">
|
||||
{factor.title}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{factor.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
} else {
|
||||
const process = processMap.get(currentCell.processId!)
|
||||
const purpose = process?.purpose
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
id="hint-info"
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 max-w-3xl mx-auto"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
||||
主要作用
|
||||
</h3>
|
||||
{purpose ? (
|
||||
<p className="text-gray-700 dark:text-gray-300 leading-relaxed">
|
||||
{purpose}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-gray-500 dark:text-gray-400">暂无主要作用说明</p>
|
||||
)}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user