偶然看到上传图片添加水印的功能,就想尝试来做下试试。中途卡在图片显示不全弄了好久,解决方法在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)
},
})
})
}
最终实现效果