更多ruoyi-nbcio功能请看演示系统
gitee源代码地址
前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio
演示地址:RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/
更多nbcio-boot功能请看演示系统
gitee源代码地址
后端代码: https://gitee.com/nbacheng/nbcio-boot
前端代码:https://gitee.com/nbacheng/nbcio-vue.git
在线演示(包括H5) : http://218.75.87.38:9888
1、因为修改vue3后也要支持自定义表单额显示处理,所以修改如下:
<template> <div class="app-container"> <el-tabs tab-position="top" :model-value="processed === true ? 'approval' : 'form'"> <el-tab-pane label="任务办理" name="approval" v-if="processed === true"> <el-card class="box-card" shadow="hover" v-if="taskFormOpen"> <template #header> <span>填写表单</span> </template> <div class="cu-content"> <v-form-render :form-json="{}" :form-data="{}" ref="vfRenderRef" /> </div> </el-card> <el-card class="box-card" shadow="hover"> <template #header> <span>审批流程</span> </template> <el-row> <el-col :span="20" :offset="2"> <el-form ref="taskFormRef" :model="taskForm" :rules="rules" label-width="120px"> <el-form-item label="审批意见" prop="comment"> <el-input type="textarea" :rows="5" v-model="taskForm.comment" placeholder="请输入 审批意见" /> </el-form-item> <el-form-item label="抄送人" prop="copyUserIds"> <el-tag :key="index" v-for="(item, index) in copyUser" closable :disable-transitions="false" @close="handleClose('copy', item)"> {{ item.nickName }} </el-tag> <el-button class="button-new-tag" type="primary" icon="el-icon-plus" circle @click="onSelectCopyUsers" /> </el-form-item> <el-form-item label="指定审批人" prop="copyUserIds"> <el-tag :key="index" v-for="(item, index) in nextUser" closable :disable-transitions="false" @close="handleClose('next', item)"> {{ item.nickName }} </el-tag> <el-button class="button-new-tag" type="primary" icon="el-icon-plus" circle @click="onSelectNextUsers" /> </el-form-item> </el-form> </el-col> </el-row> <el-row :gutter="10" type="flex" justify="center"> <el-col :span="1.5"> <el-button icon="CircleCheck" type="success" @click="handleComplete">通过</el-button> </el-col> <el-col :span="1.5"> <el-button icon="ChatLineSquare" type="primary" @click="handleDelegate">委派</el-button> </el-col> <el-col :span="1.5"> <el-button icon="Switch" type="success" @click="handleTransfer">转办</el-button> </el-col> <el-col :span="1.5"> <el-button icon="RefreshLeft" type="warning" @click="handleReturn">退回</el-button> </el-col> <el-col :span="1.5"> <el-button icon="CircleClose" type="danger" @click="handleReject">拒绝</el-button> </el-col> </el-row> </el-card> </el-tab-pane> <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 v-if="formVisible"> <el-card class="box-card" shadow="never" v-for="(item, index) in processFormList" :key="index"> <template #header> <span>{{ item.title }}</span> </template> <!--流程处理表单模块--> <div class="cu-content"> <v-form-render :form-json="item.formModel" :form-data="item.formData" ref="vFormRenderRef" /> </div> </el-card> </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" :type="tagType(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)">{{ commentType(comment.type) }}</el-tag> <el-tag type="info" effect="plain">{{ 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="processXml" :finishedInfo="finishedInfo" :allCommentList="historyProcNodeList" /> </el-card> </el-tab-pane> </el-tabs> <!--退回流程--> <el-dialog :title="returnDialog.title" v-model="returnDialog.visible" width="40%" append-to-body> <el-radio-group v-model="returnTaskKey"> <el-radio-button v-for="item in returnTaskList" :key="item.id" :label="item.id"> {{ item.name }} </el-radio-button> </el-radio-group> <template #footer> <el-button @click="returnDialog.visible = false">取 消</el-button> <el-button type="primary" @click="submitReturn">确 定</el-button> </template> </el-dialog> <el-dialog :title="userSelectDialog.title" v-model="userSelectDialog.visible" width="60%" append-to-body> <el-row type="flex" :gutter="20"> <!--部门数据--> <el-col :span="5"> <el-card shadow="never" style="height: 100%"> <template #header> <span>部门列表</span> </template> <div class="head-container"> <el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable /> <el-tree :data="deptOptions" :props="{ label: 'label', children: 'children' }" :expand-on-click-node="false" :filter-node-method="filterNode" ref="deptTreeRef" default-expand-all @node-click="handleNodeClick" /> </div> </el-card> </el-col> <el-col :span="18"> <el-table ref="userTable" :key="userSelectType" height="500" v-loading="userLoading" :data="userList" highlight-current-row @current-change="changeCurrentUser" @selection-change="handleSelectionChange" > <el-table-column v-if="userSelectType === 'copy' || userSelectType === 'next'" width="55" type="selection" /> <el-table-column v-else width="30"> <template #default="scope"> <el-radio :label="scope.row.userId" v-model="currentUserId">{{''}}</el-radio> </template> </el-table-column> <el-table-column label="用户名称" align="center" prop="userName" /> <el-table-column label="用户昵称" align="center" prop="nickName" /> <el-table-column label="手机" align="center" prop="phonenumber" /> </el-table> <pagination :total="userTotal" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> </el-col> </el-row> <template #footer> <el-button @click="userSelectDialog.visible = false">取 消</el-button> <el-button type="primary" @click="submitUserData">确 定</el-button> </template> </el-dialog> </div> </template> <script setup name="Detail" lang="ts"> import { detailProcess, processIscompleted } from "@/api/workflow/process"; import { getProcessVariables } from '@/api/workflow/task'; import { complete, delegate, transfer, rejectTask, returnList, returnTask } from "@/api/workflow/work/task"; import { deptTreeSelect, selectUser } from "@/api/workflow/identity"; import { TaskForm } from "@/api/workflow/work/types"; import { UserVO, DeptVO } from "@/api/workflow/identity/types"; import { ComponentInternalInstance } from "vue"; import {useRoute} from "vue-router"; import { useFlowable } from '@/views/workflow/hooks/useFlowable' const route = useRoute(); const router = useRouter(); const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { getFormComponent } = useFlowable() const userList = ref<UserVO[]>([]); const processed = ref(false); const taskFormOpen = ref(false) const userMultipleSelection = ref([]); const userSelectType = ref(); const currentUserId = ref(); const userLoading = ref(false); const userTotal = ref(0); const loadIndex = ref(''); const height = ref(document.documentElement.clientHeight - 205 + 'px;'); const processXml = ref(''); const taskFormVisible = ref(false); const processFormList = ref([]); const taskFormData = ref([]); const historyProcNodeList = ref<any>(); const formVisible = ref(false); const finishedInfo = ref({}); const customForm = ref({ //自定义业务表单 formId: '', title: '', disabled: false, visible: false, formComponent: null, model: {}, /*流程数据*/ customFormData: {}, isNew: false, disableSubmit: true }) const deptName = ref(''); const deptOptions = ref<DeptVO[]>([]); const returnTaskList = ref(); const returnTaskKey = ref(); const copyUser = ref([]); const nextUser = ref([]); const taskFormRef = ref(ElForm); const vFormRenderRef = ref(null); const deptTreeRef = ref(null); const refCustomForm = ref(null); const activeName = ref(''); //获取当然tabname const returnDialog = reactive<DialogOption>({ visible: false, title: '退回流程' }); const userSelectDialog = reactive<DialogOption>({ visible: false, title: '' }); const taskForm = reactive<TaskForm>({ comment: '', procInsId: '', taskId: '', userId: '', copyUserIds: '', nextUserIds: '', vars: '', targetKey: '' }); const rules = ref({ comment: [{ required: true, message: '请输入审批意见', trigger: 'blur' }] }); const queryParams = ref({ pageNum: 1, pageSize: 10 }); const tagType = (val: any) => { if (val) { return "success"; } else { return "info"; } } const commentType = (val: string) => { 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 = (val: string) => { 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 initData = () => { taskForm.procInsId = route.params && route.params.procInsId as string; taskForm.taskId = route.query && route.query.taskId as string; processed.value = route.query && (route.query.processed || false) === "true"; // 流程任务重获取变量表单 //getProcessDetails(taskForm.procInsId, taskForm.taskId); //判断流程是否结束 processIscompleted({procInsId: taskForm.procInsId}).then(res => { console.log("processIscompleted res=",res); if(res.data) { processed.value = false; } // 获取流程变量 processVariables(taskForm.taskId); }); }; /** 通过条件过滤节点 */ const filterNode = (value: string, data: any) => { if (!value) return true return data.label.indexOf(value) !== -1 } // /** 根据名称筛选部门树 */ // watchEffect( // () => {deptTreeRef.value.filter(deptName.value);}, // { // flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行 // } // ); // 节点单击事件 const handleNodeClick = (data: any) => { getList(data.id); } /** 查询部门下拉树结构 */ const getTreeSelect = async () => { const res = await deptTreeSelect(); deptOptions.value = res.data; }; /** 查询用户列表 */ const getList = async (deptId?: number) => { userLoading.value = true; const res = await selectUser({deptId: deptId}); userLoading.value = false; userList.value = res.rows; userTotal.value = res.total; } /** 获取流程变量内容 */ const processVariables = (taskId: string) => { console.log("processVariables taskId",taskId); if (taskId) { getProcessVariables(taskId).then(res => { console.log("getProcessVariables res=",res); if(res.code == 200) { if(res.data.hasOwnProperty('dataId') && res.data.dataId) { customForm.value.formId = res.data.dataId; // 流程任务重获取变量表单 getProcessDetails(taskForm.procInsId, taskForm.taskId, res.data.dataId); loadIndex.value = taskForm.procInsId; if(processed.value) { activeName.value = "approval"; } else { activeName.value = "form"; } } else { // 流程任务重获取变量表单 getProcessDetails(taskForm.procInsId, taskForm.taskId, ""); loadIndex.value = taskForm.procInsId; if(processed.value) { activeName.value = "approval"; } else { activeName.value = "form"; // 回填数据,这里主要是处理文件列表显示,临时解决,以后应该在formdesigner里完成 /*this.processFormList.forEach((item, i) => { if (item?.hasOwnProperty('list')) { fillFormData(item.list, item) // 更新表单 this.key = +new Date().getTime() } });*/ } } } }); } } const fillFormData = (list, formConf) => { // for formdesigner console.log("fillFormData list=",list); console.log("fillFormData formConf=",formConf); list.forEach((item, i) => { // 特殊处理el-upload,包括 回显图片 if(formConf.formValues[item.id] != '') { const val = formConf.formValues[item.id]; if (item.ele === 'el-upload') { console.log('fillFormData val=',val) if(item['list-type'] != 'text') {//图片 this.fileList = [] //隐藏加的el-upload文件列表 //item['file-list'] = JSON.parse(val) if(val != '') { item['file-list'] = JSON.parse(val) } } else { //列表 console.log("列表fillFormData val",val) this.fileList = JSON.parse(val) item['file-list'] = [] //隐藏加的表单设计器的文件列表 } // 回显图片 this.fileDisplay = true } } if (Array.isArray(item.columns)) { this.fillFormData(item.columns, formConf) } }) } const getProcessDetails = async (procInsId: string, taskId: string, dataId: string) => { const params = {procInsId: procInsId, taskId: taskId, dataId: dataId} const res = await detailProcess(params); const data = res.data; console.log("getProcessDetails data",data); processXml.value = data.bpmnXml; processFormList.value = data.processFormList; console.log("processFormList",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; if(data.startUserNode) { customForm.value.isNew = true; customForm.value.disabled = false; } console.log("detailProcess customForm",customForm.value); } else { taskFormVisible.value = data.existTaskForm; if (taskFormVisible.value) { taskFormData.value = data.taskFormData; } formVisible.value = true; nextTick(() => { processFormList.value.forEach((item: any, index: any) => { if (item.disabled) { vFormRenderRef.value[index].disableForm(); } }) }) } historyProcNodeList.value = data.historyProcNodeList; finishedInfo.value = data.flowViewer; } const onSelectCopyUsers = () => { userMultipleSelection.value = copyUser; onSelectUsers('添加抄送人', 'copy') } const onSelectNextUsers = () => { userMultipleSelection.value = nextUser; onSelectUsers('指定审批人', 'next') } const onSelectUsers = (title: string, type: string) => { userSelectType.value = type; userSelectDialog.title = title; userSelectDialog.visible = true; getTreeSelect(); getList() } /** 通过任务 */ const handleComplete = () => { // 校验表单 taskFormRef.value.validate(async (valid: boolean) => { if (valid) { const res = await complete(taskForm) proxy?.$modal.msgSuccess(res.msg); goBack(); } }); } /** 委派任务 */ const handleDelegate = () => { userSelectType.value = 'delegate'; userSelectDialog.title = '委派任务' userSelectDialog.visible = true; getTreeSelect(); } /** 转办任务 */ const handleTransfer = () => { userSelectType.value = 'transfer'; userSelectDialog.title = '转办任务'; userSelectDialog.visible = true; getTreeSelect(); } /** 退回任务 */ const handleReturn = async () => { // 校验表单 taskFormRef.value.validate(async (valid: boolean) => { if (valid) { const res = await returnList(taskForm); returnTaskList.value = res.data; returnDialog.visible = true; } }); } /** 拒绝任务 */ const handleReject = async () => { await proxy?.$modal.confirm('拒绝审批单流程会终止,是否继续?'); await rejectTask(taskForm); proxy?.$modal.msgSuccess("操作成功"); goBack(); } /** 返回页面 */ const goBack = () => { // 关闭当前标签页并返回上个页面 proxy?.$tab.closePage(route); router.back() } // 关闭标签 const handleClose = (type: any, tag: any) => { let userObj = userMultipleSelection.value.find(item => item.userId === tag.id); userMultipleSelection.value.splice(userMultipleSelection.value.indexOf(userObj), 1); if (type === 'copy') { copyUser.value = userMultipleSelection.value; // 设置抄送人ID if (copyUser.value && copyUser.value.length > 0) { const val = copyUser.value.map(item => item.id); taskForm.copyUserIds = val instanceof Array ? val.join(',') : val; } else { taskForm.copyUserIds = ''; } } else if (type === 'next') { nextUser.value = userMultipleSelection.value; // 设置抄送人ID if (nextUser.value && nextUser.value.length > 0) { const val = nextUser.value.map(item => item.id); taskForm.nextUserIds = val instanceof Array ? val.join(',') : val; } else { taskForm.nextUserIds = ''; } } } const changeCurrentUser = (val: any) => { // currentUserId = val.userId } const handleSelectionChange = () => { } const submitReturn = () => { // 校验表单 taskFormRef.value.validate(async (valid: boolean) => { if (valid) { if (!returnTaskKey) { proxy?.$modal.msgError("请选择退回节点!"); } taskForm.targetKey = returnTaskKey.value; const res = await returnTask(taskForm); proxy?.$modal.msgSuccess(res.msg); goBack() } }); console.log("taskForm => ", taskForm.targetKey); } const submitUserData = () => { let type = userSelectType.value; if (type === 'copy' || type === 'next') { if (!userMultipleSelection || userMultipleSelection.value.length <= 0) { proxy?.$modal.msgError("请选择用户"); return false; } let userIds = userMultipleSelection.value.map(k => k.userId); if (type === 'copy') { // 设置抄送人ID信息 copyUser.value = userMultipleSelection.value; taskForm.copyUserIds = userIds instanceof Array ? userIds.join(',') : userIds; } else if (type === 'next') { // 设置下一级审批人ID信息 nextUser.value = userMultipleSelection.value; taskForm.nextUserIds = userIds instanceof Array ? userIds.join(',') : userIds; } userSelectDialog.visible = false; } else { if (!taskForm.comment) { proxy?.$modal.msgError("请输入审批意见"); return false; } if (!currentUserId.value) { proxy?.$modal.msgError("请选择用户"); return false; } taskForm.userId = currentUserId.value; if (type === 'delegate') { delegate(taskForm).then(res => { proxy?.$modal.msgSuccess(res.msg); goBack(); }); } if (type === 'transfer') { transfer(taskForm).then(res => { proxy?.$modal.msgSuccess(res.msg); goBack(); }); } } } onMounted(() => { initData(); }); </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>
2、其中hooks的useFlowable修改如下(原先minixs)
export const useFlowable = () => { const allFormComponent = computed(() => { return [ { text:'单表示例', routeName: '@/views/workflow/demo/wf', component: defineAsyncComponent( () => import('@/views/workflow/demo/wf')), businessTable:'wf_demo' }, /*{ text:'主子表示例', routeName:'@/views/workflow/demo/modules/CesOrderMainForm', component:() => defineAsyncComponent(import(`@/views/workflow/demo/modules/CesOrderMainForm`)), businessTable:'ces_order_main' }*/ ] }) const getFormComponent = (routeName) => { return allFormComponent.value.find((item) => item.routeName === routeName) || {} } return { allFormComponent, getFormComponent } }
3、效果图如下: