本代码片段使用插件《qs-canvas》,支持 Node、web、uni-app 的canvas绘图工具。

效果图

image.png

安装npm包

npm install qs-canvas -S

代码片段

// index.vue
<template>
<view>
    <canvas
        :id="poster.canvasId"
        :canvas-id="poster.canvasId"
         :style="{
          position: 'fixed',
          top:0,
          left: '-99999px',
          'display:': 'none',
          height: poster.height + 'px',
          width: poster.width + 'px',
         }"
      >
      </canvas>
    <button @click="saveImg">保存图片到本地</button>
</view>
</template>
<script setup>
 import { reactive, ref, computed, onMounted, getCurrentInstance } from 'vue';
 import { getQrCode } from './api/index';
 import useCanvas from './useCanvas'
 
 const base64 = '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'
 
 const vm = getCurrentInstance();
 const state = reactive({
     bgImg: 'https://fakeimg.pl/299x429/', // 海报背景图
     qrCodeImg: '', // 二维码,base64
     img: '', // 本地临时地址,canvas转换获取
 })
 const poster = reactive({
    type: 'invite',
    canvasId: 'myCanvas',
    width: 299,
    height: 429,
    src: '',
  });
  
  // 获取二维码数据,接口返回base64
  const getQrCode = async () => {
        // const res = await qrCodeImg()
        // state.qrCodeImg = 'data:image/png;base64,' + res.data;
        state.qrCodeImg = `data:image/png;base64,${base64}`
  }
  
  // 绘制海报图
  const drawPhotos = () => {
        poster.src = '';

        poster.data = {
          bgImg: state.bgImg,
          qrCode: state.qrCodeImg,
          hint: '好用就点个关注呗'
        };
        // #ifdef APP-PLUS
        poster.canvasId = 'canvasId-' + new Date().getTime();
        // #endif
        await useCanvas(poster, vm)
        
        // 将base64转换成临时本地地址
        uni.canvasToTempFilePath({
          canvasId: poster.canvasId,
          success: (res) => {
            state.img = res.tempFilePath;
          },
        });
  }

  const saveImg = () => {
      uni.saveImageToPhotosAlbum({
          filePath: state.img,
          success(res) {
            console.log('保存成功');
          },
      })
  }
  
  onMounted(() => {
        getQrCode()
  })
</script>
// useCanvas.js

/**
 * qs-canvas 绘制海报
 * @version 1.0.0
 * @param {Object} options - 海报参数
 * @param {Object} vm - 自定义组件实例
 */
import QSCanvas from 'qs-canvas';
import { getPosterData } from './poster';

export default async function useCanvas(options, vm) {
  const width = options.width;
  const qsc = new QSCanvas(
    {
      canvasId: options.canvasId,
      width: options.width,
      height: options.height,
      setCanvasWH: (canvas) => {
        options.height = canvas.height;
      },
    },
    vm,
  );

  let drawer = getPosterData(options);

  // 绘制背景图
  const background = await qsc.drawImg({
    type: 'image',
    val: drawer.background,
    x: 0,
    y: 0,
    width,
    mode: 'widthFix',
    zIndex: 0,
  });
  await qsc.updateCanvasWH({
    width: background.width,
    height: background.bottom,
  });

  let list = drawer.list;

  for (let i = 0; i < list.length; i++) {
    let item = list[i];
    // 绘制文字
    if (item.type === 'text') {
      await qsc.drawText(item);
    }
    // 绘制图片
    if (item.type === 'image') {
      if (item.d) {
        qsc.setCircle({
          x: item.x,
          y: item.y,
          d: item.d,
          clip: true,
        });
      }

      if (item.r) {
        qsc.setRect({
          x: item.x,
          y: item.y,
          height: item.height,
          width: item.width,
          r: item.r,
          clip: true,
        });
      }
      await qsc.drawImg(item);
      qsc.restore();
    }

    // 绘制二维码
    if (item.type === 'qrcode') {
      await qsc.drawQrCode(item);
    }
  }

  await qsc.draw();
  // 延迟执行, 防止不稳定
  setTimeout(async () => {
    options.src = await qsc.toImage();
  }, 100);
  return options;
}
// poster/index.js

const invite = (options) => {
  const width = options.width;
  // const userInfo = sheep.$store('user').userInfo;
  const userInfo = {
        avatar: 'https://fakeimg.pl/60x60/',
        nickname: '1111'
  }

  return {
    background: options.data?.bgImg,
    list: [
      {
        name: 'avatar',
        type: 'image',
        val: userInfo.avatar,
        x: width * 0.064,
        y: width * 0.064,
        width: 60,
        height: 60,
        d: 60,
      },
      {
        name: 'nickname',
        type: 'text',
        val: userInfo.nickname,
        x: width * 0.28,
        y: width * 0.1,
        paintbrushProps: {
          fillStyle: '#333',
          font: {
            fontSize: 14
          },
        },
      },
      {
        name: 'hint',
        type: 'text',
        val: options.data?.hint,
        x: width * 0.28,
        y: width * 0.18,
        paintbrushProps: {
          fillStyle: '#999',
          font: {
            fontSize: 10
          },
        },
      },
      {
        name: 'wxacode',
        type: 'image',
        val: options.data?.qrCode,
        x: width * 0.2,
        y: width * 0.38,
        width: width * 0.6,
        height: width * 0.6,
        // size: width * 0.6,
      },
    ],
  };
};

export function getPosterData(options) {
  switch (options.type) {
    case 'invite':
      return invite(options);
  }
}

Max迪丶先生
1.8k 声望64 粉丝