423 lines
14 KiB
JavaScript
423 lines
14 KiB
JavaScript
import { getSystemDbPath } from './path.js';
|
|
import { executeWithRetry } from './utils.js';
|
|
import { getDbConnection } from './index.js';
|
|
|
|
/**
|
|
* 添加新题干及相关数据
|
|
* @param {Object} questionData - 题干数据
|
|
* @param {string} questionData.question_type - 题型代码
|
|
* @param {string} questionData.question_description - 题干描述
|
|
* @param {Array} questionData.images - 图片数据数组
|
|
* @param {Array} questionData.datasets - 数据集数据数组
|
|
* @returns {Promise<number>} 新创建的题干ID
|
|
*/
|
|
async function addQuestion(questionData) {
|
|
const { question_type, question_description, images = [], datasets = [] } = questionData;
|
|
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
// 开始事务
|
|
await db.runAsync('BEGIN TRANSACTION');
|
|
|
|
try {
|
|
// 1. 插入题干基本信息
|
|
const questionResult = await db.runAsync(
|
|
'INSERT INTO questions (question_type, question_name, question_description, created_at, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)',
|
|
[question_type, '', question_description]
|
|
);
|
|
|
|
const questionId = questionResult.lastID;
|
|
|
|
// 2. 插入图片数据
|
|
if (images.length > 0) {
|
|
for (const image of images) {
|
|
await db.runAsync(
|
|
'INSERT INTO question_images (question_id, image_name, image_base64, created_at, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)',
|
|
[questionId, image.image || 'image', image.base64]
|
|
);
|
|
}
|
|
}
|
|
|
|
// 3. 插入数据集数据
|
|
if (datasets.length > 0) {
|
|
for (const dataset of datasets) {
|
|
await db.runAsync(
|
|
'INSERT INTO question_datasets (question_id, dataset_name, dataset_data, created_at, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)',
|
|
[questionId, dataset.name || 'dataset',dataset.content]
|
|
);
|
|
}
|
|
}
|
|
|
|
// 提交事务
|
|
await db.runAsync('COMMIT');
|
|
return questionId;
|
|
} catch (error) {
|
|
// 回滚事务
|
|
await db.runAsync('ROLLBACK');
|
|
throw error;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 获取所有题干
|
|
* @returns {Promise<Array>} 题干列表
|
|
*/
|
|
async function getAllQuestions() {
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
return await db.allAsync('SELECT * FROM questions ORDER BY id DESC');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 根据ID获取题干详情
|
|
* @param {number} id - 题干ID
|
|
* @returns {Promise<Object>} 题干详情
|
|
*/
|
|
async function getQuestionById(id) {
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
return await db.getAsync('SELECT * FROM questions WHERE id = ?', [id]);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 更新题干信息
|
|
* @param {number} id - 题干ID
|
|
* @param {Object} questionData - 题干数据
|
|
* @returns {Promise<boolean>} 是否更新成功
|
|
*/
|
|
async function updateQuestion(id, questionData) {
|
|
const { question_type, question_description } = questionData;
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
const result = await db.runAsync(
|
|
'UPDATE questions SET question_type = ?, question_description = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
|
[question_type, question_description, id]
|
|
);
|
|
return result.changes > 0;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 删除题干
|
|
* @param {number} id - 题干ID
|
|
* @returns {Promise<boolean>} 是否删除成功
|
|
*/
|
|
async function deleteQuestion(id) {
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
// 开始事务
|
|
await db.runAsync('BEGIN TRANSACTION');
|
|
|
|
try {
|
|
// 先删除关联数据
|
|
await db.runAsync('DELETE FROM question_images WHERE question_id = ?', [id]);
|
|
await db.runAsync('DELETE FROM question_datasets WHERE question_id = ?', [id]);
|
|
|
|
// 删除关联的试题数据
|
|
await db.runAsync('DELETE FROM question_choices WHERE question_id = ?', [id]);
|
|
await db.runAsync('DELETE FROM question_fill_blanks WHERE question_id = ?', [id]);
|
|
await db.runAsync('DELETE FROM question_fill_table WHERE question_id = ?', [id]);
|
|
await db.runAsync('DELETE FROM question_fill_table_blanks WHERE question_id = ?', [id]);
|
|
await db.runAsync('DELETE FROM question_judge WHERE question_id = ?', [id]);
|
|
await db.runAsync('DELETE FROM question_short WHERE question_id = ?', [id]);
|
|
|
|
// 再删除题干
|
|
const result = await db.runAsync('DELETE FROM questions WHERE id = ?', [id]);
|
|
|
|
// 提交事务
|
|
await db.runAsync('COMMIT');
|
|
return result.changes > 0;
|
|
} catch (error) {
|
|
// 回滚事务
|
|
await db.runAsync('ROLLBACK');
|
|
throw error;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 获取所有题干及其关联信息
|
|
* @returns {Promise<Array>} 包含关联信息的题干列表
|
|
*/
|
|
async function getAllQuestionsWithRelations() {
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
// 1. 查询所有题干基本信息及关联的题型名称
|
|
const questions = await db.allAsync(`
|
|
SELECT q.*, di.item_name as question_type_name
|
|
FROM questions q
|
|
LEFT JOIN dict_items di ON q.question_type = di.item_code AND di.type_code = 'question_type'
|
|
ORDER BY q.id DESC
|
|
`);
|
|
|
|
// 2. 为每个题干查询关联的图片、数据集和特定类型的问题数据
|
|
for (const question of questions) {
|
|
// 查询关联的图片
|
|
const images = await db.allAsync(
|
|
'SELECT * FROM question_images WHERE question_id = ?',
|
|
[question.id]
|
|
);
|
|
question.images = images;
|
|
|
|
// 查询关联的数据集
|
|
const datasets = await db.allAsync(
|
|
'SELECT * FROM question_datasets WHERE question_id = ?',
|
|
[question.id]
|
|
);
|
|
datasets.forEach(dataset => {
|
|
try {
|
|
dataset.dataset_data = JSON.parse(dataset.dataset_data);
|
|
} catch (e) {
|
|
console.error('解析数据集失败:', e);
|
|
dataset.dataset_data = null;
|
|
}
|
|
});
|
|
question.datasets = datasets;
|
|
|
|
// 根据question_type关联不同的问题表
|
|
switch (question.question_type) {
|
|
case 'choice':
|
|
// 关联选择题表
|
|
question.choices = await db.allAsync(
|
|
'SELECT * FROM question_choices WHERE question_id = ?',
|
|
[question.id]
|
|
);
|
|
break;
|
|
case 'fill_blank':
|
|
// 关联填空题表
|
|
question.fillBlanks = await db.allAsync(
|
|
'SELECT * FROM question_fill_blanks WHERE question_id = ?',
|
|
[question.id]
|
|
);
|
|
break;
|
|
case 'fill_table':
|
|
// 关联表格填空题表和表格填空项表
|
|
question.fillTables = await db.allAsync(
|
|
'SELECT * FROM question_fill_table WHERE question_id = ?',
|
|
[question.id]
|
|
);
|
|
|
|
// 对每个表格查询其填空项
|
|
for (let table of question.fillTables) {
|
|
table.blanks = await db.allAsync(
|
|
'SELECT * FROM question_fill_table_blanks WHERE table_id = ?',
|
|
[table.id]
|
|
);
|
|
}
|
|
break;
|
|
case 'true_false':
|
|
// 关联判断题表
|
|
question.judges = await db.allAsync(
|
|
'SELECT * FROM question_judge WHERE question_id = ?',
|
|
[question.id]
|
|
);
|
|
break;
|
|
case 'short_answer':
|
|
case 'analysis':
|
|
case 'essay':
|
|
// 关联简答题表
|
|
question.shorts = await db.allAsync(
|
|
'SELECT * FROM question_short WHERE question_id = ?',
|
|
[question.id]
|
|
);
|
|
break;
|
|
default:
|
|
// 未知题型,不关联任何表
|
|
break;
|
|
}
|
|
}
|
|
|
|
return questions;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 更新题干描述
|
|
* @param {number} id - 题干ID
|
|
* @param {string} questionDescription - 新的题干描述
|
|
* @returns {Promise<boolean>} 是否更新成功
|
|
*/
|
|
async function updateQuestionDescription(id, questionDescription) {
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
const result = await db.runAsync(
|
|
'UPDATE questions SET question_description = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
|
[questionDescription, id]
|
|
);
|
|
return result.changes > 0;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 添加选择题问题
|
|
* @param {Object} choiceData - 选择题数据
|
|
* @param {number} choiceData.question_id - 题干ID
|
|
* @param {string} choiceData.choice_description - 问题描述
|
|
* @param {string} choiceData.choice_type - 题型(single/multiple)
|
|
* @param {Array} choiceData.choice_options - 候选项数组
|
|
* @param {Array} choiceData.correct_answers - 正确答案序号数组
|
|
* @returns {Promise<number>} 新创建的选择题ID
|
|
*/
|
|
async function addChoiceQuestion(choiceData) {
|
|
const { question_id, choice_description, choice_type, score, choice_options, correct_answers } = choiceData;
|
|
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
const result = await db.runAsync(
|
|
'INSERT INTO question_choices (question_id, choice_description, choice_type, score, choice_options, correct_answers, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)',
|
|
[question_id, choice_description, choice_type, score, JSON.stringify(choice_options), JSON.stringify(correct_answers)]
|
|
);
|
|
return result.lastID;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 添加选择题问题
|
|
* @param {number} id - 选择题ID
|
|
* @param {Object} choiceData - 选择题数据
|
|
* @returns {Promise<boolean>} 是否更新成功
|
|
*/
|
|
async function updateChoiceQuestion(id, choiceData) {
|
|
const { choice_description, choice_type, score, choice_options, correct_answers } = choiceData;
|
|
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
const result = await db.runAsync(
|
|
'UPDATE question_choices SET choice_description = ?, choice_type = ?, score = ?, choice_options = ?, correct_answers = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
|
[choice_description, choice_type, score, JSON.stringify(choice_options), JSON.stringify(correct_answers), id]
|
|
);
|
|
return result.changes > 0;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 添加填空题问题
|
|
* @param {Object} fillBlankData - 填空题数据
|
|
* @param {number} fillBlankData.question_id - 题干ID
|
|
* @param {string} fillBlankData.blank_description - 问题描述
|
|
* @param {number} fillBlankData.blank_count - 空白数量
|
|
* @param {Array} fillBlankData.correct_answers - 正确答案数组
|
|
* @param {number} fillBlankData.score - 分值
|
|
* @returns {Promise<number>} 新创建的填空题ID
|
|
*/
|
|
async function addFillBlankQuestion(fillBlankData) {
|
|
const { question_id, blank_description, blank_count, correct_answers, score } = fillBlankData;
|
|
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
const result = await db.runAsync(
|
|
'INSERT INTO question_fill_blanks (question_id, blank_description, blank_count, correct_answers, score, created_at, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)',
|
|
[question_id, blank_description, blank_count, JSON.stringify(correct_answers), score]
|
|
);
|
|
return result.lastID;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 更新填空题问题
|
|
* @param {number} id - 填空题ID
|
|
* @param {Object} fillBlankData - 填空题数据
|
|
* @returns {Promise<boolean>} 是否更新成功
|
|
*/
|
|
async function updateFillBlankQuestion(id, fillBlankData) {
|
|
const { blank_description, blank_count, correct_answers, score } = fillBlankData;
|
|
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
const result = await db.runAsync(
|
|
'UPDATE question_fill_blanks SET blank_description = ?, blank_count = ?, correct_answers = ?, score = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
|
[blank_description, blank_count, JSON.stringify(correct_answers), score, id]
|
|
);
|
|
return result.changes > 0;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 删除填空题问题
|
|
* @param {number} id - 填空题ID
|
|
* @returns {Promise<boolean>} 是否删除成功
|
|
*/
|
|
async function deleteFillBlankQuestion(id) {
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
const result = await db.runAsync('DELETE FROM question_fill_blanks WHERE id = ?', [id]);
|
|
return result.changes > 0;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 根据题干ID查询填空题问题
|
|
* @param {number} questionId - 题干ID
|
|
* @returns {Promise<Array>} 填空题列表
|
|
*/
|
|
async function getFillBlankQuestionsByQuestionId(questionId) {
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
const questions = await db.allAsync('SELECT * FROM question_fill_blanks WHERE question_id = ?', [questionId]);
|
|
// 解析correct_answers为数组
|
|
questions.forEach(question => {
|
|
try {
|
|
question.correct_answers = JSON.parse(question.correct_answers);
|
|
} catch (e) {
|
|
console.error('解析填空题答案失败:', e);
|
|
question.correct_answers = [];
|
|
}
|
|
});
|
|
return questions;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 查询试题总数和总分
|
|
* @returns {Promise<{totalQuestions: number, totalScore: number}>} 包含试题总数和总分的对象
|
|
*/
|
|
async function getQuestionsCountAndScore() {
|
|
const db = await getDbConnection(getSystemDbPath());
|
|
return executeWithRetry(db, async () => {
|
|
// 查询选择题总数和总分
|
|
const choiceResult = await db.getAsync(`
|
|
SELECT COUNT(*) as count, SUM(qc.score) as score
|
|
FROM questions q
|
|
LEFT JOIN question_choices qc ON q.id = qc.question_id
|
|
WHERE q.question_type = 'choice'
|
|
`);
|
|
|
|
// 查询填空题总数和总分
|
|
const fillBlankResult = await db.getAsync(`
|
|
SELECT COUNT(*) as count, SUM(qfb.score) as score
|
|
FROM questions q
|
|
LEFT JOIN question_fill_blanks qfb ON q.id = qfb.question_id
|
|
WHERE q.question_type = 'fill_blank'
|
|
`);
|
|
|
|
// 计算总题数和总分
|
|
const totalQuestions = (choiceResult.count || 0) + (fillBlankResult.count || 0);
|
|
const totalScore = (choiceResult.score || 0) + (fillBlankResult.score || 0);
|
|
|
|
return {
|
|
totalQuestions,
|
|
totalScore
|
|
};
|
|
});
|
|
}
|
|
|
|
export {
|
|
addQuestion,
|
|
getAllQuestions,
|
|
getQuestionById,
|
|
updateQuestion,
|
|
deleteQuestion,
|
|
getAllQuestionsWithRelations,
|
|
updateQuestionDescription,
|
|
addChoiceQuestion,
|
|
updateChoiceQuestion,
|
|
addFillBlankQuestion,
|
|
updateFillBlankQuestion,
|
|
deleteFillBlankQuestion,
|
|
getFillBlankQuestionsByQuestionId,
|
|
getQuestionsCountAndScore
|
|
};
|