From 00e6928c50d58ed93fef75454d4f2ef0110d89e9 Mon Sep 17 00:00:00 2001
From: 吴羡 <16976746+Xian-TT3@user.noreply.gitee.com>
Date: 星期五, 30 十月 2026 16:32:17 +0800
Subject: [PATCH] feat(cnas,business): 客户满意度模板化表单与年度汇总导出、费用统计增强、路由加载自愈

---
 src/views/CNAS/personnel/personnelInfo/index.vue                               |   11 
 src/views/CNAS/systemManagement/customerSatisfaction/components/formDialog.vue |  350 +++++++++++++++-----------
 src/permission.js                                                              |    6 
 src/views/CNAS/systemManagement/customerSatisfaction/index.vue                 |  112 +++++++-
 src/store/modules/permission.js                                                |    3 
 src/views/business/costStatistics/index.vue                                    |  156 +++++++++++
 src/views/CNAS/personnel/personnelInfo/tabs/personnel-capacity.vue             |   63 ----
 src/api/cnas/systemManagement/customerSatisfaction.js                          |   10 
 src/api/business/insOrderRates.js                                              |    8 
 src/utils/routeLoad.js                                                         |   47 +++
 10 files changed, 513 insertions(+), 253 deletions(-)

diff --git a/src/api/business/insOrderRates.js b/src/api/business/insOrderRates.js
index abe50f5..566b8f9 100644
--- a/src/api/business/insOrderRates.js
+++ b/src/api/business/insOrderRates.js
@@ -17,3 +17,11 @@
     params: query
   })
 }
+// 鏇存柊璐圭敤浼樻儬涓庡娉�
+export function updateOrderFee(data) {
+  return request({
+    url: '/insOrderRates/updateFee',
+    method: 'post',
+    data: data
+  })
+}
diff --git a/src/api/cnas/systemManagement/customerSatisfaction.js b/src/api/cnas/systemManagement/customerSatisfaction.js
index 5368a4c..e452d95 100644
--- a/src/api/cnas/systemManagement/customerSatisfaction.js
+++ b/src/api/cnas/systemManagement/customerSatisfaction.js
@@ -54,3 +54,13 @@
     data: data,
   });
 }
+
+// 瀵煎嚭瀹㈡埛婊℃剰搴﹀勾搴︽眹鎬�
+export function exportAnnualSummary(query) {
+  return request({
+    url: "/clientSatisfaction/exportAnnualSummary",
+    method: "get",
+    params: query,
+    responseType: "blob",
+  });
+}
diff --git a/src/permission.js b/src/permission.js
index 983a66e..c57e6f0 100644
--- a/src/permission.js
+++ b/src/permission.js
@@ -5,6 +5,7 @@
 import { getToken } from '@/utils/auth'
 import { isHttp, isPathMatch } from '@/utils/validate'
 import { isRelogin } from '@/utils/request'
+import { isChunkLoadError, autoReloadForChunkError } from '@/utils/routeLoad'
 import useUserStore from '@/store/modules/user'
 import useLockStore from '@/store/modules/lock'
 import useSettingsStore from '@/store/modules/settings'
@@ -78,6 +79,11 @@
 
 router.onError(error => {
   NProgress.done()
+  // 椤甸潰浠g爜妯″潡鍔犺浇澶辫触锛圴ite 閲嶅惎/閲嶇紪璇戙�佸彂鐗堝悗鏃у垎鍖咃級鑷姩鍒锋柊鎭㈠
+  if (isChunkLoadError(error) && autoReloadForChunkError()) {
+    console.warn('[router] 椤甸潰妯″潡鍔犺浇澶辫触锛岃嚜鍔ㄥ埛鏂伴噸璇曪細', error)
+    return
+  }
   console.error('[router] 椤甸潰鍔犺浇澶辫触锛�', error)
   ElMessage.error('椤甸潰鍔犺浇澶辫触锛岃鍒锋柊椤甸潰鍚庨噸璇�')
 })
diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js
index 0263689..39ca40e 100644
--- a/src/store/modules/permission.js
+++ b/src/store/modules/permission.js
@@ -4,6 +4,7 @@
 import Layout from '@/layout/index'
 import ParentView from '@/components/ParentView'
 import InnerLink from '@/layout/components/InnerLink'
+import { importWithRetry } from '@/utils/routeLoad'
 
 // 鍖归厤views閲岄潰鎵�鏈夌殑.vue鏂囦欢
 const modules = import.meta.glob('./../../views/**/*.vue')
@@ -166,7 +167,7 @@
   for (const path in modules) {
     const dir = path.split('views/')[1].split('.vue')[0]
     if (dir === view || dir === `${view}/index`) {
-      res = () => modules[path]()
+      res = () => importWithRetry(modules[path])
     }
   }
   return res
diff --git a/src/utils/routeLoad.js b/src/utils/routeLoad.js
new file mode 100644
index 0000000..c2d3309
--- /dev/null
+++ b/src/utils/routeLoad.js
@@ -0,0 +1,47 @@
+// 璺敱椤甸潰浠g爜妯″潡鍔犺浇澶辫触鐨勮嚜鎰堝鐞嗐��
+// Vite 閲嶅惎/渚濊禆棰勬瀯寤哄埛鏂般�佸彂甯冨悗鏃у垎鍖呭け鏁堟椂锛岄〉闈㈤娆� import 浼氬け璐ワ紝
+// 绋嶅悗閲嶈瘯鎴栨暣椤靛埛鏂板嵆鍙仮澶嶏紝涓嶉渶瑕佺敤鎴锋墜鍔ㄦ寜 F5銆�
+const CHUNK_LOAD_ERROR = /Failed to fetch dynamically imported module|Importing a module script failed|Loading chunk .* failed|Outdated Optimize Dep/i
+
+export function isChunkLoadError(error) {
+  if (!error) return false
+  return CHUNK_LOAD_ERROR.test(error.message || String(error))
+}
+
+export async function importWithRetry(loader, retries = 2, delay = 500) {
+  for (let attempt = 0; ; attempt++) {
+    try {
+      return await loader()
+    } catch (error) {
+      if (attempt >= retries || !isChunkLoadError(error)) throw error
+      await new Promise(resolve => setTimeout(resolve, delay * 2 ** attempt))
+    }
+  }
+}
+
+const RELOAD_STATE_KEY = 'lims-chunk-load-reload'
+const RELOAD_WINDOW = 60 * 1000
+const RELOAD_LIMIT = 3
+
+// 閲嶈瘯鍚庝粛澶辫触鍒欒嚜鍔ㄦ暣椤靛埛鏂帮紱60 绉掑唴鏈�澶� 3 娆★紝閬垮厤鐪熸鏁呴殰鏃舵棤闄愬埛鏂般��
+export function autoReloadForChunkError() {
+  const now = Date.now()
+  let state = null
+  try {
+    state = JSON.parse(sessionStorage.getItem(RELOAD_STATE_KEY) || 'null')
+  } catch (e) {
+    state = null
+  }
+  if (!state || now - state.first > RELOAD_WINDOW) {
+    state = { first: now, count: 0 }
+  }
+  if (state.count >= RELOAD_LIMIT) return false
+  state.count += 1
+  try {
+    sessionStorage.setItem(RELOAD_STATE_KEY, JSON.stringify(state))
+  } catch (e) {
+    // 闅愮妯″紡绛夊瓨鍌ㄤ笉鍙敤鏃跺拷鐣ワ紝涓嶅奖鍝嶅埛鏂�
+  }
+  setTimeout(() => window.location.reload(), 500)
+  return true
+}
diff --git a/src/views/CNAS/personnel/personnelInfo/index.vue b/src/views/CNAS/personnel/personnelInfo/index.vue
index 08796a5..f373685 100644
--- a/src/views/CNAS/personnel/personnelInfo/index.vue
+++ b/src/views/CNAS/personnel/personnelInfo/index.vue
@@ -64,10 +64,6 @@
                                 :departId="departId"
                                 :isDepartment="isDepartment"></job-responsibilities>
         </el-tab-pane>
-        <el-tab-pane label="濂栨儵璁板綍" name="濂栨儵璁板綍">
-          <rewardPunishmentRecord v-if="activeName === '濂栨儵璁板綍'"
-                                  :departId="departId" :isDepartment="isDepartment"></rewardPunishmentRecord>
-        </el-tab-pane>
         <el-tab-pane label="鍩硅璁板綍" name="鍩硅璁板綍">
           <training-record v-if="activeName === '鍩硅璁板綍'" ref="trainingRecord"
                            :departId="departId"
@@ -80,9 +76,6 @@
           <personnel-capacity v-if="activeName === '浜哄憳鑳藉姏'" ref="personnelCapacity"
                               :departId="departId"
                               :isDepartment="isDepartment"></personnel-capacity>
-        </el-tab-pane>
-        <el-tab-pane label="娌熼�氳褰�" name="娌熼�氳褰�">
-          <Communicate v-if="activeName === '娌熼�氳褰�'" ref="communicateRef" :departId="departId" :isDepartment="isDepartment"></Communicate>
         </el-tab-pane>
       </el-tabs>
     </div>
@@ -110,16 +103,14 @@
 import personnelInformation from './tabs/personnel-information.vue'
 import PersonnelTraining from './tabs/personnelTraining';
 import JobResponsibilities from './tabs/job-responsibilities.vue';
-import rewardPunishmentRecord from "./tabs/reward-punishment-record.vue";
 import TrainingRecord from './tabs/training-record.vue';
 import Mandate from './tabs/mandate.vue';
 import PersonnelCapacity from './tabs/personnel-capacity.vue';
-import Communicate from './tabs/communicate.vue'
 import {addDepartmentLims, delDepartmentLims, selectCNSAPersonTree} from "@/api/cnas/personal/personalList";
 export default {
   name: 'PersonnelInfo',
   components: {
-    PersonnelList, personnelInformation, PersonnelTraining, JobResponsibilities, rewardPunishmentRecord, TrainingRecord, Mandate, PersonnelCapacity, Communicate
+    PersonnelList, personnelInformation, PersonnelTraining, JobResponsibilities, TrainingRecord, Mandate, PersonnelCapacity
   },
   data() {
     return {
diff --git a/src/views/CNAS/personnel/personnelInfo/tabs/personnel-capacity.vue b/src/views/CNAS/personnel/personnelInfo/tabs/personnel-capacity.vue
index 3734426..76a7efc 100644
--- a/src/views/CNAS/personnel/personnelInfo/tabs/personnel-capacity.vue
+++ b/src/views/CNAS/personnel/personnelInfo/tabs/personnel-capacity.vue
@@ -19,9 +19,6 @@
     <lims-table :tableData="tableData" :column="yearColumnData"
                 @pagination="pagination" :height="'calc(100vh - 20em)'"
                 :page="search" :tableLoading="yearLoading">
-      <template #jobResponsibilities="scope"><div>
-        <div v-html="changeLine(scope.row.responsibilities)"></div>
-      </div></template>
       <template #placeWorkSlot="scope"><div>
         <div v-html="changeLine(scope.row.placeWork)"></div>
       </div></template>
@@ -146,34 +143,6 @@
             <el-input v-model="form.workLicenseRemarks" :disabled="operationType === 'view' || operationType === 'confirm'" size="small"></el-input>
           </el-form-item>
         </el-col>
-        <el-col :span="8">
-          <el-form-item label="宀椾綅鑱岃矗锛�" label-width="110px" prop="jobResponsibilitiesTem" style="height: 450px">
-            <el-checkbox-group v-model="form.jobResponsibilitiesTem" :disabled="operationType === 'view' || operationType === 'confirm'"
-                               @change="selectResponsibilities">
-              <el-checkbox v-for="city in dict.type.responsibilities_list" :key="city.value" :label="city.label"
-                           :value="city.value"></el-checkbox>
-            </el-checkbox-group>
-          </el-form-item>
-        </el-col>
-        <el-col :span="8">
-          <el-form-item class="radio-group" prop="jobResponsibilitiesConformNot"
-                        style="text-align: center;height: 420px">
-            <el-radio-group v-model="form.jobResponsibilitiesConformNot" :disabled="operationType === 'view' || operationType === 'confirm'">
-              <el-radio :value="1">绗﹀悎</el-radio>
-              <el-radio :value="2">涓嶇鍚�</el-radio>
-              <el-radio :value="3">涓嶉�傜敤</el-radio>
-            </el-radio-group>
-          </el-form-item>
-        </el-col>
-        <el-col :span="8">
-          <el-form-item prop="jobResponsibilitiesRemarks" style="height: 420px">
-            <el-input v-model="form.jobResponsibilitiesRemarks" :disabled="operationType === 'view' || operationType === 'confirm'"
-                      size="small"></el-input>
-          </el-form-item>
-        </el-col>
-        <el-col :span="24">
-          <span style="padding-left: 110px;font-size: 14px">澶囨敞锛氬矖浣嶈亴璐h揪鍒�5椤瑰強5椤逛互涓婁负绗﹀悎</span>
-        </el-col>
         <el-col :span="16">
           <el-form-item label="缁煎悎璇勪环锛�" label-width="110px" prop="comprehensiveAssessment">
             <el-radio-group v-model="form.comprehensiveAssessment" :disabled="operationType === 'view' || operationType === 'confirm'">
@@ -247,7 +216,6 @@
   computed: {
     ...mapGetters(["userId"]),
   },
-  dicts: ['responsibilities_list'],
   data() {
     return {
       userName: '',
@@ -280,12 +248,6 @@
           label: '鑱岀О',
           prop: 'professionalTitle',
           width: '100'
-        }, {
-          dataType: 'slot',
-          label: '宀椾綅鑱岃矗',
-          prop: 'jobResponsibilities',
-          width: '400',
-          slot: 'jobResponsibilities'
         }, {
           dataType: 'tag',
           label: '缁煎悎璇勪环',
@@ -384,9 +346,7 @@
       yearLoading: false,
       dialogVisible: false,
       verifyGetId: '',
-      form: {
-        jobResponsibilitiesTem: []
-      },
+      form: {},
       responsibleOptions: [],
       rules: {
         confirmOperatingPersonnelId: [{ required: true, message: '璇烽�夋嫨纭浜�', trigger: 'change' }],
@@ -402,8 +362,6 @@
         relevantExperienceConformNot: [{ required: true, message: '璇烽�夋嫨鐩稿叧缁忛獙绗﹀悎涓庡惁', trigger: 'change' }],
         workLicense: [{ required: true, message: '璇疯緭鍏ヤ笂宀楄瘉鏌ユ牳缁撴灉', trigger: 'blur' }],
         workLicenseConformNot: [{ required: true, message: '璇烽�夋嫨涓婂矖璇佺鍚堜笌鍚�', trigger: 'change' }],
-        jobResponsibilitiesTem: [{ required: true, message: '璇烽�夋嫨宀椾綅鑱岃矗鏌ユ牳缁撴灉', trigger: 'change' }],
-        jobResponsibilitiesConformNot: [{ required: true, message: '璇烽�夋嫨宀椾綅鑱岃矗绗﹀悎涓庡惁', trigger: 'change' }],
         comprehensiveAssessment: [{ required: true, message: '璇烽�夋嫨缁煎悎璇勪环', trigger: 'change' }]
       },
       responsibilities: []
@@ -454,16 +412,6 @@
       this.search.size = page.limit
       this.refreshTable()
     },
-    selectResponsibilities(arr) {
-      let arrTem = [];
-      arr.map(val => {
-        const index = this.dict.type.responsibilities_list.findIndex(item => item.label === val);
-        if (index > -1) {
-          arrTem.push(this.dict.type.responsibilities_list[index].value);
-        }
-      });
-      this.form.jobResponsibilities = arrTem.join(',');
-    },
     changeLine (val) {
       if(val) {
         return val.replace(/,/g, '<br>')
@@ -476,9 +424,7 @@
       this.operationType = type;
       this.title = '鏂板鑳藉姏璁ゅ畾'
       this.dialogVisible = true;
-      this.form = {
-        jobResponsibilitiesTem: []
-      }
+      this.form = {}
       this.form.userId = this.departId
       this.getUserList();
 
@@ -490,11 +436,6 @@
       this.title = this.operationType === 'edit' ? '淇敼鑳藉姏璁ゅ畾' : '鏌ョ湅鑳藉姏璁ゅ畾'
       this.dialogVisible = true;
       this.form = {...row}
-      if(this.form.responsibilities) {
-        this.form['jobResponsibilitiesTem'] = this.form.responsibilities.split(",")
-      } else {
-        this.form['jobResponsibilitiesTem'] = []
-      }
       this.getUserList();
     },
     // 淇濆瓨
diff --git a/src/views/CNAS/systemManagement/customerSatisfaction/components/formDialog.vue b/src/views/CNAS/systemManagement/customerSatisfaction/components/formDialog.vue
index b128c19..598a850 100644
--- a/src/views/CNAS/systemManagement/customerSatisfaction/components/formDialog.vue
+++ b/src/views/CNAS/systemManagement/customerSatisfaction/components/formDialog.vue
@@ -2,29 +2,31 @@
   <div>
     <el-dialog :close-on-click-modal="false" :close-on-press-escape="false" v-model="formDia" title="瀹㈡埛婊℃剰搴﹁皟鏌ヨ〃"
       width="70%" @close="closeFormDia">
+      <div class="survey-intro">
+        灏婃暚鐨勫鎴凤細涓轰簡浣挎湰瀹為獙瀹ょ殑鏈嶅姟姘村钩寰椾互涓嶆柇鎻愰珮锛岀壒璁炬瀹㈡埛婊℃剰搴﹁皟鏌ヨ〃锛岀儲璇锋偍鎷ㄥ啑濉啓銆傛垜浠崄鍒嗘敞閲嶆偍鐨勫弽棣堬紝鎮ㄧ殑鎰忚鍜屽缓璁紝灏嗕负鏈疄楠屽鐨勬寔缁敼杩涙彁渚涢噸瑕佸弬鑰冿紝浠庤�岃揪鍒版偍鏈熸湜鐨勬按骞炽�傝浠ヤ紶鐪熸垨閭欢鏂瑰紡灏嗘琛ㄥ洖澶嶆湰瀹為獙瀹わ紝澶氳阿鍚堜綔锛佸湪姝ら潪甯告劅璋㈡偍瀵规垜浠伐浣滅殑鏀寔锛�
+      </div>
       <table border="1" cellspacing="10" class="tables">
+        <colgroup>
+          <col style="width: 12%" />
+          <col style="width: 12%" />
+          <col style="width: 12%" />
+          <col style="width: 10.66%" />
+          <col style="width: 10.66%" />
+          <col style="width: 10.66%" />
+          <col style="width: 10.66%" />
+          <col style="width: 10.66%" />
+          <col style="width: 10.66%" />
+        </colgroup>
         <tr>
-          <td class="td-title">
-            <p>鍗曚綅鍚嶇О锛�</p>
-          </td>
-          <td class="td-info" colspan="2">
-            <el-input v-model="form.unitName" placeholder="璇疯緭鍏ュ唴瀹�" size="small">
-            </el-input>
-          </td>
-          <td class="td-title">
-            <p>鏃ユ湡锛�</p>
-          </td>
-          <td class="td-info" colspan="2">
-            <el-date-picker v-model="form.fillDate" format="YYYY-MM-DD" placeholder="閫夋嫨鏃ユ湡" size="small" type="date"
-              value-format="YYYY-MM-DD">
-            </el-date-picker>
+          <td class="section-title" colspan="9">
+            <p>瀹� 鎴� 濉� 鍐�</p>
           </td>
         </tr>
         <tr>
           <td class="td-title">
             <p>瀹㈡埛锛�</p>
           </td>
-          <td class="td-info">
+          <td class="td-info" colspan="8">
             <el-select v-model="form.orderId" filterable clearable size="small" style="width: 100%"
               popper-class="customer-select-popper" placeholder="璇烽�夋嫨瀹㈡埛锛堝凡瀹屾垚濮旀墭鍗曪級" @change="onCustomerChange">
               <el-option v-for="opt in customerOptions" :key="opt.id" :label="opt.company" :value="opt.id">
@@ -36,111 +38,114 @@
               </el-option>
             </el-select>
           </td>
+        </tr>
+        <tr>
           <td class="td-title">
-            <p>閮ㄩ棬锛�</p>
+            <p>鍦板潃锛�</p>
           </td>
-          <td class="td-info">
-            <el-input v-model="form.department" placeholder="璇疯緭鍏ュ唴瀹�" size="small">
-            </el-input>
-          </td>
-          <td class="td-title">
-            <p>鑱旂郴鐢佃瘽锛�</p>
-          </td>
-          <td class="td-info">
-            <el-input v-model="form.contactNumber" placeholder="璇疯緭鍏ュ唴瀹�" size="small">
+          <td class="td-info" colspan="8">
+            <el-input v-model="form.address" placeholder="璇疯緭鍏ュ唴瀹�" size="small">
             </el-input>
           </td>
         </tr>
         <tr>
           <td class="td-title">
-            <p>鏈嶅姟鎬佸害锛�</p>
+            <p>鑱旂郴浜猴細</p>
           </td>
-          <td class="td-info" colspan="2">
-            <el-radio-group v-model="form.serviceAttitude">
-              <el-radio :value="0">婊℃剰</el-radio>
-              <el-radio :value="1">涓�鑸�</el-radio>
-              <el-radio :value="2">涓嶆弧鎰�</el-radio>
-            </el-radio-group>
+          <td class="td-info" colspan="3">
+            <el-input v-model="form.contactPerson" placeholder="璇疯緭鍏ュ唴瀹�" size="small">
+            </el-input>
           </td>
           <td class="td-title">
-            <p>寤鸿锛�</p>
+            <p>濉〃鏃ユ湡锛�</p>
           </td>
-          <td class="td-info" colspan="2">
-            <el-input v-model="form.serviceAttitudeSuggestion" placeholder="璇疯緭鍏ュ唴瀹�" size="small">
-            </el-input>
+          <td class="td-info" colspan="4">
+            <el-date-picker v-model="form.fillDate" format="YYYY-MM-DD" placeholder="閫夋嫨鏃ユ湡" size="small" type="date"
+              value-format="YYYY-MM-DD" style="width: 100%">
+            </el-date-picker>
           </td>
         </tr>
         <tr>
-          <td class="td-title">
-            <p>鎶�鏈兘鍔涳細</p>
+          <td class="td-title" colspan="3">
+            <p>璇勫垎椤�</p>
           </td>
-          <td class="td-info" colspan="2">
-            <el-radio-group v-model="form.technicalCompetence">
-              <el-radio :value="0">婊℃剰</el-radio>
-              <el-radio :value="1">涓�鑸�</el-radio>
-              <el-radio :value="2">涓嶆弧鎰�</el-radio>
-            </el-radio-group>
+          <td class="score-head" colspan="2">浼樼</td>
+          <td class="score-head" colspan="2">涓�鑸�</td>
+          <td class="score-head" colspan="2">宸�</td>
+        </tr>
+        <tr>
+          <td class="td-title" colspan="3">
+            <p>锛堝垎鍊硷細10鈥�5鍒嗭級</p>
           </td>
-          <td class="td-title">
-            <p>寤鸿锛�</p>
+          <td class="score-head" v-for="s in scoreOptions" :key="'head-' + s">{{ s }}</td>
+        </tr>
+        <tr v-for="(q, qi) in questions" :key="q.key">
+          <td class="td-title question-title" colspan="3">
+            <p>{{ q.text }}</p>
           </td>
-          <td class="td-info" colspan="2">
-            <el-input v-model="form.technicalCompetenceSuggestion" placeholder="璇疯緭鍏ュ唴瀹�" size="small">
-            </el-input>
+          <td class="score-cell" v-for="s in scoreOptions" :key="s">
+            <el-radio v-model="form[q.key]" :value="s" class="score-radio"></el-radio>
           </td>
         </tr>
         <tr>
-          <td class="td-title">
-            <p>妫�娴嬪伐浣滐細</p>
+          <td class="td-title" colspan="3">
+            <p>*鎮ㄨ涓烘湰瀹為獙瀹よ繕鏈夊摢浜涗笉瓒筹紝鎴栧鎴戜滑鐨勫伐浣滆繕鏈夊摢浜涙剰瑙佸拰寤鸿锛熻璇存槑锛�</p>
           </td>
-          <td class="td-info" colspan="2">
-            <el-radio-group v-model="form.inspectionWork">
-              <el-radio :value="0">婊℃剰</el-radio>
-              <el-radio :value="1">涓�鑸�</el-radio>
-              <el-radio :value="2">涓嶆弧鎰�</el-radio>
-            </el-radio-group>
-          </td>
-          <td class="td-title">
-            <p>寤鸿锛�</p>
-          </td>
-          <td class="td-info" colspan="2">
-            <el-input v-model="form.inspectionWorkSuggestion" placeholder="璇疯緭鍏ュ唴瀹�" size="small">
-            </el-input>
-          </td>
-        </tr>
-        <tr>
-          <td class="td-title">
-            <p>鏀惰垂鍚堢悊鎬э細</p>
-          </td>
-          <td class="td-info" colspan="2">
-            <el-radio-group v-model="form.reasonableFees">
-              <el-radio :value="0">婊℃剰</el-radio>
-              <el-radio :value="1">涓�鑸�</el-radio>
-              <el-radio :value="2">涓嶆弧鎰�</el-radio>
-            </el-radio-group>
-          </td>
-          <td class="td-title">
-            <p>寤鸿锛�</p>
-          </td>
-          <td class="td-info" colspan="2">
-            <el-input v-model="form.reasonableFeesSuggestion" placeholder="璇疯緭鍏ュ唴瀹�" size="small">
-            </el-input>
-          </td>
-        </tr>
-        <tr>
-          <td class="td-title">
-            <p>鎮ㄥ鎴戜滑鐨勫笇鏈涳細</p>
-          </td>
-          <td class="td-info" colspan="5">
+          <td class="td-info" colspan="6">
             <el-input v-model="form.remark" :rows="4" placeholder="璇疯緭鍏ュ唴瀹�" size="small" type="textarea">
             </el-input>
+          </td>
+        </tr>
+        <tr>
+          <td class="section-title" colspan="9">
+            <p>瀹� 楠� 瀹� 濉� 鍐�</p>
+          </td>
+        </tr>
+        <tr>
+          <td class="td-title">
+            <p>鎬诲垎锛�</p>
+          </td>
+          <td class="td-info">
+            <span class="score-result">{{ scoreComplete ? totalScore : '--' }}</span> 鍒�
+          </td>
+          <td class="td-title" colspan="2">
+            <p>婊℃剰搴﹁瘎瀹氾細</p>
+          </td>
+          <td class="td-info" colspan="5">
+            {{ scoreComplete ? satisfactionText : '濉啓璇勫垎鍚庤嚜鍔ㄨ绠�' }}
+          </td>
+        </tr>
+        <tr>
+          <td class="td-title">
+            <p>鎰忚锛�</p>
+          </td>
+          <td class="td-info" colspan="8">
+            <el-input v-model="form.labOpinion" :rows="3" placeholder="璇疯緭鍏ュ唴瀹�" size="small" type="textarea">
+            </el-input>
+          </td>
+        </tr>
+        <tr>
+          <td class="td-title">
+            <p>璐ㄩ噺璐熻矗浜猴細</p>
+          </td>
+          <td class="td-info" colspan="3">
+            <el-input v-model="form.qualityManager" placeholder="璇疯緭鍏ュ唴瀹�" size="small">
+            </el-input>
+          </td>
+          <td class="td-title">
+            <p>鏃ユ湡锛�</p>
+          </td>
+          <td class="td-info" colspan="4">
+            <el-date-picker v-model="form.labDate" format="YYYY-MM-DD" placeholder="閫夋嫨鏃ユ湡" size="small" type="date"
+              value-format="YYYY-MM-DD" style="width: 100%">
+            </el-date-picker>
           </td>
         </tr>
         <tr>
           <td class="td-title">
             <p>鐓х墖锛�</p>
           </td>
-          <td class="td-info" colspan="5">
+          <td class="td-info" colspan="8">
             <lims-attachment ref="photoAttachment" inline defer-upload accept=".jpg,.jpeg,.png,.gif"
               :main-id="form.clientSatisfactionId || ''" upload-url="/clientSatisfaction/uploadAttachment"
               select-url="/clientSatisfaction/selectAttachments" delete-url="/clientSatisfaction/deleteAttachment"
@@ -148,7 +153,7 @@
           </td>
         </tr>
       </table>
-      <template #footer><span  class="dialog-footer">
+      <template #footer><span class="dialog-footer">
         <el-button @click="closeFormDia">鍙� 娑�</el-button>
         <el-button :loading="editLoad" type="primary" @click="handleEdit">鎻� 浜�</el-button>
       </span></template>
@@ -163,49 +168,80 @@
 } from '@/api/cnas/systemManagement/customerSatisfaction.js'
 import { selectCompletedOrders } from '@/api/business/productOrder'
 import limsAttachment from '@/components/Attachment/lims-attachment.vue'
+
+const QUESTIONS = [
+  { key: 'score1', text: '1.涓氬姟/鎺ュ緟浜哄憳鐨勬湇鍔℃�佸害' },
+  { key: 'score2', text: '2.涓氬姟/鎺ュ緟浜哄憳鐨勬矡閫氳兘鍔�' },
+  { key: 'score3', text: '3.涓氬姟/鎺ュ緟浜哄憳鐨勫搷搴旈�熷害鎴栧洖澶嶅強鏃舵��' },
+  { key: 'score4', text: '4.鍑哄叿妫�娴嬫姤鍛婄殑鍙婃椂鎬�' },
+  { key: 'score5', text: '5.妫�娴嬬粨鏋滅殑鍑嗙‘鎬�' },
+  { key: 'score6', text: '6.妫�娴嬫姤鍛婂唴瀹硅〃杈炬竻鏅帮紝缂栨帓鍚堢悊骞舵槗浜庣悊瑙�' },
+  { key: 'score7', text: '7.澶勭悊鎶曡瘔鎶辨�ㄧ殑鍙婃椂鎬�' },
+  { key: 'score8', text: '8.瀵规姇璇夋姳鎬ㄥ鐞嗙殑缁撴灉' },
+  { key: 'score9', text: '9.瀵规垜鍙告湇鍔$殑涓撲笟鎬ц瘎浠�' },
+  { key: 'score10', text: '10.瀵规垜鍙告湇鍔$殑鎬讳綋婊℃剰绋嬪害璇勪环' }
+]
+
+function createEmptyForm() {
+  const form = {
+    clientSatisfactionId: '',
+    orderId: '',
+    unitName: '',
+    userName: '',
+    address: '',
+    contactPerson: '',
+    fillDate: '',
+    remark: '',
+    labOpinion: '',
+    qualityManager: '',
+    labDate: null,
+    entrustCode: '',
+    sampleName: ''
+  }
+  QUESTIONS.forEach(q => {
+    form[q.key] = ''
+  })
+  return form
+}
+
 export default {
   name: 'formDialog',
-  // import 寮曞叆鐨勭粍浠堕渶瑕佹敞鍏ュ埌瀵硅薄涓墠鑳戒娇鐢�
   components: { limsAttachment },
   data() {
-    // 杩欓噷瀛樻斁鏁版嵁
     return {
       formDia: false,
-      form: {
-        unitName: '',
-        fillDate: '',
-        userName: '',
-        department: '',
-        contactNumber: '',
-        serviceAttitude: '',
-        serviceAttitudeSuggestion: '',
-        technicalCompetence: '',
-        technicalCompetenceSuggestion: '',
-        inspectionWork: '',
-        inspectionWorkSuggestion: '',
-        reasonableFees: '',
-        reasonableFeesSuggestion: '',
-        remark: '',
-        clientSatisfactionId: '',
-        orderId: '',
-        entrustCode: '',
-        sampleName: ''
-      },
-      // 瀹㈡埛涓嬫媺閫夐」锛氬彧鍖呭惈"宸蹭笅杩囧鎵樺崟涓斿凡瀹屾垚"鐨勬暟鎹�
+      form: createEmptyForm(),
+      questions: QUESTIONS,
+      scoreOptions: [10, 9, 8, 7, 6, 5],
       customerOptions: [],
       operationType: '',
       editLoad: false,
-
     };
   },
-  // 鏂规硶闆嗗悎
+  computed: {
+    scoreComplete() {
+      return this.questions.every(q => this.form[q.key] !== '' && this.form[q.key] !== null && this.form[q.key] !== undefined)
+    },
+    totalScore() {
+      return this.questions.reduce((sum, q) => sum + (Number(this.form[q.key]) || 0), 0)
+    },
+    satisfactionText() {
+      if (!this.scoreComplete) {
+        return ''
+      }
+      return this.totalScore >= 80 ? '婊℃剰锛�80鍒嗭紙鍚級浠ヤ笂锛�' : '涓嶆弧鎰忥紙80鍒嗕互涓嬶級'
+    }
+  },
   methods: {
     async openDia(type, row) {
       this.formDia = true;
       this.operationType = type
       await this.loadCustomerOptions()
       if (this.operationType === 'edit') {
-        this.form = { ...row }
+        this.form = { ...createEmptyForm(), ...row }
+        if (!this.form.unitName) {
+          this.form.unitName = this.form.userName || ''
+        }
         // 鑰佹暟鎹病鏈夊叧鑱斿鎵樺崟锛氬悎鎴愪竴涓�夐」鎶婂師鏉ョ殑鍊煎洖鏄惧嚭鏉ワ紝涓嶅己鍒堕噸閫�
         if (row.orderId == null && row.userName) {
           this.customerOptions.unshift({
@@ -229,26 +265,7 @@
       }
     },
     resetForm() {
-      this.form = {
-        unitName: '',
-        fillDate: '',
-        userName: '',
-        department: '',
-        contactNumber: '',
-        serviceAttitude: '',
-        serviceAttitudeSuggestion: '',
-        technicalCompetence: '',
-        technicalCompetenceSuggestion: '',
-        inspectionWork: '',
-        inspectionWorkSuggestion: '',
-        reasonableFees: '',
-        reasonableFeesSuggestion: '',
-        remark: '',
-        clientSatisfactionId: '',
-        orderId: '',
-        entrustCode: '',
-        sampleName: ''
-      }
+      this.form = createEmptyForm()
     },
     // 涓嬫媺鏁版嵁锛氬悗绔彧杩斿洖"宸叉楠�"鐨勫鎵樺崟锛坰tate=4锛�
     loadCustomerOptions() {
@@ -283,13 +300,16 @@
         this.$message.warning('璇烽�夋嫨瀹㈡埛')
         return
       }
-      if (!this.form.unitName) {
-        this.$message.warning('璇峰~鍐欏崟浣嶅悕绉�')
+      if (!this.form.fillDate) {
+        this.$message.warning('璇烽�夋嫨濉〃鏃ユ湡')
         return
       }
-      if (!this.form.department) {
-        this.$message.warning('璇峰~鍐欓儴闂�')
-        return
+      for (let i = 0; i < this.questions.length; i++) {
+        const score = this.form[this.questions[i].key]
+        if (score === '' || score === null || score === undefined) {
+          this.$message.warning('璇峰畬鎴愮' + (i + 1) + '棰樿瘎鍒�')
+          return
+        }
       }
       this.editLoad = true
       const payload = { ...this.form }
@@ -351,15 +371,30 @@
   margin: 10vh auto 50px !important;
 }
 
+.survey-intro {
+  margin-top: 10px;
+  font-size: 13px;
+  line-height: 22px;
+  color: #303133;
+}
+
 .tables {
   table-layout: fixed;
   width: 100%;
   margin-top: 10px;
 }
 
+.section-title {
+  height: 34px;
+  text-align: center;
+  font-size: 14px;
+  font-weight: bold;
+  background: #f5f7fa;
+  padding: 6px;
+}
+
 .td-title {
   height: 40px;
-  width: 170px;
   text-align: center;
   font-size: 14px;
   word-wrap: break-word;
@@ -367,17 +402,32 @@
   padding: 6px;
 }
 
+.question-title {
+  text-align: left;
+}
+
 .td-info {
   padding: 6px;
 }
 
-.td-info1 {
-  display: inline-block;
-  width: 100%;
-  text-align: left;
+.score-head {
+  text-align: center;
   font-size: 14px;
-  word-wrap: break-word;
-  white-space: normal;
+  padding: 6px;
+}
+
+.score-cell {
+  text-align: center;
+  height: 38px;
+}
+
+.score-cell :deep(.el-radio) {
+  margin-right: 0;
+}
+
+.score-result {
+  font-weight: bold;
+  color: #409eff;
 }
 
 .customer-card-sub {
diff --git a/src/views/CNAS/systemManagement/customerSatisfaction/index.vue b/src/views/CNAS/systemManagement/customerSatisfaction/index.vue
index f891e46..200c674 100644
--- a/src/views/CNAS/systemManagement/customerSatisfaction/index.vue
+++ b/src/views/CNAS/systemManagement/customerSatisfaction/index.vue
@@ -4,7 +4,7 @@
       <div class="search">
         <div>
           <el-form :model="searchForm" ref="searchForm" size="small" :inline="true">
-            <el-form-item label="鍗曚綅鍚嶇О" prop="unitName" v-if="tabIndex === '0'">
+            <el-form-item label="瀹㈡埛鍚嶇О" prop="unitName" v-if="tabIndex === '0'">
               <el-input v-model="searchForm.unitName" clearable size="small"></el-input>
             </el-form-item>
             <el-form-item label="鏂囦欢鍚嶇О" prop="fileName" v-if="tabIndex === '1'">
@@ -18,6 +18,7 @@
         </div>
         <div>
           <el-button v-if="tabIndex === '0'" size="small" type="primary" @click="openFormDia('add')">鏂� 澧�</el-button>
+          <el-button v-if="tabIndex === '0'" size="small" type="primary" @click="openExportDialog">瀵� 鍑�</el-button>
           <el-upload v-if="tabIndex === '1'" ref='upload' :action="action" :before-upload="beforeUpload"
                      :headers="uploadHeader" :on-error="onError" :on-success="handleSuccessUp" :show-file-list="false"
                      accept='.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar'>
@@ -49,6 +50,18 @@
       <filePreview v-if="lookDialogVisible" :currentFile="{}" :fileUrl="javaApi + '/word/' + currentInfo.fileUrl"
         style="height: 90vh;overflow-y: auto;" />
     </el-dialog>
+    <el-dialog v-model="exportDialogVisible" title="瀵煎嚭骞村害姹囨��" width="360px" :close-on-click-modal="false">
+      <el-form label-width="80px">
+        <el-form-item label="姹囨�诲勾搴�">
+          <el-date-picker v-model="exportYear" type="year" format="YYYY骞�" value-format="YYYY" placeholder="閫夋嫨骞村害"
+            style="width: 100%"></el-date-picker>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="exportDialogVisible = false">鍙� 娑�</el-button>
+        <el-button type="primary" :loading="exportLoading" @click="handleExport">瀵� 鍑�</el-button>
+      </template>
+    </el-dialog>
     <FormDialog v-if="formDialog" ref="formDialog" @closeFormDia="closeFormDia"></FormDialog>
     <lims-attachment ref="attachmentDialog" title="婊℃剰搴﹂檮浠�" name-field="unitName" id-param="clientSatisfactionId"
       upload-url="/clientSatisfaction/uploadAttachment" select-url="/clientSatisfaction/selectAttachments"
@@ -66,6 +79,7 @@
   pageAnalyseFile,
   delClientSatisfaction,
   delAnalyseFile,
+  exportAnnualSummary,
 } from '@/api/cnas/systemManagement/customerSatisfaction.js'
 
 export default {
@@ -84,48 +98,66 @@
       tabIndex: '0',
       tableColumn: [
         {
-          label: '鍗曚綅鍚嶇О',
+          label: '瀹㈡埛鍚嶇О',
           prop: 'unitName',
+          minWidth: '140'
+        },
+        {
+          label: '鍦板潃',
+          prop: 'address',
+          minWidth: '150'
+        },
+        {
+          label: '鑱旂郴浜�',
+          prop: 'contactPerson',
           minWidth: '100'
         },
         {
-          label: '鏃ユ湡',
+          label: '濉〃鏃ユ湡',
           prop: 'fillDate',
           minWidth: '100'
         },
         {
-          label: '瀹㈡埛',
-          prop: 'userName',
-          minWidth: '100'
+          label: '鎬诲垎',
+          prop: 'totalScore',
+          minWidth: '80',
+          formatData: (val) => (val === null || val === undefined || val === '') ? '--' : val
+        },
+        {
+          label: '婊℃剰搴﹁瘎瀹�',
+          prop: 'satisfactionResult',
+          minWidth: '110',
+          dataType: 'tag',
+          formatData: (val) => {
+            if (val === null || val === undefined || val === '') {
+              return '--'
+            }
+            return Number(val) === 0 ? '婊℃剰' : '涓嶆弧鎰�'
+          },
+          formatType: (val) => {
+            if (val === null || val === undefined || val === '') {
+              return 'info'
+            }
+            return Number(val) === 0 ? 'success' : 'danger'
+          }
         },
         {
           label: '濮旀墭鍗曠紪鍙�',
           prop: 'entrustCode',
-          minWidth: '100'
+          minWidth: '110'
         },
         {
           label: '鏍峰搧鍚嶇О',
           prop: 'sampleName',
-          minWidth: '100'
-        },
-        {
-          label: '閮ㄩ棬',
-          prop: 'department',
-          minWidth: '100'
-        },
-        {
-          label: '鑱旂郴鐢佃瘽',
-          prop: 'contactNumber',
-          minWidth: '100'
+          minWidth: '120'
         },
         {
           label: '鍒涘缓鏃ユ湡',
           prop: 'createTime',
-          minWidth: '100',
+          minWidth: '160',
         },
         {
           dataType: 'action',
-          minWidth: '80',
           label: '鎿嶄綔',
           operation: [
             {
@@ -178,7 +210,6 @@
         },
         {
           dataType: 'action',
-          minWidth: '50',
           label: '鎿嶄綔',
           operation: [
             {
@@ -217,6 +248,9 @@
       upLoading: false,
       currentInfo: {},
       lookDialogVisible: false,
+      exportDialogVisible: false,
+      exportYear: String(new Date().getFullYear()),
+      exportLoading: false,
     };
   },
   mounted() {
@@ -260,6 +294,42 @@
       this.formDialog = false
       this.searchList()
     },
+    // 鎵撳紑骞村害姹囨�诲鍑哄脊绐�
+    openExportDialog() {
+      this.exportYear = String(new Date().getFullYear())
+      this.exportDialogVisible = true
+    },
+    // 瀵煎嚭骞村害姹囨�� Word
+    handleExport() {
+      if (!this.exportYear) {
+        this.$message.warning('璇烽�夋嫨姹囨�诲勾搴�')
+        return
+      }
+      this.exportLoading = true
+      exportAnnualSummary({ year: this.exportYear }).then(async data => {
+        // 鍚庣鏃犳暟鎹椂杩斿洖鐨勬槸 JSON锛岄渶杞垚鏂囨湰鎻愮ず锛岄伩鍏嶅瓨鎴愬潖鏂囦欢
+        if (data && data.type && data.type.indexOf('json') !== -1) {
+          let msg = '瀵煎嚭澶辫触'
+          try {
+            const resText = await data.text()
+            const rspObj = JSON.parse(resText)
+            msg = rspObj.msg || msg
+          } catch (e) {
+            console.log('err---', e)
+          }
+          this.exportLoading = false
+          this.$message.error(msg)
+          return
+        }
+        const blob = new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' })
+        this.$download.saveAs(blob, '瀹㈡埛婊℃剰搴﹀勾搴︽眹鎬昏〃_' + this.exportYear + '骞�.docx')
+        this.exportLoading = false
+        this.exportDialogVisible = false
+      }).catch(err => {
+        console.log('err---', err)
+        this.exportLoading = false
+      })
+    },
     // 閲嶇疆鏌ヨ鏉′欢
     resetSearchForm() {
       this.searchForm.unitName = '';
diff --git a/src/views/business/costStatistics/index.vue b/src/views/business/costStatistics/index.vue
index 19a9e4f..fac168e 100644
--- a/src/views/business/costStatistics/index.vue
+++ b/src/views/business/costStatistics/index.vue
@@ -4,6 +4,12 @@
     <div class="search">
       <div>
         <el-form :model="entity" ref="entity" size="small" :inline="true">
+          <el-form-item label="閫佹牱鏃堕棿">
+            <el-date-picker v-model="monthRange" type="monthrange" size="small"
+                            value-format="YYYY-MM" range-separator="鑷�"
+                            start-placeholder="寮�濮嬫湀浠�" end-placeholder="缁撴潫鏈堜唤"
+                            style="width: 240px" @change="refreshTable()"></el-date-picker>
+          </el-form-item>
           <el-form-item label="濮旀墭缂栧彿" prop="entrustCode">
             <el-input size="small" placeholder="璇疯緭鍏�" clearable v-model="entity.entrustCode"
                       @keyup.enter="refreshTable()"></el-input>
@@ -29,8 +35,31 @@
     </div>
     <div>
       <lims-table :tableData="tableData" :column="column"
-                  :height="'calc(100vh - 250px)'" @pagination="pagination"
-                  :page="page" :tableLoading="tableLoading"></lims-table>
+                  :height="'calc(100vh - 300px)'" @pagination="pagination"
+                  :isSelection="true" :handleSelectionChange="handleSelectionChange"
+                  :page="page" :tableLoading="tableLoading">
+        <template #isDiscount="{ row }">
+          <el-select v-model="row.isDiscount" size="small" style="width: 80px"
+                     @change="handleDiscountChange(row)">
+            <el-option label="鏃�" :value="0"></el-option>
+            <el-option label="鏈�" :value="1"></el-option>
+          </el-select>
+        </template>
+        <template #discountAmount="{ row }">
+          <el-input-number v-model="row.discountAmount" size="small" style="width: 100%"
+                           :controls="false" :precision="2" :min="0"
+                           :disabled="row.isDiscount !== 1"
+                           placeholder="璇疯緭鍏�" @change="handleAmountChange(row)"></el-input-number>
+        </template>
+        <template #feeRemark="{ row }">
+          <el-input v-model="row.feeRemark" size="small" maxlength="500"
+                    @change="handleFeeRemarkChange(row)"></el-input>
+        </template>
+      </lims-table>
+    </div>
+    <div class="sum-bar">
+      <span>宸插嬀閫� <b class="sum-count">{{ selectedRows.length }}</b> 鏉�</span>
+      <span>鎬婚噾棰濓細<b class="sum-amount">楼{{ selectedTotal }}</b></span>
     </div>
     <viewInfoDia ref="viewInfoDia" v-if="viewInfoDia"></viewInfoDia>
   </div>
@@ -39,8 +68,9 @@
 <script>
 import limsTable from "@/components/Table/lims-table.vue";
 import viewInfoDia from "./components/viewInfoDia.vue";
-import {selectRatesPage} from "@/api/business/insOrderRates";
+import {selectRatesPage, updateOrderFee} from "@/api/business/insOrderRates";
 import {selectCustomPageList} from "@/api/system/customer";
+import { ElMessage } from "element-plus";
 
 export default {
   components: {limsTable,viewInfoDia},
@@ -50,10 +80,11 @@
         sample: null,
         company: null,
       },
+      monthRange: [],
       tableData: [],
       tableLoading: false,
       column: [
-        { label: '涓嬪崟鏃堕棿', prop: 'createTime', width: 150 },
+        { label: '閫佹牱鏃堕棿', prop: 'sampleDate', width: 120 },
         { label: '濮旀墭缂栧彿', prop: 'entrustCode', width: 160 },
         { label: '鏍峰搧鍚嶇О', prop: 'sample', width: 160 },
         { label: '濮旀墭鍗曚綅', prop: 'company' },
@@ -84,8 +115,28 @@
           },
         },
         { label: '濮旀墭浜�', prop: 'prepareUser' },
-        { label: '鐢熶骇鍗曚綅', prop: 'production' },
-        { label: '鎬讳环', prop: 'totalPrice' },
+        { label: '鎬讳环', prop: 'totalPrice', width: 110 },
+        {
+          label: '鏄惁浼樻儬',
+          prop: 'isDiscount',
+          width: 100,
+          dataType: 'slot',
+          slot: 'isDiscount'
+        },
+        {
+          label: '浼樻儬鍚庨噾棰�',
+          prop: 'discountAmount',
+          width: 130,
+          dataType: 'slot',
+          slot: 'discountAmount'
+        },
+        {
+          label: '澶囨敞',
+          prop: 'feeRemark',
+          minWidth: 160,
+          dataType: 'slot',
+          slot: 'feeRemark'
+        },
         {
           dataType: "action",
           fixed: "right",
@@ -107,7 +158,19 @@
         current: 1
       },
       companyOptions: [],
-      viewInfoDia: false
+      viewInfoDia: false,
+      selectedRows: []
+    }
+  },
+  computed: {
+    // 鍕鹃�夊悎璁★細鏈変紭鎯犱笖濉簡浼樻儬鍚庨噾棰濈殑鎸変紭鎯犲悗閲戦绠楋紝鍚﹀垯鎸夋�讳环绠�
+    selectedTotal() {
+      const total = this.selectedRows.reduce((sum, row) => {
+        const useDiscount = row.isDiscount === 1 && row.discountAmount !== null && row.discountAmount !== undefined
+        const amount = Number(useDiscount ? row.discountAmount : row.totalPrice)
+        return sum + (isFinite(amount) ? amount : 0)
+      }, 0)
+      return total.toFixed(2)
     }
   },
   mounted() {
@@ -117,10 +180,15 @@
   methods :{
     refreshTable() {
       this.tableLoading = true
-      selectRatesPage({ ...this.page, ...this.entity }).then(res => {
+      const [startMonth, endMonth] = this.monthRange || []
+      selectRatesPage({ ...this.page, ...this.entity, startMonth, endMonth }).then(res => {
         this.tableLoading = false
-        this.tableData = res.data.records
+        this.tableData = (res.data.records || []).map(item => {
+          item.isDiscount = item.isDiscount === 1 ? 1 : 0
+          return item
+        })
         this.page.total = res.data.total
+        this.selectedRows = []
       }).catch(err => {
         this.tableLoading = false
       })
@@ -128,6 +196,7 @@
     // 閲嶇疆
     refresh() {
       this.resetForm('entity')
+      this.monthRange = []
       this.refreshTable()
     },
     // 鏌ヨ濮旀墭鍗曚綅涓嬫媺妗嗘暟鎹�
@@ -152,6 +221,53 @@
       this.page.size = page.limit
       this.refreshTable()
     },
+    // 鍕鹃�夎鍙樺寲
+    handleSelectionChange(selection) {
+      this.selectedRows = selection
+    },
+    // 鍒囨崲鏄惁浼樻儬
+    handleDiscountChange(row) {
+      if (row.isDiscount === 1) {
+        // 閫夆�滄湁鈥濇椂甯﹀嚭鎬讳环锛岀敤鎴峰啀鏀规垚瀹為檯浼樻儬鍚庨噾棰�
+        if (row.discountAmount === null || row.discountAmount === undefined || row.discountAmount === '') {
+          row.discountAmount = this.amountOf(row.totalPrice)
+        }
+      } else {
+        row.discountAmount = null
+      }
+      this.saveFee(row)
+    },
+    // 浼樻儬鍚庨噾棰濇敼瀹屼繚瀛�
+    handleAmountChange(row) {
+      if (row.isDiscount !== 1) {
+        row.discountAmount = null
+        return
+      }
+      if (row.discountAmount === null || row.discountAmount === undefined || row.discountAmount === '') {
+        ElMessage.warning('鏈変紭鎯犳椂璇峰~鍐欎紭鎯犲悗閲戦')
+        return
+      }
+      this.saveFee(row)
+    },
+    // 澶囨敞鏀瑰畬淇濆瓨
+    handleFeeRemarkChange(row) {
+      this.saveFee(row)
+    },
+    amountOf(value) {
+      const num = Number(value)
+      if (!isFinite(num)) return 0
+      return Math.round(num * 100) / 100
+    },
+    saveFee(row) {
+      updateOrderFee({
+        id: row.id,
+        isDiscount: row.isDiscount,
+        discountAmount: row.isDiscount === 1 ? row.discountAmount : null,
+        feeRemark: row.feeRemark || null
+      }).catch(() => {
+        this.refreshTable()
+      })
+    },
     // 鏌ョ湅璇︽儏
     openDia (row) {
       this.viewInfoDia = true
@@ -164,8 +280,28 @@
 </script>
 <style scoped>
 .search {
-  height: 46px;
+  min-height: 46px;
   display: flex;
   justify-content: space-between;
 }
+.sum-bar {
+  position: sticky;
+  bottom: 0;
+  z-index: 10;
+  display: flex;
+  justify-content: flex-end;
+  gap: 24px;
+  margin-top: 8px;
+  padding: 10px 16px;
+  background: #fff;
+  border-top: 1px solid var(--el-border-color-light);
+  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
+}
+.sum-count {
+  color: var(--el-color-primary);
+}
+.sum-amount {
+  color: var(--el-color-danger);
+  font-size: 16px;
+}
 </style>

--
Gitblit v1.9.3