feat:讨论模式页面框架搭建
This commit is contained in:
parent
8de56bd07c
commit
b20cc50f44
@ -16,7 +16,7 @@ import type {
|
||||
UpdateQuestionAnswerRequest,
|
||||
CreateQuestionRepoRequest,
|
||||
UpdateQuestionRepoRequest,
|
||||
ExamInfo,
|
||||
ExamInfo
|
||||
} from '../types'
|
||||
|
||||
/**
|
||||
|
@ -890,3 +890,65 @@ export interface PaperInfo {
|
||||
updateBy: string
|
||||
updateTime: string
|
||||
}
|
||||
|
||||
// 试卷创建请求类型
|
||||
export interface CreatePaperRequest {
|
||||
/**
|
||||
* 0固定组卷,1随机组卷,组卷模式
|
||||
*/
|
||||
generateMode?: number;
|
||||
/**
|
||||
* 及格分
|
||||
*/
|
||||
passScore?: number;
|
||||
/**
|
||||
* 题库id
|
||||
*/
|
||||
repoId?: string;
|
||||
/**
|
||||
* 是否需要批阅:0不需要 1需要,是否需要批阅
|
||||
*/
|
||||
requireReview?: number;
|
||||
/**
|
||||
* 组卷规则
|
||||
* 示例: {
|
||||
* "type0_count": 1, "type0_score": 10,
|
||||
* "type1_count": 20, "type1_score": 20,
|
||||
* "type2_count": 10, "type2_score": 10,
|
||||
* "type3_count": 10, "type3_score": 10,
|
||||
* "type4_count": 5, "type4_score": 5,
|
||||
* "type5_count": 1, "type5_score": 1
|
||||
* }
|
||||
*/
|
||||
rules?: string;
|
||||
/**
|
||||
* 试卷标题
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* 总分
|
||||
*/
|
||||
totalScore?: number;
|
||||
[property: string]: any;
|
||||
}
|
||||
|
||||
// 试卷题目关联请求类型
|
||||
export interface AddPaperQuestionRequest {
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
orderNo?: number;
|
||||
/**
|
||||
* 试卷id
|
||||
*/
|
||||
paperId?: string;
|
||||
/**
|
||||
* 题目id
|
||||
*/
|
||||
questionId?: string;
|
||||
/**
|
||||
* 分值
|
||||
*/
|
||||
score?: number;
|
||||
[property: string]: any;
|
||||
}
|
||||
|
@ -30,20 +30,22 @@
|
||||
:class="{ 'is-selected': correctAnswer === index }">
|
||||
<div class="option-content">
|
||||
<div class="option-left">
|
||||
<n-radio :checked="correctAnswer === index"
|
||||
@update:checked="() => handleSetCorrectAnswer(index)" name="correct-answer"
|
||||
class="correct-radio" />
|
||||
<input type="radio"
|
||||
name="correct-answer"
|
||||
:checked="correctAnswer === index"
|
||||
@change="() => handleSetCorrectAnswer((index + 1).toString())"
|
||||
class="correct-radio-input" />
|
||||
<span class="option-label">{{ String.fromCharCode(65 + index) }}</span>
|
||||
</div>
|
||||
<n-input :value="option.content"
|
||||
@update:value="(value: string) => handleUpdateOption(index, value)" placeholder="请输入内容"
|
||||
show-count maxlength="200" class="option-input" />
|
||||
<n-button v-if="modelValue.length > 2" @click="handleRemoveOption(index)" type="error" ghost
|
||||
size="small" class="delete-option-btn">
|
||||
删除
|
||||
</n-button>
|
||||
<n-input :value="option.content"
|
||||
@update:value="(value: string) => handleUpdateOption(index, value)" placeholder="请输入内容"
|
||||
show-count maxlength="200" class="option-input" />
|
||||
<n-button v-if="modelValue.length > 2" @click="handleRemoveOption(index)" type="error" ghost
|
||||
size="small" class="delete-option-btn">
|
||||
删除
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<n-button v-if="modelValue.length < 6" @click="handleAddOption" dashed block class="add-option-btn">
|
||||
+ 添加选项
|
||||
</n-button>
|
||||
@ -128,8 +130,25 @@ const handleUpdateOption = (index: number, content: string) => {
|
||||
};
|
||||
|
||||
// 设置正确答案
|
||||
const handleSetCorrectAnswer = (index: number) => {
|
||||
emit('update:correctAnswer', index);
|
||||
const handleSetCorrectAnswer = (value: string | null) => {
|
||||
console.log('🔍 设置正确答案,偏移值:', value, '当前正确答案:', props.correctAnswer);
|
||||
|
||||
try {
|
||||
// 将偏移后的字符串转换为实际的索引(减1)
|
||||
const actualIndex = value !== null ? parseInt(value, 10) - 1 : null;
|
||||
console.log('🔍 实际索引:', actualIndex);
|
||||
|
||||
if (actualIndex !== null && !isNaN(actualIndex) && actualIndex >= 0) {
|
||||
console.log('🔍 当前选项内容:', props.modelValue[actualIndex]?.content);
|
||||
console.log('🔍 所有选项:', props.modelValue.map((opt, i) => `${String.fromCharCode(65 + i)}: ${opt.content}`));
|
||||
}
|
||||
|
||||
// 发送实际索引给父组件
|
||||
emit('update:correctAnswer', actualIndex);
|
||||
} catch (error) {
|
||||
console.error('🚨 设置正确答案时出错:', error);
|
||||
emit('update:correctAnswer', null);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -236,4 +255,5 @@ const handleSetCorrectAnswer = (index: number) => {
|
||||
.correct-radio {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -139,7 +139,10 @@
|
||||
<!-- 单选题 -->
|
||||
<SingleChoiceQuestion v-if="subQuestion.type === 'single_choice'"
|
||||
v-model="subQuestion.options!" :correctAnswer="subQuestion.correctAnswer || null"
|
||||
@update:correctAnswer="(val: number | null) => subQuestion.correctAnswer = val"
|
||||
@update:correctAnswer="(val: number | null) => {
|
||||
console.log('🎯 AddExam: 更新正确答案', val, '选项内容:', subQuestion.options?.[val as number]?.content);
|
||||
subQuestion.correctAnswer = val;
|
||||
}"
|
||||
v-model:title="subQuestion.title" v-model:explanation="subQuestion.explanation" />
|
||||
|
||||
|
||||
@ -1330,8 +1333,8 @@ const saveExam = async () => {
|
||||
const apiData = {
|
||||
title: examForm.title,
|
||||
generateMode: examForm.type === 1 ? 0 : 1, // 0: 固定试卷组, 1: 随机抽题组卷
|
||||
rules: '', // 组卷规则,暂时为空
|
||||
repoId: '', // 题库ID,暂时为空
|
||||
rules: examForm.type === 1 ? '' : generateRandomRules(), // 固定组卷不需要规则,随机组卷需要规则
|
||||
repoId: examForm.type === 1 ? '' : '', // 固定组卷不需要题库ID,随机组卷需要题库ID
|
||||
totalScore: examForm.totalScore,
|
||||
passScore: examForm.passScore || Math.floor(examForm.totalScore * 0.6), // 及格分
|
||||
requireReview: examForm.useAIGrading ? 1 : 0 // 是否需要批阅
|
||||
@ -1453,6 +1456,36 @@ const convertNumberToQuestionType = (type: number): QuestionType => {
|
||||
return typeMap[type] || QuestionType.SINGLE_CHOICE;
|
||||
};
|
||||
|
||||
// 将前端难度转换为API需要的数字类型
|
||||
const getDifficultyNumber = (difficulty: string): number => {
|
||||
const difficultyMap: Record<string, number> = {
|
||||
'easy': 1, // 简单
|
||||
'medium': 2, // 中等
|
||||
'hard': 3 // 困难
|
||||
};
|
||||
return difficultyMap[difficulty] || 1;
|
||||
};
|
||||
|
||||
// 生成随机组卷规则(暂时返回空字符串,因为我们先实现固定组卷)
|
||||
const generateRandomRules = (): string => {
|
||||
// 随机组卷规则示例:
|
||||
// {
|
||||
// "type0_count": 1, // 单选题数量
|
||||
// "type0_score": 10, // 单选题分数
|
||||
// "type1_count": 20, // 多选题数量
|
||||
// "type1_score": 20, // 多选题分数
|
||||
// "type2_count": 10, // 判断题数量
|
||||
// "type2_score": 10, // 判断题分数
|
||||
// "type3_count": 10, // 填空题数量
|
||||
// "type3_score": 10, // 填空题分数
|
||||
// "type4_count": 5, // 简答题数量
|
||||
// "type4_score": 5, // 简答题分数
|
||||
// "type5_count": 1, // 复合题数量
|
||||
// "type5_score": 1 // 复合题分数
|
||||
// }
|
||||
return '';
|
||||
};
|
||||
|
||||
// 保存试卷题目
|
||||
const saveExamQuestions = async (paperId: string) => {
|
||||
try {
|
||||
@ -1484,12 +1517,12 @@ const saveExamQuestions = async (paperId: string) => {
|
||||
try {
|
||||
// 创建题目
|
||||
const questionData = {
|
||||
repoId: '1958492351656955905', // 使用从题库模态框获取的题库ID
|
||||
repoId: '', // 固定组卷模式下不需要题库ID
|
||||
parentId: '', // 父题目ID,暂时为空
|
||||
type: convertQuestionTypeToNumber(subQuestion.type), // 题目类型
|
||||
content: subQuestion.title || '', // 题干
|
||||
analysis: '', // 题目解析,暂时为空
|
||||
difficulty: 1, // 难度,默认1
|
||||
analysis: subQuestion.explanation || '', // 题目解析
|
||||
difficulty: getDifficultyNumber(subQuestion.difficulty), // 难度
|
||||
score: subQuestion.score || 0, // 分值
|
||||
degree: 1, // 程度,默认1
|
||||
ability: 1 // 能力,默认1
|
||||
@ -1512,39 +1545,182 @@ const saveExamQuestions = async (paperId: string) => {
|
||||
console.log('✅ 题目创建成功,ID:', questionId);
|
||||
console.log('📊 题目响应详情:', questionResponse);
|
||||
|
||||
// 创建题目选项(如果是选择题)
|
||||
if (subQuestion.options && subQuestion.options.length > 0) {
|
||||
console.log('📊 题目选项数据:', subQuestion.options);
|
||||
for (let i = 0; i < subQuestion.options.length; i++) {
|
||||
const option = subQuestion.options[i];
|
||||
console.log('📊 处理选项:', option);
|
||||
// 根据题目类型处理选项和答案
|
||||
const questionType = convertQuestionTypeToNumber(subQuestion.type);
|
||||
|
||||
const optionData = {
|
||||
if (questionType === 0 || questionType === 1 || questionType === 2) {
|
||||
// 单选题、多选题、判断题 - 创建选项
|
||||
console.log('📊 处理选择题选项,题目类型:', questionType);
|
||||
|
||||
if (questionType === 0) {
|
||||
// 单选题
|
||||
if (subQuestion.options && subQuestion.options.length > 0) {
|
||||
console.log('📊 单选题选项数据:', subQuestion.options);
|
||||
for (let i = 0; i < subQuestion.options.length; i++) {
|
||||
const option = subQuestion.options[i];
|
||||
const optionData = {
|
||||
questionId: questionId,
|
||||
content: option.content || '',
|
||||
izCorrent: subQuestion.correctAnswer === i ? 1 : 0,
|
||||
orderNo: i
|
||||
};
|
||||
console.log('📝 创建单选题选项:', optionData);
|
||||
await ExamApi.createQuestionOption(optionData);
|
||||
}
|
||||
}
|
||||
} else if (questionType === 1) {
|
||||
// 多选题
|
||||
if (subQuestion.options && subQuestion.options.length > 0) {
|
||||
console.log('📊 多选题选项数据:', subQuestion.options);
|
||||
for (let i = 0; i < subQuestion.options.length; i++) {
|
||||
const option = subQuestion.options[i];
|
||||
const isCorrect = subQuestion.correctAnswers && subQuestion.correctAnswers.includes(i);
|
||||
const optionData = {
|
||||
questionId: questionId,
|
||||
content: option.content || '',
|
||||
izCorrent: isCorrect ? 1 : 0,
|
||||
orderNo: i
|
||||
};
|
||||
console.log('📝 创建多选题选项:', optionData);
|
||||
await ExamApi.createQuestionOption(optionData);
|
||||
}
|
||||
}
|
||||
} else if (questionType === 2) {
|
||||
// 判断题
|
||||
const optionData1 = {
|
||||
questionId: questionId,
|
||||
content: (option as any).text || option.content || '',
|
||||
izCorrent: (option as any).isCorrect ? 1 : 0,
|
||||
orderNo: i + 1
|
||||
content: '正确',
|
||||
izCorrent: subQuestion.trueFalseAnswer === true ? 1 : 0,
|
||||
orderNo: 0
|
||||
};
|
||||
const optionData2 = {
|
||||
questionId: questionId,
|
||||
content: '错误',
|
||||
izCorrent: subQuestion.trueFalseAnswer === false ? 1 : 0,
|
||||
orderNo: 1
|
||||
};
|
||||
console.log('📝 创建判断题选项:', optionData1, optionData2);
|
||||
await ExamApi.createQuestionOption(optionData1);
|
||||
await ExamApi.createQuestionOption(optionData2);
|
||||
}
|
||||
} else if (questionType === 3 || questionType === 4) {
|
||||
// 填空题、简答题 - 创建答案
|
||||
console.log('📊 处理主观题答案,题目类型:', questionType);
|
||||
|
||||
console.log('📝 创建题目选项:', optionData);
|
||||
await ExamApi.createQuestionOption(optionData);
|
||||
if (questionType === 3) {
|
||||
// 填空题
|
||||
if (subQuestion.fillBlanks && subQuestion.fillBlanks.length > 0) {
|
||||
console.log('📊 填空题答案数据:', subQuestion.fillBlanks);
|
||||
for (let i = 0; i < subQuestion.fillBlanks.length; i++) {
|
||||
const blank = subQuestion.fillBlanks[i];
|
||||
const answerData = {
|
||||
questionId: questionId,
|
||||
answerText: blank.content || '',
|
||||
orderNo: i
|
||||
};
|
||||
console.log('📝 创建填空题答案:', answerData);
|
||||
await ExamApi.createQuestionAnswer(answerData);
|
||||
}
|
||||
}
|
||||
} else if (questionType === 4) {
|
||||
// 简答题
|
||||
if (subQuestion.textAnswer) {
|
||||
const answerData = {
|
||||
questionId: questionId,
|
||||
answerText: subQuestion.textAnswer,
|
||||
orderNo: 0
|
||||
};
|
||||
console.log('📝 创建简答题答案:', answerData);
|
||||
await ExamApi.createQuestionAnswer(answerData);
|
||||
}
|
||||
}
|
||||
} else if (questionType === 5) {
|
||||
// 复合题 - 递归处理子题目
|
||||
console.log('📊 处理复合题,题目类型:', questionType);
|
||||
|
||||
if (subQuestion.subQuestions && subQuestion.subQuestions.length > 0) {
|
||||
console.log('📊 复合题子题目数据:', subQuestion.subQuestions);
|
||||
|
||||
// 为复合题的每个子题目创建题目、选项/答案
|
||||
for (let subIndex = 0; subIndex < subQuestion.subQuestions.length; subIndex++) {
|
||||
const compositeSubQuestion = subQuestion.subQuestions[subIndex];
|
||||
console.log('📊 处理复合题子题目:', compositeSubQuestion.title, '类型:', compositeSubQuestion.type);
|
||||
|
||||
// 创建子题目
|
||||
const subQuestionData = {
|
||||
repoId: '',
|
||||
parentId: questionId, // 设置父题目ID为复合题的ID
|
||||
type: convertQuestionTypeToNumber(compositeSubQuestion.type),
|
||||
content: compositeSubQuestion.title || '',
|
||||
analysis: compositeSubQuestion.explanation || '',
|
||||
difficulty: getDifficultyNumber(compositeSubQuestion.difficulty),
|
||||
score: compositeSubQuestion.score || 0,
|
||||
degree: 1,
|
||||
ability: 1
|
||||
};
|
||||
|
||||
console.log('📝 创建复合题子题目:', subQuestionData);
|
||||
const subQuestionResponse = await ExamApi.createQuestion(subQuestionData);
|
||||
|
||||
if (subQuestionResponse.data) {
|
||||
const subQuestionId = subQuestionResponse.data;
|
||||
console.log('✅ 复合题子题目创建成功,ID:', subQuestionId);
|
||||
|
||||
// 根据子题目类型创建选项或答案
|
||||
const subQuestionType = convertQuestionTypeToNumber(compositeSubQuestion.type);
|
||||
|
||||
if (subQuestionType === 0 || subQuestionType === 1 || subQuestionType === 2) {
|
||||
// 选择题类型的子题目 - 创建选项
|
||||
if (compositeSubQuestion.options && compositeSubQuestion.options.length > 0) {
|
||||
for (let i = 0; i < compositeSubQuestion.options.length; i++) {
|
||||
const option = compositeSubQuestion.options[i];
|
||||
const isCorrect = subQuestionType === 0 ?
|
||||
compositeSubQuestion.correctAnswer === i :
|
||||
subQuestionType === 1 ?
|
||||
compositeSubQuestion.correctAnswers && compositeSubQuestion.correctAnswers.includes(i) :
|
||||
compositeSubQuestion.trueFalseAnswer === (i === 0);
|
||||
|
||||
const optionData = {
|
||||
questionId: subQuestionId,
|
||||
content: option.content || '',
|
||||
izCorrent: isCorrect ? 1 : 0,
|
||||
orderNo: i
|
||||
};
|
||||
console.log('📝 创建复合题子题目选项:', optionData);
|
||||
await ExamApi.createQuestionOption(optionData);
|
||||
}
|
||||
}
|
||||
} else if (subQuestionType === 3 || subQuestionType === 4) {
|
||||
// 主观题类型的子题目 - 创建答案
|
||||
if (subQuestionType === 3 && compositeSubQuestion.fillBlanks) {
|
||||
// 填空题
|
||||
for (let i = 0; i < compositeSubQuestion.fillBlanks.length; i++) {
|
||||
const blank = compositeSubQuestion.fillBlanks[i];
|
||||
const answerData = {
|
||||
questionId: subQuestionId,
|
||||
answerText: blank.content || '',
|
||||
orderNo: i
|
||||
};
|
||||
console.log('📝 创建复合题子题目填空答案:', answerData);
|
||||
await ExamApi.createQuestionAnswer(answerData);
|
||||
}
|
||||
} else if (subQuestionType === 4 && compositeSubQuestion.textAnswer) {
|
||||
// 简答题
|
||||
const answerData = {
|
||||
questionId: subQuestionId,
|
||||
answerText: compositeSubQuestion.textAnswer,
|
||||
orderNo: 0
|
||||
};
|
||||
console.log('📝 创建复合题子题目简答答案:', answerData);
|
||||
await ExamApi.createQuestionAnswer(answerData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建题目答案(如果有答案)
|
||||
if (subQuestion.correctAnswer !== null && subQuestion.correctAnswer !== undefined) {
|
||||
console.log('📊 题目答案数据:', subQuestion.correctAnswer);
|
||||
const answerData = {
|
||||
questionId: questionId,
|
||||
answerText: String(subQuestion.correctAnswer),
|
||||
orderNo: 1
|
||||
};
|
||||
|
||||
console.log('📝 创建题目答案:', answerData);
|
||||
await ExamApi.createQuestionAnswer(answerData);
|
||||
} else {
|
||||
console.log('📝 题目无答案,跳过答案创建');
|
||||
}
|
||||
|
||||
// 将题目关联到试卷
|
||||
const paperQuestionData = {
|
||||
|
@ -828,7 +828,7 @@ const updateQuestionOptions = async (questionId: string, questionType: number) =
|
||||
id: existingOptionsIds.value[index] || '',
|
||||
content: option.content || '',
|
||||
izCorrent: index === correctAnswer ? 1 : 0,
|
||||
orderNo: index
|
||||
orderNo: index + 1 // orderNo从1开始
|
||||
})).filter(option => option.id); // 只更新有ID的选项
|
||||
|
||||
} else if (questionType === 1) { // 多选题
|
||||
@ -839,7 +839,7 @@ const updateQuestionOptions = async (questionId: string, questionType: number) =
|
||||
id: existingOptionsIds.value[index] || '',
|
||||
content: option.content || '',
|
||||
izCorrent: correctAnswers.includes(index) ? 1 : 0,
|
||||
orderNo: index
|
||||
orderNo: index + 1 // orderNo从1开始
|
||||
})).filter(option => option.id); // 只更新有ID的选项
|
||||
|
||||
} else if (questionType === 2) { // 判断题
|
||||
@ -1249,10 +1249,10 @@ const renderSingleChoiceData = (options: any[]) => {
|
||||
content: option.content || ''
|
||||
}));
|
||||
|
||||
// 设置正确答案
|
||||
// 设置正确答案 - 将orderNo转换为数组索引(orderNo从1开始,索引从0开始)
|
||||
const correctOption = sortedOptions.find(option => option.izCorrent === 1);
|
||||
if (correctOption) {
|
||||
questionForm.correctAnswer = correctOption.orderNo;
|
||||
questionForm.correctAnswer = correctOption.orderNo - 1; // 转换为数组索引
|
||||
}
|
||||
|
||||
console.log('✅ 单选题渲染完成:', {
|
||||
@ -1279,10 +1279,10 @@ const renderMultipleChoiceData = (options: any[]) => {
|
||||
content: option.content || ''
|
||||
}));
|
||||
|
||||
// 设置正确答案(可能有多个)
|
||||
// 设置正确答案(可能有多个) - 将orderNo转换为数组索引
|
||||
questionForm.correctAnswers = sortedOptions
|
||||
.filter(option => option.izCorrent === 1)
|
||||
.map(option => option.orderNo);
|
||||
.map(option => option.orderNo - 1); // 转换为数组索引
|
||||
|
||||
console.log('✅ 多选题渲染完成:', {
|
||||
options: questionForm.options,
|
||||
|
Loading…
x
Reference in New Issue
Block a user