uniapp canvas小程序分享海报的实现

小程序中经常会有海报的生成,为了使用方便就自己封装了js文件方便调用
文件名为creatMaterialImage.js
海报图效果如下:
在这里插入图片描述

1,海报生成方法的具体实现

// 用canvas生成分享图片,返回src
const createShareImg = function(options, that) {

	return new Promise(function(resolve, reject) {

		getImgsInfo([options.productImg, options.qrCode, options.bg, options.headUrl]).then(function(res) {

			//底部颜色
			let context = uni.createCanvasContext(options.canvasId, that);
			context.setFillStyle('#fff')
			context.fillRect(0, 0, 600, 1008);
			context.save();
			//产品图片
			if (res[0]) {
				context.drawImage(res[0], 0, 0, 600, 830);
			}
			
			//头像
			if (res[3]) {
				roundRect(context, 30, 918, 36, 36, 18)
				context.drawImage(res[3], 30, 918, 36, 36, 18);
				context.restore();
			}
			var name = options.memberName.length > 5 ? options.memberName.substring(0, 4) + '...' : options.memberName;
			context.setFillStyle('#333333');
			context.setFontSize(24);
			context.setTextAlign('left');
			context.fillText('我是您的专属顾问 ' + name, 80, 945);
			context.save();

			context.setFillStyle('#999999');
			context.setFontSize(22);
			context.setTextAlign('right');
			context.fillText('长按扫码进店', 550, 945);
			context.save();

			//二维码
			if (res[1]) {
				roundRect(context, 390, 720, 180, 180, 90)
				context.drawImage(res[1], 390, 720, 180, 180, 90);
				context.restore();
			}
			var shopname = options.productName.length > 7 ? options.productName.substring(0, 6) + '...' : options.productName;
			context.setFillStyle('#333');
			context.setFontSize(32);
			context.setTextAlign('left');
			context.font = 'normal bold 32px sans-serif';
			// context.setTextAlign('center');
			context.fillText('欢迎光临' + shopname, 30, 900);
			context.save();

			const result = uni.getSystemInfoSync();
			const platform = result.platform;
			let time = 0;
			if (platform === 'android') {
				// 在安卓平台,经测试发现如果海报过于复杂在转换时需要做延时,要不然样式会错乱
				time = 300;
			}
			//console.log(context);
			context.draw(false, function() {
				//console.log('222');
				setTimeout(function() {
					//将生成好的图片保存到本地
					uni.canvasToTempFilePath({
						canvasId: options.canvasId,
						fileType: 'jpg',
						success(res) {
							//console.log(res.tempFilePath, '111');
							resolve(res.tempFilePath);
						},
						fail(err) {
							console.log('canvasToTempFilePath err', err);
							reject(err);
						}
					}, that);
				}, time);
			});
		}).catch(function(err) {
			console.log('getImgsInfo err', err);
			reject(err);
		})
	})
}

/**
 * 
 * @param {CanvasContext} ctx canvas上下文
 * @param {number} x 圆角矩形选区的左上角 x坐标
 * @param {number} y 圆角矩形选区的左上角 y坐标
 * @param {number} w 圆角矩形选区的宽度
 * @param {number} h 圆角矩形选区的高度
 * @param {number} r 圆角的半径
 */
function roundRect(ctx, x, y, w, h, r) {
	// 开始绘制
	ctx.beginPath()
	// 因为边缘描边存在锯齿,最好指定使用 transparent 填充
	// 这里是使用 fill 还是 stroke都可以,二选一即可
	ctx.setFillStyle('#f94335')
	// ctx.setStrokeStyle('transparent')
	// 左上角
	ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)

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

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

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

	// border-left
	ctx.lineTo(x, y + r)
	ctx.lineTo(x + r, y)

	// 这里是使用 fill 还是 stroke都可以,二选一即可,但是需要与上面对应
	ctx.fill()
	// ctx.stroke()
	ctx.closePath()
	// 剪切
	ctx.clip()
}



/**
 * [分割字符串]
 * @param  {[type]} str  [字符串]
 * @param  {[type]} num  [每行个数]
 * @param  {[type]} line [行数]
 * @return {[type]}      [返回字符串数组]
 */
const splitStr = (str, num, line) => {
	let result = [];
	for (let i = 0; i < line; i++) {
		result.push(str.substr(i * num, num));
	}
	if (result[line - 1].length == num) {
		result[line - 1] = result[line - 1].substr(0, num - 1) + '...';
	}
	// console.log(result);
	return result;
}

// 获取单张图片信息
const getImgInfo = (path) => {
	return new Promise((resolve, reject) => {
		if (path) {
			uni.getImageInfo({
				src: path,
				success(res) {
					// console.log(res);
					resolve(res.path);
				},
				fail(err) {
					//console.log(err);
					// reject(err);
					resolve('');
				}
			})
		} else {
			resolve('');
		}

	})
}


// 获取多张图片的信息
const getImgsInfo = (paths) => {
	// console.log(paths);
	return new Promise((resolve, reject) => {
		let promises = paths.map(item => {
			return getImgInfo(item);
		});
		Promise.all(promises).then(res => {
			// console.log(res);
			resolve(res);
		}).catch(err => {
			reject(err);
		});
	})
}

export default {
	createShareImg
}

2、具体使用方法

<template>
	<view style="background: #f5f5f5;display: flex;flex-direction: column;min-height: 100vh;">
		<view class="scroll_image">
			<view class="item_page">
				<swiper class="swiper" style="height: 1008upx;" @change="pageChange" :current="pageIndex" previous-margin="75rpx" next-margin="45rpx" :duration="duration">
					<swiper-item v-for="(item, index) in posterData.InfoPosterUrlList" :key="index">
						<view class="item_style"><image style="width: 600upx;height: 830upx;border-radius: 15upx 15upx 0 0;" :src="imageUrl + item" mode="aspectFill"></image></view>
						<view style="background: #fff;height: 153upx;width: 580upx;padding-left: 20rpx;padding-top: 25rpx;border-radius: 0 0 15upx 15upx;">
							<view style="font-size: 32rpx;font-weight: bold;">欢迎光临{{posterData.MallName}}</view>
							<view style="font-size: 24upx;color: #333;display: flex;align-items: center;margin-top: 20rpx;">
								<image :src="posterData.WxHeadUrl" mode="aspectFill" style="width: 36rpx;height: 36rpx;border-radius: 100%;margin-right: 10rpx;"></image>
								<view style="font-size: 24rpx;color: #999;">我是您的专属顾问 {{ posterData.EmployeeName }}</view>
							</view>
						</view>
						<view style="position: absolute;right: 60rpx;top: 740rpx;">
							<image :src="miUrl" mode="aspectFill" style="width: 180rpx;height: 180rpx;border-radius: 90rpx;"></image>
							<view style="font-size: 24rpx;color: #999;text-align: center;margin-top: 10rpx;">长按扫码识别</view>
						</view>
					</swiper-item>
				</swiper>
			</view>
		</view>
		<!-- <view style="margin-bottom: 200upx;" >
			<image :src="canvasImage" mode="widthFix" style="width: 600upx;"></image>
		</view> -->
		<view style="height: 100rpx;">
		</view>
		<view style="height: 150rpx;">
		</view>
		<view class="fixed_bottom">
			<view class="btn_save" @click="savePic">保存图片</view>
		</view>
		<canvas canvas-id="canvas" style="position: fixed;left: -100000px;width: 600px;height: 1008px;"></canvas>
	</view>
</template>

<script>
var userInfo;
import config from '../../../common/js/config.js';
import createImage from '../../../common/js/creatMaterialImage.js';
export default {
	data() {
		return {
			duration: 500,
			canvasImage: '',
			posterData: {},
			pageIndex: 0,
			imageUrl: config.imgUrl,
			qrCodeUrl: '',
			miUrl: '',
			isFirstSave: true,
			isQyWx:true
		};
	},
	onLoad() {
		userInfo = uni.getStorageSync('userInfo');
		var that = this;
		uni.getSystemInfo({
		  success (res) {
			if(res.environment && res.environment=="wxwork") {
				that.isQyWx=true;
			}else{
				that.isQyWx=false;
			}
		  }
		})
		uni.showShareMenu({
		      // 是否使用带 shareTicket 的转发
		      withShareTicket: true
		});
		this.getQrCodeData();
	},
	methods: {
		async getData() {
			try {
				let data = {};
				let res = await this.$ajax({
					url: 'infoPosterIndex',
					data: data
				});
				if (res.IsSuccess) {
					res.Result.MallName = res.Result.MallName.length>7 ? res.Result.MallName.substring(0,6)+'...' : res.Result.MallName;
					res.Result.EmployeeName = res.Result.EmployeeName.length>5 ? res.Result.EmployeeName.substring(0,4)+'...' : res.Result.EmployeeName;
					this.posterData = res.Result;
					this.getMaterialImg(false);
				}
			} catch (e) {
				//TODO handle the exception
			}
		},
		//获取小程序码
		async getQrCodeData() {
			try {
				let data = {
					Page: 'pages/index/index',
					Scene: 'f=' + userInfo.EmployeeId + '&t=0',
					IsLimited: false,
					ProgramType: 0 //小程序类型 
				};
				let res = await this.$ajax({
					url: 'getQrCode',
					data: data
				});
				if (res.IsSuccess) {
					this.qrCodeUrl = JSON.parse(res.Result);
					this.miUrl = this.imageUrl + this.qrCodeUrl;
					this.getData();
					console.log(this.miUrl);
				}
			} catch (e) {
				//TODO handle the exception
			}
		},
		pageChange(e) {
			this.pageIndex = e.detail.current;
			this.getMaterialImg(false);
		},
		getMaterialImg(type) {
			if (type) {
				//显示加载
				uni.showLoading({
					title: '正在生成图片...'
				});
			}
			const params = {
				canvasId: 'canvas',
				productImg: this.imageUrl + this.posterData.InfoPosterUrlList[this.pageIndex],
				productName: this.posterData.MallName,
				qrCode: this.imageUrl + this.qrCodeUrl,
				bg: 'https://cdn.dkycn.cn/images/melyemplyee/shareMall_bg.png',
				headUrl: this.posterData.WxHeadUrl,
				memberName: this.posterData.EmployeeName
			};

			var that = this;
			createImage
				.createShareImg(params, that)
				.then(function(res) {
					if (type) {
						//显示加载
						uni.hideLoading();
					}
					that.canvasImage = res;
					// that.saveToAlbum();
					console.log(that.canvasImage, 'createImg')
				})
				.catch(err => {
					uni.showToast({
						title: '图片生成失败',
						icon: 'none'
					});
				});
		},
		savePic() {
			this.saveToAlbum();
		},
		saveToAlbum() {
			var that = this;
			if(that.isQyWx){
				that.SaveImage();
			}else{
				wx.getSetting({
					success(res) {
						if (that.isFirstSave) {
							if (typeof res.authSetting['scope.writePhotosAlbum'] == 'undefined') {
								that.SaveImage();
							} else if (!res.authSetting['scope.writePhotosAlbum']) {
								uni.showModal({
									title: '提示',
									content: '保存图片需要开启权限,是否前往设置?',
									confirmText: '前往',
									cancelText: '取消',
									success(res) {
										if (res.confirm) {
											uni.openSetting({});
										}
									}
								});
							} else {
								that.SaveImage();
							}
				
							that.isFirstSave = false;
						} else {
							if (!res.authSetting['scope.writePhotosAlbum']) {
								uni.showModal({
									title: '提示',
									content: '保存图片需要开启权限,是否前往设置?',
									confirmText: '前往',
									cancelText: '取消',
									success(res) {
										if (res.confirm) {
											uni.openSetting({});
										}
									}
								});
							} else {
								that.SaveImage();
							}
						}
					},
					fail() {}
				});
			}
		},
		SaveImage() {
			var that = this;
			wx.getImageInfo({
				src: that.canvasImage,
				success(files) {
					uni.showLoading({
						title: '图片保存中...'
					});
					wx.saveImageToPhotosAlbum({
						filePath: files.path,
						success(res) {
							console.log('--saveImageToPhotosAlbum----', res);
							uni.hideLoading();
							setTimeout(function() {
								wx.showModal({
									content: '图片已保存,赶紧去分享吧~',
									showCancel: false,
									confirmText: '好的',
									confirmColor: '#333',
									success: function(res) {
										if (res.confirm) {
										}
									}
								});
								uni.hideLoading();
							}, 200);
						},
						complete() {
							uni.hideLoading();
						}
					});
				}
			});
		}
	},
	onShareAppMessage(e) {
		if (e.from == 'button') {
			console.log(this.canvasImage);
			return {
				title: '欢迎光临' + this.posterData.MallName + ',我是您的专属顾问' + this.posterData.EmployeeName,
				imageUrl: this.canvasImage,
				path: '/pages/errPage/errPage?fromMemberType=0&fromMemberId=' + this.posterData.EmployeeId
			};
		}
	}
};
</script>

<style lang="scss" scoped>
.scroll_image {
	// margin-bottom: 200upx;
	height: 85vh;
	.item_page {
		height: 85vh;
		.swiper {
			margin-top: 5vh;
		}
	}
}
.fixed_bottom {
	height: 10vh;
	// margin-top: 40upx;
	background: #fff;
	display: flex;
	position: fixed;
	// position: absolute;
	bottom: 0;
	left: 0;
	right: 0;
	justify-content: center;
	padding: 0 100upx;
	align-items: center;
	.bottom_item {
		display: flex;
		flex-direction: column;
		align-items: center;
		font-size: 24upx;
		color: #666;
	}
	.btn_save {
		width: 550upx;
		height: 70upx;
		border-radius: 50upx;
		background: #418cf6;
		font-size: 26upx;
		color: #fff;
		text-align: center;
		line-height: 70upx;
	}
}

.item_style {
	width: 600upx;
	background: #fff;
	border-radius: 15upx;
}
.post_center{
	width: 540rpx;
	height: 550rpx;
	border-radius: 10rpx;
	position: absolute;
	top: 315rpx;
	margin-left: 30rpx;
	background: #fff;
	box-shadow: 0px 0px 25px 0px 
		rgba(0, 0, 0, 0.08);
}
.item_bottom {
	height: 15vh;
	display: flex;
	width: 600upx;
	align-items: center;
	background: #fff;
	justify-content: space-between;
	margin-right: 50upx;
	border-radius: 0 0 15upx 15upx;
}
</style>

发布了29 篇原创文章 · 获赞 40 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/xiyunmengyuan/article/details/105434231