问题背景:
实际企业的工作流程流转中都会涉及到各种业务,业务又是各种实际的业务表单,所以基于这种业务表单的流程流转显得特别实用,在不改变现有的业务的情况下,增加一个流程的流转支持,同时进行业务的控制与回写,这些都需要一个灵活结合业务表单的流程设计。
本文就是为了这种需求而实现的设计与实践,供大家参考使用。
第一章:总体设计思路
总体流程图如下:
1.1 前端的流程功能组件
- 提交申请按钮:主要提供在业务表单里增加流程申请的按钮,以便启动业务表单流程。
- 撤回申请按钮:提供流程的撤回操作,以便在业务里也能进行流程的基本操作。
- 退回申请按钮:同样也是在业务提供退回申请的操作,满足流程业务需要。
- 历史流程信息:提供流程流转中的相关信息,包括一些审批历史记录以及流程图所处的位置等。
1.2 结合业务表单增加流程按钮或页面
主要是根据业务本身需要增加相应的流程业务操作按钮或界面:
- 业务表单里:可以在具体的业务表单比如采购订单里,增加一个提交申请按钮,方便操作。
- 业务列表里:也可以在业务表单的列表里增加流程业务按钮,以便可以批量操作。
1.3 现有的业务表单与流程进行关联
主要是对现有的业务表单与流程关联起来,以便后续流程与业务的结合:
- 业务方面:主要包括业务表单名称,业务表单相关服务名称以及业务数据库表。
- 流程方面:主要包括流程key,流程名称,路由地址与前端注入方式等。
1.4 前端根据后端关联信息动态显示
根据前面的一些关联信息进行前端表单的动态显示:
- 根据流转中业务表单服务名称找到对应的业务表单组件
- 根据找到的页面进行动态显示需要的表单组件
1.5 业务流程接口类,实现业务完成后的处理
业务层实现接口方法,用于流程处理后的回调,以便满足业务上的这种特殊需求:
- 流程处理完后的回调
- 根据业务ID返回业务表单数据
- 返回当前节点的流程变量
- 返回当前节点的候选人
第二章:后端主要设计
2.1 业务层实现接口方法
主要代码如下:
public interface FlowCallBackServiceI { /** * 流程处理完成后的回调 * @param business 里面包含流程运行的现状信息,业务层可根据其信息判断,书写增强业务逻辑的代码,<br/> * 1、比如将其中关键信息存入业务表,即可单表业务操作,否则需要关联flow_my_business表获取流程信息<br/> * 2、比如在流程进行到某个节点时(business.taskId),需要特别进行某些业务逻辑操作等等<br/> */ void afterFlowHandle(FlowMyBusiness business); /** * 根据业务id返回业务表单数据<br/> * @param dataId * @return */ Object getBusinessDataById(String dataId); /** * 返回当前节点的流程变量 * @param taskNameId 节点定义id * @param values 前端传入的变量,里面包含dataId * @return */ Map<String, Object> flowValuesOfTask(String taskNameId, Map<String, Object> values); /** * 返回当前节点的候选人username * @param taskNameId 节点定义id * @param values 前端传入的变量,里面包含dataId * @return */ List<String> flowCandidateUsernamesOfTask(String taskNameId, Map<String, Object> values); }
2.2 实际业务类的流程实现
这里以testDemo服务为例,主要增加FlowCallBackServiceI的实现类,如:
@Service("testDemoService") public class TestDemoServiceImpl extends ServiceImpl<TestDemoMapper, TestDemo> implements ITestDemoService, FlowCallBackServiceI { @Autowired FlowCommonService flowCommonService; @Override public void afterFlowHandle(FlowMyBusiness business) { //流程操作后做些什么 business.getTaskNameId();//接下来审批的节点 business.getValues();//前端传进来的参数 business.getActStatus();//流程状态 ActStatus.java //....其他 } @Override public Object getBusinessDataById(String dataId) { return this.getById(dataId); } @Override public Map<String, Object> flowValuesOfTask(String taskNameId, Map<String, Object> values) { // TODO Auto-generated method stub return null; } @Override public List<String> flowCandidateUsernamesOfTask(String taskNameId, Map<String, Object> values) { // TODO Auto-generated method stub return null; } @Override public boolean save(TestDemo testDemo) { /**新增数据**/ testDemo.setId(IdUtil.fastSimpleUUID()); //Comparison method violates its general contract!有时候出现这个错误加的,原因不明 System.setProperty("java.util.Arrays.useLegacyMergeSort", "true"); return super.save(testDemo); } @Override public boolean removeById(Serializable id) { /**删除数据,移除流程关联信息**/ flowCommonService.delActBusiness(id.toString()); return super.removeById(id); } @Override public IPage<TestDemo> myPage(Page<TestDemo> page, QueryWrapper<TestDemo> queryWrapper) { return this.baseMapper.myPage(page, queryWrapper); } }
2.3 业务列表增加流程相关信息
这里主要是以列表为例增加流程相关的方式,代码如下:
public Result<IPage<TestDemo>> queryPageList(TestDemo testDemo, @RequestParam(name="pageNo", defaultValue="1") Integer pageNo, @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, HttpServletRequest req) { Map<String, String[]> ParameterMap = new HashMap<String, String[]>(req.getParameterMap()); String[] column = new String[]{""}; if(ParameterMap!=null&& ParameterMap.containsKey("column")) { column[0] = ParameterMap.get("column")[0]; column[0] = "t."+ column[0]; ParameterMap.replace("column", column); log.info("修改的排序规则>>列:" + ParameterMap.get("column")[0]); } QueryWrapper<TestDemo> queryWrapper = QueryGenerator.initQueryWrapper(testDemo, req.getParameterMap()); Page<TestDemo> page = new Page<TestDemo>(pageNo, pageSize); //IPage<TestDemo> pageList = testDemoService.page(page, queryWrapper); IPage<TestDemo> pageList = testDemoService.myPage(page, queryWrapper); return Result.OK(pageList); }
第三章:前端主要设计
3.1 流程前端组件的设计
这里主要介绍两个,流程提交与历史信息
- 流程提交申请组件:代码如下:
<template> <span> <a-button :type="btnType" @click="applySubmit()" :loading="submitLoading">{{text}}</a-button> <a-modal :z-index="100" :title="firstInitiatorTitle" @cancel="firstInitiatorOpen = false" v-model:visible="firstInitiatorOpen" :width="'50%'" append-to-body> <a-descriptions bordered layout="vertical"> <a-descriptions-item :span="3"> <a-badge status="processing" text="选择提醒" /> </a-descriptions-item> <a-descriptions-item label="重新发起新流程按钮" labelStyle="{ color: '#fff', fontWeight: 'bold', fontSize='18px'}"> 重新发起新流程会删除之前发起的任务,重新开始. </a-descriptions-item> <a-descriptions-item label="继续发起老流程按钮"> 继续发起流程就在原来流程基础上继续流转. </a-descriptions-item> </a-descriptions> <span slot="footer" class="dialog-footer"> <el-button type="primary" @click="ReStartByDataId(true)">重新发起新流程</el-button> <el-button type="primary" @click="ReStartByDataId(false)">继续发起老流程</el-button> <el-button @click="firstInitiatorOpen = false">取 消</el-button> </span> </a-modal> <!--挂载关联多个流程--> <a-modal @cancel="flowOpen = false" :title="flowTitle" v-model:visible="flowOpen" width="70%" append-to-body> <el-row :gutter="64"> <el-col :span="20" :xs="64" style="width: 100%"> <el-table ref="singleTable" :data="processList" border highlight-current-row style="width: 100%"> <el-table-column type="selection" width="55" align="center" /> <el-table-column label="主键" align="center" prop="id" v-if="true"/> <el-table-column label="业务表单名称" align="center" prop="businessName" /> <el-table-column label="业务服务名称" align="center" prop="businessService" /> <el-table-column label="流程名称" align="center" prop="flowName" /> <el-table-column label="关联流程发布主键" align="center" prop="deployId" /> <el-table-column label="前端路由地址" align="center" prop="routeName" /> <el-table-column label="组件注入方法" align="center" prop="component" /> <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <template #default="scope"> <el-button size="small" type="primary" @click="selectProcess(scope.row)">确定</el-button> </template> </el-table-column> </el-table> </el-col> </el-row> </a-modal> </span> </template> <script lang="ts" setup> import { startByDataId, isFirstInitiator, deleteActivityAndJoin, getProcesss } from "@/api/workflow/process"; defineOptions({ name: 'ActApplyBtn' }) const props = defineProps({ btnType: { type: String, default: 'primary', required: false }, dataId: { type: String, default: '', required: true }, serviceName: { type: String, default: '', required: true }, variables: { type: Object, default: {}, }, text: { type: String, default: '提交申请', required: false } }) const emit = defineEmits([ 'success' ]) const router = useRouter() const { proxy } = getCurrentInstance() as ComponentInternalInstance const modalVisible = ref(false) const submitLoading = ref(false) const form = ref<any>({}) const firstInitiatorOpen = ref(false) const firstInitiatorTitle = ref('') // 关联流程数据 const processList = ref<any>([]) const flowOpen = ref(false) const flowTitle = ref('') const selectFlowId = ref('') //选择或使用的流程ID const error = ref('') const selectProcess = (row) => { selectFlowId.value = row.id; flowOpen.value = false; var params = Object.assign({ dataId: props.dataId }, props.variables); startByDataId(props.dataId, selectFlowId.value, props.serviceName, params) .then(res => { //console.log("startByDataId res",res); if (res.code == 200 ) { proxy?.$modal.msgSuccess(res.msg); emit('success'); } else { proxy?.$modal.msgError(res.msg); } }) .finally(() => (submitLoading.value = false)); } const ReStartByDataId = (isNewFlow: boolean) => { if(isNewFlow) { submitLoading.value = true; deleteActivityAndJoin(props.dataId,props.variables) .then(res => { if (res.code == 200 && res.result) { //若删除成功 var params = Object.assign({ dataId: props.dataId }, props.variables); startByDataId(props.dataId, selectFlowId.value, props.serviceName, params) .then(res => { if (res.code == 200) { firstInitiatorOpen.value = false; proxy?.$modal.msgSuccess(res.message); emit('success'); } else { proxy?.$modal.msgError(res.message); } }) } }) .finally(() => (submitLoading.value = false)); } else {//继续原有流程流转,跳到流程处理界面上 //console.log("props.variables",props.variables); router.push({ path: '/flowable/task/record/index', query: { procInsId: props.variables.processInstanceId, deployId: props.variables.deployId, taskId: props.variables.taskId, businessKey: props.dataId, nodeType: "", category: "zdyyw", finished: true }, }) } } const applySubmit = () => { if (props.dataId && props.dataId.length < 1) { error.value = '必须传入参数dataId'; proxy?.$modal.msgError(error.value); return; } if (props.serviceName && props.serviceName.length < 1) { error.value = '必须传入参数serviceName'; proxy?.$modal.msgError(error.value); return; } else { error.value = ''; } //对于自定义业务,判断是否是驳回或退回的第一个发起人节点 submitLoading.value = true; isFirstInitiator(props.dataId, props.variables) .then(res => { if (res.code === 200 && res.data) { //若是,弹出窗口选择重新发起新流程还是继续老流程 firstInitiatorTitle.value = "根据自己需要进行选择" firstInitiatorOpen.value = true; } else { submitLoading.value = true; const processParams = { serviceName: props.serviceName } getProcesss(processParams).then(res => {/**查询关联流程信息 */ processList.value = res.data; submitLoading.value = false; if (processList.value && processList.value.length > 1) { flowOpen.value = true; } else if (processList.value && processList.value.length === 1) { selectFlowId.value = res.data[0].id; var params = Object.assign({ dataId: props.dataId }, props.variables); startByDataId(props.dataId, selectFlowId.value, props.serviceName, params) .then(res => { console.log("startByDataId res",res); if (res.code == 200 ) { proxy?.$modal.msgSuccess(res.msg); emit('success'); } else { proxy?.$modal.msgError(res.msg); } }) .finally(() => (submitLoading.value = false)); } else { proxy?.$modal.msgError("检查该业务是否已经关联流程!"); } }) .finally(() => (submitLoading.value = false)); } }) .finally(() => (submitLoading.value = false)); } </script>
- 历史信息组件:主要代码如下:
<style lang="less"> </style> <template> <span> <a-button :type="btnType" @click="history()" >{{text}}</a-button> <a-modal title="审批历史" v-model:visible="modalLsVisible" :mask-closable="true" :width="'70%'" :footer="null"> <div v-if="modalLsVisible" style="max-height: 550px; overflow-y: scroll;"> <HistoricDetail ref="historicDetail" :data-id="dataId"></HistoricDetail> </div> </a-modal> </span> </template> <script setup lang="ts"> import HistoricDetail from './HistoricDetail'; defineOptions({ name: 'ActHistoricDetailBtn' }) const props = defineProps({ btnType: { type: String, default: 'primary', required: false , }, dataId: { type: String, default: '', required: true }, text: { type: String, default: '审批历史', required: false } }) const { proxy } = getCurrentInstance() as ComponentInternalInstance; const modalLsVisible = ref(false) const history = () => { if (!props.dataId) { proxy?.$modal.msgError('流程实例ID不存在'); return; } modalLsVisible.value = true; } </script>
<style lang="less"> </style> <template> <div class="search"> <el-tabs tab-position="top" v-model="activeName" :value="processed === true ? 'approval' : 'form'" @tab-click="changeTab"> <el-tab-pane label="表单信息" name="form"> <div v-if="customForm.visible"> <!-- 自定义表单 --> <component ref="refCustomForm" :disabled="customForm.disabled" :is="customForm.formComponent" :model="customForm.model" :customFormData="customForm.customFormData" :isNew = "customForm.isNew"></component> </div> <div style="margin-left:10%;margin-bottom: 30px"> <!--对上传文件进行显示处理,临时方案 add by nbacheng 2022-07-27 --> <el-upload action="#" :on-preview="handleFilePreview" :file-list="fileList" v-if="fileDisplay" /> </div> </el-tab-pane > <el-tab-pane label="流转记录" name="record"> <el-card class="box-card" shadow="never"> <el-col :span="20" :offset="2"> <div class="block"> <el-timeline> <el-timeline-item v-for="(item,index) in historyProcNodeList" :key="index" :icon="setIcon(item.endTime)" :color="setColor(item.endTime)"> <p style="font-weight: 700">{{ item.activityName }}</p> <el-card v-if="item.activityType === 'startEvent'" class="box-card" shadow="hover"> {{ item.assigneeName }} 在 {{ item.createTime }} 发起流程 </el-card> <el-card v-if="item.activityType === 'userTask'" class="box-card" shadow="hover"> <el-descriptions :column="5" :labelStyle="{'font-weight': 'bold'}"> <el-descriptions-item label="实际办理">{{ item.assigneeName || '-'}}</el-descriptions-item> <el-descriptions-item label="候选办理">{{ item.candidate || '-'}}</el-descriptions-item> <el-descriptions-item label="接收时间">{{ item.createTime || '-'}}</el-descriptions-item> <el-descriptions-item label="办结时间">{{ item.endTime || '-' }}</el-descriptions-item> <el-descriptions-item label="耗时">{{ item.duration || '-'}}</el-descriptions-item> </el-descriptions> <div v-if="item.commentList && item.commentList.length > 0"> <div v-for="(comment, index) in item.commentList" :key="index"> <el-divider content-position="left"> <el-tag :type="approveTypeTag(comment.type)" size="small">{{ commentType(comment.type) }}</el-tag> <el-tag type="info" effect="plain" size="small">{{ comment.time }}</el-tag> </el-divider> <span>{{ comment.fullMessage }}</span> </div> </div> </el-card> <el-card v-if="item.activityType === 'endEvent'" class="box-card" shadow="hover"> {{ item.createTime }} 结束流程 </el-card> </el-timeline-item> </el-timeline> </div> </el-col> </el-card> </el-tab-pane> <el-tab-pane label="流程跟踪" name="track"> <el-card class="box-card" shadow="never"> <process-viewer :key="`designer-${loadIndex}`" :style="'height:' + height" :xml="xmlData" :finishedInfo="finishedInfo" :allCommentList="historyProcNodeList" /> </el-card> </el-tab-pane> </el-tabs> </div> </template> <script setup lang="ts"> import {detailProcessByDataId} from "@/api/workflow/process"; import ProcessViewer from '@/components/ProcessViewer' import { useFlowable } from '@/views/workflow/hooks/useFlowable' defineOptions({ name: 'HistoricDetail' }) const props = defineProps({ dataId: { type: String, default: '', required: true } }) const commentType = computed(() => { return val => { switch (val) { case '1': return '通过' case '2': return '退回' case '3': return '驳回' case '4': return '委派' case '5': return '转办' case '6': return '终止' case '7': return '撤回' case '8': return '拒绝' case '9': return '跳过' case '10': return '前加签' case '11': return '后加签' case '12': return '多实例加签' case '13': return '跳转' case '14': return '收回' } } }) const approveTypeTag = computed(() => { return val => { switch (val) { case '1': return 'success' case '2': return 'warning' case '3': return 'danger' case '4': return 'primary' case '5': return 'success' case '6': return 'danger' case '7': return 'info' } } }) const { getFormComponent } = useFlowable() const height = ref(document.documentElement.clientHeight - 205 + 'px;') // 模型xml数据 const loadIndex = ref(0) const xmlData = ref(null) const finishedInfo = ref({ finishedSequenceFlowSet: [], finishedTaskSet: [], unfinishedTaskSet: [], rejectedTaskSet: [] }) const historyProcNodeList = ref<any>([]) const processed = ref(false) const activeName = ref('form') //获取当然tabname const processFormList = ref<any>([]) const customForm = ref({ //自定义业务表单 formId: '', title: '', disabled: false, visible: false, formComponent: null, model: {}, /*流程数据*/ customFormData: {}, isNew: false, disableSubmit: true }) const fileDisplay = ref(false) // formdesigner是否显示上传的文件控件 const fileList = ref<any>([]) //表单设计器上传的文件列表 const key = ref<any>() const init = () => { // 获取流程变量 detailProcesssByDataId(props.dataId); } const detailProcesssByDataId = (dataId) => { const params = {dataId: dataId} detailProcessByDataId(params).then(res => { console.log("detailProcessByDataId res=",res); if (res.code === 200 && res.data != null) { const data = res.data; xmlData.value = data.bpmnXml; processFormList.value = data.processFormList; if(processFormList.value.length == 1 && processFormList.value[0].formValues.hasOwnProperty('routeName')) { customForm.value.disabled = true; customForm.value.visible = true; customForm.value.formComponent = getFormComponent(processFormList.value[0].formValues.routeName).component; customForm.value.model = processFormList.value[0].formValues.formData; customForm.value.customFormData = processFormList.value[0].formValues.formData; console.log("detailProcess customForm.value",customForm.value); } historyProcNodeList.value = data.historyProcNodeList; finishedInfo.value = data.flowViewer; } }) } const setIcon = (val) => { if (val) { return "el-icon-check"; } else { return "el-icon-time"; } } const setColor = (val) => { if (val) { return "#2bc418"; } else { return "#b3bdbb"; } } onMounted(() => { init(); }); </script> <style lang="scss" scoped> .clearfix:before, .clearfix:after { display: table; content: ""; } .clearfix:after { clear: both } .box-card { width: 100%; margin-bottom: 20px; } .el-tag + .el-tag { margin-left: 10px; } .el-row { margin-bottom: 20px; &:last-child { margin-bottom: 0; } } .el-col { border-radius: 4px; } .button-new-tag { margin-left: 10px; } </style>
3.2 寻找业务表单的设计
根据业务服务名称找到对应的业务表单,代码如下:
import { listCustomForm } from "@/api/workflow/customForm"; export const useFlowable = () => { const customformList = ref<any>([]); const formQueryParams = reactive<any>({ pageNum: 1, pageSize: 1000 }); const allFormComponent = computed(() => { return customformList.value; }) /* 挂载自定义业务表单列表 */ const ListCustomForForm = async () => { listCustomForm(formQueryParams).then(res => { let cfList = res.rows; cfList.forEach((item, index) => { let cms = { text:item.flowName, routeName:item.routeName, component: markRaw(defineAsyncComponent( () => import(/* @vite-ignore */`../../${item.routeName}.vue`))), businessTable:'wf_demo' } customformList.value.push(cms); }) }) } const getFormComponent = (routeName) => { return allFormComponent.value.find((item) => item.routeName === routeName) || {} } onMounted(() => { ListCustomForForm(); }); return { allFormComponent, getFormComponent } }
3.3 业务表单的设计
这里主要是列表的testDemo为例,代码如下:
<template> <div class="app-container"> <el-form v-model="queryParams" ref="queryFormRef" size="small" :inline="true" v-show="showSearch" label-width="68px"> <el-form-item label="用户账号" prop="userName"> <el-input v-model="queryParams.userName" placeholder="请输入用户账号" clearable @keyup.enter.native="handleQuery" /> </el-form-item> <el-form-item label="用户昵称" prop="nickName"> <el-input v-model="queryParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter.native="handleQuery" /> </el-form-item> <el-form-item label="用户邮箱" prop="email"> <el-input v-model="queryParams.email" placeholder="请输入用户邮箱" clearable @keyup.enter.native="handleQuery" /> </el-form-item> <el-form-item label="头像地址" prop="avatar"> <el-input v-model="queryParams.avatar" placeholder="请输入头像地址" clearable @keyup.enter.native="handleQuery" /> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button> <el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button> </el-form-item> </el-form> <el-row :gutter="10" class="mb8"> <el-col :span="1.5"> <el-button type="primary" plain icon="el-icon-plus" size="small" @click="handleAdd" v-hasPermi="['workflow:demo:add']" >新增</el-button> </el-col> <el-col :span="1.5"> <el-button type="success" plain icon="el-icon-edit" size="small" :disabled="single" @click="handleUpdate" v-hasPermi="['workflow:demo:edit']" >修改</el-button> </el-col> <el-col :span="1.5"> <el-button type="danger" plain icon="el-icon-delete" size="small" :disabled="multiple" @click="handleDelete" v-hasPermi="['workflow:demo:remove']" >删除</el-button> </el-col> <el-col :span="1.5"> <el-button type="warning" plain icon="el-icon-download" size="small" @click="handleExport" v-hasPermi="['workflow:demo:export']" >导出</el-button> </el-col> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> </el-row> <el-table v-loading="loading" :data="demoList" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="20" align="center" /> <el-table-column label="用户ID" align="center" prop="demoId" v-if="true"/> <el-table-column label="用户账号" align="center" prop="userName" /> <el-table-column label="用户昵称" align="center" prop="nickName" /> <el-table-column label="流程状态" align="center" prop="actStatus" /> <el-table-column label="待处理节点" align="center" prop="taskName" /> <el-table-column label="处理人" align="center" prop="todoUsers" /> <el-table-column label="操作" align="center" width="420"> <template #default="scope"> <act-apply-btn @success="getList" :data-id="String(scope.row.demoId)" :serviceName="serviceName" :variables="scope.row"></act-apply-btn> <a-divider type="vertical" /> <act-historic-detail-btn :data-id="String(scope.row.demoId)"></act-historic-detail-btn> <a-divider type="vertical" /> <el-button size="default" type="primary" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['workflow:demo:edit']" >修改</el-button> <el-button size="default" type="primary" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:demo:remove']" >删除</el-button> </template> </el-table-column> </el-table> <pagination v-show="total>0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> <!-- 添加或修改DEMO对话框 --> <el-dialog :title="title" v-model="open" width="500px" append-to-body> <el-form ref="formRef" :model="form" :rules="rules" label-width="80px"> <el-form-item label="用户账号" prop="userName"> <el-input v-model="form.userName" placeholder="请输入用户账号" /> </el-form-item> <el-form-item label="用户昵称" prop="nickName"> <el-input v-model="form.nickName" placeholder="请输入用户昵称" /> </el-form-item> <el-form-item label="用户邮箱" prop="email"> <el-input v-model="form.email" placeholder="请输入用户邮箱" /> </el-form-item> <el-form-item label="头像地址" prop="avatar"> <el-input v-model="form.avatar" placeholder="请输入头像地址" /> </el-form-item> <el-form-item label="备注" prop="remark"> <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button> <el-button @click="cancel">取 消</el-button> </div> </el-dialog> </div> </template> <script setup name="Demo" lang="ts"> import { listDemo, getDemo, delDemo, addDemo, updateDemo } from "@/api/workflow/demo"; import ActApplyBtn from "../components/ActApplyBtn"; import ActHistoricDetailBtn from "../components/ActHistoricDetailBtn"; const { proxy } = getCurrentInstance() as ComponentInternalInstance; const queryFormRef = ref(null) const formRef = ref<ElFormInstance>(); // 按钮loading const buttonLoading = ref(false) // 遮罩层 const loading = ref(true) // 选中数组 const ids = ref<any>([]) // 非单个禁用 const single = ref(true) // 非多个禁用 const multiple = ref(true) // 显示搜索条件 const showSearch = ref(true) // 总条数 const total = ref(0) // DEMO表格数据 const demoList = ref<any>([]) // 弹出层标题 const title = ref("") // 是否显示弹出层 const open = ref(false) // 查询参数 const queryParams = ref ({ pageNum: 1, pageSize: 10, userName: undefined, nickName: undefined, email: undefined, avatar: undefined, status: undefined, }) const serviceName = ref('wfDemoService') // 表单参数 const form = ref({ demoId: undefined, userName: '', nickName: '', }) // 表单校验 const rules = { demoId: [ { required: true, message: "DEMO-ID不能为空", trigger: "blur" } ], userName: [ { required: true, message: "用户账号不能为空", trigger: "blur" } ] } /** 查询DEMO列表 */ const getList = () => { loading.value = true; listDemo(queryParams.value).then(response => { demoList.value = response.rows; total.value = response.total; loading.value = false; }); } // 取消按钮 const cancel = () => { open.value = false; reset(); } // 表单重置 const reset = () => { form.value = { demoId: undefined, userName: '', nickName: '', email: undefined, avatar: undefined, status: undefined, delFlag: undefined, createBy: undefined, createTime: undefined, updateBy: undefined, updateTime: undefined, remark: undefined }; formRef.value?.resetFields(); } /** 搜索按钮操作 */ const handleQuery = () => { queryParams.value.pageNum = 1; getList(); } /** 重置按钮操作 */ const resetQuery = () => { queryFormRef.value?.resetFields(); handleQuery(); } // 多选框选中数据 const handleSelectionChange = (selection) => { ids.value = selection.map(item => item.demoId) single.value = selection.length!==1 multiple.value = !selection.length } /** 新增按钮操作 */ const handleAdd = () => { reset(); open.value = true; title.value = "添加DEMO"; } /** 修改按钮操作 */ const handleUpdate = (row) => { loading.value = true; reset(); const demoId = row.demoId || ids.value getDemo(demoId).then(response => { loading.value = false; form.value = response.data; open.value = true; title.value = "修改DEMO"; }); } /** 提交按钮 */ const submitForm = () => { formRef.value?.validate(async (valid: boolean) => { if (valid) { buttonLoading.value = true; if (form.value.demoId != null) { updateDemo(form.value).then(response => { proxy?.$modal.msgSuccess("修改成功"); open.value = false; getList(); }).finally(() => { buttonLoading.value = false; }); } else { addDemo(form.value).then(response => { proxy?.$modal.msgSuccess("新增成功"); open.value = false; getList(); }).finally(() => { buttonLoading.value = false; }); } } }); } /** 删除按钮操作 */ const handleDelete = (row) => { const demoIds = row.demoId || ids.value; proxy?.$modal.confirm('是否确认删除DEMO编号为"' + demoIds + '"的数据项?').then(() => { loading.value = true; return delDemo(demoIds); }).then(() => { loading.value = false; getList(); proxy?.$modal.msgSuccess("删除成功"); }).catch(() => { }).finally(() => { loading.value = false; }); } /** 导出按钮操作 */ const handleExport = () => { proxy?.download('workflow/demo/export', { ...queryParams.value }, `demo_${new Date().getTime()}.xlsx`) } onMounted(() => { getList(); }) </script> <style lang="scss" scoped> .small-padding-demo { .cell { padding-left: 5px; padding-right: 5px; } } .fixed-width-demo { .el-button--mini { padding: 7px 10px; width: 60px; } } </style>
第四章:主要界面设计
4.1 业务表单与流程的关联配置
点击关联后如下:
4.2 testDemo业务列表
- 这里以testDemo列表为例,如下:
- 点击历史信息可以看到:
第五章:总结
从上面我们可以看到,通过前后端技术实现了业务表单的流程嵌入,同时可以不影响原来的业务逻辑,大大简化了业务表单的流程审批开发,可以专注于业务的逻辑开发。
5.1 前端方面
关键是通过动态组件来动态显示对应的业务表单,实现不同业务的通用显示。
5.2 后端方面
提供流程的通用接口,可以扩展实际业务流程的业务需求。
总之,总体上在不影响原先业务的开发设计上,简单方便地加上流程功能,快速实现实际业务的需要。