feat: cron
parent
fa49d7a988
commit
81d24264d5
|
@ -42,6 +42,7 @@
|
|||
"ant-design-vue": "^3.2.20",
|
||||
"axios": "^1.4.0",
|
||||
"codemirror": "^5.65.3",
|
||||
"cron-parser": "^4.8.1",
|
||||
"cropperjs": "^1.5.13",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dayjs": "^1.11.7",
|
||||
|
|
|
@ -28,6 +28,9 @@ dependencies:
|
|||
codemirror:
|
||||
specifier: ^5.65.3
|
||||
version: 5.65.3
|
||||
cron-parser:
|
||||
specifier: ^4.8.1
|
||||
version: 4.8.1
|
||||
cropperjs:
|
||||
specifier: ^1.5.13
|
||||
version: 1.5.13
|
||||
|
@ -3867,6 +3870,13 @@ packages:
|
|||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||
dev: true
|
||||
|
||||
/cron-parser@4.8.1:
|
||||
resolution: {integrity: sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
luxon: 3.3.0
|
||||
dev: false
|
||||
|
||||
/cropperjs@1.5.13:
|
||||
resolution: {integrity: sha512-by7jKAo73y5/Do0K6sxdTKHgndY0NMjG2bEdgeJxycbcmHuCiMXqw8sxy5C5Y5WTOTcDGmbT7Sr5CgKOXR06OA==}
|
||||
dev: false
|
||||
|
@ -6064,6 +6074,11 @@ packages:
|
|||
engines: {node: '>=16.14'}
|
||||
dev: true
|
||||
|
||||
/luxon@3.3.0:
|
||||
resolution: {integrity: sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/magic-string@0.25.9:
|
||||
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
|
||||
dependencies:
|
||||
|
|
|
@ -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
|
|
@ -117,3 +117,4 @@ export type ComponentType =
|
|||
| 'ApiTransfer'
|
||||
| 'Editor'
|
||||
| 'FileUpload'
|
||||
| 'CronTab'
|
||||
|
|
|
@ -64,6 +64,45 @@ export function convertDate(date) {
|
|||
return date
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式化
|
||||
* @param date 日期
|
||||
* @param block 格式化字符串
|
||||
*/
|
||||
export function dateFormat(date, block) {
|
||||
if (!date) {
|
||||
return ''
|
||||
}
|
||||
let format = block || 'yyyy-MM-dd'
|
||||
date = new Date(date)
|
||||
const map = {
|
||||
M: date.getMonth() + 1, // 月份
|
||||
d: date.getDate(), // 日
|
||||
h: date.getHours(), // 小时
|
||||
m: date.getMinutes(), // 分
|
||||
s: date.getSeconds(), // 秒
|
||||
q: Math.floor((date.getMonth() + 3) / 3), // 季度
|
||||
S: date.getMilliseconds() // 毫秒
|
||||
}
|
||||
format = format.replace(/([yMdhmsqS])+/g, (all, t) => {
|
||||
let v = map[t]
|
||||
if (v !== undefined) {
|
||||
if (all.length > 1) {
|
||||
v = `0${v}`
|
||||
v = v.substr(v.length - 2)
|
||||
}
|
||||
return v
|
||||
} else if (t === 'y') {
|
||||
return date
|
||||
.getFullYear()
|
||||
.toString()
|
||||
.substr(4 - all.length)
|
||||
}
|
||||
return all
|
||||
})
|
||||
return format
|
||||
}
|
||||
|
||||
/**
|
||||
* 将毫秒,转换成时间字符串。例如说,xx 分钟
|
||||
*
|
||||
|
|
|
@ -113,3 +113,29 @@ export const withInstall = <T extends CustomComponent>(component: T, alias?: str
|
|||
}
|
||||
return component as WithInstall<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单实现防抖方法
|
||||
*
|
||||
* 防抖(debounce)函数在第一次触发给定的函数时,不立即执行函数,而是给出一个期限值(delay),比如100ms。
|
||||
* 如果100ms内再次执行函数,就重新开始计时,直到计时结束后再真正执行函数。
|
||||
* 这样做的好处是如果短时间内大量触发同一事件,只会执行一次函数。
|
||||
*
|
||||
* @param fn 要防抖的函数
|
||||
* @param delay 防抖的毫秒数
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function simpleDebounce(fn, delay = 100) {
|
||||
let timer: any | null = null
|
||||
return function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
const args = arguments
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
// @ts-ignore
|
||||
fn.apply(this, args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { DescItem } from '@/components/Description'
|
||||
import { BasicColumn, FormSchema, useRender } from '@/components/Table'
|
||||
import { DICT_TYPE, getDictOpts } from '@/utils/dict'
|
||||
import { useComponentRegister } from '@/components/Form'
|
||||
import { CronTab } from '@/components/CronTab'
|
||||
|
||||
useComponentRegister('CronTab', CronTab)
|
||||
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
|
@ -91,7 +95,7 @@ export const formSchema: FormSchema[] = [
|
|||
label: 'CRON 表达式',
|
||||
field: 'cronExpression',
|
||||
required: true,
|
||||
component: 'Input'
|
||||
component: 'CronTab'
|
||||
},
|
||||
{
|
||||
label: '重试次数',
|
||||
|
|
Loading…
Reference in New Issue