Procházet zdrojové kódy

feat: 定义组件相关、修改图片资源配置

jiaxing.liao před 3 měsíci
rodič
revize
da81116846

+ 2 - 1
package.json

@@ -41,7 +41,8 @@
     "vue-hooks-plus": "^2.4.1",
     "vue-i18n": "^11.1.12",
     "vue-icons-plus": "^0.1.8",
-    "vue-router": "^4.6.3"
+    "vue-router": "^4.6.3",
+    "vue3-colorpicker": "^2.3.0"
   },
   "devDependencies": {
     "@electron-toolkit/eslint-config-prettier": "3.0.0",

+ 64 - 0
pnpm-lock.yaml

@@ -71,6 +71,9 @@ importers:
       vue-router:
         specifier: ^4.6.3
         version: 4.6.3(vue@3.5.22(typescript@5.9.3))
+      vue3-colorpicker:
+        specifier: ^2.3.0
+        version: 2.3.0(@aesoper/normal-utils@0.1.5)(@popperjs/core@2.11.8)(@vueuse/core@14.0.0(vue@3.5.22(typescript@5.9.3)))(gradient-parser@1.1.1)(lodash-es@4.17.21)(tinycolor2@1.6.0)(vue-types@4.2.1(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3))
     devDependencies:
       '@electron-toolkit/eslint-config-prettier':
         specifier: 3.0.0
@@ -147,6 +150,9 @@ packages:
   7zip-bin@5.2.0:
     resolution: {integrity: sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==}
 
+  '@aesoper/normal-utils@0.1.5':
+    resolution: {integrity: sha512-LFF/6y6h5mfwhnJaWqqxuC8zzDaHCG62kMRkd8xhDtq62TQj9dM17A9DhE87W7DhiARJsHLgcina/9P4eNCN1w==}
+
   '@antfu/install-pkg@1.1.0':
     resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
 
@@ -703,6 +709,9 @@ packages:
   '@polka/url@1.0.0-next.29':
     resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
 
+  '@popperjs/core@2.11.8':
+    resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
+
   '@quansync/fs@0.1.5':
     resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==}
 
@@ -2083,6 +2092,10 @@ packages:
   graceful-fs@4.2.11:
     resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
 
+  gradient-parser@1.1.1:
+    resolution: {integrity: sha512-Hu0YfNU+38EsTmnUfLXUKFMXq9yz7htGYpF4x+dlbBhUCvIvzLt0yVLT/gJRmvLKFJdqNFrz4eKkIUjIXSr7Tw==}
+    engines: {node: '>=0.10.0'}
+
   graphemer@1.4.0:
     resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
 
@@ -2238,6 +2251,10 @@ packages:
     resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
     engines: {node: '>=0.12.0'}
 
+  is-plain-object@5.0.0:
+    resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
+    engines: {node: '>=0.10.0'}
+
   is-unicode-supported@0.1.0:
     resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
     engines: {node: '>=10'}
@@ -3206,6 +3223,9 @@ packages:
     resolution: {integrity: sha512-Z8uvtdWIlFn1GWy0HW5FhZ8VDryZwoJUdnjZU25C7/PBOltLIn1uv+WF3rVq6S1761YbsmbZYRP/l0ZJBCkvrw==}
     hasBin: true
 
+  tinycolor2@1.6.0:
+    resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
+
   tinyexec@1.0.2:
     resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
     engines: {node: '>=18'}
@@ -3468,6 +3488,24 @@ packages:
     peerDependencies:
       typescript: '>=5.0.0'
 
+  vue-types@4.2.1:
+    resolution: {integrity: sha512-DNQZmJuOvovLUIp0BENRkdnZHbI0V4e2mNvjAZOAXKD56YGvRchtUYOXA/XqTxdv7Ng5SJLZqRKRpAhm5NLaPQ==}
+    engines: {node: '>=12.16.0'}
+    peerDependencies:
+      vue: ^2.0.0 || ^3.0.0
+
+  vue3-colorpicker@2.3.0:
+    resolution: {integrity: sha512-e3lLmBcy7mkRrNQVeUny1DjOd6E11L8H5ok5Bx4MdXmrG+RzyacRF7KkhrEWmRYPhKAsaoUrWsFkmpPAaYnE5A==}
+    peerDependencies:
+      '@aesoper/normal-utils': ^0.1.5
+      '@popperjs/core': ^2.11.8
+      '@vueuse/core': ^10.1.2
+      gradient-parser: ^1.0.2
+      lodash-es: ^4.17.21
+      tinycolor2: ^1.4.2
+      vue: ^3.2.6
+      vue-types: ^4.1.0
+
   vue@3.5.22:
     resolution: {integrity: sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==}
     peerDependencies:
@@ -3591,6 +3629,8 @@ snapshots:
 
   7zip-bin@5.2.0: {}
 
+  '@aesoper/normal-utils@0.1.5': {}
+
   '@antfu/install-pkg@1.1.0':
     dependencies:
       package-manager-detector: 1.5.0
@@ -4236,6 +4276,8 @@ snapshots:
 
   '@polka/url@1.0.0-next.29': {}
 
+  '@popperjs/core@2.11.8': {}
+
   '@quansync/fs@0.1.5':
     dependencies:
       quansync: 0.2.11
@@ -5921,6 +5963,8 @@ snapshots:
 
   graceful-fs@4.2.11: {}
 
+  gradient-parser@1.1.1: {}
+
   graphemer@1.4.0: {}
 
   gzip-size@6.0.0:
@@ -6059,6 +6103,8 @@ snapshots:
 
   is-number@7.0.0: {}
 
+  is-plain-object@5.0.0: {}
+
   is-unicode-supported@0.1.0: {}
 
   is-what@3.14.1: {}
@@ -7145,6 +7191,8 @@ snapshots:
       minimatch: 3.1.2
       resolve-from: 2.0.0
 
+  tinycolor2@1.6.0: {}
+
   tinyexec@1.0.2: {}
 
   tinyglobby@0.2.15:
@@ -7407,6 +7455,22 @@ snapshots:
       '@vue/language-core': 3.1.1(typescript@5.9.3)
       typescript: 5.9.3
 
+  vue-types@4.2.1(vue@3.5.22(typescript@5.9.3)):
+    dependencies:
+      is-plain-object: 5.0.0
+      vue: 3.5.22(typescript@5.9.3)
+
+  vue3-colorpicker@2.3.0(@aesoper/normal-utils@0.1.5)(@popperjs/core@2.11.8)(@vueuse/core@14.0.0(vue@3.5.22(typescript@5.9.3)))(gradient-parser@1.1.1)(lodash-es@4.17.21)(tinycolor2@1.6.0)(vue-types@4.2.1(vue@3.5.22(typescript@5.9.3)))(vue@3.5.22(typescript@5.9.3)):
+    dependencies:
+      '@aesoper/normal-utils': 0.1.5
+      '@popperjs/core': 2.11.8
+      '@vueuse/core': 14.0.0(vue@3.5.22(typescript@5.9.3))
+      gradient-parser: 1.1.1
+      lodash-es: 4.17.21
+      tinycolor2: 1.6.0
+      vue: 3.5.22(typescript@5.9.3)
+      vue-types: 4.2.1(vue@3.5.22(typescript@5.9.3))
+
   vue@3.5.22(typescript@5.9.3):
     dependencies:
       '@vue/compiler-dom': 3.5.22

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

@@ -13,6 +13,7 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     CodeEditor: typeof import('./src/components/CodeEditor/index.vue')['default']
+    ColorPicker: typeof import('./src/components/ColorPicker/index.vue')['default']
     EditorModal: typeof import('./src/components/EditorModal/index.vue')['default']
     ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
     ElAutoComplete: typeof import('element-plus/es')['ElAutoComplete']
@@ -83,6 +84,7 @@ declare module 'vue' {
 // For TSX support
 declare global {
   const CodeEditor: typeof import('./src/components/CodeEditor/index.vue')['default']
+  const ColorPicker: typeof import('./src/components/ColorPicker/index.vue')['default']
   const EditorModal: typeof import('./src/components/EditorModal/index.vue')['default']
   const ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
   const ElAutoComplete: typeof import('element-plus/es')['ElAutoComplete']

+ 30 - 0
src/renderer/src/components/ColorPicker/index.vue

@@ -0,0 +1,30 @@
+<template>
+  <ColorPicker
+    picker-type="chrome"
+    shape="square"
+    :is-widget="false"
+    v-bind="{
+      ...$attrs,
+      ...props
+    }"
+    :lang="config.lang"
+    :theme="config.theme"
+  />
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import { ColorPicker, type ColorPickerProps } from 'vue3-colorpicker'
+import 'vue3-colorpicker/style.css'
+import { useAppStore } from '@/store/modules/app'
+
+const props = defineProps<ColorPickerProps>()
+
+const appStore = useAppStore()
+const config = computed(() => {
+  return {
+    lang: appStore.lang === 'zh_CN' ? 'ZH-cn' : 'En',
+    theme: appStore.theme === 'dark' ? 'black' : 'white'
+  }
+})
+</script>

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

@@ -79,5 +79,8 @@
   "projectNotExist": "Project Not Exist",
   "saveSuccess": "Save Success",
   "screen": "Screen",
-  "canvasSize": "Canvas Size:"
+  "canvasSize": "Canvas Size:",
+  "button": "Button",
+  "basic": "Basic",
+  "container": "Container"
 }

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

@@ -79,5 +79,8 @@
   "projectNotExist": "项目不存在",
   "saveSuccess": "保存成功",
   "screen": "屏幕",
-  "canvasSize": "画布尺寸:"
+  "canvasSize": "画布尺寸:",
+  "button": "按钮",
+  "basic": "基础",
+  "container": "容器"
 }

+ 15 - 0
src/renderer/src/lvgl-widgets/button/Button.vue

@@ -0,0 +1,15 @@
+<template>
+  <div></div>
+</template>
+
+<script setup lang="ts">
+defineProps<{
+  width: number
+  height: number
+  text: string
+  styles: any
+  state: string
+}>()
+</script>
+
+<style scoped></style>

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 4 - 0
src/renderer/src/lvgl-widgets/button/button.svg


+ 251 - 0
src/renderer/src/lvgl-widgets/button/index.ts

@@ -0,0 +1,251 @@
+import LvButton from './Button.vue'
+import icon from './button.svg'
+import { flagOptions } from '@/constants'
+import type { IComponentModelConfig } from '../type'
+import i18n from '@/locales'
+
+export default {
+  label: i18n.global.t('button'),
+  icon,
+  componentName: 'LvButton',
+  component: LvButton,
+  type: 'button',
+  group: i18n.global.t('basic'),
+  sort: 1,
+  parts: [
+    {
+      name: 'main',
+      stateList: ['default', 'focused', 'pressed', 'checked', 'disabled']
+    }
+  ],
+  defaultSchema: {
+    props: {
+      name: 'button',
+      type: 'button',
+      x: 0,
+      y: 0,
+      width: 100,
+      height: 50,
+      hidden: false,
+      locked: false,
+      flags: [],
+      scrolling: [],
+      states: [],
+      text: 'Button'
+    },
+    styles: [
+      {
+        part: {
+          name: 'main',
+          state: 'default'
+        },
+        background: {
+          color: '#2195f6',
+          alpha: 255,
+          image: {
+            src: '',
+            alpha: 255,
+            colorFormat: '',
+            colorDepth: '',
+            color: ''
+          }
+        },
+        text: {
+          color: '#ffffff',
+          family: '',
+          size: 14,
+          weight: 400,
+          align: 'center'
+        },
+        border: {
+          color: '#000000',
+          width: 1,
+          radius: 5,
+          style: 'solid',
+          side: 'all'
+        },
+        padding: {
+          top: 0,
+          right: 0,
+          bottom: 0,
+          left: 0
+        },
+        margin: {
+          top: 0,
+          right: 0,
+          bottom: 0,
+          left: 0
+        },
+        align: {
+          horizontal: 'center',
+          vertical: 'center'
+        },
+        shadow: {
+          color: '',
+          opacity: 127,
+          x: 0,
+          y: 0,
+          blur: 0
+        }
+      },
+      {
+        part: {
+          name: 'main',
+          state: 'pressed'
+        },
+        background: {
+          color: '#0a4f88ff'
+        }
+      },
+      {
+        part: {
+          name: 'main',
+          state: 'checked'
+        },
+        background: {
+          color: '#0a4f88ff'
+        }
+      },
+      {
+        part: {
+          name: 'main',
+          state: 'disabled'
+        },
+        background: {
+          color: '#a6a6a7'
+        },
+        text: {
+          color: '#373636'
+        }
+      }
+    ]
+  },
+  config: {
+    // 组件属性
+    props: [
+      {
+        label: '基本属性',
+        valueType: 'group',
+        children: [
+          {
+            label: '名称',
+            field: 'name',
+            valueType: 'text',
+            componentProps: {
+              placeholder: '请输入名称'
+            }
+          },
+          {
+            label: '类型',
+            field: 'type',
+            valueType: 'text',
+            componentProps: {
+              readOnly: true
+            }
+          }
+        ]
+      },
+      {
+        label: '位置/大小',
+        valueType: 'group',
+        children: [
+          {
+            label: 'X',
+            field: 'x',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            }
+          },
+          {
+            label: 'Y',
+            field: 'y',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            }
+          },
+          {
+            label: '宽度',
+            field: 'width',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            }
+          },
+          {
+            label: '高度',
+            field: 'height',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            }
+          }
+        ]
+      },
+      {
+        label: '标识',
+        field: 'flags',
+        valueType: 'select',
+        componentProps: {
+          options: flagOptions,
+          multiple: true,
+          clearable: true
+        }
+      },
+      {
+        label: '可见',
+        field: 'visible',
+        valueType: 'checkbox'
+      },
+      {
+        label: '启/禁用',
+        field: 'enabled',
+        valueType: 'checkbox'
+      },
+      {
+        label: '文本',
+        field: 'text',
+        valueType: 'text'
+      }
+    ],
+    // 组件样式
+    styles: [
+      {
+        label: '模块状态',
+        field: 'part',
+        valueType: 'part'
+      },
+      {
+        label: '背景',
+        field: 'background',
+        valueType: 'background'
+      },
+      {
+        label: '边框',
+        field: 'border',
+        valueType: 'border'
+      },
+      {
+        label: '内边距',
+        field: 'padding',
+        valueType: 'padding'
+      },
+      {
+        label: '外边距',
+        field: 'margin',
+        valueType: 'margin'
+      },
+      {
+        label: '阴影',
+        field: 'boxShadow',
+        valueType: 'boxShadow'
+      },
+      {
+        label: '对齐',
+        field: 'align',
+        valueType: 'align'
+      }
+    ]
+  }
+} as IComponentModelConfig

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

@@ -0,0 +1,15 @@
+<template>
+  <div></div>
+</template>
+
+<script setup lang="ts">
+defineProps<{
+  width: number
+  height: number
+  text: string
+  styles: any
+  state: string
+}>()
+</script>
+
+<style scoped></style>

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 4 - 0
src/renderer/src/lvgl-widgets/container/button.svg


+ 251 - 0
src/renderer/src/lvgl-widgets/container/index.ts

@@ -0,0 +1,251 @@
+import Container from './Container.vue'
+import icon from './button.svg'
+import { flagOptions } from '@/constants'
+import type { IComponentModelConfig } from '../type'
+import i18n from '@/locales'
+
+export default {
+  label: i18n.global.t('button'),
+  icon,
+  componentName: 'lv_container',
+  component: Container,
+  type: 'button',
+  group: i18n.global.t('container'),
+  sort: 1,
+  parts: [
+    {
+      name: 'main',
+      stateList: ['default', 'focused', 'pressed', 'checked', 'disabled']
+    }
+  ],
+  defaultSchema: {
+    props: {
+      name: 'button',
+      type: 'button',
+      x: 0,
+      y: 0,
+      width: 300,
+      height: 200,
+      hidden: false,
+      locked: false,
+      flags: [],
+      scrolling: [],
+      states: [],
+      text: 'Button'
+    },
+    styles: [
+      {
+        part: {
+          name: 'main',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffff',
+          alpha: 255,
+          image: {
+            src: '',
+            alpha: 255,
+            colorFormat: '',
+            colorDepth: '',
+            color: ''
+          }
+        },
+        text: {
+          color: '#ffffff',
+          family: '',
+          size: 14,
+          weight: 400,
+          align: 'center'
+        },
+        border: {
+          color: '#2195f6',
+          width: 2,
+          radius: 0,
+          style: 'solid',
+          side: 'all'
+        },
+        padding: {
+          top: 0,
+          right: 0,
+          bottom: 0,
+          left: 0
+        },
+        margin: {
+          top: 0,
+          right: 0,
+          bottom: 0,
+          left: 0
+        },
+        align: {
+          horizontal: 'center',
+          vertical: 'center'
+        },
+        shadow: {
+          color: '',
+          opacity: 127,
+          x: 0,
+          y: 0,
+          blur: 0
+        }
+      },
+      {
+        part: {
+          name: 'main',
+          state: 'pressed'
+        },
+        background: {
+          color: '#0a4f88ff'
+        }
+      },
+      {
+        part: {
+          name: 'main',
+          state: 'checked'
+        },
+        background: {
+          color: '#0a4f88ff'
+        }
+      },
+      {
+        part: {
+          name: 'main',
+          state: 'disabled'
+        },
+        background: {
+          color: '#a6a6a7'
+        },
+        text: {
+          color: '#373636'
+        }
+      }
+    ]
+  },
+  config: {
+    // 组件属性
+    props: [
+      {
+        label: '基本属性',
+        valueType: 'group',
+        children: [
+          {
+            label: '名称',
+            field: 'name',
+            valueType: 'text',
+            componentProps: {
+              placeholder: '请输入名称'
+            }
+          },
+          {
+            label: '类型',
+            field: 'type',
+            valueType: 'text',
+            componentProps: {
+              readOnly: true
+            }
+          }
+        ]
+      },
+      {
+        label: '位置/大小',
+        valueType: 'group',
+        children: [
+          {
+            label: 'X',
+            field: 'x',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            }
+          },
+          {
+            label: 'Y',
+            field: 'y',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            }
+          },
+          {
+            label: '宽度',
+            field: 'width',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            }
+          },
+          {
+            label: '高度',
+            field: 'height',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            }
+          }
+        ]
+      },
+      {
+        label: '标识',
+        field: 'flags',
+        valueType: 'select',
+        componentProps: {
+          options: flagOptions,
+          multiple: true,
+          clearable: true
+        }
+      },
+      {
+        label: '可见',
+        field: 'visible',
+        valueType: 'checkbox'
+      },
+      {
+        label: '启/禁用',
+        field: 'enabled',
+        valueType: 'checkbox'
+      },
+      {
+        label: '文本',
+        field: 'text',
+        valueType: 'text'
+      }
+    ],
+    // 组件样式
+    styles: [
+      {
+        label: '模块状态',
+        field: 'part',
+        valueType: 'part'
+      },
+      {
+        label: '背景',
+        field: 'background',
+        valueType: 'background'
+      },
+      {
+        label: '边框',
+        field: 'border',
+        valueType: 'border'
+      },
+      {
+        label: '内边距',
+        field: 'padding',
+        valueType: 'padding'
+      },
+      {
+        label: '外边距',
+        field: 'margin',
+        valueType: 'margin'
+      },
+      {
+        label: '阴影',
+        field: 'boxShadow',
+        valueType: 'boxShadow'
+      },
+      {
+        label: '对齐',
+        field: 'align',
+        valueType: 'align'
+      }
+    ]
+  }
+} as IComponentModelConfig

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

@@ -0,0 +1,5 @@
+import Button from './button'
+
+export const ComponentArray = [Button]
+
+export { Button }

+ 0 - 8
src/renderer/src/lvgl-widgets/lv_button/index.ts

@@ -1,8 +0,0 @@
-export default {
-  config: {
-    props: {
-      name: 'button',
-      type: 'button'
-    }
-  }
-}

+ 97 - 0
src/renderer/src/lvgl-widgets/type.d.ts

@@ -0,0 +1,97 @@
+type PartItem = {
+  name: string
+  stateList: string[]
+}
+
+export interface ComponentSchema {
+  // 其他未明确指定的属性
+  [fieldName: string]: any
+  // 子节点列表,可选
+  children?: ComponentSchema[]
+  // 组件属性,可选
+  componentProps?: any
+  // 编辑组件数据,可选(属性编辑另外绑定编辑的数据,默认则为当前选中组件数据)
+  editData?: object
+  // 节点字段,可选
+  field?: string
+  // 节点ID,可选
+  id?: string
+  // 是否为表单输入组件,可选
+  input?: boolean
+  // 节点标签,可选
+  label?: string
+  // 是否无需表单项,可选
+  noFormItem?: boolean
+  // 事件绑定
+  // 表单验证规则,可选
+  rules?: FormItemRule[]
+  // 是否显示(属性编辑组件可以添加函数动态显示隐藏),可选
+  show?: ((renderCallbackParams: RenderCallbackParams) => boolean) | boolean
+  // 插槽名称(组件为插槽类型时,需要设置插槽name),可选
+  slotName?: string
+  // 插槽列表,可选
+  slots?: { [slotName: string]: ComponentSchema[] }
+  // 节点类型,必选
+  valueType: string
+}
+
+export interface IComponentModelConfig {
+  /**
+   * 组件label
+   */
+  label: string
+  /**
+   * 组件图标
+   */
+  icon: string
+  /**
+   * 组件名称
+   */
+  componentName: string
+  /**
+   * 组件
+   */
+  component: any
+  /**
+   * 组件类型
+   */
+  type: string
+  /**
+   * 所属分组
+   */
+  group?: string
+  /**
+   * 组件排序
+   */
+  sort?: 1
+  /**
+   * 组件模块
+   */
+  parts: PartItem[]
+  /**
+   * 组件默认数据
+   */
+  defaultSchema: {
+    /**
+     * 默认属性
+     */
+    props: {}
+    /**
+     * 默认样式
+     */
+    styles: any[]
+  }
+  /**
+   * 组件配置
+   */
+  config: {
+    /**
+     * 组件属性配置
+     */
+    props?: ComponentSchema[]
+    /**
+     * 样式属性配置
+     */
+    styles?: ComponentSchema[]
+  }
+}

+ 5 - 3
src/renderer/src/model/index.ts

@@ -96,9 +96,11 @@ export const createFileResource = (path: string, type: 'image' | 'font' | 'other
         fielType: fileName.split('.').pop() || '',
         path,
         compressFormat: 'none',
-        colorFormat: '',
-        alpha: 255,
-        bin: ''
+        colorFormat: 'RGB565A8',
+        alphaColor: '#FFFFFF00',
+        bin: '',
+        ditheringAlgorithm: 'None',
+        storageMode: 'BIN'
       }
     }
     // 字体资源

+ 5 - 1
src/renderer/src/types/resource.d.ts

@@ -13,9 +13,13 @@ export type ImageResource = {
   // 颜色格式
   colorFormat: string
   // 透明度 0-255
-  alpha: number
+  alphaColor: string
   // 绑定BinId
   bin: string
+  // 抖动算法
+  ditheringAlgorithm: string
+  // 存储方式
+  storageMode: string
 }
 
 export type TextRange = 'all' | 'page' | 'custom' | 'range'

+ 25 - 11
src/renderer/src/views/designer/sidebar/Libary.vue

@@ -1,16 +1,16 @@
 <template>
   <el-scrollbar>
+    <el-input
+      v-model="search"
+      placeholder="请输入组件名称"
+      class="m-8px"
+      style="width: calc(100% - 16px)"
+    />
     <el-collapse>
       <el-collapse-item title="基础">
-        <el-space alignment="center" wrap>
-          <div
-            class="w-70px h-70px flex items-center text-text-secondary justify-center cursor-pointer hover:bg-bg-secondary hover:text-text-active"
-            v-for="item in 10"
-            :key="item"
-          >
-            控件{{ item }}
-          </div>
-        </el-space>
+        <div class="grid grid-cols-[repeat(auto-fill,minmax(70px,1fr))] gap-4px justify-center">
+          <LibaryItem v-for="item in ComponentArray" :key="item.componentName" :comp="item" />
+        </div>
       </el-collapse-item>
       <el-collapse-item title="表单"> </el-collapse-item>
       <el-collapse-item title="容器"> </el-collapse-item>
@@ -20,6 +20,20 @@
   </el-scrollbar>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import { ComponentArray } from '@/lvgl-widgets'
+import LibaryItem from './components/LibaryItem.vue'
 
-<style scoped></style>
+const search = ref('')
+
+const groups = computed(() => {
+  return ComponentArray.reduce((acc, cur) => {
+    if (cur.group) {
+      acc[cur.group] = acc[cur.group] || []
+      acc[cur.group].push(cur)
+    }
+    return acc
+  }, {})
+})
+</script>

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

@@ -42,14 +42,37 @@
         <el-select v-model="formData.colorFormat">
           <el-option
             v-for="item in colorFormatOptions"
-            :key="item.key"
+            :key="item.value"
             :label="item.label"
             :value="item.value"
           ></el-option>
         </el-select>
       </el-form-item>
-      <el-form-item label="透明度" prop="alpha">
-        <el-slider v-model="formData.alpha" :min="0" :max="255" show-input />
+      <el-form-item label="抖动算法" prop="ditheringAlgorithm">
+        <el-select v-model="formData.ditheringAlgorithm">
+          <el-option
+            v-for="item in ditheringAlgorithmOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          ></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="颜色" prop="alphaColor">
+        <ColorPicker
+          v-model:pureColor="formData.alphaColor"
+          format="hex8"
+          picker-type="chrome"
+          use-type="pure"
+          disableHistory
+        />
+        <span class="ml-8px">{{ formData.alphaColor }}</span>
+      </el-form-item>
+      <el-form-item label="存储方式" prop="storageMode">
+        <el-select v-model="formData.storageMode">
+          <el-option label="BIN" value="BIN"></el-option>
+          <el-option label="C源码" value="C"></el-option>
+        </el-select>
       </el-form-item>
       <el-form-item label="BIN配置" prop="bin" v-if="projectStore.project?.bins?.length">
         <div class="w-full flex gap-12px">
@@ -82,6 +105,7 @@ import { klona } from 'klona'
 import { getImageByPath } from '@/utils'
 import { LuPencilLine } from 'vue-icons-plus/lu'
 import EditBinModal from './EditBinModal.vue'
+import ColorPicker from '@/components/ColorPicker/index.vue'
 
 const emit = defineEmits(['change'])
 const projectStore = useProjectStore()
@@ -90,41 +114,46 @@ const form = ref<FormInstance>()
 const editBinModalRef = ref<InstanceType<typeof EditBinModal>>()
 const colorFormatOptions = [
   {
-    key: '1',
-    label: 'ARGB8888',
-    value: 'ARGB8888'
+    label: 'RGB565',
+    value: 'RGB565'
+  },
+  {
+    label: 'RGB8565A8',
+    value: 'RGB8565A8'
+  },
+  {
+    label: 'ARGB4444',
+    value: 'ARGB4444'
   },
   {
-    key: '2',
     label: 'RGB888',
     value: 'RGB888'
   },
   {
-    key: '3',
-    label: 'ARGB8565',
-    value: 'ARGB8565'
+    label: 'XRGB8888',
+    value: 'XRGB8888'
   },
   {
-    key: '3',
-    label: 'RGB565',
-    value: 'RGB565'
-  },
+    label: 'ARGB8888',
+    value: 'ARGB8888'
+  }
+]
+
+const ditheringAlgorithmOptions = [
   {
-    key: '4',
-    label: 'RGB555',
-    value: 'RGB555'
+    label: 'None',
+    value: 'None'
   },
   {
-    key: '5',
-    label: 'ARGB4444',
-    value: 'ARGB4444'
+    label: 'Floyd Steinberg',
+    value: 'Floyd-Steinberg'
   },
   {
-    key: '6',
-    label: 'RGB444',
-    value: 'RGB444'
+    label: 'Thresh',
+    value: 'Thresh'
   }
 ]
+
 const formData = ref<ImageResource>({
   id: '',
   fileName: '',
@@ -132,8 +161,10 @@ const formData = ref<ImageResource>({
   path: '',
   compressFormat: 'none',
   colorFormat: '',
-  alpha: 255,
-  bin: ''
+  bin: '',
+  alphaColor: '#FFFFFF00',
+  ditheringAlgorithm: 'None',
+  storageMode: 'BIN'
 })
 
 const imageInfo = ref<{

+ 36 - 0
src/renderer/src/views/designer/sidebar/components/LibaryItem.vue

@@ -0,0 +1,36 @@
+<template>
+  <div
+    ref="libaryItemRef"
+    :key="comp.componentName"
+    class="w-70px h-70px border border-solid border-border rounded-4px flex flex-col items-center text-text-secondary justify-center cursor-pointer hover:bg-bg-secondary hover:text-text-active"
+  >
+    <div class="w-40px h-40px flex items-center justify-center">
+      <img width="32px" :src="comp.icon" />
+    </div>
+    <span class="text-xs">{{ comp.label }}</span>
+  </div>
+</template>
+
+<script setup lang="ts">
+import type { IComponentModelConfig } from '@/lvgl-widgets/type'
+
+import { ref } from 'vue'
+import { useDrag } from 'vue-hooks-plus'
+
+const props = defineProps<{
+  comp: IComponentModelConfig
+}>()
+const libaryItemRef = ref<HTMLElement>()
+const draging = ref(false)
+
+useDrag(props.comp, libaryItemRef, {
+  onDragStart: () => {
+    draging.value = true
+  },
+  onDragEnd: () => {
+    draging.value = false
+  }
+})
+</script>
+
+<style scoped></style>

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

@@ -79,7 +79,7 @@ watch(
 
 // 刷新节点
 const refresh = () => {
-  mindMap.value?.setData(mindMapData.value)
+  mindMap.value?.updateData(mindMapData.value)
 }
 
 // 获取添加按钮

+ 6 - 2
src/renderer/src/views/designer/workspace/index.vue

@@ -3,7 +3,7 @@
     <SplitterGroup direction="vertical">
       <SplitterPanel>
         <el-tabs
-          v-model="active"
+          v-model="activeTab"
           type="border-card"
           class="w-full h-full"
           @tab-remove="handleTabRemove"
@@ -71,7 +71,7 @@ const projectStore = useProjectStore()
 const appStore = useAppStore()
 
 const content = ref('')
-const active = ref<TabPaneName>('design')
+const activeTab = ref<TabPaneName>('design')
 // tab pane列表
 const tabPaneList = ref([
   {
@@ -101,6 +101,10 @@ onMounted(() => {
 // 关闭tab
 const handleTabRemove = (tabName: TabPaneName) => {
   tabPaneList.value = tabPaneList.value.filter((item) => item.name !== tabName)
+  // 如果关闭的是当前激活前一个tab
+  if (tabName === activeTab.value) {
+    activeTab.value = tabPaneList.value.at(-1)?.name || 'design'
+  }
 }
 </script>
 

+ 7 - 0
src/renderer/src/views/designer/workspace/stage/Nodes.vue

@@ -0,0 +1,7 @@
+<template>
+  <div class=""></div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style scoped></style>