微信小程序 实现手写签名(横屏签名板)

发布于:2024-04-25 ⋅ 阅读:(27) ⋅ 点赞:(0)

效果图:

业务需求:点击签字空白处,调起签字版(横屏展示),手写完之后点击确定回显签字内容

签名是使用canvas实现的

签名确认之后生成的一个临时图片

后台接口逻辑是生成的图片先上传到文件服务器,然后获取返回的文件数据,跟其他数据一块提交到后台。(可根据自己项目情况修改)

签名单独封装成了组件

具体代码如下:

signature.wxml

 <view class="modal-content-area">
    <view class="modal-content">
        <view class="toast" wx:if="{{!hasDraw}}">
            请签写您的名字
        </view>
        <canvas canvas-id="signature" class="modal-canvas" disable-scroll="{{true}}" id="writeCanvas" bindtouchstart="scaleStart" bindtouchmove="scaleMove" bindtouchend="scaleEnd"></canvas>
        <view class="modal-bottom">
            <view class="clear_signature" bindtap=""></view>
            <view class="flex_btn">
                <view class="modal-btn modal-clear" bindtap="clearCanvas">清空</view>
                <view class="modal-btn modal-confirm" bindtap="save">确定</view>
            </view>
        </view>
    </view>
</view>

signature.wxss

.modal-content-area {
  z-index: 5;
  width: 100%;
  height: 100%;
  background: #F5F7FB;
  position: absolute;
}

.modal-content {
  width: 100%;
  height: 100%;
  background: #ffffff;
}

.modal-canvas {
  width: 100vw;
  height: 83vh;
  background: #F5F7FB !important;
  border: 1rpx solid rgba(0, 0, 0, 0.08);
  border-radius: 4rpx;
  z-index: 1;
}


.modal-bottom {
  position: absolute;
  bottom: 15rpx;
  display: flex;
  align-items: center;
  justify-content: space-between;
  z-index: 100;
  width: 100vw;
  box-sizing: border-box;
  padding: 0 32rpx;
}

.modal-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 85rpx;
  height: 30rpx;
  background: rgba(103, 149, 255, 0.2);
}

.modal-clear {
  font-size: 13rpx;
  font-family: PingFangSC-Regular, PingFang SC;
  font-weight: 400;
  color: #3670F5;
  margin-right: 12rpx;
}

.modal-confirm {
  background: #3670F5;
  font-size: 13rpx;
  font-family: PingFangSC-Regular, PingFang SC;
  font-weight: 400;
  color: #FFFFFF;
}

.modal-btn:nth-child(1) {
  border-right: 1rpx solid #ffffff;
}

.clear_signature {
  /* position: absolute; */
  top: 40rpx;
  right: 15rpx;
  display: flex;
  align-items: center;
  z-index: 1;
  font-size: 13rpx;
  font-family: PingFangSC-Regular, PingFang SC;
  font-weight: 400;
  color: #3670F5;
  /* background-color: #F5F7FB; */
  z-index: 100;
}

.clear_signature>text {
  height: 17rpx;
}

.clear_signature>image {
  width: 15rpx;
  height: 15rpx;
  margin-right: 2rpx;
}

.toast {
  position: absolute;
  top: 40%;
  left: 42%;
  transform: rotate(-50%, -50%);
  z-index: 100;
  font-size: 28rpx;
  font-family: PingFangSC-Regular, PingFang SC;
  font-weight: 400;
  color: rgba(58, 65, 85, 0.4);
}

.flex_btn {
  display: flex;
  align-items: center;
}

signature.js

Component({
  /**
   * 组件的初始数据
   */
  data: {
      tempFilePath: '',
      hideModal: false,
      hasDraw: false,
      canvasName: '#writeCanvas',
      ctx: '',
      canvasWidth: 0,
      canvasHeight: 0,
      startPoint: {
          x: 0,
          y: 0,
      },
      selectColor: 'black',
      lineColor: '#1A1A1A', // 颜色
      lineSize: 1, // 笔记倍数
      radius: 5, //画圆的半径
  },
  // 初始化画布
  ready() {
      this.setData({
          hideModal: false
      })
      let that = this
      let query = wx.createSelectorQuery().in(this);
      //获取自定义组件的SelectQuery对象
      this.canvasCtx = wx.createCanvasContext('signature', that)
      // 设置线的样式
      this.canvasCtx.setLineCap("round");
      this.canvasCtx.setLineJoin("round");
      // 初始化颜色
      this.canvasCtx.setStrokeStyle(that.data.selectColor);
      // 初始化粗细
      query.select('.modal-canvas').boundingClientRect(rect => {
          this.setData({
              canvasWidth: rect.width,
              canvasHeight: rect.height,
          });
      }).exec();
  },
  // 方法列表
  methods: {
      scaleStart(event) {
          if (event.type != 'touchstart') return false;
          let currentPoint = {
              x: event.touches[0].x,
              y: event.touches[0].y
          }
          // this.data.ctx.moveTo(currentPoint.x, currentPoint.y)
          this.drawCircle(currentPoint);
          this.setData({
              startPoint: currentPoint,
              hasDraw: true, //签字了
          });
      },
      mouseDown() {},
      scaleEnd(e) {
          this.setData({
              isStart: true
          })
      },
      scaleMove(event) {
          if (event.type != "touchmove") return false;
          let {
              startPoint
          } = this.data
          let currentPoint = {
              x: event.touches[0].x,
              y: event.touches[0].y
          }

          this.drawLine(startPoint, currentPoint)
          this.setData({
              startPoint: currentPoint
          })
      },
      drawCircle(point) { //这里负责点
          let ctx = this.canvasCtx;
          ctx.beginPath();
          ctx.setFillStyle(this.data.lineColor)
          //笔迹粗细由圆的大小决定
          ctx.arc(point.x, point.y, this.data.radius, 0, 2 * Math.PI);
          ctx.fill();
          ctx.closePath();
          ctx.draw(true)
      },
      drawLine(sourcePoint, targetPoint) {
          let ctx = this.canvasCtx;
          this.drawCircle(targetPoint);
          ctx.beginPath();
          ctx.setStrokeStyle(this.data.lineColor)
          ctx.setLineWidth(this.data.radius * 2)
          ctx.moveTo(sourcePoint.x, sourcePoint.y);
          ctx.lineTo(targetPoint.x, targetPoint.y);
          ctx.stroke();
          ctx.closePath();
      },
      clearCanvas() {
          //清空画布
          let ctx = this.canvasCtx;
          ctx.clearRect(0, 0, this.data.canvasWidth, this.data.canvasHeight);
          ctx.fillStyle = 'rgba(0, 0, 0, .1)';
          ctx.draw()
          this.setData({
              hasDraw: false //未签字
          })
      },
      save() {
          let {
              hasDraw,
          } = this.data
          if (!hasDraw) {
              wx.showToast({
                  title: '您还未签名!',
                  icon: 'none',
                  mask: true
              })
              return
          } else {
              this.saveToImage();
          }
      },
      saveToImage() {
          let that = this;
          let {
              canvasHeight,
              canvasWidth
          } = that.data;
          wx.canvasToTempFilePath({
              x: 0,
              y: 0,
              width: canvasWidth,
              height: canvasHeight,
              canvasId: 'signature',
              success(res) {
                if (res.tempFilePath) {
                that.triggerEvent('saveToImageEvent', res.tempFilePath)
                }
              },
              fail(err) {
                  console.log(err);
              }
          }, that)
      }
  }
})

业务代码:

index.wxml

<view>
  <signature bind:saveToImageEvent="saveToImageEvent"></signature>
</view>

index.js

import { config } from '@/config';
Page({
  data: {
    signFile: {}
  },
  saveToImageEvent(e){
    let that = this;
    // 上传到文件服务器返回文件信息
    wx.uploadFile({
      url: `${config.sfss_url}/space`, //仅为示例,非真实的接口地址
      filePath: e.detail,
      name: 'file',
      success (res){
        that.setData({
          signFile: JSON.parse(res.data)
        })
        let pages = getCurrentPages();//获取page
        let prevPage = pages[pages.length-2];//上一个页面(父页面)
        prevPage.setData({
            tempFilePath: e.detail,
            ['storageInfo.signature']: that.data.signFile.id
        })
        //返回上一页面
        wx.navigateBack({
          delta: 1
        })
      }
    })
    
  },
  onLoad(options) {

  },
  onReady() {

  },
  onShow() {

  },
  onHide() {

  },
  onUnload() {

  },
  onShareAppMessage() {
    return {
      title: '',
    };
  },
});

index.json

{
  "navigationBarTitleText": "",
  "pageOrientation": "landscape",
  "usingComponents": {
    "signature": "/components/signature/signature"
  }
}