Element DatePicker 日期选择器完全指南:从基础用法到源码级原理
2026/9/19 11:20:08 网站建设 项目流程

Element DatePicker 日期选择器完全指南:从基础用法到源码级原理

【免费下载链接】elementA Vue.js 2.0 UI Toolkit for Web项目地址: https://gitcode.com/gh_mirrors/eleme/element

Element(Element UI)是面向 Vue.js 2.0 的桌面端组件库,其日期选择器el-date-picker覆盖了单日、周、月、年、多选日期以及日期区间、月份区间等全部常见场景。本文以 examples/docs/fr-FR/date-picker.md 为核心骨架,结合 packages/date-picker 目录下的真实源码与 test/unit/specs/date-picker.spec.js 测试用例,系统讲解 DatePicker 的每一种用法、全部配置项,并揭示其底层实现原理,帮助你写出可投入生产、可深度定制的日期选择功能。

基础用法:选择单个日期

DatePicker 的基本单元是"日"。使用type="date"即可得到一个标准的日期选择器,用户点击输入框弹出日历面板,选中某一天后关闭面板并回填值。

<template> <div class="block"> <span class="demonstration">Défaut(默认)</span> <el-date-picker v-model="value1" type="date" placeholder="Choississez un jour"> </el-date-picker> </div> <div class="block"> <span class="demonstration">Picker avec raccourcis(带快捷选项)</span> <el-date-picker v-model="value2" type="date" placeholder="Choississez un jour" :picker-options="pickerOptions"> </el-date-picker> </div> </template> <script> export default { data() { return { pickerOptions: { // 禁用今天之后的所有日期 disabledDate(time) { return time.getTime() > Date.now(); }, shortcuts: [{ text: 'Aujourd\'hui(今天)', onClick(picker) { picker.$emit('pick', new Date()); } }, { text: 'Hier(昨天)', onClick(picker) { const date = new Date(); date.setTime(date.getTime() - 3600 * 1000 * 24); picker.$emit('pick', date); } }, { text: 'Il y a une semaine(一周前)', onClick(picker) { const date = new Date(); date.setTime(date.getTime() - 3600 * 1000 * 24 * 7); picker.$emit('pick', date); } }] }, value1: '', value2: '', }; } }; </script>

这个示例包含两个关键的picker-options配置:

  • disabledDate(time):接收一个Date对象作为参数,返回true表示该日期被禁用。time.getTime() > Date.now()禁用了所有未来的日期,常用于"只能选择过去日期"的业务场景(如出生日期、历史账单)。
  • shortcuts:一个{ text, onClick }对象数组,text是快捷项标题,onClick(picker)在点击时触发,通过picker.$emit('pick', date)把选中的值写回组件。3600 * 1000 * 24是一天的毫秒数,乘以 7 即一周。

从源码看,快捷项和禁用逻辑都发生在面板组件中。在 packages/date-picker/src/panel/date.vue 中,shortcuts会渲染成侧边栏按钮(el-picker-panel__shortcut),点击时调用handleShortcutClick执行shortcut.onClick(this),而disabledDate则被透传给date-table控制每个单元格的可点击状态:

// packages/date-picker/src/panel/date.vue handleShortcutClick(shortcut) { if (shortcut.onClick) { shortcut.onClick(this); } },

注意:快捷项回调传入的picker参数实际上是面板组件实例,所以示例中用$emit('pick', ...)与面板通信,这是官方推荐的固定用法。

其他粒度:周、月、年与多选日期

除了按"日"选择,DatePicker 还支持周、月、年以及多日期选择,全部由type属性驱动:

<div class="container"> <div class="block"> <span class="demonstration">Semaine(周)</span> <el-date-picker v-model="value1" type="week" format="Week WW" placeholder="Sélectionnez une semaine"> </el-date-picker> </div> <div class="block"> <span class="demonstration">Mois(月)</span> <el-date-picker v-model="value2" type="month" placeholder="Sélectionnez un mois"> </el-date-picker> </div> </div> <div class="container"> <div class="block"> <span class="demonstration">Année(年)</span> <el-date-picker v-model="value3" type="year" placeholder="Sélectionnez une année"> </el-date-picker> </div> <div class="block"> <span class="demonstration">Dates(多选)</span> <el-date-picker type="dates" v-model="value4" placeholder="Sélectionnez une ou plusieurs dates"> </el-date-picker> </div> </div>

各类型要点:

  • type="week":按周选择,配合format(如"Week WW")自定义周的显示格式,W/WW是周数占位符。
  • type="month":按月选择,面板直接展示 12 个月供点选。
  • type="year":按年选择。
  • type="dates":多选日期,面板保持打开直到点击"确定",返回值是一个日期数组。

从源码看,这些类型最终都会映射到对应的selectionMode,进而决定面板交互行为。packages/date-picker/src/picker.vue 中的selectionMode计算属性做了类型到模式的转换:

selectionMode() { if (this.type === 'week') return 'week'; else if (this.type === 'month') return 'month'; else if (this.type === 'year') return 'year'; else if (this.type === 'dates') return 'dates'; // months / years 同理 return 'day'; }

而多选模式(dates)下,date.vue 的emit方法会把用户选中的日期数组继续$emit('pick', value, true),第二个参数true表示保持面板打开,直到用户点击面板底部的"确定"按钮才提交——这正是多选交互的实现细节:

} else if (this.selectionMode === 'dates') { this.emit(value, true); // 保持面板打开,支持连续多选 }

日期区间:daterange 与 unlink-panels

选择起止日期是报表、预订类场景的高频需求,type="daterange"提供双面板联动选择,type="monthrange"则对应月份区间。

日期区间(daterange)

<template> <div class="block"> <span class="demonstration">Défaut(默认)</span> <el-date-picker v-model="value1" type="daterange" range-separator="à" start-placeholder="Date de début(开始日期)" end-placeholder="Date de fin(结束日期)"> </el-date-picker> </div> <div class="block"> <span class="demonstration">Avec des options(带快捷选项)</span> <el-date-picker v-model="value2" type="daterange" align="right" unlink-panels range-separator="à" start-placeholder="Date de début" end-placeholder="Date de fin" :picker-options="pickerOptions"> </el-date-picker> </div> </template> <script> export default { data() { return { pickerOptions: { shortcuts: [{ text: 'Semaine dernière(上周)', onClick(picker) { const end = new Date(); const start = new Date(); start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); picker.$emit('pick', [start, end]); } }, { text: 'Mois dernier(上个月)', onClick(picker) { const end = new Date(); const start = new Date(); start.setTime(start.getTime() - 3600 * 1000 * 24 * 30); picker.$emit('pick', [start, end]); } }, { text: 'Trois derniers mois(近三个月)', onClick(picker) { const end = new Date(); const start = new Date(); start.setTime(start.getTime() - 3600 * 1000 * 24 * 90); picker.$emit('pick', [start, end]); } }] }, value1: '', value2: '' }; } }; </script>

区间模式的要点:

  • 默认情况下左右两个面板联动:切换左侧面板的月份,右侧面板会自动跟随,保证始终展示相邻的两个月。
  • unlink-panels:加上该属性后两个面板独立切换月份,互不干扰。
  • range-separator:两个输入框之间的分隔文案,这里设置为"à"(法语"至")。
  • start-placeholder/end-placeholder:分别设置开始、结束输入框的占位提示。
  • align:下拉面板的对齐方式,取值为left/center/right
  • 快捷项回调中picker.$emit('pick', [start, end])传出的必须是长度为 2 的数组

关于unlink-panels,packages/date-picker/src/panel/date-range.vue 的实现逻辑是:只有unlinkPanelstrue时,面板头部才会渲染让左右两侧独立翻页的箭头按钮,并且左右面板的翻页与年份/月份箭头是否可点(enableYearArrow/enableMonthArrow)都以此为依据。组件入口 packages/date-picker/src/picker/date-picker.js 则根据type分派面板:

const getPanel = function(type) { if (type === 'daterange' || type === 'datetimerange') { return DateRangePanel; } else if (type === 'monthrange') { return MonthRangePanel; } return DatePanel; };

月份区间(monthrange)

<template> <div class="block"> <span class="demonstration">Défaut(默认)</span> <el-date-picker v-model="value1" type="monthrange" range-separator="à" start-placeholder="Mois de début(开始月份)" end-placeholder="Mois de fin(结束月份)"> </el-date-picker> </div> <div class="block"> <span class="demonstration">Avec options(带快捷选项)</span> <el-date-picker v-model="value2" type="monthrange" align="right" unlink-panels range-separator="à" start-placeholder="Mois de début" end-placeholder="Mois de fin" :picker-options="pickerOptions"> </el-date-picker> </div> </template> <script> export default { data() { return { pickerOptions: { shortcuts: [{ text: 'Ce mois(本月)', onClick(picker) { picker.$emit('pick', [new Date(), new Date()]); } }, { text: 'Cette année(今年)', onClick(picker) { const end = new Date(); const start = new Date(new Date().getFullYear(), 0); picker.$emit('pick', [start, end]); } }, { text: 'Les derniers 6 mois(近六个月)', onClick(picker) { const end = new Date(); const start = new Date(); start.setMonth(start.getMonth() - 6); picker.$emit('pick', [start, end]); } }] }, value1: '', value2: '' }; } }; </script>

monthrangedaterange的行为几乎一致,也支持unlink-panels使左右面板独立切换年份。它的实现位于独立的 packages/date-picker/src/panel/month-range.vue,内部使用month-table渲染月份网格。快捷项示例展示了月份计算技巧:new Date().getFullYear()取当年 1 月、start.setMonth(start.getMonth() - 6)回退 6 个月——注意用setMonth而非毫秒计算,可以正确处理跨年和闰年。

默认展示值:default-value

当用户尚未选择任何日期时,面板默认展示"今天"。如果想默认展示其他日期,使用default-value,其值必须能被new Date()解析(可以是字符串或 Date 对象)。

对于daterangedefault-value设置的是左侧面板的月份,右侧面板会自动跟随展示相邻月份。

<template> <div class="block"> <span class="demonstration">Date(单日)</span> <el-date-picker v-model="value1" type="date" placeholder="Sélectionnez une date" default-value="2010-10-01"> </el-date-picker> </div> <div class="block"> <span class="demonstration">Plage de dates(日期区间)</span> <el-date-picker v-model="value2" type="daterange" align="right" start-placeholder="Date de début" end-placeholder="Date de fin" default-value="2010-10-01"> </el-date-picker> </div> </template> <script> export default { data() { return { value1: '', value2: '' }; } }; </script>

从源码看,面板的getDefaultValue()方法正是"有default-value用它、没有就用当前时刻":

// packages/date-picker/src/panel/date.vue getDefaultValue() { return this.defaultValue ? new Date(this.defaultValue) : new Date(); }

而区间面板(date-range.vue 的calcDefaultValue)在接收到单个default-value时,会自动把右面板初始化为"该日期 +1 天",保证左右相邻展示:

const calcDefaultValue = (defaultValue) => { if (Array.isArray(defaultValue)) { return [new Date(defaultValue[0]), new Date(defaultValue[1])]; } else if (defaultValue) { return [new Date(defaultValue), nextDate(new Date(defaultValue), 1)]; } else { return [new Date(), nextDate(new Date(), 1)]; } };

另外在 packages/date-picker/src/picker.vue 中,defaultValue有专门的watch,意味着它是响应式的——外部动态修改default-value时,面板会同步更新(对应测试用例 "is reactive, works with clear")。

日期格式:format 与 value-format

DatePicker 提供两个格式属性,职责严格分离:

  • format:控制日期在输入框中的显示格式
  • value-format:控制v-model 绑定变量的存储格式。

默认情况下,组件接受并输出一个Date对象。下表是支持的格式占位符(示例以 UTC 时间2017-01-02 03:04:05为准):

格式含义说明示例
yyyy2017
M不补零1
MM补零01
MMM月(缩写)Jan
MMMM月(全称)Janvier
W周数仅用于type="week"format,不补零1
WW周数仅用于type="week"format01
d不补零2
dd补零02
H24 小时制,不补零3
HH24 小时制03
h12 小时制,需与Aa搭配,不补零3
hh12 小时制,需与Aa搭配03
m不补零4
mm补零04
s不补零5
ss补零05
AAM/PM仅用于format,大写AM
aam/pm仅用于format,小写am
timestampJS 时间戳仅用于value-format,存储值为number1483326245000
[MM]转义字符需要原样输出字母时用方括号包裹(如[A] [MM]MM

注意大小写M(月)与m(分)、d(日)等占位符区分大小写,写错会导致显示异常。这是官方文档特别强调的坑。

实际效果示例:

<template> <div class="block"> <span class="demonstration">Émet un objet Date(输出 Date 对象)</span> <div class="demonstration">Value: {{ value1 }}</div> <el-date-picker v-model="value1" type="date" placeholder="Sélectionnez une date" format="yyyy/MM/dd"> </el-date-picker> </div> <div class="block"> <span class="demonstration">Utilise value-format(使用 value-format)</span> <div class="demonstration">Value: {{ value2 }}</div> <el-date-picker v-model="value2" type="date" placeholder="Sélectionnez une date" format="yyyy/MM/dd" value-format="yyyy-MM-dd"> </el-date-picker> </div> <div class="block"> <span class="demonstration">Timestamp(时间戳)</span> <div class="demonstration">Value:{{ value3 }}</div> <el-date-picker v-model="value3" type="date" placeholder="Sélectionnez une date" format="yyyy/MM/dd" value-format="timestamp"> </el-date-picker> </div> </template> <script> export default { data() { return { value1: '', value2: '', value3: '' }; } }; </script>

三个示例分别演示了:

  1. 只设format:输入框显示2017/01/02,但value1仍然是Date对象;
  2. 同时设formatvalue-format:输入框按yyyy/MM/dd显示,value2存储为"2017-01-02"字符串;
  3. value-format="timestamp"value3存储为数字时间戳(适合直接传给后端或做数值比较)。

value-format="timestamp"时会走 packages/date-picker/src/picker.vue 中的TYPE_VALUE_RESOLVER_MAP特判逻辑——DATE_FORMATTER在格式为timestamp时直接返回value.getTime()DATE_PARSER则用new Date(Number(text))还原:

const DATE_FORMATTER = function(value, format) { if (format === 'timestamp') return value.getTime(); return formatDate(value, format); }; const DATE_PARSER = function(text, format) { if (format === 'timestamp') return new Date(Number(text)); return parseDate(text, format); };

底层格式化/解析由 src/utils/date-util.js 基于fecha库完成,并注入当前 locale 的月份/星期名称,因此MMM/MMMM会随语言切换(如法语显示Jan/Janvier):

// src/utils/date-util.js export const formatDate = function(date, format) { date = toDate(date); if (!date) return ''; return fecha.format(date, format || 'yyyy-MM-dd', getI18nSettings()); };

每种type都有内置的默认格式(DEFAULT_FORMATS),例如date默认yyyy-MM-dddatetime默认yyyy-MM-dd HH:mm:ssweek默认yyyywWW,未显式指定format时即采用这些值。此外,从 packages/date-picker/src/picker.vue 的parsedValue计算属性可以看到:如果未设置value-format而用户传入了字符串或时间戳,组件会尝试用new Date(val)做隐式转换(源码注释称其为"常见但不正确的用法兼容")。

区间默认时间:default-time

选择日期区间时,开始日期和结束日期的默认时刻都是00:00:00。通过default-time可以分别为两端指定时刻,取值是长度为 2 的字符串数组,每个元素形如12:00:00:第一个是开始日期的时刻,第二个是结束日期的时刻。

<template> <div class="block"> <p>Valeur(值): {{ value }}</p> <el-date-picker v-model="value" type="daterange" start-placeholder="Date de début" end-placeholder="Date de fin" :default-time="['00:00:00', '23:59:59']"> </el-date-picker> </div> </template> <script> export default { data() { return { value: '' }; } }; </script>

上面的配置意味着:开始日期落在当天00:00:00,结束日期落在当天23:59:59。这在"查询某自然日/自然周的数据"场景非常实用——能保证结束区间覆盖到最后一秒,避免因默认00:00:00而漏掉当天的数据。

源码层面,packages/date-picker/src/panel/date-range.vue 在收到区间选择结果时,用defaultTime数组分别修饰minDatemaxDate

const defaultTime = this.defaultTime || []; const minDate = modifyWithTimeString(val.minDate, defaultTime[0]); const maxDate = modifyWithTimeString(val.maxDate, defaultTime[1]);

其中modifyWithTimeString(定义在 src/utils/date-util.js)将"HH:mm:ss"字符串解析后合并进日期对象。测试用例 "select datetime with defaultTime"(见 test/unit/specs/date-picker.spec.js)也覆盖了该行为。default-time同样支持datetimerangemonthrange

完整属性清单

下表汇总 DatePicker 的全部属性(法语文档原表),标注了类型、可选值与默认值:

属性说明类型可选值默认值
value / v-model绑定值date(DatePicker) / array(DateRangePicker)
readonly是否只读booleanfalse
disabled是否禁用booleanfalse
size输入框尺寸stringlarge/small/mini
editable文本框是否可输入booleantrue
clearable是否显示清除按钮booleantrue
placeholder非区间模式的占位提示string
start-placeholder区间模式开始日期的占位提示string
end-placeholder区间模式结束日期的占位提示string
type选择器类型stringyear/month/date/dates/datetime/week/datetimerange/daterange/monthrangedate
format输入框中的显示格式string见日期格式yyyy-MM-dd
align对齐方式stringleft/center/rightleft
popper-class下拉面板的自定义类名string
picker-options附加选项,见下表object{}
range-separator区间分隔符string'-'
default-value日历面板的默认展示日期,可选Date任何能被new Date()解析的值
default-time区间选择时的默认时刻,可选string[]长度为 2 的数组,每项形如12:00:00,第一个是开始时刻,第二个是结束时刻
value-format绑定变量的存储格式,可选;不指定时值为 Date 对象string见日期格式
name原生 input 的 name 属性string
unlink-panels是否让区间两个面板独立翻页booleanfalse
prefix-icon前缀图标类名stringel-icon-date
clear-icon清除按钮图标类名stringel-icon-circle-close
validate-event是否触发表单校验booleantrue
append-to-body是否将弹层挂载到 bodybooleantrue

其中几个属性在 packages/date-picker/src/picker.vue 中有明确的默认值与实现逻辑可对照:

  • type默认date,由入口 packages/date-picker/src/picker/date-picker.js 的getPanel分派到对应面板;type变化时会自动unmountPicker并重新mountPicker
  • editable默认为true,但源码中typedatesweek等类型时输入框被强制设为只读,因为这类值无法通过键盘输入。
  • align决定 Popper 弹层的位置,源码中的PLACEMENT_MAP将其映射为bottom-start/bottom/bottom-end
  • clearableclear-icon配合:鼠标悬停时出现清除图标,点击后清空值并触发change
  • pickerDisabled计算属性同时考虑组件自身的disabledel-formdisabled(通过inject注入的elForm),说明 DatePicker 天然继承表单禁用状态。

picker-options 附加选项

picker-options是一个对象,用于承载所有与面板行为相关的扩展配置:

属性说明类型可选值默认值
shortcuts快捷选项数组,元素为{ text, onClick }object[]
disabledDate判断日期是否禁用的函数,接收该日期为参数,返回布尔值function
cellClassName自定义单元格 classNameFunction(Date)
firstDayOfWeek一周的第一天Number1 到 77
onPick选中日期变化时的回调,仅用于daterangedatetimerangeFunction({ maxDate, minDate })

从 packages/date-picker/src/picker.vue 的mountPicker可以看到,pickerOptions除了selectableRange(仅 time-picker 使用)外,其余所有键会被逐个赋值到面板实例上,并且通过深层watch保持响应式更新:

const updateOptions = () => { const options = this.pickerOptions; // selectableRange 特判处理(time 相关) for (const option in options) { if (options.hasOwnProperty(option) && option !== 'selectableRange') { this.picker[option] = options[option]; } } }; updateOptions(); this.unwatchPickerOptions = this.$watch('pickerOptions', () => updateOptions(), { deep: true });

也就是说,disabledDatefirstDayOfWeekcellClassNameonPick等都是面板直接消费的配置。例如firstDayOfWeek默认为 7(周日),会被透传给date-table控制每周起始列;disabledDate同时作用于日/月/年三个表格以及"此刻"按钮(changeToNow会先检查disabledDate)。

快捷选项(Shortcuts)规范

快捷项是picker-options.shortcuts数组中的元素,字段如下:

属性说明类型
text快捷项标题string
onClick点击回调,接收vm(面板实例)作为参数;通过vm.$emit('pick', ...)修改 picker 的值。示例:vm.$emit('pick', new Date())function

单值类型传一个日期,区间类型传[start, end]数组(参考上文 daterange / monthrange 示例)。快捷项渲染为面板左侧边栏,点击事件由handleShortcutClick分发。

事件、方法与插槽

事件

事件名说明参数
change用户确认值发生变化时触发组件绑定值
blur输入框失焦时触发组件实例
focus输入框聚焦时触发组件实例

注意changeinput的区别:change仅在用户确认(选中、回车或失焦)且值与打开面板前的值不同时才触发。packages/date-picker/src/picker.vue 的emitChange通过valueEquals对比当前值与valueOnOpen(打开面板时快照),只有真正变化才派发事件并通知ElFormItem触发校验:

emitChange(val) { // determine user real change only if (!valueEquals(val, this.valueOnOpen)) { this.$emit('change', val); this.valueOnOpen = val; if (this.validateEvent) { this.dispatch('ElFormItem', 'el.form.change', val); } } }

valueEquals同时兼容Date对象、字符串和数组三种形态的比较,保证"打开面板又关闭、值未变"时不会误触发change(对应测试用例 "change event: when clear(), without opening picker")。

方法

方法说明参数
focus使输入框聚焦

在 packages/date-picker/src/picker.vue 中focus()的实现对区间模式做了特殊处理:非区间模式直接调用$refs.reference.focus(),区间模式则调用handleFocus()同时展开面板。

插槽

插槽名说明
range-separator自定义区间分隔符

配合模板中v-if="!ranged"/v-else的双分支结构,区间模式下range-separator插槽可完全替换默认的分隔符<span class="el-range-separator">{{ rangeSeparator }}</span>,适合放入自定义图标或富文本。

键盘交互与无障碍细节

DatePicker 除了鼠标操作,还内置了完整的键盘支持(见 packages/date-picker/src/picker.vue 的handleKeydown):

  • ESC:关闭面板;
  • Tab:非区间模式确认当前输入并收起面板,区间模式则允许在两个输入框之间切换焦点,全部失去焦点后才关闭;
  • Enter:校验通过后确认输入并关闭面板;
  • 方向键(↑↓←→):面板打开时在日期网格中移动选中项(见 date.vue 的handleKeyControl),且会自动跳过disabledDate禁用的日期;
  • 用户正在手动输入时,键盘事件会被拦截,避免与面板快捷键冲突。

面板头部还带有aria-label(如"上一年/上一个月"的本地化文案),配合role="button"的可点击标签,实现了基本的无障碍支持。

小结

围绕 Element 的el-date-picker,本文从基础单日选择出发,覆盖了周/月/年/多选、日期区间与月份区间、默认展示值、显示与存储格式、区间默认时刻,以及属性、picker-options、快捷项、事件、方法、插槽和键盘交互的完整用法,并结合 packages/date-picker/src/picker.vue、packages/date-picker/src/panel/date.vue、packages/date-picker/src/panel/date-range.vue、src/utils/date-util.js 等源码文件剖析了类型分派、格式解析、默认时刻注入、change 事件防抖等底层机制。实践建议是:与后端交互时优先使用value-format统一字符串格式;做时间范围查询时务必用default-time明确起止时刻;需要禁选或快捷选项时把逻辑收敛到picker-options中统一维护。若需进一步验证行为,可参考 test/unit/specs/date-picker.spec.js 中覆盖 create、select、clear、value-format、default value、keydown 等场景的测试用例。

【免费下载链接】elementA Vue.js 2.0 UI Toolkit for Web项目地址: https://gitcode.com/gh_mirrors/eleme/element

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

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

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

立即咨询