审批流的其实就是提交之后发送给你的上级然后让他进行批阅,再进行反馈。实际上的就是发送、接收、改变状态、再发送的一个过程。
芋道框架帮我们集成了这个功能,下面就让我来带大家做一个简易版的审批流。
首先:数据库准备一定要有这四个字段 编辑
后端:
实体类:
DO类:
/** * 导入批次号 */ private String importBatch; /** * 录入批次号 */ private String subBatchNo; /** * 状态 */ private Long status; private String processInstanceId;
SaveReqVO类:
@Schema(description = "录入批次号", example = "9103") private String subBatchNo; @Schema(description = "导入批次号", example = "9103") private String importBatch; @Schema(description = "状态", example = "9103") private Long status; private String processInstanceId;
RespVO类:
private String subBatchNo; private Long status; private String processInstanceId;
提交审核的方法
Service:
void examine(List<TbQyyrYrzyjpljlbRespVO> examineReqVO);
实现类:
public static final String PROCESS_KEY = "carbon-yr-jcdjpl"; public static final String prefix = "yr-b-"; @Resource private BpmUtil bpmUtil; @Override public void examine(List<TbQyyrYrzyjpljlbRespVO> examineReqVO) { // 生成提交批次号 String submitBatchNo = NumberGenerate.generateNumber(prefix); // 发送到审批流程 String processInstanceId = bpmUtil.submitToBpm(submitBatchNo, PROCESS_KEY); for(TbQyyrYrzyjpljlbRespVO tbQyyrYrzyjpljlbRespVO : examineReqVO){ TbQyyrYrzyjpljlbDO updateObj = BeanUtils.toBean(tbQyyrYrzyjpljlbRespVO, TbQyyrYrzyjpljlbDO.class); // 每条数据保存提交批次号 updateObj.setSubBatchNo(submitBatchNo); // 每天数据保存审批流 processInstanceId updateObj.setProcessInstanceId(processInstanceId); updateObj.setStatus(2L); tbQyyrYrzyjpljlbMapper.updateById(updateObj); } }
记住这里的PROCESS_KEY和prefix,后面有用,这是唯一的标识。
Controller:
@PostMapping("/examine") @Operation(summary = "线缆原材料生产数据采集审核") public CommonResult<Boolean> examine(@RequestBody List<TbQyyrYrzyjpljlbRespVO> examineReqVO ) { tbQyyrYrzyjpljlbService.examine(examineReqVO); return success(true); }
generateNumber方法:
package com.todod.carbon.framework.common.util.common; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Random; public class NumberGenerate { public static String generateNumber(String prefix) { // 使用当前时间作为随机数生成器的种子 Random random = new Random(System.currentTimeMillis()); // 生成一个0到9999之间的随机数 int randomNumber = random.nextInt(10000); String batchNo = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))+randomNumber; return prefix + batchNo; } }
该方法用于生成提交的批次号。
提交方法写完后我们回到页面的工作流程去做一个审批流
编辑
编辑
这里的流程标识需要和我们定义的PROCESS_KEY是一样的。
编辑
按照这个流程,创建之后我们先去修改流程补全信息
编辑
这里的表单查看地址对应了一个只能查看的页面用于审批的时候能够看到审批的信息
点击设计流程
编辑
按照这样配置,画一个流程审批图
点击保存模型后发布
编辑
接下来我们写一个改变状态的方法:
service:
void setStatus(String subNo,Integer status);
实现类:
@Override public void setStatus(String id, Integer bpmStatus) { List<TbQyyrYrzyjpljlbDO> yrzyjpljlbDOS = tbQyyrYrzyjpljlbMapper.selectBpmList(id); if (yrzyjpljlbDOS != null && yrzyjpljlbDOS.size()>0){ yrzyjpljlbDOS.forEach(item->{ item.setStatus(Long.parseLong(bpmStatus.toString())); tbQyyrYrzyjpljlbMapper.updateById(item); }); } if (bpmStatus == 3){ //执行kettle // kettleUtils.runKTR(kettleFileName); } }
selectBpmList方法:
default List<TbQyyrYrzyjpljlbDO> selectBpmList (String processInstanceId){ return selectList(new LambdaQueryWrapperX<TbQyyrYrzyjpljlbDO>() .eq(TbQyyrYrzyjpljlbDO::getProcessInstanceId,processInstanceId) ); }
这个方法会根据返回的审批id去查找符合条件的数据集合,也就是同一时间审批的那一批数据。
改变状态的方法就写好了,接下来我们去Bpm模块写一个监听方法。
编辑
在这里写一个监听类,层级是这样的,大家不要写错了。
package com.todod.carbon.module.bpm.service.oa.listener; import com.todod.carbon.module.bpm.event.BpmProcessInstanceStatusEvent; import com.todod.carbon.module.bpm.event.BpmProcessInstanceStatusEventListener; import com.todod.carbon.module.system.api.user.AdminUserApi; import jakarta.annotation.Resource; import org.springframework.stereotype.Component; @Component public class YrZyJplListener extends BpmProcessInstanceStatusEventListener { public static final String PROCESS_KEY = "carbon-yr-jcdjpl"; @Resource private AdminUserApi adminUserApi; @Override protected String getProcessDefinitionKey() { return PROCESS_KEY; } @Override protected void onEvent(BpmProcessInstanceStatusEvent event) { String id = event.getId(); adminUserApi.setCommonStatus(id,event.getStatus(),"yr-b"); } }
这里依旧要保证PROCESS_KEY和prefix一致。
这是我自己写的一个改变状态的rpc,大家可以自己写一个,我相信并不难,我放这大家可以参考。
@GetMapping(PREFIX + "/bpmCommonStatus") @Operation(summary = "工作流状态") @Parameters({ @Parameter(name = "id", description = "订单id", example = "1", required = true), @Parameter(name = "bpmStatus", description = "工作流状态", example = "3", required = true), @Parameter(name = "type", description = "对应的工作表", example = "3", required = true) }) void setCommonStatus(@RequestParam("id")String id, @RequestParam("status")Integer status, @RequestParam("type")String type);
@Override public void setStatus(Long id, Integer bpmStatus) { switch (bpmStatus) { case 2://通过 break; case 3://未通过 break; case 4://撤回、取消 审批 break; } } @Override public void setCommonStatus(String id, Integer bpmStatus, String type) { switch (bpmStatus) { case 2://通过 bpmStatus = 3; break; case 3://未通过 bpmStatus = 4; break; case 4://撤回、取消 审批 bpmStatus = 1; break; } switch (type){ //产品信息 case "xl-a": tbXlcpCpxxService.setStatus(id,bpmStatus); //原材料生产 case "xl-b": tbXlcpYclscjdService.setStatus(id,bpmStatus); //原材料运输 case "xl-c": tbXlcpYclysjdService.setStatus(id,bpmStatus); //产品生产 case "xl-d": tbXlcpCpscjdService.setStatus(id,bpmStatus); //产品运输 case "xl-e": tbXlcpCpysjdService.setStatus(id,bpmStatus); //产品运输 case "xl-f": tbXlcpCpsyjdService.setStatus(id,bpmStatus); //产品回收 case "xl-g": tbXlcpCphsjdService.setStatus(id,bpmStatus); //楼宇电表 case "ly-a": lyntglAmmeterInfoService.setStatus(id,bpmStatus); //楼宇水表 case "ly-b": lyntglWaterinfoService.setStatus(id,bpmStatus); //楼宇天然气 case "ly-c": lyntglTrqInfoService.setStatus(id,bpmStatus); //楼宇市政热力 case "ly-d": lyntglSzrlInfoService.setStatus(id,bpmStatus); //楼宇可再生能源 case "ly-e": lyntglKzsnyInfoService.setStatus(id,bpmStatus); //工业园区 case "gyyq-a": tbGyyqGyyqcjService.setStatus(id,bpmStatus); //余热-监测点 case "yr-a": tbQyyrJcdbService.setStatus(id,bpmStatus); default: break; } }
前端:
大家可以那前端做个参考,我就把完整代码放到这里了。
<template> <ContentWrap> <!-- 搜索工作栏 --> <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="130px" > <el-form-item label="企业名称" prop="ttbQyyrQyname"> <el-input v-model="queryParams.ttbQyyrQyname" placeholder="请输入企业名称" clearable @keyup.enter="handleQuery" class="!w-240px" /> </el-form-item> <el-form-item label="余热标识" prop="ttbQyyrYrbs"> <el-input v-model="queryParams.ttbQyyrYrbs" placeholder="请输入余热标识" clearable @keyup.enter="handleQuery" class="!w-240px" /> </el-form-item> <el-form-item label="余热资源类型" prop="ttbQyyrYrzylx"> <el-input v-model="queryParams.ttbQyyrYrzylx" placeholder="请输入余热资源类型" clearable @keyup.enter="handleQuery" class="!w-240px" /> </el-form-item> <el-form-item label="填报日期" prop="ttbTbDate"> <el-date-picker v-model="queryParams.ttbTbDate" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" /> </el-form-item> <el-form-item label="创建时间" prop="createTime"> <el-date-picker v-model="queryParams.createTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px" /> </el-form-item> <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['system:tb-qyyr-yrzyjpljlb:create']" > <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> <el-button type="warning" plain @click="handleImport" > <Icon icon="ep:upload" /> 导入 </el-button> <el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['system:tb-qyyr-yrzyjpljlb:export']" > <Icon icon="ep:download" class="mr-5px" /> 导出 </el-button> <el-button type="primary" plain @click="examine"> 提交审核 </el-button> </el-form-item> </el-form> </ContentWrap> <!-- 列表 --> <ContentWrap> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange"> <el-table-column label="序号" type="index" header-align="center" align="center" width="60px" fixed /> <el-table-column type="selection" :selectable="selectable" width="55" /> <el-table-column label="企业名称" align="center" prop="ttbQyyrQyname" /> <el-table-column label="余热标识" align="center" prop="ttbQyyrYrbs" /> <el-table-column label="余热资源类型" align="center" prop="ttbQyyrYrzylx" width="120px"/> <el-table-column label="余热资源等级" align="center" prop="ttbQyyrYrzydj" width="120px"/> <el-table-column label="可回收余热资源量" align="center" prop="ttbQyyrKhsyrzyl" width="140px"/> <el-table-column label="余热资源回收率" align="center" prop="ttbQyyrYrzyhsl" width="140px"/> <el-table-column label="余热资源减排量" align="center" prop="ttbQyyrYrzyjpl" width="140px"/> <el-table-column label="余热资源回收总量" align="center" prop="ttbQyyrYrzyhszl" width="140px"/> <el-table-column label="备注" align="center" prop="ttbQyyrBz" /> <el-table-column label="填报日期" align="center" prop="createTime" :formatter="dateFormatter2" width="180px" /> <el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" /> <el-table-column label="操作" align="center" width="200px"> <template #default="scope"> <el-tag type="primary" v-if = "scope.row.status ==1" >草稿 </el-tag> <el-tag type="success" v-if = "scope.row.status ==3" >审批通过</el-tag> <el-tag type="danger" v-if = "scope.row.status ==4" >审批驳回</el-tag> <el-tag type="warning" v-if = "scope.row.status ==2" >审批中</el-tag> <el-button link type="primary" @click="openForm('update', scope.row.id)" v-if = "scope.row.status ==1 || scope.row.status ==4" > 编辑 </el-button> <el-button link type="danger" @click="handleDelete(scope.row.id)" v-if = "scope.row.status ==1 || scope.row.status ==4" > 删除 </el-button> </template> </el-table-column> </el-table> <!-- 分页 --> <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" /> </ContentWrap> <!-- 表单弹窗:添加/修改 --> <TbQyyrYrzyjpljlbForm ref="formRef" @success="getList" /> <!-- 对象导入对话框 --> <ImportForm ref="importFormRef" @success="getList" /> <Examine ref="examineFormRef" @success="getList" /> </template> <script setup lang="ts"> import { dateFormatter,dateFormatter2 } from '@/utils/formatTime' import download from '@/utils/download' import { TbQyyrYrzyjpljlbApi, TbQyyrYrzyjpljlbVO } from '@/api/system/tbqyyryrzyjpljlb' import TbQyyrYrzyjpljlbForm from './TbQyyrYrzyjpljlbForm.vue' import TbQyyrJcdbForm from './TbQyyrJcdbForm.vue' import { getIntDictOptions, DICT_TYPE, getDictLabel } from '@/utils/dict' import Examine from './examine.vue' import ImportForm from './ImportForm.vue' /** 余热资源减排量记录 列表 */ defineOptions({ name: 'TbQyyrYrzyjpljlb' }) const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 const loading = ref(true) // 列表的加载中 const list = ref<TbQyyrYrzyjpljlbVO[]>([]) // 列表的数据 const total = ref(0) // 列表的总页数 const queryParams = reactive({ pageNo: 1, pageSize: 10, ttbQyyrQyname: undefined, ttbQyyrYrbs: undefined, ttbQyyrYrzylx: undefined, ttbQyyrYrzydj: undefined, ttbQyyrKhsyrzyl: undefined, ttbQyyrYrzyhsl: undefined, ttbQyyrYrzyjpl: undefined, ttbQyyrYrzyhszl: undefined, ttbQyyrBz: undefined, ttbTbDate: [], createTime: [], }) const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 /** 查询列表 */ const getList = async () => { loading.value = true try { const data = await TbQyyrYrzyjpljlbApi.getTbQyyrYrzyjpljlbPage(queryParams) list.value = data.list total.value = data.total } finally { loading.value = false } } /** 搜索按钮操作 */ const handleQuery = () => { queryParams.pageNo = 1 getList() } /** 重置按钮操作 */ const resetQuery = () => { queryFormRef.value.resetFields() handleQuery() } /** 添加/修改操作 */ const formRef = ref() const openForm = (type: string, id?: number) => { formRef.value.open(type, id) } /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { // 删除的二次确认 await message.delConfirm() // 发起删除 await TbQyyrYrzyjpljlbApi.deleteTbQyyrYrzyjpljlb(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() } catch {} } /** 导出按钮操作 */ const handleExport = async () => { try { // 导出的二次确认 await message.exportConfirm() // 发起导出 exportLoading.value = true const data = await TbQyyrYrzyjpljlbApi.exportTbQyyrYrzyjpljlb(queryParams) download.excel(data, '余热资源减排量记录.xls') } catch { } finally { exportLoading.value = false } } const importFormRef = ref() const handleImport = () => { importFormRef.value.open() } const selectList = ref<TbQyyrYrzyjpljlbVO[]>([]) const handleSelectionChange = (val: TbQyyrYrzyjpljlbVO[]) => { selectList.value = val } const examineFormRef = ref() const examine =() => { if(selectList.value.length > 0) { examineFormRef.value.open(selectList.value) }else{ message.warning('请选择审核的数据') } } const selectable = (row: TbQyyrYrzyjpljlbVO) => ![2,3].includes(row.status) /** 初始化 **/ onMounted(() => { getList() }) </script>
examine提交页:
<template> <Dialog title="提交审核" v-model="dialogVisible" width="60%"> <el-table :data="selectList2" > <el-table-column label="序号" type="index" header-align="center" align="center" width="60px" fixed /> <el-table-column label="企业名称" align="center" prop="ttbQyyrQyname" /> <el-table-column label="余热标识" align="center" prop="ttbQyyrYrbs" /> <el-table-column label="余热资源类型" align="center" prop="ttbQyyrYrzylx" width="120px"/> <el-table-column label="余热资源等级" align="center" prop="ttbQyyrYrzydj" width="120px"/> <el-table-column label="可回收余热资源量" align="center" prop="ttbQyyrKhsyrzyl" width="140px"/> <el-table-column label="余热资源回收率" align="center" prop="ttbQyyrYrzyhsl" width="140px"/> <el-table-column label="余热资源减排量" align="center" prop="ttbQyyrYrzyjpl" width="140px"/> <el-table-column label="余热资源回收总量" align="center" prop="ttbQyyrYrzyhszl" width="140px"/> <el-table-column label="备注" align="center" prop="ttbQyyrBz" /> <el-table-column label="填报日期" align="center" prop="createTime" :formatter="dateFormatter2" width="180px" /> <el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" /> <el-table-column fixed="right" label="操作" align="center" width="120"> <template #default="scope"> <el-button @click.prevent="deleteRow(scope.$index, selectList2)" type="text" size="small" > 删除 </el-button> </template> </el-table-column> </el-table> <template #footer> <el-button @click="examine" type="primary" >提交审核</el-button> <el-button @click="closeDialong">取 消</el-button> </template> </Dialog> </template> <script lang="ts" setup> import { dateFormatter,dateFormatter2 } from '@/utils/formatTime' import { getIntDictOptions, DICT_TYPE, getDictLabel } from '@/utils/dict' import { TbQyyrYrzyjpljlbApi, TbQyyrYrzyjpljlbVO } from '@/api/system/tbqyyryrzyjpljlb' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 const dialogVisible = ref(false) // 弹窗的是否展示 const selectList2 = ref<TbQyyrYrzyjpljlbVO[]>([]) /** 打开弹窗 */ const open = async ( selectList: TbQyyrYrzyjpljlbVO[]) => { dialogVisible.value = true selectList2.value = [] selectList2.value = selectList } const deleteRow = (index, rows) => { rows.splice(index, 1) } const examine = async () => { if(selectList2.value.length > 0) { await message.confirm( t('你确定要提交审核吗?')) await TbQyyrYrzyjpljlbApi.examine(selectList2.value) message.success('已提交审核,静等相关人员处理') emit('success') dialogVisible.value = false }else{ message.warning('请选择审核的数据') } } const closeDialong = () => { dialogVisible.value = false emit('success') } defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 </script>
现在让我们试一下能不能审核成功
编辑
选中后提交审核
编辑
提交审核成功,状态变为已审核
编辑
同时在待办任务里面能看见这一条提交审核记录
编辑
点击审批
编辑
通过之后,状态变为已通过
编辑
这就大功告成了,如果有什么问题,可以和我交流,我的微信是:sxy0802000,谢谢大家浏览。