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/systemManagement/customerSatisfaction/components/formDialog.vue |  350 +++++++++++++++++++++++++++++++++-------------------------
 1 files changed, 200 insertions(+), 150 deletions(-)

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 {

--
Gitblit v1.9.3