Skip to content

Commit 5a8d208

Browse files
authored
Merge pull request #153 from cactuer/timer-event-config-branch
增加BPMN流程设计器的时间事件配置面板
2 parents d9bcd82 + 00dcbe1 commit 5a8d208

File tree

4 files changed

+525
-0
lines changed

4 files changed

+525
-0
lines changed

src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@
6868
:business-object="elementBusinessObject"
6969
/>
7070
</el-collapse-item>
71+
<!-- 新增的时间事件配置项 -->
72+
<el-collapse-item v-if="elementType === 'IntermediateCatchEvent'" name="timeEvent">
73+
<template #title><Icon icon="ep:timer" />时间事件</template>
74+
<TimeEventConfig :businessObject="bpmnElement.value?.businessObject" :key="elementId" />
75+
</el-collapse-item>
7176
</el-collapse>
7277
</div>
7378
</template>
@@ -83,6 +88,8 @@ import ElementProperties from './properties/ElementProperties.vue'
8388
// import ElementForm from './form/ElementForm.vue'
8489
import UserTaskListeners from './listeners/UserTaskListeners.vue'
8590
import { getTaskCollapseItemName, isTaskCollapseItemShow } from './task/data'
91+
import TimeEventConfig from "./time-event-config/TimeEventConfig.vue"
92+
import { ref, computed, watch, onMounted } from 'vue'
8693
8794
defineOptions({ name: 'MyPropertiesPanel' })
8895
@@ -121,6 +128,8 @@ const formVisible = ref(false) // 表单配置
121128
const bpmnElement = ref()
122129
const isReady = ref(false)
123130
131+
const type = ref('time')
132+
const condition = ref('')
124133
provide('prefix', props.prefix)
125134
provide('width', props.width)
126135
@@ -255,4 +264,54 @@ watch(
255264
activeTab.value = 'base'
256265
}
257266
)
267+
268+
function updateNode() {
269+
const moddle = window.bpmnInstances?.moddle
270+
const modeling = window.bpmnInstances?.modeling
271+
const elementRegistry = window.bpmnInstances?.elementRegistry
272+
if (!moddle || !modeling || !elementRegistry) return
273+
274+
const element = elementRegistry.get(props.businessObject.id)
275+
if (!element) return
276+
277+
let timerDef = moddle.create('bpmn:TimerEventDefinition', {})
278+
if (type.value === 'time') {
279+
timerDef.timeDate = moddle.create('bpmn:FormalExpression', { body: condition.value })
280+
} else if (type.value === 'duration') {
281+
timerDef.timeDuration = moddle.create('bpmn:FormalExpression', { body: condition.value })
282+
} else if (type.value === 'cycle') {
283+
timerDef.timeCycle = moddle.create('bpmn:FormalExpression', { body: condition.value })
284+
}
285+
286+
modeling.updateModdleProperties(
287+
element,
288+
element.businessObject,
289+
{
290+
eventDefinitions: [timerDef]
291+
}
292+
)
293+
294+
console.log('当前eventDefinitions:', element.businessObject.eventDefinitions)
295+
}
296+
297+
// 初始化和监听
298+
function syncFromBusinessObject() {
299+
if (props.businessObject) {
300+
const timerDef = (props.businessObject.eventDefinitions || [])[0]
301+
if (timerDef) {
302+
if (timerDef.timeDate) {
303+
type.value = 'time'
304+
condition.value = timerDef.timeDate.body
305+
} else if (timerDef.timeDuration) {
306+
type.value = 'duration'
307+
condition.value = timerDef.timeDuration.body
308+
} else if (timerDef.timeCycle) {
309+
type.value = 'cycle'
310+
condition.value = timerDef.timeCycle.body
311+
}
312+
}
313+
}
314+
}
315+
onMounted(syncFromBusinessObject)
316+
watch(() => props.businessObject, syncFromBusinessObject, { deep: true })
258317
</script>
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<template>
2+
<el-tabs v-model="tab">
3+
<el-tab-pane label="CRON表达式" name="cron">
4+
<div style="margin-bottom: 10px;">
5+
<el-input v-model="cronStr" readonly style="width: 400px; font-weight: bold;" :key="'cronStr'" />
6+
</div>
7+
<div style="display: flex; gap: 8px; margin-bottom: 8px;">
8+
<el-input v-model="fields.second" placeholder="" style="width: 80px;" :key="'second'" />
9+
<el-input v-model="fields.minute" placeholder="" style="width: 80px;" :key="'minute'" />
10+
<el-input v-model="fields.hour" placeholder="" style="width: 80px;" :key="'hour'" />
11+
<el-input v-model="fields.day" placeholder="" style="width: 80px;" :key="'day'" />
12+
<el-input v-model="fields.month" placeholder="" style="width: 80px;" :key="'month'" />
13+
<el-input v-model="fields.week" placeholder="" style="width: 80px;" :key="'week'" />
14+
<el-input v-model="fields.year" placeholder="" style="width: 80px;" :key="'year'" />
15+
</div>
16+
<el-tabs v-model="activeField" type="card" style="margin-bottom: 8px;">
17+
<el-tab-pane v-for="f in cronFieldList" :label="f.label" :name="f.key" :key="f.key">
18+
<div style="margin-bottom: 8px;">
19+
<el-radio-group v-model="cronMode[f.key]" :key="'radio-'+f.key">
20+
<el-radio label="every" :key="'every-'+f.key">每{{f.label}}</el-radio>
21+
<el-radio label="range" :key="'range-'+f.key">从 <el-input-number v-model="cronRange[f.key][0]" :min="f.min" :max="f.max" size="small" style="width:60px" :key="'range0-'+f.key" /> 到 <el-input-number v-model="cronRange[f.key][1]" :min="f.min" :max="f.max" size="small" style="width:60px" :key="'range1-'+f.key" /> 之间每{{f.label}}</el-radio>
22+
<el-radio label="step" :key="'step-'+f.key">从第 <el-input-number v-model="cronStep[f.key][0]" :min="f.min" :max="f.max" size="small" style="width:60px" :key="'step0-'+f.key" /> 开始每 <el-input-number v-model="cronStep[f.key][1]" :min="1" :max="f.max" size="small" style="width:60px" :key="'step1-'+f.key" /> {{f.label}}</el-radio>
23+
<el-radio label="appoint" :key="'appoint-'+f.key">指定</el-radio>
24+
</el-radio-group>
25+
</div>
26+
<div v-if="cronMode[f.key]==='appoint'">
27+
<el-checkbox-group v-model="cronAppoint[f.key]" :key="'group-'+f.key">
28+
<el-checkbox v-for="n in f.max+1" :label="pad(n-1)" :key="'cb-'+f.key+'-'+(n-1)">{{pad(n-1)}}</el-checkbox>
29+
</el-checkbox-group>
30+
</div>
31+
</el-tab-pane>
32+
</el-tabs>
33+
</el-tab-pane>
34+
<el-tab-pane label="标准格式" name="iso" :key="'iso-tab'">
35+
<div style="margin-bottom: 10px;">
36+
<el-input v-model="isoStr" placeholder="如R1/2025-05-21T21:59:54/P3DT30M30S" style="width: 400px; font-weight: bold;" :key="'isoStr'" />
37+
</div>
38+
<div style="margin-bottom: 10px;">循环次数:<el-input-number v-model="repeat" :min="1" style="width: 100px;" :key="'repeat'" /></div>
39+
<div style="margin-bottom: 10px;">日期时间:<el-date-picker v-model="isoDate" type="datetime" placeholder="选择日期时间" style="width: 200px;" :key="'isoDate'" /></div>
40+
<div style="margin-bottom: 10px;">当前时长:<el-input v-model="isoDuration" placeholder="如P3DT30M30S" style="width: 200px;" :key="'isoDuration'" /></div>
41+
<div>
42+
<div>秒:<el-button v-for="s in [5,10,30,50]" @click="setDuration('S',s)" :key="'sec-'+s">{{s}}</el-button>自定义</div>
43+
<div>分:<el-button v-for="m in [5,10,30,50]" @click="setDuration('M',m)" :key="'min-'+m">{{m}}</el-button>自定义</div>
44+
<div>小时:<el-button v-for="h in [4,8,12,24]" @click="setDuration('H',h)" :key="'hour-'+h">{{h}}</el-button>自定义</div>
45+
<div>天:<el-button v-for="d in [1,2,3,4]" @click="setDuration('D',d)" :key="'day-'+d">{{d}}</el-button>自定义</div>
46+
<div>月:<el-button v-for="mo in [1,2,3,4]" @click="setDuration('M',mo)" :key="'mon-'+mo">{{mo}}</el-button>自定义</div>
47+
<div>年:<el-button v-for="y in [1,2,3,4]" @click="setDuration('Y',y)" :key="'year-'+y">{{y}}</el-button>自定义</div>
48+
</div>
49+
</el-tab-pane>
50+
</el-tabs>
51+
</template>
52+
<script setup>
53+
import { ref, watch, computed } from 'vue'
54+
const props = defineProps({ value: String })
55+
const emit = defineEmits(['change'])
56+
57+
const tab = ref('cron')
58+
const cronStr = ref(props.value || '* * * * * ?')
59+
const fields = ref({ second: '*', minute: '*', hour: '*', day: '*', month: '*', week: '?', year: '' })
60+
const cronFieldList = [
61+
{ key: 'second', label: '', min: 0, max: 59 },
62+
{ key: 'minute', label: '', min: 0, max: 59 },
63+
{ key: 'hour', label: '', min: 0, max: 23 },
64+
{ key: 'day', label: '', min: 1, max: 31 },
65+
{ key: 'month', label: '', min: 1, max: 12 },
66+
{ key: 'week', label: '', min: 1, max: 7 },
67+
{ key: 'year', label: '', min: 1970, max: 2099 }
68+
]
69+
const activeField = ref('second')
70+
const cronMode = ref({ second: 'appoint', minute: 'every', hour: 'every', day: 'every', month: 'every', week: 'every', year: 'every' })
71+
const cronAppoint = ref({ second: ['00','01'], minute: [], hour: [], day: [], month: [], week: [], year: [] })
72+
const cronRange = ref({ second: [0,1], minute: [0,1], hour: [0,1], day: [1,2], month: [1,2], week: [1,2], year: [1970,1971] })
73+
const cronStep = ref({ second: [1,1], minute: [1,1], hour: [1,1], day: [1,1], month: [1,1], week: [1,1], year: [1970,1] })
74+
75+
function pad(n) { return n<10 ? '0'+n : ''+n }
76+
77+
watch([fields, cronMode, cronAppoint, cronRange, cronStep], () => {
78+
// 组装cron表达式
79+
let arr = cronFieldList.map(f => {
80+
if (cronMode.value[f.key]==='every') return '*'
81+
if (cronMode.value[f.key]==='appoint') return cronAppoint.value[f.key].join(',') || '*'
82+
if (cronMode.value[f.key]==='range') return `${cronRange.value[f.key][0]}-${cronRange.value[f.key][1]}`
83+
if (cronMode.value[f.key]==='step') return `${cronStep.value[f.key][0]}/${cronStep.value[f.key][1]}`
84+
return fields.value[f.key] || '*'
85+
})
86+
// week和year特殊处理
87+
arr[5] = arr[5] || '?'
88+
cronStr.value = arr.join(' ')
89+
if (tab.value==='cron') emit('change', cronStr.value)
90+
}, { deep: true })
91+
92+
// 标准格式
93+
const isoStr = ref('')
94+
const repeat = ref(1)
95+
const isoDate = ref('')
96+
const isoDuration = ref('')
97+
function setDuration(type, val) {
98+
// 组装ISO 8601字符串
99+
let d = isoDuration.value
100+
if (!d.includes(type)) d += val + type
101+
else d = d.replace(new RegExp(`\\d+${type}`), val + type)
102+
isoDuration.value = d
103+
updateIsoStr()
104+
}
105+
function updateIsoStr() {
106+
let str = `R${repeat.value}`
107+
if (isoDate.value) str += '/' + (typeof isoDate.value==='string'?isoDate.value:new Date(isoDate.value).toISOString())
108+
if (isoDuration.value) str += '/' + isoDuration.value
109+
isoStr.value = str
110+
if (tab.value==='iso') emit('change', isoStr.value)
111+
}
112+
watch([repeat, isoDate, isoDuration], updateIsoStr)
113+
watch(() => props.value, (val) => {
114+
if (!val) return
115+
if (tab.value==='cron') cronStr.value = val
116+
if (tab.value==='iso') isoStr.value = val
117+
}, { immediate: true })
118+
</script>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<template>
2+
<div>
3+
<div style="margin-bottom: 10px;">当前选择:<el-input v-model="isoString" readonly style="width: 300px;" /></div>
4+
<div v-for="unit in units" :key="unit.key" style="margin-bottom: 8px;">
5+
<span>{{ unit.label }}:</span>
6+
<el-button-group>
7+
<el-button v-for="val in unit.presets" :key="val" size="mini" @click="setUnit(unit.key, val)">{{ val }}</el-button>
8+
<el-input v-model.number="custom[unit.key]" size="mini" style="width: 60px; margin-left: 8px;" placeholder="自定义" @change="setUnit(unit.key, custom[unit.key])" />
9+
</el-button-group>
10+
</div>
11+
</div>
12+
</template>
13+
14+
<script setup>
15+
import { ref, watch, computed } from 'vue'
16+
const props = defineProps({ value: String })
17+
const emit = defineEmits(['change'])
18+
19+
const units = [
20+
{ key: 'Y', label: '', presets: [1, 2, 3, 4] },
21+
{ key: 'M', label: '', presets: [1, 2, 3, 4] },
22+
{ key: 'D', label: '', presets: [1, 2, 3, 4] },
23+
{ key: 'H', label: '', presets: [4, 8, 12, 24] },
24+
{ key: 'm', label: '', presets: [5, 10, 30, 50] },
25+
{ key: 'S', label: '', presets: [5, 10, 30, 50] }
26+
]
27+
const custom = ref({ Y: '', M: '', D: '', H: '', m: '', S: '' })
28+
const isoString = ref('')
29+
30+
function setUnit(key, val) {
31+
if (!val || isNaN(val)) {
32+
custom.value[key] = ''
33+
return
34+
}
35+
custom.value[key] = val
36+
updateIsoString()
37+
}
38+
39+
function updateIsoString() {
40+
let str = 'P'
41+
if (custom.value.Y) str += custom.value.Y + 'Y'
42+
if (custom.value.M) str += custom.value.M + 'M'
43+
if (custom.value.D) str += custom.value.D + 'D'
44+
if (custom.value.H || custom.value.m || custom.value.S) str += 'T'
45+
if (custom.value.H) str += custom.value.H + 'H'
46+
if (custom.value.m) str += custom.value.m + 'M'
47+
if (custom.value.S) str += custom.value.S + 'S'
48+
isoString.value = str === 'P' ? '' : str
49+
emit('change', isoString.value)
50+
}
51+
52+
watch(() => props.value, (val) => {
53+
if (!val) return
54+
// 解析ISO 8601字符串到custom
55+
const match = val.match(/^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/)
56+
if (match) {
57+
custom.value.Y = match[1] || ''
58+
custom.value.M = match[2] || ''
59+
custom.value.D = match[3] || ''
60+
custom.value.H = match[4] || ''
61+
custom.value.m = match[5] || ''
62+
custom.value.S = match[6] || ''
63+
updateIsoString()
64+
}
65+
}, { immediate: true })
66+
</script>

0 commit comments

Comments
 (0)