20 changed files with 1286 additions and 1 deletions
@ -0,0 +1,4 @@ |
|||||||
|
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' |
@ -0,0 +1,345 @@ |
|||||||
|
<template> |
||||||
|
<div :class="`${prefixCls}`"> |
||||||
|
<div class="content"> |
||||||
|
<Tabs :size="`small`" v-model:activeKey="activeKey"> |
||||||
|
<TabPane tab="秒" key="second" v-if="!hideSecond"> |
||||||
|
<SecondUI v-model:value="second" :disabled="disabled" /> |
||||||
|
</TabPane> |
||||||
|
<TabPane tab="分" key="minute"> |
||||||
|
<MinuteUI v-model:value="minute" :disabled="disabled" /> |
||||||
|
</TabPane> |
||||||
|
<TabPane tab="时" key="hour"> |
||||||
|
<HourUI v-model:value="hour" :disabled="disabled" /> |
||||||
|
</TabPane> |
||||||
|
<TabPane tab="日" key="day"> |
||||||
|
<DayUI v-model:value="day" :week="week" :disabled="disabled" /> |
||||||
|
</TabPane> |
||||||
|
<TabPane tab="月" key="month"> |
||||||
|
<MonthUI v-model:value="month" :disabled="disabled" /> |
||||||
|
</TabPane> |
||||||
|
<TabPane tab="周" key="week"> |
||||||
|
<WeekUI v-model:value="week" :day="day" :disabled="disabled" /> |
||||||
|
</TabPane> |
||||||
|
<TabPane tab="年" key="year" v-if="!hideYear && !hideSecond"> |
||||||
|
<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 type="textarea" :value="preTimeList" :rows="5" /> |
||||||
|
</Col> |
||||||
|
</Row> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script lang="ts" setup> |
||||||
|
import { computed, reactive, ref, watch, provide } from 'vue' |
||||||
|
import { Input, Tooltip, Row, Col, Divider, Tabs, TabPane, Textarea } from 'ant-design-vue' |
||||||
|
import { useDesign } from '@/hooks/web/useDesign' |
||||||
|
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 { dateFormat } from '@/utils/dateUtil' |
||||||
|
import { simpleDebounce } from '@/utils' |
||||||
|
|
||||||
|
const { prefixCls } = useDesign('cron-inner') |
||||||
|
provide('prefixCls', prefixCls) |
||||||
|
const emit = defineEmits([...cronEmits]) |
||||||
|
const props = defineProps({ ...cronProps }) |
||||||
|
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(() => { |
||||||
|
let 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) { |
||||||
|
let convert = (v: string) => { |
||||||
|
if (v === '0') { |
||||||
|
return '1' |
||||||
|
} |
||||||
|
if (v === '1') { |
||||||
|
return '0' |
||||||
|
} |
||||||
|
return (Number.parseInt(v) - 1).toString() |
||||||
|
} |
||||||
|
// 匹配示例 1-7 or 1/7 |
||||||
|
let patten1 = /^([0-7])([-/])([0-7])$/ |
||||||
|
// 匹配示例 1,4,7 |
||||||
|
let 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> |
||||||
|
<style lang="less"> |
||||||
|
@prefix-cls: ~'@{namespace}-cron-inner'; |
||||||
|
|
||||||
|
.@{prefix-cls} { |
||||||
|
.content { |
||||||
|
.ant-checkbox-wrapper + .ant-checkbox-wrapper { |
||||||
|
margin-left: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&-config-list { |
||||||
|
text-align: left; |
||||||
|
margin: 0 10px 10px; |
||||||
|
|
||||||
|
.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 { |
||||||
|
padding: 1px 3px; |
||||||
|
width: 4em; |
||||||
|
} |
||||||
|
|
||||||
|
.list-cn .list-check-item { |
||||||
|
width: 5em; |
||||||
|
} |
||||||
|
|
||||||
|
.tip-info { |
||||||
|
color: #999; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.allow-click { |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,76 @@ |
|||||||
|
<template> |
||||||
|
<div :class="`${prefixCls}`"> |
||||||
|
<Input :placeholder="placeholder" v-model:value="editCronValue" :disabled="disabled"> |
||||||
|
<template #addonAfter> |
||||||
|
<a class="open-btn" :disabled="disabled ? 'disabled' : null" @click="showConfigModal"> |
||||||
|
<Icon icon="ant-design:setting-outlined" /> |
||||||
|
<span>选择</span> |
||||||
|
</a> |
||||||
|
</template> |
||||||
|
</Input> |
||||||
|
<CronTabModal |
||||||
|
@register="registerModal" |
||||||
|
v-model:value="editCronValue" |
||||||
|
:exeStartTime="exeStartTime" |
||||||
|
:hideYear="hideYear" |
||||||
|
:remote="remote" |
||||||
|
:hideSecond="hideSecond" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script lang="ts" setup> |
||||||
|
import { ref, watch } from 'vue' |
||||||
|
import { Input } from 'ant-design-vue' |
||||||
|
import { useDesign } from '@/hooks/web/useDesign' |
||||||
|
import { useModal } from '@/components/Modal' |
||||||
|
import { propTypes } from '@/utils/propTypes' |
||||||
|
import { Icon } from '@/components/Icon' |
||||||
|
import CronTabModal from './CronTabModal.vue' |
||||||
|
import { cronEmits, cronProps } from './cron.data' |
||||||
|
|
||||||
|
const { prefixCls } = useDesign('cron-input') |
||||||
|
const emit = defineEmits([...cronEmits]) |
||||||
|
const props = defineProps({ |
||||||
|
...cronProps, |
||||||
|
placeholder: propTypes.string.def('请输入cron表达式'), |
||||||
|
exeStartTime: propTypes.oneOfType([propTypes.number, propTypes.string, propTypes.object]).def(0) |
||||||
|
}) |
||||||
|
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> |
||||||
|
|
||||||
|
<style lang="less"> |
||||||
|
@prefix-cls: ~'@{namespace}-cron-input'; |
||||||
|
|
||||||
|
.@{prefix-cls} { |
||||||
|
a.open-btn { |
||||||
|
cursor: pointer; |
||||||
|
|
||||||
|
.app-iconify { |
||||||
|
position: relative; |
||||||
|
top: 1px; |
||||||
|
right: 2px; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,20 @@ |
|||||||
|
<template> |
||||||
|
<BasicModal @register="registerModal" title="Cron表达式" width="800px" @ok="onOk"> |
||||||
|
<CronTab v-bind="attrs" /> |
||||||
|
</BasicModal> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script lang="ts" setup> |
||||||
|
import { useAttrs } from '@/hooks/core/useAttrs' |
||||||
|
import { BasicModal, useModalInner } from '@/components/Modal' |
||||||
|
import CronTab from './CronTabInner.vue' |
||||||
|
|
||||||
|
defineOptions({ name: 'CronTabModal', inheritAttrs: false }) |
||||||
|
|
||||||
|
const attrs = useAttrs() |
||||||
|
const [registerModal, { closeModal }] = useModalInner() |
||||||
|
|
||||||
|
function onOk() { |
||||||
|
closeModal() |
||||||
|
} |
||||||
|
</script> |
@ -0,0 +1,10 @@ |
|||||||
|
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 |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
<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> |
||||||
|
<InputNumber v-model:value="valueRange.start" v-bind="typeRangeAttrs" /> |
||||||
|
<span> 日 至 </span> |
||||||
|
<InputNumber v-model:value="valueRange.end" v-bind="typeRangeAttrs" /> |
||||||
|
<span> 日 </span> |
||||||
|
</div> |
||||||
|
<div class="item"> |
||||||
|
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</Radio> |
||||||
|
<span> 从 </span> |
||||||
|
<InputNumber v-model:value="valueLoop.start" class="w-4" v-bind="typeLoopAttrs" /> |
||||||
|
<span> 日开始,间隔 </span> |
||||||
|
<InputNumber v-model:value="valueLoop.interval" 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> |
||||||
|
|
||||||
|
<script lang="ts"> |
||||||
|
import { computed, defineComponent, watch } from 'vue' |
||||||
|
import { InputNumber, Radio, Checkbox } from 'ant-design-vue' |
||||||
|
import { TypeEnum, useTabEmits, useTabProps, useTabSetup } from './useTabMixin' |
||||||
|
|
||||||
|
export default defineComponent({ |
||||||
|
name: 'DayUI', |
||||||
|
components: { InputNumber, 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> |
@ -0,0 +1,59 @@ |
|||||||
|
<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> |
||||||
|
<InputNumber v-model:value="valueRange.start" v-bind="typeRangeAttrs" /> |
||||||
|
<span> 时 至 </span> |
||||||
|
<InputNumber v-model:value="valueRange.end" v-bind="typeRangeAttrs" /> |
||||||
|
<span> 时 </span> |
||||||
|
</div> |
||||||
|
<div class="item"> |
||||||
|
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</Radio> |
||||||
|
<span> 从 </span> |
||||||
|
<InputNumber v-model:value="valueLoop.start" v-bind="typeLoopAttrs" /> |
||||||
|
<span> 时开始,间隔 </span> |
||||||
|
<InputNumber v-model:value="valueLoop.interval" 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> |
||||||
|
|
||||||
|
<script lang="ts"> |
||||||
|
import { defineComponent } from 'vue' |
||||||
|
import { InputNumber, Radio, Checkbox } from 'ant-design-vue' |
||||||
|
import { useTabProps, useTabEmits, useTabSetup } from './useTabMixin' |
||||||
|
|
||||||
|
export default defineComponent({ |
||||||
|
name: 'HourUI', |
||||||
|
components: { InputNumber, 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> |
@ -0,0 +1,59 @@ |
|||||||
|
<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> |
||||||
|
<InputNumber v-model:value="valueRange.start" v-bind="typeRangeAttrs" /> |
||||||
|
<span> 分 至 </span> |
||||||
|
<InputNumber v-model:value="valueRange.end" v-bind="typeRangeAttrs" /> |
||||||
|
<span> 分 </span> |
||||||
|
</div> |
||||||
|
<div class="item"> |
||||||
|
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</Radio> |
||||||
|
<span> 从 </span> |
||||||
|
<InputNumber v-model:value="valueLoop.start" v-bind="typeLoopAttrs" /> |
||||||
|
<span> 分开始,间隔 </span> |
||||||
|
<InputNumber v-model:value="valueLoop.interval" 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> |
||||||
|
|
||||||
|
<script lang="ts"> |
||||||
|
import { defineComponent } from 'vue' |
||||||
|
import { InputNumber, Radio, Checkbox } from 'ant-design-vue' |
||||||
|
import { useTabProps, useTabEmits, useTabSetup } from './useTabMixin' |
||||||
|
|
||||||
|
export default defineComponent({ |
||||||
|
name: 'MinuteUI', |
||||||
|
components: { InputNumber, 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> |
@ -0,0 +1,59 @@ |
|||||||
|
<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> |
||||||
|
<InputNumber v-model:value="valueRange.start" v-bind="typeRangeAttrs" /> |
||||||
|
<span> 月 至 </span> |
||||||
|
<InputNumber v-model:value="valueRange.end" v-bind="typeRangeAttrs" /> |
||||||
|
<span> 月 </span> |
||||||
|
</div> |
||||||
|
<div class="item"> |
||||||
|
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</Radio> |
||||||
|
<span> 从 </span> |
||||||
|
<InputNumber v-model:value="valueLoop.start" v-bind="typeLoopAttrs" /> |
||||||
|
<span> 月开始,间隔 </span> |
||||||
|
<InputNumber v-model:value="valueLoop.interval" 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> |
||||||
|
|
||||||
|
<script lang="ts"> |
||||||
|
import { defineComponent } from 'vue' |
||||||
|
import { InputNumber, Radio, Checkbox } from 'ant-design-vue' |
||||||
|
import { useTabProps, useTabEmits, useTabSetup } from './useTabMixin' |
||||||
|
|
||||||
|
export default defineComponent({ |
||||||
|
name: 'MonthUI', |
||||||
|
components: { InputNumber, 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> |
@ -0,0 +1,59 @@ |
|||||||
|
<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> |
||||||
|
<InputNumber v-model:value="valueRange.start" v-bind="typeRangeAttrs" /> |
||||||
|
<span> 秒 至 </span> |
||||||
|
<InputNumber v-model:value="valueRange.end" v-bind="typeRangeAttrs" /> |
||||||
|
<span> 秒 </span> |
||||||
|
</div> |
||||||
|
<div class="item"> |
||||||
|
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</Radio> |
||||||
|
<span> 从 </span> |
||||||
|
<InputNumber v-model:value="valueLoop.start" v-bind="typeLoopAttrs" /> |
||||||
|
<span> 秒开始,间隔 </span> |
||||||
|
<InputNumber v-model:value="valueLoop.interval" v-bind="typeLoopAttrs" /> |
||||||
|
<span> 秒 </span> |
||||||
|
</div> |
||||||
|
<div class="item"> |
||||||
|
<a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-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> |
||||||
|
|
||||||
|
<script lang="ts"> |
||||||
|
import { defineComponent } from 'vue' |
||||||
|
import { InputNumber, Radio, Checkbox } from 'ant-design-vue' |
||||||
|
import { useTabProps, useTabEmits, useTabSetup } from './useTabMixin' |
||||||
|
|
||||||
|
export default defineComponent({ |
||||||
|
name: 'SecondUI', |
||||||
|
components: { InputNumber, 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> |
@ -0,0 +1,125 @@ |
|||||||
|
<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> |
||||||
|
<Select v-model:value="valueRange.start" :options="weekOptions" v-bind="typeRangeSelectAttrs" /> |
||||||
|
<span> 至 </span> |
||||||
|
<Select v-model:value="valueRange.end" :options="weekOptions" v-bind="typeRangeSelectAttrs" /> |
||||||
|
</div> |
||||||
|
<div class="item"> |
||||||
|
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</Radio> |
||||||
|
<span> 从 </span> |
||||||
|
<Select v-model:value="valueLoop.start" :options="weekOptions" v-bind="typeLoopSelectAttrs" /> |
||||||
|
<span> 开始,间隔 </span> |
||||||
|
<InputNumber v-model:value="valueLoop.interval" v-bind="typeLoopAttrs" /> |
||||||
|
<span> 天 </span> |
||||||
|
</div> |
||||||
|
<div class="item"> |
||||||
|
<a-radio :value="TypeEnum.specify" v-bind="beforeRadioAttrs">指定</a-radio> |
||||||
|
<div class="list list-cn"> |
||||||
|
<CheckboxGroup v-model:value="valueList"> |
||||||
|
<template v-for="opt in weekOptions" :key="i"> |
||||||
|
<Checkbox :value="opt.value" v-bind="typeSpecifyAttrs">{{ opt.label }}</Checkbox> |
||||||
|
</template> |
||||||
|
</CheckboxGroup> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</RadioGroup> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script lang="ts"> |
||||||
|
import { computed, watch, defineComponent } from 'vue' |
||||||
|
import { InputNumber, Radio, Checkbox, Select } from 'ant-design-vue' |
||||||
|
import { useTabProps, useTabEmits, useTabSetup, TypeEnum } 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: { InputNumber, 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(() => { |
||||||
|
let options: { label: string; value: number }[] = [] |
||||||
|
for (let weekKey of Object.keys(WEEK_MAP_CN)) { |
||||||
|
let 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> |
@ -0,0 +1,49 @@ |
|||||||
|
<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> |
||||||
|
<InputNumber class="w80" v-model:value="valueRange.start" v-bind="typeRangeAttrs" /> |
||||||
|
<span> 年 至 </span> |
||||||
|
<InputNumber class="w80" v-model:value="valueRange.end" v-bind="typeRangeAttrs" /> |
||||||
|
<span> 年 </span> |
||||||
|
</div> |
||||||
|
<div class="item"> |
||||||
|
<Radio :value="TypeEnum.loop" v-bind="beforeRadioAttrs">循环</Radio> |
||||||
|
<span> 从 </span> |
||||||
|
<InputNumber class="w80" v-model:value="valueLoop.start" v-bind="typeLoopAttrs" /> |
||||||
|
<span> 年开始,间隔 </span> |
||||||
|
<InputNumber class="w80" v-model:value="valueLoop.interval" v-bind="typeLoopAttrs" /> |
||||||
|
<span> 年 </span> |
||||||
|
</div> |
||||||
|
</RadioGroup> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script lang="ts"> |
||||||
|
import { defineComponent } from 'vue' |
||||||
|
import { InputNumber, Radio } from 'ant-design-vue' |
||||||
|
import { useTabProps, useTabEmits, useTabSetup } from './useTabMixin' |
||||||
|
|
||||||
|
export default defineComponent({ |
||||||
|
name: 'YearUI', |
||||||
|
components: { InputNumber, 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> |
@ -0,0 +1,199 @@ |
|||||||
|
// 主要用于日和星期的互斥使用
|
||||||
|
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.indexOf('?') >= 0) { |
||||||
|
type.value = TypeEnum.unset |
||||||
|
} else if (value.indexOf('-') >= 0) { |
||||||
|
type.value = TypeEnum.range |
||||||
|
const values = value.split('-') |
||||||
|
if (values.length >= 2) { |
||||||
|
valueRange.start = parseInt(values[0]) |
||||||
|
valueRange.end = parseInt(values[1]) |
||||||
|
} |
||||||
|
} else if (value.indexOf('/') >= 0) { |
||||||
|
type.value = TypeEnum.loop |
||||||
|
const values = value.split('/') |
||||||
|
if (values.length >= 2) { |
||||||
|
valueLoop.start = value[0] === '*' ? 0 : parseInt(values[0]) |
||||||
|
valueLoop.interval = parseInt(values[1]) |
||||||
|
} |
||||||
|
} else if (value.indexOf('W') >= 0) { |
||||||
|
type.value = TypeEnum.work |
||||||
|
const values = value.split('W') |
||||||
|
if (!values[0] && !isNaN(values[0])) { |
||||||
|
valueWork.value = parseInt(values[0]) |
||||||
|
} |
||||||
|
} else if (value.indexOf('L') >= 0) { |
||||||
|
type.value = TypeEnum.last |
||||||
|
} else if (value.indexOf(',') >= 0 || !isNaN(value)) { |
||||||
|
type.value = TypeEnum.specify |
||||||
|
valueList.value = value.split(',').map((item) => 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 |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
import CronParser from 'cron-parser' |
||||||
|
import type { ValidatorRule } from 'ant-design-vue/lib/form/interface' |
||||||
|
|
||||||
|
const cronRule: ValidatorRule = { |
||||||
|
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.indexOf('-') >= 0) { |
||||||
|
yearValues = year.split('-') |
||||||
|
} else if (year.indexOf('/')) { |
||||||
|
yearValues = year.split('/') |
||||||
|
} else { |
||||||
|
yearValues = [year] |
||||||
|
} |
||||||
|
// 判断是否都是数字
|
||||||
|
const checkYear = yearValues.some((item) => 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) { |
||||||
|
return Promise.reject('Cron表达式错误:' + e) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default cronRule.validator |
Loading…
Reference in new issue