优化考试排考
This commit is contained in:
@@ -68,17 +68,39 @@ const filteredTasks = computed(() => {
|
||||
const filteredSessions = computed(() => {
|
||||
const keyword = sessionFilter.keyword.trim().toLowerCase()
|
||||
return (selected.value?.sessions ?? []).filter((session: any) => {
|
||||
if (sessionFilter.allocation === 'room' && session.classroomId) return false
|
||||
if (sessionFilter.allocation === 'room' && hasRoomAssignment(session)) return false
|
||||
if (sessionFilter.allocation === 'invigilator' &&
|
||||
session.invigilatorIds.length >= session.requiredInvigilatorCount) return false
|
||||
hasCompleteInvigilators(session)) return false
|
||||
if (sessionFilter.allocation === 'complete' &&
|
||||
(!session.classroomId || session.invigilatorIds.length < session.requiredInvigilatorCount)) return false
|
||||
(!hasRoomAssignment(session) || !hasCompleteInvigilators(session))) return false
|
||||
if (!keyword) return true
|
||||
return [session.taskNumber, session.courseCode, session.courseName, session.taskName]
|
||||
.some((value: any) => String(value ?? '').toLowerCase().includes(keyword))
|
||||
})
|
||||
})
|
||||
|
||||
function sessionRooms(session: any) {
|
||||
return session.examRooms ?? []
|
||||
}
|
||||
function hasRoomAssignment(session: any) {
|
||||
return sessionRooms(session).length > 0 || Boolean(session.classroomId)
|
||||
}
|
||||
function hasCompleteInvigilators(session: any) {
|
||||
const roomAssignments = sessionRooms(session)
|
||||
if (roomAssignments.length > 0) {
|
||||
return roomAssignments.every((room: any) =>
|
||||
room.invigilatorIds.length >= session.requiredInvigilatorCount)
|
||||
}
|
||||
return session.invigilatorIds.length >= session.requiredInvigilatorCount
|
||||
}
|
||||
function sessionInvigilatorNames(session: any) {
|
||||
const roomAssignments = sessionRooms(session)
|
||||
const names = roomAssignments.length > 0
|
||||
? roomAssignments.flatMap((room: any) => room.invigilatorNames)
|
||||
: session.invigilatorNames
|
||||
return Array.from(new Set(names))
|
||||
}
|
||||
|
||||
function timeText(startsAt: string) {
|
||||
return new Date(startsAt).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false })
|
||||
}
|
||||
@@ -144,7 +166,18 @@ async function savePlan() {
|
||||
await load()
|
||||
} catch (error) { ElMessage.error(apiErrorMessage(error)) }
|
||||
}
|
||||
function openSession(existing?: any) {
|
||||
async function openSession(existing?: any) {
|
||||
if (existing && sessionRooms(existing).length > 0) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'修改该教学班的课程或考试时间会清除相关混排考场和座位,需要重新执行考场编排。',
|
||||
'修改混排场次',
|
||||
{ type: 'warning', confirmButtonText: '继续修改' },
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
editingSession.value = existing ?? null
|
||||
selectedTaskIds.value = existing ? [existing.teachingTaskId] : []
|
||||
Object.assign(taskFilter, { keyword: '', collegeId: '', courseNature: '' })
|
||||
@@ -263,8 +296,11 @@ async function autoArrange(mode: 'rooms' | 'invigilators' | 'all') {
|
||||
: '当前计划全部场次'
|
||||
const action = mode === 'rooms' ? '分配考场'
|
||||
: mode === 'invigilators' ? '分配监考教师' : '分配考场和监考教师'
|
||||
const arrangementNotice = mode === 'invigilators'
|
||||
? '已有考场和座位不会改变。'
|
||||
: '系统会按同一课程、同一时间生成完整混排组,并重建相关考场和座位。'
|
||||
await ElMessageBox.confirm(
|
||||
`系统将为${target}${action},已有安排不会被覆盖。`,
|
||||
`系统将为${target}${action}。${arrangementNotice}`,
|
||||
`一键${action}`, { type: 'info', confirmButtonText: '开始分配' })
|
||||
arrangeLoading.value = true
|
||||
const res = await http.post(`/exams/plans/${selected.value.id}/auto-arrange`, {
|
||||
@@ -373,7 +409,7 @@ onMounted(async () => {
|
||||
<div>
|
||||
<span class="section-kicker">EXAMINATION OFFICE</span>
|
||||
<h2>{{ isManager ? '考试安排与考场' : isTeacher ? '我的监考' : '我的考试' }}</h2>
|
||||
<p>{{ isManager ? '基于课表节次安排考试,自动匹配考场与监考教师。' : '查看学校已经正式发布的考试日程。' }}</p>
|
||||
<p>{{ isManager ? '基于课表节次安排考试,同课程教学班混排考场并自动匹配监考教师。' : '查看学校已经正式发布的考试日程。' }}</p>
|
||||
</div>
|
||||
<el-button v-if="isManager" type="primary" :icon="Plus" @click="openPlan">新建考试计划</el-button>
|
||||
<el-button v-else :icon="Refresh" @click="load">刷新日程</el-button>
|
||||
@@ -438,7 +474,7 @@ onMounted(async () => {
|
||||
</template>
|
||||
</div>
|
||||
<div class="exam-timeline">
|
||||
<article v-for="session in filteredSessions" :key="session.id" :class="{ unassigned: !session.classroomId }">
|
||||
<article v-for="session in filteredSessions" :key="session.id" :class="{ unassigned: !hasRoomAssignment(session) }">
|
||||
<time>
|
||||
<el-checkbox
|
||||
v-if="selected.status === 'Draft'"
|
||||
@@ -452,7 +488,14 @@ onMounted(async () => {
|
||||
<span>{{ session.courseCode }} · {{ session.taskNumber }}</span>
|
||||
<h4>{{ session.courseName }}</h4>
|
||||
<p>
|
||||
<template v-if="session.classroomId">{{ session.buildingName }} · {{ session.classroomName }} · {{ session.classroomCapacity }}座</template>
|
||||
<template v-if="sessionRooms(session).length">
|
||||
<span v-for="room in sessionRooms(session)" :key="room.examRoomId" class="exam-room-chip">
|
||||
{{ room.buildingName }} · {{ room.classroomName }}
|
||||
· 本班{{ room.seatCount }}人/全场{{ room.totalSeatCount }}人
|
||||
<el-tag v-if="room.isMixed" size="small" type="success">混排</el-tag>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="session.classroomId">{{ session.buildingName }} · {{ session.classroomName }} · {{ session.classroomCapacity }}座</template>
|
||||
<template v-else><el-tag size="small" type="warning">待分配考场</el-tag></template>
|
||||
· {{ session.studentCount }} 人
|
||||
<template v-if="session.requiredBuildingName"> · 限{{ session.requiredBuildingName }}</template>
|
||||
@@ -461,7 +504,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
<div class="exam-staff">
|
||||
<span>监考</span>
|
||||
<b>{{ session.invigilatorNames.length ? session.invigilatorNames.join('、') : '待分配' }}</b>
|
||||
<b>{{ sessionInvigilatorNames(session).length ? sessionInvigilatorNames(session).join('、') : '待分配' }}</b>
|
||||
<small>{{ timeText(session.startsAt) }}—{{ timeText(session.endsAt) }}</small>
|
||||
</div>
|
||||
<div class="exam-row-actions">
|
||||
@@ -607,6 +650,8 @@ onMounted(async () => {
|
||||
<!-- Roster Drawer -->
|
||||
<el-drawer v-model="rosterDrawer" title="考生名单" size="560px">
|
||||
<el-table v-if="roster" :data="roster.students">
|
||||
<el-table-column prop="buildingName" label="教学楼" width="110" />
|
||||
<el-table-column prop="classroomName" label="考场" width="90" />
|
||||
<el-table-column prop="seatNumber" label="座位号" width="80" />
|
||||
<el-table-column prop="studentNumber" label="学号" width="130" />
|
||||
<el-table-column prop="name" label="姓名" width="90" />
|
||||
@@ -655,6 +700,10 @@ onMounted(async () => {
|
||||
.exam-timeline article.unassigned {
|
||||
border-left-color: #e6a23c;
|
||||
}
|
||||
.exam-room-chip {
|
||||
display: block;
|
||||
margin: 3px 0;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.exam-actions {
|
||||
justify-content: flex-start;
|
||||
|
||||
Reference in New Issue
Block a user