迁移config, dict, question接口

This commit is contained in:
chenqiang 2025-08-27 22:32:59 +08:00
parent f3b783bf86
commit 6022e8e17a
9 changed files with 2040 additions and 6 deletions

143
src/background/db/config.js Normal file
View File

@ -0,0 +1,143 @@
const { getSystemDbPath } = require('./path.js');
const { executeWithRetry } = require('./utils.js');
const { getDbConnection } = require('./index.js');
/**
* 从config表获取配置项
* @param {string} key - 配置项键名
* @returns {Promise<{key: string, value: string} | null>} 配置项对象如果不存在返回null
*/
async function getConfig(key) {
try {
const db = await getDbConnection(getSystemDbPath());
const result = await executeWithRetry(db, async () => {
return await db.getAsync('SELECT * FROM config WHERE key = ?', [key]);
});
return result;
} catch (error) {
console.error(`获取配置项${key}失败:`, error);
throw error;
}
}
/**
* 获取所有配置项列表
* @returns {Promise<Array<{id: number, key: string, value: string, protected: number}>>} 配置项列表
*/
async function getAllConfigs() {
try {
const db = await getDbConnection(getSystemDbPath());
const result = await executeWithRetry(db, async () => {
return await db.allAsync('SELECT * FROM config');
});
return result;
} catch (error) {
console.error('获取配置项列表失败:', error);
throw error;
}
}
/**
* 通过ID获取配置项
* @param {number} id - 配置项ID
* @returns {Promise<{id: number, key: string, value: string, protected: number} | null>} 配置项对象如果不存在返回null
*/
async function getConfigById(id) {
try {
const db = await getDbConnection(getSystemDbPath());
const result = await executeWithRetry(db, async () => {
return await db.getAsync('SELECT * FROM config WHERE id = ?', [id]);
});
return result;
} catch (error) {
console.error(`通过ID获取配置项${id}失败:`, error);
throw error;
}
}
/**
* 更新或插入配置项
* @param {string} key - 配置项键名
* @param {string} value - 配置项值
* @returns {Promise<void>}
*/
async function setConfig(key, value) {
try {
const db = await getDbConnection(getSystemDbPath());
// 先检查是否存在
const existing = await executeWithRetry(db, async () => {
return await db.getAsync('SELECT * FROM config WHERE key = ?', [key]);
});
if (existing) {
// 检查是否受保护
// if (existing.protected === 1) {
// throw new Error(`配置项${key}是受保护的,无法修改`);
// }
// 更新
await executeWithRetry(db, async () => {
await db.runAsync('UPDATE config SET value = ? WHERE key = ?', [value, key]);
console.log(`成功更新配置项: ${key}`);
});
} else {
// 插入 (默认不保护)
await executeWithRetry(db, async () => {
await db.runAsync('INSERT INTO config (key, value, protected) VALUES (?, ?, 0)', [key, value]);
console.log(`成功插入配置项: ${key}`);
});
}
} catch (error) {
console.error(`设置配置项${key}失败:`, error);
throw error;
}
}
/**
* 删除配置项
* @param {number} id - 配置项ID
* @returns {Promise<void>}
*/
async function deleteConfig(id) {
try {
const db = await getDbConnection(getSystemDbPath());
// 先检查是否存在
const existing = await executeWithRetry(db, async () => {
return await db.getAsync('SELECT * FROM config WHERE id = ?', [id]);
});
if (!existing) {
throw new Error(`配置项ID ${id} 不存在`);
}
// 检查是否受保护
if (existing.protected === 1) {
throw new Error(`配置项 ${existing.key} 是受保护的,无法删除`);
}
// 删除
await executeWithRetry(db, async () => {
await db.runAsync('DELETE FROM config WHERE id = ?', [id]);
console.log(`成功删除配置项ID: ${id}`);
});
} catch (error) {
console.error(`删除配置项ID ${id} 失败:`, error);
throw error;
}
}
// 导出使用CommonJS格式
module.exports = {
getConfig,
setConfig,
getAllConfigs,
getConfigById,
deleteConfig
};

259
src/background/db/dict.js Normal file
View File

@ -0,0 +1,259 @@
const { getSystemDbPath } = require('./path.js');
const { executeWithRetry } = require('./utils.js');
const { getDbConnection } = require('./index.js');
/**
* 查询dict_items连接dict_types后的记录列表
* @returns {Promise<Array>} 记录列表
*/
async function getDictItemsWithTypes() {
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
const sql = `
SELECT di.*, dt.type_name
FROM dict_items di
JOIN dict_types dt ON di.type_code = dt.type_code
ORDER BY dt.type_code, di.item_code
`;
return await db.allAsync(sql);
});
}
/**
* 查询dict_types列表
* @returns {Promise<Array>} 类型列表
*/
async function getDictTypes() {
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
const sql = 'SELECT * FROM dict_types ORDER BY type_code';
return await db.allAsync(sql);
});
}
/**
* 添加dict_types
* @param {Object} typeData 类型数据
* @returns {Promise<Object>} 添加的类型
*/
async function addDictType(typeData) {
const { type_code, type_name, description } = typeData;
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
const sql = 'INSERT INTO dict_types (type_code, type_name, description) VALUES (?, ?, ?)';
const result = await db.runAsync(sql, [type_code, type_name, description || null]);
return { id: result.lastID, ...typeData };
});
}
/**
* 添加dict_items
* @param {Object} itemData 字典项数据
* @returns {Promise<Object>} 添加的字典项
*/
async function addDictItem(itemData) {
const { type_code, item_code, item_name, item_description, parent_code, is_active } = itemData;
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
const sql = 'INSERT INTO dict_items (type_code, item_code, item_name, item_description, parent_code, is_active) VALUES (?, ?, ?, ?, ?, ?)';
const result = await db.runAsync(sql, [
type_code,
item_code,
item_name,
item_description || null,
parent_code || null,
is_active !== undefined ? is_active : 1
]);
// 检查result是否存在如果不存在则获取最后插入的ID
let lastId;
if (result && result.lastID) {
lastId = result.lastID;
} else {
// 使用另一种方式获取最后插入的ID
const idResult = await db.getAsync('SELECT last_insert_rowid() as id');
lastId = idResult ? idResult.id : null;
}
if (!lastId) {
throw new Error('无法获取插入的字典项ID');
}
return { id: lastId, ...itemData };
});
}
/**
* 查询一条dict_types
* @param {number} id 类型ID
* @returns {Promise<Object|null>} 类型数据或null
*/
async function getDictTypeById(id) {
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
const sql = 'SELECT * FROM dict_types WHERE id = ?';
return await db.getAsync(sql, [id]);
});
}
/**
* 查询一条dict_items
* @param {number} id 字典项ID
* @returns {Promise<Object|null>} 字典项数据或null
*/
async function getDictItemById(id) {
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
const sql = 'SELECT * FROM dict_items WHERE id = ?';
return await db.getAsync(sql, [id]);
});
}
/**
* 根据dict_types的type_code查询dict_items列表
* @param {string} typeCode 类型编码
* @param {number} isActive 是否激活(1=激活, 0=未激活, 不传=全部)
* @returns {Promise<Array>} 字典项列表
*/
async function getDictItemsByTypeCode(typeCode, isActive = undefined) {
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
let sql = 'SELECT * FROM dict_items WHERE type_code = ?';
const params = [typeCode];
if (isActive !== undefined) {
sql += ' AND is_active = ?';
params.push(isActive);
}
sql += ' ORDER BY item_code';
return await db.allAsync(sql, params);
});
}
/**
* 更新一条dict_types
* @param {number} id 类型ID
* @param {Object} typeData 更新的数据
* @returns {Promise<boolean>} 是否更新成功
*/
async function updateDictType(id, typeData) {
const { type_code, type_name, description } = typeData;
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
const sql = 'UPDATE dict_types SET type_code = ?, type_name = ?, description = ? WHERE id = ?';
const result = await db.runAsync(sql, [type_code, type_name, description || null, id]);
return result.changes > 0;
});
}
/**
* 更新一条dict_items
* @param {number} id 字典项ID
* @param {Object} itemData 更新的数据
* @returns {Promise<boolean>} 是否更新成功
*/
async function updateDictItem(id, itemData) {
const { type_code, item_code, item_name, item_description, parent_code, is_active } = itemData;
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
const sql = 'UPDATE dict_items SET type_code = ?, item_code = ?, item_name = ?, item_description = ?, parent_code = ?, is_active = ? WHERE id = ?';
const result = await db.runAsync(sql, [
type_code,
item_code,
item_name,
item_description || null,
parent_code || null,
is_active !== undefined ? is_active : 1,
id
]);
// 检查result是否存在以及是否有changes属性
if (!result || result.changes === undefined) {
return false;
}
return result.changes > 0;
});
}
/**
* 删除一条dict_types
* @param {number} id 类型ID
* @returns {Promise<boolean>} 是否删除成功
*/
async function deleteDictType(id) {
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
// 先检查是否有关联的字典项
const checkSql = 'SELECT COUNT(*) as count FROM dict_items WHERE type_code = (SELECT type_code FROM dict_types WHERE id = ?)';
const result = await db.getAsync(checkSql, [id]);
if (result.count > 0) {
throw new Error('该字典类型下有关联的字典项,不允许删除');
}
// 删除字典类型
const deleteSql = 'DELETE FROM dict_types WHERE id = ?';
const deleteResult = await db.runAsync(deleteSql, [id]);
return deleteResult.changes > 0;
});
}
/**
* 删除一条dict_items
* @param {number} id 字典项ID
* @returns {Promise<boolean>} 是否删除成功
*/
async function deleteDictItem(id) {
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
const sql = 'DELETE FROM dict_items WHERE id = ?';
const result = await db.runAsync(sql, [id]);
// 检查result是否存在以及是否有changes属性
if (!result || result.changes === undefined) {
return false;
}
return result.changes > 0;
});
}
// 检查parent_code是否存在
async function checkParentCodeExists(parentCode) {
if (!parentCode) return true; // 允许空的parent_code
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
const sql = 'SELECT COUNT(*) as count FROM dict_items WHERE item_code = ?';
const result = await db.getAsync(sql, [parentCode]);
return result.count > 0;
});
}
// 检查是否有其他记录引用了该item_code作为parent_code
async function hasChildReferences(itemCode) {
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
const sql = 'SELECT COUNT(*) as count FROM dict_items WHERE parent_code = ?';
const result = await db.getAsync(sql, [itemCode]);
return result.count > 0;
});
}
// 导出使用CommonJS格式
module.exports = {
getDictItemsWithTypes,
getDictTypes,
addDictType,
addDictItem,
getDictTypeById,
getDictItemById,
getDictItemsByTypeCode,
updateDictType,
updateDictItem,
deleteDictType,
deleteDictItem,
checkParentCodeExists,
hasChildReferences
};

View File

@ -0,0 +1,423 @@
const { executeWithRetry } = require('./utils.js');
const { getDbConnection } = require('./index.js');
const { getSystemDbPath } = require('./path.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
};
});
}
// 导出使用CommonJS格式
module.exports = {
addQuestion,
getAllQuestions,
getQuestionById,
updateQuestion,
deleteQuestion,
getAllQuestionsWithRelations,
updateQuestionDescription,
addChoiceQuestion,
updateChoiceQuestion,
addFillBlankQuestion,
updateFillBlankQuestion,
deleteFillBlankQuestion,
getFillBlankQuestionsByQuestionId,
getQuestionsCountAndScore
};

View File

@ -9,6 +9,12 @@ const isDevelopment = process.env.NODE_ENV !== 'production'
// 导入数据库相关函数
const { checkDatabaseInitialized, initializeDatabase } = require('./db/index.js');
// 导入配置服务
const { initConfigIpc } = require('./service/configService.js');
// 导入字典服务
const { initDictIpc } = require('./service/dictService.js');
// 导入试题服务
const { initQuestionIpc } = require('./service/questionService.js');
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
@ -18,12 +24,10 @@ protocol.registerSchemesAsPrivileged([
async function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
width: 800,
height: 600,
width: 800, // 默认宽度(实际会被最大化覆盖)
height: 600, // 默认高度(实际会被最大化覆盖)
show: false, // 先隐藏窗口,避免闪烁
webPreferences: {
// 修改这一行
preload: require('path').join(__dirname, 'preload.js'),
// 改为使用绝对路径解析
preload: require('path').join(process.cwd(), 'src/preload.js'),
nodeIntegration: false,
@ -31,6 +35,11 @@ async function createWindow() {
}
})
// 在窗口显示前设置最大化
win.maximize();
// 然后显示窗口
win.show();
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
@ -119,6 +128,13 @@ app.on('ready', async () => {
console.error('SQLite测试异常:', error);
}
// 初始化配置相关的IPC处理程序
initConfigIpc(ipcMain);
// 初始化字典相关的IPC处理程序
initDictIpc(ipcMain);
// 初始化试题相关的IPC处理程序
await initQuestionIpc(ipcMain);
createWindow();
});

View File

@ -0,0 +1,250 @@
const {
getConfig,
setConfig,
getAllConfigs,
getConfigById,
deleteConfig,
} = require('../db/config.js');
const bcrypt = require('bcryptjs');
/**
* 服务层获取配置项
* @param {string} key - 配置项键名
* @returns {Promise<{key: string, value: string} | null>}
*/
async function fetchConfig(key) {
try {
return await getConfig(key);
} catch (error) {
console.error("服务层: 获取配置项失败", error);
throw error;
}
}
/**
* 服务层获取所有配置项
* @returns {Promise<Array<{id: number, key: string, value: string, protected: number}>>}
*/
async function fetchAllConfigs() {
try {
return await getAllConfigs();
} catch (error) {
console.error("服务层: 获取所有配置项失败", error);
throw error;
}
}
/**
* 服务层通过ID获取配置项
* @param {number} id - 配置项ID
* @returns {Promise<{id: number, key: string, value: string, protected: number} | null>}
*/
async function fetchConfigById(id) {
try {
return await getConfigById(id);
} catch (error) {
console.error("服务层: 通过ID获取配置项失败", error);
throw error;
}
}
/**
* 服务层保存配置项
* @param {string} key - 配置项键名
* @param {string} value - 配置项值
* @returns {Promise<void>}
*/
async function saveConfig(key, value) {
try {
await setConfig(key, value);
} catch (error) {
console.error("服务层: 保存配置项失败", error);
throw error;
}
}
/**
* 服务层删除配置项
* @param {number} id - 配置项ID
* @returns {Promise<void>}
*/
async function removeConfig(id) {
try {
await deleteConfig(id);
} catch (error) {
console.error("服务层: 删除配置项失败", error);
throw error;
}
}
/**
* 获取系统配置并转为Map
* @returns {Promise<{[key: string]: string}>}
*/
async function getSystemConfig() {
try {
const configs = await getAllConfigs();
const configMap = {};
configs.forEach((config) => {
configMap[config.key] = config.value;
});
return configMap;
} catch (error) {
console.error("获取系统配置失败:", error);
throw error;
}
}
/**
* 批量更新系统配置
* @param {{[key: string]: string}} config - 配置对象
* @returns {Promise<boolean>}
*/
async function updateSystemConfig(config) {
try {
for (const [key, value] of Object.entries(config)) {
await setConfig(key, value);
}
return true;
} catch (error) {
console.error("更新系统配置失败:", error);
throw error;
}
}
/**
* 增加题库版本号
* @returns {Promise<boolean>}
*/
async function increaseQuestionBankVersion() {
try {
const currentVersion = await getConfig("question_bank_version");
const newVersion = currentVersion ? parseInt(currentVersion.value) + 1 : 1;
await setConfig("question_bank_version", newVersion.toString());
return true;
} catch (error) {
console.error("增加题库版本号失败:", error);
throw error;
}
}
/**
* 管理员登录验证
* @param {string} password - 用户输入的密码
* @returns {Promise<{success: boolean, message: string}>}
*/
async function verifyAdminPassword(password) {
try {
const config = await getConfig("admin_password");
if (!config || !config.value) {
return { success: false, message: "管理员密码未设置" };
}
const isMatch = await bcrypt.compare(password, config.value);
if (isMatch) {
return { success: true, message: "登录成功" };
} else {
return { success: false, message: "密码错误" };
}
} catch (error) {
console.error("验证管理员密码失败:", error);
return { success: false, message: "验证过程发生错误" };
}
}
/**
* 初始化配置相关的IPC处理程序
* @param {import('electron').IpcMain} ipcMain - IPC主进程实例
*/
function initConfigIpc(ipcMain) {
// 管理员登录验证
ipcMain.handle("admin-login", async (event, credentials) => {
try {
return await verifyAdminPassword(credentials.password);
} catch (error) {
console.error("Failed to verify admin password:", error);
return { success: false, message: "验证过程发生错误" };
}
});
// 系统相关
ipcMain.handle("system-get-config", async () => {
try {
return await getSystemConfig();
} catch (error) {
console.error("Failed to get system config:", error);
return null;
}
});
ipcMain.handle("system-update-config", async (event, config) => {
try {
return await updateSystemConfig(config);
} catch (error) {
console.error("Failed to update system config:", error);
return false;
}
});
ipcMain.handle("system-increase-question-band-version", async () => {
try {
return await increaseQuestionBankVersion();
} catch (error) {
console.error("Failed to increase question band version:", error);
return false;
}
});
// 配置项管理相关IPC
ipcMain.handle("config-fetch-all", async () => {
try {
return await fetchAllConfigs();
} catch (error) {
console.error("Failed to fetch all configs:", error);
throw error;
}
});
ipcMain.handle("config-fetch-by-id", async (event, id) => {
try {
return await fetchConfigById(id);
} catch (error) {
console.error(`Failed to fetch config by id ${id}:`, error);
throw error;
}
});
ipcMain.handle("config-save", async (event, { key, value }) => {
try {
await saveConfig(key, value);
return true;
} catch (error) {
console.error(`Failed to save config ${key}:`, error);
throw error;
}
});
ipcMain.handle("config-delete", async (event, id) => {
try {
await removeConfig(id);
return true;
} catch (error) {
console.error(`Failed to delete config ${id}:`, error);
throw error;
}
});
}
// 导出使用CommonJS格式
module.exports = {
fetchConfig,
fetchAllConfigs,
fetchConfigById,
saveConfig,
removeConfig,
getSystemConfig,
updateSystemConfig,
increaseQuestionBankVersion,
verifyAdminPassword,
initConfigIpc
};

View File

@ -0,0 +1,332 @@
const {
getDictItemsWithTypes,
getDictTypes,
addDictType,
addDictItem,
getDictTypeById,
getDictItemById,
getDictItemsByTypeCode,
updateDictType,
updateDictItem,
deleteDictType,
deleteDictItem,
checkParentCodeExists,
hasChildReferences,
} = require('../db/dict.js');
/**
* 服务层查询字典项及其类型
* @returns {Promise<Array>} 字典项列表
*/
async function fetchDictItemsWithTypes() {
try {
return await getDictItemsWithTypes();
} catch (error) {
console.error("服务层: 查询字典项及其类型失败", error);
throw error;
}
}
/**
* 服务层查询字典类型列表
* @returns {Promise<Array>} 字典类型列表
*/
async function fetchDictTypes() {
try {
return await getDictTypes();
} catch (error) {
console.error("服务层: 查询字典类型列表失败", error);
throw error;
}
}
/**
* 服务层添加字典类型
* @param {Object} typeData 字典类型数据
* @returns {Promise<Object>} 添加的字典类型
*/
async function createDictType(typeData) {
try {
return await addDictType(typeData);
} catch (error) {
console.error("服务层: 添加字典类型失败", error);
throw error;
}
}
/**
* 服务层添加字典项
* @param {Object} itemData 字典项数据
* @returns {Promise<Object>} 添加的字典项
*/
async function createDictItem(itemData) {
try {
return await addDictItem(itemData);
} catch (error) {
console.error("服务层: 添加字典项失败", error);
throw error;
}
}
/**
* 服务层根据ID查询字典类型
* @param {number} id 字典类型ID
* @returns {Promise<Object|null>} 字典类型数据
*/
async function fetchDictTypeById(id) {
try {
return await getDictTypeById(id);
} catch (error) {
console.error("服务层: 根据ID查询字典类型失败", error);
throw error;
}
}
/**
* 服务层根据ID查询字典项
* @param {number} id 字典项ID
* @returns {Promise<Object|null>} 字典项数据
*/
async function fetchDictItemById(id) {
try {
return await getDictItemById(id);
} catch (error) {
console.error("服务层: 根据ID查询字典项失败", error);
throw error;
}
}
/**
* 服务层根据类型编码查询字典项
* @param {string} typeCode 类型编码
* @param {number} isActive 是否激活(1=激活, 0=未激活, 不传=全部)
* @returns {Promise<Array>} 字典项列表
*/
async function fetchDictItemsByTypeCode(typeCode, isActive = undefined) {
try {
return await getDictItemsByTypeCode(typeCode, isActive);
} catch (error) {
console.error("服务层: 根据类型编码查询字典项失败", error);
throw error;
}
}
/**
* 服务层更新字典类型
* @param {number} id 字典类型ID
* @param {Object} typeData 更新的数据
* @returns {Promise<boolean>} 是否更新成功
*/
async function modifyDictType(id, typeData) {
try {
return await updateDictType(id, typeData);
} catch (error) {
console.error("服务层: 更新字典类型失败", error);
throw error;
}
}
/**
* 服务层更新字典项
* @param {number} id 字典项ID
* @param {Object} itemData 更新的数据
* @returns {Promise<boolean>} 是否更新成功
*/
async function modifyDictItem(id, itemData) {
try {
return await updateDictItem(id, itemData);
} catch (error) {
console.error("服务层: 更新字典项失败", error);
throw error;
}
}
/**
* 服务层删除字典类型
* @param {number} id 字典类型ID
* @returns {Promise<boolean>} 是否删除成功
*/
async function removeDictType(id) {
try {
return await deleteDictType(id);
} catch (error) {
console.error("服务层: 删除字典类型失败", error);
throw error;
}
}
/**
* 服务层删除字典项
* @param {number} id 字典项ID
* @returns {Promise<boolean>} 是否删除成功
*/
async function removeDictItem(id) {
try {
return await deleteDictItem(id);
} catch (error) {
console.error("服务层: 删除字典项失败", error);
throw error;
}
}
/**
* 服务层检查父级编码是否存在
* @param {string} parentCode 父级编码
* @returns {Promise<boolean>} 是否存在
*/
async function checkDictParentCode(parentCode) {
try {
return await checkParentCodeExists(parentCode);
} catch (error) {
console.error("服务层: 检查父级编码失败", error);
throw error;
}
}
/**
* 服务层检查是否有子引用
* @param {string} itemCode 字典项编码
* @returns {Promise<boolean>} 是否有子引用
*/
async function checkDictChildReferences(itemCode) {
try {
return await hasChildReferences(itemCode);
} catch (error) {
console.error("服务层: 检查子引用失败", error);
throw error;
}
}
/**
* 初始化字典相关的IPC处理程序
* @param {import('electron').IpcMain} ipcMain - IPC主进程实例
*/
function initDictIpc(ipcMain) {
// 字典管理相关IPC
ipcMain.handle("dict-fetch-types", async () => {
try {
return await fetchDictTypes();
} catch (error) {
console.error("Failed to fetch dict types:", error);
throw error;
}
});
ipcMain.handle(
"dict-fetch-items-by-type",
async (event, typeCode, isActive = undefined) => {
try {
return await fetchDictItemsByTypeCode(typeCode, isActive);
} catch (error) {
console.error(`Failed to fetch dict items by type ${typeCode}:`, error);
throw error;
}
}
);
ipcMain.handle("dict-create-type", async (event, dictType) => {
try {
return await createDictType(dictType);
} catch (error) {
console.error("Failed to create dict type:", error);
throw error;
}
});
// 将 updateDictType 改为 modifyDictType
ipcMain.handle("dict-update-type", async (event, dictType) => {
try {
return await modifyDictType(dictType.id, dictType);
} catch (error) {
console.error("Failed to update dict type:", error);
throw error;
}
});
// 将 deleteDictType 改为 removeDictType
ipcMain.handle("dict-delete-type", async (event, typeCode) => {
try {
return await removeDictType(typeCode);
} catch (error) {
console.error(`Failed to delete dict type ${typeCode}:`, error);
throw error;
}
});
ipcMain.handle("dict-create-item", async (event, dictItem) => {
try {
return await createDictItem(dictItem);
} catch (error) {
console.error("Failed to create dict item:", error);
throw error;
}
});
// 将 updateDictItem 改为 modifyDictItem
ipcMain.handle("dict-update-item", async (event, dictItem) => {
try {
return await modifyDictItem(dictItem.id, dictItem);
} catch (error) {
console.error("Failed to update dict item:", error);
throw error;
}
});
// 将 deleteDictItem 改为 removeDictItem
ipcMain.handle("dict-delete-item", async (event, id) => {
try {
return await removeDictItem(id);
} catch (error) {
console.error(`Failed to delete dict item ${id}:`, error);
throw error;
}
});
// 将 fetchAllDictItemsWithTypes 改为 fetchDictItemsWithTypes
ipcMain.handle("dict-fetch-all-items-with-types", async () => {
try {
return await fetchDictItemsWithTypes();
} catch (error) {
console.error("Failed to fetch all dict items with types:", error);
throw error;
}
});
// 检查parent_code是否存在
ipcMain.handle("dict-check-parent-code", async (event, parentCode) => {
try {
return await checkDictParentCode(parentCode);
} catch (error) {
console.error("检查parent_code失败:", error);
throw error;
}
});
// 检查是否有子引用
ipcMain.handle("dict-check-child-references", async (event, itemCode) => {
try {
return await checkDictChildReferences(itemCode);
} catch (error) {
console.error("检查子引用失败:", error);
throw error;
}
});
}
// 导出使用CommonJS格式
module.exports = {
fetchDictItemsWithTypes,
fetchDictTypes,
createDictType,
createDictItem,
fetchDictTypeById,
fetchDictItemById,
fetchDictItemsByTypeCode,
modifyDictType,
modifyDictItem,
removeDictType,
removeDictItem,
checkDictParentCode,
checkDictChildReferences,
initDictIpc
};

View File

@ -0,0 +1,407 @@
const {
addQuestion,
getAllQuestions,
getQuestionById,
updateQuestion,
deleteQuestion,
getAllQuestionsWithRelations,
updateQuestionDescription,
addChoiceQuestion,
updateChoiceQuestion,
addFillBlankQuestion,
updateFillBlankQuestion,
deleteFillBlankQuestion,
getFillBlankQuestionsByQuestionId,
getQuestionsCountAndScore
} = require('../db/question.js');
// 导入configService中的increaseQuestionBankVersion方法
const {
increaseQuestionBankVersion
} = require('./configService.js');
/**
* 服务层添加题干
* @param {Object} questionData - 题干数据
* @returns {Promise<number>} 新创建的题干ID
*/
async function createQuestion(questionData) {
try {
const result = await addQuestion(questionData);
// 调用增加题库版本号的方法
await increaseQuestionBankVersion();
return result;
} catch (error) {
console.error('服务层: 添加题干失败', error);
throw error;
}
}
/**
* 服务层获取所有题干
* @returns {Promise<Array>} 题干列表
*/
async function fetchAllQuestions() {
try {
return await getAllQuestions();
} catch (error) {
console.error('服务层: 获取所有题干失败', error);
throw error;
}
}
/**
* 服务层获取所有题干及其关联信息
* @returns {Promise<Array>} 包含关联信息的题干列表
*/
async function fetchAllQuestionsWithRelations() {
try {
return await getAllQuestionsWithRelations();
} catch (error) {
console.error('服务层: 获取所有题干及其关联信息失败', error);
throw error;
}
}
/**
* 服务层根据ID获取题干
* @param {number} id - 题干ID
* @returns {Promise<Object|null>} 题干详情
*/
async function fetchQuestionById(id) {
try {
return await getQuestionById(id);
} catch (error) {
console.error('服务层: 根据ID获取题干失败', error);
throw error;
}
}
/**
* 服务层更新题干
* @param {number} id - 题干ID
* @param {Object} questionData - 题干数据
* @returns {Promise<boolean>} 是否更新成功
*/
async function modifyQuestion(id, questionData) {
try {
const result = await updateQuestion(id, questionData);
// 调用增加题库版本号的方法
await increaseQuestionBankVersion();
return result;
} catch (error) {
console.error('服务层: 更新题干失败', error);
throw error;
}
}
/**
* 服务层更新题干描述
* @param {number} id - 题干ID
* @param {string} questionDescription - 新的题干描述
* @returns {Promise<boolean>} 是否更新成功
*/
async function modifyQuestionDescription(id, questionDescription) {
try {
const result = await updateQuestionDescription(id, questionDescription);
// 调用增加题库版本号的方法
await increaseQuestionBankVersion();
return result;
} catch (error) {
console.error('服务层: 更新题干描述失败', error);
throw error;
}
}
/**
* 服务层删除题干
* @param {number} id - 题干ID
* @returns {Promise<boolean>} 是否删除成功
*/
async function removeQuestion(id) {
try {
const result = await deleteQuestion(id);
// 调用增加题库版本号的方法
await increaseQuestionBankVersion();
return result;
} catch (error) {
console.error('服务层: 删除题干失败', error);
throw error;
}
}
/**
* 服务层添加选择题问题
* @param {Object} choiceData - 选择题数据
* @returns {Promise<number>} 新创建的选择题ID
*/
async function createChoiceQuestion(choiceData) {
try {
const result = await addChoiceQuestion(choiceData);
// 调用增加题库版本号的方法
await increaseQuestionBankVersion();
return result;
} catch (error) {
console.error('服务层: 添加选择题失败', error);
throw error;
}
}
/**
* 服务层添加选择题问题
* @param {number} id - 选择题ID
* @param {Object} choiceData - 选择题数据
* @returns {Promise<boolean>} 是否更新成功
*/
async function modifyChoiceQuestion(id, choiceData) {
try {
const result = await updateChoiceQuestion(id, choiceData);
// 调用增加题库版本号的方法
await increaseQuestionBankVersion();
return result;
} catch (error) {
console.error('服务层: 更新选择题失败', error);
throw error;
}
}
/**
* 服务层添加填空题问题
* @param {Object} fillBlankData - 填空题数据
* @returns {Promise<number>} 新创建的填空题ID
*/
async function createFillBlankQuestion(fillBlankData) {
try {
const result = await addFillBlankQuestion(fillBlankData);
// 调用增加题库版本号的方法
await increaseQuestionBankVersion();
return result;
} catch (error) {
console.error('服务层: 添加填空题失败', error);
throw error;
}
}
/**
* 服务层更新填空题问题
* @param {number} id - 填空题ID
* @param {Object} fillBlankData - 填空题数据
* @returns {Promise<boolean>} 是否更新成功
*/
async function modifyFillBlankQuestion(id, fillBlankData) {
try {
const result = await updateFillBlankQuestion(id, fillBlankData);
// 调用增加题库版本号的方法
await increaseQuestionBankVersion();
return result;
} catch (error) {
console.error('服务层: 更新填空题失败', error);
throw error;
}
}
/**
* 服务层删除填空题问题
* @param {number} id - 填空题ID
* @returns {Promise<boolean>} 是否删除成功
*/
async function removeFillBlankQuestion(id) {
try {
const result = await deleteFillBlankQuestion(id);
// 调用增加题库版本号的方法
await increaseQuestionBankVersion();
return result;
} catch (error) {
console.error('服务层: 删除填空题失败', error);
throw error;
}
}
/**
* 服务层根据题干ID查询填空题问题
* @param {number} questionId - 题干ID
* @returns {Promise<Array>} 填空题列表
*/
async function fetchFillBlankQuestionsByQuestionId(questionId) {
try {
return await getFillBlankQuestionsByQuestionId(questionId);
} catch (error) {
console.error('服务层: 根据题干ID查询填空题失败', error);
throw error;
}
}
/**
* 服务层查询试题总数和总分
* @returns {Promise<{totalQuestions: number, totalScore: number}>} 包含试题总数和总分的对象
*/
async function fetchQuestionsCountAndScore() {
try {
return await getQuestionsCountAndScore();
} catch (error) {
console.error('服务层: 查询试题总数和总分失败', error);
throw error;
}
}
/**
* 初始化试题相关IPC通信
* @param {Electron.IpcMain} ipcMain - IPC主进程实例
*/
async function initQuestionIpc(ipcMain) {
// 题干管理相关IPC
ipcMain.handle("question-create", async (event, questionData) => {
try {
return await createQuestion(questionData);
} catch (error) {
console.error("Failed to create question:", error);
throw error;
}
});
ipcMain.handle("question-fetch-all", async () => {
try {
return await fetchAllQuestions();
} catch (error) {
console.error("Failed to fetch questions:", error);
throw error;
}
});
ipcMain.handle("question-fetch-by-id", async (event, id) => {
try {
return await fetchQuestionById(id);
} catch (error) {
console.error(`Failed to fetch question by id ${id}:`, error);
throw error;
}
});
ipcMain.handle("question-update", async (event, id, questionData) => {
try {
return await modifyQuestion(id, questionData);
} catch (error) {
console.error("Failed to update question:", error);
throw error;
}
});
ipcMain.handle("question-delete", async (event, id) => {
try {
return await removeQuestion(id);
} catch (error) {
console.error(`Failed to delete question ${id}:`, error);
throw error;
}
});
// 在已有的 question 相关 IPC 处理程序区域添加
ipcMain.handle("question-fetch-all-with-relations", async (event) => {
try {
return await fetchAllQuestionsWithRelations();
} catch (error) {
console.error("Failed to fetch all questions with relations:", error);
throw error;
}
});
// 添加更新题干描述的 IPC 处理程序
ipcMain.handle("question-update-description", async (event, id, questionDescription) => {
try {
return await modifyQuestionDescription(id, questionDescription);
} catch (error) {
console.error("Failed to update question description:", error);
throw error;
}
}
);
// 添加选择题问题的IPC处理程序
ipcMain.handle('question-update-choice', async (event, id, choiceData) => {
try {
return await modifyChoiceQuestion(id, choiceData);
} catch (error) {
console.error('Failed to update choice question:', error);
throw error;
}
});
// 添加选择题问题的IPC处理程序
ipcMain.handle("question-create-choice", async (event, choiceData) => {
try {
return await createChoiceQuestion(choiceData);
} catch (error) {
console.error("Failed to create choice question:", error);
throw error;
}
});
// 添加填空题问题的IPC处理程序
ipcMain.handle('question-create-fill-blank', async (event, fillBlankData) => {
try {
return await createFillBlankQuestion(fillBlankData);
} catch (error) {
console.error('Failed to create fill blank question:', error);
throw error;
}
});
// 更新填空题问题的IPC处理程序
ipcMain.handle('question-update-fill-blank', async (event, id, fillBlankData) => {
try {
return await modifyFillBlankQuestion(id, fillBlankData);
} catch (error) {
console.error('Failed to update fill blank question:', error);
throw error;
}
});
// 删除填空题问题的IPC处理程序
ipcMain.handle('question-delete-fill-blank', async (event, id) => {
try {
return await removeFillBlankQuestion(id);
} catch (error) {
console.error(`Failed to delete fill blank question ${id}:`, error);
throw error;
}
});
// 根据题干ID查询填空题问题的IPC处理程序
ipcMain.handle('question-fetch-fill-blank-by-question-id', async (event, questionId) => {
try {
return await fetchFillBlankQuestionsByQuestionId(questionId);
} catch (error) {
console.error(`Failed to fetch fill blank questions by question id ${questionId}:`, error);
throw error;
}
});
// 注册查询试题总数和总分的IPC处理程序
ipcMain.handle('question-get-count-and-score', async () => {
try {
return await fetchQuestionsCountAndScore();
} catch (error) {
console.error('Failed to fetch questions count and score:', error);
throw error;
}
});
}
// 导出使用CommonJS格式
module.exports = {
createQuestion,
fetchAllQuestions,
fetchAllQuestionsWithRelations,
fetchQuestionById,
modifyQuestion,
modifyQuestionDescription,
removeQuestion,
createChoiceQuestion,
modifyChoiceQuestion,
createFillBlankQuestion,
modifyFillBlankQuestion,
removeFillBlankQuestion,
fetchFillBlankQuestionsByQuestionId,
fetchQuestionsCountAndScore,
initQuestionIpc
};

View File

@ -7,6 +7,46 @@ contextBridge.exposeInMainWorld('electronAPI', {
checkDatabaseInitialized: () => ipcRenderer.invoke('check-database-initialized'),
initializeDatabase: () => ipcRenderer.invoke('initialize-database'),
// 配置服务相关接口
adminLogin: (credentials) => ipcRenderer.invoke('admin-login', credentials),
systemGetConfig: () => ipcRenderer.invoke('system-get-config'),
systemUpdateConfig: (config) => ipcRenderer.invoke('system-update-config', config),
systemIncreaseQuestionBankVersion: () => ipcRenderer.invoke('system-increase-question-band-version'),
configFetchAll: () => ipcRenderer.invoke('config-fetch-all'),
configFetchById: (id) => ipcRenderer.invoke('config-fetch-by-id', id),
configSave: (key, value) => ipcRenderer.invoke('config-save', { key, value }),
configDelete: (id) => ipcRenderer.invoke('config-delete', id),
// 字典服务相关接口
dictFetchTypes: () => ipcRenderer.invoke('dict-fetch-types'),
dictFetchItemsByType: (typeCode, isActive = undefined) => ipcRenderer.invoke('dict-fetch-items-by-type', typeCode, isActive),
dictCreateType: (dictType) => ipcRenderer.invoke('dict-create-type', dictType),
dictUpdateType: (dictType) => ipcRenderer.invoke('dict-update-type', dictType),
dictDeleteType: (typeCode) => ipcRenderer.invoke('dict-delete-type', typeCode),
dictCreateItem: (dictItem) => ipcRenderer.invoke('dict-create-item', dictItem),
dictUpdateItem: (dictItem) => ipcRenderer.invoke('dict-update-item', dictItem),
dictDeleteItem: (id) => ipcRenderer.invoke('dict-delete-item', id),
dictFetchAllItemsWithTypes: () => ipcRenderer.invoke('dict-fetch-all-items-with-types'),
dictCheckParentCode: (parentCode) => ipcRenderer.invoke('dict-check-parent-code', parentCode),
dictCheckChildReferences: (itemCode) => ipcRenderer.invoke('dict-check-child-references', itemCode),
// 试题服务相关接口
questionCreate: (questionData) => ipcRenderer.invoke('question-create', questionData),
questionFetchAll: () => ipcRenderer.invoke('question-fetch-all'),
questionFetchAllWithRelations: () => ipcRenderer.invoke('question-fetch-all-with-relations'),
questionUpdateDescription: (questionData) => ipcRenderer.invoke('question-update-description', questionData),
questionAddChoice: (choiceData) => ipcRenderer.invoke('question-add-choice', choiceData),
questionUpdateChoice: (choiceData) => ipcRenderer.invoke('question-update-choice', choiceData),
questionDeleteChoice: (id) => ipcRenderer.invoke('question-delete-choice', id),
questionAddFillBlank: (fillBlankData) => ipcRenderer.invoke('question-add-fill-blank', fillBlankData),
questionUpdateFillBlank: (fillBlankData) => ipcRenderer.invoke('question-update-fill-blank', fillBlankData),
questionDeleteFillBlank: (id) => ipcRenderer.invoke('question-delete-fill-blank', id),
questionGetQuestionWithChoices: (questionId) => ipcRenderer.invoke('question-get-question-with-choices', questionId),
questionGetQuestionWithFillBlanks: (questionId) => ipcRenderer.invoke('question-get-question-with-fill-blanks', questionId),
questionRemove: (questionId) => ipcRenderer.invoke('question-remove', questionId),
questionGetStatistics: () => ipcRenderer.invoke('question-get-statistics'),
questionGetQuestionById: (questionId) => ipcRenderer.invoke('question-get-question-by-id', questionId),
// 保留原有的ipcRenderer接口确保兼容性
ipcRenderer: {
invoke: (channel, data) => ipcRenderer.invoke(channel, data)

View File

@ -130,6 +130,68 @@
</el-card>
</div>
<!-- 配置列表显示 -->
<div class="config-list-test" v-if="dbInitialized">
<h3>配置列表</h3>
<el-card class="card-test">
<template slot="header">
<div class="card-header">
<span>系统配置项</span>
<el-button type="primary" size="small" @click="fetchConfigList">刷新列表</el-button>
</div>
</template>
<div v-if="loadingConfigList" class="loading-container">
<el-row type="flex" justify="center">
<el-col :span="6">
<el-empty description="正在加载配置..." />
</el-col>
</el-row>
</div>
<div v-else-if="configList.length === 0" class="empty-container">
<el-row type="flex" justify="center">
<el-col :span="6">
<el-empty description="暂无配置项" />
</el-col>
</el-row>
</div>
<div v-else class="config-table-container">
<el-table :data="configList" stripe border style="width: 100%">
<el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column prop="key" label="配置键" min-width="180" />
<el-table-column prop="value" label="配置值" min-width="200">
<template slot-scope="scope">
<div v-if="editingConfigId === scope.row.id">
<el-input v-model="configEdit[scope.row.id]" placeholder="配置值" />
</div>
<div v-else class="config-value-display">
{{ scope.row.value }}
</div>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" min-width="250" />
<el-table-column prop="created_at" label="创建时间" width="180" align="center" />
<el-table-column prop="updated_at" label="更新时间" width="180" align="center" />
<el-table-column label="操作" width="150" align="center">
<template slot-scope="scope">
<el-button v-if="editingConfigId !== scope.row.id" type="primary" size="small" @click="startEditConfig(scope.row)">编辑</el-button>
<div v-else>
<el-button type="success" size="small" @click="saveConfig(scope.row)">保存</el-button>
<el-button size="small" @click="cancelEdit">取消</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div v-if="configListError" class="config-list-error">
<el-alert :title="`获取配置列表失败: ${configListError}`" type="error" show-icon :closable="false" />
</div>
</el-card>
</div>
<!-- Bootstrap组件测试 -->
<div class="bootstrap-test">
<h3>Bootstrap 组件测试</h3>
@ -260,7 +322,13 @@ export default {
dbInitialized: null,
dbInitResult: null,
dbInitError: null,
initializing: false
initializing: false,
//
configList: [],
loadingConfigList: false,
configListError: null,
editingConfigId: null,
configEdit: {}
}
},
methods: {
@ -336,6 +404,8 @@ export default {
//
if (result && result.success) {
this.dbInitialized = true
//
await this.fetchConfigList()
}
} else {
throw new Error('Electron API 不可用')
@ -346,12 +416,82 @@ export default {
} finally {
this.initializing = false
}
},
//
async fetchConfigList() {
try {
this.loadingConfigList = true
this.configListError = null
// window.electronAPI
if (typeof window !== 'undefined' && window.electronAPI) {
const configList = await window.electronAPI.configFetchAll()
this.configList = configList
} else {
throw new Error('Electron API 不可用')
}
} catch (err) {
this.configListError = err.message
console.error('获取配置列表失败:', err)
} finally {
this.loadingConfigList = false
}
},
//
startEditConfig(config) {
this.editingConfigId = config.id
//
this.$set(this.configEdit, config.id, config.value)
},
//
async saveConfig(config) {
try {
// window.electronAPI
if (typeof window !== 'undefined' && window.electronAPI) {
const updatedConfig = {
id: config.id,
key: config.key,
value: this.configEdit[config.id],
description: config.description
}
await window.electronAPI.configSave(updatedConfig)
//
const index = this.configList.findIndex(item => item.id === config.id)
if (index !== -1) {
this.configList[index].value = this.configEdit[config.id]
this.configList[index].updated_at = new Date().toLocaleString()
}
this.$message.success('配置保存成功')
this.cancelEdit()
} else {
throw new Error('Electron API 不可用')
}
} catch (err) {
this.$message.error(`保存失败: ${err.message}`)
console.error('保存配置失败:', err)
}
},
//
cancelEdit() {
this.editingConfigId = null
this.configEdit = {}
}
},
//
async mounted() {
await this.checkDatabaseInitialized()
//
if (this.dbInitialized) {
await this.fetchConfigList()
}
}
}
</script>
@ -460,4 +600,28 @@ pre {
.bootstrap-test .btn {
margin-bottom: 5px;
}
/* 配置列表样式 */
.config-list-test {
margin-top: 40px;
}
.loading-container,
.empty-container {
padding: 40px 0;
}
.config-table-container {
margin-top: 20px;
}
.config-list-error {
margin-top: 20px;
}
/* 配置值显示样式 */
.config-value-display {
padding: 5px 0;
word-break: break-all;
}
</style>