Skip to content

n-calendar 日历 ​

日历面板支持单选、多选、日期范围,以及单月、滑动月份、连续月份三种视图。所有选择模式使用 YYYY-MM-DD 字符串数组,单选最多一个日期,范围使用两个端点。通过 :model-value 与 @update:model-value 双向连接。

功能与实现说明 ​

参考做法 本组件做法 选择理由
单选字符串与多选数组分别绑定;部分面板直接修改内部选中值 三种选择模式统一数组,点击只回传建议值,显示以外部值为准 切换模式无需更换绑定通道,异步拦截可由业务处理
年、月分别指定;完整日期指定展示月份 year / month 分别定位展示年月,monthSwitch 回传浏览位置 定位只需年月,日期选择由 v-model 独立表达
月份网格、按年滑动月份、连续滚动月份 单月 / 横向或纵向 swiper / scroll 共用一个月份网格和日期运算模块 保留视图能力,避免日期规则因渲染方式不同而漂移
App 使用绘图上下文,其他平台使用节点 全平台原生 view/text 日期网格,状态与内容结构通过 key 重建 支持主题、文字说明与角标,不依赖坐标命中或平台绘图分支
上下月、上下年箭头;点标题打开年月选择 单箭头切月、双箭头切年;switchMode 控制入口,标题展开可翻页的年月选择 提供滑动的按钮替代方式,同时支持远距离年月跳转
面板或弹层内容区提供水平留白,星期与日期使用相同列宽 星期行与三种月份视图共用两侧留白的容器,按此容器实际宽度计算日期格 选中背景与范围条带不会贴住面板边缘,圆形与星期列保持对齐
起止日期、禁用日期数组、日期配置禁用、格式化函数 所有禁用来源合并;范围端点与中间日期均检查 禁用日期不会经范围选择间接进入结果
多选数量限制、范围天数限制、同日范围、端点或全部日期结果 maxCount / maxRange / allowSameDay;allRangeDates 只控制确认结果,绑定始终保留两个端点 范围状态契约稳定,天数含首尾,使用整数历法避免时区与夏令时误差
日期上文、下文、红点或文字角标、配色与圆角扩展 dateConfig + formatter 合并为日期数据,说明、颜色、角标均可按日期配置 异步获取说明或角标后直接更新数组,无需组件代管网络请求
今日提示、农历与月份水印、选中形状、范围两端文字 提供对应开关、文字与主题令牌;农历运算使用内置历法数据 零运行时依赖,语言切换后重新取文案
底部弹出日历、触发器、确认取消、前后内容插槽 与 n-popup 组合,弹出前复制选择草稿,确认回写、取消丢弃;header/prepend/append/footer 插槽定制面板 遮罩、过渡、安全区和显隐沿用通用弹层;日历职责集中在日期选择
确认按钮、禁用确认文字、范围超限提示 showConfirm / buttonDisabled / confirmText / confirmDisabledText;超限保留原选择并触发 overLimit,按 showRangePrompt 显示提示 不产生无法确认的隐式选择状态,业务可自行提供反馈

年月定位 ​

year / month 是数字,分别指定展示年份与月份,不会选中某一天。默认均为 0,自动分量取首个选中日期;无选择时取本地今天。可以只指定其中一个分量。

html
<n-calendar v-model="dates" :year="2026" :month="10" />

当 dates 为 ["2026-10-12"] 时,上例展示 2026 年 10 月并选中 12 日。要选中 7 日,应将 dates 改为 ["2026-10-07"]。

首次挂载及外部年月属性变化时执行定位;用户翻月后保留浏览位置。自动分量随外部非空选择变化重新定位,清空选择保留浏览位置,组件自己的选择回写不会打断浏览。合法年月超出 minDate / maxDate 时定位到最近的边界月份。用户浏览月份通过 monthSwitch 回传 { year, month, date },date 为月首;外部年月更新不反向触发该事件。

日期配置与格式化 ​

dateConfig 按完整日期匹配,未提供的字段使用下表默认值。选中颜色优先于按日期指定的颜色。

字段 默认值 说明
date 必填 有效的 YYYY-MM-DD
topText / bottomText 空串 日期数字上下的说明;下文未提供时按开关显示今日或农历
color / bottomColor 空串 普通文字色 / 下文文字色;留空跟随主题
background / radius 空串 / -1 未选中格的背景与圆角 px;-1 使用默认圆角
disabled false 禁用此日期,范围也不能跨过它
style 空串 日期格行内样式
badge / isDot false / true 显示角标 / 使用圆点形态
badgeText 空串 isDot 为 false 时显示的角标文字
badgeColor / badgeTextColor 空串 角标背景 / 前景色;留空使用主题功能色 / 填充前景色
badgeSize / badgeTextSize 14 / 10 角标高度与文字字号 px;文字字号不超过高度的 0.7 倍
badgeRadius 999 角标圆角 px
badgeOffsetX / badgeOffsetY 4 / 4 角标距所在边缘的偏移 px
badgePosition top-right top / bottom 与 left / center / right 六种组合
badgeStyle 空串 角标容器行内样式

日期数字独立在格子中央定位,说明分别位于顶部与底部;某天缺少说明不会改变数字位置。dateConfig 与 formatter 使用相同的展示字段名。

formatter(day) 接收合并配置后的 NCalendarDay 并返回日期数据,支持修改说明、配色、角标和禁用状态。该函数也参与范围可用性检查,应保持纯函数;date、历法分量和选择标记由组件维护。日期对象始终包含 lunar,超出农历数据边界时为 null。

可直接按日期添加角标、说明与背景:

html
<n-calendar v-model="dates" :date-config="dateConfig" />
ts
const dateConfig = ref<NCalendarDateConfig[]>([
	  { date: "2026-10-08", bottomText: "有号", background: "#dcfce7", color: "#166534", badge: true },
  { date: "2026-10-12", bottomText: "¥128", badge: true, isDot: false, badgeText: "3" }
]);

monthNum 指定可连续浏览的逻辑月份数量,0 表示完整业务区间;节点数量与业务跨度分离。scroll 只生成可见月份及前后一个月,swiper 最多保留五个月,并在滑动动画结束后移动窗口。scroll 采用有限的物理坐标分段,跨段时保留当前月份和月内位置,避免 0001–9999 年的超长占位高度超过平台布局上限。导航与外部 year / month 更新可直接定位业务区间内任意月份。滚动条反映当前物理分段的位置,远距离跳转使用年月选择或 year / month。

年份快捷选择每次只生成 12 个候选;扩大年份边界不会扩大历法数据包,也不会提前生成全部日期对象。无 formatter 时,范围内的禁用日使用有序索引查询;自定义 formatter 的禁用规则需要逐日求值,allRangeDates 必须生成与天数相同的输出数组,业务可通过 maxRange 控制这两项成本。

尺寸字段统一使用有限的 px 数值,NaN 与 Infinity 会通过 error 上报并显示参数错误。日期格和角标的额外样式分别使用 style / badgeStyle 字符串。

dateConfig 按日期建立索引,同一天重复配置时采用第一项。禁用日期与选中日期使用集合查找;说明文字变化只更新内容,说明、角标或隐藏日期的节点结构变化时才重建月份。连续月份导航会重新发出滚动定位命令;关闭年月面板或切换显示开关后定位到当前浏览月份的顶部。

日期背景、内容和命中区域分层,gap 是背景之间的实际间距,默认 4px;范围保持横向连续,日期按钮仍覆盖完整格子。圆形直径由日期格实际宽度与行高决定,窗口变化自动重测;App / Web 同时监听容器尺寸。小程序祖先布局变化、隐藏容器重新显示后可调用 refresh()。

星期与日期网格默认左右各留 16px,通过 --n-calendar-padding-x 或 uni.scss 的 $n-calendar-padding-x 调整。该留白作用于单月、滑动与连续月份视图,与 gap 独立;范围背景在网格内连续,左右仍保留面板间距。运行时修改留白令牌后,在 nextTick 中调用 refresh() 同步圆形背景尺寸。

“今日”按本地日期计算,在下一个本地午夜刷新,应用或页面恢复时重新读取日期;隐藏或卸载时停止定时器。图标按钮保留视觉导航并提供语义名称;Web 支持 Tab 焦点、Enter / Space 激活和禁用状态,日期名称包含说明与选中状态;操作按钮清除 Web / 小程序的默认边框,只在获得焦点时显示焦点边框。selectedHighlight 只控制视觉,选择状态仍用于事件与辅助名称。

通过统一的组件实例方法入口重测:

html
<n-calendar ref="calendarRef" v-model="dates" shape="circle" />
ts
import { ref } from "vue";
const dates = ref<string[]>([]);
const calendarRef = ref<NCalendarComponentPublicInstance | null>(null);
function refreshCalendar() {
  calendarRef.value?.refresh();
}

日期边界 ​

  • 未设置 minDate / maxDate 时使用 1900-01-01 至 2100-12-31,与日期选择器一致;按 12 年分页。可显式设置公历 0001–9999 年内的业务边界。显式边界只限制业务可选区间,边界外的日期与翻页按钮不可操作。
  • 农历支持 1900-01-31 至 2100-12-31;范围外的公历仍可选择,日期数据中的 lunar 为 null,农历说明显示不可用标记。
  • 日期字符串必须是有效的 YYYY-MM-DD,非法参数显示错误说明并通过 error 事件上报,不把非法日期自动滚入下个月。
  • 单选再次点击同一天保持选中;多选再次点击取消。范围先选起点,第二次选择自动排序;已有完整范围后重新开始。
  • 日期、模式与边界动态变更后重新验证外部选择;组件不会擅自改写传入数组。多选上限降低时保留已有选择供取消,超出当前上限时不能确认。

示例页布局 ​

完整日历放在页面主体,随页面自然滚动;n-page footer 只显示打开弹窗的按钮。弹窗与主体使用同一套属性配置,打开时复制已提交日期作为草稿,确认后回写,取消或关闭遮罩时丢弃草稿。

实时代码随试玩状态生成,日期配置、禁用日期与星期数组在 script 中声明,模板只绑定变量。显示与复制使用同一份完整代码,可直接用于 uvue 页面。

平台兼容性 ​

面向 App(Android/iOS/鸿蒙)、Web、微信小程序。具体编译与运行验收记录见交付说明。

记录 · 思考 · 创作