vue3的CRON表达式组件用法解读

 更新时间:2026年01月15日 09:27:11   作者:没头脑和不高兴y  
该文档介绍了Cron表达式生成器的使用说明,该组件通过图形化界面帮助用户配置定时任务的执行计划,支持秒、分钟、小时、日、月、周等时间单位的灵活配置,并实时计算并显示最近5次执行时间

Cron 表达式生成器使用说明文档

功能概述

本组件是一个可视化 Cron 表达式生成器,用于帮助用户通过图形化界面配置定时任务的执行计划。

支持秒、分钟、小时、日、月、周等时间单位的灵活配置,并实时计算显示最近5次执行时间。支持编辑和回显

主要功能

1. 时间单位配置

  • :支持每秒、周期范围、间隔执行和指定秒数
  • 分钟:支持每分钟、周期范围、间隔执行和指定分钟数
  • 小时:支持每小时、周期范围、间隔执行和指定小时数
  • :支持每日、不指定、周期范围、间隔执行、工作日、月末和指定日期
  • :支持每月、不指定、周期范围、间隔执行和指定月份
  • :支持每周、不指定、周期范围、间隔执行、月末周和指定星期

2. 实时预览

  • 实时生成并显示 Cron 表达式
  • 自动计算并显示最近5次执行时间

使用说明

基本操作

  1. 通过顶部标签页切换不同时间单位
  2. 选择对应的时间配置方式(单选按钮)
  3. 根据选择的配置方式填写具体参数
  4. 系统自动生成 Cron 表达式并显示执行时间

配置选项说明

秒配置
  • 每秒:任务每秒执行一次
  • 周期从...到...秒:在指定秒数范围内执行
  • 周期从...秒开始,每...秒执行一次:从指定秒数开始,按间隔执行
  • 指定秒数:勾选具体的秒数执行
分钟配置
  • 每分钟:任务每分钟执行一次
  • 周期从...到...分钟:在指定分钟范围内执行
  • 周期从...分钟开始,每...分钟执行一次:从指定分钟开始,按间隔执行
  • 指定分钟数:勾选具体的分钟数执行
小时配置
  • 每小时:任务每小时执行一次
  • 周期从...到...小时:在指定小时范围内执行
  • 周期从...小时开始,每...小时执行一次:从指定小时开始,按间隔执行
  • 指定小时数:勾选具体的小时数执行
日配置
  • 每日:任务每天执行一次
  • 不指定:不指定具体日期
  • 周期从...到...日:在指定日期范围内执行
  • 周期从...日开始,每...日执行一次:从指定日期开始,按间隔执行
  • 每月...号最近的那个工作日:指定日期最近的工作日执行
  • 本月最后一天:每月最后一天执行
  • 指定日期:勾选具体的日期执行
月配置
  • 每月:任务每月执行一次
  • 不指定:不指定具体月份
  • 周期从...到...月:在指定月份范围内执行
  • 周期从...月开始,每...月执行一次:从指定月份开始,按间隔执行
  • 指定月份:勾选具体的月份执行
周配置
  • 每周:任务每周执行一次
  • 不指定:不指定具体星期
  • 周期从星期...到星期...:在指定星期范围内执行
  • 第...周的星期...:指定某周的某天执行
  • 本月最后一个星期...:每月最后一个指定星期执行
  • 指定星期:勾选具体的星期执行

技术实现

核心功能

  • 使用 cron-parser 库解析和计算 Cron 表达式
  • 使用 dayjs 处理日期时间格式化
  • 基于 Vue 3 的 Composition API 实现响应式逻辑
  • 使用 Element Plus 组件库构建 UI

数据流

用户通过界面配置时间参数

组件内部将配置转换为标准 Cron 表达式

通过 v-model 将表达式传递给父组件

同时计算并显示最近5次执行时间

<template>
  <div style="width: 700px; margin: 0 auto; padding: 20px" class="cron-selector">
    <el-tabs v-model="activeTab" type="border-card" :lazy="true">
      <el-tab-pane name="秒" label="秒">
        <el-radio-group @change="() => resetRadioValues('second')" v-model="secondRadio">
          <el-radio value="1" size="large">每秒 允许的通配符[,-*/]</el-radio>
          <el-radio value="2" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">周期从</span>
                <el-input v-model="secondStart1" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">到</span>
                <el-input v-model="secondEnd1" style="width: 60px; margin-right: 8px" />
                <span>秒</span>
              </div>
            </template>
          </el-radio>
          <el-radio value="3" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">周期从</span>
                <el-input v-model="secondStart2" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">秒开始,每</span>
                <el-input v-model="secondEnd2" style="width: 60px; margin-right: 8px" />
                <span>秒执行一次</span>
              </div>
            </template>
          </el-radio>
          <el-radio value="4" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">指定</span>
                <el-checkbox-group :disabled="secondRadio !== '4'" v-model="checkSeconds">
                  <el-checkbox
                    v-for="(item, index) in secondList"
                    :label="item"
                    :value="item"
                    :key="index"
                  />
                </el-checkbox-group>
              </div>
            </template>
          </el-radio>
        </el-radio-group>
      </el-tab-pane>
      <el-tab-pane name="分钟" label="分钟">
        <el-radio-group @change="() => resetRadioValues('minutes')" v-model="minutesRadio">
          <el-radio value="1" size="large">每分钟 允许的通配符[,-*/]</el-radio>
          <el-radio value="2" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">周期从</span>
                <el-input v-model="minutesStart1" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">到</span>
                <el-input v-model="minutesEnd1" style="width: 60px; margin-right: 8px" />
                <span>分钟</span>
              </div>
            </template>
          </el-radio>
          <el-radio value="3" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">周期从</span>
                <el-input v-model="minutesStart2" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">分钟开始,每</span>
                <el-input v-model="minutesEnd2" style="width: 60px; margin-right: 8px" />
                <span>分钟执行一次</span>
              </div>
            </template>
          </el-radio>
          <el-radio value="4" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">指定</span>
                <el-checkbox-group v-model="checkMinutes" :disabled="minutesRadio !== '4'">
                  <el-checkbox
                    v-for="(item, index) in minuteList"
                    :label="item"
                    :value="item"
                    :key="index"
                  />
                </el-checkbox-group>
              </div>
            </template>
          </el-radio>
        </el-radio-group>
      </el-tab-pane>

      <el-tab-pane name="小时" label="小时">
        <el-radio-group @change="() => resetRadioValues('hour')" v-model="hourRadio">
          <el-radio value="1" size="large">每小时 允许的通配符[,-*/]</el-radio>
          <el-radio value="2" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">周期从</span>
                <el-input v-model="hourStart1" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">到</span>
                <el-input v-model="hourEnd1" style="width: 60px; margin-right: 8px" />
                <span>小时</span>
              </div>
            </template>
          </el-radio>
          <el-radio value="3" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">周期从</span>
                <el-input v-model="hourStart2" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">小时开始,每</span>
                <el-input v-model="hourEnd2" style="width: 60px; margin-right: 8px" />
                <span>小时执行一次</span>
              </div>
            </template>
          </el-radio>
          <el-radio value="4" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">指定</span>
                <el-checkbox-group v-model="checkHour" :disabled="hourRadio !== '4'">
                  <el-checkbox
                    v-for="(item, index) in hourList"
                    :label="item"
                    :value="item"
                    :key="index"
                  />
                </el-checkbox-group>
              </div>
            </template>
          </el-radio>
        </el-radio-group>
      </el-tab-pane>
      <el-tab-pane name="日" label="日">
        <el-radio-group @change="() => resetRadioValues('day')" v-model="dayRadio">
          <el-radio value="1" size="large">每日 允许的通配符[,-*/]</el-radio>
          <el-radio value="2" size="large">不指定</el-radio>
          <el-radio value="3" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">周期从</span>
                <el-input v-model="dayStart1" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">到</span>
                <el-input v-model="dayEnd1" style="width: 60px; margin-right: 8px" />
                <span>日</span>
              </div>
            </template>
          </el-radio>
          <el-radio value="4" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">周期从</span>
                <el-input v-model="dayStart2" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">日开始,每</span>
                <el-input v-model="dayEnd2" style="width: 60px; margin-right: 8px" />
                <span>日执行一次</span>
              </div>
            </template>
          </el-radio>
          <el-radio value="5" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">每月</span>
                <el-input v-model="day" style="width: 60px; margin-right: 8px" />
                <span>号最近的那个工作日</span>
              </div>
            </template>
          </el-radio>
          <el-radio value="6" size="large">本月最后一天</el-radio>
          <el-radio value="7" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">指定</span>
                <el-checkbox-group v-model="checkDay" :disabled="dayRadio !== '7'">
                  <el-checkbox
                    v-for="(item, index) in dayList"
                    :label="item"
                    :value="item"
                    :key="index"
                  />
                </el-checkbox-group>
              </div>
            </template>
          </el-radio>
        </el-radio-group>
      </el-tab-pane>
      <el-tab-pane name="月" label="月">
        <el-radio-group @change="() => resetRadioValues('month')" v-model="monthRadio">
          <el-radio value="1" size="large">每月 允许的通配符[,-*/]</el-radio>
          <el-radio value="2" size="large">不指定</el-radio>
          <el-radio value="3" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">周期从</span>
                <el-input v-model="monthStart1" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">到</span>
                <el-input v-model="monthEnd1" style="width: 60px; margin-right: 8px" />
                <span>月</span>
              </div>
            </template>
          </el-radio>
          <el-radio value="4" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">周期从</span>
                <el-input v-model="monthStart2" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">月开始,每</span>
                <el-input v-model="monthEnd2" style="width: 60px; margin-right: 8px" />
                <span>月执行一次</span>
              </div>
            </template>
          </el-radio>
          <el-radio value="5" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px;x">指定</span>
                <el-checkbox-group
                  style="margin-top: 10px"
                  :disabled="monthRadio !== '5'"
                  v-model="selectedMonth"
                >
                  <el-checkbox
                    v-for="(item, index) in monthList"
                    :label="item"
                    :value="item"
                    :key="index"
                  />
                </el-checkbox-group>
              </div>
            </template>
          </el-radio>
        </el-radio-group>
      </el-tab-pane>
      <el-tab-pane name="周" label="周">
        <el-radio-group @change="() => resetRadioValues('week')" v-model="weekRadio">
          <el-radio value="1" size="large">每周 允许的通配符[,-*/]</el-radio>
          <el-radio value="2" size="large">不指定</el-radio>
          <el-radio value="3" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">周期从星期</span>
                <el-input v-model="weekStart1" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">到星期</span>
                <el-input v-model="weekEnd1" style="width: 60px; margin-right: 8px" />
              </div>
            </template>
          </el-radio>
          <el-radio value="4" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">第</span>
                <el-input v-model="weekStart2" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">周的星期</span>
                <el-input v-model="weekEnd2" style="width: 60px; margin-right: 8px" />
              </div>
            </template>
          </el-radio>
          <el-radio value="5" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">本月最后一个星期</span>
                <el-input v-model="week" style="width: 60px; margin-right: 8px" />
              </div>
            </template>
          </el-radio>
          <el-radio value="6" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">指定</span>
                <el-checkbox-group v-model="selectedWeek" :disabled="weekRadio !== '6'">
                  <el-checkbox
                    v-for="(item, index) in weekList"
                    :label="item"
                    :value="item"
                    :key="index"
                    style="margin-bottom: 0"
                  />
                </el-checkbox-group>
              </div>
            </template>
          </el-radio>
        </el-radio-group>
      </el-tab-pane>
      <!-- <el-tab-pane name="年" label="年">
        <el-radio-group @change="() => resetRadioValues('year')" v-model="yearRadio">
          <el-radio value="1" size="large">不指定 允许的通配符[, - * /]</el-radio>
          <el-radio value="2" size="large">每年</el-radio>
          <el-radio value="3" size="large">
            <template #default>
              <div style="display: flex; align-items: center">
                <span style="margin-right: 8px">周期从</span>
                <el-input v-model="yearStart" style="width: 60px; margin-right: 8px" />
                <span style="margin-right: 8px">到</span>
                <el-input v-model="yearEnd" style="width: 60px; margin-right: 8px" />
              </div>
            </template>
          </el-radio>
        </el-radio-group>
      </el-tab-pane> -->
    </el-tabs>

    <!-- 添加最近运行时间展示区域 -->
    <div class="next-runs" v-if="nextRuns.length > 0">
      <h4>最近5次运行时间:</h4>
      <ul>
        <li v-for="(run, index) in nextRuns" :key="index">{{ run }}</li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { reactive, ref, watch, watchEffect } from 'vue'
import cronParser from 'cron-parser'
import dayjs from 'dayjs'
import { onMounted } from 'vue'
const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  }
})
const activeTab = ref('秒')
const emit = defineEmits(['update:modelValue'])

const ruleFormRef = ref()
const ruleForm = ref({})

const pad = (n) => n.toString().padStart(2, '0')
const secondList = new Array(60).fill(0).map((_, index) => {
  return pad(index)
})
const hourList = new Array(24).fill(0).map((_, index) => {
  return pad(index)
})
const weekList = new Array(7).fill(0).map((_, index) => {
  return pad(index + 1)
})
const dayList = new Array(31).fill(0).map((_, index) => {
  return pad(index + 1) // 修改为从1开始,生成01,02,...31
})

const month = new Array(12).fill(0).map((_, index) => {
  return pad(index + 1)
})
const minuteList = new Array(60).fill(0).map((_, index) => {
  return pad(index)
})

const monthList = new Array(12).fill(0).map((_, index) => {
  return pad(index + 1)
})
// 秒选框
const secondRadio = ref('1')
const secondStart1 = ref(1)
const secondEnd1 = ref(2)
const secondStart2 = ref(0)
const secondEnd2 = ref(1)
// 分钟选框
const minutesRadio = ref('1')
const minutesStart1 = ref(1)
const minutesEnd1 = ref(2)
const minutesStart2 = ref(0)
const minutesEnd2 = ref(1)
// 小时选框
const hourRadio = ref('1')
const hourStart1 = ref(1)
const hourEnd1 = ref(2)
const hourStart2 = ref(0)
const hourEnd2 = ref(1)
// 日选框
// 日选框默认值修改
const dayRadio = ref('1')
const dayStart1 = ref(1) // 原值1保持不变
const dayEnd1 = ref(31) // 改为31
const dayStart2 = ref(1) // 原值1保持不变
const dayEnd2 = ref(1) // 原值1保持不变
const day = ref(1) // 原值1保持不变

// 月选框
const monthRadio = ref('1')
const monthStart1 = ref(1)
const monthEnd1 = ref(2)
const monthStart2 = ref(1)
const monthEnd2 = ref(1)
// 周选框
const weekRadio = ref('2')
const weekStart1 = ref(1)
const weekEnd1 = ref(2)
const weekStart2 = ref(1)
const weekEnd2 = ref(1)
const week = ref('')
// 年
const yearRadio = ref('1')
const yearStart = ref('')
const yearEnd = ref('')

const checkSeconds = ref([])
const checkMinutes = ref([])
const checkHour = ref([])
const checkDay = ref([])
const selectedMonth = ref([])
const selectedWeek = ref([])
const fieldVal = ref({
  second: '*',
  minutes: '*',
  hour: '*',
  day: '*',
  month: '*',
  week: '?'
})
const resetRadioValues = (field) => {
  switch (field) {
    case 'second':
      secondStart1.value = 1
      secondEnd1.value = 2
      secondStart2.value = 0
      secondEnd2.value = 1
      checkSeconds.value = []
      break
    case 'minutes':
      minutesStart1.value = 1
      minutesEnd1.value = 2
      minutesStart2.value = 0
      minutesEnd2.value = 1
      checkMinutes.value = []
      break
    case 'hour':
      hourStart1.value = 1
      hourEnd1.value = 2
      hourStart2.value = 0
      hourEnd2.value = 1
      checkHour.value = []
      break
    case 'day':
      dayStart1.value = 1
      dayEnd1.value = 2
      dayStart2.value = 1
      dayEnd2.value = 1
      day.value = 1
      checkDay.value = []
      break
    case 'month':
      monthStart1.value = 1
      monthEnd1.value = 2
      monthStart2.value = 1
      monthEnd2.value = 1
      selectedMonth.value = []
      break
    case 'week':
      weekStart1.value = 1
      weekEnd1.value = 2
      weekStart2.value = 1
      weekEnd2.value = 1
      week.value = ''
      selectedWeek.value = []
      break
    case 'year':
      yearStart.value = ''
      yearEnd.value = ''
      break
  }
}

watchEffect(() => {
  // 秒
  if (secondRadio.value == '1') {
    resetRadioValues('second')
    fieldVal.value.second = '*'
  } else if (secondRadio.value == '2') {
    fieldVal.value.second = `${secondStart1.value}-${secondEnd1.value}`
  } else if (secondRadio.value == '3') {
    fieldVal.value.second = `${secondStart2.value}/${secondEnd2.value}`
  } else if (secondRadio.value == '4') {
    const list = checkSeconds.value.map((v) => parseInt(v))
    list.sort((a, b) => a - b)
    fieldVal.value.second = list.join(',')
  }
  // 分
  if (minutesRadio.value == '1') {
    resetRadioValues('minutes')
    fieldVal.value.minutes = '*'
  } else if (minutesRadio.value == '2') {
    fieldVal.value.minutes = `${minutesStart1.value}-${minutesEnd1.value}`
  } else if (minutesRadio.value == '3') {
    fieldVal.value.minutes = `${minutesStart2.value}/${minutesEnd2.value}`
  } else if (minutesRadio.value == '4') {
    const list = checkMinutes.value.map((v) => parseInt(v))
    list.sort((a, b) => a - b)
    fieldVal.value.minutes = list.join(',')
  }
  // 时
  if (hourRadio.value == '1') {
    resetRadioValues('hour')
    fieldVal.value.hour = '*'
  } else if (hourRadio.value == '2') {
    fieldVal.value.hour = `${hourStart1.value}-${hourEnd1.value}`
  } else if (hourRadio.value == '3') {
    fieldVal.value.hour = `${hourStart2.value}/${hourEnd2.value}`
  } else if (hourRadio.value == '4') {
    const list = checkHour.value.map((v) => parseInt(v))
    list.sort((a, b) => a - b)
    fieldVal.value.hour = list.join(',')
  }
  // 日
  switch (dayRadio.value) {
    case '1':
      fieldVal.value.day = '*'
      break
    case '2':
      fieldVal.value.day = '?'
      break
    case '3':
      fieldVal.value.day = `${dayStart1.value}-${dayEnd1.value}`
      break
    case '4':
      fieldVal.value.day = `${dayStart2.value}/${dayEnd2.value}`
      break
    case '5':
      fieldVal.value.day = `${day.value}W`
      break
    case '6':
      fieldVal.value.day = `L`
      break
    case '7':
      const list = checkDay.value.map((v) => parseInt(v))
      list.sort((a, b) => a - b)
      fieldVal.value.day = list.join(',')
      break
  }
  // 月
  switch (monthRadio.value) {
    case '1':
      fieldVal.value.month = '*'
      break
    case '2':
      fieldVal.value.month = '?'
      break
    case '3':
      fieldVal.value.month = `${monthStart1.value}-${monthEnd1.value}`
      break
    case '4':
      fieldVal.value.month = `${monthStart2.value}/${monthEnd2.value}`
      break
    case '5':
      const list = selectedMonth.value.map((v) => parseInt(v))
      list.sort((a, b) => a - b)
      fieldVal.value.month = list.join(',')
      break
  }
  // 周
  switch (weekRadio.value) {
    case '1':
      fieldVal.value.week = '*'
      break
    case '2':
      fieldVal.value.week = '?'
      break
    case '3':
      fieldVal.value.week = `${weekStart1.value}-${weekEnd1.value}`
      break
    case '4':
      fieldVal.value.week = `${weekStart2.value}/${weekEnd2.value}`
      break
    case '5':
      fieldVal.value.week = `${week.value}L`
      break
    case '6':
      const list = selectedWeek.value.map((v) => parseInt(v))
      list.sort((a, b) => a - b)
      fieldVal.value.week = list.join(',')
      break
  }
  switch (yearRadio.value) {
    case '1':
      fieldVal.value.year = ''
      break
    case '2':
      fieldVal.value.year = '*'
      break
    case '3':
      fieldVal.value.year = `${yearStart.value}-${yearEnd.value}`
      break
  }
})
// 移除mounted中的强制设置
onMounted(() => {
  console.log('activeTab value:', activeTab.value)
  // 移除这行强制设置
  // activeTab.value = '秒'
})

// 在解析方法中添加activeTab自动切换逻辑
const parseField = (value, field, radioRef, checkRef, start1Ref, end1Ref, start2Ref, end2Ref) => {
  // 新增字段类型映射
  const fieldMap = {
    second: '秒',
    minutes: '分钟',
    hour: '小时',
    day: '日',
    month: '月',
    week: '周',
    year: '年'
  }

  // 在解析时自动切换tab
  // if (value !== '*' && value !== '?') {
  //   activeTab.value = fieldMap[field]
  // }
  if (value === '*') {
    radioRef.value = '1'
    checkRef.value = []
  } else if (value.includes('-')) {
    radioRef.value = '2'
    const [start, end] = value.split('-')
    start1Ref.value = parseInt(start)
    end1Ref.value = parseInt(end)
  } else if (value.includes('/')) {
    radioRef.value = '3'
    const [start, end] = value.split('/')
    start2Ref.value = parseInt(start)
    end2Ref.value = parseInt(end)
  } else if (value.includes(',')) {
    radioRef.value = '4'
    checkRef.value = value.split(',').map((v) => pad(parseInt(v)))
  } else {
    // 新增单个数值处理
    radioRef.value = '4'
    checkRef.value = [pad(parseInt(value))]
  }
}
// 添加解析日字段的特殊处理
const parseDayField = (
  value,
  radioRef,
  checkRef,
  start1Ref,
  end1Ref,
  start2Ref,
  end2Ref,
  dayRef
) => {
  if (value === '*') {
    radioRef.value = '1'
  } else if (value === '?') {
    radioRef.value = '2'
  } else if (value.includes('-')) {
    radioRef.value = '3'
    const [start, end] = value.split('-')
    start1Ref.value = parseInt(start)
    end1Ref.value = parseInt(end)
  } else if (value.includes('/')) {
    radioRef.value = '4'
    const [start, end] = value.split('/')
    start2Ref.value = parseInt(start)
    end2Ref.value = parseInt(end)
  } else if (value.endsWith('W')) {
    radioRef.value = '5'
    dayRef.value = parseInt(value.replace('W', ''))
  } else if (value === 'L') {
    radioRef.value = '6'
  } else if (value.includes(',')) {
    radioRef.value = '7'
    checkRef.value = value.split(',').map((v) => pad(parseInt(v)))
  } else {
    // 新增单个数值处理
    radioRef.value = '7'
    checkRef.value = [pad(parseInt(value))]
  }
}
// 添加解析周字段的特殊处理
const parseWeekField = (
  value,
  radioRef,
  checkRef,
  start1Ref,
  end1Ref,
  start2Ref,
  end2Ref,
  weekRef
) => {
  if (value === '*') {
    radioRef.value = '1'
  } else if (value === '?') {
    radioRef.value = '2'
  } else if (value.includes('-')) {
    radioRef.value = '3'
    const [start, end] = value.split('-')
    start1Ref.value = parseInt(start)
    end1Ref.value = parseInt(end)
  } else if (value.includes('/')) {
    radioRef.value = '4'
    const [start, end] = value.split('/')
    start2Ref.value = parseInt(start)
    end2Ref.value = parseInt(end)
  } else if (value.endsWith('L')) {
    radioRef.value = '5'
    weekRef.value = value.replace('L', '')
  } else if (value.includes(',')) {
    radioRef.value = '6'
    checkRef.value = value.split(',').map((v) => pad(parseInt(v)))
  } else {
    // 新增单个数值处理
    radioRef.value = '6'
    checkRef.value = [pad(parseInt(value))]
  }
}
const cron = ref('')
// 修改watch部分
// watch(
//   () => fieldVal.value,
//   (newVal) => {
//     const { second, minutes, hour, day, month, week } = newVal
//     // 只使用前6个标准字段
//     const cronValue = `${second} ${minutes} ${hour} ${day} ${month} ${week}`
//     cron.value = cronValue
//     emit('update:modelValue', cronValue)
//   },
//   { immediate: true, deep: true }
// )
let isInternalUpdate = false
// 添加初始化方法,解析传入的cron表达式
watch(
  () => props.modelValue,
  (newVal, oldVal) => {
    if (isInternalUpdate) {
      isInternalUpdate = false
      return
    }
    if (newVal && newVal !== oldVal) {
      nextTick(() => {
        setTimeout(() => {
          console.log('newVal', newVal)
          try {
            const parts = newVal.split(' ')
            if (parts.length >= 6) {
              clear(false)
              // 解析秒
              parseField(
                parts[0],
                'second',
                secondRadio,
                checkSeconds,
                secondStart1,
                secondEnd1,
                secondStart2,
                secondEnd2
              )
              // 解析分钟
              parseField(
                parts[1],
                'minutes',
                minutesRadio,
                checkMinutes,
                minutesStart1,
                minutesEnd1,
                minutesStart2,
                minutesEnd2
              )
              // 解析小时
              parseField(
                parts[2],
                'hour',
                hourRadio,
                checkHour,
                hourStart1,
                hourEnd1,
                hourStart2,
                hourEnd2
              )
              // 解析日
              parseDayField(
                parts[3],
                dayRadio,
                checkDay,
                dayStart1,
                dayEnd1,
                dayStart2,
                dayEnd2,
                day
              )
              // 解析月
              parseField(
                parts[4],
                'month',
                monthRadio,
                selectedMonth,
                monthStart1,
                monthEnd1,
                monthStart2,
                monthEnd2
              )
              // 解析周
              parseWeekField(
                parts[5],
                weekRadio,
                selectedWeek,
                weekStart1,
                weekEnd1,
                weekStart2,
                weekEnd2,
                week
              )
              // 最后设置activeTab
              const priorityFields = [
                { index: 0, tab: '秒' },
                { index: 1, tab: '分钟' },
                { index: 2, tab: '小时' },
                { index: 3, tab: '日' },
                { index: 4, tab: '月' },
                { index: 5, tab: '周' }
              ]
              const firstNonDefault = priorityFields.find(
                ({ index }) => parts[index] !== '*' && parts[index] !== '?'
              )
              activeTab.value = firstNonDefault?.tab || '秒'
            }
          } catch (e) {
            console.error('解析Cron表达式出错:', e)
          }
        }, 200)
      })
    }
  },
  { immediate: true }
)
// 添加计算最近运行时间的函数
const nextRuns = ref([])

const calculateNextRuns = async () => {
  try {
    if (!cron.value || cron.value.trim() === '') {
      nextRuns.value = ['请先配置有效的Cron表达式']
      return
    }

    const parser = cronParser.default || cronParser
    const interval = parser.parse(cron.value, {
      currentDate: new Date(),
      endDate: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)
    })

    const runs = []
    for (let i = 0; i < 5; i++) {
      try {
        const nextDate = interval.next().toDate()
        // 使用dayjs格式化日期
        const formattedDate = dayjs(nextDate).format('YYYY-MM-DD HH:mm:ss')
        runs.push(formattedDate)
      } catch (e) {
        break
      }
    }
    nextRuns.value = runs.length > 0 ? runs : ['无有效执行时间']
  } catch (e) {
    console.error('解析Cron表达式出错:', e)
    nextRuns.value = [`无效的Cron表达式: ${cron.value}`]
  }
}

// 修改watch部分,使其成为异步函数
watch(
  () => fieldVal.value,
  async (newVal) => {
    isInternalUpdate = true
    const { second, minutes, hour, day, month, week, year } = newVal
    const cronValue = `${second} ${minutes} ${hour} ${day} ${month} ${week} ${year}`
    cron.value = cronValue
    emit('update:modelValue', cronValue)
    await calculateNextRuns()
  },
  { immediate: true, deep: true }
)

const clear = (resetRadio = true) => {
  // 仅在需要重置radio时修改activeTab
  if (resetRadio) {
    activeTab.value = '秒'
  }
  // 重置所有radio选项为默认值
  secondRadio.value = '1'
  minutesRadio.value = '1'
  hourRadio.value = '1'
  dayRadio.value = '1'
  monthRadio.value = '1'
  weekRadio.value = '2'
  yearRadio.value = '1'

  // 清空所有checkbox选择
  checkSeconds.value = []
  checkMinutes.value = []
  checkHour.value = []
  checkDay.value = []
  selectedMonth.value = []
  selectedWeek.value = []

  // 重置所有输入框为默认值
  secondStart1.value = 1
  secondEnd1.value = 2
  secondStart2.value = 0
  secondEnd2.value = 1

  minutesStart1.value = 1
  minutesEnd1.value = 2
  minutesStart2.value = 0
  minutesEnd2.value = 1

  hourStart1.value = 1
  hourEnd1.value = 2
  hourStart2.value = 0
  hourEnd2.value = 1

  dayStart1.value = 1
  dayEnd1.value = 2
  dayStart2.value = 1
  dayEnd2.value = 1
  day.value = 1

  monthStart1.value = 1
  monthEnd1.value = 2
  monthStart2.value = 1
  monthEnd2.value = 1

  weekStart1.value = 1
  weekEnd1.value = 2
  weekStart2.value = 1
  weekEnd2.value = 1
  week.value = ''

  yearStart.value = ''
  yearEnd.value = ''
}
// watch(activeTab, () => {
//   clear(false) // 传入false不清空radio选项,只清空输入值和checkbox
// })
// 暴露clear方法
defineExpose({
  clear
})
</script>

<style lang="scss" scoped>
.form-title {
  line-height: 50px;
  font-weight: 700;
  font-weight: bold;
  font-size: 18px;
  position: relative;
  text-indent: 14px;

  &::before {
    content: ' ';
    position: absolute;
    width: 4px;
    left: 0;
    top: calc(50% - 10px);
    height: 20px;
    border-radius: 2px;
    background: #62a7f1;
  }
}

.cron-selector {
  ::v-deep(.el-tabs__content) {
    padding: 10px;
  }

  ::v-deep(.el-radio-group) {
    .el-radio {
      width: 100%;
      height: auto;
      margin-bottom: 10px;
      min-height: 32px;
    }
  }

  ::v-deep(.el-checkbox-group) {
    display: flex;
    flex-wrap: wrap;

    .el-checkbox {
      margin-right: 10px;
      height: auto;
      margin-bottom: 10px;
    }
  }
}
/* 添加样式 */
.next-runs {
  margin-top: 20px;
  padding: 15px;
  background-color: #fff;
  border-radius: 4px;

  h4 {
    margin-bottom: 10px;
    color: #333;
  }

  ul {
    padding-left: 20px;
    margin: 0;

    li {
      line-height: 1.8;
      color: #333;
    }
  }
}
</style>

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 基于Vue+elementUI实现动态表单的校验功能(根据条件动态切换校验格式)

    基于Vue+elementUI实现动态表单的校验功能(根据条件动态切换校验格式)

    这篇文章主要介绍了Vue+elementUI的动态表单的校验(根据条件动态切换校验格式),本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-04-04
  • vue中的路由跳转tabBar图片和文字的高亮效果

    vue中的路由跳转tabBar图片和文字的高亮效果

    这篇文章主要介绍了vue中的路由跳转tabBar图片和文字的高亮效果,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • Vue中比较流行且好用的组件使用示例

    Vue中比较流行且好用的组件使用示例

    这篇文章主要介绍了Vue中比较流行且好用的一些组件使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • Vue3中使用Element-Plus的el-upload组件限制只上传一个文件的功能实现

    Vue3中使用Element-Plus的el-upload组件限制只上传一个文件的功能实现

    在 Vue 3 中使用 Element-Plus 的 el-upload 组件进行文件上传时,有时候需要限制只能上传一个文件,本文将介绍如何通过配置 el-upload 组件实现这个功能,让你的文件上传变得更加简洁和易用,需要的朋友可以参考下
    2023-10-10
  • VUE中v-model和v-for指令详解

    VUE中v-model和v-for指令详解

    本篇文章主要介绍了VUE中v-model和v-for指令详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • js 实现拖拽排序详情

    js 实现拖拽排序详情

    这篇文章主要介绍了js 实现拖拽排序,拖拽排序对于小伙伴们来说应该不陌生,平时工作的时候,可能会选择使用类似Sortable.js这样的开源库来实现需求。但在完成需求后,大家有没有没想过拖拽排序是如何实现的呢?感兴趣得话一起来看看下面文章得小心内容吧
    2021-11-11
  • 前端架构vue架构插槽slot使用教程

    前端架构vue架构插槽slot使用教程

    这篇文章主要为大家介绍了前端vue架构插槽slot使用教程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-02-02
  • Vue实现省市区三级联动

    Vue实现省市区三级联动

    这篇文章主要为大家详细介绍了Vue实现省市区三级联动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-12-12
  • 关于vue-router路由的传参方式params query

    关于vue-router路由的传参方式params query

    这篇文章主要介绍了关于vue-router路由的传参方式params query,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • vue echarts左右间距调整左右空白问题及解决

    vue echarts左右间距调整左右空白问题及解决

    这篇文章主要介绍了vue echarts左右间距调整左右空白问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04

最新评论