663 lines
17 KiB
Vue
Raw Normal View History

<template>
<view class="w-process" v-if="!data.loading && data.processTasks.length > 0">
<view class="w-process-line"></view>
<view
class="w-process-render"
v-for="node in data.processTasks"
:key="node.id"
>
<process-node-render
:key="node.id"
:ref="node.id"
class="w-node-render"
:task="node"
@addOrg="addOrg"
@delOrg="delOrg"
/>
</view>
<org-picker
ref="orgPicker"
:type="data.selectedNode.type || 'user'"
:multiple="data.selectedNode.multiple || false"
:selected="data.selectedNode.users"
@ok="doAddOrg"
/>
</view>
</template>
<script setup>
import {
nextTick,
reactive,
computed,
onBeforeMount,
onMounted,
getCurrentInstance,
watch,
ref,
} from "vue";
import { $deepCopy, getRes } from "@/utils/tool.js";
import ProcessNodeRender from "./ProcessNodeRender.vue";
import { forEachNode } from "@/utils/ProcessUtil.js";
import processApi from "@/api/process";
import OrgPicker from "@/components/OrgPicker.vue";
import { getGroupModels } from "@/api/model";
import { debounce } from "@/utils/tool.js";
const instance = getCurrentInstance();
//小程序端不支持jsx真是艹了这块要重写😂
const props = defineProps({
processDefId: String,
process: {
type: Object,
default: () => {
return {};
},
},
formData: {
type: Object,
default: () => {
return {};
},
},
modelValue: {
type: Object,
default: () => {
return {};
},
},
deptId: {
type: String,
default: null,
},
});
const _value = computed({
get() {
return props.modelValue;
},
set(val) {
emits("update:modelValue", val);
},
});
defineExpose({
validate,
});
const emits = defineEmits(["update:modelValue", "render-ok"]);
const loginUser = JSON.parse(uni.getStorageSync("loginUser"));
loginUser.value = {
id: loginUser.userId,
name: loginUser.realName,
avatar: getRes(loginUser.avatar),
sn: loginUser.sn,
};
const orgPicker = ref();
const data = reactive({
selectUserNodes: new Set(),
loading: true,
selectedNode: {},
reverse: false,
userCatch: {},
oldFormData: {},
models: null,
processTasks: [],
conditionFormItem: new Set(),
branchNodeMap: new Map(),
loadingReqs: [],
calls: [],
});
onBeforeMount(() => processRenderDebounce());
//流程渲染去抖动
const processRenderDebounce = debounce(() => loadProcessRender(), 500);
async function loadProcessRender() {
console.log("渲染流程");
data.loading = true;
data.processTasks.length = 0;
data.selectUserNodes.clear();
data.loadingReqs.length = 0;
//TODO 从这里可以使用去抖动函数 this.$debounce
await loadProcess(props.process, data.processTasks);
if (data.loadingReqs.length > 0) {
Promise.all(data.loadingReqs)
.then(() => {
setTimeout(() => {
data.processTasks.push({
title: "结束",
name: "END",
icon: "checkbox-filled",
enableEdit: false,
});
data.loading = false;
console.log("渲染完成11233");
}, 3500);
emits("render-ok");
})
.catch(() => (data.loading = false));
} else {
emits("render-ok");
setTimeout(() => {
data.processTasks.push({
title: "结束",
name: "END",
icon: "checkbox-filled",
enableEdit: false,
});
data.loading = false;
console.log("渲染完成11233");
}, 3500);
}
// console.log('渲染完成', data.loading)
}
function loadProcess(processNode, processTasks, bnode, bid) {
forEachNode(processNode, (node) => {
if (bnode) {
//如果是分支内子节点
data.branchNodeMap.set(node.id, {
node: bnode,
id: bid,
});
}
switch (node.type) {
case "ROOT":
processTasks.push({
id: node.id,
title: node.name,
name: "发起人",
desc: `${loginUser.value.name} 将发起本流程`,
icon: "contact-filled",
enableEdit: false,
users: [loginUser.value],
});
break;
case "APPROVAL":
processTasks.push(getApprovalNode(node));
break;
case "TASK":
processTasks.push(getApprovalNode(node, false));
break;
case "SUBPROC":
processTasks.push(getSubProcNode(node));
break;
case "CC":
processTasks.push(getCcNode(node));
break;
case "CONDITIONS": //条件节点选一项
processTasks.push(getConditionNode(node, bnode, bid));
loadProcess(node.children, processTasks);
return true;
case "INCLUSIVES": //包容分支会执行所有符合条件的分支
processTasks.push(getInclusiveNode(node, bnode, bid));
loadProcess(node.children, processTasks);
return true;
case "CONCURRENTS": //并行分支无条件执行所有分支
processTasks.push(getConcurrentNode(node, bnode, bid));
loadProcess(node.children, processTasks);
return true;
}
});
}
function getSubProcNode(node) {
let user = {};
//提取发起人
switch (node.props.staterUser.type) {
case "ROOT":
user = loginUser.value;
break;
case "FORM":
const fd = props.formData[node.props.staterUser.value];
user =
Array.isArray(fd) && fd.length > 0
? fd[0]
: {
name: "请选人",
};
break;
case "SELECT":
user = node.props.staterUser.value || {};
break;
}
const procNode = {
id: node.id,
title: `${node.name} [由${user.id ? user.name : "?"}发起]`,
name: "子流程",
desc: "",
icon: "more-filled",
enableEdit: false,
users: [user],
};
getSubModel(() => {
procNode.desc = `调用子流程 [${data.models[node.props.subProcCode]}]`;
});
return procNode;
}
function getApprovalNode(node, isApproval = true) {
let result = {
id: node.id,
title: node.name,
name: isApproval ? "审批人" : "办理人",
icon: isApproval ? "person-filled" : "calendar-filled",
enableEdit: false,
multiple: false,
mode: node.props.mode,
users: [],
desc: "",
};
let loadCatch = true;
switch (node.props.assignedType) {
case "ASSIGN_USER":
result.users = $deepCopy(node.props.assignedUser);
result.desc = isApproval ? "指定审批人" : "指定办理人";
break;
case "ASSIGN_LEADER":
data.loadingReqs.push(
processApi
.getLeaderByDepts((node.props.assignedDept || []).map((d) => d.id))
.then((res) => {
result.users = res.data;
})
);
result.desc = "指定部门的领导";
break;
case "SELF":
result.users = [loginUser.value];
result.desc = `发起人自己${isApproval ? "审批" : "办理"}`;
break;
case "SELF_SELECT":
result.enableEdit = true;
data.selectUserNodes.add(node.id);
result.multiple = node.props.selfSelect.multiple || false;
result.desc = isApproval ? "自选审批人" : "自选办理人";
break;
case "LEADER_TOP":
result.desc = `连续多级主管${isApproval ? "审批" : "办理"}`;
const leaderTop = node.props.leaderTop;
data.loadingReqs.push(
processApi
.getUserLeaders(
"TOP" === leaderTop.endCondition ? 0 : leaderTop.endLevel,
props.deptId,
leaderTop.skipEmpty
)
.then((res) => {
result.users = res.data;
})
);
break;
case "LEADER":
result.desc =
node.props.leader.level === 1
? `直接主管${isApproval ? "审批" : "办理"}`
: `${node.props.leader.level}级主管${isApproval ? "审批" : "办理"}`;
data.loadingReqs.push(
processApi
.getUserLeader(
node.props.leader.level,
props.deptId,
node.props.leader.skipEmpty
)
.then((res) => {
result.users = res.data ? [res.data] : [];
})
);
break;
case "ROLE":
result.desc = `由角色[${(node.props.role || []).map((r) => r.name)}]${
isApproval ? "审批" : "办理"
}`;
data.loadingReqs.push(
processApi
.getUsersByRoles({
projectSn: loginUser.value.sn,
roleIds: (node.props.role || []).map((r) => r.id),
})
.then((res) => {
result.users = res.data;
})
);
break;
case "FORM_USER":
loadCatch = false;
result.desc = `由表单字段内人员${isApproval ? "审批" : "办理"}`;
data.conditionFormItem.add(node.props.formUser);
result.users = props.formData[node.props.formUser] || [];
break;
case "FORM_DEPT":
loadCatch = false;
result.desc = `由表单部门内主管${isApproval ? "审批" : "办理"}`;
data.conditionFormItem.add(node.props.formDept);
data.loadingReqs.push(
processApi
.getLeaderByDepts(
(props.formData[node.props.formDept] || []).map((d) => d.id)
)
.then((res) => {
result.users = res.data;
})
);
break;
case "REFUSE":
result.desc = `流程此处将被自动驳回`;
break;
}
if (data.userCatch[node.id] && data.userCatch[node.id].length > 0) {
result.users = data.userCatch[node.id];
}
if (loadCatch) {
data.userCatch[node.id] = result.users;
}
return result;
}
function getCcNode(node) {
let result = {
id: node.id,
title: node.name,
icon: "paperplane-filled",
name: "抄送人",
enableEdit: node.props.shouldAdd,
type: "org",
multiple: true,
desc: node.props.shouldAdd ? "可添加抄送人" : "流程将会抄送到他们",
users: $deepCopy(node.props.assignedUser),
};
if (data.userCatch[node.id] && data.userCatch[node.id].length > 0) {
result.users = data.userCatch[node.id];
}
data.userCatch[node.id] = result.users;
return result;
}
function getInclusiveNode(node, pbnode, pbid) {
let branchTasks = {
id: node.id,
title: node.name,
name: "包容分支",
icon: "tune-filled",
enableEdit: false,
active: node.branchs[0].id, //激活得分支
options: [], //分支选项,渲染单选框
desc: "满足条件的分支均会执行",
branchs: {}, //分支数据不包含分支节点key=分支子节点idvalue = [后续节点]
};
const req = processApi
.getTrueConditions({
processDfId: props.processDefId,
conditionNodeId: node.id,
multiple: true,
context: {
...props.formData,
deptId: props.deptId,
},
})
.then((rsp) => {
//拿到满足的条件
const cds = new Set(rsp.data || []);
for (let i = 0; i < node.branchs.length; i++) {
const cdNode = node.branchs[i];
cdNode.skip = !cds.has(cdNode.id);
if (!cdNode.skip) {
branchTasks.active = cdNode.id;
}
}
node.branchs.forEach((nd) => {
branchTasks.options.push({
id: nd.id,
title: nd.name,
skip: nd.skip,
});
branchTasks.branchs[nd.id] = [];
//设置下子级分支的父级分支节点
data.branchNodeMap.set(nd.id, {
node: pbnode,
id: pbid,
});
loadProcess(
nd.children,
branchTasks.branchs[nd.id],
branchTasks,
nd.id
);
});
})
.catch((err) => {
branchTasks.desc = `<span style="color:#CE5266;">条件解析异常,渲染失败😢<span>`;
//
});
data.loadingReqs.push(req);
return branchTasks;
}
function getConditionNode(node, pbnode, pbid) {
let branchTasks = {
id: node.id,
title: node.name,
name: "条件分支",
icon: "tune",
enableEdit: false,
active: node.branchs[0].id, //激活得分支
options: [], //分支选项,渲染单选框
desc: "只执行第一个满足条件的分支",
branchs: {}, //分支数据不包含分支节点key=分支子节点idvalue = [后续节点]
};
const req = processApi
.getTrueConditions({
processDfId: props.processDefId,
conditionNodeId: node.id,
multiple: false,
context: {
...props.formData,
deptId: props.deptId,
},
})
.then((rsp) => {
//拿到满足的条件
const cds = new Set(rsp.data || []);
for (let i = 0; i < node.branchs.length; i++) {
const cdNode = node.branchs[i];
cdNode.skip = !cds.has(cdNode.id);
if (!cdNode.skip) {
branchTasks.active = cdNode.id;
}
}
node.branchs.forEach((nd) => {
branchTasks.options.push({
id: nd.id,
title: nd.name,
skip: nd.skip,
});
branchTasks.branchs[nd.id] = [];
//设置下子级分支的父级分支节点
data.branchNodeMap.set(nd.id, {
node: pbnode,
id: pbid,
});
loadProcess(
nd.children,
branchTasks.branchs[nd.id],
branchTasks,
nd.id
);
});
})
.catch((err) => {
branchTasks.desc = `<span style="color:#CE5266;">条件解析异常,渲染失败😢<span>`;
//this.$err(err, "解析条件失败:")
});
data.loadingReqs.push(req);
return branchTasks;
}
function getConcurrentNode(node, pbnode, pbid) {
let concurrentTasks = {
id: node.id,
title: node.name,
name: "并行分支",
icon: "settings-filled",
enableEdit: false,
active: node.branchs[0].id, //激活得分支
options: [], //分支选项,渲染单选框
desc: "所有分支都将同时执行",
branchs: {}, //分支数据不包含分支节点key=分支子节点idvalue = [后续节点]
};
node.branchs.forEach((nd) => {
concurrentTasks.options.push({
id: nd.id,
title: nd.name,
skip: false,
});
concurrentTasks.branchs[nd.id] = [];
//设置下子级分支的父级分支节点
data.branchNodeMap.set(nd.id, {
node: pbnode,
id: pbid,
});
loadProcess(
nd.children,
concurrentTasks.branchs[nd.id],
concurrentTasks,
nd.id
);
});
return concurrentTasks;
}
function selected(users) {
_value.value[data.selectedNode.id] = [];
users.forEach((u) => {
if (data.selectedNode.users.findIndex((v) => v.id === u.id) === -1) {
u.enableEdit = true;
data.selectedNode.users.push(u);
_value.value[data.selectedNode.id] = data.selectedNode.users;
}
});
}
function getSubModel(call) {
if (data.models) {
call();
} else {
data.calls.push(call);
if (data.calls.length === 1) {
getGroupModels({}, true).then((rsp) => {
data.models = {};
rsp.data.forEach((group) => {
group.items.forEach((v) => (data.models[v.procCode] = v.procName));
});
data.calls.forEach((callFun) => callFun());
data.calls.length = 0;
});
}
}
}
function addOrg(node) {
console.log(node, "node");
data.selectedNode = node;
orgPicker.value.show();
}
function doAddOrg(orgs) {
selected(orgs);
}
function delOrg(i, node) {
node.users.splice(i, 1);
}
//执行校验流程步骤设置
function validate(call) {
//遍历自选审批人节点
let isOk = true;
const activeProcessTasksList = data.processTasks.filter(
(item) => item.active
);
const selectUserNodes = new Set(
Array.from(data.selectUserNodes).sort((a, b) => {
return (
Number(a.replace(/[^0-9]/g, "")) - Number(b.replace(/[^0-9]/g, ""))
);
})
);
console.log(isOk, "我的OK");
for (let nodeId of selectUserNodes) {
if ((_value.value[nodeId] || []).length === 0) {
const branchNodeMap = new Map(
Array.from(data.branchNodeMap)
.reverse()
.map((i) => {
return {
...i,
};
})
);
//遍历所有的分支,从底部向上搜索进行自动切换分支渲染路线
let brNode = branchNodeMap.get(nodeId);
const option = brNode.node.options.find((i) => i.id === brNode.id);
const find = activeProcessTasksList.map((i) => {
const find = i.options.find((e) => e.id === i.active);
return find ? find : {};
})[0];
while (brNode && brNode.id && !option.skip && !find.skip) {
brNode.node.active = brNode.id;
brNode = data.branchNodeMap.get(brNode.id);
//没设置审批人员
isOk = false;
break;
}
nextTick(() => {
if (instance.refs[nodeId]) {
instance.refs[nodeId][0].errorShark();
}
});
// break
}
}
console.log(isOk, "我的OK");
if (call) {
call(isOk);
}
}
watch(
props.formData,
async () => {
console.log("数据变化");
//监听表单数据变化,对流程重渲染
processRenderDebounce();
},
{ deep: true }
);
</script>
<style lang="less" scoped>
.w-process {
position: relative;
.w-process-line {
width: 4rpx;
top: 20px;
height: calc(100% - 80rpx);
background-color: #9e9e9e;
position: absolute;
left: 26rpx;
}
}
.w-process-render {
.w-node-render {
padding: 32rpx 0;
}
}
</style>