教学任务页面优化
This commit is contained in:
@@ -300,6 +300,12 @@ button { cursor: pointer; }
|
||||
.task-summary span, .task-summary small { color: #b8c1de; font-size: 10px; }
|
||||
.task-summary b { margin-left: auto; color: #58d1c0; font: 700 30px/1 Consolas, monospace; }
|
||||
.task-summary p { margin: 0; padding-left: 28px; border-left: 1px solid rgba(255,255,255,.16); color: #d4daeb; font-size: 11px; letter-spacing: .03em; }
|
||||
.batch-action-bar { min-height: 64px; padding: 11px 18px; display: flex; align-items: center; justify-content: space-between; gap: 18px; border-bottom: 1px solid #b9dcd7; background: #f1f9f7; }
|
||||
.batch-action-bar > div:first-child { display: grid; gap: 4px; }
|
||||
.batch-action-bar b { color: #244a49; font-size: 12px; }
|
||||
.batch-action-bar span { color: #66807f; font-size: 10px; }
|
||||
.batch-action-bar > div:last-child { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 7px; }
|
||||
.batch-action-bar .el-button + .el-button { margin-left: 0; }
|
||||
.muted-action { color: #9aa1ae; font-size: 11px; }
|
||||
.schedule-toolbar { min-height: 86px; padding: 12px 16px; display: flex; align-items: stretch; gap: 14px; border: 1px solid var(--line); background: white; }
|
||||
.schedule-toolbar > .el-select { width: 245px; align-self: center; }
|
||||
@@ -362,6 +368,18 @@ button { cursor: pointer; }
|
||||
.generation-preview b { color: var(--indigo); font: 700 25px/1 Consolas, monospace; }
|
||||
.generation-preview span, .generation-preview small { font-size: 10px; }
|
||||
.field-hint { display: block; margin-top: 5px; color: var(--muted); font-size: 9px; }
|
||||
.option-filter-panel { margin: -3px 0 18px; padding: 11px 13px; display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 10px; border: 1px solid #dce4eb; border-left: 3px solid var(--teal); background: #f8fafb; }
|
||||
.option-filter-title { color: #3f5367; font-size: 10px; font-weight: 650; white-space: nowrap; }
|
||||
.option-filter-panel > small { color: var(--muted); font-size: 9px; white-space: nowrap; }
|
||||
.option-filter-grid { min-width: 0; display: grid; gap: 8px; }
|
||||
.option-filter-grid.course { grid-template-columns: minmax(150px, 1fr) minmax(145px, .8fr) minmax(130px, .65fr); }
|
||||
.option-filter-grid.course.compact { grid-template-columns: minmax(180px, 1fr) minmax(180px, .8fr); }
|
||||
.option-filter-grid.classes { grid-template-columns: minmax(190px, 1.25fr) repeat(3, minmax(115px, .75fr)); }
|
||||
.class-picker { width: 100%; margin-bottom: 8px; padding: 11px 12px 8px; border: 1px solid #dce4eb; background: #f8fafb; }
|
||||
.class-picker-actions { min-height: 32px; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||
.class-picker-actions > span { color: var(--muted); font-size: 9px; }
|
||||
.class-picker-actions > div { display: flex; align-items: center; }
|
||||
.class-picker-actions .el-button + .el-button { margin-left: 4px; }
|
||||
|
||||
.selection-page { min-width: 0; }
|
||||
.selection-round-strip {
|
||||
@@ -982,6 +1000,13 @@ button { cursor: pointer; }
|
||||
.task-summary { align-items: flex-start; flex-direction: column; gap: 12px; }
|
||||
.task-summary > div { width: 100%; }
|
||||
.task-summary p { padding: 12px 0 0; border-left: none; border-top: 1px solid rgba(255,255,255,.16); line-height: 1.6; }
|
||||
.batch-action-bar { align-items: flex-start; flex-direction: column; }
|
||||
.batch-action-bar > div:last-child { justify-content: flex-start; }
|
||||
.option-filter-panel { grid-template-columns: 1fr; }
|
||||
.option-filter-grid.course,
|
||||
.option-filter-grid.course.compact,
|
||||
.option-filter-grid.classes { grid-template-columns: 1fr 1fr; }
|
||||
.class-picker-actions { align-items: flex-start; flex-direction: column; gap: 2px; }
|
||||
.schedule-toolbar { display: block; }
|
||||
.schedule-toolbar > .el-select { width: 100%; }
|
||||
.schedule-version-strip { margin-top: 10px; }
|
||||
@@ -1025,6 +1050,11 @@ button { cursor: pointer; }
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.option-filter-grid.course,
|
||||
.option-filter-grid.course.compact,
|
||||
.option-filter-grid.classes { grid-template-columns: 1fr; }
|
||||
.batch-action-bar > div:last-child { width: 100%; }
|
||||
.batch-action-bar > div:last-child .el-button { flex: 1 1 auto; }
|
||||
.selection-round-strip > button { flex-basis: 240px; }
|
||||
.selection-window { display: block; }
|
||||
.window-seal { min-height: 54px; padding: 10px; display: flex; border-right: none; border-bottom: 1px solid rgba(255,255,255,.13); }
|
||||
|
||||
@@ -16,6 +16,7 @@ const dialogVisible = ref(false)
|
||||
const generationDialog = ref(false)
|
||||
const generating = ref(false)
|
||||
const editingId = ref('')
|
||||
const selectedRows = ref<any[]>([])
|
||||
const terms = ref<any[]>([])
|
||||
const courses = ref<any[]>([])
|
||||
const teachers = ref<any[]>([])
|
||||
@@ -30,6 +31,27 @@ const query = reactive({
|
||||
})
|
||||
const form = reactive<Record<string, any>>({})
|
||||
const generationForm = reactive<Record<string, any>>({})
|
||||
const manualCourseFilters = reactive({
|
||||
keyword: '',
|
||||
collegeId: undefined as string | undefined,
|
||||
nature: undefined as string | undefined,
|
||||
})
|
||||
const manualClassFilters = reactive({
|
||||
keyword: '',
|
||||
collegeId: undefined as string | undefined,
|
||||
majorId: undefined as string | undefined,
|
||||
grade: undefined as number | undefined,
|
||||
})
|
||||
const generationCourseFilters = reactive({
|
||||
keyword: '',
|
||||
collegeId: undefined as string | undefined,
|
||||
})
|
||||
const generationClassFilters = reactive({
|
||||
keyword: '',
|
||||
collegeId: undefined as string | undefined,
|
||||
majorId: undefined as string | undefined,
|
||||
grade: undefined as number | undefined,
|
||||
})
|
||||
const eligibleTeachers = ref<any[]>([])
|
||||
const manualEligibleTeachers = ref<any[]>([])
|
||||
const statusLabels: Record<string, string> = {
|
||||
@@ -37,12 +59,20 @@ const statusLabels: Record<string, string> = {
|
||||
Published: '已发布',
|
||||
Closed: '已结课',
|
||||
}
|
||||
const courseNatureLabels: Record<string, string> = {
|
||||
GeneralRequired: '公共必修',
|
||||
GeneralElective: '公共选修',
|
||||
MajorRequired: '专业必修',
|
||||
MajorElective: '专业选修',
|
||||
Practice: '实践教学',
|
||||
}
|
||||
const availableClasses = computed(() => {
|
||||
if (!auth.user?.roles.includes('CollegeAdmin') || !auth.user.collegeId) return classes.value
|
||||
const enabled = classes.value.filter((item) => item.isEnabled)
|
||||
if (!auth.user?.roles.includes('CollegeAdmin') || !auth.user.collegeId) return enabled
|
||||
const ids = new Set(
|
||||
majors.value.filter((item) => item.collegeId === auth.user!.collegeId).map((item) => item.id),
|
||||
)
|
||||
return classes.value.filter((item) => ids.has(item.majorId))
|
||||
return enabled.filter((item) => ids.has(item.majorId))
|
||||
})
|
||||
const selectedCourse = computed(() =>
|
||||
courses.value.find((item) => item.id === form.courseId),
|
||||
@@ -72,6 +102,46 @@ const manageableCourses = computed(() => {
|
||||
['MajorRequired', 'MajorElective', 'Practice'].includes(item.nature)
|
||||
})
|
||||
})
|
||||
const courseColleges = computed(() => {
|
||||
const result = new Map<string, string>()
|
||||
courses.value.forEach((item) => result.set(item.collegeId, item.collegeName))
|
||||
return [...result].map(([id, name]) => ({ id, name })).sort((a, b) =>
|
||||
a.name.localeCompare(b.name, 'zh-CN'),
|
||||
)
|
||||
})
|
||||
const classColleges = computed(() => {
|
||||
const result = new Map<string, string>()
|
||||
majors.value.forEach((item) => result.set(item.collegeId, item.collegeName))
|
||||
return [...result].map(([id, name]) => ({ id, name })).sort((a, b) =>
|
||||
a.name.localeCompare(b.name, 'zh-CN'),
|
||||
)
|
||||
})
|
||||
const majorById = computed(() =>
|
||||
new Map(majors.value.map((item) => [item.id, item])),
|
||||
)
|
||||
const classGrades = computed(() =>
|
||||
[...new Set(availableClasses.value.map((item) => item.grade))].sort((a, b) => b - a),
|
||||
)
|
||||
const manualClassMajors = computed(() => filteredMajors(manualClassFilters.collegeId))
|
||||
const generationClassMajors = computed(() => filteredMajors(generationClassFilters.collegeId))
|
||||
const filteredManageableCourses = computed(() =>
|
||||
manageableCourses.value.filter((item) => courseMatches(item, manualCourseFilters)),
|
||||
)
|
||||
const filteredPublicCourses = computed(() =>
|
||||
publicCourses.value.filter((item) => courseMatches(item, generationCourseFilters)),
|
||||
)
|
||||
const filteredManualClasses = computed(() =>
|
||||
availableClasses.value.filter((item) => classMatches(item, manualClassFilters)),
|
||||
)
|
||||
const filteredGenerationClasses = computed(() =>
|
||||
availableClasses.value.filter((item) => classMatches(item, generationClassFilters)),
|
||||
)
|
||||
const selectedDraftRows = computed(() =>
|
||||
selectedRows.value.filter((item) => item.status === 'Draft'),
|
||||
)
|
||||
const selectedPublishedRows = computed(() =>
|
||||
selectedRows.value.filter((item) => item.status === 'Published'),
|
||||
)
|
||||
const responsibilityText = computed(() => {
|
||||
if (isSuperAdmin.value) return '超级管理员可处理全部教学任务,并保留跨层级纠错权限。'
|
||||
if (isAcademicAdmin.value && isCollegeAdmin.value) {
|
||||
@@ -91,6 +161,70 @@ function canManageRow(row: any) {
|
||||
isCollegeAdmin.value && !isPublic
|
||||
}
|
||||
|
||||
function courseMatches(item: any, filters: Record<string, any>) {
|
||||
const keyword = filters.keyword.trim().toLocaleLowerCase()
|
||||
return (!keyword ||
|
||||
item.code.toLocaleLowerCase().includes(keyword) ||
|
||||
item.name.toLocaleLowerCase().includes(keyword)) &&
|
||||
(!filters.collegeId || item.collegeId === filters.collegeId) &&
|
||||
(!filters.nature || item.nature === filters.nature)
|
||||
}
|
||||
|
||||
function filteredMajors(collegeId?: string) {
|
||||
return majors.value
|
||||
.filter((item) =>
|
||||
item.isEnabled &&
|
||||
(!collegeId || item.collegeId === collegeId) &&
|
||||
availableClasses.value.some((administrativeClass) =>
|
||||
administrativeClass.majorId === item.id),
|
||||
)
|
||||
.sort((a, b) => a.code.localeCompare(b.code))
|
||||
}
|
||||
|
||||
function classMatches(item: any, filters: Record<string, any>) {
|
||||
const keyword = filters.keyword.trim().toLocaleLowerCase()
|
||||
const major = majorById.value.get(item.majorId)
|
||||
return (!keyword ||
|
||||
item.code.toLocaleLowerCase().includes(keyword) ||
|
||||
item.name.toLocaleLowerCase().includes(keyword) ||
|
||||
item.majorName.toLocaleLowerCase().includes(keyword)) &&
|
||||
(!filters.collegeId || major?.collegeId === filters.collegeId) &&
|
||||
(!filters.majorId || item.majorId === filters.majorId) &&
|
||||
(!filters.grade || item.grade === filters.grade)
|
||||
}
|
||||
|
||||
function resetOptionFilters(filters: Record<string, any>) {
|
||||
Object.keys(filters).forEach((key) => {
|
||||
filters[key] = key === 'keyword' ? '' : undefined
|
||||
})
|
||||
}
|
||||
|
||||
function onClassCollegeChanged(filters: Record<string, any>) {
|
||||
if (filters.majorId &&
|
||||
majorById.value.get(filters.majorId)?.collegeId !== filters.collegeId) {
|
||||
filters.majorId = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function selectFilteredClasses(target: Record<string, any>, filtered: any[]) {
|
||||
const selected = new Set<string>(target.classIds ?? [])
|
||||
filtered.forEach((item) => selected.add(item.id))
|
||||
target.classIds = [...selected]
|
||||
}
|
||||
|
||||
function clearFilteredClasses(target: Record<string, any>, filtered: any[]) {
|
||||
const filteredIds = new Set(filtered.map((item) => item.id))
|
||||
target.classIds = (target.classIds ?? []).filter((id: string) => !filteredIds.has(id))
|
||||
}
|
||||
|
||||
function rowSelectable(row: any) {
|
||||
return canManageRow(row) && ['Draft', 'Published'].includes(row.status)
|
||||
}
|
||||
|
||||
function onTaskSelectionChanged(selection: any[]) {
|
||||
selectedRows.value = selection
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -102,6 +236,7 @@ async function load() {
|
||||
})
|
||||
rows.value = data.items
|
||||
total.value = data.total
|
||||
selectedRows.value = []
|
||||
} catch (error) {
|
||||
ElMessage.error(apiErrorMessage(error))
|
||||
} finally {
|
||||
@@ -141,6 +276,8 @@ function resetForm(detail?: any) {
|
||||
function openCreate() {
|
||||
editingId.value = ''
|
||||
resetForm()
|
||||
resetOptionFilters(manualCourseFilters)
|
||||
resetOptionFilters(manualClassFilters)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
@@ -149,6 +286,8 @@ async function openEdit(row: any) {
|
||||
const detail = (await http.get(`/teaching-tasks/${row.id}`)).data
|
||||
editingId.value = row.id
|
||||
resetForm(detail)
|
||||
resetOptionFilters(manualCourseFilters)
|
||||
resetOptionFilters(manualClassFilters)
|
||||
await loadManualEligibleTeachers()
|
||||
dialogVisible.value = true
|
||||
} catch (error) {
|
||||
@@ -238,6 +377,52 @@ async function closeTask(row: any) {
|
||||
}
|
||||
}
|
||||
|
||||
async function batchAction(
|
||||
operation: 'Publish' | 'Delete' | 'Archive',
|
||||
targets: any[],
|
||||
) {
|
||||
if (!targets.length) return
|
||||
const descriptions = {
|
||||
Publish: {
|
||||
title: '批量发布教学任务',
|
||||
message: `将发布选中的 ${targets.length} 个草稿。发布前会逐项校验教师资格、行政班和容量,确定继续吗?`,
|
||||
confirmButtonText: '批量发布',
|
||||
success: '教学任务已批量发布',
|
||||
type: 'warning',
|
||||
},
|
||||
Delete: {
|
||||
title: '批量删除教学任务',
|
||||
message: `将永久删除选中的 ${targets.length} 个草稿,此操作不可撤销。确定继续吗?`,
|
||||
confirmButtonText: '批量删除',
|
||||
success: '教学任务草稿已批量删除',
|
||||
type: 'error',
|
||||
},
|
||||
Archive: {
|
||||
title: '批量归档教学任务',
|
||||
message: `将选中的 ${targets.length} 个已发布任务结课并归档,后续将停止选课和排课调整。确定继续吗?`,
|
||||
confirmButtonText: '批量归档',
|
||||
success: '教学任务已批量归档',
|
||||
type: 'warning',
|
||||
},
|
||||
} as const
|
||||
const description = descriptions[operation]
|
||||
try {
|
||||
await ElMessageBox.confirm(description.message, description.title, {
|
||||
type: description.type,
|
||||
confirmButtonText: description.confirmButtonText,
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
const { data } = await http.post('/teaching-tasks/batch', {
|
||||
operation,
|
||||
ids: targets.map((item) => item.id),
|
||||
})
|
||||
ElMessage.success(`${description.success},共 ${data.affectedCount} 个`)
|
||||
await load()
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
|
||||
}
|
||||
}
|
||||
|
||||
function openGeneration() {
|
||||
const currentTerm = terms.value.find((item) => item.isCurrent)
|
||||
Object.assign(generationForm, {
|
||||
@@ -250,6 +435,8 @@ function openGeneration() {
|
||||
classIds: [],
|
||||
})
|
||||
eligibleTeachers.value = []
|
||||
resetOptionFilters(generationCourseFilters)
|
||||
resetOptionFilters(generationClassFilters)
|
||||
generationDialog.value = true
|
||||
}
|
||||
|
||||
@@ -347,7 +534,46 @@ onMounted(async () => {
|
||||
<el-button :icon="Refresh" @click="resetFilters">重置</el-button>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="rows" class="data-table task-table">
|
||||
<div v-if="selectedRows.length" class="batch-action-bar">
|
||||
<div>
|
||||
<b>已选择 {{ selectedRows.length }} 个教学任务</b>
|
||||
<span>可操作:{{ selectedDraftRows.length }} 个草稿,{{ selectedPublishedRows.length }} 个已发布</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
:disabled="!selectedDraftRows.length"
|
||||
@click="batchAction('Publish', selectedDraftRows)"
|
||||
>
|
||||
发布草稿({{ selectedDraftRows.length }})
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
:disabled="!selectedDraftRows.length"
|
||||
@click="batchAction('Delete', selectedDraftRows)"
|
||||
>
|
||||
删除草稿({{ selectedDraftRows.length }})
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
:disabled="!selectedPublishedRows.length"
|
||||
@click="batchAction('Archive', selectedPublishedRows)"
|
||||
>
|
||||
归档已发布({{ selectedPublishedRows.length }})
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="rows"
|
||||
class="data-table task-table"
|
||||
@selection-change="onTaskSelectionChanged"
|
||||
>
|
||||
<el-table-column type="selection" width="48" :selectable="rowSelectable" />
|
||||
<el-table-column label="任务编号" width="155"><template #default="{ row }"><span class="registry-number">{{ row.taskNumber }}</span></template></el-table-column>
|
||||
<el-table-column label="教学班 / 课程" min-width="230">
|
||||
<template #default="{ row }">
|
||||
@@ -371,7 +597,7 @@ onMounted(async () => {
|
||||
<el-button v-if="canManageRow(row) && row.status === 'Draft'" link type="danger" @click="remove(row)">删除</el-button>
|
||||
<el-button v-if="canManageRow(row) && row.status === 'Published'" link type="warning" @click="closeTask(row)">结课</el-button>
|
||||
<span v-if="!canManageRow(row) && row.status !== 'Closed'" class="muted-action">只读</span>
|
||||
<span v-if="row.status === 'Closed'" class="muted-action">已归档</span>
|
||||
<span v-if="row.status === 'Closed'" class="muted-action">已结课归档</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty><el-empty description="没有符合条件的教学任务" /></template>
|
||||
@@ -390,7 +616,24 @@ onMounted(async () => {
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<el-form-item label="开课学期" required><el-select v-model="form.academicTermId" @change="loadManualEligibleTeachers"><el-option v-for="item in terms" :key="item.id" :label="item.name" :value="item.id" /></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 manageableCourses" :key="item.id" :label="`${item.code} · ${item.name}`" :value="item.id" /></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}`" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="option-filter-panel">
|
||||
<span class="option-filter-title">筛选课程</span>
|
||||
<div class="option-filter-grid course">
|
||||
<el-input v-model="manualCourseFilters.keyword" clearable placeholder="课程代码或名称" />
|
||||
<el-select v-model="manualCourseFilters.collegeId" clearable placeholder="全部开课单位">
|
||||
<el-option v-for="item in courseColleges" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<el-select v-model="manualCourseFilters.nature" clearable placeholder="全部课程性质">
|
||||
<el-option v-for="(label, value) in courseNatureLabels" :key="value" :label="label" :value="value" />
|
||||
</el-select>
|
||||
</div>
|
||||
<small>当前显示 {{ filteredManageableCourses.length }} 门可维护课程</small>
|
||||
</div>
|
||||
<div class="form-grid three">
|
||||
<el-form-item label="容量"><el-input-number v-model="form.capacity" :min="1" /></el-form-item>
|
||||
@@ -410,8 +653,29 @@ onMounted(async () => {
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="关联行政班">
|
||||
<el-select v-model="form.classIds" multiple filterable>
|
||||
<el-option v-for="item in availableClasses" :key="item.id" :label="`${item.code} · ${item.name}`" :value="item.id" />
|
||||
<div class="class-picker">
|
||||
<div class="option-filter-grid classes">
|
||||
<el-input v-model="manualClassFilters.keyword" clearable placeholder="班级代码、名称或专业" />
|
||||
<el-select v-model="manualClassFilters.collegeId" clearable placeholder="全部学院" @change="onClassCollegeChanged(manualClassFilters)">
|
||||
<el-option v-for="item in classColleges" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<el-select v-model="manualClassFilters.majorId" clearable placeholder="全部专业">
|
||||
<el-option v-for="item in manualClassMajors" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<el-select v-model="manualClassFilters.grade" clearable placeholder="全部年级">
|
||||
<el-option v-for="grade in classGrades" :key="grade" :label="`${grade} 级`" :value="grade" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="class-picker-actions">
|
||||
<span>筛选到 {{ filteredManualClasses.length }} 个班,已选择 {{ form.classIds?.length ?? 0 }} 个</span>
|
||||
<div>
|
||||
<el-button link type="primary" @click="selectFilteredClasses(form, filteredManualClasses)">选择筛选结果</el-button>
|
||||
<el-button link @click="clearFilteredClasses(form, filteredManualClasses)">取消筛选结果</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-select v-model="form.classIds" multiple filterable collapse-tags collapse-tags-tooltip>
|
||||
<el-option v-for="item in filteredManualClasses" :key="item.id" :label="`${item.code} · ${item.name}`" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="form.notes" type="textarea" :rows="2" /></el-form-item>
|
||||
@@ -432,10 +696,20 @@ onMounted(async () => {
|
||||
</el-form-item>
|
||||
<el-form-item label="公共课程" required>
|
||||
<el-select v-model="generationForm.courseId" filterable @change="loadEligibleTeachers">
|
||||
<el-option v-for="item in publicCourses" :key="item.id" :label="`${item.code} · ${item.name}`" :value="item.id" />
|
||||
<el-option v-for="item in filteredPublicCourses" :key="item.id" :label="`${item.code} · ${item.name}`" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="option-filter-panel">
|
||||
<span class="option-filter-title">筛选公共课程</span>
|
||||
<div class="option-filter-grid course compact">
|
||||
<el-input v-model="generationCourseFilters.keyword" clearable placeholder="课程代码或名称" />
|
||||
<el-select v-model="generationCourseFilters.collegeId" clearable placeholder="全部开课单位">
|
||||
<el-option v-for="item in courseColleges" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</div>
|
||||
<small>当前显示 {{ filteredPublicCourses.length }} 门公共课程</small>
|
||||
</div>
|
||||
<el-alert
|
||||
:title="eligibleTeachers.length ? `已有 ${eligibleTeachers.length} 名教师审核通过` : '请选择课程查看可分配教师'"
|
||||
:type="eligibleTeachers.length ? 'success' : 'warning'"
|
||||
@@ -447,8 +721,29 @@ onMounted(async () => {
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-form-item label="参与合班的行政班" required style="margin-top: 16px">
|
||||
<div class="class-picker">
|
||||
<div class="option-filter-grid classes">
|
||||
<el-input v-model="generationClassFilters.keyword" clearable placeholder="班级代码、名称或专业" />
|
||||
<el-select v-model="generationClassFilters.collegeId" clearable placeholder="全部学院" @change="onClassCollegeChanged(generationClassFilters)">
|
||||
<el-option v-for="item in classColleges" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<el-select v-model="generationClassFilters.majorId" clearable placeholder="全部专业">
|
||||
<el-option v-for="item in generationClassMajors" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<el-select v-model="generationClassFilters.grade" clearable placeholder="全部年级">
|
||||
<el-option v-for="grade in classGrades" :key="grade" :label="`${grade} 级`" :value="grade" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="class-picker-actions">
|
||||
<span>筛选到 {{ filteredGenerationClasses.length }} 个班,已选择 {{ generationForm.classIds?.length ?? 0 }} 个</span>
|
||||
<div>
|
||||
<el-button link type="primary" @click="selectFilteredClasses(generationForm, filteredGenerationClasses)">选择筛选结果</el-button>
|
||||
<el-button link @click="clearFilteredClasses(generationForm, filteredGenerationClasses)">取消筛选结果</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-select v-model="generationForm.classIds" multiple filterable collapse-tags>
|
||||
<el-option v-for="item in availableClasses" :key="item.id" :label="`${item.code} · ${item.name}`" :value="item.id" />
|
||||
<el-option v-for="item in filteredGenerationClasses" :key="item.id" :label="`${item.code} · ${item.name}`" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div class="form-grid three">
|
||||
|
||||
Reference in New Issue
Block a user