diff --git a/web/package-lock.json b/web/package-lock.json index d382e84..c006000 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -22,6 +22,7 @@ "chart.js": "^4.5.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cronstrue": "^3.13.0", "date-fns": "^4.1.0", "lucide-react": "^0.562.0", "lucide-vue-next": "^0.562.0", @@ -983,6 +984,15 @@ "node": ">=6" } }, + "node_modules/cronstrue": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-3.13.0.tgz", + "integrity": "sha512-M06cKwRIN46AyuM8BOmF1HUkBTkd3/h7uYImnrH1T3wtRKBGOibVo3jZ42VheEvx8LtgZbG/4GI35vfIxYxMug==", + "license": "MIT", + "bin": { + "cronstrue": "bin/cli.js" + } + }, "node_modules/csstype": { "version": "3.2.3", "license": "MIT" diff --git a/web/package.json b/web/package.json index 5ff9d86..dc9b1c7 100644 --- a/web/package.json +++ b/web/package.json @@ -23,6 +23,7 @@ "chart.js": "^4.5.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cronstrue": "^3.13.0", "date-fns": "^4.1.0", "lucide-react": "^0.562.0", "lucide-vue-next": "^0.562.0", diff --git a/web/src/utils/cron.ts b/web/src/utils/cron.ts new file mode 100644 index 0000000..5b63420 --- /dev/null +++ b/web/src/utils/cron.ts @@ -0,0 +1,16 @@ +import cronstrue from 'cronstrue' +import 'cronstrue/locales/zh_CN' + +export function getCronDescription(cron: string, lang?: string) { + if (!cron || cron.trim() === '') return '' + try { + const locale = lang?.startsWith('en') ? 'en' : 'zh_CN' + return cronstrue.toString(cron, { + locale, + use24HourTimeFormat: true, + throwExceptionOnParseError: true, + }) + } catch (e) { + return '' + } +} diff --git a/web/src/views/tasks/RepoDialog.vue b/web/src/views/tasks/RepoDialog.vue index f7d39f3..5226721 100644 --- a/web/src/views/tasks/RepoDialog.vue +++ b/web/src/views/tasks/RepoDialog.vue @@ -13,6 +13,7 @@ import { X, Globe, GitBranch, Shield, Zap, Clock } from 'lucide-vue-next' import { api, type Task, type RepoConfig, type Agent } from '@/api' import { toast } from 'vue-sonner' import { cn } from '@/lib/utils' +import { getCronDescription } from '@/utils/cron' const props = defineProps<{ open: boolean @@ -65,6 +66,11 @@ const selectedAgentId = ref('local') const tagInput = ref('') const whitelistInput = ref('') +const cronDescription = computed(() => { + if (!form.value.schedule) return '' + return getCronDescription(form.value.schedule, (navigator as any).language) +}) + function addTag() { const val = tagInput.value.trim() if (!val) return @@ -420,6 +426,10 @@ async function save() {
+
+ + {{ cronDescription }} +
格式指导: 秒 分 时 日 月 周 diff --git a/web/src/views/tasks/TaskDialog.vue b/web/src/views/tasks/TaskDialog.vue index c8db6d0..5f09572 100644 --- a/web/src/views/tasks/TaskDialog.vue +++ b/web/src/views/tasks/TaskDialog.vue @@ -14,6 +14,7 @@ import { cn } from '@/lib/utils' import { api, type Task, type EnvVar, type Agent, type MiseLanguage } from '@/api' import { TRIGGER_TYPE } from '@/constants' import { toast } from 'vue-sonner' +import { getCronDescription } from '@/utils/cron' const props = defineProps<{ open: boolean @@ -54,6 +55,11 @@ const concurrency = ref(0) const concurrencyEnabled = ref(false) const allEnvsEnabled = ref(false) +const cronDescription = computed(() => { + if (!form.value.schedule) return '' + return getCronDescription(form.value.schedule, (navigator as any).language) +}) + // 监听 concurrencyEnabled 的变化,同步到 concurrency watch(concurrencyEnabled, (val) => { concurrency.value = val ? 1 : 0 @@ -705,6 +711,10 @@ async function save() {
+
+ + {{ cronDescription }} +
格式指导: 秒 分 时 日 月 周