测试pdf中文字体
This commit is contained in:
parent
f3f302da49
commit
d09a810903
@ -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}`,
|
||||||
|
@ -56,9 +56,6 @@ async function initializeSystemDatabase() {
|
|||||||
const systemDbPath = getSystemDbPath();
|
const systemDbPath = getSystemDbPath();
|
||||||
const systemDb = await exports.getDbConnection(systemDbPath);
|
const systemDb = await exports.getDbConnection(systemDbPath);
|
||||||
|
|
||||||
// 记录成功和失败的操作
|
|
||||||
const results = { success: [], failed: [] };
|
|
||||||
|
|
||||||
// 创建表结构
|
// 创建表结构
|
||||||
console.log('开始创建系统数据库表结构...');
|
console.log('开始创建系统数据库表结构...');
|
||||||
|
|
||||||
@ -66,210 +63,171 @@ async function initializeSystemDatabase() {
|
|||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.config.trim());
|
await systemDb.execAsync(systemSchema.config.trim());
|
||||||
console.log('创建 config 表成功');
|
console.log('创建 config 表成功');
|
||||||
results.success.push('创建 config 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 config 表失败:', error);
|
console.error('创建 config 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 config 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建dict_types表
|
// 创建dict_types表
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.dictTypes.trim());
|
await systemDb.execAsync(systemSchema.dict_types.trim());
|
||||||
console.log('创建 dict_types 表成功');
|
console.log('创建 dict_types 表成功');
|
||||||
results.success.push('创建 dict_types 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 dict_types 表失败:', error);
|
console.error('创建 dict_types 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 dict_types 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建dict_items表
|
// 创建dict_items表
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.dictItems.trim());
|
await systemDb.execAsync(systemSchema.dict_items.trim());
|
||||||
console.log('创建 dict_items 表成功');
|
console.log('创建 dict_items 表成功');
|
||||||
results.success.push('创建 dict_items 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 dict_items 表失败:', error);
|
console.error('创建 dict_items 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 dict_items 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建questions表
|
// 创建questions表
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.questions.trim());
|
await systemDb.execAsync(systemSchema.questions.trim());
|
||||||
console.log('创建 questions 表成功');
|
console.log('创建 questions 表成功');
|
||||||
results.success.push('创建 questions 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 questions 表失败:', error);
|
console.error('创建 questions 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 questions 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建question_datasets表
|
// 创建question_datasets表
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.questionDatasets.trim());
|
await systemDb.execAsync(systemSchema.question_datasets.trim());
|
||||||
console.log('创建 question_datasets 表成功');
|
console.log('创建 question_datasets 表成功');
|
||||||
results.success.push('创建 question_datasets 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 question_datasets 表失败:', error);
|
console.error('创建 question_datasets 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 question_datasets 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建question_images表
|
// 创建question_images表
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.questionImages.trim());
|
await systemDb.execAsync(systemSchema.question_images.trim());
|
||||||
console.log('创建 question_images 表成功');
|
console.log('创建 question_images 表成功');
|
||||||
results.success.push('创建 question_images 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 question_images 表失败:', error);
|
console.error('创建 question_images 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 question_images 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建question_fill_table表
|
// 创建question_fill_table表
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.questionFillTable.trim());
|
await systemDb.execAsync(systemSchema.question_fill_table.trim());
|
||||||
console.log('创建 question_fill_table 表成功');
|
console.log('创建 question_fill_table 表成功');
|
||||||
results.success.push('创建 question_fill_table 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 question_fill_table 表失败:', error);
|
console.error('创建 question_fill_table 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 question_fill_table 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建question_fill_table_blanks表
|
// 创建question_fill_table_blanks表
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.questionFillTableBlanks.trim());
|
await systemDb.execAsync(systemSchema.question_fill_table_blanks.trim());
|
||||||
console.log('创建 question_fill_table_blanks 表成功');
|
console.log('创建 question_fill_table_blanks 表成功');
|
||||||
results.success.push('创建 question_fill_table_blanks 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 question_fill_table_blanks 表失败:', error);
|
console.error('创建 question_fill_table_blanks 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 question_fill_table_blanks 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建question_choices表
|
// 创建question_choices表
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.questionChoices.trim());
|
await systemDb.execAsync(systemSchema.question_choices.trim());
|
||||||
console.log('创建 question_choices 表成功');
|
console.log('创建 question_choices 表成功');
|
||||||
results.success.push('创建 question_choices 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 question_choices 表失败:', error);
|
console.error('创建 question_choices 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 question_choices 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建question_fill_blanks表
|
// 创建question_fill_blanks表
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.questionFillBlanks.trim());
|
await systemDb.execAsync(systemSchema.question_fill_blanks.trim());
|
||||||
console.log('创建 question_fill_blanks 表成功');
|
console.log('创建 question_fill_blanks 表成功');
|
||||||
results.success.push('创建 question_fill_blanks 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 question_fill_blanks 表失败:', error);
|
console.error('创建 question_fill_blanks 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 question_fill_blanks 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建question_judge表
|
// 创建question_judge表
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.questionJudge.trim());
|
await systemDb.execAsync(systemSchema.question_judge.trim());
|
||||||
console.log('创建 question_judge 表成功');
|
console.log('创建 question_judge 表成功');
|
||||||
results.success.push('创建 question_judge 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 question_judge 表失败:', error);
|
console.error('创建 question_judge 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 question_judge 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建question_short表
|
// 创建question_short表
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.questionShort.trim());
|
await systemDb.execAsync(systemSchema.question_short.trim());
|
||||||
console.log('创建 question_short 表成功');
|
console.log('创建 question_short 表成功');
|
||||||
results.success.push('创建 question_short 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 question_short 表失败:', error);
|
console.error('创建 question_short 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 question_short 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建exam表
|
// 创建exam表
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.exam.trim());
|
await systemDb.execAsync(systemSchema.exam.trim());
|
||||||
console.log('创建 exam 表成功');
|
console.log('创建 exam 表成功');
|
||||||
results.success.push('创建 exam 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 exam 表失败:', error);
|
console.error('创建 exam 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 exam 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建examinee表
|
// 创建examinee表(系统库中的考生表)
|
||||||
try {
|
try {
|
||||||
await systemDb.execAsync(systemSchema.examinee.trim());
|
await systemDb.execAsync(systemSchema.examinee.trim());
|
||||||
console.log('创建 examinee 表成功');
|
console.log('创建 examinee 表成功');
|
||||||
results.success.push('创建 examinee 表');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 examinee 表失败:', error);
|
console.error('创建 examinee 表失败:', error);
|
||||||
results.failed.push({ operation: '创建 examinee 表', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 插入默认数据
|
// 插入默认数据
|
||||||
|
try {
|
||||||
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]
|
||||||
// 更新密码为哈希值
|
);
|
||||||
const configData = defaultData.config.map(item => {
|
console.log('插入管理员用户成功');
|
||||||
if (item.key === 'admin_password') {
|
|
||||||
return { ...item, value: hashedPassword };
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 插入config表数据
|
// 插入config表数据
|
||||||
try {
|
await systemDb.runAsync(
|
||||||
await batchInsert(systemDb, 'config', configData);
|
'INSERT OR IGNORE INTO config (key, value, description) VALUES (?, ?, ?)',
|
||||||
console.log('插入 config 表数据成功');
|
['initialized', 'false', '数据库初始化状态']
|
||||||
results.success.push('插入 config 表数据');
|
);
|
||||||
|
console.log('插入config表数据成功');
|
||||||
|
|
||||||
|
// 批量插入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_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表数据成功');
|
||||||
|
|
||||||
|
console.log('默认数据插入完成');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('插入 config 表数据失败:', error);
|
console.error('插入默认数据失败:', error);
|
||||||
results.failed.push({ operation: '插入 config 表数据', error: error.message });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('处理密码哈希失败:', error);
|
|
||||||
results.failed.push({ operation: '处理密码哈希', error: error.message });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 插入dict_types表数据
|
return { success: true };
|
||||||
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表数据
|
|
||||||
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('系统数据库初始化结果:');
|
|
||||||
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() {
|
||||||
try {
|
// 防止并发初始化
|
||||||
console.log('开始初始化数据库...');
|
|
||||||
|
|
||||||
// 确保只有一个初始化请求在执行
|
|
||||||
if (global.isInitializing) {
|
if (global.isInitializing) {
|
||||||
console.log('数据库初始化已在进行中,等待完成...');
|
console.log('数据库初始化正在进行中,请勿重复触发');
|
||||||
while (global.isInitializing) {
|
return { success: false, message: '数据库初始化正在进行中' };
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
}
|
}
|
||||||
return global.initResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
global.isInitializing = true;
|
global.isInitializing = true;
|
||||||
global.initResult = false;
|
|
||||||
|
|
||||||
// 先初始化系统数据库
|
try {
|
||||||
console.log('开始初始化系统数据库...');
|
// 初始化系统数据库
|
||||||
|
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('用户数据库初始化失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ -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,
|
||||||
};
|
};
|
@ -1,408 +1,23 @@
|
|||||||
const fs = require('fs');
|
// 在文件顶部添加fs模块导入
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
const PDFDocument = require('pdfkit');
|
const PDFDocument = require('pdfkit');
|
||||||
const { app } = require('electron');
|
const { app } = require('electron');
|
||||||
|
|
||||||
// 使用更可靠的方式获取应用路径
|
// 字体路径 - 简化为只关注宋体字体
|
||||||
const appPath = app.getAppPath();
|
const FONT_PATH = path.join(__dirname, '..', 'font');
|
||||||
|
const simsunTtfPath = path.join(FONT_PATH, 'simsun.ttf');
|
||||||
// 修正字体路径常量 - 从应用根路径开始构建
|
const simsunTtcPath = path.join(FONT_PATH, 'simsun.ttc');
|
||||||
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'); // 备选字体路径
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务层:获取所有考生列表
|
* 获取用户桌面路径
|
||||||
* @returns {Promise<Array>} 考生列表
|
* @returns {string} 桌面路径
|
||||||
*/
|
|
||||||
exports.createFileService = async function() {
|
|
||||||
try {
|
|
||||||
// TODO 测试用
|
|
||||||
return '文件服务测试成功';
|
|
||||||
} catch (error) {
|
|
||||||
console.error('服务层: 创建文件失败', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成PDF文件并保存到合适的目录
|
|
||||||
* @param {Object} pdfData - PDF数据
|
|
||||||
* @param {string} fileName - 文件名
|
|
||||||
* @returns {Promise<string>} 文件保存路径
|
|
||||||
*/
|
|
||||||
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} 用户桌面绝对路径
|
|
||||||
*/
|
*/
|
||||||
function getDesktopDir() {
|
function getDesktopDir() {
|
||||||
try {
|
try {
|
||||||
// 使用Electron的app.getPath方法获取桌面路径
|
|
||||||
return app.getPath('desktop');
|
return app.getPath('desktop');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取桌面路径失败:', error);
|
console.error('获取桌面路径失败:', error);
|
||||||
// 发生错误时,返回当前工作目录作为备选
|
|
||||||
return process.cwd();
|
return process.cwd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -412,13 +27,12 @@ function getDesktopDir() {
|
|||||||
* @param {string} filePath - 要复制的文件的绝对路径
|
* @param {string} filePath - 要复制的文件的绝对路径
|
||||||
* @returns {Promise<string>} 复制后的文件路径
|
* @returns {Promise<string>} 复制后的文件路径
|
||||||
*/
|
*/
|
||||||
exports.copyToDesk = async function(filePath) {
|
async function copyToDesk(filePath) {
|
||||||
try {
|
try {
|
||||||
const desktopDir = getDesktopDir();
|
const desktopDir = getDesktopDir();
|
||||||
const fileName = path.basename(filePath);
|
const fileName = path.basename(filePath);
|
||||||
const destPath = path.join(desktopDir, fileName);
|
const destPath = path.join(desktopDir, fileName);
|
||||||
|
|
||||||
// 使用fs.promises进行文件复制
|
|
||||||
await fs.promises.copyFile(filePath, destPath);
|
await fs.promises.copyFile(filePath, destPath);
|
||||||
console.log(`文件已成功复制到桌面: ${destPath}`);
|
console.log(`文件已成功复制到桌面: ${destPath}`);
|
||||||
return destPath;
|
return destPath;
|
||||||
@ -426,148 +40,200 @@ exports.copyToDesk = async function(filePath) {
|
|||||||
console.error('复制文件到桌面失败:', error);
|
console.error('复制文件到桌面失败:', error);
|
||||||
throw 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文件
|
* 生成试卷PDF文件
|
||||||
* @param {string} jsonString - 包含试卷信息的JSON字符串
|
* @param {string} jsonString - 包含试卷信息的JSON字符串
|
||||||
* @returns {Promise<string>} - 生成的PDF文件绝对路径
|
* @returns {Promise<Object|{success: boolean, message: string, errorStack?: string}>} - 生成的PDF文件信息
|
||||||
*/
|
*/
|
||||||
exports.generatePaperPdf = async function(jsonString) {
|
async function generatePaperPdfService(jsonString) {
|
||||||
try {
|
try {
|
||||||
// 解析JSON字符串
|
console.log('开始生成PDF,收到数据长度:', jsonString.length);
|
||||||
const paperData = JSON.parse(jsonString);
|
|
||||||
|
|
||||||
// 提取考生信息
|
// 解析JSON字符串为对象
|
||||||
const { examinee } = paperData;
|
let paperData;
|
||||||
const examineeName = examinee.examinee_name;
|
try {
|
||||||
const idCard = examinee.examinee_id_card;
|
paperData = JSON.parse(jsonString);
|
||||||
const admissionTicket = examinee.examinee_admission_ticket;
|
} catch (parseError) {
|
||||||
|
console.error('JSON解析失败:', parseError);
|
||||||
// 提取考试时间信息
|
return {
|
||||||
const startTime = paperData.paper_start_time;
|
success: false,
|
||||||
const endTime = paperData.paper_end_time;
|
message: '试卷数据格式错误',
|
||||||
// 截取考试日期 (假设格式为 'YYYY-MM-DD HH:mm:ss')
|
errorStack: parseError.stack
|
||||||
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;
|
// 验证数据结构
|
||||||
|
if (!paperData || typeof paperData !== 'object') {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: '无效的试卷数据'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
|
||||||
const totalScore = paperData.paper_score;
|
|
||||||
const realScore = paperData.paper_score_real;
|
|
||||||
|
|
||||||
// 对questions列表按照question_type和id进行排序
|
// 获取考生和试卷信息,并提供默认值
|
||||||
paperData.questions.sort((a, b) => {
|
const examinee = paperData.examinee || {};
|
||||||
// 先按题型排序
|
const paper = paperData.paper || paperData; // 兼容不同的数据结构
|
||||||
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 examineeName = examinee.examinee_name || '未知考生';
|
||||||
const endDate = new Date(endTime.replace(/-/g, '/'));
|
const idCard = examinee.examinee_id_card || '';
|
||||||
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 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 appDir = getAppSaveDir();
|
||||||
// 确保目录存在
|
const tempFilePath = path.join(appDir, fileName);
|
||||||
if (!fs.existsSync(appDir)) {
|
|
||||||
fs.mkdirSync(appDir, { recursive: true });
|
|
||||||
}
|
|
||||||
const filePath = path.join(appDir, fileName);
|
|
||||||
|
|
||||||
// 创建PDF文档,保留默认边距
|
console.log('PDF临时保存路径:', tempFilePath);
|
||||||
|
|
||||||
|
// 创建PDF文档对象
|
||||||
const doc = new PDFDocument({
|
const doc = new PDFDocument({
|
||||||
size: 'A4',
|
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 chineseFontLoaded = false;
|
||||||
let boldFontLoaded = false;
|
|
||||||
let currentFont = null;
|
let currentFont = null;
|
||||||
|
|
||||||
// 尝试加载字体
|
// 打印字体路径,用于调试
|
||||||
|
console.log('字体路径检查:');
|
||||||
|
console.log('simsun.ttf:', fs.existsSync(simsunTtfPath));
|
||||||
|
console.log('simsun.ttc:', fs.existsSync(simsunTtcPath));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (fs.existsSync(primaryFontPath)) {
|
// 1. 优先尝试加载simsun.ttf字体
|
||||||
doc.registerFont('SourceHanSans', primaryFontPath);
|
if (fs.existsSync(simsunTtfPath)) {
|
||||||
doc.font('SourceHanSans');
|
try {
|
||||||
currentFont = 'SourceHanSans';
|
doc.registerFont('SimSun', simsunTtfPath);
|
||||||
chineseFontLoaded = true;
|
|
||||||
} else if (fs.existsSync(simsunPath)) {
|
|
||||||
doc.registerFont('SimSun', simsunPath);
|
|
||||||
doc.font('SimSun');
|
doc.font('SimSun');
|
||||||
currentFont = 'SimSun';
|
currentFont = 'SimSun';
|
||||||
chineseFontLoaded = true;
|
chineseFontLoaded = true;
|
||||||
} else if (fs.existsSync(fallbackFontPath)) {
|
console.log('成功加载simsun.ttf字体');
|
||||||
doc.registerFont('SimSun', fallbackFontPath);
|
} catch (ttfError) {
|
||||||
doc.font('SimSun');
|
console.error('加载simsun.ttf字体失败:', ttfError);
|
||||||
currentFont = 'SimSun';
|
}
|
||||||
chineseFontLoaded = true;
|
|
||||||
} else if (process.platform === 'darwin') {
|
|
||||||
doc.font('Arial Unicode MS');
|
|
||||||
currentFont = 'Arial Unicode MS';
|
|
||||||
chineseFontLoaded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fs.existsSync(boldFontPath)) {
|
// 2. 如果simsun.ttf加载失败,尝试加载simsun.ttc字体
|
||||||
doc.registerFont('SourceHanSansBold', boldFontPath);
|
if (!chineseFontLoaded && fs.existsSync(simsunTtcPath)) {
|
||||||
boldFontLoaded = true;
|
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) {
|
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) {
|
} catch (error) {
|
||||||
console.error('加载字体失败:', error);
|
console.error('字体加载过程中发生错误:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 返回Promise处理异步操作
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
// 保存到文件
|
// 保存到文件
|
||||||
const writeStream = fs.createWriteStream(filePath);
|
const writeStream = fs.createWriteStream(tempFilePath);
|
||||||
doc.pipe(writeStream);
|
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);
|
doc.fontSize(24).text('机考试卷', { align: 'center' }).moveDown(2);
|
||||||
|
|
||||||
// 恢复常规字体
|
|
||||||
if (currentFont) {
|
|
||||||
doc.font(currentFont);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助函数:绘制表格
|
// 辅助函数:绘制表格
|
||||||
function drawTable(headers, rows, cellWidth = 120, baseCellHeight = 25) {
|
function drawTable(headers, rows, cellWidth = 120, baseCellHeight = 25) {
|
||||||
// 从左边距开始
|
// 从左边距开始
|
||||||
@ -606,24 +272,16 @@ exports.generatePaperPdf = async function(jsonString) {
|
|||||||
const cellHeight = Math.max(baseCellHeight, lines.length * 15);
|
const cellHeight = Math.max(baseCellHeight, lines.length * 15);
|
||||||
|
|
||||||
doc.rect(marginLeft + i * adjustedCellWidth, currentY, adjustedCellWidth, cellHeight).stroke();
|
doc.rect(marginLeft + i * adjustedCellWidth, currentY, adjustedCellWidth, cellHeight).stroke();
|
||||||
|
// 对于表头,设置为粗体样式(通过增加字体大小实现)
|
||||||
if (boldFontLoaded) {
|
doc.fontSize(fontSize + 1).text(header, marginLeft + i * adjustedCellWidth + 5, currentY + (cellHeight - lines.length * 15) / 2, {
|
||||||
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,
|
width: adjustedCellWidth - 10,
|
||||||
height: cellHeight - 10
|
height: cellHeight - 10
|
||||||
});
|
});
|
||||||
|
// 恢复默认字体大小
|
||||||
if (currentFont) {
|
doc.fontSize(fontSize);
|
||||||
doc.font(currentFont);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 移动到下一行(无间隙)
|
// 移动到下一行
|
||||||
const headerLines = headers.map(header => calculateLines(header, adjustedCellWidth - 10).length);
|
const headerLines = headers.map(header => calculateLines(header, adjustedCellWidth - 10).length);
|
||||||
const maxHeaderLines = Math.max(...headerLines);
|
const maxHeaderLines = Math.max(...headerLines);
|
||||||
currentY += Math.max(baseCellHeight, maxHeaderLines * 15);
|
currentY += Math.max(baseCellHeight, maxHeaderLines * 15);
|
||||||
@ -643,7 +301,7 @@ exports.generatePaperPdf = async function(jsonString) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
currentY += rowHeight; // 无间隙
|
currentY += rowHeight;
|
||||||
});
|
});
|
||||||
|
|
||||||
doc.y = currentY;
|
doc.y = currentY;
|
||||||
@ -652,20 +310,14 @@ exports.generatePaperPdf = async function(jsonString) {
|
|||||||
// 辅助函数:添加base64图片
|
// 辅助函数:添加base64图片
|
||||||
function addBase64Image(base64String, maxWidth = 400, maxHeight = 300) {
|
function addBase64Image(base64String, maxWidth = 400, maxHeight = 300) {
|
||||||
try {
|
try {
|
||||||
// 移除base64前缀
|
|
||||||
const base64Data = base64String.replace(/^data:image\/\w+;base64,/, '');
|
const base64Data = base64String.replace(/^data:image\/\w+;base64,/, '');
|
||||||
// 将base64转换为缓冲区
|
|
||||||
const imageBuffer = Buffer.from(base64Data, 'base64');
|
const imageBuffer = Buffer.from(base64Data, 'base64');
|
||||||
// 获取图片尺寸
|
|
||||||
const image = doc.openImage(imageBuffer);
|
const image = doc.openImage(imageBuffer);
|
||||||
// 计算缩放比例
|
|
||||||
const scale = Math.min(maxWidth / image.width, maxHeight / image.height, 1);
|
const scale = Math.min(maxWidth / image.width, maxHeight / image.height, 1);
|
||||||
// 添加图片,从左边距开始
|
|
||||||
doc.image(image, doc.page.margins.left, doc.y, {
|
doc.image(image, doc.page.margins.left, doc.y, {
|
||||||
width: image.width * scale,
|
width: image.width * scale,
|
||||||
height: image.height * scale
|
height: image.height * scale
|
||||||
});
|
});
|
||||||
// 移动文档指针
|
|
||||||
doc.y += image.height * scale + 10;
|
doc.y += image.height * scale + 10;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('添加图片失败:', error);
|
console.error('添加图片失败:', error);
|
||||||
@ -673,6 +325,15 @@ exports.generatePaperPdf = async function(jsonString) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取考试信息
|
||||||
|
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(
|
drawTable(
|
||||||
['姓名', '身份证号', '准考证号'],
|
['姓名', '身份证号', '准考证号'],
|
||||||
@ -683,7 +344,7 @@ exports.generatePaperPdf = async function(jsonString) {
|
|||||||
// 绘制考试信息表格
|
// 绘制考试信息表格
|
||||||
drawTable(
|
drawTable(
|
||||||
['考试日期', '开始时间', '结束时间', '用时(分钟)'],
|
['考试日期', '开始时间', '结束时间', '用时(分钟)'],
|
||||||
[[examDate, startTime.split(' ')[1], endTime.split(' ')[1], durationMinutes.toString()]]
|
[[examDate, startTime ? startTime.split(' ')[1] : '', endTime ? endTime.split(' ')[1] : '', durationMinutes.toString()]]
|
||||||
);
|
);
|
||||||
doc.moveDown();
|
doc.moveDown();
|
||||||
|
|
||||||
@ -694,21 +355,21 @@ exports.generatePaperPdf = async function(jsonString) {
|
|||||||
);
|
);
|
||||||
doc.moveDown(2);
|
doc.moveDown(2);
|
||||||
|
|
||||||
// 添加题目信息
|
// 添加题目信息(如果有)
|
||||||
paperData.questions.forEach((question, index) => {
|
if (paper.questions && paper.questions.length > 0) {
|
||||||
// 题目类型和描述
|
// 对questions列表按照question_type和id进行排序
|
||||||
if (boldFontLoaded) {
|
paper.questions.sort((a, b) => {
|
||||||
doc.font('SourceHanSansBold');
|
if (a.question_type !== b.question_type) {
|
||||||
} else if (process.platform === 'darwin' && currentFont === 'Arial Unicode MS') {
|
return a.question_type.localeCompare(b.question_type);
|
||||||
doc.font('Arial Unicode MS Bold');
|
|
||||||
}
|
}
|
||||||
doc.fontSize(14).text(`第 ${index + 1} 题 (${question.question_type_name})`, doc.page.margins.left).moveDown();
|
return a.id - b.id;
|
||||||
|
});
|
||||||
|
|
||||||
if (currentFont) {
|
paper.questions.forEach((question, index) => {
|
||||||
doc.font(currentFont);
|
// 题目类型和描述 - 使用稍大的字体表示重点
|
||||||
}
|
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, {
|
doc.fontSize(12).text(question.question_description || '', {
|
||||||
x: doc.page.margins.left,
|
x: doc.page.margins.left,
|
||||||
width: doc.page.width - doc.page.margins.left - doc.page.margins.right
|
width: doc.page.width - doc.page.margins.left - doc.page.margins.right
|
||||||
}).moveDown();
|
}).moveDown();
|
||||||
@ -750,18 +411,10 @@ exports.generatePaperPdf = async function(jsonString) {
|
|||||||
// 添加填空题
|
// 添加填空题
|
||||||
if (question.blanks && question.blanks.length > 0) {
|
if (question.blanks && question.blanks.length > 0) {
|
||||||
question.blanks.forEach((blank, blankIndex) => {
|
question.blanks.forEach((blank, blankIndex) => {
|
||||||
if (boldFontLoaded) {
|
// 使用稍大字体突出显示填空标题
|
||||||
doc.font('SourceHanSansBold');
|
doc.fontSize(13).text(`填空 ${blankIndex + 1}`, doc.page.margins.left).moveDown();
|
||||||
} 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, {
|
doc.fontSize(12).text(blank.blank_description || '', {
|
||||||
x: doc.page.margins.left,
|
x: doc.page.margins.left,
|
||||||
width: doc.page.width - doc.page.margins.left - doc.page.margins.right
|
width: doc.page.width - doc.page.margins.left - doc.page.margins.right
|
||||||
}).moveDown();
|
}).moveDown();
|
||||||
@ -769,10 +422,10 @@ exports.generatePaperPdf = async function(jsonString) {
|
|||||||
['正确答案', '考生答案', '分值', '得分'],
|
['正确答案', '考生答案', '分值', '得分'],
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
Array.isArray(blank.correct_answers) ? blank.correct_answers.join(', ') : blank.correct_answers,
|
Array.isArray(blank.correct_answers) ? blank.correct_answers.join(', ') : blank.correct_answers || '',
|
||||||
Array.isArray(blank.examinee_answers) ? blank.examinee_answers.join(', ') : blank.examinee_answers,
|
Array.isArray(blank.examinee_answers) ? blank.examinee_answers.join(', ') : blank.examinee_answers || '',
|
||||||
blank.score.toString(),
|
(blank.score || 0).toString(),
|
||||||
blank.score_real.toString()
|
(blank.score_real || 0).toString()
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
100
|
100
|
||||||
@ -783,18 +436,10 @@ exports.generatePaperPdf = async function(jsonString) {
|
|||||||
// 添加选择题
|
// 添加选择题
|
||||||
if (question.choices && question.choices.length > 0) {
|
if (question.choices && question.choices.length > 0) {
|
||||||
question.choices.forEach((choice, choiceIndex) => {
|
question.choices.forEach((choice, choiceIndex) => {
|
||||||
if (boldFontLoaded) {
|
// 使用稍大字体突出显示选择题标题
|
||||||
doc.font('SourceHanSansBold');
|
doc.fontSize(13).text(`选择题 ${choiceIndex + 1}`, doc.page.margins.left).moveDown();
|
||||||
} 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, {
|
doc.fontSize(12).text(choice.choice_description || '', {
|
||||||
x: doc.page.margins.left,
|
x: doc.page.margins.left,
|
||||||
width: doc.page.width - doc.page.margins.left - doc.page.margins.right
|
width: doc.page.width - doc.page.margins.left - doc.page.margins.right
|
||||||
}).moveDown();
|
}).moveDown();
|
||||||
@ -810,10 +455,10 @@ exports.generatePaperPdf = async function(jsonString) {
|
|||||||
['正确答案', '考生答案', '分值', '得分'],
|
['正确答案', '考生答案', '分值', '得分'],
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
Array.isArray(choice.correct_answers) ? choice.correct_answers.join(', ') : choice.correct_answers,
|
Array.isArray(choice.correct_answers) ? choice.correct_answers.join(', ') : choice.correct_answers || '',
|
||||||
Array.isArray(choice.examinee_answers) ? choice.examinee_answers.join(', ') : choice.examinee_answers,
|
Array.isArray(choice.examinee_answers) ? choice.examinee_answers.join(', ') : choice.examinee_answers || '',
|
||||||
choice.score.toString(),
|
(choice.score || 0).toString(),
|
||||||
choice.score_real.toString()
|
(choice.score_real || 0).toString()
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
100
|
100
|
||||||
@ -824,26 +469,71 @@ exports.generatePaperPdf = async function(jsonString) {
|
|||||||
// 分页处理
|
// 分页处理
|
||||||
if (doc.y > 700) {
|
if (doc.y > 700) {
|
||||||
doc.addPage();
|
doc.addPage();
|
||||||
|
// 在新页面上重新设置字体,确保字体设置正确应用
|
||||||
|
if (currentFont) {
|
||||||
|
doc.font(currentFont);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 结束文档
|
// 结束文档
|
||||||
doc.end();
|
doc.end();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
// 处理流事件
|
||||||
writeStream.on('finish', async () => {
|
writeStream.on('finish', async () => {
|
||||||
try {
|
try {
|
||||||
// 调用copyToDesk方法将文件复制到桌面,获取新的路径
|
// 复制文件到桌面
|
||||||
const newFilePath = await exports.copyToDesk(filePath);
|
const desktopFilePath = await copyToDesk(tempFilePath);
|
||||||
resolve(newFilePath);
|
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) {
|
} catch (error) {
|
||||||
|
console.error('PDF生成过程中发生错误:', error);
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
writeStream.on('error', reject);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('生成PDF失败:', error);
|
console.error('生成试卷PDF失败:', error);
|
||||||
throw 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
|
||||||
};
|
};
|
@ -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;
|
||||||
}
|
}
|
||||||
|
210
src/preload.js
210
src/preload.js
@ -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),
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
@ -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 },
|
||||||
// 可以在这里添加更多考生相关的路由
|
// 可以在这里添加更多考生相关的路由
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
434
src/views/user/EndView.vue
Normal 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 () {
|
||||||
|
// 如果store中没有数据,尝试从localStorage获取备用数据
|
||||||
|
if (!this.$store.state.paper || Object.keys(this.$store.state.paper).length === 0) {
|
||||||
|
const savedPaper = localStorage.getItem('lastExamPaper');
|
||||||
|
if (savedPaper) {
|
||||||
|
try {
|
||||||
|
// 解析并返回localStorage中的数据,但不直接修改store
|
||||||
|
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);
|
||||||
|
// 保存到store,确保所有地方都能通过this.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('加载考试结果失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 判卷方法
|
||||||
|
// 增强checkAnswers方法,确保API存在性检查
|
||||||
|
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>
|
@ -256,6 +256,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 开始考试方法
|
// 开始考试方法
|
||||||
|
// 修改startExam方法,移除保存完整试卷信息到store的代码
|
||||||
async startExam() {
|
async startExam() {
|
||||||
if (!this.lastExam) {
|
if (!this.lastExam) {
|
||||||
this.$message.warning('请先获取考试信息')
|
this.$message.warning('请先获取考试信息')
|
||||||
@ -305,8 +306,15 @@ export default {
|
|||||||
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)
|
||||||
|
|
||||||
|
// 仅保存试卷ID到localStorage(作为备用机制)
|
||||||
|
try {
|
||||||
|
localStorage.setItem('currentPaperId', result.data.id)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('保存试卷ID到localStorage失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
this.$alert(
|
this.$alert(
|
||||||
'已完成组卷,点击"进入考试"开始答题',
|
'已完成组卷,点击"进入考试"开始答题',
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果路由参数中没有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('/')
|
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中没有,尝试从后端获取
|
|
||||||
|
// 1. 从接口获取试卷信息
|
||||||
await this.loadPaperInfo(this.paperId)
|
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) {
|
||||||
// 将返回的paper对象同步到store中
|
// 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.$store.commit('setPaper', result.data)
|
||||||
this.canSubmit = result.data.paper_minutes_min ? false : true
|
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();
|
||||||
|
|
||||||
|
// 调用提交试卷API
|
||||||
|
const result = await window.electronAPI.examingSubmitPaper({ paperId: this.paper.id });
|
||||||
|
console.log('提交试卷API返回:', result);
|
||||||
|
|
||||||
if (result && result.success) {
|
if (result && result.success) {
|
||||||
this.$message.success('交卷成功!')
|
// 确保result.data是字符串类型
|
||||||
// 清理定时器
|
const paperDataStr = typeof result.data === 'string' ? result.data : JSON.stringify(result.data);
|
||||||
if (this.timer) {
|
|
||||||
clearInterval(this.timer)
|
try {
|
||||||
this.timer = null
|
// 尝试解析数据
|
||||||
}
|
const paperData = JSON.parse(paperDataStr);
|
||||||
if (this.processTimer) {
|
// console.log('解析后的试卷数据:', paperData);
|
||||||
clearInterval(this.processTimer)
|
|
||||||
this.processTimer = null
|
// 1. 将试卷数据保存到store
|
||||||
}
|
this.$store.commit('setPaper', paperData);
|
||||||
// 跳转到结果页面
|
|
||||||
this.$router.push('/user/exam-result/' + this.paperId)
|
// 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 {
|
} else {
|
||||||
this.$message.error('交卷失败: ' + (result.message || '未知错误'))
|
this.$message.error('PDF生成失败,但不影响考试结果'); // 修改错误消息,不影响交卷流程
|
||||||
|
console.error('PDF生成失败:', pdfResult);
|
||||||
|
// 即使PDF生成失败,也继续交卷流程
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || '提交失败');
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
|
||||||
// 用户取消交卷
|
|
||||||
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) {
|
||||||
|
// 将返回的paper对象同步到store中
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user