|
|
@@ -4,7 +4,7 @@
|
|
|
<!-- 弹窗容器 -->
|
|
|
<view class="report-wrap" @tap.stop="stopProp">
|
|
|
<!-- 关闭按钮 -->
|
|
|
- <view class="close-btn" @tap="handleClose">✕</view>
|
|
|
+ <!-- <view class="close-btn" @tap="handleClose">✕</view> -->
|
|
|
<scroll-view scroll-y class="report-scroll">
|
|
|
<view class="report-wrap-big-box">
|
|
|
<view class="report-wrap-min-box">
|
|
|
@@ -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>
|
|
|
|
|
|
@@ -110,7 +117,8 @@
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
- logUrl: 'https://labcontrol.nwafu.edu.cn/statics/2024/07/22/d1a9d129-ced7-49ac-b258-e5167d85232d.png',
|
|
|
+ logUrl: uni.getStorageSync('rectangleLogo'),
|
|
|
+ // logUrl: 'https://gzu.zjznai.com/statics/2026/06/17/00df3205-5321-4b46-aae3-0ffe4451ee51.png',
|
|
|
newData: {
|
|
|
userName: '',
|
|
|
account: '',
|
|
|
@@ -145,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>
|
|
|
@@ -205,8 +433,9 @@
|
|
|
width: 578rpx;
|
|
|
height: 88rpx;
|
|
|
border-radius: 6rpx;
|
|
|
- background-color: #1a6e3a;
|
|
|
- margin: 40rpx 0 0 48rpx;
|
|
|
+ // background-color: #1a6e3a;
|
|
|
+ background-color: #77161B;
|
|
|
+ margin: 40rpx 0 0 24rpx;
|
|
|
overflow: hidden;
|
|
|
|
|
|
image {
|
|
|
@@ -231,7 +460,7 @@
|
|
|
width: 578rpx;
|
|
|
background-color: #faf6ec;
|
|
|
border: 1rpx solid #e8dcc8;
|
|
|
- margin: 9rpx 0 0 48rpx;
|
|
|
+ margin: 9rpx 0 0 24rpx;
|
|
|
border-radius: 4rpx;
|
|
|
padding: 10rpx 20rpx;
|
|
|
box-sizing: border-box;
|
|
|
@@ -261,7 +490,7 @@
|
|
|
|
|
|
// 学年学时列表
|
|
|
.study-hours-max-big-box {
|
|
|
- margin: 20rpx 48rpx 0 48rpx;
|
|
|
+ margin: 20rpx 24rpx 0 24rpx;
|
|
|
|
|
|
.study-hours-title-box {
|
|
|
display: flex;
|
|
|
@@ -339,7 +568,7 @@
|
|
|
|
|
|
// 考试通过记录列表
|
|
|
.study-hours-max-big-box-2 {
|
|
|
- margin: 20rpx 48rpx 0 48rpx;
|
|
|
+ margin: 20rpx 24rpx 0 24rpx;
|
|
|
|
|
|
.study-hours-title-box {
|
|
|
display: flex;
|