Browse Source

feat: 完善下拉框配置、添加复选框

jiaxing.liao 6 days ago
parent
commit
e457058143

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

@@ -43,6 +43,7 @@ declare module 'vue' {
     ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
+    ElScollbar: typeof import('element-plus/es')['ElScollbar']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElScroller: typeof import('element-plus/es')['ElScroller']
     ElSelect: typeof import('element-plus/es')['ElSelect']
@@ -101,6 +102,7 @@ declare global {
   const ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
   const ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
   const ElRow: typeof import('element-plus/es')['ElRow']
+  const ElScollbar: typeof import('element-plus/es')['ElScollbar']
   const ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
   const ElScroller: typeof import('element-plus/es')['ElScroller']
   const ElSelect: typeof import('element-plus/es')['ElSelect']

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

@@ -92,5 +92,6 @@
   "richText": "Span Group",
   "textarea": "Textarea",
   "createProjectFirst": "Please Create The Project First!",
-  "dropdown": "Dropdown"
+  "dropdown": "Dropdown",
+  "checkbox": "Checkbox"
 }

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

@@ -92,5 +92,6 @@
   "richText": "富文本",
   "textarea": "文本框",
   "createProjectFirst": "请先创建项目",
-  "dropdown": "下拉框"
+  "dropdown": "下拉框",
+  "checkbox": "复选框"
 }

+ 12 - 8
src/renderer/src/lvgl-widgets/button-matrix/index.tsx

@@ -120,36 +120,40 @@ export default {
         valueType: 'group',
         children: [
           {
-            label: 'X',
+            label: '',
             field: 'props.x',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'X' }
           },
           {
-            label: 'Y',
+            label: '',
             field: 'props.y',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'Y' }
           },
           {
-            label: '宽度',
+            label: '',
             field: 'props.width',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'W' }
           },
           {
-            label: '高度',
+            label: '',
             field: 'props.height',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'H' }
           }
         ]
       },

+ 12 - 8
src/renderer/src/lvgl-widgets/button/index.ts

@@ -83,36 +83,40 @@ export default {
         valueType: 'group',
         children: [
           {
-            label: 'X',
+            label: '',
             field: 'props.x',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'X' }
           },
           {
-            label: 'Y',
+            label: '',
             field: 'props.y',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'Y' }
           },
           {
-            label: '宽度',
+            label: '',
             field: 'props.width',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'W' }
           },
           {
-            label: '高度',
+            label: '',
             field: 'props.height',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'H' }
           }
         ]
       },

+ 112 - 0
src/renderer/src/lvgl-widgets/checkbox/Checkbox.vue

@@ -0,0 +1,112 @@
+<template>
+  <div :style="getProps.mainStyle">
+    <div :style="getProps.indicatorStyle"></div>
+    <div class="ml-8px">{{ props.text }}</div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, type CSSProperties } from 'vue'
+import defaultStyle from './style.json'
+
+const props = defineProps<{
+  width?: number
+  height?: number
+  text?: string
+  styles: any
+  state?: string
+}>()
+
+const getProps = computed((): Record<string, CSSProperties> => {
+  const styles = props.styles
+
+  let mainStyle = styles.find((item) => item.state === props.state && item.part.name === 'main')
+  let indicatorStyle = styles.find((item) => item.part.name === 'indicator')
+
+  // 从默认样式获取样式
+  if (!mainStyle && props.state) {
+    mainStyle = defaultStyle.part
+      ?.find((item) => item.partName === 'main')
+      ?.state.find((item) => item.state === props.state)?.style
+  }
+  if (!indicatorStyle && props.state) {
+    indicatorStyle = defaultStyle.part
+      ?.find((item) => item.partName === 'selected')
+      ?.state.find((item) => item.state === 'checked')?.style
+  }
+
+  return {
+    mainStyle: {
+      boxSizing: 'border-box',
+
+      backgroundColor: mainStyle?.background.color,
+
+      fontSize: `${mainStyle?.text.size}px`,
+      color: mainStyle?.text?.color,
+      display: 'flex',
+      alignItems: 'center',
+      justifyContent: 'left',
+      fontWeight: mainStyle?.text?.weight === 'bold' ? 'bold' : 'normal',
+
+      borderRadius: `${mainStyle?.border.radius}px`,
+      borderStyle: 'solid',
+      borderColor: 'transparent',
+      borderWidth: `${mainStyle?.border.width}px`,
+      borderTopColor:
+        mainStyle?.border?.side?.includes('all') || mainStyle?.border?.side?.includes('top')
+          ? mainStyle?.border?.color
+          : 'transparent',
+      borderRightColor:
+        mainStyle?.border?.side?.includes('all') || mainStyle?.border?.side?.includes('right')
+          ? mainStyle?.border?.color
+          : 'transparent',
+      borderBottomColor:
+        mainStyle?.border?.side?.includes('all') || mainStyle?.border?.side?.includes('bottom')
+          ? mainStyle?.border?.color
+          : 'transparent',
+      borderLeftColor:
+        mainStyle?.border?.side?.includes('all') || mainStyle?.border?.side?.includes('left')
+          ? mainStyle?.border?.color
+          : 'transparent',
+      // 内边距
+      padding: `${mainStyle?.padding?.top}px ${mainStyle?.padding?.right}px ${mainStyle?.padding?.bottom}px ${mainStyle?.padding?.left}px`,
+      /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
+      boxShadow: mainStyle?.shadow
+        ? `${mainStyle?.shadow?.x}px ${mainStyle?.shadow?.y}px ${mainStyle?.shadow?.width}px ${mainStyle?.shadow?.spread}px ${mainStyle?.shadow?.color}`
+        : 'none'
+    },
+    indicatorStyle: {
+      flexShrink: 0,
+      height: '16px',
+      width: '16px',
+      backgroundColor: indicatorStyle?.background.color,
+
+      borderRadius: `${indicatorStyle?.border.radius}px`,
+      borderStyle: 'solid',
+      borderColor: 'transparent',
+      borderWidth: `${indicatorStyle?.border.width}px`,
+      borderTopColor:
+        indicatorStyle?.border?.side?.includes('all') ||
+        indicatorStyle?.border?.side?.includes('top')
+          ? indicatorStyle?.border?.color
+          : 'transparent',
+      borderRightColor:
+        indicatorStyle?.border?.side?.includes('all') ||
+        indicatorStyle?.border?.side?.includes('right')
+          ? indicatorStyle?.border?.color
+          : 'transparent',
+      borderBottomColor:
+        indicatorStyle?.border?.side?.includes('all') ||
+        indicatorStyle?.border?.side?.includes('bottom')
+          ? indicatorStyle?.border?.color
+          : 'transparent',
+      borderLeftColor:
+        indicatorStyle?.border?.side?.includes('all') ||
+        indicatorStyle?.border?.side?.includes('left')
+          ? indicatorStyle?.border?.color
+          : 'transparent',
+      padding: `${indicatorStyle?.padding?.left}px`
+    }
+  }
+})
+</script>

+ 234 - 0
src/renderer/src/lvgl-widgets/checkbox/index.ts

@@ -0,0 +1,234 @@
+import Checkbox from './Checkbox.vue'
+import icon from '../assets/icon/icon_1btn.svg'
+import type { IComponentModelConfig } from '../type'
+import i18n from '@/locales'
+import { flagOptions } from '@/constants'
+import defaultStyle from './style.json'
+
+export default {
+  label: i18n.global.t('checkbox'),
+  icon,
+  component: Checkbox,
+  key: 'lv_checkbox',
+  group: i18n.global.t('basic'),
+  sort: 1,
+  defaultStyle,
+  parts: [
+    {
+      name: 'main',
+      stateList: ['default', 'focused', 'disabled']
+    },
+    {
+      name: 'indicator',
+      stateList: ['default', 'pressed', 'checked', 'checked&pressed', 'focused', 'disabled']
+    }
+  ],
+  defaultSchema: {
+    name: 'checkbox',
+    props: {
+      x: 0,
+      y: 0,
+      addFlags: [],
+      removeFlags: [],
+      text: 'checkbox'
+    },
+    styles: [
+      {
+        part: {
+          name: 'main',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffffff'
+        },
+        text: {
+          color: '#000000ff',
+          family: 'xx',
+          size: 16,
+          weight: 'normal'
+        },
+        border: {
+          color: '#eeeeeeff',
+          width: 0,
+          radius: 6,
+          side: ['all']
+        },
+        gap: {
+          column: 2
+        },
+        padding: {
+          top: 0,
+          right: 0,
+          bottom: 0,
+          left: 0
+        },
+        shadow: {
+          color: '#2092f5ff',
+          x: 0,
+          y: 0,
+          spread: 0,
+          width: 0
+        }
+      },
+      {
+        part: {
+          name: 'indicator',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffffff'
+        },
+        border: {
+          color: '#2092f5ff',
+          width: 2,
+          radius: 6,
+          side: ['all']
+        },
+        padding: {
+          left: 3,
+          right: 3,
+          top: 3,
+          bottom: 3
+        }
+      }
+    ]
+  },
+  config: {
+    // 组件属性
+    props: [
+      {
+        label: '名称',
+        field: 'name',
+        valueType: 'text',
+        componentProps: {
+          placeholder: '请输入名称'
+        }
+      },
+      {
+        label: '位置',
+        valueType: 'group',
+        children: [
+          {
+            label: 'X',
+            field: 'props.x',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            }
+          },
+          {
+            label: 'Y',
+            field: 'props.y',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            }
+          }
+        ]
+      },
+      {
+        label: '添加标识',
+        field: 'props.addFlags',
+        valueType: 'checkbox',
+        componentProps: {
+          options: flagOptions,
+          defaultCollapsed: true
+        }
+      },
+      {
+        label: '删除标识',
+        field: 'props.removeFlags',
+        valueType: 'checkbox',
+        componentProps: {
+          options: flagOptions,
+          defaultCollapsed: true
+        }
+      },
+      {
+        label: '文本',
+        field: 'props.text',
+        valueType: 'text',
+        componentProps: {
+          useSymbol: true
+        }
+      }
+    ],
+    // 组件样式
+    styles: [
+      {
+        label: '模块/状态',
+        field: 'part',
+        valueType: 'part'
+      },
+      {
+        valueType: 'dependency',
+        name: ['part'],
+        dependency: ({ part }) => {
+          return part?.name === 'scrollbar'
+            ? [
+                {
+                  label: '背景',
+                  field: 'background',
+                  valueType: 'background',
+                  componentProps: {
+                    onlyColor: true
+                  }
+                },
+                {
+                  label: '字体',
+                  field: 'text',
+                  valueType: 'font',
+                  componentProps: {
+                    hideAlign: true
+                  }
+                },
+                {
+                  label: '边框',
+                  field: 'border',
+                  valueType: 'border'
+                },
+                {
+                  label: '间距',
+                  field: 'gap',
+                  valueType: 'gap',
+                  hide: part?.name === 'main' && part?.state === 'focused'
+                },
+                {
+                  label: '内边距',
+                  field: 'padding',
+                  valueType: 'padding'
+                },
+                {
+                  label: '阴影',
+                  field: 'shadow',
+                  valueType: 'shadow'
+                }
+              ]
+            : [
+                {
+                  label: '背景',
+                  field: 'background',
+                  valueType: 'background',
+                  componentProps: {
+                    onlyColor: true
+                  }
+                },
+                {
+                  label: '边框',
+                  field: 'border',
+                  valueType: 'border'
+                },
+                {
+                  label: '内边距',
+                  field: 'padding',
+                  valueType: 'padding',
+                  componentProps: {
+                    allInOne: true
+                  }
+                }
+              ]
+        }
+      }
+    ]
+  }
+} as IComponentModelConfig

+ 224 - 0
src/renderer/src/lvgl-widgets/checkbox/style.json

@@ -0,0 +1,224 @@
+{
+  "widget": "lv_button",
+  "styleName": "defualt",
+  "part": [
+    {
+      "partName": "main",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "text": {
+              "color": "#ffffffff",
+              "family": "xx",
+              "size": 16,
+              "weight": "normal"
+            },
+            "border": {
+              "color": "#eeeeeeff",
+              "width": 0,
+              "radius": 6,
+              "side": ["all"]
+            },
+            "gap": {
+              "column": 2
+            },
+            "padding": {
+              "top": 0,
+              "right": 0,
+              "bottom": 0,
+              "left": 0
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        },
+        {
+          "state": "focused",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "text": {
+              "color": "#ffffffff",
+              "family": "xx",
+              "size": 16,
+              "weight": "normal"
+            },
+            "border": {
+              "color": "#eeeeeeff",
+              "width": 0,
+              "radius": 6,
+              "side": ["all"]
+            },
+            "gap": {
+              "column": 2
+            },
+            "padding": {
+              "top": 0,
+              "right": 0,
+              "bottom": 0,
+              "left": 0
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        },
+        {
+          "state": "disabled",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "text": {
+              "color": "#ffffffff",
+              "family": "xx",
+              "size": 16,
+              "weight": "normal"
+            },
+            "border": {
+              "color": "#eeeeeeff",
+              "width": 0,
+              "radius": 6,
+              "side": ["all"]
+            },
+            "gap": {
+              "column": 2
+            },
+            "padding": {
+              "top": 0,
+              "right": 0,
+              "bottom": 0,
+              "left": 0
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "indicator",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#000000ff"
+            },
+            "border": {
+              "color": "#2092f5ff",
+              "width": 2,
+              "radius": 6,
+              "side": ["all"]
+            },
+            "padding": {
+              "left": 3,
+              "right": 3,
+              "top": 3,
+              "bottom": 3
+            }
+          }
+        },
+        {
+          "state": "pressed",
+          "style": {
+            "background": {
+              "color": "#2092f5ff"
+            },
+            "border": {
+              "color": "#2092f5ff",
+              "width": 2,
+              "radius": 6,
+              "side": ["all"]
+            },
+            "padding": {
+              "left": 3,
+              "right": 3,
+              "top": 3,
+              "bottom": 3
+            }
+          }
+        },
+        {
+          "state": "checked",
+          "style": {
+            "background": {
+              "color": "#2092f5ff"
+            },
+            "border": {
+              "color": "#2092f5ff",
+              "width": 2,
+              "radius": 6,
+              "side": ["all"]
+            },
+            "padding": {
+              "left": 3,
+              "right": 3,
+              "top": 3,
+              "bottom": 3
+            }
+          }
+        },
+        {
+          "state": "checked&focused",
+          "style": {
+            "background": {
+              "color": "#2092f5ff"
+            },
+            "border": {
+              "color": "#2092f5ff",
+              "width": 2,
+              "radius": 6,
+              "side": ["all"]
+            },
+            "padding": {
+              "left": 3,
+              "right": 3,
+              "top": 3,
+              "bottom": 3
+            }
+          }
+        },
+        {
+          "state": "disabled",
+          "style": {
+            "background": {
+              "color": "#2092f5ff"
+            },
+            "border": {
+              "color": "#2092f5ff",
+              "width": 2,
+              "radius": 6,
+              "side": ["all"]
+            },
+            "padding": {
+              "left": 3,
+              "right": 3,
+              "top": 3,
+              "bottom": 3
+            }
+          }
+        }
+      ]
+    }
+  ]
+}

+ 12 - 8
src/renderer/src/lvgl-widgets/container/index.ts

@@ -83,36 +83,40 @@ export default {
         valueType: 'group',
         children: [
           {
-            label: 'X',
+            label: '',
             field: 'props.x',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'X' }
           },
           {
-            label: 'Y',
+            label: '',
             field: 'props.y',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'Y' }
           },
           {
-            label: '宽度',
+            label: '',
             field: 'props.width',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'W' }
           },
           {
-            label: '高度',
+            label: '',
             field: 'props.height',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'H' }
           }
         ]
       },

+ 60 - 0
src/renderer/src/lvgl-widgets/dropdown/Config.vue

@@ -0,0 +1,60 @@
+<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 border-box pr-12px items-center gap-4px mb-4px"
+        v-for="(_, index) in modelValue || []"
+        :key="index"
+      >
+        <div class="w-full relative group/item">
+          <el-input v-model="modelValue[index]" />
+          <div class="absolute top--7px right--4px cursor-pointer" @click="handleDeleteItem(index)">
+            <LuX size="14px" />
+          </div>
+        </div>
+      </div>
+    </el-scrollbar>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import { computed, type Ref } from 'vue'
+import { LuPlus, LuX } from 'vue-icons-plus/lu'
+
+const props = defineProps<{
+  values: Ref<string[]>
+}>()
+
+const modelValue = computed({
+  get() {
+    return props.values?.value
+  },
+  set(val) {
+    props.values.value = val
+  }
+})
+
+/**
+ * 删除一项
+ * @param rowIndex 行索引
+ * @param index 索引
+ */
+const handleDeleteItem = (index: number) => {
+  modelValue.value.splice(index, 1)
+}
+
+/**
+ * 添加一项
+ */
+const handleAddRow = () => {
+  modelValue.value?.push('option')
+}
+</script>
+
+<style scoped></style>

+ 20 - 8
src/renderer/src/lvgl-widgets/dropdown/index.ts

@@ -3,6 +3,7 @@ import icon from '../assets/icon/icon_7.svg'
 import { flagOptions } from '@/constants'
 import type { IComponentModelConfig } from '../type'
 import i18n from '@/locales'
+import Config from './Config.vue'
 import defaultStyle from './style.json'
 
 export default {
@@ -125,36 +126,40 @@ export default {
         valueType: 'group',
         children: [
           {
-            label: 'X',
+            label: '',
             field: 'props.x',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'X' }
           },
           {
-            label: 'Y',
+            label: '',
             field: 'props.y',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'Y' }
           },
           {
-            label: '宽度',
+            label: '',
             field: 'props.width',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'W' }
           },
           {
-            label: '高度',
+            label: '',
             field: 'props.height',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'H' }
           }
         ]
       },
@@ -180,6 +185,13 @@ export default {
         label: '绘制箭头图标',
         field: 'props.showArrow',
         valueType: 'switch'
+      },
+      {
+        label: '属性',
+        field: 'props.options',
+        render: (val) => {
+          return <Config values={val} />
+        }
       }
     ],
     // 组件样式

+ 12 - 8
src/renderer/src/lvgl-widgets/image-button/index.ts

@@ -79,36 +79,40 @@ export default {
         valueType: 'group',
         children: [
           {
-            label: 'X',
+            label: '',
             field: 'props.x',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'X' }
           },
           {
-            label: 'Y',
+            label: '',
             field: 'props.y',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'Y' }
           },
           {
-            label: '宽度',
+            label: '',
             field: 'props.width',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'W' }
           },
           {
-            label: '高度',
+            label: '',
             field: 'props.height',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'H' }
           }
         ]
       },

+ 12 - 8
src/renderer/src/lvgl-widgets/image/index.ts

@@ -52,36 +52,40 @@ export default {
         valueType: 'group',
         children: [
           {
-            label: 'X',
+            label: '',
             field: 'props.x',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'X' }
           },
           {
-            label: 'Y',
+            label: '',
             field: 'props.y',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'Y' }
           },
           {
-            label: '宽度',
+            label: '',
             field: 'props.width',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'W' }
           },
           {
-            label: '高度',
+            label: '',
             field: 'props.height',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'H' }
           }
         ]
       },

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

@@ -5,6 +5,7 @@ import Image from './image'
 import SpanGroup from './span-group/index'
 import Textarea from './textarea'
 import Dropdown from './dropdown/index'
+import Checkbox from './checkbox'
 
 import Container from './container'
 import Label from './label'
@@ -12,6 +13,8 @@ import Page from './page'
 import { IComponentModelConfig } from './type'
 
 export const ComponentArray = [
+  Page,
+
   Button,
   ImageButton,
   MatrixButton,
@@ -20,9 +23,9 @@ export const ComponentArray = [
   Label,
   Textarea,
   Dropdown,
+  Checkbox,
 
-  Container,
-  Page
+  Container
 ]
 
 const componentMap: { [key: string]: IComponentModelConfig } = ComponentArray.reduce((acc, cur) => {

+ 12 - 8
src/renderer/src/lvgl-widgets/label/index.ts

@@ -93,36 +93,40 @@ export default {
         valueType: 'group',
         children: [
           {
-            label: 'X',
+            label: '',
             field: 'props.x',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'X' }
           },
           {
-            label: 'Y',
+            label: '',
             field: 'props.y',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'Y' }
           },
           {
-            label: '宽度',
+            label: '',
             field: 'props.width',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'W' }
           },
           {
-            label: '高度',
+            label: '',
             field: 'props.height',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'H' }
           }
         ]
       },

+ 12 - 8
src/renderer/src/lvgl-widgets/span-group/index.tsx

@@ -91,36 +91,40 @@ export default {
         valueType: 'group',
         children: [
           {
-            label: 'X',
+            label: '',
             field: 'props.x',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'X' }
           },
           {
-            label: 'Y',
+            label: '',
             field: 'props.y',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'Y' }
           },
           {
-            label: '宽度',
+            label: '',
             field: 'props.width',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'W' }
           },
           {
-            label: '高度',
+            label: '',
             field: 'props.height',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'H' }
           }
         ]
       },

+ 12 - 8
src/renderer/src/lvgl-widgets/textarea/index.ts

@@ -109,36 +109,40 @@ export default {
         valueType: 'group',
         children: [
           {
-            label: 'X',
+            label: '',
             field: 'props.x',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'X' }
           },
           {
-            label: 'Y',
+            label: '',
             field: 'props.y',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'Y' }
           },
           {
-            label: '宽度',
+            label: '',
             field: 'props.width',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'W' }
           },
           {
-            label: '高度',
+            label: '',
             field: 'props.height',
             valueType: 'number',
             componentProps: {
               span: 12
-            }
+            },
+            slots: { prefix: 'H' }
           }
         ]
       },

+ 3 - 3
src/renderer/src/lvgl-widgets/type.d.ts

@@ -169,7 +169,7 @@ export interface IStyleConfig {
     // 字体大小
     size: number
     // 对齐方式 left center right
-    align: string
+    align?: string
     // 字重 正常/粗
     weight?: 'normal' | 'bold'
     // 斜体
@@ -228,9 +228,9 @@ export interface IStyleConfig {
   // 间距
   gap?: {
     // 行间距
-    row: number
+    row?: number
     // 字间距
-    column: number
+    column?: number
   }
   // 曲线
   curve?: {

+ 29 - 9
src/renderer/src/views/designer/config/property/CusFormItem.vue

@@ -1,12 +1,15 @@
 <template>
-  <el-col :span="schema?.componentProps?.span ?? 24">
+  <el-col v-if="!schema?.hide" :span="schema?.componentProps?.span ?? 24">
     <el-form-item v-if="isFormItem" :prop="schema.field" :label="schema.label">
       <!-- 文本 -->
-      <el-input
-        v-if="schema.valueType === 'text'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
+      <el-input v-if="schema.valueType === 'text'" v-model="value" v-bind="schema?.componentProps">
+        <template #prefix>
+          {{ schema?.slots?.prefix }}
+        </template>
+        <template #suffix>
+          {{ schema?.slots?.suffix }}
+        </template>
+      </el-input>
       <!-- 数字 -->
       <el-input-number
         v-if="schema.valueType === 'number'"
@@ -14,14 +17,28 @@
         controls-position="right"
         style="width: 100%"
         v-bind="schema?.componentProps"
-      />
+      >
+        <template #prefix>
+          {{ schema?.slots?.prefix }}
+        </template>
+        <template #suffix>
+          {{ schema?.slots?.suffix }}
+        </template>
+      </el-input-number>
       <!-- 选择框 -->
       <el-select-v2
         v-if="schema.valueType === 'select'"
         :options="schema?.componentProps?.options || []"
         v-model="value"
         v-bind="schema?.componentProps"
-      />
+      >
+        <template #prefix>
+          {{ schema?.slots?.prefix }}
+        </template>
+        <template #suffix>
+          {{ schema?.slots?.suffix }}
+        </template>
+      </el-select-v2>
       <!-- 开关 -->
       <el-switch v-if="schema.valueType === 'switch'" v-model="value" />
       <!-- 文本框 -->
@@ -44,7 +61,7 @@
       :model-value="schema.componentProps?.defaultCollapsed ? [] : [key]"
     >
       <el-collapse-item :title="schema.label" :name="key">
-        <el-card body-class="p-4px!">
+        <el-card body-class="p-4px! pt-12px!" class="mb-8px!">
           <el-row :gutter="12" class="px-4px">
             <CusFormItem
               v-for="(item, index) in schema.children"
@@ -261,4 +278,7 @@ const dependencyFormItems = computed(() => {
 .el-collapse {
   border-bottom: none !important;
 }
+.el-form-item {
+  margin-bottom: 8px;
+}
 </style>

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

@@ -42,7 +42,7 @@ import { BiFontColor } from 'vue-icons-plus/bi'
 import { useProjectStore } from '@/store/modules/project'
 
 defineProps<{
-  hideAlign: boolean
+  hideAlign?: boolean
 }>()
 const projectStore = useProjectStore()
 // 字体选项

+ 30 - 1
src/renderer/src/views/designer/config/property/components/StylePadding.vue

@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div v-if="!allInOne">
     <el-form-item label="Top" label-position="left" label-width="60px">
       <el-input-number
         placeholder="请输入"
@@ -33,11 +33,27 @@
       />
     </el-form-item>
   </div>
+  <div v-else>
+    <el-input-number
+      placeholder="请输入"
+      v-model="all"
+      controls-position="right"
+      style="width: 100%"
+    >
+      <template #prefix>
+        <span>P</span>
+      </template>
+    </el-input-number>
+  </div>
 </template>
 
 <script setup lang="ts">
 import { computed } from 'vue'
 
+defineProps<{
+  allInOne?: boolean
+}>()
+
 const modelValue = defineModel<{
   left: number
   top: number
@@ -84,6 +100,19 @@ const left = computed({
     }
   }
 })
+
+// all
+const all = computed({
+  get: () => modelValue.value?.left,
+  set: (val: number) => {
+    if (modelValue.value) {
+      modelValue.value.left = val
+      modelValue.value.right = val
+      modelValue.value.top = val
+      modelValue.value.bottom = val
+    }
+  }
+})
 </script>
 
 <style scoped></style>

+ 4 - 2
src/renderer/src/views/designer/sidebar/Libary.vue

@@ -89,9 +89,11 @@ function handleAdd(item: IComponentModelConfig) {
   )
   // 随机位置
   const r = Math.floor(Math.random() * 100)
+  const width = newWidget.props.width || 0
+  const height = newWidget.props.height || 0
   if (screen) {
-    newWidget.props.x = Math.round(screen.width / 2 - newWidget.props.width / 2 + r)
-    newWidget.props.y = Math.round(screen.height / 2 - newWidget.props.height / 2 + r)
+    newWidget.props.x = Math.round(screen.width / 2 - width / 2 + r)
+    newWidget.props.y = Math.round(screen.height / 2 - height / 2 + r)
   }
 
   projectStore.activePage?.children?.unshift(newWidget)

+ 8 - 0
src/renderer/src/views/designer/workspace/stage/Moveable.vue

@@ -35,6 +35,7 @@
     :horizontalGuidelines="horizontalGuidelines"
     :controlPadding="4"
     :edge="true"
+    :individualGroupableProps="individualGroupableProps"
     @render="onRender"
     @drag="onDrag"
     @resize="onResize"
@@ -138,6 +139,13 @@ useMutationObserver(
 // 临时层级
 const zIndex = ref()
 
+const individualGroupableProps = (element: HTMLElement | SVGElement | null | undefined) => {
+  console.log(element)
+  if (element?.getAttribute('widget-type')?.includes('lv_checkbox')) {
+    return { resizable: false }
+  }
+}
+
 // 拖拽开始
 const onDragStart = (e) => {
   zIndex.value = e.target.style.zIndex

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

@@ -5,6 +5,7 @@
     :style="getStyle"
     :class="schema.type === 'page' ? '' : 'ignore-click widget-node'"
     :widget-id="schema.id"
+    :widget-type="schema.type"
     @click="handleSelect"
     @contextmenu.stop="handleContextmenu"
   >