diff --git a/README.md b/README.md index 83f398a..0dbd731 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Windows 10 64位 单机版 + 考试管理:提供考试的添加、删除、修改、查询等功能,考试信息包括考试名称、时间、各类型试题题量(各类题型的小题数)、分值设置(各类试题的小题分值)。 + 系统初始化 + 参加考试:提供考试页面,包括试题展示、答题、提交等功能。 - + 输入考生信息页:输入正确的考生姓名、准考证号才可以进入考试系统,信息来源于人员管理中维护的数据。 + + 输入考生信息页:输入正确的考生姓名、身份证号、手机号才可以进入考试系统,信息来源于人员管理中维护的数据。 + 考试确认页:随机抽题组生成本次试卷,结合考生信息、考试时间等形成唯一考试数据,展示考试说明(固定内容),提供“开始考试”入口。 + 考试页面:展示试题,支持单选题、多选题、填空题、问答题的答题功能。考试页面强制全屏,具备防切出机制,切出5次后,在第6次切出时强制结束考试。考试页面具备倒计时功能, + 单选题:支持单选项选择。 @@ -140,7 +140,6 @@ t2t6a9 > 已知的配置项: + 管理员密码(admin_password) + 题库版本号(question_bank_version) - + 考试版本号(exam_version) ##### 数据字典 @@ -309,18 +308,6 @@ t2t6a9 |created_at|TEXT|记录创建时间(默认 CURRENT_TIMESTAMP)| |updated_at|TEXT|记录更新时间(默认 CURRENT_TIMESTAMP)| -###### exam_question_set 考试题型配置表 - -|字段名|字段类型|字段描述| -|---|---|---| -|id|INTEGER PRIMARY KEY AUTOINCREMENT|主键| -|exam_id|INTEGER NOT NULL|关联到 exam.id| -|question_type|TEXT NOT NULL|问题类型(关联到 dict_types.type_code)| -|question_count|INTEGER NOT NULL DEFAULT 0|该类型问题的数量| -|question_score|INTEGER NOT NULL DEFAULT 0|该类型问题的总分数| -|created_at|TEXT|记录创建时间(默认 CURRENT_TIMESTAMP)| -|updated_at|TEXT|记录更新时间(默认 CURRENT_TIMESTAMP)| - ##### 考生 ###### examinee 考生表 @@ -330,9 +317,12 @@ t2t6a9 |id|INTEGER PRIMARY KEY AUTOINCREMENT|主键| |examinee_type|TEXT NOT NULL DEFAULT ''|考生类型(如 "student"、"teacher")| |examinee_name|TEXT NOT NULL DEFAULT ''|考生姓名| -|examinee_account|TEXT NOT NULL DEFAULT ''|考生学号/工号| -|examinee_phone|TEXT NOT NULL DEFAULT ''|考生手机号| +|examinee_gender|TEXT NOT NULL DEFAULT ''|考生性别| |examinee_unit|TEXT NOT NULL DEFAULT ''|考生单位| +|examinee_position|TEXT NOT NULL DEFAULT ''|考生职务| +|examinee_id_card|TEXT NOT NULL DEFAULT ''|考生身份证号| +|examinee_phone|TEXT NOT NULL DEFAULT ''|考生手机号| +|examinee_event|TEXT NOT NULL DEFAULT ''|考生报名赛项| |created_at|TEXT|记录创建时间(默认 CURRENT_TIMESTAMP)| |updated_at|TEXT|记录更新时间(默认 CURRENT_TIMESTAMP)| diff --git a/electron/db/exam.js b/electron/db/exam.js new file mode 100644 index 0000000..344f0d5 --- /dev/null +++ b/electron/db/exam.js @@ -0,0 +1,127 @@ +import { getSystemDbPath } from './path.js'; +import { executeWithRetry } from './utils.js'; +import { openDatabase } from './utils.js'; + +/** + * 查询所有考试 + * @returns {Promise} 考试列表 + */ +export async function getAllExams() { + const db = await openDatabase(getSystemDbPath()); + return executeWithRetry(db, async () => { + const sql = 'SELECT * FROM exam ORDER BY created_at DESC'; + return await db.allAsync(sql); + }); +} + +/** + * 根据ID查询考试 + * @param {number} id 考试ID + * @returns {Promise} 考试数据或null + */ +export async function getExamById(id) { + const db = await openDatabase(getSystemDbPath()); + return executeWithRetry(db, async () => { + const sql = 'SELECT * FROM exam WHERE id = ?'; + return await db.getAsync(sql, [id]); + }); +} + +/** + * 查询ID最大的考试记录 + * @returns {Promise} 考试数据或null + */ +export async function getLastExam() { + const db = await openDatabase(getSystemDbPath()); + return executeWithRetry(db, async () => { + const sql = 'SELECT * FROM exam ORDER BY id DESC LIMIT 1'; + return await db.getAsync(sql); + }); +} + +/** + * 添加考试 + * @param {Object} examData 考试数据 + * @returns {Promise} 添加的考试 + */ +export async function createExam(examData) { + const { exam_name, exam_description, exam_examinee_type, exam_notice, exam_minutes, exam_minutes_min } = examData; + const db = await openDatabase(getSystemDbPath()); + return executeWithRetry(db, async () => { + const sql = 'INSERT INTO exam (exam_name, exam_description, exam_examinee_type, exam_notice, exam_minutes, exam_minutes_min) VALUES (?, ?, ?, ?, ?, ?)'; + const result = await db.runAsync(sql, [ + exam_name, + exam_description || '', + exam_examinee_type, + exam_notice || '', + exam_minutes, + exam_minutes_min || 0 + ]); + + // 检查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, ...examData }; + }); +} + +/** + * 更新考试 + * @param {number} id 考试ID + * @param {Object} examData 更新的数据 + * @returns {Promise} 是否更新成功 + */ +export async function updateExam(id, examData) { + const { exam_name, exam_description, exam_examinee_type, exam_notice, exam_minutes, exam_minutes_min } = examData; + const db = await openDatabase(getSystemDbPath()); + return executeWithRetry(db, async () => { + const sql = 'UPDATE exam SET exam_name = ?, exam_description = ?, exam_examinee_type = ?, exam_notice = ?, exam_minutes = ?, exam_minutes_min = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?'; + const result = await db.runAsync(sql, [ + exam_name, + exam_description || '', + exam_examinee_type, + exam_notice || '', + exam_minutes, + exam_minutes_min || 0, + id + ]); + + // 检查result是否存在以及是否有changes属性 + if (!result || result.changes === undefined) { + return false; + } + + return result.changes > 0; + }); +} + +/** + * 删除考试 + * @param {number} id 考试ID + * @returns {Promise} 是否删除成功 + */ +export async function deleteExam(id) { + const db = await openDatabase(getSystemDbPath()); + return executeWithRetry(db, async () => { + const sql = 'DELETE FROM exam WHERE id = ?'; + const result = await db.runAsync(sql, [id]); + + // 检查result是否存在以及是否有changes属性 + if (!result || result.changes === undefined) { + return false; + } + + return result.changes > 0; + }); +} \ No newline at end of file diff --git a/electron/db/examinee.js b/electron/db/examinee.js new file mode 100644 index 0000000..fec4c2b --- /dev/null +++ b/electron/db/examinee.js @@ -0,0 +1,180 @@ +import { getSystemDbPath } from './path.js'; +import { executeWithRetry } from './utils.js'; +import { getDbConnection } from './index.js'; + +/** + * 查询所有考生列表 + * @returns {Promise} 考生列表 + */ +export async function getAllExaminees() { + const db = await getDbConnection(getSystemDbPath()); + return executeWithRetry(db, async () => { + const sql = 'SELECT * FROM examinee ORDER BY created_at DESC'; + return await db.allAsync(sql); + }); +} + +/** + * 根据ID查询考生 + * @param {number} id 考生ID + * @returns {Promise} 考生数据或null + */ +export async function getExamineeById(id) { + const db = await getDbConnection(getSystemDbPath()); + return executeWithRetry(db, async () => { + const sql = 'SELECT * FROM examinee WHERE id = ?'; + return await db.getAsync(sql, [id]); + }); +} + +/** + * 根据身份证号和准考证号查询考生 + * @param {string} idCard 身份证号 + * @param {string} admissionTicket 准考证号 + * @returns {Promise} 考生数据或null + */ +export async function getExamineeByIdCardAndAdmissionTicket(idCard, admissionTicket) { + const db = await getDbConnection(getSystemDbPath()); + return executeWithRetry(db, async () => { + const sql = 'SELECT * FROM examinee WHERE examinee_id_card = ? AND examinee_admission_ticket = ?'; + return await db.getAsync(sql, [idCard, admissionTicket]); + }); +} + +/** + * 添加考生 + * @param {Object} examineeData 考生数据 + * @returns {Promise} 添加的考生 + */ +export async function createExaminee(examineeData) { + const { + examinee_name, + examinee_gender, + examinee_unit, + written_exam_room, + written_exam_seat, + computer_exam_room, + computer_exam_seat, + examinee_id_card, + examinee_admission_ticket + } = examineeData; + + const db = await getDbConnection(getSystemDbPath()); + return executeWithRetry(db, async () => { + const sql = `INSERT INTO examinee ( + examinee_name, + examinee_gender, + examinee_unit, + written_exam_room, + written_exam_seat, + computer_exam_room, + computer_exam_seat, + examinee_id_card, + examinee_admission_ticket + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`; + + const result = await db.runAsync(sql, [ + examinee_name, + examinee_gender || '', + examinee_unit || '', + written_exam_room || '', + written_exam_seat || '', + computer_exam_room || '', + computer_exam_seat || '', + examinee_id_card || '', + examinee_admission_ticket || '' + ]); + + // 检查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, ...examineeData }; + }); +} + +/** + * 更新考生 + * @param {number} id 考生ID + * @param {Object} examineeData 更新的数据 + * @returns {Promise} 是否更新成功 + */ +export async function updateExaminee(id, examineeData) { + const { + examinee_name, + examinee_gender, + examinee_unit, + written_exam_room, + written_exam_seat, + computer_exam_room, + computer_exam_seat, + examinee_id_card, + examinee_admission_ticket + } = examineeData; + + const db = await getDbConnection(getSystemDbPath()); + return executeWithRetry(db, async () => { + const sql = `UPDATE examinee SET + examinee_name = ?, + examinee_gender = ?, + examinee_unit = ?, + written_exam_room = ?, + written_exam_seat = ?, + computer_exam_room = ?, + computer_exam_seat = ?, + examinee_id_card = ?, + examinee_admission_ticket = ?, + updated_at = CURRENT_TIMESTAMP + WHERE id = ?`; + + const result = await db.runAsync(sql, [ + examinee_name, + examinee_gender || '', + examinee_unit || '', + written_exam_room || '', + written_exam_seat || '', + computer_exam_room || '', + computer_exam_seat || '', + examinee_id_card || '', + examinee_admission_ticket || '', + id + ]); + + // 检查result是否存在以及是否有changes属性 + if (!result || result.changes === undefined) { + return false; + } + + return result.changes > 0; + }); +} + +/** + * 删除考生 + * @param {number} id 考生ID + * @returns {Promise} 是否删除成功 + */ +export async function deleteExaminee(id) { + const db = await getDbConnection(getSystemDbPath()); + return executeWithRetry(db, async () => { + const sql = 'DELETE FROM examinee WHERE id = ?'; + const result = await db.runAsync(sql, [id]); + + // 检查result是否存在以及是否有changes属性 + if (!result || result.changes === undefined) { + return false; + } + + return result.changes > 0; + }); +} diff --git a/electron/db/index.js b/electron/db/index.js index f612a61..3554f0b 100644 --- a/electron/db/index.js +++ b/electron/db/index.js @@ -103,9 +103,6 @@ async function initializeSystemDatabase() { await systemDb.execAsync(systemSchema.exam.trim()); console.log('创建 exam 表成功'); - await systemDb.execAsync(systemSchema.examQuestionSet.trim()); - console.log('创建 exam_question_set 表成功'); - await systemDb.execAsync(systemSchema.examinee.trim()); console.log('创建 examinee 表成功'); diff --git a/electron/db/question.js b/electron/db/question.js index 8303502..af8f9d7 100644 --- a/electron/db/question.js +++ b/electron/db/question.js @@ -262,18 +262,114 @@ async function updateQuestionDescription(id, questionDescription) { * @returns {Promise} 新创建的选择题ID */ async function addChoiceQuestion(choiceData) { - const { question_id, choice_description, choice_type, choice_options, correct_answers } = 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, choice_options, correct_answers, created_at, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)', - [question_id, choice_description, choice_type, JSON.stringify(choice_options), JSON.stringify(correct_answers)] + '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; + }); +} + export { addQuestion, getAllQuestions, @@ -282,5 +378,10 @@ export { deleteQuestion, getAllQuestionsWithRelations, updateQuestionDescription, - addChoiceQuestion // 添加新函数导出 + addChoiceQuestion, + updateChoiceQuestion, + addFillBlankQuestion, + updateFillBlankQuestion, + deleteFillBlankQuestion, + getFillBlankQuestionsByQuestionId }; \ No newline at end of file diff --git a/electron/db/schema.js b/electron/db/schema.js index a566182..d93d7fe 100644 --- a/electron/db/schema.js +++ b/electron/db/schema.js @@ -1,8 +1,7 @@ - // 辅助函数:将db.run包装为Promise const runAsync = (db, sql, params = []) => { return new Promise((resolve, reject) => { - db.run(sql, params, function(err) { + db.run(sql, params, function (err) { if (err) reject(err); else resolve(this); }); @@ -13,7 +12,7 @@ export { runAsync }; // 系统数据库表结构 const systemSchema = { - config: ` + config: ` CREATE TABLE IF NOT EXISTS config ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL, @@ -21,7 +20,7 @@ const systemSchema = { protected INTEGER DEFAULT 0 ); `, - dictTypes: ` + dictTypes: ` CREATE TABLE IF NOT EXISTS dict_types ( id INTEGER PRIMARY KEY AUTOINCREMENT, type_code TEXT NOT NULL UNIQUE, @@ -30,7 +29,7 @@ const systemSchema = { created_at TEXT DEFAULT CURRENT_TIMESTAMP ); `, - dictItems: ` + dictItems: ` CREATE TABLE IF NOT EXISTS dict_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, type_code TEXT NOT NULL, @@ -44,7 +43,7 @@ const systemSchema = { FOREIGN KEY (type_code) REFERENCES dict_types(type_code) ); `, - questions: ` + questions: ` CREATE TABLE IF NOT EXISTS questions ( id INTEGER PRIMARY KEY AUTOINCREMENT, question_type TEXT NOT NULL, @@ -55,7 +54,7 @@ const systemSchema = { FOREIGN KEY (question_type) REFERENCES dict_types(type_code) ); `, - questionDatasets: ` + questionDatasets: ` CREATE TABLE IF NOT EXISTS question_datasets ( id INTEGER PRIMARY KEY AUTOINCREMENT, question_id INTEGER NOT NULL, @@ -66,7 +65,7 @@ const systemSchema = { FOREIGN KEY (question_id) REFERENCES questions(id) ); `, - questionImages: ` + questionImages: ` CREATE TABLE IF NOT EXISTS question_images ( id INTEGER PRIMARY KEY AUTOINCREMENT, question_id INTEGER NOT NULL, @@ -77,19 +76,20 @@ const systemSchema = { FOREIGN KEY (question_id) REFERENCES questions(id) ); `, - questionFillTable: ` + questionFillTable: ` CREATE TABLE IF NOT EXISTS question_fill_table ( id INTEGER PRIMARY KEY AUTOINCREMENT, question_id INTEGER NOT NULL, table_name TEXT NOT NULL DEFAULT '', table_data TEXT NOT NULL, table_description TEXT NOT NULL DEFAULT '', + score REAL, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (question_id) REFERENCES questions(id) ); `, - questionFillTableBlanks: ` + questionFillTableBlanks: ` CREATE TABLE IF NOT EXISTS question_fill_table_blanks ( id INTEGER PRIMARY KEY AUTOINCREMENT, question_id INTEGER NOT NULL, @@ -103,7 +103,7 @@ const systemSchema = { FOREIGN KEY (table_id) REFERENCES question_fill_table(id) ); `, - questionChoices: ` + questionChoices: ` CREATE TABLE IF NOT EXISTS question_choices ( id INTEGER PRIMARY KEY AUTOINCREMENT, question_id INTEGER NOT NULL, @@ -111,24 +111,26 @@ const systemSchema = { choice_type TEXT NOT NULL DEFAULT 'single', choice_options TEXT NOT NULL, correct_answers TEXT NOT NULL, + score REAL, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (question_id) REFERENCES questions(id) ); `, - questionFillBlanks: ` + questionFillBlanks: ` CREATE TABLE IF NOT EXISTS question_fill_blanks ( id INTEGER PRIMARY KEY AUTOINCREMENT, question_id INTEGER NOT NULL, blank_description TEXT NOT NULL DEFAULT '', blank_count INTEGER NOT NULL DEFAULT 0, correct_answers TEXT NOT NULL DEFAULT '', + score REAL, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (question_id) REFERENCES questions(id) ); `, - questionJudge: ` + questionJudge: ` CREATE TABLE IF NOT EXISTS question_judge ( id INTEGER PRIMARY KEY AUTOINCREMENT, question_id INTEGER NOT NULL, @@ -139,7 +141,7 @@ const systemSchema = { FOREIGN KEY (question_id) REFERENCES questions(id) ); `, - questionShort: ` + questionShort: ` CREATE TABLE IF NOT EXISTS question_short ( id INTEGER PRIMARY KEY AUTOINCREMENT, question_id INTEGER NOT NULL, @@ -150,7 +152,7 @@ const systemSchema = { FOREIGN KEY (question_id) REFERENCES questions(id) ); `, - exam: ` + exam: ` CREATE TABLE IF NOT EXISTS exam ( id INTEGER PRIMARY KEY AUTOINCREMENT, exam_name TEXT NOT NULL DEFAULT '', @@ -158,40 +160,32 @@ const systemSchema = { exam_examinee_type TEXT NOT NULL DEFAULT '', exam_notice TEXT NOT NULL DEFAULT '', exam_minutes INTEGER NOT NULL DEFAULT 0, + exam_minutes_min INTEGER NOT NULL DEFAULT 0, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ); `, - examQuestionSet: ` - CREATE TABLE IF NOT EXISTS exam_question_set ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - exam_id INTEGER NOT NULL, - question_type TEXT NOT NULL, - question_count INTEGER NOT NULL DEFAULT 0, - question_score INTEGER NOT NULL DEFAULT 0, - created_at TEXT DEFAULT CURRENT_TIMESTAMP, - updated_at TEXT DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (exam_id) REFERENCES exam(id), - FOREIGN KEY (question_type) REFERENCES dict_types(type_code) - ); - `, - examinee: ` + examinee: ` CREATE TABLE IF NOT EXISTS examinee ( id INTEGER PRIMARY KEY AUTOINCREMENT, - examinee_type TEXT NOT NULL DEFAULT '', examinee_name TEXT NOT NULL DEFAULT '', - examinee_account TEXT NOT NULL DEFAULT '', - examinee_phone TEXT NOT NULL DEFAULT '', + examinee_gender TEXT NOT NULL DEFAULT '', examinee_unit TEXT NOT NULL DEFAULT '', + written_exam_room TEXT NOT NULL DEFAULT '', + written_exam_seat TEXT NOT NULL DEFAULT '', + computer_exam_room TEXT NOT NULL DEFAULT '', + computer_exam_seat TEXT NOT NULL DEFAULT '', + examinee_id_card TEXT NOT NULL DEFAULT '', + examinee_admission_ticket TEXT NOT NULL DEFAULT '', created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ); - ` + `, }; // 用户数据库表结构 const userSchema = { - examineeLog: ` + examineeLog: ` CREATE TABLE IF NOT EXISTS examinee_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, examinee_id INTEGER NOT NULL, @@ -200,7 +194,7 @@ const userSchema = { created_at TEXT DEFAULT CURRENT_TIMESTAMP ); `, - examineeExam: ` + examineeExam: ` CREATE TABLE IF NOT EXISTS examinee_exam ( id INTEGER PRIMARY KEY AUTOINCREMENT, examinee_id INTEGER NOT NULL, @@ -217,7 +211,7 @@ const userSchema = { updated_at TEXT DEFAULT CURRENT_TIMESTAMP ); `, - examineePapers: ` + examineePapers: ` CREATE TABLE IF NOT EXISTS examinee_papers ( id INTEGER PRIMARY KEY AUTOINCREMENT, examinee_id INTEGER NOT NULL, @@ -227,7 +221,7 @@ const userSchema = { created_at TEXT DEFAULT CURRENT_TIMESTAMP ); `, - paperQuestions: ` + paperQuestions: ` CREATE TABLE IF NOT EXISTS paper_questions ( id INTEGER PRIMARY KEY AUTOINCREMENT, examinee_paper_id INTEGER NOT NULL, @@ -238,7 +232,7 @@ const userSchema = { created_at TEXT DEFAULT CURRENT_TIMESTAMP ); `, - paperQuestionChoices: ` + paperQuestionChoices: ` CREATE TABLE IF NOT EXISTS paper_question_choices ( id INTEGER PRIMARY KEY AUTOINCREMENT, paper_question_id INTEGER NOT NULL, @@ -249,7 +243,7 @@ const userSchema = { FOREIGN KEY (paper_question_id) REFERENCES paper_questions(id) ); `, - paperQuestionBlanks: ` + paperQuestionBlanks: ` CREATE TABLE IF NOT EXISTS paper_question_blanks ( id INTEGER PRIMARY KEY AUTOINCREMENT, paper_question_id INTEGER NOT NULL, @@ -260,7 +254,7 @@ const userSchema = { FOREIGN KEY (paper_question_id) REFERENCES paper_questions(id) ); `, - paperQuestionJudge: ` + paperQuestionJudge: ` CREATE TABLE IF NOT EXISTS paper_question_judge ( id INTEGER PRIMARY KEY AUTOINCREMENT, paper_question_id INTEGER NOT NULL, @@ -271,7 +265,7 @@ const userSchema = { FOREIGN KEY (paper_question_id) REFERENCES paper_questions(id) ); `, - paperQuestionFillTable: ` + paperQuestionFillTable: ` CREATE TABLE IF NOT EXISTS paper_question_fill_table ( id INTEGER PRIMARY KEY AUTOINCREMENT, paper_question_id INTEGER NOT NULL, @@ -282,7 +276,7 @@ const userSchema = { FOREIGN KEY (paper_question_id) REFERENCES paper_questions(id) ); `, - paperQuestionFillTableBlanks: ` + paperQuestionFillTableBlanks: ` CREATE TABLE IF NOT EXISTS paper_question_fill_table_blanks ( id INTEGER PRIMARY KEY AUTOINCREMENT, paper_question_fill_table_id INTEGER NOT NULL, @@ -294,7 +288,7 @@ const userSchema = { FOREIGN KEY (paper_question_fill_table_id) REFERENCES paper_question_fill_table(id) ); `, - paperQuestionSubjective: ` + paperQuestionSubjective: ` CREATE TABLE IF NOT EXISTS paper_question_subjective ( id INTEGER PRIMARY KEY AUTOINCREMENT, paper_question_id INTEGER NOT NULL, @@ -307,50 +301,135 @@ const userSchema = { updated_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (paper_question_id) REFERENCES paper_questions(id) ); - ` + `, }; // 初始化默认数据 // 系统配置默认数据 -const plainPassword = 't2t6a9'; // 明文密码变量定义 +const plainPassword = "t2t6a9"; // 明文密码变量定义 // 注意:在实际初始化数据库时,需要使用argon2对plainPassword进行哈希 // 这里只定义默认数据结构,哈希操作应在index.js中的初始化函数中完成 const defaultData = { - config: [ - { key: 'admin_password', value: plainPassword, protected: 1 }, - { key: 'question_bank_version', value: '1', protected: 1 }, - { key: 'exam_version', value: '1', protected: 1 }, - { key: 'initialized', value: '1', protected: 1 } - ], - // 字典类型默认数据 - dictTypes: [ - { type_code: 'question_category', type_name: '题型分类', description: '用于区分客观题和主观题' }, - { type_code: 'question_type', type_name: '题型', description: '存储所有题型(选择题、填空题等)' }, - { type_code: 'user_role', type_name: '用户角色', description: '区分不同用户类型' } - ], - // 字典项默认数据 - dictItems: [ - // 题型分类 - { type_code: 'question_category', item_code: 'objective', item_name: '客观题', item_description: '有固定答案,机器可自动评分', is_active: 1, parent_code: null }, - { type_code: 'question_category', item_code: 'subjective', item_name: '主观题', item_description: '需人工评分,答案不唯一', is_active: 1, parent_code: null }, - // 题型 - { type_code: 'question_type', item_code: 'choice', item_name: '选择题', item_description: '包含单选和多选', is_active: 1, parent_code: 'objective' }, - { type_code: 'question_type', item_code: 'fill_blank', item_name: '填空题', item_description: '填写空白处的答案', is_active: 0, parent_code: 'objective' }, - { type_code: 'question_type', item_code: 'fill_table', item_name: '填表题', item_description: '填写表格内容', is_active: 0, parent_code: 'objective' }, - { type_code: 'question_type', item_code: 'true_false', item_name: '判断题', item_description: '判断对错', is_active: 0, parent_code: 'objective' }, - { type_code: 'question_type', item_code: 'short_answer', item_name: '问答题', item_description: '简短回答问题', is_active: 0, parent_code: 'subjective' }, - { type_code: 'question_type', item_code: 'analysis', item_name: '分析题', item_description: '需要分析问题', is_active: 0, parent_code: 'subjective' }, - { type_code: 'question_type', item_code: 'essay', item_name: '论述题', item_description: '详细论述', is_active: 0, parent_code: 'subjective' }, - // 用户角色 - { type_code: 'user_role', item_code: 'admin', item_name: '管理员', item_description: '系统管理员', is_active: 1, parent_code: null }, - { type_code: 'user_role', item_code: 'student', item_name: '考生', item_description: '参加考试的用户', is_active: 1, parent_code: null } - ] + config: [ + { key: "admin_password", value: plainPassword, protected: 1 }, + { key: "question_bank_version", value: "1", protected: 1 }, + { key: "exam_version", value: "1", protected: 1 }, + { key: "initialized", value: "1", protected: 1 }, + ], + // 字典类型默认数据 + dictTypes: [ + { + type_code: "question_category", + type_name: "题型分类", + description: "用于区分客观题和主观题", + }, + { + type_code: "question_type", + type_name: "题型", + description: "存储所有题型(选择题、填空题等)", + }, + { + type_code: "user_role", + type_name: "用户角色", + description: "区分不同用户类型", + }, + ], + // 字典项默认数据 + dictItems: [ + // 题型分类 + { + type_code: "question_category", + item_code: "objective", + item_name: "客观题", + item_description: "有固定答案,机器可自动评分", + is_active: 1, + parent_code: null, + }, + { + type_code: "question_category", + item_code: "subjective", + item_name: "主观题", + item_description: "需人工评分,答案不唯一", + is_active: 1, + parent_code: null, + }, + // 题型 + { + type_code: "question_type", + item_code: "choice", + item_name: "选择题", + item_description: "包含单选和多选", + is_active: 1, + parent_code: "objective", + }, + { + type_code: "question_type", + item_code: "fill_blank", + item_name: "填空题", + item_description: "填写空白处的答案", + is_active: 0, + parent_code: "objective", + }, + { + type_code: "question_type", + item_code: "fill_table", + item_name: "填表题", + item_description: "填写表格内容", + is_active: 0, + parent_code: "objective", + }, + { + type_code: "question_type", + item_code: "true_false", + item_name: "判断题", + item_description: "判断对错", + is_active: 0, + parent_code: "objective", + }, + { + type_code: "question_type", + item_code: "short_answer", + item_name: "问答题", + item_description: "简短回答问题", + is_active: 0, + parent_code: "subjective", + }, + { + type_code: "question_type", + item_code: "analysis", + item_name: "分析题", + item_description: "需要分析问题", + is_active: 0, + parent_code: "subjective", + }, + { + type_code: "question_type", + item_code: "essay", + item_name: "论述题", + item_description: "详细论述", + is_active: 0, + parent_code: "subjective", + }, + // 用户角色 + { + type_code: "user_role", + item_code: "admin", + item_name: "管理员", + item_description: "系统管理员", + is_active: 1, + parent_code: null, + }, + { + type_code: "user_role", + item_code: "student", + item_name: "考生", + item_description: "参加考试的用户", + is_active: 1, + parent_code: null, + }, + ], }; // 导出对象 -export { - systemSchema, - userSchema, - defaultData -}; \ No newline at end of file +export { systemSchema, userSchema, defaultData }; diff --git a/electron/main.js b/electron/main.js index 27ddc43..328607d 100644 --- a/electron/main.js +++ b/electron/main.js @@ -1,8 +1,8 @@ // 确保所有导入都使用 ES 模块语法 -import { app, BrowserWindow, ipcMain } from 'electron'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { checkDatabaseInitialized, initializeDatabase } from './db/index.js'; +import { app, BrowserWindow, ipcMain } from "electron"; +import path from "path"; +import { fileURLToPath } from "url"; +import { checkDatabaseInitialized, initializeDatabase } from "./db/index.js"; // 导入配置项服务 import { fetchAllConfigs, @@ -12,8 +12,8 @@ import { getSystemConfig, updateSystemConfig, increaseQuestionBankVersion, - initAuthIpc -} from './service/configService.js'; + initAuthIpc, +} from "./service/configService.js"; // 导入字典服务 - 使用实际导出的函数名称 import { fetchDictTypes, @@ -25,9 +25,9 @@ import { modifyDictItem, removeDictItem, fetchDictItemsWithTypes, - checkDictParentCode, // 添加这一行 - checkDictChildReferences // 添加这一行 -} from './service/dictService.js'; + checkDictParentCode, // 添加这一行 + checkDictChildReferences, // 添加这一行 +} from "./service/dictService.js"; // 导入题干服务 import { createQuestion, @@ -37,8 +37,32 @@ import { removeQuestion, fetchAllQuestionsWithRelations, modifyQuestionDescription, - createChoiceQuestion // 添加新函数导入 + createChoiceQuestion, + modifyChoiceQuestion, + createFillBlankQuestion, + modifyFillBlankQuestion, + removeFillBlankQuestion, + fetchFillBlankQuestionsByQuestionId } from './service/questionService.js'; +import { + createNewExam, + fetchAllExams, + fetchExamById, + modifyExam, + removeExam, + fetchLastExam, // 添加这一行 +} from "./service/examService.js"; + +// 添加考生服务导入 +// 在文件开头的导入语句中添加新函数 +import { + fetchAllExaminees, + fetchExamineeById, + createExamineeService, + updateExamineeService, + deleteExamineeService, + fetchExamineeByIdCardAndAdmissionTicket, +} from "./service/examineeService.js"; // 定义 __dirname 和 __filename const __filename = fileURLToPath(import.meta.url); @@ -53,7 +77,7 @@ if (!gotTheLock) { app.quit(); } else { // 设置第二个实例启动时的处理 - app.on('second-instance', (event, commandLine, workingDirectory) => { + app.on("second-instance", (event, commandLine, workingDirectory) => { // 当用户尝试启动第二个实例时,聚焦到已有的主窗口 const mainWindow = BrowserWindow.getAllWindows()[0]; if (mainWindow) { @@ -75,111 +99,127 @@ function createWindow() { mainWindow = new BrowserWindow({ fullscreen: true, webPreferences: { - preload: path.join(__dirname, 'preload.js'), + preload: path.join(__dirname, "../electron/preload.js"), nodeIntegration: false, - contextIsolation: true - } - }) + contextIsolation: true, + }, + }); - if (process.env.NODE_ENV === 'development') { - mainWindow.loadURL('http://localhost:5173/') - mainWindow.webContents.openDevTools() + if (process.env.NODE_ENV === "development") { + mainWindow.loadURL("http://localhost:5173/"); + mainWindow.webContents.openDevTools(); } else { - mainWindow.loadFile(path.join(__dirname, '../dist/index.html')) + mainWindow.loadFile(path.join(__dirname, "../dist/index.html")); } // 当窗口关闭时,清空引用 - mainWindow.on('closed', () => { + mainWindow.on("closed", () => { mainWindow = null; }); } // Initalize app app.whenReady().then(() => { - setupApp() - createWindow() - setupIpcMain() -}) + setupApp(); + createWindow(); + setupIpcMain(); +}); -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit() +app.on("window-all-closed", () => { + if (process.platform !== "darwin") { + app.quit(); } -}) +}); -app.on('activate', () => { +app.on("activate", () => { // 只有当没有窗口时才创建新窗口 if (BrowserWindow.getAllWindows().length === 0) { - createWindow() + createWindow(); } -}) +}); // Check database initialization status async function setupApp() { - try { - console.log('应用启动 - 检查数据库初始化状态...'); + try { + console.log("应用启动 - 检查数据库初始化状态..."); - // 使用全局变量防止重复初始化检查 - if (global.dbInitCheck) { - console.log('数据库初始化检查已完成'); - return; - } - global.dbInitCheck = true; - - const isInitialized = await checkDatabaseInitialized(); - console.log('数据库初始化状态:', isInitialized); - - // 只检查状态,不自动初始化 - } catch (error) { - console.error('数据库检查过程中出错:', error); + // 使用全局变量防止重复初始化检查 + if (global.dbInitCheck) { + console.log("数据库初始化检查已完成"); + return; } + global.dbInitCheck = true; + + const isInitialized = await checkDatabaseInitialized(); + console.log("数据库初始化状态:", isInitialized); + + // 只检查状态,不自动初始化 + } catch (error) { + console.error("数据库检查过程中出错:", error); + } } -// Setup IPC communication function setupIpcMain() { + // 考生登录IPC + ipcMain.handle("user-login", async (event, {idCard, admissionTicket}) => { + // return {data: 'hello world'}; + try { + const examinee = await fetchExamineeByIdCardAndAdmissionTicket(idCard, admissionTicket); + console.log(examinee); + // const examinee = 'hello world'; + if (examinee) { + return { success: true, data: examinee }; + } else { + return { success: false, error: "未找到匹配的考生信息" }; + } + } catch (error) { + console.error("Failed to login examinee:", error); + return { success: false, error: error.message }; + } + }); // 数据库相关 - ipcMain.handle('check-database-initialized', async () => { + ipcMain.handle("check-database-initialized", async () => { try { return await checkDatabaseInitialized(); } catch (error) { - console.error('Failed to check database initialization:', error); + console.error("Failed to check database initialization:", error); return false; } }); - ipcMain.handle('initialize-database', async () => { - try { - return await initializeDatabase(); - } catch (error) { - console.error('Failed to initialize database:', error); - return false; - } + ipcMain.handle("initialize-database", async () => { + try { + return await initializeDatabase(); + } catch (error) { + console.error("Failed to initialize database:", error); + return false; + } }); // 系统相关 - ipcMain.handle('system-get-config', async () => { + ipcMain.handle("system-get-config", async () => { try { return await getSystemConfig(); } catch (error) { - console.error('Failed to get system config:', error); + console.error("Failed to get system config:", error); return null; } }); - ipcMain.handle('system-update-config', async (event, config) => { + ipcMain.handle("system-update-config", async (event, config) => { try { return await updateSystemConfig(config); } catch (error) { - console.error('Failed to update system config:', error); + console.error("Failed to update system config:", error); return false; } }); - ipcMain.handle('system-increase-question-band-version', async () => { + ipcMain.handle("system-increase-question-band-version", async () => { try { return await increaseQuestionBankVersion(); } catch (error) { - console.error('Failed to increase question band version:', error); + console.error("Failed to increase question band version:", error); return false; } }); @@ -188,16 +228,16 @@ function setupIpcMain() { initAuthIpc(ipcMain); // 配置项管理相关IPC - ipcMain.handle('config-fetch-all', async () => { + ipcMain.handle("config-fetch-all", async () => { try { return await fetchAllConfigs(); } catch (error) { - console.error('Failed to fetch all configs:', error); + console.error("Failed to fetch all configs:", error); throw error; } }); - ipcMain.handle('config-fetch-by-id', async (event, id) => { + ipcMain.handle("config-fetch-by-id", async (event, id) => { try { return await fetchConfigById(id); } catch (error) { @@ -206,7 +246,7 @@ function setupIpcMain() { } }); - ipcMain.handle('config-save', async (event, { key, value }) => { + ipcMain.handle("config-save", async (event, { key, value }) => { try { await saveConfig(key, value); return true; @@ -216,7 +256,7 @@ function setupIpcMain() { } }); - ipcMain.handle('config-delete', async (event, id) => { + ipcMain.handle("config-delete", async (event, id) => { try { await removeConfig(id); return true; @@ -227,45 +267,48 @@ function setupIpcMain() { }); // 字典管理相关IPC - 使用正确的函数名称 - ipcMain.handle('dict-fetch-types', async () => { + ipcMain.handle("dict-fetch-types", async () => { try { return await fetchDictTypes(); } catch (error) { - console.error('Failed to fetch dict types:', 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-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) => { + ipcMain.handle("dict-create-type", async (event, dictType) => { try { return await createDictType(dictType); } catch (error) { - console.error('Failed to create dict type:', error); + console.error("Failed to create dict type:", error); throw error; } }); // 将 updateDictType 改为 modifyDictType - ipcMain.handle('dict-update-type', async (event, dictType) => { + 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); + console.error("Failed to update dict type:", error); throw error; } }); // 将 deleteDictType 改为 removeDictType - ipcMain.handle('dict-delete-type', async (event, typeCode) => { + ipcMain.handle("dict-delete-type", async (event, typeCode) => { try { return await removeDictType(typeCode); } catch (error) { @@ -274,27 +317,27 @@ function setupIpcMain() { } }); - ipcMain.handle('dict-create-item', async (event, dictItem) => { + ipcMain.handle("dict-create-item", async (event, dictItem) => { try { return await createDictItem(dictItem); } catch (error) { - console.error('Failed to create dict item:', error); + console.error("Failed to create dict item:", error); throw error; } }); // 将 updateDictItem 改为 modifyDictItem - ipcMain.handle('dict-update-item', async (event, dictItem) => { + 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); + console.error("Failed to update dict item:", error); throw error; } }); // 将 deleteDictItem 改为 removeDictItem - ipcMain.handle('dict-delete-item', async (event, id) => { + ipcMain.handle("dict-delete-item", async (event, id) => { try { return await removeDictItem(id); } catch (error) { @@ -304,55 +347,55 @@ function setupIpcMain() { }); // 将 fetchAllDictItemsWithTypes 改为 fetchDictItemsWithTypes - ipcMain.handle('dict-fetch-all-items-with-types', async () => { + 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); + console.error("Failed to fetch all dict items with types:", error); throw error; } }); // 添加在setupIpcMain函数中 // 检查parent_code是否存在 - ipcMain.handle('dict-check-parent-code', async (event, parentCode) => { + ipcMain.handle("dict-check-parent-code", async (event, parentCode) => { try { - return await checkDictParentCode(parentCode); // 修改这一行 + return await checkDictParentCode(parentCode); // 修改这一行 } catch (error) { - console.error('检查parent_code失败:', error); + console.error("检查parent_code失败:", error); throw error; } }); // 检查是否有子引用 - ipcMain.handle('dict-check-child-references', async (event, itemCode) => { + ipcMain.handle("dict-check-child-references", async (event, itemCode) => { try { - return await checkDictChildReferences(itemCode); // 修改这一行 + return await checkDictChildReferences(itemCode); // 修改这一行 } catch (error) { - console.error('检查子引用失败:', error); + console.error("检查子引用失败:", error); throw error; } }); // 题干管理相关IPC - ipcMain.handle('question-create', async (event, questionData) => { + ipcMain.handle("question-create", async (event, questionData) => { try { return await createQuestion(questionData); } catch (error) { - console.error('Failed to create question:', error); + console.error("Failed to create question:", error); throw error; } }); - ipcMain.handle('question-fetch-all', async () => { + ipcMain.handle("question-fetch-all", async () => { try { return await fetchAllQuestions(); } catch (error) { - console.error('Failed to fetch questions:', error); + console.error("Failed to fetch questions:", error); throw error; } }); - ipcMain.handle('question-fetch-by-id', async (event, id) => { + ipcMain.handle("question-fetch-by-id", async (event, id) => { try { return await fetchQuestionById(id); } catch (error) { @@ -361,16 +404,16 @@ function setupIpcMain() { } }); - ipcMain.handle('question-update', async (event, id, questionData) => { + ipcMain.handle("question-update", async (event, id, questionData) => { try { return await modifyQuestion(id, questionData); } catch (error) { - console.error('Failed to update question:', error); + console.error("Failed to update question:", error); throw error; } }); - ipcMain.handle('question-delete', async (event, id) => { + ipcMain.handle("question-delete", async (event, id) => { try { return await removeQuestion(id); } catch (error) { @@ -380,52 +423,238 @@ function setupIpcMain() { }); // 在已有的 question 相关 IPC 处理程序区域添加 - ipcMain.handle('question-fetch-all-with-relations', async (event) => { + 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); + console.error("Failed to fetch all questions with relations:", error); throw error; } }); // 添加更新题干描述的 IPC 处理程序 - ipcMain.handle('question-update-description', async (event, id, questionDescription) => { + 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 modifyQuestionDescription(id, questionDescription); + return await modifyChoiceQuestion(id, choiceData); } catch (error) { - console.error('Failed to update question description:', error); + console.error('Failed to update choice question:', error); throw error; } }); // 添加选择题问题的IPC处理程序 - ipcMain.handle('question-create-choice', async (event, choiceData) => { + ipcMain.handle("question-create-choice", async (event, choiceData) => { try { return await createChoiceQuestion(choiceData); } catch (error) { - console.error('Failed to create choice question:', 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("exam-create", async (event, examData) => { + try { + // 确保exam_notice是序列化的JSON字符串 + if (examData.exam_notice && typeof examData.exam_notice === "object") { + examData.exam_notice = JSON.stringify(examData.exam_notice); + } + return await createNewExam(examData); + } catch (error) { + console.error("Failed to create exam:", error); + throw error; + } + }); + + ipcMain.handle("exam-update", async (event, { id, examData }) => { + try { + // 确保exam_notice是序列化的JSON字符串 + if (examData.exam_notice && typeof examData.exam_notice === "object") { + examData.exam_notice = JSON.stringify(examData.exam_notice); + } + return await modifyExam(id, examData); + } catch (error) { + console.error("Failed to update exam:", error); + throw error; + } + }); + + ipcMain.handle("exam-fetch-last", async () => { + try { + const exam = await fetchLastExam(); + // 将exam_notice字符串解析为数组 + if (exam && exam.exam_notice) { + try { + exam.exam_notice = JSON.parse(exam.exam_notice); + } catch (e) { + console.error("解析考试须知失败:", e); + exam.exam_notice = []; + } + } + return exam; + } catch (error) { + console.error("Failed to fetch last exam:", error); + throw error; + } + }); + + ipcMain.handle("exam-fetch-all", async () => { + try { + return { success: true, data: await fetchAllExams() }; + } catch (error) { + console.error("Failed to fetch all exams:", error); + return { success: false, error: error.message }; + } + }); + + ipcMain.handle("exam-fetch-last", async () => { + try { + return { success: true, data: await fetchLastExam() }; + } catch (error) { + console.error("Failed to fetch last exam:", error); + return { success: false, error: error.message }; + } + }); + + ipcMain.handle("exam-fetch-by-id", async (event, id) => { + try { + return { success: true, data: await fetchExamById(id) }; + } catch (error) { + console.error(`Failed to fetch exam by id ${id}:`, error); + return { success: false, error: error.message }; + } + }); + + ipcMain.handle("exam-update", async (event, { id, examData }) => { + try { + const result = await modifyExam(id, examData); + return { success: result, data: { id, ...examData } }; + } catch (error) { + console.error(`Failed to update exam ${id}:`, error); + return { success: false, error: error.message }; + } + }); + + ipcMain.handle("exam-delete", async (event, id) => { + try { + const result = await removeExam(id); + return { success: result, data: { id } }; + } catch (error) { + console.error(`Failed to delete exam ${id}:`, error); + return { success: false, error: error.message }; + } + }); + + // 考生管理相关IPC + ipcMain.handle("examinee-create", async (event, examineeData) => { + try { + return await createExamineeService(examineeData); + } catch (error) { + console.error("Failed to create examinee:", error); + throw error; + } + }); + + ipcMain.handle("examinee-fetch-all", async () => { + try { + return await fetchAllExaminees(); + } catch (error) { + console.error("Failed to fetch all examinees:", error); + return []; + } + }); + + ipcMain.handle("examinee-fetch-by-id", async (event, id) => { + try { + return await fetchExamineeById(id); + } catch (error) { + console.error("Failed to fetch examinee by id:", error); + return null; + } + }); + + ipcMain.handle("examinee-update", async (event, id, examineeData) => { + try { + return await updateExamineeService(id, examineeData); + } catch (error) { + console.error("Failed to update examinee:", error); + return false; + } + }); + + ipcMain.handle("examinee-delete", async (event, id) => { + try { + return await deleteExamineeService(id); + } catch (error) { + console.error("Failed to delete examinee:", error); + return false; + } + }); + } // 确保在 app.whenReady() 中调用 setupIpcMain() app.whenReady().then(() => { - setupApp() - createWindow() - setupIpcMain() + setupApp(); + createWindow(); + setupIpcMain(); }); -// 删除下面这段重复的代码 -// app.whenReady().then(() => { -// setupApp() -// createWindow() -// setupIpcMain() -// }); - // 在应用退出前关闭所有数据库连接 -app.on('will-quit', () => { - console.log('应用即将退出...'); - // closeAllConnections(); -}); \ No newline at end of file +app.on("will-quit", () => { + console.log("应用即将退出..."); + // closeAllConnections(); +}); diff --git a/electron/preload.js b/electron/preload.js index 087d36b..42a8212 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -37,22 +37,29 @@ contextBridge.exposeInMainWorld('electronAPI', { // 题干管理相关API createQuestion: (questionData) => ipcRenderer.invoke('question-create', questionData), - createChoiceQuestion: (choiceData) => ipcRenderer.invoke('question-create-choice', choiceData), // 添加这行 + createChoiceQuestion: (choiceData) => ipcRenderer.invoke('question-create-choice', choiceData), + updateChoiceQuestion: (id, choiceData) => ipcRenderer.invoke('question-update-choice', id, choiceData), + createFillBlankQuestion: (fillBlankData) => ipcRenderer.invoke('question-create-fill-blank', fillBlankData), fetchAllQuestions: () => ipcRenderer.invoke('question-fetch-all'), fetchAllQuestionsWithRelations: () => ipcRenderer.invoke('question-fetch-all-with-relations'), fetchQuestionById: (id) => ipcRenderer.invoke('question-fetch-by-id', id), updateQuestion: (id, questionData) => ipcRenderer.invoke('question-update', id, questionData), - updateQuestionDescription: (id, questionDescription) => ipcRenderer.invoke('question-update-description', id, questionDescription), // 添加新API - deleteQuestion: (id) => ipcRenderer.invoke('question-delete', id) -}); -// 这里可以添加预加载脚本 -window.addEventListener('DOMContentLoaded', () => { - const replaceText = (selector, text) => { - const element = document.getElementById(selector) - if (element) element.innerText = text - } - - for (const dependency of ['chrome', 'node', 'electron']) { - replaceText(`${dependency}-version`, process.versions[dependency]) - } -}) \ No newline at end of file + updateQuestionDescription: (id, questionDescription) => ipcRenderer.invoke('question-update-description', id, questionDescription), + deleteQuestion: (id) => ipcRenderer.invoke('question-delete', id), + updateFillBlankQuestion: (id, fillBlankData) => ipcRenderer.invoke('question-update-fill-blank', id, fillBlankData), + deleteFillBlankQuestion: (id) => ipcRenderer.invoke('question-delete-fill-blank', id), + fetchFillBlankQuestionsByQuestionId: (questionId) => ipcRenderer.invoke('question-fetch-fill-blank-by-question-id', questionId), + // 考试管理相关API + createExam: (examData) => ipcRenderer.invoke('exam-create', examData), + fetchAllExams: () => ipcRenderer.invoke('exam-fetch-all'), + fetchExamById: (id) => ipcRenderer.invoke('exam-fetch-by-id', id), + updateExam: (id, examData) => ipcRenderer.invoke('exam-update', { id, examData }), + deleteExam: (id) => ipcRenderer.invoke('exam-delete', id), + fetchLastExam: () => ipcRenderer.invoke('exam-fetch-last'), // 添加这一行 + // 考生管理相关API + fetchAllExaminees: () => ipcRenderer.invoke('examinee-fetch-all'), + createExaminee: (examineeData) => ipcRenderer.invoke('examinee-create', examineeData), + updateExaminee: (id, examineeData) => ipcRenderer.invoke('examinee-update', id, examineeData), + deleteExaminee: (id) => ipcRenderer.invoke('examinee-delete', id), + userLogin: (idCard, admissionTicket) => ipcRenderer.invoke('user-login', { idCard, admissionTicket }) +}); \ No newline at end of file diff --git a/electron/service/examService.js b/electron/service/examService.js new file mode 100644 index 0000000..49ed0a2 --- /dev/null +++ b/electron/service/examService.js @@ -0,0 +1,150 @@ +import { + createExam, + getAllExams, + getExamById, + updateExam, + deleteExam, + getLastExam // 添加这一行 +} from '../db/exam.js'; + +/** + * 服务层:创建考试 + * @param {Object} examData 考试数据 + * @returns {Promise} 创建的考试 + */ +export async function createNewExam(examData) { + try { + // 数据验证 - 修改为exam_minutes和exam_minutes_min必填 + if (!examData.exam_minutes || !examData.exam_minutes_min) { + throw new Error('考试时长和最少考试时间为必填项'); + } + + // 移除默认值设置,因为现在是必填项 + if (typeof examData.exam_minutes_min !== 'number') { + throw new Error('最少考试时间必须是数字'); + } + + // 确保最少考试时间不大于考试时长 + if (examData.exam_minutes_min > examData.exam_minutes) { + throw new Error('最少考试时间不能大于考试时长'); + } + + return await createExam(examData); + } catch (error) { + console.error('服务层: 创建考试失败', error); + throw error; + } +} + +/** + * 服务层:查询所有考试 + * @returns {Promise} 考试列表 + */ +export async function fetchAllExams() { + try { + return await getAllExams(); + } catch (error) { + console.error('服务层: 查询所有考试失败', error); + throw error; + } +} + +/** + * 服务层:根据ID查询考试 + * @param {number} id 考试ID + * @returns {Promise} 考试数据 + */ +export async function fetchExamById(id) { + try { + if (!id) { + throw new Error('考试ID不能为空'); + } + + const exam = await getExamById(id); + if (!exam) { + throw new Error('未找到指定考试'); + } + + return exam; + } catch (error) { + console.error('服务层: 根据ID查询考试失败', error); + throw error; + } +} + +/** + * 服务层:更新考试 + * @param {number} id 考试ID + * @param {Object} examData 更新的数据 + * @returns {Promise} 是否更新成功 + */ +export async function modifyExam(id, examData) { + try { + if (!id) { + throw new Error('考试ID不能为空'); + } + + // 验证考试是否存在 + const existingExam = await getExamById(id); + if (!existingExam) { + throw new Error('未找到指定考试'); + } + + // 数据验证 - 修改为exam_minutes和exam_minutes_min必填 + if (!examData.exam_minutes || !examData.exam_minutes_min) { + throw new Error('考试时长和最少考试时间为必填项'); + } + + // 移除默认值设置,因为现在是必填项 + if (typeof examData.exam_minutes_min !== 'number') { + throw new Error('最少考试时间必须是数字'); + } + + // 确保最少考试时间不大于考试时长 + if (examData.exam_minutes_min > examData.exam_minutes) { + throw new Error('最少考试时间不能大于考试时长'); + } + + return await updateExam(id, examData); + } catch (error) { + console.error('服务层: 更新考试失败', error); + throw error; + } +} + +/** + * 服务层:删除考试 + * @param {number} id 考试ID + * @returns {Promise} 是否删除成功 + */ +export async function removeExam(id) { + try { + if (!id) { + throw new Error('考试ID不能为空'); + } + + // 验证考试是否存在 + const existingExam = await getExamById(id); + if (!existingExam) { + throw new Error('未找到指定考试'); + } + + return await deleteExam(id); + } catch (error) { + console.error('服务层: 删除考试失败', error); + throw error; + } +} + +/** + * 服务层:查询ID最大的考试记录 + * @returns {Promise} 考试数据 + */ +export async function fetchLastExam() { + try { + return await getLastExam(); + } catch (error) { + console.error('服务层: 查询ID最大的考试失败', error); + throw error; + } +} \ No newline at end of file diff --git a/electron/service/examineeService.js b/electron/service/examineeService.js new file mode 100644 index 0000000..d2d7eaa --- /dev/null +++ b/electron/service/examineeService.js @@ -0,0 +1,118 @@ +// 在文件开头的导入语句中添加新函数 +import { + getAllExaminees, + getExamineeById, + createExaminee, + updateExaminee, + deleteExaminee, + getExamineeByIdCardAndAdmissionTicket +} from '../db/examinee.js'; + +/** + * 服务层:获取所有考生列表 + * @returns {Promise} 考生列表 + */ +export async function fetchAllExaminees() { + try { + return await getAllExaminees(); + } catch (error) { + console.error('服务层: 获取所有考生列表失败', error); + throw error; + } +} + + +/** + * 服务层:根据ID查询考生 + * @param {number} id 考生ID + * @returns {Promise} 考生数据 + */ +export async function fetchExamineeById(id) { + try { + return await getExamineeById(id); + } catch (error) { + console.error('服务层: 根据ID查询考生失败', error); + throw error; + } +} + +/** + * 服务层:添加考生 + * @param {Object} examineeData 考生数据 + * @returns {Promise} 添加的考生 + */ +export async function createExamineeService(examineeData) { + try { + // 数据验证 + if (!examineeData.examinee_name || !examineeData.examinee_id_card) { + throw new Error('考生姓名和身份证号为必填项'); + } + + return await createExaminee(examineeData); + } catch (error) { + console.error('服务层: 添加考生失败', error); + throw error; + } +} + +/** + * 服务层:更新考生 + * @param {number} id 考生ID + * @param {Object} examineeData 更新的数据 + * @returns {Promise} 是否更新成功 + */ +export async function updateExamineeService(id, examineeData) { + try { + if (!id) { + throw new Error('考生ID不能为空'); + } + + // 验证考生是否存在 + const existingExaminee = await getExamineeById(id); + if (!existingExaminee) { + throw new Error('未找到指定考生'); + } + + // 数据验证 + if (!examineeData.examinee_name || !examineeData.examinee_id_card) { + throw new Error('考生姓名和身份证号为必填项'); + } + + return await updateExaminee(id, examineeData); + } catch (error) { + console.error('服务层: 更新考生失败', error); + throw error; + } +} + +/** + * 服务层:删除考生 + * @param {number} id 考生ID + * @returns {Promise} 是否删除成功 + */ +export async function deleteExamineeService(id) { + try { + return await deleteExaminee(id); + } catch (error) { + console.error('服务层: 删除考生失败', error); + throw error; + } +} + +/** + * 服务层:考生登录 + * @param {string} idCard 身份证号 + * @param {string} admissionTicket 准考证号 + * @returns {Promise} 考生数据或null + */ +export async function fetchExamineeByIdCardAndAdmissionTicket(idCard, admissionTicket) { + try { + if (!idCard || !admissionTicket) { + throw new Error('身份证号和准考证号不能为空'); + } + return await getExamineeByIdCardAndAdmissionTicket(idCard, admissionTicket); + } catch (error) { + console.error('服务层: 考生登录失败', error); + throw error; + } +} \ No newline at end of file diff --git a/electron/service/questionService.js b/electron/service/questionService.js index 69caa47..69fe6db 100644 --- a/electron/service/questionService.js +++ b/electron/service/questionService.js @@ -6,7 +6,12 @@ import { deleteQuestion, getAllQuestionsWithRelations, updateQuestionDescription, - addChoiceQuestion // 添加新函数导入 + addChoiceQuestion, + updateChoiceQuestion, // 添加这一行 + addFillBlankQuestion, + updateFillBlankQuestion, + deleteFillBlankQuestion, + getFillBlankQuestionsByQuestionId } from '../db/question.js'; // 导入configService中的increaseQuestionBankVersion方法 @@ -139,4 +144,88 @@ export async function createChoiceQuestion(choiceData) { console.error('服务层: 添加选择题失败', error); throw error; } +} + +/** + * 服务层:添加选择题问题 + * @param {number} id - 选择题ID + * @param {Object} choiceData - 选择题数据 + * @returns {Promise} 是否更新成功 + */ +export 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 + */ +export 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} 是否更新成功 + */ +export 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} 是否删除成功 + */ +export 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} 填空题列表 + */ +export async function fetchFillBlankQuestionsByQuestionId(questionId) { + try { + return await getFillBlankQuestionsByQuestionId(questionId); + } catch (error) { + console.error('服务层: 根据题干ID查询填空题失败', error); + throw error; + } } \ No newline at end of file diff --git a/package.json b/package.json index 49c2001..ff33ba0 100644 --- a/package.json +++ b/package.json @@ -41,5 +41,36 @@ "vite": "^7.0.6", "vite-plugin-electron": "^0.29.0", "vite-plugin-vue-devtools": "^8.0.0" + }, + "build": { + "appId": "com.example.electron-exam", + "productName": "电子考试系统", + "directories": { + "output": "dist-electron" + }, + "files": [ + "dist/**/*", + "electron/**/*" + ], + "win": { + "target": [ + { + "target": "nsis", + "arch": ["ia32"] + } + ], + "icon": "public/favicon.ico" + }, + "nsis": { + "oneClick": false, + "allowElevation": true, + "allowToChangeInstallationDirectory": true, + "installerIcon": "public/favicon.ico", + "uninstallerIcon": "public/favicon.ico", + "installerHeaderIcon": "public/favicon.ico", + "createDesktopShortcut": true, + "createStartMenuShortcut": true, + "shortcutName": "电子考试系统" + } } } diff --git a/src/App.vue b/src/App.vue index 6076e76..7d68107 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,14 +1,10 @@ - - diff --git a/src/components/admin/Sider.vue b/src/components/admin/Sider.vue index 6e1fbd5..71b7c66 100644 --- a/src/components/admin/Sider.vue +++ b/src/components/admin/Sider.vue @@ -82,7 +82,7 @@ const menuData = [ index: '6', label: '考生管理', icon: 'users', - route: '/admin/student-management' + route: '/admin/examinee-management' }, { index: '7', diff --git a/src/components/common/Footer.vue b/src/components/common/Footer.vue index 4129a32..bada5b5 100644 --- a/src/components/common/Footer.vue +++ b/src/components/common/Footer.vue @@ -2,7 +2,7 @@