三、通知公告发布流程搭建
3.1 功能策划
在第一篇学习笔记中已经将“通知公告”拆分为两个部分,分别为
- 全部公告:主要是实现查看所有用户已经走完发布流程且是发布状态的公告;
- 我的公告:实现当前用户新建公告、发起审批流程并发布公告的编辑位置;
在笔记一中已经实现上述两个部分的前后端的开发,但对于发起审批流程的过程尚未开发,本次予以实现,使用户在“我的公告”中能够完成以下操作:
- 可以新建、修改、删除公告的文稿
- 可以发起审批流程
- 可以对在流程中的公告文稿强制取消审批流程、删除流程、重新发起流程
- 可以在公告中显示当前流程状态和审批流程进度
3.2 流程引擎
为实现上述功能策划,需引入流程引擎。目前与ruoyi整合在一起的floawable或activiti的开源框架有很多,本笔记学习过程中重点参考了KonBAI大佬的RuoYi-Flowable-Plus项目,开源项目地址:
https://gitee.com/KonBAI-Q/ruoyi-flowable-plus
个人感觉这个项目整合的非常好,有兴趣可以深入学习并STAR
3.3 新增SQL表并生成代码
流程开发计划在上述流程引擎的基础上搭建。故需要建立一个SQL表,用以存储通知公告表与流程表之间的关联:
sys_workflow_notice表:
使用ruoyi框架中代码生成器生成代码,本笔记学习过程中,因不涉及到此新增表的前端,主要使用生成的main代码,并设置包的位置为 com.ruoyi.workflow:
controller为统一管理,放置在了admin模块下 com.ruoyi.web.controller,以便于统一管理
3.4 flowable表单构建
为了flowable流程能够与本笔记一中改造的“我的公告”的表单关联起来,计划在flowable模块中按照“我的公告”中的新建表单仿制构建一个flowable表单,当在“我的公告”中点击“发起流程”按钮时,后端直接将“我的公告”中的表单内容自动填写到这个flowable表单中:
重点是公告标题、公告类型、公告内容三个部分,尤其注意这三个部分的字段名应该与“我的公告”中对应的表单元素的字段名保持一致,本笔记中命名为noticeTitle、noticeType、noticeContent
过程问题及解决:
问题1:flowable表单构建的公告类型中字段noticeType的类型为int类型、而“我的公告”中表单的类型为string类型,导致两个表单关联后,在flowable表单中此处显示的是个数字,而不是数字对应的字典值,解决思路为更改flowable表单这个组件的数据类型为字符型:
- 修改前端文件 utils/generator/config.js 中下拉选择组件的value值为:
{
__config__: {
label: '下拉选择',
showLabel: true,
labelWidth: null,
tag: 'el-select',
tagIcon: 'select',
layout: 'colFormItem',
span: 24,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/select'
},
__slot__: {
options: [{
label: '选项一',
value: "1"
}, {
label: '选项二',
value: "2"
}]
},
placeholder: '请选择',
style: {width: '100%'},
clearable: true,
disabled: false,
filterable: false,
multiple: false
},
- 修改前端文件 views/tool/build/RightPanel.vue中的添加选项时的value默认为String(item.value),使构建表单的下拉选择组件时右侧添加时默认为字符类型数值:
<div v-for="(item, index) in activeData.__slot__.options" :key="index" class="select-item">
<div class="select-line-icon option-drag">
<i class="el-icon-s-operation" />
</div>
<el-input v-model="item.label" placeholder="选项名" size="small" />
<el-input placeholder="选项值" size="small" :value="String(item.value)" @input="setOptionValue(item, $event)" />
<div class="close-btn select-line-icon" @click="activeData.__slot__.options.splice(index, 1)">
<i class="el-icon-remove-outline" />
</div>
</div>
问题2:flowable表单搭建完毕后,前端浏览器不定时的报错:
vue.runtime.esm.js:619 [Vue warn]: Unknown custom element: <tinymce> - did you register the component correctly? For recursive components, make sure to provide the "name" option.......
同时前端页面中的内容编辑器组件出现问题,无法正常显示:
解决:经查主要是组件未能及时加载造成的,故将组件挂载到全局,针对根目录下 main.js修改
3.5 bpmn流程图
按“发起-审核-结束”的简易流程搭建bpmn流程图如下:
其中开始中添加构建的flowable表单,发起指定任务执行者为发起人,审核按角色指定发起人员所在部门上级,并将模式改为或签(有一人审批即可通过)
完成bpmn流程图搭建后予以部署:
部署完毕后,可在数据库中的act_re_procdef 表中查询到对应的ID号:
需要记录下此ID号,导入“我的公告”前端vue文件中,作为“我的公告”中的数据填写至flowable表单时请求的api的参数,导入方式可参考下节。
3.6 前端代码改造
在 views/system/notice目录下新增一config.js文件,用以存放上述procdef的ID号:
export const DefinitionId = "Process_1709900320599:1:3019a4bd-dd46-11ee-89d6-7aaf089de9eb";
其中的字符串为本笔记搭建的流程对应得prodef表的ID号。
之后针对create.vue文件作如下改造:
- 状态显示为“新建”或“流程状态”,当存在流程状态时显示流程状态,否则显示“新建”
<el-table-column label="状态" align="center" prop="status" width="100">
<template slot-scope="scope">
<dict-tag :options="dict.type.wf_process_status" :value="scope.row.processStatus" v-if="scope.row.processStatus"/>
<dict-tag :options="dict.type.sys_notice_status" :value="scope.row.status" v-else/>
</template>
</el-table-column>
dicts: ['sys_notice_status', 'sys_notice_type','wf_process_status'],
- 流程操作 中,若未发起流程则显示为“发起”按钮,否则显示“详情”和“取消”,并且当流程状态为“已取消”时,显示为“详情”和“删除流程”
<el-table-column label="流程操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
type="text"
size="mini"
icon="el-icon-video-play"
@click="handleStart(scope.row)"
v-if="!scope.row.processStatus"
v-hasPermi="['workflow:process:start']"
>发起</el-button>
<template v-else>
<el-button
type="text"
size="mini"
icon="el-icon-tickets"
@click="handleFlowRecord(scope.row)"
v-hasPermi="['workflow:process:query']"
>详情</el-button>
<el-button
type="text"
size="mini"
icon="el-icon-circle-close"
v-if="!scope.row.finishTime"
@click="handleStop(scope.row)"
v-hasPermi="['workflow:process:cancel']"
>取消</el-button>
<el-button
type="text"
size="mini"
icon="el-icon-delete"
@click="handleFlowDelete(scope.row)"
v-if="scope.row.finishTime"
v-hasPermi="['workflow:process:remove']"
>删除流程</el-button>
</template>
</template>
</el-table-column>
对应的各按钮方法改造 :
-
文档的修改、删除按钮操作:增加限制,当存在流程时不允许修改或删除文档
/** 修改按钮操作 */
handleUpdate(row) {
const noticeId = row.noticeId || this.ids
this.noticeList.forEach(element => {
if(element.noticeId == noticeId){
if(element.processStatus) {
this.$modal.msgError("非新建状态(存在流程)不能修改或删除!");
} else {
this.reset();
const noticeId = row.noticeId || this.ids
getNotice(noticeId).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改公告";
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const noticeId = row.noticeId || this.ids
this.noticeList.forEach(element => {
if(element.noticeId == noticeId){
if(element.processStatus) {
this.$modal.msgError("非新建状态(存在流程)不能修改或删除!");
} else {
this.$modal.confirm('是否确认删除公告编号为"' + noticeId + '"的数据项?').then(function() {
return delNotice(noticeId);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
}
});
},
- 流程的发起、详情、取消、删除操作:流程取消可显示删除
handleStart(row){
// 启动流程并将表单数据加入流程变量
startProcess(DefinitionId, JSON.stringify(row)).then(res => {
this.$modal.msgSuccess(res.msg);
this.getList();
})
// console.log(row);
},
/** 流程流转记录 */
handleFlowRecord(row) {
this.$router.push({
path: '/workflow/process/detail/' + row.procInsId,
query: {
processed: false
}
})
},
/** 取消流程申请 */
handleStop(row){
const params = {
procInsId: row.procInsId
}
const noticeIds = row.noticeId || this.ids
this.$modal.confirm('是否取消公告编号为"' + noticeIds + '"的流程?').then(function() {
return stopProcess(params);
})
.then( res => {
this.$modal.msgSuccess(res.msg);
this.getList();
}).catch(() => {});
},
/** 删除流程按钮操作 */
handleFlowDelete(row) {
const ids = row.procInsId || this.ids;
this.$confirm('是否确认删除流程定义编号为"' + ids + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return delProcess(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
})
},
其中的流程发起操作中的DefinitionId变量,即为导入的procdef的ID号
import { DefinitionId } from './config'
导入的api信息如下
import { listCurrUserNotice,getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice"
import { startProcess , stopProcess, delProcess } from '@/api/workflow/process'
3.7 后端代码改造
改造思路主要是两大部分,第一是查询全部已发布公告的方法改造,需要考虑公告对应的流程状态,即需要关联查询3.3中新增的sys_workflow_notice关联表;第二就是查询当前用户的所有公告并关联流程状态。为此,新增一NoticeFlowService的业务逻辑类和相关视图返回数据的实体类
其中的NoticeFlowVo视图实体类为流程相关信息和通知公告相关信息的联合体:
@Data
public class NoticeFlowVo extends BaseEntity implements Serializable {
/**
* 任务编号
*/
private String taskId;
/**
* 任务名称
*/
private String taskName;
/**
* 任务Key
*/
private String taskDefKey;
/**
* 任务执行人Id
*/
private Long assigneeId;
/**
* 部门名称
*/
@Deprecated
private String deptName;
/**
* 流程发起人部门名称
*/
@Deprecated
private String startDeptName;
/**
* 任务执行人名称
*/
private String assigneeName;
/**
* 流程发起人Id
*/
private Long startUserId;
/**
* 流程发起人名称
*/
private String startUserName;
/**
* 流程类型
*/
private String category;
/**
* 流程变量信息
*/
private Object procVars;
/**
* 局部变量信息
*/
private Object taskLocalVars;
/**
* 流程部署编号
*/
private String deployId;
/**
* 流程ID
*/
private String procDefId;
/**
* 流程key
*/
private String procDefKey;
/**
* 流程定义名称
*/
private String procDefName;
/**
* 流程定义内置使用版本
*/
private int procDefVersion;
/**
* 流程实例ID
*/
private String procInsId;
/**
* 历史流程实例ID
*/
private String hisProcInsId;
/**
* 任务耗时
*/
private String duration;
/**
* 任务意见
*/
private WfCommentDto comment;
/**
* 任务意见
*/
private List<Comment> commentList;
/**
* 候选执行人
*/
private String candidate;
/**
* 任务创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 任务完成时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date finishTime;
/**
* 流程状态
*/
private String processStatus;
/**
* 数据表的顺序号
*/
private Long noId;
/**
* 公告ID
*/
private Long noticeId;
/**
* 公告标题
*/
private String noticeTitle;
/**
* 公告类型(1通知 2公告)
*/
private String noticeType;
/**
* 公告内容
*/
private String noticeContent;
/**
* 公告状态(0正常 1关闭)
*/
private String status;
/**
* 备注
*/
private String remark;
private Long isRead;
}
NoticeFlowService的业务逻辑重点处理上述2种情况的:
- 所有流程状态为“已完成”的通知公告数据的查询功能:
@Override
public TableDataInfo<SysNotice> selectPageNoticeFinishedList(SysNotice notice, PageQuery pageQuery, Long currentUserId) {
LambdaQueryWrapper<SysNotice> lqw = new LambdaQueryWrapper<SysNotice>()
.like(StringUtils.isNotBlank(notice.getNoticeTitle()), SysNotice::getNoticeTitle, notice.getNoticeTitle())
.eq(StringUtils.isNotBlank(notice.getNoticeType()), SysNotice::getNoticeType, notice.getNoticeType())
.like(StringUtils.isNotBlank(notice.getCreateBy()), SysNotice::getCreateBy, notice.getCreateBy());
// 查询所有通知公告及其对应的流程实例
List<SysNotice> noticeList = sysNoticeBaseMapper.selectList(lqw);
List<SysNotice> records = new ArrayList<>();
int i = PageQuery.DEFAULT_PAGE_NUM;
for (SysNotice one : noticeList) {
SysWorkflowNoticeVo sysWorkflowNoticeVo = sysWorkflowNoticeService.queryOneByNoticeId(one.getNoticeId());
if (sysWorkflowNoticeVo != null) {
HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(sysWorkflowNoticeVo.getProcinstId());
List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery.list();
HistoricProcessInstance hisIns = historicProcessInstances.get(0);
HistoricVariableInstance processStatusVariable = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(sysWorkflowNoticeVo.getProcinstId())
.variableName(ProcessConstants.PROCESS_STATUS_KEY)
.singleResult();
//获取流程状态
String processStatus = null;
if (ObjectUtil.isNotNull(processStatusVariable)) {
// 获取流程节点信息
List<Comment> comments = wfProcessService.historyProcNodeList(hisIns).get(1).getCommentList();
//流程取消时,系统记录的流程状态也为已完成,此时与实际正常完成的流程无法区分,暂时按增加一条备注信息来处理,实际查询过程中查询此备注信息进行状态区分
if(!(comments.isEmpty())) {
System.out.println(comments.get(0).getType());
System.out.println(comments.get(0).getFullMessage());
if(comments.get(0).getType().contains("7") && comments.get(0).getFullMessage().contains("取消申请")){
processStatus = ProcessStatus.CANCELED.getStatus();
System.out.println(processStatus);
}else{
processStatus = Convert.toStr(processStatusVariable.getValue());
}
}
}
// 兼容旧流程
if (processStatus == null) {
processStatus = ObjectUtil.isNull(hisIns.getEndTime()) ? ProcessStatus.RUNNING.getStatus() : ProcessStatus.COMPLETED.getStatus();
}
//增加序号的显示
if (ProcessStatus.COMPLETED.getStatus().equals(processStatus)) {
one.setNoId((long) i);
one.setCreateBy(String.valueOf(sysUserService.selectUserByUserName(one.getCreateBy()).getNickName()));
SysUserNoticeVo isReadResult = sysUserNoticeService.queryByIdAndNoticeId(currentUserId, one.getNoticeId());
if(isReadResult != null){
one.setIsRead(Long.valueOf(dictDataService.selectDictValue("sys_notice_isread", "已读")));
} else {
one.setIsRead(Long.valueOf(dictDataService.selectDictValue("sys_notice_isread", "未读")));
}
records.add(one);
i++;
}
}
}
//分页
Page<SysNotice> page = new Page<>(pageQuery.getPageNum() + 1, pageQuery.getPageSize());
page.setTotal(records.size());
int start = (pageQuery.getPageNum() - 1) * pageQuery.getPageSize();
int end = pageQuery.getPageNum() * pageQuery.getPageSize();
page.setRecords(records.stream().skip(start).limit(end).collect(Collectors.toList()));
return TableDataInfo.build(page);
}
- 当前用户所有通知及流程状态的查询:
@Override
public TableDataInfo<NoticeFlowVo> selectPageCurrUserNoticeFlowList(SysNotice notice, PageQuery pageQuery, Long userId) {
LambdaQueryWrapper<SysNotice> lqw = new LambdaQueryWrapper<SysNotice>()
.like(StringUtils.isNotBlank(notice.getNoticeTitle()), SysNotice::getNoticeTitle, notice.getNoticeTitle())
.eq(StringUtils.isNotBlank(notice.getNoticeType()), SysNotice::getNoticeType, notice.getNoticeType())
.eq(SysNotice::getCreateBy, sysUserService.selectUserById(userId).getUserName());
Page<SysNotice> page = sysNoticeBaseMapper.selectPage(pageQuery.build(), lqw);
Page<NoticeFlowVo> result = new Page<>();
result.setTotal(page.getTotal());
int i = PageQuery.DEFAULT_PAGE_NUM;
//所有当前用户的通知公告
List<NoticeFlowVo> records = new ArrayList<>();
for( SysNotice one : page.getRecords()) {
NoticeFlowVo oneVo = new NoticeFlowVo();
oneVo.setNoticeId(one.getNoticeId());
oneVo.setNoticeTitle(one.getNoticeTitle());
oneVo.setNoticeType(one.getNoticeType());
oneVo.setNoticeContent(one.getNoticeContent());
oneVo.setStatus(one.getStatus());
oneVo.setCreateTime(one.getCreateTime());
oneVo.setNoId(Long.valueOf((pageQuery.getPageNum()-1)*pageQuery.getPageSize()+i));
i++;
//查询相关流程的内容
SysWorkflowNoticeVo sysWorkflowNoticeVo = sysWorkflowNoticeService.queryOneByNoticeId(one.getNoticeId());
if(sysWorkflowNoticeVo != null) {
HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(sysWorkflowNoticeVo.getProcinstId());
List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery.list();
HistoricProcessInstance hisIns = historicProcessInstances.get(0);
HistoricVariableInstance processStatusVariable = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(sysWorkflowNoticeVo.getProcinstId())
.variableName(ProcessConstants.PROCESS_STATUS_KEY)
.singleResult();
String processStatus = null;
if (ObjectUtil.isNotNull(processStatusVariable)) {
// 获取流程节点信息
List<Comment> comments = wfProcessService.historyProcNodeList(hisIns).get(1).getCommentList();
//流程取消时,系统记录的流程状态也为已完成,此时与实际正常完成的流程无法区分,暂时按增加一条备注信息来处理,实际查询过程中查询此备注信息进行状态区分
if(!(comments.isEmpty())) {
System.out.println(comments.get(0).getType());
System.out.println(comments.get(0).getFullMessage());
if(comments.get(0).getType().contains("7") && comments.get(0).getFullMessage().contains("取消申请")){
processStatus = ProcessStatus.CANCELED.getStatus();
System.out.println(processStatus);
}else{
processStatus = Convert.toStr(processStatusVariable.getValue());
}
}
}
// 兼容旧流程
if (processStatus == null) {
processStatus = ObjectUtil.isNull(hisIns.getEndTime()) ? ProcessStatus.RUNNING.getStatus() : ProcessStatus.COMPLETED.getStatus();
}
oneVo.setProcessStatus(processStatus);
oneVo.setCreateTime(hisIns.getStartTime());
oneVo.setFinishTime(hisIns.getEndTime());
oneVo.setProcInsId(hisIns.getId());
// 计算耗时
if (Objects.nonNull(hisIns.getEndTime())) {
oneVo.setDuration(DateUtils.getDatePoor(hisIns.getEndTime(), hisIns.getStartTime()));
} else {
oneVo.setDuration(DateUtils.getDatePoor(DateUtils.getNowDate(), hisIns.getStartTime()));
}
// 流程部署实例信息
Deployment deployment = repositoryService.createDeploymentQuery()
.deploymentId(hisIns.getDeploymentId()).singleResult();
oneVo.setDeployId(hisIns.getDeploymentId());
oneVo.setProcDefId(hisIns.getProcessDefinitionId());
oneVo.setProcDefName(hisIns.getProcessDefinitionName());
oneVo.setProcDefVersion(hisIns.getProcessDefinitionVersion());
oneVo.setCategory(deployment.getCategory());
// 当前所处流程
List<Task> taskList = taskService.createTaskQuery().processInstanceId(hisIns.getId()).includeIdentityLinks().list();
if (CollUtil.isNotEmpty(taskList)) {
oneVo.setTaskName(taskList.stream().map(Task::getName).filter(StringUtils::isNotEmpty).collect(Collectors.joining(",")));
}
}
records.add(oneVo);
}
result.setRecords(records);
return TableDataInfo.build(result);
}
修改controller中getLlist对应的公告查询方法为以上方法。至此主要后端的改造已完成。
其他的后端改造:
- WfTaskServiceImpl中的stopProcess方法中,增加两行取消后的备注功能
taskService.addComment(task.get(0).getId(), processInstance.getProcessInstanceId(), FlowComment.REVOKE.getType(),
StringUtils.isBlank(bo.getComment()) ? "取消申请" : bo.getComment());
- WfProcessServiceImpl中的deletProcessByIds方法,增加删除流程后同时删除sys_workfow_notice表中的相关流程数据的功能
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteProcessByIds(String[] instanceIds) {
List<String> ids = Arrays.asList(instanceIds);
// 校验流程是否结束
long activeInsCount = runtimeService.createProcessInstanceQuery()
.processInstanceIds(new HashSet<>(ids)).active().count();
if (activeInsCount > 0) {
throw new ServiceException("不允许删除进行中的流程实例");
}
//删除流程实例的同时,需要删除sys_workflow_notice表中的数据
List<String> sysWorkflowNoticeId = new ArrayList<>();
for (String instanceId : instanceIds) {
SysWorkflowNoticeVo one = sysWorkflowNoticeService.queryOneByProcinsId(instanceId);
if(one != null){
sysWorkflowNoticeId.add(instanceId);
}
}
sysWorkflowNoticeService.deleteWithValidByIds(sysWorkflowNoticeId, !sysWorkflowNoticeId.isEmpty());
// 删除历史流程实例
historyService.bulkDeleteHistoricProcessInstances(ids);
}
- WfProcessServiceImpl中的startProcess方法,增加发起流程后的在sys_workflow_notice中添加关联信息的功能
//同时记录到sys_workflow_notice表中
if(variables.containsKey("noticeId")){
Long noticeId =Long.valueOf(String.valueOf(variables.get("noticeId")));
String procInstId = processInstance.getId();
SysWorkflowNoticeBo workflowNoticeBo = new SysWorkflowNoticeBo();
workflowNoticeBo.setProcinstId(procInstId);
workflowNoticeBo.setNoticeId(noticeId);
workflowNoticeBo.setDelFlag("0");
sysWorkflowNoticeService.updateByBo(workflowNoticeBo);
}
- 同时改造代码生成器生成的SysWorkflowNoticeServiceImpl中的updateByBo方法,以支持上述的添加功能
/**
* 修改流程和通知关联
*/
@Override
public Boolean updateByBo(SysWorkflowNoticeBo bo) {
SysWorkflowNotice update = BeanUtil.toBean(bo, SysWorkflowNotice.class);
validEntityBeforeSave(update);
SysWorkflowNoticeVo sysWorkflowNoticeVo = baseMapper.selectVoOne(new LambdaQueryWrapper<SysWorkflowNotice>().eq(SysWorkflowNotice::getProcinstId, bo.getProcinstId())
.eq(SysWorkflowNotice::getNoticeId, bo.getNoticeId()));
if (sysWorkflowNoticeVo != null) {
return baseMapper.update(update, new LambdaUpdateWrapper<SysWorkflowNotice>().eq(SysWorkflowNotice::getProcinstId, bo.getProcinstId())) > 0 ;
} else {
return baseMapper.insert(update) > 0;
}
}
3.8 最终效果
最终实现的通知公告发布流程操作效果如下:
- 新增公告:
- 发起发布审批流程
- 流程详情查看
- 流程审批
- 公告发布成功!
3.9 遗留问题
改造完毕过程中,仍有些问题可以进一步改进,后期可以持续改善:
- 流程强制取消后的状态标记使用的是注释,而没有直接写入流程状态,增加了一次查询过程,数据量大时影响效率,后期需考虑改进
- 流程的procdefId号是建立完毕后在数据库手工查找的,再记录到config.js文件,这样等于是写死了,不利于流程模型的更新,后期考虑通过前端的对话框等方式予以配置
- 流程到达审批者的时候,可以考虑增加一条即时的提醒弹出窗口,提醒审批者及时审批,后期考虑增加即时提醒弹窗功能