Pārlūkot izejas kodu

二维码加密

695593266@qq.com 2 mēneši atpakaļ
vecāks
revīzija
22c58ac4a4

+ 10 - 0
src/api/produceOrder/index.js

@@ -295,3 +295,13 @@ export async function listPlanDotLine(data) {
   }
   return Promise.reject(new Error(res.data.message));
 }
+
+//删除工单
+export async function deleteWorkOrder(data) {
+  console.log('删除工单接口参数', data);
+  const res = await request.delete(`/mes/workorder/delete`, { data });
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 58 - 0
src/utils/crypto.js

@@ -0,0 +1,58 @@
+/**
+ * 条码/二维码内容编码工具(Base64Url)
+ *
+ * 设计:
+ * - 生成端用 Base64Url 对原文编码,并加 `~` 前缀标识;
+ * - 扫码端识别前缀后 Base64Url 解码还原原文;
+ * - 无前缀的历史/明文码直接透传,保证向后兼容;
+ * - 仅防止肉眼直接读取,不是真正的加密。
+ */
+
+const ENCODE_PREFIX = '~';
+
+function toBase64Url(str) {
+  const b64 = btoa(unescape(encodeURIComponent(str)));
+  return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
+}
+
+function fromBase64Url(str) {
+  let b64 = str.replace(/-/g, '+').replace(/_/g, '/');
+  while (b64.length % 4) b64 += '=';
+  return decodeURIComponent(escape(atob(b64)));
+}
+
+/**
+ * 编码原文:输出 `~` + Base64Url(原文)。
+ * @param {string} plain
+ * @returns {string}
+ */
+export function encryptCode(plain) {
+  if (plain == null || plain === '') return '';
+  try {
+    return ENCODE_PREFIX + toBase64Url(String(plain));
+  } catch (e) {
+    console.error('encryptCode failed:', e);
+    return String(plain);
+  }
+}
+
+/**
+ * 解码扫码内容:
+ * - 以 `~` 开头:Base64Url 解码还原原文;失败时回退原值;
+ * - 不以 `~` 开头:视为明文,直接返回(兼容历史码)。
+ * @param {string} code
+ * @returns {string}
+ */
+export function decryptCode(code) {
+  if (code == null || code === '') return '';
+  const str = String(code).trim();
+  if (!str.startsWith(ENCODE_PREFIX)) return str;
+  try {
+    return fromBase64Url(str.slice(ENCODE_PREFIX.length));
+  } catch (e) {
+    console.error('decryptCode failed:', e, code);
+    return str;
+  }
+}
+
+export default { encryptCode, decryptCode };

+ 96 - 0
src/views/batchRecord/components/printSelector.vue

@@ -0,0 +1,96 @@
+<template>
+  <div style="display: inline-block; margin-left: 8px">
+    <el-button
+      type="primary"
+      icon="el-icon-printer"
+      size="small"
+      @click="visible = true"
+    >
+      打印
+    </el-button>
+
+    <ele-modal
+      title="选择打印单据"
+      :visible.sync="visible"
+      v-if="visible"
+      width="60%"
+      append-to-body
+    >
+      <el-row :gutter="16">
+        <el-col
+          :span="8"
+          v-for="item in options"
+          :key="item.key"
+          style="margin-bottom: 16px"
+        >
+          <el-card
+            shadow="hover"
+            class="print-card"
+            @click.native="handleSelect(item)"
+          >
+            <div class="print-card-content">
+              <i
+                class="el-icon-document"
+                style="font-size: 32px; color: #409eff; margin-bottom: 8px"
+              ></i>
+              <div class="print-card-title">{{ item.label }}</div>
+              <div class="print-card-code" v-if="item.code">{{
+                item.code
+              }}</div>
+            </div>
+          </el-card>
+        </el-col>
+      </el-row>
+    </ele-modal>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'PrintSelector',
+    props: {
+      options: {
+        type: Array,
+        default: () => []
+      }
+    },
+    data() {
+      return {
+        visible: false
+      };
+    },
+    methods: {
+      handleSelect(item) {
+        this.visible = false;
+        this.$emit('select', item.key);
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  .print-card {
+    cursor: pointer;
+    transition: all 0.3s;
+    &:hover {
+      transform: translateY(-4px);
+    }
+  }
+  .print-card-content {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 16px 0;
+  }
+  .print-card-title {
+    font-size: 14px;
+    font-weight: bold;
+    color: #303133;
+    margin-bottom: 4px;
+    text-align: center;
+  }
+  .print-card-code {
+    font-size: 12px;
+    color: #909399;
+  }
+</style>

+ 89 - 8
src/views/batchRecord/components/tables/batchRecordTable.vue

@@ -7,6 +7,7 @@
       :columns="columns"
       :datasource="datasource"
       :cache-key="cacheKeyUrl"
+      :selection.sync="selection"
       autoAmendPage
       height="calc(86vh - 230px)"
     >
@@ -14,9 +15,6 @@
         <el-link type="primary" :underline="false" @click="goToDetail(row)">
           详情
         </el-link>
-        <!-- <el-link type="primary" :underline="false" @click="openPrint(row)">
-          打印
-        </el-link> -->
       </template>
       <template v-slot:code="{ row }">
         <el-link type="primary" @click="goToDetail(row)">{{
@@ -28,13 +26,20 @@
           fileName="生产记录"
           apiUrl="/mes/producetaskrulerecord/exportBatchRecordPage"
           :params="params"
-        ></exportButton
-      ></template>
+        ></exportButton>
+        <print-selector :options="printOptions" @select="handlePrint" />
+      </template>
     </ele-pro-table>
 
     <editModal ref="editModalRef"></editModal>
 
     <batchPecordPrint ref="batchPecordPrintRef"></batchPecordPrint>
+
+    <cleaning-record ref="cleaningRecord" />
+    <label-print-record ref="labelPrintRecord" />
+    <product-label-record ref="productLabelRecord" />
+    <packaging-record ref="packagingRecord" />
+    <process-inspection-record ref="processInspectionRecord" />
   </div>
 </template>
 
@@ -45,10 +50,26 @@
   import editModal from '../editModal.vue';
   import batchPecordPrint from './batchPecordPrint.vue';
   import exportButton from '@/components/upload/exportButton.vue';
+  import printSelector from '../printSelector.vue';
+  import cleaningRecord from '../../print/electrodeBatchRecord/cleaningRecord.vue';
+  import labelPrintRecord from '../../print/electrodeBatchRecord/labelPrintRecord.vue';
+  import productLabelRecord from '../../print/electrodeBatchRecord/productLabelRecord.vue';
+  import packagingRecord from '../../print/electrodeBatchRecord/packagingRecord.vue';
+  import processInspectionRecord from '../../print/electrodeBatchRecord/processInspectionRecord.vue';
 
   export default {
     mixins: [dictMixins, tableColumnsMixin],
-    components: { editModal, batchPecordPrint, exportButton },
+    components: {
+      editModal,
+      batchPecordPrint,
+      exportButton,
+      printSelector,
+      cleaningRecord,
+      labelPrintRecord,
+      productLabelRecord,
+      packagingRecord,
+      processInspectionRecord
+    },
     props: {
       tableQuery: {
         type: Object,
@@ -60,6 +81,13 @@
     data() {
       return {
         columns: [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            fixed: 'left'
+          },
           {
             width: 50,
             type: 'index',
@@ -154,7 +182,16 @@
           }
         ],
         cacheKeyUrl: 'mes-259231507-batchRecord-table',
-        params: {}
+        params: {},
+        tableData: [],
+        selection: [],
+        printOptions: [
+          { key: 'cleaningRecord', label: '清场清洁记录表', code: 'SC-033' },
+          { key: 'labelPrintRecord', label: '标签打印记录', code: 'SC-102' },
+          { key: 'productLabelRecord', label: '产品贴标记录', code: 'SC-050' },
+          { key: 'packagingRecord', label: '产品中外包装工序记录', code: 'SC-019' },
+          { key: 'processInspectionRecord', label: '过程检验记录', code: 'A0' }
+        ]
       };
     },
     computed: {
@@ -225,7 +262,11 @@
           recordOrder: 1
         };
         this.params = body;
-        return producetaskrulerecordPage(body);
+        const res = producetaskrulerecordPage(body);
+        Promise.resolve(res).then((data) => {
+          this.tableData = data?.list || data?.records || [];
+        });
+        return res;
       },
       search(where) {
         this.reload(where);
@@ -235,6 +276,46 @@
       },
       openPrint(row) {
         this.$refs.batchPecordPrintRef.open(row);
+      },
+      handlePrint(key) {
+        if (!this.selection.length) {
+          this.$message.warning('请先勾选要打印的生产记录');
+          return;
+        }
+        const r = this.selection[0];
+        const today = this.$util?.toDateString
+          ? this.$util.toDateString(new Date(), 'yyyy-MM-dd')
+          : new Date().toISOString().slice(0, 10);
+        const data = {
+          batchNo: this.tableQuery.batchNo,
+          productCode: this.tableQuery.productCode,
+          productName: r.productName || '',
+          specification: r.specification || '',
+          processOrderCode: r.workOrderCode || '',
+          processOrderNo: r.workOrderCode || '',
+          moCode: r.workOrderCode || '',
+          code: r.code || '',
+          recorder: r.createUserName || '',
+          checker: r.checkerName || '',
+          reviewer: r.reviewerName || '',
+          inspector: r.inspectorName || '',
+          operator: r.createUserName || '',
+          date: r.createTime || today,
+          createDate: r.createTime || today,
+          operateYear: today.slice(0, 4),
+          operateMonth: today.slice(5, 7),
+          operateDay: today.slice(8, 10),
+          startHour: '08',
+          startMinute: '00',
+          endHour: '17',
+          endMinute: '30',
+          equipmentCode: r.equipmentCode || '',
+          equipmentName: r.equipmentName || '',
+          remark: r.remark || ''
+        };
+        if (this.$refs[key]) {
+          this.$refs[key].open(data);
+        }
       }
     }
   };

+ 68 - 5
src/views/batchRecord/components/tables/materialTable.vue

@@ -7,6 +7,7 @@
       :columns="columns"
       :datasource="datasource"
       :cache-key="cacheKeyUrl"
+      :selection.sync="selection"
       autoAmendPage
       height="calc(86vh - 230px)"
     >
@@ -33,8 +34,9 @@
           fileName="领料单"
           apiUrl="/mes/pickorder/exportBatchRecordPage"
           :params="params"
-        ></exportButton
-      ></template>
+        ></exportButton>
+        <print-selector :options="printOptions" @select="handlePrint" />
+      </template>
     </ele-pro-table>
 
     <detailed
@@ -48,6 +50,9 @@
       v-if="selfDetailedShow && detailedObj"
       :detailedObj="detailedObj"
     ></selfDetailed>
+
+    <material-requisition ref="materialRequisition" />
+    <material-requisition-t-r ref="materialRequisitionTR" />
   </div>
 </template>
 
@@ -59,10 +64,20 @@
   import selfDetailed from '@/views/pick/pickApply/components/selfDetailed.vue';
   import exportButton from '@/components/upload/exportButton.vue';
   import { getDetails } from '@/api/pick/pickApply';
+  import printSelector from '../printSelector.vue';
+  import materialRequisition from '../../print/electrodeBatchRecord/materialRequisition.vue';
+  import materialRequisitionTR from '../../print/electrodeBatchRecord/materialRequisitionTR.vue';
 
   export default {
     mixins: [dictMixins, tableColumnsMixin],
-    components: { detailed, selfDetailed, exportButton },
+    components: {
+      detailed,
+      selfDetailed,
+      exportButton,
+      printSelector,
+      materialRequisition,
+      materialRequisitionTR
+    },
     props: {
       tableQuery: {
         type: Object,
@@ -74,6 +89,13 @@
     data() {
       return {
         columns: [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            fixed: 'left'
+          },
           {
             width: 50,
             type: 'index',
@@ -140,7 +162,13 @@
         selfDetailedShow: false,
         detailedObj: null,
         statusList: ['未领料', '领料中', '已出库', '已驳回'],
-        params: {}
+        params: {},
+        tableData: [],
+        selection: [],
+        printOptions: [
+          { key: 'materialRequisition', label: '领(退)料单', code: 'CG-015' },
+          { key: 'materialRequisitionTR', label: '领料单(畅捷通)', code: 'TR.D-015CG' }
+        ]
       };
     },
     computed: {
@@ -187,7 +215,11 @@
           ...this.tableQuery
         };
         this.params = body;
-        return batchRecordPage(body);
+        const res = batchRecordPage(body);
+        Promise.resolve(res).then((data) => {
+          this.tableData = data?.list || data?.records || [];
+        });
+        return res;
       },
       search(where) {
         this.reload(where);
@@ -208,6 +240,37 @@
       detailedClose() {
         this.detailedShow = false;
         this.selfDetailedShow = false;
+      },
+      handlePrint(key) {
+        if (!this.selection.length) {
+          this.$message.warning('请先勾选要打印的领料单');
+          return;
+        }
+        const r = this.selection[0];
+        const today = this.$util?.toDateString
+          ? this.$util.toDateString(new Date(), 'yyyy-MM-dd')
+          : new Date().toISOString().slice(0, 10);
+        const data = {
+          code: r.code || '',
+          date: r.createTime || today,
+          processOrderCode: r.workOrderCode || '',
+          processOrderNo: r.workOrderCode || '',
+          moCode: r.workOrderCode || '',
+          department: r.warehouseName || '生产部',
+          workshop: r.warehouseName || '',
+          applicant: r.createUserName || '',
+          handler: r.createUserName || '',
+          warehouseKeeper: r.warehouseKeeperName || '',
+          reviewer: r.reviewerName || '',
+          creator: r.createUserName || '',
+          batchNo: this.tableQuery.batchNo,
+          productCode: this.tableQuery.productCode,
+          remark: r.remark || '',
+          materialList: r.detailList || r.materialList || []
+        };
+        if (this.$refs[key]) {
+          this.$refs[key].open(data);
+        }
       }
     }
   };

+ 63 - 5
src/views/batchRecord/components/tables/qualityReportApproval.vue

@@ -16,6 +16,7 @@
         :pageSizes="[20, 30, 40, 50, 100]"
         @columns-change="handleColumnChange"
         :cache-key="cacheKeyUrl"
+        :selection.sync="selection"
         row-key="id"
       >
         <template v-slot:reportNumber="{ row }">
@@ -40,8 +41,9 @@
             fileName="质检报告"
             apiUrl="/qms/quality_work_order/exportBatchRecordPageByReport"
             :params="params"
-          ></exportButton
-        ></template>
+          ></exportButton>
+          <print-selector :options="printOptions" @select="handlePrint" />
+        </template>
       </ele-pro-table>
     </el-card>
     <ele-modal
@@ -61,6 +63,8 @@
         <el-button @click="detailVisible = false">关闭</el-button></div
       >
     </ele-modal>
+
+    <finished-product-report ref="finishedProductReport" />
   </div>
 </template>
 <script>
@@ -68,10 +72,12 @@
   import tabMixins from '@/mixins/tableColumnsMixin';
   import { getQualityReportApproval } from '@/api/qms/index.js';
   import exportButton from '@/components/upload/exportButton.vue';
+  import printSelector from '../printSelector.vue';
+  import finishedProductReport from '../../print/electrodeBatchRecord/finishedProductReport.vue';
 
   export default {
     mixins: [dictMixins, tabMixins],
-    components: { exportButton },
+    components: { exportButton, printSelector, finishedProductReport },
     props: {
       tableQuery: {
         type: Object,
@@ -88,6 +94,13 @@
         template: '',
         params: {},
         columns: [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            fixed: 'left'
+          },
           {
             type: 'index',
             columnKey: 'index',
@@ -204,7 +217,12 @@
           { value: 2, label: '已审核' },
           { value: 3, label: '审核不通过' },
           { value: 7, label: '作废' }
-        ]
+        ],
+        printOptions: [
+          { key: 'finishedProductReport', label: '成品检验报告', code: 'ZL-055' }
+        ],
+        tableData: [],
+        selection: []
       };
     },
     created() {},
@@ -248,7 +266,9 @@
           size: limit
         };
         this.params = body;
-        return getQualityReportApproval(body);
+        const data = await getQualityReportApproval(body);
+        this.tableData = data?.list || data?.records || [];
+        return data;
       },
       search(where) {
         this.$refs.table.reload({
@@ -276,6 +296,44 @@
       open(row) {
         this.template = row.reportTemplateJson.template;
         this.detailVisible = true;
+      },
+      openFinishedReport(row) {
+        this.$refs.finishedProductReport.open(row);
+      },
+      handlePrint(key) {
+        if (!this.selection.length) {
+          this.$message.warning('请先勾选要打印的质检报告');
+          return;
+        }
+        const r = this.selection[0];
+        const today = this.$util?.toDateString
+          ? this.$util.toDateString(new Date(), 'yyyy-MM-dd')
+          : new Date().toISOString().slice(0, 10);
+        const data = {
+          batchNo: this.tableQuery.batchNo,
+          productCode: this.tableQuery.productCode,
+          productName: r.productName || '',
+          specification: r.specification || '',
+          reportCode: r.reportNumber || '',
+          inspectionCode: r.code || '',
+          quantity: r.quantity || '',
+          sampleQuantity: r.sampleQuantity || '',
+          productQuantity: r.productQuantity || '',
+          productionDate: r.reportDate || today,
+          inspectionDate: r.reportDate || today,
+          reportDate: r.reportDate || today,
+          inspector: r.reportTemplateCreateUserName || '',
+          reviewer: r.reportApprovalUserName || '',
+          conclusion: r.reportApprovalStatus === 2 ? '合格' : '',
+          inspectionBasis: r.inspectionBasis || 'GB/T 国家标准/企业标准',
+          packagingSpec: r.packagingSpec || '',
+          validityPeriod: r.validityPeriod || '',
+          sterilizationBatchNo: r.sterilizationBatchNo || '',
+          remark: r.remark || ''
+        };
+        if (this.$refs[key]) {
+          this.$refs[key].open(data);
+        }
       }
     }
   };

+ 82 - 5
src/views/batchRecord/components/tables/qualityWorkOrderTable.vue

@@ -7,6 +7,7 @@
       :columns="columns"
       :datasource="datasource"
       :cache-key="cacheKeyUrl"
+      :selection.sync="selection"
       autoAmendPage
       height="calc(86vh - 230px)"
     >
@@ -26,9 +27,14 @@
           fileName="质检工单"
           apiUrl="/qms/quality_work_order/exportBatchRecordPage"
           :params="params"
-        ></exportButton
-      ></template>
+        ></exportButton>
+        <print-selector :options="printOptions" @select="handlePrint" />
+      </template>
     </ele-pro-table>
+
+    <product-inspection-report ref="productInspectionReport" />
+    <sampling-form ref="samplingForm" />
+    <finished-product-original-record ref="finishedProductOriginalRecord" />
   </div>
 </template>
 
@@ -37,10 +43,20 @@
   import tableColumnsMixin from '@/mixins/tableColumnsMixin';
   import { batchRecordPage } from '@/api/qualityWorkOrder/index.js';
   import exportButton from '@/components/upload/exportButton.vue';
+  import printSelector from '../printSelector.vue';
+  import productInspectionReport from '../../print/electrodeBatchRecord/productInspectionReport.vue';
+  import samplingForm from '../../print/electrodeBatchRecord/samplingForm.vue';
+  import finishedProductOriginalRecord from '../../print/electrodeBatchRecord/finishedProductOriginalRecord.vue';
 
   export default {
     mixins: [dictMixins, tableColumnsMixin],
-    components: { exportButton },
+    components: {
+      exportButton,
+      printSelector,
+      productInspectionReport,
+      samplingForm,
+      finishedProductOriginalRecord
+    },
     props: {
       tableQuery: {
         type: Object,
@@ -52,6 +68,13 @@
     data() {
       return {
         columns: [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            fixed: 'left'
+          },
           {
             width: 50,
             type: 'index',
@@ -149,7 +172,14 @@
           }
         ],
         cacheKeyUrl: 'mes-9221130-quality-work-order-table',
-        params: {}
+        params: {},
+        tableData: [],
+        selection: [],
+        printOptions: [
+          { key: 'productInspectionReport', label: '产品报检单', code: 'CG-038' },
+          { key: 'samplingForm', label: '取样单', code: 'ZL-041' },
+          { key: 'finishedProductOriginalRecord', label: '成品检验原始记录', code: '054-7' }
+        ]
       };
     },
     computed: {
@@ -203,7 +233,11 @@
           ...this.tableQuery
         };
         this.params = body;
-        return batchRecordPage(body);
+        const res = batchRecordPage(body);
+        Promise.resolve(res).then((data) => {
+          this.tableData = data?.list || data?.records || [];
+        });
+        return res;
       },
       search(where) {
         this.reload(where);
@@ -211,6 +245,49 @@
       goToDetail(row) {
         const path = `/page-qms/inspectionWork/details?id=${row.id}&path=&name=工单`;
         window.history.pushState(null, '', path);
+      },
+      handlePrint(key) {
+        if (!this.selection.length) {
+          this.$message.warning('请先勾选要打印的质检工单');
+          return;
+        }
+        const r = this.selection[0];
+        const today = this.$util?.toDateString
+          ? this.$util.toDateString(new Date(), 'yyyy-MM-dd')
+          : new Date().toISOString().slice(0, 10);
+        const data = {
+          batchNo: this.tableQuery.batchNo,
+          productCode: this.tableQuery.productCode,
+          productName: r.productName || '',
+          specification: r.specification || '',
+          inspectionCode: r.code || '',
+          processOrderCode: r.workOrderCode || '',
+          quantity: r.quantity || '',
+          sampleQuantity: r.sampleQuantity || '',
+          productQuantity: r.productQuantity || '',
+          submitQuantity: r.submitQuantity || '',
+          finishedQuantity: r.finishedQuantity || '',
+          productionDate: r.qualityTime || today,
+          inspectionDate: r.qualityTime || today,
+          reportDate: r.qualityTime || today,
+          sampleDate: r.qualityTime || today,
+          date: r.qualityTime || today,
+          inspector: r.qualityName || '',
+          sampler: r.qualityName || '',
+          checker: r.checkerName || '',
+          reviewer: r.reviewerName || '',
+          inspectionBasis: r.inspectionBasis || 'GB/T 国家标准/企业标准',
+          conclusion: r.status === 1 ? '合格' : '',
+          packagingSpec: r.packagingSpec || '',
+          validityPeriod: r.validityPeriod || '',
+          sterilizationBatchNo: r.sterilizationBatchNo || '',
+          sampleLocation: r.sampleLocation || '成品库',
+          sampleSource: r.sampleSource || '生产现场',
+          remark: r.remark || ''
+        };
+        if (this.$refs[key]) {
+          this.$refs[key].open(data);
+        }
       }
     }
   };

+ 63 - 5
src/views/batchRecord/components/tables/wmsOutInt.vue

@@ -7,6 +7,7 @@
       :columns="columns"
       :datasource="datasource"
       :cache-key="cacheKeyUrl"
+      :selection.sync="selection"
       autoAmendPage
       height="calc(86vh - 230px)"
     >
@@ -26,9 +27,12 @@
           fileName="入库单"
           apiUrl="/wms/outintwo/exportBatchRecordPage"
           :params="params"
-        ></exportButton
-      ></template>
+        ></exportButton>
+        <print-selector :options="printOptions" @select="handlePrint" />
+      </template>
     </ele-pro-table>
+
+    <finished-product-warehouse ref="finishedProductWarehouse" />
   </div>
 </template>
 
@@ -39,10 +43,12 @@
   import { sceneState } from '@/utils/dict/index';
   import { getBatchRecordPage } from '@/api/wms/index';
   import exportButton from '@/components/upload/exportButton.vue';
+  import printSelector from '../printSelector.vue';
+  import finishedProductWarehouse from '../../print/electrodeBatchRecord/finishedProductWarehouse.vue';
 
   export default {
     mixins: [dictMixins, tableColumnsMixin],
-    components: { exportButton },
+    components: { exportButton, printSelector, finishedProductWarehouse },
     props: {
       tableQuery: {
         type: Object,
@@ -55,6 +61,13 @@
       return {
         sceneState,
         columns: [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            fixed: 'left'
+          },
           {
             width: 50,
             type: 'index',
@@ -144,7 +157,12 @@
           }
         ],
         cacheKeyUrl: 'mes-259231507-mwmsoutint-table',
-        params: {}
+        params: {},
+        tableData: [],
+        selection: [],
+        printOptions: [
+          { key: 'finishedProductWarehouse', label: '产成品入库单(畅捷通)', code: 'TR.D-012CG' }
+        ]
       };
     },
     computed: {
@@ -194,7 +212,11 @@
           type: 1
         };
         this.params = body;
-        return getBatchRecordPage(body);
+        const res = getBatchRecordPage(body);
+        Promise.resolve(res).then((data) => {
+          this.tableData = data?.list || data?.records || [];
+        });
+        return res;
       },
       search(where) {
         console.log('where', where);
@@ -208,6 +230,42 @@
       goToDetail(row) {
         const path = `/page-wms/warehouseManagement/stockManagement/details?id=${row.id}&verifyStatus=${row.verifyStatus}`;
         window.history.pushState(null, '', path);
+      },
+      openPrint(row) {
+        this.$refs.finishedProductWarehouse.open(row);
+      },
+      handlePrint(key) {
+        if (!this.selection.length) {
+          this.$message.warning('请先勾选要打印的入库单');
+          return;
+        }
+        const r = this.selection[0];
+        const today = this.$util?.toDateString
+          ? this.$util.toDateString(new Date(), 'yyyy-MM-dd')
+          : new Date().toISOString().slice(0, 10);
+        const data = {
+          batchNo: this.tableQuery.batchNo,
+          productCode: this.tableQuery.productCode,
+          productName: r.productName || '',
+          specification: r.specification || '',
+          code: r.bizNo || '',
+          date: r.storageTime || today,
+          warehouse: r.warehouseName || '',
+          department: r.warehouseName || '',
+          processOrderCode: r.sourceBizNo || '',
+          processOrderNo: r.sourceBizNo || '',
+          moCode: r.sourceBizNo || '',
+          handler: r.createUserName || '',
+          receiver: r.createUserName || '',
+          reporter: r.createUserName || '',
+          creator: r.createUserName || '',
+          reviewer: r.reviewerName || '',
+          totalQuantity: r.totalQuantity || r.quantity || '',
+          remark: r.remark || ''
+        };
+        if (this.$refs[key]) {
+          this.$refs[key].open(data);
+        }
       }
     }
   };

+ 94 - 5
src/views/batchRecord/components/tables/workOrderTable.vue

@@ -7,6 +7,7 @@
       :columns="columns"
       :datasource="datasource"
       :cache-key="cacheKeyUrl"
+      :selection.sync="selection"
       autoAmendPage
       height="calc(86vh - 230px)"
     >
@@ -26,11 +27,17 @@
           fileName="生产工单"
           apiUrl="/mes/workorder/exportBatchRecordPage"
           :params="params"
-        ></exportButton
-      ></template>
+        ></exportButton>
+        <print-selector :options="printOptions" @select="handlePrint" />
+      </template>
     </ele-pro-table>
 
     <detailsPop ref="detailsRef"> </detailsPop>
+
+    <batch-record-cover ref="batchRecordCover" />
+    <production-order ref="productionOrder" />
+    <material-balance ref="materialBalance" />
+    <product-release-approval ref="productReleaseApproval" />
   </div>
 </template>
 
@@ -42,10 +49,23 @@
   import { getById } from '@/api/produceOrder/index';
   import { getAllProduceTaskByUsing } from '@/api/InTheSystem/index';
   import exportButton from '@/components/upload/exportButton.vue';
+  import printSelector from '../printSelector.vue';
+  import batchRecordCover from '../../print/electrodeBatchRecord/batchRecordCover.vue';
+  import productionOrder from '../../print/electrodeBatchRecord/productionOrder.vue';
+  import materialBalance from '../../print/electrodeBatchRecord/materialBalance.vue';
+  import productReleaseApproval from '../../print/electrodeBatchRecord/productReleaseApproval.vue';
 
   export default {
     mixins: [dictMixins, tableColumnsMixin],
-    components: { detailsPop, exportButton },
+    components: {
+      detailsPop,
+      exportButton,
+      printSelector,
+      batchRecordCover,
+      productionOrder,
+      materialBalance,
+      productReleaseApproval
+    },
     props: {
       tableQuery: {
         type: Object,
@@ -57,6 +77,13 @@
     data() {
       return {
         columns: [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            fixed: 'left'
+          },
           {
             width: 50,
             type: 'index',
@@ -159,7 +186,15 @@
           }
         ],
         taskList: [],
-        params: {}
+        params: {},
+        tableData: [],
+        selection: [],
+        printOptions: [
+          { key: 'batchRecordCover', label: '批记录封面', code: 'SC-034', component: 'batchRecordCover' },
+          { key: 'productionOrder', label: '生产指令', code: 'SC-040', component: 'productionOrder' },
+          { key: 'materialBalance', label: '物料平衡表', code: 'SC-041', component: 'materialBalance' },
+          { key: 'productReleaseApproval', label: '成品放行审批单', code: 'ZL-065', component: 'productReleaseApproval' }
+        ]
       };
     },
     computed: {
@@ -229,7 +264,11 @@
           ...this.tableQuery
         };
         this.params = body;
-        return batchRecordPage(body);
+        const res = batchRecordPage(body);
+        Promise.resolve(res).then((data) => {
+          this.tableData = data?.list || data?.records || [];
+        });
+        return res;
       },
       search(where) {
         this.reload(where);
@@ -239,6 +278,56 @@
         const data = await getById(row.id);
         this.$refs.detailsRef.open(data);
       },
+      async handlePrint(key) {
+        if (!this.selection.length) {
+          this.$message.warning('请先勾选要打印的工单');
+          return;
+        }
+        const row = this.selection[0];
+        const data = this.buildPrintData(row);
+        if (this.$refs[key]) {
+          this.$refs[key].open(data);
+        }
+      },
+      buildPrintData(row) {
+        const tq = this.tableQuery || {};
+        const today = this.$util?.toDateString
+          ? this.$util.toDateString(new Date(), 'yyyy-MM-dd')
+          : new Date().toISOString().slice(0, 10);
+        return {
+          productCode: tq.productCode || row.productCode || '',
+          productName: row.productName || '',
+          specification: row.specification || '',
+          unit: row.unit || '',
+          batchNo: tq.batchNo || row.batchNo || '',
+          batchQuantity: row.planQuantity || row.batchQuantity || '',
+          planQuantity: row.planQuantity || '',
+          startDate: row.startTime || row.planStartTime || '',
+          endDate: row.endTime || row.planCompleteTime || '',
+          productionDate: row.startTime || today,
+          expiryDate: row.expiryDate || '',
+          department: row.departmentName || row.department || '生产部',
+          workshop: row.workshopName || row.workshop || '生产车间',
+          storageCondition: row.storageCondition || '常温避光保存',
+          orderCode: row.code || '',
+          processOrderCode: row.code || '',
+          processOrderNo: row.code || '',
+          moCode: row.code || '',
+          code: row.code || '',
+          date: today,
+          issueDate: today,
+          createDate: today,
+          reviewDate: today,
+          approveDate: today,
+          creator: row.createUserName || '',
+          reviewer: row.reviewerName || '',
+          approver: row.approverName || '',
+          remark: row.remark || '',
+          materialRemark: row.remark || '',
+          packagingRequirement: row.packagingRequirement || '按工艺要求执行',
+          materialList: row.materialList || []
+        };
+      },
       // 工序
       async getproduceTask() {
         // 查询详情

+ 2 - 1
src/views/batchRecord/index.vue

@@ -195,6 +195,7 @@
   import tableColumnsMixin from '@/mixins/tableColumnsMixin';
   import seekPage from '@/components/common/seekPage.vue';
   import { getAllProductInWorkOrder } from '@/api/produce/workOrder';
+  import { decryptCode } from '@/utils/crypto';
   import workOrderTable from './components/tables/workOrderTable.vue';
   import materialTable from './components/tables/materialTable.vue';
   import materialReturnTable from './components/tables/materialReturnTable.vue';
@@ -442,7 +443,7 @@
               target.value = '';
               target.dispatchEvent(new Event('input', { bubbles: true }));
             }
-            this.processScanResult(code);
+            this.processScanResult(decryptCode(code));
           } else {
             this.scanBuffer = '';
           }

+ 24 - 19
src/views/produce/components/encodingDialog/index.vue

@@ -24,8 +24,12 @@
             <tr>
               <td class="label">产品编码</td>
               <td class="value">{{ item.productCode }}</td>
-              <td class="label">产品名称</td>
-              <td class="value">{{ item.productName }}</td>
+              <td class="label">{{
+                sourceType == 'jobBom' ? '单件码' : ''
+              }}</td>
+              <td class="value">{{
+                sourceType == 'jobBom' ? item.engrave : ''
+              }}</td>
               <td class="qrcode-cell" rowspan="3">
                 <img
                   v-if="item.qrCodeUrl"
@@ -44,7 +48,9 @@
             <tr>
               <td class="label">生产批号</td>
               <td class="value">{{ item.batchNo }}</td>
-              <td class="label">本批数量</td>
+              <td class="label">{{
+                sourceType == 'jobBom' ? '数量' : '本批数量'
+              }}</td>
               <td class="value">{{ item.feedQuantity }}</td>
             </tr>
           </table>
@@ -80,6 +86,7 @@
 <script>
   import QRCode from 'qrcode';
   import JsBarcode from 'jsbarcode';
+  import { encryptCode } from '@/utils/crypto';
 
   export default {
     name: 'EncodingDialog',
@@ -111,12 +118,8 @@
         this.batchList = this.workOrderList.map((item) => ({
           productCode: item.topCategoryCode || '',
           productName: item.topCategoryName || '',
-          partCode: isJobBom
-            ? item.categoryCode || ''
-            : item.productCode || '',
-          partName: isJobBom
-            ? item.name || ''
-            : item.productName || '',
+          partCode: isJobBom ? item.categoryCode || '' : item.productCode || '',
+          partName: isJobBom ? item.name || '' : item.productName || '',
           batchNo: item.batchNo || '',
           feedQuantity: isJobBom
             ? (item.feedQuantity || item.quantity || item.planQuantity || '') +
@@ -135,7 +138,8 @@
           model: isJobBom
             ? item.modelType || item.model || ''
             : item.model || '',
-          specification: item.specification || ''
+          specification: item.specification || '',
+          engrave: item.extInfo && item.extInfo.engrave
         }));
 
         if (this.mode === 'qrcode') {
@@ -150,7 +154,7 @@
       generateQRCodes() {
         this.batchList.forEach((item, index) => {
           const content = item.codeStr || item.productCode || String(index);
-          QRCode.toDataURL(content, { width: 240, margin: 1 })
+          QRCode.toDataURL(encryptCode(content), { width: 240, margin: 1 })
             .then((url) => {
               this.$set(this.batchList[index], 'qrCodeUrl', url);
             })
@@ -166,7 +170,7 @@
           const canvasArr = this.$refs[refKey];
           if (canvasArr && canvasArr[0]) {
             const content = item.codeStr || item.productCode || String(index);
-            JsBarcode(canvasArr[0], content, {
+            JsBarcode(canvasArr[0], encryptCode(content), {
               format: 'CODE128',
               width: 5,
               height: 120,
@@ -213,7 +217,7 @@
               const img = document.createElement('img');
               img.src = canvas.toDataURL('image/png');
               img.style.width = '50mm';
-              img.style.height = 'auto';
+              img.style.height = '12mm';
               imgPlaceholders[i].parentNode.replaceChild(
                 img,
                 imgPlaceholders[i]
@@ -273,14 +277,15 @@
                 }
                 .card-table .label {
                   color: #333;
-                  width: 14mm;
+                  width: 10mm;
                   font-size: 5.5pt;
-                  text-align: right;
-                  word-break: keep-all;
-                  white-space: nowrap;
-                  padding-right: 1mm;
+                  text-align: left;
+                  word-break: break-all;
+                  white-space: normal;
+                  padding-left: 1mm;
                 }
                 .card-table .value {
+                  width: 15mm;
                   font-size: 6.5pt;
                   overflow: hidden;
                   text-overflow: ellipsis;
@@ -312,7 +317,7 @@
                 }
                 .barcode-info p { margin: 0.5mm 0; font-size: 8pt; color: #333; }
                 .barcode-wrap { margin: 1mm auto; text-align: center; }
-                .barcode-wrap img { width: 50mm; height: auto; image-rendering: pixelated; }
+                .barcode-wrap img { width: 50mm; height: 20mm; image-rendering: pixelated; }
                 .barcode-text { font-size: 7pt; color: #606266; margin-top: 0.5mm; }
               </style>
             </head>

+ 3 - 2
src/views/produce/components/feeding/components/batchProductsBom.vue

@@ -130,6 +130,7 @@
   import tabMixins from '@/mixins/tableColumnsMixin';
   import { splitBatch } from '@/api/produce/feeding';
   import EncodingDialog from '../../encodingDialog/index.vue';
+  import { decryptCode } from '@/utils/crypto';
   export default {
     name: 'productsBom',
     mixins: [tabMixins],
@@ -379,7 +380,7 @@
           .trim();
         console.log('扫码粘贴事件:', text);
         if (text.length >= 3) {
-          this.matchBarcode(text);
+          this.matchBarcode(decryptCode(text));
         }
       },
       handleBarcodeScan(e) {
@@ -387,7 +388,7 @@
         if (e.key === 'Enter') {
           if (this.scanBuffer.length >= 3) {
             console.log('扫码枪扫描结果:', this.scanBuffer);
-            this.matchBarcode(this.scanBuffer);
+            this.matchBarcode(decryptCode(this.scanBuffer));
           }
           this.scanBuffer = '';
           return;

+ 3 - 2
src/views/produce/components/jobBooking/components/batchSemiProductJobBom.vue

@@ -766,6 +766,7 @@
   import juRenPackOne from '../../juRenPackOne.vue';
   import juRenPack from '../../juRenPack.vue';
   import EncodingDialog from '../../encodingDialog/index.vue';
+  import { decryptCode } from '@/utils/crypto';
   // import teRuiPrintOne from '../../teRuiPrintOne.vue';
   // import teRuiPrintTwo from '../../teRuiPrintTwo.vue';
   // import teRuiPrintThree from '../../teRuiPrintThree.vue';
@@ -1393,7 +1394,7 @@
           .trim();
         console.log('扫码粘贴事件:', text);
         if (text.length >= 3) {
-          this.matchBarcode(text);
+          this.matchBarcode(decryptCode(text));
         }
       },
       handleBarcodeScan(e) {
@@ -1401,7 +1402,7 @@
         if (e.key === 'Enter') {
           if (this.scanBuffer.length >= 3) {
             console.log('扫码枪扫描结果:', this.scanBuffer);
-            this.matchBarcode(this.scanBuffer);
+            this.matchBarcode(decryptCode(this.scanBuffer));
           }
           this.scanBuffer = '';
           return;

+ 3 - 2
src/views/produce/components/jobBooking/components/semiProductJobBom.vue

@@ -587,6 +587,7 @@
   import { parameterGetByCode } from '@/api/system/dictionary-data';
   import { juRenPrint, isJuRen } from '@/api/produce';
   import EncodingDialog from '../../encodingDialog/index.vue';
+  import { decryptCode } from '@/utils/crypto';
 
   export default {
     name: 'semiProductJobBom',
@@ -1111,7 +1112,7 @@
 
         console.log('扫码粘贴事件:', text);
         if (text.length >= 3) {
-          this.matchBarcode(text);
+          this.matchBarcode(decryptCode(text));
         }
       },
       handleBarcodeScan(e) {
@@ -1119,7 +1120,7 @@
         if (e.key === 'Enter') {
           console.log('扫码结束,结果:', this.scanBuffer);
           if (this.scanBuffer.length >= 3) {
-            this.matchBarcode(this.scanBuffer);
+            this.matchBarcode(decryptCode(this.scanBuffer));
           }
           this.scanBuffer = '';
           return;

+ 30 - 3
src/views/produceOrder/index.vue

@@ -291,6 +291,17 @@
             @click="toRelease(row)"
             >放行单</el-link
           >
+
+          <el-link
+            v-if="
+              tabValue == '10' && $hasPermission('produce:workorder:delete')
+            "
+            type="danger"
+            :underline="false"
+            @click="toDelete(row)"
+          >
+            删除
+          </el-link>
         </template>
       </ele-pro-table>
     </el-card>
@@ -350,7 +361,8 @@
     getTaskIdByInstanceId,
     updateStatusPause,
     updateStatusTerminate,
-    getMyPage
+    getMyPage,
+    deleteWorkOrder
   } from '@/api/produceOrder/index.js';
   import xlhView from './components/xlhView.vue';
   import { fieldModel } from '@/api/produceWord/index.js';
@@ -949,8 +961,8 @@
         };
       },
       clientEnvironmentId() {
-        return this.$store.state.user.info.clientEnvironmentId;
-        // return 9;
+        // return this.$store.state.user.info.clientEnvironmentId;
+        return 9;
       }
     },
     watch: {
@@ -1546,6 +1558,21 @@
       toRelease(row) {
         this.$refs.checkAddRef?.open('add', '1', [row]);
       },
+      // 删除工单
+      toDelete(row) {
+        this.$confirm(`是否要删除工单【${row.code}】?`, '提醒', {
+          confirmButtonText: '确认',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+          .then(() => {
+            deleteWorkOrder([row.id]).then(() => {
+              this.$message.success('删除成功');
+              this.reload();
+            });
+          })
+          .catch(() => {});
+      },
       // 确定放行
       checkAddConfirm(type, data) {
         this.$refs.checkDetailsRef?.open(type, data);

+ 30 - 27
src/views/produceOrder/printFlowCard.vue

@@ -27,7 +27,7 @@
           <thead>
             <tr class="title-row">
               <th colspan="9" class="title-cell">
-                <div class="flow-card-title">工序流转卡</div>
+                <div class="flow-card-title"><span>工序流转卡</span></div>
               </th>
             </tr>
             <tr class="info-row">
@@ -46,7 +46,9 @@
               <td colspan="2" class="label">产品批号(编号)</td>
               <td colspan="3" class="value">{{ card.batchNo }}</td>
               <td colspan="2" class="label">原材料本厂合格证号</td>
-              <td colspan="2" class="value">{{ card.materialFactoryCertNo }}</td>
+              <td colspan="2" class="value">{{
+                card.materialFactoryCertNo
+              }}</td>
             </tr>
             <tr class="info-row">
               <td colspan="2" class="label">原材料炉(批)号</td>
@@ -106,7 +108,6 @@
     data() {
       return {
         QRvisible: false,
-        rowCount: 25,
         printList: []
       };
     },
@@ -172,7 +173,7 @@
       },
       getRows(card) {
         const list = Array.isArray(card.taskList) ? card.taskList.slice() : [];
-        const rows = list.map((item, i) => ({
+        return list.map((item, i) => ({
           taskNo: item.taskNo || i + 1,
           taskName: item.taskName || '',
           inNum: item.inNum || '',
@@ -183,20 +184,6 @@
           date: item.date || '',
           remark: item.remark || ''
         }));
-        while (rows.length < this.rowCount) {
-          rows.push({
-            taskNo: '',
-            taskName: '',
-            inNum: '',
-            leader: '',
-            outNum: '',
-            reworkNum: '',
-            scrapNum: '',
-            date: '',
-            remark: ''
-          });
-        }
-        return rows;
       },
       print() {
         const printSection = document.getElementById('printSection');
@@ -226,12 +213,17 @@
   .flow-card-page:last-child { page-break-after: auto; }
   .flow-card-title {
     text-align: center;
+    margin: 0;
+  }
+  .flow-card-title span {
+    display: inline-block;
     font-size: 26px;
     font-weight: bold;
     letter-spacing: 30px;
-    text-indent: 30px;
-    text-decoration: underline;
-    margin: 0;
+    padding-left: 30px;
+    margin-right: -30px;
+    padding-bottom: 4px;
+    border-bottom: 2px solid #000;
   }
   table {
     width: 100%;
@@ -239,11 +231,12 @@
     table-layout: fixed;
   }
   .flow-card-detail thead { display: table-header-group; }
-  .flow-card-detail tbody tr { page-break-inside: avoid; }
+  .flow-card-detail thead tr { page-break-inside: avoid; break-inside: avoid; }
+  .flow-card-detail tbody tr { page-break-inside: avoid; break-inside: avoid; }
 
   .flow-card-detail .title-cell {
     border: none;
-    padding: 20mm 0 5mm 0;
+    padding: 10mm 0 4mm 0;
     text-align: center;
     font-weight: normal;
     height: auto;
@@ -286,6 +279,7 @@
     font-size: 15px;
   }
   .flow-card-footer .footer-item { width: 50%; }
+  .flow-card-footer .footer-item:first-child { padding-left: 13mm; }
 </style>
 </head>
 <body>
@@ -323,12 +317,17 @@ ${printSection.innerHTML}
 
   .flow-card-title {
     text-align: center;
+    margin: 0;
+  }
+  .flow-card-title span {
+    display: inline-block;
     font-size: 26px;
     font-weight: bold;
     letter-spacing: 60px;
-    text-indent: 60px;
-    text-decoration: underline;
-    margin: 0;
+    padding-left: 60px;
+    margin-right: -60px;
+    padding-bottom: 4px;
+    border-bottom: 2px solid #000;
   }
 
   .flow-card-detail {
@@ -346,7 +345,7 @@ ${printSection.innerHTML}
 
     .title-cell {
       border: none;
-      padding: 20mm 0 5mm 0;
+      padding: 10mm 0 4mm 0;
       text-align: center;
       font-weight: normal;
       height: auto;
@@ -404,6 +403,10 @@ ${printSection.innerHTML}
 
     .footer-item {
       width: 50%;
+
+      &:first-child {
+        padding-left: 13mm;
+      }
     }
   }
 </style>