Parcourir la source

feat: 添加菜单控件

jiaxing.liao il y a 3 semaines
Parent
commit
ff2dda5b6d

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

@@ -103,5 +103,6 @@
   "list": "List",
   "layout": "Layout",
   "messageBox": "MsgBox",
-  "window": "Window"
+  "window": "Window",
+  "menu": "Menu"
 }

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

@@ -103,5 +103,6 @@
   "list": "列表",
   "layout": "布局",
   "messageBox": "消息框",
-  "window": "窗口"
+  "window": "窗口",
+  "menu": "菜单"
 }

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

@@ -18,6 +18,7 @@ import Table from './table/index'
 import List from './list/index'
 import MessageBox from './message/index'
 import Window from './window/index'
+import Menu from './menu/index'
 
 import Page from './page'
 import { IComponentModelConfig } from './type'
@@ -44,7 +45,8 @@ export const ComponentArray = [
   Table,
   List,
   MessageBox,
-  Window
+  Window,
+  Menu
 ]
 
 const componentMap: { [key: string]: IComponentModelConfig } = ComponentArray.reduce((acc, cur) => {

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

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

+ 86 - 0
src/renderer/src/lvgl-widgets/menu/Menu.vue

@@ -0,0 +1,86 @@
+<template>
+  <div :style="styleMap?.mainStyle" class="flex overflow-hidden box-border">
+    <div
+      class="shrink-0 box-border"
+      :class="expand ? 'flex-1! border-r-none!' : 'basis-1/3'"
+      style="border-right: solid 1px #ededed"
+    >
+      <div :style="styleMap?.menuTitleStyle">{{ title }}</div>
+      <div :style="styleMap?.buttonBoxStyle" class="box-border overflow-hidden">
+        <div
+          v-for="(item, index) in children || []"
+          :key="index"
+          :style="activeIndex === index ? activeStyle : styleMap?.itemsStyle"
+          class="min-h-30px grid place-items-center overflow-hidden"
+        >
+          {{ item.name }}
+        </div>
+      </div>
+    </div>
+    <div v-if="!expand" class="basis-2/3"></div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, type CSSProperties } from 'vue'
+import { useWidgetStyle, getStyle } from '../hooks/useWidgetStyle'
+import { useProjectStore } from '@/store/modules/project'
+import defaultStyle from './style.json'
+
+const props = defineProps<{
+  width: number
+  height: number
+  styles: any
+  state: string
+  part: string
+  scrollbar: string
+  title: string
+  // 折叠展开
+  expand: boolean
+  // 子项
+  children?: any[]
+  // 当前激活index
+  activeIndex: number
+}>()
+
+const styleMap = useWidgetStyle({
+  widget: 'lv_menu',
+  props
+})
+
+const projectStore = useProjectStore()
+
+// 激活样式
+const activeStyle = computed(() => {
+  const styles = props.styles
+
+  const itemStyle = styles.find(
+    (item) => item?.part?.name === 'items' && item.part?.state === 'checked'
+  )
+
+  const globalItemStyle = projectStore.globalStyle
+    ?.find((item) => item.widget === 'lv_menu')
+    ?.part?.find((item) => item.partName === 'items')
+    ?.state?.find((item) => item.state === 'checked')?.style
+
+  const defaultItemStyle = defaultStyle.part
+    .find((item) => item.partName === 'items')
+    ?.state.find((item) => item.state === 'checked')?.style
+
+  const style = itemStyle || globalItemStyle || defaultItemStyle
+
+  let styleMap: CSSProperties = {}
+
+  Object.keys(style || {}).forEach((key) => {
+    // 合并样式
+    styleMap = {
+      ...styleMap,
+      ...getStyle(key, style?.[key])
+    }
+  })
+
+  return styleMap
+})
+</script>
+
+<style scoped></style>

+ 361 - 0
src/renderer/src/lvgl-widgets/menu/index.tsx

@@ -0,0 +1,361 @@
+import Menu from './Menu.vue'
+import icon from '../assets/icon/icon_19menu.svg'
+import { flagOptions } from '@/constants'
+import type { IComponentModelConfig } from '../type'
+import i18n from '@/locales'
+import defaultStyle from './style.json'
+import Config from './Config.vue'
+
+export default {
+  label: i18n.global.t('menu'),
+  icon,
+  component: Menu,
+  key: 'lv_menu',
+  group: i18n.global.t('layout'),
+  sort: 1,
+  hasChildren: true,
+  defaultStyle,
+  parts: [
+    {
+      name: 'main',
+      stateList: ['default']
+    },
+    {
+      name: 'buttonBox',
+      stateList: ['default']
+    },
+    {
+      name: 'items',
+      stateList: ['default', 'checked']
+    },
+    {
+      name: 'menuTitle',
+      stateList: ['default']
+    }
+  ],
+  defaultSchema: {
+    name: 'menu',
+    children: [
+      {
+        name: 'item_1',
+        type: 'menu',
+        children: []
+      },
+      {
+        name: 'item_1',
+        type: 'menu',
+        children: []
+      },
+      {
+        name: 'item_1',
+        type: 'menu',
+        children: []
+      }
+    ],
+    props: {
+      x: 0,
+      y: 0,
+      width: 400,
+      height: 300,
+      addFlags: [],
+      removeFlags: [],
+      scrollbar: 'off',
+      title: 'menu',
+      // 折叠展开
+      expand: false,
+      // 当前激活index
+      activeIndex: 0
+    },
+    styles: [
+      {
+        part: {
+          name: 'main',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffffff'
+        },
+        border: {
+          radius: 0
+        },
+        shadow: {
+          color: '#2092f5ff',
+          x: 0,
+          y: 0,
+          spread: 0,
+          width: 0
+        }
+      },
+      {
+        part: {
+          name: 'buttonBox',
+          state: 'default'
+        },
+        background: {
+          color: '#f6f6f6ff'
+        },
+        border: {
+          color: '#32a0faff',
+          width: 0,
+          radius: 10,
+          side: ['all']
+        },
+        margin: {
+          left: 5,
+          right: 5,
+          top: 5,
+          bottom: 5
+        }
+      },
+      {
+        part: {
+          name: 'items',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffffff'
+        },
+        text: {
+          color: '#171313ff',
+          size: 12,
+          family: 'xx',
+          weight: 'normal',
+          align: 'center'
+        },
+        border: {
+          color: '#32a0ffff',
+          width: 0,
+          radius: 5,
+          side: ['bottom']
+        }
+      },
+      {
+        part: {
+          name: 'menuTitle',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffff'
+        },
+        text: {
+          color: '#3b4250ff',
+          size: 14,
+          family: 'xx',
+          weight: 'normal',
+          align: 'center'
+        },
+        padding: {
+          top: 5,
+          bottom: 5,
+          left: 5,
+          right: 5
+        }
+      }
+    ]
+  },
+  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.title',
+        valueType: 'text'
+      },
+      {
+        label: '折叠',
+        field: 'props.expand',
+        valueType: 'switch'
+      },
+      {
+        label: '属性',
+        field: '',
+        render: (val) => {
+          return <Config values={val} />
+        }
+      }
+    ],
+    // 组件样式
+    styles: [
+      {
+        label: '模块状态',
+        field: 'part',
+        valueType: 'part'
+      },
+      {
+        valueType: 'dependency',
+        name: ['part'],
+        dependency: ({ part }) => {
+          return part?.name === 'main'
+            ? [
+                {
+                  label: '背景',
+                  field: 'background',
+                  valueType: 'background',
+                  componentProps: {
+                    onlyColor: true
+                  }
+                },
+                {
+                  label: '边框',
+                  field: 'border',
+                  valueType: 'border',
+                  componentProps: {
+                    onlyRadius: true
+                  }
+                },
+                {
+                  label: '阴影',
+                  field: 'shadow',
+                  valueType: 'shadow'
+                }
+              ]
+            : part?.name === 'buttonBox'
+              ? [
+                  {
+                    label: '背景',
+                    field: 'background',
+                    valueType: 'background',
+                    componentProps: {
+                      onlyColor: true
+                    }
+                  },
+                  {
+                    label: '边框',
+                    field: 'border',
+                    valueType: 'border'
+                  },
+                  {
+                    label: '外边距',
+                    field: 'margin',
+                    valueType: 'margin'
+                  }
+                ]
+              : part?.name === 'items'
+                ? [
+                    {
+                      label: '背景',
+                      field: 'background',
+                      valueType: 'background',
+                      componentProps: {
+                        onlyColor: true
+                      }
+                    },
+                    {
+                      label: '字体',
+                      field: 'text',
+                      valueType: 'font'
+                    },
+                    {
+                      label: '边框',
+                      field: 'border',
+                      valueType: 'border'
+                    }
+                  ]
+                : [
+                    {
+                      label: '背景',
+                      field: 'background',
+                      valueType: 'background',
+                      componentProps: {
+                        onlyColor: true
+                      }
+                    },
+                    {
+                      label: '字体',
+                      field: 'text',
+                      valueType: 'font'
+                    },
+                    {
+                      label: '内边距',
+                      field: 'padding',
+                      valueType: 'padding'
+                    }
+                  ]
+        }
+      }
+    ]
+  }
+} as IComponentModelConfig

+ 127 - 0
src/renderer/src/lvgl-widgets/menu/style.json

@@ -0,0 +1,127 @@
+{
+  "widget": "lv_menu",
+  "styleName": "defualt",
+  "part": [
+    {
+      "partName": "main",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "border": {
+              "radius": 0
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "buttonBox",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#f6f6f6ff"
+            },
+            "border": {
+              "color": "#32a0faff",
+              "width": 0,
+              "radius": 10,
+              "side": ["all"]
+            },
+            "margin": {
+              "left": 5,
+              "right": 5,
+              "top": 5,
+              "bottom": 5
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "items",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#ffffffff"
+            },
+            "text": {
+              "color": "#171313ff",
+              "size": 12,
+              "family": "xx",
+              "weight": "normal",
+              "align": "center"
+            },
+            "border": {
+              "color": "#32a0ffff",
+              "width": 0,
+              "radius": 5,
+              "side": ["bottom"]
+            }
+          }
+        },
+        {
+          "state": "checked",
+          "style": {
+            "background": {
+              "color": "#19a5ff3c"
+            },
+            "text": {
+              "color": "#9ab700ff",
+              "size": 12,
+              "family": "xx",
+              "weight": "normal",
+              "align": "center"
+            },
+            "border": {
+              "color": "#32a0ffff",
+              "width": 0,
+              "radius": 5,
+              "side": ["bottom"]
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "menuTitle",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#ffffff"
+            },
+            "text": {
+              "color": "#3b4250ff",
+              "size": 14,
+              "family": "xx",
+              "weight": "normal",
+              "align": "center"
+            },
+            "padding": {
+              "top": 5,
+              "bottom": 5,
+              "left": 5,
+              "right": 5
+            }
+          }
+        }
+      ]
+    }
+  ]
+}