liusheng
2026-10-30 d725dc65cf7f74df3aa3f5a0297fd6f8b767cdbc
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">(六档评分题:很满意/满意/一般/不满意/很不满意/未经历)</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 = [
        ["各服务项目满意度统计(六档评分题:很满意/满意/一般/不满意/很不满意/未经历)"],
        [
          "分类/题目",
          "很满意", "", "满意", "", "一般", "",
          "不满意", "", "很不满意", "", "未经历", "",
          "综合得分",
        ],
        [
          "",
          "数量", "比例", "数量", "比例", "数量", "比例",
          "数量", "比例", "数量", "比例", "数量", "比例",
          "",
        ],
      ];
      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 = [
        ["其他题目统计(六档满意度题已在上表,这里只列其余题型:推荐、抱怨回应、意见等)"],
        [],
      ];
      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>