uniapp 微信小程序 分享海报的实现

在这里插入图片描述
在这里插入图片描述
主页面

<template>
	<view class="page">
		<!-- 自定义导航栏-->
		<Navbar title="我的海报"></Navbar>
		<view class="container">
			<poster ref="poster" :imageUrl="image" :imageWidth="750" :imageHeight="1000" :drawData="drawData"
				:config="config" :wechatCode="false" @wechatCodeConfig="wechatCodeConfig" @click="createdPoster"
				@loading="onLoading" @success="onSuccess" @fail="onFail" class="poster">
				<!-- <template v-slot:save>
					<view @click="saveToAlbum">保存</view>
				</template> -->
			</poster>
			<u-image width="222rpx" height="222rpx" :src="code" class="container_image"></u-image>
		</view>
		<!-- <view class="bottom"> -->
		<!-- <view class="bottom_item" @click="share('微信好友')">
				<u-image width="74rpx" height="74rpx" 
					src="xxxxxxxxxxxxxxstatic/poster/%E7%BC%96%E7%BB%84%206%402x.png"></u-image>
				<text>微信好友</text>
			</view> -->
		<!-- <view class="bottom_item" @click="share('朋友圈')">
				<u-image width="74rpx" height="74rpx"
					src="xxxxxxxxxxxposter/%E7%BC%96%E7%BB%84%208%402x.png"></u-image>
				<text>朋友圈</text>
			</view> -->
		<!-- <view class="bottom_item" @click="share('保存图片')">
				<u-image width="74rpx" height="74rpx"  
					src="xxxxxxxxxoster/%E7%BC%96%E7%BB%84%2012%402x.png"></u-image>
				<text>保存图片</text>
			</view> -->
		<!-- </view> -->
	</view>
</template>

<script>
	import poster from "@/components/poster/index";
	import {
		saveImageToPhotosAlbum
	} from '@/utils/poster.js';
	export default {
		components: {
			poster
		},
		data() {
			return {
				code: '',
				canvasImages: '',
				image: 'xxxxxxter/static/poster/%E7%BC%96%E7%BB%84%205%402x.png',
				config: {
					imageMode: 'aspectFit',
					posterHeight: '100%',
					// canvasWidth 和 convasHeight使用的是px,防止不同设备Dpr不统一,导致最后图片留白边问题
					canvasWidth: 275,
					convasHeight: 600,
				},
				drawData: [],
				wechatCodeConfig: {
					serverUrl: '',
					scene: '123123',
					config: {
						x: 84.5,
						y: 320,
						w: 100,
						h: 100
					}
				}
			}
		},
		created() {
			this.usercode()
			// 模拟异步请求获得到的数据
			// setTimeout(() => {
			// this.wechatCodeConfig.scene = '456787';
			// this.drawData = [{
			// 		type: 'image',
			// 		config: {
			// 			url: 'https://horifon.oss-cn-shanghai.aliyuncs.com/hongyunartcenter/static/poster/%E7%BC%96%E7%BB%84%205.png',
			// 			x: 0,
			// 			y: 0,
			// 			w: 275,
			// 			h: 490
			// 		},
			// 	},
			// 	{
			// 		type: 'image',
			// 		config: {
			// 			url: this.code,
			// 			x: 40,
			// 			y: 380,
			// 			w: 40,
			// 			h: 40
			// 		},
			// 	},
			// 	{
			// 		type: 'text',
			// 		config: {
			// 			text: '',
			// 			x: 140,
			// 			y: 60,
			// 			color: '#E60012',
			// 			font: 'normal bold 16px 仿宋',
			// 			textAlign: 'center'
			// 		}
			// 	}
			// ];

			// }, 1000)
			// this.createdPoster();
		},
		methods: {
			//二维码
			usercode() {
				let $this = this
				uni.request({
					url: "xxxxxxx/mobile/index.php?m=user&c=indexapi&a=affiliate",
					method: 'POST',
					header: {
						'Content-Type': 'application/x-www-form-urlencoded'
					},
					data: {
						"user_id": parseInt(uni.getStorageSync('USER_ID'))
					},
					success(res) {
						console.log(res.data.data, '数据');
						$this.code = res.data.data
						$this.wechatCodeConfig.serverUrl = res.data.data
					}
				});
			},
			// 保存到相册
			saveToAlbum() {
				this.$refs.poster.saveToAlbum()
			},
			onLoading() {
				console.log('Loading:正在加载静态资源')
			},
			onSuccess(e) {
				console.log('Success:创建海报成功', e)
				this.canvasImages = e
				wx.showShareImageMenu({
					path: this.canvasImages,
					style:"background-color:'black'"
				})
			},
			onFail(e) {
				console.log('Fail:创建海报失败', e)
			},
			createdPoster() {
				console.log('点击')
				// 调用 createImage 开始创建海报
				// this.$refs.poster.createImage()
				this.$refs.poster.cjian()
				// wx.showShareImageMenu({
				// 	path: this.canvasImages
				// })
			},
			share(e) {
				if (e === '微信好友') {
					wx.showShareImageMenu({
						path: this.canvasImages
					})

				} else if (e === '朋友圈') {
					uni.share({
						provider: "weixin",
						scene: "WXSceneTimeline",
						type: 2,
						imageUrl: this.canvasImages,
						success: function(res) {
							console.log("success:" + JSON.stringify(res));
						},
						fail: function(err) {
							console.log("fail:" + JSON.stringify(err));
						}
					});
				} else if (e === '保存图片') {
					saveImageToPhotosAlbum(this.canvasImages).then(res => {
						uni.showToast({
							icon: 'none',
							title: '保存成功'
						})
					}).catch(err => {

					})

				}
			}
		}
	}
</script>

<style scoped lang="scss">
	.container {
		position: relative;
		width: 100%;
		height: 100%;
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;

		.poster {
			width: 100%;
			height: 100%;
		}

		.container_image {
			position: absolute;
			bottom: 214rpx;
			left: 76rpx;
		}
	}

	.bottom {
		display: flex;
		justify-content: space-evenly;
		align-items: center;
		position: absolute;
		bottom: 0;
		width: 750rpx;
		height: 218rpx;
		background: #FFFFFF;
		border-radius: 40rpx 40rpx 0rpx 0rpx;
		// border: 2rpx solid #FF583D;
	}
</style>

components/poster/index 组件

<template>
	<view class="poster_wrapper">
		<slot name="header"></slot>
		<!-- 要生成海报的图片 -->
		<image :src="imageUrl" mode="aspectFill" :style="{width:imageWidth + 'rpx'}"  @click="click" class="imagebig"></image>
		<!-- 这里把canvas移到了屏幕外面,如果需要查看canvas的话把定位去掉 -->
		<!-- position:'fixed',left:'9999px',top:'0' -->
		<canvas :style="{width:canvasWidth + 'px',height:canvasHeight + 'px',position:'fixed',left:'9999px',top:'0'}"
		 canvas-id="myCanvas" id="myCanvas" class="canvas"></canvas>
		<!-- 遮罩层 -->
		<!-- <view class="mask" v-if="showMask" @click="hideMask"> -->
			<!-- 生成的海报图 -->
			<!-- <image :style="posterSize" :src="lastPoster" :mode="config.imageMode" @click.stop=""></image> -->
			<!-- <view class="btn_wrapper" @click.stop>
				<slot name="save">
					<button type="primary" @click="saveToAlbum">保存至相册</button>
				</slot>
			</view> -->
		<!-- </view> -->
	</view>
</template>

<script>
	import {
		loadImage,
		createPoster,
		canvasToTempFilePath,
		saveImageToPhotosAlbum
	} from '@/utils/poster.js';
	import {
		getWechatCode
	} from "@/utils/appletCode.js";
	export default {
		props: {
			// 展示图片的宽 单位 rpx
			imageWidth: {
				type: [String, Number],
				default: 550
			},
			// 展示图片的高 单位 rpx
			imageHeight: {
				type: [String, Number],
				default:980
			},
			// 展示图片的url
			imageUrl: {
				type: String,
				default: '',
				required: true
			},
			// 绘制海报的数据参数
			// drawData: {
			// 	type: Array,
			// 	default: () => ([]),
			// 	required: true
			// },
			// 海报的配置参数
			config: {
				type: Object,
				default: () => ({
					imageMode: 'aspectFit',
					posterHeight:1000,
				}),
			},
			// 是否需要小程序二维码
			wechatCode: {
				type: Boolean,
				default: false
			},
			// 小程序二维码的配置参数
			wechatCodeConfig: {
				type: Object,
				default: () => ({
					serverUrl: '',
					scene: '',
					config: {
						x: 0,
						y: 0,
						w: 100,
						h: 100
					}
				}),
			}
		},
		data() {
			return {
				// 资源是否加载成功的标志
				readyed: false,
				// 将网络图片转成静态图片后的绘制参数
				imageMap: [],
				// 最后生成的海报的本地缓存地址
				lastPoster: '',
				// 是否展示遮罩
				showMask: false,
				// 是否加载资源的标志
				loadingShow: false,
				// 是否可以创建海报
				disableCreatePoster:false,
				drawData :[
					{
						type: 'image',
						config: {
							url: '',
							x: 0,
							y: 0,
							w: 275,
							h: 490
						},
					},
					{
						type: 'image',
						config: {
							url: '',
							x: 40,
							y: 380,
							w: 40,
							h: 40
						},
					},
					{
						type: 'text',
						config: {
							text: '',
							x: 140,
							y: 60,
							color: '#E60012',
							font: 'normal bold 16px 仿宋',
							textAlign: 'center'
						}
					}
				],
				
			}
		},
		computed: {
			// 所生成海报图的大小
			posterSize() {
				let str = '';
				this.config.posterWidth && (str += `width:${this.config.posterWidth};`);
				this.config.posterHeight && (str += `height:${this.config.posterHeight};`);
				return str
			},
			// 画布的宽,优先使用配置的宽,如果没用配置默认使用图片的宽
			// 需要主要的是这里canvas和image的单位不同,不过并不影响
			// 我们在绘制时(配置drawData)以px为基准进行绘制就行,用px的原因时防止不同设备Dpr不同导致无法确定画布的具体宽高,使得最后的图片可能会留白边
			canvasWidth(){
				return this.config.canvasWidth ? this.config.canvasWidth : this.imageWidth
			},
			// 画布的高,优先使用配置的高,如果没用配置默认使用图片的高
			canvasHeight(){
				return this.config.convasHeight ? this.config.convasHeight : this.imageHeight
			}
		},
		watch: {
			// 监听外部绘制参数的变化,重新加载资源
			// drawData(newVlaue) {
			// 	this.loadingResources(newVlaue)
			// },
			// 监听readyed变化
			// readyed(newVlaue) {
				
			// 	if (newVlaue == true && this.loadingShow == true) {
			// 		uni.hideLoading()
			// 		this.loadingShow = false;
					
			// 		this.createImage();
			// 	}
			// }
			// 会存在异步问题,还没解决。
			// 目前的解决方法 1.在绘制之前先改变 scene 2.改变scene后手动调用this.loadingResources 函数,但是资源会重新加载
			// "wechatCodeConfig.scene":function (newVlaue){
			// 	console.log('wechatCodeConfig.scene',this.imageMap)
			// 	this.loadingWechatCode(this.imageMap)
			// }
		},
		created() {
			this.usercode()
			// this.$emit('click')
			// this.loadingResources(this.drawData)
		},
		methods: {
			cjian(){
				this.usercode()
			},
			//二维码
			usercode() {
				let $this=this
				uni.request({
					url: "xxxxxxx?m=user&c=indexapi&a=affiliate",
					method: 'POST',
					header: {
						'Content-Type': 'application/x-www-form-urlencoded'
					},
					data: {
						"user_id": parseInt(uni.getStorageSync('USER_ID'))
					},
					success(res) {
						console.log(res.data.data,'数据');
						$this.drawData=[{
								type: 'image',
								config: {
									url: 'xxxxxxx/static/poster/%E7%BC%96%E7%BB%84%205%403x.png',
									x: 0,
									y: 0,
									w: 275,
									h: 490
								},
							},
							{
								type: 'image',
								config: {
									url: res.data.data,
									x: 35,
									y: 350,
									w: 70,
									h:70
								},
							},
							{
								type: 'text',
								config: {
									text: '',
									x: 140,
									y: 60,
									color: '#E60012',
									font: 'normal bold 16px 仿宋',
									textAlign: 'center'
								}
							}
						]
						// $this.wechatCodeConfig.serverUrl=res.data.data
						$this.loadingResources($this.drawData)
					}
				});
			},
			// 加载静态资源,创建或更新组件内所下载本地图片集合
			async loadingResources(drawData) {
				// this.readyed = false;
				if (!drawData.length || drawData.length <= 0) return;
				// 加载静态图片,将所以图片的网络地址替换成本地缓存地址
				const tempMap = [];
				for (let i = 0; i < drawData.length; i++) {
					let temp
					if (drawData[i].type === "image") {
						temp = await loadImage(drawData[i].config.url);
						drawData[i].config.url = temp;
					}
					tempMap.push({ 
						...drawData[i],
						url: temp
					})
				}
				// 加载小程序二维码
				await this.loadingWechatCode(tempMap);
				// 赋值给imageMap保存
				this.imageMap = tempMap;
				setTimeout(() => {
					// this.readyed = true;
					this.createImage()
				}, 100)
			},
			// 绘制海报图
			async createImage() {
				// console.log('点击执行',this.imageMap)
				// 禁用生成海报,直接返回
				// if(this.disableCreatePoster) return
				// this.disableCreatePoster = true;
				try {
					// if (!this.readyed) {
					// 	uni.showLoading({
					// 		title: '静态资源加载中...'
					// 	});
					// 	this.loadingShow = true;
					// 	this.$emit('loading')
					// 	return
					// }
					// 获取上下文对象,组件内一定要传this
					const ctx = uni.createCanvasContext('myCanvas', this);
					await createPoster(ctx, this.imageMap);
					this.lastPoster = await canvasToTempFilePath('myCanvas', this);
					this.showMask = true;
					console.log('点击执行',this.imageMap,this.lastPoster)
					// this.disableCreatePoster = false;
					// 创建成功函数
					this.$emit('success',this.lastPoster)
				} catch (e) {
					// 创建失败函数
					this.disableCreatePoster = false;
					this.$emit('fail', e)
				}
			},
			// 加载或更新小程序二维码
			async loadingWechatCode(tempMap) {
				if (this.wechatCode) {
					if (this.wechatCodeConfig.serverUrl) {
						const code = await getWechatCode(this.wechatCodeConfig.serverUrl, this.wechatCodeConfig.scene || '');
						// 记录替换的索引,没有就替换length位,即最后加一个
						let targetIndex = tempMap.length;
						for (let i = 0; i < tempMap.length; i++) {
							if (tempMap[i].wechatCode) targetIndex = i;
						}
						tempMap.splice(targetIndex, 1, {
							type: 'image',
							url: code.path,
							// 标记是小程序二维码
							wechatCode: true,
							config: this.wechatCodeConfig.config,
						})
					} else {
						throw new Error('serverUrl请求二维码服务器地址不能为空')
					}
				}
				return tempMap
			},
			// 保存到相册
			saveToAlbum() {
				saveImageToPhotosAlbum(this.lastPoster).then(res => {
					this.showMask = false;
					uni.showToast({
						icon: 'none',
						title: '保存成功'
					})
				}).catch(err => {
					
				})
			},
			click() {
				this.$emit('click')
				this.showMask = false;
				this.$emit('hidemask')
			},
			hideMask(){
				this.showMask = false;
				this.$emit('hidemask')
			}
		},
	}
</script>

<style scoped>
	.poster_wrapper {
		width: 100%;
		height:100vh;
		display: flex;
		flex-direction: column;
		align-items: center;
	}
	.imagebig{
		height:100%;
	}

	.canvas {
		border: 1px solid #333333;
	}

	.mask {
		width: 100vw;
		height: 100vh;
		position: fixed;
		background-color: rgba(0,0,0,.4);
		left: 0;
		top: 0;
		display: flex;
		flex-direction: column;
		justify-content: space-around;
		align-items: center;
	}
</style>

@/utils/poster.js

// 错误提示集合
const errMsgMap = {
	'arc': {
		'x': '请指定圆的起始位置 x',
		'y': '请指定圆的起始位置 y',
		'r': '请指定圆的半径 r',
		'sAngle': '请指定圆的起始弧度 sAngle',
		'eAngle': '请指定圆的终止弧度 eAngle',
	},
	'rect': {
		'x': '请指定矩形的起始位置 x',
		'y': '请指定矩形的起始位置 y',
		'w': '请指定矩形的宽度 w',
		'h': '请指定矩形的高度 h',
	},
	'round_rect': {
		'x': '请指定矩形边框的起始位置 x',
		'y': '请指定矩形边框的起始位置 y',
		'w': '请指定矩形边框的宽度 w',
		'h': '请指定矩形边框的高度 h',
	},
	'stroke_rect': {
		'x': '请指定矩形边框的起始位置 x',
		'y': '请指定矩形边框的起始位置 y',
		'w': '请指定矩形边框的宽度 w',
		'h': '请指定矩形边框的高度 h',
	},
	'stroke_round_rect': {
		'x': '请指定矩形边框的起始位置 x',
		'y': '请指定矩形边框的起始位置 y',
		'w': '请指定矩形边框的宽度 w',
		'h': '请指定矩形边框的高度 h',
	},
	'text': {
		'x': '请指定文本的起始位置 x',
		'y': '请指定文本的起始位置 y',
		'text': '请指定文本的内容 text'
	},
	'image': {
		'x': '请指定图片的起始位置 x',
		'y': '请指定图片的起始位置 y',
		'w': '请指定图片的宽度 w',
		'h': '请指定图片的高度 h',
		'url': '请指定图片的路径 url'
	},
	'line': {
		'path': '请指定线的路径 path'
	},
	'points': {
		'points': '请指定点集合 points'
	}
};
// 绘制方法集合
const DrawFuncMap = {
	drawLine(ctx, config, i, isClip) {
		// clip 图形默认不需要 fill 和 stroke 颜色。
		const defaultColor = isClip ? 'transparent' : '#333333'
		// 检验必传参数
		checkNecessaryParam(config, 'line', i, 'path');
		// 每一个path就描述了一组线的开始到结束,这一组线段不一定是连续的,根据配置属性来具体描述这个线
		// 他们的形态是一样的(线的粗细,颜色),形状不一样且不一定是连续的
		for (let j = 0; j < config.path.length; j++) {
			const path = config.path[j];
			checkNecessaryParam(path, 'points', `${i}-${j}`, 'points');
			const lineWidth = path.lineWidth || 1;
			const lineJoin = path.lineJoin || 'round';
			const lineCap = path.lineCap || 'round';
			// ctx.beginPath();
			// 设置颜色
			ctx.setStrokeStyle(path.strokeStyle || defaultColor);
			// 设置填充色
			ctx.setFillStyle(path.fillStyle || defaultColor);
			// 设置粗细
			ctx.setLineWidth(lineWidth);
			// 设置线条交点样式
			ctx.setLineJoin(lineJoin);
			// 设置线条的断点样式
			ctx.setLineCap(lineCap);
			// 遍历线的点集合,根据每个点的不同属性来绘制成线
			for (let k = 0; k < path.points.length; k++) {
				// 拿到每一个点
				const pointSet = path.points[k];
				// 如果该点是一个数组集合,则点的类型直接当 lineTo 处理
				if (Object.prototype.toString.call(pointSet) === "[object Array]") {
					if (k === 0) ctx.moveTo(...pointSet);
					else ctx.lineTo(...pointSet);
				} else {
					// 默认的第一个点一定是起始点,且点类型为 moveTo 则执行 ctx.moveTo 移动画笔
					if (k === 0 || pointSet.type === 'moveTo') {
						ctx.moveTo(...pointSet.point);
						// 点的类型为 lineTo 或 没有 type 属性也默认为 lineTo 至执行 ctx.lineTo 连线
					} else if (pointSet.type === 'lineTo' || pointSet.type === undefined) {
						ctx.lineTo(...pointSet.point);
					} else if (pointSet.type === 'bezierCurveTo') {
						const P2 = pointSet.P2 ? pointSet.P2 : pointSet.P1;
						ctx.bezierCurveTo(...pointSet.P1, ...P2, ...pointSet.point);
					} else if (pointSet.type === 'closePath') {
						ctx.closePath()
						ctx.fill();
					}
				}
			}
			// 每一组点集合(path)结束 stroke
			ctx.stroke();
		}
	},
	// 绘制图片
	drawImage(ctx, config, i) {
		checkNecessaryParam(config, 'image', i, 'x', 'y', 'w', 'h', 'url');
		ctx.drawImage(config.url, config.x, config.y, config.w, config.h);
	},
	// 绘制圆
	drawArc(ctx, config, i, isClip) {
		const defaultColor = isClip ? 'transparent' : '#333333'
		checkNecessaryParam(config, 'arc', i, 'x', 'y', 'r', 'sAngle', 'eAngle');
		// ctx.beginPath();
		ctx.arc(config.x, config.y, config.r, config.sAngle, config.eAngle);
		ctx.setFillStyle(config.fillStyle || defaultColor);
		ctx.fill();
		ctx.setLineWidth(config.lineWidth || 1);
		ctx.setStrokeStyle(config.strokeStyle || defaultColor);
		ctx.stroke();
	},
	// 绘制文字
	drawText(ctx, config, i) {
		checkNecessaryParam(config, 'text', i, 'x', 'y', 'text');
		ctx.font = config.font || '10px sans-serif';
		ctx.setFillStyle(config.color || '#333333');
		ctx.setTextAlign(config.textAlign || 'center');
		ctx.fillText(config.text, config.x, config.y);
		ctx.stroke();
	},
	// 绘制矩形
	drawRect(ctx, config, i, isClip) {
		const defaultColor = isClip ? 'transparent' : '#333333'
		checkNecessaryParam(config, 'rect', i, 'x', 'y', 'w', 'h');
		// ctx.beginPath();
		ctx.rect(config.x, config.y, config.w, config.h);
		ctx.setFillStyle(config.fillStyle || defaultColor);
		ctx.setLineWidth(config.lineWidth || 1);
		ctx.setStrokeStyle(config.strokeStyle || defaultColor);
		ctx.stroke();
		ctx.fill();
	},
	// 绘制非填充矩形
	drawStrokeRect(ctx, config, i, isClip) {
		checkNecessaryParam(config, 'stroke_rect', i, 'x', 'y', 'w', 'h');
		// ctx.beginPath();
		ctx.setStrokeStyle(config.strokeStyle || '#333333');
		ctx.setLineWidth(config.lineWidth || 1);
		ctx.strokeRect(config.x, config.y, config.w, config.h);
		ctx.stroke();
	},
	// 绘制填充圆角矩形
	drawRoundRect(ctx, config, i, isClip) {
		// 当为裁剪图形,需要把 fill 和 stroke 颜色设置为透明
		const defaultColor = isClip ? 'transparent' : '#333333'
		checkNecessaryParam(config, 'stroke_rect', i, 'x', 'y', 'w', 'h');
		ctx.setFillStyle(config.fillStyle || defaultColor);
		this.drawRoundRectPath(ctx, config.x, config.y, config.w, config.h, config.r)
		ctx.fill();
	},
	// 绘制非填充圆角矩形
	drawStrokeRoundRect(ctx, config, i, isClip) {
		checkNecessaryParam(config, 'stroke_rect', i, 'x', 'y', 'w', 'h');
		ctx.setStrokeStyle(config.strokeStyle || '#333333');
		ctx.setLineWidth(config.lineWidth || 1);
		this.drawRoundRectPath(ctx, config.x, config.y, config.w, config.h, config.r)
		ctx.stroke();
	},
	// 绘制圆角矩形路径
	drawRoundRectPath(ctx, x, y, w, h, r) {
		// ctx.beginPath();
		// ctx.strokeRect(config.x, config.y, config.w, config.h);
		//从右下角顺时针绘制,弧度从0到1/2PI  
		ctx.arc(x + w - r, y + h - r, r, 0, Math.PI / 2);
		//矩形下边线  
		ctx.lineTo(x + r, y + h);
		//左下角圆弧,弧度从1/2PI到PI  
		ctx.arc(x + r, y + h - r, r, Math.PI / 2, Math.PI);
		//矩形左边线  
		ctx.lineTo(x, y + r);
		//左上角圆弧,弧度从PI到3/2PI  
		ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2);
		//上边线  
		ctx.lineTo(x + w - r, y);
		//右上角圆弧  
		ctx.arc(x + w - r, y + r, r, Math.PI * 3 / 2, Math.PI * 2);
		//右边线  
		ctx.lineTo(x + w, y + h - r);
		ctx.closePath();
	}
}

/**
 * 检测绘制的必要属性
 * @param {Object} configObj 配置对象
 * @param {String} type 对应校验的类型
 * @param {String|Number} index 当前的错误位置 从0开始对应绘画(drawData)配置中的索引,
 * 当为 String 类型时会以'-'间隔出第几层的第几个,如1-2 表示是绘画(drawData)配置中第一个配置里的第二个子配置对象有问题,依次类推
 * @param {Array} keyArr 搜集到的所以需要进行检验的键名
 **/
function checkNecessaryParam(configObj, type, index, ...keyArr) {
	// 这里要注意由于,绘画配置有些参数可能会漏写,所以 errMsgMap[type] 作为遍历对象进行比较
	for (let prop in errMsgMap[type]) {
		if (configObj[prop] === undefined) {
			throw new Error(`${index}顺位:${errMsgMap[type][prop]}`)
		}
	}
}
/**
 * 根据不同的图形类型绘制图形
 * @param {String} type 图形的类型
 * @param {Object} ctx 当前 canvas 的上下文对象
 * @param {Object} config 图形对应的配置属性对象,
 * @param {String|Number} index 从0开始对应绘画(drawData)配置中的索引
 * @param {Boolean} isClip 是否应用于剪切
 **/
function drawFigureByType(type, ctx, config, index, isClip) {
	if (type === 'image') {
		!isClip && DrawFuncMap.drawImage(ctx, config, index);
	} else if (type === 'text') {
		!isClip && DrawFuncMap.drawText(ctx, config, index);
	} else if (type === 'arc') {
		DrawFuncMap.drawArc(ctx, config, index, isClip);
	} else if (type === 'rect') {
		DrawFuncMap.drawRect(ctx, config, index, isClip);
	} else if (type === 'stroke_rect') {
		// 这里非填充矩形也按照矩形的方式绘制裁剪区域
		isClip ? DrawFuncMap.drawRect(ctx, config, index, isClip) : DrawFuncMap.drawStrokeRect(ctx, config, index,
			isClip);
	} else if (type === 'stroke_round_rect') {
		// 这里非填充圆角矩形也按照圆角矩形的方式绘制裁剪区域
		isClip ? DrawFuncMap.drawRoundRect(ctx, config, index, isClip) : DrawFuncMap.drawStrokeRoundRect(ctx, config,
			index, isClip);
	} else if (type === 'round_rect') {
		DrawFuncMap.drawRoundRect(ctx, config, index, isClip);
	} else if (type === 'line') {
		DrawFuncMap.drawLine(ctx, config, index, isClip)
	}
}
/**
 * 不同类型的图形都可以绘制不同的剪切图形
 * @param {String} type 绘制图形的类型
 * @param {Object} ctx 当前 canvas 的上下文对象
 * @param {Object} config 绘制图形对应的配置属性对象,
 * @param {String|Number} index 绘制图形的索引 从0开始对应绘画(drawData)配置中的索引
 * @param {String} clipType 剪切图形的类型
 * @param {Object} clipConfig 剪切图形对应的配置属性对象,
 **/
function drawClipFigure(type, ctx, config, index, clipType, clipConfig) {
	ctx.beginPath();
	drawFigureByType(clipType, ctx, clipConfig, index, true)
	ctx.clip()
	ctx.beginPath();
	drawFigureByType(type, ctx, config, index, false)
}

// 获取图片信息,这里主要要获取图片缓存地址
export function loadImage(url) {
	return new Promise((resolve, reject) => {
		wx.getImageInfo({
			src: url,
			success(res) {
				resolve(res.path)
			},
			fail(err) {
				reject('海报图资源加载失败')
			}
		})
	})
}
// 解析海报对象,绘制canvas海报
export function createPoster(ctx, posterItemList) {
	return new Promise((resolve, reject) => {
		try {
			for (let i = 0; i < posterItemList.length; i++) {
				const temp = posterItemList[i];
				// 如果有 clip 属性需要先建立 clip 剪切区域
				if (temp.clip) {
					ctx.save()
					// 绘制剪切图形
					drawClipFigure(temp.type, ctx, temp.config, i, temp.clip.type, temp.clip.config)
					ctx.restore()
				} else {
					ctx.beginPath();
					//正常绘制图形
					drawFigureByType(temp.type, ctx, temp.config, i, false)
				}

			}
			ctx.draw();
			resolve({
				result: 'ok',
				msg: '绘制成功'
			})
		} catch (e) {
			console.error(e)
			reject({
				result: 'fail',
				msg: e
			})
		}
	})
}
// canvas转image图片
export function canvasToTempFilePath(canvasId, vm, delay = 50) {
	return new Promise((resolve, reject) => {
		// 这里canvas绘制完成之后想要存缓存需要一定时间,这里设置了50毫秒
		setTimeout(() => {
			uni.canvasToTempFilePath({
				canvasId: canvasId,
				x:0,
				y:0,
				width: 300,
				height: 490,
				destWidth: 300,
				destHeight:490,
				success(res) {
					if (res.errMsg && res.errMsg.indexOf('ok') != -1) resolve(res.tempFilePath);
					else reject(res)
				},
				fail(err) {
					reject(err)
				}
			}, vm);
		}, delay)
	})
}
// 保存图片到相册
export function saveImageToPhotosAlbum(imagePath) {
	return new Promise((resolve, reject) => {
		uni.saveImageToPhotosAlbum({
			filePath: imagePath,
			success(res) {
				resolve(res)
			},
			fail(err) {
				reject(err)
			}
		})
	})
}

@/utils/appletCode.js

import {base64src} from "@/utils/base64src.js";
/**
 * 微信获取小程序二维码
 * @param {String} url 微信服务器地址
 * @param {String} scene 二维码所携带的信息
 * @return {Object} 返回的二维码对象
 **/
export function getWechatCode (url,scene) {
	return new Promise((resolve,reject)=>{
		wx.request({
			url: url,
			method: 'POST',
			header: {
				'content-type': 'application/x-www-form-urlencoded'
			},
			// 二维码携带的信息
			data: {
				scene: scene
			},
			success(res) {
				//将base64图片转换成本地路径
				base64src("data:image/PNG;base64," + res.data.qcode, res => { 
					// 获取图片信息
					wx.getImageInfo({
						src: res,
						success(res) {
							resolve(res);
						},
						fail(err) {
							reject(err);
						}
					})
				})
			},
			fail(err){
				reject(err);
			}
		})
	})
}




本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/577095.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python之List列表

1. 高级数据类型 Python中的数据类型可以分为&#xff1a;数字型&#xff08;基本数据类型&#xff09;和非数字型&#xff08;高级数据类型&#xff09; 数字型包含&#xff1a;整型int、浮点型float、布尔型bool、复数型complex 非数字型包含&#xff1a;字符串str、列表l…

探索和构建 LLaMA 3 架构:深入探讨组件、编码和推理技术(四)分组多查询注意力

探索和构建 LLaMA 3 架构&#xff1a;深入探讨组件、编码和推理技术&#xff08;四&#xff09;分组多查询注意力 Grouped-query Attention&#xff0c;简称GQA 分组查询注意力&#xff08;Grouped-query Attention&#xff0c;简称GQA&#xff09;是多查询和多头注意力的插值…

【35分钟掌握金融风控策略10】风控策略部署2

目录 策略部署 决策引擎系统简介 基于决策引擎进行策略部署 策略部署结果验证 知识点补充 测试验证 回溯比对 策略部署 策略主要部署在决策引擎上进行风险决策&#xff0c;接下来分别介绍决策引擎系统&#xff0c;以及基于决策引擎进行策略部署的相关内容。 决策引擎系…

java-Spring-(MyBatis框架-xml管理)

目录 前置条件 xml与注解比较 1.1 xml定义 1.2 和SQL注解比较 建包准备 插入数据 ​编辑 更新数据 删除数据 查询数据 查看单字段查询 &#x1f3f7;&#x1f4a3;前置条件 创建一个spring boot 初始化的项目 &#x1f3f7;&#x1f4a3;xml与注解比较 1.1 xml定义 …

微信小程序简单实现购物车功能

微信小程序简单实现购物车结算和购物车列表展示功能 实现在微信小程序中对每一个购物车界面的商品订单&#xff0c;进行勾选结算和取消结算的功能&#xff0c;相关界面截图如下&#xff1a; 具体实现示例代码为&#xff1a; 1、js代码&#xff1a; Page({/*** 页面的初始数…

SpringCloudAlibaba:2.1nacos

概述 概述 简介 Nacos是阿里巴巴开源的服务注册中心以及配置中心 Nacos注册中心Eureka 服务配置Config 服务总线Bus 官网 Nacos官网 | Nacos 官方社区 | Nacos 下载 | Nacos 名字由来 Naming&#xff1a;名字 Configurations&#xff1a;配置 Service&#xff1a;服务 功能…

【基础篇】Git 基础命令与核心概念

✅作者简介&#xff1a;大家好&#xff0c;我是小杨 &#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; 一&#xff0c;Git 初识 1.1&#xff0c;问题引入 不知道你工作或学习时&#xff0c;有没有遇到…

Centos8操作系统安装mysql5.7版本以及报错解决

目录 一、卸载MySql 1.首先查看已安装的mysql 2.逐个或者执行一下命令统一卸载掉 注意&#xff1a; 3. 卸载其他相关文件 二、安装MySql 1.安装mysql的rpm源 2.安装MySql 如果遇到以下错误&#xff1a; 问题一: 解决方法&#xff1a; 问题二、 解决方法&#xff1…

国产麒麟v10系统下打包electron+vue程序,报错unknown output format set

报错如下&#xff1a; 报错第一时间想到可能是代码配置原因报错&#xff0c;查看代码似乎感觉没啥问题 又查看具体报错原因可能是因为icon的原因报错&#xff0c;后面查阅发现ico在各系统平台会不兼容&#xff0c;也就是ico是给win下使用的&#xff0c;此处改下图标格式就ok&am…

1、Qt简介

文章目录 前言一、pySide2 / pySide6 ,PyQt5 / PyQt6二、安装包1 安装pyside22 安装pyqt5三、从一个简单的例子开始三、界面动作处理---信号(signal)与槽(slot)(Qt最核心的机制)--- 绑定事件封装到类中总结前言 参考文章:Qt简介 本文开始就开始进入到qt的开发笔记书写…

【论文解读】QUEST: Query Stream for Practical Cooperative Perception

QUEST 摘要引言QUERY COOPERATION PARADIGMQUEST FRAMEWORKA. Overall ArchitectureB. Cross-agent Query Interaction 实验结论 摘要 合作感知通过提供额外的视点和扩展感知领域&#xff0c;可以有效地提高个体感知性能。现有的合作模式要么是可解释的(结果合作)&#xff0c;…

计算机视觉——OpenCV 使用分水岭算法进行图像分割

分水岭算法 分水岭算法&#xff1a;模拟地理形态的图像分割 分水岭算法通过模拟自然地形来实现图像中物体的分类。在这一过程中&#xff0c;每个像素的灰度值被视作其高度&#xff0c;灰度值较高的像素形成山脊&#xff0c;即分水岭&#xff0c;而二值化阈值则相当于水平面&am…

LabVIEW高效目标跟踪系统

LabVIEW高效目标跟踪系统 随着机器视觉技术的飞速发展&#xff0c;设计和实现高效的目标跟踪系统成为了众多领域关注的焦点。基于LabVIEW平台&#xff0c;结合NI Vision机器视觉库&#xff0c;开发了一种既高效又灵活的目标跟踪系统。通过面向对象编程方法和队列消息处理器程序…

以更多架构核心专利,推进 SDS 产业创新创造

今天是第 24 个世界知识产权日&#xff0c;今年世界知识产权日活动的主题是&#xff1a;“知识产权和可持续发展目标&#xff1a;立足创新创造&#xff0c;构建共同未来。” 这也正是 XSKY 在软件定义存储领域的目标之一。以“数据常青”为使命的 XSKY&#xff0c;始终立足于软…

济宁市中考报名照片要求及手机拍照采集证件照方法

随着中考报名季的到来&#xff0c;并且进入了中考报名演练阶段&#xff0c;济宁市的广大考生和家长都开始忙碌起来。报名过程中&#xff0c;上传一张符合要求的证件照是必不可少的环节。本文将详细介绍济宁市中考报名照片的具体要求&#xff0c;并提供一些实用的手机拍照采集证…

LeetCode in Python 74/240. Search a 2D Matrix I/II (搜索二维矩阵I/II)

搜索二维矩阵I其实可以转换为搜索一维数组&#xff0c;原因在于&#xff0c;只要先确定搜索的整数应该在哪一行&#xff0c;即可对该行进行二分查找。 搜索二维矩阵II中矩阵元素排列方式与I不同&#xff0c;但思想大致相同。 目录 LeetCode in Python 74. LeetCode in Pyth…

html表格导出为word文档,导出的部分表格内无法填写文字

导出技术实现&#xff1a;fileSaver.jshtml-docx-js 1.npm安装 npm install --save html-docx-js npm install --save file-saver 2.页面引入 import htmlDocx from html-docx-js/dist/html-docx; import saveAs from file-saver;components: {htmlDocx,saverFile, }, 3.页…

(MSFT.O)微软2024财年Q3营收619亿美元

在科技的浩渺宇宙中&#xff0c;一颗璀璨星辰再度闪耀其光芒——(MSFT.O)微软公司于2024财政年度第三季展现出惊人的财务表现&#xff0c;实现总营业收入达到令人咋舌的6190亿美元。这一辉煌成就不仅突显了微软作为全球技术领导者之一的地位&#xff0c;更引发了业界内外对这家…

Vue从0-1学会如何自定义封装v-指令

文章目录 介绍使用1. 理解指令2. 创建自定义指令3. 注册指令4. 使用自定义指令5. 自定义指令的钩子函数6. 传递参数和修饰符7. 总结 介绍 自定义封装 v-指令是 Vue.js 中非常强大的功能之一&#xff0c;它可以让我们扩展 Vue.js 的模板语法&#xff0c;为 HTML 元素添加自定义行…

在Elasticsearch 7.9.2中安装IK分词器并进行自定义词典配置

Elasticsearch是一个强大的开源搜索引擎&#xff0c;而IK分词器是针对中文文本分析的重要插件。本文将引导您完成在Elasticsearch 7.9.2版本中安装IK分词器、配置自定义词典以及验证分词效果的全过程。 步骤一&#xff1a;下载IK分词器 访问IK分词器的GitHub发布页面&#xf…
最新文章