feat: [bpm][antd] bpmn 设计器时间事件定义优化
This commit is contained in:
@@ -8,8 +8,10 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
Radio,
|
Radio,
|
||||||
|
TabPane,
|
||||||
Tabs,
|
Tabs,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value: {
|
value: {
|
||||||
@@ -41,7 +43,7 @@ const cronFieldList = [
|
|||||||
];
|
];
|
||||||
const activeField = ref('second');
|
const activeField = ref('second');
|
||||||
const cronMode = ref({
|
const cronMode = ref({
|
||||||
second: 'appoint',
|
second: 'every',
|
||||||
minute: 'every',
|
minute: 'every',
|
||||||
hour: 'every',
|
hour: 'every',
|
||||||
day: 'every',
|
day: 'every',
|
||||||
@@ -50,7 +52,7 @@ const cronMode = ref({
|
|||||||
year: 'every',
|
year: 'every',
|
||||||
});
|
});
|
||||||
const cronAppoint = ref({
|
const cronAppoint = ref({
|
||||||
second: ['00', '01'],
|
second: [],
|
||||||
minute: [],
|
minute: [],
|
||||||
hour: [],
|
hour: [],
|
||||||
day: [],
|
day: [],
|
||||||
@@ -107,103 +109,156 @@ watch(
|
|||||||
const isoStr = ref('');
|
const isoStr = ref('');
|
||||||
const repeat = ref(1);
|
const repeat = ref(1);
|
||||||
const isoDate = ref('');
|
const isoDate = ref('');
|
||||||
|
const durationUnits = [
|
||||||
|
{ key: 'Y', label: '年', presets: [1, 2, 3, 4] },
|
||||||
|
{ key: 'M', label: '月', presets: [1, 2, 3, 4] },
|
||||||
|
{ key: 'D', label: '天', presets: [1, 2, 3, 4] },
|
||||||
|
{ key: 'H', label: '时', presets: [4, 8, 12, 24] },
|
||||||
|
{ key: 'm', label: '分', presets: [5, 10, 30, 50] },
|
||||||
|
{ key: 'S', label: '秒', presets: [5, 10, 30, 50] },
|
||||||
|
];
|
||||||
|
const durationCustom = ref({ Y: '', M: '', D: '', H: '', m: '', S: '' });
|
||||||
const isoDuration = ref('');
|
const isoDuration = ref('');
|
||||||
function setDuration(type, val) {
|
|
||||||
// 组装ISO 8601字符串
|
function setDuration(key, val) {
|
||||||
let d = isoDuration.value;
|
durationCustom.value[key] = !val || Number.isNaN(val) ? '' : val;
|
||||||
if (d.includes(type)) {
|
updateDurationStr();
|
||||||
d = d.replace(new RegExp(String.raw`\d+${type}`), val + type);
|
}
|
||||||
} else {
|
|
||||||
d += val + type;
|
function updateDurationStr() {
|
||||||
}
|
let str = 'P';
|
||||||
isoDuration.value = d;
|
str += durationCustom.value.Y ? `${durationCustom.value.Y}Y` : '';
|
||||||
|
str += durationCustom.value.M ? `${durationCustom.value.M}M` : '';
|
||||||
|
str += durationCustom.value.D ? `${durationCustom.value.D}D` : '';
|
||||||
|
str +=
|
||||||
|
durationCustom.value.H || durationCustom.value.m || durationCustom.value.S
|
||||||
|
? 'T'
|
||||||
|
: '';
|
||||||
|
str += durationCustom.value.H ? `${durationCustom.value.H}H` : '';
|
||||||
|
str += durationCustom.value.m ? `${durationCustom.value.m}M` : '';
|
||||||
|
str += durationCustom.value.S ? `${durationCustom.value.S}S` : '';
|
||||||
|
isoDuration.value = str === 'P' ? '' : str;
|
||||||
updateIsoStr();
|
updateIsoStr();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateIsoStr() {
|
function updateIsoStr() {
|
||||||
let str = `R${repeat.value}`;
|
let str = `R${repeat.value}`;
|
||||||
if (isoDate.value)
|
if (isoDate.value) {
|
||||||
str += `/${
|
const dateStr =
|
||||||
typeof isoDate.value === 'string'
|
typeof isoDate.value === 'string'
|
||||||
? isoDate.value
|
? isoDate.value
|
||||||
: new Date(isoDate.value).toISOString()
|
: isoDate.value.toISOString();
|
||||||
}`;
|
str += `/${dateStr}`;
|
||||||
|
}
|
||||||
if (isoDuration.value) str += `/${isoDuration.value}`;
|
if (isoDuration.value) str += `/${isoDuration.value}`;
|
||||||
isoStr.value = str;
|
isoStr.value = str;
|
||||||
if (tab.value === 'iso') emit('change', isoStr.value);
|
if (tab.value === 'iso') emit('change', isoStr.value);
|
||||||
}
|
}
|
||||||
watch([repeat, isoDate, isoDuration], updateIsoStr);
|
watch([repeat, isoDate], updateIsoStr);
|
||||||
|
watch(durationCustom, updateDurationStr, { deep: true });
|
||||||
watch(
|
watch(
|
||||||
() => props.value,
|
() => props.value,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (!val) return;
|
if (!val) return;
|
||||||
if (tab.value === 'cron') cronStr.value = val;
|
// 自动检测格式:以R开头的是ISO 8601格式,否则是CRON表达式
|
||||||
if (tab.value === 'iso') isoStr.value = val;
|
if (val.startsWith('R')) {
|
||||||
|
tab.value = 'iso';
|
||||||
|
isoStr.value = val;
|
||||||
|
// 解析ISO格式:R{repeat}/{date}/{duration}
|
||||||
|
const parts = val.split('/');
|
||||||
|
if (parts[0]) {
|
||||||
|
const repeatMatch = parts[0].match(/^R(\d+)$/);
|
||||||
|
if (repeatMatch) repeat.value = Number.parseInt(repeatMatch[1], 10);
|
||||||
|
}
|
||||||
|
// 解析date部分(ISO 8601日期时间格式)
|
||||||
|
const datePart = parts.find(
|
||||||
|
(p) => p.includes('T') && !p.startsWith('P') && !p.startsWith('R'),
|
||||||
|
);
|
||||||
|
if (datePart) {
|
||||||
|
isoDate.value = dayjs(datePart);
|
||||||
|
}
|
||||||
|
// 解析duration部分
|
||||||
|
const durationPart = parts.find((p) => p.startsWith('P'));
|
||||||
|
if (durationPart) {
|
||||||
|
const match = durationPart.match(
|
||||||
|
/^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/,
|
||||||
|
);
|
||||||
|
if (match) {
|
||||||
|
durationCustom.value.Y = match[1] || '';
|
||||||
|
durationCustom.value.M = match[2] || '';
|
||||||
|
durationCustom.value.D = match[3] || '';
|
||||||
|
durationCustom.value.H = match[4] || '';
|
||||||
|
durationCustom.value.m = match[5] || '';
|
||||||
|
durationCustom.value.S = match[6] || '';
|
||||||
|
isoDuration.value = durationPart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tab.value = 'cron';
|
||||||
|
cronStr.value = val;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Tabs v-model:active-key="tab">
|
<Tabs v-model:active-key="tab">
|
||||||
<Tabs.TabPane key="cron" tab="CRON表达式">
|
<TabPane key="cron" tab="CRON表达式">
|
||||||
<div style="margin-bottom: 10px">
|
<div class="mb-2.5">
|
||||||
<Input
|
<Input
|
||||||
v-model:value="cronStr"
|
v-model:value="cronStr"
|
||||||
readonly
|
readonly
|
||||||
style="width: 400px; font-weight: bold"
|
class="w-[400px] font-bold"
|
||||||
key="cronStr"
|
key="cronStr"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; gap: 8px; margin-bottom: 8px">
|
<div class="mb-2 flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
v-model:value="fields.second"
|
v-model:value="fields.second"
|
||||||
placeholder="秒"
|
placeholder="秒"
|
||||||
style="width: 80px"
|
class="w-20"
|
||||||
key="second"
|
key="second"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-model:value="fields.minute"
|
v-model:value="fields.minute"
|
||||||
placeholder="分"
|
placeholder="分"
|
||||||
style="width: 80px"
|
class="w-20"
|
||||||
key="minute"
|
key="minute"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-model:value="fields.hour"
|
v-model:value="fields.hour"
|
||||||
placeholder="时"
|
placeholder="时"
|
||||||
style="width: 80px"
|
class="w-20"
|
||||||
key="hour"
|
key="hour"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-model:value="fields.day"
|
v-model:value="fields.day"
|
||||||
placeholder="天"
|
placeholder="天"
|
||||||
style="width: 80px"
|
class="w-20"
|
||||||
key="day"
|
key="day"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-model:value="fields.month"
|
v-model:value="fields.month"
|
||||||
placeholder="月"
|
placeholder="月"
|
||||||
style="width: 80px"
|
class="w-20"
|
||||||
key="month"
|
key="month"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-model:value="fields.week"
|
v-model:value="fields.week"
|
||||||
placeholder="周"
|
placeholder="周"
|
||||||
style="width: 80px"
|
class="w-20"
|
||||||
key="week"
|
key="week"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-model:value="fields.year"
|
v-model:value="fields.year"
|
||||||
placeholder="年"
|
placeholder="年"
|
||||||
style="width: 80px"
|
class="w-20"
|
||||||
key="year"
|
key="year"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Tabs
|
<Tabs v-model:active-key="activeField" type="card" class="mb-2">
|
||||||
v-model:active-key="activeField"
|
|
||||||
type="card"
|
|
||||||
style="margin-bottom: 8px"
|
|
||||||
>
|
|
||||||
<Tabs.TabPane v-for="f in cronFieldList" :key="f.key" :tab="f.label">
|
<Tabs.TabPane v-for="f in cronFieldList" :key="f.key" :tab="f.label">
|
||||||
<div style="margin-bottom: 8px">
|
<div class="mb-2">
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
v-model:value="cronMode[f.key]"
|
v-model:value="cronMode[f.key]"
|
||||||
:key="`radio-${f.key}`"
|
:key="`radio-${f.key}`"
|
||||||
@@ -218,7 +273,7 @@ watch(
|
|||||||
:min="f.min"
|
:min="f.min"
|
||||||
:max="f.max"
|
:max="f.max"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 60px"
|
class="w-[60px]"
|
||||||
:key="`range0-${f.key}`"
|
:key="`range0-${f.key}`"
|
||||||
/>
|
/>
|
||||||
到
|
到
|
||||||
@@ -227,7 +282,7 @@ watch(
|
|||||||
:min="f.min"
|
:min="f.min"
|
||||||
:max="f.max"
|
:max="f.max"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 60px"
|
class="w-[60px]"
|
||||||
:key="`range1-${f.key}`"
|
:key="`range1-${f.key}`"
|
||||||
/>
|
/>
|
||||||
之间每{{ f.label }}
|
之间每{{ f.label }}
|
||||||
@@ -239,7 +294,7 @@ watch(
|
|||||||
:min="f.min"
|
:min="f.min"
|
||||||
:max="f.max"
|
:max="f.max"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 60px"
|
class="w-[60px]"
|
||||||
:key="`step0-${f.key}`"
|
:key="`step0-${f.key}`"
|
||||||
/>
|
/>
|
||||||
开始每
|
开始每
|
||||||
@@ -248,7 +303,7 @@ watch(
|
|||||||
:min="1"
|
:min="1"
|
||||||
:max="f.max"
|
:max="f.max"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 60px"
|
class="w-[60px]"
|
||||||
:key="`step1-${f.key}`"
|
:key="`step1-${f.key}`"
|
||||||
/>
|
/>
|
||||||
{{ f.label }}
|
{{ f.label }}
|
||||||
@@ -272,109 +327,64 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Tabs.TabPane>
|
</TabPane>
|
||||||
<Tabs.TabPane key="iso" title="标准格式" tab="iso-tab">
|
<TabPane key="iso" tab="标准格式">
|
||||||
<div style="margin-bottom: 10px">
|
<div class="mb-2.5">
|
||||||
<Input
|
<Input
|
||||||
v-model:value="isoStr"
|
v-model:value="isoStr"
|
||||||
placeholder="如R1/2025-05-21T21:59:54/P3DT30M30S"
|
placeholder="如R1/2025-05-21T21:59:54/P3DT30M30S"
|
||||||
style="width: 400px; font-weight: bold"
|
class="w-[400px] font-bold"
|
||||||
key="isoStr"
|
key="isoStr"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-bottom: 10px">
|
<div class="mb-2.5">
|
||||||
循环次数:<InputNumber
|
循环次数:<InputNumber
|
||||||
v-model:value="repeat"
|
v-model:value="repeat"
|
||||||
:min="1"
|
:min="1"
|
||||||
style="width: 100px"
|
class="w-[100px]"
|
||||||
key="repeat"
|
key="repeat"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-bottom: 10px">
|
<div class="mb-2.5">
|
||||||
日期时间:<DatePicker
|
开始时间:<DatePicker
|
||||||
v-model:value="isoDate"
|
v-model:value="isoDate"
|
||||||
show-time
|
show-time
|
||||||
placeholder="选择日期时间"
|
placeholder="选择开始时间"
|
||||||
style="width: 200px"
|
class="w-[200px]"
|
||||||
key="isoDate"
|
key="isoDate"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-bottom: 10px">
|
<div class="mb-2.5">
|
||||||
当前时长:<Input
|
间隔时长:<Input
|
||||||
v-model:value="isoDuration"
|
v-model:value="isoDuration"
|
||||||
|
readonly
|
||||||
placeholder="如P3DT30M30S"
|
placeholder="如P3DT30M30S"
|
||||||
style="width: 200px"
|
class="w-[200px]"
|
||||||
key="isoDuration"
|
key="isoDuration"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div v-for="unit in durationUnits" :key="unit.key" class="mb-2">
|
||||||
秒:
|
<span>{{ unit.label }}:</span>
|
||||||
<Button
|
<Button.Group>
|
||||||
v-for="s in [5, 10, 30, 50]"
|
<Button
|
||||||
@click="setDuration('S', s)"
|
v-for="val in unit.presets"
|
||||||
:key="`sec-${s}`"
|
:key="val"
|
||||||
>
|
size="small"
|
||||||
{{ s }}
|
@click="setDuration(unit.key, val)"
|
||||||
</Button>
|
>
|
||||||
自定义
|
{{ val }}
|
||||||
</div>
|
</Button>
|
||||||
<div>
|
<Input
|
||||||
分:
|
v-model:value="durationCustom[unit.key]"
|
||||||
<Button
|
size="small"
|
||||||
v-for="m in [5, 10, 30, 50]"
|
class="ml-2 w-[60px]"
|
||||||
@click="setDuration('M', m)"
|
placeholder="自定义"
|
||||||
:key="`min-${m}`"
|
@change="setDuration(unit.key, durationCustom[unit.key])"
|
||||||
>
|
/>
|
||||||
{{ m }}
|
</Button.Group>
|
||||||
</Button>
|
|
||||||
自定义
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
小时:
|
|
||||||
<Button
|
|
||||||
v-for="h in [4, 8, 12, 24]"
|
|
||||||
@click="setDuration('H', h)"
|
|
||||||
:key="`hour-${h}`"
|
|
||||||
>
|
|
||||||
{{ h }}
|
|
||||||
</Button>
|
|
||||||
自定义
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
天:
|
|
||||||
<Button
|
|
||||||
v-for="d in [1, 2, 3, 4]"
|
|
||||||
@click="setDuration('D', d)"
|
|
||||||
:key="`day-${d}`"
|
|
||||||
>
|
|
||||||
{{ d }}
|
|
||||||
</Button>
|
|
||||||
自定义
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
月:
|
|
||||||
<Button
|
|
||||||
v-for="mo in [1, 2, 3, 4]"
|
|
||||||
@click="setDuration('M', mo)"
|
|
||||||
:key="`mon-${mo}`"
|
|
||||||
>
|
|
||||||
{{ mo }}
|
|
||||||
</Button>
|
|
||||||
自定义
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
年:
|
|
||||||
<Button
|
|
||||||
v-for="y in [1, 2, 3, 4]"
|
|
||||||
@click="setDuration('Y', y)"
|
|
||||||
:key="`year-${y}`"
|
|
||||||
>
|
|
||||||
{{ y }}
|
|
||||||
</Button>
|
|
||||||
自定义
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tabs.TabPane>
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -68,14 +68,10 @@ watch(
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div style="margin-bottom: 10px">
|
<div class="mb-2.5">
|
||||||
当前选择:<Input
|
当前选择:<Input v-model:value="isoString" readonly class="w-[300px]" />
|
||||||
v-model:value="isoString"
|
|
||||||
readonly
|
|
||||||
style="width: 300px"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-for="unit in units" :key="unit.key" style="margin-bottom: 8px">
|
<div v-for="unit in units" :key="unit.key" class="mb-2">
|
||||||
<span>{{ unit.label }}:</span>
|
<span>{{ unit.label }}:</span>
|
||||||
<Button.Group>
|
<Button.Group>
|
||||||
<Button
|
<Button
|
||||||
@@ -89,7 +85,7 @@ watch(
|
|||||||
<Input
|
<Input
|
||||||
v-model:value="custom[unit.key]"
|
v-model:value="custom[unit.key]"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 60px; margin-left: 8px"
|
class="ml-2 w-[60px]"
|
||||||
placeholder="自定义"
|
placeholder="自定义"
|
||||||
@change="setUnit(unit.key, custom[unit.key])"
|
@change="setUnit(unit.key, custom[unit.key])"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
import { computed, nextTick, onMounted, ref, toRaw, watch } from 'vue';
|
import { computed, nextTick, onMounted, ref, toRaw, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
import { Button, DatePicker, Input, Modal, Tooltip } from 'ant-design-vue';
|
import { Button, DatePicker, Input, Tooltip } from 'ant-design-vue';
|
||||||
|
|
||||||
import CycleConfig from './CycleConfig.vue';
|
import CycleConfig from './CycleConfig.vue';
|
||||||
import DurationConfig from './DurationConfig.vue';
|
import DurationConfig from './DurationConfig.vue';
|
||||||
@@ -20,13 +23,8 @@ const props = defineProps({
|
|||||||
const bpmnInstances = () => (window as any).bpmnInstances;
|
const bpmnInstances = () => (window as any).bpmnInstances;
|
||||||
const type: Ref<string> = ref('time');
|
const type: Ref<string> = ref('time');
|
||||||
const condition: Ref<string> = ref('');
|
const condition: Ref<string> = ref('');
|
||||||
const valid: Ref<boolean> = ref(true);
|
const valid: Ref<boolean> = ref(false);
|
||||||
const showDatePicker: Ref<boolean> = ref(false);
|
const dateValue = ref<Dayjs>();
|
||||||
const showDurationDialog: Ref<boolean> = ref(false);
|
|
||||||
const showCycleDialog: Ref<boolean> = ref(false);
|
|
||||||
const showHelp: Ref<boolean> = ref(false);
|
|
||||||
const dateValue: Ref<Date | null> = ref(null);
|
|
||||||
// const bpmnElement = ref(null);
|
|
||||||
|
|
||||||
const placeholder = computed<string>(() => {
|
const placeholder = computed<string>(() => {
|
||||||
if (type.value === 'time') return '请输入时间';
|
if (type.value === 'time') return '请输入时间';
|
||||||
@@ -49,6 +47,9 @@ const helpHtml = computed<string>(() => {
|
|||||||
if (type.value === 'cycle') {
|
if (type.value === 'cycle') {
|
||||||
return `支持CRON表达式(如0 0/30 * * * ?)或ISO 8601周期(如R3/PT10M)。`;
|
return `支持CRON表达式(如0 0/30 * * * ?)或ISO 8601周期(如R3/PT10M)。`;
|
||||||
}
|
}
|
||||||
|
if (type.value === 'time') {
|
||||||
|
return `支持ISO 8601格式的时间(如2024-12-12T12:12:12)`;
|
||||||
|
}
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -82,7 +83,6 @@ function setType(t: string) {
|
|||||||
// 输入校验
|
// 输入校验
|
||||||
watch([type, condition], () => {
|
watch([type, condition], () => {
|
||||||
valid.value = validate();
|
valid.value = validate();
|
||||||
// updateNode() // 可以注释掉,避免频繁触发
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function validate(): boolean {
|
function validate(): boolean {
|
||||||
@@ -93,46 +93,74 @@ function validate(): boolean {
|
|||||||
return /^P.*$/.test(condition.value);
|
return /^P.*$/.test(condition.value);
|
||||||
}
|
}
|
||||||
if (type.value === 'cycle') {
|
if (type.value === 'cycle') {
|
||||||
return /^(?:[0-9*/?, ]+|R\d*\/P.*)$/.test(condition.value);
|
// 支持CRON表达式或ISO 8601周期格式:R{n}/P... 或 R{n}/{date}/P...
|
||||||
|
return /^(?:[0-9*/?, ]+|R\d+(?:\/[^/]+)*\/P.*)$/.test(condition.value);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择时间
|
// 选择时间 Modal
|
||||||
|
const [DateModal, dateModalApi] = useVbenModal({
|
||||||
|
title: '选择时间',
|
||||||
|
class: 'w-[400px]',
|
||||||
|
onConfirm: onDateConfirm,
|
||||||
|
});
|
||||||
|
|
||||||
function onDateChange(val: any) {
|
function onDateChange(val: any) {
|
||||||
dateValue.value = val;
|
dateValue.value = val || undefined;
|
||||||
}
|
}
|
||||||
function onDateConfirm(): void {
|
function onDateConfirm(): void {
|
||||||
if (dateValue.value) {
|
if (dateValue.value) {
|
||||||
condition.value = new Date(dateValue.value).toISOString();
|
condition.value = dateValue.value.toISOString();
|
||||||
showDatePicker.value = false;
|
dateModalApi.close();
|
||||||
updateNode();
|
updateNode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 持续时长
|
// 持续时长 Modal
|
||||||
|
const [DurationModal, durationModalApi] = useVbenModal({
|
||||||
|
title: '时间配置',
|
||||||
|
class: 'w-[600px]',
|
||||||
|
onConfirm: onDurationConfirm,
|
||||||
|
});
|
||||||
|
|
||||||
function onDurationChange(val: string) {
|
function onDurationChange(val: string) {
|
||||||
condition.value = val;
|
condition.value = val;
|
||||||
}
|
}
|
||||||
function onDurationConfirm(): void {
|
function onDurationConfirm(): void {
|
||||||
showDurationDialog.value = false;
|
durationModalApi.close();
|
||||||
updateNode();
|
updateNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 循环
|
// 循环配置 Modal
|
||||||
|
const [CycleModal, cycleModalApi] = useVbenModal({
|
||||||
|
title: '时间配置',
|
||||||
|
class: 'w-[800px]',
|
||||||
|
onConfirm: onCycleConfirm,
|
||||||
|
});
|
||||||
|
|
||||||
function onCycleChange(val: string) {
|
function onCycleChange(val: string) {
|
||||||
condition.value = val;
|
condition.value = val;
|
||||||
}
|
}
|
||||||
function onCycleConfirm(): void {
|
function onCycleConfirm(): void {
|
||||||
showCycleDialog.value = false;
|
cycleModalApi.close();
|
||||||
updateNode();
|
updateNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 输入框聚焦时弹窗(可选)
|
// 帮助说明 Modal
|
||||||
function handleInputFocus(): void {
|
const [HelpModal, helpModalApi] = useVbenModal({
|
||||||
if (type.value === 'time') showDatePicker.value = true;
|
class: 'w-[600px]',
|
||||||
if (type.value === 'duration') showDurationDialog.value = true;
|
title: '格式说明',
|
||||||
if (type.value === 'cycle') showCycleDialog.value = true;
|
showCancelButton: false,
|
||||||
|
confirmText: '关闭',
|
||||||
|
onConfirm: () => helpModalApi.close(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点击输入框时弹窗
|
||||||
|
function handleInputClick(): void {
|
||||||
|
if (type.value === 'time') dateModalApi.open();
|
||||||
|
if (type.value === 'duration') durationModalApi.open();
|
||||||
|
if (type.value === 'cycle') cycleModalApi.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 同步到节点
|
// 同步到节点
|
||||||
@@ -210,8 +238,8 @@ watch(
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="panel-tab__content">
|
<div class="panel-tab__content">
|
||||||
<div style="margin-top: 10px">
|
<div class="mt-2 flex items-center">
|
||||||
<span>类型:</span>
|
<span class="w-14">类型:</span>
|
||||||
<Button.Group>
|
<Button.Group>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -238,17 +266,17 @@ watch(
|
|||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
icon="ant-design:check-circle-filled"
|
icon="ant-design:check-circle-filled"
|
||||||
v-if="valid"
|
v-if="valid"
|
||||||
style="margin-left: 8px; color: green"
|
class="ml-2 text-green-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center; margin-top: 10px">
|
<div class="mt-2 flex items-center gap-1">
|
||||||
<span>条件:</span>
|
<span class="w-14">条件:</span>
|
||||||
<Input
|
<Input
|
||||||
v-model:value="condition"
|
v-model:value="condition"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
class="w-[calc(100vw-25%)]"
|
class="w-full"
|
||||||
:readonly="type !== 'duration' && type !== 'cycle'"
|
:readonly="type !== 'duration' && type !== 'cycle'"
|
||||||
@focus="handleInputFocus"
|
@click="handleInputClick"
|
||||||
@blur="updateNode"
|
@blur="updateNode"
|
||||||
>
|
>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
@@ -262,13 +290,13 @@ watch(
|
|||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
icon="ant-design:question-circle-filled"
|
icon="ant-design:question-circle-filled"
|
||||||
class="cursor-pointer text-[#409eff]"
|
class="cursor-pointer text-[#409eff]"
|
||||||
@click="showHelp = true"
|
@click="helpModalApi.open()"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Button
|
<Button
|
||||||
v-if="type === 'time'"
|
v-if="type === 'time'"
|
||||||
@click="showDatePicker = true"
|
@click="dateModalApi.open()"
|
||||||
style="margin-left: 4px"
|
class="ml-1 flex items-center justify-center"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
@@ -276,8 +304,8 @@ watch(
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
v-if="type === 'duration'"
|
v-if="type === 'duration'"
|
||||||
@click="showDurationDialog = true"
|
@click="durationModalApi.open()"
|
||||||
style="margin-left: 4px"
|
class="ml-1 flex items-center justify-center"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
@@ -285,8 +313,8 @@ watch(
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
v-if="type === 'cycle'"
|
v-if="type === 'cycle'"
|
||||||
@click="showCycleDialog = true"
|
@click="cycleModalApi.open()"
|
||||||
style="margin-left: 4px"
|
class="ml-1 flex items-center justify-center"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
@@ -295,62 +323,32 @@ watch(
|
|||||||
</template>
|
</template>
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 时间选择器 -->
|
<!-- 时间选择器 -->
|
||||||
<Modal
|
<DateModal>
|
||||||
v-model:open="showDatePicker"
|
|
||||||
title="选择时间"
|
|
||||||
width="400px"
|
|
||||||
@cancel="showDatePicker = false"
|
|
||||||
>
|
|
||||||
<DatePicker
|
<DatePicker
|
||||||
v-model:value="dateValue"
|
v-model:value="dateValue"
|
||||||
show-time
|
show-time
|
||||||
placeholder="选择日期时间"
|
placeholder="选择日期时间"
|
||||||
style="width: 100%"
|
class="w-full"
|
||||||
@change="onDateChange"
|
@change="onDateChange"
|
||||||
/>
|
/>
|
||||||
<template #footer>
|
</DateModal>
|
||||||
<Button @click="showDatePicker = false">取消</Button>
|
|
||||||
<Button type="primary" @click="onDateConfirm">确定</Button>
|
|
||||||
</template>
|
|
||||||
</Modal>
|
|
||||||
<!-- 持续时长选择器 -->
|
<!-- 持续时长选择器 -->
|
||||||
<Modal
|
<DurationModal>
|
||||||
v-model:open="showDurationDialog"
|
|
||||||
title="时间配置"
|
|
||||||
width="600px"
|
|
||||||
@cancel="showDurationDialog = false"
|
|
||||||
>
|
|
||||||
<DurationConfig :value="condition" @change="onDurationChange" />
|
<DurationConfig :value="condition" @change="onDurationChange" />
|
||||||
<template #footer>
|
</DurationModal>
|
||||||
<Button @click="showDurationDialog = false">取消</Button>
|
|
||||||
<Button type="primary" @click="onDurationConfirm">确定</Button>
|
|
||||||
</template>
|
|
||||||
</Modal>
|
|
||||||
<!-- 循环配置器 -->
|
<!-- 循环配置器 -->
|
||||||
<Modal
|
<CycleModal>
|
||||||
v-model:open="showCycleDialog"
|
|
||||||
title="时间配置"
|
|
||||||
width="800px"
|
|
||||||
@cancel="showCycleDialog = false"
|
|
||||||
>
|
|
||||||
<CycleConfig :value="condition" @change="onCycleChange" />
|
<CycleConfig :value="condition" @change="onCycleChange" />
|
||||||
<template #footer>
|
</CycleModal>
|
||||||
<Button @click="showCycleDialog = false">取消</Button>
|
|
||||||
<Button type="primary" @click="onCycleConfirm">确定</Button>
|
|
||||||
</template>
|
|
||||||
</Modal>
|
|
||||||
<!-- 帮助说明 -->
|
<!-- 帮助说明 -->
|
||||||
<Modal
|
<HelpModal>
|
||||||
v-model:open="showHelp"
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
title="格式说明"
|
|
||||||
width="600px"
|
|
||||||
@cancel="showHelp = false"
|
|
||||||
>
|
|
||||||
<div v-html="helpHtml"></div>
|
<div v-html="helpHtml"></div>
|
||||||
<template #footer>
|
</HelpModal>
|
||||||
<Button @click="showHelp = false">关闭</Button>
|
|
||||||
</template>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user