From d725dc65cf7f74df3aa3f5a0297fd6f8b767cdbc Mon Sep 17 00:00:00 2001
From: liusheng <337615773@qq.com>
Date: 星期五, 30 十月 2026 10:23:03 +0800
Subject: [PATCH] Merge branch 'subtask' of http://116.62.18.175:6699/r/~yxh/smartor-web into subtask

---
 src/components/UploadExcel/index.vue                                      |    2 
 src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue            |  175 ++++++++---
 src/views/Satisfaction/sfstatistics/components/SatisfactionStatistics.vue |  346 ++++++++++++++++++++++
 src/views/Satisfaction/sfstatistics/components/DataOverview.vue           |  221 ++++++++++----
 src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue     |   81 ++--
 src/views/Satisfaction/sfstatistics/components/visitStatistics.vue        |   63 ++--
 6 files changed, 705 insertions(+), 183 deletions(-)

diff --git a/src/components/UploadExcel/index.vue b/src/components/UploadExcel/index.vue
index bf6d7a9..acf34f5 100644
--- a/src/components/UploadExcel/index.vue
+++ b/src/components/UploadExcel/index.vue
@@ -32,7 +32,7 @@
 </template>
 
 <script>
-import XLSX from "xlsx";
+import * as XLSX from "xlsx";
 export default {
   name: "UploadExcel",
   props: {
diff --git a/src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue b/src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue
index 86c5b41..e5b5e64 100644
--- a/src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue
+++ b/src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue
@@ -41,7 +41,7 @@
         <el-button type="primary" size="small" icon="el-icon-search" @click="refreshAll">鏌ヨ</el-button>
         <el-button size="small" icon="el-icon-refresh" @click="handleReset">閲嶇疆</el-button>
       </div>
-      <el-button type="danger" size="small" icon="el-icon-download" @click="exportPDF">瀵煎嚭PDF</el-button>
+      <el-button type="danger" size="small" icon="el-icon-download" @click="exportExcel">瀵煎嚭Excel</el-button>
     </div>
 
     <el-row :gutter="20">
@@ -171,6 +171,8 @@
 
 <script>
 import * as echarts from 'echarts';
+import ExcelJS from 'exceljs';
+import { saveAs } from 'file-saver';
 import { mydTrend, sltdMydTotalByDimension, sltdMydTotalByScore ,mzMydScoreRank} from "@/api/AiCentre/satisfaction";
 import { getissuelist } from "@/api/AiCentre/questionnaire";
 
@@ -198,10 +200,10 @@
       singleQuestionContent: '',
       singleDeptWards: [],
 
-      // 鎺掑悕缁村害绛涢��
-      rankDimension: 'dept',
+      // 鎺掑悕缁村害绛涢�夛紙榛樿鎸夌梾鍖猴級
+      rankDimension: 'ward',
       rankDeptWards: [],
-      crossDimension: 'dept',
+      crossDimension: 'ward',
       crossDeptWards: [],
 
       // 鍥捐〃鏁版嵁
@@ -845,52 +847,129 @@
       });
     },
 
-    async exportPDF() {
-      const chartRefs = [
-        { ref: this.$refs.chart1, id: 'chart1' },
-        { ref: this.$refs.chart2, id: 'chart2' },
-        { ref: this.$refs.chart4, id: 'chart4' },
-      ];
-      const chartImages = {};
-      for (const { ref, id } of chartRefs) {
-        if (ref) {
-          const instance = echarts.getInstanceByDom(ref);
-          if (instance) {
-            chartImages[id] = instance.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#fff' });
+    // 瀵煎嚭锛氬浘琛ㄨ浆鍥剧墖 + 琛ㄦ牸锛屽唴瀹逛笌椤甸潰涓�鑷达紙鐩存帴涓嬭浇 xlsx锛屼笉渚濊禆鎵撳嵃锛�
+    async exportExcel() {
+      try {
+        const wb = new ExcelJS.Workbook();
+
+        // 鍥捐〃 sheet
+        const addImageSheet = (sheetName, title, refEl, tip) => {
+          const ws = wb.addWorksheet(sheetName, {
+            views: [{ showGridLines: false }],
+          });
+          ws.getCell('A1').value = title;
+          ws.getCell('A1').font = { size: 14, bold: true };
+          ws.mergeCells('A1:F1');
+          let anchorRow = 2.2;
+          if (tip) {
+            ws.getCell('A2').value = tip;
+            ws.mergeCells('A2:F2');
+            anchorRow = 2.8;
           }
-        }
+          for (let c = 1; c <= 6; c++) {
+            ws.getColumn(c).width = 18;
+          }
+          const inst = refEl ? echarts.getInstanceByDom(refEl) : null;
+          if (inst) {
+            const url = inst.getDataURL({
+              type: 'png',
+              pixelRatio: 2,
+              backgroundColor: '#fff',
+            });
+            const imgId = wb.addImage({
+              base64: url.split(',')[1],
+              extension: 'png',
+            });
+            ws.addImage(imgId, {
+              tl: { col: 0.1, row: anchorRow },
+              ext: { width: 900, height: 400 },
+            });
+          } else {
+            ws.getCell(`A${Math.ceil(anchorRow)}`).value = '鏆傛棤鏁版嵁';
+          }
+          return ws;
+        };
+
+        addImageSheet(
+          '缁煎悎寰楀垎瓒嬪娍',
+          '鎮h�呮弧鎰忓害缁煎悎寰楀垎瓒嬪娍',
+          this.$refs.chart1,
+          `鐜瘮 ${this.trendMoM}`
+        );
+        addImageSheet(
+          '鍒嗙被缁村害瀵规瘮',
+          `鍒嗙被缁村害 路 鍚勬寚鏍�${this.intervalLabel}瀵规瘮`,
+          this.$refs.chart2,
+          ''
+        );
+        addImageSheet(
+          '闄㈠尯缁村害瀵规瘮',
+          `闄㈠尯缁村害 路 鍚勯櫌鍖�${this.intervalLabel}瀵规瘮`,
+          this.$refs.chart4,
+          ''
+        );
+
+        // 鍗曢 路 閫夐」缁熻
+        const wsQ = wb.addWorksheet('鍗曢閫夐」缁熻');
+        wsQ.addRow(['鍗曢 路 閫夐」缁熻']).font = { size: 14, bold: true };
+        wsQ.mergeCells(1, 1, 1, 3);
+        const qHead = wsQ.addRow(['閫夐」', '鍚堣鏁伴噺', '鍗犳瘮姣斾緥']);
+        qHead.font = { bold: true };
+        (this.singleQuestionData || []).forEach((r) => {
+          wsQ.addRow([r.option, r.count, r.rate]);
+        });
+        wsQ.addRow([]);
+        wsQ.addRow([
+          `鏈鏈夋晥濉啓浜烘锛�${this.singleQuestionMeta.total}銆�缁煎悎寰楀垎锛�${this.singleQuestionMeta.score}`,
+          '',
+          '',
+        ]);
+        wsQ.mergeCells(wsQ.rowCount, 1, wsQ.rowCount, 3);
+        wsQ.columns.forEach((c, i) => {
+          c.width = i === 0 ? 36 : 16;
+        });
+
+        // 鎺掑悕琛ㄦ牸
+        const addRankSheet = (sheetName, title, rows, dimLabel) => {
+          const ws = wb.addWorksheet(sheetName);
+          ws.addRow([title]).font = { size: 14, bold: true };
+          ws.mergeCells(1, 1, 1, 4);
+          const head = ws.addRow(['鎺掑悕', dimLabel, '缁煎悎寰楀垎', '椤规暟']);
+          head.font = { bold: true };
+          (rows || []).forEach((r) => {
+            ws.addRow([r.rank, r.name, r.score, r.itemCount]);
+          });
+          if (!rows || !rows.length) {
+            ws.addRow(['鏆傛棤鏁版嵁', '', '', '']);
+          }
+          ws.columns.forEach((c, i) => {
+            c.width = i === 1 ? 30 : 12;
+          });
+        };
+        addRankSheet(
+          `${this.rankDimLabel}鎺掑悕`,
+          `${this.rankDimLabel}缁村害 路 闂ㄨ瘖鎮h�呮弧鎰忓害寰楀垎鎺掑悕`,
+          this.rankRows,
+          this.rankDimLabel
+        );
+        addRankSheet(
+          `鍗曢脳${this.crossDimLabel}鎺掑悕`,
+          `鍗曢脳${this.crossDimLabel} 路 寰楀垎鎺掑悕`,
+          this.crossRows,
+          this.crossDimLabel
+        );
+
+        const buffer = await wb.xlsx.writeBuffer();
+        const blob = new Blob([buffer], {
+          type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+        });
+        const range = (this.queryParams.dateRange || []).join('_');
+        saveAs(blob, `鍚勯」缁熻琛�${range ? '_' + range : ''}.xlsx`);
+        this.$message.success('瀵煎嚭鎴愬姛');
+      } catch (e) {
+        console.error('瀵煎嚭澶辫触:', e);
+        this.$message.error('瀵煎嚭澶辫触锛�' + (e.message || e));
       }
-
-      const printContent = this.$refs.pdfTarget.cloneNode(true);
-      const chartBoxes = printContent.querySelectorAll('.chart-box');
-      const imgKeys = Object.keys(chartImages);
-      chartBoxes.forEach((box, i) => {
-        if (imgKeys[i] && chartImages[imgKeys[i]]) {
-          box.innerHTML = `<img src="${chartImages[imgKeys[i]]}" style="width:100%;height:auto;" />`;
-        }
-      });
-
-      const win = window.open('', '_blank', 'width=900,height=800');
-      if (!win) { this.$message.warning('璇峰厑璁稿脊鍑虹獥鍙d互瀵煎嚭PDF'); return; }
-      win.document.write(`
-        <html><head><meta charset="utf-8"><title>鍚勯」缁熻琛�</title>
-        <style>
-          body { font-family: 'Microsoft YaHei',sans-serif; padding: 20px; }
-          .chart-card { break-inside: avoid; margin-bottom: 20px; border: 1px solid #ebeef5; border-radius: 6px; }
-          .card-header { padding: 12px 16px; border-bottom: 1px solid #ebeef5; font-size: 15px; font-weight: 600; color: #303133; background: #f5f7fa; }
-          .chart-box img { max-width: 100%; display: block; }
-          table { width: 100%; border-collapse: collapse; margin: 12px 0; }
-          th, td { border: 1px solid #d9d9d9; padding: 8px 12px; text-align: center; font-size: 12px; }
-          th { background: #f0f2f5; font-weight: 600; }
-          .table-footer { text-align: right; padding: 8px 0; font-size: 13px; color: #606266; }
-          .export-bar, .filter-bar, .el-tag, .el-select, .el-radio-group { display: none; }
-          @media print { @page { size: A4; margin: 10mm; } }
-        </style></head><body>
-        ${printContent.innerHTML}
-        <script>window.onload=function(){window.print();}<\/script>
-        </body></html>
-      `);
-      win.document.close();
     },
   },
 };
diff --git a/src/views/Satisfaction/sfstatistics/components/DataOverview.vue b/src/views/Satisfaction/sfstatistics/components/DataOverview.vue
index 4539a87..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>
@@ -115,13 +122,6 @@
       <div slot="header" class="card-header">
         <span class="card-title">鍚勬湇鍔¢」鐩弧鎰忓害缁熻</span>
         <span class="card-subtitle">锛堝叚妗h瘎鍒嗛锛氬緢婊℃剰/婊℃剰/涓�鑸�/涓嶆弧鎰�/寰堜笉婊℃剰/鏈粡鍘嗭級</span>
-        <el-button
-          type="warning"
-          size="small"
-          icon="el-icon-download"
-          @click="exportTable('table1')"
-          >瀵煎嚭Excel</el-button
-        >
       </div>
       <el-table
         id="table1"
@@ -186,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="缁熻涓�...">
@@ -271,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
@@ -336,7 +322,7 @@
 <script>
 import { sltdMydTotalByScore,statQuestionOptionByKsOrBq } from "@/api/AiCentre/satisfaction";
 import { deptTreeSelect } from "@/api/system/user";
-import XLSX from "xlsx";
+import * as XLSX from "xlsx";
 
 // 婊℃剰搴﹂鐨勫浐瀹氶�夐」锛堥『搴忓嵆琛ㄦ牸鍒楅『搴忥級鈥斺�旇繖浜涢�夐」鐨勯鐩繘琛ㄤ竴锛屽叾浣欒繘琛ㄤ簩
 const FIXED_OPTIONS = ["寰堟弧鎰�", "婊℃剰", "涓�鑸�", "涓嶆弧鎰�", "寰堜笉婊℃剰", "鏈粡鍘�"];
@@ -1128,52 +1114,165 @@
       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 };
+    },
   },
 };
 </script>
diff --git a/src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue b/src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue
index 754e244..c299b30 100644
--- a/src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue
+++ b/src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue
@@ -401,8 +401,8 @@
   data() {
     return {
       // 婊℃剰搴︾被鍨嬶細leave=绂婚櫌婊℃剰搴︼紙鎸夊嚭闄㈢梾鍖虹粺璁★級锛宧ospital=鍦ㄩ櫌婊℃剰搴︼紙鎸夊湪闄㈢梾鍖虹粺璁★級
-      // 榛樿鍦ㄩ櫌婊℃剰搴�
-      satisfactionScope: "hospital",
+      // 榛樿绂婚櫌婊℃剰搴�
+      satisfactionScope: "leave",
 
       // 鏌ヨ鍙傛暟
       queryParams: {
@@ -432,7 +432,7 @@
       options: [
         {
           value: 6,
-          label: "浣忛櫌婊℃剰搴�",
+          label: "鍑哄叆闄㈡弧鎰忓害",
           raw: {
             cssClass: "",
             listClass: "",
@@ -539,8 +539,11 @@
 
     // 鑾峰彇绉戝鏍�
     getDeptTree() {
-      // 鑾峰彇鏈嶅姟绫诲瀷
-      this.options = this.$store.getters.tasktypes || [];
+      // 鑾峰彇鏈嶅姟绫诲瀷锛堝瓧鍏搁噷鐨勩�屼綇闄㈡弧鎰忓害銆嶅湪鏈〉鏄剧ず涓恒�屽嚭鍏ラ櫌婊℃剰搴︺�嶏級
+      const labelFix = { 浣忛櫌婊℃剰搴�: "鍑哄叆闄㈡弧鎰忓害" };
+      this.options = (this.$store.getters.tasktypes || []).map((o) =>
+        labelFix[o.label] ? { ...o, label: labelFix[o.label] } : o
+      );
 
       // 鑾峰彇绉戝鍒楄〃
       this.flatArraydept = (this.$store.getters.belongDepts || []).map(
@@ -854,9 +857,9 @@
       this.getList();
     },
 
-    // 閲嶇疆鏌ヨ锛堟仮澶嶉粯璁わ細鍦ㄩ櫌婊℃剰搴� + 鏈嶅姟绫诲瀷 6锛�
+    // 閲嶇疆鏌ヨ锛堟仮澶嶉粯璁わ細绂婚櫌婊℃剰搴� + 鏈嶅姟绫诲瀷 6锛�
     resetQuery() {
-      this.satisfactionScope = "hospital";
+      this.satisfactionScope = "leave";
       this.queryParams = {
         statisticaltype: 1,
         leavehospitaldistrictcodes: [],
@@ -1087,15 +1090,9 @@
           },
         };
 
-        // 娣诲姞鎬绘爣棰橈紙涓ゅ垪缁村害 + 鍏釜鎸囨爣鍒楋紝鍏� 10 鍒楋級
-        worksheet.mergeCells(1, 1, 1, 10);
-        const titleCell = worksheet.getCell(1, 1);
-        titleCell.value = `婊℃剰搴﹂殢璁跨粺璁¤〃锛�${sheetNameSuffix}锛塦;
-        titleCell.style = titleStyle;
-        worksheet.getRow(1).height = 35;
-
-        // 娣诲姞琛ㄥご锛堢瀹ょ粺璁″鍑哄湪闄㈢瀹�+绂婚櫌绉戝锛岀梾鍖虹粺璁″鍑哄湪闄㈢梾鍖�+绂婚櫌鐥呭尯锛�
+        // 琛ㄥご鎸夐〉闈㈠垪鍔ㄦ�佺敓鎴愶細绉戝/鐥呭尯缁熻鍒椾笉鍚岋紝銆屽強鏃剁巼銆嶅彧鏈夋櫙瀹侀櫌鍖洪〉闈㈡墠鏄剧ず
         const isWardStatistics = this.queryParams.statisticaltype == 1;
+        const showTimelyRate = this.orgname === "鏅畞鐣叉棌鑷不鍘夸汉姘戝尰闄�";
         const headers = [
           isWardStatistics ? "鍦ㄩ櫌鐥呭尯" : "鍦ㄩ櫌绉戝",
           isWardStatistics ? "绂婚櫌鐥呭尯" : "绂婚櫌绉戝",
@@ -1103,11 +1100,19 @@
           "鏃犻渶闅忚浜烘",
           "搴旈殢璁夸汉娆�",
           "闅忚鐜�",
-          "鍙婃椂鐜�",
-          "婊℃剰搴﹂鐩�婚噺",
-          "婊℃剰搴﹀~鎶ラ噺",
-          "瀹屾垚姣旂巼",
         ];
+        if (showTimelyRate) {
+          headers.push("鍙婃椂鐜�");
+        }
+        headers.push("婊℃剰搴﹂鐩�婚噺", "婊℃剰搴﹀~鎶ラ噺", "瀹屾垚姣旂巼");
+        const colCount = headers.length;
+
+        // 娣诲姞鎬绘爣棰�
+        worksheet.mergeCells(1, 1, 1, colCount);
+        const titleCell = worksheet.getCell(1, 1);
+        titleCell.value = `婊℃剰搴﹂殢璁跨粺璁¤〃锛�${sheetNameSuffix}锛塦;
+        titleCell.style = titleStyle;
+        worksheet.getRow(1).height = 35;
 
         const headerRow = worksheet.addRow(headers);
         headerRow.eachCell((cell) => {
@@ -1115,9 +1120,13 @@
         });
         headerRow.height = 25;
 
-        // 娣诲姞鏁版嵁琛�
+        // 娣诲姞鏁版嵁琛岋紙绌哄�肩粺涓�鏄剧ず鈥�-鈥濓紝涓庨〉闈繚鎸佷竴鑷达級
+        const dash = (v, formatter) => {
+          if (v === null || v === undefined || v === "") return "-";
+          return formatter ? formatter(v) : v;
+        };
         this.userList.forEach((item) => {
-          const dataRow = worksheet.addRow([
+          const rowData = [
             isWardStatistics
               ? item.hospitaldistrictname || "-"
               : item.hospitaldeptname || "-",
@@ -1127,12 +1136,17 @@
             item.dischargeCount || 0,
             item.nonFollowUp || 0,
             item.followUpNeeded || 0,
-            item.followUpRate || "0%",
-            item.rate ? this.formatPercent(item.rate) : "0%",
+            dash(item.followUpRate),
+          ];
+          if (showTimelyRate) {
+            rowData.push(dash(item.rate, (v) => this.formatPercent(v)));
+          }
+          rowData.push(
             item.joyAllCount || 0,
             item.joyCount || 0,
-            item.joyTotal ? this.formatPercent(item.joyTotal) : "0%",
-          ]);
+            dash(item.joyTotal, (v) => this.formatPercent(v))
+          );
+          const dataRow = worksheet.addRow(rowData);
 
           dataRow.eachCell((cell) => {
             cell.style = cellStyle;
@@ -1140,19 +1154,10 @@
           dataRow.height = 22;
         });
 
-        // 璁剧疆鍒楀
-        worksheet.columns = [
-          { width: 20 },
-          { width: 20 },
-          { width: 12 },
-          { width: 12 },
-          { width: 12 },
-          { width: 12 },
-          { width: 12 },
-          { width: 15 },
-          { width: 15 },
-          { width: 12 },
-        ];
+        // 璁剧疆鍒楀锛堣窡鍒楁暟涓�鑷达級
+        worksheet.columns = headers.map((h, i) => ({
+          width: i < 2 ? 20 : h.length > 6 ? 15 : 12,
+        }));
 
         // 鐢熸垚骞朵笅杞芥枃浠�
         const buffer = await workbook.xlsx.writeBuffer();
diff --git a/src/views/Satisfaction/sfstatistics/components/SatisfactionStatistics.vue b/src/views/Satisfaction/sfstatistics/components/SatisfactionStatistics.vue
index eda31ce..ec34803 100644
--- a/src/views/Satisfaction/sfstatistics/components/SatisfactionStatistics.vue
+++ b/src/views/Satisfaction/sfstatistics/components/SatisfactionStatistics.vue
@@ -14,7 +14,7 @@
           <el-form-item label="婊℃剰搴︾被鍨�" prop="satisfactionScope">
             <el-select
               v-model="satisfactionScope"
-              placeholder="榛樿鍦ㄩ櫌"
+              placeholder="榛樿绂婚櫌"
               clearable
               style="width: 140px"
               @change="handleSearch"
@@ -104,6 +104,14 @@
             </el-button>
             <el-button icon="el-icon-refresh" @click="handleReset">
               閲嶇疆
+            </el-button>
+            <el-button
+              type="warning"
+              icon="el-icon-download"
+              :loading="exporting"
+              @click="handleExportExcel"
+            >
+              瀵煎嚭Excel
             </el-button>
           </el-form-item>
         </el-form>
@@ -683,6 +691,9 @@
 
 <script>
 import * as echarts from "echarts";
+import * as XLSX from "xlsx";
+import ExcelJS from "exceljs";
+import { saveAs } from "file-saver";
 import {
   statistics,
   satisfactionGraph,
@@ -698,8 +709,8 @@
     return {
       // 婊℃剰搴︾被鍨嬶細leave=绂婚櫌婊℃剰搴︼紙leaveldeptcodes/leavehospitaldistrictcodes锛夛紝
       // hospital=鍦ㄩ櫌婊℃剰搴︼紙deptcodes/hospitaldistrictcodes锛�
-      // 榛樿鍦ㄩ櫌锛氫繚鎸佽椤靛師鏈夎姹傚弬鏁颁笉鍙�
-      satisfactionScope: "hospital",
+      // 榛樿绂婚櫌婊℃剰搴�
+      satisfactionScope: "leave",
       // 鏌ヨ鍙傛暟
       queryParams: {
         type: 2,
@@ -737,6 +748,8 @@
 
       // 棰樼洰鏄庣粏鏁版嵁
       questionDetailData: [],
+      // 棰樼洰鏄庣粏鍏ㄩ噺鏁版嵁锛堝鍑虹敤锛屼笉鍙楀垎椤靛奖鍝嶏級
+      questionDetailAll: [],
 
       // 缁村害棰樼洰鏄庣粏鏁版嵁
       dimensionDetailData: [],
@@ -771,6 +784,9 @@
 
       // 鍚勭被鍨嬬粺璁℃槑缁嗘暟鎹�
       typeDetailData: [],
+
+      // 瀵煎嚭涓紙鎸夐挳 loading锛�
+      exporting: false,
 
       // 鏌辩姸鍥炬暟鎹�
       chartData: [],
@@ -1229,6 +1245,8 @@
       const endIndex = startIndex + this.detailQueryParams.pageSize;
       const paginatedData = detailData.slice(startIndex, endIndex);
 
+      // 瀵煎嚭鐢細淇濈暀涓嶅垎椤电殑鍏ㄩ噺鏁版嵁
+      this.questionDetailAll = detailData;
       this.questionDetailData = paginatedData;
       this.detailTotal = detailData.length;
       this.totalAnswerCount = apiData.totalPerson || 0;
@@ -2059,6 +2077,8 @@
     // 澶勭悊閲嶇疆
     handleReset() {
       this.$refs.queryForm.resetFields();
+      // 閲嶇疆鍚庡洖鍒伴粯璁ょ殑绂婚櫌婊℃剰搴�
+      this.satisfactionScope = "leave";
       this.queryParams.dateRange = [];
       this.queryParams.deptCodes = []; // 閲嶇疆涓烘暟缁�
       this.queryParams.wardCodes = []; // 閲嶇疆涓烘暟缁�
@@ -2120,11 +2140,327 @@
       this.$message.info(`鏌ョ湅绫诲瀷璇︽儏锛�${row.typeName}`);
     },
 
-    // 澶勭悊瀵煎嚭鏁版嵁
+    // 澶勭悊瀵煎嚭鏁版嵁锛氭寜椤甸潰杩欏紶绫诲瀷琛ㄧ殑鍒楀鍑鸿琛�
     handleExportData(row) {
-      this.$message.success(`姝e湪瀵煎嚭 ${row.typeName} 鏁版嵁...`);
+      if (!row) {
+        this.$message.warning("娌℃湁鍙鍑虹殑鏁版嵁");
+        return;
+      }
+      try {
+        const data = [
+          [`婊℃剰搴︾被鍨嬬粺璁★紙${row.typeName || ""}锛塦],
+          ["搴忓彿", "婊℃剰搴︾被鍨�", "鍙戦�侀棶鍗锋暟", "鍥炴敹闂嵎鏁�", "鍥炴敹鐜�", "骞冲潎鍒�", "婊℃剰搴︾瓑绾�"],
+          [
+            row.id,
+            row.typeName || "",
+            Number(row.sendCount) || 0,
+            Number(row.receiveCount) || 0,
+            this.formatPercent(row.recoveryRate),
+            Number(row.averageScore || 0).toFixed(1),
+            row.satisfactionLevel || "",
+          ],
+        ];
+        const ws = XLSX.utils.aoa_to_sheet(data);
+        ws["!merges"] = [{ s: { r: 0, c: 0 }, e: { r: 0, c: 6 } }];
+        ws["!cols"] = [
+          { wch: 8 },
+          { wch: 22 },
+          { wch: 14 },
+          { wch: 14 },
+          { wch: 12 },
+          { wch: 10 },
+          { wch: 12 },
+        ];
+        const wb = XLSX.utils.book_new();
+        XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
+        XLSX.writeFile(wb, `婊℃剰搴︾被鍨嬬粺璁${row.typeName || "瀵煎嚭"}.xlsx`);
+        this.$message.success("瀵煎嚭鎴愬姛");
+      } catch (e) {
+        console.error("瀵煎嚭澶辫触:", e);
+        this.$message.error("瀵煎嚭澶辫触锛�" + (e.message || e));
+      }
     },
 
+    // 瀵煎嚭锛氫袱寮犲浘琛紙婊℃剰搴︾被鍨嬨�佺淮搴︾粺璁★紝鍥惧拰椤甸潰涓�鑷达級+ 涓夊紶鏄庣粏琛紙鍏ㄩ噺锛屼笉鍒嗛〉锛夛紝澶� sheet
+    async handleExportExcel() {
+      if (this.exporting) return;
+      this.exporting = true;
+      try {
+        // 涓夊紶鏄庣粏琛ㄦ槸鍒囧埌瀵瑰簲椤电鎵嶅姞杞界殑锛屽鍑哄墠鍏堣ˉ榻愭暟鎹紝閬垮厤瀵煎嚭绌鸿〃
+        await this.ensureDetailDataLoaded();
+        const wb = new ExcelJS.Workbook();
+
+        // 1銆�2锛氬浘琛� sheet锛堟弧鎰忓害绫诲瀷 / 缁村害缁熻锛�
+        const typeInfo = await this.captureChartImage("satisfactionType");
+        this.addChartSheet(wb, "婊℃剰搴︾被鍨�", "婊℃剰搴︾被鍨嬪~鎶ユ瘮渚嬬粺璁�", typeInfo);
+        const dimInfo = await this.captureChartImage("dimension");
+        this.addChartSheet(wb, "缁村害缁熻", "缁村害濉姤姣斾緥缁熻", dimInfo);
+
+        // 3锛氶鐩槑缁嗙粺璁★紙鍏ㄩ噺锛�
+        const qList =
+          this.questionDetailAll && this.questionDetailAll.length
+            ? this.questionDetailAll
+            : this.questionDetailData || [];
+        this.addTableSheet(
+          wb,
+          "棰樼洰鏄庣粏缁熻",
+          ["搴忓彿", "棰樼洰", "骞冲潎寰楀垎", "鏈�楂樺緱鍒�", "鏈�浣庡緱鍒�", "绛旈浜烘暟", "鏈瓟棰樹汉鏁�", "绛旈鐜�"],
+          qList.map((r, i) => [
+            i + 1,
+            r.scriptContent || "",
+            this.fix1(r.averageScore),
+            this.fix1(r.maxScore),
+            this.fix1(r.minScore),
+            r.answerCount || 0,
+            r.noAnswerPerson || 0,
+            this.formatPercent(r.answerRate),
+          ])
+        );
+
+        // 4锛氱淮搴﹂鐩槑缁嗭紙搴忓彿銆侀鐩�佺淮搴︺�佸钩鍧�/鏈�楂�/鏈�浣庡緱鍒嗐�佺瓟棰樹汉鏁般�佹湭绛旈浜烘暟銆佺瓟棰樼巼锛�
+        this.addTableSheet(
+          wb,
+          "缁村害棰樼洰鏄庣粏",
+          [
+            "搴忓彿",
+            "棰樼洰",
+            "缁村害",
+            "骞冲潎寰楀垎",
+            "鏈�楂樺緱鍒�",
+            "鏈�浣庡緱鍒�",
+            "绛旈浜烘暟",
+            "鏈瓟棰樹汉鏁�",
+            "绛旈鐜�",
+          ],
+          (this.dimensionDetailData || []).map((r, i) => [
+            i + 1,
+            r.scriptContent || "",
+            r.dimension ? this.getDimensionLabel(r.dimension) : r.dimensionName || "",
+            this.fix1(r.averageScore),
+            this.fix1(r.maxScore),
+            this.fix1(r.minScore),
+            r.answerPerson || 0,
+            r.noAnswerPerson || 0,
+            this.formatPercent(r.answerRate),
+          ])
+        );
+
+        // 5锛氬悇绫诲瀷缁熻鏄庣粏
+        this.addTableSheet(
+          wb,
+          "鍚勭被鍨嬬粺璁℃槑缁�",
+          ["搴忓彿", "婊℃剰搴︾被鍨�", "鍙戦�侀棶鍗锋暟", "鍥炴敹闂嵎鏁�", "鍥炴敹鐜�", "骞冲潎鍒�", "婊℃剰搴︾瓑绾�"],
+          (this.typeDetailData || []).map((r, i) => [
+            i + 1,
+            r.typeName || "",
+            r.sendCount || 0,
+            r.receiveCount || 0,
+            this.formatPercent(r.recoveryRate),
+            this.fix1(r.averageScore),
+            r.satisfactionLevel || "",
+          ])
+        );
+
+        const buffer = await wb.xlsx.writeBuffer();
+        const blob = new Blob([buffer], {
+          type:
+            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+        });
+        const suffix =
+          this.startTime && this.endTime
+            ? `_${String(this.startTime).slice(0, 10)}_${String(this.endTime).slice(0, 10)}`
+            : "";
+        saveAs(blob, `婊℃剰搴︽槑缁嗙粺璁�${suffix}.xlsx`);
+        this.$message.success("瀵煎嚭鎴愬姛");
+      } catch (e) {
+        console.error("瀵煎嚭澶辫触:", e);
+        this.$message.error("瀵煎嚭澶辫触锛�" + (e.message || e));
+      } finally {
+        this.exporting = false;
+      }
+    },
+
+    // 瀵煎嚭鍓嶈ˉ榻愪笁寮犳槑缁嗚〃鐨勬暟鎹紙椤甸潰鏄垏椤电鎵嶅姞杞斤紝娌″垏杩囩殑琛ㄦ槸绌虹殑锛�
+    async ensureDetailDataLoaded() {
+      const tasks = [];
+      if (!this.questionDetailAll || !this.questionDetailAll.length) {
+        tasks.push(this.loadQuestionDetailData());
+      }
+      if (!this.dimensionDetailData || !this.dimensionDetailData.length) {
+        tasks.push(this.loadDimensionDetailData());
+      }
+      if (!this.typeDetailData || !this.typeDetailData.length) {
+        tasks.push(this.loadTypeDetailData());
+      }
+      if (tasks.length) {
+        await Promise.all(tasks);
+      }
+    },
+
+    // 淇濈暀涓�浣嶅皬鏁帮紝绌哄�兼樉绀� -
+    fix1(v) {
+      if (v === null || v === undefined || v === "") return "-";
+      const n = Number(v);
+      return isNaN(n) ? "-" : n.toFixed(1);
+    },
+
+    // 鍥捐〃 sheet锛氭爣棰� + 姹囨�� + 鍥捐〃鍥剧墖
+    addChartSheet(wb, sheetName, title, info) {
+      const ws = wb.addWorksheet(sheetName, {
+        views: [{ showGridLines: false }],
+      });
+      ws.getCell("A1").value = title;
+      ws.getCell("A1").font = { size: 14, bold: true };
+      ws.mergeCells("A1:F1");
+      const rate =
+        info && info.totalSend
+          ? this.formatPercent(info.totalReceive / info.totalSend)
+          : "-";
+      ws.getCell("A2").value = `鍙戦�侀鐩�婚噺锛�${(info && info.totalSend) || 0}    鍥炴敹棰樼洰鎬婚噺锛�${(info && info.totalReceive) || 0}    鎬讳綋鍥炴敹鐜囷細${rate}`;
+      ws.mergeCells("A2:F2");
+      for (let c = 1; c <= 6; c++) {
+        ws.getColumn(c).width = 18;
+      }
+      if (info && info.url) {
+        const imgId = wb.addImage({
+          base64: info.url.split(",")[1],
+          extension: "png",
+        });
+        ws.addImage(imgId, {
+          tl: { col: 0.1, row: 3.1 },
+          ext: { width: 940, height: 420 },
+        });
+      } else {
+        ws.getCell("A4").value = "鏆傛棤鏁版嵁";
+      }
+      return ws;
+    },
+
+    // 鏅�氳〃鏍� sheet锛氭爣棰� + 琛ㄥご + 鏁版嵁
+    addTableSheet(wb, sheetName, headers, rows) {
+      const ws = wb.addWorksheet(sheetName);
+      ws.addRow([sheetName]).font = { size: 14, bold: true };
+      ws.mergeCells(1, 1, 1, Math.max(headers.length, 1));
+      const headerRow = ws.addRow(headers);
+      headerRow.eachCell((cell) => {
+        cell.font = { bold: true };
+        cell.alignment = { horizontal: "center", vertical: "middle" };
+        cell.fill = {
+          type: "pattern",
+          pattern: "solid",
+          fgColor: { argb: "FFF5F7FA" },
+        };
+      });
+      if (!rows || !rows.length) {
+        ws.addRow(["鏆傛棤鏁版嵁"]);
+      } else {
+        rows.forEach((r) => {
+          const row = ws.addRow(r);
+          row.eachCell((cell) => {
+            cell.alignment = { horizontal: "center", vertical: "middle" };
+          });
+        });
+      }
+      ws.columns.forEach((col, i) => {
+        col.width = i === 1 ? 40 : 14;
+      });
+      return ws;
+    },
+
+    // 鍙栨煇涓浘琛ㄩ〉绛剧殑鏁版嵁锛堜笉鏀瑰彉椤甸潰鐘舵�侊級
+    async fetchChartRows(chartType) {
+      const rows = [];
+      let totalSend = 0;
+      let totalReceive = 0;
+      let index = 0;
+
+      if (chartType === "dimension") {
+        const params = {
+          type: this.queryParams.type,
+          startTime: this.startTime,
+          endTime: this.endTime,
+          ...this.buildScopeParams(),
+          templateid: this.templateId,
+          questionType: this.queryParams.type || null,
+          serviceTypes: this.defaultServiceTypes,
+          dimensionList: this.dimensionList,
+        };
+        const res = await satisfactionGraphDimension(params);
+        Object.entries((res && res.rows) || {}).forEach(([code, stat]) => {
+          const sendCount = stat.subidAll || 0;
+          const receiveCount = stat.fillCountAll || 0;
+          const rate = stat.receiveRate || 0;
+          rows.push({
+            name: this.getDimensionLabel(code),
+            value: (rate * 100).toFixed(2),
+            sendCount: sendCount,
+            receiveCount: receiveCount,
+            averageScore: stat.averageScore || 0,
+            dimension: code,
+            itemStyle: { color: this.getChartColor(index++) },
+          });
+          totalSend += sendCount;
+          totalReceive += receiveCount;
+        });
+      } else {
+        const params = {
+          type: this.queryParams.type,
+          startTime: this.startTime,
+          endTime: this.endTime,
+          ...this.buildScopeParams(),
+          templateid: this.templateId,
+        };
+        const res = await satisfactionGraph(params);
+        Object.entries((res && res.rows) || {}).forEach(([name, stat]) => {
+          const sendCount = stat.subidAll || 0;
+          const receiveCount = stat.fillCountAll || 0;
+          const rate = stat.receiveRate || 0;
+          rows.push({
+            name: name,
+            value: (rate * 100).toFixed(2),
+            sendCount: sendCount,
+            receiveCount: receiveCount,
+            averageScore: stat.averageScore || 0,
+            itemStyle: { color: this.getChartColor(index++) },
+          });
+          totalSend += sendCount;
+          totalReceive += receiveCount;
+        });
+      }
+      return { rows, totalSend, totalReceive };
+    },
+
+    // 绂诲睆娓叉煋鍥捐〃骞跺鍑� base64 鍥剧墖锛堝鐢ㄩ〉闈� renderChart 鐨勬牱寮忥紝淇濊瘉鍥惧拰椤甸潰涓�鑷达級
+    async captureChartImage(chartType) {
+      const info = await this.fetchChartRows(chartType);
+      if (!info.rows.length) {
+        return { ...info, url: null };
+      }
+      const div = document.createElement("div");
+      div.style.cssText =
+        "position:absolute;left:-99999px;top:0;width:1000px;height:400px;";
+      document.body.appendChild(div);
+      const inst = echarts.init(div);
+      const prevChart = this.barChart;
+      try {
+        this.barChart = inst;
+        // 鍏抽棴鍔ㄧ敾锛氬惁鍒� getDataURL 鎴埌鐨勬槸鏌卞瓙杩樻病闀垮嚭鏉ョ殑绗竴甯э紙鍥鹃噷鏈夊潗鏍囪酱浣嗘病鏈夋煴瀛愶級
+        inst.setOption({ animation: false });
+        this.renderChart(info.rows, chartType);
+        // 绛変竴甯э紝纭繚 canvas 宸茬粡鐢诲畬
+        await new Promise((resolve) => setTimeout(resolve, 120));
+        const url = inst.getDataURL({
+          type: "png",
+          pixelRatio: 2,
+          backgroundColor: "#fff",
+        });
+        return { ...info, url };
+      } finally {
+        this.barChart = prevChart;
+        inst.dispose();
+        document.body.removeChild(div);
+      }
+    },
     // 鏍煎紡鍖栫櫨鍒嗘瘮
     formatPercent(value) {
       if (value === null || value === undefined) return "-";
diff --git a/src/views/Satisfaction/sfstatistics/components/visitStatistics.vue b/src/views/Satisfaction/sfstatistics/components/visitStatistics.vue
index 2c7ed98..f2439a7 100644
--- a/src/views/Satisfaction/sfstatistics/components/visitStatistics.vue
+++ b/src/views/Satisfaction/sfstatistics/components/visitStatistics.vue
@@ -936,25 +936,27 @@
           },
         };
 
-        // 娣诲姞鎬绘爣棰�
-        worksheet.mergeCells(1, 1, 1, 10);
-        const titleCell = worksheet.getCell(1, 1);
-        titleCell.value = `澶嶈瘖閫氱煡缁熻琛紙${sheetNameSuffix}锛塦;
-        titleCell.style = titleStyle;
-        worksheet.getRow(1).height = 35;
-
-        // 娣诲姞琛ㄥご
+        // 琛ㄥご鎸夐〉闈㈠垪鍔ㄦ�佺敓鎴愶細銆屽強鏃剁巼銆嶅彧鏈夋櫙瀹侀櫌鍖洪〉闈㈡墠鏄剧ず
+        const showTimelyRate = this.orgname === "鏅畞鐣叉棌鑷不鍘夸汉姘戝尰闄�";
         const headers = [
           this.queryParams.statisticaltype == 1 ? "鍑洪櫌鐥呭尯" : "绉戝",
           "鍑洪櫌浜烘",
           "鏃犻渶闅忚浜烘",
           "搴旈殢璁夸汉娆�",
           "闅忚鐜�",
-          "鍙婃椂鐜�",
-          "澶嶈瘖閫氱煡棰樼洰鎬婚噺",
-          "澶嶈瘖閫氱煡濉姤閲�",
-          "瀹屾垚姣旂巼",
         ];
+        if (showTimelyRate) {
+          headers.push("鍙婃椂鐜�");
+        }
+        headers.push("澶嶈瘖閫氱煡棰樼洰鎬婚噺", "澶嶈瘖閫氱煡濉姤閲�", "瀹屾垚姣旂巼");
+        const colCount = headers.length;
+
+        // 娣诲姞鎬绘爣棰�
+        worksheet.mergeCells(1, 1, 1, colCount);
+        const titleCell = worksheet.getCell(1, 1);
+        titleCell.value = `澶嶈瘖閫氱煡缁熻琛紙${sheetNameSuffix}锛塦;
+        titleCell.style = titleStyle;
+        worksheet.getRow(1).height = 35;
 
         const headerRow = worksheet.addRow(headers);
         headerRow.eachCell((cell) => {
@@ -962,21 +964,30 @@
         });
         headerRow.height = 25;
 
-        // 娣诲姞鏁版嵁琛�
+        // 娣诲姞鏁版嵁琛岋紙绌哄�肩粺涓�鏄剧ず鈥�-鈥濓紝涓庨〉闈繚鎸佷竴鑷达級
+        const dash = (v, formatter) => {
+          if (v === null || v === undefined || v === "") return "-";
+          return formatter ? formatter(v) : v;
+        };
         this.userList.forEach((item) => {
-          const dataRow = worksheet.addRow([
+          const rowData = [
             this.queryParams.statisticaltype == 1
               ? item.leavehospitaldistrictname
               : item.deptname,
             item.dischargeCount || 0,
             item.nonFollowUp || 0,
             item.followUpNeeded || 0,
-            item.followUpRate || "0%",
-            item.rate ? this.formatPercent(item.rate) : "0%",
+            dash(item.followUpRate),
+          ];
+          if (showTimelyRate) {
+            rowData.push(dash(item.rate, (v) => this.formatPercent(v)));
+          }
+          rowData.push(
             item.joyAllCount || 0,
             item.joyCount || 0,
-            item.joyTotal ? this.formatPercent(item.joyTotal) : "0%",
-          ]);
+            dash(item.joyTotal, (v) => this.formatPercent(v))
+          );
+          const dataRow = worksheet.addRow(rowData);
 
           dataRow.eachCell((cell) => {
             cell.style = cellStyle;
@@ -984,18 +995,10 @@
           dataRow.height = 22;
         });
 
-        // 璁剧疆鍒楀
-        worksheet.columns = [
-          { width: 20 },
-          { width: 12 },
-          { width: 12 },
-          { width: 12 },
-          { width: 12 },
-          { width: 12 },
-          { width: 15 },
-          { width: 15 },
-          { width: 12 },
-        ];
+        // 璁剧疆鍒楀锛堣窡鍒楁暟涓�鑷达級
+        worksheet.columns = headers.map((h, i) => ({
+          width: i === 0 ? 20 : h.length > 6 ? 15 : 12,
+        }));
 
         // 鐢熸垚骞朵笅杞芥枃浠�
         const buffer = await workbook.xlsx.writeBuffer();

--
Gitblit v1.9.3