!88 合并flowable工作流功能

* merge 合并dev
* add 添加抄送查询
* add 添加我的已办
* add 添加抄送
* add 添加附件下载
* update 优化类型
* Merge remote-tracking branch 'origin/future/flowable' into future/flowable
* fix 修复 流程设计器打包部署报错问题
* add 添加审批附件上传
* update 修复固定值未在xml显示问题
* update 修复跳转条件回显Object问题
* update 优化面板文件名称
* update 调整key
* remove 移除旧设计器,添加xml保存
* update 增加任务面板提示
* update 优化xml预览和svg预览
* update 优化xml预览和svg预览
* update 优化模型设计
* update 优化设计器
* update 优化设计器
* update 优化设计器
* update 删除旧设计器
* update 删除console.log
* add 添加模型接口
* update 优化预览xml和svg样式被修改问题
* update 优化属性面板,增加展开动画
* update 去除开发模式
* update 优化任务栏样式
* update 优化图标渲染样式
* update 增加BpmnFactory类型
* update 增加BpmnFactory
* update 移除users和group
* update 移除无用类型
* update 优化页面类型
* update 去除多余属性
* update 完善流程线
* update 增加复杂网关
* update 完善流程
* update 完善网关
* update 优化网关汉化
* update 优化过期时间选择
* update 支持多实例
* update 增加类容提示
* update 支持选择组
* update 新增角色api
* update 优化roleSelect 选中未确定,再次打开还保留选中的问题
* update 优化userSelect 选中未确定,再次打开还保留选中的问题
* update 优化userSelect 选中未确定,再次打开还保留选中的问题
* update 去掉modeler store多余属性 bpmnModel
* update 优化属性面板,当面板未选中时默认展示流程面板
* update 优化TaskPanel类型,去掉roles属性
* update 优化用户api
* update 优化用户选择器
* update 优化执行监听器
* update 优化任务监听器
* update 优化usePanel方法
* update 选人优化
* update 增加扩展节点信息
* update 增加usePanel默认方法
* update 去除处理事件
* update 扩展flowable  userinfo属性
* update 全局modeler 改为非响应式
* update 增加hooks方法
* update 修改命名
* update 修改面板formData来源
* update 重写用户任务面板选择逻辑
* update 重写用户任务面板选择逻辑
* update 修改用户选择组件获取数据逻辑
* update 修改枚举类型
* update 修改默认配置列
* update 增加修改节点方法
* update 调整预览窗口大小
* update 优化用户选择组件 返回值
* update 优化用户选择组件
* update 新增通过ids 获取用户信息
* update 重写task面板选人 未完成
* update 升级用户选择 支持多选配置
* update 升级bpmnjs依赖版本
* update 增加useDialog类型
* update 调整全局样式
* update 代码高亮设置
* update 优化领用,归还加载
* update 增加选择角色
* update  新增角色选择组件
* update 新增过期时间选择组件
* update 调整任务面板样式
* update  调整全局dialog header 增加分割线
* update 代码高亮设置
* update  调整面板位置
* update 封装用户选择组件
* update 移除所有的节点描述
* update 删除分类
* update  调整面板位置
* update  修改命名,增加自定义渲染
* update  修改命名,增加自定义渲染
* update 增加 Element类型定义
* update 调整样式
* update 移除bpmn panel依赖,升级bpmn.js依赖到最新,修改汉化包
* update 调整类型声明文件
* update 调整类型声明文件
* update 优化面板工具
* update 优化面板工具
* Merge remote-tracking branch 'origin/future/flowable' into future/flowable
* update 优化面板工具
* Merge branch 'future/flowable' of https://gitee.com/JavaLionLi/plus-ui…
* add 添加修改办理人
* update 优化面板工具
* update 初始化流程数据
* Merge remote-tracking branch 'origin/future/flowable' into future/flowable
* add 流程设计面板
* update 调整初始化xml
* add 任务面板
* add 新增bpmn.js
* update 优化request请求类判断请求头方式
* update 流程定义预览 优化
* update 流程定义预览 优化
* update 去掉console.log
* update 优化工作流代码
* fix 修复待办任务 重置查询条件失效问题
* add 增加待办任务 接口类型,优化页面
* add 增加vite 启动预编译css
* fix 修复i18n无感刷新问题
* Merge branch 'dev' into future/flowable
* update 调整选择请假事件
* Merge branch 'dev' into future/flowable
* 同步dev代码
* Merge branch 'dev' into future/flowable
* 合并dev
* remove 设计器无用代码 调整请假查询
* update 调整请假申请
* update 移动请假表单包结构,调整设计器选择引用表单请求错误
* remove 移除动态表单
* update 调整流程办理
* Merge branch 'dev' into future/flowable
* Merge branch 'dev' into future/flowable
* Merge branch 'dev' into future/flowable
* Merge branch 'dev' into future/flowable
* 合并
* update 调整请假申请流程提交
* update 修改业务单据流程提交
* update 调整业务单据流程提交
* 优化代码
* 优化工作流代码缩进
* 项目格式化配置修改
* 调整代码缩进
* Merge branch 'ts' into future/flowable
* add 添加动态表单提交流程
* add 添加动态表单单据
* add 新增流程定义与表单关联
* update 调整点击左侧部门查询人员,部门刷新问题
* update 调整按钮图标
* 调整错别字
* update 调整流程定义图片预览
* add 添加流程实例迁移版本
* fix 修复我的单据无法提交问题
* Merge branch 'ts' into future/flowable
* remove 还原代码后端解决
* update 流程设计器中分配发起人变量错误,先移除
* fix 修复设计器无法编辑问题
* update 调整设计器请求头
* Merge branch 'ts' into future/flowable
* Merge branch 'ts' into future/flowable
* add 添加流程定义历史列表
* update 审批记录v2改为v3
* update 调整请假必填项
* add 添加请假申请示例,添加流程定义文件部署
* update 移除流程表单 formConfig 属性,表单配置信息都放一起便于使用。
* add 添加任务加签,减签
* update 调整流程作废
* update 优化流程状态
* add 添加任务驳回
* add 添加查询当前租户所有待办,已办任务
* add 增加审批意见
* add 添加流程办理弹窗确认组件
* add 添加任务作废理由
* update 调整流程实例,流程定义检索
* add 添加我的单据页面
* add 添加任务归还认领
* add 添加流程实例,流程定义分类查询
* add 添加模型分类查询
* add 添加流程分类
* Merge remote-tracking branch 'origin/future/flowable' into future/flowable
* add 添加流程表单管理页面
* add 集成vForm动态表单组件
* update xml调整超出滚动
* add 添加已办列表
* add 添加单据状态
* update 优化流程实例删除
* fix 修复流程实例查询挂起状态错误
* update 调整流程实例挂起激活状态
* add 添加流程实例列表
* update 调整流程定义弹窗提示
* add 添加流程定义列表,添加流程图,xml预览,添加简单流程启动,办理
* 调整审批记录悬浮逻辑
* 删除无用代码
* add 添加节点悬浮信息
* 调整流程预览
* 调整流程追踪
* add 添加审批记录
* add 模型设计的types
* update 修改排版
* add lang=ts
* update 修改ele的废弃api
* fix调整审批记录图片不显示问题
* add 添加任务待办,流程图
* 调整设计器关闭
* add 添加待办
* remove 删除无用代码
* update types
* 添加模型token验证
* 隐藏设计器验证按钮,隐藏表单,案例,应用程序等
* 添加模型部署
* 添加画图接口token,优化画图接口
* 添加工作流模型新增,修改,查询,删除,画图工具
This commit is contained in:
疯狂的狮子Li
2024-03-05 14:57:51 +00:00
parent 542f73f0e6
commit 0108df1334
70 changed files with 9502 additions and 9 deletions

View File

@ -0,0 +1,162 @@
<template>
<el-dialog v-model="visible" draggable title="审批记录" :width="props.width" :height="props.height" append-to-body
:close-on-click-modal="false">
<div v-loading="loading">
<div style="width: 100%;height: 300px;overflow: auto;position: relative;">
<div v-for="(graphic, index) in graphicInfoVos" :key="index" :style="{
position: 'absolute',
left: `${graphic.x}px`,
top: `${graphic.y}px`,
width: `${graphic.width}px`,
height: `${graphic.height}px`,
cursor: 'pointer',
zIndex: 99
}" @mouseover="handleMouseOver(graphic)" @mouseleave="handleMouseLeave()"></div>
<!-- 弹出的 div 元素 -->
<div v-show="popupVisible" class="triangle" :style="{
position: 'absolute',
left: `${graphicX}px`,
top: `${graphicY}px`,
backgroundColor: '#fff',
padding: '10px',
zIndex: 100
}">
<p>审批人员: {{ nodeInfo.nickName }}</p>
<p>节点状态{{ nodeInfo.status }}</p>
<p>开始时间{{ nodeInfo.startTime }}</p>
<p>结束时间{{ nodeInfo.endTime }}</p>
<p>审批耗时{{ nodeInfo.runDuration }}</p>
</div>
<el-image :src="src" />
</div>
<div>
<el-table :data="historyList" style="width: 100%" border fit max-height="570">
<el-table-column label="流程审批历史记录" align="center">
<el-table-column type="index" label="序号" align="center" width="50"></el-table-column>
<el-table-column prop="name" label="任务名称" sortable align="center"></el-table-column>
<el-table-column prop="nickName" label="办理人" sortable align="center"></el-table-column>
<el-table-column label="状态" sortable align="center">
<template #default="scope">
<el-tag type="success">{{ scope.row.statusName }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="comment" label="审批意见" sortable align="center"></el-table-column>
<el-table-column prop="attachmentList" label="附件" sortable align="center">
<template #default="scope">
<el-popover placement="right" v-if="scope.row.attachmentList && scope.row.attachmentList.length > 0" :width="310" trigger="click">
<template #reference>
<el-button style="margin-right: 16px">附件</el-button>
</template>
<el-table border :data="scope.row.attachmentList">
<el-table-column prop="name" width="202" :show-overflow-tooltip="true" label="附件名称"></el-table-column>
<el-table-column prop="name" width="80" align="center" :show-overflow-tooltip="true" label="操作">
<template #default="tool">
<el-button type="text" @click="handleDownload(tool.row.contentId)">下载</el-button>
</template>
</el-table-column>
</el-table>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="startTime" label="开始时间" sortable align="center"></el-table-column>
<el-table-column prop="endTime" label="结束时间" sortable align="center"></el-table-column>
<el-table-column prop="runDuration" label="运行时长" sortable align="center"></el-table-column>
</el-table-column>
</el-table>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { getHistoryProcessImage, getHistoryRecord } from '@/api/workflow/processInstance';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { ref } from 'vue';
const props = defineProps({
width: {
type: String,
default: '70%'
},
height: {
type: String,
default: '100%'
}
});
const loading = ref(false);
const src = ref('');
const visible = ref(false);
const historyList = ref<Array<any>>([]);
const deleteReason = ref<string>('');
const graphicInfoVos = ref<Array<any>>([]);
const nodeListInfo = ref<Array<any>>([]);
const popupVisible = ref(false);
const nodeInfo = ref<any>({});
const graphicX = ref<number | string>(0);
const graphicY = ref<number | string>(0);
//初始化查询审批记录
const init = async (processInstanceId: string) => {
visible.value = true;
loading.value = true;
historyList.value = [];
graphicInfoVos.value = [];
getHistoryProcessImage(processInstanceId).then((res) => {
src.value = 'data:image/png;base64,' + res.data
});
getHistoryRecord(processInstanceId).then((response) => {
historyList.value = response.data.historyRecordList;
graphicInfoVos.value = response.data.graphicInfoVos;
nodeListInfo.value = response.data.nodeListInfo;
deleteReason.value = response.data.deleteReason;
loading.value = false;
});
};
//悬浮事件
const handleMouseOver = async (graphic: any) => {
graphicX.value = graphic.x + graphic.width + 10;
graphicY.value = graphic.y - graphic.height + -10;
nodeInfo.value = {};
if (nodeListInfo.value && nodeListInfo.value.length > 0) {
let info = nodeListInfo.value.find((e: any) => e.taskDefinitionKey == graphic.nodeId);
if (info) {
nodeInfo.value = {
nickName: info.nickName,
status: info.status,
startTime: info.startTime,
endTime: info.endTime,
runDuration: info.runDuration
};
popupVisible.value = true;
}
}
};
//关闭
const handleMouseLeave = async () => {
popupVisible.value = false;
};
/** 下载按钮操作 */
const handleDownload = (ossId: string) => {
proxy?.$download.oss(ossId);
};
/**
* 对外暴露子组件方法
*/
defineExpose({
init
});
</script>
<style scoped>
.triangle {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
border-radius: 6px;
}
.triangle::after {
content: ' ';
position: absolute;
top: 8em;
right: 215px;
border: 15px solid;
border-color: transparent #fff transparent transparent;
}
</style>

View File

@ -0,0 +1,362 @@
<template>
<el-dialog v-model="visible" draggable :title="title" :width="width" :height="height" append-to-body
:close-on-click-modal="false">
<div class="p-2" v-if="multiInstance === 'add'">
<el-row :gutter="20">
<!-- 部门树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-tree class="mt-2" ref="deptTreeRef" node-key="id" :data="deptOptions"
:props="{ label: 'label', children: 'children' }" :expand-on-click-node="false"
:filter-node-method="filterNode" highlight-current default-expand-all
@node-click="handleNodeClick"></el-tree>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter"
:leave-active-class="proxy?.animate.searchAnimate.leave">
<div class="search" v-show="showSearch">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable style="width: 240px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery" icon="Search">搜索</el-button>
<el-button @click="resetQuery" icon="Refresh">重置</el-button>
</el-form-item>
</el-form>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<right-toolbar v-model:showSearch="showSearch" @queryTable="handleQuery" :search="true"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="userList" ref="multipleTableRef" row-key="userId"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="用户编号" align="center" key="userId" prop="userId" />
<el-table-column label="用户名称" align="center" key="userName" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" width="120" />
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
<template #default="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="handleQuery" />
</el-card>
<el-card shadow="hover">
<el-tag v-for="(user, index) in chooseUserList" :key="user.userId" style="margin:2px" closable
@close="handleCloseTag(user, index)">{{ user.userName }}
</el-tag>
</el-card>
</el-col>
</el-row>
</div>
<div class="p-2" v-if="multiInstance === 'delete'">
<el-table v-loading="loading" :data="taskList" @selection-change="handleTaskSelection">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="任务名称" />
<el-table-column prop="assigneeName" label="办理人" />
</el-table>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="visible = false"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup name="User" lang="ts">
import { deptTreeSelect } from '@/api/system/user';
import { getWorkflowAddMultiListByPage, getWorkflowDeleteMultiInstanceList, getUserListByIds } from '@/api/workflow/workflowUser';
import { addMultiInstanceExecution, deleteMultiInstanceExecution } from '@/api/workflow/task';
import { UserVO } from '@/api/system/user/types';
import { DeptVO } from '@/api/system/dept/types';
import { ComponentInternalInstance } from 'vue';
import { ElTree, ElTable } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = defineProps({
// 宽
width: {
type: String,
default: '70%'
},
// 高
height: {
type: String,
default: '100%'
},
// 标题
title: {
type: String,
default: '加签人员'
},
//是否多选
multiple: {
type: Boolean,
default: true
},
//回显用户id
userIdList: {
type: Array,
default: []
}
});
const deptTreeRef = ref(ElTree);
const multipleTableRef = ref(ElTable);
const userList = ref<UserVO[]>();
const taskList = ref<Array<any>[]>();
const loading = ref(true);
const showSearch = ref(true);
const selectionTask = ref<Array<any>[]>();
const visible = ref(false);
const total = ref(0);
const deptName = ref('');
const deptOptions = ref<DeptVO[]>([]);
const chooseUserList = ref(ref<UserVO[]>());
const userIds = ref<Array<number | string>>([]);
//加签或者减签
const multiInstance = ref('');
const queryParams = ref<Record<string, any>>({
pageNum: 1,
pageSize: 10,
userName: '',
nickName: '',
taskId: ''
});
/** 查询用户列表 */
const getAddMultiInstanceList = async (taskId: string, userIdList: Array<number | string>) => {
deptOptions.value = [];
getTreeSelect();
multiInstance.value = 'add';
userIds.value = userIdList;
visible.value = true;
queryParams.value.taskId = taskId;
loading.value = true;
const res = await getWorkflowAddMultiListByPage(queryParams.value);
loading.value = false;
userList.value = res.rows;
total.value = res.total;
if (userList.value && userIds.value.length > 0) {
const data = await getUserListByIds(userIds.value);
if (data.data && data.data.length > 0) {
chooseUserList.value = data.data;
data.data.forEach((user: UserVO) => {
multipleTableRef.value!.toggleRowSelection(
userList.value.find((item) => {
return item.userId == user.userId;
}),
true
);
});
}
}
};
const getList = async () => {
loading.value = true;
const res = await getWorkflowAddMultiListByPage(queryParams.value);
loading.value = false;
userList.value = res.rows;
total.value = res.total;
if (userList.value && userIds.value.length > 0) {
const data = await getUserListByIds(userIds.value);
if (data.data && data.data.length > 0) {
chooseUserList.value = data.data;
data.data.forEach((user: UserVO) => {
multipleTableRef.value!.toggleRowSelection(
userList.value.find((item) => {
return item.userId == user.userId;
}),
true
);
});
}
}
};
const getDeleteMultiInstanceList = async (taskId: string) => {
deptOptions.value = [];
loading.value = true;
queryParams.value.taskId = taskId;
multiInstance.value = 'delete';
visible.value = true;
const res = await getWorkflowDeleteMultiInstanceList(taskId);
taskList.value = res.data;
loading.value = false;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getAddMultiInstanceList(queryParams.value.taskId, userIds.value);
};
/** 重置按钮操作 */
const resetQuery = () => {
queryParams.value.pageNum = 1;
queryParams.value.deptId = undefined;
queryParams.value.userName = undefined;
queryParams.value.nickName = undefined;
deptTreeRef.value.setCurrentKey(null);
handleQuery();
};
/** 选择条数 */
const handleSelectionChange = (selection: UserVO[]) => {
if (props.multiple) {
chooseUserList.value = selection.filter((element, index, self) => {
return self.findIndex((x) => x.userId === element.userId) === index;
});
selection.forEach((u) => {
if (chooseUserList.value && !chooseUserList.value.includes(u)) {
multipleTableRef.value!.toggleRowSelection(u, undefined);
}
});
userIds.value = chooseUserList.value.map((item) => {
return item.userId;
});
} else {
chooseUserList.value = selection;
if (selection.length > 1) {
let delRow = selection.shift();
multipleTableRef.value!.toggleRowSelection(delRow, undefined);
}
if (selection.length === 0) {
chooseUserList.value = [];
}
}
};
/** 选择条数 */
const handleTaskSelection = (selection: any) => {
selectionTask.value = selection;
};
/** 查询部门下拉树结构 */
const getTreeSelect = async () => {
const res = await deptTreeSelect();
deptOptions.value = res.data;
};
/** 通过条件过滤节点 */
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.label.indexOf(value) !== -1;
};
/** 根据名称筛选部门树 */
watchEffect(
() => {
if (visible.value && deptOptions.value && deptOptions.value.length > 0) {
deptTreeRef.value.filter(deptName.value);
}
},
{
flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发此属性控制在DOM元素更新后运行
}
);
/** 节点单击事件 */
const handleNodeClick = (data: DeptVO) => {
queryParams.value.deptId = data.id;
getList();
};
//删除tag
const handleCloseTag = (user: UserVO, index: any) => {
if (multipleTableRef.value.selection && multipleTableRef.value.selection.length > 0) {
multipleTableRef.value.selection.forEach((u: UserVO, i: Number) => {
if (user.userId === u.userId) {
multipleTableRef.value.selection.splice(i, 1);
}
});
}
if (chooseUserList.value && chooseUserList.value.length > 0) {
chooseUserList.value.splice(index, 1);
}
multipleTableRef.value.toggleRowSelection(user, undefined);
if (userIds.value && userIds.value.length > 0) {
userIds.value.forEach((userId, i) => {
if (userId === user.userId) {
userIds.value.splice(i, 1);
}
});
}
};
const submitFileForm = async () => {
if (multiInstance.value === 'add') {
if (chooseUserList.value && chooseUserList.value.length > 0) {
loading.value = true;
let userIds = chooseUserList.value.map((item) => {
return item.userId;
});
let nickNames = chooseUserList.value.map((item) => {
return item.nickName;
});
let params = {
taskId: queryParams.value.taskId,
assignees: userIds,
assigneeNames: nickNames
};
await addMultiInstanceExecution(params);
emits('submitCallback');
loading.value = false;
proxy?.$modal.msgSuccess('操作成功');
visible.value = false;
}
} else {
if (selectionTask.value && selectionTask.value.length > 0) {
loading.value = true;
let taskIds = selectionTask.value.map((item: any) => {
return item.id;
});
let executionIds = selectionTask.value.map((item: any) => {
return item.executionId;
});
let assigneeIds = selectionTask.value.map((item: any) => {
return item.assignee;
});
let assigneeNames = selectionTask.value.map((item: any) => {
return item.assigneeName;
});
let params = {
taskId: queryParams.value.taskId,
taskIds: taskIds,
executionIds: executionIds,
assigneeIds: assigneeIds,
assigneeNames: assigneeNames
};
await deleteMultiInstanceExecution(params);
emits('submitCallback');
loading.value = false;
proxy?.$modal.msgSuccess('操作成功');
visible.value = false;
}
}
};
//事件
const emits = defineEmits(['submitCallback']);
/**
* 对外暴露子组件方法
*/
defineExpose({
getAddMultiInstanceList,
getDeleteMultiInstanceList
});
</script>

View File

@ -0,0 +1,165 @@
<template>
<el-dialog v-model="dialog.visible" :title="dialog.title" width="50%" draggable :before-close="cancel" :close-on-click-modal="false">
<el-form v-loading="loading" :model="form" label-width="120px">
<el-form-item label="消息提醒">
<el-checkbox-group v-model="form.messageType">
<el-checkbox label="1" name="type" disabled>站内信</el-checkbox>
<el-checkbox label="2" name="type">邮件</el-checkbox>
<el-checkbox label="3" name="type">短信</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="附件">
<fileUpload v-model="form.fileId" :fileType="['doc', 'xls', 'ppt', 'txt', 'pdf', 'xlsx', 'docx', 'zip']" :fileSize="'20'"/>
</el-form-item>
<el-form-item label="抄送">
<el-button type="primary" @click="openUserSelectCopy" icon="Plus" circle />
<el-tag v-for="user in selectCopyUserList" :key="user.userId" closable style="margin: 2px" @close="handleCopyCloseTag(user)">
{{ user.userName }}
</el-tag>
</el-form-item>
<el-form-item label="审批意见" v-if="businessStatus === 'waiting'">
<el-input v-model="form.message" type="textarea" resize="none" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button v-loading="buttonLoading" @click="cancel">取消</el-button>
<el-button v-loading="buttonLoading" type="primary" @click="handleCompleteTask"> 提交 </el-button>
<el-button v-if="businessStatus === 'waiting'" v-loading="buttonLoading" type="danger" @click="handleBackProcess"> 退回 </el-button>
</span>
</template>
<UserSelect ref="userSelectCopyRef" :data="selectCopyUserIds" @confirm-call-back="userSelectCopyCallBack"></UserSelect>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { ComponentInternalInstance } from 'vue';
import { ElForm } from 'element-plus';
import { completeTask, backProcess, getBusinessStatus } from '@/api/workflow/task';
import UserSelect from '@/components/UserSelect';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { UserVO } from '@/api/system/user/types';
const userSelectCopyRef = ref<InstanceType<typeof UserSelect>>();
const props = defineProps({
taskVariables: {
type: Object as () => Record<string, any>,
default: {}
}
});
//遮罩层
const loading = ref(true);
//按钮
const buttonLoading = ref(true);
//流程状态
const businessStatus = ref<string>('');
//任务id
const taskId = ref<string>('');
//抄送人
const selectCopyUserList = ref<UserVO[]>([]);
//抄送人id
const selectCopyUserIds = ref<string>('');
const dialog = reactive<DialogOption>({
visible: false,
title: '提示'
});
const form = ref<Record<string, any>>({
taskId: undefined,
message: undefined,
variables: {},
messageType: ['1'],
wfCopyList: []
});
//打开弹窗
const openDialog = (id?: string) => {
selectCopyUserIds.value = ''
selectCopyUserList.value = []
form.value.fileId = undefined
taskId.value = id;
form.value.message = undefined;
dialog.visible = true;
loading.value = true;
buttonLoading.value = true;
nextTick(() => {
getBusinessStatus(taskId.value).then((response) => {
businessStatus.value = response.data;
loading.value = false;
buttonLoading.value = false;
});
});
};
onMounted(() => {});
const emits = defineEmits(['submitCallback', 'cancelCallback']);
/** 办理流程 */
const handleCompleteTask = async () => {
form.value.taskId = taskId.value;
form.value.taskVariables = props.taskVariables;
if(selectCopyUserList && selectCopyUserList.value.length > 0){
let wfCopyList = []
selectCopyUserList.value.forEach( e=> {
let copyUser = {
userId: e.userId,
userName: e.nickName
}
wfCopyList.push(copyUser)
})
form.value.wfCopyList = wfCopyList
}
await proxy?.$modal.confirm('是否确认提交?');
loading.value = true;
buttonLoading.value = true;
await completeTask(form.value).finally(() => (loading.value = false));
dialog.visible = false;
emits('submitCallback');
proxy?.$modal.msgSuccess('操作成功');
};
/** 驳回流程 */
const handleBackProcess = async () => {
form.value.taskId = taskId.value;
await proxy?.$modal.confirm('是否确认驳回到申请人?');
loading.value = true;
buttonLoading.value = true;
await backProcess(form.value).finally(() => (loading.value = false));
dialog.visible = false;
emits('submitCallback');
proxy?.$modal.msgSuccess('操作成功');
};
//取消
const cancel = async () => {
dialog.visible = false;
buttonLoading.value = false;
emits('cancelCallback');
};
//打开抄送人员
const openUserSelectCopy = () => {
userSelectCopyRef.value.open();
};
//确认抄送人员
const userSelectCopyCallBack = (data: UserVO[]) => {
if(data && data.length > 0){
selectCopyUserList.value = data
selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(',');
}
}
//删除抄送人员
const handleCopyCloseTag = (user: UserVO) => {
const userId = user.userId;
// 使用split删除用户
const index = selectCopyUserList.value.findIndex((item) => item.userId === userId);
selectCopyUserList.value.splice(index, 1);
selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(',');
};
/**
* 对外暴露子组件方法
*/
defineExpose({
openDialog
});
</script>

View File

@ -0,0 +1,301 @@
<template>
<el-dialog v-model="visible" draggable :title="title" :width="width" :height="height" append-to-body :close-on-click-modal="false">
<div class="p-2">
<el-row :gutter="20">
<!-- 部门树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-tree
ref="deptTreeRef"
class="mt-2"
node-key="id"
:data="deptOptions"
:props="{ label: 'label', children: 'children' }"
:expand-on-click-node="false"
:filter-node-method="filterNode"
highlight-current
default-expand-all
@node-click="handleNodeClick"
></el-tree>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="search">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="queryParams.nickName" placeholder="请输入用户昵称" clearable style="width: 240px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<right-toolbar v-model:showSearch="showSearch" :search="true" @query-table="getUserList"></right-toolbar>
</el-row>
</template>
<el-table ref="multipleTableRef" v-loading="loading" :data="userList" row-key="userId" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" :reserve-selection="true" />
<el-table-column key="userId" label="用户编号" align="center" prop="userId" />
<el-table-column key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" />
<el-table-column key="nickName" label="用户昵称" align="center" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" />
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
<template #default="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getUserList"
/>
</el-card>
<el-card shadow="hover">
<el-tag v-for="(user, index) in chooseUserList" :key="user.userId" style="margin: 2px" closable @close="handleCloseTag(user, index)"
>{{ user.userName }}
</el-tag>
</el-card>
</el-col>
</el-row>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="visible = false"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup name="User" lang="ts">
import { deptTreeSelect } from '@/api/system/user';
import { getUserListByPage, getUserListByIds } from '@/api/workflow/workflowUser';
import { UserVO } from '@/api/system/user/types';
import { DeptVO } from '@/api/system/dept/types';
import { ComponentInternalInstance } from 'vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = defineProps({
// 宽
width: {
type: String,
default: '70%'
},
// 高
height: {
type: String,
default: '100%'
},
// 标题
title: {
type: String,
default: '用户'
},
//是否多选
multiple: {
type: Boolean,
default: true
},
//回显用户id
userIdList: {
type: Array,
default: () => []
}
});
const deptTreeRef = ref<ElTreeInstance>();
const multipleTableRef = ref<ElTableInstance>();
const userList = ref<UserVO[]>();
const loading = ref(true);
const showSearch = ref(true);
const visible = ref(false);
const total = ref(0);
const deptName = ref('');
const deptOptions = ref<DeptVO[]>([]);
const chooseUserList = ref(ref<UserVO[]>());
const userIds = ref<Array<number | string>>([]);
//查询参数
const queryParams = ref<Record<string, any>>({
pageNum: 1,
pageSize: 10,
userName: undefined,
nickName: undefined,
deptId: undefined
});
/** 查询用户列表 */
const getUserList = async (userIdList: Array<number | string>) => {
deptOptions.value = [];
getTreeSelect();
userIds.value = userIdList;
visible.value = true;
loading.value = true;
const res = await getUserListByPage(queryParams.value);
loading.value = false;
userList.value = res.rows;
total.value = res.total;
if (userList.value && userIds.value.length > 0) {
const data = await getUserListByIds(userIds.value);
if (data.data && data.data.length > 0) {
chooseUserList.value = data.data;
data.data.forEach((user: UserVO) => {
multipleTableRef.value!.toggleRowSelection(
userList.value.find((item) => {
return item.userId == user.userId;
}),
true
);
});
}
}
};
const getList = async () => {
loading.value = true;
const res = await getUserListByPage(queryParams.value);
loading.value = false;
userList.value = res.rows;
total.value = res.total;
if (userList.value && userIds.value.length > 0) {
const data = await getUserListByIds(userIds.value);
if (data.data && data.data.length > 0) {
chooseUserList.value = data.data;
data.data.forEach((user: UserVO) => {
multipleTableRef.value!.toggleRowSelection(
userList.value.find((item) => {
return item.userId == user.userId;
}),
true
);
});
}
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getUserList(userIds.value);
};
/** 重置按钮操作 */
const resetQuery = () => {
queryParams.value.pageNum = 1;
queryParams.value.deptId = undefined;
queryParams.value.userName = undefined;
queryParams.value.nickName = undefined;
deptTreeRef.value.setCurrentKey(null);
handleQuery();
};
/** 选择条数 */
const handleSelectionChange = (selection: UserVO[]) => {
console.log(selection);
if (props.multiple) {
chooseUserList.value = selection.filter((element, index, self) => {
return self.findIndex((x) => x.userId === element.userId) === index;
});
selection.forEach((u) => {
if (chooseUserList.value && !chooseUserList.value.includes(u)) {
multipleTableRef.value!.toggleRowSelection(u, undefined);
}
});
userIds.value = chooseUserList.value.map((item) => {
return item.userId;
});
} else {
chooseUserList.value = selection;
if (selection.length > 1) {
let delRow = selection.shift();
multipleTableRef.value!.toggleRowSelection(delRow, undefined);
}
if (selection.length === 0) {
chooseUserList.value = [];
}
}
};
/** 查询部门下拉树结构 */
const getTreeSelect = async () => {
const res = await deptTreeSelect();
deptOptions.value = res.data;
};
/** 通过条件过滤节点 */
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.label.indexOf(value) !== -1;
};
/** 根据名称筛选部门树 */
watchEffect(
() => {
if (visible.value && deptOptions.value && deptOptions.value.length > 0) {
deptTreeRef.value.filter(deptName.value);
}
},
{
flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发此属性控制在DOM元素更新后运行
}
);
/** 节点单击事件 */
const handleNodeClick = (data: DeptVO) => {
queryParams.value.deptId = data.id;
getList();
};
//删除tag
const handleCloseTag = (user: UserVO, index: any) => {
if (multipleTableRef.value.selection && multipleTableRef.value.selection.length > 0) {
multipleTableRef.value.selection.forEach((u: UserVO, i: number) => {
if (user.userId === u.userId) {
multipleTableRef.value.selection.splice(i, 1);
}
});
}
if (chooseUserList.value && chooseUserList.value.length > 0) {
chooseUserList.value.splice(index, 1);
}
multipleTableRef.value.toggleRowSelection(user, undefined);
if (userIds.value && userIds.value.length > 0) {
userIds.value.forEach((userId, i) => {
if (userId === user.userId) {
userIds.value.splice(i, 1);
}
});
}
};
const submitFileForm = async () => {
loading.value = true;
emits('submitCallback', chooseUserList);
};
const close = async () => {
visible.value = false;
loading.value = false;
emits('close');
};
//事件
const emits = defineEmits(['submitCallback', 'close']);
/**
* 对外暴露子组件方法
*/
defineExpose({
getUserList,
close
});
</script>