- 前端
- UI组件
- 小程序
- 设计系统
- 移动开发
【免费下载链接】tdesign-miniprogram
Wechat MiniProgram and Uniapp UI components lib for TDesign
本文以 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。它负责三件事:
- 生成月份数据
getMonths():从minDate遍历到maxDate,逐月生成日期格子,并通过内部calcType为每个格子计算类型标记(selected/disabled/start/centre/end/start-end/''),最终返回含year、month、months、weekdayOfFirstDay的月份数组; - 选择逻辑
select():按type处理点击:range模式:首次点击记为起点;再次点击若晚于起点则组成[start, end],否则重置为新的起点;multiple模式:已选则移除(取消选择),未选则追加;single模式:直接替换当前值。
- 默认日期
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
相关推荐
ant-design-vue Calendar 日历组件完全指南:API、事件与源码级实现解析
ant design vue Calendar 日历组件完全指南:API、事件与源码级实现解析 ant design vue 的 Calendar 是“按照日历
前端UI组件设计系统TDesign 小程序 ImageViewer 图片预览组件完全指南:属性配置、事件体系与源码原理
TDesign 小程序 ImageViewer 图片预览组件完全指南:属性配置、事件体系与源码原理 ImageViewer 是 TDesign 小程序组件库(t
前端UI组件小程序设计系统移动开发ant-design-vue Calendar 日历组件完全指南:API 详解、事件体系与自定义渲染实战
ant design vue Calendar 日历组件完全指南:API 详解、事件体系与自定义渲染实战 Calendar 是 ant design vue 中
前端UI组件设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考