Kaynağa Gözat

feat: 添加列表控件

jiaxing.liao 3 hafta önce
ebeveyn
işleme
aaf098090f

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

@@ -43,9 +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']
     ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
     ElSlider: typeof import('element-plus/es')['ElSlider']
@@ -102,9 +100,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']
   const ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
   const ElSlider: typeof import('element-plus/es')['ElSlider']

+ 17 - 5
src/renderer/src/components/LocalImage/index.vue

@@ -4,21 +4,33 @@
 
 <script setup lang="ts">
 import { ref, watch } from 'vue'
+import { useProjectStore } from '@/store/modules/project'
 
 const props = defineProps<{
-  src: string
+  src?: string
+  id?: string
 }>()
 
 const imageSrc = ref('')
 const loading = ref(false)
+const projectStore = useProjectStore()
 
 // 读取文件
 const readFile = async () => {
-  if (!props.src) return
+  if (!props.src && !props.id) return
+
+  let src: string = props.src || ''
+
+  if (props.id) {
+    const path =
+      projectStore.project?.resources.images.find((item) => item.id === props.id)?.path || ''
+    src = projectStore.projectPath + path
+  }
+
   loading.value = true
   try {
-    const res = await window.electron.ipcRenderer.invoke('read-file', props.src, 'base64')
-    imageSrc.value = `data:image/${props.src.split('.')[1]};base64,` + res
+    const res = await window.electron.ipcRenderer.invoke('read-file', src, 'base64')
+    imageSrc.value = `data:image/${src.split('.')[1]};base64,` + res
   } catch (error) {
     console.log(error)
   } finally {
@@ -27,7 +39,7 @@ const readFile = async () => {
 }
 
 watch(
-  () => props.src,
+  () => [props.src, props.id],
   () => {
     readFile()
   },

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

@@ -99,5 +99,7 @@
   "slider": "Slider",
   "tabview": "Tabview",
   "tileview": "Tileview",
-  "table": "Table"
+  "table": "Table",
+  "list": "List",
+  "layout": "Layout"
 }

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

@@ -99,5 +99,7 @@
   "slider": "滑动条",
   "tabview": "选项卡",
   "tileview": "平铺视图",
-  "table": "表格"
+  "table": "表格",
+  "list": "列表",
+  "layout": "布局"
 }

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

@@ -6,11 +6,11 @@ import i18n from '@/locales'
 import defaultStyle from './style.json'
 
 export default {
-  label: i18n.global.t('container'),
+  label: i18n.global.t('layout'),
   icon,
   component: Container,
   key: 'lv_obj',
-  group: i18n.global.t('container'),
+  group: i18n.global.t('layout'),
   sort: 1,
   hasChildren: true,
   defaultStyle,

+ 3 - 1
src/renderer/src/lvgl-widgets/index.ts

@@ -15,6 +15,7 @@ import Container from './container'
 import Tabview from './tabview/index'
 import Tileview from './tileview/index'
 import Table from './table/index'
+import List from './list/index'
 
 import Page from './page'
 import { IComponentModelConfig } from './type'
@@ -38,7 +39,8 @@ export const ComponentArray = [
   Container,
   Tabview,
   Tileview,
-  Table
+  Table,
+  List
 ]
 
 const componentMap: { [key: string]: IComponentModelConfig } = ComponentArray.reduce((acc, cur) => {

+ 155 - 0
src/renderer/src/lvgl-widgets/list/Config.vue

@@ -0,0 +1,155 @@
+<template>
+  <div>
+    <el-card class="mb-12px" body-class="pr-0px!">
+      <template #header>
+        <div class="flex items-center justify-between">
+          <span>内容</span>
+          <span class="flex gap-4px">
+            <LuPlus class="cursor-pointer" @click="handleAdd" size="14px" />
+            <LuTrash2 class="cursor-pointer" @click="handleClear" size="14px" />
+          </span>
+        </div>
+      </template>
+      <el-scrollbar height="120px">
+        <div
+          v-for="(item, index) in props.values?.value || []"
+          :key="v4()"
+          class="flex items-center pr-12px"
+          @click="handleEdit(item)"
+        >
+          <span class="flex-1 truncate text-#00ff00 cursor-pointer">{{ item.text }}</span>
+          <LuTrash2 class="cursor-pointer shrink-0" @click.stop="handleDelete(index)" size="14px" />
+        </div>
+      </el-scrollbar>
+    </el-card>
+    <el-dialog v-model="dialogVisible" title="编辑" width="440px">
+      <el-form ref="formRef" :model="formData" label-position="left" label-width="60px">
+        <el-form-item label="类型">
+          <el-select v-model="formData.type">
+            <el-option label="Symbol" value="symbol"></el-option>
+            <el-option label="Image" value="img"></el-option>
+            <el-option label="Text" value="text"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="文本">
+          <el-input v-model="formData.text"></el-input>
+        </el-form-item>
+        <el-form-item v-if="formData.type === 'symbol'" label="图标">
+          <el-input readonly @click="handleShowSymbolModal">
+            <template #prefix>
+              <i class="lvgl-icon not-italic" v-html="getSymbol(formData.img_symbol)"></i>
+            </template>
+          </el-input>
+        </el-form-item>
+        <template v-if="formData.type === 'img'">
+          <el-form-item label="图片">
+            <ImageSelect v-model="formData.img_id" />
+          </el-form-item>
+          <el-row :gutter="12">
+            <el-col :span="12">
+              <el-form-item label="尺寸">
+                <el-input-number style="width: 100%" v-model="formData.img_width" :min="0">
+                  <template #prefix>宽度</template>
+                </el-input-number>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label-width="0">
+                <el-input-number style="width: 100%" v-model="formData.img_height" :min="0">
+                  <template #prefix>高度</template>
+                </el-input-number>
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </template>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="dialogVisible = false">确定</el-button>
+      </template>
+    </el-dialog>
+    <SymbolSelectModal ref="symbolModalRef" @select="handleSelectSymbol" />
+  </div>
+</template>
+
+<script setup lang="ts">
+import type { ListItem } from './data'
+
+import { type Ref, ref } from 'vue'
+import { LuPlus, LuTrash2 } from 'vue-icons-plus/lu'
+import { v4 } from 'uuid'
+import { symbols } from '@/constants'
+
+import ImageSelect from '@/views/designer/config/property/components/ImageSelect.vue'
+import SymbolSelectModal from '@/views/designer/config/property/components/SymbolSelectModal.vue'
+
+const props = defineProps<{
+  values: Ref<ListItem[]>
+}>()
+
+const dialogVisible = ref(false)
+const formData = ref<ListItem>({
+  text: '',
+  img_id: '',
+  type: 'symbol',
+  img_width: 0,
+  img_height: 0,
+  img_symbol: ''
+})
+const symbolModalRef = ref<InstanceType<typeof SymbolSelectModal>>()
+
+/**
+ * 添加文本项
+ */
+const handleAdd = () => {
+  const list =
+    props.values?.value
+      ?.map((item) => Number(item.text.split('_')?.[1]))
+      ?.filter((n) => !Number.isNaN(n)) || []
+  const index = list.length ? Math.max(...list) + 1 : 1
+
+  props.values?.value?.push({
+    type: 'symbol',
+    text: `save_${index}`,
+    img_id: '',
+    img_width: 20,
+    img_height: 20,
+    img_symbol: 'LV_SYMBOL_SAVE'
+  })
+}
+
+const getSymbol = (symbol: string) => {
+  return symbols.find((item) => item.label === symbol)?.value
+}
+
+/**
+ * 删除文本项
+ */
+const handleDelete = (index: number) => {
+  props.values?.value?.splice(index, 1)
+}
+
+/**
+ * 清除文本项
+ */
+const handleClear = () => {
+  props.values.value = []
+}
+
+const handleShowSymbolModal = () => {
+  symbolModalRef.value?.open()
+}
+
+/**
+ * 选择图标
+ */
+const handleSelectSymbol = (val: string) => {
+  formData.value.img_symbol = val
+}
+
+const handleEdit = (record: ListItem) => {
+  formData.value = record
+  dialogVisible.value = true
+}
+</script>
+
+<style scoped></style>

+ 119 - 0
src/renderer/src/lvgl-widgets/list/List.vue

@@ -0,0 +1,119 @@
+<template>
+  <div
+    ref="containerRef"
+    :style="{ ...(styleMap?.mainStyle || {}) }"
+    class="lvgl_list relative overflow-hidden box-border"
+  >
+    <div ref="contentRef" class="w-full h-auto">
+      <div
+        class="flex items-center gap-4px min-h-25px"
+        v-for="(item, index) in items"
+        :key="index"
+        :style="item.type === 'text' ? styleMap?.itemTextStyle : styleMap?.buttonStyle"
+      >
+        <span v-if="item?.type === 'symbol'">
+          <i class="lvgl-icon not-italic" v-html="getSymbol(item.img_symbol)"></i>
+        </span>
+        <LocalImage
+          v-if="item?.type === 'img'"
+          class="h-70px"
+          :id="item.img_id"
+          :style="{ width: item.img_width + 'px', height: item.img_height + 'px' }"
+        />
+        <span>{{ item.text }}</span>
+      </div>
+    </div>
+
+    <!-- 滚动条 -->
+    <!-- <div
+      class="h-scrollbar absolute bottom-6px h-4px"
+      :style="{ ...(styleMap?.scrollbarStyle || {}), ...barStyle.hStyle }"
+    ></div> -->
+    <div
+      class="v-scrollbar absolute right-6px w-4px"
+      :style="{ ...(styleMap?.scrollbarStyle || {}), ...barStyle.vStyle }"
+    ></div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import { useWidgetStyle } from '../hooks/useWidgetStyle'
+import { symbols } from '@/constants'
+import { useResizeObserver } from 'vue-hooks-plus'
+import { LocalImage } from '@/components'
+
+import type { ListItem } from './data'
+
+const props = defineProps<{
+  width: number
+  height: number
+  styles: any
+  part?: string
+  state?: string
+  items: ListItem[]
+}>()
+
+const containerRef = ref<HTMLDivElement>()
+const contentRef = ref<HTMLDivElement>()
+const conHeight = ref(0)
+const bodyHeight = ref(0)
+
+useResizeObserver(containerRef, (entries) => {
+  const entry = entries[0]
+  const { height: _h } = entry.contentRect
+  conHeight.value = _h
+})
+useResizeObserver(contentRef, (entries) => {
+  const entry = entries[0]
+  const { height: _h } = entry.contentRect
+  bodyHeight.value = _h
+})
+
+const styleMap = useWidgetStyle({
+  widget: 'lv_list',
+  props
+})
+
+const getSymbol = (key: string) => symbols.find((item) => item.label === key)?.value
+
+const barStyle = computed(() => {
+  const { styles, part, state } = props
+
+  let style = styles.find((item) => item.part?.name === part && item.part?.state === state)
+
+  if (!style) {
+    styles.find((item) => item.part?.name === part && item.part?.state === 'default')
+  }
+
+  const gapTop = (style?.border?.width || 0) + (style?.padding?.top || 0)
+  const gapBottom = (style?.border?.width || 0) + (style?.padding?.bottom || 0)
+
+  // const viewportHeight = conHeight.value || 0 // 可视区域高度
+  // const contentHeight = containerRef.value?.scrollHeight || 0 // 内容总高度
+  // const trackHeight = viewportHeight - gapTop - gapBottom // 滚动条轨道高度
+
+  // const thumbHeight = Math.max(
+  //   20, // 最小高度(通常浏览器设为20px)
+  //   (viewportHeight / contentHeight) * trackHeight
+  // )
+
+  return {
+    gapTop,
+    gapBottom,
+    vStyle: {
+      display: bodyHeight.value > conHeight.value ? '' : 'none',
+      top: `${gapTop}px`,
+      height: `${80}px`
+    }
+  }
+})
+</script>
+
+<style lang="less" scoped>
+.lvgl_list {
+  &::-webkit-scrollbar {
+    display: none;
+  }
+}
+</style>

+ 17 - 0
src/renderer/src/lvgl-widgets/list/data.ts

@@ -0,0 +1,17 @@
+/**
+ * 列表项类型
+ */
+export type ListItem = {
+  // 类型
+  type: 'symbol' | 'text' | 'img'
+  // 文字内容
+  text: string
+  // 图片ID
+  img_id: string
+  // 图片宽度
+  img_width: number
+  // 图片高度
+  img_height: number
+  // symbol
+  img_symbol: string
+}

+ 340 - 0
src/renderer/src/lvgl-widgets/list/index.tsx

@@ -0,0 +1,340 @@
+import List from './List.vue'
+import icon from '../assets/icon/icon_16list.svg'
+import type { IComponentModelConfig } from '../type'
+import i18n from '@/locales'
+import { flagOptions } from '@/constants'
+import defaultStyle from './style.json'
+import Config from './Config.vue'
+
+export default {
+  label: i18n.global.t('list'),
+  icon,
+  component: List,
+  key: 'lv_list',
+  group: i18n.global.t('layout'),
+  sort: 1,
+  defaultStyle,
+  parts: [
+    {
+      name: 'main',
+      stateList: ['default', 'focused', 'disabled']
+    },
+    {
+      name: 'scrollbar',
+      stateList: ['default']
+    },
+    {
+      name: 'button',
+      stateList: ['default', 'pressed', 'focused']
+    },
+    {
+      name: 'itemText',
+      stateList: ['default']
+    }
+  ],
+  defaultSchema: {
+    name: 'list',
+    props: {
+      x: 0,
+      y: 0,
+      width: 120,
+      height: 120,
+      addFlags: [],
+      removeFlags: [],
+      scrollbar: 'off',
+      items: [
+        {
+          type: 'symbol',
+          text: 'save_1',
+          img_id: '',
+          img_width: 20,
+          img_height: 20,
+          img_symbol: 'LV_SYMBOL_SAVE'
+        },
+        {
+          type: 'symbol',
+          text: 'save_2',
+          img_id: '',
+          img_width: 20,
+          img_height: 20,
+          img_symbol: 'LV_SYMBOL_SAVE'
+        },
+        {
+          type: 'symbol',
+          text: 'save_3',
+          img_id: '',
+          img_width: 20,
+          img_height: 20,
+          img_symbol: 'LV_SYMBOL_SAVE'
+        }
+      ]
+    },
+    styles: [
+      {
+        part: {
+          name: 'main',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffffff'
+        },
+        border: {
+          color: '#eeeeeeff',
+          width: 1,
+          radius: 3,
+          side: ['all']
+        },
+        padding: {
+          top: 4,
+          right: 4,
+          bottom: 4,
+          left: 4
+        },
+        shadow: {
+          color: '#2092f5ff',
+          x: 0,
+          y: 0,
+          spread: 0,
+          width: 0
+        }
+      },
+      {
+        part: {
+          name: 'scrollbar',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffffff'
+        },
+        border: {
+          radius: 3
+        }
+      },
+      {
+        part: {
+          name: 'button',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffffff'
+        },
+        text: {
+          color: '#000000ff',
+          size: 12,
+          family: 'xx',
+          weight: 'normal'
+        },
+        border: {
+          color: '#eeeeeeff',
+          width: 0,
+          radius: 3,
+          side: ['all']
+        }
+      },
+      {
+        part: {
+          name: 'itemText',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffffff'
+        },
+        text: {
+          color: '#000000ff',
+          size: 12,
+          family: 'xx',
+          weight: 'normal'
+        },
+        border: {
+          color: '#eeeeeeff',
+          width: 0,
+          radius: 3,
+          side: ['all']
+        },
+        padding: {
+          top: 4,
+          right: 4,
+          bottom: 4,
+          left: 4
+        }
+      }
+    ]
+  },
+  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.items',
+        valueType: '',
+        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: 'padding',
+                  valueType: 'padding'
+                },
+                {
+                  label: '阴影',
+                  field: 'shadow',
+                  valueType: 'shadow'
+                }
+              ]
+            : part?.name === 'scrollbar'
+              ? [
+                  {
+                    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'
+                  },
+                  {
+                    label: '内边距',
+                    field: 'padding',
+                    valueType: 'padding'
+                  }
+                ]
+        }
+      }
+    ]
+  }
+} as IComponentModelConfig

+ 198 - 0
src/renderer/src/lvgl-widgets/list/style.json

@@ -0,0 +1,198 @@
+{
+  "widget": "lv_list",
+  "styleName": "defualt",
+  "part": [
+    {
+      "partName": "main",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "border": {
+              "color": "#eeeeeeff",
+              "width": 1,
+              "radius": 3,
+              "side": ["all"]
+            },
+            "padding": {
+              "top": 4,
+              "right": 4,
+              "bottom": 4,
+              "left": 4
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        },
+        {
+          "state": "focused",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "border": {
+              "color": "#eeeeeeff",
+              "width": 1,
+              "radius": 3,
+              "side": ["all"]
+            },
+            "padding": {
+              "top": 4,
+              "right": 4,
+              "bottom": 4,
+              "left": 4
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        },
+        {
+          "state": "disabled",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "border": {
+              "color": "#eeeeeeff",
+              "width": 1,
+              "radius": 3,
+              "side": ["all"]
+            },
+            "padding": {
+              "top": 4,
+              "right": 4,
+              "bottom": 4,
+              "left": 4
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "scrollbar",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "border": {
+              "radius": 3
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "button",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "text": {
+              "color": "#000000ff",
+              "size": 12,
+              "family": "xx",
+              "weight": "normal"
+            },
+            "border": {
+              "color": "#eeeeeeff",
+              "width": 0,
+              "radius": 3,
+              "side": ["all"]
+            }
+          }
+        },
+        {
+          "state": "pressed",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "text": {
+              "color": "#000000ff",
+              "size": 12,
+              "family": "xx",
+              "weight": "normal"
+            },
+            "border": {
+              "color": "#eeeeeeff",
+              "width": 0,
+              "radius": 3,
+              "side": ["all"]
+            }
+          }
+        },
+        {
+          "state": "focused",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "text": {
+              "color": "#000000ff",
+              "size": 12,
+              "family": "xx",
+              "weight": "normal"
+            },
+            "border": {
+              "color": "#eeeeeeff",
+              "width": 0,
+              "radius": 3,
+              "side": ["all"]
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "button",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "text": {
+              "color": "#000000ff",
+              "size": 12,
+              "family": "xx",
+              "weight": "normal"
+            },
+            "border": {
+              "color": "#eeeeeeff",
+              "width": 0,
+              "radius": 3,
+              "side": ["all"]
+            }
+          }
+        }
+      ]
+    }
+  ]
+}

+ 1 - 1
src/renderer/src/lvgl-widgets/page/index.ts

@@ -7,7 +7,7 @@ export default {
   label: i18n.global.t('page'),
   component: Page,
   key: 'page',
-  group: i18n.global.t('container'),
+  group: i18n.global.t('layout'),
   hideLibary: true,
   hasChildren: true,
   parts: [

+ 1 - 1
src/renderer/src/lvgl-widgets/table/index.tsx

@@ -11,7 +11,7 @@ export default {
   icon,
   component: Table,
   key: 'lv_table',
-  group: i18n.global.t('container'),
+  group: i18n.global.t('layout'),
   sort: 1,
   hasChildren: false,
   defaultStyle,

+ 1 - 1
src/renderer/src/lvgl-widgets/tabview/index.tsx

@@ -11,7 +11,7 @@ export default {
   icon,
   component: Tabview,
   key: 'lv_tabview',
-  group: i18n.global.t('container'),
+  group: i18n.global.t('layout'),
   sort: 1,
   hasChildren: true,
   defaultStyle,

+ 6 - 6
src/renderer/src/lvgl-widgets/tileview/Tileview.vue

@@ -1,12 +1,12 @@
 <template>
   <div :style="styleMap?.mainStyle" class="relative overflow-hidden">
     <div
-      class="v-scrollbar absolute bottom-6px h-4px"
-      :style="{ ...(styleMap?.scrollbarStyle || {}), ...barStyle.vStyle }"
+      class="h-scrollbar absolute bottom-6px h-4px"
+      :style="{ ...(styleMap?.scrollbarStyle || {}), ...barStyle.hStyle }"
     ></div>
     <div
-      class="h-scrollbar absolute right-6px w-4px"
-      :style="{ ...(styleMap?.scrollbarStyle || {}), ...barStyle.hStyle }"
+      class="v-scrollbar absolute right-6px w-4px"
+      :style="{ ...(styleMap?.scrollbarStyle || {}), ...barStyle.vStyle }"
     ></div>
   </div>
 </template>
@@ -43,12 +43,12 @@ const barStyle = computed(() => {
   const itemHeight = (height - 20) / (maxRow + 1)
   const itemWidth = (width - 20) / (maxCol + 1)
   return {
-    hStyle: {
+    vStyle: {
       display: maxRow > 0 ? '' : 'none',
       height: `${itemHeight}px`,
       top: `${10 + itemHeight * curRow}px`
     },
-    vStyle: {
+    hStyle: {
       display: maxCol > 0 ? '' : 'none',
       width: `${itemWidth}px`,
       left: `${10 + itemWidth * curCol}px`

+ 1 - 1
src/renderer/src/lvgl-widgets/tileview/index.tsx

@@ -11,7 +11,7 @@ export default {
   icon,
   component: Tileview,
   key: 'lv_tileview',
-  group: i18n.global.t('container'),
+  group: i18n.global.t('layout'),
   sort: 1,
   hasChildren: true,
   defaultStyle,

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

@@ -29,7 +29,7 @@ const modelValue = defineModel<{
   lineHeight: number
 }>('modelValue')
 
-const props = defineProps<{
+defineProps<{
   hideLetterSpacing?: boolean
 }>()
 
@@ -48,6 +48,7 @@ const lineHeight = computed({
   get: () => modelValue.value?.lineHeight,
   set: (val: number) => {
     if (modelValue.value) {
+      
       modelValue.value.lineHeight = val
     }
   }