已改成“普通排课与实验学时分离”:

普通课表学时 = 总学时 − 实践学时。
自动排课、手工排课、课表发布都会阻止把实践学时重复排入。
全部为实践学时的课程应设为“非排时课程”,再由实验管理安排。
教学任务和排课页面会显示“常规 / 实践学时”。
已有教学任务即使仍保存旧周学时,排课时也会按拆分后的课程学时计算。
无需数据库迁移。
This commit is contained in:
2026-07-29 09:57:49 +08:00 Unverified
parent 356c410788
commit 692b521453
14 changed files with 525 additions and 30 deletions
+30 -9
View File
@@ -77,13 +77,22 @@ const availableClasses = computed(() => {
const selectedCourse = computed(() =>
courses.value.find((item) => item.id === form.courseId),
)
const regularScheduleHours = (course: any) =>
Math.max(0, Number(course?.totalHours ?? 0) - Number(course?.practiceHours ?? 0))
const targetCourseHours = (course: any, schedulingMode: string) =>
schedulingMode === 'Flexible'
? Number(course?.totalHours ?? 0)
: regularScheduleHours(course)
const selectedCourseTargetHours = computed(() =>
targetCourseHours(selectedCourse.value, form.schedulingMode),
)
const plannedHours = computed(() =>
form.startWeek && form.endWeek && form.weeklyHours && form.endWeek >= form.startWeek
? (form.endWeek - form.startWeek + 1) * form.weeklyHours
: 0,
)
const hoursMatch = computed(() =>
!selectedCourse.value || plannedHours.value === selectedCourse.value.totalHours,
!selectedCourse.value || plannedHours.value === selectedCourseTargetHours.value,
)
const assignableTeachers = computed(() =>
manualEligibleTeachers.value.map((item) => ({
@@ -115,7 +124,7 @@ const generationPlannedHours = computed(() =>
)
const generationHoursMatch = computed(() =>
!selectedGenerationCourse.value ||
generationPlannedHours.value === selectedGenerationCourse.value.totalHours,
generationPlannedHours.value === regularScheduleHours(selectedGenerationCourse.value),
)
const manageableCourses = computed(() => {
if (isSuperAdmin.value) return courses.value
@@ -355,7 +364,7 @@ async function save() {
}
if (!hoursMatch.value) {
ElMessage.warning(
`课程总学时为 ${selectedCourse.value.totalHours},当前授课安排合计 ${plannedHours.value} 学时,请调整周次或周学时`,
`课程普通课表应安排 ${selectedCourseTargetHours.value} 学时,当前安排合计 ${plannedHours.value} 学时;实践学时请在实验管理中安排`,
)
return
}
@@ -542,7 +551,7 @@ async function generatePublicTasks() {
}
if (!generationHoursMatch.value) {
ElMessage.warning(
`课程总学时为 ${selectedGenerationCourse.value.totalHours},当前授课安排合计 ${generationPlannedHours.value} 学时,请调整周次或周学时`,
`课程普通课表应安排 ${regularScheduleHours(selectedGenerationCourse.value)} 学时,当前安排合计 ${generationPlannedHours.value} 学时;实践学时不进入普通课表`,
)
return
}
@@ -683,6 +692,9 @@ onMounted(async () => {
<el-table-column label="行政班" min-width="150"><template #default="{ row }">{{ row.classNames.join('、') || '开放选课' }}</template></el-table-column>
<el-table-column label="人数 / 容量" width="105"><template #default="{ row }">{{ row.studentCount }} / {{ row.capacity }}</template></el-table-column>
<el-table-column label="周次" width="105"><template #default="{ row }">{{ row.startWeek }}{{ row.endWeek }} </template></el-table-column>
<el-table-column label="常规 / 实践学时" width="135">
<template #default="{ row }">{{ row.courseRegularScheduleHours }} / {{ row.coursePracticeHours }}</template>
</el-table-column>
<el-table-column label="状态" width="90"><template #default="{ row }"><span class="table-status" :class="{ off: row.status === 'Closed' }">{{ statusLabels[row.status] }}</span></template></el-table-column>
<el-table-column label="操作" width="210" fixed="right">
<template #default="{ row }">
@@ -714,7 +726,12 @@ onMounted(async () => {
<el-form-item label="开课学期" required><el-select v-model="form.academicTermId" @change="loadManualEligibleTeachers"><el-option v-for="item in terms" :key="item.id" :label="academicTermLabel(item)" :value="item.id" :class="academicTermOptionClass(item)" /></el-select></el-form-item>
<el-form-item label="课程" required>
<el-select v-model="form.courseId" filterable @change="form.teacherIds = []; form.primaryTeacherId = undefined; loadManualEligibleTeachers()">
<el-option v-for="item in filteredManageableCourses" :key="item.id" :label="`${item.code} · ${item.name}${item.totalHours} 学时)`" :value="item.id" />
<el-option
v-for="item in filteredManageableCourses"
:key="item.id"
:label="`${item.code} · ${item.name}(常规 ${regularScheduleHours(item)} / 实践 ${item.practiceHours} 学时)`"
:value="item.id"
/>
</el-select>
</el-form-item>
</div>
@@ -744,7 +761,10 @@ onMounted(async () => {
<b>{{ hoursMatch ? '学时匹配' : '学时不匹配' }}</b>
<span>
{{ form.startWeek }}{{ form.endWeek }} × 每周 {{ form.weeklyHours }} 学时
= {{ plannedHours }} 学时课程库总学时 {{ selectedCourse.totalHours }}
= {{ plannedHours }} 学时当前授课方式应计 {{ selectedCourseTargetHours }} 学时
<template v-if="form.schedulingMode === 'Standard'">
实践 {{ selectedCourse.practiceHours }} 学时由实验管理单独安排
</template>
</span>
</div>
<el-form-item label="授课方式">
@@ -753,7 +773,7 @@ onMounted(async () => {
<el-radio-button value="Flexible">非排时课程</el-radio-button>
</el-radio-group>
<small class="field-hint">
非排时课程不进入自动排课不占用星期节次和场地并在课表中单独列出
正常排课只安排非实践学时全部由实验模块安排的课程请选择非排时课程
</small>
</el-form-item>
<el-form-item label="授课教师">
@@ -881,8 +901,9 @@ onMounted(async () => {
>
<b>{{ generationHoursMatch ? '学时匹配' : '学时不匹配' }}</b>
<span>
当前合计 {{ generationPlannedHours }} 学时课程库总学时
{{ selectedGenerationCourse.totalHours }}
当前合计 {{ generationPlannedHours }} 学时普通课表应安排
{{ regularScheduleHours(selectedGenerationCourse) }} 学时实践
{{ selectedGenerationCourse.practiceHours }} 学时由实验管理单独安排
</span>
</div>
<div class="generation-preview">