From 6022e8e17aebc844c8125014fdf6ba354767812a Mon Sep 17 00:00:00 2001 From: chenqiang Date: Wed, 27 Aug 2025 22:32:59 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BBconfig,=20dict,=20question?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/background/db/config.js | 143 ++++++++ src/background/db/dict.js | 259 +++++++++++++ src/background/db/question.js | 423 ++++++++++++++++++++++ src/background/main.js | 26 +- src/background/service/configService.js | 250 +++++++++++++ src/background/service/dictService.js | 332 +++++++++++++++++ src/background/service/questionService.js | 407 +++++++++++++++++++++ src/preload.js | 40 ++ src/views/Home.vue | 166 ++++++++- 9 files changed, 2040 insertions(+), 6 deletions(-) create mode 100644 src/background/db/config.js create mode 100644 src/background/db/dict.js create mode 100644 src/background/db/question.js create mode 100644 src/background/service/configService.js create mode 100644 src/background/service/dictService.js create mode 100644 src/background/service/questionService.js diff --git a/src/background/db/config.js b/src/background/db/config.js new file mode 100644 index 0000000..7275855 --- /dev/null +++ b/src/background/db/config.js @@ -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>} 配置项列表 + */ +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} + */ +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} + */ +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 +}; \ No newline at end of file diff --git a/src/background/db/dict.js b/src/background/db/dict.js new file mode 100644 index 0000000..65b41de --- /dev/null +++ b/src/background/db/dict.js @@ -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} 记录列表 + */ +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} 类型列表 + */ +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} 添加的类型 + */ +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} 添加的字典项 + */ +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} 类型数据或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} 字典项数据或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} 字典项列表 + */ +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} 是否更新成功 + */ +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} 是否更新成功 + */ +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} 是否删除成功 + */ +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} 是否删除成功 + */ +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 +}; \ No newline at end of file diff --git a/src/background/db/question.js b/src/background/db/question.js new file mode 100644 index 0000000..b1fffa2 --- /dev/null +++ b/src/background/db/question.js @@ -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} 新创建的题干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} 题干列表 + */ +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} 题干详情 + */ +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} 是否更新成功 + */ +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} 是否删除成功 + */ +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} 包含关联信息的题干列表 + */ +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} 是否更新成功 + */ +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} 新创建的选择题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} 是否更新成功 + */ +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} 新创建的填空题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} 是否更新成功 + */ +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} 是否删除成功 + */ +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} 填空题列表 + */ +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 +}; \ No newline at end of file diff --git a/src/background/main.js b/src/background/main.js index 203c717..b22e3ee 100644 --- a/src/background/main.js +++ b/src/background/main.js @@ -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(); }); diff --git a/src/background/service/configService.js b/src/background/service/configService.js new file mode 100644 index 0000000..60306f9 --- /dev/null +++ b/src/background/service/configService.js @@ -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>} + */ +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} + */ +async function saveConfig(key, value) { + try { + await setConfig(key, value); + } catch (error) { + console.error("服务层: 保存配置项失败", error); + throw error; + } +} + +/** + * 服务层:删除配置项 + * @param {number} id - 配置项ID + * @returns {Promise} + */ +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} + */ +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} + */ +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 +}; \ No newline at end of file diff --git a/src/background/service/dictService.js b/src/background/service/dictService.js new file mode 100644 index 0000000..1e76122 --- /dev/null +++ b/src/background/service/dictService.js @@ -0,0 +1,332 @@ +const { + getDictItemsWithTypes, + getDictTypes, + addDictType, + addDictItem, + getDictTypeById, + getDictItemById, + getDictItemsByTypeCode, + updateDictType, + updateDictItem, + deleteDictType, + deleteDictItem, + checkParentCodeExists, + hasChildReferences, +} = require('../db/dict.js'); + +/** + * 服务层:查询字典项及其类型 + * @returns {Promise} 字典项列表 + */ +async function fetchDictItemsWithTypes() { + try { + return await getDictItemsWithTypes(); + } catch (error) { + console.error("服务层: 查询字典项及其类型失败", error); + throw error; + } +} + +/** + * 服务层:查询字典类型列表 + * @returns {Promise} 字典类型列表 + */ +async function fetchDictTypes() { + try { + return await getDictTypes(); + } catch (error) { + console.error("服务层: 查询字典类型列表失败", error); + throw error; + } +} + +/** + * 服务层:添加字典类型 + * @param {Object} typeData 字典类型数据 + * @returns {Promise} 添加的字典类型 + */ +async function createDictType(typeData) { + try { + return await addDictType(typeData); + } catch (error) { + console.error("服务层: 添加字典类型失败", error); + throw error; + } +} + +/** + * 服务层:添加字典项 + * @param {Object} itemData 字典项数据 + * @returns {Promise} 添加的字典项 + */ +async function createDictItem(itemData) { + try { + return await addDictItem(itemData); + } catch (error) { + console.error("服务层: 添加字典项失败", error); + throw error; + } +} + +/** + * 服务层:根据ID查询字典类型 + * @param {number} id 字典类型ID + * @returns {Promise} 字典类型数据 + */ +async function fetchDictTypeById(id) { + try { + return await getDictTypeById(id); + } catch (error) { + console.error("服务层: 根据ID查询字典类型失败", error); + throw error; + } +} + +/** + * 服务层:根据ID查询字典项 + * @param {number} id 字典项ID + * @returns {Promise} 字典项数据 + */ +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} 字典项列表 + */ +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} 是否更新成功 + */ +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} 是否更新成功 + */ +async function modifyDictItem(id, itemData) { + try { + return await updateDictItem(id, itemData); + } catch (error) { + console.error("服务层: 更新字典项失败", error); + throw error; + } +} + +/** + * 服务层:删除字典类型 + * @param {number} id 字典类型ID + * @returns {Promise} 是否删除成功 + */ +async function removeDictType(id) { + try { + return await deleteDictType(id); + } catch (error) { + console.error("服务层: 删除字典类型失败", error); + throw error; + } +} + +/** + * 服务层:删除字典项 + * @param {number} id 字典项ID + * @returns {Promise} 是否删除成功 + */ +async function removeDictItem(id) { + try { + return await deleteDictItem(id); + } catch (error) { + console.error("服务层: 删除字典项失败", error); + throw error; + } +} + +/** + * 服务层:检查父级编码是否存在 + * @param {string} parentCode 父级编码 + * @returns {Promise} 是否存在 + */ +async function checkDictParentCode(parentCode) { + try { + return await checkParentCodeExists(parentCode); + } catch (error) { + console.error("服务层: 检查父级编码失败", error); + throw error; + } +} + +/** + * 服务层:检查是否有子引用 + * @param {string} itemCode 字典项编码 + * @returns {Promise} 是否有子引用 + */ +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 +}; \ No newline at end of file diff --git a/src/background/service/questionService.js b/src/background/service/questionService.js new file mode 100644 index 0000000..e0bcfc0 --- /dev/null +++ b/src/background/service/questionService.js @@ -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} 新创建的题干ID + */ +async function createQuestion(questionData) { + try { + const result = await addQuestion(questionData); + // 调用增加题库版本号的方法 + await increaseQuestionBankVersion(); + return result; + } catch (error) { + console.error('服务层: 添加题干失败', error); + throw error; + } +} + +/** + * 服务层:获取所有题干 + * @returns {Promise} 题干列表 + */ +async function fetchAllQuestions() { + try { + return await getAllQuestions(); + } catch (error) { + console.error('服务层: 获取所有题干失败', error); + throw error; + } +} + +/** + * 服务层:获取所有题干及其关联信息 + * @returns {Promise} 包含关联信息的题干列表 + */ +async function fetchAllQuestionsWithRelations() { + try { + return await getAllQuestionsWithRelations(); + } catch (error) { + console.error('服务层: 获取所有题干及其关联信息失败', error); + throw error; + } +} + +/** + * 服务层:根据ID获取题干 + * @param {number} id - 题干ID + * @returns {Promise} 题干详情 + */ +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} 是否更新成功 + */ +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} 是否更新成功 + */ +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} 是否删除成功 + */ +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} 新创建的选择题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} 是否更新成功 + */ +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} 新创建的填空题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} 是否更新成功 + */ +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} 是否删除成功 + */ +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} 填空题列表 + */ +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 +}; \ No newline at end of file diff --git a/src/preload.js b/src/preload.js index 53d03d9..640d39c 100644 --- a/src/preload.js +++ b/src/preload.js @@ -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) diff --git a/src/views/Home.vue b/src/views/Home.vue index e6f3a80..b908b7e 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -130,6 +130,68 @@ + +
+

配置列表

+ + + +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + + + + + + + + + + +
+ +
+ +
+
+
+

Bootstrap 组件测试

@@ -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() + } } } @@ -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; +}