仿钉钉流程设计器- 新增审批类型

pull/452/head
jason 2024-08-17 22:49:04 +08:00
parent 996fe3329c
commit eb7e9397f5
6 changed files with 124 additions and 61 deletions

View File

@ -62,6 +62,8 @@ export interface SimpleFlowNode {
childNode?: SimpleFlowNode childNode?: SimpleFlowNode
// 条件节点 // 条件节点
conditionNodes?: SimpleFlowNode[] conditionNodes?: SimpleFlowNode[]
// 审批类型
approveType?: ApproveType
// 候选人策略 // 候选人策略
candidateStrategy?: number candidateStrategy?: number
// 候选人参数 // 候选人参数
@ -249,7 +251,23 @@ export enum AssignStartUserHandlerType {
/** /**
* *
*/ */
ASSIGN_DEPT_LEADER ASSIGN_DEPT_LEADER = 3
}
// 用户任务的审批类型。 【参考飞书】
export enum ApproveType {
/**
*
*/
USER = 1,
/**
*
*/
AUTO_APPROVE = 2,
/**
*
*/
AUTO_REJECT = 3
} }
// 时间单位枚举 // 时间单位枚举
@ -285,13 +303,13 @@ export enum ConditionConfigType {
* *
*/ */
export type ButtonSetting = { export type ButtonSetting = {
id: OpsButtonType id: OperationButtonType
displayName: string displayName: string
enable: boolean enable: boolean
} }
// 操作按钮类型枚举 (用于审批节点) // TODO @jason建议不缩写哈 // 操作按钮类型枚举 (用于审批节点)
export enum OpsButtonType { export enum OperationButtonType {
/** /**
* *
*/ */
@ -371,6 +389,12 @@ export const CANDIDATE_STRATEGY: DictDataVO[] = [
{ label: '用户组', value: CandidateStrategy.USER_GROUP }, { label: '用户组', value: CandidateStrategy.USER_GROUP },
{ label: '流程表达式', value: CandidateStrategy.EXPRESSION } { label: '流程表达式', value: CandidateStrategy.EXPRESSION }
] ]
// 审批节点 的审批类型
export const APPROVE_TYPE: DictDataVO[] = [
{ label: '人工审批', value: ApproveType.USER },
{ label: '自动通过', value: ApproveType.AUTO_APPROVE },
{ label: '自动拒绝', value: ApproveType.AUTO_REJECT }
]
export const APPROVE_METHODS: DictDataVO[] = [ export const APPROVE_METHODS: DictDataVO[] = [
{ label: '随机挑选一人审批', value: ApproveMethodType.RRANDOM_SELECT_ONE_APPROVE }, { label: '随机挑选一人审批', value: ApproveMethodType.RRANDOM_SELECT_ONE_APPROVE },
@ -442,21 +466,21 @@ export const COMPARISON_OPERATORS: DictDataVO = [
] ]
// 审批操作按钮名称 // 审批操作按钮名称
export const OPERATION_BUTTON_NAME = new Map<number, string>() export const OPERATION_BUTTON_NAME = new Map<number, string>()
OPERATION_BUTTON_NAME.set(OpsButtonType.APPROVE, '通过') OPERATION_BUTTON_NAME.set(OperationButtonType.APPROVE, '通过')
OPERATION_BUTTON_NAME.set(OpsButtonType.REJECT, '拒绝') OPERATION_BUTTON_NAME.set(OperationButtonType.REJECT, '拒绝')
OPERATION_BUTTON_NAME.set(OpsButtonType.TRANSFER, '转办') OPERATION_BUTTON_NAME.set(OperationButtonType.TRANSFER, '转办')
OPERATION_BUTTON_NAME.set(OpsButtonType.DELEGATE, '委派') OPERATION_BUTTON_NAME.set(OperationButtonType.DELEGATE, '委派')
OPERATION_BUTTON_NAME.set(OpsButtonType.ADD_SIGN, '加签') OPERATION_BUTTON_NAME.set(OperationButtonType.ADD_SIGN, '加签')
OPERATION_BUTTON_NAME.set(OpsButtonType.RETURN, '回退') OPERATION_BUTTON_NAME.set(OperationButtonType.RETURN, '回退')
// 默认的按钮权限设置 // 默认的按钮权限设置
export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [ export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [
{ id: OpsButtonType.APPROVE, displayName: '通过', enable: true }, { id: OperationButtonType.APPROVE, displayName: '通过', enable: true },
{ id: OpsButtonType.REJECT, displayName: '拒绝', enable: true }, { id: OperationButtonType.REJECT, displayName: '拒绝', enable: true },
{ id: OpsButtonType.TRANSFER, displayName: '转办', enable: false }, { id: OperationButtonType.TRANSFER, displayName: '转办', enable: false },
{ id: OpsButtonType.DELEGATE, displayName: '委派', enable: false }, { id: OperationButtonType.DELEGATE, displayName: '委派', enable: false },
{ id: OpsButtonType.ADD_SIGN, displayName: '加签', enable: false }, { id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false },
{ id: OpsButtonType.RETURN, displayName: '回退', enable: false } { id: OperationButtonType.RETURN, displayName: '回退', enable: false }
] ]
export const MULTI_LEVEL_DEPT: DictDataVO = [ export const MULTI_LEVEL_DEPT: DictDataVO = [

View File

@ -293,9 +293,9 @@ export function useNodeForm(nodeType: NodeType) {
break break
// 指定连续多级部门的负责人 // 指定连续多级部门的负责人
case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: { case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: {
// 候选人参数格式 ,分隔。 被分隔的最后一个为部门层级 // 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级
const deptIds = configForm.value.deptIds!.join(',') const deptIds = configForm.value.deptIds!.join(',')
candidateParam = deptIds.concat(',' + configForm.value.deptLevel + '') candidateParam = deptIds.concat('|' + configForm.value.deptLevel + '')
break break
} }
default: default:
@ -341,13 +341,10 @@ export function useNodeForm(nodeType: NodeType) {
break break
// 指定连续多级部门的负责人 // 指定连续多级部门的负责人
case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: { case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: {
// 候选人参数格式 ,分隔。 被分隔的最后一个为部门层级 // 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级
const paramArray = candidateParam.split(',') const paramArray = candidateParam.split('|')
configForm.value.deptIds = [] configForm.value.deptIds = paramArray[0].split(',').map((item) => +item)
for (let i = 0; i < paramArray.length - 1; i++) { configForm.value.deptLevel = +paramArray[1]
configForm.value.deptIds.push(+paramArray[i])
}
configForm.value.deptLevel = +paramArray[paramArray.length - 1]
break break
} }
default: default:

View File

@ -181,7 +181,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { SimpleFlowNode, CandidateStrategy, NodeType, CANDIDATE_STRATEGY } from '../consts' import { SimpleFlowNode, CandidateStrategy, NodeType, CANDIDATE_STRATEGY } from '../consts'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { import {
useWatchNode, useWatchNode,
useDrawer, useDrawer,
@ -261,7 +260,7 @@ const saveConfig = async () => {
const showText = getShowText() const showText = getShowText()
if (!showText) return false if (!showText) return false
currentNode.value.name = nodeName.value! currentNode.value.name = nodeName.value!
handleCandidateParam() currentNode.value.candidateParam = handleCandidateParam()
currentNode.value.candidateStrategy = configForm.value.candidateStrategy currentNode.value.candidateStrategy = configForm.value.candidateStrategy
currentNode.value.showText = showText currentNode.value.showText = showText
currentNode.value.fieldsPermission = fieldsPermissionConfig.value currentNode.value.fieldsPermission = fieldsPermissionConfig.value

View File

@ -24,7 +24,20 @@
<div class="divide-line"></div> <div class="divide-line"></div>
</div> </div>
</template> </template>
<el-tabs type="border-card" v-model="activeTabName"> <div class="flex flex-items-center mb-3">
<span class="font-size-4 mr-3">审批类型 :</span>
<el-radio-group v-model="approveType">
<el-radio
v-for="(item, index) in APPROVE_TYPE"
:key="index"
:value="item.value"
:label="item.value"
>
{{ item.label }}
</el-radio>
</el-radio-group>
</div>
<el-tabs type="border-card" v-model="activeTabName" v-if="approveType === ApproveType.USER">
<el-tab-pane label="审批人" name="user"> <el-tab-pane label="审批人" name="user">
<div> <div>
<el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules"> <el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
@ -58,7 +71,6 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- TODO @jason指定部门的选择不用联动父子例如说可能就是想某个比较高级别的部门审批 -->
<el-form-item <el-form-item
v-if=" v-if="
configForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER || configForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
@ -77,7 +89,7 @@
empty-text="加载中,请稍后" empty-text="加载中,请稍后"
multiple multiple
node-key="id" node-key="id"
check-strictly :check-strictly="true"
style="width: 100%" style="width: 100%"
show-checkbox show-checkbox
/> />
@ -404,6 +416,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
SimpleFlowNode, SimpleFlowNode,
APPROVE_TYPE,
ApproveType,
APPROVE_METHODS, APPROVE_METHODS,
CandidateStrategy, CandidateStrategy,
NodeType, NodeType,
@ -434,7 +448,7 @@ import {
} from '../node' } from '../node'
import { defaultProps } from '@/utils/tree' import { defaultProps } from '@/utils/tree'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { convertTimeUnit } from '../utils' import { convertTimeUnit, getApproveTypeText } from '../utils'
defineOptions({ defineOptions({
name: 'UserTaskNodeConfig' name: 'UserTaskNodeConfig'
}) })
@ -469,7 +483,7 @@ const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFie
// //
const { buttonsSetting, btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } = const { buttonsSetting, btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } =
useButtonsSetting() useButtonsSetting()
const approveType = ref(ApproveType.USER)
// //
const formRef = ref() // Ref const formRef = ref() // Ref
// //
@ -563,12 +577,23 @@ const {
// //
const saveConfig = async () => { const saveConfig = async () => {
activeTabName.value = 'user' activeTabName.value = 'user'
//
currentNode.value.name = nodeName.value!
//
currentNode.value.approveType = approveType.value
//
if (approveType.value !== ApproveType.USER) {
currentNode.value.showText = getApproveTypeText(approveType.value)
settingVisible.value = false
return true
}
if (!formRef) return false if (!formRef) return false
const valid = await formRef.value.validate() const valid = await formRef.value.validate()
if (!valid) return false if (!valid) return false
const showText = getShowText() const showText = getShowText()
if (!showText) return false if (!showText) return false
currentNode.value.name = nodeName.value!
currentNode.value.candidateStrategy = configForm.value.candidateStrategy currentNode.value.candidateStrategy = configForm.value.candidateStrategy
// candidateParam // candidateParam
currentNode.value.candidateParam = handleCandidateParam() currentNode.value.candidateParam = handleCandidateParam()
@ -612,7 +637,14 @@ const saveConfig = async () => {
// //
const showUserTaskNodeConfig = (node: SimpleFlowNode) => { const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
nodeName.value = node.name nodeName.value = node.name
//1.1 // 1
approveType.value = node.approveType ? node.approveType : ApproveType.USER
//
if (approveType.value !== ApproveType.USER) {
return
}
//2.1
configForm.value.candidateStrategy = node.candidateStrategy! configForm.value.candidateStrategy = node.candidateStrategy!
// //
parseCandidateParam(node.candidateStrategy!, node?.candidateParam) parseCandidateParam(node.candidateStrategy!, node?.candidateParam)
@ -621,18 +653,18 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
} else { } else {
notAllowedMultiApprovers.value = false notAllowedMultiApprovers.value = false
} }
// 1.2 // 2.2
configForm.value.approveMethod = node.approveMethod! configForm.value.approveMethod = node.approveMethod!
if (node.approveMethod == ApproveMethodType.APPROVE_BY_RATIO) { if (node.approveMethod == ApproveMethodType.APPROVE_BY_RATIO) {
configForm.value.approveRatio = node.approveRatio! configForm.value.approveRatio = node.approveRatio!
} }
// 1.3 // 2.3
configForm.value.rejectHandlerType = node.rejectHandler!.type configForm.value.rejectHandlerType = node.rejectHandler!.type
configForm.value.returnNodeId = node.rejectHandler?.returnNodeId configForm.value.returnNodeId = node.rejectHandler?.returnNodeId
const matchNodeList = [] const matchNodeList = []
emits('find:returnTaskNodes', matchNodeList) emits('find:returnTaskNodes', matchNodeList)
returnTaskList.value = matchNodeList returnTaskList.value = matchNodeList
// 1.4 // 2.4
configForm.value.timeoutHandlerEnable = node.timeoutHandler!.enable configForm.value.timeoutHandlerEnable = node.timeoutHandler!.enable
if (node.timeoutHandler?.enable && node.timeoutHandler?.timeDuration) { if (node.timeoutHandler?.enable && node.timeoutHandler?.timeDuration) {
const strTimeDuration = node.timeoutHandler.timeDuration const strTimeDuration = node.timeoutHandler.timeDuration
@ -643,14 +675,14 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
} }
configForm.value.timeoutHandlerType = node.timeoutHandler?.type configForm.value.timeoutHandlerType = node.timeoutHandler?.type
configForm.value.maxRemindCount = node.timeoutHandler?.maxRemindCount configForm.value.maxRemindCount = node.timeoutHandler?.maxRemindCount
// 1.5 // 2.5
configForm.value.assignEmptyHandlerType = node.assignEmptyHandler?.type configForm.value.assignEmptyHandlerType = node.assignEmptyHandler?.type
configForm.value.assignEmptyHandlerUserIds = node.assignEmptyHandler?.userIds configForm.value.assignEmptyHandlerUserIds = node.assignEmptyHandler?.userIds
// 1.6 // 2.6
configForm.value.assignStartUserHandlerType = node.assignStartUserHandlerType configForm.value.assignStartUserHandlerType = node.assignStartUserHandlerType
// 2. // 3.
buttonsSetting.value = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING buttonsSetting.value = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING
// 3. // 4.
getNodeConfigFormFields(node.fieldsPermission) getNodeConfigFormFields(node.fieldsPermission)
} }

View File

@ -1,4 +1,4 @@
import { TimeUnitType } from './consts' import { TimeUnitType, ApproveType, APPROVE_TYPE } from './consts'
// 获取条件节点默认的名称 // 获取条件节点默认的名称
export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean): string => { export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean): string => {
@ -20,3 +20,14 @@ export const convertTimeUnit = (strTimeUnit: string) => {
} }
return TimeUnitType.HOUR return TimeUnitType.HOUR
} }
export const getApproveTypeText = (approveType: ApproveType): string => {
let approveTypeText = ''
APPROVE_TYPE.forEach((item) => {
if (item.value === approveType) {
approveTypeText = item.label
return
}
})
return approveTypeText
}

View File

@ -59,69 +59,69 @@
<!-- TODO @jason建议搞个 if 来判断替代现有的 !item.buttonsSetting || item.buttonsSetting[OpsButtonType.APPROVE]?.enable --> <!-- TODO @jason建议搞个 if 来判断替代现有的 !item.buttonsSetting || item.buttonsSetting[OpsButtonType.APPROVE]?.enable -->
<el-button <el-button
type="success" type="success"
v-if="!item.buttonsSetting || item.buttonsSetting[OpsButtonType.APPROVE]?.enable" v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.APPROVE]?.enable"
@click="handleAudit(item, true)" @click="handleAudit(item, true)"
> >
<Icon icon="ep:select" /> <Icon icon="ep:select" />
<!-- TODO @jason这个也是类似哈搞个方法来生成名字 --> <!-- TODO @jason这个也是类似哈搞个方法来生成名字 -->
{{ {{
item.buttonsSetting?.[OpsButtonType.APPROVE]?.displayName || item.buttonsSetting?.[OperationButtonType.APPROVE]?.displayName ||
OPERATION_BUTTON_NAME.get(OpsButtonType.APPROVE) OPERATION_BUTTON_NAME.get(OperationButtonType.APPROVE)
}} }}
</el-button> </el-button>
<el-button <el-button
v-if="!item.buttonsSetting || item.buttonsSetting[OpsButtonType.REJECT]?.enable" v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.REJECT]?.enable"
type="danger" type="danger"
@click="handleAudit(item, false)" @click="handleAudit(item, false)"
> >
<Icon icon="ep:close" /> <Icon icon="ep:close" />
{{ {{
item.buttonsSetting?.[OpsButtonType.REJECT].displayName || item.buttonsSetting?.[OperationButtonType.REJECT].displayName ||
OPERATION_BUTTON_NAME.get(OpsButtonType.REJECT) OPERATION_BUTTON_NAME.get(OperationButtonType.REJECT)
}} }}
</el-button> </el-button>
<el-button <el-button
v-if="!item.buttonsSetting || item.buttonsSetting[OpsButtonType.TRANSFER]?.enable" v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.TRANSFER]?.enable"
type="primary" type="primary"
@click="openTaskUpdateAssigneeForm(item.id)" @click="openTaskUpdateAssigneeForm(item.id)"
> >
<Icon icon="ep:edit" /> <Icon icon="ep:edit" />
{{ {{
item.buttonsSetting?.[OpsButtonType.TRANSFER]?.displayName || item.buttonsSetting?.[OperationButtonType.TRANSFER]?.displayName ||
OPERATION_BUTTON_NAME.get(OpsButtonType.TRANSFER) OPERATION_BUTTON_NAME.get(OperationButtonType.TRANSFER)
}} }}
</el-button> </el-button>
<el-button <el-button
v-if="!item.buttonsSetting || item.buttonsSetting[OpsButtonType.DELEGATE]?.enable" v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.DELEGATE]?.enable"
type="primary" type="primary"
@click="handleDelegate(item)" @click="handleDelegate(item)"
> >
<Icon icon="ep:position" /> <Icon icon="ep:position" />
{{ {{
item.buttonsSetting?.[OpsButtonType.DELEGATE]?.displayName || item.buttonsSetting?.[OperationButtonType.DELEGATE]?.displayName ||
OPERATION_BUTTON_NAME.get(OpsButtonType.DELEGATE) OPERATION_BUTTON_NAME.get(OperationButtonType.DELEGATE)
}} }}
</el-button> </el-button>
<el-button <el-button
v-if="!item.buttonsSetting || item.buttonsSetting[OpsButtonType.ADD_SIGN]?.enable" v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.ADD_SIGN]?.enable"
type="primary" type="primary"
@click="handleSign(item)" @click="handleSign(item)"
> >
<Icon icon="ep:plus" /> <Icon icon="ep:plus" />
{{ {{
item.buttonsSetting?.[OpsButtonType.ADD_SIGN]?.displayName || item.buttonsSetting?.[OperationButtonType.ADD_SIGN]?.displayName ||
OPERATION_BUTTON_NAME.get(OpsButtonType.ADD_SIGN) OPERATION_BUTTON_NAME.get(OperationButtonType.ADD_SIGN)
}} }}
</el-button> </el-button>
<el-button <el-button
v-if="!item.buttonsSetting || item.buttonsSetting[OpsButtonType.RETURN]?.enable" v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.RETURN]?.enable"
type="warning" type="warning"
@click="handleBack(item)" @click="handleBack(item)"
> >
<Icon icon="ep:back" /> <Icon icon="ep:back" />
{{ {{
item.buttonsSetting?.[OpsButtonType.RETURN]?.displayName || item.buttonsSetting?.[OperationButtonType.RETURN]?.displayName ||
OPERATION_BUTTON_NAME.get(OpsButtonType.RETURN) OPERATION_BUTTON_NAME.get(OperationButtonType.RETURN)
}} }}
</el-button> </el-button>
</div> </div>
@ -192,7 +192,7 @@ import { registerComponent } from '@/utils/routerHelper'
import { isEmpty } from '@/utils/is' import { isEmpty } from '@/utils/is'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import { import {
OpsButtonType, OperationButtonType,
OPERATION_BUTTON_NAME OPERATION_BUTTON_NAME
} from '@/components/SimpleProcessDesignerV2/src/consts' } from '@/components/SimpleProcessDesignerV2/src/consts'