实验排课

This commit is contained in:
2026-08-09 12:30:33 +08:00 Unverified
parent 1229f3163d
commit c692002676
25 changed files with 21294 additions and 1189 deletions
+62 -15
View File
@@ -18,6 +18,7 @@ const termId = ref('')
const projects = ref<any[]>([])
const options = reactive({
tasks: [] as any[],
scheduleEntries: [] as any[],
classrooms: [] as any[],
periods: [] as any[],
})
@@ -28,6 +29,7 @@ const projectDialog = ref(false)
const editingProjectId = ref('')
const projectForm = reactive({
teachingTaskIds: [] as string[],
scheduleEntryId: '',
code: '',
name: '',
arrangementMode: 'Centralized',
@@ -59,8 +61,12 @@ const participantsLoading = ref(false)
const selectedTask = computed(() =>
options.tasks.find((task) => task.id === projectForm.teachingTaskIds[0]),
)
const selectedScheduleEntry = computed(() =>
options.scheduleEntries.find((entry) => entry.id === projectForm.scheduleEntryId),
)
const batchProjectOptions = computed(() =>
projects.value.filter((project) => project.status !== 'Closed'),
projects.value.filter((project) =>
project.status !== 'Closed' && project.arrangementMode === 'SelfScheduled'),
)
const activePeriods = computed(() =>
options.periods.filter((period) =>
@@ -90,6 +96,7 @@ function resetProjectForm() {
editingProjectId.value = ''
Object.assign(projectForm, {
teachingTaskIds: [],
scheduleEntryId: '',
code: '',
name: '',
arrangementMode: 'Centralized',
@@ -105,6 +112,13 @@ function onTaskChange() {
projectForm.dates = [task.termStartDate, task.termEndDate]
}
function onScheduleEntryChange() {
const entry = selectedScheduleEntry.value
if (!entry) return
projectForm.teachingTaskIds = [entry.teachingTaskId]
onTaskChange()
}
function openCreateProject() {
resetProjectForm()
projectDialog.value = true
@@ -114,6 +128,7 @@ function openEditProject(project: any) {
editingProjectId.value = project.id
Object.assign(projectForm, {
teachingTaskIds: [project.teachingTaskId],
scheduleEntryId: project.scheduleEntryId ?? '',
code: project.code,
name: project.name,
arrangementMode: project.arrangementMode,
@@ -125,13 +140,18 @@ function openEditProject(project: any) {
}
async function saveProject() {
if (!projectForm.teachingTaskIds.length || !projectForm.code.trim()
if (!projectForm.teachingTaskIds.length ||
(projectForm.arrangementMode === 'Centralized' && !projectForm.scheduleEntryId) ||
!projectForm.code.trim()
|| !projectForm.name.trim() || projectForm.dates.length !== 2) {
ElMessage.warning('请填写教学任务、项目编码、名称和开放日期')
ElMessage.warning(projectForm.arrangementMode === 'Centralized'
? '请选择课表实验课,并填写项目编码、名称和开放日期'
: '请填写教学任务、项目编码、名称和开放日期')
return
}
const payload = {
teachingTaskId: projectForm.teachingTaskIds[0],
scheduleEntryId: projectForm.scheduleEntryId || null,
code: projectForm.code,
name: projectForm.name,
arrangementMode: projectForm.arrangementMode,
@@ -144,6 +164,9 @@ async function saveProject() {
if (editingProjectId.value) {
await http.put(`/experiments/${editingProjectId.value}`, payload)
ElMessage.success('实验项目已更新')
} else if (projectForm.arrangementMode === 'Centralized') {
await http.post('/experiments', payload)
ElMessage.success('实验项目已绑定课表实验课')
} else {
await http.post('/experiments/batch', {
...payload,
@@ -404,6 +427,7 @@ async function loadOptions() {
params: { academicTermId: termId.value || undefined },
})
options.tasks = data.tasks
options.scheduleEntries = data.scheduleEntries
options.classrooms = data.classrooms
options.periods = data.periods
} catch (error) {
@@ -462,11 +486,11 @@ onMounted(async () => {
<span class="section-kicker">LABORATORY OPERATIONS</span>
<h2>{{ isStudent ? '我的实验' : '实验管理' }}</h2>
<p v-if="isStudent">查看统一安排的实验课次或为规定实验项目选择适合自己的开放时段</p>
<p v-else>把实验项目分成两条运行轨道集中排入固定课次或开放场次供学生自主预约</p>
<p v-else>集中实验直接绑定已发布课表仅自主预约实验需要在此开放场次</p>
</div>
<div class="intro-actions">
<el-button v-if="!isStudent" :icon="Calendar" @click="openBatchSession">
批量排课
批量开放场次
</el-button>
<el-button v-if="!isStudent" type="primary" :icon="Plus" @click="openCreateProject">
批量设置实验任务
@@ -563,7 +587,7 @@ onMounted(async () => {
<b>{{ modeMeta[project.arrangementMode].note }}</b>
</div>
<el-button
v-if="!isStudent && project.status !== 'Closed'"
v-if="!isStudent && project.status !== 'Closed' && project.arrangementMode === 'SelfScheduled'"
size="small"
:icon="Calendar"
@click="openSession(project)"
@@ -572,7 +596,17 @@ onMounted(async () => {
</el-button>
</div>
<div v-if="project.sessions.length" class="session-grid">
<div v-if="project.arrangementMode === 'Centralized' && project.scheduleEntry" class="session-grid">
<article class="session-ticket">
<div class="ticket-date"><strong>课表</strong><span>固定</span></div>
<div class="ticket-body">
<b>星期 {{ project.scheduleEntry.dayOfWeek }} · {{ project.scheduleEntry.startPeriod }}{{ project.scheduleEntry.startPeriod + project.scheduleEntry.periodCount - 1 }} </b>
<span> {{ project.scheduleEntry.startWeek }}{{ project.scheduleEntry.endWeek }} · {{ project.scheduleEntry.campusName }} · {{ project.scheduleEntry.buildingName }} {{ project.scheduleEntry.classroomName }}</span>
</div>
<div class="ticket-action"><el-tag type="primary" size="small" effect="plain">课表已安排</el-tag></div>
</article>
</div>
<div v-else-if="project.sessions.length" class="session-grid">
<article
v-for="session in project.sessions"
:key="session.id"
@@ -653,7 +687,7 @@ onMounted(async () => {
<el-empty
v-else
:image-size="58"
:description="project.status === 'Draft' ? '尚未安排场次,安排后才能发布' : '暂无有效实验场次'"
:description="project.arrangementMode === 'Centralized' ? '尚未绑定已发布课表中的实验课' : (project.status === 'Draft' ? '尚未开放场次,安排后才能发布' : '暂无有效实验场次')"
/>
</div>
@@ -664,7 +698,9 @@ onMounted(async () => {
<el-button
size="small"
type="primary"
:disabled="!project.sessions.some((item: any) => item.status !== 'Cancelled')"
:disabled="project.arrangementMode === 'Centralized'
? !project.scheduleEntry
: !project.sessions.some((item: any) => item.status !== 'Cancelled')"
@click="publishProject(project)"
>
发布给学生
@@ -720,11 +756,22 @@ onMounted(async () => {
<el-form label-position="top" class="experiment-form">
<div class="form-section">
<header><span>PROJECT</span><b>规定实验项目</b></header>
<el-form-item :label="editingProjectId ? '所属教学任务' : '适用教学任务(可多选)'" required>
<el-form-item v-if="projectForm.arrangementMode === 'Centralized'" label="已发布课表中的实验课" required>
<el-select v-model="projectForm.scheduleEntryId" filterable :disabled="!!editingProjectId" placeholder="选择已安排实验室的实验课" @change="onScheduleEntryChange">
<el-option
v-for="entry in options.scheduleEntries"
:key="entry.id"
:label="`${entry.courseCode} · ${entry.courseName} · ${entry.taskNumber} · 星期 ${entry.dayOfWeek} 第 ${entry.startPeriod}${entry.startPeriod + entry.periodCount - 1} 节 · ${entry.campusName} ${entry.buildingName} ${entry.classroomName}`"
:value="entry.id"
/>
</el-select>
<small class="form-help">集中实验复用课表的时间和实验室不会再生成独立实验场次</small>
</el-form-item>
<el-form-item v-else :label="editingProjectId ? '所属教学任务' : '适用教学任务(可多选)'" required>
<el-select
v-model="projectForm.teachingTaskIds"
filterable
multiple
:multiple="!editingProjectId"
collapse-tags
collapse-tags-tooltip
:disabled="!!editingProjectId"
@@ -757,9 +804,9 @@ onMounted(async () => {
<div class="form-section">
<header><span>ROUTE</span><b>选择运行轨道</b></header>
<el-radio-group v-model="projectForm.arrangementMode" class="mode-choice">
<el-radio-group v-model="projectForm.arrangementMode" class="mode-choice" :disabled="!!editingProjectId">
<el-radio-button value="Centralized">
<b>集中安排</b><small>统一时间像上课一样到场</small>
<b>集中安排</b><small>复用已发布课表的实验课</small>
</el-radio-button>
<el-radio-button value="SelfScheduled">
<b>自行安排</b><small>开放多个场次学生自主预约</small>
@@ -858,7 +905,7 @@ onMounted(async () => {
<el-form-item label="实验室" required>
<el-select v-model="sessionForm.classroomId" filterable placeholder="选择实验室或教学场所">
<el-option
v-for="room in options.classrooms"
v-for="room in options.classrooms.filter((item: any) => (Number(item.teachingVenueNature) & (2 | 4 | 8 | 16)) !== 0)"
:key="room.id"
:label="`${room.campusName} · ${room.buildingName} ${room.name} · ${room.capacity} 人`"
:value="room.id"
@@ -940,7 +987,7 @@ onMounted(async () => {
<el-input-number v-model="row.periodCount" :min="1" :max="12" controls-position="right" />
<el-select v-model="row.classroomId" filterable placeholder="实验室">
<el-option
v-for="room in options.classrooms"
v-for="room in options.classrooms.filter((item: any) => (Number(item.teachingVenueNature) & (2 | 4 | 8 | 16)) !== 0)"
:key="room.id"
:label="`${room.campusName} · ${room.buildingName} ${room.name} · ${room.capacity} 人`"
:value="room.id"