dedsudiyu 1 day ago
parent
commit
7d53e9a491

+ 227 - 0
pages_safetyEducationExamination/views/home/component/dataStatisticsReport.vue

@@ -91,7 +91,14 @@
         </view>
       </view>
       </scroll-view>
+      <!-- 底部操作栏 -->
+      <view class="action-bar">
+        <view class="btn-close" @tap="handleClose">关闭</view>
+        <view class="btn-save" @tap="saveAsImage">保存图片</view>
+      </view>
     </view>
+    <!-- 用于截图的离屏 canvas -->
+    <canvas canvas-id="reportCanvas" class="report-canvas"></canvas>
   </view>
 </template>
 
@@ -146,6 +153,226 @@
       onOverlayTap() {
         this.$emit('close');
       },
+      saveAsImage() {
+        uni.showLoading({ title: '生成中...' });
+        const d = this.newData;
+        const W = 680;
+        const lineH = 36;
+        const pad = 48;
+        const tableW = W - pad * 2;
+
+        // 预估高度
+        const studyRows = (d.studyHoursList || []).length;
+        const examRows = (d.examPassRecords || []).length;
+        const H = 120 + 100 + 60 + (studyRows > 0 ? 80 + studyRows * 32 + 32 : 0) + (examRows > 0 ? 80 + examRows * 32 : 0) + 120 + 160;
+
+        const ctx = uni.createCanvasContext('reportCanvas', this);
+
+        // 背景
+        ctx.setFillStyle('#fffdf5');
+        ctx.fillRect(0, 0, W, H);
+
+        // 外边框
+        ctx.setStrokeStyle('#c9a84c');
+        ctx.setLineWidth(1);
+        ctx.strokeRect(2, 2, W - 4, H - 4);
+        ctx.strokeRect(6, 6, W - 12, H - 12);
+
+        let y = 40;
+
+        // logo 背景块
+        ctx.setFillStyle('#77161B');
+        ctx.fillRect(pad, y, 578, 88);
+        y += 88 + 16;
+
+        // 标题
+        ctx.setFontSize(20);
+        ctx.setFillStyle('#8b452f');
+        ctx.setTextAlign('center');
+        ctx.fillText('实验室安全教育成绩报告', W / 2, y + 20);
+        y += 56;
+
+        // 用户信息背景
+        ctx.setFillStyle('#faf6ec');
+        ctx.fillRect(pad, y, tableW, 80);
+        ctx.setStrokeStyle('#e8dcc8');
+        ctx.strokeRect(pad, y, tableW, 80);
+
+        ctx.setFontSize(14);
+        ctx.setFillStyle('#4a3c2a');
+        ctx.setTextAlign('left');
+        const col1 = pad + 12;
+        const col2 = pad + tableW / 2 + 12;
+        ctx.fillText('姓名:' + (d.userName || ''), col1, y + 22);
+        ctx.fillText('学号:' + (d.account || ''), col2, y + 22);
+        ctx.fillText('学院:' + (d.deptName || ''), col1, y + 46);
+        ctx.fillText('年级:' + (d.grade || ''), col2, y + 46);
+        ctx.fillText('用户类型:' + (d.educationTypeName || ''), col1, y + 70);
+        y += 80 + 20;
+
+        // 学年学时表
+        if (studyRows > 0) {
+          // 小标题
+          ctx.setFillStyle('#c9a84c');
+          ctx.fillRect(pad, y, 3, 20);
+          ctx.setFontSize(14);
+          ctx.setFillStyle('#8b4513');
+          ctx.setTextAlign('left');
+          ctx.fillText('学年学时完成情况', pad + 10, y + 16);
+          y += 26;
+
+          // 表头
+          ctx.setFillStyle('#f5edd8');
+          ctx.fillRect(pad, y, tableW, 34);
+          ctx.setStrokeStyle('#d4c5a0');
+          ctx.strokeRect(pad, y, tableW, 34);
+          ctx.setFontSize(12);
+          ctx.setFillStyle('#5a4320');
+          ctx.setTextAlign('center');
+          const cw = tableW / 3;
+          ctx.fillText('学年', pad + cw * 0.5, y + 22);
+          ctx.fillText('完成学时', pad + cw * 1.5, y + 22);
+          ctx.fillText('要求学时', pad + cw * 2.5, y + 22);
+          y += 34;
+
+          // 数据行
+          for (let i = 0; i < studyRows; i++) {
+            const row = d.studyHoursList[i];
+            ctx.setFillStyle(i % 2 === 0 ? '#fffdf5' : '#faf6ec');
+            ctx.fillRect(pad, y, tableW, 32);
+            ctx.setStrokeStyle('#ebe3d0');
+            ctx.strokeRect(pad, y, tableW, 32);
+            ctx.setFontSize(12);
+            ctx.setFillStyle('#4a3c2a');
+            ctx.setTextAlign('center');
+            ctx.fillText(String(row.academicYear || ''), pad + cw * 0.5, y + 20);
+            ctx.fillText(String(row.completedStudyHours || ''), pad + cw * 1.5, y + 20);
+            ctx.fillText(String(row.studyHoursRequirement || ''), pad + cw * 2.5, y + 20);
+            y += 32;
+          }
+
+          // 累计行
+          ctx.setFillStyle('#faf6ec');
+          ctx.fillRect(pad, y, tableW, 32);
+          ctx.setStrokeStyle('#ebe3d0');
+          ctx.strokeRect(pad, y, tableW, 32);
+          ctx.setFontSize(12);
+          ctx.setFillStyle('#4a3c2a');
+          ctx.setTextAlign('center');
+          ctx.fillText('累计', pad + cw * 0.5, y + 20);
+          ctx.fillText((d.totalStudyHours || '') + ' 学时', pad + cw * 1.5, y + 20);
+          y += 32 + 20;
+        }
+
+        // 考试通过记录表
+        if (examRows > 0) {
+          ctx.setFillStyle('#c9a84c');
+          ctx.fillRect(pad, y, 3, 20);
+          ctx.setFontSize(14);
+          ctx.setFillStyle('#8b4513');
+          ctx.setTextAlign('left');
+          ctx.fillText('考试通过记录', pad + 10, y + 16);
+          y += 26;
+
+          ctx.setFillStyle('#f5edd8');
+          ctx.fillRect(pad, y, tableW, 34);
+          ctx.setStrokeStyle('#d4c5a0');
+          ctx.strokeRect(pad, y, tableW, 34);
+          ctx.setFontSize(12);
+          ctx.setFillStyle('#5a4320');
+          ctx.setTextAlign('center');
+          const cw2 = tableW / 3;
+          ctx.fillText('学年', pad + cw2 * 0.5, y + 22);
+          ctx.fillText('考试类型', pad + cw2 * 1.5, y + 22);
+          ctx.fillText('成绩', pad + cw2 * 2.5, y + 22);
+          y += 34;
+
+          for (let i = 0; i < examRows; i++) {
+            const row = d.examPassRecords[i];
+            ctx.setFillStyle(i % 2 === 0 ? '#fffdf5' : '#faf6ec');
+            ctx.fillRect(pad, y, tableW, 32);
+            ctx.setStrokeStyle('#ebe3d0');
+            ctx.strokeRect(pad, y, tableW, 32);
+            ctx.setFontSize(12);
+            ctx.setFillStyle('#4a3c2a');
+            ctx.setTextAlign('center');
+            ctx.fillText(String(row.academicYear || ''), pad + cw2 * 0.5, y + 20);
+            ctx.fillText(String(row.examTypeName || ''), pad + cw2 * 1.5, y + 20);
+            ctx.fillText(String(row.score || ''), pad + cw2 * 2.5, y + 20);
+            y += 32;
+          }
+          y += 20;
+        }
+
+        // 描述文字
+        ctx.setFontSize(14);
+        ctx.setFillStyle('#4a3c2a');
+        ctx.setTextAlign('left');
+        const desc = `  该同学在校期间认真完成实验室安全教育各项学习任务,累计完成 ${d.totalStudyHours || ''} 学时安全培训,通过全部年度安全考核,具备良好的实验室安全意识和规范操作能力,特此证明。`;
+        // 简单换行处理
+        const maxWidth = tableW;
+        const words = desc.split('');
+        let line = '';
+        for (let i = 0; i < words.length; i++) {
+          const testLine = line + words[i];
+          const metrics = ctx.measureText(testLine);
+          if (metrics.width > maxWidth && line !== '') {
+            ctx.fillText(line, pad, y);
+            y += lineH;
+            line = words[i];
+          } else {
+            line = testLine;
+          }
+        }
+        if (line) { ctx.fillText(line, pad, y); y += lineH; }
+        y += 40;
+
+        // 落款
+        ctx.setFontSize(14);
+        ctx.setFillStyle('#4a3c2a');
+        ctx.setTextAlign('right');
+        ctx.fillText(d.issuingUnit || '', W - pad, y);
+        y += 28;
+        ctx.fillText(d.issuingTime || '', W - pad, y);
+
+        ctx.draw(false, () => {
+          wx.canvasToTempFilePath({
+            canvasId: 'reportCanvas',
+            x: 0,
+            y: 0,
+            width: W,
+            height: H,
+            destWidth: W * 2,
+            destHeight: H * 2,
+            fileType: 'jpg',
+            quality: 1,
+            success: (res) => {
+              uni.hideLoading();
+              uni.saveImageToPhotosAlbum({
+                filePath: res.tempFilePath,
+                success: () => {
+                  uni.showToast({ title: '已保存到相册', icon: 'success' });
+                },
+                fail: (err) => {
+                  if (err.errMsg && err.errMsg.indexOf('auth deny') !== -1) {
+                    uni.showModal({
+                      title: '需要授权',
+                      content: '请在设置中允许访问相册',
+                      showCancel: false,
+                    });
+                  } else {
+                    uni.showToast({ title: '保存失败,请重试', icon: 'none' });
+                  }
+                }
+              });
+            },
+            fail: () => {
+              uni.hideLoading();
+              uni.showToast({ title: '图片生成失败', icon: 'none' });
+            }
+          }, this);
+        });
+      },
     }
   }
 </script>