考试管理

This commit is contained in:
chenqiang 2025-09-01 09:48:57 +08:00
parent 96476e6127
commit b6bc4b3a04
5 changed files with 13817 additions and 8388 deletions

21833
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -35,7 +35,7 @@ async function createWindow() {
width: 800, // 默认宽度(实际会被最大化覆盖)
height: 600, // 默认高度(实际会被最大化覆盖)
show: false, // 先隐藏窗口,避免闪烁
frame: false, // 无边框窗口(可选,根据需求决定是否保留)
// frame: false, // 无边框窗口(可选,根据需求决定是否保留)
webPreferences: {
// 改为使用绝对路径解析
preload: require('path').join(process.cwd(), 'src/preload.js'),

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

@ -4,7 +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 ExamineeManagementView from '../views/admin/ExamineeManagementView.vue'
// 添加考试管理组件导入
import ExamManagementView from '../views/admin/ExamManagementView.vue'
Vue.use(VueRouter)
@ -33,9 +35,14 @@ const routes = [
component: QuestionManagementView
},
{
path: 'examinee', // 添加考生管理路由
path: 'examinee',
name: 'ExamineeManagement',
component: ExamineeManagementView
},
{
path: 'exam',
name: 'ExamManagement',
component: ExamManagementView
}
// 可以在这里添加更多子路由
]

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>