13 Incheckningar f21c3fa29a ... 52240ead20

Upphovsman SHA1 Meddelande Datum
  jiaxing.liao 52240ead20 Merge branch 'main' of https://git.shalu.com/Shalu/shalu-agent-workflow 1 månad sedan
  jiaxing.liao 630c29c69a perf: 优化功能 1 månad sedan
  lj1559651600@163.com b5e45e5601 Merge branch 'main' of https://git.shalu.com/Shalu/shalu-agent-workflow 1 månad sedan
  lj1559651600@163.com b02ece97a9 fix:删除.idea/目录下文件,并禁止提交 1 månad sedan
  jiaxing.liao aaf421ff10 Merge branch 'main' of https://git.shalu.com/Shalu/shalu-agent-workflow 1 månad sedan
  jiaxing.liao b5b5307f40 fix: 修改代码编辑器 1 månad sedan
  Mickey Mike a3f72726ce fix: 关于页面高度修改 1 månad sedan
  Mickey Mike df6c56353c fix: 添加丢失的svg 1 månad sedan
  jiaxing.liao 73caff261b Merge branch 'main' of https://git.shalu.com/Shalu/shalu-agent-workflow 1 månad sedan
  jiaxing.liao 107442b17e fix: 修改工作流打开 1 månad sedan
  lj1559651600@163.com 0290b48d9e Merge branch 'feature-0123-Ai-workFlow' of https://git.shalu.com/Shalu/shalu-agent-workflow 1 månad sedan
  lj1559651600@163.com 0e1f1f68eb Merge branch 'main' of https://git.shalu.com/Shalu/shalu-agent-workflow 1 månad sedan
  lj1559651600@163.com 345702d223 update 1 månad sedan

+ 0 - 8
.idea/modules.xml

@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="ProjectModuleManager">
-    <modules>
-      <module fileurl="file://$PROJECT_DIR$/.idea/shalu-agent-workflow.iml" filepath="$PROJECT_DIR$/.idea/shalu-agent-workflow.iml" />
-    </modules>
-  </component>
-</project>

+ 0 - 12
.idea/shalu-agent-workflow.iml

@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="WEB_MODULE" version="4">
-  <component name="NewModuleRootManager">
-    <content url="file://$MODULE_DIR$">
-      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
-      <excludeFolder url="file://$MODULE_DIR$/temp" />
-      <excludeFolder url="file://$MODULE_DIR$/tmp" />
-    </content>
-    <orderEntry type="inheritedJdk" />
-    <orderEntry type="sourceFolder" forTests="false" />
-  </component>
-</module>

+ 0 - 6
.idea/vcs.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="VcsDirectoryMappings">
-    <mapping directory="" vcs="Git" />
-  </component>
-</project>

+ 0 - 157
.idea/workspace.xml

@@ -1,157 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="AutoImportSettings">
-    <option name="autoReloadType" value="SELECTIVE" />
-  </component>
-  <component name="ChangeListManager">
-    <list default="true" id="41cd2400-3735-4e1a-9a77-7ea2e4a1c041" name="更改" comment="update">
-      <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/apps/web/src/components/setter/ConditionSetter.vue" beforeDir="false" afterPath="$PROJECT_DIR$/apps/web/src/components/setter/ConditionSetter.vue" afterDir="false" />
-    </list>
-    <option name="SHOW_DIALOG" value="false" />
-    <option name="HIGHLIGHT_CONFLICTS" value="true" />
-    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
-    <option name="LAST_RESOLUTION" value="IGNORE" />
-  </component>
-  <component name="Git.Merge.Settings">
-    <option name="BRANCH" value="feature-0123-Ai-workFlow" />
-  </component>
-  <component name="Git.Settings">
-    <option name="RECENT_BRANCH_BY_REPOSITORY">
-      <map>
-        <entry key="$PROJECT_DIR$" value="main" />
-      </map>
-    </option>
-    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
-  </component>
-  <component name="ProblemsViewState">
-    <option name="selectedTabId" value="CurrentFile" />
-  </component>
-  <component name="ProjectColorInfo">{
-  &quot;associatedIndex&quot;: 7
-}</component>
-  <component name="ProjectId" id="38m4mc5E2lcGRA9UjefZP9wo9yp" />
-  <component name="ProjectLevelVcsManager" settingsEditedManually="true">
-    <ConfirmationsSetting value="2" id="Add" />
-  </component>
-  <component name="ProjectViewState">
-    <option name="hideEmptyMiddlePackages" value="true" />
-    <option name="showLibraryContents" value="true" />
-  </component>
-  <component name="PropertiesComponent"><![CDATA[{
-  "keyToString": {
-    "RunOnceActivity.ShowReadmeOnStart": "true",
-    "git-widget-placeholder": "feature-0123-Ai-workFlow",
-    "last_opened_file_path": "/Users/liujie/Desktop/shalu/shalu-agent-workflow",
-    "node.js.detected.package.eslint": "true",
-    "node.js.detected.package.tslint": "true",
-    "node.js.selected.package.eslint": "(autodetect)",
-    "node.js.selected.package.tslint": "(autodetect)",
-    "nodejs_package_manager_path": "pnpm",
-    "settings.editor.selected.configurable": "preferences.sourceCode.JavaScript",
-    "ts.external.directory.path": "/Users/liujie/Desktop/shalu/shalu-agent-workflow/node_modules/typescript/lib",
-    "vue.rearranger.settings.migration": "true"
-  }
-}]]></component>
-  <component name="RecentsManager">
-    <key name="MoveFile.RECENT_KEYS">
-      <recent name="$PROJECT_DIR$/apps/web/src/components/SetterCommonComponents/Code" />
-      <recent name="$PROJECT_DIR$/apps/web/src/components/RunWorkflow" />
-    </key>
-  </component>
-  <component name="SharedIndexes">
-    <attachedChunks>
-      <set>
-        <option value="bundled-js-predefined-d6986cc7102b-5c90d61e3bab-JavaScript-WS-242.23726.96" />
-      </set>
-    </attachedChunks>
-  </component>
-  <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
-  <component name="SvnConfiguration">
-    <configuration>$USER_HOME$/.subversion</configuration>
-  </component>
-  <component name="TaskManager">
-    <task active="true" id="Default" summary="默认任务">
-      <changelist id="41cd2400-3735-4e1a-9a77-7ea2e4a1c041" name="更改" comment="" />
-      <created>1769391572649</created>
-      <option name="number" value="Default" />
-      <option name="presentableId" value="Default" />
-      <updated>1769391572649</updated>
-      <workItem from="1769391573741" duration="223000" />
-      <workItem from="1769391800519" duration="10090000" />
-      <workItem from="1769518660683" duration="5115000" />
-      <workItem from="1769525730430" duration="3654000" />
-      <workItem from="1769529933165" duration="6257000" />
-      <workItem from="1769579076775" duration="529000" />
-    </task>
-    <task id="LOCAL-00001" summary="合并冲突">
-      <option name="closed" value="true" />
-      <created>1769392417458</created>
-      <option name="number" value="00001" />
-      <option name="presentableId" value="LOCAL-00001" />
-      <option name="project" value="LOCAL" />
-      <updated>1769392417458</updated>
-    </task>
-    <task id="LOCAL-00002" summary="合并冲突">
-      <option name="closed" value="true" />
-      <created>1769392717747</created>
-      <option name="number" value="00002" />
-      <option name="presentableId" value="LOCAL-00002" />
-      <option name="project" value="LOCAL" />
-      <updated>1769392717747</updated>
-    </task>
-    <task id="LOCAL-00003" summary="合并冲突">
-      <option name="closed" value="true" />
-      <created>1769392895672</created>
-      <option name="number" value="00003" />
-      <option name="presentableId" value="LOCAL-00003" />
-      <option name="project" value="LOCAL" />
-      <updated>1769392895672</updated>
-    </task>
-    <task id="LOCAL-00004" summary="合并冲突">
-      <option name="closed" value="true" />
-      <created>1769393225277</created>
-      <option name="number" value="00004" />
-      <option name="presentableId" value="LOCAL-00004" />
-      <option name="project" value="LOCAL" />
-      <updated>1769393225277</updated>
-    </task>
-    <task id="LOCAL-00005" summary="合并冲突">
-      <option name="closed" value="true" />
-      <created>1769523432476</created>
-      <option name="number" value="00005" />
-      <option name="presentableId" value="LOCAL-00005" />
-      <option name="project" value="LOCAL" />
-      <updated>1769523432476</updated>
-    </task>
-    <task id="LOCAL-00006" summary="update">
-      <option name="closed" value="true" />
-      <created>1769537149075</created>
-      <option name="number" value="00006" />
-      <option name="presentableId" value="LOCAL-00006" />
-      <option name="project" value="LOCAL" />
-      <updated>1769537149075</updated>
-    </task>
-    <option name="localTasksCounter" value="7" />
-    <servers />
-  </component>
-  <component name="TypeScriptGeneratedFilesManager">
-    <option name="version" value="3" />
-  </component>
-  <component name="Vcs.Log.Tabs.Properties">
-    <option name="TAB_STATES">
-      <map>
-        <entry key="MAIN">
-          <value>
-            <State />
-          </value>
-        </entry>
-      </map>
-    </option>
-  </component>
-  <component name="VcsManagerConfiguration">
-    <MESSAGE value="合并冲突" />
-    <MESSAGE value="update" />
-    <option name="LAST_COMMIT_MESSAGE" value="update" />
-  </component>
-</project>

+ 0 - 2
apps/web/auto-imports.d.ts

@@ -7,8 +7,6 @@
 export {}
 declare global {
   const EffectScope: typeof import('vue').EffectScope
-  const ElMessage: typeof import('element-plus/es').ElMessage
-  const ElMessageBox: typeof import('element-plus/es').ElMessageBox
   const computed: typeof import('vue').computed
   const createApp: typeof import('vue').createApp
   const customRef: typeof import('vue').customRef

+ 4 - 0
apps/web/src/assets/icons/code.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <polyline points="16 18 22 12 16 6"></polyline>
+  <polyline points="8 6 2 12 8 18"></polyline>
+</svg>

+ 3 - 0
apps/web/src/assets/icons/shield.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M12 2l7 4v6c0 5-3.5 9.5-7 10-3.5-.5-7-5-7-10V6l7-4z"></path>
+</svg>

+ 6 - 0
apps/web/src/assets/icons/users.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M17 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
+  <circle cx="9" cy="7" r="4"></circle>
+  <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
+  <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+</svg>

+ 183 - 67
apps/web/src/components/SetterCommon/Code/CodeEditor.vue

@@ -1,76 +1,192 @@
-<!--
- * @Author: liuJie
- * @Date: 2026-01-28 02:04:56
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-28 16:55:30
- * @Describe: 代码编辑器
--->
-<template>
-    <div class="space-y-4">
-        <!-- <div class="flex items-center justify-between text-gray-800">
-            <span class="w-[60px] text-sm">语法</span>
-            <ElSelect v-model="selectedLanguageVal" @change="changeLanguage">
-                <el-option v-for="li in language" :key="li" :value="li">{{li}}</el-option>
-            </ElSelect>
-        </div> -->
-        <div ref="editorContainer" style="height: 240px;"></div>
-    </div>
-</template>
-
 <script setup lang="ts">
+import type { editor } from 'monaco-editor'
 
-import { ref, onMounted, onUnmounted } from 'vue'
+import { nextTick, onMounted, ref, watch } from 'vue'
 import * as monaco from 'monaco-editor'
-// const language = ref(['javascript','python']);
-// let selectedLanguageVal = ref('javascript')
+import { IconButton } from '@repo/ui'
+
+const props = withDefaults(
+	defineProps<{
+		allowFullscreen?: boolean
+		autoToggleTheme?: boolean
+		bordered?: boolean
+		config?: editor.IStandaloneEditorConstructionOptions
+		language?: string
+		lineNumbers?: 'off' | 'on'
+		modelValue?: any
+		readOnly?: boolean
+		theme?: 'hc-black' | 'vs-dark' | 'vs-light'
+		valueFormat?: string
+		height?: string
+		appendTo?: string | HTMLElement
+	}>(),
+	{
+		allowFullscreen: true,
+		config: () => ({
+			minimap: {
+				enabled: false
+			},
+			selectOnLineNumbers: true
+		}),
+		autoToggleTheme: true,
+		language: 'json',
+		lineNumbers: 'on',
+		readOnly: false,
+		theme: 'vs-light',
+		valueFormat: 'string',
+		height: '120px'
+	}
+)
+
+// todo 动态切换主题
+const theme = ref('dark')
+
+const emit = defineEmits(['update:modelValue', 'update:language'])
+
+const isFullScreen = ref(false)
+
+const fullScreenStyle = `position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 2999;`
+
+const editContainer = ref<HTMLElement | null>(null)
+
+let monacoEditor: monaco.editor.IStandaloneCodeEditor | null = null
 
-const editorContainer = ref<HTMLElement | null>(null)
-let editor = ref(null) as any;
+/**
+ * 设置文本
+ * @param text
+ */
+function setValue(text: string) {
+	monacoEditor?.setValue(text || '')
+}
+
+/**
+ * 光标处插入文本
+ * @param text
+ */
+function insertText(text: string) {
+	// 获取光标位置
+	const position = monacoEditor?.getPosition()
+	// 未获取到光标位置信息
+	if (!position) {
+		return
+	}
+	// 插入
+	monacoEditor?.executeEdits('', [
+		{
+			range: new monaco.Range(
+				position.lineNumber,
+				position.column,
+				position.lineNumber,
+				position.column
+			),
+			text
+		}
+	])
+	// 设置新的光标位置
+	monacoEditor?.setPosition({
+		...position,
+		column: position.column + text.length
+	})
+	// 重新聚焦
+	monacoEditor?.focus()
+}
 
 onMounted(() => {
-    try {
-        if (!editorContainer.value) {
-            console.log('编辑器挂载节点没获取到')
-            return
-        }
-        editor.value = monaco.editor.create(editorContainer.value, {
-            value: [
-                'function x() {',
-                '    console.log("Hello world!");',
-                '}'
-            ].join('\n'),
-            language: 'javascript',
-            theme: 'vs-dark',
-            automaticLayout: true,
-            fixedOverflowWidgets: true,
-        });
-        // 防止冒泡
-        editor.value.onKeyDown((e: Event) => {
-            e.stopPropagation();
-        });
-
-    } catch (err) {
-        console.error("Monaco 创建失败:", err);
-    }
-
-});
-
-onUnmounted(() => {
-    if (editor.value) {
-        editor.value.dispose();
-    }
-});
-
-// // 动态设置模型语言
-// const changeLanguage = ()=>{
-// 		console.log('changeLanguage', selectedLanguageVal.value,editor.value );
-// 		if(editor.value){
-// 			const model = editor.value.getModel()
-// 			if (model) {
-// 				monaco.editor.setModelLanguage(model, selectedLanguageVal.value)
-// 			}
-// 		}
-// }
+	monacoEditor = monaco.editor.create(editContainer.value as HTMLElement, {
+		value: getValue(),
+		...props.config,
+		automaticLayout: true,
+		language: props.language,
+		lineNumbers: props.lineNumbers,
+		readOnly: props.readOnly,
+		scrollBeyondLastLine: false,
+		theme: props.theme
+	})
+
+	function handleToggleTheme() {
+		if (theme.value === 'dark') {
+			monaco.editor.setTheme('vs-dark')
+		} else {
+			monaco.editor.setTheme('vs-light')
+		}
+	}
+
+	// 自动切换主题
+	if (props.autoToggleTheme) {
+		watch(
+			() => theme,
+			() => {
+				nextTick(() => handleToggleTheme())
+			},
+			{
+				immediate: true
+			}
+		)
+	}
+
+	// 获取值
+	function getValue() {
+		// valueFormat 为json 格式,需要转换处理
+		if (props.valueFormat === 'json' && props.modelValue) {
+			return JSON.stringify(props.modelValue, null, 2)
+		}
+		return props.modelValue ?? ''
+	}
+
+	// 监听值变化
+	monacoEditor.onDidChangeModelContent(() => {
+		const currenValue = monacoEditor?.getValue()
 
+		// valueFormat 为json 格式,需要转换处理
+		if (props.valueFormat === 'json' && currenValue) {
+			emit('update:modelValue', JSON.parse(currenValue))
+			return
+		}
 
+		emit('update:modelValue', currenValue ?? '')
+	})
+})
+
+defineExpose({
+	insertText,
+	setValue
+})
 </script>
+<template>
+	<Teleport :disabled="!appendTo" :to="appendTo">
+		<div
+			ref="editContainer"
+			:class="{ bordered: props.bordered }"
+			:style="isFullScreen ? fullScreenStyle : { height: props.height }"
+			class="code-editor w-full h-full relative"
+		>
+			<div
+				class="z-999 text-$epic-text-helper absolute right-4 top-2 cursor-pointer text-xl"
+				@click="isFullScreen = !isFullScreen"
+				v-if="props.allowFullscreen"
+			>
+				<IconButton v-if="!isFullScreen" icon="lucide:fullscreen" link />
+				<IconButton v-else icon="material-symbols-light:fullscreen-exit-rounded" link />
+			</div>
+		</div>
+	</Teleport>
+</template>
+<style lang="less" scoped>
+.code-editor {
+	width: 100%;
+	min-height: 150px;
+
+	:deep(.monaco-editor) {
+		height: 100%;
+	}
+
+	&.bordered {
+		border: 1px solid var(--epic-border-color);
+	}
+}
+</style>

+ 46 - 43
apps/web/src/components/setter/CodeSetter.vue

@@ -1,24 +1,28 @@
 <template>
-    <div class="bg-white w-full">
-        <div class="w-full">
-            <!-- Content -->
-            <div class="p-4">
-                <div class="space-y-6">
-                    <!-- 输入变量 -->
-                    <InputVariables v-model="formData.inputVariables" />
+	<div class="bg-white w-full">
+		<div class="w-full">
+			<!-- Content -->
+			<div class="p-4">
+				<div class="space-y-6">
+					<!-- 输入变量 -->
+					<InputVariables v-model="formData.inputVariables" />
 
-                    <!-- 代码编辑器 -->
-                    <CodeEditor v-model="formData.code" v-model:language="formData.language" />
+					<!-- 代码编辑器 -->
+					<CodeEditor
+						v-model="formData.code"
+						v-model:language="formData.language"
+						value-format="javascript"
+					/>
 
-                    <!-- 输出变量 -->
-                    <OutputVariables v-model="formData.outputVariables" />
+					<!-- 输出变量 -->
+					<OutputVariables v-model="formData.outputVariables" />
 
-                    <!-- 测试配置 -->
-                    <TestConfig v-model="formData.testConfig" />
-                </div>
-            </div>
-        </div>
-    </div>
+					<!-- 测试配置 -->
+					<TestConfig v-model="formData.testConfig" />
+				</div>
+			</div>
+		</div>
+	</div>
 </template>
 
 <script setup lang="ts">
@@ -29,19 +33,19 @@ import OutputVariables from '@/components/SetterCommon/Code/OutputVariables.vue'
 import TestConfig from '@/components/SetterCommon/Code/TestConfig.vue'
 
 interface Variable {
-    id: string
-    name: string
+	id: string
+	name: string
 }
 
 interface OutputVariable {
-    id: string
-    name: string
-    type: string
+	id: string
+	name: string
+	type: string
 }
 
 interface TestConfigData {
-    retryEnabled: boolean
-    errorHandling: string
+	retryEnabled: boolean
+	errorHandling: string
 }
 
 // const props = withDefaults(defineProps<{
@@ -52,26 +56,25 @@ interface TestConfigData {
 // }>()
 
 const formData = reactive({
-    // 输入变量, 变量默认值
-    inputVariables: [
-        // { id: 'var_1', name: 'arg1' },
-    ] as Variable[],
-    // 代码内容
-    code: `function main(arg1, arg2) {
+	// 输入变量, 变量默认值
+	inputVariables: [
+		// { id: 'var_1', name: 'arg1' },
+	] 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
+}}`,
+	// 编程语言
+	language: 'javascript',
+	// 输出变量
+	outputVariables: [
+		// { id: 'output_1', name: 'result', type: 'String' }
+	] as OutputVariable[],
+	// 测试配置
+	testConfig: {
+		retryEnabled: false,
+		errorHandling: 'none'
+	} as TestConfigData
 })
-
 </script>

+ 15 - 9
apps/web/src/store/index.ts

@@ -5,17 +5,23 @@
  * @LastEditTime: 2026-01-27 10:10:22
  * @Describe: file describe
  */
-import type { App } from "vue";
-import { createPinia } from "pinia";
-import useComponentMapInspector from "@/store/modules/materialSetterMaps.store"
-import useConditionOptionsStore from "@/store/modules/conditionNode.store"
-import useHttpOptionsStore from "@/store/modules/httpNode.store"
+import type { App } from 'vue'
+import { createPinia } from 'pinia'
+import useComponentMapInspector from '@/store/modules/materialSetterMaps.store'
+import useConditionOptionsStore from '@/store/modules/conditionNode.store'
+import useHttpOptionsStore from '@/store/modules/httpNode.store'
+import { useDashboardStore } from './modules/dashboard'
 
-const pinia = createPinia();
+const pinia = createPinia()
 
 const store = (app: App<Element>) => {
-    app.use(pinia);
+	app.use(pinia)
+}
+export {
+	useComponentMapInspector,
+	useConditionOptionsStore,
+	useHttpOptionsStore,
+	useDashboardStore
 }
-export { useComponentMapInspector, useConditionOptionsStore, useHttpOptionsStore };
 
-export default store;
+export default store

apps/web/src/stores/dashboard.ts → apps/web/src/store/modules/dashboard.ts


+ 4 - 4
apps/web/src/views/About.vue

@@ -21,22 +21,22 @@
 
 			<div class="features-grid">
 				<div class="feature-card">
-					<SvgIcon name="zap" class="feature-icon" />
+					<SvgIcon name="zap" class="feature-icon" size="30"/>
 					<h3>高性能</h3>
 					<p>优化的执行引擎,确保工作流快速可靠运行</p>
 				</div>
 				<div class="feature-card">
-					<SvgIcon name="shield" class="feature-icon" />
+					<SvgIcon name="shield" class="feature-icon" size="30"/>
 					<h3>安全可靠</h3>
 					<p>企业级安全标准,保护你的数据和凭证</p>
 				</div>
 				<div class="feature-card">
-					<SvgIcon name="users" class="feature-icon" />
+					<SvgIcon name="users" class="feature-icon" size="30"/>
 					<h3>团队协作</h3>
 					<p>支持多人协作,共同构建和维护工作流</p>
 				</div>
 				<div class="feature-card">
-					<SvgIcon name="code" class="feature-icon" />
+					<SvgIcon name="code" class="feature-icon" size="30"/>
 					<h3>开放扩展</h3>
 					<p>支持自定义节点和集成,满足个性化需求</p>
 				</div>

+ 5 - 5
apps/web/src/views/Dashboard.vue

@@ -150,7 +150,7 @@
 							<el-card>
 								<div style="display: flex; justify-content: space-between; align-items: center">
 									<div>
-										<div style="font-weight: 700" class="cursor-pointer" @click="toEditor">
+										<div style="font-weight: 700" class="cursor-pointer" @click="toEditor(item.id)">
 											{{ item.title }}
 										</div>
 										<div style="color: #999; font-size: 12px">上次更新时间: {{ item.created }}</div>
@@ -162,7 +162,7 @@
 											<span class="el-dropdown-link cursor-pointer" @click.stop>•••</span>
 											<template #dropdown>
 												<el-dropdown-menu>
-													<el-dropdown-item @click="toEditor">打开</el-dropdown-item>
+													<el-dropdown-item @click="toEditor(item.id)">打开</el-dropdown-item>
 													<el-dropdown-item>分享</el-dropdown-item>
 													<el-dropdown-item>复制</el-dropdown-item>
 													<el-dropdown-item>移动</el-dropdown-item>
@@ -492,7 +492,7 @@ import { ref, computed } from 'vue'
 import { useRouter } from 'vue-router'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Search } from '@element-plus/icons-vue'
-import { useDashboardStore } from '@/stores/dashboard'
+import { useDashboardStore } from '@/store'
 import { v4 } from 'uuid'
 
 const $router = useRouter()
@@ -813,8 +813,8 @@ const filteredVariables = computed(() => {
 	return result
 })
 
-const toEditor = () => {
-	$router.push('/workflow/1')
+const toEditor = (id: string) => {
+	$router.push(`/workflow/${id}`)
 }
 </script>
 

+ 12 - 25
apps/web/src/views/Docs.vue

@@ -8,9 +8,7 @@
 				<div class="nav-section">
 					<h3>快速开始</h3>
 					<ul>
-						<li :class="{ active: activeDoc === 'intro' }" @click="activeDoc = 'intro'">
-							介绍
-						</li>
+						<li :class="{ active: activeDoc === 'intro' }" @click="activeDoc = 'intro'">介绍</li>
 						<li
 							:class="{ active: activeDoc === 'installation' }"
 							@click="activeDoc = 'installation'"
@@ -26,10 +24,7 @@
 							工作流
 						</li>
 						<li :class="{ active: activeDoc === 'nodes' }" @click="activeDoc = 'nodes'">节点</li>
-						<li
-							:class="{ active: activeDoc === 'credentials' }"
-							@click="activeDoc = 'credentials'"
-						>
+						<li :class="{ active: activeDoc === 'credentials' }" @click="activeDoc = 'credentials'">
 							凭证管理
 						</li>
 					</ul>
@@ -37,12 +32,8 @@
 				<div class="nav-section">
 					<h3>节点文档</h3>
 					<ul>
-						<li :class="{ active: activeDoc === 'http' }" @click="activeDoc = 'http'">
-							HTTP 节点
-						</li>
-						<li :class="{ active: activeDoc === 'code' }" @click="activeDoc = 'code'">
-							代码节点
-						</li>
+						<li :class="{ active: activeDoc === 'http' }" @click="activeDoc = 'http'">HTTP 节点</li>
+						<li :class="{ active: activeDoc === 'code' }" @click="activeDoc = 'code'">代码节点</li>
 						<li :class="{ active: activeDoc === 'database' }" @click="activeDoc = 'database'">
 							数据库节点
 						</li>
@@ -60,7 +51,8 @@
 					<section class="doc-section">
 						<h2>概述</h2>
 						<p>
-							AI Agent 是一个强大的自动化工作流平台,允许你创建复杂的自动化流程。通过可视化的工作流编辑器,你可以轻松添加各种节点类型,连接各种服务和应用,实现数据的自动化处理和传输。
+							AI Agent
+							是一个强大的自动化工作流平台,允许你创建复杂的自动化流程。通过可视化的工作流编辑器,你可以轻松添加各种节点类型,连接各种服务和应用,实现数据的自动化处理和传输。
 						</p>
 					</section>
 
@@ -68,17 +60,12 @@
 						<h2>主要特性</h2>
 						<ul class="feature-list">
 							<li>
-								<strong>可视化编辑器</strong> - 直观的工作流设计器,支持开始、结束、HTTP请求、条件分支、代码执行、数据查询等多种节点
-							</li>
-							<li>
-								<strong>丰富的节点库</strong> - 支持 HTTP、数据库、代码执行等多种节点类型
-							</li>
-							<li>
-								<strong>实时调试</strong> - 支持单步执行和断点调试
-							</li>
-							<li>
-								<strong>数据转换</strong> - 强大的数据映射和转换能力
+								<strong>可视化编辑器</strong> -
+								直观的工作流设计器,支持开始、结束、HTTP请求、条件分支、代码执行、数据查询等多种节点
 							</li>
+							<li><strong>丰富的节点库</strong> - 支持 HTTP、数据库、代码执行等多种节点类型</li>
+							<li><strong>实时调试</strong> - 支持单步执行和断点调试</li>
+							<li><strong>数据转换</strong> - 强大的数据映射和转换能力</li>
 						</ul>
 					</section>
 
@@ -154,7 +141,7 @@ const docDescription = computed(() => {
 <style lang="less" scoped>
 .docs-container {
 	display: flex;
-	height: calc(100vh - 32px);
+	height: 100vh;
 	background: #f8f9fa;
 
 	.sidebar {

+ 23 - 0
apps/web/src/views/Editor.vue

@@ -101,6 +101,7 @@ const footerHeight = ref(32)
 const route = useRoute()
 const router = useRouter()
 const id = (route.params?.id as string) || uuid()
+
 const projectMap = JSON.parse(localStorage.getItem(`workflow-map`) || '{}') as Record<
 	string,
 	IWorkflow
@@ -128,6 +129,28 @@ watch(
 	},
 	{ deep: true }
 )
+
+/**
+ * 监听路由参数变化
+ */
+watch(
+	() => route.params?.id,
+	(newId) => {
+		if (newId) {
+			const projectMap = JSON.parse(localStorage.getItem(`workflow-map`) || '{}') as Record<
+				string,
+				IWorkflow
+			>
+			workflow.value = projectMap[newId as string] ?? {
+				id: newId as string,
+				name: 'workflow_1',
+				created: dayjs().format('MM 月 DD 日'),
+				nodes: [startNode, endNode],
+				edges: []
+			}
+		}
+	}
+)
 /**
  * Editor
  */

+ 13 - 2
packages/workflow/src/components/Canvas.vue

@@ -9,7 +9,7 @@ import type {
 import type { SourceType } from '@repo/nodes'
 import type { NodeMouseEvent, Connection, NodeDragEvent } from '@vue-flow/core'
 
-import { ref, onMounted, computed } from 'vue'
+import { ref, onMounted, computed, provide } from 'vue'
 import { VueFlow, useVueFlow, MarkerType } from '@vue-flow/core'
 import { MiniMap } from '@vue-flow/minimap'
 
@@ -17,7 +17,7 @@ import CanvasNode from './elements/nodes/CanvasNode.vue'
 import CanvasEdge from './elements/edges/CanvasEdge.vue'
 import CanvasArrowHeadMarker from './elements/edges/CanvasArrowHeadMarker.vue'
 import CanvasBackground from './elements/background/CanvasBackground.vue'
-import CanvasControlBar from './elements/CanvasControlBar.vue'
+import CanvasControlBar from './elements/control-bar/CanvasControlBar.vue'
 import ConditionNode from './elements/node-temp/ConditionNode.vue'
 import StartNode from './elements/node-temp/StartNode.vue'
 import HttpNode from './elements/node-temp/HttpNode1.vue'
@@ -235,6 +235,17 @@ const handleRun = () => {
 onMounted(() => {
 	fitView()
 })
+
+provide('vueflow', {
+	id: props.id,
+	nodes: props.nodes,
+	edges: props.edges,
+	vueFlow
+})
+
+defineExpose({
+	vueFlow
+})
 </script>
 
 <template>

+ 103 - 0
packages/workflow/src/components/elements/control-bar/AddNode.vue

@@ -0,0 +1,103 @@
+<!--
+ * @Author: liuJie
+ * @Date: 2026-01-24 16:40:11
+ * @LastEditors: liuJie
+ * @LastEditTime: 2026-01-25 11:56:32
+ * @Describe: 添加物料
+-->
+<script lang="ts" setup>
+import { ref, computed, inject } from 'vue'
+import { Icon } from '@iconify/vue'
+import { ElPopover, ElButton } from 'element-plus'
+import { materialTools, type SourceType } from '@repo/nodes'
+
+defineOptions({
+	name: 'AddMaterialsPop'
+})
+
+const emit = defineEmits<{
+	'add-node': [value: SourceType]
+}>()
+
+const vueflow = inject('vueflow')
+const hasStart = computed(() => vueflow?.nodes?.some((node) => node.data.type === 'start'))
+const hasEnd = computed(() => vueflow?.nodes?.some((node) => node.data.type === 'end'))
+
+const materials = computed(() => {
+	return materialTools.map((group) => {
+		return {
+			...group,
+			source: group.source.filter((item) => {
+				return (
+					!(item.type === 'start' && !hasStart.value) && !(item.type === 'end' && !hasEnd.value)
+				)
+			})
+		}
+	})
+})
+
+const onAddNode = (value: SourceType) => {
+	emit('add-node', value)
+	show.value = false
+}
+const show = ref(false)
+const togglePop = () => {
+	show.value = !show.value
+}
+</script>
+<template>
+	<ElPopover
+		v-bind:visible="show"
+		trigger="click"
+		transition="el-zoom-in-top"
+		:show-after="400"
+		:hide-after="1000"
+		placement="top"
+	>
+		<div v-for="item in materials" :key="item.id">
+			<p class="mb-2 mt-1 text-[#676f83]">{{ item.label }}</p>
+			<ul>
+				<li
+					class="tool mb-3 flex items-center"
+					v-for="value in item.source"
+					:key="value.id"
+					@click="onAddNode(value)"
+				>
+					<Icon
+						:icon="value.icon"
+						height="16"
+						width="16"
+						class="mr-2 bg-[#6172f3] p-1 rounded"
+						color="#fff"
+					/>
+					<span>{{ value.name }}</span>
+				</li>
+			</ul>
+		</div>
+		<template #reference>
+			<ElButton @click="togglePop" type="primary">
+				<Icon icon="lucide:package-plus" height="16" width="16" class="mr-1" /> 新增节点
+			</ElButton>
+		</template>
+	</ElPopover>
+</template>
+<style lang="less" scoped>
+ul {
+	padding: 0 0 0 10px;
+
+	.tool {
+		list-style: none;
+		cursor: pointer;
+
+		span {
+			color: #354052;
+		}
+
+		&:hover {
+			span {
+				color: #6172f3;
+			}
+		}
+	}
+}
+</style>

+ 2 - 1
packages/workflow/src/components/elements/CanvasControlBar.vue

@@ -2,8 +2,9 @@
 import { Controls } from '@vue-flow/controls'
 import { Icon } from '@iconify/vue'
 import { ElButton } from 'element-plus'
-import AddNode from './handles/AddNode.vue'
+import AddNode from './AddNode.vue'
 import type { SourceType } from '@repo/nodes'
+
 const emit = defineEmits<{
 	'reset-zoom': []
 	'zoom-in': []

+ 0 - 71
packages/workflow/src/components/elements/handles/AddNode.vue

@@ -1,71 +0,0 @@
-<!--
- * @Author: liuJie
- * @Date: 2026-01-24 16:40:11
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 11:56:32
- * @Describe: 添加物料
--->
-<script lang="ts" setup>
-import { ref, reactive } from 'vue';
-import { Icon } from '@iconify/vue';
-import { ElPopover, ElButton } from 'element-plus';
-import { materialTools, type SourceType } from '@repo/nodes'
-
-const materials = reactive(materialTools)
-defineOptions({
-    name: 'AddMaterialsPop'
-})
-const emit = defineEmits<{
-    'add-node': [value: SourceType]
-}>()
-const onAddNode = (value: SourceType) => {
-    emit('add-node', value)
-    show.value = false
-}
-const show = ref(false);
-const togglePop = () => {
-    show.value = !show.value;
-}
-</script>
-<template>
-    <ElPopover v-bind:visible="show" trigger="click" transition="el-zoom-in-top" :show-after="400" :hide-after="1000"
-        placement="top">
-        <div v-for="item in materials" :key="item.id">
-            <p class="mb-2 mt-1 text-[#676f83]">{{ item.label }}</p>
-            <ul>
-                <li class="tool mb-3 flex items-center" v-for="value in item.source" :key="value.id"
-                    @click="onAddNode(value)">
-                    <Icon :icon="value.icon" height="16" width="16" class="mr-2 bg-[#6172f3] p-1 rounded"
-                        color="#fff" />
-                    <span>{{ value.name }}</span>
-                </li>
-            </ul>
-        </div>
-        <template #reference>
-            <ElButton @click="togglePop" type="primary">
-                <Icon icon="lucide:package-plus" height="16" width="16" class="mr-1" /> 新增节点
-            </ElButton>
-        </template>
-    </ElPopover>
-</template>
-<style lang="less" scoped>
-ul {
-    padding: 0 0 0 10px;
-
-    .tool {
-        list-style: none;
-        cursor: pointer;
-
-        span {
-            color: #354052;
-        }
-
-        &:hover {
-
-            span {
-                color: #6172f3;
-            }
-        }
-    }
-}
-</style>