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