Compare commits

...

3 Commits

Author SHA1 Message Date
chenqiang
1da167638a 实现考生登录后,开始考试前的自动组卷功能 2025-08-10 18:34:54 +08:00
chenqiang
627ca9bc75 创建ExamingView考试页模板 2025-08-10 08:20:02 +08:00
chenqiang
fc110beee0 重新实现数据库初始化,改变user.db库中的表结构 2025-08-09 19:50:03 +08:00
12 changed files with 1271 additions and 200 deletions

View File

217
electron/db/examing.js Normal file
View File

@ -0,0 +1,217 @@
// 导入必要的模块和函数
import { getSystemDbPath, getUserDbPath } from './path.js';
import { getDbConnection } from './index.js';
import { batchInsert } from './utils.js';
/**
* 生成考生试卷
* @param {Object} examineeData - 考生数据
* @param {number} examDuration - 考试时长(分钟)
* @returns {Promise<Object>} - 包含试卷ID和状态的对象
*/
// 变更函数入参从examDuration改为examData
export async function generateExamineePaper(examineeData, examData) {
try {
// 1. 获取数据库连接
const systemDb = await getDbConnection(getSystemDbPath());
const userDb = await getDbConnection(getUserDbPath());
// 2. 处理考生数据,将空值转换为空字符串
console.log('开始处理考生数据...');
const processedExamineeData = {
id: examineeData.id,
examinee_name: examineeData.examinee_name || '',
examinee_gender: examineeData.examinee_gender || '',
examinee_unit: examineeData.examinee_unit || '',
written_exam_room: examineeData.written_exam_room || '',
written_exam_seat: examineeData.written_exam_seat || '',
computer_exam_room: examineeData.computer_exam_room || '',
computer_exam_seat: examineeData.computer_exam_seat || '',
examinee_id_card: examineeData.examinee_id_card || '',
examinee_admission_ticket: examineeData.examinee_admission_ticket || ''
};
// 3. 在user库中添加考生数据
console.log('开始添加考生数据...');
const examineeResult = await userDb.runAsync(
`INSERT INTO examinee (
id, examinee_name, examinee_gender, examinee_unit,
written_exam_room, written_exam_seat, computer_exam_room,
computer_exam_seat, examinee_id_card, examinee_admission_ticket, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
[
processedExamineeData.id,
processedExamineeData.examinee_name,
processedExamineeData.examinee_gender,
processedExamineeData.examinee_unit,
processedExamineeData.written_exam_room,
processedExamineeData.written_exam_seat,
processedExamineeData.computer_exam_room,
processedExamineeData.computer_exam_seat,
processedExamineeData.examinee_id_card,
processedExamineeData.examinee_admission_ticket
]
);
const examineeId = examineeResult.lastID || processedExamineeData.id;
console.log(`成功添加考生数据ID: ${examineeId}`);
// 3. 生成examinee_papers记录
console.log('开始生成试卷记录...');
// 使用examData中的exam_minutes和exam_minutes_min
const paperResult = await userDb.runAsync(
`INSERT INTO examinee_papers (
examinee_id, paper_minutes, paper_minuts_min, paper_status
) VALUES (?, ?, ?, ?)`,
[examineeId, examData.exam_minutes, examData.exam_minutes_min, 0] // 0表示未开始
);
const paperId = paperResult.lastID;
console.log(`成功生成试卷记录ID: ${paperId}`);
// 4. 从system库获取所有questions记录
console.log('开始获取系统题库...');
const systemQuestions = await systemDb.allAsync('SELECT * FROM questions');
console.log(`成功获取 ${systemQuestions.length} 道题目`);
// 打乱题目顺序
const shuffledQuestions = [...systemQuestions].sort(() => Math.random() - 0.5);
// 5. 准备paper_questions数据
console.log('开始准备试卷题目数据...');
const paperQuestionsData = shuffledQuestions.map(question => ({
examinee_id: examineeId,
paper_id: paperId,
question_type: question.question_type,
question_name: question.question_name,
question_description: question.question_description,
created_at: new Date().toISOString()
}));
// 创建question_id映射关系system_id -> user_id
const questionIdMap = new Map();
// 6. 插入paper_questions数据并记录映射关系
console.log('开始插入试卷题目数据...');
for (let i = 0; i < paperQuestionsData.length; i++) {
const result = await userDb.runAsync(
`INSERT INTO paper_questions (
examinee_id, paper_id, question_type,
question_name, question_description, created_at
) VALUES (?, ?, ?, ?, ?, ?)`,
[
paperQuestionsData[i].examinee_id,
paperQuestionsData[i].paper_id,
paperQuestionsData[i].question_type,
paperQuestionsData[i].question_name,
paperQuestionsData[i].question_description,
paperQuestionsData[i].created_at
]
);
questionIdMap.set(systemQuestions[i].id, result.lastID);
}
console.log(`成功插入 ${paperQuestionsData.length} 条试卷题目数据`);
// 7. 处理question_datasets表
console.log('开始处理数据集...');
const systemDatasets = await systemDb.allAsync('SELECT * FROM question_datasets');
const userDatasets = systemDatasets
.filter(dataset => questionIdMap.has(dataset.question_id))
.map(dataset => ({
question_id: questionIdMap.get(dataset.question_id),
dataset_name: dataset.dataset_name,
dataset_data: dataset.dataset_data,
created_at: new Date().toISOString()
}));
if (userDatasets.length > 0) {
await batchInsert(userDb, 'question_datasets', userDatasets);
}
console.log(`成功处理 ${userDatasets.length} 个数据集`);
// 8. 处理question_images表
console.log('开始处理图片...');
const systemImages = await systemDb.allAsync('SELECT * FROM question_images');
const userImages = systemImages
.filter(image => questionIdMap.has(image.question_id))
.map(image => ({
question_id: questionIdMap.get(image.question_id),
image_name: image.image_name,
image_base64: image.image_base64,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}));
if (userImages.length > 0) {
await batchInsert(userDb, 'question_images', userImages);
}
console.log(`成功处理 ${userImages.length} 张图片`);
// 9. 处理question_choices表
console.log('开始处理选择题...');
const systemChoices = await systemDb.allAsync('SELECT * FROM question_choices');
let userChoices = systemChoices
.filter(choice => questionIdMap.has(choice.question_id))
.map(choice => {
// 打乱选项顺序
const options = JSON.parse(choice.choice_options);
const shuffledOptions = [...options].sort(() => Math.random() - 0.5);
return {
question_id: questionIdMap.get(choice.question_id),
choice_description: choice.choice_description,
choice_type: choice.choice_type,
choice_options: JSON.stringify(shuffledOptions),
correct_answers: choice.correct_answers,
examinee_answers: '',
score: choice.score,
score_real: 0,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
});
// 打乱选择题顺序
userChoices = userChoices.sort(() => Math.random() - 0.5);
if (userChoices.length > 0) {
await batchInsert(userDb, 'question_choices', userChoices);
}
console.log(`成功处理 ${userChoices.length} 道选择题`);
// 10. 处理question_fill_blanks表
console.log('开始处理填空题...');
const systemFillBlanks = await systemDb.allAsync('SELECT * FROM question_fill_blanks');
let userFillBlanks = systemFillBlanks
.filter(blank => questionIdMap.has(blank.question_id))
.map(blank => ({
question_id: questionIdMap.get(blank.question_id),
blank_description: blank.blank_description,
blank_count: blank.blank_count,
correct_answers: blank.correct_answers,
examinee_answers: '',
score: blank.score,
score_real: 0,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}));
// 打乱填空题顺序
userFillBlanks = userFillBlanks.sort(() => Math.random() - 0.5);
if (userFillBlanks.length > 0) {
await batchInsert(userDb, 'question_fill_blanks', userFillBlanks);
}
console.log(`成功处理 ${userFillBlanks.length} 道填空题`);
// 返回成功结果
return {
success: true,
paperId,
examineeId,
message: '试卷生成成功'
};
} catch (error) {
console.error('生成试卷失败:', error);
return {
success: false,
message: `生成试卷失败: ${error.message}`
};
}
}

View File

@ -20,7 +20,7 @@ export async function getDbConnection(dbPath) {
}
// 关闭所有数据库连接
function closeAllConnections() {
export function closeAllConnections() {
dbConnections.forEach((db, path) => {
try {
db.close();
@ -57,57 +57,157 @@ async function initializeSystemDatabase() {
const systemDbPath = getSystemDbPath();
const systemDb = await getDbConnection(systemDbPath);
try {
// 开始事务
await systemDb.runAsync('BEGIN TRANSACTION');
console.log('开始事务');
// 记录成功和失败的操作
const results = { success: [], failed: [] };
// 创建表结构
console.log('开始创建系统数据库表结构...');
// 创建表结构
console.log('开始创建系统数据库表结构...');
// 创建config表
try {
await systemDb.execAsync(systemSchema.config.trim());
console.log('创建 config 表成功');
results.success.push('创建 config 表');
} catch (error) {
console.error('创建 config 表失败:', error);
results.failed.push({ operation: '创建 config 表', error: error.message });
}
// 创建dict_types表
try {
await systemDb.execAsync(systemSchema.dictTypes.trim());
console.log('创建 dict_types 表成功');
results.success.push('创建 dict_types 表');
} catch (error) {
console.error('创建 dict_types 表失败:', error);
results.failed.push({ operation: '创建 dict_types 表', error: error.message });
}
// 创建dict_items表
try {
await systemDb.execAsync(systemSchema.dictItems.trim());
console.log('创建 dict_items 表成功');
results.success.push('创建 dict_items 表');
} catch (error) {
console.error('创建 dict_items 表失败:', error);
results.failed.push({ operation: '创建 dict_items 表', error: error.message });
}
// 创建questions表
try {
await systemDb.execAsync(systemSchema.questions.trim());
console.log('创建 questions 表成功');
results.success.push('创建 questions 表');
} catch (error) {
console.error('创建 questions 表失败:', error);
results.failed.push({ operation: '创建 questions 表', error: error.message });
}
// 创建question_datasets表
try {
await systemDb.execAsync(systemSchema.questionDatasets.trim());
console.log('创建 question_datasets 表成功');
results.success.push('创建 question_datasets 表');
} catch (error) {
console.error('创建 question_datasets 表失败:', error);
results.failed.push({ operation: '创建 question_datasets 表', error: error.message });
}
// 创建question_images表
try {
await systemDb.execAsync(systemSchema.questionImages.trim());
console.log('创建 question_images 表成功');
results.success.push('创建 question_images 表');
} catch (error) {
console.error('创建 question_images 表失败:', error);
results.failed.push({ operation: '创建 question_images 表', error: error.message });
}
// 创建question_fill_table表
try {
await systemDb.execAsync(systemSchema.questionFillTable.trim());
console.log('创建 question_fill_table 表成功');
results.success.push('创建 question_fill_table 表');
} catch (error) {
console.error('创建 question_fill_table 表失败:', error);
results.failed.push({ operation: '创建 question_fill_table 表', error: error.message });
}
// 创建question_fill_table_blanks表
try {
await systemDb.execAsync(systemSchema.questionFillTableBlanks.trim());
console.log('创建 question_fill_table_blanks 表成功');
results.success.push('创建 question_fill_table_blanks 表');
} catch (error) {
console.error('创建 question_fill_table_blanks 表失败:', error);
results.failed.push({ operation: '创建 question_fill_table_blanks 表', error: error.message });
}
// 创建question_choices表
try {
await systemDb.execAsync(systemSchema.questionChoices.trim());
console.log('创建 question_choices 表成功');
results.success.push('创建 question_choices 表');
} catch (error) {
console.error('创建 question_choices 表失败:', error);
results.failed.push({ operation: '创建 question_choices 表', error: error.message });
}
// 创建question_fill_blanks表
try {
await systemDb.execAsync(systemSchema.questionFillBlanks.trim());
console.log('创建 question_fill_blanks 表成功');
results.success.push('创建 question_fill_blanks 表');
} catch (error) {
console.error('创建 question_fill_blanks 表失败:', error);
results.failed.push({ operation: '创建 question_fill_blanks 表', error: error.message });
}
// 创建question_judge表
try {
await systemDb.execAsync(systemSchema.questionJudge.trim());
console.log('创建 question_judge 表成功');
results.success.push('创建 question_judge 表');
} catch (error) {
console.error('创建 question_judge 表失败:', error);
results.failed.push({ operation: '创建 question_judge 表', error: error.message });
}
// 创建question_short表
try {
await systemDb.execAsync(systemSchema.questionShort.trim());
console.log('创建 question_short 表成功');
results.success.push('创建 question_short 表');
} catch (error) {
console.error('创建 question_short 表失败:', error);
results.failed.push({ operation: '创建 question_short 表', error: error.message });
}
// 创建exam表
try {
await systemDb.execAsync(systemSchema.exam.trim());
console.log('创建 exam 表成功');
results.success.push('创建 exam 表');
} catch (error) {
console.error('创建 exam 表失败:', error);
results.failed.push({ operation: '创建 exam 表', error: error.message });
}
// 创建examinee表
try {
await systemDb.execAsync(systemSchema.examinee.trim());
console.log('创建 examinee 表成功');
results.success.push('创建 examinee 表');
} catch (error) {
console.error('创建 examinee 表失败:', error);
results.failed.push({ operation: '创建 examinee 表', error: error.message });
}
// 插入默认数据
console.log('开始插入默认数据...');
// 插入默认数据
console.log('开始插入默认数据...');
// 处理密码哈希
try {
const plainPassword = defaultData.config.find(item => item.key === 'admin_password').value;
const hashedPassword = await argon2.hash(plainPassword);
@ -119,26 +219,54 @@ async function initializeSystemDatabase() {
return item;
});
await batchInsert(systemDb, 'config', configData);
console.log('插入 config 表数据成功');
// 插入config表数据
try {
await batchInsert(systemDb, 'config', configData);
console.log('插入 config 表数据成功');
results.success.push('插入 config 表数据');
} catch (error) {
console.error('插入 config 表数据失败:', error);
results.failed.push({ operation: '插入 config 表数据', error: error.message });
}
} catch (error) {
console.error('处理密码哈希失败:', error);
results.failed.push({ operation: '处理密码哈希', error: error.message });
}
// 插入dict_types表数据
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 表数据成功');
// 提交事务
await systemDb.runAsync('COMMIT');
console.log('提交事务成功');
return true;
results.success.push('插入 dict_items 表数据');
} catch (error) {
// 处理错误
await systemDb.runAsync('ROLLBACK');
console.error('回滚事务:', error);
throw 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;
}
// 初始化用户数据库
@ -147,54 +275,96 @@ async function initializeUserDatabase() {
const userDbPath = getUserDbPath();
const userDb = await getDbConnection(userDbPath);
// 记录成功和失败的操作
const results = { success: [], failed: [] };
// 创建表结构
console.log('开始创建用户数据库表结构...');
// 创建examinee表
try {
// 开始事务
await userDb.runAsync('BEGIN TRANSACTION');
console.log('开始事务');
// 创建表结构
console.log('开始创建用户数据库表结构...');
await userDb.execAsync(userSchema.examineeLog.trim());
console.log('创建 examinee_log 表成功');
await userDb.execAsync(userSchema.examineeExam.trim());
console.log('创建 examinee_exam 表成功');
await userDb.execAsync(userSchema.examineePapers.trim());
console.log('创建 examinee_papers 表成功');
await userDb.execAsync(userSchema.paperQuestions.trim());
console.log('创建 paper_questions 表成功');
await userDb.execAsync(userSchema.paperQuestionChoices.trim());
console.log('创建 paper_question_choices 表成功');
await userDb.execAsync(userSchema.paperQuestionBlanks.trim());
console.log('创建 paper_question_blanks 表成功');
await userDb.execAsync(userSchema.paperQuestionJudge.trim());
console.log('创建 paper_question_judge 表成功');
await userDb.execAsync(userSchema.paperQuestionFillTable.trim());
console.log('创建 paper_question_fill_table 表成功');
await userDb.execAsync(userSchema.paperQuestionFillTableBlanks.trim());
console.log('创建 paper_question_fill_table_blanks 表成功');
await userDb.execAsync(userSchema.paperQuestionSubjective.trim());
console.log('创建 paper_question_subjective 表成功');
// 提交事务
await userDb.runAsync('COMMIT');
console.log('提交事务成功');
return true;
await userDb.execAsync(userSchema.examinee.trim());
console.log('创建 examinee 表成功');
results.success.push('创建 examinee 表');
} catch (error) {
// 处理错误
await userDb.runAsync('ROLLBACK');
console.error('回滚事务:', error);
throw error;
console.error('创建 examinee 表失败:', error);
results.failed.push({ operation: '创建 examinee 表', error: error.message });
}
// 创建examinee_papers表
try {
await userDb.execAsync(userSchema.examinee_papers.trim());
console.log('创建 examinee_papers 表成功');
results.success.push('创建 examinee_papers 表');
} catch (error) {
console.error('创建 examinee_papers 表失败:', error);
results.failed.push({ operation: '创建 examinee_papers 表', error: error.message });
}
// 创建paper_questions表
try {
await userDb.execAsync(userSchema.paper_questions.trim());
console.log('创建 paper_questions 表成功');
results.success.push('创建 paper_questions 表');
} catch (error) {
console.error('创建 paper_questions 表失败:', error);
results.failed.push({ operation: '创建 paper_questions 表', error: error.message });
}
// 创建question_datasets表
try {
await userDb.execAsync(userSchema.question_datasets.trim());
console.log('创建 question_datasets 表成功');
results.success.push('创建 question_datasets 表');
} catch (error) {
console.error('创建 question_datasets 表失败:', error);
results.failed.push({ operation: '创建 question_datasets 表', error: error.message });
}
// 创建question_images表
try {
await userDb.execAsync(userSchema.question_images.trim());
console.log('创建 question_images 表成功');
results.success.push('创建 question_images 表');
} catch (error) {
console.error('创建 question_images 表失败:', error);
results.failed.push({ operation: '创建 question_images 表', error: error.message });
}
// 创建question_choices表
try {
await userDb.execAsync(userSchema.question_choices.trim());
console.log('创建 question_choices 表成功');
results.success.push('创建 question_choices 表');
} catch (error) {
console.error('创建 question_choices 表失败:', error);
results.failed.push({ operation: '创建 question_choices 表', error: error.message });
}
// 创建question_fill_blanks表
try {
await userDb.execAsync(userSchema.question_fill_blanks.trim());
console.log('创建 question_fill_blanks 表成功');
results.success.push('创建 question_fill_blanks 表');
} catch (error) {
console.error('创建 question_fill_blanks 表失败:', error);
results.failed.push({ operation: '创建 question_fill_blanks 表', error: error.message });
}
console.log('用户数据库初始化结果:');
console.log('成功操作:', results.success);
console.log('失败操作:', results.failed);
// 如果有失败操作,仅打印错误信息,不抛出异常
if (results.failed.length > 0) {
console.error(`用户数据库初始化有 ${results.failed.length} 个操作失败,请查看日志`);
// 输出详细的失败信息
results.failed.forEach((item, index) => {
console.error(`${index + 1}. ${item.operation} 失败: ${item.error}`);
});
}
return true;
}
// 初始化数据库

View File

@ -370,6 +370,40 @@ async function getFillBlankQuestionsByQuestionId(questionId) {
});
}
/**
* 查询试题总数和总分
* @returns {Promise<{totalQuestions: number, totalScore: number}>} 包含试题总数和总分的对象
*/
async function getQuestionsCountAndScore() {
const db = await getDbConnection(getSystemDbPath());
return executeWithRetry(db, async () => {
// 查询选择题总数和总分
const choiceResult = await db.getAsync(`
SELECT COUNT(*) as count, SUM(qc.score) as score
FROM questions q
LEFT JOIN question_choices qc ON q.id = qc.question_id
WHERE q.question_type = 'choice'
`);
// 查询填空题总数和总分
const fillBlankResult = await db.getAsync(`
SELECT COUNT(*) as count, SUM(qfb.score) as score
FROM questions q
LEFT JOIN question_fill_blanks qfb ON q.id = qfb.question_id
WHERE q.question_type = 'fill_blank'
`);
// 计算总题数和总分
const totalQuestions = (choiceResult.count || 0) + (fillBlankResult.count || 0);
const totalScore = (choiceResult.score || 0) + (fillBlankResult.score || 0);
return {
totalQuestions,
totalScore
};
});
}
export {
addQuestion,
getAllQuestions,
@ -383,5 +417,6 @@ export {
addFillBlankQuestion,
updateFillBlankQuestion,
deleteFillBlankQuestion,
getFillBlankQuestionsByQuestionId
};
getFillBlankQuestionsByQuestionId,
getQuestionsCountAndScore
};

View File

@ -1,5 +1,5 @@
// 辅助函数将db.run包装为Promise
const runAsync = (db, sql, params = []) => {
export const runAsync = (db, sql, params = []) => {
return new Promise((resolve, reject) => {
db.run(sql, params, function (err) {
if (err) reject(err);
@ -8,14 +8,12 @@ const runAsync = (db, sql, params = []) => {
});
};
export { runAsync };
// 系统数据库表结构
const systemSchema = {
export const systemSchema = {
config: `
CREATE TABLE IF NOT EXISTS config (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT NOT NULL,
key TEXT NOT NULL UNIQUE,
value TEXT NOT NULL,
protected INTEGER DEFAULT 0
);
@ -184,124 +182,97 @@ const systemSchema = {
};
// 用户数据库表结构
const userSchema = {
examineeLog: `
CREATE TABLE IF NOT EXISTS examinee_log (
export const userSchema = {
examinee: `
CREATE TABLE IF NOT EXISTS examinee (
id INTEGER PRIMARY KEY AUTOINCREMENT,
examinee_id INTEGER NOT NULL,
operation TEXT NOT NULL DEFAULT '',
operation_time TEXT NOT NULL,
examinee_name TEXT NOT NULL DEFAULT '',
examinee_gender TEXT,
examinee_unit TEXT,
written_exam_room TEXT,
written_exam_seat TEXT,
computer_exam_room TEXT,
computer_exam_seat TEXT,
examinee_id_card TEXT NOT NULL DEFAULT '',
examinee_admission_ticket TEXT NOT NULL DEFAULT '',
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
examineeExam: `
CREATE TABLE IF NOT EXISTS examinee_exam (
examinee_papers: `
CREATE TABLE IF NOT EXISTS examinee_papers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
examinee_id INTEGER NOT NULL,
exam_name TEXT NOT NULL DEFAULT '',
exam_description TEXT NOT NULL DEFAULT '',
exam_examinee_type TEXT NOT NULL DEFAULT '',
exam_notice TEXT NOT NULL DEFAULT '',
exam_minutes INTEGER NOT NULL DEFAULT 0,
start_time TEXT NOT NULL,
latest_end_time TEXT NOT NULL,
end_time TEXT NOT NULL,
exam_duration INTEGER NOT NULL DEFAULT 0,
paper_minutes INTEGER NOT NULL DEFAULT 0,
paper_minuts_min INTEGER NOT NULL DEFAULT 0,
paper_start_time TEXT,
paper_last_time TEXT,
paper_submit_time TEXT,
paper_end_time TEXT,
paper_status INTEGER NOT NULL DEFAULT 0, -- 试卷状态0未开始1进行中2已交卷
paper_score_real REAL DEFAULT 0,
paper_score REAL DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
paper_questions: `
CREATE TABLE IF NOT EXISTS paper_questions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
examinee_id INTEGER NOT NULL,
paper_id INTEGER NOT NULL,
question_type TEXT NOT NULL,
question_name TEXT NOT NULL DEFAULT '',
question_description TEXT NOT NULL DEFAULT '',
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
question_datasets: `
CREATE TABLE IF NOT EXISTS question_datasets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
dataset_name TEXT NOT NULL DEFAULT '',
dataset_data TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
question_images: `
CREATE TABLE IF NOT EXISTS question_images (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
image_name TEXT NOT NULL DEFAULT '',
image_base64 TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
examineePapers: `
CREATE TABLE IF NOT EXISTS examinee_papers (
question_choices: `
CREATE TABLE IF NOT EXISTS question_choices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
examinee_id INTEGER NOT NULL,
exam_id INTEGER NOT NULL,
paper_minutes INTEGER NOT NULL DEFAULT 0,
paper_total_score INTEGER NOT NULL DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
paperQuestions: `
CREATE TABLE IF NOT EXISTS paper_questions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
examinee_paper_id INTEGER NOT NULL,
question_type TEXT NOT NULL,
question_name TEXT NOT NULL DEFAULT '',
question_description TEXT NOT NULL DEFAULT '',
question_score INTEGER NOT NULL DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
paperQuestionChoices: `
CREATE TABLE IF NOT EXISTS paper_question_choices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_question_id INTEGER NOT NULL,
question_id INTEGER NOT NULL,
choice_description TEXT NOT NULL DEFAULT '',
choice_type TEXT NOT NULL DEFAULT 'single',
choice_options TEXT NOT NULL,
user_answers TEXT NOT NULL DEFAULT '',
correct_answers TEXT NOT NULL,
examinee_answers TEXT NOT NULL DEFAULT '',
score REAL,
score_real REAL DEFAULT 0, -- 本题得分
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (paper_question_id) REFERENCES paper_questions(id)
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
paperQuestionBlanks: `
CREATE TABLE IF NOT EXISTS paper_question_blanks (
question_fill_blanks: `
CREATE TABLE IF NOT EXISTS question_fill_blanks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_question_id INTEGER NOT NULL,
question_id INTEGER NOT NULL,
blank_description TEXT NOT NULL DEFAULT '',
blank_count INTEGER NOT NULL DEFAULT 0,
user_answers TEXT NOT NULL DEFAULT '',
correct_answers TEXT NOT NULL DEFAULT '',
examinee_answers TEXT NOT NULL DEFAULT '',
score REAL,
score_real REAL DEFAULT 0, -- 本题得分
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (paper_question_id) REFERENCES paper_questions(id)
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`,
paperQuestionJudge: `
CREATE TABLE IF NOT EXISTS paper_question_judge (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_question_id INTEGER NOT NULL,
judge_ask TEXT NOT NULL DEFAULT '',
user_answer INTEGER NOT NULL DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (paper_question_id) REFERENCES paper_questions(id)
);
`,
paperQuestionFillTable: `
CREATE TABLE IF NOT EXISTS paper_question_fill_table (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_question_id INTEGER NOT NULL,
table_name TEXT NOT NULL DEFAULT '',
table_data TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (paper_question_id) REFERENCES paper_questions(id)
);
`,
paperQuestionFillTableBlanks: `
CREATE TABLE IF NOT EXISTS paper_question_fill_table_blanks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_question_fill_table_id INTEGER NOT NULL,
cell_position TEXT NOT NULL,
cell_type TEXT NOT NULL DEFAULT 'number',
user_answer TEXT NOT NULL DEFAULT '',
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (paper_question_fill_table_id) REFERENCES paper_question_fill_table(id)
);
`,
paperQuestionSubjective: `
CREATE TABLE IF NOT EXISTS paper_question_subjective (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_question_id INTEGER NOT NULL,
subjective_type TEXT NOT NULL,
user_answer TEXT NOT NULL DEFAULT '',
score INTEGER NOT NULL DEFAULT -1,
scored_by TEXT NOT NULL DEFAULT '',
scored_at TEXT DEFAULT CURRENT_TIMESTAMP,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (paper_question_id) REFERENCES paper_questions(id)
);
`,
`
};
// 初始化默认数据
@ -310,7 +281,7 @@ const plainPassword = "t2t6a9"; // 明文密码变量定义
// 注意在实际初始化数据库时需要使用argon2对plainPassword进行哈希
// 这里只定义默认数据结构哈希操作应在index.js中的初始化函数中完成
const defaultData = {
export const defaultData = {
config: [
{ key: "admin_password", value: plainPassword, protected: 1 },
{ key: "question_bank_version", value: "1", protected: 1 },
@ -368,7 +339,7 @@ const defaultData = {
item_code: "fill_blank",
item_name: "填空题",
item_description: "填写空白处的答案",
is_active: 0,
is_active: 1,
parent_code: "objective",
},
{
@ -431,5 +402,3 @@ const defaultData = {
],
};
// 导出对象
export { systemSchema, userSchema, defaultData };

View File

@ -27,6 +27,10 @@ import {
initExamineeIpc
} from "./service/examineeService.js";
import {
initExamingIpc
} from "./service/examingService.js";
// 定义 __dirname 和 __filename
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@ -157,6 +161,8 @@ function setupIpcMain() {
// 初始化考生相关IPC
initExamineeIpc(ipcMain);
initExamingIpc(ipcMain);
}
// 确保在 app.whenReady() 中调用 setupIpcMain()

View File

@ -49,6 +49,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
updateFillBlankQuestion: (id, fillBlankData) => ipcRenderer.invoke('question-update-fill-blank', id, fillBlankData),
deleteFillBlankQuestion: (id) => ipcRenderer.invoke('question-delete-fill-blank', id),
fetchFillBlankQuestionsByQuestionId: (questionId) => ipcRenderer.invoke('question-fetch-fill-blank-by-question-id', questionId),
getQuestionsCountAndScore: () => ipcRenderer.invoke('question-get-count-and-score'),
// 考试管理相关API
createExam: (examData) => ipcRenderer.invoke('exam-create', examData),
fetchAllExams: () => ipcRenderer.invoke('exam-fetch-all'),
@ -62,5 +63,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
createExaminee: (examineeData) => ipcRenderer.invoke('examinee-create', examineeData),
updateExaminee: (id, examineeData) => ipcRenderer.invoke('examinee-update', { id, examineeData }),
deleteExaminee: (id) => ipcRenderer.invoke('examinee-delete', id),
userLogin: (idCard, admissionTicket) => ipcRenderer.invoke('user-login', { idCard, admissionTicket })
userLogin: (idCard, admissionTicket) => ipcRenderer.invoke('user-login', { idCard, admissionTicket }),
// 考试进行相关API
generateExamineePaper: (examineeData, examData) => ipcRenderer.invoke('examing-generate-paper', { examineeData, examData }),
getExamineePaperStatus: (examineeId) => ipcRenderer.invoke('examing-get-paper-status', examineeId),
updatePaperStatus: (paperId, statusData) => ipcRenderer.invoke('examing-update-paper-status', { paperId, statusData }),
});

View File

@ -0,0 +1,165 @@
// 导入数据库操作函数
import { generateExamineePaper } from '../db/examing.js';
import { getDbConnection, closeAllConnections } from '../db/index.js';
import { getUserDbPath } from '../db/path.js';
/**
* 服务层生成考生试卷
* @param {Object} examineeData - 考生数据
* @param {number} examDuration - 考试时长(分钟)
* @returns {Promise<Object>} - 包含试卷ID和状态的对象
*/
// 变更函数入参和验证逻辑
export async function generateExamineePaperService(examineeData, examData) {
try {
// 数据验证
if (!examineeData || !examineeData.id || !examineeData.examinee_name) {
throw new Error('考生数据不完整必须包含ID和姓名');
}
if (!examData || !examData.exam_minutes || examData.exam_minutes <= 0) {
throw new Error('考试时长必须为正数');
}
if (examData.exam_minutes_min === undefined || examData.exam_minutes_min < 0) {
throw new Error('最短考试时长必须为非负数');
}
const result = await generateExamineePaper(examineeData, examData);
return result;
} catch (error) {
console.error('服务层: 生成考生试卷失败', error);
return {
success: false,
message: `生成试卷失败: ${error.message}`
};
}
}
/**
* 服务层获取考生试卷状态
* @param {number} examineeId - 考生ID
* @returns {Promise<Object|null>} - 试卷状态信息
*/
export async function getExamineePaperStatusService(examineeId) {
try {
if (!examineeId || examineeId <= 0) {
throw new Error('考生ID必须为正数');
}
const userDb = await getDbConnection(getUserDbPath());
const paperStatus = await userDb.getAsync(
'SELECT * FROM examinee_papers WHERE examinee_id = ?',
[examineeId]
);
return paperStatus;
} catch (error) {
console.error('服务层: 获取考生试卷状态失败', error);
throw error;
}
}
/**
* 服务层更新试卷状态
* @param {number} paperId - 试卷ID
* @param {Object} statusData - 状态数据
* @returns {Promise<boolean>} - 是否更新成功
*/
export async function updatePaperStatusService(paperId, statusData) {
try {
if (!paperId || paperId <= 0) {
throw new Error('试卷ID必须为正数');
}
const userDb = await getDbConnection(getUserDbPath());
// 构建更新字段
const fields = [];
const values = [];
if (statusData.paper_start_time !== undefined) {
fields.push('paper_start_time = ?');
values.push(statusData.paper_start_time);
}
if (statusData.paper_last_time !== undefined) {
fields.push('paper_last_time = ?');
values.push(statusData.paper_last_time);
}
if (statusData.paper_submit_time !== undefined) {
fields.push('paper_submit_time = ?');
values.push(statusData.paper_submit_time);
}
if (statusData.paper_end_time !== undefined) {
fields.push('paper_end_time = ?');
values.push(statusData.paper_end_time);
}
if (statusData.paper_status !== undefined) {
fields.push('paper_status = ?');
values.push(statusData.paper_status);
}
if (statusData.paper_score_real !== undefined) {
fields.push('paper_score_real = ?');
values.push(statusData.paper_score_real);
}
if (fields.length === 0) {
return true; // 没有需要更新的字段
}
// 添加WHERE条件的值
values.push(paperId);
const sql = `UPDATE examinee_papers SET ${fields.join(', ')} WHERE id = ?`;
await userDb.runAsync(sql, values);
return true;
} catch (error) {
console.error('服务层: 更新试卷状态失败', error);
throw error;
}
}
/**
* 初始化考试相关的IPC处理程序
* @param {import('electron').IpcMain} ipcMain - IPC主进程实例
*/
export function initExamingIpc(ipcMain) {
// 生成考生试卷
ipcMain.handle('examing-generate-paper', async (event, { examineeData, examData }) => {
try {
return await generateExamineePaperService(examineeData, examData);
} catch (error) {
console.error('生成考生试卷失败:', error);
return {
success: false,
message: `生成试卷失败: ${error.message}`
};
}
});
// 获取考生试卷状态
ipcMain.handle('examing-get-paper-status', async (event, examineeId) => {
try {
return await getExamineePaperStatusService(examineeId);
} catch (error) {
console.error('获取考生试卷状态失败:', error);
return null;
}
});
// 更新试卷状态
ipcMain.handle('examing-update-paper-status', async (event, { paperId, statusData }) => {
try {
return await updatePaperStatusService(paperId, statusData);
} catch (error) {
console.error('更新试卷状态失败:', error);
return false;
}
});
}

View File

@ -11,7 +11,8 @@ import {
addFillBlankQuestion,
updateFillBlankQuestion,
deleteFillBlankQuestion,
getFillBlankQuestionsByQuestionId
getFillBlankQuestionsByQuestionId,
getQuestionsCountAndScore
} from '../db/question.js';
// 导入configService中的increaseQuestionBankVersion方法
@ -231,8 +232,21 @@ export async function fetchFillBlankQuestionsByQuestionId(questionId) {
}
/**
* 初始化question相关IPC
* @param {*} ipcMain
* 服务层查询试题总数和总分
* @returns {Promise<{totalQuestions: number, totalScore: number}>} 包含试题总数和总分的对象
*/
export async function fetchQuestionsCountAndScore() {
try {
return await getQuestionsCountAndScore();
} catch (error) {
console.error('服务层: 查询试题总数和总分失败', error);
throw error;
}
}
/**
* 初始化试题相关IPC通信
* @param {Electron.IpcMain} ipcMain - IPC主进程实例
*/
export async function initQuestionIpc(ipcMain) {
// 题干管理相关IPC
@ -361,4 +375,14 @@ export async function initQuestionIpc(ipcMain) {
throw error;
}
});
}
// 注册查询试题总数和总分的IPC处理程序
ipcMain.handle('question-get-count-and-score', async () => {
try {
return await fetchQuestionsCountAndScore();
} catch (error) {
console.error('Failed to fetch questions count and score:', error);
throw error;
}
});
}

View File

@ -1,6 +1,8 @@
import { createRouter, createWebHistory } from 'vue-router'
import WelcomeView from '@/views/WelcomeView.vue'
import ExamingView from '@/views/user/ExamingView.vue'
import ExamineeHomeView from '@/views/user/ExamineeHomeView.vue'
import AdminHomeView from '@/views/admin/AdminHomeView.vue'
// 导入QuestionManagementView
import QuestionManagementView from '@/views/admin/QuestionManagementView.vue'
@ -26,6 +28,12 @@ const router = createRouter({
name: 'examinee-home',
component: ExamineeHomeView,
},
// Modify the examing route to use lazy loading
{
path: '/examinee/examing',
name: 'examinee-examing',
component: ExamingView,
},
// admin/AdminHomeView路由
{
path: '/admin/home',

View File

@ -85,7 +85,7 @@
考题数量
</div>
</template>
<span class="text-gray-500">待设置</span>
<span class="text-gray-500">{{ totalQuestions }}</span>
</el-descriptions-item>
<el-descriptions-item>
<template #label>
@ -96,7 +96,7 @@
考试总分
</div>
</template>
<span class="text-gray-500">待设置</span>
<span class="text-gray-500">{{ totalScore }}</span>
</el-descriptions-item>
</el-descriptions>
@ -120,6 +120,10 @@
暂无考试须知
</div>
</div>
<div class="text-center">
<el-button type="info" @click="exitExam">返回</el-button>
<el-button type="primary" @click="startExam">开始考试</el-button>
</div>
</div>
</div>
</div>
@ -135,9 +139,9 @@
import Header from '@/components/common/Header.vue'
import Footer from '@/components/common/Footer.vue'
import { useRouter } from 'vue-router'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { ref, computed, onMounted, watch } from 'vue' // watch
import { ElMessage, ElDescriptions, ElDescriptionsItem, ElIcon } from 'element-plus'
import { ref, computed, onMounted, watch } from 'vue'
// ElMessageBox
import { ElMessage, ElDescriptions, ElDescriptionsItem, ElIcon, ElMessageBox } from 'element-plus'
import { User, Postcard, Ticket, Clock, ScaleToOriginal, Timer } from '@element-plus/icons-vue'
//
@ -161,6 +165,8 @@ const size = ref('default')
const lastExam = ref(null)
const examLoading = ref(true)
const examNotices = ref([])
const totalQuestions = ref(0)
const totalScore = ref(0)
// watchlastExam
watch(lastExam, (newValue) => {
@ -195,6 +201,18 @@ const formatExamDuration = (minutes) => {
return `${hours}小时${mins}分钟`
}
//
const getQuestionsCountAndScore = async () => {
try {
const result = await window.electronAPI.getQuestionsCountAndScore()
totalQuestions.value = result.totalQuestions
totalScore.value = result.totalScore
} catch (error) {
console.error('获取考题数量和总分失败:', error)
ElMessage.error(`获取考题数量和总分失败: ${error.message || '未知错误'}`)
}
}
//
const fetchLastExam = async () => {
examLoading.value = true
@ -218,6 +236,19 @@ const fetchLastExam = async () => {
}
}
const exitExam = async () => {
try {
if (!confirm('确定要退出考试吗?')) {
return
}
userStore.state.isLoggedIn = false
router.push('/')
} catch (error) {
console.error('退出考试失败:', error)
ElMessage.error(`退出考试失败: ${error.message || '未知错误'}`)
}
}
//
const startExam = async () => {
if (!lastExam.value) {
@ -225,25 +256,66 @@ const startExam = async () => {
return
}
console.log('开始考试 - 调用接口')
if (!examinee.value) {
ElMessage.warning('未获取到考生信息')
return
}
isLoading.value = true
try {
//
// API
// const examInfo = await window.electronAPI.startExam(lastExam.value.id)
console.log('开始生成试卷...')
// API
setTimeout(() => {
console.log('开始考试 - 成功')
ElMessage.success('即将开始考试!')
//
// router.push('/exam')
isLoading.value = false
}, 1000)
//
const examineeData = {
id: examinee.value.id,
examinee_name: examinee.value.examinee_name || '',
examinee_id_card: examinee.value.examinee_id_card || '',
examinee_admission_ticket: examinee.value.examinee_admission_ticket || '',
examinee_gender: examinee.value.examinee_gender || '',
examinee_unit: examinee.value.examinee_unit || '',
written_exam_room: examinee.value.written_exam_room || '',
written_exam_seat: examinee.value.written_exam_seat || '',
computer_exam_room: examinee.value.computer_exam_room || '',
computer_exam_seat: examinee.value.computer_exam_seat || ''
}
// 使JSON/
const examData = JSON.parse(JSON.stringify({
id: lastExam.value.id,
exam_name: lastExam.value.exam_name || '',
exam_description: lastExam.value.exam_description || '',
exam_minutes: lastExam.value.exam_minutes || 0,
exam_minutes_min: lastExam.value.exam_minutes_min || 0,
exam_notice: lastExam.value.exam_notice || []
}))
// API
const result = await window.electronAPI.generateExamineePaper(
examineeData,
examData
)
if (result.success) {
console.log('生成试卷成功:', result)
ElMessageBox.alert(
'已完成组卷,点击"进入考试"开始答题',
'组卷完成',
{
confirmButtonText: '进入考试',
type: 'success'
}
).then(() => {
router.push('/examinee/examing')
})
} else {
console.error('生成试卷失败:', result)
ElMessage.error(`生成试卷失败: ${result.message || '未知错误'}`)
}
} catch (error) {
console.error('开始考试 - 异常:', error)
ElMessage.error(`无法开始考试: ${error.message || '未知错误'}`)
console.error('生成试卷异常:', error)
ElMessage.error(`无法生成试卷: ${error.message || '未知错误'}`)
} finally {
isLoading.value = false
}
}
@ -251,6 +323,7 @@ const startExam = async () => {
//
onMounted(() => {
fetchLastExam()
getQuestionsCountAndScore()
})
</script>

View File

@ -0,0 +1,398 @@
<template>
<div v-if="isReady" class="examing-container">
<el-container>
<!-- 头部 -->
<Header />
<!-- 主要内容区域 -->
<el-main class="exam-content">
<!-- 顶栏考生信息和考试信息 -->
<div class="exam-top-bar">
<div class="left-info">
<div class="info-item"><span class="label">考生姓名</span><span class="value">张三</span></div>
<div class="info-item"><span class="label">身份证号</span><span class="value">2104XXXXXXXXXXXX1234</span></div>
<div class="info-item"><span class="label">准考证号</span><span class="value">20230001</span></div>
</div>
<div class="right-info">
<div class="info-item"><span class="label">考试总分</span><span class="value">100</span></div>
<div class="info-item"><span class="label">总时长</span><span class="value">120分钟</span></div>
<div class="info-item"><span class="label">最小时长</span><span class="value">60分钟</span></div>
<div class="info-item timer"><span class="label">倒计时</span><span class="value">{{ countdown }}</span></div>
</div>
</div>
<div class="exam-main">
<!-- 左侧边栏试题序号列表 -->
<div class="exam-sidebar">
<div class="sidebar-title">试题列表</div>
<div class="question-list">
<div v-for="i in 20" :key="i" class="question-item" :class="{ 'current': i === currentQuestion }"
@click="goToQuestion(i)">{{ i }}</div>
</div>
</div>
<!-- 主体部分试题内容 -->
<div class="exam-question-content">
<div class="question-header">
<span class="question-number"> {{ currentQuestion }} </span>
<span class="question-score">({{ questionScore }})</span>
</div>
<div class="question-body">
<div class="question-type">选择题</div>
<div class="question-text">
以下关于统计法的描述正确的是
</div>
<div class="question-options" v-if="questionType === 'choice'">
<div class="option-item" v-for="(option, index) in ['A', 'B', 'C', 'D']" :key="index"
@click="selectOption(index)">
<span class="option-letter">{{ option }}</span>
<span class="option-text">{{ getOptionText(option) }}</span>
</div>
</div>
<div class="question-answer" v-else-if="questionType === 'fill_blank'">
<input type="text" placeholder="请在此填写答案">
</div>
</div>
</div>
</div>
<!-- 底栏操作按钮 -->
<div class="exam-bottom-bar">
<el-button type="default" @click="prevQuestion" :disabled="currentQuestion === 1">上一题</el-button>
<el-button type="primary" @click="nextQuestion" :disabled="currentQuestion === totalQuestions">下一题</el-button>
<el-button type="danger" @click="submitExam">交卷</el-button>
</div>
</el-main>
<!-- 底部 -->
<Footer />
</el-container>
</div>
</template>
<script setup>
//
import Header from '@/components/common/Header.vue'
import Footer from '@/components/common/Footer.vue'
import { ElButton } from 'element-plus'
import { ref, onUnmounted, onMounted } from 'vue'
//
const currentQuestion = ref(1)
const totalQuestions = ref(20)
const questionScore = ref(5)
const questionType = ref('choice') // 'choice' 'fill_blank'
const countdown = ref('01:59:59') // 120
//
let timer = null
//
const formatTime = (seconds) => {
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
}
//
const getOptionText = (option) => {
const options = {
'A': '统计法是调整统计关系的法律规范的总称',
'B': '统计法仅适用于政府统计活动',
'C': '统计法不包括地方性统计法规',
'D': '统计法制定的依据是宪法'
}
return options[option] || ''
}
//
const prevQuestion = () => {
if (currentQuestion.value > 1) {
currentQuestion.value--
}
}
//
const nextQuestion = () => {
if (currentQuestion.value < totalQuestions.value) {
currentQuestion.value++
}
}
//
const goToQuestion = (index) => {
currentQuestion.value = index
}
//
const selectOption = (index) => {
//
}
//
const submitExam = () => {
if (confirm('确定要交卷吗?交卷后将无法修改答案。')) {
//
alert('交卷成功!')
}
}
//
// Add this line
const isReady = ref(false)
// Modify onMounted
onMounted(() => {
// Set isReady to true after mount
isReady.value = true
// 1207200
let remainingSeconds = 120 * 60
//
countdown.value = formatTime(remainingSeconds)
//
timer = setInterval(() => {
remainingSeconds--
if (remainingSeconds <= 0) {
clearInterval(timer)
countdown.value = '00:00:00'
alert('考试时间已结束,系统将自动交卷。')
//
} else {
countdown.value = formatTime(remainingSeconds)
}
}, 1000)
})
//
onUnmounted(() => {
if (timer) {
clearInterval(timer)
timer = null // Add this line to prevent memory leaks
}
})
</script>
<style scoped>
/* 自定义样式 */
.examing-container, .el-container {
height: 100vh;
display: flex;
flex-direction: column;
}
.exam-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 1rem;
box-sizing: border-box;
overflow: hidden;
}
/* 顶栏样式 */
.exam-top-bar {
display: flex;
justify-content: space-between;
padding: 0.5rem 1rem;
background-color: #f5f7fa;
border-radius: 4px;
margin-bottom: 1rem;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.left-info, .right-info {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.info-item {
display: flex;
align-items: center;
}
.label {
font-weight: bold;
margin-right: 0.5rem;
color: #606266;
}
.value {
color: #303133;
}
.timer .value {
color: #f56c6c;
font-weight: bold;
}
/* 主要内容区样式 */
.exam-main {
display: flex;
flex: 1;
gap: 1rem;
overflow: hidden;
}
/* 侧边栏样式 */
.exam-sidebar {
width: 240px;
background-color: #f5f7fa;
border-radius: 4px;
display: flex;
flex-direction: column;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.sidebar-title {
padding: 0.75rem 1rem;
font-weight: bold;
color: #1e40af;
border-bottom: 1px solid #e4e7ed;
}
.question-list {
padding: 1rem;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
overflow-y: auto;
flex: 1;
}
.question-item {
width: 36px;
height: 36px;
border-radius: 4px;
background-color: #fff;
border: 1px solid #dcdfe6;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.question-item:hover {
border-color: #409eff;
}
.question-item.current {
background-color: #409eff;
color: #fff;
border-color: #409eff;
}
/* 试题内容区样式 */
.exam-question-content {
flex: 1;
background-color: #fff;
border-radius: 4px;
padding: 1rem;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
overflow-y: auto;
}
.question-header {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #e4e7ed;
}
.question-number {
font-weight: bold;
color: #303133;
}
.question-score {
color: #f56c6c;
}
.question-type {
display: inline-block;
padding: 0.25rem 0.5rem;
background-color: #e6f7ff;
color: #1890ff;
border-radius: 4px;
margin-bottom: 1rem;
font-size: 0.875rem;
}
.question-text {
margin-bottom: 1.5rem;
line-height: 1.6;
color: #303133;
}
/* 选项样式 */
.question-options {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.option-item {
padding: 0.75rem 1rem;
border-radius: 4px;
border: 1px solid #dcdfe6;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: flex-start;
}
.option-item:hover {
border-color: #409eff;
background-color: #f0f8ff;
}
.option-letter {
display: inline-block;
width: 24px;
height: 24px;
border-radius: 50%;
background-color: #f5f7fa;
text-align: center;
line-height: 24px;
margin-right: 0.75rem;
flex-shrink: 0;
}
/* 填空题样式 */
.question-answer input {
width: 100%;
padding: 0.75rem;
border: 1px solid #dcdfe6;
border-radius: 4px;
box-sizing: border-box;
}
/* 底栏样式 */
.exam-bottom-bar {
display: flex;
justify-content: space-between;
padding: 1rem;
background-color: #f5f7fa;
border-radius: 4px;
margin-top: 1rem;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
/* 响应式样式 */
@media (max-width: 992px) {
.exam-main {
flex-direction: column;
}
.exam-sidebar {
width: 100%;
height: 200px;
}
}
</style>