2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-07 18:13:13 +08:00

feat: 新的Cron表达式生成器

This commit is contained in:
2025-12-31 17:38:08 +08:00
parent b86fc187d7
commit 80fde60526
4 changed files with 430 additions and 14 deletions

View File

@@ -0,0 +1,155 @@
<script setup lang="ts">
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const props = defineProps({
cron: {
type: String,
required: true
}
})
// 星期名称映射
const weekdayNames: Record<string, string> = {
'0': $gettext('Sunday'),
'1': $gettext('Monday'),
'2': $gettext('Tuesday'),
'3': $gettext('Wednesday'),
'4': $gettext('Thursday'),
'5': $gettext('Friday'),
'6': $gettext('Saturday'),
'7': $gettext('Sunday') // 有些系统用 7 表示周日
}
// 格式化时间为 HH:MM
const formatTime = (hour: string, minute: string): string => {
const h = hour.padStart(2, '0')
const m = minute.padStart(2, '0')
return `${h}:${m}`
}
// 解析 Cron 表达式并生成人类可读描述
const parseDescription = computed((): string => {
const cron = props.cron.trim()
const parts = cron.split(/\s+/)
// Cron 表达式应该有 5 个部分:分 时 日 月 周
if (parts.length !== 5) {
return $gettext('Cron expression: %{cron}', { cron })
}
const [minute, hour, day, month, weekday] = parts
try {
// 每 N 分钟:*/N * * * *
if (
minute.startsWith('*/') &&
hour === '*' &&
day === '*' &&
month === '*' &&
weekday === '*'
) {
const n = minute.slice(2)
return $gettext('Run every %{n} minutes', { n })
}
// 每 N 小时的某分钟M */N * * *
if (
!minute.includes('*') &&
hour.startsWith('*/') &&
day === '*' &&
month === '*' &&
weekday === '*'
) {
const n = hour.slice(2)
const m = minute.padStart(2, '0')
return $gettext('Run every %{n} hours at minute %{m}', { n, m })
}
// 每 N 天的某时某分M H */N * *
if (
!minute.includes('*') &&
!hour.includes('*') &&
day.startsWith('*/') &&
month === '*' &&
weekday === '*'
) {
const n = day.slice(2)
const time = formatTime(hour, minute)
return $gettext('Run every %{n} days at %{time}', { n, time })
}
// 每小时的某分钟M * * * *
if (!minute.includes('*') && hour === '*' && day === '*' && month === '*' && weekday === '*') {
const m = minute.padStart(2, '0')
return $gettext('Run hourly at minute %{m}', { m })
}
// 每天的某时某分M H * * *
if (
!minute.includes('*') &&
!hour.includes('*') &&
day === '*' &&
month === '*' &&
weekday === '*'
) {
const time = formatTime(hour, minute)
return $gettext('Run daily at %{time}', { time })
}
// 每周某天的某时某分M H * * W
if (
!minute.includes('*') &&
!hour.includes('*') &&
day === '*' &&
month === '*' &&
!weekday.includes('*')
) {
const time = formatTime(hour, minute)
const weekdayName = weekdayNames[weekday] || weekday
return $gettext('Run weekly on %{weekday} at %{time}', { weekday: weekdayName, time })
}
// 每月某日的某时某分M H D * *
if (
!minute.includes('*') &&
!hour.includes('*') &&
!day.includes('*') &&
month === '*' &&
weekday === '*'
) {
const time = formatTime(hour, minute)
return $gettext('Run monthly on day %{day} at %{time}', { day, time })
}
// 每年某月某日的某时某分M H D Mon *
if (
!minute.includes('*') &&
!hour.includes('*') &&
!day.includes('*') &&
!month.includes('*') &&
weekday === '*'
) {
const time = formatTime(hour, minute)
return $gettext('Run yearly on month %{month} day %{day} at %{time}', { month, day, time })
}
// 每分钟:* * * * *
if (minute === '*' && hour === '*' && day === '*' && month === '*' && weekday === '*') {
return $gettext('Run every minute')
}
// 无法解析,返回原始表达式
return $gettext('Cron expression: %{cron}', { cron })
} catch {
return $gettext('Cron expression: %{cron}', { cron })
}
})
</script>
<template>
<span>{{ parseDescription }}</span>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,260 @@
<script setup lang="ts">
import { useGettext } from 'vue3-gettext'
import CronPreview from './CronPreview.vue'
const { $gettext } = useGettext()
// 生成的 Cron 表达式值
const value = defineModel<string>('value', {
type: String,
required: true
})
// 当前选择的周期类型
const selectedOption = ref<string>('every-n-minutes')
// 表单数据
const formData = ref({
// Every N 系列
nMinutes: 30, // 每 N 分钟
nHours: 2, // 每 N 小时
nDays: 3, // 每 N 天
// 时间配置
minute: 30, // 分钟
hour: 1, // 小时
day: 3, // 日期1-31
month: 1, // 月份1-12
weekday: 1, // 星期0-60=周日)
// 自定义表达式
customCron: '* * * * *'
})
// 周期选项
const options = [
{ label: $gettext('Every N Minutes'), value: 'every-n-minutes' },
{ label: $gettext('Every N Hours'), value: 'every-n-hours' },
{ label: $gettext('Every N Days'), value: 'every-n-days' },
{ label: $gettext('Hourly'), value: 'every-hour' },
{ label: $gettext('Daily'), value: 'every-day' },
{ label: $gettext('Weekly'), value: 'every-week' },
{ label: $gettext('Monthly'), value: 'every-month' },
{ label: $gettext('Yearly'), value: 'every-year' },
{ label: $gettext('Custom'), value: 'custom' }
]
// 星期选项
const weekdayOptions = [
{ label: $gettext('Sunday'), value: 0 },
{ label: $gettext('Monday'), value: 1 },
{ label: $gettext('Tuesday'), value: 2 },
{ label: $gettext('Wednesday'), value: 3 },
{ label: $gettext('Thursday'), value: 4 },
{ label: $gettext('Friday'), value: 5 },
{ label: $gettext('Saturday'), value: 6 }
]
// 月份选项
const monthOptions = Array.from({ length: 12 }, (_, i) => ({
label: $gettext('Month %{month}', { month: String(i + 1) }),
value: i + 1
}))
// 生成 Cron 表达式
const generateCron = (): string => {
const { minute, hour, day, month, weekday, nMinutes, nHours, nDays, customCron } = formData.value
switch (selectedOption.value) {
case 'every-n-minutes':
// 每 N 分钟:*/N * * * *
return `*/${nMinutes} * * * *`
case 'every-n-hours':
// 每 N 小时的第 M 分钟M */N * * *
return `${minute} */${nHours} * * *`
case 'every-n-days':
// 每 N 天的 H 时 M 分M H */N * *
return `${minute} ${hour} */${nDays} * *`
case 'every-hour':
// 每小时的第 M 分钟M * * * *
return `${minute} * * * *`
case 'every-day':
// 每天 H 时 M 分M H * * *
return `${minute} ${hour} * * *`
case 'every-week':
// 每周几的 H 时 M 分M H * * W
return `${minute} ${hour} * * ${weekday}`
case 'every-month':
// 每月 D 日 H 时 M 分M H D * *
return `${minute} ${hour} ${day} * *`
case 'every-year':
// 每年 Mon 月 D 日 H 时 M 分M H D Mon *
return `${minute} ${hour} ${day} ${month} *`
case 'custom':
return customCron
default:
return '* * * * *'
}
}
// 监听变化,更新 Cron 表达式
watch(
[selectedOption, formData],
() => {
value.value = generateCron()
},
{ deep: true, immediate: true }
)
// 判断是否显示某个输入框
const showMonth = computed(() => selectedOption.value === 'every-year')
const showDay = computed(() =>
['every-n-days', 'every-month', 'every-year'].includes(selectedOption.value)
)
const showWeekday = computed(() => selectedOption.value === 'every-week')
const showHour = computed(() =>
['every-n-days', 'every-day', 'every-week', 'every-month', 'every-year'].includes(
selectedOption.value
)
)
const showMinute = computed(() =>
[
'every-n-minutes',
'every-n-hours',
'every-n-days',
'every-hour',
'every-day',
'every-week',
'every-month',
'every-year'
].includes(selectedOption.value)
)
const showNDays = computed(() => selectedOption.value === 'every-n-days')
const showNHours = computed(() => selectedOption.value === 'every-n-hours')
const showNMinutes = computed(() => selectedOption.value === 'every-n-minutes')
const showCustom = computed(() => selectedOption.value === 'custom')
</script>
<template>
<n-flex vertical :size="12">
<n-flex align="center" :wrap="false">
<!-- 周期类型选择 -->
<n-select
v-model:value="selectedOption"
:options="options"
:style="{ width: '160px', flexShrink: 0 }"
/>
<!-- N 分钟 -->
<n-input-number
v-if="showNMinutes"
v-model:value="formData.nMinutes"
:min="1"
:max="59"
:style="{ width: '140px' }"
>
<template #suffix>{{ $gettext('Minutes') }}</template>
</n-input-number>
<!-- N 小时 -->
<n-input-number
v-if="showNHours"
v-model:value="formData.nHours"
:min="1"
:max="23"
:style="{ width: '140px' }"
>
<template #suffix>{{ $gettext('Hours') }}</template>
</n-input-number>
<!-- N -->
<n-input-number
v-if="showNDays"
v-model:value="formData.nDays"
:min="1"
:max="31"
:style="{ width: '140px' }"
>
<template #suffix>{{ $gettext('Days') }}</template>
</n-input-number>
<!-- 月份选择每年 -->
<n-select
v-if="showMonth"
v-model:value="formData.month"
:options="monthOptions"
:style="{ width: '140px' }"
/>
<!-- 日期选择每月每年 -->
<n-input-number
v-if="showDay && !showNDays"
v-model:value="formData.day"
:min="1"
:max="31"
:style="{ width: '140px' }"
>
<template #suffix>{{ $gettext('Day') }}</template>
</n-input-number>
<!-- 星期选择每周 -->
<n-select
v-if="showWeekday"
v-model:value="formData.weekday"
:options="weekdayOptions"
:style="{ width: '140px' }"
/>
<!-- 小时选择 -->
<n-input-number
v-if="showHour"
v-model:value="formData.hour"
:min="0"
:max="23"
:style="{ width: '140px' }"
>
<template #suffix>{{ $gettext('Hour') }}</template>
</n-input-number>
<!-- 分钟选择 -->
<n-input-number
v-if="showMinute && !showNMinutes"
v-model:value="formData.minute"
:min="0"
:max="59"
:style="{ width: '140px' }"
>
<template #suffix>{{ $gettext('Minute') }}</template>
</n-input-number>
<!-- 自定义 Cron 表达式 -->
<n-input
v-if="showCustom"
v-model:value="formData.customCron"
:placeholder="$gettext('Enter Cron expression')"
:style="{ width: '240px' }"
/>
</n-flex>
<!-- 预览 -->
<n-text depth="3">
<cron-preview :cron="value" />
</n-text>
</n-flex>
</template>
<style scoped lang="scss"></style>