微信小程序 拖拽签章

发布于:2025-08-16 ⋅ 阅读:(15) ⋅ 点赞:(0)

微信小程序 拖拽签章

效果

在这里插入图片描述

主要实现的功能点

  1. 文件按比例加载图片(宽高设定拖拽范围)
  2. 弹层展示印章模板
  3. 模板拖拽到文件图片上
  4. 实时获取拽拽位置

难点 弹层中的元素如何拖拽到文件图片上

实现历程

版本1.0

以前我们拖拽一个图层到另一个图层上,pc端使用的是mousedown mousemove mouseup事件

在这里插入图片描述

如上图:

  1. 給物料区域,图片A绑定点击事件,点击图片A,传输数据

  2. 生成一个拖拽dom元素,追加div元素到文件区域中心

  3. 然后绑定鼠标事件,鼠标事件实时获取坐标位置

为什么不直接使用 dragstart dragend drop, 当时文件时pdf,pdf展现的形式是canvas

  • 缺点:点击添加拖拽dom元素,然后再点击元素进行拖拽,多页文件得点击按钮翻页进行拖拽

版本2.0

现在文件的展示形式是图片的形式,然后使用的是dragstart dragend drop事件

在这里插入图片描述

如上图:

现在是多页滚动展示文件

  1. 給物料区域,图片A绑定drag事件,点击图片A,触发drag事件,按住鼠标左键,拖动图片A元素,图片A跟随鼠标到拖拽区域

  2. 蒙层也就是div图层绑定drop事件,用于接收数据(img只是用来展示,img元素和蒙层div是兄弟关系)

  3. 接收数据后,拖拽div数组添加元素,通过for循环展示拖拽div(这个拖拽div蒙层div的子元素),并设定当前文件页的拖拽范围

  4. 拖拽div绑定mouse事件,实时获取坐标位置

鼠标移动过程中,拖拽div跟随鼠标移动

鼠标松开,拖拽div固定在当前位置

  • 优点:文件多页是滚动展示,拖拽添加

  • 缺点:拖拽添加拖拽dom元素,然后再点击元素进行拖拽还是是两步操作

虽然文件多页是滚动展示,但是拖拽dom元素只能在当前文件页进行拖拽,上下分页的情况不能直接从上一页拖拽到下一页

版本3.0

h5版本,使用的是touchstart touchmove touchend事件

在这里插入图片描述

移动端不支持dragstart dragend dropmousedown mousemove mouseup事件,所以h5版本使用的是touchstart touchmove touchend事件,但是同理:

  1. 还是先给物料区域的图片A绑定点击事件,点击图片A,传输数据

  2. 在点击图片A事件中,生成一个拖拽dom元素,追加div元素到文件区域中心

  3. 然后给拖拽dom元素绑定touchstart touchmove touchend事件

touchstart事件中,获取拖拽dom元素的坐标位置

touchmove事件中,实时获取拖拽dom元素的坐标位置

touchend事件中,拖拽dom元素固定在当前位置

touchmove事件中,实时获取拖拽dom元素的坐标位置,计算拖拽dom元素的位置,实时更新拖拽dom元素的位置

  • 优点:增加了拖拽元素可以从上一页直接拖拽到下一页

  • 缺点:还是点击添加拖拽dom元素,然后再点击元素进行拖拽

版本4.0

微信小程序版本,使用的是touchstart touchmove touchend事件

在这里插入图片描述

项目目录

─src
├─components
│ ├─z-drag-add 印章模板弹层-添加操作
│ ├─z-drag-dom 拖拽dom组件
│ ├─z-drag-files 拖拽区域(文件展示)
│ ├─z-drag-pineapples 展示印模组件-编辑、删除操作
├─config
├─hooks
├─mock
├─pages
│ ├─index
├─plugins
├─static
│ └─images
├─store
│ └─modules
├─types
└─utils

准备阶段:印章模板弹层,拖拽区域(文件展示),拖拽dom组件,拖拽到文件区域展示印模组件

他们之间的关系

在这里插入图片描述
看不清可以看这个链接

如上图:

z-drag-files组件

只负责接收父组件index的数据,展示文件图片

<template>
  <view class="files-box">
    <view class="files" v-for="item in files" :key="item.id"
      :style="{ width: item.width + 'px', height: item.height + 'px' }">
      <image class="file-img" :src="item.url" :style="{ width: item.width + 'px', height: item.height + 'px' }"></image>
    </view>
  </view>
</template>
<script setup lang="ts">
import type { File } from '@/types/mock'
const props = defineProps<{
  files: File[]
}>();
</script>
<style>
.files-box {
  margin-top: 27px;
}

.files {
  margin: 0 auto 20rpx;
}

.file-img {
  box-shadow: 0 5rpx 10rpx rgba(0, 0, 0, 0.3);
  border-radius: 16rpx;
}
</style>

z-drag-add组件-添加操作

印章模板弹层,每一个印模添加dragStartdragMovedragEnd事件

实时传输拖拽dom元素的坐标位置,印章高宽及业务数据

父组件index负责接收数据,添加数据,z-drag-pineapples增加拖拽dom元素

<template>
  <view class="custom-collapse">
    <view class="content" :class="isOpen ? 'show-content' : ''">
      <view class="content-box">
        <view class="pineapple" v-for="(item, index) in pineapples" :key="index"
          @touchstart.stop="dragStart($event, item, index)" @touchmove.stop="dragMove($event, item)"
          @touchend.stop="dragEnd($event, item)">
          <view class="pineapple-t">
            {
  
  { item.typeName }}
          </view>
          <view class="pineapple-b">
            <text class="name">{
  
  { item.name }}</text>
          </view>
        </view>
      </view>
    </view>
    <view class="custom-collapse-header" v-show="isOpen">
      请添加签署区拖拽到需要签字盖章的位置
    </view>
    <view class="content-header" :class="isOpen ? 'is-open' : ''">
      <view class="content-title" @click.stop="handleVisible">
        <text class="arrow-icon" :class="isOpen ? 'is-open' : ''"></text>
      </view>
    </view>
  </view>
</template>

<script setup lang="ts">
import { ref, computed, getCurrentInstance, watch, } from "vue";
import type { File, Pineapple } from '@/types/mock'
import { throttle } from 'lodash-es'
import { getRect } from '@/hooks/helper'

const instance = getCurrentInstance();
const props = defineProps({
  pineapples: {
    type: Array,
    default: () => [],
  },
  files: {
    type: Array as () => File[],
    default: () => [],
  },
  scrollTopHeight: {
    type: Number,
    default: 0,
  },
});

const emit = defineEmits(["dragStart", "dragMove", "dragEnd"]);
defineExpose({ open: () => isOpen.value = true, close: () => isOpen.value = false });
const handleVisible = () => {
  isOpen.value = !isOpen.value;
};

const isOpen = ref(true); // 控制折叠状态的变量
const isDown = ref(false); // 是否正在拖拽
const pineapplePos = ref({ divX: 0, divY: 0 });

const dragStart = async (e: TouchEvent, item: Pineapple) => {
  e.stopPropagation();
  isDown.value = true;
  isOpen.value = false;
  try {
    const rect = await getRect('.pineapple', instance);
    const touch = e.changedTouches[0];
    const { clientX: startX, clientY: startY } = touch;
    let { offsetLeft: left, offsetTop: top } = e.currentTarget as HTMLElement;
    pineapplePos.value = {
      divX: startX - left,
      divY: startY - top,
    };
    top = props.scrollTopHeight + top;
    emit("dragStart", 'add', item, rect, left, top);

  } catch (error) {
  }
};

const dragMove = async (e: TouchEvent, item: Pineapple) => {
  e.stopPropagation();
  if (!isDown.value) return;
  try {
    const rect = await getRect('.pineapple', instance);
    const touch = e.changedTouches[0];
    const { divX, divY } = pineapplePos.value;
    // 使用保存的偏移量计算新位置
    const newX = touch.clientX - divX;
    let newY = touch.clientY - divY;
    newY = newY + props.scrollTopHeight; // 添加滚动高度; 
    const { width, height } = rect;
    emit("dragMove", 'add', item, newX, newY, width, height);
  } catch (error) {
  }
}

const dragEnd = async (e: TouchEvent, item: Pineapple) => {
  e.stopPropagation();
  isDown.value = false;
  const rect = await getRect('.pineapple', instance);
  emit("dragEnd", 'add', item, rect,);
};
</script>
<style>
.custom-collapse {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  overflow: hidden;
  margin-bottom: 20rpx;
  background: #fff;
  z-index: 11000;
  box-shadow: 0 -4rpx 15rpx rgba(0, 0, 0, 0.3);
  border-bottom-left-radius: 32rpx;
  border-bottom-right-radius: 32rpx;
}

.content {
  width: 95vw;
  max-height: 0;
  overflow-y: hidden;
  transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  margin: 0 auto;
  /* 添加硬件加速 */
  transform: translateZ(0);
  will-change: max-height;
}

.content-header {
  padding: 0 24rpx 12rpx 24rpx;
  display: flex;
  align-items: center;
  justify-content: space-between;
  transition: all 0.3s;
}

.is-open {
  border-bottom: 1px solid #eee;
}

.content-title {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1;
  padding-top: 12rpx;
}

.arrow-icon {
  display: inline-block;
  width: 30rpx;
  height: 30rpx;
  transition: transform 0.3s ease;
}

.arrow-icon::after {
  content: "";
  position: absolute;
  top: 60%;
  left: 50%;
  width: 16rpx;
  height: 16rpx;
  border-left: 2rpx solid #666;
  border-bottom: 2rpx solid #666;
  transform: translate(-50%, -70%) rotate(-45deg);
  transition: transform 0.3s ease;
}

.arrow-icon.is-open::after {
  transform: translate(-50%, -30%) rotate(135deg);
}

.show-content {
  max-height: 560rpx;
  /* 添加以下属性改善动画性能 */
  transform: translateZ(0);
  backface-visibility: hidden;
  perspective: 1000;
  will-change: transform;
}

.content-box {
  display: flex;
  width: 100%;
  padding-top: 24rpx;
}

.custom-collapse-header {
  width: 90vw;
  margin: 0 auto;
  font-size: 24rpx;
  color: #999;
  padding: 20rpx 0;
}
</style>
<style>
.pineapple {
  display: flex;
  flex-direction: column;
  width: 160rpx;
  height: 200rpx;
  border-radius: 12rpx;
  background-color: #faf6f5;
  box-shadow: 0 15rpx 23rpx rgba(0, 0, 0, 0.3);
  overflow: hidden;
  color: #f66e5d;
  margin: 0 20rpx;
}

.pineapple-t {
  width: 100%;
  font-size: 24rpx;
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  background-size: cover;
  background-image: url('data:image/png;base64,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