edu/admin/src/views/scheduling/index.vue

985 lines
35 KiB
Vue
Raw Normal View History

2025-12-26 08:15:02 +00:00
<template>
<div class="index-lists">
<el-card class="!border-none" shadow="never">
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" :inline="true">
<el-form-item label="班级" prop="classId">
<el-select
v-model="queryParams.classId"
placeholder="请输入班级名称搜索"
:remote="true"
:remote-method="handleClassRemoteSearch"
:loading="classSearchLoading"
filterable
clearable
remote-show-suffix
class="w-[280px]"
@change="handleClassChange"
>
<el-option
v-for="item in getClassLists.lists"
:key="item.id"
:value="item.id"
:label="item.className"
@click="handleStudentClick(item)"
>
<template #default>
<span
style="color: var(--el-text-color-secondary); font-size: 13px"
>
{{ item.classCode }}
</span>
<span style="margin-left: 8px">{{ item.className }}</span>
</template>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="semesterId">
<el-select
v-model="queryParams.semesterId"
class="w-[280px]"
clearable
@change="handleSemesterChange"
>
<el-option
v-for="college in getConfigLists.lists"
:key="college.id"
:value="college.id"
:label="college.config"
/>
</el-select>
</el-form-item>
2025-12-29 06:46:24 +00:00
<el-form-item label="当前周数" prop="weekNumber">
<el-select
v-model="selectedWeekNumber"
:disabled="!queryParams.semesterId || weekOptions.length === 0"
class="w-[120px]"
placeholder="请选择周数"
@change="handleWeekChange"
>
<el-option
v-for="week in weekOptions"
:key="week"
:label="`第${week}周`"
:value="week"
/>
</el-select>
<div class="text-xs text-gray-500 ml-2">
{{ currentWeekDateInfo }}
</div>
</el-form-item>
2025-12-29 06:15:08 +00:00
<!-- 选中时间段信息显示和清除按钮 -->
<el-form-item v-if="selectedTimeSlot" label="已选中时间段">
<el-tag type="info" size="large" closable @close="clearSelectedTimeSlot">
{{ getSelectedTimeSlotText() }}
</el-tag>
</el-form-item>
2025-12-26 08:15:02 +00:00
</el-form>
</el-card>
<div class="flex">
<el-card class="!border-none mt-4 mr-4" shadow="never" style="width: 75%">
<el-table class="mb-4" size="large" v-loading="loading" :data="tableData" border>
<!-- 序号列包含时间类型状态 -->
<el-table-column label="节次" width="50" class-name="first-column" align="left">
<template #default="{ $index }">
<div class="index-info">
<div class="index-details">
<div class="index-number">{{ $index + 1 }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="时间" width="80" class-name="first-column" align="left">
<template #default="{ row }">
<div class="index-info">
<div class="index-details">
<div class="flex justify-center" style="font-size: 12px">
{{ getRowStartTime(row) }}
</div>
<div class="flex justify-center" style="font-size: 6px"></div>
<div class="flex justify-center" style="font-size: 12px">
{{ getRowEndTime(row) }}
</div>
</div>
</div>
</template>
</el-table-column>
2025-12-29 06:15:08 +00:00
<!-- 星期列改为循环渲染方便处理点击事件 -->
<el-table-column
v-for="day in weekDays"
:key="day.value"
:label="day.label"
min-width="80"
align="center"
>
<template #default="{ row, $index }">
<div
:class="[
'time-slot-cell',
isTimeSlotSelected($index, day.value, row[day.value]) ? 'time-slot-selected' : '',
row[day.value] ? '' : 'empty-slot'
]"
@click="handleTimeSlotClick($index, day.value, row[day.value])"
>
<!-- 如果有课程信息显示课程 -->
<div v-if="row[day.value]" class="course-info">
<div class="course-name">{{ row[day.value].courseName || '课程' }}</div>
<div class="course-details">
<div>教师: {{ row[day.value].teacherName || '未知' }}</div>
<div>教室: {{ row[day.value].classroomName || '未知' }}</div>
</div>
</div>
<!-- 空时间段显示可选状态 -->
<div v-else class="empty-slot-content">
<el-icon v-if="isTimeSlotSelected($index, day.value, row[day.value])" class="check-icon">
<Check />
</el-icon>
<span v-else class="empty-text">点击选择</span>
</div>
</div>
2025-12-26 08:15:02 +00:00
</template>
</el-table-column>
</el-table>
</el-card>
<el-card class="mt-4" shadow="never" style="width: 24%">
<!-- 改为标签页 -->
<el-tabs v-model="activeTab" style="margin-top: -10px">
<el-tab-pane label="课程列表" name="course" class="course-list-container">
<div class="mb-4">
<h3 class="text-lg font-semibold">课程列表</h3>
<p class="text-sm text-gray-500 mt-1">当前班级和学期下的可用课程</p>
</div>
<div
v-if="getCourseLists.lists.length === 0"
class="text-center py-8 text-gray-400"
>
暂无课程数据请选择班级和学期
</div>
<div v-else class="space-y-4 max-h-[700px] overflow-y-auto">
<el-card
v-for="item in getCourseLists.lists"
2025-12-29 06:46:24 +00:00
:key="`course-${item.id}`"
2025-12-26 08:15:02 +00:00
shadow="hover"
2025-12-29 06:15:08 +00:00
:class="['cursor-pointer transition-all duration-200', selectedCourseId === item.id ? 'selected-card' : 'hover:bg-blue-50']"
2025-12-26 08:15:02 +00:00
@click="handleCourseClick(item)"
>
<div class="p-0">
<div class="flex justify-between items-start">
<div>
<h4 class="font-medium text-sm">
课程{{ item.courseName }}
</h4>
</div>
2025-12-29 06:46:24 +00:00
<el-tag v-if="selectedCourseId === item.id" size="small" type="info">
{{ item.totalWeeks }}
</el-tag>
2025-12-26 08:15:02 +00:00
</div>
<div class="flex justify-between text-xs text-gray-600">
<span>教师: {{ item.teacherName }}</span>
2025-12-29 06:46:24 +00:00
<span>总学时: {{ item.totalHours }}</span>
2025-12-26 08:15:02 +00:00
</div>
<div
v-if="selectedCourseId === item.id"
class="pt-3 border-t border-gray-100"
>
<div class="text-xs text-gray-700">
<div class="flex items-center mb-1">
2025-12-29 06:46:24 +00:00
<span class="font-medium">周学时:</span>
<span>{{ Math.round(item.totalHours / item.totalWeeks) }}</span>
2025-12-26 08:15:02 +00:00
</div>
<div class="flex items-center">
2025-12-29 06:46:24 +00:00
<span class="font-medium">任务代码:</span>
<span>{{ item.taskCode }}</span>
2025-12-26 08:15:02 +00:00
</div>
</div>
</div>
</div>
</el-card>
</div>
<div class="flex justify-end mt-6">
<pagination
layout="prev, pager, next"
v-model="pager"
@change="getLists"
/>
</div>
</el-tab-pane>
<el-tab-pane label="教室列表" name="classroom" class="course-list-container">
<div class="mb-4">
<h3 class="text-lg font-semibold">教室列表</h3>
<p class="text-sm text-gray-500 mt-1">当前可用的教室资源</p>
</div>
<div
v-if="getRoomLists.lists.length === 0"
class="text-center py-8 text-gray-400"
>
暂无教室数据
</div>
<div v-else class="space-y-4 max-h-[700px] overflow-y-auto">
<el-card
v-for="item in getRoomLists.lists"
2025-12-29 06:46:24 +00:00
:key="`room-${item.id}`"
2025-12-26 08:15:02 +00:00
shadow="hover"
2025-12-29 06:15:08 +00:00
:class="['cursor-pointer transition-all duration-200', selectedRoomId === item.id ? 'selected-card' : 'hover:bg-blue-50']"
2025-12-26 08:15:02 +00:00
@click="handleClassroomClick(item)"
>
<div class="p-0">
<div class="flex justify-between items-start">
<div>
<h4 class="font-medium text-sm">
教室{{ item.classroomName }}
</h4>
</div>
</div>
<div class="flex justify-between text-xs text-gray-600">
<span>类型: {{ item.classroomTypeName }}</span>
<span>容量: {{ item.capacity }}</span>
</div>
<div
v-if="selectedRoomId === item.id"
class="pt-3 border-t border-gray-100"
>
<div class="text-xs text-gray-700">
<div class="flex items-center mb-1">
<span class="font-medium">编号:</span>
<span>{{ item.classroomCode }}</span>
</div>
<div class="flex items-center">
<span class="font-medium">位置:</span>
<span>{{ item.location }}</span>
</div>
</div>
</div>
</div>
</el-card>
</div>
</el-tab-pane>
</el-tabs>
2025-12-29 06:15:08 +00:00
<!-- 排课操作按钮 -->
<div v-if="selectedTimeSlot" class="mt-6 p-4 border-t">
<h4 class="text-lg font-semibold mb-4">排课操作</h4>
<div class="space-y-4">
<div class="text-sm">
<div class="flex items-center mb-2">
<span class="font-medium mr-2">选中的时间段:</span>
<span>{{ getSelectedTimeSlotText() }}</span>
</div>
2025-12-29 06:46:24 +00:00
<div class="flex items-center mb-2">
<span class="font-medium mr-2">具体日期:</span>
<span>{{ selectedDate || '请选择周数' }}</span>
</div>
2025-12-29 06:15:08 +00:00
<div class="flex items-center mb-2">
<span class="font-medium mr-2">选中的课程:</span>
<span>{{ selectedCourseName || '未选择' }}</span>
</div>
2025-12-29 06:46:24 +00:00
<div class="flex items-center mb-2">
<span class="font-medium mr-2">授课教师:</span>
<span>{{ selectedCourseTeacherName || '未选择' }}</span>
</div>
2025-12-29 06:15:08 +00:00
<div class="flex items-center">
<span class="font-medium mr-2">选中的教室:</span>
<span>{{ selectedRoomName || '未选择' }}</span>
</div>
</div>
<el-button
type="primary"
size="large"
2025-12-29 06:46:24 +00:00
:disabled="!selectedCourseId || !selectedTimeSlot || !selectedWeekNumber"
2025-12-29 06:15:08 +00:00
@click="handleScheduleCourse"
plain
>
安排课程
</el-button>
<el-button
type="danger"
size="large"
2025-12-29 06:46:24 +00:00
:disabled="!selectedTimeSlot || !selectedTimeSlot.hasCourse"
2025-12-29 06:15:08 +00:00
@click="handleRemoveCourse"
plain
>
移除课程
</el-button>
</div>
</div>
2025-12-26 08:15:02 +00:00
</el-card>
</div>
</div>
</template>
<script lang="ts" setup>
2025-12-29 06:46:24 +00:00
import { computed, reactive, ref, onBeforeUnmount } from 'vue'
2025-12-29 06:15:08 +00:00
import { Check } from '@element-plus/icons-vue'
2025-12-26 08:15:02 +00:00
import { classLists } from '@/api/class'
import { configLists } from '@/api/config'
2025-12-29 06:15:08 +00:00
import { courseAvailableRooms, courseAvailableSlots, courseSchedule } from '@/api/course'
2025-12-26 08:15:02 +00:00
import { taskLists } from '@/api/task'
import { timeCurrentSemester } from '@/api/time'
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
2025-12-29 06:46:24 +00:00
// 星期类型
interface WeekDay {
label: string
value: number
}
// 课程数据类型
interface CourseItem {
id: number
courseId: number
courseName: string
taskCode: string
totalHours: number
teacherId: number
teacherName: string
totalWeeks: number
}
// 教室数据类型
interface ClassroomItem {
id: number
classroomName: string
classroomCode: string
classroomTypeName: string
location: string
capacity: number
}
// 选中时间段类型
interface SelectedTimeSlot {
id: number
rowIndex: number
dayOfWeek: number
startTime: string
endTime: string
hasCourse: boolean
courseData?: any
}
// 学期配置类型
interface SemesterConfigItem {
id: number
academicYear: string
semesterCode: string
config: string
startDate: string
endDate: string
}
// 班级数据类型
interface ClassItem {
id: number
className: string
classCode: string
maxStudentCount: number
}
2025-12-29 06:15:08 +00:00
// 星期数组
2025-12-29 06:46:24 +00:00
const weekDays: WeekDay[] = [
2025-12-29 06:15:08 +00:00
{ label: '星期一', value: 1 },
{ label: '星期二', value: 2 },
{ label: '星期三', value: 3 },
{ label: '星期四', value: 4 },
{ label: '星期五', value: 5 },
{ label: '星期六', value: 6 },
{ label: '星期日', value: 7 }
]
2025-12-29 06:46:24 +00:00
const getConfigLists = ref<{ lists: SemesterConfigItem[] }>({ lists: [] })
const getClassLists = ref<{ lists: ClassItem[] }>({ lists: [] })
2025-12-26 08:15:02 +00:00
const classSearchLoading = ref(false)
const classSearchTimer = ref<number | null>(null)
const activeTab = ref('course')
// 时间段数据
const getTimeData = ref<{
lists: Array<{
timeSlotId: number
classId: number
classroomId: number
courseId: number
semesterId: number
teacherId: number
}>
}>({ lists: [] })
2025-12-29 06:46:24 +00:00
// 课程列表数据 - 明确类型
const getCourseLists = ref<{ lists: CourseItem[] }>({ lists: [] })
2025-12-26 08:15:02 +00:00
// 课程加载状态
const courseLoading = ref(false)
// 选中的课程ID
const selectedCourseId = ref<number | null>(null)
2025-12-29 06:46:24 +00:00
// 选中的课程完整对象
const selectedCourse = computed(() => {
if (!selectedCourseId.value) return null
return getCourseLists.value.lists.find(item => item.id === selectedCourseId.value) || null
})
2025-12-29 06:15:08 +00:00
// 选中的课程名称
const selectedCourseName = computed(() => {
2025-12-29 06:46:24 +00:00
return selectedCourse.value?.courseName || '未选择'
2025-12-29 06:15:08 +00:00
})
2025-12-26 08:15:02 +00:00
2025-12-29 06:46:24 +00:00
// 选中的课程教师ID
const selectedCourseTeacherId = computed(() => {
return selectedCourse.value?.teacherId || null
})
// 选中的课程教师姓名
const selectedCourseTeacherName = computed(() => {
return selectedCourse.value?.teacherName || '未选择'
})
// 教室列表数据 - 明确类型
const getRoomLists = ref<{ lists: ClassroomItem[] }>({ lists: [] })
2025-12-26 08:15:02 +00:00
const roomLoading = ref(false)
const selectedRoomId = ref<number | null>(null)
2025-12-29 06:15:08 +00:00
// 选中的教室名称
const selectedRoomName = computed(() => {
2025-12-29 06:46:24 +00:00
if (!selectedRoomId.value) return '未选择'
const room = getRoomLists.value.lists.find(item => item.id === selectedRoomId.value)
return room ? room.classroomName : '未选择'
2025-12-29 06:15:08 +00:00
})
// 选中的时间段
const selectedTimeSlot = ref<SelectedTimeSlot | null>(null)
2025-12-26 08:15:02 +00:00
2025-12-29 06:46:24 +00:00
// 新增:选中的周数
const selectedWeekNumber = ref<number>(1)
// 新增:当前学期配置
const currentSemesterConfig = computed(() => {
if (!queryParams.semesterId) return null
return getConfigLists.value.lists.find(item => item.id.toString() === queryParams.semesterId.toString())
})
// 新增:周数选项
const weekOptions = computed(() => {
if (!selectedCourse.value) return []
const weeks = selectedCourse.value.totalWeeks
return Array.from({ length: weeks }, (_, i) => i + 1)
})
// 新增:当前周对应的日期信息
const currentWeekDateInfo = computed(() => {
if (!selectedWeekNumber.value || !currentSemesterConfig.value) return ''
const startDate = new Date(currentSemesterConfig.value.startDate)
if (isNaN(startDate.getTime())) return '日期格式错误'
// 计算当前周第一天的日期(星期一)
// startDate可能是学期开始日期我们需要根据周数计算
const weekStartDate = new Date(startDate)
// 将开始日期调整到星期一
const dayOfWeek = weekStartDate.getDay() // 0 = 周日, 1 = 周一, ..., 6 = 周六
const daysToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek // 调整到星期一
// 根据周数计算目标周的第一天(星期一)
const targetWeekStart = new Date(weekStartDate)
targetWeekStart.setDate(targetWeekStart.getDate() + daysToMonday + (selectedWeekNumber.value - 1) * 7)
// 格式化日期
const formatDate = (date: Date) => {
return `${date.getMonth() + 1}${date.getDate()}`
}
// 计算本周日期范围
const weekEnd = new Date(targetWeekStart)
weekEnd.setDate(weekEnd.getDate() + 6) // 周日
return `${formatDate(targetWeekStart)} - ${formatDate(weekEnd)}`
})
// 新增:选中的具体日期(用于排课)
const selectedDate = computed(() => {
if (!selectedWeekNumber.value || !currentSemesterConfig.value || !selectedTimeSlot.value) return ''
const startDate = new Date(currentSemesterConfig.value.startDate)
if (isNaN(startDate.getTime())) return '日期格式错误'
// 计算当前周第一天的日期(星期一)
const weekStartDate = new Date(startDate)
const dayOfWeek = weekStartDate.getDay() // 0 = 周日, 1 = 周一, ..., 6 = 周六
const daysToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek // 调整到星期一
// 根据周数计算目标周的第一天(星期一)
const targetWeekStart = new Date(weekStartDate)
targetWeekStart.setDate(targetWeekStart.getDate() + daysToMonday + (selectedWeekNumber.value - 1) * 7)
// 根据星期几计算具体日期
const targetDate = new Date(targetWeekStart)
targetDate.setDate(targetDate.getDate() + selectedTimeSlot.value.dayOfWeek - 1) // -1是因为星期一对应dayOfWeek=1
// 格式化日期为YYYY-MM-DD
const year = targetDate.getFullYear()
const month = String(targetDate.getMonth() + 1).padStart(2, '0')
const day = String(targetDate.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
})
2025-12-26 08:15:02 +00:00
const loading = ref(false)
const queryParams = reactive({
classId: '',
semesterId: '',
maxStudentCount: 0
})
2025-12-29 06:46:24 +00:00
// 周数变化处理
const handleWeekChange = () => {
// 周数变化时,可以在这里处理相关逻辑
console.log('周数变化为:', selectedWeekNumber.value)
2025-12-26 08:15:02 +00:00
}
2025-12-29 06:46:24 +00:00
// 班级变化处理
const handleClassChange = async () => {
// 切换班级时,清空选中的课程、教室、时间段和周数
selectedCourseId.value = null
selectedRoomId.value = null
selectedWeekNumber.value = 1
clearSelectedTimeSlot()
if (queryParams.classId && queryParams.semesterId) {
await fetchCourseLists()
await fetchRoomLists()
}
2025-12-26 08:15:02 +00:00
}
// 学期变化处理
const handleSemesterChange = async () => {
2025-12-29 06:46:24 +00:00
// 切换学期时,清空选中的课程、教室、时间段和周数
selectedCourseId.value = null
selectedRoomId.value = null
selectedWeekNumber.value = 1
clearSelectedTimeSlot()
if (queryParams.classId && queryParams.semesterId) {
await fetchCourseLists()
}
}
const handleStudentClick = (item: ClassItem) => {
queryParams.maxStudentCount = item.maxStudentCount || 0
2025-12-26 08:15:02 +00:00
}
// 获取班级列表
const fetchClassLists = async (params: string) => {
2025-12-29 06:46:24 +00:00
try {
const res = await classLists({
className: params.trim()
})
getClassLists.value = res
} catch (err) {
feedback.msgError('获取班级列表失败')
getClassLists.value = { lists: [] }
} finally {
classSearchLoading.value = false
}
2025-12-26 08:15:02 +00:00
}
2025-12-29 06:46:24 +00:00
2025-12-26 08:15:02 +00:00
const handleClassRemoteSearch = (keyword: string) => {
2025-12-29 06:46:24 +00:00
if (classSearchTimer.value) clearTimeout(classSearchTimer.value)
2025-12-26 08:15:02 +00:00
2025-12-29 06:46:24 +00:00
classSearchTimer.value = setTimeout(async () => {
classSearchLoading.value = true
await fetchClassLists(keyword)
}, 300) as unknown as number
2025-12-26 08:15:02 +00:00
}
// 获取学期列表
const fetchConfigLists = async () => {
2025-12-29 06:46:24 +00:00
try {
const res = await configLists()
const handleData = JSON.parse(JSON.stringify(res))
handleData.lists = handleData.lists.map((item: SemesterConfigItem) => ({
...item,
config: `${item.academicYear}${item.semesterCode === 'SPRING' ? ' 春' : ' 秋'}`
}))
getConfigLists.value = handleData
await fetchSemester()
} catch (err) {
feedback.msgError('获取学期列表失败')
}
2025-12-26 08:15:02 +00:00
}
const fetchSemester = async () => {
2025-12-29 06:46:24 +00:00
try {
const res = await timeCurrentSemester()
if (res) {
queryParams.semesterId = res.id
2025-12-26 08:15:02 +00:00
}
2025-12-29 06:46:24 +00:00
} catch (error) {
console.error('获取学期信息失败', error)
}
2025-12-26 08:15:02 +00:00
}
// 获取课程列表
const fetchCourseLists = async () => {
2025-12-29 06:46:24 +00:00
if (!queryParams.classId || !queryParams.semesterId) return
try {
courseLoading.value = true
const response = await taskLists({
classId: queryParams.classId,
semesterId: queryParams.semesterId
})
const mappedCourses: CourseItem[] = response.lists.map(
(task: any) => ({
id: task.id,
courseId: task.courseId,
courseName: task.courseName,
taskCode: task.taskCode,
totalHours: task.weeklyHours * task.totalWeeks,
teacherId: task.teacherId,
teacherName: task.teacherName,
totalWeeks: task.totalWeeks
})
)
getCourseLists.value = { lists: mappedCourses }
// 课程列表刷新后,校验选中的课程是否仍存在,不存在则清空
if (selectedCourseId.value && !mappedCourses.find(item => item.id === selectedCourseId.value)) {
selectedCourseId.value = null
selectedWeekNumber.value = 1 // 清空周数选择
2025-12-26 08:15:02 +00:00
}
2025-12-29 06:46:24 +00:00
} catch (err) {
console.error('获取课程列表失败', err)
feedback.msgError('获取课程列表失败')
getCourseLists.value = { lists: [] }
} finally {
courseLoading.value = false
}
2025-12-26 08:15:02 +00:00
}
2025-12-29 06:46:24 +00:00
// 课程点击处理(切换选中/取消选中)
const handleCourseClick = (course: CourseItem) => {
selectedCourseId.value = selectedCourseId.value === course.id ? null : course.id
selectedWeekNumber.value = 1
activeTab.value = 'course'
2025-12-26 08:15:02 +00:00
}
// 获取教室列表
const fetchRoomLists = async () => {
2025-12-29 06:46:24 +00:00
try {
roomLoading.value = true
const response = await courseAvailableRooms({
num: queryParams.maxStudentCount
})
const mappedRooms: ClassroomItem[] = response.map(
(task: any) => ({
id: task.id,
classroomName: task.classroomName,
classroomCode: task.classroomCode,
classroomTypeName: task.classroomTypeName,
location: task.location,
capacity: task.capacity
})
)
getRoomLists.value = { lists: mappedRooms }
// 教室列表刷新后,校验选中的教室是否仍存在,不存在则清空
if (selectedRoomId.value && !mappedRooms.find(item => item.id === selectedRoomId.value)) {
selectedRoomId.value = null
2025-12-26 08:15:02 +00:00
}
2025-12-29 06:46:24 +00:00
} catch (err) {
console.error('获取教室列表失败', err)
feedback.msgError('获取教室列表失败')
getRoomLists.value = { lists: [] }
} finally {
roomLoading.value = false
}
2025-12-26 08:15:02 +00:00
}
2025-12-29 06:46:24 +00:00
2025-12-26 08:15:02 +00:00
// 教室点击处理
2025-12-29 06:46:24 +00:00
const handleClassroomClick = (classroom: ClassroomItem) => {
selectedRoomId.value = selectedRoomId.value === classroom.id ? null : classroom.id
activeTab.value = 'classroom'
2025-12-26 08:15:02 +00:00
}
2025-12-29 06:15:08 +00:00
// 处理时间段点击
const handleTimeSlotClick = (rowIndex: number, dayOfWeek: number, cellData: any) => {
2025-12-29 06:46:24 +00:00
const row = tableData.value[rowIndex]
const id = cellData?.id || Date.now()
const startTime = getRowStartTime(row)
const endTime = getRowEndTime(row)
// 取消选中(点击已选中的时间段)
if (selectedTimeSlot.value &&
selectedTimeSlot.value.rowIndex === rowIndex &&
selectedTimeSlot.value.dayOfWeek === dayOfWeek) {
selectedTimeSlot.value = null
return
}
// 设置选中的时间段
selectedTimeSlot.value = {
id,
rowIndex,
dayOfWeek,
startTime,
endTime,
hasCourse: !!cellData,
courseData: cellData
}
// 自动选中对应课程基于courseId精准匹配
if (cellData && cellData.courseId) {
const targetCourse = getCourseLists.value.lists.find(item => item.courseId === cellData.courseId)
if (targetCourse) {
selectedCourseId.value = targetCourse.id
// 如果有课程设置默认周数为第1周
if (targetCourse.totalWeeks > 0) {
selectedWeekNumber.value = 1
}
2025-12-29 06:15:08 +00:00
}
2025-12-29 06:46:24 +00:00
}
2025-12-29 06:15:08 +00:00
}
// 判断时间段是否被选中
const isTimeSlotSelected = (rowIndex: number, dayOfWeek: number, cellData: any) => {
2025-12-29 06:46:24 +00:00
return selectedTimeSlot.value &&
selectedTimeSlot.value.rowIndex === rowIndex &&
selectedTimeSlot.value.dayOfWeek === dayOfWeek
2025-12-29 06:15:08 +00:00
}
// 清除选中的时间段
const clearSelectedTimeSlot = () => {
2025-12-29 06:46:24 +00:00
selectedTimeSlot.value = null
2025-12-29 06:15:08 +00:00
}
// 获取选中时间段的显示文本
const getSelectedTimeSlotText = () => {
2025-12-29 06:46:24 +00:00
const slot = selectedTimeSlot.value
if (!slot) return ''
const dayText = weekDays.find(day => day.value === slot.dayOfWeek)?.label || ''
return `${slot.rowIndex + 1}${dayText} ${slot.startTime}-${slot.endTime}`
2025-12-29 06:15:08 +00:00
}
// 安排课程
const handleScheduleCourse = async () => {
2025-12-29 06:46:24 +00:00
// 增强校验确保时间段、课程、教师ID、周数都存在
if (!selectedTimeSlot.value || !selectedCourseId.value || !selectedCourseTeacherId.value || !selectedWeekNumber.value) {
feedback.msgError('请先选择有效的时间段、课程和周数')
return
}
try {
// 排课API参数新增 teacherId 和 date 传递
await courseSchedule({
timeSlotId: selectedTimeSlot.value.id,
courseId: selectedCourseId.value,
classroomId: selectedRoomId.value,
semesterId: queryParams.semesterId,
classId: queryParams.classId,
teacherId: selectedCourseTeacherId.value,
date: selectedDate.value // 使用计算出的具体日期
})
2025-12-29 06:15:08 +00:00
2025-12-29 06:46:24 +00:00
feedback.msgSuccess('排课成功')
// 刷新课表数据
await getTimeLists()
// 清空选中状态,提升用户体验
clearSelectedTimeSlot()
selectedCourseId.value = null
selectedRoomId.value = null
selectedWeekNumber.value = 1
} catch (error) {
console.error('排课失败', error)
feedback.msgError('排课失败,请稍后重试')
}
2025-12-29 06:15:08 +00:00
}
// 移除课程
const handleRemoveCourse = async () => {
2025-12-29 06:46:24 +00:00
if (!selectedTimeSlot.value || !selectedTimeSlot.value.hasCourse) {
feedback.msgError('请先选择一个有课程的时间段')
return
}
try {
// TODO: 调用移除课程API
// 示例await removeCourse({ timeSlotId: selectedTimeSlot.value.id })
2025-12-29 06:15:08 +00:00
2025-12-29 06:46:24 +00:00
feedback.msgSuccess('课程移除成功')
await getTimeLists()
clearSelectedTimeSlot()
} catch (error) {
console.error('移除课程失败', error)
feedback.msgError('移除课程失败,请稍后重试')
}
2025-12-29 06:15:08 +00:00
}
2025-12-26 08:15:02 +00:00
// 清理定时器
onBeforeUnmount(() => {
2025-12-29 06:46:24 +00:00
if (classSearchTimer.value) clearTimeout(classSearchTimer.value)
2025-12-26 08:15:02 +00:00
})
// 所有数据列表
const allLists = ref<any[]>([])
const { pager, getLists } = usePaging({
2025-12-29 06:46:24 +00:00
fetchFun: taskLists,
params: queryParams,
size: 7
2025-12-26 08:15:02 +00:00
})
// 请求数据列表
const getTimeLists = async () => {
2025-12-29 06:46:24 +00:00
loading.value = true
try {
const res = await courseAvailableSlots()
allLists.value = res
return Promise.resolve(res)
} catch (err) {
console.error('获取时间段列表失败', err)
return Promise.reject(err)
} finally {
loading.value = false
}
2025-12-26 08:15:02 +00:00
}
// 处理表格数据,按节次分组,构建星期矩阵
const tableData = computed(() => {
2025-12-29 06:46:24 +00:00
const timeSlotMap = new Map<string, Record<number, any>>()
allLists.value.forEach((item) => {
const key = `${item.startTime}-${item.endTime}`
if (!timeSlotMap.has(key)) {
timeSlotMap.set(key, {})
}
const slotGroup = timeSlotMap.get(key)!
slotGroup[item.dayOfWeek] = item
})
return Array.from(timeSlotMap.values()).sort((a, b) => {
const aTime = Object.values(a)[0]?.startTime || ''
const bTime = Object.values(b)[0]?.startTime || ''
return aTime.localeCompare(bTime)
})
2025-12-26 08:15:02 +00:00
})
const getRowBaseInfo = (row: Record<number, any>) => {
2025-12-29 06:46:24 +00:00
return Object.values(row).find((item) => item) || {}
2025-12-26 08:15:02 +00:00
}
const getRowStartTime = (row: Record<number, any>) => {
2025-12-29 06:46:24 +00:00
const baseInfo = getRowBaseInfo(row)
const start = baseInfo?.startTime
if (start == null) return ''
const s = String(start)
return s.length > 3 ? s.slice(0, -3) : ''
2025-12-26 08:15:02 +00:00
}
const getRowEndTime = (row: Record<number, any>) => {
2025-12-29 06:46:24 +00:00
const baseInfo = getRowBaseInfo(row)
const end = baseInfo?.endTime
if (end == null) return ''
const s = String(end)
return s.length > 3 ? s.slice(0, -3) : ''
2025-12-26 08:15:02 +00:00
}
// 初始化时获取数据
getTimeLists()
fetchClassLists('')
fetchConfigLists()
</script>
<style scoped>
:deep(.first-column) {
background-color: #f8f8f8;
}
.course-list-container {
height: auto;
display: flex;
flex-direction: column;
}
.course-list-container .el-card {
border: 1px solid #e4e7ed;
}
.course-list-container .el-card:hover {
border-color: #409eff;
}
2025-12-29 06:15:08 +00:00
.selected-card {
border-color: #409eff !important;
background-color: #f0f7ff;
box-shadow: 0 2px 12px rgba(64, 158, 255, 0.2);
}
2025-12-26 08:15:02 +00:00
:deep(.el-tag) {
height: 24px;
line-height: 22px;
}
2025-12-29 06:15:08 +00:00
/* 时间段单元格样式 */
.time-slot-cell {
height: 100%;
cursor: pointer;
padding: 4px;
border: 2px solid transparent;
transition: all 0.2s ease;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.time-slot-cell:hover {
border-color: #96caff;
}
.time-slot-selected {
border-color: #409eff !important;
background-color: #f0f9ff;
border-color: #e0f2ff;
}
.empty-slot {
background-color: #fafafa;
}
.empty-slot-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #909399;
font-size: 12px;
}
.empty-slot-content .check-icon {
color: #67c23a;
font-size: 16px;
margin-bottom: 4px;
}
.course-info {
text-align: center;
width: 100%;
}
.course-name {
font-weight: 500;
font-size: 12px;
margin-bottom: 4px;
color: #303133;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.course-details {
font-size: 10px;
color: #606266;
line-height: 1.2;
}
.course-details div {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
2025-12-26 08:15:02 +00:00
</style>