一、常见业务场景
toB业务中,经常有日期查询的需求,常见组件库都提供有日期组件,比如AntDV中提供了DatePicker和RangePicker,DatePicker组件用来选择单一时间,RangePicker组件用来选择时间范围,通过配置可以实现常用功能。
但是实际业务场景可能比较复杂,比如需要按一定的规则初始化、需要切换不同的日期类型,并且展示对应类型下的初始值、需要定制化的展示格式、需要校验时间范围、需要在单一日期和日期范围间切换、需要在满足一定条件后同步展示时间等,如果每个页面都去实现这些功能,还是比较繁杂的,所以我们基于实际业务场景采用AntDV提供的DatePicker和RangePicker组件结合dayjs封装了常用日期功能,提供丰富常用的默认配置,使页面可以通过简单配置和调用就能满足需求,不需要关注具体的实现细节,提高开发效率,代码也更简洁易维护,下面展示一些常见的需求场景:
1、多种日期类型切换
常用日期类型有分钟、小时、天、周、月、季度等,选择不同类型时,需要按一定格式展示对应类型下的默认日期。比如选择周类型时,默认日期为最近5周且展示格式为xxxx年第xx周 xx/xx ~ xx/xx,选择小时类型时,默认时间为当天00点到现在的时刻,需要显示的格式为xxxx-xx-xx xx点。下图展示日期为范围区间即有开始时间和结束时间时,类型切换和对应默认值展示的效果:



2、单一类型
不需要多种日期类型切换,只需要固定某种类型。下图展示的是,按天这种类型下单一日期展示效果:

3、单一日期和日期范围之间切换
有些业务场景需要在单一日期和日期范围之间切换。比如下图中,按天、周、月时,只需要单一日期,自定义选项下需要日期范围,并且需要自由配置显示方式,默认值等。
4、日期范围校验
当日期为范围区间时,希望用户选择的范围区间能满足一定条件,满足条件后才进行查询,否则给出错误提示且不查询。比如按分钟时,要求时间范围不超过30分钟;按天时,要求时间范围不超过31天等。下图展示的是分钟超出限制时的效果:
5、同步展示日期
如果页面内容比较多,比如查询结果有图也有表格,偏下的部分可能就看不到查询日期,为了更好的用户体验,可以在查询结果处也显示当前的查询日期,通常格式和日期组件中显示的一致,且只有在点击查询后才更新。下图展示的是在点击查询后,表格后面的日期同步为最新的查询日期:

二、使用方法及实现原理
1、多种日期类型切换
使用方法:html中RangePicker配置的参数是调用useDatePicker的返回,js部分通过简单配置即可实现
html
<Col>
<FormItem label="日期类型">
<Select v-model:value="params.datatype">
<SelectOption value="0">按分钟</SelectOption>
<SelectOption value="1">按小时</SelectOption>
<SelectOption value="2">按天</SelectOption>
<SelectOption value="3">按周</SelectOption>
<SelectOption value="4">按月</SelectOption>
<SelectOption value="5">按季度</SelectOption>
</Select>
</FormItem>
</Col>
<Col>
<FormItem name="queryTime" label="时间" >
<RangePicker
v-model:value="queryTime"
:format="format"
:show-time="showTime"
:picker="picker"
/>
</FormItem>
</Col>
javascript
// 初始化日期
const { queryTime, start, end, format, picker, showTime } = useDatePicker(params, {
watchKey: 'datatype',
componentType: 'rangePicker',
list: {
'0': {},
'1': {},
'2': {},
'3': {},
'4': {},
'5': {},
}
})
实现原理:封装的功能函数里,提供了丰富的常用配置,所以list对象里只要提供对应的key值即可使用对应的默认配置。这里的key值,可以是语义化的,如minute,hour,week等,这也是默认的key值,如果实际参数是数字字符串类型的如'0','1'等,可以通过map参数进行映射。这里默认添加了一个映射,所以可以直接使用数字字符串类型的key值。
如果默认的配置不满足需求,也可以在list列表对应的字段里进行配置,优先读取list列表里的配置。
javascript
const defaultConfig: IConfigList = {
minute: {
format: 'YYYY-MM-DD HH:mm',
picker: '',
showTime: { format: 'HH:mm' },
limit: {
value: 30,
unit: 'minute',
tip: '时间跨度不能超过30分钟',
},
start: dayjs().subtract(30, 'minute'),
},
hour: {
format: defaultHourFormat,
resultFormat: 'YYYYMMDDHH',
picker: '',
showTime: { format: 'HH' },
limit: {
value: 24,
unit: 'hour',
tip: '时间跨度不能超过24小时',
},
start: dayjs().startOf('date'),
},
day: {//...},
//...
};
function defaultWeekFormat(value) {
const weekFormat = 'MM/DD';
const date = dayjs(value);
return `${date.year()}年第${(date as any).week()}周 ${date
.startOf('week')
.format(weekFormat)} ~ ${date.endOf('week').format(weekFormat)}`;
}
//...
目前支持的配置项如下所示:
javascript
export type limitType = string | {
value: number;
tip: string; // 超出限制提示
unit: string; // 参考day.js difference 单位列表
}
interface IItem { // 日期类型配置参数
picker: string;
format: string | Function;
// 结果需要的格式(默认和format一致)
resultFormat?: string;
showTime?: Object | string;
// 下拉切换时支持改变组件类型
componentType?: 'rangePicker' | 'datePicker';
// 查询限制, 如限制31天,12个月等
limit?: limitType;
start?: string | Dayjs;
end?: string | Dayjs;
}
监听watchKey的值响应不同日期类型间切换并更新配置。
javascript
watch(
() => {
if(isReactive(params)) return params[config.watchKey]
return params.value[config.watchKey]
},
(value) => {
value = value || defaultKey
let item = config.list[value]
item && updateValue(value, item)
}
)
2、单一类型
使用方法:单一类型和多种类型切换场景的参数一致,使用起来更方便。
html
<Col>
<FormItem name="queryTime" label="时间">
<DatePicker
v-model:value="queryTime"
:format="format"
/>
</FormItem>
</Col>
javascript
// 初始化日期
const { queryTime, start, format } = useDatePicker(params, {
componentType: 'datePicker',
list: {
'2': {},
}
})
实现原理:直接取list配置的第一项作为默认值
javascript
let [defaultKey, defaultItem] = Object.entries(config.list)[0]
3、单一日期和日期范围之间切换
使用方法:html部分会判断使用DatePicker还是RangePicker,配置参数是一样的。js部分在调用useDatePicker时配置了componentType,表示默认使用的组件类型,list列表每个具体项里也可以配置此属性,表示当前日期类型下需要的组件类型。
list列表里除了可以使用默认类型,还支持自定义,只要自定义里面的配置字段和要求一致即可。
html
<Col>
<FormItem label="日期类型">
<Select v-model:value="params.datatype" placeholder="日期类型">
<SelectOption value="2">按天</SelectOption>
<SelectOption value="3">按周</SelectOption>
<SelectOption value="4">按月</SelectOption>
<SelectOption value="custom">自定义</SelectOption>
</Select>
</FormItem>
</Col>
<Col >
<FormItem name="queryTime" label="时间" v-if="params.datatype === 'custom'">
<RangePicker
v-model:value="queryTime"
:format="format"
:picker="picker"
/>
</FormItem>
<FormItem name="queryTime" label="时间" v-else>
<DatePicker
v-model:value="queryTime"
:format="format"
:picker="picker"
/>
</FormItem>
</Col>
javascript
// 初始化日期
const { queryTime, format, picker, judgeDate } = useDatePicker(params, {
watchKey: 'datatype',
componentType: 'datePicker',
list: {
'2': {},
'3': {
start: dayjs().subtract(1, 'week'),
},
'4': {
start: dayjs().subtract(0, 'month'),
},
'custom': {
format: 'YYYY-MM-DD',
pickerType: 'date',
componentType: 'rangePicker',
start: dayjs().subtract(6, 'day'),
end: dayjs().subtract(0, 'day'),
limit: {
limit: 31,
unit: 'day',
tip: '时间跨度不能超过31天',
},
},
},
});
实现原理:在初始化和切换类型更新时,通过判断componentType的值,来决定queryTime的值,componentType的值,优先从当前项的配置里面获取
javascript
const queryTime = componentType === 'datePicker' ?
ref<Dayjs>(dayjs(defaultStart)):
ref<[Dayjs, Dayjs]>([dayjs(defaultStart), dayjs(defaultEnd)])
4、日期范围校验
使用方法:在查询之前调用judgeDate()进行校验,校验通过再进行查询。
javascript
function query(bool = true) {
judgeDate().then(() => {
// 实际查询
})
}
实现原理:内部实现了判断方法,通过配置的limit项判断是否符合需求,不符合则给出错误提示
javascript
// 判断日期是否超出限制范围
function judgeDate() {
return new Promise<void>((resolve, reject) => {
if(typeof toValue(limit) !== 'object' || !Array.isArray(queryTime.value)) resolve()
const { value, tip, unit } = toValue(limit) as any
if(dayjs(queryTime.value[1]).diff(dayjs(queryTime.value[0]), unit) > value ) {
message.error(tip)
reject()
} else {
resolve()
}
})
}
5、同步展示日期
使用方法:需要更新时调用getDate()拿最新日期数据,页面同步更新
html
<span>( {{ dateTips }} ) </span>
js
const dateTips = ref(getDate())
function query(bool = true) {
judgeDate().then(() => {
// todo something
dateTips.value = getDate()
})
}
实现原理:内部实现了格式化日期的方法
javascript
// 可用于页面日期展示,比如展示表格或图查询时间等
function getDate(sep = '~') {
let _format: string | Function = toValue(format)
if(!queryTime.value) return sep
if(Array.isArray(queryTime.value)) {
if(typeof _format === 'function') {
return `${(_format as Function)(queryTime.value[0])} ${sep} ${ (_format as Function)(queryTime.value[1])}`
}
return `${dayjs(queryTime.value[0]).format(_format)} ${sep} ${dayjs(queryTime.value[1]).format(_format)}`
} else {
if(typeof _format === 'function') {
return (_format as Function)(queryTime.value)
}
return dayjs(queryTime.value).format(_format)
}
}