
1. 从“用户乱输日期”到“程序自动纠错”一个被忽略的真实痛点做后台管理系统的人十有八九都跟ElementUI的el-date-picker打过交道。这个组件功能确实全单选、范围、快捷选项、禁用日期统统都有但真正用到“让用户手动输入日期”这个场景时问题就全冒出来了用户输入了“2024-5-1”组件失焦后显示正常了可绑定值变成了null用户输入了“2024年05月01日”程序直接报错用户把结束时间选成了开始时间的前一天列表查出来一片空白还不知道错在哪。我在好几个中后台项目里都踩过这些坑后来专门花了两个晚上梳理了一套围绕el-date-picker的输入格式智能转换与动态适配方案。这篇文章就把完整的思路、实现代码、踩坑记录都摊开来讲目标是让你在项目里遇到类似需求时不用再去翻文档、试错直接能照搬一套可用的方案。适合谁看正在用Vue 2 ElementUI维护老项目的同学准备在Vue 3 Element Plus里做日期组件二次封装的同僚以及所有被“用户不按格式输入日期”折磨过的前端开发。文章不端着每一步都是实际可以跑的代码。2. 为什么el-date-picker的输入格式这么难搞2.1format与value-format的“双重人格”是问题的根源el-date-picker最让人混淆的设计就是format和value-format这两个属性的各司其职。format控制的是输入框里显示给用户看的日期样子比如yyyy-MM-dd会显示成“2024-05-01”而value-format控制的是组件绑定值真正存下来的格式比如yyyy-MM-dd HH:mm:ss会让v-model拿到的值直接就是“2024-05-01 14:30:00”这种字符串。如果只设置了format而忘记设置value-format组件绑定的值会是一个JavaScript的Date对象如果两者都设置了但格式不一样那输入框显示的内容和实际提交给后端的值就是两套内容。很多项目出问题就是因为在表单里看到的是“2024-05-01”提交后后端收到的却是Date对象或者null接口直接报参数格式错误。要想彻底掌控这个组件的表现必须养成一个条件反射只要项目里要提交日期字符串给后端就明确同时设置format和value-format并且让这两者尽量保持一致或者确保二者之间只差你不知道就会被坑的那部分。2.2 手动输入时的“宽容”与“严格”之争el-date-picker在手动输入时对格式的容忍度其实非常低。比如你设置了formatyyyy-MM-dd用户输入“2024-5-1”回车组件不会自动帮你补成“2024-05-01”。在某些版本中这种输入会被判定为非法输入失焦后输入框直接变成空白绑定值变成null在另一些场景下输入框会暂时显示“2024-5-1”但绑定值依然没有正确更新。为什么会这样因为ElementUI源码在处理手动输入时对字符串的解析走的是内部的一套严格正则匹配逻辑。yyyy-MM-dd格式就要求必须是四位年份、两位月份、两位日期且中间用连字符分隔。一旦用户输入的内容不符合这个模板解析就会失败组件不会去做模糊匹配或自动纠错。问题就在于真实用户不会按开发者的格式规范输入日期。有人习惯输“2024-05-01”有人输“2024/05/01”有人输“2024年5月1日”还有人只输“0501”想表达5月1号。指望用户自律是不现实的我们需要在组件外面包一层自己的解析和纠错逻辑。2.3 “显示对但值不对”的隐蔽Bug是怎么产生的还有一种经常被忽视的坑是“显示对但值不对”。比如设置formatyyyy-MM-dd和value-formatyyyy-MM-dd HH:mm:ss用户通过日期面板选择“2024-05-01”输入框显示“2024-05-01”但绑定值却是“2024-05-01 00:00:00”。查数据库时如果字段类型是date这一串带时分秒的字符串传过去可能没问题但如果字段类型是datetime且你期望存的是当天某个时刻或者你在前端拿这个值去做字符串比较就会发现跟预期对不上。还有一种更隐蔽的情况用户手动输入“2024-05-01 14:30:00”到只有yyyy-MM-dd的组件里输入框失焦后可能会自动截断成“2024-05-01”并正常绑定也可能会整个清空。这取决于ElementUI的版本和浏览器环境具有很强的不确定性。正式项目里出现“上次还能用这次突然不能用了”的反馈很多就是这种版本差异和不稳定解析造成的。3. 方案设计既要“智能转换”也要“动态适配”3.1 整体思路在组件外部包一层“格式网关”既然el-date-picker本身不具备智能输入的能力最常见的做法就是自己定义一个包装组件在它的外层监听用户的输入、失焦和变更事件执行格式标准化和转换逻辑再同步给el-date-picker。这个思路跟后端接口前面的网关层很像对外接收各种格式的请求对内统一转换成标准格式。包装组件内部需要拆成三个层次展示层即el-date-picker本身负责渲染日期面板和接收点击选择配置format和value-format解析层一个自己写的日期字符串解析函数接收用户手动输入的内容结合当前组件的format配置尝试用多种模式去解析返回标准化的Date对象或标准字符串适配层根据业务场景是查询条件还是表单编辑是单个日期还是范围日期需不需要时分秒动态决定最终传给上层v-model的值格式。这里有一个关键设计决策解析层作为纯函数独立出来不依赖ElementUI的自有逻辑。这样可以单独做单元测试也能在需要支持更多输入格式时进行独立扩展。3.2 动态适配的两个维度业务场景与输入内容“动态适配”具体适配什么我总结下来主要落在两个维度。第一个维度是业务场景适配。同一个日期选择器在“新增记录”表单里可能只需要精确到天在“查询条件”里可能需要精确到毫秒在“报表统计”里可能又是另外一套口径。我们不能要求业务方迁就组件得让组件根据配置自动匹配。具体做法是把“日期精度”“提交格式”“面板交互模式”都作为包装组件的props让页面在使用时按需声明而不是每个地方写死一堆属性。第二个维度是输入内容适配。用户输入“2024-05-01 14:30”即使当前format是yyyy-MM-dd系统也应该能够识别出用户意图是带时间的输入“2024-5-1”系统应该自动补零输入“2024/05/01”系统应该把斜杠替换成连接符。这个识别和规整的过程就是输入内容层面的动态适配。配合这套方案最好再做一个“失焦回填”的操作当用户输入完毕后点击输入框外部如果输入内容能被成功解析就用标准化后的格式回写输入框让用户看到自己的输入被自动纠正。这个交互细节对用户感知的提升非常明显很多人用过一次就再也回不去了。4. 核心实现从解析函数到组件封装4.1 手写一个兼容多格式的日期解析函数解析函数是整个方案的核心我不建议在投入产出比很低的情况下自己去写一套完备的日期解析器更务实的做法是围绕常见输入模式写一个覆盖80%场景的解析工具。下面这个函数就是我在项目中实际在用的一个版本/** * 解析用户输入的日期字符串兼容多种格式 * param {string} input 用户原始输入 * returns {Date|null} 解析成功返回Date对象失败返回null */ export function parseFlexibleDate(input) { if (!input || typeof input ! string) return null; // 1. 去掉首尾空格统一替换中文年月日等分隔符为英文符号 let trimmed input.trim(); trimmed trimmed.replace(/[年月日.]/g, -); trimmed trimmed.replace(/[时点]/g, :); trimmed trimmed.replace(/[分]/g, :); trimmed trimmed.replace(/[秒]/g, ); // 2. 提取日期和时间部分 let datePart trimmed; let timePart ; if (trimmed.includes( )) { const parts trimmed.split(/\s/); datePart parts[0]; timePart parts[1] || ; } else if (trimmed.includes(T)) { const parts trimmed.split(T); datePart parts[0]; timePart parts[1] || ; } // 3. 处理日期部分支持 yyyy-M-d 或 yyyy/M/d const dateSegments datePart.split(-).filter(seg seg ! ); if (dateSegments.length 3) return null; let year parseInt(dateSegments[0], 10); let month parseInt(dateSegments[1], 10); let day parseInt(dateSegments[2], 10); if (isNaN(year) || isNaN(month) || isNaN(day)) return null; if (month 1 || month 12) return null; // 4. 处理时间部分支持 H:mm:ss / H:mm let hour 0, minute 0, second 0; if (timePart) { const timeSegments timePart.split(:).filter(seg seg ! ); if (timeSegments.length 1) hour parseInt(timeSegments[0], 10) || 0; if (timeSegments.length 2) minute parseInt(timeSegments[1], 10) || 0; if (timeSegments.length 3) second parseInt(timeSegments[2], 10) || 0; if (hour 23 || minute 59 || second 59) return null; } // 5. 构造Date对象注意月份要减1 const date new Date(year, month - 1, day, hour, minute, second); // 6. 校验日期是否合法比如2月30日应被判定为非法 if (date.getFullYear() ! year || date.getMonth() ! month - 1 || date.getDate() ! day) { return null; } return date; }这个函数的处理逻辑不算复杂但有几个细节值得说清楚。第一月、日这类中文单位替换成-是出于“尽可能解析用户输入”的考虑替换后“2024年5月1日”会变成“2024-5-1”进入日期段拆分逻辑后能正确解析。第二对月份和日期的合法性校验不能省直接new Date(year, month - 1, day)再去读取一遍可以过滤掉“2024-02-30”这种肉眼看着像日期但实际不存在的值。第三函数故意没有预设当前时间补全逻辑——比如只输入“2024-05-01”要不要默认00:00:00我认为应该在组件层决定而不是在解析函数里写死否则后续做value-format切换时很容易产生预期外的行为。4.2 封装smart-date-picker组件接管失焦与回填有了可靠的解析函数接下来就是把它接进组件里。完整的封装代码比较长这里先展示最核心的失焦与回填逻辑。template el-date-picker refpickerRef :typepickerType :formatdisplayFormat :value-formatbindValueFormat :placeholderplaceholder v-modelinnerValue :clearableclearable changehandleChange blurhandleBlur /el-date-picker /template script import { parseFlexibleDate } from ./parseFlexibleDate; export default { name: SmartDatePicker, props: { // 页面上的展示格式比如 yyyy-MM-dd displayFormat: { type: String, default: yyyy-MM-dd }, // 真正提交给后端/表单的值格式 bindValueFormat: { type: String, default: yyyy-MM-dd HH:mm:ss }, // 是否打开智能纠正输入 smartCorrect: { type: Boolean, default: true }, value: [String, Date, null] }, data() { return { innerValue: this.value || null }; }, computed: { pickerType() { if (this.displayFormat.includes(HH:mm:ss)) return datetime; if (this.displayFormat.includes(HH:mm)) return datetime; return date; } }, watch: { value(newVal) { this.innerValue newVal; } }, methods: { handleChange(val) { // 通过面板选择的日期走正常逻辑无需额外转换 this.$emit(update:value, val); this.$emit(change, val); }, handleBlur() { if (!this.smartCorrect) return; const inputText (this.$refs.pickerRef this.$refs.pickerRef.$el this.$refs.pickerRef.$el.querySelector(input)) ? this.$refs.pickerRef.$el.querySelector(input).value : ; if (!inputText) return; const parsed parseFlexibleDate(inputText); if (parsed) { // 根据 bindValueFormat 将 Date 对象格式化为字符串 const formatted this.formatDateToStr(parsed, this.bindValueFormat); this.innerValue formatted; this.$emit(update:value, formatted); this.$emit(change, formatted); this.$emit(blur, formatted); } else { // 解析失败可以保留输入也可以清空这里默认清空并通知外部 this.innerValue null; this.$emit(update:value, null); this.$emit(blur, null); } }, formatDateToStr(date, fmt) { // 常见日期格式化函数也可以用 moment/dayjs 代替 const o { yyyy: date.getFullYear(), MM: String(date.getMonth() 1).padStart(2, 0), dd: String(date.getDate()).padStart(2, 0), HH: String(date.getHours()).padStart(2, 0), mm: String(date.getMinutes()).padStart(2, 0), ss: String(date.getSeconds()).padStart(2, 0) }; return fmt .replace(yyyy, o[yyyy]) .replace(MM, o[MM]) .replace(dd, o[dd]) .replace(HH, o[HH]) .replace(mm, o[mm]) .replace(ss, o[ss]); } } }; /script这里要划几个重点。组件的value/v-model设计我采用了update:value这种事件名方便在使用时通过.sync修饰符或v-model自定义model选项来接值。在实际项目中我更倾向于让外层页面用v-model绑定所以封装时会为组件配置model选项把value和update:value对应到v-model上。pickerType的计算是整个动态适配的关键之一。它根据displayFormat是否包含时分秒自动决定组件是date模式还是datetime模式避免开发者在每个使用处手动指定type。这一点在表单字段多、日期类型杂的大型项目里特别省事。失焦回填的时机我选择在blur事件里处理而不是change事件。原因是change事件在输入过程中可能多次触发如果用户正在输入“2024-05-01”在输入“2024-05-0”时就触发转换回填会打断用户的输入节奏。blur时做一次性纠正用户体验最舒服。4.3 范围日期选择的智能校验与结束时间限制除了单个日期el-date-picker的daterange模式也是重灾区。用户选择了一个范围但你无法保证他们的开始时间早于结束时间哪怕用户是通过面板选择的也可能因为日期选择器允许跨月跨年而误选。对于范围模式我的建议是做两层校验。第一层是前置校验当用户通过面板完成选择时在change事件里判断startDate和endDate的顺序如果开始时间大于结束时间自动交换并重新赋值输入框。handleRangeChange(val) { if (Array.isArray(val) val.length 2) { const [start, end] val; if (start end new Date(start).getTime() new Date(end).getTime()) { // 交换顺序 const swapped [end, start]; this.innerValue swapped; this.$emit(update:value, swapped); } } }第二层是输入拦截设置picker-options里的disabled-date让结束时间之前的日期在开始时间已选定的情况下不可点击同时配合onPick回调拿到当前的选中值去动态更新disabled-date。这是一个老生常谈的ElementUI技巧但要真正做得顺手必须把disabled-date包进一个响应式方法computed: { pickerOptions() { return { disabledDate: (time) { if (this.rangeStart this.type daterange) { return time.getTime() this.rangeStart.getTime() - 24 * 60 * 60 * 1000; } return false; }, onPick: ({ maxDate, minDate }) { this.rangeStart minDate ? minDate.getTime() : null; } }; } }这两个方案要搭配使用才能做到“面板选择时受限、手动输入时纠错”。单独只加disabled-date用户手动输入非法范围时依然会穿透。4.4 时间格式字符串与Value格式的动态映射动态适配的最后一个关键点是时间格式字符串的统一管理。在普通项目中format与value-format往往是散落在各个页面里的硬编码改一处漏一处。我建议在项目里维护一份格式常量定义// dateFormats.js export const DATE_FORMAT { DATE: yyyy-MM-dd, TIME: HH:mm:ss, DATE_TIME: yyyy-MM-dd HH:mm:ss, MONTH: yyyy-MM, YEAR: yyyy };然后在包装组件内部通过一个映射方法把displayFormat转换成对应的valueFormat并且允许上层通过bindValueFormat覆盖默认值。这样开发者在添加一个“只需要年”的选择器时传入的props是dateTypeyear组件自动把显示格式设置成yyyy绑定值格式顺带设置成yyyy。如果哪天后端要求年份提交格式变成“2024年”只需要改常量表所有使用处统一生效。这套映射逻辑的另一个价值在于它让“动态适配”变成了配置化。页面开发不再需要关心ElementUI的format和value-format差异只需要声明业务上想要什么精度、什么提交格式剩下的事情交给组件。5. 实操过程从零接入到上线全流程记录5.1 在我自己的项目里这套方案怎么落地我接手的一个旧后台项目里有十几个页面用到了日期选择器代码里各种format、value-format写法混乱有的页面甚至没设置value-format导致提交给后端的值是Date对象接口一直报错。之前是业务侧每次遇到问题就临时改接口兼容要么就塞给后端让他手动处理折腾得不行。我用这套方案做了一次集中改造。步骤大致如下第一步先把parseFlexibleDate和SmartDatePicker组件放进项目的公共目录日常用到的格式常量也一并整理。第二步做一个全项目搜索找出所有用到el-date-picker的地方逐一替换成SmartDatePicker。第三步在替换时记录每个页面原始的format和value-format然后统一映射到displayFormat与bindValueFormat两个props上。第四步针对范围选择的页面单独检查picker-options里的disabled-date逻辑是否已包含在内。改造过程中最耗时间的反而是那些“隐藏用法”——比如有人把el-date-picker放在表格的每一行里作为行内编辑控件有人把它放在弹窗里。这些场景下组件的失焦触发时机不同还需要额外处理一下pickerRef的获取方式但整体架构是通用的。5.2 失焦回填与格式化回显的联调细节在联调过程中一个很容易被忽略的细节是当用户点击输入框后不输入内容直接点击外面也就是输入框从聚焦变成失焦这时如果走handleBlur里“解析失败即清空”的逻辑会误伤一个本来就为空的字段。所以我在handleBlur里加了一个前置判断如果输入框的值本来就是空字符串直接return不去触发展值清空。还有一个细节是输入框里的值跟组件value值并不总是同步。用户手动输入后即使组件内部value是null输入框依然展示了用户输入的字符串。如果不做任何处理就会出现“我明明输入了为什么表单提交时空了”的困惑。失焦回填正好解决了这个问题——解析成功就回填标准格式绑定值同步更新解析失败就清空输入框绑定值置空用户能明确感知到输入没有被接受。回填的展示格式应该用displayFormat而不是bindValueFormat。这个原则一开始没有严格执行导致用户在输入“2024-05-01 14:30”后失焦输入框被回填成了带秒的“2024-05-01 14:30:00”用户还跑来问我为什么多了一个“:00”。这个细节要在组件设计里从一开始就定好不然后期改动涉及的面很广。5.3 测试用例清单这些边界情况必须覆盖在我自己的实践里会给这个组件维护一份边界测试清单。专门说几个代表性用例。输入“2024-5-1”不带前导零期望解析成功并回填为“2024-05-01”输入“2024/05/01”使用斜杠分隔期望解析成功并回填为“2024-05-01”输入“2024年5月1日”中文日期期望解析成功并回填为“2024-05-01”输入“2024-02-30”存在但非法的日期期望解析失败并清空输入“2024-13-01”月份越界期望解析失败并清空输入“2024-05-01 14:2”时间缺位期望解析成功秒数补为0分钟补为“02”输入只有时间没有日期的内容期望解析失败不干扰已有值。这份清单不一定覆盖所有用户可能的输入习惯但在实际项目中足以拦截90%以上的脏数据。上线后如果再遇到新的输入格式问题就往parseFlexibleDate里追加替代规则测试也顺带跑一遍。6. 常见问题与排查技巧实录6.1 v-model的值突然变成Date对象排查思路是什么如果你发现绑定的值从字符串变成了Date对象第一件事是检查value-format有没有设置。只要没有设置value-formatel-date-picker默认返回的就是Date对象。就算你设置了value-format在某些版本的ElementUI里手动输入时也可能会出现绑定值为Date对象的异常情况。遇到这种问题最直接的排查方式是打个断点在el-date-picker的handleChange里看看组件内部传出来的是什么类型。如果是Date对象就说明手动输入路径没有走value-format格式化逻辑。在自定义组件里我们可以通过统一的解析函数将Date对象再次格式化成目标字符串这样即使ElementUI行为有差异传到外层的值依然保持一致。6.2 固定列变透明的历史Bug与日期选择器有什么关系很多老项目里会遇到“ElementUI报表的固定列有时候会变透明”的诡异问题。这个问题表面上看跟日期选择器毫无关系但在实际排查中它往往出现在表格里嵌入了el-date-picker的行内编辑场景。原因是日期选择器在打开下拉面板时会在顶层插入一个浮层节点当表格固定列和浮层同时存在时在某些低版本浏览器中渲染层的GPU合成器会抽风导致固定列背景色丢失表现为“变透明”。这不是一个能靠日期组件本身修复的问题但如果你在自己项目里遇到了可以先用最简单的方式验证打开浏览器开发者工具手动给固定列外层容器加上background-color: #fff如果透明现象消失说明是合成层背景丢失加个强制背景色即可。这个方法不优雅但能救急。把它放在这篇关于日期选择器的文章里提一句是因为我确实遇到过一个项目报表页的固定列变透明排查了大半天才发现是页面里嵌的那个日期选择器浮层触发的渲染问题。设计表单时如果表格行内编辑不是刚需建议优先考虑用弹窗编辑替代行内编辑这一下能避开不少老版本ElementUI的渲染怪毛病。6.3 范围选择结束时间早于开始时间的校验应该放在哪一层关于el-date-picker判断结束时间大于起始时间的需求每个团队给出过不同的解法。有的在后端接口里校验前端不管有的在表单提交时统一校验提示“结束时间不能早于开始时间”还有的在change事件里做判断不合规则弹个报错。我的建议是三层都要有但每一层的职责不同面板层的disabled-date负责让用户“选不出来”输入层的handleRangeChange负责“选了也自动纠正”最终提交前的表单校验负责“万一前面都漏了兜底拦截”。只做一层就会在某些极端路径下漏掉。特别是自动交换顺序这种做法有一个隐患如果用户在日期面板里先点结束日期、再点开始日期组件内部的minDate和maxDate逻辑会跟我们的交换逻辑打架。所以自动交换只建议在手动输入场景用面板选择场景交给ElementUI自身的范围逻辑不要画蛇添足。6.4 动态禁用日期后原有值还在但面板上无法点选怎么处理disabled-date动态更新后可能会出现一种尴尬情况表单在编辑回显时有一个历史日期但这个日期被新的禁用规则覆盖了导致用户点开面板发现选中的日期处于禁用状态却又无法取消。此时即使绑定值还在保存提交时前端校验也会因为日期被禁用而报错。处理方式有两种一种是在初始化时判断当前回显值是否命中禁用规则如果命中则强制清空另一种是给组件增加一个disabled-date-ignore-initial开关允许回显值在未修改前不受禁用限制一旦用户主动修改就启用禁用规则。第二种方式更贴近用户心智我只是想看一下旧数据但我可以不改。这个开关的实际实现不复杂在禁用函数里加一个if (!this.isTouched) return false的判断即可。但要注意在用户有效操作日期后把isTouched置为true否则禁用规则永远不生效。7. 项目改造中的几个坑与心得说几个改造过程中跟测试、协作直接相关的坑。第一个坑是兼容性测试覆盖不足。el-date-picker在原生input的blur事件上不同浏览器的触发时序略有差异。在Chrome里blur触发时输入框的值已经是最新的在某个特定版本的Safari里blur触发时输入框的值有概率还是上一帧的旧值。我在项目里遇到过Safari下用户输入日期后失焦回填却是“上一次输入”的结果。解决办法是在blur里套一个requestAnimationFrame或setTimeout延迟几毫秒再读取输入框值实测这个“脏读”问题能被消除。第二个坑是不要把解析函数做得过于“聪明”比如尝试“2024-05-01至2024-05-03”这种带中文“至”的范围解析。这类需求的接纳面太窄解析逻辑写起来复杂度成倍增加一旦用户输入变体维护成本很高。实际做下来范围输入还是让用户通过面板选择最稳妥手动输入范围本身反人类。第三个坑是项目里已经有了一层封装时改造要先看旧封装在哪些地方做了特殊约定。我接过一个项目旧封装把el-date-picker的change事件重新定义成了只返回数组形式的值导致所有下游都在解构数组。直接用新方案替换时这些下游全都报错。正确做法是先梳理旧组件的对外接口和行为尽量在技术上兼容旧的调用方式或者一次性扫描所有调用处批量修改不要新旧混用。第四个坑是关于空值处理。日期选择器在表单里经常是可选项用户可能故意清空再提交。我在封装里默认了“清空后就置空并提交null”但有些业务要求清空后提交一个空字符串。这种差异最好在组件里做成一个emptyValue的prop默认是null需要空字符串的页面显式传避免不同页面的行为来回横跳。8. 后续扩展方向这套能力还能怎么用如果你已经按照上面的思路把SmartDatePicker封装起来了后续有几个方向可以继续扩展。方向一支持更多日期粒度。目前只处理了date和datetime但实际业务里还有monthrange、year、week等模式。这些模式同样存在格式解析问题只是场景少一些。可以把parseFlexibleDate扩展成parseFlexibleByType根据不同的picker类型调整解析的段数和分段逻辑。方向二接入第三方日期库。我的示例里自己写了格式化函数但如果项目里已经引入了dayjs或moment可以直接在失焦回填时用现成的format方法替换。这个改动基本不影响组件设计只影响formatDateToStr内部一行实现。我最近维护的一个项目已经把dayjs的customParseFormat插件接进来解析支持的格式更多代码还更简洁。方向三把“动态适配”的配置能力再往上提一层。现在只是页面通过props控制组件的格式更彻底的做法是维护一个全局的格式配置中心结合当前用户的选择时区、语言、地区习惯等动态生成displayFormat。比如用户设置过“习惯12小时制”组件自动在format里追加A hh:mm。这个方向适合平台型系统普通业务系统暂时不必搞这么重。对我来说做这套东西最大的感受是ElementUI的日期组件本身没有问题问题在于它把“用户输入”和“组件值”之间的桥接责任交给了开发者。与其在每个页面里零散地修Bug不如把这块逻辑收拢成一个可靠的网关让复杂的事情只发生一次。