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