From db6af269067592c94b9407136f92027bcea5bd31 Mon Sep 17 00:00:00 2001 From: Stickman <2048979561@qq.com> Date: Fri, 20 Mar 2026 11:44:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A5=E5=B8=B8=E4=BA=8B=E5=8A=A1-=E5=A4=84?= =?UTF-8?q?=E5=88=86=E7=AE=A1=E7=90=86=EF=BC=9A=E6=B7=BB=E5=8A=A0=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E6=B0=B4=E5=8D=B0=E5=8A=9F=E8=83=BD=E5=92=8C=E5=8D=B0?= =?UTF-8?q?=E7=AB=A0=E5=9B=BE=E7=89=87=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在审批任务对话框中添加了水印显示和隐藏功能 - 替换了静态印章图片为动态获取的印章base64数据 - 添加了下载处分送达书的按钮和相关处理逻辑 - 优化了印章样式布局,支持多种类型的印章展示 - 引入了html2canvas库用于将内容转为带水印的图片下载 - 添加了水印画布创建和背景图生成功能 ``` --- package.json | 10 +- src/views/flowable/task/todo/detail/index.vue | 251 ++++++++++++++++-- .../task/todo/detail/quitSchoolIndex.vue | 27 +- .../task/todo/detail/reentryIndex.vue | 38 ++- 4 files changed, 283 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index 97a19ba..b3fb122 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,11 @@ "author": "srs", "license": "MIT", "scripts": { - "dev": "cross-env vue-cli-service serve", - "build:prod": "cross-env NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build", - "build:stage": "cross-env NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build --mode staging", - "build": "cross-env NODE_OPTIONS=--openssl-legacy-provider vite build", - "preview": "cross-env NODE_OPTIONS=--openssl-legacy-provider node build/index.js --preview", + "dev": "vue-cli-service serve", + "build:prod": "vue-cli-service build", + "build:stage": "vue-cli-service build --mode staging", + "build": "vite build", + "preview": "node build/index.js --preview", "lint": "eslint --ext .js,.vue src", "lint:fix": "eslint --ext .js,.vue src --fix" }, diff --git a/src/views/flowable/task/todo/detail/index.vue b/src/views/flowable/task/todo/detail/index.vue index b9806b9..cebbead 100644 --- a/src/views/flowable/task/todo/detail/index.vue +++ b/src/views/flowable/task/todo/detail/index.vue @@ -757,7 +757,7 @@ + append-to-body @opened="showGlobalWatermark" @closed="hideGlobalWatermark">
@@ -766,19 +766,16 @@

{{ form.stuName }}同学:

{{ form.letterService }}

- Stamp -
学生工作处
{{ form.violationDate }}
+
- 下载处分下文 +
下载解除处分下文 @@ -790,13 +787,13 @@ }}学生,学号:{{ form.stuNo }}.该生于个人原因-{{ form.reasonApplying }},申请休学.经学校研究,同意休学,时间从{{ form.quitStartTime }}至{{ form.quitEndTime }}.

抄送:教务处、财务处、{{ form.departmentName }}

-
- Stamp -
- 学生工作处 - {{ form.quitStartTime }} +
+
+ +
+ 学生工作处 + {{ form.quitStartTime }} +
@@ -893,6 +890,7 @@ + 下载处分送达书 取 消 确 定 @@ -938,6 +936,7 @@ import { flowXmlAndNode, getProcessVariables } from '@/api/flowable/definition' import { flowRecord } from '@/api/flowable/finished' import { complete, delegate, flowTaskForm, getNextFlowNode, rejectTask, returnList, returnTask } from '@/api/flowable/todo' import { getDisciplinaryApplicationByProcInsId, getStuInfo, updateDisciplinaryApplication } from '@/api/routine/disciplinaryApplication' +import { getStampBase64 } from '@/api/common/stamp' import { getLeaveApplicationByProcInsId, getStuInfoByStuId } from '@/api/routine/leaveApplication' import { getStuDisciplinaryRelieveByProcInsId, updateRelieve } from '@/api/routine/relieve' import { getRtStuQuitSchoolByProcInsId, updateRtStuQuitSchool } from '@/api/routine/rtStuQuitSchool' @@ -1098,6 +1097,9 @@ export default { basicForm: false,//退伍复学表单 BasicTestData: 0, // 新增:默认0,仅退回学生申请时改为1 user: [], // 当前登录用户 + stampBase64: '', // 印章Base64数据 + globalWatermarkEl: null, + globalWatermarkText: '\u5e7f\u897f\u6c34\u5229\u7535\u529b\u804c\u4e1a\u6280\u672f\u5b66\u9662\u5b66\u5de5\u7cfb\u7edf' } }, created() { @@ -1120,9 +1122,11 @@ export default { } if ((this.taskName == '学生接收' || this.taskName == '申请人(辅导员)接收') && this.category == 'disposal') { this.showLetterService = true + this.getStampBase64() } if ((this.taskName == '学生接收' || this.taskName == '辅导员接收') && this.category == 'quitSchool') { this.showQuitSchoolProve = true + this.getStampBase64() } if (this.taskName == '学生接收' && this.category == 'relieve') { this.showFileDowload = true @@ -1177,7 +1181,86 @@ export default { } }, methods: { - + createWatermarkCanvas(text, options = {}) { + const { + width = 300, + height = 200, + angle = -25, + fontSize = 18, + alpha = 0.2 + } = options + const canvas = document.createElement('canvas') + canvas.width = width + canvas.height = height + const ctx = canvas.getContext('2d') + if (!ctx) { + return null + } + ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.translate(canvas.width / 2, canvas.height / 2) + ctx.rotate((angle * Math.PI) / 180) + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + ctx.font = `${fontSize}px Microsoft YaHei` + ctx.fillStyle = `rgba(0, 0, 0, ${alpha})` + ctx.fillText(text, 0, 0) + return canvas + }, + createWatermarkDataUrl(text, options = {}) { + const canvas = this.createWatermarkCanvas(text, options) + if (!canvas) { + return '' + } + return canvas.toDataURL('image/png') + }, + showGlobalWatermark() { + if (typeof document === 'undefined') { + return + } + + // 检查是否有显示处分决定送达书的卡片且包含.stamp元素 + const letterServiceCard = document.querySelector('.el-card:has(.stamp)') + if (!letterServiceCard) { + // 如果没有找到符合条件的元素,则不显示水印 + return + } + + if (!this.globalWatermarkEl) { + this.globalWatermarkEl = document.createElement('div') + this.globalWatermarkEl.className = 'global-dialog-watermark-mask' + } + const topZIndex = Array.from(document.querySelectorAll('body *')) + .map(item => Number(window.getComputedStyle(item).zIndex) || 0) + .reduce((max, value) => Math.max(max, value), 2002) + const watermarkUrl = this.createWatermarkDataUrl(this.globalWatermarkText) + this.globalWatermarkEl.style.cssText = ` + position: fixed; + inset: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: ${topZIndex + 1}; + background-image: url(${watermarkUrl}); + background-size: 300px 200px; + background-repeat: repeat; + background-position: 0 0; + ` + if (!document.body.contains(this.globalWatermarkEl)) { + document.body.appendChild(this.globalWatermarkEl) + } + }, + hideGlobalWatermark() { + if (this.globalWatermarkEl && this.globalWatermarkEl.parentNode) { + this.globalWatermarkEl.parentNode.removeChild(this.globalWatermarkEl) + } + }, + + getStampBase64() { + getStampBase64().then(res => { + this.stampBase64 = res.msg + }) + }, + handleChange1(value) { if (value && value.length === 3) { const [gradeId, majorId, classId] = value; @@ -1525,7 +1608,7 @@ export default { // this.completeOpen = true; // this.completeTitle = "流程审批"; this.submitForm(null) - // 获取当前用户的签名,并赋值给this.taskForm.variables.signature传入监听器 + // 获取当前用户的签名,并赋值给this.taskForm.variables传入监听器 this.taskForm.variables.signature = this.user.signature }, /** 用户审批任务 */ @@ -2380,7 +2463,53 @@ export default { }) }, fileUpload() { - download.resource(this.pdfURL) + // 使用html2canvas将处分决定书卡片内容转换为图片并下载 + import('html2canvas').then((html2canvas) => { + // 优先选择处分决定书的卡片内容,如果找不到再尝试通用的.el-card__body + const element = document.querySelector('.certificate-service .el-card__body') || + document.querySelector('.certificate-service') || + document.querySelector('.el-card__body'); + if (element) { + html2canvas.default(element).then((canvas) => { + // 创建一个新的canvas用于添加水印 + const watermarkedCanvas = document.createElement('canvas'); + const ctx = watermarkedCanvas.getContext('2d'); + watermarkedCanvas.width = canvas.width; + watermarkedCanvas.height = canvas.height; + + // 绘制原始canvas内容 + ctx.drawImage(canvas, 0, 0); + + // 使用平铺水印块,避免文字重叠并降低透明度 + const watermarkCanvas = this.createWatermarkCanvas(this.globalWatermarkText, { + width: 320, + height: 220, + angle: -25, + fontSize: 22, + alpha: 0.3 + }) + if (watermarkCanvas) { + ctx.save() + const pattern = ctx.createPattern(watermarkCanvas, 'repeat') + if (pattern) { + ctx.fillStyle = pattern + ctx.fillRect(0, 0, watermarkedCanvas.width, watermarkedCanvas.height) + } + ctx.restore() + } + + // 创建下载链接 + const link = document.createElement('a'); + link.download = '处分决定送达书.png'; // 更具描述性的文件名 + link.href = watermarkedCanvas.toDataURL('image/png'); + link.click(); + }); + } else { + console.error('找不到需要截图的元素'); + } + }).catch((error) => { + console.error('html2canvas 加载失败:', error); + }); }, // 初始化入伍保留学籍申请审核意见参数 initApproval() { @@ -2470,6 +2599,9 @@ export default { }) } }, + beforeDestroy() { + this.hideGlobalWatermark() + } } diff --git a/src/views/flowable/task/todo/detail/quitSchoolIndex.vue b/src/views/flowable/task/todo/detail/quitSchoolIndex.vue index 3314a16..a61cc67 100644 --- a/src/views/flowable/task/todo/detail/quitSchoolIndex.vue +++ b/src/views/flowable/task/todo/detail/quitSchoolIndex.vue @@ -194,11 +194,11 @@

{{ form.stuName }},{{ form.gender }},{{ form.mz }},{{ form.birthday }}出生,{{ form.jg }}人,{{ form.className }}学生,学号:{{ form.stuNo }}.该生于个人原因-{{ form.reasonApplying }},申请休学.经学校研究,同意休学,时间从{{ form.quitStartTime }}至{{ form.quitEndTime }}.

抄送:教务处、财务处、{{ form.departmentName }}

- Stamp
学生工作处 {{ form.quitStartTime }}
+
@@ -289,6 +289,7 @@ import { flowXmlAndNode, getProcessVariables } from '@/api/flowable/definition' import { flowRecord } from '@/api/flowable/finished' import { complete, delegate, flowTaskForm, getNextFlowNode, rejectTask, returnList, returnTask } from '@/api/flowable/todo' import { getRtStuQuitSchoolByProcInsId, updateRtStuQuitSchool } from '@/api/routine/rtStuQuitSchool' +import { getStampBase64 } from '@/api/common/stamp' import FlowRole from '@/components/flow/Role' import FlowUser from '@/components/flow/User' import Parser from '@/components/parser/Parser' @@ -403,6 +404,7 @@ export default { quitSchoolForm: false, // 休学申请表单 showQuitSchoolProve: false, //休学证明 quitSchoolGLKSHShow: false, // 学生教育管理科审核 + stampBase64: '', // 印章Base64数据 } }, created() { @@ -418,6 +420,7 @@ export default { // 如果任务名是其中的两个,则改变审批意见的输入框内容 if ((this.taskName == '学生接收' || this.taskName == '辅导员接收') && this.category == 'quitSchool') { this.showQuitSchoolProve = true + this.getStampBase64() } if (this.category == 'quitSchool') { this.quitSchoolForm = true @@ -439,6 +442,12 @@ export default { } }, methods: { + getStampBase64() { + getStampBase64().then(res => { + this.stampBase64 = res.msg + }) + }, + reentryYearMethodFormat(row, column) { return this.selectDictLabel(this.dict.type.sys_teacher_kpi_filling_year, row.quitYear) }, @@ -765,14 +774,24 @@ export default { .stamp { text-align: right; + position: relative; + min-height: 120px; + padding-top: 10px; + & > div { - margin-top: -70px; + position: relative; + z-index: 2; + margin-top: 10px; margin-right: 25px; } img { - width: 120px; - height: 120px; + position: absolute; + right: 0; + top: 10px; + width: 200px; + height: 200px; + z-index: 1; } } } diff --git a/src/views/flowable/task/todo/detail/reentryIndex.vue b/src/views/flowable/task/todo/detail/reentryIndex.vue index a71657a..0063f75 100644 --- a/src/views/flowable/task/todo/detail/reentryIndex.vue +++ b/src/views/flowable/task/todo/detail/reentryIndex.vue @@ -213,11 +213,11 @@

抄送:教务处、财务处、{{ form.departmentName }}

- Stamp
学生工作处 {{ form.reentryTime }}
+
@@ -322,6 +322,7 @@ import { flowXmlAndNode, getProcessVariables } from '@/api/flowable/definition' import { flowRecord } from '@/api/flowable/finished' import { complete, delegate, flowTaskForm, getNextFlowNode, rejectTask, returnList, returnTask } from '@/api/flowable/todo' import { getRtStuReentrySchoolByProcInsId, updateRtStuReentrySchool } from '@/api/routine/rtStuReentrySchool' +import { getStampBase64 } from '@/api/common/stamp' import { getClass } from '@/api/stuCQS/basedata/class' import { getClassName } from '@/api/stuCQS/basedata/student' import FlowRole from '@/components/flow/Role' @@ -442,6 +443,7 @@ export default { classVlue2: [], //班级添加修改选择 ClassNameList: [], //班级名称 showReentryProve: false, // 复学证明 + stampBase64: '', // 印章Base64数据 } }, created() { @@ -466,6 +468,7 @@ export default { } if (this.taskName == '辅导员接收' || this.taskName == '学生接收') { this.showReentryProve = true + this.getStampBase64() } } // 流程任务获取变量信息 @@ -480,6 +483,12 @@ export default { this.getClassNameList() }, methods: { + getStampBase64() { + getStampBase64().then(res => { + this.stampBase64 = res.msg + }) + }, + reentryYearMethodFormat(row, column) { return this.selectDictLabel(this.dict.type.sys_teacher_kpi_filling_year, row.reentryYear) }, @@ -837,14 +846,24 @@ export default { .stamp { text-align: right; + position: relative; + min-height: 120px; + padding-top: 10px; + & > div { - margin-top: -70px; + position: relative; + z-index: 2; + margin-top: 10px; margin-right: 25px; } img { - width: 120px; - height: 120px; + position: absolute; + right: 0; + top: 10px; + width: 200px; + height: 200px; + z-index: 1; } } } @@ -880,17 +899,20 @@ export default { align-items: flex-end; & > div { + position: relative; + z-index: 2; display: flex; flex-direction: column; margin-right: 28px; } img { - width: 180px; - height: 180px; - top: -54px; position: absolute; - z-index: -1; + right: 0; + top: 0; + width: 200px; + height: 200px; + z-index: 1; } } }