Browse Source

feat: 添加render渲染方法,修改富文本渲染

jiaxing.liao 2 months ago
parent
commit
278f8994e9

+ 2 - 0
src/renderer/components.d.ts

@@ -44,6 +44,7 @@ declare module 'vue' {
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
+    ElScroller: typeof import('element-plus/es')['ElScroller']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
     ElSlider: typeof import('element-plus/es')['ElSlider']
@@ -101,6 +102,7 @@ declare global {
   const ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
   const ElRow: typeof import('element-plus/es')['ElRow']
   const ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
+  const ElScroller: typeof import('element-plus/es')['ElScroller']
   const ElSelect: typeof import('element-plus/es')['ElSelect']
   const ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
   const ElSlider: typeof import('element-plus/es')['ElSlider']

+ 2 - 2
src/renderer/src/lvgl-widgets/button-matrix/ButtonMatrix.vue

@@ -34,7 +34,7 @@ const getProps = computed((): Record<string, CSSProperties> => {
   const styles = props.styles
   let mainStyle = styles.find((item) => item.state === props.state && item.part.name === 'main')
   let itemsStyle = styles.find((item) => item.state === props.state && item.part.name === 'items')
-  console.log(mainStyle, itemsStyle)
+
   // 从默认样式获取样式
   if (!mainStyle && props.state) {
     mainStyle = defaultStyle.part
@@ -108,7 +108,7 @@ const getProps = computed((): Record<string, CSSProperties> => {
       textAlign: itemsStyle?.text?.align,
       textDecoration: itemsStyle?.text?.strike ? 'line-through' : itemsStyle?.text?.underline,
       /* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
-      boxShadow: itemsStyle?.box
+      boxShadow: itemsStyle?.shadow
         ? `${itemsStyle.shadow?.x}px ${itemsStyle.shadow?.y}px ${itemsStyle.shadow?.width}px ${itemsStyle.shadow?.spread}px ${itemsStyle.shadow?.color}`
         : 'none'
     },

+ 82 - 0
src/renderer/src/lvgl-widgets/button-matrix/Config.vue

@@ -0,0 +1,82 @@
+<template>
+  <el-card class="mb-12px">
+    <template #header>
+      <div class="flex items-center justify-between">
+        <span>按钮组</span>
+        <el-button type="primary" :icon="LuPlus" @click="handleAddRow"></el-button>
+      </div>
+    </template>
+    <div
+      class="flex items-center gap-4px mb-4px"
+      v-for="(row, rowIndex) in modelValue || []"
+      :key="rowIndex"
+    >
+      <div class="relative group/item" v-for="(_val, columnIndex) in row" :key="columnIndex">
+        <el-input v-model="modelValue[rowIndex][columnIndex]" />
+        <div
+          class="absolute top--7px right--4px cursor-pointer invisible group-hover/item:visible"
+          @click="handleDeleteItem(rowIndex, columnIndex)"
+        >
+          <LuX size="14px" />
+        </div>
+      </div>
+
+      <el-button
+        type="primary"
+        :icon="LuPlus"
+        :disabled="row.length === 5"
+        @click="handleAddItem(rowIndex)"
+      ></el-button>
+    </div>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import { LuPlus, LuX } from 'vue-icons-plus/lu'
+
+const props = defineProps<{
+  values: any
+}>()
+
+const modelValue = computed({
+  get() {
+    return props.values?.value
+  },
+  set(val) {
+    props.values.value = val
+  }
+})
+
+/**
+ * 添加一项
+ * @param rowIndex 行索引
+ */
+const handleAddItem = (rowIndex: number | string) => {
+  const row = modelValue.value?.[rowIndex]
+  if (row?.length < 5) {
+    row.push('0')
+  }
+}
+
+/**
+ * 删除一项
+ * @param rowIndex 行索引
+ * @param index 索引
+ */
+const handleDeleteItem = (rowIndex: number | string, index: number | string) => {
+  modelValue.value[rowIndex].splice(index, 1)
+  if (modelValue.value[rowIndex].length === 0) {
+    modelValue.value.splice(rowIndex, 1)
+  }
+}
+
+/**
+ * 添加一行
+ */
+const handleAddRow = () => {
+  modelValue.value?.push(['0', '0', '0'])
+}
+</script>
+
+<style scoped></style>

+ 10 - 2
src/renderer/src/lvgl-widgets/button-matrix/index.ts

@@ -4,6 +4,7 @@ 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('buttonMatrix'),
@@ -169,6 +170,13 @@ export default {
           options: flagOptions,
           defaultCollapsed: true
         }
+      },
+      {
+        label: '属性',
+        field: 'props.group',
+        render: (val) => {
+          return <Config values={val} />
+        }
       }
     ],
     // 组件样式
@@ -192,12 +200,12 @@ export default {
         valueType: 'dependency',
         name: ['part'],
         dependency: ({ part }) => {
-          return part === 'items'
+          return part?.name === 'items'
             ? [
                 {
                   label: '字体',
                   field: 'text',
-                  valueType: 'text'
+                  valueType: 'font'
                 },
                 {
                   label: '阴影',

+ 2 - 2
src/renderer/src/lvgl-widgets/index.ts

@@ -1,8 +1,8 @@
 import Button from './button'
 import ImageButton from './image-button'
-import MatrixButton from './button-matrix'
+import MatrixButton from './button-matrix/index'
 import Image from './image'
-import SpanGroup from './span-group'
+import SpanGroup from './span-group/index'
 
 import Container from './container'
 import Label from './label'

+ 2 - 2
src/renderer/src/lvgl-widgets/page/Page.vue

@@ -18,8 +18,8 @@ import { getImageByPath } from '@/utils'
 import { useProjectStore } from '@/store/modules/project'
 
 const props = defineProps<{
-  width: number
-  height: number
+  width?: number
+  height?: number
   styles: any
   state?: string
 }>()

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

@@ -46,7 +46,7 @@ export default {
       },
       {
         label: '滚动条',
-        field: 'props.scollbarMode',
+        field: 'props.scrollbarMode',
         valueType: 'select',
         componentProps: {
           options: scrollbarModes

+ 163 - 0
src/renderer/src/lvgl-widgets/span-group/Config.vue

@@ -0,0 +1,163 @@
+<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="字体颜色">
+          <ColorPicker
+            v-model:pureColor="formData.text_color"
+            format="hex8"
+            picker-type="chrome"
+            use-type="pure"
+          >
+            <template #trigger>
+              <BiFontColor size="22px" :style="{ color: formData.text_color }" />
+            </template>
+          </ColorPicker>
+          <span class="text-text-active">{{ formData.text_color }}</span>
+        </el-form-item>
+        <el-form-item label="字体" label-width="60px">
+          <el-select-v2
+            v-model="formData.font_family"
+            placeholder="请选择"
+            :options="fontOptions"
+          />
+        </el-form-item>
+        <el-form-item label="字体大小">
+          <el-input-number
+            v-model="formData.font_size"
+            controls-position="right"
+            placeholder="请输入"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="">
+          <el-radio-group v-model="formData.text_decor">
+            <el-radio-button :value="LineEnum.LV_TEXT_DECOR_NONE">
+              <el-tooltip content="none">
+                <MdNotInterested size="14px" />
+              </el-tooltip>
+            </el-radio-button>
+            <el-radio-button :value="LineEnum.LV_TEXT_DECOR_UNDERLINE">
+              <el-tooltip content="underline">
+                <RxUnderline size="14px" />
+              </el-tooltip>
+            </el-radio-button>
+            <el-radio-button :value="LineEnum.LV_TEXT_DECOR_STRIKETHROUGH">
+              <el-tooltip content="strikethrough">
+                <RxStrikethrough size="14px" />
+              </el-tooltip>
+            </el-radio-button>
+            <el-radio-button
+              :value="LineEnum['LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH']"
+            >
+              <el-tooltip content="strikethrough and underline">
+                <GoStrikethrough size="14px" />
+              </el-tooltip>
+            </el-radio-button>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="文本">
+          <el-input type="textarea" v-model="formData.text" placeholder="请输入文本" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="dialogVisible = false">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { type SpanItem, LineEnum } from './data'
+
+import { computed, type Ref, ref } from 'vue'
+import { LuPlus, LuTrash2 } from 'vue-icons-plus/lu'
+import { v4 } from 'uuid'
+import { ColorPicker } from '@/components'
+import { BiFontColor } from 'vue-icons-plus/bi'
+import { MdNotInterested } from 'vue-icons-plus/md'
+import { RxUnderline, RxStrikethrough } from 'vue-icons-plus/rx'
+import { GoStrikethrough } from 'vue-icons-plus/go'
+import { useProjectStore } from '@/store/modules/project'
+
+const props = defineProps<{
+  values: Ref<SpanItem[]>
+}>()
+
+const dialogVisible = ref(false)
+const formData = ref<SpanItem>({
+  text: '',
+  text_color: '',
+  font_family: '',
+  font_size: 0,
+  text_decor: LineEnum.LV_TEXT_DECOR_NONE
+})
+
+const projectStore = useProjectStore()
+// 字体选项
+const fontOptions = computed(() => {
+  const list = (projectStore.project?.resources.fonts || []).map((font) => {
+    return {
+      label: font.fileName,
+      value: font.id
+    }
+  })
+  return [{ label: '默认字体', value: 'xx' }, ...list]
+})
+
+/**
+ * 添加文本项
+ */
+const handleAdd = () => {
+  props.values?.value?.push({
+    text: 'hello world',
+    text_color: '#000000ff',
+    font_family: 'xx',
+    font_size: 16,
+    text_decor: LineEnum.LV_TEXT_DECOR_NONE
+  })
+}
+
+/**
+ * 删除文本项
+ */
+const handleDelete = (index: number) => {
+  props.values?.value?.splice(index, 1)
+}
+
+/**
+ * 清除文本项
+ */
+const handleClear = () => {
+  props.values.value = []
+}
+
+const handleEdit = (record: SpanItem) => {
+  formData.value = record
+  dialogVisible.value = true
+}
+</script>
+
+<style scoped></style>

+ 23 - 14
src/renderer/src/lvgl-widgets/span-group/SpanGroup.vue

@@ -1,8 +1,12 @@
 <template>
   <div v-bind="getProps">
-    <span v-for="(item, index) in items" :key="index" :style="getSpanStyle(item)">{{
-      item.text
-    }}</span>
+    <span
+      class="shrink-0"
+      v-for="(item, index) in items"
+      :key="index"
+      :style="getSpanStyle(item)"
+      >{{ item.text }}</span
+    >
   </div>
 </template>
 
@@ -10,26 +14,30 @@
 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
-}
+import type { SpanItem } from './data'
 
 const props = defineProps<{
   width: number
   height: number
   styles: any
   state?: string
-  mode: 'break' | 'fixed' | 'Expand'
-  items: Item[]
+  mode: 'break' | 'fixed' | 'expand'
+  items: SpanItem[]
 }>()
 
 const getProps = computed(() => {
   const styles = props.styles
   const stateStyles = styles.find((item) => item.state === props.state)
+  const otherStyles: CSSProperties = {}
+  console.log(props.mode)
+  if (props.mode === 'expand') {
+    otherStyles.display = 'flex'
+    otherStyles.flexWrap = 'nowrap'
+  }
+
+  if (props.mode === 'fixed') {
+    otherStyles.overflow = 'hidden'
+  }
 
   return {
     class: 'button',
@@ -67,13 +75,14 @@ const getProps = computed(() => {
         ? `${stateStyles?.shadow?.x}px ${stateStyles?.shadow?.y}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`
+      padding: `${stateStyles?.padding.top}px ${stateStyles?.padding.right}px ${stateStyles?.padding.bottom}px ${stateStyles?.padding.left}px`,
+      ...otherStyles
     } as CSSProperties
   }
 })
 
 // 获取子项样式
-const getSpanStyle = (item: Item) => {
+const getSpanStyle = (item: SpanItem) => {
   const lineStyle =
     item.text_decor === LineEnum['LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH']
       ? 'underline line-through'

+ 12 - 4
src/renderer/src/lvgl-widgets/span-group/data.ts

@@ -1,6 +1,14 @@
 export enum LineEnum {
-  'LV_TEXT_DECOR_NONE',
-  'LV_TEXT_DECOR_UNDERLINE',
-  'LV_TEXT_DECOR_STRIKETHROUGH',
-  'LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH'
+  'LV_TEXT_DECOR_NONE' = 'LV_TEXT_DECOR_NONE',
+  'LV_TEXT_DECOR_UNDERLINE' = 'LV_TEXT_DECOR_UNDERLINE',
+  'LV_TEXT_DECOR_STRIKETHROUGH' = 'LV_TEXT_DECOR_STRIKETHROUGH',
+  'LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH' = 'LV_TEXT_DECOR_UNDERLINE | LV_TEXT_DECOR_STRIKETHROUGH'
+}
+
+export type SpanItem = {
+  text: string
+  text_color: string
+  font_family: string
+  font_size: number
+  text_decor: LineEnum
 }

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

@@ -4,6 +4,7 @@ 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('richText'),
@@ -152,6 +153,13 @@ export default {
             { label: 'Fixed', value: 'fixed' }
           ]
         }
+      },
+      {
+        label: '内容列表',
+        field: 'props.items',
+        render: (val) => {
+          return <Config values={val} />
+        }
       }
     ],
     // 组件样式

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

@@ -251,6 +251,7 @@ export const useActionStore = defineStore('action', () => {
         })
       })
     })
+    projectStore.activeWidgets = []
   }
 
   /**

+ 32 - 4
src/renderer/src/views/designer/config/property/CusFormItem.vue

@@ -34,7 +34,7 @@
           <el-row :gutter="12" class="px-4px">
             <CusFormItem
               v-for="(item, index) in schema.children"
-              :key="item.valueType + index"
+              :key="item.valueType + '_' + index"
               :schema="item"
               :form-data="formData"
             />
@@ -49,9 +49,6 @@
       </el-collapse-item>
     </el-collapse>
 
-    <!-- 自定义渲染 -->
-    <component v-if="schema?.render" :is="getComp(schema.render)" />
-
     <!-- 样式配置 -->
     <el-card v-if="isStyle" :header="schema.label" body-class="p-8px!" class="mb-12px">
       <!-- 模块 -->
@@ -77,7 +74,19 @@
       <!-- 线段  -->
       <StyleLine v-if="schema.valueType === 'line'" v-model="value" />
     </el-card>
+
+    <!-- 自定义渲染 -->
+    <component v-if="schema?.render" :is="getComp(schema.render)" />
   </el-col>
+
+  <!-- 依赖项 -->
+  <CusFormItem
+    v-if="schema.valueType === 'dependency'"
+    v-for="(item, index) in dependencyFormItems"
+    :key="index"
+    :schema="item"
+    :form-data="formData"
+  />
 </template>
 
 <script setup lang="tsx">
@@ -182,6 +191,25 @@ const getComp = (fun: any) => {
     }
   })
 }
+
+/**
+ * 依赖项
+ * 1. 根据依赖性的name获取对应值
+ * 2. 根据值获取对应的表单项
+ */
+const dependencyFormItems = computed(() => {
+  const { schema, formData } = props
+  if (schema.valueType === 'dependency') {
+    const values: { [key: string]: any } = {}
+    schema?.name?.forEach((key) => {
+      const val = get(formData, key)
+      values[key] = val
+    })
+    const result = schema.dependency(values) || []
+    return Array.isArray(result) ? result : [result]
+  }
+  return []
+})
 </script>
 
 <style lang="less" scoped>

+ 1 - 1
src/renderer/src/views/designer/config/property/components/StyleFont.vue

@@ -50,7 +50,7 @@ const fontOptions = computed(() => {
       value: font.id
     }
   })
-  return [{ fileName: '默认字体', id: 'xx' }, ...list]
+  return [{ label: '默认字体', value: 'xx' }, ...list]
 })
 
 //对齐选项

+ 2 - 2
src/renderer/src/views/designer/config/property/index.vue

@@ -11,7 +11,7 @@
           <el-row :gutter="12">
             <CusFormItem
               v-for="(item, index) in formConfig.props || []"
-              :key="item.valueType + index"
+              :key="item.valueType + '_' + index"
               :schema="item"
               :formData="projectStore.activeWidget!"
             />
@@ -35,7 +35,7 @@
           <el-row :gutter="12" v-if="styleFormData">
             <CusFormItem
               v-for="(item, index) in formConfig.styles || []"
-              :key="item.valueType + index"
+              :key="item.valueType + '_' + index"
               :schema="item"
               :formData="styleFormData"
             />

+ 8 - 0
src/renderer/src/views/designer/sidebar/components/ScreenTreeItem.vue

@@ -93,6 +93,14 @@ const addPage = (screen: Screen) => {
   })
   // 选择当前页面
   projectStore.activePageId = newScreen.id
+  // 当前屏幕打开的页面
+  projectStore.project?.screens.forEach((screen, index) => {
+    screen.pages.forEach((page) => {
+      if (page.id === newScreen.id) {
+        projectStore.openPages[index] = page
+      }
+    })
+  })
 }
 
 // 删除页面

+ 2 - 1
src/renderer/src/views/designer/workspace/stage/index.vue

@@ -177,7 +177,8 @@ const handleMouseDown = (e: MouseEvent) => {
     target?.closest('.refer-line') ||
     target?.closest('.ignore-click') ||
     target?.closest('.moveable-area') ||
-    target?.closest('.moveable-control')
+    target?.closest('.moveable-control') ||
+    target?.closest('.moveable-edge')
   ) {
     return
   }