├── .babelrc ├── .gitignore ├── App.jsx ├── CHANGELO1G.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cron.jpg ├── dist ├── index.html └── index.js ├── index.js ├── jest.config.js ├── jest.setup.js ├── package-lock.json ├── package.json ├── screenshot.png ├── src ├── .npmignore ├── components │ ├── Day.jsx │ ├── Hour.jsx │ ├── Minute.jsx │ ├── Month.jsx │ ├── Second.jsx │ ├── Week.jsx │ └── Year.jsx ├── css │ └── index.less ├── index.jsx ├── test │ ├── __mocks__ │ │ ├── fileMock.js │ │ └── styleMock.js │ └── __test__ │ │ └── app.spec.jsx └── utils │ ├── index.js │ └── parse-lib.js ├── webpack.config.build.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], //next/babel next内置的模块,脚手架自带无需安装 3 | "plugins": [ 4 | [ 5 | "@babel/plugin-proposal-decorators", 6 | { 7 | "legacy": true 8 | } 9 | ], 10 | [ 11 | "@babel/plugin-proposal-class-properties", 12 | { 13 | "loose": true 14 | } 15 | ], 16 | ["@babel/plugin-transform-runtime"] 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | .ipr 4 | .iws 5 | *~ 6 | ~* 7 | *.diff 8 | *.patch 9 | *.bak 10 | .DS_Store 11 | Thumbs.db 12 | .project 13 | .*proj 14 | .svn/ 15 | *.swp 16 | *.swo 17 | *.log 18 | *.log.* 19 | *.json.gzip 20 | node_modules/ 21 | .buildpath 22 | .settings 23 | npm-debug.log 24 | nohup.out 25 | _site 26 | _data 27 | /lib 28 | /es 29 | elasticsearch-* 30 | config/base.yaml 31 | /.vscode/ 32 | /coverage 33 | yarn.lock 34 | components/**/*.js 35 | components/**/*.jsx 36 | !components/**/__tests__/*.js 37 | !components/**/__tests__/*.js.snap 38 | /.history 39 | -------------------------------------------------------------------------------- /App.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 功能:主界面 3 | * 作者:宋鑫鑫 4 | * 日期:2019.11.04 5 | */ 6 | import React, { PureComponent } from "react"; 7 | import PropTypes from 'prop-types' 8 | import Cron from "./src/index"; 9 | import { Modal, Button } from 'antd'; 10 | 11 | 12 | class Test extends PureComponent { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | visible: false, 17 | crontab: '0 0 0 * * ?' 18 | }; 19 | 20 | } 21 | 22 | showModal = () => { 23 | this.setState({ 24 | visible: true, 25 | }); 26 | }; 27 | 28 | handleOk = e => { 29 | console.log(e); 30 | this.setState({ 31 | visible: false, 32 | }); 33 | }; 34 | 35 | handleCancel = e => { 36 | console.log(e); 37 | this.setState({ 38 | visible: false, 39 | }); 40 | }; 41 | 42 | handleCronChange = (cronExpression) => { 43 | this.setState({ 44 | crontab: cronExpression 45 | }) 46 | console.log(cronExpression) //0 0 0 * * ? 47 | } 48 | 49 | render() { 50 | const { crontab } = this.state 51 | return ( 52 |
53 | 56 | 63 | 69 | 70 |
71 | ); 72 | } 73 | } 74 | 75 | export default Test; 76 | -------------------------------------------------------------------------------- /CHANGELO1G.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 (2020-03-27) 2 | - 版本号重置,组件名称修改,以下作为存档 3 | 4 | # 2.0.0 (2019-11-28) 5 | - 新增 API:tabType、showCrontab 6 | 7 | # 1.0.9 (2019-11-21) 8 | ### 业务需求修改 9 | > 日: 10 | - 不指定:‘日’不指定和‘周’不指定,有且仅有一个不指定 11 | - 第一个值小于等于第二个值 12 | - 周期:几日开始,每几天执行一次 13 | - 本月最后一天,不能填,只能是最后一天 14 | - 具体天数,默认第一天 15 | 16 | > 月: 17 | - 不指定:删掉 18 | - 周期:第一个值小于等于第二个值 19 | - 具体月数,默认第一月 20 | 21 | > 星期: 22 | - 不指定:‘日’不指定和‘周’不指定,有且仅有一个不指定 23 | - 周期:第一个值小于等于第二个值 24 | - 具体星期数:默认星期一 25 | 26 | - 暂时禁用Input修改cron 27 | # 1.0.7 (2019-11-07) 28 | - 暂时禁用Input修改cron 29 | 30 | # 1.0.6 (2019-11-07) 31 | - 增加propTypes 校验 32 | - 可选显示客户端计算周期执行时间 33 | - 修改默认值 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.0.1-beta.0](https://github.com/tulaoda/cron-editor-react/compare/v1.0.0...v1.0.1-beta.0) (2020-08-03) 6 | 7 | ## 1.0.0 (2020-08-03) 8 | 9 | 10 | ### Features 11 | 12 | * fix bug and change file tree ([49ed092](https://github.com/tulaoda/cron-editor-react/commit/49ed092db8b429f35c7ac2c85ab63c1a8b5a30c8)) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * fix bug ([643b44b](https://github.com/tulaoda/cron-editor-react/commit/643b44bb8319dc12dae42cab7625eb3ec47e1257)) 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Tulaoda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## cron-editor-react 2 | cron表达式生成工具 3 | ![](./screenshot.png) 4 | 5 | ## Installation 6 | 7 | ```shell 8 | npm i @tulaoda/cron-editor-react --save 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```javascript 14 | import CronEditor from '@tulaoda/cron-editor-react'; 15 | 16 | const handleCronChange = (cronExpression) => { 17 | console.log(cronExpression) //0 0 0 * * ? 18 | } 19 | 20 | 26 | ``` 27 | 28 | ## API 29 | 30 | | Prop | Description | Default | 31 | | --------------------- | ---------------------------------------------------------------- | ----------- | 32 | | value | crontab表达式传值 | 0 0 0 * * ? | 33 | | onChange | 值改变触发 | noop | 34 | | showRunTime(待优化) | 本地计算并展示最近五次的运行时间(未全面测试api,谨慎使用,通常由后端计算返回,也更合理,可作为备选方案。) | false | 35 | | tabType | antd tab 页签的基本样式,可选 line、card editable-card 类型 | 'line' | 36 | | showCrontab | 是否显示crontab表达式input,目前通过input修改禁用 | true | 37 | 38 | ## Keywords 39 | cron、react、js 40 | 41 | ## 更新日志 42 | 43 | ### 5.0.0 (2020-04-03) 44 | - 修复星期表达式从星期一为1的问题 45 | 46 | ### 2.0.0 (2019-11-28) 47 | - 新增 API:tabType、showCrontab 48 | 49 | ### 1.0.9 (2019-11-21) 50 | #### 业务需求修改 51 | > 日: 52 | - 不指定:‘日’不指定和‘周’不指定,有且仅有一个不指定 53 | - 第一个值小于等于第二个值 54 | - 周期:几日开始,每几天执行一次 55 | - 本月最后一天,不能填,只能是最后一天 56 | - 具体天数,默认第一天 57 | 58 | > 月: 59 | - 不指定:删掉 60 | - 周期:第一个值小于等于第二个值 61 | - 具体月数,默认第一月 62 | 63 | > 星期: 64 | - 不指定:‘日’不指定和‘周’不指定,有且仅有一个不指定 65 | - 周期:第一个值小于等于第二个值 66 | - 具体星期数:默认星期一 67 | 68 | - 暂时禁用Input修改cron 69 | ### 1.0.7 (2019-11-07) 70 | - 暂时禁用Input修改cron 71 | 72 | ### 1.0.6 (2019-11-07) 73 | - 增加propTypes 校验 74 | - 可选显示客户端计算周期执行时间 75 | - 修改默认值 76 | 77 | 78 | ## License 79 | 80 | [MIT](./LICENSE) 81 | 82 | 83 | # CRON表达式简介 84 | 85 | > CRON表达式是一个字符串,包含五个到七个由空格分隔的字段(每种软件不一样),表示一组时间,通常作为执行某个程序的时间表。 86 | 87 | 例子:每月的最后1天:0 0 L * * * 88 | 89 | 说明: 90 | 91 | ``` 92 | * * * * * * * 93 | ┬ ┬ ┬ ┬ ┬ ┬ ┬ 94 | │ │ │ │ │ | └ year (*) 95 | │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun) 96 | │ │ │ │ └───── month (1 - 12) 97 | │ │ │ └────────── day of month (1 - 31) 98 | │ │ └─────────────── hour (0 - 23) 99 | │ └──────────────────── minute (0 - 59) 100 | └───────────────────────── second (0 - 59, optional) 101 | ``` 102 | 103 | | 字段 | 是否必填 | 允许值 | 允许特殊字符 | 备注 | 104 | | ------------ | -------- | --------------- | ------------ | ---------------------- | 105 | | Seconds | 是 | 0–59 | *,- | 标准实现不支持此字段。 | 106 | | Minutes | 是 | 0–59 | *,- | 107 | | Hours | 是 | 0–23 | *,- | 108 | | Day of month | 是 | 1–31 | *,-?LW | ?LW只有部分软件实现了 | 109 | | Month | 是 | 1–12 or JAN–DEC | *,- | 110 | | Day of week | 是 | 0–6 or SUN–SAT | *,-?L# | ?L#只有部分软件实现了 | 111 | | Year | 否 | 1970–2099 | *,- | 标准实现不支持此字段。 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /cron.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tulaoda/cron-editor-react/b72e1f33b3225addfcf929a8fec2b3408488f358/cron.jpg -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDom from 'react-dom' 3 | import App from './App' 4 | ReactDom.render(, document.getElementById('app')) 5 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { '^.+\\.jsx?$': 'babel-jest' }, 3 | moduleFileExtensions: ['js', 'jsx'], 4 | transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(ts|tsx)$'], 5 | moduleNameMapper: { 6 | '\\.(css|less|scss)$': 'identity-obj-proxy', 7 | }, 8 | setupFilesAfterEnv: ['/jest.setup.js'], 9 | } 10 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { configure } from 'enzyme' 3 | import Adapter from 'enzyme-adapter-react-16' 4 | 5 | configure({ adapter: new Adapter() }) 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tulaoda/cron-editor", 3 | "version": "1.0.1-beta.0", 4 | "description": "基于antd、react的crontab表达式生成工具", 5 | "main": "./dist/index", 6 | "scripts": { 7 | "start": "webpack-dev-server", 8 | "build": "webpack -p --config ./webpack.config.build.js", 9 | "release": "standard-version", 10 | "postrelease": "git push --follow-tags origin master", 11 | "test": "jest" 12 | }, 13 | "keywords": [ 14 | "cron", 15 | "react", 16 | "js" 17 | ], 18 | "author": { 19 | "name": "tulaoda", 20 | "email": "coderaxin@163.com" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git@github.com:tulaoda/cron-editor-react.git" 25 | }, 26 | "license": "MIT", 27 | "dependencies": { 28 | "antd": "^3.9.3", 29 | "classnames": "^2.2.6", 30 | "cron-parser": "^2.13.0", 31 | "moment": "^2.19.1", 32 | "prop-types": "^15.7.2", 33 | "react": "^16.0.0", 34 | "react-dom": "^16.0.0" 35 | }, 36 | "devDependencies": { 37 | "@babel/core": "^7.4.5", 38 | "@babel/plugin-proposal-class-properties": "^7.4.4", 39 | "@babel/plugin-proposal-decorators": "^7.4.4", 40 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 41 | "@babel/plugin-transform-runtime": "^7.4.4", 42 | "@babel/preset-env": "^7.11.0", 43 | "@babel/preset-react": "^7.10.4", 44 | "@babel/types": "^7.4.4", 45 | "@testing-library/react": "^10.4.7", 46 | "babel-jest": "^26.2.2", 47 | "babel-loader": "^8.0.6", 48 | "babel-plugin-import": "^1.12.0", 49 | "css-loader": "^3.0.0", 50 | "enzyme": "^3.11.0", 51 | "enzyme-adapter-react-16": "^1.15.2", 52 | "identity-obj-proxy": "^3.0.0", 53 | "jest": "^26.2.2", 54 | "less": "^3.9.0", 55 | "less-loader": "^5.0.0", 56 | "react-test-renderer": "^16.13.1", 57 | "style-loader": "^0.23.1", 58 | "webpack": "^4.35.2", 59 | "webpack-cli": "^3.3.5", 60 | "webpack-dev-server": "^3.7.2" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tulaoda/cron-editor-react/b72e1f33b3225addfcf929a8fec2b3408488f358/screenshot.png -------------------------------------------------------------------------------- /src/.npmignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | npm-debug.log.* 3 | node_modules/ 4 | .DS_Store 5 | package-lock.json 6 | yarn.lock 7 | yarn-error.log 8 | src/ 9 | stories/ 10 | test/ 11 | storybook-static/ 12 | App.jsx 13 | -------------------------------------------------------------------------------- /src/components/Day.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 功能:周期-天 3 | * 作者:宋鑫鑫 4 | * 日期:2019.11.04 5 | */ 6 | import React, { PureComponent } from "react"; 7 | import { Radio, InputNumber, Row, Col, Select, List, Checkbox, message } from "antd"; 8 | const { Group } = Radio; 9 | import { isNumber } from '../utils/index' 10 | 11 | export default class Day extends PureComponent { 12 | constructor(props) { 13 | super(props); 14 | this.formatDayOptions(); 15 | } 16 | 17 | // formatDayOptions() { 18 | // this.dayOptions = []; 19 | // for (let x = 1; x < 32; x++) { 20 | // this.dayOptions.push({ 21 | // label: x, 22 | // value: `${x}` 23 | // }); 24 | // } 25 | // } 26 | 27 | formatDayOptions() { 28 | this.dayOptions = []; 29 | for (let x = 1; x < 32; x++) { 30 | const label = x < 10 ? `0${x}` : x; 31 | const value = `${x}`; 32 | const ele = ( 33 | 34 | {label} 35 | 36 | ); 37 | this.dayOptions.push(ele); 38 | } 39 | } 40 | 41 | changeParams(type, value) { 42 | const state = { ...this.props.day }; 43 | state[type] = value; 44 | if (type === 'start') { 45 | if (state.end - state.start <= 1) { 46 | state.end = value + 1; 47 | } 48 | } 49 | if (type === 'end') { 50 | if (state.end - state.start <= 1) { 51 | state.start = value - 1; 52 | } 53 | } 54 | this.props.onChange(state); 55 | } 56 | 57 | changeType = e => { 58 | const state = { ...this.props.day }; 59 | // if (e.target.value === "some") { 60 | // state.some = ["1"]; 61 | // } 62 | state.type = e.target.value; 63 | this.props.onChange(state); 64 | }; 65 | 66 | render() { 67 | const { 68 | day: { type, start, end, some, begin, beginEvery, last, closeWorkDay } 69 | } = this.props; 70 | return ( 71 |
72 | 73 | 74 | 75 | 每日 76 | 77 | 78 | 不指定 79 | 80 | 81 | 周期从{" "} 82 | value.toString().replace(/[^\d\.]/g, '')} 91 | onChange={(value) => { 92 | if (isNumber(value) && Number(value) >= 1 && Number(value) <= 30) { 93 | this.changeParams("start", value); 94 | } else { 95 | message.info('输入不合法') 96 | } 97 | }} 98 | disabled={type !== "period"} 99 | />{" "} 100 | 到{" "} 101 | value.toString().replace(/[^\d\.]/g, '')} 110 | onChange={(value) => { 111 | if (isNumber(value) && Number(value) >= 2 && Number(value) <= 31) { 112 | this.changeParams("end", value); 113 | } else { 114 | message.info('输入不合法') 115 | } 116 | }} 117 | disabled={type !== "period"} 118 | /> 119 |  日  120 | 121 | 122 | 123 | 从{" "} 124 | value.toString().replace(/[^\d\.]/g, '')} 131 | onChange={(value) => { 132 | if (isNumber(value) && Number(value) >= 1) { 133 | this.changeParams("begin", value); 134 | } else { 135 | message.info('输入不合法') 136 | } 137 | }} 138 | disabled={type !== "beginInterval"} 139 | />{" "} 140 | 日开始, 每{" "} 141 | value.toString().replace(/[^\d\.]/g, '')} 148 | onChange={(value) => { 149 | if (isNumber(value) && Number(value) >= 0) { 150 | this.changeParams("beginEvery", value); 151 | } else { 152 | message.info('输入不合法') 153 | } 154 | }} 155 | disabled={type !== "beginInterval"} 156 | /> 157 |  天执行一次 158 | 159 | 160 | 161 | 每月{" "} 162 | value.toString().replace(/[^\d\.]/g, '')} 169 | onChange={(value) => { 170 | if (isNumber(value) && Number(value) >= 1) { 171 | this.changeParams("closeWorkDay", value); 172 | } else { 173 | message.info('输入不合法') 174 | } 175 | }} 176 | disabled={type !== "closeWorkDay"} 177 | /> 178 |  日最近的那个工作日 179 | 180 | 181 | 182 | 本月最后{" "} 183 | value.toString().replace(/[^\d\.]/g, '')} 189 | onChange={(value) => { 190 | if (isNumber(value) && Number(value) >= 0) { 191 | this.changeParams("last", value); 192 | } else { 193 | message.info('输入不合法') 194 | } 195 | }} 196 | disabled 197 | // disabled={type !== "last"} 198 | />{" "} 199 | 天 200 | 201 | 202 | 203 | 具体天数(可多选) 204 | 222 | {/* { 225 | this.changeParams("some", value); 226 | }} 227 | options={this.dayOptions} 228 | disabled={type !== "some"} 229 | /> */} 230 | 231 | 232 | 233 |
234 | ); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/components/Hour.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 功能:周期-小时 3 | * 作者:宋鑫鑫 4 | * 日期:2019.11.04 5 | */ 6 | import React, { PureComponent } from "react"; 7 | import { Radio, InputNumber, message, List, Checkbox, Select } from "antd"; 8 | const { Group } = Radio; 9 | import { isNumber } from '../utils/index' 10 | 11 | export default class Hour extends PureComponent { 12 | constructor(props) { 13 | super(props); 14 | this.formatHourOptions(); 15 | } 16 | 17 | // formatHourOptions() { 18 | // this.hourOptions = []; 19 | // for (let x = 0; x < 24; x++) { 20 | // this.hourOptions.push({ 21 | // label: x < 10 ? `0${x}` : x, 22 | // value: `${x}` 23 | // }); 24 | // } 25 | // } 26 | 27 | formatHourOptions() { 28 | this.hourOptions = []; 29 | for (let x = 0; x < 24; x++) { 30 | const label = x < 10 ? `0${x}` : x; 31 | const value = `${x}`; 32 | const ele = ( 33 | 34 | {label} 35 | 36 | ); 37 | this.hourOptions.push(ele); 38 | } 39 | } 40 | 41 | changeParams(type, value) { 42 | const state = { ...this.props.hour }; 43 | state[type] = value; 44 | if (type === 'start') { 45 | if (state.end - state.start <= 1) { 46 | state.end = value + 1; 47 | } 48 | } 49 | if (type === 'end') { 50 | if (state.end - state.start <= 1) { 51 | state.start = value - 1; 52 | } 53 | } 54 | this.props.onChange(state); 55 | } 56 | 57 | changeType = e => { 58 | const state = { ...this.props.hour }; 59 | // if (e.target.value === "some") { 60 | // state.some = ["1"]; 61 | // } 62 | state.type = e.target.value; 63 | this.props.onChange(state); 64 | }; 65 | 66 | render() { 67 | const { 68 | hour: { type, start, end, begin, some, beginEvery } 69 | } = this.props; 70 | return ( 71 |
72 | 73 | 74 | 75 | 每小时 76 | 77 | 78 | 周期从{" "} 79 | value.toString().replace(/[^\d\.]/g, '')} 88 | onChange={(value) => { 89 | if (isNumber(value) && Number(value) >= 0 && Number(value) <= 22) { 90 | this.changeParams("start", value); 91 | } else { 92 | message.info('输入不合法') 93 | } 94 | }} 95 | disabled={type !== "period"} 96 | /> 97 | 到 98 | value.toString().replace(/[^\d\.]/g, '')} 107 | onChange={(value) => { 108 | if (isNumber(value) && Number(value) >= 1 && Number(value) <= 23) { 109 | this.changeParams("end", value); 110 | } else { 111 | message.info('输入不合法') 112 | } 113 | }} 114 | disabled={type !== "period"} 115 | /> 116 |  小时  117 | 118 | 119 | 120 | 从 121 | value.toString().replace(/[^\d\.]/g, '')} 129 | onChange={(value) => { 130 | if (isNumber(value) && Number(value) >= 0 && Number(value) <= 23) { 131 | this.changeParams("begin", value); 132 | } else { 133 | message.info('输入不合法') 134 | } 135 | }} 136 | disabled={type !== "beginInterval"} 137 | /> 138 | 时开始, 每 139 | value.toString().replace(/[^\d\.]/g, '')} 147 | onChange={(value) => { 148 | if (isNumber(value) && Number(value) >= 1 && Number(value) <= 23) { 149 | this.changeParams("beginEvery", value); 150 | } else { 151 | message.info('输入不合法') 152 | } 153 | }} 154 | disabled={type !== "beginInterval"} 155 | /> 156 | 时执行一次 157 | 158 | 159 | 具体小时数(可多选) 160 | 178 | {/* { 181 | if (value.length < 1) { 182 | return message.warn("至少选择一项"); 183 | } 184 | this.changeParams("some", value); 185 | }} 186 | options={this.hourOptions} 187 | disabled={type !== "some"} 188 | /> */} 189 | 190 | 191 | 192 |
193 | ); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/components/Minute.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 功能:周期-分钟 3 | * 作者:宋鑫鑫 4 | * 日期:2019.11.04 5 | */ 6 | import React, { PureComponent } from 'react' 7 | import { Radio, InputNumber, message, List, Checkbox, Select } from 'antd' 8 | const { Group } = Radio 9 | import { isNumber } from '../utils/index' 10 | 11 | export default class Minute extends PureComponent { 12 | constructor(props) { 13 | super(props) 14 | this.formatMinuteOptions() 15 | } 16 | 17 | // formatMinuteOptions() { 18 | // this.minuteOptions = []; 19 | // for (let x = 0; x < 60; x++) { 20 | // this.minuteOptions.push({ 21 | // label: x < 10 ? `0${x}` : x, 22 | // value: `${x}` 23 | // }); 24 | // } 25 | // } 26 | 27 | formatMinuteOptions() { 28 | this.minuteOptions = [] 29 | for (let x = 0; x < 60; x++) { 30 | const label = x < 10 ? `0${x}` : x 31 | const value = `${x}` 32 | const ele = ( 33 | 34 | {label} 35 | 36 | ) 37 | this.minuteOptions.push(ele) 38 | } 39 | } 40 | 41 | changeParams(type, value) { 42 | const state = { ...this.props.minute } 43 | state[type] = value 44 | if (type === 'start') { 45 | if (state.end - state.start <= 1) { 46 | state.end = value + 1 47 | } 48 | } 49 | if (type === 'end') { 50 | if (state.end - state.start <= 1) { 51 | state.start = value - 1 52 | } 53 | } 54 | this.props.onChange(state) 55 | } 56 | 57 | changeType = (e) => { 58 | const state = { ...this.props.minute } 59 | // if (e.target.value === "some") { 60 | // state.some = ["1"]; 61 | // } 62 | state.type = e.target.value 63 | this.props.onChange(state) 64 | } 65 | 66 | render() { 67 | const { 68 | minute: { type, start, end, some, begin, beginEvery }, 69 | } = this.props 70 | return ( 71 |
72 | 73 | 74 | 75 | 每分钟 76 | 77 | 78 | 周期 79 | 从  80 | value.toString().replace(/[^\d\.]/g, '')} 89 | onChange={(value) => { 90 | if (isNumber(value) && Number(value) >= 0 && Number(value) <= 58) { 91 | this.changeParams('start', value) 92 | } else { 93 | message.info('输入不合法') 94 | } 95 | }} 96 | disabled={type !== 'period'} 97 | /> 98 |  到  99 | value.toString().replace(/[^\d\.]/g, '')} 108 | onChange={(value) => { 109 | if (isNumber(value) && Number(value) >= 1 && Number(value) <= 59) { 110 | this.changeParams('end', value) 111 | } else { 112 | message.info('输入不合法') 113 | } 114 | }} 115 | disabled={type !== 'period'} 116 | /> 117 |  分钟  118 | 119 | 120 | 121 | 从第  122 | value.toString().replace(/[^\d\.]/g, '')} 130 | onChange={(value) => { 131 | if (isNumber(value) && Number(value) >= 0 && Number(value) <= 59) { 132 | this.changeParams('begin', value) 133 | } else { 134 | message.info('输入不合法') 135 | } 136 | }} 137 | disabled={type !== 'beginInterval'} 138 | /> 139 |  分开始, 每  140 | value.toString().replace(/[^\d\.]/g, '')} 148 | onChange={(value) => { 149 | if (isNumber(value) && Number(value) >= 1 && Number(value) <= 59) { 150 | this.changeParams('beginEvery', value) 151 | } else { 152 | message.info('输入不合法') 153 | } 154 | }} 155 | disabled={type !== 'beginInterval'} 156 | /> 157 |  分执行一次 158 | 159 | 160 | 具体分钟数(可多选) 161 | 179 | {/* { 182 | if (value.length < 1) { 183 | return message.warn("至少选择一项"); 184 | } 185 | this.changeParams("some", value); 186 | }} 187 | options={this.minuteOptions} 188 | disabled={type !== "some"} 189 | /> */} 190 | 191 | 192 | 193 |
194 | ) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/components/Month.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 功能:周期-月 3 | * 作者:宋鑫鑫 4 | * 日期:2019.11.04 5 | */ 6 | import React, { PureComponent } from 'react' 7 | import { Radio, InputNumber, Row, Col, List, message, Select } from 'antd' 8 | const { Group } = Radio 9 | import { isNumber } from '../utils/index' 10 | 11 | export default class Month extends PureComponent { 12 | constructor(props) { 13 | super(props) 14 | this.formatMonthOptions() 15 | } 16 | 17 | changeParams(type, value) { 18 | const state = { ...this.props.month } 19 | state[type] = value 20 | if (type === 'start') { 21 | if (state.end - state.start <= 1) { 22 | state.end = value + 1 23 | } 24 | } 25 | if (type === 'end') { 26 | if (state.end - state.start <= 1) { 27 | state.start = value - 1 28 | } 29 | } 30 | this.props.onChange(state) 31 | } 32 | 33 | // eachMonthOptions() { 34 | // const options = []; 35 | // for (let i = 1; i < 13; i++) { 36 | // options.push({ label: `${i}月`, value: `${i}` }); 37 | // } 38 | // return options; 39 | // } 40 | 41 | formatMonthOptions() { 42 | this.monthOptions = [] 43 | for (let x = 1; x < 13; x++) { 44 | const label = `${x}月` 45 | const value = `${x}` 46 | const ele = ( 47 | 48 | {label} 49 | 50 | ) 51 | this.monthOptions.push(ele) 52 | } 53 | } 54 | 55 | changeType = (e) => { 56 | const state = { ...this.props.month } 57 | // if (e.target.value === "some") { 58 | // state.some = ["1"]; 59 | // } 60 | state.type = e.target.value 61 | this.props.onChange(state) 62 | } 63 | 64 | render() { 65 | const { 66 | month: { type, start, end, beginEvery, begin, some }, 67 | } = this.props 68 | return ( 69 |
70 | 71 | 72 | 73 | 每月 74 | 75 | {/* 76 | 不指定 77 | */} 78 | 79 | 周期从{' '} 80 | value.toString().replace(/[^\d\.]/g, '')} 88 | onChange={(value) => { 89 | if (isNumber(value) && Number(value) >= 1 && Number(value) <= 11) { 90 | this.changeParams('start', value) 91 | } else { 92 | message.info('输入不合法') 93 | } 94 | }} 95 | disabled={type !== 'period'} 96 | />{' '} 97 | 到{' '} 98 | value.toString().replace(/[^\d\.]/g, '')} 106 | onChange={(value) => { 107 | if (isNumber(value) && Number(value) >= 2 && Number(value) <= 12) { 108 | this.changeParams('end', value) 109 | } else { 110 | message.info('输入不合法') 111 | } 112 | }} 113 | disabled={type !== 'period'} 114 | /> 115 |  月  116 | 117 | 118 | 119 | 从 120 | value.toString().replace(/[^\d\.]/g, '')} 128 | onChange={(value) => { 129 | if (isNumber(value) && Number(value) >= 1 && Number(value) <= 12) { 130 | this.changeParams('begin', value) 131 | } else { 132 | message.info('输入不合法') 133 | } 134 | }} 135 | disabled={type !== 'beginInterval'} 136 | />{' '} 137 | 月开始, 每{' '} 138 | value.toString().replace(/[^\d\.]/g, '')} 146 | onChange={(value) => { 147 | if (isNumber(value) && Number(value) >= 1 && Number(value) <= 12) { 148 | this.changeParams('beginEvery', value) 149 | } else { 150 | message.info('输入不合法') 151 | } 152 | }} 153 | disabled={type !== 'beginInterval'} 154 | />{' '} 155 | 月执行一次 156 | 157 | 158 | 具体月数(可多选) 159 | 177 | {/* { 180 | this.changeParams("some", value); 181 | }} 182 | options={this.eachMonthOptions()} 183 | disabled={type !== "some"} 184 | /> */} 185 | 186 | 187 | 188 |
189 | ) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/components/Second.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 功能:周期-秒 3 | * 作者:宋鑫鑫 4 | * 日期:2019.11.04 5 | */ 6 | import React, { PureComponent } from 'react' 7 | import { Radio, InputNumber, message, List, Checkbox, Select } from 'antd' 8 | const { Group } = Radio 9 | import { isNumber } from '../utils/index' 10 | export default class Second extends PureComponent { 11 | constructor(props) { 12 | super(props) 13 | this.formatSecondOptions() 14 | } 15 | 16 | // formatSecondOptions() { 17 | // this.secondOptions = []; 18 | // for (let x = 0; x < 60; x++) { 19 | // this.secondOptions.push({ 20 | // label: x < 10 ? `0${x}` : x, 21 | // value: `${x}` 22 | // }); 23 | // } 24 | // } 25 | 26 | changeParams(type, value) { 27 | const state = { ...this.props.second } 28 | state[type] = value 29 | if (type === 'start') { 30 | if (state.end - state.start <= 1) { 31 | state.end = value + 1 32 | } 33 | } 34 | if (type === 'end') { 35 | if (state.end - state.start <= 1) { 36 | state.start = value - 1 37 | } 38 | } 39 | this.props.onChange(state) 40 | } 41 | 42 | formatSecondOptions() { 43 | this.secondOptions = [] 44 | for (let x = 0; x < 60; x++) { 45 | const label = x < 10 ? `0${x}` : x 46 | const value = `${x}` 47 | const ele = ( 48 | 49 | {label} 50 | 51 | ) 52 | this.secondOptions.push(ele) 53 | } 54 | } 55 | 56 | render() { 57 | const { 58 | second: { type, start, end, begin, beginEvery, some }, 59 | } = this.props 60 | return ( 61 |
62 | { 65 | const state = { ...this.props.second } 66 | // if (e.target.value !== "some") { 67 | // state.some = ["0"]; 68 | // } 69 | state.type = e.target.value 70 | this.props.onChange(state) 71 | }} 72 | > 73 | 74 | 75 | 每秒 76 | 77 | 78 | 周期 79 | 从   80 | value.toString().replace(/[^\d\.]/g, '')} 89 | onChange={(value) => { 90 | if (isNumber(value) && Number(value) >= 0 && Number(value) <= 58) { 91 | this.changeParams('start', value) 92 | } else { 93 | message.info('输入不合法') 94 | } 95 | }} 96 | disabled={type !== 'period'} 97 | /> 98 |  到  99 | value.toString().replace(/[^\d\.]/g, '')} 108 | onChange={(value) => { 109 | if (isNumber(value) && Number(value) >= 1 && Number(value) <= 59) { 110 | this.changeParams('end', value) 111 | } else { 112 | message.info('输入不合法') 113 | } 114 | }} 115 | disabled={type !== 'period'} 116 | /> 117 |  秒  118 | 119 | 120 | 121 | 从第   122 | value.toString().replace(/[^\d\.]/g, '')} 130 | onChange={(value) => { 131 | if (isNumber(value) && Number(value) >= 0 && Number(value) <= 59) { 132 | this.changeParams('begin', value) 133 | } else { 134 | message.info('输入不合法') 135 | } 136 | }} 137 | disabled={type !== 'beginInterval'} 138 | />{' '} 139 |  秒开始, 每   140 | value.toString().replace(/[^\d\.]/g, '')} 148 | onChange={(value) => { 149 | if (isNumber(value) && Number(value) >= 0 && Number(value) <= 59) { 150 | this.changeParams('beginEvery', value) 151 | } else { 152 | message.info('输入不合法') 153 | } 154 | }} 155 | disabled={type !== 'beginInterval'} 156 | />{' '} 157 |  秒执行一次 158 | 159 | 160 | 具体秒数(可多选) 161 | 179 | {/* { 182 | if (value.length < 1) { 183 | return message.warn("至少选择一项"); 184 | } 185 | this.changeParams("some", value); 186 | }} 187 | options={this.secondOptions} 188 | disabled={type !== "some"} 189 | /> */} 190 | 191 | 192 | 193 |
194 | ) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/components/Week.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 功能:周期-周 3 | * 作者:宋鑫鑫 4 | * 日期:2019.11.04 5 | */ 6 | import React, { PureComponent } from 'react' 7 | import { Radio, InputNumber, Row, Col, Select, List, Checkbox, message } from 'antd' 8 | const { Group } = Radio 9 | import { isNumber } from '../utils/index' 10 | 11 | export default class Week extends PureComponent { 12 | weekOptions = [ 13 | { 14 | label: '星期日', 15 | value: '1', 16 | }, 17 | { 18 | label: '星期一', 19 | value: '2', 20 | }, 21 | { 22 | label: '星期二', 23 | value: '3', 24 | }, 25 | { 26 | label: '星期三', 27 | value: '4', 28 | }, 29 | { 30 | label: '星期四', 31 | value: '5', 32 | }, 33 | { 34 | label: '星期五', 35 | value: '6', 36 | }, 37 | { 38 | label: '星期六', 39 | value: '7', 40 | }, 41 | ] 42 | 43 | getWeekOptions() { 44 | return this.weekOptions.map((item, index) => { 45 | return ( 46 | 47 | {item.label} 48 | 49 | ) 50 | }) 51 | } 52 | 53 | changeParams(type, value) { 54 | const state = { ...this.props.week } 55 | state[type] = value 56 | // if (type === 'start') { 57 | // if (state.end - state.start <= 1) { 58 | // state.end = value + 1; 59 | // } 60 | // } 61 | // if (type === 'end') { 62 | // if (state.end - state.start <= 1) { 63 | // state.start = value - 1; 64 | // } 65 | // } 66 | this.props.onChange(state) 67 | } 68 | 69 | render() { 70 | const { 71 | week: { type, start, end, some, begin, beginEvery, last }, 72 | } = this.props 73 | return ( 74 |
75 | { 78 | const state = { ...this.props.week } 79 | // if (e.target.value === "some") { 80 | // state.some = ["1"]; 81 | // } 82 | state.type = e.target.value 83 | this.props.onChange(state) 84 | }} 85 | > 86 | 87 | 88 | 每周 89 | 90 | 91 | 不指定 92 | 93 | 94 | 周期从{' '} 95 | {' '} 107 | 到{' '} 108 | 120 | 121 | 122 | 第{' '} 123 | value.toString().replace(/[^\d\.]/g, '')} 130 | onChange={(value) => { 131 | if (isNumber(value) && Number(value) >= 1 && Number(value) <= 4) { 132 | this.changeParams('begin', value) 133 | } else { 134 | message.info('输入不合法') 135 | } 136 | }} 137 | disabled={type !== 'beginInterval'} 138 | />{' '} 139 | 周的{' '} 140 | 153 | 154 | 155 | 156 | 本月最后一个 157 | 169 | 170 | 171 | 具体星期数(可多选) 172 | 189 | {/* { 193 | this.changeParams("some", value); 194 | }} 195 | options={this.weekOptions} 196 | disabled={type !== "some"} 197 | /> */} 198 | 199 | 200 | 201 |
202 | ) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/components/Year.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 功能:周期-年 3 | * 作者:宋鑫鑫 4 | * 日期:2019.11.04 5 | */ 6 | import React, { PureComponent } from 'react' 7 | import { Radio, InputNumber, message, Col, List } from 'antd' 8 | const { Group } = Radio 9 | import { isNumber } from '../utils/index' 10 | 11 | export default class Year extends PureComponent { 12 | changeParams(type, value) { 13 | const state = { ...this.props.year } 14 | state[type] = value 15 | if (type === 'start') { 16 | if (state.end - state.start <= 1) { 17 | state.end = value + 1 18 | } 19 | } 20 | if (type === 'end') { 21 | if (state.end - state.start <= 1) { 22 | state.start = value - 1 23 | } 24 | } 25 | this.props.onChange(state) 26 | } 27 | 28 | render() { 29 | const { 30 | year: { type, start, end }, 31 | } = this.props 32 | return ( 33 |
34 | { 37 | this.changeParams('type', e.target.value) 38 | }} 39 | defaultValue="" 40 | > 41 | 42 | 43 | 不指定 44 | 45 | 46 | 每年 47 | 48 | 49 | 周期 50 | value.toString().replace(/[^\d\.]/g, '')} 55 | onChange={(value) => { 56 | if (isNumber(value) && Number(value) >= new Date().getFullYear()) { 57 | this.changeParams('start', value) 58 | } else { 59 | message.info('输入不合法') 60 | } 61 | }} 62 | disabled={type !== 'period'} 63 | /> 64 | {' - '} 65 | value.toString().replace(/[^\d\.]/g, '')} 70 | onChange={(value) => { 71 | if (isNumber(value) && Number(value) >= new Date().getFullYear() + 1) { 72 | this.changeParams('end', value) 73 | } else { 74 | message.info('输入不合法') 75 | } 76 | }} 77 | disabled={type !== 'period'} 78 | /> 79 | 80 | 81 | 82 |
83 | ) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/css/index.less: -------------------------------------------------------------------------------- 1 | .cron-editor-mlamp-react { 2 | min-width: 200px; 3 | overflow: auto; 4 | .ant-radio-group { 5 | width: 100%; 6 | } 7 | .ant-select { 8 | min-width: 90px; 9 | } 10 | .highlight { 11 | border: 1px solid #40a9ff; 12 | color: #000; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 功能:主界面 3 | * 作者:宋鑫鑫 4 | * 日期:2019.11.04 5 | */ 6 | import React, { PureComponent } from 'react' 7 | import classNames from 'classnames' 8 | import PropTypes from 'prop-types' 9 | import { Tabs, Dropdown, Row, Col, Input, List, Collapse } from 'antd' 10 | import Year from './components/Year' 11 | import Month from './components/Month' 12 | import Week from './components/Week' 13 | import Day from './components/Day' 14 | import Hour from './components/Hour' 15 | import Minute from './components/Minute' 16 | import Second from './components/Second' 17 | import CronParse from './utils/parse-lib' 18 | import CronParser from 'cron-parser' 19 | import moment from 'moment' 20 | const { TabPane } = Tabs 21 | const { Panel } = Collapse 22 | import './css/index.less' 23 | 24 | const noop = function () {} 25 | 26 | const dateMinute = 'YYYY-MM-DD HH:mm' 27 | 28 | class Cron extends PureComponent { 29 | constructor(props) { 30 | super(props) 31 | const date = new Date() 32 | this.state = { 33 | activeKey: 'second', 34 | year: { 35 | type: '', 36 | start: date.getFullYear(), 37 | end: date.getFullYear() + 1, 38 | }, 39 | month: { 40 | start: 1, 41 | end: 2, 42 | begin: 1, 43 | beginEvery: 1, 44 | type: '*', 45 | some: ['1'], 46 | }, 47 | week: { 48 | start: '1', 49 | end: '2', 50 | last: '1', 51 | begin: 1, 52 | beginEvery: '1', 53 | type: '?', 54 | some: ['1'], 55 | }, 56 | day: { 57 | last: 1, 58 | closeWorkDay: 1, 59 | start: 1, 60 | end: 2, 61 | begin: 1, 62 | beginEvery: 1, 63 | type: '*', 64 | some: ['1'], 65 | }, 66 | hour: { 67 | start: 0, 68 | end: 1, 69 | begin: 0, 70 | beginEvery: 1, 71 | type: '*', 72 | some: ['0'], 73 | }, 74 | minute: { 75 | start: 0, 76 | end: 1, 77 | begin: 0, 78 | beginEvery: 1, 79 | type: '*', 80 | some: ['0'], 81 | }, 82 | second: { 83 | start: 0, 84 | end: 1, 85 | begin: 0, 86 | beginEvery: 1, 87 | type: '*', 88 | some: ['0'], 89 | }, 90 | runTime: [], 91 | } 92 | } 93 | 94 | initValue() { 95 | let { value } = this.props 96 | value = value.toUpperCase() 97 | const valuesArray = value.split(' ') 98 | let newState = { ...this.state } 99 | newState.second.value = valuesArray[0] || '' 100 | newState.minute.value = valuesArray[1] || '' 101 | newState.hour.value = valuesArray[2] || '' 102 | newState.day.value = valuesArray[3] || '' 103 | newState.month.value = valuesArray[4] || '' 104 | newState.week.value = valuesArray[5] || '' 105 | newState.year.value = valuesArray[6] || '' 106 | this.setState(newState, () => { 107 | this.parse() 108 | }) 109 | } 110 | 111 | componentDidMount(props) { 112 | this.initValue(props) 113 | } 114 | 115 | componentDidUpdate(props) { 116 | const { value } = this.props 117 | if (props.value !== value && value) { 118 | this.initValue() 119 | } 120 | } 121 | 122 | parse() { 123 | let { year, month, week, day, hour, minute, second } = this.state 124 | if (year.value.indexOf('-') > -1) { 125 | year.type = 'period' 126 | year.start = year.value.split('-')[0] 127 | year.end = year.value.split('-')[1] 128 | } else { 129 | year.type = year.value 130 | } 131 | if (week.value.indexOf('-') > -1) { 132 | week.type = 'period' 133 | week.start = week.value.split('-')[0] 134 | week.end = week.value.split('-')[1] 135 | } else if (week.value.indexOf('L') > -1) { 136 | week.type = 'last' 137 | week.last = week.value.split('L')[0] || 1 138 | } else if (week.value.indexOf('#') > -1) { 139 | week.type = 'beginInterval' 140 | week.begin = week.value.split('#')[1] 141 | week.beginEvery = week.value.split('#')[0] 142 | } else if (week.value.indexOf(',') > -1 || /^[0-9]+$/.test(week.value)) { 143 | week.type = 'some' 144 | week.some = week.value.split(',') 145 | } else { 146 | week.type = week.value || '?' 147 | } 148 | 149 | if (month.value.indexOf('-') > -1) { 150 | month.type = 'period' 151 | month.start = month.value.split('-')[0] 152 | month.end = month.value.split('-')[1] 153 | } else if (month.value.indexOf('/') > -1) { 154 | month.type = 'beginInterval' 155 | month.begin = month.value.split('/')[0] 156 | month.beginEvery = month.value.split('/')[1] 157 | } else if (month.value.indexOf(',') > -1 || /^[0-9]+$/.test(month.value)) { 158 | month.type = 'some' 159 | month.some = month.value.split(',') 160 | } else { 161 | month.type = month.value || '?' 162 | } 163 | 164 | if (day.value.indexOf('-') > -1) { 165 | day.type = 'period' 166 | day.start = day.value.split('-')[0] 167 | day.end = day.value.split('-')[1] 168 | } else if (day.value.indexOf('W') > -1) { 169 | day.type = 'closeWorkDay' 170 | day.closeWorkDay = day.value.split('W')[0] || 1 171 | } else if (day.value.indexOf('L') > -1) { 172 | day.type = 'last' 173 | day.last = day.value.split('L')[0] || 1 174 | } else if (day.value.indexOf('/') > -1) { 175 | day.type = 'beginInterval' 176 | day.begin = day.value.split('/')[0] 177 | day.beginEvery = day.value.split('/')[1] 178 | } else if (day.value.indexOf(',') > -1 || /^[0-9]+$/.test(day.value)) { 179 | day.type = 'some' 180 | day.some = day.value.split(',') 181 | } else { 182 | day.type = day.value || '?' 183 | } 184 | 185 | if (hour.value.indexOf('-') > -1) { 186 | hour.type = 'period' 187 | hour.start = hour.value.split('-')[0] 188 | hour.end = hour.value.split('-')[1] 189 | } else if (hour.value.indexOf('/') > -1) { 190 | hour.type = 'beginInterval' 191 | hour.begin = hour.value.split('/')[0] 192 | hour.beginEvery = hour.value.split('/')[1] 193 | } else if (hour.value.indexOf(',') > -1 || /^[0-9]+$/.test(hour.value)) { 194 | hour.type = 'some' 195 | hour.some = hour.value.split(',') 196 | } else { 197 | hour.type = hour.value || '?' 198 | } 199 | 200 | if (minute.value.indexOf('-') > -1) { 201 | minute.type = 'period' 202 | minute.start = minute.value.split('-')[0] 203 | minute.end = minute.value.split('-')[1] 204 | } else if (minute.value.indexOf('/') > -1) { 205 | minute.type = 'beginInterval' 206 | minute.begin = minute.value.split('/')[0] 207 | minute.beginEvery = minute.value.split('/')[1] 208 | } else if (minute.value.indexOf(',') > -1 || /^[0-9]+$/.test(minute.value)) { 209 | minute.type = 'some' 210 | minute.some = minute.value.split(',') 211 | } else { 212 | minute.type = minute.value || '?' 213 | } 214 | 215 | if (second.value.indexOf('-') > -1) { 216 | second.type = 'period' 217 | second.start = second.value.split('-')[0] 218 | second.end = second.value.split('-')[1] 219 | } else if (second.value.indexOf('/') > -1) { 220 | second.type = 'beginInterval' 221 | second.begin = second.value.split('/')[0] 222 | second.beginEvery = second.value.split('/')[1] 223 | } else if (second.value.indexOf(',') > -1 || /^[0-9]+$/.test(second.value)) { 224 | second.type = 'some' 225 | second.some = second.value.split(',') 226 | } else { 227 | second.type = second.value || '?' 228 | } 229 | this.setState({ 230 | year: { ...year }, 231 | month: { ...month }, 232 | week: { ...week }, 233 | day: { ...day }, 234 | hour: { ...hour }, 235 | minute: { ...minute }, 236 | second: { ...second }, 237 | }) 238 | console.log('this.state :', this.state) 239 | } 240 | 241 | format() { 242 | const { year, month, week, day, hour, minute, second } = this.state 243 | return `${second.value} ${minute.value} ${hour.value} ${day.value} ${month.value} ${week.value} ${year.value}` 244 | } 245 | 246 | changeState(state) { 247 | this.setState(state, () => { 248 | this.culcCron() 249 | }) 250 | } 251 | 252 | // 计算用户的cron 253 | culcCron() { 254 | const { n2s } = this 255 | let { year, month, week, day, hour, minute, second } = this.state 256 | if (year.type === 'period') { 257 | year.value = `${n2s(year.start)}-${n2s(year.end)}` 258 | } else { 259 | year.value = year.type 260 | } 261 | if (month.type === 'period') { 262 | month.value = `${n2s(month.start)}-${n2s(month.end)}` 263 | } else if (month.type === 'beginInterval') { 264 | month.value = `${n2s(month.begin)}/${n2s(month.beginEvery)}` 265 | } else if (month.type === 'some') { 266 | month.value = month.some.join(',') 267 | } else { 268 | month.value = month.type 269 | } 270 | if (week.type === 'period') { 271 | week.value = `${n2s(week.start)}-${n2s(week.end)}` 272 | } else if (week.type === 'beginInterval') { 273 | week.value = `${n2s(week.beginEvery)}#${n2s(week.begin)}` 274 | } else if (week.type === 'last') { 275 | week.value = n2s(week.last) + 'L' 276 | } else if (week.type === 'some') { 277 | week.value = week.some.join(',') 278 | } else { 279 | week.value = week.type 280 | } 281 | if (day.type === 'period') { 282 | day.value = `${n2s(day.start)}-${n2s(day.end)}` 283 | } else if (day.type === 'beginInterval') { 284 | day.value = `${n2s(day.begin)}/${n2s(day.beginEvery)}` 285 | } else if (day.type === 'closeWorkDay') { 286 | day.value = n2s(day.closeWorkDay || 1) + 'W' 287 | } else if (day.type === 'last') { 288 | // day.value = n2s(day.last || 1) + "L"; 289 | day.value = 'L' 290 | } else if (day.type === 'some') { 291 | day.value = day.some.join(',') 292 | } else { 293 | day.value = day.type 294 | } 295 | if (hour.type === 'period') { 296 | hour.value = `${n2s(hour.start)}-${n2s(hour.end)}` 297 | } else if (hour.type === 'beginInterval') { 298 | hour.value = `${n2s(hour.begin)}/${n2s(hour.beginEvery)}` 299 | } else if (hour.type === 'some') { 300 | hour.value = hour.some.join(',') 301 | } else { 302 | hour.value = hour.type 303 | } 304 | if (minute.type === 'period') { 305 | minute.value = `${n2s(minute.start)}-${n2s(minute.end)}` 306 | } else if (minute.type === 'beginInterval') { 307 | minute.value = `${n2s(minute.begin)}/${n2s(minute.beginEvery)}` 308 | } else if (minute.type === 'some') { 309 | minute.value = minute.some.join(',') 310 | } else { 311 | minute.value = minute.type 312 | } 313 | if (second.type === 'period') { 314 | second.value = `${n2s(second.start)}-${n2s(second.end)}` 315 | } else if (second.type === 'beginInterval') { 316 | second.value = `${n2s(second.begin)}/${n2s(second.beginEvery)}` 317 | } else if (second.type === 'some') { 318 | second.value = second.some.join(',') 319 | } else { 320 | second.value = second.type 321 | } 322 | this.setState( 323 | { 324 | year: { ...year }, 325 | month: { ...month }, 326 | week: { ...week }, 327 | day: { ...day }, 328 | hour: { ...hour }, 329 | minute: { ...minute }, 330 | second: { ...second }, 331 | }, 332 | () => { 333 | this.triggerChange() 334 | } 335 | ) 336 | } 337 | 338 | n2s(number) { 339 | if (typeof number === 'number' && number !== NaN) { 340 | return `${number}` 341 | } 342 | return number 343 | } 344 | 345 | triggerChange() { 346 | const { onChange, showRunTime } = this.props 347 | const crontab = this.format() 348 | console.log('crontab', crontab) 349 | onChange && onChange(crontab) 350 | if (!showRunTime) return // 既然不需要,那就不算了 351 | let tempArr = [] 352 | const weekCron = crontab.split(' ')[5] 353 | try { 354 | if (weekCron !== '?') { 355 | const interval = CronParser.parseExpression(String(crontab).trim()) 356 | for (let i = 0; i < 5; i++) { 357 | const temp = moment(interval.next().toString()).format(dateMinute) 358 | tempArr.push(temp) 359 | } 360 | } else { 361 | const cron = new CronParse() 362 | tempArr = cron.expressionChange(String(crontab).trim()) 363 | } 364 | } catch (error) { 365 | // console.log("error :", error); 366 | tempArr.push('暂无最新执行周期') 367 | } 368 | if (tempArr.length > 0) { 369 | this.setState({ 370 | runTime: tempArr, 371 | }) 372 | } 373 | } 374 | 375 | // 发生表单值改变,重新计算 376 | onChange = (type, value) => { 377 | this.state[type].value = value 378 | 379 | this.setState({ ...this.state }, () => { 380 | this.parse() 381 | }) 382 | } 383 | 384 | renderOverLay() { 385 | const { activeKey, week, day } = this.state 386 | const { tabType } = this.props 387 | return ( 388 | { 391 | this.setState({ activeKey: key }) 392 | }} 393 | type={tabType} 394 | > 395 | 396 | { 399 | this.changeState({ second: state }) 400 | }} 401 | /> 402 | 403 | 404 | { 407 | this.changeState({ minute: state }) 408 | }} 409 | /> 410 | 411 | 412 | { 415 | this.changeState({ hour: state }) 416 | }} 417 | /> 418 | 419 | 420 | { 423 | if (week.type === '?' && state.type === '?') { 424 | const obj = { ...week, type: '*' } 425 | console.log('obj', obj) 426 | this.setState({ 427 | week: obj, 428 | }) 429 | } else { 430 | const obj = { ...week, type: '?' } 431 | console.log('obj', obj) 432 | this.setState({ 433 | week: obj, 434 | }) 435 | } 436 | this.changeState({ day: state }) 437 | }} 438 | /> 439 | 440 | 441 | { 444 | if (day.type === '?' && state.type === '?') { 445 | const obj = { ...week, type: '*' } 446 | console.log('obj', obj) 447 | this.setState({ 448 | day: obj, 449 | }) 450 | } else { 451 | const obj = { ...week, type: '?' } 452 | console.log('obj', obj) 453 | this.setState({ 454 | day: obj, 455 | }) 456 | } 457 | 458 | this.changeState({ week: state }) 459 | }} 460 | /> 461 | 462 | 463 | { 466 | this.changeState({ month: state }) 467 | }} 468 | /> 469 | 470 | 471 | 472 | { 475 | this.changeState({ year: state }) 476 | }} 477 | /> 478 | 479 | 480 | ) 481 | } 482 | 483 | render() { 484 | const state = JSON.parse(JSON.stringify(this.state)) 485 | const { year, month, week, day, hour, minute, second, runTime, activeKey } = state 486 | const { showRunTime, showCrontab } = this.props 487 | return ( 488 |
489 | {this.renderOverLay()} 490 | {showCrontab && ( 491 | 492 | 493 | 494 | 秒 495 | 分 496 | 小时 497 | 天 498 | 月 499 | 星期 500 | 年 501 | 502 | 503 | 504 | 505 | 506 | { 510 | this.onChange('second', e.target.value) 511 | }} 512 | disabled 513 | /> 514 | 515 | 516 | { 519 | this.onChange('minute', e.target.value) 520 | }} 521 | className={classNames({ highlight: activeKey === 'minute' })} 522 | disabled 523 | /> 524 | 525 | 526 | { 530 | this.onChange('hour', e.target.value) 531 | }} 532 | disabled 533 | /> 534 | 535 | 536 | { 540 | this.onChange('day', e.target.value) 541 | }} 542 | disabled 543 | /> 544 | 545 | 546 | { 550 | this.onChange('month', e.target.value) 551 | }} 552 | disabled 553 | /> 554 | 555 | 556 | { 560 | this.onChange('week', e.target.value) 561 | }} 562 | disabled 563 | /> 564 | 565 | 566 | { 570 | this.onChange('year', e.target.value) 571 | }} 572 | disabled 573 | /> 574 | 575 | 576 | 577 | 578 | )} 579 | {showRunTime && ( 580 | 581 | 582 | ( 586 | 587 | 第{index + 1}执行时间: {item} 588 | 589 | )} 590 | /> 591 | 592 | 593 | )} 594 |
595 | ) 596 | } 597 | } 598 | 599 | Cron.propTypes = { 600 | onChange: PropTypes.func, 601 | showRunTime: PropTypes.bool, 602 | value: PropTypes.string, 603 | tabType: PropTypes.string, 604 | showCrontab: PropTypes.bool, 605 | } 606 | 607 | Cron.defaultProps = { 608 | onChange: noop, 609 | showRunTime: false, 610 | value: '0 0 0 * * ? *', 611 | tabType: 'line', 612 | showCrontab: true, 613 | } 614 | 615 | export default Cron 616 | -------------------------------------------------------------------------------- /src/test/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub' 2 | -------------------------------------------------------------------------------- /src/test/__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process: function () { 3 | return '' 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /src/test/__test__/app.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Todo from '../../index' 3 | import { shallow } from 'enzyme' 4 | 5 | const setup = () => { 6 | const props = { 7 | text: 'todo-1', 8 | completed: false, 9 | onClick: jest.fn(), 10 | onRemoveTodoClick: jest.fn(), 11 | } 12 | const wrapper = shallow() 13 | return { 14 | props, 15 | wrapper, 16 | } 17 | } 18 | 19 | describe('Todo', () => { 20 | const { props, wrapper } = setup() 21 | 22 | // 通过 input 是否存在来判断 Todo组件是否被渲染 23 | it('Todo item should render', () => { 24 | expect(wrapper.find('input').length).toBe(1) 25 | }) 26 | 27 | // 当点击 单选按钮,onClick 方法应该被调用 28 | it('click checkbox input, onClick called', () => { 29 | const mockEvent = { 30 | key: 'Click', 31 | } 32 | wrapper.find('input').simulate('click', mockEvent) 33 | expect(props.onClick).toBeCalled() 34 | }) 35 | 36 | it('the item should remove when click remove button', () => { 37 | const mockEvent = { 38 | key: 'Click', 39 | } 40 | wrapper.find('button').simulate('click', mockEvent) 41 | expect(props.onRemoveTodoClick).toBeCalled() 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export const isNumber = (obj) => { 2 | return typeof obj === 'number' && !isNaN(obj) 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/parse-lib.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 功能:解析cron类 3 | * 作者:宋鑫鑫 4 | * 日期:2019.11.04 5 | */ 6 | 7 | class CronParse { 8 | constructor() { 9 | // constructor是一个构造方法,用来接收参数 10 | this.dayRule = ""; 11 | this.dayRuleSup = ""; 12 | this.dateArr = []; 13 | this.resultList = []; 14 | this.isShow = false; 15 | } 16 | 17 | // 表达式值变化时,开始去计算结果 18 | expressionChange(cron) { 19 | // 计算开始-隐藏结果 20 | this.isShow = false; 21 | // 获取规则数组[0秒、1分、2时、3日、4月、5星期、6年] 22 | const ruleArr = cron.split(" "); 23 | // 用于记录进入循环的次数 24 | let nums = 0; 25 | // 用于暂时存符号时间规则结果的数组 26 | const resultArr = []; 27 | // 获取当前时间精确至[年、月、日、时、分、秒] 28 | const nTime = new Date(); 29 | const nYear = nTime.getFullYear(); 30 | let nMouth = nTime.getMonth() + 1; 31 | let nDay = nTime.getDate(); 32 | let nHour = nTime.getHours(); 33 | let nMin = nTime.getMinutes(); 34 | let nSecond = nTime.getSeconds(); 35 | // 根据规则获取到近100年可能年数组、月数组等等 36 | this.getSecondArr(ruleArr[0]); 37 | this.getMinArr(ruleArr[1]); 38 | this.getHourArr(ruleArr[2]); 39 | this.getDayArr(ruleArr[3]); 40 | this.getMouthArr(ruleArr[4]); 41 | this.getWeekArr(ruleArr[5]); 42 | this.getYearArr(ruleArr[6], nYear); 43 | // 将获取到的数组赋值-方便使用 44 | const sDate = this.dateArr[0]; 45 | const mDate = this.dateArr[1]; 46 | const hDate = this.dateArr[2]; 47 | const DDate = this.dateArr[3]; 48 | const MDate = this.dateArr[4]; 49 | const YDate = this.dateArr[5]; 50 | // 获取当前时间在数组中的索引 51 | let sIdx = this.getIndex(sDate, nSecond); 52 | let mIdx = this.getIndex(mDate, nMin); 53 | let hIdx = this.getIndex(hDate, nHour); 54 | let DIdx = this.getIndex(DDate, nDay); 55 | let MIdx = this.getIndex(MDate, nMouth); 56 | const YIdx = this.getIndex(YDate, nYear); 57 | // 重置月日时分秒的函数(后面用的比较多) 58 | const resetSecond = function() { 59 | sIdx = 0; 60 | nSecond = sDate[sIdx]; 61 | }; 62 | const resetMin = function() { 63 | mIdx = 0; 64 | nMin = mDate[mIdx]; 65 | resetSecond(); 66 | }; 67 | const resetHour = function() { 68 | hIdx = 0; 69 | nHour = hDate[hIdx]; 70 | resetMin(); 71 | }; 72 | const resetDay = function() { 73 | DIdx = 0; 74 | nDay = DDate[DIdx]; 75 | resetHour(); 76 | }; 77 | const resetMouth = function() { 78 | MIdx = 0; 79 | nMouth = MDate[MIdx]; 80 | resetDay(); 81 | }; 82 | // 如果当前年份不为数组中当前值 83 | if (nYear !== YDate[YIdx]) { 84 | resetMouth(); 85 | } 86 | // 如果当前月份不为数组中当前值 87 | if (nMouth !== MDate[MIdx]) { 88 | resetDay(); 89 | } 90 | // 如果当前“日”不为数组中当前值 91 | if (nDay !== DDate[DIdx]) { 92 | resetHour(); 93 | } 94 | // 如果当前“时”不为数组中当前值 95 | if (nHour !== hDate[hIdx]) { 96 | resetMin(); 97 | } 98 | // 如果当前“分”不为数组中当前值 99 | if (nMin !== mDate[mIdx]) { 100 | resetSecond(); 101 | } 102 | 103 | // 循环年份数组 104 | goYear: for (let Yi = YIdx; Yi < YDate.length; Yi++) { 105 | const YY = YDate[Yi]; 106 | // 如果到达最大值时 107 | if (nMouth > MDate[MDate.length - 1]) { 108 | resetMouth(); 109 | continue; 110 | } 111 | // 循环月份数组 112 | goMouth: for (let Mi = MIdx; Mi < MDate.length; Mi++) { 113 | // 赋值、方便后面运算 114 | let MM = MDate[Mi]; 115 | MM = MM < 10 ? `0${MM}` : MM; 116 | // 如果到达最大值时 117 | if (nDay > DDate[DDate.length - 1]) { 118 | resetDay(); 119 | if (Mi === MDate.length - 1) { 120 | resetMouth(); 121 | continue goYear; 122 | } 123 | continue; 124 | } 125 | // 循环日期数组 126 | goDay: for (let Di = DIdx; Di < DDate.length; Di++) { 127 | // 赋值、方便后面运算 128 | let DD = DDate[Di]; 129 | let thisDD = DD < 10 ? `0${DD}` : DD; 130 | // 如果到达最大值时 131 | if (nHour > hDate[hDate.length - 1]) { 132 | resetHour(); 133 | if (Di === DDate.length - 1) { 134 | resetDay(); 135 | if (Mi === MDate.length - 1) { 136 | resetMouth(); 137 | continue goYear; 138 | } 139 | continue goMouth; 140 | } 141 | continue; 142 | } 143 | // 判断日期的合法性,不合法的话也是跳出当前循环 144 | if ( 145 | this.checkDate(`${YY}-${MM}-${thisDD} 00:00:00`) !== true && 146 | this.dayRule !== "workDay" && 147 | this.dayRule !== "lastWeek" && 148 | this.dayRule !== "lastDay" 149 | ) { 150 | resetDay(); 151 | continue goMouth; 152 | } 153 | // 如果日期规则中有值时 154 | if (this.dayRule === "lastDay") { 155 | // 如果不是合法日期则需要将前将日期调到合法日期即月末最后一天 156 | if (this.checkDate(`${YY}-${MM}-${thisDD} 00:00:00`) !== true) { 157 | while (this.checkDate(`${YY}-${MM}-${thisDD} 00:00:00`) !== true) { 158 | DD--; 159 | thisDD = DD < 10 ? `0${DD}` : DD; 160 | } 161 | } 162 | } else if (this.dayRule === "workDay") { 163 | // 校验并调整如果是2月30号这种日期传进来时需调整至正常月底 164 | if (this.checkDate(`${YY}-${MM}-${thisDD} 00:00:00`) !== true) { 165 | while (this.checkDate(`${YY}-${MM}-${thisDD} 00:00:00`) !== true) { 166 | DD--; 167 | thisDD = DD < 10 ? `0${DD}` : DD; 168 | } 169 | } 170 | // 获取达到条件的日期是星期X 171 | const thisWeek = this.formatDate(new Date(`${YY}-${MM}-${thisDD} 00:00:00`), "week"); 172 | // 当星期日时 173 | if (thisWeek === 0) { 174 | // 先找下一个日,并判断是否为月底 175 | DD++; 176 | thisDD = DD < 10 ? `0${DD}` : DD; 177 | // 判断下一日已经不是合法日期 178 | if (this.checkDate(`${YY}-${MM}-${thisDD} 00:00:00`) !== true) { 179 | DD -= 3; 180 | } 181 | } else if (thisWeek === 6) { 182 | // 当星期6时只需判断不是1号就可进行操作 183 | if (this.dayRuleSup !== 1) { 184 | DD--; 185 | } else { 186 | DD += 2; 187 | } 188 | } 189 | } else if (this.dayRule === "weekDay") { 190 | // 如果指定了是星期几 191 | // 获取当前日期是属于星期几 192 | const thisWeek = this.formatDate(new Date(`${YY}-${MM}-${DD} 00:00:00`), "week"); 193 | // 校验当前星期是否在星期池(dayRuleSup)中 194 | if (Array.indexOf(this.dayRuleSup, thisWeek) < 0) { 195 | // 如果到达最大值时 196 | if (Di === DDate.length - 1) { 197 | resetDay(); 198 | if (Mi === MDate.length - 1) { 199 | resetMouth(); 200 | continue goYear; 201 | } 202 | continue goMouth; 203 | } 204 | continue; 205 | } 206 | } else if (this.dayRule === "assWeek") { 207 | // 如果指定了是第几周的星期几 208 | // 获取每月1号是属于星期几 209 | const thisWeek = this.formatDate(new Date(`${YY}-${MM}-${DD} 00:00:00`), "week"); 210 | if (this.dayRuleSup[1] >= thisWeek) { 211 | DD = (this.dayRuleSup[0] - 1) * 7 + this.dayRuleSup[1] - thisWeek + 1; 212 | } else { 213 | DD = this.dayRuleSup[0] * 7 + this.dayRuleSup[1] - thisWeek + 1; 214 | } 215 | } else if (this.dayRule === "lastWeek") { 216 | // 如果指定了每月最后一个星期几 217 | // 校验并调整如果是2月30号这种日期传进来时需调整至正常月底 218 | if (this.checkDate(`${YY}-${MM}-${thisDD} 00:00:00`) !== true) { 219 | while (this.checkDate(`${YY}-${MM}-${thisDD} 00:00:00`) !== true) { 220 | DD--; 221 | thisDD = DD < 10 ? `0${DD}` : DD; 222 | } 223 | } 224 | // 获取月末最后一天是星期几 225 | const thisWeek = this.formatDate(new Date(`${YY}-${MM}-${thisDD} 00:00:00`), "week"); 226 | // 找到要求中最近的那个星期几 227 | if (this.dayRuleSup < thisWeek) { 228 | DD -= thisWeek - this.dayRuleSup; 229 | } else if (this.dayRuleSup > thisWeek) { 230 | DD -= 7 - (this.dayRuleSup - thisWeek); 231 | } 232 | } 233 | // 判断时间值是否小于10置换成“05”这种格式 234 | DD = DD < 10 ? `0${DD}` : DD; 235 | // 循环“时”数组 236 | goHour: for (let hi = hIdx; hi < hDate.length; hi++) { 237 | const hh = hDate[hi] < 10 ? `0${hDate[hi]}` : hDate[hi]; 238 | // 如果到达最大值时 239 | if (nMin > mDate[mDate.length - 1]) { 240 | resetMin(); 241 | if (hi === hDate.length - 1) { 242 | resetHour(); 243 | if (Di === DDate.length - 1) { 244 | resetDay(); 245 | if (Mi === MDate.length - 1) { 246 | resetMouth(); 247 | continue goYear; 248 | } 249 | continue goMouth; 250 | } 251 | continue goDay; 252 | } 253 | continue; 254 | } 255 | // 循环"分"数组 256 | goMin: for (let mi = mIdx; mi < mDate.length; mi++) { 257 | const mm = mDate[mi] < 10 ? `0${mDate[mi]}` : mDate[mi]; 258 | // 如果到达最大值时 259 | if (nSecond > sDate[sDate.length - 1]) { 260 | resetSecond(); 261 | if (mi === mDate.length - 1) { 262 | resetMin(); 263 | if (hi === hDate.length - 1) { 264 | resetHour(); 265 | if (Di === DDate.length - 1) { 266 | resetDay(); 267 | if (Mi === MDate.length - 1) { 268 | resetMouth(); 269 | continue goYear; 270 | } 271 | continue goMouth; 272 | } 273 | continue goDay; 274 | } 275 | continue goHour; 276 | } 277 | continue; 278 | } 279 | // 循环"秒"数组 280 | for (let si = sIdx; si <= sDate.length - 1; si++) { 281 | const ss = sDate[si] < 10 ? `0${sDate[si]}` : sDate[si]; 282 | // 添加当前时间(时间合法性在日期循环时已经判断) 283 | resultArr.push(`${YY}-${MM}-${DD} ${hh}:${mm}:${ss}`); 284 | nums++; 285 | // 如果条数满了就退出循环 286 | if (nums === 5) break goYear; 287 | // 如果到达最大值时 288 | if (si === sDate.length - 1) { 289 | resetSecond(); 290 | if (mi === mDate.length - 1) { 291 | resetMin(); 292 | if (hi === hDate.length - 1) { 293 | resetHour(); 294 | if (Di === DDate.length - 1) { 295 | resetDay(); 296 | if (Mi === MDate.length - 1) { 297 | resetMouth(); 298 | continue goYear; 299 | } 300 | continue goMouth; 301 | } 302 | continue goDay; 303 | } 304 | continue goHour; 305 | } 306 | continue goMin; 307 | } 308 | } // goSecond 309 | } // goMin 310 | } // goHour 311 | } // goDay 312 | } // goMouth 313 | } 314 | // 判断100年内的结果条数 315 | if (resultArr.length === 0) { 316 | this.resultList = ["没有达到条件的结果!"]; 317 | } else { 318 | this.resultList = resultArr; 319 | if (resultArr.length !== 5) { 320 | this.resultList.push(`最近100年内只有上面${resultArr.length}条结果!`); 321 | } 322 | } 323 | // 计算完成-显示结果 324 | this.isShow = true; 325 | return this.resultList; 326 | } 327 | 328 | // 用于计算某位数字在数组中的索引 329 | getIndex(arr, value) { 330 | if (value <= arr[0] || value > arr[arr.length - 1]) { 331 | return 0; 332 | } 333 | for (let i = 0; i < arr.length - 1; i++) { 334 | if (value > arr[i] && value <= arr[i + 1]) { 335 | return i + 1; 336 | } 337 | } 338 | } 339 | 340 | // 获取"年"数组 341 | getYearArr(rule, year) { 342 | this.dateArr[5] = this.getOrderArr(year, year + 100); 343 | if (rule !== undefined) { 344 | if (rule.indexOf("-") >= 0) { 345 | this.dateArr[5] = this.getCycleArr(rule, year + 100, false); 346 | } else if (rule.indexOf("/") >= 0) { 347 | this.dateArr[5] = this.getAverageArr(rule, year + 100); 348 | } else if (rule !== "*") { 349 | this.dateArr[5] = this.getAssignArr(rule); 350 | } 351 | } 352 | } 353 | 354 | // 获取"月"数组 355 | getMouthArr(rule) { 356 | this.dateArr[4] = this.getOrderArr(1, 12); 357 | if (rule.indexOf("-") >= 0) { 358 | this.dateArr[4] = this.getCycleArr(rule, 12, false); 359 | } else if (rule.indexOf("/") >= 0) { 360 | this.dateArr[4] = this.getAverageArr(rule, 12); 361 | } else if (rule !== "*") { 362 | this.dateArr[4] = this.getAssignArr(rule); 363 | } 364 | } 365 | 366 | // 获取"日"数组-主要为日期规则 367 | getWeekArr(rule) { 368 | // 只有当日期规则的两个值均为“”时则表达日期是有选项的 369 | if (this.dayRule === "" && this.dayRuleSup === "") { 370 | if (rule.indexOf("-") >= 0) { 371 | this.dayRule = "weekDay"; 372 | this.dayRuleSup = this.getCycleArr(rule, 7, false); 373 | } else if (rule.indexOf("#") >= 0) { 374 | this.dayRule = "assWeek"; 375 | const matchRule = rule.match(/[0-9]{1}/g); 376 | this.dayRuleSup = [Number(matchRule[0]), Number(matchRule[1])]; 377 | this.dateArr[3] = [1]; 378 | if (this.dayRuleSup[1] === 7) { 379 | this.dayRuleSup[1] = 0; 380 | } 381 | } else if (rule.indexOf("L") >= 0) { 382 | this.dayRule = "lastWeek"; 383 | this.dayRuleSup = Number(rule.match(/[0-9]{1,2}/g)[0]); 384 | this.dateArr[3] = [31]; 385 | if (this.dayRuleSup === 7) { 386 | this.dayRuleSup = 0; 387 | } 388 | } else if (rule !== "*" && rule !== "?") { 389 | this.dayRule = "weekDay"; 390 | this.dayRuleSup = this.getAssignArr(rule); 391 | } 392 | // 如果weekDay时将7调整为0【week值0即是星期日】 393 | if (this.dayRule === "weekDay") { 394 | for (let i = 0; i < this.dayRuleSup.length; i++) { 395 | if (this.dayRuleSup[i] === 7) { 396 | this.dayRuleSup[i] = 0; 397 | } 398 | } 399 | } 400 | } 401 | } 402 | 403 | // 获取"日"数组-少量为日期规则 404 | getDayArr(rule) { 405 | this.dateArr[3] = this.getOrderArr(1, 31); 406 | this.dayRule = ""; 407 | this.dayRuleSup = ""; 408 | if (rule.indexOf("-") >= 0) { 409 | this.dateArr[3] = this.getCycleArr(rule, 31, false); 410 | this.dayRuleSup = "null"; 411 | } else if (rule.indexOf("/") >= 0) { 412 | this.dateArr[3] = this.getAverageArr(rule, 31); 413 | this.dayRuleSup = "null"; 414 | } else if (rule.indexOf("W") >= 0) { 415 | this.dayRule = "workDay"; 416 | this.dayRuleSup = Number(rule.match(/[0-9]{1,2}/g)[0]); 417 | this.dateArr[3] = [this.dayRuleSup]; 418 | } else if (rule.indexOf("L") >= 0) { 419 | this.dayRule = "lastDay"; 420 | this.dayRuleSup = "null"; 421 | this.dateArr[3] = [31]; 422 | } else if (rule !== "*" && rule !== "?") { 423 | this.dateArr[3] = this.getAssignArr(rule); 424 | this.dayRuleSup = "null"; 425 | } else if (rule === "*") { 426 | this.dayRuleSup = "null"; 427 | } 428 | } 429 | 430 | // 获取"时"数组 431 | getHourArr(rule) { 432 | this.dateArr[2] = this.getOrderArr(0, 23); 433 | if (rule.indexOf("-") >= 0) { 434 | this.dateArr[2] = this.getCycleArr(rule, 24, true); 435 | } else if (rule.indexOf("/") >= 0) { 436 | this.dateArr[2] = this.getAverageArr(rule, 23); 437 | } else if (rule !== "*") { 438 | this.dateArr[2] = this.getAssignArr(rule); 439 | } 440 | } 441 | 442 | // 获取"分"数组 443 | getMinArr(rule) { 444 | this.dateArr[1] = this.getOrderArr(0, 59); 445 | if (rule.indexOf("-") >= 0) { 446 | this.dateArr[1] = this.getCycleArr(rule, 60, true); 447 | } else if (rule.indexOf("/") >= 0) { 448 | this.dateArr[1] = this.getAverageArr(rule, 59); 449 | } else if (rule !== "*") { 450 | this.dateArr[1] = this.getAssignArr(rule); 451 | } 452 | } 453 | 454 | // 获取"秒"数组 455 | getSecondArr(rule) { 456 | this.dateArr[0] = this.getOrderArr(0, 59); 457 | if (rule.indexOf("-") >= 0) { 458 | this.dateArr[0] = this.getCycleArr(rule, 60, true); 459 | } else if (rule.indexOf("/") >= 0) { 460 | this.dateArr[0] = this.getAverageArr(rule, 59); 461 | } else if (rule !== "*") { 462 | this.dateArr[0] = this.getAssignArr(rule); 463 | } 464 | } 465 | 466 | // 根据传进来的min-max返回一个顺序的数组 467 | getOrderArr(min, max) { 468 | const arr = []; 469 | for (let i = min; i <= max; i++) { 470 | arr.push(i); 471 | } 472 | return arr; 473 | } 474 | 475 | // 根据规则中指定的零散值返回一个数组 476 | getAssignArr(rule) { 477 | const arr = []; 478 | const assiginArr = rule.split(","); 479 | for (let i = 0; i < assiginArr.length; i++) { 480 | arr[i] = Number(assiginArr[i]); 481 | } 482 | arr.sort(this.compare); 483 | return arr; 484 | } 485 | 486 | // 根据一定算术规则计算返回一个数组 487 | getAverageArr(rule, limit) { 488 | const arr = []; 489 | const agArr = rule.split("/"); 490 | 491 | let min = Number(agArr[0]); 492 | const step = Number(agArr[1]); 493 | while (min <= limit) { 494 | arr.push(min); 495 | min += step; 496 | } 497 | return arr; 498 | } 499 | 500 | // 根据规则返回一个具有周期性的数组 501 | getCycleArr(rule, limit, status) { 502 | // status--表示是否从0开始(则从1开始) 503 | const arr = []; 504 | const cycleArr = rule.split("-"); 505 | const min = Number(cycleArr[0]); 506 | let max = Number(cycleArr[1]); 507 | if (min > max) { 508 | max += limit; 509 | } 510 | for (let i = min; i <= max; i++) { 511 | let add = 0; 512 | if (status === false && i % limit === 0) { 513 | add = limit; 514 | } 515 | arr.push(Math.round((i % limit) + add)); 516 | } 517 | arr.sort(this.compare); 518 | return arr; 519 | } 520 | 521 | // 比较数字大小(用于Array.sort) 522 | compare(value1, value2) { 523 | if (value2 - value1 > 0) { 524 | return -1; 525 | } 526 | return 1; 527 | } 528 | 529 | // 格式化日期格式如:2017-9-19 18:04:33 530 | formatDate(value, type) { 531 | // 计算日期相关值 532 | const time = typeof value === "number" ? new Date(value) : value; 533 | const Y = time.getFullYear(); 534 | const M = time.getMonth() + 1; 535 | const D = time.getDate(); 536 | const h = time.getHours(); 537 | const m = time.getMinutes(); 538 | const s = time.getSeconds(); 539 | const week = time.getDay(); 540 | // 如果传递了type的话 541 | if (type === undefined) { 542 | return `${Y}-${M < 10 ? `0${M}` : M}-${D < 10 ? `0${D}` : D} ${h < 10 ? `0${h}` : h}:${m < 10 ? `0${m}` : m}:${s < 10 ? `0${s}` : s}`; 543 | } 544 | if (type === "week") { 545 | return week; 546 | } 547 | } 548 | 549 | // 检查日期是否存在 550 | checkDate(value) { 551 | const time = new Date(value); 552 | const format = this.formatDate(time); 553 | return value === format; 554 | } 555 | } 556 | 557 | // export default { 558 | // CronParse 559 | // } 560 | export default CronParse; 561 | // module.exports = CronParse 562 | -------------------------------------------------------------------------------- /webpack.config.build.js: -------------------------------------------------------------------------------- 1 | const webpackConfig = { 2 | entry: './src/index.jsx', 3 | output: { 4 | filename: 'index.js', 5 | library: 'antd-cron', 6 | libraryTarget: 'umd', 7 | libraryExport: 'default', // 默认导出 8 | }, 9 | externals: { 10 | react: 'react', //打包时候排除react 11 | antd: 'antd', 12 | reactDom: 'react-dom', 13 | moment: 'moment', 14 | }, 15 | mode: 'production', 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.(js|jsx)$/, 20 | exclude: /(node_modules|bower_components)/, 21 | use: ['babel-loader'], 22 | }, 23 | { 24 | test: /\.less$/, 25 | use: [ 26 | { loader: 'style-loader' }, 27 | { loader: 'css-loader' }, 28 | { 29 | loader: 'less-loader', 30 | options: { 31 | javascriptEnabled: true, 32 | }, 33 | }, 34 | ], 35 | }, 36 | ], 37 | }, 38 | resolve: { 39 | extensions: ['.js', '.jsx'], 40 | }, 41 | } 42 | module.exports = webpackConfig 43 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const webpackConfig = { 3 | entry: './index.js', 4 | output: { 5 | filename: 'index.js', 6 | publicPath: '/', 7 | }, 8 | mode: 'development', 9 | devtool: 'cheap-module-eval-source-map', 10 | devServer: { 11 | port: 3000, 12 | open: true, 13 | host: '0.0.0.0', 14 | openPage: '/dist/index.html', 15 | hot: true, 16 | publicPath: '/', 17 | }, 18 | 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.(js|jsx)$/, 23 | exclude: /(node_modules|bower_components)/, 24 | use: [ 25 | { 26 | loader: 'babel-loader', 27 | options: { 28 | plugins: [ 29 | [ 30 | 'import', 31 | { 32 | libraryName: 'antd', 33 | libraryDirectory: 'lib', 34 | style: true, 35 | }, 36 | ], 37 | ], 38 | }, 39 | }, 40 | ], 41 | }, 42 | { 43 | test: /\.less$/, 44 | use: [ 45 | { loader: 'style-loader' }, 46 | { loader: 'css-loader' }, 47 | { 48 | loader: 'less-loader', 49 | options: { 50 | javascriptEnabled: true, 51 | }, 52 | }, 53 | ], 54 | }, 55 | ], 56 | }, 57 | resolve: { 58 | extensions: ['.js', '.jsx'], 59 | }, 60 | plugins: [new webpack.HotModuleReplacementPlugin()], 61 | } 62 | 63 | module.exports = webpackConfig 64 | --------------------------------------------------------------------------------