|
|
|
|
@ -17,6 +17,7 @@
|
|
|
|
|
v-for="ev in eventsForDate(date)"
|
|
|
|
|
:key="ev._id"
|
|
|
|
|
:class="['event-item', getEventClass(ev, date)]"
|
|
|
|
|
:style="getEventItemStyle(ev)"
|
|
|
|
|
:title="getEventTooltip(ev, date)"
|
|
|
|
|
@click.stop="openCreateModal('editor', ev.id)"
|
|
|
|
|
>
|
|
|
|
|
@ -101,7 +102,7 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
|
|
|
|
|
eventsForDate(date) {
|
|
|
|
|
const d = new Date(date)
|
|
|
|
|
return this.list.filter(ev => {
|
|
|
|
|
const events = this.list.filter(ev => {
|
|
|
|
|
const startDate = this.parseDateTime(ev.start_time)
|
|
|
|
|
|
|
|
|
|
// 如果没有end_time,只在start_time的日期显示
|
|
|
|
|
@ -128,6 +129,111 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
const eventStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
|
|
|
|
|
return currentDate.getTime() === eventStartDate.getTime()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 为同一天的事件分配垂直位置,避免重叠
|
|
|
|
|
return this.arrangeEventsVertically(events)
|
|
|
|
|
},
|
|
|
|
|
// 为同一天的事件分配垂直位置,避免重叠
|
|
|
|
|
arrangeEventsVertically(events) {
|
|
|
|
|
if (!events || events.length === 0) return events
|
|
|
|
|
|
|
|
|
|
// 按开始时间排序
|
|
|
|
|
const sortedEvents = events.sort((a, b) => {
|
|
|
|
|
const timeA = this.parseDateTime(a.start_time)
|
|
|
|
|
const timeB = this.parseDateTime(b.start_time)
|
|
|
|
|
return timeA.getTime() - timeB.getTime()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 为每个事件分配垂直位置
|
|
|
|
|
const lanes = [] // 存储每个时间段的占用情况
|
|
|
|
|
const eventHeight = 16 // 事件高度
|
|
|
|
|
const eventSpacing = 2 // 事件间距
|
|
|
|
|
|
|
|
|
|
sortedEvents.forEach(event => {
|
|
|
|
|
const startTime = this.parseDateTime(event.start_time)
|
|
|
|
|
const endTime = event.end_time ? this.parseDateTime(event.end_time) : startTime
|
|
|
|
|
|
|
|
|
|
// 计算时间范围(以小时为单位)
|
|
|
|
|
const startHour = startTime.getHours() + startTime.getMinutes() / 60
|
|
|
|
|
const endHour = endTime.getHours() + endTime.getMinutes() / 60
|
|
|
|
|
|
|
|
|
|
// 找到可用的垂直位置
|
|
|
|
|
let laneIndex = 0
|
|
|
|
|
let foundLane = false
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < lanes.length; i++) {
|
|
|
|
|
const lane = lanes[i]
|
|
|
|
|
// 检查当前时间段是否与已有事件冲突
|
|
|
|
|
const hasConflict = lane.some(occupied => {
|
|
|
|
|
return !(endHour <= occupied.start || startHour >= occupied.end)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!hasConflict) {
|
|
|
|
|
laneIndex = i
|
|
|
|
|
foundLane = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果没有找到可用位置,创建新的位置
|
|
|
|
|
if (!foundLane) {
|
|
|
|
|
laneIndex = lanes.length
|
|
|
|
|
lanes.push([])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 记录当前事件占用的时间段
|
|
|
|
|
lanes[laneIndex].push({
|
|
|
|
|
start: startHour,
|
|
|
|
|
end: endHour
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 为事件添加垂直位置信息
|
|
|
|
|
event.verticalPosition = laneIndex
|
|
|
|
|
// 使用bottom定位时,需要从底部开始计算偏移
|
|
|
|
|
event.topOffset = (lanes.length - 1 - laneIndex) * (eventHeight + eventSpacing)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return sortedEvents
|
|
|
|
|
},
|
|
|
|
|
// 获取同一天内所有事件(包括跨天事件)的垂直位置分配
|
|
|
|
|
getDayEventsWithPositions(date) {
|
|
|
|
|
const d = new Date(date)
|
|
|
|
|
const currentDateStr = d.toDateString()
|
|
|
|
|
|
|
|
|
|
// 获取单天事件
|
|
|
|
|
const singleDayEvents = this.list.filter(ev => {
|
|
|
|
|
const startDate = this.parseDateTime(ev.start_time)
|
|
|
|
|
const currentDate = new Date(d.getFullYear(), d.getMonth(), d.getDate())
|
|
|
|
|
const eventStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
|
|
|
|
|
return currentDate.getTime() === eventStartDate.getTime()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 获取跨天事件(在该日期有显示的事件)
|
|
|
|
|
const multiDayEvents = this.getContinuousEvents().filter(ev => {
|
|
|
|
|
const eventStart = new Date(ev.segStartISO)
|
|
|
|
|
const eventEnd = new Date(ev.segEndISO)
|
|
|
|
|
return eventStart <= d && d <= eventEnd
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 合并所有事件
|
|
|
|
|
const allEvents = [
|
|
|
|
|
...singleDayEvents.map(ev => ({ ...ev, isMultiDay: false })),
|
|
|
|
|
...multiDayEvents.map(ev => ({ ...ev, isMultiDay: true }))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
// 为所有事件分配垂直位置
|
|
|
|
|
return this.arrangeEventsVertically(allEvents)
|
|
|
|
|
},
|
|
|
|
|
// 获取事件项的样式
|
|
|
|
|
getEventItemStyle(event) {
|
|
|
|
|
if (event.topOffset !== undefined) {
|
|
|
|
|
return {
|
|
|
|
|
position: 'relative',
|
|
|
|
|
bottom: `${event.topOffset}px`,
|
|
|
|
|
zIndex: 70 + (event.verticalPosition || 0) // 确保单天事件在跨天事件之上
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {}
|
|
|
|
|
},
|
|
|
|
|
getEventClass(event, date) {
|
|
|
|
|
const startDate = new Date(event.start_time)
|
|
|
|
|
@ -205,6 +311,14 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
return s.toDateString() !== e.toDateString()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 获取所有单天事件,用于检查冲突
|
|
|
|
|
const singleDayEvents = this.list.filter(ev => {
|
|
|
|
|
if (!ev.end_time) return true
|
|
|
|
|
const s = this.parseDateTime(ev.start_time)
|
|
|
|
|
const e = this.parseDateTime(ev.end_time)
|
|
|
|
|
return s.toDateString() === e.toDateString()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const currentMonth = this.calendarDate.getMonth()
|
|
|
|
|
const currentYear = this.calendarDate.getFullYear()
|
|
|
|
|
const monthStart = new Date(currentYear, currentMonth, 1)
|
|
|
|
|
@ -238,6 +352,15 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
const clampedStart = eventStart < monthStart ? monthStart : eventStart
|
|
|
|
|
const clampedEnd = eventEnd > monthEnd ? monthEnd : eventEnd
|
|
|
|
|
|
|
|
|
|
// 检查跨天事件是否与单天事件冲突
|
|
|
|
|
const hasSingleDayConflict = (date) => {
|
|
|
|
|
const dateStr = date.toDateString()
|
|
|
|
|
return singleDayEvents.some(singleEv => {
|
|
|
|
|
const singleStart = this.parseDateTime(singleEv.start_time)
|
|
|
|
|
return singleStart.toDateString() === dateStr
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let cursor = getWeekStart(clampedStart)
|
|
|
|
|
while (cursor <= clampedEnd) {
|
|
|
|
|
const weekStart = new Date(cursor)
|
|
|
|
|
@ -272,7 +395,9 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
displayWeekStartISO: displayWeekStart.toISOString(),
|
|
|
|
|
displayStartCol,
|
|
|
|
|
displayEndCol,
|
|
|
|
|
laneIndex: 0
|
|
|
|
|
laneIndex: 0,
|
|
|
|
|
// 添加冲突信息,用于后续处理
|
|
|
|
|
hasSingleDayConflict: hasSingleDayConflict
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
cursor.setDate(cursor.getDate() + 7)
|
|
|
|
|
@ -360,7 +485,11 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
const dateNumberHeight = 40 // 调整日期数字高度,确保有足够空间
|
|
|
|
|
const eventHeight = 16
|
|
|
|
|
const eventSpacing = 2
|
|
|
|
|
const verticalOffset = (event.laneIndex || 0) * (eventHeight + eventSpacing)
|
|
|
|
|
|
|
|
|
|
// 获取该日期所有事件的垂直位置分配
|
|
|
|
|
const dayEvents = this.getDayEventsWithPositions(adjStart)
|
|
|
|
|
const currentEvent = dayEvents.find(ev => ev.id === event.id)
|
|
|
|
|
const verticalOffset = currentEvent ? currentEvent.topOffset : 0
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
@ -368,7 +497,7 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
top: `${headerHeight + weekRow * cellHeight + dateNumberHeight + 25 + verticalOffset}px`, // 增加偏移量到25px,确保课程标题完全在日期数字下方
|
|
|
|
|
width: `calc(${event.spanCols * cellWidth}% - 4px)`,
|
|
|
|
|
height: `${eventHeight}px`,
|
|
|
|
|
zIndex: 1000,
|
|
|
|
|
zIndex: 50, // 降低跨天事件的层级
|
|
|
|
|
background: `linear-gradient(90deg, ${this.getEventTypeColor(event.type)} 0%, ${this.darkenColor(this.getEventTypeColor(event.type))} 100%)`,
|
|
|
|
|
borderRadius: '3px',
|
|
|
|
|
fontSize: '11px',
|
|
|
|
|
@ -639,7 +768,7 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
z-index: 100;
|
|
|
|
|
z-index: 50; /* 降低跨天事件的层级 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.continuous-event {
|
|
|
|
|
@ -650,7 +779,7 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
.continuous-event:hover {
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
filter: brightness(1.1);
|
|
|
|
|
z-index: 101;
|
|
|
|
|
z-index: 51; /* 调整悬停时的层级 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Element UI 日历样式覆盖 */
|
|
|
|
|
@ -701,7 +830,8 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow: visible;
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 5;
|
|
|
|
|
z-index: 60; /* 提高单天事件的层级,确保在跨天事件之上 */
|
|
|
|
|
min-height: 60px; /* 确保有足够空间显示多个事件 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.event-item {
|
|
|
|
|
@ -718,6 +848,9 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
transition: background-color 0.2s;
|
|
|
|
|
position: relative;
|
|
|
|
|
height: 16px; /* 固定高度 */
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.event-title {
|
|
|
|
|
|