Pārlūkot izejas kodu

fix: 调整节点弹窗逻辑及节点分类

jiaxing.liao 3 nedēļas atpakaļ
vecāks
revīzija
c825b27a14

+ 8 - 12
apps/web/src/features/nodeLibary/index.vue

@@ -27,7 +27,7 @@ interface NodeGroup {
 	nodes: NodeItem[]
 }
 
-type NodeLibraryGroupId = 'start' | 'logic' | 'data' | 'other'
+type NodeLibraryGroupId = 'start' | 'logic' | 'data' | 'other' | 'tool'
 
 const props = defineProps<{
 	hideStart?: boolean
@@ -43,27 +43,23 @@ const { onDragStart } = useDragAndDrop()
 const { t } = useI18n()
 
 const GROUP_ALIASES: Record<string, NodeLibraryGroupId> = {
-	开始: 'start',
 	start: 'start',
-	业务逻辑: 'logic',
 	logic: 'logic',
-	工具: 'data',
-	tool: 'data',
-	数据处理: 'data',
 	data: 'data',
-	其他: 'other',
-	other: 'other',
-	transition: 'data',
-	转换: 'data'
+	tool: 'tool',
+	other: 'other'
 }
 
 const groupLabels = computed(() => ({
 	start: t('pages.nodeLibrary.groups.start'),
-	logic: t('pages.nodeLibrary.groups.logic'),
 	data: t('pages.nodeLibrary.groups.data'),
+	logic: t('pages.nodeLibrary.groups.logic'),
+	tool: t('pages.nodeLibrary.groups.tool'),
 	other: t('pages.nodeLibrary.groups.other')
 }))
 
+const GROUP_ORDER: NodeLibraryGroupId[] = ['start', 'data', 'logic', 'tool', 'other']
+
 const canAddNodeType = (nodeType: string) => {
 	const isLoopContainer = ['loop', 'iteration'].includes(props.parentNodeType || '')
 
@@ -114,7 +110,7 @@ const materials = computed<NodeGroup[]>(() => {
 			})
 		})
 
-	return Array.from(groupMap.values())
+	return GROUP_ORDER.filter((groupId) => groupMap.has(groupId)).map((groupId) => groupMap.get(groupId)!)
 })
 
 const activeGroup = ref('')

+ 4 - 2
apps/web/src/i18n/locales/en-us.ts

@@ -1271,8 +1271,9 @@ export default {
 		nodeLibrary: {
 			groups: {
 				start: 'Start',
+				data: 'Data Processing',
 				logic: 'Logic',
-				data: 'Data',
+				tool: 'Tool',
 				other: 'Other'
 			}
 		},
@@ -1530,8 +1531,9 @@ export default {
 	nodes: {
 		groups: {
 			start: 'Start',
+			data: 'Data Processing',
 			logic: 'Logic',
-			data: 'Data',
+			tool: 'Tool',
 			other: 'Other'
 		},
 		meta: {

+ 4 - 2
apps/web/src/i18n/locales/zh-cn.ts

@@ -1176,8 +1176,9 @@ export default {
 		nodeLibrary: {
 			groups: {
 				start: '开始',
-				logic: '业务逻辑',
 				data: '数据处理',
+				logic: '业务逻辑',
+				tool: '工具',
 				other: '其他'
 			}
 		},
@@ -1432,8 +1433,9 @@ export default {
 	nodes: {
 		groups: {
 			start: '开始',
-			logic: '业务逻辑',
 			data: '数据处理',
+			logic: '业务逻辑',
+			tool: '工具',
 			other: '其他'
 		},
 		meta: {

+ 1 - 1
apps/web/src/nodes/Interface.ts

@@ -90,7 +90,7 @@ export interface INodeType {
 	/**
 	 * 所属分组
 	 */
-	group?: 'start' | 'logic' | 'data' | 'other'
+	group?: 'start' | 'logic' | 'data' | 'tool' | 'other'
 	/**
 	 * 展示名称
 	 */

+ 6 - 5
apps/web/src/nodes/_base/CodeEditor.vue

@@ -79,7 +79,8 @@ const resizeState = reactive({
 	startHeight: props.height,
 	isDragging: false
 })
-const javaCompletionConfig = ref<CodeEditorCompletionConfig>()
+// json文件配置
+const jsonCompletionConfig = ref<CodeEditorCompletionConfig>()
 
 const isFullScreen = ref(false)
 const { monacoTheme, themeClass, toggleTheme } = useLocalEditorTheme({
@@ -100,7 +101,7 @@ const languageSource = [
 
 const getCompletionConfig = computed((): CodeEditorCompletionConfig => {
 	return {
-		java: javaCompletionConfig.value?.java || [],
+		...(jsonCompletionConfig.value || {}),
 		...props.completionConfig
 	}
 })
@@ -121,11 +122,11 @@ const syncCompletionConfig = () => {
 	syncCodeEditorCompletionConfig(model, componentConfig.language, getCompletionConfig.value)
 }
 
-const getJavaCompletionConfig = () => {
+const getJsonCompletionConfig = () => {
 	fetch('/Content/Lib/monacoEditor/completion/java.json')
 		.then((res) => res.json())
 		.then((data) => {
-			javaCompletionConfig.value = data?.java
+			jsonCompletionConfig.value = data?.java
 			syncCompletionConfig()
 		})
 		.catch(() => {
@@ -133,7 +134,7 @@ const getJavaCompletionConfig = () => {
 		})
 }
 
-getJavaCompletionConfig()
+getJsonCompletionConfig()
 
 onMounted(() => {
 	// 处理代码转换

+ 1 - 1
apps/web/src/nodes/_base/condition/index.vue

@@ -9,7 +9,7 @@
 				class="absolute right-1 top-1/2 flex h-[21px] -translate-y-1/2 cursor-pointer select-none items-center rounded-md border-solid border-[0.5px] border-gray-200 px-1 text-[10px] font-semibold shadow-xs"
 				@click="toggleOperator"
 			>
-				{{ modelOperator === 'and' ? t('common.nodeBase.condition.operatorAnd') : t('common.nodeBase.condition.operatorOr') }}
+				{{ modelOperator === 'and' ? 'and' : 'or' }}
 				<Icon icon="lucide:refresh-ccw" />
 			</div>
 		</div>

+ 6 - 4
apps/web/src/nodes/i18n.ts

@@ -1,14 +1,14 @@
 import i18n from '@/i18n'
 
-const NODE_GROUP_KEYS: Record<string, 'start' | 'logic' | 'data' | 'other'> = {
+const NODE_GROUP_KEYS: Record<string, 'start' | 'logic' | 'data' | 'tool' | 'other'> = {
 	start: 'start',
-	end: 'logic',
+	end: 'other',
 	'http-request': 'data',
 	'if-else': 'logic',
 	database: 'data',
 	'database-query': 'data',
-	code: 'logic',
-	'module-invoke': 'data',
+	code: 'data',
+	'module-invoke': 'tool',
 	'basic-dataset': 'data',
 	'view-data': 'data',
 	iteration: 'logic',
@@ -18,6 +18,8 @@ const NODE_GROUP_KEYS: Record<string, 'start' | 'logic' | 'data' | 'other'> = {
 	'loop-end': 'logic',
 	'trigger-schedule': 'start',
 	'trigger-webhook': 'start',
+	'mail-sender': 'tool',
+	'sms-sender': 'tool',
 	'loop-start': 'logic',
 	'iteration-start': 'logic',
 	stickyNote: 'other'

+ 3 - 3
apps/web/src/nodes/src/index.ts

@@ -74,12 +74,12 @@ const withFailBranchOutput = (node: INodeType): INodeType => {
 				return outputs
 			}
 
-			const nodeId = typeof data?.id === 'string' ? data.id.trim() : ''
-			if (!nodeId) {
+			const failOutputId = data?.fail_branch_node_id
+
+			if (!failOutputId) {
 				return outputs
 			}
 
-			const failOutputId = `${nodeId}_fail`
 			const hasFailOutput = outputs.some((item) => {
 				if (typeof item === 'string') {
 					return item === failOutputId

+ 5 - 5
apps/web/src/nodes/src/mail-sender/index.ts

@@ -6,12 +6,12 @@ import { getNodeDescription, getNodeDisplayName } from '@/nodes/i18n'
 export type MailSenderData = INodeDataBaseSchema & {
 	subject: string // '主题'
 	body: string // '内容'
-	toLists: string // '收件人,多个以,分割'
+	toLists: string // '收件人,多个�分割'
 	ccLists: string // '抄�人,多个以,分割'
-	bccLists: string // '密抄人,多个以,分割'
-	attachmentLists: string // '附件id, 多个以,分割'
+	bccLists: string // '密抄人,多个�分割'
+	attachmentLists: string // '附件id, 多个�分割'
 	memo: string // '�述'
-	in_queue: boolean // '是�入队列'
+	in_queue: boolean // '是�入队�
 	delaySeconds: number // '入队列�延时几秒'
 }
 
@@ -21,7 +21,7 @@ export const mailSenderNode: INodeType = {
 	name: 'mail-sender',
 	Setter,
 	description: getNodeDescription('mail-sender'),
-	group: 'other',
+	group: 'tool',
 	icon: 'lucide:mail',
 	iconColor: '#00c375',
 	inputs: [NodeConnectionTypes.main],

+ 1 - 1
apps/web/src/nodes/src/module-invoke/index.ts

@@ -13,7 +13,7 @@ export const moduleInvokeNode: INodeType = {
 	name: 'module-invoke',
 	Setter,
 	description: getNodeDescription('module-invoke'),
-	group: 'data',
+	group: 'tool',
 	icon: 'lucide:blocks',
 	iconColor: '#f79009',
 	inputs: [NodeConnectionTypes.main],

+ 1 - 1
apps/web/src/nodes/src/question-classifier/index.ts

@@ -20,7 +20,7 @@ export const questionClassifierNode: INodeType = {
 	name: 'question-classifier',
 	description: getNodeDescription('question-classifier'),
 	Setter,
-	group: 'data',
+	group: 'logic',
 	icon: 'lucide:gallery-horizontal-end',
 	iconColor: '#17b26a',
 	inputs: [NodeConnectionTypes.main],

+ 2 - 2
apps/web/src/nodes/src/sms-sender/index.ts

@@ -7,7 +7,7 @@ export type SMSSenderData = INodeDataBaseSchema & {
 	config_name: string // '�置�称'
 	sign_name: string // '签�'
 	template_code: string // '模版编�'
-	senders: string // '��者,多个以,分割'
+	senders: string // '��者,多个�分割'
 }
 
 export const smsSenderNode: INodeType = {
@@ -16,7 +16,7 @@ export const smsSenderNode: INodeType = {
 	name: 'sms-sender',
 	Setter,
 	description: getNodeDescription('sms-sender'),
-	group: 'other',
+	group: 'tool',
 	icon: 'lucide:message-square-more',
 	iconColor: '#00c375',
 	inputs: [NodeConnectionTypes.main],

+ 65 - 10
apps/web/src/views/editor/NodeView.vue

@@ -59,13 +59,14 @@
 		width="360px"
 		virtual-triggering
 	>
-		<NodeLibary
-			@add-node="handleNodeCreate"
-			@mouseleave="onHideNodeLibary"
-			:parent-node-type="nodeLibaryParentType"
-			hide-start
-			ignore-drag
-		/>
+		<div ref="nodeLibraryPanelRef">
+			<NodeLibary
+				@add-node="handleNodeCreate"
+				:parent-node-type="nodeLibaryParentType"
+				hide-start
+				ignore-drag
+			/>
+		</div>
 	</el-popover>
 </template>
 
@@ -127,6 +128,7 @@ const workflowRef = ref<InstanceType<typeof Workflow>>()
 const showNodeLibary = ref(false)
 const libaryRefferenceRef = ref<HTMLElement>()
 const nodeLibaryPopoverAnchorRef = ref<HTMLElement>()
+const nodeLibraryPanelRef = ref<HTMLElement>()
 const runVisible = ref(false)
 const closeRunWorkflowOnSubmit = ref(false)
 const runWorkflowInputOnly = ref(false)
@@ -617,7 +619,8 @@ const handleNodeCreate = (value: { type: string; position?: XYPosition } | strin
 			newNodeParam.position = position
 			newNodeParam.prevNodeId = handle?.nodeId
 			newNodeParam.parentId = parentId
-			if (handle?.handleId?.includes('_')) {
+			// 包含下划线或者没中划线的
+			if (handle?.handleId?.includes('_') || !handle?.handleId?.includes('-')) {
 				newNodeParam.nodeHandleId = handle?.handleId
 			}
 		}
@@ -745,8 +748,8 @@ const onCreateConnection = async (connection: Connection) => {
 		target,
 		zIndex: 1
 	}
-
-	if (sourceHandle && sourceHandle.includes('_')) {
+	// 只有当 sourceHandle 包含下划线或者不包含中划线时才传给后端,其他情况后端会自动匹配。
+	if (sourceHandle && sourceHandle.includes('_') && !sourceHandle.includes('-')) {
 		params.sourceHandle = sourceHandle
 	}
 
@@ -863,6 +866,7 @@ const handleUpdateNode = (node: IWorkflowNode) => {
 		agent
 			.postAgentDoUpdateAgentNode(buildUpdateNodePayload(syncedNode))
 			.then((response) => {
+				// todo: 更新完成后会返回节点数据,同步本地节点数据
 				handleApiResult(response, undefined, t('pages.nodeView.messages.updateNodeFailed'))
 			})
 			.catch((error) => {
@@ -977,6 +981,40 @@ const onHideNodeLibary = () => {
 	}, 500)
 }
 
+const onGlobalPointerDown = (event: PointerEvent) => {
+	if (!showNodeLibary.value) {
+		return
+	}
+
+	const target = event.target as Node | null
+	if (!target) {
+		onHideNodeLibary()
+		return
+	}
+
+	const panelEl = nodeLibraryPanelRef.value
+	if (panelEl?.contains(target)) {
+		return
+	}
+
+	const anchorEl = libaryRefferenceRef.value
+	if (anchorEl?.contains(target)) {
+		return
+	}
+
+	onHideNodeLibary()
+}
+
+const onGlobalKeyDown = (event: KeyboardEvent) => {
+	if (!showNodeLibary.value) {
+		return
+	}
+
+	if (event.key === 'Escape') {
+		onHideNodeLibary()
+	}
+}
+
 // 根据边上加号按钮的位置打开节点库弹层。
 const handleClickConectionAdd = (connection: Connection & { id: string }, parentId?: string) => {
 	const el = document.querySelector(`[edge-add-btn="${connection.id}"]`) as HTMLElement
@@ -1020,5 +1058,22 @@ const handleViewportChange = useDebounceFn((viewport: { x: number; y: number; zo
 onBeforeUnmount(() => {
 	removeNodeLibaryPopoverAnchor()
 	resetDisplayedNodeStatuses()
+	document.removeEventListener('pointerdown', onGlobalPointerDown, true)
+	document.removeEventListener('keydown', onGlobalKeyDown)
 })
+
+watch(
+	showNodeLibary,
+	(visible) => {
+		if (visible) {
+			document.addEventListener('pointerdown', onGlobalPointerDown, true)
+			document.addEventListener('keydown', onGlobalKeyDown)
+			return
+		}
+
+		document.removeEventListener('pointerdown', onGlobalPointerDown, true)
+		document.removeEventListener('keydown', onGlobalKeyDown)
+	},
+	{ immediate: true }
+)
 </script>