Browse Source

feat: 添加图片下拉选择

jiaxing.liao 5 days ago
parent
commit
eea5a8e29d

+ 1 - 1
src/renderer/src/components/LocalImage/index.vue

@@ -3,7 +3,7 @@
 </template>
 
 <script setup lang="ts">
-import { defineProps, ref, watch } from 'vue'
+import { ref, watch } from 'vue'
 
 const props = defineProps<{
   src: string

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

@@ -164,7 +164,7 @@ export default {
         valueType: 'dependency',
         name: ['part'],
         dependency: ({ part }) => {
-          return part?.name === 'scrollbar'
+          return part?.name === 'main'
             ? [
                 {
                   label: '背景',
@@ -191,7 +191,10 @@ export default {
                   label: '间距',
                   field: 'gap',
                   valueType: 'gap',
-                  hide: part?.name === 'main' && part?.state === 'focused'
+                  hide: part?.name === 'main' && part?.state === 'focused',
+                  componentProps: {
+                    hideRow: true
+                  }
                 },
                 {
                   label: '内边距',

+ 1 - 1
src/renderer/src/lvgl-widgets/dropdown/Dropdown.vue

@@ -11,7 +11,7 @@
       style="padding: 0; width: 100%; height: auto"
     >
       <div
-        class="trucate h-24px leading-24px"
+        class="truncate h-24px leading-24px"
         v-for="(item, index) in options"
         :key="item"
         :style="index === 0 ? getProps.selectedStyle : ''"

+ 1 - 1
src/renderer/src/lvgl-widgets/image/Image.vue

@@ -62,7 +62,7 @@ const imgStyle = computed(() => {
   const { rotate = { x: 50, y: 0, angle: 0 } } = props
 
   return {
-    transform: `rotate(${rotate.angle}deg)`,
+    // transform: `rotate(${rotate.angle}deg)`,
     transformOrigin: `${rotate.x}px ${rotate.y}px`
   }
 })

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

@@ -11,7 +11,7 @@ export default {
   key: 'lv_image',
   group: i18n.global.t('basic'),
   sort: 1,
-  hasChildren: true,
+  hasChildren: false,
   parts: [
     {
       name: 'main',

+ 1 - 1
src/renderer/src/model/index.ts

@@ -102,7 +102,7 @@ export const createFileResource = (path: string, type: 'image' | 'font' | 'other
         path,
         compressFormat: 'none',
         colorFormat: 'RGB565A8',
-        alphaColor: '#FFFFFF00',
+        // alphaColor: '#FFFFFF00',
         alpha: 255,
         bin: '',
         ditheringAlgorithm: 'None',

+ 1 - 1
src/renderer/src/types/resource.d.ts

@@ -15,7 +15,7 @@ export type ImageResource = {
   // 透明度 0-255
   alpha: number
   // 颜色
-  alphaColor: string
+  // alphaColor: string
   // 绑定BinId
   bin: string
   // 抖动算法

+ 71 - 31
src/renderer/src/views/designer/config/property/components/ImageSelect.vue

@@ -1,45 +1,85 @@
 <template>
-  <div class="flex-1 flex items-center gap-8px">
-    <el-select-v2 v-model="modelValue" placeholder="请选择" :options="imageOptions" />
-    <el-image
-      style="width: 50px; height: 50px; flex-shrink: 0"
-      :src="imgSrc || defaultImg"
-      fit="fill"
-    />
-  </div>
+  <el-dialog title="图片选择" v-model="visible" width="800px">
+    <div class="flex h-300px overflow-y-auto gap-8px flex-wrap">
+      <div
+        class="w-100px h-100px border-solid border-border cursor-pointer flex flex-col"
+        v-for="item in imageList"
+        :key="item.id"
+        :class="item.id === selected ? 'border-accent-blue!' : ''"
+        @click="handleClick(item.id)"
+        :title="item.fileName"
+      >
+        <LocalImage class="h-70px" :src="projectStore.projectPath + item.path" />
+        <div class="h-20px leading-30px text-12px text-text-secondary px-2px truncate">
+          {{ item.fileName }}
+        </div>
+      </div>
+      <el-empty class="mx-auto" v-if="!imageList.length" description="暂无资源" />
+    </div>
+    <template #footer>
+      <el-button @click="handleCancel">取消</el-button>
+      <el-button type="primary" :disabled="!selected" @click="handleConfirm">确定</el-button>
+    </template>
+  </el-dialog>
+  <el-input :model-value="selectedFile?.fileName" readonly>
+    <template #suffix>
+      <LuX v-if="selectedFile" class="cursor-pointer" size="16px" @click="handleClear" />
+    </template>
+    <template #append>
+      <LocalImage
+        v-if="selectedFile"
+        class="h-20px cursor-pointer"
+        :src="projectStore.projectPath + selectedFile.path"
+        @click="visible = true"
+      />
+      <LuFileImage v-else class="cursor-pointer" size="20px" @click="visible = true" />
+    </template>
+  </el-input>
 </template>
 
 <script setup lang="ts">
-import { computed, ref, watch } from 'vue'
+import { computed, ref } from 'vue'
 import { useProjectStore } from '@/store/modules/project'
-import { getImageByPath } from '@/utils'
-import defaultImg from '@/assets/default.png'
+import { LocalImage } from '@/components'
+import { LuFileImage, LuX } from 'vue-icons-plus/lu'
 
 const modelValue = defineModel('modelValue')
 const projectStore = useProjectStore()
-const imgSrc = ref<string>()
-
-const imageOptions = computed(() => {
-  const list = projectStore.project?.resources.images || []
-  return list.map((item) => ({ label: item.fileName, value: item.id }))
+const visible = ref(false)
+const selected = ref(modelValue.value)
+// 图片列表
+const imageList = computed(() => {
+  return projectStore.project?.resources.images || []
+})
+// 选中资源
+const selectedFile = computed(() => {
+  return imageList.value.find((item) => item.id === modelValue.value)
 })
 
-watch(
-  () => modelValue.value,
-  async (val) => {
-    if (val && projectStore.project) {
-      // 加载图片
-      const basePath = projectStore.project.meta.path
-      const imagePath = projectStore.project.resources.images.find((item) => item.id === val)?.path
-      const result = await getImageByPath(basePath + imagePath)
-      if (result?.base64) {
-        imgSrc.value = result.base64
-      }
-    } else {
-      imgSrc.value = ''
-    }
+const handleClick = (val: string) => {
+  if (selected.value === val) {
+    selected.value = ''
+  } else {
+    selected.value = val
   }
-)
+}
+
+// 取消
+const handleCancel = () => {
+  visible.value = false
+}
+
+// 确定
+const handleConfirm = () => {
+  visible.value = false
+  modelValue.value = selected.value
+}
+
+// 清除
+const handleClear = () => {
+  selected.value = ''
+  modelValue.value = ''
+}
 </script>
 
 <style scoped></style>

+ 15 - 24
src/renderer/src/views/designer/config/property/components/StyleBackground.vue

@@ -11,13 +11,7 @@
       <span class="text-text-active">{{ modelValue?.color }}</span>
     </el-form-item>
     <el-form-item v-if="!onlyColor" label="背景图片" label-position="left" label-width="60px">
-      <el-select-v2
-        placeholder="选择图片"
-        :model-value="modelValue?.image?.imgId"
-        :options="imageOptions"
-        clearable
-        @change="handleImageChange"
-      />
+      <ImageSelect v-model="image" />
     </el-form-item>
     <el-form-item v-if="!onlyColor" label="图片颜色" label-position="left" label-width="60px">
       <ColorPicker
@@ -36,7 +30,7 @@
 import { computed } from 'vue'
 import { ColorPicker } from '@/components'
 import { parseCssGradient, generateCssGradient } from '@/utils'
-import { useProjectStore } from '@/store/modules/project'
+import ImageSelect from './ImageSelect.vue'
 
 import type { GradientColor } from '@/lvgl-widgets/type'
 
@@ -44,8 +38,6 @@ withDefaults(defineProps<{ useType?: 'pure' | 'gradient' | 'both'; onlyColor?: b
   useType: 'pure'
 })
 
-const projectStore = useProjectStore()
-
 const modelValue = defineModel<{
   color: string | GradientColor
   image?: {
@@ -90,21 +82,20 @@ const imageColor = computed({
   }
 })
 
-// 图像列表
-const imageOptions = computed(() => {
-  const list = projectStore.project?.resources.images || []
-  return list.map((item) => ({ label: item.fileName, value: item.id }))
-})
-
-const handleImageChange = (imageId: string) => {
-  if (modelValue.value) {
-    if (!modelValue.value?.image) {
-      modelValue.value.image = {
-        imgId: '',
-        color: ''
+const image = computed({
+  get() {
+    return modelValue.value?.image?.imgId
+  },
+  set(val: string) {
+    if (modelValue.value) {
+      if (!modelValue.value?.image) {
+        modelValue.value.image = {
+          imgId: '',
+          color: ''
+        }
       }
+      modelValue.value.image.imgId = val
     }
-    modelValue.value.image.imgId = imageId
   }
-}
+})
 </script>

+ 4 - 4
src/renderer/src/views/designer/sidebar/components/EditImageModal.vue

@@ -68,7 +68,7 @@
               ></el-option>
             </el-select> </el-form-item
         ></el-col>
-        <el-col :span="12">
+        <!-- <el-col :span="12">
           <el-form-item label="颜色" prop="alphaColor">
             <ColorPicker
               v-model:pureColor="formData.alphaColor"
@@ -79,7 +79,7 @@
             />
             <span class="ml-8px">{{ formData.alphaColor }}</span>
           </el-form-item></el-col
-        >
+        > -->
         <el-col :span="12">
           <el-form-item label="透明度" prop="alpha">
             <el-slider v-model="formData.alpha" :max="255" :min="0"></el-slider> </el-form-item
@@ -124,7 +124,7 @@ import { klona } from 'klona'
 import { getImageByPath } from '@/utils'
 import { LuPencilLine } from 'vue-icons-plus/lu'
 import EditBinModal from './EditBinModal.vue'
-import ColorPicker from '@/components/ColorPicker/index.vue'
+// import ColorPicker from '@/components/ColorPicker/index.vue'
 
 const emit = defineEmits(['change'])
 const projectStore = useProjectStore()
@@ -182,7 +182,7 @@ const formData = ref<ImageResource>({
   colorFormat: '',
   alpha: 255,
   bin: '',
-  alphaColor: '#FFFFFF00',
+  // alphaColor: '#FFFFFF00',
   ditheringAlgorithm: 'None',
   storageMode: 'BIN'
 })

+ 21 - 2
src/renderer/src/views/designer/workspace/stage/Moveable.vue

@@ -35,6 +35,7 @@
     :horizontalGuidelines="horizontalGuidelines"
     :controlPadding="4"
     :edge="true"
+    :individualGroupable="true"
     :individualGroupableProps="individualGroupableProps"
     @render="onRender"
     @drag="onDrag"
@@ -46,6 +47,7 @@
     @dragGroupEnd="onDragGroupEnd"
     @resizeGroup="onResizeGroup"
     @resizeGroupEnd="onResizeGroupEnd"
+    @rotateEnd="onRotateEnd"
   />
 </template>
 
@@ -140,10 +142,18 @@ useMutationObserver(
 const zIndex = ref()
 
 const individualGroupableProps = (element: HTMLElement | SVGElement | null | undefined) => {
-  console.log(element)
-  if (element?.getAttribute('widget-type')?.includes('lv_checkbox')) {
+  console.log(element?.getAttribute('widget-type'))
+  if (element?.getAttribute('widget-type') === 'lv_checkbox') {
     return { resizable: false }
   }
+  if (element?.getAttribute('widget-type') === 'lv_image') {
+    return { rotatable: true }
+  }
+
+  return {
+    draggable: true,
+    resizable: true
+  }
 }
 
 // 拖拽开始
@@ -229,6 +239,15 @@ const onResizeGroupEnd = ({ events }) => {
     }
   })
 }
+
+// 旋转结束
+const onRotateEnd = (e) => {
+  const id = e.target.attributes['widget-id']?.value
+  if (e.lastEvent && id && projectStore.activeWidgetMap[id]) {
+    // 设置位置
+    projectStore.activeWidgetMap[id].props.rotate.angle = Math.round(e.lastEvent.rotate)
+  }
+}
 </script>
 
 <style less>

+ 10 - 1
src/renderer/src/views/designer/workspace/stage/Node.vue

@@ -90,11 +90,20 @@ const getStyle = computed((): CSSProperties => {
     other.transform = `translate(${schema.props.x - 1}px, ${schema.props.y - 1}px)`
   }
 
+  let rotate = ''
+  if (schema.props?.rotate) {
+    rotate = `rotate(${schema.props.rotate.angle}deg)`
+    other.transformOrigin = `${schema.props.rotate.x}px ${schema.props.rotate.y}px`
+    if (other.transform) {
+      other.transform += ` ${rotate}`
+    }
+  }
+
   return {
     position: 'absolute',
     left: 0,
     top: 0,
-    transform: `translate(${schema.props.x}px, ${schema.props.y}px)`,
+    transform: `translate(${schema.props.x}px, ${schema.props.y}px) ${rotate}`,
     width: schema.props.width + 'px',
     height: schema.props.height + 'px',
     zIndex: zIndex.value,