基于vue3的Crontab组件
网上找的没有满意的,决定从若依前后端分离其前端vue2中的crontab进行转换,先上效果
若依v2版:


改v3后:


v2转v3没什么难度,其中有大量的将 this.*** 替换为 ***.value,笔者写了个正则替换,希望可以帮助大家
this.(\w+) $1.value
需要注意的有,在v2中【this.$refs[refName].cycle01 = indexArr[0]】这样写
在v3中要转换一下,在子组件中用【defineExpose】抛出一个setData方法,然后【proxy.$refs[refName].setData("cycle01", Number(indexArr[0]))】赋值
其中子组件CrontabSecond.vue的代码,其它分/时/日/月/周/年的类似,参照着改就可以了
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
每秒,允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期,从
<el-input-number v-model='cycle01' :min="0" :max="58" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" /> 秒
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
从
<el-input-number v-model='average01' :min="0" :max="58" /> 秒开始,每
<el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 秒执行一次
</el-radio>
</el-form-item>
<el-form-item class="crontab-select">
<el-radio v-model='radioValue' :label="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:300px">
<el-option v-for="item in 60" :key="item" :value="String(item-1)" :label="'第' + String(item-1) + '秒'">第 {{item-1}} 秒</el-option>
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script setup name="CrontabSecond">
import { ref } from 'vue';
const emits = defineEmits(["update"]);
const props = defineProps({
check: {
type: Function,
require:false
},
cron: {
type: Object,
default: function(){
return {}
}
},
});
const radioValue = ref(1);
const cycle01 = ref(1);
const cycle02 = ref(2);
const average01 = ref(0);
const average02 = ref(1);
const checkboxList = ref([]);
// 计算两个周期值
const cycleTotal = computed(() => {
const c01 = props.check(cycle01.value, 0, 58)
const c02 = props.check(cycle02.value, c01 ? c01 + 1 : 1, 59)
return c01 + '-' + c02;
})
// 计算平均用到的值
const averageTotal = computed(() => {
const a01 = props.check(average01.value, 0, 58)
const a02 = props.check(average02.value, 1, 59 - a01 || 0)
return a01 + '/' + a02;
})
// 计算勾选的checkbox值合集
const checkboxString = computed(() => {
let str = checkboxList.value.join();
return str == '' ? '*' : str;
})
watch(()=>radioValue.value, ()=>{
radioChange();
})
watch(()=>cycleTotal.value, ()=>{
cycleChange();
})
watch(()=>averageTotal.value, ()=>{
averageChange();
})
watch(()=>checkboxString.value, ()=>{
checkboxChange();
})
// 单选按钮值变化时
function radioChange() {
switch (radioValue.value) {
case 1:
emits('update', 'second', '*', 'second');
break;
case 2:
emits('update', 'second', cycleTotal.value);
break;
case 3:
emits('update', 'second', averageTotal.value);
break;
case 4:
emits('update', 'second', checkboxString.value);
break;
}
}
// 周期两个值变化时
function cycleChange() {
if (radioValue.value == '2') {
emits('update', 'second', cycleTotal.value);
}
}
// 平均两个值变化时
function averageChange() {
if (radioValue.value == '3') {
emits('update', 'second', averageTotal.value);
}
}
// checkbox值变化时
function checkboxChange() {
if (radioValue.value == '4') {
emits('update', 'second', checkboxString.value);
}
}
defineExpose({
setData(key, value){
eval(key).value = value
}
})
</script>
关于CrontabResult.vue的组件,转v3时,额外注意数组排序这里就好了,
# 如果改为compare.value会出现bug的 正确为 arr.sort(this.compare) 改为 arr.sort(compare)
贴出核心Crontab.vue的代码,其子组件就不一一贴了,需要的可以自己下若依代码进行转换
<template>
<div class="crontab">
<el-tabs type="border-card">
<el-tab-pane label="秒">
<CrontabSecond
@update="updateCrontabValue"
v-model:check="checkNumber"
v-model:cron="crontabValueObj"
ref="cronsecond"
/>
</el-tab-pane>
<el-tab-pane label="分钟">
<CrontabMin
@update="updateCrontabValue"
v-model:check="checkNumber"
v-model:cron="crontabValueObj"
ref="cronmin"
/>
</el-tab-pane>
<el-tab-pane label="小时">
<CrontabHour
@update="updateCrontabValue"
v-model:check="checkNumber"
v-model:cron="crontabValueObj"
ref="cronhour"
/>
</el-tab-pane>
<el-tab-pane label="日">
<CrontabDay
@update="updateCrontabValue"
v-model:check="checkNumber"
v-model:cron="crontabValueObj"
ref="cronday"
/>
</el-tab-pane>
<el-tab-pane label="月">
<CrontabMonth
@update="updateCrontabValue"
v-model:check="checkNumber"
v-model:cron="crontabValueObj"
ref="cronmonth"
/>
</el-tab-pane>
<el-tab-pane label="周">
<CrontabWeek
@update="updateCrontabValue"
v-model:check="checkNumber"
v-model:cron="crontabValueObj"
ref="cronweek"
/>
</el-tab-pane>
<el-tab-pane label="年">
<CrontabYear
@update="updateCrontabValue"
v-model:check="checkNumber"
v-model:cron="crontabValueObj"
ref="cronyear"
/>
</el-tab-pane>
</el-tabs>
<div class="crontab-main">
<div class="crontab-main-table">
<table>
<thead>
<th v-for="item of tabTitles" width="40" :key="item">{{item}}</th>
<!-- <th>Cron 表达式</th> -->
</thead>
<tbody>
<td>
<span>{{crontabValueObj.second}}</span>
</td>
<td>
<span>{{crontabValueObj.min}}</span>
</td>
<td>
<span>{{crontabValueObj.hour}}</span>
</td>
<td>
<span>{{crontabValueObj.day}}</span>
</td>
<td>
<span>{{crontabValueObj.month}}</span>
</td>
<td>
<span>{{crontabValueObj.week}}</span>
</td>
<td>
<span>{{crontabValueObj.year}}</span>
</td>
<!-- <td>
<span>{{crontabValueString}}</span>
</td> -->
</tbody>
</table>
<table>
<thead>
<th>Cron 表达式</th>
</thead>
<tbody>
<td>
<span>{{crontabValueString}}</span>
</td>
</tbody>
</table>
</div>
<div class="crontab-main-result">
<CrontabResult v-model:ex="crontabValueString"></CrontabResult>
</div>
</div>
</div>
</template>
<script setup name="Crontab">
import CrontabSecond from "./crontab/CrontabSecond.vue";
import CrontabMin from "./crontab/CrontabMin.vue";
import CrontabHour from "./crontab/CrontabHour.vue";
import CrontabDay from "./crontab/CrontabDay.vue";
import CrontabMonth from "./crontab/CrontabMonth.vue";
import CrontabWeek from "./crontab/CrontabWeek.vue";
import CrontabYear from "./crontab/CrontabYear.vue";
import CrontabResult from "./crontab/CrontabResult.vue";
const { proxy } = getCurrentInstance();
const emits = defineEmits(["hide", "fill"]);
const props = defineProps({
expression: {type: String, default: ""}
})
const tabTitles = ref(["秒", "分钟", "小时", "日", "月", "周", "年"])
const tabActive = ref(0)
const crontabValueObj = ref({
second: "*",
min: "*",
hour: "*",
day: "*",
month: "*",
week: "?",
year: "",
})
const crontabValueString = computed(() => {
let obj = crontabValueObj.value;
let str =
obj.second +
" " +
obj.min +
" " +
obj.hour +
" " +
obj.day +
" " +
obj.month +
" " +
obj.week +
(obj.year == "" ? "" : " " + obj.year);
return str;
})
onMounted(() => {
resolveExp();
})
watch(() => props.expression, (v) => {
resolveExp();
});
function resolveExp() {
// 反解析 表达式
if (props.expression) {
let arr = props.expression.split(" ");
if (arr.length >= 6) {
//6 位以上是合法表达式
let obj = {
second: arr[0],
min: arr[1],
hour: arr[2],
day: arr[3],
month: arr[4],
week: arr[5],
year: arr[6] ? arr[6] : "",
};
crontabValueObj.value = {
...obj,
};
for (let i in obj) {
if (obj[i]) changeRadio(i, obj[i]);
}
}
} else {
// 没有传入的表达式 则还原
clearCron();
}
}
// tab切换值
function tabCheck(index) {
tabActive.value = index;
}
// 由子组件触发,更改表达式组成的字段值
function updateCrontabValue(name, value, from) {
// "updateCrontabValue", name, value, from;
crontabValueObj.value[name] = value;
if (from && from !== name) {
console.log(`来自组件 ${from} 改变了 ${name} ${value}`);
changeRadio(name, value);
}
}
// 赋值到组件
function changeRadio(name, value) {
let arr = ["second", "min", "hour", "month"]
let refName = "cron" + name
let insValue;
if (!proxy.$refs[refName]) return;
if (arr.includes(name)) {
if (value === "*") {
insValue = 1;
} else if (value.indexOf("-") > -1) {
let indexArr = value.split("-");
isNaN(indexArr[0])
? (proxy.$refs[refName].setData("cycle01", 0))
: (proxy.$refs[refName].setData("cycle01", Number(indexArr[0])));
proxy.$refs[refName].setData("cycle02", Number(indexArr[1]));
insValue = 2;
} else if (value.indexOf("/") > -1) {
let indexArr = value.split("/");
isNaN(indexArr[0])
? (proxy.$refs[refName].setData("average01", 0))
: (proxy.$refs[refName].setData("average01", Number(indexArr[0])));
proxy.$refs[refName].setData("average02", Number(indexArr[1]));
insValue = 3;
} else {
insValue = 4;
let list = value.split(",");
for(let item of list){
item = String(item)
}
proxy.$refs[refName].setData("checkboxList", list);
}
} else if (name == "day") {
if (value === "*") {
insValue = 1;
} else if (value == "?") {
insValue = 2;
} else if (value.indexOf("-") > -1) {
let indexArr = value.split("-");
isNaN(indexArr[0])
? (proxy.$refs[refName].setData("cycle01", 0))
: (proxy.$refs[refName].setData("cycle01", Number(indexArr[0])));
proxy.$refs[refName].setData("cycle02", Number(indexArr[1]));
insValue = 3;
} else if (value.indexOf("/") > -1) {
let indexArr = value.split("/");
isNaN(indexArr[0])
? (proxy.$refs[refName].setData("average01", 0))
: (proxy.$refs[refName].setData("average01", Number(indexArr[0])));
proxy.$refs[refName].setData("average02", Number(indexArr[1]));
insValue = 4;
} else if (value.indexOf("W") > -1) {
let indexArr = value.split("W");
isNaN(indexArr[0])
? (proxy.$refs[refName].setData("workday", 0))
: (proxy.$refs[refName].setData("workday", Number(indexArr[0])));
insValue = 5;
} else if (value === "L") {
insValue = 6;
} else {
let list = value.split(",");
for(let item of list){
item = String(item)
}
proxy.$refs[refName].setData("checkboxList", list);
insValue = 7;
}
} else if (name == "week") {
if (value === "*") {
insValue = 1;
} else if (value == "?") {
insValue = 2;
} else if (value.indexOf("-") > -1) {
let indexArr = value.split("-");
isNaN(indexArr[0])
? (proxy.$refs[refName].setData("cycle01", "0"))
: (proxy.$refs[refName].setData("cycle01", String(indexArr[0])));
proxy.$refs[refName].setData("cycle02", String(indexArr[1]));
insValue = 3;
} else if (value.indexOf("#") > -1) {
let indexArr = value.split("#");
isNaN(indexArr[0])
? (proxy.$refs[refName].setData("average01", 1))
: (proxy.$refs[refName].setData("average01", Number(indexArr[0])));
proxy.$refs[refName].setData("average02", String(indexArr[1]));
insValue = 4;
} else if (value.indexOf("L") > -1) {
let indexArr = value.split("L");
isNaN(indexArr[0])
? (proxy.$refs[refName].setData("weekday", "1"))
: (proxy.$refs[refName].setData("weekday", String(indexArr[0])));
insValue = 5;
} else {
let list = value.split(",");
for(let item of list){
item = String(item)
}
proxy.$refs[refName].setData("checkboxList", list);
insValue = 6;
}
} else if (name == "year") {
if (value == "") {
insValue = 1;
} else if (value == "*") {
insValue = 2;
} else if (value.indexOf("-") > -1) {
insValue = 3;
} else if (value.indexOf("/") > -1) {
insValue = 4;
} else {
let list = value.split(",");
for(let item of list){
item = String(item)
}
proxy.$refs[refName].setData("checkboxList", list);
insValue = 5;
}
}
proxy.$refs[refName].setData("radioValue", insValue);
}
// 表单选项的子组件校验数字格式(通过-props传递)
function checkNumber(value, minLimit, maxLimit) {
// 检查必须为整数
value = Math.floor(Number(value));
if (value < minLimit) {
value = minLimit;
} else if (value > maxLimit) {
value = maxLimit;
}
return value;
}
// 隐藏弹窗
function hidePopup() {
emits("hide");
}
// 填充表达式
function submitFill() {
emits("fill", crontabValueString);
hidePopup();
}
function clearCron() {
// 还原选择项
("准备还原");
crontabValueObj.value = {
second: "*",
min: "*",
hour: "*",
day: "*",
month: "*",
week: "?",
year: "",
};
for (let j in crontabValueObj.value) {
changeRadio(j, crontabValueObj.value[j]);
}
}
defineExpose({
submitFill, clearCron
})
</script>
<style scoped>
.crontab{
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
}
.crontab-main {
flex: 1;
width: 100%;
margin: 10px auto;
background: #fff;
border-radius: 5px;
font-size: 12px;
border: 1px solid #ccc;
box-sizing: border-box;
line-height: 24px;
padding: 5px 10px 5px;
display: flex;
justify-content: space-between;
overflow-y: auto;
}
.crontab-main-table {
box-sizing: border-box;
line-height: 24px;
padding: 5px 10px 5px;
width: 50%;
display: flex;
flex-direction: column;
justify-content: space-around;
table {
text-align: center;
width: 100%;
margin: 0;
span {
display: block;
width: 100%;
font-family: arial;
line-height: 30px;
height: 30px;
white-space: nowrap;
overflow: hidden;
border: 1px solid #e8e8e8;
}
}
}
.crontab-main-result {
box-sizing: border-box;
padding: 5px 10px 5px;
background-color: #f1f1f1;
background-size: cover;
width: 48%;
display: flex;
flex-direction: column;
.crontab-result-title{
padding: 5px;
}
:deep(.crontab-result-scroll) {
font-size: 12px;
line-height: 24px;
margin: 0 !important;
padding-left: 80px;
}
}
.crontab-footer {
text-align: right;
height: 25px;
padding: 5px 20px;
}
</style>
将原来组件的按钮移到引用层,引用样例
<el-dialog title="Cron表达式生成器" v-model="formCrontabOpen" append-to-body destroy-on-close class="nine-tanchuang-001">
<!-- <crontab @change="cronChange" v-model:value="formData.cronExpression" /> -->
<Crontab ref="crontabRef" @hide="formCrontabOpen=false" @fill="crontabFill" v-model:expression="formData.cronExpression"></Crontab>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="formCrontSubmit">确 定</el-button>
<el-button type="warning" @click="formCrontReset">重 置</el-button>
<el-button @click="formCrontabOpen=false">取 消</el-button>
</div>
</template>
</el-dialog>
对应引用位置的调用方法
/** 确定访问子组件方法 */ function formCrontSubmit(){ proxy.$refs.crontabRef.submitFill(); } /** 重置访问子组件方法 */ function formCrontReset(){ proxy.$refs.crontabRef.clearCron(); } /** 子组件确认回调方法 */ function crontabFill(value) { formData.value.cronExpression = value; }
作者名称:Vrapile
联系方式:发送邮件Vrapile@163.com,另博客账号也是本人微信号。
版权声明:此文是博主业余爱好所写,文章或有错误与不足之处,欢迎留言指正建议、共同探讨!另此文为博主原创,未经博主同意不得转载,否则保留追究法律责任的权利。

浙公网安备 33010602011771号