Vue 2 项目集成 TOAST UI Calendar:@toast-ui/vue-calendar 安装、配置与事件处理实战指南
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.json 将 vue 声明为 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.json 的 exports 字段):import 指向 ESM 构建产物 toastui-vue-calendar.mjs,require 指向 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-picker 与 tui-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.md 的 defaultView 说明)。
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声明日历分类(含id与name),events中的每条日程通过calendarId归属到对应日历;日程对象的category: 'time'表示按时段渲染,start/end采用 ISO 8601 字符串。
Prop 的响应式更新机制(源码级原理)
Wrapper 并非简单地把 Props 一次性传给核心日历,而是针对每个 Prop 都建立了响应式监听。从 src/Calendar.js 的 watch 块可以看到:
view变化 → 调用calendarInstance.changeView(value)切换视图;useFormPopup、useDetailPopup、isReadOnly、eventFilter、week、month、gridSelection、timezone、template变化 → 调用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 中还演示了更多事件组合,包括 beforeCreateEvent、beforeUpdateEvent、beforeDeleteEvent、afterRenderEvent、clickDayName、clickEvent、clickTimezonesCollapseBtn 等,可作为事件用法的完整参考。
方法: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 核心实例
拿到核心实例后,就可以调用完整的日历实例方法(如 setDate、changeView、createEvents、updateEvent、deleteEvent、today、move 等)。类型声明同样可以在 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 展示了最小入口挂载方式——直接对照这份示例改造,即可快速搭建出满足业务需求的日历应用。