From 29be8639dc16b5d82bddc6099b1fa5cdca75f77a Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 20 十月 2026 16:28:22 +0800
Subject: [PATCH] feat(import): 统一导入弹框与模板,修复模板日期格式及上传报错

---
 src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue |  894 +++++++++++++++++++++++++++++++++++++----------------------
 1 files changed, 559 insertions(+), 335 deletions(-)

diff --git a/src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue b/src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue
index c0708a5..f6346ed 100644
--- a/src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue
+++ b/src/views/CNAS/process/ensureResults/ensureResultsValidity/index.vue
@@ -1,38 +1,82 @@
 <template>
   <!--  7.7璐ㄩ噺鐩戞帶璁″垝-->
   <div class="app-container">
-    <div class="table-card">
-      <div style="display: flex;justify-content: space-between">
-        <div style="display: flex;">
-          <div style="margin-bottom: 18px;margin-right: 10px;display: flex;align-items: center;line-height: 32px;">
-            <span style="width: 88px;font-size: 14px;font-weight: 700;color: #606266;">璁″垝鍚嶇О</span>
-            <el-input size="small" placeholder="璇疯緭鍏�" clearable v-model="yearForm.monitorName"
-                      @keyup.enter="getYearPlanList"></el-input>
-          </div>
-          <div style="line-height: 30px;">
-            <el-button type="primary" size="small" @click="getYearPlanList">鏌ヨ</el-button>
-            <el-button size="small" @click="clearYear">閲嶇疆</el-button>
-          </div>
+    <section v-if="pageMode === 'list'" class="plan-list-card">
+      <div class="plan-list-toolbar">
+        <div class="plan-list-filters">
+          <span class="annual-plan-label">璁″垝骞村害</span>
+          <el-date-picker v-model="selectedYear" type="year" value-format="YYYY" format="YYYY骞�" :clearable="false"
+            size="small" style="width: 130px" @change="handleYearChange" />
+          <span class="filter-label">璁″垝鍚嶇О</span>
+          <el-input v-model="yearForm.monitorName" clearable placeholder="璇疯緭鍏�" size="small" style="width: 220px"
+            @keyup.enter="getYearPlanList" />
+          <el-button type="primary" size="small" @click="getYearPlanList">鏌ヨ</el-button>
+          <el-button size="small" @click="clearYear">閲嶇疆</el-button>
         </div>
-        <div style="line-height: 30px;">
-          <el-button size="small" type="primary" @click="leadInto">瀵煎叆</el-button>
+        <div class="plan-list-actions">
+          <el-button size="small" @click="leadInto">瀵煎叆</el-button>
+          <el-button type="primary" size="small" @click="openCreatePlan">鏂板缓骞村害璁″垝</el-button>
         </div>
       </div>
-      <lims-table :tableData="yearTableData" :column="yearColumnData" :page="yearPage" :tableLoading="yearLoading"
-                  :height="'calc(100vh - 34em)'" @pagination="pagination" :rowClick="rowClick" key="yearTableData"></lims-table>
+      <lims-table :tableData="yearTableData" :column="yearColumnData" :page="yearPage"
+        :tableLoading="yearLoading" :height="'calc(100vh - 11em)'" @pagination="pagination" key="yearTableData">
+        <template #planActions="{ row }">
+          <div class="detail-actions">
+            <el-button link type="primary" size="small" @click="openPlan(row)">鏌ョ湅璁″垝</el-button>
+            <el-button v-if="canReviewRow(row)" link type="primary" size="small" @click="examinePlan(row)">瀹℃牳</el-button>
+            <el-button v-if="canApproveRow(row)" link type="primary" size="small" @click="approvalPlan(row)">鎵瑰噯</el-button>
+            <el-dropdown trigger="click" @command="command => handlePlanCommand(command, row)">
+              <el-button link type="primary" size="small">鏇村鈱�</el-button>
+              <template #dropdown>
+                <el-dropdown-menu>
+                  <el-dropdown-item command="export" :disabled="row.ratifyStatus !== 1">瀵煎嚭骞村害璁″垝</el-dropdown-item>
+                  <el-dropdown-item command="delete" divided :disabled="!canDeleteRow(row)">鍒犻櫎</el-dropdown-item>
+                </el-dropdown-menu>
+              </template>
+            </el-dropdown>
+          </div>
+        </template>
+      </lims-table>
+    </section>
+
+    <template v-else>
+    <div class="annual-plan-header">
+      <div class="annual-plan-main">
+        <el-button link type="primary" size="small" @click="backToPlanList">鈫� 杩斿洖骞村害璁″垝</el-button>
+        <span class="annual-plan-label">{{ selectedYear }}骞村害</span>
+        <template v-if="currentPlan">
+          <strong>{{ currentPlan.monitorName }}</strong>
+          <el-tag :type="planStatusType">{{ planStatusText }}</el-tag>
+          <span>缂栧埗锛歿{ currentPlan.writeName || '-' }} {{ formatDate(currentPlan.writeTime) }}</span>
+          <span>瀹℃牳锛歿{ currentPlan.examineName || '-' }}</span>
+          <span>鎵瑰噯锛歿{ currentPlan.ratifyName || '-' }}</span>
+        </template>
+        <el-tag v-else type="info">璇ュ勾搴﹀皻鏈缓绔嬭鍒�</el-tag>
+      </div>
+      <div class="annual-plan-actions">
+        <el-button v-if="canEditPlan" type="primary" size="small" @click="submitPlan">鎻愪氦瀹℃牳</el-button>
+        <el-button v-if="canReviewPlan" type="primary" size="small" @click="examinePlan(currentPlan)">瀹℃牳</el-button>
+        <el-button v-if="canApprovePlan" type="primary" size="small" @click="approvalPlan(currentPlan)">鎵瑰噯</el-button>
+        <el-button size="small" @click="leadInto">瀵煎叆</el-button>
+        <el-button v-if="currentPlan" size="small" :disabled="currentPlan.ratifyStatus !== 1" @click="handleDown(currentPlan)">瀵煎嚭骞村害璁″垝</el-button>
+      </div>
     </div>
-    <div style="margin-top: 20px">
-      <div style="display: flex;justify-content: space-between">
+    <div class="plan-detail-card">
+      <el-alert v-if="currentPlan && currentPlan.ratifyStatus !== 1" type="info" :closable="false" show-icon
+        style="margin-bottom: 12px"
+        title="妫�娴嬬被璐ㄦ帶椤圭洰闇�鍦ㄥ勾搴﹁鍒掑鏍搞�佹壒鍑嗛�氳繃鍚庯紝鐐瑰嚮瀵瑰簲琛岀殑鈥滀笅鍙戜换鍔♀�濊繘琛屼笅鍙戯紱闈炴娴嬬被椤圭洰鏃犻渶涓嬪彂銆�" />
+      <div class="plan-detail-toolbar">
         <div style="display: flex;">
           <div style="margin-bottom: 18px;margin-right: 10px;display: flex;align-items: center;line-height: 32px;">
-            <span style="width: 88px;font-size: 14px;font-weight: 700;color: #606266;">鐩戞帶鐩殑</span>
-            <el-input v-model="yearDetailForm.monitorPurpose" placeholder="璇疯緭鍏�" size="small"
+            <span style="width: 88px;font-size: 14px;font-weight: 700;color: var(--lims-text-2);">璐ㄦ帶椤圭洰</span>
+            <el-input v-model="yearDetailForm.monitorProject" placeholder="璇疯緭鍏�" size="small"
                       @keyup.enter="getYearDetailPlanList"></el-input>
           </div>
           <div style="margin-bottom: 18px;margin-right: 10px;display: flex;align-items: center;line-height: 32px;">
-            <span style="width: 88px;font-size: 14px;font-weight: 700;color: #606266;">鐩戞帶椤圭洰</span>
-            <el-input v-model="yearDetailForm.monitorProject" placeholder="璇疯緭鍏�" size="small"
-                      @keyup.enter="getYearDetailPlanList"></el-input>
+            <span style="width: 70px;font-size: 14px;font-weight: 700;color: var(--lims-text-2);">璐熻矗浜�</span>
+            <el-select v-model="yearDetailForm.responsibleUserId" clearable filterable size="small" style="width: 160px">
+              <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
           </div>
           <div style="line-height: 30px;">
             <el-button type="primary" size="small" @click="getYearDetailPlanList">鏌ヨ</el-button>
@@ -40,14 +84,65 @@
           </div>
         </div>
         <div style="line-height: 30px;">
-          <el-button size="small" type="primary" @click="exportSignatureTemplate">瀵煎嚭绛惧悕妯℃澘</el-button>
-          <el-button size="small" type="primary" @click="showDialog('add')">鏂板</el-button>
+<!--          <el-button size="small" type="primary" @click="exportSignatureTemplate">瀵煎嚭绛惧悕妯℃澘</el-button>-->
+          <el-button size="small" type="primary" :disabled="!canEditPlan" @click="showDialog('add')">鏂板璐ㄦ帶椤圭洰</el-button>
         </div>
       </div>
       <lims-table :tableData="yearDetailTableData" :column="yearDetailColumnData" :page="yearDetailPage"
-        :tableLoading="yearDetailLoading" :height="'calc(100vh - 34em)'" @pagination="pagination1" ref="yearDetailTableData"
-        key="yearDetailTableData"></lims-table>
+        :tableLoading="yearDetailLoading" :height="'calc(100vh - 15em)'" @pagination="pagination1" ref="yearDetailTableData"
+        key="yearDetailTableData">
+        <template #detailActions="{ row }">
+          <div class="detail-actions">
+            <el-button link type="primary" size="small" :disabled="!canEditPlan || !!row.inspectionTaskId"
+              @click="showDialog('edit', row)">缂栬緫</el-button>
+
+            <el-button v-if="row.itemKind !== 'INSPECTION'" link type="primary" size="small"
+              @click="carryOut(row)">瀹炴柦</el-button>
+            <el-button v-else-if="!row.inspectionTaskId" link type="primary" size="small"
+              :disabled="row.planRatifyStatus !== 1" @click="dispatchTask(row)">涓嬪彂浠诲姟</el-button>
+            <el-button v-else link type="primary" size="small" @click="evaluate(row)">
+              {{ row.inspectionTaskState === 5 ? '璇勪环璁板綍' : '璇勪环瑕佹眰' }}
+            </el-button>
+
+            <el-dropdown trigger="click" @command="command => handleDetailCommand(command, row)">
+              <el-button link type="primary" size="small">鏇村鈱�</el-button>
+              <template #dropdown>
+                <el-dropdown-menu>
+                  <el-dropdown-item v-if="row.itemKind !== 'INSPECTION'" command="complete">瀹屾垚鎶ュ憡</el-dropdown-item>
+                  <el-dropdown-item v-if="row.itemKind !== 'INSPECTION'" command="evaluate">璇勪环</el-dropdown-item>
+                  <el-dropdown-item v-if="row.itemKind === 'INSPECTION' && !row.inspectionTaskId" command="requirement">璇勪环瑕佹眰</el-dropdown-item>
+                  <el-dropdown-item v-if="row.itemKind === 'INSPECTION' && row.inspectionTaskId" command="cancel"
+                    :disabled="row.inspectionTaskOrderState !== 1 || row.inspectionTaskState !== 0">鍙栨秷浠诲姟</el-dropdown-item>
+                  <el-dropdown-item command="attachment">闄勪欢涓婁紶</el-dropdown-item>
+                  <el-dropdown-item command="export">瀵煎嚭</el-dropdown-item>
+                  <el-dropdown-item command="delete" divided
+                    :disabled="!canEditPlan || !!row.inspectionTaskId">鍒犻櫎</el-dropdown-item>
+                </el-dropdown-menu>
+              </template>
+            </el-dropdown>
+          </div>
+        </template>
+      </lims-table>
     </div>
+    </template>
+    <el-dialog v-model="createPlanDialog" title="鏂板缓骞村害璐ㄩ噺鎺у埗璁″垝" width="520px">
+      <el-form :model="planForm" label-width="90px">
+        <el-form-item label="骞村害">
+          <el-date-picker v-model="planForm.monitorYear" type="year" value-format="YYYY" format="YYYY骞�"
+            :clearable="false" style="width: 100%" placeholder="璇烽�夋嫨骞村害" />
+        </el-form-item>
+        <el-form-item label="璁″垝鍚嶇О"><el-input v-model="planForm.monitorName" /></el-form-item>
+        <el-form-item label="瀹℃牳浜�">
+          <el-select v-model="planForm.examineUserId" filterable style="width:100%">
+            <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="createPlanDialog=false">鍙栨秷</el-button>
+        <el-button type="primary" :loading="createPlanLoading" @click="createPlan">纭畾</el-button>
+      </template>
+    </el-dialog>
     <!--鏂板淇敼寮规-->
     <detail-form-dialog v-if="formDia" ref="formDia" :qualityMonitorId="qualityMonitorId"
       @closeDia="closeDia"></detail-form-dialog>
@@ -56,6 +151,8 @@
       @closeCarryOutDia="closeCarryOutDia"></carry-out-dialog>
     <!--璇勪环娴佺▼寮规-->
     <evaluate-dialog v-if="evaluateDialog" ref="evaluateDialog" @closeEvaDia="closeEvaDia"></evaluate-dialog>
+    <inspection-evaluation-dialog v-if="inspectionEvaluateDialog" ref="inspectionEvaluateDialog"
+      @close="closeInspectionEvaluateDialog" />
     <el-dialog v-model="examineDialog" title="瀹℃牳" width="30%" @close="closeExamineDia">
       <span>
         瀹℃牳澶囨敞锛�
@@ -117,27 +214,38 @@
     <!--瀵煎叆璁″垝-->
     <el-dialog v-model="uploadDia" title="鏁版嵁瀵煎叆" width="500px" :close-on-click-modal="false"
       :close-on-press-escape="false">
-      <div style="display: flex; align-items: center;">
-        <span style="width: 60px">骞翠唤锛�</span>
-        <el-date-picker v-model="planYear" type="year" value-format="YYYY" clearable size="small" format="YYYY"
-          placeholder="閫夋嫨骞�">
-        </el-date-picker>
-      </div>
-      <div style="display: flex;align-items: center;margin: 10px 0">
-        <div style="width: 60px">瀹℃牳浜猴細</div>
-        <el-select v-model="examineUserId" clearable filterable size="small" style="width: 50%;">
-          <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id">
-          </el-option>
-        </el-select>
+      <el-form label-width="70px" size="small">
+        <el-form-item label="骞翠唤">
+          <el-date-picker v-model="planYear" type="year" value-format="YYYY" clearable format="YYYY"
+            placeholder="閫夋嫨骞�" style="width: 100%">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="瀹℃牳浜�">
+          <el-select v-model="examineUserId" clearable filterable style="width: 100%">
+            <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id">
+            </el-option>
+          </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>
       <div style="margin: 0 auto;">
-        <el-upload ref="upload" :action="action" :auto-upload="false" :file-list="fileList" :headers="uploadHeader"
-          :limit="1" accept='.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'
+        <el-upload ref="upload" :action="action" :auto-upload="false" v-model:file-list="fileList" :headers="uploadHeader"
+          :limit="1" accept=".xls,.xlsx"
           :on-change="beforeUpload" :on-error="onError" :on-success="handleSuccessUp" drag
-          :data="{ planYear: planYear, examineUserId: examineUserId }" name="file">
+          :data="{ monitorYear: planYear, examineUserId: examineUserId }" name="file">
           <lims-icon name="el-icon-upload"></lims-icon>
           <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
         </el-upload>
+      </div>
+      <div style="margin-top: 10px; color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px;">
+        <div>鏀寔鏍煎紡锛�.xls銆�.xlsx锛涘崟涓枃浠朵笉瓒呰繃10MB銆�</div>
+        <div>璐ㄦ帶椤圭洰銆佹妧鏈爣鍑嗐�佽川鎺ф柟寮忋�佸畬鎴愭椂闄愩�佽礋璐d汉涓哄繀濉」銆�</div>
+        <div>瀹屾垚鏃堕檺鏍煎紡蹇呴』涓� YYYY-MM锛堟ā鏉夸腑璇ュ垪涓烘枃鏈牸寮忥紝鐩存帴杈撳叆 2026-12 鍗冲彲锛夛紱璐熻矗浜哄繀椤诲~鍐欑郴缁熶腑宸插瓨鍦ㄧ殑浜哄憳濮撳悕銆�</div>
+        <div>瀵煎叆鍚庤鍒掍繚瀛樹负鑽夌銆傝鍦ㄨ鍒掕鎯呬腑缂栬緫璐ㄦ帶椤圭洰骞堕�夋嫨浜嬮」绫诲埆锛涙娴嬬被杩橀渶閫夋嫨妫�娴嬫爣鍑嗐�佸師濮嬭褰曟ā鏉裤�佹娴嬪憳銆佽瘎浠蜂汉骞跺~鍐欎换鍔℃埅姝㈡棩锛岀‘璁ゅ悗鍐嶆彁浜ゅ鏍搞��</div>
+        <div>瀵煎叆鍙鍙栨ā鏉跨殑绗竴涓伐浣滆〃銆�</div>
       </div>
       <template #footer><span  class="dialog-footer">
         <el-button @click="uploadDia = false">鍙� 娑�</el-button>
@@ -152,7 +260,7 @@
       </div>
       <div style="margin: 0 auto;">
         <el-upload ref="upload1" :action="action1" :auto-upload="false"
-          :data="{ qualityMonitorDetailsId: qualityMonitorDetailsId }" :file-list="fileList1" :headers="uploadHeader"
+          :data="{ qualityMonitorDetailsId: qualityMonitorDetailsId }" v-model:file-list="fileList1" :headers="uploadHeader"
           :limit="1" :on-change="beforeUpload1" :on-error="onError1" :on-success="onSuccess1" accept='.doc,.docx,application/pdf,' drag
           name="file">
           <lims-icon name="el-icon-upload"></lims-icon>
@@ -170,17 +278,20 @@
 
 <script>
 import DetailFormDialog from './components/detailFormDialog.vue';
+import InspectionEvaluationDialog from './components/inspectionEvaluationDialog.vue';
 import CarryOutDialog from './components/carryOutDialog.vue';
 import EvaluateDialog from './components/evaluateDialog.vue';
 import filePreview from "@/components/Preview/filePreview.vue";
 import limsTable from "@/components/Table/lims-table.vue";
 import {
+  createAnnualPlan, getAnnualPlan, submitAnnualPlan,
   delQualityMonitorDetail,
   delQualitySupervise,
   examineQualityMonitor, exportQualityMonitorDetail, exportQualityMonitorEvaluate, exportQualityMonitorRatify,
   pageQualityMonitor, pageQualityMonitorDetail, ratifyFinishReport,
   ratifyQualityMonitor
 } from "@/api/cnas/process/ensureResults/qualityMonitor";
+import { dispatchInspectionTask, cancelInspectionTask } from "@/api/cnas/process/ensureResults/qualityMonitor";
 import { selectUserCondition } from "@/api/performance/class";
 import { mapGetters } from "@/utils/store-compat";
 import ViewRecord from "./components/ViewRecord.vue";
@@ -188,11 +299,19 @@
 export default {
   name: 'EnsureResultsValidity',
   // import 寮曞叆鐨勭粍浠堕渶瑕佹敞鍏ュ埌瀵硅薄涓墠鑳戒娇鐢�
-  components: {ViewRecord, limsTable, filePreview, EvaluateDialog, CarryOutDialog, DetailFormDialog },
+  components: {ViewRecord, limsTable, filePreview, EvaluateDialog, CarryOutDialog, DetailFormDialog, InspectionEvaluationDialog },
   data() {
     // 杩欓噷瀛樻斁鏁版嵁
     return {
       ViewRecord: false,
+      inspectionEvaluateDialog: false,
+      pageMode: 'list',
+      selectedYear: String(new Date().getFullYear()),
+      currentPlan: null,
+      selectedPlanId: null,
+      createPlanDialog: false,
+      createPlanLoading: false,
+      planForm: { monitorYear: String(new Date().getFullYear()), monitorName: '', examineUserId: null },
       examineUserId: '',
       responsibleOptions: [],
       yearForm: {
@@ -207,247 +326,91 @@
         {
           label: '璁″垝鍚嶇О',
           prop: 'monitorName',
-          width: '150px'
+          minWidth: '220px'
+        }, {
+          label: '璁″垝骞村害',
+          prop: 'monitorYear',
+          width: '100px'
+        }, {
+          dataType: 'tag',
+          label: '璁″垝鐘舵��',
+          prop: 'workflowStatus',
+          width: '110px',
+          formatData: (value, row) => this.planWorkflowText(value),
+          formatType: value => this.planWorkflowType(value)
         }, {
           label: '缂栧埗浜�',
           prop: 'writeName',
-          width: '100'
+          minWidth: '100px'
         }, {
           label: '缂栧埗鏃ユ湡',
           prop: 'writeTime',
-          width: '160'
-        }, {
-          dataType: 'tag',
-          label: '瀹℃牳鐘舵��',
-          prop: 'examineStatus',
-          width: '100',
-          formatData: (params) => {
-            if (params === 0) {
-              return '涓嶉�氳繃';
-            } else if (params === 1) {
-              return '閫氳繃';
-            } else {
-              return null;
-            }
-          },
-          formatType: (params) => {
-            if (params === 0) {
-              return 'danger';
-            } else if (params === 1) {
-              return 'success';
-            } else {
-              return null;
-            }
-          }
-        }, {
-          label: '瀹℃牳鍐呭',
-          prop: 'examineRemark',
-          width: '100'
+          minWidth: '150px',
+          formatData: value => this.formatDate(value)
         }, {
           label: '瀹℃牳浜�',
           prop: 'examineName',
-          width: '100'
+          minWidth: '100px'
         }, {
           label: '瀹℃牳鏃ユ湡',
           prop: 'examineTime',
-          width: '160'
-        }, {
-          dataType: 'tag',
-          label: '鎵瑰噯鐘舵��',
-          prop: 'ratifyStatus',
-          width: '100',
-          formatData: (params) => {
-            if (params === 0) {
-              return '涓嶆壒鍑�';
-            } else if (params === 1) {
-              return '鎵瑰噯';
-            } else {
-              return null;
-            }
-          },
-          formatType: (params) => {
-            if (params === 0) {
-              return 'danger';
-            } else if (params === 1) {
-              return 'success';
-            } else {
-              return null;
-            }
-          }
-        }, {
-          label: '鎵瑰噯鍐呭',
-          prop: 'ratifyRemark',
-          width: '100'
+          minWidth: '150px',
+          formatData: value => this.formatDate(value)
         }, {
           label: '鎵瑰噯浜�',
           prop: 'ratifyName',
-          width: '100'
+          minWidth: '100px'
         }, {
           label: '鎵瑰噯鏃ユ湡',
           prop: 'ratifyTime',
-          width: '160'
+          minWidth: '150px',
+          formatData: value => this.formatDate(value)
         }, {
-          dataType: 'action',
+          dataType: 'slot',
+          slot: 'planActions',
           label: '鎿嶄綔',
           fixed: 'right',
-          operation: [
-            {
-              name: '瀹℃牳',
-              type: 'text',
-              disabled: (row) => {
-                if (row.examineStatus === 1 || this.userId != row.examineUserId) {
-                  return true
-                } else {
-                  return false
-                }
-              },
-              clickFun: (row) => {
-                this.examinePlan(row)
-              }
-            },
-            {
-              name: '鎵瑰噯',
-              type: 'text',
-              disabled: (row) => {
-                if (row.ratifyStatus === 1 || row.examineStatus !== 1 || this.userId != row.ratifyUserId) {
-                  return true
-                } else {
-                  return false
-                }
-              },
-              clickFun: (row) => {
-                this.approvalPlan(row)
-              }
-            },
-            {
-              name: '瀵煎嚭',
-              type: 'text',
-              clickFun: (row) => {
-                this.handleDown(row)
-              },
-              disabled: (row) => {
-                if (row.ratifyStatus !== 1) {
-                  return true
-                } else {
-                  return false
-                }
-              },
-            },
-            {
-              name: '鍒犻櫎',
-              type: 'text',
-              color: 'var(--el-color-danger)',
-              clickFun: (row) => {
-                this.delPlan(row)
-              },
-              disabled: (row) => {
-                if (row.examineStatus === 1) {
-                  return true;
-                } else {
-                  return false;
-                }
-              },
-            }
-          ]
+          width: '220px'
         }],
       yearTableData: [],  // 骞磋〃
       yearLoading: false,
       yearDetailForm: {
-        monitorPurpose: '',
         monitorProject: '',
+        responsibleUserId: null,
       },
       yearDetailColumnData: [
         {
-          label: '鐩戞帶鐩殑',
-          prop: 'monitorPurpose',
-          width: '150px',
-          showOverflowTooltip: true,
-        }, {
-          label: '璁″垝寮�灞曟椂闂�',
-          prop: 'plannedTime',
-          width: '150px'
-        }, {
-          label: '鐩戞帶椤圭洰',
+          label: '璐ㄦ帶椤圭洰',
           prop: 'monitorProject',
-          width: '150px'
+          minWidth: '140px'
         }, {
-          label: '鍙傚姞鍗曚綅锛堜汉鍛橈級',
-          prop: 'participant',
-          width: '190px'
+          label: '鎶�鏈爣鍑�',
+          prop: 'technicalStandardText',
+          minWidth: '220px'
         }, {
-          label: '棰勭畻锛堝厓锛�',
-          prop: 'budget',
-          width: '150px'
-        }, {
-          label: '缁勭粐浜哄憳',
-          prop: 'organization',
-          width: '150px'
-        }, {
-          label: '鐩戞帶鏂瑰紡',
+          label: '璐ㄦ帶鏂瑰紡',
           prop: 'monitorWay',
-          width: '150px'
-        },{
-          dataType: 'tag',
-          label: '瀹炴柦鐘舵��',
-          prop: 'detailsRatifyStatus',
-          width: '100',
-          formatData: (params) => {
-            if (params === 0) {
-              return '鏈紑濮�';
-            } else if (params === 1) {
-              return '寰呰瘎浠�';
-            }  else if (params === 2) {
-              return '涓嶆壒鍑�';
-            } else if (params === 3) {
-              return '宸叉壒鍑�';
-            } else {
-              return null;
-            }
-          },
-          formatType: (params) => {
-            if (params === 0) {
-              return '';
-            } else if (params === 1) {
-              return 'warning';
-            } else if (params === 2) {
-              return 'danger';
-            }  else if (params === 3) {
-              return 'success';
-            } else {
-              return null;
-            }
-          }
-        },{
-          dataType: 'tag',
-          label: '鎶ュ憡鐘舵��',
-          prop: 'reportStatus',
-          width: '100',
-          formatData: (params) => {
-            if (params === 0) {
-              return '鏈紑濮�';
-            } else if (params === 1) {
-              return '寰呰瘎浠�';
-            }  else if (params === 2) {
-              return '涓嶆壒鍑�';
-            } else if (params === 3) {
-              return '宸叉壒鍑�';
-            } else {
-              return null;
-            }
-          },
-          formatType: (params) => {
-            if (params === 0) {
-              return '';
-            } else if (params === 1) {
-              return 'warning';
-            } else if (params === 2) {
-              return 'danger';
-            }  else if (params === 3) {
-              return 'success';
-            } else {
-              return null;
-            }
-          }
+          minWidth: '120px'
+        }, {
+          label: '瀹屾垚鏃堕檺',
+          prop: 'completionMonth',
+          width: '110px',
+          formatData: value => value ? value.replace('-', '.') : ''
+        }, {
+          label: '璐熻矗浜�',
+          prop: 'responsibleName',
+          minWidth: '100px'
+        }, {
+          label: '璇勪环浜�',
+          prop: 'evaluatorName',
+          minWidth: '100px'
+        }, {
+          label: '浜嬮」绫诲埆', prop: 'itemKind', width: '100px',
+          formatData: value => value === 'INSPECTION' ? '妫�娴嬬被' : '闈炴娴嬬被'
+        }, {
+          label: '鐩戞帶浠诲姟缂栧彿', prop: 'inspectionTaskCode', minWidth: '150px'
+        }, {
+          label: '浠诲姟鐘舵��', prop: 'taskStatusText', width: '110px'
         },{
           dataType: 'tag',
           label: '璇勪环鐘舵��',
@@ -480,61 +443,11 @@
             }
           }
         }, {
-          dataType: 'action',
+          dataType: 'slot',
+          slot: 'detailActions',
           label: '鎿嶄綔',
           fixed: 'right',
-          operation: [
-            {
-              name: '缂栬緫',
-              type: 'text',
-              clickFun: (row) => {
-                this.showDialog('edit', row)
-              }
-            },
-            {
-              name: '瀹炴柦',
-              type: 'text',
-              clickFun: (row) => {
-                this.carryOut(row)
-              }
-            },
-            {
-              name: '瀹屾垚鎶ュ憡',
-              type: 'text',
-              clickFun: (row) => {
-                this.record(row)
-              },
-            },
-            {
-              name: '璇勪环',
-              type: 'text',
-              clickFun: (row) => {
-                this.evaluate(row)
-              }
-            },
-            {
-              name: '闄勪欢涓婁紶',
-              type: 'text',
-              clickFun: (row) => {
-                this.viewTestRecord(row)
-              }
-            },
-            {
-              name: '瀵煎嚭',
-              type: 'text',
-              clickFun: (row) => {
-                this.downLoadPost(row);
-              },
-            },
-            {
-              name: '鍒犻櫎',
-              type: 'text',
-              color: 'var(--el-color-danger)',
-              clickFun: (row) => {
-                this.delYearPlanDetail(row)
-              }
-            }
-          ]
+          width: '220px'
         }
       ],
       yearDetailTableData: [], // 骞存槑缁嗚〃
@@ -574,31 +487,91 @@
     };
   },
   mounted() {
+    this.getUserList()
     this.getYearPlanList()
   },
   // 鏂规硶闆嗗悎
   methods: {
     // 鏌ヨ骞村害璁″垝琛�
     getYearPlanList() {
-      const entity = {
-        monitorName: this.yearForm.monitorName,
-      }
-      const page = this.yearPage
       this.yearLoading = true
-      pageQualityMonitor({ ...entity, ...page }).then(res => {
-        this.yearLoading = false
-        this.yearTableData = res.data.records
-        this.yearPage.total = res.data.total
-        if (this.yearTableData.length > 0) {
-          this.rowClick(this.yearTableData[0])
-        }
-      }).catch(err => {
-        console.log('err---', err);
+      const entity = {
+        monitorYear: this.selectedYear,
+        monitorName: this.yearForm.monitorName
+      }
+      return pageQualityMonitor({ ...entity, ...this.yearPage }).then(res => {
+        this.yearTableData = res.data?.records || []
+        this.yearPage.total = res.data?.total || 0
+      }).finally(() => {
         this.yearLoading = false
       })
     },
+    loadAnnualPlan() {
+      if (!this.selectedPlanId) return Promise.resolve()
+      return this.loadPlanVersion()
+    },
+    loadPlanVersion() {
+      if (!this.selectedPlanId) return Promise.resolve()
+      return getAnnualPlan({ monitorYear: this.selectedYear, qualityMonitorId: this.selectedPlanId }).then(res => {
+        this.currentPlan = res.data || null
+        this.qualityMonitorId = this.currentPlan?.qualityMonitorId || ''
+        this.yearDetailPage.current = 1
+        return this.getYearDetailPlanList()
+      })
+    },
+    openPlan(row, addFirstDetail = false) {
+      this.selectedYear = String(row.monitorYear || this.selectedYear)
+      this.selectedPlanId = row.qualityMonitorId
+      this.pageMode = 'detail'
+      return this.loadPlanVersion().then(() => {
+        if (addFirstDetail) this.showDialog('add')
+      })
+    },
+    backToPlanList() {
+      this.pageMode = 'list'
+      this.currentPlan = null
+      this.qualityMonitorId = ''
+      this.yearDetailForm = { monitorProject: '', responsibleUserId: null }
+      this.getYearPlanList()
+    },
+    handleYearChange() {
+      this.yearPage.current = 1
+      this.getYearPlanList()
+    },
+    openCreatePlan() {
+      this.planForm = {
+        monitorYear: String(new Date().getFullYear()),
+        monitorName: '',
+        examineUserId: null
+      }
+      this.createPlanDialog = true
+    },
+    createPlan() {
+      if (!this.planForm.monitorYear || !this.planForm.monitorName || !this.planForm.examineUserId) {
+        this.$message.warning('璇烽�夋嫨骞村害銆佸~鍐欒鍒掑悕绉板苟閫夋嫨瀹℃牳浜�')
+        return
+      }
+      this.createPlanLoading = true
+      createAnnualPlan({ ...this.planForm }).then(res => {
+        this.createPlanDialog = false
+        this.selectedYear = String(res.data.monitorYear)
+        this.yearPage.current = 1
+        this.$message.success('骞村害璁″垝宸插垱寤猴紝璇峰~鍐欑涓�涓川鎺ч」鐩�')
+        return this.getYearPlanList().then(() => this.openPlan(res.data, true))
+      }).finally(() => { this.createPlanLoading = false })
+    },
+    submitPlan() {
+      this.$confirm('鎻愪氦鍚庤川鎺ч」鐩皢閿佸畾锛岀‘璁ゆ彁浜ゅ鏍革紵', '鎻愪氦瀹℃牳').then(() => {
+        submitAnnualPlan(this.qualityMonitorId).then(() => {
+          this.$message.success('宸叉彁浜ゅ鏍�')
+          this.loadPlanVersion()
+          this.getYearPlanList()
+        })
+      }).catch(() => {})
+    },
     clearYear() {
       this.yearForm.monitorName = ''
+      this.yearPage.current = 1
       this.getYearPlanList()
     },
     pagination({ page, limit }) {
@@ -609,14 +582,31 @@
     pagination1({ page, limit }) {
       this.yearDetailPage.current = page;
       this.yearDetailPage.size = limit;
-      this.getYearPlanList();
+      this.getYearDetailPlanList();
     },
     leadInto() {
       this.uploadDia = true
+      this.planYear = this.selectedYear
+      this.fileList = []
       this.getUserList()
+    },
+    // 涓嬭浇瀵煎叆妯℃澘
+    downloadTemplate() {
+      const link = document.createElement('a')
+      // 甯︾増鏈弬鏁伴伩鍏嶆祻瑙堝櫒缁х画浣跨敤缂撳瓨閲岀殑鏃фā鏉�
+      link.href = '/import-templates/璐ㄩ噺鐩戞帶璁″垝瀵煎叆妯℃澘.xlsx?v=2'
+      link.download = '璐ㄩ噺鐩戞帶璁″垝瀵煎叆妯℃澘.xlsx'
+      document.body.appendChild(link)
+      link.click()
+      document.body.removeChild(link)
     },
     // 瀵煎叆娴佺▼
     beforeUpload(file) {
+      if (!/\.(xls|xlsx)$/i.test(file.name || '')) {
+        this.$message.error('璇烽�夋嫨 .xls 鎴� .xlsx 鏍煎紡鐨勬枃浠�');
+        this.$refs.upload.clearFiles()
+        return false;
+      }
       if (file.size > 1024 * 1024 * 10) {
         this.$message.error('涓婁紶鏂囦欢涓嶈秴杩�10M');
         this.$refs.upload.clearFiles()
@@ -626,23 +616,40 @@
       }
     },
     onError(err, file, fileList) {
-      this.$message.error('涓婁紶澶辫触')
-      this.$refs.upload.clearFiles()
+      this.uploading = false
+      this.fileList = []
+      this.$message.error('涓婁紶澶辫触锛岃閲嶆柊閫夋嫨鏂囦欢')
     },
     handleSuccessUp(response) {
       this.uploading = false;
       if (response.code == 200) {
-        this.$message.success('涓婁紶鎴愬姛');
+        this.$message.success('瀵煎叆鎴愬姛锛岃鍒掑凡淇濆瓨涓鸿崏绋匡紝璇疯ˉ鍏ㄨ川鎺ч」鐩悗鎻愪氦瀹℃牳');
+        const importedPlan = response.data || null
         this.timer = setTimeout(() => {
           this.uploadDia = false
           this.fileList = []
-          this.planYear = ''
           this.examineUserId = ''
-          this.getYearPlanList()
+          // 瀵煎叆瀹屾垚鍚庣洿鎺ヨ繘鍏ヨ鍒掕鎯咃紝鏂逛究缁х画缂栬緫璐ㄦ帶椤圭洰銆侀�夋嫨妫�娴嬫爣鍑嗙瓑
+          if (importedPlan && importedPlan.qualityMonitorId) {
+            this.openPlan(importedPlan)
+          } else {
+            this.getYearPlanList()
+          }
         }, 1000)
       } else {
-        this.$message.error(response.msg)
+        this.$message.error(response.msg || '涓婁紶澶辫触')
+        // 鍚庣杩斿洖涓氬姟閿欒鏃� HTTP 鐘舵�佷粛鏄� 200锛宔l-upload 浼氭妸鏂囦欢鏍囪涓� success锛�
+        // 鐩存帴鍐嶆璋冪敤 submit() 涓嶄細閲嶆柊涓婁紶锛岃繖閲屾妸鏂囦欢鎭㈠涓哄緟涓婁紶鐘舵�併��
+        this.resetUploadFiles()
       }
+    },
+    resetUploadFiles() {
+      const files = this.$refs.upload?.uploadFiles || this.fileList || []
+      files.forEach(file => {
+        file.status = 'ready'
+        file.response = undefined
+        file.percentage = 0
+      })
     },
     submitUpload() {
       if (!this.planYear) {
@@ -650,9 +657,15 @@
         return;
       }
       if (!this.examineUserId) {
-        this.$message.warning('璇烽�夋嫨鎵瑰噯浜�')
+        this.$message.warning('璇烽�夋嫨瀹℃牳浜�')
         return;
       }
+      const files = this.$refs.upload?.uploadFiles || this.fileList || []
+      if (!files.length) {
+        this.$message.warning('璇烽�夋嫨瑕佸鍏ョ殑鏂囦欢')
+        return;
+      }
+      this.uploading = true
       this.$refs.upload.submit();
     },
     // end
@@ -678,7 +691,7 @@
     closeExamineDia() {
       this.examineDialog = false
       this.examineInfo.examineRemark = ''
-      this.getYearPlanList()
+      this.refreshPlanData()
     },
     // 鎵瑰噯
     approvalPlan(row) {
@@ -701,7 +714,14 @@
     closeRatifyDia() {
       this.ratifyDialog = false
       this.ratifyInfo.ratifyRemark = ''
-      this.getYearPlanList()
+      this.refreshPlanData()
+    },
+    refreshPlanData() {
+      return this.getYearPlanList().then(() => {
+        if (this.pageMode === 'detail' && this.selectedPlanId) {
+          return this.loadPlanVersion()
+        }
+      })
     },
     // 鎵瑰噯鎶ュ憡
     handleApproval(status) {
@@ -762,16 +782,19 @@
     getYearDetailPlanList() {
       const entity = {
         qualityMonitorId: this.qualityMonitorId,
-        monitorPurpose: this.yearDetailForm.monitorPurpose,
         monitorProject: this.yearDetailForm.monitorProject,
+        responsibleUserId: this.yearDetailForm.responsibleUserId,
       }
       const page = this.yearDetailPage
       this.yearDetailLoading = true
       pageQualityMonitorDetail({ ...entity, ...page }).then(res => {
         this.yearDetailLoading = false
-        this.yearDetailTableData = res.data.records
+        this.yearDetailTableData = res.data.records.map(item => ({
+          ...item,
+          taskStatusText: this.inspectionTaskStatus(item)
+        }))
         this.yearDetailPage.total = res.data.total
-        this.$refs.yearDetailTableData.$refs.multipleTable.doLayout()
+        this.$nextTick(() => this.$refs.yearDetailTableData?.$refs?.multipleTable?.doLayout())
       }).catch(err => {
         console.log('err---', err);
         this.yearDetailLoading = false
@@ -780,8 +803,8 @@
     // 閲嶇疆鏄庣粏琛�
     clearDetail() {
       this.yearDetailForm = {
-        monitorPurpose: '',
-        monitorProject: ''
+        monitorProject: '',
+        responsibleUserId: null
       }
       this.getYearDetailPlanList()
     },
@@ -822,6 +845,7 @@
         }
         this.lookDialogVisible = true
       } else {
+        this.fileList1 = []
         this.uploadDia1 = true
       }
     },
@@ -851,7 +875,7 @@
       this.uploading1 = false
     },
     submitUpload1() {
-      if (this.$refs.upload1.uploadFiles.length === 0) {
+      if (!this.fileList1 || this.fileList1.length === 0) {
         this.$message.error('鏈�夋嫨鏂囦欢')
         return
       }
@@ -862,6 +886,11 @@
     // end
     // 鎵撳紑璇勪环寮规
     evaluate(row) {
+      if (row.itemKind === 'INSPECTION') {
+        this.inspectionEvaluateDialog = true
+        this.$nextTick(() => this.$refs.inspectionEvaluateDialog.open(row))
+        return
+      }
       this.evaluateDialog = true
       this.$nextTick(() => {
         this.$refs.evaluateDialog.openDia(row)
@@ -870,6 +899,26 @@
     closeEvaDia() {
       this.evaluateDialog = false
       this.getYearDetailPlanList()
+    },
+    closeInspectionEvaluateDialog() {
+      this.inspectionEvaluateDialog = false
+      this.getYearDetailPlanList()
+    },
+    dispatchTask(row) {
+      this.$confirm(`纭涓嬪彂鈥�${row.monitorProject}鈥濈殑璐ㄩ噺鐩戞帶妫�楠屼换鍔★紵`, '涓嬪彂浠诲姟').then(() => {
+        dispatchInspectionTask(row.qualityMonitorDetailsId).then(res => {
+          this.$message.success(`宸蹭笅鍙戠洃鎺т换鍔★細${res.data.entrustCode}`)
+          this.getYearDetailPlanList()
+        })
+      }).catch(() => {})
+    },
+    cancelTask(row) {
+      this.$confirm(`纭鍙栨秷鐩戞帶浠诲姟鈥�${row.inspectionTaskCode}鈥濓紵宸插彇娑堜换鍔′細淇濈暀鍘嗗彶璁板綍銆俙, '鍙栨秷浠诲姟').then(() => {
+        cancelInspectionTask(row.qualityMonitorDetailsId).then(() => {
+          this.$message.success('浠诲姟宸插彇娑�')
+          this.getYearDetailPlanList()
+        })
+      }).catch(() => {})
     },
     // 闄勪欢涓婁紶
     viewTestRecord (row) {
@@ -885,6 +934,14 @@
     },
     // 鎵撳紑骞村害鏄庣粏鏂板銆佷慨鏀瑰脊妗�
     showDialog(type, row) {
+      if (!this.qualityMonitorId) {
+        this.$message.warning('璇峰厛鏂板缓骞村害璐ㄩ噺鎺у埗璁″垝')
+        return
+      }
+      if (!this.canEditPlan) {
+        this.$message.warning('褰撳墠璁″垝鐘舵�佷笉鑳界淮鎶よ川鎺ч」鐩�')
+        return
+      }
       this.formDia = true
       this.$nextTick(() => {
         this.$refs.formDia.openDia(type, row)
@@ -949,11 +1006,98 @@
       link.target = '_blank';
       document.body.appendChild(link);
       link.click();
+    },
+    formatDate(value) {
+      return value ? String(value).slice(0, 10) : '-'
+    },
+    planWorkflowText(value) {
+      return ({
+        DRAFT: '鑽夌',
+        PENDING_REVIEW: '寰呭鏍�',
+        REVIEW_REJECTED: '瀹℃牳閫�鍥�',
+        PENDING_APPROVAL: '寰呮壒鍑�',
+        APPROVAL_REJECTED: '鎵瑰噯閫�鍥�',
+        APPROVED: '宸叉壒鍑�'
+      })[value] || '鍘嗗彶璁″垝'
+    },
+    planWorkflowType(value) {
+      return ({
+        DRAFT: 'info',
+        PENDING_REVIEW: 'warning',
+        REVIEW_REJECTED: 'danger',
+        PENDING_APPROVAL: 'warning',
+        APPROVAL_REJECTED: 'danger',
+        APPROVED: 'success'
+      })[value] || 'info'
+    },
+    canReviewRow(row) {
+      return row.workflowStatus === 'PENDING_REVIEW'
+        && Number(this.userId) === Number(row.examineUserId)
+    },
+    canApproveRow(row) {
+      return row.workflowStatus === 'PENDING_APPROVAL'
+        && Number(this.userId) === Number(row.ratifyUserId)
+    },
+    canDeleteRow(row) {
+      return Number(this.userId) === Number(row.writeUserId)
+        && ['DRAFT', 'REVIEW_REJECTED', 'APPROVAL_REJECTED'].includes(row.workflowStatus || 'DRAFT')
+    },
+    handlePlanCommand(command, row) {
+      if (command === 'export') this.handleDown(row)
+      if (command === 'delete') this.delPlan(row)
+    },
+    inspectionTaskStatus(row) {
+      if (row.itemKind !== 'INSPECTION') return '鏃犻渶涓嬪彂'
+      const value = row.inspectionTaskState
+      if (value === null || value === undefined) {
+        return Number(row.planRatifyStatus) === 1 ? '鏈笅鍙�' : '寰呰鍒掓壒鍑�'
+      }
+      const map = { 0: '寰呮娴�', 1: '妫�娴嬩腑', 2: '妫�娴嬩腑', 3: '寰呭鏍�', 4: '澶嶆牳閫�鍥�', 5: '寰呰瘎浠�' }
+      return map[value] || '澶勭悊涓�'
+    },
+    handleDetailCommand(command, row) {
+      const handlers = {
+        complete: () => this.record(row),
+        evaluate: () => this.evaluate(row),
+        requirement: () => this.evaluate(row),
+        cancel: () => this.cancelTask(row),
+        attachment: () => this.viewTestRecord(row),
+        export: () => this.downLoadPost(row),
+        delete: () => this.delYearPlanDetail(row)
+      }
+      handlers[command]?.()
     }
   },
   // 鐢ㄤ簬涓婁紶鏂囦欢鐨勪俊鎭�
   computed: {
     ...mapGetters(["userId", "nickName"]),
+    effectivePlanStatus() {
+      if (!this.currentPlan) return ''
+      if (this.currentPlan.workflowStatus) return this.currentPlan.workflowStatus
+      if (this.currentPlan.ratifyStatus === 1) return 'APPROVED'
+      if (this.currentPlan.ratifyStatus === 0) return 'APPROVAL_REJECTED'
+      if (this.currentPlan.examineStatus === 1) return 'PENDING_APPROVAL'
+      if (this.currentPlan.examineStatus === 0) return 'REVIEW_REJECTED'
+      return 'DRAFT'
+    },
+    planStatusText() {
+      return ({DRAFT:'鑽夌',PENDING_REVIEW:'寰呭鏍�',REVIEW_REJECTED:'瀹℃牳閫�鍥�',PENDING_APPROVAL:'寰呮壒鍑�',APPROVAL_REJECTED:'鎵瑰噯閫�鍥�',APPROVED:'宸叉壒鍑�'})[this.effectivePlanStatus] || '鍘嗗彶璁″垝'
+    },
+    planStatusType() {
+      return ({DRAFT:'info',PENDING_REVIEW:'warning',REVIEW_REJECTED:'danger',PENDING_APPROVAL:'warning',APPROVAL_REJECTED:'danger',APPROVED:'success'})[this.effectivePlanStatus] || 'info'
+    },
+    canEditPlan() {
+      return !!this.currentPlan && Number(this.userId) === Number(this.currentPlan.writeUserId)
+        && ['DRAFT','REVIEW_REJECTED','APPROVAL_REJECTED'].includes(this.effectivePlanStatus)
+    },
+    canReviewPlan() {
+      return !!this.currentPlan && this.effectivePlanStatus === 'PENDING_REVIEW'
+        && Number(this.userId) === Number(this.currentPlan.examineUserId)
+    },
+    canApprovePlan() {
+      return !!this.currentPlan && this.effectivePlanStatus === 'PENDING_APPROVAL'
+        && Number(this.userId) === Number(this.currentPlan.ratifyUserId)
+    },
     action() {
       return this.javaApi + '/qualityMonitor/importQualityMonitor'
     },
@@ -967,4 +1111,84 @@
 };
 </script>
 
-<style scoped></style>
+<style scoped>
+.plan-list-card {
+  min-width: 0;
+  padding: 14px 16px;
+  background: var(--lims-card-bg, var(--el-bg-color));
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 4px;
+}
+.plan-list-toolbar,
+.plan-list-filters,
+.plan-list-actions {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+.plan-list-toolbar {
+  justify-content: space-between;
+  margin-bottom: 14px;
+}
+.filter-label {
+  margin-left: 8px;
+  font-size: 14px;
+  font-weight: 700;
+  color: var(--lims-text-2);
+  white-space: nowrap;
+}
+.annual-plan-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 16px;
+  padding: 14px 16px;
+  margin-bottom: 16px;
+  background: var(--lims-card-bg, var(--el-bg-color));
+  border: 1px solid var(--el-border-color-light);
+  border-radius: 4px;
+}
+.annual-plan-main, .annual-plan-actions {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+.annual-plan-label { font-weight: 700; color: var(--lims-text-2); }
+.plan-detail-card {
+  min-width: 0;
+  background: var(--lims-card-bg, var(--el-bg-color));
+}
+.plan-detail-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+.section-title {
+  display: inline-flex;
+  align-items: center;
+  margin-right: 18px;
+  font-size: 15px;
+  font-weight: 700;
+  color: var(--lims-text-1, var(--el-text-color-primary));
+  white-space: nowrap;
+}
+.detail-actions {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  white-space: nowrap;
+}
+@media (max-width: 1200px) {
+  .annual-plan-header {
+    align-items: flex-start;
+    flex-direction: column;
+  }
+  .annual-plan-actions {
+    width: 100%;
+  }
+}
+</style>

--
Gitblit v1.9.3