在 Vue 项目中玩转 FullCalendar:从零搭建可交互的事件日历

在很多业务场景中,我们都需要把「时间维度上的事件」清晰地呈现在一个可交互的日历里,并和其它数据视图(图表、报表、分析面板等)联动。本文基于一个典型的「事件日历 + 外部报表联动」场景,总结一套在 Vue 项目中落地 FullCalendar 的通用实践。

概览

FullCalendar 是最受欢迎的js calendar组件

官网:fullcalendar.io/

demos: fullcalendar.io/demos

vue demo: github.com/fullcalenda...

安装

使用 NPM 或 Yarn 安装软件包core以及您计划使用的任何插件(按需安装) 插件列表:fullcalendar.io/docs/plugin...

标题 描述 示意
@fullcalendar/core 必须
@fullcalendar/vue vue2项目
@fullcalendar/vue3 vue3项目
@fullcalendar/interaction 支持点击、拖拽等事件
@fullcalendar/daygrid DayGrid 视图
@fullcalendar/timegrid timegrid视图
@fullcalendar/resource-timeline 时间轴视图
js 复制代码
npm install \
  @fullcalendar/core \ 
  @fullcalendar/vue  \  
  @fullcalendar/daygrid \   
  @fullcalendar/timegrid \   
  @fullcalendar/interaction \  

如果是vue3项目用@fullcalendar/vue3

使用

引入组件

xml 复制代码
<template>
    <FullCalendar ref="myCalendar" :options="calendarOptions" />
</template>

<script>
  // 引入已经安装好的,页面所需要的 FullCalendar 插件
  import FullCalendar from '@fullcalendar/vue'
  import dayGridPlugin from '@fullcalendar/daygrid'
  import timeGridPlugin from '@fullcalendar/timegrid'
  import interactionPlugin from '@fullcalendar/interaction'
  // 日历参数配置
  const calendarOptions = {}

  export default {
    name: "my-calendar",
    components: {
      FullCalendar
    },
    data () {
      return {
        calendarOptions
      }
    }
  }
</script>

参数配置

dayGridMonth视图的简单配置参考:

js 复制代码
calendarOptions = {
        locale: 'zh-cn',
        plugins: [
          dayGridPlugin,
          // interactionPlugin, // needed for dateClick
        ],
        headerToolbar: {
          left: 'prev,next today',
          center: 'title',
          right: 'dayGridMonth',
        },
        buttonText: { today: '今天', prev: '上个月', next: '下个月', dayGridMonth: '月' }, // 设置按钮文本内容
        initialView: 'dayGridMonth',
        initialEvents: [], // alternatively, use the `events` setting to fetch from a feed

        firstDay: 1,
        aspectRatio: 1.35, // 日历单元格宽高比 默认值:1.35
        eventColor: '#3a79eb', // 日历中事件的默认背景色颜色,优先级低于添加事件时设置的背景色
        dayMaxEvents: true,
        editable: false,
        selectable: false,
        selectMirror: true,
        dayMaxEvents: true,
        weekends: true,
        events: this.fetchEvents, // 获取事件
        eventClick: this.handleEventClick, // 点击事件
        eventsSet: this.handleEvents,
        // you can update a remote database when these fire:
        // eventChange: this.handleEventChange        // eventAdd:
        // eventRemove:

详细的配置可参考文章:blog.csdn.net/FlowGuanEr/...

slot模板

xml 复制代码
<template>
  <FullCalendar :options="calendarOptions">
    <template v-slot:eventContent='arg'>
      <b>{{ arg.event.title }}</b>
    </template>
  </FullCalendar>
</template>

Calendar API

ini 复制代码
let calendarApi = this.$refs.myCalendar.getApi() 
calendarApi.next()

事件

1. 事件对象

js 复制代码
var calendar = new Calendar(calendarEl, {
  timeZone: 'UTC',
  events: [
    {
      id: 'a',
      title: 'my event',
      start: '2018-09-01',
      end: '2018-09-01'
    }
  ]
})

更多字段详解:fullcalendar.io/docs/event-...

2. 初始化事件

可以在initialEvents配置初始化事件列表

也可以用events中配置方法,调用接口去获取数据,将数据格式化成事件对象规范的格式,显示事件列表。

事件排序 :接口返回的顺序可能杂乱,建议在传给 successCallback 前对列表按 start(及可选的 endtitle)排序,这样同一天内多事件在月视图中的展示顺序一致、可预期(FullCalendar 会按你传入的顺序在同一格内排列)。

js 复制代码
fetchEvents (info, successCallback, failureCallback) {
  // info.start / info.end 是当前视图的起止时间
  requestAPI(api.fetchCalendarEvents, {
    startTime: info.start.getTime(),
    endTime: info.end.getTime()
  }).then(data => {
    if (data?.length) {
      const list = data.map((item) => ({
        id: item.eventId,
        title: item.eventTitle,
        start: moment(item.startTime).format('YYYY-MM-DD'),
        end: moment(item.endTime + MS_PER_DAY).format('YYYY-MM-DD')
      }))
      // 按开始时间排序,同一天内按结束时间、再按标题排序,保证展示顺序稳定
      list.sort((a, b) => {
        const startDiff = new Date(a.start) - new Date(b.start)
        if (startDiff !== 0) return startDiff
        const endDiff = new Date(a.end) - new Date(b.end)
        if (endDiff !== 0) return endDiff
        return (a.title || '').localeCompare(b.title || '')
      })
      successCallback(list)
    } else {
      failureCallback()
    }
  }).catch(failureCallback)
}

3. 事件回调

实战:事件日历与外部报表的联动方案

下面是一个抽象化的实战场景:在某个运营看板页面,我们用 FullCalendar 搭建了一个「事件日历」,并和右侧的数据分析报表(通过 iframe 嵌入)联动,整体思路可以概括为三步:

  1. Calendar 只负责展示事件排期
    • 通过 events: this.fetchEvents 懒加载当前视图范围内的事件,避免一次性加载整年数据。
    • 接口返回后在前端做一次格式化,转成 FullCalendar 认可的事件对象:
      • id: 使用 eventId 标识事件;
      • title: 使用 eventTitle 作为日历上展示的文案;
      • start / end: 用 moment 格式化为 YYYY-MM-DD,结束时间额外 +1 天,避免跨天活动少算一天。
    • 在调用 successCallback(list) 前对 liststartendtitle 排序,保证同一天内多事件的展示顺序稳定(见上文「事件排序」)。
js 复制代码
fetchEvents (info, successCallback, failureCallback) {
  requestAPI(api.fetchCalendarEvents, {
    startTime: info.start.getTime(),
    endTime: info.end.getTime()
  }).then(data => {
    if (data?.length) {
      const list = data.map(item => ({
        id: item.eventId,
        title: item.eventTitle,
        start: moment(item.startTime).format('YYYY-MM-DD'),
        end: moment(item.endTime + MS_PER_DAY).format('YYYY-MM-DD')
      }))
      list.sort((a, b) => {
        const startDiff = new Date(a.start) - new Date(b.start)
        if (startDiff !== 0) return startDiff
        const endDiff = new Date(a.end) - new Date(b.end)
        if (endDiff !== 0) return endDiff
        return (a.title || '').localeCompare(b.title || '')
      })
      successCallback(list)
    } else {
      failureCallback()
    }
  }).catch(failureCallback)
}
  1. 点击日历事件,高亮并联动外部报表
    • eventClick 中拿到被点击的事件,做两件事:
      • 把上一次选中的事件颜色还原为默认蓝色;
      • 把当前事件改成高亮色,并记录 currentEventId
    • 同时,基于事件编号 eventId 拼接外部报表地址,赋值给 pageUrl,iframe 会自动切到该事件对应的数据分析页面:
js 复制代码
handleEventClick (clickInfo) {
  if (clickInfo?.event?.id) {
    this.currentEvents.forEach(event => {
      if (event.id === this.currentEventId) {
        event.setProp('color', '#3a79eb')
      }
    })
    clickInfo.event.setProp('color', '#db3491')
    this.currentEventId = clickInfo.event.id
    this.pageUrl = `${REPORT_BASE_URL}?eventId=${clickInfo.event.id}`
  }
}
  1. 通过插槽自定义事件渲染
    • 使用 eventContent 插槽可以灵活控制日历单元里的展示结构,比如在运营看板里我们希望同时显示「活动时间 + 活动名称」:
vue 复制代码
<FullCalendar class="calendar-app-calendar" :options="calendarOptions">
  <template v-slot:eventContent="arg">
    <b>{{ arg.timeText }}</b>
    <i>{{ arg.event.title }}</i>
  </template>
</FullCalendar>

综合以上三点,一个完整的「事件日历 + 数据分析联动」就搭建好了:

使用者只需要在日历上点选某个事件,对应的外部报表就会自动切换到该事件的分析视图,从「时间排期」自然跳转到「结果分析」,大大提升日常分析效率。

相关推荐
决斗小饼干2 小时前
低代码平台工作流引擎设计:从状态机到智能流转的技术演进
前端·低代码·工作流引擎
豆苗学前端2 小时前
彻底讲透浏览器缓存机制,吊打面试官
前端·javascript·面试
米丘2 小时前
了解 window.history 和 window.location, 更好地掌握 vue-router、react-router单页面路由
前端
swipe2 小时前
箭头函数与 this 面试题深度解析:从原理到实战
前端·javascript·面试
星_离2 小时前
《Vue 自定义指令注册技巧:从手动到自动,效率翻倍》
前端·vue.js
狗头大军之江苏分军2 小时前
消耗 760万 Token 后,一文看懂了“小龙虾” OpenClaw 和 OpenCode 的区别
前端·后端
毛骗导演2 小时前
万字解析 OpenClaw 源码架构-安全与权限
前端·架构
哇哇哇哇3 小时前
vue3 ref解析
前端
哇哇哇哇3 小时前
vue3 reactive解析
前端