执行监听器

This commit is contained in:
caixiaofeng 2024-06-15 14:24:52 +08:00
parent a0c7db739a
commit d65509f2eb
12 changed files with 419 additions and 101 deletions

View File

@ -5,73 +5,85 @@
// Generated by unplugin-auto-import // Generated by unplugin-auto-import
export {} export {}
declare global { declare global {
const EffectScope: typeof import('vue')['EffectScope'] const EffectScope: (typeof import('vue'))['EffectScope']
const ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] const ElCheckbox: (typeof import('element-plus/es'))['ElCheckbox']
const ElDivider: typeof import('element-plus/es')['ElDivider'] const ElDivider: (typeof import('element-plus/es'))['ElDivider']
const ElInput: typeof import('element-plus/es')['ElInput'] const ElInput: (typeof import('element-plus/es'))['ElInput']
const ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] const ElInputNumber: (typeof import('element-plus/es'))['ElInputNumber']
const ElRadio: typeof import('element-plus/es')['ElRadio'] const ElRadio: (typeof import('element-plus/es'))['ElRadio']
const ElSelect: typeof import('element-plus/es')['ElSelect'] const ElSelect: (typeof import('element-plus/es'))['ElSelect']
const computed: typeof import('vue')['computed'] const computed: (typeof import('vue'))['computed']
const createApp: typeof import('vue')['createApp'] const createApp: (typeof import('vue'))['createApp']
const customRef: typeof import('vue')['customRef'] const customRef: (typeof import('vue'))['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent'] const defineComponent: (typeof import('vue'))['defineComponent']
const effectScope: typeof import('vue')['effectScope'] const effectScope: (typeof import('vue'))['effectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const getCurrentInstance: (typeof import('vue'))['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope'] const getCurrentScope: (typeof import('vue'))['getCurrentScope']
const h: typeof import('vue')['h'] const h: (typeof import('vue'))['h']
const inject: typeof import('vue')['inject'] const inject: (typeof import('vue'))['inject']
const isProxy: typeof import('vue')['isProxy'] const isProxy: (typeof import('vue'))['isProxy']
const isReactive: typeof import('vue')['isReactive'] const isReactive: (typeof import('vue'))['isReactive']
const isReadonly: typeof import('vue')['isReadonly'] const isReadonly: (typeof import('vue'))['isReadonly']
const isRef: typeof import('vue')['isRef'] const isRef: (typeof import('vue'))['isRef']
const markRaw: typeof import('vue')['markRaw'] const markRaw: (typeof import('vue'))['markRaw']
const nextTick: typeof import('vue')['nextTick'] const nextTick: (typeof import('vue'))['nextTick']
const onActivated: typeof import('vue')['onActivated'] const onActivated: (typeof import('vue'))['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount'] const onBeforeMount: (typeof import('vue'))['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] const onBeforeRouteLeave: (typeof import('vue-router'))['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] const onBeforeRouteUpdate: (typeof import('vue-router'))['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated'] const onDeactivated: (typeof import('vue'))['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured'] const onErrorCaptured: (typeof import('vue'))['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted'] const onMounted: (typeof import('vue'))['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked'] const onRenderTracked: (typeof import('vue'))['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered'] const onRenderTriggered: (typeof import('vue'))['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose'] const onScopeDispose: (typeof import('vue'))['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch'] const onServerPrefetch: (typeof import('vue'))['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted'] const onUnmounted: (typeof import('vue'))['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated'] const onUpdated: (typeof import('vue'))['onUpdated']
const provide: typeof import('vue')['provide'] const provide: (typeof import('vue'))['provide']
const reactive: typeof import('vue')['reactive'] const reactive: (typeof import('vue'))['reactive']
const readonly: typeof import('vue')['readonly'] const readonly: (typeof import('vue'))['readonly']
const ref: typeof import('vue')['ref'] const ref: (typeof import('vue'))['ref']
const resolveComponent: typeof import('vue')['resolveComponent'] const resolveComponent: (typeof import('vue'))['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive'] const shallowReactive: (typeof import('vue'))['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly'] const shallowReadonly: (typeof import('vue'))['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef'] const shallowRef: (typeof import('vue'))['shallowRef']
const toRaw: typeof import('vue')['toRaw'] const toRaw: (typeof import('vue'))['toRaw']
const toRef: typeof import('vue')['toRef'] const toRef: (typeof import('vue'))['toRef']
const toRefs: typeof import('vue')['toRefs'] const toRefs: (typeof import('vue'))['toRefs']
const toValue: typeof import('vue')['toValue'] const toValue: (typeof import('vue'))['toValue']
const triggerRef: typeof import('vue')['triggerRef'] const triggerRef: (typeof import('vue'))['triggerRef']
const unref: typeof import('vue')['unref'] const unref: (typeof import('vue'))['unref']
const useAttrs: typeof import('vue')['useAttrs'] const useAttrs: (typeof import('vue'))['useAttrs']
const useCssModule: typeof import('vue')['useCssModule'] const useCssModule: (typeof import('vue'))['useCssModule']
const useCssVars: typeof import('vue')['useCssVars'] const useCssVars: (typeof import('vue'))['useCssVars']
const useLink: typeof import('vue-router')['useLink'] const useLink: (typeof import('vue-router'))['useLink']
const useRoute: typeof import('vue-router')['useRoute'] const useRoute: (typeof import('vue-router'))['useRoute']
const useRouter: typeof import('vue-router')['useRouter'] const useRouter: (typeof import('vue-router'))['useRouter']
const useSlots: typeof import('vue')['useSlots'] const useSlots: (typeof import('vue'))['useSlots']
const watch: typeof import('vue')['watch'] const watch: (typeof import('vue'))['watch']
const watchEffect: typeof import('vue')['watchEffect'] const watchEffect: (typeof import('vue'))['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect'] const watchPostEffect: (typeof import('vue'))['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect'] const watchSyncEffect: (typeof import('vue'))['watchSyncEffect']
} }
// for type re-export // for type re-export
declare global { declare global {
// @ts-ignore // @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' export type {
Component,
ComponentPublicInstance,
ComputedRef,
ExtractDefaultPropTypes,
ExtractPropTypes,
ExtractPublicPropTypes,
InjectionKey,
PropType,
Ref,
VNode,
WritableComputedRef
} from 'vue'
import('vue') import('vue')
} }

View File

@ -9,6 +9,7 @@ declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
AdvancedFilter: typeof import('./../components/AdvancedFilter/index.vue')['default'] AdvancedFilter: typeof import('./../components/AdvancedFilter/index.vue')['default']
ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']

View File

@ -203,6 +203,7 @@ const addApproval = (node: FlowNode) => {
pid: node.id, pid: node.id,
type: 'approval', type: 'approval',
name: '审批人', name: '审批人',
executionListeners: [],
child: child, child: child,
// //
assigneeType: 'user', assigneeType: 'user',

View File

@ -1,22 +1,62 @@
<script setup lang="ts"></script> <script setup lang="ts">
import type { FlowNode } from '@/views/flowDesign/nodes/type'
import type { Ref } from 'vue'
const _inject = inject<{
readOnly?: Ref<boolean>
}>('flowDesign', { readOnly: ref(false) })
const $emits = defineEmits<{
(e: 'activeNode', node: FlowNode): void
}>()
const $props = withDefaults(
defineProps<{
node: FlowNode
readOnly?: boolean
}>(),
{
readOnly: false
}
)
const _readOnly = computed(() => _inject.readOnly?.value || $props.readOnly)
const activeNode = () => {
if (_readOnly.value) return
$emits('activeNode', $props.node)
}
</script>
<template> <template>
<div class="end-node"> <div class="node-box">
<div class="end-node-circle" /> <div class="end-node-circle"></div>
<el-text>流程结束</el-text> <div class="end-node" @click="activeNode">
<el-text>{{ node.name }}</el-text>
</div>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.end-node { .node-box {
padding: 0 0 30px; position: relative;
.end-node-circle { .end-node-circle {
width: 10px; width: 10px;
height: 10px; height: 10px;
border-radius: 50%; border-radius: 50%;
background-color: var(--el-border-color); background-color: var(--el-border-color);
margin: auto; margin: auto auto 5px;
}
.end-node {
position: relative;
background: var(--el-border-color-lighter);
padding: 7px 24px;
border-radius: 24px;
cursor: pointer;
overflow: visible;
z-index: 10;
&:hover {
box-shadow: 0 0 5px 0 var(--el-color-primary);
}
} }
} }
</style> </style>

View File

@ -15,9 +15,16 @@ export interface FlowNode {
pid?: string pid?: string
name: string name: string
type: NodeType type: NodeType
executionListeners?: NodeListener[]
child?: FlowNode child?: FlowNode
} }
export interface NodeListener {
event: string
implementationType: 'class' | 'expression' | 'delegateExpression'
implementation: string
}
export interface StartNode extends FlowNode { export interface StartNode extends FlowNode {
formProperties: FormProperty[] formProperties: FormProperty[]
} }
@ -77,6 +84,8 @@ export interface ApprovalNode extends AssigneeNode {
formProperties: FormProperty[] formProperties: FormProperty[]
// 操作权限 // 操作权限
operations: OperationPermissions operations: OperationPermissions
// 任务监听器
taskListeners?: NodeListener[]
} }
export interface TimerNode extends FlowNode { export interface TimerNode extends FlowNode {

View File

@ -4,6 +4,7 @@ import type { Field } from '@/components/Render/type'
import UserSelector from '@/components/UserSelector/index.vue' import UserSelector from '@/components/UserSelector/index.vue'
import AssigneePanel from './AssigneePanel.vue' import AssigneePanel from './AssigneePanel.vue'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import TaskListeners from './TaskListeners.vue'
const { fields } = inject<{ fields: Ref<Field[]>; admin: string[] }>('flowDesign', { const { fields } = inject<{ fields: Ref<Field[]>; admin: string[] }>('flowDesign', {
fields: ref([]), fields: ref([]),
@ -133,6 +134,9 @@ watchEffect(() => {
placeholder="指定人员" placeholder="指定人员"
/> />
</el-form-item> </el-form-item>
<el-form-item prop="taskListeners" label="任务监听器">
<TaskListeners :node="activeData" />
</el-form-item>
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="表单权限" name="formPermissions"> <el-tab-pane label="表单权限" name="formPermissions">

View File

@ -0,0 +1,18 @@
<script setup lang="ts">
import type { EndNode } from '../nodes/type'
import ExecutionListeners from './ExecutionListeners.vue'
defineProps<{
activeData: EndNode
}>()
</script>
<template>
<el-form label-position="top" label-width="90px">
<el-form-item prop="executionListeners" label="执行监听器">
<ExecutionListeners :node="activeData" />
</el-form-item>
</el-form>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,107 @@
<script setup lang="ts">
import type { FlowNode } from '@/views/flowDesign/nodes/type'
const props = defineProps<{
node: FlowNode
}>()
const drawer = ref(false)
const addListener = () => {
if (!props.node.executionListeners) {
props.node.executionListeners = []
}
props.node.executionListeners?.push({
event: 'start',
implementationType: 'delegateExpression',
implementation: ''
})
}
const delListener = (index: number) => {
props.node.executionListeners?.splice(index, 1)
}
</script>
<template>
<div>
<slot>
<el-badge :value="node.executionListeners?.length || 0" class="item" type="primary">
<el-button icon="Setting" @click="drawer = true"> 配置</el-button>
</el-badge>
</slot>
<el-drawer v-model="drawer" title="执行监听器">
<div class="flex-col">
<el-button @click="addListener" type="primary" icon="Plus">添加监听器</el-button>
<div v-for="(item, index) in node.executionListeners" :key="index" class="listener-box">
<el-button
class="listener-close"
@click="delListener(index)"
plain
circle
icon="CircleClose"
size="small"
type="danger"
/>
<el-form-item label="事件" :prop="`executionListeners.${index}.event`">
<el-radio-group v-model="item.event">
<el-radio-button label="开始" value="start" />
<el-radio-button label="结束" value="end" />
<el-radio-button label="迁移" value="take" />
</el-radio-group>
</el-form-item>
<el-form-item label="类型" :prop="`executionListeners.${index}.implementationType`">
<el-radio-group v-model="item.implementationType">
<el-radio-button label="委托表达式" value="delegateExpression" />
<el-radio-button label="java类" value="class" />
<el-radio-button label="表达式" value="expression" />
</el-radio-group>
</el-form-item>
<el-form-item label="监听器" :prop="`executionListeners.${index}.implementation`">
<template #label>
<div class="flex-items-center gap3px">
<span>监听器</span>
<el-tooltip placement="top-start">
<template #content>
实现 ExecutionListener 接口 <br />
委托表达式${myExecutionListener} <br />
表达式: ${myExecutionListener.notify(execution)} <br />
java类${com.example.listener.MyExecutionListener}
</template>
<el-icon>
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input v-model="item.implementation" placeholder="请输入监听器" clearable>
</el-input>
</el-form-item>
</div>
</div>
</el-drawer>
</div>
</template>
<style scoped lang="scss">
.listener-box {
border: 1px dashed var(--el-border-color);
border-radius: var(--el-border-radius-base);
margin-top: 10px;
padding: 7px;
position: relative;
&:hover {
border-color: var(--el-color-primary);
.listener-close {
display: block;
}
}
.listener-close {
position: absolute;
top: -7px;
right: -7px;
z-index: 1;
display: none;
}
}
</style>

View File

@ -2,11 +2,13 @@
import type { FormProperty, StartNode } from '../nodes/type' import type { FormProperty, StartNode } from '../nodes/type'
import type { Field } from '@/components/Render/type' import type { Field } from '@/components/Render/type'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import ExecutionListeners from './ExecutionListeners.vue'
const { fields } = inject<{ fields: Ref<Field[]> }>('flowDesign', { fields: ref([]) }) const { fields } = inject<{ fields: Ref<Field[]> }>('flowDesign', { fields: ref([]) })
const props = defineProps<{ const props = defineProps<{
activeData: StartNode activeData: StartNode
}>() }>()
const activeName = ref('basicSettings')
const allReadonly = computed({ const allReadonly = computed({
get() { get() {
return props.activeData.formProperties.every((e) => e.readonly) return props.activeData.formProperties.every((e) => e.readonly)
@ -84,33 +86,46 @@ watchEffect(() => {
</script> </script>
<template> <template>
<el-table :data="activeData.formProperties"> <el-tabs v-model="activeName" stretch class="el-segmented">
<el-table-column prop="name" label="字段" /> <el-tab-pane label="基础设置" name="basicSettings">
<el-table-column prop="readonly"> <el-form label-position="top" label-width="90px">
<template #header> <el-form-item prop="executionListeners" label="执行监听器">
<el-checkbox v-model="allReadonly" label="只读" /> <ExecutionListeners :node="activeData" />
</template> </el-form-item>
<template #default="{ row }"> </el-form>
<el-checkbox v-model="row.readonly" @change="changeReadonly(row)" /> </el-tab-pane>
</template> <el-tab-pane label="表单权限" name="formPermissions">
</el-table-column> <el-table :data="activeData.formProperties">
<el-table-column prop="required"> <el-table-column prop="name" label="字段" />
<template #header> <el-table-column prop="readonly">
<el-checkbox v-model="allRequired" label="必填" /> <template #header>
</template> <el-checkbox v-model="allReadonly" label="只读" />
<template #default="{ row }"> </template>
<el-checkbox v-model="row.required" @change="changeRequired(row)" /> <template #default="{ row }">
</template> <el-checkbox v-model="row.readonly" @change="changeReadonly(row)" />
</el-table-column> </template>
<el-table-column prop="hidden"> </el-table-column>
<template #header> <el-table-column prop="required">
<el-checkbox v-model="allHidden" label="隐藏" /> <template #header>
</template> <el-checkbox v-model="allRequired" label="必填" />
<template #default="{ row }"> </template>
<el-checkbox v-model="row.hidden" @change="changeHidden(row)" /> <template #default="{ row }">
</template> <el-checkbox v-model="row.required" @change="changeRequired(row)" />
</el-table-column> </template>
</el-table> </el-table-column>
<el-table-column prop="hidden">
<template #header>
<el-checkbox v-model="allHidden" label="隐藏" />
</template>
<template #default="{ row }">
<el-checkbox v-model="row.hidden" @change="changeHidden(row)" />
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</template> </template>
<style scoped lang="scss"></style> <style scoped lang="scss">
@import '@/styles/el-segmented.scss';
</style>

View File

@ -0,0 +1,107 @@
<script setup lang="ts">
import type { ApprovalNode } from '@/views/flowDesign/nodes/type'
const props = defineProps<{
node: ApprovalNode
}>()
const drawer = ref(false)
const addListener = () => {
if (!props.node.taskListeners) {
props.node.taskListeners = []
}
props.node.taskListeners?.push({
event: 'create',
implementationType: 'delegateExpression',
implementation: ''
})
}
const delListener = (index: number) => {
props.node.taskListeners?.splice(index, 1)
}
</script>
<template>
<div>
<slot>
<el-badge :value="node.taskListeners?.length || 0" class="item" type="primary">
<el-button icon="Setting" @click="drawer = true"> 配置</el-button>
</el-badge>
</slot>
<el-drawer v-model="drawer" title="任务监听器">
<div class="flex-col">
<el-button @click="addListener" type="primary" icon="Plus">添加监听器</el-button>
<div v-for="(item, index) in node.taskListeners" :key="index" class="listener-box">
<el-button
class="listener-close"
@click="delListener(index)"
plain
circle
icon="CircleClose"
size="small"
type="danger"
/>
<el-form-item label="事件" :prop="`taskListeners.${index}.event`">
<el-radio-group v-model="item.event">
<el-radio-button label="创建" value="create" />
<el-radio-button label="指派" value="assignment" />
<el-radio-button label="完成" value="complete" />
<el-radio-button label="删除" value="delete" />
</el-radio-group>
</el-form-item>
<el-form-item label="类型" :prop="`taskListeners.${index}.implementationType`">
<el-radio-group v-model="item.implementationType">
<el-radio-button label="委托表达式" value="delegateExpression" />
<el-radio-button label="java类" value="class" />
<el-radio-button label="表达式" value="expression" />
</el-radio-group>
</el-form-item>
<el-form-item label="监听器" :prop="`taskListeners.${index}.implementation`">
<template #label>
<div class="flex-items-center gap3px">
<span>监听器</span>
<el-tooltip placement="top-start">
<template #content>
委托表达式${myCreateTaskListener} <br />
表达式: ${myCreateTaskListener.notify(execution)} <br />
java类${com.example.listener.MyCreateTaskListener}
</template>
<el-icon>
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<el-input v-model="item.implementation" placeholder="请输入监听器" clearable>
</el-input>
</el-form-item>
</div>
</div>
</el-drawer>
</div>
</template>
<style scoped lang="scss">
.listener-box {
border: 1px dashed var(--el-border-color);
border-radius: var(--el-border-radius-base);
margin-top: 10px;
padding: 7px;
position: relative;
&:hover {
border-color: var(--el-color-primary);
.listener-close {
display: block;
}
}
.listener-close {
position: absolute;
top: -7px;
right: -7px;
z-index: 1;
display: none;
}
}
</style>

View File

@ -7,6 +7,7 @@ import Cc from './CcPanel.vue'
import Timer from './TimerPanel.vue' import Timer from './TimerPanel.vue'
import Notify from './NotifyPanel.vue' import Notify from './NotifyPanel.vue'
import Condition from './ConditionPanel.vue' import Condition from './ConditionPanel.vue'
import End from './EndPanel.vue'
import type { FlowNode } from '../nodes/type' import type { FlowNode } from '../nodes/type'
defineProps<{ defineProps<{
@ -19,7 +20,8 @@ const panels: Recordable<Component> = {
cc: Cc, cc: Cc,
timer: Timer, timer: Timer,
notify: Notify, notify: Notify,
condition: Condition condition: Condition,
end: End
} }
const showInput = ref(false) const showInput = ref(false)
const onClickOutside = () => { const onClickOutside = () => {

View File

@ -10,12 +10,14 @@ const process = ref<FlowNode>({
pid: undefined, pid: undefined,
type: 'start', type: 'start',
name: '发起人', name: '发起人',
executionListeners: [],
formProperties: [], formProperties: [],
child: { child: {
id: 'end', id: 'end',
pid: 'root', pid: 'root',
type: 'end', type: 'end',
name: '结束', name: '流程结束',
executionListeners: [],
child: undefined child: undefined
} as EndNode } as EndNode
} as StartNode) } as StartNode)