index.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. /*
  2. * <<
  3. * Davinci
  4. * ==
  5. * Copyright (C) 2016 - 2017 EDP
  6. * ==
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. * >>
  19. */
  20. import React, {
  21. useEffect,
  22. useState,
  23. useCallback,
  24. useMemo,
  25. useImperativeHandle,
  26. forwardRef
  27. } from 'react'
  28. import moment, { Moment } from 'moment'
  29. import { Form, Row, Col, Input, Select, DatePicker, Spin, Checkbox } from 'antd'
  30. const FormItem = Form.Item
  31. const { TextArea } = Input
  32. const { Option } = Select
  33. const { RangePicker } = DatePicker
  34. import { FormComponentProps } from 'antd/lib/form'
  35. import {
  36. SchedulePeriodUnit,
  37. ISchedule,
  38. ICronExpressionPartition,
  39. JobType
  40. } from '../types'
  41. import { CheckboxChangeEvent } from 'antd/lib/checkbox'
  42. import { FormItemStyle, LongFormItemStyle } from '../constants'
  43. import Styles from './ScheduleBaseConfig.less'
  44. const periodUnitList: SchedulePeriodUnit[] = [
  45. 'Minute',
  46. 'Hour',
  47. 'Day',
  48. 'Week',
  49. 'Month',
  50. 'Year'
  51. ]
  52. const periodUnitListLocale: { [key in SchedulePeriodUnit]: string } = {
  53. Minute: '分钟',
  54. Hour: '小时',
  55. Day: '天',
  56. Week: '周',
  57. Month: '月',
  58. Year: '年'
  59. }
  60. const minutePeriodOptions = [...Array(50).keys()].map((s) => (
  61. <Option key={s + 10} value={s + 10}>
  62. {s + 10}
  63. </Option>
  64. ))
  65. const minuteOptions = [...Array(60).keys()].map((m) => (
  66. <Option key={m} value={m}>
  67. {`0${m}`.slice(-2)} 分
  68. </Option>
  69. ))
  70. const hourOptions = [...Array(24).keys()].map((h) => (
  71. <Option key={h} value={h}>
  72. {`0${h}`.slice(-2)} 时
  73. </Option>
  74. ))
  75. const dayOptions = [...Array(31).keys()].map((d) => (
  76. <Option key={d + 1} value={d + 1}>
  77. {`0${d + 1}`.slice(-2)} 日
  78. </Option>
  79. ))
  80. const weekOptions = [
  81. '星期天',
  82. '星期一',
  83. '星期二',
  84. '星期三',
  85. '星期四',
  86. '星期五',
  87. '星期六'
  88. ].map((w, idx) => (
  89. <Option key={idx + 1} value={idx + 1}>
  90. {w}
  91. </Option>
  92. ))
  93. const monthOptions = [...Array(12).keys()].map((m) => (
  94. <Option key={m + 1} value={m + 1}>
  95. {`0${m + 1}`.slice(-2)}月
  96. </Option>
  97. ))
  98. export type ScheduleBaseFormProps = ISchedule &
  99. ICronExpressionPartition & {
  100. dateRange: [Moment, Moment]
  101. setCronExpressionManually: boolean
  102. }
  103. interface IScheduleBaseConfigProps
  104. extends FormComponentProps<ScheduleBaseFormProps> {
  105. schedule: ISchedule
  106. loading: boolean
  107. onCheckUniqueName: (
  108. data: any,
  109. resolve: () => any,
  110. reject: (error: string) => any
  111. ) => any
  112. onChangeJobType: (data: any) => any
  113. }
  114. const computePeriodUnit = (cronExpression: string) => {
  115. const partitions = cronExpression.split(' ')
  116. const stars = partitions.filter((item) => item === '*').length
  117. let periodUnit: SchedulePeriodUnit = 'Minute'
  118. switch (stars) {
  119. case 3:
  120. periodUnit = partitions[1].includes('/') ? 'Minute' : 'Hour'
  121. break
  122. case 2:
  123. periodUnit = 'Day'
  124. break
  125. case 1:
  126. periodUnit = partitions[partitions.length - 1] === '?' ? 'Month' : 'Week'
  127. break
  128. case 0:
  129. periodUnit = 'Year'
  130. break
  131. }
  132. return periodUnit
  133. }
  134. export const ScheduleBaseConfig: React.FC<IScheduleBaseConfigProps> = (
  135. props,
  136. ref
  137. ) => {
  138. const { form, schedule, loading, onCheckUniqueName, onChangeJobType } = props
  139. const { cronExpression, config } = schedule
  140. const [currentPeriodUnit, setCurrentPeriodUnit] = useState<
  141. SchedulePeriodUnit
  142. >(computePeriodUnit(cronExpression))
  143. const [manual, setManual] = useState(config.setCronExpressionManually)
  144. const checkNameUnique = useCallback(
  145. (_, name = '', callback) => {
  146. const { id, projectId } = schedule
  147. const data = { id, name, projectId }
  148. if (!name) {
  149. callback()
  150. }
  151. onCheckUniqueName(
  152. data,
  153. () => {
  154. callback()
  155. },
  156. (err) => callback(err)
  157. )
  158. },
  159. [onCheckUniqueName, schedule]
  160. )
  161. const changeJobType = useCallback(
  162. (value: JobType) => {
  163. onChangeJobType(value)
  164. },
  165. [onChangeJobType]
  166. )
  167. const changeManual = useCallback((e: CheckboxChangeEvent) => {
  168. setManual(e.target.checked)
  169. }, [])
  170. useEffect(() => {
  171. const periodUnit = computePeriodUnit(cronExpression)
  172. setCurrentPeriodUnit(periodUnit)
  173. }, [cronExpression])
  174. useEffect(() => {
  175. setManual(config.setCronExpressionManually)
  176. }, [config.setCronExpressionManually])
  177. let { minute, hour, day, month, weekDay } = useMemo<
  178. Partial<ScheduleBaseFormProps>
  179. >(() => {
  180. const partitions = cronExpression.split(' ')
  181. let minute =
  182. form.getFieldValue('minute') ||
  183. +(partitions[1].includes('/')
  184. ? partitions[1].slice(2) // slice(2) to remove */
  185. : partitions[1])
  186. // min minute duration is 10
  187. if (currentPeriodUnit === 'Minute' && minute < 10) {
  188. minute = 10
  189. form.setFieldsValue({ minute })
  190. }
  191. const hour = +partitions[2] || 0
  192. const day = +partitions[3] || 1
  193. const month = +partitions[4] || 1
  194. const weekDay = +partitions[5] || 1
  195. return { minute, hour, day, month, weekDay }
  196. }, [cronExpression, currentPeriodUnit])
  197. const { getFieldDecorator } = form
  198. const { startDate, endDate } = schedule
  199. useImperativeHandle(ref, () => ({ form }))
  200. return (
  201. <Form>
  202. <Row>
  203. <Col span={12}>
  204. <FormItem label="名称" {...FormItemStyle} hasFeedback>
  205. {getFieldDecorator<ScheduleBaseFormProps>('name', {
  206. rules: [
  207. { required: true, message: '名称不能为空' },
  208. { validator: checkNameUnique }
  209. ],
  210. initialValue: schedule.name
  211. })(<Input autoComplete="new-name" />)}
  212. </FormItem>
  213. </Col>
  214. <Col span={12}>
  215. <FormItem label="类型" {...FormItemStyle}>
  216. {getFieldDecorator<ScheduleBaseFormProps>('jobType', {
  217. initialValue: schedule.jobType
  218. })(
  219. <Select onChange={changeJobType}>
  220. <Option value="email">Email</Option>
  221. <Option value="weChatWork">企业微信</Option>
  222. </Select>
  223. )}
  224. </FormItem>
  225. </Col>
  226. </Row>
  227. <FormItem label="描述" {...LongFormItemStyle}>
  228. {getFieldDecorator<ScheduleBaseFormProps>('description', {
  229. initialValue: schedule.description
  230. })(<TextArea />)}
  231. </FormItem>
  232. <FormItem label="有效时间范围" {...LongFormItemStyle}>
  233. {getFieldDecorator<ScheduleBaseFormProps>('dateRange', {
  234. initialValue: [
  235. startDate && moment(startDate),
  236. endDate && moment(endDate)
  237. ]
  238. })(
  239. <RangePicker
  240. style={{ width: '100%' }}
  241. showTime
  242. format="YYYY-MM-DD HH:mm:ss"
  243. />
  244. )}
  245. </FormItem>
  246. <FormItem label="执行时间设置" {...LongFormItemStyle}>
  247. {loading ? (
  248. <Spin />
  249. ) : (
  250. <Row className={Styles.cronSetting} gutter={8}>
  251. {manual ? (
  252. <Col span={12}>
  253. {getFieldDecorator<ScheduleBaseFormProps>('cronExpression', {
  254. rules: [{ required: true }],
  255. initialValue: cronExpression
  256. })(<Input placeholder="请输入cron表达式" />)}
  257. </Col>
  258. ) : (
  259. <>
  260. <span>每</span>
  261. {/* Minute */}
  262. {currentPeriodUnit === 'Minute' && (
  263. <>
  264. {getFieldDecorator<ScheduleBaseFormProps>('minute', {
  265. initialValue: minute
  266. })(
  267. <Select style={{ width: 80 }}>
  268. {minutePeriodOptions}
  269. </Select>
  270. )}
  271. </>
  272. )}
  273. {/** */}
  274. {getFieldDecorator<ScheduleBaseFormProps>('periodUnit', {
  275. initialValue: currentPeriodUnit
  276. })(
  277. <Select
  278. style={{ width: 80 }}
  279. onChange={(value: SchedulePeriodUnit) =>
  280. setCurrentPeriodUnit(value)
  281. }
  282. >
  283. {periodUnitList.map((unit) => (
  284. <Option key={unit} value={unit}>
  285. {periodUnitListLocale[unit]}
  286. </Option>
  287. ))}
  288. </Select>
  289. )}
  290. {/* Hour */}
  291. {currentPeriodUnit === 'Hour' && (
  292. <>
  293. <span>的第</span>
  294. {getFieldDecorator<ScheduleBaseFormProps>('minute', {
  295. initialValue: minute
  296. })(<Select style={{ width: 80 }}>{minuteOptions}</Select>)}
  297. </>
  298. )}
  299. {/* Day */}
  300. {currentPeriodUnit === 'Day' && (
  301. <>
  302. <span>的</span>
  303. {getFieldDecorator<ScheduleBaseFormProps>('hour', {
  304. initialValue: hour
  305. })(<Select style={{ width: 80 }}>{hourOptions}</Select>)}
  306. <span>:</span>
  307. {getFieldDecorator<ScheduleBaseFormProps>('minute', {
  308. initialValue: minute
  309. })(<Select style={{ width: 100 }}>{minuteOptions}</Select>)}
  310. </>
  311. )}
  312. {/* Week */}
  313. {currentPeriodUnit === 'Week' && (
  314. <>
  315. {getFieldDecorator<ScheduleBaseFormProps>('weekDay', {
  316. initialValue: weekDay
  317. })(<Select style={{ width: 95 }}>{weekOptions}</Select>)}
  318. <span>的</span>
  319. {getFieldDecorator<ScheduleBaseFormProps>('hour', {
  320. initialValue: hour
  321. })(<Select style={{ width: 80 }}>{hourOptions}</Select>)}
  322. <span>:</span>
  323. {getFieldDecorator<ScheduleBaseFormProps>('minute', {
  324. initialValue: minute
  325. })(<Select style={{ width: 80 }}>{minuteOptions}</Select>)}
  326. </>
  327. )}
  328. {/* Month */}
  329. {currentPeriodUnit === 'Month' && (
  330. <>
  331. {getFieldDecorator<ScheduleBaseFormProps>('day', {
  332. initialValue: day
  333. })(<Select style={{ width: 80 }}>{dayOptions}</Select>)}
  334. <span>的</span>
  335. {getFieldDecorator<ScheduleBaseFormProps>('hour', {
  336. initialValue: hour
  337. })(<Select style={{ width: 80 }}>{hourOptions}</Select>)}
  338. <span>:</span>
  339. {getFieldDecorator('minute', { initialValue: minute })(
  340. <Select style={{ width: 80 }}>{minuteOptions}</Select>
  341. )}
  342. </>
  343. )}
  344. {/* Year */}
  345. {currentPeriodUnit === 'Year' && (
  346. <>
  347. {getFieldDecorator<ScheduleBaseFormProps>('month', {
  348. initialValue: month
  349. })(<Select style={{ width: 80 }}>{monthOptions}</Select>)}
  350. {getFieldDecorator<ScheduleBaseFormProps>('day', {
  351. initialValue: day
  352. })(<Select style={{ width: 80 }}>{dayOptions}</Select>)}
  353. <span>的</span>
  354. {getFieldDecorator<ScheduleBaseFormProps>('hour', {
  355. initialValue: hour
  356. })(<Select style={{ width: 80 }}>{hourOptions}</Select>)}
  357. <span>:</span>
  358. {getFieldDecorator<ScheduleBaseFormProps>('minute', {
  359. initialValue: minute
  360. })(<Select style={{ width: 80 }}>{minuteOptions}</Select>)}
  361. </>
  362. )}
  363. </>
  364. )}
  365. {getFieldDecorator<ScheduleBaseFormProps>(
  366. 'setCronExpressionManually',
  367. { initialValue: manual, valuePropName: 'checked' }
  368. )(
  369. <Checkbox className={Styles.manual} onChange={changeManual}>
  370. 手动输入
  371. </Checkbox>
  372. )}
  373. </Row>
  374. )}
  375. </FormItem>
  376. </Form>
  377. )
  378. }
  379. export default Form.create<IScheduleBaseConfigProps>()(
  380. forwardRef(ScheduleBaseConfig)
  381. )