考生登录和ExamineeHome页
This commit is contained in:
parent
fd2a4bc6eb
commit
3f61b5eb72
41
src/components/user/ExamineeLayout.vue
Normal file
41
src/components/user/ExamineeLayout.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<el-container class="examinee-layout-container">
|
||||
<!-- Header -->
|
||||
<Header />
|
||||
|
||||
<!-- Main content area -->
|
||||
<el-main class="content-container">
|
||||
<router-view />
|
||||
</el-main>
|
||||
|
||||
<!-- Footer -->
|
||||
<Footer />
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ExamineeLayout',
|
||||
components: {
|
||||
Header: require('../common/Header.vue').default,
|
||||
Footer: require('../common/Footer.vue').default,
|
||||
ElContainer: require('element-ui').Container,
|
||||
ElMain: require('element-ui').Main
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.examinee-layout-container,
|
||||
.el-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
@ -52,7 +52,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
questionRemove: (questionId) => ipcRenderer.invoke('question-remove', questionId),
|
||||
// 添加新的questionDelete方法,调用主进程中已注册的'question-delete'通道
|
||||
questionDelete: (questionId) => ipcRenderer.invoke('question-delete', questionId),
|
||||
questionGetStatistics: () => ipcRenderer.invoke('question-get-statistics'),
|
||||
// 修改后
|
||||
questionGetStatistics: () => ipcRenderer.invoke('question-get-count-and-score'),
|
||||
questionGetQuestionById: (questionId) => ipcRenderer.invoke('question-get-question-by-id', questionId),
|
||||
|
||||
// 考试服务相关接口
|
||||
|
@ -2,11 +2,13 @@ import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import WelcomeView from '../views/WelcomeView.vue'
|
||||
import AdminLayout from '../components/admin/AdminLayout.vue'
|
||||
import ExamineeLayout from '../components/user/ExamineeLayout.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'
|
||||
// 导入考生相关组件
|
||||
import ExamineeHomeView from '../views/user/ExamineeHomeView.vue'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
@ -20,45 +22,81 @@ const routes = [
|
||||
path: '/admin',
|
||||
name: 'AdminLayout',
|
||||
component: AdminLayout,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
},
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{
|
||||
path: 'home',
|
||||
name: 'AdminHome',
|
||||
component: AdminHomeView
|
||||
},
|
||||
{
|
||||
path: 'question',
|
||||
name: 'QuestionManagement',
|
||||
component: QuestionManagementView
|
||||
},
|
||||
{
|
||||
path: 'examinee',
|
||||
name: 'ExamineeManagement',
|
||||
component: ExamineeManagementView
|
||||
},
|
||||
{
|
||||
path: 'exam',
|
||||
name: 'ExamManagement',
|
||||
component: ExamManagementView
|
||||
}
|
||||
// 可以在这里添加更多子路由
|
||||
{ path: 'home', name: 'AdminHome', component: AdminHomeView },
|
||||
{ path: 'question', name: 'QuestionManagement', component: QuestionManagementView },
|
||||
{ path: 'examinee', name: 'ExamineeManagement', component: ExamineeManagementView },
|
||||
{ path: 'exam', name: 'ExamManagement', component: ExamManagementView }
|
||||
]
|
||||
},
|
||||
// 添加考生相关路由
|
||||
{
|
||||
path: '/examinee',
|
||||
name: 'Examinee',
|
||||
component: ExamineeLayout,
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{ path: 'home', name: 'ExamineeHome', component: ExamineeHomeView },
|
||||
// 可以在这里添加更多考生相关的路由
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'hash', // 将history改为hash
|
||||
base: process.env.BASE_URL,
|
||||
routes
|
||||
})
|
||||
|
||||
// 添加路由守卫
|
||||
// 添加路由守卫,检查登录状态
|
||||
router.beforeEach((to, from, next) => {
|
||||
// 这里可以添加实际的认证逻辑
|
||||
next()
|
||||
const store = require('../store/index.js').default
|
||||
|
||||
// 检查路由是否需要认证
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
// 检查是否已登录
|
||||
if (!store.state.isLoggedIn) {
|
||||
// 如果未登录且不是去欢迎页,则重定向到欢迎页
|
||||
if (to.path !== '/') {
|
||||
next({
|
||||
path: '/',
|
||||
query: { redirect: to.fullPath }
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
// 不需要认证的路由直接通过
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
// 添加路由守卫,检查登录状态
|
||||
router.beforeEach((to, from, next) => {
|
||||
const store = require('../store/index.js').default
|
||||
|
||||
// 检查路由是否需要认证
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
// 检查是否已登录
|
||||
if (!store.state.isLoggedIn) {
|
||||
// 如果未登录且不是去欢迎页,则重定向到欢迎页
|
||||
if (to.path !== '/') {
|
||||
next({
|
||||
path: '/',
|
||||
query: { redirect: to.fullPath }
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
// 不需要认证的路由直接通过
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
|
@ -6,16 +6,28 @@ Vue.use(Vuex)
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
examinee: null, // 保存考生信息
|
||||
isLoggedIn: false // 登录状态
|
||||
admin: null, // 保存管理员信息
|
||||
isLoggedIn: false, // 通用登录状态
|
||||
userType: null // 用户类型: 'examinee' 或 'admin'
|
||||
},
|
||||
mutations: {
|
||||
setExaminee(state, examineeInfo) {
|
||||
state.examinee = examineeInfo
|
||||
state.admin = null
|
||||
state.isLoggedIn = true
|
||||
state.userType = 'examinee'
|
||||
},
|
||||
clearExaminee(state) {
|
||||
setAdmin(state, adminInfo) {
|
||||
state.admin = adminInfo
|
||||
state.examinee = null
|
||||
state.isLoggedIn = true
|
||||
state.userType = 'admin'
|
||||
},
|
||||
clearUser(state) {
|
||||
state.examinee = null
|
||||
state.admin = null
|
||||
state.isLoggedIn = false
|
||||
state.userType = null
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
@ -7,14 +7,16 @@
|
||||
<el-main>
|
||||
<div class="d-flex align-items-center justify-content-center p-4" style="padding: 0; width: 600px;">
|
||||
<!-- 数据库初始化提示卡片 -->
|
||||
<div class="login-card bg-white rounded shadow-lg p-5 w-100 max-w-md" id="init-section" v-show="!isDatabaseInitialized">
|
||||
<div class="login-card bg-white rounded shadow-lg p-5 w-100 max-w-md" id="init-section"
|
||||
v-show="!isDatabaseInitialized">
|
||||
<div class="text-center">
|
||||
<div class="mb-6">
|
||||
<i class="fa fa-database text-primary" style="font-size: 64px;"></i>
|
||||
</div>
|
||||
<h2 class="display-6 mb-4">系统未初始化</h2>
|
||||
<p class="fs-5 mb-6 text-muted">请点击下方按钮进行系统初始化,初始化完成后将自动显示登录界面</p>
|
||||
<button id="initialize-db" @click="initializeDatabase" class="btn btn-primary px-8 py-3 fs-5" :disabled="isInitializing">
|
||||
<button id="initialize-db" @click="initializeDatabase" class="btn btn-primary px-8 py-3 fs-5"
|
||||
:disabled="isInitializing">
|
||||
<i v-if="isInitializing" class="fa fa-spinner fa-spin me-2"></i>
|
||||
<i v-else class="fa fa-refresh me-2"></i>
|
||||
{{ isInitializing ? '初始化中...' : '数据初始化' }}
|
||||
@ -23,17 +25,20 @@
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap登录卡片 -->
|
||||
<div class="login-card bg-white rounded shadow-lg p-5 w-100 max-w-md" id="login-section" style="height: 500px;" v-show="isDatabaseInitialized">
|
||||
<div class="login-card bg-white rounded shadow-lg p-5 w-100 max-w-md" id="login-section"
|
||||
style="height: 500px;" v-show="isDatabaseInitialized">
|
||||
<!-- 登录类型切换标签页 -->
|
||||
<ul class="nav nav-tabs fs-4" id="loginTab" role="tablist">
|
||||
<li class="nav-item flex-fill" role="presentation">
|
||||
<button class="nav-link rounded-0 active w-100" id="exam-tab" data-toggle="tab" data-target="#exam-login" type="button" role="tab" aria-controls="exam-login" aria-selected="true">
|
||||
<button class="nav-link rounded-0 active w-100" id="exam-tab" data-toggle="tab"
|
||||
data-target="#exam-login" type="button" role="tab" aria-controls="exam-login" aria-selected="true">
|
||||
<i class="fa fa-graduation-cap me-2"></i>
|
||||
考生登录
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item flex-fill" role="presentation">
|
||||
<button class="nav-link rounded-0 w-100" id="admin-tab" data-toggle="tab" data-target="#admin-login" type="button" role="tab" aria-controls="admin-login" aria-selected="false">
|
||||
<button class="nav-link rounded-0 w-100" id="admin-tab" data-toggle="tab" data-target="#admin-login"
|
||||
type="button" role="tab" aria-controls="admin-login" aria-selected="false">
|
||||
<i class="fa fa-cog me-2"></i>
|
||||
系统管理
|
||||
</button>
|
||||
@ -41,7 +46,8 @@
|
||||
</ul>
|
||||
|
||||
<!-- 登录表单内容 -->
|
||||
<div class="tab-content fs-5 p-4 border border-left border-right border-bottom" id="loginTabContent" style="height: calc(100% - 60px);">
|
||||
<div class="tab-content fs-5 p-4 border border-left border-right border-bottom" id="loginTabContent"
|
||||
style="height: calc(100% - 60px);">
|
||||
<!-- 考生登录表单 -->
|
||||
<div class="h-100 tab-pane fade show active" id="exam-login" role="tabpanel" aria-labelledby="exam-tab">
|
||||
<form @submit.prevent="handleExamineeLogin" class="d-flex flex-column h-100">
|
||||
@ -60,7 +66,8 @@
|
||||
<span class="input-group-text">
|
||||
<font-awesome-icon :icon="['fas', 'key']" />
|
||||
</span>
|
||||
<input type="text" class="form-control" id="examineeAdmissionTicket" v-model="examineeAdmissionTicket" required>
|
||||
<input type="text" class="form-control" id="examineeAdmissionTicket"
|
||||
v-model="examineeAdmissionTicket" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex-grow-1 d-flex flex-column justify-content-end">
|
||||
@ -109,6 +116,8 @@
|
||||
import Header from '../components/common/Header.vue'
|
||||
import Footer from '../components/common/Footer.vue'
|
||||
import { Message } from 'element-ui'
|
||||
// 导入Vuex store
|
||||
import store from '../store/index.js'
|
||||
|
||||
export default {
|
||||
name: 'WelcomeView',
|
||||
@ -116,7 +125,7 @@ export default {
|
||||
Header,
|
||||
Footer
|
||||
},
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
examineeIdCard: '', // 身份证号
|
||||
examineeAdmissionTicket: '', // 准考证号
|
||||
@ -126,158 +135,179 @@ export default {
|
||||
isLoading: false // 添加加载状态
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
mounted () {
|
||||
this.checkDatabaseStatus()
|
||||
},
|
||||
methods: {
|
||||
async checkDatabaseStatus() {
|
||||
async checkDatabaseStatus () {
|
||||
try {
|
||||
console.log('组件挂载 - 开始检查数据库初始化状态');
|
||||
const initialized = await window.electronAPI.checkDatabaseInitialized();
|
||||
console.log('组件挂载 - 数据库初始化状态检查完成:', initialized);
|
||||
this.isDatabaseInitialized = initialized;
|
||||
console.log('组件挂载 - 开始检查数据库初始化状态')
|
||||
const initialized = await window.electronAPI.checkDatabaseInitialized()
|
||||
console.log('组件挂载 - 数据库初始化状态检查完成:', initialized)
|
||||
this.isDatabaseInitialized = initialized
|
||||
} catch (error) {
|
||||
console.error('检查数据库初始化状态失败:', error);
|
||||
Message.error('检查数据库初始化状态失败,请重试');
|
||||
console.error('检查数据库初始化状态失败:', error)
|
||||
Message.error('检查数据库初始化状态失败,请重试')
|
||||
}
|
||||
},
|
||||
|
||||
async initializeDatabase() {
|
||||
async initializeDatabase () {
|
||||
try {
|
||||
console.log('初始化数据库 - 开始');
|
||||
this.isInitializing = true;
|
||||
Message.info('开始初始化数据库...');
|
||||
console.log('初始化数据库 - 开始')
|
||||
this.isInitializing = true
|
||||
Message.info('开始初始化数据库...')
|
||||
|
||||
const result = await window.electronAPI.initializeDatabase();
|
||||
console.log('初始化数据库 - 结果:', result);
|
||||
const result = await window.electronAPI.initializeDatabase()
|
||||
console.log('初始化数据库 - 结果:', result)
|
||||
|
||||
// 修复:同时处理布尔值true和带success属性的对象
|
||||
if (result === true || (result && result.success)) {
|
||||
Message.success('数据库初始化成功!');
|
||||
console.log('初始化数据库 - 成功,更新初始化状态');
|
||||
this.isDatabaseInitialized = true;
|
||||
Message.success('数据库初始化成功!')
|
||||
console.log('初始化数据库 - 成功,更新初始化状态')
|
||||
this.isDatabaseInitialized = true
|
||||
} else {
|
||||
const errorMessage = result && result.error ? result.error : '未知错误';
|
||||
Message.error(`数据库初始化失败: ${errorMessage}`);
|
||||
console.error('初始化数据库 - 失败:', errorMessage);
|
||||
const errorMessage = result && result.error ? result.error : '未知错误'
|
||||
Message.error(`数据库初始化失败: ${errorMessage}`)
|
||||
console.error('初始化数据库 - 失败:', errorMessage)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('数据库初始化失败:', error);
|
||||
Message.error(`数据库初始化失败: ${error.message || '未知错误'}`);
|
||||
console.error('数据库初始化失败:', error)
|
||||
Message.error(`数据库初始化失败: ${error.message || '未知错误'}`)
|
||||
} finally {
|
||||
console.log('初始化数据库 - 结束');
|
||||
this.isInitializing = false;
|
||||
console.log('初始化数据库 - 结束')
|
||||
this.isInitializing = false
|
||||
}
|
||||
},
|
||||
|
||||
async handleExamineeLogin() {
|
||||
async handleExamineeLogin () {
|
||||
console.log('考生登录 - 开始', {
|
||||
examineeIdCard: this.examineeIdCard,
|
||||
examineeAdmissionTicket: this.examineeAdmissionTicket
|
||||
});
|
||||
})
|
||||
|
||||
// 清除首尾空格
|
||||
const idCard = this.examineeIdCard.trim();
|
||||
const admissionTicket = this.examineeAdmissionTicket.trim();
|
||||
const idCard = this.examineeIdCard.trim()
|
||||
const admissionTicket = this.examineeAdmissionTicket.trim()
|
||||
|
||||
// 前端验证
|
||||
if (!idCard || !admissionTicket) {
|
||||
console.warn('考生登录 - 验证失败: 身份证号和准考证号不能为空');
|
||||
Message.error('请输入身份证号和准考证号');
|
||||
return;
|
||||
console.warn('考生登录 - 验证失败: 身份证号和准考证号不能为空')
|
||||
Message.error('请输入身份证号和准考证号')
|
||||
return
|
||||
}
|
||||
|
||||
// 设置加载状态
|
||||
this.isLoading = true;
|
||||
this.isLoading = true
|
||||
|
||||
try {
|
||||
// 调用登录API
|
||||
const result = await window.electronAPI.userLogin(idCard, admissionTicket);
|
||||
console.log(result);
|
||||
// 调用登录API - 使用正确的参数格式
|
||||
const result = await window.electronAPI.userLogin({ idCard, admissionTicket })
|
||||
console.log('考生登录 - 结果:', result)
|
||||
|
||||
if (result && result.id) {
|
||||
console.log('考生登录 - 成功', result);
|
||||
// 保存用户信息到store - 在Vue 2中通常使用Vuex
|
||||
if (this.$store && this.$store.commit) {
|
||||
this.$store.commit('setExaminee', result);
|
||||
}
|
||||
Message.success('登录成功');
|
||||
console.log('考生登录 - 成功', result)
|
||||
// 保存用户信息到store
|
||||
this.$store.commit('setExaminee', result)
|
||||
Message.success('登录成功')
|
||||
// 跳转到考生首页
|
||||
this.$router.push('/examinee/home');
|
||||
this.$router.push('/examinee/home')
|
||||
} else {
|
||||
console.warn('考生登录 - 失败:', result);
|
||||
Message.error(result.error || '登录失败,请检查身份证号和准考证号');
|
||||
console.warn('考生登录 - 失败:', result)
|
||||
Message.error(result && result.error ? result.error : '登录失败,请检查身份证号和准考证号')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('考生登录 - 异常:', error);
|
||||
Message.error('登录失败,请重试');
|
||||
console.error('考生登录 - 异常:', error)
|
||||
Message.error(`登录失败: ${error.message || '未知错误'}`)
|
||||
} finally {
|
||||
// 无论成功失败,都关闭加载状态
|
||||
this.isLoading = false;
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
async handleAdminLogin() {
|
||||
console.log('管理员登录 - 开始', { passwordLength: this.adminPassword.length });
|
||||
// 修改handleAdminLogin方法
|
||||
async handleAdminLogin () {
|
||||
console.log('管理员登录 - 开始', { passwordLength: this.adminPassword.length })
|
||||
|
||||
// 前端密码验证
|
||||
const passwordError = this.validateAdminPassword(this.adminPassword);
|
||||
const passwordError = this.validateAdminPassword(this.adminPassword)
|
||||
if (passwordError) {
|
||||
console.warn('管理员登录 - 验证失败:', passwordError);
|
||||
const errorElement = document.getElementById('admin-error-message');
|
||||
console.warn('管理员登录 - 验证失败:', passwordError)
|
||||
const errorElement = document.getElementById('admin-error-message')
|
||||
if (errorElement) {
|
||||
errorElement.textContent = passwordError;
|
||||
errorElement.style.display = 'block';
|
||||
errorElement.textContent = passwordError
|
||||
errorElement.style.display = 'block'
|
||||
}
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// 清除之前的错误信息
|
||||
const errorElement = document.getElementById('admin-error-message');
|
||||
const errorElement = document.getElementById('admin-error-message')
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'none';
|
||||
errorElement.style.display = 'none'
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('管理员登录 - 调用主进程登录方法');
|
||||
console.log('管理员登录 - 调用主进程登录方法')
|
||||
// 使用新的adminLogin方法
|
||||
const result = await window.electronAPI.adminLogin({
|
||||
username: 'admin',
|
||||
password: this.adminPassword
|
||||
});
|
||||
console.log('管理员登录 - 登录结果:', result);
|
||||
})
|
||||
console.log('管理员登录 - 登录结果:', result)
|
||||
|
||||
if (result && result.success) {
|
||||
console.log('管理员登录 - 成功,跳转到管理首页');
|
||||
Message.success('登录成功');
|
||||
this.$router.push('/admin/home');
|
||||
console.log('管理员登录 - 成功,更新store状态并跳转')
|
||||
// 使用新的setAdmin mutation
|
||||
this.$store.commit('setAdmin', { username: 'admin' })
|
||||
Message.success('登录成功')
|
||||
this.$router.push('/admin/home')
|
||||
} else {
|
||||
const errorMessage = result && result.message ? result.message : '登录失败';
|
||||
console.warn('管理员登录 - 失败:', errorMessage);
|
||||
Message.error(errorMessage);
|
||||
const errorMessage = result && result.message ? result.message : '登录失败'
|
||||
console.warn('管理员登录 - 失败:', errorMessage)
|
||||
Message.error(errorMessage)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('管理员登录 - 异常:', error);
|
||||
Message.error(`登录异常: ${error.message || '未知错误'}`);
|
||||
console.error('管理员登录 - 异常:', error)
|
||||
Message.error(`登录异常: ${error.message || '未知错误'}`)
|
||||
}
|
||||
},
|
||||
|
||||
validateAdminPassword(password) {
|
||||
validateAdminPassword (password) {
|
||||
// 检查密码是否为空
|
||||
if (!password) {
|
||||
return '请输入管理员密码';
|
||||
return '请输入管理员密码'
|
||||
}
|
||||
// 检查密码长度
|
||||
if (password.length < 4 || password.length > 32) {
|
||||
return '密码长度必须在4-32个字符之间';
|
||||
return '密码长度必须在4-32个字符之间'
|
||||
}
|
||||
// 检查密码是否只包含英文大小写和数字
|
||||
const regex = /^[A-Za-z0-9]+$/;
|
||||
const regex = /^[A-Za-z0-9]+$/
|
||||
if (!regex.test(password)) {
|
||||
return '密码只能包含英文大小写字母和数字';
|
||||
return '密码只能包含英文大小写字母和数字'
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null
|
||||
},
|
||||
// 修改exitExam方法
|
||||
async exitExam () {
|
||||
this.$confirm(
|
||||
'确定要退出考试吗?',
|
||||
'退出确认',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
// 完全清除store中的数据
|
||||
this.$store.commit('clearUser')
|
||||
// 重定向到欢迎页
|
||||
this.$router.push('/')
|
||||
}).catch(() => {
|
||||
// 用户取消操作
|
||||
console.log('用户取消退出')
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -311,9 +341,12 @@ export default {
|
||||
|
||||
.main-background {
|
||||
background-image: url('../assets/bg.jpeg');
|
||||
background-size: 100% 100%; /* 拉伸背景图以填满容器 */
|
||||
background-position: center; /* 保持居中 */
|
||||
background-repeat: no-repeat; /* 避免重复 */
|
||||
background-size: 100% 100%;
|
||||
/* 拉伸背景图以填满容器 */
|
||||
background-position: center;
|
||||
/* 保持居中 */
|
||||
background-repeat: no-repeat;
|
||||
/* 避免重复 */
|
||||
}
|
||||
|
||||
/* 适配移动设备 */
|
||||
|
413
src/views/user/ExamineeHomeView.vue
Normal file
413
src/views/user/ExamineeHomeView.vue
Normal file
@ -0,0 +1,413 @@
|
||||
<template>
|
||||
<!-- 添加外层容器确保占满整个空间 -->
|
||||
<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>
|
||||
<!-- 考生信息部分 -->
|
||||
<el-descriptions class="margin-top" :column="3" :size="size" border>
|
||||
<el-descriptions-item>
|
||||
<template slot="label">
|
||||
<div class="cell-item">
|
||||
<i class="fa fa-user mr-2" :style="iconStyle"></i>
|
||||
姓名
|
||||
</div>
|
||||
</template>
|
||||
{{ examinee && examinee.examinee_name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template slot="label">
|
||||
<div class="cell-item">
|
||||
<i class="fa fa-id-card mr-2" :style="iconStyle"></i>
|
||||
身份证号
|
||||
</div>
|
||||
</template>
|
||||
{{ formatIdCard(examinee && examinee.examinee_id_card) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template slot="label">
|
||||
<div class="cell-item">
|
||||
<i class="fa fa-ticket mr-2" :style="iconStyle"></i>
|
||||
准考证号
|
||||
</div>
|
||||
</template>
|
||||
{{ examinee && examinee.examinee_admission_ticket }}
|
||||
</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>
|
||||
<div class="space-y-4">
|
||||
<el-descriptions class="margin-top" :column="2" :size="size" border>
|
||||
<el-descriptions-item>
|
||||
<template slot="label">
|
||||
<div class="cell-item">
|
||||
<i class="fa fa-clock-o mr-2" :style="iconStyle"></i>
|
||||
考试时长
|
||||
</div>
|
||||
</template>
|
||||
{{ lastExam && lastExam.exam_minutes }}分钟
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template slot="label">
|
||||
<div class="cell-item">
|
||||
<i class="fa fa-hourglass-half mr-2" :style="iconStyle"></i>
|
||||
最短考试时长
|
||||
</div>
|
||||
</template>
|
||||
{{ lastExam && lastExam.exam_minutes_min }}分钟
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template slot="label">
|
||||
<div class="cell-item">
|
||||
<i class="fa fa-list-ol mr-2" :style="iconStyle"></i>
|
||||
考题数量
|
||||
</div>
|
||||
</template>
|
||||
<span class="text-gray-500">{{ totalQuestions }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template slot="label">
|
||||
<div class="cell-item">
|
||||
<i class="fa fa-list-ol mr-2" :style="iconStyle"></i>
|
||||
考试总分
|
||||
</div>
|
||||
</template>
|
||||
<span class="text-gray-500">{{ totalScore }}</span>
|
||||
</el-descriptions-item>
|
||||
</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>
|
||||
<div class="space-y-4">
|
||||
<div v-if="!examLoading && examNotices.length > 0" class="space-y-2 pl-4">
|
||||
<ol class="list-decimal pl-4 space-y-2">
|
||||
<li v-for="(notice, index) in examNotices" :key="index" class="text-gray-700">
|
||||
{{ notice }}
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div v-else-if="examLoading" class="text-center text-gray-500 py-4">
|
||||
加载考试信息中...
|
||||
</div>
|
||||
<div v-else class="text-center text-gray-500 py-4">
|
||||
暂无考试须知
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-6">
|
||||
<el-button type="info" @click="exitExam">返回</el-button>
|
||||
<el-button type="primary" @click="startExam" :loading="isLoading">开始考试</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ExamineeHomeView',
|
||||
components: {
|
||||
ElDescriptions: require('element-ui').Descriptions,
|
||||
ElDescriptionsItem: require('element-ui').DescriptionsItem,
|
||||
ElDivider: require('element-ui').Divider,
|
||||
ElButton: require('element-ui').Button
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
size: 'default',
|
||||
lastExam: null,
|
||||
examLoading: true,
|
||||
examNotices: [],
|
||||
totalQuestions: 0,
|
||||
totalScore: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
examinee() {
|
||||
return this.$store.state.examinee
|
||||
},
|
||||
isLoggedIn() {
|
||||
return this.$store.state.isLoggedIn
|
||||
},
|
||||
iconStyle() {
|
||||
const marginMap = {
|
||||
large: '8px',
|
||||
default: '6px',
|
||||
small: '4px'
|
||||
}
|
||||
return {
|
||||
marginRight: marginMap[this.size] || marginMap.default
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
lastExam(newValue) {
|
||||
if (newValue) {
|
||||
this.examLoading = false
|
||||
}
|
||||
},
|
||||
isLoggedIn(newVal) {
|
||||
if (!newVal) {
|
||||
// 如果未登录,重定向到欢迎页
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 检查是否已登录
|
||||
if (!this.isLoggedIn) {
|
||||
this.$router.push('/')
|
||||
return
|
||||
}
|
||||
|
||||
this.fetchLastExam()
|
||||
this.getQuestionsCountAndScore()
|
||||
},
|
||||
methods: {
|
||||
// 所有方法保持不变
|
||||
// 格式化身份证号(第11-15位替换为*)
|
||||
formatIdCard(idCard) {
|
||||
if (!idCard || idCard.length !== 18) return idCard
|
||||
return idCard.substring(0, 9) + '*****' + idCard.substring(14)
|
||||
},
|
||||
|
||||
// 格式化考试时长(分钟转小时)
|
||||
formatExamDuration(minutes) {
|
||||
if (!minutes) return '未知'
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const mins = minutes % 60
|
||||
return `${hours}小时${mins}分钟`
|
||||
},
|
||||
|
||||
// 得到考题数量和总分
|
||||
async getQuestionsCountAndScore() {
|
||||
try {
|
||||
// 注意:这里需要确认接口是否存在
|
||||
// 如果不存在,可能需要创建或修改现有接口
|
||||
const result = await window.electronAPI.questionGetStatistics()
|
||||
this.totalQuestions = result.totalQuestions || 0
|
||||
this.totalScore = result.totalScore || 0
|
||||
} catch (error) {
|
||||
console.error('获取考题数量和总分失败:', error)
|
||||
this.$message.error(`获取考题数量和总分失败: ${error.message || '未知错误'}`)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取最新考试信息
|
||||
async fetchLastExam() {
|
||||
this.examLoading = true
|
||||
try {
|
||||
// 调用Electron API获取最新考试信息
|
||||
const examData = await window.electronAPI.examFetchLast()
|
||||
this.lastExam = examData
|
||||
|
||||
// 解析考试须知数组
|
||||
if (this.lastExam && this.lastExam.exam_notice) {
|
||||
try {
|
||||
// 尝试解析JSON字符串
|
||||
if (typeof this.lastExam.exam_notice === 'string') {
|
||||
this.examNotices = JSON.parse(this.lastExam.exam_notice)
|
||||
} else if (Array.isArray(this.lastExam.exam_notice)) {
|
||||
this.examNotices = this.lastExam.exam_notice
|
||||
} else {
|
||||
this.examNotices = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('解析考试须知失败,使用原始数据:', error)
|
||||
this.examNotices = [this.lastExam.exam_notice]
|
||||
}
|
||||
} else {
|
||||
this.examNotices = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取考试信息失败:', error)
|
||||
this.$message.error(`获取考试信息失败: ${error.message || '未知错误'}`)
|
||||
} finally {
|
||||
this.examLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 退出考试
|
||||
exitExam() {
|
||||
this.$confirm(
|
||||
'确定要退出考试吗?',
|
||||
'退出确认',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
// 完全清除store中的数据
|
||||
this.$store.commit('clearExaminee')
|
||||
// 重定向到欢迎页
|
||||
this.$router.push('/')
|
||||
}).catch(() => {
|
||||
// 用户取消操作
|
||||
console.log('用户取消退出')
|
||||
})
|
||||
},
|
||||
|
||||
// 开始考试方法
|
||||
async startExam() {
|
||||
if (!this.lastExam) {
|
||||
this.$message.warning('请先获取考试信息')
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.examinee) {
|
||||
this.$message.warning('未获取到考生信息')
|
||||
return
|
||||
}
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
try {
|
||||
console.log('开始生成试卷...')
|
||||
|
||||
// 创建可序列化的考生数据对象
|
||||
const examineeData = {
|
||||
id: this.examinee.id,
|
||||
examinee_name: this.examinee.examinee_name || '',
|
||||
examinee_id_card: this.examinee.examinee_id_card || '',
|
||||
examinee_admission_ticket: this.examinee.examinee_admission_ticket || '',
|
||||
examinee_gender: this.examinee.examinee_gender || '',
|
||||
examinee_unit: this.examinee.examinee_unit || '',
|
||||
written_exam_room: this.examinee.written_exam_room || '',
|
||||
written_exam_seat: this.examinee.written_exam_seat || '',
|
||||
computer_exam_room: this.examinee.computer_exam_room || '',
|
||||
computer_exam_seat: this.examinee.computer_exam_seat || ''
|
||||
}
|
||||
|
||||
// 使用JSON序列化/反序列化确保对象可克隆
|
||||
const examData = JSON.parse(JSON.stringify({
|
||||
id: this.lastExam.id,
|
||||
exam_name: this.lastExam.exam_name || '',
|
||||
exam_description: this.lastExam.exam_description || '',
|
||||
exam_minutes: this.lastExam.exam_minutes || 0,
|
||||
exam_minutes_min: this.lastExam.exam_minutes_min || 0,
|
||||
exam_notice: this.lastExam.exam_notice || []
|
||||
}))
|
||||
|
||||
// 调用生成试卷API
|
||||
const result = await window.electronAPI.examingGeneratePaper({
|
||||
examineeId: examineeData.id,
|
||||
examId: examData.id
|
||||
})
|
||||
|
||||
if (result && result.success) {
|
||||
console.log('生成试卷成功:', result)
|
||||
// 由于Vuex store中没有setPaper方法,这里可以考虑添加或者直接传递参数
|
||||
this.$alert(
|
||||
'已完成组卷,点击"进入考试"开始答题',
|
||||
'组卷完成',
|
||||
{
|
||||
confirmButtonText: '进入考试',
|
||||
type: 'success'
|
||||
}
|
||||
).then(() => {
|
||||
// 使用命名路由传递参数
|
||||
this.$router.push({
|
||||
name: 'examinee-examing',
|
||||
params: { paperId: result.data.id }
|
||||
})
|
||||
})
|
||||
} else {
|
||||
console.error('生成试卷失败:', result)
|
||||
this.$message.error(`生成试卷失败: ${result.message || '未知错误'}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('生成试卷异常:', error)
|
||||
this.$message.error(`无法生成试卷: ${error.message || '未知错误'}`)
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 自定义样式 */
|
||||
.bg-primary {
|
||||
background-color: #1E88E5 !important;
|
||||
/* 蓝色主题,与WelcomeView保持一致 */
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #1E88E5 !important;
|
||||
}
|
||||
|
||||
/* 确保容器占满可用空间 */
|
||||
.student-home-container {
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 内容包装器,用于居中卡片并使其可扩展 */
|
||||
.content-wrapper {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 考试卡片样式 - 响应式设计 */
|
||||
.exam-card {
|
||||
width: 100%;
|
||||
min-height: calc(100vh - 4rem);
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 适配中小屏幕 */
|
||||
@media (max-width: 768px) {
|
||||
.exam-card {
|
||||
max-width: 100%;
|
||||
min-height: calc(100vh - 2rem);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 适配大屏幕 - 适当增加最大宽度但保留边距 */
|
||||
@media (min-width: 1200px) {
|
||||
.exam-card {
|
||||
max-width: 80vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* 适配超大屏幕 - 进一步增加最大宽度 */
|
||||
@media (min-width: 1600px) {
|
||||
.exam-card {
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
}
|
||||
|
||||
.cell-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.margin-top {
|
||||
margin-top: 20px !important;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user