Преглед на файлове

feat(qms): 新增质检报告页面

- 添加质检报告列表与详情页面
- 支持按模板动态渲染报告内容
- 实现报告的查看、编辑、保存与删除功能
- 新增质检报告相关 API 封装
- 更新版本号至 V1.0.4.43
yusheng преди 2 дни
родител
ревизия
ed45d74b94

+ 113 - 0
api/inspectionReport/index.js

@@ -0,0 +1,113 @@
+import { get, postJ, deleteApi } from "@/utils/request";
+import Vue from "vue";
+
+// 质检报告列表
+export async function getReportList(data) {
+  const res = await get(
+    Vue.prototype.apiUrl + "/qms/quality_work_order/pageByReport",
+    data,
+  );
+  if (res.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.message));
+}
+
+// 检测报告基本信息
+export async function queryInspectionReportData({ id, type }) {
+  const res = await get(
+    Vue.prototype.apiUrl +
+      `/qms/quality_work_order/queryInspectionReportData/${id}`,
+    { type },
+  );
+  if (res.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.message));
+}
+
+// 检测报告检测项列表信息
+export async function queryInspectionReportList({ id, type }) {
+  const res = await get(
+    Vue.prototype.apiUrl +
+      `/qms/quality_work_order/queryInspectionReportList/${id}`,
+    { type },
+  );
+  if (res.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.message));
+}
+
+// 生成/修改检测报告
+export async function generateReport(data) {
+  const res = await postJ(
+    Vue.prototype.apiUrl + "/qms/quality_work_order/generateReport",
+    data,
+  );
+  if (res.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.message));
+}
+
+// 报表模板分页查询(质检工单生成报告时选择模板)
+export async function getQmsReportTemplatePageList(params) {
+  const res = await get(
+    Vue.prototype.apiUrl + "/main/qmsreporttemplate/page",
+    params,
+  );
+  if (res.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.message));
+}
+
+// 生成编码(报告单号)
+export async function getCode(code) {
+  const res = await get(
+    Vue.prototype.apiUrl + `/main/codemanage/getCode/${code}`,
+  );
+  if (res.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.message));
+}
+
+// 删除检测报告
+export async function deleteReport(id) {
+  const res = await deleteApi(
+    Vue.prototype.apiUrl + `/qms/quality_work_order/deleteReport/${id}`,
+  );
+  if (res.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 不合格品台账列表
+ */
+export async function getList(data) {
+  let par = new URLSearchParams(data);
+
+  const res = await get(
+    Vue.prototype.apiUrl + `/qms/unqualifiedproducts/page?` + par,
+  );
+  if (res.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.message));
+}
+
+// 详情页面
+
+export async function getById(id) {
+  const res = await get(
+    Vue.prototype.apiUrl + `/qms/unqualifiedproducts/getById/${id}`,
+  );
+  if (res.code == 0) {
+    return res.data;
+  }
+  return Promise.reject(new Error(res.message));
+}

+ 1 - 1
manifest.json

@@ -2,7 +2,7 @@
     "name" : "AiMill工业互联网平台",
     "appid" : "__UNI__45B3907",
     "description" : "",
-    "versionName" : "V1.0.4.42",
+    "versionName" : "V1.0.4.43",
     "versionCode" : "100",
     "transformPx" : false,
     "h5" : {

+ 16 - 0
pages.json

@@ -2723,6 +2723,22 @@
 				"navigationBarTextStyle": "black"
 			}
 		},
+		{
+			"path": "pages/qms/inspectionReport/index",
+			"style": {
+				"navigationBarTitleText": "质检报告",
+				"navigationStyle": "custom",
+				"navigationBarTextStyle": "black"
+			}
+		},
+		{
+			"path": "pages/qms/inspectionReport/detail",
+			"style": {
+				"navigationBarTitleText": "检测报告",
+				"navigationStyle": "custom",
+				"navigationBarTextStyle": "black"
+			}
+		},
 		{
 			"path": "pages/pcs/productionRecords",
 			"style": {

+ 806 - 0
pages/qms/inspectionReport/detail.vue

@@ -0,0 +1,806 @@
+<template>
+  <view class="mainBox">
+    <uni-nav-bar
+      fixed="true"
+      statusBar="true"
+      left-icon="back"
+      :title="isEdit ? '编辑检测报告' : '检测报告'"
+      @clickLeft="back"
+    >
+    </uni-nav-bar>
+
+    <view class="page-body">
+      <!-- 按 reportTemplateCode 渲染对应模板 -->
+      <component
+        :is="templateComp"
+        v-if="templateComp"
+        :basicInfoData="basicInfoData"
+        :inspectionItems="inspectionItems"
+        :isEdit="isEdit"
+        ref="templateRef"
+      ></component>
+      <view class="empty-tip" v-else-if="!loading"
+        >暂不支持该报告模板({{ templateCode }})</view
+      >
+
+      <!-- 检验结论(对应Web端报告容器通用区) -->
+      <view class="card">
+        <view class="card-title">
+          <view class="herder_text"></view>
+          <view class="card-title-text">检验结论</view>
+        </view>
+        <view class="info-row column" v-if="isEdit">
+          <text class="label">结论</text>
+          <u-radio-group
+            class="radio-group"
+            v-model="basicInfoData.qualityResults"
+            placement="row"
+            iconSize="28"
+            labelSize="24"
+            @change="selectResult"
+          >
+            <u-radio
+              v-for="(it, idx) in qualityResultsList"
+              :key="idx"
+              :customStyle="{ marginRight: '12rpx', marginTop: '8rpx' }"
+              :label="it.label"
+              :name="it.value"
+            ></u-radio>
+          </u-radio-group>
+        </view>
+        <view class="info-row" v-else>
+          <text class="label">结论</text>
+          <text class="value">{{
+            qualityResultsText(basicInfoData.qualityResults)
+          }}</text>
+        </view>
+        <view class="info-row">
+          <text class="label">合格数</text>
+          <text class="value">{{ basicInfoData.qualifiedNumber || 0 }}</text>
+        </view>
+        <view class="info-row">
+          <text class="label">不合格数</text>
+          <text class="value">{{ basicInfoData.noQualifiedNumber || 0 }}</text>
+        </view>
+        <view class="info-row">
+          <text class="label">留样数(合格)</text>
+          <text class="value">{{
+            basicInfoData.retainedSampleQuantity || 0
+          }}</text>
+        </view>
+        <view class="info-row">
+          <text class="label">留样数(不合格)</text>
+          <text class="value">{{
+            basicInfoData.retainedSampleUnqualified || 0
+          }}</text>
+        </view>
+        <view class="info-row">
+          <text class="label">消耗数(合格)</text>
+          <text class="value">{{ basicInfoData.lossNumber || 0 }}</text>
+        </view>
+        <view class="info-row">
+          <text class="label">消耗数(不合格)</text>
+          <text class="value">{{
+            basicInfoData.lossNumberUnqualified || 0
+          }}</text>
+        </view>
+      </view>
+
+      <view style="height: 160rpx"></view>
+    </view>
+
+    <!-- 底部操作栏 -->
+    <view class="footerButton" v-if="canEdit">
+      <view>
+        <u-button v-if="isEdit" @click="cancelEdit">取消</u-button>
+        <u-button
+          v-else
+          type="primary"
+          text="编辑"
+          @click="enterEdit"
+        ></u-button>
+      </view>
+      <view v-if="isEdit">
+        <u-button
+          type="primary"
+          text="保存"
+          :loading="saving"
+          @click="save"
+        ></u-button>
+      </view>
+    </view>
+
+    <u-toast ref="uToast" />
+  </view>
+</template>
+
+<script>
+import inspection_report1 from "./template/inspection_report1.vue";
+import inspection_report2 from "./template/inspection_report2.vue";
+import inspection_report4 from "./template/inspection_report4.vue";
+import inspection_report5 from "./template/inspection_report5.vue";
+import inspection_report6 from "./template/inspection_report6.vue";
+import {
+  queryInspectionReportData,
+  queryInspectionReportList,
+  generateReport,
+  getCode,
+  getList,
+  getById,
+} from "@/api/inspectionReport/index.js";
+import {
+  queryQualitySamplContent,
+  queryQualityInventory,
+} from "@/api/inspectionWork";
+const templateComponents = {
+  inspection_report1,
+  inspection_report2,
+  inspection_report4,
+  inspection_report5,
+  inspection_report6,
+};
+
+const qualityResultsList = [
+  { label: "整单合格", value: 1 },
+  { label: "整单不合格", value: 2 },
+  { label: "部分合格", value: 3 },
+  { label: "整单让步接收", value: 4 },
+];
+
+export default {
+  components: {
+    inspection_report1,
+    inspection_report2,
+    inspection_report4,
+    inspection_report5,
+    inspection_report6,
+  },
+  data() {
+    return {
+      id: "",
+      isEdit: false,
+      saving: false,
+      loading: false,
+      currentRow: {},
+      basicInfoData: {},
+      inspectionItems: [],
+      inventoryList: [],
+      sampleList: [],
+      oldList: [],
+      poList: [],
+
+      qualityResultsList,
+    };
+  },
+  computed: {
+    templateCode() {
+      let code =
+        this.currentRow.reportTemplateCode ||
+        this.basicInfoData.reportTemplateCode ||
+        (this.currentRow.reportTemplateJson &&
+          this.currentRow.reportTemplateJson.reportTemplateCode) ||
+        "";
+      code = String(code)
+        .replace(/\.vue$/i, "")
+        .trim();
+      if (!templateComponents[code]) {
+        code = "inspection_report1";
+      }
+      return code;
+    },
+    templateComp() {
+      return templateComponents[this.templateCode] || inspection_report1;
+    },
+    canEdit() {
+      // 从工单“生成质检报告”进入时可直接编辑;查看模式仅未提交/审核不通过可编辑
+      if (this.isEdit) {
+        return true;
+      }
+      const status = this.currentRow.reportApprovalStatus;
+      return status == 0 || status == 3;
+    },
+    // 可用总数 = 总数 - 留样数 - 消耗数
+    availableTotal() {
+      if (!this.sampleList.length) return this.basicInfoData.total;
+      const usedQuantity = this.sampleList
+        .filter((item) => item.disposeType == 6 || item.disposeType == 7)
+        .reduce((sum, item) => sum + (item.measureQuantity || 0), 0);
+      return this.basicInfoData.total - usedQuantity;
+    },
+  },
+  onLoad(options) {
+    this.id = options.id || "";
+    this.isEdit = options.edit == "1";
+    const row = uni.getStorageSync("inspectionReportRow");
+    if (row && row.id == this.id) {
+      this.currentRow = row;
+      uni.removeStorageSync("inspectionReportRow");
+    } else {
+      this.currentRow = { id: this.id };
+    }
+    // URL兜底:模板编码(从工单生成/查看时带入)
+    if (!this.currentRow.reportTemplateCode && options.templateCode) {
+      this.$set(
+        this.currentRow,
+        "reportTemplateCode",
+        decodeURIComponent(options.templateCode),
+      );
+    }
+    this.init();
+  },
+  methods: {
+    async init() {
+      this.loading = true;
+      uni.showLoading({ title: "加载中", mask: true });
+      try {
+        if (
+          this.currentRow.reportTemplateJson &&
+          this.currentRow.reportTemplateJson.basicInfoData
+        ) {
+          this.basicInfoData =
+            this.currentRow.reportTemplateJson.basicInfoData || {};
+          this.inspectionItems =
+            this.currentRow.reportTemplateJson.inspectionItems || [];
+          this.afterDataReady();
+          this.$set(
+            this.basicInfoData,
+            "qualityResults",
+            this.basicInfoData.qualityResults,
+          );
+          this.$set(
+            this.basicInfoData,
+            "qualifiedNumber",
+            this.basicInfoData.qualifiedNumber,
+          );
+          this.$set(
+            this.basicInfoData,
+            "noQualifiedNumber",
+            this.basicInfoData.noQualifiedNumber,
+          );
+        } else {
+          await this.getBasicInfo();
+          await this.getInspectionItems();
+          this.afterDataReady();
+
+          this.$set(
+            this.basicInfoData,
+            "qualityResults",
+            this.currentRow.noQualifiedNumber == this.basicInfoData.total
+              ? 2
+              : this.currentRow.noQualifiedNumber == 0
+                ? 1
+                : 3,
+          );
+          this.$set(
+            this.basicInfoData,
+            "qualifiedNumber",
+            this.currentRow.qualifiedNumber,
+          );
+          this.$set(
+            this.basicInfoData,
+            "noQualifiedNumber",
+            this.currentRow.noQualifiedNumber,
+          );
+          this.$set(this.basicInfoData, "qualityWorkerId", this.currentRow.id);
+          this.$set(
+            this.basicInfoData,
+            "workOrderId",
+            this.currentRow.workOrderId,
+          );
+          this.$set(this.basicInfoData, "type", this.type || 0);
+          this.$set(
+            this.basicInfoData,
+            "source",
+            this.currentRow.workOrderCode,
+          );
+
+          this.$set(
+            this.basicInfoData,
+            "qualityType",
+            this.currentRow.qualityType,
+          );
+          this.initUnqualifiedProduct();
+        }
+      } catch (err) {
+        this.$refs.uToast.show({
+          type: "error",
+          icon: false,
+          message: err.message || "数据获取失败",
+        });
+      } finally {
+        this.loading = false;
+        uni.hideLoading();
+      }
+    },
+    getBasicInfo() {
+      return queryInspectionReportData({
+        id: this.currentRow.id || this.id,
+        type: this.currentRow.type || 0,
+      }).then((res) => {
+        this.basicInfoData = res || {};
+        this.basicInfoData.reportCode = res.reportCode || "";
+      });
+    },
+    getInspectionItems() {
+      return queryInspectionReportList({
+        id: this.currentRow.id || this.id,
+        type: this.currentRow.type || 0,
+      }).then((res) => {
+        this.inspectionItems = res || [];
+      });
+    },
+
+    async initUnqualifiedProduct() {
+      const data = await getList({
+        sourceCode: this.currentRow.code,
+      });
+      // console.log(data,'data')
+      if (data.list[0]) {
+        const res = await getById(data.list[0].id);
+        this.poList = res.poList || [];
+        this.oldList = JSON.parse(JSON.stringify(res.poList || []));
+      }
+      await this.getQueryQualityInventory();
+      await this.queryQualitySamplContent();
+      this.setQualifiedNumber();
+    },
+
+    async getQueryQualityInventory() {
+      const res = await queryQualityInventory({
+        qualityWorkerId: this.basicInfoData.qualityWorkerId,
+        size: -1,
+      });
+
+      if (res.list.length > 0) {
+        this.inventoryList = res.list;
+      }
+      return;
+    },
+
+    async queryQualitySamplContent() {
+      const res = await queryQualitySamplContent({
+        qualityWorkerId: this.basicInfoData.qualityWorkerId,
+        size: 1000,
+      });
+      this.sampleList = res.list;
+      let retainedSampleQuantity = 0;
+      let retainedSampleUnqualified = 0;
+      let lossNumber = 0;
+      let lossNumberUnqualified = 0;
+      [
+        "retainedSampleQuantity",
+        "retainedSampleUnqualified",
+        "lossNumber",
+        "lossNumberUnqualified",
+      ].forEach((key) => {
+        this[key] = 0;
+      });
+      if (this.sampleList.length > 0 && this.inventoryList.length > 0) {
+        const processedList = this.sampleList.filter(
+          (item) => item.disposeType == 6 || item.disposeType == 7,
+        );
+        processedList.forEach((sampleItem) => {
+          if (sampleItem.disposeType == 6) {
+            sampleItem.qualityResults == 2
+              ? (retainedSampleUnqualified += sampleItem.measureQuantity)
+              : (retainedSampleQuantity += sampleItem.measureQuantity);
+          } else {
+            sampleItem.qualityResults == 2
+              ? (lossNumberUnqualified += sampleItem.measureQuantity)
+              : (lossNumber += sampleItem.measureQuantity);
+          }
+          const inventoryItem = this.inventoryList.find(
+            (item) => item.sourceId === sampleItem.sourceId,
+          );
+          if (inventoryItem) {
+            inventoryItem.measureQuantity = Math.max(
+              0,
+              (inventoryItem.measureQuantity || 0) -
+                (sampleItem.measureQuantity || 0),
+            );
+          }
+        });
+        if (this.inventoryList.length == 1 && !this.inventoryList[0].sourceId) {
+          this.inventoryList[0].measureQuantity =
+            this.inventoryList[0].measureQuantity -
+            lossNumber -
+            lossNumberUnqualified -
+            retainedSampleQuantity -
+            retainedSampleUnqualified;
+        }
+      }
+      this.$set(
+        this.basicInfoData,
+        "retainedSampleQuantity",
+        retainedSampleQuantity,
+      );
+      this.$set(
+        this.basicInfoData,
+        "retainedSampleUnqualified",
+        retainedSampleUnqualified,
+      );
+      this.$set(this.basicInfoData, "lossNumber", lossNumber);
+      this.$set(
+        this.basicInfoData,
+        "lossNumberUnqualified",
+        lossNumberUnqualified,
+      );
+      return;
+    },
+
+    setQualifiedNumber() {
+      let isQualifiedNumber = true;
+      console.log(this.poList, "this.poList");
+      let noQualifiedNumber = this.poList
+        .filter((item) => ![5, 6, 7].includes(item.disposeType))
+        .reduce((acc, cur) => acc + cur.measureQuantity, 0);
+
+      if (
+        this.poList
+          .filter((item) => item.disposeType == 5)
+          .reduce((acc, cur) => acc + cur.measureQuantity, 0) ==
+        this.availableTotal
+      ) {
+        isQualifiedNumber = false;
+      }
+
+      this.basicInfoData.qualifiedNumber =
+        this.availableTotal - noQualifiedNumber;
+      this.basicInfoData.noQualifiedNumber = noQualifiedNumber;
+
+      console.log(noQualifiedNumber, "noQualifiedNumber");
+      console.log(this.basicInfoData.qualifiedNumber, "qualifiedNumber");
+      if (!noQualifiedNumber) {
+        if (isQualifiedNumber) {
+          this.basicInfoData.qualityResults = 1;
+        } else {
+          this.basicInfoData.qualityResults = 4;
+        }
+      } 
+      if (!this.basicInfoData.qualifiedNumber) {
+        this.basicInfoData.qualityResults = 2;
+      }
+
+      if (this.basicInfoData.qualifiedNumber && noQualifiedNumber) {
+        this.basicInfoData.qualityResults = 3;
+      }
+      console.log(this.basicInfoData, "this.basicInfoData");
+    },
+    selectResult(qualityResults) {
+      if (qualityResults == 1) {
+        this.poList = JSON.parse(JSON.stringify(this.oldList));
+        this.poList = this.poList.map((item) => {
+          if (item.disposalStatus != 2) {
+            item.disposalStatus = 1;
+            item.disposeType = 5;
+          }
+
+          return item;
+        });
+      }
+      if (qualityResults == 4) {
+        this.poList = [
+          ...JSON.parse(JSON.stringify(this.inventoryList)),
+          ...JSON.parse(
+            JSON.stringify(
+              this.oldList.filter((item) => item.disposalStatus == 2),
+            ),
+          ),
+        ];
+        this.poList = this.poList.map((item) => {
+          if (item.disposalStatus != 2) {
+            item.disposalStatus = 1;
+            item.disposeType = 5;
+          }
+          return item;
+        });
+      }
+      if (qualityResults == 2) {
+        this.poList = [
+          ...JSON.parse(JSON.stringify(this.inventoryList)),
+          ...JSON.parse(
+            JSON.stringify(
+              this.oldList.filter((item) => item.disposalStatus == 2),
+            ),
+          ),
+        ];
+      }
+      if (qualityResults == 3) {
+        this.poList = JSON.parse(JSON.stringify(this.oldList));
+      }
+      this.setQualifiedNumber();
+    },
+
+    afterDataReady() {
+      const vos = this.currentRow.reportApprovalTaskVos || [];
+      const reviewTime = vos[vos.length - 2]?.endTime?.split(" ")[0] || "";
+      const approvedDate = vos[vos.length - 3]?.endTime?.split(" ")[0] || "";
+      const reviewer = vos[vos.length - 2]?.approvalUserName || "";
+      const checker = vos[vos.length - 3]?.approvalUserName || "";
+
+      this.$set(
+        this.basicInfoData,
+        "inspectionTime",
+        (this.basicInfoData.inspectionTime || "").split(" ")[0] || "",
+      );
+      this.$set(
+        this.basicInfoData,
+        "reviewTime",
+        (this.basicInfoData.reviewTime || "").split(" ")[0] || reviewTime || "",
+      );
+      this.$set(
+        this.basicInfoData,
+        "approvedDate",
+        (this.basicInfoData.approvedDate || "").split(" ")[0] ||
+          approvedDate ||
+          "",
+      );
+      this.$set(
+        this.basicInfoData,
+        "reviewer",
+        this.basicInfoData.reviewer || reviewer || "",
+      );
+      this.$set(
+        this.basicInfoData,
+        "checker",
+        this.basicInfoData.checker || checker || "",
+      );
+
+      // 版本号:参考PC端由模板版本字段拼接
+      if (!this.basicInfoData.version) {
+        const v =
+          (this.currentRow.versionSymbol ?? "") +
+          (this.currentRow.bigVersion ?? "") +
+          (this.currentRow.versionMark ?? "") +
+          (this.currentRow.smallVersion ?? "");
+        if (v) {
+          this.$set(this.basicInfoData, "version", v);
+        }
+      }
+
+      // 编辑日期控件默认值
+      [
+        "reportDate",
+        "expirationDate",
+        "manufactureTime",
+        "pleaseVerifyDate",
+        "inspectionTime",
+        "reviewTime",
+        "approvedDate",
+      ].forEach((key) => {
+        if (!this.basicInfoData[key]) {
+          this.$set(this.basicInfoData, key, "");
+        }
+      });
+
+      if (!this.basicInfoData.source && this.currentRow.workOrderCode) {
+        this.$set(this.basicInfoData, "source", this.currentRow.workOrderCode);
+      }
+    },
+    qualityResultsText(value) {
+      return (
+        (qualityResultsList.find((it) => it.value == value) || {}).label || ""
+      );
+    },
+    enterEdit() {
+      this.isEdit = true;
+      if (!this.basicInfoData.reportNumber) {
+        getCode("quality_Inspection_report_number")
+          .then((res) => {
+            this.$set(this.basicInfoData, "reportNumber", res);
+          })
+          .catch(() => {});
+      }
+    },
+    cancelEdit() {
+      this.isEdit = false;
+      // 放弃修改,重新加载原始数据
+      this.init();
+    },
+    getValueUnqualifiedProducts() {
+      let unqualifiedProducts = {};
+      if (this.poList.length > 0) {
+        unqualifiedProducts.poList = this.poList.map((item) => {
+          item.qualityResults = 1;
+          item.qualityStatus = 1;
+          item.qualityWorkOrderId = this.basicInfoData.qualityWorkerId;
+          return item;
+        });
+        [
+          "batchNo",
+          "brandNum",
+          "categoryCode",
+          "categoryId",
+          "categoryName",
+          "factoriesId",
+          "measureUnit",
+          "modelType",
+          "produceRoutingId",
+          "produceRoutingName",
+          "productCategory",
+        ].forEach((item) => {
+          unqualifiedProducts[item] = this.poList[0][item];
+        });
+
+        unqualifiedProducts.qualityType = this.basicInfoData.qualityType;
+        unqualifiedProducts.qualityWorkOrderId =
+          this.basicInfoData.qualityWorkerId;
+        unqualifiedProducts.sourceCode = this.basicInfoData.source;
+        unqualifiedProducts.quantity = this.poList.reduce(
+          (total, item) => total + item.measureQuantity,
+          0,
+        );
+        unqualifiedProducts.sourceType = this.basicInfoData.workOrderId ? 1 : 0;
+
+        return unqualifiedProducts;
+      }
+      return null;
+    },
+    save() {
+      if (!this.basicInfoData.reportNumber) {
+        this.$refs.uToast.show({
+          type: "error",
+          icon: false,
+          message: "请输入报告单号",
+        });
+        return;
+      }
+      this.saving = true;
+      const params = {
+        id: this.currentRow.id || this.id,
+        type: this.currentRow.type || this.basicInfoData.type || 0,
+        reportTemplateId:
+          this.currentRow.reportTemplateId || this.currentRow.id || this.id,
+        reportTemplateCode:
+          this.currentRow.reportTemplateCode || this.currentRow.code || "",
+        reportTemplateName:
+          this.currentRow.reportTemplateName || this.currentRow.name || "",
+        unqualifiedProducts: this.getValueUnqualifiedProducts(),
+        reportTemplateJson: {
+          template:
+            (this.currentRow.reportTemplateJson &&
+              this.currentRow.reportTemplateJson.template) ||
+            "",
+          basicInfoData: this.basicInfoData,
+          inspectionItems: this.inspectionItems,
+        },
+      };
+      // console.log(params, 'params')
+      // return
+      generateReport(params)
+        .then(() => {
+          this.saving = false;
+          this.$refs.uToast.show({
+            type: "success",
+            icon: false,
+            message: "保存成功",
+          });
+          uni.$emit("inspectionReportRefresh");
+          uni.$emit("successInit");
+          setTimeout(() => {
+            this.back();
+          }, 500);
+        })
+        .catch((err) => {
+          this.saving = false;
+          this.$refs.uToast.show({
+            type: "error",
+            icon: false,
+            message: err.message || "保存失败",
+          });
+        });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.mainBox {
+  background-color: #f3f8fb;
+  height: 100vh;
+  font-size: 27rpx;
+  overflow-y: auto;
+}
+
+.page-body {
+  padding-bottom: 20rpx;
+}
+
+.card {
+  width: 720rpx;
+  background: #fff;
+  margin: 20rpx auto 0;
+  border-radius: 30rpx;
+  padding: 26rpx;
+  box-sizing: border-box;
+
+  .card-title {
+    display: flex;
+    align-items: center;
+    font-weight: bold;
+    font-size: 30rpx;
+    margin-bottom: 16rpx;
+
+    .herder_text {
+      min-width: 10rpx;
+      height: 32rpx;
+      border-radius: 10rpx;
+      background: #00c0a1;
+      margin-right: 12rpx;
+      margin-top: 5rpx;
+    }
+  }
+
+  .info-row {
+    display: flex;
+    align-items: center;
+    margin-top: 20rpx;
+    font-size: 26rpx;
+
+    .label {
+      color: #666;
+      min-width: 150rpx;
+      font-weight: bold;
+    }
+
+    .value {
+      flex: 1;
+      word-break: break-all;
+      color: #333;
+    }
+
+    &.column {
+      flex-direction: column;
+      align-items: flex-start;
+
+      .label {
+        margin-bottom: 12rpx;
+      }
+
+      .value {
+        width: 100%;
+      }
+    }
+  }
+}
+
+.radio-group {
+  flex-wrap: wrap;
+}
+
+.empty-tip {
+  color: #999;
+  text-align: center;
+  padding: 60rpx 40rpx;
+  font-size: 26rpx;
+}
+
+.footerButton {
+  width: 100%;
+  height: 110rpx;
+  display: flex;
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  z-index: 10;
+  background-color: #fff;
+  padding: 12rpx 24rpx;
+  box-sizing: border-box;
+
+  /deep/.u-button {
+    height: 100%;
+  }
+
+  > view {
+    flex: 1;
+    margin: 0 10rpx;
+  }
+}
+/deep/.u-radio__icon-wrap {
+  width: 30rpx !important;
+  height: 30rpx !important;
+}
+/deep/.u-radio__text {
+  font-size: 26rpx !important;
+}
+</style>

+ 438 - 0
pages/qms/inspectionReport/index.vue

@@ -0,0 +1,438 @@
+<template>
+  <view class="mainBox">
+    <uni-nav-bar fixed="true" statusBar="true" left-icon="back" title="质检报告" @clickLeft="back">
+    </uni-nav-bar>
+
+    <!-- 搜索栏 -->
+    <view class="top-wrapper">
+      <uni-section>
+        <uni-easyinput
+          prefixIcon="search"
+          style="width: 420rpx"
+          v-model="searchForm.reportNumber"
+          placeholder="请输入报告编号"
+          @confirm="doSearch"
+        >
+        </uni-easyinput>
+      </uni-section>
+      <button class="search_btn filter_btn" @click="filterVisible = true">筛选</button>
+      <button class="search_btn" @click="doSearch">搜索</button>
+    </view>
+
+    <view class="list-content">
+      <u-list @scrolltolower="scrolltolower" class="listContent">
+        <view v-for="(item, idx) in tableList" :key="item.id" class="card-wrapper">
+          <myCard
+            :item="item"
+            :index="idx + 1"
+            :btnList="getBtnList(item)"
+            :columns="cardColumns"
+            :title="item.reportNumber || item.code"
+            :status="statusText(item.reportApprovalStatus)"
+            @goDetail="openDetail"
+            @edit="openEdit(item)"
+            @delete="deleteReportBtn(item)"
+          />
+        </view>
+        <view style="width: 100%; height: 40rpx"></view>
+        <u-loadmore :status="loadStatus" />
+        <view style="margin-top: 20vh" v-if="tableList.length == 0">
+          <u-empty iconSize="150" textSize="32" text="暂无数据" />
+        </view>
+      </u-list>
+    </view>
+
+    <!-- 筛选弹窗 -->
+    <u-popup :show="filterVisible" mode="bottom" :round="20" closeable @close="filterVisible = false" bgColor="#fff">
+      <view class="search_wrap">
+        <view class="title">筛选</view>
+        <view class="form_item">
+          <view class="label">工单编号</view>
+          <u-input v-model="filterForm.code" placeholder="请输入"></u-input>
+        </view>
+        <view class="form_item">
+          <view class="label">产品编码</view>
+          <u-input v-model="filterForm.productCode" placeholder="请输入"></u-input>
+        </view>
+        <view class="form_item">
+          <view class="label">产品名称</view>
+          <u-input v-model="filterForm.productName" placeholder="请输入"></u-input>
+        </view>
+        <view class="form_item">
+          <view class="label">型号</view>
+          <u-input v-model="filterForm.modelType" placeholder="请输入"></u-input>
+        </view>
+        <view class="form_item">
+          <view class="label">规格</view>
+          <u-input v-model="filterForm.specification" placeholder="请输入"></u-input>
+        </view>
+        <view class="form_item">
+          <view class="label">审批状态</view>
+          <u-radio-group class="radio-group" v-model="filterForm.reportApprovalStatus" placement="row" iconSize="32" labelSize="28">
+            <u-radio v-for="(it, idx) in statusList" :key="idx" :customStyle="{ marginRight: '20rpx' }" :label="it.label" :name="it.value"></u-radio>
+          </u-radio-group>
+        </view>
+        <view class="form_item">
+          <view class="label">记录方法</view>
+          <u-radio-group class="radio-group" v-model="filterForm.recordingMethod" placement="row" iconSize="32" labelSize="28">
+            <u-radio v-for="(it, idx) in recordingMethodList" :key="idx" :customStyle="{ marginRight: '20rpx' }" :label="it.label" :name="it.value"></u-radio>
+          </u-radio-group>
+        </view>
+        <view class="btns">
+          <button class="btn reset" @click="resetFilter">重置</button>
+          <button class="btn confirm" @click="confirmFilter">确定</button>
+        </view>
+      </view>
+    </u-popup>
+
+    <u-toast ref="uToast" />
+  </view>
+</template>
+
+<script>
+  import myCard from "@/components/myCard.vue";
+  import { getReportList, deleteReport } from "@/api/inspectionReport/index.js";
+  import { recordingMethodList } from "@/utils/utils.js";
+
+  const statusList = [
+    { value: 0, label: "未提交" },
+    { value: 1, label: "审核中" },
+    { value: 2, label: "已审核" },
+    { value: 3, label: "审核不通过" },
+    { value: 7, label: "作废" }
+  ];
+
+  export default {
+    components: { myCard },
+    data() {
+      return {
+        statusList,
+        recordingMethodList,
+        tableList: [],
+        page: 1,
+        size: 10,
+        isEnd: false,
+        loadStatus: "loadmore",
+        filterVisible: false,
+        searchForm: {
+          reportNumber: ""
+        },
+        filterForm: {
+          code: "",
+          productCode: "",
+          productName: "",
+          modelType: "",
+          specification: "",
+          reportApprovalStatus: "",
+          recordingMethod: ""
+        },
+        cardColumns: [
+          [{ prop: "code", label: "工单编号", className: "perce100" }],
+          [
+            {
+              prop: "productName",
+              label: "产品",
+              className: "perce100",
+              formatter: (item) =>
+                (item.productName || "") + "(" + (item.productCode || "") + ")"
+            }
+          ],
+          [
+            {
+              prop: "specification",
+              label: "规格型号",
+              className: "perce50",
+              formatter: (item) =>
+                (item.specification || "") + "/" + (item.modelType || "")
+            },
+            {
+              prop: "recordingMethod",
+              label: "记录方法",
+              className: "perce50",
+              formatter: (item) => this.recordingMethodText(item.recordingMethod)
+            }
+          ],
+          [
+            {
+              prop: "total",
+              label: "数量",
+              className: "perce50",
+              formatter: (item) =>
+                item.total ? item.total + (item.sampleMeasureUnit || "") : ""
+            },
+            { prop: "reportDate", label: "报告日期", className: "perce50" }
+          ],
+          [
+            {
+              prop: "reportTemplateCreateUserName",
+              label: "创建人",
+              className: "perce50"
+            },
+            {
+              prop: "reportApprovalTime",
+              label: "审批时间",
+              className: "perce50",
+              formatter: (item) =>
+                (item.reportApprovalTime || "") +
+                (item.reportApprovalUserName ? " " + item.reportApprovalUserName : "")
+            }
+          ],
+          [{ label: "操作", prop: "action", type: "action", className: "perce100" }]
+        ]
+      };
+    },
+    onLoad() {
+      this.getList();
+    },
+    onShow() {
+      uni.$on("inspectionReportRefresh", this.doSearch);
+    },
+    onUnload() {
+      uni.$off("inspectionReportRefresh", this.doSearch);
+    },
+    methods: {
+      getList() {
+        uni.showLoading({ title: "加载中", mask: true });
+        const params = {
+          pageNum: this.page,
+          size: this.size
+        };
+        const fields = {
+          reportNumber: this.searchForm.reportNumber,
+          code: this.filterForm.code,
+          productCode: this.filterForm.productCode,
+          productName: this.filterForm.productName,
+          modelType: this.filterForm.modelType,
+          specification: this.filterForm.specification
+        };
+        Object.keys(fields).forEach((key) => {
+          const val = fields[key];
+          if (val !== "" && val !== undefined && val !== null) {
+            params[key] = val;
+          }
+        });
+        const statusVal = this.filterForm.reportApprovalStatus;
+        if (statusVal !== "" && statusVal !== undefined && statusVal !== null) {
+          params.reportApprovalStatus = statusVal;
+        }
+        const methodVal = this.filterForm.recordingMethod;
+        if (methodVal !== "" && methodVal !== undefined && methodVal !== null) {
+          params.recordingMethod = methodVal;
+        }
+        getReportList(params)
+          .then((res) => {
+            uni.hideLoading();
+            const list = res.list || res.records || [];
+            if (this.page === 1) {
+              this.tableList = list;
+            } else {
+              this.tableList.push(...list);
+            }
+            this.isEnd = this.tableList.length >= (res.count || res.total || 0);
+            this.loadStatus = this.isEnd ? "nomore" : "loadmore";
+            this.page += 1;
+          })
+          .catch(() => {
+            uni.hideLoading();
+          });
+      },
+      doSearch() {
+        this.page = 1;
+        this.getList();
+      },
+      scrolltolower() {
+        if (this.isEnd) {
+          return;
+        }
+        this.getList();
+      },
+      confirmFilter() {
+        this.filterVisible = false;
+        this.page = 1;
+        this.getList();
+      },
+      resetFilter() {
+        this.filterForm = {
+          code: "",
+          productCode: "",
+          productName: "",
+          modelType: "",
+          specification: "",
+          reportApprovalStatus: "",
+          recordingMethod: ""
+        };
+        this.filterVisible = false;
+        this.page = 1;
+        this.getList();
+      },
+      getBtnList(item) {
+        const btns = [];
+        if (item.reportApprovalStatus == 0 || item.reportApprovalStatus == 3) {
+          btns.push({ name: "编辑", apiName: "edit", btnType: "warning" });
+          btns.push({ name: "删除", apiName: "delete", btnType: "danger" });
+        }
+        return btns;
+      },
+      statusText(status) {
+        return (statusList.find((it) => it.value == status) || {}).label || "";
+      },
+      recordingMethodText(method) {
+        return (recordingMethodList.find((it) => it.value == method) || {}).label || "";
+      },
+      openDetail(row) {
+        uni.setStorageSync("inspectionReportRow", row);
+        uni.navigateTo({
+          url: "/pages/qms/inspectionReport/detail?id=" + row.id
+        });
+      },
+      openEdit(row) {
+        uni.setStorageSync("inspectionReportRow", row);
+        uni.navigateTo({
+          url: "/pages/qms/inspectionReport/detail?id=" + row.id + "&edit=1"
+        });
+      },
+      deleteReportBtn(row) {
+        uni.showModal({
+          title: "提示",
+          content: "确定要删除该检测报告吗?",
+          success: (res) => {
+            if (res.confirm) {
+              deleteReport(row.id)
+                .then(() => {
+                  this.$refs.uToast.show({
+                    type: "success",
+                    icon: false,
+                    message: "删除成功"
+                  });
+                  this.page = 1;
+                  this.getList();
+                })
+                .catch((err) => {
+                  this.$refs.uToast.show({
+                    type: "error",
+                    icon: false,
+                    message: err.message || "删除失败"
+                  });
+                });
+            }
+          }
+        });
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  .mainBox {
+    background-color: #f3f8fb;
+    height: 100vh;
+    font-size: 27rpx;
+    overflow-y: auto;
+  }
+
+  .top-wrapper {
+    background-color: #fff;
+    display: flex;
+    width: 750rpx;
+    height: 88rpx;
+    padding: 16rpx 32rpx;
+    align-items: center;
+    position: absolute;
+    z-index: 999;
+
+    /deep/.uni-section {
+      margin-top: 0px;
+    }
+
+    /deep/.uni-section-header {
+      padding: 0px;
+    }
+
+    .search_btn {
+      width: 110rpx;
+      height: 70rpx;
+      line-height: 70rpx;
+      padding: 0 24rpx;
+      background: $theme-color;
+      font-size: 28rpx;
+      color: #fff;
+      margin: 0;
+      margin-left: 16rpx;
+    }
+
+    .filter_btn {
+      background: #fff;
+      color: $theme-color;
+      border: 1rpx solid $theme-color;
+    }
+  }
+
+  .list-content {
+    padding-top: 120rpx;
+  }
+
+  .card-wrapper {
+    width: 720rpx;
+    margin: 0 auto;
+  }
+
+  .search_wrap {
+    padding: 30rpx 30rpx 60rpx;
+    max-height: 70vh;
+    overflow-y: auto;
+
+    .title {
+      font-size: 32rpx;
+      font-weight: 600;
+      text-align: center;
+      margin-bottom: 20rpx;
+    }
+
+    .form_item {
+      margin-bottom: 24rpx;
+
+      .label {
+        font-size: 26rpx;
+        color: #333;
+        margin-bottom: 10rpx;
+      }
+    }
+
+    /deep/ .radio-group {
+      flex-wrap: wrap;
+
+      .u-radio__text {
+        font-size: 28rpx !important;
+      }
+
+      .u-radio__icon-wrap {
+        width: 32rpx !important;
+        height: 32rpx !important;
+      }
+    }
+
+    .btns {
+      display: flex;
+      gap: 20rpx;
+      margin-top: 30rpx;
+
+      .btn {
+        flex: 1;
+        height: 72rpx;
+        line-height: 72rpx;
+        border-radius: 8rpx;
+        font-size: 28rpx;
+      }
+
+      .reset {
+        background: #f5f5f5;
+        color: #333;
+      }
+
+      .confirm {
+        background: $theme-color;
+        color: #fff;
+      }
+    }
+  }
+</style>

+ 204 - 0
pages/qms/inspectionReport/template/inspection_report1.vue

@@ -0,0 +1,204 @@
+<template>
+  <view class="report">
+    <view class="report-title">检测报告</view>
+
+    <!-- 报告单信息 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">报告单信息</view>
+      </view>
+      <view class="info-row">
+        <text class="label">编号</text>
+        <text class="value">{{ basicInfoData.code || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">报告单号</text>
+        <view class="edit-control" v-if="isEdit">
+          <u-input v-model="basicInfoData.reportNumber" placeholder="请输入报告编号" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.reportNumber || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 基本信息 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">基本信息</view>
+      </view>
+      <view class="info-row">
+        <text class="label">检品名称</text>
+        <text class="value">{{ basicInfoData.productName || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">批号/序列号</text>
+        <text class="value">{{ basicInfoData.batchNo || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">规格型号</text>
+        <text class="value">{{ basicInfoData.specification || "" }}/{{ basicInfoData.modelType || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">数量</text>
+        <text class="value">{{ basicInfoData.total || "" }}{{ basicInfoData.sampleMeasureUnit || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">请验日期</text>
+        <text class="value">{{ basicInfoData.pleaseVerifyDate || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">请验部门</text>
+        <text class="value">{{ basicInfoData.pleaseVerifyDepartment || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">报告日期</text>
+        <view class="edit-control" v-if="isEdit">
+          <uni-datetime-picker type="date" v-model="basicInfoData.reportDate" placeholder="选择日期"></uni-datetime-picker>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.reportDate || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">有效期</text>
+        <view class="edit-control" v-if="isEdit">
+          <uni-datetime-picker type="date" v-model="basicInfoData.expirationDate" placeholder="选择日期"></uni-datetime-picker>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.expirationDate || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">来源</text>
+        <view class="edit-control" v-if="isEdit">
+          <u-input v-model="basicInfoData.source" placeholder="请输入来源" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.source || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">储存条件</text>
+        <view class="edit-control" v-if="isEdit">
+          <u-input v-model="basicInfoData.storageCondition" placeholder="请输入储存条件" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.storageCondition || "" }}</text>
+      </view>
+      <view class="info-row column">
+        <text class="label">检验依据</text>
+        <view class="edit-control full" v-if="isEdit">
+          <u-input v-model="basicInfoData.inspectionBasis" placeholder="请输入检验依据" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.inspectionBasis || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 检测项目 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">检测项目</view>
+      </view>
+      <scroll-view class="table-scroll" scroll-x v-if="inspectionItems.length">
+        <view class="quality-table" style="width: 700rpx;">
+          <view class="table-header">
+            <view class="table-cell cell-150">检验项目</view>
+            <view class="table-cell cell-200">标准规定</view>
+            <view class="table-cell cell-200">检测内容</view>
+            <view class="table-cell cell-150">检验结果</view>
+          </view>
+          <view class="table-body">
+            <view class="table-row" v-for="(item, index) in inspectionItems" :key="index">
+              <view class="table-cell cell-150">{{ item.item || "" }}</view>
+              <view class="table-cell cell-200">{{ item.standardRegulations || "" }}</view>
+              <view class="table-cell cell-200">{{ item.qualityResultContent || "" }}</view>
+              <view class="table-cell cell-150">{{ item.results || "" }}</view>
+            </view>
+          </view>
+        </view>
+      </scroll-view>
+      <view class="empty-tip" v-else>暂无检测项目</view>
+    </view>
+
+    <!-- 结论备注 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">结论</view>
+      </view>
+      <view class="info-row column">
+        <text class="label">结论</text>
+        <view class="edit-control full" v-if="isEdit">
+          <u-textarea v-model="basicInfoData.conclusion" placeholder="请输入结论" border="surround"></u-textarea>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.conclusion || "" }}</text>
+      </view>
+      <view class="info-row column">
+        <text class="label">备注</text>
+        <view class="edit-control full" v-if="isEdit">
+          <u-textarea v-model="basicInfoData.remarks" placeholder="请输入备注" border="surround"></u-textarea>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.remarks || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 签字区 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">签字</view>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">检验员/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.inspector" placeholder="检验员" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.inspectionTime" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.inspector || "" }} / {{ basicInfoData.inspectionTime || "" }}</text>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">复核人/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.reviewer" placeholder="复核人" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.reviewTime" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.reviewer || "" }} / {{ basicInfoData.reviewTime || "" }}</text>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">审核人/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.checker" placeholder="审核人" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.approvedDate" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.checker || "" }} / {{ basicInfoData.approvedDate || "" }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: "inspection_report1",
+    props: {
+      basicInfoData: { type: Object, default: () => ({}) },
+      inspectionItems: { type: Array, default: () => [] },
+      isEdit: { type: Boolean, default: false }
+    },
+    methods: {
+      getValue() {
+        return this.basicInfoData;
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  @import "./report-common.scss";
+</style>

+ 207 - 0
pages/qms/inspectionReport/template/inspection_report2.vue

@@ -0,0 +1,207 @@
+<template>
+  <view class="report">
+    <view class="report-title">{{ basicInfoData.groupName || "湖南特瑞精密医疗器器械有限公司" }}</view>
+    <view class="report-title">成品检验报告</view>
+
+    <!-- 报告单信息 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">报告单信息</view>
+      </view>
+      <view class="info-row">
+        <text class="label">报告编号</text>
+        <text class="value">{{ basicInfoData.code || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">表单编号</text>
+        <view class="edit-control" v-if="isEdit">
+          <u-input v-model="basicInfoData.reportNumber" placeholder="请输入表单编号" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.reportNumber || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">版本</text>
+        <text class="value">{{ basicInfoData.version || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 基本信息 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">基本信息</view>
+      </view>
+      <view class="info-row">
+        <text class="label">产品名称</text>
+        <text class="value">{{ basicInfoData.productName || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">生产批号</text>
+        <text class="value">{{ basicInfoData.batchNo || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">规格型号</text>
+        <text class="value">{{ basicInfoData.specification || "" }}/{{ basicInfoData.modelType || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">灭菌批号</text>
+        <view class="edit-control" v-if="isEdit">
+          <u-input v-model="basicInfoData.reportNumber1" placeholder="请输入灭菌批号" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.reportNumber1 || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">产品数量</text>
+        <text class="value">{{ basicInfoData.total || "" }}{{ basicInfoData.sampleMeasureUnit || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">抽检数量</text>
+        <text class="value">{{ basicInfoData.sampleQuantity || "" }}{{ basicInfoData.sampleMeasureUnit || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">包装规格</text>
+        <text class="value">{{ basicInfoData.packingSpecification || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">生产日期</text>
+        <view class="edit-control" v-if="isEdit">
+          <uni-datetime-picker type="date" v-model="basicInfoData.manufactureTime" placeholder="选择日期"></uni-datetime-picker>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.manufactureTime || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">有效期</text>
+        <view class="edit-control" v-if="isEdit">
+          <uni-datetime-picker type="date" v-model="basicInfoData.expirationDate" placeholder="选择日期"></uni-datetime-picker>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.expirationDate || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">报告日期</text>
+        <view class="edit-control" v-if="isEdit">
+          <uni-datetime-picker type="date" v-model="basicInfoData.reportDate" placeholder="选择日期"></uni-datetime-picker>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.reportDate || "" }}</text>
+      </view>
+      <view class="info-row column">
+        <text class="label">检验依据</text>
+        <view class="edit-control full" v-if="isEdit">
+          <u-input v-model="basicInfoData.inspectionBasis" placeholder="请输入检验依据" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.inspectionBasis || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 检测项目 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">检测项目</view>
+      </view>
+      <scroll-view class="table-scroll" scroll-x v-if="inspectionItems.length">
+        <view class="quality-table" style="width: 600rpx;">
+          <view class="table-header">
+            <view class="table-cell cell-200">检验项目</view>
+            <view class="table-cell cell-240">标准要求</view>
+            <view class="table-cell cell-160">检验结果</view>
+          </view>
+          <view class="table-body">
+            <view class="table-row" v-for="(item, index) in inspectionItems" :key="index">
+              <view class="table-cell cell-200">{{ item.item || "" }}</view>
+              <view class="table-cell cell-240">{{ item.standardRegulations || "" }}</view>
+              <view class="table-cell cell-160">{{ item.results || "" }}</view>
+            </view>
+          </view>
+        </view>
+      </scroll-view>
+      <view class="empty-tip" v-else>暂无检测项目</view>
+    </view>
+
+    <!-- 结论备注 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">结论</view>
+      </view>
+      <view class="info-row column">
+        <text class="label">结论</text>
+        <view class="edit-control full" v-if="isEdit">
+          <u-textarea v-model="basicInfoData.conclusion" placeholder="请输入结论" border="surround"></u-textarea>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.conclusion || "" }}</text>
+      </view>
+      <view class="info-row column">
+        <text class="label">备注</text>
+        <view class="edit-control full" v-if="isEdit">
+          <u-textarea v-model="basicInfoData.remarks" placeholder="请输入备注" border="surround"></u-textarea>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.remarks || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 签字区 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">签字</view>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">检验员/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.inspector" placeholder="检验员" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.inspectionTime" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.inspector || "" }} / {{ basicInfoData.inspectionTime || "" }}</text>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">复核人/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.reviewer" placeholder="复核人" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.reviewTime" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.reviewer || "" }} / {{ basicInfoData.reviewTime || "" }}</text>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">审核人/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.checker" placeholder="审核人" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.approvedDate" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.checker || "" }} / {{ basicInfoData.approvedDate || "" }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: "inspection_report2",
+    props: {
+      basicInfoData: { type: Object, default: () => ({}) },
+      inspectionItems: { type: Array, default: () => [] },
+      isEdit: { type: Boolean, default: false }
+    },
+    methods: {
+      getValue() {
+        return this.basicInfoData;
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  @import "./report-common.scss";
+</style>

+ 201 - 0
pages/qms/inspectionReport/template/inspection_report4.vue

@@ -0,0 +1,201 @@
+<template>
+  <view class="report">
+    <view class="report-title">检测报告</view>
+
+    <!-- 报告单信息 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">报告单信息</view>
+      </view>
+      <view class="info-row">
+        <text class="label">编号</text>
+        <text class="value">{{ basicInfoData.code || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">报告单号</text>
+        <view class="edit-control" v-if="isEdit">
+          <u-input v-model="basicInfoData.reportNumber" placeholder="请输入报告编号" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.reportNumber || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 基本信息 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">基本信息</view>
+      </view>
+      <view class="info-row">
+        <text class="label">检品名称</text>
+        <text class="value">{{ basicInfoData.productName || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">批号/序列号</text>
+        <text class="value">{{ basicInfoData.batchNo || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">规格型号</text>
+        <text class="value">{{ basicInfoData.specification || "" }}/{{ basicInfoData.modelType || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">产品数量</text>
+        <text class="value">{{ basicInfoData.total || "" }}{{ basicInfoData.sampleMeasureUnit || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">样品来源</text>
+        <view class="edit-control" v-if="isEdit">
+          <u-input v-model="basicInfoData.source" placeholder="请输入来源" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.source || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">样品数量</text>
+        <text class="value">{{ basicInfoData.sampleQuantity || "" }}{{ basicInfoData.sampleMeasureUnit || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">请验日期</text>
+        <text class="value">{{ basicInfoData.pleaseVerifyDate || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">请验部门</text>
+        <text class="value">{{ basicInfoData.pleaseVerifyDepartment || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">报告日期</text>
+        <view class="edit-control" v-if="isEdit">
+          <uni-datetime-picker type="date" v-model="basicInfoData.reportDate" placeholder="选择日期"></uni-datetime-picker>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.reportDate || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">储存条件</text>
+        <view class="edit-control" v-if="isEdit">
+          <u-input v-model="basicInfoData.storageCondition" placeholder="请输入储存条件" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.storageCondition || "" }}</text>
+      </view>
+      <view class="info-row column">
+        <text class="label">检验依据</text>
+        <view class="edit-control full" v-if="isEdit">
+          <u-input v-model="basicInfoData.inspectionBasis" placeholder="请输入检验依据" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.inspectionBasis || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 检测项目 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">检测项目</view>
+      </view>
+      <scroll-view class="table-scroll" scroll-x v-if="inspectionItems.length">
+        <view class="quality-table" style="width: 700rpx;">
+          <view class="table-header">
+            <view class="table-cell cell-150">检验项目</view>
+            <view class="table-cell cell-200">标准规定</view>
+            <view class="table-cell cell-200">检测内容</view>
+            <view class="table-cell cell-150">检验结果</view>
+          </view>
+          <view class="table-body">
+            <view class="table-row" v-for="(item, index) in inspectionItems" :key="index">
+              <view class="table-cell cell-150">{{ item.item || "" }}</view>
+              <view class="table-cell cell-200">{{ item.standardRegulations || "" }}</view>
+              <view class="table-cell cell-200">{{ item.qualityResultContent || "" }}</view>
+              <view class="table-cell cell-150">{{ item.results || "" }}</view>
+            </view>
+          </view>
+        </view>
+      </scroll-view>
+      <view class="empty-tip" v-else>暂无检测项目</view>
+    </view>
+
+    <!-- 结论备注 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">结论</view>
+      </view>
+      <view class="info-row column">
+        <text class="label">结论</text>
+        <view class="edit-control full" v-if="isEdit">
+          <u-textarea v-model="basicInfoData.conclusion" placeholder="请输入结论" border="surround"></u-textarea>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.conclusion || "" }}</text>
+      </view>
+      <view class="info-row column">
+        <text class="label">备注</text>
+        <view class="edit-control full" v-if="isEdit">
+          <u-textarea v-model="basicInfoData.remarks" placeholder="请输入备注" border="surround"></u-textarea>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.remarks || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 签字区 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">签字</view>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">检验员/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.inspector" placeholder="检验员" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.inspectionTime" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.inspector || "" }} / {{ basicInfoData.inspectionTime || "" }}</text>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">复核人/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.reviewer" placeholder="复核人" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.reviewTime" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.reviewer || "" }} / {{ basicInfoData.reviewTime || "" }}</text>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">审核人/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.checker" placeholder="审核人" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.approvedDate" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.checker || "" }} / {{ basicInfoData.approvedDate || "" }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: "inspection_report4",
+    props: {
+      basicInfoData: { type: Object, default: () => ({}) },
+      inspectionItems: { type: Array, default: () => [] },
+      isEdit: { type: Boolean, default: false }
+    },
+    methods: {
+      getValue() {
+        return this.basicInfoData;
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  @import "./report-common.scss";
+</style>

+ 224 - 0
pages/qms/inspectionReport/template/inspection_report5.vue

@@ -0,0 +1,224 @@
+<template>
+  <view class="report">
+    <view class="report-title">{{ basicInfoData.groupName || "湖南特瑞精密医疗器器械有限公司" }}</view>
+    <view class="report-title">来料/半成品检验报告单</view>
+
+    <!-- 报告单信息 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">报告单信息</view>
+      </view>
+      <view class="info-row">
+        <text class="label">报告编号</text>
+        <text class="value">{{ basicInfoData.code || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">表单编号</text>
+        <view class="edit-control" v-if="isEdit">
+          <u-input v-model="basicInfoData.reportNumber" placeholder="请输入表单编号" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.reportNumber || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">版本</text>
+        <text class="value">{{ basicInfoData.version || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 基本信息 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">基本信息</view>
+      </view>
+      <view class="info-row">
+        <text class="label">品名</text>
+        <text class="value">{{ basicInfoData.productName || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">规格型号</text>
+        <text class="value">{{ basicInfoData.specification || "" }}/{{ basicInfoData.modelType || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">来料/半成品批号</text>
+        <text class="value">{{ basicInfoData.batchNo || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">数量</text>
+        <text class="value">{{ basicInfoData.total || "" }}{{ basicInfoData.sampleMeasureUnit || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">供应商名称</text>
+        <text class="value">{{ basicInfoData.supplierName || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">抽检数量</text>
+        <text class="value">{{ basicInfoData.sampleQuantity || "" }}{{ basicInfoData.sampleMeasureUnit || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">检验日期</text>
+        <view class="edit-control" v-if="isEdit">
+          <uni-datetime-picker type="date" v-model="basicInfoData.pleaseVerifyDate" placeholder="选择日期"></uni-datetime-picker>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.pleaseVerifyDate || basicInfoData.manufactureTime || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">报告日期</text>
+        <view class="edit-control" v-if="isEdit">
+          <uni-datetime-picker type="date" v-model="basicInfoData.reportDate" placeholder="选择日期"></uni-datetime-picker>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.reportDate || "" }}</text>
+      </view>
+      <view class="info-row column">
+        <text class="label">检验依据</text>
+        <view class="edit-control full" v-if="isEdit">
+          <u-input v-model="basicInfoData.inspectionBasis" placeholder="请输入检验依据" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.inspectionBasis || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 检测项目 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">检测项目</view>
+      </view>
+      <scroll-view class="table-scroll" scroll-x v-if="inspectionItems.length">
+        <view class="quality-table" style="width: 1180rpx;">
+          <view class="table-header">
+            <view class="table-cell cell-150">检验项目</view>
+            <view class="table-cell cell-180">标准规定</view>
+            <view class="table-cell cell-200">检验工具及编号</view>
+            <view class="table-cell cell-180">检测内容</view>
+            <view class="table-cell cell-150">检验结果</view>
+            <view class="table-cell cell-160">备注</view>
+          </view>
+          <view class="table-body">
+            <view class="table-row" v-for="(item, index) in inspectionItems" :key="index">
+              <view class="table-cell cell-150">{{ item.item || "" }}</view>
+              <view class="table-cell cell-180">{{ item.standardRegulations || "" }}</view>
+              <view class="table-cell cell-200">{{ toolText(item) }}</view>
+              <view class="table-cell cell-180">{{ item.qualityResultContent || "" }}</view>
+              <view class="table-cell cell-150">{{ item.results || "" }}</view>
+              <view class="table-cell cell-160">
+                <u-input v-if="isEdit" v-model="item.remark" placeholder="请输入" border="none"></u-input>
+                <text v-else>{{ item.remark || "" }}</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </scroll-view>
+      <view class="empty-tip" v-else>暂无检测项目</view>
+    </view>
+
+    <!-- 结论 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">结论</view>
+      </view>
+      <view class="info-row" v-if="isEdit">
+        <text class="label">结论</text>
+        <u-radio-group class="radio-group" v-model="basicInfoData.qualityResultsStr" placement="row" iconSize="28" labelSize="26">
+          <u-radio :customStyle="{ marginRight: '20rpx' }" label="符合规定" name="符合"></u-radio>
+          <u-radio :customStyle="{ marginRight: '20rpx' }" label="不符合规定" name="不符合"></u-radio>
+        </u-radio-group>
+      </view>
+      <view class="info-row" v-else>
+        <text class="label">结论</text>
+        <text class="value">{{ qualityResultsStrText }}</text>
+      </view>
+      <view class="info-row column" v-if="isEdit && basicInfoData.qualityResultsStr == '不符合'">
+        <text class="label">不符合规定说明</text>
+        <view class="edit-control full">
+          <u-input v-model="basicInfoData.qualityResultsStrRemark" placeholder="请输入不符合规定说明" border="surround"></u-input>
+        </view>
+      </view>
+      <view class="info-row column" v-else-if="basicInfoData.qualityResultsStrRemark">
+        <text class="label">不符合规定说明</text>
+        <text class="value">{{ basicInfoData.qualityResultsStrRemark }}</text>
+      </view>
+    </view>
+
+    <!-- 签字区 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">签字</view>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">检验人/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.inspector" placeholder="检验人" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.inspectionTime" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.inspector || "" }} / {{ basicInfoData.inspectionTime || "" }}</text>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">复核人/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.reviewer" placeholder="复核人" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.reviewTime" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.reviewer || "" }} / {{ basicInfoData.reviewTime || "" }}</text>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">批准人/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.checker" placeholder="批准人" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.approvedDate" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.checker || "" }} / {{ basicInfoData.approvedDate || "" }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: "inspection_report5",
+    props: {
+      basicInfoData: { type: Object, default: () => ({}) },
+      inspectionItems: { type: Array, default: () => [] },
+      isEdit: { type: Boolean, default: false }
+    },
+    computed: {
+      qualityResultsStrText() {
+        if (this.basicInfoData.qualityResultsStr) {
+          return this.basicInfoData.qualityResultsStr == "不符合"
+            ? "不符合规定" + (this.basicInfoData.qualityResultsStrRemark ? ":" + this.basicInfoData.qualityResultsStrRemark : "")
+            : "符合规定";
+        }
+        return "";
+      }
+    },
+    methods: {
+      toolText(item) {
+        return (item.toolList || [])
+          .map((t) => (t.name || "") + " " + (t.code || ""))
+          .join(",");
+      },
+      getValue() {
+        return this.basicInfoData;
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  @import "./report-common.scss";
+</style>

+ 220 - 0
pages/qms/inspectionReport/template/inspection_report6.vue

@@ -0,0 +1,220 @@
+<template>
+  <view class="report">
+    <view class="report-title">{{ basicInfoData.groupName || "湖南特瑞精密医疗器器械有限公司" }}</view>
+    <view class="report-title">自制品检验报告单</view>
+
+    <!-- 报告单信息 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">报告单信息</view>
+      </view>
+      <view class="info-row">
+        <text class="label">报告编号</text>
+        <text class="value">{{ basicInfoData.code || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">表单编号</text>
+        <view class="edit-control" v-if="isEdit">
+          <u-input v-model="basicInfoData.reportNumber" placeholder="请输入表单编号" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.reportNumber || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">版本</text>
+        <text class="value">{{ basicInfoData.version || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 基本信息 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">基本信息</view>
+      </view>
+      <view class="info-row">
+        <text class="label">品名</text>
+        <text class="value">{{ basicInfoData.productName || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">规格型号</text>
+        <text class="value">{{ basicInfoData.specification || "" }}/{{ basicInfoData.modelType || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">自制品批号</text>
+        <text class="value">{{ basicInfoData.batchNo || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">数量</text>
+        <text class="value">{{ basicInfoData.total || "" }}{{ basicInfoData.sampleMeasureUnit || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">检验日期</text>
+        <view class="edit-control" v-if="isEdit">
+          <uni-datetime-picker type="date" v-model="basicInfoData.pleaseVerifyDate" placeholder="选择日期"></uni-datetime-picker>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.pleaseVerifyDate || basicInfoData.manufactureTime || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">报告日期</text>
+        <view class="edit-control" v-if="isEdit">
+          <uni-datetime-picker type="date" v-model="basicInfoData.reportDate" placeholder="选择日期"></uni-datetime-picker>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.reportDate || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">检验依据</text>
+        <view class="edit-control" v-if="isEdit">
+          <u-input v-model="basicInfoData.inspectionBasis" placeholder="请输入检验依据" border="surround"></u-input>
+        </view>
+        <text class="value" v-else>{{ basicInfoData.inspectionBasis || "" }}</text>
+      </view>
+      <view class="info-row">
+        <text class="label">抽检数量</text>
+        <text class="value">{{ basicInfoData.sampleQuantity || "" }}{{ basicInfoData.sampleMeasureUnit || "" }}</text>
+      </view>
+    </view>
+
+    <!-- 检测项目 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">检测项目</view>
+      </view>
+      <scroll-view class="table-scroll" scroll-x v-if="inspectionItems.length">
+        <view class="quality-table" style="width: 1180rpx;">
+          <view class="table-header">
+            <view class="table-cell cell-150">检验项目</view>
+            <view class="table-cell cell-180">标准规定</view>
+            <view class="table-cell cell-200">检验工具及编号</view>
+            <view class="table-cell cell-180">检测内容</view>
+            <view class="table-cell cell-150">检验结果</view>
+            <view class="table-cell cell-160">备注</view>
+          </view>
+          <view class="table-body">
+            <view class="table-row" v-for="(item, index) in inspectionItems" :key="index">
+              <view class="table-cell cell-150">{{ item.item || "" }}</view>
+              <view class="table-cell cell-180">{{ item.standardRegulations || "" }}</view>
+              <view class="table-cell cell-200">{{ toolText(item) }}</view>
+              <view class="table-cell cell-180">{{ item.qualityResultContent || "" }}</view>
+              <view class="table-cell cell-150">{{ item.results || "" }}</view>
+              <view class="table-cell cell-160">
+                <u-input v-if="isEdit" v-model="item.remark" placeholder="请输入" border="none"></u-input>
+                <text v-else>{{ item.remark || "" }}</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </scroll-view>
+      <view class="empty-tip" v-else>暂无检测项目</view>
+    </view>
+
+    <!-- 结论 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">结论</view>
+      </view>
+      <view class="info-row" v-if="isEdit">
+        <text class="label">结论</text>
+        <u-radio-group class="radio-group" v-model="basicInfoData.qualityResultsStr" placement="row" iconSize="28" labelSize="26">
+          <u-radio :customStyle="{ marginRight: '20rpx' }" label="符合规定" name="符合"></u-radio>
+          <u-radio :customStyle="{ marginRight: '20rpx' }" label="不符合规定" name="不符合"></u-radio>
+        </u-radio-group>
+      </view>
+      <view class="info-row" v-else>
+        <text class="label">结论</text>
+        <text class="value">{{ qualityResultsStrText }}</text>
+      </view>
+      <view class="info-row column" v-if="isEdit && basicInfoData.qualityResultsStr == '不符合'">
+        <text class="label">不符合规定说明</text>
+        <view class="edit-control full">
+          <u-input v-model="basicInfoData.qualityResultsStrRemark" placeholder="请输入不符合规定说明" border="surround"></u-input>
+        </view>
+      </view>
+      <view class="info-row column" v-else-if="basicInfoData.qualityResultsStrRemark">
+        <text class="label">不符合规定说明</text>
+        <text class="value">{{ basicInfoData.qualityResultsStrRemark }}</text>
+      </view>
+    </view>
+
+    <!-- 签字区 -->
+    <view class="card">
+      <view class="card-title">
+        <view class="herder_text"></view>
+        <view class="card-title-text">签字</view>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">检验人/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.inspector" placeholder="检验人" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.inspectionTime" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.inspector || "" }} / {{ basicInfoData.inspectionTime || "" }}</text>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">复核人/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.reviewer" placeholder="复核人" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.reviewTime" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.reviewer || "" }} / {{ basicInfoData.reviewTime || "" }}</text>
+      </view>
+      <view class="sign-row">
+        <view class="sign-label">批准人/日期:</view>
+        <view class="sign-value" v-if="isEdit">
+          <view class="edit-control">
+            <u-input v-model="basicInfoData.checker" placeholder="批准人" border="surround"></u-input>
+          </view>
+          <view class="edit-control">
+            <uni-datetime-picker type="date" v-model="basicInfoData.approvedDate" placeholder="选择日期"></uni-datetime-picker>
+          </view>
+        </view>
+        <text class="sign-value" v-else>{{ basicInfoData.checker || "" }} / {{ basicInfoData.approvedDate || "" }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: "inspection_report6",
+    props: {
+      basicInfoData: { type: Object, default: () => ({}) },
+      inspectionItems: { type: Array, default: () => [] },
+      isEdit: { type: Boolean, default: false }
+    },
+    computed: {
+      qualityResultsStrText() {
+        if (this.basicInfoData.qualityResultsStr) {
+          return this.basicInfoData.qualityResultsStr == "不符合"
+            ? "不符合规定" + (this.basicInfoData.qualityResultsStrRemark ? ":" + this.basicInfoData.qualityResultsStrRemark : "")
+            : "符合规定";
+        }
+        return "";
+      }
+    },
+    methods: {
+      toolText(item) {
+        return (item.toolList || [])
+          .map((t) => (t.name || "") + " " + (t.code || ""))
+          .join(",");
+      },
+      getValue() {
+        return this.basicInfoData;
+      }
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  @import "./report-common.scss";
+</style>

+ 182 - 0
pages/qms/inspectionReport/template/report-common.scss

@@ -0,0 +1,182 @@
+/* 质检报告模板通用移动端样式 */
+.report {
+  padding-bottom: 20rpx;
+}
+
+.report-title {
+  text-align: center;
+  font-size: 34rpx;
+  font-weight: bold;
+  padding: 30rpx 20rpx 0;
+}
+
+.report-subtitle {
+  text-align: center;
+  font-size: 26rpx;
+  color: #666;
+  margin-top: 10rpx;
+}
+
+.card {
+  width: 720rpx;
+  background: #fff;
+  margin: 20rpx auto 0;
+  border-radius: 30rpx;
+  padding: 26rpx;
+  box-sizing: border-box;
+
+  .card-title {
+    display: flex;
+    align-items: center;
+    font-weight: bold;
+    font-size: 30rpx;
+    margin-bottom: 16rpx;
+
+    .herder_text {
+      min-width: 10rpx;
+      height: 32rpx;
+      border-radius: 10rpx;
+      background: #00c0a1;
+      margin-right: 12rpx;
+      margin-top: 5rpx;
+    }
+  }
+
+  .info-row {
+    display: flex;
+    align-items: center;
+    margin-top: 20rpx;
+    font-size: 26rpx;
+
+    .label {
+      color: #666;
+      min-width: 150rpx;
+      font-weight: bold;
+    }
+
+    .value {
+      flex: 1;
+      word-break: break-all;
+      color: #333;
+    }
+
+    &.column {
+      flex-direction: column;
+      align-items: flex-start;
+
+      .label {
+        margin-bottom: 12rpx;
+      }
+
+      .value {
+        width: 100%;
+      }
+    }
+  }
+
+  .edit-control {
+    flex: 1;
+    min-width: 0;
+
+    &.full {
+      width: 100%;
+    }
+  }
+
+  .sign-row {
+    margin-top: 20rpx;
+
+    .sign-label {
+      color: #666;
+      font-weight: bold;
+      margin-bottom: 12rpx;
+    }
+
+    .sign-value {
+      display: block;
+      color: #333;
+      word-break: break-all;
+    }
+
+    .sign-value .edit-control {
+      margin-bottom: 12rpx;
+    }
+  }
+
+  .radio-group {
+    flex-wrap: wrap;
+  }
+
+  .empty-tip {
+    color: #999;
+    text-align: center;
+    padding: 40rpx 0;
+    font-size: 26rpx;
+  }
+}
+
+.table-scroll {
+  width: 100%;
+  margin-top: 16rpx;
+}
+
+.quality-table {
+  border: 1rpx solid #e5e5e5;
+
+  .table-header,
+  .table-row {
+    display: flex;
+  }
+
+  .table-header {
+    background: #f7f8fa;
+    font-weight: bold;
+  }
+
+  .table-cell {
+    padding: 14rpx 10rpx;
+    font-size: 24rpx;
+    border-right: 1rpx solid #e5e5e5;
+    border-bottom: 1rpx solid #e5e5e5;
+    word-break: break-all;
+    box-sizing: border-box;
+
+    &:last-child {
+      border-right: none;
+    }
+  }
+
+  .table-body .table-row:last-child .table-cell {
+    border-bottom: none;
+  }
+
+  .cell-100 {
+    width: 100rpx;
+    min-width: 100rpx;
+  }
+
+  .cell-150 {
+    width: 150rpx;
+    min-width: 150rpx;
+  }
+
+  .cell-180 {
+    width: 180rpx;
+    min-width: 180rpx;
+  }
+
+  .cell-200 {
+    width: 200rpx;
+    min-width: 200rpx;
+  }
+
+  .cell-240 {
+    width: 240rpx;
+    min-width: 240rpx;
+  }
+
+  .cell-280 {
+    width: 280rpx;
+    min-width: 280rpx;
+  }
+}

+ 166 - 3
pages/qms/inspectionWork/list.vue

@@ -22,7 +22,7 @@
       <uni-section>
         <uni-easyinput
           prefixIcon="search"
-          style="width: 460rpx"
+          style="width: 100%"
           v-model="keyWord"
           placeholder="名称、批次号、工单编码"
         >
@@ -145,7 +145,39 @@
                 </u-button>
               </view>
             </view>
-           
+
+            <view class="text btnlist" v-if="isReportReady(item)">
+              <text>质检报告:</text>
+              <view style="display: flex">
+                <uni-data-picker
+                  v-if="!hasReport(item)"
+                  :localdata="templateOptions"
+                  placeholder="生成质检报告"
+                  popup-title="选择报告模板"
+                  @change="onTemplateChange($event, item)"
+                ></uni-data-picker>
+                <u-button
+                  v-if="
+                    Object.keys(item.reportTemplateJson).length &&
+                    (!item.reportApprovalStatus ||
+                      item.reportApprovalStatus == 3)
+                  "
+                  type="primary"
+                  text="质检报告审批"
+                  @click="listSubmit(item)"
+                ></u-button>
+                <u-button
+                  v-if="
+                    Object.keys(item.reportTemplateJson).length &&
+                    item.reportApprovalStatus
+                  "
+                  type="primary"
+                  text="查看质检报告"
+                  @click="viewReport(item)"
+                ></u-button>
+              </view>
+            </view>
+
             <!-- <view
               class="text btnlist"
               v-if="
@@ -189,6 +221,11 @@ import {
   verificationQualityInspector,
   checkByQualityWorkOrderId,
 } from "@/api/inspectionWork/index.js";
+	import {
+		processInstanceCreateAPI,
+		processInstancePage
+	} from '@/api/wt/index.js'
+import { getQmsReportTemplatePageList } from "@/api/inspectionReport/index.js";
 export default {
   components: {
     addSample,
@@ -242,11 +279,27 @@ export default {
       isEnd: false,
       keyWord: "",
       userInfo: {},
+      reportTemplateList: [],
     };
   },
-  computed: {},
+  computed: {
+    templateOptions() {
+      return (this.reportTemplateList || []).map((t) => ({
+        text:
+          (t.name || "") +
+          "(" +
+          (t.versionSymbol ?? "") +
+          (t.bigVersion ?? "") +
+          (t.versionMark ?? "") +
+          (t.smallVersion ?? "") +
+          ")",
+        value: t.code,
+      }));
+    },
+  },
   created() {
     this.requestDict("质检计划类型");
+    this.getReportTemplateList();
   },
   onLoad() {},
 
@@ -263,6 +316,39 @@ export default {
       }
       return data;
     },
+    async listSubmit(item) {
+      try {
+        //后台不提供接口
+        let list = await processInstancePage({
+          pageNo: 1,
+          pageSize: 1,
+          reset: true,
+          key: "qms_report_approval",
+        });
+        let params = {
+          businessId: item.id,
+          businessKey: "qms_report_approval",
+          formCreateUserId: item.createUserId,
+          processDefinitionId: list?.list[0]?.processDefinition.id,
+          variables: {
+            businessCode: item.code,
+            businessName: item.name,
+            businessType: "检测报告单",
+          },
+        };
+
+        await processInstanceCreateAPI(params);
+        uni.showModal({
+          title: `提交成功`,
+          content: "",
+          confirmText: "确认",
+          showCancel: false, // 是否显示取消按钮,默认为 true
+          success: () => {
+            this.successInit();
+          },
+        });
+      } catch {}
+    },
     successInit() {
       uni.showLoading({
         title: "加载中",
@@ -305,6 +391,83 @@ export default {
       this.page = 1;
       this.getList();
     },
+    getReportTemplateList() {
+      getQmsReportTemplatePageList({
+        pageNum: 1,
+        pageSize: 999,
+        isEnabled: 1,
+        type: 0,
+      })
+        .then((res) => {
+          this.reportTemplateList = res.list || [];
+        })
+        .catch(() => {});
+    },
+    isReportReady(item) {
+      if (
+        this.pageName == "myInspectionProjectTask" ||
+        this.pageName == "myInspectionProjectEntrusted"
+      ) {
+        return item.status == 2;
+      }
+      return item.status == 1;
+    },
+    hasReport(item) {
+      return !!(
+        item.reportApprovalStatus ||
+        (item.reportTemplateJson && Object.keys(item.reportTemplateJson).length)
+      );
+    },
+    onTemplateChange(e, item) {
+      const arr = (e && e.detail && e.detail.value) || [];
+      const node = arr[arr.length - 1];
+      if (!node || !node.value) {
+        return;
+      }
+      const tpl = this.reportTemplateList.find(
+        (t) => String(t.code) == String(node.value),
+      );
+      if (tpl && item && item.id) {
+        this.selectReportTemplate(item, tpl);
+      }
+    },
+    selectReportTemplate(item, template) {
+      if (!item || !item.id) {
+        return;
+      }
+      const row = {
+        ...item,
+        workOrderId: item.id,
+        type: item.type || 0,
+        reportTemplateJson: item.reportTemplateJson || {},
+        reportApprovalTaskVos: item.reportApprovalTaskVos || [],
+      };
+      uni.setStorageSync("inspectionReportRow", row);
+      uni.navigateTo({
+        url:
+          "/pages/qms/inspectionReport/detail?id=" +
+          item.id +
+          "&edit=1&templateCode=" +
+          encodeURIComponent(template.code || ""),
+      });
+    },
+    viewReport(item) {
+      const row = {
+        ...item,
+        workOrderId: item.id,
+        type: item.type || 0,
+        reportTemplateJson: item.reportTemplateJson || {},
+        reportApprovalTaskVos: item.reportApprovalTaskVos || [],
+      };
+      uni.setStorageSync("inspectionReportRow", row);
+      uni.navigateTo({
+        url:
+          "/pages/qms/inspectionReport/detail?id=" +
+          item.id +
+          "&templateCode=" +
+          encodeURIComponent(item.reportTemplateCode || ""),
+      });
+    },
     //获取列表信息
     getList() {
       this.userInfo = uni.getStorageSync("userInfo");