使用canvas进行绘制

// 合成后的分享图临时路径
const shareImgUrl = ref('');

// 图标
const STATIC_USER = '/static/images/shareImg/user.png';

const assetUserPath = ref('');

// 绘制图片到画布(通用函数)
const drawImageToCanvas = async (canvas, ctx, imgSrc, x, y, width, height) => {
  return new Promise((resolve, reject) => {
    if (!imgSrc) {
      reject(new Error('图片路径为空'));
      return;
    }
    
    const img = canvas.createImage();
    img.src = imgSrc;
    
    img.onload = () => {
      ctx.drawImage(img, x, y, width, height);
      resolve(img);
    };
    
    img.onerror = (err) => {
      console.error(`图片加载失败: ${imgSrc}`, err);
      reject(err);
    };
  });
};

// 缓存,避免每次打开都重新下载图片(能显著减少 4~5s 等待)
const imagePathCache = new Map();
const resolveLocalImagePath = (src) => {
	if (imagePathCache.has(src)) return imagePathCache.get(src);

	const p = new Promise((resolve) => {
		if (!src) return resolve('');
		const isAbsoluteLike =
			src.startsWith('/') ||
			src.startsWith('https') ||
			src.startsWith('wxfile://') ||
			src.startsWith('file://') ||
			src.startsWith('data:');
		const normalizedSrc = isAbsoluteLike ? src : `/${src}`;
		uni.getImageInfo({
			src: normalizedSrc,
			success: (res) => {
				const p = res?.path || normalizedSrc;
				resolve(p.startsWith('static/') ? `/${p}` : p);
			},
			fail: () => resolve(normalizedSrc),
		});
	});
	imagePathCache.set(src, p);
	return p;
};

// 通用函数
const setAssetPath = async (r, p) => !r.value && (r.value = await resolveLocalImagePath(p))


// 绘制分享卡片
const createShareImg = async () => {
  try {

	// 并行执行,速度更快
	await Promise.all([
		[assetUserPath, STATIC_USER],
	].map(([ref, path]) => setAssetPath(ref, path)))

    
    // 1. 获取活动信息
    const activityInfo = getActivityInfo();
    
    // 2. 查询 Canvas 节点(Vue 3 script setup 中不需要 .in(this))
    const query = uni.createSelectorQuery();
    const res = await new Promise(resolve => {
      query.select('#shareCanvas')
        .fields({ node: true, size: true })
        .exec(resolve);
    });

    if (!res[0]?.node) {
      console.error('Canvas 节点获取失败');
      return;
    }

    const canvas = res[0].node;
    const ctx = canvas.getContext('2d');
	// 从基础库 2.20.1 开始,本接口停止维护,建议使用 uni.getWindowInfo() 获取窗口信息
    // const dpr = uni.getSystemInfoSync().pixelRatio;
    const dpr = uni.getWindowInfo().pixelRatio;
    // 3. 设置画布分辨率(5:4)
    canvas.width = 500 * dpr;
    canvas.height = 400 * dpr;
    ctx.scale(dpr, dpr);

    // 4. 绘制背景图
	try {
		// 参数从左到右说明, 图片文件,x轴开始位置,y轴开始位置,x轴结束位置,y轴结束位置
    	await drawImageToCanvas(canvas, ctx, activityInfo.bgImg, 0, 0, 500, 400); 
    } catch (e) {
      	console.warn('背景图加载失败,使用默认图占位:', e.message);
		await drawImageToCanvas(canvas, ctx, assetDefaultBgPath.value, 0, 0, 500, 400); 
    }

    // 5. 绘制绿色边框(模拟卡片效果)
	if (assetBorderPath.value) {
		try {
			// 参数从左到右说明, 图片文件,x轴开始位置,y轴开始位置,x轴结束位置,y轴结束位置
			await drawImageToCanvas(canvas, ctx, assetBorderPath.value, 0, 0, 500, 400);
		} catch (e) {
			console.warn('边框绘制失败:', e.message);
		}
	}


    // 7. 绘制右上角红色价格标签
	if (activityInfo.price) {
		try {
			// 参数从左到右说明, 图片文件,x轴开始位置,y轴开始位置,x轴结束位置,y轴结束位置
			await drawImageToCanvas(canvas, ctx, assetLabelPath.value, 390, 1, 69, 80);
		}catch(e){
			console.warn('价格标签背景绘制失败:', e.message);
		}
		
		ctx.fillStyle = '#fff';
		ctx.font = '15px sans-serif';
		ctx.textAlign = 'center';
		ctx.fillText(activityInfo.priceTagText, 427, 25);
		ctx.fillText('¥', 406, 57);
		ctx.font = 'bold 22px sans-serif';
		ctx.fillText(activityInfo.price, 430, 57);
	}
	

    // 8. 绘制底部信息栏背景
	if (assetJianbianPath.value) {
		try {
			await drawImageToCanvas(canvas, ctx, assetJianbianPath.value, 0, 270, 500, 130);
		} catch (e) {
			console.warn('底部信息背景绘制失败:', e.message);
		}
	}

    // 9. 绘制报名信息、时间、地址图标和文字
    ctx.fillStyle = '#fff';
    ctx.font = '20px sans-serif';
    ctx.textAlign = 'left';

    // 报名信息
    if (activityInfo.participant) {
      try {
		await drawImageToCanvas(canvas, ctx, assetUserPath.value, 23, 295, 17, 17);
      } catch (e) {
        console.warn('报名图标加载失败:', e.message);
      }
      ctx.fillText(activityInfo.participant, 50, 310);
    }

    // 时间
    if (activityInfo.time) {
      try {
      await drawImageToCanvas(canvas, ctx, assetTimePath.value, 23, 330, 17, 17);
	} catch (e) {
        console.warn('时间图标加载失败:', e.message);
      }
      ctx.fillText(`时间:${activityInfo.time}`, 50, 345);
    }

    // 地址
    if (activityInfo.address) {
      try {
		await drawImageToCanvas(canvas, ctx, assetAddressPath.value, 23, 365, 17, 17);
      } catch (e) {
        console.warn('地址图标加载失败:', e.message);
      }
      ctx.fillText(`地址:${(activityInfo.address || '').slice(0,19)} ${(activityInfo.address || '').length>19 ? '...' : ''}`, 50, 380);
    }

    // 10. 转成临时图片
    uni.canvasToTempFilePath({
      canvas: canvas,
      width: 500,
      height: 400,
      success: (res) => {
        shareImgUrl.value = res.tempFilePath;
        console.log('分享卡片生成成功:', shareImgUrl.value);
      },
      fail: (err) => {
        console.error('生成分享卡片失败:', err);
      }
    });
  } catch (err) {
    console.error('createShareImg 异常:', err);
  }
}

// 分享给好友
onShareAppMessage(() => {
	const detail = activityDetail.value;
	return {
		// 标题不切割,微信限制最多展示两行
		title: `${formatStartDate.value} | ${detail.activityName}` || '精彩活动等你来',
		path: `/pages/active/ActivityDetail?id=${contentId.value}`,
		imageUrl: shareImgUrl.value
	};
});

更多推荐