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>
							 |