Files
test-repo/docs/.vitepress/theme/components/appendix/scheduled-tasks/CronExpressionDemo.vue
T

80 lines
3.5 KiB
Vue
Raw Normal View History

<template>
<div class="cron-demo">
<div class="header">
<div class="title">Cron 表达式解析</div>
<div class="subtitle">选择或输入 Cron 表达式查看下次执行时间</div>
</div>
<div class="presets">
<button v-for="p in presets" :key="p.expr" :class="['preset-btn', { active: expr === p.expr }]" @click="expr = p.expr">
{{ p.label }}
</button>
</div>
<div class="input-row">
<input v-model="expr" class="cron-input" placeholder="* * * * *" />
<button class="calc-btn" @click="calculate">计算</button>
</div>
<div class="result" v-if="nextRun">
<div class="next-label">下次执行时间</div>
<div class="next-time">{{ nextRun }}</div>
</div>
<div class="desc">
<div class="desc-title">字段说明</div>
<div class="desc-grid">
<div class="desc-item"><span class="field"></span> 0-59</div>
<div class="desc-item"><span class="field"></span> 0-23</div>
<div class="desc-item"><span class="field"></span> 1-31</div>
<div class="desc-item"><span class="field"></span> 1-12</div>
<div class="desc-item"><span class="field"></span> 0-6</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const expr = ref('0 * * * *')
const nextRun = ref('')
const presets = [
{ label: '每小时', expr: '0 * * * *' },
{ label: '每天午夜', expr: '0 0 * * *' },
{ label: '每周一', expr: '0 0 * * 1' },
{ label: '每月1号', expr: '0 0 1 * *' },
{ label: '每5分钟', expr: '*/5 * * * *' },
]
function calculate() {
const parts = expr.value.trim().split(/\s+/)
if (parts.length !== 5) {
nextRun.value = '请输入 5 个字段的 Cron 表达式'
return
}
const now = new Date()
const fieldNames = ['分钟', '小时', '日', '月', '星期']
let desc = parts.map((p, i) => `${fieldNames[i]}: ${p}`).join('')
nextRun.value = `${now.toLocaleString()} 开始,${desc}`
}
</script>
<style scoped>
.cron-demo { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft); border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0; }
.header { margin-bottom: 1rem; }
.title { font-weight: 700; font-size: 1.1rem; }
.subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; }
.presets { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1rem; }
.preset-btn { padding: 0.4rem 0.8rem; border-radius: 6px; border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); cursor: pointer; font-size: 0.85rem; }
.preset-btn.active { border-color: var(--vp-c-brand); color: var(--vp-c-brand); }
.input-row { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.cron-input { flex: 1; padding: 0.5rem; border: 1px solid var(--vp-c-divider); border-radius: 6px; background: var(--vp-c-bg); font-family: var(--vp-font-family-mono); font-size: 1rem; }
.calc-btn { padding: 0.5rem 1rem; border-radius: 6px; background: var(--vp-c-brand); color: #fff; border: none; cursor: pointer; }
.result { padding: 1rem; background: var(--vp-c-bg); border-radius: 8px; margin-bottom: 1rem; }
.next-label { font-size: 0.9rem; color: var(--vp-c-text-2); }
.next-time { font-size: 1rem; font-weight: 600; color: var(--vp-c-brand); }
.desc { font-size: 0.85rem; }
.desc-title { font-weight: 600; margin-bottom: 0.5rem; }
.desc-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); gap: 0.5rem; }
.desc-item { padding: 0.5rem; background: var(--vp-c-bg); border-radius: 4px; }
.field { font-weight: 600; color: var(--vp-c-brand); }
</style>