Mantine Schedule 组件库实战指南:用 @mantine/schedule 构建企业级日历与排期界面
2026/9/10 8:37:16 网站建设 项目流程

Mantine Schedule 组件库实战指南:用 @mantine/schedule 构建企业级日历与排期界面

【免费下载链接】mantineA fully featured React components library项目地址: https://gitcode.com/GitHub_Trending/ma/mantine

@mantine/schedule是 Mantine 生态中面向 React 的完整日历/排期组件包,提供日、周、月、年四种视图以及资源视图(Resource Views)与议程视图(Agenda View),内置重复事件(RFC 5545 RRule)、拖拽移动、边缘缩放、时间槽框选、外部拖入等交互能力。本文以该包在 packages/@mantine/schedule 中的真实源码实现为依据,从安装配置、事件数据结构、各视图 API、交互事件到 i18n 与样式定制,系统讲解如何在一个 Mantine 应用中落地一套生产可用的排期界面。

安装与依赖关系

在 Mantine 项目中安装@mantine/schedule需要同时安装它的几个核心依赖包。根据包内 package.json 中的peerDependencies声明,它依赖:

  • @mantine/core
  • @mantine/dates
  • @mantine/hooks
  • react(^19.2.0)与react-dom(^19.2.0)

官方 README 提供了两种包管理器安装方式:

# With yarn yarn add @mantine/schedule @mantine/core @mantine/hooks @mantine/dates # With npm npm install @mantine/schedule @mantine/core @mantine/hooks @mantine/dates

其中@mantine/dates是必须的,因为 Schedule 系列组件通过useDatesContext()读取DatesProvider提供的 locale、firstDayOfWeekweekendDays等全局日期配置(见 DayView.tsx 与 WeekView.tsx),组件间通过上下文共享同一套日期语义。

运行时依赖方面,@mantine/schedule还直接依赖rrule(^2.8.1)用于解析与展开 RFC 5545 重复规则。安装后需要确保引入包导出的样式文件(如@mantine/schedule/styles.css,见 package.json 的exports字段),并按 Mantine 文档约定在全局配置中启用 MantineProvider。

包结构与组件全景

从 index.ts 的导出清单可以完整看到包的组成:

  • 主入口组件Schedule:一个可切换四种视图的“一站式”调度器;
  • 独立视图组件:DayViewWeekViewMonthViewYearView
  • 资源视图:ResourcesDayViewResourcesWeekViewResourcesMonthViewResourcesSchedule
  • 辅助视图:AgendaViewMobileMonthView
  • 底层构件:ScheduleEventScheduleBackgroundEventScheduleHeaderCurrentTimeIndicatorMoreEventsDragContext
  • 类型、工具函数与标签(i18n)定义。

仓库结构上,components/下每个视图目录都自带同名.module.css.story.tsx.test.tsx,并配有独立的布局计算工具目录(如WeekView/get-week-view-events/),便于读者按模块深入阅读实现细节。

事件数据模型:ScheduleEventData

所有视图消费统一的事件数据结构ScheduleEventData,它由三种变体联合而成(定义在 types.ts):

  1. 单次事件ScheduleSingleEventData:不含recurrence字段;
  2. 重复系列ScheduleRecurringSeriesEventData:带recurrence(rrule 规则);
  3. 单次覆盖ScheduleRecurringOverrideEventData:通过recurringEventId+recurrenceId覆盖系列中的某一次发生。

公共字段(ScheduleEventBase,见 types.ts):

字段类型说明
idstring \| number事件唯一标识,用作 key 与识别
titlestring事件标题
start/endDate \| DateTimeStringValue开始/结束时间,字符串格式为YYYY-MM-DD HH:mm:ss
colorMantineColortheme.colors键名或任意合法 CSS 颜色
variant'filled' \| 'light'事件样式变体,默认light
display'default' \| 'background'背景事件以整块色块渲染在普通事件之后,默认非交互
payloadRecord<PropertyKey, any>业务自定义数据,库内部不消费
resourceIdstring \| number资源视图中使用的事件归属

时间统一约定:日期字符串为YYYY-MM-DDDateStringValue),日期时间字符串为YYYY-MM-DD HH:mm:ssDateTimeStringValue),这是包内所有回调、工具函数与内部计算的通用格式。

一个典型的事件数组示例(取自 Schedule.story.tsx 的真实用法):

import { ScheduleEventData } from '@mantine/schedule'; const events: ScheduleEventData[] = [ { id: 1, title: 'Team Meeting', start: new Date(2024, 0, 15, 10, 0), end: new Date(2024, 0, 15, 11, 0), color: 'blue', payload: {}, }, { id: 2, title: 'Conference Day 1', start: new Date(2024, 0, 15, 0, 0, 0), end: new Date(2024, 0, 15, 23, 59, 59), color: 'cyan', payload: {}, }, ];

重复事件(Recurrence)

重复系列通过 RFC 5545 规则描述:

const recurringEvent: ScheduleEventData = { id: 'weekly-standup', title: 'Weekly Standup', start: '2024-01-15 09:00:00', end: '2024-01-15 09:30:00', color: 'blue', recurrence: { rrule: 'FREQ=WEEKLY;BYDAY=MO,WE,FR', exdate: ['2024-01-17 09:00:00'], // 排除特定日期 dtstart: '2024-01-15 09:00:00', // 显式系列起点(可选) }, };

运行时会由expandRecurringEvents(见 expand-recurring-events.ts)在渲染前把系列展开为可见范围内的具体发生实例:

  • 使用RRule.parseString解析 rrule 字符串,兼容带RRULE:前缀与多行 iCalendar 文本的情况(getRRuleString);
  • 通过rule.between()查询与当前视图范围重叠的发生,并按expansionLimit(默认 2000,见DEFAULT_EXPANSION_LIMIT)截断,防止无限循环;
  • 生成实例的id形如{seriesId}::{recurrenceId},并附带recurringInstance元数据(isRecurringInstancerecurringEventIdrecurrenceIdoriginalStart/originalEnd),拖拽/缩放后可用于回写原系列;
  • exdate命中的发生被跳过,若存在对应的 override 事件则替换为该 override。

每个视图组件都在渲染前调用expandRecurringEvents(例如 DayView.tsx),因此无需手工展开。

一站式入口:Schedule 组件

Schedule是面向“快速搭建”的聚合组件(源码见 Schedule.tsx),内部用useUncontrolled同时管理dateview两个状态:既可以传入date/onDateChangeview/onViewChange做受控使用,也可以只传defaultDate(默认今天)与defaultView(默认week)做非受控使用。

import { Schedule } from '@mantine/schedule'; function CalendarPage() { return ( <Schedule defaultDate="2024-01-15" defaultView="week" events={events} withAgenda layout="responsive" /> ); }

核心 props 一览(详见 Schedule.tsx):

Prop默认值说明
view/defaultView'week'视图级别:dayweekmonthyear
date/defaultDate今天当前展示日期(受控/非受控)
events跨所有视图展示的事件数组
layout'default''responsive'时小屏自动切换到YearView/MobileMonthView
mode'default''static'时禁用全部事件交互(拖拽、缩放、点击)
withEventsDragAndDropfalse启用事件拖拽
withEventResizefalse启用事件边缘缩放
withDragSlotSelectfalse启用拖拽框选时间段
withInteractiveBackgroundEventsfalse背景事件可点击
recurrenceExpansionLimit2000每条重复系列最多展开的实例数
withAgendafalse在 Day/Week/Month 视图头部显示 Agenda 按钮
localeradiuslabels语言、圆角、文案覆盖

视图专属配置通过dayViewPropsweekViewPropsmonthViewPropsyearViewPropsmobileMonthViewProps透传。Schedulemode === 'static'时会把withEventsDragAndDropwithEventResizewithInteractiveBackgroundEvents强制置为false(见 Schedule.tsx),保证只读展示场景下不会有任何交互残留。

视图组件逐个击破

DayView:单日时间轴

DayView是“日网格”视图(DayView.tsx),其时间槽布局由getDayTimeIntervals生成。关键 props 与默认值:

  • startTime/endTimeHH:mm:ss):时间轴范围,默认00:00:0023:59:59
  • intervalMinutes(默认15):每个时间槽的分钟数,必须能整除一小时(如 15、30)或为整小时数(如 120、240);
  • withSubHourGridLines(默认true):小于 1 小时的间隔是否显示细分网格线;
  • withAllDaySlot(默认true):显示顶部全天槽;
  • slotLabelFormat(默认HH:mm)与headerFormat(默认MMMM D, YYYY):时间标签与头部日期格式,可传 dayjs 格式字符串或回调函数;
  • slotHeight(默认64px)与allDaySlotHeight(默认44px):行高,通过 CSS 变量--day-view-slot-height--day-view-all-day-slot-height注入;
  • highlightBusinessHours(默认false)与businessHours(默认['09:00:00', '17:00:00']):高亮工作时间;
  • withCurrentTimeIndicator(默认仅当天显示)与withCurrentTimeBubble(默认true):当前时间指示线及其气泡;
  • getCurrentTime:自定义“当前时间”获取函数,可用于时区修正;
  • startScrollTime:初始渲染时滚动到指定时刻;
  • getTimeSlotProps:为每个时间槽注入额外 props,返回的事件处理器会与内部处理器组合而非覆盖。

交互回调:onTimeSlotClick(携带slotStart/slotEnd与原生事件)、onAllDaySlotClickonEventClickonEventDragStart/onEventDrop/onEventDragEndonEventResizeonSlotDragEndonExternalEventDrop

事件定位算法位于 get-day-positioned-events.ts,对重叠事件进行分组布局,为每个事件计算百分比topheightwidthoffset(见DayEventPositionData),从而在时间轴上实现多列并排而不互相遮挡。全天事件最多展示 2 条,超出部分通过MoreEvents弹出“+N more”列表(由getVisibleEvents计算)。

WeekView:周网格

WeekView在 DayView 能力基础上增加周维度(WeekView.tsx)。差异点:

  • intervalMinutes默认60(周视图单格更大);
  • firstDayOfWeek(默认1,周一)与weekendDays(默认由DatesProvider决定):周起始日与周末标记,也可从DatesProvider继承;
  • withWeekendDays(默认true):隐藏周末列;
  • withWeekNumber(默认true):左上角显示周数;
  • weekdayFormat(默认ddd)与weekLabelFormat(默认MMM DD);
  • highlightToday(默认false):高亮今天所在列;
  • forceCurrentTimeIndicator(默认false):跨周浏览时也显示当前时间线;
  • businessHours支持按天配置:传入以星期为键的记录(0为周日),将某天设为null表示该天完全不在工作时间内;
  • renderWeekLabel:完全自定义头部周标签的渲染。

布局计算位于 get-week-view-events 目录:assign-event-rows负责全天事件的纵向堆叠,calculate-regular-event-overlaps负责定时事件的重叠分列,calculate-all-day-event-width/calculate-all-day-event-offset处理跨天全天事件的宽度与偏移,calculate-event-daysget-hanging-status计算跨周事件的“悬挂”状态(hangingstart/end/both/none)。点击星期头部会通过onViewChange('day')联动切换到日视图。

MonthView:月历网格

MonthView把事件按“周行 + 天列”布局(目录见 MonthView)。布局管线由多个带独立单测的小工具组成:

  • get-weeks-in-range:计算当月覆盖的周;
  • calculate-event-position-in-weekfind-available-row:为事件分配行号(row)与起始偏移(startOffset);
  • get-renderable-month-event-segments:处理跨周事件的分段渲染;
  • get-visible-columns:计算可展示的列数。

关键 props 包括firstDayOfWeekweekendDayswithWeekendDaysweekdayFormatgetDayProps(细粒度定制每天单元格)等,同时支持onDayClick与事件点击回调。月视图顶部同样可通过withAgenda展开议程列表。

YearView:年视图

YearView将一年拆分为 12 个月的小网格(YearView.tsx),支持onMonthClick点击月份后切换到月视图;在Schedule中该跳转由handleMonthClick统一处理(先更新日期再切换视图,见 Schedule.tsx)。

资源视图:按资源分列的排期

资源视图适合会议室、医生排班、工位等“资源 × 时间”场景,由ResourcesDayViewResourcesWeekViewResourcesMonthViewResourcesSchedule(资源版一站式入口)提供。资源数据模型见 types.ts:

interface ScheduleResourceData { id: string | number; // 资源唯一标识 label: React.ReactNode; // 资源显示名 color?: MantineColor; // 资源颜色 payload?: Record<PropertyKey, any>; // 自定义数据 } interface ScheduleResourceGroup { label: React.ReactNode; resourceIds: (string | number)[]; // 组内资源 }

事件通过resourceId关联到具体资源列。重叠事件的分组计算在 get-overlap-clusters 与get-resources-week-view-events中实现;分组以类似 rowspan 的方式跨列合并显示。ResourcesSchedule在顶部提供资源切换能力,适合“同一天内快速切换资源”的运营型界面。

AgendaView 与 MobileMonthView

  • AgendaView:把一段时间内的事件渲染为扁平列表(get-agenda-view-events负责收集与排序),可作为 Day/Week/Month 视图的补充信息流,通过withAgenda开启;
  • MobileMonthView:为触屏优化的精简月视图,layout="responsive"时在窄屏替代 Day/Week/Month 视图(见 Schedule.tsx)。

交互能力:拖拽、缩放与框选

Schedule系列的交互由hooks/目录下的专用 hooks 驱动(hooks/index.ts):

  • use-drag-drop-handlers:事件拖拽主逻辑,计算落点(calculateDropTarget)、维护DragContext(被拖事件、预览、落点高亮),并处理拖拽中自动滚动(配合use-auto-scroll-on-drag);
  • use-event-resize:事件边缘缩放,按intervalMinuteseventResizeInterval对齐步长;
  • use-slot-drag-select:按住拖拽框选连续时间段,结束时通过onSlotDragEnd(rangeStart, rangeEnd)回调;
  • use-drag-state:拖拽状态机(use-drag-state.test.ts覆盖了状态迁移)。

启用拖拽移动

<Schedule events={events} withEventsDragAndDrop canDragEvent={(event) => event.payload?.editable !== false} onEventDrop={({ eventId, newStart, newEnd, event }) => { // 更新业务数据源,例如调用 API 保存新时间 console.log(eventId, newStart, newEnd, event); }} />

拖放时间由calculateDropTime(utils/calculate-drop-time)计算:按时间槽或eventDragInterval对齐,并保留鼠标在事件内的偏移量,保证拖拽手感。拖拽期间会渲染dragPreview预览块(DayView 与 WeekView 分别有dayViewDragPreviewweekViewDragPreview样式名)。

启用边缘缩放

<Schedule events={events} withEventResize onEventResize={({ eventId, newStart, newEnd, event }) => { // 保存新的开始/结束时间 }} />

缩放同样按时间步长吸附,DayView/WeekView 中受eventResizeInterval控制(默认取intervalMinutes)。resize 过程中事件本体即时更新(getResizePosition),松手后触发onEventResize

外部元素拖入

通过onExternalEventDrop(dataTransfer, dropDateTime)可以把日程应用之外的元素(如任务列表)直接拖入时间轴,dataTransfer中可携带自定义数据;与内部事件拖拽共用onDragOver高亮与自动滚动逻辑。

框选时间段

<Schedule withDragSlotSelect onSlotDragEnd={(rangeStart, rangeEnd) => { // 例如弹出“新建事件”表单并预填时间 setNewEvent({ start: rangeStart, end: rangeEnd }); }} />

框选在 DayView 与 WeekView 中可用,选中的槽位带drag-selectedmod 高亮。

mode="static" 只读模式

mode设为'static'会同时关闭事件拖拽、缩放、点击、时间槽点击与键盘导航(各槽位tabIndex变为 -1),适合“只读预览/分享页”场景;背景事件交互也会被强制关闭。

背景事件与自定义渲染

背景事件

设置display: 'background'的事件会作为整块半透明色块渲染在普通事件之后,常用来表示工作时间、假期、维护窗口等。默认不可交互;Day/Week/Month 视图可通过withInteractiveBackgroundEvents开启点击并触发onEventClick(注意YearView与响应式布局下的MobileMonthView不渲染背景事件,见 Schedule.tsx 的注释说明)。

自定义事件渲染

两个层级:

// 1. 只改事件内容 const renderEventBody = (event: ScheduleEventData) => ( <div> <strong>{event.title}</strong> <span>{event.payload?.location}</span> </div> ); // 2. 完全接管事件根元素渲染 const renderEvent: RenderEvent = (event, props) => ( <button {...props}><Schedule events={events} labels={{ today: '今天', week: '周', month: '月', allDay: '全天', moreLabel: (n) => `还有 ${n} 个`, noEvents: '暂无事件', }} />

日期与星期名的本地化则走@mantine/datesDatesProviderlocale字段),各视图的localeprop 可单独覆盖;Schedulelocale也会透传给内部视图。

无障碍与键盘导航

源码在多处体现了无障碍设计:

  • 时间槽、星期头、全天槽均为可聚焦的UnstyledButton,带aria-label(如Time slot 09:00:00 - 10:00:00),并提供previousControlProps/nextControlProps/todayControlProps/viewSelectProps透传 ARIA 属性;
  • DayView 时间槽支持ArrowUp/ArrowDown在槽位间移动焦点(DayView.tsx);
  • WeekView 提供完整的焦点网格导航(handleWeekViewKeyDown),可在星期列、全天槽、时间槽之间用方向键穿梭(WeekView.tsx);
  • mode="static"时所有交互控件移出 Tab 顺序。

样式定制:Styles API 与 CSS 变量

所有视图组件都遵循 Mantine 的 Styles API 约定:factory+StylesApiProps+classNames/styles/vars/unstyled。每个视图导出StylesNames联合类型,例如WeekViewStylesNames覆盖weekViewweekViewHeaderweekViewDayweekViewAllDaySlotsweekViewSlotLabelweekViewBackgroundEventweekViewDragPreview等全部命名节点(WeekView.tsx)。

尺寸与圆角通过 CSS 变量注入(varsResolver):如 DayView 的--day-view-radius--day-view-slot-height--day-view-all-day-slot-height,WeekView 的--week-view-radius--week-view-slot-height--week-view-all-day-slots-height,ScheduleEvent 的--event-bg/--event-color/--event-radius。示例:

<WeekView date="2024-01-15" events={events} slotHeight={72} allDaySlotHeight={56} radius="md" classNames={{ weekViewDay: classes.myDay }} />

包内每个组件都带.module.css与 Storybook story(.story.tsx),可作为样式覆盖的参照实现。

测试与可靠性

该包对布局算法与交互状态做了较充分的测试覆盖,可作为实现正确性的参照:

  • 布局算法单测:get-week-positioned-events.test.tsget-day-positioned-events.test.tsget-month-view-events下的calculate-event-position-in-week.test.tsfind-available-row.test.tsget-weeks-in-range.test.ts,以及get-overlap-clusters.test.ts(资源视图重叠分组)等;
  • 交互 hook 测试:use-drag-drop-handlers.test.tsuse-drag-state.test.tsuse-event-resize.test.tsuse-horizontal-event-resize.test.ts
  • 组件测试:每个视图组件均有*.test.tsx(如 Schedule.test.tsx)。

在自行扩展布局算法或交互逻辑时,这些测试文件是很好的行为契约参考。

结语

@mantine/schedule以“单一事件模型 + 多视图聚合”的方式,把日/周/月/年视图、资源排期、议程列表、重复事件、拖拽/缩放/框选等排期系统的高频能力收敛进一个包中。无论是直接用Schedule快速搭建,还是用DayView/WeekView等独立组件定制专属界面,其 源码目录 都提供了清晰的分层:视图组件负责组合与交互,utils/负责纯布局计算(可独立单测),hooks/负责拖拽等交互状态。结合 Mantine 的 Styles API 与DatesProvider国际化体系,它足以支撑从内部工具到对外产品的各类日历场景。

【免费下载链接】mantineA fully featured React components library项目地址: https://gitcode.com/GitHub_Trending/ma/mantine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询