feat: 更新拖放功能,重构状态管理,优化工作流组件,添加节点和边的确认删除功能

This commit is contained in:
jxxghp
2025-02-26 21:11:24 +08:00
parent 6d5d4354d9
commit 3d64382c9b
6 changed files with 234 additions and 70 deletions

View File

@@ -1,19 +1,59 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import Sidebar from '../workflow/Sidebar.vue'
import DropzoneBackground from '../workflow/DropzoneBackground.vue'
import { MiniMap } from '@vue-flow/minimap'
import useDragAndDrop from '@core/utils/workflow'
import { Workflow } from '@/api/types'
import { useToast } from 'vue-toast-notification'
import api from '@/api'
import { useConfirm } from 'vuetify-use-dialog'
import Sidebar from '../workflow/Sidebar.vue'
import DropzoneBackground from '../workflow/DropzoneBackground.vue'
const { onConnect, addEdges } = useVueFlow()
const { onConnect, addEdges, nodes, edges, onNodesChange, applyNodeChanges, onEdgesChange, applyEdgeChanges } =
useVueFlow()
const { onDragOver, onDrop, onDragLeave, isDragOver } = useDragAndDrop()
const nodes = ref([])
onConnect(addEdges)
onNodesChange(async (changes: any) => {
const nextChanges = []
for (const change of changes) {
if (change.type === 'remove') {
const isConfirmed = await createConfirm({
title: '确认',
content: `确定要删除该节点吗?`,
})
if (!isConfirmed) {
nextChanges.push(change)
}
} else {
nextChanges.push(change)
}
}
applyNodeChanges(nextChanges)
})
onEdgesChange(async (changes: any) => {
const nextChanges = []
for (const change of changes) {
if (change.type === 'remove') {
const isConfirmed = await createConfirm({
title: '确认',
content: `确定要删除该节点吗?`,
})
if (isConfirmed) {
nextChanges.push(change)
}
} else {
nextChanges.push(change)
}
}
applyEdgeChanges(nextChanges)
})
// 定义输入参数
const props = defineProps({
workflow: Object as PropType<Workflow>,
@@ -21,6 +61,41 @@ const props = defineProps({
// 定义事件
const emit = defineEmits(['close', 'save'])
// 站点编辑表单数据
const workflowForm = ref<any>(props.workflow || {})
// 提示框
const $toast = useToast()
// 确认框
const createConfirm = useConfirm()
// 调用API 编辑任务
async function updateWorkflow() {
// 更新节点和流程
workflowForm.value.actions = nodes
workflowForm.value.flows = edges
try {
const result: { [key: string]: string } = await api.put(`workflow/${workflowForm.value.id}`, workflowForm.value)
if (result.success) {
$toast.success(`保存任务流程成功!`)
emit('save')
} else {
$toast.error(`保存任务流程失败:${result.message}`)
}
} catch (error) {
console.error(error)
}
}
onMounted(() => {
if (props.workflow) {
nodes.value = props.workflow.actions ?? []
edges.value = props.workflow.flows ?? []
}
})
</script>
<template>
@@ -35,24 +110,23 @@ const emit = defineEmits(['close', 'save'])
</VBtn>
</VToolbarItems>
<VToolbarTitle> 编辑流程 - {{ workflow?.name }} </VToolbarTitle>
<VSpacer />
<VToolbarItems>
<VBtn icon @click="emit('save')" class="me-5">
<VBtn icon @click="updateWorkflow" class="me-5">
<VIcon size="large" color="white" icon="mdi-content-save" />
</VBtn>
</VToolbarItems>
</VToolbar>
</div>
<VCardText>
<VCardText class="px-0 py-0">
<div class="dnd-flow" @drop="onDrop">
<VueFlow :nodes="nodes" @dragover="onDragOver" @dragleave="onDragLeave">
<VueFlow :nodes="nodes" :edges="edges" @dragover="onDragOver" @dragleave="onDragLeave">
<MiniMap />
<DropzoneBackground
:style="{
backgroundColor: isDragOver ? '#e7f3ff' : 'transparent',
transition: 'background-color 0.2s ease',
}"
>
<p v-if="isDragOver">Drop here</p>
</DropzoneBackground>
</VueFlow>
<Sidebar />
@@ -67,4 +141,80 @@ const emit = defineEmits(['close', 'save'])
@import '@vue-flow/controls/dist/style.css';
@import '@vue-flow/minimap/dist/style.css';
@import '@vue-flow/node-resizer/dist/style.css';
.vue-flow__minimap {
transform: scale(75%);
transform-origin: bottom right;
}
.dnd-flow {
flex-direction: column;
display: flex;
height: 100%;
}
.dnd-flow aside {
color: #fff;
font-weight: 700;
border-right: 1px solid #eee;
padding: 15px 10px;
font-size: 12px;
background: #10b981bf;
-webkit-box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.3);
box-shadow: 0 5px 10px #0000004d;
}
.dnd-flow aside .nodes > * {
margin-bottom: 10px;
cursor: grab;
font-weight: 500;
-webkit-box-shadow: 5px 5px 10px 2px rgba(0, 0, 0, 0.25);
box-shadow: 5px 5px 10px 2px #00000040;
}
.dnd-flow aside .description {
margin-bottom: 10px;
}
.dnd-flow .vue-flow-wrapper {
flex-grow: 1;
height: 100%;
}
@media screen and (min-width: 640px) {
.dnd-flow {
flex-direction: row;
}
.dnd-flow aside {
min-width: 25%;
}
}
@media screen and (max-width: 639px) {
.dnd-flow aside .nodes {
display: flex;
flex-direction: row;
gap: 5px;
}
}
.dropzone-background {
position: relative;
height: 100%;
width: 100%;
}
.dropzone-background .overlay {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
pointer-events: none;
}
</style>

View File

@@ -5,7 +5,6 @@ import { Background } from '@vue-flow/background'
<template>
<div class="dropzone-background">
<Background :size="2" :gap="20" pattern-color="#BDBDBD" />
<div class="overlay">
<slot />
</div>

View File

@@ -2,20 +2,73 @@
import useDragAndDrop from '@core/utils/workflow'
const { onDragStart } = useDragAndDrop()
// 组件列表
const actions = [
{
type: 'AddDownloadAction',
label: '添加下载资源',
},
{
type: 'AddSubscribeAction',
label: '添加订阅',
},
{
type: 'FetchDownloadsAction',
label: '获取下载任务',
},
{
type: 'FetchMediasAction',
label: '获取媒体数据',
},
{
type: 'FetchRssAction',
label: '获取RSS数据',
},
{
type: 'FetchTorrentsAction',
label: '搜索站点资源',
},
{
type: 'FilterMediasAction',
label: '过滤媒体数据',
},
{
type: 'FilterTorrentsAction',
label: '过滤资源数据',
},
{
type: 'ScrapeFileAction',
label: '刮削文件',
},
{
type: 'SendEventAction',
label: '发送事件',
},
{
type: 'SendMessageAction',
label: '发送消息',
},
{
type: 'TransferFileAction',
label: '整理文件',
},
]
</script>
<template>
<aside>
<div class="description">You can drag these nodes to the pane.</div>
<div class="mb-3"><VLabel>可选动作组件</VLabel></div>
<div class="nodes">
<div class="vue-flow__node-input" :draggable="true" @dragstart="onDragStart($event, 'input')">Input Node</div>
<div class="vue-flow__node-default" :draggable="true" @dragstart="onDragStart($event, 'default')">
Default Node
<div
class="vue-flow__node-default"
v-for="action in actions"
:draggable="true"
@dragstart="onDragStart($event, action)"
>
{{ action['label'] }}
</div>
<div class="vue-flow__node-output" :draggable="true" @dragstart="onDragStart($event, 'output')">Output Node</div>
</div>
</aside>
</template>