amis InputQuarterRange 季度范围组件详解:配置属性、取值格式与源码实现
【免费下载链接】amis前端低代码框架,通过 JSON 配置就能生成各种页面。项目地址: https://gitcode.com/GitHub_Trending/am/amis
在 amis 低代码框架中,input-quarter-range(季度范围)控件是表单体系里面向财报、销售看板等按季度筛选场景的专用选择器:一次点击即可选定“开始季度—结束季度”区间,并以逗号分隔的字符串写入表单数据。本文基于仓库中的组件文档与实现源码,完整覆盖它的基本用法、内嵌模式、extraName双字段存储、全部配置属性、事件与动作机制,并深入DateRangePicker与parseDuration等底层实现,帮助你既会用、又懂其原理。
组件定位:季度范围选择器
从源码结构看,input-quarter-range是日期范围控件族的一员。它在 InputQuarterRange.tsx 中定义,直接继承日期范围基类InputDateRange,并复用 amis-ui 的DateRangePicker,只是把弹层视图固定为viewMode="quarters"(季度视图):
// packages/amis/src/renderers/Form/InputQuarterRange.tsx export default class QuarterRangeControl extends InputDateRange { @supportStatic() render() { // ... return ( <div className={cx(`${ns}DateRangeControl`, className)}> <DateRangePicker viewMode="quarters" // ... /> </div> ); } } @FormItem({ type: 'input-quarter-range' }) export class QuarterRangeControlRenderer extends QuarterRangeControl { static defaultProps = { format: 'X', inputFormat: 'YYYY-[Q]Q', joinValues: true, delimiter: ',', /** shortcuts的兼容配置 */ ranges: 'thisquarter,prevquarter', shortcuts: 'thisquarter,prevquarter', animation: true }; }这段defaultProps揭示了几个文档属性表里没有直接写明的默认值:存储格式默认X(Unix 时间戳秒)、显示格式默认YYYY-[Q]Q(即2024-Q1这种形式)、起止值默认用逗号joinValues + delimiter拼成一个字符串、并且默认开启季度相关的快捷键thisquarter,prevquarter。弹层中每个季度单元格(Q1~Q4)的渲染逻辑在 DateRangePicker.tsx 的renderQuarter中实现,它用moment().year(year).quarter(quarter)定位每个季度,并根据是否落在[startDate, endDate]区间内打上选中样式。
基本用法
在表单中声明一个input-quarter-range表单项即可使用,最简配置如下(文档示例,可直接在 表单组件 体系中运行):
{ "type": "form", "api": "/api/mock2/form/saveForm", "body": [ { "type": "input-quarter-range", "name": "quarter-range", "label": "季度范围" } ] }选择完成后,表单数据中quarter-range的值为起止两个时间戳(按默认valueFormat: "X"存储)以逗号分隔的字符串。仓库中的单元测试 inputQuarterRange.test.tsx 验证了这条链路:点击输入框、在弹层中依次点选 Q1 和 Q4、确认之后,两个输入框的显示值分别是2024-Q1(当年)与2024-Q4:
fireEvent.click(inputs[0]!); fireEvent.click( await within(document.querySelector('.cxd-DateRangePicker-start')!) .findByText('Q1') ); fireEvent.click( await within(document.querySelector('.cxd-DateRangePicker-start')!) .findByText('Q4') ); fireEvent.click(getByText('确认')); const thisYear = moment().format('YYYY'); expect((inputs[0] as HTMLInputElement).value).toEqual(`${thisYear}-Q1`); expect((inputs[1] as HTMLInputElement).value).toEqual(`${thisYear}-Q4`);这个测试同时说明:显示格式(2024-Q1)与存储格式(时间戳)是两套独立的格式体系,分别由displayFormat/valueFormat控制。
内嵌模式(embed)
配置"embed": true后,季度面板不再以弹层形式出现,而是直接内联渲染在表单项位置,适合作为页面固定筛选区使用:
{ "type": "form", "api": "/api/mock2/form/saveForm", "debug": true, "body": [ { "type": "input-quarter-range", "name": "quarter-range", "label": "季度范围", "embed": true } ] }测试用例 inputQuarterRange.test.tsx 对embed模式还叠加了自定义格式:valueFormat: "YYYY-MM"、displayFormat: "YYYY/MM",并回显value: "2021-10,2021-12",断言内联面板中起止两侧的激活季度(rdtActive)均落在 Q4。需要注意内嵌模式与焦点事件的关系:文档事件表中明确focus/blur仅在非内嵌模式下触发。
存成两个字段(extraName)
默认情况下,季度范围只会写入name指定的一个字段,起止值用delimiter(默认逗号)拼接;若配置extraName,结束值会单独写入另一个字段。文档示例:
{ "type": "form", "debug": true, "api": "/api/mock2/form/saveForm", "body": [ { "type": "input-quarter-range", "name": "begin", "extraName": "end", "label": "季度范围" } ] }这一行为不是季度组件独有的,而是表单控件包装层的通用机制。在 wrapControl.tsx 中可以看到:当model.extraName存在时,onChange 会把区间值的第二部分单独写入extraName对应的字段,即onChange(values[1], model.extraName, false, true)。因此表单提交后数据里会得到begin与end两个独立字段,而不是一个逗号拼接的字符串,便于后端直接按字段接收起止时间。
属性表
除了支持 普通表单项属性表 中的配置以外,input-quarter-range还支持下面一些配置(完整继承自文档属性表):
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
|---|---|---|---|---|
| valueFormat | string | X | 日期选择器值格式 | 3.4.0 |
| displayFormat | string | YYYY-DD | 日期选择器显示格式 | 3.4.0 |
| placeholder | string | "请选择季度范围" | 占位文本 | |
| minDate | string | 限制最小日期,用法同 限制范围 | ||
| maxDate | string | 限制最大日期,用法同 限制范围 | ||
| minDuration | string | 限制最小跨度,如: 2quarter | ||
| maxDuration | string | 限制最大跨度,如:4quarter | ||
| utc | boolean | false | 保存 UTC 值 | |
| clearable | boolean | true | 是否可清除 | |
| embed | boolean | false | 是否内联模式 | |
| animation | boolean | true | 是否启用游标动画 | 2.2.0 |
| extraName | string | 是否存成两个字段 | 3.3.0 | |
| popOverContainerSelector | string | 弹层挂载位置选择器,会通过querySelector获取 | 6.4.0 |
此外,由于它继承自日期范围基类AMISDateRangeSchemaBase(定义于 InputDateRange.tsx),还可使用基类声明的这些属性:delimiter(分隔符,默认逗号)、format/valueFormat(存储格式,format为旧版写法)、inputFormat/displayFormat(显示格式,旧版写作inputFormat)、joinValues(是否拼接值,默认true)、startPlaceholder/endPlaceholder(起止占位符)、borderMode(full/half/none)、transform(日期数据处理函数,用于自定义处理选择后的值),以及ranges(3.1.0 起废弃,建议改用shortcuts)。
几个属性在源码中的解析路径值得展开:
minDate/maxDate:渲染前会先经过filterDate(minDate, data, valueFormat || format)(见 InputQuarterRange.tsx),即支持在限制值里写表达式引用表单数据(如${min}),并按存储格式解析为 moment 对象。minDuration/maxDuration:由 date.ts 中的parseDuration解析,正则支持的单位包括second、minute、hour、day、week、month、quarter、year、weekday、millisecond(可加复数s、可带+/-前缀、支持小数),最终生成moment.Duration传给弹层做跨度校验,所以2quarter这种写法就是被这条正则直接命中的。popOverContainerSelector:透传给DateRangePicker后通过querySelector获取挂载节点;移动端(mobileUI)下弹层/弹窗统一挂载到env.getModalContainer,桌面端默认也是env.getModalContainer,该属性用于解决弹层被父容器overflow裁剪等问题。- 默认显示格式:属性表中标注
displayFormat默认值为YYYY-DD,而从源码结构看,渲染时的取值链是displayFormat || inputFormat,且季度控件的defaultProps.inputFormat为YYYY-[Q]Q,因此未显式配置displayFormat时实际呈现的是2024-Q1这类“年-季度”文本,与上文测试断言一致。
快捷键(shortcuts)
季度控件的defaultProps中默认配置了shortcuts: 'thisquarter,prevquarter',对应弹层左侧的快捷选项。这两个快捷项在 DateRangePicker.tsx 的availableShortcuts中定义:
thisquarter(本季度):startDate = now.startOf('quarter'),endDate = now.endOf('quarter');prevquarter(上季度):startDate = now.startOf('quarter').add(-1, 'quarter'),endDate = now.startOf('quarter').add(-1, 'day').endOf('day')。
此外,源码中还实现了可扩展的“高级范围”advancedRanges,支持按正则解析的动态快捷键,其中与季度直接相关的有两组:Nquartersago(N 个季度前到今天之前一天)与Nquarterslater(本季度起 N 个季度后)。也就是说除了默认两项,你还可以配置类似3quartersago的字符串快捷项,获得“最近 3 个季度”这类常用筛选。配置形式为逗号分隔字符串或ShortCuts数组(见 InputDateRange.tsx 的shortcuts声明)。
事件表
当前组件会对外派发以下事件,可以通过onEvent来监听这些事件,并通过actions来配置执行的动作,在actions中可以通过${事件参数名}或${event.data.[事件参数名]}来获取事件产生的数据,详细请查看事件动作。
[name]表示当前组件绑定的名称,即name属性,如果没有配置name属性,则通过value取值。
| 事件名称 | 事件参数 | 说明 |
|---|---|---|
| change | [name]: string组件的值 | 时间值变化时触发 |
| focus | [name]: string组件的值 | 输入框获取焦点(非内嵌模式)时触发 |
| blur | [name]: string组件的值 | 输入框失去焦点(非内嵌模式)时触发 |
从实现上看,change事件在基类 InputDateRange.tsx 的handleChange中派发:它先调用dispatchEvent('change', resolveEventData(this.props, {value: nextValue})),随后无条件执行props.onChange(nextValue)写入表单;focus/blur则分别在DateRangePicker的onFocus/onBlur回调中通过dispatchEvent派发,事件参数里的值由resolveEventData结合name/value生成。
动作表
当前组件对外暴露以下特性动作,其他组件可以通过指定actionType: 动作名称、componentId: 该组件id来触发这些动作,动作配置可以通过args: {动作配置项名称: xxx}来配置具体的参数,详细请查看事件动作。
| 动作名称 | 动作配置 | 说明 |
|---|---|---|
| clear | - | 清空 |
| reset | - | 将值重置为初始值。6.3.0 及以下版本为resetValue |
| setValue | value: string更新的时间区间值,用,隔开 | 更新数据,依赖格式format,例如 '1640966400,1664553600' |
这三个动作的落点在基类中:
clear/reset:由 doAction 处理。clear直接调用弹层实例的this.dateRef?.clear();reset先从formStore.pristine(表单初始数据)或store.pristine中按name取出初始值,取不到则回退到resetValue属性,再调用this.dateRef?.reset(pristineVal)。这正是文档中“reset 将值重置为初始值”的含义——重置目标是表单 pristine 快照里该字段对应的值。setValue:由 setData 处理。传入的字符串值先按delimiter拆成起止两段,各自经filterDate解析后交给DateRangePicker.formatValue统一格式化,最后调用onChange(value)写回表单。formatValue同样接收joinValues/delimiter/utc参数,所以setValue传入'1640966400,1664553600'(时间戳)时会按当前format/valueFormat归一化后存储。
移动端与静态渲染
两点补充行为同样来自源码:
- 移动端适配:
render中透传了mobileUI标志,移动端下DateRangePicker会把季度视图切换为日历弹层形态(DateRangePicker.tsx 中对['days', 'months', 'quarters']视图启用移动端日历),且弹层容器切换为env.getModalContainer。 - 静态展示:组件类上标注了
@supportStatic()(StaticHoc.tsx),当表单以静态模式渲染(如详情展示、只读态)时,季度范围会按纯文本形式展示起止季度,而不是可交互的弹层。
小结
input-quarter-range是 amis 表单中“按季度筛选”的标准答案:默认以X(时间戳)格式存一个逗号分隔的字段,可用valueFormat/displayFormat调整存储与展示格式,用extraName拆成两个字段,用minDate/maxDate(支持表达式)与minDuration/maxDuration(如2quarter,由parseDuration校验)约束可选范围,配合thisquarter/prevquarter/Nquartersago等快捷键覆盖常见报表场景,并通过change/focus/blur事件与clear/reset/setValue动作接入 amis 的事件动作体系。其实现位于 InputQuarterRange.tsx,底层依赖 DateRangePicker.tsx,行为验证见 inputQuarterRange.test.tsx;可视化配置则对应编辑器插件 InputQuarterRange.tsx。
【免费下载链接】amis前端低代码框架,通过 JSON 配置就能生成各种页面。项目地址: https://gitcode.com/GitHub_Trending/am/amis
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考