react+ts手写cron表达式转换组件

news2025/7/19 10:09:26

前言

最近在写的一个分布式调度系统,后端同学需要让我传入cron表达式,给调度接口传参。我去了学习了解了cron表达式的用法,发现有3个通用的表达式刚好符合我们的需求:

需求

  1. 每天 xx 的时间:

0 11 20 * * ?

上面是每天20:11的cron表达式

  1. 每周的 xxx 星期 的 xxx 时间

0 14 20 * * WED,THU

上面是 每周星期三,星期四20:14的cron表达式

  1. 每周的 xxx号 的 xxx时间

0 15 20 3,7 * ?

上面是 每月的3,7号20:15的cron表达式

这三个表达式刚好符合我们的需求,并且每个符号表达的意思也很直观。那么,话不多说,直接开写!

环境

  • react

    • 我的版本:“react”: “18.2.0”,用的函数组件+react hooks
  • moment

    • npm install moment
  • semi-design(组件库)

    • npm install semi-design
  • typescript

实现

数据

utils下创建cron.ts,对组件所用到的数据进行统一的管理:

export const dayOfTheWeekData = [
  { key: 'MON', label: '星期一' },
  { key: 'TUE', label: '星期二' },
  { key: 'WED', label: '星期三' },
  { key: 'THU', label: '星期四' },
  { key: 'FRI', label: '星期五' },
  { key: 'SAT', label: '星期六' },
  { key: 'SUN', label: '星期天' }
];

export const dayOfTheWeekOption = [
  { key: '1', label: '星期一' },
  { key: '2', label: '星期二' },
  { key: '3', label: '星期三' },
  { key: '4', label: '星期四' },
  { key: '5', label: '星期五' },
  { key: '6', label: '星期六' },
  { key: '7', label: '星期天' }
];

export const monthOption = [
  { key: '1', label: '一月' },
  { key: '2', label: '二月' },
  { key: '3', label: '三月' },
  { key: '4', label: '四月' },
  { key: '5', label: '五月' },
  { key: '6', label: '六月' },
  { key: '7', label: '七月' },
  { key: '8', label: '八月' },
  { key: '9', label: '九月' },
  { key: '10', label: '十月' },
  { key: '11', label: '十一月' },
  { key: '12', label: '十二月' }
];

//获取dayOfTheMonthOption的每月对象
function getDayOfTheMonthOption() {
  const days = [];
  for (let i = 1; i < 32; i += 1) {
    days.push({ key: i.toString(), label: i.toString().concat('号') });
  }
  return days;
}

export const dayOfTheMonthOption = getDayOfTheMonthOption();

组件

到了组件的具体实现,个人感觉我写的注释挺全的,就单挑几个核心重点讲下:

时间转换函数(handleTimeChange)

  //时间选择函数
  const handleTimeChange = (time: moment.Moment | null) => {
    setSelectTime(time);
    if (!time) return;
    const currentCron = expression ? expression.split(' ') : [];
    const [seconds, , , dayOfMonth, month1, dayOfWeek] = currentCron;
    const minutes = moment(time).minutes().toString(); //获取分钟
    const hours = moment(time).hours().toString(); //获取小时
    let result = null;
    if (!Number.isNaN(Number(hours)) && !Number.isNaN(Number(minutes))) {
      const minutesAndHour = seconds
        .concat(space)
        .concat(minutes)
        .concat(space)
        .concat(hours)
        .concat(space);
      if (defaultTimeType === 'everyDay') result = minutesAndHour.concat('* * ?');
      if (defaultTimeType !== 'everyDay')
        result = minutesAndHour
          .concat(dayOfMonth)
          .concat(space)
          .concat(month1)
          .concat(space)
          .concat(dayOfWeek);
    }
    if (result) onChange?.(result);
    setExpression(result);
  };
  1. 使用moment函数将time转成数字类型
    1. minutes = moment(time).minutes().toString(); //获取分钟
    2. hours = moment(time).hours().toString(); //获取小时
  2. 获取时间cron字符串minutesAndHour:
const minutesAndHour = seconds.concat(space).concat(minutes).concat(space).concat(hours).concat(space);
  1. 拼接得到完整的cron表达式:
    1. defaultTimeType === ‘everyDay’

result = minutesAndHour.concat(‘* * ?’);

  1. defaultTimeType !== ‘everyDay’
result = minutesAndHour.concat(dayOfMonth).concat(space).concat(month1).concat(space).concat(dayOfWeek);

日期转换函数(handleSelectChange)

setSelectedValue(data);
const selectValues = data.join(',');
const currentCron = expression ? expression.split(' ') : [];
const [seconds, minutes, hours, dayOfMonth, month1, dayOfWeek] = currentCron;
let result = '';
if (defaultTimeType === 'everyWeek') {
  result = seconds
    .concat(space)
    .concat(minutes)
    .concat(space)
    .concat(hours)
    .concat(space)
    .concat(dayOfMonth)
    .concat(space)
    .concat(month1)
    .concat(space)
    .concat(selectValues);
}
if (defaultTimeType === 'everyMonth') {
  result = seconds
    .concat(space)
    .concat(minutes)
    .concat(space)
    .concat(hours)
    .concat(space)
    .concat(data.length ? selectValues : '*')
    .concat(space)
    .concat(month1)
    .concat(space)
    .concat(dayOfWeek);
}
if (selectTime) onChange?.(result);
setExpression(result);
  1. defaultTimeType === ‘everyWeek’
result = seconds.concat(space).concat(minutes).concat(space).concat(hours).concat(space).concat(dayOfMonth).concat(space).concat(month1).concat(space).concat(selectValues);
  1. defaultTimeType === ‘everyMonth’
result = seconds.concat(space).concat(minutes).concat(space).concat(hours).concat(space).concat(data.length ? selectValues : '*').concat(space).concat(month1).concat(space).concat(dayOfWeek);

组件全部代码(CronInput)

import { ConfigProvider, TimePicker } from '@douyinfe/semi-ui';
import { Fragment, useState } from 'react';
import { Select } from '@douyinfe/semi-ui';
import moment from 'moment';
//引入数据
import { dayOfTheMonthOption, dayOfTheWeekData } from '@/utils/cron';

const { Option } = Select;
const format = 'HH:mm';
const defaultCron = '0 * * * * ?';
const space = ' '; //空格
//类型选择
const timeTypes = [
  { key: 'everyDay', label: '每天' },
  { key: 'everyWeek', label: '每周' },
  { key: 'everyMonth', label: '每月' }
];

interface Props {
  onChange?: (cron?: string) => void;
}
const CronInput: React.FC<Props> = ({ onChange }) => {
  const [defaultTimeType, setDefaultTimeType] = useState(timeTypes[0].key); //选择类型
  const [selectedValue, setSelectedValue] = useState<[]>([]); //日期,多选数组
  const [selectTime, setSelectTime] = useState<any>(null); //时间
  const [expression, setExpression] = useState<string | null>(defaultCron); //bzd

  //类型选择函数
  const handleTimeTypeChange = (selectValue: string) => {
    setDefaultTimeType(selectValue);
    setSelectTime(null);
    setSelectedValue([]);
    setExpression(defaultCron);
  };

  //时间选择函数
  const handleTimeChange = (time: moment.Moment | null) => {
    setSelectTime(time);
    if (!time) return;
    const currentCron = expression ? expression.split(' ') : [];
    const [seconds, , , dayOfMonth, month1, dayOfWeek] = currentCron;
    const minutes = moment(time).minutes().toString(); //获取分钟
    const hours = moment(time).hours().toString(); //获取小时
    let result = null;
    if (!Number.isNaN(Number(hours)) && !Number.isNaN(Number(minutes))) {
      const minutesAndHour = seconds
        .concat(space)
        .concat(minutes)
        .concat(space)
        .concat(hours)
        .concat(space);
      if (defaultTimeType === 'everyDay') result = minutesAndHour.concat('* * ?');
      if (defaultTimeType !== 'everyDay')
        result = minutesAndHour
          .concat(dayOfMonth)
          .concat(space)
          .concat(month1)
          .concat(space)
          .concat(dayOfWeek);
    }
    if (result) onChange?.(result);
    setExpression(result);
  };

  const handleSelectChange = (data: []) => {
    setSelectedValue(data);
    const selectValues = data.join(',');
    const currentCron = expression ? expression.split(' ') : [];
    const [seconds, minutes, hours, dayOfMonth, month1, dayOfWeek] = currentCron;
    let result = '';
    if (defaultTimeType === 'everyWeek') {
      result = seconds
        .concat(space)
        .concat(minutes)
        .concat(space)
        .concat(hours)
        .concat(space)
        .concat(dayOfMonth)
        .concat(space)
        .concat(month1)
        .concat(space)
        .concat(selectValues);
    }
    if (defaultTimeType === 'everyMonth') {
      result = seconds
        .concat(space)
        .concat(minutes)
        .concat(space)
        .concat(hours)
        .concat(space)
        .concat(data.length ? selectValues : '*')
        .concat(space)
        .concat(month1)
        .concat(space)
        .concat(dayOfWeek);
    }
    if (selectTime) onChange?.(result);
    setExpression(result);
  };

  const RenderSelect = ({
    placeholder,
    data = []
  }: {
    placeholder: string;
    data: { key: string; label: string }[];
  }) => {
    return (
      <Fragment>
        <Select
          multiple
          placeholder={placeholder}
          onChange={(val: any) => handleSelectChange(val)}
          style={{ marginRight: '16px', width: 'auto' }}
          value={selectedValue}
        >
          {data.map((item: { key: string; label: string }) => (
            <Option key={item.key} value={item.key}>
              {item.label}
            </Option>
          ))}
        </Select>
        <ConfigProvider>
          <TimePicker
            value={selectTime && moment(selectTime, format).toDate()}
            format={format}
            placeholder="请选择时间"
            onChange={(val: any) => handleTimeChange(val)}
          />
        </ConfigProvider>
      </Fragment>
    );
  };
  return (
    <>
      <div className={'cron'}>
        <Select
          // role="cron-type"
          style={{ marginRight: '16px', width: 'auto' }}
          placeholder="请选择类型"
          onChange={(val: any) => handleTimeTypeChange(val)}
          value={defaultTimeType}
        >
          {timeTypes.map((item) => (
            <Option key={item.key} value={item.key}>
              {' '}
              {item.label}
            </Option>
          ))}
        </Select>
        {defaultTimeType === 'everyDay' && (
          <ConfigProvider>
            <TimePicker
              value={selectTime && moment(selectTime, format).toDate()}
              format={format}
              placeholder="请选择时间"
              onChange={(val: any) => handleTimeChange(val)}
            />
          </ConfigProvider>
        )}
        {defaultTimeType === 'everyWeek' && (
          <RenderSelect data={dayOfTheWeekData} placeholder="请选择星期" />
        )}
        {defaultTimeType === 'everyMonth' && (
          <RenderSelect data={dayOfTheMonthOption} placeholder="请选择日期" />
        )}
      </div>
    </>
  );
};

export default CronInput;

使用与效果

使用

使用方法很简单,接收onChange传来的cron表达式即可:

const App: FC<IProps> = (props) => {
  const { datas = [] } = props;
  let [value, setValue] = useState<string>();
  return (
    <div>
      <CronInput onChange={(cron) => setValue(cron)} />
      <div>{value}</div>
    </div>
  );
};

效果

  1. 每天

image.png

  1. 每周

image.png

  1. 每月

image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1103565.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jQuery+AJAX请求的统一封装

记录一下使用jQueryAJAX对http请求的统一封装 很久都没有使用jquery和ajax的组合了&#xff0c;这里记录一下jquery和ajax的组合简单封装 将来或许有机会重新启用这个组合 新建jquery.request.js&#xff1b;demo目录结构如下 const baseURL http://127.0.0.1:8116;// con…

4K壁纸小程序源码 全内容自动采集

全内容自动采集 4K壁纸小程序源码&#xff0c;带流量主。用的都是一个接口&#xff0c;不过这个不知是谁改的&#xff0c;成了LSP版&#xff0c;是真色啊&#xff0c;专搜小姐姐。 4K壁纸&#xff0c;静态壁纸&#xff0c;头像等都有保留&#xff0c;界面广告位很多&#xff0c…

List小练习,实现添加图书,并且有序遍历

SuppressWarnings({"all"})public static void main(String[] args) {List list new LinkedList(); // List list new Vector(); // List list new ArrayList();list.add(new Book1("红楼小梦",35.5,"曹雪芹"));list.add(new B…

算法-堆/归并排序-排序链表

算法-堆/归并排序-排序链表 1 题目概述 1.1 题目出处 https://leetcode.cn/problems/sort-list/description/?envTypestudy-plan-v2&envIdtop-interview-150 1.2 题目描述 2 优先级队列构建大顶堆 2.1 思路 优先级队列构建小顶堆链表所有元素放入小顶堆依次取出堆顶…

【Java 进阶篇】JavaScript BOM History 详解

当用户浏览网页时&#xff0c;可以使用JavaScript的BOM (Browser Object Model)中的History对象来访问浏览器的历史记录。这个对象允许您在不更改页面的情况下导航到不同的历史记录项&#xff0c;或者查看有关用户访问过的页面的信息。 在本篇博客中&#xff0c;我们将围绕Jav…

IIS7.0解析漏洞

IIS7.0解析漏洞 实验环境 windows server 2008 r2&#xff08;x64&#xff09; IIS 7 phpStudyIIS 2016版本 漏洞条件 1. php.ini里的cgi.cgi_pathinfo1 2. IIS7在Fast-CGI运行模式下 漏洞复现 先搭建IIS7 出现如下界面安装成功 安装phpstudy &#xff0c;使用较老的版…

通过核密度分析工具建模,基于arcgis js api 4.27 加载gp服务

一、通过arcmap10.2建模&#xff0c;其中包含三个参数 注意input属性&#xff0c;选择数据类型为要素类&#xff1a; 二、建模之后&#xff0c;加载数据&#xff0c;执行模型&#xff0c;无错误的话&#xff0c;找到执行结果&#xff0c;进行发布gp服务 注意&#xff0c;发布g…

Vue3语法-双向绑定

点击加入精英计划可以加入 点击名字可以删除 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><!-- vue.js --><script src"https://unpkg.com/vue3/dist/vue.glob…

Nested loop(PostgreSQL 14 Internals翻译版)

连接类型和方法 连接是SQL语言的一个关键特性;它们是其力量和灵活性的基础。行集(要么直接从表中检索&#xff0c;要么作为某些其他操作的结果接收)总是成对连接。 有几种类型的连接&#xff1a; 内连接。 内连接(指定为“INNER JOIN”或简称为“JOIN”)由满足特定连接条件的…

单一职责模式

三、单一职责模式 单一职责模式概念1、装饰着模式&#xff08;Decorator&#xff09;动机&#xff08;Motivation)模式定义代码具体实现要点总结 2、桥模式&#xff08;Bridge&#xff09;动机 &#xff08;Motivation)模式定义代码具体实现要点总结 单一职责模式概念 在软件组…

MTK APP实现动态修改logo和开机动画

MTK APP实现动态修改logo和开机动画 前言一、修改对新分区的权限1.修改开机动画对新分区的权限2.修改系统APP对新分区的权限3.修改SE权限,不然编译会报错4.修改开机动画文件&#xff0c;让其加载新分区中的文件 二、系统APP代码使用1.系统app修改开机logo2.系统app修改开机动画…

设计模式:工厂方法模式(C#、JAVA、JavaScript、C++、Python、Go、PHP):

本节主要介绍设计模式中的工厂方法模式。 简介&#xff1a; 工厂方法模式&#xff0c;它是对简单工厂模式的进一步抽象化&#xff0c;其好处是可以使系统在不修改原来代码的情况下引进新的产品&#xff0c;即满足开闭原则。 它定义了一个用于创建对象的工厂接口&#xff0c;让…

Nginx正向代理,反向代理,负载均衡

Nginx正向代理&#xff0c;反向代理&#xff0c;负载均衡 Nginx当中有两种代理方式&#xff1a; 七层代理&#xff08;http协议&#xff09; 四层代理&#xff08;tcp/udp流量转发&#xff09; 七层代理&#xff1a;七层代理&#xff0c;代理的是http的请求和响应 客户端请求…

Redis RDB持久化

前言 我们知道 Redis 之所以快&#xff0c;很大程度是因为它的数据直接放在内存里&#xff0c;而内存是易失性存储器&#xff0c;只有通电才存储数据&#xff0c;断电数据就会丢失。 这个时候就要看你的应用场景了&#xff0c;如果你只是拿 Redis 做关系型数据库的缓存&#x…

SpringBoot实现SSMP整合

一、整合JUnit 1、Spring 整合 JUnit 核心注解有两个&#xff1a; RunWith(SpringJUnit4ClassRunner.class) 是设置Spring专用于测试的类运行器&#xff08;Spring程序执行程序有自己的一套独立的运行程序的方式&#xff0c;不能使用JUnit提供的类运行方式&#xff09;Conte…

Deep Learning(0-14草履虫)

深度学习解决的问题 自动提取出最合适的特征 深度学习应用 神经网络基础 损失函数 前向传播 反向传播 绿色字体为正向传播i输入&#xff0c;红色字体为反向传播梯度 MAX门单元只把梯度传给最大的 神经网络整体架构 激活函数 隐藏层激活函数 一般选择Relu&#xff0c;sigmoid会…

STM32 IWDGWWDG

STM32 IWDG&WWDG 启动看门狗之后&#xff0c;看门狗是不能再被关闭的&#xff0c;除非发生复位。 IWDG独立看门狗 独立看门狗配置流程 开启LSI时钟&#xff0c;只有LSI时钟开启了&#xff0c;独立看门狗才能运行。 但是开启LSI的代码&#xff0c;并不需要我们来写&#xf…

【23种设计模式】装饰器模式

个人主页&#xff1a;金鳞踏雨 个人简介&#xff1a;大家好&#xff0c;我是金鳞&#xff0c;一个初出茅庐的Java小白 目前状况&#xff1a;22届普通本科毕业生&#xff0c;几经波折了&#xff0c;现在任职于一家国内大型知名日化公司&#xff0c;从事Java开发工作 我的博客&am…

WinSCP 集成 putty(也可以其他Terminal客户端)

putty 安装 官网安装地址 WinSCP集成putty&#xff08;也可以其他Terminal客户端&#xff09; 扩展 WinSCP是什么&#xff1f; WinSCP&#xff08;Windows Secure Copy Protocol&#xff09;是一个用于 Windows 操作系统的开源的 SFTP&#xff08;SSH File Transfer Protoc…

【Unity HDRP渲染管线下的WorleyUtilities文件,“Hash”函数】

Unity HDRP内置文件WorleyUtilities WorleyUtilities文件路径如下:文件代码如下然后转译到ShaderLab中:存档:WorleyUtilities文件路径如下: D:…\Library\PackageCache\com.unity.render-pipelines.high-definition@14.0.8\Runtime\Lighting\VolumetricClouds\WorleyUtili…