已修改完成,同时解决了“考生名单都是 0 人”的根因。

考生范围统一为“行政班关联学生 + 已选课学生”,并自动去重;考场分配、人数显示、冲突检测、学生考试安排均使用同一口径。[TeachingTaskRosterQuery.cs (line 40)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Teaching/TeachingTaskRosterQuery.cs:40)
新增考场签名单 Excel 导出接口。[ExamsController.cs (line 568)](E:/jiaowu/src/Jiaowu.Api/Controllers/ExamsController.cs:568)
考试计划页面新增“导出考场签名单”按钮。[ExamsView.vue (line 255)](E:/jiaowu/web/src/views/ExamsView.vue:255)
工作簿包含“考场汇总”以及每个考试场次独立的 A4 签到表,字段包括座位号、学号、姓名、行政班、考生签名和备注。[ExamSignInWorkbookExporter.cs (line 12)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Exams/ExamSignInWorkbookExporter.cs:12)
未分配考场或监考教师时,也能导出并显示“待分配”。
This commit is contained in:
2026-07-27 18:24:32 +08:00 Unverified
parent 2e422acd19
commit fb2acc6751
9 changed files with 856 additions and 71 deletions
+24 -1
View File
@@ -1,7 +1,8 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { Plus, Promotion, Refresh, UserFilled, Setting } from '@element-plus/icons-vue'
import { Download, Plus, Promotion, Refresh, UserFilled, Setting } from '@element-plus/icons-vue'
import http, { apiErrorMessage } from '../api/http'
import { downloadApiFile } from '../api/excel'
import { useAuthStore } from '../stores/auth'
import { academicTermLabel, academicTermOptionClass, defaultAcademicTermId } from '../utils/academicTerms'
@@ -20,6 +21,7 @@ const buildings = ref<any[]>([])
const timeSlots = ref<any[]>([])
const loading = ref(false)
const arrangeLoading = ref(false)
const exportLoading = ref(false)
const planDialog = ref(false)
const sessionDialog = ref(false)
const editingSession = ref<any | null>(null)
@@ -250,6 +252,21 @@ async function publishPlan() {
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
}
}
async function exportSignInSheets() {
if (!selected.value) return
exportLoading.value = true
try {
await downloadApiFile(
`/exams/plans/${selected.value.id}/sign-in-sheets.xlsx`,
`考场签名单-${selected.value.name}.xlsx`,
)
ElMessage.success('考场签名单已导出')
} catch (error) {
ElMessage.error(apiErrorMessage(error))
} finally {
exportLoading.value = false
}
}
async function showRoster(row: any) {
try {
roster.value = (await http.get(`/exams/sessions/${row.id}/roster`)).data
@@ -317,6 +334,12 @@ onMounted(async () => {
<p>{{ selected.termName }} · {{ selected.sessions.length }} 个考试场次</p>
</div>
<div class="exam-actions">
<el-button
:icon="Download"
:loading="exportLoading"
:disabled="selected.sessions.length === 0"
@click="exportSignInSheets"
>导出考场签名单</el-button>
<el-button v-if="selected.status === 'Draft'" @click="autoArrange('rooms')" :loading="arrangeLoading">一键分配考场</el-button>
<el-button v-if="selected.status === 'Draft'" @click="autoArrange('invigilators')" :loading="arrangeLoading">一键分配监考</el-button>
<el-button v-if="selected.status === 'Draft'" :icon="Setting" @click="autoArrange('all')" :loading="arrangeLoading">一键完成</el-button>