节点初步完成

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

@@ -53,10 +53,10 @@
<div class="flex-1 flex">
<!-- 左侧工作流面板 -->
<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 }"
>
<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>
@@ -69,20 +69,27 @@
</button>
</div>
<div class="p-4 h-[calc(100vh-120px)] overflow-y-auto">
<div class="flex-1 overflow-y-auto p-4">
<draggable
v-model="workflowNodes"
:animation="200"
class="space-y-2 min-h-full"
item-key="id"
handle=".drag-handle"
@change="onWorkflowChange"
>
<template #item="{ element, index }">
<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 }"
>
<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
:is="getIconComponent(element.icon)"
class="w-4 h-4"
@@ -93,13 +100,13 @@
<div class="flex items-center space-x-1">
<button
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)"
>
<Settings class="w-3 h-3" />
</button>
<button
class="p-1 rounded hover:bg-white/20"
class="p-1 rounded hover:bg-white/20 cursor-pointer"
@click.stop="removeNode(index)"
>
<X class="w-3 h-3" />
@@ -107,7 +114,7 @@
</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
</div>
</div>
@@ -115,12 +122,15 @@
</draggable>
<div
v-if="workflowNodes.length === 0" class="text-center py-8 transition-colors"
:class="dragOverWorkflow ? 'text-blue-600' : 'text-gray-400'"
v-if="workflowNodes.length === 0"
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" />
<p class="text-sm">
{{ dragOverWorkflow ? '松开鼠标添加节点' : '从右侧拖拽节点到此处' }}
从右侧点击节点添加到此处
</p>
<p class="text-xs mt-1 opacity-75">
添加后可拖拽排序
</p>
</div>
</div>
@@ -136,38 +146,40 @@
</button>
<!-- 右侧节点库 -->
<div class="flex-1 p-6 bg-gray-50">
<div class="mb-6">
<div class="flex-1 flex flex-col bg-gray-50 min-h-0">
<div class="p-6 pb-4 bg-gray-50 flex-shrink-0">
<h1 class="text-2xl font-bold text-gray-900">
节点库
</h1>
<p class="text-gray-600 mt-1">
拖拽节点到左侧创建任务流
击节点添加到左侧任务流
</p>
</div>
<!-- 节点库 - 改为点击按钮 -->
<div class="space-y-6">
<div v-for="(nodes, category) in categorizedNodes" :key="category">
<h3 class="text-lg font-semibold text-gray-800 mb-3">
{{ category }}
</h3>
<div class="grid grid-cols-4 gap-4">
<button
v-for="element in nodes"
:key="element.id"
class="p-4 border rounded-lg cursor-pointer hover:shadow-lg transition-all hover:scale-105 text-center"
:style="{ backgroundColor: element.backgroundColor, color: element.textColor }"
@click="addNodeToWorkflow(element)"
>
<component
:is="getIconComponent(element.icon)"
class="w-8 h-8 mx-auto mb-2"
/>
<p class="text-sm font-medium">
{{ element.name }}
</p>
</button>
<!-- 节点库 - 独立滚动区域 -->
<div class="flex-1 overflow-y-auto px-6 pb-6 min-h-0">
<div class="space-y-6">
<div v-for="(nodes, category) in categorizedNodes" :key="category">
<h3 class="text-lg font-semibold text-gray-800 mb-3">
{{ category }}
</h3>
<div class="grid grid-cols-4 gap-4">
<button
v-for="element in nodes"
:key="element.id"
class="p-4 border rounded-lg cursor-pointer hover:shadow-lg transition-all hover:scale-105 text-center"
:style="{ backgroundColor: element.backgroundColor, color: element.textColor }"
@click="addNodeToWorkflow(element)"
>
<component
:is="getIconComponent(element.icon)"
class="w-8 h-8 mx-auto mb-2"
/>
<p class="text-sm font-medium">
{{ element.name }}
</p>
</button>
</div>
</div>
</div>
</div>
@@ -222,7 +234,7 @@
</template>
<script setup lang="ts">
import type { FlowNode } from "~/components/flow-nodes";
import type { FlowNode } from "~/components/flow-nodes/nodes";
import {
ArrowLeft,
ChevronLeft,
@@ -250,14 +262,12 @@
import { computed, ref } from "vue";
import { useRouter } from "vue-router";
import draggable from "vuedraggable";
import { getNodesByCategory, nodeDefinitions } from "~/components/flow-nodes";
import { getNodesByCategory, nodeDefinitions } from "~/components/flow-nodes/nodes";
const router = useRouter();
const workflowNodes = ref<FlowNode[]>([]);
const showLeftPanel = ref(true);
const editingNode = ref<FlowNode | null>(null);
const isDragging = ref(false);
const dragOverWorkflow = ref(false);
const categorizedNodes = computed(() => getNodesByCategory());
@@ -282,7 +292,7 @@
const cloneNode = (original: FlowNode): FlowNode => {
// 生成更健壮的唯一ID
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}`;
return {
@@ -335,30 +345,6 @@
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) => {
editingNode.value = { ...node };
};
@@ -463,4 +449,40 @@
.w-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>

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>
<script setup lang="ts">
// 简单的页面包装器,使用WorkflowEditor组件
import WorkflowEditor from "~/components/flow-nodes/WorkflowEditor.vue";
definePageMeta({
layout: "flow"
});
</script>