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

985 lines
35 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>
<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>
<!-- 选中时间段信息显示和清除按钮 -->
<el-form-item v-if="selectedTimeSlot" label="已选中时间段">
<el-tag type="info" size="large" closable @close="clearSelectedTimeSlot">
{{ getSelectedTimeSlotText() }}
</el-tag>
</el-form-item>
</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>
<!-- 星期列改为循环渲染,方便处理点击事件 -->
<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>
</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"
:key="`course-${item.id}`"
shadow="hover"
:class="['cursor-pointer transition-all duration-200', selectedCourseId === item.id ? 'selected-card' : 'hover:bg-blue-50']"
@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>
<el-tag v-if="selectedCourseId === item.id" size="small" type="info">
共{{ item.totalWeeks }}周
</el-tag>
</div>
<div class="flex justify-between text-xs text-gray-600">
<span>教师: {{ item.teacherName }}</span>
<span>总学时: {{ item.totalHours }}</span>
</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">
<span class="font-medium">周学时:</span>
<span>{{ Math.round(item.totalHours / item.totalWeeks) }}</span>
</div>
<div class="flex items-center">
<span class="font-medium">任务代码:</span>
<span>{{ item.taskCode }}</span>
</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"
:key="`room-${item.id}`"
shadow="hover"
:class="['cursor-pointer transition-all duration-200', selectedRoomId === item.id ? 'selected-card' : 'hover:bg-blue-50']"
@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>
<!-- 排课操作按钮 -->
<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>
<div class="flex items-center mb-2">
<span class="font-medium mr-2">具体日期:</span>
<span>{{ selectedDate || '请选择周数' }}</span>
</div>
<div class="flex items-center mb-2">
<span class="font-medium mr-2">选中的课程:</span>
<span>{{ selectedCourseName || '未选择' }}</span>
</div>
<div class="flex items-center mb-2">
<span class="font-medium mr-2">授课教师:</span>
<span>{{ selectedCourseTeacherName || '未选择' }}</span>
</div>
<div class="flex items-center">
<span class="font-medium mr-2">选中的教室:</span>
<span>{{ selectedRoomName || '未选择' }}</span>
</div>
</div>
<el-button
type="primary"
size="large"
:disabled="!selectedCourseId || !selectedTimeSlot || !selectedWeekNumber"
@click="handleScheduleCourse"
plain
>
安排课程
</el-button>
<el-button
type="danger"
size="large"
:disabled="!selectedTimeSlot || !selectedTimeSlot.hasCourse"
@click="handleRemoveCourse"
plain
>
移除课程
</el-button>
</div>
</div>
</el-card>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, onBeforeUnmount } from 'vue'
import { Check } from '@element-plus/icons-vue'
import { classLists } from '@/api/class'
import { configLists } from '@/api/config'
import { courseAvailableRooms, courseAvailableSlots, courseSchedule } from '@/api/course'
import { taskLists } from '@/api/task'
import { timeCurrentSemester } from '@/api/time'
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
// 星期类型
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
}
// 星期数组
const weekDays: WeekDay[] = [
{ label: '星期一', value: 1 },
{ label: '星期二', value: 2 },
{ label: '星期三', value: 3 },
{ label: '星期四', value: 4 },
{ label: '星期五', value: 5 },
{ label: '星期六', value: 6 },
{ label: '星期日', value: 7 }
]
const getConfigLists = ref<{ lists: SemesterConfigItem[] }>({ lists: [] })
const getClassLists = ref<{ lists: ClassItem[] }>({ lists: [] })
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: [] })
// 课程列表数据 - 明确类型
const getCourseLists = ref<{ lists: CourseItem[] }>({ lists: [] })
// 课程加载状态
const courseLoading = ref(false)
// 选中的课程ID
const selectedCourseId = ref<number | null>(null)
// 选中的课程完整对象
const selectedCourse = computed(() => {
if (!selectedCourseId.value) return null
return getCourseLists.value.lists.find(item => item.id === selectedCourseId.value) || null
})
// 选中的课程名称
const selectedCourseName = computed(() => {
return selectedCourse.value?.courseName || '未选择'
})
// 选中的课程教师ID
const selectedCourseTeacherId = computed(() => {
return selectedCourse.value?.teacherId || null
})
// 选中的课程教师姓名
const selectedCourseTeacherName = computed(() => {
return selectedCourse.value?.teacherName || '未选择'
})
// 教室列表数据 - 明确类型
const getRoomLists = ref<{ lists: ClassroomItem[] }>({ lists: [] })
const roomLoading = ref(false)
const selectedRoomId = ref<number | null>(null)
// 选中的教室名称
const selectedRoomName = computed(() => {
if (!selectedRoomId.value) return '未选择'
const room = getRoomLists.value.lists.find(item => item.id === selectedRoomId.value)
return room ? room.classroomName : '未选择'
})
// 选中的时间段
const selectedTimeSlot = ref<SelectedTimeSlot | null>(null)
// 新增:选中的周数
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}`
})
const loading = ref(false)
const queryParams = reactive({
classId: '',
semesterId: '',
maxStudentCount: 0
})
// 周数变化处理
const handleWeekChange = () => {
// 周数变化时,可以在这里处理相关逻辑
console.log('周数变化为:', selectedWeekNumber.value)
}
// 班级变化处理
const handleClassChange = async () => {
// 切换班级时,清空选中的课程、教室、时间段和周数
selectedCourseId.value = null
selectedRoomId.value = null
selectedWeekNumber.value = 1
clearSelectedTimeSlot()
if (queryParams.classId && queryParams.semesterId) {
await fetchCourseLists()
await fetchRoomLists()
}
}
// 学期变化处理
const handleSemesterChange = async () => {
// 切换学期时,清空选中的课程、教室、时间段和周数
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
}
// 获取班级列表
const fetchClassLists = async (params: string) => {
try {
const res = await classLists({
className: params.trim()
})
getClassLists.value = res
} catch (err) {
feedback.msgError('获取班级列表失败')
getClassLists.value = { lists: [] }
} finally {
classSearchLoading.value = false
}
}
const handleClassRemoteSearch = (keyword: string) => {
if (classSearchTimer.value) clearTimeout(classSearchTimer.value)
classSearchTimer.value = setTimeout(async () => {
classSearchLoading.value = true
await fetchClassLists(keyword)
}, 300) as unknown as number
}
// 获取学期列表
const fetchConfigLists = async () => {
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('获取学期列表失败')
}
}
const fetchSemester = async () => {
try {
const res = await timeCurrentSemester()
if (res) {
queryParams.semesterId = res.id
}
} catch (error) {
console.error('获取学期信息失败', error)
}
}
// 获取课程列表
const fetchCourseLists = async () => {
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 // 清空周数选择
}
} catch (err) {
console.error('获取课程列表失败', err)
feedback.msgError('获取课程列表失败')
getCourseLists.value = { lists: [] }
} finally {
courseLoading.value = false
}
}
// 课程点击处理(切换选中/取消选中)
const handleCourseClick = (course: CourseItem) => {
selectedCourseId.value = selectedCourseId.value === course.id ? null : course.id
selectedWeekNumber.value = 1
activeTab.value = 'course'
}
// 获取教室列表
const fetchRoomLists = async () => {
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
}
} catch (err) {
console.error('获取教室列表失败', err)
feedback.msgError('获取教室列表失败')
getRoomLists.value = { lists: [] }
} finally {
roomLoading.value = false
}
}
// 教室点击处理
const handleClassroomClick = (classroom: ClassroomItem) => {
selectedRoomId.value = selectedRoomId.value === classroom.id ? null : classroom.id
activeTab.value = 'classroom'
}
// 处理时间段点击
const handleTimeSlotClick = (rowIndex: number, dayOfWeek: number, cellData: any) => {
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
}
}
}
}
// 判断时间段是否被选中
const isTimeSlotSelected = (rowIndex: number, dayOfWeek: number, cellData: any) => {
return selectedTimeSlot.value &&
selectedTimeSlot.value.rowIndex === rowIndex &&
selectedTimeSlot.value.dayOfWeek === dayOfWeek
}
// 清除选中的时间段
const clearSelectedTimeSlot = () => {
selectedTimeSlot.value = null
}
// 获取选中时间段的显示文本
const getSelectedTimeSlotText = () => {
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}`
}
// 安排课程
const handleScheduleCourse = async () => {
// 增强校验确保时间段、课程、教师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 // 使用计算出的具体日期
})
feedback.msgSuccess('排课成功')
// 刷新课表数据
await getTimeLists()
// 清空选中状态,提升用户体验
clearSelectedTimeSlot()
selectedCourseId.value = null
selectedRoomId.value = null
selectedWeekNumber.value = 1
} catch (error) {
console.error('排课失败', error)
feedback.msgError('排课失败,请稍后重试')
}
}
// 移除课程
const handleRemoveCourse = async () => {
if (!selectedTimeSlot.value || !selectedTimeSlot.value.hasCourse) {
feedback.msgError('请先选择一个有课程的时间段')
return
}
try {
// TODO: 调用移除课程API
// 示例await removeCourse({ timeSlotId: selectedTimeSlot.value.id })
feedback.msgSuccess('课程移除成功')
await getTimeLists()
clearSelectedTimeSlot()
} catch (error) {
console.error('移除课程失败', error)
feedback.msgError('移除课程失败,请稍后重试')
}
}
// 清理定时器
onBeforeUnmount(() => {
if (classSearchTimer.value) clearTimeout(classSearchTimer.value)
})
// 所有数据列表
const allLists = ref<any[]>([])
const { pager, getLists } = usePaging({
fetchFun: taskLists,
params: queryParams,
size: 7
})
// 请求数据列表
const getTimeLists = async () => {
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
}
}
// 处理表格数据,按节次分组,构建星期矩阵
const tableData = computed(() => {
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)
})
})
const getRowBaseInfo = (row: Record<number, any>) => {
return Object.values(row).find((item) => item) || {}
}
const getRowStartTime = (row: Record<number, any>) => {
const baseInfo = getRowBaseInfo(row)
const start = baseInfo?.startTime
if (start == null) return ''
const s = String(start)
return s.length > 3 ? s.slice(0, -3) : ''
}
const getRowEndTime = (row: Record<number, any>) => {
const baseInfo = getRowBaseInfo(row)
const end = baseInfo?.endTime
if (end == null) return ''
const s = String(end)
return s.length > 3 ? s.slice(0, -3) : ''
}
// 初始化时获取数据
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;
}
.selected-card {
border-color: #409eff !important;
background-color: #f0f7ff;
box-shadow: 0 2px 12px rgba(64, 158, 255, 0.2);
}
:deep(.el-tag) {
height: 24px;
line-height: 22px;
}
/* 时间段单元格样式 */
.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;
}
</style>