feat:init

This commit is contained in:
zhongyidian committed 2025-12-29 19:19:10 +08:00
1 parent 53d0eeac6b
commit ae0c050010
494 files changed
+17701 -2318

No files matched your search

@@ -0,0 +1,129 @@
import { DragFill, type DragFillResult } from "@/components";
import { cn } from "@/lib/utils";
import type { DragQuizPanelProps } from "./types";
/** 拖拽题面板:使用 DragFill 组件 */
const DragQuizPanel = ({
visible,
zIndex = 10000,
title,
questionPrefix,
questionSuffix,
options,
blanks,
value,
error,
onChange,
}: DragQuizPanelProps) => {
if (!visible) return null;
return (
<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 pt-12 pb-14 gap-10">
{/* 题干卡片 */}
<div className="w-[1600px] min-h-[220px] rounded-[32px] bg-black/90 px-20 py-12 shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9]">
<div className="text-[32px] leading-relaxed text-white">
{title && <div className="mb-4">{title}</div>}
<div>
{questionPrefix && <span>{questionPrefix}</span>}
{blanks.map((blank, index) => {
const filledResult = value.find((r) => r.blankId === blank.id);
const filledValue = filledResult?.optionValue || null;
return (
<span key={blank.id}>
<span
data-blank-id={blank.id}
className={cn(
"inline-flex items-center justify-center min-w-[140px] h-[56px] px-6 mx-3",
"rounded-xl border-2 transition-all duration-300 shadow-[0_4px_16px_rgba(0,0,0,0.2)]",
filledValue
? "border-[#f0be75] bg-gradient-to-br from-[#3a3020] to-[#2a2520] backdrop-blur-sm"
: "border-[#d4c5a9]/60 bg-white/10 backdrop-blur-sm hover:border-[#d4c5a9]"
)}
>
{filledValue ? (
<span className="text-[#f0be75] font-semibold flex items-center gap-3">
<span className="text-[28px]">{filledValue}</span>
<button
type="button"
className="w-7 h-7 flex items-center justify-center rounded-full bg-[#8b4513]/80 hover:bg-[#a0522d] text-white text-sm transition-all hover:scale-110 shadow-lg"
onClick={(e) => {
e.stopPropagation();
onChange(
value.filter((r) => r.blankId !== blank.id)
);
}}
>
×
</button>
</span>
) : (
<span className="text-white/40 text-[24px] font-light">
____
</span>
)}
</span>
{index < blanks.length - 1 && (
<span className="text-white/80 mx-1">、</span>
)}
</span>
);
})}
{questionSuffix && <span>{questionSuffix}</span>}
</div>
</div>
</div>
{/* 使用 DragFill 组件 */}
<div className="w-[1500px] mt-16">
<DragFill
options={options}
blanks={blanks}
value={value}
onChange={onChange}
hideLabels
showSubmitButton={false}
layout="vertical"
className="w-full"
renderOption={(option, isUsed) => (
<div
className={cn(
"relative h-[110px] px-10 flex items-center justify-center",
"rounded-[32px] transition-all duration-300",
"text-[32px] font-bold border-3 shadow-[0_8px_32px_rgba(0,0,0,0.3)]",
isUsed
? "border-[#8b7355] bg-[#3d3329] text-[#a89378] cursor-not-allowed opacity-50"
: "border-[#d4c5a9] bg-gradient-to-br from-[#4a3f35] to-[#3a2f25] text-[#f5e6d3] cursor-move hover:border-[#f0be75] hover:shadow-[0_12px_40px_rgba(240,190,117,0.7)] hover:scale-[1.02]"
)}
>
{!isUsed && (
<div className="absolute inset-0 rounded-[32px] bg-gradient-to-br from-[#d4c5a9]/20 to-transparent pointer-events-none" />
)}
<span className="relative z-10 drop-shadow-[0_2px_4px_rgba(0,0,0,0.3)]">
{option.label}
</span>
</div>
)}
renderBlank={(_blank) => {
// 返回一个隐藏元素,实际的空白区域在题干中
// 但需要通过 data-blank-id 让 DragFill 能找到它
return <div className="hidden" />;
}}
/>
</div>
{error && (
<div className="mt-6 text-center text-[26px] text-[#ff4d4f]">
{error}
</div>
)}
</div>
</div>
);
};
export default DragQuizPanel;
@@ -0,0 +1,238 @@
import { useState } from "react";
import { MultipleChoice } from "@/components";
import { getBackgroundImage } from "@/lib/utils";
import type { MultipleChoiceQuizPanelProps } from "./types";
/**
* 多选题面板组件
*
* 内部管理多选状态,通过提交按钮提交并验证答案
*
* @example
* ```tsx
* <MultipleChoiceQuizPanel
* visible={true}
* title="这是一道多选题"
* options={[
* { value: "A", label: "选项A" },
* { value: "B", label: "选项B" },
* { value: "C", label: "选项C" },
* ]}
* correctAnswer={["A", "B"]}
* errorMessage="很抱歉,你选错了!请你再思考一下吧!"
* minSelections={1}
* maxSelections={2}
* minSelectionsErrorMessage="请至少选择 1 项才能提交"
* onSubmit={(values, isCorrect) => {
* console.log("用户选择:", values, "是否正确:", isCorrect);
* }}
* onError={(error) => {
* console.log("错误信息:", error);
* }}
* />
* ```
*/
const MultipleChoiceQuizPanel = ({
visible,
zIndex = 10000,
title,
options,
correctAnswer,
errorMessage,
minSelectionsErrorMessage,
onSubmit,
onError,
disabled = false,
minSelections,
maxSelections,
submitText = "提交",
}: MultipleChoiceQuizPanelProps) => {
const [selectedValues, setSelectedValues] = useState<string[]>([]);
const [error, setError] = useState<string>("");
if (!visible) return null;
// 处理选项点击
const handleOptionChange = (values: string[]) => {
if (disabled) return;
setSelectedValues(values);
setError("");
};
// 处理提交
const handleSubmit = () => {
if (disabled) return;
// 检查最小选择数量
if (minSelections !== undefined && selectedValues.length < minSelections) {
const errorMsg =
minSelectionsErrorMessage || `请至少选择 ${minSelections} 项`;
setError(errorMsg);
onError?.(errorMsg);
return;
}
// 验证答案
let isCorrect: boolean | undefined = undefined;
if (correctAnswer !== undefined) {
// 比较两个数组是否相等(不考虑顺序)
const correctSet = new Set(correctAnswer);
const selectedSet = new Set(selectedValues);
isCorrect =
correctSet.size === selectedSet.size &&
[...correctSet].every((val) => selectedSet.has(val));
if (isCorrect) {
// 答题成功:清空状态,执行 onSubmit(外部控制关闭窗口)
const submittedValues = [...selectedValues];
setSelectedValues([]);
setError("");
onSubmit?.(submittedValues, true);
} else {
// 答题失败:清空状态,执行 onError,允许重新选择
setSelectedValues([]);
const errorMsg = errorMessage || "很抱歉,你选错了!请你再思考一下吧!";
setError(errorMsg);
onError?.(errorMsg);
}
} else {
// 没有提供正确答案,直接提交
const submittedValues = [...selectedValues];
setSelectedValues([]);
setError("");
onSubmit?.(submittedValues, true);
}
};
return (
<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 py-8">
{/* 题干卡片 */}
<div className="w-[1600px] min-h-[200px] max-h-[280px] rounded-[32px] bg-black px-20 py-12 shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9] flex-shrink-0">
<div className="text-[34px] leading-relaxed text-white">{title}</div>
</div>
{/* 选项区域 - 可滚动 */}
<div className="mt-8 w-[1500px] flex-1 overflow-y-auto overflow-x-hidden px-4 min-h-0 quiz-scrollbar">
<MultipleChoice
options={options}
value={selectedValues}
disabled={disabled}
minSelections={minSelections}
maxSelections={maxSelections}
showSubmitButton={false}
onChange={handleOptionChange}
renderOption={(option, isSelected) => {
const isDisabled =
disabled ||
(!isSelected &&
maxSelections !== undefined &&
selectedValues.length >= maxSelections);
// 处理选项点击
const handleClick = () => {
if (isDisabled) return;
let newValues: string[];
if (isSelected) {
// 取消选择
newValues = selectedValues.filter((v) => v !== option.value);
// 检查最小选择数量限制
if (
minSelections !== undefined &&
newValues.length < minSelections
) {
return;
}
} else {
// 选择
// 检查最大选择数量限制
if (
maxSelections !== undefined &&
selectedValues.length >= maxSelections
) {
return;
}
newValues = [...selectedValues, option.value];
}
handleOptionChange(newValues);
};
return (
<div key={option.value} className="mb-4">
<div
className="w-full max-w-[1431px] min-h-[120px] h-auto py-4 flex items-center px-16 text-[28px] leading-relaxed text-[#fdf5e6] bg-cover bg-center bg-no-repeat transition-all duration-150"
style={{
backgroundImage: getBackgroundImage(
isSelected
? "components/quiz/选中"
: "components/quiz/未选择"
),
filter: isSelected ? undefined : "brightness(0.96)",
opacity: isDisabled ? 0.6 : 1,
cursor: isDisabled ? "not-allowed" : "pointer",
}}
onClick={handleClick}
onMouseEnter={(e) => {
if (isSelected || isDisabled) return;
(
e.currentTarget as HTMLDivElement
).style.backgroundImage =
getBackgroundImage("components/quiz/选中");
(e.currentTarget as HTMLDivElement).style.filter = "";
}}
onMouseLeave={(e) => {
if (isSelected || isDisabled) return;
(
e.currentTarget as HTMLDivElement
).style.backgroundImage = getBackgroundImage(
"components/quiz/未选择"
);
(e.currentTarget as HTMLDivElement).style.filter =
"brightness(0.96)";
}}
>
{option.label}
</div>
</div>
);
}}
/>
</div>
{/* 提交按钮和错误信息 - 固定在底部,不滚动 */}
<div className="w-[1500px] flex-shrink-0 mt-4">
{/* 答案反馈 */}
{error && (
<div className="mb-4 text-center text-[26px] text-[#ff4d4f]">
{error}
</div>
)}
{/* 提交按钮 */}
<div className="flex justify-center">
<button
onClick={handleSubmit}
disabled={
disabled ||
selectedValues.length === 0 ||
(minSelections !== undefined &&
selectedValues.length < minSelections)
}
className="px-30 py-4 text-[28px] font-medium text-white bg-[#b4915a] rounded-[40px] transition-all duration-200 hover:brightness-110 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg"
>
{submitText}
</button>
</div>
</div>
</div>
</div>
);
};
export default MultipleChoiceQuizPanel;
+81
View File
@@ -0,0 +1,81 @@
import { SingleChoice } from "@/components";
import { getBackgroundImage } from "@/lib/utils";
import type { QuizPanelProps } from "./types";
/** 单选题展示面板:题干使用“底板”背景,点击即选,hover 高亮 */
const QuizPanel = ({
visible,
zIndex = 10000,
title,
options,
value,
error,
onChange,
}: QuizPanelProps) => {
if (!visible) return null;
return (
<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-[1600px] min-h-[260px] rounded-[32px] bg-black px-20 py-14 shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9]">
<div className="text-[34px] leading-relaxed text-white">{title}</div>
</div>
{/* 选项区域 */}
<div className="mt-16 w-[1500px]">
<SingleChoice
options={options}
value={value}
renderOption={(option, isSelected) => (
<div
key={option.value}
className="mb-6 cursor-pointer"
onClick={() => onChange?.(option.value)}
>
<div
className="w-[1431px] h-[156px] flex items-center px-16 text-[28px] leading-snug text-[#fdf5e6] bg-cover bg-center bg-no-repeat transition-all duration-150"
style={{
backgroundImage: getBackgroundImage(
isSelected
? "components/quiz/选中"
: "components/quiz/未选择"
),
filter: isSelected ? undefined : "brightness(0.96)",
}}
onMouseEnter={(e) => {
if (isSelected) return;
(e.currentTarget as HTMLDivElement).style.backgroundImage =
getBackgroundImage("components/quiz/选中");
(e.currentTarget as HTMLDivElement).style.filter = "";
}}
onMouseLeave={(e) => {
if (isSelected) return;
(e.currentTarget as HTMLDivElement).style.backgroundImage =
getBackgroundImage("components/quiz/未选择");
(e.currentTarget as HTMLDivElement).style.filter =
"brightness(0.96)";
}}
>
{option.label}
</div>
</div>
)}
/>
{/* 答案反馈 */}
{error && (
<div className="mt-6 text-center text-[26px] text-[#ff4d4f]">
{error}
</div>
)}
</div>
</div>
</div>
);
};
export default QuizPanel;
+211
View File
@@ -0,0 +1,211 @@
# Quiz Panel 组件
用于展示单选题和多选题的面板组件,内部管理选择状态,通过 `onSubmit` 回调通知外部。
## 组件列表
- **SingleChoiceQuizPanel** - 单选题面板
- **MultipleChoiceQuizPanel** - 多选题面板
- **QuizPanel** - 旧版组件(已废弃,保留以保持向后兼容)
## SingleChoiceQuizPanel - 单选题面板
### 功能特性
- ✅ 内部管理选择状态
- ✅ 点击选项后自动提交
- ✅ 支持答案验证(可选)
- ✅ 自动显示错误提示
- ✅ 提交后禁用交互
### 使用方法
```tsx
import { SingleChoiceQuizPanel } from "@/components";
function MyComponent() {
const [showQuiz, setShowQuiz] = useState(false);
return (
<SingleChoiceQuizPanel
visible={showQuiz}
title="这是一道单选题:以下哪个选项是正确的?"
options={[
{ value: "A", label: "选项A" },
{ value: "B", label: "选项B" },
{ value: "C", label: "选项C" },
]}
correctAnswer="B"
errorMessage="再想一想,注意题干中的时间和提示信息哦~"
onSubmit={(value, isCorrect) => {
console.log("用户选择:", value);
console.log("是否正确:", isCorrect);
if (isCorrect) {
// 选择正确,可以关闭面板或进入下一步
setShowQuiz(false);
}
}}
onError={(error) => {
console.log("错误信息:", error);
}}
/>
);
}
```
### API
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| visible | boolean | - | 是否可见(必填) |
| zIndex | number | 10000 | z-index 层级 |
| title | string | - | 题目(必填) |
| options | SingleChoiceOption[] | - | 选项列表(必填) |
| correctAnswer | string | - | 正确答案(可选,用于验证) |
| errorMessage | string | "很抱歉,你选错了!请你再思考一下吧!" | 自定义错误提示信息(答案错误时显示) |
| onSubmit | (value: string, isCorrect: boolean) => void | - | 提交回调 |
| onError | (error: string) => void | - | 错误回调 |
| disabled | boolean | false | 是否禁用 |
### 行为说明
1. **点击选项**:立即触发提交,如果提供了 `correctAnswer`,会自动验证
2. **答案正确**:调用 `onSubmit(value, true)`,面板进入已提交状态
3. **答案错误**:显示错误提示,调用 `onError(error)` 和 `onSubmit(value, false)`
4. **未提供正确答案**:直接调用 `onSubmit(value, true)`,不进行验证
## MultipleChoiceQuizPanel - 多选题面板
### 功能特性
- ✅ 内部管理多选状态
- ✅ 支持最小/最大选择数量限制
- ✅ 通过提交按钮提交
- ✅ 支持答案验证(可选)
- ✅ 自动显示错误提示
- ✅ 提交后禁用交互
### 使用方法
```tsx
import { MultipleChoiceQuizPanel } from "@/components";
function MyComponent() {
const [showQuiz, setShowQuiz] = useState(false);
return (
<MultipleChoiceQuizPanel
visible={showQuiz}
title="这是一道多选题:以下哪些选项是正确的?(可多选)"
options={[
{ value: "A", label: "选项A" },
{ value: "B", label: "选项B" },
{ value: "C", label: "选项C" },
{ value: "D", label: "选项D" },
]}
correctAnswer={["A", "B"]}
errorMessage="很抱歉,你选错了!请你再思考一下吧!"
minSelections={1}
maxSelections={3}
minSelectionsErrorMessage="请至少选择 1 项才能提交"
submitText="确认提交"
onSubmit={(values, isCorrect) => {
console.log("用户选择:", values);
console.log("是否正确:", isCorrect);
if (isCorrect) {
setShowQuiz(false);
}
}}
onError={(error) => {
console.log("错误信息:", error);
}}
/>
);
}
```
### API
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| visible | boolean | - | 是否可见(必填) |
| zIndex | number | 10000 | z-index 层级 |
| title | string | - | 题目(必填) |
| options | MultipleChoiceOption[] | - | 选项列表(必填) |
| correctAnswer | string[] | - | 正确答案数组(可选,用于验证) |
| errorMessage | string | "很抱歉,你选错了!请你再思考一下吧!" | 自定义错误提示信息(答案错误时显示) |
| minSelectionsErrorMessage | string | `请至少选择 ${minSelections} 项` | 自定义最小选择数量不足时的提示信息 |
| onSubmit | (values: string[], isCorrect: boolean) => void | - | 提交回调 |
| onError | (error: string) => void | - | 错误回调 |
| disabled | boolean | false | 是否禁用 |
| minSelections | number | - | 最小选择数量 |
| maxSelections | number | - | 最大选择数量 |
| submitText | string | "提交" | 提交按钮文本 |
### 行为说明
1. **选择选项**:点击选项进行多选,受 `minSelections` 和 `maxSelections` 限制
2. **点击提交**:验证最小选择数量,如果提供了 `correctAnswer`,会验证答案
3. **答案正确**:调用 `onSubmit(values, true)`,面板进入已提交状态
4. **答案错误**:显示错误提示,调用 `onError(error)` 和 `onSubmit(values, false)`
5. **未提供正确答案**:直接调用 `onSubmit(values, true)`,不进行验证
## 迁移指南
### 从旧版 QuizPanel 迁移
**旧版用法:**
```tsx
<QuizPanel
visible={showQuiz}
title="题目"
options={options}
value={quizAnswer}
error={quizError}
onChange={(val) => {
setQuizAnswer(val);
if (val === "B") {
setQuizError("");
} else {
setQuizError("很抱歉,你选错了!");
}
}}
/>
```
**新版用法:**
```tsx
<SingleChoiceQuizPanel
visible={showQuiz}
title="题目"
options={options}
correctAnswer="B"
onSubmit={(value, isCorrect) => {
if (isCorrect) {
// 选择正确
} else {
// 选择错误,错误信息已由组件内部显示
}
}}
/>
```
### 主要变化
1. **不再需要 `onChange`**:组件内部管理选择状态
2. **不再需要 `value` 和 `error`**:组件内部管理
3. **使用 `onSubmit`**:替代 `onChange`,返回用户选择和是否正确
4. **使用 `correctAnswer`**:让组件自动验证答案
5. **使用 `onError`**:可选,用于处理错误信息
## 注意事项
1. 两个组件都使用内部状态管理,不需要外部传入 `value`
2. 提交后组件会进入禁用状态,防止重复提交
3. 如果提供了 `correctAnswer`,组件会自动验证并显示错误信息
4. 多选题的答案比较不考虑顺序,只要选择的选项和正确答案一致即可
5. 旧版 `QuizPanel` 仍然可用,但建议迁移到新组件
@@ -0,0 +1,147 @@
import { useState } from "react";
import { SingleChoice } from "@/components";
import { getBackgroundImage } from "@/lib/utils";
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,
onError,
disabled = false,
}: SingleChoiceQuizPanelProps) => {
const [selectedValue, setSelectedValue] = useState<string>("");
const [error, setError] = useState<string>("");
if (!visible) return null;
// 处理选项点击
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);
} else {
// 答题失败:清空状态,执行 onError,允许重新选择
setSelectedValue("");
const errorMsg = errorMessage || "很抱歉,你选错了!请你再思考一下吧!";
setError(errorMsg);
onError?.(errorMsg);
}
} else {
// 没有提供正确答案,直接提交
setSelectedValue("");
setError("");
onSubmit?.(value, true);
}
};
return (
<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-[1600px] min-h-[260px] rounded-[32px] bg-black px-20 py-14 shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9]">
<div className="text-[34px] leading-relaxed text-white">{title}</div>
</div>
{/* 选项区域 */}
<div className="mt-16 w-[1500px]">
<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-[1431px] h-[156px] flex items-center px-16 text-[28px] leading-snug text-[#fdf5e6] bg-cover bg-center bg-no-repeat transition-all duration-150"
style={{
backgroundImage: getBackgroundImage(
isSelected
? "components/quiz/选中"
: "components/quiz/未选择"
),
filter: isSelected ? undefined : "brightness(0.96)",
opacity: disabled ? 0.6 : 1,
cursor: disabled ? "not-allowed" : "pointer",
}}
onMouseEnter={(e) => {
if (isSelected || disabled) return;
(e.currentTarget as HTMLDivElement).style.backgroundImage =
getBackgroundImage("components/quiz/选中");
(e.currentTarget as HTMLDivElement).style.filter = "";
}}
onMouseLeave={(e) => {
if (isSelected || disabled) return;
(e.currentTarget as HTMLDivElement).style.backgroundImage =
getBackgroundImage("components/quiz/未选择");
(e.currentTarget as HTMLDivElement).style.filter =
"brightness(0.96)";
}}
>
{option.label}
</div>
</div>
)}
/>
{/* 答案反馈 */}
{error && (
<div className="mt-6 text-center text-[26px] text-[#ff4d4f]">
{error}
</div>
)}
</div>
</div>
</div>
);
};
export default SingleChoiceQuizPanel;
+13
View File
@@ -0,0 +1,13 @@
// 新组件
export { default as SingleChoiceQuizPanel } from "./SingleChoiceQuizPanel";
export { default as MultipleChoiceQuizPanel } from "./MultipleChoiceQuizPanel";
export { default as DragQuizPanel } from "./DragQuizPanel";
export type {
SingleChoiceQuizPanelProps,
MultipleChoiceQuizPanelProps,
DragQuizPanelProps,
} from "./types";
// 保留旧组件以保持向后兼容
export { default as QuizPanel } from "./QuizPanal";
export type { QuizPanelProps } from "./types";
+93
View File
@@ -0,0 +1,93 @@
import {
SingleChoiceOption,
MultipleChoiceOption,
DragFillResult,
DragFillBlank,
} from "@/components";
/** 拖拽题面板配置 */
export interface DragQuizPanelProps {
/** 是否可见 */
visible: boolean;
/** z-index 层级 */
zIndex?: number;
/** 题目标题(可选) */
title?: string;
/** 题干前缀文本(空白区域之前的文本) */
questionPrefix?: string;
/** 题干后缀文本(空白区域之后的文本) */
questionSuffix?: string;
/** 选项列表 */
options: { value: string; label: string }[];
/** 空白区域列表 */
blanks: DragFillBlank[];
/** 当前值 */
value: DragFillResult[];
/** 错误提示信息 */
error?: string;
/** 值变化回调 */
onChange: (value: DragFillResult[]) => void;
}
/** 单选题面板配置 */
export interface SingleChoiceQuizPanelProps {
/** 是否可见 */
visible: boolean;
/** z-index 层级 */
zIndex?: number;
/** 题目 */
title: string;
/** 选项列表 */
options: SingleChoiceOption[];
/** 正确答案(可选,用于验证) */
correctAnswer?: string;
/** 自定义错误提示信息(答案错误时显示) */
errorMessage?: string;
/** 提交回调,返回用户选择和是否正确 */
onSubmit?: (value: string, isCorrect: boolean) => void;
/** 错误回调(可选) */
onError?: (error: string) => void;
/** 是否禁用 */
disabled?: boolean;
}
/** 多选题面板配置 */
export interface MultipleChoiceQuizPanelProps {
/** 是否可见 */
visible: boolean;
/** z-index 层级 */
zIndex?: number;
/** 题目 */
title: string;
/** 选项列表 */
options: MultipleChoiceOption[];
/** 正确答案数组(可选,用于验证) */
correctAnswer?: string[];
/** 自定义错误提示信息(答案错误时显示) */
errorMessage?: string;
/** 自定义最小选择数量不足时的提示信息 */
minSelectionsErrorMessage?: string;
/** 提交回调,返回用户选择和是否正确 */
onSubmit?: (values: string[], isCorrect: boolean) => void;
/** 错误回调(可选) */
onError?: (error: string) => void;
/** 是否禁用 */
disabled?: boolean;
/** 最小选择数量 */
minSelections?: number;
/** 最大选择数量 */
maxSelections?: number;
/** 提交按钮文本 */
submitText?: string;
}
/** @deprecated 使用 SingleChoiceQuizPanelProps 替代 */
export interface QuizPanelProps {
visible: boolean;
zIndex?: number;
title: string;
options: SingleChoiceOption[];
value: string;
error?: string;
onChange?: (value: string) => void;
}