Quellcode durchsuchen

feat:新增设置器模版

lj1559651600@163.com vor 6 Tagen
Ursprung
Commit
cfd9f7bb27

+ 9 - 1
apps/web/components.d.ts

@@ -11,6 +11,7 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    CodeEditor: typeof import('./src/components/SetterCommon/Code/CodeEditor.vue')['default']
     CodeSetter: typeof import('./src/components/setter/CodeSetter.vue')['default']
     ConditionSetter: typeof import('./src/components/setter/ConditionSetter.vue')['default']
     CustomDropdown: typeof import('./src/components/CustomDropdown/index.vue')['default']
@@ -39,7 +40,9 @@ declare module 'vue' {
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElSlider: typeof import('element-plus/es')['ElSlider']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
+    ElSwitch: typeof import('element-plus/es')['ElSwitch']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
@@ -47,13 +50,18 @@ declare module 'vue' {
     ElTag: typeof import('element-plus/es')['ElTag']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     HttpSetter: typeof import('./src/components/setter/HttpSetter.vue')['default']
+    InputVariables: typeof import('./src/components/SetterCommon/Code/InputVariables.vue')['default']
+    OutputVariables: typeof import('./src/components/SetterCommon/Code/OutputVariables.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
-    RunWork: typeof import('./src/components/RunWork.vue')['default']
+    RunWork: typeof import('./src/components/RunWorkflow/RunWork.vue')['default']
+    RunWorkflow: typeof import('./src/components/RunWorkflow/index.vue')['default']
     SearchDialog: typeof import('./src/components/SearchDialog/index.vue')['default']
     Setter: typeof import('./src/components/setter/index.vue')['default']
     Sidebar: typeof import('./src/components/Sidebar/index.vue')['default']
     SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
+    TabGroup: typeof import('./src/components/SetterCommon/Code/TabGroup.vue')['default']
+    TestConfig: typeof import('./src/components/SetterCommon/Code/TestConfig.vue')['default']
   }
   export interface GlobalDirectives {
     vLoading: typeof import('element-plus/es')['ElLoadingDirective']

+ 0 - 48
apps/web/src/components/RunWork.vue

@@ -1,48 +0,0 @@
-<!--
- * @Author: liuJie
- * @Date: 2026-01-24 21:25:04
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-24 22:01:08
- * @Describe: 运行工作流
--->
-<script lang="ts" setup>
-import { ElDrawer, ElButton } from 'element-plus';
-import { Icon } from '@iconify/vue';
-const props = withDefaults(
-    defineProps<{
-        visible: boolean,
-    }>(),
-    {
-        visible: false,
-    }
-);
-const emit = defineEmits<{
-    'update:visible': [value: boolean]
-}>()
-</script>
-<template>
-    <div class='content'>
-        <ElDrawer :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>
-
-            <!-- Drawer content -->
-            This is drawer content.
-
-            <template #footer>
-                <ElButton type="success" size="large" class="w-full" @click="emit('update:visible', false)">
-                    运行
-                </ElButton>
-            </template>
-
-        </ElDrawer>
-    </div>
-</template>
-<style lang="less" scoped>
-.pt-0 {
-    padding-top: 0px;
-}
-</style>

+ 87 - 0
apps/web/src/components/RunWorkflow/index.vue

@@ -0,0 +1,87 @@
+<!--
+ * @Author: liuJie
+ * @Date: 2026-01-24 21:25:04
+ * @LastEditors: liuJie
+ * @LastEditTime: 2026-01-24 22:01:08
+ * @Describe: 运行工作流
+-->
+<script lang="ts" setup>
+import { ElButton } from 'element-plus';
+import { Icon } from '@iconify/vue';
+const props = withDefaults(
+    defineProps<{
+        visible: boolean,
+    }>(),
+    {
+        visible: false,
+    }
+);
+const emit = defineEmits<{
+    'update:visible': [value: boolean]
+}>()
+</script>
+<template>
+    <div class='runWorkflow'>
+			<div class="drawer shadow-2xl" :class="{ 'drawer--open': props.visible }">
+
+            <header>
+                <h4>运行工作流</h4>
+								<Icon icon="lucide:x" height="24" width="24" @click="emit('update:visible', false)" class="cursor-pointer"></Icon>
+            </header>
+
+            <!-- Drawer content -->
+            This is drawer content.
+
+            <footer>
+                <ElButton type="success" size="large" class="w-full" @click="emit('update:visible', false)">
+                    运行
+                </ElButton>
+            </footer>
+
+			</div>
+    </div>
+</template>
+<style lang="less" scoped>
+.runWorkflow {
+	/* 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;
+		}
+
+		/* 显示状态 */
+		.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;
+		}
+
+		/* 内容区 */
+		.drawer .content {
+			flex: 1;
+			padding: 16px;
+			overflow-y: auto;
+		}
+}
+</style>

+ 126 - 0
apps/web/src/components/SetterCommon/Code/CodeEditor.vue

@@ -0,0 +1,126 @@
+<template>
+    <div class="space-y-2">
+        <div class="flex items-center justify-between">
+            <div class="flex items-center gap-2">
+                <select v-model="selectedLanguage" @change="handleLanguageChange"
+                    class="px-3 py-1.5 text-sm font-medium border border-gray-200 rounded-lg focus:outline-none bg-white">
+                    <option value="python">Python</option>
+                    <option value="javascript">JavaScript</option>
+                    <option value="typescript">TypeScript</option>
+                </select>
+            </div>
+
+            <div class="flex items-center gap-1">
+                <button @click="formatCode" class="p-1.5 hover:bg-gray-100 rounded transition-colors" title="格式化代码">
+                    <Icon icon="lucide:align-left" :height="16" :width="16" class="text-gray-600" />
+                </button>
+                <button @click="copyCode" class="p-1.5 hover:bg-gray-100 rounded transition-colors" title="复制代码">
+                    <Icon icon="lucide:copy" :height="16" :width="16" class="text-gray-600" />
+                </button>
+            </div>
+        </div>
+
+        <div class="relative">
+            <div
+                class="absolute left-0 top-0 bottom-[8px] w-12 bg-gray-200 border-r border-gray-200 flex flex-col text-xs text-gray-500 select-none rounded-l-lg overflow-hidden">
+                <div v-for="line in lineNumbers" :key="line" class="h-6 flex items-center justify-end px-2">
+                    {{ line }}
+                </div>
+            </div>
+            <textarea v-model="code" @input="handleCodeChange"
+                class="w-[80%] h-64 pl-12 font-mono text-sm border border-gray-100 rounded-lg focus:outline-none resize-none bg-gray-200"
+                :placeholder="placeholder" spellcheck="false"></textarea>
+        </div>
+
+        <div v-if="showCopySuccess" class="flex items-center gap-1 text-xs text-green-600">
+            <Icon icon="lucide:check" :height="14" :width="14" />
+            代码已复制到剪贴板
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch } from 'vue'
+import { Icon } from '@iconify/vue'
+
+interface Props {
+    modelValue: string
+    language?: string
+}
+
+interface Emits {
+    (e: 'update:modelValue', value: string): void
+    (e: 'update:language', value: string): void
+}
+
+const props = withDefaults(defineProps<Props>(), {
+    language: 'python'
+})
+
+const emit = defineEmits<Emits>()
+
+const code = ref(props.modelValue || '')
+const selectedLanguage = ref(props.language)
+const showCopySuccess = ref(false)
+
+const placeholder = computed(() => {
+    const placeholders: Record<string, string> = {
+        python: 'def main(arg1: str, arg2: str):\n    return {\n        "result": arg1 + arg2,\n    }',
+        javascript: 'function main(arg1, arg2) {\n    return {\n        result: arg1 + arg2\n    };\n}',
+        typescript: 'function main(arg1: string, arg2: string): object {\n    return {\n        result: arg1 + arg2\n    };\n}'
+    }
+    return placeholders[selectedLanguage.value] || placeholders.python
+})
+
+const lineNumbers = computed(() => {
+    const lines = code.value.split('\n').length
+    return Array.from({ length: Math.max(lines, 10) }, (_, i) => i + 1)
+})
+
+watch(
+    () => props.modelValue,
+    (newVal) => {
+        code.value = newVal || ''
+    }
+)
+
+watch(
+    () => props.language,
+    (newVal) => {
+        selectedLanguage.value = newVal
+    }
+)
+
+const handleCodeChange = () => {
+    emit('update:modelValue', code.value)
+}
+
+const handleLanguageChange = () => {
+    emit('update:language', selectedLanguage.value)
+}
+
+const formatCode = () => {
+    // 简单的代码格式化逻辑
+    const lines = code.value.split('\n')
+    const formatted = lines.map(line => line.trim()).join('\n')
+    code.value = formatted
+    handleCodeChange()
+}
+
+const copyCode = async () => {
+    try {
+        await navigator.clipboard.writeText(code.value)
+        showCopySuccess.value = true
+        setTimeout(() => {
+            showCopySuccess.value = false
+        }, 2000)
+    } catch (err) {
+        console.error('Failed to copy code:', err)
+    }
+}
+
+const toggleFullscreen = () => {
+    // 全屏功能需要父组件支持
+    console.log('Toggle fullscreen')
+}
+</script>

+ 93 - 0
apps/web/src/components/SetterCommon/Code/InputVariables.vue

@@ -0,0 +1,93 @@
+<template>
+	<div class="space-y-3">
+		<div class="flex items-center justify-between">
+			<label class="text-sm font-medium text-gray-700">输入变量</label>
+			<button
+				@click="addVariable"
+				class="p-1 hover:bg-gray-100 rounded transition-colors"
+				title="添加变量"
+			>
+				<Icon icon="lucide:plus" :height="16" :width="16" class="text-gray-600" />
+			</button>
+		</div>
+
+		<div class="space-y-2">
+			<div
+				v-for="(variable, index) in variables"
+				:key="variable.id"
+				class="flex items-center gap-2 group"
+			>
+				<input
+					v-model="variable.name"
+					type="text"
+					:placeholder="`arg${index + 1}`"
+					class="flex-1 px-3 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
+					@input="handleVariableChange"
+				/>
+				<span class="text-xs text-gray-400 flex items-center gap-1">
+          <Icon icon="lucide:x" :height="12" :width="12" />
+          设置变量值
+        </span>
+				<button
+					@click="removeVariable(index)"
+					class="p-1 opacity-0 group-hover:opacity-100 hover:bg-red-50 rounded transition-all"
+					title="删除变量"
+				>
+					<Icon icon="lucide:trash-2" :height="16" :width="16" class="text-red-500" />
+				</button>
+			</div>
+
+			<div v-if="variables.length === 0" class="text-sm text-gray-400 text-center py-4">
+				暂无输入变量
+			</div>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+import { Icon } from '@iconify/vue'
+
+interface Variable {
+	id: string
+	name: string
+}
+
+interface Props {
+	modelValue: Variable[]
+}
+
+interface Emits {
+	(e: 'update:modelValue', value: Variable[]): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const variables = ref<Variable[]>(props.modelValue || [])
+
+watch(
+	() => props.modelValue,
+	(newVal) => {
+		variables.value = newVal || []
+	}
+)
+
+const addVariable = () => {
+	const newVariable: Variable = {
+		id: `var_${Date.now()}`,
+		name: `arg${variables.value.length + 1}`
+	}
+	variables.value.push(newVariable)
+	handleVariableChange()
+}
+
+const removeVariable = (index: number) => {
+	variables.value.splice(index, 1)
+	handleVariableChange()
+}
+
+const handleVariableChange = () => {
+	emit('update:modelValue', variables.value)
+}
+</script>

+ 94 - 0
apps/web/src/components/SetterCommon/Code/OutputVariables.vue

@@ -0,0 +1,94 @@
+<!--
+ * @Author: liuJie
+ * @Date: 2026-01-27 14:38:32
+ * @LastEditors: liuJie
+ * @LastEditTime: 2026-01-27 17:26:25
+ * @Describe: file describe
+-->
+<template>
+    <div class="">
+        <div class="flex items-center justify-between">
+            <label class="text-sm font-medium text-gray-700">
+                输出变量
+                <span class="text-red-500">*</span>
+            </label>
+            <button @click="addOutput" class="p-1 hover:bg-gray-100 rounded transition-colors" title="添加输出">
+                <Icon icon="lucide:plus" :height="16" :width="16" class="text-gray-600" />
+            </button>
+        </div>
+
+        <div class="space-y-2">
+            <div v-for="(output, index) in outputs" :key="output.id" class="flex items-center gap-2 group">
+                <input v-model="output.name" type="text" placeholder="result"
+                    class="flex-1 px-3 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
+                    @input="handleOutputChange" />
+                <select v-model="output.type" @change="handleOutputChange"
+                    class="px-3 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white min-w-[100px]">
+                    <option value="String">String</option>
+                    <option value="Number">Number</option>
+                    <option value="Boolean">Boolean</option>
+                    <option value="Object">Object</option>
+                    <option value="Array">Array</option>
+                </select>
+                <button @click="removeOutput(index)"
+                    class="p-1 opacity-0 group-hover:opacity-100 hover:bg-red-50 rounded transition-all" title="删除输出">
+                    <Icon icon="lucide:trash-2" :height="16" :width="16" class="text-red-500" />
+                </button>
+            </div>
+
+            <div v-if="outputs.length === 0" class="text-sm text-gray-400 text-center py-4">
+                请添加至少一个输出变量
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+import { Icon } from '@iconify/vue'
+
+interface OutputVariable {
+    id: string
+    name: string
+    type: string
+}
+
+interface Props {
+    modelValue: OutputVariable[]
+}
+
+interface Emits {
+    (e: 'update:modelValue', value: OutputVariable[]): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const outputs = ref<OutputVariable[]>(props.modelValue || [])
+
+watch(
+    () => props.modelValue,
+    (newVal) => {
+        outputs.value = newVal || []
+    }
+)
+
+const addOutput = () => {
+    const newOutput: OutputVariable = {
+        id: `output_${Date.now()}`,
+        name: 'result',
+        type: 'String'
+    }
+    outputs.value.push(newOutput)
+    handleOutputChange()
+}
+
+const removeOutput = (index: number) => {
+    outputs.value.splice(index, 1)
+    handleOutputChange()
+}
+
+const handleOutputChange = () => {
+    emit('update:modelValue', outputs.value)
+}
+</script>

+ 46 - 0
apps/web/src/components/SetterCommon/Code/TabGroup.vue

@@ -0,0 +1,46 @@
+<template>
+	<div class="border-b border-gray-200">
+		<div class="flex gap-4">
+			<div
+				v-for="tab in tabs"
+				:key="tab.value"
+				@click="selectTab(tab.value)"
+				class="px-4 py-2 text-sm font-medium transition-colors relative"
+				:class="[
+          modelValue === tab.value
+            ? 'text-blue-600'
+            : 'text-gray-600 hover:text-gray-900'
+        ]"
+			>
+				{{ tab.label }}
+				<div
+					v-if="modelValue === tab.value"
+					class="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-600"
+				></div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts">
+interface Tab {
+	label: string
+	value: string
+}
+
+interface Props {
+	modelValue: string
+	tabs: Tab[]
+}
+
+interface Emits {
+	(e: 'update:modelValue', value: string): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const selectTab = (value: string) => {
+	emit('update:modelValue', value)
+}
+</script>

+ 93 - 0
apps/web/src/components/SetterCommon/Code/TestConfig.vue

@@ -0,0 +1,93 @@
+<!--
+ * @Author: liuJie
+ * @Date: 2026-01-27 14:45:03
+ * @LastEditors: liuJie
+ * @LastEditTime: 2026-01-27 18:19:49
+ * @Describe: file describe
+-->
+<template>
+    <div class="space-y-4">
+        <!-- 失败时重试 -->
+        <div class="space-y-2">
+            <div class="flex items-center justify-between">
+                <label class="text-sm font-medium text-gray-700">失败时重试</label>
+                <el-switch v-model="retryState" @change="toggleRetry" />
+            </div>
+            <div class="space-y-2" v-if="config.retryEnabled">
+                <div class="pl-[22px]">
+                    <p class="m-0 text-sm">最大重试次数</p>
+                    <el-slider v-model="config.maxRetries" show-input />
+                </div>
+                <div class="pl-[22px]">
+                    <p class="m-0 text-sm">重试间隔</p>
+                    <el-slider v-model="config.retryInterval" show-input />
+                </div>
+            </div>
+        </div>
+
+        <!-- 异常处理 -->
+        <div class="space-y-2">
+            <label class="text-sm font-medium text-gray-700 flex items-center gap-1">
+                异常处理
+                <Icon icon="lucide:info" :height="14" :width="14" class="text-gray-400" />
+            </label>
+            <select v-model="config.errorHandling" @change="handleConfigChange"
+                class="w-full px-3 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white">
+                <option value="none">无</option>
+                <option value="stop">停止执行</option>
+                <option value="continue">继续执行</option>
+                <option value="retry">重试</option>
+            </select>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+import { Icon } from '@iconify/vue'
+
+interface TestConfig {
+    retryEnabled: boolean
+    errorHandling: string,
+    maxRetries?: number
+    retryInterval?: number
+}
+
+interface Props {
+    modelValue: TestConfig
+}
+
+interface Emits {
+    (e: 'update:modelValue', value: TestConfig): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const retryState = ref(false)
+const config = ref<TestConfig>(props.modelValue || {
+    retryEnabled: false,
+    errorHandling: 'none',
+    maxRetries: 3,
+    retryInterval: 1000
+})
+
+watch(
+    () => props.modelValue,
+    (newVal) => {
+        config.value = newVal || {
+            retryEnabled: false,
+            errorHandling: 'none'
+        }
+    }
+)
+
+const toggleRetry = () => {
+    config.value.retryEnabled = !config.value.retryEnabled
+    handleConfigChange()
+}
+
+const handleConfigChange = () => {
+    emit('update:modelValue', config.value)
+}
+</script>

+ 132 - 39
apps/web/src/components/setter/CodeSetter.vue

@@ -1,46 +1,139 @@
-<!--
- * @Author: liuJie
- * @Date: 2026-01-25 22:08:04
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 23:04:14
- * @Describe: code设置器
--->
-<script lang="ts" setup>
-import { ElDrawer, ElButton } from 'element-plus';
-import { Icon } from '@iconify/vue';
-const props = withDefaults(
-    defineProps<{
-        data: any,
-        visible: boolean,
-    }>(),
-    {
-        visible: false,
-        data: {}
-    }
-);
-const emit = defineEmits<{
-    'update:visible': [value: boolean]
-}>()
-</script>
 <template>
-    <div class='content'>
-        <ElDrawer :model-value="visible" :show-close="false" size="25%" @close="emit('update:visible', false)">
+    <div class=" bg-black bg-opacity-50 flex items-center justify-center">
+        <div class="bg-white  w-full max-w-4xl max-h-[90vh] flex flex-col">
+            <!-- Header -->
+            <div class="flex items-start justify-between border-b border-gray-200">
+                <div class="flex items-center gap-3">
+                    <div class="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
+                        <Icon icon="lucide:code-2" :height="20" :width="20" class="text-blue-600" />
+                    </div>
+                    <div>
+                        <h2 class="text-lg font-semibold text-gray-900 m-0">代码执行</h2>
+                    </div>
+
+                </div>
+                <div class="flex items-center gap-2">
+                    <button @click="emit('update:visible', false)"
+                        class="px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 focus:outline-none rounded-lg flex items-center gap-2">
+                        <Icon icon="lucide:play" :height="16" :width="16" />
+                    </button>
+                    <button class="p-2 hover:bg-gray-100 rounded-lg focus:outline-none">
+                        <Icon icon="lucide:more-horizontal" :height="18" :width="18" class="text-gray-600" />
+                    </button>
+                    <button @click="handleClose" class="p-2 hover:bg-gray-100 rounded-lg focus:outline-none">
+                        <Icon icon="lucide:x" :height="18" :width="18" class="text-gray-600" />
+                    </button>
+                </div>
+
+            </div>
+            <div>
+                <input v-model="nodeName" type="text" placeholder="添加描述..."
+                    class="text-sm text-gray-500 border-none outline-none focus:ring-0 p-0 mt-0.5 w-full" />
+            </div>
+            <!--			 Tab 导航-->
+            <div class="mt-2">
+                <TabGroup v-model="activeTab" :tabs="tabs" />
+            </div>
 
-            <template #header>
-                <h4>代码执行</h4>
-                <Icon icon="lucide:x" height="24" width="24"></Icon>
-            </template>
+            <!-- Content -->
+            <div class=" overflow-auto pt-4 pb-20">
+                <div v-show="activeTab === 'settings'" class="space-y-6">
+                    <!-- 输入变量 -->
+                    <InputVariables v-model="formData.inputVariables" />
 
-            <!-- Drawer content -->
-            This is drawer content.
+                    <!-- 代码编辑器 -->
+                    <CodeEditor v-model="formData.code" v-model:language="formData.language" />
 
-            <!-- <template #footer>
-                <ElButton type="success" size="large" class="w-full" @click="emit('update:visible', false)">
-                    运行
-                </ElButton>
-            </template> -->
+                    <!-- 输出变量 -->
+                    <OutputVariables v-model="formData.outputVariables" />
 
-        </ElDrawer>
+                    <!-- 测试配置 -->
+                    <TestConfig v-model="formData.testConfig" />
+                </div>
+
+                <div v-show="activeTab === 'lastRun'" class="space-y-4">
+                    <div class="text-center py-12 text-gray-400">
+                        <Icon icon="lucide:clock" :height="48" :width="48" class="mx-auto mb-3 text-gray-300" />
+                        <p class="text-sm">暂无运行记录</p>
+                    </div>
+                </div>
+            </div>
+        </div>
     </div>
 </template>
-<style lang="scss" scoped></style>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue'
+import { Icon } from '@iconify/vue'
+import TabGroup from '@/components/SetterCommon/Code/TabGroup.vue'
+import InputVariables from '@/components/SetterCommon/Code/InputVariables.vue'
+import CodeEditor from '@/components/SetterCommon/Code/CodeEditor.vue'
+import OutputVariables from '@/components/SetterCommon/Code/OutputVariables.vue'
+import TestConfig from '@/components/SetterCommon/Code/TestConfig.vue'
+interface Variable {
+    id: string
+    name: string
+}
+
+interface OutputVariable {
+    id: string
+    name: string
+    type: string
+}
+
+interface TestConfigData {
+    retryEnabled: boolean
+    errorHandling: string
+}
+
+const props = withDefaults(defineProps<{
+    visible: boolean
+}>(), {
+    visible: false
+})
+
+const emit = defineEmits<{
+    'update:visible': [value: boolean]
+}>()
+
+const nodeName = ref('代码执行')
+const activeTab = ref('settings')
+
+const tabs = [
+    { label: '设置', value: 'settings' },
+    { label: '上次运行', value: 'lastRun' }
+]
+
+const formData = reactive({
+    // 输入变量
+    inputVariables: [
+        { id: 'var_1', name: 'arg1' },
+        { id: 'var_2', name: 'arg2' }
+    ] as Variable[],
+    // 代码内容
+    code: `function main(arg1, arg2) {
+            return {
+                "result": arg1 + arg2,
+            }`,
+    // 编程语言
+    language: 'JavaScript',
+    // 输出变量
+    outputVariables: [
+        { id: 'output_1', name: 'result', type: 'String' }
+    ] as OutputVariable[],
+    // 测试配置
+    testConfig: {
+        retryEnabled: false,
+        errorHandling: 'none'
+    } as TestConfigData
+})
+
+const handleClose = () => {
+    emit('update:visible', false)
+}
+
+const handleTest = () => {
+    console.log('Testing code execution...', formData)
+}
+
+</script>

+ 10 - 23
apps/web/src/components/setter/index.vue

@@ -2,7 +2,7 @@
  * @Author: liuJie
  * @Date: 2026-01-25 22:13:06
  * @LastEditors: liuJie
- * @LastEditTime: 2026-01-27 10:01:23
+ * @LastEditTime: 2026-01-27 18:24:02
  * @Describe: file describe
 -->
 <script lang="ts" setup>
@@ -12,14 +12,15 @@ import { useComponentMapInspector } from "@/store"
 
 const store = useComponentMapInspector()
 
-console.log(store.componentMap['http'])
+console.log(store.$state.componentMap['http'], '节点映射')
 
 interface Props {
+    nodeType: string
     data: any, // 暂时定义
     visible: boolean
 }
 const props = withDefaults(defineProps<Props>(), {
-    id: '',
+    nodeType: '',
     data: null,
     visible: false
 })
@@ -33,16 +34,13 @@ const closeDrawer = () => {
 <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>
+                <component :is="store.$state.componentMap['code']" :data="props.data" v-model:visible="props.visible">
+                </component>
+                <!--               <component :is="store.$state.componentMap[props.nodeType]" :data="props.data"></component>-->
             </div>
         </div>
-
-        <HttpSetter :data="data" v-model:visible="props.visible" />
     </div>
 </template>
 <style lang="less" scoped>
@@ -63,7 +61,7 @@ const closeDrawer = () => {
         border: 1px solid #e4e4e4;
 
         /* 初始隐藏状态 */
-        transform: translateX(100%);
+        transform: translateX(110%);
         transition: transform 0.25s ease;
     }
 
@@ -72,21 +70,10 @@ const closeDrawer = () => {
         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;
-    }
-
     /* 内容区 */
     .drawer .content {
-        flex: 1;
         padding: 16px;
-        overflow-y: auto;
+        overflow: hidden;
     }
 }
 </style>

+ 9 - 5
apps/web/src/views/Editor.vue

@@ -8,8 +8,8 @@
 			@drop="handleDrop"
 			@run="handleRunWorkflow"
 		/>
-		<RunWork v-model:visible="runVisible" />
-		<Setter :data="nodeID" v-model:visible="setterVisible" />
+		<RunWorkflow v-model:visible="runVisible" />
+		<Setter :data="{}" :nodeType="nodeType" v-model:visible="setterVisible" />
 	</div>
 </template>
 
@@ -17,7 +17,7 @@
 import { startNode, endNode, httpNode, conditionNode, databaseNode, codeNode } from '@repo/nodes'
 import { Workflow, type IWorkflow, type XYPosition, type Connection } from '@repo/workflow'
 import Setter from '@/components/setter/index.vue'
-import RunWork from '@/components/RunWork.vue'
+import RunWorkflow from '@/components/RunWorkflow/index.vue'
 import type { SourceType } from '@repo/nodes'
 import { ref } from 'vue'
 
@@ -267,13 +267,16 @@ const workflow = ref<IWorkflow>({
 	// 		}
 	// 	]
 })
-const nodeID = ref('')
+// 节点类型
+const nodeType = ref('')
 const setterVisible = ref(false)
 const runVisible = ref(false)
 const handleRunWorkflow = () => {
 	runVisible.value = true
 	console.log('run workflow')
 }
+
+//  创建节点
 const handleNodeCreate = (value: SourceType) => {
 	console.log(value)
 
@@ -291,9 +294,10 @@ const handleNodeCreate = (value: SourceType) => {
 	}
 	console.log(workflow.value.nodes, 'workflow.nodes')
 }
+// 点击节点
 const handleNodeClick = (id: string, position: XYPosition) => {
 	console.log('click node', id, position)
-	nodeID.value = id
+	nodeType.value = id
 	setterVisible.value = true
 }
 

+ 27 - 18
apps/web/tsconfig.app.json

@@ -1,20 +1,29 @@
 {
-  "extends": "@vue/tsconfig/tsconfig.dom.json",
-  "compilerOptions": {
-    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
-    "types": ["vite/client"],
-    "baseUrl": "./",
-    "paths": {
-      "@/*": ["src/*"]
+    "extends": "@vue/tsconfig/tsconfig.dom.json",
+    "compilerOptions": {
+        "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+        "types": [
+            "vite/client"
+        ],
+        "baseUrl": "./",
+        "paths": {
+            "@/*": [
+                "src/*"
+            ]
+        },
+        /* Linting */
+        "strict": true,
+        "noUnusedLocals": true,
+        "noUnusedParameters": true,
+        "erasableSyntaxOnly": true,
+        "noFallthroughCasesInSwitch": true,
+        "noUncheckedSideEffectImports": true
     },
-
-    /* Linting */
-    "strict": true,
-    "noUnusedLocals": true,
-    "noUnusedParameters": true,
-    "erasableSyntaxOnly": true,
-    "noFallthroughCasesInSwitch": true,
-    "noUncheckedSideEffectImports": true
-  },
-  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
-}
+    "include": [
+        "src/**/*.ts",
+        "src/**/*.tsx",
+        "src/**/*.vue",
+        "src/**/**/*.vue",
+        "src/**/**/**/*.vue"
+    ]
+}

+ 4 - 0
apps/web/vite.config.ts

@@ -1,3 +1,4 @@
+
 import { defineConfig } from 'vite'
 import vue from '@vitejs/plugin-vue'
 import path from 'path'
@@ -10,6 +11,9 @@ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
 
 // https://vite.dev/config/
 export default defineConfig({
+    server: {
+            hmr: true
+        },
 	plugins: [
 		vue(),
 		UnoCss(),