Commit non-media application and asset changes while leaving audio, video, and environment files out of version control. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
170 lines
5.2 KiB
TypeScript
170 lines
5.2 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { SingleChoice } from '@/components'
|
|
import SuccessAlert from '@/components/layout/alert/success-alert'
|
|
import type { SingleChoiceQuizPanelProps } from './types'
|
|
|
|
/**
|
|
* 单选题面板组件
|
|
*
|
|
* 内部管理选择状态,点击选项后自动提交并验证答案
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <SingleChoiceQuizPanel
|
|
* visible={true}
|
|
* title="这是一道单选题"
|
|
* options={[
|
|
* { value: "A", label: "选项A" },
|
|
* { value: "B", label: "选项B" },
|
|
* ]}
|
|
* correctAnswer="B"
|
|
* errorMessage="再想一想,注意题干中的时间和提示信息哦~"
|
|
* onSubmit={(value, isCorrect) => {
|
|
* console.log("用户选择:", value, "是否正确:", isCorrect);
|
|
* }}
|
|
* onError={(error) => {
|
|
* console.log("错误信息:", error);
|
|
* }}
|
|
* />
|
|
* ```
|
|
*/
|
|
const SingleChoiceQuizPanel = ({
|
|
visible,
|
|
zIndex = 10000,
|
|
title,
|
|
options,
|
|
correctAnswer,
|
|
errorMessage,
|
|
onSubmit,
|
|
onWrongSelect,
|
|
onError,
|
|
disabled = false,
|
|
showSuccessAlert = false,
|
|
successAlertProps,
|
|
onSuccessContinue,
|
|
}: SingleChoiceQuizPanelProps) => {
|
|
const [selectedValue, setSelectedValue] = useState<string>('')
|
|
const [error, setError] = useState<string>('')
|
|
const [successAlertVisible, setSuccessAlertVisible] = useState<boolean>(false)
|
|
|
|
// 当窗口关闭时,重置内部状态
|
|
useEffect(() => {
|
|
if (!visible) {
|
|
setSelectedValue('')
|
|
setError('')
|
|
setSuccessAlertVisible(false)
|
|
}
|
|
}, [visible])
|
|
|
|
// 当成功弹窗显示时,隐藏题目面板,避免双层遮罩叠加
|
|
const shouldRenderQuizPanel = visible && !successAlertVisible
|
|
|
|
if (!visible) return null
|
|
|
|
// 约定:显式开启,或传入 title/content 任一文案,即启用答对成功弹窗
|
|
const hasSuccessAlertCopy =
|
|
Boolean(successAlertProps?.title) || Boolean(successAlertProps?.content)
|
|
const shouldShowSuccessAlert = showSuccessAlert || hasSuccessAlertCopy
|
|
|
|
const handleSuccessContinue = () => {
|
|
setSuccessAlertVisible(false)
|
|
onSuccessContinue?.()
|
|
}
|
|
|
|
// 处理选项点击
|
|
const handleOptionClick = (value: string) => {
|
|
if (disabled) return
|
|
|
|
setSelectedValue(value)
|
|
setError('')
|
|
|
|
// 验证答案
|
|
const isCorrect = correctAnswer !== undefined ? value === correctAnswer : undefined
|
|
|
|
// 如果提供了正确答案,进行验证
|
|
if (correctAnswer !== undefined) {
|
|
if (isCorrect) {
|
|
// 答题成功:清空状态,执行 onSubmit(外部控制关闭窗口)
|
|
setSelectedValue('')
|
|
setError('')
|
|
onSubmit?.(value, true)
|
|
|
|
if (shouldShowSuccessAlert) {
|
|
setSuccessAlertVisible(true)
|
|
}
|
|
} else {
|
|
// 答题失败:清空状态,执行 onError,允许重新选择
|
|
setSelectedValue('')
|
|
const customError = onWrongSelect?.(value)
|
|
const errorMsg = customError || errorMessage || '很抱歉,你选错了!请你再思考一下吧!'
|
|
setError(errorMsg)
|
|
onError?.(errorMsg)
|
|
}
|
|
} else {
|
|
// 没有提供正确答案,直接提交
|
|
setSelectedValue('')
|
|
setError('')
|
|
onSubmit?.(value, true)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{shouldRenderQuizPanel && (
|
|
<div
|
|
className="fixed inset-0 flex items-center justify-center bg-black/40"
|
|
style={{ zIndex }}
|
|
>
|
|
<div className="relative w-[1728px] h-[972px] flex flex-col items-center justify-center">
|
|
{/* 题干卡片 */}
|
|
<div className="w-[1700px] rounded-[32px] bg-black px-20 py-8 shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9]">
|
|
<div className="text-[34px] leading-relaxed text-white">{title}</div>
|
|
|
|
{/* 答案反馈 */}
|
|
{error && <div className="mt-5 text-center text-[26px] text-[#fcbe53]">{error}</div>}
|
|
</div>
|
|
|
|
{/* 选项区域 */}
|
|
<div className="mt-16">
|
|
<SingleChoice
|
|
options={options}
|
|
value={selectedValue}
|
|
disabled={disabled}
|
|
renderOption={(option, isSelected) => (
|
|
<div
|
|
key={option.value}
|
|
className="mb-6 cursor-pointer"
|
|
onClick={() => handleOptionClick(option.value)}
|
|
>
|
|
<div
|
|
className={`w-[1500px] flex items-center p-4 text-[28px] leading-relaxed rounded-tr-[50px] rounded-bl-4xl transition-all duration-150 ${
|
|
disabled ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer'
|
|
} ${
|
|
isSelected
|
|
? 'bg-[#d4a574] text-[#5c3e2e]'
|
|
: 'bg-[#d7b888] text-[#5c3e2e] hover:bg-linear-to-r from-[#ddf3e1] to-[#fbdd97]'
|
|
}`}
|
|
>
|
|
{option.label}
|
|
</div>
|
|
</div>
|
|
)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{shouldShowSuccessAlert && (
|
|
<SuccessAlert
|
|
{...successAlertProps}
|
|
visible={successAlertVisible}
|
|
onClose={handleSuccessContinue}
|
|
/>
|
|
)}
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default SingleChoiceQuizPanel
|