first commit
This commit is contained in:
380
src/components/GenCron/CronForm/index.vue
Normal file
380
src/components/GenCron/CronForm/index.vue
Normal file
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<div class="cron-inner">
|
||||
<div class="content">
|
||||
<!-- 设置表单 -->
|
||||
<a-tabs v-model:active-key="activeKey" size="small">
|
||||
<!-- 秒 -->
|
||||
<a-tab-pane v-if="!hideSecond" key="second" title="秒">
|
||||
<SecondForm v-model="second" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
<!-- 分 -->
|
||||
<a-tab-pane key="minute" title="分">
|
||||
<MinuteForm v-model="minute" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
<!-- 时 -->
|
||||
<a-tab-pane key="hour" title="时">
|
||||
<HourForm v-model="hour" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
<!-- 日 -->
|
||||
<a-tab-pane key="day" title="日">
|
||||
<DayForm v-model="day" :week="week" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
<!-- 月 -->
|
||||
<a-tab-pane key="month" title="月">
|
||||
<MonthForm v-model="month" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
<!-- 周 -->
|
||||
<a-tab-pane key="week" title="周">
|
||||
<WeekForm v-model="week" :day="day" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
<!-- 年 -->
|
||||
<a-tab-pane v-if="!hideYear && !hideSecond" key="year" title="年">
|
||||
<YearForm v-model="year" :disabled="disabled" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<!-- 执行时间预览 -->
|
||||
<a-row :gutter="8">
|
||||
<!-- 快捷修改 -->
|
||||
<a-col :span="18" style="margin-top: 28px">
|
||||
<a-row :gutter="[12, 12]">
|
||||
<!-- 秒 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="cronInputs.second" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'second'">秒</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 分 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="cronInputs.minute" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'minute'">分</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 时 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="cronInputs.hour" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'hour'">时</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 日 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="cronInputs.day" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'day'">日</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 月 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="cronInputs.month" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'month'">月</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 周 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="cronInputs.week" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'week'">周</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 年 -->
|
||||
<a-col :span="8">
|
||||
<a-input v-model="cronInputs.year" @change="onInputChange">
|
||||
<template #prepend>
|
||||
<span class="allow-click" @click="activeKey = 'year'">年</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<!-- 表达式 -->
|
||||
<a-col :span="16">
|
||||
<a-input
|
||||
v-model="cronInputs.cron"
|
||||
:placeholder="placeholder"
|
||||
@change="onInputCronChange"
|
||||
>
|
||||
<template #prepend>
|
||||
<span class="allow-click">表达式</span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<!-- 执行时间 -->
|
||||
<a-col :span="6">
|
||||
<div class="preview-times usn">近五次执行时间 (不解析年)</div>
|
||||
<a-textarea v-model="previewTimes" :auto-size="{ minRows: 5, maxRows: 5 }" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import CronParser from 'cron-parser'
|
||||
import SecondForm from '@/components/GenCron/CronForm/component/second-form.vue'
|
||||
import MinuteForm from '@/components/GenCron/CronForm/component/minute-form.vue'
|
||||
import HourForm from '@/components/GenCron/CronForm/component/hour-form.vue'
|
||||
import DayForm from '@/components/GenCron/CronForm/component/day-form.vue'
|
||||
import MonthForm from '@/components/GenCron/CronForm/component/month-form.vue'
|
||||
import WeekForm from '@/components/GenCron/CronForm/component/week-form.vue'
|
||||
import YearForm from '@/components/GenCron/CronForm/component/year-form.vue'
|
||||
import { dateFormat } from '@/utils'
|
||||
import type { CronPropType } from '@/components/GenCron/CronForm/type'
|
||||
|
||||
const props = withDefaults(defineProps<Partial<CronPropType>>(), {
|
||||
disabled: false,
|
||||
hideSecond: false,
|
||||
hideYear: false,
|
||||
placeholder: '请输入 Cron 表达式',
|
||||
})
|
||||
const emit = defineEmits(['change', 'update:modelValue'])
|
||||
const activeKey = ref(props.hideSecond ? 'minute' : 'second')
|
||||
const second = ref('*')
|
||||
const minute = ref('*')
|
||||
const hour = ref('*')
|
||||
const day = ref('*')
|
||||
const month = ref('*')
|
||||
const week = ref('?')
|
||||
const year = ref('*')
|
||||
const cronInputs = reactive({
|
||||
second: '',
|
||||
minute: '',
|
||||
hour: '',
|
||||
day: '',
|
||||
month: '',
|
||||
week: '',
|
||||
year: '',
|
||||
cron: '',
|
||||
})
|
||||
|
||||
const previewTimes = ref('执行预览')
|
||||
|
||||
// cron 表达式
|
||||
const cronExpression = computed(() => {
|
||||
const result: string[] = []
|
||||
if (!props.hideSecond) {
|
||||
result.push(second.value ? second.value : '*')
|
||||
}
|
||||
result.push(minute.value ? minute.value : '*')
|
||||
result.push(hour.value ? hour.value : '*')
|
||||
result.push(day.value ? day.value : '*')
|
||||
result.push(month.value ? month.value : '*')
|
||||
result.push(week.value ? week.value : '?')
|
||||
if (!props.hideYear && !props.hideSecond) {
|
||||
result.push(year.value ? year.value : '*')
|
||||
}
|
||||
return result.join(' ')
|
||||
})
|
||||
|
||||
// 不含年的 cron 表达式
|
||||
const expressionNoYear = (corn: string) => {
|
||||
if (props.hideYear || props.hideSecond) return corn
|
||||
const vs = corn.split(' ')
|
||||
return vs.slice(0, vs.length - 1).join(' ')
|
||||
}
|
||||
|
||||
// 计算触发时间
|
||||
const calculateNextExecutionTimes = (corn: string = cronExpression.value) => {
|
||||
try {
|
||||
const parse = expressionNoYear(corn)
|
||||
// 解析表达式
|
||||
const date = dateFormat(new Date())
|
||||
const iter = CronParser.parseExpression(parse, {
|
||||
currentDate: date,
|
||||
})
|
||||
const result: string[] = []
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
result.push(dateFormat(new Date(iter.next() as any)))
|
||||
}
|
||||
previewTimes.value = result.length > 0 ? result.join('\n') : '无执行时间'
|
||||
// 回调
|
||||
if (props.callback) {
|
||||
props.callback(cronExpression.value, +new Date(), true)
|
||||
}
|
||||
} catch (e) {
|
||||
previewTimes.value = '表达式错误'
|
||||
// 回调
|
||||
if (props.callback) {
|
||||
props.callback(cronExpression.value, +new Date(), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const calcTriggerTimeList = useDebounceFn(calculateNextExecutionTimes, 500)
|
||||
|
||||
// 监听 cron 修改
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal === cronExpression.value) {
|
||||
return
|
||||
}
|
||||
parseCron()
|
||||
})
|
||||
|
||||
// 监听 cron 修改
|
||||
watch(cronExpression, (newValue) => {
|
||||
calcTriggerTimeList()
|
||||
emitValue(newValue)
|
||||
assignInput()
|
||||
})
|
||||
|
||||
// 根据 cron 解析
|
||||
const parseCron = () => {
|
||||
// 计算执行时间
|
||||
calcTriggerTimeList()
|
||||
if (!props.modelValue) {
|
||||
return
|
||||
}
|
||||
const values = props.modelValue.split(' ').filter((item) => !!item)
|
||||
if (!values || values.length <= 0) {
|
||||
return
|
||||
}
|
||||
let i = 0
|
||||
if (!props.hideSecond) second.value = values[i++]
|
||||
if (values.length > i) minute.value = values[i++]
|
||||
if (values.length > i) hour.value = values[i++]
|
||||
if (values.length > i) day.value = values[i++]
|
||||
if (values.length > i) month.value = values[i++]
|
||||
if (values.length > i) week.value = values[i++]
|
||||
if (values.length > i) year.value = values[i]
|
||||
// 重新分配
|
||||
assignInput()
|
||||
}
|
||||
|
||||
// 重新分配
|
||||
const assignInput = () => {
|
||||
cronInputs.second = second.value
|
||||
cronInputs.minute = minute.value
|
||||
cronInputs.hour = hour.value
|
||||
cronInputs.day = day.value
|
||||
cronInputs.month = month.value
|
||||
cronInputs.week = week.value
|
||||
cronInputs.year = year.value
|
||||
cronInputs.cron = cronExpression.value
|
||||
}
|
||||
|
||||
// 修改 cron 解析内容
|
||||
const onInputChange = () => {
|
||||
second.value = cronInputs.second
|
||||
minute.value = cronInputs.minute
|
||||
hour.value = cronInputs.hour
|
||||
day.value = cronInputs.day
|
||||
month.value = cronInputs.month
|
||||
week.value = cronInputs.week
|
||||
year.value = cronInputs.year
|
||||
}
|
||||
|
||||
// 修改 cron 输入框
|
||||
const onInputCronChange = (value: string) => {
|
||||
emitValue(value)
|
||||
}
|
||||
|
||||
// 修改 cron
|
||||
const emitValue = (value: string) => {
|
||||
emit('change', value)
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
assignInput()
|
||||
parseCron()
|
||||
// 如果 modelValue 没有值则更新为 cronExpression
|
||||
if (!props.modelValue) {
|
||||
emitValue(cronExpression.value)
|
||||
}
|
||||
})
|
||||
const checkCron = () => {
|
||||
return (day.value === '?' && week.value === '?')
|
||||
}
|
||||
|
||||
defineExpose({ checkCron })
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.cron-inner {
|
||||
user-select: none;
|
||||
|
||||
:deep(.arco-tabs-content) {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
:deep(.cron-inner-config-list) {
|
||||
text-align: left;
|
||||
margin: 0 12px 4px 12px;
|
||||
|
||||
.item {
|
||||
margin-top: 6px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.choice {
|
||||
padding: 4px 8px 4px 0;
|
||||
}
|
||||
|
||||
.w60 {
|
||||
margin: 0 8px !important;
|
||||
padding: 0 8px !important;
|
||||
width: 60px !important;
|
||||
}
|
||||
|
||||
.w80 {
|
||||
margin: 0 8px !important;
|
||||
padding: 0 8px !important;
|
||||
width: 80px !important;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.list-check-item {
|
||||
padding: 1px 3px;
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.list-cn .list-check-item {
|
||||
width: 5em;
|
||||
}
|
||||
|
||||
.tip-info {
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-input-prepend) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.arco-input-append) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.preview-times {
|
||||
color: var(--color-text-3);
|
||||
margin: 2px 0 4px 0;
|
||||
}
|
||||
|
||||
.allow-click {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user