小程序一站式海报生成解决方案:从分享到保存的全链路实践指南

🌟 组件亮点功能
  • 双模式弹窗:支持微信分享与海报生成两种交互
  • 智能参数解析:自动处理扫码场景参数
  • 自适应画布:完美适配不同设备屏幕
  • 安全保存:完善的相册权限处理流程

🛠 核心实现解析

1. 参数传递流程图
graph TD
    A[页面调用] --> B{参数类型}
    B -->|scene参数| C[后端解析]
    B -->|直接code| D[直接使用]
    C --> E[获取详细数据]
    D --> F[数据展示]
2. Canvas绘制优化点
// 使用Promise保证图片加载顺序
Promise.all([img1, img2]).then(res => {
    // 动态计算画布尺寸
    const ratio = canvasWidth / originWidth;
    canvasHeight = originHeight * ratio + codeSize;
    
    // 添加圆角矩形背景
    ctx.arc(x+r, y+r, r, Math.PI, Math.PI*1.5);
    // ...其他路径绘制
    ctx.clip();
    
    // 层级绘制策略
    ctx.drawImage(背景图, 0, 0, ...);
    ctx.drawImage(二维码, 居中位置计算, ...);
});
3. 安全保存策略
uni.saveImageToPhotosAlbum({
    success: () => {
        // 成功提示
    },
    fail: (err) => {
        // 引导开启权限
        uni.openSetting({
            success: (res) => {
                // 二次授权处理
            }
        });
    }
});

🚀 性能优化建议

  1. 图片预加载
    在组件挂载时预加载静态资源:
mounted() {
    this.shareList.forEach(item => {
        uni.preloadImage(item.icon);
    });
}
  1. Canvas缓存策略
    对已生成的canvas内容进行本地缓存:
const cacheKey = `poster_${Date.now()}`;
try {
    uni.setStorageSync(cacheKey, tempFilePath);
} catch (e) {
    console.error('存储失败');
}
  1. 动态分辨率适配
    根据DPI调整绘制精度:
const dpi = uni.getSystemInfoSync().pixelRatio;
ctx.scale(dpi, dpi);

🐞 常见问题排查

Q1:海报模糊怎么办?
✅ 解决方案:

  1. 确保使用2倍图资源
  2. 设置canvas尺寸时乘以pixelRatio
const dpi = uni.getSystemInfoSync().pixelRatio;
canvasWidth *= dpi;
canvasHeight *= dpi;

Q2:安卓保存失败?
✅ 处理方案:

// 增加格式转换
wx.canvasToTempFilePath({
    fileType: 'jpg',
    quality: 0.8,
    // ...
});

Q3:扫码进入无数据?
✅ 排查步骤:

  1. 检查scene参数是否URL编码
  2. 验证后端接口返回数据结构
  3. 添加失败重试机制:
let retryCount = 0;
const getData = () => {
    appModel.analysisCode(params).catch(() => {
        if(retryCount++ < 3) getData();
    });
}

📦 组件注册建议

推荐使用按需加载方式:

// 在需要的页面中
import Poster from '@/components/poster';

export default {
    components: {
        Poster
    }
}

完整代码

<template>
	<view class="poster">
		<u-popup v-model="shareShow" mode="bottom" border-radius="20" height="420rpx" @close="shareShowFn">
			<view class="shareBox">
				<button class="shareButton" open-type="share" @click="shareLink">
					<u-icon :label="shareList[0].label" label-pos="bottom" margin-top="20" label-color="#282828"
						size="110" :name="shareList[0].icon"></u-icon>
				</button>
				<u-icon @click="posterShowFn" style="width: 50%;justify-content: center;" :label="shareList[1].label"
					label-pos="bottom" margin-top="20" label-color="#282828" size="110" :name="shareList[1].icon">
				</u-icon>
			</view>
			<view class="shareCancel" @click="$emit('posterClose',1)">取消</view>
		</u-popup>

		<u-popup v-model="posterShow" mode="center" width="85%" safe-area-inset-bottom @close="$emit('posterClose',1)">
			<view class="canvasBox">
				<!-- <u-image class="imageBox" border-radius="14" :show-error="false" :src="posterImg" mode="widthFix"
							:fade="false"></u-image> -->
				<canvas canvas-id="shareCanvas" class="canvas"
					:style="{height: canvasHeight+'px',width:canvasWidth+'px'}">
				</canvas>


			</view>
			<u-button class="button" v-show="drawFinish" hover-class="none" type="primary" @click="downImg"
				shape="circle">保存图片
			</u-button>
		</u-popup>

	</view>
</template>
<script>
	import {
		AppModel
	} from '@/models/app.js'
	const appModel = new AppModel();
	export default {
		data() {
			return {
				shareShow: true,
				posterShow: false,
				posterImg: 'https://www.xxxx.com/home/photo/hyq/wechat/weChatShare.png',
				shareList: [{
					label: '微信好友',
					icon: 'https://www.xxxxxx.com/home/photo/hyq/wechat/share_wx.png'
				}, {
					label: '生成海报',
					icon: 'https://www.xxxxxx.com/home/photo/hyq/wechat/share_hb.png'
				}],
				pageData: {
					post_img: '',
					codePng: 'https://www.xxxxxx.com/home/photo/hyq/wechat/qrcodetest.png'
				},
				ctx: null,
				windowObj: {},
				canvasHeight: 0,
				canvasWidth: 0,
				drawFinish: false
			}
		},
		props: {
			posterData: {
				type: Object,
				default: {}
			}
		},
		onReady() {
			// 在自定义组件下,第二个参数传入组件实例this
			this.ctx = wx.createCanvasContext('shareCanvas', this)
		},
		methods: {
			posterShowFn() {
				this.posterShow = true
				this.shareShow = false
				appModel.shareGetCode(this.posterData).then(res => {
					console.log(res, 'shareGetCode');
					this.exportPost(res.data)
				})
			},
			shareShowFn() {
				if (!this.posterShow) {
					this.$emit('posterClose', 1)
				}
			},
			//获取图片的基本信息,即将网络图片转成本地图片,
			getImageInfo(src) {
				console.log(src, "src,src");
				return new Promise((resolve, reject) => {
					wx.getImageInfo({
						src,
						success: (res) => {
							console.log(res)
							resolve(res)
						},
						fail: (res) => {
							uni.hideLoading()
							uni.showToast({
								title: '图片下载失败',
								icon: 'none',
								duration: 3500
							})
							reject(res)
						}
					})
				});
			},
			downImg() {
				let that = this
				uni.showLoading({
					title: '海报下载中',
					mask: true
				})
                //画布。2.9.0 起支持一套新 Canvas 2D 接口(需指定 type 属性)
				wx.canvasToTempFilePath({
					quality: 1,
					canvasId: 'shareCanvas',
					// width: that.canvasWidth,
					// height: that.canvasHeight,
					// destWidth: that.canvasWidth*2,
					// destHeight: that.canvasHeight*2,
					fileType: 'png', //设置导出图片的后缀名
					success: function(res) {
						that.pageData.post_img = res.tempFilePath
						//保存图片到本地
						uni.saveImageToPhotosAlbum({
							filePath: that.pageData.post_img,
							success: function() {
								uni.hideLoading()
								uni.showToast({
									title: '保存成功'
								})
								that.$emit('posterClose', 1)
							},
							fail: (err) => {
								uni.hideLoading()
								uni.showModal({
									title: '提示',
									content: '需要您授权保存相册',
									showCancel: false,
									success(res) {
										if (res.confirm) {
											uni.openSetting({
												success(settingdata) {
													if (settingdata.authSetting[
															'scope.writePhotosAlbum'
														]) {
														uni.showModal({
															title: '提示',
															content: '获取权限成功,再次保存图片即可成功',
															showCancel: false,
														})
													} else {
														uni.showModal({
															title: '提示',
															content: '获取权限失败,无法保存到相册',
															showCancel: false
														})
													}
												}
											})
										}
									}
								})
							}
						})
					},
					fail: (err) => {
						uni.hideLoading()
						console.log(err)
					}
				}, this)
				//uni.canvasToTempFilePath(object, component)在自定义组件下,第二个参数传入自定义组件实例,以操作组件内 <canvas> 组件
			},
			exportPost(codeData) {
				let that = this
				uni.showLoading({
					title: '海报生成中',
					mask: true
				})
				uni.getSystemInfo({
					success: (systemInfo) => {
						console.log(systemInfo, 'systemInfo')
						//获取系统的基本信息,为后期的画布和底图适配宽高
						Promise.all([that.getImageInfo(codeData.posterPath), that.getImageInfo(codeData
							.qrCodePath)]).then(res => {
							//获取底图和二维码图片的基本信息,通常前端导出的二维码是base64格式的,所以要转成图片格式的才可以获取图片的基本信息

							that.canvasWidth = systemInfo.windowWidth * 0.85 //设置画布的宽高
							let ratio = that.canvasWidth / res[0].width
							let codeWidth = res[1].width * 0.27
							that.canvasHeight = res[0].height * ratio + codeWidth + 20
							// 设置背景色


							let x = 0,
								y = 0,
								w = that.canvasWidth,
								h = that.canvasHeight,
								r = 10
							// that.ctx.beginPath()
							// // 因为边缘描边存在锯齿,最好指定使用 transparent 填充
							// // that.ctx.setFillStyle('transparent')
							// that.ctx.setStrokeStyle('transparent')
							// // 左上角
							// that.ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)

							// // border-top
							// that.ctx.moveTo(x + r, y)
							// that.ctx.lineTo(x + w - r, y)
							// that.ctx.lineTo(x + w, y + r)
							// // 右上角
							// that.ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2)

							// // border-right
							// that.ctx.lineTo(x + w, y + h - r)
							// that.ctx.lineTo(x + w - r, y + h)
							// // 右下角
							// that.ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5)

							// // border-bottom
							// that.ctx.lineTo(x + r, y + h)
							// that.ctx.lineTo(x, y + h - r)
							// // 左下角
							// that.ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI)

							// // border-left
							// that.ctx.lineTo(x, y + r)
							// that.ctx.lineTo(x + r, y)
							// // that.ctx.fill()
							// that.ctx.stroke()
							// that.ctx.closePath()
							// // 剪切
							// that.ctx.clip()

							that.ctx.setFillStyle('#fff')
							that.ctx.fillRect(0, 0, that.canvasWidth, that.canvasHeight)


							that.ctx.drawImage(res[0].path, 0, 0, that.canvasWidth, res[0].height *
								ratio);
							that.ctx.drawImage(res[1].path, (that.canvasWidth - codeWidth) * 0.5, res[
								0].height * ratio + 10, codeWidth, codeWidth);
							that.ctx.draw()
							that.drawFinish = true
							uni.hideLoading()
						})
					}
				});
			},
			shareLink() {
				appModel.shareLink(this.posterData).then(res => {

				})
			}
		},
	}
</script>

<style lang="scss">
	.shareBox {
		display: flex;
		justify-content: center;
		align-items: center;
		justify-content: space-around;
		height: 300rpx;
	}

	.shareCancel {
		border-top: 1rpx solid #EEEEEE;
		height: 100rpx;
		line-height: 100rpx;
		text-align: center;
		color: #666666;
		font-size: 28rpx;
	}

	.button {
		background-color: transparent;
		line-height: 0;
		padding: 0;
		font-size: 0;

		/deep/button {
			height: 100rpx;
			line-height: 1;
			width: 300rpx;
			margin-top: 60rpx;
			color: #4788e9 !important;
			background: #FFFFFF !important;
			font-weight: bold;
		}
	}

	.shareButton {
		background-color: transparent;
		line-height: 0;
		padding: 0;
		font-size: 0;
		width: 50%;
		margin: 0;

		&::after {
			border: 0;
		}
	}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值