冲突修改 Merge branch 'feature/bpm' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into feature/bpm

This commit is contained in:
jason
2025-01-19 20:45:27 +08:00
58 changed files with 1591 additions and 2039 deletions

View File

@@ -9,6 +9,10 @@ import { useAppStore } from '@/store/modules/app'
import { isString } from '@/utils/is'
import { useDesign } from '@/hooks/web/useDesign'
import 'echarts/lib/component/markPoint'
import 'echarts/lib/component/markLine'
import 'echarts/lib/component/markArea'
defineOptions({ name: 'EChart' })
const { getPrefixCls, variables } = useDesign()

View File

@@ -79,9 +79,14 @@ function remoteMethod(data) {
function handleChange(path) {
router.push({ path })
hiddenSearch()
hiddenTopSearch()
}
function hiddenSearch() {
showSearch.value = false
}
function hiddenTopSearch() {
showTopSearch.value = false
}

View File

@@ -46,7 +46,7 @@
</div>
<div class="handler-item-text">延迟器</div>
</div>
<div class="handler-item" @click="addNode(NodeType.ROUTE_BRANCH_NODE)">
<div class="handler-item" @click="addNode(NodeType.ROUTER_BRANCH_NODE)">
<!-- TODO @芋艿 需要更换一下iconfont的图标 -->
<div class="handler-item-icon copy">
<span class="iconfont icon-size icon-copy"></span>
@@ -67,12 +67,13 @@ import {
ApproveMethodType,
AssignEmptyHandlerType,
AssignStartUserHandlerType,
ConditionType,
NODE_DEFAULT_NAME,
NodeType,
RejectHandlerType,
SimpleFlowNode
} from './consts'
import { generateUUID } from '@/utils'
import {generateUUID} from '@/utils'
defineOptions({
name: 'NodeHandler'
@@ -164,9 +165,24 @@ const addNode = (type: number) => {
type: NodeType.CONDITION_NODE,
childNode: undefined,
conditionSetting: {
defaultFlow: false
},
defaultFlow: false,
conditionType: ConditionType.RULE,
conditionGroups: {
and: true,
conditions: [
{
and: true,
rules: [
{
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
]
}
}
},
{
id: 'Flow_' + generateUUID(),
@@ -221,9 +237,24 @@ const addNode = (type: number) => {
type: NodeType.CONDITION_NODE,
childNode: undefined,
conditionSetting: {
defaultFlow: false
defaultFlow: false,
conditionType: ConditionType.RULE,
conditionGroups: {
and: true,
conditions: [
{
and: true,
rules: [
{
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
]
}
}
},
{
id: 'Flow_' + generateUUID(),
@@ -249,14 +280,13 @@ const addNode = (type: number) => {
}
emits('update:childNode', data)
}
if (type === NodeType.ROUTE_BRANCH_NODE) {
if (type === NodeType.ROUTER_BRANCH_NODE) {
const data: SimpleFlowNode = {
id: 'GateWay_' + generateUUID(),
name: NODE_DEFAULT_NAME.get(NodeType.ROUTE_BRANCH_NODE) as string,
name: NODE_DEFAULT_NAME.get(NodeType.ROUTER_BRANCH_NODE) as string,
showText: '',
type: NodeType.ROUTE_BRANCH_NODE,
childNode: props.childNode,
defaultFlowId: 'Flow_' + generateUUID()
type: NodeType.ROUTER_BRANCH_NODE,
childNode: props.childNode
}
emits('update:childNode', data)
}

View File

@@ -45,8 +45,8 @@
@update:flow-node="handleModelValueUpdate"
/>
<!-- 路由分支节点 -->
<RouteNode
v-if="currentNode && currentNode.type === NodeType.ROUTE_BRANCH_NODE"
<RouterNode
v-if="currentNode && currentNode.type === NodeType.ROUTER_BRANCH_NODE"
:flow-node="currentNode"
@update:flow-node="handleModelValueUpdate"
/>
@@ -73,7 +73,7 @@ import ExclusiveNode from './nodes/ExclusiveNode.vue'
import ParallelNode from './nodes/ParallelNode.vue'
import InclusiveNode from './nodes/InclusiveNode.vue'
import DelayTimerNode from './nodes/DelayTimerNode.vue'
import RouteNode from './nodes/RouteNode.vue'
import RouterNode from './nodes/RouterNode.vue'
import { SimpleFlowNode, NodeType } from './consts'
import { useWatchNode } from './node'
defineOptions({

View File

@@ -40,7 +40,7 @@ defineOptions({
name: 'SimpleProcessDesigner'
})
const emits = defineEmits(['success', 'init-finished']) // 保存成功事件
const emits = defineEmits(['success']) // 保存成功事件
const props = defineProps({
modelId: {
@@ -59,13 +59,10 @@ const props = defineProps({
startUserIds: {
type: Array,
required: false
},
value: {
type: [String, Object],
required: false
}
})
const processData = inject('processData') as Ref
const loading = ref(false)
const formFields = ref<string[]>([])
const formType = ref(20)
@@ -76,9 +73,6 @@ const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
const deptTreeOptions = ref()
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
// 添加当前值的引用
const currentValue = ref<SimpleFlowNode | undefined>()
provide('formFields', formFields)
provide('formType', formType)
provide('roleList', roleOptions)
@@ -88,7 +82,8 @@ provide('deptList', deptOptions)
provide('userGroupList', userGroupOptions)
provide('deptTree', deptTreeOptions)
provide('startUserIds', props.startUserIds)
provide('tasks', [])
provide('processInstance', {})
const message = useMessage() // 国际化
const processNodeTree = ref<SimpleFlowNode | undefined>()
provide('processNodeTree', processNodeTree)
@@ -113,70 +108,13 @@ const updateModel = () => {
}
}
// 加载流程数据
const loadProcessData = async (data: any) => {
try {
if (data) {
const parsedData = typeof data === 'string' ? JSON.parse(data) : data
processNodeTree.value = parsedData
currentValue.value = parsedData
// 确保数据加载后刷新视图
await nextTick()
if (simpleProcessModelRef.value?.refresh) {
await simpleProcessModelRef.value.refresh()
}
}
} catch (error) {
console.error('加载流程数据失败:', error)
}
}
// 监听属性变化
watch(
() => props.value,
async (newValue, oldValue) => {
if (newValue && newValue !== oldValue) {
await loadProcessData(newValue)
}
},
{ immediate: true, deep: true }
)
// 监听流程节点树变化,自动保存
watch(
() => processNodeTree.value,
async (newValue, oldValue) => {
if (newValue && oldValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
await saveSimpleFlowModel(newValue)
}
},
{ deep: true }
)
const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => {
if (!simpleModelNode) {
return
}
// 校验节点
errorNodes = []
validateNode(simpleModelNode, errorNodes)
if (errorNodes.length > 0) {
errorDialogVisible.value = true
return
}
try {
if (props.modelId) {
// 编辑模式
const data = {
id: props.modelId,
simpleModel: simpleModelNode
}
await updateBpmSimpleModel(data)
}
// 无论是编辑还是新建模式,都更新当前值并触发事件
currentValue.value = simpleModelNode
processData.value = simpleModelNode
emits('success', simpleModelNode)
} catch (error) {
console.error('保存失败:', error)
@@ -247,61 +185,18 @@ onMounted(async () => {
deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id')
// 获取用户组列表
userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
// 加载流程数据
if (props.modelId) {
// 获取 SIMPLE 设计器模型
const result = await getBpmSimpleModel(props.modelId)
if (result) {
await loadProcessData(result)
} else {
updateModel()
}
} else if (props.value) {
await loadProcessData(props.value)
if (processData.value) {
processNodeTree.value = processData?.value
} else {
updateModel()
}
} finally {
loading.value = false
emits('init-finished')
}
})
const simpleProcessModelRef = ref()
/** 获取当前流程数据 */
const getCurrentFlowData = async () => {
try {
if (simpleProcessModelRef.value) {
const data = await simpleProcessModelRef.value.getCurrentFlowData()
if (data) {
currentValue.value = data
return data
}
}
return currentValue.value
} catch (error) {
console.error('获取流程数据失败:', error)
return currentValue.value
}
}
// 刷新方法
const refresh = async () => {
try {
if (currentValue.value) {
await loadProcessData(currentValue.value)
}
} catch (error) {
console.error('刷新失败:', error)
}
}
defineExpose({
getCurrentFlowData,
updateModel,
loadProcessData,
refresh
})
defineExpose({})
</script>

View File

@@ -3,6 +3,22 @@
<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 v-if="!readonly" size="default" @click="exportJson">
<Icon icon="ep:download" /> 导出
</el-button>
<el-button v-if="!readonly" size="default" @click="importJson">
<Icon icon="ep:upload" />导入
</el-button>
<!-- 用于打开本地文件-->
<input
v-if="!readonly"
type="file"
id="files"
ref="refFile"
style="display: none"
accept=".json"
@change="importLocalFile"
/>
<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>
@@ -34,6 +50,8 @@ import ProcessNodeTree from './ProcessNodeTree.vue'
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts'
import { useWatchNode } from './node'
import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue'
import { isString } from '@/utils/is'
import download from '@/utils/download'
defineOptions({
name: 'SimpleProcessModel'
@@ -52,7 +70,7 @@ const props = defineProps({
})
const emits = defineEmits<{
'save': [node: SimpleFlowNode | undefined]
save: [node: SimpleFlowNode | undefined]
}>()
const processNodeTree = useWatchNode(props)
@@ -85,6 +103,16 @@ const processReZoom = () => {
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) {
@@ -143,6 +171,28 @@ const getCurrentFlowData = async () => {
defineExpose({
getCurrentFlowData
})
/** 导出 JSON */
const exportJson = () => {
download.json(new Blob([JSON.stringify(processNodeTree.value)]), 'model.json')
}
/** 导入 JSON */
const refFile = ref()
const importJson = () => {
refFile.value.click()
}
const importLocalFile = () => {
const file = refFile.value.files[0]
const reader = new FileReader()
reader.readAsText(file)
reader.onload = function () {
if (isString(this.result)) {
processNodeTree.value = JSON.parse(this.result)
emits('save', processNodeTree.value)
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -48,7 +48,7 @@ export enum NodeType {
/**
* 路由分支节点
*/
ROUTE_BRANCH_NODE = 54
ROUTER_BRANCH_NODE = 54
}
export enum NodeId {
@@ -110,10 +110,12 @@ export interface SimpleFlowNode {
// 延迟设置
delaySetting?: DelaySetting
// 路由分支
routerGroups?: RouteCondition[]
routerGroups?: RouterCondition[]
defaultFlowId?: string
// 签名
signEnable?: boolean
// 审批意见
reasonRequire?: boolean
}
// 候选人策略枚举 用于审批节点。抄送节点 )
export enum CandidateStrategy {
@@ -447,8 +449,6 @@ export enum OperationButtonType {
* 条件规则结构定义
*/
export type ConditionRule = {
type: number
opName: string
opCode: string
leftSide: string
rightSide: string
@@ -479,7 +479,7 @@ NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人')
NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件')
NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人')
NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_NODE, '请设置延迟器')
NODE_DEFAULT_TEXT.set(NodeType.ROUTE_BRANCH_NODE, '请设置路由节点')
NODE_DEFAULT_TEXT.set(NodeType.ROUTER_BRANCH_NODE, '请设置路由节点')
export const NODE_DEFAULT_NAME = new Map<number, string>()
NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
@@ -487,7 +487,7 @@ NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人')
NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_NODE, '延迟器')
NODE_DEFAULT_NAME.set(NodeType.ROUTE_BRANCH_NODE, '路由分支')
NODE_DEFAULT_NAME.set(NodeType.ROUTER_BRANCH_NODE, '路由分支')
// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
export const CANDIDATE_STRATEGY: DictDataVO[] = [
@@ -668,7 +668,7 @@ export const DELAY_TYPE = [
/**
* 路由分支结构定义
*/
export type RouteCondition = {
export type RouterCondition = {
nodeId: string
conditionType: ConditionType
conditionExpression: string

View File

@@ -148,6 +148,7 @@ export type UserTaskFormType = {
taskCompleteListenerHeader?: ListenerParam[]
taskCompleteListenerBody?: ListenerParam[]
signEnable: boolean
reasonRequire: boolean
}
export type CopyTaskFormType = {

View File

@@ -26,127 +26,11 @@
</div>
</template>
<div>
<div class="mb-3 font-size-16px" v-if="currentNode.conditionSetting?.defaultFlow">
未满足其它条件时将进入此分支该分支不可编辑和删除
</div>
<div class="mb-3 font-size-16px" v-if="currentNode.conditionSetting?.defaultFlow"
>未满足其它条件时将进入此分支该分支不可编辑和删除</div
>
<div v-else>
<el-form ref="formRef" :model="currentNode.conditionSetting" :rules="formRules" label-position="top">
<el-form-item label="配置方式" prop="conditionType">
<el-radio-group
v-model="currentNode.conditionSetting!.conditionType"
@change="changeConditionType"
>
<el-radio
v-for="(dict, index) in conditionConfigTypes"
:key="index"
:value="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="currentNode.conditionSetting?.conditionType === ConditionType.EXPRESSION"
label="条件表达式"
prop="conditionExpression"
>
<el-input
type="textarea"
v-model="currentNode.conditionSetting.conditionExpression"
clearable
style="width: 100%"
/>
</el-form-item>
<el-form-item
v-if="currentNode.conditionSetting?.conditionType === ConditionType.RULE"
label="条件规则"
>
<div class="condition-group-tool">
<div class="flex items-center">
<div class="mr-4">条件组关系</div>
<el-switch
v-model="conditionGroups.and"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<el-space direction="vertical" :spacer="conditionGroups.and ? '且' : '或'">
<el-card
class="condition-group"
style="width: 530px"
v-for="(condition, cIdx) in conditionGroups.conditions"
:key="cIdx"
>
<div class="condition-group-delete" v-if="conditionGroups.conditions.length > 1">
<Icon
color="#0089ff"
icon="ep:circle-close-filled"
:size="18"
@click="deleteConditionGroup(cIdx)"
/>
</div>
<template #header>
<div class="flex items-center justify-between">
<div>条件组</div>
<div class="flex">
<div class="mr-4">规则关系</div>
<el-switch
v-model="condition.and"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
</template>
<div class="flex pt-2" v-for="(rule, rIdx) in condition.rules" :key="rIdx">
<div class="mr-2">
<el-select style="width: 160px" v-model="rule.leftSide">
<el-option
v-for="(item, index) in fieldOptions"
:key="index"
:label="item.title"
:value="item.field"
:disabled="!item.required"
/>
</el-select>
</div>
<div class="mr-2">
<el-select v-model="rule.opCode" style="width: 100px">
<el-option
v-for="item in COMPARISON_OPERATORS"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-input v-model="rule.rightSide" style="width: 160px" />
</div>
<div class="mr-1 flex items-center" v-if="condition.rules.length > 1">
<Icon
icon="ep:delete"
:size="18"
@click="deleteConditionRule(condition, rIdx)"
/>
</div>
<div class="flex items-center">
<Icon icon="ep:plus" :size="18" @click="addConditionRule(condition, rIdx)" />
</div>
</div>
</el-card>
</el-space>
<div title="添加条件组" class="mt-4 cursor-pointer">
<Icon color="#0089ff" icon="ep:plus" :size="24" @click="addConditionGroup" />
</div>
</el-form-item>
</el-form>
<Condition ref="conditionRef" v-model="condition" />
</div>
</div>
<template #footer>
@@ -161,33 +45,18 @@
<script setup lang="ts">
import {
SimpleFlowNode,
CONDITION_CONFIG_TYPES,
ConditionType,
ConditionSetting,
COMPARISON_OPERATORS,
ConditionGroup,
Condition,
ConditionRule,
ProcessVariableEnum
} from '../consts'
import { getDefaultConditionNodeName } from '../utils'
import { useFormFields } from '../node'
import { BpmModelFormType } from '@/utils/constants'
import Condition from './components/Condition.vue'
const message = useMessage() // 消息弹窗
defineOptions({
name: 'ConditionNodeConfig'
})
const formType = inject<Ref<number>>('formType') // 表单类型
const conditionConfigTypes = computed(() => {
return CONDITION_CONFIG_TYPES.filter((item) => {
// 业务表单暂时去掉条件规则选项
if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
return false
} else {
return true
}
})
})
const props = defineProps({
conditionNode: {
type: Object as () => SimpleFlowNode,
@@ -199,12 +68,10 @@ const props = defineProps({
}
})
const settingVisible = ref(false)
const currentNode = ref<SimpleFlowNode>(props.conditionNode)
const condition = ref<ConditionSetting>()
const open = () => {
if (currentNode.value.conditionSetting?.conditionType === ConditionType.RULE) {
if (currentNode.value.conditionSetting?.conditionGroups) {
conditionGroups.value = currentNode.value.conditionSetting.conditionGroups
}
}
condition.value = currentNode.value.conditionSetting
settingVisible.value = true
}
@@ -228,7 +95,7 @@ const blurEvent = () => {
getDefaultConditionNodeName(props.nodeIndex, currentNode.value?.conditionSetting?.defaultFlow)
}
const currentNode = ref<SimpleFlowNode>(props.conditionNode)
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
@@ -245,31 +112,27 @@ const handleClose = async (done: (cancel?: boolean) => void) => {
done()
}
}
// 表单校验规则
const formRules = reactive({
conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const conditionRef = ref()
// 保存配置
const saveConfig = async () => {
if (!currentNode.value.conditionSetting?.defaultFlow) {
// 校验表单
if (!formRef) return false
const valid = await formRef.value.validate()
const valid = await conditionRef.value.validate()
if (!valid) return false
const showText = getShowText()
if (!showText) {
return false
}
currentNode.value.showText = showText
currentNode.value.conditionSetting!.conditionType = condition.value?.conditionType
if (currentNode.value.conditionSetting?.conditionType === ConditionType.EXPRESSION) {
currentNode.value.conditionSetting.conditionGroups = undefined
currentNode.value.conditionSetting.conditionExpression = condition.value?.conditionExpression
}
if (currentNode.value.conditionSetting?.conditionType === ConditionType.RULE) {
currentNode.value.conditionSetting.conditionExpression = undefined
currentNode.value.conditionSetting.conditionGroups = conditionGroups.value
if (currentNode.value.conditionSetting!.conditionType === ConditionType.RULE) {
currentNode.value.conditionSetting!.conditionExpression = undefined
currentNode.value.conditionSetting!.conditionGroups = condition.value?.conditionGroups
}
}
settingVisible.value = false
@@ -277,16 +140,16 @@ const saveConfig = async () => {
}
const getShowText = (): string => {
let showText = ''
if (currentNode.value.conditionSetting?.conditionType === ConditionType.EXPRESSION) {
if (currentNode.value.conditionSetting.conditionExpression) {
showText = `表达式:${currentNode.value.conditionSetting.conditionExpression}`
if (condition.value?.conditionType === ConditionType.EXPRESSION) {
if (condition.value.conditionExpression) {
showText = `表达式:${condition.value.conditionExpression}`
}
}
if (currentNode.value.conditionSetting?.conditionType === ConditionType.RULE) {
if (condition.value?.conditionType === ConditionType.RULE) {
// 条件组是否为与关系
const groupAnd = conditionGroups.value.and
const groupAnd = condition.value.conditionGroups?.and
let warningMesg: undefined | string = undefined
const conditionGroup = conditionGroups.value.conditions.map((item) => {
const conditionGroup = condition.value.conditionGroups?.conditions.map((item) => {
return (
'(' +
item.rules
@@ -309,70 +172,13 @@ const getShowText = (): string => {
message.warning(warningMesg)
showText = ''
} else {
showText = conditionGroup.join(groupAnd ? ' 且 ' : ' 或 ')
showText = conditionGroup!.join(groupAnd ? ' 且 ' : ' 或 ')
}
}
return showText
}
// 改变条件配置方式
const changeConditionType = () => {}
const conditionGroups = ref<ConditionGroup>({
and: true,
conditions: [
{
and: true,
rules: [
{
type: 1,
opName: '等于',
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
]
})
// 添加条件组
const addConditionGroup = () => {
const condition = {
and: true,
rules: [
{
type: 1,
opName: '等于',
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
conditionGroups.value.conditions.push(condition)
}
// 删除条件组
const deleteConditionGroup = (idx: number) => {
conditionGroups.value.conditions.splice(idx, 1)
}
// 添加条件规则
const addConditionRule = (condition: Condition, idx: number) => {
const rule: ConditionRule = {
type: 1,
opName: '等于',
opCode: '==',
leftSide: '',
rightSide: ''
}
condition.rules.splice(idx + 1, 0, rule)
}
const deleteConditionRule = (condition: Condition, idx: number) => {
condition.rules.splice(idx, 1)
}
const fieldsInfo = useFormFields()
/** 条件规则可选择的表单字段 */
const fieldOptions = computed(() => {
const fieldsCopy = fieldsInfo.slice()

View File

@@ -37,16 +37,19 @@
:value="node.value"
/>
</el-select>
<el-button class="mla" type="danger" link @click="deleteRouteGroup(index)"
>删除</el-button
>
<el-button class="mla" type="danger" link @click="deleteRouterGroup(index)">
删除
</el-button>
</div>
</template>
<Condition v-model="routerGroups[index]" />
<Condition
:ref="($event) => (conditionRef[index] = $event)"
v-model="routerGroups[index]"
/>
</el-card>
</el-form>
<el-button class="w-1/1" type="primary" :icon="Plus" @click="addRouteGroup">
<el-button class="w-1/1" type="primary" :icon="Plus" @click="addRouterGroup">
新增路由分支
</el-button>
</div>
@@ -61,11 +64,12 @@
</template>
<script setup lang="ts">
import { Plus } from '@element-plus/icons-vue'
import { SimpleFlowNode, NodeType, ConditionType, RouteCondition } from '../consts'
import { SimpleFlowNode, NodeType, ConditionType, RouterCondition } from '../consts'
import { useWatchNode, useDrawer, useNodeName } from '../node'
import Condition from './components/Condition.vue'
defineOptions({
name: 'RouteNodeConfig'
name: 'RouterNodeConfig'
})
const message = useMessage() //
const props = defineProps({
@@ -80,12 +84,21 @@ const { settingVisible, closeDrawer, openDrawer } = useDrawer()
//
const currentNode = useWatchNode(props)
//
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTE_BRANCH_NODE)
const routerGroups = ref<RouteCondition[]>([])
const nodeOptions = ref()
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTER_BRANCH_NODE)
const routerGroups = ref<RouterCondition[]>([])
const nodeOptions = ref<any>([])
const conditionRef = ref([])
//
/** 保存配置 */
const saveConfig = async () => {
//
let valid = true
for (const item of conditionRef.value) {
if (item && !(await item.validate())) {
valid = false
}
}
if (!valid) return false
const showText = getShowText()
if (!showText) return false
currentNode.value.name = nodeName.value!
@@ -96,7 +109,7 @@ const saveConfig = async () => {
}
//
const showRouteNodeConfig = (node: SimpleFlowNode) => {
getRoutableNode()
getRouterNode(processNodeTree?.value)
routerGroups.value = []
nodeName.value = node.name
if (node.routerGroups) {
@@ -132,7 +145,7 @@ const getShowText = () => {
return `${routerGroups.value.length}条路由分支`
}
const addRouteGroup = () => {
const addRouterGroup = () => {
routerGroups.value.push({
nodeId: '',
conditionType: ConditionType.RULE,
@@ -144,8 +157,6 @@ const addRouteGroup = () => {
and: true,
rules: [
{
type: 1,
opName: '等于',
opCode: '==',
leftSide: '',
rightSide: ''
@@ -157,20 +168,18 @@ const addRouteGroup = () => {
})
}
const deleteRouteGroup = (index: number) => {
const deleteRouterGroup = (index: number) => {
routerGroups.value.splice(index, 1)
}
// TODO @lesan router
const getRoutableNode = () => {
// TODO @lesan
//
const getRouterNode = (node) => {
// TODO
//
//
let node = processNodeTree?.value
nodeOptions.value = []
while (true) {
if (!node) break
if (node.type !== NodeType.ROUTE_BRANCH_NODE) {
if (node.type !== NodeType.ROUTER_BRANCH_NODE && node.type !== NodeType.CONDITION_NODE) {
nodeOptions.value.push({
label: node.name,
value: node.id
@@ -179,6 +188,11 @@ const getRoutableNode = () => {
if (!node.childNode || node.type === NodeType.END_EVENT_NODE) {
break
}
if (node.conditionNodes && node.conditionNodes.length) {
node.conditionNodes.forEach((item) => {
getRouterNode(item)
})
}
node = node.childNode
}
}

View File

@@ -359,11 +359,12 @@
<el-divider content-position="left">是否需要签名</el-divider>
<el-form-item prop="signEnable">
<el-switch
v-model="configForm.signEnable"
active-text="是"
inactive-text="否"
/>
<el-switch v-model="configForm.signEnable" active-text="是" inactive-text="否" />
</el-form-item>
<el-divider content-position="left">审批意见</el-divider>
<el-form-item prop="reasonRequire">
<el-switch v-model="configForm.reasonRequire" active-text="必填" inactive-text="非必填" />
</el-form-item>
</el-form>
</div>
@@ -445,161 +446,7 @@
</div>
</el-tab-pane>
<el-tab-pane label="监听器" name="listener">
<el-form :model="configForm" label-position="top">
<div v-for="(listener, listenerIdx) in taskListener" :key="listenerIdx">
<el-divider content-position="left">
<el-text tag="b" size="large">{{ listener.name }}</el-text>
</el-divider>
<el-form-item>
<el-switch
v-model="configForm[`task${listener.type}ListenerEnable`]"
active-text="开启"
inactive-text="关闭"
/>
</el-form-item>
<div v-if="configForm[`task${listener.type}ListenerEnable`]">
<el-form-item>
<el-alert
title="仅支持 POST 请求,以请求体方式接收参数"
type="warning"
show-icon
:closable="false"
/>
</el-form-item>
<el-form-item
label="请求地址"
:prop="`task${listener.type}ListenerPath`"
:rules="{
required: true,
message: '请求地址不能为空',
trigger: 'blur'
}"
>
<el-input v-model="configForm[`task${listener.type}ListenerPath`]" />
</el-form-item>
<el-form-item label="请求头">
<div
class="flex pt-2"
v-for="(item, index) in configForm[`task${listener.type}ListenerHeader`]"
:key="index"
>
<div class="mr-2">
<el-input class="w-160px" v-model="item.key" />
</div>
<div class="mr-2">
<el-select class="w-100px!" v-model="item.type">
<el-option
v-for="types in LISTENER_MAP_TYPES"
:key="types.value"
:label="types.label"
:value="types.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-input
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
class="w-160px"
v-model="item.value"
/>
<el-select
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
class="w-160px!"
v-model="item.value"
>
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</div>
<div class="mr-1 flex items-center">
<Icon
icon="ep:delete"
:size="18"
@click="
deleteTaskListenerParam(
configForm[`task${listener.type}ListenerHeader`],
index
)
"
/>
</div>
</div>
<el-button
type="primary"
text
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerHeader`])"
>
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
<el-form-item label="请求体">
<div
class="flex pt-2"
v-for="(item, index) in configForm[`task${listener.type}ListenerBody`]"
:key="index"
>
<div class="mr-2">
<el-input class="w-160px" v-model="item.key" />
</div>
<div class="mr-2">
<el-select class="w-100px!" v-model="item.type">
<el-option
v-for="types in LISTENER_MAP_TYPES"
:key="types.value"
:label="types.label"
:value="types.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-input
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
class="w-160px"
v-model="item.value"
/>
<el-select
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
class="w-160px!"
v-model="item.value"
>
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</div>
<div class="mr-1 flex items-center">
<Icon
icon="ep:delete"
:size="18"
@click="
deleteTaskListenerParam(
configForm[`task${listener.type}ListenerBody`],
index
)
"
/>
</div>
</div>
<el-button
type="primary"
text
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerBody`])"
>
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
</div>
</div>
</el-form>
<UserTaskListener ref="userTaskListenerRef" v-model="configForm" :form-field-options="formFieldOptions" />
</el-tab-pane>
</el-tabs>
<template #footer>
@@ -636,9 +483,7 @@ import {
ASSIGN_EMPTY_HANDLER_TYPES,
AssignEmptyHandlerType,
FieldPermissionType,
ProcessVariableEnum,
LISTENER_MAP_TYPES,
ListenerParamTypeEnum
ProcessVariableEnum
} from '../consts'
import {
@@ -652,6 +497,7 @@ import {
import { defaultProps } from '@/utils/tree'
import { cloneDeep } from 'lodash-es'
import { convertTimeUnit, getApproveTypeText } from '../utils'
import UserTaskListener from './components/UserTaskListener.vue'
defineOptions({
name: 'UserTaskNodeConfig'
})
@@ -729,21 +575,6 @@ const formRules = reactive({
assignEmptyHandlerUserIds: [{ required: true, message: '用户不能为空', trigger: 'change' }],
assignStartUserHandlerType: [{ required: true }]
})
// 监听器数组
const taskListener = ref([
{
name: '创建任务',
type: 'Create'
},
{
name: '指派任务执行人员',
type: 'Assign'
},
{
name: '完成任务',
type: 'Complete'
}
])
const {
configForm: tempConfigForm,
@@ -792,6 +623,8 @@ const {
cTimeoutMaxRemindCount
} = useTimeoutHandler()
const userTaskListenerRef = ref()
// 保存配置
const saveConfig = async () => {
activeTabName.value = 'user'
@@ -807,7 +640,8 @@ const saveConfig = async () => {
}
if (!formRef) return false
const valid = await formRef.value.validate()
if (!userTaskListenerRef) return false
const valid = (await formRef.value.validate()) && (await userTaskListenerRef.value.validate())
if (!valid) return false
const showText = getShowText()
if (!showText) return false
@@ -869,6 +703,8 @@ const saveConfig = async () => {
}
// 签名
currentNode.value.signEnable = configForm.value.signEnable
// 审批意见
currentNode.value.reasonRequire = configForm.value.reasonRequire
currentNode.value.showText = showText
settingVisible.value = false
@@ -937,7 +773,9 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
configForm.value.taskCompleteListenerHeader = node.taskCompleteListener?.header ?? []
configForm.value.taskCompleteListenerBody = node.taskCompleteListener?.body ?? []
// 6. 签名
configForm.value.signEnable = node.signEnable ?? false
configForm.value.signEnable = node?.signEnable ?? false
// 7. 审批意见
configForm.value.reasonRequire = node?.reasonRequire ?? false
}
defineExpose({ openDrawer, showUserTaskNodeConfig }) // 暴露方法给父组件
@@ -1050,17 +888,6 @@ function useTimeoutHandler() {
cTimeoutMaxRemindCount
}
}
const addTaskListenerParam = (arr) => {
arr.push({
key: '',
type: 1,
value: ''
})
}
const deleteTaskListenerParam = (arr, index) => {
arr.splice(index, 1)
}
</script>
<style lang="scss" scoped>

View File

@@ -1,9 +1,7 @@
<!-- TODO @lesan其它路由条件可以使用这个哇 -->
<template>
<el-form ref="formRef" :model="condition" :rules="formRules" label-position="top">
<!-- TODO @lesan1默认选中 条件规则2条件规则放前面因为更常用-->
<el-form-item label="配置方式" prop="conditionType">
<el-radio-group v-model="condition.conditionType">
<el-radio-group v-model="condition!.conditionType">
<el-radio
v-for="(dict, indexConditionType) in conditionConfigTypes"
:key="indexConditionType"
@@ -14,46 +12,34 @@
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="condition.conditionType === ConditionType.EXPRESSION"
label="条件表达式"
prop="conditionExpression"
>
<el-input
type="textarea"
v-model="condition.conditionExpression"
clearable
style="width: 100%"
/>
</el-form-item>
<el-form-item v-if="condition.conditionType === ConditionType.RULE" label="条件规则">
<div class="condition-group-tool">
<div class="flex items-center">
<div class="mr-4">条件组关系</div>
<el-switch
v-model="condition.conditionGroups.and"
v-model="condition!.conditionGroups!.and"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<el-space direction="vertical" :spacer="condition.conditionGroups.and ? '且' : '或'">
<el-space direction="vertical" :spacer="condition!.conditionGroups!.and ? '且' : '或'">
<el-card
class="condition-group"
style="width: 530px"
v-for="(equation, cIdx) in condition.conditionGroups.conditions"
v-for="(equation, cIdx) in condition!.conditionGroups!.conditions"
:key="cIdx"
>
<div
class="condition-group-delete"
v-if="condition.conditionGroups.conditions.length > 1"
v-if="condition!.conditionGroups!.conditions.length > 1"
>
<Icon
color="#0089ff"
icon="ep:circle-close-filled"
:size="18"
@click="deleteConditionGroup(condition.conditionGroups.conditions, cIdx)"
@click="deleteConditionGroup(condition!.conditionGroups!.conditions, cIdx)"
/>
</div>
<template #header>
@@ -73,15 +59,24 @@
<div class="flex pt-2" v-for="(rule, rIdx) in equation.rules" :key="rIdx">
<div class="mr-2">
<el-select style="width: 160px" v-model="rule.leftSide">
<el-option
v-for="(field, fIdx) in fieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
<el-form-item
:prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.leftSide`"
:rules="{
required: true,
message: '左值不能为空',
trigger: 'change'
}"
>
<el-select style="width: 160px" v-model="rule.leftSide">
<el-option
v-for="(field, fIdx) in fieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-2">
<el-select v-model="rule.opCode" style="width: 100px">
@@ -94,7 +89,16 @@
</el-select>
</div>
<div class="mr-2">
<el-input v-model="rule.rightSide" style="width: 160px" />
<el-form-item
:prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.rightSide`"
:rules="{
required: true,
message: '右值不能为空',
trigger: 'blur'
}"
>
<el-input v-model="rule.rightSide" style="width: 160px" />
</el-form-item>
</div>
<div class="mr-1 flex items-center" v-if="equation.rules.length > 1">
<Icon icon="ep:delete" :size="18" @click="deleteConditionRule(equation, rIdx)" />
@@ -110,29 +114,37 @@
color="#0089ff"
icon="ep:plus"
:size="24"
@click="addConditionGroup(condition.conditionGroups.conditions)"
@click="addConditionGroup(condition?.conditionGroups!.conditions)"
/>
</div>
</el-form-item>
<el-form-item
v-if="condition.conditionType === ConditionType.EXPRESSION"
label="条件表达式"
prop="conditionExpression"
>
<el-input
type="textarea"
v-model="condition!.conditionExpression"
clearable
style="width: 100%"
/>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import {
CONDITION_CONFIG_TYPES,
COMPARISON_OPERATORS,
CONDITION_CONFIG_TYPES,
ConditionType,
ConditionSetting,
ProcessVariableEnum
} from '../../consts'
import { BpmModelFormType } from '@/utils/constants'
import { useFormFields } from '../../node'
const props = defineProps({
modelValue: {
type: Object,
required: true
}
})
const props = defineProps<{modelValue: ConditionSetting}>()
const emit = defineEmits(['update:modelValue'])
const condition = computed({
get() {
@@ -181,8 +193,6 @@ const deleteConditionRule = (condition, index) => {
const addConditionRule = (condition, index) => {
const rule = {
type: 1,
opName: '等于',
opCode: '==',
leftSide: '',
rightSide: ''
@@ -195,8 +205,6 @@ const addConditionGroup = (conditions) => {
and: true,
rules: [
{
type: 1, // TODO @lesan枚举~
opName: '等于',
opCode: '==',
leftSide: '',
rightSide: ''
@@ -205,6 +213,13 @@ const addConditionGroup = (conditions) => {
}
conditions.push(condition)
}
const validate = async () => {
if (!formRef) return false
return await formRef.value.validate()
}
defineExpose({ validate })
</script>
<style lang="scss" scoped>

View File

@@ -0,0 +1,261 @@
<template>
<el-form ref="listenerFormRef" :model="configForm" label-position="top">
<div v-for="(listener, listenerIdx) in taskListener" :key="listenerIdx">
<el-divider content-position="left">
<el-text tag="b" size="large">{{ listener.name }}</el-text>
</el-divider>
<el-form-item>
<el-switch
v-model="configForm[`task${listener.type}ListenerEnable`]"
active-text="开启"
inactive-text="关闭"
/>
</el-form-item>
<div v-if="configForm[`task${listener.type}ListenerEnable`]">
<el-form-item>
<el-alert
title="仅支持 POST 请求,以请求体方式接收参数"
type="warning"
show-icon
:closable="false"
/>
</el-form-item>
<el-form-item
label="请求地址"
:prop="`task${listener.type}ListenerPath`"
:rules="{
required: true,
message: '请求地址不能为空',
trigger: 'blur'
}"
>
<el-input v-model="configForm[`task${listener.type}ListenerPath`]" />
</el-form-item>
<el-form-item label="请求头">
<div
class="flex pt-2"
v-for="(item, index) in configForm[`task${listener.type}ListenerHeader`]"
:key="index"
>
<div class="mr-2">
<el-form-item
:prop="`task${listener.type}ListenerHeader.${index}.key`"
:rules="{
required: true,
message: '参数名不能为空',
trigger: 'blur'
}"
>
<el-input class="w-160px" v-model="item.key" />
</el-form-item>
</div>
<div class="mr-2">
<el-select class="w-100px!" v-model="item.type">
<el-option
v-for="types in LISTENER_MAP_TYPES"
:key="types.value"
:label="types.label"
:value="types.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-form-item
:prop="`task${listener.type}ListenerHeader.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'blur'
}"
>
<el-input
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
class="w-160px"
v-model="item.value"
/>
</el-form-item>
<el-form-item
:prop="`task${listener.type}ListenerHeader.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'change'
}"
>
<el-select
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
class="w-160px!"
v-model="item.value"
>
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-1 flex items-center">
<Icon
icon="ep:delete"
:size="18"
@click="
deleteTaskListenerParam(configForm[`task${listener.type}ListenerHeader`], index)
"
/>
</div>
</div>
<el-button
type="primary"
text
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerHeader`])"
>
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
<el-form-item label="请求体">
<div
class="flex pt-2"
v-for="(item, index) in configForm[`task${listener.type}ListenerBody`]"
:key="index"
>
<div class="mr-2">
<el-form-item
:prop="`task${listener.type}ListenerBody.${index}.key`"
:rules="{
required: true,
message: '参数名不能为空',
trigger: 'blur'
}"
>
<el-input class="w-160px" v-model="item.key" />
</el-form-item>
</div>
<div class="mr-2">
<el-select class="w-100px!" v-model="item.type">
<el-option
v-for="types in LISTENER_MAP_TYPES"
:key="types.value"
:label="types.label"
:value="types.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-form-item
:prop="`task${listener.type}ListenerBody.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'blur'
}"
>
<el-input
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
class="w-160px"
v-model="item.value"
/>
</el-form-item>
<el-form-item
:prop="`task${listener.type}ListenerBody.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'change'
}"
>
<el-select
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
class="w-160px!"
v-model="item.value"
>
<el-option
v-for="(field, fIdx) in formFieldOptions"
:key="fIdx"
:label="field.title"
:value="field.field"
:disabled="!field.required"
/>
</el-select>
</el-form-item>
</div>
<div class="mr-1 flex items-center">
<Icon
icon="ep:delete"
:size="18"
@click="
deleteTaskListenerParam(configForm[`task${listener.type}ListenerBody`], index)
"
/>
</div>
</div>
<el-button
type="primary"
text
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerBody`])"
>
<Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button>
</el-form-item>
</div>
</div>
</el-form>
</template>
<script setup lang="ts">
import { LISTENER_MAP_TYPES, ListenerParamTypeEnum } from '../../consts'
const props = defineProps({
modelValue: {
type: Object,
required: true
},
formFieldOptions: {
type: Object,
required: true
}
})
const emit = defineEmits(['update:modelValue'])
const listenerFormRef = ref()
const configForm = computed({
get() {
return props.modelValue
},
set(newValue) {
emit('update:modelValue', newValue)
}
})
const taskListener = ref([
{
name: '创建任务',
type: 'Create'
},
{
name: '指派任务执行人员',
type: 'Assign'
},
{
name: '完成任务',
type: 'Complete'
}
])
const addTaskListenerParam = (arr) => {
arr.push({
key: '',
type: 1,
value: ''
})
}
const deleteTaskListenerParam = (arr, index) => {
arr.splice(index, 1)
}
const validate = async () => {
if (!listenerFormRef) return false
return await listenerFormRef.value.validate()
}
defineExpose({ validate })
</script>

View File

@@ -31,7 +31,7 @@
{{ currentNode.showText }}
</div>
<div class="node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(NodeType.ROUTE_BRANCH_NODE) }}
{{ NODE_DEFAULT_TEXT.get(NodeType.ROUTER_BRANCH_NODE) }}
</div>
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
</div>
@@ -49,17 +49,17 @@
:current-node="currentNode"
/>
</div>
<RouteNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
<RouterNodeConfig 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, useTaskStatusClass } from '../node'
import RouteNodeConfig from '../nodes-config/RouteNodeConfig.vue'
import RouterNodeConfig from '../nodes-config/RouterNodeConfig.vue'
defineOptions({
name: 'RouteNode'
name: 'RouterNode'
})
const props = defineProps({
@@ -77,7 +77,7 @@ const readonly = inject<Boolean>('readonly')
//
const currentNode = useWatchNode(props)
//
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.ROUTE_BRANCH_NODE)
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.ROUTER_BRANCH_NODE)
const nodeSetting = ref()
//

View File

@@ -308,28 +308,6 @@ const props = defineProps({
}
})
// 监听value变化,重新加载流程图
watch(
() => props.value,
(newValue) => {
if (newValue && bpmnModeler) {
createNewDiagram(newValue)
}
},
{ immediate: true }
)
// 监听processId和processName变化
watch(
[() => props.processId, () => props.processName],
([newId, newName]) => {
if (newId && newName && !props.value) {
createNewDiagram(null)
}
},
{ immediate: true }
)
provide('configGlobal', props)
let bpmnModeler: any = null
const defaultZoom = ref(1)
@@ -480,6 +458,7 @@ const initModelListeners = () => {
emit('commandStack-changed', event)
emit('input', xml)
emit('change', xml)
emit('save', xml)
} catch (e: any) {
console.error(`[Process Designer Warn]: ${e.message || e}`)
}
@@ -568,6 +547,7 @@ const importLocalFile = () => {
reader.onload = function () {
let xmlStr = this.result
createNewDiagram(xmlStr)
emit('save', xmlStr)
}
}
/* ------------------------------------------------ refs methods ------------------------------------------------------ */

View File

@@ -1438,6 +1438,45 @@
"isBody": true
}
]
},
{
"name": "SignEnable",
"superClass": ["Element"],
"meta": {
"allowedIn": ["bpmn:UserTask"]
},
"properties": [
{
"name": "value",
"type": "Boolean",
"isBody": true
}
]
},
{
"name": "SkipExpression",
"extends": ["bpmn:UserTask"],
"properties": [
{
"name": "skipExpression",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "ReasonRequire",
"superClass": ["Element"],
"meta": {
"allowedIn": ["bpmn:UserTask"]
},
"properties": [
{
"name": "value",
"type": "Boolean",
"isBody": true
}
]
}
],
"emumerations": []

View File

@@ -1,5 +1,5 @@
<template>
<div class="process-panel__container" :style="{ width: `${width}px` }">
<div class="process-panel__container" :style="{ width: `${width}px`, maxHeight: '600px' }">
<el-collapse v-model="activeTab" v-if="isReady">
<el-collapse-item name="base">
<!-- class="panel-tab__title" -->

View File

@@ -152,6 +152,9 @@ watch(
handleKeyUpdate(props.model.key)
handleNameUpdate(props.model.name)
}
},
{
immediate: true
}
)

View File

@@ -5,6 +5,7 @@
4. 操作按钮
5. 字段权限
6. 审批类型
7. 是否需要签名
-->
<template>
<div>
@@ -161,6 +162,16 @@
</el-radio-group>
</div>
</div>
<el-divider content-position="left">是否需要签名</el-divider>
<el-form-item prop="signEnable">
<el-switch v-model="signEnable.value" active-text="是" inactive-text="否" />
</el-form-item>
<el-divider content-position="left">审批意见</el-divider>
<el-form-item prop="reasonRequire">
<el-switch v-model="reasonRequire.value" active-text="必填" inactive-text="非必填" />
</el-form-item>
</div>
</template>
@@ -218,6 +229,12 @@ const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFie
// 审批类型
const approveType = ref({ value: ApproveType.USER })
// 是否需要签名
const signEnable = ref({ value: false })
// 审批意见
const reasonRequire = ref({ value: false })
const elExtensionElements = ref()
const otherExtensions = ref()
const bpmnElement = ref()
@@ -311,6 +328,16 @@ const resetCustomConfigList = () => {
})
}
// 是否需要签名
signEnable.value =
elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:SignEnable`)?.[0] ||
bpmnInstances().moddle.create(`${prefix}:SignEnable`, { value: false })
// 审批意见
reasonRequire.value =
elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:ReasonRequire`)?.[0] ||
bpmnInstances().moddle.create(`${prefix}:ReasonRequire`, { value: false })
// 保留剩余扩展元素,便于后面更新该元素对应属性
otherExtensions.value =
elExtensionElements.value.values?.filter(
@@ -322,7 +349,9 @@ const resetCustomConfigList = () => {
ex.$type !== `${prefix}:AssignEmptyUserIds` &&
ex.$type !== `${prefix}:ButtonsSetting` &&
ex.$type !== `${prefix}:FieldsPermission` &&
ex.$type !== `${prefix}:ApproveType`
ex.$type !== `${prefix}:ApproveType` &&
ex.$type !== `${prefix}:SignEnable` &&
ex.$type !== `${prefix}:ReasonRequire`
) ?? []
// 更新元素扩展属性,避免后续报错
@@ -373,7 +402,9 @@ const updateElementExtensions = () => {
assignEmptyUserIdsEl.value,
approveType.value,
...buttonsSettingEl.value,
...fieldsPermissionEl.value
...fieldsPermissionEl.value,
signEnable.value,
reasonRequire.value
]
})
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {

View File

@@ -192,6 +192,16 @@
<!-- 选择弹窗 -->
<ProcessExpressionDialog ref="processExpressionDialogRef" @select="selectProcessExpression" />
</el-form-item>
<el-form-item label="跳过表达式" prop="skipExpression">
<el-input
type="textarea"
v-model="userTaskForm.skipExpression"
clearable
style="width: 100%"
@change="updateSkipExpression"
/>
</el-form-item>
</el-form>
</template>
@@ -220,7 +230,8 @@ const props = defineProps({
const prefix = inject('prefix')
const userTaskForm = ref({
candidateStrategy: undefined, // 分配规则
candidateParam: [] // 分配选项
candidateParam: [], // 分配选项
skipExpression: '' // 跳过表达式
})
const bpmnElement = ref()
const bpmnInstances = () => (window as any)?.bpmnInstances
@@ -311,6 +322,13 @@ const resetTaskForm = () => {
(ex) => ex.$type !== `${prefix}:CandidateStrategy` && ex.$type !== `${prefix}:CandidateParam`
) ?? []
// 跳过表达式
if (businessObject.skipExpression != undefined) {
userTaskForm.value.skipExpression = businessObject.skipExpression
} else {
userTaskForm.value.skipExpression = ''
}
// 改用通过extensionElements来存储数据
return
if (businessObject.candidateStrategy != undefined) {
@@ -390,6 +408,18 @@ const updateElementTask = () => {
})
}
const updateSkipExpression = () => {
if (userTaskForm.value.skipExpression && userTaskForm.value.skipExpression !== '') {
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
skipExpression: userTaskForm.value.skipExpression
})
} else {
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
skipExpression: null
})
}
}
// 打开监听器弹窗
const processExpressionDialogRef = ref()
const openProcessExpressionDialog = async () => {

View File

@@ -1,6 +1,4 @@
@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 {
@@ -97,12 +95,12 @@
box-sizing: border-box;
}
}
svg {
width: 100%;
height: 100%;
min-height: 100%;
overflow: hidden;
}
// svg {
// width: 100%;
// height: 100%;
// min-height: 100%;
// overflow: hidden;
// }
}
}