执行监听器

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
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
const ElDivider: typeof import('element-plus/es')['ElDivider']
const ElInput: typeof import('element-plus/es')['ElInput']
const ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
const ElRadio: typeof import('element-plus/es')['ElRadio']
const ElSelect: typeof import('element-plus/es')['ElSelect']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const effectScope: typeof import('vue')['effectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useLink: typeof import('vue-router')['useLink']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
const EffectScope: (typeof import('vue'))['EffectScope']
const ElCheckbox: (typeof import('element-plus/es'))['ElCheckbox']
const ElDivider: (typeof import('element-plus/es'))['ElDivider']
const ElInput: (typeof import('element-plus/es'))['ElInput']
const ElInputNumber: (typeof import('element-plus/es'))['ElInputNumber']
const ElRadio: (typeof import('element-plus/es'))['ElRadio']
const ElSelect: (typeof import('element-plus/es'))['ElSelect']
const computed: (typeof import('vue'))['computed']
const createApp: (typeof import('vue'))['createApp']
const customRef: (typeof import('vue'))['customRef']
const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent']
const defineComponent: (typeof import('vue'))['defineComponent']
const effectScope: (typeof import('vue'))['effectScope']
const getCurrentInstance: (typeof import('vue'))['getCurrentInstance']
const getCurrentScope: (typeof import('vue'))['getCurrentScope']
const h: (typeof import('vue'))['h']
const inject: (typeof import('vue'))['inject']
const isProxy: (typeof import('vue'))['isProxy']
const isReactive: (typeof import('vue'))['isReactive']
const isReadonly: (typeof import('vue'))['isReadonly']
const isRef: (typeof import('vue'))['isRef']
const markRaw: (typeof import('vue'))['markRaw']
const nextTick: (typeof import('vue'))['nextTick']
const onActivated: (typeof import('vue'))['onActivated']
const onBeforeMount: (typeof import('vue'))['onBeforeMount']
const onBeforeRouteLeave: (typeof import('vue-router'))['onBeforeRouteLeave']
const onBeforeRouteUpdate: (typeof import('vue-router'))['onBeforeRouteUpdate']
const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount']
const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate']
const onDeactivated: (typeof import('vue'))['onDeactivated']
const onErrorCaptured: (typeof import('vue'))['onErrorCaptured']
const onMounted: (typeof import('vue'))['onMounted']
const onRenderTracked: (typeof import('vue'))['onRenderTracked']
const onRenderTriggered: (typeof import('vue'))['onRenderTriggered']
const onScopeDispose: (typeof import('vue'))['onScopeDispose']
const onServerPrefetch: (typeof import('vue'))['onServerPrefetch']
const onUnmounted: (typeof import('vue'))['onUnmounted']
const onUpdated: (typeof import('vue'))['onUpdated']
const provide: (typeof import('vue'))['provide']
const reactive: (typeof import('vue'))['reactive']
const readonly: (typeof import('vue'))['readonly']
const ref: (typeof import('vue'))['ref']
const resolveComponent: (typeof import('vue'))['resolveComponent']
const shallowReactive: (typeof import('vue'))['shallowReactive']
const shallowReadonly: (typeof import('vue'))['shallowReadonly']
const shallowRef: (typeof import('vue'))['shallowRef']
const toRaw: (typeof import('vue'))['toRaw']
const toRef: (typeof import('vue'))['toRef']
const toRefs: (typeof import('vue'))['toRefs']
const toValue: (typeof import('vue'))['toValue']
const triggerRef: (typeof import('vue'))['triggerRef']
const unref: (typeof import('vue'))['unref']
const useAttrs: (typeof import('vue'))['useAttrs']
const useCssModule: (typeof import('vue'))['useCssModule']
const useCssVars: (typeof import('vue'))['useCssVars']
const useLink: (typeof import('vue-router'))['useLink']
const useRoute: (typeof import('vue-router'))['useRoute']
const useRouter: (typeof import('vue-router'))['useRouter']
const useSlots: (typeof import('vue'))['useSlots']
const watch: (typeof import('vue'))['watch']
const watchEffect: (typeof import('vue'))['watchEffect']
const watchPostEffect: (typeof import('vue'))['watchPostEffect']
const watchSyncEffect: (typeof import('vue'))['watchSyncEffect']
}
// for type re-export
declare global {
// @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')
}

View File

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

View File

@ -203,6 +203,7 @@ const addApproval = (node: FlowNode) => {
pid: node.id,
type: 'approval',
name: '审批人',
executionListeners: [],
child: child,
//
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>
<div class="end-node">
<div class="end-node-circle" />
<el-text>流程结束</el-text>
<div class="node-box">
<div class="end-node-circle"></div>
<div class="end-node" @click="activeNode">
<el-text>{{ node.name }}</el-text>
</div>
</div>
</template>
<style scoped lang="scss">
.end-node {
padding: 0 0 30px;
.node-box {
position: relative;
.end-node-circle {
width: 10px;
height: 10px;
border-radius: 50%;
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>

View File

@ -15,9 +15,16 @@ export interface FlowNode {
pid?: string
name: string
type: NodeType
executionListeners?: NodeListener[]
child?: FlowNode
}
export interface NodeListener {
event: string
implementationType: 'class' | 'expression' | 'delegateExpression'
implementation: string
}
export interface StartNode extends FlowNode {
formProperties: FormProperty[]
}
@ -77,6 +84,8 @@ export interface ApprovalNode extends AssigneeNode {
formProperties: FormProperty[]
// 操作权限
operations: OperationPermissions
// 任务监听器
taskListeners?: NodeListener[]
}
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 AssigneePanel from './AssigneePanel.vue'
import type { Ref } from 'vue'
import TaskListeners from './TaskListeners.vue'
const { fields } = inject<{ fields: Ref<Field[]>; admin: string[] }>('flowDesign', {
fields: ref([]),
@ -133,6 +134,9 @@ watchEffect(() => {
placeholder="指定人员"
/>
</el-form-item>
<el-form-item prop="taskListeners" label="任务监听器">
<TaskListeners :node="activeData" />
</el-form-item>
</el-form>
</el-tab-pane>
<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 { Field } from '@/components/Render/type'
import type { Ref } from 'vue'
import ExecutionListeners from './ExecutionListeners.vue'
const { fields } = inject<{ fields: Ref<Field[]> }>('flowDesign', { fields: ref([]) })
const props = defineProps<{
activeData: StartNode
}>()
const activeName = ref('basicSettings')
const allReadonly = computed({
get() {
return props.activeData.formProperties.every((e) => e.readonly)
@ -84,33 +86,46 @@ watchEffect(() => {
</script>
<template>
<el-table :data="activeData.formProperties">
<el-table-column prop="name" label="字段" />
<el-table-column prop="readonly">
<template #header>
<el-checkbox v-model="allReadonly" label="只读" />
</template>
<template #default="{ row }">
<el-checkbox v-model="row.readonly" @change="changeReadonly(row)" />
</template>
</el-table-column>
<el-table-column prop="required">
<template #header>
<el-checkbox v-model="allRequired" label="必填" />
</template>
<template #default="{ row }">
<el-checkbox v-model="row.required" @change="changeRequired(row)" />
</template>
</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-tabs v-model="activeName" stretch class="el-segmented">
<el-tab-pane label="基础设置" name="basicSettings">
<el-form label-position="top" label-width="90px">
<el-form-item prop="executionListeners" label="执行监听器">
<ExecutionListeners :node="activeData" />
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="表单权限" name="formPermissions">
<el-table :data="activeData.formProperties">
<el-table-column prop="name" label="字段" />
<el-table-column prop="readonly">
<template #header>
<el-checkbox v-model="allReadonly" label="只读" />
</template>
<template #default="{ row }">
<el-checkbox v-model="row.readonly" @change="changeReadonly(row)" />
</template>
</el-table-column>
<el-table-column prop="required">
<template #header>
<el-checkbox v-model="allRequired" label="必填" />
</template>
<template #default="{ row }">
<el-checkbox v-model="row.required" @change="changeRequired(row)" />
</template>
</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>
<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 Notify from './NotifyPanel.vue'
import Condition from './ConditionPanel.vue'
import End from './EndPanel.vue'
import type { FlowNode } from '../nodes/type'
defineProps<{
@ -19,7 +20,8 @@ const panels: Recordable<Component> = {
cc: Cc,
timer: Timer,
notify: Notify,
condition: Condition
condition: Condition,
end: End
}
const showInput = ref(false)
const onClickOutside = () => {

View File

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