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

571 lines
22 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>
</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 label="星期一" min-width="80" align="center">
<template #default="{ row }">
<div v-if="row[1]" />
</template>
</el-table-column>
<el-table-column label="星期二" min-width="80" align="center">
<template #default="{ row }">
<div v-if="row[2]" />
</template>
</el-table-column>
<el-table-column label="星期三" min-width="80" align="center">
<template #default="{ row }">
<div v-if="row[3]" />
</template>
</el-table-column>
<el-table-column label="星期四" min-width="80" align="center">
<template #default="{ row }">
<div v-if="row[4]" />
</template>
</el-table-column>
<el-table-column label="星期五" min-width="80" align="center">
<template #default="{ row }">
<div v-if="row[5]" />
</template>
</el-table-column>
<el-table-column label="星期六" min-width="80" align="center">
<template #default="{ row }">
<div v-if="row[6]" />
</template>
</el-table-column>
<el-table-column label="星期日" min-width="80" align="center">
<template #default="{ row }">
<div v-if="row[7]" />
</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="item.id"
shadow="hover"
class="cursor-pointer hover:bg-blue-50 transition-colors duration-200"
@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>
</div>
<div class="flex justify-between text-xs text-gray-600">
<span>教师: {{ item.teacherName }}</span>
<span>周学时: {{ item.totalWeeks }}</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>{{ item.totalHours }}</span>
</div>
<div class="flex items-center">
<span class="font-medium">周数:</span>
<span>{{ item.totalWeeks }}</span>
</div>
<p
class="text-xs text-gray-500"
style="color: darkgray"
>
{{ item.taskCode }}
</p>
</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="item.id"
shadow="hover"
class="cursor-pointer hover:bg-blue-50 transition-colors duration-200"
@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>
</el-card>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, ref } from 'vue'
import { classLists } from '@/api/class'
import { configLists } from '@/api/config'
import { courseAvailableRooms, courseAvailableSlots } from '@/api/course'
import { taskLists } from '@/api/task'
import { timeCurrentSemester } from '@/api/time'
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
const getConfigLists = ref<{
lists: Array<{
id: number
academicYear: string
semesterCode: string
config: string
startDate: string
endDate: string
}>
}>({ lists: [] })
const getClassLists = ref<{
lists: Array<{ id: number; className: string; classCode: string; maxStudentCount: number }>
}>({ 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: Array<{
id: number
courseId: number
courseName: string
taskCode: string
totalHours: number
teacherId: number
teacherName: string
totalWeeks: number
}>
}>({ lists: [] })
// 课程加载状态
const courseLoading = ref(false)
// 选中的课程ID
const selectedCourseId = ref<number | null>(null)
// 教室列表数据
const getRoomLists = ref<{
lists: Array<{
id: number
classroomName: string
classroomCode: string
classroomTypeName: string
location: string
capacity: number
}>
}>({ lists: [] })
// 教室加载状态
const roomLoading = ref(false)
// 选中的教室ID
const selectedRoomId = ref<number | null>(null)
const loading = ref(false)
const queryParams = reactive({
classId: '',
semesterId: '',
maxStudentCount: 0
})
// 班级变化处理
const handleClassChange = async () => {
if (queryParams.classId && queryParams.semesterId) {
await fetchCourseLists()
await fetchRoomLists()
}
}
const handleStudentClick = (item: any) => {
queryParams.maxStudentCount = item.maxStudentCount || 0
}
// 学期变化处理
const handleSemesterChange = async () => {
if (queryParams.classId && queryParams.semesterId) {
await fetchCourseLists()
}
}
// 获取班级列表
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)
}
// 获取学期列表
const fetchConfigLists = async () => {
try {
const res = await configLists()
const handleData = JSON.parse(JSON.stringify(res))
handleData.lists = handleData.lists.map((item: any) => ({
...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 () => {
try {
courseLoading.value = true
// 使用taskLists接口获取课程列表
const response = await taskLists({
classId: queryParams.classId,
semesterId: queryParams.semesterId
})
// 映射新接口返回的数据格式到组件所需格式
const mappedCourses = response.lists.map(
(task: {
id: any
courseId: any
courseName: any
taskCode: any
weeklyHours: number
totalWeeks: number
teacherId: any
teacherName: 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 }
} catch (err) {
console.log('获取课程列表失败', err)
feedback.msgError('获取课程列表失败')
getCourseLists.value = { lists: [] }
} finally {
courseLoading.value = false
}
}
// 课程点击处理
const handleCourseClick = (course: any) => {
selectedCourseId.value = selectedCourseId.value === course.id ? null : course.id
}
// 获取教室列表
const fetchRoomLists = async () => {
try {
roomLoading.value = true
// 使用taskLists接口获取教室列表
const response = await courseAvailableRooms({
num: queryParams.maxStudentCount
})
// 映射新接口返回的数据格式到组件所需格式
const mappedRooms = response.map(
(task: {
id: number
classroomName: string
classroomCode: string
classroomTypeName: string
location: string
capacity: number
}) => ({
id: task.id,
classroomName: task.classroomName,
classroomCode: task.classroomCode,
classroomTypeName: task.classroomTypeName,
location: task.location,
capacity: task.capacity
})
)
getRoomLists.value = { lists: mappedRooms }
} catch (err) {
console.log('获取教室列表失败', err)
feedback.msgError('获取教室列表失败')
getRoomLists.value = { lists: [] }
} finally {
roomLoading.value = false
}
}
// 教室点击处理
const handleClassroomClick = (classroom: any) => {
selectedRoomId.value = selectedRoomId.value === classroom.id ? null : classroom.id
}
// 清理定时器
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) {
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;
}
:deep(.el-tag) {
height: 24px;
line-height: 22px;
}
</style>