فهرست منبع

perf: 优化节点设置

jiaxing.liao 6 روز پیش
والد
کامیت
a42018cec6

+ 3 - 0
apps/web/components.d.ts

@@ -21,8 +21,11 @@ declare module 'vue' {
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
     ElCol: typeof import('element-plus/es')['ElCol']
+    ElCollapse: typeof import('element-plus/es')['ElCollapse']
+    ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
     ElContainer: typeof import('element-plus/es')['ElContainer']
     ElDialog: typeof import('element-plus/es')['ElDialog']
+    ElDrawer: typeof import('element-plus/es')['ElDrawer']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']

+ 9 - 19
apps/web/src/components/setter/DatabaseSetter.vue

@@ -22,24 +22,14 @@ const emit = defineEmits<{
 }>()
 </script>
 <template>
-	<div class="content">
-		<el-drawer
-			:model-value="visible"
-			:show-close="false"
-			size="25%"
-			@close="emit('update:visible', false)"
-		>
-			<template #header>
-				<h4>数据查询</h4>
-				<Icon icon="lucide:x" height="24" width="24"></Icon>
-			</template>
-
-			<el-collapse>
-				<el-collapse-item title="数据表" name="1"> </el-collapse-item>
-				<el-collapse-item title="输出" name="2"> </el-collapse-item>
-				<el-collapse-item title="异常处理" name="3"> </el-collapse-item>
-			</el-collapse>
-		</el-drawer>
+	<div class="w-full">
+		<el-collapse>
+			<el-collapse-item title="数据表" name="1">
+				<el-empty description="暂无数据表配置" :image-size="40" />
+			</el-collapse-item>
+			<el-collapse-item title="输出" name="2"> </el-collapse-item>
+			<el-collapse-item title="异常处理" name="3"> </el-collapse-item>
+		</el-collapse>
 	</div>
 </template>
-<style lang="scss" scoped></style>
+<style lang="less" scoped></style>

+ 90 - 59
apps/web/src/components/setter/index.vue

@@ -6,87 +6,118 @@
  * @Describe: file describe
 -->
 <script lang="ts" setup>
-import HttpSetter from './HttpSetter.vue';
-import { Icon } from "@iconify/vue";
-import { useComponentMapInspector } from "@/store"
+import { computed } from 'vue'
+import { Icon } from '@iconify/vue'
+import { useComponentMapInspector } from '@/store'
+import type { IWorkflow } from '@repo/workflow'
+
+import HttpSetter from './HttpSetter.vue'
+import CodeSetter from './CodeSetter.vue'
+import ConditionSetter from './ConditionSetter.vue'
+import DatabaseSetter from './DatabaseSetter.vue'
+
+const setterMap = {
+	'http-node': HttpSetter,
+	'code-node': CodeSetter,
+	'condition-node': ConditionSetter,
+	'database-node': DatabaseSetter
+}
 
 const store = useComponentMapInspector()
 
 console.log(store.componentMap['http'])
 
 interface Props {
-    data: any, // 暂时定义
-    visible: boolean
+	id: string
+	workflow: IWorkflow
+	visible: boolean
 }
 const props = withDefaults(defineProps<Props>(), {
-    id: '',
-    data: null,
-    visible: false
+	id: '',
+	data: null,
+	visible: false
 })
 const emit = defineEmits<{
-    'update:visible': [value: boolean]
+	'update:visible': [value: boolean]
 }>()
+
+const node = computed(() => {
+	return props.workflow.nodes.find((node) => node.id === props.id)
+})
+
+const setter = computed(() => {
+	return node.value?.type && setterMap[node.value.type as keyof typeof setterMap]
+})
+
 const closeDrawer = () => {
-    emit('update:visible', false)
+	emit('update:visible', false)
 }
 </script>
 <template>
-    <div class='setter'>
-        <div class="drawer shadow-2xl" :class="{ 'drawer--open': props.visible }">
-            <header>
-                <h4>HTTP请求</h4>
-                <Icon icon="lucide:x" height="24" width="24" @click="closeDrawer" class="cursor-pointer"></Icon>
-            </header>
-            <div class="content">
-                <component :is="'http-node'" :data="props.data"></component>
-            </div>
-        </div>
-
-        <HttpSetter :data="data" v-model:visible="props.visible" />
-    </div>
+	<div class="setter">
+		<div class="drawer shadow-2xl" :class="{ 'drawer--open': props.visible && setter }">
+			<header>
+				<h4>{{ node?.label }}</h4>
+				<Icon
+					icon="lucide:x"
+					height="24"
+					width="24"
+					@click="closeDrawer"
+					class="cursor-pointer"
+				></Icon>
+			</header>
+			<div class="content">
+				<component :is="setter" :data="node?.data"></component>
+			</div>
+		</div>
+	</div>
 </template>
 <style lang="less" scoped>
 .setter {
+	/* Drawer 主体 */
+	.drawer {
+		position: fixed;
+		top: 100px;
+		right: 5px;
+		bottom: 10px;
+		width: 420px;
+		background: #fff;
+		z-index: 1000;
+		border-radius: 8px;
+		display: flex;
+		flex-direction: column;
+		border: 1px solid #e4e4e4;
 
-    /* Drawer 主体 */
-    .drawer {
-        position: fixed;
-        top: 100px;
-        right: 5px;
-        bottom: 10px;
-        width: 420px;
-        background: #fff;
-        z-index: 1000;
-        border-radius: 8px;
-        display: flex;
-        flex-direction: column;
-        border: 1px solid #e4e4e4;
+		/* 初始隐藏状态 */
+		transform: translateX(110%);
+		transition: transform 0.25s ease;
+	}
 
-        /* 初始隐藏状态 */
-        transform: translateX(100%);
-        transition: transform 0.25s ease;
-    }
+	/* 显示状态 */
+	.drawer--open {
+		transform: translateX(0);
+	}
 
-    /* 显示状态 */
-    .drawer--open {
-        transform: translateX(0);
-    }
+	/* Header */
+	.drawer header {
+		height: 56px;
+		padding: 0 16px;
+		border-bottom: 1px solid #eee;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+	}
 
-    /* Header */
-    .drawer header {
-        height: 56px;
-        padding: 0 16px;
-        border-bottom: 1px solid #eee;
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-    }
+	/* 内容区 */
+	.drawer .content {
+		flex: 1;
+		// padding: 16px;
+		// overflow-y: auto;
+	}
 
-    /* 内容区 */
-    .drawer .content {
-        flex: 1;
-        padding: 16px;
-        overflow-y: auto;
-    }
+	::v-deep(.el-collapse-item__header, .el-collapse-item__content) {
+		box-sizing: border-box;
+		padding: 0 8px;
+	}
 }
 </style>

+ 10 - 1
apps/web/src/layouts/MainLayout.vue

@@ -5,14 +5,23 @@
 		</el-aside>
 
 		<el-container>
-			<el-main style="padding: 16px; overflow: auto">
+			<el-main style="padding: 16px; overflow: auto" :style="mainStyle">
 				<router-view />
 			</el-main>
 		</el-container>
 	</el-container>
 </template>
 <script setup lang="ts">
+import { ref, provide, type CSSProperties } from 'vue'
 import Sidebar from '@/components/Sidebar/index.vue'
+
+const mainStyle = ref<CSSProperties>()
+
+provide('layout', {
+	setMainStyle(style: CSSProperties) {
+		mainStyle.value = style
+	}
+})
 </script>
 
 <style lang="less" scoped></style>

+ 43 - 6
apps/web/src/views/Editor.vue

@@ -1,7 +1,7 @@
 <template>
 	<div class="w-full h-full flex flex-col">
 		<div
-			class="h-60px border-b border-b-solid border-gray-200 flex items-center justify-between px-12px"
+			class="h-60px shrink-0 border-b border-b-solid border-gray-200 flex items-center justify-between px-12px"
 		>
 			<div class="left flex items-center gap-4">
 				<el-breadcrumb separator="/">
@@ -34,10 +34,16 @@
 					@drop="handleDrop"
 					@run="handleRunWorkflow"
 					@update:nodes:position="handleUpdateNodesPosition"
+					@update:node:attrs="handleUpdateNodeProps"
 					class="bg-#f5f5f5"
 				/>
 				<RunWork v-model:visible="runVisible" />
-				<Setter :data="nodeID" v-model:visible="setterVisible" />
+				<Setter
+					:id="nodeID"
+					:workflow="workflow"
+					@update:node:data="hangleUpdateNodeData"
+					v-model:visible="setterVisible"
+				/>
 			</el-splitter-panel>
 
 			<el-splitter-panel v-model:size.lazy="footerHeight" :min="32">
@@ -48,7 +54,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, inject, type CSSProperties } from 'vue'
+import { ref, inject, type CSSProperties, onBeforeUnmount } from 'vue'
 import { startNode, endNode, httpNode, conditionNode, databaseNode, codeNode } from '@repo/nodes'
 import { Workflow, type IWorkflow, type XYPosition, type Connection } from '@repo/workflow'
 import { v4 as uuid } from 'uuid'
@@ -71,7 +77,7 @@ const footerHeight = ref(32)
 
 const workflow = ref<IWorkflow>({
 	id: uuid(),
-	nodes: [],
+	nodes: [startNode, endNode],
 	edges: []
 })
 
@@ -100,14 +106,14 @@ const handleNodeCreate = (value: SourceType | string) => {
 			workflow.value.nodes.push({
 				id: uuid(),
 				type: 'canvas-node',
-				zIndex: 0,
+				zIndex: -1,
 				position: { x: 600, y: 300 },
 				data: {
 					version: ['1.0.0'],
 					inputs: [],
 					outputs: [],
 					renderType: 'stickyNote',
-					content: '注释内容,可以使用 **Markdown** 语法进行格式化。',
+					content: '注释内容,可以使用 **Markdown** 语法进行格式化, 双击进入编辑。',
 					width: 400,
 					height: 200,
 					color: '#fff5d6'
@@ -176,4 +182,35 @@ const handleUpdateNodesPosition = (events: { id: string; position: XYPosition }[
 		}
 	})
 }
+
+/**
+ * 修改节点数据
+ */
+const hangleUpdateNodeData = (id: string, data: any) => {
+	const node = workflow.value.nodes.find((node) => node.id === id)
+	if (node) {
+		node.data = {
+			...node.data,
+			...data
+		}
+	}
+}
+
+/**
+ * 修改节点属性
+ */
+const handleUpdateNodeProps = (id: string, attrs: Record<string, unknown>) => {
+	const node = workflow.value.nodes.find((node) => node.id === id)
+	if (node) {
+		if (node.data?.renderType === 'stickyNote') {
+			Object.assign(node.data, attrs)
+		} else {
+			Object.assign(node, attrs)
+		}
+	}
+}
+
+onBeforeUnmount(() => {
+	layout?.setMainStyle({})
+})
 </script>

+ 1 - 0
packages/ui/components/sticky-note/StickyNote.vue

@@ -136,6 +136,7 @@ const onInputScroll = (event: WheelEvent) => {
 	position: absolute;
 	padding: 0.5rem 0.75rem 0;
 	overflow: hidden;
+	box-sizing: border-box;
 }
 </style>
 

+ 7 - 1
packages/workflow/src/components/Canvas.vue

@@ -24,6 +24,7 @@ defineOptions({
 })
 
 const emit = defineEmits<{
+	'update:node:size': [id: string, size: { width: number; height: number }]
 	'update:node:position': [id: string, position: XYPosition]
 	'update:nodes:position': [events: CanvasNodeMoveEvent[]]
 	'update:node:activated': [id: string, event?: MouseEvent]
@@ -34,6 +35,7 @@ const emit = defineEmits<{
 	'update:node:parameters': [id: string, parameters: Record<string, unknown>]
 	'update:node:inputs': [id: string]
 	'update:node:outputs': [id: string]
+	'update:node:attrs': [id: string, attrs: Record<string, unknown>]
 	'update:logs-open': [open?: boolean]
 	'update:logs:input-open': [open?: boolean]
 	'update:logs:output-open': [open?: boolean]
@@ -155,6 +157,10 @@ function onNodeDragStop(event: NodeDragEvent) {
 	onUpdateNodesPosition(event.nodes.map(({ id, position }) => ({ id, position })))
 }
 
+function onUpdateNodeAttrs(id: string, attrs: Record<string, unknown>) {
+	emit('update:node:attrs', id, attrs)
+}
+
 /**
  * Connections / Edges
  */
@@ -222,7 +228,7 @@ const handleRun = () => {
 		v-bind="$attrs"
 	>
 		<template #node-canvas-node="nodeProps">
-			<CanvasNode v-bind="nodeProps" @move="onUpdateNodePosition" />
+			<CanvasNode v-bind="nodeProps" @move="onUpdateNodePosition" @update="onUpdateNodeAttrs" />
 		</template>
 
 		<template #node-start-node="nodeProps">

+ 10 - 1
packages/workflow/src/components/elements/nodes/CanvasNode.vue

@@ -18,6 +18,11 @@ type Props = NodeProps<IWorkflowNode['data']> & {
 
 const props = defineProps<Props>()
 
+const emit = defineEmits<{
+	update: [id: string, parameters: Record<string, unknown>]
+	move: [id: string, position: { x: number; y: number }]
+}>()
+
 /**
  * 处理节点
  */
@@ -72,6 +77,10 @@ const outputs = computed(() =>
 	)
 )
 
+const onUpdate = (prop: Record<string, unknown>) => {
+	emit('update', props.id, prop)
+}
+
 provide('canvas-node-data', {
 	props,
 	inputs,
@@ -81,7 +90,7 @@ provide('canvas-node-data', {
 
 <template>
 	<div class="relative">
-		<NodeRenderer />
+		<NodeRenderer v-bind="$attrs" @update="onUpdate" />
 
 		<template v-for="target in inputs" :key="'handle-inputs-port' + target.index">
 			<CanvasHandle v-bind="target" type="target" />

+ 16 - 2
packages/workflow/src/components/elements/nodes/render-types/NodeStickyNote.vue

@@ -7,6 +7,9 @@ import type { OnResize } from '@vue-flow/node-resizer'
 import type { NodeProps, XYPosition } from '@vue-flow/core'
 import type { IWorkflowNode } from '../../../../Interface'
 
+// make sure to include the necessary styles!
+import '@vue-flow/node-resizer/dist/style.css'
+
 defineOptions({
 	inheritAttrs: false
 })
@@ -49,6 +52,15 @@ const modelValue = computed({
 
 const editMode = ref(false)
 
+const nodeClass = computed(() => {
+	let classes: string[] = []
+	if (node?.props?.selected) {
+		classes.push('ring-6px', 'ring-#e0e2e7')
+	}
+
+	return classes
+})
+
 const handleSetEditMode = (edit: boolean) => {
 	if (!isReadOnly.value) {
 		editMode.value = edit
@@ -78,11 +90,13 @@ function onResize(event: OnResize) {
 		:min-width="150"
 		:height="data?.height"
 		:width="data?.width"
-		:is-visible="!isReadOnly"
+		:is-visible="!isReadOnly && node?.props?.selected"
+		handleClassName="bg-transparent! border-transparent!"
+		lineClassName="border-transparent!"
 		@resize="onResize"
 	/>
 
-	<div class="sticky-note__node relative">
+	<div class="sticky-note__node relative rounded-4px" :class="nodeClass">
 		<StickyNote
 			v-model="modelValue"
 			:minHeight="100"