From 0a40c5890d13d9f2de5c3ab663876afecbfbefbe Mon Sep 17 00:00:00 2001
From: 吴羡 <16976746+Xian-TT3@user.noreply.gitee.com>
Date: 星期三, 28 十月 2026 17:57:46 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 src/api/structural/laboratory.js                                  |   29 +++++
 src/views/CNAS/process/method/standardMethod/index.vue            |   29 -----
 src/views/structural/capabilityAndLaboratory/laboratory/index.vue |  222 +++++++++++++++++++++++++++++++++++---------
 3 files changed, 205 insertions(+), 75 deletions(-)

diff --git a/src/api/structural/laboratory.js b/src/api/structural/laboratory.js
index 1e2f9b0..8499174 100644
--- a/src/api/structural/laboratory.js
+++ b/src/api/structural/laboratory.js
@@ -8,6 +8,16 @@
     params: query
   })
 }
+
+// 鎸塈D鏌ヨ璧勮川璇︽儏
+export function getCertificationById(id) {
+  return request({
+    url: '/certification/getCertificationById',
+    method: 'get',
+    params: { id }
+  })
+}
+
 // 娣诲姞璧勮川鏄庣粏鍒楄〃
 export function addCertificationDetail(query) {
   return request({
@@ -16,6 +26,25 @@
     data: query
   })
 }
+
+// 鏇存柊璧勮川璧勬枡
+export function updateCertificationDetail(data) {
+  return request({
+    url: '/certification/updateCertificationDetail',
+    method: 'put',
+    data
+  })
+}
+
+// 鏇存柊璧勮川鍥剧墖
+export function updateCertificationImage(data) {
+  return request({
+    url: '/certification/updateCertificationImage',
+    method: 'put',
+    data
+  })
+}
+
 // 鍒犻櫎璧勮川鏄庣粏鍒楄〃
 export function delCertificationDetail(query) {
   return request({
diff --git a/src/views/CNAS/process/method/standardMethod/index.vue b/src/views/CNAS/process/method/standardMethod/index.vue
index b141bf1..c611153 100644
--- a/src/views/CNAS/process/method/standardMethod/index.vue
+++ b/src/views/CNAS/process/method/standardMethod/index.vue
@@ -65,12 +65,6 @@
             </el-option>
           </el-select>
         </el-form-item>
-        <el-form-item label="鏄惁浜у搧鏍囧噯" prop="isProduct">
-          <el-select v-model="addForm.isProduct" size="small" clearable placeholder="璇烽�夋嫨" style="width: 100%">
-            <el-option label="鍚�" :value="0"></el-option>
-            <el-option label="鏄�" :value="1"></el-option>
-          </el-select>
-        </el-form-item>
         <el-form-item label="鏄惁鍚敤" prop="isUse">
           <el-select v-model="addForm.isUse" size="small" clearable placeholder="璇烽�夋嫨" style="width: 100%">
             <el-option label="鍚�" :value="0"></el-option>
@@ -150,26 +144,6 @@
         {label: "鏍囧噯鎻忚堪", prop: "name", width: "100px"},
         {label: "澶囨敞", prop: "remark"},
         {
-          label: "鏄惁浜у搧鏍囧噯",
-          prop: "isProduct",
-          width: "100px",
-          dataType: "tag",
-          formatData: (params) => {
-            if (params == 0) {
-              return "鍚�";
-            } else {
-              return "鏄�";
-            }
-          },
-          formatType: (params) => {
-            if (params == 0) {
-              return "danger";
-            } else {
-              return "primary";
-            }
-          },
-        },
-        {
           label: "鏄惁鍚敤",
           prop: "isUse",
           formatData: (params) => {
@@ -246,9 +220,6 @@
       addRules: {
         code: [{required: true, message: "璇疯緭鍏ユ爣鍑嗙紪鍙�", trigger: "blur"}],
         name: [{required: true, message: "璇疯緭鍏ユ爣鍑嗘弿杩�", trigger: "blur"}],
-        isProduct: [
-          {required: true, message: "璇烽�夋嫨鏄惁浜у搧鏍囧噯", trigger: "change"},
-        ],
         isUse: [
           {required: true, message: "璇烽�夋嫨鏄惁鍚敤", trigger: "change"},
         ],
diff --git a/src/views/structural/capabilityAndLaboratory/laboratory/index.vue b/src/views/structural/capabilityAndLaboratory/laboratory/index.vue
index c05a6fe..7dde2e5 100644
--- a/src/views/structural/capabilityAndLaboratory/laboratory/index.vue
+++ b/src/views/structural/capabilityAndLaboratory/laboratory/index.vue
@@ -3,7 +3,7 @@
     <div>
       <el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
         <el-form-item label="璧勮川鍚嶇О" prop="name">
-          <el-select v-model="queryParams.name" placeholder="閫夋嫨璧勮川鍚嶇О" size="small" @change="refreshTable(), list = []">
+          <el-select v-model="queryParams.name" placeholder="閫夋嫨璧勮川鍚嶇О" size="small" @change="handleQuery">
             <el-option v-for="dict in dict.type.cnas_method_qualification" :key="dict.value" :label="dict.label"
               :value="dict.value">
               {{ dict.label }}
@@ -11,7 +11,7 @@
           </el-select>
         </el-form-item>
         <el-form-item>
-          <el-button type="primary" size="small" @click="refreshTable">鏌ヨ</el-button>
+          <el-button type="primary" size="small" @click="handleQuery">鏌ヨ</el-button>
           <el-button size="small" @click="refresh">閲嶇疆</el-button>
         </el-form-item>
       </el-form>
@@ -25,7 +25,7 @@
           </el-radio-group>
         </el-col>
         <el-col :span="12" style="text-align: right" v-if="radio === 0">
-          <el-button size="small" type="primary" @click="openAdd">璧勮川鏇存柊</el-button>
+          <el-button size="small" type="primary" @click="openAdd">璧勮川鏂板</el-button>
           <el-button size="small" :icon="$icon('el-icon-delete')" @click="handleDel">鍒犻櫎</el-button>
         </el-col>
       </el-row>
@@ -37,7 +37,7 @@
     </div>
     <div class="table" v-if="radio === 1" v-loading="pageLoading" @scroll="scrollFn">
       <el-row :gutter="16">
-        <el-col :span="8" v-for="(m, i) in list" :key="i" :xs="24" :sm="12" :md="8" :lg="8" :xl="6"
+        <el-col :span="8" v-for="m in list" :key="m.id" :xs="24" :sm="12" :md="8" :lg="8" :xl="6"
           style="margin-bottom: 16px">
           <div class="table-item">
             <el-image style="
@@ -45,7 +45,7 @@
                 height: 102px;
                 margin-right: 20px;
                 border-radius: 16px;
-              " :src="javaApi + '/img/' + m.imageUrl">
+              " :src="getImageUrl(m.imageUrl)">
               <template #error><div  class="image-error" style="
                   width: 100px;
                   height: 100px;
@@ -69,6 +69,15 @@
                 <el-tag :type="m.state === 0 ? 'danger' : 'success'" size="small">{{ m.state === 0 ? "澶辨晥" : "鏈夋晥"
                   }}</el-tag>
               </p>
+              <el-upload :action="uploadAction" :headers="headers" :show-file-list="false"
+                :disabled="!!overviewUploading[m.id]"
+                accept="image/jpg,image/jpeg,image/png" :before-upload="file => beforeOverviewImageUpload(file, m)"
+                :on-success="response => handleOverviewImageSuccess(response, m)"
+                :on-error="() => handleOverviewImageError(m.id)">
+                <el-button link type="primary" size="small" :loading="!!overviewUploading[m.id]">
+                  {{ m.imageUrl ? "鏇存崲鍥剧墖" : "涓婁紶鍥剧墖" }}
+                </el-button>
+              </el-upload>
             </div>
           </div>
         </el-col>
@@ -89,7 +98,7 @@
           style="display: flex; margin: 0 auto; color: var(--lims-text-3)">宸茬粡娌℃湁鏇村鍟</el-button>
       </div>
     </div>
-    <el-dialog title="璧勮川鏇存柊" v-model="qualificationsConnectVisible" width="400px">
+    <el-dialog :title="dialogTitle" v-model="qualificationsConnectVisible" width="400px" @closed="resetDialog">
       <el-form ref="formDataRef" :model="formData" label-position="right" :rules="formDataRules" label-width="78px">
         <el-form-item label="璧勮川鍚嶇О" prop="name">
           <el-select v-model="formData.name" placeholder="璇烽�夋嫨" style="width: 100%" size="small" clearable>
@@ -121,21 +130,26 @@
         <el-form-item label="璧勮川鍥剧墖">
           <el-upload ref="upload" :action="uploadAction" :on-success="(m) => handleSuccessUpImg(m, 'imageUrl')"
             accept="image/jpg,image/jpeg,image/png" :multiple="false" :limit="1" :headers="headers"
-            :on-change="beforeUpload" :on-error="onError">
+            :show-file-list="false" :before-upload="beforeFormImageUpload" :on-error="() => onError('imageUrl')">
             <template #trigger><el-button  size="small" type="primary">閫夊彇鍥剧墖</el-button></template>
           </el-upload>
+          <el-image v-if="formData.imageUrl" class="qualification-preview" :src="getImageUrl(formData.imageUrl)" fit="cover" />
         </el-form-item>
         <el-form-item label="璧勮川闄勪欢">
           <el-upload ref="upload1" :action="uploadAction" :on-success="(m) => handleSuccessUpImg(m, 'fileUrl')"
             accept="image/jpg,image/jpeg,image/png,application/pdf,.doc,.docx" :headers="headers" :multiple="false"
-            :limit="1" :on-change="beforeUpload1" :on-error="onError1">
+            :limit="1" :show-file-list="false" :before-upload="beforeFormFileUpload" :on-error="() => onError('fileUrl')">
             <template #trigger><el-button  size="small" type="primary">閫夊彇鏂囦欢</el-button></template>
           </el-upload>
+          <el-button v-if="formData.fileUrl" link type="primary" size="small" @click="handleDownLoad(formData)">
+            宸叉湁闄勪欢锛歿{ getFileName(formData.fileUrl) }}
+          </el-button>
         </el-form-item>
       </el-form>
       <template #footer><span  class="dialog-footer">
         <el-button @click="qualificationsConnectVisible = false">鍙� 娑�</el-button>
-        <el-button type="primary" @click="confirmQualifications" :loading="loading">纭� 瀹�</el-button>
+        <el-button type="primary" @click="confirmQualifications" :loading="loading"
+          :disabled="imageUploading || fileUploading">纭� 瀹�</el-button>
       </span></template>
     </el-dialog>
   </div>
@@ -146,7 +160,10 @@
 import {
   addCertificationDetail,
   delCertificationDetail,
+  getCertificationById,
   getCertificationDetail,
+  updateCertificationDetail,
+  updateCertificationImage,
 } from "@/api/structural/laboratory";
 import { getToken } from "@/utils/auth";
 
@@ -177,6 +194,13 @@
           label: "鎿嶄綔",
           operation: [
             {
+              name: "璧勮川鏇存柊",
+              type: "text",
+              clickFun: (row) => {
+                this.openEdit(row);
+              },
+            },
+            {
               name: "闄勪欢涓嬭浇",
               type: "text",
               clickFun: (row) => {
@@ -194,6 +218,7 @@
       tableLoading: false,
       qualificationsList: [],
       qualificationsConnectVisible: false,
+      dialogTitle: "璧勮川鏂板",
       formData: {},
       formDataRules: {
         name: [
@@ -211,6 +236,10 @@
         ],
       },
       loading: false,
+      imageUploading: false,
+      fileUploading: false,
+      overviewUploading: {},
+      scrollHandler: null,
       pageLoading: false,
       isLoding: false, // 鍔犺浇涓紝loading鍥炬爣,榛樿涓簍rue
       finishLoding: false, // 鍔犺浇瀹屾垚锛屾樉绀哄凡缁忔病鏈夋洿澶氫簡
@@ -223,14 +252,32 @@
     };
   },
   mounted() {
+    this.scrollHandler = this.throttle(this.scrollFn.bind(this), 200);
+    window.addEventListener("scroll", this.scrollHandler);
     this.refreshTable();
   },
   methods: {
     selectorSwitch(radio) {
       if (radio === 1) {
-        this.list = [];
+        this.reloadOverview();
+      } else {
+        this.page.current = 1;
         this.refreshTable();
       }
+    },
+    handleQuery() {
+      if (this.radio === 0) {
+        this.page.current = 1;
+        this.refreshTable();
+      } else {
+        this.reloadOverview();
+      }
+    },
+    reloadOverview() {
+      this.currentPage = 1;
+      this.list = [];
+      this.finishLoding = false;
+      this.refreshTable();
     },
     refreshTable() {
       if (this.radio === 0) {
@@ -251,12 +298,6 @@
           this.isLoding = true;
         } else {
           this.pageLoading = true;
-        }
-        if (this.list.length === 0) {
-          window.addEventListener(
-            "scroll",
-            this.throttle(this.scrollFn, 20000)
-          );
         }
         getCertificationDetail({
           current: this.currentPage,
@@ -290,16 +331,13 @@
     },
     // 閲嶇疆
     refresh() {
+      this.queryParams.name = "";
       if (this.radio === 0) {
-        this.queryParams.name = "";
         this.page.size = 10;
         this.page.current = 1;
         this.refreshTable();
       } else {
-        this.finishLoding = false;
-        this.currentPage = 1;
-        this.list = [];
-        this.refreshTable();
+        this.reloadOverview();
       }
     },
     // 琛ㄦ牸澶氶��
@@ -307,6 +345,7 @@
       this.selection = selection;
     },
     pagination(page) {
+      this.page.current = page.page;
       this.page.size = page.limit;
       this.refreshTable();
     },
@@ -335,57 +374,140 @@
       this.$download.saveAs(row.fileUrl, row.fileUrl);
     },
     openAdd() {
-      this.qualificationsConnectVisible = true;
-      this.$nextTick(() => {
-        this.$refs.upload.clearFiles();
-        this.$refs.upload1.clearFiles();
-      });
+      this.dialogTitle = "璧勮川鏂板";
       this.formData = {};
+      this.qualificationsConnectVisible = true;
+      this.clearUploadFiles();
+    },
+    openEdit(row) {
+      this.dialogTitle = "璧勮川鏇存柊";
+      this.loading = true;
+      getCertificationById(row.id).then((res) => {
+        if (res.code !== 200 || !res.data) return;
+        this.formData = { ...res.data };
+        this.qualificationsConnectVisible = true;
+        this.clearUploadFiles();
+      }).finally(() => {
+        this.loading = false;
+      });
+    },
+    resetDialog() {
+      this.formData = {};
+      this.dialogTitle = "璧勮川鏂板";
+      this.imageUploading = false;
+      this.fileUploading = false;
+      this.clearUploadFiles();
+      this.$refs.formDataRef?.clearValidate();
+    },
+    clearUploadFiles() {
+      this.$nextTick(() => {
+        this.$refs.upload?.clearFiles();
+        this.$refs.upload1?.clearFiles();
+      });
     },
     handleSuccessUpImg(response, name) {
-      if (response.code === 200) {
+      this.setFormUploadState(name, false);
+      if (response && response.code === 200 && response.data && response.data.url) {
         this.formData[name] = response.data.url;
+        this.$message.success(name === 'imageUrl' ? "鍥剧墖涓婁紶鎴愬姛" : "闄勪欢涓婁紶鎴愬姛");
+        this.clearUploadFiles();
+      } else {
+        this.$message.error("涓婁紶澶辫触");
       }
     },
-    beforeUpload(file) {
+    validateImage(file) {
+      const allowed = ["image/jpeg", "image/png"];
+      if (!allowed.includes(file.type)) {
+        this.$message.error("浠呮敮鎸丣PG銆丣PEG銆丳NG鍥剧墖");
+        return false;
+      }
       if (file.size > 1024 * 1024 * 10) {
         this.$message.error("涓婁紶鏂囦欢涓嶈秴杩�10M");
-        this.$refs.upload.clearFiles();
         return false;
-      } else {
-        return true;
       }
+      return true;
     },
-    onError(err, file, fileList) {
-      this.$message.error("涓婁紶澶辫触");
-      this.$refs.upload.clearFiles();
+    beforeFormImageUpload(file) {
+      const valid = this.validateImage(file);
+      this.imageUploading = valid;
+      return valid;
     },
-    beforeUpload1(file) {
+    beforeFormFileUpload(file) {
+      const extension = (file.name.split('.').pop() || '').toLowerCase();
+      const allowed = ["jpg", "jpeg", "png", "pdf", "doc", "docx"];
+      if (!allowed.includes(extension)) {
+        this.$message.error("浠呮敮鎸丣PG銆丣PEG銆丳NG銆丳DF銆丏OC銆丏OCX鏂囦欢");
+        return false;
+      }
       if (file.size > 1024 * 1024 * 10) {
         this.$message.error("涓婁紶鏂囦欢涓嶈秴杩�10M");
-        this.$refs.upload1.clearFiles();
         return false;
-      } else {
-        return true;
       }
+      this.fileUploading = true;
+      return true;
     },
-    onError1(err, file, fileList) {
+    setFormUploadState(name, value) {
+      if (name === 'imageUrl') this.imageUploading = value;
+      if (name === 'fileUrl') this.fileUploading = value;
+    },
+    onError(name) {
+      this.setFormUploadState(name, false);
       this.$message.error("涓婁紶澶辫触");
-      this.$refs.upload1.clearFiles();
+      this.clearUploadFiles();
+    },
+    beforeOverviewImageUpload(file, row) {
+      const valid = this.validateImage(file);
+      if (valid) this.overviewUploading[row.id] = true;
+      return valid;
+    },
+    handleOverviewImageSuccess(response, row) {
+      if (!response || response.code !== 200 || !response.data || !response.data.url) {
+        this.overviewUploading[row.id] = false;
+        this.$message.error("鍥剧墖涓婁紶澶辫触");
+        return;
+      }
+      const imageUrl = response.data.url;
+      updateCertificationImage({ id: row.id, imageUrl }).then((res) => {
+        if (res.code !== 200) return;
+        row.imageUrl = imageUrl;
+        this.$message.success("鍥剧墖鏇存柊鎴愬姛");
+      }).finally(() => {
+        this.overviewUploading[row.id] = false;
+      });
+    },
+    handleOverviewImageError(id) {
+      this.overviewUploading[id] = false;
+      this.$message.error("鍥剧墖涓婁紶澶辫触");
+    },
+    getImageUrl(imageUrl) {
+      if (!imageUrl) return "";
+      if (/^https?:\/\//i.test(imageUrl)) return imageUrl;
+      if (imageUrl.startsWith('/')) return this.javaApi + imageUrl;
+      return this.javaApi + '/img/' + imageUrl;
+    },
+    getFileName(fileUrl) {
+      if (!fileUrl) return "";
+      return fileUrl.split(/[\\/]/).pop();
     },
     confirmQualifications() {
+      if (this.imageUploading || this.fileUploading) {
+        this.$message.warning("璇风瓑寰呮枃浠朵笂浼犲畬鎴�");
+        return;
+      }
       this.$refs["formDataRef"].validate((valid) => {
         if (valid) {
           this.loading = true;
-          addCertificationDetail({ ...this.formData })
+          const submit = this.dialogTitle === "璧勮川鏂板"
+            ? addCertificationDetail
+            : updateCertificationDetail;
+          submit({ ...this.formData })
             .then((res) => {
-              this.loading = false;
+              if (res.code !== 200) return;
               this.$message.success("宸叉彁浜�");
-              this.refreshTable();
-              this.resetForm("formDataRef");
               this.qualificationsConnectVisible = false;
+              this.handleQuery();
             })
-            .catch((err) => {
+            .finally(() => {
               this.loading = false;
             });
         }
@@ -428,7 +550,7 @@
     },
   },
   unmounted() {
-    window.removeEventListener("scroll", this.throttle(), false);
+    if (this.scrollHandler) window.removeEventListener("scroll", this.scrollHandler);
   },
 };
 </script>
@@ -450,4 +572,12 @@
   align-items: center;
   justify-content: space-between;
 }
+
+.qualification-preview {
+  width: 72px;
+  height: 72px;
+  margin-left: 10px;
+  border-radius: 8px;
+  vertical-align: middle;
+}
 </style>

--
Gitblit v1.9.3