master
lzq 2025-12-24 10:45:01 +08:00
parent 5fb7de0b81
commit a297536102
13 changed files with 96 additions and 366 deletions

View File

@ -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]
// }
}
},
})

View File

@ -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() {
</template>
</ElInput>
<ElDialog v-model="showDialog"
title="配置面板"
:close-on-click-modal="false"
destroy-on-close width="50vw">
<CronPanel/>

View File

@ -1,241 +0,0 @@
/**
* Unix Cron Cron
* 8:00 1:30
*
* <P>
* Cron 6 1
*
* <table cellspacing="8">
* <tr>
* <th align="left"></th>
* <th align="left">&nbsp;</th>
* <th align="left"></th>
* <th align="left">&nbsp;</th>
* <th align="left"></th>
* </tr>
* <tr>
* <td align="left"><code>Seconds</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>0-59</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* <tr>
* <td align="left"><code>Minutes</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>0-59</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* <tr>
* <td align="left"><code>Hours</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>0-23</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* <tr>
* <td align="left"><code>Day-of-month</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>1-31</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * ? / L W</code></td>
* </tr>
* <tr>
* <td align="left"><code>Month</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>0-11 JAN-DEC</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* <tr>
* <td align="left"><code>Day-of-Week</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>1-7 SUN-SAT</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * ? / L #</code></td>
* </tr>
* <tr>
* <td align="left"><code>Year</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>1970-2199</code></td>
* <td align="left">&nbsp;</th>
* <td align="left"><code>, - * /</code></td>
* </tr>
* </table>
* <P>
* '*' 使 "*"
* <P>
* '?' Day-of-monthDay-of-Week使
*
* <P>
* '-' 使 "10-12" 10 11 12
* <P>
* ',' 使 "MON,WED,FRI"
* <P>
* '/' 使 "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
* <P>
* 'L' Day-of-monthDay-of-Week使last
* 使 "L" 1 31
* 2 28 使 "L"7SAT
* 使 "6L"
* "L-3"
* <i>使 'L' </i>
* <P>
* 'W' Day-of-month使
* "15W" 15 15
* 14 15 16 15
* 15 "1W" 1 3
* 'W' 使
*
* <P>
* 'L' 'W' Day-of-month使 "LW"
* <P>
* '#' Day-of-Week使 n
* 使 "6#3" 6 "#3"
* "2#1" "4#5"
* "#5" 5
* 使 '#' "3#1,6#3"
* <P>
* <!--'C' Day-of-monthDay-of-Week使calendar
* 使
* 使 "5C" 5 5
* 使 "1C" -->
* <P>
*
*
* <p>
* <b></b>
* <ul>
* <li> Day-of-monthDay-of-Week
* 使 '?' </li>
* <li> 使 22-2 10 2
* 使 NOV-FEB 11 2 使
* CronExpression "0 0 14-6 ? * FRI-MON" </li>
* </ul>
* </p>
*
* @author ··
* @author ·
* @author · CronTrigger CronExpression
* <p>
* 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<string, number> = {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<string, number> = {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 TimeZoneTS 中可用 string 或原生 TimeZone 替代
/**
* <code>CronExpression</code>
*
* @param cronExpression Cron
* @throws Error <code>CronExpression</code>
*/
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(<TimeZone>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;

View File

@ -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<string[]>([])
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<string[]>) => {
ElMessage.success(res.msg ?? (res.message ?? '获取失败'))
console.log(res)
})
}
</script>
<template>
<div class="cron-panel">
<ElTabs type="border-card">
<ElTabs type="border-card" @tab-click="checkExpression">
<ElTabPane :label="`秒(${secondVal}`">
<SecondPanel/>
</ElTabPane>
@ -40,9 +63,14 @@ const {
<ElTabPane :label="`周(${weekVal}`">
<WeekPanel/>
</ElTabPane>
<ElTabPane :label="`年${yearVal}`">
<ElTabPane :label="`年${Strings.isBlank(yearVal)?'':''+yearVal+''}`">
<YearPanel/>
</ElTabPane>
<ElTabPane label="最近几次执行时间" name="next-list">
<div class="next-list">
<div v-for="next in nexts" :key="next">{{ next }}</div>
</div>
</ElTabPane>
</ElTabs>
</div>
</template>
@ -59,4 +87,8 @@ const {
height: 100%;
}
}
.next-list {
position relative
}
</style>

View File

@ -11,21 +11,21 @@ const {dayState} = toRefs(useCronStore())
<ElRadio value="last">每月最后一天</ElRadio>
<ElRadio value="near">
<span>每月</span>
<ElInputNumber v-model="dayState.near" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="dayState.near" :controls="false" :max="31" :min="1" size="small"/>
<span>号最近的那个工作日</span>
</ElRadio>
<ElRadio value="period1">
<span>周期</span>
<ElInputNumber v-model="dayState.period1.start" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="dayState.period1.start" :controls="false" :max="30" :min="1" size="small"/>
<span>号到</span>
<ElInputNumber v-model="dayState.period1.end" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="dayState.period1.end" :controls="false" :max="31" :min="dayState.period1.start" size="small"/>
<span></span>
</ElRadio>
<ElRadio value="period2">
<span>周期从第</span>
<ElInputNumber v-model="dayState.period2.start" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="dayState.period2.start" :controls="false" :max="31" :min="1" size="small"/>
<span>天开始</span>
<ElInputNumber v-model="dayState.period2.end" :controls="false" :max="60" :min="1" size="small"/>
<ElInputNumber v-model="dayState.period2.end" :controls="false" :max="31" :min="1" size="small"/>
<span>天执行一次</span>
</ElRadio>
<ElRadio value="specify">
@ -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) {

View File

@ -9,16 +9,16 @@ const {hourState} = toRefs(useCronStore())
<ElRadio value="every">每小时</ElRadio>
<ElRadio value="period1">
<span>周期</span>
<ElInputNumber v-model="hourState.period1.start" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="hourState.period1.start" :controls="false" :max="23" :min="0" size="small"/>
<span>时到</span>
<ElInputNumber v-model="hourState.period1.end" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="hourState.period1.end" :controls="false" :max="23" :min="hourState.period1.start" size="small"/>
<span></span>
</ElRadio>
<ElRadio value="period2">
<span>周期从第</span>
<ElInputNumber v-model="hourState.period2.start" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="hourState.period2.start" :controls="false" :max="23" :min="0" size="small"/>
<span>时开始</span>
<ElInputNumber v-model="hourState.period2.end" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="hourState.period2.end" :controls="false" :max="23" :min="0" size="small"/>
<span>小时执行一次</span>
</ElRadio>
<ElRadio value="specify">
@ -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) {

View File

@ -10,15 +10,16 @@ const {minuteState} = toRefs(useCronStore())
<ElRadio value="every">每分钟</ElRadio>
<ElRadio value="period1">
<span>周期</span>
<ElInputNumber v-model="minuteState.period1.start" :controls="false" :max="59" :min="1" size="small"/>
<span></span>
<ElInputNumber v-model="minuteState.period1.end" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="minuteState.period1.start" :controls="false" :max="58" :min="0" size="small"/>
<span>分到</span>
<ElInputNumber v-model="minuteState.period1.end" :controls="false" :max="59" :min="minuteState.period1.start" size="small"/>
<span></span>
</ElRadio>
<ElRadio value="period2">
<span>周期从第</span>
<ElInputNumber v-model="minuteState.period2.start" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="minuteState.period2.start" :controls="false" :max="59" :min="0" size="small"/>
<span>分钟开始</span>
<ElInputNumber v-model="minuteState.period2.end" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="minuteState.period2.end" :controls="false" :max="59" :min="0" size="small"/>
<span>分钟执行一次</span>
</ElRadio>
<ElRadio value="specify">
@ -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) {

View File

@ -11,12 +11,10 @@ const months = [ '一月', '二月', '三月', '四月', '五月', '六月', '
<ElRadio value="every">每月</ElRadio>
<ElRadio value="period1">
<span>周期</span>
<!-- <ElInputNumber v-model="monthState.period1.start" :controls="false" :max="59" :min="1" size="small"/> -->
<ElSelect v-model="monthState.period1.start" size="small">
<ElOption v-for="(month,i) in months" :key="'monthState.period1.start' + i" :label="month" :value="i"/>
</ElSelect>
<span></span>
<!-- <ElInputNumber v-model="monthState.period1.end" :controls="false" :max="59" :min="1" size="small"/> -->
<ElSelect v-model="monthState.period1.end" size="small">
<ElOption v-for="(month,i) in months" :key="'monthState.period1.end' + i" :disabled="i <= monthState.period1.start" :label="month" :value="i"/>
</ElSelect>
@ -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
}
}

View File

@ -10,15 +10,16 @@ const {secondState} = toRefs(useCronStore())
<ElRadio value="every">每秒</ElRadio>
<ElRadio value="period1">
<span>周期</span>
<ElInputNumber v-model="secondState.period1.start" :controls="false" :max="59" :min="0" size="small"/>
<span></span>
<ElInputNumber v-model="secondState.period1.end" :controls="false" :max="59" :min="0" size="small"/>
<ElInputNumber v-model="secondState.period1.start" :controls="false" :max="58" :min="0" size="small"/>
<span>秒到</span>
<ElInputNumber v-model="secondState.period1.end" :controls="false" :max="59" :min="secondState.period1.start + 1" size="small"/>
<span></span>
</ElRadio>
<ElRadio value="period2">
<span>周期从第</span>
<ElInputNumber v-model="secondState.period2.start" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="secondState.period2.start" :controls="false" :max="59" :min="0" size="small"/>
<span>秒开始</span>
<ElInputNumber v-model="secondState.period2.end" :controls="false" :max="59" :min="1" size="small"/>
<ElInputNumber v-model="secondState.period2.end" :controls="false" :max="59" :min="0" size="small"/>
<span>秒执行一次</span>
</ElRadio>
<ElRadio value="specify">
@ -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) {

View File

@ -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
}
}

View File

@ -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
}
}
</style>

View File

@ -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])

View File

@ -19,6 +19,9 @@ export default {
disable(id: string, disable: boolean) {
return get<boolean>('/task/disable', {id, disable})
},
checkExpression(expression: string) {
return get<string[]>('/task/check_expression', {expression})
},
add(data: TaskTypes.AddTaskParam) {
return post('/task/add', data)
},