liusheng
2026-10-30 d725dc65cf7f74df3aa3f5a0297fd6f8b767cdbc
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(
          '综合得分趋势',
          '患者满意度综合得分趋势',
          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}维度 · 门诊患者满意度得分排名`,
          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('请允许弹出窗口以导出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();
    },
  },
};