ExamingView.vue考试页面,同时设置了软件标题,增加了退出提醒
This commit is contained in:
parent
3f61b5eb72
commit
f3f302da49
@ -15,6 +15,48 @@ function formatDateTime(date) {
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量插入数据到指定表
|
||||
* @param {sqlite3.Database} db - 数据库连接对象
|
||||
* @param {string} tableName - 表名
|
||||
* @param {Array} dataArray - 要插入的数据数组
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function batchInsert(db, tableName, dataArray) {
|
||||
if (!dataArray || dataArray.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 开始事务以提高性能
|
||||
await db.runAsync('BEGIN TRANSACTION');
|
||||
|
||||
try {
|
||||
// 获取第一个对象的字段名
|
||||
const fields = Object.keys(dataArray[0]);
|
||||
const placeholders = dataArray.map(() => `(${fields.map(() => '?').join(', ')})`).join(', ');
|
||||
const sql = `INSERT INTO ${tableName} (${fields.join(', ')}) VALUES ${placeholders}`;
|
||||
|
||||
// 扁平化所有值
|
||||
const values = [];
|
||||
dataArray.forEach(item => {
|
||||
fields.forEach(field => {
|
||||
values.push(item[field]);
|
||||
});
|
||||
});
|
||||
|
||||
// 执行批量插入
|
||||
await db.runAsync(sql, values);
|
||||
|
||||
// 提交事务
|
||||
await db.runAsync('COMMIT');
|
||||
} catch (error) {
|
||||
// 发生错误时回滚事务
|
||||
await db.runAsync('ROLLBACK');
|
||||
console.error(`批量插入数据到${tableName}失败:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成考生试卷
|
||||
* @param {Object} examineeData - 考生数据
|
||||
@ -398,6 +440,7 @@ async function generateExamineePaper(examineeData, examData) {
|
||||
* @returns {Promise<Array>} - 包含试题序列的数组
|
||||
*/
|
||||
async function loadPaperSerial(paperId) {
|
||||
console.log('0903调试(loadPaperSerial): ', paperId);
|
||||
try {
|
||||
// 1. 获取数据库连接
|
||||
const userDb = await getDbConnection(getUserDbPath());
|
||||
@ -827,7 +870,7 @@ async function processPaper(paperId) {
|
||||
`SELECT * FROM examinee_papers WHERE id = ?`,
|
||||
[paperId]
|
||||
);
|
||||
console.log('更新试卷最后操作时间成功:', paper);
|
||||
// console.log('更新试卷最后操作时间成功:', paper);
|
||||
return {
|
||||
success: true,
|
||||
message: '试卷处理时间已更新',
|
||||
@ -1162,6 +1205,7 @@ async function getPaper(paperId) {
|
||||
// 在文件末尾添加所有导出
|
||||
module.exports = {
|
||||
formatDateTime,
|
||||
batchInsert,
|
||||
generateExamineePaper,
|
||||
loadPaperSerial,
|
||||
getQuestionByRelatedId,
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
import { app, protocol, BrowserWindow, ipcMain } from 'electron'
|
||||
import { app, protocol, BrowserWindow, ipcMain, dialog } from 'electron'
|
||||
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
|
||||
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
// 替换argon2为bcryptjs
|
||||
@ -29,9 +29,12 @@ protocol.registerSchemesAsPrivileged([
|
||||
{ scheme: 'app', privileges: { secure: true, standard: true } }
|
||||
])
|
||||
|
||||
// 添加全局变量保存窗口引用
|
||||
let mainWindow = null
|
||||
|
||||
async function createWindow() {
|
||||
// Create the browser window.
|
||||
const win = new BrowserWindow({
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 800, // 默认宽度(实际会被最大化覆盖)
|
||||
height: 600, // 默认高度(实际会被最大化覆盖)
|
||||
show: false, // 先隐藏窗口,避免闪烁
|
||||
@ -44,21 +47,21 @@ async function createWindow() {
|
||||
})
|
||||
|
||||
// 设置隐藏菜单栏
|
||||
win.setMenu(null);
|
||||
mainWindow.setMenu(null);
|
||||
|
||||
// 在窗口显示前设置最大化
|
||||
win.maximize();
|
||||
mainWindow.maximize();
|
||||
// 然后显示窗口
|
||||
win.show();
|
||||
mainWindow.show();
|
||||
|
||||
if (process.env.WEBPACK_DEV_SERVER_URL) {
|
||||
// Load the url of the dev server if in development mode
|
||||
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
|
||||
if (!process.env.IS_TEST) win.webContents.openDevTools()
|
||||
await mainWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
|
||||
if (!process.env.IS_TEST) mainWindow.webContents.openDevTools()
|
||||
} else {
|
||||
createProtocol('app')
|
||||
// Load the index.html when not in development
|
||||
win.loadURL('app://./index.html')
|
||||
mainWindow.loadURL('app://./index.html')
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,6 +80,39 @@ app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
|
||||
// 添加退出确认逻辑
|
||||
app.on('before-quit', (event) => {
|
||||
// 如果是MacOS系统,让系统默认处理
|
||||
if (process.platform === 'darwin') {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果没有窗口,直接退出
|
||||
if (!mainWindow) {
|
||||
return
|
||||
}
|
||||
|
||||
// 阻止默认的退出行为
|
||||
event.preventDefault()
|
||||
|
||||
// 显示确认对话框
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
type: 'warning',
|
||||
title: '确认关闭',
|
||||
message: '系统关闭后,已进行的试题将不会被保存,是否确认要关闭?',
|
||||
buttons: ['取消', '确认关闭'],
|
||||
defaultId: 0,
|
||||
cancelId: 0
|
||||
}).then((result) => {
|
||||
// 如果用户确认关闭,则退出应用
|
||||
if (result.response === 1) {
|
||||
app.exit(0)
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error('显示退出确认对话框失败:', err)
|
||||
})
|
||||
})
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
@ -85,6 +121,8 @@ const sqlite3 = require('sqlite3').verbose();
|
||||
|
||||
// 在app.on('ready')事件中添加
|
||||
app.on('ready', async () => {
|
||||
// 禁用Vue DevTools扩展
|
||||
/*
|
||||
if (isDevelopment && !process.env.IS_TEST) {
|
||||
// Install Vue Devtools
|
||||
try {
|
||||
@ -93,6 +131,7 @@ app.on('ready', async () => {
|
||||
console.error('Vue Devtools failed to install:', e.toString())
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// 初始化配置相关的IPC处理程序
|
||||
initConfigIpc(ipcMain);
|
||||
@ -109,8 +148,18 @@ app.on('ready', async () => {
|
||||
// 初始化文件相关的IPC处理程序
|
||||
initFileIpc(ipcMain);
|
||||
|
||||
createWindow();
|
||||
});
|
||||
// 检查数据库是否初始化
|
||||
try {
|
||||
const isInitialized = await checkDatabaseInitialized();
|
||||
if (!isInitialized) {
|
||||
await initializeDatabase();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('数据库初始化失败:', error);
|
||||
}
|
||||
|
||||
createWindow()
|
||||
})
|
||||
|
||||
// Exit cleanly on request from parent process in development mode.
|
||||
if (isDevelopment) {
|
||||
|
@ -143,6 +143,7 @@ async function updatePaperStatusService(paperId, statusData) {
|
||||
* @returns {Promise<Array>} - 包含试题序列的数组
|
||||
*/
|
||||
async function loadPaperSerialService(paperId) {
|
||||
console.log("0903调试(loadPaperSerialService): ", paperId);
|
||||
try {
|
||||
if (!paperId || paperId <= 0) {
|
||||
throw new Error("试卷ID必须为正数");
|
||||
@ -372,8 +373,9 @@ function initExamingIpc(ipcMain) {
|
||||
);
|
||||
|
||||
// 加载试卷试题序列
|
||||
ipcMain.handle("examing-load-paper-serial", async (event, paperId) => {
|
||||
ipcMain.handle("examing-load-paper-serial", async (event, { paperId }) => {
|
||||
try {
|
||||
console.log("0903调试:", paperId);
|
||||
return await loadPaperSerialService(paperId);
|
||||
} catch (error) {
|
||||
console.error("加载试卷试题序列失败:", error);
|
||||
@ -387,9 +389,9 @@ function initExamingIpc(ipcMain) {
|
||||
// 根据表名和ID获取完整的试题数据
|
||||
ipcMain.handle(
|
||||
"examing-get-question-by-related-id",
|
||||
async (event, { tableName, id }) => {
|
||||
async (event, { tableName, relatedId }) => {
|
||||
try {
|
||||
return await getQuestionByRelatedIdService(tableName, id);
|
||||
return await getQuestionByRelatedIdService(tableName, relatedId);
|
||||
} catch (error) {
|
||||
console.error("获取试题数据失败:", error);
|
||||
return {
|
||||
@ -417,7 +419,7 @@ function initExamingIpc(ipcMain) {
|
||||
);
|
||||
|
||||
// 开始考试
|
||||
ipcMain.handle("examing-start-paper", async (event, paperId) => {
|
||||
ipcMain.handle("examing-start-paper", async (event, { paperId }) => {
|
||||
try {
|
||||
return await startPaperService(paperId);
|
||||
} catch (error) {
|
||||
@ -430,7 +432,7 @@ function initExamingIpc(ipcMain) {
|
||||
});
|
||||
|
||||
// 提交考试
|
||||
ipcMain.handle("examing-submit-paper", async (event, paperId) => {
|
||||
ipcMain.handle("examing-submit-paper", async (event, { paperId }) => {
|
||||
try {
|
||||
return await submitPaperService(paperId);
|
||||
} catch (error) {
|
||||
@ -443,7 +445,7 @@ function initExamingIpc(ipcMain) {
|
||||
});
|
||||
|
||||
// 结束考试
|
||||
ipcMain.handle("examing-end-paper", async (event, paperId) => {
|
||||
ipcMain.handle("examing-end-paper", async (event, { paperId }) => {
|
||||
try {
|
||||
return await endPaperService(paperId);
|
||||
} catch (error) {
|
||||
@ -456,7 +458,7 @@ function initExamingIpc(ipcMain) {
|
||||
});
|
||||
|
||||
// 处理试卷
|
||||
ipcMain.handle("examing-process-paper", async (event, paperId) => {
|
||||
ipcMain.handle("examing-process-paper", async (event, { paperId }) => {
|
||||
try {
|
||||
return await processPaperService(paperId);
|
||||
} catch (error) {
|
||||
@ -469,7 +471,7 @@ function initExamingIpc(ipcMain) {
|
||||
});
|
||||
|
||||
// 检查试卷答案并计算得分
|
||||
ipcMain.handle("examing-check-paper-answers", async (event, paperId) => {
|
||||
ipcMain.handle("examing-check-paper-answers", async (event, { paperId }) => {
|
||||
try {
|
||||
return await checkPaperAnswersService(paperId);
|
||||
} catch (error) {
|
||||
|
@ -37,5 +37,6 @@ export default {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
</style>
|
@ -78,7 +78,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
examingUpdatePaperStatus: ({ paperId, status }) => ipcRenderer.invoke('examing-update-paper-status', { paperId, status }),
|
||||
examingLoadPaperSerial: ({ paperId }) => ipcRenderer.invoke('examing-load-paper-serial', { paperId }),
|
||||
examingGetQuestionByRelatedId: ({ tableName, relatedId }) => ipcRenderer.invoke('examing-get-question-by-related-id', { tableName, relatedId }),
|
||||
examingUpdateAnswer: ({ paperId, questionId, answer }) => ipcRenderer.invoke('examing-update-answer', { paperId, questionId, answer }),
|
||||
// 修复:examingUpdateAnswer接口的参数结构
|
||||
examingUpdateAnswer: ({ tableName, id, answers }) => ipcRenderer.invoke('examing-update-answer', { tableName, id, answers }),
|
||||
examingStartPaper: ({ paperId }) => ipcRenderer.invoke('examing-start-paper', { paperId }),
|
||||
examingSubmitPaper: ({ paperId }) => ipcRenderer.invoke('examing-submit-paper', { paperId }),
|
||||
examingEndPaper: ({ paperId }) => ipcRenderer.invoke('examing-end-paper', { paperId }),
|
||||
|
@ -9,6 +9,8 @@ import ExamineeManagementView from '../views/admin/ExamineeManagementView.vue'
|
||||
import ExamManagementView from '../views/admin/ExamManagementView.vue'
|
||||
// 导入考生相关组件
|
||||
import ExamineeHomeView from '../views/user/ExamineeHomeView.vue'
|
||||
// 添加ExamingView组件导入
|
||||
import ExamingView from '../views/user/ExamingView.vue'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
@ -38,6 +40,8 @@ const routes = [
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{ path: 'home', name: 'ExamineeHome', component: ExamineeHomeView },
|
||||
// 添加考试页面路由
|
||||
{ path: 'exam/:paperId', name: 'Examing', component: ExamingView },
|
||||
// 可以在这里添加更多考生相关的路由
|
||||
]
|
||||
}
|
||||
|
@ -8,7 +8,8 @@ export default new Vuex.Store({
|
||||
examinee: null, // 保存考生信息
|
||||
admin: null, // 保存管理员信息
|
||||
isLoggedIn: false, // 通用登录状态
|
||||
userType: null // 用户类型: 'examinee' 或 'admin'
|
||||
userType: null, // 用户类型: 'examinee' 或 'admin'
|
||||
paper: null // 新增:保存当前考试的试卷信息
|
||||
},
|
||||
mutations: {
|
||||
setExaminee(state, examineeInfo) {
|
||||
@ -28,6 +29,14 @@ export default new Vuex.Store({
|
||||
state.admin = null
|
||||
state.isLoggedIn = false
|
||||
state.userType = null
|
||||
},
|
||||
// 新增:设置试卷信息
|
||||
setPaper(state, paperInfo) {
|
||||
state.paper = paperInfo
|
||||
},
|
||||
// 新增:清除试卷信息
|
||||
clearPaper(state) {
|
||||
state.paper = null
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
@ -3,9 +3,9 @@
|
||||
<div class="student-home-container">
|
||||
<div class="content-wrapper">
|
||||
<!-- 考试须知卡片 -->
|
||||
<div class="exam-card rounded-lg shadow-lg p-4 border-2">
|
||||
<div class="flex justify-between text-center mb-2">
|
||||
<h2 class="text-2xl font-bold text-primary">考生信息</h2>
|
||||
<div class="exam-card rounded-lg shadow-lg p-4 border">
|
||||
<div class="flex justify-between text-center">
|
||||
<h3 class="font-bold text-primary">考生信息</h3>
|
||||
</div>
|
||||
<!-- 考生信息部分 -->
|
||||
<el-descriptions class="margin-top" :column="3" :size="size" border>
|
||||
@ -38,8 +38,8 @@
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-divider />
|
||||
<div class="flex justify-between text-center mt-4 mb-2">
|
||||
<h2 class="text-2xl font-bold text-primary">考试信息</h2>
|
||||
<div class="flex justify-between text-center mt-2">
|
||||
<h3 class="font-bold text-primary">考试信息</h3>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<el-descriptions class="margin-top" :column="2" :size="size" border>
|
||||
@ -82,8 +82,8 @@
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<el-divider />
|
||||
<div class="flex justify-between text-center mt-4 mb-4">
|
||||
<h2 class="text-2xl font-bold text-primary">考试须知</h2>
|
||||
<div class="flex justify-between text-center mt-2">
|
||||
<h3 class="font-bold text-primary">考试须知</h3>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div v-if="!examLoading && examNotices.length > 0" class="space-y-2 pl-4">
|
||||
@ -262,17 +262,17 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.examinee) {
|
||||
this.$message.warning('未获取到考生信息')
|
||||
if (!this.examinee || !this.examinee.id || !this.examinee.examinee_name) {
|
||||
this.$message.warning('未获取到完整的考生信息')
|
||||
return
|
||||
}
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
try {
|
||||
console.log('开始生成试卷...')
|
||||
console.log('开始生成试卷...', { examinee: this.examinee, exam: this.lastExam })
|
||||
|
||||
// 创建可序列化的考生数据对象
|
||||
// 创建可序列化的完整考生数据对象
|
||||
const examineeData = {
|
||||
id: this.examinee.id,
|
||||
examinee_name: this.examinee.examinee_name || '',
|
||||
@ -296,15 +296,18 @@ export default {
|
||||
exam_notice: this.lastExam.exam_notice || []
|
||||
}))
|
||||
|
||||
// 调用生成试卷API
|
||||
const result = await window.electronAPI.examingGeneratePaper({
|
||||
examineeId: examineeData.id,
|
||||
examId: examData.id
|
||||
// 直接调用ipcRenderer接口,跳过preload.js中定义的不匹配方法
|
||||
const result = await window.electronAPI.ipcRenderer.invoke('examing-generate-paper', {
|
||||
examineeData: examineeData,
|
||||
examData: examData
|
||||
})
|
||||
|
||||
if (result && result.success) {
|
||||
console.log('生成试卷成功:', result)
|
||||
// 由于Vuex store中没有setPaper方法,这里可以考虑添加或者直接传递参数
|
||||
|
||||
// 新增:保存试卷信息到store
|
||||
this.$store.commit('setPaper', result.data)
|
||||
|
||||
this.$alert(
|
||||
'已完成组卷,点击"进入考试"开始答题',
|
||||
'组卷完成',
|
||||
@ -313,9 +316,9 @@ export default {
|
||||
type: 'success'
|
||||
}
|
||||
).then(() => {
|
||||
// 使用命名路由传递参数
|
||||
// 使用正确的路由名称进行跳转
|
||||
this.$router.push({
|
||||
name: 'examinee-examing',
|
||||
name: 'Examing',
|
||||
params: { paperId: result.data.id }
|
||||
})
|
||||
})
|
||||
@ -348,7 +351,6 @@ export default {
|
||||
/* 确保容器占满可用空间 */
|
||||
.student-home-container {
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@ -357,17 +359,17 @@ export default {
|
||||
.content-wrapper {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 考试卡片样式 - 响应式设计 */
|
||||
.exam-card {
|
||||
width: 100%;
|
||||
min-height: calc(100vh - 4rem);
|
||||
margin: 0px 0px 20px 0px;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -378,11 +380,6 @@ export default {
|
||||
@media (max-width: 768px) {
|
||||
.exam-card {
|
||||
max-width: 100%;
|
||||
min-height: calc(100vh - 2rem);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,7 +387,6 @@ export default {
|
||||
@media (min-width: 1200px) {
|
||||
.exam-card {
|
||||
max-width: 80vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
}
|
||||
|
||||
@ -398,7 +394,6 @@ export default {
|
||||
@media (min-width: 1600px) {
|
||||
.exam-card {
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
}
|
||||
|
||||
|
1185
src/views/user/ExamingView.vue
Normal file
1185
src/views/user/ExamingView.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,13 @@
|
||||
module.exports = {
|
||||
// 添加应用标题配置
|
||||
chainWebpack: config => {
|
||||
config
|
||||
.plugin('html')
|
||||
.tap(args => {
|
||||
args[0].title = '统计数据考试系统'
|
||||
return args
|
||||
})
|
||||
},
|
||||
pluginOptions: {
|
||||
electronBuilder: {
|
||||
nodeIntegration: false,
|
||||
|
Loading…
Reference in New Issue
Block a user