This commit is contained in:
2026-07-24 20:37:42 +08:00 Unverified
parent 5da1a8a403
commit 20ca205080
9 changed files with 1115 additions and 7 deletions
+171 -6
View File
@@ -31,9 +31,18 @@ const detailLoading = ref(false)
const roundDialog = ref(false)
const offeringDialog = ref(false)
const rosterDrawer = ref(false)
const proxyDialog = ref(false)
const editingRoundId = ref('')
const editingOfferingId = ref('')
const roster = ref<any | null>(null)
const rosterLoading = ref(false)
const eligibleStudents = ref<any[]>([])
const eligibleTotal = ref(0)
const eligiblePage = ref(1)
const eligibleLoading = ref(false)
const proxySubmitting = ref(false)
const studentKeyword = ref('')
const selectedStudentIds = ref<string[]>([])
const roundForm = reactive<Record<string, any>>({})
const offeringForm = reactive<Record<string, any>>({})
@@ -286,13 +295,97 @@ async function deleteOffering(offering: any) {
}
async function showRoster(offering: any) {
rosterDrawer.value = true
await loadRoster(offering.id)
}
async function loadRoster(offeringId: string) {
rosterLoading.value = true
try {
roster.value = (
await http.get(`/course-selections/offerings/${offering.id}/roster`)
await http.get(`/course-selections/offerings/${offeringId}/roster`)
).data
rosterDrawer.value = true
} catch (error) {
ElMessage.error(apiErrorMessage(error))
} finally {
rosterLoading.value = false
}
}
async function openProxyEnrollment() {
studentKeyword.value = ''
selectedStudentIds.value = []
eligiblePage.value = 1
proxyDialog.value = true
await loadEligibleStudents()
}
async function loadEligibleStudents(page = eligiblePage.value) {
if (!roster.value) return
eligibleLoading.value = true
eligiblePage.value = page
try {
const { data } = await http.get(
`/course-selections/offerings/${roster.value.id}/eligible-students`,
{
params: {
keyword: studentKeyword.value.trim() || undefined,
page,
pageSize: 20,
},
},
)
eligibleStudents.value = data.items
eligibleTotal.value = data.total
} catch (error) {
ElMessage.error(apiErrorMessage(error))
} finally {
eligibleLoading.value = false
}
}
function onEligibleSelectionChanged(rows: any[]) {
selectedStudentIds.value = rows.map((item) => item.id)
}
async function proxyEnroll() {
if (!roster.value || selectedStudentIds.value.length === 0) {
ElMessage.warning('请至少选择一名学生。')
return
}
proxySubmitting.value = true
try {
const { data } = await http.post(
`/course-selections/offerings/${roster.value.id}/admin-enrollments`,
{ studentIds: selectedStudentIds.value },
)
ElMessage.success(`已为 ${data.enrolledCount} 名学生完成代选`)
proxyDialog.value = false
await loadRoster(roster.value.id)
if (selectedRound.value) await selectRound(selectedRound.value)
} catch (error) {
ElMessage.error(apiErrorMessage(error))
} finally {
proxySubmitting.value = false
}
}
async function removeFromRoster(student: any) {
if (!roster.value) return
try {
await ElMessageBox.confirm(
`确认将 ${student.studentNumber} ${student.name} 移出“${roster.value.courseName}”教学班名单?`,
'调整教学班名单',
{ type: 'warning', confirmButtonText: '确认移出', cancelButtonText: '取消' },
)
await http.delete(
`/course-selections/offerings/${roster.value.id}/admin-enrollments/${student.id}`,
)
ElMessage.success('已移出教学班名单')
await loadRoster(roster.value.id)
if (selectedRound.value) await selectRound(selectedRound.value)
} catch (error: any) {
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
}
}
@@ -455,7 +548,7 @@ onMounted(async () => {
</el-table-column>
<el-table-column label="操作" width="190" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="showRoster(row)">查看名单</el-button>
<el-button link type="primary" @click="showRoster(row)">名单管理</el-button>
<el-button
v-if="selectedRound.status === 'Draft'"
link
@@ -603,26 +696,98 @@ onMounted(async () => {
</template>
</el-dialog>
<el-drawer v-model="rosterDrawer" title="教学班名单" size="620px">
<el-drawer v-model="rosterDrawer" title="教学班名单管理" size="720px">
<template v-if="roster">
<div class="roster-summary">
<el-icon><UserFilled /></el-icon>
<div>
<span>{{ roster.taskNumber }}</span>
<span>{{ roster.courseCode }} · {{ roster.taskNumber }}</span>
<b>{{ roster.taskName }}</b>
<small>{{ roster.enrolledCount }} / {{ roster.capacity }} </small>
</div>
<el-button
v-if="roster.canProxyEnroll"
type="primary"
:icon="Plus"
@click="openProxyEnrollment"
>代选学生</el-button>
</div>
<el-table :data="roster.students">
<el-alert
v-if="roster.canProxyEnroll"
class="roster-notice"
type="info"
:closable="false"
title="公共必修课支持校级教务代选;系统仍会校验教学班容量、学分上限、重复课程和课表冲突。"
/>
<el-table v-loading="rosterLoading" :data="roster.students">
<el-table-column prop="studentNumber" label="学号" width="130" />
<el-table-column prop="name" label="姓名" width="90" />
<el-table-column prop="className" label="行政班" min-width="150" />
<el-table-column label="选课时间" min-width="130">
<template #default="{ row }">{{ formatDateTime(row.enrolledAt) }}</template>
</el-table-column>
<el-table-column v-if="roster.canProxyEnroll" label="操作" width="80" fixed="right">
<template #default="{ row }">
<el-button link type="danger" @click="removeFromRoster(row)">移出</el-button>
</template>
</el-table-column>
<template #empty><el-empty description="暂无学生选课" /></template>
</el-table>
</template>
</el-drawer>
<el-dialog v-model="proxyDialog" title="公共必修课代选" width="760px">
<template v-if="roster">
<div class="proxy-course-note">
<b>{{ roster.courseName }}</b>
<span>{{ roster.taskNumber }} · 当前 {{ roster.enrolledCount }} / {{ roster.capacity }} </span>
</div>
<div class="proxy-search">
<el-input
v-model="studentKeyword"
clearable
placeholder="输入学号、姓名或行政班"
@keyup.enter="loadEligibleStudents(1)"
@clear="loadEligibleStudents(1)"
/>
<el-button type="primary" @click="loadEligibleStudents(1)">查询学生</el-button>
</div>
<el-table
v-loading="eligibleLoading"
:data="eligibleStudents"
row-key="id"
height="360"
@selection-change="onEligibleSelectionChanged"
>
<el-table-column type="selection" width="48" />
<el-table-column prop="studentNumber" label="学号" width="130" />
<el-table-column prop="name" label="姓名" width="90" />
<el-table-column prop="className" label="行政班" min-width="150" />
<el-table-column prop="majorName" label="专业" min-width="150" />
<template #empty><el-empty description="没有可代选的在籍学生" /></template>
</el-table>
<el-pagination
v-if="eligibleTotal > 20"
class="proxy-pagination"
layout="prev, pager, next, total"
:current-page="eligiblePage"
:page-size="20"
:total="eligibleTotal"
@current-change="loadEligibleStudents"
/>
</template>
<template #footer>
<div class="proxy-dialog-footer">
<span class="proxy-selected-count">已选择 {{ selectedStudentIds.length }} </span>
<el-button @click="proxyDialog = false">取消</el-button>
<el-button
type="primary"
:loading="proxySubmitting"
:disabled="selectedStudentIds.length === 0"
@click="proxyEnroll"
>确认代选</el-button>
</div>
</template>
</el-dialog>
</div>
</template>