uniapp 多图上传,加水印功能(全平台通用)

发布于:2025-06-22 ⋅ 阅读:(13) ⋅ 点赞:(0)

多图上传和水印都是比较难得,特别是有的api只支持在小程序用,h5不给用

效果图
模拟插件
没加水印

加水印

普通的多图上传

// 多图上传
// count:最大数量
export function headerUploads0(count = 9, orderNumber = '', watermarkInfo) {
	return new Promise((resolve, reject) => {
		let UploadList = []
		uni.chooseImage({
			// 最多可以选择的图片总数
			count,
			success: async res => {
				let processedFiles = res.tempFilePaths
				if (watermarkInfo) {
					// 创建列式水印文本数组
					const watermarkLines = [
						`订单号: ${watermarkInfo.number}`,
						`买  家: ${watermarkInfo.traderName}`,
						`卖  家: ${watermarkInfo.sellerTraderName}`,
						`资金方: ${watermarkInfo.capitalTraderName}`,
						`时  间: ${format(new Date(), false)}`
					];
					// 批量添加水印
				}
				//启动上传等待中...  
				uni.showLoading({
					title: '上传中',
				});
				await Promise.all(
						processedFiles.map((item, index) => {
							return new Promise((resolve1, reject1) => {

								console.log('上传');
								uni.uploadFile({
									// 上传地址
									url: BASE_URL + VUE_APP_BASE_API +
										'/AppBusiness/AppCommon/UploadFile',
									name: 'file',
									filePath: processedFiles[index],
									formData: {
										FileNameType: 3,
										FileDir: orderNumber || '',
										ClassifyType: orderNumber ? 'order' : ''
									},
									header: {
										"Authorization": (store.state.token ||
												uni.getStorageSync('token')) ?
											'Bearer ' + (store.state.token ||
												uni.getStorageSync('token')) :
											''
									},
									success: (resz) => {
										console.log('后端返回', (JSON.parse(resz
												.data)
											.data));
										uni.hideLoading()
										UploadList.push(JSON.parse(resz
												.data)
											.data)
										resolve1()
									},
									fail: (resz) => {
										console.log('失败返回', resz);
										uni.hideLoading()
										reject1()
									}
								})
							});
						}))
					.then(() => {
						console.log('循环后', UploadList);
						resolve(UploadList)
					})
					.catch((error) => {
						console.log('循环后', UploadList);
						resolve(UploadList)
					})
			},
			complete: compRes => {}
		});
	})
}


带水印的多图上传

在组件页面那需要加个空画布用来操作

// vue页面上
<!-- 隐藏的Canvas,用于绘制水印(全端兼容)-->
<canvas canvas-id="watermarkCanvas"
	:style="{position: 'absolute', top: '0', left: '-1000vw', width: canvasWidth + 'px', height: canvasHeight + 'px'}"></canvas>

// 引入下面的函数tools.js是我的封装js,别搞错了
import {
	headerUploads, // 多图上传
} from "@/utils/tools.js"

// data数据中
canvasWidth: 500, // 动态绑定Canvas宽高
canvasHeight: 500

// js中调用 
// 参数 多少张图片3张,携带的订单号(不重要我这边上传后端要) Y12345, 订单信息 this.watermarkInfo, this 用来设置canvasWidth的
headerUploads(3, 'Y12345', this.watermarkInfo, this)

函数–封装的tools.js中


/**
 * 跨平台图片压缩(兼容小程序和H5)
 * @param {string} filePath 原始图片路径
 * @param {number} [quality=0.7] 压缩质量(0-1)
 * @param {number} [maxWidth=1024] 最大宽度
 * @param {number} [maxHeight=1024] 最大高度
 * @return {Promise<string>} 压缩后的图片路径
 */
export function compressImage(filePath, quality = 0.8, maxWidth = 1024, maxHeight = 1024) {
  return new Promise((resolve, reject) => {
    // 平台判断
    // #ifdef MP-WEIXIN
    // 微信小程序使用官方API
    wx.compressImage({
      src: filePath,
      quality: Math.floor(quality * 100), // 微信使用0-100整数
      success: (res) => resolve(res.tempFilePath),
      fail: reject
    });
    // #endif

    // #ifdef H5
    // H5使用Canvas压缩
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.src = filePath;
    
    img.onload = () => {
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d');
      
      // 计算压缩尺寸
      let width = img.width;
      let height = img.height;
      
      if (width > maxWidth) {
        height *= maxWidth / width;
        width = maxWidth;
      }
      
      if (height > maxHeight) {
        width *= maxHeight / height;
        height = maxHeight;
      }
      
      canvas.width = width;
      canvas.height = height;
      
      // 绘制压缩图片
      ctx.drawImage(img, 0, 0, width, height);
      console.log(666);
      // 获取压缩结果
      canvas.toBlob(
        (blob) => {
          const reader = new FileReader();
          reader.onload = () => resolve(reader.result);
          reader.readAsDataURL(blob);
        },
        'image/jpeg',
        quality
      );
    };
    
    img.onerror = reject;
    // #endif

    // 其他平台(如App)或未处理平台返回原图
    // #ifndef MP-WEIXIN, H5
    resolve(filePath);
    // #endif
  });
}

// 多图上传(带水印功能)
export function headerUploads(count = 9, orderNumber = '', watermarkInfo, pageContext) {
    return new Promise((resolve, reject) => {
        uni.chooseImage({
            count,
            success: async res => {
                try {
                    const tempFiles = res.tempFilePaths;
                    let processedFiles = tempFiles;
                    
                    // 如果需要加水印
                    if (watermarkInfo && watermarkInfo.number) {
                        const watermarkLines = [
                            `时  间: ${format(new Date(), false)}`,
                            `资金方: ${watermarkInfo.capitalTraderName || ''}`,
                            `卖  家: ${watermarkInfo.sellerTraderName || ''}`,
                            `买  家: ${watermarkInfo.traderName || ''}`,
                            `订单号: ${watermarkInfo.number}`
                        ];
                        
                        // 串行处理水印(确保画布状态独立)
                        processedFiles = [];
                        for (const imgPath of tempFiles) {
                            try {
                                const watermarked = await addWatermarkByContext(
                                    imgPath, 
                                    watermarkLines, 
                                    pageContext
                                );
                                processedFiles.push(watermarked);
                            } catch (e) {
                                console.error('水印处理失败,使用原图:', e);
                                processedFiles.push(imgPath); // 失败时使用原图
                            }
                        }
                    }
                    
                    // 上传所有处理后的文件
                    const UploadList = await uploadAllFiles(processedFiles, orderNumber);
                    resolve(UploadList);
                } catch (e) {
                    reject(e);
                }
            },
            fail: reject
        });
    });
}

// 重置画布状态(关键解决画布污染问题)
function resetCanvasContext(pageContext) {
    if (!pageContext) return;
    
    // 重置画布尺寸(避免上一张图片的尺寸影响)
    pageContext.canvasWidth = 0;
    pageContext.canvasHeight = 0;
    
    // 清除画布内容
    const ctx = uni.createCanvasContext('watermarkCanvas', pageContext);
    ctx.clearRect(0, 0, pageContext.canvasWidth || 500, pageContext.canvasHeight || 500); // 清除超大区域确保干净
    ctx.draw(); // 立即执行清除
}

// 具体的加水印
function addWatermarkByContext(imgPath, watermarkLines, pageContext) {
    // 无需水印直接返回原图
    if (!watermarkLines || watermarkLines.length === 0 || !pageContext) {
        return Promise.resolve(imgPath);
    }
    
    return new Promise((resolve, reject) => {
        // 1. 先重置画布状态(关键步骤)
        resetCanvasContext(pageContext);
        
        // 2. 获取原图信息
        uni.getImageInfo({
            src: imgPath,
            success: (imgInfo) => {
                const { width: imgWidth, height: imgHeight } = imgInfo;
                const dpr = 1// uni.getSystemInfoSync().pixelRatio;
				let dpr2 = uni.getSystemInfoSync().pixelRatio
                
                // 3. 设置画布尺寸(使用原图尺寸)
                pageContext.canvasWidth = imgWidth * dpr;
                pageContext.canvasHeight = imgHeight * dpr;
				
				const maxCanvasSize = 4096; // 大多数设备的限制
				if (imgWidth * dpr > maxCanvasSize || imgHeight * dpr > maxCanvasSize) {
				    const scale = Math.min(maxCanvasSize/(imgWidth*dpr), maxCanvasSize/(imgHeight*dpr));
				    pageContext.canvasWidth = imgWidth * dpr * scale;
				    pageContext.canvasHeight = imgHeight * dpr * scale;
				}
                
                // 4. 创建画布上下文
                const ctx = uni.createCanvasContext('watermarkCanvas', pageContext);
                ctx.scale(dpr, dpr);
                
                // 5. 绘制原图
                ctx.drawImage(imgPath, 0, 0, imgWidth, imgHeight);
                
                // 6. 绘制水印
                const fontSize = 28 * dpr2;
                const lineHeight = fontSize + 8;
                const margin = 20 * dpr2;
                ctx.setFontSize(fontSize);
                ctx.setFillStyle('#3975e2');
                ctx.setTextAlign('left');
                ctx.setTextBaseline('bottom');
				console.log(777);
                
                watermarkLines.forEach((line, index) => {
                    const y = imgHeight - margin - (index * lineHeight);
                    ctx.fillText(line, margin, y);
                });
                
                // 7. 延迟确保绘制完成
                setTimeout(() => {
                    ctx.draw(false, () => {
                        uni.canvasToTempFilePath({
                            canvasId: 'watermarkCanvas',
                            destWidth: imgWidth,
                            destHeight: imgHeight,
                            fileType: 'jpg',
                            quality: 0.9,
                            success: (res) => {
                                // 8. 再次重置画布(为下一张图准备)
                                resetCanvasContext(pageContext);
                                resolve(res.tempFilePath);
                            },
                            fail: (err) => {
                                resetCanvasContext(pageContext);
                                reject(new Error(`Canvas转图片失败: ${JSON.stringify(err)}`));
                            }
                        }, pageContext);
                    });
                }, 300); // 适当延迟确保绘制完成
            },
            fail: (err) => {
                reject(new Error(`获取图片信息失败: ${JSON.stringify(err)}`));
            }
        });
    });
}