diff --git a/src/pages/sys/task/TaskForm.vue b/src/pages/sys/task/TaskForm.vue index 4a67ec4..61e0de3 100644 --- a/src/pages/sys/task/TaskForm.vue +++ b/src/pages/sys/task/TaskForm.vue @@ -77,7 +77,7 @@ function scheduleTypeChange(val: string) { } else if (val === 'Fixed') { taskFormData.value.scheduleConf = '1' } else if (val === 'Cron') { - taskFormData.value.scheduleConf = '* * * * * ? ?' + taskFormData.value.scheduleConf = '* * * * * ?' } } @@ -120,13 +120,8 @@ defineExpose({ }) } else { status.value = 'add' - data.scheduleConf = '* * * * * ? ?' + data.scheduleConf = '* * * * * ?' taskFormData.value = data - // taskFormData = {} - // for (const key in taskFormData) { - // taskFormData[key] = undefined - // // delete taskFormData[key] - // } } }, }) diff --git a/src/pages/sys/task/cron/Cron.vue b/src/pages/sys/task/cron/Cron.vue index 1c95547..d3b86d9 100644 --- a/src/pages/sys/task/cron/Cron.vue +++ b/src/pages/sys/task/cron/Cron.vue @@ -15,7 +15,6 @@ watch( () => props.modelValue, (newVal) => { if (newVal !== undefined && newVal !== cronVal.value) { - console.log('设置', newVal) cronVal.value = newVal } }, @@ -26,11 +25,11 @@ watch( cronVal, (newCronVal) => { if (newCronVal !== props.modelValue) { - console.log('更新') emits('update:modelValue', newCronVal) } }, ) + function openPanel() { showDialog.value = true } @@ -43,6 +42,7 @@ function openPanel() { diff --git a/src/pages/sys/task/cron/cron-expression.ts b/src/pages/sys/task/cron/cron-expression.ts deleted file mode 100644 index 3d210bc..0000000 --- a/src/pages/sys/task/cron/cron-expression.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * 提供类 Unix 风格 Cron 表达式的解析器和求值器。Cron 表达式能够指定复杂的时间组合, - * 例如“每周一至周五的上午 8:00”或“每月最后一个周五的凌晨 1:30”。 - * - *

- * Cron 表达式由 6 个必填字段和 1 个可选字段组成,字段之间通过空格分隔。各字段的详细说明如下: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
字段名称 允许值 允许的特殊字符
秒(Seconds)  - * 0-59  - * , - * /
分(Minutes)  - * 0-59  - * , - * /
时(Hours)  - * 0-23  - * , - * /
日(Day-of-month)  - * 1-31  - * , - * ? / L W
月(Month)  - * 0-11 或 JAN-DEC(一月至十二月)  - * , - * /
周(Day-of-Week)  - * 1-7 或 SUN-SAT(周日至周六)  - * , - * ? / L #
年(Year,可选)  - * 空值、1970-2199  - * , - * /
- *

- * '*' 字符用于指定“所有可能的值”。例如,在“分钟”字段中使用 "*" 表示“每分钟”。 - *

- * '?' 字符仅允许在“日(Day-of-month)”和“周(Day-of-Week)”字段中使用, - * 用于表示“不指定具体值”。当你需要在这两个字段中的一个指定条件,而另一个无需指定时,该字符非常有用。 - *

- * '-' 字符用于指定时间范围。例如,在“小时”字段中使用 "10-12" 表示“上午 10 点、11 点和中午 12 点”。 - *

- * ',' 字符用于指定多个额外的取值。例如,在“周”字段中使用 "MON,WED,FRI" 表示“周一、周三和周五”。 - *

- * '/' 字符用于指定递增步长。例如,在“秒”字段中使用 "0/15" 表示“第 0 秒、15 秒、30 秒和 45 秒”; - * 而 "5/15" 在“秒”字段中表示“第 5 秒、20 秒、35 秒和 50 秒”。在 '/' 前指定 '*' 等价于以 0 作为起始值。 - * 本质上,表达式的每个字段都对应一组可启用或禁用的数字集合:秒和分的范围是 0-59,小时是 0-23, - * 日是 1-31,月是 0-11(对应 JAN 至 DEC)。'/' 字符的作用是在指定集合中启用“每隔 n 个”的取值。 - * 需要注意一个细节:在“月”字段中使用 "7/6" 仅会启用“7 月”,并不表示“每 6 个月”。 - *

- * 'L' 字符仅允许在“日(Day-of-month)”和“周(Day-of-Week)”字段中使用,是“last(最后一个)”的缩写, - * 但在两个字段中的含义有所不同。例如,在“日”字段中使用 "L" 表示“当月的最后一天”——1 月对应 31 日, - * 非闰年的 2 月对应 28 日。若在“周”字段中单独使用 "L",仅表示“7”或“SAT(周六)”; - * 若在其他值后使用,则表示“当月最后一个指定星期几”,例如 "6L" 表示“当月最后一个周五”。 - * 你也可以指定相对于月末的偏移量,例如 "L-3" 表示“当月的倒数第三天”。 - * 注意:使用 'L' 选项时,请勿同时指定多个取值(列表)或范围,否则会得到混乱或不符合预期的结果。 - *

- * 'W' 字符仅允许在“日(Day-of-month)”字段中使用,用于指定“距离给定日期最近的工作日(周一至周五)”。 - * 例如,若在“日”字段中指定 "15W",含义是“当月 15 日附近最近的工作日”:如果 15 日是周六, - * 则触发时间为 14 日(周五);如果 15 日是周日,则触发时间为 16 日(周一);如果 15 日是周二, - * 则触发时间为 15 日(周二)本身。若指定 "1W" 且 1 日是周六,触发时间会是 3 日(周一), - * 因为该字符不会“跨越”月份的边界。'W' 字符仅能在“日”字段指定单个日期时使用, - * 不能用于日期范围或多个日期列表。 - *

- * 'L' 和 'W' 字符可在“日(Day-of-month)”字段中组合使用,形成 "LW",含义是“当月的最后一个工作日”。 - *

- * '#' 字符仅允许在“周(Day-of-Week)”字段中使用,用于指定“当月第 n 个星期几”。 - * 例如,在“周”字段中使用 "6#3" 表示“当月第三个周五”(6 对应周五,"#3" 对应第三个)。 - * 其他示例:"2#1" 表示“当月第一个周一”,"4#5" 表示“当月第五个周三”。 - * 注意:若指定了 "#5" 但当月不存在对应的第 5 个星期几,则该月不会触发任务。 - * 此外,使用 '#' 字符时,“周”字段中只能包含一个表达式(例如 "3#1,6#3" 是无效的,因为它包含两个表达式)。 - *

- * - *

- * 所有合法字符以及月份、星期几的名称均不区分大小写。 - * - *

- * 注意事项: - *

- *

- * - * @author 沙拉达·詹布拉、詹姆斯·豪斯 - * @author 贡献者:麦兹·亨德森 - * @author 重构者:艾伦·克雷文(将功能从 CronTrigger 重构为 CronExpression) - *

- * 源自 Quartz 框架 v2.3.1 版本 - */ - -export class CronExpression { - // 公共常量:最大年份(当前年份+100,与 Java 保持一致) - public static readonly MAX_YEAR: number = new Date().getFullYear() + 100 - - // 私有静态常量:字段索引(秒、分、时、日、月、周、年) - private static readonly SECOND: number = 0 - private static readonly MINUTE: number = 1 - private static readonly HOUR: number = 2 - private static readonly DAY_OF_MONTH: number = 3 - private static readonly MONTH: number = 4 - private static readonly DAY_OF_WEEK: number = 5 - private static readonly YEAR: number = 6 - - // 私有静态常量:特殊标记值(* 对应 99,? 对应 98) - private static readonly ALL_SPEC_INT: number = 99 // 对应 '*' - private static readonly NO_SPEC_INT: number = 98 // 对应 '?' - private static readonly ALL_SPEC: number = CronExpression.ALL_SPEC_INT - private static readonly NO_SPEC: number = CronExpression.NO_SPEC_INT - - // 私有静态映射:月份英文缩写 -> 数字(0-11,与 Java 一致) - private static readonly monthMap: Record = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11} - // 私有静态映射:星期英文缩写 -> 数字(1-7,与 Java 一致) - private static readonly dayMap: Record = {SUN: 1, MON: 2, TUE: 3, WED: 4, THU: 5, FRI: 6, SAT: 7} - - // 实例属性 - private readonly cronExpression: string - private seconds?: number[] // TS 中无原生TreeSet,后续可实现或用SortedSet替代 - private minutes?: number[] - private hours?: number[] - private daysOfMonth?: number[] - private months?: number[] - private daysOfWeek?: number[] - private years?: number[] - - private lastdayOfWeek: boolean = false - private nthdayOfWeek: number = 0 - private lastdayOfMonth: boolean = false - private nearestWeekday: boolean = false - private lastdayOffset: number = 0 - private expressionParsed: boolean = false - private timeZone?: TimeZone // 对应 Java TimeZone,TS 中可用 string 或原生 TimeZone 替代 - - - /** - * 根据指定的参数构造一个新的 CronExpression 实例。 - * - * @param cronExpression 字符串形式的 Cron 表达式,该表达式将由新建的对象进行承载 - * @throws Error 如果传入的字符串表达式无法被解析为一个有效的 CronExpression 实例,则抛出该异常 - */ - constructor(cronExpression: string) { - if (cronExpression == null) { - throw new Error('表达式不能为空') - } - - this.cronExpression = cronExpression.toUpperCase(Locale.US) - - buildExpression(this.cronExpression) - } - - /** - * 复制现有实例,创建新的 Cron 表达式 - */ - constructor(expression: CronExpression) { - this.cronExpression = expression.getCronExpression() - buildExpression(cronExpression) - if (expression.getTimeZone() != null) { - setTimeZone(expression.getTimeZone().clone()) - } - } - - /** - * 检查指定的 Cron 表达式是否能够被解析为一个有效的 Cron 表达式 - * @param cronExpression 待校验的 Cron 表达式字符串 - * @returns 布尔值:如果传入的表达式是有效的 Cron 表达式,返回 true;否则返回 false - */ - public static isValidExpression(cronExpression: string): boolean { - try { - new CronExpression(cronExpression) - } catch (e: Error) { - return false - } - return true - } - - /** - * 判断指定的日期是否满足当前 Cron 表达式的规则。 - * 注意:该方法会忽略日期的毫秒部分,因此在同一秒钟内不同毫秒数的两个日期,在此方法中会返回相同的结果。 - * @param date 待校验的日期对象 - * @returns 布尔值:如果指定日期满足 Cron 表达式规则,返回 true;否则返回 false - */ - public isSatisfiedBy(date: Date): boolean { - const testDateCal = new Date(date.getTime()) - testDateCal.setMilliseconds(0) - - const originalDate = new Date(testDateCal.getTime()) - - testDateCal.setSeconds(date.getSeconds() - 1) - - const timeAfter = this.getTimeAfter(testDateCal) - - return timeAfter !== null && timeAfter.getTime() === originalDate.getTime() - } -} - - -/** - * 时间区类型(简化版,对应 Java TimeZone) - * 可根据实际需求扩展,此处保持与原代码结构一致 - */ -type TimeZone = { - id: string; - offset: number; -} | null; diff --git a/src/pages/sys/task/cron/cron-panel/CronPanel.vue b/src/pages/sys/task/cron/cron-panel/CronPanel.vue index 3ff9fb3..c312724 100644 --- a/src/pages/sys/task/cron/cron-panel/CronPanel.vue +++ b/src/pages/sys/task/cron/cron-panel/CronPanel.vue @@ -7,6 +7,11 @@ import MonthPanel from '@/pages/sys/task/cron/cron-panel/MonthPanel.vue' import WeekPanel from '@/pages/sys/task/cron/cron-panel/WeekPanel.vue' import YearPanel from '@/pages/sys/task/cron/cron-panel/YearPanel.vue' import { useCronStore } from '@/pages/sys/task/cron/cron-store.ts' +import Strings from '@/common/utils/strings.ts' +import TaskApi from '@/pages/sys/task/task-api.ts' +import type { R } from '@/common/utils/http-util.ts' +import { ref } from 'vue' +import type { TabsPaneContext } from 'element-plus' const { secondVal, @@ -16,12 +21,30 @@ const { monthVal, weekVal, yearVal, + cronVal, } = toRefs(useCronStore()) + +const nexts = ref([]) + +function checkExpression(pane: TabsPaneContext) { + if (pane.paneName !== 'next-list') { + return + } + TaskApi.checkExpression(cronVal.value) + .then(res => { + ElMessage.success('获取成功') + nexts.value = res.data + }) + .catch((res: R) => { + ElMessage.success(res.msg ?? (res.message ?? '获取失败')) + console.log(res) + }) +} @@ -59,4 +87,8 @@ const { height: 100%; } } + +.next-list { + position relative +} diff --git a/src/pages/sys/task/cron/cron-panel/DayPanel.vue b/src/pages/sys/task/cron/cron-panel/DayPanel.vue index 47d0d38..4919ccf 100644 --- a/src/pages/sys/task/cron/cron-panel/DayPanel.vue +++ b/src/pages/sys/task/cron/cron-panel/DayPanel.vue @@ -11,21 +11,21 @@ const {dayState} = toRefs(useCronStore()) 每月最后一天 每月 - + 号最近的那个工作日 周期,从 - + 号到 - + 周期,从第 - + 天开始,每 - + 天执行一次 @@ -46,16 +46,8 @@ const {dayState} = toRefs(useCronStore()) flex-wrap nowrap :deep(.el-input-number) { - margin 0 5px - width: 60px - } - - :deep(.el-radio):nth-child(4) { - position relative - } - - .second-item { - position relative + margin 0 10px + width: 80px } :deep(.el-checkbox-group) { diff --git a/src/pages/sys/task/cron/cron-panel/HourPanel.vue b/src/pages/sys/task/cron/cron-panel/HourPanel.vue index bef27e7..35eb2de 100644 --- a/src/pages/sys/task/cron/cron-panel/HourPanel.vue +++ b/src/pages/sys/task/cron/cron-panel/HourPanel.vue @@ -9,16 +9,16 @@ const {hourState} = toRefs(useCronStore()) 每小时 周期,从 - + 时到 - + 周期,从第 - + 时开始,每 - + 小时执行一次 @@ -39,16 +39,8 @@ const {hourState} = toRefs(useCronStore()) flex-wrap nowrap :deep(.el-input-number) { - margin 0 5px - width: 60px - } - - :deep(.el-radio):nth-child(4) { - position relative - } - - .second-item { - position relative + margin 0 10px + width: 80px } :deep(.el-checkbox-group) { diff --git a/src/pages/sys/task/cron/cron-panel/MinutePanel.vue b/src/pages/sys/task/cron/cron-panel/MinutePanel.vue index 829bd00..7254d55 100644 --- a/src/pages/sys/task/cron/cron-panel/MinutePanel.vue +++ b/src/pages/sys/task/cron/cron-panel/MinutePanel.vue @@ -10,15 +10,16 @@ const {minuteState} = toRefs(useCronStore()) 每分钟 周期,从 - - - + + 分到 + + 周期,从第 - + 分钟开始,每 - + 分钟执行一次 @@ -39,16 +40,8 @@ const {minuteState} = toRefs(useCronStore()) flex-wrap nowrap :deep(.el-input-number) { - margin 0 5px - width: 60px - } - - :deep(.el-radio):nth-child(4) { - position relative - } - - .second-item { - position relative + margin 0 10px + width: 80px } :deep(.el-checkbox-group) { diff --git a/src/pages/sys/task/cron/cron-panel/MonthPanel.vue b/src/pages/sys/task/cron/cron-panel/MonthPanel.vue index 559c955..ae5476a 100644 --- a/src/pages/sys/task/cron/cron-panel/MonthPanel.vue +++ b/src/pages/sys/task/cron/cron-panel/MonthPanel.vue @@ -11,12 +11,10 @@ const months = [ '一月', '二月', '三月', '四月', '五月', '六月', ' 每月 周期,从 - - @@ -48,16 +46,13 @@ const months = [ '一月', '二月', '三月', '四月', '五月', '六月', ' flex-wrap nowrap :deep(.el-input-number) { - margin 0 5px - width: 60px + margin 0 10px + width: 80px } - :deep(.el-radio):nth-child(4) { - position relative - } - - .second-item { - position relative + :deep(.el-select) { + margin 0 10px + width: 80px } :deep(.el-checkbox-group) { @@ -67,10 +62,10 @@ const months = [ '一月', '二月', '三月', '四月', '五月', '六月', ' display flex flex-wrap wrap gap 10px - width 500px + width 420px .el-checkbox { - width 40px; + width 60px; margin 0 } } diff --git a/src/pages/sys/task/cron/cron-panel/SecondPanel.vue b/src/pages/sys/task/cron/cron-panel/SecondPanel.vue index b46688c..80d9b2b 100644 --- a/src/pages/sys/task/cron/cron-panel/SecondPanel.vue +++ b/src/pages/sys/task/cron/cron-panel/SecondPanel.vue @@ -10,15 +10,16 @@ const {secondState} = toRefs(useCronStore()) 每秒 周期,从 - - - + + 秒到 + + 周期,从第 - + 秒开始,每 - + 秒执行一次 @@ -39,16 +40,8 @@ const {secondState} = toRefs(useCronStore()) flex-wrap nowrap :deep(.el-input-number) { - margin 0 5px - width: 60px - } - - :deep(.el-radio):nth-child(4) { - position relative - } - - .second-item { - position relative + margin 0 10px + width: 80px } :deep(.el-checkbox-group) { diff --git a/src/pages/sys/task/cron/cron-panel/WeekPanel.vue b/src/pages/sys/task/cron/cron-panel/WeekPanel.vue index bbe1ec8..783658f 100644 --- a/src/pages/sys/task/cron/cron-panel/WeekPanel.vue +++ b/src/pages/sys/task/cron/cron-panel/WeekPanel.vue @@ -48,19 +48,17 @@ const weeks = [ '星期一', '星期二', '星期三', '星期四', '星期五', align-items start gap 10px width 100%; + flex-wrap nowrap :deep(.el-input-number) { - margin 0 5px - width: 60px + margin 0 10px + width: 80px } - :deep(.el-radio):nth-child(4) { - position relative - } - - .second-item { - position relative + :deep(.el-select) { + margin 0 10px + width: 80px } :deep(.el-checkbox-group) { @@ -73,7 +71,7 @@ const weeks = [ '星期一', '星期二', '星期三', '星期四', '星期五', width 500px .el-checkbox { - width 40px; + width 90px; margin 0 } } diff --git a/src/pages/sys/task/cron/cron-panel/YearPanel.vue b/src/pages/sys/task/cron/cron-panel/YearPanel.vue index 0128c63..98ece40 100644 --- a/src/pages/sys/task/cron/cron-panel/YearPanel.vue +++ b/src/pages/sys/task/cron/cron-panel/YearPanel.vue @@ -28,31 +28,8 @@ let year = new Date().getFullYear() flex-wrap nowrap :deep(.el-input-number) { - margin 0 5px - width: 60px - } - - :deep(.el-radio):nth-child(4) { - position relative - } - - .second-item { - position relative - } - - :deep(.el-checkbox-group) { - position absolute - top 0 - left 60px - display flex - flex-wrap wrap - gap 10px - width 500px - - .el-checkbox { - width 40px; - margin 0 - } + margin 0 10px + width: 80px } } diff --git a/src/pages/sys/task/cron/cron-store.ts b/src/pages/sys/task/cron/cron-store.ts index fdecb63..345b4a4 100644 --- a/src/pages/sys/task/cron/cron-store.ts +++ b/src/pages/sys/task/cron/cron-store.ts @@ -383,16 +383,16 @@ export const useCronStore = defineStore('Cron', () => { }) const yearVal = computed(() => { - let data: string = '*' + let data: string = '' if (yearState.mode === 'none') { - data = '?' + data = '' } else if (yearState.mode === 'period1') { data = `${yearState.period1.start}-${yearState.period1.end}` } return data }) const setYearVal = (val: string) => { - if (val === '?') { + if (val === '') { yearState.mode = 'none' } else if (val.includes('-')) { const s = val.split('-') @@ -403,18 +403,19 @@ export const useCronStore = defineStore('Cron', () => { yearState.mode = 'every' } } + const cronVal = computed({ get() { - console.log('获取值', `${secondVal.value} ${minuteVal.value} ${hourVal.value} ${dayVal.value} ${monthVal.value} ${weekVal.value} ${yearVal.value}`) return `${secondVal.value} ${minuteVal.value} ${hourVal.value} ${dayVal.value} ${monthVal.value} ${weekVal.value} ${yearVal.value}` }, - set(val: string = '* * * * * ? ?') { - + set(val: string = '* * * * * ?') { let sections = val.split(' ') - if (sections.length != 7) { - sections = '* * * * * ? ?'.split(' ') + if (sections.length === 6) { + sections.push('') + } + if (sections.length != 7) { + sections = [ '*', '*', '*', '*', '*', '*', '' ] } - console.log('设置值', val, sections) setSecondVal(sections[0]) setMinuteVal(sections[1]) setHourVal(sections[2]) diff --git a/src/pages/sys/task/task-api.ts b/src/pages/sys/task/task-api.ts index e4902c3..d32622f 100644 --- a/src/pages/sys/task/task-api.ts +++ b/src/pages/sys/task/task-api.ts @@ -19,6 +19,9 @@ export default { disable(id: string, disable: boolean) { return get('/task/disable', {id, disable}) }, + checkExpression(expression: string) { + return get('/task/check_expression', {expression}) + }, add(data: TaskTypes.AddTaskParam) { return post('/task/add', data) },