Преглед на файлове

fix: 统一处理 props.data 与本地 formData 的双向同步,减少无效更新和抖动;条件分支变量值组件更换,

hewei преди 1 месец
родител
ревизия
b8f1919d4d

+ 12 - 13
apps/web/src/nodes/_base/condition/ConditionItem.vue

@@ -21,23 +21,22 @@
 		</div>
 		<el-divider class="my-0!" />
 		<div>
-			<Input
-				v-if="modelValue.varType !== 'number'"
-				v-model="modelValue.right_value"
-				variant="borderless"
-				placeholder="输入值"
-				class="w-full"
-			/>
-			<div v-else class="flex items-center">
+			<div v-if="modelValue.varType !== 'number'" class="flex items-center gap-8px">
+				<VarInput
+					v-model="modelValue.right_value"
+					variant="borderless"
+					placeholder="输入值,输入 / 选择变量"
+					class="flex-1"
+				/>
+			</div>
+			<div v-else class="flex items-center gap-8px">
 				<el-select v-model="numberType" :options="numberValOptions"></el-select>
-				<Input
+				<VarInput
 					v-model="modelValue.right_value"
 					variant="borderless"
-					placeholder="输入值"
+					placeholder="输入值,输入 / 选择变量"
 					class="flex-1"
-					type="number"
 				/>
-				<VarSelect v-model="modelValue.right_value" class="flex-1" />
 			</div>
 		</div>
 	</div>
@@ -45,7 +44,7 @@
 
 <script setup lang="ts">
 import { ref, computed } from 'vue'
-import { Input } from '@repo/ui'
+import VarInput from '../VarInput.vue'
 import VarSelect from '../VarSelect.vue'
 import { VARIABLE_TYPE_OPERATORS } from '@/constant'
 

+ 39 - 0
apps/web/src/nodes/src/_shared/useSetterModel.ts

@@ -0,0 +1,39 @@
+import { ref, watch, type Ref } from 'vue'
+import { cloneDeep, isEqual } from 'lodash-es'
+
+export const useSetterModel = <T>(
+	props: { data: T },
+	emit: (event: 'update', value: T) => void
+): Ref<T> => {
+	const formData = ref<T>(cloneDeep(props.data)) as Ref<T>
+
+	watch(
+		() => props.data,
+		(newVal) => {
+			if (Object.is(newVal, formData.value)) {
+				return
+			}
+
+			if (!isEqual(newVal, formData.value)) {
+				formData.value = cloneDeep(newVal)
+			}
+		},
+		{ deep: true, immediate: true }
+	)
+
+	watch(
+		formData,
+		(newVal) => {
+			if (Object.is(newVal, props.data)) {
+				return
+			}
+
+			if (!isEqual(newVal, props.data)) {
+				emit('update', cloneDeep(newVal))
+			}
+		},
+		{ deep: true }
+	)
+
+	return formData
+}

+ 2 - 14
apps/web/src/nodes/src/code/setter.vue

@@ -35,12 +35,12 @@
 </template>
 
 <script setup lang="ts">
-import { ref, watch } from 'vue'
 import InputVariables from '../../_base/InputVariables.vue'
 import CodeEditor from '../../_base/CodeEditor.vue'
 import OutputVariables from '../../_base/OutputVariables.vue'
 import TestConfig from '../../_base/RetryConfig.vue'
 import ErrorHandling from '../../_base/ErrorHandling.vue'
+import { useSetterModel } from '../_shared/useSetterModel'
 
 import type { CodeData } from './index'
 
@@ -54,17 +54,5 @@ const props = defineProps<{
 
 const emit = defineEmits<Emits>()
 
-const formData = ref<CodeData>(props.data)
-
-watch(
-	() => formData.value,
-	(val) => {
-		if (val) {
-			emit('update', val)
-		}
-	},
-	{
-		deep: true
-	}
-)
+const formData = useSetterModel<CodeData>(props, emit)
 </script>

+ 3 - 10
apps/web/src/nodes/src/condition/setter.vue

@@ -32,9 +32,10 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref, watch } from 'vue'
+import { computed } from 'vue'
 import Condition from '../../_base/condition/index.vue'
 import { Icon } from '@repo/ui'
+import { useSetterModel } from '../_shared/useSetterModel'
 
 import type { ConditionData } from './index'
 import type { ConditionType } from '../../Interface'
@@ -50,17 +51,9 @@ const props = defineProps<{
 
 const emit = defineEmits<Emits>()
 
-const formData = ref<ConditionData>(props.data)
+const formData = useSetterModel<ConditionData>(props, emit)
 const cases = computed<ConditionData['cases']>(() => formData.value.cases)
 
-watch(
-	formData.value,
-	(newVal) => {
-		emit('update', newVal)
-	},
-	{ deep: true }
-)
-
 const getId = () => {
 	const ids = cases.value.map((caseItem) => caseItem.id.split('_')[1]).map(Number)
 	const maxId = Math.max(...ids, 0)

+ 2 - 9
apps/web/src/nodes/src/end/setter.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
-import { ref, watch } from 'vue'
 import OutputVariables from '@/nodes/_base/OutputVariables.vue'
+import { useSetterModel } from '../_shared/useSetterModel'
 
 import type { EndData } from './index'
 
@@ -12,14 +12,7 @@ const emit = defineEmits<{
 	(e: 'update', data: EndData): void
 }>()
 
-const formData = ref<EndData>(props.data)
-
-watch(
-	() => formData.value,
-	(newVal) => {
-		emit('update', newVal)
-	}
-)
+const formData = useSetterModel<EndData>(props, emit)
 </script>
 
 <template>

+ 63 - 34
apps/web/src/nodes/src/http/setter.vue

@@ -1,9 +1,11 @@
 <script lang="ts" setup>
 import { watch, ref } from 'vue'
+import { cloneDeep, isEqual } from 'lodash-es'
 import { IconButton } from '@repo/ui'
 import ErrorHandling from '@/nodes/_base/ErrorHandling.vue'
 import RetryConfig from '@/nodes/_base/RetryConfig.vue'
 import VarInput from '@/nodes/_base/VarInput.vue'
+import { useSetterModel } from '../_shared/useSetterModel'
 
 import type { HttpRequestData } from './index'
 
@@ -37,64 +39,91 @@ const headers = ref<{ name: string; value: string }[]>([])
 const params = ref<{ name: string; value: string }[]>([])
 const body = ref<{ key: string; value: string; type: string }[]>([])
 
-const formData = ref<HttpRequestData>(props.data)
+const formData = useSetterModel<HttpRequestData>(props, emit)
 const dialogVisible = ref(false)
 
-watch(
-	() => formData.value,
-	(val) => {
-		if (val) {
-			console.log('formData changed:', val)
-			emit('update', val)
-		}
-	},
-	{
-		deep: true
+const createEmptyHeader = () => ({ name: '', value: '' })
+const createEmptyBody = () => ({ key: '', value: '', type: 'text' })
+
+const isKVBodyType = (type: string) => {
+	return type === 'form-data' || type === 'x-www-form-urlencoded'
+}
+
+const syncEditorRowsFromFormData = () => {
+	const nextHeaders = formData.value.heads.length
+		? cloneDeep(formData.value.heads)
+		: [createEmptyHeader()]
+	if (!isEqual(nextHeaders, headers.value)) {
+		headers.value = nextHeaders
+	}
+
+	const nextParams = formData.value.params.length
+		? cloneDeep(formData.value.params)
+		: [createEmptyHeader()]
+	if (!isEqual(nextParams, params.value)) {
+		params.value = nextParams
 	}
-)
+
+	const nextBody = formData.value.body.type === 'none'
+		? []
+		: formData.value.body.data.length
+			? cloneDeep(formData.value.body.data)
+			: [createEmptyBody()]
+
+	if (!isEqual(nextBody, body.value)) {
+		body.value = nextBody
+	}
+}
 
 watch(
-	props.data,
-	(newVal) => {
-		if (newVal) {
-			headers.value = newVal.heads.length ? newVal.heads : [{ name: '', value: '' }]
-			params.value = newVal.params.length ? newVal.params : [{ name: '', value: '' }]
-			body.value = newVal.body.data.length
-				? newVal.body.data
-				: [{ key: '', value: '', type: 'text' }]
-		}
+	() => [formData.value.heads, formData.value.params, formData.value.body.type, formData.value.body.data],
+	() => {
+		syncEditorRowsFromFormData()
 	},
 	{
-		immediate: true,
-		once: true
+		deep: true,
+		immediate: true
 	}
 )
 
 watch(
-	headers.value,
+	headers,
 	(newVal) => {
-		if (newVal) {
-			formData.value.heads = newVal.filter((item) => item.name || item.value)
+		const normalized = newVal.filter((item) => item.name || item.value)
+		if (!isEqual(normalized, formData.value.heads)) {
+			formData.value.heads = normalized
 		}
 	},
 	{ deep: true }
 )
 
 watch(
-	params.value,
+	params,
 	(newVal) => {
-		if (newVal) {
-			formData.value.params = newVal.filter((item) => item.name || item.value)
+		const normalized = newVal.filter((item) => item.name || item.value)
+		if (!isEqual(normalized, formData.value.params)) {
+			formData.value.params = normalized
 		}
 	},
 	{ deep: true }
 )
 
 watch(
-	body.value,
+	body,
 	(newVal) => {
-		if (newVal) {
-			formData.value.body.data = newVal.filter((item) => item.key || item.value)
+		if (formData.value.body.type === 'none') {
+			if (formData.value.body.data.length) {
+				formData.value.body.data = []
+			}
+			return
+		}
+
+		const normalized = isKVBodyType(formData.value.body.type)
+			? newVal.filter((item) => item.key || item.value)
+			: [{ key: '', value: newVal[0]?.value || '', type: 'text' }]
+
+		if (!isEqual(normalized, formData.value.body.data)) {
+			formData.value.body.data = normalized
 		}
 	},
 	{ deep: true }
@@ -127,9 +156,9 @@ const handleDeleteParam = (index: number) => {
 
 const handleChangeBodyType = (type: string) => {
 	if (type === 'none') {
-		formData.value.body.data = []
+		body.value = []
 	} else {
-		formData.value.body.data = [{ key: '', value: '', type: 'text' }]
+		body.value = [createEmptyBody()]
 	}
 }
 

+ 3 - 14
apps/web/src/nodes/src/iteration/setter.vue

@@ -1,6 +1,7 @@
 <script lang="ts" setup>
-import { watch, ref, computed } from 'vue'
+import { computed } from 'vue'
 import VarSelect from '@/nodes/_base/VarSelect.vue'
+import { useSetterModel } from '../_shared/useSetterModel'
 
 import type { IterationData } from './index'
 import type { NodeVariableType } from '@/nodes/Interface'
@@ -19,7 +20,7 @@ const error_handle_mode_options = [
 	{ label: '移除错误输出', value: 'remove-abnormal-output' }
 ]
 
-const formData = ref<IterationData>(props.data)
+const formData = useSetterModel<IterationData>(props, emit)
 
 const inputVar = computed({
 	get() {
@@ -34,18 +35,6 @@ const inputVar = computed({
 	}
 })
 
-watch(
-	() => formData.value,
-	(val) => {
-		if (val) {
-			emit('update', val)
-		}
-	},
-	{
-		deep: true
-	}
-)
-
 const handleInputVarChange = (val: { value: string; type: string }) => {
 	if (inputVar.value) {
 		inputVar.value.value = val.value

+ 3 - 14
apps/web/src/nodes/src/list/setter.vue

@@ -1,7 +1,8 @@
 <script lang="ts" setup>
-import { watch, ref, computed } from 'vue'
+import { computed } from 'vue'
 import VarSelect from '@/nodes/_base/VarSelect.vue'
 import VarInput from '@/nodes/_base/VarInput.vue'
+import { useSetterModel } from '../_shared/useSetterModel'
 
 import type { ListData } from './index'
 import type { NodeVariableType } from '@/nodes/Interface'
@@ -15,7 +16,7 @@ const emit = defineEmits<{
 	update: [data: ListData]
 }>()
 
-const formData = ref<ListData>(props.data)
+const formData = useSetterModel<ListData>(props, emit)
 
 const inputVar = computed({
 	get() {
@@ -43,18 +44,6 @@ const conditions = computed({
 	}
 })
 
-watch(
-	() => formData.value,
-	(val) => {
-		if (val) {
-			emit('update', val)
-		}
-	},
-	{
-		deep: true
-	}
-)
-
 const handleInputVarChange = (val: { value: string; type: string }) => {
 	if (inputVar.value) {
 		inputVar.value.value = val.value

+ 2 - 14
apps/web/src/nodes/src/loop/setter.vue

@@ -1,8 +1,8 @@
 <script lang="ts" setup>
-import { watch, ref } from 'vue'
 import Condition from '@/nodes/_base/condition/index.vue'
 import LoopVar from './components/LoopVar.vue'
 import { IconButton } from '@repo/ui'
+import { useSetterModel } from '../_shared/useSetterModel'
 
 import type { LoopData } from './index'
 import type { ConditionType } from '@/nodes/Interface'
@@ -15,19 +15,7 @@ const emit = defineEmits<{
 	update: [data: LoopData]
 }>()
 
-const formData = ref<LoopData>(props.data)
-
-watch(
-	() => formData.value,
-	(val) => {
-		if (val) {
-			emit('update', val)
-		}
-	},
-	{
-		deep: true
-	}
-)
+const formData = useSetterModel<LoopData>(props, emit)
 
 const handleAddLoopVar = () => {
 	formData.value.variables.push({

Файловите разлики са ограничени, защото са твърде много
+ 8479 - 10071
pnpm-lock.yaml