72 changed files with 3042 additions and 3973 deletions
@ -1,4 +0,0 @@
|
||||
export { default as CronTab } from './src/CronTabInput.vue' |
||||
export { default as CronTabInner } from './src/CronTabInner.vue' |
||||
export { default as CronTabModal } from './src/CronTabModal.vue' |
||||
export { default as CronValidator } from './src/validator' |
@ -1,360 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { computed, provide, reactive, ref, watch } from 'vue' |
||||
import { Col, Divider, Input, Row, TabPane, Tabs, Textarea, Tooltip } from 'ant-design-vue' |
||||
import CronParser from 'cron-parser' |
||||
import SecondUI from './tabs/SecondUI.vue' |
||||
import MinuteUI from './tabs/MinuteUI.vue' |
||||
import HourUI from './tabs/HourUI.vue' |
||||
import DayUI from './tabs/DayUI.vue' |
||||
import MonthUI from './tabs/MonthUI.vue' |
||||
import WeekUI from './tabs/WeekUI.vue' |
||||
import YearUI from './tabs/YearUI.vue' |
||||
import { cronEmits, cronProps } from './cron.data' |
||||
import { useDesign } from '@/hooks/web/useDesign' |
||||
import { dateFormat } from '@/utils/dateUtil' |
||||
import { simpleDebounce } from '@/utils' |
||||
|
||||
const props = defineProps({ ...cronProps }) |
||||
const emit = defineEmits([...cronEmits]) |
||||
const { prefixCls } = useDesign('cron-inner') |
||||
provide('prefixCls', prefixCls) |
||||
const activeKey = ref(props.hideSecond ? 'minute' : 'second') |
||||
const second = ref('*') |
||||
const minute = ref('*') |
||||
const hour = ref('*') |
||||
const day = ref('*') |
||||
const month = ref('*') |
||||
const week = ref('?') |
||||
const year = ref('*') |
||||
const inputValues = reactive({ |
||||
second: '', |
||||
minute: '', |
||||
hour: '', |
||||
day: '', |
||||
month: '', |
||||
week: '', |
||||
year: '', |
||||
cron: '', |
||||
}) |
||||
const preTimeList = ref('执行预览,会忽略年份参数。') |
||||
|
||||
// cron表达式 |
||||
const cronValueInner = computed(() => { |
||||
const result: string[] = [] |
||||
if (!props.hideSecond) |
||||
result.push(second.value ? second.value : '*') |
||||
|
||||
result.push(minute.value ? minute.value : '*') |
||||
result.push(hour.value ? hour.value : '*') |
||||
result.push(day.value ? day.value : '*') |
||||
result.push(month.value ? month.value : '*') |
||||
result.push(week.value ? week.value : '?') |
||||
if (!props.hideYear && !props.hideSecond) |
||||
result.push(year.value ? year.value : '*') |
||||
return result.join(' ') |
||||
}) |
||||
// 不含年 |
||||
const cronValueNoYear = computed(() => { |
||||
const v = cronValueInner.value |
||||
if (props.hideYear || props.hideSecond) |
||||
return v |
||||
const vs = v.split(' ') |
||||
if (vs.length >= 6) { |
||||
// 转成 Quartz 的规则 |
||||
vs[5] = convertWeekToQuartz(vs[5]) |
||||
} |
||||
return vs.slice(0, vs.length - 1).join(' ') |
||||
}) |
||||
const calTriggerList = simpleDebounce(calTriggerListInner, 500) |
||||
|
||||
watch( |
||||
() => props.value, |
||||
(newVal) => { |
||||
if (newVal === cronValueInner.value) |
||||
return |
||||
|
||||
formatValue() |
||||
}, |
||||
) |
||||
|
||||
watch(cronValueInner, (newValue) => { |
||||
calTriggerList() |
||||
emitValue(newValue) |
||||
assignInput() |
||||
}) |
||||
|
||||
assignInput() |
||||
formatValue() |
||||
calTriggerListInner() |
||||
|
||||
function assignInput() { |
||||
inputValues.second = second.value |
||||
inputValues.minute = minute.value |
||||
inputValues.hour = hour.value |
||||
inputValues.day = day.value |
||||
inputValues.month = month.value |
||||
inputValues.week = week.value |
||||
inputValues.year = year.value |
||||
inputValues.cron = cronValueInner.value |
||||
} |
||||
|
||||
function formatValue() { |
||||
if (!props.value) |
||||
return |
||||
const values = props.value.split(' ').filter(item => !!item) |
||||
if (!values || values.length <= 0) |
||||
return |
||||
let i = 0 |
||||
if (!props.hideSecond) |
||||
second.value = values[i++] |
||||
if (values.length > i) |
||||
minute.value = values[i++] |
||||
if (values.length > i) |
||||
hour.value = values[i++] |
||||
if (values.length > i) |
||||
day.value = values[i++] |
||||
if (values.length > i) |
||||
month.value = values[i++] |
||||
if (values.length > i) |
||||
week.value = values[i++] |
||||
if (values.length > i) |
||||
year.value = values[i] |
||||
assignInput() |
||||
} |
||||
|
||||
// Quartz 的规则: |
||||
// 1 = 周日,2 = 周一,3 = 周二,4 = 周三,5 = 周四,6 = 周五,7 = 周六 |
||||
function convertWeekToQuartz(week: string) { |
||||
const convert = (v: string) => { |
||||
if (v === '0') |
||||
return '1' |
||||
|
||||
if (v === '1') |
||||
return '0' |
||||
|
||||
return (Number.parseInt(v) - 1).toString() |
||||
} |
||||
// 匹配示例 1-7 or 1/7 |
||||
const patten1 = /^([0-7])([-/])([0-7])$/ |
||||
// 匹配示例 1,4,7 |
||||
const patten2 = /^([0-7])(,[0-7])+$/ |
||||
if (/^[0-7]$/.test(week)) { |
||||
return convert(week) |
||||
} |
||||
else if (patten1.test(week)) { |
||||
return week.replace(patten1, (_$0, before, separator, after) => { |
||||
if (separator === '/') |
||||
return convert(before) + separator + after |
||||
else |
||||
return convert(before) + separator + convert(after) |
||||
}) |
||||
} |
||||
else if (patten2.test(week)) { |
||||
return week |
||||
.split(',') |
||||
.map(v => convert(v)) |
||||
.join(',') |
||||
} |
||||
return week |
||||
} |
||||
|
||||
function calTriggerListInner() { |
||||
// 设置了回调函数 |
||||
if (props.remote) { |
||||
props.remote(cronValueInner.value, +new Date(), (v) => { |
||||
preTimeList.value = v |
||||
}) |
||||
return |
||||
} |
||||
const format = 'yyyy-MM-dd hh:mm:ss' |
||||
const options = { |
||||
currentDate: dateFormat(new Date(), format), |
||||
} |
||||
const iter = CronParser.parseExpression(cronValueNoYear.value, options) |
||||
const result: string[] = [] |
||||
for (let i = 1; i <= 10; i++) |
||||
result.push(dateFormat(new Date(iter.next() as any), format)) |
||||
|
||||
preTimeList.value = result.length > 0 ? result.join('\n') : '无执行时间' |
||||
} |
||||
|
||||
function onInputBlur() { |
||||
second.value = inputValues.second |
||||
minute.value = inputValues.minute |
||||
hour.value = inputValues.hour |
||||
day.value = inputValues.day |
||||
month.value = inputValues.month |
||||
week.value = inputValues.week |
||||
year.value = inputValues.year |
||||
} |
||||
|
||||
function onInputCronBlur(event) { |
||||
emitValue(event.target.value) |
||||
} |
||||
|
||||
function emitValue(value) { |
||||
emit('change', value) |
||||
emit('update:value', value) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div :class="`${prefixCls}`"> |
||||
<div class="content"> |
||||
<Tabs v-model:activeKey="activeKey" size="small"> |
||||
<TabPane v-if="!hideSecond" key="second" tab="秒"> |
||||
<SecondUI v-model:value="second" :disabled="disabled" /> |
||||
</TabPane> |
||||
<TabPane key="minute" tab="分"> |
||||
<MinuteUI v-model:value="minute" :disabled="disabled" /> |
||||
</TabPane> |
||||
<TabPane key="hour" tab="时"> |
||||
<HourUI v-model:value="hour" :disabled="disabled" /> |
||||
</TabPane> |
||||
<TabPane key="day" tab="日"> |
||||
<DayUI v-model:value="day" :week="week" :disabled="disabled" /> |
||||
</TabPane> |
||||
<TabPane key="month" tab="月"> |
||||
<MonthUI v-model:value="month" :disabled="disabled" /> |
||||
</TabPane> |
||||
<TabPane key="week" tab="周"> |
||||
<WeekUI v-model:value="week" :day="day" :disabled="disabled" /> |
||||
</TabPane> |
||||
<TabPane v-if="!hideYear && !hideSecond" key="year" tab="年"> |
||||
<YearUI v-model:value="year" :disabled="disabled" /> |
||||
</TabPane> |
||||
</Tabs> |
||||
<Divider /> |
||||
<!-- 执行时间预览 --> |
||||
<Row :gutter="8"> |
||||
<Col :span="18" style="margin-top: 22px"> |
||||
<Row :gutter="8"> |
||||
<Col :span="8" style="margin-bottom: 12px"> |
||||
<Input v-model:value="inputValues.second" @blur="onInputBlur"> |
||||
<template #addonBefore> |
||||
<span class="allow-click" @click="activeKey = 'second'">秒</span> |
||||
</template> |
||||
</Input> |
||||
</Col> |
||||
<Col :span="8" style="margin-bottom: 12px"> |
||||
<Input v-model:value="inputValues.minute" @blur="onInputBlur"> |
||||
<template #addonBefore> |
||||
<span class="allow-click" @click="activeKey = 'minute'">分</span> |
||||
</template> |
||||
</Input> |
||||
</Col> |
||||
<Col :span="8" style="margin-bottom: 12px"> |
||||
<Input v-model:value="inputValues.hour" @blur="onInputBlur"> |
||||
<template #addonBefore> |
||||
<span class="allow-click" @click="activeKey = 'hour'">时</span> |
||||
</template> |
||||
</Input> |
||||
</Col> |
||||
<Col :span="8" style="margin-bottom: 12px"> |
||||
<Input v-model:value="inputValues.day" @blur="onInputBlur"> |
||||
<template #addonBefore> |
||||
<span class="allow-click" @click="activeKey = 'day'">日</span> |
||||
</template> |
||||
</Input> |
||||
</Col> |
||||
<Col :span="8" style="margin-bottom: 12px"> |
||||
<Input v-model:value="inputValues.month" @blur="onInputBlur"> |
||||
<template #addonBefore> |
||||
<span class="allow-click" @click="activeKey = 'month'">月</span> |
||||
</template> |
||||
</Input> |
||||
</Col> |
||||
<Col :span="8" style="margin-bottom: 12px"> |
||||
<Input v-model:value="inputValues.week" @blur="onInputBlur"> |
||||
<template #addonBefore> |
||||
<span class="allow-click" @click="activeKey = 'week'">周</span> |
||||
</template> |
||||
</Input> |
||||
</Col> |
||||
<Col :span="8"> |
||||
<Input v-model:value="inputValues.year" @blur="onInputBlur"> |
||||
<template #addonBefore> |
||||
<span class="allow-click" @click="activeKey = 'year'">年</span> |
||||
</template> |
||||
</Input> |
||||
</Col> |
||||
<Col :span="16"> |
||||
<Input v-model:value="inputValues.cron" @blur="onInputCronBlur"> |
||||
<template #addonBefore> |
||||
<Tooltip title="Cron表达式"> |
||||
式 |
||||
</Tooltip> |
||||
</template> |
||||
</Input> |
||||
</Col> |
||||
</Row> |
||||
</Col> |
||||
<Col :span="6"> |
||||
<div>近十次执行时间(不含年)</div> |
||||
<Textarea :value="preTimeList" :rows="5" /> |
||||
</Col> |
||||
</Row> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="less"> |
||||
@prefix-cls: ~'@{namespace}-cron-inner'; |
||||
|
||||
.@{prefix-cls} { |
||||
.content { |
||||
.ant-checkbox-wrapper + .ant-checkbox-wrapper { |
||||
margin-left: 0; |
||||
} |
||||
} |
||||
|
||||
&-config-list { |
||||
margin: 0 10px 10px; |
||||
text-align: left; |
||||
|
||||
.item { |
||||
margin-top: 5px; |
||||
font-size: 14px; |
||||
|
||||
span { |
||||
padding: 0 2px; |
||||
} |
||||
} |
||||
|
||||
.choice { |
||||
padding: 5px 8px; |
||||
} |
||||
|
||||
.w60 { |
||||
width: 60px; |
||||
min-width: 60px; |
||||
} |
||||
|
||||
.w80 { |
||||
width: 80px; |
||||
min-width: 80px; |
||||
} |
||||
|
||||
.list { |
||||
margin: 0 20px; |
||||
} |
||||
|
||||
.list-check-item { |
||||
width: 4em; |
||||
padding: 1px 3px; |
||||
} |
||||
|
||||
.list-cn .list-check-item { |
||||
width: 5em; |
||||
} |
||||
|
||||
.tip-info { |
||||
color: #999; |
||||
} |
||||
} |
||||
|
||||
.allow-click { |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
</style> |
@ -1,55 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import { ref, watch } from 'vue' |
||||
import { Input } from 'ant-design-vue' |
||||
import CronTabModal from './CronTabModal.vue' |
||||
import { cronEmits, cronProps } from './cron.data' |
||||
import { useModal } from '@/components/Modal' |
||||
import { propTypes } from '@/utils/propTypes' |
||||
|
||||
const props = defineProps({ |
||||
...cronProps, |
||||
placeholder: propTypes.string.def('请输入cron表达式'), |
||||
exeStartTime: propTypes.oneOfType([propTypes.number, propTypes.string, propTypes.object]).def(0), |
||||
}) |
||||
const emit = defineEmits([...cronEmits]) |
||||
const [registerModal, { openModal }] = useModal() |
||||
const editCronValue = ref(props.value) |
||||
|
||||
watch( |
||||
() => props.value, |
||||
(newVal) => { |
||||
if (newVal !== editCronValue.value) |
||||
editCronValue.value = newVal |
||||
}, |
||||
) |
||||
watch(editCronValue, (newVal) => { |
||||
emit('change', newVal) |
||||
emit('update:value', newVal) |
||||
}) |
||||
|
||||
function showConfigModal() { |
||||
if (!props.disabled) |
||||
openModal() |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div> |
||||
<Input v-model:value="editCronValue" :placeholder="placeholder" :disabled="disabled"> |
||||
<template #addonAfter> |
||||
<a class="cursor-pointer" :disabled="disabled ? 'disabled' : null" @click="showConfigModal"> |
||||
<span class="i-ant-design:setting-outlined relative right-0.5 top-0.25" /> |
||||
<span>选择</span> |
||||
</a> |
||||
</template> |
||||
</Input> |
||||
<CronTabModal |
||||
v-model:value="editCronValue" |
||||
:exe-start-time="exeStartTime" |
||||
:hide-year="hideYear" |
||||
:remote="remote" |
||||
:hide-second="hideSecond" |
||||
@register="registerModal" |
||||
/> |
||||
</div> |
||||
</template> |
@ -1,18 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
import CronTab from './CronTabInner.vue' |
||||
import { BasicModal, useModalInner } from '@/components/Modal' |
||||
|
||||
defineOptions({ name: 'CronTabModal', inheritAttrs: false }) |
||||
|
||||
const [registerModal, { closeModal }] = useModalInner() |
||||
|
||||
function onOk() { |
||||
closeModal() |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<BasicModal title="Cron表达式" width="800px" @register="registerModal" @ok="onOk"> |
||||
<CronTab v-bind="$attrs" /> |
||||
</BasicModal> |
||||
</template> |
@ -1,10 +0,0 @@
|
||||
import { propTypes } from '@/utils/propTypes' |
||||
|
||||
export const cronEmits = ['change', 'update:value'] |
||||
export const cronProps = { |
||||
value: propTypes.string.def(''), |
||||
disabled: propTypes.bool.def(false), |
||||
hideSecond: propTypes.bool.def(false), |
||||
hideYear: propTypes.bool.def(false), |
||||
remote: propTypes.func, |
||||
} |
@ -1,101 +0,0 @@
|
||||
<script lang="ts"> |
||||
import { computed, defineComponent, watch } from 'vue' |
||||
import { Checkbox, Input, Radio } from 'ant-design-vue' |
||||
import { TypeEnum, useTabEmits, useTabProps, useTabSetup } from './useTabMixin' |
||||
|
||||
export default defineComponent({ |
||||
name: 'DayUI', |
||||
components: { AInput: Input, Checkbox, CheckboxGroup: Checkbox.Group, Radio, RadioGroup: Radio.Group }, |
||||
props: useTabProps({ |
||||
defaultValue: '*', |
||||
props: { |
||||
week: { type: String, default: '?' }, |
||||
}, |
||||
}), |
||||
emits: useTabEmits(), |
||||
setup(props, context) { |
||||
const disabledChoice = computed(() => { |
||||
return (props.week && props.week !== '?') || props.disabled |
||||
}) |
||||
const setup = useTabSetup(props, context, { |
||||
defaultValue: '*', |
||||
valueWork: 1, |
||||
minValue: 1, |
||||
maxValue: 31, |
||||
valueRange: { start: 1, end: 31 }, |
||||
valueLoop: { start: 1, interval: 1 }, |
||||
disabled: disabledChoice, |
||||
}) |
||||
const typeWorkAttrs = computed(() => ({ |
||||
disabled: setup.type.value !== TypeEnum.work || props.disabled || disabledChoice.value, |
||||
...setup.inputNumberAttrs.value, |
||||
})) |
||||
|
||||
watch( |
||||
() => props.week, |
||||
() => { |
||||
setup.updateValue(disabledChoice.value ? '?' : setup.computeValue.value) |
||||
}, |
||||
) |
||||
|
||||
return { ...setup, typeWorkAttrs } |
||||
}, |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div :class="`${prefixCls}-config-list`"> |
||||
<RadioGroup v-model:value="type"> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.unset" v-bind="beforeRadioAttrs"> |
||||
不设置 |
||||
</Radio> |
||||
<span class="tip-info">日和周只能设置其中之一</span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.every" v-bind="beforeRadioAttrs"> |
||||
每日 |
||||
</Radio> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs"> |
||||
区间 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<AInput v-model:value="valueRange.start" type="number" v-bind="typeRangeAttrs" /> |
||||
<span> 日 至 </span> |
||||
<AInput v-model:value="valueRange.end" type="number" v-bind="typeRangeAttrs" /> |
||||
<span> 日 </span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs"> |
||||
循环 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<AInput v-model:value="valueLoop.start" type="number" class="w-4" v-bind="typeLoopAttrs" /> |
||||
<span> 日开始,间隔 </span> |
||||
<AInput v-model:value="valueLoop.interval" type="number" v-bind="typeLoopAttrs" /> |
||||
<span> 日 </span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.last" v-bind="beforeRadioAttrs"> |
||||
最后一日 |
||||
</Radio> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs"> |
||||
指定 |
||||
</Radio> |
||||
<div class="list"> |
||||
<CheckboxGroup v-model:value="valueList"> |
||||
<template v-for="i in specifyRange" :key="i"> |
||||
<Checkbox :value="i" v-bind="typeSpecifyAttrs"> |
||||
{{ i }} |
||||
</Checkbox> |
||||
</template> |
||||
</CheckboxGroup> |
||||
</div> |
||||
</div> |
||||
</RadioGroup> |
||||
</div> |
||||
</template> |
@ -1,69 +0,0 @@
|
||||
<script lang="ts"> |
||||
import { defineComponent } from 'vue' |
||||
import { Checkbox, Input, Radio } from 'ant-design-vue' |
||||
import { useTabEmits, useTabProps, useTabSetup } from './useTabMixin' |
||||
|
||||
export default defineComponent({ |
||||
name: 'HourUI', |
||||
components: { AInput: Input, Checkbox, CheckboxGroup: Checkbox.Group, Radio, RadioGroup: Radio.Group }, |
||||
props: useTabProps({ |
||||
defaultValue: '*', |
||||
}), |
||||
emits: useTabEmits(), |
||||
setup(props, context) { |
||||
return useTabSetup(props, context, { |
||||
defaultValue: '*', |
||||
minValue: 0, |
||||
maxValue: 23, |
||||
valueRange: { start: 0, end: 23 }, |
||||
valueLoop: { start: 0, interval: 1 }, |
||||
}) |
||||
}, |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div :class="`${prefixCls}-config-list`"> |
||||
<RadioGroup v-model:value="type"> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.every" v-bind="beforeRadioAttrs"> |
||||
每时 |
||||
</Radio> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs"> |
||||
区间 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<AInput v-model:value="valueRange.start" type="number" v-bind="typeRangeAttrs" /> |
||||
<span> 时 至 </span> |
||||
<AInput v-model:value="valueRange.end" type="number" v-bind="typeRangeAttrs" /> |
||||
<span> 时 </span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs"> |
||||
循环 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<AInput v-model:value="valueLoop.start" type="number" v-bind="typeLoopAttrs" /> |
||||
<span> 时开始,间隔 </span> |
||||
<AInput v-model:value="valueLoop.interval" type="number" v-bind="typeLoopAttrs" /> |
||||
<span> 时 </span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs"> |
||||
指定 |
||||
</Radio> |
||||
<div class="list"> |
||||
<CheckboxGroup v-model:value="valueList"> |
||||
<template v-for="i in specifyRange" :key="i"> |
||||
<Checkbox :value="i" v-bind="typeSpecifyAttrs"> |
||||
{{ i }} |
||||
</Checkbox> |
||||
</template> |
||||
</CheckboxGroup> |
||||
</div> |
||||
</div> |
||||
</RadioGroup> |
||||
</div> |
||||
</template> |
@ -1,69 +0,0 @@
|
||||
<script lang="ts"> |
||||
import { defineComponent } from 'vue' |
||||
import { Checkbox, Input, Radio } from 'ant-design-vue' |
||||
import { useTabEmits, useTabProps, useTabSetup } from './useTabMixin' |
||||
|
||||
export default defineComponent({ |
||||
name: 'MinuteUI', |
||||
components: { AInput: Input, Checkbox, CheckboxGroup: Checkbox.Group, Radio, RadioGroup: Radio.Group }, |
||||
props: useTabProps({ |
||||
defaultValue: '*', |
||||
}), |
||||
emits: useTabEmits(), |
||||
setup(props, context) { |
||||
return useTabSetup(props, context, { |
||||
defaultValue: '*', |
||||
minValue: 0, |
||||
maxValue: 59, |
||||
valueRange: { start: 0, end: 59 }, |
||||
valueLoop: { start: 0, interval: 1 }, |
||||
}) |
||||
}, |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div :class="`${prefixCls}-config-list`"> |
||||
<RadioGroup v-model:value="type"> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.every" v-bind="beforeRadioAttrs"> |
||||
每分 |
||||
</Radio> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs"> |
||||
区间 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<AInput v-model:value="valueRange.start" type="number" v-bind="typeRangeAttrs" /> |
||||
<span> 分 至 </span> |
||||
<AInput v-model:value="valueRange.end" type="number" v-bind="typeRangeAttrs" /> |
||||
<span> 分 </span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs"> |
||||
循环 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<AInput v-model:value="valueLoop.start" type="number" v-bind="typeLoopAttrs" /> |
||||
<span> 分开始,间隔 </span> |
||||
<AInput v-model:value="valueLoop.interval" type="number" v-bind="typeLoopAttrs" /> |
||||
<span> 分 </span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs"> |
||||
指定 |
||||
</Radio> |
||||
<div class="list"> |
||||
<CheckboxGroup v-model:value="valueList"> |
||||
<template v-for="i in specifyRange" :key="i"> |
||||
<Checkbox :value="i" v-bind="typeSpecifyAttrs"> |
||||
{{ i }} |
||||
</Checkbox> |
||||
</template> |
||||
</CheckboxGroup> |
||||
</div> |
||||
</div> |
||||
</RadioGroup> |
||||
</div> |
||||
</template> |
@ -1,69 +0,0 @@
|
||||
<script lang="ts"> |
||||
import { defineComponent } from 'vue' |
||||
import { Checkbox, Input, Radio } from 'ant-design-vue' |
||||
import { useTabEmits, useTabProps, useTabSetup } from './useTabMixin' |
||||
|
||||
export default defineComponent({ |
||||
name: 'MonthUI', |
||||
components: { AInput: Input, Checkbox, CheckboxGroup: Checkbox.Group, Radio, RadioGroup: Radio.Group }, |
||||
props: useTabProps({ |
||||
defaultValue: '*', |
||||
}), |
||||
emits: useTabEmits(), |
||||
setup(props, context) { |
||||
return useTabSetup(props, context, { |
||||
defaultValue: '*', |
||||
minValue: 1, |
||||
maxValue: 12, |
||||
valueRange: { start: 1, end: 12 }, |
||||
valueLoop: { start: 1, interval: 1 }, |
||||
}) |
||||
}, |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div :class="`${prefixCls}-config-list`"> |
||||
<RadioGroup v-model:value="type"> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.every" v-bind="beforeRadioAttrs"> |
||||
每月 |
||||
</Radio> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs"> |
||||
区间 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<AInput v-model:value="valueRange.start" type="number" v-bind="typeRangeAttrs" /> |
||||
<span> 月 至 </span> |
||||
<AInput v-model:value="valueRange.end" type="number" v-bind="typeRangeAttrs" /> |
||||
<span> 月 </span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs"> |
||||
循环 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<AInput v-model:value="valueLoop.start" type="number" v-bind="typeLoopAttrs" /> |
||||
<span> 月开始,间隔 </span> |
||||
<AInput v-model:value="valueLoop.interval" type="number" v-bind="typeLoopAttrs" /> |
||||
<span> 月 </span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs"> |
||||
指定 |
||||
</Radio> |
||||
<div class="list"> |
||||
<CheckboxGroup v-model:value="valueList"> |
||||
<template v-for="i in specifyRange" :key="i"> |
||||
<Checkbox :value="i" v-bind="typeSpecifyAttrs"> |
||||
{{ i }} |
||||
</Checkbox> |
||||
</template> |
||||
</CheckboxGroup> |
||||
</div> |
||||
</div> |
||||
</RadioGroup> |
||||
</div> |
||||
</template> |
@ -1,69 +0,0 @@
|
||||
<script lang="ts"> |
||||
import { defineComponent } from 'vue' |
||||
import { Checkbox, Input, Radio } from 'ant-design-vue' |
||||
import { useTabEmits, useTabProps, useTabSetup } from './useTabMixin' |
||||
|
||||
export default defineComponent({ |
||||
name: 'SecondUI', |
||||
components: { AInput: Input, Checkbox, CheckboxGroup: Checkbox.Group, Radio, RadioGroup: Radio.Group }, |
||||
props: useTabProps({ |
||||
defaultValue: '*', |
||||
}), |
||||
emits: useTabEmits(), |
||||
setup(props, context) { |
||||
return useTabSetup(props, context, { |
||||
defaultValue: '*', |
||||
minValue: 0, |
||||
maxValue: 59, |
||||
valueRange: { start: 0, end: 59 }, |
||||
valueLoop: { start: 0, interval: 1 }, |
||||
}) |
||||
}, |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div :class="`${prefixCls}-config-list`"> |
||||
<RadioGroup v-model:value="type"> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.every" v-bind="beforeRadioAttrs"> |
||||
每秒 |
||||
</Radio> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs"> |
||||
区间 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<AInput v-model:value="valueRange.start" type="number" v-bind="typeRangeAttrs" /> |
||||
<span> 秒 至 </span> |
||||
<AInput v-model:value="valueRange.end" type="number" v-bind="typeRangeAttrs" /> |
||||
<span> 秒 </span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs"> |
||||
循环 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<AInput v-model:value="valueLoop.start" type="number" v-bind="typeLoopAttrs" /> |
||||
<span> 秒开始,间隔 </span> |
||||
<AInput v-model:value="valueLoop.interval" type="number" v-bind="typeLoopAttrs" /> |
||||
<span> 秒 </span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs"> |
||||
指定 |
||||
</Radio> |
||||
<div class="list"> |
||||
<CheckboxGroup v-model:value="valueList"> |
||||
<template v-for="i in specifyRange" :key="i"> |
||||
<Checkbox :value="i" v-bind="typeSpecifyAttrs"> |
||||
{{ i }} |
||||
</Checkbox> |
||||
</template> |
||||
</CheckboxGroup> |
||||
</div> |
||||
</div> |
||||
</RadioGroup> |
||||
</div> |
||||
</template> |
@ -1,135 +0,0 @@
|
||||
<script lang="ts"> |
||||
import { computed, defineComponent, watch } from 'vue' |
||||
import { Checkbox, Input, Radio, Select } from 'ant-design-vue' |
||||
import { TypeEnum, useTabEmits, useTabProps, useTabSetup } from './useTabMixin' |
||||
|
||||
const WEEK_MAP_EN = { |
||||
1: 'SUN', |
||||
2: 'MON', |
||||
3: 'TUE', |
||||
4: 'WED', |
||||
5: 'THU', |
||||
6: 'FRI', |
||||
7: 'SAT', |
||||
} |
||||
|
||||
const WEEK_MAP_CN = { |
||||
1: '周日', |
||||
2: '周一', |
||||
3: '周二', |
||||
4: '周三', |
||||
5: '周四', |
||||
6: '周五', |
||||
7: '周六', |
||||
} |
||||
|
||||
export default defineComponent({ |
||||
name: 'WeekUI', |
||||
components: { AInput: Input, ASelect: Select, Checkbox, CheckboxGroup: Checkbox.Group, Radio, RadioGroup: Radio.Group }, |
||||
props: useTabProps({ |
||||
defaultValue: '?', |
||||
props: { |
||||
day: { type: String, default: '*' }, |
||||
}, |
||||
}), |
||||
emits: useTabEmits(), |
||||
setup(props, context) { |
||||
const disabledChoice = computed(() => { |
||||
return (props.day && props.day !== '?') || props.disabled |
||||
}) |
||||
const setup = useTabSetup(props, context, { |
||||
defaultType: TypeEnum.unset, |
||||
defaultValue: '?', |
||||
minValue: 1, |
||||
maxValue: 7, |
||||
// 0,7表示周日 1表示周一 |
||||
valueRange: { start: 1, end: 7 }, |
||||
valueLoop: { start: 2, interval: 1 }, |
||||
disabled: disabledChoice, |
||||
}) |
||||
const weekOptions = computed(() => { |
||||
const options: { label: string, value: number }[] = [] |
||||
for (const weekKey of Object.keys(WEEK_MAP_CN)) { |
||||
const weekName: string = WEEK_MAP_CN[weekKey] |
||||
options.push({ |
||||
value: Number.parseInt(weekKey), |
||||
label: weekName, |
||||
}) |
||||
} |
||||
return options |
||||
}) |
||||
|
||||
const typeRangeSelectAttrs = computed(() => ({ |
||||
class: ['w80'], |
||||
disabled: setup.typeRangeAttrs.value.disabled, |
||||
})) |
||||
|
||||
const typeLoopSelectAttrs = computed(() => ({ |
||||
class: ['w80'], |
||||
disabled: setup.typeLoopAttrs.value.disabled, |
||||
})) |
||||
|
||||
watch( |
||||
() => props.day, |
||||
() => { |
||||
setup.updateValue(disabledChoice.value ? '?' : setup.computeValue.value) |
||||
}, |
||||
) |
||||
|
||||
return { |
||||
...setup, |
||||
weekOptions, |
||||
typeLoopSelectAttrs, |
||||
typeRangeSelectAttrs, |
||||
WEEK_MAP_CN, |
||||
WEEK_MAP_EN, |
||||
} |
||||
}, |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div :class="`${prefixCls}-config-list`"> |
||||
<RadioGroup v-model:value="type"> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.unset" v-bind="beforeRadioAttrs"> |
||||
不设置 |
||||
</Radio> |
||||
<span class="tip-info">日和周只能设置其中之一</span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs"> |
||||
区间 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<ASelect v-model:value="valueRange.start" :options="weekOptions" v-bind="typeRangeSelectAttrs" /> |
||||
<span> 至 </span> |
||||
<ASelect v-model:value="valueRange.end" :options="weekOptions" v-bind="typeRangeSelectAttrs" /> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs"> |
||||
循环 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<ASelect v-model:value="valueLoop.start" :options="weekOptions" v-bind="typeLoopSelectAttrs" /> |
||||
<span> 开始,间隔 </span> |
||||
<AInput v-model:value="valueLoop.interval" type="number" v-bind="typeLoopAttrs" /> |
||||
<span> 天 </span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs"> |
||||
指定 |
||||
</Radio> |
||||
<div class="list list-cn"> |
||||
<CheckboxGroup v-model:value="valueList"> |
||||
<template v-for="opt in weekOptions" :key="opt.value"> |
||||
<Checkbox :value="opt.value" v-bind="typeSpecifyAttrs"> |
||||
{{ opt.label }} |
||||
</Checkbox> |
||||
</template> |
||||
</CheckboxGroup> |
||||
</div> |
||||
</div> |
||||
</RadioGroup> |
||||
</div> |
||||
</template> |
@ -1,55 +0,0 @@
|
||||
<script lang="ts"> |
||||
import { defineComponent } from 'vue' |
||||
import { Input, Radio } from 'ant-design-vue' |
||||
import { useTabEmits, useTabProps, useTabSetup } from './useTabMixin' |
||||
|
||||
export default defineComponent({ |
||||
name: 'YearUI', |
||||
components: { AInput: Input, Radio, RadioGroup: Radio.Group }, |
||||
props: useTabProps({ |
||||
defaultValue: '*', |
||||
}), |
||||
emits: useTabEmits(), |
||||
setup(props, context) { |
||||
const nowYear = new Date().getFullYear() |
||||
return useTabSetup(props, context, { |
||||
defaultValue: '*', |
||||
minValue: 0, |
||||
valueRange: { start: nowYear, end: nowYear + 100 }, |
||||
valueLoop: { start: nowYear, interval: 1 }, |
||||
}) |
||||
}, |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div :class="`${prefixCls}-config-list`"> |
||||
<RadioGroup v-model:value="type"> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.every" v-bind="beforeRadioAttrs"> |
||||
每年 |
||||
</Radio> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.range" v-bind="beforeRadioAttrs"> |
||||
区间 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<AInput v-model:value="valueRange.start" type="number" class="w80" v-bind="typeRangeAttrs" /> |
||||
<span> 年 至 </span> |
||||
<AInput v-model:value="valueRange.end" type="number" class="w80" v-bind="typeRangeAttrs" /> |
||||
<span> 年 </span> |
||||
</div> |
||||
<div class="item"> |
||||
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs"> |
||||
循环 |
||||
</Radio> |
||||
<span> 从 </span> |
||||
<AInput v-model:value="valueLoop.start" type="number" class="w80" v-bind="typeLoopAttrs" /> |
||||
<span> 年开始,间隔 </span> |
||||
<AInput v-model:value="valueLoop.interval" type="number" class="w80" v-bind="typeLoopAttrs" /> |
||||
<span> 年 </span> |
||||
</div> |
||||
</RadioGroup> |
||||
</div> |
||||
</template> |
@ -1,204 +0,0 @@
|
||||
// 主要用于日和星期的互斥使用
|
||||
import { computed, inject, reactive, ref, unref, watch } from 'vue' |
||||
import { propTypes } from '@/utils/propTypes' |
||||
|
||||
export enum TypeEnum { |
||||
unset = 'UNSET', |
||||
every = 'EVERY', |
||||
range = 'RANGE', |
||||
loop = 'LOOP', |
||||
work = 'WORK', |
||||
last = 'LAST', |
||||
specify = 'SPECIFY', |
||||
} |
||||
|
||||
// use 公共 props
|
||||
export function useTabProps(options) { |
||||
const defaultValue = options?.defaultValue ?? '?' |
||||
return { |
||||
value: propTypes.string.def(defaultValue), |
||||
disabled: propTypes.bool.def(false), |
||||
...options?.props, |
||||
} |
||||
} |
||||
|
||||
// use 公共 emits
|
||||
export function useTabEmits() { |
||||
return ['change', 'update:value'] |
||||
} |
||||
|
||||
// use 公共 setup
|
||||
export function useTabSetup(props, context, options) { |
||||
const { emit } = context |
||||
const prefixCls = inject('prefixCls') |
||||
const defaultValue = ref(options?.defaultValue ?? '?') |
||||
// 类型
|
||||
const type = ref(options.defaultType ?? TypeEnum.every) |
||||
const valueList = ref<any[]>([]) |
||||
// 对于不同的类型,所定义的值也有所不同
|
||||
const valueRange = reactive(options.valueRange) |
||||
const valueLoop = reactive(options.valueLoop) |
||||
const valueWeek = reactive(options.valueWeek) |
||||
const valueWork = ref(options.valueWork) |
||||
const maxValue = ref(options.maxValue) |
||||
const minValue = ref(options.minValue) |
||||
|
||||
// 根据不同的类型计算出的value
|
||||
const computeValue = computed(() => { |
||||
const valueArray: any[] = [] |
||||
switch (type.value) { |
||||
case TypeEnum.unset: |
||||
valueArray.push('?') |
||||
break |
||||
case TypeEnum.every: |
||||
valueArray.push('*') |
||||
break |
||||
case TypeEnum.range: |
||||
valueArray.push(`${valueRange.start}-${valueRange.end}`) |
||||
break |
||||
case TypeEnum.loop: |
||||
valueArray.push(`${valueLoop.start}/${valueLoop.interval}`) |
||||
break |
||||
case TypeEnum.work: |
||||
valueArray.push(`${valueWork.value}W`) |
||||
break |
||||
case TypeEnum.last: |
||||
valueArray.push('L') |
||||
break |
||||
case TypeEnum.specify: |
||||
if (valueList.value.length === 0) |
||||
valueList.value.push(minValue.value) |
||||
|
||||
valueArray.push(valueList.value.join(',')) |
||||
break |
||||
default: |
||||
valueArray.push(defaultValue.value) |
||||
break |
||||
} |
||||
return valueArray.length > 0 ? valueArray.join('') : defaultValue.value |
||||
}) |
||||
// 指定值范围区间,介于最小值和最大值之间
|
||||
const specifyRange = computed(() => { |
||||
const range: number[] = [] |
||||
if (maxValue.value != null) { |
||||
for (let i = minValue.value; i <= maxValue.value; i++) |
||||
range.push(i) |
||||
} |
||||
return range |
||||
}) |
||||
|
||||
watch( |
||||
() => props.value, |
||||
(val) => { |
||||
if (val !== computeValue.value) |
||||
parseValue(val) |
||||
}, |
||||
{ immediate: true }, |
||||
) |
||||
|
||||
watch(computeValue, v => updateValue(v)) |
||||
|
||||
function updateValue(value) { |
||||
emit('change', value) |
||||
emit('update:value', value) |
||||
} |
||||
|
||||
/** |
||||
* parseValue |
||||
* @param value |
||||
*/ |
||||
function parseValue(value) { |
||||
if (value === computeValue.value) |
||||
return |
||||
|
||||
try { |
||||
if (!value || value === defaultValue.value) { |
||||
type.value = TypeEnum.every |
||||
} |
||||
else if (value.includes('?')) { |
||||
type.value = TypeEnum.unset |
||||
} |
||||
else if (value.includes('-')) { |
||||
type.value = TypeEnum.range |
||||
const values = value.split('-') |
||||
if (values.length >= 2) { |
||||
valueRange.start = Number.parseInt(values[0]) |
||||
valueRange.end = Number.parseInt(values[1]) |
||||
} |
||||
} |
||||
else if (value.includes('/')) { |
||||
type.value = TypeEnum.loop |
||||
const values = value.split('/') |
||||
if (values.length >= 2) { |
||||
valueLoop.start = value[0] === '*' ? 0 : Number.parseInt(values[0]) |
||||
valueLoop.interval = Number.parseInt(values[1]) |
||||
} |
||||
} |
||||
else if (value.includes('W')) { |
||||
type.value = TypeEnum.work |
||||
const values = value.split('W') |
||||
if (!values[0] && !Number.isNaN(values[0])) |
||||
valueWork.value = Number.parseInt(values[0]) |
||||
} |
||||
else if (value.includes('L')) { |
||||
type.value = TypeEnum.last |
||||
} |
||||
else if (value.includes(',') || !Number.isNaN(value)) { |
||||
type.value = TypeEnum.specify |
||||
valueList.value = value.split(',').map(item => Number.parseInt(item)) |
||||
} |
||||
else { |
||||
type.value = TypeEnum.every |
||||
} |
||||
} |
||||
catch (e) { |
||||
type.value = TypeEnum.every |
||||
} |
||||
} |
||||
|
||||
const beforeRadioAttrs = computed(() => ({ |
||||
class: ['choice'], |
||||
disabled: props.disabled || unref(options.disabled), |
||||
})) |
||||
const inputNumberAttrs = computed(() => ({ |
||||
class: ['w60'], |
||||
max: maxValue.value, |
||||
min: minValue.value, |
||||
precision: 0, |
||||
})) |
||||
const typeRangeAttrs = computed(() => ({ |
||||
disabled: type.value !== TypeEnum.range || props.disabled || unref(options.disabled), |
||||
...inputNumberAttrs.value, |
||||
})) |
||||
const typeLoopAttrs = computed(() => ({ |
||||
disabled: type.value !== TypeEnum.loop || props.disabled || unref(options.disabled), |
||||
...inputNumberAttrs.value, |
||||
})) |
||||
const typeSpecifyAttrs = computed(() => ({ |
||||
disabled: type.value !== TypeEnum.specify || props.disabled || unref(options.disabled), |
||||
class: ['list-check-item'], |
||||
})) |
||||
|
||||
return { |
||||
type, |
||||
TypeEnum, |
||||
prefixCls, |
||||
defaultValue, |
||||
valueRange, |
||||
valueLoop, |
||||
valueWeek, |
||||
valueList, |
||||
valueWork, |
||||
maxValue, |
||||
minValue, |
||||
computeValue, |
||||
specifyRange, |
||||
updateValue, |
||||
parseValue, |
||||
beforeRadioAttrs, |
||||
inputNumberAttrs, |
||||
typeRangeAttrs, |
||||
typeLoopAttrs, |
||||
typeSpecifyAttrs, |
||||
} |
||||
} |
@ -1,50 +0,0 @@
|
||||
/* eslint-disable prefer-promise-reject-errors */ |
||||
import CronParser from 'cron-parser' |
||||
import type { ValidatorRule } from 'ant-design-vue/lib/form/interface' |
||||
|
||||
const cronRule: ValidatorRule = { |
||||
// eslint-disable-next-line no-empty-pattern
|
||||
validator({}, value) { |
||||
// 没填写就不校验
|
||||
if (!value) |
||||
return Promise.resolve() |
||||
|
||||
const values: string[] = value.split(' ').filter(item => !!item) |
||||
if (values.length > 7) |
||||
return Promise.reject('Cron表达式最多7项!') |
||||
|
||||
// 检查第7项
|
||||
let val: string = value |
||||
if (values.length === 7) { |
||||
const year = values[6] |
||||
if (year !== '*' && year !== '?') { |
||||
let yearValues: string[] = [] |
||||
if (year.includes('-')) |
||||
yearValues = year.split('-') |
||||
else if (year.indexOf('/')) |
||||
yearValues = year.split('/') |
||||
else |
||||
yearValues = [year] |
||||
|
||||
// 判断是否都是数字
|
||||
const checkYear = yearValues.some(item => Number.isNaN(Number(item))) |
||||
if (checkYear) |
||||
return Promise.reject(`Cron表达式参数[年]错误:${year}`) |
||||
} |
||||
// 取其中的前六项
|
||||
val = values.slice(0, 6).join(' ') |
||||
} |
||||
// 6位 没有年
|
||||
// 5位没有秒、年
|
||||
try { |
||||
const iter = CronParser.parseExpression(val) |
||||
iter.next() |
||||
return Promise.resolve() |
||||
} |
||||
catch (e: any) { |
||||
return Promise.reject(`Cron表达式错误:${e}`) |
||||
} |
||||
}, |
||||
} |
||||
|
||||
export default cronRule.validator |
Loading…
Reference in new issue