vue3 vue-draggable-resizable-gorkys 拼接导出

vue yekong

vue3 vue-draggable-resizable-gorkys 图片拼接导出

<template>
  <div class="parInfo" :style="{ transform: `scale(${scale})`, transformOrigin: '0 0' }">
    <vue-draggable-resizable
        v-for="(image, index) in list"
        :key="index"
        :w="imageWidth"
        :h="imageHeights[index]"
        :parent="false"
        :x="index * (imageWidth + 10)"
        :debug="false"
        :min-width="imageWidth"
        :min-height="imageHeights[index]"
        :isConflictCheck="true"
        :snap="true"
        :snapTolerance="10"
        @refLineParams="getRefLineParams"
        class="draggable-image">
      <div
          class="imgMain"
          @click="getIndex(index)"
          :style="{
            backgroundImage: `url(${image})`,
            backgroundSize: 'cover',
            transform: `
              ${flipStates[index] ? 'scaleX(-1)' : 'scaleX(1)'}
              rotate(${rotationStates[index]}deg)
            `
          }">
      </div>
    </vue-draggable-resizable>

    <!-- 裁剪框 -->
    <vue-draggable-resizable
        v-if="showCropBox"
        @dragging="onCropBoxDrag"
        @resizing="onCropBoxResize"
        :class="['crop-box', { transparent: !showCropBox }]">
      <div class="crop-box-handle"></div>
    </vue-draggable-resizable>

    <!--辅助线-->
    <span class="ref-line v-line"
          v-for="item in vLine"
          v-show="item.display"
          :style="{ left: item.position, top: item.origin, height: item.lineLength}"
    />
    <span class="ref-line h-line"
          v-for="item in hLine"
          v-show="item.display"
          :style="{ top: item.position, left: item.origin, width: item.lineLength}"
    />
    <!--辅助线END-->

    <!-- 下载按钮 -->
<!--    <button @click="downloadCroppedImage">下载裁剪图片</button>-->
  </div>
</template>

<script>
import VueDraggableResizable from 'vue-draggable-resizable-gorkys/src/components/vue-draggable-resizable.vue'
import 'vue-draggable-resizable-gorkys/dist/VueDraggableResizable.css'
import html2canvas from 'html2canvas'; // 引入 html2canvas
import {ElLoading} from 'element-plus'; // 引入 element-plus 的 Loading 组件

export default {
  name: 'app',
  components: {
    VueDraggableResizable
  },
  props: {
    list: {
      type: Array,
      required: true,
      default: () => [] // 提供默认值,防止 undefined
    }
  },
  data() {
    return {
      vLine: [],
      hLine: [],
      imageWidth: 200, // 固定宽度
      imageHeights: [], // 动态计算的高度数组
      scale: 1, // 缩放比例,默认是1
      activeIndex: null, // 当前选中的图片索引
      flipStates: [], // 记录每个图片的翻转状态
      rotationStates: [], // 记录每个图片的旋转角度
      cropBox: {
        x: 50,
        y: 50,
        width: 200,
        height: 200
      },
      showCropBox: false // 控制裁剪框的显示状态
    }
  },
  watch: {
    // 监听 list 的变化
    list: {
      handler(newList) {
        this.initialize(newList); // 重新初始化
      },
      immediate: true // 立即执行一次
    }
  },
  methods: {
    getIndex(index) {
      this.activeIndex = index;
    },
    // 辅助线回调事件
    getRefLineParams(params) {
      const {vLine, hLine} = params;
      this.vLine = vLine;
      this.hLine = hLine;
    },
    // 初始化方法
    initialize(list) {
      if (!list || !list.length) return; // 如果 list 为空或 undefined,直接返回
      this.imageHeights = []; // 清空高度数组
      this.flipStates = new Array(list.length).fill(false); // 初始化翻转状态数组
      this.rotationStates = new Array(list.length).fill(0); // 初始化旋转角度数组
      this.loadImageDimensions(list); // 重新加载图片宽高
    },
    // 动态加载图片并获取宽高
    loadImageDimensions(list) {
      list.forEach((image, index) => {
        const img = new Image();
        img.src = image;
        img.onload = () => {
          const aspectRatio = img.height / img.width; // 计算宽高比
          const height = this.imageWidth * aspectRatio; // 根据宽度计算高度
          this.imageHeights[index] = height; // 直接更新数组
        };
      });
    },
    // 设置缩放比例
    setScale(scale) {
      this.scale = scale;
    },
    // 翻转图片
    flipHorizontal() {
      if (this.activeIndex !== null) {
        this.flipStates[this.activeIndex] = !this.flipStates[this.activeIndex];
      }
    },
    // 逆时针旋转90度
    rotateLeft() {
      if (this.activeIndex !== null) {
        this.rotationStates[this.activeIndex] = (this.rotationStates[this.activeIndex] - 90) % 360;
      }
    },
    // 裁剪框拖动事件
    onCropBoxDrag(x, y) {
      this.cropBox.x = x;
      this.cropBox.y = y;
    },
    // 裁剪框缩放事件
    onCropBoxResize(x, y, width, height) {
      this.cropBox.x = x;
      this.cropBox.y = y;
      this.cropBox.width = width;
      this.cropBox.height = height;
    },
    // 控制裁剪框的显示和隐藏
    getCrop() {
      this.showCropBox = !this.showCropBox;
    },
    // 下载裁剪后的图片
    async downloadCroppedImage() {
      // 显示 loading
      const loadingInstance = ElLoading.service({
        lock: true,
        text: '裁剪中,请稍候...',
        background: 'rgba(0, 0, 0, 0.7)'
      });

      try {
        // 隐藏裁剪框(设置透明度为 0)
        this.showCropBox = false; // 隐藏裁剪框
        await this.$nextTick(); // 等待 DOM 更新

        const parInfo = document.querySelector('.parInfo');
        const cropBoxRect = {
          x: this.cropBox.x,
          y: this.cropBox.y,
          width: this.cropBox.width,
          height: this.cropBox.height
        };

        // 使用 html2canvas 渲染裁剪框内的内容
        const canvas = await html2canvas(parInfo, {
          x: cropBoxRect.x,
          y: cropBoxRect.y,
          width: cropBoxRect.width,
          height: cropBoxRect.height,
          scale: 1, // 设置缩放比例
          useCORS: true, // 支持跨域图片
          ignoreElements: (element) => {
            // 忽略不支持的元素
            return element.tagName === 'IFRAME' || element.tagName === 'VIDEO';
          },
          onclone: (clonedDoc) => {
            // 调试:打印克隆的 DOM 结构
            console.log('Cloned DOM:', clonedDoc);
          }
        });

        // 将 canvas 转换为图片并触发下载
        const link = document.createElement('a');
        link.href = canvas.toDataURL('image/png');
        link.download = 'cropped-image.png';
        link.click();
      } catch (error) {
        console.error('Error rendering canvas:', error);
      } finally {
        // 显示裁剪框(恢复透明度)
        this.showCropBox = true; // 恢复裁剪框
        await this.$nextTick(); // 等待 DOM 更新

        // 关闭 loading
        loadingInstance.close();
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.parInfo {
  position: relative;
  width: 5000px;
  height: 5000px;
  //transition: transform 0.3s ease; // 添加平滑过渡效果
  pointer-events: none; /* 确保不拦截裁剪框的事件 */
}

.draggable-image {
  background-position: center;
  background-repeat: no-repeat;
  pointer-events: all; /* 允许图片接收事件 */
}

.imgMain {
  position: relative;
  width: 100%;
  height: 100%;
  transition: transform 0.3s ease; // 添加平滑过渡效果
}

.crop-box {
  border: 2px dashed rgba(red, 0.8);
  z-index: 1000;
  pointer-events: all; /* 确保裁剪框可以接收事件 */
  transition: opacity 0.3s ease; // 添加透明过渡效果
}

.crop-box.transparent {
  opacity: 0; // 裁剪框透明
}

.crop-box-handle {
  width: 100%;
  height: 100%;
  background: rgba(red, 0.3);
}

button {
  position: absolute;
  bottom: 10px;
  right: 10px;
  z-index: 1000;
  pointer-events: all; /* 确保按钮可以接收事件 */
}
</style>
喜欢