Browse Source

canvas生成版

htc 20 hours ago
parent
commit
fe164caa3d
1 changed files with 129 additions and 76 deletions
  1. 129 76
      pagesHome/pdfZyb.vue

+ 129 - 76
pagesHome/pdfZyb.vue

@@ -462,6 +462,9 @@
 		        ]);
 		        this.pdfImages = allImageUrls;
 				
+				console.log(allImageUrls);
+				return
+				
 				this.$api.post('/core/report/reportToPdf',{
 					images:this.pdfImages,
 					reportId:this.reportId
@@ -490,10 +493,10 @@
 				const headerHeight = 26;
 				const rowHeight = 54;
 				const totalHeight = headerHeight + rowHeight * scoreData.length;
-			
+		
 				const leftColWidth = 280;
 				const rightColWidth = 308;
-			
+		
 				// --- 2. 获取 Canvas 节点 ---
 				const query = uni.createSelectorQuery().in(this);
 				query.select('#score-canvas')
@@ -510,36 +513,36 @@
 							});
 							return;
 						}
-			
+		
 						const canvasNode = res[0].node;
 						const ctx = canvasNode.getContext('2d');
 						const dpr = uni.getSystemInfoSync().pixelRatio;
-			
+		
 						// --- 3. 设置画布尺寸和缩放以适应高分屏 ---
 						canvasNode.width = canvasWidth * dpr;
 						canvasNode.height = totalHeight * dpr;
 						ctx.scale(dpr, dpr);
-			
+		
 						// --- 4. 开始绘制 ---
 						// 绘制大背景
 						ctx.fillStyle = '#FFFFFF';
 						ctx.fillRect(0, 0, canvasWidth, totalHeight);
-			
+		
 						// --- 5. 绘制表头 ---
 						// 左侧表头背景
 						ctx.fillStyle = dimensionData.thbgcolor;
 						ctx.fillRect(0, 0, leftColWidth, headerHeight);
-			
+		
 						// 右侧表头背景
 						ctx.fillRect(leftColWidth, 0, rightColWidth, headerHeight);
-			
+		
 						// 左侧表头文字
 						ctx.fillStyle = dimensionData.thtextcolor;
 						ctx.font = 'bold 9px sans-serif';
 						ctx.textBaseline = 'middle';
 						ctx.textAlign = 'left';
 						ctx.fillText('主题', 15, headerHeight / 2);
-			
+		
 						// 左侧表头分割线
 						ctx.strokeStyle = 'rgba(255,255,255,0.24)';
 						ctx.lineWidth = 1;
@@ -553,62 +556,34 @@
 						ctx.moveTo(leftColWidth, 0);
 						ctx.lineTo(leftColWidth, headerHeight);
 						ctx.stroke();
-			
+		
 						ctx.textAlign = 'center';
 						ctx.fillText('影响力分', leftColWidth - 27, headerHeight / 2);
-			
+		
 						// 右侧表头分割线
 						ctx.beginPath();
 						ctx.moveTo(leftColWidth + rightColWidth / 2, 0);
 						ctx.lineTo(leftColWidth + rightColWidth / 2, headerHeight);
 						ctx.stroke();
-			
+		
 						// 右侧表头文字
 						ctx.fillText('认同度分', leftColWidth + rightColWidth / 4, headerHeight / 2);
 						ctx.fillText('重要性分', leftColWidth + rightColWidth * 0.75, headerHeight / 2);
-			
-			
+		
+		
 						// --- 6. 循环绘制每一行 ---
 						for (let i = 0; i < scoreData.length; i++) {
 							const item = scoreData[i];
 							const yPos = headerHeight + i * rowHeight;
 							this.drawTableItem(ctx, item, yPos, leftColWidth, rightColWidth, rowHeight, dimensionData);
 						}
-			
-						// --- 7. 绘制右侧底部的刻度 (绘制在最上层) ---
-						// 先绘制白色背景遮挡网格线
-						ctx.fillStyle = '#FFFFFF';
-						const axisHeight = 20; // 底部文字区域高度
-						ctx.fillRect(leftColWidth, totalHeight - axisHeight, rightColWidth, axisHeight);
-						
-						ctx.fillStyle = '#002846';
-						ctx.font = 'bold 10px sans-serif';
-						ctx.textAlign = 'center';
-						ctx.textBaseline = 'bottom';
-			
-						const centerX = leftColWidth + rightColWidth / 2;
-						const colWidth = rightColWidth / 12;
-			
-						// 刻度 0-5
-						for (let k = 0; k <= 5; k++) {
-							let xR = centerX + k * colWidth;
-							let xL = centerX - k * colWidth;
-							let yTxt = totalHeight - 5; // 距离底部一点距离
-			
-							if (k === 0) {
-								ctx.fillText(0, centerX, yTxt);
-							} else {
-								ctx.fillText(k, xR, yTxt);
-								ctx.fillText(k, xL, yTxt);
-							}
-						}
-			
-						// --- 8. 绘制最外层边框 ---
+		
+						// --- 7. 绘制最外层边框 ---
 						ctx.strokeStyle = '#E6EAED';
 						ctx.lineWidth = 1;
 						ctx.strokeRect(0, 0, canvasWidth, totalHeight);
-			
-			
+		
+		
 						// --- 9. 生成图片 ---
 						uni.hideLoading();
 						uni.canvasToTempFilePath({
@@ -637,17 +612,14 @@
 			})
 		},
 		drawTableItem(ctx, item, y, leftW, rightW, h, dimensionData) {
-			// 1. 左侧
-			// 边框
-			ctx.strokeStyle = '#E6EAED';
+			// 1. 设置通用边框样式
+			ctx.strokeStyle = dimensionData.bordercolor;
 			ctx.lineWidth = 1;
-			// 左侧整体边框
-			ctx.strokeRect(0, y, leftW, h);
 		
-			// 竖线分割 Theme/Question 和 Score
+			// 绘制整行下边框 (贯穿左右)
 			ctx.beginPath();
-			ctx.moveTo(leftW - 54, y);
-			ctx.lineTo(leftW - 54, y + h);
+			ctx.moveTo(0, y + h);
+			ctx.lineTo(leftW + rightW, y + h);
 			ctx.stroke();
 		
 			// --- 计算高度 ---
@@ -683,24 +655,6 @@
 		
 			// 2. 右侧
 			const rightX = leftW;
-			
-			// 裁剪右侧区域,防止内容溢出
-			ctx.save();
-			ctx.beginPath();
-			ctx.rect(rightX, y, rightW, h);
-			ctx.clip();
-			
-			// 右侧不需要横向分割线,只绘制竖向网格线
-			// 绘制背景网格线 (竖向,贯穿整个高度)
-			ctx.strokeStyle = '#F0F2F8';
-			ctx.lineWidth = 1;
-			const colWidth = rightW / 12;
-			for (let k = 1; k < 12; k++) {
-				ctx.beginPath();
-				ctx.moveTo(rightX + k * colWidth, y); 
-				ctx.lineTo(rightX + k * colWidth, y + h);
-				ctx.stroke();
-			}
 		
 			// 绘制中间线
 			const centerX = rightX + rightW / 2;
@@ -720,17 +674,116 @@
 			const agreeW = (avgAgreement / maxVal) * halfW;
 			const vitalW = (avgVital / maxVal) * halfW;
 		
+			// --- 绘制背景轨道 (灰色) ---
+			// 模仿 CSS .vbt2r-tb-l-pre-bg: width: calc(100% - 16px); height: 4px;
+			// 左右各留 8px 空隙?或者就是简单的横条
+			const trackHeight = 4;
+			const trackY = y + (h - trackHeight) / 2;
+			ctx.fillStyle = '#E6EAED';
+			
+			// 左侧轨道背景 (从右向左)
+			this.drawRoundedRect(ctx, rightX + 8, trackY, halfW - 8, trackHeight, 2);
+			// 右侧轨道背景 (从左向右)
+			this.drawRoundedRect(ctx, centerX, trackY, halfW - 8, trackHeight, 2);
+		
 			// Draw Agreement Bar (grows from center to left)
 			ctx.fillStyle = '#BA8EB4';
-			ctx.fillRect(centerX - agreeW, barY, agreeW, barHeight);
+			// 左侧条,圆角在左边 [2, 0, 0, 2]
+			this.drawCustomRoundedRect(ctx, centerX - agreeW, barY, agreeW, barHeight, [2, 0, 0, 2]);
 		
 			// Draw Vital Bar (grows from center to right)
 			ctx.fillStyle = '#80C8C8';
-			ctx.fillRect(centerX, barY, vitalW, barHeight);
+			// 右侧条,圆角在右边 [0, 2, 2, 0]
+			this.drawCustomRoundedRect(ctx, centerX, barY, vitalW, barHeight, [0, 2, 2, 0]);
+			
+			// --- 绘制数字标签 ---
+			const tagW = 16;
+			const tagH = 20;
+			
+			// 左标签位置 (中心点)
+			// CSS: left: -8px (relative to bar start/end). 
+			// 实际上是在条形图的末端(远离中心的那一端)
+			// 左侧条形图末端 x = centerX - agreeW
+			// 标签中心 x = centerX - agreeW
+			this.drawValueTag(ctx, avgAgreement, centerX - agreeW, y + h/2, '#904A87', 'rgba(131,52,120,0.19)', 'rgba(118,30,106,0.1)');
+			
+			// 右标签位置
+			// 右侧条形图末端 x = centerX + vitalW
+			this.drawValueTag(ctx, avgVital, centerX + vitalW, y + h/2, '#199C9C', 'rgba(51,167,167,0.31)', 'rgba(118,30,106,0.1)');
+		},
+		// 辅助函数:绘制带样式的数值标签
+		drawValueTag(ctx, value, cx, cy, textColor, borderColor, shadowColor) {
+			const width = 16;
+			const height = 20;
+			const x = cx - width / 2;
+			const y = cy - height / 2;
+			const radius = 4;
+			
+			ctx.save();
+			// 阴影
+			ctx.shadowOffsetX = 0;
+			ctx.shadowOffsetY = 4;
+			ctx.shadowBlur = 8;
+			ctx.shadowColor = shadowColor;
+			
+			// 背景
+			ctx.fillStyle = '#FFFFFF';
+			this.drawRoundedRectPath(ctx, x, y, width, height, radius);
+			ctx.fill();
+			
+			// 边框 (取消阴影绘制边框)
+			ctx.shadowColor = 'transparent';
+			ctx.strokeStyle = borderColor;
+			ctx.lineWidth = 1;
+			ctx.stroke();
+			
+			// 文字
+			ctx.fillStyle = textColor;
+			ctx.font = 'bold 12px DIN, sans-serif';
+			ctx.textAlign = 'center';
+			ctx.textBaseline = 'middle';
+			ctx.fillText(value, cx, cy);
 			
-			// 恢复裁剪
 			ctx.restore();
 		},
+		// 辅助函数:绘制圆角矩形路径
+		drawRoundedRectPath(ctx, x, y, w, h, r) {
+			ctx.beginPath();
+			ctx.moveTo(x + r, y);
+			ctx.lineTo(x + w - r, y);
+			ctx.arcTo(x + w, y, x + w, y + h, r);
+			ctx.lineTo(x + w, y + h - r);
+			ctx.arcTo(x + w, y + h, x, y + h, r);
+			ctx.lineTo(x + r, y + h);
+			ctx.arcTo(x, y + h, x, y, r);
+			ctx.lineTo(x, y + r);
+			ctx.arcTo(x, y, x + w, y, r);
+			ctx.closePath();
+		},
+		// 辅助函数:绘制圆角矩形 (简单版)
+		drawRoundedRect(ctx, x, y, w, h, r) {
+			this.drawRoundedRectPath(ctx, x, y, w, h, r);
+			ctx.fill();
+		},
+		// 辅助函数:绘制自定义四个角圆角的矩形
+		drawCustomRoundedRect(ctx, x, y, w, h, radii) {
+			// radii: [tl, tr, br, bl]
+			if (!Array.isArray(radii)) radii = [radii, radii, radii, radii];
+			const [tl, tr, br, bl] = radii;
+			
+			ctx.beginPath();
+			ctx.moveTo(x + tl, y);
+			ctx.lineTo(x + w - tr, y);
+			ctx.arcTo(x + w, y, x + w, y + h, tr);
+			ctx.lineTo(x + w, y + h - br);
+			ctx.arcTo(x + w, y + h, x, y + h, br);
+			ctx.lineTo(x + bl, y + h);
+			ctx.arcTo(x, y + h, x, y, bl);
+			ctx.lineTo(x, y + tl);
+			ctx.arcTo(x, y, x + w, y, tl);
+			ctx.closePath();
+			ctx.fill();
+		},
 		// 辅助函数:计算自动换行文字的总高度
 		calculateWrappedTextHeight(ctx, text, lineHeight, maxWidth) {
 			if (!text) return 0;
@@ -756,7 +809,7 @@
 			let words = text.split('');
 			let line = '';
 			let currentY = y;
-
+		
 			for (let n = 0; n < words.length; n++) {
 				let testLine = line + words[n];
 				let metrics = ctx.measureText(testLine);