继续考试功能已实现

This commit is contained in:
chenqiang 2025-09-13 07:24:13 +08:00
parent 681e3fe273
commit 08bf170216
10 changed files with 1451 additions and 346 deletions

View File

@ -772,9 +772,10 @@ async function startPaper(paperId) {
/** /**
* 提交考试 * 提交考试
* @param {number} paperId - 试卷ID * @param {number} paperId - 试卷ID
* @param {number} duration_seconds - 答题时长
* @returns {Promise<Object>} - 包含操作结果和试卷数据的对象 * @returns {Promise<Object>} - 包含操作结果和试卷数据的对象
*/ */
async function submitPaper(paperId) { async function submitPaper(paperId, duration_seconds = 0) {
try { try {
const userDb = await getDbConnection(getUserDbPath()); const userDb = await getDbConnection(getUserDbPath());
const currentTime = formatDateTime(new Date()); const currentTime = formatDateTime(new Date());
@ -783,9 +784,10 @@ async function submitPaper(paperId) {
`UPDATE examinee_papers `UPDATE examinee_papers
SET paper_status = 2, SET paper_status = 2,
paper_submit_time = ?, paper_submit_time = ?,
paper_end_time = ? paper_end_time = ?,
paper_duration_seconds = ?
WHERE id = ?`, WHERE id = ?`,
[currentTime, currentTime, paperId] [currentTime, currentTime, duration_seconds, paperId]
); );
// 查询更新后的试卷数据 // 查询更新后的试卷数据
@ -819,12 +821,23 @@ async function endPaper(paperId) {
const userDb = await getDbConnection(getUserDbPath()); const userDb = await getDbConnection(getUserDbPath());
const currentTime = formatDateTime(new Date()); const currentTime = formatDateTime(new Date());
// 先查询当前试卷的paper_minutes
const existingPaper = await userDb.getAsync(
`SELECT paper_minutes FROM examinee_papers WHERE id = ?`,
[paperId]
);
// 计算paper_duration_seconds (将分钟转换为秒)
const paperMinutes = existingPaper ? parseInt(existingPaper.paper_minutes) : 0;
const paperDurationSeconds = paperMinutes > 0 ? paperMinutes * 60 : 0;
await userDb.runAsync( await userDb.runAsync(
`UPDATE examinee_papers `UPDATE examinee_papers
SET paper_status = 2, SET paper_status = 2,
paper_end_time = ? paper_end_time = ?,
paper_duration_seconds = ?
WHERE id = ?`, WHERE id = ?`,
[currentTime, paperId] [currentTime, paperDurationSeconds, paperId]
); );
// 查询更新后的试卷数据 // 查询更新后的试卷数据
@ -851,18 +864,20 @@ async function endPaper(paperId) {
/** /**
* 更新试卷最后操作时间 * 更新试卷最后操作时间
* @param {number} paperId - 试卷ID * @param {number} paperId - 试卷ID
* @param {number} duration_seconds - 答题时长
* @returns {Promise<Object>} - 包含操作结果和试卷数据的对象 * @returns {Promise<Object>} - 包含操作结果和试卷数据的对象
*/ */
async function processPaper(paperId) { async function processPaper(paperId, duration_seconds) {
try { try {
const userDb = await getDbConnection(getUserDbPath()); const userDb = await getDbConnection(getUserDbPath());
const currentTime = formatDateTime(new Date()); const currentTime = formatDateTime(new Date());
await userDb.runAsync( await userDb.runAsync(
`UPDATE examinee_papers `UPDATE examinee_papers
SET paper_last_time = ? SET paper_last_time = ?,
paper_duration_seconds = ?
WHERE id = ?`, WHERE id = ?`,
[currentTime, paperId] [currentTime, duration_seconds, paperId]
); );
// 查询更新后的试卷数据 // 查询更新后的试卷数据
@ -1196,8 +1211,195 @@ async function getPaper(paperId) {
} }
} }
// 在文件末尾添加所有导出 /**
module.exports = { * 根据考生ID获取历史考试记录
* @param {number} examineeId - 考生ID
* @returns {Promise<Object>} - 包含查询结果的对象
*/
async function getExamineePaper(examineeId) {
try {
// 1. 获取数据库连接
const systemDb = await getDbConnection(getSystemDbPath());
const userDb = await getDbConnection(getUserDbPath());
// 2. 查询考生信息
const examinee = await userDb.getAsync(
`SELECT * FROM examinee WHERE id = ?`,
[examineeId]
);
if (!examinee) {
console.log(`未找到ID为${examineeId}的考生`);
return {
success: false,
message: '未找到历史考试',
data: null
};
}
// 3. 查询考生的所有试卷记录
const examineePapers = await userDb.allAsync(
`SELECT * FROM examinee_papers WHERE examinee_id = ? ORDER BY created_at DESC`,
[examineeId]
);
if (!examineePapers || examineePapers.length === 0) {
console.log(`未找到ID为${examineeId}的考生的试卷记录`);
return {
success: false,
message: '未找到历史考试',
data: null
};
}
// 4. 为每个试卷获取详细信息
const papersWithDetails = [];
for (const paper of examineePapers) {
const paperId = paper.id;
// 4.1 查询试卷关联的题目
const paperQuestions = await userDb.allAsync(
`SELECT * FROM paper_questions WHERE paper_id = ?`,
[paperId]
);
// 4.2 为每个题目获取详细信息
const questionsWithDetails = [];
for (const question of paperQuestions) {
const questionId = question.id;
const questionType = question.question_type;
// 查询题型名称
const dictItem = await systemDb.getAsync(
`SELECT item_name FROM dict_items WHERE type_code = 'question_type' AND item_code = ?`,
[questionType]
);
const questionWithDetails = {
...question,
question_type_name: dictItem && dictItem.item_name ? dictItem.item_name : '',
images: [],
datasets: [],
choices: [],
blanks: []
};
// 查询题目图片
const images = await userDb.allAsync(
`SELECT * FROM question_images WHERE question_id = ?`,
[questionId]
);
questionWithDetails.images = images;
// 查询题目数据集
const datasets = await userDb.allAsync(
`SELECT * FROM question_datasets WHERE question_id = ?`,
[questionId]
);
// 解析dataset_data为数组
questionWithDetails.datasets = datasets.map(dataset => ({
...dataset,
dataset_data: JSON.parse(dataset.dataset_data || '[]')
}));
// 根据题型查询对应的答案表
if (questionType === 'choice') {
// 查询选择题
const choices = await userDb.allAsync(
`SELECT * FROM question_choices WHERE question_id = ?`,
[questionId]
);
// 解析数组列
questionWithDetails.choices = choices.map(choice => ({
...choice,
choice_options: JSON.parse(choice.choice_options || '[]'),
correct_answers: JSON.parse(choice.correct_answers || '[]'),
examinee_answers: JSON.parse(choice.examinee_answers || '[]')
}));
} else if (questionType === 'fill_blank') {
// 查询填空题
const blanks = await userDb.allAsync(
`SELECT * FROM question_fill_blanks WHERE question_id = ?`,
[questionId]
);
// 解析数组列
questionWithDetails.blanks = blanks.map(blank => ({
...blank,
correct_answers: JSON.parse(blank.correct_answers || '[]'),
examinee_answers: JSON.parse(blank.examinee_answers || '[]')
}));
}
questionsWithDetails.push(questionWithDetails);
}
// 4.3 构建完整的试卷对象
const fullPaper = {
...paper,
examinee: examinee,
questions: questionsWithDetails
};
papersWithDetails.push(fullPaper);
}
return {
success: true,
message: '找到历史考试',
data: papersWithDetails
};
} catch (error) {
console.error('获取考生历史试卷过程中发生错误:', error);
return {
success: false,
message: `获取历史考试失败: ${error.message}`,
data: null
};
}
}
// 更新导出语句添加getExamineePaper方法
/**
* 继续考试
* @param {number} paperId - 试卷ID
* @param {number} usedMinutes - 已用时间分钟
* @returns {Promise<Object>} - 包含操作结果和试卷数据的对象
*/
async function continueExam(paperId) {
try {
const userDb = await getDbConnection(getUserDbPath());
const currentTime = new Date();
const formattedCurrentTime = formatDateTime(currentTime);
await userDb.runAsync(
`UPDATE examinee_papers
SET paper_status = 1,
paper_last_time = ?
WHERE id = ?`,
[formattedCurrentTime, paperId]
);
// 查询更新后的试卷数据
const paper = await userDb.getAsync(
`SELECT * FROM examinee_papers WHERE id = ?`,
[paperId]
);
return {
success: true,
message: '考试已成功继续',
data: paper
};
} catch (error) {
console.error('继续考试失败:', error);
return {
success: false,
message: `继续考试失败: ${error.message}`
};
}
}
exports = module.exports = {
formatDateTime, formatDateTime,
batchInsert, batchInsert,
generateExamineePaper, generateExamineePaper,
@ -1209,5 +1411,7 @@ module.exports = {
endPaper, endPaper,
processPaper, processPaper,
checkPaperAnswers, checkPaperAnswers,
getPaper getPaper,
getExamineePaper,
continueExam
}; };

View File

@ -1,6 +1,7 @@
const path = require('path');
const fs = require('fs'); const fs = require('fs');
const { app } = require('electron'); const { app } = require('electron');
// 添加缺少的path模块导入
const path = require('path');
// 判断是否为开发环境 // 判断是否为开发环境
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged; const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;

View File

@ -208,6 +208,7 @@ userSchema = {
paper_last_time TEXT, paper_last_time TEXT,
paper_submit_time TEXT, paper_submit_time TEXT,
paper_end_time TEXT, paper_end_time TEXT,
paper_duration_seconds INTEGER DEFAULT 0,
paper_status INTEGER NOT NULL DEFAULT 0, paper_status INTEGER NOT NULL DEFAULT 0,
paper_score_real REAL DEFAULT 0, paper_score_real REAL DEFAULT 0,
paper_score REAL DEFAULT 0, paper_score REAL DEFAULT 0,

View File

@ -1,5 +1,6 @@
const sqlite3 = require('sqlite3');
const { promisify } = require('util'); const { promisify } = require('util');
// 添加缺少的sqlite3模块导入
const sqlite3 = require('sqlite3');
sqlite3.verbose(); sqlite3.verbose();

View File

@ -110,8 +110,10 @@ contextBridge.exposeInMainWorld("electronAPI", {
ipcRenderer.invoke("examinee-update", { id, examineeData }), ipcRenderer.invoke("examinee-update", { id, examineeData }),
examineeDelete: (id) => ipcRenderer.invoke("examinee-delete", id), examineeDelete: (id) => ipcRenderer.invoke("examinee-delete", id),
// 在考生考试服务相关接口部分添加examingCheckPaperAnswers // 在"考生考试服务相关接口"部分添加getPaper接口
// 考生考试服务相关接口 examingGetPaper: ({ paperId }) =>
ipcRenderer.invoke("examing-get-paper", { paperId }),
// 在考生考试服务相关接口
examingGeneratePaper: ({ examineeId, examId }) => examingGeneratePaper: ({ examineeId, examId }) =>
ipcRenderer.invoke("examing-generate-paper", { examineeId, examId }), ipcRenderer.invoke("examing-generate-paper", { examineeId, examId }),
examingGetPaperStatus: ({ examineeId, examId }) => examingGetPaperStatus: ({ examineeId, examId }) =>
@ -125,21 +127,26 @@ contextBridge.exposeInMainWorld("electronAPI", {
tableName, tableName,
relatedId, relatedId,
}), }),
// 修复examingUpdateAnswer接口的参数结构
examingUpdateAnswer: ({ tableName, id, answers }) => examingUpdateAnswer: ({ tableName, id, answers }) =>
ipcRenderer.invoke("examing-update-answer", { tableName, id, answers }), ipcRenderer.invoke("examing-update-answer", { tableName, id, answers }),
// 在考生考试服务相关接口部分确保examingStartPaper正确暴露
examingStartPaper: ({ paperId }) => examingStartPaper: ({ paperId }) =>
ipcRenderer.invoke("examing-start-paper", { paperId }), ipcRenderer.invoke("examing-start-paper", { paperId }),
examingSubmitPaper: ({ paperId }) => examingSubmitPaper: ({ paperId, duration_seconds = 0 }) =>
ipcRenderer.invoke("examing-submit-paper", { paperId }), ipcRenderer.invoke("examing-submit-paper", { paperId, duration_seconds }),
// 确保examingGetExamResult API正确暴露
examingGetExamResult: ({ paperId }) => examingGetExamResult: ({ paperId }) =>
ipcRenderer.invoke("examing-get-exam-result", { paperId }), ipcRenderer.invoke("examing-get-exam-result", { paperId }),
examingCheckPaperAnswers: ({ paperId }) => examingCheckPaperAnswers: ({ paperId }) =>
ipcRenderer.invoke("examing-check-paper-answers", { paperId }), ipcRenderer.invoke("examing-check-paper-answers", { paperId }),
examingProcessPaper: ({ paperId }) => examingProcessPaper: ({ paperId, duration_seconds = 0 }) =>
ipcRenderer.invoke("examing-process-paper", { paperId }), ipcRenderer.invoke("examing-process-paper", { paperId, duration_seconds }),
examingGetExamineePaper: ({ examineeId }) =>
ipcRenderer.invoke("examing-get-examinee-paper", { examineeId }),
// 继续考试接口
examingContinuePaper: ({ paperId }) =>
ipcRenderer.invoke("examing-continue-paper", { paperId }),
// 新增:结束考试接口
examingEndPaper: ({ paperId }) =>
ipcRenderer.invoke("examing-end-paper", { paperId }),
// 文件服务相关接口 // 文件服务相关接口
// 保留一个fileGeneratePaperPdf定义移除重复的 // 保留一个fileGeneratePaperPdf定义移除重复的

View File

@ -1,4 +1,4 @@
// 保留第一个导入 // 首先在文件顶部的require语句中添加getExamineePaper
const { const {
generateExamineePaper, generateExamineePaper,
loadPaperSerial, loadPaperSerial,
@ -10,13 +10,12 @@ const {
processPaper, processPaper,
checkPaperAnswers, checkPaperAnswers,
getPaper, getPaper,
getExamineePaper,
continueExam, // 添加continueExam到导入列表中
} = 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');
/** /**
* 服务层生成考生试卷 * 服务层生成考生试卷
* @param {Object} examineeData - 考生数据 * @param {Object} examineeData - 考生数据
@ -244,19 +243,41 @@ async function startPaperService(paperId) {
} }
} }
/**
* 服务层获取试卷详细信息
* @param {number} paperId - 试卷ID
* @returns {Promise<Object>} - 包含试卷详细信息的对象
*/
async function getPaperService(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
* @returns {Promise<Object>} - 包含操作结果的对象 * @returns {Promise<Object>} - 包含操作结果的对象
*/ */
async function submitPaperService(paperId) { async function submitPaperService(paperId, duration_seconds) {
try { try {
if (!paperId || paperId <= 0) { if (!paperId || paperId <= 0) {
throw new Error("试卷ID必须为正数"); throw new Error("试卷ID必须为正数");
} }
// 1. 提交试卷 // 1. 提交试卷
const submitResult = await submitPaper(paperId); const submitResult = await submitPaper(paperId, duration_seconds);
if (!submitResult.success) { if (!submitResult.success) {
throw new Error(submitResult.message); throw new Error(submitResult.message);
@ -338,16 +359,20 @@ async function endPaperService(paperId) {
/** /**
* 服务层处理试卷 * 服务层处理试卷
* @param {number} paperId - 试卷ID * @param {number} paperId - 试卷ID
* @param {number} duration_seconds - 答题时长
* @returns {Promise<Object>} - 包含操作结果的对象 * @returns {Promise<Object>} - 包含操作结果的对象
*/ */
async function processPaperService(paperId) { async function processPaperService(paperId, duration_seconds) {
try { try {
if (!paperId || paperId <= 0) { if (!paperId || paperId <= 0) {
throw new Error("试卷ID必须为正数"); throw new Error("试卷ID必须为正数");
} }
const result = await processPaper(paperId); // 这样在继续考试时就不会覆盖数据库中已有的paper_duration_seconds值
return result; if (duration_seconds === undefined || duration_seconds === null) {
return await processPaper(paperId);
}
return await processPaper(paperId, duration_seconds);
} catch (error) { } catch (error) {
console.error("服务层: 处理试卷失败", error); console.error("服务层: 处理试卷失败", error);
return { return {
@ -380,10 +405,50 @@ async function checkPaperAnswersService(paperId) {
} }
/** /**
* 初始化考试相关的IPC处理程序 * 服务层根据考生ID获取试卷数据
* @param {import('electron').IpcMain} ipcMain - IPC主进程实例 * @param {number} examineeId - 考生ID
* @returns {Promise<Object>} - 包含试卷数据的对象
*/ */
// 在initExamingIpc函数中添加examing-get-exam-result处理器 async function getExamineePaperService(examineeId) {
try {
if (!examineeId || examineeId <= 0) {
throw new Error("考生ID必须为正数");
}
const result = await getExamineePaper(examineeId);
return result;
} catch (error) {
console.error("服务层: 获取考生试卷数据失败", error);
return {
success: false,
message: `获取考生试卷数据失败: ${error.message}`,
};
}
}
/**
* 服务层继续未完成的考试
* @param {number} paperId - 试卷ID
* @returns {Promise<Object>} - 包含操作结果的对象
*/
async function continueExamService(paperId) {
try {
if (!paperId || paperId <= 0) {
throw new Error("试卷ID必须为正数");
}
const result = await continueExam(paperId);
return result;
} catch (error) {
console.error("服务层: 继续考试失败", error);
return {
success: false,
message: `继续考试失败: ${error.message}`,
};
}
}
// 在initExamingIpc函数中添加continueExam的IPC处理程序在其他处理器之后添加
function initExamingIpc(ipcMain) { function initExamingIpc(ipcMain) {
// 生成考生试卷 // 生成考生试卷
ipcMain.handle( ipcMain.handle(
@ -490,17 +555,21 @@ function initExamingIpc(ipcMain) {
}); });
// 提交考试 // 提交考试
ipcMain.handle("examing-submit-paper", async (event, { paperId }) => { // 提交考试
try { ipcMain.handle(
return await submitPaperService(paperId); "examing-submit-paper",
} catch (error) { async (event, { paperId, duration_seconds }) => {
console.error("提交考试失败:", error); try {
return { return await submitPaperService(paperId, duration_seconds);
success: false, } catch (error) {
message: `提交考试失败: ${error.message}`, console.error("提交考试失败:", error);
}; return {
success: false,
message: `提交考试失败: ${error.message}`,
};
}
} }
}); );
// 结束考试 // 结束考试
ipcMain.handle("examing-end-paper", async (event, { paperId }) => { ipcMain.handle("examing-end-paper", async (event, { paperId }) => {
@ -516,17 +585,20 @@ function initExamingIpc(ipcMain) {
}); });
// 处理试卷 // 处理试卷
ipcMain.handle("examing-process-paper", async (event, { paperId }) => { ipcMain.handle(
try { "examing-process-paper",
return await processPaperService(paperId); async (event, { paperId, duration_seconds }) => {
} catch (error) { try {
console.error("处理试卷失败:", error); return await processPaperService(paperId, duration_seconds);
return { } catch (error) {
success: false, console.error("处理试卷失败:", error);
message: `处理试卷失败: ${error.message}`, return {
}; success: false,
message: `处理试卷失败: ${error.message}`,
};
}
} }
}); );
// 检查试卷答案并计算得分 // 检查试卷答案并计算得分
ipcMain.handle("examing-check-paper-answers", async (event, { paperId }) => { ipcMain.handle("examing-check-paper-answers", async (event, { paperId }) => {
@ -553,6 +625,48 @@ function initExamingIpc(ipcMain) {
}; };
} }
}); });
// 添加通过考生ID获取试卷数据的IPC处理器
ipcMain.handle(
"examing-get-examinee-paper",
async (event, { examineeId }) => {
try {
return await getExamineePaperService(examineeId);
} catch (error) {
console.error("获取考生试卷数据失败:", error);
return {
success: false,
message: `获取考生试卷数据失败: ${error.message}`,
};
}
}
);
// 添加继续考试的IPC处理器
ipcMain.handle("examing-continue-paper", async (event, { paperId }) => {
try {
return await continueExamService(paperId);
} catch (error) {
console.error("继续考试失败:", error);
return {
success: false,
message: `继续考试失败: ${error.message}`,
};
}
});
// 在initExamingIpc函数中添加getPaper的IPC处理器
ipcMain.handle("examing-get-paper", async (event, { paperId }) => {
try {
return await getPaperService(paperId);
} catch (error) {
console.error("获取试卷详细信息失败:", error);
return {
success: false,
message: `获取试卷详细信息失败: ${error.message}`,
};
}
});
} }
// 导出使用CommonJS格式 // 导出使用CommonJS格式
@ -568,5 +682,9 @@ module.exports = {
endPaperService, endPaperService,
processPaperService, processPaperService,
checkPaperAnswersService, checkPaperAnswersService,
getExamResultService,
getExamineePaperService,
continueExamService,
getPaperService,
initExamingIpc, initExamingIpc,
}; };

View File

@ -395,10 +395,17 @@ async function generatePaperPdf(jsonString) {
const endTime = paperData.paper_end_time; const endTime = paperData.paper_end_time;
// 截取考试日期 (假设格式为 'YYYY-MM-DD HH:mm:ss') // 截取考试日期 (假设格式为 'YYYY-MM-DD HH:mm:ss')
const examDate = startTime.split(' ')[0]; const examDate = startTime.split(' ')[0];
// 计算用时(分钟) // 计算用时(分钟)- 直接使用paper_duration_seconds字段
const start = new Date(startTime.replace(/-/g, '/')); let durationMinutes = 0;
const end = new Date(endTime.replace(/-/g, '/')); if (paperData.paper_duration_seconds && paperData.paper_duration_seconds > 0) {
const durationMinutes = Math.round((end - start) / (1000 * 60)); // 直接从paper_duration_seconds字段获取并转换为分钟
durationMinutes = Math.round(paperData.paper_duration_seconds / 60);
} else {
// 备选方案如果paper_duration_seconds不存在或无效使用原来的计算方式
const start = new Date(startTime.replace(/-/g, '/'));
const end = new Date(endTime.replace(/-/g, '/'));
durationMinutes = Math.round((end - start) / (1000 * 60));
}
// 提取试卷信息 // 提取试卷信息
// 计算总题量每个question下的choice题和fill_blank题的数量之和 // 计算总题量每个question下的choice题和fill_blank题的数量之和

View File

@ -106,7 +106,8 @@ export default {
return { return {
size: 'default', size: 'default',
pdfPath: '', pdfPath: '',
isChecking: false isChecking: false,
paperData: null // paper
} }
}, },
// script // script
@ -115,22 +116,9 @@ export default {
examinee () { examinee () {
return this.$store.state.examinee || {} return this.$store.state.examinee || {}
}, },
// store // store使
paper () { paper () {
// storelocalStorage return this.paperData || {}
if (!this.$store.state.paper || Object.keys(this.$store.state.paper).length === 0) {
const savedPaper = localStorage.getItem('lastExamPaper');
if (savedPaper) {
try {
// localStoragestore
return JSON.parse(savedPaper);
} catch (e) {
console.error('解析localStorage试卷数据失败:', e);
}
}
return {};
}
return this.$store.state.paper || {};
}, },
// //
iconStyle () { iconStyle () {
@ -164,112 +152,101 @@ export default {
// //
checkPaperData () { checkPaperData () {
// 1. store // 1. paperId
if (this.paper && this.paper.id) { const paperIdFromRoute = this.$route.params.paperId
console.log('store中已有完整试卷数据');
//
if (!this.paper.is_checked) {
this.checkAnswers(this.paper.id);
}
return;
}
// 2. paperId
const paperIdFromRoute = this.$route.params.paperId;
if (!paperIdFromRoute) { if (!paperIdFromRoute) {
console.error('路由参数中没有找到paperId'); console.error('路由参数中没有找到paperId')
this.$message.error('无法获取试卷信息,请重新提交试卷'); this.$message.error('无法获取试卷信息,请重新提交试卷')
return; return
} }
// 3. localStorage // 2. localStorage
const savedPaperData = localStorage.getItem('lastExamPaper'); const savedPaperData = localStorage.getItem('lastExamPaper')
if (savedPaperData) { if (savedPaperData) {
try { try {
const paperData = JSON.parse(savedPaperData); const paperData = JSON.parse(savedPaperData)
// paperId // paperId
if (paperData.id && paperData.id.toString() === paperIdFromRoute.toString()) { if (paperData.id && paperData.id.toString() === paperIdFromRoute.toString()) {
console.log('从localStorage恢复到store的试卷数据:', paperData); console.log('从localStorage恢复的试卷数据:', paperData)
// storethis.paper访 // store
this.$store.commit('setPaper', paperData); this.paperData = paperData
// //
if (!paperData.is_checked) { if (!paperData.is_checked) {
this.checkAnswers(paperData.id); this.checkAnswers(paperData.id)
} }
return; return
} }
} catch (e) { } catch (e) {
console.error('解析localStorage中的试卷数据失败:', e); console.error('解析localStorage中的试卷数据失败:', e)
} }
} }
// 4. API // 3. API
console.log('调用API加载试卷数据paperId:', paperIdFromRoute); console.log('调用API加载试卷数据paperId:', paperIdFromRoute)
this.loadExamResult(paperIdFromRoute); this.loadExamResult(paperIdFromRoute)
}, },
// - store // - store
async loadExamResult (paperIdFromParams) { async loadExamResult (paperIdFromParams) {
try { try {
const paperId = paperIdFromParams || this.$route.params.paperId; const paperId = paperIdFromParams || this.$route.params.paperId
if (!paperId) { if (!paperId) {
console.error('没有找到试卷ID'); console.error('没有找到试卷ID')
this.$message.error('无法获取试卷信息,请重新提交试卷'); this.$message.error('无法获取试卷信息,请重新提交试卷')
return; return
} }
// API // API
if (!window.electronAPI || typeof window.electronAPI.examingGetExamResult !== 'function') { if (!window.electronAPI || typeof window.electronAPI.examingGetExamResult !== 'function') {
console.error('examingGetExamResult API不存在'); console.error('examingGetExamResult API不存在')
this.$message.warning('获取考试结果功能暂不可用'); this.$message.warning('获取考试结果功能暂不可用')
return; return
} }
const result = await window.electronAPI.examingGetExamResult({ paperId }); const result = await window.electronAPI.examingGetExamResult({ paperId })
if (result && result.success && result.data) { if (result && result.success && result.data) {
// store // store
this.$store.commit('setPaper', result.data); this.paperData = result.data
// localStorage // localStorage
localStorage.setItem('lastExamPaper', JSON.stringify(result.data)); localStorage.setItem('lastExamPaper', JSON.stringify(result.data))
console.log('API获取并保存到store的试卷数据:', this.$store.state.paper); console.log('API获取的试卷数据:', this.paperData)
// //
if (!result.data.is_checked) { if (!result.data.is_checked) {
this.checkAnswers(result.data.id || paperId); this.checkAnswers(result.data.id || paperId)
} }
} else { } else {
console.error('获取考试结果失败:', result?.message || '未知错误'); console.error('获取考试结果失败:', result?.message || '未知错误')
this.$message.error('获取考试结果失败'); this.$message.error('获取考试结果失败')
} }
} catch (error) { } catch (error) {
console.error('加载考试结果失败:', error); console.error('加载考试结果失败:', error)
this.$message.error('加载考试结果失败'); this.$message.error('加载考试结果失败')
} }
}, },
// //
//
async checkAnswers () { async checkAnswers () {
try { try {
if (!this.paper || !this.paper.id) { if (!this.paperData || !this.paperData.id) {
console.error('没有试卷ID无法判卷'); console.error('没有试卷ID无法判卷')
this.$message.warning('缺少试卷信息,无法判卷'); this.$message.warning('缺少试卷信息,无法判卷')
return; return
} }
this.isChecking = true this.isChecking = true
const paperId = this.paper.id const paperId = this.paperData.id
// API // API
if (!window.electronAPI || typeof window.electronAPI.examingCheckPaperAnswers !== 'function') { if (!window.electronAPI || typeof window.electronAPI.examingCheckPaperAnswers !== 'function') {
console.error('examingCheckPaperAnswers API不存在'); console.error('examingCheckPaperAnswers API不存在')
this.$message.warning('判卷功能暂不可用'); this.$message.warning('判卷功能暂不可用')
this.isChecking = false; this.isChecking = false
return; return
} }
const result = await window.electronAPI.examingCheckPaperAnswers({ paperId }) const result = await window.electronAPI.examingCheckPaperAnswers({ paperId })
@ -280,26 +257,26 @@ export default {
// PDF // PDF
try { try {
// //
const paperDataStr = JSON.stringify(this.paper); const paperDataStr = JSON.stringify(this.paperData)
// PDF // PDF
const pdfResult = await window.electronAPI.fileGeneratePaperPdf(paperDataStr); const pdfResult = await window.electronAPI.fileGeneratePaperPdf(paperDataStr)
if (pdfResult && pdfResult.filePath) { if (pdfResult && pdfResult.filePath) {
this.pdfPath = pdfResult; this.pdfPath = pdfResult
console.log('PDF生成成功保存路径:', pdfResult.filePath); console.log('PDF生成成功保存路径:', pdfResult.filePath)
// this.$message.success('PDF'); // this.$message.success('PDF');
} else { } else {
console.error('PDF生成失败:', pdfResult); console.error('PDF生成失败:', pdfResult)
} }
} catch (pdfError) { } catch (pdfError) {
console.error('PDF生成过程异常:', pdfError); console.error('PDF生成过程异常:', pdfError)
} }
} else { } else {
console.error('判卷失败:', result?.message || '未知错误'); console.error('判卷失败:', result?.message || '未知错误')
this.$message.error('判卷失败,请稍后重试'); this.$message.error('判卷失败,请稍后重试')
} }
} catch (error) { } catch (error) {
console.error('判卷过程异常:', error); console.error('判卷过程异常:', error)
this.$message.error('判卷过程中发生异常'); this.$message.error('判卷过程中发生异常')
} finally { } finally {
this.isChecking = false this.isChecking = false
} }
@ -328,36 +305,54 @@ export default {
// //
getFormattedStartTime () { getFormattedStartTime () {
if (!this.paper || !this.paper.paper_start_time) return '未知'; if (!this.paper || !this.paper.paper_start_time) return '未知'
return this.formatDateTime(this.paper.paper_start_time); return this.formatDateTime(this.paper.paper_start_time)
}, },
// //
getFormattedEndTime () { getFormattedEndTime () {
if (!this.paper || !this.paper.paper_end_time) return '未知'; if (!this.paper || !this.paper.paper_end_time) return '未知'
return this.formatDateTime(this.paper.paper_end_time); return this.formatDateTime(this.paper.paper_end_time)
}, },
// //
getDuration () { getDuration () {
if (!this.paper || !this.paper.paper_start_time || !this.paper.paper_end_time) { // paper_duration_seconds
return '未知'; if (this.paper && this.paper.paper_duration_seconds !== undefined && this.paper.paper_duration_seconds !== null) {
const totalSeconds = this.paper.paper_duration_seconds
const hours = Math.floor(totalSeconds / 3600)
const minutes = Math.floor((totalSeconds % 3600) / 60)
const seconds = totalSeconds % 60
if (hours > 0) {
return `${hours}小时${minutes}${seconds}`
} else if (minutes > 0) {
return `${minutes}${seconds}`
} else {
return `${seconds}`
}
} }
const startTime = new Date(this.paper.paper_start_time); // paper_duration_seconds使
const endTime = new Date(this.paper.paper_end_time); if (!this.paper || !this.paper.paper_start_time || !this.paper.paper_end_time) {
const durationMs = endTime - startTime; return '未知'
}
const hours = Math.floor(durationMs / (1000 * 60 * 60)); const startTime = new Date(this.paper.paper_start_time)
const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60)); const endTime = new Date(this.paper.paper_end_time)
const seconds = Math.floor((durationMs % (1000 * 60)) / 1000); 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) { if (hours > 0) {
return `${hours}小时${minutes}${seconds}`; return `${hours}小时${minutes}${seconds}`
} else if (minutes > 0) { } else if (minutes > 0) {
return `${minutes}${seconds}`; return `${minutes}${seconds}`
} else { } else {
return `${seconds}`; return `${seconds}`
} }
}, },

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@
<div class="exam-bottom-bar rounded shadow-sm mt-2"> <div class="exam-bottom-bar rounded shadow-sm mt-2">
<el-button type="primary" size="small" @click="prevQuestion" :disabled="currentQuestion === 1">上一题</el-button> <el-button type="primary" size="small" @click="prevQuestion" :disabled="currentQuestion === 1">上一题</el-button>
<el-button type="primary" size="small" @click="nextQuestion" :disabled="currentQuestion === questionList.length">下一题</el-button> <el-button type="primary" size="small" @click="nextQuestion" :disabled="currentQuestion === questionList.length">下一题</el-button>
<el-button type="danger" size="small" @click="showExamPreview" :disabled="!canSubmit">交卷预览</el-button> <el-button type="danger" size="small" @click="showExamPreview" :disabled="!canSubmitComputed">交卷预览</el-button>
</div> </div>
</div> </div>
@ -70,6 +70,8 @@ export default {
timer: null, timer: null,
processTimer: null, processTimer: null,
paperId: '', paperId: '',
action: '', // action
paperData: null, // paper
blankAnswerTip: '数字请保留2位小数如"100.00"', blankAnswerTip: '数字请保留2位小数如"100.00"',
showPreviewModal: false, // showPreviewModal: false, //
previewQuestionData: [] // previewQuestionData: [] //
@ -80,9 +82,9 @@ export default {
examinee() { examinee() {
return this.$store.state.examinee return this.$store.state.examinee
}, },
// store // store使
paper() { paper() {
return this.$store.state.paper return this.paperData
}, },
// //
currentQuestionData() { currentQuestionData() {
@ -91,6 +93,34 @@ export default {
// //
answeredCount() { answeredCount() {
return this.questionList.filter(item => item.answered === 1).length; return this.questionList.filter(item => item.answered === 1).length;
},
// 使computed
canSubmitComputed() {
// paper
if (!this.paper) {
return false;
}
//
if (!this.paper.paper_minutes_min) {
return true;
}
// countdown
const timeParts = this.countdown.split(':');
if (timeParts.length !== 3) {
return false;
}
const hours = parseInt(timeParts[0], 10);
const minutes = parseInt(timeParts[1], 10);
const seconds = parseInt(timeParts[2], 10);
const remainingSeconds = hours * 3600 + minutes * 60 + seconds;
const paperMinutesSeconds = this.paper.paper_minutes * 60;
const paperMinutesMinSeconds = this.paper.paper_minutes_min * 60;
// + paper_minutes_min <= paper_minutes
return remainingSeconds + paperMinutesMinSeconds <= paperMinutesSeconds;
} }
}, },
created() { created() {
@ -98,6 +128,10 @@ export default {
if (this.$route.params && this.$route.params.paperId) { if (this.$route.params && this.$route.params.paperId) {
this.paperId = this.$route.params.paperId this.paperId = this.$route.params.paperId
} }
// action
if (this.$route.params && this.$route.params.action) {
this.action = this.$route.params.action
}
}, },
mounted() { mounted() {
// //
@ -106,10 +140,13 @@ export default {
return return
} }
// paperId // paperIdaction
if (!this.paperId && this.$route.params && this.$route.params.paperId) { if (!this.paperId && this.$route.params && this.$route.params.paperId) {
this.paperId = this.$route.params.paperId this.paperId = this.$route.params.paperId
} }
if (!this.action && this.$route.params && this.$route.params.action) {
this.action = this.$route.params.action
}
// paperIdlocalStorage // paperIdlocalStorage
if (!this.paperId) { if (!this.paperId) {
@ -138,37 +175,70 @@ export default {
clearInterval(this.processTimer) clearInterval(this.processTimer)
this.processTimer = null this.processTimer = null
} }
// //
this.$store.commit('clearPaper') // this.$store.commit('clearPaper')
}, },
methods: { methods: {
// //
async initializeExam() { async initializeExam() {
try { try {
console.log('初始化考试paperId:', this.paperId) console.log('初始化考试paperId:', this.paperId, 'action:', this.action)
// //
this.$store.commit('clearPaper') this.$store.commit('clearPaper')
// 1. // action
await this.loadPaperInfo(this.paperId) if (this.action === 'start') {
//
// 2. const startResult = await window.electronAPI.examingStartPaper({ paperId: this.paperId })
await this.loadQuestionList(this.paperId) console.log('开始考试结果:', startResult);
if (startResult && startResult.success && startResult.data) {
// 3. this.paperData = startResult.data
if (this.paper && this.paper.paper_minutes) { } else {
this.initializeTimer(this.paper.paper_minutes * 60) throw new Error('开始考试失败')
}
} else if (this.action === 'continue') {
//
const continueResult = await window.electronAPI.examingContinuePaper({ paperId: this.paperId })
console.log('继续考试结果:', continueResult);
if (continueResult && continueResult.success && continueResult.data) {
this.paperData = continueResult.data
} else {
throw new Error('继续考试失败')
}
} else { } else {
this.initializeTimer(120 * 60) // 120 //
await this.loadPaperInfo(this.paperId)
} }
// 4. // paperData
if (!this.paperData) {
throw new Error('未能获取到试卷数据,请重试')
}
//
await this.loadQuestionList(this.paperId)
// - paper_duration_seconds
let totalSeconds = 0
if (this.paperData && this.paperData.paper_minutes) {
//
const paperTotalSeconds = this.paperData.paper_minutes * 60
// 使paper_duration_seconds
// 0使
const dbDurationSeconds = this.paperData.paper_duration_seconds || 0
totalSeconds = Math.max(0, paperTotalSeconds - dbDurationSeconds)
console.log(`使用数据库中的paper_duration_seconds计算剩余时间 - 总时长: ${paperTotalSeconds}秒, 已用时: ${dbDurationSeconds}秒, 剩余时间: ${totalSeconds}`)
}
this.initializeTimer(totalSeconds)
// - paper_duration_seconds
this.startProcessTimer(this.paperId) this.startProcessTimer(this.paperId)
this.isReady = true this.isReady = true
this.canSubmit = this.paper && this.paper.paper_minutes_min ?
false : true //
} catch (error) { } catch (error) {
console.error('初始化考试失败:', error) console.error('初始化考试失败:', error)
this.$message.error(`初始化考试失败: ${error.message || '未知错误'}`) this.$message.error(`初始化考试失败: ${error.message || '未知错误'}`)
@ -197,11 +267,12 @@ export default {
this.$message.warning('考试时间剩余10分钟') this.$message.warning('考试时间剩余10分钟')
} }
// canSubmit
// //
if (this.paper && this.paper.paper_minutes_min && // if (this.paper && this.paper.paper_minutes_min &&
remainingSeconds <= totalSeconds - (this.paper.paper_minutes_min * 60)) { // remainingSeconds + (this.paper.paper_minutes_min * 60) <= totalSeconds) {
this.canSubmit = true // this.canSubmit = true
} // }
} }
}, 1000) }, 1000)
}, },
@ -222,75 +293,80 @@ export default {
this.processTimer = null this.processTimer = null
} }
// 15processPaper // processPaper
this.processTimer = setInterval(async () => { this.processTimer = setInterval(async () => {
try { try {
// { paperId: paperId } //
const result = await window.electronAPI.examingProcessPaper({ paperId: paperId }) let duration_seconds = 0
// paperstore let shouldUpdateDuration = false
if (this.paperData && this.paperData.paper_minutes && this.countdown) {
// countdown
const timeParts = this.countdown.split(':')
if (timeParts.length === 3) {
const hours = parseInt(timeParts[0], 10)
const minutes = parseInt(timeParts[1], 10)
const seconds = parseInt(timeParts[2], 10)
const remainingSeconds = hours * 3600 + minutes * 60 + seconds
//
duration_seconds = this.paperData.paper_minutes * 60 - remainingSeconds
duration_seconds = Math.max(0, duration_seconds)
// duration_seconds
const dbDurationSeconds = this.paperData.paper_duration_seconds || 0
shouldUpdateDuration = duration_seconds > dbDurationSeconds
if (!shouldUpdateDuration) {
console.log(`跳过更新paper_duration_seconds: 计算值(${duration_seconds})不大于数据库值(${dbDurationSeconds})`)
}
}
}
// - duration_seconds
const params = { paperId: paperId }
if (shouldUpdateDuration) {
params.duration_seconds = duration_seconds
}
const result = await window.electronAPI.examingProcessPaper(params)
// store
if (result.success && result.data) { if (result.success && result.data) {
this.$store.commit('setPaper', result.data) // paper_duration_seconds
const newData = result.data
const currentDuration = this.paperData.paper_duration_seconds || 0
const newDuration = newData.paper_duration_seconds || 0
// duration_seconds
if (newDuration < currentDuration) {
newData.paper_duration_seconds = currentDuration
}
this.paperData = newData
} }
} catch (error) { } catch (error) {
console.error('自动更新试卷状态失败:', error) console.error('自动更新试卷状态失败:', error)
// //
} }
}, 15000) // 15 = 15000 }, 5000) // 5 = 5000
}, },
// // -
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) {
// 2. 0startPaper //
if (result.data.paper_status === 0) { this.paperData = result.data
console.log('试卷状态为未开始调用startPaper开始考试')
const startResult = await window.electronAPI.examingStartPaper({ paperId: paperId })
console.log('开始考试结果:', startResult);
if (startResult && startResult.success && startResult.data) {
// 使
this.$store.commit('setPaper', startResult.data)
this.canSubmit = startResult.data.paper_minutes_min ? false : true
} else {
console.error('开始考试失败')
this.$store.commit('setPaper', result.data)
this.canSubmit = result.data.paper_minutes_min ? false : true
}
} else {
// 使
this.$store.commit('setPaper', result.data)
this.canSubmit = result.data.paper_minutes_min ? false : true
}
} else {
console.error('加载试卷信息失败: 返回数据无效')
//
this.$store.commit('setPaper', {
id: paperId,
paper_score: 0,
paper_minutes: 0,
paper_minutes_min: 0
})
this.canSubmit = false
} }
} catch (error) { } catch (error) {
console.error('加载试卷信息失败:', error) console.error('加载试卷信息失败:', error)
// this.$message.error(`加载试卷信息失败: ${error.message || '未知错误'}`)
this.$store.commit('setPaper', {
id: paperId,
paper_score: 0,
paper_minutes: 0,
paper_minutes_min: 0
})
this.canSubmit = false
} }
}, },
@ -624,10 +700,10 @@ export default {
// - store // - store
async submitExam() { async submitExam() {
try { try {
if (!this.canSubmit) { if (!this.canSubmitComputed) {
this.$message.warning('您的答案尚未保存,请等待保存完成后再提交'); this.$message.warning('您的答案尚未保存,请等待保存完成后再提交');
return; return;
} }
// //
const confirmResult = await this.$confirm('确定要提交试卷吗?提交后将无法继续答题。', '确认提交', { const confirmResult = await this.$confirm('确定要提交试卷吗?提交后将无法继续答题。', '确认提交', {
@ -639,8 +715,29 @@ export default {
// //
await this.saveCurrentAnswer(); await this.saveCurrentAnswer();
// API // 使start_timecurrentTimeduration_seconds
const result = await window.electronAPI.examingSubmitPaper({ paperId: this.paper.id }); // 使paper_minutes
let durationSeconds = 0
if (this.paperData && this.paperData.paper_minutes && this.countdown) {
// countdown
const timeParts = this.countdown.split(':')
if (timeParts.length === 3) {
const hours = parseInt(timeParts[0], 10)
const minutes = parseInt(timeParts[1], 10)
const seconds = parseInt(timeParts[2], 10)
const remainingSeconds = hours * 3600 + minutes * 60 + seconds
//
durationSeconds = this.paperData.paper_minutes * 60 - remainingSeconds
durationSeconds = Math.max(0, durationSeconds)
}
}
// API
const result = await window.electronAPI.examingSubmitPaper({
paperId: this.paperData.id,
duration_seconds: durationSeconds
});
console.log('提交试卷API返回:', result); console.log('提交试卷API返回:', result);
if (result && result.success) { if (result && result.success) {
@ -649,12 +746,12 @@ export default {
try { try {
// //
const paperData = JSON.parse(paperDataStr); // const paperData = JSON.parse(paperDataStr);
// 1. store // 1. store
this.$store.commit('setPaper', paperData); // this.$store.commit('setPaper', paperData);
// 2. localStorage // 2. localStorage
localStorage.setItem('lastExamPaper', paperDataStr); localStorage.setItem('lastExamPaper', paperDataStr);
// PDFEndView.vue // PDFEndView.vue
@ -664,7 +761,7 @@ export default {
setTimeout(() => { setTimeout(() => {
this.$router.push({ this.$router.push({
name: 'End', name: 'End',
params: { paperId: this.paper.id } params: { paperId: this.paperData.id }
}); });
}, 300); // 300ms }, 300); // 300ms
} catch (jsonError) { } catch (jsonError) {
@ -672,13 +769,14 @@ export default {
this.$message.error('处理试卷数据失败'); this.$message.error('处理试卷数据失败');
// 使 // 使
this.$store.commit('setPaper', { id: this.paper.id, rawData: result.data }); // store
localStorage.setItem('lastExamPaper', paperDataStr); // this.$store.commit('setPaper', { id: this.paperData.id, rawData: result.data });
localStorage.setItem('lastExamPaper', paperDataStr);
setTimeout(() => { setTimeout(() => {
this.$router.push({ this.$router.push({
name: 'End', name: 'End',
params: { paperId: this.paper.id } params: { paperId: this.paperData.id }
}); });
}, 300); }, 300);
} }
@ -702,13 +800,13 @@ export default {
await this.saveCurrentAnswer() await this.saveCurrentAnswer()
// 使high_version // 使high_version
const result = await window.electronAPI.endPaper(this.paper.id) const result = await window.electronAPI.examingEndPaper(this.paper.id)
if (result && result.success) { if (result && result.success) {
// paperstore // paperstore
if (result.data) { // if (result.data) {
this.$store.commit('setPaper', result.data) // this.$store.commit('setPaper', result.data)
} // }
// //
if (this.processTimer) { if (this.processTimer) {
clearInterval(this.processTimer) clearInterval(this.processTimer)