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,自动分量取首个选中日期;无选择时取本地今天。可以只指定其中一个分量。
<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。
可直接按日期添加角标、说明与背景:
<n-calendar v-model="dates" :date-config="dateConfig" />
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 只控制视觉,选择状态仍用于事件与辅助名称。
通过统一的组件实例方法入口重测:
<n-calendar ref="calendarRef" v-model="dates" shape="circle" />
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、微信小程序。具体编译与运行验收记录见交付说明。