WXL (wul)
5 天以前 77970a545af56e946e439ead97b23557d0b44f0f
提交
已修改4个文件
已添加2个文件
500 ■■■■■ 文件已修改
src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue 268 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/DataOverview.vue 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/index.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/CombinedFollowUp.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,268 @@
<template>
  <div class="charts-panel" ref="pdfTarget">
    <!-- PDF导出按钮 -->
    <div class="export-bar">
      <el-button type="danger" size="small" icon="el-icon-download" @click="exportPDF">导出PDF</el-button>
    </div>
    <el-row :gutter="20">
      <!-- 1. æ—¶é—´ç»´åº¦ - æŠ˜çº¿å›¾ -->
      <el-col :span="12">
        <el-card shadow="never" class="chart-card">
          <div slot="header" class="card-header">
            <span>时间维度 Â· é—¨è¯Šæ‚£è€…满意度趋势</span>
            <el-tag type="success" size="small">环比 +0.97</el-tag>
          </div>
          <div ref="chart1" class="chart-box"></div>
        </el-card>
      </el-col>
      <!-- 2. åˆ†ç±»ç»´åº¦ - åˆ†ç»„柱状图 -->
      <el-col :span="12">
        <el-card shadow="never" class="chart-card">
          <div slot="header" class="card-header">
            <span>分类维度 Â· å„指标年度对比</span>
          </div>
          <div ref="chart2" class="chart-box"></div>
        </el-card>
      </el-col>
    </el-row>
    <el-row :gutter="20" style="margin-top: 20px">
      <!-- 3. å•题选项 - æ•°æ®è¡¨æ ¼ -->
      <el-col :span="12">
        <el-card shadow="never" class="chart-card">
          <div slot="header" class="card-header">
            <span>单题 Â· ç¬¬22题:抱怨和不满能否及时得到回应?</span>
          </div>
          <el-table :data="singleQuestionData" border size="small">
            <el-table-column prop="option" label="选项" />
            <el-table-column prop="count" label="合计数量" width="120" align="center" />
            <el-table-column prop="rate" label="占比比例" width="120" align="center" />
          </el-table>
          <div class="table-footer">本题有效填写人次:<b>26,233</b> 综合满意度:<b>83.20%</b></div>
        </el-card>
      </el-col>
      <!-- 4. é™¢åŒºç»´åº¦ - åˆ†ç»„柱状图 -->
      <el-col :span="12">
        <el-card shadow="never" class="chart-card">
          <div slot="header" class="card-header">
            <span>院区维度 Â· å„院区年度对比</span>
          </div>
          <div ref="chart4" class="chart-box"></div>
        </el-card>
      </el-col>
    </el-row>
    <el-row :gutter="20" style="margin-top: 20px">
      <!-- 5. ç§‘室维度 - æŽ’名表格 -->
      <el-col :span="12">
        <el-card shadow="never" class="chart-card">
          <div slot="header" class="card-header">
            <span>科室维度 Â· é—¨è¯Šæ‚£è€…满意度得分排名</span>
          </div>
          <el-table :data="deptRankData" border size="small">
            <el-table-column type="index" label="排名" width="60" align="center" />
            <el-table-column prop="dept" label="科室" />
            <el-table-column prop="score" label="综合得分" width="120" align="center" />
          </el-table>
        </el-card>
      </el-col>
      <!-- 6. å•题科室 - äº¤å‰åˆ†æžè¡¨æ ¼ -->
      <el-col :span="12">
        <el-card shadow="never" class="chart-card">
          <div slot="header" class="card-header">
            <span>单题×科室 Â· ç¬¬22题各科室得分</span>
          </div>
          <el-table :data="crossDeptData" border size="small" max-height="400">
            <el-table-column type="index" label="排名" width="60" align="center" />
            <el-table-column prop="dept" label="科室" />
            <el-table-column prop="score" label="综合得分" width="120" align="center" />
          </el-table>
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>
<script>
import * as echarts from 'echarts';
export default {
  name: "ChartsPanel",
  data() {
    return {
      singleQuestionData: [
        { option: "非常满意", count: 16513, rate: "62.95%" },
        { option: "满意", count: 7088, rate: "27.02%" },
        { option: "一般", count: 1760, rate: "6.71%" },
        { option: "不满意", count: 872, rate: "3.32%" },
      ],
      deptRankData: [
        { dept: "保健科", score: "81.54" }, { dept: "介入治疗科", score: "80.72" },
        { dept: "专科护士门诊", score: "79.27" }, { dept: "康复医学科", score: "78.65" },
        { dept: "皮肤科", score: "78.12" }, { dept: "内分泌科", score: "77.89" },
        { dept: "消化内科", score: "77.34" }, { dept: "肾内科", score: "76.50" },
        { dept: "呼吸内科", score: "75.88" }, { dept: "心血管内科", score: "75.21" },
      ],
      crossDeptData: [
        { dept: "保健科", score: "81.24" }, { dept: "介入治疗科", score: "81.22" },
        { dept: "专科护士门诊", score: "80.15" }, { dept: "康复医学科", score: "79.88" },
        { dept: "皮肤科", score: "79.01" }, { dept: "内分泌科", score: "78.45" },
        { dept: "消化内科", score: "77.92" }, { dept: "肾内科", score: "77.13" },
        { dept: "呼吸内科", score: "76.80" }, { dept: "心血管内科", score: "76.05" },
        { dept: "神经内科", score: "75.62" }, { dept: "血液科", score: "75.01" },
        { dept: "风湿免疫科", score: "74.38" }, { dept: "肿瘤内科", score: "73.85" },
        { dept: "感染科", score: "73.12" }, { dept: "老年科", score: "72.56" },
        { dept: "普外科", score: "71.90" }, { dept: "骨科", score: "71.23" },
        { dept: "泌尿外科", score: "70.55" }, { dept: "妇产科", score: "69.88" },
      ],
    };
  },
  mounted() { this.$nextTick(() => { this.initCharts(); }); },
  methods: {
    initCharts() {
      const quarters = ['第一季度', '第二季度', '第三季度', '第四季度'];
      const categories = ['隐私保护', '环境与标识', '医务人员回应', '医生沟通', '挂号体验', '护士沟通'];
      const hospitals = ['院区一', '院区二', '院区三'];
      const baseGrid = { top: 20, right: 30, bottom: 30, left: 50 };
      const baseTooltip = { backgroundColor: 'rgba(255,255,255,0.95)', borderColor: '#e8e8e8', textStyle: { color: '#333', fontSize: 13 }, extraCssText: 'box-shadow: 0 2px 12px rgba(0,0,0,0.1); border-radius: 6px; padding: 10px 14px;' };
      const baseXAxis = { axisLine: { lineStyle: { color: '#e0e0e0' } }, axisTick: { show: false }, axisLabel: { color: '#666', fontSize: 12 } };
      const baseYAxis = { splitLine: { lineStyle: { color: '#f0f0f0', type: 'dashed' } }, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: '#999', fontSize: 11 } };
      // 1. æŠ˜çº¿å›¾ - æ—¶é—´ç»´åº¦
      const c1 = echarts.init(this.$refs.chart1);
      c1.setOption({
        color: ['#5B8FF9'],
        grid: { ...baseGrid, top: 25 },
        tooltip: { ...baseTooltip, trigger: 'axis' },
        xAxis: [{ ...baseXAxis, data: quarters, boundaryGap: false }],
        yAxis: [{ ...baseYAxis, min: 86, max: 91, name: '满意度得分', nameTextStyle: { color: '#999', fontSize: 11 } }],
        series: [{
          name: '门诊患者满意度', type: 'line', smooth: true, symbol: 'circle', symbolSize: 8,
          data: [88.20, 87.32, 88.50, 89.17],
          lineStyle: { width: 3, shadowBlur: 8, shadowColor: 'rgba(91,143,249,0.3)' },
          itemStyle: { color: '#5B8FF9' },
          areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: 'rgba(91,143,249,0.25)' }, { offset: 1, color: 'rgba(91,143,249,0.02)' }
          ])},
        }],
      });
      // 2. åˆ†ç»„柱状图 - åˆ†ç±»ç»´åº¦
      const c2 = echarts.init(this.$refs.chart2);
      c2.setOption({
        color: ['#6DC8EC', '#5B8FF9'],
        grid: baseGrid,
        tooltip: { ...baseTooltip, trigger: 'axis', axisPointer: { type: 'shadow', shadowStyle: { color: 'rgba(0,0,0,0.03)' } } },
        legend: { data: ['2023å¹´', '2024å¹´'], bottom: 0, textStyle: { color: '#666', fontSize: 12 } },
        xAxis: [{ ...baseXAxis, data: categories, axisLabel: { ...baseXAxis.axisLabel, interval: 0 } }],
        yAxis: [{ ...baseYAxis, name: '得分', nameTextStyle: { color: '#999', fontSize: 11 } }],
        series: [
          { name: '2023å¹´', type: 'bar', barWidth: 22, barGap: '30%',
            itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#6DC8EC' }, { offset: 1, color: '#48B0D5' }]) },
            data: [86.34, 85.12, 83.45, 86.22, 88.55, 87.33] },
          { name: '2024å¹´', type: 'bar', barWidth: 22,
            itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#5B8FF9' }, { offset: 1, color: '#3D6FDB' }]) },
            data: [88.06, 84.57, 82.88, 87.77, 88.12, 87.00] },
        ],
      });
      // 4. é™¢åŒºç»´åº¦ - åˆ†ç»„柱状图
      const c4 = echarts.init(this.$refs.chart4);
      c4.setOption({
        color: ['#6DC8EC', '#5B8FF9'],
        grid: baseGrid,
        tooltip: { ...baseTooltip, trigger: 'axis', axisPointer: { type: 'shadow' } },
        legend: { data: ['2023å¹´', '2024å¹´'], bottom: 0, textStyle: { color: '#666', fontSize: 12 } },
        xAxis: [{ ...baseXAxis, data: hospitals }],
        yAxis: [{ ...baseYAxis, name: '满意度得分', nameTextStyle: { color: '#999', fontSize: 11 } }],
        series: [
          { name: '2023å¹´', type: 'bar', barWidth: 24, barGap: '30%',
            itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#6DC8EC' }, { offset: 1, color: '#48B0D5' }]) },
            data: [86.20, 87.69, 85.55],
            label: { show: true, position: 'top', color: '#666', fontSize: 12, fontWeight: 500, formatter: p => p.value.toFixed(2) } },
          { name: '2024å¹´', type: 'bar', barWidth: 24,
            itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#5B8FF9' }, { offset: 1, color: '#3D6FDB' }]) },
            data: [88.15, 88.14, 85.88],
            label: { show: true, position: 'top', color: '#666', fontSize: 12, fontWeight: 500, formatter: p => p.value.toFixed(2) } },
        ],
      });
      // å“åº”式
      window.addEventListener('resize', () => { c1.resize(); c2.resize(); c4.resize(); });
    },
    async exportPDF() {
      // å…ˆå°† ECharts å®žä¾‹è½¬ä¸ºå›¾ç‰‡ dataURL
      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' });
          }
        }
      }
      // å…‹éš†å†…容并替换图表为图片
      const printContent = this.$refs.pdfTarget.cloneNode(true);
      for (const { ref, id } of chartRefs) {
        if (chartImages[id] && ref) {
          const boxes = printContent.querySelectorAll('.chart-box');
          for (const box of boxes) {
            if (box.innerHTML.includes(id) || boxes.length === chartRefs.length) {
              box.innerHTML = `<img src="${chartImages[id]}" style="width:100%;height:auto;" />`;
            }
          }
        }
      }
      // æ›´å¯é çš„æ–¹å¼ï¼šæŒ‰ä½ç½®åŒ¹é…
      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, .el-tag { 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();
    },
  },
};
</script>
<style lang="scss" scoped>
.export-bar { text-align: right; margin-bottom: 16px; }
.chart-card { margin-bottom: 0; }
.chart-box { width: 100%; height: 360px; }
.card-header { display: flex; justify-content: space-between; align-items: center; span { font-size: 15px; font-weight: 600; color: #303133; } }
.table-footer { text-align: right; padding: 10px 0 0; font-size: 13px; color: #606266; }
</style>
src/views/Satisfaction/sfstatistics/components/DataOverview.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,195 @@
<template>
  <div class="data-overview">
    <!-- è¡¨æ ¼ä¸€ï¼šå„服务项目满意度统计 -->
    <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>
      </div>
      <el-table id="table1" :data="serviceSatisfaction" border size="small" :header-cell-style="{ background: '#f5f7fa', color: '#303133' }">
        <el-table-column prop="category" label="分类/题目" min-width="220" fixed="left" />
        <el-table-column label="非常满意" align="center">
          <el-table-column prop="veryGoodCount" label="数量" width="90" />
          <el-table-column prop="veryGoodRate" label="比例" width="90" />
        </el-table-column>
        <el-table-column label="满意" align="center">
          <el-table-column prop="goodCount" label="数量" width="90" />
          <el-table-column prop="goodRate" label="比例" width="90" />
        </el-table-column>
        <el-table-column label="一般" align="center">
          <el-table-column prop="normalCount" label="数量" width="90" />
          <el-table-column prop="normalRate" label="比例" width="90" />
        </el-table-column>
        <el-table-column label="不满意" align="center">
          <el-table-column prop="badCount" label="数量" width="90" />
          <el-table-column prop="badRate" label="比例" width="90" />
        </el-table-column>
        <el-table-column label="不适用" align="center">
          <el-table-column prop="naCount" label="数量" width="90" />
          <el-table-column prop="naRate" label="比例" width="90" />
        </el-table-column>
        <el-table-column prop="score" label="综合得分" width="100" align="center" />
      </el-table>
    </el-card>
    <!-- è¡¨æ ¼äºŒï¼šå•选题/多选题统计 -->
    <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('table2')">导出Excel</el-button>
      </div>
      <div class="question-block" v-for="(q, qi) in questionStats" :key="qi">
        <h4 class="q-title">{{ q.title }}</h4>
        <el-table :data="q.options" border size="small" :header-cell-style="{ background: '#f5f7fa', color: '#303133' }">
          <el-table-column prop="option" label="选项" min-width="180" />
          <el-table-column prop="count" label="数量" width="100" align="center" />
          <el-table-column prop="rate" label="比例" width="100" align="center" />
        </el-table>
        <div v-if="q.total" class="q-footer">本题有效填写人次:<b>{{ q.total }}</b></div>
      </div>
    </el-card>
    <!-- è¡¨æ ¼ä¸‰ï¼šå„科室满意度评分对比 -->
    <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('table3')">导出Excel</el-button>
      </div>
      <el-table id="table3" :data="deptComparison" border size="small" :header-cell-style="{ background: '#f5f7fa', color: '#303133' }">
        <el-table-column prop="dept" label="科室"fixed="left" align="center" />
        <el-table-column label="第1题" align="center">
          <el-table-column prop="q1VeryGood" label="非常满意" width="100" />
          <el-table-column prop="q1Good" label="满意" width="90" />
          <el-table-column prop="q1Normal" label="一般" width="90" />
          <el-table-column prop="q1Bad" label="不满意" width="90" />
          <el-table-column prop="q1Score" label="得分" width="100" />
        </el-table-column>
        <el-table-column label="第2题" align="center">
          <el-table-column prop="q2VeryGood" label="非常满意" width="100" />
          <el-table-column prop="q2Good" label="满意" width="90" />
          <el-table-column prop="q2Normal" label="一般" width="90" />
          <el-table-column prop="q2Bad" label="不满意" width="90" />
          <el-table-column prop="q2Score" label="得分" width="100" />
        </el-table-column>
        <el-table-column prop="totalScore" label="综合得分"  align="center" />
      </el-table>
    </el-card>
  </div>
</template>
<script>
import XLSX from 'xlsx';
export default {
  name: "DataOverview",
  data() {
    return {
      serviceSatisfaction: [
        { category: "诊疗过程中,医务人员是否注意保护您的隐私?", veryGoodCount: 3219, veryGoodRate: "67.84%", goodCount: 1351, goodRate: "28.47%", normalCount: 142, normalRate: "2.99%", badCount: 33, badRate: "0.70%", naCount: 0, naRate: "0.00%", score: "87.82" },
        { category: "您对医院服务设施的总体印象如何?", veryGoodCount: 3110, veryGoodRate: "65.54%", goodCount: 1369, goodRate: "28.85%", normalCount: 217, normalRate: "4.57%", badCount: 49, badRate: "1.03%", naCount: 0, naRate: "0.00%", score: "86.30" },
        { category: "医院的厕所是否清洁无异味?", veryGoodCount: 2973, veryGoodRate: "62.66%", goodCount: 1504, goodRate: "31.70%", normalCount: 235, normalRate: "4.95%", badCount: 33, badRate: "0.70%", naCount: 0, naRate: "0.00%", score: "85.44" },
        { category: "医院内的路标和指示是否明确?", veryGoodCount: 3234, veryGoodRate: "68.16%", goodCount: 1289, goodRate: "27.17%", normalCount: 196, normalRate: "4.13%", badCount: 26, badRate: "0.55%", naCount: 0, naRate: "0.00%", score: "87.64" },
        { category: "医院的空间布局是否便利?", veryGoodCount: 3037, veryGoodRate: "64.00%", goodCount: 1460, goodRate: "30.77%", normalCount: 209, normalRate: "4.40%", badCount: 39, badRate: "0.82%", naCount: 0, naRate: "0.00%", score: "85.99" },
        { category: "看病期间,您的抱怨和不满能否及时得到回应?", veryGoodCount: 3019, veryGoodRate: "63.62%", goodCount: 1317, goodRate: "27.76%", normalCount: 294, normalRate: "6.20%", badCount: 115, badRate: "2.42%", naCount: 0, naRate: "0.00%", score: "84.19" },
        { category: "医生是否用您听得懂的方式解释问题?", veryGoodCount: 3402, veryGoodRate: "71.70%", goodCount: 967, goodRate: "20.38%", normalCount: 254, normalRate: "5.35%", badCount: 122, badRate: "2.57%", naCount: 0, naRate: "0.00%", score: "87.07" },
        { category: "医生对您是否尊重?", veryGoodCount: 3495, veryGoodRate: "73.66%", goodCount: 897, goodRate: "18.90%", normalCount: 249, normalRate: "5.25%", badCount: 104, badRate: "2.19%", naCount: 0, naRate: "0.00%", score: "88.01" },
        { category: "医生是否仔细倾听您讲话?", veryGoodCount: 3422, veryGoodRate: "72.12%", goodCount: 937, goodRate: "19.75%", normalCount: 251, normalRate: "5.29%", badCount: 135, badRate: "2.85%", naCount: 0, naRate: "0.00%", score: "87.05" },
        { category: "对挂号服务人员服务态度是否满意", veryGoodCount: 3458, veryGoodRate: "72.88%", goodCount: 1205, goodRate: "25.40%", normalCount: 52, normalRate: "1.10%", badCount: 30, badRate: "0.63%", naCount: 0, naRate: "0.00%", score: "90.17" },
        { category: "您对预约挂号是否满意?", veryGoodCount: 3527, veryGoodRate: "74.33%", goodCount: 1088, goodRate: "22.93%", normalCount: 72, normalRate: "1.52%", badCount: 58, badRate: "1.22%", naCount: 0, naRate: "0.00%", score: "90.12" },
        { category: "护士对您是否尊重?", veryGoodCount: 3420, veryGoodRate: "72.08%", goodCount: 1184, goodRate: "24.95%", normalCount: 113, normalRate: "2.38%", badCount: 28, badRate: "0.59%", naCount: 0, naRate: "0.00%", score: "89.50" },
        { category: "护士是否用您听得懂的方式解释问题?", veryGoodCount: 3346, veryGoodRate: "70.52%", goodCount: 1250, goodRate: "26.34%", normalCount: 122, normalRate: "2.57%", badCount: 27, badRate: "0.57%", naCount: 0, naRate: "0.00%", score: "88.94" },
        { category: "护士是否仔细倾听您讲话?", veryGoodCount: 3378, veryGoodRate: "71.19%", goodCount: 1205, goodRate: "25.40%", normalCount: 134, normalRate: "2.82%", badCount: 28, badRate: "0.59%", naCount: 0, naRate: "0.00%", score: "89.06" },
        { category: "总计", veryGoodCount: 54863, veryGoodRate: "69.02%", goodCount: 20800, goodRate: "26.17%", normalCount: 2920, normalRate: "3.67%", badCount: 908, badRate: "1.14%", naCount: 0, naRate: "0.00%", score: "87.66" },
      ],
      questionStats: [
        {
          title: "第1题:您对我院工作人员的服务质量满意不满意?【分值单选题】- ç»¼åˆå¾—分",
          options: [
            { option: "非常满意", count: 1084, rate: "75.66%" },
            { option: "满意", count: 1959, rate: "20.92%" },
            { option: "一般", count: 167, rate: "1.78%" },
            { option: "不满意", count: 153, rate: "1.63%" },
          ],
          total: 3363
        },
        {
          title: "第2题:您对哪部分服务不满意?【多选题】",
          options: [
            { option: "医生诊断和诊疗", count: 131, rate: "32.11%" },
            { option: "护理服务", count: 47, rate: "11.52%" },
            { option: "其他医务人员技术水平", count: 30, rate: "7.33%" },
            { option: "工人", count: 72, rate: "17.65%" },
          ],
          total: 408
        },
        {
          title: "第3题:医生诊断和治疗中哪些服务质量不满意?【多选题】",
          options: [
            { option: "医生诊断错误", count: 23, rate: "15.44%" },
            { option: "对治疗效果不满意", count: 72, rate: "48.32%" },
            { option: "其他", count: 54, rate: "36.24%" },
          ],
          total: 131
        },
      ],
      deptComparison: [
        { dept: "内分泌科", q1VeryGood: 103, q1Good: 34, q1Normal: 0, q1Bad: 0, q1Score: "377.00", q2VeryGood: 101, q2Good: 35, q2Normal: 1, q2Bad: 0, q2Score: "374.00", totalScore: "75.25" },
        { dept: "皮肤科", q1VeryGood: 112, q1Good: 25, q1Normal: 2, q1Bad: 3, q1Score: "388.00", q2VeryGood: 111, q2Good: 27, q2Normal: 3, q2Bad: 1, q2Score: "390.00", totalScore: "76.12" },
        { dept: "肿瘤内科", q1VeryGood: 24, q1Good: 12, q1Normal: 2, q1Bad: 0, q1Score: "98.00", q2VeryGood: 25, q2Good: 12, q2Normal: 1, q2Bad: 0, q2Score: "100.00", totalScore: "70.22" },
        { dept: "消化内科", q1VeryGood: 180, q1Good: 52, q1Normal: 1, q1Bad: 0, q1Score: "645.00", q2VeryGood: 168, q2Good: 62, q2Normal: 3, q2Bad: 0, q2Score: "631.00", totalScore: "73.56" },
        { dept: "肾内科", q1VeryGood: 21, q1Good: 4, q1Normal: 0, q1Bad: 0, q1Score: "71.00", q2VeryGood: 19, q2Good: 6, q2Normal: 0, q2Bad: 0, q2Score: "65.00", totalScore: "68.50" },
      ],
    };
  },
  methods: {
    exportTable(tableId) {
      const tableNames = { table1: '各服务项目满意度统计', table2: '单选题多选题统计', table3: '各科室满意度评分对比' };
      const name = tableNames[tableId] || '统计数据';
      const table = document.getElementById(tableId);
      if (!table) return;
      try {
        // ä»Žè¡¨æ ¼æå–数据构建带样式的 Excel
        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);
        });
        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');
      } 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();
      }
    },
  },
};
</script>
<style lang="scss" scoped>
.data-overview { .overview-card { margin-bottom: 20px; } }
.card-header { display: flex; justify-content: space-between; align-items: center; }
.card-title { font-size: 16px; font-weight: 600; color: #303133; }
.question-block { margin-bottom: 24px;
  .q-title { margin: 0 0 12px 0; font-size: 14px; color: #606266; }
  .q-footer { margin-top: 8px; font-size: 13px; color: #909399; text-align: right; }
}
</style>
src/views/Satisfaction/sfstatistics/index.vue
@@ -3,16 +3,16 @@
  <div class="statistics-main">
    <el-tabs v-model="activeTab" @tab-click="handleTabChange">
      <el-tab-pane label="满意度随访统计" name="followup">
        <followup-statistics
          v-if="activeTab === 'followup'"
          ref="followupRef"
        />
        <followup-statistics v-if="activeTab === 'followup'" ref="followupRef" />
      </el-tab-pane>
      <el-tab-pane label="满意度明细统计" name="satisfaction">
        <satisfaction-statistics
          v-if="activeTab === 'satisfaction'"
          ref="satisfactionRef"
        />
        <satisfaction-statistics v-if="activeTab === 'satisfaction'" ref="satisfactionRef" />
      </el-tab-pane>
      <el-tab-pane label="统计数据总览" name="dataOverview">
        <data-overview v-if="activeTab === 'dataOverview'" />
      </el-tab-pane>
      <el-tab-pane label="各项统计表" name="chartsPanel">
        <charts-panel v-if="activeTab === 'chartsPanel'" />
      </el-tab-pane>
    </el-tabs>
  </div>
@@ -21,12 +21,16 @@
<script>
import FollowupStatistics from "./components/FollowupStatistics.vue";
import SatisfactionStatistics from "./components/SatisfactionStatistics.vue";
import DataOverview from "./components/DataOverview.vue";
import ChartsPanel from "./components/ChartsPanel.vue";
export default {
  name: "Satisfaction",
  components: {
    FollowupStatistics,
    SatisfactionStatistics,
    DataOverview,
    ChartsPanel,
  },
  data() {
    return {
src/views/sfstatistics/percentage/components/CombinedFollowUp.vue
@@ -173,9 +173,6 @@
            </el-button>
          </template>
        </el-table-column>
        <!-- ===== é¦–次随访 ===== -->
        <el-table-column align="center" label="首次随访">
          <el-table-column label="无需随访" align="center" prop="nonFollowUp">
            <template slot-scope="scope">
              <el-button type="text" @click="handleViewDetails(scope.row, 'nonFollowUpInfo', '首次无需随访列表')">
@@ -190,6 +187,10 @@
              </el-button>
            </template>
          </el-table-column>
        <!-- ===== é¦–次随访 ===== -->
        <el-table-column align="center" label="首次随访">
          <el-table-column label="需随访" align="center" prop="needFollowUp">
            <template slot-scope="scope">
              <el-button type="text" @click="handleViewDetails(scope.row, 'needFollowUpInfo', '首次需随访列表')">
src/views/sfstatistics/percentage/index.vue
@@ -475,8 +475,14 @@
        obj.deptcodes = [row.deptcode];
        obj.leavehospitaldistrictcodes = [];
      }
      if (this.queryParams.endTime) {
      obj.endTime = this.queryParams.endTime;
      obj.startTime = this.queryParams.startTime;
      } else {
        obj.startTime = this.parseTime(this.queryParams.dateRange[0]);
        obj.endTime = this.parseTime(this.queryParams.dateRange[1]);
      }
      obj.statisticaltype = this.queryParams.statisticaltype;
      getSfStatisticsHyperlink(obj).then((response) => {
vue.config.js
@@ -36,8 +36,8 @@
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      [process.env.VUE_APP_BASE_API]: {
        // target: `https://www.health-y.cn/lssf`,
        // target: `http://192.168.100.10:8096`,
        target: `http://192.168.100.10:8094`,//省立同德
        target: `http://192.168.100.10:8096`,
        // target: `http://192.168.100.10:8094`,//省立同德
        // target: `http://192.168.100.10:8095`,//新华
        // target: `http://192.168.100.10:8098`,//市一
        // target:`http://localhost:8095`,