原生微信小程序,canvas生成凭证,保存到手机

原生微信小程序通过canvas可以将表单( 文本、图片 )转化成图片,同时添加水印,生成凭证,这里只是基本功能实现。可以继续完善和扩展。

<view class="container">
  <!-- Canvas 组件 -->
  <canvas type="2d" id="myCanvas" style="width: {{canvasWidth}}px;height: {{canvasHeight}}px;"></canvas>

  <!-- 保存按钮 -->
  <button bindtap="saveImage">保存图片到手机</button>
</view>
Page({
  data: {
    canvasWidth: 300,
    canvasHeight: 0,

    fields: {
      schemeType: "方案类型:普通保险",
      mainInsurance: "主险金额:100,000元",
      medicalInsurance: "附加医疗险:20,000元",
      employerInsurance: "附加补充雇主险:50,000元",
      thirdPartyInsurance: "附加第三者财产险:30,000元",
      imgs: [
        'https://cdn.bellmesse.com/upload/images/article/20241031/17303356875926158.jpg', // 示例图片1
        'https://cdn.bellmesse.com/upload/images/article/20241031/17303356875926158.jpg', // 示例图片2
        'https://cdn.bellmesse.com/upload/images/article/20241031/17303356875926158.jpg', // 示例图片3
      ],
    },
    watermarkText: "2025-4-24 xx保险公司 xx",
  },

  onReady() {
    this.init();
  },

  async init() {
    let {
      canvas,
      ctx
    } = await this.initCanvas();

    let paddingTop = 30; // 顶部预留空间
    let titleHeight = 30; // 标题高度
    let oneFieldHeight = 30; // 单字段高度

    // 总高度
    let totalHeight = paddingTop + titleHeight + Object.keys(this.data.fields).length * oneFieldHeight;
    // 减去img
    if (this.data.fields.imgs) {
      totalHeight -= oneFieldHeight;
    }

    // 计算图片的缩放比例和高度
    let images = await this.initImg(canvas, this.data.fields.imgs);
    if (images.length) {
      images = images.map(image => {
        const originalWidth = image.width;
        const originalHeight = image.height;
        const newHeight = (this.data.canvasWidth / originalWidth) * originalHeight;

        let result = {
          imageSource: image,
          x: 0,
          y: totalHeight,
          width: this.data.canvasWidth,
          height: newHeight
        }
        totalHeight = totalHeight + newHeight + 16;
        return result;
      })
    }

    // 设置 Canvas 尺寸
    this.setData({
      canvasHeight: totalHeight
    })
    const dpr = wx.getSystemInfoSync().pixelRatio;
    canvas.width = this.data.canvasWidth * dpr;
    canvas.height = totalHeight * dpr;
    ctx.scale(dpr, dpr);

    // 设置背景颜色
    ctx.fillStyle = '#ffffff';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 绘制标题
    ctx.font = '16px sans-serif';
    ctx.fillStyle = '#333333';
    ctx.fillText("保险凭证", 10, titleHeight);

    // 绘制字段
    let keyIndex = 0;
    for (const key in this.data.fields) {
      if (key !== 'imgs') {
        ctx.font = '14px sans-serif';
        ctx.fillStyle = '#666666';
        ctx.fillText(this.data.fields[key], 10, (keyIndex * oneFieldHeight + titleHeight + paddingTop));
        keyIndex += 1;
      }
    }

    // 渲染图片
    images.forEach(image => {
      ctx.drawImage(image.imageSource, image.x, image.y, image.width, image.height);
    })
    // 渲染水印
    this.addEnhancedWatermark(ctx, canvas.width, canvas.height, this.data.watermarkText);
  },

  // 获取canvas对象
  initCanvas() {
    return new Promise((resolve, reject) => {
      const query = wx.createSelectorQuery();
      query
        .select('#myCanvas')
        .fields({
          node: true,
          size: true
        })
        .exec((res) => {
          const canvas = res[0].node;
          const ctx = canvas.getContext('2d');
          if (res[0].node) {
            resolve({
              canvas,
              ctx
            })
          } else {
            reject()
          }
        })
    })
  },

  // 加载图片
  initImg(canvas, imgs) {
    if (!canvas) {
      wx.showToast({
        title: '未找到canvas',
        icon: 'none'
      })
      return;
    }

    const imgPromises = imgs.map((imgUrl) =>
      new Promise((resolve, reject) => {
        const image = canvas.createImage();
        image.onload = () => resolve(image);
        image.onerror = reject;
        image.src = imgUrl; // 设置图片路径
      })
    );

    return Promise.all(imgPromises);
  },

  addEnhancedWatermark(ctx, canvasWidth, canvasHeight, watermarkText) {
    ctx.save();

    // 水印样式设置
    ctx.font = '16px sans-serif';
    ctx.fillStyle = 'rgba(200, 200, 200, 0.5)'; // 更浅的透明度
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.rotate(-20 * Math.PI / 180); // 保持倾斜

    // 水印间距参数
    const watermarkWidth = 280;
    const watermarkHeight = 60;
    const horizontalOffset = watermarkWidth * 0.5; // 水平交错偏移量(半格)

    // 计算需要绘制的水印行数和列数(扩大范围确保覆盖整个Canvas)
    const cols = Math.ceil(canvasWidth / watermarkWidth) + 2;
    const rows = Math.ceil(canvasHeight / watermarkHeight) + 2;

    // 绘制交错水印
    for (let i = -1; i < rows; i++) {
      for (let j = -1; j < cols; j++) {
        // 关键修改:奇数行水平偏移半格
        const xOffset = (i % 2 === 0) ? 0 : horizontalOffset;
        const x = j * watermarkWidth + xOffset;
        const y = i * watermarkHeight;

        ctx.fillText(watermarkText, x, y);

        // 可选:添加随机微调(更自然的效果)
        // const randomOffsetX = (Math.random() - 0.5) * 10;
        // const randomOffsetY = (Math.random() - 0.5) * 10;
        // ctx.fillText(watermarkText, x + randomOffsetX, y + randomOffsetY);
      }
    }
    ctx.restore();
  },

  // 保存图片到手机
  saveImage() {
    const query = wx.createSelectorQuery();
    query
      .select('#myCanvas')
      .fields({
        node: true,
        size: true
      })
      .exec((res) => {
        const canvas = res[0].node;

        // 导出 Canvas 为临时文件
        wx.canvasToTempFilePath({
          canvas,
          success: (res) => {
            const tempFilePath = res.tempFilePath;

            // 保存图片到相册
            wx.saveImageToPhotosAlbum({
              filePath: tempFilePath,
              success: () => {
                wx.showToast({
                  title: '保存成功',
                  icon: 'success',
                });
              },
              fail: () => {
                wx.showToast({
                  title: '保存失败',
                  icon: 'none',
                });
              },
            });
          },
          fail: () => {
            wx.showToast({
              title: '生成图片失败',
              icon: 'none',
            });
          },
        });
      });
  },
});
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

button {
  margin-top: 20px;
  padding: 10px 20px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 5px;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值