feat: 重构自定义配置,支持对不同类型节点绩效配置
							parent
							
								
									886f3391aa
								
							
						
					
					
						commit
						3c7526de2f
					
				|  | @ -54,8 +54,8 @@ | |||
|         <template #title><Icon icon="ep:promotion" />其他</template> | ||||
|         <element-other-config :id="elementId" /> | ||||
|       </el-collapse-item> | ||||
|       <el-collapse-item name="customConfig" v-if="elementType.indexOf('Task') !== -1" key="customConfig"> | ||||
|         <template #title><Icon icon="ep:circle-plus-filled" />自定义配置</template> | ||||
|       <el-collapse-item name="customConfig" key="customConfig"> | ||||
|         <template #title><Icon icon="ep:tools" />自定义配置</template> | ||||
|         <element-custom-config :id="elementId" :type="elementType" /> | ||||
|       </el-collapse-item> | ||||
|     </el-collapse> | ||||
|  |  | |||
|  | @ -1,656 +1,41 @@ | |||
| <!-- UserTask 自定义配置: | ||||
|      1. 审批人与提交人为同一人时 | ||||
|      2. 审批人拒绝时 | ||||
|      3. 审批人为空时 | ||||
|      4. 操作按钮 | ||||
|      5. 字段权限 | ||||
| --> | ||||
| <template> | ||||
|   <div class="panel-tab__content"> | ||||
|     <el-divider content-position="left">审批人拒绝时</el-divider> | ||||
|     <el-form-item prop="rejectHandlerType"> | ||||
|       <el-radio-group | ||||
|         v-model="rejectHandlerType" | ||||
|         :disabled="returnTaskList.length === 0" | ||||
|         @change="updateRejectHandlerType" | ||||
|       > | ||||
|         <div class="flex-col"> | ||||
|           <div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index"> | ||||
|             <el-radio :key="item.value" :value="item.value" :label="item.label" /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </el-radio-group> | ||||
|     </el-form-item> | ||||
|     <el-form-item | ||||
|       v-if="rejectHandlerType == RejectHandlerType.RETURN_USER_TASK" | ||||
|       label="驳回节点" | ||||
|       prop="returnNodeId" | ||||
|     > | ||||
|       <el-select v-model="returnNodeId" clearable style="width: 100%" @change="updateReturnNodeId"> | ||||
|         <el-option | ||||
|           v-for="item in returnTaskList" | ||||
|           :key="item.id" | ||||
|           :label="item.name" | ||||
|           :value="item.id" | ||||
|         /> | ||||
|       </el-select> | ||||
|     </el-form-item> | ||||
| 
 | ||||
|     <el-divider content-position="left">审批人为空时</el-divider> | ||||
|     <el-form-item prop="assignEmptyHandlerType"> | ||||
|       <el-radio-group v-model="assignEmptyHandlerType" @change="updateAssignEmptyHandlerType"> | ||||
|         <div class="flex-col"> | ||||
|           <div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index"> | ||||
|             <el-radio :key="item.value" :value="item.value" :label="item.label" /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </el-radio-group> | ||||
|     </el-form-item> | ||||
|     <el-form-item | ||||
|       v-if="assignEmptyHandlerType == AssignEmptyHandlerType.ASSIGN_USER" | ||||
|       label="指定用户" | ||||
|       prop="assignEmptyHandlerUserIds" | ||||
|       span="24" | ||||
|     > | ||||
|       <el-select | ||||
|         v-model="assignEmptyUserIds" | ||||
|         clearable | ||||
|         multiple | ||||
|         style="width: 100%" | ||||
|         @change="updateAssignEmptyUserIds" | ||||
|       > | ||||
|         <el-option | ||||
|           v-for="item in userOptions" | ||||
|           :key="item.id" | ||||
|           :label="item.nickname" | ||||
|           :value="item.id" | ||||
|         /> | ||||
|       </el-select> | ||||
|     </el-form-item> | ||||
| 
 | ||||
|     <el-divider content-position="left">审批人与提交人为同一人时</el-divider> | ||||
|     <el-radio-group v-model="assignStartUserHandlerType" @change="updateAssignStartUserHandlerType"> | ||||
|       <div class="flex-col"> | ||||
|         <div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index"> | ||||
|           <el-radio :key="item.value" :value="item.value" :label="item.label" /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </el-radio-group> | ||||
| 
 | ||||
|     <el-divider content-position="left">操作按钮</el-divider> | ||||
|     <div class="button-setting-pane"> | ||||
|       <div class="button-setting-title"> | ||||
|         <div class="button-title-label">操作按钮</div> | ||||
|         <div class="pl-4 button-title-label">显示名称</div> | ||||
|         <div class="button-title-label">启用</div> | ||||
|       </div> | ||||
|       <div class="button-setting-item" v-for="(item, index) in buttonsSettingEl" :key="index"> | ||||
|         <div class="button-setting-item-label"> {{ OPERATION_BUTTON_NAME.get(item.id) }} </div> | ||||
|         <div class="button-setting-item-label"> | ||||
|           <input | ||||
|             type="text" | ||||
|             class="editable-title-input" | ||||
|             @blur="btnDisplayNameBlurEvent(index)" | ||||
|             v-mountedFocus | ||||
|             v-model="item.displayName" | ||||
|             :placeholder="item.displayName" | ||||
|             v-if="btnDisplayNameEdit[index]" | ||||
|           /> | ||||
|           <el-button v-else text @click="changeBtnDisplayName(index)" | ||||
|             >{{ item.displayName }}  <Icon icon="ep:edit" | ||||
|           /></el-button> | ||||
|         </div> | ||||
|         <div class="button-setting-item-label"> | ||||
|           <el-switch v-model="item.enable" /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <el-divider content-position="left">字段权限</el-divider> | ||||
|     <div class="field-setting-pane" v-if="formType === 10"> | ||||
|       <div class="field-permit-title"> | ||||
|         <div class="setting-title-label first-title"> 字段名称 </div> | ||||
|         <div class="other-titles"> | ||||
|           <span class="setting-title-label">只读</span> | ||||
|           <span class="setting-title-label">可编辑</span> | ||||
|           <span class="setting-title-label">隐藏</span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="field-setting-item" v-for="(item, index) in fieldsPermissionEl" :key="index"> | ||||
|         <div class="field-setting-item-label"> {{ item.title }} </div> | ||||
|         <el-radio-group class="field-setting-item-group" v-model="item.permission"> | ||||
|           <div class="item-radio-wrap"> | ||||
|             <el-radio | ||||
|               :value="FieldPermissionType.READ" | ||||
|               size="large" | ||||
|               :label="FieldPermissionType.READ" | ||||
|               ><span></span | ||||
|             ></el-radio> | ||||
|           </div> | ||||
|           <div class="item-radio-wrap"> | ||||
|             <el-radio | ||||
|               :value="FieldPermissionType.WRITE" | ||||
|               size="large" | ||||
|               :label="FieldPermissionType.WRITE" | ||||
|               ><span></span | ||||
|             ></el-radio> | ||||
|           </div> | ||||
|           <div class="item-radio-wrap"> | ||||
|             <el-radio | ||||
|               :value="FieldPermissionType.NONE" | ||||
|               size="large" | ||||
|               :label="FieldPermissionType.NONE" | ||||
|               ><span></span | ||||
|             ></el-radio> | ||||
|           </div> | ||||
|         </el-radio-group> | ||||
|       </div> | ||||
|     </div> | ||||
|     <component :is="customConfigComponent" v-bind="$props" /> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { | ||||
|   ASSIGN_START_USER_HANDLER_TYPES, | ||||
|   RejectHandlerType, | ||||
|   REJECT_HANDLER_TYPES, | ||||
|   ASSIGN_EMPTY_HANDLER_TYPES, | ||||
|   AssignEmptyHandlerType, | ||||
|   OPERATION_BUTTON_NAME, | ||||
|   DEFAULT_BUTTON_SETTING, | ||||
|   FieldPermissionType | ||||
| } from '@/components/SimpleProcessDesignerV2/src/consts' | ||||
| import * as UserApi from '@/api/system/user' | ||||
| import { cloneDeep } from 'lodash-es' | ||||
| import { CustomConfigMap } from './data' | ||||
| 
 | ||||
| defineOptions({ name: 'ElementCustomConfig' }) | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   id: String, | ||||
|   type: String | ||||
| }) | ||||
| const prefix = inject('prefix') | ||||
| 
 | ||||
| // 审批人与提交人为同一人时 | ||||
| const assignStartUserHandlerTypeEl = ref() | ||||
| const assignStartUserHandlerType = ref() | ||||
| 
 | ||||
| // 审批人拒绝时 | ||||
| const rejectHandlerTypeEl = ref() | ||||
| const rejectHandlerType = ref() | ||||
| const returnNodeIdEl = ref() | ||||
| const returnNodeId = ref() | ||||
| const returnTaskList = ref([]) | ||||
| 
 | ||||
| // 审批人为空时 | ||||
| const assignEmptyHandlerTypeEl = ref() | ||||
| const assignEmptyHandlerType = ref() | ||||
| const assignEmptyUserIdsEl = ref() | ||||
| const assignEmptyUserIds = ref() | ||||
| 
 | ||||
| // 操作按钮 | ||||
| const buttonsSettingEl = ref() | ||||
| const { buttonsSetting, btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } = | ||||
|   useButtonsSetting() | ||||
| 
 | ||||
| // 字段权限 | ||||
| const fieldsPermissionEl = ref() | ||||
| const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission( | ||||
|   FieldPermissionType.READ | ||||
| ) | ||||
| 
 | ||||
| const elExtensionElements = ref() | ||||
| const otherExtensions = ref() | ||||
| const bpmnElement = ref() | ||||
| const bpmnInstances = () => (window as any)?.bpmnInstances | ||||
| 
 | ||||
| const resetCustomConfigList = () => { | ||||
|   bpmnElement.value = bpmnInstances().bpmnElement | ||||
| 
 | ||||
|   // 获取可回退的列表 | ||||
|   returnTaskList.value = findAllPredecessorsExcludingStart( | ||||
|     bpmnElement.value.id, | ||||
|     bpmnInstances().modeler | ||||
|   ) | ||||
| 
 | ||||
|   // 获取元素扩展属性 或者 创建扩展属性 | ||||
|   elExtensionElements.value = | ||||
|     bpmnElement.value.businessObject?.extensionElements ?? | ||||
|     bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] }) | ||||
| 
 | ||||
|   // 审批人与提交人为同一人时 | ||||
|   assignStartUserHandlerTypeEl.value = | ||||
|     elExtensionElements.value.values?.filter( | ||||
|       (ex) => ex.$type === `${prefix}:AssignStartUserHandlerType` | ||||
|     )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, { value: 1 }) | ||||
|   assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value | ||||
| 
 | ||||
|   // 审批人拒绝时 | ||||
|   rejectHandlerTypeEl.value = | ||||
|     elExtensionElements.value.values?.filter( | ||||
|       (ex) => ex.$type === `${prefix}:RejectHandlerType` | ||||
|     )?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 }) | ||||
|   rejectHandlerType.value = rejectHandlerTypeEl.value.value | ||||
|   returnNodeIdEl.value = | ||||
|     elExtensionElements.value.values?.filter( | ||||
|       (ex) => ex.$type === `${prefix}:RejectReturnTaskId` | ||||
|     )?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, { value: '' }) | ||||
|   returnNodeId.value = returnNodeIdEl.value.value | ||||
| 
 | ||||
|   // 审批人为空时 | ||||
|   assignEmptyHandlerTypeEl.value = | ||||
|     elExtensionElements.value.values?.filter( | ||||
|       (ex) => ex.$type === `${prefix}:AssignEmptyHandlerType` | ||||
|     )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, { value: 1 }) | ||||
|   assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value | ||||
|   assignEmptyUserIdsEl.value = | ||||
|     elExtensionElements.value.values?.filter( | ||||
|       (ex) => ex.$type === `${prefix}:AssignEmptyUserIds` | ||||
|     )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, { value: '' }) | ||||
|   assignEmptyUserIds.value = assignEmptyUserIdsEl.value.value.split(',').map((item) => { | ||||
|     // 如果数字超出了最大安全整数范围,则将其作为字符串处理 | ||||
|     let num = Number(item) | ||||
|     return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num | ||||
|   }) | ||||
| 
 | ||||
|   // 操作按钮 | ||||
|   buttonsSettingEl.value = elExtensionElements.value.values?.filter( | ||||
|     (ex) => ex.$type === `${prefix}:ButtonsSetting` | ||||
|   ) | ||||
|   if (buttonsSettingEl.value.length === 0) { | ||||
|     DEFAULT_BUTTON_SETTING.forEach((item) => { | ||||
|       buttonsSettingEl.value.push( | ||||
|         bpmnInstances().moddle.create(`${prefix}:ButtonsSetting`, { | ||||
|           'flowable:id': item.id, | ||||
|           'flowable:displayName': item.displayName, | ||||
|           'flowable:enable': item.enable | ||||
|         }) | ||||
|       ) | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   // 字段权限 | ||||
|   if (formType.value === 10) { | ||||
|     fieldsPermissionEl.value = elExtensionElements.value.values?.filter( | ||||
|       (ex) => ex.$type === `${prefix}:FieldsPermission` | ||||
|     ) | ||||
|     if (fieldsPermissionEl.value.length === 0) { | ||||
|       getNodeConfigFormFields() | ||||
|       fieldsPermissionConfig.value.forEach((el) => { | ||||
|         fieldsPermissionEl.value.push( | ||||
|           bpmnInstances().moddle.create(`${prefix}:FieldsPermission`, el) | ||||
|         ) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // 保留剩余扩展元素,便于后面更新该元素对应属性 | ||||
|   otherExtensions.value = | ||||
|     elExtensionElements.value.values?.filter( | ||||
|       (ex) => | ||||
|         ex.$type !== `${prefix}:AssignStartUserHandlerType` && | ||||
|         ex.$type !== `${prefix}:RejectHandlerType` && | ||||
|         ex.$type !== `${prefix}:RejectReturnTaskId` && | ||||
|         ex.$type !== `${prefix}:AssignEmptyHandlerType` && | ||||
|         ex.$type !== `${prefix}:AssignEmptyUserIds` && | ||||
|         ex.$type !== `${prefix}:ButtonsSetting` && | ||||
|         ex.$type !== `${prefix}:FieldsPermission` | ||||
|     ) ?? [] | ||||
| 
 | ||||
|   // 更新元素扩展属性,避免后续报错 | ||||
|   updateElementExtensions() | ||||
| } | ||||
| 
 | ||||
| const updateAssignStartUserHandlerType = () => { | ||||
|   assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value | ||||
| 
 | ||||
|   updateElementExtensions() | ||||
| } | ||||
| 
 | ||||
| const updateRejectHandlerType = () => { | ||||
|   rejectHandlerTypeEl.value.value = rejectHandlerType.value | ||||
| 
 | ||||
|   returnNodeId.value = returnTaskList.value[0].id | ||||
|   returnNodeIdEl.value.value = returnNodeId.value | ||||
| 
 | ||||
|   updateElementExtensions() | ||||
| } | ||||
| 
 | ||||
| const updateReturnNodeId = () => { | ||||
|   returnNodeIdEl.value.value = returnNodeId.value | ||||
| 
 | ||||
|   updateElementExtensions() | ||||
| } | ||||
| 
 | ||||
| const updateAssignEmptyHandlerType = () => { | ||||
|   assignEmptyHandlerTypeEl.value.value = assignEmptyHandlerType.value | ||||
| 
 | ||||
|   updateElementExtensions() | ||||
| } | ||||
| 
 | ||||
| const updateAssignEmptyUserIds = () => { | ||||
|   assignEmptyUserIdsEl.value.value = assignEmptyUserIds.value.toString() | ||||
| 
 | ||||
|   updateElementExtensions() | ||||
| } | ||||
| 
 | ||||
| const updateElementExtensions = () => { | ||||
|   const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', { | ||||
|     values: [ | ||||
|       ...otherExtensions.value, | ||||
|       assignStartUserHandlerTypeEl.value, | ||||
|       rejectHandlerTypeEl.value, | ||||
|       returnNodeIdEl.value, | ||||
|       assignEmptyHandlerTypeEl.value, | ||||
|       assignEmptyUserIdsEl.value, | ||||
|       ...buttonsSettingEl.value, | ||||
|       ...fieldsPermissionEl.value | ||||
|     ] | ||||
|   }) | ||||
|   bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { | ||||
|     extensionElements: extensions | ||||
|   }) | ||||
| } | ||||
| const customConfigComponent = ref<any>(null) | ||||
| 
 | ||||
| watch( | ||||
|   () => props.id, | ||||
|   (val) => { | ||||
|     val && | ||||
|       val.length && | ||||
|       nextTick(() => { | ||||
|         resetCustomConfigList() | ||||
|       }) | ||||
|   () => props.type, | ||||
|   () => { | ||||
|     if (props.type) { | ||||
|       const element = bpmnInstances().bpmnElement.businessObject | ||||
|       let elementType = props.type | ||||
|       if (element.eventDefinitions) { | ||||
|         // 处理类似共用BoundaryEvent类型的TimerEvent | ||||
|         elementType += element.eventDefinitions[0].$type.split(':')[1] | ||||
|       } | ||||
|       const config = CustomConfigMap[elementType] | ||||
|       if (config) { | ||||
|         customConfigComponent.value = config.componet | ||||
|         return | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { immediate: true } | ||||
| ) | ||||
| 
 | ||||
| function findAllPredecessorsExcludingStart(elementId, modeler) { | ||||
|   const elementRegistry = modeler.get('elementRegistry') | ||||
|   const allConnections = elementRegistry.filter((element) => element.type === 'bpmn:SequenceFlow') | ||||
|   const predecessors = new Set() // 使用 Set 来避免重复节点 | ||||
|   const visited = new Set() // 用于记录已访问的节点 | ||||
| 
 | ||||
|   // 检查是否是开始事件节点 | ||||
|   function isStartEvent(element) { | ||||
|     return element.type === 'bpmn:StartEvent' | ||||
|   } | ||||
| 
 | ||||
|   function findPredecessorsRecursively(element) { | ||||
|     // 如果该节点已经访问过,直接返回,避免循环 | ||||
|     if (visited.has(element)) { | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     // 标记当前节点为已访问 | ||||
|     visited.add(element) | ||||
| 
 | ||||
|     // 获取与当前节点相连的所有连接 | ||||
|     const incomingConnections = allConnections.filter((connection) => connection.target === element) | ||||
| 
 | ||||
|     incomingConnections.forEach((connection) => { | ||||
|       const source = connection.source // 获取前置节点 | ||||
| 
 | ||||
|       // 只添加不是开始事件的前置节点 | ||||
|       if (!isStartEvent(source)) { | ||||
|         predecessors.add(source.businessObject) | ||||
|         // 递归查找前置节点 | ||||
|         findPredecessorsRecursively(source) | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   const targetElement = elementRegistry.get(elementId) | ||||
|   if (targetElement) { | ||||
|     findPredecessorsRecursively(targetElement) | ||||
|   } | ||||
| 
 | ||||
|   return Array.from(predecessors) // 返回前置节点数组 | ||||
| } | ||||
| 
 | ||||
| function useButtonsSetting() { | ||||
|   const buttonsSetting = ref<ButtonSetting[]>() | ||||
|   // 操作按钮显示名称可编辑 | ||||
|   const btnDisplayNameEdit = ref<boolean[]>([]) | ||||
|   const changeBtnDisplayName = (index: number) => { | ||||
|     btnDisplayNameEdit.value[index] = true | ||||
|   } | ||||
|   const btnDisplayNameBlurEvent = (index: number) => { | ||||
|     btnDisplayNameEdit.value[index] = false | ||||
|     const buttonItem = buttonsSetting.value![index] | ||||
|     buttonItem.displayName = buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)! | ||||
|   } | ||||
|   return { | ||||
|     buttonsSetting, | ||||
|     btnDisplayNameEdit, | ||||
|     changeBtnDisplayName, | ||||
|     btnDisplayNameBlurEvent | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // 表单字段权限设置 | ||||
| function useFormFieldsPermission(defaultPermission) { | ||||
|   // 字段权限配置. 需要有 field, title,  permissioin 属性 | ||||
|   const fieldsPermissionConfig = ref<Array<Record<string, string>>>([]) | ||||
| 
 | ||||
|   const formType = inject<Ref<number>>('formType') // 表单类型 | ||||
| 
 | ||||
|   const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段 | ||||
| 
 | ||||
|   const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => { | ||||
|     nodeFormFields = toRaw(nodeFormFields) | ||||
|     fieldsPermissionConfig.value = | ||||
|       cloneDeep(nodeFormFields) || getDefaultFieldsPermission(unref(formFields)) | ||||
|   } | ||||
|   // 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读 | ||||
|   const getDefaultFieldsPermission = (formFields?: string[]) => { | ||||
|     const defaultFieldsPermission: Array<Record<string, string>> = [] | ||||
|     if (formFields) { | ||||
|       formFields.forEach((fieldStr: string) => { | ||||
|         parseFieldsSetDefaultPermission(JSON.parse(fieldStr), defaultFieldsPermission) | ||||
|       }) | ||||
|     } | ||||
|     return defaultFieldsPermission | ||||
|   } | ||||
|   // 解析字段。赋给默认权限 | ||||
|   const parseFieldsSetDefaultPermission = ( | ||||
|     rule: Record<string, any>, | ||||
|     fieldsPermission: Array<Record<string, string>>, | ||||
|     parentTitle: string = '' | ||||
|   ) => { | ||||
|     const { /**type,*/ field, title: tempTitle, children } = rule | ||||
|     if (field && tempTitle) { | ||||
|       let title = tempTitle | ||||
|       if (parentTitle) { | ||||
|         title = `${parentTitle}.${tempTitle}` | ||||
|       } | ||||
|       fieldsPermission.push({ | ||||
|         field, | ||||
|         title, | ||||
|         permission: defaultPermission | ||||
|       }) | ||||
|       // TODO 子表单 需要处理子表单字段 | ||||
|       // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) { | ||||
|       //   // 解析子表单的字段 | ||||
|       //   rule.props.rule.forEach((item) => { | ||||
|       //     parseFieldsSetDefaultPermission(item, fieldsPermission, title) | ||||
|       //   }) | ||||
|       // } | ||||
|     } | ||||
|     if (children && Array.isArray(children)) { | ||||
|       children.forEach((rule) => { | ||||
|         parseFieldsSetDefaultPermission(rule, fieldsPermission) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     formType, | ||||
|     fieldsPermissionConfig, | ||||
|     getNodeConfigFormFields | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表 | ||||
| onMounted(async () => { | ||||
|   // 获得用户列表 | ||||
|   userOptions.value = await UserApi.getSimpleUserList() | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .button-setting-pane { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   font-size: 14px; | ||||
|   margin-top: 8px; | ||||
| 
 | ||||
|   .button-setting-desc { | ||||
|     padding-right: 8px; | ||||
|     margin-bottom: 16px; | ||||
|     font-size: 16px; | ||||
|     font-weight: 700; | ||||
|   } | ||||
| 
 | ||||
|   .button-setting-title { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     height: 45px; | ||||
|     padding-left: 12px; | ||||
|     background-color: #f8fafc0a; | ||||
|     border: 1px solid #1f38581a; | ||||
| 
 | ||||
|     & > :first-child { | ||||
|       width: 100px !important; | ||||
|       text-align: left !important; | ||||
|     } | ||||
| 
 | ||||
|     & > :last-child { | ||||
|       text-align: center !important; | ||||
|     } | ||||
| 
 | ||||
|     .button-title-label { | ||||
|       width: 150px; | ||||
|       font-size: 13px; | ||||
|       font-weight: 700; | ||||
|       color: #000; | ||||
|       text-align: left; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .button-setting-item { | ||||
|     align-items: center; | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     height: 38px; | ||||
|     padding-left: 12px; | ||||
|     border: 1px solid #1f38581a; | ||||
|     border-top: 0; | ||||
| 
 | ||||
|     & > :first-child { | ||||
|       width: 100px !important; | ||||
|     } | ||||
| 
 | ||||
|     & > :last-child { | ||||
|       text-align: center !important; | ||||
|     } | ||||
| 
 | ||||
|     .button-setting-item-label { | ||||
|       width: 150px; | ||||
|       overflow: hidden; | ||||
|       text-align: left; | ||||
|       text-overflow: ellipsis; | ||||
|       white-space: nowrap; | ||||
|     } | ||||
| 
 | ||||
|     .editable-title-input { | ||||
|       height: 24px; | ||||
|       max-width: 130px; | ||||
|       margin-left: 4px; | ||||
|       line-height: 24px; | ||||
|       border: 1px solid #d9d9d9; | ||||
|       border-radius: 4px; | ||||
|       transition: all 0.3s; | ||||
| 
 | ||||
|       &:focus { | ||||
|         border-color: #40a9ff; | ||||
|         outline: 0; | ||||
|         box-shadow: 0 0 0 2px rgb(24 144 255 / 20%); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .field-setting-pane { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   font-size: 14px; | ||||
| 
 | ||||
|   .field-setting-desc { | ||||
|     padding-right: 8px; | ||||
|     margin-bottom: 16px; | ||||
|     font-size: 16px; | ||||
|     font-weight: 700; | ||||
|   } | ||||
| 
 | ||||
|   .field-permit-title { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     height: 45px; | ||||
|     padding-left: 12px; | ||||
|     line-height: 45px; | ||||
|     background-color: #f8fafc0a; | ||||
|     border: 1px solid #1f38581a; | ||||
| 
 | ||||
|     .first-title { | ||||
|       text-align: left !important; | ||||
|     } | ||||
| 
 | ||||
|     .other-titles { | ||||
|       display: flex; | ||||
|       justify-content: space-between; | ||||
|     } | ||||
| 
 | ||||
|     .setting-title-label { | ||||
|       display: inline-block; | ||||
|       width: 100px; | ||||
|       padding: 5px 0; | ||||
|       font-size: 13px; | ||||
|       font-weight: 700; | ||||
|       color: #000; | ||||
|       text-align: center; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .field-setting-item { | ||||
|     align-items: center; | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     height: 38px; | ||||
|     padding-left: 12px; | ||||
|     border: 1px solid #1f38581a; | ||||
|     border-top: 0; | ||||
| 
 | ||||
|     .field-setting-item-label { | ||||
|       display: inline-block; | ||||
|       width: 100px; | ||||
|       min-height: 16px; | ||||
|       overflow: hidden; | ||||
|       text-overflow: ellipsis; | ||||
|       white-space: nowrap; | ||||
|       cursor: text; | ||||
|     } | ||||
| 
 | ||||
|     .field-setting-item-group { | ||||
|       display: flex; | ||||
|       justify-content: space-between; | ||||
| 
 | ||||
|       .item-radio-wrap { | ||||
|         display: inline-block; | ||||
|         width: 100px; | ||||
|         text-align: center; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| <style lang="scss" scoped></style> | ||||
|  |  | |||
|  | @ -0,0 +1,13 @@ | |||
| <template> | ||||
|   <div> | ||||
|     定时边界事件(非中断) | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| // TODO 未来继续完善 | ||||
| defineOptions({ name: 'ElementCustomConfig4BoundaryEventTimer' }) | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| </style> | ||||
|  | @ -0,0 +1,656 @@ | |||
| <!-- UserTask 自定义配置: | ||||
|      1. 审批人与提交人为同一人时 | ||||
|      2. 审批人拒绝时 | ||||
|      3. 审批人为空时 | ||||
|      4. 操作按钮 | ||||
|      5. 字段权限 | ||||
| --> | ||||
| <template> | ||||
|   <div> | ||||
|     <el-divider content-position="left">审批人拒绝时</el-divider> | ||||
|     <el-form-item prop="rejectHandlerType"> | ||||
|       <el-radio-group | ||||
|         v-model="rejectHandlerType" | ||||
|         :disabled="returnTaskList.length === 0" | ||||
|         @change="updateRejectHandlerType" | ||||
|       > | ||||
|         <div class="flex-col"> | ||||
|           <div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index"> | ||||
|             <el-radio :key="item.value" :value="item.value" :label="item.label" /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </el-radio-group> | ||||
|     </el-form-item> | ||||
|     <el-form-item | ||||
|       v-if="rejectHandlerType == RejectHandlerType.RETURN_USER_TASK" | ||||
|       label="驳回节点" | ||||
|       prop="returnNodeId" | ||||
|     > | ||||
|       <el-select v-model="returnNodeId" clearable style="width: 100%" @change="updateReturnNodeId"> | ||||
|         <el-option | ||||
|           v-for="item in returnTaskList" | ||||
|           :key="item.id" | ||||
|           :label="item.name" | ||||
|           :value="item.id" | ||||
|         /> | ||||
|       </el-select> | ||||
|     </el-form-item> | ||||
| 
 | ||||
|     <el-divider content-position="left">审批人为空时</el-divider> | ||||
|     <el-form-item prop="assignEmptyHandlerType"> | ||||
|       <el-radio-group v-model="assignEmptyHandlerType" @change="updateAssignEmptyHandlerType"> | ||||
|         <div class="flex-col"> | ||||
|           <div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index"> | ||||
|             <el-radio :key="item.value" :value="item.value" :label="item.label" /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </el-radio-group> | ||||
|     </el-form-item> | ||||
|     <el-form-item | ||||
|       v-if="assignEmptyHandlerType == AssignEmptyHandlerType.ASSIGN_USER" | ||||
|       label="指定用户" | ||||
|       prop="assignEmptyHandlerUserIds" | ||||
|       span="24" | ||||
|     > | ||||
|       <el-select | ||||
|         v-model="assignEmptyUserIds" | ||||
|         clearable | ||||
|         multiple | ||||
|         style="width: 100%" | ||||
|         @change="updateAssignEmptyUserIds" | ||||
|       > | ||||
|         <el-option | ||||
|           v-for="item in userOptions" | ||||
|           :key="item.id" | ||||
|           :label="item.nickname" | ||||
|           :value="item.id" | ||||
|         /> | ||||
|       </el-select> | ||||
|     </el-form-item> | ||||
| 
 | ||||
|     <el-divider content-position="left">审批人与提交人为同一人时</el-divider> | ||||
|     <el-radio-group v-model="assignStartUserHandlerType" @change="updateAssignStartUserHandlerType"> | ||||
|       <div class="flex-col"> | ||||
|         <div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index"> | ||||
|           <el-radio :key="item.value" :value="item.value" :label="item.label" /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </el-radio-group> | ||||
| 
 | ||||
|     <el-divider content-position="left">操作按钮</el-divider> | ||||
|     <div class="button-setting-pane"> | ||||
|       <div class="button-setting-title"> | ||||
|         <div class="button-title-label">操作按钮</div> | ||||
|         <div class="pl-4 button-title-label">显示名称</div> | ||||
|         <div class="button-title-label">启用</div> | ||||
|       </div> | ||||
|       <div class="button-setting-item" v-for="(item, index) in buttonsSettingEl" :key="index"> | ||||
|         <div class="button-setting-item-label"> {{ OPERATION_BUTTON_NAME.get(item.id) }} </div> | ||||
|         <div class="button-setting-item-label"> | ||||
|           <input | ||||
|             type="text" | ||||
|             class="editable-title-input" | ||||
|             @blur="btnDisplayNameBlurEvent(index)" | ||||
|             v-mountedFocus | ||||
|             v-model="item.displayName" | ||||
|             :placeholder="item.displayName" | ||||
|             v-if="btnDisplayNameEdit[index]" | ||||
|           /> | ||||
|           <el-button v-else text @click="changeBtnDisplayName(index)" | ||||
|             >{{ item.displayName }}  <Icon icon="ep:edit" | ||||
|           /></el-button> | ||||
|         </div> | ||||
|         <div class="button-setting-item-label"> | ||||
|           <el-switch v-model="item.enable" /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <el-divider content-position="left">字段权限</el-divider> | ||||
|     <div class="field-setting-pane" v-if="formType === 10"> | ||||
|       <div class="field-permit-title"> | ||||
|         <div class="setting-title-label first-title"> 字段名称 </div> | ||||
|         <div class="other-titles"> | ||||
|           <span class="setting-title-label">只读</span> | ||||
|           <span class="setting-title-label">可编辑</span> | ||||
|           <span class="setting-title-label">隐藏</span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="field-setting-item" v-for="(item, index) in fieldsPermissionEl" :key="index"> | ||||
|         <div class="field-setting-item-label"> {{ item.title }} </div> | ||||
|         <el-radio-group class="field-setting-item-group" v-model="item.permission"> | ||||
|           <div class="item-radio-wrap"> | ||||
|             <el-radio | ||||
|               :value="FieldPermissionType.READ" | ||||
|               size="large" | ||||
|               :label="FieldPermissionType.READ" | ||||
|               ><span></span | ||||
|             ></el-radio> | ||||
|           </div> | ||||
|           <div class="item-radio-wrap"> | ||||
|             <el-radio | ||||
|               :value="FieldPermissionType.WRITE" | ||||
|               size="large" | ||||
|               :label="FieldPermissionType.WRITE" | ||||
|               ><span></span | ||||
|             ></el-radio> | ||||
|           </div> | ||||
|           <div class="item-radio-wrap"> | ||||
|             <el-radio | ||||
|               :value="FieldPermissionType.NONE" | ||||
|               size="large" | ||||
|               :label="FieldPermissionType.NONE" | ||||
|               ><span></span | ||||
|             ></el-radio> | ||||
|           </div> | ||||
|         </el-radio-group> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { | ||||
|   ASSIGN_START_USER_HANDLER_TYPES, | ||||
|   RejectHandlerType, | ||||
|   REJECT_HANDLER_TYPES, | ||||
|   ASSIGN_EMPTY_HANDLER_TYPES, | ||||
|   AssignEmptyHandlerType, | ||||
|   OPERATION_BUTTON_NAME, | ||||
|   DEFAULT_BUTTON_SETTING, | ||||
|   FieldPermissionType | ||||
| } from '@/components/SimpleProcessDesignerV2/src/consts' | ||||
| import * as UserApi from '@/api/system/user' | ||||
| import { cloneDeep } from 'lodash-es' | ||||
| 
 | ||||
| defineOptions({ name: 'ElementCustomConfig4UserTask' }) | ||||
| const props = defineProps({ | ||||
|   id: String, | ||||
|   type: String | ||||
| }) | ||||
| const prefix = inject('prefix') | ||||
| 
 | ||||
| // 审批人与提交人为同一人时 | ||||
| const assignStartUserHandlerTypeEl = ref() | ||||
| const assignStartUserHandlerType = ref() | ||||
| 
 | ||||
| // 审批人拒绝时 | ||||
| const rejectHandlerTypeEl = ref() | ||||
| const rejectHandlerType = ref() | ||||
| const returnNodeIdEl = ref() | ||||
| const returnNodeId = ref() | ||||
| const returnTaskList = ref([]) | ||||
| 
 | ||||
| // 审批人为空时 | ||||
| const assignEmptyHandlerTypeEl = ref() | ||||
| const assignEmptyHandlerType = ref() | ||||
| const assignEmptyUserIdsEl = ref() | ||||
| const assignEmptyUserIds = ref() | ||||
| 
 | ||||
| // 操作按钮 | ||||
| const buttonsSettingEl = ref() | ||||
| const { buttonsSetting, btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } = | ||||
|   useButtonsSetting() | ||||
| 
 | ||||
| // 字段权限 | ||||
| const fieldsPermissionEl = ref() | ||||
| const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission( | ||||
|   FieldPermissionType.READ | ||||
| ) | ||||
| 
 | ||||
| const elExtensionElements = ref() | ||||
| const otherExtensions = ref() | ||||
| const bpmnElement = ref() | ||||
| const bpmnInstances = () => (window as any)?.bpmnInstances | ||||
| 
 | ||||
| const resetCustomConfigList = () => { | ||||
|   bpmnElement.value = bpmnInstances().bpmnElement | ||||
| 
 | ||||
|   // 获取可回退的列表 | ||||
|   returnTaskList.value = findAllPredecessorsExcludingStart( | ||||
|     bpmnElement.value.id, | ||||
|     bpmnInstances().modeler | ||||
|   ) | ||||
| 
 | ||||
|   // 获取元素扩展属性 或者 创建扩展属性 | ||||
|   elExtensionElements.value = | ||||
|     bpmnElement.value.businessObject?.extensionElements ?? | ||||
|     bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] }) | ||||
| 
 | ||||
|   // 审批人与提交人为同一人时 | ||||
|   assignStartUserHandlerTypeEl.value = | ||||
|     elExtensionElements.value.values?.filter( | ||||
|       (ex) => ex.$type === `${prefix}:AssignStartUserHandlerType` | ||||
|     )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, { value: 1 }) | ||||
|   assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value | ||||
| 
 | ||||
|   // 审批人拒绝时 | ||||
|   rejectHandlerTypeEl.value = | ||||
|     elExtensionElements.value.values?.filter( | ||||
|       (ex) => ex.$type === `${prefix}:RejectHandlerType` | ||||
|     )?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 }) | ||||
|   rejectHandlerType.value = rejectHandlerTypeEl.value.value | ||||
|   returnNodeIdEl.value = | ||||
|     elExtensionElements.value.values?.filter( | ||||
|       (ex) => ex.$type === `${prefix}:RejectReturnTaskId` | ||||
|     )?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, { value: '' }) | ||||
|   returnNodeId.value = returnNodeIdEl.value.value | ||||
| 
 | ||||
|   // 审批人为空时 | ||||
|   assignEmptyHandlerTypeEl.value = | ||||
|     elExtensionElements.value.values?.filter( | ||||
|       (ex) => ex.$type === `${prefix}:AssignEmptyHandlerType` | ||||
|     )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, { value: 1 }) | ||||
|   assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value | ||||
|   assignEmptyUserIdsEl.value = | ||||
|     elExtensionElements.value.values?.filter( | ||||
|       (ex) => ex.$type === `${prefix}:AssignEmptyUserIds` | ||||
|     )?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, { value: '' }) | ||||
|   assignEmptyUserIds.value = assignEmptyUserIdsEl.value.value.split(',').map((item) => { | ||||
|     // 如果数字超出了最大安全整数范围,则将其作为字符串处理 | ||||
|     let num = Number(item) | ||||
|     return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num | ||||
|   }) | ||||
| 
 | ||||
|   // 操作按钮 | ||||
|   buttonsSettingEl.value = elExtensionElements.value.values?.filter( | ||||
|     (ex) => ex.$type === `${prefix}:ButtonsSetting` | ||||
|   ) | ||||
|   if (buttonsSettingEl.value.length === 0) { | ||||
|     DEFAULT_BUTTON_SETTING.forEach((item) => { | ||||
|       buttonsSettingEl.value.push( | ||||
|         bpmnInstances().moddle.create(`${prefix}:ButtonsSetting`, { | ||||
|           'flowable:id': item.id, | ||||
|           'flowable:displayName': item.displayName, | ||||
|           'flowable:enable': item.enable | ||||
|         }) | ||||
|       ) | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   // 字段权限 | ||||
|   if (formType.value === 10) { | ||||
|     fieldsPermissionEl.value = elExtensionElements.value.values?.filter( | ||||
|       (ex) => ex.$type === `${prefix}:FieldsPermission` | ||||
|     ) | ||||
|     if (fieldsPermissionEl.value.length === 0) { | ||||
|       getNodeConfigFormFields() | ||||
|       fieldsPermissionConfig.value.forEach((el) => { | ||||
|         fieldsPermissionEl.value.push( | ||||
|           bpmnInstances().moddle.create(`${prefix}:FieldsPermission`, el) | ||||
|         ) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // 保留剩余扩展元素,便于后面更新该元素对应属性 | ||||
|   otherExtensions.value = | ||||
|     elExtensionElements.value.values?.filter( | ||||
|       (ex) => | ||||
|         ex.$type !== `${prefix}:AssignStartUserHandlerType` && | ||||
|         ex.$type !== `${prefix}:RejectHandlerType` && | ||||
|         ex.$type !== `${prefix}:RejectReturnTaskId` && | ||||
|         ex.$type !== `${prefix}:AssignEmptyHandlerType` && | ||||
|         ex.$type !== `${prefix}:AssignEmptyUserIds` && | ||||
|         ex.$type !== `${prefix}:ButtonsSetting` && | ||||
|         ex.$type !== `${prefix}:FieldsPermission` | ||||
|     ) ?? [] | ||||
| 
 | ||||
|   // 更新元素扩展属性,避免后续报错 | ||||
|   updateElementExtensions() | ||||
| } | ||||
| 
 | ||||
| const updateAssignStartUserHandlerType = () => { | ||||
|   assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value | ||||
| 
 | ||||
|   updateElementExtensions() | ||||
| } | ||||
| 
 | ||||
| const updateRejectHandlerType = () => { | ||||
|   rejectHandlerTypeEl.value.value = rejectHandlerType.value | ||||
| 
 | ||||
|   returnNodeId.value = returnTaskList.value[0].id | ||||
|   returnNodeIdEl.value.value = returnNodeId.value | ||||
| 
 | ||||
|   updateElementExtensions() | ||||
| } | ||||
| 
 | ||||
| const updateReturnNodeId = () => { | ||||
|   returnNodeIdEl.value.value = returnNodeId.value | ||||
| 
 | ||||
|   updateElementExtensions() | ||||
| } | ||||
| 
 | ||||
| const updateAssignEmptyHandlerType = () => { | ||||
|   assignEmptyHandlerTypeEl.value.value = assignEmptyHandlerType.value | ||||
| 
 | ||||
|   updateElementExtensions() | ||||
| } | ||||
| 
 | ||||
| const updateAssignEmptyUserIds = () => { | ||||
|   assignEmptyUserIdsEl.value.value = assignEmptyUserIds.value.toString() | ||||
| 
 | ||||
|   updateElementExtensions() | ||||
| } | ||||
| 
 | ||||
| const updateElementExtensions = () => { | ||||
|   const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', { | ||||
|     values: [ | ||||
|       ...otherExtensions.value, | ||||
|       assignStartUserHandlerTypeEl.value, | ||||
|       rejectHandlerTypeEl.value, | ||||
|       returnNodeIdEl.value, | ||||
|       assignEmptyHandlerTypeEl.value, | ||||
|       assignEmptyUserIdsEl.value, | ||||
|       ...buttonsSettingEl.value, | ||||
|       ...fieldsPermissionEl.value | ||||
|     ] | ||||
|   }) | ||||
|   bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { | ||||
|     extensionElements: extensions | ||||
|   }) | ||||
| } | ||||
| 
 | ||||
| watch( | ||||
|   () => props.id, | ||||
|   (val) => { | ||||
|     val && | ||||
|       val.length && | ||||
|       nextTick(() => { | ||||
|         resetCustomConfigList() | ||||
|       }) | ||||
|   }, | ||||
|   { immediate: true } | ||||
| ) | ||||
| 
 | ||||
| function findAllPredecessorsExcludingStart(elementId, modeler) { | ||||
|   const elementRegistry = modeler.get('elementRegistry') | ||||
|   const allConnections = elementRegistry.filter((element) => element.type === 'bpmn:SequenceFlow') | ||||
|   const predecessors = new Set() // 使用 Set 来避免重复节点 | ||||
|   const visited = new Set() // 用于记录已访问的节点 | ||||
| 
 | ||||
|   // 检查是否是开始事件节点 | ||||
|   function isStartEvent(element) { | ||||
|     return element.type === 'bpmn:StartEvent' | ||||
|   } | ||||
| 
 | ||||
|   function findPredecessorsRecursively(element) { | ||||
|     // 如果该节点已经访问过,直接返回,避免循环 | ||||
|     if (visited.has(element)) { | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     // 标记当前节点为已访问 | ||||
|     visited.add(element) | ||||
| 
 | ||||
|     // 获取与当前节点相连的所有连接 | ||||
|     const incomingConnections = allConnections.filter((connection) => connection.target === element) | ||||
| 
 | ||||
|     incomingConnections.forEach((connection) => { | ||||
|       const source = connection.source // 获取前置节点 | ||||
| 
 | ||||
|       // 只添加不是开始事件的前置节点 | ||||
|       if (!isStartEvent(source)) { | ||||
|         predecessors.add(source.businessObject) | ||||
|         // 递归查找前置节点 | ||||
|         findPredecessorsRecursively(source) | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   const targetElement = elementRegistry.get(elementId) | ||||
|   if (targetElement) { | ||||
|     findPredecessorsRecursively(targetElement) | ||||
|   } | ||||
| 
 | ||||
|   return Array.from(predecessors) // 返回前置节点数组 | ||||
| } | ||||
| 
 | ||||
| function useButtonsSetting() { | ||||
|   const buttonsSetting = ref<ButtonSetting[]>() | ||||
|   // 操作按钮显示名称可编辑 | ||||
|   const btnDisplayNameEdit = ref<boolean[]>([]) | ||||
|   const changeBtnDisplayName = (index: number) => { | ||||
|     btnDisplayNameEdit.value[index] = true | ||||
|   } | ||||
|   const btnDisplayNameBlurEvent = (index: number) => { | ||||
|     btnDisplayNameEdit.value[index] = false | ||||
|     const buttonItem = buttonsSetting.value![index] | ||||
|     buttonItem.displayName = buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)! | ||||
|   } | ||||
|   return { | ||||
|     buttonsSetting, | ||||
|     btnDisplayNameEdit, | ||||
|     changeBtnDisplayName, | ||||
|     btnDisplayNameBlurEvent | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // 表单字段权限设置 | ||||
| function useFormFieldsPermission(defaultPermission) { | ||||
|   // 字段权限配置. 需要有 field, title,  permissioin 属性 | ||||
|   const fieldsPermissionConfig = ref<Array<Record<string, string>>>([]) | ||||
| 
 | ||||
|   const formType = inject<Ref<number>>('formType') // 表单类型 | ||||
| 
 | ||||
|   const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段 | ||||
| 
 | ||||
|   const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => { | ||||
|     nodeFormFields = toRaw(nodeFormFields) | ||||
|     fieldsPermissionConfig.value = | ||||
|       cloneDeep(nodeFormFields) || getDefaultFieldsPermission(unref(formFields)) | ||||
|   } | ||||
|   // 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读 | ||||
|   const getDefaultFieldsPermission = (formFields?: string[]) => { | ||||
|     const defaultFieldsPermission: Array<Record<string, string>> = [] | ||||
|     if (formFields) { | ||||
|       formFields.forEach((fieldStr: string) => { | ||||
|         parseFieldsSetDefaultPermission(JSON.parse(fieldStr), defaultFieldsPermission) | ||||
|       }) | ||||
|     } | ||||
|     return defaultFieldsPermission | ||||
|   } | ||||
|   // 解析字段。赋给默认权限 | ||||
|   const parseFieldsSetDefaultPermission = ( | ||||
|     rule: Record<string, any>, | ||||
|     fieldsPermission: Array<Record<string, string>>, | ||||
|     parentTitle: string = '' | ||||
|   ) => { | ||||
|     const { /**type,*/ field, title: tempTitle, children } = rule | ||||
|     if (field && tempTitle) { | ||||
|       let title = tempTitle | ||||
|       if (parentTitle) { | ||||
|         title = `${parentTitle}.${tempTitle}` | ||||
|       } | ||||
|       fieldsPermission.push({ | ||||
|         field, | ||||
|         title, | ||||
|         permission: defaultPermission | ||||
|       }) | ||||
|       // TODO 子表单 需要处理子表单字段 | ||||
|       // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) { | ||||
|       //   // 解析子表单的字段 | ||||
|       //   rule.props.rule.forEach((item) => { | ||||
|       //     parseFieldsSetDefaultPermission(item, fieldsPermission, title) | ||||
|       //   }) | ||||
|       // } | ||||
|     } | ||||
|     if (children && Array.isArray(children)) { | ||||
|       children.forEach((rule) => { | ||||
|         parseFieldsSetDefaultPermission(rule, fieldsPermission) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     formType, | ||||
|     fieldsPermissionConfig, | ||||
|     getNodeConfigFormFields | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表 | ||||
| onMounted(async () => { | ||||
|   // 获得用户列表 | ||||
|   userOptions.value = await UserApi.getSimpleUserList() | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .button-setting-pane { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   font-size: 14px; | ||||
|   margin-top: 8px; | ||||
| 
 | ||||
|   .button-setting-desc { | ||||
|     padding-right: 8px; | ||||
|     margin-bottom: 16px; | ||||
|     font-size: 16px; | ||||
|     font-weight: 700; | ||||
|   } | ||||
| 
 | ||||
|   .button-setting-title { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     height: 45px; | ||||
|     padding-left: 12px; | ||||
|     background-color: #f8fafc0a; | ||||
|     border: 1px solid #1f38581a; | ||||
| 
 | ||||
|     & > :first-child { | ||||
|       width: 100px !important; | ||||
|       text-align: left !important; | ||||
|     } | ||||
| 
 | ||||
|     & > :last-child { | ||||
|       text-align: center !important; | ||||
|     } | ||||
| 
 | ||||
|     .button-title-label { | ||||
|       width: 150px; | ||||
|       font-size: 13px; | ||||
|       font-weight: 700; | ||||
|       color: #000; | ||||
|       text-align: left; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .button-setting-item { | ||||
|     align-items: center; | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     height: 38px; | ||||
|     padding-left: 12px; | ||||
|     border: 1px solid #1f38581a; | ||||
|     border-top: 0; | ||||
| 
 | ||||
|     & > :first-child { | ||||
|       width: 100px !important; | ||||
|     } | ||||
| 
 | ||||
|     & > :last-child { | ||||
|       text-align: center !important; | ||||
|     } | ||||
| 
 | ||||
|     .button-setting-item-label { | ||||
|       width: 150px; | ||||
|       overflow: hidden; | ||||
|       text-align: left; | ||||
|       text-overflow: ellipsis; | ||||
|       white-space: nowrap; | ||||
|     } | ||||
| 
 | ||||
|     .editable-title-input { | ||||
|       height: 24px; | ||||
|       max-width: 130px; | ||||
|       margin-left: 4px; | ||||
|       line-height: 24px; | ||||
|       border: 1px solid #d9d9d9; | ||||
|       border-radius: 4px; | ||||
|       transition: all 0.3s; | ||||
| 
 | ||||
|       &:focus { | ||||
|         border-color: #40a9ff; | ||||
|         outline: 0; | ||||
|         box-shadow: 0 0 0 2px rgb(24 144 255 / 20%); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .field-setting-pane { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   font-size: 14px; | ||||
| 
 | ||||
|   .field-setting-desc { | ||||
|     padding-right: 8px; | ||||
|     margin-bottom: 16px; | ||||
|     font-size: 16px; | ||||
|     font-weight: 700; | ||||
|   } | ||||
| 
 | ||||
|   .field-permit-title { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|     height: 45px; | ||||
|     padding-left: 12px; | ||||
|     line-height: 45px; | ||||
|     background-color: #f8fafc0a; | ||||
|     border: 1px solid #1f38581a; | ||||
| 
 | ||||
|     .first-title { | ||||
|       text-align: left !important; | ||||
|     } | ||||
| 
 | ||||
|     .other-titles { | ||||
|       display: flex; | ||||
|       justify-content: space-between; | ||||
|     } | ||||
| 
 | ||||
|     .setting-title-label { | ||||
|       display: inline-block; | ||||
|       width: 100px; | ||||
|       padding: 5px 0; | ||||
|       font-size: 13px; | ||||
|       font-weight: 700; | ||||
|       color: #000; | ||||
|       text-align: center; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .field-setting-item { | ||||
|     align-items: center; | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     height: 38px; | ||||
|     padding-left: 12px; | ||||
|     border: 1px solid #1f38581a; | ||||
|     border-top: 0; | ||||
| 
 | ||||
|     .field-setting-item-label { | ||||
|       display: inline-block; | ||||
|       width: 100px; | ||||
|       min-height: 16px; | ||||
|       overflow: hidden; | ||||
|       text-overflow: ellipsis; | ||||
|       white-space: nowrap; | ||||
|       cursor: text; | ||||
|     } | ||||
| 
 | ||||
|     .field-setting-item-group { | ||||
|       display: flex; | ||||
|       justify-content: space-between; | ||||
| 
 | ||||
|       .item-radio-wrap { | ||||
|         display: inline-block; | ||||
|         width: 100px; | ||||
|         text-align: center; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|  | @ -0,0 +1,13 @@ | |||
| import UserTask from './components/UserTask.vue' | ||||
| import BoundaryEventTimer from './components/BoundaryEventTimer.vue' | ||||
| 
 | ||||
| export const CustomConfigMap = { | ||||
|   UserTask: { | ||||
|     name: '用户任务', | ||||
|     componet: UserTask | ||||
|   }, | ||||
|   BoundaryEventTimerEventDefinition: { | ||||
|     name: '定时边界事件(非中断)', | ||||
|     componet: BoundaryEventTimer | ||||
|   } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	 Lesan
						Lesan