mirror of
https://gitee.com/hhyykk/ipms-sjy-ui.git
synced 2025-07-30 02:35:08 +08:00
Merge remote-tracking branch 'refs/remotes/yudao/dev' into dev
This commit is contained in:
@ -185,7 +185,6 @@ export const useApiSelect = (option: ApiSelectProps) => {
|
||||
</el-select>
|
||||
)
|
||||
}
|
||||
debugger
|
||||
return (
|
||||
<el-select
|
||||
class="w-1/1"
|
||||
|
@ -16,3 +16,46 @@ export const localeProps = (t, prefix, rules) => {
|
||||
return rule
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析表单组件的 field, title 等字段(递归,如果组件包含子组件)
|
||||
*
|
||||
* @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule
|
||||
* @param fields 解析后表单组件字段
|
||||
* @param parentTitle 如果是子表单,子表单的标题,默认为空
|
||||
*/
|
||||
export const parseFormFields = (
|
||||
rule: Record<string, any>,
|
||||
fields: Array<Record<string, any>> = [],
|
||||
parentTitle: string = ''
|
||||
) => {
|
||||
const { type, field, $required, title: tempTitle, children } = rule
|
||||
if (field && tempTitle) {
|
||||
let title = tempTitle
|
||||
if (parentTitle) {
|
||||
title = `${parentTitle}.${tempTitle}`
|
||||
}
|
||||
let required = false
|
||||
if ($required) {
|
||||
required = true
|
||||
}
|
||||
fields.push({
|
||||
field,
|
||||
title,
|
||||
type,
|
||||
required
|
||||
})
|
||||
// TODO 子表单 需要处理子表单字段
|
||||
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||
// // 解析子表单的字段
|
||||
// rule.props.rule.forEach((item) => {
|
||||
// parseFields(item, fieldsPermission, title)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
if (children && Array.isArray(children)) {
|
||||
children.forEach((rule) => {
|
||||
parseFormFields(rule, fields)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div class="node-handler-wrapper">
|
||||
<div class="node-handler" v-if="props.showAdd">
|
||||
<div class="node-handler">
|
||||
<el-popover
|
||||
trigger="hover"
|
||||
v-model:visible="popoverShow"
|
||||
placement="right-start"
|
||||
width="auto"
|
||||
v-if="!readonly"
|
||||
>
|
||||
<div class="handler-item-wrapper">
|
||||
<div class="handler-item" @click="addNode(NodeType.USER_TASK_NODE)">
|
||||
@ -27,11 +28,17 @@
|
||||
<div class="handler-item-text">条件分支</div>
|
||||
</div>
|
||||
<div class="handler-item" @click="addNode(NodeType.PARALLEL_BRANCH_NODE)">
|
||||
<div class="handler-item-icon condition">
|
||||
<div class="handler-item-icon parallel">
|
||||
<span class="iconfont icon-size icon-parallel"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">并行分支</div>
|
||||
</div>
|
||||
<div class="handler-item" @click="addNode(NodeType.INCLUSIVE_BRANCH_NODE)">
|
||||
<div class="handler-item-icon inclusive">
|
||||
<span class="iconfont icon-size icon-inclusive"></span>
|
||||
</div>
|
||||
<div class="handler-item-text">包容分支</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #reference>
|
||||
<div class="add-icon"><Icon icon="ep:plus" /></div>
|
||||
@ -56,23 +63,36 @@ import { generateUUID } from '@/utils'
|
||||
defineOptions({
|
||||
name: 'NodeHandler'
|
||||
})
|
||||
const popoverShow = ref(false)
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const popoverShow = ref(false)
|
||||
const props = defineProps({
|
||||
childNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
default: null
|
||||
},
|
||||
showAdd: {
|
||||
// 是否显示添加节点
|
||||
type: Boolean,
|
||||
default: true
|
||||
currentNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits(['update:childNode'])
|
||||
|
||||
const readonly = inject<Boolean>('readonly') // 是否只读
|
||||
|
||||
const addNode = (type: number) => {
|
||||
// 校验:条件分支、包容分支后面,不允许直接添加并行分支
|
||||
if (
|
||||
type === NodeType.PARALLEL_BRANCH_NODE &&
|
||||
[NodeType.CONDITION_BRANCH_NODE, NodeType.INCLUSIVE_BRANCH_NODE].includes(
|
||||
props.currentNode?.type
|
||||
)
|
||||
) {
|
||||
message.error('条件分支、包容分支后面,不允许直接添加并行分支')
|
||||
return
|
||||
}
|
||||
|
||||
popoverShow.value = false
|
||||
if (type === NodeType.USER_TASK_NODE) {
|
||||
const id = 'Activity_' + generateUUID()
|
||||
@ -122,12 +142,11 @@ const addNode = (type: number) => {
|
||||
childNode: undefined,
|
||||
conditionType: 1,
|
||||
defaultFlow: false
|
||||
|
||||
},
|
||||
{
|
||||
id: 'Flow_' + generateUUID(),
|
||||
name: '其它情况',
|
||||
showText: '其它情况进入此流程',
|
||||
showText: '未满足其它条件时,将进入此分支',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionType: undefined,
|
||||
@ -162,6 +181,33 @@ const addNode = (type: number) => {
|
||||
}
|
||||
emits('update:childNode', data)
|
||||
}
|
||||
if (type === NodeType.INCLUSIVE_BRANCH_NODE) {
|
||||
const data: SimpleFlowNode = {
|
||||
name: '包容分支',
|
||||
type: NodeType.INCLUSIVE_BRANCH_NODE,
|
||||
id: 'GateWay_' + generateUUID(),
|
||||
childNode: props.childNode,
|
||||
conditionNodes: [
|
||||
{
|
||||
id: 'Flow_' + generateUUID(),
|
||||
name: '包容条件1',
|
||||
showText: '',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
defaultFlow: false
|
||||
},
|
||||
{
|
||||
id: 'Flow_' + generateUUID(),
|
||||
name: '其它情况',
|
||||
showText: '未满足其它条件时,将进入此分支',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
defaultFlow: true
|
||||
}
|
||||
]
|
||||
}
|
||||
emits('update:childNode', data)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -31,6 +31,13 @@
|
||||
@update:model-value="handleModelValueUpdate"
|
||||
@find:parent-node="findFromParentNode"
|
||||
/>
|
||||
<!-- 包容分支节点 -->
|
||||
<InclusiveNode
|
||||
v-if="currentNode && currentNode.type === NodeType.INCLUSIVE_BRANCH_NODE"
|
||||
:flow-node="currentNode"
|
||||
@update:model-value="handleModelValueUpdate"
|
||||
@find:parent-node="findFromParentNode"
|
||||
/>
|
||||
<!-- 递归显示孩子节点 -->
|
||||
<ProcessNodeTree
|
||||
v-if="currentNode && currentNode.childNode"
|
||||
@ -40,7 +47,10 @@
|
||||
/>
|
||||
|
||||
<!-- 结束节点 -->
|
||||
<EndEventNode v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE" />
|
||||
<EndEventNode
|
||||
v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE"
|
||||
:flow-node="currentNode"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import StartUserNode from './nodes/StartUserNode.vue'
|
||||
@ -49,6 +59,7 @@ import UserTaskNode from './nodes/UserTaskNode.vue'
|
||||
import CopyTaskNode from './nodes/CopyTaskNode.vue'
|
||||
import ExclusiveNode from './nodes/ExclusiveNode.vue'
|
||||
import ParallelNode from './nodes/ParallelNode.vue'
|
||||
import InclusiveNode from './nodes/InclusiveNode.vue'
|
||||
import { SimpleFlowNode, NodeType } from './consts'
|
||||
import { useWatchNode } from './node'
|
||||
defineOptions({
|
||||
|
@ -1,23 +1,11 @@
|
||||
<template>
|
||||
<div class="simple-flow-canvas" v-loading="loading">
|
||||
<div class="simple-flow-container">
|
||||
<div class="top-area-container">
|
||||
<div class="top-actions">
|
||||
<div class="canvas-control">
|
||||
<span class="control-scale-group">
|
||||
<span class="control-scale-button"> <Icon icon="ep:plus" @click="zoomOut()" /></span>
|
||||
<span class="control-scale-label">{{ scaleValue }}%</span>
|
||||
<span class="control-scale-button"><Icon icon="ep:minus" @click="zoomIn()" /></span>
|
||||
</span>
|
||||
</div>
|
||||
<el-button type="primary" @click="saveSimpleFlowModel">保存</el-button>
|
||||
<!-- <el-button type="primary">全局设置</el-button> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="scale-container" :style="`transform: scale(${scaleValue / 100});`">
|
||||
<ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-loading="loading" class="overflow-auto">
|
||||
<SimpleProcessModel
|
||||
v-if="processNodeTree"
|
||||
:flow-node="processNodeTree"
|
||||
:readonly="false"
|
||||
@save="saveSimpleFlowModel"
|
||||
/>
|
||||
<Dialog v-model="errorDialogVisible" title="保存失败" width="400" :fullscreen="false">
|
||||
<div class="mb-2">以下节点内容不完善,请修改后保存</div>
|
||||
<div
|
||||
@ -35,7 +23,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProcessNodeTree from './ProcessNodeTree.vue'
|
||||
import SimpleProcessModel from './SimpleProcessModel.vue'
|
||||
import { updateBpmSimpleModel, getBpmSimpleModel } from '@/api/bpm/simple'
|
||||
import { SimpleFlowNode, NodeType, NodeId, NODE_DEFAULT_TEXT } from './consts'
|
||||
import { getModel } from '@/api/bpm/model'
|
||||
@ -50,14 +38,16 @@ import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
defineOptions({
|
||||
name: 'SimpleProcessDesigner'
|
||||
})
|
||||
const router = useRouter() // 路由
|
||||
const emits = defineEmits(['success']) // 保存成功事件
|
||||
|
||||
const props = defineProps({
|
||||
modelId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const loading = ref(true)
|
||||
|
||||
const loading = ref(false)
|
||||
const formFields = ref<string[]>([])
|
||||
const formType = ref(20)
|
||||
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
|
||||
@ -79,28 +69,26 @@ const message = useMessage() // 国际化
|
||||
const processNodeTree = ref<SimpleFlowNode | undefined>()
|
||||
const errorDialogVisible = ref(false)
|
||||
let errorNodes: SimpleFlowNode[] = []
|
||||
const saveSimpleFlowModel = async () => {
|
||||
if (!props.modelId) {
|
||||
message.error('缺少模型 modelId 编号')
|
||||
const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
|
||||
if (!simpleModelNode) {
|
||||
message.error('模型数据为空')
|
||||
return
|
||||
}
|
||||
errorNodes = []
|
||||
validateNode(processNodeTree.value, errorNodes)
|
||||
if (errorNodes.length > 0) {
|
||||
errorDialogVisible.value = true
|
||||
return
|
||||
}
|
||||
const data = {
|
||||
id: props.modelId,
|
||||
simpleModel: processNodeTree.value
|
||||
}
|
||||
|
||||
const result = await updateBpmSimpleModel(data)
|
||||
if (result) {
|
||||
message.success('修改成功')
|
||||
close()
|
||||
} else {
|
||||
message.alert('修改失败')
|
||||
try {
|
||||
loading.value = true
|
||||
const data = {
|
||||
id: props.modelId,
|
||||
simpleModel: simpleModelNode
|
||||
}
|
||||
const result = await updateBpmSimpleModel(data)
|
||||
if (result) {
|
||||
message.success('修改成功')
|
||||
emits('success')
|
||||
} else {
|
||||
message.alert('修改失败')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
// 校验节点设置。 暂时以 showText 为空 未节点错误配置
|
||||
@ -111,58 +99,37 @@ const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNo
|
||||
return
|
||||
}
|
||||
if (type == NodeType.START_USER_NODE) {
|
||||
// 发起人节点暂时不用校验,直接校验孩子节点
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
|
||||
if (type === NodeType.USER_TASK_NODE) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node)
|
||||
}
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
if (type === NodeType.COPY_TASK_NODE) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node)
|
||||
}
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
if (type === NodeType.CONDITION_NODE) {
|
||||
if (
|
||||
type === NodeType.USER_TASK_NODE ||
|
||||
type === NodeType.COPY_TASK_NODE ||
|
||||
type === NodeType.CONDITION_NODE
|
||||
) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node)
|
||||
}
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
|
||||
if (type == NodeType.CONDITION_BRANCH_NODE) {
|
||||
if (
|
||||
type == NodeType.CONDITION_BRANCH_NODE ||
|
||||
type == NodeType.PARALLEL_BRANCH_NODE ||
|
||||
type == NodeType.INCLUSIVE_BRANCH_NODE
|
||||
) {
|
||||
// 分支节点
|
||||
// 1. 先校验各个分支
|
||||
conditionNodes?.forEach((item) => {
|
||||
validateNode(item, errorNodes)
|
||||
})
|
||||
// 2. 校验孩子节点
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
router.push({ path: '/bpm/manager/model' })
|
||||
}
|
||||
let scaleValue = ref(100)
|
||||
const MAX_SCALE_VALUE = 200
|
||||
const MIN_SCALE_VALUE = 50
|
||||
// 放大
|
||||
const zoomOut = () => {
|
||||
if (scaleValue.value == MAX_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value += 10
|
||||
}
|
||||
// 缩小
|
||||
const zoomIn = () => {
|
||||
if (scaleValue.value == MIN_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value -= 10
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
@ -188,7 +155,7 @@ onMounted(async () => {
|
||||
// 获取用户组列表
|
||||
userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
|
||||
|
||||
// 获取 SIMPLE 设计器模型
|
||||
//获取 SIMPLE 设计器模型
|
||||
const result = await getBpmSimpleModel(props.modelId)
|
||||
if (result) {
|
||||
processNodeTree.value = result
|
||||
|
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div class="simple-process-model-container position-relative">
|
||||
<div class="position-absolute top-0px right-0px bg-#fff">
|
||||
<el-row type="flex" justify="end">
|
||||
<el-button-group key="scale-control" size="default">
|
||||
<el-button size="default" :icon="ScaleToOriginal" @click="processReZoom()" />
|
||||
<el-button size="default" :plain="true" :icon="ZoomOut" @click="zoomOut()" />
|
||||
<el-button size="default" class="w-80px"> {{ scaleValue }}% </el-button>
|
||||
<el-button size="default" :plain="true" :icon="ZoomIn" @click="zoomIn()" />
|
||||
</el-button-group>
|
||||
<el-button
|
||||
v-if="!readonly"
|
||||
size="default"
|
||||
class="ml-4px"
|
||||
type="primary"
|
||||
:icon="Select"
|
||||
@click="saveSimpleFlowModel"
|
||||
>保存模型</el-button
|
||||
>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="simple-process-model" :style="`transform: scale(${scaleValue / 100});`">
|
||||
<ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
|
||||
</div>
|
||||
</div>
|
||||
<Dialog v-model="errorDialogVisible" title="保存失败" width="400" :fullscreen="false">
|
||||
<div class="mb-2">以下节点内容不完善,请修改后保存</div>
|
||||
<div
|
||||
class="mb-3 b-rounded-1 bg-gray-100 p-2 line-height-normal"
|
||||
v-for="(item, index) in errorNodes"
|
||||
:key="index"
|
||||
>
|
||||
{{ item.name }} : {{ NODE_DEFAULT_TEXT.get(item.type) }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="errorDialogVisible = false">知道了</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProcessNodeTree from './ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
|
||||
import { useWatchNode } from './node'
|
||||
import { Select, ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
|
||||
defineOptions({
|
||||
name: 'SimpleProcessModel'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
const emits = defineEmits<{
|
||||
'save': [node: SimpleFlowNode | undefined]
|
||||
}>()
|
||||
|
||||
const processNodeTree = useWatchNode(props)
|
||||
|
||||
provide('readonly', props.readonly)
|
||||
let scaleValue = ref(100)
|
||||
const MAX_SCALE_VALUE = 200
|
||||
const MIN_SCALE_VALUE = 50
|
||||
// 放大
|
||||
const zoomIn = () => {
|
||||
if (scaleValue.value == MAX_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value += 10
|
||||
}
|
||||
// 缩小
|
||||
const zoomOut = () => {
|
||||
if (scaleValue.value == MIN_SCALE_VALUE) {
|
||||
return
|
||||
}
|
||||
scaleValue.value -= 10
|
||||
}
|
||||
const processReZoom = () => {
|
||||
scaleValue.value = 100
|
||||
}
|
||||
|
||||
const errorDialogVisible = ref(false)
|
||||
let errorNodes: SimpleFlowNode[] = []
|
||||
const saveSimpleFlowModel = async () => {
|
||||
errorNodes = []
|
||||
validateNode(processNodeTree.value, errorNodes)
|
||||
if (errorNodes.length > 0) {
|
||||
errorDialogVisible.value = true
|
||||
return
|
||||
}
|
||||
emits('save', processNodeTree.value)
|
||||
}
|
||||
// 校验节点设置。 暂时以 showText 为空 未节点错误配置
|
||||
const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => {
|
||||
if (node) {
|
||||
const { type, showText, conditionNodes } = node
|
||||
if (type == NodeType.END_EVENT_NODE) {
|
||||
return
|
||||
}
|
||||
if (type == NodeType.START_USER_NODE) {
|
||||
// 发起人节点暂时不用校验,直接校验孩子节点
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
|
||||
if (
|
||||
type === NodeType.USER_TASK_NODE ||
|
||||
type === NodeType.COPY_TASK_NODE ||
|
||||
type === NodeType.CONDITION_NODE
|
||||
) {
|
||||
if (!showText) {
|
||||
errorNodes.push(node)
|
||||
}
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
|
||||
if (
|
||||
type == NodeType.CONDITION_BRANCH_NODE ||
|
||||
type == NodeType.PARALLEL_BRANCH_NODE ||
|
||||
type == NodeType.INCLUSIVE_BRANCH_NODE
|
||||
) {
|
||||
// 分支节点
|
||||
// 1. 先校验各个分支
|
||||
conditionNodes?.forEach((item) => {
|
||||
validateNode(item, errorNodes)
|
||||
})
|
||||
// 2. 校验孩子节点
|
||||
validateNode(node.childNode, errorNodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<SimpleProcessModel :flow-node="simpleModel" :readonly="true" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useWatchNode } from './node'
|
||||
import { SimpleFlowNode } from './consts'
|
||||
|
||||
defineOptions({
|
||||
name: 'SimpleProcessViewer'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
},
|
||||
// 流程任务
|
||||
tasks: {
|
||||
type: Array,
|
||||
default: () => [] as any[]
|
||||
},
|
||||
// 流程实例
|
||||
processInstance: {
|
||||
type: Object,
|
||||
default: () => undefined
|
||||
}
|
||||
})
|
||||
const approveTasks = ref<any[]>(props.tasks)
|
||||
const currentProcessInstance = ref(props.processInstance)
|
||||
const simpleModel = useWatchNode(props)
|
||||
watch(
|
||||
() => props.tasks,
|
||||
(newValue) => {
|
||||
approveTasks.value = newValue
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.processInstance,
|
||||
(newValue) => {
|
||||
currentProcessInstance.value = newValue
|
||||
}
|
||||
)
|
||||
|
||||
provide('tasks', approveTasks)
|
||||
provide('processInstance', currentProcessInstance)
|
||||
</script>
|
||||
p
|
@ -1,6 +1,6 @@
|
||||
// @ts-ignore
|
||||
import { DictDataVO } from '@/api/system/dict/types'
|
||||
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
@ -79,7 +79,7 @@ export interface SimpleFlowNode {
|
||||
// 审批按钮设置
|
||||
buttonsSetting?: any[]
|
||||
// 表单权限
|
||||
fieldsPermission?: Array<Record<string, string>>
|
||||
fieldsPermission?: Array<Record<string, any>>
|
||||
// 审批任务超时处理
|
||||
timeoutHandler?: TimeoutHandler
|
||||
// 审批任务拒绝处理
|
||||
@ -96,7 +96,8 @@ export interface SimpleFlowNode {
|
||||
conditionGroups?: ConditionGroup
|
||||
// 是否默认的条件
|
||||
defaultFlow?: boolean
|
||||
|
||||
// 活动的状态,用于前端节点状态展示
|
||||
activityStatus?: TaskStatusEnum
|
||||
}
|
||||
// 候选人策略枚举 ( 用于审批节点。抄送节点 )
|
||||
export enum CandidateStrategy {
|
||||
@ -144,6 +145,14 @@ export enum CandidateStrategy {
|
||||
* 指定用户组
|
||||
*/
|
||||
USER_GROUP = 40,
|
||||
/**
|
||||
* 表单内用户字段
|
||||
*/
|
||||
FORM_USER = 50,
|
||||
/**
|
||||
* 表单内部门负责人
|
||||
*/
|
||||
FORM_DEPT_LEADER = 51,
|
||||
/**
|
||||
* 流程表达式
|
||||
*/
|
||||
@ -178,7 +187,7 @@ export enum ApproveMethodType {
|
||||
export type RejectHandler = {
|
||||
// 审批拒绝类型
|
||||
type: RejectHandlerType
|
||||
// 回退节点 Id
|
||||
// 退回节点 Id
|
||||
returnNodeId?: string
|
||||
}
|
||||
|
||||
@ -360,9 +369,13 @@ export enum OperationButtonType {
|
||||
*/
|
||||
ADD_SIGN = 5,
|
||||
/**
|
||||
* 回退
|
||||
* 退回
|
||||
*/
|
||||
RETURN = 6
|
||||
RETURN = 6,
|
||||
/**
|
||||
* 抄送
|
||||
*/
|
||||
COPY = 7
|
||||
}
|
||||
|
||||
/**
|
||||
@ -419,6 +432,8 @@ export const CANDIDATE_STRATEGY: DictDataVO[] = [
|
||||
{ label: '发起人部门负责人', value: CandidateStrategy.START_USER_DEPT_LEADER },
|
||||
{ label: '发起人连续部门负责人', value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER },
|
||||
{ label: '用户组', value: CandidateStrategy.USER_GROUP },
|
||||
{ label: '表单内用户字段', value: CandidateStrategy.FORM_USER },
|
||||
{ label: '表单内部门负责人', value: CandidateStrategy.FORM_DEPT_LEADER },
|
||||
{ label: '流程表达式', value: CandidateStrategy.EXPRESSION }
|
||||
]
|
||||
// 审批节点 的审批类型
|
||||
@ -503,16 +518,17 @@ OPERATION_BUTTON_NAME.set(OperationButtonType.REJECT, '拒绝')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.TRANSFER, '转办')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.DELEGATE, '委派')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.ADD_SIGN, '加签')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.RETURN, '回退')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.RETURN, '退回')
|
||||
OPERATION_BUTTON_NAME.set(OperationButtonType.COPY, '抄送')
|
||||
|
||||
// 默认的按钮权限设置
|
||||
export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [
|
||||
{ id: OperationButtonType.APPROVE, displayName: '通过', enable: true },
|
||||
{ id: OperationButtonType.REJECT, displayName: '拒绝', enable: true },
|
||||
{ id: OperationButtonType.TRANSFER, displayName: '转办', enable: false },
|
||||
{ id: OperationButtonType.DELEGATE, displayName: '委派', enable: false },
|
||||
{ id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false },
|
||||
{ id: OperationButtonType.RETURN, displayName: '回退', enable: false }
|
||||
{ id: OperationButtonType.TRANSFER, displayName: '转办', enable: true },
|
||||
{ id: OperationButtonType.DELEGATE, displayName: '委派', enable: true },
|
||||
{ id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: true },
|
||||
{ id: OperationButtonType.RETURN, displayName: '退回', enable: true }
|
||||
]
|
||||
|
||||
// 发起人的按钮权限。暂时定死,不可以编辑
|
||||
@ -522,7 +538,7 @@ export const START_USER_BUTTON_SETTING: ButtonSetting[] = [
|
||||
{ id: OperationButtonType.TRANSFER, displayName: '转办', enable: false },
|
||||
{ id: OperationButtonType.DELEGATE, displayName: '委派', enable: false },
|
||||
{ id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false },
|
||||
{ id: OperationButtonType.RETURN, displayName: '回退', enable: false }
|
||||
{ id: OperationButtonType.RETURN, displayName: '退回', enable: false }
|
||||
]
|
||||
|
||||
export const MULTI_LEVEL_DEPT: DictDataVO = [
|
||||
@ -542,3 +558,13 @@ export const MULTI_LEVEL_DEPT: DictDataVO = [
|
||||
{ label: '第 14 级部门', value: 14 },
|
||||
{ label: '第 15 级部门', value: 15 }
|
||||
]
|
||||
|
||||
/**
|
||||
* 流程实例的变量枚举
|
||||
*/
|
||||
export enum ProcessVariableEnum {
|
||||
/**
|
||||
* 发起用户 ID
|
||||
*/
|
||||
START_USER_ID = 'PROCESS_START_USER_ID'
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import SimpleProcessDesigner from './SimpleProcessDesigner.vue'
|
||||
import SimpleProcessViewer from './SimpleProcessViewer.vue'
|
||||
import '../theme/simple-process-designer.scss'
|
||||
|
||||
export { SimpleProcessDesigner }
|
||||
export { SimpleProcessDesigner, SimpleProcessViewer}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { TaskStatusEnum } from '@/api/bpm/task'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
@ -13,8 +14,10 @@ import {
|
||||
NODE_DEFAULT_NAME,
|
||||
AssignStartUserHandlerType,
|
||||
AssignEmptyHandlerType,
|
||||
FieldPermissionType
|
||||
FieldPermissionType,
|
||||
ProcessVariableEnum
|
||||
} from './consts'
|
||||
import { parseFormFields } from '@/components/FormCreate/src/utils/index'
|
||||
export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
|
||||
const node = ref<SimpleFlowNode>(props.flowNode)
|
||||
watch(
|
||||
@ -26,12 +29,30 @@ export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlo
|
||||
return node
|
||||
}
|
||||
|
||||
// 解析 formCreate 所有表单字段, 并返回
|
||||
const parseFormCreateFields = (formFields?: string[]) => {
|
||||
const result: Array<Record<string, any>> = []
|
||||
if (formFields) {
|
||||
formFields.forEach((fieldStr: string) => {
|
||||
parseFormFields(JSON.parse(fieldStr), result)
|
||||
})
|
||||
}
|
||||
// 固定添加发起人 ID 字段
|
||||
result.unshift({
|
||||
field: ProcessVariableEnum.START_USER_ID,
|
||||
title: '发起人',
|
||||
type: 'UserSelect',
|
||||
required: true
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 表单数据权限配置,用于发起人节点 、审批节点、抄送节点
|
||||
*/
|
||||
export function useFormFieldsPermission(defaultPermission: FieldPermissionType) {
|
||||
// 字段权限配置. 需要有 field, title, permissioin 属性
|
||||
const fieldsPermissionConfig = ref<Array<Record<string, string>>>([])
|
||||
const fieldsPermissionConfig = ref<Array<Record<string, any>>>([])
|
||||
|
||||
const formType = inject<Ref<number>>('formType') // 表单类型
|
||||
|
||||
@ -44,49 +65,26 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
|
||||
}
|
||||
// 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读
|
||||
const getDefaultFieldsPermission = (formFields?: string[]) => {
|
||||
const defaultFieldsPermission: Array<Record<string, string>> = []
|
||||
let defaultFieldsPermission: Array<Record<string, any>> = []
|
||||
if (formFields) {
|
||||
formFields.forEach((fieldStr: string) => {
|
||||
parseFieldsSetDefaultPermission(JSON.parse(fieldStr), defaultFieldsPermission)
|
||||
defaultFieldsPermission = parseFormCreateFields(formFields).map((item) => {
|
||||
return {
|
||||
field: item.field,
|
||||
title: item.title,
|
||||
permission: defaultPermission
|
||||
}
|
||||
})
|
||||
}
|
||||
return defaultFieldsPermission
|
||||
}
|
||||
// 解析字段。赋给默认权限
|
||||
const parseFieldsSetDefaultPermission = (
|
||||
rule: Record<string, any>,
|
||||
fieldsPermission: Array<Record<string, string>>,
|
||||
parentTitle: string = ''
|
||||
) => {
|
||||
const { /**type,*/ field, title: tempTitle, children } = rule
|
||||
if (field && tempTitle) {
|
||||
let title = tempTitle
|
||||
if (parentTitle) {
|
||||
title = `${parentTitle}.${tempTitle}`
|
||||
}
|
||||
fieldsPermission.push({
|
||||
field,
|
||||
title,
|
||||
permission: defaultPermission
|
||||
})
|
||||
// TODO 子表单 需要处理子表单字段
|
||||
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||
// // 解析子表单的字段
|
||||
// rule.props.rule.forEach((item) => {
|
||||
// parseFieldsSetDefaultPermission(item, fieldsPermission, title)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
if (children && Array.isArray(children)) {
|
||||
children.forEach((rule) => {
|
||||
parseFieldsSetDefaultPermission(rule, fieldsPermission)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取表单的所有字段,作为下拉框选项
|
||||
const formFieldOptions = parseFormCreateFields(unref(formFields))
|
||||
|
||||
return {
|
||||
formType,
|
||||
fieldsPermissionConfig,
|
||||
formFieldOptions,
|
||||
getNodeConfigFormFields
|
||||
}
|
||||
}
|
||||
@ -94,50 +92,8 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType)
|
||||
* @description 获取表单的字段
|
||||
*/
|
||||
export function useFormFields() {
|
||||
// 解析后的表单字段
|
||||
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
|
||||
const parseFormFields = () => {
|
||||
const parsedFormFields: Array<Record<string, string>> = []
|
||||
if (formFields) {
|
||||
formFields.value.forEach((fieldStr: string) => {
|
||||
parseField(JSON.parse(fieldStr), parsedFormFields)
|
||||
})
|
||||
}
|
||||
return parsedFormFields
|
||||
}
|
||||
// 解析字段。
|
||||
const parseField = (
|
||||
rule: Record<string, any>,
|
||||
parsedFormFields: Array<Record<string, string>>,
|
||||
parentTitle: string = ''
|
||||
) => {
|
||||
const { field, title: tempTitle, children, type } = rule
|
||||
if (field && tempTitle) {
|
||||
let title = tempTitle
|
||||
if (parentTitle) {
|
||||
title = `${parentTitle}.${tempTitle}`
|
||||
}
|
||||
parsedFormFields.push({
|
||||
field,
|
||||
title,
|
||||
type
|
||||
})
|
||||
// TODO 子表单 需要处理子表单字段
|
||||
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||
// // 解析子表单的字段
|
||||
// rule.props.rule.forEach((item) => {
|
||||
// parseFieldsSetDefaultPermission(item, fieldsPermission, title)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
if (children && Array.isArray(children)) {
|
||||
children.forEach((rule) => {
|
||||
parseField(rule, parsedFormFields)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return parseFormFields()
|
||||
return parseFormCreateFields(unref(formFields))
|
||||
}
|
||||
|
||||
export type UserTaskFormType = {
|
||||
@ -151,6 +107,8 @@ export type UserTaskFormType = {
|
||||
userGroups?: number[] // 用户组
|
||||
postIds?: number[] // 岗位
|
||||
expression?: string // 流程表达式
|
||||
formUser?: string // 表单内用户字段
|
||||
formDept?: string // 表单内部门字段
|
||||
approveRatio?: number
|
||||
rejectHandlerType?: RejectHandlerType
|
||||
returnNodeId?: string
|
||||
@ -173,6 +131,8 @@ export type CopyTaskFormType = {
|
||||
userIds?: number[] // 用户
|
||||
userGroups?: number[] // 用户组
|
||||
postIds?: number[] // 岗位
|
||||
formUser?: string // 表单内用户字段
|
||||
formDept?: string // 表单内部门字段
|
||||
expression?: string // 流程表达式
|
||||
}
|
||||
|
||||
@ -186,6 +146,7 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') // 部门列表
|
||||
const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表
|
||||
const deptTreeOptions = inject('deptTree') // 部门树
|
||||
const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
|
||||
const configForm = ref<UserTaskFormType | CopyTaskFormType>()
|
||||
if (nodeType === NodeType.USER_TASK_NODE) {
|
||||
configForm.value = {
|
||||
@ -281,6 +242,18 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
}
|
||||
}
|
||||
|
||||
// 表单内用户字段
|
||||
if (configForm.value?.candidateStrategy === CandidateStrategy.FORM_USER) {
|
||||
const formFieldOptions = parseFormCreateFields(unref(formFields))
|
||||
const item = formFieldOptions.find((item) => item.field === configForm.value?.formUser)
|
||||
showText = `表单用户:${item?.title}`
|
||||
}
|
||||
|
||||
// 表单内部门负责人
|
||||
if (configForm.value?.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER) {
|
||||
showText = `表单内部门负责人`
|
||||
}
|
||||
|
||||
// 发起人自选
|
||||
if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
|
||||
showText = `发起人自选`
|
||||
@ -327,6 +300,9 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
case CandidateStrategy.USER_GROUP:
|
||||
candidateParam = configForm.value.userGroups!.join(',')
|
||||
break
|
||||
case CandidateStrategy.FORM_USER:
|
||||
candidateParam = configForm.value.formUser!
|
||||
break
|
||||
case CandidateStrategy.EXPRESSION:
|
||||
candidateParam = configForm.value.expression!
|
||||
break
|
||||
@ -346,6 +322,13 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
candidateParam = deptIds.concat('|' + configForm.value.deptLevel + '')
|
||||
break
|
||||
}
|
||||
// 表单内部门的负责人
|
||||
case CandidateStrategy.FORM_DEPT_LEADER: {
|
||||
// 候选人参数格式: | 分隔 。左边为表单内部门字段。 右边为部门层级
|
||||
const deptFieldOnForm = configForm.value.formDept!
|
||||
candidateParam = deptFieldOnForm.concat('|' + configForm.value.deptLevel + '')
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -375,6 +358,9 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
case CandidateStrategy.USER_GROUP:
|
||||
configForm.value.userGroups = candidateParam.split(',').map((item) => +item)
|
||||
break
|
||||
case CandidateStrategy.FORM_USER:
|
||||
configForm.value.formUser = candidateParam
|
||||
break
|
||||
case CandidateStrategy.EXPRESSION:
|
||||
configForm.value.expression = candidateParam
|
||||
break
|
||||
@ -395,6 +381,14 @@ export function useNodeForm(nodeType: NodeType) {
|
||||
configForm.value.deptLevel = +paramArray[1]
|
||||
break
|
||||
}
|
||||
// 表单内的部门负责人
|
||||
case CandidateStrategy.FORM_DEPT_LEADER: {
|
||||
// 候选人参数格式: | 分隔 。左边为表单内的部门字段。 右边为部门层级
|
||||
const paramArray = candidateParam.split('|')
|
||||
configForm.value.formDept = paramArray[0]
|
||||
configForm.value.deptLevel = +paramArray[1]
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -476,3 +470,26 @@ export function useNodeName2(node: Ref<SimpleFlowNode>, nodeType: NodeType) {
|
||||
blurEvent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 根据节点任务状态,获取节点任务状态样式
|
||||
*/
|
||||
export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined): string {
|
||||
if (!taskStatus) {
|
||||
return ''
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.APPROVE) {
|
||||
return 'status-pass'
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.RUNNING) {
|
||||
return 'status-running'
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.REJECT) {
|
||||
return 'status-reject'
|
||||
}
|
||||
if (taskStatus === TaskStatusEnum.CANCEL) {
|
||||
return 'status-cancel'
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<div class="mb-3 font-size-16px" v-if="currentNode.defaultFlow">其它条件不满足进入此分支(该分支不可编辑和删除)</div>
|
||||
<div class="mb-3 font-size-16px" v-if="currentNode.defaultFlow">未满足其它条件时,将进入此分支(该分支不可编辑和删除)</div>
|
||||
<div v-else>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
|
@ -60,7 +60,8 @@
|
||||
<el-form-item
|
||||
v-if="
|
||||
configForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.DEPT_LEADER
|
||||
configForm.candidateStrategy == CandidateStrategy.DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
|
||||
"
|
||||
label="指定部门"
|
||||
prop="deptIds"
|
||||
@ -122,7 +123,57 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.FORM_USER"
|
||||
label="表单内用户字段"
|
||||
prop="formUser"
|
||||
>
|
||||
<el-select v-model="configForm.formUser" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="(item, idx) in userFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled ="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
|
||||
label="表单内部门字段"
|
||||
prop="formDept"
|
||||
>
|
||||
<el-select v-model="configForm.formDept" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="(item, idx) in deptFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled ="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="
|
||||
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
configForm.candidateStrategy ==
|
||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
|
||||
"
|
||||
:label="deptLevelLabel!"
|
||||
prop="deptLevel"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="configForm.deptLevel" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.EXPRESSION"
|
||||
label="流程表达式"
|
||||
@ -201,7 +252,8 @@ import {
|
||||
CandidateStrategy,
|
||||
NodeType,
|
||||
CANDIDATE_STRATEGY,
|
||||
FieldPermissionType
|
||||
FieldPermissionType,
|
||||
MULTI_LEVEL_DEPT
|
||||
} from '../consts'
|
||||
import {
|
||||
useWatchNode,
|
||||
@ -221,6 +273,15 @@ const props = defineProps({
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const deptLevelLabel = computed(() => {
|
||||
let label = '部门负责人来源'
|
||||
if (configForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
|
||||
label = label + '(指定部门向上)'
|
||||
} else {
|
||||
label = label + '(发起人部门向上)'
|
||||
}
|
||||
return label
|
||||
})
|
||||
// 抽屉配置
|
||||
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
||||
// 当前节点
|
||||
@ -230,9 +291,16 @@ const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.COPY_
|
||||
// 激活的 Tab 标签页
|
||||
const activeTabName = ref('user')
|
||||
// 表单字段权限配置
|
||||
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
|
||||
FieldPermissionType.READ
|
||||
)
|
||||
const { formType, fieldsPermissionConfig, formFieldOptions, getNodeConfigFormFields } =
|
||||
useFormFieldsPermission(FieldPermissionType.READ)
|
||||
// 表单内用户字段选项, 必须是必填和用户选择器
|
||||
const userFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'UserSelect')
|
||||
})
|
||||
// 表单内部门字段选项, 必须是必填和部门选择器
|
||||
const deptFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'DeptSelect')
|
||||
})
|
||||
// 抄送人表单配置
|
||||
const formRef = ref() // 表单 Ref
|
||||
// 表单校验规则
|
||||
@ -243,6 +311,8 @@ const formRules = reactive({
|
||||
deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
|
||||
userGroups: [{ required: true, message: '用户组不能为空', trigger: 'change' }],
|
||||
postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
|
||||
formUser: [{ required: true, message: '表单内用户字段不能为空', trigger: 'change' }],
|
||||
formDept: [{ required: true, message: '表单内部门字段不能为空', trigger: 'change' }],
|
||||
expression: [{ required: true, message: '流程表达式不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
@ -260,11 +330,7 @@ const {
|
||||
const configForm = tempConfigForm as Ref<CopyTaskFormType>
|
||||
// 抄送人策略, 去掉发起人自选 和 发起人自己
|
||||
const copyUserStrategies = computed(() => {
|
||||
return CANDIDATE_STRATEGY.filter(
|
||||
(item) =>
|
||||
item.value !== CandidateStrategy.START_USER_SELECT &&
|
||||
item.value !== CandidateStrategy.START_USER
|
||||
)
|
||||
return CANDIDATE_STRATEGY.filter((item) => item.value !== CandidateStrategy.START_USER)
|
||||
})
|
||||
// 改变抄送人设置策略
|
||||
const changeCandidateStrategy = () => {
|
||||
@ -274,6 +340,7 @@ const changeCandidateStrategy = () => {
|
||||
configForm.value.postIds = []
|
||||
configForm.value.userGroups = []
|
||||
configForm.value.deptLevel = 1
|
||||
configForm.value.formUser = ''
|
||||
}
|
||||
// 保存配置
|
||||
const saveConfig = async () => {
|
||||
|
@ -119,7 +119,6 @@ const saveConfig = async () => {
|
||||
currentNode.value.fieldsPermission = fieldsPermissionConfig.value
|
||||
// 设置发起人的按钮权限
|
||||
currentNode.value.buttonsSetting = START_USER_BUTTON_SETTING
|
||||
console.log('currentNode.value.buttonsSetting==>', currentNode.value.buttonsSetting)
|
||||
settingVisible.value = false
|
||||
return true
|
||||
}
|
||||
|
@ -56,7 +56,6 @@
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy == CandidateStrategy.ROLE"
|
||||
label="指定角色"
|
||||
@ -94,25 +93,6 @@
|
||||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="
|
||||
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
||||
"
|
||||
:label="deptLevelLabel!"
|
||||
prop="deptLevel"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="configForm.deptLevel" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy == CandidateStrategy.POST"
|
||||
label="指定岗位"
|
||||
@ -134,13 +114,7 @@
|
||||
prop="userIds"
|
||||
span="24"
|
||||
>
|
||||
<el-select
|
||||
v-model="configForm.userIds"
|
||||
clearable
|
||||
multiple
|
||||
style="width: 100%"
|
||||
@change="changedCandidateUsers"
|
||||
>
|
||||
<el-select v-model="configForm.userIds" clearable multiple style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
@ -163,6 +137,57 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.FORM_USER"
|
||||
label="表单内用户字段"
|
||||
prop="formUser"
|
||||
>
|
||||
<el-select v-model="configForm.formUser" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="(item, idx) in userFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled ="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER"
|
||||
label="表单内部门字段"
|
||||
prop="formDept"
|
||||
>
|
||||
<el-select v-model="configForm.formDept" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="(item, idx) in deptFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled ="!item.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="
|
||||
configForm.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
configForm.candidateStrategy ==
|
||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
|
||||
configForm.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
|
||||
"
|
||||
:label="deptLevelLabel!"
|
||||
prop="deptLevel"
|
||||
span="24"
|
||||
>
|
||||
<el-select v-model="configForm.deptLevel" clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- TODO @jason:后续要支持选择已经存好的表达式 -->
|
||||
<el-form-item
|
||||
v-if="configForm.candidateStrategy === CandidateStrategy.EXPRESSION"
|
||||
@ -184,14 +209,7 @@
|
||||
:key="index"
|
||||
class="flex items-center"
|
||||
>
|
||||
<el-radio
|
||||
:value="item.value"
|
||||
:label="item.value"
|
||||
:disabled="
|
||||
item.value !== ApproveMethodType.RANDOM_SELECT_ONE_APPROVE &&
|
||||
notAllowedMultiApprovers
|
||||
"
|
||||
>
|
||||
<el-radio :value="item.value" :label="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
<el-form-item prop="approveRatio">
|
||||
@ -481,6 +499,8 @@ const deptLevelLabel = computed(() => {
|
||||
let label = '部门负责人来源'
|
||||
if (configForm.value.candidateStrategy == CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) {
|
||||
label = label + '(指定部门向上)'
|
||||
} else if (configForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER) {
|
||||
label = label + '(表单内部门向上)'
|
||||
} else {
|
||||
label = label + '(发起人部门向上)'
|
||||
}
|
||||
@ -495,9 +515,16 @@ const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.USER_
|
||||
// 激活的 Tab 标签页
|
||||
const activeTabName = ref('user')
|
||||
// 表单字段权限设置
|
||||
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission(
|
||||
FieldPermissionType.READ
|
||||
)
|
||||
const { formType, fieldsPermissionConfig, formFieldOptions, getNodeConfigFormFields } =
|
||||
useFormFieldsPermission(FieldPermissionType.READ)
|
||||
// 表单内用户字段选项, 必须是必填和用户选择器
|
||||
const userFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'UserSelect')
|
||||
})
|
||||
// 表单内部门字段选项, 必须是必填和部门选择器
|
||||
const deptFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'DeptSelect')
|
||||
})
|
||||
// 操作按钮设置
|
||||
const { buttonsSetting, btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } =
|
||||
useButtonsSetting()
|
||||
@ -511,6 +538,8 @@ const formRules = reactive({
|
||||
roleIds: [{ required: true, message: '角色不能为空', trigger: 'change' }],
|
||||
deptIds: [{ required: true, message: '部门不能为空', trigger: 'change' }],
|
||||
userGroups: [{ required: true, message: '用户组不能为空', trigger: 'change' }],
|
||||
formUser: [{ required: true, message: '表单内用户字段不能为空', trigger: 'change' }],
|
||||
formDept: [{ required: true, message: '表单内部门字段不能为空', trigger: 'change' }],
|
||||
postIds: [{ required: true, message: '岗位不能为空', trigger: 'change' }],
|
||||
expression: [{ required: true, message: '流程表达式不能为空', trigger: 'blur' }],
|
||||
approveMethod: [{ required: true, message: '多人审批方式不能为空', trigger: 'change' }],
|
||||
@ -537,8 +566,7 @@ const {
|
||||
getShowText
|
||||
} = useNodeForm(NodeType.USER_TASK_NODE)
|
||||
const configForm = tempConfigForm as Ref<UserTaskFormType>
|
||||
// 不允许多人审批
|
||||
const notAllowedMultiApprovers = ref(false)
|
||||
|
||||
// 改变审批人设置策略
|
||||
const changeCandidateStrategy = () => {
|
||||
configForm.value.userIds = []
|
||||
@ -547,30 +575,11 @@ const changeCandidateStrategy = () => {
|
||||
configForm.value.postIds = []
|
||||
configForm.value.userGroups = []
|
||||
configForm.value.deptLevel = 1
|
||||
configForm.value.formUser = ''
|
||||
configForm.value.formDept = ''
|
||||
configForm.value.approveMethod = ApproveMethodType.SEQUENTIAL_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.userIds &&
|
||||
configForm.value.userIds?.length <= 1 &&
|
||||
configForm.value.candidateStrategy === CandidateStrategy.USER
|
||||
) {
|
||||
configForm.value.approveMethod = ApproveMethodType.RANDOM_SELECT_ONE_APPROVE
|
||||
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
|
||||
notAllowedMultiApprovers.value = true
|
||||
} else {
|
||||
notAllowedMultiApprovers.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 审批方式改变
|
||||
const approveMethodChanged = () => {
|
||||
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS
|
||||
@ -579,7 +588,7 @@ const approveMethodChanged = () => {
|
||||
}
|
||||
formRef.value.clearValidate('approveRatio')
|
||||
}
|
||||
// 审批拒绝 可回退的节点
|
||||
// 审批拒绝 可退回的节点
|
||||
const returnTaskList = ref<SimpleFlowNode[]>([])
|
||||
// 审批人超时未处理设置
|
||||
const {
|
||||
@ -666,11 +675,6 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
|
||||
configForm.value.candidateStrategy = node.candidateStrategy!
|
||||
// 解析候选人参数
|
||||
parseCandidateParam(node.candidateStrategy!, node?.candidateParam)
|
||||
if (configForm.value.userIds && configForm.value.userIds.length > 1) {
|
||||
notAllowedMultiApprovers.value = true
|
||||
} else {
|
||||
notAllowedMultiApprovers.value = false
|
||||
}
|
||||
// 2.2 设置审批方式
|
||||
configForm.value.approveMethod = node.approveMethod!
|
||||
if (node.approveMethod == ApproveMethodType.APPROVE_BY_RATIO) {
|
||||
|
@ -1,11 +1,17 @@
|
||||
<template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !currentNode.showText },
|
||||
`${useTaskStatusClass(currentNode?.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div class="node-title-icon copy-task"><span class="iconfont icon-copy"></span></div>
|
||||
<input
|
||||
v-if="showInput"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
@ -24,9 +30,9 @@
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.COPY_TASK_NODE) }}
|
||||
</div>
|
||||
<Icon icon="ep:arrow-right-bold" />
|
||||
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
|
||||
</div>
|
||||
<div class="node-toolbar">
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon"
|
||||
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
|
||||
/></div>
|
||||
@ -34,15 +40,23 @@
|
||||
</div>
|
||||
|
||||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
<CopyTaskNodeConfig v-if="currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
<CopyTaskNodeConfig
|
||||
v-if="!readonly && currentNode"
|
||||
ref="nodeSetting"
|
||||
:flow-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import { useNodeName2, useWatchNode } from '../node'
|
||||
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
|
||||
import CopyTaskNodeConfig from '../nodes-config/CopyTaskNodeConfig.vue'
|
||||
defineOptions({
|
||||
name: 'CopyTaskNode'
|
||||
@ -57,7 +71,8 @@ const props = defineProps({
|
||||
const emits = defineEmits<{
|
||||
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||
}>()
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
// 监控节点的变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 节点名称编辑
|
||||
@ -66,6 +81,9 @@ const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.
|
||||
const nodeSetting = ref()
|
||||
// 打开节点配置
|
||||
const openNodeConfig = () => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
nodeSetting.value.showCopyTaskNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
}
|
||||
|
@ -1,13 +1,102 @@
|
||||
<template>
|
||||
<div class="end-node-wrapper">
|
||||
<div class="end-node-box">
|
||||
<div class="end-node-box cursor-pointer" :class="`${useTaskStatusClass(currentNode?.activityStatus)}`" @click="nodeClick">
|
||||
<span class="node-fixed-name" title="结束">结束</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog title="审批信息" v-model="dialogVisible" width="1000px" append-to-body>
|
||||
<el-row>
|
||||
<el-table
|
||||
:data="processInstanceInfos"
|
||||
size="small"
|
||||
border
|
||||
header-cell-class-name="table-header-gray"
|
||||
>
|
||||
<el-table-column
|
||||
label="序号"
|
||||
header-align="center"
|
||||
align="center"
|
||||
type="index"
|
||||
width="50"
|
||||
/>
|
||||
<el-table-column
|
||||
label="发起人"
|
||||
prop="assigneeUser.nickname"
|
||||
min-width="100"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column label="部门" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="开始时间"
|
||||
prop="createTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="结束时间"
|
||||
prop="endTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" min-width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode } from '../consts'
|
||||
import { useWatchNode, useTaskStatusClass } from '../node'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
defineOptions({
|
||||
name: 'EndEventNode'
|
||||
})
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
// 监控节点变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
const processInstance = inject<Ref<any>>('processInstance')
|
||||
// 审批信息的弹窗显示,用于只读模式
|
||||
const dialogVisible = ref(false) // 弹窗可见性
|
||||
const processInstanceInfos = ref<any[]>([]) // 流程的审批信息
|
||||
|
||||
const nodeClick = () => {
|
||||
if (readonly) {
|
||||
if(processInstance && processInstance.value){
|
||||
processInstanceInfos.value = [
|
||||
{
|
||||
assigneeUser: processInstance.value.startUser,
|
||||
createTime: processInstance.value.startTime,
|
||||
endTime: processInstance.value.endTime,
|
||||
status: processInstance.value.status,
|
||||
durationInMillis: processInstance.value.durationInMillis
|
||||
}
|
||||
]
|
||||
dialogVisible.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -1,7 +1,17 @@
|
||||
<template>
|
||||
<div class="branch-node-wrapper">
|
||||
<div class="branch-node-container">
|
||||
<div class="branch-node-add" @click="addCondition">添加条件</div>
|
||||
<div
|
||||
v-if="readonly"
|
||||
class="branch-node-readonly"
|
||||
:class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
|
||||
>
|
||||
<span class="iconfont icon-exclusive icon-size condition"></span>
|
||||
</div>
|
||||
<el-button v-else class="branch-node-add" color="#67c23a" @click="addCondition" plain
|
||||
>添加条件</el-button
|
||||
>
|
||||
|
||||
<div
|
||||
class="branch-node-item"
|
||||
v-for="(item, index) in currentNode.conditionNodes"
|
||||
@ -17,9 +27,15 @@
|
||||
</template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !item.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !item.showText },
|
||||
`${useTaskStatusClass(item.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="showInputs[index]">
|
||||
<div v-if="!readonly && showInputs[index]">
|
||||
<input
|
||||
type="text"
|
||||
class="input-max-width editable-title-input"
|
||||
@ -39,7 +55,10 @@
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-toolbar" v-if="index + 1 !== currentNode.conditionNodes?.length">
|
||||
<div
|
||||
class="node-toolbar"
|
||||
v-if="!readonly && index + 1 !== currentNode.conditionNodes?.length"
|
||||
>
|
||||
<div class="toolbar-icon">
|
||||
<Icon
|
||||
color="#0089ff"
|
||||
@ -65,7 +84,7 @@
|
||||
<Icon icon="ep:arrow-right" />
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-model:child-node="item.childNode" />
|
||||
<NodeHandler v-model:child-node="item.childNode" :current-node="item" />
|
||||
</div>
|
||||
</div>
|
||||
<ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
|
||||
@ -78,7 +97,11 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -87,6 +110,7 @@ import NodeHandler from '../NodeHandler.vue'
|
||||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { getDefaultConditionNodeName } from '../utils'
|
||||
import { useTaskStatusClass } from '../node'
|
||||
import { generateUUID } from '@/utils'
|
||||
import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
@ -94,10 +118,6 @@ defineOptions({
|
||||
name: 'ExclusiveNode'
|
||||
})
|
||||
const props = defineProps({
|
||||
// parentNode : {
|
||||
// type: Object as () => SimpleFlowNode,
|
||||
// required: true
|
||||
// },
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
@ -113,10 +133,9 @@ const emits = defineEmits<{
|
||||
nodeType: number
|
||||
]
|
||||
}>()
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
// const conditionNodes = computed(() => currentNode.value.conditionNodes);
|
||||
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
(newValue) => {
|
||||
@ -139,6 +158,9 @@ const clickEvent = (index: number) => {
|
||||
}
|
||||
|
||||
const conditionNodeConfig = (nodeId: string) => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
const conditionNode = proxy.$refs[nodeId][0]
|
||||
conditionNode.open()
|
||||
}
|
||||
@ -193,7 +215,7 @@ const recursiveFindParentNode = (
|
||||
node: SimpleFlowNode,
|
||||
nodeType: number
|
||||
) => {
|
||||
if (!node || node.type === NodeType.START_EVENT_NODE) {
|
||||
if (!node || node.type === NodeType.START_USER_NODE) {
|
||||
return
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
|
@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<div class="branch-node-wrapper">
|
||||
<div class="branch-node-container">
|
||||
<div
|
||||
v-if="readonly"
|
||||
class="branch-node-readonly"
|
||||
:class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
|
||||
>
|
||||
<span class="iconfont icon-inclusive icon-size inclusive"></span>
|
||||
</div>
|
||||
<el-button v-else class="branch-node-add" color="#345da2" @click="addCondition" plain
|
||||
>添加条件</el-button
|
||||
>
|
||||
<div
|
||||
class="branch-node-item"
|
||||
v-for="(item, index) in currentNode.conditionNodes"
|
||||
:key="index"
|
||||
>
|
||||
<template v-if="index == 0">
|
||||
<div class="branch-line-first-top"> </div>
|
||||
<div class="branch-line-first-bottom"></div>
|
||||
</template>
|
||||
<template v-if="index + 1 == currentNode.conditionNodes?.length">
|
||||
<div class="branch-line-last-top"></div>
|
||||
<div class="branch-line-last-bottom"></div>
|
||||
</template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !item.showText },
|
||||
`${useTaskStatusClass(item.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="showInputs[index]">
|
||||
<input
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent(index)"
|
||||
v-mountedFocus
|
||||
v-model="item.name"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="branch-title" @click="clickEvent(index)"> {{ item.name }} </div>
|
||||
</div>
|
||||
<div class="branch-node-content" @click="conditionNodeConfig(item.id)">
|
||||
<div class="branch-node-text" :title="item.showText" v-if="item.showText">
|
||||
{{ item.showText }}
|
||||
</div>
|
||||
<div class="branch-node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="node-toolbar"
|
||||
v-if="!readonly && index + 1 !== currentNode.conditionNodes?.length"
|
||||
>
|
||||
<div class="toolbar-icon">
|
||||
<Icon
|
||||
color="#0089ff"
|
||||
icon="ep:circle-close-filled"
|
||||
:size="18"
|
||||
@click="deleteCondition(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="branch-node-move move-node-left"
|
||||
v-if="!readonly && index != 0 && index + 1 !== currentNode.conditionNodes?.length"
|
||||
@click="moveNode(index, -1)"
|
||||
>
|
||||
<Icon icon="ep:arrow-left" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="branch-node-move move-node-right"
|
||||
v-if="
|
||||
!readonly &&
|
||||
currentNode.conditionNodes &&
|
||||
index < currentNode.conditionNodes.length - 2
|
||||
"
|
||||
@click="moveNode(index, 1)"
|
||||
>
|
||||
<Icon icon="ep:arrow-right" />
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-model:child-node="item.childNode" :current-node="item" />
|
||||
</div>
|
||||
</div>
|
||||
<ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
|
||||
<!-- 递归显示子节点 -->
|
||||
<ProcessNodeTree
|
||||
v-if="item && item.childNode"
|
||||
:parent-node="item"
|
||||
v-model:flow-node="item.childNode"
|
||||
@find:recursive-find-parent-node="recursiveFindParentNode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { useTaskStatusClass } from '../node'
|
||||
import { getDefaultInclusiveConditionNodeName } from '../utils'
|
||||
import { generateUUID } from '@/utils'
|
||||
import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
defineOptions({
|
||||
name: 'InclusiveNode'
|
||||
})
|
||||
const props = defineProps({
|
||||
flowNode: {
|
||||
type: Object as () => SimpleFlowNode,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
// 定义事件,更新父组件
|
||||
const emits = defineEmits<{
|
||||
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: number]
|
||||
'find:recursiveFindParentNode': [
|
||||
nodeList: SimpleFlowNode[],
|
||||
curentNode: SimpleFlowNode,
|
||||
nodeType: number
|
||||
]
|
||||
}>()
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
(newValue) => {
|
||||
currentNode.value = newValue
|
||||
}
|
||||
)
|
||||
|
||||
const showInputs = ref<boolean[]>([])
|
||||
// 失去焦点
|
||||
const blurEvent = (index: number) => {
|
||||
showInputs.value[index] = false
|
||||
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
||||
conditionNode.name =
|
||||
conditionNode.name || getDefaultInclusiveConditionNodeName(index, conditionNode.defaultFlow)
|
||||
}
|
||||
|
||||
// 点击条件名称
|
||||
const clickEvent = (index: number) => {
|
||||
showInputs.value[index] = true
|
||||
}
|
||||
|
||||
const conditionNodeConfig = (nodeId: string) => {
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
const conditionNode = proxy.$refs[nodeId][0]
|
||||
conditionNode.open()
|
||||
}
|
||||
|
||||
// 新增条件
|
||||
const addCondition = () => {
|
||||
const conditionNodes = currentNode.value.conditionNodes
|
||||
if (conditionNodes) {
|
||||
const len = conditionNodes.length
|
||||
let lastIndex = len - 1
|
||||
const conditionData: SimpleFlowNode = {
|
||||
id: 'Flow_' + generateUUID(),
|
||||
name: '包容条件' + len,
|
||||
showText: '',
|
||||
type: NodeType.CONDITION_NODE,
|
||||
childNode: undefined,
|
||||
conditionNodes: [],
|
||||
conditionType: 1,
|
||||
defaultFlow: false
|
||||
}
|
||||
conditionNodes.splice(lastIndex, 0, conditionData)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除条件
|
||||
const deleteCondition = (index: number) => {
|
||||
const conditionNodes = currentNode.value.conditionNodes
|
||||
if (conditionNodes) {
|
||||
conditionNodes.splice(index, 1)
|
||||
if (conditionNodes.length == 1) {
|
||||
const childNode = currentNode.value.childNode
|
||||
// 更新此节点为后续孩子节点
|
||||
emits('update:modelValue', childNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动节点
|
||||
const moveNode = (index: number, to: number) => {
|
||||
// -1 :向左 1: 向右
|
||||
if (currentNode.value.conditionNodes) {
|
||||
currentNode.value.conditionNodes[index] = currentNode.value.conditionNodes.splice(
|
||||
index + to,
|
||||
1,
|
||||
currentNode.value.conditionNodes[index]
|
||||
)[0]
|
||||
}
|
||||
}
|
||||
// 递归从父节点中查询匹配的节点
|
||||
const recursiveFindParentNode = (
|
||||
nodeList: SimpleFlowNode[],
|
||||
node: SimpleFlowNode,
|
||||
nodeType: number
|
||||
) => {
|
||||
if (!node || node.type === NodeType.START_USER_NODE) {
|
||||
return
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
nodeList.push(node)
|
||||
}
|
||||
// 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点条件分支节点(NodeType.INCLUSIVE_BRANCH_NODE) 继续查找
|
||||
emits('find:parentNode', nodeList, nodeType)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -1,7 +1,16 @@
|
||||
<template>
|
||||
<div class="branch-node-wrapper">
|
||||
<div class="branch-node-container">
|
||||
<div class="branch-node-add" @click="addCondition">添加分支</div>
|
||||
<div
|
||||
v-if="readonly"
|
||||
class="branch-node-readonly"
|
||||
:class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
|
||||
>
|
||||
<span class="iconfont icon-parallel icon-size parallel"></span>
|
||||
</div>
|
||||
<el-button v-else class="branch-node-add" color="#626aef" @click="addCondition" plain
|
||||
>添加分支</el-button
|
||||
>
|
||||
<div
|
||||
class="branch-node-item"
|
||||
v-for="(item, index) in currentNode.conditionNodes"
|
||||
@ -17,7 +26,7 @@
|
||||
</template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box">
|
||||
<div class="node-box" :class="`${useTaskStatusClass(item.activityStatus)}`">
|
||||
<div class="branch-node-title-container">
|
||||
<div v-if="showInputs[index]">
|
||||
<input
|
||||
@ -39,7 +48,7 @@
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-toolbar">
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon">
|
||||
<Icon
|
||||
color="#0089ff"
|
||||
@ -49,20 +58,8 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div
|
||||
class="branch-node-move move-node-left"
|
||||
v-if="index != 0 && index + 1 !== currentNode.conditionNodes?.length" @click="moveNode(index, -1)">
|
||||
<Icon icon="ep:arrow-left" />
|
||||
</div> -->
|
||||
|
||||
<!-- <div
|
||||
class="branch-node-move move-node-right"
|
||||
v-if="currentNode.conditionNodes && index < currentNode.conditionNodes.length - 2"
|
||||
@click="moveNode(index, 1)">
|
||||
<Icon icon="ep:arrow-right" />
|
||||
</div> -->
|
||||
</div>
|
||||
<NodeHandler v-model:child-node="item.childNode" />
|
||||
<NodeHandler v-model:child-node="item.childNode" :current-node="item" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 递归显示子节点 -->
|
||||
@ -74,7 +71,11 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -82,8 +83,8 @@
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { useTaskStatusClass } from '../node'
|
||||
import { generateUUID } from '@/utils'
|
||||
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
defineOptions({
|
||||
name: 'ParallelNode'
|
||||
@ -106,6 +107,8 @@ const emits = defineEmits<{
|
||||
}>()
|
||||
|
||||
const currentNode = ref<SimpleFlowNode>(props.flowNode)
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
|
||||
watch(
|
||||
() => props.flowNode,
|
||||
@ -169,7 +172,7 @@ const recursiveFindParentNode = (
|
||||
node: SimpleFlowNode,
|
||||
nodeType: number
|
||||
) => {
|
||||
if (!node || node.type === NodeType.START_EVENT_NODE) {
|
||||
if (!node || node.type === NodeType.START_USER_NODE) {
|
||||
return
|
||||
}
|
||||
if (node.type === nodeType) {
|
||||
|
@ -1,7 +1,13 @@
|
||||
<template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !currentNode.showText },
|
||||
`${useTaskStatusClass(currentNode?.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div class="node-title-icon start-user"
|
||||
><span class="iconfont icon-start-user"></span
|
||||
@ -19,27 +25,88 @@
|
||||
{{ currentNode.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-content" @click="openNodeConfig">
|
||||
<div class="node-content" @click="nodeClick">
|
||||
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
|
||||
{{ currentNode.showText }}
|
||||
</div>
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.START_USER_NODE) }}
|
||||
</div>
|
||||
<Icon icon="ep:arrow-right-bold" />
|
||||
<Icon icon="ep:arrow-right-bold" v-if="!readonly" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<StartUserNodeConfig v-if="currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
<StartUserNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
|
||||
<!-- 审批记录 -->
|
||||
<el-dialog
|
||||
:title="dialogTitle || '审批记录'"
|
||||
v-model="dialogVisible"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
>
|
||||
<el-row>
|
||||
<el-table :data="selectTasks" size="small" border header-cell-class-name="table-header-gray">
|
||||
<el-table-column
|
||||
label="序号"
|
||||
header-align="center"
|
||||
align="center"
|
||||
type="index"
|
||||
width="50"
|
||||
/>
|
||||
<el-table-column label="审批人" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="部门" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="开始时间"
|
||||
prop="createTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="结束时间"
|
||||
prop="endTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" min-width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="审批建议" prop="reason" min-width="120" />
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import { useWatchNode, useNodeName2 } from '../node'
|
||||
import { useWatchNode, useNodeName2, useTaskStatusClass } from '../node'
|
||||
import { SimpleFlowNode, NODE_DEFAULT_TEXT, NodeType } from '../consts'
|
||||
import StartUserNodeConfig from '../nodes-config/StartUserNodeConfig.vue'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
defineOptions({
|
||||
name: 'StartEventNode'
|
||||
})
|
||||
@ -49,6 +116,8 @@ const props = defineProps({
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
const readonly = inject<Boolean>('readonly') // 是否只读
|
||||
const tasks = inject<Ref<any[]>>('tasks')
|
||||
// 定义事件,更新父组件。
|
||||
const emits = defineEmits<{
|
||||
'update:modelValue': [node: SimpleFlowNode | undefined]
|
||||
@ -59,11 +128,27 @@ const currentNode = useWatchNode(props)
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.START_USER_NODE)
|
||||
|
||||
const nodeSetting = ref()
|
||||
// 打开节点配置
|
||||
const openNodeConfig = () => {
|
||||
// 把当前节点传递给配置组件
|
||||
nodeSetting.value.showStartUserNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
//
|
||||
const nodeClick = () => {
|
||||
if (readonly) {
|
||||
// 只读模式,弹窗显示任务信息
|
||||
if (tasks && tasks.value) {
|
||||
dialogTitle.value = currentNode.value.name
|
||||
selectTasks.value = tasks.value.filter(
|
||||
(item: any) => item?.taskDefinitionKey === currentNode.value.id
|
||||
)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
} else {
|
||||
// 编辑模式,打开节点配置、把当前节点传递给配置组件
|
||||
nodeSetting.value.showStartUserNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
}
|
||||
}
|
||||
|
||||
// 任务的弹窗显示,用于只读模式
|
||||
const dialogVisible = ref(false) // 弹窗可见性
|
||||
const dialogTitle = ref<string | undefined>(undefined) // 弹窗标题
|
||||
const selectTasks = ref<any[] | undefined>([]) // 选中的任务数组
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -1,11 +1,17 @@
|
||||
<template>
|
||||
<div class="node-wrapper">
|
||||
<div class="node-container">
|
||||
<div class="node-box" :class="{ 'node-config-error': !currentNode.showText }">
|
||||
<div
|
||||
class="node-box"
|
||||
:class="[
|
||||
{ 'node-config-error': !currentNode.showText },
|
||||
`${useTaskStatusClass(currentNode?.activityStatus)}`
|
||||
]"
|
||||
>
|
||||
<div class="node-title-container">
|
||||
<div class="node-title-icon user-task"><span class="iconfont icon-approve"></span></div>
|
||||
<input
|
||||
v-if="showInput"
|
||||
v-if="!readonly && showInput"
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="blurEvent()"
|
||||
@ -17,23 +23,27 @@
|
||||
{{ currentNode.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="node-content" @click="openNodeConfig">
|
||||
<div class="node-content" @click="nodeClick">
|
||||
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
|
||||
{{ currentNode.showText }}
|
||||
</div>
|
||||
<div class="node-text" v-else>
|
||||
{{ NODE_DEFAULT_TEXT.get(NodeType.USER_TASK_NODE) }}
|
||||
</div>
|
||||
<Icon icon="ep:arrow-right-bold" />
|
||||
<Icon icon="ep:arrow-right-bold" v-if="!readonly" />
|
||||
</div>
|
||||
<div class="node-toolbar">
|
||||
<div v-if="!readonly" class="node-toolbar">
|
||||
<div class="toolbar-icon"
|
||||
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
|
||||
/></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->
|
||||
<NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
|
||||
<NodeHandler
|
||||
v-if="currentNode"
|
||||
v-model:child-node="currentNode.childNode"
|
||||
:current-node="currentNode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<UserTaskNodeConfig
|
||||
@ -42,12 +52,69 @@
|
||||
:flow-node="currentNode"
|
||||
@find:return-task-nodes="findReturnTaskNodes"
|
||||
/>
|
||||
<!-- 审批记录 -->
|
||||
<el-dialog
|
||||
:title="dialogTitle || '审批记录'"
|
||||
v-model="dialogVisible"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
>
|
||||
<el-row>
|
||||
<el-table :data="selectTasks" size="small" border header-cell-class-name="table-header-gray">
|
||||
<el-table-column
|
||||
label="序号"
|
||||
header-align="center"
|
||||
align="center"
|
||||
type="index"
|
||||
width="50"
|
||||
/>
|
||||
<el-table-column label="审批人" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="部门" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="开始时间"
|
||||
prop="createTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="结束时间"
|
||||
prop="endTime"
|
||||
min-width="140"
|
||||
/>
|
||||
<el-table-column align="center" label="审批状态" prop="status" min-width="90">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="审批建议" prop="reason" min-width="120" />
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
|
||||
import { useWatchNode, useNodeName2 } from '../node'
|
||||
import { useWatchNode, useNodeName2, useTaskStatusClass } from '../node'
|
||||
import NodeHandler from '../NodeHandler.vue'
|
||||
import UserTaskNodeConfig from '../nodes-config/UserTaskNodeConfig.vue'
|
||||
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
defineOptions({
|
||||
name: 'UserTaskNode'
|
||||
})
|
||||
@ -61,22 +128,36 @@ const emits = defineEmits<{
|
||||
'update:flowNode': [node: SimpleFlowNode | undefined]
|
||||
'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: NodeType]
|
||||
}>()
|
||||
|
||||
// 是否只读
|
||||
const readonly = inject<Boolean>('readonly')
|
||||
const tasks = inject<Ref<any[]>>('tasks')
|
||||
// 监控节点变化
|
||||
const currentNode = useWatchNode(props)
|
||||
// 节点名称编辑
|
||||
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.START_USER_NODE)
|
||||
const nodeSetting = ref()
|
||||
// 打开节点配置
|
||||
const openNodeConfig = () => {
|
||||
// 把当前节点传递给配置组件
|
||||
nodeSetting.value.showUserTaskNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
|
||||
const nodeClick = () => {
|
||||
if (readonly) {
|
||||
if (tasks && tasks.value) {
|
||||
dialogTitle.value = currentNode.value.name
|
||||
// 只读模式,弹窗显示任务信息
|
||||
selectTasks.value = tasks.value.filter(
|
||||
(item: any) => item?.taskDefinitionKey === currentNode.value.id
|
||||
)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
} else {
|
||||
// 编辑模式,打开节点配置、把当前节点传递给配置组件
|
||||
nodeSetting.value.showUserTaskNodeConfig(currentNode.value)
|
||||
nodeSetting.value.openDrawer()
|
||||
}
|
||||
}
|
||||
|
||||
const deleteNode = () => {
|
||||
emits('update:flowNode', currentNode.value.childNode)
|
||||
}
|
||||
|
||||
// 查找可以驳回用户节点
|
||||
const findReturnTaskNodes = (
|
||||
matchNodeList: SimpleFlowNode[] // 匹配的节点
|
||||
@ -84,5 +165,10 @@ const findReturnTaskNodes = (
|
||||
// 从父节点查找
|
||||
emits('find:parentNode', matchNodeList, NodeType.USER_TASK_NODE)
|
||||
}
|
||||
|
||||
// 任务的弹窗显示,用于只读模式
|
||||
const dialogVisible = ref(false) // 弹窗可见性
|
||||
const dialogTitle = ref<string | undefined>(undefined) // 弹窗标题
|
||||
const selectTasks = ref<any[] | undefined>([]) // 选中的任务数组
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -8,6 +8,14 @@ export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean
|
||||
return '条件' + (index + 1)
|
||||
}
|
||||
|
||||
// 获取包容分支条件节点默认的名称
|
||||
export const getDefaultInclusiveConditionNodeName = (index: number, defaultFlow: boolean | undefined): string => {
|
||||
if (defaultFlow) {
|
||||
return '其它情况'
|
||||
}
|
||||
return '包容条件' + (index + 1)
|
||||
}
|
||||
|
||||
export const convertTimeUnit = (strTimeUnit: string) => {
|
||||
if (strTimeUnit === 'M') {
|
||||
return TimeUnitType.MINUTE
|
||||
|
File diff suppressed because it is too large
Load Diff
152
src/components/UserSelectForm/index.vue
Normal file
152
src/components/UserSelectForm/index.vue
Normal file
@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="人员选择" width="800">
|
||||
<el-row class="gap2" v-loading="formLoading">
|
||||
<el-col :span="6">
|
||||
<ContentWrap class="h-1/1">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="deptTree"
|
||||
:expand-on-click-node="false"
|
||||
:props="defaultProps"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
node-key="id"
|
||||
@node-click="handleNodeClick"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
<el-col :span="17">
|
||||
<el-transfer
|
||||
v-model="selectedUserIdList"
|
||||
:titles="['未选', '已选']"
|
||||
filterable
|
||||
filter-placeholder="搜索成员"
|
||||
:data="transferUserList"
|
||||
:props="{ label: 'nickname', key: 'id' }"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<el-button
|
||||
:disabled="formLoading || !selectedUserIdList?.length"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>
|
||||
确 定
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { defaultProps, findTreeNode, handleTree } from '@/utils/tree'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'UserSelectForm' })
|
||||
const emit = defineEmits<{
|
||||
confirm: [id: any, userList: any[]]
|
||||
}>()
|
||||
const { t } = useI18n() // 国际
|
||||
const message = useMessage() // 消息弹窗
|
||||
const deptTree = ref<Tree[]>([]) // 部门树形结构化
|
||||
const userList = ref<UserApi.UserVO[]>([]) // 所有用户列表
|
||||
const filteredUserList = ref<UserApi.UserVO[]>([]) // 当前部门过滤后的用户列表
|
||||
const selectedUserIdList: any = ref([]) // 选中的用户列表
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const activityId = ref()
|
||||
|
||||
/** 计算属性:合并已选择的用户和当前部门过滤后的用户 */
|
||||
const transferUserList = computed(() => {
|
||||
// 1.1 获取所有已选择的用户
|
||||
const selectedUsers = userList.value.filter((user: any) =>
|
||||
selectedUserIdList.value.includes(user.id)
|
||||
)
|
||||
|
||||
// 1.2 获取当前部门过滤后的未选择用户
|
||||
const filteredUnselectedUsers = filteredUserList.value.filter(
|
||||
(user: any) => !selectedUserIdList.value.includes(user.id)
|
||||
)
|
||||
|
||||
// 2. 合并并去重
|
||||
return [...selectedUsers, ...filteredUnselectedUsers]
|
||||
})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number, selectedList?: any[]) => {
|
||||
activityId.value = id
|
||||
resetForm()
|
||||
|
||||
// 加载部门、用户列表
|
||||
deptTree.value = handleTree(await DeptApi.getSimpleDeptList())
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
|
||||
// 初始状态下,过滤列表等于所有用户列表
|
||||
filteredUserList.value = [...userList.value]
|
||||
selectedUserIdList.value = selectedList?.map((item: any) => item.id) || []
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
/** 获取部门过滤后的用户列表 */
|
||||
const getUserList = async (deptId?: number) => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
// @ts-ignore
|
||||
// TODO @芋艿:替换到 simple List 暂不支持 deptId 过滤
|
||||
// TODO @Zqqq:这个,可以使用前端过滤么?通过 deptList 获取到 deptId 子节点,然后去 userList
|
||||
const data = await UserApi.getUserPage({ pageSize: 100, pageNo: 1, deptId })
|
||||
// 更新过滤后的用户列表
|
||||
filteredUserList.value = data.list
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交选择 */
|
||||
const submitForm = async () => {
|
||||
try {
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogVisible.value = false
|
||||
// 从所有用户列表中筛选出已选择的用户
|
||||
const emitUserList = userList.value.filter((user: any) =>
|
||||
selectedUserIdList.value.includes(user.id)
|
||||
)
|
||||
// 发送操作成功的事件
|
||||
emit('confirm', activityId.value, emitUserList)
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
deptTree.value = []
|
||||
userList.value = []
|
||||
filteredUserList.value = []
|
||||
selectedUserIdList.value = []
|
||||
}
|
||||
|
||||
/** 处理部门被点击 */
|
||||
const handleNodeClick = (row: { [key: string]: any }) => {
|
||||
getUserList(row.id)
|
||||
}
|
||||
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep() {
|
||||
.el-transfer {
|
||||
display: flex;
|
||||
}
|
||||
.el-transfer__buttons {
|
||||
display: flex !important;
|
||||
flex-direction: column-reverse;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
.el-transfer__button:nth-child(2) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
File diff suppressed because it is too large
Load Diff
@ -1211,6 +1211,76 @@
|
||||
"isAttr": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AssignStartUserHandlerType",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RejectHandlerType",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RejectReturnTaskId",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "String",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AssignEmptyHandlerType",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AssignEmptyUserIds",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "String",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"emumerations": []
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="process-panel__container" :style="{ width: `${width}px` }">
|
||||
<div class="process-panel__container" :style="{ width: `${width}px`,maxHeight: '700px' }">
|
||||
<el-collapse v-model="activeTab">
|
||||
<el-collapse-item name="base">
|
||||
<!-- class="panel-tab__title" -->
|
||||
@ -54,6 +54,10 @@
|
||||
<template #title><Icon icon="ep:promotion" />其他</template>
|
||||
<element-other-config :id="elementId" />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="customConfig" v-if="elementType.indexOf('Task') !== -1" key="customConfig">
|
||||
<template #title><Icon icon="ep:circle-plus-filled" />自定义配置</template>
|
||||
<element-custom-config :id="elementId" :type="elementType" />
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -0,0 +1,283 @@
|
||||
<!-- UserTask 自定义配置:
|
||||
1. 审批人与提交人为同一人时
|
||||
2. 审批人拒绝时
|
||||
3. 审批人为空时
|
||||
-->
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<el-divider content-position="left">审批人拒绝时</el-divider>
|
||||
<el-form-item prop="rejectHandlerType">
|
||||
<el-radio-group
|
||||
v-model="rejectHandlerType"
|
||||
:disabled="returnTaskList.length === 0"
|
||||
@change="updateRejectHandlerType"
|
||||
>
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index">
|
||||
<el-radio :key="item.value" :value="item.value" :label="item.label" />
|
||||
</div>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="rejectHandlerType == RejectHandlerType.RETURN_USER_TASK"
|
||||
label="驳回节点"
|
||||
prop="returnNodeId"
|
||||
>
|
||||
<el-select v-model="returnNodeId" clearable style="width: 100%" @change="updateReturnNodeId">
|
||||
<el-option
|
||||
v-for="item in returnTaskList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">审批人为空时</el-divider>
|
||||
<el-form-item prop="assignEmptyHandlerType">
|
||||
<el-radio-group v-model="assignEmptyHandlerType" @change="updateAssignEmptyHandlerType">
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index">
|
||||
<el-radio :key="item.value" :value="item.value" :label="item.label" />
|
||||
</div>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="assignEmptyHandlerType == AssignEmptyHandlerType.ASSIGN_USER"
|
||||
label="指定用户"
|
||||
prop="assignEmptyHandlerUserIds"
|
||||
span="24"
|
||||
>
|
||||
<el-select
|
||||
v-model="assignEmptyUserIds"
|
||||
clearable
|
||||
multiple
|
||||
style="width: 100%"
|
||||
@change="updateAssignEmptyUserIds"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">审批人与提交人为同一人时</el-divider>
|
||||
<el-radio-group v-model="assignStartUserHandlerType" @change="updateAssignStartUserHandlerType">
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES" :key="index">
|
||||
<el-radio :key="item.value" :value="item.value" :label="item.label" />
|
||||
</div>
|
||||
</div>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
ASSIGN_START_USER_HANDLER_TYPES,
|
||||
RejectHandlerType,
|
||||
REJECT_HANDLER_TYPES,
|
||||
ASSIGN_EMPTY_HANDLER_TYPES,
|
||||
AssignEmptyHandlerType
|
||||
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'ElementCustomConfig' })
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
type: String
|
||||
})
|
||||
const prefix = inject('prefix')
|
||||
|
||||
// 审批人与提交人为同一人时
|
||||
const assignStartUserHandlerTypeEl = ref()
|
||||
const assignStartUserHandlerType = ref()
|
||||
|
||||
// 审批人拒绝时
|
||||
const rejectHandlerTypeEl = ref()
|
||||
const rejectHandlerType = ref()
|
||||
const returnNodeIdEl = ref()
|
||||
const returnNodeId = ref()
|
||||
const returnTaskList = ref([])
|
||||
|
||||
// 审批人为空时
|
||||
const assignEmptyHandlerTypeEl = ref()
|
||||
const assignEmptyHandlerType = ref()
|
||||
const assignEmptyUserIdsEl = ref()
|
||||
const assignEmptyUserIds = ref()
|
||||
|
||||
const elExtensionElements = ref()
|
||||
const otherExtensions = ref()
|
||||
const bpmnElement = ref()
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
|
||||
const resetCustomConfigList = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement
|
||||
|
||||
// 获取可回退的列表
|
||||
returnTaskList.value = findAllPredecessorsExcludingStart(
|
||||
bpmnElement.value.id,
|
||||
bpmnInstances().modeler
|
||||
)
|
||||
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnElement.value.businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
|
||||
|
||||
// 审批人与提交人为同一人时
|
||||
assignStartUserHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:AssignStartUserHandlerType`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, { value: 1 })
|
||||
assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value
|
||||
|
||||
// 审批人拒绝时
|
||||
rejectHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:RejectHandlerType`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 })
|
||||
rejectHandlerType.value = rejectHandlerTypeEl.value.value
|
||||
returnNodeIdEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:RejectReturnTaskId`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, { value: '' })
|
||||
returnNodeId.value = returnNodeIdEl.value.value
|
||||
|
||||
// 审批人为空时
|
||||
assignEmptyHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:AssignEmptyHandlerType`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, { value: 1 })
|
||||
assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value
|
||||
assignEmptyUserIdsEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) => ex.$type === `${prefix}:AssignEmptyUserIds`
|
||||
)?.[0] || bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, { value: '' })
|
||||
assignEmptyUserIds.value = assignEmptyUserIdsEl.value.value.split(',').map((item) => {
|
||||
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
|
||||
let num = Number(item)
|
||||
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER ? item : num
|
||||
})
|
||||
|
||||
// 保留剩余扩展元素,便于后面更新该元素对应属性
|
||||
otherExtensions.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex) =>
|
||||
ex.$type !== `${prefix}:AssignStartUserHandlerType` &&
|
||||
ex.$type !== `${prefix}:RejectHandlerType` &&
|
||||
ex.$type !== `${prefix}:RejectReturnTaskId` &&
|
||||
ex.$type !== `${prefix}:AssignEmptyHandlerType` &&
|
||||
ex.$type !== `${prefix}:AssignEmptyUserIds`
|
||||
) ?? []
|
||||
|
||||
// 更新元素扩展属性,避免后续报错
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateAssignStartUserHandlerType = () => {
|
||||
assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateRejectHandlerType = () => {
|
||||
rejectHandlerTypeEl.value.value = rejectHandlerType.value
|
||||
|
||||
returnNodeId.value = returnTaskList.value[0].id
|
||||
returnNodeIdEl.value.value = returnNodeId.value
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateReturnNodeId = () => {
|
||||
returnNodeIdEl.value.value = returnNodeId.value
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateAssignEmptyHandlerType = () => {
|
||||
assignEmptyHandlerTypeEl.value.value = assignEmptyHandlerType.value
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateAssignEmptyUserIds = () => {
|
||||
assignEmptyUserIdsEl.value.value = assignEmptyUserIds.value.toString()
|
||||
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...otherExtensions.value,
|
||||
assignStartUserHandlerTypeEl.value,
|
||||
rejectHandlerTypeEl.value,
|
||||
returnNodeIdEl.value,
|
||||
assignEmptyHandlerTypeEl.value,
|
||||
assignEmptyUserIdsEl.value
|
||||
]
|
||||
})
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length &&
|
||||
nextTick(() => {
|
||||
resetCustomConfigList()
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function findAllPredecessorsExcludingStart(elementId, modeler) {
|
||||
const elementRegistry = modeler.get('elementRegistry')
|
||||
const allConnections = elementRegistry.filter((element) => element.type === 'bpmn:SequenceFlow')
|
||||
const predecessors = new Set() // 使用 Set 来避免重复节点
|
||||
|
||||
// 检查是否是开始事件节点
|
||||
function isStartEvent(element) {
|
||||
return element.type === 'bpmn:StartEvent'
|
||||
}
|
||||
|
||||
function findPredecessorsRecursively(element) {
|
||||
// 获取与当前节点相连的所有连接
|
||||
const incomingConnections = allConnections.filter((connection) => connection.target === element)
|
||||
|
||||
incomingConnections.forEach((connection) => {
|
||||
const source = connection.source // 获取前置节点
|
||||
|
||||
// 只添加不是开始事件的前置节点
|
||||
if (!isStartEvent(source)) {
|
||||
predecessors.add(source.businessObject)
|
||||
// 递归查找前置节点
|
||||
findPredecessorsRecursively(source)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const targetElement = elementRegistry.get(elementId)
|
||||
if (targetElement) {
|
||||
findPredecessorsRecursively(targetElement)
|
||||
}
|
||||
|
||||
return Array.from(predecessors) // 返回前置节点数组
|
||||
}
|
||||
|
||||
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
// 获得用户列表
|
||||
userOptions.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
@ -268,9 +268,9 @@ const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
const resetFormList = () => {
|
||||
bpmnELement.value = bpmnInstances().bpmnElement
|
||||
formKey.value = bpmnELement.value.businessObject.formKey
|
||||
if (formKey.value?.length > 0) {
|
||||
formKey.value = parseInt(formKey.value)
|
||||
}
|
||||
// if (formKey.value?.length > 0) {
|
||||
// formKey.value = parseInt(formKey.value)
|
||||
// }
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnELement.value.businessObject.get('extensionElements') ||
|
||||
|
@ -80,7 +80,7 @@ const resetAttributesList = () => {
|
||||
otherExtensionList.value = [] // 其他扩展配置
|
||||
bpmnElementProperties.value =
|
||||
// bpmnElement.value.businessObject?.extensionElements?.filter((ex) => {
|
||||
bpmnElement.value.businessObject?.extensionElements?.values.filter((ex) => {
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter((ex) => {
|
||||
if (ex.$type !== `${prefix}:Properties`) {
|
||||
otherExtensionList.value.push(ex)
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ $--color-danger: #ff4d4f;
|
||||
/* 改变 icon 字体路径变量,必需 */
|
||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
|
||||
@import '~element-ui/packages/theme-chalk/src/index';
|
||||
@use '~element-ui/packages/theme-chalk/src/index';
|
||||
|
||||
.el-table td,
|
||||
.el-table th {
|
||||
|
@ -1,2 +1,117 @@
|
||||
@import './process-designer.scss';
|
||||
@import './process-panel.scss';
|
||||
@use './process-designer.scss';
|
||||
@use './process-panel.scss';
|
||||
|
||||
$success-color: #4eb819;
|
||||
$primary-color: #409EFF;
|
||||
$danger-color: #F56C6C;
|
||||
$cancel-color: #909399;
|
||||
|
||||
.process-viewer {
|
||||
position: relative;
|
||||
border: 1px solid #EFEFEF;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+') repeat!important;
|
||||
|
||||
.success-arrow {
|
||||
fill: $success-color;
|
||||
stroke: $success-color;
|
||||
}
|
||||
|
||||
.success-conditional {
|
||||
fill: white;
|
||||
stroke: $success-color;
|
||||
}
|
||||
|
||||
.success.djs-connection {
|
||||
.djs-visual path {
|
||||
stroke: $success-color!important;
|
||||
//marker-end: url(#sequenceflow-end-white-success)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.success.djs-connection.condition-expression {
|
||||
.djs-visual path {
|
||||
//marker-start: url(#conditional-flow-marker-white-success)!important;
|
||||
}
|
||||
}
|
||||
|
||||
.success.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $success-color!important;
|
||||
}
|
||||
|
||||
.djs-visual path:nth-child(2) {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.primary.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $primary-color!important;
|
||||
fill: $primary-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $primary-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $primary-color!important;
|
||||
fill: $primary-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.danger.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $danger-color!important;
|
||||
fill: $danger-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $danger-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $danger-color!important;
|
||||
fill: $danger-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $cancel-color!important;
|
||||
fill: $cancel-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $cancel-color!important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $cancel-color!important;
|
||||
fill: $cancel-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.process-viewer .djs-tooltip-container, .process-viewer .djs-overlay-container, .process-viewer .djs-palette {
|
||||
display: none;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
@import 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
|
||||
@import 'bpmn-js-token-simulation/assets/css/font-awesome.min.css';
|
||||
@import 'bpmn-js-token-simulation/assets/css/normalize.css';
|
||||
@use 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css';
|
||||
@use 'bpmn-js-token-simulation/assets/css/font-awesome.min.css';
|
||||
@use 'bpmn-js-token-simulation/assets/css/normalize.css';
|
||||
|
||||
// 边框被 token-simulation 样式覆盖了
|
||||
.djs-palette {
|
||||
|
Reference in New Issue
Block a user