ChildProcessNodeConfig.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. <template>
  2. <el-drawer
  3. :append-to-body="true"
  4. v-model="settingVisible"
  5. :show-close="false"
  6. :size="550"
  7. :before-close="saveConfig"
  8. >
  9. <template #header>
  10. <div class="config-header">
  11. <input
  12. v-if="showInput"
  13. type="text"
  14. class="config-editable-input"
  15. @blur="blurEvent()"
  16. v-mountedFocus
  17. v-model="nodeName"
  18. :placeholder="nodeName"
  19. />
  20. <div v-else class="node-name">
  21. {{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
  22. </div>
  23. <div class="divide-line"></div>
  24. </div>
  25. </template>
  26. <el-tabs type="border-card" v-model="activeTabName">
  27. <el-tab-pane label="子流程" name="child">
  28. <div>
  29. <el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
  30. <el-form-item label="是否异步" prop="async">
  31. <el-switch v-model="configForm.async" active-text="异步" inactive-text="不异步" />
  32. </el-form-item>
  33. <el-form-item label="选择子流程" prop="calledProcessDefinitionKey">
  34. <el-select
  35. v-model="configForm.calledProcessDefinitionKey"
  36. clearable
  37. @change="handleCalledElementChange"
  38. >
  39. <el-option
  40. v-for="(item, index) in childProcessOptions"
  41. :key="index"
  42. :label="item.name"
  43. :value="item.key"
  44. />
  45. </el-select>
  46. </el-form-item>
  47. <el-form-item label="是否自动跳过子流程发起节点" prop="skipStartUserNode">
  48. <el-switch
  49. v-model="configForm.skipStartUserNode"
  50. active-text="跳过"
  51. inactive-text="不跳过"
  52. />
  53. </el-form-item>
  54. <el-form-item label="主→子变量传递" prop="inVariables">
  55. <div class="flex pt-2" v-for="(item, index) in configForm.inVariables" :key="index">
  56. <div class="mr-2">
  57. <el-form-item
  58. :prop="`inVariables.${index}.source`"
  59. :rules="{
  60. required: true,
  61. message: '变量不能为空',
  62. trigger: 'blur'
  63. }"
  64. >
  65. <el-select class="w-200px!" v-model="item.source">
  66. <el-option
  67. v-for="(field, fIdx) in formFieldOptions"
  68. :key="fIdx"
  69. :label="field.title"
  70. :value="field.field"
  71. />
  72. </el-select>
  73. </el-form-item>
  74. </div>
  75. <div class="mr-2">
  76. <el-form-item
  77. :prop="`inVariables.${index}.target`"
  78. :rules="{
  79. required: true,
  80. message: '变量不能为空',
  81. trigger: 'blur'
  82. }"
  83. >
  84. <el-select class="w-200px!" v-model="item.target">
  85. <el-option
  86. v-for="(field, fIdx) in childFormFieldOptions"
  87. :key="fIdx"
  88. :label="field.title"
  89. :value="field.field"
  90. />
  91. </el-select>
  92. </el-form-item>
  93. </div>
  94. <div class="mr-1 flex items-center">
  95. <Icon
  96. icon="ep:delete"
  97. :size="18"
  98. @click="deleteVariable(configForm.inVariables, index)"
  99. />
  100. </div>
  101. </div>
  102. <el-button type="primary" text @click="addVariable(configForm.inVariables)">
  103. <Icon icon="ep:plus" class="mr-5px" />添加一行
  104. </el-button>
  105. </el-form-item>
  106. <!-- TODO @lesan:async、source、target 几个字段,会告警 -->
  107. <el-form-item
  108. v-if="configForm.async === false"
  109. label="子→主变量传递"
  110. prop="outVariables"
  111. >
  112. <div class="flex pt-2" v-for="(item, index) in configForm.outVariables" :key="index">
  113. <div class="mr-2">
  114. <el-form-item
  115. :prop="`outVariables.${index}.source`"
  116. :rules="{
  117. required: true,
  118. message: '变量不能为空',
  119. trigger: 'blur'
  120. }"
  121. >
  122. <el-select class="w-200px!" v-model="item.source">
  123. <el-option
  124. v-for="(field, fIdx) in childFormFieldOptions"
  125. :key="fIdx"
  126. :label="field.title"
  127. :value="field.field"
  128. />
  129. </el-select>
  130. </el-form-item>
  131. </div>
  132. <div class="mr-2">
  133. <el-form-item
  134. :prop="`outVariables.${index}.target`"
  135. :rules="{
  136. required: true,
  137. message: '变量不能为空',
  138. trigger: 'blur'
  139. }"
  140. >
  141. <el-select class="w-200px!" v-model="item.target">
  142. <el-option
  143. v-for="(field, fIdx) in formFieldOptions"
  144. :key="fIdx"
  145. :label="field.title"
  146. :value="field.field"
  147. />
  148. </el-select>
  149. </el-form-item>
  150. </div>
  151. <div class="mr-1 flex items-center">
  152. <Icon
  153. icon="ep:delete"
  154. :size="18"
  155. @click="deleteVariable(configForm.outVariables, index)"
  156. />
  157. </div>
  158. </div>
  159. <el-button type="primary" text @click="addVariable(configForm.outVariables)">
  160. <Icon icon="ep:plus" class="mr-5px" />添加一行
  161. </el-button>
  162. </el-form-item>
  163. <!-- TODO @lesan:startUserType、startUserEmptyType 要不走写下枚举类? -->
  164. <el-form-item label="子流程发起人" prop="startUserType">
  165. <el-radio-group v-model="configForm.startUserType">
  166. <el-radio :value="1">同主流程发起人</el-radio>
  167. <el-radio :value="2">表单</el-radio>
  168. </el-radio-group>
  169. </el-form-item>
  170. <el-form-item
  171. v-if="configForm.startUserType === 2"
  172. label="当子流程发起人为空时"
  173. prop="startUserType"
  174. >
  175. <el-radio-group v-model="configForm.startUserEmptyType">
  176. <el-radio :value="1">同主流程发起人</el-radio>
  177. <el-radio :value="2">子流程管理员</el-radio>
  178. <el-radio :value="3">主流程管理员</el-radio>
  179. </el-radio-group>
  180. </el-form-item>
  181. <el-form-item
  182. v-if="configForm.startUserType === 2"
  183. label="发起人表单"
  184. prop="startUserFormField"
  185. >
  186. <el-select class="w-200px!" v-model="configForm.startUserFormField">
  187. <el-option
  188. v-for="(field, fIdx) in formFieldOptions"
  189. :key="fIdx"
  190. :label="field.title"
  191. :value="field.field"
  192. />
  193. </el-select>
  194. </el-form-item>
  195. <el-divider content-position="left">超时设置</el-divider>
  196. <el-form-item label="启用开关" prop="timeoutEnable">
  197. <el-switch
  198. v-model="configForm.timeoutEnable"
  199. active-text="开启"
  200. inactive-text="关闭"
  201. />
  202. </el-form-item>
  203. <div v-if="configForm.timeoutEnable">
  204. <el-form-item prop="timeoutType">
  205. <el-radio-group v-model="configForm.timeoutType">
  206. <el-radio-button
  207. v-for="item in DELAY_TYPE"
  208. :key="item.value"
  209. :label="item.label"
  210. :value="item.value"
  211. />
  212. </el-radio-group>
  213. </el-form-item>
  214. <el-form-item v-if="configForm.timeoutType === DelayTypeEnum.FIXED_TIME_DURATION">
  215. <el-form-item prop="timeDuration">
  216. <el-input-number
  217. class="mr-2"
  218. :style="{ width: '100px' }"
  219. v-model="configForm.timeDuration"
  220. :min="1"
  221. controls-position="right"
  222. />
  223. </el-form-item>
  224. <el-select v-model="configForm.timeUnit" class="mr-2" :style="{ width: '100px' }">
  225. <el-option
  226. v-for="item in TIME_UNIT_TYPES"
  227. :key="item.value"
  228. :label="item.label"
  229. :value="item.value"
  230. />
  231. </el-select>
  232. <el-text>后进入下一节点</el-text>
  233. </el-form-item>
  234. <el-form-item
  235. v-if="configForm.timeoutType === DelayTypeEnum.FIXED_DATE_TIME"
  236. prop="dateTime"
  237. >
  238. <el-date-picker
  239. class="mr-2"
  240. v-model="configForm.dateTime"
  241. type="datetime"
  242. placeholder="请选择日期和时间"
  243. value-format="YYYY-MM-DDTHH:mm:ss"
  244. />
  245. <el-text>后进入下一节点</el-text>
  246. </el-form-item>
  247. </div>
  248. </el-form>
  249. </div>
  250. </el-tab-pane>
  251. </el-tabs>
  252. <template #footer>
  253. <el-divider />
  254. <div>
  255. <el-button type="primary" @click="saveConfig">确 定</el-button>
  256. <el-button @click="closeDrawer">取 消</el-button>
  257. </div>
  258. </template>
  259. </el-drawer>
  260. </template>
  261. <script setup lang="ts">
  262. import { getModelList } from '@/api/bpm/model'
  263. import { getForm } from '@/api/bpm/form'
  264. import {
  265. SimpleFlowNode,
  266. NodeType,
  267. TIME_UNIT_TYPES,
  268. TimeUnitType,
  269. DelayTypeEnum,
  270. DELAY_TYPE
  271. } from '../consts'
  272. import { useWatchNode, useDrawer, useNodeName, useFormFieldsAndStartUser } from '../node'
  273. import { parseFormFields } from '@/components/FormCreate/src/utils'
  274. import { convertTimeUnit } from '../utils'
  275. defineOptions({
  276. name: 'ChildProcessNodeConfig'
  277. })
  278. const props = defineProps({
  279. flowNode: {
  280. type: Object as () => SimpleFlowNode,
  281. required: true
  282. }
  283. })
  284. // 抽屉配置
  285. const { settingVisible, closeDrawer, openDrawer } = useDrawer()
  286. // 当前节点
  287. const currentNode = useWatchNode(props)
  288. // 节点名称
  289. const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.CHILD_PROCESS_NODE)
  290. // 激活的 Tab 标签页
  291. const activeTabName = ref('child')
  292. // 子流程表单配置
  293. const formRef = ref() // 表单 Ref
  294. // 表单校验规则
  295. const formRules = reactive({
  296. async: [{ required: true, message: '是否异步不能为空', trigger: 'change' }],
  297. calledProcessDefinitionKey: [{ required: true, message: '子流程不能为空', trigger: 'change' }],
  298. skipStartUserNode: [
  299. { required: true, message: '是否自动跳过子流程发起节点不能为空', trigger: 'change' }
  300. ],
  301. startUserType: [{ required: true, message: '子流程发起人不能为空', trigger: 'change' }],
  302. startUserEmptyType: [
  303. { required: true, message: '当子流程发起人为空时不能为空', trigger: 'change' }
  304. ],
  305. startUserFormField: [{ required: true, message: '发起人表单不能为空', trigger: 'change' }],
  306. timeoutEnable: [{ required: true, message: '超时设置是否开启不能为空', trigger: 'change' }],
  307. timeoutType: [{ required: true, message: '超时设置时间不能为空', trigger: 'change' }],
  308. timeDuration: [{ required: true, message: '超时设置时间不能为空', trigger: 'change' }],
  309. dateTime: [{ required: true, message: '超时设置时间不能为空', trigger: 'change' }]
  310. })
  311. const configForm = ref({
  312. async: false,
  313. calledProcessDefinitionKey: '',
  314. skipStartUserNode: false,
  315. inVariables: [],
  316. outVariables: [],
  317. startUserType: 1,
  318. startUserEmptyType: 1,
  319. startUserFormField: '',
  320. timeoutEnable: false,
  321. timeoutType: DelayTypeEnum.FIXED_TIME_DURATION,
  322. timeDuration: 1,
  323. timeUnit: TimeUnitType.HOUR,
  324. dateTime: ''
  325. })
  326. const childProcessOptions = ref()
  327. const formFieldOptions = useFormFieldsAndStartUser()
  328. const childFormFieldOptions = ref()
  329. // 保存配置
  330. const saveConfig = async () => {
  331. activeTabName.value = 'child'
  332. if (!formRef) return false
  333. const valid = await formRef.value.validate()
  334. if (!valid) return false
  335. // TODO @lesan:这里的 option 黄色告警,也处理下哈
  336. const childInfo = childProcessOptions.value.find(
  337. (option) => option.key === configForm.value.calledProcessDefinitionKey
  338. )
  339. currentNode.value.name = nodeName.value!
  340. if (currentNode.value.childProcessSetting) {
  341. // 1. 是否异步
  342. currentNode.value.childProcessSetting.async = configForm.value.async
  343. // 2. 调用流程
  344. currentNode.value.childProcessSetting.calledProcessDefinitionKey = childInfo.key
  345. currentNode.value.childProcessSetting.calledProcessDefinitionName = childInfo.name
  346. // 3. 是否跳过发起人
  347. currentNode.value.childProcessSetting.skipStartUserNode = configForm.value.skipStartUserNode
  348. // 4. 主->子变量
  349. currentNode.value.childProcessSetting.inVariables = configForm.value.inVariables
  350. // 5. 子->主变量
  351. currentNode.value.childProcessSetting.outVariables = configForm.value.outVariables
  352. // 6. 发起人设置
  353. currentNode.value.childProcessSetting.startUserSetting.type = configForm.value.startUserType
  354. currentNode.value.childProcessSetting.startUserSetting.emptyType =
  355. configForm.value.startUserEmptyType
  356. currentNode.value.childProcessSetting.startUserSetting.formField =
  357. configForm.value.startUserFormField
  358. // 7. 超时设置
  359. currentNode.value.childProcessSetting.timeoutSetting = {
  360. enable: configForm.value.timeoutEnable
  361. }
  362. if (configForm.value.timeoutEnable) {
  363. currentNode.value.childProcessSetting.timeoutSetting.type = configForm.value.timeoutType
  364. if (configForm.value.timeoutType === DelayTypeEnum.FIXED_TIME_DURATION) {
  365. currentNode.value.childProcessSetting.timeoutSetting.timeExpression = getIsoTimeDuration()
  366. }
  367. if (configForm.value.timeoutType === DelayTypeEnum.FIXED_DATE_TIME) {
  368. currentNode.value.childProcessSetting.timeoutSetting.timeExpression =
  369. configForm.value.dateTime
  370. }
  371. }
  372. }
  373. currentNode.value.showText = `调用子流程:${childInfo.name}`
  374. settingVisible.value = false
  375. return true
  376. }
  377. // 显示子流程节点配置, 由父组件传过来
  378. // TODO @lesan:inVariables、outVariables 红色告警
  379. const showChildProcessNodeConfig = (node: SimpleFlowNode) => {
  380. nodeName.value = node.name
  381. if (node.childProcessSetting) {
  382. // 1. 是否异步
  383. configForm.value.async = node.childProcessSetting.async
  384. // 2. 调用流程
  385. configForm.value.calledProcessDefinitionKey =
  386. node.childProcessSetting?.calledProcessDefinitionKey
  387. // 3. 是否跳过发起人
  388. configForm.value.skipStartUserNode = node.childProcessSetting.skipStartUserNode
  389. // 4. 主->子变量
  390. configForm.value.inVariables = node.childProcessSetting.inVariables
  391. // 5. 子->主变量
  392. configForm.value.outVariables = node.childProcessSetting.outVariables
  393. // 6. 发起人设置
  394. configForm.value.startUserType = node.childProcessSetting.startUserSetting.type
  395. configForm.value.startUserEmptyType = node.childProcessSetting.startUserSetting.emptyType ?? 1
  396. configForm.value.startUserFormField = node.childProcessSetting.startUserSetting.formField ?? ''
  397. // 7. 超时设置
  398. configForm.value.timeoutEnable = node.childProcessSetting.timeoutSetting.enable ?? false
  399. if (configForm.value.timeoutEnable) {
  400. configForm.value.timeoutType =
  401. node.childProcessSetting.timeoutSetting.type ?? DelayTypeEnum.FIXED_TIME_DURATION
  402. // 固定时长
  403. if (configForm.value.timeoutType === DelayTypeEnum.FIXED_TIME_DURATION) {
  404. const strTimeDuration = node.childProcessSetting.timeoutSetting.timeExpression ?? ''
  405. let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
  406. let parseTimeUnit = strTimeDuration.slice(strTimeDuration.length - 1)
  407. configForm.value.timeDuration = parseInt(parseTime)
  408. configForm.value.timeUnit = convertTimeUnit(parseTimeUnit)
  409. }
  410. // 固定日期时间
  411. if (configForm.value.timeoutType === DelayTypeEnum.FIXED_DATE_TIME) {
  412. configForm.value.dateTime = node.childProcessSetting.timeoutSetting.timeExpression ?? ''
  413. }
  414. }
  415. }
  416. loadFormInfo()
  417. }
  418. defineExpose({ openDrawer, showChildProcessNodeConfig }) // 暴露方法给父组件
  419. // TODO @lesan:这里的 arr 黄色告警,也处理下哈,可以用 cursor quick fix 哈
  420. const addVariable = (arr) => {
  421. arr.push({
  422. source: '',
  423. target: ''
  424. })
  425. }
  426. const deleteVariable = (arr, index: number) => {
  427. arr.splice(index, 1)
  428. }
  429. const handleCalledElementChange = () => {
  430. configForm.value.inVariables = []
  431. configForm.value.outVariables = []
  432. loadFormInfo()
  433. }
  434. const loadFormInfo = async () => {
  435. const childInfo = childProcessOptions.value.find(
  436. (option) => option.key === configForm.value.calledProcessDefinitionKey
  437. )
  438. const formInfo = await getForm(childInfo.formId)
  439. childFormFieldOptions.value = []
  440. if (formInfo.fields) {
  441. formInfo.fields.forEach((fieldStr: string) => {
  442. parseFormFields(JSON.parse(fieldStr), childFormFieldOptions.value)
  443. })
  444. }
  445. }
  446. const getIsoTimeDuration = () => {
  447. let strTimeDuration = 'PT'
  448. if (configForm.value.timeUnit === TimeUnitType.MINUTE) {
  449. strTimeDuration += configForm.value.timeDuration + 'M'
  450. }
  451. if (configForm.value.timeUnit === TimeUnitType.HOUR) {
  452. strTimeDuration += configForm.value.timeDuration + 'H'
  453. }
  454. if (configForm.value.timeUnit === TimeUnitType.DAY) {
  455. strTimeDuration += configForm.value.timeDuration + 'D'
  456. }
  457. return strTimeDuration
  458. }
  459. onMounted(async () => {
  460. childProcessOptions.value = await getModelList(undefined)
  461. })
  462. </script>
  463. <style lang="scss" scoped></style>