|
|
|
|
@ -3,12 +3,12 @@
|
|
|
|
|
<!-- 顶部操作区 -->
|
|
|
|
|
<div class="admin-header">
|
|
|
|
|
<el-button type="success" icon="el-icon-plus" @click="openCreateModal('add')">新建日历事件</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 日历预览区 -->
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 日历预览区 -->
|
|
|
|
|
<div class="admin-main">
|
|
|
|
|
<div class="calendar-panel">
|
|
|
|
|
<div class="calendar-wrapper">
|
|
|
|
|
<el-calendar v-model="calendarDate" :first-day-of-week="7">
|
|
|
|
|
<el-calendar v-model="calendarDate" :first-day-of-week="1">
|
|
|
|
|
<template slot="dateCell" slot-scope="{date}">
|
|
|
|
|
<div class="cell-content">
|
|
|
|
|
<span class="date-number">{{ date.getDate() }}</span>
|
|
|
|
|
@ -22,22 +22,22 @@
|
|
|
|
|
>
|
|
|
|
|
<span class="event-title">{{ ev.title }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-calendar>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-calendar>
|
|
|
|
|
<!-- 连续事件覆盖层 -->
|
|
|
|
|
<div class="continuous-events-overlay">
|
|
|
|
|
<div
|
|
|
|
|
v-for="event in getContinuousEvents()"
|
|
|
|
|
:key="`continuous-${event.id}-${event.weekStart}`"
|
|
|
|
|
:key="`continuous-${event.id}-${event.segStartISO}`"
|
|
|
|
|
:class="['continuous-event', `event-type-${event.type || 'default'}`]"
|
|
|
|
|
:style="getContinuousEventStyle(event)"
|
|
|
|
|
:title="getEventTooltip(event, new Date(event.weekStart))"
|
|
|
|
|
:title="getEventTooltip(event, new Date(event.segStartISO))"
|
|
|
|
|
@click.stop="openCreateModal('editor', event.id)"
|
|
|
|
|
>
|
|
|
|
|
{{ event.title }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -180,7 +180,8 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
getContinuousEvents() {
|
|
|
|
|
const FIRST_DOW = 0 // 0=Sunday to match :first-day-of-week="7" on el-calendar
|
|
|
|
|
const FIRST_DOW = 1 // 1=Monday to match :first-day-of-week="1"
|
|
|
|
|
const OFFSET_DAYS = 0 // 不做任何全局偏移
|
|
|
|
|
const continuousEvents = []
|
|
|
|
|
|
|
|
|
|
// 仅选择跨天事件
|
|
|
|
|
@ -204,9 +205,19 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
return d
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function adjustEndForDisplay(d) {
|
|
|
|
|
// 若结束时间恰好为 00:00:00,则视为不包含该天(显示到前一日),避免与下一事件在同日重叠
|
|
|
|
|
const end = new Date(d)
|
|
|
|
|
if (end.getHours() === 0 && end.getMinutes() === 0 && end.getSeconds() === 0) {
|
|
|
|
|
end.setSeconds(end.getSeconds() - 1)
|
|
|
|
|
}
|
|
|
|
|
return end
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
multiDayEvents.forEach(ev => {
|
|
|
|
|
const eventStart = this.parseDateTime(ev.start_time)
|
|
|
|
|
const eventEnd = this.parseDateTime(ev.end_time)
|
|
|
|
|
const eventEndRaw = this.parseDateTime(ev.end_time)
|
|
|
|
|
const eventEnd = adjustEndForDisplay(eventEndRaw)
|
|
|
|
|
|
|
|
|
|
if (eventEnd < monthStart || eventStart > monthEnd) return
|
|
|
|
|
|
|
|
|
|
@ -227,6 +238,14 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
const startCol = (segStart.getDay() - FIRST_DOW + 7) % 7
|
|
|
|
|
const endCol = (segEnd.getDay() - FIRST_DOW + 7) % 7
|
|
|
|
|
const spanCols = endCol - startCol + 1
|
|
|
|
|
|
|
|
|
|
// 计算用于显示的偏移后起点与周键
|
|
|
|
|
const adjStart = new Date(segStart)
|
|
|
|
|
adjStart.setDate(adjStart.getDate() + OFFSET_DAYS)
|
|
|
|
|
const displayWeekStart = getWeekStart(adjStart)
|
|
|
|
|
const displayStartCol = (adjStart.getDay() - FIRST_DOW + 7) % 7
|
|
|
|
|
const displayEndCol = displayStartCol + spanCols - 1
|
|
|
|
|
|
|
|
|
|
continuousEvents.push({
|
|
|
|
|
...ev,
|
|
|
|
|
weekStartISO: weekStart.toISOString(),
|
|
|
|
|
@ -234,7 +253,13 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
segEndISO: segEnd.toISOString(),
|
|
|
|
|
startCol,
|
|
|
|
|
spanCols,
|
|
|
|
|
endCol
|
|
|
|
|
endCol,
|
|
|
|
|
// 显示用字段(含偏移)
|
|
|
|
|
adjSegStartISO: adjStart.toISOString(),
|
|
|
|
|
displayWeekStartISO: displayWeekStart.toISOString(),
|
|
|
|
|
displayStartCol,
|
|
|
|
|
displayEndCol,
|
|
|
|
|
laneIndex: 0
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
cursor.setDate(cursor.getDate() + 7)
|
|
|
|
|
@ -244,28 +269,42 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
// 为同一周的分段分配“轨道”(lane),避免垂直重叠
|
|
|
|
|
const byWeek = {}
|
|
|
|
|
continuousEvents.forEach(seg => {
|
|
|
|
|
const key = seg.weekStartISO
|
|
|
|
|
const key = seg.displayWeekStartISO || seg.weekStartISO
|
|
|
|
|
if (!byWeek[key]) byWeek[key] = []
|
|
|
|
|
byWeek[key].push(seg)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Object.values(byWeek).forEach(segs => {
|
|
|
|
|
// 按开始列排序,开始相同则优先更长的,减少冲突
|
|
|
|
|
segs.sort((a, b) => (a.startCol - b.startCol) || (b.spanCols - a.spanCols))
|
|
|
|
|
const laneEndCols = [] // 每个轨道当前占据的最后列
|
|
|
|
|
// 按开始列排序,开始相同则优先更长的,减少冲突;开始与长度都相同再按 id 稳定排序
|
|
|
|
|
segs.sort((a, b) => (a.displayStartCol - b.displayStartCol) || (b.spanCols - a.spanCols) || String(a.id).localeCompare(String(b.id)))
|
|
|
|
|
const laneEndCols = [] // 每个轨道当前占据的最后列(显示列)
|
|
|
|
|
const placedSegs = []
|
|
|
|
|
segs.forEach(seg => {
|
|
|
|
|
let placed = false
|
|
|
|
|
for (let i = 0; i < laneEndCols.length; i += 1) {
|
|
|
|
|
if (laneEndCols[i] < seg.startCol) {
|
|
|
|
|
// 只要前一个轨道的结束列 < 当前分段的开始列,就可复用该轨道
|
|
|
|
|
if (laneEndCols[i] < seg.displayStartCol) {
|
|
|
|
|
seg.laneIndex = i
|
|
|
|
|
laneEndCols[i] = seg.endCol
|
|
|
|
|
laneEndCols[i] = seg.displayEndCol
|
|
|
|
|
placed = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!placed) {
|
|
|
|
|
seg.laneIndex = laneEndCols.length
|
|
|
|
|
laneEndCols.push(seg.endCol)
|
|
|
|
|
laneEndCols.push(seg.displayEndCol)
|
|
|
|
|
}
|
|
|
|
|
placedSegs.push(seg)
|
|
|
|
|
})
|
|
|
|
|
// 同一轨道再做“同起始列”的细分:将完全同起止的切片平移到新轨道
|
|
|
|
|
const seen = {}
|
|
|
|
|
placedSegs.forEach(seg => {
|
|
|
|
|
const k = `${seg.displayStartCol}-${seg.displayEndCol}-${seg.laneIndex}`
|
|
|
|
|
if (seen[k]) {
|
|
|
|
|
// 已存在相同起止且同轨道,放到新轨道
|
|
|
|
|
seg.laneIndex = ++seen[k].maxLane
|
|
|
|
|
} else {
|
|
|
|
|
seen[k] = { maxLane: seg.laneIndex }
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
@ -273,23 +312,26 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
return continuousEvents
|
|
|
|
|
},
|
|
|
|
|
getContinuousEventStyle(event) {
|
|
|
|
|
const FIRST_DOW = 0
|
|
|
|
|
const FIRST_DOW = 1
|
|
|
|
|
const currentMonth = this.calendarDate.getMonth()
|
|
|
|
|
const currentYear = this.calendarDate.getFullYear()
|
|
|
|
|
const firstDay = new Date(currentYear, currentMonth, 1)
|
|
|
|
|
|
|
|
|
|
// 可控的视觉偏移:将分段起始向后顺延 7 天
|
|
|
|
|
const adjStart = new Date(event.segStartISO)
|
|
|
|
|
adjStart.setDate(adjStart.getDate() + 7)
|
|
|
|
|
// 可控的视觉偏移:使用预计算的偏移起点
|
|
|
|
|
const adjStart = new Date(event.adjSegStartISO || event.segStartISO)
|
|
|
|
|
|
|
|
|
|
const msPerDay = 1000 * 60 * 60 * 24
|
|
|
|
|
const daysFromFirstOfMonth = Math.floor((adjStart - firstDay) / msPerDay)
|
|
|
|
|
const firstDayOffset = (firstDay.getDay() - FIRST_DOW + 7) % 7
|
|
|
|
|
// 使用显示用周起点来确定行,确保与分组一致
|
|
|
|
|
const displayWeekStart = new Date(event.displayWeekStartISO || adjStart)
|
|
|
|
|
const daysFromFirstOfMonth = Math.floor((displayWeekStart - firstDay) / msPerDay)
|
|
|
|
|
const totalDaysFromCalendarStart = daysFromFirstOfMonth + firstDayOffset
|
|
|
|
|
const weekRow = Math.floor(totalDaysFromCalendarStart / 7)
|
|
|
|
|
|
|
|
|
|
// 起始列使用偏移后的日期重新计算
|
|
|
|
|
const startColAdjusted = (adjStart.getDay() - FIRST_DOW + 7) % 7
|
|
|
|
|
// 起始列使用偏移后的日期重新计算(若已携带显示列则直接用)
|
|
|
|
|
const startColAdjusted = (event.displayStartCol != null)
|
|
|
|
|
? event.displayStartCol
|
|
|
|
|
: (adjStart.getDay() - FIRST_DOW + 7) % 7
|
|
|
|
|
|
|
|
|
|
const cellWidth = 100 / 7
|
|
|
|
|
const cellHeight = 100
|
|
|
|
|
@ -696,7 +738,7 @@ import addCalendar from './components/addCalendar.vue'
|
|
|
|
|
@media (max-width: 1200px) {
|
|
|
|
|
.admin-header {
|
|
|
|
|
padding: 15px 20px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.admin-main {
|
|
|
|
|
padding: 15px 20px;
|
|
|
|
|
|