mobile-workflow/pages/submit/ProcessRender.vue
2024-04-30 00:30:46 +08:00

562 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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 :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,
getCurrentInstance,
watch,
ref
} from 'vue';
import {
$deepCopy
} 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: loginUser.avatar,
sn: loginUser.sn
}
const orgPicker = ref()
const data = reactive({
selectUserNodes: new Set(),
loading: false,
selectedNode: {},
reverse: false,
userCatch: {},
oldFormData: {},
models: null,
processTasks: [],
conditionFormItem: new Set(),
branchNodeMap: new Map(),
loadingReqs: [],
calls: []
})
onBeforeMount(() => loadProcessRender())
//流程渲染去抖动
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
loadProcess(props.process, data.processTasks)
data.processTasks.push({
title: '结束',
name: 'END',
icon: 'checkbox-filled',
enableEdit: false
})
if (data.loadingReqs.length > 0) {
Promise.all(data.loadingReqs).then(() => {
data.loading = false
emits('render-ok')
}).catch(() => data.loading = false)
} else {
emits('render-ok')
data.loading = false
}
}
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
console.log(isOk,'我的OK')
for (let nodeId of data.selectUserNodes) {
if ((_value.value[nodeId] || []).length === 0) {
//没设置审批人员
isOk = false
//遍历所有的分支,从底部向上搜索进行自动切换分支渲染路线
let brNode = data.branchNodeMap.get(nodeId)
while (brNode && brNode.id) {
brNode.node.active = brNode.id
brNode = data.branchNodeMap.get(brNode.id)
}
nextTick(() => {
if (instance.refs[nodeId]) {
instance.refs[nodeId][0].errorShark()
}
})
break
}
}
console.log(isOk,'我的OK')
if (call) {
call(isOk)
}
}
watch(props.formData, async () => {
//监听表单数据变化,对流程重渲染
processRenderDebounce()
})
</script>
<style lang="less" scoped>
.w-process {
position: relative;
.w-process-line {
width: 2px;
top: 20px;
height: calc(100% - 40px);
background-color: #9e9e9e;
position: absolute;
left: 13px;
}
}
.w-process-render {
.w-node-render {
padding: 32rpx 0;
}
}
</style>