529 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Vue
		
	
	
	
		
		
			
		
	
	
			529 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Vue
		
	
	
	
|  | <template> | |||
|  | 	<view class="canvas"> | |||
|  | 		<view ref="Content" class="theContent"> | |||
|  | 			<slot></slot> | |||
|  | 		</view> | |||
|  | 		<canvas canvas-id="myCanvas" id="myCanvas" @onReady="onCanvasReady" | |||
|  | 			:style="{ width: width + 'px', height: height + 'px' }"></canvas> | |||
|  | 	</view> | |||
|  | </template> | |||
|  | <!--  | |||
|  | list参数说明: | |||
|  |  图片渲染: | |||
|  |  type: 'image', | |||
|  |  x: X轴位置, | |||
|  |  y: Y轴位置, | |||
|  |  path: 图片路径, | |||
|  |  width: 图片宽度, | |||
|  |  height: 图片高度, | |||
|  |  rotate: 旋转角度 | |||
|  |  mode:'center' 居中自适应 | |||
|  |  shape: 形状,默认无,可选值:circle 圆形 如果是number,则生成圆角矩形 | |||
|  |  area: {x,y,width,height}  // 绘制范围,超出该范围会被剪裁掉 该属性与shape暂时无法同时使用,area存在时,shape失效
 | |||
|  | 矩形渲染: | |||
|  |  type: 'square', | |||
|  |  x: X轴位置, | |||
|  |  y: Y轴位置, | |||
|  |  width: 图片宽度, | |||
|  |  height: 图片高度, | |||
|  |  rotate: 旋转角度 | |||
|  |  shape: 形状,默认无,可选值:circle 圆形 如果是number,则生成圆角矩形 | |||
|  |  fillStyle:填充的背景样式 | |||
|  |  area: {x,y,width,height}  // 绘制范围,超出该范围会被剪裁掉 该属性与shape暂时无法同时使用,area存在时,shape失效
 | |||
|  |  文字渲染: | |||
|  |  type: 'text', | |||
|  |  x: X轴位置, | |||
|  |  y: Y轴位置, | |||
|  |  text: 文本内容, | |||
|  |  size: 字体大小, | |||
|  |  textBaseline: 基线 默认top  可选值:'top'、'bottom'、'middle'、'normal' | |||
|  |  color: 颜色 | |||
|  |  多行文字渲染: | |||
|  |  type: 'textarea', | |||
|  |  x: X轴位置, | |||
|  |  y: Y轴位置, | |||
|  |  width:换行的宽度 | |||
|  |  height: 高度,溢出会展示“...” | |||
|  |  lineSpace: 行间距 | |||
|  |  text: 文本内容, | |||
|  |  size: 字体大小, | |||
|  |  textBaseline: 基线 默认top  可选值:'top'、'bottom'、'middle'、'normal' | |||
|  |  color: 颜色 | |||
|  |  --> | |||
|  | <script> | |||
|  | 	let timer = null; | |||
|  | 	export default { | |||
|  | 		name: "Poster", | |||
|  | 		props: { | |||
|  | 			// 绘制队列
 | |||
|  | 			list: { | |||
|  | 				type: Array, | |||
|  | 				required: true, | |||
|  | 			}, | |||
|  | 			// 海报宽度(默认设备宽度放大两倍) 建议都放大两倍
 | |||
|  | 			width: { | |||
|  | 				type: [Number, String], | |||
|  | 				default: uni.getSystemInfoSync().windowWidth * 2, | |||
|  | 			}, | |||
|  | 			// 海报高度(默认设备高度放大两倍)
 | |||
|  | 			height: { | |||
|  | 				type: [Number, String], | |||
|  | 				default: uni.getSystemInfoSync().windowHeight * 2, | |||
|  | 			}, | |||
|  | 			//背景颜色
 | |||
|  | 			backgroundColor: { | |||
|  | 				type: String, | |||
|  | 				default: "rgba(0,0,0,0)", | |||
|  | 			}, | |||
|  | 		}, | |||
|  | 		data() { | |||
|  | 			return { | |||
|  | 				posterUrl: "", | |||
|  | 				renderList: [], | |||
|  | 				ctx: null, //画布上下文
 | |||
|  | 				counter: -1, //计数器
 | |||
|  | 				drawPathQueue: [], //画图路径队列
 | |||
|  | 			}; | |||
|  | 		}, | |||
|  | 		watch: { | |||
|  | 			drawPathQueue(newVal, oldVal) { | |||
|  | 				// 绘制单行文字
 | |||
|  | 				const fillText = (textOptions) => { | |||
|  | 					this.ctx.setFillStyle(textOptions.color); | |||
|  | 					this.ctx.setFontSize(textOptions.size); | |||
|  | 					this.ctx.setTextBaseline(textOptions.textBaseline || "top"); | |||
|  | 					this.ctx.fillText(textOptions.text, textOptions.x, textOptions.y); | |||
|  | 				}; | |||
|  | 				// 绘制段落
 | |||
|  | 				const fillParagraph = (textOptions) => { | |||
|  | 					this.ctx.setFontSize(textOptions.size); | |||
|  | 					let tempOptions = JSON.parse(JSON.stringify(textOptions)); | |||
|  | 					// 如果没有指定行间距则设置默认值
 | |||
|  | 					tempOptions.lineSpace = tempOptions.lineSpace ? tempOptions.lineSpace : 10; | |||
|  | 					// 获取字符串
 | |||
|  | 					let str = textOptions.text; | |||
|  | 					// 计算指定高度可以输出的最大行数
 | |||
|  | 					let lineCount = Math.floor((tempOptions.height + tempOptions.lineSpace) / (tempOptions.size + | |||
|  | 						tempOptions.lineSpace)); | |||
|  | 					// 初始化单行宽度
 | |||
|  | 					let lineWidth = 0; | |||
|  | 					let lastSubStrIndex = 0; //每次开始截取的字符串的索引
 | |||
|  | 
 | |||
|  | 					// 构建一个打印数组
 | |||
|  | 					let strArr = str.split(""); | |||
|  | 					let drawArr = []; | |||
|  | 					let text = ""; | |||
|  | 					while (strArr.length) { | |||
|  | 						let word = strArr.shift(); | |||
|  | 						text += word; | |||
|  | 						let textWidth = this.ctx.measureText(text).width; | |||
|  | 						if (textWidth > textOptions.width) { | |||
|  | 							// 因为超出宽度 所以要截取掉最后一个字符
 | |||
|  | 							text = text.substr(0, text.length - 1); | |||
|  | 							drawArr.push(text); | |||
|  | 							text = ""; | |||
|  | 							// 最后一个字还给strArr
 | |||
|  | 							strArr.unshift(word); | |||
|  | 						} else if (!strArr.length) { | |||
|  | 							drawArr.push(text); | |||
|  | 						} | |||
|  | 					} | |||
|  | 
 | |||
|  | 					if (drawArr.length > lineCount) { | |||
|  | 						// 超出最大行数
 | |||
|  | 						drawArr.length = lineCount; | |||
|  | 						let pointWidth = this.ctx.measureText("...").width; | |||
|  | 						let wordWidth = 0; | |||
|  | 						let wordArr = drawArr[drawArr.length - 1].split(""); | |||
|  | 						let words = ""; | |||
|  | 						while (pointWidth > wordWidth) { | |||
|  | 							words += wordArr.pop(); | |||
|  | 							wordWidth = this.ctx.measureText(words).width; | |||
|  | 						} | |||
|  | 						drawArr[drawArr.length - 1] = wordArr.join("") + "..."; | |||
|  | 					} | |||
|  | 					// 打印
 | |||
|  | 					for (let i = 0; i < drawArr.length; i++) { | |||
|  | 						let _h = i > 0 ? tempOptions.size + tempOptions.lineSpace : 0; | |||
|  | 						tempOptions.y = tempOptions.y + _h; // y的位置
 | |||
|  | 						tempOptions.text = drawArr[i]; // 绘制的文本
 | |||
|  | 						fillText(tempOptions); | |||
|  | 					} | |||
|  | 				}; | |||
|  | 				const roundRect = (x, y, w, h, r) => { | |||
|  | 					// 开始绘制
 | |||
|  | 					this.ctx.beginPath(); | |||
|  | 					// 因为边缘描边存在锯齿,最好指定使用 transparent 填充
 | |||
|  | 					// 这里是使用 fill 还是 stroke都可以,二选一即可
 | |||
|  | 					this.ctx.setFillStyle("transparent"); | |||
|  | 					// ctx.setStrokeStyle('transparent')
 | |||
|  | 					// 左上角
 | |||
|  | 					this.ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5); | |||
|  | 					// border-top
 | |||
|  | 					this.ctx.moveTo(x + r, y); | |||
|  | 					this.ctx.lineTo(x + w - r, y); | |||
|  | 					this.ctx.lineTo(x + w, y + r); | |||
|  | 					// 右上角
 | |||
|  | 					this.ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2); | |||
|  | 
 | |||
|  | 					// border-right
 | |||
|  | 					this.ctx.lineTo(x + w, y + h - r); | |||
|  | 					this.ctx.lineTo(x + w - r, y + h); | |||
|  | 					// 右下角
 | |||
|  | 					this.ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5); | |||
|  | 
 | |||
|  | 					// border-bottom
 | |||
|  | 					this.ctx.lineTo(x + r, y + h); | |||
|  | 					this.ctx.lineTo(x, y + h - r); | |||
|  | 					// 左下角
 | |||
|  | 					this.ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI); | |||
|  | 					// border-left
 | |||
|  | 					this.ctx.lineTo(x, y + r); | |||
|  | 					this.ctx.lineTo(x + r, y); | |||
|  | 					// 这里是使用 fill 还是 stroke都可以,二选一即可,但是需要与上面对应
 | |||
|  | 					this.ctx.fill(); | |||
|  | 					// ctx.stroke()
 | |||
|  | 					this.ctx.closePath(); | |||
|  | 					// 剪切
 | |||
|  | 					this.ctx.clip(); | |||
|  | 				}; | |||
|  | 				// 图片自适应
 | |||
|  | 				const adaptiveImg = (img, x, y, mode) => { | |||
|  | 					const { | |||
|  | 						imgW: w, | |||
|  | 						imgH: h, | |||
|  | 						width: dWidth, | |||
|  | 						height: dHeight | |||
|  | 					} = img; | |||
|  | 					let dw = dWidth / w; //canvas与图片的宽比
 | |||
|  | 					let dh = dHeight / h; //canvas与图片的高比
 | |||
|  | 					// 裁剪图片中间部分
 | |||
|  | 					if ((w > dWidth && h > dHeight) || (w < dWidth && h < dHeight)) { | |||
|  | 						if (dw > dh) { | |||
|  | 							this.ctx.drawImage(img.path, 0, (h - dHeight / dw) / 2, w, dHeight / dw, x, y, dWidth, | |||
|  | 								dHeight); | |||
|  | 						} else { | |||
|  | 							this.ctx.drawImage(img.path, (w - dWidth / dh) / 2, 0, dWidth / dh, h, x, y, dWidth, | |||
|  | 								dHeight); | |||
|  | 						} | |||
|  | 					} else { | |||
|  | 						// 拉伸图片
 | |||
|  | 						if (w < dWidth) { | |||
|  | 							this.ctx.drawImage(img.path, 0, (h - dHeight / dw) / 2, w, dHeight / dw, x, y, dWidth, | |||
|  | 								dHeight); | |||
|  | 						} else { | |||
|  | 							this.ctx.drawImage(img.path, (w - dWidth / dh) / 2, 0, dWidth / dh, h, x, y, dWidth, | |||
|  | 								dHeight); | |||
|  | 						} | |||
|  | 					} | |||
|  | 					// 裁剪图片中间部分
 | |||
|  | 					// this.ctx.drawImage(img.path, sx, sy, sWidth, sHeight, x, y, dWidth, dHeight);
 | |||
|  | 					// this.ctx.drawImage(img.path, x, y, temp.dWidth, temp.dHeight);
 | |||
|  | 				}; | |||
|  | 				// 绘制背景
 | |||
|  | 				this.ctx.setFillStyle(this.backgroundColor); | |||
|  | 				this.ctx.fillRect(0, 0, this.width, this.height); | |||
|  | 				/* 所有元素入队则开始绘制 */ | |||
|  | 				if (newVal.length === this.renderList.length) { | |||
|  | 					if (newVal.length == 0) { | |||
|  | 						this.$emit("on-error", { | |||
|  | 							msg: "数据为空", | |||
|  | 						}); | |||
|  | 						return; | |||
|  | 					} | |||
|  | 					try { | |||
|  | 						// console.log('生成的队列:' + JSON.stringify(newVal));
 | |||
|  | 						console.log("开始绘制..."); | |||
|  | 						for (let i = 0; i < this.drawPathQueue.length; i++) { | |||
|  | 							for (let j = 0; j < this.drawPathQueue.length; j++) { | |||
|  | 								let current = this.drawPathQueue[j]; | |||
|  | 								/* 按顺序绘制 */ | |||
|  | 								if (current.index === i) { | |||
|  | 									/* 文本绘制 */ | |||
|  | 									if (current.type === "text") { | |||
|  | 										console.log("开始绘制...text"); | |||
|  | 										fillText(current); | |||
|  | 										this.counter--; | |||
|  | 									} | |||
|  | 									/* 多行文本 */ | |||
|  | 									if (current.type === "textarea") { | |||
|  | 										console.log("开始绘制...textarea"); | |||
|  | 										fillParagraph(current); | |||
|  | 										this.counter--; | |||
|  | 									} | |||
|  | 									/* 多行文本 */ | |||
|  | 									if (current.type === "square") { | |||
|  | 										console.log("开始绘制...square"); | |||
|  | 										this.ctx.save(); // 保存上下文,绘制后恢复
 | |||
|  | 										this.ctx.beginPath(); //开始绘制
 | |||
|  | 										//画好了圆 剪切  原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内 这也是我们要save上下文的原因
 | |||
|  | 										// 设置旋转中心
 | |||
|  | 										let offsetX = current.x + Number(current.width) / 2; | |||
|  | 										let offsetY = current.y + Number(current.height) / 2; | |||
|  | 										this.ctx.translate(offsetX, offsetY); | |||
|  | 										let degrees = current.rotate ? Number(current.rotate) % 360 : 0; | |||
|  | 										this.ctx.rotate((degrees * Math.PI) / 180); | |||
|  | 										this.ctx.fillStyle = current.fillStyle; | |||
|  | 										this.ctx.fillRect(current.x - offsetX, current.y - offsetY, current.width, current | |||
|  | 											.height); | |||
|  | 										this.ctx.closePath(); | |||
|  | 										this.ctx.restore(); // 恢复之前保存的上下文
 | |||
|  | 										this.counter--; | |||
|  | 									} | |||
|  | 									/* 图片绘制 */ | |||
|  | 									if (current.type === "image") { | |||
|  | 										console.log("开始绘制...image"); | |||
|  | 										if (current.area) { | |||
|  | 											// 绘制绘图区域
 | |||
|  | 											this.ctx.save(); | |||
|  | 											this.ctx.beginPath(); //开始绘制
 | |||
|  | 											this.ctx.rect(current.area.x, current.area.y, current.area.width, current.area | |||
|  | 												.height); | |||
|  | 											this.ctx.clip(); | |||
|  | 											// 设置旋转中心
 | |||
|  | 											let offsetX = current.x + Number(current.width) / 2; | |||
|  | 											let offsetY = current.y + Number(current.height) / 2; | |||
|  | 											this.ctx.translate(offsetX, offsetY); | |||
|  | 											let degrees = current.rotate ? Number(current.rotate) % 360 : 0; | |||
|  | 											this.ctx.rotate((degrees * Math.PI) / 180); | |||
|  | 											this.ctx.drawImage(current.path, current.x - offsetX, current.y - offsetY, | |||
|  | 												current.width, current.height); | |||
|  | 											this.ctx.closePath(); | |||
|  | 											this.ctx.restore(); // 恢复之前保存的上下文
 | |||
|  | 										} else if (current.shape == "circle") { | |||
|  | 											this.ctx.save(); // 保存上下文,绘制后恢复
 | |||
|  | 											this.ctx.beginPath(); //开始绘制
 | |||
|  | 											//先画个圆   前两个参数确定了圆心 (x,y) 坐标  第三个参数是圆的半径  四参数是绘图方向  默认是false,即顺时针
 | |||
|  | 											let width = current.width / 2 + current.x; | |||
|  | 											let height = current.height / 2 + current.y; | |||
|  | 											let r = current.width / 2; | |||
|  | 											this.ctx.arc(width, height, r, 0, Math.PI * 2); | |||
|  | 											this.ctx.lineTo(current.x, current.y); | |||
|  | 											this.ctx.fill(); | |||
|  | 											//画好了圆 剪切  原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内 这也是我们要save上下文的原因
 | |||
|  | 											this.ctx.clip(); | |||
|  | 											// 设置旋转中心
 | |||
|  | 											let offsetX = current.x + Number(current.width) / 2; | |||
|  | 											let offsetY = current.y + Number(current.height) / 2; | |||
|  | 											this.ctx.translate(offsetX, offsetY); | |||
|  | 											let degrees = current.rotate ? Number(current.rotate) % 360 : 0; | |||
|  | 											this.ctx.rotate((degrees * Math.PI) / 180); | |||
|  | 											current.mode ? | |||
|  | 												adaptiveImg(current, current.x - offsetX, current.y - offsetY, current | |||
|  | 													.mode) : | |||
|  | 												this.ctx.drawImage(current.path, current.x - offsetX, current.y - offsetY, | |||
|  | 													current.width, current.height); | |||
|  | 											this.ctx.closePath(); | |||
|  | 											this.ctx.restore(); // 恢复之前保存的上下文
 | |||
|  | 										} else if (typeof current.shape == "number") { | |||
|  | 											this.ctx.save(); // 保存上下文,绘制后恢复
 | |||
|  | 											this.ctx.beginPath(); //开始绘制
 | |||
|  | 											roundRect(current.x, current.y, current.width, current.height, current.shape); | |||
|  | 											//画好了圆 剪切 原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内 这也是我们要save上下文的原因
 | |||
|  | 											// 设置旋转中心
 | |||
|  | 											let offsetX = current.x + Number(current.width) / 2; | |||
|  | 											let offsetY = current.y + Number(current.height) / 2; | |||
|  | 											this.ctx.translate(offsetX, offsetY); | |||
|  | 											let degrees = current.rotate ? Number(current.rotate) % 360 : 0; | |||
|  | 											this.ctx.rotate((degrees * Math.PI) / 180); | |||
|  | 											current.mode ? | |||
|  | 												adaptiveImg(current, current.x - offsetX, current.y - offsetY, current | |||
|  | 													.mode) : | |||
|  | 												this.ctx.drawImage(current.path, current.x - offsetX, current.y - offsetY, | |||
|  | 													current.width, current.height); | |||
|  | 											this.ctx.closePath(); | |||
|  | 											this.ctx.restore(); // 恢复之前保存的上下文
 | |||
|  | 										} else { | |||
|  | 											this.ctx.drawImage(current.path, current.x, current.y, current.width, current | |||
|  | 												.height); | |||
|  | 										} | |||
|  | 										this.counter--; | |||
|  | 									} | |||
|  | 								} | |||
|  | 							} | |||
|  | 						} | |||
|  | 					} catch (err) { | |||
|  | 						console.log(err); | |||
|  | 						this.$emit("on-error", err); | |||
|  | 					} | |||
|  | 				} | |||
|  | 			}, | |||
|  | 			counter(newVal, oldVal) { | |||
|  | 				if (newVal === 0) { | |||
|  | 					this.ctx.draw(false, (draw) => { | |||
|  | 						uni.canvasToTempFilePath({ | |||
|  | 								canvasId: "myCanvas", | |||
|  | 								success: (res) => { | |||
|  | 									// 在H5平台下,tempFilePath 为 base64
 | |||
|  | 									console.log("图片已保存至本地:", res.tempFilePath); | |||
|  | 									this.posterUrl = res.tempFilePath; | |||
|  | 									this.$emit("on-success", res.tempFilePath); | |||
|  | 								}, | |||
|  | 								fail: (error) => { | |||
|  | 									this.$emit("on-error", error); | |||
|  | 								}, | |||
|  | 							}, | |||
|  | 							this | |||
|  | 						); | |||
|  | 					}); | |||
|  | 				} | |||
|  | 			}, | |||
|  | 		}, | |||
|  | 		mounted() { | |||
|  | 			// this.generateImg();
 | |||
|  | 			// console.log('mounted');
 | |||
|  | 		}, | |||
|  | 		methods: { | |||
|  | 			onCanvasReady() { | |||
|  | 				// #ifdef MP-ALIPAY
 | |||
|  | 				const query = my.createSelectorQuery(); | |||
|  | 				query | |||
|  | 					.select("#myCanvas") | |||
|  | 					.node() | |||
|  | 					.exec((res) => { | |||
|  | 						const canvas = res[0].node; | |||
|  | 						const ctx = canvas.getContext("2d"); | |||
|  | 					}); | |||
|  | 				// #endif
 | |||
|  | 			}, | |||
|  | 			/** | |||
|  | 			 * @param {*} elClass 元素名称 | |||
|  | 			 * @param {*} slot 是否采用slot方式 | |||
|  | 			 * @param {*} startX  X偏移 | |||
|  | 			 * @param {*} startY Y偏移 | |||
|  | 			 * @return {*} | |||
|  | 			 */ | |||
|  | 			createForElRect(elClass = "Poster", slot = true, startX = 0, startY = 0) { | |||
|  | 				uni | |||
|  | 					.createSelectorQuery() | |||
|  | 					.selectAll("." + elClass) | |||
|  | 					.fields({ | |||
|  | 							dataset: true, | |||
|  | 							size: true, | |||
|  | 							rect: true, | |||
|  | 							value: true, | |||
|  | 							scrollOffset: true, | |||
|  | 							properties: ["src", "mode"], | |||
|  | 							computedStyle: ["margin", "padding", "backgroundColor", "fontSize", "color", "fontWeight", | |||
|  | 								"borderRadius" | |||
|  | 							], | |||
|  | 							context: true, | |||
|  | 						}, | |||
|  | 						(res) => { | |||
|  | 							let list = []; | |||
|  | 							const sys = uni.getSystemInfoSync(); | |||
|  | 							let multiple = sys.windowWidth / this.width; | |||
|  | 							res.forEach((val, index) => { | |||
|  | 								let src = val.src || val.dataset.enode || ""; | |||
|  | 								let type = val.src ? "image" : val.dataset.etype || "text"; | |||
|  | 								let text = val.dataset.enode || ""; | |||
|  | 								let size = val.fontSize.replace("px", "") || 13; | |||
|  | 								let shape = val.borderRadius == "50%" ? "circle" : val.borderRadius.replace("px", | |||
|  | 									"") * 2; | |||
|  | 								let x = (startX + val.left - (slot ? sys.screenWidth : 0)) / multiple; | |||
|  | 								let y = (startY + val.top) / multiple; | |||
|  | 								y = (startY + val.top - (slot ? 50 : 0)) / multiple; | |||
|  | 								// #ifdef H5
 | |||
|  | 								y = (startY + val.top) / multiple; | |||
|  | 								// #endif
 | |||
|  | 								list.push({ | |||
|  | 									type: type, | |||
|  | 									shape, | |||
|  | 									text, | |||
|  | 									mode: "center", | |||
|  | 									x, | |||
|  | 									y, | |||
|  | 									path: src, | |||
|  | 									width: val.width / multiple, | |||
|  | 									height: val.height / multiple, | |||
|  | 									size: size / multiple, | |||
|  | 									color: val.color, | |||
|  | 								}); | |||
|  | 							}); | |||
|  | 							let canvas = uni.createCanvasContext("myCanvas", this); | |||
|  | 							this.ctx = canvas; | |||
|  | 							this.renderList = [...this.list, ...list]; | |||
|  | 							this.generateImg(); | |||
|  | 						} | |||
|  | 					) | |||
|  | 					.exec(); | |||
|  | 			}, | |||
|  | 			create() { | |||
|  | 				let canvas = uni.createCanvasContext("myCanvas", this); | |||
|  | 				this.ctx = canvas; | |||
|  | 				this.renderList = this.list; | |||
|  | 				this.generateImg(); | |||
|  | 			}, | |||
|  | 			async generateImg() { | |||
|  | 				console.log("generateimg"); | |||
|  | 				this.counter = this.renderList.length; | |||
|  | 				this.drawPathQueue = []; | |||
|  | 				const getImgInfo = async (current) => { | |||
|  | 					// console.log("current", current);
 | |||
|  | 					return new Promise((resolve, reject) => { | |||
|  | 						uni.getImageInfo ? | |||
|  | 							uni.getImageInfo({ | |||
|  | 								src: current.path, | |||
|  | 								success: (res) => { | |||
|  | 									current.path = res.path; | |||
|  | 									current.imgW = res.width; | |||
|  | 									current.imgH = res.height; | |||
|  | 									// this.drawPathQueue.push(current);
 | |||
|  | 									resolve(current); | |||
|  | 								}, | |||
|  | 							}) : | |||
|  | 							resolve(current); | |||
|  | 					}); | |||
|  | 				}; | |||
|  | 				const delayedLog = async (v, i) => { | |||
|  | 					let current = this.renderList[i]; | |||
|  | 					current.index = i; | |||
|  | 					/* 如果是文本直接放入队列 */ | |||
|  | 					if (current.type === "text" || current.type === "textarea" || current.type === "square") { | |||
|  | 						// this.drawPathQueue.push(current);
 | |||
|  | 						return current; | |||
|  | 					} else { | |||
|  | 						return await getImgInfo(current); | |||
|  | 						/* 图片需获取本地缓存path放入队列 */ | |||
|  | 					} | |||
|  | 				}; | |||
|  | 				const processArray = async (array) => { | |||
|  | 					// map array to promises
 | |||
|  | 					const promises = array.map((v, i) => delayedLog(v, i)); | |||
|  | 					// wait until all promises are resolved
 | |||
|  | 					const allData = await Promise.all(promises); | |||
|  | 					console.log("Done!", allData); | |||
|  | 					return allData; | |||
|  | 				}; | |||
|  | 				this.drawPathQueue = await processArray(this.renderList); | |||
|  | 			}, | |||
|  | 			saveImg() { | |||
|  | 				uni.canvasToTempFilePath({ | |||
|  | 						canvasId: "myCanvas", | |||
|  | 						success: (res) => { | |||
|  | 							// 在H5平台下,tempFilePath 为 base64
 | |||
|  | 							uni.saveImageToPhotosAlbum({ | |||
|  | 								filePath: res.tempFilePath, | |||
|  | 								success: () => { | |||
|  | 									console.log("save success"); | |||
|  | 								}, | |||
|  | 							}); | |||
|  | 						}, | |||
|  | 					}, | |||
|  | 					this | |||
|  | 				); | |||
|  | 			}, | |||
|  | 		}, | |||
|  | 	}; | |||
|  | </script> | |||
|  | 
 | |||
|  | <style lang="scss" scoped> | |||
|  | 	.canvas { | |||
|  | 		position: fixed; | |||
|  | 		top: 100rpx; | |||
|  | 		left: 750rpx; | |||
|  | 		width: 100vw; | |||
|  | 	} | |||
|  | 
 | |||
|  | 	.theContent {} | |||
|  | </style> |