自动排课完成后逐条显示“需人工处理”的任务和原因,并提供“检查排课约束”入口;刷新页面后仍可读取最近一次结果。
批量排课约束新增校区、教学楼、多教室统一指定,也支持清除原有限定。 课表新增“总视图”,同一时段、不同周次课程会并列显示。 周视图按教学周过滤,支持上一周、下一周、周次下拉和回到本周。 日视图按周次过滤;连续两节课程会跨两行完整占格。
This commit is contained in:
1 parent
db91988264
commit
e4a12931ea
8 files changed
+652
-51
No files matched your search
@@ -331,6 +331,9 @@ button { cursor: pointer; }
|
||||
.auto-schedule-progress.is-succeeded { border-bottom-color: #bedac8; background: #f1f8f3; }
|
||||
.auto-schedule-progress.is-failed { border-bottom-color: #e6c5c5; background: #fff5f4; }
|
||||
.auto-schedule-progress.is-failed b { color: #9d3434; }
|
||||
.auto-schedule-progress .auto-schedule-issues { grid-column: 1 / -1; padding: 11px 13px; gap: 8px; border: 1px solid #e3d3aa; background: #fffdf7; }
|
||||
.auto-schedule-issues-head { display: flex !important; align-items: center; justify-content: space-between; gap: 12px; }
|
||||
.auto-schedule-issues ol { max-height: 240px; margin: 0; padding: 0 8px 0 21px; display: grid; gap: 6px; overflow: auto; color: #715826; font-size: 11px; line-height: 1.55; }
|
||||
.schedule-search { padding: 12px 16px; display: flex; align-items: center; gap: 10px; background: #fafbfc; border-bottom: 1px solid var(--line); }
|
||||
.schedule-search .el-input { width: 320px; }
|
||||
.schedule-search > span { margin-left: auto; color: var(--muted); font-size: 10px; }
|
||||
@@ -363,6 +366,7 @@ button { cursor: pointer; }
|
||||
.constraint-result-summary { margin-bottom: 10px; color: var(--muted); font-size: 10px; text-align: right; }
|
||||
.constraint-batch-form { margin-top: 16px; display: grid; gap: 10px; }
|
||||
.constraint-batch-form > .el-checkbox { padding: 8px 10px; background: #f7f9fb; border-left: 3px solid #ccd8e1; }
|
||||
.batch-classroom-scope { padding: 12px 14px 2px; display: grid; gap: 12px; border: 1px solid #d8e2e8; background: #fbfcfd; }
|
||||
.constraint-list article { min-width: 0; padding: 13px 15px; display: grid; grid-template-columns: minmax(230px, 1fr) minmax(170px, auto) auto; align-items: center; gap: 14px; border: 1px solid var(--line); background: #fff; }
|
||||
.constraint-list article > div:first-child { min-width: 0; display: grid; gap: 4px; }
|
||||
.constraint-list article span { color: var(--teal); font: 700 9px/1.2 Consolas, monospace; }
|
||||
|
||||
@@ -141,6 +141,19 @@ const filteredClassrooms = computed(() =>
|
||||
(!constraintForm.requiredBuildingId || item.buildingId === constraintForm.requiredBuildingId),
|
||||
),
|
||||
)
|
||||
const batchFilteredBuildings = computed(() =>
|
||||
constraintBatchForm.requiredCampusId
|
||||
? buildings.value.filter((item) => item.campusId === constraintBatchForm.requiredCampusId)
|
||||
: buildings.value,
|
||||
)
|
||||
const batchFilteredClassrooms = computed(() =>
|
||||
classrooms.value.filter((item) =>
|
||||
(!constraintBatchForm.requiredCampusId ||
|
||||
item.campusId === constraintBatchForm.requiredCampusId) &&
|
||||
(!constraintBatchForm.requiredBuildingId ||
|
||||
item.buildingId === constraintBatchForm.requiredBuildingId),
|
||||
),
|
||||
)
|
||||
const filteredEntries = computed(() => {
|
||||
const text = keyword.value.trim().toLowerCase()
|
||||
if (!text) return selected.value?.entries ?? []
|
||||
@@ -285,6 +298,10 @@ function openConstraintBatch() {
|
||||
schedulingMode: 'Standard',
|
||||
updateRequiresClassroom: false,
|
||||
requiresClassroom: true,
|
||||
updateClassroomScope: false,
|
||||
requiredCampusId: undefined,
|
||||
requiredBuildingId: undefined,
|
||||
allowedClassroomIds: [],
|
||||
updateDays: false,
|
||||
allowedDayOfWeeks: [1, 2, 3, 4, 5],
|
||||
updatePeriodRange: false,
|
||||
@@ -297,6 +314,7 @@ function openConstraintBatch() {
|
||||
async function saveConstraintBatch() {
|
||||
if (!constraintBatchForm.updateSchedulingMode &&
|
||||
!constraintBatchForm.updateRequiresClassroom &&
|
||||
!constraintBatchForm.updateClassroomScope &&
|
||||
!constraintBatchForm.updateDays &&
|
||||
!constraintBatchForm.updatePeriodRange) {
|
||||
ElMessage.warning('请至少勾选一项需要批量修改的设置。')
|
||||
@@ -312,6 +330,10 @@ async function saveConstraintBatch() {
|
||||
constraintBatchSaving.value = true
|
||||
const flexible = constraintBatchForm.updateSchedulingMode &&
|
||||
constraintBatchForm.schedulingMode === 'Flexible'
|
||||
const updateClassroomScope = !flexible &&
|
||||
!(constraintBatchForm.updateRequiresClassroom &&
|
||||
!constraintBatchForm.requiresClassroom) &&
|
||||
constraintBatchForm.updateClassroomScope
|
||||
const { data } = await http.put('/schedules/constraints/batch', {
|
||||
academicTermId: termId.value,
|
||||
teachingTaskIds: targets.map((item) => item.id),
|
||||
@@ -321,6 +343,16 @@ async function saveConstraintBatch() {
|
||||
requiresClassroom: !flexible && constraintBatchForm.updateRequiresClassroom
|
||||
? constraintBatchForm.requiresClassroom
|
||||
: null,
|
||||
updateClassroomScope,
|
||||
requiredCampusId: updateClassroomScope
|
||||
? constraintBatchForm.requiredCampusId || null
|
||||
: null,
|
||||
requiredBuildingId: updateClassroomScope
|
||||
? constraintBatchForm.requiredBuildingId || null
|
||||
: null,
|
||||
allowedClassroomIds: updateClassroomScope
|
||||
? constraintBatchForm.allowedClassroomIds
|
||||
: null,
|
||||
allowedDayOfWeeks: !flexible && constraintBatchForm.updateDays
|
||||
? constraintBatchForm.allowedDayOfWeeks
|
||||
: null,
|
||||
@@ -342,6 +374,11 @@ async function saveConstraintBatch() {
|
||||
}
|
||||
}
|
||||
|
||||
function openManualHandling() {
|
||||
settingsTab.value = 'constraints'
|
||||
settingsDrawer.value = true
|
||||
}
|
||||
|
||||
async function autoSchedule() {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
@@ -694,6 +731,20 @@ onBeforeUnmount(clearAutoSchedulePoll)
|
||||
:indeterminate="autoJob.status === 'Queued'"
|
||||
:duration="2"
|
||||
/>
|
||||
<div
|
||||
v-if="autoJob.status === 'Succeeded' && autoJob.messages.length"
|
||||
class="auto-schedule-issues"
|
||||
>
|
||||
<div class="auto-schedule-issues-head">
|
||||
<b>未完成任务与处理建议</b>
|
||||
<el-button size="small" type="warning" plain @click="openManualHandling">
|
||||
检查排课约束
|
||||
</el-button>
|
||||
</div>
|
||||
<ol>
|
||||
<li v-for="message in autoJob.messages" :key="message">{{ message }}</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="schedule-search">
|
||||
@@ -1024,8 +1075,59 @@ onBeforeUnmount(clearAutoSchedulePoll)
|
||||
v-model="constraintBatchForm.requiresClassroom"
|
||||
active-text="需要占用教室"
|
||||
inactive-text="不占用教室"
|
||||
@change="!constraintBatchForm.requiresClassroom && (constraintBatchForm.updateClassroomScope = false)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<template v-if="!(constraintBatchForm.updateRequiresClassroom && !constraintBatchForm.requiresClassroom)">
|
||||
<el-checkbox v-model="constraintBatchForm.updateClassroomScope">
|
||||
批量指定教室范围
|
||||
</el-checkbox>
|
||||
<div v-if="constraintBatchForm.updateClassroomScope" class="batch-classroom-scope">
|
||||
<el-alert
|
||||
title="将统一设置为需要教室;校区、教学楼和教室均不选择时,表示清除原有限定并允许系统自动分配。"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
<div class="form-grid">
|
||||
<el-form-item label="统一限定校区">
|
||||
<el-select
|
||||
v-model="constraintBatchForm.requiredCampusId"
|
||||
clearable
|
||||
@change="constraintBatchForm.requiredBuildingId = undefined; constraintBatchForm.allowedClassroomIds = []"
|
||||
>
|
||||
<el-option v-for="item in campuses" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="统一限定教学楼">
|
||||
<el-select
|
||||
v-model="constraintBatchForm.requiredBuildingId"
|
||||
clearable
|
||||
@change="constraintBatchForm.allowedClassroomIds = []"
|
||||
>
|
||||
<el-option v-for="item in batchFilteredBuildings" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="统一指定可用教室">
|
||||
<el-select
|
||||
v-model="constraintBatchForm.allowedClassroomIds"
|
||||
multiple
|
||||
filterable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="不选择则允许范围内任意教室"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in batchFilteredClassrooms"
|
||||
:key="item.id"
|
||||
:label="`${item.buildingName} / ${item.name}(${item.capacity}人)`"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
<el-checkbox v-model="constraintBatchForm.updateDays">修改允许上课日</el-checkbox>
|
||||
<el-form-item v-if="constraintBatchForm.updateDays" label="统一允许上课日">
|
||||
<el-checkbox-group v-model="constraintBatchForm.allowedDayOfWeeks">
|
||||
|
||||
@@ -19,7 +19,6 @@ const editingId = ref('')
|
||||
const selectedRows = ref<any[]>([])
|
||||
const terms = ref<any[]>([])
|
||||
const courses = ref<any[]>([])
|
||||
const teachers = ref<any[]>([])
|
||||
const classes = ref<any[]>([])
|
||||
const majors = ref<any[]>([])
|
||||
const query = reactive({
|
||||
@@ -85,13 +84,15 @@ const plannedHours = computed(() =>
|
||||
const hoursMatch = computed(() =>
|
||||
!selectedCourse.value || plannedHours.value === selectedCourse.value.totalHours,
|
||||
)
|
||||
const assignableTeachers = computed(() => {
|
||||
if (!selectedCourse.value) return teachers.value
|
||||
const approvedIds = new Set(manualEligibleTeachers.value.map((item) => item.teacherId))
|
||||
return teachers.value.filter((item) =>
|
||||
approvedIds.has(item.id) || form.teacherIds?.includes(item.id),
|
||||
)
|
||||
})
|
||||
const assignableTeachers = computed(() =>
|
||||
manualEligibleTeachers.value.map((item) => ({
|
||||
id: item.teacherId,
|
||||
teacherNumber: item.teacherNumber,
|
||||
name: item.name,
|
||||
title: item.title,
|
||||
collegeName: item.collegeName,
|
||||
})),
|
||||
)
|
||||
const selectedTeachers = computed(() =>
|
||||
assignableTeachers.value.filter((item) => form.teacherIds?.includes(item.id)),
|
||||
)
|
||||
@@ -521,16 +522,14 @@ async function generatePublicTasks() {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const [termRes, courseRes, teacherRes, classRes, majorRes] = await Promise.all([
|
||||
const [termRes, courseRes, classRes, majorRes] = await Promise.all([
|
||||
http.get('/base-data/terms'),
|
||||
http.get('/courses/options'),
|
||||
http.get('/personnel/teachers', { params: { page: 1, pageSize: 100, teacherStatus: 'Active' } }),
|
||||
http.get('/base-data/classes'),
|
||||
http.get('/base-data/majors'),
|
||||
])
|
||||
terms.value = termRes.data
|
||||
courses.value = courseRes.data
|
||||
teachers.value = teacherRes.data.items
|
||||
classes.value = classRes.data
|
||||
majors.value = majorRes.data
|
||||
query.academicTermId = terms.value.find((item) => item.isCurrent)?.id
|
||||
@@ -700,7 +699,7 @@ onMounted(async () => {
|
||||
<el-select v-model="form.teacherIds" multiple filterable @change="onTeachersChanged">
|
||||
<el-option v-for="item in assignableTeachers" :key="item.id" :label="`${item.teacherNumber} · ${item.name}`" :value="item.id" />
|
||||
</el-select>
|
||||
<small v-if="selectedCourse" class="field-hint">仅显示本学期申报该课程且学院审核通过的教师。</small>
|
||||
<small v-if="selectedCourse" class="field-hint">仅显示本学期已审核通过或由学院直接分配的授课资格教师。</small>
|
||||
</el-form-item>
|
||||
<el-form-item label="主讲教师">
|
||||
<el-select v-model="form.primaryTeacherId" clearable :disabled="!form.teacherIds?.length">
|
||||
|
||||
Loaded 3 of 8 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user