You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

350 lines
8.1 KiB

<script setup lang="ts">
// 覆盖层配置接口
interface OverlayConfig {
id: string
dayIndex: number // 0=周日, 1=周一, ..., 6=周六
startLessonIndex: number // 开始的课程索引 (0-based)
endLessonIndex: number // 结束的课程索引 (0-based, 包含)
title: string
courseInfo: {
name: string
time: string
location: string
teacher: string
}
}
// 覆盖层样式接口
interface OverlayStyle {
top: string
left: string
width: string
height: string
transform: string
}
import { ref, onMounted, reactive } from 'vue'
const table = ref<HTMLElement | null>(null)
const showModal = ref(false)
const currentModalInfo = ref<OverlayConfig | null>(null)
// 覆盖层配置数据
const overlayConfigs: OverlayConfig[] = [
{
id: 'overlay-1',
dayIndex: 0, // 周日
startLessonIndex: 0, // 第1节课
endLessonIndex: 1, // 第2节课
title: '专题研讨',
courseInfo: {
name: '专题研讨',
time: '08:00 - 09:40',
location: '教学楼A301',
teacher: '张老师'
}
},
{
id: 'overlay-2',
dayIndex: 1, // 周一
startLessonIndex: 2, // 第3节课
endLessonIndex: 3, // 第4节课
title: '高等数学',
courseInfo: {
name: '高等数学',
time: '10:00 - 11:40',
location: '教学楼B205',
teacher: '李老师'
}
},
{
id: 'overlay-3',
dayIndex: 2, // 周二
startLessonIndex: 5, // 第6节课
endLessonIndex: 6, // 第7节课
title: '英语听力',
courseInfo: {
name: '英语听力',
time: '14:00 - 15:40',
location: '语音室101',
teacher: '王老师'
}
},
{
id: 'overlay-4',
dayIndex: 3, // 周三
startLessonIndex: 7, // 第8节课
endLessonIndex: 8, // 第9节课
title: '计算机基础',
courseInfo: {
name: '计算机基础',
time: '16:00 - 17:40',
location: '机房302',
teacher: '赵老师'
}
},
{
id: 'overlay-5',
dayIndex: 4, // 周四
startLessonIndex: 0, // 第1节课
endLessonIndex: 2, // 第3节课
title: '物理实验',
courseInfo: {
name: '物理实验',
time: '08:00 - 10:30',
location: '实验楼201',
teacher: '陈老师'
}
}
]
// 存储所有覆盖层的样式
const overlayStyles = reactive<Record<string, OverlayStyle>>({})
const hoveredOverlays = reactive<Record<string, boolean>>({})
const currentMonth = new Date().getMonth() + 1
const weekdays = ['Sun','Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
const lessons = Array.from({length: 14}, (_, i) => `${i + 1}节课`)
const Data = Array(14).fill('').map(() => Array(7).fill(''))
onMounted(() => {
calculateAllOverlayPositions()
window.addEventListener('resize', calculateAllOverlayPositions)
})
/**
* 计算所有覆盖层的位置
*/
function calculateAllOverlayPositions() {
overlayConfigs.forEach(config => {
calculateOverlayPosition(config)
})
}
/**
* 计算单个覆盖层的位置
*/
function calculateOverlayPosition(config: OverlayConfig) {
if (!table.value) return
const tbody = table.value.querySelector('tbody')
if (!tbody) return
const rows = tbody.querySelectorAll('tr')
if (rows.length <= config.endLessonIndex) return
// 获取开始和结束课程的单元格
const startCell = rows[config.startLessonIndex].querySelectorAll('td')[config.dayIndex]
const endCell = rows[config.endLessonIndex].querySelectorAll('td')[config.dayIndex]
if (!startCell || !endCell) return
const startRect = startCell.getBoundingClientRect()
const endRect = endCell.getBoundingClientRect()
const tableRect = table.value.getBoundingClientRect()
// 计算覆盖层的位置和尺寸
const top = startRect.top - tableRect.top
const left = startRect.left - tableRect.left
const width = startRect.width
const height = (endRect.bottom - startRect.top)
// 更新对应覆盖层的样式
overlayStyles[config.id] = {
top: `${top}px`,
left: `${left}px`,
width: `${width}px`,
height: `${height}px`,
transform: hoveredOverlays[config.id] ? 'scale(0.8)' : 'scale(1)'
}
}
function handleMouseEnter(overlayId: string) {
hoveredOverlays[overlayId] = true
if (overlayStyles[overlayId]) {
overlayStyles[overlayId].transform = 'scale(0.8)'
}
}
function handleMouseLeave(overlayId: string) {
hoveredOverlays[overlayId] = false
if (overlayStyles[overlayId]) {
overlayStyles[overlayId].transform = 'scale(1)'
}
}
function openModal(config: OverlayConfig) {
currentModalInfo.value = config
showModal.value = true
}
function closeModal(event?: MouseEvent) {
if (event && event.target !== event.currentTarget) return
showModal.value = false
currentModalInfo.value = null
}
</script>
<template>
<div class="flow-container">
<table ref="table" class="timetable">
<thead>
<tr>
<th class="month-cell">{{ currentMonth }}</th>
<th v-for="day in weekdays" :key="day">{{day}}</th>
</tr>
</thead>
<tbody>
<tr v-for="(lesson,lessonIndex) in lessons" :key="lessonIndex">
<th class="lesson-cell">{{lesson}}</th>
<td
v-for="(_,dayIndex) in weekdays"
:key="dayIndex"
contenteditable="false"
>
{{ Data[lessonIndex][dayIndex] }}
</td>
</tr>
</tbody>
<div
v-for="config in overlayConfigs"
:key="config.id"
:ref="config.id"
class="overlay"
:class="{ 'hovered': hoveredOverlays[config.id] }"
:style="overlayStyles[config.id] || {}"
@mouseenter="handleMouseEnter(config.id)"
@mouseleave="handleMouseLeave(config.id)"
@click="openModal(config)">
{{ config.title }}
</div>
</table>
<!-- 模态框 -->
<div v-if="showModal && currentModalInfo" class="modal-backdrop" @click="closeModal">
<div class="modal-content" @click.stop>
<h3>{{ currentModalInfo.courseInfo.name }}</h3>
<ul>
<li>课程名称: {{ currentModalInfo.courseInfo.name }}</li>
<li>上课时间: {{ currentModalInfo.courseInfo.time }}</li>
<li>上课地点: {{ currentModalInfo.courseInfo.location }}</li>
<li>授课老师: {{ currentModalInfo.courseInfo.teacher }}</li>
</ul>
<button @click="closeModal"></button>
</div>
</div>
</div>
</template>
<style scoped>
.timetable-container
{
width: 100%;
overflow: hidden;
}
.timetable
{
margin-top:100px;
margin-left:10px;
position:relative;
width: 90%;
height:80%;
table-layout: fixed;
border-collapse: collapse;
}
.month-cell
{
background-color: mediumpurple;
}
.timetable tr:first-child th:not(.month-cell)
{
background-color: pink;
}
.lesson-cell
{
background-color: pink;
}
.timetable td
{
border: 1px solid #e0e0e0;
padding: 4px;
height: 28px;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background-color: white;
}
.overlay {
position: absolute;
background-color: rgba(0, 123, 255, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
color: white;
font-weight: bold;
transform-origin: center;
transition: transform 0.3s ease;
box-sizing: border-box;
}
.modal-backdrop
{
position:fixed;
top:0;
left:0;
width:100vw;
height:100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index:100;
}
.modal-content
{
background-color: white;
padding: 25px;
border-radius: 8px;
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.2);
max-width: 400px;
width: 90%;
text-align: center;
color: #333;
z-index: 101;
}
.modal-content h3
{
margin-top: 0;
color: #007bff;
}
.modal-content ul
{
list-style: none;
padding: 0;
text-align: left;
margin-top: 15px;
margin-bottom: 20px;
}
.modal-content ul li
{
margin-bottom: 8px;
}
.modal-content button
{
padding: 10px 20px;
background-color: #007bff;
color:white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.2s ease;
}
.modal-content button:hover
{
background-color: #0056b3;
}
</style>