Vue 2 项目集成 TOAST UI Calendar:@toast-ui/vue-calendar 安装、配置与事件处理实战指南

原创2026-09-22 15:24:2118 阅读
文章标签:前端UI组件

Vue 2 项目集成 TOAST UI Calendar:@toast-ui/vue-calendar 安装、配置与事件处理实战指南

@toast-ui/vue-calendar 是 TOAST UI Calendar 官方为 Vue 2 提供的组件封装(Vue Wrapper),本文将基于仓库中的官方入门文档(apps/vue-calendar/docs/ko/guide/getting-started.md)展开,完整讲解从环境准备、npm 安装、模块加载、CSS 引入,到 Props 传参、事件监听、实例方法调用以及关闭 GA 统计的全部流程。读完本文,你将能够在自己的 Vue 2 项目中快速落地一个可配置视图、可绑定数据、可响应交互、可调用底层实例的完整日历应用,并能结合源码理解 Wrapper 的底层实现机制。

环境准备:Vue 2 是硬性前提

要使用 TOAST UI Calendar 的 Vue Wrapper,必须先安装 Vue 2。Vue 3 目前不被支持。

这一点在源码的依赖声明中同样可以确认:apps/vue-calendar/package.jsonvue 声明为 peerDependencies,其版本约束为 ^2.6.14,开发依赖同样锁定在 vue: ^2.6.14。也就是说,如果你的项目是 Vue 3(Composition API 或 <script setup>),本包并不适用,需要等待官方提供 Vue 3 版本或改用其他集成方案。

TOAST UI 系列产品既支持通过包管理器安装,也支持直接下载源码使用,但官方明确推荐使用包管理器(npm)。

安装:使用 npm 获取 Wrapper

@toast-ui/vue-calendar 已发布到 npm 包管理器。使用 npm 需要预先安装 Node.js。

# 安装最新版本
npm install @toast-ui/vue-calendar

# 安装指定版本
npm install @toast-ui/vue-calendar@<version>

安装完成后,包会同时带来其核心依赖 @toast-ui/calendar(版本 ^2.1.3,见 package.json),Wapper 本身不包含日历实现,而是基于这个核心包进行封装。

加载模块:三种引入方式

TOAST UI Calendar Vue Wrapper 支持三种加载方式,适用于不同运行环境:

/* Node.js 环境:ES6 模块 */
import Calendar from '@toast-ui/vue-calendar';
/* Node.js 环境:CommonJS */
const Calendar = require('@toast-ui/vue-calendar');
/* 浏览器环境:namespace */
const Calendar = tui.VueCalendar;

包导出配置与这三种方式一一对应(见 package.jsonexports 字段):import 指向 ESM 构建产物 toastui-vue-calendar.mjsrequire 指向 CommonJS 构建产物 toastui-vue-calendar.js。同时,该包还额外提供了 ./esm 导出路径,便于需要纯 ESM 产物的场景使用。

为 IE11 等遗留浏览器加载带 polyfill 的版本

默认构建产物面向现代浏览器的最新两个大版本,但不包含 IE11 所需的 polyfill。若要支持 IE11 或更旧的浏览器,必须改从专用入口加载带 polyfill 的 IE11 版本:

/* Node.js 环境:ES6 模块 */
import Calendar from '@toast-ui/vue-calendar/ie11';
/* Node.js 环境:CommonJS */
const Calendar = require('@toast-ui/vue-calendar/ie11');

需要特别注意的是:IE11 版本因为注入了 polyfill,体积约为默认版本的 2 倍。官方文档提醒务必评估真实的浏览器支持范围,避免无谓地增大打包体积。从 package.json 的构建脚本(build:ie11 使用 Vite 的 ie11 模式)可以看到,该版本是独立构建产物 toastui-vue-calendar.ie11.js

引入 CSS:日历样式是必需的

要让日历正常渲染,必须引入 TOAST UI Calendar 的样式文件。样式文件来自核心包 @toast-ui/calendar,可以通过 import / require 或 CDN 引入。

/* Node.js 环境:ES6 模块 */
import '@toast-ui/calendar/dist/toastui-calendar.min.css'; // Calendar 样式
/* Node.js 环境:CommonJS */
require('@toast-ui/calendar/dist/toastui-calendar.min.css');
<!-- CDN -->
<link rel="stylesheet" href="https://uicdn.toast.com/calendar/latest/toastui-calendar.min.css" />

补充说明:如果启用了内置的日程创建弹窗(useFormPopup),官方 options 文档(docs/ko/apis/options.md)还提示需要额外引入 tui-date-pickertui-time-picker 的 CSS,否则日期/时间选择器的样式无法正确应用。仓库示例 example/App.vue 中正是这样做的:

import 'tui-date-picker/dist/tui-date-picker.min.css';
import 'tui-time-picker/dist/tui-time-picker.min.css';

在 Vue 中使用:两种挂载方式

Wrapper 是一个全局注册的 Vue 组件,既可以在组件(SFC)中注册使用,也可以在 new Vue 实例中直接使用。

方式一:在单文件组件中注册

<template>
  <Calendar style="height: 800px" />
</template>

<script>
import Calendar from '@toast-ui/vue-calendar';
import '@toast-ui/calendar/dist/toastui-calendar.min.css';

export default {
  name: 'YourComponent',
  components: {
    Calendar,
  },
};
</script>

方式二:在 Vue 实例中挂载

import Calendar from '@toast-ui/vue-calendar';
import '@toast-ui/calendar/dist/toastui-calendar.min.css';

new Vue({
  el: '#app',
  components: {
    Calendar,
  },
});

注意上面两种写法都没有显式传入任何配置,日历将以默认配置渲染(默认视图为 week,见 docs/ko/apis/options.mddefaultView 说明)。

Props:以 Vue 属性方式传参

TOAST UI Calendar 的所有选项都被实现为 Vue 组件的 Props。只有一个特殊映射:核心选项中的 defaultView 在组件中更名为 view,其余选项保持同名。

src/Calendar.js 的源码可以确认 Wrapper 完整暴露了以下 Props:

Prop 类型 对应 Calendar 选项/行为
view String defaultView(可选值 `'month'
useFormPopup Boolean useFormPopup
useDetailPopup Boolean useDetailPopup
isReadOnly Boolean isReadOnly
usageStatistics Boolean usageStatistics
eventFilter Function eventFilter
week Object week
month Object month
gridSelection Object / Boolean gridSelection
timezone Object timezone
theme Object 通过 setTheme() 应用
template Object template
calendars Array 通过 setCalendars() 应用
events Array 通过 clear() + createEvents() 应用

除选项外,events Prop 可以直接传入日程数据,组件挂载时会立即创建这些日程。

一个完整的 Props 使用示例

<template>
  <Calendar
    style="height: 800px"
    :view="view"
    :use-detail-popup="true"
    :month="month"
    :calendars="calendars"
    :events="events"
  />
</template>

<script>
import Calendar from '@toast-ui/vue-calendar';
import '@toast-ui/calendar/dist/toastui-calendar.min.css';

export default {
  name: 'YourComponent',
  components: {
    Calendar,
  },
  data() {
    return {
      view: 'month',
      month: {
        dayNames: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
        visibleWeeksCount: 3,
      },
      calendars: [{ id: 'cal1', name: 'Personal' }],
      events: [
        {
          id: '1',
          calendarId: 'cal1',
          title: 'Lunch',
          category: 'time',
          start: '2022-06-28T12:00:00',
          end: '2022-06-28T13:30:00',
        },
        {
          id: '2',
          calendarId: 'cal1',
          title: 'Coffee Break',
          category: 'time',
          start: '2022-06-28T15:00:00',
          end: '2022-06-28T15:30:00',
        },
      ],
    };
  },
};
</script>

示例中演示了几个关键点:

  • 视图切换view: 'month' 指定初始视图为月视图;
  • 月视图细分配置month 对象中的 dayNames 自定义星期表头缩写,visibleWeeksCount: 3 控制月视图只显示 3 周(对应选项文档中的 month.visibleWeeksCount);
  • 日历与日程数据calendars 声明日历分类(含 idname),events 中的每条日程通过 calendarId 归属到对应日历;日程对象的 category: 'time' 表示按时段渲染,start/end 采用 ISO 8601 字符串。

Prop 的响应式更新机制(源码级原理)

Wrapper 并非简单地把 Props 一次性传给核心日历,而是针对每个 Prop 都建立了响应式监听。从 src/Calendar.jswatch 块可以看到:

  • view 变化 → 调用 calendarInstance.changeView(value) 切换视图;
  • useFormPopupuseDetailPopupisReadOnlyeventFilterweekmonthgridSelectiontimezonetemplate 变化 → 调用 calendarInstance.setOptions({ ... }) 更新对应选项;
  • theme 变化 → 调用 calendarInstance.setTheme(value)
  • calendars 变化 → 调用 calendarInstance.setCalendars(value)
  • events 变化 → 先 clear() 清空,再 createEvents(value) 重新创建。

这意味着你在父组件中动态修改这些数据属性时,日历会实时响应,无需手动刷新。

生命周期中的初始化(源码级原理)

组件挂载(mounted)时,Wrapper 用 this.$refs.container 作为容器创建核心实例:

this.calendarInstance = new Calendar(this.$refs.container, {
  defaultView: this.view,
  useFormPopup: this.useFormPopup,
  useDetailPopup: this.useDetailPopup,
  // ... 其余选项
});
this.addEventListeners();
this.calendarInstance.createEvents(this.events);

销毁(beforeDestroy)时则依次调用 calendarInstance.off()calendarInstance.destroy() 完成事件解绑和资源释放。容器元素是一个带有 toastui-vue-calendar class 的 <div ref="container">

事件:用 v-on 监听日历交互

Vue 的 v-on 指令可以直接用于监听日历实例事件,事件名与核心 Calendar 的实例事件一一对应。各事件的完整语义(参数结构、触发时机)请参考核心文档的实例事件章节:docs/ko/apis/calendar.md

<template>
  <Calendar
    style="height: 800px"
    ref="calendar"
    @selectDateTime="onSelectDateTime"
    @beforeCreateSchedule="onBeforeCreateSchedule"
  />
</template>

<script>
import Calendar from '@toast-ui/vue-calendar';
import '@toast-ui/calendar/dist/toastui-calendar.min.css';

export default {
  name: 'YourComponent',
  components: {
    Calendar,
  },
  methods: {
    onSelectDateTime({ start, end }) {
      alert(`Select ${start} ~ ${end}`);
    },
    onBeforeCreateSchedule(event) {
      const calendarInstance = this.$refs.calendar.getInstance();
      calendarInstance.createEvents([
        {
          ...event,
          id: uuid(),
        }
      ]);
    },
  },
};
</script>

这个示例呈现了 Vue 集成中最典型的“选择时间段 → 创建日程”闭环:用户在日历上框选时间段触发 selectDateTime,回调中拿到 start/end;随后表单提交触发 beforeCreateSchedule,回调里通过 getInstance() 拿到核心实例并调用 createEvents() 把新日程写回日历。

事件转发的底层实现(源码级原理)

Wrapper 的事件能力建立在 Vue 2 的 $listeners 机制之上。看 src/Calendar.js 中的 addEventListeners

addEventListeners() {
  Object.keys(this.$listeners).forEach((eventName) => {
    this.calendarInstance.on(eventName, (...args) => this.$emit(eventName, ...args));
  });
}

即:组件挂载时,把父组件通过 v-on 绑定的所有监听器逐个注册到核心实例上,核心实例触发事件时再通过 $emit 回传给父组件。因此父组件侧无需关心事件是来自核心库还是 Wrapper,用法与普通 Vue 事件完全一致。

仓库示例 example/App.vue 中还演示了更多事件组合,包括 beforeCreateEventbeforeUpdateEventbeforeDeleteEventafterRenderEventclickDayNameclickEventclickTimezonesCollapseBtn 等,可作为事件用法的完整参考。

方法:getRootElement 与 getInstance

Wrapper 为组件暴露了两个实例方法(点击方法名可查看更详细的说明与使用示例):

方法 说明
getRootElement 返回 TOAST UI Calendar 挂载的 DOM 元素
getInstance 返回 TOAST UI Calendar 核心实例

getRootElement

  • 类型:getRootElement(): HTMLDivElement
  • 返回值:HTMLDivElement —— 日历挂载的容器元素

源码实现非常直接(src/Calendar.js):

getRootElement() {
  return this.$refs.container;
}

getInstance

  • 类型:getInstance(): Calendar
  • 返回值:Calendar —— TOAST UI Calendar 核心实例

拿到核心实例后,就可以调用完整的日历实例方法(如 setDatechangeViewcreateEventsupdateEventdeleteEventtodaymove 等)。类型声明同样可以在 index.d.ts 中找到(同时提供了模块导出与 tui.VueCalendar namespace 两种类型形态)。

方法使用示例

<template>
  <Calendar
    style="height: 800px"
    ref="calendar"
  />
</template>

<script>
import Calendar from '@toast-ui/vue-calendar';
import '@toast-ui/calendar/dist/toastui-calendar.min.css';

export default {
  name: 'YourComponent',
  components: {
    Calendar,
  },
  computed: {
    calendarInstance() {
      return this.$refs.calendar.getInstance();
    }
  },
  mounted() {
    this.calendarInstance.setDate('2022-06-29T12:30:00');
  }
};
</script>

getInstance() 放进 computed 是常用模式,这样后续任意方法中都可以直接通过 this.calendarInstance 操作日历。仓库示例 example/App.vue 正是这样组织代码的——它的 Today/Prev/Next 按钮分别调用 today()move(offset),日程增删改分别调用 createEvents/updateEvent/deleteEvent,并通过 getDate()getDateRangeStart()getDateRangeEnd() 计算并展示当前日期范围文本。

关闭 Google Analytics(GA)统计

TOAST UI Calendar 通过 Google Analytics 收集开源使用统计(仅采集 location.hostname,例如 ui.toast.com),用于衡量项目在全球的使用广度,并作为项目未来走向的重要决策指标。统计数据的唯一用途就是衡量使用量。

如果你不希望上报数据,将 usageStatistics 选项设为 false 即可(该选项默认值为 true,见 docs/ko/apis/options.md)。在 Vue Wrapper 中对应的是 usage-statistics Prop:

<template>
  <Calendar :usage-statistics="false"/>
</template>

在源码中,该 Prop 的类型为 Boolean(src/Calendar.js),挂载时作为 usageStatistics 选项原样传入核心实例,因此关闭统计只需这一行属性。

结语:从官方示例到你的项目

本文基于官方入门文档(apps/vue-calendar/docs/ko/guide/getting-started.md)并对照源码展开,完整覆盖了 Vue 2 项目中接入 TOAST UI Calendar 的路径:安装(npm)→ 加载(ESM/CommonJS/namespace/IE11)→ 引入 CSS → 组件注册与 Props 传参 → v-on 事件监听 → 实例方法调用 → 关闭 GA 统计。

如果要从 Vue 1.x 升级到 Vue 2 版本,可以进一步参考官方迁移指南 apps/vue-calendar/docs/ko/guide/migration-guide-v2.md。最直观的实战样板则在仓库的 example/ 目录下:App.vue 展示了视图切换、多时区、主题、模板、完整事件流与实例方法的综合用法,main.js 展示了最小入口挂载方式——直接对照这份示例改造,即可快速搭建出满足业务需求的日历应用。

登录后查看全文
tui.calendar