jiaxing.liao недель назад: 3
Родитель
Сommit
81853bc803

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

@@ -102,5 +102,6 @@
   "table": "Table",
   "list": "List",
   "layout": "Layout",
-  "messageBox": "MsgBox"
+  "messageBox": "MsgBox",
+  "window": "Window"
 }

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

@@ -102,5 +102,6 @@
   "table": "表格",
   "list": "列表",
   "layout": "布局",
-  "messageBox": "消息框"
+  "messageBox": "消息框",
+  "window": "窗口"
 }

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

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

+ 1 - 3
src/renderer/src/lvgl-widgets/list/List.vue

@@ -39,9 +39,9 @@
 <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 { getSymbol } from '@/utils'
 
 import type { ListItem } from './data'
 
@@ -75,8 +75,6 @@ const styleMap = useWidgetStyle({
   props
 })
 
-const getSymbol = (key: string) => symbols.find((item) => item.label === key)?.value
-
 const barStyle = computed(() => {
   const { styles, part, state } = props
 

+ 162 - 0
src/renderer/src/lvgl-widgets/window/Config.vue

@@ -0,0 +1,162 @@
+<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-select>
+        </el-form-item>
+        <el-form-item label="宽度">
+          <el-input-number style="width: 100%" v-model="formData.width" :min="1" :max="1000" />
+        </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="1"
+                  :max="100"
+                >
+                  <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="1"
+                  :max="100"
+                >
+                  <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 { getSymbol } from '@/utils'
+
+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: '',
+  width: 40,
+  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({
+    text: `button_${index}`,
+    type: 'symbol',
+    width: 40,
+    img_symbol: 'LV_SYMBOL_SAVE',
+    img_id: '',
+    img_width: 20,
+    img_height: 20
+  })
+}
+
+/**
+ * 删除文本项
+ */
+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>

+ 55 - 0
src/renderer/src/lvgl-widgets/window/Window.vue

@@ -0,0 +1,55 @@
+<template>
+  <div :style="styleMap?.mainStyle" class="flex flex-col overflow-hidden box-broder">
+    <div
+      :style="{ ...styleMap?.headerStyle, height: titleHeight + 'px' }"
+      class="flex items-center justify-between box-broder p-10px"
+    >
+      <span>{{ title }}</span>
+      <div class="flex items-center gap-4px">
+        <div
+          v-for="(btn, index) in btns"
+          :style="{ ...styleMap?.buttonStyle, width: btn.width + 'px', height: '30px' }"
+          :key="index"
+          class="grid place-items-center text-white shadow-[0_4px_0_#cccccc]"
+        >
+          <span v-if="btn.type === 'symbol'">
+            <i class="lvgl-icon not-italic" v-html="getSymbol(btn.img_symbol)"></i>
+          </span>
+          <LocalImage
+            v-if="btn.type === 'img'"
+            class="h-70px"
+            :id="btn.img_id"
+            :style="{ width: btn.img_width + 'px', height: btn.img_height + 'px' }"
+          />
+        </div>
+      </div>
+    </div>
+    <div :style="styleMap?.contentStyle" class="flex-1"></div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useWidgetStyle } from '../hooks/useWidgetStyle'
+import { getSymbol } from '@/utils'
+
+import { LocalImage } from '@/components'
+import type { ListItem } from './data'
+
+const props = defineProps<{
+  width: number
+  height: number
+  styles: any
+  state: string
+  part: string
+  title: string
+  titleHeight: number
+  btns: ListItem[]
+}>()
+
+const styleMap = useWidgetStyle({
+  widget: 'lv_win',
+  props
+})
+</script>
+
+<style scoped></style>

+ 19 - 0
src/renderer/src/lvgl-widgets/window/data.ts

@@ -0,0 +1,19 @@
+/**
+ * 列表项类型
+ */
+export type ListItem = {
+  // 文本内容
+  text: string
+  // 类型
+  type: 'symbol' | 'img'
+  // 按钮宽
+  width: number
+  // symbol
+  img_symbol: string
+  // 图片id
+  img_id: string
+  // 图片宽
+  img_width: number
+  // 图片高
+  img_height: number
+}

+ 343 - 0
src/renderer/src/lvgl-widgets/window/index.tsx

@@ -0,0 +1,343 @@
+import Window from './Window.vue'
+import icon from '../assets/icon/icon_18windows.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('window'),
+  icon,
+  component: Window,
+  key: 'lv_win',
+  group: i18n.global.t('layout'),
+  sort: 1,
+  hasChildren: true,
+  defaultStyle,
+  parts: [
+    {
+      name: 'main',
+      stateList: ['default']
+    },
+    {
+      name: 'content',
+      stateList: ['default']
+    },
+    {
+      name: 'header',
+      stateList: ['default']
+    },
+    {
+      name: 'button',
+      stateList: ['default']
+    }
+  ],
+  defaultSchema: {
+    name: 'window',
+    props: {
+      x: 0,
+      y: 0,
+      width: 400,
+      height: 250,
+      addFlags: [],
+      removeFlags: [],
+      scrollbar: 'off',
+      title: 'Title',
+      titleHeight: 40,
+      btns: [
+        {
+          text: 'button_0',
+          type: 'symbol',
+          width: 40,
+          img_symbol: 'LV_SYMBOL_CLOSE',
+          img_id: '',
+          img_width: 20,
+          img_height: 20
+        }
+      ]
+    },
+    styles: [
+      {
+        part: {
+          name: 'main',
+          state: 'default'
+        },
+        background: {
+          color: '#eeeeeeff'
+        },
+        outline: {
+          color: '#000000ff',
+          width: 0
+        },
+        border: {
+          color: '#000000ff',
+          width: 0,
+          radius: 0,
+          side: ['all']
+        }
+      },
+      {
+        part: {
+          name: 'content',
+          state: 'default'
+        },
+        background: {
+          color: '#eeeeeeff'
+        }
+      },
+      {
+        part: {
+          name: 'header',
+          state: 'default'
+        },
+        background: {
+          color: '#e6e6e6ff'
+        },
+        text: {
+          color: '#393c41ff',
+          size: 12,
+          weight: 'normal',
+          family: 'xx'
+        },
+        spacer: {
+          letterSpacing: 2,
+          lineHeight: 0
+        },
+        padding: {
+          left: 5,
+          right: 5,
+          top: 5,
+          bottom: 5
+        }
+      },
+      {
+        part: {
+          name: 'button',
+          state: 'default'
+        },
+        background: {
+          color: '#2092f5ff'
+        },
+        border: {
+          color: '#2092f5ff',
+          width: 0,
+          radius: 0,
+          side: ['all']
+        },
+        shadow: {
+          color: '#2092f5ff',
+          x: 0,
+          y: 0,
+          spread: 0,
+          width: 0
+        }
+      }
+    ]
+  },
+  config: {
+    // 组件属性
+    props: [
+      {
+        label: '名称',
+        field: 'name',
+        valueType: 'text',
+        componentProps: {
+          placeholder: '请输入名称'
+        }
+      },
+      {
+        label: '位置/大小',
+        valueType: 'group',
+        children: [
+          {
+            field: 'props.x',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            },
+            slots: { prefix: 'X' }
+          },
+          {
+            field: 'props.y',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            },
+            slots: { prefix: 'Y' }
+          },
+          {
+            field: 'props.width',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            },
+            slots: { prefix: 'W' }
+          },
+          {
+            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.titleHeight',
+        valueType: 'number',
+        componentProps: {
+          min: 1,
+          max: 1000
+        }
+      },
+      {
+        label: '按钮组',
+        field: 'props.btns',
+        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: 'outline',
+                  valueType: 'outline'
+                },
+                {
+                  label: '边框',
+                  field: 'border',
+                  valueType: 'border'
+                },
+                {
+                  label: '阴影',
+                  field: 'shadow',
+                  valueType: 'shadow'
+                }
+              ]
+            : part?.name === 'content'
+              ? [
+                  {
+                    label: '背景',
+                    field: 'background',
+                    valueType: 'background',
+                    componentProps: {
+                      onlyColor: true
+                    }
+                  }
+                ]
+              : part?.name === 'header'
+                ? [
+                    {
+                      label: '背景',
+                      field: 'background',
+                      valueType: 'background',
+                      componentProps: {
+                        onlyColor: true
+                      }
+                    },
+                    {
+                      label: '字体',
+                      field: 'text',
+                      valueType: 'font',
+                      componentProps: {
+                        hideAlign: true
+                      }
+                    },
+                    {
+                      label: '间距',
+                      field: 'spacer',
+                      valueType: 'spacer'
+                    },
+                    {
+                      label: '内边距',
+                      field: 'padding',
+                      valueType: 'padding'
+                    }
+                  ]
+                : [
+                    {
+                      label: '背景',
+                      field: 'background',
+                      valueType: 'background',
+                      componentProps: {
+                        onlyColor: true
+                      }
+                    },
+                    {
+                      label: '边框',
+                      field: 'border',
+                      valueType: 'border'
+                    },
+                    {
+                      label: '阴影',
+                      field: 'shadow',
+                      valueType: 'shadow'
+                    }
+                  ]
+        }
+      }
+    ]
+  }
+} as IComponentModelConfig

+ 97 - 0
src/renderer/src/lvgl-widgets/window/style.json

@@ -0,0 +1,97 @@
+{
+  "widget": "lv_win",
+  "styleName": "defualt",
+  "part": [
+    {
+      "partName": "main",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#eeeeeeff"
+            },
+            "outline": {
+              "color": "#000000ff",
+              "width": 0
+            },
+            "border": {
+              "color": "#000000ff",
+              "width": 0,
+              "radius": 0,
+              "side": ["all"]
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "content",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#eeeeeeff"
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "header",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#e6e6e6ff"
+            },
+            "text": {
+              "color": "#393c41ff",
+              "size": 12,
+              "weight": "normal",
+              "family": "xx"
+            },
+            "spacer": {
+              "letterSpacing": 2,
+              "lineHeight": 0
+            },
+            "padding": {
+              "left": 5,
+              "right": 5,
+              "top": 5,
+              "bottom": 5
+            }
+          }
+        }
+      ]
+    },
+    {
+      "partName": "button",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#2092f5ff"
+            },
+            "border": {
+              "color": "#2092f5ff",
+              "width": 0,
+              "radius": 0,
+              "side": ["all"]
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        }
+      ]
+    }
+  ]
+}

+ 8 - 0
src/renderer/src/utils/index.ts

@@ -1,3 +1,4 @@
+import { symbols } from '@/constants'
 import { Page } from '@/types/page'
 import { imageSize } from 'image-size'
 import { bfsWalk } from 'simple-mind-map/src/utils'
@@ -85,3 +86,10 @@ export function moveToPosition(arr, fromIndex, toIndex) {
   arr.splice(normalizedTo, 0, item)
   return arr
 }
+
+/**
+ * 获取symbol值
+ * @param key
+ * @returns lvgl symbol
+ */
+export const getSymbol = (key: string) => symbols.find((item) => item.label === key)?.value