自定义任务调度Cron表达式生成器

自定义任务调度Cron表达式生成器

本篇文档主要记录自己在开发过程中,通过各种插件集成cron到项目总是会存在一些小问题,于是自己在查询资料之后发现一个别人写的,把源码弄过来自己在进行调整和样式的优化,目前看起来正常。
主要使用的是vue3+elementPlus+jscript实现的,在此简单记录一下。

1、定义组件

定义Vue3Cron.vue组件,代码如下:

<!-- 自定义Cron表达式 -->
<template>
    <div class="v3c">
        <ul class="v3c-tab">
            <li class="v3c-tab-item"
                :class="{ 'v3c-active': tabActive == 1 }"
                @click="onHandleTab(1)">秒
            </li>
            <li class="v3c-tab-item"
                :class="{ 'v3c-active': tabActive == 2 }"
                @click="onHandleTab(2)">分</li>
            <li class="v3c-tab-item"
                :class="{ 'v3c-active': tabActive == 3 }"
                @click="onHandleTab(3)">时</li>
            <li class="v3c-tab-item"
                :class="{ 'v3c-active': tabActive == 4 }"
                @click="onHandleTab(4)">日</li>
            <li class="v3c-tab-item"
                :class="{ 'v3c-active': tabActive == 5 }"
                @click="onHandleTab(5)">月</li>
            <li class="v3c-tab-item"
                :class="{ 'v3c-active': tabActive == 6 }"
                @click="onHandleTab(6)">年</li>
        </ul>
        <!-- 秒 -->
        <div class="v3c-content"
             v-show="tabActive == 1">
            <!-- 每一秒 -->
            <div>
                <el-radio label="1"
                          v-model="state.second.cronEvery">每秒 允许的通配符[, - * /]</el-radio>
            </div>
            <!-- 每隔多久 -->
            <div class="margin-tb10">
                <el-radio label="2"
                          v-model="state.second.cronEvery">每隔</el-radio>
                <el-input-number v-model="state.second.incrementIncrement"
                                 :min="1"
                                 :max="60"
                                 controls-position="right" />
                <span class="margin-lr5">秒执行, 从</span>
                <el-input-number v-model="state.second.incrementStart"
                                 :min="0"
                                 :max="59"
                                 controls-position="right" />
                <span>秒开始</span>
            </div>
            <!-- 具体秒数 -->
            <div class="margin-tb10">
                <el-radio label="3"
                          v-model="state.second.cronEvery">指定(可多选)</el-radio>
                <div class="margin-l22 margin-t5">
                    <el-checkbox-group v-model="state.second.specificSpecific">
                        <el-checkbox v-for="(item, index) in 60"
                                     :key="index"
                                     :label="index"
                                     :value="index" />
                    </el-checkbox-group>
                </div>
            </div>
            <!-- 周期从 -->
            <div class="margin-tb10">
                <el-radio label="4"
                          v-model="state.second.cronEvery">周期从</el-radio>
                <el-input-number v-model="state.second.rangeStart"
                                 :min="0"
                                 :max="59"
                                 controls-position="right" />
                <sapn>秒</sapn><span class="margin-lr5">到</span>
                <el-input-number v-model="state.second.rangeEnd"
                                 :min="0"
                                 :max="59"
                                 controls-position="right" />
                <sapn>秒</sapn>
            </div>
        </div>
        <!-- 分钟 -->
        <div class="v3c-content"
             v-show="tabActive == 2">
            <!-- 每一分钟 -->
            <div>
                <el-radio label="1"
                          v-model="state.minute.cronEvery">每分 允许的通配符[, - * /]</el-radio>
            </div>
            <!-- 每隔多久 -->
            <div class="margin-tb10">
                <el-radio label="2"
                          v-model="state.minute.cronEvery">每隔</el-radio>
                <el-input-number v-model="state.minute.incrementIncrement"
                                 :min="1"
                                 :max="60"
                                 controls-position="right" />
                <span class="margin-lr5">分执行,从</span>
                <el-input-number v-model="state.minute.incrementStart"
                                 :min="0"
                                 :max="59"
                                 controls-position="right" />
                <span>分开始</span>
            </div>
            <!-- 具体分钟数 -->
            <div class="margin-tb10">
                <el-radio label="3"
                          v-model="state.minute.cronEvery">指定(可多选)</el-radio>
                <div class="margin-l22 margin-t5">
                    <el-checkbox-group v-model="state.minute.specificSpecific">
                        <el-checkbox v-for="(item, index) in 60"
                                     :key="index"
                                     :label="index"
                                     :value="index" />
                    </el-checkbox-group>
                </div>
            </div>
            <!-- 周期从 -->
            <div class="margin-tb10">
                <el-radio label="4"
                          v-model="state.minute.cronEvery">周期从</el-radio>
                <el-input-number v-model="state.minute.rangeStart"
                                 :min="0"
                                 :max="59"
                                 controls-position="right" />
                <span>分</span><span class="margin-lr5">到</span>
                <el-input-number v-model="state.minute.rangeEnd"
                                 :min="0"
                                 :max="59"
                                 controls-position="right" />
                <span>分</span>
            </div>
        </div>
        <!-- 小时 -->
        <div class="v3c-content"
             v-show="tabActive == 3">
            <!-- 每一小时 -->
            <div>
                <el-radio label="1"
                          v-model="state.hour.cronEvery">每小时 允许的通配符[, - * /]</el-radio>
            </div>
            <!-- 每隔多久 -->
            <div class="margin-tb10">
                <el-radio label="2"
                          v-model="state.hour.cronEvery">每隔</el-radio>
                <el-input-number v-model="state.hour.incrementIncrement"
                                 :min="1"
                                 :max="24"
                                 controls-position="right" />
                <span class="margin-lr5">小时执行,从</span>
                <el-input-number v-model="state.hour.incrementStart"
                                 :min="0"
                                 :max="23"
                                 controls-position="right" />
                <span>小时开始</span>
            </div>
            <!-- 具体小时数 -->
            <div class="margin-tb10">
                <el-radio label="3"
                          v-model="state.hour.cronEvery">指定(可多选)</el-radio>
                <div class="margin-l22 margin-t5">
                    <el-checkbox-group v-model="state.hour.specificSpecific">
                        <el-checkbox v-for="(item, index) in 24"
                                     :key="index"
                                     :label="index"
                                     :value="index" />
                    </el-checkbox-group>
                </div>
            </div>
            <!-- 周期从 -->
            <div class="margin-tb10">
                <el-radio label="4"
                          v-model="state.hour.cronEvery">周期从</el-radio>
                <el-input-number v-model="state.hour.rangeStart"
                                 :min="0"
                                 :max="23"
                                 controls-position="right" />
                <span>时</span><span class="margin-lr5">到</span>
                <el-input-number v-model="state.hour.rangeEnd"
                                 :min="0"
                                 :max="23"
                                 controls-position="right" />
                <span>时</span>
            </div>
        </div>
        <!-- 天 -->
        <div class="v3c-content"
             v-show="tabActive == 4">
            <!-- 1 -->
            <div>
                <el-radio label="1"
                          v-model="state.day.cronEvery">每天 允许的通配符[, - * /]</el-radio>
            </div>
            <!-- 2 -->
            <div class="margin-tb10">
                <el-radio label="2"
                          v-model="state.day.cronEvery">每隔</el-radio>
                <el-input-number v-model="state.week.incrementIncrement"
                                 :min="1"
                                 :max="60"
                                 controls-position="right" />
                <span class="margin-lr5">周执行,从</span>
                <el-input-number v-model="state.week.incrementStart"
                                 :min="1"
                                 :max="52"
                                 controls-position="right" />
                <span>周开始</span>
            </div>
            <!-- 3 -->
            <div class="margin-tb10">
                <el-radio label="3"
                          v-model="state.day.cronEvery">每隔</el-radio>
                <el-input-number v-model="state.day.incrementIncrement"
                                 :min="1"
                                 :max="30"
                                 controls-position="right" />
                <span class="margin-lr5">天执行,从</span>
                <el-input-number v-model="state.day.incrementStart"
                                 :min="1"
                                 :max="30"
                                 controls-position="right" />
                <span>天开始</span>
            </div>
            <!-- 4 -->
            <div class="margin-tb10">
                <el-radio label="4"
                          v-model="state.day.cronEvery">指定(可多选)</el-radio>
                <div class="margin-l22 margin-t5">
                    <el-checkbox-group v-model="state.week.specificSpecific">
                        <el-checkbox v-for="(item, index) in weekList"
                                     :key="index"
                                     :label="item.name"
                                     :value="item.value" />
                    </el-checkbox-group>
                </div>
            </div>
            <!-- 5 -->
            <div class="margin-tb10">
                <el-radio label="5"
                          v-model="state.day.cronEvery">具体天数(可多选)</el-radio>
                <div class="margin-l22 margin-t5">
                    <el-checkbox-group v-model="state.day.specificSpecific">
                        <el-checkbox v-for="(item, index) in 31"
                                     :key="index"
                                     :label="item"
                                     :value="item" />
                    </el-checkbox-group>
                </div>
            </div>
            <!-- 6 -->
            <div class="margin-tb10">
                <el-radio label="6"
                          v-model="state.day.cronEvery">本月的最后一日</el-radio>
            </div>
            <!-- 7 -->
            <div class="margin-tb10">
                <el-radio label="7"
                          v-model="state.day.cronEvery">本月的最后一工作日</el-radio>
            </div>
            <!-- 8 -->
            <!-- <div class="margin-tb10">
          <el-radio label="8" v-model="state.day.cronEvery">在这个月的最后一个</el-radio>
          <el-select v-model="state.day.cronLastSpecificDomDay" style="width: 140px">
            <el-option v-for="(item, index) in weekList" :key="index" :label="item.name" :value="item.val" />
          </el-select>
        </div> -->
            <!-- 9 -->
            <!-- <div class="margin-tb10">
            <el-radio label="9" v-model="state.day.cronEvery">{{ }}</el-radio>
            <el-input-number v-model="state.day.cronDaysBeforeEomMinus" :min="1" :max="31" controls-position="right" />
            <span>在本月底前</span>
        </div> -->
            <!-- 10 -->
            <!-- <div class="margin-tb10">
          <el-radio label="10" v-model="state.day.cronEvery">最近的工作日(周一至周五)至本月</el-radio>
          <el-input-number v-model="state.day.cronDaysNearestWeekday" :min="1" :max="31" controls-position="right" />
          <span>日</span>
        </div> -->
            <!-- 11 -->
            <!-- <div class="margin-tb10">
          <el-radio label="11" v-model="state.day.cronEvery">在这个月的第</el-radio>
          <el-input-number v-model="state.week.cronNthDayNth" :min="1" :max="5" controls-position="right" />
          <span>个</span>
          <el-select v-model="state.week.cronNthDayDay" style="width: 140px">
            <el-option v-for="(item, index) in weekList" :key="index" :label="item.name" :value="item.val" />
          </el-select>
        </div> -->
        </div>
        <!-- 月 -->
        <div class="v3c-content"
             v-show="tabActive == 5">
            <!-- 1 -->
            <div>
                <el-radio label="1"
                          v-model="state.month.cronEvery">每月 允许的通配符[, - * /]</el-radio>
            </div>
            <!-- 2 -->
            <div class="margin-tb10">
                <el-radio label="2"
                          v-model="state.month.cronEvery">每隔</el-radio>
                <el-input-number v-model="state.month.incrementIncrement"
                                 :min="1"
                                 :max="12"
                                 controls-position="right" />
                <span class="margin-lr5">月执行,从</span>
                <el-input-number v-model="state.month.incrementStart"
                                 :min="1"
                                 :max="12"
                                 controls-position="right" />
                <span>月开始</span>
            </div>
            <!-- 3 -->
            <div class="margin-tb10">
                <el-radio label="3"
                          v-model="state.month.cronEvery">指定(可多选)</el-radio>
                <div class="margin-l22 margin-t5">
                    <el-checkbox-group v-model="state.month.specificSpecific">
                        <el-checkbox v-for="(item, index) in 12"
                                     :key="index"
                                     :label="item"
                                     :value="item" />
                    </el-checkbox-group>
                </div>
            </div>
            <!-- 4 -->
            <div class="margin-tb10">
                <el-radio label="4"
                          v-model="state.month.cronEvery">周期从</el-radio>
                <el-input-number v-model="state.month.rangeStart"
                                 :min="1"
                                 :max="12"
                                 controls-position="right" />
                <span>月</span><span class="margin-lr5">到</span>
                <el-input-number v-model="state.month.rangeEnd"
                                 :min="1"
                                 :max="12"
                                 controls-position="right" />
                <span>月</span>
            </div>
        </div>
        <!-- 年 -->
        <div class="v3c-content"
             v-show="tabActive == 6">
            <!-- 1 -->
            <div>
                <el-radio label="1"
                          v-model="state.year.cronEvery">每年</el-radio>
            </div>
            <!-- 2 -->
            <div class="margin-tb10">
                <el-radio label="2"
                          v-model="state.year.cronEvery">每隔</el-radio>
                <el-input-number v-model="state.year.incrementIncrement"
                                 :min="1"
                                 :max="99"
                                 controls-position="right" />
                <span class="margin-lr5">年执行,从</span>
                <el-input-number v-model="state.year.incrementStart"
                                 :min="currYear"
                                 :max="currYear + 10"
                                 controls-position="right"
                                 style="width:100px;" />
                <span>年开始</span>
            </div>
            <!-- 3 -->
            <div class="margin-tb10">
                <el-radio label="3"
                          v-model="state.year.cronEvery">具体年份(可多选)</el-radio>
                <el-select multiple
                           clearable
                           collapse-tags
                           collapse-tags-tooltip
                           v-model="state.year.specificSpecific"
                           style="width: 200px">
                    <el-option v-for="(item, index) in 100"
                               :key="index"
                               :label="currYear + item"
                               :value="currYear + item" />
                </el-select>
            </div>
            <!-- 4 -->
            <div class="margin-tb10">
                <el-radio label="4"
                          v-model="state.year.cronEvery">周期从</el-radio>
                <el-input-number v-model="state.year.rangeStart"
                                 :min="currYear"
                                 :max="currYear + 10"
                                 controls-position="right"
                                 style="width:100px;" />
                <span>年</span><span class="margin-lr5">到</span>
                <el-input-number v-model="state.year.rangeEnd"
                                 :min="currYear"
                                 :max="currYear + 10"
                                 controls-position="right"
                                 style="width:100px;" />
                <span>年</span>
            </div>
        </div>
        <!-- 结果 -->
        <div class="v3c-footer">
            <div class="cron"> Cron表达式:{{ state.cron }}</div>
            <el-button type="primary"
                       @click.stop="handleChange">生成cron</el-button>
        </div>
    </div>
</template>

 <script>
import { reactive, computed, toRefs, defineComponent, ref, watch } from "vue";
export default defineComponent({
    name: "Vue3Cron",
    props: {
        maxHeight: String,
        change: Function,
        value: String,
    },
    setup(props, { emit }) {
        const weekList = ref([
            { name: '星期日', value: 'SUN', val: 1, },
            { name: '星期一', value: 'MON', val: 2, },
            { name: '星期二', value: 'TUE', val: 3, },
            { name: '星期三', value: 'WED', val: 4, },
            { name: '星期四', value: 'THU', val: 5, },
            { name: '星期五', value: 'FRI', val: 6, },
            { name: '星期六', value: 'SAT', val: 7, },
        ])

        const tabActive = ref(1);
        const currYear = ref(new Date().getFullYear());
        const onHandleTab = (index) => {
            tabActive.value = index;
        };

        // (默认是每一分钟一次)
        const state = reactive({
            second: {
                cronEvery: "1",
                incrementStart: 0,
                incrementIncrement: 1,
                rangeStart: 0,
                rangeEnd: 0,
                specificSpecific: [],
            },
            minute: {
                cronEvery: "1",
                incrementStart: 0,
                incrementIncrement: 1,
                rangeStart: 0,
                rangeEnd: 0,
                specificSpecific: [],
            },
            hour: {
                cronEvery: "1",
                incrementStart: 1,
                incrementIncrement: 1,
                rangeStart: 0,
                rangeEnd: 0,
                specificSpecific: [],
            },
            day: {
                cronEvery: "1",
                incrementStart: 1,
                incrementIncrement: 1,
                rangeStart: 0,
                rangeEnd: 0,
                specificSpecific: [],
                cronLastSpecificDomDay: 1,
                cronDaysBeforeEomMinus: 0,
                cronDaysNearestWeekday: 1,
            },
            week: {
                cronEvery: "1",
                incrementStart: 1,
                incrementIncrement: 1,
                specificSpecific: [],
                cronNthDayDay: 1,
                cronNthDayNth: 1,
            },
            month: {
                cronEvery: "1",
                incrementStart: 1,
                incrementIncrement: 1,
                rangeStart: 1,
                rangeEnd: 1,
                specificSpecific: [],
            },
            year: {
                cronEvery: "1",
                incrementStart: new Date().getFullYear(),
                incrementIncrement: 1,
                rangeStart: new Date().getFullYear(),
                rangeEnd: new Date().getFullYear(),
                specificSpecific: [],
            },
            output: {
                second: "",
                minute: "",
                hour: "",
                day: "",
                month: "",
                Week: "",
                year: "",
            },
            secondsText: computed(() => {
                let seconds = "";
                let cronEvery = state.second.cronEvery;
                switch (cronEvery?.toString()) {
                    case "1":
                        seconds = "*";
                        break;
                    case "2":
                        seconds = state.second.incrementStart + "/" + state.second.incrementIncrement;
                        break;
                    case "3":
                        state.second.specificSpecific.map((val) => {
                            seconds += val + ",";
                        });
                        seconds = seconds.slice(0, -1);
                        break;
                    case "4":
                        seconds = state.second.rangeStart + "-" + state.second.rangeEnd;
                        break;
                }
                return seconds;
            }),
            minutesText: computed(() => {
                let minutes = "";
                let cronEvery = state.minute.cronEvery;
                switch (cronEvery?.toString()) {
                    case "1":
                        minutes = "*";
                        break;
                    case "2":
                        minutes = state.minute.incrementStart + "/" + state.minute.incrementIncrement;
                        break;
                    case "3":
                        state.minute.specificSpecific.map((val) => {
                            minutes += val + ",";
                        });
                        minutes = minutes.slice(0, -1);
                        break;
                    case "4":
                        minutes = state.minute.rangeStart + "-" + state.minute.rangeEnd;
                        break;
                }
                return minutes;
            }),
            hoursText: computed(() => {
                let hours = "";
                let cronEvery = state.hour.cronEvery;
                switch (cronEvery?.toString()) {
                    case "1":
                        hours = "*";
                        break;
                    case "2":
                        hours = state.hour.incrementStart + "/" + state.hour.incrementIncrement;
                        break;
                    case "3":
                        state.hour.specificSpecific.map((val) => {
                            hours += val + ",";
                        });
                        hours = hours.slice(0, -1);
                        break;
                    case "4":
                        hours = state.hour.rangeStart + "-" + state.hour.rangeEnd;
                        break;
                }
                return hours;
            }),
            daysText: computed(() => {
                let days = "";
                let cronEvery = state.day.cronEvery;
                switch (cronEvery?.toString()) {
                    case "1":
                        break;
                    case "2":
                    case "4":
                    case "11":
                        days = "?";
                        break;
                    case "3":
                        days = state.day.incrementStart + "/" + state.day.incrementIncrement;
                        break;
                    case "5":
                        state.day.specificSpecific.map((val) => {
                            days += val + ",";
                        });
                        days = days.slice(0, -1);
                        break;
                    case "6":
                        days = "L";
                        break;
                    case "7":
                        days = "LW";
                        break;
                    case "8":
                        days = state.day.cronLastSpecificDomDay + "L";
                        break;
                    case "9":
                        days = "L-" + state.day.cronDaysBeforeEomMinus;
                        break;
                    case "10":
                        days = state.day.cronDaysNearestWeekday + "W";
                        break;
                }
                return days;
            }),
            weeksText: computed(() => {
                let weeks = "";
                let cronEvery = state.day.cronEvery;
                switch (cronEvery?.toString()) {
                    case "1":
                    case "3":
                    case "5":
                        weeks = "?";
                        break;
                    case "2":
                        weeks = state.week.incrementStart + "/" + state.week.incrementIncrement;
                        break;
                    case "4":
                        state.week.specificSpecific.map((val) => {
                            weeks += val + ",";
                        });
                        weeks = weeks.slice(0, -1);
                        break;
                    case "6":
                    case "7":
                    case "8":
                    case "9":
                    case "10":
                        weeks = "?";
                        break;
                    case "11":
                        weeks = state.week.cronNthDayDay + "#" + state.week.cronNthDayNth;
                        break;
                }
                return weeks;
            }),
            monthsText: computed(() => {
                let months = "";
                let cronEvery = state.month.cronEvery;
                switch (cronEvery?.toString()) {
                    case "1":
                        months = "*";
                        break;
                    case "2":
                        months = state.month.incrementStart + "/" + state.month.incrementIncrement;
                        break;
                    case "3":
                        state.month.specificSpecific.map((val) => {
                            months += val + ",";
                        });
                        months = months.slice(0, -1);
                        break;
                    case "4":
                        months = state.month.rangeStart + "-" + state.month.rangeEnd;
                        break;
                }
                return months;
            }),
            yearsText: computed(() => {
                let years = "";
                // TODO,目前先不指定年份,注释以下代码
                let cronEvery = state.year.cronEvery;
                switch (cronEvery?.toString()) {
                    case "1":
                        years = "*";
                        break;
                    case "2":
                        years = state.year.incrementStart + "/" + state.year.incrementIncrement;
                        break;
                    case "3":
                        state.year.specificSpecific.map((val) => {
                            years += val + ",";
                        });
                        years = years.slice(0, -1);
                        break;
                    case "4":
                        years = state.year.rangeStart + "-" + state.year.rangeEnd;
                        break;
                }
                return years;
            }),
            cron: computed(() => {
                return `${state.secondsText || "*"} ${state.minutesText || "*"} ${state.hoursText || "*"} ${state.daysText || "*"} ${state.monthsText || "*"} ${state.weeksText || "?"} ${state.yearsText || "*"}`;
            }),
        });

        const handleChange = () => {
            if (typeof state.cron !== "string") return false;
            emit("change", state.cron);
        };
        const rest = (data) => {
            for (let i in data) {
                if (data[i] instanceof Object) {
                    this.rest(data[i]);
                } else {
                    switch (typeof data[i]) {
                        case "object":
                            data[i] = [];
                            break;
                        case "string":
                            data[i] = "";
                            break;
                    }
                }
            }
        };

        watch(
            () => state.cron,
            (value) => {
                if (typeof state.cron !== "string") return;
                emit("update:value", value);
            }
        );

        return {
            weekList,
            state,
            handleChange,
            rest,
            tabActive,
            onHandleTab,
            currYear,
        };
    },
});
  </script>

<style lang="scss" scoped>
:deep(.el-input-number) {
    width: 80px;
    margin-right: 5px;
}

:deep(.el-radio) {
    margin-right: 10px;
}

.v3c {
    width: auto;
    border: 1px solid #f7f7f7;
    border-radius: 4px;
}

.v3c-tab {
    padding: 0;
    list-style: none;
    margin: 0;
    display: flex;
    background-color: #f7f7f7;
}

.v3c-tab-item {
    flex: 1;
    text-align: center;
    cursor: pointer;
    padding: 6px;
}

.v3c-tab-item.v3c-active {
    background-color: $theme-color;
    color: #ffffff;
    border-radius: 4px 4px 0 0;
}

.v3c-content {
    padding: 20px;
    max-height: v-bind(maxHeight);
    overflow: hidden;
    overflow-y: auto;
}

.v3c-footer {
    background-color: #f7f7f7;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 5px;
}

.cron {
    color: #5c5c5c;
    font-size: 16px;
    font-weight: 400;
    margin-right: 10px;
}
</style>

2、使用组件

在需要使用的页面引入Vue3Cron.vue组件,代码如下:

//引入组件
import Vue3Cron from './Vue3Cron'
//模版使用
<Vue3Cron @change="fillValue" v-model:value="你定义的cron值"></Vue3Cron>

//点击生成
const fillValue = (val) => {
    if (typeof val !== "string") return false;
    你定义的cron值 = val;
}

目前不支持回显,需要自己可自行调整。

二分
JSRUN前端笔记, 是针对前端工程师开放的一个笔记分享平台,是前端工程师记录重点、分享经验的一个笔记本。JSRUN前端采用的 MarkDown 语法 (极客专用语法), 这里属于IT工程师。