Cron表达式生成器表单以及Cron表单校验

497 阅读2分钟

运行结果:

image.png

image.png

环境以及依赖

没用ts

antd使用了两个版本进行测试 5.4.0, 4.21.0

校验cron合法性工具: cron-parser, 直接安装就行, 我使用的版本是4.9.0,注意这个校验工具不支持校验带有年的cron表达式

cron生成器: qnn-react-cron, 版本:0.8.6 这个版本是没问题的, 我尝试最新版本是不好使的, 这个可能和antd版本有关

代码:

写的不是很好多多包涵


import React, { useState, useRef, useEffect } from 'react';
import { Form, Input, Button, Modal, Dropdown, Space } from "antd";
import cronParser from 'cron-parser';
import Cron from "qnn-react-cron";
import moment from "moment";

export default function CronComponent() {
  const [modalConfig, setModalConfig] = useState({ visible: false, cronTextArray: [], disabled: true });
  const [update, setUpdate] = useState(false);
  const [form] = Form.useForm();
  const [cronValue, setCronValue] = useState("");
  const onParseFunction = useRef({
    getValue: () => { },
    onParse: () => { },
  });

  const triggerValidation = async () => {
    try {
      // 仅校验 'username' 字段
      await form.validateFields(['cronString']);
    } catch (errorInfo) {
      // eslint-disable-next-line
      console.log('校验失败:', errorInfo);
    }
  };

  const handleOk = () => {
    const { getValue } = onParseFunction.current || {};
    let cronString = "";
    try {
      // 因为用来检测正则格式的cronPaeser不能验证带有 年 的cron表达式, 所以让我剪掉了最后一个字符串
      cronString = getValue().slice(0, -1);
    }
    catch {
      cronString = "";
    }
    form.setFieldValue("cronString", cronString);
    triggerValidation();
  };

  const onOpenChange = () => {
    const { onParse } = onParseFunction.current || {};
    const currentCronString = form.getFieldValue("cronString");
    try {
      onParse(currentCronString);
      setCronValue(currentCronString);
      setModalConfig((modalConfig) => {
        return { ...modalConfig, disabled: false };
      });
    }
    catch {
      setCronValue("* * * * * ?");
    }
  };

  // 使用 cron-parser 进行 cron 表达式的校验,以及生成五次运行结果
  const validateCronExpression = (rule, value) => {
    // 如果值为空,则不进行校验(因为 required 规则会处理)
    if (!value) {
      return Promise.reject(new Error("cron表达式为必填项"));
    }
    try {
      // 尝试解析表达式
      const cronEntriesObj = cronParser.parseExpression(value);
      const cronTextArray = [
        moment(cronEntriesObj.next().toString()).format("YYYY-MM-DD HH:mm:ss"),
        moment(cronEntriesObj.next().toString()).format("YYYY-MM-DD HH:mm:ss"),
        moment(cronEntriesObj.next().toString()).format("YYYY-MM-DD HH:mm:ss"),
        moment(cronEntriesObj.next().toString()).format("YYYY-MM-DD HH:mm:ss"),
        moment(cronEntriesObj.next().toString()).format("YYYY-MM-DD HH:mm:ss")
      ];
      setModalConfig((modalConfig) => {
        return { ...modalConfig, cronTextArray, disabled: false };
      });
      // 如果没有抛出错误,表达式是有效的
      return Promise.resolve();
    } catch (e) {
      // 如果解析时抛出错误,表达式是无效的
      setModalConfig((modalConfig) => {
        return { ...modalConfig, disabled: true };
      });
      return Promise.reject(new Error("无效的 Cron 表达式"));
    }
  };

  const validateEvent = () => {
    validateCronExpression("_", form.getFieldValue("cronString")).then(() => {
      setModalConfig((modalConfig) => { return { ...modalConfig, visible: true, disabled: false }; });
    }).catch(() => {
      setModalConfig((modalConfig) => { return { ...modalConfig, visible: true, disabled: true }; });
    });
  };

  useEffect(() => {
    onOpenChange();
  }, []);

  return (
    <>
      <Form form={form}>
        <Form.Item label="Cron表达式">
          <div style={{ display: "flex", gap: "12px" }}>
            <Dropdown
              menu={{}}
              onOpenChange={onOpenChange}
              dropdownRender={menu => (
                <div style={{ width: 800 }}>
                  <Cron
                    value={cronValue}
                    key={update}
                    onOk={(e) => {
                      console.log('e: ', e);
                      setCronValue(e);
                      form.setFieldValue("cronString", e);
                    }}
                    panesShow={{
                      second: true,
                      minute: true,
                      hour: true,
                      day: true,
                      month: true,
                      week: true,
                      year: false,
                    }}
                    getCronFns={(e) => {
                      onParseFunction.current = e;
                    }}
                    footer={
                      <Space>
                        <Button
                          type="default"
                          onClick={() => {
                            setUpdate(!update);
                            setCronValue("* * * * * ?");
                            form.setFieldValue("cronString", "* * * * * ?");
                            triggerValidation();
                          }}
                        >
                          重置
                        </Button>
                        <Button type="primary" htmlType="submit" onClick={handleOk}>
                          生成
                        </Button>
                      </Space>
                    }
                  />
                </div>
              )}
            >
              <Form.Item
                name="cronString"
                rules={[{ required: true, validator: validateCronExpression }]}
                style={{ width: "800px" }}
              >
                <Input placeholder="请输入cron表达式" onChange={onOpenChange} />
              </Form.Item>
            </Dropdown>
            <Button type="primary" disabled={modalConfig.disabled} onClick={validateEvent}>运行结果</Button>
          </div>
        </Form.Item>
      </Form>
      <Modal
        title="最近5次运行时间"
        visible={modalConfig.visible}
        onOk={() => setModalConfig((modalConfig) => { return { ...modalConfig, visible: false }; })}
        onCancel={() => setModalConfig((modalConfig) => { return { ...modalConfig, visible: false }; })}
        cancelText="关闭"
      >
        <div style={{ minHeight: 80 }}>
          <ul style={{ fontSize: "16px" }}>
            {Array.isArray(modalConfig.cronTextArray) ? modalConfig.cronTextArray.map((item, index) => {
              return <li>第{index + 1}次运行: {item}</li>;
            }) : null}
          </ul>
        </div>
      </Modal>
    </>
  );
}