Compare commits

...

2 Commits

Author SHA1 Message Date
chenqiang
b6bc4b3a04 考试管理 2025-09-01 09:48:57 +08:00
chenqiang
96476e6127 考生管理 2025-08-31 11:09:10 +08:00
8 changed files with 14066 additions and 8400 deletions

21833
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
const { executeWithRetry } = require('./utils.js');
// 修复导入方式
const { getDbConnection } = require('./index.js');
const { getSystemDbPath } = require('./index.js');
const { getSystemDbPath } = require('./path.js'); // 从 path.js 导入
const { executeWithRetry } = require('./utils.js'); // 从 utils.js 导入
/**
* 查询所有考生列表

View File

@ -35,6 +35,7 @@ async function createWindow() {
width: 800, // 默认宽度(实际会被最大化覆盖)
height: 600, // 默认高度(实际会被最大化覆盖)
show: false, // 先隐藏窗口,避免闪烁
// frame: false, // 无边框窗口(可选,根据需求决定是否保留)
webPreferences: {
// 改为使用绝对路径解析
preload: require('path').join(process.cwd(), 'src/preload.js'),
@ -43,6 +44,9 @@ async function createWindow() {
}
})
// 设置隐藏菜单栏
win.setMenu(null);
// 在窗口显示前设置最大化
win.maximize();
// 然后显示窗口

View File

@ -25,8 +25,8 @@
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item>修改密码</el-dropdown-item>
<!-- <el-dropdown-item>个人中心</el-dropdown-item> -->
<!-- <el-dropdown-item>修改密码</el-dropdown-item> -->
<el-dropdown-item divided @click.native="handleLogout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>

View File

@ -20,26 +20,18 @@
<i class="el-icon-menu"></i>
<span slot="title">后台首页</span>
</el-menu-item>
<el-menu-item index="/admin/examinee">
<i class="el-icon-user-solid"></i>
<span slot="title">考生管理</span>
</el-menu-item>
<el-menu-item index="/admin/question">
<i class="el-icon-document-checked"></i>
<span slot="title">试题管理</span>
</el-menu-item>
<el-menu-item index="/admin/examinee">
<i class="el-icon-user-solid"></i>
<span slot="title">考生管理</span>
</el-menu-item>
<el-menu-item index="/admin/exam">
<i class="el-icon-date"></i>
<span slot="title">考试管理</span>
</el-menu-item>
<el-menu-item index="/admin/statistics">
<i class="el-icon-data-analysis"></i>
<span slot="title">数据统计</span>
</el-menu-item>
<el-menu-item index="/admin/settings">
<i class="el-icon-setting"></i>
<span slot="title">系统设置</span>
</el-menu-item>
<el-menu-item index="logout">
<i class="el-icon-switch-button"></i>
<span slot="title">退出登录</span>

View File

@ -4,6 +4,9 @@ import WelcomeView from '../views/WelcomeView.vue'
import AdminLayout from '../components/admin/AdminLayout.vue'
import AdminHomeView from '../views/admin/AdminHomeView.vue'
import QuestionManagementView from '../views/admin/QuestionManagementView.vue'
import ExamineeManagementView from '../views/admin/ExamineeManagementView.vue'
// 添加考试管理组件导入
import ExamManagementView from '../views/admin/ExamManagementView.vue'
Vue.use(VueRouter)
@ -30,6 +33,16 @@ const routes = [
path: 'question',
name: 'QuestionManagement',
component: QuestionManagementView
},
{
path: 'examinee',
name: 'ExamineeManagement',
component: ExamineeManagementView
},
{
path: 'exam',
name: 'ExamManagement',
component: ExamManagementView
}
// 可以在这里添加更多子路由
]
@ -37,7 +50,7 @@ const routes = [
]
const router = new VueRouter({
mode: 'history',
mode: 'hash', // 将history改为hash
base: process.env.BASE_URL,
routes
})

View File

@ -0,0 +1,355 @@
<template>
<div class="exam-management-container">
<div class="exam-header">
<h1>考试管理</h1>
<!-- <el-button type="primary" @click="handleAddExam">+ 添加考试</el-button> -->
</div>
<div class="exam-content">
<el-table :data="examList" style="width: 100%" v-loading="loading">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="exam_minutes" label="考试时长(分钟)" width="180" />
<el-table-column prop="exam_minutes_min" label="最短考试时长(分钟)" width="180" />
<el-table-column label="考试须知" min-width="200">
<template slot-scope="scope">
<div class="exam-notice-cell" v-if="scope.row.exam_notice">
<div v-if="Array.isArray(scope.row.exam_notice)" class="notice-list">
<div v-for="(item, index) in scope.row.exam_notice" :key="index" class="notice-item">
{{ item }}
</div>
</div>
<div v-else-if="typeof scope.row.exam_notice === 'string'">
<!-- 尝试将字符串按换行符分割显示 -->
<div v-if="scope.row.exam_notice.includes('\n')" class="notice-list">
<div v-for="(item, index) in scope.row.exam_notice.split('\n')" :key="index" class="notice-item">
{{ item }}
</div>
</div>
<!-- 普通字符串直接显示 -->
<div v-else :title="scope.row.exam_notice" class="ellipsis-cell">
{{ scope.row.exam_notice }}
</div>
</div>
</div>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" width="180" />
<el-table-column prop="updated_at" label="更新时间" width="180" />
<el-table-column label="操作" width="180" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="small" @click="handleEditExam(scope.row)">
编辑
</el-button>
<!-- <el-button
type="danger"
size="small"
@click="handleDeleteExam(scope.row.id)"
>
删除
</el-button> -->
</template>
</el-table-column>
</el-table>
</div>
<!-- 添加/编辑考试弹窗 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="600px" append-to-body show-close>
<el-form :model="formData" label-width="120px">
<el-form-item label="考试名称" prop="exam_name">
<el-input v-model="formData.exam_name" placeholder="请输入考试名称(选填)" />
</el-form-item>
<el-form-item label="考试描述" prop="exam_description">
<el-input v-model="formData.exam_description" type="textarea" placeholder="请输入考试描述" :rows="4" />
</el-form-item>
<el-form-item label="考试须知" prop="exam_notice">
<el-input v-model="formData.exam_notice" type="textarea" placeholder="请输入考试须知,每行一条" :rows="3" />
</el-form-item>
<el-form-item label="考试时长(分钟)" prop="exam_minutes"
:rules="[{ required: true, message: '请输入考试时长', trigger: 'blur' }, { type: 'number', min: 1, message: '考试时长必须大于0分钟' }]">
<el-input v-model.number="formData.exam_minutes" type="number" placeholder="请输入考试时长" />
</el-form-item>
<el-form-item label="最少考试时间(分钟)" prop="exam_minutes_min"
:rules="[{ required: true, message: '请输入最少考试时间', trigger: 'blur' }, { type: 'number', min: 0, message: '最少考试时间不能小于0分钟' }]">
<el-input v-model.number="formData.exam_minutes_min" type="number" placeholder="请输入最少考试时间" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveExam">保存</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'ExamManagementView',
data () {
return {
examList: [],
loading: false,
dialogVisible: false,
dialogTitle: '添加考试',
isEdit: false,
formData: {
id: null,
exam_name: '',
exam_description: '',
exam_notice: '',
exam_minutes: null,
exam_minutes_min: null,
exam_examinee_type: ''
}
}
},
mounted () {
this.fetchExams()
},
methods: {
//
// fetchExams
async fetchExams () {
this.loading = true
try {
const result = await window.electronAPI.examFetchAll()
//
if (Array.isArray(result)) {
// result
//
const formattedExams = result.map(exam => ({
...exam
// created_at: this.formatDate(exam.created_at),
// updated_at: this.formatDate(exam.updated_at)
}))
this.examList = formattedExams
} else if (result && !result.success) {
// resultsuccess: falseerror
this.$message.error('获取考试列表失败: ' + result.error)
console.error('获取考试列表失败', result.error)
} else {
//
this.$message.error('获取考试列表失败: 返回格式异常')
console.error('获取考试列表失败,返回格式异常', result)
}
} catch (error) {
this.$message.error('获取考试列表失败: ' + error.message)
console.error('获取考试列表失败', error)
} finally {
this.loading = false
}
},
//
async handleSaveExam () {
try {
//
const examData = { ...this.formData }
//
if (examData.exam_notice) {
const noticeArray = examData.exam_notice.split('\n').filter(item => item.trim() !== '')
examData.exam_notice = JSON.stringify(noticeArray)
}
if (this.isEdit) {
await window.electronAPI.examUpdate(examData.id, examData)
this.$message.success('考试更新成功')
} else {
await window.electronAPI.examCreate(examData)
this.$message.success('考试添加成功')
}
this.dialogVisible = false
this.fetchExams() //
} catch (error) {
this.$message.error('保存考试失败: ' + error.message)
console.error('保存考试失败', error)
}
},
//
handleDeleteExam (id) {
this.$confirm(
'确定要删除该考试吗?',
'确认删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(async () => {
try {
await window.electronAPI.examDelete(id)
this.$message.success('考试删除成功')
this.fetchExams() //
} catch (error) {
this.$message.error('删除考试失败: ' + error.message)
console.error('删除考试失败', error)
}
})
.catch(() => {
//
})
},
//
formatDate (dateString) {
if (!dateString) return ''
const date = new Date(dateString)
return date.toLocaleString()
},
//
handleAddExam () {
this.isEdit = false
this.dialogTitle = '添加考试'
this.formData = {
id: null,
exam_name: '',
exam_description: '',
exam_notice: '',
exam_minutes: null,
exam_minutes_min: null,
exam_examinee_type: ''
}
this.dialogVisible = true
},
//
handleEditExam (row) {
this.isEdit = true
this.dialogTitle = '编辑考试'
// row
const examData = { ...row }
// JSON
if (examData.exam_notice) {
try {
//
if (typeof examData.exam_notice === 'string') {
// JSON
try {
const noticeArray = JSON.parse(examData.exam_notice)
if (Array.isArray(noticeArray)) {
// JSON
examData.exam_notice = noticeArray.join('\n')
}
//
} catch (e) {
// JSON
console.log('考试须知不是JSON字符串保持原样:', examData.exam_notice)
}
} else if (Array.isArray(examData.exam_notice)) {
//
examData.exam_notice = examData.exam_notice.join('\n')
}
} catch (error) {
//
console.error('解析考试须知失败:', error)
}
}
this.formData = examData
this.dialogVisible = true
},
//
async handleSaveExam () {
try {
//
const examData = { ...this.formData }
//
if (examData.exam_notice) {
const noticeArray = examData.exam_notice.split('\n').filter(item => item.trim() !== '')
examData.exam_notice = JSON.stringify(noticeArray)
}
if (this.isEdit) {
// APIexamUpdate
await window.electronAPI.examUpdate(examData.id, examData)
this.$message.success('考试更新成功')
} else {
// APIexamCreate
await window.electronAPI.examCreate(examData)
this.$message.success('考试添加成功')
}
this.dialogVisible = false
this.fetchExams() //
} catch (error) {
this.$message.error('保存考试失败: ' + error.message)
console.error('保存考试失败', error)
}
},
//
handleDeleteExam (id) {
this.$confirm(
'确定要删除该考试吗?',
'确认删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(async () => {
try {
await window.electronAPI.deleteExam(id)
this.$message.success('考试删除成功')
this.fetchExams() //
} catch (error) {
this.$message.error('删除考试失败: ' + error.message)
console.error('删除考试失败', error)
}
})
.catch(() => {
//
})
}
}
}
</script>
<style scoped>
.exam-management-container {
width: 100%;
height: 100%;
}
.exam-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.exam-content {
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
/* 考试须知样式 */
.exam-notice-cell {
padding: 5px 0;
}
.notice-list {
max-height: 100px;
overflow-y: auto;
}
.notice-item {
padding: 2px 0;
white-space: normal;
word-break: break-all;
}
.ellipsis-cell {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@ -0,0 +1,234 @@
<template>
<div class="examinee-management-container">
<div class="examinee-header">
<h1>考生管理</h1>
<el-button type="primary" @click="handleAddExaminee">+ 添加考生</el-button>
</div>
<div class="examinee-content">
<el-table :data="examineeList" style="width: 100%" v-loading="loading">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="examinee_name" label="姓名" width="120" />
<el-table-column prop="examinee_id_card" label="身份证号" width="180" />
<el-table-column prop="examinee_admission_ticket" label="准考证号" width="180" />
<el-table-column label="操作" width="180" fixed="right">
<template slot-scope="scope">
<el-button
type="primary"
size="small"
@click="handleEditExaminee(scope.row)"
>
编辑
</el-button>
<el-button
type="danger"
size="small"
@click="handleDeleteExaminee(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 添加/编辑考生弹窗 -->
<el-dialog
:title="dialogTitle"
:visible.sync="dialogVisible"
append-to-body
show-close
width="600px"
>
<el-form :model="formData" label-width="120px" ref="formRef">
<el-form-item label="考生姓名" prop="examinee_name" :rules="[{ required: true, message: '请输入考生姓名', trigger: 'blur' }]">
<el-input v-model="formData.examinee_name" placeholder="请输入考生姓名" />
</el-form-item>
<el-form-item label="性别" prop="examinee_gender">
<el-select v-model="formData.examinee_gender" placeholder="请选择性别">
<el-option label="男" value="男" />
<el-option label="女" value="女" />
</el-select>
</el-form-item>
<el-form-item label="单位" prop="examinee_unit">
<el-input v-model="formData.examinee_unit" placeholder="请输入单位" />
</el-form-item>
<el-form-item label="笔试考场" prop="written_exam_room">
<el-input v-model="formData.written_exam_room" placeholder="请输入笔试考场" />
</el-form-item>
<el-form-item label="笔试座位号" prop="written_exam_seat">
<el-input v-model="formData.written_exam_seat" placeholder="请输入笔试座位号" />
</el-form-item>
<el-form-item label="机试考场" prop="computer_exam_room">
<el-input v-model="formData.computer_exam_room" placeholder="请输入机试考场" />
</el-form-item>
<el-form-item label="机试座位号" prop="computer_exam_seat">
<el-input v-model="formData.computer_exam_seat" placeholder="请输入机试座位号" />
</el-form-item>
<el-form-item label="身份证号" prop="examinee_id_card" :rules="[{ required: true, message: '请输入身份证号', trigger: 'blur' }]">
<el-input v-model="formData.examinee_id_card" placeholder="请输入身份证号" />
</el-form-item>
<el-form-item label="准考证号" prop="examinee_admission_ticket" :rules="[{ required: true, message: '请输入准考证号', trigger: 'blur' }]">
<el-input v-model="formData.examinee_admission_ticket" placeholder="请输入准考证号" />
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveExaminee">保存</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'ExamineeManagementView',
data() {
return {
examineeList: [],
loading: false,
dialogVisible: false,
dialogTitle: '添加考生',
isEdit: false,
formData: {
id: null,
examinee_name: '',
examinee_gender: '',
examinee_unit: '',
written_exam_room: '',
written_exam_seat: '',
computer_exam_room: '',
computer_exam_seat: '',
examinee_id_card: '',
examinee_admission_ticket: ''
}
}
},
mounted() {
this.fetchExaminees()
},
methods: {
//
async fetchExaminees() {
this.loading = true
try {
const result = await window.electronAPI.examineeFetchAll() //
this.examineeList = result
} catch (error) {
this.$message.error('获取考生列表失败: ' + error.message)
console.error('获取考生列表失败', error)
} finally {
this.loading = false
}
},
// handleAddExaminee
//
handleAddExaminee() {
console.log('点击了添加考生按钮');
this.isEdit = false;
this.dialogTitle = '添加考生';
this.formData = {
id: null,
examinee_name: '',
examinee_gender: '',
examinee_unit: '',
written_exam_room: '',
written_exam_seat: '',
computer_exam_room: '',
computer_exam_seat: '',
examinee_id_card: '',
examinee_admission_ticket: ''
};
//
this.dialogVisible = true;
console.log('对话框状态:', this.dialogVisible);
},
//
handleEditExaminee(row) {
this.isEdit = true
this.dialogTitle = '编辑考生'
this.formData = { ...row }
this.dialogVisible = true
},
//
async handleSaveExaminee() {
this.$refs.formRef.validate(async (valid) => {
if (valid) {
try {
//
const examineeData = { ...this.formData }
if (this.isEdit) {
console.log(examineeData)
await window.electronAPI.examineeUpdate(examineeData.id, examineeData) //
this.$message.success('考生更新成功')
} else {
await window.electronAPI.examineeCreate(examineeData) //
this.$message.success('考生添加成功')
}
this.dialogVisible = false
this.fetchExaminees() //
} catch (error) {
this.$message.error('保存考生失败: ' + error.message)
console.error('保存考生失败', error)
}
}
})
},
//
handleDeleteExaminee(id) {
this.$confirm(
'确定要删除该考生吗?',
'确认删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(async () => {
try {
await window.electronAPI.examineeDelete(id) //
this.$message.success('考生删除成功')
this.fetchExaminees() //
} catch (error) {
this.$message.error('删除考生失败: ' + error.message)
console.error('删除考生失败', error)
}
})
.catch(() => {
//
})
}
}
}
</script>
<style scoped>
.examinee-management-container {
width: 100%;
height: 100%;
}
.examinee-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.examinee-content {
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
/* 确保表格单元格内容不换行 */
.el-table__cell {
white-space: nowrap;
}
</style>