695593266@qq.com 1 месяц назад
Родитель
Сommit
2486e08d87

+ 9 - 0
src/api/pickorder/index.js

@@ -8,3 +8,12 @@ export async function batchRecordPage(body) {
   }
   return Promise.reject(new Error(res.data.message));
 }
+
+// 打印前查询
+export async function queryPrint(body) {
+  const res = await request.post(`/mes/pickorder/queryPrint`, body);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 9 - 0
src/api/qualityWorkOrder/index.js

@@ -11,3 +11,12 @@ export async function batchRecordPage(body) {
   }
   return Promise.reject(new Error(res.data.message));
 }
+
+// 打印前查询
+export async function queryPrint(body) {
+  const res = await request.post(`/qms/quality_work_order/queryPrint`, body);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 9 - 0
src/api/wms/index.js

@@ -18,3 +18,12 @@ export async function getBatchRecordPage(params) {
   }
   return Promise.reject(new Error(res.data.message));
 }
+
+// /wms/outintwo/printInbound post
+export async function printInbound(ids) {
+  const res = await request.post(`/wms/outintwo/printInbound`, ids);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}

+ 9 - 0
src/api/workOrderList/index.js

@@ -195,6 +195,15 @@ export async function batchRecordPage(body) {
   return Promise.reject(new Error(res.data.message));
 }
 
+// 打印前查询
+export async function queryWorkOrderPrint(body) {
+  const res = await request.post('/mes/workorder/queryWorkOrderPrint', body);
+  if (res.data.code == 0) {
+    return res.data.data;
+  }
+  return Promise.reject(new Error(res.data.message));
+}
+
 // 任务列表 班组任务
 export async function pageByCurrentCurrentUserTeam(data) {
   const res = await request.get('/aps/assign/pageByCurrentUserTeam', {

+ 107 - 22
src/views/batchRecord/components/tables/materialTable.vue

@@ -59,7 +59,7 @@
 <script>
   import dictMixins from '@/mixins/dictMixins';
   import tableColumnsMixin from '@/mixins/tableColumnsMixin';
-  import { batchRecordPage } from '@/api/pickorder/index';
+  import { batchRecordPage, queryPrint } from '@/api/pickorder/index';
   import detailed from '@/views/produce/components/picking/detailed.vue';
   import selfDetailed from '@/views/pick/pickApply/components/selfDetailed.vue';
   import exportButton from '@/components/upload/exportButton.vue';
@@ -165,9 +165,17 @@
         params: {},
         tableData: [],
         selection: [],
+        printTypeMap: {
+          materialRequisition: 'PICK_ORDER',
+          materialRequisitionTR: 'CHANTIJTONG_PICK_ORDER'
+        },
         printOptions: [
           { key: 'materialRequisition', label: '领(退)料单', code: 'CG-015' },
-          { key: 'materialRequisitionTR', label: '领料单(畅捷通)', code: 'TR.D-015CG' }
+          {
+            key: 'materialRequisitionTR',
+            label: '领料单(畅捷通)',
+            code: 'TR.D-015CG'
+          }
         ]
       };
     },
@@ -241,36 +249,113 @@
         this.detailedShow = false;
         this.selfDetailedShow = false;
       },
-      handlePrint(key) {
+      async handlePrint(key) {
         if (!this.selection.length) {
           this.$message.warning('请先勾选要打印的领料单');
           return;
         }
-        const r = this.selection[0];
+        const printType = this.printTypeMap[key];
+        if (!printType) {
+          this.$message.warning('暂不支持该打印单据');
+          return;
+        }
+        try {
+          const ids = this.selection.map((item) => item.id).filter(Boolean);
+          const res = await queryPrint({ ids, printType });
+          const data = this.buildPrintData(res);
+          if (!data) {
+            this.$message.warning('未查询到可打印的领料单数据');
+            return;
+          }
+          if (this.$refs[key]) {
+            this.$refs[key].open(data);
+          }
+        } catch (error) {
+          this.$message.error(error.message || '查询打印数据失败');
+        }
+      },
+      buildPrintData(res) {
+        const list = Array.isArray(res?.pickOrderList)
+          ? res.pickOrderList
+          : Array.isArray(res)
+          ? res
+          : [];
+        const r = list[0];
+        if (!r) {
+          return null;
+        }
         const today = this.$util?.toDateString
           ? this.$util.toDateString(new Date(), 'yyyy-MM-dd')
           : new Date().toISOString().slice(0, 10);
-        const data = {
+        const materialList = list.length
+          ? list.flatMap((item) => this.buildMaterialList(item))
+          : this.buildMaterialList(r);
+        return {
           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,
+          date: r.executorTime || r.createTime || today,
+          processOrderCode: r.joinWorkOrderCode || r.workOrderCode || '',
+          processOrderNo: r.joinWorkOrderCode || r.workOrderCode || '',
+          moCode: r.joinWorkOrderCode || r.workOrderCode || '',
+          department: r.joinWarehouseName || r.warehouseName || '生产部',
+          workshop: r.joinWarehouseName || r.warehouseName || '',
+          applicant: r.executorName || r.createUserName || '',
+          handler: r.executorName || r.createUserName || '',
+          warehouseKeeper:
+            r.warehouseKeeperName ||
+            r.warehouseLeaderName ||
+            materialList.find((item) => item.warehouseLeaderName)
+              ?.warehouseLeaderName ||
+            '',
+          reviewer: r.joinReviewerName || r.reviewerName || '',
+          creator: r.createUserName || r.executorName || '',
+          batchNo:
+            r.batchNo ||
+            materialList.find((item) => item.batchNo)?.batchNo ||
+            this.tableQuery.batchNo ||
+            '',
+          productCode: r.categoryCode || this.tableQuery.productCode || '',
           remark: r.remark || '',
-          materialList: r.detailList || r.materialList || []
+          materialList
         };
-        if (this.$refs[key]) {
-          this.$refs[key].open(data);
-        }
+      },
+      buildMaterialList(order) {
+        const detailList = [];
+        const collectDetail = (item) => {
+          if (!item) {
+            return;
+          }
+          if (Array.isArray(item.detailList)) {
+            detailList.push(...item.detailList);
+          }
+          if (Array.isArray(item.materialList)) {
+            detailList.push(...item.materialList);
+          }
+          if (Array.isArray(item.orderInfoList)) {
+            item.orderInfoList.forEach((orderInfo) => {
+              detailList.push(...(orderInfo.bomDetailDTOS || []));
+              detailList.push(...(orderInfo.instanceList || []));
+            });
+          }
+          if (Array.isArray(item.childList)) {
+            item.childList.forEach(collectDetail);
+          }
+        };
+        collectDetail(order);
+        return detailList.map((item) => ({
+          ...item,
+          materialCode: item.materialCode || item.categoryCode || '',
+          materialName: item.materialName || item.categoryName || '',
+          specification:
+            item.specification || item.specifications || item.model || '',
+          batchNo: item.batchNo || '',
+          unit: item.unit || '',
+          quantity:
+            item.quantity || item.outQuantity || item.demandQuantity || '',
+          reason: item.reason || '',
+          purpose: item.purpose || item.taskName || '',
+          remark: item.remark || '',
+          warehouseLeaderName: item.warehouseLeaderName || ''
+        }));
       }
     }
   };

+ 761 - 0
src/views/batchRecord/components/tables/pendingBatchRecordTable.vue

@@ -0,0 +1,761 @@
+<template>
+  <div>
+    <seek-page :seekList="seekList" @search="search"></seek-page>
+    <ele-pro-table
+      ref="table"
+      row-key="id"
+      :columns="columns"
+      :datasource="datasource"
+      :cache-key="cacheKeyUrl"
+      :selection.sync="selection"
+      autoAmendPage
+      height="calc(86vh - 230px)"
+    >
+      <template v-slot:toolbar>
+        <print-selector
+          v-if="printOptions.length"
+          :options="printOptions"
+          @select="handlePrint"
+        />
+      </template>
+    </ele-pro-table>
+
+    <unqualified-product-review ref="unqualifiedProductReviewRef" />
+    <release-order-print ref="releaseOrderPrintRef" />
+  </div>
+</template>
+
+<script>
+  import printSelector from '../printSelector.vue';
+  import unqualifiedProductReview from '../../print/electrodeBatchRecord/unqualifiedProductReview.vue';
+  import releaseOrderPrint from '../../print/electrodeBatchRecord/releaseOrderPrint.vue';
+  import { checklistrecordPage } from '@/api/checklistrecord/index';
+  import { samplingRecordsPage } from '@/api/samplingRecords';
+  import { getList as unacceptedProductPage } from '@/api/unacceptedProduct/index';
+  import { getByCode } from '@/api/system/dictionary-data';
+  import { reviewStatus } from '@/enum/dict';
+  import { recordingMethodList } from '@/utils/util.js';
+  import dictMixins from '@/mixins/dictMixins';
+
+  export default {
+    name: 'pendingBatchRecordTable',
+    mixins: [dictMixins],
+    components: {
+      printSelector,
+      unqualifiedProductReview,
+      releaseOrderPrint
+    },
+    props: {
+      tableQuery: {
+        type: Object,
+        default: () => {
+          return {};
+        }
+      },
+      tableType: {
+        type: String,
+        default: ''
+      }
+    },
+    data() {
+      return {
+        selection: [],
+        unacceptedProductPrintOptions: [
+          {
+            key: 'unqualifiedProductReview',
+            label: '不合格品通知及评审处置表',
+            code: 'TR.D-060/ZL'
+          }
+        ],
+        releaseOrderPrintOptions: [
+          {
+            key: 'releaseOrderPrint',
+            label: '放行单',
+            code: '放行单详情'
+          }
+        ],
+        disposeList: []
+      };
+    },
+    computed: {
+      isSample() {
+        return this.tableType === 'sample';
+      },
+      isUnacceptedProduct() {
+        return this.tableType === 'unacceptedProduct';
+      },
+      isReleaseOrder() {
+        return this.tableType === 'releaseOrder';
+      },
+      cacheKeyUrl() {
+        return `batchRecord-${this.tableType || 'pending'}-table`;
+      },
+      printOptions() {
+        if (this.isReleaseOrder) {
+          return this.releaseOrderPrintOptions;
+        }
+        if (this.isUnacceptedProduct) {
+          return this.unacceptedProductPrintOptions;
+        }
+        return [];
+      },
+      seekList() {
+        if (this.isReleaseOrder) {
+          return [
+            {
+              label: '放行单编码:',
+              value: 'code',
+              type: 'input',
+              placeholder: '请输入'
+            },
+            {
+              label: '放行单名称:',
+              value: 'name',
+              type: 'input',
+              placeholder: '请输入'
+            },
+            {
+              label: '审核状态:',
+              value: 'approvalStatus',
+              type: 'select',
+              placeholder: '请选择',
+              planList: [
+                { value: 0, label: '未提交' },
+                { value: 1, label: '审核中' },
+                { value: 2, label: '审核通过' },
+                { value: 3, label: '审核不通过' }
+              ]
+            }
+          ];
+        }
+
+        if (this.isSample) {
+          return [
+            {
+              label: '检测工单编码:',
+              value: 'qualityWorkOrderCode',
+              type: 'input',
+              placeholder: '请输入',
+              labelWidth: 120
+            },
+            {
+              label: '取样记录编码:',
+              value: 'code',
+              type: 'input',
+              placeholder: '请输入',
+              labelWidth: 120
+            },
+            {
+              label: '类型:',
+              value: 'qualityType',
+              type: 'DictSelection',
+              dictName: '质检计划类型',
+              placeholder: '请选择'
+            },
+            {
+              label: '记录方法:',
+              value: 'recordingMethod',
+              type: 'select',
+              placeholder: '请选择',
+              planList: recordingMethodList
+            }
+          ];
+        }
+
+        return [
+          {
+            label: '编码:',
+            value: 'unqualifiedProductsCode',
+            type: 'input',
+            placeholder: '请输入'
+          },
+          {
+            label: '来源编码:',
+            value: 'sourceCode',
+            type: 'input',
+            placeholder: '请输入'
+          },
+          {
+            label: '物品编码:',
+            value: 'categoryCode',
+            type: 'input',
+            placeholder: '请输入'
+          },
+          {
+            label: '状态:',
+            value: 'status',
+            type: 'select',
+            placeholder: '请选择',
+            planList: [
+              { value: 0, label: '待处置' },
+              { value: 1, label: '部分处置' },
+              { value: 2, label: '处置完成' }
+            ]
+          }
+        ];
+      },
+      columns() {
+        if (this.isReleaseOrder) {
+          return [
+            {
+              width: 45,
+              type: 'selection',
+              columnKey: 'selection',
+              align: 'center',
+              fixed: 'left'
+            },
+            {
+              width: 50,
+              type: 'index',
+              columnKey: 'index',
+              label: '序号',
+              align: 'center'
+            },
+            {
+              prop: 'code',
+              label: '放行单编码',
+              align: 'center',
+              minWidth: 130,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'name',
+              label: '放行单名称',
+              align: 'center',
+              minWidth: 150,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'checklistType',
+              label: '放行类型',
+              align: 'center',
+              minWidth: 120,
+              showOverflowTooltip: true,
+              formatter: (row) =>
+                this.getDictValue('放行类型', row.checklistType + '')
+            },
+            {
+              prop: 'productCode',
+              label: '产品编码',
+              align: 'center',
+              minWidth: 140,
+              showOverflowTooltip: true,
+              formatter: (row) => this.getOrderField(row, 'productCode')
+            },
+            {
+              prop: 'productName',
+              label: '产品名称',
+              align: 'center',
+              minWidth: 150,
+              showOverflowTooltip: true,
+              formatter: (row) => this.getOrderField(row, 'productName')
+            },
+            {
+              prop: 'batchNo',
+              label: '批次号',
+              align: 'center',
+              minWidth: 120,
+              showOverflowTooltip: true,
+              formatter: (row) => this.getOrderField(row, 'batchNo')
+            },
+            {
+              prop: 'workOrderCode',
+              label: '生产工单号',
+              align: 'center',
+              minWidth: 150,
+              showOverflowTooltip: true,
+              formatter: (row) => this.getOrderField(row, 'workOrderCode')
+            },
+            {
+              prop: 'formingNum',
+              label: '要求生产数量',
+              align: 'center',
+              minWidth: 120,
+              showOverflowTooltip: true,
+              formatter: (row) => this.getOrderField(row, 'formingNum')
+            },
+            {
+              prop: 'formedNum',
+              label: '实际生产数量',
+              align: 'center',
+              minWidth: 120,
+              showOverflowTooltip: true,
+              formatter: (row) => this.getOrderField(row, 'formedNum')
+            },
+            {
+              prop: 'specification',
+              label: '规格',
+              align: 'center',
+              minWidth: 120,
+              showOverflowTooltip: true,
+              formatter: (row) => this.getOrderField(row, 'specification')
+            },
+            {
+              prop: 'productModel',
+              label: '型号',
+              align: 'center',
+              minWidth: 120,
+              showOverflowTooltip: true,
+              formatter: (row) => this.getOrderField(row, 'productModel')
+            },
+            {
+              prop: 'approvalStatus',
+              label: '审核',
+              align: 'center',
+              minWidth: 120,
+              showOverflowTooltip: true,
+              formatter: (row) =>
+                this.getApprovalStatusLabel(row.approvalStatus)
+            },
+            {
+              prop: 'createTime',
+              label: '创建时间',
+              align: 'center',
+              minWidth: 160,
+              showOverflowTooltip: true
+            },
+            {
+              columnKey: 'action',
+              label: '操作',
+              align: 'center',
+              minWidth: 110,
+              fixed: 'right'
+            }
+          ];
+        }
+
+        if (this.isSample) {
+          return [
+            {
+              width: 50,
+              type: 'index',
+              columnKey: 'index',
+              label: '序号',
+              align: 'center'
+            },
+            {
+              prop: 'code',
+              label: '取样记录编码',
+              align: 'center',
+              minWidth: 150,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'qualityWorkOrderCode',
+              label: '检测工单编码',
+              align: 'center',
+              minWidth: 150,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'qualityWorkOrderName',
+              label: '检测工单名称',
+              align: 'center',
+              minWidth: 140,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'sourceCode',
+              label: '来源单号',
+              align: 'center',
+              minWidth: 150,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'qualityType',
+              label: '类型',
+              align: 'center',
+              minWidth: 100,
+              showOverflowTooltip: true,
+              formatter: (row) =>
+                this.getDictValue('质检计划类型', row.qualityType)
+            },
+            {
+              prop: 'qualityMode',
+              label: '检测方式',
+              align: 'center',
+              minWidth: 100,
+              showOverflowTooltip: true,
+              formatter: (row) => (row.qualityMode == 1 ? '全检' : '抽检')
+            },
+            {
+              prop: 'productCode',
+              label: '编码',
+              align: 'center',
+              minWidth: 110,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'productName',
+              label: '名称',
+              align: 'center',
+              minWidth: 120,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'batchNo',
+              label: '批次号',
+              align: 'center',
+              minWidth: 110,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'specification',
+              label: '规格',
+              align: 'center',
+              minWidth: 110,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'modelType',
+              label: '型号',
+              align: 'center',
+              minWidth: 110,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'brandNo',
+              label: '牌号',
+              align: 'center',
+              minWidth: 110,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'produceTaskName',
+              label: '工序名称',
+              align: 'center',
+              minWidth: 120,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'conditionType',
+              label: '取样类型',
+              align: 'center',
+              minWidth: 110,
+              showOverflowTooltip: true,
+              formatter: (row) => (row.conditionType == 1 ? '整样' : '小样')
+            },
+            {
+              prop: 'recordingMethod',
+              label: '记录方法',
+              align: 'center',
+              minWidth: 110,
+              showOverflowTooltip: true,
+              formatter: (row) =>
+                recordingMethodList.find(
+                  (item) => item.value == row.recordingMethod
+                )?.label || ''
+            },
+            {
+              prop: 'pleasePurpose',
+              label: '取样目的',
+              align: 'center',
+              minWidth: 120,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'sampleQuantity',
+              label: '取样数量',
+              align: 'center',
+              minWidth: 110,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'measureUnit',
+              label: '单位',
+              align: 'center',
+              minWidth: 90,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'status',
+              label: '状态',
+              align: 'center',
+              minWidth: 110,
+              showOverflowTooltip: true,
+              formatter: (row) => this.getSampleStatusLabel(row.status)
+            },
+            {
+              prop: 'approvalStatus',
+              label: '审核状态',
+              align: 'center',
+              minWidth: 110,
+              showOverflowTooltip: true,
+              formatter: (row) => reviewStatus[row.approvalStatus] || ''
+            },
+            {
+              prop: 'createUserName',
+              label: '创建人',
+              align: 'center',
+              minWidth: 110,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'createTime',
+              label: '创建时间',
+              align: 'center',
+              minWidth: 160,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'approvalUserName',
+              label: '审批人',
+              align: 'center',
+              minWidth: 110,
+              showOverflowTooltip: true
+            },
+            {
+              prop: 'approvalTime',
+              label: '审批时间',
+              align: 'center',
+              minWidth: 160,
+              showOverflowTooltip: true
+            },
+            {
+              columnKey: 'action',
+              label: '操作',
+              align: 'center',
+              minWidth: 110,
+              fixed: 'right'
+            }
+          ];
+        }
+
+        const codeColumn = {
+          prop: 'unqualifiedProductsCode',
+          label: '不良品编码',
+          align: 'center',
+          minWidth: 150,
+          showOverflowTooltip: true
+        };
+
+        return [
+          {
+            width: 45,
+            type: 'selection',
+            columnKey: 'selection',
+            align: 'center',
+            fixed: 'left'
+          },
+          {
+            width: 50,
+            type: 'index',
+            columnKey: 'index',
+            label: '序号',
+            align: 'center'
+          },
+          codeColumn,
+          {
+            prop: 'sourceCode',
+            label: '来源编码',
+            align: 'center',
+            minWidth: 150,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'categoryCode',
+            label: '物品编码',
+            align: 'center',
+            minWidth: 150,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'categoryName',
+            label: '物品名称',
+            align: 'center',
+            minWidth: 150,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'batchNo',
+            label: '批次号',
+            align: 'center',
+            minWidth: 120,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'specification',
+            label: '规格',
+            align: 'center',
+            minWidth: 110,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'brandNum',
+            label: '牌号',
+            align: 'center',
+            minWidth: 110,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'modelType',
+            label: '型号',
+            align: 'center',
+            minWidth: 130,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'quantity',
+            label: '数量',
+            align: 'center',
+            minWidth: 100,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'workOrderCode',
+            label: '工单编码',
+            align: 'center',
+            minWidth: 140,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'disposeTime',
+            label: '处置时间',
+            align: 'center',
+            minWidth: 160,
+            showOverflowTooltip: true
+          },
+          {
+            prop: 'status',
+            label: '状态',
+            align: 'center',
+            minWidth: 90,
+            showOverflowTooltip: true,
+            formatter: (row) => this.getDisposeStatusLabel(row.status)
+          },
+          {
+            columnKey: 'action',
+            label: '操作',
+            align: 'center',
+            minWidth: 110,
+            fixed: 'right'
+          }
+        ];
+      }
+    },
+    created() {
+      this.requestDict('放行类型');
+      this.requestDict('质检计划类型');
+      this.requestDict('处置状态');
+      this.getDisposeList('dispose_status');
+    },
+    methods: {
+      reload(where = {}) {
+        this.$refs.table.reload({
+          where,
+          ...this.tableQuery
+        });
+      },
+      search(where) {
+        this.reload(where);
+      },
+      handlePrint(key) {
+        if (!this.selection.length) {
+          this.$message.warning('请先勾选要打印的数据');
+          return;
+        }
+
+        const row = this.selection[0] || {};
+        if (key === 'unqualifiedProductReview') {
+          this.$refs.unqualifiedProductReviewRef.open(row);
+          return;
+        }
+        if (key === 'releaseOrderPrint') {
+          this.$refs.releaseOrderPrintRef.open(row);
+        }
+      },
+      getOrderField(row, key) {
+        if (row[key] !== undefined && row[key] !== null && row[key] !== '') {
+          return row[key];
+        }
+        if (!Array.isArray(row.orders)) {
+          return '';
+        }
+        return row.orders
+          .map((item) => item[key])
+          .filter(Boolean)
+          .join(', ');
+      },
+      getApprovalStatusLabel(status) {
+        switch (status) {
+          case 0:
+            return '未提交';
+          case 1:
+            return '审核中';
+          case 2:
+            return '审核通过';
+          case 3:
+            return '审核不通过';
+          default:
+            return '';
+        }
+      },
+      getSampleStatusLabel(status) {
+        switch (status) {
+          case 1:
+            return '审核中';
+          case 2:
+            return '已取样';
+          case 3:
+            return '已作废';
+          case 4:
+            return '不通过';
+          default:
+            return '未提交';
+        }
+      },
+      getDisposeStatusLabel(status) {
+        const data = this.disposeList.find((item) => item.value == status);
+        return data ? data.label : '';
+      },
+      async getDisposeList(code) {
+        const res = await getByCode(code);
+        if (res?.code == 0) {
+          this.disposeList = res.data.map((item) => {
+            const key = Object.keys(item)[0];
+            return { value: Number(key), label: item[key] };
+          });
+        }
+      },
+      datasource({ page, limit, where, order }) {
+        if (this.isReleaseOrder) {
+          const body = {
+            ...where,
+            ...order,
+            pageNum: page,
+            size: limit,
+            ...this.tableQuery
+          };
+          return checklistrecordPage(body);
+        }
+
+        if (this.isSample) {
+          const body = {
+            ...where,
+            ...order,
+            pageNum: page,
+            size: limit,
+            ...this.tableQuery
+          };
+          return samplingRecordsPage(body);
+        }
+
+        if (this.isUnacceptedProduct) {
+          const body = {
+            ...where,
+            ...order,
+            pageNum: page,
+            size: limit,
+            ...this.tableQuery
+          };
+          return unacceptedProductPage(body);
+        }
+
+        return Promise.resolve({
+          list: [],
+          count: 0
+        });
+      }
+    }
+  };
+</script>

+ 150 - 39
src/views/batchRecord/components/tables/qualityWorkOrderTable.vue

@@ -41,7 +41,7 @@
 <script>
   import dictMixins from '@/mixins/dictMixins';
   import tableColumnsMixin from '@/mixins/tableColumnsMixin';
-  import { batchRecordPage } from '@/api/qualityWorkOrder/index.js';
+  import { batchRecordPage, queryPrint } from '@/api/qualityWorkOrder/index.js';
   import exportButton from '@/components/upload/exportButton.vue';
   import printSelector from '../printSelector.vue';
   import productInspectionReport from '../../print/electrodeBatchRecord/productInspectionReport.vue';
@@ -175,10 +175,23 @@
         params: {},
         tableData: [],
         selection: [],
+        printTypeMap: {
+          productInspectionReport: 'PRODUCT_INSPECTION_REPORT',
+          samplingForm: 'SAMPLING_SLIP',
+          finishedProductOriginalRecord: 'FINISHED_PRODUCT_INSPECTION_RECORD'
+        },
         printOptions: [
-          { key: 'productInspectionReport', label: '产品报检单', code: 'CG-038' },
+          {
+            key: 'productInspectionReport',
+            label: '产品报检单',
+            code: 'CG-038'
+          },
           { key: 'samplingForm', label: '取样单', code: 'ZL-041' },
-          { key: 'finishedProductOriginalRecord', label: '成品检验原始记录', code: '054-7' }
+          {
+            key: 'finishedProductOriginalRecord',
+            label: '成品检验原始记录',
+            code: '054-7'
+          }
         ]
       };
     },
@@ -246,48 +259,146 @@
         const path = `/page-qms/inspectionWork/details?id=${row.id}&path=&name=工单`;
         window.history.pushState(null, '', path);
       },
-      handlePrint(key) {
+      async 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 || ''
+        const printType = this.printTypeMap[key];
+        if (!printType) {
+          this.$message.warning('暂不支持该打印单据');
+          return;
+        }
+        try {
+          const ids = this.selection.map((item) => item.id).filter(Boolean);
+          const res = await queryPrint({ ids, printType });
+          const data = this.buildPrintData(res, key);
+          if (!data) {
+            this.$message.warning('未查询到可打印的质检工单数据');
+            return;
+          }
+          if (this.$refs[key]) {
+            this.$refs[key].open(data);
+          }
+        } catch (error) {
+          this.$message.error(error.message || '查询打印数据失败');
+        }
+      },
+      buildPrintData(res, key) {
+        const list = this.getPrintList(res, key);
+        const first = list[0];
+        if (!first) {
+          return null;
+        }
+        const selected =
+          this.selection.find((item) => item.id == first.id) ||
+          this.selection[0] ||
+          {};
+        const date = this.formatPrintDate(
+          first.createTime || selected.qualityTime || selected.createTime
+        );
+        const base = this.buildPrintItem(first, selected);
+
+        if (key == 'productInspectionReport') {
+          return {
+            ...base,
+            code: selected.code || first.workOrderCode || '',
+            date,
+            reporter: selected.qualityName || selected.createUserName || '',
+            receiver: selected.reviewerName || selected.checkerName || '',
+            department: selected.departmentName || selected.workshopName || '',
+            items: list.map((item, index) =>
+              this.buildPrintItem(
+                item,
+                this.selection.find((row) => row.id == item.id) || selected,
+                index
+              )
+            )
+          };
+        }
+
+        if (key == 'samplingForm') {
+          return {
+            ...base,
+            date,
+            sampleDate: date,
+            batchNo:
+              first.produceBatch || first.batch || selected.batchNo || '',
+            batchQuantity: first.batch || first.quantity || '',
+            sampleQuantity: first.quantity || selected.sampleQuantity || '',
+            sampleLocation: selected.sampleLocation || '',
+            sampleSource: selected.sampleSource || '',
+            sampler: selected.qualityName || selected.createUserName || '',
+            checker: selected.checkerName || selected.reviewerName || ''
+          };
+        }
+
+        return {
+          ...base,
+          date,
+          inspectionDate: date,
+          batchNo: first.produceBatch || first.batch || selected.batchNo || '',
+          submitQuantity:
+            first.quantityVerified ||
+            selected.submitQuantity ||
+            first.quantity ||
+            '',
+          sampleQuantity: first.quantity || selected.sampleQuantity || '',
+          inspectionBasis: selected.inspectionBasis || '',
+          inspector: selected.qualityName || selected.createUserName || '',
+          checker: selected.checkerName || selected.reviewerName || '',
+          conclusion: selected.status === 1 ? '合格' : '',
+          remark: selected.remark || ''
+        };
+      },
+      getPrintList(res, key) {
+        const listMap = {
+          productInspectionReport: 'productInspectionReportList',
+          samplingForm: 'samplingSlipList',
+          finishedProductOriginalRecord: 'finishedProductInspectionRecordList'
+        };
+        const listKey = listMap[key];
+        if (Array.isArray(res?.[listKey])) {
+          return res[listKey];
+        }
+        if (Array.isArray(res)) {
+          return res;
+        }
+        return [];
+      },
+      buildPrintItem(item, selected = {}, index = 0) {
+        return {
+          ...item,
+          seq: index + 1,
+          productCode: item.materialCode || selected.productCode || '',
+          productName:
+            item.materialName || selected.productName || selected.name || '',
+          materialCode: item.materialCode || '',
+          materialName: item.materialName || '',
+          specification: item.specModel || selected.specification || '',
+          sterilizationBatchNo:
+            item.sterilizationBatch || selected.sterilizationBatchNo || '',
+          productionBatchNo: item.produceBatch || selected.batchNo || '',
+          batchNo: item.produceBatch || item.batch || selected.batchNo || '',
+          unit: item.unit || selected.unit || '',
+          quantity: item.quantity || selected.quantity || '',
+          processOrderCode:
+            item.workOrderCode ||
+            selected.workOrderCode ||
+            selected.sourceCode ||
+            '',
+          inspectionCode: selected.code || ''
         };
-        if (this.$refs[key]) {
-          this.$refs[key].open(data);
+      },
+      formatPrintDate(value) {
+        const source = value || new Date();
+        if (typeof source == 'string') {
+          return source.slice(0, 10);
+        }
+        if (this.$util?.toDateString) {
+          return this.$util.toDateString(source, 'yyyy-MM-dd');
         }
+        return new Date(source).toISOString().slice(0, 10);
       }
     }
   };

+ 69 - 11
src/views/batchRecord/components/tables/wmsOutInt.vue

@@ -41,7 +41,7 @@
   import tableColumnsMixin from '@/mixins/tableColumnsMixin';
   import { getPages } from '@/api/wms/index';
   import { sceneState } from '@/utils/dict/index';
-  import { getBatchRecordPage } from '@/api/wms/index';
+  import { getBatchRecordPage, printInbound } from '@/api/wms/index';
   import exportButton from '@/components/upload/exportButton.vue';
   import printSelector from '../printSelector.vue';
   import finishedProductWarehouse from '../../print/electrodeBatchRecord/finishedProductWarehouse.vue';
@@ -234,22 +234,64 @@
       openPrint(row) {
         this.$refs.finishedProductWarehouse.open(row);
       },
-      handlePrint(key) {
+      async handlePrint(key) {
         if (!this.selection.length) {
           this.$message.warning('请先勾选要打印的入库单');
           return;
         }
-        const r = this.selection[0];
+        try {
+          const ids = this.selection.map((item) => item.id).filter(Boolean);
+          if (!ids.length) {
+            this.$message.warning('请先勾选要打印的入库单');
+            return;
+          }
+          const res = await printInbound(ids);
+          const data = this.buildPrintData(res);
+          if (!data) {
+            this.$message.warning('未查询到可打印的入库单数据');
+            return;
+          }
+          if (this.$refs[key]) {
+            this.$refs[key].open(data);
+          }
+        } catch (error) {
+          this.$message.error(error.message || '查询打印数据失败');
+        }
+      },
+      buildPrintData(res) {
+        const list = Array.isArray(res)
+          ? res
+          : Array.isArray(res?.data)
+          ? res.data
+          : [];
+        const r = this.selection[0] || {};
+        const firstItem = list[0] || {};
         const today = this.$util?.toDateString
           ? this.$util.toDateString(new Date(), 'yyyy-MM-dd')
           : new Date().toISOString().slice(0, 10);
-        const data = {
+        const itemList = list.map((item) => ({
+          ...item,
+          itemCode: item.code || '',
+          itemName: item.name || '',
+          specification: item.specification || '',
+          batchNo: item.batchNo || '',
+          productionDate: this.formatPrintDate(item.productionDate),
+          expiryDate: this.formatPrintDate(item.expirationDate),
+          unit: item.unit || '',
+          quantity: item.quantity ?? '',
+          remark: item.remark || ''
+        }));
+        if (!itemList.length) {
+          return null;
+        }
+        return {
           batchNo: this.tableQuery.batchNo,
           productCode: this.tableQuery.productCode,
-          productName: r.productName || '',
-          specification: r.specification || '',
+          productName: r.productName || firstItem.name || '',
+          specification: r.specification || firstItem.specification || '',
           code: r.bizNo || '',
-          date: r.storageTime || today,
+          date:
+            this.formatPrintDate(r.storageTime || firstItem.createTime) || today,
           warehouse: r.warehouseName || '',
           department: r.warehouseName || '',
           processOrderCode: r.sourceBizNo || '',
@@ -260,12 +302,28 @@
           reporter: r.createUserName || '',
           creator: r.createUserName || '',
           reviewer: r.reviewerName || '',
-          totalQuantity: r.totalQuantity || r.quantity || '',
-          remark: r.remark || ''
+          totalQuantity: this.getTotalQuantity(itemList),
+          remark: r.remark || firstItem.remark || '',
+          itemList
         };
-        if (this.$refs[key]) {
-          this.$refs[key].open(data);
+      },
+      formatPrintDate(value) {
+        if (!value) {
+          return '';
         }
+        if (typeof value === 'string') {
+          return value.slice(0, 10);
+        }
+        return this.$util?.toDateString
+          ? this.$util.toDateString(value, 'yyyy-MM-dd')
+          : '';
+      },
+      getTotalQuantity(list) {
+        const total = list.reduce((sum, item) => {
+          const quantity = Number(item.quantity);
+          return Number.isNaN(quantity) ? sum : sum + quantity;
+        }, 0);
+        return total || '';
       }
     }
   };

+ 127 - 37
src/views/batchRecord/components/tables/workOrderTable.vue

@@ -44,7 +44,7 @@
 <script>
   import dictMixins from '@/mixins/dictMixins';
   import tableColumnsMixin from '@/mixins/tableColumnsMixin';
-  import { batchRecordPage } from '@/api/workOrderList';
+  import { batchRecordPage, queryWorkOrderPrint } from '@/api/workOrderList';
   import detailsPop from '@/views/produceOrder/components/details/index.vue';
   import { getById } from '@/api/produceOrder/index';
   import { getAllProduceTaskByUsing } from '@/api/InTheSystem/index';
@@ -189,6 +189,12 @@
         params: {},
         tableData: [],
         selection: [],
+        printTypeMap: {
+          batchRecordCover: 'BATCH_RECORD_COVER',
+          productionOrder: 'PRODUCTION_INSTRUCTION',
+          materialBalance: 'MATERIAL_BALANCE',
+          productReleaseApproval: 'PRODUCT_RELEASE_APPROVAL'
+        },
         printOptions: [
           {
             key: 'batchRecordCover',
@@ -303,60 +309,144 @@
           this.$message.warning('请先勾选要打印的工单');
           return;
         }
-        const row = this.selection[0];
-        const data = this.buildPrintData(row);
-        if (this.$refs[key]) {
-          this.$refs[key].open(data);
+        const printType = this.printTypeMap[key];
+        if (!printType) {
+          this.$message.warning('暂不支持该打印单据');
+          return;
+        }
+        try {
+          const ids = this.selection.map((item) => item.id).filter(Boolean);
+          const res = await queryWorkOrderPrint({ ids, printType });
+          const data = this.buildPrintData(key, res);
+          if (!data) {
+            this.$message.warning('未查询到可打印的工单数据');
+            return;
+          }
+          if (this.$refs[key]) {
+            this.$refs[key].open(data);
+          }
+        } catch (error) {
+          this.$message.error(error.message || '查询打印数据失败');
         }
       },
-      buildPrintData(row) {
-        const tq = this.tableQuery || {};
+      buildPrintData(key, res) {
+        const map = {
+          batchRecordCover: 'batchRecordCoverList',
+          productionOrder: 'productionInstructionList',
+          materialBalance: 'materialBalanceList',
+          productReleaseApproval: 'productReleaseApprovalList'
+        };
+        const list = Array.isArray(res?.[map[key]]) ? res[map[key]] : [];
+        const row = list[0];
+        if (!row) {
+          return null;
+        }
+        const fallback = this.selection[0] || {};
         const today = this.$util?.toDateString
           ? this.$util.toDateString(new Date(), 'yyyy-MM-dd')
           : new Date().toISOString().slice(0, 10);
+        const base = this.buildBasePrintData(row, fallback, today);
+        if (key === 'productionOrder') {
+          return {
+            ...base,
+            materialList: this.buildProductionMaterials(row.materials)
+          };
+        }
+        if (key === 'materialBalance') {
+          return {
+            ...base,
+            finishedQuantity: row.finishedQuantity || '',
+            rawMaterialList: this.buildBalanceMaterials(row.rawMaterials),
+            packagingMaterialList: this.buildBalanceMaterials(
+              row.packagingMaterials
+            )
+          };
+        }
+        if (key === 'productReleaseApproval') {
+          return {
+            ...base,
+            quantity: row.quantity || '',
+            inspectionCode: row.inspectionNo || row.inspectionCode || ''
+          };
+        }
+        return base;
+      },
+      buildBasePrintData(row, fallback, today) {
+        const tq = this.tableQuery || {};
         return {
-          productCode: tq.productCode || row.productCode || '',
-          productName: row.productName || '',
-          specification: row.specification || '',
-          unit: row.unit || '',
-          batchNo: tq.batchNo || row.batchNo || '',
+          productCode:
+            row.productCode || tq.productCode || fallback.productCode || '',
+          productName: row.productName || fallback.productName || '',
+          specification: row.specification || fallback.specification || '',
+          unit: row.unit || fallback.unit || '',
+          batchNo: row.batchNo || tq.batchNo || fallback.batchNo || '',
           sterilizationBatchNo:
             row.sterilizationBatchNo ||
-            row.sterileBatchNo ||
-            row.sterilizationNo ||
+            fallback.sterilizationBatchNo ||
+            fallback.sterileBatchNo ||
+            fallback.sterilizationNo ||
+            '',
+          batchQuantity:
+            row.quantity ||
+            fallback.planQuantity ||
+            fallback.batchQuantity ||
             '',
-          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 || '',
+          planQuantity:
+            row.quantity ||
+            fallback.planQuantity ||
+            fallback.batchQuantity ||
+            '',
+          startDate: fallback.startTime || fallback.planStartTime || '',
+          endDate: fallback.endTime || fallback.planCompleteTime || '',
+          productionDate: row.productionDate || fallback.startTime || today,
+          expiryDate: row.expiryDate || fallback.expiryDate || '',
+          department:
+            fallback.departmentName || fallback.department || '生产部',
+          workshop: fallback.workshopName || fallback.workshop || '生产车间',
+          storageCondition: fallback.storageCondition || '常温避光保存',
+          orderCode: fallback.code || row.orderCode || '',
+          processOrderCode: fallback.code || row.processOrderCode || '',
+          processOrderNo: fallback.code || row.processOrderNo || '',
+          moCode: fallback.code || row.moCode || '',
+          code: fallback.code || row.code || '',
           date: today,
           issueDate: today,
           createDate: today,
           reviewDate: today,
           approveDate: today,
-          creator: row.createUserName || '',
-          reviewer: row.reviewerName || '',
-          approver: row.approverName || '',
+          creator: fallback.createUserName || '',
+          reviewer: fallback.reviewerName || '',
+          approver: fallback.approverName || '',
           productionManagerSignDate:
-            row.productionManagerSignDate || row.productionManager || '',
+            fallback.productionManagerSignDate ||
+            fallback.productionManager ||
+            '',
           qualityManagerSignDate:
-            row.qualityManagerSignDate || row.qualityManager || '',
-          remark: row.remark || '',
-          materialRemark: row.remark || '',
-          packagingRequirement: row.packagingRequirement || '按工艺要求执行',
-          materialList: row.materialList || []
+            fallback.qualityManagerSignDate || fallback.qualityManager || '',
+          remark: row.remark || fallback.remark || '',
+          materialRemark: row.remark || fallback.remark || '',
+          packagingRequirement:
+            fallback.packagingRequirement || '按工艺要求执行',
+          materialList: fallback.materialList || []
         };
       },
+      buildProductionMaterials(materials) {
+        const list = Array.isArray(materials) ? materials : [];
+        return list.map((item) => ({
+          ...item,
+          quantity: item.demandQuantity || '',
+          planQuantity: item.plannedQuantity || '',
+          lossQuantity: item.lossQuantity || ''
+        }));
+      },
+      buildBalanceMaterials(materials) {
+        const list = Array.isArray(materials) ? materials : [];
+        return list.map((item) => ({
+          ...item,
+          receiveQuantity: item.pickedQuantity || '',
+          remainQuantity: item.remainingQuantity || ''
+        }));
+      },
       // 工序
       async getproduceTask() {
         // 查询详情

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

@@ -180,8 +180,10 @@
           <div class="table-box">
             <component
               :is="activeComponentName"
+              :key="activeType"
               ref="tableRef"
               :tableQuery="tableQuery"
+              :table-type="activeTableType"
             ></component>
           </div>
         </template>
@@ -207,6 +209,7 @@
   import qualityInspection from './components/tables/qualityInspection.vue';
   import certificate from './components/tables/certificate.vue';
   import qualityReportApproval from './components/tables/qualityReportApproval.vue';
+  import pendingBatchRecordTable from './components/tables/pendingBatchRecordTable.vue';
   import print from './print/index.vue';
 
   export default {
@@ -224,6 +227,7 @@
       qualityInspection,
       certificate,
       qualityReportApproval,
+      pendingBatchRecordTable,
       print
     },
     name: 'batchRecord',
@@ -278,10 +282,26 @@
           {
             name: '合格证',
             componentName: 'certificate'
+          },
+          {
+            name: '不良品',
+            componentName: 'pendingBatchRecordTable',
+            tableType: 'unacceptedProduct'
+          },
+          {
+            name: '取样',
+            componentName: 'pendingBatchRecordTable',
+            tableType: 'sample'
+          },
+          {
+            name: '放行单',
+            componentName: 'pendingBatchRecordTable',
+            tableType: 'releaseOrder'
           }
         ],
         activeType: '生产工单',
         activeComponentName: 'workOrderTable',
+        activeTableType: '',
         // 产品搜索条件
         productBody: {
           param: '',
@@ -350,6 +370,7 @@
         this.batchNos = [];
         this.activeType = '生产工单';
         this.activeComponentName = 'workOrderTable';
+        this.activeTableType = '';
 
         this.getAllProductInWorkOrder();
       },
@@ -390,7 +411,10 @@
       changeActiveType(i) {
         this.activeType = i.name;
         this.activeComponentName = i.componentName;
-        this.reload();
+        this.activeTableType = i.tableType || '';
+        this.$nextTick(() => {
+          this.reload();
+        });
       },
       resetSubmit() {
         this.productBody.param = '';

+ 330 - 0
src/views/batchRecord/print/electrodeBatchRecord/releaseOrderPrint.vue

@@ -0,0 +1,330 @@
+<template>
+  <ele-modal
+    title="放行单打印"
+    :visible.sync="visible"
+    v-if="visible"
+    width="90%"
+    :maxable="true"
+    append-to-body
+  >
+    <div id="printSection_releaseOrderPrint">
+      <div class="release-print-page">
+        <div class="release-header">
+          <div class="company">湖南特瑞精密医疗器械有限公司</div>
+          <div class="title">放行单详情</div>
+        </div>
+
+        <table class="info-table">
+          <tr>
+            <td class="label">放行单编码</td>
+            <td>{{ field('code') }}</td>
+            <td class="label">放行单名称</td>
+            <td>{{ field('name') }}</td>
+          </tr>
+          <tr>
+            <td class="label">放行类型</td>
+            <td>{{ checklistTypeLabel }}</td>
+            <td class="label">审核状态</td>
+            <td>{{ approvalStatusLabel }}</td>
+          </tr>
+          <tr>
+            <td class="label">创建人</td>
+            <td>{{ field('createUserName') }}</td>
+            <td class="label">模板名称</td>
+            <td>{{ field('templateName') }}</td>
+          </tr>
+        </table>
+
+        <div class="section-title">物品清单</div>
+        <table class="data-table">
+          <thead>
+            <tr>
+              <th style="width: 45px">序号</th>
+              <th>批次号</th>
+              <th>产品编码</th>
+              <th>产品名称</th>
+              <th>生产工单号</th>
+              <th>要求生产数量</th>
+              <th>实际生产数量</th>
+              <th>规格</th>
+              <th>型号</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr v-for="(item, index) in orders" :key="'order_' + index">
+              <td>{{ index + 1 }}</td>
+              <td>{{ item.batchNo }}</td>
+              <td>{{ item.productCode }}</td>
+              <td>{{ item.productName }}</td>
+              <td>{{ item.workOrderCode }}</td>
+              <td>{{ item.formingNum }}</td>
+              <td>{{ item.formedNum }}</td>
+              <td>{{ item.specification }}</td>
+              <td>{{ item.productModel }}</td>
+            </tr>
+            <tr v-if="!orders.length">
+              <td colspan="9" class="empty-cell">暂无数据</td>
+            </tr>
+          </tbody>
+        </table>
+
+        <div class="section-title">生产放行规则</div>
+        <table class="data-table rule-table">
+          <thead>
+            <tr>
+              <th style="width: 45px">序号</th>
+              <th>指标名称</th>
+              <th style="width: 90px">审核结果</th>
+              <th>备注</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr v-for="(item, index) in workDetails" :key="'work_' + index">
+              <td>{{ index + 1 }}</td>
+              <td class="left">{{ item.mainIndicatorName }}</td>
+              <td>{{ passLabel(item.isPass) }}</td>
+              <td class="left">{{ item.remark }}</td>
+            </tr>
+            <tr v-if="!workDetails.length">
+              <td colspan="4" class="empty-cell">暂无数据</td>
+            </tr>
+          </tbody>
+        </table>
+
+        <table class="conclusion-table">
+          <tr>
+            <td class="label">结论</td>
+            <td>{{ conclusionLabel(form.workConclution) }}</td>
+            <td class="label">验收人</td>
+            <td>{{ field('workCheckUserName') }}</td>
+            <td class="label">验收时间</td>
+            <td>{{ field('workCheckTime') }}</td>
+          </tr>
+        </table>
+
+        <div class="section-title">质检放行规则</div>
+        <table class="data-table rule-table">
+          <thead>
+            <tr>
+              <th style="width: 45px">序号</th>
+              <th>指标名称</th>
+              <th style="width: 90px">审核结果</th>
+              <th>备注</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr
+              v-for="(item, index) in qualityDetails"
+              :key="'quality_' + index"
+            >
+              <td>{{ index + 1 }}</td>
+              <td class="left">{{ item.mainIndicatorName }}</td>
+              <td>{{ passLabel(item.isPass) }}</td>
+              <td class="left">{{ item.remark }}</td>
+            </tr>
+            <tr v-if="!qualityDetails.length">
+              <td colspan="4" class="empty-cell">暂无数据</td>
+            </tr>
+          </tbody>
+        </table>
+
+        <table class="conclusion-table">
+          <tr>
+            <td class="label">结论</td>
+            <td>{{ conclusionLabel(form.qualityConclution) }}</td>
+            <td class="label">验收人</td>
+            <td>{{ field('qualityCheckUserName') }}</td>
+            <td class="label">验收时间</td>
+            <td>{{ field('qualityCheckTime') }}</td>
+          </tr>
+        </table>
+      </div>
+    </div>
+    <div slot="footer">
+      <el-button @click="print">打印预览</el-button>
+      <el-button @click="visible = false">关闭</el-button>
+    </div>
+  </ele-modal>
+</template>
+
+<script>
+  import dictMixins from '@/mixins/dictMixins';
+  import { checklistrecordGetById } from '@/api/checklistrecord/index';
+
+  export default {
+    name: 'ReleaseOrderPrint',
+    mixins: [dictMixins],
+    data() {
+      return {
+        visible: false,
+        form: {}
+      };
+    },
+    computed: {
+      orders() {
+        return Array.isArray(this.form.orders) ? this.form.orders : [];
+      },
+      workDetails() {
+        return this.details.filter((item) => item.checkType == 1);
+      },
+      qualityDetails() {
+        return this.details.filter((item) => item.checkType == 2);
+      },
+      details() {
+        return Array.isArray(this.form.details) ? this.form.details : [];
+      },
+      checklistTypeLabel() {
+        return this.getDictValue('放行类型', this.form.checklistType + '');
+      },
+      approvalStatusLabel() {
+        switch (this.form.approvalStatus) {
+          case 0:
+            return '未提交';
+          case 1:
+            return '审核中';
+          case 2:
+            return '审核通过';
+          case 3:
+            return '审核不通过';
+          default:
+            return '';
+        }
+      }
+    },
+    created() {
+      this.requestDict('放行类型');
+    },
+    methods: {
+      async open(row) {
+        this.form = row || {};
+        this.visible = true;
+        if (row?.id) {
+          try {
+            this.form = await checklistrecordGetById(row.id);
+          } catch (error) {
+            this.$message.error(error.message || '查询放行单详情失败');
+          }
+        }
+      },
+      field(key) {
+        const value = this.form[key];
+        return value === 0 || value ? value : '';
+      },
+      passLabel(value) {
+        if (value === 1) {
+          return '是';
+        }
+        if (value === 0) {
+          return '否';
+        }
+        return '';
+      },
+      conclusionLabel(value) {
+        if (value === 1) {
+          return '符合规定';
+        }
+        if (value === 0) {
+          return '不符合规定';
+        }
+        return '';
+      },
+      print() {
+        const el = document.getElementById('printSection_releaseOrderPrint');
+        const win = window.open('', '_blank');
+        win.document.open();
+        win.document.write('<html><head><title>放行单打印</title>');
+        win.document.write(`<style>${this.getPrintStyle()}</style>`);
+        win.document.write('</head><body>');
+        win.document.write(el.innerHTML);
+        win.document.write('</body></html>');
+        win.document.close();
+        win.onload = function () {
+          win.print();
+        };
+      },
+      getPrintStyle() {
+        return `
+          @page{size:A4 landscape;margin:10mm;}
+          body{margin:0;background:#fff;font-family:SimSun,serif;color:#000;font-size:12px;}
+          .release-print-page{box-sizing:border-box;width:100%;padding:8mm;background:#fff;}
+          .release-header{text-align:center;margin-bottom:8px;}
+          .company{font-size:18px;letter-spacing:4px;margin-bottom:6px;}
+          .title{font-size:20px;font-weight:bold;}
+          .section-title{font-size:15px;font-weight:bold;margin:12px 0 6px;border-left:4px solid #000;padding-left:8px;}
+          table{width:100%;border-collapse:collapse;table-layout:fixed;}
+          td,th{border:1px solid #000;padding:6px 8px;line-height:1.45;text-align:center;vertical-align:middle;word-break:break-all;}
+          th{font-weight:bold;background:#f5f5f5;}
+          .info-table .label,.conclusion-table .label{width:90px;font-weight:bold;background:#f5f5f5;}
+          .data-table .left{text-align:left;}
+          .rule-table td{min-height:34px;}
+          .conclusion-table{margin-top:-1px;}
+          .empty-cell{height:34px;color:#666;}
+        `;
+      }
+    }
+  };
+</script>
+
+<style scoped>
+  .release-print-page {
+    font-family: SimSun, serif;
+    background: #fff;
+    color: #000;
+    padding: 20px;
+    font-size: 12px;
+  }
+  .release-header {
+    text-align: center;
+    margin-bottom: 8px;
+  }
+  .company {
+    font-size: 18px;
+    letter-spacing: 4px;
+    margin-bottom: 6px;
+  }
+  .title {
+    font-size: 20px;
+    font-weight: bold;
+  }
+  .section-title {
+    font-size: 15px;
+    font-weight: bold;
+    margin: 12px 0 6px;
+    border-left: 4px solid #000;
+    padding-left: 8px;
+  }
+  table {
+    width: 100%;
+    border-collapse: collapse;
+    table-layout: fixed;
+  }
+  td,
+  th {
+    border: 1px solid #000;
+    padding: 6px 8px;
+    line-height: 1.45;
+    text-align: center;
+    vertical-align: middle;
+    word-break: break-all;
+  }
+  th {
+    font-weight: bold;
+    background: #f5f5f5;
+  }
+  .info-table .label,
+  .conclusion-table .label {
+    width: 90px;
+    font-weight: bold;
+    background: #f5f5f5;
+  }
+  .data-table .left {
+    text-align: left;
+  }
+  .conclusion-table {
+    margin-top: -1px;
+  }
+  .empty-cell {
+    height: 34px;
+    color: #666;
+  }
+</style>

+ 684 - 0
src/views/batchRecord/print/electrodeBatchRecord/unqualifiedProductReview.vue

@@ -0,0 +1,684 @@
+<template>
+  <ele-modal
+    title="不合格品通知及评审处置表 TR.D-060/ZL"
+    :visible.sync="visible"
+    v-if="visible"
+    width="90%"
+    :maxable="true"
+    append-to-body
+  >
+    <div id="printSection_unqualifiedProductReview">
+      <div class="uqr-page">
+        <div class="crop crop-tl"></div>
+        <div class="crop crop-tr"></div>
+        <div class="crop crop-bl"></div>
+        <div class="crop crop-br"></div>
+
+        <div class="uqr-header">
+          <div class="uqr-logo">
+            <div class="logo-mark">
+              <span></span>
+              <span></span>
+              <span></span>
+            </div>
+            <div>
+              <div class="logo-cn">特瑞精密医疗</div>
+              <div class="logo-en">Triple Precision Medical</div>
+            </div>
+          </div>
+          <div class="uqr-company">湖南特瑞精密医疗器械有限公司</div>
+        </div>
+
+        <div class="uqr-title">不合格品通知及评审处置表</div>
+        <div class="uqr-meta"
+          >表单编号:TR.D-060/ZL&nbsp;&nbsp;&nbsp;版本:B/3</div
+        >
+
+        <table class="uqr-table">
+          <tr>
+            <td class="nowrap" colspan="4">
+              不合格品类别
+              <span class="check">□</span>原辅料
+              <span class="check">□</span>包材
+              <span class="check">□</span>中间产品
+              <span class="check">□</span>成品
+            </td>
+            <td class="nowrap" colspan="2"
+              >处置编号<span class="fill-line">{{
+                field('disposeNo')
+              }}</span></td
+            >
+          </tr>
+          <tr>
+            <td class="section-title" colspan="6">基本信息</td>
+          </tr>
+          <tr class="center-row">
+            <td>品名</td>
+            <td>型号规格</td>
+            <td>批号</td>
+            <td>数量</td>
+            <td colspan="2">来源</td>
+          </tr>
+          <tr class="info-row">
+            <td>{{ field('categoryName') }}</td>
+            <td>{{ field('specification') || field('modelType') }}</td>
+            <td>{{ field('batchNo') }}</td>
+            <td>{{ quantityText }}</td>
+            <td colspan="2" class="source-cell">
+              <span><span class="check">□</span>来料检</span>
+              <span><span class="check">□</span>过程检</span>
+              <span><span class="check">□</span>成品检</span>
+              <span><span class="check">□</span>库存</span>
+              <span><span class="check">□</span>交付后</span>
+              <span
+                ><span class="check">□</span>其他<span class="short-line"></span
+              ></span>
+            </td>
+          </tr>
+          <tr>
+            <td colspan="6" class="large-cell">
+              <div
+                >不合格描述:(来料不合格的,应描述供应商名称、厂家批号等信息)</div
+              >
+              <div class="write-area">{{
+                field('unqualifiedDescription') || field('unqualifiedReason')
+              }}</div>
+              <div>
+                是否有其他物料/中间产品/成品受影响(<span class="check">□</span
+                >否,
+                <span class="check">□</span
+                >是,受影响的物料/中间产品/成品批次如下):
+              </div>
+              <div class="write-area small"></div>
+              <div class="suggestion">
+                处置建议:
+                <span class="check">□</span>退货
+                <span class="check">□</span>销毁
+                <span class="check">□</span>让步接收
+                <span class="check">□</span>挑选使用
+                <span class="check">□</span>返工
+                <span class="check">□</span>其他<span class="long-line"></span>
+              </div>
+              <div class="signature-row">
+                <span>反馈部门:</span>
+                <span>反馈人/日期:</span>
+                <span>部门负责人/日期:</span>
+              </div>
+            </td>
+          </tr>
+          <tr>
+            <td colspan="6" class="confirm-cell">
+              <div>质量部QA 确认:</div>
+              <div class="confirm-sign">确认人/日期:</div>
+            </td>
+          </tr>
+          <tr>
+            <td colspan="6" class="confirm-cell">
+              <div>委托方意见(仅适用于受托不合格品处置):</div>
+              <div class="confirm-sign">确认人/日期:</div>
+            </td>
+          </tr>
+          <tr>
+            <td class="section-title" colspan="6">不合格评审</td>
+          </tr>
+          <tr class="review-head">
+            <td rowspan="6" class="review-label">评审<br />意见</td>
+            <td>部门</td>
+            <td colspan="3">意见</td>
+            <td>评审人签字/日期</td>
+          </tr>
+          <tr v-for="n in 5" :key="'review_' + n" class="review-row">
+            <td></td>
+            <td colspan="3"></td>
+            <td></td>
+          </tr>
+        </table>
+
+        <div class="page-number">1 / 2</div>
+      </div>
+
+      <div class="uqr-page">
+        <div class="crop crop-tl"></div>
+        <div class="crop crop-tr"></div>
+        <div class="crop crop-bl"></div>
+        <div class="crop crop-br"></div>
+
+        <div class="uqr-header">
+          <div class="uqr-logo">
+            <div class="logo-mark">
+              <span></span>
+              <span></span>
+              <span></span>
+            </div>
+            <div>
+              <div class="logo-cn">特瑞精密医疗</div>
+              <div class="logo-en">Triple Precision Medical</div>
+            </div>
+          </div>
+          <div class="uqr-company">湖南特瑞精密医疗器械有限公司</div>
+        </div>
+
+        <div class="uqr-title">不合格品通知及评审处置表</div>
+        <div class="uqr-meta"
+          >表单编号:TR.D-060/ZL&nbsp;&nbsp;&nbsp;版本:B/3</div
+        >
+
+        <table class="uqr-table">
+          <tr>
+            <td class="approval-cell" colspan="4">
+              <div class="bold">管代审批:</div>
+              <div class="approval-options">
+                <span class="check">□</span>退货
+                <span class="check">□</span>销毁
+                <span class="check">□</span>让步接收
+                <span class="check">□</span>挑选使用
+                <span class="check">□</span>返工
+              </div>
+              <div
+                ><span class="check">□</span>其他<span
+                  class="approval-line"
+                ></span
+              ></div>
+              <div
+                >是否采取纠正预防措施:<span class="check">□</span
+                >是,编号:<span class="approval-line mid"></span
+              ></div>
+              <div class="indent-line"
+                ><span class="check">□</span>否,理由:<span
+                  class="approval-line mid"
+                ></span
+              ></div>
+              <div class="approval-sign">签名/日期:</div>
+            </td>
+            <td class="approval-cell" colspan="2">
+              <div class="bold">总经理/副总经理审批:</div>
+              <div>(不适用于受托不合格品处置)</div>
+              <div class="gm-options">
+                <span class="check">□</span>同意&nbsp;&nbsp;
+                <span class="check">□</span>不同意
+              </div>
+              <div class="approval-sign">签名/日期:</div>
+            </td>
+          </tr>
+          <tr>
+            <td class="section-title" colspan="6">不合格品的处置记录</td>
+          </tr>
+          <tr>
+            <td colspan="6" class="record-cell">
+              <div>处理方法:</div>
+              <div
+                ><span class="check">□</span>退货,退货日期:<span
+                  class="form-line"
+                ></span
+                >;退货单号:<span class="form-line wide"></span>。</div
+              >
+              <div>
+                <span class="check">□</span>销毁,方法(附销毁照片):
+                <span class="check">□</span>撕毁
+                <span class="check">□</span>掩埋
+                <span class="check">□</span>焚烧
+                <span class="check">□</span>破碎
+                <span class="check">□</span>废弃
+              </div>
+              <div class="indent-line"
+                ><span class="check">□</span>其他<span
+                  class="form-line full"
+                ></span
+              ></div>
+              <div
+                ><span class="check">□</span>让步接收,放行日期:<span
+                  class="form-line"
+                ></span
+              ></div>
+              <div>
+                <span class="check">□</span>挑选使用,挑选人:<span
+                  class="form-line wide"
+                ></span
+                >;挑选日期: <span class="form-line"></span>;
+              </div>
+              <div class="indent-line"
+                >挑选结果:<span class="form-line full"></span
+              ></div>
+              <div>
+                <span class="check">□</span>返工,返工方案:<span
+                  class="form-line wide"
+                ></span
+                >;返工日期: <span class="form-line"></span>;
+              </div>
+              <div class="indent-line"
+                >返工结果:<span class="form-line full"></span
+              ></div>
+              <div
+                ><span class="check">□</span>其他<span
+                  class="form-line short"
+                ></span
+              ></div>
+            </td>
+          </tr>
+          <tr class="bottom-sign-row">
+            <td colspan="2">执行人/日期:</td>
+            <td colspan="2">执行部门负责人/日期:</td>
+            <td colspan="2">监督人(质量部)/日期:</td>
+          </tr>
+        </table>
+
+        <div class="uqr-note">
+          注:1.当报废处置金额达 5
+          万元及以上时,必须经总经理或其授权代表审批;2.处置过程可另附照片或其他证据等。
+        </div>
+        <div class="page-number">2 / 2</div>
+      </div>
+    </div>
+    <div slot="footer">
+      <el-button @click="print">打印预览</el-button>
+      <el-button @click="visible = false">关闭</el-button>
+    </div>
+  </ele-modal>
+</template>
+
+<script>
+  export default {
+    name: 'UnqualifiedProductReview',
+    data() {
+      return {
+        visible: false,
+        form: {}
+      };
+    },
+    computed: {
+      quantityText() {
+        const quantity =
+          this.field('quantity') || this.field('unqualifiedQuantity');
+        const unit = this.field('measureUnit') || this.field('unit');
+        return `${quantity || ''}${unit || ''}`;
+      }
+    },
+    methods: {
+      open(data) {
+        this.form = data || {};
+        this.visible = true;
+      },
+      field(key) {
+        const value = this.form[key];
+        return value === 0 || value ? value : '';
+      },
+      print() {
+        const el = document.getElementById(
+          'printSection_unqualifiedProductReview'
+        );
+        const win = window.open('', '_blank');
+        win.document.open();
+        win.document.write(
+          '<html><head><title>不合格品通知及评审处置表</title>'
+        );
+        win.document.write(`<style>${this.getPrintStyle()}</style>`);
+        win.document.write('</head><body>');
+        win.document.write(el.innerHTML);
+        win.document.write('</body></html>');
+        win.document.close();
+        win.onload = function () {
+          win.print();
+        };
+      },
+      getPrintStyle() {
+        return `
+          @page{size:A4;margin:0;}
+          body{margin:0;background:#fff;font-family:SimSun,serif;color:#000;}
+          .uqr-page{width:210mm;min-height:297mm;margin:0 auto;padding:12mm 18mm 14mm;box-sizing:border-box;position:relative;page-break-after:always;background:#fff;font-size:14px;}
+          .uqr-page:last-child{page-break-after:auto;}
+          .uqr-header{height:28mm;display:flex;align-items:flex-start;justify-content:center;position:relative;padding-top:4mm;}
+          .uqr-logo{position:absolute;left:7mm;top:4mm;display:flex;align-items:center;color:#999;font-family:Arial,'Microsoft YaHei',sans-serif;}
+          .logo-mark{width:22px;height:24px;margin-right:6px;position:relative;}
+          .logo-mark span{position:absolute;display:block;border-radius:2px;background:#86b958;}
+          .logo-mark span:nth-child(1){width:6px;height:18px;left:6px;top:0;transform:rotate(20deg);}
+          .logo-mark span:nth-child(2){width:6px;height:18px;left:13px;top:6px;transform:rotate(45deg);background:#6fb6c9;}
+          .logo-mark span:nth-child(3){width:6px;height:16px;left:1px;top:10px;transform:rotate(-35deg);background:#d6c95b;}
+          .logo-cn{font-size:10px;font-weight:bold;line-height:1.2;}
+          .logo-en{font-size:5px;line-height:1.2;}
+          .uqr-company{font-size:15px;letter-spacing:8px;margin-top:4mm;}
+          .uqr-title{text-align:center;font-size:20px;font-weight:bold;margin-top:-12mm;margin-bottom:5mm;color:#666;}
+          .uqr-meta{text-align:right;font-size:12px;margin-top:-5mm;margin-bottom:1mm;}
+          .uqr-table{width:100%;border-collapse:collapse;table-layout:fixed;}
+          .uqr-table td{border:1px solid #000;padding:4px 6px;vertical-align:top;line-height:1.55;word-break:break-all;}
+          .section-title{text-align:center;font-weight:bold;font-size:16px;padding:2px 6px!important;line-height:1.2!important;}
+          .nowrap{white-space:nowrap;}
+          .check{font-family:SimSun,serif;margin:0 2px 0 7px;}
+          .fill-line,.short-line,.long-line,.approval-line,.form-line{display:inline-block;border-bottom:1px solid #000;height:16px;vertical-align:baseline;}
+          .fill-line{width:100px;margin-left:4px;}
+          .short-line{width:90px;}
+          .long-line{width:130px;}
+          .center-row td{text-align:center;vertical-align:middle;}
+          .info-row td{height:42px;text-align:center;vertical-align:middle;}
+          .source-cell{text-align:left!important;line-height:1.5!important;}
+          .source-cell span{display:inline-block;}
+          .large-cell{height:278px;}
+          .write-area{min-height:108px;white-space:pre-wrap;}
+          .write-area.small{min-height:44px;}
+          .suggestion{margin-top:2px;}
+          .signature-row{display:flex;justify-content:space-between;margin-top:14px;}
+          .confirm-cell{height:84px;}
+          .confirm-sign{text-align:center;margin-top:42px;}
+          .review-head td{text-align:center;vertical-align:middle;}
+          .review-label{text-align:center!important;vertical-align:middle!important;width:64px;font-size:15px;}
+          .review-row td{height:32px;}
+          .page-number{position:absolute;bottom:10mm;left:0;right:0;text-align:center;color:#666;font-size:12px;}
+          .crop{position:absolute;width:8mm;height:8mm;}
+          .crop-tl{left:13mm;top:13mm;border-left:1px solid #999;border-bottom:1px solid #999;}
+          .crop-tr{right:13mm;top:13mm;border-right:1px solid #999;border-bottom:1px solid #999;}
+          .crop-bl{left:13mm;bottom:13mm;border-left:1px solid #999;border-top:1px solid #999;}
+          .crop-br{right:13mm;bottom:13mm;border-right:1px solid #999;border-top:1px solid #999;}
+          .approval-cell{height:145px;}
+          .bold{font-weight:bold;}
+          .approval-options{margin:10px 0;}
+          .approval-line{width:130px;margin-left:5px;}
+          .approval-line.mid{width:165px;}
+          .indent-line{padding-left:14px;}
+          .approval-sign{margin-top:28px;text-align:left;padding-left:20px;}
+          .gm-options{text-align:center;margin:16px 0 46px;}
+          .record-cell{height:510px;font-size:14px;}
+          .record-cell div{margin-bottom:13px;}
+          .form-line{width:160px;margin:0 5px;}
+          .form-line.wide{width:220px;}
+          .form-line.full{width:520px;}
+          .form-line.short{width:160px;}
+          .bottom-sign-row td{height:56px;vertical-align:top;}
+          .uqr-note{font-size:12px;margin-top:4px;}
+        `;
+      }
+    }
+  };
+</script>
+
+<style scoped>
+  .uqr-page {
+    width: 210mm;
+    min-height: 297mm;
+    margin: 0 auto 18px;
+    padding: 12mm 18mm 14mm;
+    box-sizing: border-box;
+    position: relative;
+    page-break-after: always;
+    background: #fff;
+    font-family: SimSun, serif;
+    color: #000;
+    font-size: 14px;
+    box-shadow: 0 0 8px rgba(0, 0, 0, 0.12);
+  }
+  .uqr-page:last-child {
+    page-break-after: auto;
+  }
+  .uqr-header {
+    height: 28mm;
+    display: flex;
+    align-items: flex-start;
+    justify-content: center;
+    position: relative;
+    padding-top: 4mm;
+  }
+  .uqr-logo {
+    position: absolute;
+    left: 7mm;
+    top: 4mm;
+    display: flex;
+    align-items: center;
+    color: #999;
+    font-family: Arial, 'Microsoft YaHei', sans-serif;
+  }
+  .logo-mark {
+    width: 22px;
+    height: 24px;
+    margin-right: 6px;
+    position: relative;
+  }
+  .logo-mark span {
+    position: absolute;
+    display: block;
+    border-radius: 2px;
+    background: #86b958;
+  }
+  .logo-mark span:nth-child(1) {
+    width: 6px;
+    height: 18px;
+    left: 6px;
+    top: 0;
+    transform: rotate(20deg);
+  }
+  .logo-mark span:nth-child(2) {
+    width: 6px;
+    height: 18px;
+    left: 13px;
+    top: 6px;
+    transform: rotate(45deg);
+    background: #6fb6c9;
+  }
+  .logo-mark span:nth-child(3) {
+    width: 6px;
+    height: 16px;
+    left: 1px;
+    top: 10px;
+    transform: rotate(-35deg);
+    background: #d6c95b;
+  }
+  .logo-cn {
+    font-size: 10px;
+    font-weight: bold;
+    line-height: 1.2;
+  }
+  .logo-en {
+    font-size: 5px;
+    line-height: 1.2;
+  }
+  .uqr-company {
+    font-size: 15px;
+    letter-spacing: 8px;
+    margin-top: 4mm;
+  }
+  .uqr-title {
+    text-align: center;
+    font-size: 20px;
+    font-weight: bold;
+    margin-top: -12mm;
+    margin-bottom: 5mm;
+    color: #666;
+  }
+  .uqr-meta {
+    text-align: right;
+    font-size: 12px;
+    margin-top: -5mm;
+    margin-bottom: 1mm;
+  }
+  .uqr-table {
+    width: 100%;
+    border-collapse: collapse;
+    table-layout: fixed;
+  }
+  .uqr-table td {
+    border: 1px solid #000;
+    padding: 4px 6px;
+    vertical-align: top;
+    line-height: 1.55;
+    word-break: break-all;
+  }
+  .section-title {
+    text-align: center;
+    font-weight: bold;
+    font-size: 16px;
+    padding: 2px 6px !important;
+    line-height: 1.2 !important;
+  }
+  .nowrap {
+    white-space: nowrap;
+  }
+  .check {
+    font-family: SimSun, serif;
+    margin: 0 2px 0 7px;
+  }
+  .fill-line,
+  .short-line,
+  .long-line,
+  .approval-line,
+  .form-line {
+    display: inline-block;
+    border-bottom: 1px solid #000;
+    height: 16px;
+    vertical-align: baseline;
+  }
+  .fill-line {
+    width: 100px;
+    margin-left: 4px;
+  }
+  .short-line {
+    width: 90px;
+  }
+  .long-line {
+    width: 130px;
+  }
+  .center-row td {
+    text-align: center;
+    vertical-align: middle;
+  }
+  .info-row td {
+    height: 42px;
+    text-align: center;
+    vertical-align: middle;
+  }
+  .source-cell {
+    text-align: left !important;
+    line-height: 1.5 !important;
+  }
+  .source-cell span {
+    display: inline-block;
+  }
+  .large-cell {
+    height: 278px;
+  }
+  .write-area {
+    min-height: 108px;
+    white-space: pre-wrap;
+  }
+  .write-area.small {
+    min-height: 44px;
+  }
+  .suggestion {
+    margin-top: 2px;
+  }
+  .signature-row {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 14px;
+  }
+  .confirm-cell {
+    height: 84px;
+  }
+  .confirm-sign {
+    text-align: center;
+    margin-top: 42px;
+  }
+  .review-head td {
+    text-align: center;
+    vertical-align: middle;
+  }
+  .review-label {
+    text-align: center !important;
+    vertical-align: middle !important;
+    width: 64px;
+    font-size: 15px;
+  }
+  .review-row td {
+    height: 32px;
+  }
+  .page-number {
+    position: absolute;
+    bottom: 10mm;
+    left: 0;
+    right: 0;
+    text-align: center;
+    color: #666;
+    font-size: 12px;
+  }
+  .crop {
+    position: absolute;
+    width: 8mm;
+    height: 8mm;
+  }
+  .crop-tl {
+    left: 13mm;
+    top: 13mm;
+    border-left: 1px solid #999;
+    border-bottom: 1px solid #999;
+  }
+  .crop-tr {
+    right: 13mm;
+    top: 13mm;
+    border-right: 1px solid #999;
+    border-bottom: 1px solid #999;
+  }
+  .crop-bl {
+    left: 13mm;
+    bottom: 13mm;
+    border-left: 1px solid #999;
+    border-top: 1px solid #999;
+  }
+  .crop-br {
+    right: 13mm;
+    bottom: 13mm;
+    border-right: 1px solid #999;
+    border-top: 1px solid #999;
+  }
+  .approval-cell {
+    height: 145px;
+  }
+  .bold {
+    font-weight: bold;
+  }
+  .approval-options {
+    margin: 10px 0;
+  }
+  .approval-line {
+    width: 130px;
+    margin-left: 5px;
+  }
+  .approval-line.mid {
+    width: 165px;
+  }
+  .indent-line {
+    padding-left: 14px;
+  }
+  .approval-sign {
+    margin-top: 28px;
+    text-align: left;
+    padding-left: 20px;
+  }
+  .gm-options {
+    text-align: center;
+    margin: 16px 0 46px;
+  }
+  .record-cell {
+    height: 510px;
+    font-size: 14px;
+  }
+  .record-cell div {
+    margin-bottom: 13px;
+  }
+  .form-line {
+    width: 160px;
+    margin: 0 5px;
+  }
+  .form-line.wide {
+    width: 220px;
+  }
+  .form-line.full {
+    width: 520px;
+  }
+  .form-line.short {
+    width: 160px;
+  }
+  .bottom-sign-row td {
+    height: 56px;
+    vertical-align: top;
+  }
+  .uqr-note {
+    font-size: 12px;
+    margin-top: 4px;
+  }
+</style>