From a99e82da2b087d7019cdce015c25befc620b9d61 Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期二, 10 十一月 2026 14:14:16 +0800
Subject: [PATCH] 测试完成

---
 src/views/Satisfaction/sfstatistics/components/DataOverview.vue |  383 ++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 283 insertions(+), 100 deletions(-)

diff --git a/src/views/Satisfaction/sfstatistics/components/DataOverview.vue b/src/views/Satisfaction/sfstatistics/components/DataOverview.vue
index f189c1f..3e8f084 100644
--- a/src/views/Satisfaction/sfstatistics/components/DataOverview.vue
+++ b/src/views/Satisfaction/sfstatistics/components/DataOverview.vue
@@ -106,6 +106,13 @@
           <el-button size="small" icon="el-icon-refresh" @click="handleReset"
             >閲嶇疆</el-button
           >
+          <el-button
+            type="warning"
+            size="small"
+            icon="el-icon-download"
+            @click="exportTable()"
+            >瀵煎嚭Excel</el-button
+          >
         </el-form-item>
       </el-form>
     </el-card>
@@ -114,13 +121,7 @@
     <el-card shadow="never" class="overview-card">
       <div slot="header" class="card-header">
         <span class="card-title">鍚勬湇鍔¢」鐩弧鎰忓害缁熻</span>
-        <el-button
-          type="warning"
-          size="small"
-          icon="el-icon-download"
-          @click="exportTable('table1')"
-          >瀵煎嚭Excel</el-button
-        >
+        <span class="card-subtitle">锛堝叚妗h瘎鍒嗛锛氬緢婊℃剰/婊℃剰/涓�鑸�/涓嶆弧鎰�/寰堜笉婊℃剰/鏈粡鍘嗭級</span>
       </div>
       <el-table
         id="table1"
@@ -174,7 +175,8 @@
     <!-- 琛ㄦ牸浜岋細鍗曢�夐/澶氶�夐缁熻锛堝叾浣欓鐩級 -->
     <el-card shadow="never" class="overview-card">
       <div slot="header" class="card-header">
-        <span class="card-title">鍗曢�夐/澶氶�夐缁熻</span>
+        <span class="card-title">鍏朵粬棰樼洰缁熻</span>
+        <span class="card-subtitle">锛堝叚妗f弧鎰忓害棰樺凡鍦ㄤ笂琛紝杩欓噷鍙垪鍏朵綑棰樺瀷锛氭帹鑽愩�佹姳鎬ㄥ洖搴斻�佹剰瑙佺瓑锛�</span>
         <div class="header-right">
           <el-input
             v-model="questionSearch"
@@ -184,13 +186,6 @@
             placeholder="鎸夐鐩ā绯婃煡璇�"
             class="search-input"
           />
-          <el-button
-            type="warning"
-            size="small"
-            icon="el-icon-download"
-            @click="exportTable('table2')"
-            >瀵煎嚭Excel</el-button
-          >
         </div>
       </div>
       <div id="table2" v-loading="loading" element-loading-text="缁熻涓�...">
@@ -269,13 +264,6 @@
               />
             </template>
           </el-select>
-          <el-button
-            type="warning"
-            size="small"
-            icon="el-icon-download"
-            @click="exportTable('table3')"
-            >瀵煎嚭Excel</el-button
-          >
         </div>
       </div>
       <el-table
@@ -333,7 +321,8 @@
 
 <script>
 import { sltdMydTotalByScore,statQuestionOptionByKsOrBq } from "@/api/AiCentre/satisfaction";
-import XLSX from "xlsx";
+import { deptTreeSelect } from "@/api/system/user";
+import * as XLSX from "xlsx";
 
 // 婊℃剰搴﹂鐨勫浐瀹氶�夐」锛堥『搴忓嵆琛ㄦ牸鍒楅『搴忥級鈥斺�旇繖浜涢�夐」鐨勯鐩繘琛ㄤ竴锛屽叾浣欒繘琛ㄤ簩
 const FIXED_OPTIONS = ["寰堟弧鎰�", "婊℃剰", "涓�鑸�", "涓嶆弧鎰�", "寰堜笉婊℃剰", "鏈粡鍘�"];
@@ -360,9 +349,10 @@
 // 鏈嶅姟绫诲瀷榛樿閫変腑椤癸紙鎸� label 鍖归厤锛岄�傞厤涓嶅悓瀛楀吀鐨� value 宸紓锛�
 const DEFAULT_SERVICE_LABELS = ["浣忛櫌婊℃剰搴�", "闂ㄨ瘖婊℃剰搴�"];
 
-// 鍏滃簳锛歴tore 涓棤绉戝/鐥呭尯鏁版嵁鏃朵娇鐢紙琛ㄤ笁瀵规瘮 + 绛涢�変笅鎷夛級
-const DEPTS = ["鍐呭垎娉岀", "鐨偆绉�", "鑲跨槫鍐呯", "娑堝寲鍐呯", "鑲惧唴绉�"];
-const WARDS = ["涓�鐥呭尯", "浜岀梾鍖�", "涓夌梾鍖�", "鍥涚梾鍖�", "浜旂梾鍖�"];
+// 娉ㄦ剰锛氳繖閲屼互鍓嶇敤 DEPTS / WARDS 涓や釜銆屾紨绀虹敤銆嶅悕绉版暟缁勫厹搴曪紝
+// 缁撴灉琚綋鎴愮紪鐮佸杩� leavehospitaldistrictcodes / leaveldeptcodes锛�
+// 鍚庣鎸� leavehospitaldistrictcode IN ('涓�鐥呭尯','浜岀梾鍖�'鈥�) 杩囨护 鈫� 涓�鏉′篃鍖归厤涓嶄笂銆�
+// 绉戝/鐥呭尯涓�寰嬪彧鐢ㄧ湡瀹炵紪鐮侊細鏈夊綊灞炲彇 store锛屾棤褰掑睘锛堝绠$悊鍛橈級鍙� deptTreeSelect 瀛楀吀銆�
 
 /**
  * 鎺ュ彛鏁版嵁 鈫� 椤甸潰鍐呴儴棰樼洰缁撴瀯
@@ -537,22 +527,27 @@
       // 琛ㄤ笁锛氱瀹�/鐥呭尯璇勫垎瀵规瘮锛坮ows=琛屾暟鎹紝questions=鍔ㄦ�佸垪锛�
       deptComparison: { rows: [], questions: [] },
       wardComparison: { rows: [], questions: [] },
+
+      // 绉戝/鐥呭尯瀛楀吀锛氬綋鍓嶈处鍙锋病鏈夊綊灞炵瀹�/鐥呭尯鏃讹紙濡傜鐞嗗憳锛変粠 deptTreeSelect 鎷夊彇锛�
+      // 淇濊瘉涓嬫媺閲屾斁鐨勬槸鐪熷疄缂栫爜锛坉eptCode / districtCode锛夛紝鑰屼笉鏄悕绉�
+      deptDict: [],
+      wardDict: [],
     };
   },
   computed: {
-    // 绉戝閫夐」锛氫紭鍏堝彇褰撳墠鐢ㄦ埛鎵�灞炵瀹わ紝鏃犲垯鍏滃簳
+    // 绉戝閫夐」锛氫紭鍏堝彇褰撳墠鐢ㄦ埛鎵�灞炵瀹わ紱娌℃湁褰掑睘鏃剁敤绉戝瀛楀吀锛堢湡瀹� deptCode锛�
     deptOptions() {
-      const list = this.$store.getters.belongDepts || [];
-      return list.length
-        ? list.map((d) => ({ label: d.deptName, value: d.deptCode }))
-        : DEPTS.map((d) => ({ label: d, value: d }));
+      const list = (this.$store.getters.belongDepts || [])
+        .map((d) => ({ label: d.deptName, value: d.deptCode }))
+        .filter((d) => d.label && d.value);
+      return list.length ? list : this.deptDict;
     },
-    // 鐥呭尯閫夐」锛氫紭鍏堝彇褰撳墠鐢ㄦ埛鎵�灞炵梾鍖猴紝鏃犲垯鍏滃簳
+    // 鐥呭尯閫夐」锛氫紭鍏堝彇褰撳墠鐢ㄦ埛鎵�灞炵梾鍖猴紱娌℃湁褰掑睘鏃剁敤鐥呭尯瀛楀吀锛堢湡瀹� districtCode锛�
     wardOptions() {
-      const list = this.$store.getters.belongWards || [];
-      return list.length
-        ? list.map((w) => ({ label: w.districtName, value: w.districtCode }))
-        : WARDS.map((w) => ({ label: w, value: w }));
+      const list = (this.$store.getters.belongWards || [])
+        .map((w) => ({ label: w.districtName, value: w.districtCode }))
+        .filter((w) => w.label && w.value);
+      return list.length ? list : this.wardDict;
     },
     // 鏈嶅姟绫诲瀷涓嬫媺锛氬彇 store 鐨勯殢璁跨被鍨嬪瓧鍏� tasktypes锛屽彇涓嶅埌鏃跺厹搴曟湰鍦� SERVICE_TYPES
     serviceTypeOptions() {
@@ -566,18 +561,22 @@
       const taskTypes = toOptions(this.$store.getters.tasktypes);
       return taskTypes.length ? taskTypes : SERVICE_TYPES;
     },
-    // 鏈�夌梾鍖烘椂鐨勯粯璁ゆ煡璇㈣寖鍥达細鍏ㄩ儴鐥呭尯缂栫爜
+    // 鏈�夌梾鍖烘椂鐨勯粯璁ゆ煡璇㈣寖鍥达細鍏ㄩ儴鐥呭尯缂栫爜锛堢┖鏁扮粍 = 涓嶉檺锛屽悗绔笉鎷艰繃婊ゆ潯浠讹級
     defaultWardCodes() {
-      return Array.from(new Set(this.wardOptions.map((w) => w.value)));
+      return Array.from(
+        new Set(this.wardOptions.map((w) => w.value).filter(Boolean))
+      );
     },
     // 鏄惁鎸夈�屽湪闄㈡弧鎰忓害銆嶅彛寰勬煡璇細鍦ㄩ櫌婊℃剰搴︾敤 deptcodes/hospitaldistrictcodes锛�
     // 绂婚櫌婊℃剰搴︼紙浠ュ強娓呯┖涓嬫媺鏃剁殑榛樿鍙e緞锛夌敤 leaveldeptcodes/leavehospitaldistrictcodes
     isInHospitalSatisfaction() {
       return this.satisfactionScope === "hospital";
     },
-    // 鏈�夌瀹ゆ椂鐨勯粯璁ゆ煡璇㈣寖鍥达細鍏ㄩ儴绉戝缂栫爜
+    // 鏈�夌瀹ゆ椂鐨勯粯璁ゆ煡璇㈣寖鍥达細鍏ㄩ儴绉戝缂栫爜锛堢┖鏁扮粍 = 涓嶉檺锛�
     defaultDeptCodes() {
-      return Array.from(new Set(this.deptOptions.map((d) => d.value)));
+      return Array.from(
+        new Set(this.deptOptions.map((d) => d.value).filter(Boolean))
+      );
     },
     // 鍥哄畾鍏」閫夐」鐨勯鐩� => 琛ㄤ竴
     satisfactionQuestions() {
@@ -714,7 +713,9 @@
     }
     this.queryParams.serviceTypeList = this.defaultServiceTypes();
   },
-  mounted() {
+  async mounted() {
+    // 鍏堟妸绉戝/鐥呭尯瀛楀吀琛ラ綈锛屼繚璇侀娆℃煡璇㈢敤鐨勫氨鏄湡瀹炵紪鐮�
+    await this.ensureOrgDict();
     this.fetchData();
   },
   methods: {
@@ -725,6 +726,52 @@
         return item ? item.value : null;
       }).filter((v) => v != null);
       return Array.from(new Set(hit));
+    },
+    // 褰掍竴鍖栫瀹�/鐥呭尯缂栫爜锛氶�変腑椤逛紭鍏堬紝鍏舵榛樿鍏ㄩ儴锛涘幓閲嶃�佸幓绌�
+    // 杩斿洖绌烘暟缁勮〃绀恒�屼笉闄愩�嶏紙鍚庣 mapper 鐢� size() > 0 鍒ゆ柇锛岀┖闆嗗悎涓嶆嫾杩囨护鏉′欢锛�
+    codeList(selected, fallback) {
+      const list = (selected && selected.length ? selected : fallback) || [];
+      return Array.from(
+        new Set(
+          list.filter((v) => v !== null && v !== undefined && String(v).trim() !== "")
+        )
+      );
+    },
+    // 琛ュ叏绉戝/鐥呭尯瀛楀吀锛氫粎褰� store 閲屾病鏈夊綊灞炴暟鎹椂锛堝吀鍨嬫槸绠$悊鍛樿处鍙凤級鎵嶈姹�
+    async ensureOrgDict() {
+      const tasks = [];
+      if (!(this.$store.getters.belongDepts || []).length) {
+        tasks.push(this.loadOrgDict(1));
+      }
+      if (!(this.$store.getters.belongWards || []).length) {
+        tasks.push(this.loadOrgDict(2));
+      }
+      if (tasks.length) await Promise.all(tasks);
+    },
+    // deptType锛�1=绉戝锛�2=鐥呭尯锛涚粨鏋滄棦浣滀笅鎷夐�夐」锛屼篃浣溿�屽叏閮ㄣ�嶆椂鐨勭紪鐮佹潵婧�
+    async loadOrgDict(deptType) {
+      let flat = [];
+      try {
+        const res = await deptTreeSelect(deptType);
+        const walk = (nodes) => {
+          (Array.isArray(nodes) ? nodes : []).forEach((n) => {
+            // deptTreeSelect 杩斿洖 TreeSelect锛歿 id, label, deptCode, children }
+            // 鍙 deptCode锛岀己澶卞氨璺宠繃鈥斺�旂敤 deptId 鍐掑厖缂栫爜鍚屾牱鏌ヤ笉鍒版暟鎹�
+            if (n.label && n.deptCode) {
+              flat.push({ label: n.label, value: String(n.deptCode) });
+            }
+            if (n.children && n.children.length) walk(n.children);
+          });
+        };
+        walk(res && res.data);
+      } catch (e) {
+        console.warn("[DataOverview] 鑾峰彇绉戝/鐥呭尯瀛楀吀澶辫触锛屽皢鎸夆�滃叏閮ㄢ�濇煡璇�", e);
+      }
+      if (deptType === 1) {
+        this.deptDict = flat;
+      } else {
+        this.wardDict = flat;
+      }
     },
     // 琛ㄤ笁鐙珛鏉′欢锛�'inherit' 鏃舵部鐢ㄩ《閮ㄦ墍閫夛紱杩斿洖閫変腑鐨勫悕绉版暟缁勶紙鎺ュ彛鍙洖鍚嶇О锛夛紝绌烘暟缁勮〃绀轰笉闄�
     statFilterNames() {
@@ -862,22 +909,26 @@
         endFinishTime: range[1],
       };
       if (this.queryParams.statMode === "dept") {
-        const deptCodes = depts.length
-          ? Array.from(new Set(depts))
-          : this.defaultDeptCodes;
-        if (this.isInHospitalSatisfaction) {
-          payload.deptcodes = deptCodes;
-        } else {
-          payload.leaveldeptcodes = deptCodes;
+        const deptCodes = this.codeList(depts, this.defaultDeptCodes);
+        // 鏈夌紪鐮佹墠浼狅紱绌烘暟缁勭瓑浠蜂簬銆屽叏閮ㄣ�嶏紙鍚庣 mapper 瀵圭┖闆嗗悎涓嶆嫾鏉′欢锛�
+        if (deptCodes.length) {
+          if (this.isInHospitalSatisfaction) {
+            payload.deptcodes = deptCodes;
+          } else {
+            payload.leaveldeptcodes = deptCodes;
+          }
         }
       } else {
-        const wardCodes = wards.length
-          ? Array.from(new Set(wards))
-          : this.defaultWardCodes;
-        if (this.isInHospitalSatisfaction) {
-          payload.hospitaldistrictcodes = wardCodes;
-        } else {
-          payload.leavehospitaldistrictcodes = wardCodes;
+        // 鍙紶銆岀敤鎴风湡姝i�変腑鐨勭梾鍖恒�嶏細娌¢�夛紙= 鍏ㄩ儴鐥呭尯锛夊氨涓嶄紶锛屽悗绔寜涓嶉檺澶勭悊銆�
+        // 闂ㄨ瘖婊℃剰搴︾瓑浠诲姟鐨� leavehospitaldistrictcode 涓虹┖锛�
+        // 鍘熸潵鎶娿�屽叏閮ㄧ梾鍖虹紪鐮併�嶄紶涓嬪幓浼氳杩欎簺浠诲姟琚� IN 鏉′欢鏁存杩囨护鎺夛紙25 棰樺彧鍓� 10 棰橈級銆�
+        const wardCodes = this.codeList(wards, []);
+        if (wardCodes.length) {
+          if (this.isInHospitalSatisfaction) {
+            payload.hospitaldistrictcodes = wardCodes;
+          } else {
+            payload.leavehospitaldistrictcodes = wardCodes;
+          }
         }
       }
       // 鏈嶅姟绫诲瀷澶氶�夛細閫楀彿鍒嗛殧锛堟帴鍙e瓧娈典负 serviceType 瀛楃涓诧級
@@ -927,12 +978,25 @@
       try {
         // 鎸夌瀹ょ粺璁°�佹寜鐥呭尯缁熻鍚勬煡涓�娆★紝鏉′欢闅忋�屾弧鎰忓害绫诲瀷銆嶅垏鎹細
         // 鍦ㄩ櫌婊℃剰搴� => deptcodes / hospitaldistrictcodes锛涚闄㈡弧鎰忓害 => leaveldeptcodes / leavehospitaldistrictcodes
-        const deptParam = this.isInHospitalSatisfaction
-          ? { deptcodes: this.defaultDeptCodes }
-          : { leaveldeptcodes: this.defaultDeptCodes };
-        const wardParam = this.isInHospitalSatisfaction
-          ? { hospitaldistrictcodes: this.defaultWardCodes }
-          : { leavehospitaldistrictcodes: this.defaultWardCodes };
+        // 娌℃湁鐪熷疄缂栫爜鏃朵笉浼犺瀛楁锛�= 鍏ㄩ儴锛夛紝缁濅笉鎶婂悕绉板綋缂栫爜浼�
+        const deptParam = {};
+        const deptCodes = this.codeList([], this.defaultDeptCodes);
+        if (deptCodes.length) {
+          if (this.isInHospitalSatisfaction) {
+            deptParam.deptcodes = deptCodes;
+          } else {
+            deptParam.leaveldeptcodes = deptCodes;
+          }
+        }
+        const wardParam = {};
+        const wardCodes = this.codeList([], this.defaultWardCodes);
+        if (wardCodes.length) {
+          if (this.isInHospitalSatisfaction) {
+            wardParam.hospitaldistrictcodes = wardCodes;
+          } else {
+            wardParam.leavehospitaldistrictcodes = wardCodes;
+          }
+        }
         const [deptRes, wardRes] = await Promise.all([
           statQuestionOptionByKsOrBq({ ...base, ...deptParam }),
           statQuestionOptionByKsOrBq({ ...base, ...wardParam }),
@@ -1050,51 +1114,164 @@
       const hit = opts.find((o) => String(o.label) === String(name));
       return hit ? hit.value : name;
     },
+    // 瀵煎嚭 Excel锛氫竴娆″鍑烘暣椤典笁涓〃锛堝悇涓�涓� sheet锛夛紝鍜岄〉闈㈡樉绀轰繚鎸佷竴鑷�
     exportTable(tableId) {
-      const tableNames = {
-        table1: "鍚勬湇鍔¢」鐩弧鎰忓害缁熻",
-        table2: "鍗曢�夐澶氶�夐缁熻",
-        table3: "鍚勭瀹ゆ弧鎰忓害璇勫垎瀵规瘮",
-      };
-      const name = tableNames[tableId] || "缁熻鏁版嵁";
-      const table = document.getElementById(tableId);
-      if (!table) return;
-
       try {
-        // 浠庤〃鏍兼彁鍙栨暟鎹瀯寤哄甫鏍峰紡鐨� Excel
+        const order = ["table1", "table2", "table3"];
+        if (tableId && order.indexOf(tableId) > 0) {
+          order.splice(order.indexOf(tableId), 1);
+          order.unshift(tableId);
+        }
+        const builders = {
+          table1: () => this.buildExportTable1(),
+          table2: () => this.buildExportTable2(),
+          table3: () => this.buildExportTable3(),
+        };
         const wb = XLSX.utils.book_new();
-        const data = [];
-        table.querySelectorAll("tr").forEach((tr) => {
-          const row = [];
-          tr.querySelectorAll("th,td").forEach((td) => {
-            row.push((td.innerText || "").trim());
-          });
-          if (row.length) data.push(row);
+        let count = 0;
+        order.forEach((id) => {
+          const sheet = builders[id]();
+          if (!sheet) return;
+          const ws = XLSX.utils.aoa_to_sheet(sheet.data);
+          ws["!merges"] = sheet.merges || [];
+          const widthRow = sheet.data.find((r) => r && r.length) || [];
+          ws["!cols"] = widthRow.map((_, i) => ({ wch: i === 0 ? 32 : 12 }));
+          XLSX.utils.book_append_sheet(wb, ws, sheet.name);
+          count++;
         });
-
-        const ws = XLSX.utils.aoa_to_sheet(data);
-        // 璁剧疆鍒楀
-        const colWidths = data[0] ? data[0].map(() => ({ wch: 16 })) : [];
-        ws["!cols"] = colWidths;
-
-        XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
-        XLSX.writeFile(wb, name + ".xlsx");
+        if (!count) {
+          this.$message.warning("娌℃湁鍙鍑虹殑鏁版嵁");
+          return;
+        }
+        XLSX.writeFile(wb, "婊℃剰搴︽暟鎹�昏.xlsx");
+        this.$message.success("瀵煎嚭鎴愬姛");
       } catch (e) {
-        // fallback: CSV
-        let csv = "锘�";
-        table.querySelectorAll("tr").forEach((tr) => {
-          const row = [];
-          tr.querySelectorAll("th,td").forEach((td) =>
-            row.push('"' + (td.innerText || "").replace(/"/g, '""') + '"')
-          );
-          csv += row.join(",") + "\n";
-        });
-        const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
-        const link = document.createElement("a");
-        link.href = URL.createObjectURL(blob);
-        link.download = name + ".csv";
-        link.click();
+        console.error("瀵煎嚭澶辫触:", e);
+        this.$message.error("瀵煎嚭澶辫触锛�" + (e.message || e));
       }
+    },
+
+    // 琛ㄤ竴锛氬悇鏈嶅姟椤圭洰婊℃剰搴︾粺璁�
+    buildExportTable1() {
+      const rows = this.serviceSatisfaction || [];
+      if (!rows.length) return null;
+      const data = [
+        ["鍚勬湇鍔¢」鐩弧鎰忓害缁熻锛堝叚妗h瘎鍒嗛锛氬緢婊℃剰/婊℃剰/涓�鑸�/涓嶆弧鎰�/寰堜笉婊℃剰/鏈粡鍘嗭級"],
+        [
+          "鍒嗙被/棰樼洰",
+          "寰堟弧鎰�", "", "婊℃剰", "", "涓�鑸�", "",
+          "涓嶆弧鎰�", "", "寰堜笉婊℃剰", "", "鏈粡鍘�", "",
+          "缁煎悎寰楀垎",
+        ],
+        [
+          "",
+          "鏁伴噺", "姣斾緥", "鏁伴噺", "姣斾緥", "鏁伴噺", "姣斾緥",
+          "鏁伴噺", "姣斾緥", "鏁伴噺", "姣斾緥", "鏁伴噺", "姣斾緥",
+          "",
+        ],
+      ];
+      rows.forEach((r) => {
+        data.push([
+          r.category,
+          r.veryGoodCount, r.veryGoodRate,
+          r.goodCount, r.goodRate,
+          r.normalCount, r.normalRate,
+          r.badCount, r.badRate,
+          r.veryBadCount, r.veryBadRate,
+          r.naCount, r.naRate,
+          r.score,
+        ]);
+      });
+      const merges = [
+        { s: { r: 0, c: 0 }, e: { r: 0, c: 13 } },
+        { s: { r: 1, c: 0 }, e: { r: 2, c: 0 } },
+      ];
+      for (let i = 0; i < 6; i++) {
+        merges.push({ s: { r: 1, c: 1 + i * 2 }, e: { r: 1, c: 2 + i * 2 } });
+      }
+      merges.push({ s: { r: 1, c: 13 }, e: { r: 2, c: 13 } });
+      return { name: "鍚勬湇鍔¢」鐩弧鎰忓害", data, merges };
+    },
+
+    // 琛ㄤ簩锛氬叾浠栭鐩粺璁★紙姣忛涓�鍧楋細棰樼洰鏍囬 鈫� 閫夐」/鏁伴噺/姣斾緥 鈫� 鏈濉姤鎬婚噺锛�
+    buildExportTable2() {
+      const list = this.filteredQuestionStats || [];
+      if (!list.length) return null;
+      const data = [
+        ["鍏朵粬棰樼洰缁熻锛堝叚妗f弧鎰忓害棰樺凡鍦ㄤ笂琛紝杩欓噷鍙垪鍏朵綑棰樺瀷锛氭帹鑽愩�佹姳鎬ㄥ洖搴斻�佹剰瑙佺瓑锛�"],
+        [],
+      ];
+      const merges = [{ s: { r: 0, c: 0 }, e: { r: 0, c: 2 } }];
+      list.forEach((q) => {
+        data.push([q.title, "", ""]);
+        merges.push({
+          s: { r: data.length - 1, c: 0 },
+          e: { r: data.length - 1, c: 2 },
+        });
+        data.push(["閫夐」", "鏁伴噺", "姣斾緥"]);
+        const opts =
+          q.options && q.options.length
+            ? q.options
+            : [{ option: "锛堟棤閫夐」锛�", count: 0, rate: "-" }];
+        opts.forEach((o) => {
+          data.push([o.option, o.count, o.rate]);
+        });
+        data.push([`鏈濉姤鎬婚噺锛�${q.total}`, "", ""]);
+        merges.push({
+          s: { r: data.length - 1, c: 0 },
+          e: { r: data.length - 1, c: 2 },
+        });
+        data.push([]);
+      });
+      return { name: "鍏朵粬棰樼洰缁熻", data, merges };
+    },
+
+    // 琛ㄤ笁锛氬悇绉戝/鐥呭尯婊℃剰搴﹁瘎鍒嗗姣旓紙涓よ鍔ㄦ�佽〃澶达級
+    buildExportTable3() {
+      const rows = this.table3Rows || [];
+      const questions = this.table3Questions || [];
+      if (!rows.length) return null;
+      const head1 = [this.statDimension === "dept" ? "绉戝" : "鐥呭尯"];
+      const head2 = [""];
+      const groups = [];
+      questions.forEach((q) => {
+        const opts = q.options || [];
+        const startCol = head1.length;
+        head1.push(q.title);
+        for (let i = 1; i < opts.length + 1; i++) head1.push("");
+        groups.push({ start: startCol, end: startCol + opts.length });
+        opts.forEach((opt) => head2.push(opt));
+        head2.push("寰楀垎");
+      });
+      head1.push("缁煎悎寰楀垎");
+      head2.push("");
+      const data = [["鍚勭瀹�/鐥呭尯婊℃剰搴﹁瘎鍒嗗姣�"], head1, head2];
+      const merges = [
+        { s: { r: 0, c: 0 }, e: { r: 0, c: head1.length - 1 } },
+        { s: { r: 1, c: 0 }, e: { r: 2, c: 0 } },
+        {
+          s: { r: 1, c: head1.length - 1 },
+          e: { r: 2, c: head1.length - 1 },
+        },
+      ];
+      groups.forEach((g) => {
+        if (g.end > g.start) {
+          merges.push({ s: { r: 1, c: g.start }, e: { r: 1, c: g.end } });
+        }
+      });
+      rows.forEach((row) => {
+        const line = [row.name];
+        questions.forEach((q) => {
+          const cell = (row.cells && row.cells[q.title]) || {};
+          (q.options || []).forEach((opt) => {
+            line.push((cell[opt] && cell[opt].count) || 0);
+          });
+          line.push(cell._score || "0.00");
+        });
+        line.push(row.totalScore);
+        data.push(line);
+      });
+      return { name: "鍚勭瀹ょ梾鍖鸿瘎鍒嗗姣�", data, merges };
     },
   },
 };
@@ -1137,6 +1314,12 @@
   font-weight: 600;
   color: #303133;
 }
+.card-subtitle {
+  margin-left: 6px;
+  font-size: 12px;
+  font-weight: 400;
+  color: #909399;
+}
 .header-right {
   display: flex;
   align-items: center;

--
Gitblit v1.9.3