TimeEventConfig.vue 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. <template>
  2. <div class="panel-tab__content">
  3. <div style="margin-top: 10px">
  4. <span>类型:</span>
  5. <el-button-group>
  6. <el-button size="mini" :type="type === 'time' ? 'primary' : ''" @click="setType('time')"
  7. >时间</el-button
  8. >
  9. <el-button
  10. size="mini"
  11. :type="type === 'duration' ? 'primary' : ''"
  12. @click="setType('duration')"
  13. >持续</el-button
  14. >
  15. <el-button size="mini" :type="type === 'cycle' ? 'primary' : ''" @click="setType('cycle')"
  16. >循环</el-button
  17. >
  18. </el-button-group>
  19. <el-icon v-if="valid" color="green" style="margin-left: 8px"><CircleCheckFilled /></el-icon>
  20. </div>
  21. <div style="margin-top: 10px; display: flex; align-items: center">
  22. <span>条件:</span>
  23. <el-input
  24. v-model="condition"
  25. :placeholder="placeholder"
  26. style="width: calc(100% - 100px)"
  27. :readonly="type !== 'duration' && type !== 'cycle'"
  28. @focus="handleInputFocus"
  29. @blur="updateNode"
  30. >
  31. <template #suffix>
  32. <el-tooltip v-if="!valid" content="格式错误" placement="top">
  33. <el-icon color="orange"><WarningFilled /></el-icon>
  34. </el-tooltip>
  35. <el-tooltip :content="helpText" placement="top">
  36. <el-icon color="#409EFF" style="cursor: pointer" @click="showHelp = true"
  37. ><QuestionFilled
  38. /></el-icon>
  39. </el-tooltip>
  40. <el-button
  41. v-if="type === 'time'"
  42. @click="showDatePicker = true"
  43. style="margin-left: 4px"
  44. circle
  45. size="small"
  46. >
  47. <Icon icon="ep:calendar" />
  48. </el-button>
  49. <el-button
  50. v-if="type === 'duration'"
  51. @click="showDurationDialog = true"
  52. style="margin-left: 4px"
  53. circle
  54. size="small"
  55. >
  56. <Icon icon="ep:timer" />
  57. </el-button>
  58. <el-button
  59. v-if="type === 'cycle'"
  60. @click="showCycleDialog = true"
  61. style="margin-left: 4px"
  62. circle
  63. size="small"
  64. >
  65. <Icon icon="ep:setting" />
  66. </el-button>
  67. </template>
  68. </el-input>
  69. </div>
  70. <!-- 时间选择器 -->
  71. <el-dialog
  72. v-model="showDatePicker"
  73. title="选择时间"
  74. width="400px"
  75. @close="showDatePicker = false"
  76. >
  77. <el-date-picker
  78. v-model="dateValue"
  79. type="datetime"
  80. placeholder="选择日期时间"
  81. style="width: 100%"
  82. @change="onDateChange"
  83. />
  84. <template #footer>
  85. <el-button @click="showDatePicker = false">取消</el-button>
  86. <el-button type="primary" @click="onDateConfirm">确定</el-button>
  87. </template>
  88. </el-dialog>
  89. <!-- 持续时长选择器 -->
  90. <el-dialog
  91. v-model="showDurationDialog"
  92. title="时间配置"
  93. width="600px"
  94. @close="showDurationDialog = false"
  95. >
  96. <DurationConfig :value="condition" @change="onDurationChange" />
  97. <template #footer>
  98. <el-button @click="showDurationDialog = false">取消</el-button>
  99. <el-button type="primary" @click="onDurationConfirm">确定</el-button>
  100. </template>
  101. </el-dialog>
  102. <!-- 循环配置器 -->
  103. <el-dialog
  104. v-model="showCycleDialog"
  105. title="时间配置"
  106. width="800px"
  107. @close="showCycleDialog = false"
  108. >
  109. <CycleConfig :value="condition" @change="onCycleChange" />
  110. <template #footer>
  111. <el-button @click="showCycleDialog = false">取消</el-button>
  112. <el-button type="primary" @click="onCycleConfirm">确定</el-button>
  113. </template>
  114. </el-dialog>
  115. <!-- 帮助说明 -->
  116. <el-dialog v-model="showHelp" title="格式说明" width="600px" @close="showHelp = false">
  117. <div v-html="helpHtml"></div>
  118. <template #footer>
  119. <el-button @click="showHelp = false">关闭</el-button>
  120. </template>
  121. </el-dialog>
  122. </div>
  123. </template>
  124. <script lang="ts" setup>
  125. import { ref, computed, watch, onMounted } from 'vue'
  126. import { CircleCheckFilled, WarningFilled, QuestionFilled } from '@element-plus/icons-vue'
  127. import DurationConfig from './DurationConfig.vue'
  128. import CycleConfig from './CycleConfig.vue'
  129. import { createListenerObject, updateElementExtensions } from '../../utils'
  130. const bpmnInstances = () => (window as any).bpmnInstances
  131. const props = defineProps({ businessObject: Object })
  132. const type = ref('time')
  133. const condition = ref('')
  134. const valid = ref(true)
  135. const showDatePicker = ref(false)
  136. const showDurationDialog = ref(false)
  137. const showCycleDialog = ref(false)
  138. const showHelp = ref(false)
  139. const dateValue = ref(null)
  140. const bpmnElement = ref(null)
  141. const placeholder = computed(() => {
  142. if (type.value === 'time') return '请输入时间'
  143. if (type.value === 'duration') return '请输入持续时长'
  144. if (type.value === 'cycle') return '请输入循环表达式'
  145. return ''
  146. })
  147. const helpText = computed(() => {
  148. if (type.value === 'time') return '选择具体时间'
  149. if (type.value === 'duration') return 'ISO 8601格式,如PT1H'
  150. if (type.value === 'cycle') return 'CRON表达式或ISO 8601周期'
  151. return ''
  152. })
  153. const helpHtml = computed(() => {
  154. if (type.value === 'duration') {
  155. return `指定定时器之前要等待多长时间。S表示秒,M表示分,D表示天;P表示时间段,T表示精确到时间的时间段。<br>
  156. 时间格式依然为ISO 8601格式,一年两个月三天四小时五分六秒内,可以写成P1Y2M3DT4H5M6S。<br>
  157. P是开始标记,T是时间和日期分割标记,没有日期只有时间T是不能省去的,比如1小时执行一次应写成PT1H。`
  158. }
  159. if (type.value === 'cycle') {
  160. return `支持CRON表达式(如0 0/30 * * * ?)或ISO 8601周期(如R3/PT10M)。`
  161. }
  162. return ''
  163. })
  164. // 初始化和监听
  165. function syncFromBusinessObject() {
  166. if (props.businessObject) {
  167. const timerDef = (props.businessObject.eventDefinitions || [])[0]
  168. if (timerDef) {
  169. if (timerDef.timeDate) {
  170. type.value = 'time'
  171. condition.value = timerDef.timeDate.body
  172. } else if (timerDef.timeDuration) {
  173. type.value = 'duration'
  174. condition.value = timerDef.timeDuration.body
  175. } else if (timerDef.timeCycle) {
  176. type.value = 'cycle'
  177. condition.value = timerDef.timeCycle.body
  178. }
  179. }
  180. }
  181. }
  182. onMounted(syncFromBusinessObject)
  183. // 切换类型
  184. function setType(t) {
  185. type.value = t
  186. condition.value = ''
  187. updateNode()
  188. }
  189. // 输入校验
  190. watch([type, condition], () => {
  191. valid.value = validate()
  192. // updateNode() // 可以注释掉,避免频繁触发
  193. })
  194. function validate() {
  195. if (type.value === 'time') {
  196. return !!condition.value && !isNaN(Date.parse(condition.value))
  197. }
  198. if (type.value === 'duration') {
  199. return /^P.*$/.test(condition.value)
  200. }
  201. if (type.value === 'cycle') {
  202. return /^([0-9*\/?, ]+|R\d*\/P.*)$/.test(condition.value)
  203. }
  204. return true
  205. }
  206. // 选择时间
  207. function onDateChange(val) {
  208. dateValue.value = val
  209. }
  210. function onDateConfirm() {
  211. if (dateValue.value) {
  212. condition.value = new Date(dateValue.value).toISOString()
  213. showDatePicker.value = false
  214. updateNode()
  215. }
  216. }
  217. // 持续时长
  218. function onDurationChange(val) {
  219. condition.value = val
  220. }
  221. function onDurationConfirm() {
  222. showDurationDialog.value = false
  223. updateNode()
  224. }
  225. // 循环
  226. function onCycleChange(val) {
  227. condition.value = val
  228. }
  229. function onCycleConfirm() {
  230. showCycleDialog.value = false
  231. updateNode()
  232. }
  233. // 输入框聚焦时弹窗(可选)
  234. function handleInputFocus() {
  235. if (type.value === 'time') showDatePicker.value = true
  236. if (type.value === 'duration') showDurationDialog.value = true
  237. if (type.value === 'cycle') showCycleDialog.value = true
  238. }
  239. // 同步到节点
  240. function updateNode() {
  241. const moddle = window.bpmnInstances?.moddle
  242. const modeling = window.bpmnInstances?.modeling
  243. const elementRegistry = window.bpmnInstances?.elementRegistry
  244. if (!moddle || !modeling || !elementRegistry) return
  245. // 获取元素
  246. if (!props.businessObject || !props.businessObject.id) return
  247. const element = elementRegistry.get(props.businessObject.id)
  248. if (!element) return
  249. // 1. 复用原有 timerDef,或新建
  250. let timerDef =
  251. element.businessObject.eventDefinitions && element.businessObject.eventDefinitions[0]
  252. if (!timerDef) {
  253. timerDef = bpmnInstances().bpmnFactory.create('bpmn:TimerEventDefinition', {})
  254. modeling.updateProperties(element, {
  255. eventDefinitions: [timerDef]
  256. })
  257. }
  258. // 2. 清空原有
  259. delete timerDef.timeDate
  260. delete timerDef.timeDuration
  261. delete timerDef.timeCycle
  262. // 3. 设置新的
  263. if (type.value === 'time' && condition.value) {
  264. timerDef.timeDate = bpmnInstances().bpmnFactory.create('bpmn:FormalExpression', {
  265. body: condition.value
  266. })
  267. } else if (type.value === 'duration' && condition.value) {
  268. timerDef.timeDuration = bpmnInstances().bpmnFactory.create('bpmn:FormalExpression', {
  269. body: condition.value
  270. })
  271. } else if (type.value === 'cycle' && condition.value) {
  272. timerDef.timeCycle = bpmnInstances().bpmnFactory.create('bpmn:FormalExpression', {
  273. body: condition.value
  274. })
  275. }
  276. bpmnInstances().modeling.updateProperties(toRaw(element), {
  277. eventDefinitions: [timerDef]
  278. })
  279. }
  280. watch(
  281. () => props.businessObject,
  282. (val) => {
  283. if (val) {
  284. nextTick(() => {
  285. syncFromBusinessObject()
  286. })
  287. }
  288. },
  289. { immediate: true }
  290. )
  291. </script>
  292. <style scoped>
  293. /* 相关样式 */
  294. </style>