☰
TDesign Uniapp Calendar 日历组件全解析:API 配置、事件体系与源码实现原理
2026/10/15 15:03:26 网站建设 项目流程
  • 前端
  • UI组件
  • 小程序
  • 设计系统
  • 移动开发

【免费下载链接】tdesign-miniprogram

Wechat MiniProgram and Uniapp UI components lib for TDesign

项目地址:https://gitcode.com/gh_mirrors/tde/tdesign-miniprogram
点击查看免费下载

本文以 TDesign 开源仓库中packages/uniapp-components/calendar的官方文档与源码为基准,系统讲解 TDesign Uniapp 日历组件(t-calendar)的完整 API、事件与插槽用法、CSS 变量定制方案,并结合组件源码与官方示例,深入剖析单选/多选/区间选择、月份翻页、日期格式化、国际化等能力的底层实现原理。读完本文,你将能够熟练地在 UniApp 项目中集成并定制该日历组件,并能读懂其选择逻辑与渲染流程。

组件定位与引入方式

Calendar 日历组件是 TDesign Uniapp 组件库(packages/uniapp-components/calendar)中用于"按照日历形式展示数据或选择日期的容器"。它支持单选、多选、区间选择三种选择类型,默认以底部弹出层(Popup)形式呈现,也可平铺在页面中直接使用。

推荐:easycom 模式引入

官方文档推荐使用 easycom 模式引入组件:在 UniApp 项目中配置 easycom 规则后,无需手动import,即可直接在模板中使用<t-calendar />标签(详见项目内的快速开始说明,见 README.md)。

手动引入

如需手动引入,写法如下(与官方示例_example/base/index.vue中的引入方式一致):

import TCalendar from '@tdesign/uniapp/calendar/calendar.vue';

并在页面components中注册后使用:

<template> <view> <t-calendar :visible="visible" @update:visible="(e) => (visible = e)" @confirm="handleConfirm" /> </view> </template>

注意:visible与update:visible是组件弹层开合的关键双向绑定入口(源码中通过emits: ['update:visible']声明),在use-popup为true时必须配合使用。

Props 属性全解

组件属性定义位于 props.ts,完整 TS 类型见 type.ts。下表为完整属性清单(融合官方中英文文档):

| 名称 | 类型 | 默认值 | 说明 | | -- | -- | -- | -- | | custom-style | Object | - | 自定义样式 | | allow-same-day | Boolean | false | 是否允许区间选择日历的起止时间相同,仅当type='range'时有效 | | auto-close | Boolean | true | 自动关闭;在点击关闭按钮、确认按钮、遮罩层时自动关闭,不需要手动设置 visible | | confirm-btn | String / Object | '' | 确认按钮。值为 null 则不显示确认按钮;字符串表示自定义按钮文本;Object 表示透传 Button 组件属性 | | first-day-of-week | Number | 0 | 第一天从星期几开始,默认 0 = 周日 | | format | Function | - | 用于格式化日期的函数 | | locale-text | Object | - | 国际化文案 | | max-date | Number | - | 最大可选的日期,不传则默认半年后 | | min-date | Number | - | 最小可选的日期,不传则默认今天 | | popup-props | Object | {} | 透传 Popup 组件全部属性 | | readonly | Boolean | - | 是否只读,只读状态下不能选择日期 | | switch-mode | String | none | 切换模式,可选项:none / month / year-month | | title | String | - | 标题,不传默认为"请选择日期" | | type | String | single | 选择类型,可选项:single / multiple / range | | use-popup | Boolean | true | 是否使用弹出层包裹日历 | | using-custom-navbar | Boolean | false | 是否使用了自定义导航栏 | | value | Number / Array | - | 当前选择的日期,支持v-model:value语法糖;type 为 multiple / range 时传入数组 | | default-value | Number / Array | - | 非受控属性,语义同 value | | visible | Boolean | false | 是否显示日历,use-popup为 true 时有效 |

关键属性详解与源码印证

(1)type选择类型与value数据格式

  • single(单选):value为时间戳number;
  • multiple(多选):value为时间戳数组number[];
  • range(区间):value为包含起止两个时间戳的数组。

value未传时,组件会取minDate或今天作为默认值,优先级为minDate > today。源码calendar.vue的initialValue()方法实现了该逻辑:

initialValue() { const { dataValue: value, type, minDate } = this; if (!value) { const today = new Date(); const now = minDate || new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime(); const initialValue = type === 'single' ? now : [now]; if (type === 'range') { initialValue[1] = now + 24 * 3600 * 1000; // 第二天 } this.dataValue = initialValue; } }

可见 range 模式默认区间为"今天 + 第二天"(跨一天)。

(2)受控与非受控

value为受控属性,支持v-model:value;default-value为非受控属性。源码中通过uniComponent的controlledProps声明value受confirm与change两个事件控制,因此不显示确认按钮时选择完即通过change事件同步值。

(3)min-date/max-date/allow-same-day

  • min-date不传默认今天,max-date不传默认半年后(核心逻辑类common/shared/calendar/index.js构造函数中getDate()与getDate(6)分别取今天与六个月后的日期);
  • allow-same-day仅对type='range'生效:开启后允许同一天作为区间起止日,此时该日期格子的类型为'start-end'。

(4)confirm-btn确认按钮

  • 传null:不显示确认按钮,选择完成立即触发change并关闭弹层(single 与区间选择完成时);
  • 传字符串:自定义按钮文本,如confirm-btn="确定";
  • 传对象:透传 Button 组件属性(theme、variant、disabled、loading等),并可通过confirm-btn="slot"配合#confirm-btn插槽完全自定义。

源码calendar.vue中对应处理:

confirmBtn: { handler(v) { if (typeof v === 'string') { this.innerConfirmBtn = v === 'slot' ? 'slot' : { content: v }; } else if (typeof v === 'object') { this.innerConfirmBtn = v; } }, immediate: true, },

(5)format格式化函数

format接收一个TDate对象并返回新的TDate,可用于为每个日期追加描述文本、样式类名等。类型定义如下(见 type.ts):

type CalendarFormatType = (day: TDate) => TDate; type TDateType = 'selected' | 'disabled' | 'start' | 'start-end' | 'centre' | 'end' | ''; interface TDate { date: Date; day: number; type: TDateType; className?: string; prefix?: string; suffix?: string; }

其中prefix/suffix会在日期数字上下方渲染为单行/双行描述,className可挂自定义样式类,type标记选中态(区间模式下还有start/centre/end/start-end)。核心逻辑类common/shared/calendar/index.js在生成每月日期时会调用:months.push(format ? format(dateObj) : dateObj)。

(6)locale-text国际化文案

类型为:

interface CalendarLocaleText { title?: string; weekdays?: string[]; monthTitle?: string; months?: string[]; confirm?: string; }

未传入时,组件通过using-configmixin 从全局配置获取默认文案(中文默认值见 locale/zh_CN.ts,其中monthTitle支持{year}、{month}占位符,如'{year} 年 {month}')。

(7)switch-mode翻页模式

  • none:平铺展示min-date到max-date之间所有月份,可上下滚动;
  • month:显示头部切换栏,支持按月前后切换;
  • year-month:在month基础上额外支持按年前后切换(头部出现双箭头图标)。

(8)popup-props与use-popup/visible

use-popup=false时组件直接平铺在页面内(visible属性不再生效);默认use-popup=true时由 Popup 包裹并从底部弹出,popup-props可透传 Popup 的全部属性(如z-index、overlay-props、using-custom-navbar等),源码calendar.vue模板中通过:z-index="popupProps && popupProps.zIndex"、:overlay-props="..."等逐项透传。

Events 事件

| 名称 | 参数 | 说明 | | -- | -- | -- | | select |(context: { value: number \| number[] })| 点击日期时触发 | | change |(context: { value: number \| number[] })| 不显示 confirm-btn 时,完成选择时触发(暂不支持 type = multiple) | | confirm |(context: { value: number \| number[] })| 点击确认按钮时触发 | | close |(context: { trigger: CalendarTrigger })| 弹层关闭时触发 | | panel-change |(context: { year: number, month: number })| 切换月或年时触发(switch-mode 不为 none 时有效) | | scroll |(context: {scrollLeft, scrollTop, scrollHeight, scrollWidth, deltaX, deltaY})| 月份区域滚动时触发 |

其中close的trigger类型为:

type CalendarTrigger = 'close-btn' | 'confirm-btn' | 'overlay' | 'auto-close';

即分别对应:点击右上角关闭图标、点击确认按钮、点击遮罩层、自动关闭四种关闭来源。源码calendar.vue的close(trigger)方法统一在autoClose为 true 时先更新visible,再派发close事件。

事件触发时序从源码handleSelect可以清晰看出:点击日期 → 判定 disabled/readonly 拦截 → 调用核心类的select()更新值 → 重新计算月份 →(无确认按钮时)关闭弹层并触发change→ 最终触发select事件。confirm事件则在onTplButtonTap(点击确认按钮)时触发。

Slots 插槽

| 名称 | 说明 | | -- | -- | | title | 标题区域,配合title属性或locale-text.title使用 | | confirm-btn | 确认按钮区域,需配合confirm-btn="slot"使用,可完全自定义按钮内容 |

插槽在源码 template.vue 中通过<slot name="title" />、<slot name="confirm-btn" />透出;其中确认按钮插槽仅在use-popup为 true 且innerConfirmBtn为'slot'时渲染。

CSS Variables 样式定制

组件提供了以下 CSS 变量,可通过覆盖变量值实现主题定制(样式定义见 calendar.less,全部变量均以var(--td-calendar-xxx, 默认值)形式消费):

| 名称 | 默认值 | 说明 | | -- | -- | -- | | --td-calendar-active-color | @brand-color | 选中态背景色 | | --td-calendar-bg-color | @bg-color-container | 组件背景色 | | --td-calendar-days-color | @text-color-secondary | 星期表头文字色 | | --td-calendar-item-centre-color | @brand-color-light | 区间中间日期背景色 | | --td-calendar-item-disabled-color | @text-color-disabled | 禁用日期文字色 | | --td-calendar-item-suffix-color | @text-color-placeholder | 日期描述(suffix)文字色 | | --td-calendar-radius | 24rpx | 弹层圆角 | | --td-calendar-selected-border-radius | @radius-default | 选中日期圆角 | | --td-calendar-selected-color | @text-color-anti | 选中态文字色 | | --td-calendar-switch-mode-icon-color | @text-color-secondary | 翻页图标颜色 | | --td-calendar-switch-mode-icon-disabled-color | @text-color-disabled | 翻页图标禁用色 | | --td-calendar-title-color | @text-color-primary | 标题文字色 | | --td-calendar-title-font | @font-title-large | 标题字体 |

例如在页面样式中覆盖:

page { --td-calendar-active-color: #0052d9; --td-calendar-radius: 12rpx; }

源码级实现原理

核心日历逻辑类 TCalendar

组件底层核心是一个与 UI 无关的日历逻辑类,位于 common/shared/calendar/index.js。它负责三件事:

  1. 生成月份数据getMonths():从minDate遍历到maxDate,逐月生成日期格子,并通过内部calcType为每个格子计算类型标记(selected/disabled/start/centre/end/start-end/''),最终返回含year、month、months、weekdayOfFirstDay的月份数组;
  2. 选择逻辑select():按type处理点击:
    • range模式:首次点击记为起点;再次点击若晚于起点则组成[start, end],否则重置为新的起点;
    • multiple模式:已选则移除(取消选择),未选则追加;
    • single模式:直接替换当前值。
  3. 默认日期getTrimValue()/getDays():规范化值为 Date 对象数组,并按first-day-of-week重排星期表头顺序(weekdays[i % 7]循环取值)。

日期辅助工具(getDateRect、isSameDate、getMonthDateRect等)位于 common/shared/date.js,用于比较日期是否同年同月同日、计算每月天数与首日星期等。

组件壳与渲染流程

calendar.vue 是组件的"壳":它维护months、currentMonth、days、actionButtons(翻页按钮禁用态)、innerConfirmBtn等内部状态,监听value/visible/type/format等属性的变化并同步到核心逻辑类。当use-popup为 true 时,将日历模板(template.vue)包裹进t-popup;否则直接渲染。

翻页头部由 calendar-header.vue 实现:year-month模式渲染"上一年的双箭头 + 上一月单箭头"与"下一月单箭头 + 下一年双箭头"共四个操作图标;month模式只渲染单箭头。各按钮是否禁用由updateActionButton()依据min-date/max-date边界计算(例如"上月"时间戳早于minDate所在月则禁用上月按钮)。

区间选择的高亮渲染

区间高亮在样式层完成(见 calendar.less):

  • 起点(--start)只保留左侧圆角,终点(--end)只保留右侧圆角;
  • 起点与终点相邻时,中间用 8rpx 宽的色块桥接(&--start + &--end::before);
  • 中间日期(--centre)使用@brand-color-light浅色铺底,并在左右两侧各补 8rpx 色块,使整段区间视觉连续。

日期可访问性

computed.js 中的getDateLabel()为每个日期格子生成无障碍描述(如"已选中, 2月18日, 星期五, 初一, ¥100"),配合模板中的aria-role="button"、aria-disabled与aria-label属性,保证弱视群体可正常使用(区间模式下还会标注"开始日期/结束日期")。

实战示例:官方用例全解读

官方示例位于 calendar/_example,覆盖了组件全部典型场景,可直接作为开发参考。

单选日历(base)

<t-calendar :visible="visible" @update:visible="(e) => (visible = e)" @confirm="handleConfirm" />
methods: { handleConfirm(e) { const { value } = e; this.value = value; // 时间戳 }, }

完整代码见 base/index.vue。

多选日历(multiple)

<t-calendar :visible="visible" :value="value" type="multiple" @confirm="handleConfirm" />

value为时间戳数组,handleConfirm中直接回填。

区间选择日历(range)

<t-calendar :visible="visible" :value="value" :min-date="minDate" :max-date="maxDate" type="range" @confirm="handleConfirm" />
data() { return { value: [new Date(2024, 11, 5).getTime(), new Date(2024, 11, 10).getTime()], minDate: new Date(2024, 10, 1).getTime(), maxDate: new Date(2024, 12, 1).getTime(), }; },

完整代码见 range/index.vue。

带单行/双行描述的日历(custom-text)

利用format函数为日期附加suffix(价格)与prefix(节日名、农历),并给特定日期挂className实现自定义样式(如节日标红):

format(day) { day.suffix = '¥60'; if (month === 2) { const map = { 1: '初一', 14: '情人节', 15: '元宵节' }; if (curDate in map) { day.prefix = map[curDate]; day.suffix = '¥100'; day.className = 'is-holiday'; } } return day; }
.is-holiday:not(.t-calendar__dates-item--selected) { color: #e34d59 !important; }

完整代码见 custom-text/index.vue。

带翻页功能的日历(switch-mode)

<t-calendar :visible="visible" :value="value" switch-mode="year-month" :min-date="minDate" :max-date="maxDate" @panel-change="handlePanelChange" />

panel-change回调中可拿到切换后的year与month(月份从 1 开始计数)。完整代码见 switch-mode/index.vue。

含不可选日期的日历(custom-range)

通过收窄min-date/max-date制造禁用区间,format中配合day.type === 'disabled'可进一步做节日禁用等高级限制。完整代码见 custom-range/index.vue。

国际化(local-text)

通过locale-text传入英文文案:

localeText: { title: 'Select Date', weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], monthTitle: '{month} {year}', confirm: 'Confirm', months: ['January', ..., 'December'], },

完整代码见 local-text/index.vue。

不使用 Popup(without-popup)

<t-calendar title="日历标题" :use-popup="false" type="multiple" @select="handleSelect" />

组件直接平铺渲染,此时通过select事件实时接收选择结果(无需visible控制)。完整代码见 without-popup/index.vue。

总结

TDesign Uniapp 的 Calendar 组件在 API 设计上兼顾了"开箱即用"(默认 Popup 弹层、默认中文文案、auto-close自动关闭)与"深度定制"(format函数、confirm-btn透传、popup-props透传、CSS 变量、双插槽),其底层"UI 无关的日历逻辑类 + 组件壳 + 模板"的分层结构清晰易维护。无论你是需要快速接入一个标准单选日历,还是要实现复杂的区间价格日历、节日标记日历或多语言日历,本文给出的配置说明、源码路径与官方示例都可以直接作为开发依据。

  • 前端
  • UI组件
  • 小程序
  • 设计系统
  • 移动开发

【免费下载链接】tdesign-miniprogram

Wechat MiniProgram and Uniapp UI components lib for TDesign

项目地址:https://gitcode.com/gh_mirrors/tde/tdesign-miniprogram
点击查看免费下载

相关推荐

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

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

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

立即咨询