vue项目中需要用到cron组件,在网上找了好多资料,但是都不符合我们要求。有的比较复杂,有的文档不太友好,有的不支持自定义,后面通过AI写了一个简单的cron组件。
先推荐一下网上的cron组件:
1.vcrontab:支持解析/反解析 cron 表达式,生成最近五次的符合条件时间,依赖 vue2 和 element-ui 效果图:
2.vue-cron
效果图:
3.vue-js-cron
效果图:
AI编写的cron组件:使用cron-parser
效果图:
代码:
组件:
<template>
<div class="simple-cron">
<!-- 模式切换 -->
<el-radio-group v-model="mode" class="mode-switch">
<el-radio-button label="hourly">时间段模式</el-radio-button>
<el-radio-button label="daily">间隔天数模式</el-radio-button>
</el-radio-group>
<!-- 时间段模式配置 -->
<div v-if="mode === 'hourly'" class="config-section">
<div class="config-item">
<span>每</span>
<el-input-number
v-model="hourly.interval"
:min="1"
:max="24"
controls-position="right"
></el-input-number>
<span>小时执行一次</span>
</div>
<div class="config-item">
<span>时间段:</span>
<el-time-select
v-model="hourly.startHour"
:picker-options="startTimeOptions"
placeholder="开始时间"
/>
<span class="time-separator">至</span>
<el-time-select
v-model="hourly.endHour"
:picker-options="endTimeOptions"
placeholder="结束时间"
/>
</div>
</div>
<!-- 间隔天数模式配置 -->
<div v-else class="config-section">
<div class="config-item">
<span>每隔</span>
<el-input-number
v-model="daily.interval"
:min="1"
:max="31"
controls-position="right"
></el-input-number>
<span>天执行一次</span>
</div>
<div class="config-item">
<span>开始时间:</span>
<el-time-picker
v-model="daily.startTime"
:picker-options="{
selectableRange: '00:00:00 - 23:59:59'
}"
format="HH:mm:ss"
value-format="HH:mm:ss"
placeholder="选择时间"
/>
</div>
</div>
<!-- 预览区域 -->
<div class="preview-section">
<el-alert
:title="`Cron表达式:${cronExpression}`"
type="info"
:closable="false"
/>
<el-divider content-position="left">最近5次执行时间</el-divider>
<el-table :data="executionTimes" size="mini" empty-text="无有效时间">
<el-table-column prop="time" label="执行时间" width="240"></el-table-column>
</el-table>
</div>
<!-- 清空按钮 -->
<el-button
@click="handleReset"
class="reset-btn"
size="mini"
>
清空配置
</el-button>
</div>
</template>
<script>
import cronParser from 'cron-parser';
export default {
name: 'SimpleCron',
data() {
return {
// 定义默认值常量
DEFAULT_START: '00:00',
DEFAULT_END: '23:00',
DEFAULT_TIME: '00:00:00',
// 数据初始化
mode: 'hourly',
hourly: {
interval: 1,
startHour: this.DEFAULT_START,
endHour: this.DEFAULT_END
},
daily: {
interval: 1,
startTime: this.DEFAULT_TIME
}
}
},
mounted(){
this.handleReset()
},
computed: {
// 时间段模式配置
startTimeOptions() {
return {
start: '00:00',
step: '01:00',
end: '23:00'
}
},
endTimeOptions() {
return {
start: '00:00',
step: '01:00',
end: '23:00',
minTime: this.hourly.startHour
}
},
// 日期选择限制
dateOptions() {
return {
disabledDate(time) {
return time.getTime() < Date.now() - 8.64e7 // 禁止选择昨天之前的日期
}
}
},
cronExpression() {
// 添加安全校验
try {
if (this.mode === 'hourly') {
const start = this.parseHour(this.hourly.startHour)
const end = this.parseHour(this.hourly.endHour)
return `0 0 ${start}-${end}/${this.hourly.interval} * * *`
} else {
const [hour, minute, second] = this.safeSplit(this.daily.startTime)
return `${second} ${minute} ${hour} */${this.daily.interval} * * *`
}
} catch {
return '无效配置'
}
},
// 执行时间计算
executionTimes() {
return this.mode === 'hourly'
? this.calculateHourlyTimes()
: this.calculateDailyTimes()
}
},
methods: {
// 安全分割方法
safeSplit(str, separator = ':', fallback = ['00', '00', '00']) {
return (str || '').split(separator).map(p => p || fallback[i])
},
// 解析小时数(带校验)
parseHour(timeStr) {
const [hour] = this.safeSplit(timeStr, ':', [this.DEFAULT_START])
return parseInt(hour) || 0
},
// 清空操作处理
handleReset() {
this.hourly = {
interval: 1,
startHour: this.DEFAULT_START,
endHour: this.DEFAULT_END
}
this.daily = {
interval: 1,
startTime: this.DEFAULT_TIME
}
// if (this.mode === 'hourly') {
// this.hourly = {
// interval: 1,
// startHour: this.DEFAULT_START,
// endHour: this.DEFAULT_END
// }
// } else {
// this.daily = {
// interval: 1,
// startTime: this.DEFAULT_TIME
// }
// }
// this.$nextTick(() => {
// this.$message.success('配置已重置')
// })
},
// 新增Cron表达式验证方法
validateCron() {
try {
cronParser.parseExpression(this.cronExpression)
return true
} catch {
return false
}
},
// 新增获取当天日期方法
getTodayString() {
const today = new Date()
const year = today.getFullYear()
const month = String(today.getMonth() + 1).padStart(2, '0')
const day = String(today.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
// 格式化时间(新增秒显示)
formatTime(date) {
const pad = n => n.toString().padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())}
${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
},
// 时间计算添加校验
calculateHourlyTimes() {
try {
if (!this.hourly.startHour || !this.hourly.endHour) {
throw new Error('时间段未配置')
}
const interval = cronParser.parseExpression(this.cronExpression, {
currentDate: new Date(),
iterator: true
})
return Array.from({ length: 5 }, () => {
const next = interval.next()
return { time: this.formatTime(next.value.toDate()) }
})
} catch (e) {
return [{ time: e.message || '配置错误' }]
}
},
calculateDailyTimes() {
try {
const now = new Date()
// 解析时分秒
const [hour, minute, second] = this.daily.startTime.split(':')
// 创建基准时间(今日 + 用户选择的时间)
let baseDate = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate(),
hour,
minute,
second
)
// 如果已过当前时间,跳到下个周期
if (baseDate < now) {
baseDate.setDate(baseDate.getDate() + this.daily.interval)
}
// 生成5次执行时间
return Array.from({ length: 5 }, (_, i) => {
const time = new Date(baseDate)
time.setDate(time.getDate() + i * this.daily.interval)
return { time: this.formatTime(time) }
})
} catch {
return [{ time: '无效配置' }]
}
}
},
watch: {
// 监视时间字段变化
'hourly.startHour'(val) {
if (!val) this.hourly.startHour = this.DEFAULT_START
},
'hourly.endHour'(val) {
if (!val) this.hourly.endHour = this.DEFAULT_END
},
'daily.startTime'(val) {
if (!val) this.daily.startTime = this.DEFAULT_TIME
},
// 监听所有配置项变化
cronExpression: {
handler: function(newVal){
this.$emit('input', newVal) // 触发v-model更新
this.$emit('change', newVal) // 同时触发自定义事件
},
immediate: true
}
},
props: {
value: {
type: String,
default: ''
}
},
}
</script>
<style scoped>
.simple-cron {
padding: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
background: #fff;
max-width: 800px;
position: relative;
}
.mode-switch {
margin-bottom: 20px;
}
.config-section {
display: flex;
flex-direction: column;
gap: 15px;
}
.config-item {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.time-separator {
margin: 0 10px;
color: #909399;
}
.ml-10 {
margin-left: 10px;
}
.preview-section {
margin-top: 20px;
}
.el-table {
margin-top: 10px;
}
.el-time-picker {
width: 160px !important;
}
.el-table .cell {
white-space: nowrap;
}
.reset-btn {
position: absolute;
right: 20px;
top: 20px;
z-index: 1;
}
/* 错误状态高亮 */
.invalid .el-input__inner {
border-color: #f56c6c !important;
}
</style>
组件使用:
<simple-cron v-model="rules_form.cron" @change="handleCronChange" />