关于ruoyi框架集成activiti步骤
简介
此集成参照的是gitee作者 CalvinHwang123源代码位置的集成方式,若依属于前后端分离的版本3.5.0。
- ruoyi3.5.0
- activiti6.0
傻瓜式集成步骤(后端部分)
- 在ruoyi根目录添加新的模块,命名为ruoyi-activiti,得到如下结构:
- 在父pom.xml文件中设置全局activiti版本
- 在ruoyi-activiti的pom.xml中加入如下依赖:
<dependencies>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-rest-api</artifactId>
<version>${activiti.version}</version>
<exclusions>
<exclusion>
<artifactId>mybatis</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-framework</artifactId>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
</dependency>
<!--activiti modeler 5.22 start-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>6.0.0</version>
<exclusions>
<exclusion>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-model</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- xml解析依赖-->
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-codec</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-css</artifactId>
<version> 1.7</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-svg-dom</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-svggen</artifactId>
<version>1.7</version>
</dependency>
<!-- xml解析依赖-->
<!--activiti modeler 5.22 end-->
</dependencies>
- 在ruoyi-admin的pom.xml中加入如下依赖:
<!-- 依赖activiti -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-activiti</artifactId>
<version>3.5.0</version>
</dependency>
- 在ruoyi-admin的application.yml文件中加入如下配置(在spring配置的下级节点上加):
# activiti 模块
# 解决启动报错:class path resource [processes/] cannot be resolved to URL because it does not exist
activiti:
check-process-definitions: false
# 检测身份信息表是否存在
db-identity-used: false
- 在 ruoyi-admin的application-druid.yml文件中的数据库路径最后加上:&nullCatalogMeansCurrent=true
如:
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/dcm?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
username: admin
password: 1234
- 在ruoyi启动类RuoYiApplication上加入注解:
/**
* 启动程序
*
* @author ruoyi
*/
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
org.activiti.spring.boot.SecurityAutoConfiguration.class })
public class RuoYiApplication
{
public static void main(String[] args)
{
// System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(RuoYiApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 若依启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" +
" | _ _ \\ \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" +
" |(_ o _) / _( )_ .' \n" +
" | (_,_).' __ ___(_ o _)' \n" +
" | |\\ \\ | || |(_,_)' \n" +
" | | \\ `' /| `-' / \n" +
" | | \\ / \\ / \n" +
" ''-' `'-' `-..-' ");
}
}
9.启动测试,看ruoyi集成activiti有没有成功,如果成功了会自动生成以下表:
ACT_EVT_LOG
ACT_GE_BYTEARRAY
ACT_GE_PROPERTY
ACT_HI_ACTINST
ACT_HI_ATTACHMENT
ACT_HI_COMMENT
ACT_HI_DETAIL
ACT_HI_IDENTITYLINK
ACT_HI_PROCINST
ACT_HI_TASKINST
ACT_HI_VARINST
ACT_PROCDEF_INFO
ACT_RE_DEPLOYMENT
ACT_RE_MODEL
ACT_RE_PROCDEF
ACT_RU_DEADLETTER_JOB
ACT_RU_EVENT_SUBSCR
ACT_RU_EXECUTION
ACT_RU_IDENTITYLINK
ACT_RU_JOB
ACT_RU_SUSPENDED_JOB
ACT_RU_TASK
ACT_RU_TIMER_JOB
ACT_RU_VARIABLE
- 对资源进行放行:
在com.ruoyi.framework.config.SecurityConfig中加入如下代码:
//activiti相关接口放行
.antMatchers("/modeler/**").anonymous()
.antMatchers("/activiti/definition/upload").anonymous()
.antMatchers("/activiti/definition/readResource").anonymous()
.antMatchers("/activiti/process/read-resource").anonymous()
- 你可以直接在浏览器上输入:http://localhost:8080/management/tables进行访问,看是否可以返回表信息
12.再拿到作者的源码,再ruoyi-activiti的resources下拷贝以下文件:
- 再到ruoyi-activiti下加入以下包名com.ruoyi.activiti.modeler,并拷贝添加以下类:
ModelEditorJsonRestResource:
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ruoyi.activiti.modeler;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Model;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* @author Tijs Rademakers
* @author 一只闲鹿
*/
@RestController
public class ModelEditorJsonRestResource extends BaseController implements ModelDataJsonConstants {
private static final Logger LOGGER = LoggerFactory.getLogger(ModelEditorJsonRestResource.class);
@Autowired
private RepositoryService repositoryService;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ISysUserService userService;
@Autowired
private ISysRoleService roleService;
@Autowired
private SysUserMapper userMapper;
@RequestMapping(value="/modeler/model/{modelId}/json", method = RequestMethod.GET, produces = "application/json")
public ObjectNode getEditorJson(@PathVariable String modelId) {
ObjectNode modelNode = null;
Model model = repositoryService.getModel(modelId);
if (model != null) {
try {
if (StringUtils.isNotEmpty(model.getMetaInfo())) {
modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
} else {
modelNode = objectMapper.createObjectNode();
modelNode.put(MODEL_NAME, model.getName());
}
modelNode.put(MODEL_ID, model.getId());
ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(
new String(repositoryService.getModelEditorSource(model.getId()), StandardCharsets.UTF_8));
modelNode.put("model", editorJsonNode);
} catch (Exception e) {
LOGGER.error("Error creating model JSON", e);
throw new ActivitiException("Error creating model JSON", e);
}
}
return modelNode;
}
/**
* 免登录访问用户列表
* @param user
* @return
*/
@GetMapping("/list")
public TableDataInfo list(SysUser user)
{
startPage();
List<SysUser> list = userService.selectUserList(user);
return getDataTable(list);
}
/**
* 角色列表
* @param role
* @return
*/
@GetMapping("/modeler/role/list")
public TableDataInfo list(SysRole role)
{
startPage();
List<SysRole> list = roleService.selectRoleList(role);
return getDataTable(list);
}
/**
* 某角色下的用户列表
* @param roleKey
* @return
*/
@GetMapping("/modeler/user/listByRoleKey")
public TableDataInfo list(String roleKey)
{
startPage();
SysUser sysUser = new SysUser();
// sysUser.setRoleIds();
List<SysUser> list = userMapper.selectUserList(sysUser);
return getDataTable(list);
}
}
ModelerController:
package com.ruoyi.activiti.modeler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.pagehelper.Page;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.enums.BusinessType;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.persistence.entity.ModelEntityImpl;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ModelQuery;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import static org.activiti.editor.constants.ModelDataJsonConstants.MODEL_DESCRIPTION;
import static org.activiti.editor.constants.ModelDataJsonConstants.MODEL_NAME;
/**
* 流程模型控制层
*
* @author 一只闲鹿
*/
@Controller
@RequestMapping("/activiti/modeler")
public class ModelerController extends BaseController {
private static final Logger LOGGER = LoggerFactory.getLogger(ModelEditorJsonRestResource.class);
@Autowired
private RepositoryService repositoryService;
/**
* 模型列表
*/
@PreAuthorize("@ss.hasPermi('activiti:modeler:list')")
@GetMapping("/list")
@ResponseBody
public TableDataInfo list(ModelEntityImpl modelEntity) {
ModelQuery modelQuery = repositoryService.createModelQuery();
modelQuery.orderByLastUpdateTime().desc();
// 条件过滤
if (com.ruoyi.common.utils.StringUtils.isNotBlank(modelEntity.getKey())) {
modelQuery.modelKey(modelEntity.getKey());
}
if (com.ruoyi.common.utils.StringUtils.isNotBlank(modelEntity.getName())) {
modelQuery.modelNameLike("%" + modelEntity.getName() + "%");
}
PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
List<Model> resultList = modelQuery.listPage((pageNum - 1) * pageSize, pageSize);
Page<ModelEntityImpl> list = new Page<>();
resultList.parallelStream().forEach(model -> {
ModelEntityImpl modelEntity1 = (ModelEntityImpl) model;
list.add(modelEntity1);
});
list.setTotal(modelQuery.count());
list.setPageNum(pageNum);
list.setPageSize(pageSize);
return getDataTable(list);
}
/**
* 创建模型
*/
@PostMapping(value = "/create")
@ResponseBody
public AjaxResult create(@RequestParam("name") String name, @RequestParam("key") String key,
@RequestParam(value = "description", required = false) String description) {
try {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode editorNode = objectMapper.createObjectNode();
editorNode.put("id", "canvas");
editorNode.put("resourceId", "canvas");
ObjectNode stencilSetNode = objectMapper.createObjectNode();
stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
editorNode.put("stencilset", stencilSetNode);
ObjectNode modelObjectNode = objectMapper.createObjectNode();
modelObjectNode.put(MODEL_NAME, name);
modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
description = StringUtils.defaultString(description);
modelObjectNode.put(MODEL_DESCRIPTION, description);
Model newModel = repositoryService.newModel();
newModel.setMetaInfo(modelObjectNode.toString());
newModel.setName(name);
newModel.setKey(StringUtils.defaultString(key));
repositoryService.saveModel(newModel);
repositoryService.addModelEditorSource(newModel.getId(), editorNode.toString().getBytes(StandardCharsets.UTF_8));
return new AjaxResult(200, "创建模型成功", newModel.getId());
} catch (Exception e) {
logger.error("创建模型失败:", e);
}
return error();
}
/**
* 根据Model部署流程
*/
@GetMapping(value = "/deploy/{modelId}")
@ResponseBody
public AjaxResult deploy(@PathVariable("modelId") String modelId) {
try {
Model modelData = repositoryService.getModel(modelId);
ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
String processName = modelData.getName() + ".bpmn20.xml";
Deployment deployment = repositoryService.createDeployment().name(modelData.getName()).addString(processName, new String(bpmnBytes, "UTF-8")).deploy();
LOGGER.info("部署成功,部署ID=" + deployment.getId());
return success("部署成功");
} catch (Exception e) {
LOGGER.error("根据模型部署流程失败:modelId={}", modelId, e);
}
return error("部署失败");
}
/**
* 导出model的xml文件
*/
@GetMapping(value = "/export/{modelId}")
public void export(@PathVariable("modelId") String modelId, HttpServletResponse response) {
try {
Model modelData = repositoryService.getModel(modelId);
BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
JsonNode editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
// 流程非空判断
if (!CollectionUtils.isEmpty(bpmnModel.getProcesses())) {
BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel);
ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);
String filename = bpmnModel.getMainProcess().getId() + ".bpmn";
response.setHeader("Content-Disposition", "attachment; filename=" + filename);
IOUtils.copy(in, response.getOutputStream());
response.flushBuffer();
}
} catch (Exception e) {
LOGGER.error("导出model的xml文件失败:modelId={}", modelId, e);
}
}
@Log(title = "流程模型", businessType = BusinessType.DELETE)
@DeleteMapping("/remove/{ids}")
@ResponseBody
public AjaxResult remove(@PathVariable("ids") String ids) {
try {
repositoryService.deleteModel(ids);
return AjaxResult.success();
}
catch (Exception e) {
return error(e.getMessage());
}
}
}
ModelSaveRestResource:
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ruoyi.activiti.modeler;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Model;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
* @author Tijs Rademakers
*/
@RestController
public class ModelSaveRestResource implements ModelDataJsonConstants {
private static final Logger LOGGER = LoggerFactory.getLogger(ModelSaveRestResource.class);
@Autowired
private RepositoryService repositoryService;
@Autowired
private ObjectMapper objectMapper;
@RequestMapping(value="/modeler/model/{modelId}/save", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public void saveModel(@PathVariable String modelId, @RequestBody MultiValueMap<String, String> values) {
try {
Model model = repositoryService.getModel(modelId);
ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
modelJson.put(MODEL_NAME, values.getFirst("name"));
modelJson.put(MODEL_DESCRIPTION, values.getFirst("description"));
model.setMetaInfo(modelJson.toString());
model.setName(values.getFirst("name"));
repositoryService.saveModel(model);
repositoryService.addModelEditorSource(model.getId(), values.getFirst("json_xml").getBytes(StandardCharsets.UTF_8));
InputStream svgStream = new ByteArrayInputStream(values.getFirst("svg_xml").getBytes(StandardCharsets.UTF_8));
TranscoderInput input = new TranscoderInput(svgStream);
PNGTranscoder transcoder = new PNGTranscoder();
// Setup output
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
TranscoderOutput output = new TranscoderOutput(outStream);
// Do the transformation
transcoder.transcode(input, output);
final byte[] result = outStream.toByteArray();
repositoryService.addModelEditorSourceExtra(model.getId(), result);
outStream.close();
} catch (Exception e) {
LOGGER.error("Error saving model", e);
throw new ActivitiException("Error saving model", e);
}
}
}
StencilsetRestResource:
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ruoyi.activiti.modeler;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.activiti.engine.ActivitiException;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Tijs Rademakers
*/
@RestController
public class StencilsetRestResource {
@RequestMapping(value="/modeler/editor/stencilset", method = RequestMethod.GET, produces = "application/json;charset=utf-8")
public @ResponseBody String getStencilset() {
InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("stencilset.json");
try {
return IOUtils.toString(stencilsetStream, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new ActivitiException("Error while loading stencil set", e);
}
}
}
傻瓜式集成步骤(前端部分)
14.加入前端代码,也是进行拷贝就可以了:
definition.js:
import request from '@/utils/request'
// 查询modeler列表
export function listDefinition(query) {
return request({
url: '/processDefinition/list',
method: 'get',
params: query
})
}
// 查询请假详细
export function getDefinitionsByInstanceId(instanceId) {
return request({
url: '/processDefinition/getDefinitions/' + instanceId,
method: 'get'
})
}
// 挂起激活转换
export function suspendOrActiveApply(data) {
return request({
url: '/processDefinition/suspendOrActiveApply',
method: 'post',
data:data
})
}
// 删除Modeler
export function delDefinition(id) {
return request({
url: '/processDefinition/remove/' + id,
method: 'delete'
})
}
historyFormdata.js:
import request from '@/utils/request'
// 查询请假详细
export function historyFromData(instanceId) {
return request({
url: '/historyFromData/ByInstanceId/' + instanceId,
method: 'get'
})
}
modeler.js:
import request from '@/utils/request'
// 查询模型列表
export function listModeler(query) {
return request({
url: '/activiti/modeler/list',
method: 'get',
params: query
})
}
// 新增模型
export function addModeler(data) {
return request({
url: '/activiti/modeler/create',
method: 'post',
params: data
})
}
// 删除模型
export function delModeler(modelId) {
return request({
url: '/activiti/modeler/remove/' + modelId,
method: 'delete',
})
}
// 导出模型
export function exportModeler(modelId) {
return request({
url: '/activiti/modeler/export/' + modelId,
method: 'get',
})
}
// 部署模型
export function deployModeler(modelId) {
return request({
url: '/activiti/modeler/deploy/' + modelId,
method: 'get',
})
}
- 加如views代码:
definition/index.vue:
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" v-show="showSearch" :inline="true">
<el-form-item label="KEY" prop="key">
<el-input
v-model="queryParams.key"
placeholder="请输入流程KEY"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="所属分类" prop="name">
<el-input
v-model="queryParams.category"
placeholder="请输入所属分类"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
icon="el-icon-upload"
size="mini"
@click="handleUpload"
>部署流程定义</el-button>
<span style="color: red; font-size: 12px;">(查看流程图模糊时,可选择 .bpmn 和 .png 一同打成 zip 包部署)</span>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="definitionList">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="流程ID" align="center" prop="id" width="120" />
<el-table-column label="流程KEY" align="center" prop="key" width="120" />
<el-table-column label="流程名称" align="center" prop="name" width="150" />
<el-table-column label="版本" align="center" prop="version" width="90" />
<el-table-column label="流程描述" align="center" prop="description" width="150" />
<el-table-column label="所属分类" align="center" prop="category" width="120" />
<el-table-column label="部署时间" align="center" prop="deploymentTime" width="100" />
<!--<el-table-column label="流程定义" align="center" prop="resourceName" width="120">
<template slot-scope="scope">
<a
:href="baseURL + '/activiti/definition/readResource?pdid=' + scope.row.id + '&resourceName=' + scope.row.resourceName "
target="_blank"
style="color: #409EFF;"
>{{ scope.row.resourceName.substring(scope.row.resourceName.lastIndexOf('/') + 1) }}</a>
</template>
</el-table-column>
<el-table-column label="流程图" align="center" prop="diagramResourceName" width="120">
<template slot-scope="scope">
<img
style="width: 100px;"
:src="baseURL + '/activiti/definition/readResource?pdid=' + scope.row.id + '&resourceName=' + scope.row.diagramResourceName"
/>
</template>
</el-table-column>-->
<el-table-column label="状态" align="center" prop="suspendStateName" width="90" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-sort"
@click="handleState(scope.row)"
>{{ scope.row.suspendState === '2' ? '激活' : '挂起' }}</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh"
@click="handleConvert(scope.row)"
>转模型</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 部署流程定义对话框 -->
<el-dialog v-bind="$attrs" v-on="$listeners" @open="onOpen" @close="onClose" :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="elForm" :model="formData" :rules="rules" size="medium" label-width="100px">
<el-form-item label="上传" prop="upload" required>
<el-upload ref="upload" :file-list="uploadfileList" :action="uploadAction"
:before-upload="uploadBeforeUpload">
<el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="handelConfirm">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listDefinition, delDefinition, suspendOrActiveDefinition, convert2Model } from "@/api/activiti/definition.js"
import { format } from "@/utils/activiti/myUtil.js"
export default {
name: "Definition",
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 流程定义表格数据
definitionList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 日期范围
dateRange: [],
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
key: undefined,
name: undefined,
category: undefined,
},
// 表单参数
form: {},
rules: {},
formData: {
upload: null,
},
uploadAction: process.env.VUE_APP_BASE_API + '/activiti/definition/upload',
uploadfileList: [],
baseURL: process.env.VUE_APP_BASE_API,
};
},
created() {
this.getList();
},
methods: {
/** 查询流程定义列表 */
getList() {
this.loading = true;
listDefinition(this.queryParams).then(
response => {
this.definitionList = response.rows;
this.definitionList.forEach(row => {
row.deploymentTime = format(row.deploymentTime, 'yyyy-MM-dd HH:mm:ss');
});
this.total = response.total;
this.loading = false;
}
);
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleUpload() {
this.open = true;
this.title = "部署流程定义";
},
onOpen() {},
onClose() {
this.$refs['elForm'].resetFields()
},
close() {
this.open = false;
this.formData = {
upload: null,
};
this.uploadfileList = [];
},
handelConfirm() {
// this.$refs['elForm'].validate(valid => {
// if (!valid) return;
// this.close()
// })
this.$refs.upload.submit();
this.close();
this.getList();
},
uploadBeforeUpload(file) {
let isRightSize = file.size / 1024 / 1024 < 2
if (!isRightSize) {
this.$message.error('文件大小超过 2MB')
}
return isRightSize
},
/** 激活挂起按钮操作 */
handleState(row) {
const pid = row.id;
const suspendState = row.suspendState;
const opt = row.suspendState === '2' ? '激活': '挂起';
this.$confirm('是否确认' + opt + 'ID为"' + pid + '"的流程定义?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function () {
const data = { "id": pid, "suspendState": suspendState };
return suspendOrActiveDefinition(data);
}).then(response => {
this.msgSuccess("操作成功");
this.getList();
}).catch(function () {
});
},
/** 转模型按钮操作 */
handleConvert(row) {
const pid = row.id;
this.$confirm('是否确认将ID为"' + pid + '"的流程定义转换成流程模型?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function () {
const data = { "processDefinitionId": pid };
return convert2Model(data);
}).then(response => {
this.msgSuccess("操作成功");
this.getList();
}).catch(function () {
});
},
/** 删除按钮操作 */
handleDelete(row) {
const deploymentId = row.deploymentId;
this.$confirm('是否确认删除ID为"' + deploymentId + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function () {
return delDefinition(deploymentId);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
}).catch(function () {
});
},
},
}
</script>
<style scoped>
</style>
@/utils/activiti/myUtil.js
export function format(time, format) {
var t = new Date(time);
var tf = function (i) { return (i < 10 ? '0' : '') + i };
return format.replace(/yyyy|MM|dd|HH|mm|ss/g, function (a) {
switch (a) {
case 'yyyy':
return tf(t.getFullYear());
break;
case 'MM':
return tf(t.getMonth() + 1);
break;
case 'mm':
return tf(t.getMinutes());
break;
case 'dd':
return tf(t.getDate());
break;
case 'HH':
return tf(t.getHours());
break;
case 'ss':
return tf(t.getSeconds());
break;
}
})
}
/**
* 计算出相差天数
* @param secondSub
*/
export function formatTotalDateSub (secondSub) {
var days = Math.floor(secondSub / (24 * 3600)); // 计算出小时数
var leave1 = secondSub % (24*3600) ; // 计算天数后剩余的毫秒数
var hours = Math.floor(leave1 / 3600); // 计算相差分钟数
var leave2 = leave1 % (3600); // 计算小时数后剩余的毫秒数
var minutes = Math.floor(leave2 / 60); // 计算相差秒数
var leave3 = leave2 % 60; // 计算分钟数后剩余的毫秒数
var seconds = Math.round(leave3);
return days + "天" + hours + "时" + minutes + "分" + seconds + '秒';
}
modeler/index.vue:
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" v-show="showSearch" :inline="true">
<el-form-item label="KEY" prop="key">
<el-input
v-model="queryParams.key"
placeholder="请输入KEY"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
size="small"
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['activiti:modeler:add']"
>创建新模型</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="modelerList">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="ID" align="center" prop="id" width="120" />
<el-table-column label="KEY" align="center" prop="key" width="120" />
<el-table-column label="名称" align="center" prop="name" width="150" />
<el-table-column label="版本" align="center" prop="version" width="150" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="最后更新时间" align="center" prop="lastUpdateTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['activiti:modeler:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-upload2"
@click="handleDeploy(scope.row)"
v-hasPermi="['activiti:modeler:deploy']"
>部署</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-download"
@click="handleExport(scope.row)"
v-hasPermi="['activiti:modeler:export']"
>导出</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['activiti:modeler:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改模型对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="KEY" prop="key">
<el-input v-model="form.key" placeholder="请输入KEY" />
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="form.description" type="textarea" placeholder="请输入描述" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listModeler, delModeler, addModeler, deployModeler, exportModeler } from "@/api/activiti/modeler.js"
import { format } from "@/utils/activiti/myUtil.js"
export default {
name: "Modeler",
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 模型表格数据
modelerList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 日期范围
dateRange: [],
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
key: undefined,
name: undefined,
},
// 表单参数
form: {},
defaultProps: {
children: "children",
label: "label"
},
// 表单校验
rules: {
key: [
{ required: true, message: "KEY不能为空", trigger: "blur" }
],
name: [
{ required: true, message: "名称不能为空", trigger: "blur" }
],
}
};
},
created() {
this.getList();
},
methods: {
/** 查询模型列表 */
getList() {
this.loading = true;
listModeler(this.queryParams).then(
response => {
this.modelerList = response.rows;
this.modelerList.forEach(row => {
row.createTime = format(row.createTime, 'yyyy-MM-dd HH:mm:ss');
row.lastUpdateTime = format(row.lastUpdateTime, 'yyyy-MM-dd HH:mm:ss');
});
this.total = response.total;
this.loading = false;
}
);
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
id: undefined,
name: undefined,
key: undefined,
description: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加模型";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const modelId = row.id;
this.designModeler(modelId);
},
/** 提交按钮 */
submitForm: function () {
this.$refs["form"].validate(valid => {
if (valid) {
addModeler(this.form).then(response => {
if (response.code === 200) {
this.msgSuccess("新增成功");
this.open = false;
this.getList();
this.designModeler(response.data)
}
});
}
});
},
/** 模型在线设计 */
designModeler(id) {
// 打开新标签页
const routeUrl = this.$router.resolve({
path: process.env.VUE_APP_BASE_API + "/modeler/modeler.html?modelId=" + id,
});
window.open(routeUrl.href, '_blank');
},
/** 删除按钮操作 */
handleDelete(row) {
const modelIds = row.id;
this.$confirm('是否确认删除ID为"' + modelIds + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function () {
return delModeler(modelIds);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
}).catch(function () {
});
},
/** 导出按钮操作 */
handleExport(row) {
const modelId = row.id;
this.$confirm('是否确认导出ID为"' + modelId + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function () {
return exportModeler(modelId);
}).then(response => {
//创建一个隐藏的a连接,
const link = document.createElement('a');
let blob = new Blob([response]/*, {type: 'application/xml'}*/);
link.style.display = 'none';
//设置连接
link.href = URL.createObjectURL(blob);
link.download = '导出_' + new Date().getTime() + '.bpmn';
document.body.appendChild(link);
//模拟点击事件
link.click();
}).catch(function () {
});
},
/** 部署按钮操作 */
handleDeploy(row) {
const modelId = row.id;
this.$confirm('是否确认部署ID为"' + modelId + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function () {
return deployModeler(modelId);
}).then(response => {
this.msgSuccess("部署成功");
}).catch(function () {
});
},
},
}
</script>
<style scoped>
</style>
-
添加菜单路径:
-
这时候就可以刷新页面,进入流程模型菜单里了
18.创建新的模型对象,会在新的窗口打开流程在线设计器,但有个别的会出现设计器出来了,但没得操作图标那些,是因为需要清理缓存:也就是下面这个打了勾,去掉刷新就可以了: