jiaxing.liao 2 дней назад
Родитель
Сommit
f8adad2f2f

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

@@ -97,5 +97,6 @@
   "switch": "Switch",
   "bar": "Bar",
   "slider": "Slider",
-  "tabview": "Tabview"
+  "tabview": "Tabview",
+  "tileview": "Tileview"
 }

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

@@ -97,5 +97,6 @@
   "switch": "开关",
   "bar": "进度条",
   "slider": "滑动条",
-  "tabview": "选项卡"
+  "tabview": "选项卡",
+  "tileview": "平铺视图"
 }

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

@@ -29,7 +29,7 @@ export default {
       height: 200,
       addFlags: [],
       removeFlags: [],
-      scrollbar: 'on'
+      scrollbar: 'off'
     },
     styles: [
       {

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

@@ -13,6 +13,7 @@ import Slider from './slider'
 
 import Container from './container'
 import Tabview from './tabview/index'
+import Tileview from './tileview/index'
 
 import Page from './page'
 import { IComponentModelConfig } from './type'
@@ -34,7 +35,8 @@ export const ComponentArray = [
   Slider,
 
   Container,
-  Tabview
+  Tabview,
+  Tileview
 ]
 
 const componentMap: { [key: string]: IComponentModelConfig } = ComponentArray.reduce((acc, cur) => {

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

@@ -55,7 +55,7 @@ export default {
       height: 250,
       addFlags: [],
       removeFlags: [],
-      scrollbar: 'on',
+      scrollbar: 'off',
       size: 50,
       position: 'top',
       // 当前激活index

+ 146 - 0
src/renderer/src/lvgl-widgets/tileview/Config.vue

@@ -0,0 +1,146 @@
+<template>
+  <el-card class="mb-12px" body-class="p-8px!">
+    <template #header>
+      <div class="flex items-center justify-between">
+        <span>
+          瓷砖
+          <el-tooltip content="瓷砖位置索引">
+            <LuInfo size="12px" class="text-accent-yellow" /> </el-tooltip
+        ></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="(item, 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-number
+            controls-position="right"
+            :model-value="item.row"
+            @change="(val) => setRow(index, val)"
+          >
+            <template #prefix>
+              <span>行</span>
+            </template>
+          </el-input-number>
+          <el-input-number
+            controls-position="right"
+            :model-value="item.col"
+            @change="(val) => setColumn(index, val)"
+          >
+            <template #prefix>
+              <span>列</span>
+            </template>
+          </el-input-number>
+          <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, LuInfo } 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
+    }
+  }
+})
+
+/*
+ * 设置行号
+ */
+const setRow = (index: number, val?: number) => {
+  if (typeof val === 'undefined') return
+
+  const record = children.value[index]
+
+  // 判断是否存在相同索引
+  const existIndex = children.value.findIndex((item) => item.row === val && item.col === record.col)
+
+  if (existIndex === -1) {
+    // 存在相同索引
+    record.row = val
+    record.name = `tile_${val}_${record.col}`
+  }
+}
+
+/*
+ * 设置列号
+ */
+const setColumn = (index: number, val?: number) => {
+  if (typeof val === 'undefined') return
+
+  const record = children.value[index]
+
+  // 判断是否存在相同索引
+  const existIndex = children.value.findIndex((item) => item.col === val && item.row === record.row)
+
+  if (existIndex === -1) {
+    // 存在相同索引
+    record.col = val
+    record.name = `tile_${record.row}_${val}`
+  }
+}
+
+/**
+ * 删除一项
+ * @param index 索引
+ */
+const handleDeleteItem = (index: number) => {
+  children.value.splice(index, 1)
+}
+
+/**
+ * 添加一项
+ */
+const handleAddRow = () => {
+  const maxRow = children.value.reduce((max, item) => {
+    return Math.max(max, item.row)
+  }, 0)
+  const maxCol = children.value
+    .filter((item) => item.row === maxRow)
+    .reduce((max, item) => {
+      return Math.max(max, item.col)
+    }, 0)
+  children.value?.push({
+    name: `tile_${maxRow}_${maxCol + 1}`,
+    type: 'tile',
+    row: maxRow,
+    col: maxCol + 1,
+    children: []
+  })
+}
+</script>
+
+<style scoped></style>

+ 60 - 0
src/renderer/src/lvgl-widgets/tileview/Tileview.vue

@@ -0,0 +1,60 @@
+<template>
+  <div :style="styleMap?.mainStyle" class="relative overflow-hidden">
+    <div
+      class="v-scrollbar absolute bottom-6px h-4px"
+      :style="{ ...(styleMap?.scrollbarStyle || {}), ...barStyle.vStyle }"
+    ></div>
+    <div
+      class="h-scrollbar absolute right-6px w-4px"
+      :style="{ ...(styleMap?.scrollbarStyle || {}), ...barStyle.hStyle }"
+    ></div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import { useWidgetStyle } from '../hooks/useWidgetStyle'
+
+const props = defineProps<{
+  width: number
+  height: number
+  styles: any
+  state: string
+  part: string
+  scrollbar: string
+  children?: any[]
+  activeIndex: number
+}>()
+
+const styleMap = useWidgetStyle({
+  widget: 'lv_tileview',
+  props
+})
+
+const barStyle = computed(() => {
+  const { activeIndex, children = [], width, height } = props
+
+  const curRow = children[activeIndex]?.row ?? 0
+  const curCol = children[activeIndex]?.col ?? 0
+
+  const maxRow = children.reduce((max, item) => Math.max(max, item.row), 0)
+  const maxCol = children.reduce((max, item) => Math.max(max, item.col), 0)
+
+  const itemHeight = (height - 20) / (maxRow + 1)
+  const itemWidth = (width - 20) / (maxCol + 1)
+  return {
+    hStyle: {
+      display: maxRow > 0 ? '' : 'none',
+      height: `${itemHeight}px`,
+      top: `${10 + itemHeight * curRow}px`
+    },
+    vStyle: {
+      display: maxCol > 0 ? '' : 'none',
+      width: `${itemWidth}px`,
+      left: `${10 + itemWidth * curCol}px`
+    }
+  }
+})
+</script>
+
+<style scoped></style>

+ 250 - 0
src/renderer/src/lvgl-widgets/tileview/index.tsx

@@ -0,0 +1,250 @@
+import Tileview from './Tileview.vue'
+import icon from '../assets/icon/icon_14tileview.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('tileview'),
+  icon,
+  component: Tileview,
+  key: 'lv_tileview',
+  group: i18n.global.t('container'),
+  sort: 1,
+  hasChildren: true,
+  defaultStyle,
+  parts: [
+    {
+      name: 'main',
+      stateList: ['default']
+    },
+    {
+      name: 'scrollbar',
+      stateList: ['default']
+    }
+  ],
+  defaultSchema: {
+    name: 'tileview',
+    children: [
+      {
+        name: 'tile_0_0',
+        type: 'tile',
+        row: 0,
+        col: 0,
+        children: []
+      },
+      {
+        name: 'tile_1_0',
+        type: 'tile',
+        row: 1,
+        col: 0,
+        children: []
+      },
+      {
+        name: 'tile_2_0',
+        type: 'tile',
+        row: 2,
+        col: 0,
+        children: []
+      }
+    ],
+    props: {
+      x: 0,
+      y: 0,
+      width: 400,
+      height: 250,
+      addFlags: [],
+      removeFlags: [],
+      scrollbar: 'off',
+      // 当前激活index
+      activeIndex: 0
+    },
+    styles: [
+      {
+        part: {
+          name: 'main',
+          state: 'default'
+        },
+        background: {
+          color: '#f6f6f6ff'
+        },
+        border: {
+          color: '#000000ff',
+          width: 0,
+          radius: 0,
+          side: ['all']
+        },
+        shadow: {
+          color: '#2092f5ff',
+          x: 0,
+          y: 0,
+          spread: 0,
+          width: 0
+        }
+      },
+      {
+        part: {
+          name: 'scrollbar',
+          state: 'default'
+        },
+        background: {
+          color: '#eeeeeeff'
+        },
+        border: {
+          radius: 0
+        }
+      }
+    ]
+  },
+  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: '',
+        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: 'shadow',
+                  valueType: 'shadow'
+                }
+              ]
+            : [
+                {
+                  label: '背景',
+                  field: 'background',
+                  valueType: 'background',
+                  componentProps: {
+                    onlyColor: true
+                  }
+                },
+                {
+                  label: '边框',
+                  field: 'border',
+                  valueType: 'border',
+                  componentProps: {
+                    onlyRadius: true
+                  }
+                }
+              ]
+        }
+      }
+    ]
+  }
+} as IComponentModelConfig

+ 48 - 0
src/renderer/src/lvgl-widgets/tileview/style.json

@@ -0,0 +1,48 @@
+{
+  "widget": "lv_tileview",
+  "styleName": "defualt",
+  "part": [
+    {
+      "partName": "main",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#f6f6f6ff"
+            },
+            "border": {
+              "color": "#000000ff",
+              "width": 0,
+              "radius": 0,
+              "side": ["all"]
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "scrollbar",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#eeeeeeff"
+            },
+            "border": {
+              "radius": 0
+            }
+          }
+        }
+      ]
+    }
+  ]
+}

+ 10 - 2
src/renderer/src/views/designer/sidebar/Hierarchy.vue

@@ -92,13 +92,21 @@ const handlePageNodeClick = (node: any) => {
   })
 }
 
-const handleWidgetNodeClick = (nodeData, _node, e) => {
-  if (nodeData.type !== 'page') {
+const handleWidgetNodeClick = (nodeData, node, e) => {
+  if (nodeData.type !== 'page' && nodeData?.id) {
     if (e.ctrlKey) {
       projectStore.activeWidgets.push(nodeData)
     } else {
       projectStore.setSelectWidgets([nodeData])
     }
   }
+  const parent = node.parent?.data
+  if (!nodeData?.id && parent) {
+    if (e.ctrlKey) {
+      projectStore.activeWidgets.push(parent)
+    } else {
+      projectStore.setSelectWidgets([parent])
+    }
+  }
 }
 </script>