import { Button, Input, Space, Switch, Tabs, Typography, Radio, Select } from '@arco-design/web-react' import { useEffect, useMemo, useState } from 'react' export interface CronInputProps { value?: string onChange?: (value: string) => void } const DEFAULT_CRON = '* * * * *' type CronPart = 'minute' | 'hour' | 'day' | 'month' | 'week' interface CronState { minute: string hour: string day: string month: string week: string } // 常用预设 const PRESETS = [ { label: '每天 02:00', value: '0 2 * * *' }, { label: '每天 00:00', value: '0 0 * * *' }, { label: '每 6 小时', value: '0 */6 * * *' }, { label: '每 12 小时', value: '0 */12 * * *' }, { label: '每周日 03:00', value: '0 3 * * 0' }, { label: '每月 1 日 02:00', value: '0 2 1 * *' }, { label: '每 30 分钟', value: '*/30 * * * *' }, { label: '每小时', value: '0 * * * *' }, ] function parseCron(expr: string): CronState { const parts = (expr || DEFAULT_CRON).trim().split(/\s+/) return { minute: parts[0] || '*', hour: parts[1] || '*', day: parts[2] || '*', month: parts[3] || '*', week: parts[4] || '*', } } function stringifyCron(state: CronState): string { return `${state.minute} ${state.hour} ${state.day} ${state.month} ${state.week}` } // 将 cron 表达式转为中文可读描述 function describeCron(expr: string): string { const parts = expr.trim().split(/\s+/) if (parts.length !== 5) return '' const [minute, hour, day, month, week] = parts const segments: string[] = [] // 月 if (month !== '*') segments.push(`${month} 月`) // 日 if (day !== '*') segments.push(`${day} 日`) // 周 if (week !== '*') { const weekNames: Record = { '0': '日', '1': '一', '2': '二', '3': '三', '4': '四', '5': '五', '6': '六', '7': '日' } const weekDesc = week.split(',').map((w) => weekNames[w] || w).join('、') segments.push(`星期${weekDesc}`) } // 小时 if (hour.includes('/')) { segments.push(`每 ${hour.split('/')[1]} 小时`) } else if (hour !== '*') { segments.push(`${hour.padStart(2, '0')} 时`) } // 分钟 if (minute.includes('/')) { segments.push(`每 ${minute.split('/')[1]} 分钟`) } else if (minute !== '*') { segments.push(`${minute.padStart(2, '0')} 分`) } else if (hour !== '*' && !hour.includes('/')) { segments.push('00 分') } if (segments.length === 0) return '每分钟执行' return segments.join(' ') + ' 执行' } function generateOptions(min: number, max: number) { return Array.from({ length: max - min + 1 }, (_, i) => ({ label: String(i + min), value: String(i + min), })) } const MINUTES_OPTIONS = generateOptions(0, 59) const HOURS_OPTIONS = generateOptions(0, 23) const DAYS_OPTIONS = generateOptions(1, 31) const MONTHS_OPTIONS = generateOptions(1, 12) const WEEKS_OPTIONS = [ { label: '星期日', value: '0' }, { label: '星期一', value: '1' }, { label: '星期二', value: '2' }, { label: '星期三', value: '3' }, { label: '星期四', value: '4' }, { label: '星期五', value: '5' }, { label: '星期六', value: '6' }, ] export function CronInput({ value, onChange }: CronInputProps) { const [internalValue, setInternalValue] = useState(value || DEFAULT_CRON) const [isAdvanced, setIsAdvanced] = useState(false) const [state, setState] = useState(parseCron(internalValue)) // Sync prop to internal state useEffect(() => { if (value !== undefined && value !== internalValue) { setInternalValue(value || DEFAULT_CRON) if (!isAdvanced) { setState(parseCron(value || DEFAULT_CRON)) } } }, [value, isAdvanced, internalValue]) const description = useMemo(() => describeCron(internalValue), [internalValue]) const notifyChange = (nextValue: string) => { setInternalValue(nextValue) if (onChange) { onChange(nextValue) } } const handleStateChange = (part: CronPart, val: string) => { const nextState = { ...state, [part]: val } setState(nextState) notifyChange(stringifyCron(nextState)) } const handlePreset = (cronExpr: string) => { setInternalValue(cronExpr) setState(parseCron(cronExpr)) if (onChange) onChange(cronExpr) } const renderPartTab = ( part: CronPart, title: string, options: { label: string; value: string }[], allowAnyVal = '*', ) => { const currentVal = state[part] const isAny = currentVal === allowAnyVal || currentVal === '*' || currentVal === '?' const isSpecific = !isAny && !currentVal.includes('/') && !currentVal.includes('-') const type = isAny ? 'any' : 'specific' const specificValues = isSpecific ? currentVal.split(',') : [] return (
{ if (val === 'any') { handleStateChange(part, allowAnyVal) } else { handleStateChange(part, options[0].value) } }} > 通配 ({allowAnyVal}) - 任意{title} 指定{title} {type === 'specific' && (
{ setInternalValue(val) if (isAdvanced && onChange) { onChange(val) } }} readOnly={!isAdvanced} style={{ width: 240, fontFamily: 'monospace' }} placeholder="* * * * *" /> 高级模式 { setIsAdvanced(checked) if (!checked) { setState(parseCron(internalValue)) notifyChange(stringifyCron(parseCron(internalValue))) } }} />
{/* 中文可读描述 */} {description && ( {description} )} {!isAdvanced && ( {renderPartTab('minute', '分钟', MINUTES_OPTIONS, '*')} {renderPartTab('hour', '小时', HOURS_OPTIONS, '*')} {renderPartTab('day', '日', DAYS_OPTIONS, '*')} {renderPartTab('month', '月', MONTHS_OPTIONS, '*')} {renderPartTab('week', '周', WEEKS_OPTIONS, '*')} )}
) }