644 lines
18 KiB
Vue
644 lines
18 KiB
Vue
<template>
|
||
<div>
|
||
<el-row>
|
||
<h4><b>审批人设置</b></h4>
|
||
<el-radio-group v-model="dataType" @change="changeDataType">
|
||
<el-radio label="USERS">指定用户</el-radio>
|
||
<el-radio label="ROLES">角色</el-radio>
|
||
<el-radio label="DEPTS">部门</el-radio>
|
||
<el-radio label="INITIATOR">发起人</el-radio>
|
||
</el-radio-group>
|
||
</el-row>
|
||
<el-row>
|
||
<div v-if="dataType === 'USERS'">
|
||
<el-tag
|
||
v-for="userText in selectedUser.text"
|
||
:key="userText"
|
||
effect="plain"
|
||
>
|
||
{{ userText }}
|
||
</el-tag>
|
||
<div class="element-drawer__button">
|
||
<el-button
|
||
size="small"
|
||
type="primary"
|
||
icon="plus"
|
||
@click="onSelectUsers()"
|
||
>添加用户</el-button
|
||
>
|
||
</div>
|
||
</div>
|
||
<div v-if="dataType === 'ROLES'">
|
||
<el-select
|
||
v-model="roleIds"
|
||
multiple
|
||
size="small"
|
||
placeholder="请选择 角色"
|
||
@change="changeSelectRoles"
|
||
>
|
||
<el-option
|
||
v-for="item in roleOptions"
|
||
:key="item.roleId"
|
||
:label="item.roleName"
|
||
:value="`ROLE${item.roleId}`"
|
||
:disabled="item.status === 1"
|
||
>
|
||
</el-option>
|
||
</el-select>
|
||
</div>
|
||
<div v-if="dataType === 'DEPTS'">
|
||
<!-- :width="320"
|
||
:height="400"
|
||
size="small"
|
||
:defaultProps="deptProps"
|
||
multiple
|
||
clearable
|
||
nodeKey="id"
|
||
:checkedKeys="deptIds"
|
||
checkStrictly -->
|
||
<!-- <tree-select :options="deptTreeData" @update:value="checkedDeptChange">
|
||
</tree-select> -->
|
||
<el-cascader
|
||
:options="deptTreeData"
|
||
:modelValue="deptCascaderData"
|
||
clearable
|
||
@change="checkedDeptChange"
|
||
:props="{
|
||
checkStrictly: true,
|
||
value: 'id',
|
||
multiple: true,
|
||
}"
|
||
></el-cascader>
|
||
</div>
|
||
</el-row>
|
||
<el-row>
|
||
<div v-show="showMultiFlog">
|
||
<el-divider />
|
||
<h4><b>多实例审批方式</b></h4>
|
||
<el-row>
|
||
<el-radio-group v-model="multiLoopType" @change="changeMultiLoopType">
|
||
<el-row
|
||
:style="{
|
||
width: '100%',
|
||
}"
|
||
>
|
||
<el-radio label="Null">无</el-radio>
|
||
</el-row>
|
||
<el-row
|
||
:style="{
|
||
width: '100%',
|
||
}"
|
||
><el-radio label="SequentialMultiInstance"
|
||
>会签(需所有审批人同意)</el-radio
|
||
></el-row
|
||
>
|
||
<el-row
|
||
:style="{
|
||
width: '100%',
|
||
}"
|
||
><el-radio label="ParallelMultiInstance"
|
||
>或签(一名审批人同意即可)</el-radio
|
||
></el-row
|
||
>
|
||
</el-radio-group>
|
||
</el-row>
|
||
<el-row v-if="multiLoopType !== 'Null'">
|
||
<el-tooltip
|
||
content="开启后,实例需按顺序轮流审批"
|
||
placement="top-start"
|
||
@click.stop.prevent
|
||
>
|
||
<i class="header-icon el-icon-info"></i>
|
||
</el-tooltip>
|
||
<span class="custom-label">顺序审批:</span>
|
||
<el-switch v-model="isSequential" @change="changeMultiLoopType" />
|
||
</el-row>
|
||
</div>
|
||
</el-row>
|
||
|
||
<!-- 候选用户弹窗 -->
|
||
<el-dialog title="候选用户" v-model="userOpen" width="60%" append-to-body>
|
||
<el-row type="flex" :gutter="20">
|
||
<!--部门数据-->
|
||
<el-col :span="7">
|
||
<el-card shadow="never" style="height: 100%">
|
||
<div slot="header">
|
||
<span>部门列表</span>
|
||
</div>
|
||
<div class="head-container">
|
||
<el-input
|
||
v-model="deptName"
|
||
placeholder="请输入部门名称"
|
||
clearable
|
||
size="small"
|
||
prefix-icon="search"
|
||
style="margin-bottom: 20px"
|
||
/>
|
||
<el-tree
|
||
:data="deptOptions"
|
||
:props="deptProps"
|
||
:expand-on-click-node="false"
|
||
:filter-node-method="filterNode"
|
||
ref="tree"
|
||
default-expand-all
|
||
@node-click="handleNodeClick"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="17">
|
||
<el-table
|
||
ref="multipleTable"
|
||
height="600"
|
||
:data="userTableList"
|
||
border
|
||
@selection-change="handleSelectionChange"
|
||
>
|
||
<el-table-column type="selection" width="50" align="center" />
|
||
<el-table-column label="用户名" align="center" prop="nickName" />
|
||
<el-table-column label="部门" align="center" prop="dept.deptName" />
|
||
</el-table>
|
||
<pagination
|
||
:total="userTotal"
|
||
v-model:page="queryParams.pageNum"
|
||
v-model:limit="queryParams.pageSize"
|
||
@pagination="getUserList"
|
||
/>
|
||
</el-col>
|
||
</el-row>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button type="primary" @click="handleTaskUserComplete"
|
||
>确 定</el-button
|
||
>
|
||
<el-button @click="userOpen = false">取 消</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { listUser, deptTreeSelect } from "@/api/system/user";
|
||
import { listRole } from "@/api/system/role";
|
||
import { addDateRange } from "@/utils/ruoyi";
|
||
import TreeSelect from "@/components/TreeSelect";
|
||
import { ref, reactive, watch } from "vue";
|
||
import { ElMessage } from "element-plus";
|
||
const testResult = ref([]);
|
||
const userTaskForm = {
|
||
dataType: "",
|
||
assignee: "",
|
||
candidateUsers: "",
|
||
candidateGroups: "",
|
||
text: "",
|
||
};
|
||
const props = defineProps({
|
||
id: String,
|
||
type: String,
|
||
});
|
||
const { id, type } = toRefs(props);
|
||
const loading = ref(false);
|
||
const dataType = ref("USERS");
|
||
const userOpen = ref(false);
|
||
const deptName = ref(undefined);
|
||
const deptOptions = ref([]);
|
||
const deptTempOptions = ref([]);
|
||
const userTableList = ref([]);
|
||
const userTotal = ref(0);
|
||
const selectedUserDate = ref([]);
|
||
const roleOptions = ref([]);
|
||
const roleIds = ref([]);
|
||
const deptTreeData = ref([]);
|
||
const deptIds = ref([]);
|
||
const showMultiFlog = ref(false);
|
||
const isSequential = ref(false);
|
||
const multiLoopType = ref("Null");
|
||
const deptCascaderData = ref([]); // 部门级联 modelValue
|
||
const data = reactive({
|
||
selectedUser: {
|
||
ids: [],
|
||
text: [],
|
||
},
|
||
deptProps: {
|
||
children: "children",
|
||
label: "label",
|
||
},
|
||
// 查询参数
|
||
queryParams: {
|
||
deptId: undefined,
|
||
},
|
||
});
|
||
const { selectedUser, deptProps, queryParams } = toRefs(data);
|
||
let bpmnElement;
|
||
let multiLoopInstance;
|
||
let dateRange;
|
||
|
||
watch(
|
||
id,
|
||
() => {
|
||
bpmnElement = window.bpmnInstances.bpmnElement;
|
||
nextTick(() => resetTaskForm());
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
|
||
function resetTaskForm() {
|
||
const bpmnElementObj = bpmnElement?.businessObject;
|
||
if (!bpmnElementObj) {
|
||
return;
|
||
}
|
||
clearOptionsData();
|
||
dataType.value = bpmnElementObj["dataType"];
|
||
if (dataType.value === "USERS") {
|
||
let userIdData =
|
||
bpmnElementObj["candidateUsers"] || bpmnElementObj["assignee"];
|
||
let userText = bpmnElementObj["text"] || [];
|
||
if (
|
||
userIdData &&
|
||
userIdData.toString().length > 0 &&
|
||
userText &&
|
||
userText.length > 0
|
||
) {
|
||
selectedUser.value.ids = userIdData?.toString().split(",");
|
||
selectedUser.value.text = userText?.split(",");
|
||
}
|
||
if (selectedUser.value.ids.length > 1) {
|
||
showMultiFlog.value = true;
|
||
}
|
||
} else if (dataType.value === "ROLES") {
|
||
getRoleOptions();
|
||
let roleIdData = bpmnElementObj["candidateGroups"] || [];
|
||
if (roleIdData && roleIdData.length > 0) {
|
||
roleIds.value = roleIdData.split(",");
|
||
}
|
||
showMultiFlog.value = true;
|
||
} else if (dataType.value === "DEPTS") {
|
||
getDeptTreeData().then(() => {
|
||
let deptIdData = bpmnElementObj["candidateGroups"] || [];
|
||
if (deptIdData && deptIdData.length > 0) {
|
||
deptIds.value = deptIdData.split(",");
|
||
deptCascaderData.value = deptIds.value.map((id) => [
|
||
...getParentId(deptTreeData.value, id).reverse(),
|
||
]);
|
||
}
|
||
});
|
||
showMultiFlog.value = true;
|
||
}
|
||
getElementLoop(bpmnElementObj);
|
||
}
|
||
/**
|
||
* 清空选项数据
|
||
*/
|
||
function clearOptionsData() {
|
||
selectedUser.value.ids = [];
|
||
selectedUser.value.text = [];
|
||
roleIds.value = [];
|
||
deptIds.value = [];
|
||
}
|
||
/**
|
||
* 跟新节点数据
|
||
*/
|
||
function updateElementTask() {
|
||
const taskAttr = Object.create(null);
|
||
for (let key in userTaskForm) {
|
||
taskAttr[key] = userTaskForm[key];
|
||
}
|
||
window.bpmnInstances.modeling.updateProperties(bpmnElement, taskAttr);
|
||
}
|
||
/**
|
||
* 查询部门下拉树结构
|
||
*/
|
||
function getDeptOptions() {
|
||
return new Promise((resolve, reject) => {
|
||
if (!deptOptions.value || deptOptions.value.length <= 0) {
|
||
deptTreeSelect().then((response) => {
|
||
deptTempOptions.value = response.data;
|
||
deptOptions.value = response.data;
|
||
resolve();
|
||
});
|
||
} else {
|
||
reject();
|
||
}
|
||
});
|
||
}
|
||
/**
|
||
* 查询部门下拉树结构(含部门前缀)
|
||
*/
|
||
function getDeptTreeData() {
|
||
function refactorTree(data) {
|
||
return data.map((node) => {
|
||
let treeData = {
|
||
id: `DEPT${node.id}`,
|
||
label: node.label,
|
||
parentId: node.parentId,
|
||
weight: node.weight,
|
||
};
|
||
if (node.children && node.children.length > 0) {
|
||
treeData.children = refactorTree(node.children);
|
||
}
|
||
return treeData;
|
||
});
|
||
}
|
||
return new Promise((resolve, reject) => {
|
||
if (!deptTreeData.value || deptTreeData.value.length <= 0) {
|
||
getDeptOptions()
|
||
.then(() => {
|
||
deptTreeData.value = refactorTree(deptOptions.value);
|
||
resolve();
|
||
})
|
||
.catch(() => {
|
||
reject();
|
||
});
|
||
} else {
|
||
resolve();
|
||
}
|
||
});
|
||
}
|
||
/**
|
||
* 查询部门下拉树结构
|
||
*/
|
||
function getRoleOptions() {
|
||
if (!roleOptions.value || roleOptions.value.length <= 0) {
|
||
listRole().then((response) => (roleOptions.value = response.rows));
|
||
}
|
||
}
|
||
/** 查询用户列表 */
|
||
function getUserList() {
|
||
listUser(addDateRange(queryParams.value, dateRange)).then((response) => {
|
||
userTableList.value = response.rows;
|
||
userTotal.value = response.total;
|
||
});
|
||
}
|
||
// 筛选节点
|
||
function filterNode(value, data) {
|
||
if (!value) return true;
|
||
return data.label.indexOf(value) !== -1;
|
||
}
|
||
// 节点单击事件
|
||
function handleNodeClick(data) {
|
||
queryParams.value.deptId = data.id;
|
||
getUserList();
|
||
}
|
||
// 关闭标签
|
||
const multipleTable = ref();
|
||
function handleClose(tag) {
|
||
selectedUserDate.value.splice(selectedUserDate.value.indexOf(tag), 1);
|
||
multipleTable.value.toggleRowSelection(tag);
|
||
}
|
||
// 多选框选中数据
|
||
function handleSelectionChange(selection) {
|
||
selectedUserDate.value = selection;
|
||
}
|
||
function onSelectUsers() {
|
||
selectedUserDate.value = [];
|
||
multipleTable.value?.clearSelection();
|
||
getDeptOptions();
|
||
userOpen.value = true;
|
||
}
|
||
function handleTaskUserComplete() {
|
||
if (!selectedUserDate.value || selectedUserDate.value.length <= 0) {
|
||
ElMessage.error("请选择用户");
|
||
return;
|
||
}
|
||
userTaskForm.dataType = "USERS";
|
||
selectedUser.value.text = selectedUserDate.value.map((k) => k.nickName) || [];
|
||
if (selectedUserDate.value.length === 1) {
|
||
let data = selectedUserDate.value[0];
|
||
userTaskForm.assignee = data.userId;
|
||
userTaskForm.text = data.nickName;
|
||
userTaskForm.candidateUsers = null;
|
||
showMultiFlog.value = false;
|
||
multiLoopType.value = "Null";
|
||
changeMultiLoopType(multiLoopType.value);
|
||
} else {
|
||
userTaskForm.candidateUsers =
|
||
selectedUserDate.value.map((k) => k.userId).join() || null;
|
||
userTaskForm.text =
|
||
selectedUserDate.value.map((k) => k.nickName).join() || null;
|
||
userTaskForm.assignee = null;
|
||
showMultiFlog.value = true;
|
||
}
|
||
updateElementTask();
|
||
userOpen.value = false;
|
||
}
|
||
function changeSelectRoles(val) {
|
||
userTaskForm.dataType = "ROLES";
|
||
userTaskForm.candidateGroups = val.join() || null;
|
||
let textArr = roleOptions.value.filter(
|
||
(k) => val.indexOf(`ROLE${k.roleId}`) >= 0
|
||
);
|
||
userTaskForm.text = textArr?.map((k) => k.roleName).join() || null;
|
||
updateElementTask();
|
||
}
|
||
// function checkedDeptChange(checkedIds, checkedData) {
|
||
// userTaskForm.dataType = "DEPTS";
|
||
// if (checkedIds && checkedIds.length > 0) {
|
||
// deptIds.value = checkedIds;
|
||
// }
|
||
// if (checkedData && checkedData.length > 0) {
|
||
// userTaskForm.candidateGroups = checkedData.map((k) => k.id).join() || null;
|
||
// userTaskForm.text = checkedData.map((k) => k.label).join() || null;
|
||
// updateElementTask();
|
||
// }
|
||
// }
|
||
|
||
function findInDeptTree(id) {
|
||
let hasFound = false, // 表示是否有找到id值
|
||
result = null;
|
||
var fn = function (data) {
|
||
if (Array.isArray(data) && !hasFound) {
|
||
// 判断是否是数组并且没有的情况下,
|
||
data.forEach((item) => {
|
||
if (item.id === id) {
|
||
// 数据循环每个子项,并且判断子项下边是否有id值
|
||
result = item; // 返回的结果等于每一项
|
||
hasFound = true; // 并且找到id值
|
||
} else if (item.children) {
|
||
fn(item.children); // 递归调用下边的子项
|
||
}
|
||
});
|
||
}
|
||
};
|
||
fn(deptTreeData.value); // 调用一下
|
||
return result;
|
||
}
|
||
// 获取所有父级
|
||
function getParentId(list, id) {
|
||
for (let i in list) {
|
||
if (list[i].id == id) {
|
||
return [list[i].id];
|
||
}
|
||
if (list[i].children) {
|
||
let node = getParentId(list[i].children, id);
|
||
if (node !== undefined) {
|
||
return node.concat(list[i].id);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
function checkedDeptChange(checkedIds) {
|
||
const ids = checkedIds.map((el) => el[el.length - 1]);
|
||
const checkedData = ids.map((el) => findInDeptTree(el));
|
||
|
||
userTaskForm.dataType = "DEPTS";
|
||
if (ids && ids.length > 0) {
|
||
deptIds.value = ids;
|
||
}
|
||
if (checkedData && checkedData.length > 0) {
|
||
userTaskForm.candidateGroups = checkedData.map((k) => k.id).join() || null;
|
||
userTaskForm.text = checkedData.map((k) => k.label).join() || null;
|
||
|
||
updateElementTask();
|
||
}
|
||
}
|
||
function changeDataType(val) {
|
||
// 清空 userTaskForm 所有属性值
|
||
Object.keys(userTaskForm).forEach((key) => (userTaskForm[key] = null));
|
||
userTaskForm.dataType = val;
|
||
if (val === "USERS") {
|
||
if (
|
||
selectedUser.value &&
|
||
selectedUser.value.ids &&
|
||
selectedUser.value.ids.length > 0
|
||
) {
|
||
if (selectedUser.value.ids.length === 1) {
|
||
userTaskForm.assignee = selectedUser.value.ids[0];
|
||
} else {
|
||
userTaskForm.candidateUsers = selectedUser.value.ids.join();
|
||
}
|
||
userTaskForm.text = selectedUser.value.text?.join() || null;
|
||
}
|
||
} else if (val === "ROLES") {
|
||
getRoleOptions();
|
||
if (roleIds.value && roleIds.value.length > 0) {
|
||
userTaskForm.candidateGroups = roleIds.value.join() || null;
|
||
let textArr = roleOptions.value.filter(
|
||
(k) => roleIds.value.indexOf(`ROLE${k.roleId}`) >= 0
|
||
);
|
||
userTaskForm.text = textArr?.map((k) => k.roleName).join() || null;
|
||
}
|
||
} else if (val === "DEPTS") {
|
||
getDeptTreeData();
|
||
if (deptIds.value && deptIds.value.length > 0) {
|
||
userTaskForm.candidateGroups = deptIds.value.join() || null;
|
||
let textArr = [];
|
||
let treeStarkData = JSON.parse(JSON.stringify(deptTreeData.value));
|
||
deptIds.value.forEach((id) => {
|
||
let stark = [];
|
||
stark = stark.concat(treeStarkData);
|
||
while (stark.length) {
|
||
let temp = stark.shift();
|
||
if (temp.children) {
|
||
stark = temp.children.concat(stark);
|
||
}
|
||
if (id === temp.id) {
|
||
textArr.push(temp);
|
||
}
|
||
}
|
||
});
|
||
userTaskForm.text = textArr?.map((k) => k.label).join() || null;
|
||
}
|
||
} else if (val === "INITIATOR") {
|
||
userTaskForm.assignee = "${initiator}";
|
||
userTaskForm.text = "流程发起人";
|
||
}
|
||
updateElementTask();
|
||
if (
|
||
val === "ROLES" ||
|
||
val === "DEPTS" ||
|
||
(val === "USERS" && selectedUser.value.ids.length > 1)
|
||
) {
|
||
showMultiFlog.value = true;
|
||
} else {
|
||
showMultiFlog.value = false;
|
||
}
|
||
multiLoopType.value = "Null";
|
||
changeMultiLoopType(multiLoopType.value);
|
||
}
|
||
function getElementLoop(businessObject) {
|
||
if (!businessObject.loopCharacteristics) {
|
||
multiLoopType.value = "Null";
|
||
return;
|
||
}
|
||
isSequential.value = businessObject.loopCharacteristics.isSequential;
|
||
if (businessObject.loopCharacteristics.completionCondition) {
|
||
if (
|
||
businessObject.loopCharacteristics.completionCondition.body ===
|
||
"${nrOfCompletedInstances >= nrOfInstances}"
|
||
) {
|
||
multiLoopType.value = "SequentialMultiInstance";
|
||
} else {
|
||
multiLoopType.value = "ParallelMultiInstance";
|
||
}
|
||
}
|
||
}
|
||
function changeMultiLoopType(type) {
|
||
// 取消多实例配置
|
||
if (type === "Null") {
|
||
window.bpmnInstances.modeling.updateProperties(bpmnElement, {
|
||
loopCharacteristics: null,
|
||
});
|
||
return;
|
||
}
|
||
multiLoopInstance = window.bpmnInstances.moddle.create(
|
||
"bpmn:MultiInstanceLoopCharacteristics",
|
||
{ isSequential: isSequential.value }
|
||
);
|
||
// 更新多实例配置
|
||
window.bpmnInstances.modeling.updateProperties(bpmnElement, {
|
||
loopCharacteristics: multiLoopInstance,
|
||
assignee: "${assignee}",
|
||
});
|
||
// 完成条件
|
||
let completionCondition = null;
|
||
// 会签
|
||
if (type === "SequentialMultiInstance") {
|
||
completionCondition = window.bpmnInstances.moddle.create(
|
||
"bpmn:FormalExpression",
|
||
{ body: "${nrOfCompletedInstances >= nrOfInstances}" }
|
||
);
|
||
}
|
||
// 或签
|
||
if (type === "ParallelMultiInstance") {
|
||
completionCondition = window.bpmnInstances.moddle.create(
|
||
"bpmn:FormalExpression",
|
||
{ body: "${nrOfCompletedInstances > 0}" }
|
||
);
|
||
}
|
||
// 更新模块属性信息
|
||
window.bpmnInstances.modeling.updateModdleProperties(
|
||
bpmnElement,
|
||
multiLoopInstance,
|
||
{
|
||
collection: "${multiInstanceHandler.getUserIds(execution)}",
|
||
elementVariable: "assignee",
|
||
completionCondition,
|
||
}
|
||
);
|
||
}
|
||
onBeforeUnmount(() => {
|
||
bpmnElement = null;
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.el-row .el-radio-group {
|
||
margin-bottom: 15px;
|
||
.el-radio {
|
||
line-height: 28px;
|
||
}
|
||
}
|
||
.el-tag {
|
||
margin-bottom: 10px;
|
||
+ .el-tag {
|
||
margin-left: 10px;
|
||
}
|
||
}
|
||
|
||
.custom-label {
|
||
padding-left: 5px;
|
||
font-weight: 500;
|
||
font-size: 14px;
|
||
color: #606266;
|
||
}
|
||
</style>
|