From b93657ca98a9f1ced3aacc255c3fa09b7d39cca4 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 20 十月 2026 18:01:45 +0800
Subject: [PATCH] feat: 质量监控/人员培训/体系文件控制功能与界面调整

---
 src/components/Table/lims-table.vue                                                         |    5 
 src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue                        |   12 +-
 src/views/CNAS/personnel/personnelInfo/tabs/personnelTraining.vue                           |   36 +++---
 src/views/CNAS/process/ensureResults/ensureResultsValidity/components/evaluateDialog.vue    |    4 
 src/views/CNAS/systemManagement/documentControl/components/DistributionCollectionRecord.vue |    2 
 public/import-templates/质量监控计划导入模板.xlsx                                                     |    0 
 src/views/CNAS/systemManagement/documentControl/components/FileList.vue                     |    7 +
 src/views/CNAS/personnel/personnelInfo/components/Edit.vue                                  |   80 +++++++++------
 src/views/CNAS/personnel/personnelInfo/components/AddInDetail.vue                           |   47 ++++++++-
 src/views/CNAS/systemManagement/documentControl/components/ControlledFileApplication.vue    |   19 ++-
 src/components/ImportDialog/index.vue                                                       |   12 ++
 src/components/UpPdfStamp/index.vue                                                         |   31 +++++
 src/views/CNAS/process/ensureResults/qualityControlPlan/index.vue                           |    6 
 src/views/CNAS/systemManagement/documentControl/components/FileChangeRequest.vue            |   14 ++
 public/import-templates/人员培训年度计划导入模板.xlsx                                                   |    0 
 src/views/CNAS/process/ensureResults/ensureResultsValidity/components/detailFormDialog.vue  |   25 ++++
 16 files changed, 214 insertions(+), 86 deletions(-)

diff --git "a/public/import-templates/\344\272\272\345\221\230\345\237\271\350\256\255\345\271\264\345\272\246\350\256\241\345\210\222\345\257\274\345\205\245\346\250\241\346\235\277.xlsx" "b/public/import-templates/\344\272\272\345\221\230\345\237\271\350\256\255\345\271\264\345\272\246\350\256\241\345\210\222\345\257\274\345\205\245\346\250\241\346\235\277.xlsx"
index 269954c..7a0ed12 100644
--- "a/public/import-templates/\344\272\272\345\221\230\345\237\271\350\256\255\345\271\264\345\272\246\350\256\241\345\210\222\345\257\274\345\205\245\346\250\241\346\235\277.xlsx"
+++ "b/public/import-templates/\344\272\272\345\221\230\345\237\271\350\256\255\345\271\264\345\272\246\350\256\241\345\210\222\345\257\274\345\205\245\346\250\241\346\235\277.xlsx"
Binary files differ
diff --git "a/public/import-templates/\350\264\250\351\207\217\347\233\221\346\216\247\350\256\241\345\210\222\345\257\274\345\205\245\346\250\241\346\235\277.xlsx" "b/public/import-templates/\350\264\250\351\207\217\347\233\221\346\216\247\350\256\241\345\210\222\345\257\274\345\205\245\346\250\241\346\235\277.xlsx"
index 5e42a1b..9b48a50 100644
--- "a/public/import-templates/\350\264\250\351\207\217\347\233\221\346\216\247\350\256\241\345\210\222\345\257\274\345\205\245\346\250\241\346\235\277.xlsx"
+++ "b/public/import-templates/\350\264\250\351\207\217\347\233\221\346\216\247\350\256\241\345\210\222\345\257\274\345\205\245\346\250\241\346\235\277.xlsx"
Binary files differ
diff --git a/src/components/ImportDialog/index.vue b/src/components/ImportDialog/index.vue
index 51b5ede..cfb3c87 100644
--- a/src/components/ImportDialog/index.vue
+++ b/src/components/ImportDialog/index.vue
@@ -186,7 +186,8 @@
 .template-tip {
   display: flex;
   align-items: center;
-  gap: 6px;
+  flex-wrap: nowrap;
+  gap: 4px;
   margin-bottom: 12px;
   padding: 8px 12px;
   border-radius: 4px;
@@ -195,6 +196,15 @@
   font-size: 13px;
 }
 
+.template-tip > span {
+  white-space: nowrap;
+}
+
+.template-tip .el-button {
+  flex-shrink: 0;
+  margin-left: 0;
+}
+
 .upload-tips {
   margin-top: 10px;
   color: var(--el-text-color-secondary);
diff --git a/src/components/Table/lims-table.vue b/src/components/Table/lims-table.vue
index 70539e5..946cca8 100644
--- a/src/components/Table/lims-table.vue
+++ b/src/components/Table/lims-table.vue
@@ -231,7 +231,10 @@
 }
 
 function indexMethod(index) {
-  return (props.page.current - 1) * props.page.size + index + 1
+  // 鏈紶 page 鏃堕粯璁� current 涓� 0锛屼細绠楀嚭 -9銆�-8 杩欑被璐熷簭鍙凤紱杩欓噷鍏滃簳涓虹涓�椤�
+  const current = props.page && props.page.current > 0 ? props.page.current : 1
+  const size = props.page && props.page.size > 0 ? props.page.size : 10
+  return (current - 1) * size + index + 1
 }
 
 function onPagination({ page, limit }) {
diff --git a/src/components/UpPdfStamp/index.vue b/src/components/UpPdfStamp/index.vue
index 0c6088e..fd92a4e 100644
--- a/src/components/UpPdfStamp/index.vue
+++ b/src/components/UpPdfStamp/index.vue
@@ -1,7 +1,10 @@
 <template>
   <div class="up-pdf-stamp">
     <div v-if="isUpFile" class="work" style="display: flex;justify-content: space-between;align-items: center;margin-bottom: 10px;">
-      <input ref="fileInput" type="file" accept="application/pdf" @change="handleFileUpload" />
+      <input ref="fileInput" type="file" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.wps,.et,.dps,.txt,.jpg,.jpeg,.png" @change="handleFileUpload" />
+      <span v-if="fileName !== '鏂囦欢鍚�'" style="margin-left: 10px;font-size: 12px;color: var(--el-text-color-secondary);">
+        宸查�夋嫨锛歿{ fileName }}<template v-if="!isPdfFile">锛堥潪 PDF 鏂囦欢鎸夊師鏂囦欢涓婁紶锛屼笉鏀寔鐩栫珷锛�</template>
+      </span>
     </div>
     <div v-if="canvasNumPages > 0" style="color: red;font-size: 12px;margin: 16px 0;">鎻愮ず锛氬湪鏂囦欢鑼冨洿鍐咃紝鍗曞嚮榧犳爣鐩栫珷锛屽弻鍑婚紶鏍囧凡鐩栧ソ绔犲鍙垹闄ゅ綋鍓嶇珷</div>
     <canvas
@@ -35,7 +38,9 @@
       stampHeight: 80, // 鐩栫珷楂樺害
       stampsName: '',
       stampsList: ['涓讳换', '璐ㄩ噺璐熻矗浜�', '鎶�鏈礋璐d汉'],
-      fileName: '鏂囦欢鍚�'
+      fileName: '鏂囦欢鍚�',
+      selectedFile: null, // 闈� PDF 鏂囦欢锛氫繚鐣欏師濮� File锛岀洿鎺ヤ笂浼�
+      isPdfFile: false
     }
   },
   methods: {
@@ -49,6 +54,9 @@
       const file = event.target.files[0]
       if (file.size > 20 * 1024 * 1024) {
         this.$refs.fileInput.value = '' // 娓呯┖鏂囦欢杈撳叆妗嗗唴瀹�
+        this.selectedFile = null
+        this.isPdfFile = false
+        this.fileName = '鏂囦欢鍚�'
         return this.$message.error('鏂囦欢澶у皬涓嶈兘瓒呰繃20M')
       }
       this.lookFile(file)
@@ -67,14 +75,25 @@
           this.stampsName = '缁煎悎瀹�'
         }
       }
-      if (file && file.type === 'application/pdf') {
+      this.isPdfFile = !!file && (file.type === 'application/pdf' || /\.pdf$/i.test(file.name || ''))
+      if (this.isPdfFile) {
+        this.selectedFile = file
         const reader = new FileReader()
         reader.onload = e => {
           const typedArray = new Uint8Array(e.target.result)
           this.loadPDF(typedArray)
         }
         reader.readAsArrayBuffer(file)
+      } else if (this.isUpFile) {
+        // 鏂囦欢娓呭崟涓婁紶鏀寔 Word/Excel/鍥剧墖绛夋牸寮忥紝涓嶅仛鐩栫珷锛岄�変腑鍚庢寜鍘熸枃浠朵笂浼�
+        this.selectedFile = file
+        this.pdfDoc = null
+        this.canvasNumPages = 0
+        this.stamps = []
+        this.contextList = []
       } else {
+        // 鍏朵粬娴佺▼锛堟枃浠剁敵璇枫�佸彉鏇寸敵璇风瓑锛変粛鍙鐞� PDF
+        this.selectedFile = null
         this.$message.error('璇烽�夋嫨 PDF 鏂囦欢')
       }
     },
@@ -167,8 +186,12 @@
     },
     // 鐢熸垚 PDF 鐨勫嚱鏁�
     async generatePDF() {
+      // 闈� PDF锛氫笉鐩栫珷锛屽師鏂囦欢鐩存帴涓婁紶
+      if (this.selectedFile && !this.isPdfFile) {
+        return this.$emit('uploadPDF', this.selectedFile, this.fileName)
+      }
       if (this.contextList.length === 0) {
-        this.$message({ message: '璇峰厛涓婁紶PDF鏂囦欢', type: 'error' })
+        this.$message({ message: '璇峰厛閫夋嫨鏂囦欢', type: 'error' })
         this.$emit('uploadPDFErr')
         return false
       }
diff --git a/src/views/CNAS/personnel/personnelInfo/components/AddInDetail.vue b/src/views/CNAS/personnel/personnelInfo/components/AddInDetail.vue
index de32220..1b2291b 100644
--- a/src/views/CNAS/personnel/personnelInfo/components/AddInDetail.vue
+++ b/src/views/CNAS/personnel/personnelInfo/components/AddInDetail.vue
@@ -33,12 +33,20 @@
             </el-col>
             <el-col :span="12">
               <el-form-item label="鍩硅璁插笀:" prop="trainingLecturerId">
-                <el-select v-model="trainingPlan.trainingLecturerId"
-                           clearable filterable
-                           placeholder="璇烽�夋嫨" size="small" style="width: 100%;">
-                  <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id">
-                  </el-option>
-                </el-select>
+                <div style="display: flex; align-items: center; gap: 8px; width: 100%;">
+                  <el-radio-group v-model="lecturerType" size="small" @change="handleLecturerTypeChange">
+                    <el-radio-button label="internal">鍐呴儴浜哄憳</el-radio-button>
+                    <el-radio-button label="external">澶栬仒璁插笀</el-radio-button>
+                  </el-radio-group>
+                  <el-select v-if="lecturerType === 'internal'" v-model="trainingPlan.trainingLecturerId"
+                             clearable filterable
+                             placeholder="璇烽�夋嫨" size="small" style="flex: 1;">
+                    <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id">
+                    </el-option>
+                  </el-select>
+                  <el-input v-else v-model="trainingPlan.trainingLecturerName" clearable
+                            placeholder="璇疯緭鍏ュ鑱樿甯堝鍚�" size="small" style="flex: 1;"></el-input>
+                </div>
               </el-form-item>
             </el-col>
             <el-col :span="12">
@@ -88,6 +96,7 @@
     return {
       addTrainingPlanDia: false,
       submitAddLoading: false,
+      lecturerType: 'internal',
       trainingPlan: {
         planId: '',
         id: '',
@@ -97,6 +106,7 @@
         participants: '',
         trainingContent: '',
         trainingLecturerId: '',
+        trainingLecturerName: '',
         trainingMode: '',
         holdingDepartment: '',
         remarks: '',
@@ -104,7 +114,6 @@
       trainingPlanRules: {
         trainingDate: [{ required: true, message: '璇烽�夋嫨鍩硅鏃ユ湡', trigger: 'change' }],
         trainingContent: [{ required: true, message: '璇疯緭鍏ュ煿璁唴瀹�', trigger: 'blur' }],
-        trainingLecturerId: [{ required: true, message: '璇烽�夋嫨鍩硅璁插笀', trigger: 'blur' }],
       },
       responsibleOptions: [],
       operationType: ''
@@ -117,7 +126,9 @@
       this.operationType = type
       if (this.operationType === 'edit') {
         this.trainingPlan = this.HaveJson(row)
+        this.lecturerType = row.trainingLecturerId ? 'internal' : (row.trainingLecturerName ? 'external' : 'internal')
       } else {
+        this.lecturerType = 'internal'
         this.trainingPlan = {
           id: '',
           trainingDate: '',
@@ -134,10 +145,32 @@
       this.trainingPlan.planId = id
       this.getUserList()
     },
+    handleLecturerTypeChange(type) {
+      if (type === 'internal') {
+        this.trainingPlan.trainingLecturerName = ''
+      } else {
+        this.trainingPlan.trainingLecturerId = null
+      }
+    },
     // 鎻愪氦鏂板
     submitAdd() {
       this.$refs.trainingPlanForm.validate((valid) => {
         if (valid) {
+          if (this.lecturerType === 'internal' && !this.trainingPlan.trainingLecturerId) {
+            this.$message.warning('璇烽�夋嫨鍩硅璁插笀')
+            return
+          }
+          if (this.lecturerType === 'external') {
+            const lecturerName = (this.trainingPlan.trainingLecturerName || '').trim()
+            if (!lecturerName) {
+              this.$message.warning('璇疯緭鍏ュ鑱樿甯堝鍚�')
+              return
+            }
+            this.trainingPlan.trainingLecturerId = null
+            this.trainingPlan.trainingLecturerName = lecturerName
+          } else {
+            this.trainingPlan.trainingLecturerName = null
+          }
           this.submitAddLoading = true
           this.trainingPlan.planId = this.currentChangeRow.id
           const personTrainingDetailed = this.trainingPlan
diff --git a/src/views/CNAS/personnel/personnelInfo/components/Edit.vue b/src/views/CNAS/personnel/personnelInfo/components/Edit.vue
index 1dc611e..95d521d 100644
--- a/src/views/CNAS/personnel/personnelInfo/components/Edit.vue
+++ b/src/views/CNAS/personnel/personnelInfo/components/Edit.vue
@@ -10,40 +10,44 @@
     </div>
     <div class="form_title">
       <el-row>
-        <el-col :span="7">
+        <el-col :span="8">
           <span class="form_label">鍩硅鍐呭锛�</span>
           <span> {{ trainingForm.trainingContent }} </span>
         </el-col>
-        <el-col :span="5">
+        <el-col :span="4">
           <span class="form_label">鐘舵�侊細</span>
           <el-tag v-if="trainingForm.state === 1" type="success">宸插畬鎴�</el-tag>
           <el-tag v-if="trainingForm.state === 2" type="warning">寰呰瘎浠�</el-tag>
           <el-tag v-if="trainingForm.state === 3" type="primary">鏈紑濮�</el-tag>
         </el-col>
-        <el-col :span="4">
+        <el-col :span="5">
           <span class="form_label">鍩硅璁插笀锛�</span>
           <span> {{ trainingForm.trainingLecturerName }} </span>
         </el-col>
+        <el-col :span="7">
+          <span class="form_label">鍩硅鏃ユ湡锛�</span>
+          <span> {{ trainingForm.trainingDate || '-' }} </span>
+        </el-col>
       </el-row>
       <el-row style="margin: 15px 0">
-        <el-form>
+        <el-form label-width="130px">
           <el-row>
           <el-col :span="6">
-            <el-form-item label="鍩硅鏃ユ湡:">
+            <el-form-item label="寮�濮嬫椂闂�:">
               <el-date-picker v-model="trainingForm.openingTime" format="YYYY-MM-DD" :disabled="currentRow.state !== 3"
-                              placeholder="閫夋嫨鏃ユ湡" size="small" value-format="YYYY-MM-DD"
-                              type="date" style="width: 60%"></el-date-picker>
+                              placeholder="閫夋嫨瀹為檯寮�濮嬫棩鏈�" size="small" value-format="YYYY-MM-DD"
+                              type="date" style="width: 100%"></el-date-picker>
             </el-form-item>
           </el-col>
-            <el-col :span="7">
+            <el-col :span="9">
               <el-form-item label="鍩硅鍦扮偣">
                 <el-input v-model="trainingForm.placeTraining" :disabled="currentRow.state !== 3" :rows="2" placeholder="璇疯緭鍏�"
-                          size="small" style="width: 60%" type="text"></el-input>
+                          size="small" style="width: 100%" type="text"></el-input>
               </el-form-item>
             </el-col>
-            <el-col :span="6">
+            <el-col :span="9">
               <el-form-item label="璇勪环浜�">
-                <el-select v-model="trainingForm.assessmentUserId" :disabled="currentRow.state !== 3" placeholder="璇烽�夋嫨" size="small" style="width: 50%">
+                <el-select v-model="trainingForm.assessmentUserId" :disabled="currentRow.state !== 3" placeholder="璇烽�夋嫨" size="small" style="width: 100%">
                   <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"></el-option>
                 </el-select>
               </el-form-item>
@@ -51,7 +55,7 @@
             <el-col :span="12">
               <el-form-item label="鑰冩牳鏂瑰紡">
                 <el-input v-model="trainingForm.assessmentMethod" :disabled="currentRow.state !== 3" :rows="2" placeholder="璇疯緭鍏�"
-                          size="small" style="width: 79%" type="textarea"></el-input>
+                          size="small" style="width: 100%" type="textarea"></el-input>
               </el-form-item>
             </el-col>
           </el-row>
@@ -60,14 +64,14 @@
               <el-form-item label="璇勪环鏃堕棿">
                 <el-date-picker v-model="trainingForm.assessmentDate" :disabled="currentRow.state !== 2 || isDisabled"
                                 type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" size="small"
-                                placeholder="閫夋嫨鏃ユ湡">
+                                placeholder="閫夋嫨鏃ユ湡" style="width: 100%">
                 </el-date-picker>
               </el-form-item>
             </el-col>
           <el-col :span="12">
             <el-form-item label="鏈鍩硅缁煎悎璇勪环">
               <el-input v-model="trainingForm.comprehensiveAssessment" :disabled="currentRow.state !== 2 || isDisabled" :rows="2" placeholder="璇疯緭鍏�"
-                        size="small" style="width: 68%" type="textarea"></el-input>
+                        size="small" style="width: 100%" type="textarea"></el-input>
             </el-form-item>
           </el-col>
           </el-row>
@@ -76,15 +80,17 @@
     </div>
     <el-divider>浜哄憳璇︽儏</el-divider>
     <div>
-      <div class="items_center">
-        <span>濮撳悕</span>
-        <el-input v-model="userName" class="search" placeholder="璇疯緭鍏�"
-                  size="small"></el-input>
-        <el-button size="small" type="primary" @click="getInfo">鏌ヨ</el-button>
-      </div>
-      <div class="items_btn">
-        <el-button :disabled="currentRow.state === 1" size="small" type="primary" @click="addPerson">鏂板浜哄憳</el-button>
-        <el-button :disabled="currentRow.state === 1" size="small" @click="batchDelete">鎵归噺鍒犻櫎</el-button>
+      <div class="items_header">
+        <div class="items_center">
+          <span>濮撳悕</span>
+          <el-input v-model="userName" class="person-search-input" placeholder="璇疯緭鍏�"
+                    size="small" clearable @keyup.enter="getInfo"></el-input>
+          <el-button size="small" type="primary" @click="getInfo">鏌ヨ</el-button>
+        </div>
+        <div class="items_btn">
+          <el-button :disabled="currentRow.state === 1" size="small" type="primary" @click="addPerson">鏂板浜哄憳</el-button>
+          <el-button :disabled="currentRow.state === 1" size="small" @click="batchDelete">鎵归噺鍒犻櫎</el-button>
+        </div>
       </div>
       <el-table :data="trainingTableData" height="calc(100vh - 30em)" stripe style="width: 100%"
  border
@@ -117,7 +123,7 @@
         </el-form>
       </div>
       <div v-if="selectUserDia" class="body" style="height: 60vh;">
-        <lims-table :tableData="tableData1" :column="column1"
+        <lims-table :tableData="tableData1" :column="column1" :page="page1"
                     :isSelection="true" :handleSelectionChange="selectMethod"
                     height="500" :tableLoading="tableLoading1"></lims-table>
       </div>
@@ -402,15 +408,27 @@
 }
 
 
+.items_header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 10px;
+}
 .items_center {
-  float: left;
-  width: 50%;
-  text-align: left;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.items_center > span {
+  line-height: 24px;
+  white-space: nowrap;
+}
+.items_center .person-search-input {
+  width: 200px;
 }
 .items_btn {
-  text-align: right;
-  width: 50%;
-  float: right;
-  margin-bottom: 1em;
+  display: flex;
+  align-items: center;
+  gap: 8px;
 }
 </style>
diff --git a/src/views/CNAS/personnel/personnelInfo/tabs/personnelTraining.vue b/src/views/CNAS/personnel/personnelInfo/tabs/personnelTraining.vue
index 71c311b..5d58279 100644
--- a/src/views/CNAS/personnel/personnelInfo/tabs/personnelTraining.vue
+++ b/src/views/CNAS/personnel/personnelInfo/tabs/personnelTraining.vue
@@ -95,9 +95,9 @@
           </el-option>
         </el-select>
       </div>
-      <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;">
-        <span>绗竴娆″鍏ヨ鍏堜笅杞芥ā鏉匡紝鎸夋ā鏉挎牸寮忓~鍐欏悗鍐嶄笂浼犮��</span>
-        <el-button link type="primary" @click="downloadTemplate">涓嬭浇瀵煎叆妯℃澘</el-button>
+      <div style="display: flex; align-items: center; flex-wrap: nowrap; gap: 4px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;">
+        <span style="white-space: nowrap;">绗竴娆″鍏ヨ鍏堜笅杞芥ā鏉匡紝鎸夋ā鏉挎牸寮忓~鍐欏悗鍐嶄笂浼犮��</span>
+        <el-button link type="primary" style="flex-shrink: 0; margin-left: 0;" @click="downloadTemplate">涓嬭浇瀵煎叆妯℃澘</el-button>
       </div>
       <div style="margin: 0 auto;">
         <el-upload ref="upload"
@@ -110,7 +110,7 @@
       </div>
       <div style="margin-top: 10px; color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px;">
         <div>鏀寔鏍煎紡锛�.xls銆�.xlsx锛涘崟涓枃浠朵笉瓒呰繃10MB銆�</div>
-        <div>鍩硅璁插笀蹇呴』濉啓绯荤粺涓凡瀛樺湪鐨勪汉鍛樺鍚嶏紝鍚﹀垯鏁存壒瀵煎叆澶辫触銆�</div>
+        <div>鍩硅璁插笀濉啓绯荤粺鍐呬汉鍛樺鍚嶆椂鑷姩鍏宠仈锛涘鑱樿甯堢洿鎺ュ~鍐欏鍚嶏紝鎸夋枃鏈繚瀛樸��</div>
         <div>鍩硅鏃堕棿寤鸿濉啓 YYYY-MM-DD锛岃鏃跺~鍐欐暣鏁般��</div>
         <div>瀵煎叆鍙鍙栨ā鏉跨殑绗竴涓伐浣滆〃銆�</div>
       </div>
@@ -325,23 +325,23 @@
         {
           label: '鍩硅澶х被',
           prop: 'trainingCategory',
-          width: '100px',
+          minWidth: '100px',
         },{
           label: '鍩硅鐩爣',
           prop: 'trainingObjectives',
-          width: '100px',
+          minWidth: '100px',
         }, {
           label: '鍩硅鍐呭',
           prop: 'trainingContent',
-          width: '100px',
+          minWidth: '100px',
         }, {
           label: '鍩硅鏂瑰紡',
           prop: 'trainingMode',
-          width: '100px',
+          minWidth: '100px',
         }, {
           dataType: 'tag',
           label: '璇剧▼鐘舵��',
-          width: '100px',
+          minWidth: '100px',
           prop: 'state',
           formatData: (params) => {
             if (params == 1) {
@@ -370,34 +370,34 @@
         }, {
           label: '鍙傚姞瀵硅薄',
           prop: 'participants',
-          width: '100px',
+          minWidth: '100px',
         }, {
           label: '涓惧姙閮ㄩ棬',
           prop: 'holdingDepartment',
-          width: '100px',
+          minWidth: '100px',
         }, {
           label: '鍩硅璁插笀',
           prop: 'trainingLecturerName',
-          width: '100px',
+          minWidth: '100px',
         }, {
           label: '鍩硅鏃ユ湡',
           prop: 'trainingDate',
-          width: '100px',
+          minWidth: '100px',
         }, {
           label: '寮�濮嬫椂闂�',
           prop: 'openingTime',
-          width: '100px',
+          minWidth: '100px',
         }, {
           label: '璇炬椂',
           prop: 'classHour',
-          width: '100px',
+          minWidth: '100px',
         }, {
           label: '澶囨敞',
           prop: 'remarks',
-          width: '100px',
+          minWidth: '100px',
         }, {
           dataType: 'action',
-          width: '200',
+          minWidth: '200px',
           label: '鎿嶄綔',
           fixed: 'right',
           operation: [
@@ -645,7 +645,7 @@
     // 涓嬭浇骞村害璁″垝瀵煎叆妯℃澘
     downloadTemplate() {
       const link = document.createElement('a')
-      link.href = '/import-templates/浜哄憳鍩硅骞村害璁″垝瀵煎叆妯℃澘.xlsx?v=2'
+      link.href = '/import-templates/浜哄憳鍩硅骞村害璁″垝瀵煎叆妯℃澘.xlsx?v=3'
       link.download = '浜哄憳鍩硅骞村害璁″垝瀵煎叆妯℃澘.xlsx'
       document.body.appendChild(link)
       link.click()
diff --git a/src/views/CNAS/process/ensureResults/ensureResultsValidity/components/detailFormDialog.vue b/src/views/CNAS/process/ensureResults/ensureResultsValidity/components/detailFormDialog.vue
index 88318c1..073dca1 100644
--- a/src/views/CNAS/process/ensureResults/ensureResultsValidity/components/detailFormDialog.vue
+++ b/src/views/CNAS/process/ensureResults/ensureResultsValidity/components/detailFormDialog.vue
@@ -21,8 +21,11 @@
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="璐ㄦ帶鏂瑰紡" prop="monitorWay">
-            <el-input v-model="form.monitorWay" size="small" clearable placeholder="濡傜洸鏍锋娴嬨�佷汉鍛樻瘮瀵广�佺洃鐫f鏌�" />
+          <el-form-item label="璐ㄦ帶鏂瑰紡" prop="monitorWayList">
+            <el-select v-model="form.monitorWayList" multiple filterable allow-create default-first-option
+                       size="small" style="width: 100%" placeholder="鍙閫夛紝涔熷彲杈撳叆鍏朵粬鏂瑰紡">
+              <el-option v-for="item in monitorWayOptions" :key="item" :label="item" :value="item" />
+            </el-select>
           </el-form-item>
         </el-col>
         <el-col :span="12">
@@ -103,6 +106,11 @@
     // 杩欓噷瀛樻斁鏁版嵁
     return {
       formDia: false,
+      // LMSY-CX30-2024銆婅川閲忔帶鍒剁▼搴忋��4.1 瑙勫畾鐨勫父鐢ㄨ川閲忔帶鍒舵墜娈碉紝鍙閫�
+      monitorWayOptions: [
+        '浜哄憳姣斿', '璁惧姣斿', '鏂规硶姣斿', '鐩叉牱妫�娴�',
+        '鐣欐牱鍐嶆祴', '鏍囧噯鐗╄川鐩戞帶', '瀹為獙瀹ら棿姣斿', '鑳藉姏楠岃瘉',
+      ],
       form: {
         qualityMonitorDetailsId: '',
         sortNo: null,
@@ -116,6 +124,7 @@
         budget: '',
         organization: '',
         monitorWay: '',
+        monitorWayList: [],
         itemKind: 'OTHER',
         standardMethodId: null,
         templateId: null,
@@ -134,7 +143,7 @@
       rules: {
         monitorProject: [{ required: true, message: '璇疯緭鍏ヨ川鎺ч」鐩�', trigger: 'blur' }],
         technicalStandardText: [{ required: true, message: '璇疯緭鍏ユ妧鏈爣鍑�', trigger: 'blur' }],
-        monitorWay: [{ required: true, message: '璇疯緭鍏ヨ川鎺ф柟寮�', trigger: 'blur' }],
+        monitorWayList: [{ required: true, type: 'array', min: 1, message: '璇烽�夋嫨璐ㄦ帶鏂瑰紡', trigger: 'change' }],
         completionMonth: [{ required: true, message: '璇烽�夋嫨瀹屾垚鏃堕檺', trigger: 'change' }],
         responsibleUserId: [{ required: true, message: '璇烽�夋嫨璐熻矗浜�', trigger: 'change' }],
         templateIds: [{ required: true, type: 'array', min: 1, message: '璇疯嚦灏戦�夋嫨涓�涓師濮嬭褰曟ā鏉�', trigger: 'change' }],
@@ -148,6 +157,7 @@
     openDia(type, row) {
       this.formDia = true
       this.operationType = type
+      this.form.monitorWayList = []
       this.templateOptions = []
       this.allTemplateOptions = []
       this.templateBindingLoading = false
@@ -165,7 +175,12 @@
       const templateIds = row.templateIds
         ? String(row.templateIds).split(',').filter(Boolean).map(id => Number(id))
         : (row.templateId ? [Number(row.templateId)] : [])
-      this.form = {...row, itemKind: row.itemKind || 'OTHER', templateIds}
+      this.form = {
+        ...row,
+        itemKind: row.itemKind || 'OTHER',
+        templateIds,
+        monitorWayList: row.monitorWay ? String(row.monitorWay).split(/[銆�,锛�;锛沒/).map(item => item.trim()).filter(Boolean) : [],
+      }
     },
     handleStandardChange(id) {
       const standard = this.standardOptions.find(item => Number(item.id) === Number(id))
@@ -211,6 +226,8 @@
         : []
       entity.templateIds = templateIds.join(',') || null
       entity.templateId = templateIds.length ? templateIds[0] : null
+      entity.monitorWay = (this.form.monitorWayList || []).map(item => item.trim()).filter(Boolean).join('銆�')
+      delete entity.monitorWayList
       entity.reviewerUserId = null
       return entity
     },
diff --git a/src/views/CNAS/process/ensureResults/ensureResultsValidity/components/evaluateDialog.vue b/src/views/CNAS/process/ensureResults/ensureResultsValidity/components/evaluateDialog.vue
index 40d28e6..1571068 100644
--- a/src/views/CNAS/process/ensureResults/ensureResultsValidity/components/evaluateDialog.vue
+++ b/src/views/CNAS/process/ensureResults/ensureResultsValidity/components/evaluateDialog.vue
@@ -2,8 +2,10 @@
   <div>
     <el-dialog :close-on-click-modal="false" :close-on-press-escape="false"
                v-model="formDia"
-               title="璐ㄩ噺鐩戞帶璇勪环"
+               title="璐ㄩ噺鎺у埗璇勪环璁板綍"
                width="60%" @close="closeEvaDia">
+      <el-alert type="info" :closable="false" show-icon style="margin-bottom: 12px"
+        title="妫�娴嬪涓讳换缁勭粐璇勪环骞跺~鍐欐湰璁板綍锛屾妧鏈礋璐d汉瀹℃牳锛涜瘎瀹$粨璁哄闇�閲囧彇鎺柦锛屾寜銆婄籂姝f帾鏂界▼搴忋�嬫墽琛屽苟鐣欏瓨璁板綍銆�" />
       <el-steps :active="currentStep" align-center finish-status="success">
         <el-step title="鐩戞帶璁″垝瀹炴柦鎯呭喌" @click="setStep(0)"></el-step>
         <el-step title="鐩戞帶缁撴灉璇勪环" @click="setStep(1)"></el-step>
diff --git a/src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue b/src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue
index f6346ed..e6288e8 100644
--- a/src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue
+++ b/src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue
@@ -227,9 +227,9 @@
           </el-select>
         </el-form-item>
       </el-form>
-      <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;">
-        <span>绗竴娆″鍏ヨ鍏堜笅杞芥ā鏉匡紝鎸夋ā鏉挎牸寮忓~鍐欏悗鍐嶄笂浼犮��</span>
-        <el-button link type="primary" @click="downloadTemplate">涓嬭浇瀵煎叆妯℃澘</el-button>
+      <div style="display: flex; align-items: center; flex-wrap: nowrap; gap: 4px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;">
+        <span style="white-space: nowrap;">绗竴娆″鍏ヨ鍏堜笅杞芥ā鏉匡紝鎸夋ā鏉挎牸寮忓~鍐欏悗鍐嶄笂浼犮��</span>
+        <el-button link type="primary" style="flex-shrink: 0; margin-left: 0;" @click="downloadTemplate">涓嬭浇瀵煎叆妯℃澘</el-button>
       </div>
       <div style="margin: 0 auto;">
         <el-upload ref="upload" :action="action" :auto-upload="false" v-model:file-list="fileList" :headers="uploadHeader"
@@ -594,7 +594,7 @@
     downloadTemplate() {
       const link = document.createElement('a')
       // 甯︾増鏈弬鏁伴伩鍏嶆祻瑙堝櫒缁х画浣跨敤缂撳瓨閲岀殑鏃фā鏉�
-      link.href = '/import-templates/璐ㄩ噺鐩戞帶璁″垝瀵煎叆妯℃澘.xlsx?v=2'
+      link.href = '/import-templates/璐ㄩ噺鐩戞帶璁″垝瀵煎叆妯℃澘.xlsx?v=3'
       link.download = '璐ㄩ噺鐩戞帶璁″垝瀵煎叆妯℃澘.xlsx'
       document.body.appendChild(link)
       link.click()
@@ -956,7 +956,7 @@
       exportQualityMonitorRatify({ qualityMonitorDetailsId: this.download.qualityMonitorDetailsId }).then(res => {
         this.outLoading = false
         const blob = new Blob([res], { type: 'application/msword' });
-        this.$download.saveAs(blob, '璐ㄩ噺鐩戞帶瀹炴柦璁″垝.docx')
+        this.$download.saveAs(blob, '璐ㄩ噺鎺у埗鎺柦.docx')
       })
     },
     // 瀹屾垚鎶ュ憡瀵煎嚭
@@ -973,7 +973,7 @@
       exportQualityMonitorEvaluate({ qualityMonitorDetailsId: this.download.qualityMonitorDetailsId }).then(res => {
         this.outLoading = false
         const blob = new Blob([res], { type: 'application/msword' });
-        this.$download.saveAs(blob, '璐ㄩ噺鐩戞帶璇勪环.docx')
+        this.$download.saveAs(blob, '璐ㄩ噺鎺у埗璇勪环璁板綍.docx')
       })
     },
     delYearPlanDetail(row) {
diff --git a/src/views/CNAS/process/ensureResults/qualityControlPlan/index.vue b/src/views/CNAS/process/ensureResults/qualityControlPlan/index.vue
index fe29f64..730ad01 100644
--- a/src/views/CNAS/process/ensureResults/qualityControlPlan/index.vue
+++ b/src/views/CNAS/process/ensureResults/qualityControlPlan/index.vue
@@ -131,9 +131,9 @@
           </el-select>
         </el-form-item>
       </el-form>
-      <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;">
-        <span>绗竴娆″鍏ヨ鍏堜笅杞芥ā鏉匡紝鎸夋ā鏉挎牸寮忓~鍐欏悗鍐嶄笂浼犮��</span>
-        <el-button link type="primary" @click="downloadTemplate">涓嬭浇瀵煎叆妯℃澘</el-button>
+      <div style="display: flex; align-items: center; flex-wrap: nowrap; gap: 4px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;">
+        <span style="white-space: nowrap;">绗竴娆″鍏ヨ鍏堜笅杞芥ā鏉匡紝鎸夋ā鏉挎牸寮忓~鍐欏悗鍐嶄笂浼犮��</span>
+        <el-button link type="primary" style="flex-shrink: 0; margin-left: 0;" @click="downloadTemplate">涓嬭浇瀵煎叆妯℃澘</el-button>
       </div>
       <div style="margin: 0 auto;">
         <el-upload ref="upload1" :action="action" :auto-upload="false" :file-list="fileList" :headers="uploadHeader"
diff --git a/src/views/CNAS/systemManagement/documentControl/components/ControlledFileApplication.vue b/src/views/CNAS/systemManagement/documentControl/components/ControlledFileApplication.vue
index dae8fc0..9f02bb5 100644
--- a/src/views/CNAS/systemManagement/documentControl/components/ControlledFileApplication.vue
+++ b/src/views/CNAS/systemManagement/documentControl/components/ControlledFileApplication.vue
@@ -71,7 +71,7 @@
           <div class="search_thing">
             <div class="search_label">涓婁紶闄勪欢锛�</div>
             <div class="search_input"><el-upload style="margin: 8px 0 0px 50px;" action="#" :auto-upload="false"
-                :multiple="false" accept='.pdf' :on-change="handleChangeUpload" v-if="addDialogVisible">
+                :multiple="false" accept='.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.wps,.et,.dps,.txt,.jpg,.jpeg,.png' :on-change="handleChangeUpload" v-if="addDialogVisible">
                 <el-button size="small" type="primary">涓婁紶闄勪欢</el-button>
               </el-upload></div>
           </div>
@@ -437,21 +437,30 @@
     handleCheck0(row) {
       this.checkStampDialogVisible = false
       this.checkDialogVisible = true
-      checkManageDocumentControlledPdf({ id: row.id }).then(res => {
-        const blob = new Blob([res]);
+      checkManageDocumentControlledPdf({ id: row.id }).then(async res => {
+        // 鍚庣杞� PDF 澶辫触鏃朵細杩斿洖 JSON 閿欒锛宐lob 閲屽氨涓嶆槸 PDF
+        if (res.type && res.type.includes('json')) {
+          const data = JSON.parse(await res.text())
+          this.$message.error(data.msg || '闄勪欢杞崲棰勮澶辫触')
+          this.checkDialogVisible = false
+          return
+        }
+        const blob = new Blob([res], { type: 'application/pdf' });
         const file = new File([blob], row.name, { type: 'application/pdf' })
         this.$refs.UpPdfStamp.lookFile(file, this.currentStamp)
       }).catch(err => {
+        this.checkDialogVisible = false
         console.log(err)
       })
     },
     handleDown(row) {
       if (!row.url) return this.$message.warning('鏂囦欢鏈笂浼�')
-      this.$download.saveAs(row.url, row.url);
+      const fileName = row.url.replace(/^[0-9a-fA-F-]{36}_/, '')
+      this.$download.saveAs(this.javaApi + '/word/' + row.url, fileName);
     },
     async uploadPDF(pdfBlob) {
       const formData = new FormData();
-      formData.append('file', pdfBlob, this.fileName + '.pdf'); // 鏂囦欢瀛楁
+      formData.append('file', pdfBlob, (this.currentInfo.name || 'controlled') + '.pdf'); // 鏂囦欢瀛楁
       formData.append('id', this.currentInfo.id); // 鏂囦欢鍚嶅瓧娈�
       formData.append('state', this.type); // 鏂囦欢鍚嶅瓧娈�
       formData.append('writer', this.currentInfo.writer); // 鏂囦欢鍚嶅瓧娈�
diff --git a/src/views/CNAS/systemManagement/documentControl/components/DistributionCollectionRecord.vue b/src/views/CNAS/systemManagement/documentControl/components/DistributionCollectionRecord.vue
index cbff184..9e0ec33 100644
--- a/src/views/CNAS/systemManagement/documentControl/components/DistributionCollectionRecord.vue
+++ b/src/views/CNAS/systemManagement/documentControl/components/DistributionCollectionRecord.vue
@@ -154,7 +154,7 @@
           <div class="search_thing">
             <div class="search_label">涓婁紶闄勪欢锛�</div>
             <div class="search_input"><el-upload :auto-upload="false" :multiple="false" :on-change="handleChangeUpload"
-                accept='.pdf,.doc,.docx,.xls,.xlsx' action="#" style="margin: 8px 0 0px 50px;">
+                accept='.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.wps,.et,.dps,.txt,.jpg,.jpeg,.png' action="#" style="margin: 8px 0 0px 50px;">
                 <el-button size="small" type="primary">涓婁紶闄勪欢</el-button>
               </el-upload></div>
           </div>
diff --git a/src/views/CNAS/systemManagement/documentControl/components/FileChangeRequest.vue b/src/views/CNAS/systemManagement/documentControl/components/FileChangeRequest.vue
index fcf241d..d65c19d 100644
--- a/src/views/CNAS/systemManagement/documentControl/components/FileChangeRequest.vue
+++ b/src/views/CNAS/systemManagement/documentControl/components/FileChangeRequest.vue
@@ -184,7 +184,7 @@
             <div class="search_thing" style="width: 100%;">
               <div class="search_label">鐩稿叧闄勪欢锛�</div>
               <div class="search_input"><el-upload v-if="addDialogVisible" :auto-upload="false" :multiple="false"
-                                                   :on-change="handleChangeUpload" accept='.pdf' action="#" style="margin: 8px 0 0px 50px;">
+                                                   :on-change="handleChangeUpload" accept='.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.wps,.et,.dps,.txt,.jpg,.jpeg,.png' action="#" style="margin: 8px 0 0px 50px;">
                 <el-button size="small" type="primary">涓婁紶闄勪欢</el-button>
               </el-upload></div>
             </div>
@@ -536,15 +536,23 @@
       this.getCurrentFile(alterBeforeCode)
       this.addDialogVisible = true
       this.diaLoading = true
-      checkManageDocumentAlterPdf({ id: row.id }).then(res => {
+      checkManageDocumentAlterPdf({ id: row.id }).then(async res => {
         //
         this.diaLoading = false
-        const blob = new Blob([res]);
+        // 鍚庣杞� PDF 澶辫触鏃朵細杩斿洖 JSON 閿欒锛宐lob 閲屽氨涓嶆槸 PDF
+        if (res.type && res.type.includes('json')) {
+          const data = JSON.parse(await res.text())
+          this.$message.error(data.msg || '闄勪欢杞崲棰勮澶辫触')
+          this.addDialogVisible = false
+          return
+        }
+        const blob = new Blob([res], { type: 'application/pdf' });
         const file = new File([blob], row.name, { type: 'application/pdf' })
         this.$refs.UpPdfStamp.lookFile(file)
         this.currentInfo = row
       }).catch(err => {
         this.diaLoading = false
+        this.addDialogVisible = false
         console.log(err)
       })
     },
diff --git a/src/views/CNAS/systemManagement/documentControl/components/FileList.vue b/src/views/CNAS/systemManagement/documentControl/components/FileList.vue
index 256bc60..96cea97 100644
--- a/src/views/CNAS/systemManagement/documentControl/components/FileList.vue
+++ b/src/views/CNAS/systemManagement/documentControl/components/FileList.vue
@@ -29,6 +29,9 @@
         :height="'calc(100vh - 290px)'" @pagination="pagination"></lims-table>
     </div>
     <el-dialog title="涓婁紶" v-model="addDialogVisible" width="1000px" top="3vh">
+      <div style="margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px; color: var(--el-text-color-regular);">
+        鏀寔 PDF銆乄ord锛�.doc/.docx锛夈�丒xcel锛�.xls/.xlsx锛夈�丳PT銆佸浘鐗囩瓑甯歌鏍煎紡锛屽崟涓枃浠朵笉瓒呰繃 20MB銆侾DF 鍙瑙堝苟鐩栫珷锛涘叾浠栨牸寮忔寜鍘熸枃浠朵笂浼狅紝涓嶇洊绔犮��
+      </div>
       <UpPdfStamp ref="UpPdfStamp" v-if="addDialogVisible" @uploadPDF="uploadPDF" :isUpFile="true"
         @uploadPDFErr="uploadPDFErr"></UpPdfStamp>
       <template #footer><span  class="dialog-footer">
@@ -277,7 +280,9 @@
     // 涓嬭浇鏂囦欢
     handleDown(row) {
       if (!row.url) return this.$message.warning('鏂囦欢鏈笂浼�')
-      this.$download.saveAs(row.url, row.url);
+      // 鍚庣淇濆瓨鐨勬槸 word 鐩綍涓嬬殑 uuid_鍘熸枃浠跺悕锛屼笅杞芥椂璧伴潤鎬佽祫婧愬湴鍧�骞跺幓鎺� uuid 鍓嶇紑
+      const fileName = row.url.replace(/^[0-9a-fA-F-]{36}_/, '')
+      this.$download.saveAs(this.javaApi + '/word/' + row.url, fileName);
     },
     // 鏌ョ湅鏂囦欢
     handleLook(row) {

--
Gitblit v1.9.3