
amis InputDatetimeRange 日期时间范围控件从 JSON 配置到源码实现的完整指南【免费下载链接】amis前端低代码框架通过 JSON 配置就能生成各种页面。项目地址: https://gitcode.com/GitHub_Trending/am/amis在 amis 低代码框架中input-datetime-range是最常用于「时间区间查询」的表单控件——后台报表筛选、日志时间范围、订单起止时间等场景几乎都会用到它。本文基于 amis 官方文档 input-datetime-range.md完整覆盖该控件的配置示例、属性表、事件表与动作表并结合开源仓库中的渲染器源码InputDateRange.tsx与 UI 层选择器实现DateRangePicker.tsx讲清每个配置项背后真正的行为逻辑帮助你在复杂业务中准确配置该控件。一、基本用法一个字段存一个区间最小可用配置如下name指定字段名选中后表单数据中会生成一个形如startValue,endValue的字符串默认以英文逗号分隔存储格式为秒级时间戳{ type: form, debug: true, api: /api/mock2/form/saveForm, body: [ { type: input-datetime-range, name: select, label: 日期时间范围 } ] }从源码结构看这个控件在渲染层由 DateTimeRangeControlRenderer 注册它与input-date-range日期范围、input-time-range时间范围共用同一个基类DateRangeControl通过FormItem({ type: input-datetime-range, sizeMutable: false })装饰器完成类型注册。三者共享的默认值定义在基类的defaultProps中static defaultProps { format: X, // 默认存储格式秒级时间戳 joinValues: true, // 首尾两个值拼接成一个字符串 delimiter: ,, // 拼接用的分隔符 animation: true // 启用游标动画 };而input-datetime-range在此基础上额外覆盖了输入框的显示格式inputFormat: YYYY-MM-DD HH:mm也就是说不配置任何格式时输入框展示精确到分钟提交值默认是1650556800,1652889599这样的时间戳对。sizeMutable: false表示该控件不允许被size属性改变尺寸从源码上解释了为何它对size配置不敏感。二、时间显示到秒timeFormat 与 inputFormat 的分工通过timeFormat: HH:mm:ss可以让时间输入部分显示秒{ type: form, api: /api/mock2/form/saveForm, body: [ { type: input-datetime-range, name: select, timeFormat: HH:mm:ss, label: 日期时间范围 } ] }timeFormat与inputFormat的职责不同inputFormat决定两个输入框整体的展示格式而timeFormat决定弹层内时间选择部分时、分、秒滚轮显示哪几个层级。在 DateRangePicker 的构造函数 中可以看到当未显式指定timeFormat时组件会解析displayFormat || inputFormat中的H、m、s字符自动推导出HH:mm之类的滚动条格式显式配置HH:mm:ss后秒位滚轮才会出现。更进一步timeFormat还决定了「点选日期时时间值自动对齐的精度」。在 filterDate 方法 中源码按以下优先级对时间做对齐timeFormat含ss→ 起始时间startOf(second)、结束时间endOf(second)精确到当前秒含mm→ 对齐到分钟起始 00 分、结束 59 分含HH→ 对齐到小时其他情况 → 对齐到当天 00:00 / 23:59:59.999。这解释了为什么默认配置下选择结束日期的时间总是落在当天末尾而不是当天零点——结束值天然取「区间尾部」避免区间差一截。三、快捷键 shortcuts内置项、正则扩展项与表达式shortcuts属性支持自定义日期时间范围快捷键{ type: form, api: /api/mock2/form/saveForm, body: [ { type: input-datetime-range, name: select, label: 日期范围, shortcuts: today,yesterday,1dayago,7daysago } ] }不配置shortcuts时默认值为yesterday,7daysago,prevweek,thismonth,prevmonth,prevquarter昨日、最近 7 天、上周、本月、上月、上季度该默认值定义在 DateRangePicker.defaultProps 中。3.1 内置快捷键全集在 availableShortcuts 中框架内置了以下字符串项label会走国际化翻译快捷键含义today/yesterday/tomorrow今天 / 昨天 / 明天1dayago兼容拼写1daysago1 天前到现在7daysago/30daysago/90daysagoN 天前到昨天末尾prevweek/thisweek上周 / 本周thismonth/prevmonth本月 / 上月thisquarter/prevquarter本季度 / 上季度thisyear/prevyear兼容旧写法lastYear今年 / 去年3.2 正则扩展项任意 N 天 / 小时 / 周 / 月 / 季度 / 年除了内置项advancedRanges 定义了一组正则匹配规则shortcuts中的字符串只要匹配上即可动态生成快捷键Nhoursago / Nhourslater N 小时前 / N 小时以内 Ndaysago / Ndayslater N 天前 / N 天后 Nweeksago / Nweekslater N 周前 / N 周后 Nmonthsago / Nmonthslater N 个月前 / N 个月后 Nquartersago / Nquarterslater N 个季度前 / 后 Nyearsago / Nyearslater N 年前 / N 年后例如shortcuts: [1hoursago, 2hourslater]会渲染出「最近1小时」「2小时以内」两个按钮。这一点在自动化测试 datetimeRange.test.tsx 中得到验证点击「最近1小时」后起始输入框值为当前时间 - 1 小时的整点结束值为前一小时的:59与advancedRanges中startOf(hour)/endOf(hour)的实现完全对应。3.3 表达式写法3.1.0 及以上版本快捷键也支持使用表达式的写法可以这样定义完全自定义的快捷键{ type: form, debug: true, api: /api/mock2/form/saveForm, body: [ { type: input-datetime-range, name: select, label: 日期范围, inputFormat: YYYY-MM-DD HH:mm:ss, timeFormat: HH:mm:ss, format: x, shortcuts: [ { label: 1天前, startDate: ${DATEMODIFY(NOW(), -1, day)}, endDate: ${NOW()} }, { label: 1个月前, startDate: ${DATEMODIFY(NOW(), -1, months)}, endDate: ${NOW()} }, { label: 本季度, startDate: ${STARTOF(NOW(), quarter)}, endDate: ${ENDOF(NOW(), quarter)} } ] } ] }对象形式的快捷键结构为Array{label: string; startDate: string; endDate: string}。在 renderShortcuts 中源码对对象项的startDate/endDate先判断isExpression是表达式则调用FormulaExec[formula]用当前数据data求值否则按valueFormat || format解析为普通日期字符串——这正是示例里${DATEMODIFY(NOW(), -1, day)}等公式能生效的底层机制。注意示例中format: x毫秒级时间戳与表达式返回的时间字符串要能正确解析startDate/endDate的值会按存储格式解释。点击快捷键时的取值逻辑在 selectShortcut 中以moment()当前时间为基准计算起止点并且会实时重算minDate/maxDate注释明确写了「用户可能设置为${NOW()}」再用moment.max/moment.min把快捷键结果夹在允许范围内最后默认closeOnSelect: true时立即confirm提交。快捷键的完整细节还可以参考 input-date-range 文档的快捷键章节。四、格式体系valueFormat、displayFormat 与 format除了支持 普通表单项属性 中的配置外input-datetime-range还支持以下配置属性名类型默认值说明版本valueFormatstringX日期时间选择器值格式3.4.0 版本后支持displayFormatstringYYYY-MM-DD日期时间选择器显示格式3.4.0 版本后支持placeholderstring请选择日期范围占位文本shortcutsstring \| string[] \| Array{label: string; startDate: string; endDate: string}yesterday,7daysago,prevweek,thismonth,prevmonth,prevquarter日期范围快捷键详情参考快捷键3.1.0版本后支持表达式minDatestring限制最小日期时间用法同 限制范围maxDatestring限制最大日期时间用法同 限制范围utcbooleanfalse保存 UTC 值clearablebooleantrue是否可清除animationbooleantrue是否启用游标动画2.2.0extraNamestring是否存成两个字段3.3.0popOverContainerSelectorstring弹层挂载位置选择器会通过querySelector获取6.4.04.1 新旧格式属性的优先级3.4.0 之前的老属性是format存储格式inputFormat显示格式3.4.0 引入了语义更清晰的valueFormatdisplayFormat。在渲染器 render 方法 中传给底层DateRangePicker的实参是valueFormat{valueFormat || format} displayFormat{displayFormat || inputFormat}即新属性优先、老属性兜底两套写法可以混用但建议统一用新版。底层所有值的序列化/反序列化都围绕这两个最终值展开序列化formatValue 把startDate/endDate两个 moment 值按valueFormat格式化joinValues为真时用delimiter拼成单字符串否则返回数组反序列化unFormatValue 把字符串按delimiter拆开再解析回两个 moment 值并做了防坑处理——源码注释特别指出「undefined 会被 moment 当作合法输入并转化为 now」因此要求原始值非空且解析结果isValid()否则丢弃为undefined。这保证了脏数据如后端返回空串不会静默变成当前时间。4.2 minDate / maxDate 支持表达式在 InputDateRange 的 render 中minDate/maxDate并不是直接透传而是先经过filterDate(minDate, data, valueFormat || format)解析再同时以解析后的minDate和原始字符串minDateRaw传给底层组件。这意味着两个实用特性可以写minDate: ${NOW() | date:YYYY-MM-DD}这类表达式限制值基于当前表单数据实时计算快捷键被点击时会用minDateRaw重算边界并夹紧结果见上文selectShortcut保证表达式边界在每次点击时都是新鲜的。同理minDuration/maxDuration限制跨度在渲染层经parseDuration转换后传入底层 getEndDateByDuration 会在用户选择时把越界的结束时间自动修正为startDate minDuration/maxDuration。4.3 utc 选项utc为true时序列化改用moment.utc(...)、反序列化走 UTC 解析见 formatValue / unFormatValue 中utc ? moment.utc(newValue.startDate) : newValue.startDate的分支。适用于后端要求 UTC 时间戳且不想受浏览器时区影响的场景。4.4 其他属性行为clearable默认trueDateRangePicker的默认值里为clearable: true输入框右侧显示清除图标点击后值被置空animation默认true2.2.0 引入控制日历游标动画的开关渲染层默认animation: trueplaceholder范围控件实际上是「开始时间/结束时间」两个输入框分别有startPlaceholder/endPlaceholder属性默认走Calendar.startPick/Calendar.endPick国际化文案placeholder作为整体占位配置popOverContainerSelector6.4.0 引入弹层挂载位置选择器。从 渲染层透传逻辑 看桌面端弹层容器优先级为popOverContainerenv.getModalContainer并额外把popOverContainerSelector直接传给底层组件组件内部通过querySelector获取节点用于弹层在overflow: hidden容器中仍要跟随定位的特殊场景transform源码中支持、文档属性表未列出的进阶项AMISDateRangeSchemaBase 类型定义中包含transform属性即一段 JS 函数源码(value, config, props, data, moment) moment.Moment在每次选值时经str2function转换后执行可程序化改写日期例如强制起始时间为当月 1 日是比transform表达式更底层的定制手段。五、存成两个字段extraName默认日期范围存储到一个字段用delimiter默认,分割配置extraName3.3.0 引入则会存成两个字段{ type: form, debug: true, api: /api/mock2/form/saveForm, body: [ { type: input-datetime-range, name: begin, extraName: end, label: 日期范围 } ] }此时提交的数据是{ begin: 1650556800, end: 1652889599 }两个独立字段而不是一个逗号拼接的字符串。实现上这个能力并非日期控件自己完成而是由通用控件包装层 wrapControl.tsx 提供的当模型带extraName时内部值以数组形式维护[startValue, endValue]onChange时把第二个元素单独写入extraName字段if (model.extraName) { onChange(values[1], model.extraName, false, true); }配套逻辑还包括formItem.ts 中对「extraName 导致清空时 value 为空值数组、进而必填校验不生效」的异常做了专门清理form.ts 在重置表单时会把extraName字段一并重置为resetValue。也就是说extraName是 amis 表单项的通用能力数字范围、时间范围等控件同样受益。测试用例 datetimeRange.test.tsx 中也使用了name: beginextraName: end的组合来覆盖「首次选择后时间自动取当前时间」的行为验证了该组合在真实交互下的取值正确性。六、事件表change / focus / blur当前组件会对外派发以下事件可以通过onEvent来监听这些事件并通过actions来配置执行的动作在actions中可以通过${事件参数名}或${event.data.[事件参数名]}来获取事件产生的数据详细请查看事件动作。[name]表示当前组件绑定的名称即name属性如果没有配置name属性则通过value取值。事件名称事件参数说明change[name]: string组件的值时间值变化时触发focus[name]: string组件的值输入框获取焦点(非内嵌模式)时触发blur[name]: string组件的值输入框失去焦点(非内嵌模式)时触发从源码看这三个事件各有明确的派发点change由 handleChange 派发。值得注意的细节是源码注释指出这里「没有 await 所以 dispatcher.prevented 是不准确的」——即change事件上的拦截动作不能可靠地阻止值更新onChange总会继续执行。设计拦截逻辑时应把副作用放在事件动作里而不是依赖它阻断赋值focus / blur在渲染层直接绑定onFocus{() this.dispatchEvent(focus)}、onBlur{() this.dispatchEvent(blur)}InputDateRange.tsx对应底层 DateRangePicker 的 handleFocus/handleBlur。文档中「非内嵌模式」的限定对应底层的embed属性——内嵌模式日历直接平铺在页面中下弹层不存在开合焦点行为事件语义不同故不派发。七、动作表clear / reset / setValue当前组件对外暴露以下特性动作其他组件可以通过指定actionType: 动作名称、componentId: 该组件id来触发这些动作动作配置可以通过args: {动作配置项名称: xxx}来配置具体的参数详细请查看事件动作。动作名称动作配置说明clear-清空reset-将值重置为初始值。6.3.0 及以下版本为resetValuesetValuevalue: string更新的时间区间值用,隔开更新数据依赖格式format例如 1650556800,1652889599组件内部的 doAction 方法 实现了前两个动作clear直接调用底层组件的dateRef.clear()清空两个输入框reset优先从formStore.pristine或store.pristine中取name对应的初始值取不到再回退到resetValue最后调用dateRef.reset(pristineVal)——这段??链正是「6.3.0 起 reset 语义从 resetValue 改为 pristine 初始值」的版本差异来源。setValue则走通用数据更新通道把value如1650556800,1652889599写入数据链后底层componentDidUpdate检测到prevProps.value ! value调用unFormatValue重新解析并刷新两个输入框。注意这里「依赖格式format」的含义写入的字符串必须能被当前存储格式正确解析默认X即秒级时间戳若配了format: x则应写毫秒时间戳否则按上文反序列化规则会被当作非法值丢弃。八、典型完整配置与注意事项综合以上各节一个贴近真实业务的完整配置示例区间限制 表达式边界 双字段存储 事件联动{ type: input-datetime-range, name: begin, extraName: end, label: 时间范围, valueFormat: x, displayFormat: YYYY-MM-DD HH:mm:ss, timeFormat: HH:mm:ss, minDate: ${DATEMODIFY(NOW(), -90, day)}, maxDate: ${NOW()}, maxDuration: 90d, shortcuts: [today, yesterday, 7daysago, 30daysago], clearable: true, onEvent: { change: { actions: [ { actionType: reload, componentId: list-crud } ] } } }使用时的几点注意事项默认值是秒级时间戳对不配valueFormat/format时回显、defaultValue、setValue动作传入的值都应是X秒格式换成x、YYYY-MM-DD HH:mm:ss等格式后要全链路保持一致结束值默认取区间末尾点选结束日期会得到当天23:59分钟级timeFormat或对应秒位配合maxDuration时按相同对齐规则修正写后端查询条件时一般无需再加「1 天」表达式快捷键从 3.1.0 起可用valueFormat/displayFormat从 3.4.0 起可用extraName从 3.3.0 起可用popOverContainerSelector从 6.4.0 起可用——低版本项目中遇到属性不生效时先核对版本支持范围change 事件不可靠拦截如第六节所述不要在onEvent.change中依赖 prevented 来阻止赋值该控件与 input-datetime 共享值格式、显示格式、限制范围、UTC 等约定与 input-date-range、input-time-range 共用同一实现基类三者的快捷键体系availableShortcutsadvancedRanges完全一致只是默认format/inputFormat/viewMode不同input-time-range默认HH:mm且viewMode: time可以按本文思路对照阅读。【免费下载链接】amis前端低代码框架通过 JSON 配置就能生成各种页面。项目地址: https://gitcode.com/GitHub_Trending/am/amis创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考