Skip to content

feat: The workflow condition node supports drag and drop sorting #2810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@
"vue-clipboard3": "^2.0.0",
"vue-codemirror": "^6.1.1",
"vue-demi": "latest",
"vue-draggable-plus": "^0.6.0",
"vue-i18n": "^9.13.1",
"vue-router": "^4.2.4",
"vue3-menus": "^1.1.2"
"vue3-menus": "^1.1.2",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.2",
Expand Down
1 change: 0 additions & 1 deletion ui/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import LogoIcon from './logo/LogoIcon.vue'
import SendIcon from './logo/SendIcon.vue'
import CodemirrorEditor from './codemirror-editor/index.vue'
import ModelSelect from './model-select/index.vue'

export default {
install(app: App) {
app.component(AppIcon.name, AppIcon)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no immediate issues with the given code snippet. It appears to be setting up Vue components for use in a larger application. The only comment line has been removed, which is fine but might indicate an intention to remove it later.

Here are some minor optimizations:

  1. You can reorder the import statements alphabetically for consistency.
  2. Ensure that all imported components are used appropriately throughout your plugin's setup method if necessary.

Overall, the code looks clean and ready for further development or integration into a project.

Expand Down
261 changes: 143 additions & 118 deletions ui/src/workflow/nodes/condition-node/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,130 +8,144 @@
ref="ConditionNodeFormRef"
@submit.prevent
>
<template v-for="(item, index) in form_data.branch" :key="item.id">
<el-card
v-resize="(wh: any) => resizeCondition(wh, item, index)"
shadow="never"
class="card-never mb-8"
style="--el-card-padding: 12px"
>
<div class="flex-between lighter">
{{ item.type }}
<div class="info" v-if="item.conditions.length > 1">
<span>{{ $t('views.applicationWorkflow.nodes.conditionNode.conditions.info') }}</span>
<el-select
:teleported="false"
v-model="item.condition"
size="small"
style="width: 60px; margin: 0 8px"
>
<el-option :label="$t('views.applicationWorkflow.condition.AND')" value="and" />
<el-option :label="$t('views.applicationWorkflow.condition.OR')" value="or" />
</el-select>
<span>{{
$t('views.applicationWorkflow.nodes.conditionNode.conditions.label')
}}</span>
<VueDraggable
ref="el"
v-model="form_data.branch"
:disabled="form_data.branch === 2"
:filter="'.no-drag'"
handle=".handle"
:animation="150"
ghostClass="ghost"
@end="onEnd"
>
<template v-for="(item, index) in form_data.branch" :key="item.id">
<el-card
v-resize="(wh: any) => resizeCondition(wh, item, index)"
shadow="never"
class="card-never mb-8"
:class="{ 'no-drag': index === form_data.branch.length - 1 }"
style="--el-card-padding: 12px"
>
<div class="handle flex-between lighter">
{{ item.type }}
<div class="info" v-if="item.conditions.length > 1">
<span>{{
$t('views.applicationWorkflow.nodes.conditionNode.conditions.info')
}}</span>
<el-select
:teleported="false"
v-model="item.condition"
size="small"
style="width: 60px; margin: 0 8px"
>
<el-option :label="$t('views.applicationWorkflow.condition.AND')" value="and" />
<el-option :label="$t('views.applicationWorkflow.condition.OR')" value="or" />
</el-select>
<span>{{
$t('views.applicationWorkflow.nodes.conditionNode.conditions.label')
}}</span>
</div>
</div>
</div>
<div v-if="index !== form_data.branch.length - 1" class="mt-8">
<template v-for="(condition, cIndex) in item.conditions" :key="cIndex">
<el-row :gutter="8">
<el-col :span="11">
<el-form-item
:prop="'branch.' + index + '.conditions.' + cIndex + '.field'"
:rules="{
type: 'array',
required: true,
message: $t('views.applicationWorkflow.variable.placeholder'),
trigger: 'change'
}"
>
<NodeCascader
ref="nodeCascaderRef"
:nodeModel="nodeModel"
class="w-full"
:placeholder="$t('views.applicationWorkflow.variable.placeholder')"
v-model="condition.field"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
:prop="'branch.' + index + '.conditions.' + cIndex + '.compare'"
:rules="{
required: true,
message: $t(
'views.applicationWorkflow.nodes.conditionNode.conditions.requiredMessage'
),
trigger: 'change'
}"
>
<el-select
@wheel="wheel"
:teleported="false"
v-model="condition.compare"
:placeholder="
$t(
<div v-if="index !== form_data.branch.length - 1" class="mt-8">
<template v-for="(condition, cIndex) in item.conditions" :key="cIndex">
<el-row :gutter="8">
<el-col :span="11">
<el-form-item
:prop="'branch.' + index + '.conditions.' + cIndex + '.field'"
:rules="{
type: 'array',
required: true,
message: $t('views.applicationWorkflow.variable.placeholder'),
trigger: 'change'
}"
>
<NodeCascader
ref="nodeCascaderRef"
:nodeModel="nodeModel"
class="w-full"
:placeholder="$t('views.applicationWorkflow.variable.placeholder')"
v-model="condition.field"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
:prop="'branch.' + index + '.conditions.' + cIndex + '.compare'"
:rules="{
required: true,
message: $t(
'views.applicationWorkflow.nodes.conditionNode.conditions.requiredMessage'
),
trigger: 'change'
}"
>
<el-select
@wheel="wheel"
:teleported="false"
v-model="condition.compare"
:placeholder="
$t(
'views.applicationWorkflow.nodes.conditionNode.conditions.requiredMessage'
)
"
clearable
@change="changeCondition($event, index, cIndex)"
>
<template v-for="(item, index) in compareList" :key="index">
<el-option :label="item.label" :value="item.value" />
</template>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
v-if="
!['is_null', 'is_not_null', 'is_true', 'is_not_true'].includes(
condition.compare
)
"
clearable
@change="changeCondition($event, index, cIndex)"
:prop="'branch.' + index + '.conditions.' + cIndex + '.value'"
:rules="{
required: true,
message: $t('views.applicationWorkflow.nodes.conditionNode.valueMessage'),
trigger: 'blur'
}"
>
<template v-for="(item, index) in compareList" :key="index">
<el-option :label="item.label" :value="item.value" />
</template>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
v-if="
!['is_null', 'is_not_null', 'is_true', 'is_not_true'].includes(
condition.compare
)
"
:prop="'branch.' + index + '.conditions.' + cIndex + '.value'"
:rules="{
required: true,
message: $t('views.applicationWorkflow.nodes.conditionNode.valueMessage'),
trigger: 'blur'
}"
>
<el-input
v-model="condition.value"
:placeholder="
$t('views.applicationWorkflow.nodes.conditionNode.valueMessage')
"
/>
</el-form-item>
</el-col>
<el-col :span="1">
<el-button
:disabled="index === 0 && cIndex === 0"
link
type="info"
class="mt-4"
@click="deleteCondition(index, cIndex)"
>
<el-icon><Delete /></el-icon>
</el-button>
</el-col>
</el-row>
</template>
</div>
<el-input
v-model="condition.value"
:placeholder="
$t('views.applicationWorkflow.nodes.conditionNode.valueMessage')
"
/>
</el-form-item>
</el-col>
<el-col :span="1">
<el-button
:disabled="index === 0 && cIndex === 0"
link
type="info"
class="mt-4"
@click="deleteCondition(index, cIndex)"
>
<el-icon><Delete /></el-icon>
</el-button>
</el-col>
</el-row>
</template>
</div>

<el-button
link
type="primary"
@click="addCondition(index)"
v-if="index !== form_data.branch.length - 1"
>
<el-icon class="mr-4"><Plus /></el-icon>
{{ $t('views.applicationWorkflow.nodes.conditionNode.addCondition') }}
</el-button>
</el-card>
</template>
<el-button
link
type="primary"
@click="addCondition(index)"
v-if="index !== form_data.branch.length - 1"
>
<el-icon class="mr-4"><Plus /></el-icon>
{{ $t('views.applicationWorkflow.nodes.conditionNode.addCondition') }}
</el-button>
</el-card>
</template>
</VueDraggable>
<el-button link type="primary" @click="addBranch">
<el-icon class="mr-4"><Plus /></el-icon>
{{ $t('views.applicationWorkflow.nodes.conditionNode.addBranch') }}
Expand All @@ -147,6 +161,7 @@ import type { FormInstance } from 'element-plus'
import { ref, computed, onMounted, nextTick } from 'vue'
import { randomId } from '@/utils/utils'
import { compareList } from '@/workflow/common/data'
import { type DraggableEvent, type UseDraggableReturn, VueDraggable } from 'vue-draggable-plus'

const props = defineProps<{ nodeModel: any }>()
const form = {
Expand Down Expand Up @@ -223,6 +238,16 @@ const validate = () => {
})
}

function onEnd(evt: DraggableEvent) {
const { oldIndex, newIndex, clonedData } = evt
if (oldIndex === undefined || newIndex === undefined) return
const list = cloneDeep(props.nodeModel.properties.node_data.branch)

list[newIndex].type = list[oldIndex].type
list[oldIndex].type = clonedData.type // 恢复原始 type
set(props.nodeModel.properties.node_data, 'branch', list)
}

function addBranch() {
const list = cloneDeep(props.nodeModel.properties.node_data.branch)
const obj = {
Expand Down