|
|
@@ -1,49 +1,100 @@
|
|
|
<template>
|
|
|
<ele-modal
|
|
|
- title="合格证"
|
|
|
+ :title="isAttachmentMode ? '合格证附件' : '合格证'"
|
|
|
:visible.sync="QRvisible"
|
|
|
v-if="QRvisible"
|
|
|
- width="750px"
|
|
|
+ :width="isAttachmentMode ? '1000px' : '750px'"
|
|
|
append-to-body
|
|
|
@close="close"
|
|
|
>
|
|
|
- <div class="switch" v-if="type == 'print'">
|
|
|
- <div class="switch_left">
|
|
|
- <ul>
|
|
|
- <li
|
|
|
- v-for="item in tabOptions"
|
|
|
- :key="item.key"
|
|
|
- :class="{ active: activeComp == item.key }"
|
|
|
- @click="activeComp = item.key"
|
|
|
- >
|
|
|
- {{ item.name }}
|
|
|
- </li>
|
|
|
- </ul>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div v-show="activeComp === 'main' && type != 'print'">
|
|
|
- 合格证列表:
|
|
|
- <el-button type="primary" @click="add()"> 选择 </el-button>
|
|
|
+ <!-- 无打印模板数据的合格证(如军检回传):不渲染打印模板,展示回传附件预览/打印/下载 -->
|
|
|
+ <div v-if="isAttachmentMode" class="attachment-pdf-area">
|
|
|
+ <template v-if="attachmentPdfUrls.length">
|
|
|
+ <div
|
|
|
+ v-for="(item, idx) in attachmentPdfUrls"
|
|
|
+ :key="idx"
|
|
|
+ class="attachment-pdf-item"
|
|
|
+ >
|
|
|
+ <div class="attachment-pdf-title">
|
|
|
+ <span>{{ item.name || `合格证附件${idx + 1}` }}</span>
|
|
|
+ <div>
|
|
|
+ <el-link
|
|
|
+ type="primary"
|
|
|
+ :underline="false"
|
|
|
+ icon="el-icon-printer"
|
|
|
+ @click="printAttachment(item)"
|
|
|
+ >打印</el-link
|
|
|
+ >
|
|
|
+ <el-link
|
|
|
+ type="primary"
|
|
|
+ :underline="false"
|
|
|
+ icon="el-icon-download"
|
|
|
+ :href="item.url"
|
|
|
+ target="_blank"
|
|
|
+ :download="attachmentPdfName(idx)"
|
|
|
+ style="margin-left: 12px"
|
|
|
+ >下载</el-link
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 附件可能是 PDF 也可能是图片(按 base64 魔数自动识别) -->
|
|
|
+ <iframe
|
|
|
+ v-if="item.kind === 'pdf'"
|
|
|
+ :src="item.url"
|
|
|
+ class="attachment-pdf-frame"
|
|
|
+ frameborder="0"
|
|
|
+ ></iframe>
|
|
|
+ <img v-else :src="item.url" class="attachment-image" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-empty
|
|
|
+ v-else-if="!attachmentPdfLoading"
|
|
|
+ description="该合格证无打印模板,且暂无附件"
|
|
|
+ ></el-empty>
|
|
|
</div>
|
|
|
- <div id="printSection" v-show="activeComp === 'main'">
|
|
|
- <!-- <certificate_conformity_jangnan_template
|
|
|
- ref="template1Ref"
|
|
|
- :type="printType"
|
|
|
- ></certificate_conformity_jangnan_template> -->
|
|
|
+ <template v-else>
|
|
|
+ <div class="switch" v-if="type == 'print'">
|
|
|
+ <div class="switch_left">
|
|
|
+ <ul>
|
|
|
+ <li
|
|
|
+ v-for="item in tabOptions"
|
|
|
+ :key="item.key"
|
|
|
+ :class="{ active: activeComp == item.key }"
|
|
|
+ @click="activeComp = item.key"
|
|
|
+ >
|
|
|
+ {{ item.name }}
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-show="activeComp === 'main' && type != 'print'">
|
|
|
+ 合格证列表:
|
|
|
+ <el-button type="primary" @click="add()"> 选择 </el-button>
|
|
|
+ </div>
|
|
|
+ <div id="printSection" v-show="activeComp === 'main'">
|
|
|
+ <!-- <certificate_conformity_jangnan_template
|
|
|
+ ref="template1Ref"
|
|
|
+ :type="printType"
|
|
|
+ ></certificate_conformity_jangnan_template> -->
|
|
|
|
|
|
- <component
|
|
|
- :is="form.reportTemplateCode"
|
|
|
- ref="template1Ref"
|
|
|
- :type="printType"
|
|
|
- :key="form.reportTemplateCode"
|
|
|
- ></component>
|
|
|
- </div>
|
|
|
- <bpmDetail
|
|
|
- v-if="activeComp == 'bpm' && form.processInstanceId"
|
|
|
- :id="form.processInstanceId"
|
|
|
- ></bpmDetail>
|
|
|
+ <component
|
|
|
+ :is="form.reportTemplateCode"
|
|
|
+ ref="template1Ref"
|
|
|
+ :type="printType"
|
|
|
+ :key="form.reportTemplateCode"
|
|
|
+ ></component>
|
|
|
+ </div>
|
|
|
+ <bpmDetail
|
|
|
+ v-if="activeComp == 'bpm' && form.processInstanceId"
|
|
|
+ :id="form.processInstanceId"
|
|
|
+ ></bpmDetail>
|
|
|
+ </template>
|
|
|
<div slot="footer">
|
|
|
- <el-button @click="print" v-if="type == 'print'">打印预览</el-button>
|
|
|
+ <el-button
|
|
|
+ @click="print"
|
|
|
+ v-if="type == 'print' && !isAttachmentMode"
|
|
|
+ >打印预览</el-button
|
|
|
+ >
|
|
|
<el-button
|
|
|
type="primary"
|
|
|
v-if="type != 'print'"
|
|
|
@@ -111,6 +162,10 @@
|
|
|
type: '',
|
|
|
printType: '',
|
|
|
activeComp: 'main',
|
|
|
+ // 打印模板数据不存在的合格证(如军检回传)走 PDF 附件预览分支,不影响原模板打印
|
|
|
+ isAttachmentMode: false,
|
|
|
+ attachmentPdfUrls: [],
|
|
|
+ attachmentPdfLoading: false,
|
|
|
tabOptions: [
|
|
|
{ key: 'main', name: '合格证详情' },
|
|
|
{ key: 'bpm', name: '流程详情' }
|
|
|
@@ -129,6 +184,18 @@
|
|
|
this.form = row || {};
|
|
|
}
|
|
|
|
|
|
+ // 兼容性分支:打印模板数据不存在(reportTemplateCode 为空,如军检回传合格证)
|
|
|
+ // 时才拉取附件 base64 走 PDF 预览;有模板数据的一律走原打印逻辑
|
|
|
+ this.isAttachmentMode =
|
|
|
+ type == 'print' && !this.form.reportTemplateCode;
|
|
|
+ if (this.isAttachmentMode) {
|
|
|
+ this.type = type;
|
|
|
+ this.attachmentPdfUrls = [];
|
|
|
+ this.QRvisible = true;
|
|
|
+ this.loadAttachmentPdfs();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
this.activeComp = 'main';
|
|
|
this.type = type;
|
|
|
this.printType = type;
|
|
|
@@ -137,6 +204,150 @@
|
|
|
this.$refs.template1Ref.init(this.form);
|
|
|
});
|
|
|
},
|
|
|
+ /** 打印模板数据不存在时,拉取合格证附件(大字段详情接口才返回),base64 转 PDF Blob 预览 */
|
|
|
+ async loadAttachmentPdfs() {
|
|
|
+ this.attachmentPdfLoading = true;
|
|
|
+ try {
|
|
|
+ const res = await getById(this.form.id);
|
|
|
+ const detail = res.data || {};
|
|
|
+ let attachments = [];
|
|
|
+ const raw = detail.certificateAttachments;
|
|
|
+ if (raw) {
|
|
|
+ try {
|
|
|
+ attachments = JSON.parse(raw);
|
|
|
+ } catch (e) {
|
|
|
+ // 兼容裸 base64 字符串
|
|
|
+ attachments = [raw];
|
|
|
+ }
|
|
|
+ if (!Array.isArray(attachments) && attachments) {
|
|
|
+ attachments = [attachments];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.attachmentPdfUrls = (Array.isArray(attachments) ? attachments : [])
|
|
|
+ .map((item) => {
|
|
|
+ // 结构化附件 {fileName, base64}
|
|
|
+ if (item && typeof item === 'object' && item.base64) {
|
|
|
+ return this.buildAttachmentFile(item.base64, item.fileName);
|
|
|
+ }
|
|
|
+ // 兼容旧格式:纯 base64 字符串
|
|
|
+ return this.buildAttachmentFile(item, '');
|
|
|
+ })
|
|
|
+ .filter(Boolean);
|
|
|
+ if (!this.attachmentPdfUrls.length) {
|
|
|
+ this.$message.info('该合格证无打印模板,且暂无附件');
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('获取合格证附件失败');
|
|
|
+ } finally {
|
|
|
+ this.attachmentPdfLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * base64 → Blob URL,并按魔数识别真实类型。
|
|
|
+ * 对方名义上按 PDF 处理,但实际推送过图片(如 PNG),按内容自动识别渲染。
|
|
|
+ */
|
|
|
+ buildAttachmentFile(base64, fileName) {
|
|
|
+ const type = this.detectBase64Type(base64);
|
|
|
+ if (!type) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return { url: type.url, name: fileName || '', kind: type.kind };
|
|
|
+ },
|
|
|
+ /** 识别 base64 文件类型:PNG/JPEG 走图片渲染,其余(含 PDF)统一按 PDF 处理 */
|
|
|
+ detectBase64Type(base64) {
|
|
|
+ if (!base64 || typeof base64 !== 'string') {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ let head = '';
|
|
|
+ try {
|
|
|
+ head = atob(base64.slice(0, 16));
|
|
|
+ } catch (e) {
|
|
|
+ console.error('附件 base64 解析失败:', e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ let mime = 'application/pdf';
|
|
|
+ let kind = 'pdf';
|
|
|
+ if (head.startsWith('\u0089PNG')) {
|
|
|
+ mime = 'image/png';
|
|
|
+ kind = 'image';
|
|
|
+ } else if (head.startsWith('\u00FF\u00D8\u00FF')) {
|
|
|
+ mime = 'image/jpeg';
|
|
|
+ kind = 'image';
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const binary = atob(base64);
|
|
|
+ const bytes = new Uint8Array(binary.length);
|
|
|
+ for (let i = 0; i < binary.length; i++) {
|
|
|
+ bytes[i] = binary.charCodeAt(i);
|
|
|
+ }
|
|
|
+ const url = URL.createObjectURL(new Blob([bytes], { type: mime }));
|
|
|
+ return { url, kind };
|
|
|
+ } catch (e) {
|
|
|
+ console.error('附件 base64 解析失败:', e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 打印单个附件:图片开新窗口打印;PDF 用隐藏 iframe 调起浏览器打印,
|
|
|
+ * 失败时(如浏览器不支持 PDF 内嵌打印)降级为新窗口由用户手动打印。
|
|
|
+ */
|
|
|
+ printAttachment(item) {
|
|
|
+ if (!item || !item.url) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (item.kind === 'pdf') {
|
|
|
+ const iframe = document.createElement('iframe');
|
|
|
+ iframe.style.position = 'fixed';
|
|
|
+ iframe.style.width = '0';
|
|
|
+ iframe.style.height = '0';
|
|
|
+ iframe.style.border = '0';
|
|
|
+ iframe.style.visibility = 'hidden';
|
|
|
+ iframe.src = item.url;
|
|
|
+ iframe.onload = () => {
|
|
|
+ try {
|
|
|
+ iframe.contentWindow.print();
|
|
|
+ } catch (e) {
|
|
|
+ window.open(item.url, '_blank');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ document.body.appendChild(iframe);
|
|
|
+ // 打印窗口复用结束后清理
|
|
|
+ setTimeout(() => {
|
|
|
+ if (iframe.parentNode) {
|
|
|
+ iframe.parentNode.removeChild(iframe);
|
|
|
+ }
|
|
|
+ }, 60000);
|
|
|
+ } else {
|
|
|
+ const win = window.open('', '_blank');
|
|
|
+ if (!win) {
|
|
|
+ this.$message.warning('浏览器拦截了弹出窗口,请允许弹窗后重试');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ win.document.write(
|
|
|
+ `<html><head><title>${item.name || '合格证附件'}</title>` +
|
|
|
+ '<style>body{margin:0;text-align:center;}img{max-width:100%;}</style></head>' +
|
|
|
+ `<body><img src="${item.url}" onload="window.print()" /></body></html>`
|
|
|
+ );
|
|
|
+ win.document.close();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /** 下载文件名:优先用附件自带 fileName(无扩展名补 .pdf),否则合格证编号-序号.pdf */
|
|
|
+ attachmentPdfName(idx) {
|
|
|
+ const item = this.attachmentPdfUrls[idx] || {};
|
|
|
+ if (item.name) {
|
|
|
+ return /\.pdf$/i.test(item.name) ? item.name : `${item.name}.pdf`;
|
|
|
+ }
|
|
|
+ return `${this.form.code || '合格证'}-${idx + 1}.pdf`;
|
|
|
+ },
|
|
|
+ /** 释放 Blob URL,避免内存泄漏 */
|
|
|
+ revokeAttachmentPdfUrls() {
|
|
|
+ this.attachmentPdfUrls.forEach((item) => {
|
|
|
+ if (item && item.url) {
|
|
|
+ URL.revokeObjectURL(item.url);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.attachmentPdfUrls = [];
|
|
|
+ },
|
|
|
add() {
|
|
|
this.$refs.listCoomModalRef.open();
|
|
|
},
|
|
|
@@ -178,6 +389,10 @@
|
|
|
},
|
|
|
close() {
|
|
|
this.QRvisible = false;
|
|
|
+ if (this.isAttachmentMode) {
|
|
|
+ this.revokeAttachmentPdfUrls();
|
|
|
+ this.isAttachmentMode = false;
|
|
|
+ }
|
|
|
this.form = {
|
|
|
reportTemplateCode: '',
|
|
|
reportTemplateId: '',
|
|
|
@@ -227,4 +442,40 @@
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
-<style lang="scss"></style>
|
|
|
+<style lang="scss">
|
|
|
+ .attachment-pdf-area {
|
|
|
+ max-height: 65vh;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ .attachment-pdf-item {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .attachment-pdf-title {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 4px 6px;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ .attachment-pdf-frame {
|
|
|
+ display: block;
|
|
|
+ width: 100%;
|
|
|
+ height: 65vh;
|
|
|
+ border: 1px solid #e6e6e6;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #525659;
|
|
|
+ }
|
|
|
+
|
|
|
+ .attachment-image {
|
|
|
+ display: block;
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 65vh;
|
|
|
+ margin: 0 auto;
|
|
|
+ border: 1px solid #e6e6e6;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|