mobile-workflow/pages/submit/ProcessRender.vue

562 lines
14 KiB
Vue
Raw Normal View History

2024-04-28 10:10:03 +08:00
<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'])
2024-04-30 00:30:46 +08:00
const loginUser = JSON.parse(uni.getStorageSync('loginUser'))
2024-04-28 10:10:03 +08:00
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
2024-04-30 00:30:46 +08:00
console.log(isOk,'我的OK')
2024-04-28 10:10:03 +08:00
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
}
}
2024-04-30 00:30:46 +08:00
console.log(isOk,'我的OK')
2024-04-28 10:10:03 +08:00
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>