|
@@ -0,0 +1,127 @@
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { inject, computed } from 'vue'
|
|
|
|
|
+import { Icon } from '@repo/ui'
|
|
|
|
|
+import { NodeResizer } from '@vue-flow/node-resizer'
|
|
|
|
|
+import type { OnResize } from '@vue-flow/node-resizer'
|
|
|
|
|
+import Canvas from '../../../Canvas.vue'
|
|
|
|
|
+
|
|
|
|
|
+import type { NodeProps, XYPosition } from '@vue-flow/core'
|
|
|
|
|
+import type { IWorkflowNode, CanvasConnectionPort, IWorkflowEdge } from '../../../../Interface'
|
|
|
|
|
+
|
|
|
|
|
+import '@vue-flow/node-resizer/dist/style.css'
|
|
|
|
|
+
|
|
|
|
|
+defineOptions({
|
|
|
|
|
+ inheritAttrs: false
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const node = inject<{
|
|
|
|
|
+ props?: NodeProps<IWorkflowNode> & {
|
|
|
|
|
+ readOnly?: boolean
|
|
|
|
|
+ hovered?: boolean
|
|
|
|
|
+ node: IWorkflowNode
|
|
|
|
|
+ }
|
|
|
|
|
+ inputs?: { value: CanvasConnectionPort[] }
|
|
|
|
|
+ outputs?: { value: CanvasConnectionPort[] }
|
|
|
|
|
+}>('canvas-node-data')
|
|
|
|
|
+
|
|
|
|
|
+const nodeMap = inject<{ nodeMap: Record<string, any> }>('vueflow')?.nodeMap
|
|
|
|
|
+
|
|
|
|
|
+const nodes = inject<{ nodes: IWorkflowNode[] }>('vueflow')?.nodes
|
|
|
|
|
+const edges = inject<{ edges: IWorkflowEdge[] }>('vueflow')?.edges
|
|
|
|
|
+
|
|
|
|
|
+const childrenNodes = computed(
|
|
|
|
|
+ () => nodes?.filter((item) => item?.parentId === node?.props?.id) || []
|
|
|
|
|
+)
|
|
|
|
|
+console.log(childrenNodes.value, nodes)
|
|
|
|
|
+const emit = defineEmits<{
|
|
|
|
|
+ update: [parameters: Record<string, unknown>]
|
|
|
|
|
+ move: [position: XYPosition]
|
|
|
|
|
+ 'add-inner-node': []
|
|
|
|
|
+}>()
|
|
|
|
|
+
|
|
|
|
|
+const nodeData = computed(() => node?.props?.node?.data ?? node?.props?.data)
|
|
|
|
|
+const nodeType = computed(() => {
|
|
|
|
|
+ const type = nodeData.value?.nodeType
|
|
|
|
|
+ return type ? nodeMap?.[type] : undefined
|
|
|
|
|
+})
|
|
|
|
|
+const isReadOnly = computed(() => node?.props?.readOnly ?? false)
|
|
|
|
|
+
|
|
|
|
|
+const width = computed(() => Number(nodeData.value?.width) || 424)
|
|
|
|
|
+const height = computed(() => Number(nodeData.value?.height) || 244)
|
|
|
|
|
+
|
|
|
|
|
+const nodeClass = computed(() => {
|
|
|
|
|
+ const classes: string[] = []
|
|
|
|
|
+ if (node?.props?.selected) {
|
|
|
|
|
+ classes.push('ring-2', 'ring-#06aed4')
|
|
|
|
|
+ }
|
|
|
|
|
+ return classes
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+function onResize(event: OnResize) {
|
|
|
|
|
+ emit('move', {
|
|
|
|
|
+ x: event.params.x,
|
|
|
|
|
+ y: event.params.y
|
|
|
|
|
+ })
|
|
|
|
|
+ emit('update', {
|
|
|
|
|
+ ...(event.params.width != null ? { width: event.params.width } : {}),
|
|
|
|
|
+ ...(event.params.height != null ? { height: event.params.height } : {})
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onAddNode() {
|
|
|
|
|
+ if (!isReadOnly.value) {
|
|
|
|
|
+ emit('add-inner-node')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<template>
|
|
|
|
|
+ <NodeResizer
|
|
|
|
|
+ :min-width="280"
|
|
|
|
|
+ :min-height="180"
|
|
|
|
|
+ :width="width"
|
|
|
|
|
+ :height="height"
|
|
|
|
|
+ :is-visible="!isReadOnly && node?.props?.selected"
|
|
|
|
|
+ handle-class-name="bg-#06aed4! border-white! w-8px h-8px"
|
|
|
|
|
+ line-class-name="border-#06aed4!"
|
|
|
|
|
+ @resize="onResize"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ :class="nodeClass"
|
|
|
|
|
+ class="node-loop rounded-12px border-2 border-solid border-#dcdcdc bg-#fafafa overflow-hidden flex flex-col"
|
|
|
|
|
+ :style="{ width: width + 'px', height: height + 'px' }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <!-- 标题栏 -->
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="loop-header shrink-0 flex items-center gap-8px pl-12px py-8px border-b border-b-solid border-#e8e8e8 bg-#fff"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-4px"
|
|
|
|
|
+ :style="{ background: nodeType?.iconColor ?? '#06aed4' }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Icon :icon="nodeType?.icon ?? 'lucide:infinity'" color="#ffffff" :size="16" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="text-14px font-medium text-#333">{{ nodeType?.displayName ?? '循环' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 内部画布区域:虚线网格 + 占位内容 -->
|
|
|
|
|
+ <div class="loop-body flex-1 min-h-0 relative p-16px">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="absolute inset-16px rounded-8px border-1 border-dashed border-#d9d9d9 bg-[repeating-linear-gradient(to_right,#e8e8e8_0,transparent_1px),repeating-linear-gradient(to_bottom,#e8e8e8_0,transparent_1px)] bg-[length:12px_12px]"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div class="w-full h-full relative z-1 flex items-center gap-12px pt-8px">
|
|
|
|
|
+ <Canvas
|
|
|
|
|
+ :id="node?.props?.id"
|
|
|
|
|
+ :nodes="childrenNodes"
|
|
|
|
|
+ :edges="edges || []"
|
|
|
|
|
+ :read-only="isReadOnly"
|
|
|
|
|
+ :node-map="nodeMap!"
|
|
|
|
|
+ :show-control-bar="false"
|
|
|
|
|
+ :hide-child-node="false"
|
|
|
|
|
+ :zoom-to-fit="false"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|