仿钉钉流程设计器- 代码优化

pull/452/head
jason 2024-07-23 22:39:06 +08:00
parent 2c11228f55
commit 3a5c93fa84
9 changed files with 675 additions and 603 deletions

View File

@ -50,6 +50,7 @@ import CopyTaskNode from './nodes/CopyTaskNode.vue'
import ExclusiveNode from './nodes/ExclusiveNode.vue'
import ParallelNode from './nodes/ParallelNode.vue'
import { SimpleFlowNode, NodeType } from './consts'
import { useWatchNode } from './node'
defineOptions({
name: 'ProcessNodeTree'
})
@ -72,15 +73,8 @@ const emits = defineEmits<{
]
}>()
const currentNode = ref<SimpleFlowNode>(props.flowNode)
const currentNode = useWatchNode(props)
// .
watch(
() => props.flowNode,
(newValue) => {
currentNode.value = newValue
}
)
//
const handleModelValueUpdate = (updateValue) => {
emits('update:flowNode', updateValue)

View File

@ -138,6 +138,7 @@ const zoomIn = () => {
onMounted(async () => {
const result = await getBpmSimpleModel(props.modelId)
console.log('the result is :', result)
if (result) {
processNodeTree.value = result
} else {

View File

@ -1,6 +1,8 @@
// @ts-ignore
import { DictDataVO } from '@/api/system/dict/types'
/**
*
*/
export enum NodeType {
/**
*
@ -46,68 +48,37 @@ export enum NodeType {
*/
INCLUSIVE_NODE_JOIN = 8
}
// 时间单位枚举
export enum TimeUnitType {
/**
*
*/
MINUTE = 1,
/**
*
*/
HOUR = 2,
/**
*
*/
DAY = 3
/**
*
*/
export interface SimpleFlowNode {
id: string
type: NodeType
name: string
showText?: string
attributes?: any
// 孩子节点
childNode?: SimpleFlowNode
// 条件节点
conditionNodes?: SimpleFlowNode[]
// 候选人策略
candidateStrategy?: number
// 候选人参数
candidateParam?: string
// 多人审批方式
approveMethod?: ApproveMethodType
//通过比例
approveRatio?: number
// 审批按钮设置
buttonsSetting?: any[]
// 表单权限
fieldsPermission?: Array<Record<string, string>>
// 审批任务超时处理
timeoutHandler?: TimeoutHandler
// 审批任务拒绝处理
rejectHandler?: RejectHandler
}
export enum RejectHandlerType {
/**
*
*/
FINISH_PROCESS = 1,
/**
*
*/
RETURN_USER_TASK = 2
}
// 条件配置类型 用于条件节点配置
export enum ConditionConfigType {
/**
*
*/
EXPRESSION = 1,
/**
*
*/
RULE = 2
}
// 多人审批方式类型 用于审批节点
export enum ApproveMethodType {
/**
*
*/
RRANDOM_SELECT_ONE_APPROVE = 1,
/**
* ()
*/
APPROVE_BY_RATIO = 2,
/**
* ()
*/
ANY_APPROVE = 3,
/**
*
*/
SEQUENTIAL_APPROVE = 4
}
// 候选人策略 用于审批节点。抄送节点 )
// 候选人策略枚举 用于审批节点。抄送节点 )
export enum CandidateStrategy {
/**
*
@ -147,12 +118,41 @@ export enum CandidateStrategy {
EXPRESSION = 60
}
export type RejectHandler = {
type: RejectHandlerType
// 多人审批方式类型枚举 用于审批节点
export enum ApproveMethodType {
/**
*
*/
RRANDOM_SELECT_ONE_APPROVE = 1,
/**
* ()
*/
APPROVE_BY_RATIO = 2,
/**
* ()
*/
ANY_APPROVE = 3,
/**
*
*/
SEQUENTIAL_APPROVE = 4
}
/**
*
*/
export type RejectHandler = {
// 审批拒绝类型
type: RejectHandlerType
// 回退节点 Id
returnNodeId?: string
}
/**
*
*/
export type TimeoutHandler = {
//是否开启超时处理
enable: boolean
@ -163,60 +163,57 @@ export type TimeoutHandler = {
// 执行动作是自动提醒, 最大提醒次数
maxRemindCount?: number
}
export type SimpleFlowNode = {
id: string
type: NodeType
name: string
showText?: string
attributes?: any
// 孩子节点
childNode?: SimpleFlowNode
// 条件节点
conditionNodes?: SimpleFlowNode[]
// 候选人策略
candidateStrategy?: number
// 候选人参数
candidateParam?: string
// 多人审批方式
approveMethod?: ApproveMethodType
//通过比例
approveRatio?: number
// 审批按钮设置
buttonsSetting?: any[]
// 表单权限
fieldsPermission?: any[]
// 审批任务超时处理
timeoutHandler?: TimeoutHandler
// 审批任务拒绝处理
rejectHandler?: RejectHandler
// 审批拒绝类型枚举
export enum RejectHandlerType {
/**
*
*/
FINISH_PROCESS = 1,
/**
*
*/
RETURN_USER_TASK = 2
}
// 条件组
export type ConditionGroup = {
// 条件组的逻辑关系是否为且
and: boolean
// 条件数组
conditions: Condition[]
// 时间单位枚举
export enum TimeUnitType {
/**
*
*/
MINUTE = 1,
/**
*
*/
HOUR = 2,
/**
*
*/
DAY = 3
}
// 条件
export type Condition = {
// 条件规则的逻辑关系是否为且
and: boolean
rules: ConditionRule[]
// 条件配置类型 用于条件节点配置
export enum ConditionConfigType {
/**
*
*/
EXPRESSION = 1,
/**
*
*/
RULE = 2
}
// 条件规则
export type ConditionRule = {
type: number
opName: string
opCode: string
leftSide: string
rightSide: string
/**
*
*/
export type ButtonSetting = {
id: OpsButtonType
displayName: string
enable: boolean
}
// 审批操作按钮类型
// 操作按钮类型枚举 (用于审批节点)
export enum OpsButtonType {
/**
*
@ -243,11 +240,34 @@ export enum OpsButtonType {
*/
RETURN = 6
}
/**
*
*/
export type ConditionRule = {
type: number
opName: string
opCode: string
leftSide: string
rightSide: string
}
export type ButtonSetting = {
id: OpsButtonType
displayName: string
enable: boolean
/**
*
*/
export type ConditionGroup = {
// 条件组的逻辑关系是否为且
and: boolean
// 条件数组
conditions: Condition[]
}
/**
*
*/
export type Condition = {
// 条件规则的逻辑关系是否为且
and: boolean
rules: ConditionRule[]
}
export const NODE_DEFAULT_TEXT = new Map<number, string>()

View File

@ -0,0 +1,260 @@
import { cloneDeep } from 'lodash-es'
import * as RoleApi from '@/api/system/role'
import * as DeptApi from '@/api/system/dept'
import * as PostApi from '@/api/system/post'
import * as UserApi from '@/api/system/user'
import * as UserGroupApi from '@/api/bpm/userGroup'
import {
SimpleFlowNode,
CandidateStrategy,
NodeType,
ApproveMethodType,
RejectHandlerType,
NODE_DEFAULT_NAME
} from './consts'
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
const node = ref<SimpleFlowNode>(props.flowNode)
watch(
() => props.flowNode,
(newValue) => {
node.value = newValue
}
)
return node
}
/**
* @description
*/
export function useFormFieldsPermission() {
// 字段权限配置. 需要有 field, title, permissioin 属性
const fieldsPermissionConfig = ref<Array<Record<string, string>>>([])
const formType = inject<Ref<number>>('formType') // 表单类型
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => {
nodeFormFields = toRaw(nodeFormFields)
fieldsPermissionConfig.value =
cloneDeep(nodeFormFields) || getDefaultFieldsPermission(unref(formFields))
}
// 获取默认的表单权限。 所有字段只读
const getDefaultFieldsPermission = (formFields?: string[]) => {
const defaultFieldsPermission: Array<Record<string, string>> = []
if (formFields) {
formFields.forEach((fieldStr: string) => {
const { field, title } = JSON.parse(fieldStr)
defaultFieldsPermission.push({
field,
title,
permission: '1' // 只读
})
})
}
return defaultFieldsPermission
}
return {
formType,
fieldsPermissionConfig,
getNodeConfigFormFields
}
}
export type UserTaskFormType = {
candidateParamArray: any[]
candidateStrategy: CandidateStrategy
approveMethod: ApproveMethodType
approveRatio?: number
rejectHandlerType?: RejectHandlerType
returnNodeId?: string
timeoutHandlerEnable?: boolean
timeoutHandlerAction?: number
timeDuration?: number
maxRemindCount?: number
buttonsSetting: any[]
}
export type CopyTaskFormType = {
candidateParamArray: any[]
candidateStrategy: CandidateStrategy
}
/**
* @description
*/
export function useNodeForm(nodeType: NodeType) {
const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList') // 角色列表
const postOptions = inject<Ref<PostApi.PostVO[]>>('postList') // 岗位列表
const userOptions = inject<Ref<UserApi.UserVO[]>>('userList') // 用户列表
const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') // 部门列表
const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表
const deptTreeOptions = inject('deptTree') // 部门树
const configForm = ref<UserTaskFormType | CopyTaskFormType>()
if (nodeType === NodeType.USER_TASK_NODE) {
configForm.value = {
candidateParamArray: [],
candidateStrategy: CandidateStrategy.USER,
approveMethod: ApproveMethodType.RRANDOM_SELECT_ONE_APPROVE,
approveRatio: 100,
rejectHandlerType: RejectHandlerType.FINISH_PROCESS,
returnNodeId: '',
timeoutHandlerEnable: false,
timeoutHandlerAction: 1,
timeDuration: 6, // 默认 6小时
maxRemindCount: 1, // 默认 提醒 1次
buttonsSetting: []
}
} else {
configForm.value = {
candidateParamArray: [],
candidateStrategy: CandidateStrategy.USER
}
}
const getShowText = (): string => {
let showText = ''
// 指定成员
if (configForm.value?.candidateStrategy === CandidateStrategy.USER) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
userOptions?.value.forEach((item) => {
if (configForm.value?.candidateParamArray.includes(item.id)) {
candidateNames.push(item.nickname)
}
})
showText = `指定成员:${candidateNames.join(',')}`
}
}
// 指定角色
if (configForm.value?.candidateStrategy === CandidateStrategy.ROLE) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
roleOptions?.value.forEach((item) => {
if (configForm.value?.candidateParamArray.includes(item.id)) {
candidateNames.push(item.name)
}
})
showText = `指定角色:${candidateNames.join(',')}`
}
}
// 指定部门
if (
configForm.value?.candidateStrategy === CandidateStrategy.DEPT_MEMBER ||
configForm.value?.candidateStrategy === CandidateStrategy.DEPT_LEADER
) {
if (configForm.value?.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
deptOptions?.value.forEach((item) => {
if (configForm.value?.candidateParamArray.includes(item.id)) {
candidateNames.push(item.name)
}
})
if (configForm.value.candidateStrategy === CandidateStrategy.DEPT_MEMBER) {
showText = `部门成员:${candidateNames.join(',')}`
} else {
showText = `部门的负责人:${candidateNames.join(',')}`
}
}
}
// 指定岗位
if (configForm.value?.candidateStrategy === CandidateStrategy.POST) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
postOptions?.value.forEach((item) => {
if (configForm.value?.candidateParamArray.includes(item.id)) {
candidateNames.push(item.name)
}
})
showText = `指定岗位: ${candidateNames.join(',')}`
}
}
// 指定用户组
if (configForm.value?.candidateStrategy === CandidateStrategy.USER_GROUP) {
if (configForm.value?.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
userGroupOptions?.value.forEach((item) => {
if (configForm.value?.candidateParamArray.includes(item.id)) {
candidateNames.push(item.name)
}
})
showText = `指定用户组: ${candidateNames.join(',')}`
}
}
// 发起人自选
if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
showText = `发起人自选`
}
// 发起人自己
if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER) {
showText = `发起人自己`
}
// 流程表达式
if (configForm.value?.candidateStrategy === CandidateStrategy.EXPRESSION) {
if (configForm.value.candidateParamArray?.length > 0) {
showText = `流程表达式:${configForm.value.candidateParamArray[0]}`
}
}
return showText
}
return {
configForm,
roleOptions,
postOptions,
userOptions,
userGroupOptions,
deptTreeOptions,
getShowText
}
}
/**
* @description
*/
export function useDrawer() {
// 抽屉配置是否可见
const settingVisible = ref(false)
// 关闭配置抽屉
const closeDrawer = () => {
settingVisible.value = false
}
// 打开配置抽屉
const openDrawer = () => {
settingVisible.value = true
}
return {
settingVisible,
closeDrawer,
openDrawer
}
}
/**
* @description
*/
export function useNodeName(nodeType: NodeType) {
// 节点名称
const nodeName = ref<string>()
// 节点名称输入框
const showInput = ref(false)
// 点击节点名称编辑图标
const clickIcon = () => {
showInput.value = true
}
// 节点名称输入框失去焦点
const blurEvent = () => {
showInput.value = false
nodeName.value = nodeName.value || (NODE_DEFAULT_NAME.get(nodeType) as string)
}
return {
nodeName,
showInput,
clickIcon,
blurEvent
}
}

View File

@ -14,14 +14,12 @@
class="config-editable-input"
@blur="blurEvent()"
v-mountedFocus
v-model="configForm.name"
:placeholder="configForm.name"
v-model="nodeName"
:placeholder="nodeName"
/>
<div v-else class="node-name"
>{{ configForm.name }}
<Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()"
/></div>
<div v-else class="node-name">
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
</div>
<div class="divide-line"></div>
</div>
</template>
@ -173,7 +171,7 @@
</div>
<div
class="field-setting-item"
v-for="(item, index) in configForm.fieldsPermission"
v-for="(item, index) in fieldsPermissionConfig"
:key="index"
>
<div class="field-setting-item-label"> {{ item.title }} </div>
@ -202,16 +200,17 @@
</el-drawer>
</template>
<script setup lang="ts">
import { SimpleFlowNode, CandidateStrategy, NodeType, NODE_DEFAULT_NAME } from '../consts'
import { getDefaultFieldsPermission } from '../utils'
import { SimpleFlowNode, CandidateStrategy, NodeType } from '../consts'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as RoleApi from '@/api/system/role'
import * as DeptApi from '@/api/system/dept'
import * as PostApi from '@/api/system/post'
import * as UserApi from '@/api/system/user'
import * as UserGroupApi from '@/api/bpm/userGroup'
import {
useWatchNode,
useDrawer,
useNodeName,
useFormFieldsPermission,
useNodeForm,
CopyTaskFormType
} from '../node'
import { defaultProps } from '@/utils/tree'
import { cloneDeep } from 'lodash-es'
defineOptions({
name: 'CopyTaskNodeConfig'
})
@ -221,26 +220,34 @@ const props = defineProps({
required: true
}
})
//
const settingVisible = ref(false)
//
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
//
const currentNode = ref<SimpleFlowNode>(props.flowNode)
//
watch(
() => props.flowNode,
(newValue) => {
currentNode.value = newValue
}
)
const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList') //
const postOptions = inject<Ref<PostApi.PostVO[]>>('postList') //
const userOptions = inject<Ref<UserApi.UserVO[]>>('userList') //
const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') //
const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') //
const deptTreeOptions = inject('deptTree') //
const formType = inject('formType') //
const formFields = inject<Ref<string[]>>('formFields')
const currentNode = useWatchNode(props)
//
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.COPY_TASK_NODE)
// Tab
const activeTabName = ref('user')
//
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission()
//
const formRef = ref() // Ref
//
const formRules = reactive({
candidateStrategy: [{ required: true, message: '抄送人设置不能为空', trigger: 'change' }],
candidateParamArray: [{ required: true, message: '选项不能为空', trigger: 'blur' }]
})
const {
configForm: tempConfigForm,
roleOptions,
postOptions,
userOptions,
userGroupOptions,
deptTreeOptions,
getShowText
} = useNodeForm(NodeType.COPY_TASK_NODE)
const configForm = tempConfigForm as Ref<CopyTaskFormType>
//
const copyUserStrategies = computed(() => {
return getIntDictOptions(DICT_TYPE.BPM_TASK_CANDIDATE_STRATEGY).filter(
@ -249,25 +256,9 @@ const copyUserStrategies = computed(() => {
item.value !== CandidateStrategy.START_USER
)
})
// Tab
const activeTabName = ref('user')
const formRef = ref() // Ref
const configForm = ref<any>({
name: NODE_DEFAULT_NAME.get(NodeType.COPY_TASK_NODE),
candidateParamArray: [],
candidateStrategy: CandidateStrategy.USER,
fieldsPermission: []
})
//
const formRules = reactive({
candidateStrategy: [{ required: true, message: '抄送人设置不能为空', trigger: 'change' }],
candidateParamArray: [{ required: true, message: '选项不能为空', trigger: 'blur' }]
})
//
const closeDrawer = () => {
settingVisible.value = false
//
const changeCandidateStrategy = () => {
configForm.value.candidateParamArray = []
}
//
const saveConfig = async () => {
@ -277,23 +268,19 @@ const saveConfig = async () => {
if (!valid) return false
const showText = getShowText()
if (!showText) return false
currentNode.value.name = configForm.value.name
currentNode.value.name = nodeName.value!
currentNode.value.candidateParam = configForm.value.candidateParamArray?.join(',')
currentNode.value.candidateStrategy = configForm.value.candidateStrategy
currentNode.value.showText = showText
currentNode.value.fieldsPermission = configForm.value.fieldsPermission
currentNode.value.fieldsPermission = fieldsPermissionConfig.value
settingVisible.value = false
return true
}
const open = () => {
settingVisible.value = true
}
//
const setCurrentNode = (node: SimpleFlowNode) => {
configForm.value.name = node.name
//
const showCopyTaskNodeConfig = (node: SimpleFlowNode) => {
nodeName.value = node.name
//
configForm.value.candidateStrategy = node.candidateStrategy
configForm.value.candidateStrategy = node.candidateStrategy!
const strCandidateParam = node?.candidateParam
if (node.candidateStrategy === CandidateStrategy.EXPRESSION) {
configForm.value.candidateParamArray[0] = strCandidateParam
@ -303,104 +290,10 @@ const setCurrentNode = (node: SimpleFlowNode) => {
}
}
//
configForm.value.fieldsPermission =
cloneDeep(node.fieldsPermission) || getDefaultFieldsPermission(formFields?.value)
getNodeConfigFormFields(node.fieldsPermission)
}
defineExpose({ open, setCurrentNode }) //
const changeCandidateStrategy = () => {
configForm.value.candidateParamArray = []
}
// TODO UserTaskNodeConfig ??
const getShowText = (): string => {
let showText = ''
//
if (configForm.value.candidateStrategy === CandidateStrategy.USER) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
userOptions?.value.forEach((item) => {
if (configForm.value.candidateParamArray.includes(item.id)) {
candidateNames.push(item.nickname)
}
})
showText = `指定成员:${candidateNames.join(',')}`
}
}
//
if (configForm.value.candidateStrategy === CandidateStrategy.ROLE) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
roleOptions?.value.forEach((item) => {
if (configForm.value.candidateParamArray.includes(item.id)) {
candidateNames.push(item.name)
}
})
showText = `指定角色:${candidateNames.join(',')}`
}
}
//
if (
configForm.value.candidateStrategy === CandidateStrategy.DEPT_MEMBER ||
configForm.value.candidateStrategy === CandidateStrategy.DEPT_LEADER
) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
deptOptions?.value.forEach((item) => {
if (configForm.value.candidateParamArray.includes(item.id)) {
candidateNames.push(item.name)
}
})
if (currentNode.value.candidateStrategy === CandidateStrategy.DEPT_MEMBER) {
showText = `部门成员:${candidateNames.join(',')}`
} else {
showText = `部门的负责人:${candidateNames.join(',')}`
}
}
}
//
if (configForm.value.candidateStrategy === CandidateStrategy.POST) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
postOptions?.value.forEach((item) => {
if (configForm.value.candidateParamArray.includes(item.id)) {
candidateNames.push(item.name)
}
})
showText = `指定岗位: ${candidateNames.join(',')}`
}
}
//
if (configForm.value.candidateStrategy === CandidateStrategy.USER_GROUP) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
userGroupOptions?.value.forEach((item) => {
if (configForm.value.candidateParamArray.includes(item.id)) {
candidateNames.push(item.name)
}
})
showText = `指定用户组: ${candidateNames.join(',')}`
}
}
//
if (configForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) {
if (configForm.value.candidateParamArray?.length > 0) {
showText = `流程表达式:${configForm.value.candidateParamArray[0]}`
}
}
return showText
}
//
const showInput = ref(false)
const clickIcon = () => {
showInput.value = true
}
//
const blurEvent = () => {
showInput.value = false
configForm.value.name =
configForm.value.name || (NODE_DEFAULT_NAME.get(NodeType.COPY_TASK_NODE) as string)
}
defineExpose({ openDrawer, showCopyTaskNodeConfig }) //
</script>
<style lang="scss" scoped></style>

View File

@ -15,14 +15,12 @@
class="config-editable-input"
@blur="blurEvent()"
v-mountedFocus
v-model="configForm.name"
:placeholder="configForm.name"
v-model="nodeName"
:placeholder="nodeName"
/>
<div v-else class="node-name"
>{{ configForm.name }}
<Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()"
/></div>
<div v-else class="node-name">
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
</div>
<div class="divide-line"></div>
</div>
</template>
@ -280,7 +278,7 @@
</el-form>
</div>
</el-tab-pane>
<el-tab-pane label="操作按钮设置" name="buttons" v-if="formType === 10">
<el-tab-pane label="操作按钮设置" name="buttons">
<div class="button-setting-pane">
<div class="button-setting-desc">操作按钮</div>
<div class="button-setting-title">
@ -288,11 +286,7 @@
<div class="pl-4 button-title-label">显示名称</div>
<div class="button-title-label">启用</div>
</div>
<div
class="button-setting-item"
v-for="(item, index) in configForm.buttonsSetting"
:key="index"
>
<div class="button-setting-item" v-for="(item, index) in buttonsSetting" :key="index">
<div class="button-setting-item-label"> {{ OPERATION_BUTTON_NAME.get(item.id) }} </div>
<div class="button-setting-item-label">
<input
@ -327,7 +321,7 @@
</div>
<div
class="field-setting-item"
v-for="(item, index) in configForm.fieldsPermission"
v-for="(item, index) in fieldsPermissionConfig"
:key="index"
>
<div class="field-setting-item-label"> {{ item.title }} </div>
@ -368,19 +362,22 @@ import {
TIMEOUT_HANDLER_ACTION_TYPES,
TIME_UNIT_TYPES,
REJECT_HANDLER_TYPES,
NODE_DEFAULT_NAME,
DEFAULT_BUTTON_SETTING,
OPERATION_BUTTON_NAME
OPERATION_BUTTON_NAME,
ButtonSetting
} from '../consts'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { getDefaultFieldsPermission } from '../utils'
import {
useWatchNode,
useNodeName,
useFormFieldsPermission,
useNodeForm,
UserTaskFormType,
useDrawer
} from '../node'
import { defaultProps } from '@/utils/tree'
import * as RoleApi from '@/api/system/role'
import * as DeptApi from '@/api/system/dept'
import * as PostApi from '@/api/system/post'
import * as UserApi from '@/api/system/user'
import * as UserGroupApi from '@/api/bpm/userGroup'
import { cloneDeep } from 'lodash-es'
import { convertTimeUnit } from '../utils'
defineOptions({
name: 'UserTaskNodeConfig'
})
@ -393,44 +390,22 @@ const props = defineProps({
const emits = defineEmits<{
'find:returnTaskNodes': [nodeList: SimpleFlowNode[]]
}>()
//
const currentNode = useWatchNode(props)
//
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
//
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.USER_TASK_NODE)
// Tab
const activeTabName = ref('user')
//
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission()
//
const { buttonsSetting, btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } =
useButtonsSetting()
const currentNode = ref<SimpleFlowNode>(props.flowNode)
//
watch(
() => props.flowNode,
(newValue) => {
currentNode.value = newValue
}
)
const notAllowedMultiApprovers = ref(false)
const settingVisible = ref(false)
const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList') //
const postOptions = inject<Ref<PostApi.PostVO[]>>('postList') //
const userOptions = inject<Ref<UserApi.UserVO[]>>('userList') //
const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') //
const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') //
const deptTreeOptions = inject('deptTree') //
const formType = inject('formType') //
const formFields = inject<Ref<string[]>>('formFields')
const returnTaskList = ref<SimpleFlowNode[]>([])
//
const formRef = ref() // Ref
const activeTabName = ref('user') // Tab
const configForm = ref<any>({
name: NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE),
candidateParamArray: [],
candidateStrategy: CandidateStrategy.USER,
approveMethod: ApproveMethodType.RRANDOM_SELECT_ONE_APPROVE,
approveRatio: 100,
rejectHandlerType: RejectHandlerType.FINISH_PROCESS,
returnNodeId: '',
timeoutHandlerEnable: false,
timeoutHandlerAction: 1,
timeDuration: 6, // 6
maxRemindCount: 1, // 1
fieldsPermission: [],
buttonsSetting: []
})
//
const formRules = reactive({
candidateStrategy: [{ required: true, message: '审批人设置不能为空', trigger: 'change' }],
@ -443,10 +418,66 @@ const formRules = reactive({
timeDuration: [{ required: true, message: '超时时间不能为空', trigger: 'blur' }],
maxRemindCount: [{ required: true, message: '提醒次数不能为空', trigger: 'blur' }]
})
//
const closeDrawer = () => {
settingVisible.value = false
const {
configForm: tempConfigForm,
roleOptions,
postOptions,
userOptions,
userGroupOptions,
deptTreeOptions,
getShowText
} = useNodeForm(NodeType.USER_TASK_NODE)
const configForm = tempConfigForm as Ref<UserTaskFormType>
//
const notAllowedMultiApprovers = ref(false)
//
const changeCandidateStrategy = () => {
configForm.value.candidateParamArray = []
configForm.value.approveMethod = ApproveMethodType.RRANDOM_SELECT_ONE_APPROVE
if (
configForm.value.candidateStrategy === CandidateStrategy.START_USER ||
configForm.value.candidateStrategy === CandidateStrategy.USER
) {
notAllowedMultiApprovers.value = true
} else {
notAllowedMultiApprovers.value = false
}
}
//
const changedCandidateUsers = () => {
if (
configForm.value.candidateParamArray?.length <= 1 &&
configForm.value.candidateStrategy === CandidateStrategy.USER
) {
configForm.value.approveMethod = ApproveMethodType.RRANDOM_SELECT_ONE_APPROVE
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
notAllowedMultiApprovers.value = true
} else {
notAllowedMultiApprovers.value = false
}
}
//
const approveMethodChanged = () => {
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
if (configForm.value.approveMethod === ApproveMethodType.APPROVE_BY_RATIO) {
configForm.value.approveRatio = 100
}
formRef.value.clearValidate('approveRatio')
}
// 退
const returnTaskList = ref<SimpleFlowNode[]>([])
//
const {
timeoutHandlerChange,
cTimeoutAction,
timeoutActionChanged,
timeUnit,
timeUnitChange,
isoTimeDuration,
cTimeoutMaxRemindCount
} = useTimeoutHandler()
//
const saveConfig = async () => {
activeTabName.value = 'user'
@ -455,7 +486,7 @@ const saveConfig = async () => {
if (!valid) return false
const showText = getShowText()
if (!showText) return false
currentNode.value.name = configForm.value.name
currentNode.value.name = nodeName.value!
currentNode.value.candidateStrategy = configForm.value.candidateStrategy
currentNode.value.candidateParam = configForm.value.candidateParamArray?.join(',')
//
@ -465,121 +496,31 @@ const saveConfig = async () => {
}
//
currentNode.value.rejectHandler = {
type: configForm.value.rejectHandlerType,
type: configForm.value.rejectHandlerType!,
returnNodeId: configForm.value.returnNodeId
}
//
currentNode.value.timeoutHandler = {
enable: configForm.value.timeoutHandlerEnable,
enable: configForm.value.timeoutHandlerEnable!,
action: cTimeoutAction.value,
timeDuration: isoTimeDuration.value,
maxRemindCount: cTimeoutMaxRemindCount.value
}
//
currentNode.value.fieldsPermission = configForm.value.fieldsPermission
currentNode.value.fieldsPermission = fieldsPermissionConfig.value
//
currentNode.value.buttonsSetting = configForm.value.buttonsSetting
currentNode.value.buttonsSetting = buttonsSetting.value
currentNode.value.showText = getShowText()
currentNode.value.showText = showText
settingVisible.value = false
return true
}
const getShowText = (): string => {
let showText = ''
//
if (configForm.value.candidateStrategy === CandidateStrategy.USER) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
userOptions?.value.forEach((item) => {
if (configForm.value.candidateParamArray.includes(item.id)) {
candidateNames.push(item.nickname)
}
})
showText = `指定成员:${candidateNames.join(',')}`
}
}
//
if (configForm.value.candidateStrategy === CandidateStrategy.ROLE) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
roleOptions?.value.forEach((item) => {
if (configForm.value.candidateParamArray.includes(item.id)) {
candidateNames.push(item.name)
}
})
showText = `指定角色:${candidateNames.join(',')}`
}
}
//
if (
configForm.value.candidateStrategy === CandidateStrategy.DEPT_MEMBER ||
configForm.value.candidateStrategy === CandidateStrategy.DEPT_LEADER
) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
deptOptions?.value.forEach((item) => {
if (configForm.value.candidateParamArray.includes(item.id)) {
candidateNames.push(item.name)
}
})
if (configForm.value.candidateStrategy === CandidateStrategy.DEPT_MEMBER) {
showText = `部门成员:${candidateNames.join(',')}`
} else {
showText = `部门的负责人:${candidateNames.join(',')}`
}
}
}
//
if (configForm.value.candidateStrategy === CandidateStrategy.POST) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
postOptions?.value.forEach((item) => {
if (configForm.value.candidateParamArray.includes(item.id)) {
candidateNames.push(item.name)
}
})
showText = `指定岗位: ${candidateNames.join(',')}`
}
}
//
if (configForm.value.candidateStrategy === CandidateStrategy.USER_GROUP) {
if (configForm.value.candidateParamArray?.length > 0) {
const candidateNames: string[] = []
userGroupOptions?.value.forEach((item) => {
if (configForm.value.candidateParamArray.includes(item.id)) {
candidateNames.push(item.name)
}
})
showText = `指定用户组: ${candidateNames.join(',')}`
}
}
//
if (configForm.value.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
showText = `发起人自选`
}
//
if (configForm.value.candidateStrategy === CandidateStrategy.START_USER) {
showText = `发起人自己`
}
//
if (configForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) {
if (configForm.value.candidateParamArray?.length > 0) {
showText = `流程表达式:${configForm.value.candidateParamArray[0]}`
}
}
return showText
}
const open = () => {
settingVisible.value = true
}
//
const setCurrentNode = (node: SimpleFlowNode) => {
configForm.value.name = node.name
//
const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
nodeName.value = node.name
//1.1
configForm.value.candidateStrategy = node.candidateStrategy
configForm.value.candidateStrategy = node.candidateStrategy!
const strCandidateParam = node?.candidateParam
if (node.candidateStrategy === CandidateStrategy.EXPRESSION) {
configForm.value.candidateParamArray[0] = strCandidateParam
@ -598,18 +539,18 @@ const setCurrentNode = (node: SimpleFlowNode) => {
notAllowedMultiApprovers.value = false
}
//1.2
configForm.value.approveMethod = node.approveMethod
configForm.value.approveMethod = node.approveMethod!
if (node.approveMethod == ApproveMethodType.APPROVE_BY_RATIO) {
configForm.value.approveRatio = node.approveRatio
configForm.value.approveRatio = node.approveRatio!
}
// 1.3
configForm.value.rejectHandlerType = node.rejectHandler?.type
configForm.value.rejectHandlerType = node.rejectHandler!.type
configForm.value.returnNodeId = node.rejectHandler?.returnNodeId
const matchNodeList = []
emits('find:returnTaskNodes', matchNodeList)
returnTaskList.value = matchNodeList
// 1.4
configForm.value.timeoutHandlerEnable = node.timeoutHandler?.enable
configForm.value.timeoutHandlerEnable = node.timeoutHandler!.enable
if (node.timeoutHandler?.enable && node.timeoutHandler?.timeDuration) {
const strTimeDuration = node.timeoutHandler.timeDuration
let parseTime = strTimeDuration.slice(2, strTimeDuration.length - 1)
@ -620,152 +561,121 @@ const setCurrentNode = (node: SimpleFlowNode) => {
configForm.value.timeoutHandlerAction = node.timeoutHandler?.action
configForm.value.maxRemindCount = node.timeoutHandler?.maxRemindCount
// 2.
configForm.value.buttonsSetting = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING
buttonsSetting.value = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING
// 3.
configForm.value.fieldsPermission =
cloneDeep(node.fieldsPermission) || getDefaultFieldsPermission(formFields?.value)
getNodeConfigFormFields(node.fieldsPermission)
}
defineExpose({ open, setCurrentNode }) //
defineExpose({ openDrawer, showUserTaskNodeConfig }) //
const changeCandidateStrategy = () => {
configForm.value.candidateParamArray = []
configForm.value.approveMethod = ApproveMethodType.RRANDOM_SELECT_ONE_APPROVE
if (
configForm.value.candidateStrategy === CandidateStrategy.START_USER ||
configForm.value.candidateStrategy === CandidateStrategy.USER
) {
notAllowedMultiApprovers.value = true
} else {
notAllowedMultiApprovers.value = false
/**
* @description 操作按钮设置
*/
function useButtonsSetting() {
const buttonsSetting = ref<ButtonSetting[]>()
//
const btnDisplayNameEdit = ref<boolean[]>([])
const changeBtnDisplayName = (index: number) => {
btnDisplayNameEdit.value[index] = true
}
const btnDisplayNameBlurEvent = (index: number) => {
btnDisplayNameEdit.value[index] = false
const buttonItem = buttonsSetting.value![index]
buttonItem.displayName = buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)!
}
return {
buttonsSetting,
btnDisplayNameEdit,
changeBtnDisplayName,
btnDisplayNameBlurEvent
}
}
const changedCandidateUsers = () => {
if (
configForm.value.candidateParamArray?.length <= 1 &&
configForm.value.candidateStrategy === CandidateStrategy.USER
) {
configForm.value.approveMethod = ApproveMethodType.RRANDOM_SELECT_ONE_APPROVE
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
notAllowedMultiApprovers.value = true
} else {
notAllowedMultiApprovers.value = false
}
}
//
const showInput = ref(false)
/**
* @description 审批人超时未处理配置
*/
function useTimeoutHandler() {
//
const timeUnit = ref(TimeUnitType.HOUR)
const clickIcon = () => {
showInput.value = true
}
//
const blurEvent = () => {
showInput.value = false
configForm.value.name =
configForm.value.name || (NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string)
}
//
const timeoutHandlerChange = () => {
if (configForm.value.timeoutHandlerEnable) {
timeUnit.value = 2
configForm.value.timeDuration = 6
configForm.value.timeoutHandlerAction = 1
configForm.value.maxRemindCount = 1
}
}
//
const cTimeoutAction = computed(() => {
if (!configForm.value.timeoutHandlerEnable) {
return undefined
}
return configForm.value.timeoutHandlerAction
})
const approveMethodChanged = () => {
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
if (configForm.value.approveMethod === ApproveMethodType.APPROVE_BY_RATIO) {
configForm.value.approveRatio = 100
//
const timeoutActionChanged = () => {
if (configForm.value.timeoutHandlerAction === 1) {
configForm.value.maxRemindCount = 1 // 1
}
}
formRef.value.clearValidate('approveRatio')
}
const timeUnit = ref(TimeUnitType.HOUR)
//
const timeUnitChange = () => {
// 60
if (timeUnit.value === TimeUnitType.MINUTE) {
configForm.value.timeDuration = 60
}
// 6
if (timeUnit.value === TimeUnitType.HOUR) {
configForm.value.timeDuration = 6
}
// 1
if (timeUnit.value === TimeUnitType.DAY) {
configForm.value.timeDuration = 1
}
}
// ISO
const isoTimeDuration = computed(() => {
if (!configForm.value.timeoutHandlerEnable) {
return undefined
}
let strTimeDuration = 'PT'
if (timeUnit.value === TimeUnitType.MINUTE) {
strTimeDuration += configForm.value.timeDuration + 'M'
}
if (timeUnit.value === TimeUnitType.HOUR) {
strTimeDuration += configForm.value.timeDuration + 'H'
}
if (timeUnit.value === TimeUnitType.DAY) {
strTimeDuration += configForm.value.timeDuration + 'D'
}
return strTimeDuration
})
// ISO
const isoTimeDuration = computed(() => {
if (!configForm.value.timeoutHandlerEnable) {
return undefined
}
let strTimeDuration = 'PT'
if (timeUnit.value === TimeUnitType.MINUTE) {
strTimeDuration += configForm.value.timeDuration + 'M'
}
if (timeUnit.value === TimeUnitType.HOUR) {
strTimeDuration += configForm.value.timeDuration + 'H'
}
if (timeUnit.value === TimeUnitType.DAY) {
strTimeDuration += configForm.value.timeDuration + 'D'
}
return strTimeDuration
})
//
const cTimeoutAction = computed(() => {
if (!configForm.value.timeoutHandlerEnable) {
return undefined
}
return configForm.value.timeoutHandlerAction
})
//
const cTimeoutMaxRemindCount = computed(() => {
if (!configForm.value.timeoutHandlerEnable) {
return undefined
}
if (configForm.value.timeoutHandlerAction !== 1) {
return undefined
}
return configForm.value.maxRemindCount
})
//
const cTimeoutMaxRemindCount = computed(() => {
if (!configForm.value.timeoutHandlerEnable) {
return undefined
}
if (configForm.value.timeoutHandlerAction !== 1) {
return undefined
}
return configForm.value.maxRemindCount
})
//
const timeoutHandlerChange = () => {
if (configForm.value.timeoutHandlerEnable) {
timeUnit.value = 2
configForm.value.timeDuration = 6
configForm.value.timeoutHandlerAction = 1
configForm.value.maxRemindCount = 1
return {
timeoutHandlerChange,
cTimeoutAction,
timeoutActionChanged,
timeUnit,
timeUnitChange,
isoTimeDuration,
cTimeoutMaxRemindCount
}
}
//
const timeoutActionChanged = () => {
if (configForm.value.timeoutHandlerAction === 1) {
configForm.value.maxRemindCount = 1 // 1
}
}
//
const timeUnitChange = () => {
// 60
if (timeUnit.value === TimeUnitType.MINUTE) {
configForm.value.timeDuration = 60
}
// 6
if (timeUnit.value === TimeUnitType.HOUR) {
configForm.value.timeDuration = 6
}
// 1
if (timeUnit.value === TimeUnitType.DAY) {
configForm.value.timeDuration = 1
}
}
const convertTimeUnit = (strTimeUnit: string) => {
if (strTimeUnit === 'M') {
return TimeUnitType.MINUTE
}
if (strTimeUnit === 'H') {
return TimeUnitType.HOUR
}
if (strTimeUnit === 'D') {
return TimeUnitType.DAY
}
return TimeUnitType.HOUR
}
//
const btnDisplayNameEdit = ref<boolean[]>([])
const changeBtnDisplayName = (index: number) => {
btnDisplayNameEdit.value[index] = true
}
const btnDisplayNameBlurEvent = (index: number) => {
btnDisplayNameEdit.value[index] = false
const buttonItem = configForm.value.buttonPermission[index]
buttonItem.displayName = buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)
}
</script>
<style lang="scss" scoped>

View File

@ -43,7 +43,6 @@
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT, NODE_DEFAULT_NAME } from '../consts'
import NodeHandler from '../NodeHandler.vue'
import CopyTaskNodeConfig from '../nodes-config/CopyTaskNodeConfig.vue'
import { generateUUID } from '@/utils'
defineOptions({
name: 'CopyTaskNode'
})
@ -81,8 +80,8 @@ const clickEvent = () => {
const nodeSetting = ref()
//
const openNodeConfig = () => {
nodeSetting.value.setCurrentNode(currentNode.value)
nodeSetting.value.open()
nodeSetting.value.showCopyTaskNodeConfig(currentNode.value)
nodeSetting.value.openDrawer()
}
//

View File

@ -45,6 +45,7 @@
</template>
<script setup lang="ts">
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT, NODE_DEFAULT_NAME } from '../consts'
import { useWatchNode } from '../node'
import NodeHandler from '../NodeHandler.vue'
import UserTaskNodeConfig from '../nodes-config/UserTaskNodeConfig.vue'
defineOptions({
@ -61,21 +62,16 @@ const emits = defineEmits<{
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: NodeType]
}>()
const currentNode = ref<SimpleFlowNode>(props.flowNode)
const currentNode = useWatchNode(props)
const nodeSetting = ref()
//
const openNodeConfig = () => {
//
nodeSetting.value.setCurrentNode(currentNode.value)
nodeSetting.value.open()
nodeSetting.value.showUserTaskNodeConfig(currentNode.value)
nodeSetting.value.openDrawer()
}
//
watch(
() => props.flowNode,
(newValue) => {
currentNode.value = newValue
}
)
//
const showInput = ref(false)
//

View File

@ -1,3 +1,5 @@
import { TimeUnitType } from './consts'
// 获取条件节点默认的名称
export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean): string => {
if (defaultFlow) {
@ -6,18 +8,15 @@ export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean)
return '条件' + (index + 1)
}
// 获得默认的表单字段权限.
export const getDefaultFieldsPermission = (formFields: string[] | undefined) => {
const defaultFieldsPermission: any[] = []
if (formFields) {
formFields.forEach((fieldStr: string) => {
const { field, title } = JSON.parse(fieldStr)
defaultFieldsPermission.push({
field,
title,
permission: '1' // 只读
})
})
export const convertTimeUnit = (strTimeUnit: string) => {
if (strTimeUnit === 'M') {
return TimeUnitType.MINUTE
}
return defaultFieldsPermission
if (strTimeUnit === 'H') {
return TimeUnitType.HOUR
}
if (strTimeUnit === 'D') {
return TimeUnitType.DAY
}
return TimeUnitType.HOUR
}