<template>
  <div class="admin-calendar">
    <!-- 顶部操作区 -->
    <div class="admin-header" style="display: flex; align-items: center; gap: 20px;">
      <el-button type="success" icon="el-icon-plus" @click="openCreateModal('add')">新建日历事件</el-button>
      <el-button type="primary" icon="el-icon-plus" @click="exportCalendar">导出日历事件</el-button>
      <div style="color: #333; font-size: 16px; font-weight: 500;">
        本月开课天数：{{ monthDayCalendar }}天 {{ calendarDate.getFullYear() }}年累计开课天数：{{ yearDayCalendar }}天
      </div>
    </div>
      <!-- 日历预览区 -->
    <div class="admin-main">
      <div class="calendar-panel">
        <div class="calendar-wrapper">
        <el-calendar v-model="calendarDate" :first-day-of-week="1">
          <template slot="dateCell" slot-scope="{date}">
            <div class="cell-content" @click.stop.prevent="onDateCellClick(date)">
                <span class="date-number">{{ date.getDate() }}</span>
                <div class="event-list">
                  <div
                    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)"
                  >
                    <span class="event-title">{{ ev.title }}</span>
                  </div>
              </div>
            </div>
          </template>
        </el-calendar>
          <!-- 跨天事件条：直接作为 wrapper 的绝对定位子元素渲染，避免覆盖层拦截点击 -->
          <div
            v-for="event in getContinuousEvents()"
            :key="`continuous-${event.id}-${event.segStartISO}`"
            :class="['continuous-event', getContinuousEventClass(event)]"
            :style="getContinuousEventStyle(event)"
            :title="getEventTooltip(event, new Date(event.segStartISO))"
            @click.stop="openCreateModal('editor', event.id)"
          >
            {{ event.title }}
          </div>
        </div>
      </div>
    </div>
    <addCalendar ref="addCalendar" @refresh="getList"></addCalendar>
  </div>
</template>

<script>

import addCalendar from './components/addCalendar.vue'
  import {
    index
} from '@/api/calendars/index.js'
import { getToken } from '@/utils/auth';
  export default {
  components: {
      addCalendar
    },
    data() {
      return {
      list: [],
      calendarDate: new Date(),
      // 记录每一周行在容器内的实际像素 top，解决不等高行导致定位偏差
      weekRowTops: [],
      monthDayCalendar: 0,
      yearDayCalendar: 0
      }
    },
    computed: {
    selectMonth() {
      const now = this.calendarDate instanceof Date ? this.calendarDate : new Date(this.calendarDate)
      const month = now.getMonth() + 1 < 10 ? '0' + (now.getMonth() + 1) : now.getMonth() + 1
      const year = now.getFullYear()
      return year + '-' + month
      }
    },
    watch: {
    calendarDate: {
      handler(newVal, oldVal) {
        if (!oldVal) return
        const n = newVal instanceof Date ? newVal : new Date(newVal)
        const o = oldVal instanceof Date ? oldVal : new Date(oldVal)
        if (n.getFullYear() !== o.getFullYear() || n.getMonth() !== o.getMonth()) {
          this.getList()
        }
      }
    }
    },
    created() {
      this.getList()
      this.generateDynamicStyles()
    },
    mounted() {
      this.$nextTick(() => this.measureWeekRowTops())
      window.addEventListener('resize', this.measureWeekRowTops)
    },
    beforeDestroy() {
      window.removeEventListener('resize', this.measureWeekRowTops)
    },
    methods: {
    async exportCalendar() {
      try {
        await this.$confirm('请选择导出范围', '导出日历事件', {
          confirmButtonText: '导出本月',
          cancelButtonText: '导出全部',
          distinguishCancelAndClose: true,
          type: 'info'
        })
        this.triggerExport(this.selectMonth)
      } catch (action) {
        if (action === 'cancel') {
          this.triggerExport('')
        }
        // 关闭弹框不做任何操作
      }
      },
    triggerExport(month) {
      const monthQuery = month ? `month=${month}` : 'month='
      const url =
        process.env.VUE_APP_BASE_API +
        `/api/admin/calendars/index?${monthQuery}` +
        '&is_export=1&export_fields[is_publish_text]=是否对外展示' +
        '&export_fields[is_count_days_text]=是否统计天数' +
        '&export_fields[is_count_people_text]=是否统计人数' +
        '&export_fields[days]=天数' +
        '&export_fields[type_text]=日程类型' +
        '&export_fields[course.name]=课程名称' +
        '&export_fields[introduce]=具体说明' +
        '&export_fields[title]=标题' +
        '&export_fields[url]=资讯链接' +
        '&export_fields[start_time]=开始时间' +
        '&export_fields[end_time]=截止时间' +
        '&export_fields[address]=地址' +
        '&export_fields[color]=主题颜色' +
        '&export_fields[history_courses]=课程人数' +
        '&export_fields[content]=内容' +
        '&token=' + getToken()
      window.open(url, '_blank')
      },
    onDateCellClick() {
      // 阻止 el-calendar 默认点击日期触发的月份切换
      return false
    },
    async getList() {
        const res = await index({
        month: this.selectMonth
        })
        // 统一规范化 id，避免 _id 与 id 不一致导致点击与定位异常
        this.list = (res.list || res || []).map(e => ({ ...e, id: e.id || e._id }))
        this.monthDayCalendar = res.monthDayCalendar || 0
        this.yearDayCalendar = res.yearDayCalendar || 0
        // 重新生成动态样式
        this.generateDynamicStyles()
        // 渲染后测量行位置信息
        this.$nextTick(() => this.measureWeekRowTops())
      },
    // 计算某一天内单天事件的轨道数量，用于为跨天条预留垂直空间
    getSingleDayLaneCount(date) {
      const d = new Date(date)
      const target = new Date(d.getFullYear(), d.getMonth(), d.getDate())
      const dayEvents = (this.list || []).filter(ev => {
        if (!ev) return false
        const s = this.parseDateTime(ev.start_time)
        if (!s) return false
        const sOnly = new Date(s.getFullYear(), s.getMonth(), s.getDate())
        const hasEnd = !!ev.end_time
        const e = hasEnd ? this.parseDateTime(ev.end_time) : s
        const eOnly = new Date(e.getFullYear(), e.getMonth(), e.getDate())
        // 仅统计单天事件
        const isMulti = hasEnd && (sOnly.getTime() !== eOnly.getTime())
        if (isMulti) return false
        return sOnly.getTime() === target.getTime()
      })
      if (dayEvents.length === 0) return 0
      // 简化的轨道计算（与 arrangeEventsVertically 同逻辑）
      const sorted = dayEvents.sort((a, b) => {
        const as = this.parseDateTime(a.start_time)
        const bs = this.parseDateTime(b.start_time)
        return (as ? as.getTime() : 0) - (bs ? bs.getTime() : 0)
      })
      const lanes = []
      sorted.forEach(ev => {
        const s = this.parseDateTime(ev.start_time)
        const e = ev.end_time ? this.parseDateTime(ev.end_time) : s
        const sh = s.getHours() + s.getMinutes() / 60
        const eh = e.getHours() + e.getMinutes() / 60
        let placed = false
        for (let i = 0; i < lanes.length; i += 1) {
          const lane = lanes[i]
          const conflict = lane.some(r => !(eh <= r.start || sh >= r.end))
          if (!conflict) {
            lane.push({ start: sh, end: eh })
            placed = true
            break
          }
        }
        if (!placed) {
          lanes.push([{ start: sh, end: eh }])
        }
      })
      return lanes.length
    },
    // 计算跨天切片跨度内最大单天轨道数
    getMaxSingleDayLaneCountBetween(startISO, endISO) {
      if (!startISO || !endISO) return 0
      const start = new Date(startISO)
      const end = new Date(endISO)
      const cursor = new Date(start)
      let maxCount = 0
      while (cursor <= end) {
        maxCount = Math.max(maxCount, this.getSingleDayLaneCount(cursor))
        cursor.setDate(cursor.getDate() + 1)
      }
      return maxCount
    },
    // 测量日历每一行(周)的实际 top，用于跨天条精确定位
    measureWeekRowTops() {
      try {
        const wrapper = this.$el.querySelector('.calendar-wrapper')
        const wrapperTop = wrapper ? wrapper.getBoundingClientRect().top : 0
        const tableRows = this.$el.querySelectorAll('.el-calendar-table tbody tr')
        if (!tableRows || !tableRows.length) return
        const tops = []
        tableRows.forEach(row => {
          const rect = row.getBoundingClientRect()
          const relTop = rect.top - wrapperTop
          tops.push(relTop)
        })
        this.weekRowTops = tops
      } catch (e) {
        // 忽略测量异常
      }
    },
    openCreateModal(type, id) {
      // 调试日志：记录点击行为与入参
      try { console.log('[calendar] openCreateModal called with:', { type, id }) } catch (e) {}
      // 兼容传入对象或 id/_id 的情况，确保能正确打开编辑弹窗
      const finalId = (id && typeof id === 'object') ? (id.id || id._id) : (id || null)
      try { console.log('[calendar] resolved finalId:', finalId) } catch (e) {}
      const addRef = this.$refs && this.$refs.addCalendar
      if (!addRef) {
        try { console.warn('[calendar] addCalendar ref not found') } catch (e) {}
        return
      }
      if (type === 'editor') {
          addRef.id = finalId
        }
        addRef.type = type
        addRef.isShow = true
        try { console.log('[calendar] modal state set:', { id: addRef.id, type: addRef.type, isShow: addRef.isShow }) } catch (e) {}
      },

    eventsForDate(date) {
      const d = new Date(date)
      const currentDate = new Date(d.getFullYear(), d.getMonth(), d.getDate())
      const oneDayEvents = [];
      
      (this.list || []).forEach(ev => {
        const startDate = this.parseDateTime(ev.start_time)
        const startOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
        const hasEnd = !!ev.end_time
        const endDate = hasEnd ? this.parseDateTime(ev.end_time) : startDate
        const endOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate())

        // 仅单天事件在格子内渲染；跨天交给覆盖层
        const isMultiDay = hasEnd && (startOnly.getTime() !== endOnly.getTime())
        if (isMultiDay) return

        if (currentDate.getTime() === startOnly.getTime()) {
          oneDayEvents.push({
            ...ev,
            id: ev.id || ev._id
          })
        }
      })

      // 为同一天所有事件分配垂直位置，避免重叠
      return this.arrangeEventsVertically(oneDayEvents)
    },
    // 为同一天的事件分配垂直位置，避免重叠
    arrangeEventsVertically(events) {
      if (!events || events.length === 0) return events
      
      // 按开始时间排序（片段无具体时间则按标题/ID兜底）
      const sortedEvents = events.sort((a, b) => {
        const aStart = this.parseDateTime(a.start_time)
        const bStart = this.parseDateTime(b.start_time)
        const aTs = aStart ? aStart.getTime() : 0
        const bTs = bStart ? bStart.getTime() : 0
        const diff = aTs - bTs
        if (diff !== 0) return diff
        return String(a.id).localeCompare(String(b.id))
      })
      
      // 为每个事件分配垂直位置
      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
        
        // 片段（跨天按全天）采用 0..24，单天按具体时间
        const isSegment = !!event.isSegment
        const startHour = isSegment && event.isMultiDay ? 0 : (startTime.getHours() + startTime.getMinutes() / 60)
        const endHour = isSegment && event.isMultiDay ? 24 : (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
        // 自上而下堆叠：从顶部开始计算偏移
        event.topOffset = 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',
          top: `${Math.max(0, event.topOffset - 8)}px`,
          zIndex: 70 + (event.verticalPosition || 0) // 单天事件保持在顶层
        }
      }
      return {}
    },
    getEventClass(event, date) {
      const startDate = new Date(event.start_time)
      const currentDate = new Date(date)
      
      // 基础样式类
      let baseClass = ''
      
      // 如果没有end_time，直接返回单天事件样式
      if (!event.end_time) {
        baseClass = 'single-day'
      } else {
        const endDate = new Date(event.end_time)
        
        // 判断是否是跨天事件
        const isMultiDay = startDate.getDate() !== endDate.getDate() || 
                          startDate.getMonth() !== endDate.getMonth() || 
                          startDate.getFullYear() !== endDate.getFullYear()
        
        if (!isMultiDay) {
          baseClass = 'single-day'
        } else {
          // 判断当前日期在跨天事件中的位置
          const eventStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
          const eventEndDate = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate())
          const currentDateOnly = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate())
          
          if (currentDateOnly.getTime() === eventStartDate.getTime()) {
            baseClass = 'multi-day-start'
          } else if (currentDateOnly.getTime() === eventEndDate.getTime()) {
            baseClass = 'multi-day-end'
          } else {
            baseClass = 'multi-day-middle'
          }
        }
      }
      
      // 如果有自定义颜色，添加color类；否则使用默认的type类
      if (event.color) {
        return `${baseClass} event-color-${event.color.replace('#', '')}`
      } else {
        return `${baseClass} event-type-${event.type || 'default'}`
      }
    },
    getEventTooltip(event, date) {
      const startDate = new Date(event.start_time)
      
      // 如果没有end_time，只显示事件标题和开始时间
      if (!event.end_time) {
        return `${event.title}\n时间：${this.formatDateTime(event.start_time)}`
      }
      
      const endDate = new Date(event.end_time)
      
      const isMultiDay = startDate.getDate() !== endDate.getDate() || 
                        startDate.getMonth() !== endDate.getMonth() || 
                        startDate.getFullYear() !== endDate.getFullYear()
      
      if (isMultiDay) {
        return `${event.title}\n时间：${this.formatDateTime(event.start_time)} ~ ${this.formatDateTime(event.end_time)}`
        } else {
        return `${event.title}\n时间：${this.formatDateTime(event.start_time)} ~ ${this.formatDateTime(event.end_time)}`
      }
    },
    getContinuousEvents() {
      const FIRST_DOW = 1 // 1=Monday to match :first-day-of-week="1"
      const OFFSET_DAYS = 0 // 不做任何全局偏移
      const continuousEvents = []

      // 仅选择跨天事件
      const multiDayEvents = this.list.filter(ev => {
        if (!ev.end_time) return false
        const s = this.parseDateTime(ev.start_time)
        const e = this.parseDateTime(ev.end_time)
        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)
      const monthEnd = new Date(currentYear, currentMonth + 1, 0)

      function getWeekStart(date) {
        const d = new Date(date)
        const jsDow = d.getDay() // 0..6 (Sun..Sat)
        const offset = (jsDow - FIRST_DOW + 7) % 7
        d.setDate(d.getDate() - offset)
        // 规范化到 00:00:00，避免不同时间导致同一周被分到不同key
        d.setHours(0, 0, 0, 0)
        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 eventEndRaw = this.parseDateTime(ev.end_time)
        const eventEnd = adjustEndForDisplay(eventEndRaw)

        if (eventEnd < monthStart || eventStart > monthEnd) return

        // Clamp to month range so we only render in current month viewport
        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)
          const weekEnd = new Date(cursor)
          weekEnd.setDate(weekEnd.getDate() + 6)

          // Segment inside this week
          const segStart = clampedStart > weekStart ? clampedStart : weekStart
          const segEnd = clampedEnd < weekEnd ? clampedEnd : weekEnd
          if (segStart <= segEnd) {
            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(),
              segStartISO: segStart.toISOString(),
              segEndISO: segEnd.toISOString(),
              startCol,
              spanCols,
              endCol,
              // 显示用字段（含偏移）
              adjSegStartISO: adjStart.toISOString(),
              displayWeekStartISO: displayWeekStart.toISOString(),
              displayStartCol,
              displayEndCol,
              laneIndex: 0,
              // 添加冲突信息，用于后续处理
              hasSingleDayConflict: hasSingleDayConflict
            })
          }
          cursor.setDate(cursor.getDate() + 7)
        }
      })

      // 为同一周的分段分配“轨道”（lane），避免垂直重叠
      const byWeek = {}
      continuousEvents.forEach(seg => {
        const key = seg.displayWeekStartISO || seg.weekStartISO
        if (!byWeek[key]) byWeek[key] = []
        byWeek[key].push(seg)
      })

      Object.values(byWeek).forEach(segs => {
        // 排序：开始列优先，其次跨度更长，最后按 id 稳定
        segs.sort((a, b) => (a.displayStartCol - b.displayStartCol) || (b.spanCols - a.spanCols) || String(a.id).localeCompare(String(b.id)))
        // 使用严格区间冲突检测的多区间轨道
        const lanes = [] // 每个元素是该轨道内的区间数组 [{start,end}]
        segs.forEach(seg => {
          let laneIndex = 0
          let placed = false
          for (let i = 0; i < lanes.length; i += 1) {
            const intervals = lanes[i]
            const overlaps = intervals.some(it => !(seg.displayEndCol < it.start || seg.displayStartCol > it.end))
            if (!overlaps) {
              intervals.push({ start: seg.displayStartCol, end: seg.displayEndCol })
              laneIndex = i
              placed = true
              break
            }
          }
          if (!placed) {
            laneIndex = lanes.length
            lanes.push([{ start: seg.displayStartCol, end: seg.displayEndCol }])
          }
          seg.laneIndex = laneIndex
        })
      })

      return continuousEvents
    },
    getContinuousEventClass(event) {
      const baseClass = 'continuous-event'
      if (event.color) {
        return `${baseClass} event-color-${event.color.replace('#', '')}`
      } else {
        return `${baseClass} event-type-${event.type || 'default'}`
      }
    },
    getContinuousEventStyle(event) {
      const FIRST_DOW = 1
      const currentMonth = this.calendarDate.getMonth()
      const currentYear = this.calendarDate.getFullYear()
      const firstDay = new Date(currentYear, currentMonth, 1)

      // 可控的视觉偏移：使用预计算的偏移起点
      const adjStart = new Date(event.adjSegStartISO || event.segStartISO)

      const msPerDay = 1000 * 60 * 60 * 24
      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 = (event.displayStartCol != null)
        ? event.displayStartCol
        : (adjStart.getDay() - FIRST_DOW + 7) % 7

      const cellWidth = 100 / 7
      const cellHeight = 120  // 兜底高度
      const overlayBaseTop = 50 // 与 CSS .continuous-events-overlay 保持一致
      const dateNumberHeight = 8  // 进一步压缩日期占位
      const eventHeight = 20
      const eventSpacing = 3
      
      // 使用跨天分段在本周内的 laneIndex 做垂直堆叠，避免同一周同一行的跨天事件重叠
      const verticalOffset = (event.laneIndex || 0) * (eventHeight + eventSpacing)

      // 基于实际测量的行 top；没有测到则退回计算值
      const measuredRowTop = (this.weekRowTops && this.weekRowTops[weekRow] != null)
        ? this.weekRowTops[weekRow]
        : (weekRow * cellHeight)

      // 为跨天条在该分段跨度内预留单天事件堆叠高度，避免遮挡
      const singleDayLanes = this.getMaxSingleDayLaneCountBetween(event.segStartISO, event.segEndISO)
      const singleDayStackPx = singleDayLanes * (16 + 2) // 与单天事件高度与间距保持一致
      const safeGap = 0 // 去除额外间距，使横条进一步上移

      return {
        position: 'absolute',
        left: `calc(${startColAdjusted * cellWidth}% + 1px)`,
        top: `${overlayBaseTop + measuredRowTop + dateNumberHeight + safeGap + singleDayStackPx + verticalOffset}px`, // 预留单天事件空间+安全间距
        width: `calc(${event.spanCols * cellWidth}% - 2px)`,
        height: `${eventHeight}px`,
        zIndex: 500, // 置于表格之上，结合预留空隙不遮挡单天，确保可点
        background: `linear-gradient(90deg, ${this.getEventTypeColor(event.type)} 0%, ${this.darkenColor(this.getEventTypeColor(event.type))} 100%)`,
        borderRadius: '3px',
        fontSize: '12px',
        lineHeight: `${eventHeight}px`,
        color: 'white',
        padding: '0 4px',
        whiteSpace: 'nowrap',
        overflow: 'hidden',
        textOverflow: 'ellipsis',
        cursor: 'pointer',
        boxShadow: '0 1px 3px rgba(0, 0, 0, 0.3)'
      }
    },
    getEventStyle(event, date) {
      const startDate = new Date(event.start_time)
      const currentDate = new Date(date)
      
      // 如果没有end_time，使用默认样式
      if (!event.end_time) {
        return {}
      }
      
      const endDate = new Date(event.end_time)
      
      // 判断是否是跨天事件
      const isMultiDay = startDate.getDate() !== endDate.getDate() || 
                        startDate.getMonth() !== endDate.getMonth() || 
                        startDate.getFullYear() !== endDate.getFullYear()
      
      if (!isMultiDay) {
        return {}
      }
      
      // 获取当前周的开始日期（周日）
      const currentWeekStart = new Date(currentDate)
      const dayOfWeek = currentDate.getDay()
      currentWeekStart.setDate(currentDate.getDate() - dayOfWeek)
      
      // 计算事件在当前周的开始和结束位置
      const eventStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
      const eventEndDate = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate())
      const currentDateOnly = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate())
      
      // 计算当前周内事件的开始和结束天数
      const weekStart = new Date(currentWeekStart)
      const weekEnd = new Date(currentWeekStart)
      weekEnd.setDate(weekEnd.getDate() + 6)
      
      // 事件在当前周的实际开始和结束日期
      const eventWeekStart = eventStartDate < weekStart ? weekStart : eventStartDate
      const eventWeekEnd = eventEndDate > weekEnd ? weekEnd : eventEndDate
      
      // 如果当前日期不在事件范围内，不应用特殊样式
      if (currentDateOnly < eventWeekStart || currentDateOnly > eventWeekEnd) {
        return {}
      }
      
      // 如果是事件在当前周的第一天，显示标题并延伸到周末尾或事件结束
      if (currentDateOnly.getTime() === eventWeekStart.getTime()) {
        const startDayOfWeek = eventWeekStart.getDay()
        const endDayOfWeek = eventWeekEnd.getDay()
        const spanDays = endDayOfWeek - startDayOfWeek + 1
        
        // 根据事件类型设置背景色
        const bgColor = this.getEventTypeColor(event.type)
        
        return {
          position: 'absolute',
          left: '0',
          top: '105px', // 使用固定值：headerHeight(50) + dateNumberHeight(40) + 15px = 105px，确保完全在日期数字下方
          width: `calc(${spanDays * 100}% - 2px)`,
          zIndex: 10,
          background: `linear-gradient(90deg, ${bgColor} 0%, ${this.darkenColor(bgColor)} 100%)`,
          boxShadow: '0 1px 3px rgba(0, 0, 0, 0.2)',
          border: '1px solid rgba(255, 255, 255, 0.3)',
          borderRadius: '3px'
        }
      } else if (currentDateOnly > eventWeekStart && currentDateOnly <= eventWeekEnd) {
        // 其他天完全隐藏内容，保留占位
        return {
          opacity: '0',
          pointerEvents: 'none'
        }
      }
      
      return {}
    },
    getEventDisplayTitle(event, date) {
      const startDate = new Date(event.start_time)
      const currentDate = new Date(date)
      
      // 如果没有end_time，显示完整标题
      if (!event.end_time) {
        return event.title
      }
      
      const endDate = new Date(event.end_time)
      
      // 判断是否是跨天事件
      const isMultiDay = startDate.getDate() !== endDate.getDate() || 
                        startDate.getMonth() !== endDate.getMonth() || 
                        startDate.getFullYear() !== endDate.getFullYear()
      
      if (!isMultiDay) {
        return event.title
      }
      
      // 对于跨天事件，只在开始日期显示标题
      const eventStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
      const currentDateOnly = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate())
      
      // 获取当前周的开始日期
      const currentWeekStart = new Date(currentDate)
      const dayOfWeek = currentDate.getDay()
      currentWeekStart.setDate(currentDate.getDate() - dayOfWeek)
      
      const weekStart = new Date(currentWeekStart)
      const eventWeekStart = eventStartDate < weekStart ? weekStart : eventStartDate
      
      if (currentDateOnly.getTime() === eventWeekStart.getTime()) {
        return event.title
      }
      
      return '' // 其他日期不显示标题
    },
    getEventTypeColor(type) {
      // 根据事件类型返回不同颜色
      const colorMap = {
        1: '#67C23A', // 课程 - 绿色
        2: '#409EFF', // 会议 - 蓝色
        3: '#E6A23C', // 自定义事件 - 橙色
        4: '#F56C6C', // 资讯 - 红色
        5: '#909399', // 其他 - 灰色
        default: '#409EFF' // 默认蓝色
      }
      return colorMap[type] || colorMap.default
    },
    // 可靠的日期解析：避免 Safari/时区导致的偏移
    parseDateTime(dateTimeStr) {
      if (!dateTimeStr) return null
      // 支持 "YYYY-MM-DD HH:mm:ss" 或 "YYYY-MM-DD" 形式
      const [datePart, timePart = '00:00:00'] = dateTimeStr.trim().split(/\s+/)
      const [y, m, d] = datePart.split('-').map(n => parseInt(n, 10))
      const [hh = 0, mm = 0, ss = 0] = timePart.split(':').map(n => parseInt(n, 10))
      return new Date(y, (m || 1) - 1, d || 1, hh || 0, mm || 0, ss || 0)
    },
    darkenColor(color) {
      // 将颜色变暗，用于渐变效果
      const colorMap = {
        '#67C23A': '#5CB85C', // 绿色变暗
        '#409EFF': '#337ecc', // 蓝色变暗
        '#E6A23C': '#D4952B', // 橙色变暗
        '#F56C6C': '#E85555', // 红色变暗
        '#909399': '#73767A'  // 灰色变暗
      }
      return colorMap[color] || '#337ecc'
    },
    formatDateTime(val) {
      if (!val) return ''
      return val.replace('T', ' ')
      },
      typeText(className) {
      if (!className) return ''
      if (className === 1) return '课程'
      if (className === 3) return '自定义事件'
      if (className === 4) return '资讯'
      return ''
      },
      // 生成动态样式
      generateDynamicStyles() {
        // 移除已存在的动态样式
        const existingStyle = document.getElementById('dynamic-calendar-styles')
        if (existingStyle) {
          existingStyle.remove()
        }
        
        // 创建新的样式元素
        const style = document.createElement('style')
        style.id = 'dynamic-calendar-styles'
        
        // 生成CSS规则
        let cssRules = ''
        
        // 为每个事件生成颜色样式
        this.list.forEach(event => {
          if (event.color) {
            const colorClass = event.color.replace('#', '')
            cssRules += `
              .admin-calendar .event-item.event-color-${colorClass},
              .admin-calendar .continuous-event.event-color-${colorClass} {
                background: ${event.color} !important;
              }
              .admin-calendar .event-item.event-color-${colorClass}:hover,
              .admin-calendar .continuous-event.event-color-${colorClass}:hover {
                background: ${this.adjustBrightness(event.color, -20)} !important;
              }
            `
          }
        })
        
        style.textContent = cssRules
        document.head.appendChild(style)
      },
      
      // 调整颜色亮度
      adjustBrightness(hex, percent) {
        const num = parseInt(hex.replace('#', ''), 16)
        const amt = Math.round(2.55 * percent)
        const R = (num >> 16) + amt
        const G = (num >> 8 & 0x00FF) + amt
        const B = (num & 0x0000FF) + amt
        return '#' + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
          (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
          (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1)
      }
    },
    filters: {
      formatDateTime(val) {
      if (!val) return ''
      return val.replace('T', ' ')
      }
    }
  }
</script>

<style scoped lang="scss">
  ::v-deep .el-calendar__body{
    padding-left:0;
    padding-right:0;
  }
  .admin-calendar {
    background: #f4f6fa;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
  }

  .admin-header {
    background: transparent;
    padding: 20px 30px 10px 30px;
    border-bottom: none;
    box-shadow: none;
  }

  .admin-main {
    flex: 1;
    padding: 20px 30px;
    overflow: hidden;
  }

  .calendar-panel {
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
    padding: 24px;
    height: 100%;
    overflow: auto;
  }

  .calendar-wrapper {
    position: relative;
  }

  /* 不再需要覆盖层，跨天条直接渲染为 wrapper 的绝对定位子元素 */

  .continuous-event {
    pointer-events: auto; /* 条目本身可点击（覆盖父层 none） */
    transition: all 0.2s ease;
  }

.continuous-event:hover {
  transform: translateY(-1px);
  filter: brightness(1.1);
  z-index: 501; /* 悬停时略高于默认 */
}

  /* Element UI 日历样式覆盖 */
  .calendar-panel ::v-deep .el-calendar-table {
    overflow: visible;
  }

  .calendar-panel ::v-deep .el-calendar-table .el-calendar-day {
    position: relative;
    overflow: visible;
    height: auto;
    min-height: 100px;
    padding: 4px;
  }

  /* 禁止点击当月视图中“上月/下月”的日期格子，避免触发月份切换请求 */
  .calendar-panel ::v-deep .el-calendar-table td.is-prev-month .el-calendar-day,
  .calendar-panel ::v-deep .el-calendar-table td.is-next-month .el-calendar-day {
    pointer-events: none;
  }

  .calendar-panel ::v-deep .el-calendar-table td {
    position: relative;
    overflow: visible;
    border: 1px solid #ebeef5;
  }

  .calendar-panel ::v-deep .el-calendar-table tbody tr {
    overflow: visible;
  }

  .calendar-panel ::v-deep .el-calendar-table tbody {
    overflow: visible;
  }

  .cell-content {
    position: relative;
    min-height: 100px;
    display: flex;
    flex-direction: column;
    padding: 2px;
    overflow: visible;
  }

  .date-number {
    font-weight: bold;
    color: #ccc;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    margin: 0;
    width: 100%;
    font-size: 24px;
    text-align: center;
    z-index: 1; /* 位于事件之下，不影响点击 */
    pointer-events: none;
  }

  .event-list {
    flex: 1;
    overflow: visible;
    position: relative;
    z-index: 60; /* 提高单天事件的层级，确保在跨天事件之上 */
    min-height: 60px; /* 确保有足够空间显示多个事件 */
  }

  .event-item {
    font-size: 11px;
    line-height: 14px;
    padding: 1px 3px;
    margin: 1px 0;
    background: #409EFF;
    color: white;
    border-radius: 3px;
    cursor: pointer;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    transition: background-color 0.2s;
    position: relative;
    height: 16px; /* 固定高度 */
    display: flex;
    align-items: center;
  }

  .event-title {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .event-item:hover {
    background: #337ecc;
  }

  /* 不同事件类型的颜色 */
  .event-item.event-type-1 {
    background: #67C23A; /* 课程 - 绿色 */
  }

  .event-item.event-type-2 {
    background: #409EFF; /* 会议 - 蓝色 */
  }

  .event-item.event-type-3 {
    background: #E6A23C; /* 自定义事件 - 橙色 */
  }

  .event-item.event-type-4 {
    background: #F56C6C; /* 资讯 - 红色 */
  }

  .event-item.event-type-5 {
    background: #909399; /* 其他 - 灰色 */
  }

  .event-item.event-type-default {
    background: #409EFF; /* 默认 - 蓝色 */
  }

  /* 动态颜色支持 - 根据color字段设置背景色 */
  .event-item[class*="event-color-"] {
    /* 默认样式，会被具体的颜色类覆盖 */
  }

  /* 连续事件的动态颜色支持 */
  .continuous-event[class*="event-color-"] {
    /* 默认样式，会被具体的颜色类覆盖 */
  }

  /* 悬停效果 */
  .event-item.event-type-1:hover {
    background: #5CB85C;
  }

  .event-item.event-type-2:hover {
    background: #337ecc;
  }

  .event-item.event-type-3:hover {
    background: #D4952B;
  }

  .event-item.event-type-4:hover {
    background: #E85555;
  }

  .event-item.event-type-5:hover {
    background: #73767A;
  }

  .event-item.event-type-default:hover {
    background: #337ecc;
  }

  /* 连续事件的特殊样式 */
  .event-item[style*="position: absolute"] {
    border-radius: 3px !important;
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .event-item[style*="position: absolute"]:hover {
    transform: translateY(-1px);
    filter: brightness(1.1);
  }

  .mt-4 {
    margin-top: 24px;
  }

  .mb-2 {
    margin-bottom: 8px;
  }

  @media (max-width: 1200px) {
    .admin-header {
      padding: 15px 20px;
  }

    .admin-main {
      padding: 15px 20px;
    }
    
    .calendar-panel {
      padding: 16px;
    }
  }
</style>
