微信小程序实现日历时间轴以及时间轴内容

82 阅读2分钟

📌 背景介绍

最近在做一个会议室预约系统,需要实现一个「日历时间轴」页面:

  • 上方显示日期(支持左右滑动切换)
  • 左侧是时间轴(例如 08:30、09:00、09:30 …)
  • 右侧展示会议预约块(不同时间段显示会议内容)
  • 支持动态计算高度、重叠会议的布局、当前时间线显示等。

🧩 实现步骤

  • 先定义左侧固定的时间刻度
左侧显示固定的时间刻度
const timeMap = {
1: '08:30',
2: '09:00',
3: '09:30',
4: '10:00',
5: '10:30',
// ...
};
  • 会议预约内容
时间使用具体的数字和timeMap进行映射
{
  id: 1,
  startTime: 2,
  endTime: 6,
  data: {
    title: '项目讨论',
    host: '张三',
    comment: '主要讨论开发进度',
  }
}
  • 动态计算每个会议块的位置以及高度
const SLOT_HEIGHT = 100 // 每半小时的高度
const SLOT_LEFT = 72 // 左侧偏移的部分
const OVERLAP_OFFSET = 70 // 如果有多个的话会有重叠部分
// 动态计算每个会议块的位置
const positionedMeetings = computed(() => {
  if (!data.value?.data) return []
  const result: any[] = []
  const filteredMeetings = data.value.data.filter((m) => {
    return m.meetingDate === selectedDate.value
  })

  filteredMeetings.forEach((m) => {
    if (!m.startTime) m.startTime = 1
    if (!m.endTime || m.endTime <= m.startTime) m.endTime = m.startTime + 1
    if (m.startTime % 2 !== 0) return

    const top = (m.startTime - 1) * SLOT_HEIGHT
    const height = Math.max((m.endTime - m.startTime) * SLOT_HEIGHT, SLOT_HEIGHT)

    // 找出重叠会议块
    const overlapping = result.filter(
      (r) => !(r.endTime <= m.startTime || r.startTime >= m.endTime),
    )

    const left = SLOT_LEFT + overlapping.length * OVERLAP_OFFSET

    result.push({
      id: m.id,
      startTime: m.startTime,
      endTime: m.endTime,
      conferenceStatus: m.conferenceStatus,
      data: m,
      top,
      height,
      left,
    })
  })

  return result
})

  • 当同一时间段存在多个会议室的预约时,多个会议块会在页面上重叠。为提升交互体验,在用户点击会议块时,会动态调整该会议块的 z-index,让它显示在最上方,避免被其他会议信息遮挡。
// 子组件请求提升层级时调用
function raiseLayer() {
  popupZIndex.value = 9999
  // 也可以同步调整会议块层级,比如:
  positionedMeetings.value.forEach((m) => (m.zIndex = 1))
}

// 子组件请求恢复层级
function restoreLayer() {
  popupZIndex.value = 2000
}
//点击会议卡片的时候切换层级
function viewMeetingDetails(meeting) {
  meetingDetailsShow.value = true
  currentMeeting.value = { ...meeting } // 拷贝为普通对象

  // 计算当前的最大卡片 zIndex
  const all = positionedMeetings.value
  const maxZ = Math.max(...all.map((m) => m.zIndex ?? 10))
  // 提升被点击卡片到最上
  meeting.zIndex = maxZ + 1

  // 为了防止 zIndex 不断变大,我们可以做一次归整(可选)
  // 按 zIndex 排序并重新分配基准 zIndex,使值保持在一个合理范围
  const sorted = all.slice().sort((a, b) => (b.zIndex ?? 10) - (a.zIndex ?? 10))
  let base = 100 // 你可以设置基值
  sorted.forEach((m, i) => {
    m.zIndex = base - i // 大的在上,小的在下
  })

  // uni.navigateTo({ url: '/pages-sub/workbench/chamber/meetingDetails' })
}
function handleMeetingDetailsClose(meeting) {
  meetingDetailsShow.value = false
  positionedMeetings.value.forEach((m) => {
    m.zIndex = m.initialZIndex
  })
}

以上是部分关键代码,完整代码在