测试pdf中文字体

This commit is contained in:
chenqiang 2025-09-05 16:15:20 +08:00
parent f3f302da49
commit d09a810903
14 changed files with 1619 additions and 1235 deletions

View File

@ -440,7 +440,7 @@ async function generateExamineePaper(examineeData, examData) {
* @returns {Promise<Array>} - 包含试题序列的数组 * @returns {Promise<Array>} - 包含试题序列的数组
*/ */
async function loadPaperSerial(paperId) { async function loadPaperSerial(paperId) {
console.log('0903调试loadPaperSerial: ', paperId); // console.log('0903调试loadPaperSerial: ', paperId);
try { try {
// 1. 获取数据库连接 // 1. 获取数据库连接
const userDb = await getDbConnection(getUserDbPath()); const userDb = await getDbConnection(getUserDbPath());
@ -905,7 +905,6 @@ async function checkPaperAnswers(paperId) {
); );
if (!paper) { if (!paper) {
await closeAllConnections();
return { return {
success: false, success: false,
message: `未找到ID为${paperId}的试卷`, message: `未找到ID为${paperId}的试卷`,
@ -1047,7 +1046,7 @@ async function checkPaperAnswers(paperId) {
// 5. 查询更新后的试卷信息(包含关联数据) // 5. 查询更新后的试卷信息(包含关联数据)
const updatedPaper = await getPaper(paperId); const updatedPaper = await getPaper(paperId);
console.log(updatedPaper); // console.log(updatedPaper);
return { return {
success: true, success: true,
@ -1086,7 +1085,6 @@ async function getPaper(paperId) {
); );
if (!paper) { if (!paper) {
await closeAllConnections();
return { return {
success: false, success: false,
message: `未找到ID为${paperId}的试卷`, message: `未找到ID为${paperId}的试卷`,
@ -1183,9 +1181,6 @@ async function getPaper(paperId) {
questions: questionsWithDetails questions: questionsWithDetails
}; };
// 关闭数据库连接
await closeAllConnections();
return { return {
success: true, success: true,
message: '获取试卷成功', message: '获取试卷成功',
@ -1193,7 +1188,6 @@ async function getPaper(paperId) {
}; };
} catch (error) { } catch (error) {
console.error('获取试卷过程中发生错误:', error); console.error('获取试卷过程中发生错误:', error);
await closeAllConnections();
return { return {
success: false, success: false,
message: `获取试卷失败: ${error.message}`, message: `获取试卷失败: ${error.message}`,

View File

@ -52,224 +52,182 @@ exports.checkDatabaseInitialized = async function checkDatabaseInitialized() {
// 初始化系统数据库 // 初始化系统数据库
async function initializeSystemDatabase() { async function initializeSystemDatabase() {
console.log('开始初始化系统数据库...'); console.log('开始初始化系统数据库...');
const systemDbPath = getSystemDbPath(); const systemDbPath = getSystemDbPath();
const systemDb = await exports.getDbConnection(systemDbPath); const systemDb = await exports.getDbConnection(systemDbPath);
// 记录成功和失败的操作 // 创建表结构
const results = { success: [], failed: [] }; console.log('开始创建系统数据库表结构...');
// 创建表结构 // 创建config表
console.log('开始创建系统数据库表结构...'); try {
await systemDb.execAsync(systemSchema.config.trim());
console.log('创建 config 表成功');
} catch (error) {
console.error('创建 config 表失败:', error);
}
// 创建config表 // 创建dict_types表
try { try {
await systemDb.execAsync(systemSchema.config.trim()); await systemDb.execAsync(systemSchema.dict_types.trim());
console.log('创建 config 表成功'); console.log('创建 dict_types 表成功');
results.success.push('创建 config 表'); } catch (error) {
} catch (error) { console.error('创建 dict_types 表失败:', error);
console.error('创建 config 表失败:', error); }
results.failed.push({ operation: '创建 config 表', error: error.message });
}
// 创建dict_types表 // 创建dict_items表
try { try {
await systemDb.execAsync(systemSchema.dictTypes.trim()); await systemDb.execAsync(systemSchema.dict_items.trim());
console.log('创建 dict_types 表成功'); console.log('创建 dict_items 表成功');
results.success.push('创建 dict_types 表'); } catch (error) {
} catch (error) { console.error('创建 dict_items 表失败:', error);
console.error('创建 dict_types 表失败:', error); }
results.failed.push({ operation: '创建 dict_types 表', error: error.message });
}
// 创建dict_items表 // 创建questions表
try { try {
await systemDb.execAsync(systemSchema.dictItems.trim()); await systemDb.execAsync(systemSchema.questions.trim());
console.log('创建 dict_items 表成功'); console.log('创建 questions 表成功');
results.success.push('创建 dict_items 表'); } catch (error) {
} catch (error) { console.error('创建 questions 表失败:', error);
console.error('创建 dict_items 表失败:', error); }
results.failed.push({ operation: '创建 dict_items 表', error: error.message });
}
// 创建questions表 // 创建question_datasets表
try { try {
await systemDb.execAsync(systemSchema.questions.trim()); await systemDb.execAsync(systemSchema.question_datasets.trim());
console.log('创建 questions 表成功'); console.log('创建 question_datasets 表成功');
results.success.push('创建 questions 表'); } catch (error) {
} catch (error) { console.error('创建 question_datasets 表失败:', error);
console.error('创建 questions 表失败:', error); }
results.failed.push({ operation: '创建 questions 表', error: error.message });
}
// 创建question_datasets表 // 创建question_images表
try { try {
await systemDb.execAsync(systemSchema.questionDatasets.trim()); await systemDb.execAsync(systemSchema.question_images.trim());
console.log('创建 question_datasets 表成功'); console.log('创建 question_images 表成功');
results.success.push('创建 question_datasets 表'); } catch (error) {
} catch (error) { console.error('创建 question_images 表失败:', error);
console.error('创建 question_datasets 表失败:', error); }
results.failed.push({ operation: '创建 question_datasets 表', error: error.message });
}
// 创建question_images表 // 创建question_fill_table表
try { try {
await systemDb.execAsync(systemSchema.questionImages.trim()); await systemDb.execAsync(systemSchema.question_fill_table.trim());
console.log('创建 question_images 表成功'); console.log('创建 question_fill_table 表成功');
results.success.push('创建 question_images 表'); } catch (error) {
} catch (error) { console.error('创建 question_fill_table 表失败:', error);
console.error('创建 question_images 表失败:', error); }
results.failed.push({ operation: '创建 question_images 表', error: error.message });
}
// 创建question_fill_table表 // 创建question_fill_table_blanks表
try { try {
await systemDb.execAsync(systemSchema.questionFillTable.trim()); await systemDb.execAsync(systemSchema.question_fill_table_blanks.trim());
console.log('创建 question_fill_table 表成功'); console.log('创建 question_fill_table_blanks 表成功');
results.success.push('创建 question_fill_table 表'); } catch (error) {
} catch (error) { console.error('创建 question_fill_table_blanks 表失败:', error);
console.error('创建 question_fill_table 表失败:', error); }
results.failed.push({ operation: '创建 question_fill_table 表', error: error.message });
}
// 创建question_fill_table_blanks表 // 创建question_choices表
try { try {
await systemDb.execAsync(systemSchema.questionFillTableBlanks.trim()); await systemDb.execAsync(systemSchema.question_choices.trim());
console.log('创建 question_fill_table_blanks 表成功'); console.log('创建 question_choices 表成功');
results.success.push('创建 question_fill_table_blanks 表'); } catch (error) {
} catch (error) { console.error('创建 question_choices 表失败:', error);
console.error('创建 question_fill_table_blanks 表失败:', error); }
results.failed.push({ operation: '创建 question_fill_table_blanks 表', error: error.message });
}
// 创建question_choices表 // 创建question_fill_blanks表
try { try {
await systemDb.execAsync(systemSchema.questionChoices.trim()); await systemDb.execAsync(systemSchema.question_fill_blanks.trim());
console.log('创建 question_choices 表成功'); console.log('创建 question_fill_blanks 表成功');
results.success.push('创建 question_choices 表'); } catch (error) {
} catch (error) { console.error('创建 question_fill_blanks 表失败:', error);
console.error('创建 question_choices 表失败:', error); }
results.failed.push({ operation: '创建 question_choices 表', error: error.message });
}
// 创建question_fill_blanks表 // 创建question_judge表
try { try {
await systemDb.execAsync(systemSchema.questionFillBlanks.trim()); await systemDb.execAsync(systemSchema.question_judge.trim());
console.log('创建 question_fill_blanks 表成功'); console.log('创建 question_judge 表成功');
results.success.push('创建 question_fill_blanks 表'); } catch (error) {
} catch (error) { console.error('创建 question_judge 表失败:', error);
console.error('创建 question_fill_blanks 表失败:', error); }
results.failed.push({ operation: '创建 question_fill_blanks 表', error: error.message });
}
// 创建question_judge表 // 创建question_short表
try { try {
await systemDb.execAsync(systemSchema.questionJudge.trim()); await systemDb.execAsync(systemSchema.question_short.trim());
console.log('创建 question_judge 表成功'); console.log('创建 question_short 表成功');
results.success.push('创建 question_judge 表'); } catch (error) {
} catch (error) { console.error('创建 question_short 表失败:', error);
console.error('创建 question_judge 表失败:', error); }
results.failed.push({ operation: '创建 question_judge 表', error: error.message });
}
// 创建question_short表 // 创建exam表
try { try {
await systemDb.execAsync(systemSchema.questionShort.trim()); await systemDb.execAsync(systemSchema.exam.trim());
console.log('创建 question_short 表成功'); console.log('创建 exam 表成功');
results.success.push('创建 question_short 表'); } catch (error) {
} catch (error) { console.error('创建 exam 表失败:', error);
console.error('创建 question_short 表失败:', error); }
results.failed.push({ operation: '创建 question_short 表', error: error.message });
}
// 创建exam表 // 创建examinee表系统库中的考生表
try { try {
await systemDb.execAsync(systemSchema.exam.trim()); await systemDb.execAsync(systemSchema.examinee.trim());
console.log('创建 exam 表成功'); console.log('创建 examinee 表成功');
results.success.push('创建 exam 表'); } catch (error) {
} catch (error) { console.error('创建 examinee 表失败:', error);
console.error('创建 exam 表失败:', error); }
results.failed.push({ operation: '创建 exam 表', error: error.message });
}
// 创建examinee表 // 插入默认数据
try { 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 });
}
// 插入默认数据
console.log('开始插入默认数据...'); console.log('开始插入默认数据...');
// 处理密码哈希 // 插入admin用户
try { const adminPasswordHash = await bcrypt.hash('123456', 10);
const plainPassword = defaultData.config.find(item => item.key === 'admin_password').value; await systemDb.runAsync(
const hashedPassword = await bcrypt.hash(plainPassword, 10); 'INSERT INTO examinee (id_card, name, role, password) VALUES (?, ?, ?, ?)',
['admin', '管理员', 'admin', adminPasswordHash]
);
console.log('插入管理员用户成功');
// 更新密码为哈希值 // 插入config表数据
const configData = defaultData.config.map(item => { await systemDb.runAsync(
if (item.key === 'admin_password') { 'INSERT OR IGNORE INTO config (key, value, description) VALUES (?, ?, ?)',
return { ...item, value: hashedPassword }; ['initialized', 'false', '数据库初始化状态']
} );
return item; console.log('插入config表数据成功');
});
// 插入config表数据 // 批量插入dict_types数据
try { const dictTypes = defaultData.dictTypes;
await batchInsert(systemDb, 'config', configData); for (const type of dictTypes) {
console.log('插入 config 表数据成功'); await systemDb.runAsync(
results.success.push('插入 config 表数据'); 'INSERT INTO dict_types (code, name, description, sort_order) VALUES (?, ?, ?, ?)',
} catch (error) { [type.code, type.name, type.description, type.sortOrder]
console.error('插入 config 表数据失败:', error); );
results.failed.push({ operation: '插入 config 表数据', error: error.message });
}
} catch (error) {
console.error('处理密码哈希失败:', error);
results.failed.push({ operation: '处理密码哈希', error: error.message });
} }
console.log('插入dict_types表数据成功');
// 插入dict_types表数据 // 批量插入dict_items数据
try { const dictItems = defaultData.dictItems;
await batchInsert(systemDb, 'dict_types', defaultData.dictTypes); for (const item of dictItems) {
console.log('插入 dict_types 表数据成功'); await systemDb.runAsync(
results.success.push('插入 dict_types 表数据'); 'INSERT INTO dict_items (type_code, code, name, description, sort_order, is_active, parent_code) VALUES (?, ?, ?, ?, ?, ?, ?)',
} catch (error) { [
console.error('插入 dict_types 表数据失败:', error); item.typeCode,
results.failed.push({ operation: '插入 dict_types 表数据', error: error.message }); item.code,
item.name,
item.description,
item.sortOrder,
item.isActive ? 1 : 0,
item.parentCode
]
);
} }
console.log('插入dict_items表数据成功');
// 插入dict_items表数据 console.log('默认数据插入完成');
try { } catch (error) {
await batchInsert(systemDb, 'dict_items', defaultData.dictItems); console.error('插入默认数据失败:', error);
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('系统数据库初始化结果:'); return { success: true };
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;
} }
// 初始化用户数据库 // 初始化用户数据库 - 注意这里使用了exports导出
async function initializeUserDatabase() { exports.initializeUserDatabase = async function initializeUserDatabase() {
console.log('开始初始化用户数据库...'); console.log('开始初始化用户数据库...');
const userDbPath = getUserDbPath(); const userDbPath = getUserDbPath();
const userDb = await exports.getDbConnection(userDbPath); const userDb = await exports.getDbConnection(userDbPath);
@ -350,54 +308,76 @@ async function initializeUserDatabase() {
results.failed.push({ operation: '创建 question_fill_blanks 表', error: error.message }); results.failed.push({ operation: '创建 question_fill_blanks 表', error: error.message });
} }
console.log('用户数据库初始化结果:'); // 创建question_judge表
console.log('成功操作:', results.success); try {
console.log('失败操作:', results.failed); await userDb.execAsync(userSchema.question_judge.trim());
console.log('创建 question_judge 表成功');
// 如果有失败操作,仅打印错误信息,不抛出异常 results.success.push('创建 question_judge 表');
if (results.failed.length > 0) { } catch (error) {
console.error(`用户数据库初始化有 ${results.failed.length} 个操作失败,请查看日志`); console.error('创建 question_judge 表失败:', error);
// 输出详细的失败信息 results.failed.push({ operation: '创建 question_judge 表', error: error.message });
results.failed.forEach((item, index) => {
console.error(`${index + 1}. ${item.operation} 失败: ${item.error}`);
});
} }
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() { exports.initializeDatabase = async function initializeDatabase() {
// 防止并发初始化
if (global.isInitializing) {
console.log('数据库初始化正在进行中,请勿重复触发');
return { success: false, message: '数据库初始化正在进行中' };
}
global.isInitializing = true;
try { try {
console.log('开始初始化数据库...'); // 初始化系统数据库
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('开始初始化系统数据库...');
const systemResult = await initializeSystemDatabase(); const systemResult = await initializeSystemDatabase();
console.log('系统数据库初始化结果:', systemResult ? '成功' : '失败'); if (!systemResult.success) {
if (!systemResult) {
throw new Error('系统数据库初始化失败'); throw new Error('系统数据库初始化失败');
} }
// 再初始化用户数据库 // 初始化用户数据库
console.log('开始初始化用户数据库...'); const userResult = await exports.initializeUserDatabase();
const userResult = await initializeUserDatabase(); if (!userResult.success) {
console.log('用户数据库初始化结果:', userResult ? '成功' : '失败');
if (!userResult) {
throw new Error('用户数据库初始化失败'); throw new Error('用户数据库初始化失败');
} }

View File

@ -2,13 +2,16 @@
import { app, protocol, BrowserWindow, ipcMain, dialog } from 'electron' import { app, protocol, BrowserWindow, ipcMain, dialog } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib' import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
// 替换argon2为bcryptjs // 替换argon2为bcryptjs
const bcrypt = require('bcryptjs') const bcrypt = require('bcryptjs')
const isDevelopment = process.env.NODE_ENV !== 'production' 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'); const { initConfigIpc } = require('./service/configService.js');
// 导入字典服务 // 导入字典服务
@ -207,3 +210,27 @@ ipcMain.handle('initialize-database', async () => {
return false 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 }
}
})

View File

@ -1,3 +1,4 @@
// 保留第一个导入
const { const {
generateExamineePaper, generateExamineePaper,
loadPaperSerial, loadPaperSerial,
@ -8,10 +9,13 @@ const {
endPaper, endPaper,
processPaper, processPaper,
checkPaperAnswers, checkPaperAnswers,
getPaper getPaper,
} = require('../db/examing.js'); } = require("../db/examing.js");
const { getDbConnection, closeAllConnections } = require('../db/index.js'); const { getDbConnection, closeAllConnections } = require("../db/index.js");
const { getUserDbPath } = require('../db/path.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必须为正数"); throw new Error("试卷ID必须为正数");
} }
const result = await submitPaper(paperId); // 1. 提交试卷
return result; 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) { } catch (error) {
console.error("服务层: 提交考试失败", error); console.error("服务层: 提交考试失败", error);
return { return {
@ -262,6 +291,28 @@ async function submitPaperService(paperId) {
} }
} }
/**
* 服务层获取考试结果
* @param {number} paperId - 试卷ID
* @returns {Promise<Object>} - 包含操作结果的对象
*/
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 * @param {number} paperId - 试卷ID
@ -332,6 +383,7 @@ async function checkPaperAnswersService(paperId) {
* 初始化考试相关的IPC处理程序 * 初始化考试相关的IPC处理程序
* @param {import('electron').IpcMain} ipcMain - IPC主进程实例 * @param {import('electron').IpcMain} ipcMain - IPC主进程实例
*/ */
// 在initExamingIpc函数中添加examing-get-exam-result处理器
function initExamingIpc(ipcMain) { function initExamingIpc(ipcMain) {
// 生成考生试卷 // 生成考生试卷
ipcMain.handle( ipcMain.handle(
@ -373,9 +425,11 @@ function initExamingIpc(ipcMain) {
); );
// 加载试卷试题序列 // 加载试卷试题序列
// 移除或注释掉loadPaperSerial处理程序中的调试日志
ipcMain.handle("examing-load-paper-serial", async (event, { paperId }) => { ipcMain.handle("examing-load-paper-serial", async (event, { paperId }) => {
try { try {
console.log("0903调试", paperId); // 注释掉这行调试日志
// console.log("0903调试", paperId);
return await loadPaperSerialService(paperId); return await loadPaperSerialService(paperId);
} catch (error) { } catch (error) {
console.error("加载试卷试题序列失败:", 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 { try {
return await startPaperService(paperId); const { paperId } = args;
if (!paperId) {
return { success: false, message: "缺少试卷ID" };
}
const result = await startPaper(paperId);
return result;
} catch (error) { } catch (error) {
console.error("开始考试失败:", error); console.error("开始考试失败:", error);
return { return { success: false, message: error.message };
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格式 // 导出使用CommonJS格式
@ -497,5 +568,5 @@ module.exports = {
endPaperService, endPaperService,
processPaperService, processPaperService,
checkPaperAnswersService, checkPaperAnswersService,
initExamingIpc initExamingIpc,
}; };

File diff suppressed because it is too large Load Diff

View File

@ -35,7 +35,7 @@ export default {
.content-container { .content-container {
flex: 1; flex: 1;
padding: 20px; padding: 15px;
overflow-y: auto; overflow-y: auto;
background-color: #f0f2f5; background-color: #f0f2f5;
} }

View File

@ -1,106 +1,166 @@
// Preload script runs in a context that has access to both Node.js and browser APIs // 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给渲染进程 // 暴露API给渲染进程
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld("electronAPI", {
// 数据库相关 // 数据库相关
checkDatabaseInitialized: () => ipcRenderer.invoke('check-database-initialized'), checkDatabaseInitialized: () =>
initializeDatabase: () => ipcRenderer.invoke('initialize-database'), 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), adminLogin: (credentials) => ipcRenderer.invoke("admin-login", credentials),
systemGetConfig: () => ipcRenderer.invoke('system-get-config'), systemGetConfig: () => ipcRenderer.invoke("system-get-config"),
systemUpdateConfig: (config) => ipcRenderer.invoke('system-update-config', config), systemUpdateConfig: (config) =>
systemIncreaseQuestionBankVersion: () => ipcRenderer.invoke('system-increase-question-band-version'), ipcRenderer.invoke("system-update-config", config),
configFetchAll: () => ipcRenderer.invoke('config-fetch-all'), systemIncreaseQuestionBankVersion: () =>
configFetchById: (id) => ipcRenderer.invoke('config-fetch-by-id', id), ipcRenderer.invoke("system-increase-question-band-version"),
configSave: (key, value) => ipcRenderer.invoke('config-save', { key, value }), configFetchAll: () => ipcRenderer.invoke("config-fetch-all"),
configDelete: (id) => ipcRenderer.invoke('config-delete', id), 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'), dictFetchTypes: () => ipcRenderer.invoke("dict-fetch-types"),
dictFetchItemsByType: (typeCode, isActive = undefined) => ipcRenderer.invoke('dict-fetch-items-by-type', typeCode, isActive), dictFetchItemsByType: (typeCode, isActive = undefined) =>
dictCreateType: (dictType) => ipcRenderer.invoke('dict-create-type', dictType), ipcRenderer.invoke("dict-fetch-items-by-type", typeCode, isActive),
dictUpdateType: (dictType) => ipcRenderer.invoke('dict-update-type', dictType), dictCreateType: (dictType) =>
dictDeleteType: (typeCode) => ipcRenderer.invoke('dict-delete-type', typeCode), ipcRenderer.invoke("dict-create-type", dictType),
dictCreateItem: (dictItem) => ipcRenderer.invoke('dict-create-item', dictItem), dictUpdateType: (dictType) =>
dictUpdateItem: (dictItem) => ipcRenderer.invoke('dict-update-item', dictItem), ipcRenderer.invoke("dict-update-type", dictType),
dictDeleteItem: (id) => ipcRenderer.invoke('dict-delete-item', id), dictDeleteType: (typeCode) =>
dictFetchAllItemsWithTypes: () => ipcRenderer.invoke('dict-fetch-all-items-with-types'), ipcRenderer.invoke("dict-delete-type", typeCode),
dictCheckParentCode: (parentCode) => ipcRenderer.invoke('dict-check-parent-code', parentCode), dictCreateItem: (dictItem) =>
dictCheckChildReferences: (itemCode) => ipcRenderer.invoke('dict-check-child-references', itemCode), 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), questionCreate: (questionData) =>
questionFetchAll: () => ipcRenderer.invoke('question-fetch-all'), ipcRenderer.invoke("question-create", questionData),
questionFetchAllWithRelations: () => ipcRenderer.invoke('question-fetch-all-with-relations'), questionFetchAll: () => ipcRenderer.invoke("question-fetch-all"),
questionUpdateDescription: (questionData) => ipcRenderer.invoke('question-update-description', questionData), questionFetchAllWithRelations: () =>
questionAddChoice: (choiceData) => ipcRenderer.invoke('question-add-choice', choiceData), 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方法使其接收两个参数并正确传递
questionUpdateChoice: (id, choiceData) => ipcRenderer.invoke('question-update-choice', id, choiceData), questionUpdateChoice: (id, choiceData) =>
questionDeleteChoice: (id) => ipcRenderer.invoke('question-delete-choice', id), ipcRenderer.invoke("question-update-choice", id, choiceData),
questionDeleteChoice: (id) =>
ipcRenderer.invoke("question-delete-choice", id),
// 添加questionCreateChoice方法调用主进程中已注册的'question-create-choice'通道 // 添加questionCreateChoice方法调用主进程中已注册的'question-create-choice'通道
questionCreateChoice: (choiceData) => ipcRenderer.invoke('question-create-choice', choiceData), questionCreateChoice: (choiceData) =>
questionAddFillBlank: (fillBlankData) => ipcRenderer.invoke('question-add-fill-blank', fillBlankData), ipcRenderer.invoke("question-create-choice", choiceData),
questionAddFillBlank: (fillBlankData) =>
ipcRenderer.invoke("question-add-fill-blank", fillBlankData),
// 添加新的questionCreateFillBlank方法调用主进程中已注册的'question-create-fill-blank'通道 // 添加新的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方法使其接收两个参数并正确传递
questionUpdateFillBlank: (id, fillBlankData) => ipcRenderer.invoke('question-update-fill-blank', id, fillBlankData), questionUpdateFillBlank: (id, fillBlankData) =>
questionDeleteFillBlank: (id) => ipcRenderer.invoke('question-delete-fill-blank', id), ipcRenderer.invoke("question-update-fill-blank", id, fillBlankData),
questionGetQuestionWithChoices: (questionId) => ipcRenderer.invoke('question-get-question-with-choices', questionId), questionDeleteFillBlank: (id) =>
questionGetQuestionWithFillBlanks: (questionId) => ipcRenderer.invoke('question-get-question-with-fill-blanks', questionId), ipcRenderer.invoke("question-delete-fill-blank", id),
questionRemove: (questionId) => ipcRenderer.invoke('question-remove', questionId), 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方法调用主进程中已注册的'question-delete'通道
questionDelete: (questionId) => ipcRenderer.invoke('question-delete', questionId), questionDelete: (questionId) =>
ipcRenderer.invoke("question-delete", questionId),
// 修改后 // 修改后
questionGetStatistics: () => ipcRenderer.invoke('question-get-count-and-score'), questionGetStatistics: () =>
questionGetQuestionById: (questionId) => ipcRenderer.invoke('question-get-question-by-id', questionId), ipcRenderer.invoke("question-get-count-and-score"),
questionGetQuestionById: (questionId) =>
ipcRenderer.invoke("question-get-question-by-id", questionId),
// 考试服务相关接口 // 考试服务相关接口
examCreate: (examData) => ipcRenderer.invoke('exam-create', examData), examCreate: (examData) => ipcRenderer.invoke("exam-create", examData),
examUpdate: (id, examData) => ipcRenderer.invoke('exam-update', { id, examData }), examUpdate: (id, examData) =>
examFetchLast: () => ipcRenderer.invoke('exam-fetch-last'), ipcRenderer.invoke("exam-update", { id, examData }),
examFetchAll: () => ipcRenderer.invoke('exam-fetch-all'), examFetchLast: () => ipcRenderer.invoke("exam-fetch-last"),
examFetchById: (id) => ipcRenderer.invoke('exam-fetch-by-id', id), examFetchAll: () => ipcRenderer.invoke("exam-fetch-all"),
examDelete: (id) => ipcRenderer.invoke('exam-delete', id), examFetchById: (id) => ipcRenderer.invoke("exam-fetch-by-id", id),
examDelete: (id) => ipcRenderer.invoke("exam-delete", id),
// 考生服务相关接口 // 考生服务相关接口
examineeFetchAll: () => ipcRenderer.invoke('examinee-fetch-all'), examineeFetchAll: () => ipcRenderer.invoke("examinee-fetch-all"),
userLogin: ({ idCard, admissionTicket }) => ipcRenderer.invoke('user-login', { idCard, admissionTicket }), userLogin: ({ idCard, admissionTicket }) =>
examineeFetchById: (id) => ipcRenderer.invoke('examinee-fetch-by-id', id), ipcRenderer.invoke("user-login", { idCard, admissionTicket }),
examineeCreate: (examineeData) => ipcRenderer.invoke('examinee-create', examineeData), examineeFetchById: (id) => ipcRenderer.invoke("examinee-fetch-by-id", id),
examineeUpdate: (id, examineeData) => ipcRenderer.invoke('examinee-update', { id, examineeData }), examineeCreate: (examineeData) =>
examineeDelete: (id) => ipcRenderer.invoke('examinee-delete', id), 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 }), examingGeneratePaper: ({ examineeId, examId }) =>
examingGetPaperStatus: ({ examineeId, examId }) => ipcRenderer.invoke('examing-get-paper-status', { examineeId, examId }), ipcRenderer.invoke("examing-generate-paper", { examineeId, examId }),
examingUpdatePaperStatus: ({ paperId, status }) => ipcRenderer.invoke('examing-update-paper-status', { paperId, status }), examingGetPaperStatus: ({ examineeId, examId }) =>
examingLoadPaperSerial: ({ paperId }) => ipcRenderer.invoke('examing-load-paper-serial', { paperId }), ipcRenderer.invoke("examing-get-paper-status", { examineeId, examId }),
examingGetQuestionByRelatedId: ({ tableName, relatedId }) => ipcRenderer.invoke('examing-get-question-by-related-id', { tableName, relatedId }), 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接口的参数结构
examingUpdateAnswer: ({ tableName, id, answers }) => ipcRenderer.invoke('examing-update-answer', { tableName, id, answers }), examingUpdateAnswer: ({ tableName, id, answers }) =>
examingStartPaper: ({ paperId }) => ipcRenderer.invoke('examing-start-paper', { paperId }), ipcRenderer.invoke("examing-update-answer", { tableName, id, answers }),
examingSubmitPaper: ({ paperId }) => ipcRenderer.invoke('examing-submit-paper', { paperId }), // 在考生考试服务相关接口部分确保examingStartPaper正确暴露
examingEndPaper: ({ paperId }) => ipcRenderer.invoke('examing-end-paper', { paperId }), examingStartPaper: ({ paperId }) =>
examingProcessPaper: ({ paperId }) => ipcRenderer.invoke('examing-process-paper', { paperId }), ipcRenderer.invoke("examing-start-paper", { paperId }),
examingCheckPaperAnswers: ({ paperId }) => ipcRenderer.invoke('examing-check-paper-answers', { 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'), // 保留一个fileGeneratePaperPdf定义移除重复的
fileGeneratePdf: (pdfData, fileName) => ipcRenderer.invoke('file-generate-pdf', pdfData, fileName), fileGeneratePaperPdf: (jsonString) =>
fileGeneratePaperPdf: (jsonString) => ipcRenderer.invoke('file-generate-paper-pdf', jsonString), ipcRenderer.invoke("file-generate-paper-pdf", jsonString),
fileCopyToDesktop: (filePath) => ipcRenderer.invoke('file-copy-to-desktop', filePath), 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接口确保兼容性
ipcRenderer: { ipcRenderer: {
invoke: (channel, data) => ipcRenderer.invoke(channel, data) invoke: (channel, data) => ipcRenderer.invoke(channel, data),
} },
}) });
// 也保留原来的electron对象确保现有功能正常 // 也保留原来的electron对象确保现有功能正常
contextBridge.exposeInMainWorld('electron', { contextBridge.exposeInMainWorld("electron", {
ipcRenderer: { ipcRenderer: {
invoke: (channel, data) => ipcRenderer.invoke(channel, data) invoke: (channel, data) => ipcRenderer.invoke(channel, data),
} },
}) });

View File

@ -11,6 +11,8 @@ import ExamManagementView from '../views/admin/ExamManagementView.vue'
import ExamineeHomeView from '../views/user/ExamineeHomeView.vue' import ExamineeHomeView from '../views/user/ExamineeHomeView.vue'
// 添加ExamingView组件导入 // 添加ExamingView组件导入
import ExamingView from '../views/user/ExamingView.vue' import ExamingView from '../views/user/ExamingView.vue'
// 添加EndView组件导入
import EndView from '../views/user/EndView.vue'
Vue.use(VueRouter) Vue.use(VueRouter)
@ -41,7 +43,9 @@ const routes = [
children: [ children: [
{ path: 'home', name: 'ExamineeHome', component: ExamineeHomeView }, { 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 },
// 可以在这里添加更多考生相关的路由 // 可以在这里添加更多考生相关的路由
] ]
} }

View File

@ -31,8 +31,16 @@ export default new Vuex.Store({
state.userType = null state.userType = null
}, },
// 新增:设置试卷信息 // 新增:设置试卷信息
// 确保setPaper mutation正确实现保留完整数据结构
setPaper(state, paperInfo) { 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) { clearPaper(state) {

View File

@ -137,8 +137,27 @@ export default {
}, },
mounted () { mounted () {
this.checkDatabaseStatus() this.checkDatabaseStatus()
this.checkAndInitializeUserDb()
}, },
methods: { 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 () { async checkDatabaseStatus () {
try { try {
console.log('组件挂载 - 开始检查数据库初始化状态') console.log('组件挂载 - 开始检查数据库初始化状态')

434
src/views/user/EndView.vue Normal file
View File

@ -0,0 +1,434 @@
<template>
<!-- 添加外层容器确保占满整个空间 -->
<div class="student-home-container">
<div class="content-wrapper">
<div class="bg-white rounded-4 shadow-lg p-4 w-100 max-w-2xl border-2 border-primary/20">
<el-result icon="success" title="考试已完成" sub-title="您已成功提交试卷感谢您的参与">
<!--
<template slot="extra">
<p v-if="pdfPath">{{ pdfPath.filePath }}</p>
</template>
-->
</el-result>
<el-divider />
<div class="flex justify-between text-center">
<h3 class="text-2xl font-bold text-primary">考生信息</h3>
</div>
<!-- 考生信息部分 -->
<el-descriptions class="margin-top" :column="3" :size="size" border>
<el-descriptions-item>
<template slot="label">
<div class="cell-item">
<i class="fas fa-user" :style="iconStyle"></i>
姓名
</div>
</template>
{{ getExamineeName() }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<div class="cell-item">
<i class="fas fa-id-card" :style="iconStyle"></i>
身份证号
</div>
</template>
{{ getFormattedIdCard() }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<div class="cell-item">
<i class="fas fa-ticket-alt" :style="iconStyle"></i>
准考证号
</div>
</template>
{{ getAdmissionTicket() }}
</el-descriptions-item>
</el-descriptions>
<el-divider />
<div class="flex justify-between text-center">
<h3 class="text-2xl font-bold text-primary">考试信息</h3>
</div>
<!-- 试卷信息部分 -->
<el-descriptions class="margin-top" :column="2" :size="size" border>
<el-descriptions-item>
<template slot="label">
<div class="cell-item">
<i class="fas fa-clock" :style="iconStyle"></i>
开始时间
</div>
</template>
{{ getFormattedStartTime() }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<div class="cell-item">
<i class="fas fa-clock" :style="iconStyle"></i>
结束时间
</div>
</template>
{{ getFormattedEndTime() }}
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">
<div class="cell-item">
<i class="fas fa-hourglass-half" :style="iconStyle"></i>
总使用时间
</div>
</template>
{{ getDuration() }}
</el-descriptions-item>
</el-descriptions>
<!-- 操作按钮 -->
<div class="action-buttons">
<el-button type="primary" size="large" @click="goHome">
<i class="fas fa-home"></i> 返回首页
</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'EndView',
components: {
ElButton: require('element-ui').Button,
ElDescriptions: require('element-ui').Descriptions,
ElDescriptionsItem: require('element-ui').DescriptionsItem,
ElResult: require('element-ui').Result,
ElDivider: require('element-ui').Divider
},
data () {
return {
size: 'default',
pdfPath: '',
isChecking: false
}
},
// script
computed: {
// store
examinee () {
return this.$store.state.examinee || {}
},
// store
paper () {
// storelocalStorage
if (!this.$store.state.paper || Object.keys(this.$store.state.paper).length === 0) {
const savedPaper = localStorage.getItem('lastExamPaper');
if (savedPaper) {
try {
// localStoragestore
return JSON.parse(savedPaper);
} catch (e) {
console.error('解析localStorage试卷数据失败:', e);
}
}
return {};
}
return this.$store.state.paper || {};
},
//
iconStyle () {
const marginMap = {
large: '8px',
default: '6px',
small: '4px',
}
return {
marginRight: marginMap[this.size] || marginMap.default,
fontSize: '16px'
}
}
},
mounted () {
console.log('EndView初始化')
this.checkLoginStatus()
//
this.checkPaperData()
},
methods: {
//
checkLoginStatus () {
if (!this.$store.state.isLoggedIn) {
this.$router.push('/')
}
},
//
checkPaperData () {
// 1. store
if (this.paper && this.paper.id) {
console.log('store中已有完整试卷数据');
//
if (!this.paper.is_checked) {
this.checkAnswers(this.paper.id);
}
return;
}
// 2. paperId
const paperIdFromRoute = this.$route.params.paperId;
if (!paperIdFromRoute) {
console.error('路由参数中没有找到paperId');
this.$message.error('无法获取试卷信息,请重新提交试卷');
return;
}
// 3. localStorage
const savedPaperData = localStorage.getItem('lastExamPaper');
if (savedPaperData) {
try {
const paperData = JSON.parse(savedPaperData);
// paperId
if (paperData.id && paperData.id.toString() === paperIdFromRoute.toString()) {
console.log('从localStorage恢复到store的试卷数据:', paperData);
// storethis.paper访
this.$store.commit('setPaper', paperData);
//
if (!paperData.is_checked) {
this.checkAnswers(paperData.id);
}
return;
}
} catch (e) {
console.error('解析localStorage中的试卷数据失败:', e);
}
}
// 4. API
console.log('调用API加载试卷数据paperId:', paperIdFromRoute);
this.loadExamResult(paperIdFromRoute);
},
// - store
async loadExamResult (paperIdFromParams) {
try {
const paperId = paperIdFromParams || this.$route.params.paperId;
if (!paperId) {
console.error('没有找到试卷ID');
this.$message.error('无法获取试卷信息,请重新提交试卷');
return;
}
// API
if (!window.electronAPI || typeof window.electronAPI.examingGetExamResult !== 'function') {
console.error('examingGetExamResult API不存在');
this.$message.warning('获取考试结果功能暂不可用');
return;
}
const result = await window.electronAPI.examingGetExamResult({ paperId });
if (result && result.success && result.data) {
// store
this.$store.commit('setPaper', result.data);
// localStorage
localStorage.setItem('lastExamPaper', JSON.stringify(result.data));
console.log('API获取并保存到store的试卷数据:', this.$store.state.paper);
//
if (!result.data.is_checked) {
this.checkAnswers(result.data.id || paperId);
}
} else {
console.error('获取考试结果失败:', result?.message || '未知错误');
this.$message.error('获取考试结果失败');
}
} catch (error) {
console.error('加载考试结果失败:', error);
this.$message.error('加载考试结果失败');
}
},
//
// checkAnswersAPI
async checkAnswers () {
try {
if (!this.paper || !this.paper.id) {
console.error('没有试卷ID无法判卷');
this.$message.warning('缺少试卷信息,无法判卷');
return;
}
this.isChecking = true
const paperId = this.paper.id
// API
if (!window.electronAPI || typeof window.electronAPI.examingCheckPaperAnswers !== 'function') {
console.error('examingCheckPaperAnswers API不存在');
this.$message.warning('判卷功能暂不可用');
this.isChecking = false;
return;
}
const result = await window.electronAPI.examingCheckPaperAnswers({ paperId })
if (result && result.success) {
// store
if (result.data) {
this.$store.commit('setPaper', result.data)
}
this.$message.success('判卷完成!');
} else {
console.error('判卷失败:', result?.message || '未知错误');
this.$message.error('判卷失败,请稍后重试');
}
} catch (error) {
console.error('判卷过程异常:', error);
this.$message.error('判卷过程中发生异常');
} finally {
this.isChecking = false
}
},
//
getFormattedIdCard () {
if (!this.examinee || !this.examinee.examinee_id_card) return ''
const idCard = this.examinee.examinee_id_card
if (idCard.length !== 18) return idCard
return idCard.substring(0, 9) + '*****' + idCard.substring(14)
},
//
formatDateTime (dateString) {
if (!dateString) return '未知'
const date = new Date(dateString)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
},
//
getFormattedStartTime () {
if (!this.paper || !this.paper.paper_start_time) return '未知';
return this.formatDateTime(this.paper.paper_start_time);
},
//
getFormattedEndTime () {
if (!this.paper || !this.paper.paper_end_time) return '未知';
return this.formatDateTime(this.paper.paper_end_time);
},
//
getDuration () {
if (!this.paper || !this.paper.paper_start_time || !this.paper.paper_end_time) {
return '未知';
}
const startTime = new Date(this.paper.paper_start_time);
const endTime = new Date(this.paper.paper_end_time);
const durationMs = endTime - startTime;
const hours = Math.floor(durationMs / (1000 * 60 * 60));
const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((durationMs % (1000 * 60)) / 1000);
if (hours > 0) {
return `${hours}小时${minutes}${seconds}`;
} else if (minutes > 0) {
return `${minutes}${seconds}`;
} else {
return `${seconds}`;
}
},
//
goHome () {
this.$router.push('/')
},
//
getExamineeName () {
return this.examinee && this.examinee.examinee_name ? this.examinee.examinee_name : ''
},
//
getAdmissionTicket () {
return this.examinee && this.examinee.examinee_admission_ticket ? this.examinee.examinee_admission_ticket : ''
},
//
getScoreLevel (score, fullScore) {
if (!score || !fullScore) return ''
const percentage = score / fullScore
if (percentage >= 0.9) return '优秀'
if (percentage >= 0.8) return '良好'
if (percentage >= 0.7) return '中等'
if (percentage >= 0.6) return '及格'
return '不及格'
}
}
}
</script>
<style scoped>
/* 主题色 */
:root {
--primary-color: #1E88E5;
--success-color: #4CAF50;
--warning-color: #FF9800;
--danger-color: #F44336;
}
/* 自定义样式 */
.text-primary {
color: var(--primary-color) !important;
}
/* 确保容器占满整个空间 */
.student-home-container {
height: 100%;
display: flex;
flex-direction: column;
}
/* 内容包装器,用于居中卡片并使其可扩展 */
.content-wrapper {
flex: 1;
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
/* 适配移动设备 */
@media (max-width: 640px) {
.max-w-2xl {
max-width: 100%;
}
}
.cell-item {
display: flex;
align-items: center;
}
.margin-top {
margin-top: 20px !important;
}
/* 操作按钮 */
.action-buttons {
display: flex;
justify-content: center;
margin-top: 30px;
}
</style>

View File

@ -256,82 +256,90 @@ export default {
}, },
// //
// startExamstore
async startExam() { async startExam() {
if (!this.lastExam) { if (!this.lastExam) {
this.$message.warning('请先获取考试信息') this.$message.warning('请先获取考试信息')
return return
} }
if (!this.examinee || !this.examinee.id || !this.examinee.examinee_name) { if (!this.examinee || !this.examinee.id || !this.examinee.examinee_name) {
this.$message.warning('未获取到完整的考生信息') this.$message.warning('未获取到完整的考生信息')
return return
} }
this.isLoading = true this.isLoading = true
try { try {
console.log('开始生成试卷...', { examinee: this.examinee, exam: this.lastExam }) console.log('开始生成试卷...', { examinee: this.examinee, exam: this.lastExam })
// //
const examineeData = { const examineeData = {
id: this.examinee.id, id: this.examinee.id,
examinee_name: this.examinee.examinee_name || '', examinee_name: this.examinee.examinee_name || '',
examinee_id_card: this.examinee.examinee_id_card || '', examinee_id_card: this.examinee.examinee_id_card || '',
examinee_admission_ticket: this.examinee.examinee_admission_ticket || '', examinee_admission_ticket: this.examinee.examinee_admission_ticket || '',
examinee_gender: this.examinee.examinee_gender || '', examinee_gender: this.examinee.examinee_gender || '',
examinee_unit: this.examinee.examinee_unit || '', examinee_unit: this.examinee.examinee_unit || '',
written_exam_room: this.examinee.written_exam_room || '', written_exam_room: this.examinee.written_exam_room || '',
written_exam_seat: this.examinee.written_exam_seat || '', written_exam_seat: this.examinee.written_exam_seat || '',
computer_exam_room: this.examinee.computer_exam_room || '', computer_exam_room: this.examinee.computer_exam_room || '',
computer_exam_seat: this.examinee.computer_exam_seat || '' computer_exam_seat: this.examinee.computer_exam_seat || ''
} }
// 使JSON/ // 使JSON/
const examData = JSON.parse(JSON.stringify({ const examData = JSON.parse(JSON.stringify({
id: this.lastExam.id, id: this.lastExam.id,
exam_name: this.lastExam.exam_name || '', exam_name: this.lastExam.exam_name || '',
exam_description: this.lastExam.exam_description || '', exam_description: this.lastExam.exam_description || '',
exam_minutes: this.lastExam.exam_minutes || 0, exam_minutes: this.lastExam.exam_minutes || 0,
exam_minutes_min: this.lastExam.exam_minutes_min || 0, exam_minutes_min: this.lastExam.exam_minutes_min || 0,
exam_notice: this.lastExam.exam_notice || [] exam_notice: this.lastExam.exam_notice || []
})) }))
// ipcRendererpreload.js // ipcRendererpreload.js
const result = await window.electronAPI.ipcRenderer.invoke('examing-generate-paper', { const result = await window.electronAPI.ipcRenderer.invoke('examing-generate-paper', {
examineeData: examineeData, examineeData: examineeData,
examData: examData examData: examData
}) })
if (result && result.success) { if (result && result.success) {
console.log('生成试卷成功:', result) console.log('生成试卷成功:', result)
// store // store
this.$store.commit('setPaper', result.data) // this.$store.commit('setPaper', result.data)
this.$alert( // IDlocalStorage
'已完成组卷,点击"进入考试"开始答题', try {
'组卷完成', localStorage.setItem('currentPaperId', result.data.id)
{ } catch (error) {
confirmButtonText: '进入考试', console.warn('保存试卷ID到localStorage失败:', error)
type: 'success' }
}
).then(() => { this.$alert(
// 使 '已完成组卷,点击"进入考试"开始答题',
this.$router.push({ '组卷完成',
name: 'Examing', {
params: { paperId: result.data.id } confirmButtonText: '进入考试',
}) type: 'success'
}) }
} else { ).then(() => {
console.error('生成试卷失败:', result) // 使
this.$message.error(`生成试卷失败: ${result.message || '未知错误'}`) this.$router.push({
} name: 'Examing',
} catch (error) { params: { paperId: result.data.id }
console.error('生成试卷异常:', error) })
this.$message.error(`无法生成试卷: ${error.message || '未知错误'}`) })
} finally { } else {
this.isLoading = false console.error('生成试卷失败:', result)
} this.$message.error(`生成试卷失败: ${result.message || '未知错误'}`)
}
} catch (error) {
console.error('生成试卷异常:', error)
this.$message.error(`无法生成试卷: ${error.message || '未知错误'}`)
} finally {
this.isLoading = false
}
} }
} }
} }

View File

@ -30,10 +30,14 @@
<div v-if="questionList.length > 0"> <div v-if="questionList.length > 0">
<div class="question-header"> <div class="question-header">
<span class="question-number"> {{ currentQuestion }} </span> <span class="question-number"> {{ currentQuestion }} </span>
<span class="question-score">({{ currentQuestionData.question_detail && currentQuestionData.question_detail.score || 0 }})</span> <span class="question-score">
<el-tag>
{{ currentQuestionData.question && currentQuestionData.question.type_info && currentQuestionData.question.type_info.item_name || '' }}
</el-tag>
({{ currentQuestionData.question_detail && currentQuestionData.question_detail.score || 0 }})
</span>
</div> </div>
<div class="question-body"> <div class="question-body">
<div class="question-type">{{ currentQuestionData.question && currentQuestionData.question.type_info && currentQuestionData.question.type_info.item_name || '' }}</div>
<div class="question-text" v-html="currentQuestionData.question && currentQuestionData.question.question_description || ''"></div> <div class="question-text" v-html="currentQuestionData.question && currentQuestionData.question.question_description || ''"></div>
<!-- 题目图片 --> <!-- 题目图片 -->
@ -99,7 +103,8 @@
export default { export default {
name: 'ExamingView', name: 'ExamingView',
components: { components: {
ElButton: require('element-ui').Button ElButton: require('element-ui').Button,
ElTag: require('element-ui').Tag
}, },
data() { data() {
return { return {
@ -113,7 +118,8 @@ export default {
currentImage: '', currentImage: '',
timer: null, timer: null,
processTimer: null, processTimer: null,
paperId: '' paperId: '',
blankAnswerTip: '数字请保留2位小数如“100.00”'
} }
}, },
computed: { computed: {
@ -138,7 +144,27 @@ export default {
}, },
mounted() { 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
}
// paperIdlocalStorage
if (!this.paperId) {
try {
this.paperId = localStorage.getItem('currentPaperId')
} catch (error) {
console.error('从localStorage获取paperId失败:', error)
}
}
// paperId
if (!this.paperId) {
this.$router.push('/') this.$router.push('/')
return return
} }
@ -162,13 +188,13 @@ export default {
// //
async initializeExam() { async initializeExam() {
try { try {
console.warn(this.paper) console.log('初始化考试paperId:', this.paperId)
console.warn(this.paperId)
// 1. store //
if (!this.paper || !this.paper.id) { this.$store.commit('clearPaper')
// store
await this.loadPaperInfo(this.paperId) // 1.
} await this.loadPaperInfo(this.paperId)
// 2. // 2.
await this.loadQuestionList(this.paperId) await this.loadQuestionList(this.paperId)
@ -265,14 +291,34 @@ export default {
async loadPaperInfo(paperId) { async loadPaperInfo(paperId) {
try { try {
console.log('开始加载试卷信息paperId:', paperId) console.log('开始加载试卷信息paperId:', paperId)
// 使
//
this.$store.commit('clearPaper')
// 1.
const result = await window.electronAPI.examingProcessPaper({ paperId: paperId }) const result = await window.electronAPI.examingProcessPaper({ paperId: paperId })
console.log('loadPaperInfo结果:', result) console.log('loadPaperInfo结果:', result)
if (result && result.success && result.data) { if (result && result.success && result.data) {
// paperstore // 2. 0startPaper
this.$store.commit('setPaper', result.data) if (result.data.paper_status === 0) {
this.canSubmit = result.data.paper_minutes_min ? false : true 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 { } else {
console.error('加载试卷信息失败: 返回数据无效') console.error('加载试卷信息失败: 返回数据无效')
// //
@ -499,49 +545,88 @@ export default {
await this.loadQuestionDetail(serialNo) await this.loadQuestionDetail(serialNo)
}, },
// // - store
async submitExam() { async submitExam() {
try { try {
if (!this.canSubmit) { if (!this.canSubmit) {
this.$message.warning('暂不能交卷,请先完成考试或等待最小时长') this.$message.warning('您的答案尚未保存,请等待保存完成后再提交');
return return;
} }
// //
this.$confirm('确定要交卷吗?交卷后将无法修改答案。', '确认交卷', { const confirmResult = await this.$confirm('确定要提交试卷吗?提交后将无法继续答题。', '确认提交', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(async () => { });
//
await this.saveCurrentAnswer()
// 使 //
const result = await window.electronAPI.examingSubmitPaper({ paperId: this.paperId }) await this.saveCurrentAnswer();
if (result && result.success) { // API
this.$message.success('交卷成功!') const result = await window.electronAPI.examingSubmitPaper({ paperId: this.paper.id });
// console.log('提交试卷API返回:', result);
if (this.timer) {
clearInterval(this.timer) if (result && result.success) {
this.timer = null // 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('storestorepaper:', 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.$message.success('试卷提交成功!');
this.processTimer = null
} // 3.
// setTimeout(() => {
this.$router.push('/user/exam-result/' + this.paperId) this.$router.push({
} else { name: 'End',
this.$message.error('交卷失败: ' + (result.message || '未知错误')) 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(() => { } else {
// throw new Error(result?.message || '提交失败');
this.$message.info('已取消交卷') }
})
} catch (error) { } catch (error) {
console.error('交卷过程异常:', error) if (error !== 'cancel') {
this.$message.error('交卷失败: ' + error.message) console.error('提交试卷失败:', error);
this.$message.error('已取消交卷');
}
} }
}, },
@ -553,17 +638,21 @@ export default {
// //
await this.saveCurrentAnswer() await this.saveCurrentAnswer()
// 使 // 使high_version
const result = await window.electronAPI.examingSubmitPaper({ paperId: this.paperId }) const result = await window.electronAPI.endPaper(this.paper.id)
if (result && result.success) { if (result && result.success) {
// paperstore
if (result.data) {
this.$store.commit('setPaper', result.data)
}
// //
if (this.processTimer) { if (this.processTimer) {
clearInterval(this.processTimer) clearInterval(this.processTimer)
this.processTimer = null this.processTimer = null
} }
// //
this.$router.push('/user/exam-result/' + this.paperId) this.$router.push('/examinee/end/' + this.paperId)
} else { } else {
this.$message.error('自动交卷失败,请手动交卷') this.$message.error('自动交卷失败,请手动交卷')
} }
@ -756,7 +845,7 @@ export default {
/* 左侧边栏:试题序号列表 - 固定宽度,可垂直滚动 */ /* 左侧边栏:试题序号列表 - 固定宽度,可垂直滚动 */
.exam-sidebar { .exam-sidebar {
width: 300px; width: 280px;
background-color: #fff; background-color: #fff;
border-radius: 4px; border-radius: 4px;
padding: 1rem; padding: 1rem;
@ -765,6 +854,7 @@ export default {
flex-direction: column; flex-direction: column;
overflow-y: auto; overflow-y: auto;
flex-shrink: 0; flex-shrink: 0;
margin-right:10px;
} }
/* 左侧边栏自定义滚动条 */ /* 左侧边栏自定义滚动条 */
@ -882,7 +972,6 @@ export default {
.question-score { .question-score {
font-size: 14px; font-size: 14px;
color: #f56c6c;
font-weight: 500; font-weight: 500;
} }
@ -1074,7 +1163,7 @@ export default {
display: flex; display: flex;
justify-content: center; justify-content: center;
gap: 15px; gap: 15px;
padding: 15px; padding: 8px;
background-color: white; background-color: white;
margin-top: 10px; margin-top: 10px;
} }

View File

@ -4,7 +4,7 @@ module.exports = {
config config
.plugin('html') .plugin('html')
.tap(args => { .tap(args => {
args[0].title = '统计数据考试系统' args[0].title = '统计技能考试系统'
return args return args
}) })
}, },