feat: 新增条件分支编辑与详情抽屉
This commit is contained in:
parent
11e8188559
commit
63285b755f
@ -97,3 +97,11 @@ export function fetchWorkflowNodeRetry(id: string, workflowNodeId: number) {
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchCheckNodeExpression(expression: Flow.BrachNodeType) {
|
||||
return request<{ key: number; value: string }>({
|
||||
url: '/workflow/check-node-expression',
|
||||
method: 'post',
|
||||
data: expression
|
||||
});
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { useFlowStore } from '../stores';
|
||||
import { fetchBatchDetail, fetchJobDetail, fetchTaskList, fetchWorkflowNodeRetry } from '../api';
|
||||
import { executorTypeRecord, operationReasonRecord, taskBatchStatusRecord } from '../constants/business';
|
||||
import { $t } from '../locales';
|
||||
import { isNotNull } from '../utils/common';
|
||||
import LogDrawer from './log-drawer.vue';
|
||||
|
||||
defineOptions({
|
||||
@ -19,6 +20,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
id: undefined,
|
||||
show: false,
|
||||
ids: () => []
|
||||
});
|
||||
@ -267,8 +269,8 @@ const onUpdatePage = (page: number) => {
|
||||
<NDescriptionsItem :label="$t('snail.jobBatch.jobName')">{{ jobData?.jobName }}</NDescriptionsItem>
|
||||
|
||||
<NDescriptionsItem :label="$t('snail.jobBatch.taskBatchStatus')">
|
||||
<NTag v-if="jobData.taskBatchStatus" :type="tagColor(jobData?.taskBatchStatus!)">
|
||||
{{ $t(taskBatchStatusRecord[jobData?.taskBatchStatus]) }}
|
||||
<NTag v-if="isNotNull(jobData.taskBatchStatus)" :type="tagColor(jobData.taskBatchStatus!)">
|
||||
{{ $t(taskBatchStatusRecord[jobData.taskBatchStatus!]) }}
|
||||
</NTag>
|
||||
</NDescriptionsItem>
|
||||
|
||||
@ -277,14 +279,14 @@ const onUpdatePage = (page: number) => {
|
||||
</NDescriptionsItem>
|
||||
|
||||
<NDescriptionsItem :label="$t('snail.jobBatch.operationReason')">
|
||||
<NTag v-if="jobData.operationReason" :type="tagColor(jobData?.operationReason!)">
|
||||
{{ $t(operationReasonRecord[jobData.operationReason]) }}
|
||||
<NTag v-if="isNotNull(jobData.operationReason)" :type="tagColor(jobData.operationReason!)">
|
||||
{{ $t(operationReasonRecord[jobData.operationReason!]) }}
|
||||
</NTag>
|
||||
</NDescriptionsItem>
|
||||
|
||||
<NDescriptionsItem v-if="!slots.default" :label="$t('snail.jobBatch.executorType')">
|
||||
<NTag v-if="jobData.executorType" :type="tagColor(jobData?.executorType!)">
|
||||
{{ $t(executorTypeRecord[jobData?.executorType!]) }}
|
||||
<NTag v-if="isNotNull(jobData.executorType)" :type="tagColor(jobData.executorType!)">
|
||||
{{ $t(executorTypeRecord[jobData.executorType!]) }}
|
||||
</NTag>
|
||||
</NDescriptionsItem>
|
||||
|
||||
|
111
packages/work-flow/src/detail/branch-detail.vue
Normal file
111
packages/work-flow/src/detail/branch-detail.vue
Normal file
@ -0,0 +1,111 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref, watch } from 'vue';
|
||||
import CodeMirror from 'vue-codemirror6';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { expressionRecord, logicalConditionRecord } from '../constants/business';
|
||||
|
||||
defineOptions({
|
||||
name: 'BranchDetail'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
modelValue?: Flow.ConditionNodeType;
|
||||
open?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
open: false,
|
||||
modelValue: () => ({})
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:open', open: boolean): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = ref(false);
|
||||
const nodeExpression = ref('');
|
||||
|
||||
const theme = ref({
|
||||
'.cm-line': {
|
||||
fontSize: '18px'
|
||||
},
|
||||
'.cm-scroller': {
|
||||
height: '520px',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden'
|
||||
}
|
||||
});
|
||||
|
||||
const onClose = () => {
|
||||
emit('update:open', false);
|
||||
};
|
||||
|
||||
const createDocument = () => {
|
||||
const desc = document.getElementById('branch-desc');
|
||||
const descriptions = desc?.querySelector('.n-descriptions-table');
|
||||
const body = descriptions?.querySelector('tbody');
|
||||
const tr = document.createElement('tr');
|
||||
tr.className = 'n-descriptions-table-row';
|
||||
const th = document.createElement('th');
|
||||
th.className = 'n-descriptions-table-header';
|
||||
th.innerHTML = '条件表达式';
|
||||
th.setAttribute('colspan', '4');
|
||||
tr.appendChild(th);
|
||||
body?.insertBefore(tr, body?.childNodes[4]);
|
||||
|
||||
const rows: HTMLCollectionOf<Element> = body!.getElementsByClassName('n-descriptions-table-row')!;
|
||||
|
||||
const element = rows[3] as HTMLElement;
|
||||
|
||||
element.querySelector('.n-descriptions-table-header')?.remove();
|
||||
const content = element.querySelector('.n-descriptions-table-content') as HTMLElement;
|
||||
content.setAttribute('style', 'padding: 0');
|
||||
content?.setAttribute('colspan', '4');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
val => {
|
||||
visible.value = val;
|
||||
if (val) {
|
||||
nextTick(() => {
|
||||
createDocument();
|
||||
});
|
||||
if (props.modelValue.decision?.nodeExpression) {
|
||||
nodeExpression.value = props.modelValue.decision.nodeExpression;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" placement="right" :width="500" display-directive="if" @after-leave="onClose">
|
||||
<NDrawerContent title="决策详情">
|
||||
<NDescriptions id="branch-desc" :column="2" label-placement="left" bordered :label-style="{ width: '120px' }">
|
||||
<NDescriptionsItem label="节点名称" :span="2">{{ modelValue.nodeName }}</NDescriptionsItem>
|
||||
<NDescriptionsItem label="判定逻辑">
|
||||
{{ logicalConditionRecord[modelValue.decision?.logicalCondition!] }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="表达式类型">
|
||||
{{ expressionRecord[modelValue.decision?.expressionType!] }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="条件表达式" :span="2" :content-style="{ padding: 0 }">
|
||||
<CodeMirror
|
||||
v-model="nodeExpression"
|
||||
readonly
|
||||
disabled
|
||||
:theme="theme"
|
||||
basic
|
||||
:lang="javascript()"
|
||||
:extensions="[oneDark]"
|
||||
/>
|
||||
</NDescriptionsItem>
|
||||
</NDescriptions>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
204
packages/work-flow/src/drawer/branch-drawer.vue
Normal file
204
packages/work-flow/src/drawer/branch-drawer.vue
Normal file
@ -0,0 +1,204 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import CodeMirror from 'vue-codemirror6';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import EditableInput from '../common/editable-input.vue';
|
||||
import { fetchCheckNodeExpression } from '../api';
|
||||
import { expressionOptions, logicalConditionOptions } from '../constants/business';
|
||||
|
||||
defineOptions({
|
||||
name: 'BranchDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
open?: boolean;
|
||||
len?: number;
|
||||
modelValue?: Flow.ConditionNodeType;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
open: false,
|
||||
len: 0,
|
||||
modelValue: () => ({})
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:open', open: boolean): void;
|
||||
(e: 'save', form: Flow.NodeDataType): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const drawer = ref<boolean>(false);
|
||||
const form = ref<Flow.ConditionNodeType>({
|
||||
decision: {
|
||||
logicalCondition: 1,
|
||||
expressionType: 1
|
||||
}
|
||||
});
|
||||
|
||||
const theme = ref({
|
||||
'.cm-line': {
|
||||
fontSize: '18px'
|
||||
},
|
||||
'.cm-scroller': {
|
||||
height: '500px',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden'
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
val => {
|
||||
drawer.value = val;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
val => {
|
||||
form.value = val;
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
const formRef = ref<FormInst>();
|
||||
|
||||
const close = () => {
|
||||
emit('update:open', false);
|
||||
drawer.value = false;
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
formRef.value
|
||||
?.validate(errors => {
|
||||
if (!errors) {
|
||||
close();
|
||||
emit('save', form.value);
|
||||
}
|
||||
})
|
||||
.catch(() => window.$message?.warning('请检查表单信息'));
|
||||
};
|
||||
|
||||
const checkNodeExpression = async () => {
|
||||
if (!form.value.decision?.nodeExpression) {
|
||||
return Promise.reject(new Error('请填写条件表达式'));
|
||||
}
|
||||
const { error, data } = await fetchCheckNodeExpression(form.value.decision!);
|
||||
if (!error) {
|
||||
if (data.key !== 1) {
|
||||
return Promise.reject(data.value ?? '请检查条件表达式');
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const rules: FormRules = {
|
||||
decision: {
|
||||
logicalCondition: [{ required: true, message: '请选择判定逻辑', trigger: 'change', type: 'number' }],
|
||||
expressionType: [{ required: true, message: '请选择表达式类型', trigger: 'change', type: 'number' }],
|
||||
nodeExpression: [{ required: true, validator: checkNodeExpression, trigger: 'blur' }]
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="drawer" display-directive="if" :width="500" @after-leave="close">
|
||||
<NDrawerContent>
|
||||
<template #header>
|
||||
<div class="w-460px flex-center">
|
||||
<EditableInput v-model="form.nodeName" class="mr-16px max-w-320px min-w-320px" />
|
||||
|
||||
<NSelect
|
||||
v-model:value="form.priorityLevel"
|
||||
class="max-w-110px"
|
||||
:options="
|
||||
Array(len)
|
||||
.fill(0)
|
||||
.map((_, index) => {
|
||||
return {
|
||||
label: '优先级 ' + (index + 1),
|
||||
value: index + 1
|
||||
};
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<NForm ref="formRef" :rules="rules" :model="form" label-align="left" label-width="100px">
|
||||
<NFormItem path="decision.logicalCondition" label="判定逻辑">
|
||||
<NRadioGroup v-model:value="form.decision!.logicalCondition">
|
||||
<NSpace>
|
||||
<NRadio
|
||||
v-for="logical in logicalConditionOptions"
|
||||
:key="logical.value"
|
||||
:label="logical.label"
|
||||
:value="logical.value"
|
||||
/>
|
||||
</NSpace>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
<NFormItem path="decision.expressionType" label="表达式类型">
|
||||
<NRadioGroup v-model:value="form.decision!.expressionType">
|
||||
<NSpace>
|
||||
<NRadio
|
||||
v-for="strategy in expressionOptions"
|
||||
:key="strategy.value"
|
||||
:label="strategy.label"
|
||||
:value="strategy.value"
|
||||
/>
|
||||
</NSpace>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
<NFormItem path="decision.nodeExpression" label="条件表达式">
|
||||
<CodeMirror
|
||||
v-model="form.decision!.nodeExpression"
|
||||
class="sj-code-mirror"
|
||||
:theme="theme"
|
||||
basic
|
||||
:lang="javascript()"
|
||||
:extensions="[oneDark]"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
|
||||
<template #footer>
|
||||
<NButton type="primary" @click="save">保存</NButton>
|
||||
<NButton class="ml-12px" @click="close">取消</NButton>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.drawer-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.sj-code-mirror {
|
||||
width: 100%;
|
||||
|
||||
.cm-scroller::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.cm-scroller::-webkit-scrollbar-thumb {
|
||||
background: #9c9c9c9c;
|
||||
-webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.cm-scroller::-webkit-scrollbar-track {
|
||||
background: #282c34;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import CronInput from '@sa/cron-input';
|
||||
import { type FormInst, type FormItemRule, useMessage } from 'naive-ui';
|
||||
import { type FormInst, type FormItemRule } from 'naive-ui';
|
||||
import { blockStrategyOptions, triggerTypeOptions, workFlowNodeStatusOptions } from '../constants/business';
|
||||
import { $t } from '../locales';
|
||||
import { fetchGroupNameList } from '../api';
|
||||
@ -30,7 +30,6 @@ interface Emits {
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const message = useMessage();
|
||||
const store = useFlowStore();
|
||||
|
||||
let title: string = '';
|
||||
@ -73,14 +72,13 @@ const close = () => {
|
||||
|
||||
const save = () => {
|
||||
formRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
close();
|
||||
emit('save', form.value);
|
||||
?.validate(errors => {
|
||||
if (!errors) {
|
||||
close();
|
||||
emit('save', form.value);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
message.warning('请检查表单信息');
|
||||
});
|
||||
.catch(() => window.$message?.warning('请检查表单信息'));
|
||||
};
|
||||
|
||||
const getGroupNameList = async () => {
|
||||
@ -145,7 +143,14 @@ const rules: Record<RuleKey, FormItemRule> = {
|
||||
<NSelect
|
||||
v-model:value="form.triggerType"
|
||||
placeholder="请选择触发类型"
|
||||
:options="triggerTypeOptions"
|
||||
:options="
|
||||
triggerTypeOptions.map(option => {
|
||||
return {
|
||||
label: $t(option.label),
|
||||
value: option.value
|
||||
};
|
||||
})
|
||||
"
|
||||
@update:value="typeChange"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { type FormInst, useMessage } from 'naive-ui';
|
||||
import { type FormInst } from 'naive-ui';
|
||||
import { useFlowStore } from '../stores';
|
||||
import { $t } from '../locales';
|
||||
import { failStrategyOptions, workFlowNodeStatusOptions } from '../constants/business';
|
||||
@ -30,7 +30,6 @@ interface Emits {
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const store = useFlowStore();
|
||||
const message = useMessage();
|
||||
const drawer = ref<boolean>(false);
|
||||
const form = ref<Flow.ConditionNodeType>({});
|
||||
const jobList = ref<{ id: string; jobName: string }[]>([]);
|
||||
@ -67,14 +66,14 @@ const close = () => {
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
formRef.value?.validate(errors => {
|
||||
if (!errors) {
|
||||
close();
|
||||
emit('save', form.value);
|
||||
} else {
|
||||
message.warning('请检查表单信息');
|
||||
}
|
||||
});
|
||||
formRef.value
|
||||
?.validate(errors => {
|
||||
if (!errors) {
|
||||
close();
|
||||
emit('save', form.value);
|
||||
}
|
||||
})
|
||||
.catch(() => window.$message?.warning('请检查表单信息'));
|
||||
};
|
||||
|
||||
const rules = {
|
||||
|
@ -3,6 +3,8 @@ import { nextTick, ref, watch } from 'vue';
|
||||
import { $t } from '../locales';
|
||||
import { useFlowStore } from '../stores';
|
||||
import { expressionRecord, logicalConditionRecord, taskBatchStatusEnum } from '../constants/business';
|
||||
import BranchDrawer from '../drawer/branch-drawer.vue';
|
||||
import BranchDetail from '../detail/branch-detail.vue';
|
||||
import AddNode from './add-node.vue';
|
||||
|
||||
defineOptions({
|
||||
@ -111,15 +113,15 @@ const drawer = ref<boolean>(false);
|
||||
const detailDrawer = ref<boolean[]>([]);
|
||||
const form = ref<Flow.ConditionNodeType>({});
|
||||
|
||||
// const save = (val: Flow.ConditionNodeType) => {
|
||||
// const oldLevel = nodeConfig.value.conditionNodes![currentIndex.value].priorityLevel;
|
||||
// const newLevel = val.priorityLevel;
|
||||
// nodeConfig.value.conditionNodes![currentIndex.value] = val;
|
||||
// if (oldLevel !== newLevel) {
|
||||
// arrTransfer(currentIndex.value, newLevel! - oldLevel!);
|
||||
// }
|
||||
// emit('update:modelValue', nodeConfig.value);
|
||||
// };
|
||||
const save = (val: Flow.ConditionNodeType) => {
|
||||
const oldLevel = nodeConfig.value.conditionNodes![currentIndex.value].priorityLevel;
|
||||
const newLevel = val.priorityLevel;
|
||||
nodeConfig.value.conditionNodes![currentIndex.value] = val;
|
||||
if (oldLevel !== newLevel) {
|
||||
arrTransfer(currentIndex.value, newLevel! - oldLevel!);
|
||||
}
|
||||
emit('update:modelValue', nodeConfig.value);
|
||||
};
|
||||
|
||||
const show = (index: number) => {
|
||||
if (!props.disabled && index !== nodeConfig.value.conditionNodes!.length - 1) {
|
||||
@ -247,13 +249,12 @@ const getClass = (item: Flow.ConditionNodeType) => {
|
||||
<div v-if="index == 0" class="bottom-left-cover-line"></div>
|
||||
<div v-if="index == nodeConfig.conditionNodes!.length - 1" class="top-right-cover-line"></div>
|
||||
<div v-if="index == nodeConfig.conditionNodes!.length - 1" class="bottom-right-cover-line"></div>
|
||||
<!--
|
||||
<BranchDetail
|
||||
v-if="store.type !== 0 && detailDrawer[index]"
|
||||
<BranchDetail
|
||||
v-if="store.type !== 0"
|
||||
v-model:open="detailDrawer[index]"
|
||||
v-model="nodeConfig.conditionNodes![index]"
|
||||
/>
|
||||
-->
|
||||
|
||||
<!--
|
||||
<DetailCard
|
||||
v-if="store.type !== 0 && cardDrawer[index]"
|
||||
@ -271,7 +272,7 @@ const getClass = (item: Flow.ConditionNodeType) => {
|
||||
</div>
|
||||
<AddNode v-model="nodeConfig.childNode!" :disabled="disabled"></AddNode>
|
||||
</div>
|
||||
<!-- <BranchDrawer v-model:open="drawer" v-model="form" v-model:len="nodeConfig.conditionNodes!.length" @save="save" /> -->
|
||||
<BranchDrawer v-model:open="drawer" v-model="form" v-model:len="nodeConfig.conditionNodes!.length" @save="save" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -176,6 +176,10 @@ const isRetry = (taskBatchStatus: number) => {
|
||||
const isStop = (taskBatchStatus: number) => {
|
||||
return taskBatchStatus === 1 || taskBatchStatus === 2;
|
||||
};
|
||||
|
||||
const isShow = (taskBatchStatus: number) => {
|
||||
return isRetry(taskBatchStatus!) || isStop(taskBatchStatus!);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -187,7 +191,7 @@ const isStop = (taskBatchStatus: number) => {
|
||||
<div v-for="(item, i) in nodeConfig.conditionNodes" :key="i" class="col-box">
|
||||
<div class="condition-node">
|
||||
<div class="condition-node-box">
|
||||
<NPopover :disabled="store.type !== 2">
|
||||
<NPopover :disabled="store.type !== 2 || !isShow(item.taskBatchStatus!)">
|
||||
<div class="popover">
|
||||
<NButton v-if="isRetry(item.taskBatchStatus!)" text @click="retry(item!)">
|
||||
<span class="popover-item">
|
||||
|
@ -325,7 +325,7 @@
|
||||
}
|
||||
|
||||
.auto-judge .content {
|
||||
line-height: 136%;
|
||||
line-height: 23px;
|
||||
position: relative;
|
||||
padding-top: 15px;
|
||||
min-height: 59px;
|
||||
|
Loading…
Reference in New Issue
Block a user