uni-app vue3 上传图片时添加水印功能

1,315 阅读2分钟

偶然看到上传图片添加水印的功能,就想尝试来做下试试。中途卡在图片显示不全弄了好久,解决方法在script代码部分的17、18行。

废话不多说 咱们直接上代码!

代码部分

注意:这里在图片上传的时候canvas图层也会显示图片出来。我在网上找的教程都是给canvas上display:none,但是我会报错“The image argument is a canvas element with a width or height of 0.” 应该是在上传之后识别不到这个canvas。我用模拟器调试的时候单独上display:none就没问题。所以,我给最外层设置了style="position: absolute; left: -9999px;"可以解决这个问题。

<template>
    <!-- 添加水印 -->
    <view class="upload-view" style="position: absolute; left: -9999px;">
        <canvas id="watermark-canvas" :style="{ width: canvasWidth, height: canvasHeight }" canvas-id="watermark-canvas" />
    </view>
</template>
<script lang="ts" setup>
// 定义响应式状态
const canvasWidth = ref(1080); // 画布宽度
const canvasHeight = ref(2160); // 画布高度

// 自定义的 sleep 函数,用于延迟
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// 添加水印函数
async function addWatermark(tempFilePath) {
  return new Promise((resolve, reject) => {
    uni.getImageInfo({
      src: tempFilePath,
      success: async (res) => {
      
        // 设置 canvas 宽高为图片的宽高
        canvasWidth.value = `${res.width}px`;
        canvasHeight.value = `${res.height}px`;
        await sleep(200); // 等待画布渲染

        // 创建 canvas 上下文
        const ctx = uni.createCanvasContext('watermark-canvas');
        // 清空画布
        ctx.clearRect(0, 0, res.width, res.height);
        // 绘制图片
        ctx.drawImage(tempFilePath, 0, 0, res.width, res.height);

        // 设置背景色的高度和位置
        const backgroundHeight = 40; // 背景色区域的高度
        const backgroundColor = 'rgba(229, 230, 235, 0.7)'; // 半透明灰色
        // 绘制背景色矩形
        ctx.setFillStyle(backgroundColor);
        ctx.fillRect(0, res.height - backgroundHeight, res.width, backgroundHeight);
        
        // 添加水印文本
        const text = `上传时间:${formatDate(new Date())}`;
        const fontSize = 24;
        // 设置文本样式
        ctx.setFontSize(fontSize);
        ctx.setFillStyle('black');
        
        // 计算文本宽度和高度
        ctx.setTextAlign('left'); // 左对齐文本
        ctx.setTextBaseline('middle'); // 垂直居中
        
        // 绘制文本
        ctx.fillText(text, 30, res.height - (backgroundHeight / 2));

        // 开始绘制
        ctx.draw(false, async () => {
          await sleep(500); // 等待渲染完成
          
          uni.canvasToTempFilePath({
            canvasId: 'watermark-canvas',
            destWidth: res.width,
            destHeight: res.height,
            fileType: 'jpg',
            quality: 1,
            success: (fileRes) => {
              resolve(fileRes.tempFilePath);  // 成功后返回带水印的临时文件路径
            },
            fail: (err) => {
              console.error('[Error draw]', err);
              uni.showToast({ title: err.errMsg, icon: 'none' });
              reject(err);  // 绘制失败处理
            },
          });
        });
      },
      fail: (err) => {
        console.error('[Error getImageInfo]', err);
        uni.showToast({ title: err.errMsg, icon: 'none' });
        reject(err);  // 获取图片信息失败处理
      },
    });
  });
}

// 上传图片函数
const upLoad = () => {
  uni.chooseImage({
    count: 8,  // 允许选择的最大图片数量
    sizeType: ['original', 'compressed'],  // 选择原图或压缩图
    sourceType: ['album', 'camera'],  // 图片来源
    success: async (res) => {
      const tempFilePaths = res.tempFilePaths;  // 临时文件路径列表

      // 使用 Promise.all 处理多张图片的并行添加水印和上传
      try {
        const uploadPromises = tempFilePaths.map(async (filePath) => {
          const watermarkedPath = await addWatermark(filePath);  // 添加水印
          return uploadStationImg(watermarkedPath, id.value, name.value);  // 上传水印后的图片
        });

        // 等待所有图片上传完成
        const responses = await Promise.all(uploadPromises);
        // 检查是否有错误码 500
        const errorResponse = responses.find(res => res.code === 500);
        if (errorResponse) {
          uni.showToast({
            icon: 'error',
            title: errorResponse.msg || '上传失败,只能上传8张图片',
          });
        } else {
          uni.showToast({
            icon: 'success',
            title: '所有图片上传成功',
          });
          // 所有图片上传完成后,调用 fetchStationImg 刷新图片列表
          fetchStationImg();
        }
      } catch (error) {
        uni.showToast({
          icon: 'none',
          title: '部分图片上传失败,请重试',
        });
      }
    },
    fail: (err) => {
      console.error('[Error chooseImage]', err);
      uni.showToast({ title: err.errMsg, icon: 'none' });
    },
  });
};
</script>

api部分

import { baseURL, http } from '@/utils/http'

/**
 * 上传图片
 */
export const uploadStationImg = (file: any, stationId: string, stationName: any) => {
  return new Promise((resolve, reject) => {
    uni.uploadFile({
      url: `${baseURL}/upload/img`,
      filePath: file,
      name: 'file',
      formData: {
        stationId,
        stationName,
      },
      success: (uploadFileRes) => {
        // 假设服务器返回的响应是简单的 JSON 格式
        if (uploadFileRes.statusCode === 200) {
          // 解析响应数据,假设服务器返回的是 JSON 格式
          try {
            const data = JSON.parse(uploadFileRes.data)
            uni.showToast({
              icon: 'success',
              title: '上传成功',
            })
            resolve(data)
          } catch (error) {
            uni.showToast({
              icon: 'none',
              title: '解析响应失败',
            })
            reject(error)
          }
        } else {
          uni.showToast({
            icon: 'none',
            title: '上传失败,请重试',
          })
          reject(new Error('上传失败'))
        }
      },
      fail: (err) => {
        uni.showToast({
          icon: 'none',
          title: '上传失败,请重试',
        })
        reject(err)
      },
    })
  })
}

最终实现效果 截屏2024-08-27 15.12.30.png

代码参考:uni-app为图片添加自定义水印(解决生成图片不全问题)