From 137bf2c0666b60cfc0d98b1a9ca7ead860ee4e7c Mon Sep 17 00:00:00 2001
From: 吴羡 <16976746+Xian-TT3@user.noreply.gitee.com>
Date: 星期三, 28 十月 2026 17:57:29 +0800
Subject: [PATCH] feat(device,supply,satisfaction): 设备计划改造、耗材采购调整与满意度照片上传

---
 src/views/CNAS/resourceDemand/device/component/equipmentVerificationPlan.vue |  486 +++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 352 insertions(+), 134 deletions(-)

diff --git a/src/views/CNAS/resourceDemand/device/component/equipmentVerificationPlan.vue b/src/views/CNAS/resourceDemand/device/component/equipmentVerificationPlan.vue
index 26aceb1..119dd16 100644
--- a/src/views/CNAS/resourceDemand/device/component/equipmentVerificationPlan.vue
+++ b/src/views/CNAS/resourceDemand/device/component/equipmentVerificationPlan.vue
@@ -4,14 +4,14 @@
     <div style="margin: 10px 0 14px;">
       <div class="plan-top">
         <div class="plan-top-left">
-          <span class="title" style="margin-bottom: 0;">{{ planInfo.planName || (planYear + '骞村害璁惧鏍告煡璁″垝') }}</span>
+          <span class="title" style="margin-bottom: 0;">{{ planInfo.planName || (planYear ? planYear + '骞村害璁惧鏍告煡璁″垝' : '璁惧鏍告煡璁″垝') }}</span>
           <el-select v-model="planYear" size="small" style="width: 110px; margin-left: 16px;" @change="handleYearChange">
             <el-option v-for="y in yearOptions" :key="y" :label="y" :value="y"></el-option>
           </el-select>
         </div>
         <div>
           <el-button size="small" type="primary" @click="handleAddRecord">鏂板鏍告煡璁板綍</el-button>
-          <el-button size="small" type="primary" @click="loadPlan">鍒锋柊</el-button>
+          <el-button size="small" type="primary" @click="loadYearOptions">鍒锋柊</el-button>
           <el-button size="small" type="primary" @click="downLoadPost">瀵煎嚭</el-button>
         </div>
       </div>
@@ -51,13 +51,17 @@
           width="70"></el-table-column>
         <el-table-column label="璁惧缂栧彿" min-width="170" prop="deviceNumber"></el-table-column>
         <el-table-column label="璁惧鍚嶇О" min-width="180" prop="deviceName" show-overflow-tooltip></el-table-column>
-        <el-table-column label="鏍告煡鏃堕棿" min-width="120" prop="checkTime"
-          :formatter="formatCheckMonth"></el-table-column>
+        <el-table-column label="璁惧鍨嬪彿" min-width="120" prop="deviceModel" show-overflow-tooltip></el-table-column>
+        <el-table-column label="鏍告煡鏃堕棿" min-width="120">
+          <template #default="scope">
+            {{ scope.row.completeDate || formatCheckMonth(null, null, scope.row.checkTime) }}
+          </template>
+        </el-table-column>
         <el-table-column label="鏍告煡鎸囨爣" min-width="140" prop="checkIndex"> </el-table-column>
         <el-table-column label="鏍告煡鏂规硶" min-width="140" prop="checkMethod" show-overflow-tooltip></el-table-column>
         <el-table-column label="缁撴灉濡備綍鍒ゅ畾" min-width="140" prop="howResults" show-overflow-tooltip></el-table-column>
         <el-table-column label="鏍告煡璐熻矗浜�" min-width="120" prop="checkChargerUser"></el-table-column>
-        <el-table-column label="璁板綍鐘舵��" min-width="100" prop="recordStatus">
+        <el-table-column label="瀹℃牳鐘舵��" min-width="100" prop="recordStatus">
           <template #default="scope">
             <el-tag type="" v-if="scope.row.recordStatus === 0" disable-transitions>鏈紑濮�</el-tag>
             <el-tag type="warning" v-if="scope.row.recordStatus === 1" disable-transitions>寰呮壒鍑�</el-tag>
@@ -65,18 +69,13 @@
             <el-tag type="danger" v-if="scope.row.recordStatus === 3" disable-transitions>涓嶉�氳繃</el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="瀵规瘮鐘舵��" min-width="100" prop="recordContrastStatus">
-          <template #default="scope">
-            <el-tag type="" v-if="scope.row.recordContrastStatus === 0" disable-transitions>鏈紑濮�</el-tag>
-            <el-tag type="warning" v-if="scope.row.recordContrastStatus === 1" disable-transitions>寰呮壒鍑�</el-tag>
-            <el-tag type="success" v-if="scope.row.recordContrastStatus === 2" disable-transitions>閫氳繃</el-tag>
-            <el-tag type="danger" v-if="scope.row.recordContrastStatus === 3" disable-transitions>涓嶉�氳繃</el-tag>
-          </template>
-        </el-table-column>
         <el-table-column label="澶囨敞" min-width="130" prop="remark"></el-table-column>
         <el-table-column label="闄勪欢" min-width="140">
           <template #default="scope">
-            <el-link v-if="scope.row.fileUrl" type="primary" @click="openAttachment(scope.row)">
+            <el-link v-if="scope.row.attachmentCount > 0" type="primary" @click="handleAttachment(scope.row)">
+              鏌ョ湅闄勪欢锛坽{ scope.row.attachmentCount }}锛�
+            </el-link>
+            <el-link v-else-if="scope.row.fileUrl" type="primary" @click="openAttachment(scope.row)">
               {{ scope.row.fileName || '鏌ョ湅闄勪欢' }}
             </el-link>
             <span v-else>鈥�</span>
@@ -86,18 +85,15 @@
         <el-table-column align="center" fixed="right" label="鎿嶄綔" min-width="320">
           <template #default="scope">
             <el-button size="small" link type="primary"
+              :disabled="scope.row.recordStatus === 2 || scope.row.recordStatus === 3"
               @click="handleForm('edit', scope.row)">缂栬緫</el-button>
             <el-button size="small" link type="primary" @click="handleForm('view', scope.row)">鏌ョ湅</el-button>
-            <el-button v-if="scope.row.recordStatus === 1" size="small" link type="primary"
+            <el-button v-if="scope.row.recordStatus === 0 || scope.row.recordStatus === 1"
+              size="small" link type="primary" :disabled="!scope.row.checkChargerUser"
               @click="handleRecord(scope.row)">瀹℃牳</el-button>
-            <el-upload class="row-upload" :action="javaApi + '/deviceScope/uploadFile'"
-              :headers="uploadHeader" :show-file-list="false" :before-upload="beforeAttachmentUpload"
-              :on-success="(res) => handleAttachmentSuccess(res, scope.row)"
-              :on-error="handleAttachmentError">
-              <el-button size="small" link type="primary">涓婁紶闄勪欢</el-button>
-            </el-upload>
-            <el-button size="small" link type="primary" @click="handleDown(scope.row)">瀵煎嚭</el-button>
-            <el-button v-if="scope.row.recordStatus === 0 && scope.row.recordContrastStatus === 0"
+            <el-button v-if="scope.row.recordStatus === 2 || scope.row.recordStatus === 3"
+              size="small" link type="primary" @click="handleDown(scope.row)">瀵煎嚭</el-button>
+            <el-button v-if="scope.row.recordStatus !== 2"
               size="small" link type="danger" @click="deleteFun1(scope.row.planDetailsId)">鍒犻櫎</el-button>
           </template>
         </el-table-column>
@@ -108,71 +104,171 @@
         @size-change="handleSizeChange1" @current-change="handleCurrentChange1">
       </el-pagination>
     </div>
-    <el-dialog :close-on-click-modal="false" :close-on-press-escape="false" v-model="dialogVisible" title="璁惧鏍告煡璁″垝"
-      width="50%" @close="resetForm">
-      <el-form ref="modelForm" :model="form" :rules="rules" label-width="110px">
-        <el-form-item label="璁惧缂栧彿锛�" prop="deviceNumber">
-          <el-input v-model="form.deviceNumber" :disabled="operationType === 'view'" clearable
-            size="small"></el-input>
-        </el-form-item>
-        <el-form-item label="璁惧鍚嶇О锛�" prop="deviceName">
-          <el-input v-model="form.deviceName" :disabled="operationType === 'view'" clearable
-            size="small"></el-input>
-        </el-form-item>
-        <el-form-item label="鏍告煡鏃堕棿锛�" prop="checkTime">
-          <el-date-picker v-model="form.checkTime" :disabled="operationType === 'view'" clearable format="YYYY-MM"
-            placeholder="閫夋嫨鏃ユ湡" size="small" style="width:100%" type="month" value-format="YYYY-MM">
-          </el-date-picker>
-        </el-form-item>
-        <el-form-item label="鏍告煡鎸囨爣锛�" prop="checkIndex">
-          <el-input v-model="form.checkIndex" :disabled="operationType === 'view'" clearable
-            size="small"></el-input>
-        </el-form-item>
-        <el-form-item label="鏍告煡鏂规硶锛�" prop="checkMethod">
-          <el-input v-model="form.checkMethod" :disabled="operationType === 'view'" clearable size="small"
-            type="textarea"></el-input>
-        </el-form-item>
-        <el-form-item label="缁撴灉濡備綍鍒ゅ畾锛�" prop="howResults">
-          <el-input v-model="form.howResults" :disabled="operationType === 'view'" clearable size="small"
-            type="textarea"></el-input>
-        </el-form-item>
-        <el-form-item label="鏍告煡璐d换浜猴細" prop="checkChargerUserId">
-          <el-select v-model="form.checkChargerUserId" @change="setCheckChargerUser"
-            :disabled="operationType === 'view'" placeholder="璇烽�夋嫨" size="small" style="width: 100%">
-            <el-option v-for="item in chargerOptions" :key="item.id" :label="item.name"
-              :value="item.id"></el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item label="澶囨敞锛�" prop="remark">
-          <el-input v-model="form.remark" :disabled="operationType === 'view'" clearable size="small"></el-input>
-        </el-form-item>
+    <el-dialog :close-on-click-modal="false" :close-on-press-escape="false" v-model="dialogVisible"
+      :title="operationType === 'view' ? '璁惧鏍告煡璁板綍' : '缂栬緫鏍告煡璁板綍'" top="5vh" width="960px" @close="resetForm">
+      <el-form ref="modelForm" :model="form" :rules="rules" label-width="104px">
+        <div class="form-section">
+          <div class="form-section-title">鍩烘湰淇℃伅</div>
+          <el-row :gutter="12">
+            <el-col :span="12">
+              <el-form-item label="璁惧鍚嶇О锛�" prop="deviceName">
+                <el-input v-model="form.deviceName" disabled size="small"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="妫�鏌ユ棩鏈燂細" prop="checkTime">
+                <el-input v-if="operationType === 'view'" :model-value="formatCheckMonth(null, null, form.checkTime)"
+                  disabled size="small"></el-input>
+                <template v-else>
+                  <el-date-picker v-model="checkTimeEditValue" clearable format="YYYY-MM-DD" placeholder="閫夋嫨妫�鏌ユ棩鏈�"
+                    size="small" style="width:100%" type="date" value-format="YYYY-MM-DD">
+                  </el-date-picker>
+                  <div v-if="isMonthOnlyCheckTime" class="check-time-tip">绯荤粺鐧昏鍙埌鏈堜唤锛岄�夋嫨鍏蜂綋鏃ユ湡鍚庡皢鎸夋棩淇濆瓨</div>
+                </template>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="绠$悊缂栧彿锛�" prop="deviceNumber">
+                <el-input v-model="form.deviceNumber" disabled size="small"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="璁惧鍨嬪彿锛�" prop="deviceModel">
+                <el-input v-model="form.deviceModel" disabled size="small"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="鏍告煡浜猴細" prop="checkChargerUserId">
+                <el-select v-model="form.checkChargerUserId" @change="setCheckChargerUser" clearable filterable
+                  :disabled="operationType === 'view'" placeholder="璇烽�夋嫨" size="small" style="width: 100%">
+                  <el-option v-for="item in chargerOptions" :key="item.id" :label="item.name"
+                    :value="item.id"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+        <div class="form-section">
+          <div class="form-section-title">鏍告煡鍐呭</div>
+          <el-row :gutter="12">
+            <el-col :span="8">
+              <el-form-item label="鐜娓╂箍搴︼細" prop="environment">
+                <el-input v-model="form.environment" :disabled="operationType === 'view'" clearable size="small"
+                  placeholder="濡傦細25鈩� / 50%RH"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="鏍告煡渚濇嵁锛�" prop="checkBasis">
+                <el-input v-model="form.checkBasis" :disabled="operationType === 'view'" clearable
+                  size="small"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="鏍告煡鎸囨爣锛�" prop="checkIndex">
+                <el-input v-model="form.checkIndex" :disabled="operationType === 'view'" clearable
+                  size="small"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="鏍告煡鏂瑰紡锛�" prop="checkMethod">
+                <el-input v-model="form.checkMethod" :disabled="operationType === 'view'" clearable :rows="2"
+                  size="small" type="textarea"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="缁撴灉濡備綍鍒ゅ畾锛�" prop="howResults">
+                <el-input v-model="form.howResults" :disabled="operationType === 'view'" clearable :rows="2"
+                  size="small" type="textarea"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="澶栬鎯呭喌锛�">
+                <el-radio-group v-model="form.appearanceStatus" :disabled="operationType === 'view'">
+                  <el-radio value="鑹ソ">鑹ソ</el-radio>
+                  <el-radio value="涓�鑸�">涓�鑸�</el-radio>
+                  <el-radio value="鎹熷潖">鎹熷潖</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="杩愯鐘舵�侊細">
+                <el-radio-group v-model="form.runningStatus" :disabled="operationType === 'view'">
+                  <el-radio value="姝e父">姝e父</el-radio>
+                  <el-radio value="寮傚父">寮傚父</el-radio>
+                  <el-radio value="鍋滄満">鍋滄満</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-col>
+            <el-col :span="24">
+              <el-form-item label="澶囨敞锛�" prop="remark">
+                <el-input v-model="form.remark" :disabled="operationType === 'view'" clearable size="small"></el-input>
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+        <div class="form-section">
+          <div class="form-section-title">闄勪欢璧勬枡</div>
+          <el-form-item label-width="0">
+            <lims-attachment inline upload-url="/deviceExaminePlan/uploadAttachment"
+              select-url="/deviceExaminePlan/selectAttachments" delete-url="/deviceExaminePlan/deleteAttachment"
+              id-param="planDetailsId" name-field="deviceName" title="鏍告煡璁板綍闄勪欢"
+              :main-id="form.planDetailsId" :readonly="operationType === 'view'"></lims-attachment>
+            <el-link v-if="form.fileUrl" type="primary" @click="openAttachment(form)">
+              {{ form.fileName || '鏌ョ湅鍘熼檮浠�' }}
+            </el-link>
+          </el-form-item>
+        </div>
       </el-form>
       <template #footer><span class="dialog-footer">
-        <el-button @click="resetForm">鍙� 娑�</el-button>
+        <el-button @click="handleCancel">鍙� 娑�</el-button>
         <el-button :loading="submitFormLoading" type="primary" @click="submitForm"
           v-if="operationType !== 'view'">纭� 璁�</el-button>
       </span></template>
     </el-dialog>
-    <el-dialog v-model="downloadDialog" title="瀵煎嚭" width="600px">
-      <span>
-        <el-button :loading="outLoading" type="primary" :disabled="!downRow.recordStatus"
-          @click="controlDown">鏍告煡璁板綍</el-button>
-        <el-button :loading="outLoading" type="primary" :disabled="!downRow.recordContrastStatus"
-          @click="processingDown">鏍告煡姣斿璁板綍</el-button>
-      </span>
+    <el-dialog v-model="downloadDialog" title="瀵煎嚭" width="480px">
+      <div class="export-panel">
+        <div class="export-panel-head">
+          <span class="export-panel-name">浠櫒璁惧鏈熼棿鏍告煡璁板綍琛�</span>
+          <el-tag v-if="downRow.recordStatus === 2" type="success" disable-transitions>閫氳繃</el-tag>
+          <el-tag v-else-if="downRow.recordStatus === 1" type="warning" disable-transitions>寰呮壒鍑�</el-tag>
+          <el-tag v-else-if="downRow.recordStatus === 3" type="danger" disable-transitions>涓嶉�氳繃</el-tag>
+          <el-tag v-else type="info" disable-transitions>鏈紑濮�</el-tag>
+        </div>
+        <div class="export-panel-meta">
+          <div class="export-panel-item">
+            <span class="export-panel-label">璁惧缂栧彿</span>{{ downRow.deviceNumber || '鈥�' }}
+          </div>
+          <div class="export-panel-item">
+            <span class="export-panel-label">璁惧鍚嶇О</span>{{ downRow.deviceName || '鈥�' }}
+          </div>
+          <div class="export-panel-item">
+            <span class="export-panel-label">鏍告煡鏃堕棿</span>{{ downRow.completeDate || formatCheckMonth(null, null, downRow.checkTime) || '鈥�' }}
+          </div>
+        </div>
+        <div v-if="!canExport" class="export-panel-tip">
+          {{ downRow.recordStatus === 1 ? '璇ヨ鍒掕灏氭湭瀹℃牳瀹屾垚锛屽鏍搁�氳繃鎴栦笉閫氳繃鍚庢墠鑳藉鍑恒��' : '璇ヨ鍒掕杩樻病鏈夋牳鏌ヨ褰曪紝褰曞叆骞舵彁浜ゅ鏍稿悗鎵嶈兘瀵煎嚭銆�' }}
+        </div>
+      </div>
       <template #footer><span class="dialog-footer">
         <el-button @click="downloadDialog = false">鍙� 娑�</el-button>
+        <el-button :loading="outLoading" type="primary" :disabled="!canExport"
+          @click="controlDown">瀵煎嚭璁板綍琛�</el-button>
       </span></template>
     </el-dialog>
     <check-record v-if="checkRecord" ref="checkRecord" @closeDialog1="closeDialog1"></check-record>
+    <lims-attachment ref="rowAttachment" upload-url="/deviceExaminePlan/uploadAttachment"
+      select-url="/deviceExaminePlan/selectAttachments" delete-url="/deviceExaminePlan/deleteAttachment"
+      id-param="planDetailsId" name-field="deviceName" title="鏍告煡璁板綍闄勪欢" :readonly="attachmentReadonly"
+      @closed="getYearTableDetailData"></lims-attachment>
   </div>
 </template>
 
 <script>
 import CheckRecord from "./checkRecord.vue";
+import LimsAttachment from '@/components/Attachment/lims-attachment.vue'
 import {
-  pageDeviceExaminePlan,
   getExaminePlanByYear,
+  getExaminePlanYears,
   exportDeviceExaminePlanDetail,
   delDeviceExaminePlanDetail,
   pageDeviceExaminePlanDetail,
@@ -186,7 +282,7 @@
 export default {
   name: "equipment-verification-plan",
   // import 寮曞叆鐨勭粍浠堕渶瑕佹敞鍏ュ埌瀵硅薄涓墠鑳戒娇鐢�
-  components: { CheckRecord },
+  components: { CheckRecord, LimsAttachment },
   mixins: [draftSave],
   props: {
     clickNodeVal: {
@@ -216,24 +312,31 @@
       form: {
         deviceNumber: '',
         deviceName: '',
+        deviceModel: '',
         checkTime: '',
         checkIndex: '',
         checkMethod: '',
         howResults: '',
         checkChargerUserId: '',
         checkChargerUser: '',
+        environment: '',
+        checkBasis: '',
+        appearanceStatus: '',
+        runningStatus: '',
         remark: '',
+        fileName: '',
+        fileUrl: '',
         planId: '',
         planDetailsId: '',
       },
       operationType: '',
       rules: {
-        deviceNumber: [{ required: true, message: '璇峰~鍐欒澶囩紪鍙�', trigger: 'blur' }],
-        checkMethod: [{ required: true, message: '璇峰~鍐欐牳鏌ユ柟娉�', trigger: 'blur' }],
+        checkMethod: [{ required: true, message: '璇峰~鍐欐牳鏌ユ柟寮�', trigger: 'blur' }],
       },
       yearTableDetailDataLoading: false,
       submitFormLoading: false,
       userList: [],
+      attachmentReadonly: false,
       checkRecord: false,
       downloadDialog: false,
       outLoading: false,
@@ -245,23 +348,24 @@
   },
   // 鏂规硶闆嗗悎
   methods: {
-    // 骞翠唤涓嬫媺閫夐」锛氬凡鏈夎鍒掑勾浠� + 褰撳墠骞翠唤
+    // 骞翠唤涓嬫媺鍙繚鐣欐湁璁″垝鏄庣粏鐨勫勾浠斤紝绌哄勾浠借鍒掕嚜鍔ㄥ垹闄わ紝榛樿鏄剧ず鏈勾搴︼紙鏈勾搴︽棤璁″垝鏃堕��鍥炴渶鏂板勾浠斤級
     loadYearOptions() {
-      pageDeviceExaminePlan({
-        current: 1,
-        size: 99,
-      }).then(res => {
-        let years = []
-        if (res.code == 200) {
-          years = (res.data.records || []).map(r => r.planYear).filter(Boolean)
+      const currentYear = String(new Date().getFullYear())
+      const finish = (years) => {
+        let options = [...new Set((years || []).map(y => String(y)).filter(Boolean))]
+        options.sort((a, b) => b.localeCompare(a))
+        if (options.length === 0) {
+          options = [currentYear]
         }
-        const currentYear = String(new Date().getFullYear())
-        if (years.indexOf(currentYear) === -1) {
-          years.push(currentYear)
-        }
-        this.yearOptions = [...new Set(years)].sort((a, b) => b.localeCompare(a))
-        this.planYear = currentYear
+        this.yearOptions = options
+        const keep = options.indexOf(String(this.planYear)) > -1
+        this.planYear = keep ? String(this.planYear) : (options.includes(currentYear) ? currentYear : options[0])
         this.loadPlan()
+      }
+      getExaminePlanYears().then(res => {
+        finish(res.code == 200 ? (res.data || []) : [])
+      }).catch(() => {
+        finish([])
       })
     },
     // 鍒囨崲骞翠唤
@@ -271,6 +375,12 @@
     },
     // 鏌ヨ骞翠唤璁″垝锛堜笉瀛樺湪鑷姩鍒涘缓骞惰ˉ鍏ㄥ埌鐐规槑缁嗭級锛岄殢鍚庡埛鏂版槑缁�
     loadPlan() {
+      if (!this.planYear) {
+        this.planInfo = {}
+        this.yearTableDetailData = []
+        this.pagination1.total = 0
+        return
+      }
       getExaminePlanByYear({ planYear: this.planYear }).then(res => {
         if (res.code == 200) {
           this.planInfo = res.data || {}
@@ -311,19 +421,27 @@
       })
     },
     // 鍒扮偣闇�鏍告煡鐨勮鏍囩孩
-    // 鏍告煡鏃堕棿鎸夋湀浠芥樉绀猴紙鍏煎鍘嗗彶鏁版嵁涓殑瀹屾暣鏃ユ湡锛�
+    // 鎵嬪姩褰曞叆鐨勬牳鏌ユ棩鏈熸樉绀哄埌鏃ワ紝绯荤粺鐧昏鐨勬湀浠芥暟鎹粛鎸夊勾鏈堟樉绀�
     formatCheckMonth(row, column, cellValue) {
       if (cellValue === null || cellValue === undefined || cellValue === '') {
         return cellValue
       }
       const str = String(cellValue).trim().replace(/[./]/g, '-')
+      const dateMatched = str.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/)
+      if (dateMatched) {
+        return `${dateMatched[1]}-${dateMatched[2].padStart(2, '0')}-${dateMatched[3].padStart(2, '0')}`
+      }
       const matched = str.match(/^(\d{4})-(\d{1,2})/)
       if (matched) {
         return `${matched[1]}-${matched[2].padStart(2, '0')}`
       }
       const formatted = formatListDate(str)
-      const fallback = String(formatted).match(/^(\d{4})-(\d{1,2})/)
-      return fallback ? `${fallback[1]}-${fallback[2].padStart(2, '0')}` : cellValue
+      const fallback = String(formatted).match(/^(\d{4})-(\d{1,2})-(\d{1,2})/)
+      if (fallback) {
+        return `${fallback[1]}-${fallback[2].padStart(2, '0')}-${fallback[3].padStart(2, '0')}`
+      }
+      const monthFallback = String(formatted).match(/^(\d{4})-(\d{1,2})/)
+      return monthFallback ? `${monthFallback[1]}-${monthFallback[2].padStart(2, '0')}` : cellValue
     },
     tableRowClassName({ row }) {
       return row.redFlag === 1 ? 'red-row' : ''
@@ -355,7 +473,10 @@
       this.$nextTick(() => {
         this.form = this.HaveJson(row)
         if (this.operationType !== 'view') {
-          this.draftRestore('equipmentVerificationPlan', 'form', this.form.planDetailsId, 'dialogVisible')
+          const rowDeviceName = this.form.deviceName
+          this.draftRestore('equipmentVerificationPlan', 'form', this.form.planDetailsId, 'dialogVisible',
+            // 璁惧鍚嶇О鏄鐢ㄥ瓧娈碉紝鑽夌閲屼负绌鸿鏄庢槸琚〃鍗曢噸缃竻绌虹殑搴熻崏绋匡紝鐩存帴涓㈠純
+            draft => !!draft.deviceName || !rowDeviceName)
         }
       })
     },
@@ -380,14 +501,14 @@
     },
     // 鏄庣粏-鍒犻櫎
     deleteFun1(id) {
-      this.$confirm('姝ゆ搷浣滃皢姘镐箙鍒犻櫎璇ユ潯鏄庣粏, 鏄惁缁х画?', '鎻愮ず', {
+      this.$confirm('鍒犻櫎鍚庯紝璇ヨ鐨勬牳鏌ヨ褰曪紙鏈鏍告垨瀹℃牳涓嶉�氳繃锛夊強闄勪欢浼氫竴骞跺垹闄わ紱鑻ヨ璁惧杩樻湁瀹℃牳閫氳繃鐨勬牳鏌ヨ褰曪紝妗f鐨勬渶杩戞牳鏌ユ棩鏈熷皢鍥為��鍒版渶杩戜竴娆″畬鎴愯褰曪紱鑻ユ病鏈夛紝鍒欐竻绌烘。妗堢殑鏍告煡鏃ユ湡鍜屾牳鏌ュ懆鏈熴�傛槸鍚︾户缁�?', '鎻愮ず', {
         confirmButtonText: '纭畾',
         cancelButtonText: '鍙栨秷',
         type: 'warning'
       }).then(() => {
         delDeviceExaminePlanDetail({ planDetailsId: id }).then(res => {
           this.$message.success('鍒犻櫎鎴愬姛锛�');
-          this.getYearTableDetailData()
+          this.loadYearOptions()
         });
       }).catch(() => {
         this.$message({
@@ -396,13 +517,39 @@
         });
       });
     },
-    // 瀵煎嚭骞村害璁″垝琛�
+    // 瀵煎嚭骞村害璁″垝琛紙Excel锛夛細鏈夌瓫閫夋潯浠跺鍑虹瓫閫夌粨鏋滐紝娌℃湁绛涢�夊鍑鸿骞翠唤鍏ㄩ儴
     downLoadPost() {
-      exportDeviceExaminePlanDetail({ planId: this.planInfo.planId }).then(res => {
-        this.saveDocx(res, '浠櫒璁惧鏈熼棿鏍告煡璁″垝琛�.docx')
+      exportDeviceExaminePlanDetail({
+        planId: this.planInfo.planId,
+        deviceName: this.searchForm.deviceName,
+        deviceNumber: this.searchForm.deviceNumber,
+      }).then(res => {
+        const planName = this.planInfo.planName || (this.planYear ? this.planYear + '骞村害璁惧鏍告煡璁″垝' : '璁惧鏍告煡璁″垝')
+        this.saveDocx(res, planName + '.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
+      }).catch(() => { })
+    },
+    // 鏈鏄惁鏈夋敼鍔紙鍚墦寮�鏃舵仮澶嶇殑鑽夌锛�
+    hasUnsavedEdit() {
+      const state = this.draftStates && this.draftStates['equipmentVerificationPlan']
+      return !!state && this.draftSnapshot(state) !== state.baseline
+    },
+    // 鍙栨秷锛氭棤缂栬緫鐩存帴閫�鍑猴紱鏈夌紪杈戝厛纭锛岀‘璁ゅ悗涓㈠純鑽夌閫�鍑�
+    handleCancel() {
+      if (this.operationType === 'view' || !this.hasUnsavedEdit()) {
+        this.dialogVisible = false
+        return
+      }
+      this.$confirm('鏄惁纭鍙栨秷缂栬緫锛熸湰娆$紪杈戣褰曚笉淇濆瓨锛�', '鎻愮ず', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '缁х画缂栬緫',
+        type: 'warning'
+      }).then(() => {
+        this.draftClear('equipmentVerificationPlan')
+        this.dialogVisible = false
       }).catch(() => { })
     },
     resetForm() {
+      // 鍙充笂瑙掑叧闂椂 close 浜嬩欢鍏堜簬鏄鹃殣鐩戝惉瑙﹀彂锛屽厛鎶婅崏绋垮瓨涓嬪啀閲嶇疆琛ㄥ崟锛岄伩鍏嶉噸缃悗鐨勭┖鍐呭琚瓨鎴愯崏绋�
       this.draftStop('equipmentVerificationPlan')
       this.$refs.modelForm.resetFields();
       this.dialogVisible = false
@@ -423,42 +570,17 @@
     },
     // 鎵撳紑闄勪欢
     openAttachment(row) {
-      const isImage = /\.(jpg|jpeg|png|gif)$/i.test(row.fileUrl)
-      window.open(this.javaApi + (isImage ? '/img/' : '/word/') + row.fileUrl, '_blank')
+      return this.$download.saveAs(row.fileUrl, row.fileName)
     },
-    beforeAttachmentUpload(file) {
-      if (file.size > 1024 * 1024 * 10) {
-        this.$message.error('涓婁紶鏂囦欢涓嶈秴杩�10M')
-        return false
-      }
-      return true
-    },
-    // 闄勪欢涓婁紶鎴愬姛鍚庝繚瀛樺埌璁″垝鏄庣粏
-    handleAttachmentSuccess(response, row) {
-      if (response.code == 200) {
-        updateDeviceExaminePlanDetail({
-          planDetailsId: row.planDetailsId,
-          fileName: response.data.name,
-          fileUrl: response.data.url
-        }).then(res => {
-          if (res.code == 200) {
-            this.$message.success('闄勪欢涓婁紶鎴愬姛')
-            this.getYearTableDetailData()
-          } else {
-            this.$message.error(res.msg || '闄勪欢淇濆瓨澶辫触')
-          }
-        })
-      } else {
-        this.$message.error(response.msg || '闄勪欢涓婁紶澶辫触')
-      }
-    },
-    handleAttachmentError() {
-      this.$message.error('闄勪欢涓婁紶澶辫触')
+    // 鍒楄〃闄勪欢鍒楀彧璇绘煡鐪嬶紙涓婁紶/鍒犻櫎鍏ュ彛鍙湪缂栬緫銆佹柊澧炲脊绐楀唴锛�
+    handleAttachment(row) {
+      this.attachmentReadonly = true
+      this.$refs.rowAttachment.open(row)
     },
     // 鍏抽棴璁板綍寮规
     closeDialog1() {
       this.checkRecord = false
-      this.getYearTableDetailData()
+      this.loadYearOptions()
     },
     // 鎵撳紑瀵煎嚭寮规
     handleDown(row) {
@@ -466,7 +588,7 @@
       this.downloadDialog = true
     },
     // 瀵煎嚭缁撴灉鎷︽埅: 鍚庣杩斿洖閿欒 JSON 鏃舵彁绀洪敊璇�, 涓嶅瓨鎴愬潖鏂囦欢
-    saveDocx(res, filename) {
+    saveDocx(res, filename, mime = 'application/msword') {
       const reader = new FileReader();
       reader.readAsText(res, 'utf-8');
       reader.onload = () => {
@@ -476,11 +598,11 @@
           try { msg = JSON.parse(text).msg || msg; } catch (e) { }
           this.$message.error(msg);
         } else {
-          this.$download.saveAs(new Blob([res], { type: 'application/msword' }), filename);
+          this.$download.saveAs(new Blob([res], { type: mime }), filename);
         }
       };
       reader.onerror = () => {
-        this.$download.saveAs(new Blob([res], { type: 'application/msword' }), filename);
+        this.$download.saveAs(new Blob([res], { type: mime }), filename);
       };
     },
     // 鏍告煡璁板綍瀵煎嚭
@@ -520,6 +642,10 @@
     isRatified() {
       return this.planInfo.ratifyStatus === 0 || this.planInfo.ratifyStatus === 1;
     },
+    // 璁板綍鐘舵�佷负閫氳繃鎴栦笉閫氳繃锛堝鏍稿凡缁撴潫锛夋墠鍏佽瀵煎嚭
+    canExport() {
+      return this.downRow.recordStatus === 2 || this.downRow.recordStatus === 3;
+    },
     // 璐d换浜轰笅鎷夛細宸蹭繚瀛樼殑璐d换浜轰笉鍦ㄥ�欓�夊垪琛ㄦ椂琛ヤ竴鏉★紝閬垮厤鍥炴樉鎴愮敤鎴稩D
     chargerOptions() {
       const list = this.userList || []
@@ -528,6 +654,19 @@
         return [...list, { id, name: this.form.checkChargerUser || String(id) }]
       }
       return list
+    },
+    // 鏃ユ湡鎺т欢鍙帴鍙� YYYY-MM-DD锛涚郴缁熺櫥璁扮殑寰呮牳鏌ヨ鍙湁骞存湀锛屾湭鏀规棩鏈熸椂涓嶈兘鎶娾�滄棩鈥濆啓杩涜〃鍗�
+    checkTimeEditValue: {
+      get() {
+        const val = this.form.checkTime || ''
+        return /^\d{4}-\d{2}$/.test(val) ? `${val}-01` : val
+      },
+      set(val) {
+        this.form.checkTime = val || ''
+      }
+    },
+    isMonthOnlyCheckTime() {
+      return /^\d{4}-\d{2}$/.test(this.form.checkTime || '')
     },
   },
 }
@@ -589,9 +728,88 @@
   background: #ffcccc !important;
 }
 
-.row-upload {
-  display: inline-block;
-  margin-left: 10px;
-  vertical-align: middle;
+.form-section {
+  padding: 12px 14px 0;
+  border: 1px solid #ebeef5;
+  border-radius: 6px;
+  margin-bottom: 10px;
+}
+
+.form-section:last-child {
+  margin-bottom: 0;
+}
+
+.form-section-title {
+  display: flex;
+  align-items: center;
+  padding-left: 8px;
+  border-left: 3px solid var(--el-color-primary);
+  margin-bottom: 12px;
+  font-size: 14px;
+  font-weight: 600;
+  line-height: 1;
+  color: #303133;
+}
+
+.form-section :deep(.el-form-item) {
+  margin-bottom: 12px;
+}
+
+.check-time-tip {
+  width: 100%;
+  font-size: 12px;
+  line-height: 1.4;
+  color: #909399;
+  margin-top: 2px;
+}
+
+.export-panel {
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  padding: 18px 20px;
+  background: #fafbfc;
+}
+
+.export-panel-head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+  margin-bottom: 14px;
+}
+
+.export-panel-name {
+  font-size: 15px;
+  font-weight: 600;
+  color: var(--lims-text);
+}
+
+.export-panel-meta {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  font-size: 13px;
+  color: #606266;
+}
+
+.export-panel-item {
+  display: flex;
+  align-items: center;
+}
+
+.export-panel-label {
+  width: 72px;
+  flex-shrink: 0;
+  color: #909399;
+}
+
+.export-panel-tip {
+  margin-top: 14px;
+  padding: 8px 12px;
+  border-radius: 4px;
+  background: #fdf6ec;
+  color: #e6a23c;
+  font-size: 13px;
+  line-height: 1.5;
 }
 </style>

--
Gitblit v1.9.3