react+ts手写cron表达式转换组件
前言
最近在写的一个分布式调度系统,后端同学需要让我传入cron表达式,给调度接口传参。我去了学习了解了cron表达式的用法,发现有3个通用的表达式刚好符合我们的需求:
需求
- 每天 xx 的时间:
0 11 20 * * ?
上面是每天20:11的cron表达式
- 每周的 xxx 星期 的 xxx 时间
0 14 20 * * WED,THU
上面是 每周星期三,星期四20:14的cron表达式
- 每周的 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);};
- 使用moment函数将time转成数字类型
- minutes = moment(time).minutes().toString(); //获取分钟
- hours = moment(time).hours().toString(); //获取小时
- 获取时间cron字符串minutesAndHour:
const minutesAndHour = seconds.concat(space).concat(minutes).concat(space).concat(hours).concat(space);
- 拼接得到完整的cron表达式:
- defaultTimeType === ‘everyDay’
result = minutesAndHour.concat(‘* * ?’);
- 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);
- defaultTimeType === ‘everyWeek’
result = seconds.concat(space).concat(minutes).concat(space).concat(hours).concat(space).concat(dayOfMonth).concat(space).concat(month1).concat(space).concat(selectValues);
- 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><Selectmultipleplaceholder={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><TimePickervalue={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><TimePickervalue={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>);
};
效果
- 每天

- 每周

- 每月

相关文章:
react+ts手写cron表达式转换组件
前言 最近在写的一个分布式调度系统,后端同学需要让我传入cron表达式,给调度接口传参。我去了学习了解了cron表达式的用法,发现有3个通用的表达式刚好符合我们的需求: 需求 每天 xx 的时间: 0 11 20 * * ? 上面是…...
民安智库(第三方市民健康素养调研)居民健康素养调查的重要性及实施步骤
一、背景和意义 健康素养是衡量一个社区或国家居民对健康知识的理解,以及他们如何将这些知识应用于日常生活中的能力的重要指标。它不仅包括了基本的医学知识,如疾病预防和治疗,也包括了生活方式的改善,如合理饮食和适当运动。因…...
Linux | vim的入门手册
目录 前言 一、什么是vim 二、vim编辑器的模式 1、插入模式 (1)用vim打开文件 (2)进入插入模式 2、默认模式 (1)光标移动 (2)复制、粘贴与剪切操作 (3&#x…...
B053 项目部署
目录 Linux简介虚拟机软件安装安装centos步骤备份系统网络设置 远程访问Linux步骤永久关闭CentOS防火墙 linux命令linux文件系统linux常用命令目录相关命令文件相关命令 安装JDK先卸载自带的JDK安装JDK复制压缩包到linux解压配置环境变量 安装MySql清理旧文件安装mysqlMysql编码…...
视觉Slam面试题(不定时更新)
文章目录 0 引言1 单目、双目、深度相机和RGBD相机的区别2 特征点法与直接法的优缺点3 等距变换、相似变换、仿射变换、射影变换的区别4 单应矩阵、本质矩阵和基础矩阵的区别5 Slam中为什么用李群李代数6 解释Slam中的绑架问题7 ORB、SIFT和SURF特征点检测算法的区别8 什么是对…...
从入门到进阶 之 ElasticSearch 节点配置 集群篇
🌹 以上分享 ElasticSearch 安装部署,如有问题请指教写。🌹🌹 如你对技术也感兴趣,欢迎交流。🌹🌹🌹 如有需要,请👍点赞💖收藏🐱&a…...
UE4中无法保存项目问题
系列文章目录 文章目录 系列文章目录前言一、解决方法 前言 取消:停止保存所有资产并返回编辑器。 重试:尝试再次保存资产。 继续:仅跳过保存该资产。 当我点击继续时,关闭项目,然后重新打开项目,发现之前…...
解剖—顺序表相关OJ练习题
目录 一、删除有序数组中的重复项,返回出现一次元素的个数。 二、原地移除数组中所有数值等于val的元素 三、合并两个有序数组 四、旋转数组 五、数组形式的整数加法 一、删除有序数组中的重复项,返回出现一次元素的个数。 26. 删除有序数组中的重…...
NAT网关在阿里云的应用
NAT网关(Network Address Translation Gateway)是一种网络地址转换服务,提供NAT代理(SNAT和DNAT)能力。NAT是用于在本地网络中使用私有地址,在连接互联网时转而使用全局 IP 地址的技术。NAT实际上是为解决I…...
操作系统体系结构和OS
1.冯诺依曼计算机体系 关于冯诺伊曼系统,在这里我只是简单讲一讲,更加详细的内容可以看我的计算机组成系列。 常见的笔记本、台式机,不常见的服务器、工作站,大部分都遵守“冯诺依曼体系”,因此该计算机体系就是现代…...
Flutter ☞ 常量
常量 只能被定义一次,并且不可修改的值叫做常量。 在 Flutter 中有两种常量修饰方法 finalconst 相同点 类型声明可以省略 final String a 123; final a 123;const String a 123; const a 123;初始化后不能再赋值 final a 123; a abc; // 错误const a …...
C++ 配置VSCode开发环境
C配置VSCode开发环境 简介 Visual Studio Code (VSCode) 是一款开源的轻量级代码编辑器。它支持许多编程语言,包括C。本文档将详细介绍如何在Windows环境下配置VSCode的C开发环境。 安装步骤 1. 安装Visual Studio Code 首先,你需要下载并安装Visua…...
Arduino_STM32整理贴
Arduion-STM32 stm32duino 让stm32 在arduino中使用 源代码:https://github.com/stm32duino/Arduino_Core_STM32 busybox文件位置 stm32duino 下有个stm32tool 项目,内含有busybox.exe 使用usb转TTL烧写 使用 PA9 PA10 端口 PA9接 RX ,PA10接 TX …...
MoeCTF 2023 Web+Jail wp
----------签到---------- hello CTFer 给了一个URL,是赛博厨子解码base64的flag,flag直接给了。 远程端口转发: 这次比赛估计好多大师傅都没参加,题目环境是在本机内网上的(比如localhost:52005)导致请…...
494.目标和 474.一和零
目标和 题目 给一个都是正整数的组合,然后你可以在里面任意添加或-,求使得最后结果为 目标和S(target)的有多少种方法? 范围 数组非空,且长度不会超过 20 。初始的数组的和不会超过 1000 。保证返回的…...
模拟电源与数字电源之间的区别
BOSHIDA 模拟电源与数字电源之间的区别 模拟电源与数字电源是两种不同的电源类型,其核心区别在于电源控制方式和输出特性。本文将从这两方面对模拟电源和数字电源进行比较和分析。 电源控制方式: 模拟电源的控制方式以模拟电压和模拟电流为基础。模拟电…...
P5461 赦免战俘
题目描述 现有 2 n 2 n ( n ≤ 10 ) 2^n\times 2^n (n\le10) 2n2n(n≤10) 名作弊者站成一个正方形方阵等候 kkksc03 的发落。kkksc03 决定赦免一些作弊者。他将正方形矩阵均分为 4 个更小的正方形矩阵,每个更小的矩阵的边长是原矩阵的一半。其中左上角那一个矩阵…...
【工具】转码silk格式为mp3
【工具】转码slk格式为mp3 前提 安装 ffmpeg 【安装】Linux安装ffmpeg_linux安装ffmpeg4.4_我是Superman丶的博客-CSDN博客 GitHub - kn007/silk-v3-decoder: [Skype Silk Codec SDK]Decode silk v3 audio files (like wechat amr, aud files, qq slk files) and convert to o…...
蓝桥杯每日一题2023.10.18
题目描述 特别数的和 - 蓝桥云课 (lanqiao.cn) 题目分析 简单枚举每一个可行的数 #include<bits/stdc.h> using namespace std; int flag, ans; int main() {int n;cin >> n;for(int i 1; i < n; i ){flag 0;int x i;while(x){int y x % 10;if(y 2 || y…...
大数据开发中的秘密武器:探索Hadoop纠删码的奇妙世界
随着大数据技术的发展,HDFS作为Hadoop的核心模块之一得到了广泛的应用。为了系统的可靠性,HDFS通过复制来实现这种机制。但在HDFS中每一份数据都有两个副本,这也使得存储利用率仅为1/3,每TB数据都需要占用3TB的存储空间。因此&…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...
【若依】框架项目部署笔记
参考【SpringBoot】【Vue】项目部署_no main manifest attribute, in springboot-0.0.1-sn-CSDN博客 多一个redis安装 准备工作: 压缩包下载:http://download.redis.io/releases 1. 上传压缩包,并进入压缩包所在目录,解压到目标…...
学习 Hooks【Plan - June - Week 2】
一、React API React 提供了丰富的核心 API,用于创建组件、管理状态、处理副作用、优化性能等。本文档总结 React 常用的 API 方法和组件。 1. React 核心 API React.createElement(type, props, …children) 用于创建 React 元素,JSX 会被编译成该函数…...
c++算法学习3——深度优先搜索
一、深度优先搜索的核心概念 DFS算法是一种通过递归或栈实现的"一条路走到底"的搜索策略,其核心思想是: 深度优先:从起点出发,选择一个方向探索到底,直到无路可走 回溯机制:遇到死路时返回最近…...
