Commit 490f907ada7c51b325bf493013b789b4d4e964ba
1 parent
c4003396
完善 bpmnProcessDesigner 流程设计器的使用,基本可用了!
Showing
20 changed files
with
242 additions
and
135 deletions
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/controller/form/BpmFormController.java
| 1 | 1 | package cn.iocoder.yudao.adminserver.modules.bpm.controller.form; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.adminserver.modules.bpm.controller.form.vo.*; | |
| 3 | 4 | import cn.iocoder.yudao.adminserver.modules.bpm.convert.form.BpmFormConvert; |
| 4 | 5 | import cn.iocoder.yudao.adminserver.modules.bpm.dal.dataobject.form.BpmFormDO; |
| 5 | 6 | import cn.iocoder.yudao.adminserver.modules.bpm.service.form.BpmFormService; |
| 6 | -import cn.iocoder.yudao.adminserver.modules.bpm.controller.form.vo.*; | |
| 7 | 7 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 9 | 9 | import io.swagger.annotations.Api; |
| ... | ... | @@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.*; |
| 15 | 15 | |
| 16 | 16 | import javax.annotation.Resource; |
| 17 | 17 | import javax.validation.Valid; |
| 18 | -import java.util.Collection; | |
| 19 | 18 | import java.util.List; |
| 20 | 19 | |
| 21 | 20 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| ... | ... | @@ -62,13 +61,11 @@ public class BpmFormController { |
| 62 | 61 | return success(BpmFormConvert.INSTANCE.convert(form)); |
| 63 | 62 | } |
| 64 | 63 | |
| 65 | - @GetMapping("/list") | |
| 66 | - @ApiOperation("获得动态表单列表") | |
| 67 | - @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class) | |
| 68 | - @PreAuthorize("@ss.hasPermission('bpm:form:query')") | |
| 69 | - public CommonResult<List<BpmFormRespVO>> getFormList(@RequestParam("ids") Collection<Long> ids) { | |
| 70 | - List<BpmFormDO> list = formService.getFormList(ids); | |
| 71 | - return success(BpmFormConvert.INSTANCE.convertList(list)); | |
| 64 | + @GetMapping("/list-all-simple") | |
| 65 | + @ApiOperation(value = "获得动态表单的精简列表", notes = "用于表单下拉框") | |
| 66 | + public CommonResult<List<BpmFormSimpleRespVO>> getSimpleForms() { | |
| 67 | + List<BpmFormDO> list = formService.getFormList(); | |
| 68 | + return success(BpmFormConvert.INSTANCE.convertList2(list)); | |
| 72 | 69 | } |
| 73 | 70 | |
| 74 | 71 | @GetMapping("/page") | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/controller/form/vo/BpmFormSimpleRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.adminserver.modules.bpm.controller.form.vo; | |
| 2 | + | |
| 3 | +import io.swagger.annotations.ApiModel; | |
| 4 | +import io.swagger.annotations.ApiModelProperty; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +@ApiModel("流程表单精简 Response VO") | |
| 8 | +@Data | |
| 9 | +public class BpmFormSimpleRespVO { | |
| 10 | + | |
| 11 | + @ApiModelProperty(value = "表单编号", required = true, example = "1024") | |
| 12 | + private Long id; | |
| 13 | + | |
| 14 | + @ApiModelProperty(value = "表单名称", required = true, example = "芋道") | |
| 15 | + private String name; | |
| 16 | + | |
| 17 | +} | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/controller/model/vo/BpmModelRespVO.java
| ... | ... | @@ -6,6 +6,8 @@ import lombok.Data; |
| 6 | 6 | import lombok.EqualsAndHashCode; |
| 7 | 7 | import lombok.ToString; |
| 8 | 8 | |
| 9 | +import java.util.Date; | |
| 10 | + | |
| 9 | 11 | @ApiModel("流程模型的创建 Request VO") |
| 10 | 12 | @Data |
| 11 | 13 | @EqualsAndHashCode(callSuper = true) |
| ... | ... | @@ -18,4 +20,7 @@ public class BpmModelRespVO extends BpmModelBaseVO { |
| 18 | 20 | @ApiModelProperty(value = "BPMN XML", required = true) |
| 19 | 21 | private String bpmnXml; |
| 20 | 22 | |
| 23 | + @ApiModelProperty(value = "创建时间", required = true) | |
| 24 | + private Date createTime; | |
| 25 | + | |
| 21 | 26 | } | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/convert/form/BpmFormConvert.java
| ... | ... | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.adminserver.modules.bpm.convert.form; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.adminserver.modules.bpm.controller.form.vo.BpmFormCreateReqVO; |
| 4 | 4 | import cn.iocoder.yudao.adminserver.modules.bpm.controller.form.vo.BpmFormRespVO; |
| 5 | +import cn.iocoder.yudao.adminserver.modules.bpm.controller.form.vo.BpmFormSimpleRespVO; | |
| 5 | 6 | import cn.iocoder.yudao.adminserver.modules.bpm.controller.form.vo.BpmFormUpdateReqVO; |
| 6 | 7 | import cn.iocoder.yudao.adminserver.modules.bpm.dal.dataobject.form.BpmFormDO; |
| 7 | 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| ... | ... | @@ -26,7 +27,7 @@ public interface BpmFormConvert { |
| 26 | 27 | |
| 27 | 28 | BpmFormRespVO convert(BpmFormDO bean); |
| 28 | 29 | |
| 29 | - List<BpmFormRespVO> convertList(List<BpmFormDO> list); | |
| 30 | + List<BpmFormSimpleRespVO> convertList2(List<BpmFormDO> list); | |
| 30 | 31 | |
| 31 | 32 | PageResult<BpmFormRespVO> convertPage(PageResult<BpmFormDO> page); |
| 32 | 33 | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/convert/model/ModelConvert.java
| ... | ... | @@ -57,7 +57,20 @@ public interface ModelConvert { |
| 57 | 57 | return modelRespVO; |
| 58 | 58 | } |
| 59 | 59 | |
| 60 | - BpmModelRespVO convert(Model model); | |
| 60 | + default BpmModelRespVO convert(Model model) { | |
| 61 | + BpmModelRespVO modelRespVO = new BpmModelRespVO(); | |
| 62 | + modelRespVO.setId(model.getId()); | |
| 63 | + modelRespVO.setName(model.getName()); | |
| 64 | + modelRespVO.setKey(model.getKey()); | |
| 65 | + modelRespVO.setCategory(model.getCategory()); | |
| 66 | + modelRespVO.setCreateTime(model.getCreateTime()); | |
| 67 | + BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); | |
| 68 | + if (metaInfo != null) { | |
| 69 | + modelRespVO.setFormId(metaInfo.getFormId()); | |
| 70 | + modelRespVO.setDescription(metaInfo.getDescription()); | |
| 71 | + } | |
| 72 | + return modelRespVO; | |
| 73 | + } | |
| 61 | 74 | |
| 62 | 75 | default BpmDefinitionCreateReqDTO convert2(Model model) { |
| 63 | 76 | BpmDefinitionCreateReqDTO createReqDTO = new BpmDefinitionCreateReqDTO(); | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/enums/BpmErrorCodeConstants.java
| ... | ... | @@ -26,6 +26,8 @@ public interface BpmErrorCodeConstants { |
| 26 | 26 | // ========== OA 工作流模块 1-009-002-000 ========== |
| 27 | 27 | ErrorCode BPM_MODEL_KEY_EXISTS = new ErrorCode(1009002000, "已经存在流程标识为【{}】的流程"); |
| 28 | 28 | ErrorCode BPMN_MODEL_NOT_EXISTS = new ErrorCode(1009002001, "流程模型不存在"); |
| 29 | + ErrorCode BPMN_MODEL_KEY_VALID = new ErrorCode(1009002002, "流程标识格式不正确,需要以字母或下划线开头,后接任意字母、数字、中划线、下划线、句点!"); | |
| 30 | + | |
| 29 | 31 | |
| 30 | 32 | ErrorCode BPMN_MODEL_ERROR = new ErrorCode(1004001002, "工作流模型异常"); |
| 31 | 33 | ErrorCode BPMN_MODEL_PROCESS_NOT_EXISTS = new ErrorCode(1004001009, "流程数据为空"); | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/service/form/BpmFormService.java
| ... | ... | @@ -7,6 +7,7 @@ import cn.iocoder.yudao.adminserver.modules.bpm.controller.form.vo.BpmFormUpdate |
| 7 | 7 | import cn.iocoder.yudao.adminserver.modules.bpm.dal.dataobject.form.BpmFormDO; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 9 | 9 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 10 | +import org.activiti.engine.repository.Model; | |
| 10 | 11 | |
| 11 | 12 | import javax.validation.Valid; |
| 12 | 13 | import java.util.Collection; |
| ... | ... | @@ -55,6 +56,13 @@ public interface BpmFormService { |
| 55 | 56 | /** |
| 56 | 57 | * 获得动态表单列表 |
| 57 | 58 | * |
| 59 | + * @return 动态表单列表 | |
| 60 | + */ | |
| 61 | + List<BpmFormDO> getFormList(); | |
| 62 | + | |
| 63 | + /** | |
| 64 | + * 获得动态表单列表 | |
| 65 | + * | |
| 58 | 66 | * @param ids 编号 |
| 59 | 67 | * @return 动态表单列表 |
| 60 | 68 | */ | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/service/form/impl/BpmFormServiceImpl.java
| ... | ... | @@ -76,6 +76,11 @@ public class BpmFormServiceImpl implements BpmFormService { |
| 76 | 76 | } |
| 77 | 77 | |
| 78 | 78 | @Override |
| 79 | + public List<BpmFormDO> getFormList() { | |
| 80 | + return formMapper.selectList(); | |
| 81 | + } | |
| 82 | + | |
| 83 | + @Override | |
| 79 | 84 | public List<BpmFormDO> getFormList(Collection<Long> ids) { |
| 80 | 85 | return formMapper.selectBatchIds(ids); |
| 81 | 86 | } | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/service/model/impl/BpmModelServiceImpl.java
| ... | ... | @@ -13,6 +13,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 13 | 13 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 14 | 14 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 15 | 15 | import cn.iocoder.yudao.framework.common.util.object.PageUtils; |
| 16 | +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | |
| 16 | 17 | import lombok.extern.slf4j.Slf4j; |
| 17 | 18 | import org.activiti.engine.RepositoryService; |
| 18 | 19 | import org.activiti.engine.repository.Model; |
| ... | ... | @@ -29,8 +30,7 @@ import java.util.List; |
| 29 | 30 | import java.util.Map; |
| 30 | 31 | import java.util.Set; |
| 31 | 32 | |
| 32 | -import static cn.iocoder.yudao.adminserver.modules.bpm.enums.BpmErrorCodeConstants.BPMN_MODEL_NOT_EXISTS; | |
| 33 | -import static cn.iocoder.yudao.adminserver.modules.bpm.enums.BpmErrorCodeConstants.BPM_MODEL_KEY_EXISTS; | |
| 33 | +import static cn.iocoder.yudao.adminserver.modules.bpm.enums.BpmErrorCodeConstants.*; | |
| 34 | 34 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 35 | 35 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; |
| 36 | 36 | |
| ... | ... | @@ -89,6 +89,9 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 89 | 89 | @Override |
| 90 | 90 | public BpmModelRespVO getModel(String id) { |
| 91 | 91 | Model model = repositoryService.getModel(id); |
| 92 | + if (model == null) { | |
| 93 | + return null; | |
| 94 | + } | |
| 92 | 95 | BpmModelRespVO modelRespVO = ModelConvert.INSTANCE.convert(model); |
| 93 | 96 | // 拼接 bpmn XML |
| 94 | 97 | byte[] bpmnBytes = repositoryService.getModelEditorSource(id); |
| ... | ... | @@ -99,12 +102,12 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 99 | 102 | @Override |
| 100 | 103 | @Transactional(rollbackFor = Exception.class) // 因为进行多个 activiti 操作,所以开启事务 |
| 101 | 104 | public String createModel(BpmModelCreateReqVO createReqVO) { |
| 105 | + checkKeyNCName(createReqVO.getKey()); | |
| 102 | 106 | // 校验流程标识已经存在 |
| 103 | 107 | Model keyModel = this.getModelByKey(createReqVO.getKey()); |
| 104 | 108 | if (keyModel != null) { |
| 105 | - throw exception(BPM_MODEL_KEY_EXISTS); | |
| 109 | + throw exception(BPM_MODEL_KEY_EXISTS, createReqVO.getKey()); | |
| 106 | 110 | } |
| 107 | - // TODO @芋艿:需要校验下 key 的格式 | |
| 108 | 111 | |
| 109 | 112 | // 创建流程定义 |
| 110 | 113 | Model model = repositoryService.newModel(); |
| ... | ... | @@ -119,12 +122,12 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 119 | 122 | @Override |
| 120 | 123 | @Transactional(rollbackFor = Exception.class) // 因为进行多个 activiti 操作,所以开启事务 |
| 121 | 124 | public void updateModel(BpmModelUpdateReqVO updateReqVO) { |
| 125 | + checkKeyNCName(updateReqVO.getKey()); | |
| 122 | 126 | // 校验流程模型存在 |
| 123 | 127 | Model model = repositoryService.getModel(updateReqVO.getId()); |
| 124 | 128 | if (model == null) { |
| 125 | 129 | throw exception(BPMN_MODEL_NOT_EXISTS); |
| 126 | 130 | } |
| 127 | - // TODO @芋艿:需要校验下 key 的格式 | |
| 128 | 131 | |
| 129 | 132 | // 修改流程定义 |
| 130 | 133 | ModelConvert.INSTANCE.copy(model, updateReqVO); |
| ... | ... | @@ -173,4 +176,10 @@ public class BpmModelServiceImpl implements BpmModelService { |
| 173 | 176 | return repositoryService.createModelQuery().modelKey(key).singleResult(); |
| 174 | 177 | } |
| 175 | 178 | |
| 179 | + private void checkKeyNCName(String key) { | |
| 180 | + if (!ValidationUtils.isXmlNCName(key)) { | |
| 181 | + throw exception(BPMN_MODEL_KEY_VALID); | |
| 182 | + } | |
| 183 | + } | |
| 184 | + | |
| 176 | 185 | } | ... | ... |
yudao-admin-ui/src/api/bpm/form.js
yudao-admin-ui/src/components/RADME.md
| ... | ... | @@ -5,4 +5,17 @@ github 地址:https://github.com/JakHuang/form-generator |
| 5 | 5 | * generator |
| 6 | 6 | * parser |
| 7 | 7 | * render |
| 8 | -* tinymce | |
| 9 | 8 | \ No newline at end of file |
| 9 | +* tinymce | |
| 10 | + | |
| 11 | +## bpmn-process-designer | |
| 12 | + | |
| 13 | +github 地址:https://github.com/miyuesc/bpmn-process-designer | |
| 14 | + | |
| 15 | +* bpmnProcessDesigner | |
| 16 | + | |
| 17 | +TODO 目前存在的问题,如果选择 activiti 类型时,因为不支持内置表单的设计,所以会报 Error: unknown type activiti:FormData 错误。具体可见 https://github.com/miyuesc/bpmn-process-designer/issues/16 。 | |
| 18 | + | |
| 19 | +另外几个流程设计器的选型: | |
| 20 | + | |
| 21 | +* https://gitee.com/jimlow/vue-bpmn 相比差一些,已经停止维护,不算推荐。 | |
| 22 | +* https://github.com/GoldSubmarine/workflow-bpmn-modeler 仅支持 flowable 流程引擎。如果只考虑 flowable 的话,也是非常不错的选择。 | |
| 10 | 23 | \ No newline at end of file | ... | ... |
yudao-admin-ui/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
| ... | ... | @@ -4,7 +4,7 @@ |
| 4 | 4 | <slot name="control-header"></slot> |
| 5 | 5 | <template v-if="!$slots['control-header']"> |
| 6 | 6 | <el-button-group key="file-control"> |
| 7 | - <el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-folder-opened" @click="$refs.refFile.click()">打开文件</el-button> | |
| 7 | + <el-button :size="headerButtonSize" icon="el-icon-folder-opened" @click="$refs.refFile.click()">打开文件</el-button> | |
| 8 | 8 | <el-tooltip effect="light"> |
| 9 | 9 | <div slot="content"> |
| 10 | 10 | <el-button :size="headerButtonSize" type="text" @click="downloadProcessAsXml()">下载为XML文件</el-button> |
| ... | ... | @@ -13,7 +13,7 @@ |
| 13 | 13 | <br /> |
| 14 | 14 | <el-button :size="headerButtonSize" type="text" @click="downloadProcessAsBpmn()">下载为BPMN文件</el-button> |
| 15 | 15 | </div> |
| 16 | - <el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-download">下载文件</el-button> | |
| 16 | + <el-button :size="headerButtonSize" icon="el-icon-download">下载文件</el-button> | |
| 17 | 17 | </el-tooltip> |
| 18 | 18 | <el-tooltip effect="light"> |
| 19 | 19 | <div slot="content"> |
| ... | ... | @@ -21,10 +21,10 @@ |
| 21 | 21 | <br /> |
| 22 | 22 | <el-button :size="headerButtonSize" type="text" @click="previewProcessJson">预览JSON</el-button> |
| 23 | 23 | </div> |
| 24 | - <el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-view">预览</el-button> | |
| 24 | + <el-button :size="headerButtonSize" icon="el-icon-view">预览</el-button> | |
| 25 | 25 | </el-tooltip> |
| 26 | 26 | <el-tooltip v-if="simulation" effect="light" :content="this.simulationStatus ? '退出模拟' : '开启模拟'"> |
| 27 | - <el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-cpu" @click="processSimulation"> | |
| 27 | + <el-button :size="headerButtonSize" icon="el-icon-cpu" @click="processSimulation"> | |
| 28 | 28 | 模拟 |
| 29 | 29 | </el-button> |
| 30 | 30 | </el-tooltip> |
| ... | ... | @@ -72,6 +72,7 @@ |
| 72 | 72 | <el-button :size="headerButtonSize" icon="el-icon-refresh" @click="processRestart" /> |
| 73 | 73 | </el-tooltip> |
| 74 | 74 | </el-button-group> |
| 75 | + <el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-plus" @click="processSave">保存模型</el-button> | |
| 75 | 76 | </template> |
| 76 | 77 | <!-- 用于打开本地文件--> |
| 77 | 78 | <input type="file" id="files" ref="refFile" style="display: none" accept=".xml, .bpmn" @change="importLocalFile" /> |
| ... | ... | @@ -111,8 +112,9 @@ export default { |
| 111 | 112 | componentName: "MyProcessDesigner", |
| 112 | 113 | props: { |
| 113 | 114 | value: String, // xml 字符串 |
| 114 | - processId: String, | |
| 115 | - processName: String, | |
| 115 | + processId: String, // 流程 key 标识 | |
| 116 | + processName: String, // 流程 name 名字 | |
| 117 | + formId: Number, // 流程 form 表单编号 | |
| 116 | 118 | translations: Object, // 自定义的翻译文件 |
| 117 | 119 | additionalModel: [Object, Array], // 自定义model |
| 118 | 120 | moddleExtension: Object, // 自定义moddle |
| ... | ... | @@ -244,6 +246,11 @@ export default { |
| 244 | 246 | this.bpmnModeler = null; |
| 245 | 247 | }); |
| 246 | 248 | }, |
| 249 | + watch: { | |
| 250 | + value: function (newValue) { // 在 xmlString 发生变化时,重新创建,从而绘制流程图 | |
| 251 | + this.createNewDiagram(newValue); | |
| 252 | + } | |
| 253 | + }, | |
| 247 | 254 | methods: { |
| 248 | 255 | initBpmnModeler() { |
| 249 | 256 | if (this.bpmnModeler) return; |
| ... | ... | @@ -445,6 +452,17 @@ export default { |
| 445 | 452 | this.previewType = "json"; |
| 446 | 453 | this.previewModelVisible = true; |
| 447 | 454 | }); |
| 455 | + }, | |
| 456 | + /* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */ | |
| 457 | + async processSave() { | |
| 458 | + const { err, xml } = await this.bpmnModeler.saveXML(); | |
| 459 | + // 读取异常时抛出异常 | |
| 460 | + if (err) { | |
| 461 | + this.msgError('保存模型失败,请重试!') | |
| 462 | + return | |
| 463 | + } | |
| 464 | + // 触发 save 事件 | |
| 465 | + this.$emit('save', xml) | |
| 448 | 466 | } |
| 449 | 467 | } |
| 450 | 468 | }; | ... | ... |
yudao-admin-ui/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
| ... | ... | @@ -3,7 +3,8 @@ |
| 3 | 3 | <el-collapse v-model="activeTab"> |
| 4 | 4 | <el-collapse-item name="base"> |
| 5 | 5 | <div slot="title" class="panel-tab__title"><i class="el-icon-info"></i>常规</div> |
| 6 | - <element-base-info :id-edit-disabled="idEditDisabled" :business-object="elementBusinessObject" :type="elementType" /> | |
| 6 | + <element-base-info :id-edit-disabled="idEditDisabled" :business-object="elementBusinessObject" :type="elementType" | |
| 7 | + :model="model" /> | |
| 7 | 8 | </el-collapse-item> |
| 8 | 9 | <el-collapse-item name="condition" v-if="elementType === 'Process'" key="message"> |
| 9 | 10 | <div slot="title" class="panel-tab__title"><i class="el-icon-s-comment"></i>消息与信号</div> |
| ... | ... | @@ -89,7 +90,8 @@ export default { |
| 89 | 90 | idEditDisabled: { |
| 90 | 91 | type: Boolean, |
| 91 | 92 | default: false |
| 92 | - } | |
| 93 | + }, | |
| 94 | + model: Object, // 流程模型的数据 | |
| 93 | 95 | }, |
| 94 | 96 | provide() { |
| 95 | 97 | return { |
| ... | ... | @@ -159,6 +161,7 @@ export default { |
| 159 | 161 | // 初始化数据 |
| 160 | 162 | initFormOnChanged(element) { |
| 161 | 163 | let activatedElement = element; |
| 164 | + // debugger | |
| 162 | 165 | if (!activatedElement) { |
| 163 | 166 | activatedElement = |
| 164 | 167 | window.bpmnInstances.elementRegistry.find(el => el.type === "bpmn:Process") ?? | ... | ... |
yudao-admin-ui/src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue
| 1 | 1 | <template> |
| 2 | 2 | <div class="panel-tab__content"> |
| 3 | - <el-form size="mini" label-width="90px" @submit.native.prevent> | |
| 4 | - <el-form-item label="ID"> | |
| 5 | - <el-input | |
| 6 | - v-model="elementBaseInfo.id" | |
| 7 | - :disabled="idEditDisabled || elementBaseInfo.$type === 'bpmn:Process'" | |
| 8 | - clearable | |
| 9 | - @change="updateBaseInfo('id')" | |
| 10 | - /> | |
| 3 | + <el-form size="mini" label-width="90px" :model="model" :rules="rules" @submit.native.prevent> | |
| 4 | + <el-form-item label="流程标识" prop="key"> | |
| 5 | + <el-input v-model="model.key" placeholder="请输入流标标识" | |
| 6 | + :disabled="model.id !== undefined && model.id.length > 0"/> | |
| 11 | 7 | </el-form-item> |
| 12 | - <el-form-item label="名称"> | |
| 13 | - <el-input v-model="elementBaseInfo.name" clearable @change="updateBaseInfo('name')" /> | |
| 8 | + <el-form-item label="流程名称" prop="name"> | |
| 9 | + <el-input v-model="model.name" placeholder="请输入流程名称" clearable /> | |
| 10 | + </el-form-item> | |
| 11 | + <el-form-item label="流程分类" prop="category"> | |
| 12 | + <el-select v-model="model.category" placeholder="请选择流程分类" clearable style="width: 100%"> | |
| 13 | + <el-option v-for="dict in categoryDictDatas" :key="dict.value" :label="dict.label" :value="dict.value"/> | |
| 14 | + </el-select> | |
| 15 | + </el-form-item> | |
| 16 | + <el-form-item label="流程表单" prop="formId"> | |
| 17 | + <el-select v-model="model.formId" placeholder="请选择流程表单,非必选哟!" clearable style="width: 100%"> | |
| 18 | + <el-option v-for="form in forms" :key="form.id" :label="form.name" :value="form.id"/> | |
| 19 | + </el-select> | |
| 20 | + </el-form-item> | |
| 21 | + <el-form-item label="流程描述" prop="description"> | |
| 22 | + <el-input type="textarea" v-model="model.description" clearable /> | |
| 14 | 23 | </el-form-item> |
| 15 | - <!--流程的基础属性--> | |
| 16 | - <template v-if="elementBaseInfo.$type === 'bpmn:Process'"> | |
| 17 | - <el-form-item label="版本标签"> | |
| 18 | - <el-input v-model="elementBaseInfo.versionTag" clearable @change="updateBaseInfo('versionTag')" /> | |
| 19 | - </el-form-item> | |
| 20 | - <el-form-item label="可执行"> | |
| 21 | - <el-switch v-model="elementBaseInfo.isExecutable" active-text="是" inactive-text="否" @change="updateBaseInfo('isExecutable')" /> | |
| 22 | - </el-form-item> | |
| 23 | - </template> | |
| 24 | 24 | </el-form> |
| 25 | 25 | </div> |
| 26 | 26 | </template> |
| 27 | 27 | <script> |
| 28 | +import {DICT_TYPE, getDictDatas} from "@/utils/dict"; | |
| 29 | +import {getSimpleForms} from "@/api/bpm/form"; | |
| 30 | +import {getModel} from "@/api/bpm/model"; | |
| 31 | + | |
| 28 | 32 | export default { |
| 29 | 33 | name: "ElementBaseInfo", |
| 30 | 34 | props: { |
| 31 | 35 | businessObject: Object, |
| 32 | - type: String, | |
| 33 | - idEditDisabled: { | |
| 34 | - type: Boolean, | |
| 35 | - default: true | |
| 36 | - } | |
| 36 | + model: Object, // 流程模型的数据 | |
| 37 | 37 | }, |
| 38 | 38 | data() { |
| 39 | 39 | return { |
| 40 | - elementBaseInfo: {} | |
| 40 | + elementBaseInfo: {}, | |
| 41 | + // 流程表单的下拉框的数据 | |
| 42 | + forms: [], | |
| 43 | + // 流程模型的校验 | |
| 44 | + rules: { | |
| 45 | + key: [{ required: true, message: "流程标识不能为空", trigger: "blur" }], | |
| 46 | + name: [{ required: true, message: "流程名称不能为空", trigger: "blur" }], | |
| 47 | + category: [{ required: true, message: "流程分类不能为空", trigger: "blur" }], | |
| 48 | + }, | |
| 49 | + // 数据字典 | |
| 50 | + categoryDictDatas: getDictDatas(DICT_TYPE.BPM_MODEL_CATEGORY), | |
| 41 | 51 | }; |
| 42 | 52 | }, |
| 43 | 53 | watch: { |
| ... | ... | @@ -50,12 +60,19 @@ export default { |
| 50 | 60 | } |
| 51 | 61 | } |
| 52 | 62 | }, |
| 63 | + created() { | |
| 64 | + // 获得流程表单的下拉框的数据 | |
| 65 | + getSimpleForms().then(response => { | |
| 66 | + this.forms = response.data | |
| 67 | + }) | |
| 68 | + }, | |
| 53 | 69 | methods: { |
| 54 | 70 | resetBaseInfo() { |
| 55 | 71 | this.bpmnElement = window?.bpmnInstances?.bpmnElement; |
| 56 | 72 | this.elementBaseInfo = JSON.parse(JSON.stringify(this.bpmnElement.businessObject)); |
| 57 | 73 | }, |
| 58 | 74 | updateBaseInfo(key) { |
| 75 | + // 触发 elementBaseInfo 对应的字段 | |
| 59 | 76 | const attrObj = Object.create(null); |
| 60 | 77 | attrObj[key] = this.elementBaseInfo[key]; |
| 61 | 78 | if (key === "id") { | ... | ... |
yudao-admin-ui/src/main.js
| ... | ... | @@ -76,11 +76,10 @@ Vue.component('RightToolbar', RightToolbar) |
| 76 | 76 | Vue.use(permission) |
| 77 | 77 | // Vue.use(hljs.vuePlugin); |
| 78 | 78 | |
| 79 | -// TODO 芋艿:bpmnProcessDesigner 引入 | |
| 79 | +// bpmnProcessDesigner 需要引入 | |
| 80 | 80 | import MyPD from "@/components/bpmnProcessDesigner/package/index.js"; |
| 81 | 81 | Vue.use(MyPD); |
| 82 | 82 | import "@/components/bpmnProcessDesigner/package/theme/index.scss"; |
| 83 | -// TODO 芋艿:bpmnProcessDesigner 引入 | |
| 84 | 83 | import "bpmn-js/dist/assets/diagram-js.css"; |
| 85 | 84 | import "bpmn-js/dist/assets/bpmn-font/css/bpmn.css"; |
| 86 | 85 | import "bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css"; | ... | ... |
yudao-admin-ui/src/views/bpm/model/index.vue
| ... | ... | @@ -67,12 +67,6 @@ |
| 67 | 67 | <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize" |
| 68 | 68 | @pagination="getList"/> |
| 69 | 69 | |
| 70 | - <!-- 流程编辑器 --> | |
| 71 | -<!-- <el-dialog class="bpmnclass dialogClass" :visible.sync="showBpmnOpen" :before-cancel="cancel" :fullscreen="true">--> | |
| 72 | -<!-- <vue-bpmn v-if="showBpmnOpen" product="activiti" @processSave="processSave"--> | |
| 73 | -<!-- :bpmnXml="bpmnXML" :bpmnData="bpmnData" @beforeClose="cancel" />--> | |
| 74 | -<!-- </el-dialog>--> | |
| 75 | - | |
| 76 | 70 | <!-- 流程表单配置详情 --> |
| 77 | 71 | <el-dialog title="表单详情" :visible.sync="detailOpen" width="50%" append-to-body> |
| 78 | 72 | <div class="test-form"> |
| ... | ... | @@ -83,8 +77,7 @@ |
| 83 | 77 | </template> |
| 84 | 78 | |
| 85 | 79 | <script> |
| 86 | -import {deleteModel, deployModel, createModel, updateModel, getModelPage, getModel} from "@/api/bpm/model"; | |
| 87 | -// import VueBpmn from "@/components/bpmn/VueBpmn"; | |
| 80 | +import {deleteModel, deployModel, getModelPage} from "@/api/bpm/model"; | |
| 88 | 81 | import {DICT_TYPE, getDictDatas} from "@/utils/dict"; |
| 89 | 82 | import {getForm} from "@/api/bpm/form"; |
| 90 | 83 | import {decodeFields} from "@/utils/formGenerator"; |
| ... | ... | @@ -93,8 +86,7 @@ import Parser from '@/components/parser/Parser' |
| 93 | 86 | export default { |
| 94 | 87 | name: "model", |
| 95 | 88 | components: { |
| 96 | - Parser, | |
| 97 | - // VueBpmn | |
| 89 | + Parser | |
| 98 | 90 | }, |
| 99 | 91 | data() { |
| 100 | 92 | return { |
| ... | ... | @@ -157,55 +149,22 @@ export default { |
| 157 | 149 | this.resetForm("queryForm"); |
| 158 | 150 | this.handleQuery(); |
| 159 | 151 | }, |
| 160 | - processSave(data) { | |
| 161 | - // TODO 芋艿:临时写死的参数 | |
| 162 | - data.category = '1' | |
| 163 | - data.formId = 11 | |
| 164 | - | |
| 165 | - // 修改的提交 | |
| 166 | - if (data.id) { | |
| 167 | - updateModel(data).then(response => { | |
| 168 | - this.msgSuccess("修改成功"); | |
| 169 | - // 关闭弹窗,刷新列表 | |
| 170 | - this.showBpmnOpen = false | |
| 171 | - this.getList(); | |
| 172 | - }) | |
| 173 | - return | |
| 174 | - } | |
| 175 | - // 添加的提交 | |
| 176 | - createModel(data).then(response => { | |
| 177 | - this.bpmnData.id = response.data | |
| 178 | - this.msgSuccess("保存成功"); | |
| 179 | - // 关闭弹窗,刷新列表 | |
| 180 | - this.showBpmnOpen = false | |
| 181 | - this.getList(); | |
| 182 | - }) | |
| 183 | - }, | |
| 152 | + /** 新增按钮操作 */ | |
| 184 | 153 | handleAdd() { |
| 185 | - // 重置 Model 信息 | |
| 186 | - this.reset() | |
| 187 | - // 打开弹窗 | |
| 188 | - this.showBpmnOpen = true | |
| 189 | - }, | |
| 190 | - cancel() { | |
| 191 | - // 打开弹窗 | |
| 192 | - this.showBpmnOpen = false | |
| 193 | - // 重置 Model 信息 | |
| 194 | - this.reset() | |
| 195 | - // 刷新列表 | |
| 196 | - this.getList() | |
| 154 | + this.$router.push({ | |
| 155 | + path:"/bpm/manager/model/edit" | |
| 156 | + }); | |
| 197 | 157 | }, |
| 158 | + /** 修改按钮操作 */ | |
| 198 | 159 | handleUpdate(row) { |
| 199 | - // 重置 Model 信息 | |
| 200 | - this.reset() | |
| 201 | - // 获得 Model 信息 | |
| 202 | - getModel(row.id).then(response => { | |
| 203 | - this.bpmnXML = response.data.bpmnXml | |
| 204 | - this.bpmnData = response.data | |
| 205 | - // 打开弹窗 | |
| 206 | - this.showBpmnOpen = true | |
| 207 | - }) | |
| 160 | + this.$router.push({ | |
| 161 | + path:"/bpm/manager/model/edit", | |
| 162 | + query:{ | |
| 163 | + modelId: row.id | |
| 164 | + } | |
| 165 | + }); | |
| 208 | 166 | }, |
| 167 | + /** 删除按钮操作 */ | |
| 209 | 168 | handleDelete(row) { |
| 210 | 169 | const that = this; |
| 211 | 170 | this.$confirm('是否删除该流程!!', "警告", { |
| ... | ... | @@ -219,6 +178,7 @@ export default { |
| 219 | 178 | }) |
| 220 | 179 | }) |
| 221 | 180 | }, |
| 181 | + /** 部署按钮操作 */ | |
| 222 | 182 | handleDeploy(row) { |
| 223 | 183 | const that = this; |
| 224 | 184 | this.$confirm('是否部署该流程!!', "提示", { |
| ... | ... | @@ -248,19 +208,3 @@ export default { |
| 248 | 208 | } |
| 249 | 209 | }; |
| 250 | 210 | </script> |
| 251 | -<style> | |
| 252 | -.el-dialog > .el-dialog__body{ | |
| 253 | - margin: 0; | |
| 254 | - border: 0; | |
| 255 | -} | |
| 256 | -.bpmn-viewer-header{ | |
| 257 | - background: white; | |
| 258 | -} | |
| 259 | -.v-modal{ | |
| 260 | - z-index: 2000!important; | |
| 261 | -} | |
| 262 | -.dialogClass{ | |
| 263 | - padding: 0 ; | |
| 264 | -} | |
| 265 | -</style> | |
| 266 | - | ... | ... |
yudao-admin-ui/src/views/bpm/model/modelEditor.vue
| 1 | 1 | <template> |
| 2 | 2 | <div class="app-container"> |
| 3 | 3 | |
| 4 | - <!-- 流程编辑器 --> | |
| 4 | + <!-- 流程设计器,负责绘制流程等 --> | |
| 5 | 5 | <my-process-designer :key="`designer-${reloadIndex}`" v-model="xmlString" v-bind="controlForm" |
| 6 | - keyboard ref="processDesigner" @init-finished="initModeler"/> | |
| 7 | - <!-- 右边工具栏 --> | |
| 8 | - <my-properties-panel :key="`penal-${reloadIndex}`" :bpmn-modeler="modeler" :prefix="controlForm.prefix" class="process-panel" /> | |
| 6 | + keyboard ref="processDesigner" @init-finished="initModeler" | |
| 7 | + @save="save"/> | |
| 8 | + | |
| 9 | + <!-- 流程属性器,负责编辑每个流程节点的属性 --> | |
| 10 | + <my-properties-panel :key="`penal-${reloadIndex}`" :bpmn-modeler="modeler" :prefix="controlForm.prefix" class="process-panel" | |
| 11 | + :model="model" /> | |
| 9 | 12 | |
| 10 | 13 | </div> |
| 11 | 14 | </template> |
| ... | ... | @@ -18,6 +21,7 @@ import CustomContentPadProvider from "@/components/bpmnProcessDesigner/package/d |
| 18 | 21 | import CustomPaletteProvider from "@/components/bpmnProcessDesigner/package/designer/plugins/palette"; |
| 19 | 22 | // import xmlObj2json from "./utils/xml2json"; |
| 20 | 23 | import MyProcessPalette from "@/components/bpmnProcessDesigner/package/palette/ProcessPalette"; |
| 24 | +import {createModel, getModel, updateModel} from "@/api/bpm/model"; | |
| 21 | 25 | // 自定义侧边栏 |
| 22 | 26 | // import MyProcessPanel from "../package/process-panel/ProcessPanel"; |
| 23 | 27 | |
| ... | ... | @@ -32,23 +36,34 @@ export default { |
| 32 | 36 | controlDrawerVisible: false, |
| 33 | 37 | translationsSelf: translations, |
| 34 | 38 | controlForm: { |
| 35 | - processId: "", | |
| 36 | - processName: "", | |
| 37 | 39 | simulation: true, |
| 38 | 40 | labelEditing: false, |
| 39 | 41 | labelVisible: false, |
| 40 | 42 | prefix: "activiti", |
| 41 | 43 | headerButtonSize: "mini", |
| 42 | - // additionalModel: [] | |
| 43 | 44 | additionalModel: [CustomContentPadProvider, CustomPaletteProvider] |
| 44 | 45 | }, |
| 45 | 46 | addis: { |
| 46 | 47 | CustomContentPadProvider, |
| 47 | 48 | CustomPaletteProvider |
| 48 | - } | |
| 49 | + }, | |
| 50 | + // 流程模型的信息 | |
| 51 | + model: {}, | |
| 49 | 52 | }; |
| 50 | 53 | }, |
| 51 | - created() {}, | |
| 54 | + created() { | |
| 55 | + // 如果 modelId 非空,说明是修改流程模型 | |
| 56 | + const modelId = this.$route.query && this.$route.query.modelId | |
| 57 | + if (modelId) { | |
| 58 | + getModel(modelId).then(response => { | |
| 59 | + this.xmlString = response.data.bpmnXml | |
| 60 | + this.model = { | |
| 61 | + ...response.data, | |
| 62 | + bpmnXml: undefined, // 清空 bpmnXml 属性 | |
| 63 | + } | |
| 64 | + }) | |
| 65 | + } | |
| 66 | + }, | |
| 52 | 67 | methods: { |
| 53 | 68 | initModeler(modeler) { |
| 54 | 69 | setTimeout(() => { |
| ... | ... | @@ -70,7 +85,34 @@ export default { |
| 70 | 85 | // this.xmlString = undefined; |
| 71 | 86 | // this.$refs.processDesigner.processRestart(); |
| 72 | 87 | // } |
| 73 | - } | |
| 88 | + }, | |
| 89 | + save(bpmnXml) { | |
| 90 | + const data = { | |
| 91 | + ...this.model, | |
| 92 | + bpmnXml: bpmnXml, // this.bpmnXml 只是初始化流程图,后续修改无法通过它获得 | |
| 93 | + } | |
| 94 | + | |
| 95 | + // 修改的提交 | |
| 96 | + if (data.id) { | |
| 97 | + updateModel(data).then(response => { | |
| 98 | + this.msgSuccess("修改成功") | |
| 99 | + // 跳转回去 | |
| 100 | + this.close() | |
| 101 | + }) | |
| 102 | + return | |
| 103 | + } | |
| 104 | + // 添加的提交 | |
| 105 | + createModel(data).then(response => { | |
| 106 | + this.msgSuccess("保存成功") | |
| 107 | + // 跳转回去 | |
| 108 | + this.close() | |
| 109 | + }) | |
| 110 | + }, | |
| 111 | + /** 关闭按钮 */ | |
| 112 | + close() { | |
| 113 | + this.$store.dispatch("tagsView/delView", this.$route); | |
| 114 | + this.$router.push({ path: "/bpm/manager/model", query: { t: Date.now()}}) | |
| 115 | + }, | |
| 74 | 116 | } |
| 75 | 117 | }; |
| 76 | 118 | </script> | ... | ... |
yudao-admin-ui/vue.config.js
| ... | ... | @@ -35,6 +35,7 @@ module.exports = { |
| 35 | 35 | // detail: https://cli.vuejs.org/config/#devserver-proxy |
| 36 | 36 | [process.env.VUE_APP_BASE_API]: { |
| 37 | 37 | target: `http://localhost:48080`, |
| 38 | + // target: `http://api-dashboard.yudao.iocoder.cn`, | |
| 38 | 39 | changeOrigin: true, |
| 39 | 40 | pathRewrite: { |
| 40 | 41 | ['^' + process.env.VUE_APP_BASE_API]: '' | ... | ... |
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/validation/ValidationUtils.java
| ... | ... | @@ -12,7 +12,9 @@ import java.util.regex.Pattern; |
| 12 | 12 | */ |
| 13 | 13 | public class ValidationUtils { |
| 14 | 14 | |
| 15 | - private static Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"); | |
| 15 | + private static final Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"); | |
| 16 | + | |
| 17 | + private static final Pattern PATTERN_XML_NCNAME = Pattern.compile("[a-zA-Z_][\\-_.0-9_a-zA-Z$]*"); | |
| 16 | 18 | |
| 17 | 19 | public static boolean isMobile(String mobile) { |
| 18 | 20 | if (StrUtil.length(mobile) != 11) { |
| ... | ... | @@ -27,4 +29,9 @@ public class ValidationUtils { |
| 27 | 29 | && PATTERN_URL.matcher(url).matches(); |
| 28 | 30 | } |
| 29 | 31 | |
| 32 | + public static boolean isXmlNCName(String str) { | |
| 33 | + return StringUtils.hasText(str) | |
| 34 | + && PATTERN_XML_NCNAME.matcher(str).matches(); | |
| 35 | + } | |
| 36 | + | |
| 30 | 37 | } | ... | ... |