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