diff --git a/src/background/db/examing.js b/src/background/db/examing.js index 7da34a6..c6eb5e7 100644 --- a/src/background/db/examing.js +++ b/src/background/db/examing.js @@ -440,7 +440,7 @@ async function generateExamineePaper(examineeData, examData) { * @returns {Promise} - 包含试题序列的数组 */ async function loadPaperSerial(paperId) { - console.log('0903调试(loadPaperSerial): ', paperId); + // console.log('0903调试(loadPaperSerial): ', paperId); try { // 1. 获取数据库连接 const userDb = await getDbConnection(getUserDbPath()); @@ -905,7 +905,6 @@ async function checkPaperAnswers(paperId) { ); if (!paper) { - await closeAllConnections(); return { success: false, message: `未找到ID为${paperId}的试卷`, @@ -1047,7 +1046,7 @@ async function checkPaperAnswers(paperId) { // 5. 查询更新后的试卷信息(包含关联数据) const updatedPaper = await getPaper(paperId); - console.log(updatedPaper); + // console.log(updatedPaper); return { success: true, @@ -1086,7 +1085,6 @@ async function getPaper(paperId) { ); if (!paper) { - await closeAllConnections(); return { success: false, message: `未找到ID为${paperId}的试卷`, @@ -1183,9 +1181,6 @@ async function getPaper(paperId) { questions: questionsWithDetails }; - // 关闭数据库连接 - await closeAllConnections(); - return { success: true, message: '获取试卷成功', @@ -1193,7 +1188,6 @@ async function getPaper(paperId) { }; } catch (error) { console.error('获取试卷过程中发生错误:', error); - await closeAllConnections(); return { success: false, message: `获取试卷失败: ${error.message}`, diff --git a/src/background/db/index.js b/src/background/db/index.js index 6770839..b2c3cf3 100644 --- a/src/background/db/index.js +++ b/src/background/db/index.js @@ -52,224 +52,182 @@ exports.checkDatabaseInitialized = async function checkDatabaseInitialized() { // 初始化系统数据库 async function initializeSystemDatabase() { - console.log('开始初始化系统数据库...'); - const systemDbPath = getSystemDbPath(); - const systemDb = await exports.getDbConnection(systemDbPath); + console.log('开始初始化系统数据库...'); + const systemDbPath = getSystemDbPath(); + const systemDb = await exports.getDbConnection(systemDbPath); - // 记录成功和失败的操作 - const results = { success: [], failed: [] }; + // 创建表结构 + console.log('开始创建系统数据库表结构...'); - // 创建表结构 - console.log('开始创建系统数据库表结构...'); + // 创建config表 + try { + await systemDb.execAsync(systemSchema.config.trim()); + console.log('创建 config 表成功'); + } catch (error) { + console.error('创建 config 表失败:', error); + } - // 创建config表 - try { - await systemDb.execAsync(systemSchema.config.trim()); - console.log('创建 config 表成功'); - results.success.push('创建 config 表'); - } catch (error) { - console.error('创建 config 表失败:', error); - results.failed.push({ operation: '创建 config 表', error: error.message }); - } + // 创建dict_types表 + try { + await systemDb.execAsync(systemSchema.dict_types.trim()); + console.log('创建 dict_types 表成功'); + } catch (error) { + console.error('创建 dict_types 表失败:', error); + } - // 创建dict_types表 - try { - await systemDb.execAsync(systemSchema.dictTypes.trim()); - console.log('创建 dict_types 表成功'); - results.success.push('创建 dict_types 表'); - } catch (error) { - console.error('创建 dict_types 表失败:', error); - results.failed.push({ operation: '创建 dict_types 表', error: error.message }); - } + // 创建dict_items表 + try { + await systemDb.execAsync(systemSchema.dict_items.trim()); + console.log('创建 dict_items 表成功'); + } catch (error) { + console.error('创建 dict_items 表失败:', error); + } - // 创建dict_items表 - try { - await systemDb.execAsync(systemSchema.dictItems.trim()); - console.log('创建 dict_items 表成功'); - results.success.push('创建 dict_items 表'); - } catch (error) { - console.error('创建 dict_items 表失败:', error); - results.failed.push({ operation: '创建 dict_items 表', error: error.message }); - } + // 创建questions表 + try { + await systemDb.execAsync(systemSchema.questions.trim()); + console.log('创建 questions 表成功'); + } catch (error) { + console.error('创建 questions 表失败:', error); + } - // 创建questions表 - try { - await systemDb.execAsync(systemSchema.questions.trim()); - console.log('创建 questions 表成功'); - results.success.push('创建 questions 表'); - } catch (error) { - console.error('创建 questions 表失败:', error); - results.failed.push({ operation: '创建 questions 表', error: error.message }); - } + // 创建question_datasets表 + try { + await systemDb.execAsync(systemSchema.question_datasets.trim()); + console.log('创建 question_datasets 表成功'); + } catch (error) { + console.error('创建 question_datasets 表失败:', error); + } - // 创建question_datasets表 - try { - await systemDb.execAsync(systemSchema.questionDatasets.trim()); - console.log('创建 question_datasets 表成功'); - results.success.push('创建 question_datasets 表'); - } catch (error) { - console.error('创建 question_datasets 表失败:', error); - results.failed.push({ operation: '创建 question_datasets 表', error: error.message }); - } + // 创建question_images表 + try { + await systemDb.execAsync(systemSchema.question_images.trim()); + console.log('创建 question_images 表成功'); + } catch (error) { + console.error('创建 question_images 表失败:', error); + } - // 创建question_images表 - try { - await systemDb.execAsync(systemSchema.questionImages.trim()); - console.log('创建 question_images 表成功'); - results.success.push('创建 question_images 表'); - } catch (error) { - console.error('创建 question_images 表失败:', error); - results.failed.push({ operation: '创建 question_images 表', error: error.message }); - } + // 创建question_fill_table表 + try { + await systemDb.execAsync(systemSchema.question_fill_table.trim()); + console.log('创建 question_fill_table 表成功'); + } catch (error) { + console.error('创建 question_fill_table 表失败:', error); + } - // 创建question_fill_table表 - try { - await systemDb.execAsync(systemSchema.questionFillTable.trim()); - console.log('创建 question_fill_table 表成功'); - results.success.push('创建 question_fill_table 表'); - } catch (error) { - console.error('创建 question_fill_table 表失败:', error); - results.failed.push({ operation: '创建 question_fill_table 表', error: error.message }); - } + // 创建question_fill_table_blanks表 + try { + await systemDb.execAsync(systemSchema.question_fill_table_blanks.trim()); + console.log('创建 question_fill_table_blanks 表成功'); + } catch (error) { + console.error('创建 question_fill_table_blanks 表失败:', error); + } - // 创建question_fill_table_blanks表 - try { - await systemDb.execAsync(systemSchema.questionFillTableBlanks.trim()); - console.log('创建 question_fill_table_blanks 表成功'); - results.success.push('创建 question_fill_table_blanks 表'); - } catch (error) { - console.error('创建 question_fill_table_blanks 表失败:', error); - results.failed.push({ operation: '创建 question_fill_table_blanks 表', error: error.message }); - } + // 创建question_choices表 + try { + await systemDb.execAsync(systemSchema.question_choices.trim()); + console.log('创建 question_choices 表成功'); + } catch (error) { + console.error('创建 question_choices 表失败:', error); + } - // 创建question_choices表 - try { - await systemDb.execAsync(systemSchema.questionChoices.trim()); - console.log('创建 question_choices 表成功'); - results.success.push('创建 question_choices 表'); - } catch (error) { - console.error('创建 question_choices 表失败:', error); - results.failed.push({ operation: '创建 question_choices 表', error: error.message }); - } + // 创建question_fill_blanks表 + try { + await systemDb.execAsync(systemSchema.question_fill_blanks.trim()); + console.log('创建 question_fill_blanks 表成功'); + } catch (error) { + console.error('创建 question_fill_blanks 表失败:', error); + } - // 创建question_fill_blanks表 - try { - await systemDb.execAsync(systemSchema.questionFillBlanks.trim()); - console.log('创建 question_fill_blanks 表成功'); - results.success.push('创建 question_fill_blanks 表'); - } catch (error) { - console.error('创建 question_fill_blanks 表失败:', error); - results.failed.push({ operation: '创建 question_fill_blanks 表', error: error.message }); - } + // 创建question_judge表 + try { + await systemDb.execAsync(systemSchema.question_judge.trim()); + console.log('创建 question_judge 表成功'); + } catch (error) { + console.error('创建 question_judge 表失败:', error); + } - // 创建question_judge表 - try { - await systemDb.execAsync(systemSchema.questionJudge.trim()); - console.log('创建 question_judge 表成功'); - results.success.push('创建 question_judge 表'); - } catch (error) { - console.error('创建 question_judge 表失败:', error); - results.failed.push({ operation: '创建 question_judge 表', error: error.message }); - } + // 创建question_short表 + try { + await systemDb.execAsync(systemSchema.question_short.trim()); + console.log('创建 question_short 表成功'); + } catch (error) { + console.error('创建 question_short 表失败:', error); + } - // 创建question_short表 - try { - await systemDb.execAsync(systemSchema.questionShort.trim()); - console.log('创建 question_short 表成功'); - results.success.push('创建 question_short 表'); - } catch (error) { - console.error('创建 question_short 表失败:', error); - results.failed.push({ operation: '创建 question_short 表', error: error.message }); - } + // 创建exam表 + try { + await systemDb.execAsync(systemSchema.exam.trim()); + console.log('创建 exam 表成功'); + } catch (error) { + console.error('创建 exam 表失败:', error); + } - // 创建exam表 - try { - await systemDb.execAsync(systemSchema.exam.trim()); - console.log('创建 exam 表成功'); - results.success.push('创建 exam 表'); - } catch (error) { - console.error('创建 exam 表失败:', error); - results.failed.push({ operation: '创建 exam 表', error: error.message }); - } + // 创建examinee表(系统库中的考生表) + try { + await systemDb.execAsync(systemSchema.examinee.trim()); + console.log('创建 examinee 表成功'); + } catch (error) { + console.error('创建 examinee 表失败:', error); + } - // 创建examinee表 - try { - await systemDb.execAsync(systemSchema.examinee.trim()); - console.log('创建 examinee 表成功'); - results.success.push('创建 examinee 表'); - } catch (error) { - console.error('创建 examinee 表失败:', error); - results.failed.push({ operation: '创建 examinee 表', error: error.message }); - } - - // 插入默认数据 + // 插入默认数据 + try { console.log('开始插入默认数据...'); - // 处理密码哈希 - try { - const plainPassword = defaultData.config.find(item => item.key === 'admin_password').value; - const hashedPassword = await bcrypt.hash(plainPassword, 10); + // 插入admin用户 + const adminPasswordHash = await bcrypt.hash('123456', 10); + await systemDb.runAsync( + 'INSERT INTO examinee (id_card, name, role, password) VALUES (?, ?, ?, ?)', + ['admin', '管理员', 'admin', adminPasswordHash] + ); + console.log('插入管理员用户成功'); - // 更新密码为哈希值 - const configData = defaultData.config.map(item => { - if (item.key === 'admin_password') { - return { ...item, value: hashedPassword }; - } - return item; - }); + // 插入config表数据 + await systemDb.runAsync( + 'INSERT OR IGNORE INTO config (key, value, description) VALUES (?, ?, ?)', + ['initialized', 'false', '数据库初始化状态'] + ); + console.log('插入config表数据成功'); - // 插入config表数据 - try { - await batchInsert(systemDb, 'config', configData); - console.log('插入 config 表数据成功'); - results.success.push('插入 config 表数据'); - } catch (error) { - console.error('插入 config 表数据失败:', error); - results.failed.push({ operation: '插入 config 表数据', error: error.message }); - } - } catch (error) { - console.error('处理密码哈希失败:', error); - results.failed.push({ operation: '处理密码哈希', error: error.message }); + // 批量插入dict_types数据 + const dictTypes = defaultData.dictTypes; + for (const type of dictTypes) { + await systemDb.runAsync( + 'INSERT INTO dict_types (code, name, description, sort_order) VALUES (?, ?, ?, ?)', + [type.code, type.name, type.description, type.sortOrder] + ); } + console.log('插入dict_types表数据成功'); - // 插入dict_types表数据 - try { - await batchInsert(systemDb, 'dict_types', defaultData.dictTypes); - console.log('插入 dict_types 表数据成功'); - results.success.push('插入 dict_types 表数据'); - } catch (error) { - console.error('插入 dict_types 表数据失败:', error); - results.failed.push({ operation: '插入 dict_types 表数据', error: error.message }); + // 批量插入dict_items数据 + const dictItems = defaultData.dictItems; + for (const item of dictItems) { + await systemDb.runAsync( + 'INSERT INTO dict_items (type_code, code, name, description, sort_order, is_active, parent_code) VALUES (?, ?, ?, ?, ?, ?, ?)', + [ + item.typeCode, + item.code, + item.name, + item.description, + item.sortOrder, + item.isActive ? 1 : 0, + item.parentCode + ] + ); } + console.log('插入dict_items表数据成功'); - // 插入dict_items表数据 - try { - await batchInsert(systemDb, 'dict_items', defaultData.dictItems); - console.log('插入 dict_items 表数据成功'); - results.success.push('插入 dict_items 表数据'); - } catch (error) { - console.error('插入 dict_items 表数据失败:', error); - results.failed.push({ operation: '插入 dict_items 表数据', error: error.message }); - } + console.log('默认数据插入完成'); + } catch (error) { + console.error('插入默认数据失败:', error); + } - console.log('系统数据库初始化结果:'); - console.log('成功操作:', results.success); - console.log('失败操作:', results.failed); - - // 如果有失败操作,抛出错误 - if (results.failed.length > 0) { - console.log(`系统数据库初始化有 ${results.failed.length} 个操作失败,请查看日志`); - // 输出详细的失败信息 - results.failed.forEach((item, index) => { - console.log(`${index + 1}. ${item.operation} 失败: ${item.error}`); - }); - } - - return true; + return { success: true }; } -// 初始化用户数据库 -async function initializeUserDatabase() { +// 初始化用户数据库 - 注意这里使用了exports导出 +exports.initializeUserDatabase = async function initializeUserDatabase() { console.log('开始初始化用户数据库...'); const userDbPath = getUserDbPath(); const userDb = await exports.getDbConnection(userDbPath); @@ -350,54 +308,76 @@ async function initializeUserDatabase() { results.failed.push({ operation: '创建 question_fill_blanks 表', error: error.message }); } - console.log('用户数据库初始化结果:'); - console.log('成功操作:', results.success); - console.log('失败操作:', results.failed); - - // 如果有失败操作,仅打印错误信息,不抛出异常 - if (results.failed.length > 0) { - console.error(`用户数据库初始化有 ${results.failed.length} 个操作失败,请查看日志`); - // 输出详细的失败信息 - results.failed.forEach((item, index) => { - console.error(`${index + 1}. ${item.operation} 失败: ${item.error}`); - }); + // 创建question_judge表 + try { + await userDb.execAsync(userSchema.question_judge.trim()); + console.log('创建 question_judge 表成功'); + results.success.push('创建 question_judge 表'); + } catch (error) { + console.error('创建 question_judge 表失败:', error); + results.failed.push({ operation: '创建 question_judge 表', error: error.message }); } - return true; -} + // 创建question_short表 + try { + await userDb.execAsync(userSchema.question_short.trim()); + console.log('创建 question_short 表成功'); + results.success.push('创建 question_short 表'); + } catch (error) { + console.error('创建 question_short 表失败:', error); + results.failed.push({ operation: '创建 question_short 表', error: error.message }); + } + + // 创建question_fill_table表 + try { + await userDb.execAsync(userSchema.question_fill_table.trim()); + console.log('创建 question_fill_table 表成功'); + results.success.push('创建 question_fill_table 表'); + } catch (error) { + console.error('创建 question_fill_table 表失败:', error); + results.failed.push({ operation: '创建 question_fill_table 表', error: error.message }); + } + + // 创建question_fill_table_blanks表 + try { + await userDb.execAsync(userSchema.question_fill_table_blanks.trim()); + console.log('创建 question_fill_table_blanks 表成功'); + results.success.push('创建 question_fill_table_blanks 表'); + } catch (error) { + console.error('创建 question_fill_table_blanks 表失败:', error); + results.failed.push({ operation: '创建 question_fill_table_blanks 表', error: error.message }); + } + + console.log('用户数据库表结构创建完成'); + console.log('成功操作:', results.success.length); + console.log('失败操作:', results.failed.length); + if (results.failed.length > 0) { + console.error('部分操作失败:', results.failed); + } + + return { success: true, results }; +}; // 初始化数据库 exports.initializeDatabase = async function initializeDatabase() { + // 防止并发初始化 + if (global.isInitializing) { + console.log('数据库初始化正在进行中,请勿重复触发'); + return { success: false, message: '数据库初始化正在进行中' }; + } + global.isInitializing = true; + try { - console.log('开始初始化数据库...'); - - // 确保只有一个初始化请求在执行 - if (global.isInitializing) { - console.log('数据库初始化已在进行中,等待完成...'); - while (global.isInitializing) { - await new Promise(resolve => setTimeout(resolve, 100)); - } - return global.initResult; - } - - global.isInitializing = true; - global.initResult = false; - - // 先初始化系统数据库 - console.log('开始初始化系统数据库...'); + // 初始化系统数据库 + console.log('开始数据库整体初始化...'); const systemResult = await initializeSystemDatabase(); - console.log('系统数据库初始化结果:', systemResult ? '成功' : '失败'); - - if (!systemResult) { + if (!systemResult.success) { throw new Error('系统数据库初始化失败'); } - // 再初始化用户数据库 - console.log('开始初始化用户数据库...'); - const userResult = await initializeUserDatabase(); - console.log('用户数据库初始化结果:', userResult ? '成功' : '失败'); - - if (!userResult) { + // 初始化用户数据库 + const userResult = await exports.initializeUserDatabase(); + if (!userResult.success) { throw new Error('用户数据库初始化失败'); } diff --git a/src/background/main.js b/src/background/main.js index 66e99f0..d06fc35 100644 --- a/src/background/main.js +++ b/src/background/main.js @@ -2,13 +2,16 @@ import { app, protocol, BrowserWindow, ipcMain, dialog } from 'electron' import { createProtocol } from 'vue-cli-plugin-electron-builder/lib' -import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer' // 替换argon2为bcryptjs const bcrypt = require('bcryptjs') const isDevelopment = process.env.NODE_ENV !== 'production' +// 导入fs模块用于文件操作 +const fs = require('fs') // 导入数据库相关函数 -const { checkDatabaseInitialized, initializeDatabase } = require('./db/index.js'); +const { checkDatabaseInitialized, initializeDatabase, initializeUserDatabase } = require('./db/index.js'); +// 导入数据库路径函数 +const { getUserDbPath } = require('./db/path.js'); // 导入配置服务 const { initConfigIpc } = require('./service/configService.js'); // 导入字典服务 @@ -207,3 +210,27 @@ ipcMain.handle('initialize-database', async () => { return false } }) + +// 检查user.db是否存在 +ipcMain.handle('checkUserDbExists', async () => { + try { + const userDbPath = getUserDbPath() + return fs.existsSync(userDbPath) + } catch (error) { + console.error('检查user.db文件是否存在失败:', error) + return false + } +}) + +// 静默初始化用户数据库 +ipcMain.handle('initializeUserDatabaseSilently', async () => { + try { + console.log('开始静默初始化用户数据库...') + const result = await initializeUserDatabase() + console.log('静默初始化用户数据库完成:', result) + return { success: true, result } + } catch (error) { + console.error('静默初始化用户数据库失败:', error) + return { success: false, error: error.message } + } +}) diff --git a/src/background/service/examingService.js b/src/background/service/examingService.js index 95ab075..264d42f 100644 --- a/src/background/service/examingService.js +++ b/src/background/service/examingService.js @@ -1,3 +1,4 @@ +// 保留第一个导入 const { generateExamineePaper, loadPaperSerial, @@ -8,10 +9,13 @@ const { endPaper, processPaper, checkPaperAnswers, - getPaper -} = require('../db/examing.js'); -const { getDbConnection, closeAllConnections } = require('../db/index.js'); -const { getUserDbPath } = require('../db/path.js'); + getPaper, +} = require("../db/examing.js"); +const { getDbConnection, closeAllConnections } = require("../db/index.js"); +const { getUserDbPath } = require("../db/path.js"); + +// 删除下面这行重复的导入 +// const { generateExamineePaper, loadPaperSerial, getQuestionByRelatedId, updateExamineeAnswer, startPaper, submitPaper, endPaper, processPaper, checkPaperAnswers, getPaper } = require('../db/examing.js'); /** * 服务层:生成考生试卷 @@ -251,8 +255,33 @@ async function submitPaperService(paperId) { throw new Error("试卷ID必须为正数"); } - const result = await submitPaper(paperId); - return result; + // 1. 提交试卷 + const submitResult = await submitPaper(paperId); + + if (!submitResult.success) { + throw new Error(submitResult.message); + } + + // 2. 判卷 + const checkResult = await checkPaperAnswers(paperId); + + if (!checkResult.success) { + throw new Error(checkResult.message); + } + + // 3. 获取完整试卷数据 + const paperResult = await getPaper(paperId); + + if (!paperResult.success) { + throw new Error(paperResult.message); + } + + // 返回带有完整试卷数据的结果 + return { + success: true, + message: "考试提交并判卷成功", + data: paperResult.data, + }; } catch (error) { console.error("服务层: 提交考试失败", error); return { @@ -262,6 +291,28 @@ async function submitPaperService(paperId) { } } +/** + * 服务层:获取考试结果 + * @param {number} paperId - 试卷ID + * @returns {Promise} - 包含操作结果的对象 + */ +async function getExamResultService(paperId) { + try { + if (!paperId || paperId <= 0) { + throw new Error("试卷ID必须为正数"); + } + + const result = await getPaper(paperId); + return result; + } catch (error) { + console.error("服务层: 获取考试结果失败", error); + return { + success: false, + message: `获取考试结果失败: ${error.message}`, + }; + } +} + /** * 服务层:结束考试 * @param {number} paperId - 试卷ID @@ -332,6 +383,7 @@ async function checkPaperAnswersService(paperId) { * 初始化考试相关的IPC处理程序 * @param {import('electron').IpcMain} ipcMain - IPC主进程实例 */ +// 在initExamingIpc函数中添加examing-get-exam-result处理器 function initExamingIpc(ipcMain) { // 生成考生试卷 ipcMain.handle( @@ -373,9 +425,11 @@ function initExamingIpc(ipcMain) { ); // 加载试卷试题序列 + // 移除或注释掉loadPaperSerial处理程序中的调试日志 ipcMain.handle("examing-load-paper-serial", async (event, { paperId }) => { try { - console.log("0903调试:", paperId); + // 注释掉这行调试日志 + // console.log("0903调试:", paperId); return await loadPaperSerialService(paperId); } catch (error) { console.error("加载试卷试题序列失败:", error); @@ -419,15 +473,19 @@ function initExamingIpc(ipcMain) { ); // 开始考试 - ipcMain.handle("examing-start-paper", async (event, { paperId }) => { + // 在initExamingIpc函数中添加examing-start-paper处理器 + ipcMain.handle("examing-start-paper", async (event, args) => { try { - return await startPaperService(paperId); + const { paperId } = args; + if (!paperId) { + return { success: false, message: "缺少试卷ID" }; + } + + const result = await startPaper(paperId); + return result; } catch (error) { console.error("开始考试失败:", error); - return { - success: false, - message: `开始考试失败: ${error.message}`, - }; + return { success: false, message: error.message }; } }); @@ -482,6 +540,19 @@ function initExamingIpc(ipcMain) { }; } }); + + // 添加获取考试结果的IPC处理器 + ipcMain.handle("examing-get-exam-result", async (event, { paperId }) => { + try { + return await getExamResultService(paperId); + } catch (error) { + console.error("获取考试结果失败:", error); + return { + success: false, + message: `获取考试结果失败: ${error.message}`, + }; + } + }); } // 导出使用CommonJS格式 @@ -497,5 +568,5 @@ module.exports = { endPaperService, processPaperService, checkPaperAnswersService, - initExamingIpc -}; \ No newline at end of file + initExamingIpc, +}; diff --git a/src/background/service/fileService.js b/src/background/service/fileService.js index 3cd7b53..6cfe931 100644 --- a/src/background/service/fileService.js +++ b/src/background/service/fileService.js @@ -1,408 +1,23 @@ -const fs = require('fs'); +// 在文件顶部添加fs模块导入 const path = require('path'); +const fs = require('fs'); const PDFDocument = require('pdfkit'); const { app } = require('electron'); -// 使用更可靠的方式获取应用路径 -const appPath = app.getAppPath(); - -// 修正字体路径常量 - 从应用根路径开始构建 -const FONT_PATH = path.join(appPath, 'src', 'background', 'font'); -// 优先使用SourceHanSansSC字体 -const primaryFontPath = path.join(FONT_PATH, 'SourceHanSansSC-Regular.otf'); -const boldFontPath = path.join(FONT_PATH, 'SourceHanSansSC-Bold.otf'); -// 保留宋体作为备选 -const simsunPath = path.join(FONT_PATH, 'simsun.ttf'); -const fallbackFontPath = path.join(FONT_PATH, 'simsun.ttc'); // 备选字体路径 +// 字体路径 - 简化为只关注宋体字体 +const FONT_PATH = path.join(__dirname, '..', 'font'); +const simsunTtfPath = path.join(FONT_PATH, 'simsun.ttf'); +const simsunTtcPath = path.join(FONT_PATH, 'simsun.ttc'); /** - * 服务层:获取所有考生列表 - * @returns {Promise} 考生列表 - */ -exports.createFileService = async function() { - try { - // TODO 测试用 - return '文件服务测试成功'; - } catch (error) { - console.error('服务层: 创建文件失败', error); - throw error; - } -}; - -/** - * 生成PDF文件并保存到合适的目录 - * @param {Object} pdfData - PDF数据 - * @param {string} fileName - 文件名 - * @returns {Promise} 文件保存路径 - */ -exports.generatePdfService = async function(pdfData, fileName) { - try { - // 获取合适的保存目录 - const appDir = path.join(getAppSaveDir(), '..'); - const filePath = path.join(appDir, `${fileName || 'document'}.pdf`); - - return new Promise((resolve, reject) => { - // 创建PDF文档 - const doc = new PDFDocument(); - - // 加载中文字体的标志 - let chineseFontLoaded = false; - let boldFontLoaded = false; - - // 保存当前字体 - let currentFont = null; - - // 修改字体加载逻辑 - try { - // 1. 尝试加载SourceHanSansSC常规字体 - if (fs.existsSync(primaryFontPath)) { - try { - doc.registerFont('SourceHanSans', primaryFontPath); - doc.font('SourceHanSans'); - currentFont = 'SourceHanSans'; - chineseFontLoaded = true; - console.log('成功加载SourceHanSansSC-Regular.otf字体'); - } catch (error) { - console.error('加载SourceHanSansSC-Regular.otf字体失败:', error); - } - } - - // 2. 尝试加载SourceHanSansSC粗体字体(用于标题) - if (fs.existsSync(boldFontPath)) { - try { - doc.registerFont('SourceHanSansBold', boldFontPath); - boldFontLoaded = true; - console.log('成功加载SourceHanSansSC-Bold.otf字体'); - } catch (error) { - console.error('加载SourceHanSansSC-Bold.otf字体失败:', error); - } - } - - // 3. 如果SourceHanSansSC字体加载失败,尝试加载宋体 - if (!chineseFontLoaded) { - if (fs.existsSync(simsunPath)) { - try { - doc.registerFont('SimSun', simsunPath); - doc.font('SimSun'); - currentFont = 'SimSun'; - chineseFontLoaded = true; - console.log('成功加载simsun.ttf字体'); - } catch (ttfError) { - console.error('加载simsun.ttf字体失败:', ttfError); - // 尝试加载备选TTC字体 - if (fs.existsSync(fallbackFontPath)) { - try { - doc.registerFont('SimSun', fallbackFontPath); - doc.font('SimSun'); - currentFont = 'SimSun'; - chineseFontLoaded = true; - console.log('成功加载simsun.ttc字体'); - } catch (ttcError) { - console.error('加载simsun.ttc字体失败:', ttcError); - } - } - } - } else { - console.warn(`未找到simsun.ttf字体文件: ${simsunPath}`); - // 检查是否有备选TTC字体 - if (fs.existsSync(fallbackFontPath)) { - try { - doc.registerFont('SimSun', fallbackFontPath); - doc.font('SimSun'); - currentFont = 'SimSun'; - chineseFontLoaded = true; - console.log('成功加载simsun.ttc字体'); - } catch (error) { - console.error('加载simsun.ttc字体失败:', error); - } - } - } - } - - if (!chineseFontLoaded) { - console.warn('无法加载中文字体,将使用默认字体,可能导致中文显示异常'); - // 在macOS上尝试使用系统字体 - if (process.platform === 'darwin') { - try { - doc.font('Arial Unicode MS'); // macOS内置支持多语言的字体 - currentFont = 'Arial Unicode MS'; - chineseFontLoaded = true; - console.log('成功加载系统Arial Unicode MS字体'); - } catch (error) { - console.error('加载系统字体失败:', error); - } - } - } - } catch (error) { - console.error('加载字体失败:', error); - console.warn('将使用默认字体,可能导致中文显示异常'); - } - - // 保存到文件 - const writeStream = fs.createWriteStream(filePath); - doc.pipe(writeStream); - - // 设置文档标题 - if (pdfData.title) { - // 保存当前字体 - const tempFont = currentFont; - try { - // 尝试使用粗体字体 - if (boldFontLoaded) { - doc.fontSize(20).font('SourceHanSansBold').text(pdfData.title, { align: 'center' }).moveDown(); - } else if (process.platform === 'darwin') { - doc.fontSize(20).font('Arial Unicode MS Bold').text(pdfData.title, { align: 'center' }).moveDown(); - } else { - doc.fontSize(20).text(pdfData.title, { align: 'center' }).moveDown(); - } - } catch (error) { - console.error('设置标题字体失败:', error); - doc.fontSize(20).text(pdfData.title, { align: 'center' }).moveDown(); - } - // 恢复字体 - if (currentFont) { - doc.font(currentFont); - } - } - - // 添加内容 - if (pdfData.content) { - pdfData.content.forEach(item => { - if (item.type === 'text') { - doc.fontSize(item.fontSize || 12).text(item.text, item.options || {}).moveDown(); - } else if (item.type === 'heading') { - // 保存当前字体 - const tempFont = currentFont; - try { - // 尝试使用SourceHanSansBold粗体字体 - if (boldFontLoaded) { - doc.fontSize(item.fontSize || 16).font('SourceHanSansBold').text(item.text, item.options || {}).moveDown(); - } else if (process.platform === 'darwin') { - doc.fontSize(item.fontSize || 16).font('Arial Unicode MS Bold').text(item.text, item.options || {}).moveDown(); - } else { - // 如果没有粗体字体,使用当前字体加大字号 - doc.fontSize(item.fontSize || 16).text(item.text, item.options || {}).moveDown(); - } - } catch (error) { - console.error('切换到粗体字体失败:', error); - doc.fontSize(item.fontSize || 16).text(item.text, item.options || {}).moveDown(); - } - // 恢复之前的字体 - if (currentFont) { - doc.font(currentFont); - } - } else if (item.type === 'table') { - // 改进表格实现 - const { headers, rows } = item; - const cellWidth = 100; - const baseCellHeight = 25; // 增加基础单元格高度,更好地适应中文 - const marginLeft = 50; - let currentY = doc.y; - const fontSize = 12; - - // 辅助函数:计算文本在指定宽度内的行数 - const calculateLines = (text, width) => { - // 估算每行字符数(假设平均字符宽度为字体大小的一半) - const charsPerLine = Math.floor(width / (fontSize / 2)); - const lines = []; - let currentText = text; - - while (currentText.length > 0) { - // 找到合适的换行位置 - let splitIndex = Math.min(currentText.length, charsPerLine); - // 尝试在空格处换行 - if (currentText.length > splitIndex && currentText[splitIndex] !== ' ') { - const lastSpace = currentText.lastIndexOf(' ', splitIndex); - if (lastSpace > 0) { - splitIndex = lastSpace; - } - } - lines.push(currentText.substring(0, splitIndex).trim()); - currentText = currentText.substring(splitIndex).trim(); - } - return lines; - }; - - // 绘制表头 - headers.forEach((header, i) => { - // 计算单元格实际高度(考虑换行) - const lines = calculateLines(header, cellWidth - 10); - const cellHeight = Math.max(baseCellHeight, lines.length * 15); - - doc.rect(marginLeft + i * cellWidth, currentY, cellWidth, cellHeight).stroke(); - - // 保存当前字体 - const tempFont = currentFont; - try { - if (boldFontLoaded) { - doc.font('SourceHanSansBold'); - } else if (process.platform === 'darwin') { - doc.font('Arial Unicode MS Bold'); - } - // 垂直居中显示文本 - doc.fontSize(fontSize).text(header, marginLeft + i * cellWidth + 5, currentY + (cellHeight - lines.length * 15) / 2, { - width: cellWidth - 10, - height: cellHeight - 10 - }); - } catch (error) { - console.error('设置表头字体失败:', error); - doc.fontSize(fontSize).text(header, marginLeft + i * cellWidth + 5, currentY + (cellHeight - lines.length * 15) / 2, { - width: cellWidth - 10, - height: cellHeight - 10 - }); - } - // 恢复字体 - if (currentFont) { - doc.font(currentFont); - } - }); - // 移动到下一行,考虑最高的表头单元格高度 - const headerLines = headers.map(header => calculateLines(header, cellWidth - 10).length); - const maxHeaderLines = Math.max(...headerLines); - currentY += Math.max(baseCellHeight, maxHeaderLines * 15) + 5; // 添加一些间距 - - // 绘制行 - rows.forEach(row => { - // 计算这一行中最高的单元格 - const rowLines = row.map(cell => calculateLines(cell.toString(), cellWidth - 10).length); - const maxRowLines = Math.max(...rowLines); - const rowHeight = Math.max(baseCellHeight, maxRowLines * 15); - - row.forEach((cell, i) => { - doc.rect(marginLeft + i * cellWidth, currentY, cellWidth, rowHeight).stroke(); - const cellLines = calculateLines(cell.toString(), cellWidth - 10); - doc.fontSize(fontSize).text(cell.toString(), marginLeft + i * cellWidth + 5, currentY + (rowHeight - cellLines.length * 15) / 2, { - width: cellWidth - 10, - height: rowHeight - 10 - }); - }); - - // 移动到下一行 - currentY += rowHeight + 5; // 添加一些间距 - }); - - // 更新文档的当前Y位置 - doc.y = currentY; - } - }); - } - - // 结束文档 - doc.end(); - - // 监听完成事件 - writeStream.on('finish', () => { - resolve(filePath); - }); - - // 监听错误事件 - writeStream.on('error', (error) => { - reject(error); - }); - }); - } catch (error) { - console.error('服务层: 生成PDF失败', error); - throw error; - } -}; - -/** - * 初始化文件相关IPC服务 - * @param {ipcMain} ipcMain - Electron IPC主进程实例 - */ -exports.initFileIpc = function(ipcMain) { - // 测试用接口 - ipcMain.handle('file-test', async () => { - try { - // 测试用 - return '文件服务测试成功'; - } catch (error) { - console.error('服务层: 文件测试失败:', error); - return { success: false, message: error.message }; - } - }); - - // 生成PDF文件接口 - ipcMain.handle('file-generate-pdf', async (event, pdfData, fileName) => { - try { - const filePath = await exports.generatePdfService(pdfData, fileName); - return { success: true, filePath }; - } catch (error) { - console.error('服务层: 生成PDF失败:', error); - return { success: false, message: error.message }; - } - }); - - // 生成试卷PDF文件接口 - ipcMain.handle('file-generate-paper-pdf', async (event, jsonString) => { - try { - const filePath = await exports.generatePaperPdf(jsonString); - return { success: true, filePath }; - } catch (error) { - console.error('服务层: 生成试卷PDF失败:', error); - return { success: false, message: error.message }; - } - }); - - // 复制文件到桌面接口 - ipcMain.handle('file-copy-to-desktop', async (event, filePath) => { - try { - const destPath = await exports.copyToDesk(filePath); - return { success: true, filePath: destPath }; - } catch (error) { - console.error('服务层: 复制文件到桌面失败:', error); - return { success: false, message: error.message }; - } - }); -}; - -/** - * 获取应用保存目录,适配不同操作系统和环境 - * @returns {string} 保存目录路径 - */ -function getAppSaveDir() { - // 判断是否为开发环境 - const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged; - - if (isDev) { - // 开发环境:使用项目根目录 - const outputDir = path.join(process.cwd(), 'output'); - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); - } - return outputDir; - } else { - // 检测是否为便携模式 - const exePath = app.getPath('exe'); - const appDir = path.dirname(exePath); - const portableFlagPath = path.join(appDir, 'portable.txt'); - const isPortable = fs.existsSync(portableFlagPath); - - // 便携模式:使用应用根目录 - if (isPortable) { - return appDir; - } else { - // 非便携模式:使用应用同级的output目录 - const outputDir = path.join(appDir, 'output'); - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); - } - return outputDir; - } - } -} - -/** - * 获取用户桌面目录路径 - * @returns {string} 用户桌面绝对路径 + * 获取用户桌面路径 + * @returns {string} 桌面路径 */ function getDesktopDir() { try { - // 使用Electron的app.getPath方法获取桌面路径 return app.getPath('desktop'); } catch (error) { console.error('获取桌面路径失败:', error); - // 发生错误时,返回当前工作目录作为备选 return process.cwd(); } } @@ -412,13 +27,12 @@ function getDesktopDir() { * @param {string} filePath - 要复制的文件的绝对路径 * @returns {Promise} 复制后的文件路径 */ -exports.copyToDesk = async function(filePath) { +async function copyToDesk(filePath) { try { const desktopDir = getDesktopDir(); const fileName = path.basename(filePath); const destPath = path.join(desktopDir, fileName); - // 使用fs.promises进行文件复制 await fs.promises.copyFile(filePath, destPath); console.log(`文件已成功复制到桌面: ${destPath}`); return destPath; @@ -426,424 +40,500 @@ exports.copyToDesk = async function(filePath) { console.error('复制文件到桌面失败:', error); throw error; } -}; +} + +/** + * 获取应用保存目录 + * @returns {string} 保存目录路径 + */ +function getAppSaveDir() { + let saveDir; + + try { + let userDataPath; + try { + userDataPath = app.getPath('userData'); + console.log('用户数据路径:', userDataPath); + } catch (error) { + console.error('获取用户数据路径失败,使用当前目录作为备选:', error); + userDataPath = process.cwd(); + } + + saveDir = path.join(userDataPath, 'output'); + console.log('PDF保存目录:', saveDir); + + // 确保目录存在 + try { + if (!fs.existsSync(saveDir)) { + console.log('创建保存目录:', saveDir); + fs.mkdirSync(saveDir, { recursive: true }); + } + } catch (error) { + console.error('创建保存目录失败:', error); + saveDir = process.cwd(); + console.log('使用备选保存目录:', saveDir); + } + } catch (error) { + console.error('获取保存目录时发生严重错误:', error); + saveDir = process.cwd(); + } + + return saveDir; +} /** * 生成试卷PDF文件 * @param {string} jsonString - 包含试卷信息的JSON字符串 - * @returns {Promise} - 生成的PDF文件绝对路径 + * @returns {Promise} - 生成的PDF文件信息 */ -exports.generatePaperPdf = async function(jsonString) { +async function generatePaperPdfService(jsonString) { try { - // 解析JSON字符串 - const paperData = JSON.parse(jsonString); + console.log('开始生成PDF,收到数据长度:', jsonString.length); - // 提取考生信息 - const { examinee } = paperData; - const examineeName = examinee.examinee_name; - const idCard = examinee.examinee_id_card; - const admissionTicket = examinee.examinee_admission_ticket; - - // 提取考试时间信息 - const startTime = paperData.paper_start_time; - const endTime = paperData.paper_end_time; - // 截取考试日期 (假设格式为 'YYYY-MM-DD HH:mm:ss') - const examDate = startTime.split(' ')[0]; - // 计算用时(分钟) - const start = new Date(startTime.replace(/-/g, '/')); - const end = new Date(endTime.replace(/-/g, '/')); - const durationMinutes = Math.round((end - start) / (1000 * 60)); - - // 提取试卷信息 - // 计算总题量(每个question下的choice题和fill_blank题的数量之和) - let totalQuestions = 0; - paperData.questions.forEach(question => { - if (question.choices && question.choices.length > 0) { - totalQuestions += question.choices.length; - } - if (question.blanks && question.blanks.length > 0) { - totalQuestions += question.blanks.length; - } - }); - const totalScore = paperData.paper_score; - const realScore = paperData.paper_score_real; - - // 对questions列表按照question_type和id进行排序 - paperData.questions.sort((a, b) => { - // 先按题型排序 - if (a.question_type !== b.question_type) { - return a.question_type.localeCompare(b.question_type); - } - // 再按id排序 - return a.id - b.id; - }); - - // 生成文件名 - // 格式化paper_end_time,移除特殊字符 - const endDate = new Date(endTime.replace(/-/g, '/')); - const formattedEndTime = [ - endDate.getFullYear(), - String(endDate.getMonth() + 1).padStart(2, '0'), - String(endDate.getDate()).padStart(2, '0'), - String(endDate.getHours()).padStart(2, '0'), - String(endDate.getMinutes()).padStart(2, '0'), - String(endDate.getSeconds()).padStart(2, '0') - ].join(''); - const fileName = `${examineeName}_${idCard}_${formattedEndTime}.pdf`; - - // 获取保存路径 - const appDir = getAppSaveDir(); - // 确保目录存在 - if (!fs.existsSync(appDir)) { - fs.mkdirSync(appDir, { recursive: true }); + // 解析JSON字符串为对象 + let paperData; + try { + paperData = JSON.parse(jsonString); + } catch (parseError) { + console.error('JSON解析失败:', parseError); + return { + success: false, + message: '试卷数据格式错误', + errorStack: parseError.stack + }; } - const filePath = path.join(appDir, fileName); - // 创建PDF文档,保留默认边距 + // 验证数据结构 + if (!paperData || typeof paperData !== 'object') { + return { + success: false, + message: '无效的试卷数据' + }; + } + + // 获取考生和试卷信息,并提供默认值 + const examinee = paperData.examinee || {}; + const paper = paperData.paper || paperData; // 兼容不同的数据结构 + + // 安全获取数据,提供默认值 + const examineeName = examinee.examinee_name || '未知考生'; + const idCard = examinee.examinee_id_card || ''; + + // 提取身份证号后几位作为文件名的备选 + const idCardSuffix = idCard ? idCard.slice(-4) : '0000'; + + // 格式化日期为yyyymmdd格式 + const examDate = paper.paper_submit_time ? + new Date(paper.paper_submit_time).toISOString().slice(0, 10).replace(/-/g, '') : + new Date().toISOString().slice(0, 10).replace(/-/g, ''); + + // 生成符合要求的文件名:姓名_身份证号_yyyymmdd.pdf + const safeExamineeName = examineeName || `考生${idCardSuffix}`; + const safeIdCard = idCard || `ID${idCardSuffix}`; + const fileName = `${safeExamineeName}_${safeIdCard}_${examDate}.pdf` + .replace(/[\/:*?"<>|]/g, '_'); + + console.log('生成的PDF文件名:', fileName); + + // 获取保存目录 + const appDir = getAppSaveDir(); + const tempFilePath = path.join(appDir, fileName); + + console.log('PDF临时保存路径:', tempFilePath); + + // 创建PDF文档对象 const doc = new PDFDocument({ size: 'A4', - margin: 50 + margin: 50, + autoFirstPage: true, + bufferPages: true }); - // 加载中文字体 - const FONT_PATH = path.join(__dirname, '..', 'font'); - const primaryFontPath = path.join(FONT_PATH, 'SourceHanSansSC-Regular.otf'); - const boldFontPath = path.join(FONT_PATH, 'SourceHanSansSC-Bold.otf'); - const simsunPath = path.join(FONT_PATH, 'simsun.ttf'); - const fallbackFontPath = path.join(FONT_PATH, 'simsun.ttc'); - + // 加载中文字体 - 只使用宋体字体 let chineseFontLoaded = false; - let boldFontLoaded = false; let currentFont = null; - // 尝试加载字体 + // 打印字体路径,用于调试 + console.log('字体路径检查:'); + console.log('simsun.ttf:', fs.existsSync(simsunTtfPath)); + console.log('simsun.ttc:', fs.existsSync(simsunTtcPath)); + try { - if (fs.existsSync(primaryFontPath)) { - doc.registerFont('SourceHanSans', primaryFontPath); - doc.font('SourceHanSans'); - currentFont = 'SourceHanSans'; - chineseFontLoaded = true; - } else if (fs.existsSync(simsunPath)) { - doc.registerFont('SimSun', simsunPath); - doc.font('SimSun'); - currentFont = 'SimSun'; - chineseFontLoaded = true; - } else if (fs.existsSync(fallbackFontPath)) { - doc.registerFont('SimSun', fallbackFontPath); - doc.font('SimSun'); - currentFont = 'SimSun'; - chineseFontLoaded = true; - } else if (process.platform === 'darwin') { - doc.font('Arial Unicode MS'); - currentFont = 'Arial Unicode MS'; - chineseFontLoaded = true; + // 1. 优先尝试加载simsun.ttf字体 + if (fs.existsSync(simsunTtfPath)) { + try { + doc.registerFont('SimSun', simsunTtfPath); + doc.font('SimSun'); + currentFont = 'SimSun'; + chineseFontLoaded = true; + console.log('成功加载simsun.ttf字体'); + } catch (ttfError) { + console.error('加载simsun.ttf字体失败:', ttfError); + } } - if (fs.existsSync(boldFontPath)) { - doc.registerFont('SourceHanSansBold', boldFontPath); - boldFontLoaded = true; + // 2. 如果simsun.ttf加载失败,尝试加载simsun.ttc字体 + if (!chineseFontLoaded && fs.existsSync(simsunTtcPath)) { + try { + // 对于TTC字体,需要指定字体索引,通常0是常规字体,1是粗体 + doc.registerFont('SimSun', simsunTtcPath, 0); + doc.font('SimSun'); + currentFont = 'SimSun'; + chineseFontLoaded = true; + console.log('成功加载simsun.ttc字体,使用索引0'); + } catch (ttcError) { + console.error('加载simsun.ttc字体失败:', ttcError); + try { + // 尝试使用不同的索引 + doc.registerFont('SimSun', simsunTtcPath, 1); + doc.font('SimSun'); + currentFont = 'SimSun'; + chineseFontLoaded = true; + console.log('成功加载simsun.ttc字体,使用索引1'); + } catch (ttcError2) { + console.error('使用索引1加载simsun.ttc字体也失败:', ttcError2); + } + } } if (!chineseFontLoaded) { - console.warn('无法加载中文字体,可能导致中文显示异常'); + console.warn('无法加载宋体字体,将尝试使用系统字体'); + // 在macOS上尝试使用系统字体 + if (process.platform === 'darwin') { + try { + doc.font('Arial Unicode MS'); + currentFont = 'Arial Unicode MS'; + chineseFontLoaded = true; + console.log('成功加载系统Arial Unicode MS字体'); + } catch (error) { + console.error('加载系统字体失败:', error); + } + } else { + try { + // Windows系统上尝试直接使用'SimSun' + doc.font('SimSun'); + currentFont = 'SimSun'; + chineseFontLoaded = true; + console.log('尝试使用系统SimSun字体'); + } catch (error) { + console.error('无法设置默认字体:', error); + } + } } } catch (error) { - console.error('加载字体失败:', error); + console.error('字体加载过程中发生错误:', error); } - // 保存到文件 - const writeStream = fs.createWriteStream(filePath); - doc.pipe(writeStream); - - // 添加标题 - if (boldFontLoaded) { - doc.font('SourceHanSansBold'); - } else if (process.platform === 'darwin' && currentFont === 'Arial Unicode MS') { - doc.font('Arial Unicode MS Bold'); - } - doc.fontSize(24).text('机考试卷', { align: 'center' }).moveDown(2); - - // 恢复常规字体 - if (currentFont) { - doc.font(currentFont); - } - - // 辅助函数:绘制表格 - function drawTable(headers, rows, cellWidth = 120, baseCellHeight = 25) { - // 从左边距开始 - const marginLeft = doc.page.margins.left; - let currentY = doc.y; - const fontSize = 12; - const pageWidth = doc.page.width - doc.page.margins.left - doc.page.margins.right; - - // 计算文本在指定宽度内的行数 - const calculateLines = (text, width) => { - const charsPerLine = Math.floor(width / (fontSize / 2)); - const lines = []; - let currentText = text; - - while (currentText.length > 0) { - let splitIndex = Math.min(currentText.length, charsPerLine); - if (currentText.length > splitIndex && currentText[splitIndex] !== ' ') { - const lastSpace = currentText.lastIndexOf(' ', splitIndex); - if (lastSpace > 0) { - splitIndex = lastSpace; - } - } - lines.push(currentText.substring(0, splitIndex).trim()); - currentText = currentText.substring(splitIndex).trim(); - } - return lines; - }; - - // 确保表格不会超出页面宽度 - const totalTableWidth = headers.length * cellWidth; - const adjustedCellWidth = totalTableWidth > pageWidth ? pageWidth / headers.length : cellWidth; - - // 绘制表头 - headers.forEach((header, i) => { - const lines = calculateLines(header, adjustedCellWidth - 10); - const cellHeight = Math.max(baseCellHeight, lines.length * 15); - - doc.rect(marginLeft + i * adjustedCellWidth, currentY, adjustedCellWidth, cellHeight).stroke(); - - if (boldFontLoaded) { - doc.font('SourceHanSansBold'); - } else if (process.platform === 'darwin' && currentFont === 'Arial Unicode MS') { - doc.font('Arial Unicode MS Bold'); - } - - doc.fontSize(fontSize).text(header, marginLeft + i * adjustedCellWidth + 5, currentY + (cellHeight - lines.length * 15) / 2, { - width: adjustedCellWidth - 10, - height: cellHeight - 10 - }); - - if (currentFont) { - doc.font(currentFont); - } - }); - - // 移动到下一行(无间隙) - const headerLines = headers.map(header => calculateLines(header, adjustedCellWidth - 10).length); - const maxHeaderLines = Math.max(...headerLines); - currentY += Math.max(baseCellHeight, maxHeaderLines * 15); - - // 绘制行 - rows.forEach(row => { - const rowLines = row.map(cell => calculateLines(cell.toString(), adjustedCellWidth - 10).length); - const maxRowLines = Math.max(...rowLines); - const rowHeight = Math.max(baseCellHeight, maxRowLines * 15); - - row.forEach((cell, i) => { - doc.rect(marginLeft + i * adjustedCellWidth, currentY, adjustedCellWidth, rowHeight).stroke(); - const cellLines = calculateLines(cell.toString(), adjustedCellWidth - 10); - doc.fontSize(fontSize).text(cell.toString(), marginLeft + i * adjustedCellWidth + 5, currentY + (rowHeight - cellLines.length * 15) / 2, { - width: adjustedCellWidth - 10, - height: rowHeight - 10 - }); - }); - - currentY += rowHeight; // 无间隙 - }); - - doc.y = currentY; - } - - // 辅助函数:添加base64图片 - function addBase64Image(base64String, maxWidth = 400, maxHeight = 300) { - try { - // 移除base64前缀 - const base64Data = base64String.replace(/^data:image\/\w+;base64,/, ''); - // 将base64转换为缓冲区 - const imageBuffer = Buffer.from(base64Data, 'base64'); - // 获取图片尺寸 - const image = doc.openImage(imageBuffer); - // 计算缩放比例 - const scale = Math.min(maxWidth / image.width, maxHeight / image.height, 1); - // 添加图片,从左边距开始 - doc.image(image, doc.page.margins.left, doc.y, { - width: image.width * scale, - height: image.height * scale - }); - // 移动文档指针 - doc.y += image.height * scale + 10; - } catch (error) { - console.error('添加图片失败:', error); - doc.fontSize(10).text('图片加载失败', doc.page.margins.left, doc.y).moveDown(); - } - } - - // 绘制考生信息表格 - drawTable( - ['姓名', '身份证号', '准考证号'], - [[examineeName, idCard, admissionTicket]] - ); - doc.moveDown(); - - // 绘制考试信息表格 - drawTable( - ['考试日期', '开始时间', '结束时间', '用时(分钟)'], - [[examDate, startTime.split(' ')[1], endTime.split(' ')[1], durationMinutes.toString()]] - ); - doc.moveDown(); - - // 绘制试卷信息表格 - drawTable( - ['总题量', '试卷总分', '考试得分'], - [[totalQuestions.toString(), totalScore.toString(), realScore.toString()]] - ); - doc.moveDown(2); - - // 添加题目信息 - paperData.questions.forEach((question, index) => { - // 题目类型和描述 - if (boldFontLoaded) { - doc.font('SourceHanSansBold'); - } else if (process.platform === 'darwin' && currentFont === 'Arial Unicode MS') { - doc.font('Arial Unicode MS Bold'); - } - doc.fontSize(14).text(`第 ${index + 1} 题 (${question.question_type_name})`, doc.page.margins.left).moveDown(); - - if (currentFont) { - doc.font(currentFont); - } - // 确保题干描述从左边距开始 - doc.fontSize(12).text(question.question_description, { - x: doc.page.margins.left, - width: doc.page.width - doc.page.margins.left - doc.page.margins.right - }).moveDown(); - - // 添加图片 - if (question.images && question.images.length > 0) { - doc.fontSize(12).text('图片:', doc.page.margins.left).moveDown(); - question.images.forEach((image) => { - if (image.image_base64) { - addBase64Image(image.image_base64); - } else { - doc.fontSize(10).text(`图片: ${image.image_name || '无名称'}`, doc.page.margins.left).moveDown(); - } - }); - } - - // 添加数据集 - if (question.datasets && question.datasets.length > 0) { - doc.fontSize(12).text('数据集:', doc.page.margins.left).moveDown(); - question.datasets.forEach((dataset, dataIndex) => { - doc.fontSize(10).text(`数据集 ${dataIndex + 1} (${dataset.dataset_name || '无名称'})`, doc.page.margins.left).moveDown(); - // 尝试解析数据集数据为表格 - try { - const datasetData = JSON.parse(dataset.dataset_data); - if (Array.isArray(datasetData) && datasetData.length > 0) { - // 假设第一行是表头 - const headers = Object.keys(datasetData[0]); - const rows = datasetData.map(item => headers.map(header => item[header])); - // 缩小单元格宽度以适应页面 - drawTable(headers, rows, 80, 20); - } - } catch (error) { - console.error('解析数据集失败:', error); - doc.fontSize(10).text(`数据集内容: ${dataset.dataset_data.substring(0, 100)}...`, doc.page.margins.left).moveDown(); - } - }); - } - - // 添加填空题 - if (question.blanks && question.blanks.length > 0) { - question.blanks.forEach((blank, blankIndex) => { - if (boldFontLoaded) { - doc.font('SourceHanSansBold'); - } else if (process.platform === 'darwin' && currentFont === 'Arial Unicode MS') { - doc.font('Arial Unicode MS Bold'); - } - doc.fontSize(12).text(`填空 ${blankIndex + 1}`, doc.page.margins.left).moveDown(); - - if (currentFont) { - doc.font(currentFont); - } - // 确保问题描述从左边距开始 - doc.fontSize(12).text(blank.blank_description, { - x: doc.page.margins.left, - width: doc.page.width - doc.page.margins.left - doc.page.margins.right - }).moveDown(); - drawTable( - ['正确答案', '考生答案', '分值', '得分'], - [ - [ - Array.isArray(blank.correct_answers) ? blank.correct_answers.join(', ') : blank.correct_answers, - Array.isArray(blank.examinee_answers) ? blank.examinee_answers.join(', ') : blank.examinee_answers, - blank.score.toString(), - blank.score_real.toString() - ] - ], - 100 - ); - }); - } - - // 添加选择题 - if (question.choices && question.choices.length > 0) { - question.choices.forEach((choice, choiceIndex) => { - if (boldFontLoaded) { - doc.font('SourceHanSansBold'); - } else if (process.platform === 'darwin' && currentFont === 'Arial Unicode MS') { - doc.font('Arial Unicode MS Bold'); - } - doc.fontSize(12).text(`选择题 ${choiceIndex + 1}`, doc.page.margins.left).moveDown(); - - if (currentFont) { - doc.font(currentFont); - } - // 确保问题描述从左边距开始 - doc.fontSize(12).text(choice.choice_description, { - x: doc.page.margins.left, - width: doc.page.width - doc.page.margins.left - doc.page.margins.right - }).moveDown(); - // 显示选项 - if (choice.choice_options && choice.choice_options.length > 0) { - const optionsText = choice.choice_options.map((option, i) => { - const optionLabel = String.fromCharCode(65 + i); // A, B, C, D... - return `${optionLabel}. ${option}`; - }).join(' '); - doc.fontSize(10).text(`选项: ${optionsText}`, doc.page.margins.left).moveDown(); - } - drawTable( - ['正确答案', '考生答案', '分值', '得分'], - [ - [ - Array.isArray(choice.correct_answers) ? choice.correct_answers.join(', ') : choice.correct_answers, - Array.isArray(choice.examinee_answers) ? choice.examinee_answers.join(', ') : choice.examinee_answers, - choice.score.toString(), - choice.score_real.toString() - ] - ], - 100 - ); - }); - } - - // 分页处理 - if (doc.y > 700) { - doc.addPage(); - } - }); - - // 结束文档 - doc.end(); - + // 返回Promise处理异步操作 return new Promise((resolve, reject) => { - writeStream.on('finish', async () => { - try { - // 调用copyToDesk方法将文件复制到桌面,获取新的路径 - const newFilePath = await exports.copyToDesk(filePath); - resolve(newFilePath); - } catch (error) { - reject(error); + try { + // 保存到文件 + const writeStream = fs.createWriteStream(tempFilePath); + doc.pipe(writeStream); + + // 添加标题 + doc.fontSize(24).text('机考试卷', { align: 'center' }).moveDown(2); + + // 辅助函数:绘制表格 + function drawTable(headers, rows, cellWidth = 120, baseCellHeight = 25) { + // 从左边距开始 + const marginLeft = doc.page.margins.left; + let currentY = doc.y; + const fontSize = 12; + const pageWidth = doc.page.width - doc.page.margins.left - doc.page.margins.right; + + // 计算文本在指定宽度内的行数 + const calculateLines = (text, width) => { + const charsPerLine = Math.floor(width / (fontSize / 2)); + const lines = []; + let currentText = text; + + while (currentText.length > 0) { + let splitIndex = Math.min(currentText.length, charsPerLine); + if (currentText.length > splitIndex && currentText[splitIndex] !== ' ') { + const lastSpace = currentText.lastIndexOf(' ', splitIndex); + if (lastSpace > 0) { + splitIndex = lastSpace; + } + } + lines.push(currentText.substring(0, splitIndex).trim()); + currentText = currentText.substring(splitIndex).trim(); + } + return lines; + }; + + // 确保表格不会超出页面宽度 + const totalTableWidth = headers.length * cellWidth; + const adjustedCellWidth = totalTableWidth > pageWidth ? pageWidth / headers.length : cellWidth; + + // 绘制表头 + headers.forEach((header, i) => { + const lines = calculateLines(header, adjustedCellWidth - 10); + const cellHeight = Math.max(baseCellHeight, lines.length * 15); + + doc.rect(marginLeft + i * adjustedCellWidth, currentY, adjustedCellWidth, cellHeight).stroke(); + // 对于表头,设置为粗体样式(通过增加字体大小实现) + doc.fontSize(fontSize + 1).text(header, marginLeft + i * adjustedCellWidth + 5, currentY + (cellHeight - lines.length * 15) / 2, { + width: adjustedCellWidth - 10, + height: cellHeight - 10 + }); + // 恢复默认字体大小 + doc.fontSize(fontSize); + }); + + // 移动到下一行 + const headerLines = headers.map(header => calculateLines(header, adjustedCellWidth - 10).length); + const maxHeaderLines = Math.max(...headerLines); + currentY += Math.max(baseCellHeight, maxHeaderLines * 15); + + // 绘制行 + rows.forEach(row => { + const rowLines = row.map(cell => calculateLines(cell.toString(), adjustedCellWidth - 10).length); + const maxRowLines = Math.max(...rowLines); + const rowHeight = Math.max(baseCellHeight, maxRowLines * 15); + + row.forEach((cell, i) => { + doc.rect(marginLeft + i * adjustedCellWidth, currentY, adjustedCellWidth, rowHeight).stroke(); + const cellLines = calculateLines(cell.toString(), adjustedCellWidth - 10); + doc.fontSize(fontSize).text(cell.toString(), marginLeft + i * adjustedCellWidth + 5, currentY + (rowHeight - cellLines.length * 15) / 2, { + width: adjustedCellWidth - 10, + height: rowHeight - 10 + }); + }); + + currentY += rowHeight; + }); + + doc.y = currentY; } - }); - writeStream.on('error', reject); + + // 辅助函数:添加base64图片 + function addBase64Image(base64String, maxWidth = 400, maxHeight = 300) { + try { + const base64Data = base64String.replace(/^data:image\/\w+;base64,/, ''); + const imageBuffer = Buffer.from(base64Data, 'base64'); + const image = doc.openImage(imageBuffer); + const scale = Math.min(maxWidth / image.width, maxHeight / image.height, 1); + doc.image(image, doc.page.margins.left, doc.y, { + width: image.width * scale, + height: image.height * scale + }); + doc.y += image.height * scale + 10; + } catch (error) { + console.error('添加图片失败:', error); + doc.fontSize(10).text('图片加载失败', doc.page.margins.left, doc.y).moveDown(); + } + } + + // 获取考试信息 + const startTime = paper.paper_start_time || ''; + const endTime = paper.paper_end_time || ''; + const durationMinutes = paper.paper_minutes || 0; + const totalQuestions = paper.questions ? paper.questions.length : 0; + const totalScore = paper.paper_score || 0; + const realScore = paper.paper_score_real || 0; + const admissionTicket = examinee.examinee_admission_ticket || ''; + + // 绘制考生信息表格 + drawTable( + ['姓名', '身份证号', '准考证号'], + [[examineeName, idCard, admissionTicket]] + ); + doc.moveDown(); + + // 绘制考试信息表格 + drawTable( + ['考试日期', '开始时间', '结束时间', '用时(分钟)'], + [[examDate, startTime ? startTime.split(' ')[1] : '', endTime ? endTime.split(' ')[1] : '', durationMinutes.toString()]] + ); + doc.moveDown(); + + // 绘制试卷信息表格 + drawTable( + ['总题量', '试卷总分', '考试得分'], + [[totalQuestions.toString(), totalScore.toString(), realScore.toString()]] + ); + doc.moveDown(2); + + // 添加题目信息(如果有) + if (paper.questions && paper.questions.length > 0) { + // 对questions列表按照question_type和id进行排序 + paper.questions.sort((a, b) => { + if (a.question_type !== b.question_type) { + return a.question_type.localeCompare(b.question_type); + } + return a.id - b.id; + }); + + paper.questions.forEach((question, index) => { + // 题目类型和描述 - 使用稍大的字体表示重点 + doc.fontSize(14).text(`第 ${index + 1} 题 (${question.question_type_name || question.question_type})`, doc.page.margins.left).moveDown(); + // 确保题干描述从左边距开始 + doc.fontSize(12).text(question.question_description || '', { + x: doc.page.margins.left, + width: doc.page.width - doc.page.margins.left - doc.page.margins.right + }).moveDown(); + + // 添加图片 + if (question.images && question.images.length > 0) { + doc.fontSize(12).text('图片:', doc.page.margins.left).moveDown(); + question.images.forEach((image) => { + if (image.image_base64) { + addBase64Image(image.image_base64); + } else { + doc.fontSize(10).text(`图片: ${image.image_name || '无名称'}`, doc.page.margins.left).moveDown(); + } + }); + } + + // 添加数据集 + if (question.datasets && question.datasets.length > 0) { + doc.fontSize(12).text('数据集:', doc.page.margins.left).moveDown(); + question.datasets.forEach((dataset, dataIndex) => { + doc.fontSize(10).text(`数据集 ${dataIndex + 1} (${dataset.dataset_name || '无名称'})`, doc.page.margins.left).moveDown(); + // 尝试解析数据集数据为表格 + try { + const datasetData = JSON.parse(dataset.dataset_data); + if (Array.isArray(datasetData) && datasetData.length > 0) { + // 假设第一行是表头 + const headers = Object.keys(datasetData[0]); + const rows = datasetData.map(item => headers.map(header => item[header])); + // 缩小单元格宽度以适应页面 + drawTable(headers, rows, 80, 20); + } + } catch (error) { + console.error('解析数据集失败:', error); + doc.fontSize(10).text(`数据集内容: ${dataset.dataset_data.substring(0, 100)}...`, doc.page.margins.left).moveDown(); + } + }); + } + + // 添加填空题 + if (question.blanks && question.blanks.length > 0) { + question.blanks.forEach((blank, blankIndex) => { + // 使用稍大字体突出显示填空标题 + doc.fontSize(13).text(`填空 ${blankIndex + 1}`, doc.page.margins.left).moveDown(); + // 确保问题描述从左边距开始 + doc.fontSize(12).text(blank.blank_description || '', { + x: doc.page.margins.left, + width: doc.page.width - doc.page.margins.left - doc.page.margins.right + }).moveDown(); + drawTable( + ['正确答案', '考生答案', '分值', '得分'], + [ + [ + Array.isArray(blank.correct_answers) ? blank.correct_answers.join(', ') : blank.correct_answers || '', + Array.isArray(blank.examinee_answers) ? blank.examinee_answers.join(', ') : blank.examinee_answers || '', + (blank.score || 0).toString(), + (blank.score_real || 0).toString() + ] + ], + 100 + ); + }); + } + + // 添加选择题 + if (question.choices && question.choices.length > 0) { + question.choices.forEach((choice, choiceIndex) => { + // 使用稍大字体突出显示选择题标题 + doc.fontSize(13).text(`选择题 ${choiceIndex + 1}`, doc.page.margins.left).moveDown(); + // 确保问题描述从左边距开始 + doc.fontSize(12).text(choice.choice_description || '', { + x: doc.page.margins.left, + width: doc.page.width - doc.page.margins.left - doc.page.margins.right + }).moveDown(); + // 显示选项 + if (choice.choice_options && choice.choice_options.length > 0) { + const optionsText = choice.choice_options.map((option, i) => { + const optionLabel = String.fromCharCode(65 + i); // A, B, C, D... + return `${optionLabel}. ${option}`; + }).join(' '); + doc.fontSize(10).text(`选项: ${optionsText}`, doc.page.margins.left).moveDown(); + } + drawTable( + ['正确答案', '考生答案', '分值', '得分'], + [ + [ + Array.isArray(choice.correct_answers) ? choice.correct_answers.join(', ') : choice.correct_answers || '', + Array.isArray(choice.examinee_answers) ? choice.examinee_answers.join(', ') : choice.examinee_answers || '', + (choice.score || 0).toString(), + (choice.score_real || 0).toString() + ] + ], + 100 + ); + }); + } + + // 分页处理 + if (doc.y > 700) { + doc.addPage(); + // 在新页面上重新设置字体,确保字体设置正确应用 + if (currentFont) { + doc.font(currentFont); + } + } + }); + } + + // 结束文档 + doc.end(); + + // 处理流事件 + writeStream.on('finish', async () => { + try { + // 复制文件到桌面 + const desktopFilePath = await copyToDesk(tempFilePath); + console.log('PDF生成成功,文件已保存到桌面:', desktopFilePath); + resolve({ filePath: desktopFilePath }); + } catch (copyError) { + console.error('复制文件到桌面失败,返回临时文件路径:', copyError); + // 如果复制到桌面失败,返回临时文件路径 + resolve({ filePath: tempFilePath }); + } + }); + + writeStream.on('error', (error) => { + console.error('PDF写入失败:', error); + reject(error); + }); + } catch (error) { + console.error('PDF生成过程中发生错误:', error); + reject(error); + } }); } catch (error) { - console.error('生成PDF失败:', error); - throw error; + console.error('生成试卷PDF失败:', error); + return { + success: false, + message: `生成PDF失败: ${error.message}`, + errorStack: error.stack + }; } +} + +/** + * 初始化文件服务IPC处理程序 + * @param {*} ipcMain - Electron的ipcMain实例 + */ +function initFileIpc(ipcMain) { + // 生成试卷PDF + ipcMain.handle('file-generate-paper-pdf', async (event, jsonString) => { + try { + return await generatePaperPdfService(jsonString); + } catch (error) { + console.error('生成试卷PDF失败:', error); + return { + success: false, + message: `生成PDF失败: ${error.message}` + }; + } + }); +} + +// 导出模块 +exports = module.exports = { + generatePaperPdfService, + initFileIpc }; \ No newline at end of file diff --git a/src/components/user/ExamineeLayout.vue b/src/components/user/ExamineeLayout.vue index 5a6e72e..8219a08 100644 --- a/src/components/user/ExamineeLayout.vue +++ b/src/components/user/ExamineeLayout.vue @@ -35,7 +35,7 @@ export default { .content-container { flex: 1; - padding: 20px; + padding: 15px; overflow-y: auto; background-color: #f0f2f5; } diff --git a/src/preload.js b/src/preload.js index e9f0159..8fa9e8e 100644 --- a/src/preload.js +++ b/src/preload.js @@ -1,106 +1,166 @@ // Preload script runs in a context that has access to both Node.js and browser APIs -const { contextBridge, ipcRenderer } = require('electron') +// 在preload.js文件中添加examingProcessPaper API并移除重复的fileGeneratePaperPdf +const { contextBridge, ipcRenderer } = require("electron"); // 暴露API给渲染进程 -contextBridge.exposeInMainWorld('electronAPI', { +contextBridge.exposeInMainWorld("electronAPI", { // 数据库相关 - checkDatabaseInitialized: () => ipcRenderer.invoke('check-database-initialized'), - initializeDatabase: () => ipcRenderer.invoke('initialize-database'), + checkDatabaseInitialized: () => + ipcRenderer.invoke("check-database-initialized"), + initializeDatabase: () => ipcRenderer.invoke("initialize-database"), + // 添加检查user.db是否存在的API + checkUserDbExists: () => ipcRenderer.invoke("checkUserDbExists"), + // 添加静默初始化用户数据库的API + initializeUserDatabaseSilently: () => + ipcRenderer.invoke("initializeUserDatabaseSilently"), // 配置服务相关接口 - 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), + 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), + 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), + 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方法,使其接收两个参数并正确传递 - questionUpdateChoice: (id, choiceData) => ipcRenderer.invoke('question-update-choice', id, choiceData), - questionDeleteChoice: (id) => ipcRenderer.invoke('question-delete-choice', id), + questionUpdateChoice: (id, choiceData) => + ipcRenderer.invoke("question-update-choice", id, choiceData), + questionDeleteChoice: (id) => + ipcRenderer.invoke("question-delete-choice", id), // 添加questionCreateChoice方法,调用主进程中已注册的'question-create-choice'通道 - questionCreateChoice: (choiceData) => ipcRenderer.invoke('question-create-choice', choiceData), - questionAddFillBlank: (fillBlankData) => ipcRenderer.invoke('question-add-fill-blank', fillBlankData), + questionCreateChoice: (choiceData) => + ipcRenderer.invoke("question-create-choice", choiceData), + questionAddFillBlank: (fillBlankData) => + ipcRenderer.invoke("question-add-fill-blank", fillBlankData), // 添加新的questionCreateFillBlank方法,调用主进程中已注册的'question-create-fill-blank'通道 - questionCreateFillBlank: (fillBlankData) => ipcRenderer.invoke('question-create-fill-blank', fillBlankData), + questionCreateFillBlank: (fillBlankData) => + ipcRenderer.invoke("question-create-fill-blank", fillBlankData), // 修改questionUpdateFillBlank方法,使其接收两个参数并正确传递 - questionUpdateFillBlank: (id, fillBlankData) => ipcRenderer.invoke('question-update-fill-blank', id, 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), + questionUpdateFillBlank: (id, fillBlankData) => + ipcRenderer.invoke("question-update-fill-blank", id, 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), // 添加新的questionDelete方法,调用主进程中已注册的'question-delete'通道 - questionDelete: (questionId) => ipcRenderer.invoke('question-delete', questionId), + questionDelete: (questionId) => + ipcRenderer.invoke("question-delete", questionId), // 修改后 - questionGetStatistics: () => ipcRenderer.invoke('question-get-count-and-score'), - questionGetQuestionById: (questionId) => ipcRenderer.invoke('question-get-question-by-id', questionId), + questionGetStatistics: () => + ipcRenderer.invoke("question-get-count-and-score"), + questionGetQuestionById: (questionId) => + ipcRenderer.invoke("question-get-question-by-id", questionId), // 考试服务相关接口 - examCreate: (examData) => ipcRenderer.invoke('exam-create', examData), - examUpdate: (id, examData) => ipcRenderer.invoke('exam-update', { id, examData }), - examFetchLast: () => ipcRenderer.invoke('exam-fetch-last'), - examFetchAll: () => ipcRenderer.invoke('exam-fetch-all'), - examFetchById: (id) => ipcRenderer.invoke('exam-fetch-by-id', id), - examDelete: (id) => ipcRenderer.invoke('exam-delete', id), + examCreate: (examData) => ipcRenderer.invoke("exam-create", examData), + examUpdate: (id, examData) => + ipcRenderer.invoke("exam-update", { id, examData }), + examFetchLast: () => ipcRenderer.invoke("exam-fetch-last"), + examFetchAll: () => ipcRenderer.invoke("exam-fetch-all"), + examFetchById: (id) => ipcRenderer.invoke("exam-fetch-by-id", id), + examDelete: (id) => ipcRenderer.invoke("exam-delete", id), // 考生服务相关接口 - examineeFetchAll: () => ipcRenderer.invoke('examinee-fetch-all'), - userLogin: ({ idCard, admissionTicket }) => ipcRenderer.invoke('user-login', { idCard, admissionTicket }), - examineeFetchById: (id) => ipcRenderer.invoke('examinee-fetch-by-id', id), - examineeCreate: (examineeData) => ipcRenderer.invoke('examinee-create', examineeData), - examineeUpdate: (id, examineeData) => ipcRenderer.invoke('examinee-update', { id, examineeData }), - examineeDelete: (id) => ipcRenderer.invoke('examinee-delete', id), + examineeFetchAll: () => ipcRenderer.invoke("examinee-fetch-all"), + userLogin: ({ idCard, admissionTicket }) => + ipcRenderer.invoke("user-login", { idCard, admissionTicket }), + examineeFetchById: (id) => ipcRenderer.invoke("examinee-fetch-by-id", id), + examineeCreate: (examineeData) => + ipcRenderer.invoke("examinee-create", examineeData), + examineeUpdate: (id, examineeData) => + ipcRenderer.invoke("examinee-update", { id, examineeData }), + examineeDelete: (id) => ipcRenderer.invoke("examinee-delete", id), + // 在考生考试服务相关接口部分添加examingCheckPaperAnswers // 考生考试服务相关接口 - examingGeneratePaper: ({ examineeId, examId }) => ipcRenderer.invoke('examing-generate-paper', { examineeId, examId }), - examingGetPaperStatus: ({ examineeId, examId }) => ipcRenderer.invoke('examing-get-paper-status', { examineeId, examId }), - examingUpdatePaperStatus: ({ paperId, status }) => ipcRenderer.invoke('examing-update-paper-status', { paperId, status }), - examingLoadPaperSerial: ({ paperId }) => ipcRenderer.invoke('examing-load-paper-serial', { paperId }), - examingGetQuestionByRelatedId: ({ tableName, relatedId }) => ipcRenderer.invoke('examing-get-question-by-related-id', { tableName, relatedId }), + examingGeneratePaper: ({ examineeId, examId }) => + ipcRenderer.invoke("examing-generate-paper", { examineeId, examId }), + examingGetPaperStatus: ({ examineeId, examId }) => + ipcRenderer.invoke("examing-get-paper-status", { examineeId, examId }), + examingUpdatePaperStatus: ({ paperId, status }) => + ipcRenderer.invoke("examing-update-paper-status", { paperId, status }), + examingLoadPaperSerial: ({ paperId }) => + ipcRenderer.invoke("examing-load-paper-serial", { paperId }), + examingGetQuestionByRelatedId: ({ tableName, relatedId }) => + ipcRenderer.invoke("examing-get-question-by-related-id", { + tableName, + relatedId, + }), // 修复:examingUpdateAnswer接口的参数结构 - examingUpdateAnswer: ({ tableName, id, answers }) => ipcRenderer.invoke('examing-update-answer', { tableName, id, answers }), - examingStartPaper: ({ paperId }) => ipcRenderer.invoke('examing-start-paper', { paperId }), - examingSubmitPaper: ({ paperId }) => ipcRenderer.invoke('examing-submit-paper', { paperId }), - examingEndPaper: ({ paperId }) => ipcRenderer.invoke('examing-end-paper', { paperId }), - examingProcessPaper: ({ paperId }) => ipcRenderer.invoke('examing-process-paper', { paperId }), - examingCheckPaperAnswers: ({ paperId }) => ipcRenderer.invoke('examing-check-paper-answers', { paperId }), + examingUpdateAnswer: ({ tableName, id, answers }) => + ipcRenderer.invoke("examing-update-answer", { tableName, id, answers }), + // 在考生考试服务相关接口部分确保examingStartPaper正确暴露 + examingStartPaper: ({ paperId }) => + ipcRenderer.invoke("examing-start-paper", { paperId }), + examingSubmitPaper: ({ paperId }) => + ipcRenderer.invoke("examing-submit-paper", { paperId }), + // 确保examingGetExamResult API正确暴露 + examingGetExamResult: ({ paperId }) => + ipcRenderer.invoke("examing-get-exam-result", { paperId }), + examingCheckPaperAnswers: ({ paperId }) => + ipcRenderer.invoke("examing-check-paper-answers", { paperId }), + examingProcessPaper: ({ paperId }) => + ipcRenderer.invoke("examing-process-paper", { paperId }), // 文件服务相关接口 - fileTest: () => ipcRenderer.invoke('file-test'), - fileGeneratePdf: (pdfData, fileName) => ipcRenderer.invoke('file-generate-pdf', pdfData, fileName), - fileGeneratePaperPdf: (jsonString) => ipcRenderer.invoke('file-generate-paper-pdf', jsonString), - fileCopyToDesktop: (filePath) => ipcRenderer.invoke('file-copy-to-desktop', filePath), + // 保留一个fileGeneratePaperPdf定义,移除重复的 + fileGeneratePaperPdf: (jsonString) => + ipcRenderer.invoke("file-generate-paper-pdf", jsonString), + fileTest: () => ipcRenderer.invoke("file-test"), + fileGeneratePdf: (pdfData, fileName) => + ipcRenderer.invoke("file-generate-pdf", pdfData, fileName), + fileCopyToDesktop: (filePath) => + ipcRenderer.invoke("file-copy-to-desktop", filePath), // 保留原有的ipcRenderer接口,确保兼容性 ipcRenderer: { - invoke: (channel, data) => ipcRenderer.invoke(channel, data) - } -}) + invoke: (channel, data) => ipcRenderer.invoke(channel, data), + }, +}); // 也保留原来的electron对象,确保现有功能正常 -contextBridge.exposeInMainWorld('electron', { +contextBridge.exposeInMainWorld("electron", { ipcRenderer: { - invoke: (channel, data) => ipcRenderer.invoke(channel, data) - } -}) \ No newline at end of file + invoke: (channel, data) => ipcRenderer.invoke(channel, data), + }, +}); diff --git a/src/router/index.js b/src/router/index.js index 36c81c5..c2e1f7c 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -11,6 +11,8 @@ import ExamManagementView from '../views/admin/ExamManagementView.vue' import ExamineeHomeView from '../views/user/ExamineeHomeView.vue' // 添加ExamingView组件导入 import ExamingView from '../views/user/ExamingView.vue' +// 添加EndView组件导入 +import EndView from '../views/user/EndView.vue' Vue.use(VueRouter) @@ -41,7 +43,9 @@ const routes = [ children: [ { path: 'home', name: 'ExamineeHome', component: ExamineeHomeView }, // 添加考试页面路由 - { path: 'exam/:paperId', name: 'Examing', component: ExamingView }, + { path: 'examing/:paperId', name: 'Examing', component: ExamingView }, + // 添加考试结果页面路由 + { path: 'end/:paperId', name: 'End', component: EndView }, // 可以在这里添加更多考生相关的路由 ] } diff --git a/src/store/index.js b/src/store/index.js index 77dfee7..19cd2f3 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -31,8 +31,16 @@ export default new Vuex.Store({ state.userType = null }, // 新增:设置试卷信息 + // 确保setPaper mutation正确实现,保留完整数据结构 setPaper(state, paperInfo) { - state.paper = paperInfo + // 确保paperInfo是对象类型 + if (typeof paperInfo === 'object' && paperInfo !== null) { + state.paper = { ...paperInfo }; + console.log('store更新后的paper数据:', state.paper); + } else { + console.error('尝试设置无效的试卷数据:', paperInfo); + state.paper = {}; + } }, // 新增:清除试卷信息 clearPaper(state) { diff --git a/src/views/WelcomeView.vue b/src/views/WelcomeView.vue index 654deeb..5069c69 100644 --- a/src/views/WelcomeView.vue +++ b/src/views/WelcomeView.vue @@ -137,8 +137,27 @@ export default { }, mounted () { this.checkDatabaseStatus() + this.checkAndInitializeUserDb() }, methods: { + // 检查并静默初始化用户数据库 + async checkAndInitializeUserDb() { + try { + console.log('检查user.db是否存在...') + const userDbExists = await window.electronAPI.checkUserDbExists() + console.log('user.db存在状态:', userDbExists) + + if (!userDbExists) { + console.log('user.db不存在,开始静默初始化...') + await window.electronAPI.initializeUserDatabaseSilently() + console.log('user.db静默初始化完成') + } + } catch (error) { + console.error('检查或初始化user.db失败:', error) + // 这里不显示错误信息,因为是静默初始化 + } + }, + async checkDatabaseStatus () { try { console.log('组件挂载 - 开始检查数据库初始化状态') diff --git a/src/views/user/EndView.vue b/src/views/user/EndView.vue new file mode 100644 index 0000000..08a9556 --- /dev/null +++ b/src/views/user/EndView.vue @@ -0,0 +1,434 @@ + + + + + \ No newline at end of file diff --git a/src/views/user/ExamineeHomeView.vue b/src/views/user/ExamineeHomeView.vue index 08c531d..edc7fb2 100644 --- a/src/views/user/ExamineeHomeView.vue +++ b/src/views/user/ExamineeHomeView.vue @@ -256,82 +256,90 @@ export default { }, // 开始考试方法 + // 修改startExam方法,移除保存完整试卷信息到store的代码 async startExam() { - if (!this.lastExam) { - this.$message.warning('请先获取考试信息') - return - } + if (!this.lastExam) { + this.$message.warning('请先获取考试信息') + return + } - if (!this.examinee || !this.examinee.id || !this.examinee.examinee_name) { - this.$message.warning('未获取到完整的考生信息') - return - } + if (!this.examinee || !this.examinee.id || !this.examinee.examinee_name) { + this.$message.warning('未获取到完整的考生信息') + return + } - this.isLoading = true + this.isLoading = true - try { - console.log('开始生成试卷...', { examinee: this.examinee, exam: this.lastExam }) + try { + console.log('开始生成试卷...', { examinee: this.examinee, exam: this.lastExam }) - // 创建可序列化的完整考生数据对象 - const examineeData = { - id: this.examinee.id, - examinee_name: this.examinee.examinee_name || '', - examinee_id_card: this.examinee.examinee_id_card || '', - examinee_admission_ticket: this.examinee.examinee_admission_ticket || '', - examinee_gender: this.examinee.examinee_gender || '', - examinee_unit: this.examinee.examinee_unit || '', - written_exam_room: this.examinee.written_exam_room || '', - written_exam_seat: this.examinee.written_exam_seat || '', - computer_exam_room: this.examinee.computer_exam_room || '', - computer_exam_seat: this.examinee.computer_exam_seat || '' - } + // 创建可序列化的完整考生数据对象 + const examineeData = { + id: this.examinee.id, + examinee_name: this.examinee.examinee_name || '', + examinee_id_card: this.examinee.examinee_id_card || '', + examinee_admission_ticket: this.examinee.examinee_admission_ticket || '', + examinee_gender: this.examinee.examinee_gender || '', + examinee_unit: this.examinee.examinee_unit || '', + written_exam_room: this.examinee.written_exam_room || '', + written_exam_seat: this.examinee.written_exam_seat || '', + computer_exam_room: this.examinee.computer_exam_room || '', + computer_exam_seat: this.examinee.computer_exam_seat || '' + } - // 使用JSON序列化/反序列化确保对象可克隆 - const examData = JSON.parse(JSON.stringify({ - id: this.lastExam.id, - exam_name: this.lastExam.exam_name || '', - exam_description: this.lastExam.exam_description || '', - exam_minutes: this.lastExam.exam_minutes || 0, - exam_minutes_min: this.lastExam.exam_minutes_min || 0, - exam_notice: this.lastExam.exam_notice || [] - })) + // 使用JSON序列化/反序列化确保对象可克隆 + const examData = JSON.parse(JSON.stringify({ + id: this.lastExam.id, + exam_name: this.lastExam.exam_name || '', + exam_description: this.lastExam.exam_description || '', + exam_minutes: this.lastExam.exam_minutes || 0, + exam_minutes_min: this.lastExam.exam_minutes_min || 0, + exam_notice: this.lastExam.exam_notice || [] + })) - // 直接调用ipcRenderer接口,跳过preload.js中定义的不匹配方法 - const result = await window.electronAPI.ipcRenderer.invoke('examing-generate-paper', { - examineeData: examineeData, - examData: examData - }) + // 直接调用ipcRenderer接口,跳过preload.js中定义的不匹配方法 + const result = await window.electronAPI.ipcRenderer.invoke('examing-generate-paper', { + examineeData: examineeData, + examData: examData + }) - if (result && result.success) { - console.log('生成试卷成功:', result) + if (result && result.success) { + console.log('生成试卷成功:', result) - // 新增:保存试卷信息到store - this.$store.commit('setPaper', result.data) + // 移除:不再保存完整试卷信息到store + // this.$store.commit('setPaper', result.data) - this.$alert( - '已完成组卷,点击"进入考试"开始答题', - '组卷完成', - { - confirmButtonText: '进入考试', - type: 'success' - } - ).then(() => { - // 使用正确的路由名称进行跳转 - this.$router.push({ - name: 'Examing', - params: { paperId: result.data.id } - }) - }) - } else { - console.error('生成试卷失败:', result) - this.$message.error(`生成试卷失败: ${result.message || '未知错误'}`) - } - } catch (error) { - console.error('生成试卷异常:', error) - this.$message.error(`无法生成试卷: ${error.message || '未知错误'}`) - } finally { - this.isLoading = false - } + // 仅保存试卷ID到localStorage(作为备用机制) + try { + localStorage.setItem('currentPaperId', result.data.id) + } catch (error) { + console.warn('保存试卷ID到localStorage失败:', error) + } + + this.$alert( + '已完成组卷,点击"进入考试"开始答题', + '组卷完成', + { + confirmButtonText: '进入考试', + type: 'success' + } + ).then(() => { + // 使用正确的路由名称进行跳转 + this.$router.push({ + name: 'Examing', + params: { paperId: result.data.id } + }) + }) + } else { + console.error('生成试卷失败:', result) + this.$message.error(`生成试卷失败: ${result.message || '未知错误'}`) + } + } catch (error) { + console.error('生成试卷异常:', error) + this.$message.error(`无法生成试卷: ${error.message || '未知错误'}`) + } finally { + this.isLoading = false + } } } } diff --git a/src/views/user/ExamingView.vue b/src/views/user/ExamingView.vue index ad02f27..695284b 100644 --- a/src/views/user/ExamingView.vue +++ b/src/views/user/ExamingView.vue @@ -30,10 +30,14 @@
第 {{ currentQuestion }} 题 - ({{ currentQuestionData.question_detail && currentQuestionData.question_detail.score || 0 }}分) + + + {{ currentQuestionData.question && currentQuestionData.question.type_info && currentQuestionData.question.type_info.item_name || '' }} + + ({{ currentQuestionData.question_detail && currentQuestionData.question_detail.score || 0 }}分) +
-
{{ currentQuestionData.question && currentQuestionData.question.type_info && currentQuestionData.question.type_info.item_name || '' }}
@@ -99,7 +103,8 @@ export default { name: 'ExamingView', components: { - ElButton: require('element-ui').Button + ElButton: require('element-ui').Button, + ElTag: require('element-ui').Tag }, data() { return { @@ -113,7 +118,8 @@ export default { currentImage: '', timer: null, processTimer: null, - paperId: '' + paperId: '', + blankAnswerTip: '数字请保留2位小数,如“100.00”' } }, computed: { @@ -138,7 +144,27 @@ export default { }, mounted() { // 检查是否已登录 - if (!this.$store.state.isLoggedIn || !this.paperId) { + if (!this.$store.state.isLoggedIn) { + this.$router.push('/') + return + } + + // 优先从路由参数获取paperId + if (!this.paperId && this.$route.params && this.$route.params.paperId) { + this.paperId = this.$route.params.paperId + } + + // 如果路由参数中没有paperId,尝试从localStorage获取 + if (!this.paperId) { + try { + this.paperId = localStorage.getItem('currentPaperId') + } catch (error) { + console.error('从localStorage获取paperId失败:', error) + } + } + + // 如果仍然没有paperId,跳转到首页 + if (!this.paperId) { this.$router.push('/') return } @@ -162,13 +188,13 @@ export default { // 初始化考试 async initializeExam() { try { - console.warn(this.paper) - console.warn(this.paperId) - // 1. 检查store中是否已有试卷信息 - if (!this.paper || !this.paper.id) { - // 如果store中没有,尝试从后端获取 - await this.loadPaperInfo(this.paperId) - } + console.log('初始化考试,paperId:', this.paperId) + + // 清除可能存在的旧试卷信息 + this.$store.commit('clearPaper') + + // 1. 从接口获取试卷信息 + await this.loadPaperInfo(this.paperId) // 2. 加载试题列表 await this.loadQuestionList(this.paperId) @@ -265,14 +291,34 @@ export default { async loadPaperInfo(paperId) { try { console.log('开始加载试卷信息,paperId:', paperId) - // 修复:使用直接调用方式 + + // 清除可能存在的旧试卷信息 + this.$store.commit('clearPaper') + + // 1. 先获取试卷的当前状态 const result = await window.electronAPI.examingProcessPaper({ paperId: paperId }) console.log('loadPaperInfo结果:', result) if (result && result.success && result.data) { - // 将返回的paper对象同步到store中 - this.$store.commit('setPaper', result.data) - this.canSubmit = result.data.paper_minutes_min ? false : true + // 2. 检查试卷状态,如果是未开始(0),则调用startPaper开始考试 + if (result.data.paper_status === 0) { + console.log('试卷状态为未开始,调用startPaper开始考试') + const startResult = await window.electronAPI.examingStartPaper({ paperId: paperId }) + console.log('开始考试结果:', startResult); + if (startResult && startResult.success && startResult.data) { + // 使用开始考试后的数据 + this.$store.commit('setPaper', startResult.data) + this.canSubmit = startResult.data.paper_minutes_min ? false : true + } else { + console.error('开始考试失败') + this.$store.commit('setPaper', result.data) + this.canSubmit = result.data.paper_minutes_min ? false : true + } + } else { + // 如果不是未开始状态,直接使用返回的数据 + this.$store.commit('setPaper', result.data) + this.canSubmit = result.data.paper_minutes_min ? false : true + } } else { console.error('加载试卷信息失败: 返回数据无效') // 如果无法获取试卷信息,创建一个默认的试卷对象以避免页面白屏 @@ -499,49 +545,88 @@ export default { await this.loadQuestionDetail(serialNo) }, - // 交卷方法 + // 交卷方法 - 确保数据完全保存到store后再跳转 async submitExam() { try { if (!this.canSubmit) { - this.$message.warning('暂不能交卷,请先完成考试或等待最小时长') - return + this.$message.warning('您的答案尚未保存,请等待保存完成后再提交'); + return; } // 显示确认对话框 - this.$confirm('确定要交卷吗?交卷后将无法修改答案。', '确认交卷', { + const confirmResult = await this.$confirm('确定要提交试卷吗?提交后将无法继续答题。', '确认提交', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' - }).then(async () => { - // 保存当前题目的答案 - await this.saveCurrentAnswer() + }); - // 修复:使用直接调用方式 - const result = await window.electronAPI.examingSubmitPaper({ paperId: this.paperId }) + // 保存当前答案 + await this.saveCurrentAnswer(); - if (result && result.success) { - this.$message.success('交卷成功!') - // 清理定时器 - if (this.timer) { - clearInterval(this.timer) - this.timer = null + // 调用提交试卷API + const result = await window.electronAPI.examingSubmitPaper({ paperId: this.paper.id }); + console.log('提交试卷API返回:', result); + + if (result && result.success) { + // 确保result.data是字符串类型 + const paperDataStr = typeof result.data === 'string' ? result.data : JSON.stringify(result.data); + + try { + // 尝试解析数据 + const paperData = JSON.parse(paperDataStr); + // console.log('解析后的试卷数据:', paperData); + + // 1. 将试卷数据保存到store + this.$store.commit('setPaper', paperData); + + // 2. 同时保存到localStorage作为备用 + localStorage.setItem('lastExamPaper', paperDataStr); + + // console.log('保存到store后,store中的paper数据:', this.$store.state.paper); + + // 生成PDF + const pdfResult = await window.electronAPI.fileGeneratePaperPdf(paperDataStr); + if (pdfResult && pdfResult.filePath) { + this.pdfPath = pdfResult; + console.log('PDF生成成功,保存路径:', pdfResult.filePath); + } else { + this.$message.error('PDF生成失败,但不影响考试结果'); // 修改错误消息,不影响交卷流程 + console.error('PDF生成失败:', pdfResult); + // 即使PDF生成失败,也继续交卷流程 } - if (this.processTimer) { - clearInterval(this.processTimer) - this.processTimer = null - } - // 跳转到结果页面 - this.$router.push('/user/exam-result/' + this.paperId) - } else { - this.$message.error('交卷失败: ' + (result.message || '未知错误')) + + this.$message.success('试卷提交成功!'); + + // 3. 增加延迟时间,确保数据完全保存 + setTimeout(() => { + this.$router.push({ + name: 'End', + params: { paperId: this.paper.id } + }); + }, 300); // 增加延迟时间到300ms + } catch (jsonError) { + console.error('解析试卷数据失败:', jsonError); + this.$message.error('处理试卷数据失败'); + + // 即使解析失败,也尝试保存数据 + this.$store.commit('setPaper', { id: this.paper.id, rawData: result.data }); + localStorage.setItem('lastExamPaper', paperDataStr); + + setTimeout(() => { + this.$router.push({ + name: 'End', + params: { paperId: this.paper.id } + }); + }, 300); } - }).catch(() => { - // 用户取消交卷 - this.$message.info('已取消交卷') - }) + } else { + throw new Error(result?.message || '提交失败'); + } } catch (error) { - console.error('交卷过程异常:', error) - this.$message.error('交卷失败: ' + error.message) + if (error !== 'cancel') { + console.error('提交试卷失败:', error); + this.$message.error('已取消交卷'); + } } }, @@ -553,17 +638,21 @@ export default { // 保存当前题目的答案 await this.saveCurrentAnswer() - // 修复:使用直接调用方式 - const result = await window.electronAPI.examingSubmitPaper({ paperId: this.paperId }) + // 修复:使用与high_version一致的接口调用方式 + const result = await window.electronAPI.endPaper(this.paper.id) if (result && result.success) { + // 将返回的paper对象同步到store中 + if (result.data) { + this.$store.commit('setPaper', result.data) + } // 清理定时器 if (this.processTimer) { clearInterval(this.processTimer) this.processTimer = null } - // 跳转到结果页面 - this.$router.push('/user/exam-result/' + this.paperId) + // 跳转到结果页面(根据您调整的路由) + this.$router.push('/examinee/end/' + this.paperId) } else { this.$message.error('自动交卷失败,请手动交卷') } @@ -756,7 +845,7 @@ export default { /* 左侧边栏:试题序号列表 - 固定宽度,可垂直滚动 */ .exam-sidebar { - width: 300px; + width: 280px; background-color: #fff; border-radius: 4px; padding: 1rem; @@ -765,6 +854,7 @@ export default { flex-direction: column; overflow-y: auto; flex-shrink: 0; + margin-right:10px; } /* 左侧边栏自定义滚动条 */ @@ -882,7 +972,6 @@ export default { .question-score { font-size: 14px; - color: #f56c6c; font-weight: 500; } @@ -1074,7 +1163,7 @@ export default { display: flex; justify-content: center; gap: 15px; - padding: 15px; + padding: 8px; background-color: white; margin-top: 10px; } diff --git a/vue.config.js b/vue.config.js index 5c19788..aa32a77 100644 --- a/vue.config.js +++ b/vue.config.js @@ -4,7 +4,7 @@ module.exports = { config .plugin('html') .tap(args => { - args[0].title = '统计数据考试系统' + args[0].title = '统计技能考试系统' return args }) },