Browse Source

Merge branch 'dev' into test

wuzh 1 day ago
parent
commit
dc6e156e98

+ 2 - 9
src/api/aps/index.js

@@ -59,16 +59,9 @@ export async function produceOrder(params) {
 }
 /**
  * 入库申请
+ * 说明:处置入库单已独立为 /qms/disposalstorage/page,原 /mes/applystorage/page 仅返回制造执行来源单据,
+ * 此处不再保留指向 MES 入库申请列表的死函数。
  */
-export async function warehousing(data) {
-  const res = await request.get('/mes/applystorage/page', {
-    params: data
-  });
-  if (res.data.code == 0) {
-    return res.data.data;
-  }
-  return Promise.reject(new Error(res.data.message));
-}
 
 // 工厂列表(兼容 { code, data } 包装与直接返回数组两种后端实现)
 export async function getFactoryList(params) {

+ 288 - 37
src/views/certificateManagement/components/certificate.vue

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

+ 2 - 0
src/views/certificateManagement/components/listCoom.vue

@@ -382,6 +382,8 @@ import exportButton from '@/components/upload/exportButton.vue';
                 ? '自动生成'
                 : row.generateType == 2
                 ? '手动新增'
+                : row.generateType == 3
+                ? '外部推送'
                 : '';
             },
             showOverflowTooltip: true

+ 1 - 1
src/views/unqualifiedProduct/unqualifiedList/probationalGoods/index.vue

@@ -31,7 +31,7 @@
             type="primary"
             :underline="false"
             icon="el-icon-edit"
-            v-if="row.status == 0"
+            v-if="row.status == 1"
             @click="disposeList(row)"
           >
             处置