继续考试功能已实现

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} duration_seconds - 答题时长
* @returns {Promise<Object>} - 包含操作结果和试卷数据的对象
*/
async function submitPaper(paperId) {
async function submitPaper(paperId, duration_seconds = 0) {
try {
const userDb = await getDbConnection(getUserDbPath());
const currentTime = formatDateTime(new Date());
@ -783,9 +784,10 @@ async function submitPaper(paperId) {
`UPDATE examinee_papers
SET paper_status = 2,
paper_submit_time = ?,
paper_end_time = ?
paper_end_time = ?,
paper_duration_seconds = ?
WHERE id = ?`,
[currentTime, currentTime, paperId]
[currentTime, currentTime, duration_seconds, paperId]
);
// 查询更新后的试卷数据
@ -819,12 +821,23 @@ async function endPaper(paperId) {
const userDb = await getDbConnection(getUserDbPath());
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(
`UPDATE examinee_papers
SET paper_status = 2,
paper_end_time = ?
paper_end_time = ?,
paper_duration_seconds = ?
WHERE id = ?`,
[currentTime, paperId]
[currentTime, paperDurationSeconds, paperId]
);
// 查询更新后的试卷数据
@ -851,18 +864,20 @@ async function endPaper(paperId) {
/**
* 更新试卷最后操作时间
* @param {number} paperId - 试卷ID
* @param {number} duration_seconds - 答题时长
* @returns {Promise<Object>} - 包含操作结果和试卷数据的对象
*/
async function processPaper(paperId) {
async function processPaper(paperId, duration_seconds) {
try {
const userDb = await getDbConnection(getUserDbPath());
const currentTime = formatDateTime(new Date());
await userDb.runAsync(
`UPDATE examinee_papers
SET paper_last_time = ?
SET paper_last_time = ?,
paper_duration_seconds = ?
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,
batchInsert,
generateExamineePaper,
@ -1209,5 +1411,7 @@ module.exports = {
endPaper,
processPaper,
checkPaperAnswers,
getPaper
getPaper,
getExamineePaper,
continueExam
};

View File

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

View File

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

View File

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

View File

@ -110,8 +110,10 @@ contextBridge.exposeInMainWorld("electronAPI", {
ipcRenderer.invoke("examinee-update", { id, examineeData }),
examineeDelete: (id) => ipcRenderer.invoke("examinee-delete", id),
// 在考生考试服务相关接口部分添加examingCheckPaperAnswers
// 考生考试服务相关接口
// 在"考生考试服务相关接口"部分添加getPaper接口
examingGetPaper: ({ paperId }) =>
ipcRenderer.invoke("examing-get-paper", { paperId }),
// 在考生考试服务相关接口
examingGeneratePaper: ({ examineeId, examId }) =>
ipcRenderer.invoke("examing-generate-paper", { examineeId, examId }),
examingGetPaperStatus: ({ examineeId, examId }) =>
@ -125,21 +127,26 @@ contextBridge.exposeInMainWorld("electronAPI", {
tableName,
relatedId,
}),
// 修复examingUpdateAnswer接口的参数结构
examingUpdateAnswer: ({ tableName, id, answers }) =>
ipcRenderer.invoke("examing-update-answer", { tableName, id, answers }),
// 在考生考试服务相关接口部分确保examingStartPaper正确暴露
examingStartPaper: ({ paperId }) =>
ipcRenderer.invoke("examing-start-paper", { paperId }),
examingSubmitPaper: ({ paperId }) =>
ipcRenderer.invoke("examing-submit-paper", { paperId }),
// 确保examingGetExamResult API正确暴露
examingSubmitPaper: ({ paperId, duration_seconds = 0 }) =>
ipcRenderer.invoke("examing-submit-paper", { paperId, duration_seconds }),
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 }),
examingProcessPaper: ({ paperId, duration_seconds = 0 }) =>
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定义移除重复的

View File

@ -1,4 +1,4 @@
// 保留第一个导入
// 首先在文件顶部的require语句中添加getExamineePaper
const {
generateExamineePaper,
loadPaperSerial,
@ -10,13 +10,12 @@ const {
processPaper,
checkPaperAnswers,
getPaper,
getExamineePaper,
continueExam, // 添加continueExam到导入列表中
} = require("../db/examing.js");
const { getDbConnection, closeAllConnections } = require("../db/index.js");
const { getUserDbPath } = require("../db/path.js");
// 删除下面这行重复的导入
// const { generateExamineePaper, loadPaperSerial, getQuestionByRelatedId, updateExamineeAnswer, startPaper, submitPaper, endPaper, processPaper, checkPaperAnswers, getPaper } = require('../db/examing.js');
/**
* 服务层生成考生试卷
* @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
* @returns {Promise<Object>} - 包含操作结果的对象
*/
async function submitPaperService(paperId) {
async function submitPaperService(paperId, duration_seconds) {
try {
if (!paperId || paperId <= 0) {
throw new Error("试卷ID必须为正数");
}
// 1. 提交试卷
const submitResult = await submitPaper(paperId);
const submitResult = await submitPaper(paperId, duration_seconds);
if (!submitResult.success) {
throw new Error(submitResult.message);
@ -338,16 +359,20 @@ async function endPaperService(paperId) {
/**
* 服务层处理试卷
* @param {number} paperId - 试卷ID
* @param {number} duration_seconds - 答题时长
* @returns {Promise<Object>} - 包含操作结果的对象
*/
async function processPaperService(paperId) {
async function processPaperService(paperId, duration_seconds) {
try {
if (!paperId || paperId <= 0) {
throw new Error("试卷ID必须为正数");
}
const result = await processPaper(paperId);
return result;
// 这样在继续考试时就不会覆盖数据库中已有的paper_duration_seconds值
if (duration_seconds === undefined || duration_seconds === null) {
return await processPaper(paperId);
}
return await processPaper(paperId, duration_seconds);
} catch (error) {
console.error("服务层: 处理试卷失败", error);
return {
@ -380,10 +405,50 @@ async function checkPaperAnswersService(paperId) {
}
/**
* 初始化考试相关的IPC处理程序
* @param {import('electron').IpcMain} ipcMain - IPC主进程实例
* 服务层根据考生ID获取试卷数据
* @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) {
// 生成考生试卷
ipcMain.handle(
@ -490,17 +555,21 @@ function initExamingIpc(ipcMain) {
});
// 提交考试
ipcMain.handle("examing-submit-paper", async (event, { paperId }) => {
try {
return await submitPaperService(paperId);
} catch (error) {
console.error("提交考试失败:", error);
return {
success: false,
message: `提交考试失败: ${error.message}`,
};
// 提交考试
ipcMain.handle(
"examing-submit-paper",
async (event, { paperId, duration_seconds }) => {
try {
return await submitPaperService(paperId, duration_seconds);
} catch (error) {
console.error("提交考试失败:", error);
return {
success: false,
message: `提交考试失败: ${error.message}`,
};
}
}
});
);
// 结束考试
ipcMain.handle("examing-end-paper", async (event, { paperId }) => {
@ -516,17 +585,20 @@ function initExamingIpc(ipcMain) {
});
// 处理试卷
ipcMain.handle("examing-process-paper", async (event, { paperId }) => {
try {
return await processPaperService(paperId);
} catch (error) {
console.error("处理试卷失败:", error);
return {
success: false,
message: `处理试卷失败: ${error.message}`,
};
ipcMain.handle(
"examing-process-paper",
async (event, { paperId, duration_seconds }) => {
try {
return await processPaperService(paperId, duration_seconds);
} catch (error) {
console.error("处理试卷失败:", error);
return {
success: false,
message: `处理试卷失败: ${error.message}`,
};
}
}
});
);
// 检查试卷答案并计算得分
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格式
@ -568,5 +682,9 @@ module.exports = {
endPaperService,
processPaperService,
checkPaperAnswersService,
getExamResultService,
getExamineePaperService,
continueExamService,
getPaperService,
initExamingIpc,
};

View File

@ -395,10 +395,17 @@ async function generatePaperPdf(jsonString) {
const endTime = paperData.paper_end_time;
// 截取考试日期 (假设格式为 'YYYY-MM-DD HH:mm:ss')
const examDate = startTime.split(' ')[0];
// 计算用时(分钟)
const start = new Date(startTime.replace(/-/g, '/'));
const end = new Date(endTime.replace(/-/g, '/'));
const durationMinutes = Math.round((end - start) / (1000 * 60));
// 计算用时(分钟)- 直接使用paper_duration_seconds字段
let durationMinutes = 0;
if (paperData.paper_duration_seconds && paperData.paper_duration_seconds > 0) {
// 直接从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题的数量之和

View File

@ -106,7 +106,8 @@ export default {
return {
size: 'default',
pdfPath: '',
isChecking: false
isChecking: false,
paperData: null // paper
}
},
// script
@ -115,22 +116,9 @@ export default {
examinee () {
return this.$store.state.examinee || {}
},
// store
// store使
paper () {
// storelocalStorage
if (!this.$store.state.paper || Object.keys(this.$store.state.paper).length === 0) {
const savedPaper = localStorage.getItem('lastExamPaper');
if (savedPaper) {
try {
// localStoragestore
return JSON.parse(savedPaper);
} catch (e) {
console.error('解析localStorage试卷数据失败:', e);
}
}
return {};
}
return this.$store.state.paper || {};
return this.paperData || {}
},
//
iconStyle () {
@ -164,112 +152,101 @@ export default {
//
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;
// 1. paperId
const paperIdFromRoute = this.$route.params.paperId
if (!paperIdFromRoute) {
console.error('路由参数中没有找到paperId');
this.$message.error('无法获取试卷信息,请重新提交试卷');
return;
console.error('路由参数中没有找到paperId')
this.$message.error('无法获取试卷信息,请重新提交试卷')
return
}
// 3. localStorage
const savedPaperData = localStorage.getItem('lastExamPaper');
// 2. localStorage
const savedPaperData = localStorage.getItem('lastExamPaper')
if (savedPaperData) {
try {
const paperData = JSON.parse(savedPaperData);
const paperData = JSON.parse(savedPaperData)
// paperId
if (paperData.id && paperData.id.toString() === paperIdFromRoute.toString()) {
console.log('从localStorage恢复到store的试卷数据:', paperData);
// storethis.paper访
this.$store.commit('setPaper', paperData);
console.log('从localStorage恢复的试卷数据:', paperData)
// store
this.paperData = paperData
//
if (!paperData.is_checked) {
this.checkAnswers(paperData.id);
this.checkAnswers(paperData.id)
}
return;
return
}
} catch (e) {
console.error('解析localStorage中的试卷数据失败:', e);
console.error('解析localStorage中的试卷数据失败:', e)
}
}
// 4. API
console.log('调用API加载试卷数据paperId:', paperIdFromRoute);
this.loadExamResult(paperIdFromRoute);
// 3. API
console.log('调用API加载试卷数据paperId:', paperIdFromRoute)
this.loadExamResult(paperIdFromRoute)
},
// - store
async loadExamResult (paperIdFromParams) {
try {
const paperId = paperIdFromParams || this.$route.params.paperId;
const paperId = paperIdFromParams || this.$route.params.paperId
if (!paperId) {
console.error('没有找到试卷ID');
this.$message.error('无法获取试卷信息,请重新提交试卷');
return;
console.error('没有找到试卷ID')
this.$message.error('无法获取试卷信息,请重新提交试卷')
return
}
// API
if (!window.electronAPI || typeof window.electronAPI.examingGetExamResult !== 'function') {
console.error('examingGetExamResult API不存在');
this.$message.warning('获取考试结果功能暂不可用');
return;
console.error('examingGetExamResult API不存在')
this.$message.warning('获取考试结果功能暂不可用')
return
}
const result = await window.electronAPI.examingGetExamResult({ paperId });
const result = await window.electronAPI.examingGetExamResult({ paperId })
if (result && result.success && result.data) {
// store
this.$store.commit('setPaper', result.data);
// store
this.paperData = result.data
// 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) {
this.checkAnswers(result.data.id || paperId);
this.checkAnswers(result.data.id || paperId)
}
} else {
console.error('获取考试结果失败:', result?.message || '未知错误');
this.$message.error('获取考试结果失败');
console.error('获取考试结果失败:', result?.message || '未知错误')
this.$message.error('获取考试结果失败')
}
} catch (error) {
console.error('加载考试结果失败:', error);
this.$message.error('加载考试结果失败');
console.error('加载考试结果失败:', error)
this.$message.error('加载考试结果失败')
}
},
//
//
async checkAnswers () {
try {
if (!this.paper || !this.paper.id) {
console.error('没有试卷ID无法判卷');
this.$message.warning('缺少试卷信息,无法判卷');
return;
if (!this.paperData || !this.paperData.id) {
console.error('没有试卷ID无法判卷')
this.$message.warning('缺少试卷信息,无法判卷')
return
}
this.isChecking = true
const paperId = this.paper.id
const paperId = this.paperData.id
// API
if (!window.electronAPI || typeof window.electronAPI.examingCheckPaperAnswers !== 'function') {
console.error('examingCheckPaperAnswers API不存在');
this.$message.warning('判卷功能暂不可用');
this.isChecking = false;
return;
console.error('examingCheckPaperAnswers API不存在')
this.$message.warning('判卷功能暂不可用')
this.isChecking = false
return
}
const result = await window.electronAPI.examingCheckPaperAnswers({ paperId })
@ -280,26 +257,26 @@ export default {
// PDF
try {
//
const paperDataStr = JSON.stringify(this.paper);
const paperDataStr = JSON.stringify(this.paperData)
// PDF
const pdfResult = await window.electronAPI.fileGeneratePaperPdf(paperDataStr);
const pdfResult = await window.electronAPI.fileGeneratePaperPdf(paperDataStr)
if (pdfResult && pdfResult.filePath) {
this.pdfPath = pdfResult;
console.log('PDF生成成功保存路径:', pdfResult.filePath);
this.pdfPath = pdfResult
console.log('PDF生成成功保存路径:', pdfResult.filePath)
// this.$message.success('PDF');
} else {
console.error('PDF生成失败:', pdfResult);
console.error('PDF生成失败:', pdfResult)
}
} catch (pdfError) {
console.error('PDF生成过程异常:', pdfError);
console.error('PDF生成过程异常:', pdfError)
}
} else {
console.error('判卷失败:', result?.message || '未知错误');
this.$message.error('判卷失败,请稍后重试');
console.error('判卷失败:', result?.message || '未知错误')
this.$message.error('判卷失败,请稍后重试')
}
} catch (error) {
console.error('判卷过程异常:', error);
this.$message.error('判卷过程中发生异常');
console.error('判卷过程异常:', error)
this.$message.error('判卷过程中发生异常')
} finally {
this.isChecking = false
}
@ -328,36 +305,54 @@ export default {
//
getFormattedStartTime () {
if (!this.paper || !this.paper.paper_start_time) return '未知';
return this.formatDateTime(this.paper.paper_start_time);
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);
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 '未知';
// paper_duration_seconds
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);
const endTime = new Date(this.paper.paper_end_time);
const durationMs = endTime - startTime;
// paper_duration_seconds使
if (!this.paper || !this.paper.paper_start_time || !this.paper.paper_end_time) {
return '未知'
}
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);
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}`;
return `${hours}小时${minutes}${seconds}`
} else if (minutes > 0) {
return `${minutes}${seconds}`;
return `${minutes}${seconds}`
} 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">
<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="danger" size="small" @click="showExamPreview" :disabled="!canSubmit">交卷预览</el-button>
<el-button type="danger" size="small" @click="showExamPreview" :disabled="!canSubmitComputed">交卷预览</el-button>
</div>
</div>
@ -70,6 +70,8 @@ export default {
timer: null,
processTimer: null,
paperId: '',
action: '', // action
paperData: null, // paper
blankAnswerTip: '数字请保留2位小数如"100.00"',
showPreviewModal: false, //
previewQuestionData: [] //
@ -80,9 +82,9 @@ export default {
examinee() {
return this.$store.state.examinee
},
// store
// store使
paper() {
return this.$store.state.paper
return this.paperData
},
//
currentQuestionData() {
@ -91,6 +93,34 @@ export default {
//
answeredCount() {
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() {
@ -98,6 +128,10 @@ export default {
if (this.$route.params && 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() {
//
@ -106,10 +140,13 @@ export default {
return
}
// paperId
// paperIdaction
if (!this.paperId && this.$route.params && 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
if (!this.paperId) {
@ -138,37 +175,70 @@ export default {
clearInterval(this.processTimer)
this.processTimer = null
}
//
this.$store.commit('clearPaper')
//
// this.$store.commit('clearPaper')
},
methods: {
//
async initializeExam() {
try {
console.log('初始化考试paperId:', this.paperId)
console.log('初始化考试paperId:', this.paperId, 'action:', this.action)
//
this.$store.commit('clearPaper')
// 1.
await this.loadPaperInfo(this.paperId)
// 2.
await this.loadQuestionList(this.paperId)
// 3.
if (this.paper && this.paper.paper_minutes) {
this.initializeTimer(this.paper.paper_minutes * 60)
// action
if (this.action === 'start') {
//
const startResult = await window.electronAPI.examingStartPaper({ paperId: this.paperId })
console.log('开始考试结果:', startResult);
if (startResult && startResult.success && startResult.data) {
this.paperData = startResult.data
} else {
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 {
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.isReady = true
this.canSubmit = this.paper && this.paper.paper_minutes_min ?
false : true //
} catch (error) {
console.error('初始化考试失败:', error)
this.$message.error(`初始化考试失败: ${error.message || '未知错误'}`)
@ -197,11 +267,12 @@ export default {
this.$message.warning('考试时间剩余10分钟')
}
// canSubmit
//
if (this.paper && this.paper.paper_minutes_min &&
remainingSeconds <= totalSeconds - (this.paper.paper_minutes_min * 60)) {
this.canSubmit = true
}
// if (this.paper && this.paper.paper_minutes_min &&
// remainingSeconds + (this.paper.paper_minutes_min * 60) <= totalSeconds) {
// this.canSubmit = true
// }
}
}, 1000)
},
@ -222,75 +293,80 @@ export default {
this.processTimer = null
}
// 15processPaper
// processPaper
this.processTimer = setInterval(async () => {
try {
// { paperId: paperId }
const result = await window.electronAPI.examingProcessPaper({ paperId: paperId })
// paperstore
//
let duration_seconds = 0
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) {
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) {
console.error('自动更新试卷状态失败:', error)
//
}
}, 15000) // 15 = 15000
}, 5000) // 5 = 5000
},
//
// -
async loadPaperInfo(paperId) {
try {
console.log('开始加载试卷信息paperId:', paperId)
//
this.$store.commit('clearPaper')
// 1.
//
const result = await window.electronAPI.examingProcessPaper({ paperId: paperId })
console.log('loadPaperInfo结果:', result)
if (result && result.success && result.data) {
// 2. 0startPaper
if (result.data.paper_status === 0) {
console.log('试卷状态为未开始调用startPaper开始考试')
const startResult = await window.electronAPI.examingStartPaper({ paperId: paperId })
console.log('开始考试结果:', startResult);
if (startResult && startResult.success && startResult.data) {
// 使
this.$store.commit('setPaper', startResult.data)
this.canSubmit = startResult.data.paper_minutes_min ? false : true
} else {
console.error('开始考试失败')
this.$store.commit('setPaper', result.data)
this.canSubmit = result.data.paper_minutes_min ? false : true
}
} else {
// 使
this.$store.commit('setPaper', result.data)
this.canSubmit = result.data.paper_minutes_min ? false : true
}
} else {
console.error('加载试卷信息失败: 返回数据无效')
//
this.$store.commit('setPaper', {
id: paperId,
paper_score: 0,
paper_minutes: 0,
paper_minutes_min: 0
})
this.canSubmit = false
//
this.paperData = result.data
}
} catch (error) {
console.error('加载试卷信息失败:', error)
//
this.$store.commit('setPaper', {
id: paperId,
paper_score: 0,
paper_minutes: 0,
paper_minutes_min: 0
})
this.canSubmit = false
this.$message.error(`加载试卷信息失败: ${error.message || '未知错误'}`)
}
},
@ -624,10 +700,10 @@ export default {
// - store
async submitExam() {
try {
if (!this.canSubmit) {
this.$message.warning('您的答案尚未保存,请等待保存完成后再提交');
return;
}
if (!this.canSubmitComputed) {
this.$message.warning('您的答案尚未保存,请等待保存完成后再提交');
return;
}
//
const confirmResult = await this.$confirm('确定要提交试卷吗?提交后将无法继续答题。', '确认提交', {
@ -639,8 +715,29 @@ export default {
//
await this.saveCurrentAnswer();
// API
const result = await window.electronAPI.examingSubmitPaper({ paperId: this.paper.id });
// 使start_timecurrentTimeduration_seconds
// 使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);
if (result && result.success) {
@ -649,12 +746,12 @@ export default {
try {
//
const paperData = JSON.parse(paperDataStr);
// 1. store
this.$store.commit('setPaper', paperData);
// const paperData = JSON.parse(paperDataStr);
// 1. store
// this.$store.commit('setPaper', paperData);
// 2. localStorage
localStorage.setItem('lastExamPaper', paperDataStr);
// 2. localStorage
localStorage.setItem('lastExamPaper', paperDataStr);
// PDFEndView.vue
@ -664,7 +761,7 @@ export default {
setTimeout(() => {
this.$router.push({
name: 'End',
params: { paperId: this.paper.id }
params: { paperId: this.paperData.id }
});
}, 300); // 300ms
} catch (jsonError) {
@ -672,13 +769,14 @@ export default {
this.$message.error('处理试卷数据失败');
// 使
this.$store.commit('setPaper', { id: this.paper.id, rawData: result.data });
localStorage.setItem('lastExamPaper', paperDataStr);
// store
// this.$store.commit('setPaper', { id: this.paperData.id, rawData: result.data });
localStorage.setItem('lastExamPaper', paperDataStr);
setTimeout(() => {
this.$router.push({
name: 'End',
params: { paperId: this.paper.id }
params: { paperId: this.paperData.id }
});
}, 300);
}
@ -702,13 +800,13 @@ export default {
await this.saveCurrentAnswer()
// 使high_version
const result = await window.electronAPI.endPaper(this.paper.id)
const result = await window.electronAPI.examingEndPaper(this.paper.id)
if (result && result.success) {
// paperstore
if (result.data) {
this.$store.commit('setPaper', result.data)
}
// if (result.data) {
// this.$store.commit('setPaper', result.data)
// }
//
if (this.processTimer) {
clearInterval(this.processTimer)