因为找了半天都没找到合适的,索性自己写了个Cron表达式组件

因为找了半天都没找到合适的,索性自己写了个Cron表达式组件

最近项目中需要一个corn表达式的组件,然后去网上找了资源,发现个qnn-react-cron,但是和我项目的环境不兼容,导入进来无法使用,后面又折腾了一阵时间,都没找到合适的,最终索性,自己写一个了,虽然浪费时间,但是也是无奈之举😂

一、了解Cron表达式

cron表达式是用以控制固定时间、日期或者是间隔定期来执行任务,非常适合安排重复性任务,比如:发送消息通知、监控数据等等,一般的格式:0 0 18 ? * 4(意思:周四晚上8点发通知)。

Cron字符

Cron表达式的字符串一般都是由简单的几个字符和空格拼接而成,一般会有4-6个空格,空格隔开的那部分就会形成时间子配置。

子配置 是否必填 范围 允许的特殊字符 说明
0-59 * , - / 不常用
0-59 * , - /
小时 0-23 * , - /
几号 1-31 * , - / ? L W
月份 1-12或JAN – DEC * , - /
星期几 0-7或SUN-SAT * , - / ? L # 0和7是周六,1是周日
1970-2099 * , - / 不常用

特殊字符说明

* :匹配任意的单个值,比如每秒、每分钟、每小时等。

, :用以分隔列表项,比如星期:2,4,6表示每周的一三五匹配。

- :定义一个范围区间,小时:6-10 表示匹配早上6点到早上10点。

/ :定义间隔时间执行,小时:6/2 表示从早上6点开始,每2小时执行一次任务。

? :只用于日期项和星期项,表示互斥关系,日期如果有值,星期则就要用?,反之同理,否则配置项会失效。

L :只用于日期项和星期项,表示一个月倒数第几天或者一个星期倒数第几天,5L表示倒数第五天。

W :只用于日期项,表示距离工作日(周一到周五)最近的一天。

# :只用于星期项,5#3对应于每个月的第三个星期五。

二、React Hooks中实现Cron表达式组件

本案例中的环境:React:18.2.0 ; antd:4.24.5typescript;

思路核心:就是根据用户点击不同的按钮来判断生成cron表达式,方法差不多就是根据上面的特殊字符来进行判断的,就是操作会很繁琐,我个人写的也没有将上面所有的情况全部囊括进去,仅满足的是日常非特殊业务完成而设计的,各位小伙伴可以依据个人实际情况,进行修改和添加,以创建更完善的版本;

废话不多说,直接上代码:

import React, { useEffect, useState, forwardRef, useImperativeHandle } from 'react'
import { Input, InputNumber, Tabs, Radio, Row, Col, RadioChangeEvent, Card, Select } from 'antd'
import { CheckboxValueType } from 'antd/lib/checkbox/Group'
// 样式导入
import classes from './index.module.scss'
// 输出cron最近执行时间函数
import { cronRunTime } from './cronRunTime'
// cron 类型
const cronType = ['second', 'minute', 'hour', 'day', 'month', 'week']
// 按钮基本样式
const radioStyle = {
display: 'block',
lineHeight: 2,
}
const Cron = (props: {}, ref: any) => {
const [cronText, setCronText] = useState('')
const [radioValue, setRadioValue] = useState<any>({
second: 1,
minute: 1,
hour: 1,
day: 1,
month: 1,
week: 1,
})
const [periodValue, setPeriodValue] = useState<any>({
second: { max: 1, min: 1 },
minute: { max: 1, min: 1 },
hour: { max: 1, min: 1 },
day: { max: 1, min: 1 },
month: { max: 1, min: 1 },
week: { max: 3, min: 2 },
})
const [loopValue, setLoopValue] = useState<any>({
second: { start: 1, end: 1 },
minute: { start: 1, end: 1 },
hour: { start: 1, end: 1 },
day: { start: 1, end: 1 },
month: { start: 1, end: 1 },
week: { start: 1, end: 1 },
})
const [cronParams, setCronParams] = useState<any>({
second: '*',
minute: '*',
hour: '*',
day: '*',
month: '*',
week: '*',
})
const [second, setSecond] = useState<number[]>([])
const [minute, setMinute] = useState<number[]>([])
const [hour, setHour] = useState<number[]>([])
const [day, setDay] = useState<number[]>([])
const [month, setMonth] = useState<number[]>([])
const [week, setWeek] = useState<number[]>([])
// 最近运行时间
const [resultTime, setResultTime] = useState<string[]>([])
useEffect(() => {
createCron()
}, [radioValue, periodValue, loopValue])
// ref 上传函数
useImperativeHandle(ref, () => ({
refGetCron,
resetCronState,
}))
// ref 获取值
const refGetCron = () => cronText
// 重置设置初始值
const resetCronState = () => {
setRadioValue({
second: 1,
minute: 1,
hour: 1,
day: 1,
month: 1,
week: 1,
})
}
//生成cron
const createCron = () => {
let changeCron = {} as any
cronType.forEach((item) => (changeCron = { ...changeCron, ...cronGenerator(item) }))
const { second, minute, hour, day, month, week } = changeCron
const cronText = second + ' ' + minute + ' ' + hour + ' ' + day + ' ' + month + ' ' + week
setCronText(cronText)
setResultTime(cronRunTime(cronText))
}
/**
* cron生成器
* @param type
*/
const cronGenerator = (type: string) => {
let srv = radioValue[type]
let period = periodValue[type]
let loop = loopValue[type]
let param = cronParams[type]
let data = ''
switch (srv) {
case 1:
data = '*'
break
case 2:
data = '?'
break
case 'point':
for (let v of param) {
data = data + v + ','
}
data = data.substring(0, data.length - 1)
break
case 'period':
data = period.min + '-' + period.max
break
case 'loop':
data = loop.start + '/' + loop.end
break
default:
data = '*'
}
return cronItemGenerator(type, data)
}
/**
* 对象生成器
* @param type
* @param data
* @returns {{second: *}|{minute: *}}
*/
const cronItemGenerator = (type: string, data: string): { [key: string]: string } => {
switch (type) {
case 'second':
return { second: data }
case 'minute':
return { minute: data }
case 'hour':
return { hour: data }
case 'day':
return { day: data }
case 'month':
return { month: data }
case 'week':
return { week: data }
default:
return {}
}
}
/**
* 生成 日期选择时间 options
* @param num
* @returns {lable:number,value:number}[]
*/
const createSelectOption = (num: number): { label: number; value: number }[] => {
let obj = [] as { label: number; value: number }[]
for (let i = 1; i <= num; i++) {
obj.push({
label: i,
value: i,
})
}
return obj
}
/**
* 生成 week options
* @param num
* @returns {lable:string,value:number}[]
*/
const createWeekSelect = (num: number): { label: string; value: number }[] => {
let obj = [] as { label: string; value: number }[]
const weeks = ['', '一', '二', '三', '四', '五', '六', '日']
for (let i = 1; i <= num; i++) {
obj.push({
label: `星期${weeks[i]}`,
value: i + 1 === 8 ? 1 : i + 1,
})
}
return obj
}
// 单选按钮选择
const handleRadioChange = (e: RadioChangeEvent, type: string) => {
switch (type) {
case 'week':
setRadioValue({ ...radioValue, day: 2, ...cronItemGenerator(type, e.target.value) })
break
case 'day':
setRadioValue({ ...radioValue, week: 2, ...cronItemGenerator(type, e.target.value) })
break
default:
setRadioValue({
...radioValue,
...cronItemGenerator(type, e.target.value),
})
break
}
}
// 指定时间选择
const handleCheckboxChange = (
checkedValues: string | CheckboxValueType[],
type: string,
fun: Function,
) => {
// select双向绑定
fun(checkedValues)
// 选择时间自动跳到 指定
setRadioValue({
...radioValue,
[type]: 'point',
})
setCronParams({
...cronParams,
...cronItemGenerator(type, checkedValues as string),
})
}
// InputNumber按钮选择
const handlePeriodChange = (e: number | null, type: string, tar: string) => {
let data = periodValue
data[type] = tar == 'max' ? { max: e, min: data[type].min } : { max: data[type].max, min: e }
setPeriodValue({
...periodValue,
...cronItemGenerator(type, data[type]),
})
}
// InputNumber按钮选择
const handleLoopChange = (e: number | null, type: string, tar: string) => {
let data = loopValue
data[type] =
tar == 'start' ? { start: e, end: data[type].end } : { start: data[type].start, end: e }
setLoopValue({
...loopValue,
...cronItemGenerator(type, data[type]),
})
}
return (
<div className={classes['cron-com']}>
<Tabs
type="card"
items={[
{
label: ``,
key: '1',
children: (
<Card className={classes['card-top']}>
<Radio.Group
onChange={(e) => {
handleRadioChange(e, 'second')
}}
value={radioValue['second']}
>
<Radio style={radioStyle} value={1}>
每秒执行
</Radio>
<Radio style={radioStyle} value="period">
周期从
<InputNumber
size="small"
min={0}
max={58}
defaultValue={0}
onChange={(e) => handlePeriodChange(e, 'second', 'min')}
/>
-
<InputNumber
size="small"
min={1}
max={59}
defaultValue={1}
onChange={(e) => handlePeriodChange(e, 'second', 'max')}
/>
秒
</Radio>
<Radio style={radioStyle} value="loop"><InputNumber
size="small"
min={0}
max={58}
defaultValue={0}
onChange={(e) => handleLoopChange(e, 'second', 'start')}
/>
秒开始,每
<InputNumber
size="small"
min={1}
max={59}
defaultValue={1}
onChange={(e) => handleLoopChange(e, 'second', 'end')}
/>
秒执行一次
</Radio>
<Row>
<Radio style={radioStyle} value="point">
指定
</Radio>
<Col span={18}>
<Select
value={second}
style={{ width: '80%', marginTop: 7 }}
mode="multiple"
allowClear
placeholder="请选择秒"
onChange={(value) => handleCheckboxChange(value, 'second', setSecond)}
options={createSelectOption(59)}
/>
</Col>
</Row>
</Radio.Group>
</Card>
),
},
{
label: `分`,
key: '2',
children: (
<Card className={classes['card-top']}>
<Radio.Group
value={radioValue['minute']}
onChange={(e) => handleRadioChange(e, 'minute')}
>
<Radio style={radioStyle} value={1}>
每分执行
</Radio>
<Radio style={radioStyle} value="period">
周期从
<InputNumber
size="small"
min={0}
max={58}
defaultValue={0}
onChange={(e) => handlePeriodChange(e, 'minute', 'min')}
/>
-
<InputNumber
size="small"
min={2}
max={59}
defaultValue={2}
onChange={(e) => handlePeriodChange(e, 'minute', 'max')}
/>
分
</Radio>
<Radio style={radioStyle} value="loop"><InputNumber
size="small"
min={0}
max={58}
defaultValue={0}
onChange={(e) => handleLoopChange(e, 'minute', 'start')}
/>
分开始,每
<InputNumber
size="small"
min={1}
max={58}
defaultValue={1}
onChange={(e) => handleLoopChange(e, 'minute', 'end')}
/>
分执行一次
</Radio>
<Row>
<Radio style={radioStyle} value="point">
指定
</Radio>
<Col span={18}>
<Select
value={minute}
style={{ width: '80%', marginTop: 7 }}
mode="multiple"
allowClear
placeholder="请选择分钟"
onChange={(value) => handleCheckboxChange(value, 'minute', setMinute)}
options={createSelectOption(59)}
/>
</Col>
</Row>
</Radio.Group>
</Card>
),
},
{
label: `时`,
key: '3',
children: (
<Card className={classes['card-top']}>
<Radio.Group
onChange={(e) => handleRadioChange(e, 'hour')}
value={radioValue['hour']}
>
<Radio style={radioStyle} value={1}>
每小时执行
</Radio>
<Radio style={radioStyle} value="period">
周期从
<InputNumber
size="small"
min={0}
max={22}
defaultValue={0}
onChange={(e) => handlePeriodChange(e, 'hour', 'min')}
/>
-
<InputNumber
size="small"
min={1}
max={23}
defaultValue={1}
onChange={(e) => handlePeriodChange(e, 'hour', 'max')}
/>
时
</Radio>
<Radio style={radioStyle} value="loop"><InputNumber
size="small"
min={0}
max={22}
defaultValue={0}
onChange={(e) => handleLoopChange(e, 'hour', 'start')}
/>
时开始,每
<InputNumber
size="small"
min={1}
max={22}
defaultValue={1}
onChange={(e) => handleLoopChange(e, 'hour', 'end')}
/>
时执行一次
</Radio>
<Row>
<Radio style={radioStyle} value="point">
指定
</Radio>
<Col span={18}>
<Select
value={hour}
style={{ width: '80%', marginTop: 7 }}
mode="multiple"
allowClear
placeholder="请选择小时"
onChange={(value) => handleCheckboxChange(value, 'hour', setHour)}
options={createSelectOption(23)}
/>
</Col>
</Row>
</Radio.Group>
</Card>
),
},
{
label: `日`,
key: '4',
children: (
<Card className={classes['card-top']}>
<Radio.Group
onChange={(e) => handleRadioChange(e, 'day')}
value={radioValue['day']}
>
<Radio style={radioStyle} value={1}>
每日执行
</Radio>
<Radio style={radioStyle} value={2}>
不指定
</Radio>
<Radio style={radioStyle} value="period">
周期从
<InputNumber
size="small"
min={1}
max={30}
defaultValue={1}
onChange={(e) => handlePeriodChange(e, 'day', 'min')}
/>
-
<InputNumber
size="small"
min={2}
max={31}
defaultValue={2}
onChange={(e) => handlePeriodChange(e, 'day', 'max')}
/>
日
</Radio>
<Radio style={radioStyle} value="loop"><InputNumber
size="small"
min={1}
max={31}
defaultValue={1}
onChange={(e) => handleLoopChange(e, 'day', 'start')}
/>
日开始,每
<InputNumber
size="small"
min={1}
max={31}
defaultValue={1}
onChange={(e) => handleLoopChange(e, 'day', 'end')}
/>
日执行一次
</Radio>
<Row>
<Radio style={radioStyle} value="point">
指定
</Radio>
<Col span={18}>
<Select
value={day}
style={{ width: '80%', marginTop: 7 }}
mode="multiple"
allowClear
placeholder="请选择日期"
onChange={(value) => handleCheckboxChange(value, 'day', setDay)}
options={createSelectOption(23)}
/>
</Col>
</Row>
</Radio.Group>
</Card>
),
},
{
label: `月`,
key: '5',
children: (
<Card className={classes['card-top']}>
<Radio.Group
onChange={(e) => handleRadioChange(e, 'month')}
value={radioValue['month']}
>
<Radio style={radioStyle} value={1}>
每月执行
</Radio>
<Radio style={radioStyle} value={2}>
不指定
</Radio>
<Radio style={radioStyle} value="period">
周期从
<InputNumber
size="small"
min={1}
max={11}
defaultValue={1}
onChange={(e) => handlePeriodChange(e, 'month', 'min')}
/>
-
<InputNumber
size="small"
min={2}
max={12}
defaultValue={2}
onChange={(e) => handlePeriodChange(e, 'month', 'max')}
/>
月
</Radio>
<Radio style={radioStyle} value="loop"><InputNumber
size="small"
min={1}
max={12}
defaultValue={1}
onChange={(e) => handleLoopChange(e, 'month', 'start')}
/>
月开始,每
<InputNumber
size="small"
min={1}
max={12}
defaultValue={1}
onChange={(e) => handleLoopChange(e, 'month', 'end')}
/>
月执行一次
</Radio>
<Row>
<Radio style={radioStyle} value="point">
指定
</Radio>
<Col span={18}>
<Select
value={month}
style={{ width: '80%', marginTop: 7 }}
mode="multiple"
allowClear
placeholder="请选择月份"
onChange={(value) => handleCheckboxChange(value, 'month', setMonth)}
options={createSelectOption(12)}
/>
</Col>
</Row>
</Radio.Group>
</Card>
),
},
{
label: `周`,
key: '6',
children: (
<Card className={classes['card-top']}>
<Radio.Group
onChange={(e) => handleRadioChange(e, 'week')}
value={radioValue['week']}
>
<Radio style={radioStyle} value={1}>
每周执行
</Radio>
<Radio style={radioStyle} value={2}>
不指定
</Radio>
<Row>
<Col>
<Radio style={radioStyle} value="period">
周期从 周
</Radio>
</Col>
<Col>
<Select
style={{ marginTop: 7 }}
allowClear
defaultValue={2}
placeholder="请选择周"
onChange={(e) => handlePeriodChange(e, 'week', 'min')}
options={createWeekSelect(7)}
/>
</Col>
<Col className={classes['week-font']}>- 周</Col>
<Col>
<Select
style={{ marginTop: 7 }}
allowClear
defaultValue={3}
placeholder="请选择周"
onChange={(e) => handlePeriodChange(e, 'week', 'max')}
options={createWeekSelect(7)}
/>
</Col>
</Row>
<Row>
<Radio style={radioStyle} value="point">
指定
</Radio>
<Col span={18}>
<Select
value={week}
style={{ width: '80%', marginTop: 7 }}
mode="multiple"
allowClear
placeholder="请选择周"
onChange={(value) => handleCheckboxChange(value, 'week', setWeek)}
options={createWeekSelect(7)}
/>
</Col>
</Row>
</Radio.Group>
</Card>
),
},
]}
/>
<Card className={classes['corn-time']}>
<Row gutter={10}>
<Col className={classes['titile']}>时间表达式:</Col>
<Col>
<Input
placeholder="生成Cron"
style={{ width: 400, marginTop: 10 }}
readOnly
value={cronText}
/>
</Col>
</Row>
</Card>
<Card className={classes['run-time']}>
<p className="title">最近五次运行时间</p>
<ul>
{resultTime.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</Card>
</div>
)
}
export default forwardRef(Cron)

三、效果图片

因为找了半天都没找到合适的,索性自己写了个Cron表达式组件

四、结语

因为代码确实有点长,字数已经快到两万四千个字符了,所以将最近运行时间的封装函数放到第二篇文章中😘

Cron表达式,如何生成最近运行时间?(开箱即用、无需配置

原文链接:https://juejin.cn/post/7248249730478817337 作者:我搁这敲代码呢

(0)
上一篇 2023年6月25日 上午10:46
下一篇 2023年6月25日 上午10:56

相关推荐

发表回复

登录后才能评论