Explorar o código

可以生成图片了,但样式需要微调,先保存一版最为基础版,然后进行样式调整

htc hai 4 días
pai
achega
124769b3b3
Modificáronse 1 ficheiros con 231 adicións e 6 borrados
  1. 231 6
      pagesHome/pdf.vue

+ 231 - 6
pagesHome/pdf.vue

@@ -69,6 +69,7 @@
 				</view>
 			</view>
 			<!-- 多维度 -->
+			<canvas type="2d" canvas-id="score-canvas" id="score-canvas" class="offscreen-canvas"></canvas>
 			<template v-if="reportData&&reportData.dimensionAnalysis&&reportData.dimensionAnalysis.length">
 				<div class="cd_box adffc" style="border: none;" v-for="(item,index) in reportData.dimensionAnalysis" :key="index">
 					<div class="v2-top adfacjb" :style="{'background-image':'url('+'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/'+typeDict[item.title]+'_title_bg.png)'}">
@@ -81,17 +82,21 @@
 						<div class="v2-p2">{{ item.desc }}</div>
 						<div class="v2-p2" style="margin-top: 16px;">评分总体分布</div>
 						<div class="vb-table" :style="{'border':'1px solid '+item.bcolor}">
-							<div class="vbt-pre adfac" v-for="i in 5" :key="item">
-								<div class="vbtp-left adfacjc" :class="{'black':item.title==='目的与动机'}" :style="{'background':item.titlecolor}">{{ '宗旨共融,同心共识' }}</div>
+							<!-- 将 v-for="i in 5" 修改为遍历真实的 scoreData -->
+							<div class="vbt-pre adfac" v-for="(scoreItem, scoreIndex) in scoreData" :key="scoreIndex">
+								<div class="vbtp-left adfacjc" :class="{'black':item.title==='目的与动机'}" :style="{'background':item.titlecolor}">{{ scoreItem.title }}</div>
 								<div class="vbtp-right" :style="{'border':'1px solid '+item.bcolor}">
-									<div class="vbtpr-title">{{ '团队成员能够共同阐述其共享目的,并且在团队使命上保持高度一致。' }}</div>
+									<div class="vbtpr-title">{{ scoreItem.desc }}</div>
 									<div class="vbtpr-jd">
-										<div class="vj_num" :style="{'width':(4/5*100)+'%','background':item.pfztfb}"></div>   
-										<div class="vj-val" :style="{'border':'1px solid '+item.bcolor,'left':'calc('+(4/5*100)+'% - 12px)','box-shadow':'0px 2px 6px 0px '+item.bcolor}">{{ 4 }}</div>
+										<!-- 分数和宽度动态化 -->
+										<div class="vj_num" :style="{'width':(scoreItem.score/5*100)+'%','background':item.pfztfb}"></div>   
+										<div class="vj-val" :style="{'border':'1px solid '+item.bcolor,'left':'calc('+(scoreItem.score/5*100)+'% - 12px)','box-shadow':'0px 2px 6px 0px '+item.bcolor}">{{ scoreItem.score }}</div>
 									</div>
 								</div>
 							</div>
 						</div>
+						<!-- (可选)添加一个生成图片的按钮 -->
+						<button @click="generateScoreImage(item, 0)" style="margin-top: 20px;">生成“目的与动机”评分图片</button>
 					</div>
 					<div class="v2-data" :style="{'border':'1px solid '+item.bcolor}">
 						<div class="vd-title vt3" :class="{'black':item.title==='目的与动机'}" :style="{'background-image':'url(https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/'+typeDict[item.title]+'_title_bg1.png)'}">诊断结果</div>
@@ -130,6 +135,38 @@
 		components:{ lEchart },
         data() {
         return {
+			scoreData: [
+				{
+					title: "宗旨共融,同心共识",
+					desc: "团队成员能够共同阐述其共享目的,并且在团队使命上保持高度一致。",
+					score: 4
+				},
+				{
+					title: "价值引领,行践合一",
+					desc: "团队经常根据其愿景、使命、目的和价值观来评估它们所做的事情及自身的行为。",
+					score: 3
+				},
+				{
+					title: "使命驱动,热忱贡献",
+					desc: "团队对它们为实现宗旨和愿景所面临的挑战与目标充满热情,并相信它们的工作为世界带来积极的贡献。",
+					score: 4.8
+				},
+				{
+					title: "团队优先,人尽其才",
+					desc: "团队成员(包括领导者)将团队优先事项置于个人优先事务之上,并在分配工作任务时充分发挥每个人的优势。",
+					score: 2
+				},
+				{
+					title: "审时度势,与时俱进",
+					desc: "团队定期(每隔数月)审视目标与优先事项,以确保它们能够适应外部环境的变化。",
+					score: 3
+				},
+				{
+					title: "快乐工作,具成就感",
+					desc: "团队成员(包括领导者)对所做的工作以及与同事共事感到快乐并从中获得成就感。",
+					score: 4
+				}
+			],
             reportData: null,
 			isChartReady: false,
 			scale:1,
@@ -214,13 +251,195 @@
             ]
         };
 		
-		
 		this.calculateScaleAndPosition();
 		uni.onWindowResize(() => {
 			this.calculateScaleAndPosition();
 		});
     },
     methods: {
+		// 绘制主函数
+		async generateScoreImage(dimensionData, index) {
+		    console.log('开始生成图片...');
+		    uni.showLoading({ title: '图片生成中...' });
+		
+		    // --- 1. 定义尺寸和样式 ---
+		    const canvasWidth = 588; // .v2-box 的宽度大约是 630 - 20*2(padding) - 1*2(border) = 588
+		    const itemHeight = 70; // 每个评估项的高度
+		    const totalHeight = itemHeight * this.scoreData.length;
+		    // 调整为整数,避免边框模糊
+		    const canvasHeight = totalHeight;
+		
+		    // --- 2. 获取 Canvas 节点 ---
+		    // 使用 ID 选择器更精确
+		    const query = uni.createSelectorQuery().in(this);
+		    query.select('#score-canvas')
+		        .fields({ node: true, size: true })
+		        .exec(async (res) => {
+		            // 【重要】增加健壮性检查
+		            if (!res || !res[0] || !res[0].node) {
+		                uni.hideLoading();
+		                console.error('获取 Canvas 节点失败,请检查 canvas-id 和 type="2d" 是否正确设置。');
+		                uni.showToast({ title: '组件初始化失败', icon: 'none' });
+		                return;
+		            }
+		
+		            const canvasNode = res[0].node;
+		            const context = canvasNode.getContext('2d');
+		            const dpr = uni.getSystemInfoSync().pixelRatio;
+		
+		            // --- 3. 设置画布尺寸和缩放以适应高分屏 ---
+		            canvasNode.width = canvasWidth * dpr;
+		            canvasNode.height = canvasHeight * dpr;
+		            context.scale(dpr, dpr);
+		
+		            // --- 4. 开始绘制 ---
+		            // 绘制大背景
+		            context.fillStyle = '#FFFFFF';
+		            context.fillRect(0, 0, canvasWidth, canvasHeight);
+		
+		            // --- 5. 循环绘制每一项 ---
+		            for (let i = 0; i < this.scoreData.length; i++) {
+		                const item = this.scoreData[i];
+		                const yPos = i * itemHeight;
+		                // 注意:这里不再需要 await,因为 canvas 2d 绘图是同步的
+		                this.drawScoreItem(context, item, yPos, canvasWidth, itemHeight, dimensionData);
+		            }
+		            
+		            // 【补充】绘制最外层的上下边框,避免被循环内的矩形覆盖
+		            context.strokeStyle = dimensionData.bcolor;
+		            context.lineWidth = 1;
+		            context.strokeRect(0, 0, canvasWidth, canvasHeight);
+		
+		
+		            // --- 6. 生成图片 ---
+		            uni.hideLoading();
+		            uni.canvasToTempFilePath({
+		                canvas: canvasNode,
+		                x: 0,
+		                y: 0,
+		                width: canvasWidth,
+		                height: canvasHeight,
+		                destWidth: canvasWidth * dpr,
+		                destHeight: canvasHeight * dpr,
+		                success: async (result) => {
+		                    console.log('图片生成成功!', result.tempFilePath);
+		                    const fileurl = await this.uploadFilePromise(result.tempFilePath);
+		                    console.log(fileurl, 'fileurl');
+		                },
+		                fail: (err) => {
+		                    console.error('图片生成失败', err);
+		                    uni.showToast({ title: '图片生成失败', icon: 'none' });
+		                }
+		            }, this);
+		    });
+		},
+		// 辅助函数:绘制单个评估项
+		drawScoreItem(ctx, scoreItem, y, width, height, dimensionData) {
+			const leftBoxWidth = 110;
+			const padding = 10;
+			const rightBoxX = leftBoxWidth;
+			const rightBoxWidth = width - leftBoxWidth;
+	
+			// 绘制左侧标题背景
+			ctx.fillStyle = dimensionData.titlecolor;
+			ctx.fillRect(0, y, leftBoxWidth, height);
+			
+			// 绘制左侧标题文字
+			ctx.fillStyle = dimensionData.title === '目的与动机' ? '#000000' : '#FFFFFF';
+			ctx.font = '13px sans-serif';
+			ctx.textAlign = 'center';
+			ctx.textBaseline = 'middle';
+			// 实现文字自动换行
+			this.drawWrappedText(ctx, scoreItem.title, leftBoxWidth / 2, y + height / 2, 20, leftBoxWidth - 10);
+	
+			// 绘制右侧边框
+			ctx.strokeStyle = dimensionData.bcolor;
+			ctx.lineWidth = 1;
+			ctx.strokeRect(rightBoxX, y, rightBoxWidth, height);
+	
+			// 绘制右侧描述文字
+			ctx.fillStyle = '#333333';
+			ctx.font = '12px sans-serif';
+			ctx.textAlign = 'left';
+			ctx.textBaseline = 'top';
+			this.drawWrappedText(ctx, scoreItem.desc, rightBoxX + 10, y + 12, 16, rightBoxWidth - 20);
+	
+			// 绘制进度条
+			const progressBarY = y + 45;
+			const progressBarWidth = rightBoxWidth - 20;
+			const scoreWidth = (scoreItem.score / 5) * progressBarWidth;
+	
+			// 创建渐变
+			const gradient = ctx.createLinearGradient(rightBoxX + 10, 0, rightBoxX + 10 + progressBarWidth, 0);
+			// 解析渐变色
+			const gradientColors = this.parseGradient(dimensionData.pfztfb);
+			gradientColors.forEach(c => gradient.addColorStop(c.stop, c.color));
+			ctx.fillStyle = gradient;
+			ctx.fillRect(rightBoxX + 10, progressBarY, scoreWidth, 6);
+	
+			// 绘制分数气泡
+			const bubbleSize = 24;
+			const bubbleX = rightBoxX + 10 + scoreWidth - (bubbleSize / 2);
+			const bubbleY = progressBarY - (bubbleSize / 2) + 3; // +3 微调居中
+			
+			// 气泡阴影
+			ctx.save();
+			ctx.shadowColor = dimensionData.bcolor;
+			ctx.shadowBlur = 6;
+			ctx.shadowOffsetX = 0;
+			ctx.shadowOffsetY = 2;
+			
+			// 气泡背景和边框
+			ctx.fillStyle = '#FFFFFF';
+			ctx.strokeStyle = dimensionData.bcolor;
+			ctx.lineWidth = 1;
+			ctx.beginPath();
+			ctx.arc(bubbleX + bubbleSize / 2, bubbleY + bubbleSize / 2, bubbleSize / 2, 0, Math.PI * 2);
+			ctx.fill();
+			ctx.stroke();
+			ctx.closePath();
+			ctx.restore();
+	
+			// 气泡内分数文字
+			ctx.fillStyle = '#333333';
+			ctx.font = 'bold 12px sans-serif';
+			ctx.textAlign = 'center';
+			ctx.textBaseline = 'middle';
+			ctx.fillText(scoreItem.score, bubbleX + bubbleSize / 2, bubbleY + bubbleSize / 2);
+		},
+		// 辅助函数:绘制自动换行的文字
+		drawWrappedText(ctx, text, x, y, lineHeight, maxWidth) {
+			let words = text.split('');
+			let line = '';
+			for (let n = 0; n < words.length; n++) {
+				let testLine = line + words[n];
+				let metrics = ctx.measureText(testLine);
+				let testWidth = metrics.width;
+				if (testWidth > maxWidth && n > 0) {
+					ctx.fillText(line, x, y);
+					line = words[n];
+					y += lineHeight;
+				} else {
+					line = testLine;
+				}
+			}
+			ctx.fillText(line, x, y);
+		},
+		// 辅助函数:解析 CSS linear-gradient 字符串
+		parseGradient(gradientString) {
+			const colorStops = [];
+			// 简化解析,仅适用于 "linear-gradient(90deg, #RRGGBB 0%, #RRGGBB 100%)" 格式
+			const matches = gradientString.match(/#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})\s+(\d+)%/g);
+			if (matches) {
+				matches.forEach(match => {
+					const parts = match.split(' ');
+					colorStops.push({ color: parts[0], stop: parseInt(parts[1]) / 100 });
+				});
+			}
+			return colorStops;
+		},
+		
+		
 		calculateScaleAndPosition() {
 		    uni.getSystemInfo({
 				success: (res) => {
@@ -342,6 +561,12 @@
 </script>
 
 <style scoped lang="scss">
+	.offscreen-canvas {
+		position: fixed;
+		top: -9999px;
+		left: -9999px;
+	}
+	
 	.page-wrappe{
 		width: 100%;
 		background: #FFFFFF;