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

feat: 完善样式部分设置、打开/关闭、折叠/展开等功能

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

+ 84 - 16
src/renderer/src/lvgl-widgets/button-matrix/Config.vue

@@ -75,19 +75,21 @@
             <div class="w-full">
               <StylePart
                 v-model="part"
-                :hasStyle="!!styleFormData"
+                :form-data="styleFormData"
+                :form-items-schema="btn.config.styles || []"
                 @change="onChangeStyleByState"
               />
             </div>
           </el-form-item>
 
-          <el-row :gutter="12" v-if="styleFormData">
+          <el-row :gutter="12" v-if="part.expandStyle">
             <CusFormItem
               v-for="(item, index) in btn.config.styles || []"
               :key="item.valueType + '_' + index"
               :schema="item"
               :formData="styleFormData"
               :widgetData="projectStore.activeWidget!"
+              @change-state-style="onChangeStateStyle"
             />
           </el-row>
         </el-form>
@@ -100,7 +102,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref, provide, type Ref } from 'vue'
+import { computed, ref, provide, type Ref, watch } from 'vue'
 import { LuPlus, LuX } from 'vue-icons-plus/lu'
 import { ButtonItem } from './data'
 import { getNextIndex } from '@/utils'
@@ -108,8 +110,11 @@ import { useProjectStore } from '@/store/modules/project'
 import btn from './index'
 import { klona } from 'klona'
 import { FormInstance } from 'element-plus'
+import { get, assign } from 'lodash-es'
+
 import StylePart from '@/views/designer/config/property/components/StylePart.vue'
 import CusFormItem from '@/views/designer/config/property/CusFormItem.vue'
+import { IoColorPaletteOutline } from 'vue-icons-plus/io'
 
 const props = defineProps<{
   values: Ref<{
@@ -255,32 +260,95 @@ const styleFormData = computed(() => {
 const part = ref<{
   name: string
   state: string
+  expandStyle: boolean
 }>({
   name: 'items',
-  state: 'default'
+  state: 'default',
+  expandStyle: true
 })
 
-const onChangeStyleByState = (type: 'add' | 'delete') => {
-  if (!part.value.name || !part.value.state) return
-  if (type === 'add') {
-    // 从全局样式查找控件样式
-    const widgetType = 'lv_buttonmatrix'
-    const style = projectStore.globalStyle
+watch(
+  () => open.value,
+  (val) => {
+    if (val) {
+      part.value = {
+        name: 'items',
+        state: 'default',
+        expandStyle: true
+      }
+    }
+  }
+)
+
+/**
+ * 获取控件预设样式
+ */
+const getWidgetDefaultStyle = () => {
+  const widgetType = 'lv_buttonmatrix'
+
+  const state = projectStore.globalStyle
+    ?.find((item) => item.widget === widgetType)
+    ?.part?.find((item) => item.partName === part.value.name)?.state
+
+  const defaultStyle =
+    projectStore.globalStyle
       ?.find((item) => item.widget === widgetType)
-      ?.part?.find((item) => item.partName === part.value.name)
-      ?.state?.find((item) => item.state === part.value.state)?.style
+      ?.part?.find((item) => item.partName === part.value.name)?.defaultStyle || {}
+
+  const stateStyle = state?.find((item) => item.state === part.value.state)?.style || {}
 
-    // 找多对应样式时添加
-    if (style) {
+  return {
+    defaultStyle,
+    stateStyle
+  }
+}
+
+/**
+ * 设置控件状态部分样式
+ * @param field 样式字段名
+ * @param type 修改类型
+ */
+const onChangeStateStyle = (field: string, type: 'add' | 'delete') => {
+  if (type === 'add') {
+    const { defaultStyle, stateStyle } = getWidgetDefaultStyle()
+
+    const style = get(assign({}, defaultStyle, stateStyle), field)
+
+    if (styleFormData.value) {
+      styleFormData.value[field] = style
+    } else {
       const result = klona({
-        ...style,
+        [field]: style,
         part: {
           name: part.value.name,
           state: part.value.state
         }
       })
-      formData.value.style?.push(result)
+      formData.value?.style?.push(result)
     }
+  } else {
+    if (styleFormData.value?.[field]) {
+      delete styleFormData.value[field]
+    }
+  }
+}
+
+/**
+ * 开启/关闭控件部件状态样式
+ * @param type
+ */
+const onChangeStyleByState = (type: 'add' | 'delete') => {
+  if (type === 'add') {
+    const { defaultStyle, stateStyle } = getWidgetDefaultStyle()
+    // 找多对应样式时添加 状态的样式合并到默认样式
+    const result = klona({
+      ...assign({}, defaultStyle, stateStyle),
+      part: {
+        name: part.value.name,
+        state: part.value.state
+      }
+    })
+    formData.value?.style?.push(result)
   } else {
     // 删除样式
     const index =

+ 48 - 16
src/renderer/src/lvgl-widgets/hooks/useWidgetStyle.ts

@@ -3,8 +3,9 @@ import { useProjectStore } from '@/store/modules/project'
 import { assign } from 'lodash-es'
 import { ref, watch } from 'vue'
 
-import type { IStyleConfig } from '../type'
+import type { IStyleConfig, PartItem } from '../type'
 import type { CSSProperties } from 'vue'
+import { klona } from 'klona'
 
 type StyleParam = {
   widget: keyof typeof componentMap
@@ -161,12 +162,51 @@ export const useWidgetStyle = (param: StyleParam) => {
 
   const widget = param.widget
   // 全局默认样式
-  const defaultStyle = componentMap[widget]?.defaultStyle
+  const widgetDefaultStyle = componentMap[widget]?.defaultStyle
   // 控件模块状态
   const parts = componentMap[widget].parts
   // 控件样式集合
   const styleMap = ref<StyleMap>({})
 
+  /**
+   * 获取控件兜底预设样式
+   */
+  const getPresetDefaultStyle = (partItem: PartItem) => {
+    const { part, state } = param.props
+
+    const defaultStyle = widgetDefaultStyle?.part?.find(
+      (item) => item.partName === partItem.name
+    )?.defaultStyle
+
+    const stateStyle = widgetDefaultStyle?.part
+      ?.find((item) => item.partName === partItem.name)
+      ?.state?.find(
+        (s) => s.state === (part === partItem.name ? state : partItem.stateList?.[0])
+      )?.style
+
+    return klona(assign({}, defaultStyle, stateStyle))
+  }
+
+  /**
+   * 获取控件默认样式
+   */
+  const getWidgetDefaultStyle = (partName: string, stateName?: string) => {
+    const widgetType = projectStore.activeWidget?.type
+
+    const state = projectStore.globalStyle
+      ?.find((item) => item.widget === widgetType)
+      ?.part?.find((item) => item.partName === partName)?.state
+
+    const defaultStyle =
+      projectStore.globalStyle
+        ?.find((item) => item.widget === widgetType)
+        ?.part?.find((item) => item.partName === partName)?.defaultStyle || {}
+
+    const stateStyle = state?.find((item) => item.state === stateName)?.style || {}
+
+    return klona(assign({}, defaultStyle, stateStyle))
+  }
+
   const handleStyle = () => {
     const { part, state, styles = [] } = param.props
     parts.forEach((partItem) => {
@@ -180,22 +220,14 @@ export const useWidgetStyle = (param: StyleParam) => {
           s.part?.state === (part === partItem.name ? state : partItem.stateList?.[0])
       )
 
-      // 配置样式未配置时,从默认样式中获取
-      if (!style) {
-        style = defaultStyle?.part
-          ?.find((item) => item.partName === partItem.name)
-          ?.state?.find(
-            (s) => s.state === (part === partItem.name ? state : partItem.stateList?.[0])
-          )?.style
-      }
-
-      // 预设默认样式
-      const presetStyle = defaultStyle?.part?.find(
-        (item) => item.partName === partItem.name
-      )?.defaultStyle
+      const presetStyle = getPresetDefaultStyle(partItem)
+      const defaultStyle = getWidgetDefaultStyle(
+        partItem.name,
+        part === partItem.name ? state : partItem.stateList?.[0]
+      )
 
       // 合并到预设样式
-      style = assign(presetStyle || {}, style || {})
+      style = assign({}, presetStyle || {}, defaultStyle || {}, style || {})
 
       // 遍历style 获取样式
       Object.keys(style || {}).forEach((key) => {

+ 1 - 1
src/renderer/src/lvgl-widgets/window/Window.vue

@@ -2,7 +2,7 @@
   <div :style="styleMap?.mainStyle" class="w-full h-full flex flex-col overflow-hidden box-broder">
     <div
       :style="{ ...styleMap?.headerStyle, height: titleHeight + 'px' }"
-      class="flex items-center justify-between box-broder p-10px"
+      class="flex items-center justify-between box-broder p-10px whitespace-pre"
     >
       <span>{{ title }}</span>
       <div class="flex items-center gap-4px">

+ 7 - 0
src/renderer/src/style.less

@@ -70,6 +70,13 @@ body {
   border: none !important;
 }
 
+.el-textarea {
+  textarea {
+    min-height: 32px !important;
+    line-height: 22px;
+  }
+}
+
 [data-state="hover"], [data-state="drag"] {
   background: var(--accent-blue);
 }

+ 98 - 77
src/renderer/src/views/designer/config/property/CusFormItem.vue

@@ -17,6 +17,7 @@
         spellcheck="false"
         :type="schema?.slots ? 'text' : 'textarea'"
         :rows="1"
+        resize="none"
         v-bind="schema?.componentProps"
       >
         <template #prefix>
@@ -108,81 +109,97 @@
     </el-collapse>
 
     <!-- 样式配置 -->
-    <el-card v-if="isStyle" :header="schema.label" body-class="p-8px!" class="mb-12px">
-      <!-- 模块 -->
-      <!-- <StylePart v-if="schema.valueType === 'part'" v-model="value" /> -->
-      <!-- 背景 -->
-      <StyleBackground
-        v-if="schema.valueType === 'background'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
-      <!-- 边框 -->
-      <StyleBorder
-        v-if="schema.valueType === 'border'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
-      <!-- 字体 -->
-      <StyleFont
-        v-if="schema.valueType === 'font'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
-      <!-- 外边距 -->
-      <StyleMargin
-        v-if="schema.valueType === 'margin'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
-      <!-- 内边距 -->
-      <StylePadding
-        v-if="schema.valueType === 'padding'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
-      <!-- 阴影 -->
-      <StyleShadow
-        v-if="schema.valueType === 'shadow'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
-      <!-- 间距 -->
-      <StyleSpace
-        v-if="schema.valueType === 'spacer'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
-      <!-- 线段  -->
-      <StyleLine
-        v-if="schema.valueType === 'line'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
-      <!-- 外边框 -->
-      <StyleOutline
-        v-if="schema.valueType === 'outline'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
-      <!-- 图像样式 -->
-      <StyleImage
-        v-if="schema.valueType === 'imageStyle'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
-      <!-- 变换样式 -->
-      <StyleTransform
-        v-if="schema.valueType === 'transform'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
-      <!-- 动画样式 -->
-      <StyleAnimation
-        v-if="schema.valueType === 'animation'"
-        v-model="value"
-        v-bind="schema?.componentProps"
-      />
+    <el-card
+      v-if="isStyle"
+      :body-class="value ? 'p-8px!' : 'hidden'"
+      :header-class="value ? '' : 'border-b-none!'"
+      class="mb-12px"
+    >
+      <template #header>
+        <div class="flex items-center justify-between">
+          <span>{{ schema.label }}</span>
+          <el-checkbox
+            :model-value="!!value"
+            @change="(val) => $emit('changeStateStyle', schema?.field!, val ? 'add' : 'delete')"
+          />
+        </div>
+      </template>
+      <template v-if="value">
+        <!-- 模块 -->
+        <!-- <StylePart v-if="schema.valueType === 'part'" v-model="value" /> -->
+        <!-- 背景 -->
+        <StyleBackground
+          v-if="schema.valueType === 'background'"
+          v-model="value"
+          v-bind="schema?.componentProps"
+        />
+        <!-- 边框 -->
+        <StyleBorder
+          v-if="schema.valueType === 'border'"
+          v-model="value"
+          v-bind="schema?.componentProps"
+        />
+        <!-- 字体 -->
+        <StyleFont
+          v-if="schema.valueType === 'font'"
+          v-model="value"
+          v-bind="schema?.componentProps"
+        />
+        <!-- 外边距 -->
+        <StyleMargin
+          v-if="schema.valueType === 'margin'"
+          v-model="value"
+          v-bind="schema?.componentProps"
+        />
+        <!-- 内边距 -->
+        <StylePadding
+          v-if="schema.valueType === 'padding'"
+          v-model="value"
+          v-bind="schema?.componentProps"
+        />
+        <!-- 阴影 -->
+        <StyleShadow
+          v-if="schema.valueType === 'shadow'"
+          v-model="value"
+          v-bind="schema?.componentProps"
+        />
+        <!-- 间距 -->
+        <StyleSpace
+          v-if="schema.valueType === 'spacer'"
+          v-model="value"
+          v-bind="schema?.componentProps"
+        />
+        <!-- 线段  -->
+        <StyleLine
+          v-if="schema.valueType === 'line'"
+          v-model="value"
+          v-bind="schema?.componentProps"
+        />
+        <!-- 外边框 -->
+        <StyleOutline
+          v-if="schema.valueType === 'outline'"
+          v-model="value"
+          v-bind="schema?.componentProps"
+        />
+        <!-- 图像样式 -->
+        <StyleImage
+          v-if="schema.valueType === 'imageStyle'"
+          v-model="value"
+          v-bind="schema?.componentProps"
+        />
+        <!-- 变换样式 -->
+        <StyleTransform
+          v-if="schema.valueType === 'transform'"
+          v-model="value"
+          v-bind="schema?.componentProps"
+        />
+        <!-- 动画样式 -->
+        <StyleAnimation
+          v-if="schema.valueType === 'animation'"
+          v-model="value"
+          v-bind="schema?.componentProps"
+        />
+      </template>
     </el-card>
 
     <!-- 自定义渲染 -->
@@ -227,9 +244,13 @@ defineOptions({
   name: 'CusFormItem'
 })
 
+const emit = defineEmits<{
+  changeStateStyle: [field: string, type: 'add' | 'delete']
+}>()
+
 const props = defineProps<{
   schema: ComponentSchema
-  formData: Record<string, any>
+  formData?: Record<string, any>
   // 控件数据,补充额外数据
   widgetData?: Record<string, any>
 }>()
@@ -243,7 +264,7 @@ const value = computed({
   },
   set(val) {
     const { schema } = props
-    if (schema.field) {
+    if (schema.field && props.formData) {
       set(props.formData, schema.field, val)
     }
     // 触发change事件

+ 83 - 9
src/renderer/src/views/designer/config/property/components/StylePart.vue

@@ -1,6 +1,6 @@
 <template>
   <el-row :gutter="12" align="middle">
-    <el-col :span="11">
+    <el-col :span="10">
       <el-select-v2
         placeholder="模块"
         :options="partOptions"
@@ -8,13 +8,24 @@
         @change="handleChangePart"
       />
     </el-col>
-    <el-col :span="13">
+    <el-col :span="14">
       <div class="flex items-center gap-12px">
         <el-select-v2 placeholder="状态" :options="stateOptions || []" v-model="state" />
-        <el-checkbox
-          :model-value="hasStyle"
-          @change="(val) => $emit('change', val ? 'add' : 'delete')"
-        ></el-checkbox>
+        <div class="flex flex-col items-center mr-8px gap-4px">
+          <el-tooltip ref="openTooltipRef" content="启用此样式">
+            <el-checkbox
+              :model-value="openAllStyle"
+              style="height: 20px"
+              @change="onChangeOpen"
+            ></el-checkbox>
+          </el-tooltip>
+          <el-tooltip ref="expandTooltipRef" content="折叠/展开">
+            <span class="flex" @click="onChangeExpand">
+              <lu-chevrons-down v-if="!expandStyle" size="12px" class="cursor-pointer" />
+              <lu-chevrons-up v-else size="12px" class="cursor-pointer" />
+            </span>
+          </el-tooltip>
+        </div>
       </div>
     </el-col>
   </el-row>
@@ -22,26 +33,34 @@
 
 <script setup lang="ts">
 import type { Ref } from 'vue'
-import { computed, inject } from 'vue'
+import { computed, inject, ref } from 'vue'
+import { has } from 'lodash-es'
 
-import type { PartItem } from '@/lvgl-widgets/type'
+import type { ComponentSchema, PartItem } from '@/lvgl-widgets/type'
+import { LuChevronsDown, LuChevronsUp } from 'vue-icons-plus/lu'
+import type { TooltipInstance } from 'element-plus'
 
 const parts = inject<Ref<PartItem[]>>('parts')
+const openTooltipRef = ref<TooltipInstance>()
+const expandTooltipRef = ref<TooltipInstance>()
 
 const modelValue = defineModel<{
   name: string
   state: string
+  expandStyle: boolean
 }>('modelValue')
 
 const props = defineProps<{
-  hasStyle: boolean
+  formData?: Record<string, any>
   widgetData?: Record<string, any>
+  formItemsSchema?: ComponentSchema[]
   componentProps?: {
     // 处理part选项方法
     filterPartOptions?: (item: PartItem, widgetData?: Record<string, any>) => any
     [key: string]: any
   }
 }>()
+
 const emit = defineEmits<{
   (e: 'change', val: 'add' | 'delete'): void
 }>()
@@ -80,6 +99,61 @@ const state = computed({
   }
 })
 
+// 展开/折叠全部样式
+const expandStyle = computed({
+  get() {
+    return modelValue.value?.expandStyle
+  },
+  set(val: boolean) {
+    if (modelValue.value) {
+      modelValue.value.expandStyle = val
+    }
+  }
+})
+
+const onChangeOpen = (val) => {
+  openTooltipRef.value?.hide()
+  emit('change', val ? 'add' : 'delete')
+}
+
+const onChangeExpand = () => {
+  expandTooltipRef.value?.hide()
+  expandStyle.value = !expandStyle.value
+}
+
+const getFields = (arr: ComponentSchema[]) => {
+  const keys = (arr || []).map((config) => {
+    if (config.valueType === 'dependency') {
+      return getFields(config.dependency({ part: modelValue.value }))
+    } else {
+      return config.field
+    }
+  })
+  return keys
+}
+
+// 开启/关闭全部样式
+const openAllStyle = computed({
+  get() {
+    const formData = props.formData || {}
+    const valueLength = Object.keys(formData).length
+    const fields = getFields(props.formItemsSchema || []).flat(Infinity)
+
+    let hasNotValue = false
+    fields.forEach((field) => {
+      if (!has(formData, field) && field !== 'part') {
+        hasNotValue = true
+      }
+    })
+
+    // 所有样式都开启
+    return !!valueLength && !hasNotValue
+  },
+  set(val: boolean) {
+    emit('change', val ? 'add' : 'delete')
+  }
+})
+
 const stateOptions = computed(() => {
   return parts?.value
     ?.find?.((item) => item.name === part.value)

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

@@ -64,23 +64,25 @@
             <div class="w-full">
               <StylePart
                 v-model="part"
-                :hasStyle="!!styleFormData"
-                @change="onChangeStyleByState"
+                :formData="styleFormData"
                 :widgetData="projectStore.activeWidget!"
+                :formItemsSchema="formConfig.styles"
                 :componentProps="
                   formConfig.styles.find((item) => item.field === 'part')?.componentProps
                 "
+                @change="onChangeStyleByState"
               />
             </div>
           </el-form-item>
 
-          <el-row :gutter="12" v-if="styleFormData">
+          <el-row :gutter="12" v-show="part.expandStyle">
             <CusFormItem
               v-for="(item, index) in formConfig.styles || []"
               :key="item.valueType + '_' + index"
               :schema="item"
               :formData="styleFormData"
               :widgetData="projectStore.activeWidget!"
+              @change-state-style="onChangeStateStyle"
             />
           </el-row>
         </el-form>
@@ -100,6 +102,7 @@ import type { ScrollbarInstance } from 'element-plus'
 import { LuSliders } from 'vue-icons-plus/lu'
 import { IoColorPaletteOutline } from 'vue-icons-plus/io'
 import { klona } from 'klona'
+import { assign, get } from 'lodash-es'
 
 const projectStore = useProjectStore()
 const activeKeys = ref<string[]>(['props', 'style'])
@@ -107,9 +110,11 @@ const scrollbarRef = ref<ScrollbarInstance>()
 const part = ref<{
   name: string
   state: string
+  expandStyle: boolean
 }>({
   name: '',
-  state: ''
+  state: '',
+  expandStyle: true
 })
 // 样式表单数据
 const styleFormData = computed(() => {
@@ -139,6 +144,7 @@ watch(
     if (first) {
       part.value.name = first.part.name
       part.value.state = first.part.state
+      part.value.expandStyle = true
     }
     scrollbarRef.value?.scrollTo(0, 0)
   },
@@ -160,19 +166,45 @@ watch(
   }
 )
 
-const onChangeStyleByState = (type: 'add' | 'delete') => {
-  if (type === 'add') {
-    // 从全局样式查找控件样式
-    const widgetType = projectStore.activeWidget?.type
+/**
+ * 获取控件预设样式
+ */
+const getWidgetDefaultStyle = () => {
+  const widgetType = projectStore.activeWidget?.type
+
+  const state = projectStore.globalStyle
+    ?.find((item) => item.widget === widgetType)
+    ?.part?.find((item) => item.partName === part.value.name)?.state
 
-    const state = projectStore.globalStyle
+  const defaultStyle =
+    projectStore.globalStyle
       ?.find((item) => item.widget === widgetType)
-      ?.part?.find((item) => item.partName === part.value.name)?.state
-    const style = state?.find((item) => item.state === part.value.state)?.style
-    // 找多对应样式时添加
-    if (style) {
+      ?.part?.find((item) => item.partName === part.value.name)?.defaultStyle || {}
+
+  const stateStyle = state?.find((item) => item.state === part.value.state)?.style || {}
+
+  return {
+    defaultStyle,
+    stateStyle
+  }
+}
+
+/**
+ * 设置控件状态部分样式
+ * @param field 样式字段名
+ * @param type 修改类型
+ */
+const onChangeStateStyle = (field: string, type: 'add' | 'delete') => {
+  if (type === 'add') {
+    const { defaultStyle, stateStyle } = getWidgetDefaultStyle()
+
+    const style = get(assign({}, defaultStyle, stateStyle), field)
+
+    if (styleFormData.value) {
+      styleFormData.value[field] = style
+    } else {
       const result = klona({
-        ...style,
+        [field]: style,
         part: {
           name: part.value.name,
           state: part.value.state
@@ -180,6 +212,29 @@ const onChangeStyleByState = (type: 'add' | 'delete') => {
       })
       projectStore.activeWidget?.style?.push(result)
     }
+  } else {
+    if (styleFormData.value?.[field]) {
+      delete styleFormData.value[field]
+    }
+  }
+}
+
+/**
+ * 开启/关闭控件部件状态样式
+ * @param type
+ */
+const onChangeStyleByState = (type: 'add' | 'delete') => {
+  if (type === 'add') {
+    const { defaultStyle, stateStyle } = getWidgetDefaultStyle()
+    // 找多对应样式时添加 状态的样式合并到默认样式
+    const result = klona({
+      ...assign({}, defaultStyle, stateStyle),
+      part: {
+        name: part.value.name,
+        state: part.value.state
+      }
+    })
+    projectStore.activeWidget?.style?.push(result)
   } else {
     // 删除样式
     const index =