Просмотр исходного кода

feat: 添加快捷键、图像控件、富文本、文本框控件等

jiaxing.liao 2 месяцев назад
Родитель
Сommit
d3f5b9a553

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

@@ -87,5 +87,8 @@
   "createTime": "Create Time",
   "imgButton": "Image Button",
   "label": "Label",
-  "buttonMatrix": "Matrix Button"
+  "buttonMatrix": "Matrix Button",
+  "image": "Image",
+  "richText": "Span Group",
+  "textarea": "Textarea"
 }

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

@@ -87,5 +87,8 @@
   "createTime": "创建时间",
   "imgButton": "图片按钮",
   "label": "标签",
-  "buttonMatrix": "矩阵按钮"
+  "buttonMatrix": "矩阵按钮",
+  "image": "图片",
+  "richText": "富文本",
+  "textarea": "文本框"
 }

Разница между файлами не показана из-за своего большого размера
+ 11 - 0
src/renderer/src/lvgl-widgets/assets/icon/icon_5rich_text.svg


+ 1 - 1
src/renderer/src/lvgl-widgets/button-matrix/style.json

@@ -1,5 +1,5 @@
 {
-  "widget": "button",
+  "widget": "buttonmatrix",
   "styleName": "defualt",
   "part": [
     {

+ 2 - 4
src/renderer/src/lvgl-widgets/image-button/ImageButton.vue

@@ -1,13 +1,13 @@
 <template>
   <div v-bind="getProps">
-    <img width="100%" height="100%" :src="defaultImg" />
+    <img width="100%" height="100%" :src="defaultImg" alt="image btn" />
     <div>{{ props.text }}</div>
   </div>
 </template>
 
 <script setup lang="ts">
 import { computed } from 'vue'
-import defaultImg from './image.svg'
+import defaultImg from './default.png'
 
 const props = defineProps<{
   width: number
@@ -27,8 +27,6 @@ const getProps = computed(() => {
       width: `${props.width}px`,
       height: `${props.height}px`,
 
-      backgroundColor: stateStyles?.background.color,
-
       fontSize: `${stateStyles?.text.size}px`,
       color: stateStyles?.text?.color,
       display: 'flex',

BIN
src/renderer/src/lvgl-widgets/image-button/default.png


+ 45 - 0
src/renderer/src/lvgl-widgets/image/Image.vue

@@ -0,0 +1,45 @@
+<template>
+  <div :style="boxStyle">
+    <img :style="imgStyle" width="100%" height="100%" :src="imageSrc || defaultImg" alt="img" />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import defaultImg from './default.png'
+
+const props = defineProps<{
+  width: number
+  height: number
+  imageSrc: string
+  rotate: {
+    x: number
+    y: number
+    angle: number
+  }
+  styles: any
+  state?: string
+}>()
+
+const boxStyle = computed(() => {
+  const { width, height } = props
+
+  return {
+    width: `${width}px`,
+    height: `${height}px`,
+
+    display: 'flex',
+    justifyContent: 'center',
+    alignItems: 'center'
+  }
+})
+
+const imgStyle = computed(() => {
+  const { rotate = { x: 50, y: 0, angle: 0 } } = props
+
+  return {
+    transform: `rotate(${rotate.angle}deg)`,
+    transformOrigin: `${rotate.x}px ${rotate.y}px`
+  }
+})
+</script>

BIN
src/renderer/src/lvgl-widgets/image/default.png


+ 159 - 0
src/renderer/src/lvgl-widgets/image/index.ts

@@ -0,0 +1,159 @@
+import Image from './Image.vue'
+import icon from '../assets/icon/icon_4image.svg'
+import { flagOptions } from '@/constants'
+import type { IComponentModelConfig } from '../type'
+import i18n from '@/locales'
+
+export default {
+  label: i18n.global.t('image'),
+  icon,
+  component: Image,
+  key: 'lv_image',
+  group: i18n.global.t('basic'),
+  sort: 1,
+  hasChildren: true,
+  parts: [
+    {
+      name: 'main',
+      stateList: ['default']
+    }
+  ],
+  defaultSchema: {
+    props: {
+      name: 'image',
+      x: 0,
+      y: 0,
+      width: 100,
+      height: 100,
+      addFlags: [],
+      removeFlags: [],
+      imageSrc: '',
+      rotate: {
+        x: 50,
+        y: 50,
+        angle: 0
+      }
+    },
+    styles: []
+  },
+  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: 'addFlags',
+        valueType: 'checkbox',
+        componentProps: {
+          options: flagOptions
+        }
+      },
+      {
+        label: '删除标识',
+        field: 'removeFlags',
+        valueType: 'checkbox',
+        componentProps: {
+          options: flagOptions
+        }
+      },
+      {
+        label: '图片',
+        field: 'imageSrc',
+        valueType: 'image'
+      },
+      {
+        label: '旋转中心',
+        valueType: 'group',
+        children: [
+          {
+            label: 'X',
+            field: 'rotate.x',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            }
+          },
+          {
+            label: 'Y',
+            field: 'rotate.y',
+            valueType: 'number',
+            componentProps: {
+              span: 12
+            }
+          }
+        ]
+      },
+      {
+        label: '旋转角度',
+        field: 'rotate.angle',
+        valueType: 'number',
+        componentProps: {
+          span: 12,
+          min: 0,
+          max: 360
+        }
+      }
+    ],
+    // 组件样式
+    styles: []
+  }
+} as IComponentModelConfig

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

@@ -1,12 +1,24 @@
 import Button from './button'
 import ImageButton from './image-button'
 import MatrixButton from './button-matrix'
+import Image from './image'
+import SpanGroup from './span-group'
+
 import Container from './container'
 import Label from './label'
 import Page from './page'
 import { IComponentModelConfig } from './type'
 
-export const ComponentArray = [Button, ImageButton, MatrixButton, Container, Page, Label]
+export const ComponentArray = [
+  Button,
+  ImageButton,
+  MatrixButton,
+  Image,
+  SpanGroup,
+  Container,
+  Page,
+  Label
+]
 
 const componentMap: { [key: string]: IComponentModelConfig } = ComponentArray.reduce((acc, cur) => {
   acc[cur.key] = cur

+ 89 - 0
src/renderer/src/lvgl-widgets/span-group/SpanGroup.vue

@@ -0,0 +1,89 @@
+<template>
+  <div v-bind="getProps">
+    <span v-for="(item, index) in items" :key="index" :style="getSpanStyle(item)">{{
+      item.text
+    }}</span>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, CSSProperties } from 'vue'
+import { LineEnum } from './data'
+
+type Item = {
+  text: string
+  text_color: string
+  font_family: string
+  font_size: number
+  text_decor: LineEnum
+}
+
+const props = defineProps<{
+  width: number
+  height: number
+  styles: any
+  state?: string
+  mode: 'break' | 'fixed' | 'Expand'
+  items: Item[]
+}>()
+
+const getProps = computed(() => {
+  const styles = props.styles
+  const stateStyles = styles.find((item) => item.state === props.state)
+
+  return {
+    class: 'button',
+    style: {
+      width: `${props.width}px`,
+      height: `${props.height}px`,
+      boxSizing: 'border-box',
+      // 背景
+      backgroundColor: stateStyles?.background.color,
+
+      // 边框
+      borderRadius: `${stateStyles?.border.radius}px`,
+      borderColor: 'transparent',
+      borderWidth: `${stateStyles?.border.width}px`,
+      borderTopColor: ['all', 'top'].includes(stateStyles?.border?.side)
+        ? stateStyles?.border?.color
+        : 'transparent',
+      borderRightColor: ['all', 'right'].includes(stateStyles?.border?.side)
+        ? stateStyles?.border?.color
+        : 'transparent',
+      borderBottomColor: ['all', 'bottom'].includes(stateStyles?.border?.side)
+        ? stateStyles?.border?.color
+        : 'transparent',
+      borderLeftColor: ['all', 'left'].includes(stateStyles?.border?.side)
+        ? stateStyles?.border?.color
+        : 'transparent',
+      // 阴影
+      /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
+      boxShodow: stateStyles?.shadow
+        ? `${stateStyles?.shadow?.offsetX}px ${stateStyles?.shadow?.offsetY}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
+        : 'none',
+      // 内边距
+      padding: `${stateStyles?.padding.top}px ${stateStyles?.padding.right}px ${stateStyles?.padding.bottom}px ${stateStyles?.padding.left}px`
+    } as CSSProperties
+  }
+})
+
+// 获取子项样式
+const getSpanStyle = (item: Item) => {
+  const lineStyle =
+    item.text_decor === LineEnum['LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH']
+      ? 'underline line-through'
+      : item.text_decor === LineEnum.LV_TEXT_DECOR_UNDERLINE
+        ? 'underline'
+        : item.text_decor === LineEnum.LV_TEXT_DECOR_STRIKETHROUGH
+          ? 'line-through'
+          : 'none'
+
+  return {
+    color: item.text_color,
+    fontSize: `${item.font_size}px`,
+    textDecoration: lineStyle
+  }
+}
+</script>
+
+<style scoped></style>

+ 6 - 0
src/renderer/src/lvgl-widgets/span-group/data.ts

@@ -0,0 +1,6 @@
+export enum LineEnum {
+  'LV_TEXT_DECOR_NONE',
+  'LV_TEXT_DECOR_UNDERLINE',
+  'LV_TEXT_DECOR_STRIKETHROUGH',
+  'LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH'
+}

+ 182 - 0
src/renderer/src/lvgl-widgets/span-group/index.ts

@@ -0,0 +1,182 @@
+import SpanGroup from './SpanGroup.vue'
+import icon from '../assets/icon/icon_5rich_text.svg'
+import type { IComponentModelConfig } from '../type'
+import i18n from '@/locales'
+import { flagOptions } from '@/constants'
+
+export default {
+  label: i18n.global.t('richText'),
+  icon,
+  component: SpanGroup,
+  key: 'lv_span',
+  group: i18n.global.t('basic'),
+  sort: 1,
+  parts: [
+    {
+      name: 'main',
+      stateList: ['default']
+    }
+  ],
+  defaultSchema: {
+    props: {
+      name: 'spangroup',
+      x: 0,
+      y: 0,
+      width: 200,
+      height: 100,
+      addFlags: [],
+      removeFlags: [],
+      mode: 'break',
+      items: [
+        {
+          text: 'hello world',
+          text_color: '#000000',
+          font_family: 'xx',
+          font_size: 16,
+          text_decor: 'LV_TEXT_DECOR_NONE' // none: LV_TEXT_DECOR_NONE  下划线: LV_TEXT_DECOR_UNDERLINE 中划线: LV_TEXT_DECOR_STRIKETHROUGH 下划线、删除线:"LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH"
+        }
+      ]
+    },
+    styles: [
+      {
+        part: {
+          name: 'main',
+          state: 'default'
+        },
+        background: {
+          color: '#2092f500',
+          image: {
+            imgId: '',
+            color: ''
+          }
+        },
+        border: {
+          color: '#000000ff',
+          width: 0,
+          radius: 0,
+          side: ['all']
+        },
+        padding: {
+          top: 0,
+          right: 0,
+          bottom: 0,
+          left: 0
+        },
+        shadow: {
+          color: '#2092f5ff',
+          x: 0,
+          y: 0,
+          spread: 0,
+          width: 0
+        }
+      }
+    ]
+  },
+  config: {
+    // 组件属性
+    props: [
+      {
+        label: '名称',
+        field: 'name',
+        valueType: 'text',
+        componentProps: {
+          placeholder: '请输入名称'
+        }
+      },
+      {
+        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: 'addFlags',
+        valueType: 'checkbox',
+        componentProps: {
+          options: flagOptions
+        }
+      },
+      {
+        label: '删除标识',
+        field: 'removeFlags',
+        valueType: 'checkbox',
+        componentProps: {
+          options: flagOptions
+        }
+      },
+      {
+        label: '模式',
+        field: 'mode',
+        valueType: 'select',
+        componentProps: {
+          options: [
+            { label: 'Break', value: 'break' },
+            { label: 'Expand', value: 'expand' },
+            { label: 'Fixed', value: 'fixed' }
+          ]
+        }
+      }
+    ],
+    // 组件样式
+    styles: [
+      {
+        label: '模块状态',
+        field: 'part',
+        valueType: 'part'
+      },
+      {
+        label: '背景',
+        field: 'background',
+        valueType: 'background'
+      },
+      {
+        label: '边框',
+        field: 'border',
+        valueType: 'border'
+      },
+      {
+        label: '内边距',
+        field: 'padding',
+        valueType: 'padding'
+      },
+      {
+        label: '阴影',
+        field: 'shadow',
+        valueType: 'shadow'
+      }
+    ]
+  }
+} as IComponentModelConfig

+ 42 - 0
src/renderer/src/lvgl-widgets/span-group/style.json

@@ -0,0 +1,42 @@
+{
+  "widget": "spangroup",
+  "styleName": "defualt",
+  "part": [
+    {
+      "partName": "main",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#2092f500",
+              "image": {
+                "imgId": "",
+                "color": ""
+              }
+            },
+            "border": {
+              "color": "#000000ff",
+              "width": 0,
+              "radius": 0,
+              "side": ["all"]
+            },
+            "padding": {
+              "top": 0,
+              "right": 0,
+              "bottom": 0,
+              "left": 0
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        }
+      ]
+    }
+  ]
+}

+ 62 - 0
src/renderer/src/lvgl-widgets/textarea/Textarea.vue

@@ -0,0 +1,62 @@
+<template>
+  <div v-bind="getProps">{{ props.text }}</div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+const props = defineProps<{
+  width: number
+  height: number
+  styles: any
+  state?: string
+  text: string
+  placeholder: string
+  maxLength: number
+  allowString: string
+  passwordMode: boolean
+  nowrap: boolean
+}>()
+
+const getProps = computed(() => {
+  const styles = props.styles
+  const stateStyles = styles.find((item) => item.state === props.state)
+
+  return {
+    class: 'button',
+    style: {
+      width: `${props.width}px`,
+      height: `${props.height}px`,
+
+      backgroundColor: stateStyles?.background.color,
+
+      fontSize: `${stateStyles?.text.size}px`,
+      color: stateStyles?.text?.color,
+      display: 'flex',
+      justifyContent: stateStyles?.text?.align || 'center',
+      alignItems: 'center',
+
+      borderRadius: `${stateStyles?.border.radius}px`,
+      borderColor: 'transparent',
+      borderWidth: `${stateStyles?.border.width}px`,
+      borderTopColor: ['all', 'top'].includes(stateStyles?.border?.side)
+        ? stateStyles?.border?.color
+        : 'transparent',
+      borderRightColor: ['all', 'right'].includes(stateStyles?.border?.side)
+        ? stateStyles?.border?.color
+        : 'transparent',
+      borderBottomColor: ['all', 'bottom'].includes(stateStyles?.border?.side)
+        ? stateStyles?.border?.color
+        : 'transparent',
+      borderLeftColor: ['all', 'left'].includes(stateStyles?.border?.side)
+        ? stateStyles?.border?.color
+        : 'transparent',
+      /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
+      boxShodow: stateStyles?.shadow
+        ? `${stateStyles?.shadow?.offsetX}px ${stateStyles?.shadow?.offsetY}px ${stateStyles?.shadow?.width}px ${stateStyles?.shadow?.spread}px ${stateStyles?.shadow?.color}`
+        : 'none'
+    }
+  }
+})
+</script>
+
+<style scoped></style>

+ 221 - 0
src/renderer/src/lvgl-widgets/textarea/index.ts

@@ -0,0 +1,221 @@
+import Textarea from './Textarea.vue'
+import icon from '../assets/icon/icon_label.svg'
+import type { IComponentModelConfig } from '../type'
+import i18n from '@/locales'
+import { flagOptions } from '@/constants'
+
+export default {
+  label: i18n.global.t('textarea'),
+  icon,
+  component: Textarea,
+  key: 'lv_textarea',
+  group: i18n.global.t('basic'),
+  sort: 1,
+  parts: [
+    {
+      name: 'main',
+      stateList: ['default', 'focused']
+    },
+    {
+      name: 'scrollbar',
+      stateList: ['default', 'focused']
+    }
+  ],
+  defaultSchema: {
+    props: {
+      name: 'textarea',
+      x: 0,
+      y: 0,
+      width: 260,
+      height: 130,
+      addFlags: [],
+      removeFlags: [],
+      text: 'hello world',
+      placeholder: 'placeholder',
+      maxLength: 32,
+      allowString: '',
+      passwordMode: false,
+      nowrap: false
+    },
+    styles: [
+      {
+        part: {
+          name: 'main',
+          state: 'default'
+        },
+        background: {
+          color: '#ffffffff'
+        },
+        text: {
+          color: '#000000ff',
+          family: 'xx',
+          size: 12,
+          weight: 'normal',
+          align: 'left'
+        },
+        border: {
+          color: '#e6e6e6ff',
+          width: 2,
+          radius: 4,
+          side: ['all']
+        },
+        gap: {
+          row: 0,
+          column: 2
+        },
+        padding: {
+          top: 4,
+          right: 4,
+          bottom: 4,
+          left: 4
+        },
+        shadow: {
+          color: '#2092f5ff',
+          x: 0,
+          y: 0,
+          spread: 0,
+          width: 0
+        }
+      },
+      {
+        part: {
+          name: 'scrollbar',
+          state: 'default'
+        },
+        background: {
+          color: '#2195f6ff'
+        },
+        border: {
+          radius: 0
+        }
+      }
+    ]
+  },
+  config: {
+    // 组件属性
+    props: [
+      {
+        label: '名称',
+        field: 'name',
+        valueType: 'text',
+        componentProps: {
+          placeholder: '请输入名称'
+        }
+      },
+      {
+        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: 'addFlags',
+        valueType: 'checkbox',
+        componentProps: {
+          options: flagOptions
+        }
+      },
+      {
+        label: '删除标识',
+        field: 'removeFlags',
+        valueType: 'checkbox',
+        componentProps: {
+          options: flagOptions
+        }
+      },
+      {
+        label: '文本',
+        field: 'text',
+        valueType: 'text',
+        componentProps: {
+          useSymbol: true
+        }
+      },
+      {
+        label: '模式',
+        field: 'mode',
+        valueType: 'select',
+        componentProps: {
+          options: [
+            { label: 'Circular', value: 'circular' },
+            { label: 'Clip', value: 'clip' },
+            { label: 'Dot', value: 'dot' },
+            { label: 'Scroll', value: 'Scroll' },
+            { label: 'Wrap', value: 'wrap' }
+          ]
+        }
+      }
+    ],
+    // 组件样式
+    styles: [
+      {
+        label: '模块状态',
+        field: 'part',
+        valueType: 'part'
+      },
+      {
+        label: '背景',
+        field: 'background',
+        valueType: 'background'
+      },
+      {
+        label: '字体',
+        field: 'text',
+        valueType: 'text'
+      },
+      {
+        label: '间距',
+        field: 'gap',
+        valueType: 'gap'
+      },
+      {
+        label: '内边距',
+        field: 'padding',
+        valueType: 'padding'
+      },
+      {
+        label: '边框',
+        field: 'border',
+        valueType: 'border'
+      },
+      {
+        label: '阴影',
+        field: 'shadow',
+        valueType: 'shadow'
+      }
+    ]
+  }
+} as IComponentModelConfig

+ 95 - 0
src/renderer/src/lvgl-widgets/textarea/style.json

@@ -0,0 +1,95 @@
+{
+  "widget": "label",
+  "styleName": "defualt",
+  "part": [
+    {
+      "partName": "main",
+      "state": [
+        {
+          "state": "default",
+          "style": {
+            "background": {
+              "color": "#2092F500",
+              "image": {
+                "imgId": "",
+                "color": ""
+              }
+            },
+            "text": {
+              "color": "#000000ff",
+              "family": "xx",
+              "size": 16,
+              "weight": 500,
+              "align": "center"
+            },
+            "gap": {
+              "row": 0,
+              "column": 0
+            },
+            "padding": {
+              "top": 0,
+              "right": 0,
+              "bottom": 0,
+              "left": 0
+            },
+            "border": {
+              "color": "#2092f5ff",
+              "width": 0,
+              "radius": 0,
+              "side": ["all"]
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        },
+        {
+          "state": "disabled",
+          "style": {
+            "background": {
+              "color": "#2092F500",
+              "image": {
+                "imgId": "",
+                "color": ""
+              }
+            },
+            "text": {
+              "color": "#000000ff",
+              "family": "xx",
+              "size": 16,
+              "weight": 500,
+              "align": "center"
+            },
+            "gap": {
+              "row": 0,
+              "column": 0
+            },
+            "padding": {
+              "top": 0,
+              "right": 0,
+              "bottom": 0,
+              "left": 0
+            },
+            "border": {
+              "color": "#2092f5ff",
+              "width": 0,
+              "radius": 0,
+              "side": ["all"]
+            },
+            "shadow": {
+              "color": "#2092f5ff",
+              "x": 0,
+              "y": 0,
+              "spread": 0,
+              "width": 0
+            }
+          }
+        }
+      ]
+    }
+  ]
+}

+ 77 - 1
src/renderer/src/store/modules/action.ts

@@ -6,6 +6,7 @@ import { moveToPosition } from '@/utils'
 import { klona } from 'klona'
 import { v4 } from 'uuid'
 
+import { useKeyPress } from 'vue-hooks-plus'
 import { useProjectStore } from '@/store/modules/project'
 import { useAppStore } from './app'
 
@@ -351,6 +352,13 @@ export const useActionStore = defineStore('action', () => {
     })
   }
 
+  /**
+   * 全选
+   */
+  const onSelectAll = () => {
+    projectStore.setSelectWidgets(projectStore.activePage?.children || [])
+  }
+
   /**
    * 显示事件
    */
@@ -359,6 +367,73 @@ export const useActionStore = defineStore('action', () => {
     appStore.compositeTabAcitve = 'event'
   }
 
+  // 键盘事件 复制
+  useKeyPress(['ctrl.c'], () => {
+    onCopy()
+  })
+  // 键盘事件 粘贴
+  useKeyPress(['ctrl.v'], () => {
+    onPaste()
+  })
+  // 键盘事件 剪切
+  useKeyPress(['ctrl.x'], () => {
+    onCut()
+  })
+  // 键盘事件 复用
+  useKeyPress(['ctrl.d'], () => {
+    onCopyFrom()
+  })
+  // 键盘事件 删除
+  useKeyPress(['del', 'backspace'], () => {
+    onDelete()
+  })
+  // 键盘事件 撤销
+  useKeyPress(['ctrl.z'], () => {
+    projectStore.undo()
+  })
+  // 键盘事件 重做
+  useKeyPress(['ctrl.shift.z'], () => {
+    projectStore.redo()
+  })
+  // 键盘事件 隐藏
+  useKeyPress(['ctrl.h'], () => {
+    onHidden(true)
+  })
+  // 键盘事件 锁定
+  useKeyPress(['ctrl.l'], () => {
+    onLock(true)
+  })
+  // 键盘事件 锁定
+  useKeyPress(['ctrl.shift.l'], () => {
+    onLock(false)
+  })
+  // 键盘事件 上移
+  useKeyPress(['ctrl.up'], () => {
+    onLevel('up')
+  })
+  // 键盘事件 下移
+  useKeyPress(['ctrl.down'], () => {
+    onLevel('down')
+  })
+  // 键盘事件 置顶
+  useKeyPress(['ctrl.shift.up'], () => {
+    onLevel('top')
+  })
+  // 键盘事件 置底
+  useKeyPress(['ctrl.shift.down'], () => {
+    onLevel('bottom')
+  })
+  // 键盘事件 全选
+  useKeyPress(['ctrl.a'], () => {
+    onSelectAll()
+  })
+  // 保存
+  useKeyPress(['ctrl.s'], () => {
+    if (projectStore.project) {
+      projectStore.saveProject()
+    }
+  })
+
   return {
     onAlign,
     onMatchSize,
@@ -372,6 +447,7 @@ export const useActionStore = defineStore('action', () => {
     onPaste,
     onCut,
     onHidden,
-    onShowEvent
+    onShowEvent,
+    onSelectAll
   }
 })

+ 10 - 8
src/renderer/src/views/designer/workspace/stage/index.vue

@@ -157,9 +157,8 @@ const handleCenter = () => {
 }
 
 const handleClick = () => {
-  // 点击画布空白
+  // 点击画布空白
   projectStore.activePageId = props.page?.id
-  projectStore.setSelectWidgets([])
 }
 
 /* ====================处理框选多个组件======================== */
@@ -216,6 +215,7 @@ const handleMouseMove = throttle((e: MouseEvent) => {
 }, 50)
 /* 鼠标抬起 */
 const handleMouseUp = (e: MouseEvent) => {
+  projectStore.setSelectWidgets([])
   if (!isMouseDown || (startX === 0 && startY === 0)) return
 
   isMouseDown.value = false
@@ -242,13 +242,15 @@ const handleSelectComponent = (startX: number, startY: number, endX: number, end
   const widgets = props.page?.children || []
   const select = widgets.filter((item) => {
     const { x, y, width, height } = item.props
-    const x1 = Math.min(startX, endX)
-    const x2 = Math.max(startX, endX)
-    const y1 = Math.min(startY, endY)
-    const y2 = Math.max(startY, endY)
-    // 返回判断完全包裹组件
-    return x >= x1 && y >= y1 && x + width <= x2 && y + height <= y2
+    const minX = Math.min(startX, endX)
+    const maxX = Math.max(startX, endX)
+    const minY = Math.min(startY, endY)
+    const maxY = Math.max(startY, endY)
+
+    // 返回判断包裹的组件
+    return x >= minX && x + width <= maxX && y >= minY && y + height <= maxY
   })
+
   projectStore.setSelectWidgets(select)
 }
 /* ====================处理框选多个组件======================== */