227 lines
5.0 KiB
Vue
227 lines
5.0 KiB
Vue
|
|
<!-- 下次执行时间预览组件 -->
|
|||
|
|
<template>
|
|||
|
|
<div class="next-execution-preview">
|
|||
|
|
<div class="preview-header">
|
|||
|
|
<Icon icon="ep:timer" class="preview-icon" />
|
|||
|
|
<span class="preview-title">执行时间预览</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div v-if="isValidCron" class="preview-content">
|
|||
|
|
<div class="current-expression">
|
|||
|
|
<span class="expression-label">CRON表达式:</span>
|
|||
|
|
<code class="expression-code">{{ cronExpression }}</code>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="description">
|
|||
|
|
<span class="description-label">执行规律:</span>
|
|||
|
|
<span class="description-text">{{ cronDescription }}</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="next-times">
|
|||
|
|
<span class="times-label">接下来5次执行时间:</span>
|
|||
|
|
<div class="times-list">
|
|||
|
|
<div
|
|||
|
|
v-for="(time, index) in nextExecutionTimes"
|
|||
|
|
:key="index"
|
|||
|
|
class="time-item"
|
|||
|
|
>
|
|||
|
|
<Icon icon="ep:clock" class="time-icon" />
|
|||
|
|
<span class="time-text">{{ time }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div v-else class="preview-error">
|
|||
|
|
<el-alert
|
|||
|
|
title="CRON表达式无效"
|
|||
|
|
description="请检查CRON表达式格式是否正确"
|
|||
|
|
type="error"
|
|||
|
|
:closable="false"
|
|||
|
|
show-icon
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { validateCronExpression } from '../../utils/validation'
|
|||
|
|
|
|||
|
|
/** 下次执行时间预览组件 */
|
|||
|
|
defineOptions({ name: 'NextExecutionPreview' })
|
|||
|
|
|
|||
|
|
interface Props {
|
|||
|
|
cronExpression?: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const props = defineProps<Props>()
|
|||
|
|
|
|||
|
|
// 计算属性
|
|||
|
|
const isValidCron = computed(() => {
|
|||
|
|
return props.cronExpression ? validateCronExpression(props.cronExpression) : false
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const cronDescription = computed(() => {
|
|||
|
|
if (!isValidCron.value) return ''
|
|||
|
|
|
|||
|
|
// 简单的CRON描述生成
|
|||
|
|
const parts = props.cronExpression?.split(' ') || []
|
|||
|
|
if (parts.length < 6) return '无法解析'
|
|||
|
|
|
|||
|
|
const [second, minute, hour, day, month, week] = parts
|
|||
|
|
|
|||
|
|
// 生成描述
|
|||
|
|
let description = ''
|
|||
|
|
|
|||
|
|
if (second === '0' && minute === '0' && hour === '12' && day === '*' && month === '*' && week === '?') {
|
|||
|
|
description = '每天中午12点执行'
|
|||
|
|
} else if (second === '0' && minute === '*' && hour === '*' && day === '*' && month === '*' && week === '?') {
|
|||
|
|
description = '每分钟执行一次'
|
|||
|
|
} else if (second === '0' && minute === '0' && hour === '*' && day === '*' && month === '*' && week === '?') {
|
|||
|
|
description = '每小时执行一次'
|
|||
|
|
} else {
|
|||
|
|
description = '按自定义时间规律执行'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return description
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const nextExecutionTimes = computed(() => {
|
|||
|
|
if (!isValidCron.value) return []
|
|||
|
|
|
|||
|
|
// 模拟生成下次执行时间
|
|||
|
|
const now = new Date()
|
|||
|
|
const times = []
|
|||
|
|
|
|||
|
|
for (let i = 1; i <= 5; i++) {
|
|||
|
|
// 这里应该使用真实的CRON解析库来计算
|
|||
|
|
// 暂时生成模拟时间
|
|||
|
|
const nextTime = new Date(now.getTime() + i * 60 * 60 * 1000)
|
|||
|
|
times.push(nextTime.toLocaleString('zh-CN', {
|
|||
|
|
year: 'numeric',
|
|||
|
|
month: '2-digit',
|
|||
|
|
day: '2-digit',
|
|||
|
|
hour: '2-digit',
|
|||
|
|
minute: '2-digit',
|
|||
|
|
second: '2-digit'
|
|||
|
|
}))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return times
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.next-execution-preview {
|
|||
|
|
margin-top: 16px;
|
|||
|
|
border: 1px solid var(--el-border-color-light);
|
|||
|
|
border-radius: 6px;
|
|||
|
|
background: var(--el-fill-color-blank);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.preview-header {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 8px;
|
|||
|
|
padding: 12px 16px;
|
|||
|
|
background: var(--el-fill-color-light);
|
|||
|
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.preview-icon {
|
|||
|
|
color: var(--el-color-primary);
|
|||
|
|
font-size: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.preview-title {
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: var(--el-text-color-primary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.preview-content {
|
|||
|
|
padding: 16px;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.current-expression {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.expression-label {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--el-text-color-secondary);
|
|||
|
|
min-width: 80px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.expression-code {
|
|||
|
|
font-family: 'Courier New', monospace;
|
|||
|
|
background: var(--el-fill-color-light);
|
|||
|
|
padding: 4px 8px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--el-color-primary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.description {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.description-label {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--el-text-color-secondary);
|
|||
|
|
min-width: 80px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.description-text {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--el-text-color-primary);
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.next-times {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.times-label {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--el-text-color-secondary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.times-list {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 4px;
|
|||
|
|
margin-left: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.time-item {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.time-icon {
|
|||
|
|
color: var(--el-color-success);
|
|||
|
|
font-size: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.time-text {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--el-text-color-primary);
|
|||
|
|
font-family: 'Courier New', monospace;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.preview-error {
|
|||
|
|
padding: 16px;
|
|||
|
|
}
|
|||
|
|
</style>
|