节点初步完成
This commit is contained in:
@@ -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_
|
||||||
|
@@ -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
5
app/layouts/flow.vue
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
@@ -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>
|
||||||
|
Reference in New Issue
Block a user