ソースを参照

feat: 添加节点编辑处理

jiaxing.liao 1 週間 前
コミット
32250e99ca

+ 14 - 5
project.json5

@@ -270,7 +270,7 @@
       // 组ID
       "id": "group_1",
       // 组名称
-      "name": "全局变量",
+      "name": "default_group",
       // 变量
       "variables": [
         {
@@ -438,10 +438,19 @@
           // 页面变量
           "variables": [
             {
-              "id": "page_1_var_1",
-              "name": "page_1_a",
-              "value": "1",
-              "type": "int"
+              // 组ID
+              "id": "page_group_1",
+              // 组名称
+              "name": "default_group",
+              // 变量
+              "variables": [
+                {
+                  "id": "var_1",
+                  "name": "a",
+                  "value": "1",
+                  "type": "int"
+                }
+              ]
             }
           ],
           // 子组件

+ 2 - 0
src/renderer/components.d.ts

@@ -59,6 +59,7 @@ declare module 'vue' {
     ElSpace: typeof import('element-plus/es')['ElSpace']
     ElSplitter: typeof import('element-plus/es')['ElSplitter']
     ElSplitterPanel: typeof import('element-plus/es')['ElSplitterPanel']
+    ElSwitch: typeof import('element-plus/es')['ElSwitch']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabPanel: typeof import('element-plus/es')['ElTabPanel']
     ElTabs: typeof import('element-plus/es')['ElTabs']
@@ -127,6 +128,7 @@ declare global {
   const ElSpace: typeof import('element-plus/es')['ElSpace']
   const ElSplitter: typeof import('element-plus/es')['ElSplitter']
   const ElSplitterPanel: typeof import('element-plus/es')['ElSplitterPanel']
+  const ElSwitch: typeof import('element-plus/es')['ElSwitch']
   const ElTabPane: typeof import('element-plus/es')['ElTabPane']
   const ElTabPanel: typeof import('element-plus/es')['ElTabPanel']
   const ElTabs: typeof import('element-plus/es')['ElTabs']

+ 60 - 0
src/renderer/src/constants/index.ts

@@ -0,0 +1,60 @@
+// 常用常量 选项配置在这里
+
+/**
+ * Flag 标识选项
+ */
+export const flagOptions = [
+  { label: 'Hidden', value: 'LV_OBJ_FLAG_HIDDEN' },
+  { label: 'Clickable', value: 'LV_OBJ_FLAG_CLICKABLE' },
+  { label: 'Focusable', value: 'LV_OBJ_FLAG_CLICK_FOCUSABLE' },
+  { label: 'Checkable', value: 'LV_OBJ_FLAG_CHECKABLE' },
+  { label: 'Scrollable', value: 'LV_OBJ_FLAG_SCROLLABLE' },
+  { label: 'Scroll Elastic', value: 'LV_OBJ_FLAG_SCROLL_ELASTIC' },
+  { label: 'Scroll momentun', value: 'LV_OBJ_FLAG_SCROLL_MOMENTUM' },
+  { label: 'Scroll One', value: 'LV_OBJ_FLAG_SCROLL_ONE' },
+  { label: 'Scroll Chain Hor', value: 'LV_OBJ_FLAG_SCROLL_CHAIN_HOR' },
+  { label: 'Scroll Chain Ver', value: 'LV_OBJ_FLAG_SCROLL_CHAIN_VER' },
+  { label: 'Scroll Chain', value: 'LV_OBJ_FLAG_SCROLL_CHAIN' },
+  { label: 'Scroll On Focus', value: 'LV_OBJ_FLAG_SCROLL_ON_FOCUS' },
+  { label: 'Scroll With Arrow', value: 'LV_OBJ_FLAG_SCROLL_WITH_ARROW' },
+  { label: 'Snappable', value: 'LV_OBJ_FLAG_SNAPPABLE' },
+  { label: 'Press Lock', value: 'LV_OBJ_FLAG_PRESS_LOCK' },
+  { label: 'Event Bubble', value: 'LV_OBJ_FLAG_EVENT_BUBBLE' },
+  { label: 'Gesture Bubble', value: 'LV_OBJ_FLAG_GESTURE_BUBBLE' },
+  { label: 'Adv Hittest', value: 'LV_OBJ_FLAG_ADV_HITTEST' },
+  { label: 'Ignore Layout', value: 'LV_OBJ_FLAG_IGNORE_LAYOUT' },
+  { label: 'floating', value: 'LV_OBJ_FLAG_FLOATING' },
+  { label: 'Send Draw Task Events', value: 'LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS' },
+  { label: 'Overflow Visible', value: 'LV_OBJ_FLAG_OVERFLOW_VISIBLE' },
+  { label: 'Event Trickle', value: 'LV_OBJ_FLAG_EVENT_TRICKLE' },
+  { label: 'State Trickle', value: 'LV_OBJ_FLAG_STATE_TRICKLE' },
+  { label: 'Layout1', value: 'LV_OBJ_FLAG_LAYOUT_1' },
+  { label: 'Layout2', value: 'LV_OBJ_FLAG_LAYOUT_2' },
+  { label: 'Flex In New Track', value: 'LV_OBJ_FLAG_FLEX_IN_NEW_TRACK' },
+  { label: 'Widget1', value: 'LV_OBJ_FLAG_WIDGET_1' },
+  { label: 'Widget2', value: 'LV_OBJ_FLAG_WIDGET_2' },
+  { label: 'User1', value: 'LV_OBJ_FLAG_USER_1' },
+  { label: 'User2', value: 'LV_OBJ_FLAG_USER_2' },
+  { label: 'User3', value: 'LV_OBJ_FLAG_USER_3' },
+  { label: 'User4', value: 'LV_OBJ_FLAG_USER_4' }
+]
+
+/**
+ * State 状态选项
+ */
+export const stateOptions = [
+  { label: 'Default', value: 'LV_STATE_DEFAULT' },
+  { label: 'Checked', value: 'LV_STATE_CHECKED' },
+  { label: 'Focused', value: 'LV_STATE_FOCUSED' },
+  { label: 'Focus Key', value: 'LV_STATE_FOCUS_KEY' },
+  { label: 'Edited', value: 'LV_STATE_EDITED' },
+  { label: 'Hoverd', value: 'LV_STATE_HOVERED' },
+  { label: 'Pressed', value: 'LV_STATE_PRESSED' },
+  { label: 'Scrolled', value: 'LV_STATE_SCROLLED' },
+  { label: 'Disabled', value: 'LV_STATE_DISABLED' },
+  { label: 'User1', value: 'LV_STATE_USER_1' },
+  { label: 'User2', value: 'LV_STATE_USER_2' },
+  { label: 'User3', value: 'LV_STATE_USER_3' },
+  { label: 'User4', value: 'LV_STATE_USER_4' },
+  { label: 'Any', value: 'LV_STATE_ANY' }
+]

+ 1 - 0
src/renderer/src/store/modules/project.ts

@@ -99,6 +99,7 @@ export const useProjectStore = defineStore('project', () => {
     activePageId.value = ''
     meta.screens.forEach((screen) => {
       const newScreen = createScreen(screen)
+      newScreen.name += '_1'
       project.value?.screens.push(newScreen)
     })
     activePageId.value = project.value.screens[0].pages[0].id

+ 1 - 1
src/renderer/src/views/designer/sidebar/components/EditBinModal.vue

@@ -23,7 +23,7 @@
 import type { FormInstance } from 'element-plus'
 import type { Bin } from '@/types/bins'
 
-import { ref, defineExpose } from 'vue'
+import { ref } from 'vue'
 import { useProjectStore } from '@/store/modules/project'
 
 const show = ref(false)

+ 1 - 1
src/renderer/src/views/designer/sidebar/components/EditFontModal.vue

@@ -69,7 +69,7 @@
 <script setup lang="ts">
 import type { FontResource } from '@/types/resource'
 import type { FormInstance } from 'element-plus'
-import { ref, defineExpose, defineEmits } from 'vue'
+import { ref } from 'vue'
 import { useProjectStore } from '@/store/modules/project'
 import { klona } from 'klona'
 import { LuPencilLine } from 'vue-icons-plus/lu'

+ 1 - 1
src/renderer/src/views/designer/sidebar/components/EditImageModal.vue

@@ -76,7 +76,7 @@
 <script setup lang="ts">
 import type { ImageResource } from '@/types/resource'
 import type { FormInstance } from 'element-plus'
-import { ref, defineExpose, defineEmits } from 'vue'
+import { ref } from 'vue'
 import { useProjectStore } from '@/store/modules/project'
 import { klona } from 'klona'
 import { getImageByPath } from '@/utils'

+ 1 - 1
src/renderer/src/views/designer/sidebar/components/MethodItem.vue

@@ -45,7 +45,7 @@
 <script setup lang="ts">
 import type { Method } from '@/types/method'
 
-import { ref, defineProps, defineEmits, nextTick } from 'vue'
+import { ref, nextTick } from 'vue'
 import MonacoEditor from '@/components/MonacoEditor/index.vue'
 import { LuBox, LuPencilLine, LuTrash2 } from 'vue-icons-plus/lu'
 import { AiOutlineRight, AiOutlineDown } from 'vue-icons-plus/ai'

+ 0 - 1
src/renderer/src/views/designer/sidebar/components/PageTreeItem.vue

@@ -33,7 +33,6 @@
 
 <script setup lang="ts">
 import type { RenderContentContext } from 'element-plus'
-import { defineProps } from 'vue'
 import {
   LuTrash2,
   LuLock,

+ 1 - 1
src/renderer/src/views/designer/sidebar/components/ResourceItem.vue

@@ -70,7 +70,7 @@
 import type { FontResource, ImageResource, Resource } from '@/types/resource'
 import type { DropdownInstance } from 'element-plus'
 
-import { defineProps, defineEmits, ref } from 'vue'
+import { ref } from 'vue'
 import LocalImage from '@/components/LocalImage/index.vue'
 import { useProjectStore } from '@/store/modules/project'
 import { LuTrash2, LuPencilLine } from 'vue-icons-plus/lu'

+ 5 - 2
src/renderer/src/views/designer/sidebar/components/ScreenTreeItem.vue

@@ -53,7 +53,7 @@
 <script setup lang="ts">
 import type { Screen } from '@/types/screen'
 import type { RenderContentContext } from 'element-plus'
-import { defineProps, ref } from 'vue'
+import { ref } from 'vue'
 import {
   LuTrash2,
   LuLock,
@@ -80,7 +80,10 @@ const projectStore = useProjectStore()
 // 创建页面
 const addPage = (screen: Screen) => {
   const newScreen = createPage()
-  screen.pages.push(newScreen)
+  screen.pages.push({
+    ...newScreen,
+    name: `${newScreen.name}_${screen.pages.length + 1}`
+  })
   // 选择当前页面
   projectStore.activePageId = newScreen.id
 }

+ 60 - 0
src/renderer/src/views/designer/workspace/composite/eventEdit/ActionNode.vue

@@ -0,0 +1,60 @@
+<template>
+  <div class="h-32px bg-#4EB2BF rounded-4px flex items-center pr-4px">
+    <div class="w-160px text-#fff grid place-items-center shrink-0">
+      {{ node.name }}
+    </div>
+    <div>
+      <!-- 自定义代码 -->
+      <div v-if="node.name === 'custom_code'" class="w-120px">Edit Code</div>
+      <!-- 字符串 -->
+      <div v-if="node?.config?.data?.valueType === 'string'">
+        <el-input v-model="node.config.value" size="small" />
+      </div>
+      <!-- 数字 -->
+      <div v-if="node?.config?.data?.valueType === 'number'">
+        <el-input-number
+          v-model="node.config.value"
+          :min="node.config.data?.min"
+          :max="node.config.data?.max"
+          size="small"
+        />
+      </div>
+      <!-- 布尔 -->
+      <div v-if="node?.config?.data?.valueType === 'boolean'">
+        <el-switch v-model="node.config.value" size="small" />
+      </div>
+      <!-- 枚举 -->
+      <div v-if="node?.config?.data?.valueType === 'select'">
+        <el-select
+          style="width: 160px"
+          v-model="node.config.value"
+          size="small"
+          placeholder="please select"
+        >
+          <el-option
+            v-for="item in node.config.data.options"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </div>
+      <!-- 颜色 -->
+      <div v-if="node?.config?.data?.valueType === 'color'">
+        <el-color-picker v-model="node.config.value" size="small" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import type { NodeItemType } from './type'
+
+const props = defineProps<{
+  node: NodeItemType
+}>()
+
+console.log(props.node)
+</script>
+
+<style scoped></style>

+ 68 - 43
src/renderer/src/views/designer/workspace/composite/eventEdit/NodeItem.vue

@@ -2,7 +2,7 @@
   <!-- 添加节点 -->
   <SelectPopover
     v-if="node.nodeType === 'add'"
-    v-bind="addOptions"
+    v-bind="getAddOptions(node.parentType!)"
     @confirm="(val) => val && emit('add', node.parentId!, node.parentType!, val)"
   >
     <div
@@ -25,78 +25,103 @@
   <!-- 事件节点 -->
   <div
     v-if="node.nodeType === 'event'"
-    class="w-200px h-40px bg-#4b85e2 text-#fff grid place-items-center rounded-4px"
+    class="w-200px h-40px bg-#4b85e2 text-#fff grid place-items-center rounded-4px relative group"
   >
+    <div class="absolute top-4px right-4px gap-4px hidden group-hover:flex">
+      <SelectPopover
+        v-bind="getAddOptions('root')"
+        :multiple="false"
+        @confirm="(val) => val && emit('change', node.id, val)"
+      >
+        <div class="bg-[rgba(0,0,0,.5)] p-2px cursor-pointer"><LuPencilLine size="12px" /></div>
+      </SelectPopover>
+
+      <div class="bg-[rgba(0,0,0,.5)] p-2px cursor-pointer" @click="emit('delete', node.id)">
+        <LuTrash2 size="12px" class="text-accent-red" />
+      </div>
+    </div>
     {{ node.name }}
   </div>
 
   <!-- 目标节点 -->
   <div
     v-if="node.nodeType === 'target'"
-    class="w-200px h-40px bg-#4B9889 text-#fff grid place-items-center rounded-4px"
+    class="w-200px h-40px bg-#4B9889 text-#fff grid place-items-center rounded-4px group"
   >
+    <div class="absolute top-4px right-4px gap-4px hidden group-hover:flex">
+      <div class="bg-[rgba(0,0,0,.5)] p-2px cursor-pointer" @click="emit('delete', node.id)">
+        <LuTrash2 size="12px" class="text-accent-red" />
+      </div>
+    </div>
     {{ node.name }}
   </div>
 
   <!-- 动作节点 -->
-  <div
+  <ActionNode
     v-if="node.nodeType === 'action'"
-    class="w-200px h-40px bg-#4EB2BF text-#fff grid place-items-center rounded-4px"
-  >
-    {{ node.name }}
-  </div>
+    :node="node"
+    @delete="() => emit('delete', node.id)"
+  />
 </template>
 
 <script setup lang="ts">
 import type { NodeItemType } from './type'
 
-import { LuPlus } from 'vue-icons-plus/lu'
-import SelectPopover from './SelectPopover.vue'
 import { computed } from 'vue'
-import { widgetEventOptions, pageEventOptions } from './config'
+import { LuPlus, LuTrash2, LuPencilLine } from 'vue-icons-plus/lu'
+import SelectPopover from './SelectPopover.vue'
+import ActionNode from './ActionNode.vue'
+import { widgetEventOptions, pageEventOptions, actionOptions } from './config'
+import { useAllWidgets } from './useAllWidgets'
 
 const emit = defineEmits<{
-  (e: 'add', parentId: string, parentType: string, events: string[]): void
-  (e: 'delete', node: NodeItemType): void
+  (e: 'add', parentId: string, parentType: string, value: any[]): void
+  (e: 'change', id: string, value: any[]): void
+  (e: 'delete', nodeId: string): void
 }>()
 const props = defineProps<{
   node: NodeItemType
 }>()
 
+const { allWidgets } = useAllWidgets()
+
 // 添加节点弹窗配置
-const addOptions = computed(() => {
-  const node = props.node
-  if (node.nodeType === 'add') {
-    switch (node.parentType) {
-      case 'root':
-        return {
-          title: '触发方式',
-          multiple: true,
-          options: widgetEventOptions
-        }
-      case 'event':
-        return {
-          title: '目标',
-          multiple: true,
-          options: [
-            { label: '事件', value: 'event' },
-            { label: '条件', value: 'condition' }
-          ]
-        }
-      case 'target':
-        return {
-          title: '动作',
-          multiple: true,
-          options: [
-            { label: '事件', value: 'event' },
-            { label: '条件', value: 'condition' }
-          ]
-        }
-    }
+const getAddOptions = (type: string) => {
+  switch (type) {
+    case 'root':
+      return {
+        title: '触发方式',
+        multiple: true,
+        // 判断选择元素是否是页面
+        options: props.node.isPage ? pageEventOptions : widgetEventOptions
+      }
+    case 'event':
+      const options = allWidgets.value.map((item) => ({
+        label: item.name,
+        value: item.id,
+        data: item
+      }))
+      return {
+        title: '目标',
+        multiple: true,
+        options: [
+          {
+            label: '自定义代码',
+            value: 'customCode'
+          },
+          ...options
+        ]
+      }
+    case 'target':
+      return {
+        title: '动作',
+        multiple: true,
+        options: actionOptions
+      }
   }
 
   return
-})
+}
 
 const addBtnStyle = computed(() => {
   const node = props.node

+ 5 - 3
src/renderer/src/views/designer/workspace/composite/eventEdit/SelectPopover.vue

@@ -45,15 +45,17 @@
 
 <script setup lang="ts">
 import type { PopoverInstance } from 'element-plus'
+import type { OptionType } from './type'
+
 import { computed, ref } from 'vue'
 
 const emit = defineEmits<{
-  (e: 'confirm', value: string[]): void
+  (e: 'confirm', value?: OptionType[]): void
 }>()
 const props = defineProps<{
   title?: string
   multiple?: boolean
-  options?: { label: string; value: string }[]
+  options?: OptionType[]
   selected?: string | string[]
 }>()
 
@@ -89,7 +91,7 @@ const handleCancel = () => {
 
 // 确定
 const handleConfirm = () => {
-  const result = selectedList.value
+  const result = props.options?.filter((item) => selectedList.value.includes(item.value))
   handleCancel()
   setTimeout(() => {
     emit('confirm', result)

+ 61 - 0
src/renderer/src/views/designer/workspace/composite/eventEdit/config.ts

@@ -1,3 +1,5 @@
+import { flagOptions, stateOptions } from '@/constants'
+
 /**
  * @description: 组件事件选项
  */
@@ -48,3 +50,62 @@ export const pageEventOptions = [
   { label: 'Unloaded', value: 'LV_EVENT_SCREEN_UNLOADED' },
   { label: 'Created', value: 'LV_EVENT_CHILD_CREATED' }
 ]
+
+/**
+ * @description: 动作选项
+ */
+export const actionOptions = [
+  { label: 'X', value: 'x', valueType: 'number', defaultValue: 0 },
+  { label: 'Y', value: 'y', valueType: 'number', defaultValue: 0 },
+  { label: 'Width', value: 'width', valueType: 'number', defaultValue: 100 },
+  { label: 'Height', value: 'height', valueType: 'number', defaultValue: 100 },
+  { label: 'Text', value: 'text', valueType: 'string', defaultValue: 'default' },
+  { label: 'Font Size', value: 'font_size', valueType: 'number', defaultValue: 12 },
+  { label: 'Add Flag', value: 'add_flag', valueType: 'select', options: flagOptions },
+  { label: 'Remove Flag', value: 'remove_flag', valueType: 'select', options: flagOptions },
+  { label: 'Background Color', value: 'bg_color', valueType: 'color' },
+  { label: 'Background Alpha', value: 'bg_alpha', valueType: 'number', min: 0, max: 255 },
+  { label: 'Gradient Color', value: 'bg_grad_color', valueType: 'color' },
+  {
+    label: 'Gradient Direction',
+    value: '',
+    valueType: 'select',
+    options: [
+      { label: 'None', value: 'none' },
+      { label: 'Vertical', value: 'vertical' },
+      { label: 'Horizontal', value: 'horizontal' }
+    ]
+  },
+  { label: 'Background Image Alpha', value: 'bg_img_alpha', valueType: 'number', min: 0, max: 255 },
+  { label: 'Background Image Render Color', value: 'bg_img_render_color', valueType: 'color' },
+  { label: 'Image Alpha', value: 'img_alpha', valueType: 'number', min: 0, max: 255 },
+  { label: 'Image Render Color', value: 'img_render_color', valueType: 'color' },
+  { label: 'Image Render Alpha', value: 'img_render_alpha', valueType: 'number', min: 0, max: 255 },
+  { label: 'Alpha', value: 'alpha', valueType: 'number', min: 0, max: 255 },
+  {
+    label: 'Border',
+    value: 'border_type',
+    valueType: 'select',
+    options: [
+      { label: 'None', value: 'none' },
+      { label: 'All', value: 'all' },
+      { label: 'Top', value: 'top' },
+      { label: 'Bottom', value: 'bottom' },
+      { label: 'Left', value: 'left' },
+      { label: 'Right', value: 'right' }
+    ]
+  },
+  { label: 'Border Size', value: 'border_width', valueType: 'number', defaultValue: 1 },
+  { label: 'Border Alpha', value: 'border_alpha', valueType: 'number', min: 0, max: 255 },
+  { label: 'Border Color', value: 'border_color', valueType: 'color' },
+  { label: 'Light', value: 'led_set_light', valueType: 'number', min: 0, max: 255 },
+  { label: 'Color', value: 'led_color', valueType: 'color' },
+  { label: 'Radius', value: 'radius', valueType: 'number' },
+  { label: 'Visible', value: 'visible', valueType: 'boolean' },
+  { label: 'Add State', value: 'add_state', valueType: 'select', options: stateOptions },
+  { label: 'Remove State', value: 'remove_state', valueType: 'select', options: stateOptions },
+  { label: 'Rotate', value: 'rotate', valueType: 'rotate' },
+  { label: 'Zoom', value: 'zoom', valueType: 'number' },
+  { label: 'Widget Value', value: 'widget_value', valueType: 'string' },
+  { label: 'Play Animation', value: 'play_animation', valueType: 'animation' }
+]

+ 55 - 20
src/renderer/src/views/designer/workspace/composite/eventEdit/index.vue

@@ -12,7 +12,7 @@ import NodeItem from './NodeItem.vue'
 import { useProjectStore } from '@/store/modules/project'
 import { bfsWalk } from 'simple-mind-map/src/utils'
 
-import type { NodeItemType } from './type'
+import type { NodeItemType, OptionType } from './type'
 import { v4 } from 'uuid'
 
 const containerRef = ref<HTMLElement | null>(null)
@@ -45,7 +45,7 @@ watch(
       // 添加增加节点
       bfsWalk(data, (child) => {
         if (child.children) {
-          child.children.push(getAddNode(child.id, child.nodeType))
+          child.children.push(getAddNode(child.id, child.nodeType, widget.type === 'page'))
         }
       })
       mindMapData.value = data
@@ -60,53 +60,88 @@ const refresh = () => {
   mindMap.value?.setData(mindMapData.value)
 }
 
-// 获取添加节点
-const getAddNode = (parentId: string, parentType: NodeItemType['parentType']): NodeItemType => {
+// 获取添加按钮
+const getAddNode = (
+  parentId: string,
+  parentType: NodeItemType['parentType'],
+  isPage: boolean = false
+): NodeItemType => {
   return {
     id: v4(),
     name: 'add_btn',
     nodeType: 'add',
     parentId,
-    parentType
+    parentType,
+    isPage
   }
 }
 
 // 添加节点
-const handleAddNode = (parentId: string, parentType: string, keys: string[]) => {
+const handleAddNode = (parentId: string, parentType: string, values: OptionType[]) => {
   const nodes: NodeItemType[] = []
   switch (parentType) {
     case 'root':
-      keys.forEach((key) => {
+      values.forEach((val) => {
         const id = v4()
         nodes.push({
           id,
-          name: key,
+          name: val.value,
           nodeType: 'event',
-          config: {},
+          config: {
+            data: val
+          },
           children: [getAddNode(id, 'event')]
         })
       })
       break
     case 'event':
-      keys.forEach((key) => {
+      values.forEach((val) => {
         const id = v4()
-        nodes.push({
-          id,
-          name: key,
-          nodeType: 'target',
-          config: {},
-          children: [getAddNode(id, 'target')]
-        })
+        // 自定义代码
+        if (val.value === 'customCode') {
+          nodes.push({
+            id,
+            name: val.label,
+            nodeType: 'target',
+            config: {
+              data: val
+            },
+            children: [
+              {
+                id: v4(),
+                name: 'custom_code',
+                nodeType: 'action',
+                config: {
+                  code: ''
+                },
+                children: []
+              }
+            ]
+          })
+        } else {
+          nodes.push({
+            id,
+            name: val.label,
+            nodeType: 'target',
+            config: {
+              data: val
+            },
+            children: [getAddNode(id, 'target')]
+          })
+        }
       })
       break
     case 'target':
-      keys.forEach((key) => {
+      values.forEach((val) => {
         const id = v4()
         nodes.push({
           id,
-          name: key,
+          name: val.value,
           nodeType: 'action',
-          config: {},
+          config: {
+            data: val,
+            value: val?.defaultValue
+          },
           children: []
         })
       })

+ 2 - 0
src/renderer/src/views/designer/workspace/composite/eventEdit/type.d.ts

@@ -32,3 +32,5 @@ export type NodeItemType = {
    */
   isPage?: boolean
 }
+
+export type OptionType = { label: string; value: string; [key: string]: any }

+ 40 - 0
src/renderer/src/views/designer/workspace/composite/eventEdit/useAllWidgets.ts

@@ -0,0 +1,40 @@
+import { computed } from 'vue'
+import { useProjectStore } from '@/store/modules/project'
+import { bfsWalk } from 'simple-mind-map/src/utils'
+
+type Widget = {
+  id: string
+  name: string
+  type: string
+}
+/**
+ * 获取所有 widget
+ */
+export const useAllWidgets = () => {
+  const projectStore = useProjectStore()
+
+  const allWidgets = computed(() => {
+    const widgets: Widget[] = []
+    projectStore.project?.screens?.forEach((screen) => {
+      widgets.push({
+        id: screen.id,
+        name: screen.name,
+        type: 'screen'
+      })
+      screen.pages.forEach((page) => {
+        // 递归获取页面所有 widget
+        bfsWalk(page, (child) => {
+          widgets.push({
+            id: child.id,
+            name: child.name,
+            type: child.type
+          })
+        })
+      })
+    })
+
+    return widgets
+  })
+
+  return { allWidgets }
+}

+ 1 - 1
src/renderer/src/views/designer/workspace/stage/index.vue

@@ -7,7 +7,7 @@
     @mouseup="handleMouseUp"
   >
     <div class="workspace flex flex-col">
-      <div class="h-32px bg-bg-secondary stage-title border-y-1px border-y-solid border-border">
+      <div class="h-32px bg-bg-secondary stage-title border-b-1px border-b-solid border-border">
         <div class="px-12px leading-32px text-text-primary font-bold">屏幕</div>
       </div>
       <div class="workspace-top">