节点初步完成

This commit is contained in:
2025-08-22 14:03:18 +08:00
parent e1c0e7f633
commit dbf190597b
5 changed files with 111 additions and 66 deletions

View File

@@ -34,3 +34,17 @@
- Validate that original nodes remain unchanged in right panel - Validate that original nodes remain unchanged in right panel
- Test configuration preservation and editing functionality - Test configuration preservation and editing functionality
- _Requirements: 1.1, 1.2, 1.3, 1.5, 2.1, 2.2, 3.1, 3.2, 3.3_ - _Requirements: 1.1, 1.2, 1.3, 1.5, 2.1, 2.2, 3.1, 3.2, 3.3_
- [x] 6. Fix workflow node drag and drop reordering
- Enable proper drag and drop functionality for reordering nodes within the workflow panel
- Configure vuedraggable with correct options for internal sorting
- Add proper event handlers for drag operations
- Remove unused drag event handlers to fix linting issues
- _Requirements: 1.2, 1.3, 2.2_
- [x] 7. Implement independent scrolling for left and right panels
- Separate scroll containers for left workflow panel and right node library
- Ensure left panel scrolling doesn't affect right panel and vice versa
- Maintain proper layout and responsive behavior
- Fix overflow handling for both panels
- _Requirements: 4.1, 4.2_

View File

@@ -53,10 +53,10 @@
<div class="flex-1 flex"> <div class="flex-1 flex">
<!-- 左侧工作流面板 --> <!-- 左侧工作流面板 -->
<div <div
class="bg-white shadow-lg transition-all duration-300 overflow-hidden" class="bg-white shadow-lg transition-all duration-300 overflow-hidden flex flex-col"
:class="{ 'w-0': !showLeftPanel, 'w-280px': showLeftPanel }" :class="{ 'w-0': !showLeftPanel, 'w-280px': showLeftPanel }"
> >
<div class="p-4 border-b border-gray-200 flex justify-between items-center"> <div class="p-4 border-b border-gray-200 flex justify-between items-center flex-shrink-0">
<h2 class="text-lg font-semibold text-gray-800"> <h2 class="text-lg font-semibold text-gray-800">
任务流 任务流
</h2> </h2>
@@ -69,20 +69,27 @@
</button> </button>
</div> </div>
<div class="p-4 h-[calc(100vh-120px)] overflow-y-auto"> <div class="flex-1 overflow-y-auto p-4">
<draggable <draggable
v-model="workflowNodes" v-model="workflowNodes"
:animation="200" :animation="200"
class="space-y-2 min-h-full" class="space-y-2 min-h-full"
item-key="id"
handle=".drag-handle"
@change="onWorkflowChange" @change="onWorkflowChange"
> >
<template #item="{ element, index }"> <template #item="{ element, index }">
<div <div
class="p-3 border rounded-lg cursor-move hover:shadow-md transition-shadow" class="p-3 border rounded-lg hover:shadow-md transition-shadow drag-handle"
:style="{ backgroundColor: element.backgroundColor, color: element.textColor }" :style="{ backgroundColor: element.backgroundColor, color: element.textColor }"
> >
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2 cursor-move flex-1">
<div class="flex flex-col space-y-0.5 opacity-60">
<div class="w-1 h-1 bg-current rounded-full" />
<div class="w-1 h-1 bg-current rounded-full" />
<div class="w-1 h-1 bg-current rounded-full" />
</div>
<component <component
:is="getIconComponent(element.icon)" :is="getIconComponent(element.icon)"
class="w-4 h-4" class="w-4 h-4"
@@ -93,13 +100,13 @@
<div class="flex items-center space-x-1"> <div class="flex items-center space-x-1">
<button <button
v-if="element.config?.editable" v-if="element.config?.editable"
class="p-1 rounded hover:bg-white/20" class="p-1 rounded hover:bg-white/20 cursor-pointer"
@click.stop="editNodeConfig(element)" @click.stop="editNodeConfig(element)"
> >
<Settings class="w-3 h-3" /> <Settings class="w-3 h-3" />
</button> </button>
<button <button
class="p-1 rounded hover:bg-white/20" class="p-1 rounded hover:bg-white/20 cursor-pointer"
@click.stop="removeNode(index)" @click.stop="removeNode(index)"
> >
<X class="w-3 h-3" /> <X class="w-3 h-3" />
@@ -107,7 +114,7 @@
</div> </div>
</div> </div>
<div v-if="element.config?.duration" class="mt-2 text-xs opacity-80"> <div v-if="element.config?.duration" class="mt-2 text-xs opacity-80 ml-6">
延时: {{ element.config.duration }}ms 延时: {{ element.config.duration }}ms
</div> </div>
</div> </div>
@@ -115,12 +122,15 @@
</draggable> </draggable>
<div <div
v-if="workflowNodes.length === 0" class="text-center py-8 transition-colors" v-if="workflowNodes.length === 0"
:class="dragOverWorkflow ? 'text-blue-600' : 'text-gray-400'" class="text-center py-8 text-gray-400 flex flex-col items-center justify-center min-h-[200px]"
> >
<Move class="w-8 h-8 mx-auto mb-2" /> <Move class="w-8 h-8 mx-auto mb-2" />
<p class="text-sm"> <p class="text-sm">
{{ dragOverWorkflow ? '松开鼠标添加节点' : '从右侧拖拽节点到此处' }} 从右侧点击节点添加到此处
</p>
<p class="text-xs mt-1 opacity-75">
添加后可拖拽排序
</p> </p>
</div> </div>
</div> </div>
@@ -136,38 +146,40 @@
</button> </button>
<!-- 右侧节点库 --> <!-- 右侧节点库 -->
<div class="flex-1 p-6 bg-gray-50"> <div class="flex-1 flex flex-col bg-gray-50 min-h-0">
<div class="mb-6"> <div class="p-6 pb-4 bg-gray-50 flex-shrink-0">
<h1 class="text-2xl font-bold text-gray-900"> <h1 class="text-2xl font-bold text-gray-900">
节点库 节点库
</h1> </h1>
<p class="text-gray-600 mt-1"> <p class="text-gray-600 mt-1">
拖拽节点到左侧创建任务流 击节点添加到左侧任务流
</p> </p>
</div> </div>
<!-- 节点库 - 改为点击按钮 --> <!-- 节点库 - 独立滚动区域 -->
<div class="space-y-6"> <div class="flex-1 overflow-y-auto px-6 pb-6 min-h-0">
<div v-for="(nodes, category) in categorizedNodes" :key="category"> <div class="space-y-6">
<h3 class="text-lg font-semibold text-gray-800 mb-3"> <div v-for="(nodes, category) in categorizedNodes" :key="category">
{{ category }} <h3 class="text-lg font-semibold text-gray-800 mb-3">
</h3> {{ category }}
<div class="grid grid-cols-4 gap-4"> </h3>
<button <div class="grid grid-cols-4 gap-4">
v-for="element in nodes" <button
:key="element.id" v-for="element in nodes"
class="p-4 border rounded-lg cursor-pointer hover:shadow-lg transition-all hover:scale-105 text-center" :key="element.id"
:style="{ backgroundColor: element.backgroundColor, color: element.textColor }" class="p-4 border rounded-lg cursor-pointer hover:shadow-lg transition-all hover:scale-105 text-center"
@click="addNodeToWorkflow(element)" :style="{ backgroundColor: element.backgroundColor, color: element.textColor }"
> @click="addNodeToWorkflow(element)"
<component >
:is="getIconComponent(element.icon)" <component
class="w-8 h-8 mx-auto mb-2" :is="getIconComponent(element.icon)"
/> class="w-8 h-8 mx-auto mb-2"
<p class="text-sm font-medium"> />
{{ element.name }} <p class="text-sm font-medium">
</p> {{ element.name }}
</button> </p>
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -222,7 +234,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { FlowNode } from "~/components/flow-nodes"; import type { FlowNode } from "~/components/flow-nodes/nodes";
import { import {
ArrowLeft, ArrowLeft,
ChevronLeft, ChevronLeft,
@@ -250,14 +262,12 @@
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import draggable from "vuedraggable"; import draggable from "vuedraggable";
import { getNodesByCategory, nodeDefinitions } from "~/components/flow-nodes"; import { getNodesByCategory, nodeDefinitions } from "~/components/flow-nodes/nodes";
const router = useRouter(); const router = useRouter();
const workflowNodes = ref<FlowNode[]>([]); const workflowNodes = ref<FlowNode[]>([]);
const showLeftPanel = ref(true); const showLeftPanel = ref(true);
const editingNode = ref<FlowNode | null>(null); const editingNode = ref<FlowNode | null>(null);
const isDragging = ref(false);
const dragOverWorkflow = ref(false);
const categorizedNodes = computed(() => getNodesByCategory()); const categorizedNodes = computed(() => getNodesByCategory());
@@ -282,7 +292,7 @@
const cloneNode = (original: FlowNode): FlowNode => { const cloneNode = (original: FlowNode): FlowNode => {
// 生成更健壮的唯一ID // 生成更健壮的唯一ID
const timestamp = Date.now(); const timestamp = Date.now();
const randomString = Math.random().toString(36).substr(2, 9); const randomString = Math.random().toString(36).substring(2, 11);
const uniqueId = `${original.id}-${timestamp}-${randomString}`; const uniqueId = `${original.id}-${timestamp}-${randomString}`;
return { return {
@@ -335,30 +345,6 @@
console.log("当前工作流节点:", workflowNodes.value); console.log("当前工作流节点:", workflowNodes.value);
}; };
// 拖拽事件处理器用于视觉反馈
const onDragStart = (event: any) => {
isDragging.value = true;
console.log("开始拖拽", event);
};
const onDragEnd = (event: any) => {
isDragging.value = false;
dragOverWorkflow.value = false;
console.log("拖拽结束", event);
};
const onDragEnter = (event: any) => {
if (isDragging.value) {
dragOverWorkflow.value = true;
console.log("进入工作流区域", event);
}
};
const onDragLeave = (event: any) => {
dragOverWorkflow.value = false;
console.log("离开工作流区域", event);
};
const editNodeConfig = (node: FlowNode) => { const editNodeConfig = (node: FlowNode) => {
editingNode.value = { ...node }; editingNode.value = { ...node };
}; };
@@ -463,4 +449,40 @@
.w-280px { .w-280px {
width: 280px; width: 280px;
} }
/* 拖拽时的样式 */
.sortable-ghost {
opacity: 0.5;
}
.sortable-chosen {
transform: scale(1.02);
}
.sortable-drag {
transform: rotate(2deg);
}
/* 确保滚动容器独立 */
.overflow-y-auto {
scrollbar-width: thin;
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
}
.overflow-y-auto::-webkit-scrollbar {
width: 6px;
}
.overflow-y-auto::-webkit-scrollbar-track {
background: transparent;
}
.overflow-y-auto::-webkit-scrollbar-thumb {
background-color: rgba(156, 163, 175, 0.5);
border-radius: 3px;
}
.overflow-y-auto::-webkit-scrollbar-thumb:hover {
background-color: rgba(156, 163, 175, 0.7);
}
</style> </style>

5
app/layouts/flow.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<div>
<slot />
</div>
</template>

View File

@@ -3,5 +3,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// 简单的页面包装器,使用WorkflowEditor组件 import WorkflowEditor from "~/components/flow-nodes/WorkflowEditor.vue";
definePageMeta({
layout: "flow"
});
</script> </script>