217 lines
6.6 KiB
JavaScript
217 lines
6.6 KiB
JavaScript
/**
|
|
* 客观题结果服务模块
|
|
* 负责试卷结果数据获取和处理
|
|
*/
|
|
|
|
import { questionApi } from '../../config/question_api.js';
|
|
|
|
/**
|
|
* 格式化时间显示(秒数转为 MM:SS 或 HH:MM:SS 格式)
|
|
* @param {Number} totalSeconds - 总秒数
|
|
* @returns {String} 格式化后的时间字符串
|
|
*/
|
|
export function formatTime(totalSeconds) {
|
|
if (totalSeconds < 0) totalSeconds = 0;
|
|
const hours = Math.floor(totalSeconds / 3600);
|
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
const seconds = totalSeconds % 60;
|
|
|
|
const formatNumber = (num) => {
|
|
return num < 10 ? '0' + num : String(num);
|
|
};
|
|
|
|
if (hours > 0) {
|
|
return `${formatNumber(hours)}:${formatNumber(minutes)}:${formatNumber(seconds)}`;
|
|
} else {
|
|
return `${formatNumber(minutes)}:${formatNumber(seconds)}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取试卷和答题记录
|
|
* @param {String} paperId - 试卷ID
|
|
* @param {String} taskId - 任务ID
|
|
* @param {String} userId - 用户ID
|
|
* @returns {Promise} 处理后的结果数据
|
|
*/
|
|
export function fetchQuestionPaper(paperId, taskId, userId) {
|
|
return new Promise((resolve, reject) => {
|
|
if (!paperId) {
|
|
reject(new Error('试卷ID不能为空'));
|
|
return;
|
|
}
|
|
|
|
if (!userId) {
|
|
reject(new Error('用户未登录'));
|
|
return;
|
|
}
|
|
|
|
Promise.all([
|
|
questionApi.getAnswerRecord(userId, paperId, 1, 361),
|
|
questionApi.getPaperWithAnswers(paperId), // 使用新接口获取包含答案和解析的试卷数据
|
|
]).then(([recordRes, paperRes]) => {
|
|
// 处理试卷数据
|
|
let paperInfo = null;
|
|
if (paperRes.code === 200 && paperRes.data) {
|
|
paperInfo = paperRes.data;
|
|
} else if (paperRes.success === true && (paperRes.paper || paperRes.data)) {
|
|
paperInfo = paperRes.paper || paperRes.data;
|
|
}
|
|
|
|
if (!paperInfo) {
|
|
reject(new Error(paperRes.msg || '获取试卷失败'));
|
|
return;
|
|
}
|
|
|
|
// 处理答题记录
|
|
const records = recordRes.records || recordRes.data || [];
|
|
const answerRecordMap = {};
|
|
records.forEach(record => {
|
|
answerRecordMap[record.question_id] = record;
|
|
});
|
|
|
|
// 统计信息
|
|
const totalCount = records.length;
|
|
const correctCount = records.filter(r => r.is_correct === true).length;
|
|
const correctRate = totalCount > 0 ? (correctCount / totalCount * 100) : 0;
|
|
|
|
// 计算总用时
|
|
let usedTimeSeconds = 0;
|
|
if (records.length > 0) {
|
|
const sortedRecords = records.sort((a, b) => a.start_time - b.start_time);
|
|
const firstStartTime = sortedRecords[0].start_time;
|
|
const lastEndTime = sortedRecords[sortedRecords.length - 1].end_time;
|
|
usedTimeSeconds = lastEndTime - firstStartTime;
|
|
}
|
|
const usedTime = formatTime(usedTimeSeconds);
|
|
|
|
// 合并试卷题目和答题记录
|
|
const mapped = paperInfo.questions.map(q => {
|
|
let mappedQ = null;
|
|
|
|
if (q && (q.question_id || q.question_content || q.question_type)) {
|
|
// 旧结构
|
|
mappedQ = {
|
|
...q,
|
|
id: q.question_id,
|
|
title: q.question_content,
|
|
type: q.question_type,
|
|
materials: q.materials || [],
|
|
options: (q.options || []).map(opt => ({
|
|
...opt,
|
|
id: opt.option_id,
|
|
content: opt.option_content,
|
|
label: opt.option_label
|
|
})),
|
|
explanation: q.explanation || ''
|
|
};
|
|
} else {
|
|
// 新结构
|
|
const opts = Array.isArray(q.options) ? q.options.map((text, idx) => {
|
|
if (typeof text === 'string') {
|
|
return {
|
|
id: idx + 1,
|
|
content: text,
|
|
label: String.fromCharCode(65 + idx)
|
|
};
|
|
} else {
|
|
return {
|
|
...text,
|
|
id: text.id || text.option_id || (idx + 1),
|
|
content: text.content || text.option_content || text,
|
|
label: text.label || text.option_label || String.fromCharCode(65 + idx)
|
|
};
|
|
}
|
|
}) : [];
|
|
|
|
const mappedType = (q.type === 2 || q.type === 'single_choice') ? 'single_choice' : (q.type === 3 || q.type === 'multiple_choice' ? 'multiple_choice' : 'single_choice');
|
|
mappedQ = {
|
|
...q,
|
|
id: q.id,
|
|
title: q.title || q.content || '',
|
|
type: mappedType,
|
|
materials: q.materials || [],
|
|
options: opts,
|
|
explanation: q.explanation || ''
|
|
};
|
|
}
|
|
|
|
// 获取答题记录
|
|
const record = answerRecordMap[mappedQ.id];
|
|
const userAnswer = record ? record.user_answer : '';
|
|
// 优先使用题目本身的答案(新接口会返回),如果没有则使用答题记录中的答案
|
|
const correctAnswer = q.answer || (record ? record.correct_answer : '');
|
|
const isCorrect = record ? record.is_correct : false;
|
|
|
|
// 处理用户答案
|
|
const userAnswerArray = userAnswer ? (Array.isArray(userAnswer) ? userAnswer : [userAnswer]) : [];
|
|
const isUserAnswerEmpty = !userAnswer || userAnswer === '';
|
|
|
|
// 处理选项样式
|
|
const optionsWithStyle = mappedQ.options.map((opt, optIndex) => {
|
|
const optionLabel = opt.label || opt.option_label;
|
|
const isOptionCorrect = opt.is_correct || (opt.answer === 'true') || (optionLabel === correctAnswer);
|
|
const isUserSelected = userAnswerArray.includes(optionLabel);
|
|
|
|
let cssClass = '';
|
|
if (isOptionCorrect) {
|
|
cssClass = 'option-correct';
|
|
} else if (isUserSelected) {
|
|
cssClass = 'option-wrong';
|
|
}
|
|
|
|
// 确保每个选项都有唯一的 id
|
|
const optionId = opt.id || opt.option_id || (mappedQ.id ? `${mappedQ.id}_opt_${optIndex}` : `opt_${optIndex}`);
|
|
|
|
return {
|
|
...opt,
|
|
id: optionId,
|
|
content: opt.content || opt.option_content,
|
|
label: optionLabel,
|
|
isCorrect: isOptionCorrect,
|
|
cssClass: cssClass
|
|
};
|
|
});
|
|
|
|
// 获取解析内容,优先使用题目本身的解析(新接口会返回)
|
|
// 注意:空字符串也是有效值,需要明确检查
|
|
let answerAnalysis = '';
|
|
if (q.explanation !== undefined && q.explanation !== null && q.explanation !== '') {
|
|
answerAnalysis = q.explanation;
|
|
} else if (mappedQ.explanation !== undefined && mappedQ.explanation !== null && mappedQ.explanation !== '') {
|
|
answerAnalysis = mappedQ.explanation;
|
|
} else if (q.answer_analysis !== undefined && q.answer_analysis !== null && q.answer_analysis !== '') {
|
|
answerAnalysis = q.answer_analysis;
|
|
}
|
|
|
|
return {
|
|
...mappedQ,
|
|
options: optionsWithStyle,
|
|
cssClass: isCorrect ? 'arc-correct' : !isUserAnswerEmpty ? 'arc-wrong' : 'arc-unanswered',
|
|
correct_answer: correctAnswer,
|
|
user_answer: userAnswer,
|
|
is_correct: isCorrect,
|
|
answer_analysis: answerAnalysis
|
|
};
|
|
});
|
|
|
|
resolve({
|
|
paperInfo,
|
|
questions: mapped,
|
|
stats: {
|
|
totalCount,
|
|
correctCount,
|
|
correctRate,
|
|
usedTime,
|
|
usedTimeSeconds
|
|
}
|
|
});
|
|
})
|
|
.catch(err => {
|
|
console.error('API调用失败:', err);
|
|
reject(err);
|
|
});
|
|
});
|
|
}
|
|
|