点名、成绩册统一读取“行政班在籍学生 ∪ 已选课学生”,不再错误显示 0 人。

教师选课名单改为展示全部本人教学班及完整学生名单,支持 Excel 导出。
调停课改用教师专用教学班接口,并在服务端限制只能操作本人教学班。
This commit is contained in:
2026-07-27 08:05:42 +08:00 Unverified
parent bc6ffa2ada
commit 22321abe23
9 changed files with 491 additions and 69 deletions
+18 -9
View File
@@ -101,6 +101,18 @@ async function submit() {
} catch (e) { ElMessage.error(apiErrorMessage(e)) }
}
async function loadTaskOptions() {
if (!isTeacher.value && isManager.value) return
try {
tasks.value = (await http.get('/course-adjustments/task-options', {
params: { academicTermId: termId.value || undefined },
})).data
} catch (error) {
tasks.value = []
ElMessage.error(apiErrorMessage(error))
}
}
async function doSubmit(adj: any) {
try {
await http.post(`/course-adjustments/${adj.id}/submit`)
@@ -137,16 +149,13 @@ async function doReject() {
onMounted(async () => {
try {
const [termRes, taskRes] = await Promise.all([
http.get('/base-data/terms'),
isTeacher.value || !isManager.value
? http.get('/teaching-tasks', { params: { page: 1, pageSize: 100 } })
: Promise.resolve({ data: { items: [] } }),
])
const termRes = await http.get('/base-data/terms')
terms.value = termRes.data
tasks.value = taskRes.data.items
termId.value = defaultAcademicTermId(terms.value)
} catch (_) {}
await loadTaskOptions()
} catch (error) {
ElMessage.error(apiErrorMessage(error))
}
await load()
})
@@ -173,7 +182,7 @@ function showCancel(type: string) { return type === 'Cancel' }
</section>
<section class="adj-toolbar">
<el-select v-model="termId" clearable placeholder="全部学期" @change="load">
<el-select v-model="termId" clearable placeholder="全部学期" @change="load(); loadTaskOptions()">
<el-option v-for="t in terms" :key="t.id" :label="academicTermLabel(t)" :value="t.id" :class="academicTermOptionClass(t)" />
</el-select>
<el-segmented v-model="tab" :options="[
+33 -33
View File
@@ -8,22 +8,22 @@ import { academicTermLabel, academicTermOptionClass, defaultAcademicTermId } fro
const loading = ref(false)
const detailLoading = ref(false)
const terms = ref<any[]>([])
const offerings = ref<any[]>([])
const selectedOffering = ref<any>(null)
const tasks = ref<any[]>([])
const selectedTask = ref<any>(null)
const roster = ref<any>(null)
const termId = ref<string>()
async function loadOfferings() {
async function loadTasks() {
loading.value = true
try {
offerings.value = (await http.get('/course-selections/my-offerings', {
tasks.value = (await http.get('/course-selections/my-teaching-tasks', {
params: { academicTermId: termId.value || undefined },
})).data
const preferred = offerings.value.find((item: any) => item.id === selectedOffering.value?.id)
?? offerings.value[0]
if (preferred) await selectOffering(preferred)
const preferred = tasks.value.find((item: any) => item.id === selectedTask.value?.id)
?? tasks.value[0]
if (preferred) await selectTask(preferred)
else {
selectedOffering.value = null
selectedTask.value = null
roster.value = null
}
} catch (error) {
@@ -33,11 +33,11 @@ async function loadOfferings() {
}
}
async function selectOffering(offering: any) {
selectedOffering.value = offering
async function selectTask(task: any) {
selectedTask.value = task
detailLoading.value = true
try {
roster.value = (await http.get(`/course-selections/offerings/${offering.id}/roster`)).data
roster.value = (await http.get(`/course-selections/teaching-tasks/${task.id}/roster`)).data
} catch (error) {
roster.value = null
ElMessage.error(apiErrorMessage(error))
@@ -47,11 +47,11 @@ async function selectOffering(offering: any) {
}
async function exportRoster() {
if (!selectedOffering.value) return
if (!selectedTask.value) return
try {
await downloadApiFile(
`/course-selections/offerings/${selectedOffering.value.id}/roster/export.xlsx`,
`选课名单-${selectedOffering.value.taskNumber}.xlsx`,
`/course-selections/teaching-tasks/${selectedTask.value.id}/roster/export.xlsx`,
`教学班名单-${selectedTask.value.taskNumber}.xlsx`,
)
} catch (error) {
ElMessage.error(apiErrorMessage(error))
@@ -61,7 +61,7 @@ async function exportRoster() {
onMounted(async () => {
terms.value = (await http.get('/base-data/terms')).data
termId.value = defaultAcademicTermId(terms.value)
await loadOfferings()
await loadTasks()
})
</script>
@@ -71,43 +71,43 @@ onMounted(async () => {
<div>
<span class="section-kicker">ENROLLMENT ROSTER</span>
<h2>选课名单</h2>
<p>查看本人授课班级的选课学生名单支持 Excel 导出</p>
<p>查看本人授课教学班的完整学生名单支持 Excel 导出</p>
</div>
<el-button :icon="Refresh" @click="loadOfferings">刷新</el-button>
<el-button :icon="Refresh" @click="loadTasks">刷新</el-button>
</section>
<section class="roster-toolbar">
<el-select v-model="termId" clearable placeholder="全部学期" @change="loadOfferings">
<el-select v-model="termId" clearable placeholder="全部学期" @change="loadTasks">
<el-option v-for="term in terms" :key="term.id" :label="academicTermLabel(term)" :value="term.id" :class="academicTermOptionClass(term)" />
</el-select>
<span> {{ offerings.length }} 个教学班</span>
<span> {{ tasks.length }} 个教学班</span>
</section>
<section class="roster-workspace" v-loading="loading">
<aside class="roster-task-list">
<button
v-for="offering in offerings"
:key="offering.id"
v-for="task in tasks"
:key="task.id"
type="button"
:class="{ active: selectedOffering?.id === offering.id }"
@click="selectOffering(offering)"
:class="{ active: selectedTask?.id === task.id }"
@click="selectTask(task)"
>
<span>{{ offering.courseCode }} · {{ offering.taskNumber }}</span>
<b>{{ offering.courseName }}</b>
<small>{{ offering.termName }} · {{ offering.roundName }}</small>
<i>{{ offering.enrolledCount }} / {{ offering.capacity }} </i>
<span>{{ task.courseCode }} · {{ task.taskNumber }}</span>
<b>{{ task.courseName }}</b>
<small>{{ task.termName }}</small>
<i>{{ task.studentCount }} / {{ task.capacity }} </i>
</button>
<el-empty v-if="!offerings.length" description="当前学期没有课教学班" />
<el-empty v-if="!tasks.length" description="当前学期没有课教学班" />
</aside>
<main class="roster-detail" v-loading="detailLoading">
<el-empty v-if="!selectedOffering" description="请选择一个教学班" />
<el-empty v-if="!selectedTask" description="请选择一个教学班" />
<template v-else-if="roster">
<header class="roster-head">
<div>
<span>{{ roster.courseCode }} · {{ roster.taskNumber }}</span>
<h3>{{ roster.courseName }}选课名单</h3>
<p>已选 {{ roster.enrolledCount }} / 容量 {{ roster.capacity }} </p>
<h3>{{ roster.courseName }}教学班名单</h3>
<p> {{ roster.studentCount }} / 容量 {{ roster.capacity }} </p>
</div>
<el-button :icon="Download" @click="exportRoster">导出 Excel</el-button>
</header>
@@ -118,8 +118,8 @@ onMounted(async () => {
<el-table-column prop="name" label="姓名" min-width="100" />
<el-table-column prop="className" label="班级" min-width="130" />
<el-table-column prop="majorName" label="专业" min-width="150" />
<el-table-column label="选课时间" width="170">
<template #default="{ row }">{{ new Date(row.enrolledAt).toLocaleString('zh-CN') }}</template>
<el-table-column label="名单来源" width="210">
<template #default="{ row }">{{ row.enrolledAt ? `选课 · ${new Date(row.enrolledAt).toLocaleString('zh-CN')}` : '行政班关联' }}</template>
</el-table-column>
</el-table>
</template>