jiaxing.liao недель назад: 3
Родитель
Сommit
eb08c9767e
23 измененных файлов с 775 добавлено и 18 удалено
  1. 2 1
      src/renderer/src/locales/en_US.json
  2. 2 1
      src/renderer/src/locales/zh_CN.json
  3. 12 0
      src/renderer/src/lvgl-widgets/assets/icon/icon_13tab.svg
  4. 10 0
      src/renderer/src/lvgl-widgets/assets/icon/icon_14tileview.svg
  5. 15 0
      src/renderer/src/lvgl-widgets/assets/icon/icon_28spinbox.svg
  6. 10 0
      src/renderer/src/lvgl-widgets/assets/icon/icon_40crcode.svg
  7. 10 0
      src/renderer/src/lvgl-widgets/assets/icon/icon_41barcode.svg
  8. 10 0
      src/renderer/src/lvgl-widgets/assets/icon/icon_8checkbox.svg
  9. 10 0
      src/renderer/src/lvgl-widgets/assets/icon/icon_9swithch.svg
  10. 11 0
      src/renderer/src/lvgl-widgets/assets/icon/icon_meter.svg
  11. 10 0
      src/renderer/src/lvgl-widgets/assets/icon/icon_slider.svg
  12. 1 0
      src/renderer/src/lvgl-widgets/container/Container.vue
  13. 1 2
      src/renderer/src/lvgl-widgets/container/index.ts
  14. 6 2
      src/renderer/src/lvgl-widgets/hooks/useWidgetStyle.ts
  15. 5 2
      src/renderer/src/lvgl-widgets/index.ts
  16. 79 0
      src/renderer/src/lvgl-widgets/tabview/Config.vue
  17. 121 0
      src/renderer/src/lvgl-widgets/tabview/Tabview.vue
  18. 313 0
      src/renderer/src/lvgl-widgets/tabview/index.tsx
  19. 96 0
      src/renderer/src/lvgl-widgets/tabview/style.json
  20. 4 0
      src/renderer/src/model/index.ts
  21. 7 2
      src/renderer/src/views/designer/config/property/CusFormItem.vue
  22. 1 0
      src/renderer/src/views/designer/config/property/components/CusTextarea.vue
  23. 39 8
      src/renderer/src/views/designer/workspace/stage/Node.vue

+ 2 - 1
src/renderer/src/locales/en_US.json

@@ -96,5 +96,6 @@
   "checkbox": "Checkbox",
   "switch": "Switch",
   "bar": "Bar",
-  "slider": "Slider"
+  "slider": "Slider",
+  "tabview": "Tabview"
 }

+ 2 - 1
src/renderer/src/locales/zh_CN.json

@@ -96,5 +96,6 @@
   "checkbox": "复选框",
   "switch": "开关",
   "bar": "进度条",
-  "slider": "滑动条"
+  "slider": "滑动条",
+  "tabview": "选项卡"
 }

+ 12 - 0
src/renderer/src/lvgl-widgets/assets/icon/icon_13tab.svg

@@ -0,0 +1,12 @@
+<svg width="80" height="52" viewBox="0 0 80 52" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2_750)">
+<path d="M52.6161 10V2H71V10H52.6161ZM31 2H49.4286V10H31V2Z" fill="#999999"/>
+<path d="M71 16V49H10V16H71ZM66.8409 20.125H14.1591V44.875H66.8409V20.125Z" fill="#999999"/>
+<path d="M10 2H28V10H10V2Z" fill="#00A2FF"/>
+</g>
+<defs>
+<clipPath id="clip0_2_750">
+<rect width="80" height="52" fill="white"/>
+</clipPath>
+</defs>
+</svg>

Разница между файлами не показана из-за своего большого размера
+ 10 - 0
src/renderer/src/lvgl-widgets/assets/icon/icon_14tileview.svg


Разница между файлами не показана из-за своего большого размера
+ 15 - 0
src/renderer/src/lvgl-widgets/assets/icon/icon_28spinbox.svg


Разница между файлами не показана из-за своего большого размера
+ 10 - 0
src/renderer/src/lvgl-widgets/assets/icon/icon_40crcode.svg


+ 10 - 0
src/renderer/src/lvgl-widgets/assets/icon/icon_41barcode.svg

@@ -0,0 +1,10 @@
+<svg width="80" height="52" viewBox="0 0 80 52" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2_1126)">
+<path d="M13 11H22.8182V41H13V11ZM38.7727 11H43.6818V41H38.7727V11ZM49.8182 11H59.6364V41H49.8182V11ZM64.5455 11H67V41H64.5455V11ZM28.9545 11H31.4091V41H28.9545V11Z" fill="#B0B1B1"/>
+</g>
+<defs>
+<clipPath id="clip0_2_1126">
+<rect width="80" height="52" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 10 - 0
src/renderer/src/lvgl-widgets/assets/icon/icon_8checkbox.svg

@@ -0,0 +1,10 @@
+<svg width="80" height="52" viewBox="0 0 80 52" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2_664)">
+<path d="M53.5714 7H26.4284C23.4427 7 21 9.44275 21 12.4285V39.5712C21 42.5572 23.4427 45.0004 26.4284 45.0004H53.5714C56.5568 45.0004 59 42.5572 59 39.5712V12.4285C59 9.44275 56.5569 7 53.5714 7ZM45.7003 24.9146C41.0858 31.7004 37.0142 39.5717 37.0142 39.5717L26.4285 27.6289L29.143 24.6432L35.6572 30.886C35.6572 30.886 38.9145 25.4574 43.2573 20.8432C47.6003 16.2287 52.7574 12.4287 52.7574 12.4287L53.5716 15.1432C53.5716 15.1432 49.2287 19.7574 45.7003 24.9146Z" fill="#B0B1B1"/>
+</g>
+<defs>
+<clipPath id="clip0_2_664">
+<rect width="80" height="52" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 10 - 0
src/renderer/src/lvgl-widgets/assets/icon/icon_9swithch.svg

@@ -0,0 +1,10 @@
+<svg width="80" height="52" viewBox="0 0 80 52" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2_674)">
+<path d="M50.875 13C58.1238 13 64 18.8203 64 26C64 33.1797 58.1238 39 50.875 39H29.125C21.8763 39 16 33.1797 16 26C16 18.8203 21.8763 13 29.125 13H50.875ZM50.875 18.3061C46.5849 18.3061 43.1071 21.7508 43.1071 26C43.1071 30.2492 46.5849 33.6939 50.875 33.6939C55.1651 33.6939 58.6429 30.2492 58.6429 26C58.6429 21.7508 55.1651 18.3061 50.875 18.3061Z" fill="#B0B1B1"/>
+</g>
+<defs>
+<clipPath id="clip0_2_674">
+<rect width="80" height="52" fill="white"/>
+</clipPath>
+</defs>
+</svg>

Разница между файлами не показана из-за своего большого размера
+ 11 - 0
src/renderer/src/lvgl-widgets/assets/icon/icon_meter.svg


Разница между файлами не показана из-за своего большого размера
+ 10 - 0
src/renderer/src/lvgl-widgets/assets/icon/icon_slider.svg


+ 1 - 0
src/renderer/src/lvgl-widgets/container/Container.vue

@@ -10,6 +10,7 @@ const props = defineProps<{
   height: number
   styles: any
   state: string
+  part: string
 }>()
 
 const styleMap = useWidgetStyle({

+ 1 - 2
src/renderer/src/lvgl-widgets/container/index.ts

@@ -29,8 +29,7 @@ export default {
       height: 200,
       addFlags: [],
       removeFlags: [],
-      scrollbar: 'on',
-      state: 'default'
+      scrollbar: 'on'
     },
     styles: [
       {

+ 6 - 2
src/renderer/src/lvgl-widgets/hooks/useWidgetStyle.ts

@@ -28,7 +28,7 @@ type StyleMap = Record<
  * @param value
  * @returns
  */
-const getStyle = (key, value) => {
+export const getStyle = (key, value) => {
   const style: CSSProperties = {}
   switch (key) {
     // 背景样式
@@ -54,20 +54,24 @@ const getStyle = (key, value) => {
     // 边框样式
     case 'border': {
       style.borderColor = 'transparent'
-      style.borderWidth = `${value?.width || 0}px`
+      style.borderWidth = 0
       style.borderRadius = `${value?.radius}px`
       style.borderStyle = 'solid'
       if (value?.side?.includes('all') || value?.side?.includes('top')) {
         style.borderTopColor = value?.color
+        style.borderTopWidth = `${value?.width || 0}px`
       }
       if (value?.side?.includes('all') || value?.side?.includes('right')) {
         style.borderRightColor = value?.color
+        style.borderRightWidth = `${value?.width || 0}px`
       }
       if (value?.side?.includes('all') || value?.side?.includes('bottom')) {
         style.borderBottomColor = value?.color
+        style.borderBottomWidth = `${value?.width || 0}px`
       }
       if (value?.side?.includes('all') || value?.side?.includes('left')) {
         style.borderLeftColor = value?.color
+        style.borderLeftWidth = `${value?.width || 0}px`
       }
       break
     }

+ 5 - 2
src/renderer/src/lvgl-widgets/index.ts

@@ -1,5 +1,6 @@
 import Button from './button'
 import ImageButton from './image-button'
+import Label from './label'
 import MatrixButton from './button-matrix/index'
 import Image from './image'
 import SpanGroup from './span-group/index'
@@ -11,7 +12,8 @@ import Bar from './bar'
 import Slider from './slider'
 
 import Container from './container'
-import Label from './label'
+import Tabview from './tabview/index'
+
 import Page from './page'
 import { IComponentModelConfig } from './type'
 
@@ -31,7 +33,8 @@ export const ComponentArray = [
   Bar,
   Slider,
 
-  Container
+  Container,
+  Tabview
 ]
 
 const componentMap: { [key: string]: IComponentModelConfig } = ComponentArray.reduce((acc, cur) => {

+ 79 - 0
src/renderer/src/lvgl-widgets/tabview/Config.vue

@@ -0,0 +1,79 @@
+<template>
+  <el-card class="mb-12px" body-class="p-8px!">
+    <template #header>
+      <div class="flex items-center justify-between">
+        <span>标签</span>
+        <LuPlus class="cursor-pointer" size="16px" @click="handleAddRow" />
+      </div>
+    </template>
+    <el-scrollbar max-height="120px">
+      <div
+        class="flex items-center gap-4px box-border pr-12px mb-4px"
+        v-for="(_, index) in children || []"
+        :key="index"
+      >
+        <div class="w-full flex items-center gap-4px relative group/item">
+          <el-radio v-model="activeIndex" :value="index" class="mr-0!" />
+          <el-input spellcheck="false" v-model="children[index].name" />
+          <div class="cursor-pointer" @click="handleDeleteItem(index)">
+            <LuTrash2 size="14px" />
+          </div>
+        </div>
+      </div>
+    </el-scrollbar>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import { computed, type Ref } from 'vue'
+import { LuPlus, LuTrash2 } from 'vue-icons-plus/lu'
+
+const props = defineProps<{
+  values: Ref<any>
+}>()
+
+// 子项
+const children = computed({
+  get() {
+    return (props.values?.value?.children || []) as any[]
+  },
+  set(list: any[]) {
+    if (props.values?.value?.children) {
+      props.values.value.children = list
+    }
+  }
+})
+
+// 当前激活
+const activeIndex = computed({
+  get() {
+    return props.values?.value?.props?.activeIndex
+  },
+  set(index: number) {
+    if (props.values?.value?.props) {
+      props.values.value.props.activeIndex = index
+    }
+  }
+})
+
+/**
+ * 删除一项
+ * @param index 索引
+ */
+const handleDeleteItem = (index: number) => {
+  children.value.splice(index, 1)
+}
+
+/**
+ * 添加一项
+ */
+const handleAddRow = () => {
+  children.value?.push({
+    name: 'Tab',
+    type: 'tab',
+    children: []
+  })
+}
+</script>
+
+<style scoped></style>

+ 121 - 0
src/renderer/src/lvgl-widgets/tabview/Tabview.vue

@@ -0,0 +1,121 @@
+<template>
+  <div :style="mainStyle" class="flex overflow-hidden">
+    <div class="flex box-border" :style="tabMainStyle">
+      <div
+        class="flex-1 shrink-0 grid place-items-center box-border"
+        v-for="(item, index) in children || []"
+        :key="index"
+        :style="activeIndex === index ? activeStyle : styleMap?.itemsStyle"
+      >
+        {{ item.name }}
+      </div>
+    </div>
+    <div class="w-auto h-auto flex-1"></div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, type CSSProperties } from 'vue'
+import { useWidgetStyle, getStyle } from '../hooks/useWidgetStyle'
+import { useProjectStore } from '@/store/modules/project'
+import defaultStyle from './style.json'
+
+const props = defineProps<{
+  width: number
+  height: number
+  styles: any
+  state: string
+  part: string
+  scrollbar: string
+  size: number
+  position: string
+  children?: any[]
+  activeIndex: number
+}>()
+
+const styleMap = useWidgetStyle({
+  widget: 'lv_tabview',
+  props
+})
+
+const projectStore = useProjectStore()
+
+// 根据位置动态调整样式
+const tabMainStyle = computed((): CSSProperties => {
+  const { position = 'top', size } = props
+  return ['left', 'right'].includes(position)
+    ? {
+        width: `${size}px`,
+        height: '100%',
+        flexDirection: 'column',
+        ...styleMap.value?.tabStyle
+      }
+    : {
+        height: `${size}px`,
+        width: '100%',
+        flexDirection: 'row',
+        ...styleMap.value?.tabStyle
+      }
+})
+
+// 激活样式
+const activeStyle = computed(() => {
+  const styles = props.styles
+
+  const itemStyle = styles.find(
+    (item) => item?.part?.name === 'items' && item.part?.state === 'checked'
+  )
+
+  const globalItemStyle = projectStore.globalStyle
+    ?.find((item) => item.widget === 'lv_tabview')
+    ?.part?.find((item) => item.partName === 'items')
+    ?.state?.find((item) => item.state === 'checked')?.style
+
+  const defaultItemStyle = defaultStyle.part
+    .find((item) => item.partName === 'items')
+    ?.state.find((item) => item.state === 'checked')?.style
+
+  const style = itemStyle || globalItemStyle || defaultItemStyle
+
+  let styleMap: CSSProperties = {}
+
+  Object.keys(style || {}).forEach((key) => {
+    // 合并样式
+    styleMap = {
+      ...styleMap,
+      ...getStyle(key, style?.[key])
+    }
+  })
+
+  return styleMap
+})
+
+// 动态计算main样式
+const mainStyle = computed(() => {
+  const { position = 'top' } = props
+  const otherStyle: CSSProperties = {}
+
+  if (position === 'top') {
+    otherStyle.flexDirection = 'column'
+  }
+
+  if (position === 'left') {
+    otherStyle.flexDirection = 'row'
+  }
+
+  if (position === 'right') {
+    otherStyle.flexFlow = 'row-reverse'
+  }
+
+  if (position === 'bottom') {
+    otherStyle.flexFlow = 'column-reverse'
+  }
+
+  return {
+    ...styleMap.value?.mainStyle,
+    ...otherStyle
+  }
+})
+</script>
+
+<style scoped></style>

+ 313 - 0
src/renderer/src/lvgl-widgets/tabview/index.tsx

@@ -0,0 +1,313 @@
+import Tabview from './Tabview.vue'
+import icon from '../assets/icon/icon_13tab.svg'
+import { flagOptions } from '@/constants'
+import type { IComponentModelConfig } from '../type'
+import i18n from '@/locales'
+import defaultStyle from './style.json'
+import Config from './Config.vue'
+
+export default {
+  label: i18n.global.t('tabview'),
+  icon,
+  component: Tabview,
+  key: 'lv_tabview',
+  group: i18n.global.t('container'),
+  sort: 1,
+  hasChildren: true,
+  defaultStyle,
+  parts: [
+    {
+      name: 'main',
+      stateList: ['default']
+    },
+    {
+      name: 'tab',
+      stateList: ['default']
+    },
+    {
+      name: 'items',
+      stateList: ['default', 'checked']
+    }
+  ],
+  defaultSchema: {
+    name: 'tabview',
+    children: [
+      {
+        name: 'Tab',
+        type: 'tab',
+        children: []
+      },
+      {
+        name: 'Tab',
+        type: 'tab',
+        children: []
+      },
+      {
+        name: 'Tab',
+        type: 'tab',
+        children: []
+      }
+    ],
+    props: {
+      x: 0,
+      y: 0,
+      width: 400,
+      height: 250,
+      addFlags: [],
+      removeFlags: [],
+      scrollbar: 'on',
+      size: 50,
+      position: 'top',
+      // 当前激活index
+      activeIndex: 0
+    },
+    styles: [
+      {
+        part: {
+          name: 'main',
+          state: 'default'
+        },
+        background: {
+          color: '#eeeeeeff'
+        },
+        border: {
+          color: '#c0c0c064',
+          width: 0,
+          radius: 0,
+          side: ['all']
+        },
+        shadow: {
+          color: '#2092f5ff',
+          x: 0,
+          y: 0,
+          spread: 0,
+          width: 0
+        }
+      },
+      {
+        part: {
+          name: 'tab',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffffff'
+        },
+        border: {
+          color: '#2092f5ff',
+          width: 0,
+          radius: 0,
+          side: ['all']
+        }
+      },
+      {
+        part: {
+          name: 'items',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffffff'
+        },
+        text: {
+          color: '#4d4d4dff',
+          size: 16,
+          family: 'xx',
+          weight: 'normal'
+        },
+        border: {
+          color: '#2092f500',
+          width: 4,
+          radius: 0,
+          side: ['bottom']
+        }
+      }
+    ]
+  },
+  config: {
+    // 组件属性
+    props: [
+      {
+        label: '名称',
+        field: 'name',
+        valueType: 'text',
+        componentProps: {
+          placeholder: '请输入名称'
+        }
+      },
+      {
+        label: '位置/大小',
+        valueType: 'group',
+        children: [
+          {
+            label: '',
+            field: 'props.x',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            },
+            slots: { prefix: 'X' }
+          },
+          {
+            label: '',
+            field: 'props.y',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            },
+            slots: { prefix: 'Y' }
+          },
+          {
+            label: '',
+            field: 'props.width',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            },
+            slots: { prefix: 'W' }
+          },
+          {
+            label: '',
+            field: 'props.height',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            },
+            slots: { prefix: 'H' }
+          }
+        ]
+      },
+      {
+        label: '滚动条',
+        field: 'props.scrollbar',
+        valueType: 'select',
+        componentProps: {
+          options: [
+            { label: 'On', value: 'on' },
+            { label: 'Off', value: 'off' },
+            { label: 'Auto', value: 'auto' },
+            { label: 'Active', value: 'active' }
+          ]
+        }
+      },
+      {
+        label: '添加标识',
+        field: 'props.addFlags',
+        valueType: 'checkbox',
+        componentProps: {
+          options: flagOptions,
+          defaultCollapsed: true
+        }
+      },
+      {
+        label: '删除标识',
+        field: 'props.removeFlags',
+        valueType: 'checkbox',
+        componentProps: {
+          options: flagOptions,
+          defaultCollapsed: true
+        }
+      }
+    ],
+    coreProps: [
+      {
+        label: '大小',
+        field: 'props.size',
+        valueType: 'number',
+        componentProps: {
+          min: 1,
+          max: 1000
+        }
+      },
+      {
+        label: '位置',
+        field: 'props.position',
+        valueType: 'select',
+        componentProps: {
+          options: [
+            { label: 'Top', value: 'top' },
+            { label: 'Bottom', value: 'bottom' },
+            { label: 'Left', value: 'left' },
+            { label: 'Right', value: 'right' }
+          ]
+        }
+      },
+      {
+        label: '属性',
+        field: '',
+        render: (val) => {
+          return <Config values={val} />
+        }
+      }
+    ],
+    // 组件样式
+    styles: [
+      {
+        label: '模块状态',
+        field: 'part',
+        valueType: 'part'
+      },
+      {
+        valueType: 'dependency',
+        name: ['part'],
+        dependency: ({ part }) => {
+          return part?.name === 'main'
+            ? [
+                {
+                  label: '背景',
+                  field: 'background',
+                  valueType: 'background',
+                  componentProps: {
+                    onlyColor: true
+                  }
+                },
+                {
+                  label: '边框',
+                  field: 'border',
+                  valueType: 'border'
+                },
+                {
+                  label: '阴影',
+                  field: 'shadow',
+                  valueType: 'shadow'
+                }
+              ]
+            : part?.name === 'tab'
+              ? [
+                  {
+                    label: '背景',
+                    field: 'background',
+                    valueType: 'background',
+                    componentProps: {
+                      onlyColor: true
+                    }
+                  },
+                  {
+                    label: '边框',
+                    field: 'border',
+                    valueType: 'border'
+                  }
+                ]
+              : [
+                  {
+                    label: '背景',
+                    field: 'background',
+                    valueType: 'background',
+                    componentProps: {
+                      onlyColor: true
+                    }
+                  },
+                  {
+                    label: '字体',
+                    field: 'text',
+                    valueType: 'font'
+                  },
+                  {
+                    label: '边框',
+                    field: 'border',
+                    valueType: 'border'
+                  }
+                ]
+        }
+      }
+    ]
+  }
+} as IComponentModelConfig

+ 96 - 0
src/renderer/src/lvgl-widgets/tabview/style.json

@@ -0,0 +1,96 @@
+{
+  "widget": "lv_tabview",
+  "styleName": "defualt",
+  "part": [
+    {
+      "partName": "main",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#eeeeeeff"
+            },
+            "border": {
+              "color": "#c0c0c064",
+              "width": 0,
+              "radius": 0,
+              "side": ["all"]
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "tab",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "border": {
+              "color": "#2092f5ff",
+              "width": 0,
+              "radius": 0,
+              "side": ["all"]
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "items",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "text": {
+              "color": "#4d4d4dff",
+              "size": 16,
+              "family": "xx",
+              "weight": "normal"
+            },
+            "border": {
+              "color": "#2092f500",
+              "width": 4,
+              "radius": 0,
+              "side": ["bottom"]
+            }
+          }
+        },
+        {
+          "state": "checked",
+          "style": {
+            "background": {
+              "color": "#2092f53c"
+            },
+            "text": {
+              "color": "#2092f5ff",
+              "size": 16,
+              "family": "xx",
+              "weight": "normal"
+            },
+            "border": {
+              "color": "#2092f5ff",
+              "width": 4,
+              "radius": 0,
+              "side": ["bottom"]
+            }
+          }
+        }
+      ]
+    }
+  ]
+}

+ 4 - 0
src/renderer/src/model/index.ts

@@ -170,5 +170,9 @@ export const createWidget = (schema: IComponentModelConfig, index: number) => {
     componentSchema.children = []
   }
 
+  if (defaultSchema?.children) {
+    componentSchema.children = klona(defaultSchema.children)
+  }
+
   return componentSchema
 }

+ 7 - 2
src/renderer/src/views/designer/config/property/CusFormItem.vue

@@ -10,7 +10,12 @@
       :label-width="schema.label ? '90px' : '0px'"
     >
       <!-- 文本 -->
-      <el-input v-if="schema.valueType === 'text'" v-model="value" v-bind="schema?.componentProps">
+      <el-input
+        v-if="schema.valueType === 'text'"
+        v-model="value"
+        spellcheck="false"
+        v-bind="schema?.componentProps"
+      >
         <template #prefix>
           {{ schema?.slots?.prefix }}
         </template>
@@ -197,7 +202,7 @@ const key = v4()
 const value = computed({
   get() {
     const { schema, formData } = props
-    return schema.field ? get(formData, schema.field) : null
+    return schema.field ? get(formData, schema.field) : formData
   },
   set(val) {
     const { schema } = props

+ 1 - 0
src/renderer/src/views/designer/config/property/components/CusTextarea.vue

@@ -2,6 +2,7 @@
   <div class="w-full relative">
     <el-input
       type="textarea"
+      spellcheck="false"
       v-model="modelValue"
       placeholder="请输入内容"
       :rows="rows"

+ 39 - 8
src/renderer/src/views/designer/workspace/stage/Node.vue

@@ -11,20 +11,17 @@
   >
     <!-- 控件 -->
     <component
-      v-bind="schema.props"
+      v-bind="widgetProps"
       :is="widget"
-      :part="schema?.part"
-      :state="schema?.state"
-      :styles="schema.style"
       style="width: 100%; height: 100%"
       :style="{ 'pointer-events': schema.type === 'page' ? 'none' : '' }"
     />
     <!-- 子节点 -->
     <NodeItem
       v-if="schema.children"
-      v-for="(child, index) in schema.children"
+      v-for="(child, index) in children"
       :key="child.id"
-      :zIndex="schema.children.length - index"
+      :zIndex="schema.children.length - Number(index)"
       :schema="child"
       :rootContainer="nodeRef!"
     />
@@ -48,6 +45,7 @@ import { useDrop } from 'vue-hooks-plus'
 import { createWidget } from '@/model'
 import LvglWidgets from '@/lvgl-widgets'
 import { useProjectStore } from '@/store/modules/project'
+import { has } from 'lodash-es'
 
 import ContentMenu from './ContentMenu.vue'
 import { getAddWidgetIndex } from '@/utils'
@@ -81,6 +79,30 @@ const zIndex = ref(props.zIndex)
 // 放置效果样式
 const dropStyle = ref<CSSProperties>({})
 
+// 子节点
+// 默认为子节点 当子节点为可切换容器时 根据activeIndex展示下一级节点
+const children = computed(() => {
+  const { schema } = props
+
+  return has(schema.props, 'activeIndex')
+    ? schema.children?.[schema.props.activeIndex]?.children || []
+    : schema.children
+})
+
+// 控件属性
+const widgetProps = computed(() => {
+  const { schema } = props
+
+  return {
+    ...(schema?.props || {}),
+    part: schema?.part,
+    state: schema?.state,
+    styles: schema?.style,
+    // 处理可切换子节点容器属性
+    ...(has(schema.props, 'activeIndex') && schema.children ? { children: schema.children } : {})
+  }
+})
+
 // 组件样式
 const getStyle = computed((): CSSProperties => {
   const { style = {}, schema } = props
@@ -119,17 +141,26 @@ const getStyle = computed((): CSSProperties => {
 useDrop(nodeRef, {
   // 元素放置
   onDom: (content, event) => {
+    const { schema } = props
+
     dropStyle.value = {}
     event?.stopPropagation()
-    if (!content) return
+    if (!content || !schema?.children) return
+
     // 创建控件
     const { offsetX = 0, offsetY = 0 } = event || {}
     const index = getAddWidgetIndex(page!, content.key)
     const newWidget = createWidget(content, index)
     newWidget.props.x = offsetX
     newWidget.props.y = offsetY
+
     // 添加到前面
-    props.schema.children?.unshift(newWidget)
+    if (has(schema.props, 'activeIndex')) {
+      schema.children[schema.props.activeIndex].children?.unshift(newWidget)
+    } else {
+      schema.children?.unshift(newWidget)
+    }
+
     projectStore.setSelectWidgets([newWidget])
   },
   onDragOver: () => {