Browse Source

feat: 添加line控件

jiaxing.liao 2 weeks ago
parent
commit
0a9a99bbf0

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

@@ -104,5 +104,7 @@
   "layout": "Layout",
   "messageBox": "MsgBox",
   "window": "Window",
-  "menu": "Menu"
+  "menu": "Menu",
+  "line": "Line",
+  "display": "Display"
 }

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

@@ -104,5 +104,7 @@
   "layout": "布局",
   "messageBox": "消息框",
   "window": "窗口",
-  "menu": "菜单"
+  "menu": "菜单",
+  "line": "线条",
+  "display": "显示"
 }

+ 9 - 1
src/renderer/src/lvgl-widgets/hooks/useWidgetStyle.ts

@@ -19,7 +19,11 @@ type StyleParam = {
 
 type StyleMap = Record<
   string,
-  CSSProperties & { imageSrc?: string; imageColorStyle?: CSSProperties }
+  CSSProperties & {
+    imageSrc?: string
+    imageColorStyle?: CSSProperties
+    line?: { color: string; width: number; radius: boolean }
+  }
 >
 
 /**
@@ -173,6 +177,10 @@ export const useWidgetStyle = (param: StyleParam) => {
             })
           }
         }
+        // 线段返回原本值
+        if (key === 'line') {
+          styleMap.value[`${partItem.name}Style`].line = style?.[key]
+        }
       })
       // 处理行高 默认行高为1.2倍
       if (style?.spacer?.lineHeight && style?.text?.size) {

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

@@ -20,6 +20,8 @@ import MessageBox from './message/index'
 import Window from './window/index'
 import Menu from './menu/index'
 
+import Line from './line/index'
+
 import Page from './page'
 import { IComponentModelConfig } from './type'
 
@@ -46,7 +48,9 @@ export const ComponentArray = [
   List,
   MessageBox,
   Window,
-  Menu
+  Menu,
+
+  Line
 ]
 
 const componentMap: { [key: string]: IComponentModelConfig } = ComponentArray.reduce((acc, cur) => {

+ 104 - 0
src/renderer/src/lvgl-widgets/line/Config.vue

@@ -0,0 +1,104 @@
+<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="(item, index) in children || []"
+        :key="index"
+      >
+        <div class="w-full flex items-center gap-4px relative group/item">
+          <el-input-number
+            controls-position="right"
+            :model-value="item.x"
+            @change="(val) => setRow(index, val)"
+          >
+            <template #prefix>
+              <span>X</span>
+            </template>
+          </el-input-number>
+          <el-input-number
+            controls-position="right"
+            :model-value="item.y"
+            @change="(val) => setColumn(index, val)"
+          >
+            <template #prefix>
+              <span>Y</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 } from 'vue-icons-plus/lu'
+
+const props = defineProps<{
+  values: Ref<{ x: number; y: number }[]>
+}>()
+
+// 子项
+const children = computed({
+  get() {
+    return (props.values?.value || []) as any[]
+  },
+  set(list: { x: number; y: number }[]) {
+    if (props.values?.value) {
+      props.values.value = list
+    }
+  }
+})
+
+/*
+ * 设置x
+ */
+const setRow = (index: number, val?: number) => {
+  if (typeof val === 'undefined') return
+
+  const record = children.value[index]
+
+  record.x = val
+}
+
+/*
+ * 设置y
+ */
+const setColumn = (index: number, val?: number) => {
+  if (typeof val === 'undefined') return
+
+  const record = children.value[index]
+
+  record.y = val
+}
+
+/**
+ * 删除一项
+ * @param index 索引
+ */
+const handleDeleteItem = (index: number) => {
+  children.value.splice(index, 1)
+}
+
+/**
+ * 添加一项
+ */
+const handleAddRow = () => {
+  children.value?.push({
+    x: 0,
+    y: 0
+  })
+}
+</script>
+
+<style scoped></style>

+ 53 - 0
src/renderer/src/lvgl-widgets/line/Line.vue

@@ -0,0 +1,53 @@
+<template>
+  <div :style="styleMap?.mainStyle" class="box-border overflow-hidden">
+    <svg
+      :viewBox="`${-(strokeWidth / 2)} ${-(strokeWidth / 2)} ${width + strokeWidth} ${height + strokeWidth}`"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <line
+        v-for="line in lines"
+        :x1="line.x1"
+        :y1="line.y1"
+        :x2="line.x2"
+        :y2="line.y2"
+        :stroke="styleMap?.mainStyle?.line?.color"
+        :stroke-width="strokeWidth"
+        :stroke-linecap="styleMap?.mainStyle?.line?.radius ? 'round' : 'butt'"
+      />
+    </svg>
+  </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
+  points: { x: number; y: number }[]
+}>()
+
+const styleMap = useWidgetStyle({
+  widget: 'lv_line',
+  props
+})
+
+// 获取全部线段
+const lines = computed(() => {
+  return props.points.map((point, index) => {
+    const nextPoint = props.points[index + 1] || props.points[index]
+    return {
+      x1: point.x,
+      y1: point.y,
+      x2: nextPoint.x,
+      y2: nextPoint.y
+    }
+  })
+})
+
+const strokeWidth = computed(() => styleMap.value?.mainStyle?.line?.width ?? 2)
+</script>

+ 155 - 0
src/renderer/src/lvgl-widgets/line/index.tsx

@@ -0,0 +1,155 @@
+import Line from './Line.vue'
+import icon from '../assets/icon/icon_20line.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('line'),
+  icon,
+  component: Line,
+  key: 'lv_line',
+  group: i18n.global.t('display'),
+  sort: 1,
+  defaultStyle,
+  parts: [
+    {
+      name: 'main',
+      stateList: ['default']
+    }
+  ],
+  defaultSchema: {
+    name: 'line',
+    props: {
+      x: 0,
+      y: 0,
+      width: 80,
+      height: 80,
+      addFlags: [],
+      removeFlags: [],
+      points: [
+        { x: 0, y: 0 },
+        { x: 60, y: 60 }
+      ]
+    },
+    styles: [
+      {
+        part: {
+          name: 'main',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffff00'
+        },
+        line: {
+          color: '#777777ff',
+          width: 2,
+          radius: true
+        }
+      }
+    ]
+  },
+  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.addFlags',
+        valueType: 'checkbox',
+        componentProps: {
+          options: flagOptions,
+          defaultCollapsed: true
+        }
+      },
+      {
+        label: '删除标识',
+        field: 'props.removeFlags',
+        valueType: 'checkbox',
+        componentProps: {
+          options: flagOptions,
+          defaultCollapsed: true
+        }
+      }
+    ],
+    coreProps: [
+      {
+        label: '属性',
+        field: 'props.points',
+        valueType: '',
+        render: (val) => {
+          return <Config values={val} />
+        }
+      }
+    ],
+    // 组件样式
+    styles: [
+      {
+        label: '模块状态',
+        field: 'part',
+        valueType: 'part'
+      },
+      {
+        label: '背景',
+        field: 'background',
+        valueType: 'background'
+      },
+      {
+        label: '直线',
+        field: 'line',
+        valueType: 'line'
+      }
+    ]
+  }
+} as IComponentModelConfig

+ 24 - 0
src/renderer/src/lvgl-widgets/line/style.json

@@ -0,0 +1,24 @@
+{
+  "widget": "lv_line",
+  "styleName": "defualt",
+  "part": [
+    {
+      "partName": "main",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#ffffff00"
+            },
+            "line": {
+              "color": "#777777ff",
+              "width": 2,
+              "radius": true
+            }
+          }
+        }
+      ]
+    }
+  ]
+}