16 changed files with 1025 additions and 243 deletions
@ -1,125 +1,198 @@ |
|||
<template> |
|||
<div class="schedule-container"> |
|||
<!-- 周视图日历 --> |
|||
<FullCalendar |
|||
:options="calendarOptions" |
|||
ref="fullCalendar" |
|||
class="calendar" |
|||
/> |
|||
|
|||
<!-- 课程详情弹窗 --> |
|||
<el-dialog |
|||
v-model="dialogVisible" |
|||
title="课程人员安排" |
|||
width="30%" |
|||
:before-close="handleClose" |
|||
> |
|||
<div v-if="selectedEvent"> |
|||
<h3>{{ selectedEvent.title }}</h3> |
|||
<p>时间:{{ selectedEvent.time }}</p> |
|||
<h4>参与人员:</h4> |
|||
<ul> |
|||
<li v-for="person in selectedEvent.people" :key="person.id"> |
|||
{{ person.name }} - {{ person.role }} |
|||
</li> |
|||
</ul> |
|||
<el-card class="box-card !border-none" shadow="never"> |
|||
<el-button @click="addDict">添加</el-button> |
|||
<div class="schedule-container"> |
|||
<!-- 周一到周日的布局 --> |
|||
<div v-for="(day, index) in days" :key="index" class="day-column"> |
|||
<div class="day-header">{{ day.date }}</div> |
|||
<el-table |
|||
:data="day.timeSlots" |
|||
border |
|||
:span-method="(data) => objectSpanMethod(day.timeSlots, data)" |
|||
style="width: 100%" |
|||
@cell-click="handleCellClick" |
|||
> |
|||
<!-- 时间列 --> |
|||
<el-table-column |
|||
prop="timeRange" |
|||
label="时间" |
|||
width="80" |
|||
align="center" |
|||
> |
|||
<template #default="{ row }"> |
|||
<div :style="{ backgroundColor: row.color }"> |
|||
{{ row.timeRange }} |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<!-- 教室列 --> |
|||
<el-table-column |
|||
v-for="(classroom, idx) in day.classrooms" |
|||
:key="idx" |
|||
:label="`教室${idx + 1}`" |
|||
:prop="`classroom${idx + 1}`" |
|||
align="center" |
|||
> |
|||
<template #default="{ row }"> |
|||
<div v-if="row.course && row.course.classroom === classroom"> |
|||
<div class="teacher-name">{{ row.course.teacher }}</div> |
|||
<div class="student-list"> |
|||
<el-tag |
|||
v-for="student in row.course.students" |
|||
:key="student" |
|||
size="small" |
|||
effect="plain" |
|||
> |
|||
{{ student }} |
|||
</el-tag> |
|||
</div> |
|||
<div class="classroom-name"> |
|||
剩余空位:{{ row.course.hasnumber }} |
|||
</div> |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</div> |
|||
</el-dialog> |
|||
</div> |
|||
|
|||
<!-- 详情弹窗 --> |
|||
<el-dialog v-model="dialogVisible" title="课程详情"> |
|||
<p><strong>教师:</strong> {{ selectedCourse?.teacher }}</p> |
|||
<p><strong>学员:</strong> {{ selectedCourse?.students.join(', ') }}</p> |
|||
</el-dialog> |
|||
</div> |
|||
</el-card> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref } from 'vue' |
|||
import FullCalendar from '@fullcalendar/vue3' |
|||
import dayGridPlugin from '@fullcalendar/daygrid' |
|||
import timeGridPlugin from '@fullcalendar/timegrid' |
|||
import interactionPlugin from '@fullcalendar/interaction' // 新增此行 |
|||
import zhLocale from '@fullcalendar/core/locales/zh-cn' |
|||
|
|||
// 模拟课程数据(替换为真实接口数据) |
|||
const courses = ref([ |
|||
|
|||
const days = [ |
|||
// 示例数据结构 |
|||
{ |
|||
id: 1, |
|||
title: '数学课', |
|||
start: '2025-05-18 08:00:00', |
|||
end: '2025-05-18 09:00:00', |
|||
people: [ |
|||
{ id: 1, name: '张老师', role: '主讲' }, |
|||
{ id: 2, name: '李同学', role: '学生' }, |
|||
date: '周一', |
|||
timeSlots: [ |
|||
{ |
|||
timeRange: '9:00-10:00', |
|||
color: '#FFA07A', |
|||
course: { |
|||
teacher: '', |
|||
students: [], |
|||
classroom: '教室1', |
|||
hasnumber: 5, |
|||
}, |
|||
}, |
|||
// 其他时间段... |
|||
], |
|||
classrooms: ['教室1'], // 当天可用教室列表 |
|||
}, |
|||
{ |
|||
id: 2, |
|||
title: '英语课', |
|||
start: '2025-05-18 10:00:00', |
|||
end: '2025-05-18 11:00:00', |
|||
people: [{ id: 3, name: '王老师', role: '主讲' }], |
|||
date: '周二', |
|||
timeSlots: [ |
|||
{ |
|||
timeRange: '9:00-10:00', |
|||
color: '#FFA07A', |
|||
course: { |
|||
teacher: '张老师', |
|||
students: ['小明', '小红'], |
|||
classroom: '教室1', |
|||
hasnumber: 5, |
|||
}, |
|||
}, |
|||
{ |
|||
timeRange: '11:00-12:00', |
|||
color: '#712e13', |
|||
course: { |
|||
teacher: '张老师', |
|||
students: ['小明', '小红'], |
|||
classroom: '教室2', |
|||
hasnumber: 5, |
|||
}, |
|||
}, |
|||
// 其他时间段... |
|||
], |
|||
classrooms: ['教室1', '教室2'], // 当天可用教室列表 |
|||
}, |
|||
]) |
|||
] |
|||
|
|||
// 弹窗相关 |
|||
const dialogVisible = ref(false) |
|||
const selectedEvent = ref(null) |
|||
|
|||
// FullCalendar 配置 |
|||
const calendarOptions = ref({ |
|||
locale: zhLocale, |
|||
plugins: [ |
|||
dayGridPlugin, |
|||
timeGridPlugin, |
|||
interactionPlugin, // 新增此行 |
|||
], |
|||
initialView: 'timeGridWeek', // 周视图 |
|||
headerToolbar: { |
|||
left: 'prev,next today', |
|||
center: 'title', |
|||
right: 'timeGridWeek,timeGridDay', // 切换视图 |
|||
}, |
|||
events: courses.value, |
|||
eventClick: (info) => { |
|||
selectedEvent.value = { |
|||
title: info.event.title, |
|||
time: `${info.event.start.toLocaleTimeString()} - ${info.event.end.toLocaleTimeString()}`, |
|||
people: info.event.extendedProps.people, |
|||
const selectedCourse = ref(null) |
|||
|
|||
// 合并单元格方法 |
|||
const objectSpanMethod = (timeSlots, { row, column, rowIndex }) => { |
|||
if (column.property === 'timeRange') { |
|||
// 获取当前时间段 |
|||
const current = timeSlots[rowIndex] |
|||
|
|||
// 如果当前行没有时间范围,不合并 |
|||
if (!current || !current.timeRange) return { rowspan: 0, colspan: 0 } |
|||
|
|||
let spanCount = 1 |
|||
|
|||
// 向下查找相同时间段的行数 |
|||
while (timeSlots[rowIndex + spanCount]?.timeRange === current.timeRange) { |
|||
spanCount++ |
|||
} |
|||
dialogVisible.value = true |
|||
}, |
|||
eventContent: (arg) => { |
|||
// 自定义事件内容(可选) |
|||
return { html: `<div class="fc-event-title">${arg.event.title}</div>` } |
|||
}, |
|||
allDaySlot: false, // 隐藏全天时段 |
|||
slotDuration: '01:00', // 时间段间隔(1小时) |
|||
slotLabelFormat: { |
|||
hour: 'numeric', |
|||
minute: '2-digit', |
|||
omitZeroMinute: false, |
|||
meridiem: 'short', |
|||
}, |
|||
}) |
|||
|
|||
// 关闭弹窗时重置数据 |
|||
const handleClose = () => { |
|||
dialogVisible.value = false |
|||
selectedEvent.value = null |
|||
// 如果是重复的,隐藏该行 |
|||
if ( |
|||
spanCount > 1 && |
|||
rowIndex > 0 && |
|||
timeSlots[rowIndex - 1]?.timeRange === current.timeRange |
|||
) { |
|||
return { rowspan: 0, colspan: 0 } |
|||
} |
|||
|
|||
return { rowspan: spanCount, colspan: 1 } |
|||
} |
|||
|
|||
return { rowspan: 1, colspan: 1 } |
|||
} |
|||
|
|||
// 单元格点击事件 |
|||
const handleCellClick = (row, column, cell, event) => { |
|||
console.log(row, column, cell, event) |
|||
if (column.property.startsWith('classroom')) { |
|||
selectedCourse.value = row.course |
|||
dialogVisible.value = true |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
<style scoped> |
|||
.schedule-container { |
|||
padding: 20px; |
|||
display: flex; |
|||
gap: 10px; |
|||
} |
|||
|
|||
.day-column { |
|||
flex: 1; |
|||
min-width: 200px; |
|||
} |
|||
|
|||
.day-header { |
|||
text-align: center; |
|||
font-weight: bold; |
|||
padding: 8px; |
|||
background-color: #f0f0f0; |
|||
} |
|||
|
|||
.teacher-name { |
|||
font-weight: bold; |
|||
margin-bottom: 5px; |
|||
} |
|||
|
|||
.calendar { |
|||
margin-bottom: 20px; |
|||
.student-list { |
|||
margin-top: 5px; |
|||
} |
|||
|
|||
.fc-event-title { |
|||
white-space: normal !important; |
|||
font-size: 14px; |
|||
.el-table__cell { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.fc-timegrid-event { |
|||
cursor: pointer; |
|||
.classroom-name { |
|||
margin-bottom: 5px; |
|||
} |
|||
</style> |
|||
|
|||
Loading…
Reference in new issue