Browse Source

pref: 优化样式配置

jiaxing.liao 1 week ago
parent
commit
ef68c57e0f

+ 36 - 26
src/renderer/src/lvgl-widgets/button-matrix/ButtonMatrix.vue

@@ -34,7 +34,7 @@ const getProps = computed((): Record<string, CSSProperties> => {
   const styles = props.styles
   let mainStyle = styles.find((item) => item.state === props.state && item.part.name === 'main')
   let itemsStyle = styles.find((item) => item.state === props.state && item.part.name === 'items')
-
+  console.log(mainStyle, itemsStyle)
   // 从默认样式获取样式
   if (!mainStyle && props.state) {
     mainStyle = defaultStyle.part
@@ -57,41 +57,51 @@ const getProps = computed((): Record<string, CSSProperties> => {
       height: `${props.height}px`,
       backgroundColor: mainStyle?.background.color,
       borderRadius: `${mainStyle?.border.radius}px`,
+      borderStyle: 'solid',
       borderColor: 'transparent',
       borderWidth: `${mainStyle?.border.width}px`,
-      borderTopColor: ['all', 'top'].includes(mainStyle?.border?.side)
-        ? mainStyle?.border?.color
-        : 'transparent',
-      borderRightColor: ['all', 'right'].includes(mainStyle?.border?.side)
-        ? mainStyle?.border?.color
-        : 'transparent',
-      borderBottomColor: ['all', 'bottom'].includes(mainStyle?.border?.side)
-        ? mainStyle?.border?.color
-        : 'transparent',
-      borderLeftColor: ['all', 'left'].includes(mainStyle?.border?.side)
-        ? mainStyle?.border?.color
-        : 'transparent',
+      borderTopColor:
+        mainStyle?.border?.side?.includes('top') || mainStyle?.border?.side?.includes('all')
+          ? mainStyle?.border?.color
+          : 'transparent',
+      borderRightColor:
+        mainStyle?.border?.side?.includes('right') || mainStyle?.border?.side?.includes('all')
+          ? mainStyle?.border?.color
+          : 'transparent',
+      borderBottomColor:
+        mainStyle?.border?.side?.includes('bottom') || mainStyle?.border?.side?.includes('all')
+          ? mainStyle?.border?.color
+          : 'transparent',
+      borderLeftColor:
+        mainStyle?.border?.side?.includes('left') || mainStyle?.border?.side?.includes('all')
+          ? mainStyle?.border?.color
+          : 'transparent',
       gap: `${mainStyle?.gap?.row}px`,
       // 内边距
       padding: `${mainStyle?.padding.top}px ${mainStyle?.padding.right}px ${mainStyle?.padding.bottom}px ${mainStyle?.padding.left}px`
     },
     itemStyle: {
       backgroundColor: itemsStyle?.background.color,
+      borderStyle: 'solid',
       borderRadius: `${itemsStyle?.border.radius}px`,
       borderColor: 'transparent',
       borderWidth: `${itemsStyle?.border.width}px`,
-      borderTopColor: ['all', 'top'].includes(itemsStyle?.border?.side)
-        ? itemsStyle?.border?.color
-        : 'transparent',
-      borderRightColor: ['all', 'right'].includes(itemsStyle?.border?.side)
-        ? itemsStyle?.border?.color
-        : 'transparent',
-      borderBottomColor: ['all', 'bottom'].includes(itemsStyle?.border?.side)
-        ? itemsStyle?.border?.color
-        : 'transparent',
-      borderLeftColor: ['all', 'left'].includes(itemsStyle?.border?.side)
-        ? itemsStyle?.border?.color
-        : 'transparent',
+      borderTopColor:
+        itemsStyle?.border?.side.includes('top') || itemsStyle?.border?.side.includes('all')
+          ? itemsStyle?.border?.color
+          : 'transparent',
+      borderRightColor:
+        itemsStyle?.border?.side.includes('right') || itemsStyle?.border?.side.includes('all')
+          ? itemsStyle?.border?.color
+          : 'transparent',
+      borderBottomColor:
+        itemsStyle?.border?.side.includes('bottom') || itemsStyle?.border?.side.includes('all')
+          ? itemsStyle?.border?.color
+          : 'transparent',
+      borderLeftColor:
+        itemsStyle?.border?.side.includes('left') || itemsStyle?.border?.side.includes('all')
+          ? itemsStyle?.border?.color
+          : 'transparent',
       color: itemsStyle?.text?.color,
       fontSize: `${itemsStyle?.text.size}px`,
       fontWeight: itemsStyle?.text?.weight === 'bold' ? 'bold' : 'normal',
@@ -99,7 +109,7 @@ const getProps = computed((): Record<string, CSSProperties> => {
       textDecoration: itemsStyle?.text?.strike ? 'line-through' : itemsStyle?.text?.underline,
       /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
       boxShadow: itemsStyle?.box
-        ? `${itemsStyle.shadow?.offsetX}px ${itemsStyle.shadow?.offsetY}px ${itemsStyle.shadow?.width}px ${itemsStyle.shadow?.spread}px ${itemsStyle.shadow?.color}`
+        ? `${itemsStyle.shadow?.x}px ${itemsStyle.shadow?.y}px ${itemsStyle.shadow?.width}px ${itemsStyle.shadow?.spread}px ${itemsStyle.shadow?.color}`
         : 'none'
     },
     columnStyle: {

+ 1 - 1
src/renderer/src/lvgl-widgets/button-matrix/index.ts

@@ -52,7 +52,7 @@ export default {
           }
         },
         border: {
-          color: '#c8c8c8c8ff',
+          color: '#c8c8c8ff',
           width: 1,
           radius: 4,
           side: ['all']

+ 65 - 72
src/renderer/src/lvgl-widgets/button-matrix/style.json

@@ -9,31 +9,27 @@
           "state": "default",
           "style": {
             "background": {
-              "color": "#2195f6ff",
+              "color": "#ffffffff",
               "image": {
                 "imgId": "",
                 "color": ""
               }
             },
-            "text": {
-              "color": "#ffffffff",
-              "family": "xx",
-              "size": 16,
-              "weight": "normal",
-              "align": "center"
-            },
             "border": {
-              "color": "#000000ff",
-              "width": 0,
-              "radius": 5,
+              "color": "#c8c8c8ff",
+              "width": 1,
+              "radius": 4,
               "side": ["all"]
             },
-            "shadow": {
-              "color": "#2092f5ff",
-              "x": 0,
-              "y": 0,
-              "spread": 0,
-              "width": 0
+            "padding": {
+              "top": 12,
+              "right": 12,
+              "bottom": 12,
+              "left": 12
+            },
+            "gap": {
+              "row": 8,
+              "column": 8
             }
           }
         },
@@ -41,87 +37,84 @@
           "state": "focused",
           "style": {
             "background": {
-              "color": "#2195f6ff",
+              "color": "#ffffffff",
               "image": {
                 "imgId": "",
                 "color": ""
               }
             },
-            "text": {
-              "color": "#ffffffff",
-              "family": "xx",
-              "size": 16,
-              "weight": "normal",
-              "align": "center"
-            },
             "border": {
-              "color": "#000000ff",
-              "width": 0,
-              "radius": 5,
+              "color": "#c8c8c8ff",
+              "width": 1,
+              "radius": 4,
               "side": ["all"]
             },
-            "shadow": {
-              "color": "#2092f5ff",
-              "x": 0,
-              "y": 0,
-              "spread": 0,
-              "width": 0
+            "padding": {
+              "top": 12,
+              "right": 12,
+              "bottom": 12,
+              "left": 12
+            },
+            "gap": {
+              "row": 8,
+              "column": 8
             }
           }
         },
         {
-          "state": "pressed",
+          "state": "disabled",
           "style": {
             "background": {
-              "color": "#2195f6ff",
+              "color": "#ffffffff",
               "image": {
                 "imgId": "",
                 "color": ""
               }
             },
-            "text": {
-              "color": "#ffffffff",
-              "family": "xx",
-              "size": 16,
-              "weight": "normal",
-              "align": "center"
-            },
             "border": {
-              "color": "#000000ff",
-              "width": 0,
-              "radius": 5,
+              "color": "#c8c8c8ff",
+              "width": 1,
+              "radius": 4,
               "side": ["all"]
             },
-            "shadow": {
-              "color": "#2092f5ff",
-              "x": 0,
-              "y": 0,
-              "spread": 0,
-              "width": 0
+            "padding": {
+              "top": 12,
+              "right": 12,
+              "bottom": 12,
+              "left": 12
+            },
+            "gap": {
+              "row": 8,
+              "column": 8
             }
           }
-        },
+        }
+      ]
+    },
+    {
+      "partName": "items",
+      "state": [
         {
-          "state": "checked",
+          "state": "default",
           "style": {
             "background": {
-              "color": "#2195f6ff",
+              "color": "#2092f5ff",
               "image": {
                 "imgId": "",
-                "color": ""
+                "color": "#00000000"
               }
             },
             "text": {
-              "color": "#000000ff",
+              "color": "#ffffffff",
               "family": "xx",
               "size": 16,
-              "weight": "normal",
-              "align": "center"
+              "align": "center",
+              "weight": "normal"
             },
             "border": {
-              "color": "#000000ff",
-              "width": 0,
-              "radius": 5,
+              "color": "#c8c8c8ff",
+              "width": 1,
+              "radius": 4,
               "side": ["all"]
             },
             "shadow": {
@@ -134,26 +127,26 @@
           }
         },
         {
-          "state": "disabled",
+          "state": "pressed",
           "style": {
             "background": {
-              "color": "#2195f6ff",
+              "color": "#2092f5ff",
               "image": {
                 "imgId": "",
-                "color": ""
+                "color": "#00000000"
               }
             },
             "text": {
-              "color": "#000000ff",
+              "color": "#ffffffff",
               "family": "xx",
-              "size": 16,
-              "weight": "normal",
-              "align": "center"
+              "size": 14,
+              "align": "center",
+              "weight": "normal"
             },
             "border": {
-              "color": "#000000ff",
-              "width": 0,
-              "radius": 5,
+              "color": "#c8c8c8ff",
+              "width": 1,
+              "radius": 4,
               "side": ["all"]
             },
             "shadow": {

+ 1 - 1
src/renderer/src/lvgl-widgets/button/Button.vue

@@ -59,7 +59,7 @@ const getProps = computed((): CSSProperties => {
         : 'transparent',
     /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
     boxShadow: stateStyles?.shadow
-      ? `${stateStyles?.shadow?.offsetX}px ${stateStyles?.shadow?.offsetY}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
+      ? `${stateStyles?.shadow?.x}px ${stateStyles?.shadow?.y}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
       : 'none'
   }
 })

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

@@ -53,7 +53,7 @@ const getStyle = computed((): CSSProperties => {
     padding: `${stateStyles?.padding.top}px ${stateStyles?.padding.right}px ${stateStyles?.padding.bottom}px ${stateStyles?.padding.left}px`,
     /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
     boxShadow: stateStyles?.shadow
-      ? `${stateStyles?.shadow?.offsetX}px ${stateStyles?.shadow?.offsetY}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
+      ? `${stateStyles?.shadow?.x}px ${stateStyles?.shadow?.y}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
       : 'none'
   }
 })

+ 34 - 5
src/renderer/src/lvgl-widgets/image-button/ImageButton.vue

@@ -1,13 +1,15 @@
 <template>
   <div v-bind="getProps">
-    <img width="100%" height="100%" :src="defaultImg" alt="image btn" />
-    <div>{{ props.text }}</div>
+    <img width="100%" height="100%" class="absolute z-0" :src="src || defaultImg" alt="image btn" />
+    <div class="z-1">{{ props.text }}</div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { computed } from 'vue'
+import { computed, ref, watch } from 'vue'
 import defaultImg from '@/assets/default.png'
+import { getImageByPath } from '@/utils'
+import { useProjectStore } from '@/store/modules/project'
 
 const props = defineProps<{
   width: number
@@ -15,8 +17,34 @@ const props = defineProps<{
   text?: string
   styles: any
   state?: string
+  image: string
+  pressedImage: string
+  selectedImage: string
+  selectedPressedImage: string
 }>()
 
+const src = ref('')
+const projectStore = useProjectStore()
+
+watch(
+  () => props.image,
+  async (val) => {
+    if (val && projectStore.project) {
+      // 加载图片
+      const basePath = projectStore.project.meta.path
+      const imagePath = projectStore.project.resources.images.find((item) => item.id === val)?.path
+      const result = await getImageByPath(basePath + imagePath)
+      if (result?.base64) {
+        src.value = result.base64
+      } else {
+        src.value = ''
+      }
+    } else {
+      src.value = ''
+    }
+  }
+)
+
 const getProps = computed(() => {
   const styles = props.styles
   const stateStyles = styles.find((item) => item.state === props.state)
@@ -32,10 +60,11 @@ const getProps = computed(() => {
       display: 'flex',
       justifyContent: stateStyles?.text?.align || 'center',
       alignItems: 'center',
+      fontWeight: stateStyles?.text?.weight,
 
       /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
-      boxShodow: stateStyles?.shadow
-        ? `${stateStyles?.shadow?.offsetX}px ${stateStyles?.shadow?.offsetY}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
+      boxShadow: stateStyles?.shadow
+        ? `${stateStyles?.shadow?.x}px ${stateStyles?.shadow?.y}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
         : 'none'
     }
   }

+ 27 - 3
src/renderer/src/lvgl-widgets/image/Image.vue

@@ -1,17 +1,19 @@
 <template>
   <div :style="boxStyle">
-    <img :style="imgStyle" width="100%" height="100%" :src="imageSrc || defaultImg" alt="img" />
+    <img :style="imgStyle" width="100%" height="100%" :src="src || defaultImg" alt="img" />
   </div>
 </template>
 
 <script setup lang="ts">
-import { computed } from 'vue'
+import { computed, watch, ref } from 'vue'
+import { getImageByPath } from '@/utils'
+import { useProjectStore } from '@/store/modules/project'
 import defaultImg from '@/assets/default.png'
 
 const props = defineProps<{
   width: number
   height: number
-  imageSrc: string
+  image: string
   rotate: {
     x: number
     y: number
@@ -21,6 +23,28 @@ const props = defineProps<{
   state?: string
 }>()
 
+const src = ref('')
+const projectStore = useProjectStore()
+
+watch(
+  () => props.image,
+  async (val) => {
+    if (val && projectStore.project) {
+      // 加载图片
+      const basePath = projectStore.project.meta.path
+      const imagePath = projectStore.project.resources.images.find((item) => item.id === val)?.path
+      const result = await getImageByPath(basePath + imagePath)
+      if (result?.base64) {
+        src.value = result.base64
+      } else {
+        src.value = ''
+      }
+    } else {
+      src.value = ''
+    }
+  }
+)
+
 const boxStyle = computed(() => {
   const { width, height } = props
 

BIN
src/renderer/src/lvgl-widgets/image/default.png


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

@@ -27,7 +27,7 @@ export default {
       height: 100,
       addFlags: [],
       removeFlags: [],
-      imageSrc: '',
+      image: '',
       rotate: {
         x: 50,
         y: 50,
@@ -105,7 +105,7 @@ export default {
       },
       {
         label: '图片',
-        field: 'props.imageSrc',
+        field: 'props.image',
         valueType: 'image'
       },
       {

+ 2 - 2
src/renderer/src/lvgl-widgets/label/Label.vue

@@ -51,8 +51,8 @@ const getProps = computed(() => {
           ? stateStyles?.border?.color
           : 'transparent',
       /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
-      boxShodow: stateStyles?.shadow
-        ? `${stateStyles?.shadow?.offsetX}px ${stateStyles?.shadow?.offsetY}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
+      boxShadow: stateStyles?.shadow
+        ? `${stateStyles?.shadow?.x}px ${stateStyles?.shadow?.y}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
         : 'none'
     }
   }

+ 2 - 2
src/renderer/src/lvgl-widgets/span-group/SpanGroup.vue

@@ -63,8 +63,8 @@ const getProps = computed(() => {
           : 'transparent',
       // 阴影
       /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
-      boxShodow: stateStyles?.shadow
-        ? `${stateStyles?.shadow?.offsetX}px ${stateStyles?.shadow?.offsetY}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
+      boxShadow: stateStyles?.shadow
+        ? `${stateStyles?.shadow?.x}px ${stateStyles?.shadow?.y}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
         : 'none',
       // 内边距
       padding: `${stateStyles?.padding.top}px ${stateStyles?.padding.right}px ${stateStyles?.padding.bottom}px ${stateStyles?.padding.left}px`

+ 2 - 2
src/renderer/src/lvgl-widgets/textarea/Textarea.vue

@@ -56,8 +56,8 @@ const getProps = computed(() => {
           ? stateStyles?.border?.color
           : 'transparent',
       /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
-      boxShodow: stateStyles?.shadow
-        ? `${stateStyles?.shadow?.offsetX}px ${stateStyles?.shadow?.offsetY}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
+      boxShadow: stateStyles?.shadow
+        ? `${stateStyles?.shadow?.x}px ${stateStyles?.shadow?.y}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
         : 'none'
     }
   }

+ 1 - 1
src/renderer/src/store/modules/action.ts

@@ -402,7 +402,7 @@ export const useActionStore = defineStore('action', () => {
   )
   // 键盘事件 删除
   useKeyPress(
-    ['delete', 'backspace'],
+    ['delete'],
     debounce(() => {
       onDelete()
     }, 100)

+ 46 - 2
src/renderer/src/views/designer/config/property/components/StyleGap.vue

@@ -1,7 +1,51 @@
 <template>
-  <div>间距设置</div>
+  <div>
+    <el-form-item label="行间距" label-position="left" label-width="60px">
+      <el-input-number
+        placeholder="请输入"
+        v-model="row"
+        controls-position="right"
+        style="width: 100%"
+      />
+    </el-form-item>
+    <el-form-item label="列间距" label-position="left" label-width="60px">
+      <el-input-number
+        placeholder="请输入"
+        v-model="column"
+        controls-position="right"
+        style="width: 100%"
+      />
+    </el-form-item>
+  </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const modelValue = defineModel<{
+  column: number
+  row: number
+}>('modelValue')
+
+// column
+const column = computed({
+  get: () => modelValue.value?.column,
+  set: (val: number) => {
+    if (modelValue.value) {
+      modelValue.value.column = val
+    }
+  }
+})
+
+// row
+const row = computed({
+  get: () => modelValue.value?.row,
+  set: (val: number) => {
+    if (modelValue.value) {
+      modelValue.value.row = val
+    }
+  }
+})
+</script>
 
 <style scoped></style>

+ 56 - 2
src/renderer/src/views/designer/config/property/components/StyleLine.vue

@@ -1,7 +1,61 @@
 <template>
-  <div>线段设置</div>
+  <div>
+    <el-form-item label="颜色" label-position="left" label-width="60px">
+      <ColorPicker v-model:pureColor="color" format="hex8" picker-type="chrome" use-type="pure" />
+      <span class="text-text-active">{{ modelValue?.color }}</span>
+    </el-form-item>
+    <el-form-item label="宽度" label-position="left" label-width="60px">
+      <el-input-number
+        placeholder="请输入"
+        v-model="width"
+        controls-position="right"
+        style="width: 100%"
+      />
+    </el-form-item>
+    <el-form-item label="圆角" label-position="left" label-width="60px">
+      <el-switch v-model="radius" />
+    </el-form-item>
+  </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const modelValue = defineModel<{
+  color: string
+  width: number
+  radius: boolean
+}>('modelValue')
+
+// color
+const color = computed({
+  get: () => modelValue.value?.color,
+  set: (val: string) => {
+    if (modelValue.value) {
+      modelValue.value.color = val
+    }
+  }
+})
+
+// width
+const width = computed({
+  get: () => modelValue.value?.width,
+  set: (val: number) => {
+    if (modelValue.value) {
+      modelValue.value.width = val
+    }
+  }
+})
+
+// radius
+const radius = computed({
+  get: () => modelValue.value?.radius,
+  set: (val: boolean) => {
+    if (modelValue.value) {
+      modelValue.value.radius = val
+    }
+  }
+})
+</script>
 
 <style scoped></style>

+ 84 - 2
src/renderer/src/views/designer/config/property/components/StyleMargin.vue

@@ -1,7 +1,89 @@
 <template>
-  <div>外边距设置</div>
+  <div>
+    <el-form-item label="Top" label-position="left" label-width="60px">
+      <el-input-number
+        placeholder="请输入"
+        v-model="top"
+        controls-position="right"
+        style="width: 100%"
+      />
+    </el-form-item>
+    <el-form-item label="Right" label-position="left" label-width="60px">
+      <el-input-number
+        placeholder="请输入"
+        v-model="right"
+        controls-position="right"
+        style="width: 100%"
+      />
+    </el-form-item>
+    <el-form-item label="Bottom" label-position="left" label-width="60px">
+      <el-input-number
+        placeholder="请输入"
+        v-model="bottom"
+        controls-position="right"
+        style="width: 100%"
+      />
+    </el-form-item>
+    <el-form-item label="Left" label-position="left" label-width="60px">
+      <el-input-number
+        placeholder="请输入"
+        v-model="left"
+        controls-position="right"
+        style="width: 100%"
+      />
+    </el-form-item>
+  </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const modelValue = defineModel<{
+  left: number
+  top: number
+  right: number
+  bottom: number
+}>('modelValue')
+
+// top
+const top = computed({
+  get: () => modelValue.value?.top,
+  set: (val: number) => {
+    if (modelValue.value) {
+      modelValue.value.top = val
+    }
+  }
+})
+
+// right
+const right = computed({
+  get: () => modelValue.value?.right,
+  set: (val: number) => {
+    if (modelValue.value) {
+      modelValue.value.right = val
+    }
+  }
+})
+
+// bottom
+const bottom = computed({
+  get: () => modelValue.value?.bottom,
+  set: (val: number) => {
+    if (modelValue.value) {
+      modelValue.value.bottom = val
+    }
+  }
+})
+
+// left
+const left = computed({
+  get: () => modelValue.value?.left,
+  set: (val: number) => {
+    if (modelValue.value) {
+      modelValue.value.left = val
+    }
+  }
+})
+</script>
 
 <style scoped></style>

+ 84 - 2
src/renderer/src/views/designer/config/property/components/StylePadding.vue

@@ -1,7 +1,89 @@
 <template>
-  <div>外边距设置</div>
+  <div>
+    <el-form-item label="Top" label-position="left" label-width="60px">
+      <el-input-number
+        placeholder="请输入"
+        v-model="top"
+        controls-position="right"
+        style="width: 100%"
+      />
+    </el-form-item>
+    <el-form-item label="Right" label-position="left" label-width="60px">
+      <el-input-number
+        placeholder="请输入"
+        v-model="right"
+        controls-position="right"
+        style="width: 100%"
+      />
+    </el-form-item>
+    <el-form-item label="Bottom" label-position="left" label-width="60px">
+      <el-input-number
+        placeholder="请输入"
+        v-model="bottom"
+        controls-position="right"
+        style="width: 100%"
+      />
+    </el-form-item>
+    <el-form-item label="Left" label-position="left" label-width="60px">
+      <el-input-number
+        placeholder="请输入"
+        v-model="left"
+        controls-position="right"
+        style="width: 100%"
+      />
+    </el-form-item>
+  </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const modelValue = defineModel<{
+  left: number
+  top: number
+  right: number
+  bottom: number
+}>('modelValue')
+
+// top
+const top = computed({
+  get: () => modelValue.value?.top,
+  set: (val: number) => {
+    if (modelValue.value) {
+      modelValue.value.top = val
+    }
+  }
+})
+
+// right
+const right = computed({
+  get: () => modelValue.value?.right,
+  set: (val: number) => {
+    if (modelValue.value) {
+      modelValue.value.right = val
+    }
+  }
+})
+
+// bottom
+const bottom = computed({
+  get: () => modelValue.value?.bottom,
+  set: (val: number) => {
+    if (modelValue.value) {
+      modelValue.value.bottom = val
+    }
+  }
+})
+
+// left
+const left = computed({
+  get: () => modelValue.value?.left,
+  set: (val: number) => {
+    if (modelValue.value) {
+      modelValue.value.left = val
+    }
+  }
+})
+</script>
 
 <style scoped></style>