main
parent
33f89729e7
commit
dbbbcc9e13
@ -0,0 +1,350 @@
|
||||
<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>
|
Loading…
Reference in new issue