WXL (wul)
2026-04-15 a7eb9f0ef66e3528a58f0f6c140e7bf3eda6e596
测试完成
已修改6个文件
已添加12个文件
10274 ■■■■■ 文件已修改
dist.zip 补丁 | 查看 | 原始文档 | blame | 历史
ls&sltd.zip 补丁 | 查看 | 原始文档 | blame | 历史
sltd.zip 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/configurationmyd/index.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/discharge/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/record/detailpage/index.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/ChartDialog.vue 579 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/ContinuedCare.vue 633 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/DetailDialog.vue 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/FirstFollowUp.vue 728 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/SecondFollowUp.vue 679 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/TimelyRateDialog.vue 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/styles.scss 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/index copy.vue 2898 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/index.vue 3910 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
市一.zip 补丁 | 查看 | 原始文档 | blame | 历史
dist.zip
Binary files differ
ls&sltd.zip
Binary files differ
sltd.zip
Binary files differ
src/api/system/user.js
@@ -102,6 +102,14 @@
    data: data,
  });
}
// å»¶ç»­æŠ¤ç†ç»Ÿè®¡
export function getContinueNerseCount(data) {
  return request({
    url: "/smartor/serviceSubtask/getContinueNerseCount",
    method: "post",
    data: data,
  });
}
// æ»¡æ„åº¦ç»Ÿè®¡è¯¦æƒ…
export function getSfStatisticsJoyInfo(data) {
  return request({
src/views/Satisfaction/configurationmyd/index.vue
@@ -976,12 +976,16 @@
      return new Promise((resolve) => {
        getQtemplatelist({ pageSize: 1000 })
          .then((res) => {
            if (res.code === 200) {
            console.log(res.rows, "res.rows");
            if (res.code == 200) {
              console.log(res.rows, 2);
              this.questionnaireTemplates = (res.rows || []).map((item) => ({
                id: item.svyid,
                templateName: item.svyname,
                isavailable: item.isavailable,
              }));
              console.log(this.followupTemplates, 3);
            } else {
              this.$message.error(res.msg || "加载问卷模板失败");
            }
@@ -1000,12 +1004,16 @@
      return new Promise((resolve) => {
        getFollowuplist({ pageSize: 1000 })
          .then((res) => {
            if (res.code === 200) {
            console.log(res.rows, "res.rows");
            if (res.code == 200) {
              console.log(res.rows, 2);
              this.followupTemplates = (res.rows || []).map((item) => ({
                id: item.id,
                templateName: item.templateName,
                isavailable: item.isavailable,
              }));
              console.log(this.followupTemplates, 3);
            } else {
              this.$message.error(res.msg || "加载语音模板失败");
            }
@@ -1200,8 +1208,10 @@
    /** é…ç½®å˜æ›´å¤„理 */
    handleConfigChange(question) {
      this.$nextTick(() => {
        const index = this.filteredQuestionList.findIndex((q) => q.id === question.id);
        console.log(index,'index');
        const index = this.filteredQuestionList.findIndex(
          (q) => q.id === question.id
        );
        console.log(index, "index");
        if (index !== -1) {
          const formRef = this.$refs.configForm && this.$refs.configForm[index];
src/views/followvisit/discharge/index.vue
@@ -210,7 +210,7 @@
      </el-form>
      <el-divider></el-divider>
      <el-row :gutter="10" class="mb8">
        <!-- <el-col :span="1.5">
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
@@ -223,7 +223,7 @@
              >
            </div>
          </div>
        </el-col> -->
        </el-col>
        <el-col :span="1.5">
          <el-button
            type="primary"
@@ -2047,7 +2047,7 @@
        {
          ...this.topqueryParams,
        },
        `user_${new Date().getTime()}.xlsx`
        `user_${new Date().getTime()}.xlsx`,
      );
    },
    // å¼‚常列渲染
src/views/followvisit/record/detailpage/index.vue
@@ -447,7 +447,7 @@
                      <div class="scriptTopic-dev" :key="index" v-else>
                        <div class="dev-text">
                          {{ index + 1 }}、[问答]<span>{{
                            item.scriptContent
                            item.questiontext
                          }}</span>
                          <span v-if="item.valueType == 3">(只能输入数字)</span>
                        </div>
@@ -1570,8 +1570,6 @@
  methods: {
    // èŽ·å–ä¸»é¢˜æ ·å¼ç±»
    getTopicClass(item) {
      console.log(item.isabnormal, "getTopicClass");
      // æ ¹æ®çŠ¶æ€å€¼è¿”å›žå¯¹åº”çš„æ ·å¼ç±»
      if (item.isabnormal == 1) {
        return "scriptTopic-isabnormal"; // å¼‚常 - çº¢è‰²
@@ -1614,13 +1612,15 @@
          this.tableDatatop = res.data.scriptResult;
          this.tableDatatop.forEach((item) => {
            if (item.scriptType == 2) item.scriptResult = [];
            if (item.scriptResultId && item.scriptType != 2) {
              item.isoption = 3;
              item.scriptResult = item.scriptResult;
            } else if (item.scriptResultId && item.scriptType == 2) {
              console.log(item.scriptResult, "item.scriptResult");
              item.scriptResult = item.scriptResult.split("&");
              item.isoption = 3;
            } else if (!item.scriptResultId && item.scriptType == 2) {
              item.scriptResult = [];
            }
          });
          this.taskname = res.data.taskName;
@@ -1744,7 +1744,7 @@
            if (item.targetvalue) {
              item.scriptResult = item.targetvalue.split("&");
            } else {
              item.scriptResult = [];
              item.scriptResult = item.asrtext;
            }
          });
@@ -1763,7 +1763,7 @@
            item.scriptID = item.id;
            item.id = null;
            // ç±»åž‹åˆ¤æ–­èµ‹å€¼
            if (item.ivrTaskScriptTargetoptionList) {
            if (item.ivrTaskScriptTargetoptionList.length) {
              item.targetvalue = 1;
              item.questiontext = item.scriptContent;
@@ -1773,8 +1773,6 @@
            }
            if (item.targetvalue) {
              item.scriptResult = item.targetvalue.split("&");
            } else {
              item.scriptResult = [];
            }
          });
        }
@@ -1782,16 +1780,18 @@
    },
    // åŒ»æŠ¤äººå‘˜å­˜å‚¨æ•°æ®
    getdetail() {
      console.log(1);
      let excep = "";
      const promises = [];
      // å…ˆå¤„理 tableDatatop ä¸­çš„æ•°æ®
      this.tableDatatop.forEach((item) => {
        if (item.valueType == 3 && item.scriptResult) {
          // éªŒè¯æ˜¯å¦ä¸ºæœ‰æ•ˆæ•°å­—
          if (!/^\d+$/.test(item.scriptResult)) {
            this.$message.error(`问题 "${item.scriptContent}" å¿…须输入数字`);
            return;
          }
        }
        var objs = item.svyTaskTemplateTargetoptions.find(
          (items) => items.optioncontent == item.scriptResult
        );
@@ -1802,36 +1802,36 @@
            this.selectedTag = objs.isabnormal;
          }
        }
        console.log(excep, "excep");
        let obj = {
          asrtext: null,
          patid: this.patid,
          subId: this.id,
          taskid: this.taskid,
          scriptid: item.id,
          excep: excep,
          questiontext: item.scriptContent,
          categoryid: item.categoryid,
          answerps: item.answerps || null, // æ·»åŠ é™„åŠ ä¿¡æ¯
        };
        if (item.scriptType == 2 && item.scriptResult[0]) {
          obj.asrtext = item.scriptResult.join("&");
          obj.ivrtext = item.scriptResult.join("&");
        // å¤„理 scriptResult,直接修改原始数据
        if (item.scriptType == 2 && item.scriptResult) {
          // å¤„理数组类型的 scriptResult
          if (
            Array.isArray(item.scriptResult) &&
            item.scriptResult.length > 0
          ) {
            item.originalScriptResult = item.scriptResult; // ä¿å­˜åŽŸå§‹æ•°ç»„ï¼ˆå¯é€‰ï¼‰
            item.scriptResult = item.scriptResult.join("&"); // è½¬æ¢ä¸ºå­—符串
          }
        } else if (item.scriptType != 2 && item.scriptResult) {
          obj.asrtext = item.scriptResult;
          obj.ivrtext = item.scriptResult;
          // ç¡®ä¿éžæ•°ç»„类型已经是字符串
          if (Array.isArray(item.scriptResult)) {
            item.originalScriptResult = item.scriptResult; // ä¿å­˜åŽŸå§‹æ•°ç»„ï¼ˆå¯é€‰ï¼‰
            item.scriptResult =
              item.scriptResult.length > 0 ? item.scriptResult[0] : "";
          }
        }
        // if (item.isoption == 3) {
        //   promises.push(serviceSubtaskDetailedit(obj));
        // } else {
        //   promises.push(serviceSubtaskDetailadd(obj));
        // }
      });
      console.log(this.tableDatatop);
      // åˆ›å»ºå‰¯æœ¬ç”¨äºŽä¿å­˜ï¼Œé¿å…å½±å“æ˜¾ç¤º
      const saveData = this.tableDatatop.map((item) => ({
        ...item,
        // å¦‚果需要,可以在这里做最后的数据清理
        scriptResult: item.scriptResult || "", // ç¡®ä¿ä¸ä¸º undefined
      }));
      let obj = {
        svyTaskTemplateScriptVOS: this.tableDatatop, // æäº¤å¤„理后的副本
        svyTaskTemplateScriptVOS: saveData, // ä½¿ç”¨å¤„理后的数据
        param1: this.taskid,
        param2: this.patid,
        param6: this.id,
@@ -1872,51 +1872,6 @@
            });
        }
      });
      // ä½¿ç”¨ Promise.all ç­‰å¾…所有异步操作完成
      // Promise.all(promises)
      //   .then((results) => {
      //     // æ‰€æœ‰å¼‚步操作成功完成后的逻辑
      //     results.forEach((res) => {
      //       if (res.code !== 200) {
      //         this.$modal.error("修改失败");
      //       }
      //     });
      //     this.Editsingletasksonyic(6);
      //     const orgName = localStorage.getItem("orgname");
      //     console.log(orgName, "orgName");
      //     if (this.form.isVisitAgain != 1 || orgName == "丽水市中医院") {
      //       this.Torouter();
      //       return;
      //     }
      //     this.$modal
      //       .confirm(
      //         '任务保存成功是否针对患者:"' +
      //           this.userform.name +
      //           '"再次随访?',
      //         "确认",
      //         {
      //           confirmButtonText: "确定",
      //           cancelButtonText: "取消",
      //           showCancelButton: true,
      //           dangerouslyUseHTMLString: true,
      //           confirmButtonClass: "custom-confirm-button", // è‡ªå®šä¹‰ç¡®è®¤æŒ‰é’®çš„类名
      //           cancelButtonClass: "custom-cancel-button", // è‡ªå®šä¹‰å–消按钮的类名
      //         }
      //       )
      //       .then(() => {
      //         document.querySelector("#app").scrollTo(0, 0);
      //         this.formtidy();
      //         this.dialogFormVisible = true;
      //       })
      //       .catch(() => {
      //         this.Torouter();
      //       });
      //   })
      //   .catch((error) => {
      //     // å¦‚果有任何一个异步操作失败,会进入这里
      //     console.error("发生错误:", error);
      //   });
    },
    Torouter() {
      if (this.form.serviceType == 13) {
@@ -2048,20 +2003,28 @@
    },
    yuyingetdetail() {
      const dataToSubmit = JSON.parse(JSON.stringify(this.tableDatatop));
      dataToSubmit.forEach((item, index) => {
        // å¯¹æ‹·è´çš„æ•°æ®è¿›è¡Œæ“ä½œï¼Œä¸å½±å“åŽŸå§‹çš„ scriptResult æ•°ç»„
        item.scriptResult = item.scriptResult.join("&");
        if (item.targetvalue) {
          item.scriptResult = item.scriptResult.join("&");
          item.asrtext = item.matchedtext;
          item.ivrtext = item.matchedtext;
        } else {
          item.asrtext = item.scriptResult;
          item.ivrtext = item.scriptResult;
        }
        item.templatequestionnum = index + 1;
        item.subId = this.id;
        item.taskid = this.taskid;
        item.asrtext = item.matchedtext;
        item.ivrtext = item.matchedtext;
        if (!item.id) {
          item.isoperation = 1;
        }
        item.patid = this.patid;
        item.templateid = item.templateID;
      });
      console.log("c", 3);
      let obj = {
        ivrTaskTemplateScriptVOList: dataToSubmit, // æäº¤å¤„理后的副本
@@ -2070,6 +2033,7 @@
        param6: this.id,
        type: 1,
      };
      console.log("c", 4);
      const orgName = localStorage.getItem("orgname");
      console.log(orgName, "orgName");
@@ -2322,35 +2286,7 @@
        })
        .catch(() => {});
    },
    aahandleOptionChange(a, b, c) {
      const result = c.find((item) => item.optioncontent == a);
      if (result.nextQuestion == 0) {
        this.tableDatatop = this.tableDatatop.reduce((acc, item, i) => {
          acc.push(i > b ? { ...item, astrict: 1 } : item);
          return acc;
        }, []);
      } else {
        this.tableDatatop = this.tableDatatop.reduce((acc, item, i) => {
          acc.push(i > b ? { ...item, astrict: 0 } : item);
          return acc;
        }, []);
      }
      if (this.Voicetype) {
        var obj = this.tableDatatop[b].ivrTaskScriptTargetoptionList.find(
          (item) => item.optioncontent == a
        );
      } else {
        var obj = this.tableDatatop[b].svyTaskTemplateTargetoptions.find(
          (item) => item.optioncontent == a
        );
      }
      if (obj.isabnormal) {
        this.tableDatatop[b].isabnormal = true;
      } else {
        this.tableDatatop[b].isabnormal = false;
      }
      this.$forceUpdate();
    },
    handleRadioToggles(questionItem, optionValue) {
      if (!questionItem.matchedtext) {
        questionItem.matchedtext == "";
@@ -2409,7 +2345,13 @@
      this.tableDatatop[questionIndex].showAppendInput =
        selectedOptionObj.appendflag == 1;
      console.log(this.tableDatatop);
      if (
        selectedOptionObj.nextQuestion !== undefined &&
        selectedOptionObj.nextQuestion !== null
      ) {
        this.tableDatatop[questionIndex].nextScriptno =
          selectedOptionObj.nextQuestion;
      }
      // if (!this.tableDatatop[questionIndex].showAppendInput) {
      //   this.tableDatatop[questionIndex].answerps = ""; // æ¸…除附加信息
      // }
@@ -2479,7 +2421,6 @@
          hiddenByEnd: index === questionIndex + 1 ? false : item.hiddenByEnd,
        }));
      }
      2;
      this.$forceUpdate();
    },
src/views/sfstatistics/percentage/components/ChartDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,579 @@
<template>
  <el-dialog
    title="随访统计趋势图"
    :visible.sync="visible"
    width="80%"
    :close-on-click-modal="false"
    @close="handleClose"
  >
    <div class="chart-container">
      <el-row :gutter="20">
        <el-col :span="12">
          <div class="chart-title">随访状态分布</div>
          <div id="pieChart" style="width: 100%; height: 400px"></div>
        </el-col>
        <el-col :span="12">
          <div class="chart-title">随访趋势分析</div>
          <div id="barLineChart" style="width: 100%; height: 400px"></div>
        </el-col>
      </el-row>
    </div>
  </el-dialog>
</template>
<script>
import * as echarts from 'echarts'
export default {
  name: 'ChartDialog',
  props: {
    visible: {
      type: Boolean,
      default: false
    },
    data: {
      type: Array,
      default: () => []
    },
    activeTab: {
      type: String,
      default: 'first'
    }
  },
  data() {
    return {
      pieChart: null,
      barLineChart: null
    }
  },
  watch: {
    visible(newVal) {
      if (newVal) {
        this.$nextTick(() => {
          this.initCharts()
        })
      } else {
        this.destroyCharts()
      }
    }
  },
  mounted() {
    if (this.visible) {
      this.$nextTick(() => {
        this.initCharts()
      })
    }
  },
  beforeDestroy() {
    this.destroyCharts()
  },
  methods: {
    initCharts() {
      this.initPieChart()
      this.initBarLineChart()
    },
    initPieChart() {
      const pieDom = document.getElementById('pieChart')
      if (!pieDom) return
      if (this.pieChart) {
        this.pieChart.dispose()
      }
      this.pieChart = echarts.init(pieDom)
      // æ ¹æ®å½“前tab计算饼图数据
      const pieData = this.getPieChartData()
      const pieOption = {
        title: {
          text: '随访状态分布',
          left: 'center',
          textStyle: {
            color: '#333',
            fontSize: 16
          }
        },
        tooltip: {
          trigger: 'item',
          formatter: '{a} <br/>{b}: {c} ({d}%)'
        },
        legend: {
          orient: 'vertical',
          left: 'left',
          data: pieData.legendData,
          textStyle: {
            color: '#666'
          }
        },
        color: ['#FF9D4D', '#36B37E', '#FF5C5C'],
        series: [
          {
            name: '随访状态',
            type: 'pie',
            radius: ['40%', '70%'],
            avoidLabelOverlap: true,
            itemStyle: {
              borderRadius: 10,
              borderColor: '#fff',
              borderWidth: 2
            },
            label: {
              show: true,
              formatter: '{b}: {c} ({d}%)',
              color: '#333'
            },
            emphasis: {
              label: {
                show: true,
                fontSize: '18',
                fontWeight: 'bold'
              },
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(0, 0, 0, 0.5)'
              }
            },
            data: pieData.seriesData
          }
        ]
      }
      this.pieChart.setOption(pieOption)
      window.addEventListener('resize', this.resizePieChart)
    },
    getPieChartData() {
      let legendData = []
      let seriesData = []
      if (this.activeTab === 'first') {
        legendData = ['待随访', '随访成功', '随访失败']
        const followUpData = {
          pending: 0,
          success: 0,
          fail: 0
        }
        this.data.forEach((item) => {
          followUpData.pending += item.pendingFollowUp || 0
          followUpData.success += item.followUpSuccess || 0
          followUpData.fail += item.followUpFail || 0
        })
        seriesData = [
          { value: followUpData.pending, name: '待随访' },
          { value: followUpData.success, name: '随访成功' },
          { value: followUpData.fail, name: '随访失败' }
        ]
      } else if (this.activeTab === 'second') {
        legendData = ['待随访(再次)', '随访成功(再次)', '随访失败(再次)']
        const followUpData = {
          pending: 0,
          success: 0,
          fail: 0
        }
        this.data.forEach((item) => {
          followUpData.pending += item.pendingFollowUpAgain || 0
          followUpData.success += item.followUpSuccessAgain || 0
          followUpData.fail += item.followUpFailAgain || 0
        })
        seriesData = [
          { value: followUpData.pending, name: '待随访(再次)' },
          { value: followUpData.success, name: '随访成功(再次)' },
          { value: followUpData.fail, name: '随访失败(再次)' }
        ]
      } else if (this.activeTab === 'continued') {
        legendData = ['护理完成', '护理进行中', '护理未开始']
        const careData = {
          completed: 0,
          inProgress: 0,
          notStarted: 0
        }
        this.data.forEach((item) => {
          careData.completed += item.careCompleted || 0
          careData.inProgress += item.careInProgress || 0
          careData.notStarted += item.careNotStarted || 0
        })
        seriesData = [
          { value: careData.completed, name: '护理完成' },
          { value: careData.inProgress, name: '护理进行中' },
          { value: careData.notStarted, name: '护理未开始' }
        ]
      }
      return { legendData, seriesData }
    },
    initBarLineChart() {
      const barDom = document.getElementById('barLineChart')
      if (!barDom) return
      if (this.barLineChart) {
        this.barLineChart.dispose()
      }
      this.barLineChart = echarts.init(barDom)
      // å‡†å¤‡æ•°æ®
      const chartData = this.getBarLineChartData()
      const option = {
        title: {
          text: `${chartData.title}趋势`,
          left: 'center',
          textStyle: {
            color: '#333',
            fontSize: 16
          }
        },
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'cross',
            crossStyle: {
              color: '#999'
            }
          }
        },
        legend: {
          data: chartData.legendData,
          top: 'bottom',
          textStyle: {
            color: '#666'
          }
        },
        color: chartData.colors,
        xAxis: {
          type: 'category',
          data: chartData.categories,
          axisLabel: {
            interval: 0,
            rotate: 30,
            color: '#666'
          },
          axisLine: {
            lineStyle: {
              color: '#ddd'
            }
          }
        },
        yAxis: [
          {
            type: 'value',
            name: chartData.yAxisName1,
            min: 0,
            axisLabel: {
              color: '#666'
            },
            axisLine: {
              lineStyle: {
                color: '#ddd'
              }
            },
            splitLine: {
              lineStyle: {
                color: '#f0f0f0'
              }
            }
          },
          {
            type: 'value',
            name: '百分比(%)',
            min: 0,
            max: 100,
            axisLabel: {
              color: '#666',
              formatter: '{value}%'
            },
            axisLine: {
              lineStyle: {
                color: '#ddd'
              }
            },
            splitLine: {
              show: false
            }
          }
        ],
        series: chartData.series,
        grid: {
          top: '15%',
          left: '3%',
          right: '4%',
          bottom: '15%',
          containLabel: true
        }
      }
      this.barLineChart.setOption(option)
      window.addEventListener('resize', this.resizeBarLineChart)
    },
    getBarLineChartData() {
      const categories = this.data.map(
        (item) => item.leavehospitaldistrictname || item.deptname
      )
      let title = '科室/病区'
      let yAxisName1 = '人次'
      let legendData = []
      let colors = []
      let series = []
      if (this.activeTab === 'first') {
        title = '首次随访'
        yAxisName1 = '人次'
        legendData = ['出院人次', '应随访人次', '随访率(%)', '及时率(%)']
        colors = ['#5470C6', '#91CC75', '#EE6666', '#9A60B4']
        const dischargeData = this.data.map((item) => item.dischargeCount || 0)
        const followUpData = this.data.map((item) => item.followUpNeeded || 0)
        const followUpRateData = this.data.map((item) => {
          if (!item.followUpRate) return 0
          const rateStr = String(item.followUpRate).replace('%', '')
          return parseFloat(rateStr) || 0
        })
        const timelyRateData = this.data.map((item) =>
          item.rate ? (Number(item.rate) * 100).toFixed(2) : 0
        )
        series = [
          {
            name: '出院人次',
            type: 'bar',
            barWidth: '25%',
            data: dischargeData,
            itemStyle: {
              borderRadius: [4, 4, 0, 0]
            }
          },
          {
            name: '应随访人次',
            type: 'bar',
            barWidth: '25%',
            data: followUpData,
            itemStyle: {
              borderRadius: [4, 4, 0, 0]
            }
          },
          {
            name: '随访率(%)',
            type: 'line',
            yAxisIndex: 1,
            data: followUpRateData,
            symbolSize: 8,
            lineStyle: {
              width: 3
            },
            markLine: {
              silent: true,
              data: [
                {
                  yAxis: 80,
                  lineStyle: {
                    color: '#EE6666',
                    type: 'dashed'
                  }
                }
              ]
            }
          },
          {
            name: '及时率(%)',
            type: 'line',
            yAxisIndex: 1,
            data: timelyRateData,
            symbolSize: 8,
            lineStyle: {
              width: 3,
              type: 'dotted'
            },
            markLine: {
              silent: true,
              data: [
                {
                  yAxis: 90,
                  lineStyle: {
                    color: '#9A60B4',
                    type: 'dashed'
                  }
                }
              ]
            }
          }
        ]
      } else if (this.activeTab === 'second') {
        title = '再次随访'
        yAxisName1 = '人次'
        legendData = ['出院人次', '应随访人次', '随访率(%)']
        colors = ['#5470C6', '#91CC75', '#EE6666']
        const dischargeData = this.data.map((item) => item.dischargeCount || 0)
        const followUpData = this.data.map((item) => item.followUpNeeded || 0)
        const followUpRateAgainData = this.data.map((item) => {
          if (!item.followUpRateAgain) return 0
          const rateStr = String(item.followUpRateAgain).replace('%', '')
          return parseFloat(rateStr) || 0
        })
        series = [
          {
            name: '出院人次',
            type: 'bar',
            barWidth: '25%',
            data: dischargeData,
            itemStyle: {
              borderRadius: [4, 4, 0, 0]
            }
          },
          {
            name: '应随访人次',
            type: 'bar',
            barWidth: '25%',
            data: followUpData,
            itemStyle: {
              borderRadius: [4, 4, 0, 0]
            }
          },
          {
            name: '随访率(%)',
            type: 'line',
            yAxisIndex: 1,
            data: followUpRateAgainData,
            symbolSize: 8,
            lineStyle: {
              width: 3
            },
            markLine: {
              silent: true,
              data: [
                {
                  yAxis: 80,
                  lineStyle: {
                    color: '#EE6666',
                    type: 'dashed'
                  }
                }
              ]
            }
          }
        ]
      } else if (this.activeTab === 'continued') {
        title = '延续护理'
        yAxisName1 = '人次'
        legendData = ['延续护理人次', '护理完成', '完成率(%)']
        colors = ['#5470C6', '#91CC75', '#EE6666']
        const continuedCareData = this.data.map((item) => item.continuedCareCount || 0)
        const careCompletedData = this.data.map((item) => item.careCompleted || 0)
        const completionRateData = this.data.map((item) => {
          if (!item.completionRate) return 0
          const rateStr = String(item.completionRate).replace('%', '')
          return parseFloat(rateStr) || 0
        })
        series = [
          {
            name: '延续护理人次',
            type: 'bar',
            barWidth: '25%',
            data: continuedCareData,
            itemStyle: {
              borderRadius: [4, 4, 0, 0]
            }
          },
          {
            name: '护理完成',
            type: 'bar',
            barWidth: '25%',
            data: careCompletedData,
            itemStyle: {
              borderRadius: [4, 4, 0, 0]
            }
          },
          {
            name: '完成率(%)',
            type: 'line',
            yAxisIndex: 1,
            data: completionRateData,
            symbolSize: 8,
            lineStyle: {
              width: 3
            },
            markLine: {
              silent: true,
              data: [
                {
                  yAxis: 85,
                  lineStyle: {
                    color: '#EE6666',
                    type: 'dashed'
                  }
                }
              ]
            }
          }
        ]
      }
      return {
        title,
        yAxisName1,
        categories,
        legendData,
        colors,
        series
      }
    },
    resizePieChart() {
      if (this.pieChart) {
        this.pieChart.resize()
      }
    },
    resizeBarLineChart() {
      if (this.barLineChart) {
        this.barLineChart.resize()
      }
    },
    destroyCharts() {
      if (this.pieChart) {
        this.pieChart.dispose()
        this.pieChart = null
      }
      if (this.barLineChart) {
        this.barLineChart.dispose()
        this.barLineChart = null
      }
      window.removeEventListener('resize', this.resizePieChart)
      window.removeEventListener('resize', this.resizeBarLineChart)
    },
    handleClose() {
      this.destroyCharts()
      this.$emit('close')
    }
  }
}
</script>
<style lang="scss" scoped>
.chart-container {
  .chart-title {
    text-align: center;
    font-size: 16px;
    font-weight: bold;
    margin-bottom: 20px;
    color: #333;
  }
}
</style>
src/views/sfstatistics/percentage/components/ContinuedCare.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,633 @@
<template>
  <div class="continued-care">
    <div class="your-table-container">
      <el-table
        v-loading="loading"
        :data="tableData"
        :border="true"
        show-summary
        :summary-method="getSummaries"
      >
        <!-- è¡¨æ ¼åˆ—定义 -->
        <el-table-column
          label="序号"
          type="index"
          align="center"
          width="60"
        />
        <el-table-column
          label="病区名称"
          align="center"
          prop="wardName"
          width="200"
          :show-overflow-tooltip="true"
        />
        <el-table-column
          label="已延续数量"
          align="center"
          prop="continuedCount"
        >
          <template slot-scope="scope">
            <el-button
              size="medium"
              type="text"
              @click="handleViewDetails(scope.row, 'continued', '已延续列表')"
            >
              <span class="button-zx">{{ scope.row.continuedCount }}</span>
            </el-button>
          </template>
        </el-table-column>
        <el-table-column
          label="未延续数量"
          align="center"
          prop="unContinuedCount"
        >
          <template slot-scope="scope">
            <el-button
              size="medium"
              type="text"
              @click="handleViewDetails(scope.row, 'uncontinued', '未延续列表')"
            >
              <span class="button-zx">{{ scope.row.unContinuedCount }}</span>
            </el-button>
          </template>
        </el-table-column>
        <el-table-column
          label="延续率"
          align="center"
          prop="continuedRate"
          width="120"
        />
        <!-- <el-table-column
          label="操作"
          align="center"
          width="150"
        >
          <template slot-scope="scope">
            <el-button
              size="small"
              type="primary"
              @click="handleRowClick(scope.row)"
            >
              æŸ¥çœ‹æŠ¤å£«è¯¦æƒ…
            </el-button>
          </template>
        </el-table-column> -->
      </el-table>
    </div>
    <!-- æŠ¤å£«è¯¦æƒ…弹窗 -->
    <el-dialog
      title="护士延续护理详情"
      :visible.sync="nurseDialogVisible"
      width="80%"
      :close-on-click-modal="false"
    >
      <div v-if="currentWardData">
        <el-table
          v-loading="nurseLoading"
          :data="nurseData"
          :border="true"
        >
          <el-table-column
            label="护士姓名"
            prop="nurseName"
            align="center"
            width="120"
          />
          <el-table-column
            label="科室"
            prop="deptName"
            align="center"
            width="150"
          />
          <el-table-column
            label="已延续数量"
            prop="continuedCount"
            align="center"
          />
          <el-table-column
            label="未延续数量"
            prop="unContinuedCount"
            align="center"
          />
          <el-table-column
            label="延续率"
            prop="continuedRate"
            align="center"
            width="120"
          />
        </el-table>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import { getContinueNerseCount, getNurseContinuedDetail } from "@/api/system/user";
import ExcelJS from "exceljs";
import { saveAs } from "file-saver";
export default {
  name: "ContinuedCare",
  props: {
    queryParams: {
      type: Object,
      required: true,
    },
    flatArrayhospit: {
      type: Array,
      default: () => [],
    },
    flatArraydept: {
      type: Array,
      default: () => [],
    },
    options: {
      type: Array,
      default: () => [],
    },
    orgname: {
      type: String,
      default: "",
    },
  },
  data() {
    return {
      tableData: [],
      loading: false,
      nurseDialogVisible: false,
      nurseLoading: false,
      currentWardData: null,
      nurseData: [],
      originalData: {},
      totalData: {
        continued: 0,
        uncontinued: 0
      }
    };
  },
  methods: {
    loadData() {
      this.loading = true;
      const params = {
        leavehospitaldistrictcodes:
          this.queryParams.leavehospitaldistrictcodes.includes("all")
            ? this.getAllWardCodes()
            : this.queryParams.leavehospitaldistrictcodes,
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.getAllDeptCodes()
          : this.queryParams.deptcodes,
      };
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      getContinueNerseCount(params)
        .then((response) => {
          this.originalData = response.data;
          this.processData(response.data);
        })
        .catch((error) => {
          console.error("获取延续护理数据失败:", error);
          this.$message.error("获取延续护理数据失败");
        })
        .finally(() => {
          this.loading = false;
        });
    },
    processData(data) {
      this.totalData = {
        continued: data.已延续总数量 || 0,
        uncontinued: data.未延续总数量 || 0
      };
      const processedData = [];
      if (data.详情 && Array.isArray(data.详情)) {
        data.详情.forEach((item) => {
          // æå–病区名称和数据
          Object.keys(item).forEach(key => {
            if (key.startsWith('已延续_')) {
              const wardName = key.replace('已延续_', '');
              const continuedCount = item[key];
              const unContinuedCount = item[`未延续_${wardName}`] || 0;
              const total = continuedCount + unContinuedCount;
              const continuedRate = total > 0 ? ((continuedCount / total) * 100).toFixed(2) + '%' : '0.00%';
              processedData.push({
                wardName,
                continuedCount,
                unContinuedCount,
                continuedRate,
                originalData: item
              });
            }
          });
        });
      }
      // æŽ’序
      this.tableData = this.customSort(processedData);
    },
    getAllWardCodes() {
      return this.flatArrayhospit
        .filter((item) => item.value !== "all")
        .map((item) => item.value);
    },
    getAllDeptCodes() {
      return this.flatArraydept
        .filter((item) => item.value !== "all")
        .map((item) => item.value);
    },
    customSort(data) {
      const order = [
        "一", "二", "三", "四", "五", "六", "七", "八", "九", "十",
        "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十",
        "二十一", "二十二", "二十三", "二十四", "二十五", "二十六", "二十七", "二十八", "二十九", "三十",
        "三十一", "三十二", "三十三", "三十四", "三十五", "三十六", "三十七", "三十八", "三十九", "四十",
        "四十一", "四十二", "四十三", "四十四", "四十五"
      ];
      return data.sort((a, b) => {
        const getIndex = (name) => {
          if (!name || typeof name !== "string") return -1;
          const chineseMatch = name.match(/^(\d+)-/);
          if (chineseMatch && chineseMatch[1]) {
            const num = parseInt(chineseMatch[1], 10);
            if (num >= 1 && num <= 45) {
              return num - 1;
            }
          }
          // å°è¯•匹配中文数字
          for (let i = 0; i < order.length; i++) {
            if (name.includes(order[i])) {
              return i;
            }
          }
          return -1;
        };
        const indexA = getIndex(a.wardName);
        const indexB = getIndex(b.wardName);
        if (indexA === -1 && indexB === -1) {
          return (a.wardName || "").localeCompare(b.wardName || "");
        }
        if (indexA === -1) return 1;
        if (indexB === -1) return -1;
        return indexA - indexB;
      });
    },
    getSummaries(param) {
      const { columns, data } = param;
      const sums = [];
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "合计";
          return;
        }
        if (index === 1) { // ç—…区名称列
          sums[index] = "/";
          return;
        }
        if (index === 2) { // å·²å»¶ç»­æ•°é‡åˆè®¡
          const totalContinued = this.tableData.reduce((sum, item) => sum + item.continuedCount, 0);
          sums[index] = this.formatNumber(totalContinued);
        } else if (index === 3) { // æœªå»¶ç»­æ•°é‡åˆè®¡
          const totalUnContinued = this.tableData.reduce((sum, item) => sum + item.unContinuedCount, 0);
          sums[index] = this.formatNumber(totalUnContinued);
        } else if (index === 4) { // å»¶ç»­çŽ‡å¹³å‡å€¼
          const totalContinued = this.tableData.reduce((sum, item) => sum + item.continuedCount, 0);
          const totalUnContinued = this.tableData.reduce((sum, item) => sum + item.unContinuedCount, 0);
          const total = totalContinued + totalUnContinued;
          const avgRate = total > 0 ? ((totalContinued / total) * 100).toFixed(2) + '%' : '0.00%';
          sums[index] = avgRate;
        } else {
          sums[index] = "";
        }
      });
      return sums;
    },
    formatNumber(num) {
      if (isNaN(num)) return "-";
      return Number.isInteger(num) ? num.toString() : num.toFixed(0);
    },
    handleViewDetails(row, type, titleSuffix) {
      const title = `${row.wardName}${titleSuffix}`;
      // è¿™é‡Œéœ€è¦æ ¹æ®ä½ çš„实际情况获取详情数据
      // ä½ å¯ä»¥ä»Žrow.originalData中获取,或者调用新的API
      const detailData = this.getMockDetailData(row, type);
      this.$emit("view-details", detailData, title);
    },
    handleRowClick(row) {
      this.currentWardData = row;
      this.nurseDialogVisible = true;
      this.loadNurseData(row.wardName);
    },
    async loadNurseData(wardName) {
      this.nurseLoading = true;
      try {
        const params = {
          wardName: wardName,
          startTime: this.queryParams.startTime,
          endTime: this.queryParams.endTime
        };
        const response = await getNurseContinuedDetail(params);
        this.nurseData = this.processNurseData(response.data);
      } catch (error) {
        console.error("获取护士详情失败:", error);
        this.$message.error("获取护士详情失败");
      } finally {
        this.nurseLoading = false;
      }
    },
    processNurseData(data) {
      // æ ¹æ®ä½ çš„实际API响应结构处理数据
      // è¿™é‡Œæ˜¯ä¸€ä¸ªç¤ºä¾‹
      return [
        {
          nurseName: "护士A",
          deptName: this.currentWardData.wardName,
          continuedCount: Math.floor(this.currentWardData.continuedCount * 0.3),
          unContinuedCount: Math.floor(this.currentWardData.unContinuedCount * 0.3),
          continuedRate: "75.00%"
        },
        {
          nurseName: "护士B",
          deptName: this.currentWardData.wardName,
          continuedCount: Math.floor(this.currentWardData.continuedCount * 0.4),
          unContinuedCount: Math.floor(this.currentWardData.unContinuedCount * 0.4),
          continuedRate: "80.00%"
        },
        {
          nurseName: "护士C",
          deptName: this.currentWardData.wardName,
          continuedCount: Math.floor(this.currentWardData.continuedCount * 0.3),
          unContinuedCount: Math.floor(this.currentWardData.unContinuedCount * 0.3),
          continuedRate: "70.00%"
        }
      ];
    },
    getMockDetailData(row, type) {
      // æ¨¡æ‹Ÿè¯¦æƒ…数据,实际应该调用API
      const detailData = [];
      const count = type === 'continued' ? row.continuedCount : row.unContinuedCount;
      for (let i = 1; i <= Math.min(count, 10); i++) {
        detailData.push({
          sendname: `患者${i}`,
          taskName: `${row.wardName}延续护理`,
          sendstate: type === 'continued' ? 6 : 2,
          preachform: ["人工随访", "电话随访"],
          visitTime: "2024-01-15 10:00:00",
          finishtime: type === 'continued' ? "2024-01-15 11:00:00" : "",
          endtime: "2024-01-10 09:00:00",
          nurseName: "护士A",
          drname: "医生A",
          excep: 1,
          suggest: 2,
          templatename: "延续护理服务模板",
          remark: type === 'continued' ? "已完成延续护理" : "未开始延续护理",
          bankcardno: "已完成"
        });
      }
      return detailData;
    },
    async exportTable() {
      try {
        let dateRangeString = "";
        let sheetNameSuffix = "";
        if (
          this.queryParams.dateRange &&
          this.queryParams.dateRange.length === 2
        ) {
          const startDateStr = this.queryParams.dateRange[0];
          const endDateStr = this.queryParams.dateRange[1];
          const formatDateForDisplay = (dateTimeStr) => {
            return dateTimeStr.split(" ")[0];
          };
          const startDateFormatted = formatDateForDisplay(startDateStr);
          const endDateFormatted = formatDateForDisplay(endDateStr);
          dateRangeString = `${startDateFormatted}至${endDateFormatted}`;
          sheetNameSuffix = `${startDateFormatted}至${endDateFormatted}`;
        } else {
          const now = new Date();
          const currentMonth = now.getMonth() + 1;
          dateRangeString = `${currentMonth}月`;
          sheetNameSuffix = `${currentMonth}月`;
        }
        const excelName = `延续护理统计表_${dateRangeString}.xlsx`;
        const worksheetName = `延续护理统计_${sheetNameSuffix}`;
        if (!this.tableData || this.tableData.length === 0) {
          this.$message.warning("暂无延续护理数据可导出");
          return false;
        }
        const workbook = new ExcelJS.Workbook();
        const worksheet = workbook.addWorksheet(worksheetName);
        this.buildExportSheet(worksheet, sheetNameSuffix);
        const buffer = await workbook.xlsx.writeBuffer();
        const blob = new Blob([buffer], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        });
        saveAs(blob, excelName);
        this.$message.success("导出成功");
        return true;
      } catch (error) {
        console.error("导出失败:", error);
        this.$message.error(`导出失败: ${error.message}`);
        return false;
      }
    },
    buildExportSheet(worksheet, sheetNameSuffix) {
      const titleStyle = {
        font: {
          name: "微软雅黑",
          size: 16,
          bold: true,
          color: { argb: "FF000000" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFE6F3FF" },
        },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const headerStyle = {
        font: {
          name: "微软雅黑",
          size: 11,
          bold: true,
          color: { argb: "FF000000" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFF5F7FA" },
        },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const cellStyle = {
        font: { name: "宋体", size: 10, color: { argb: "FF000000" } },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const summaryStyle = {
        font: {
          name: "宋体",
          size: 10,
          bold: true,
          color: { argb: "FF409EFF" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFF5F7FA" },
        },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      // æ·»åŠ æ ‡é¢˜è¡Œ
      worksheet.mergeCells(1, 1, 1, 6);
      const titleCell = worksheet.getCell(1, 1);
      titleCell.value = `延续护理统计表_${sheetNameSuffix}`;
      titleCell.style = titleStyle;
      worksheet.getRow(1).height = 35;
      // è¡¨å¤´
      const headers = ["序号", "病区名称", "已延续数量", "未延续数量", "延续率", "操作"];
      headers.forEach((header, index) => {
        const cell = worksheet.getCell(2, index + 1);
        cell.value = header;
        cell.style = headerStyle;
      });
      worksheet.getRow(2).height = 25;
      // æ•°æ®è¡Œ
      this.tableData.forEach((item, rowIndex) => {
        const dataRow = worksheet.addRow(
          [
            rowIndex + 1,
            item.wardName,
            item.continuedCount,
            item.unContinuedCount,
            item.continuedRate,
            "查看护士详情"
          ],
          rowIndex + 3
        );
        dataRow.eachCell((cell) => {
          cell.style = cellStyle;
        });
        dataRow.height = 24;
      });
      // åˆè®¡è¡Œ
      const totalContinued = this.tableData.reduce((sum, item) => sum + item.continuedCount, 0);
      const totalUnContinued = this.tableData.reduce((sum, item) => sum + item.unContinuedCount, 0);
      const total = totalContinued + totalUnContinued;
      const totalRate = total > 0 ? ((totalContinued / total) * 100).toFixed(2) + '%' : '0.00%';
      const summaryRow = worksheet.addRow([
        "合计",
        "/",
        totalContinued,
        totalUnContinued,
        totalRate,
        "/"
      ]);
      summaryRow.eachCell((cell, colNumber) => {
        cell.style = summaryStyle;
      });
      summaryRow.height = 28;
      // åˆ—宽
      worksheet.columns = [
        { width: 8 },
        { width: 30 },
        { width: 15 },
        { width: 15 },
        { width: 12 },
        { width: 15 }
      ];
    }
  }
};
</script>
<style lang="scss" scoped>
.continued-care {
  .your-table-container {
    margin-top: 10px;
  }
  .button-zx {
    color: rgb(70, 204, 238);
  }
}
</style>
src/views/sfstatistics/percentage/components/DetailDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,302 @@
<template>
  <el-dialog
    :title="title"
    :visible.sync="visible"
    v-loading="loading"
    width="70%"
    :close-on-click-modal="false"
    @close="handleClose"
  >
    <div class="detail-dialog">
      <div style="margin-bottom: 16px; display: flex; align-items: center">
        <span style="margin-right: 10px; font-weight: bold">患者姓名查询:</span>
        <el-input
          v-model="searchName"
          placeholder="请输入患者姓名进行筛选"
          clearable
          style="width: 300px"
          @input="handleSearch"
          @clear="handleSearch"
        />
        <span style="margin-left: 10px; color: rgb(35, 81, 233); font-size: 16px">
          å…± {{ displayList.length }} æ¡è®°å½•
        </span>
      </div>
      <div class="examine-jic">
        <div class="jic-value">
          <el-row :gutter="20">
            <div class="data-list" ref="dataList" @scroll="handleScroll" v-loading="loading">
              <el-table :data="currentDisplayList" height="660" style="width: 100%">
                <el-table-column prop="sendname" align="center" label="姓名" width="100" />
                <el-table-column prop="taskName" align="center" width="200" show-overflow-tooltip label="任务名称" />
                <el-table-column prop="sendstate" align="center" width="200" label="任务状态">
                  <template slot-scope="scope">
                    <el-tag
                      :type="getStateTagType(scope.row.sendstate)"
                      :disable-transitions="false"
                    >
                      {{ getStateText(scope.row.sendstate) }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column
                  label="任务执行方式"
                  align="center"
                  key="preachform"
                  prop="preachform"
                  width="160"
                  :show-overflow-tooltip="true"
                >
                  <template slot-scope="scope">
                    <span v-for="(item, index) in scope.row.preachform" :key="index">
                      {{ item }}{{ index < scope.row.preachform.length - 1 ? '、' : '' }}
                    </span>
                  </template>
                </el-table-column>
                <el-table-column
                  prop="visitTime"
                  align="center"
                  label="应随访时间"
                  width="200"
                  show-overflow-tooltip
                />
                <el-table-column
                  prop="finishtime"
                  align="center"
                  label="随访完成时间"
                  width="200"
                  show-overflow-tooltip
                />
                <el-table-column label="出院日期" width="200" align="center" key="endtime" prop="endtime">
                  <template slot-scope="scope">
                    <span>{{ formatTime(scope.row.endtime) }}</span>
                  </template>
                </el-table-column>
                <el-table-column label="责任护士" width="120" align="center" key="nurseName" prop="nurseName" />
                <el-table-column label="主治医生" width="120" align="center" key="drname" prop="drname" />
                <el-table-column label="结果状态" align="center" key="excep" prop="excep" width="120">
                  <template slot-scope="scope">
                    <dict-tag :options="dict.type.sys_yujing" :value="scope.row.excep" />
                  </template>
                </el-table-column>
                <el-table-column label="处理意见" align="center" key="suggest" prop="suggest" width="120">
                  <template slot-scope="scope">
                    <dict-tag :options="dict.type.sys_suggest" :value="scope.row.suggest" />
                  </template>
                </el-table-column>
                <el-table-column prop="templatename" align="center" label="服务模板" width="200" show-overflow-tooltip />
                <el-table-column prop="remark" align="center" label="服务记录" width="200" show-overflow-tooltip />
                <el-table-column prop="bankcardno" align="center" label="呼叫状态" width="210" />
                <el-table-column label="操作" fixed="right" align="center" width="200" class-name="small-padding fixed-width">
                  <template slot-scope="scope">
                    <el-button size="medium" type="text" @click="handleDetailsGo(scope.row)">
                      <span class="button-zx">
                        <i class="el-icon-s-order"></i>查看
                      </span>
                    </el-button>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </el-row>
        </div>
      </div>
    </div>
  </el-dialog>
</template>
<script>
export default {
  name: 'DetailDialog',
  dicts: ['sys_yujing', 'sys_suggest'],
  props: {
    visible: {
      type: Boolean,
      default: false
    },
    title: {
      type: String,
      default: ''
    },
    data: {
      type: Array,
      default: () => []
    },
    searchName: {
      type: String,
      default: ''
    },
    loading: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      localSearchName: '',
      displayList: [],
      currentDisplayList: [],
      loadIndex: 0,
      pageSize: 100,
      isLoading: false
    }
  },
  watch: {
    data: {
      immediate: true,
      handler(newData) {
        this.initializeData(newData)
      }
    },
    searchName(newVal) {
      this.localSearchName = newVal
      this.handleSearch()
    }
  },
  mounted() {
    if (this.data && this.data.length > 0) {
      this.initializeData(this.data)
    }
  },
  methods: {
    initializeData(data) {
      this.displayList = [...data]
      this.formatPreachformData()
      this.loadIndex = 0
      this.currentDisplayList = []
      this.$nextTick(() => {
        this.loadMoreData()
      })
    },
    formatPreachformData() {
      this.displayList.forEach((item) => {
        if (item.preachform) {
          if (item.endtime) {
            item.preachformson = item.preachform
            const idArray = item.preachform.split(',')
            item.preachform = idArray.map((value) => {
              const checkboxlist = this.$store.getters.checkboxlist
              const foundItem = checkboxlist.find((item) => item.value == value)
              return foundItem ? foundItem.label : null
            }).filter(label => label !== null)
          }
        }
      })
    },
    handleSearch() {
      if (!this.localSearchName.trim()) {
        this.displayList = [...this.data]
        this.formatPreachformData()
      } else {
        const keyword = this.localSearchName.toLowerCase()
        this.displayList = this.data.filter((item) => {
          return item.sendname && item.sendname.toLowerCase().includes(keyword)
        })
        this.formatPreachformData()
      }
      this.loadIndex = 0
      this.currentDisplayList = []
      this.$nextTick(() => {
        this.loadMoreData()
      })
      this.$emit('search', this.localSearchName)
    },
    loadMoreData() {
      if (this.isLoading || this.loadIndex >= this.displayList.length) return
      this.isLoading = true
      setTimeout(() => {
        const nextChunk = this.displayList.slice(
          this.loadIndex,
          this.loadIndex + this.pageSize
        )
        this.currentDisplayList = this.currentDisplayList.concat(nextChunk)
        this.loadIndex += this.pageSize
        this.isLoading = false
      }, 200)
    },
    handleScroll(event) {
      const scrollContainer = event.target
      const isAtBottom =
        scrollContainer.scrollTop + scrollContainer.clientHeight >=
        scrollContainer.scrollHeight - 10
      if (
        isAtBottom &&
        !this.isLoading &&
        this.loadIndex < this.displayList.length
      ) {
        this.loadMoreData()
      }
    },
    getStateTagType(state) {
      const stateMap = {
        1: 'primary',  // è¡¨å•已领取
        2: 'primary',  // å¾…随访
        3: 'success',  // è¡¨å•已发送
        4: 'info',     // ä¸æ‰§è¡Œ
        5: 'danger',   // å‘送失败
        6: 'success'   // å·²å®Œæˆ
      }
      return stateMap[state] || 'info'
    },
    getStateText(state) {
      const stateTextMap = {
        1: '表单已领取',
        2: '待随访',
        3: '表单已发送',
        4: '不执行',
        5: '发送失败',
        6: '已完成'
      }
      return stateTextMap[state] || '未知状态'
    },
    formatTime(time) {
      if (!time) return ''
      return this.parseTime(time)
    },
    handleDetailsGo(row) {
      this.$emit('details-go', row)
    },
    handleClose() {
      this.$emit('close')
    }
  }
}
</script>
<style lang="scss" scoped>
.detail-dialog {
  .data-list {
    max-height: 800px;
    overflow-y: auto;
  }
  .button-zx {
    color: rgb(70, 204, 238);
  }
}
</style>
src/views/sfstatistics/percentage/components/FirstFollowUp.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,728 @@
<template>
  <div class="first-follow-up">
    <div class="your-table-container">
      <el-table
        ref="exportTable"
        id="exportTableid"
        v-loading="loading"
        :data="tableData"
        :border="true"
        @selection-change="handleSelectionChange"
        @expand-change="handleRowClick"
        :row-key="getRowKey"
        show-summary
        :summary-method="getSummaries"
        :expand-row-keys="expands"
      >
        <!-- å±•开行箭头列 -->
        <el-table-column type="expand">
          <template slot-scope="props">
            <el-table
              :data="props.row.doctorStats"
              border
              style="width: 95%; margin: 0 auto"
              class="inner-table"
              show-summary
              :summary-method="getInnerSummaries"
            >
              <el-table-column label="医生姓名" prop="drname" align="center" />
              <el-table-column label="科室" width="120" prop="deptname" align="center" />
              <el-table-column label="出院人次" prop="dischargeCount" align="center" />
              <el-table-column label="出院人次" align="center" key="dischargeCount" prop="dischargeCount" />
              <el-table-column label="无需随访人次" align="center" width="100" key="nonFollowUp" prop="nonFollowUp" />
              <el-table-column label="应随访人次" align="center" width="100" key="followUpNeeded" prop="followUpNeeded" />
              <el-table-column align="center" label="首次出院随访">
                <el-table-column label="需随访" align="center" key="needFollowUp" prop="needFollowUp" />
                <el-table-column label="待随访" align="center" key="pendingFollowUp" prop="pendingFollowUp" />
                <el-table-column label="随访成功" align="center" key="followUpSuccess" prop="followUpSuccess" />
                <el-table-column label="随访失败" align="center" key="followUpFail" prop="followUpFail" />
                <el-table-column label="随访率" align="center" width="120" key="followUpRate" prop="followUpRate" />
                <el-table-column v-if="orgname != '丽水市中医院'" label="及时率" align="center" width="120" key="rate" prop="rate">
                  <template slot-scope="scope">
                    <el-button size="medium" type="text" @click="handleSeeDetails(scope.row)">
                      <span class="button-zx">{{ (Number(scope.row.rate) * 100).toFixed(2) }}%</span>
                    </el-button>
                  </template>
                </el-table-column>
                <el-table-column label="人工" align="center" key="manual" prop="manual" />
                <el-table-column label="短信" align="center" key="sms" prop="sms" />
                <el-table-column label="微信" align="center" key="weChat" prop="weChat" />
              </el-table-column>
            </el-table>
          </template>
        </el-table-column>
        <!-- è¡¨æ ¼åˆ—定义 -->
        <el-table-column
          label="出院病区"
          align="center"
          sortable
          key="leavehospitaldistrictname"
          prop="leavehospitaldistrictname"
          width="150"
          :show-overflow-tooltip="true"
          :sort-method="sortChineseNumber"
        />
        <el-table-column label="科室" align="center" key="deptname" prop="deptname" :show-overflow-tooltip="true" />
        <el-table-column label="出院人次" align="center" key="dischargeCount" prop="dischargeCount" />
        <el-table-column label="无需随访人次" align="center" width="100" key="nonFollowUp" prop="nonFollowUp" />
        <el-table-column label="应随访人次" align="center" width="100" key="followUpNeeded" prop="followUpNeeded" />
        <el-table-column align="center" label="首次出院随访">
          <el-table-column label="需随访" align="center" key="needFollowUp" prop="needFollowUp">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'needFollowUpInfo', '需随访列表')">
                <span class="button-zx">{{ scope.row.needFollowUp }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="待随访" align="center" key="pendingFollowUp" prop="pendingFollowUp">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'pendingFollowUpInfo', '待随访列表')">
                <span class="button-zx">{{ scope.row.pendingFollowUp }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="随访成功" align="center" key="followUpSuccess" prop="followUpSuccess">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'followUpSuccessInfo', '随访成功列表')">
                <span class="button-zx">{{ scope.row.followUpSuccess }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="随访失败" align="center" key="followUpFail" prop="followUpFail">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'followUpFailInfo', '随访失败列表')">
                <span class="button-zx">{{ scope.row.followUpFail }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="随访率" align="center" width="120" key="followUpRate" prop="followUpRate" />
          <el-table-column v-if="orgname != '丽水市中医院'" label="及时率" align="center" width="120" key="rate" prop="rate">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleSeeDetails(scope.row)">
                <span class="button-zx">{{ (Number(scope.row.rate) * 100).toFixed(2) }}%</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="人工" align="center" key="manual" prop="manual">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'manualInfo', '人工随访列表')">
                <span class="button-zx">{{ scope.row.manual }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="短信" align="center" key="sms" prop="sms">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'smsInfo', '短信随访列表')">
                <span class="button-zx">{{ scope.row.sms }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="微信" align="center" key="weChat" prop="weChat">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'weChatInfo', '微信随访列表')">
                <span class="button-zx">{{ scope.row.weChat }}</span>
              </el-button>
            </template>
          </el-table-column>
        </el-table-column>
        <!-- éšè®¿æƒ…况列(仅丽水市中医院显示) -->
        <el-table-column v-if="orgname == '丽水市中医院'" align="center" label="随访情况">
          <el-table-column label="正常语音" align="center" width="100" key="taskSituation1" prop="taskSituation1" />
          <el-table-column label="患者拒接或拒访" align="center" width="100" key="taskSituation2" prop="taskSituation2" />
          <el-table-column label="面访或者接诊" align="center" width="100" key="taskSituation3" prop="taskSituation3" />
          <el-table-column label="微信随访" align="center" width="100" key="taskSituation4" prop="taskSituation4" />
          <el-table-column label="随访电话不正确" align="center" width="100" key="taskSituation5" prop="taskSituation5" />
          <el-table-column label="其他情况不宜随访" align="center" width="100" key="taskSituation6" prop="taskSituation6" />
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>
<script>
import { getSfStatistics, selectTimelyRate } from "@/api/system/user";
import ExcelJS from "exceljs";
import { saveAs } from "file-saver";
export default {
  name: 'FirstFollowUp',
  props: {
    queryParams: {
      type: Object,
      required: true
    },
    flatArrayhospit: {
      type: Array,
      default: () => []
    },
    flatArraydept: {
      type: Array,
      default: () => []
    },
    options: {
      type: Array,
      default: () => []
    },
    orgname: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      tableData: [],
      loading: false,
      expands: [],
      ids: []
    }
  },
  methods: {
    loadData() {
      this.loading = true
      const params = {
        ...this.queryParams,
        visitCount: 1,
        leavehospitaldistrictcodes: this.queryParams.leavehospitaldistrictcodes.includes("all")
          ? this.getAllWardCodes()
          : this.queryParams.leavehospitaldistrictcodes,
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.getAllDeptCodes()
          : this.queryParams.deptcodes
      }
      delete params.leavehospitaldistrictcodes.all
      delete params.deptcodes.all
      getSfStatistics(params)
        .then(response => {
          this.tableData = this.customSort(response.data)
        })
        .catch(error => {
          console.error("获取首次随访数据失败:", error)
          this.$message.error("获取首次随访数据失败")
        })
        .finally(() => {
          this.loading = false
        })
    },
    getAllWardCodes() {
      return this.flatArrayhospit
        .filter(item => item.value !== 'all')
        .map(item => item.value)
    },
    getAllDeptCodes() {
      return this.flatArraydept
        .filter(item => item.value !== 'all')
        .map(item => item.value)
    },
    customSort(data) {
      const order = [
        "一","二","三","四","五","六","七","八","九","十",
        "十一","十二","十三","十四","十五","十六","十七","十八","十九","二十",
        "二十一","二十二","二十三","二十四","二十五","二十六","二十七","二十八","二十九","三十",
        "三十一","三十二","三十三","三十四","三十五","三十六","三十七","三十八","三十九","四十",
        "四十一","四十二","四十三","四十四","四十五"
      ]
      return data.sort((a, b) => {
        const getIndex = (name) => {
          if (!name || typeof name !== "string") return -1
          const chineseMatch = name.match(/^([一二三四五六七八九十]+)/)
          if (chineseMatch && chineseMatch[1]) {
            return order.indexOf(chineseMatch[1])
          }
          const arabicMatch = name.match(/^(\d+)/)
          if (arabicMatch && arabicMatch[1]) {
            const num = parseInt(arabicMatch[1], 10)
            if (num >= 1 && num <= 45) {
              return num - 1
            }
          }
          return -1
        }
        const indexA = getIndex(a.leavehospitaldistrictname)
        const indexB = getIndex(b.leavehospitaldistrictname)
        if (indexA === -1 && indexB === -1) {
          return (a.leavehospitaldistrictname || "").localeCompare(b.leavehospitaldistrictname || "")
        }
        if (indexA === -1) return 1
        if (indexB === -1) return -1
        return indexA - indexB
      })
    },
    sortChineseNumber(aRow, bRow) {
      const a = aRow.leavehospitaldistrictname
      const b = bRow.leavehospitaldistrictname
      const chineseNumMap = {
        ä¸€:1,二:2,三:3,四:4,五:5,六:6,七:7,八:8,九:9,十:10,
        åä¸€:11,十二:12,十三:13,十四:14,十五:15,十六:16,十七:17,十八:18,十九:19,二十:20,
        äºŒåä¸€:21,二十二:22,二十三:23,二十四:24,二十五:25,二十六:26,二十七:27,二十八:28,二十九:29,三十:30,
        ä¸‰åä¸€:31,三十二:32,三十三:33,三十四:34,三十五:35,三十六:36,三十七:37,三十八:38,三十九:39,四十:40,
        å››åä¸€:41,四十二:42,四十三:43,四十四:44,四十五:45
      }
      const getNumberFromText = (text) => {
        if (!text || typeof text !== "string") return -1
        const match = text.match(/^([一二三四五六七八九十]+)/)
        if (match && match[1]) {
          const chineseNum = match[1]
          return chineseNumMap[chineseNum] !== undefined ? chineseNumMap[chineseNum] : -1
        }
        const arabicMatch = text.match(/^(\d+)/)
        if (arabicMatch && arabicMatch[1]) {
          const num = parseInt(arabicMatch[1], 10)
          return num >= 1 && num <= 45 ? num : -1
        }
        return -1
      }
      const numA = getNumberFromText(a)
      const numB = getNumberFromText(b)
      if (numA === -1 && numB === -1) {
        return (a || "").localeCompare(b || "")
      }
      if (numA === -1) return 1
      if (numB === -1) return -1
      return numA - numB
    },
    getRowKey(row) {
      return row.statisticaltype === 1 ? row.leavehospitaldistrictcode : row.deptcode
    },
    handleRowClick(row) {
      if (this.expands.includes(this.getRowKey(row))) {
        this.expands = []
        return
      }
      const params = {
        ...this.queryParams,
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.getAllDeptCodes()
          : this.queryParams.deptcodes,
        leavehospitaldistrictcodes: [row.leavehospitaldistrictcode],
        drcode: "1",
        visitCount: 1
      }
      delete params.leavehospitaldistrictcodes.all
      delete params.deptcodes.all
      if (!row.doctorStats) {
        this.loading = true
        getSfStatistics(params).then((res) => {
          this.$set(row, "doctorStats", res.data)
          this.expands = [this.getRowKey(row)]
          this.loading = false
        })
      } else {
        this.expands = [this.getRowKey(row)]
      }
    },
    getSummaries(param) {
      const { columns, data } = param
      const sums = []
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "合计"
          return
        }
        if (index === 1 || index === 2) {
          sums[index] = "/"
          return
        }
        if (column.property === "followUpRate" || column.property === "rate") {
          const percentageValues = data
            .map((item) => {
              const value = item[column.property]
              if (!value || value === "-" || value === "0%") return null
              if (typeof value === "string" && value.includes("%")) {
                const numValue = parseFloat(value.replace("%", "")) / 100
                return isNaN(numValue) ? null : numValue
              } else {
                const numValue = parseFloat(value)
                return isNaN(numValue) ? null : numValue
              }
            })
            .filter((value) => value !== null && value !== 0)
          if (percentageValues.length > 0) {
            const average = percentageValues.reduce((sum, value) => sum + value, 0) / percentageValues.length
            sums[index] = (average * 100).toFixed(2) + "%"
          } else {
            sums[index] = "0.00%"
          }
        } else {
          const values = data.map((item) => {
            const value = item[column.property]
            if (value === "-" || value === "" || value === null) return 0
            return Number(value) || 0
          })
          if (!values.every((value) => isNaN(value))) {
            sums[index] = values.reduce((prev, curr) => prev + curr, 0)
            sums[index] = this.formatNumber(sums[index])
          } else {
            sums[index] = "-"
          }
        }
      })
      return sums
    },
    getInnerSummaries(param) {
      const { columns, data } = param
      const sums = []
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "小计"
          return
        }
        if (column.property === "drname" || column.property === "deptname") {
          sums[index] = "-"
          return
        }
        if (column.property === "followUpRate" || column.property === "rate") {
          const percentageValues = data
            .map((item) => {
              const value = item[column.property]
              if (!value || value === "-" || value === "0%") return null
              if (typeof value === "string" && value.includes("%")) {
                const numValue = parseFloat(value.replace("%", "")) / 100
                return isNaN(numValue) ? null : numValue
              } else {
                const numValue = parseFloat(value)
                return isNaN(numValue) ? null : numValue
              }
            })
            .filter((value) => value !== null && value !== 0)
          if (percentageValues.length > 0) {
            const average = percentageValues.reduce((sum, value) => sum + value, 0) / percentageValues.length
            sums[index] = (average * 100).toFixed(2) + "%"
          } else {
            sums[index] = "0.00%"
          }
        } else {
          const values = data.map((item) => {
            const value = item[column.property]
            if (value === "-" || value === "" || value === null) return 0
            return Number(value) || 0
          })
          if (!values.every((value) => isNaN(value))) {
            sums[index] = values.reduce((prev, curr) => prev + curr, 0)
            sums[index] = this.formatNumber(sums[index])
          } else {
            sums[index] = "-"
          }
        }
      })
      return sums
    },
    formatNumber(num) {
      if (isNaN(num)) return "-"
      return Number.isInteger(num) ? num.toString() : num.toFixed(0)
    },
    handleSelectionChange(selection) {
      this.ids = selection.map((item) => item.tagid)
    },
    handleViewDetails(row, infoKey, titleSuffix) {
      const title = `${row.leavehospitaldistrictname || row.deptname}${titleSuffix}`
      this.$emit('view-details', row[infoKey], title)
    },
    handleSeeDetails(row) {
      this.$emit('see-details', row)
    },
    async exportTable() {
      try {
        let dateRangeString = ""
        let sheetNameSuffix = ""
        if (this.queryParams.dateRange && this.queryParams.dateRange.length === 2) {
          const startDateStr = this.queryParams.dateRange[0]
          const endDateStr = this.queryParams.dateRange[1]
          const formatDateForDisplay = (dateTimeStr) => {
            return dateTimeStr.split(" ")[0]
          }
          const startDateFormatted = formatDateForDisplay(startDateStr)
          const endDateFormatted = formatDateForDisplay(endDateStr)
          dateRangeString = `${startDateFormatted}至${endDateFormatted}`
          sheetNameSuffix = `${startDateFormatted}至${endDateFormatted}`
        } else {
          const now = new Date()
          const currentMonth = now.getMonth() + 1
          dateRangeString = `${currentMonth}月`
          sheetNameSuffix = `${currentMonth}月`
        }
        const excelName = `首次出院随访统计表_${dateRangeString}.xlsx`
        const worksheetName = `首次随访统计_${sheetNameSuffix}`
        if (!this.tableData || this.tableData.length === 0) {
          this.$message.warning("暂无首次随访数据可导出")
          return false
        }
        const workbook = new ExcelJS.Workbook()
        const worksheet = workbook.addWorksheet(worksheetName)
        // æž„建表格
        this.buildExportSheet(worksheet, sheetNameSuffix)
        const buffer = await workbook.xlsx.writeBuffer()
        const blob = new Blob([buffer], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        })
        saveAs(blob, excelName)
        this.$message.success("导出成功")
        return true
      } catch (error) {
        console.error("导出失败:", error)
        this.$message.error(`导出失败: ${error.message}`)
        return false
      }
    },
    buildExportSheet(worksheet, sheetNameSuffix) {
      const titleStyle = {
        font: { name: "微软雅黑", size: 16, bold: true, color: { argb: "FF000000" } },
        fill: { type: "pattern", pattern: "solid", fgColor: { argb: "FFE6F3FF" } },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } }
        }
      }
      const headerStyle = {
        font: { name: "微软雅黑", size: 11, bold: true, color: { argb: "FF000000" } },
        fill: { type: "pattern", pattern: "solid", fgColor: { argb: "FFF5F7FA" } },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } }
        }
      }
      const cellStyle = {
        font: { name: "宋体", size: 10, color: { argb: "FF000000" } },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } }
        }
      }
      const summaryStyle = {
        font: { name: "宋体", size: 10, bold: true, color: { argb: "FF409EFF" } },
        fill: { type: "pattern", pattern: "solid", fgColor: { argb: "FFF5F7FA" } },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } }
        }
      }
      // æ·»åŠ æ ‡é¢˜è¡Œ
      worksheet.mergeCells(1, 1, 1, 16)
      const titleCell = worksheet.getCell(1, 1)
      titleCell.value = `首次出院随访统计表_${sheetNameSuffix}`
      titleCell.style = titleStyle
      worksheet.getRow(1).height = 35
      // è¡¨å¤´
      const secondRowHeaders = [
        "", "出院病区", "科室", "出院人次", "无需随访人次", "应随访人次",
        "需随访", "待随访", "随访成功", "随访失败", "随访率", "及时率", "人工", "短信", "微信"
      ]
      secondRowHeaders.forEach((header, index) => {
        const cell = worksheet.getCell(3, index + 1)
        cell.value = header
        cell.style = headerStyle
      })
      // åˆå¹¶å•元格
      for (let i = 1; i <= 6; i++) {
        worksheet.mergeCells(2, i, 3, i)
        const cell = worksheet.getCell(2, i)
        cell.style = headerStyle
      }
      worksheet.getCell(2, 1).value = ""
      worksheet.getCell(2, 2).value = "出院病区"
      worksheet.getCell(2, 3).value = "科室"
      worksheet.getCell(2, 4).value = "出院人次"
      worksheet.getCell(2, 5).value = "无需随访人次"
      worksheet.getCell(2, 6).value = "应随访人次"
      worksheet.mergeCells(2, 7, 2, 15)
      worksheet.getCell(2, 7).value = "首次出院随访"
      worksheet.getCell(2, 7).style = headerStyle
      worksheet.getRow(2).height = 28
      worksheet.getRow(3).height = 25
      // æ•°æ®è¡Œ
      this.tableData.forEach((item, rowIndex) => {
        const dataRow = worksheet.addRow([
          "",
          item.leavehospitaldistrictname || "",
          item.deptname || "",
          item.dischargeCount || 0,
          item.nonFollowUp || 0,
          item.followUpNeeded || 0,
          item.needFollowUp || 0,
          item.pendingFollowUp || 0,
          item.followUpSuccess || 0,
          item.followUpFail || 0,
          item.followUpRate || "0%",
          item.rate ? (Number(item.rate) * 100).toFixed(2) + "%" : "0%",
          item.manual || 0,
          item.sms || 0,
          item.weChat || 0
        ], rowIndex + 4)
        dataRow.eachCell((cell) => {
          cell.style = cellStyle
        })
        dataRow.height = 24
      })
      // åˆè®¡è¡Œ
      const summaries = this.getExportSummaries()
      const summaryRow = worksheet.addRow(summaries)
      summaryRow.eachCell((cell, colNumber) => {
        cell.style = summaryStyle
        if (colNumber === 1) {
          cell.value = "合计"
        }
      })
      summaryRow.height = 28
      // åˆ—宽
      worksheet.columns = [
        { width: 8 }, { width: 20 }, { width: 15 }, { width: 12 }, { width: 12 }, { width: 12 },
        { width: 10 }, { width: 10 }, { width: 10 }, { width: 10 }, { width: 12 }, { width: 12 },
        { width: 8 }, { width: 8 }, { width: 8 }
      ]
    },
    getExportSummaries() {
      const summaries = ["合计", "/", "/", 0, 0, 0, 0, 0, 0, 0, "0%", "0%", 0, 0, 0]
      this.tableData.forEach((item) => {
        summaries[3] += Number(item.dischargeCount) || 0
        summaries[4] += Number(item.nonFollowUp) || 0
        summaries[5] += Number(item.followUpNeeded) || 0
        summaries[6] += Number(item.needFollowUp) || 0
        summaries[7] += Number(item.pendingFollowUp) || 0
        summaries[8] += Number(item.followUpSuccess) || 0
        summaries[9] += Number(item.followUpFail) || 0
        summaries[12] += Number(item.manual) || 0
        summaries[13] += Number(item.sms) || 0
        summaries[14] += Number(item.weChat) || 0
      })
      const followUpRateValues = this.tableData
        .map((item) => this.extractPercentageValue(item.followUpRate))
        .filter((value) => value !== null)
      const rateValues = this.tableData
        .map((item) => this.extractPercentageValue(item.rate))
        .filter((value) => value !== null)
      if (followUpRateValues.length > 0) {
        const avgFollowUpRate = followUpRateValues.reduce((sum, val) => sum + val, 0) / followUpRateValues.length
        summaries[10] = (avgFollowUpRate * 100).toFixed(2) + "%"
      }
      if (rateValues.length > 0) {
        const avgRate = rateValues.reduce((sum, val) => sum + val, 0) / rateValues.length
        summaries[11] = (avgRate * 100).toFixed(2) + "%"
      }
      summaries[3] = this.formatNumber(summaries[3])
      summaries[4] = this.formatNumber(summaries[4])
      summaries[5] = this.formatNumber(summaries[5])
      summaries[6] = this.formatNumber(summaries[6])
      summaries[7] = this.formatNumber(summaries[7])
      summaries[8] = this.formatNumber(summaries[8])
      summaries[9] = this.formatNumber(summaries[9])
      summaries[12] = this.formatNumber(summaries[12])
      summaries[13] = this.formatNumber(summaries[13])
      summaries[14] = this.formatNumber(summaries[14])
      return summaries
    },
    extractPercentageValue(value) {
      if (!value) return null
      if (typeof value === "string" && value.includes("%")) {
        const num = parseFloat(value.replace("%", ""))
        return isNaN(num) ? null : num / 100
      }
      const num = parseFloat(value)
      return isNaN(num) ? null : num
    },
    selectTimelyRate(row, dateRange) {
      const params = {
        ...this.patientqueryParams,
        starttime: this.parseTime(dateRange[0]),
        endtime: this.parseTime(dateRange[1]),
        deptcode: row.deptcode
      }
      return selectTimelyRate(params)
    }
  }
}
</script>
<style lang="scss" scoped>
.first-follow-up {
  .your-table-container {
    margin-top: 10px;
  }
  .button-zx {
    color: rgb(70, 204, 238);
  }
}
</style>
src/views/sfstatistics/percentage/components/SecondFollowUp.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,679 @@
<template>
  <div class="second-follow-up">
    <div class="your-table-container">
      <el-table
        ref="exportTableSecond"
        id="exportTableidSecond"
        v-loading="loading"
        :data="tableData"
        :border="true"
        @selection-change="handleSelectionChange"
        @expand-change="handleRowClick"
        :row-key="getRowKey"
        show-summary
        :summary-method="getSummaries"
        :expand-row-keys="expands"
      >
        <!-- å±•开行箭头列 -->
        <el-table-column type="expand">
          <template slot-scope="props">
            <el-table
              :data="props.row.doctorStats"
              border
              style="width: 95%; margin: 0 auto"
              class="inner-table"
              show-summary
              :summary-method="getInnerSummaries"
            >
              <el-table-column label="医生姓名" prop="drname" align="center" />
              <el-table-column label="科室" width="120" prop="deptname" align="center" />
              <el-table-column label="出院人次" prop="dischargeCount" align="center" />
              <el-table-column label="无需随访人次" align="center" width="100" key="nonFollowUp" prop="nonFollowUp" />
              <el-table-column label="应随访人次" align="center" width="100" key="followUpNeeded" prop="followUpNeeded" />
              <el-table-column align="center" label="再次出院随访">
                <el-table-column label="需随访" align="center" key="needFollowUpAgain" prop="needFollowUpAgain" />
                <el-table-column label="待随访" align="center" key="pendingFollowUpAgain" prop="pendingFollowUpAgain" />
                <el-table-column label="随访成功" align="center" key="followUpSuccessAgain" prop="followUpSuccessAgain" />
                <el-table-column label="随访失败" align="center" key="followUpFailAgain" prop="followUpFailAgain" />
                <el-table-column label="随访率" align="center" width="120" key="followUpRateAgain" prop="followUpRateAgain" />
                <el-table-column label="人工" align="center" key="manualAgain" prop="manualAgain" />
                <el-table-column label="短信" align="center" key="smsAgain" prop="smsAgain" />
                <el-table-column label="微信" align="center" key="weChatAgain" prop="weChatAgain" />
              </el-table-column>
            </el-table>
          </template>
        </el-table-column>
        <!-- è¡¨æ ¼åˆ—定义 -->
        <el-table-column
          label="出院病区"
          align="center"
          sortable
          key="leavehospitaldistrictname"
          prop="leavehospitaldistrictname"
          width="150"
          :show-overflow-tooltip="true"
          :sort-method="sortChineseNumber"
        />
        <el-table-column label="科室" align="center" key="deptname" prop="deptname" :show-overflow-tooltip="true" />
        <el-table-column label="出院人次" align="center" key="dischargeCount" prop="dischargeCount" />
        <el-table-column label="无需随访人次" align="center" width="100" key="nonFollowUp" prop="nonFollowUp" />
        <el-table-column label="应随访人次" align="center" width="100" key="followUpNeeded" prop="followUpNeeded" />
        <el-table-column align="center" label="再次出院随访">
          <el-table-column label="需随访" align="center" key="needFollowUpAgain" prop="needFollowUpAgain">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'needFollowUpAgainInfo', '再次随访需随访列表')">
                <span class="button-zx">{{ scope.row.needFollowUpAgain }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="待随访" align="center" key="pendingFollowUpAgain" prop="pendingFollowUpAgain">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'pendingFollowUpAgainInfo', '再次随访待随访列表')">
                <span class="button-zx">{{ scope.row.pendingFollowUpAgain }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="随访成功" align="center" key="followUpSuccessAgain" prop="followUpSuccessAgain">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'followUpSuccessAgainInfo', '再次随访随访成功列表')">
                <span class="button-zx">{{ scope.row.followUpSuccessAgain }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="随访失败" align="center" key="followUpFailAgain" prop="followUpFailAgain">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'followUpFailAgainInfo', '再次随访随访失败列表')">
                <span class="button-zx">{{ scope.row.followUpFailAgain }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="随访率" align="center" width="120" key="followUpRateAgain" prop="followUpRateAgain" />
          <el-table-column label="人工" align="center" key="manualAgain" prop="manualAgain">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'manualAgainInfo', '再次随访人工随访列表')">
                <span class="button-zx">{{ scope.row.manualAgain }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="短信" align="center" key="smsAgain" prop="smsAgain">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'smsAgainInfo', '再次随访短信随访列表')">
                <span class="button-zx">{{ scope.row.smsAgain }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="微信" align="center" key="weChatAgain" prop="weChatAgain">
            <template slot-scope="scope">
              <el-button size="medium" type="text" @click="handleViewDetails(scope.row, 'weChatAgainInfo', '再次随访微信随访列表')">
                <span class="button-zx">{{ scope.row.weChatAgain }}</span>
              </el-button>
            </template>
          </el-table-column>
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>
<script>
import { getSfStatistics } from "@/api/system/user";
import ExcelJS from "exceljs";
import { saveAs } from "file-saver";
export default {
  name: 'SecondFollowUp',
  props: {
    queryParams: {
      type: Object,
      required: true
    },
    flatArrayhospit: {
      type: Array,
      default: () => []
    },
    flatArraydept: {
      type: Array,
      default: () => []
    },
    options: {
      type: Array,
      default: () => []
    },
    orgname: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      tableData: [],
      loading: false,
      expands: [],
      ids: []
    }
  },
  methods: {
    loadData() {
      this.loading = true
      const params = {
        ...this.queryParams,
        visitCount: 2,
        leavehospitaldistrictcodes: this.queryParams.leavehospitaldistrictcodes.includes("all")
          ? this.getAllWardCodes()
          : this.queryParams.leavehospitaldistrictcodes,
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.getAllDeptCodes()
          : this.queryParams.deptcodes
      }
      delete params.leavehospitaldistrictcodes.all
      delete params.deptcodes.all
      getSfStatistics(params)
        .then(response => {
          this.tableData = this.customSort(response.data)
        })
        .catch(error => {
          console.error("获取再次随访数据失败:", error)
          this.$message.error("获取再次随访数据失败")
        })
        .finally(() => {
          this.loading = false
        })
    },
    getAllWardCodes() {
      return this.flatArrayhospit
        .filter(item => item.value !== 'all')
        .map(item => item.value)
    },
    getAllDeptCodes() {
      return this.flatArraydept
        .filter(item => item.value !== 'all')
        .map(item => item.value)
    },
    customSort(data) {
      const order = [
        "一","二","三","四","五","六","七","八","九","十",
        "十一","十二","十三","十四","十五","十六","十七","十八","十九","二十",
        "二十一","二十二","二十三","二十四","二十五","二十六","二十七","二十八","二十九","三十",
        "三十一","三十二","三十三","三十四","三十五","三十六","三十七","三十八","三十九","四十",
        "四十一","四十二","四十三","四十四","四十五"
      ]
      return data.sort((a, b) => {
        const getIndex = (name) => {
          if (!name || typeof name !== "string") return -1
          const chineseMatch = name.match(/^([一二三四五六七八九十]+)/)
          if (chineseMatch && chineseMatch[1]) {
            return order.indexOf(chineseMatch[1])
          }
          const arabicMatch = name.match(/^(\d+)/)
          if (arabicMatch && arabicMatch[1]) {
            const num = parseInt(arabicMatch[1], 10)
            if (num >= 1 && num <= 45) {
              return num - 1
            }
          }
          return -1
        }
        const indexA = getIndex(a.leavehospitaldistrictname)
        const indexB = getIndex(b.leavehospitaldistrictname)
        if (indexA === -1 && indexB === -1) {
          return (a.leavehospitaldistrictname || "").localeCompare(b.leavehospitaldistrictname || "")
        }
        if (indexA === -1) return 1
        if (indexB === -1) return -1
        return indexA - indexB
      })
    },
    sortChineseNumber(aRow, bRow) {
      const a = aRow.leavehospitaldistrictname
      const b = bRow.leavehospitaldistrictname
      const chineseNumMap = {
        ä¸€:1,二:2,三:3,四:4,五:5,六:6,七:7,八:8,九:9,十:10,
        åä¸€:11,十二:12,十三:13,十四:14,十五:15,十六:16,十七:17,十八:18,十九:19,二十:20,
        äºŒåä¸€:21,二十二:22,二十三:23,二十四:24,二十五:25,二十六:26,二十七:27,二十八:28,二十九:29,三十:30,
        ä¸‰åä¸€:31,三十二:32,三十三:33,三十四:34,三十五:35,三十六:36,三十七:37,三十八:38,三十九:39,四十:40,
        å››åä¸€:41,四十二:42,四十三:43,四十四:44,四十五:45
      }
      const getNumberFromText = (text) => {
        if (!text || typeof text !== "string") return -1
        const match = text.match(/^([一二三四五六七八九十]+)/)
        if (match && match[1]) {
          const chineseNum = match[1]
          return chineseNumMap[chineseNum] !== undefined ? chineseNumMap[chineseNum] : -1
        }
        const arabicMatch = text.match(/^(\d+)/)
        if (arabicMatch && arabicMatch[1]) {
          const num = parseInt(arabicMatch[1], 10)
          return num >= 1 && num <= 45 ? num : -1
        }
        return -1
      }
      const numA = getNumberFromText(a)
      const numB = getNumberFromText(b)
      if (numA === -1 && numB === -1) {
        return (a || "").localeCompare(b || "")
      }
      if (numA === -1) return 1
      if (numB === -1) return -1
      return numA - numB
    },
    getRowKey(row) {
      return row.statisticaltype === 1 ? row.leavehospitaldistrictcode : row.deptcode
    },
    handleRowClick(row) {
      if (this.expands.includes(this.getRowKey(row))) {
        this.expands = []
        return
      }
      const params = {
        ...this.queryParams,
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.getAllDeptCodes()
          : this.queryParams.deptcodes,
        leavehospitaldistrictcodes: [row.leavehospitaldistrictcode],
        drcode: "1",
        visitCount: 2
      }
      delete params.leavehospitaldistrictcodes.all
      delete params.deptcodes.all
      if (!row.doctorStats) {
        this.loading = true
        getSfStatistics(params).then((res) => {
          this.$set(row, "doctorStats", res.data)
          this.expands = [this.getRowKey(row)]
          this.loading = false
        })
      } else {
        this.expands = [this.getRowKey(row)]
      }
    },
    getSummaries(param) {
      const { columns, data } = param
      const sums = []
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "合计"
          return
        }
        if (index === 1 || index === 2) {
          sums[index] = "/"
          return
        }
        if (column.property === "followUpRateAgain") {
          const percentageValues = data
            .map((item) => {
              const value = item[column.property]
              if (!value || value === "-" || value === "0%") return null
              if (typeof value === "string" && value.includes("%")) {
                const numValue = parseFloat(value.replace("%", "")) / 100
                return isNaN(numValue) ? null : numValue
              } else {
                const numValue = parseFloat(value)
                return isNaN(numValue) ? null : numValue
              }
            })
            .filter((value) => value !== null && value !== 0)
          if (percentageValues.length > 0) {
            const average = percentageValues.reduce((sum, value) => sum + value, 0) / percentageValues.length
            sums[index] = (average * 100).toFixed(2) + "%"
          } else {
            sums[index] = "0.00%"
          }
        } else {
          const values = data.map((item) => {
            const value = item[column.property]
            if (value === "-" || value === "" || value === null) return 0
            return Number(value) || 0
          })
          if (!values.every((value) => isNaN(value))) {
            sums[index] = values.reduce((prev, curr) => prev + curr, 0)
            sums[index] = this.formatNumber(sums[index])
          } else {
            sums[index] = "-"
          }
        }
      })
      return sums
    },
    getInnerSummaries(param) {
      const { columns, data } = param
      const sums = []
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "小计"
          return
        }
        if (column.property === "drname" || column.property === "deptname") {
          sums[index] = "-"
          return
        }
        if (column.property === "followUpRateAgain") {
          const percentageValues = data
            .map((item) => {
              const value = item[column.property]
              if (!value || value === "-" || value === "0%") return null
              if (typeof value === "string" && value.includes("%")) {
                const numValue = parseFloat(value.replace("%", "")) / 100
                return isNaN(numValue) ? null : numValue
              } else {
                const numValue = parseFloat(value)
                return isNaN(numValue) ? null : numValue
              }
            })
            .filter((value) => value !== null && value !== 0)
          if (percentageValues.length > 0) {
            const average = percentageValues.reduce((sum, value) => sum + value, 0) / percentageValues.length
            sums[index] = (average * 100).toFixed(2) + "%"
          } else {
            sums[index] = "0.00%"
          }
        } else {
          const values = data.map((item) => {
            const value = item[column.property]
            if (value === "-" || value === "" || value === null) return 0
            return Number(value) || 0
          })
          if (!values.every((value) => isNaN(value))) {
            sums[index] = values.reduce((prev, curr) => prev + curr, 0)
            sums[index] = this.formatNumber(sums[index])
          } else {
            sums[index] = "-"
          }
        }
      })
      return sums
    },
    formatNumber(num) {
      if (isNaN(num)) return "-"
      return Number.isInteger(num) ? num.toString() : num.toFixed(0)
    },
    handleSelectionChange(selection) {
      this.ids = selection.map((item) => item.tagid)
    },
    handleViewDetails(row, infoKey, titleSuffix) {
      const title = `${row.leavehospitaldistrictname || row.deptname}${titleSuffix}`
      this.$emit('view-details', row[infoKey], title)
    },
    async exportTable() {
      try {
        let dateRangeString = ""
        let sheetNameSuffix = ""
        if (this.queryParams.dateRange && this.queryParams.dateRange.length === 2) {
          const startDateStr = this.queryParams.dateRange[0]
          const endDateStr = this.queryParams.dateRange[1]
          const formatDateForDisplay = (dateTimeStr) => {
            return dateTimeStr.split(" ")[0]
          }
          const startDateFormatted = formatDateForDisplay(startDateStr)
          const endDateFormatted = formatDateForDisplay(endDateStr)
          dateRangeString = `${startDateFormatted}至${endDateFormatted}`
          sheetNameSuffix = `${startDateFormatted}至${endDateFormatted}`
        } else {
          const now = new Date()
          const currentMonth = now.getMonth() + 1
          dateRangeString = `${currentMonth}月`
          sheetNameSuffix = `${currentMonth}月`
        }
        const excelName = `再次出院随访统计表_${dateRangeString}.xlsx`
        const worksheetName = `再次随访统计_${sheetNameSuffix}`
        if (!this.tableData || this.tableData.length === 0) {
          this.$message.warning("暂无再次随访数据可导出")
          return false
        }
        const workbook = new ExcelJS.Workbook()
        const worksheet = workbook.addWorksheet(worksheetName)
        // æž„建表格
        this.buildExportSheet(worksheet, sheetNameSuffix)
        const buffer = await workbook.xlsx.writeBuffer()
        const blob = new Blob([buffer], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        })
        saveAs(blob, excelName)
        this.$message.success("导出成功")
        return true
      } catch (error) {
        console.error("导出失败:", error)
        this.$message.error(`导出失败: ${error.message}`)
        return false
      }
    },
    buildExportSheet(worksheet, sheetNameSuffix) {
      const titleStyle = {
        font: { name: "微软雅黑", size: 16, bold: true, color: { argb: "FF000000" } },
        fill: { type: "pattern", pattern: "solid", fgColor: { argb: "FFE6F3FF" } },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } }
        }
      }
      const headerStyle = {
        font: { name: "微软雅黑", size: 11, bold: true, color: { argb: "FF000000" } },
        fill: { type: "pattern", pattern: "solid", fgColor: { argb: "FFF5F7FA" } },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } }
        }
      }
      const cellStyle = {
        font: { name: "宋体", size: 10, color: { argb: "FF000000" } },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } }
        }
      }
      const summaryStyle = {
        font: { name: "宋体", size: 10, bold: true, color: { argb: "FF409EFF" } },
        fill: { type: "pattern", pattern: "solid", fgColor: { argb: "FFF5F7FA" } },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } }
        }
      }
      // æ·»åŠ æ ‡é¢˜è¡Œ
      worksheet.mergeCells(1, 1, 1, 15)
      const titleCell = worksheet.getCell(1, 1)
      titleCell.value = `再次出院随访统计表_${sheetNameSuffix}`
      titleCell.style = titleStyle
      worksheet.getRow(1).height = 35
      // è¡¨å¤´
      const secondRowHeaders = [
        "", "出院病区", "科室", "出院人次", "无需随访人次", "应随访人次",
        "需随访", "待随访", "随访成功", "随访失败", "随访率", "人工", "短信", "微信"
      ]
      secondRowHeaders.forEach((header, index) => {
        const cell = worksheet.getCell(3, index + 1)
        cell.value = header
        cell.style = headerStyle
      })
      // åˆå¹¶å•元格
      for (let i = 1; i <= 6; i++) {
        worksheet.mergeCells(2, i, 3, i)
        const cell = worksheet.getCell(2, i)
        cell.style = headerStyle
      }
      worksheet.getCell(2, 1).value = ""
      worksheet.getCell(2, 2).value = "出院病区"
      worksheet.getCell(2, 3).value = "科室"
      worksheet.getCell(2, 4).value = "出院人次"
      worksheet.getCell(2, 5).value = "无需随访人次"
      worksheet.getCell(2, 6).value = "应随访人次"
      worksheet.mergeCells(2, 7, 2, 14)
      worksheet.getCell(2, 7).value = "再次出院随访"
      worksheet.getCell(2, 7).style = headerStyle
      worksheet.getRow(2).height = 28
      worksheet.getRow(3).height = 25
      // æ•°æ®è¡Œ
      this.tableData.forEach((item, rowIndex) => {
        const dataRow = worksheet.addRow([
          "",
          item.leavehospitaldistrictname || "",
          item.deptname || "",
          item.dischargeCount || 0,
          item.nonFollowUp || 0,
          item.followUpNeeded || 0,
          item.needFollowUpAgain || 0,
          item.pendingFollowUpAgain || 0,
          item.followUpSuccessAgain || 0,
          item.followUpFailAgain || 0,
          item.followUpRateAgain || "0%",
          item.manualAgain || 0,
          item.smsAgain || 0,
          item.weChatAgain || 0
        ], rowIndex + 4)
        dataRow.eachCell((cell) => {
          cell.style = cellStyle
        })
        dataRow.height = 24
      })
      // åˆè®¡è¡Œ
      const summaries = this.getExportSummaries()
      const summaryRow = worksheet.addRow(summaries)
      summaryRow.eachCell((cell, colNumber) => {
        cell.style = summaryStyle
        if (colNumber === 1) {
          cell.value = "合计"
        }
      })
      summaryRow.height = 28
      // åˆ—宽
      worksheet.columns = [
        { width: 8 }, { width: 20 }, { width: 15 }, { width: 12 }, { width: 12 }, { width: 12 },
        { width: 10 }, { width: 10 }, { width: 10 }, { width: 10 }, { width: 12 },
        { width: 8 }, { width: 8 }, { width: 8 }
      ]
    },
    getExportSummaries() {
      const summaries = ["合计", "/", "/", 0, 0, 0, 0, 0, 0, 0, "0%", 0, 0, 0]
      this.tableData.forEach((item) => {
        summaries[3] += Number(item.dischargeCount) || 0
        summaries[4] += Number(item.nonFollowUp) || 0
        summaries[5] += Number(item.followUpNeeded) || 0
        summaries[6] += Number(item.needFollowUpAgain) || 0
        summaries[7] += Number(item.pendingFollowUpAgain) || 0
        summaries[8] += Number(item.followUpSuccessAgain) || 0
        summaries[9] += Number(item.followUpFailAgain) || 0
        summaries[11] += Number(item.manualAgain) || 0
        summaries[12] += Number(item.smsAgain) || 0
        summaries[13] += Number(item.weChatAgain) || 0
      })
      const followUpRateAgainValues = this.tableData
        .map((item) => this.extractPercentageValue(item.followUpRateAgain))
        .filter((value) => value !== null)
      if (followUpRateAgainValues.length > 0) {
        const avgFollowUpRateAgain = followUpRateAgainValues.reduce((sum, val) => sum + val, 0) / followUpRateAgainValues.length
        summaries[10] = (avgFollowUpRateAgain * 100).toFixed(2) + "%"
      }
      summaries[3] = this.formatNumber(summaries[3])
      summaries[4] = this.formatNumber(summaries[4])
      summaries[5] = this.formatNumber(summaries[5])
      summaries[6] = this.formatNumber(summaries[6])
      summaries[7] = this.formatNumber(summaries[7])
      summaries[8] = this.formatNumber(summaries[8])
      summaries[9] = this.formatNumber(summaries[9])
      summaries[11] = this.formatNumber(summaries[11])
      summaries[12] = this.formatNumber(summaries[12])
      summaries[13] = this.formatNumber(summaries[13])
      return summaries
    },
    extractPercentageValue(value) {
      if (!value) return null
      if (typeof value === "string" && value.includes("%")) {
        const num = parseFloat(value.replace("%", ""))
        return isNaN(num) ? null : num / 100
      }
      const num = parseFloat(value)
      return isNaN(num) ? null : num
    }
  }
}
</script>
<style lang="scss" scoped>
.second-follow-up {
  .your-table-container {
    margin-top: 10px;
  }
  .button-zx {
    color: rgb(70, 204, 238);
  }
}
</style>
src/views/sfstatistics/percentage/components/TimelyRateDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,249 @@
<template>
  <el-dialog
    title="未及时随访患者服务"
    :visible.sync="visible"
    v-loading="loading"
    width="70%"
    :close-on-click-modal="false"
    @close="handleClose"
  >
    <div class="timely-rate-dialog">
      <div class="examine-jic">
        <div class="jic-value">
          <el-row :gutter="20">
            <!-- æœç´¢è¡¨å• -->
            <el-form
              :model="queryParams"
              ref="queryForm"
              size="small"
              :inline="true"
              label-width="98px"
              class="search-form"
            >
              <el-form-item label="患者:">
                <el-input
                  v-model="queryParams.name"
                  placeholder="请输入患者姓名"
                  @keyup.enter.native="handleSearch"
                />
              </el-form-item>
              <el-form-item label="患者诊断:">
                <el-input
                  v-model="queryParams.leavediagname"
                  placeholder="请输入患者诊断"
                  @keyup.enter.native="handleSearch"
                />
              </el-form-item>
              <el-form-item>
                <el-button
                  type="primary"
                  icon="el-icon-search"
                  size="medium"
                  @click="handleSearch"
                >
                  æœç´¢
                </el-button>
                <el-button
                  icon="el-icon-refresh"
                  size="medium"
                  @click="resetQuery"
                >
                  é‡ç½®
                </el-button>
              </el-form-item>
            </el-form>
            <!-- æ‚£è€…列表 -->
            <el-table :data="data" style="width: 100%" v-loading="loading">
              <el-table-column prop="sendname" align="center" label="姓名" width="100" />
              <el-table-column prop="taskName" align="center" width="200" show-overflow-tooltip label="任务名称" />
              <el-table-column prop="sendstate" align="center" width="200" label="任务状态">
                <template slot-scope="scope">
                  <el-tag
                    :type="getStateTagType(scope.row.sendstate)"
                    :disable-transitions="false"
                  >
                    {{ getStateText(scope.row.sendstate) }}
                  </el-tag>
                </template>
              </el-table-column>
              <el-table-column prop="visitTime" align="center" label="应随访时间" width="200" show-overflow-tooltip />
              <el-table-column prop="finishtime" align="center" label="随访完成时间" width="200" show-overflow-tooltip />
              <el-table-column label="出院日期" width="200" align="center" key="endtime" prop="endtime">
                <template slot-scope="scope">
                  <span>{{ formatTime(scope.row.endtime) }}</span>
                </template>
              </el-table-column>
              <el-table-column label="责任护士" width="120" align="center" key="nurseName" prop="nurseName" />
              <el-table-column label="主治医生" width="120" align="center" key="drname" prop="drname" />
              <el-table-column label="结果状态" align="center" key="excep" prop="excep" width="120">
                <template slot-scope="scope">
                  <dict-tag :options="dict.type.sys_yujing" :value="scope.row.excep" />
                </template>
              </el-table-column>
              <el-table-column label="处理意见" align="center" key="suggest" prop="suggest" width="120">
                <template slot-scope="scope">
                  <dict-tag :options="dict.type.sys_suggest" :value="scope.row.suggest" />
                </template>
              </el-table-column>
              <el-table-column prop="templatename" align="center" label="服务模板" width="200" show-overflow-tooltip />
              <el-table-column prop="remark" align="center" label="服务记录" width="200" show-overflow-tooltip />
              <el-table-column prop="bankcardno" align="center" label="呼叫状态" width="210" />
              <el-table-column label="操作" fixed="right" align="center" width="200" class-name="small-padding fixed-width">
                <template slot-scope="scope">
                  <el-button size="medium" type="text" @click="handleDetailsGo(scope.row)">
                    <span class="button-zx">
                      <i class="el-icon-s-order"></i>查看
                    </span>
                  </el-button>
                </template>
              </el-table-column>
            </el-table>
          </el-row>
          <!-- åˆ†é¡µ -->
          <pagination
            v-show="total > 0"
            :total="total"
            :page.sync="queryParams.pn"
            :limit.sync="queryParams.ps"
            @pagination="handlePagination"
          />
        </div>
      </div>
    </div>
  </el-dialog>
</template>
<script>
import Pagination from '@/components/Pagination'
export default {
  name: 'TimelyRateDialog',
  components: {
    Pagination
  },
  dicts: ['sys_yujing', 'sys_suggest'],
  props: {
    visible: {
      type: Boolean,
      default: false
    },
    loading: {
      type: Boolean,
      default: false
    },
    data: {
      type: Array,
      default: () => []
    },
    total: {
      type: Number,
      default: 0
    },
    queryParams: {
      type: Object,
      default: () => ({
        pn: 1,
        ps: 10
      })
    }
  },
  data() {
    return {
      localQueryParams: { ...this.queryParams }
    }
  },
  watch: {
    queryParams: {
      deep: true,
      handler(newParams) {
        this.localQueryParams = { ...newParams }
      }
    }
  },
  mounted() {
    this.localQueryParams = { ...this.queryParams }
  },
  methods: {
    handleSearch() {
      this.$emit('search', this.localQueryParams)
    },
    resetQuery() {
      this.localQueryParams = {
        pn: 1,
        ps: 10,
        name: '',
        leavediagname: ''
      }
      this.$emit('search', this.localQueryParams)
    },
    handlePagination(pagination) {
      this.localQueryParams.pn = pagination.page
      this.localQueryParams.ps = pagination.limit
      this.$emit('search', this.localQueryParams)
    },
    getStateTagType(state) {
      const stateMap = {
        1: 'primary',
        2: 'primary',
        3: 'success',
        4: 'info',
        5: 'danger',
        6: 'success'
      }
      return stateMap[state] || 'info'
    },
    getStateText(state) {
      const stateTextMap = {
        1: '表单已领取',
        2: '待随访',
        3: '表单已发送',
        4: '不执行',
        5: '发送失败',
        6: '已完成'
      }
      return stateTextMap[state] || '未知状态'
    },
    formatTime(time) {
      if (!time) return ''
      return this.parseTime(time)
    },
    handleDetailsGo(row) {
      this.$emit('details-go', row)
    },
    handleClose() {
      this.$emit('close')
    }
  }
}
</script>
<style lang="scss" scoped>
.timely-rate-dialog {
  .search-form {
    margin-bottom: 20px;
  }
  .button-zx {
    color: rgb(70, 204, 238);
  }
}
</style>
src/views/sfstatistics/percentage/components/styles.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
// å…¨å±€æ ·å¼
.follow-up-statistics,
.first-follow-up,
.second-follow-up,
.continued-care {
  .your-table-container {
    margin-top: 10px;
  }
  .button-zx {
    color: rgb(70, 204, 238);
  }
  // ç¾ŽåŒ–合计行样式
  ::v-deep .el-table__footer {
    .el-table__cell {
      background-color: #f5f7fa;
      font-weight: 600;
      color: #409eff;
      .cell {
        font-weight: 600;
        color: #409eff;
      }
    }
  }
  // å†…部表格合计行样式
  ::v-deep .inner-table .el-table__footer {
    .el-table__cell {
      background-color: #ecf5ff;
      font-weight: 500;
      color: #67c23a;
      .cell {
        font-weight: 500;
        color: #67c23a;
      }
    }
  }
  // ç™¾åˆ†æ¯”字段特殊样式
  ::v-deep .el-table__footer .el-table__cell[data-field="followUpRate"] .cell,
  ::v-deep .el-table__footer .el-table__cell[data-field="rate"] .cell,
  ::v-deep .el-table__footer .el-table__cell[data-field="followUpRateAgain"] .cell,
  ::v-deep .el-table__footer .el-table__cell[data-field="completionRate"] .cell {
    color: #e6a23c !important;
    font-weight: 700 !important;
  }
  // å†…层医生表格样式
  .inner-table {
    ::v-deep .el-table__header-wrapper {
      background-color: #f0f7ff !important;
      th {
        background-color: #f0f7ff !important;
      }
    }
    ::v-deep .el-table__body-wrapper {
      tr {
        background-color: #f9fbfe !important;
        &:hover {
          background-color: #e6f1ff !important;
        }
      }
    }
    ::v-deep .el-table--border {
      border-color: #d9e8ff !important;
      td, th {
        border-color: #d9e8ff !important;
      }
    }
  }
  /* ä½¿è¡Œæœ‰æ‰‹åž‹æŒ‡é’ˆ */
  ::v-deep .el-table__row {
    cursor: pointer;
  }
  /* å±•开行样式 */
  ::v-deep .el-table__expanded-cell {
    padding: 10px 0 !important;
    background: #f8f8f8;
  }
}
src/views/sfstatistics/percentage/index copy.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2898 @@
<template>
  <div class="Questionnairemanagement">
    <div class="leftvlue">
      <div class="leftvlue-bg">
        <el-row :gutter="20">
          <!--标签数据-->
          <el-col :span="24" :xs="24">
            <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
              label-width="98px">
              <el-form-item label="统计类型" prop="userName">
                <el-select v-model="queryParams.statisticaltype" placeholder="请选择统计类型">
                  <el-option v-for="item in Statisticallist" :key="item.value" :label="item.label" :value="item.value">
                  </el-option>
                </el-select>
                <el-select style="margin-left: 10px" v-if="queryParams.statisticaltype == 1"
                  v-model="queryParams.leavehospitaldistrictcodes" size="medium" multiple filterable
                  placeholder="请选择病区">
                  <el-option v-for="item in flatArrayhospit" :key="item.value" :label="item.label" :value="item.value">
                  </el-option>
                </el-select>
                <el-select v-else-if="queryParams.statisticaltype == 2" v-model="queryParams.deptcodes" size="medium"
                  multiple filterable placeholder="请选择科室">
                  <el-option v-for="item in flatArraydept" :key="item.value" :label="item.label" :value="item.value">
                  </el-option>
                </el-select>
              </el-form-item>
              <el-form-item label="服务类型" prop="userName">
                <el-select v-model="queryParams.serviceType" multiple placeholder="请选择">
                  <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
                  </el-option>
                </el-select>
              </el-form-item>
              <el-form-item label-width="200" label="应随访时间范围" prop="userName">
                <el-date-picker v-model="queryParams.dateRange" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
                  range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
                  :default-time="['00:00:00', '23:59:59']">
                </el-date-picker>
              </el-form-item>
              <el-form-item>
                <el-button type="primary" icon="el-icon-search" size="medium" @click="handleQuery">搜索</el-button>
                <el-button icon="el-icon-refresh" size="medium" @click="resetQuery">重置</el-button>
              </el-form-item>
              <el-button type="warning" plain icon="el-icon-download" size="medium" @click="exportTable">导出</el-button>
              <el-button type="primary" plain icon="el-icon-data-line" size="medium"
                @click="showChartDialog">统计趋势图</el-button>
            </el-form>
            <!-- æ–°å¢žï¼šTab标签页 -->
            <el-tabs v-model="activeTab" @tab-click="handleTabClick">
              <el-tab-pane label="首次随访" name="first">
                <div class="your-table-container">
                  <el-table ref="exportTable" id="exportTableid" v-loading="loading" :data="firstFollowUpList"
                    :border="true" @selection-change="handleSelectionChange" @expand-change="handleRowClick"
                    :row-key="getRowKey" show-summary :summary-method="getSummaries" :expand-row-keys="expands">
                    <!-- å±•开行箭头列 -->
                    <el-table-column type="expand">
                      <template slot-scope="props">
                        <el-table :data="props.row.doctorStats" border style="width: 95%; margin: 0 auto"
                          class="inner-table" show-summary :summary-method="getInnerSummaries">
                          <el-table-column label="医生姓名" prop="drname" align="center" />
                          <el-table-column label="科室" width="120" prop="deptname" align="center" />
                          <el-table-column label="出院人次" prop="dischargeCount" align="center" />
                          <el-table-column label="出院人次" align="center" key="dischargeCount" prop="dischargeCount">
                          </el-table-column>
                          <el-table-column label="无需随访人次" align="center" width="100" key="nonFollowUp"
                            prop="nonFollowUp">
                          </el-table-column>
                          <el-table-column label="应随访人次" align="center" width="100" key="followUpNeeded"
                            prop="followUpNeeded">
                          </el-table-column>
                          <el-table-column align="center" label="首次出院随访">
                            <el-table-column label="需随访" align="center" key="needFollowUp" prop="needFollowUp">
                            </el-table-column>
                            <el-table-column label="待随访" align="center" key="pendingFollowUp" prop="pendingFollowUp">
                            </el-table-column>
                            <el-table-column label="随访成功" align="center" key="followUpSuccess" prop="followUpSuccess">
                            </el-table-column>
                            <el-table-column label="随访失败" align="center" key="followUpFail" prop="followUpFail">
                            </el-table-column>
                            <el-table-column label="随访率" align="center" width="120" key="followUpRate"
                              prop="followUpRate">
                            </el-table-column>
                            <el-table-column v-if="orgname != '丽水市中医院'" label="及时率" align="center" width="120"
                              key="rate" prop="rate">
                              <template slot-scope="scope">
                                <el-button size="medium" type="text" @click="Seedetails(scope.row)"><span
                                    class="button-zx">{{
                                      (Number(scope.row.rate) * 100).toFixed(2)
                                    }}%</span></el-button>
                              </template>
                            </el-table-column>
                            <el-table-column label="人工" align="center" key="manual" prop="manual">
                            </el-table-column>
                            <el-table-column label="短信" align="center" key="sms" prop="sms">
                            </el-table-column>
                            <el-table-column label="微信" align="center" key="weChat" prop="weChat">
                            </el-table-column>
                          </el-table-column>
                        </el-table>
                      </template>
                    </el-table-column>
                    <el-table-column label="出院病区" align="center" sortable key="leavehospitaldistrictname"
                      prop="leavehospitaldistrictname" width="150" :show-overflow-tooltip="true"
                      :sort-method="sortChineseNumber" />
                    <el-table-column label="科室" align="center" key="deptname" prop="deptname"
                      :show-overflow-tooltip="true" />
                    <el-table-column label="出院人次" align="center" key="dischargeCount" prop="dischargeCount">
                    </el-table-column>
                    <el-table-column label="无需随访人次" align="center" width="100" key="nonFollowUp" prop="nonFollowUp">
                    </el-table-column>
                    <el-table-column label="应随访人次" align="center" width="100" key="followUpNeeded"
                      prop="followUpNeeded">
                    </el-table-column>
                    <el-table-column align="center" label="首次出院随访">
                      <el-table-column label="需随访" align="center" key="needFollowUp" prop="needFollowUp">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.needFollowUpInfo,
                              scope.row.leavehospitaldistrictname +
                              '需随访列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.needFollowUp
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="待随访" align="center" key="pendingFollowUp" prop="pendingFollowUp">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.pendingFollowUpInfo,
                              scope.row.leavehospitaldistrictname +
                              '待随访列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.pendingFollowUp
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="随访成功" align="center" key="followUpSuccess" prop="followUpSuccess">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.followUpSuccessInfo,
                              scope.row.leavehospitaldistrictname +
                              '随访成功列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.followUpSuccess
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="随访失败" align="center" key="followUpFail" prop="followUpFail">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.followUpFailInfo,
                              scope.row.leavehospitaldistrictname +
                              '随访失败列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.followUpFail
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="随访率" align="center" width="120" key="followUpRate" prop="followUpRate">
                      </el-table-column>
                      <el-table-column v-if="orgname != '丽水市中医院'" label="及时率" align="center" width="120" key="rate"
                        prop="rate">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="Seedetails(scope.row)"><span class="button-zx">{{
                            (Number(scope.row.rate) * 100).toFixed(2)
                          }}%</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="人工" align="center" key="manual" prop="manual">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.manualInfo,
                              scope.row.leavehospitaldistrictname +
                              '人工随访列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.manual
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="短信" align="center" key="sms" prop="sms">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.smsInfo,
                              scope.row.leavehospitaldistrictname +
                              '短信随访列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.sms
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="微信" align="center" key="weChat" prop="weChat">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.weChatInfo,
                              scope.row.leavehospitaldistrictname +
                              '微信随访列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.weChat
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                    </el-table-column>
                    <!-- éšè®¿æƒ…况列(仅丽水市中医院显示) -->
                    <el-table-column v-if="orgname == '丽水市中医院'" align="center" label="随访情况">
                      <el-table-column label="正常语音" align="center" width="100" key="taskSituation1"
                        prop="taskSituation1">
                      </el-table-column><el-table-column label="患者拒接或拒访" align="center" width="100" key="taskSituation2"
                        prop="taskSituation2">
                      </el-table-column><el-table-column label="面访或者接诊" align="center" width="100" key="taskSituation3"
                        prop="taskSituation3">
                      </el-table-column><el-table-column label="微信随访" align="center" width="100" key="taskSituation4"
                        prop="taskSituation4">
                      </el-table-column><el-table-column label="随访电话不正确" align="center" width="100" key="taskSituation5"
                        prop="taskSituation5">
                      </el-table-column><el-table-column label="其他情况不宜随访" align="center" width="100"
                        key="taskSituation6" prop="taskSituation6">
                      </el-table-column>
                    </el-table-column>
                  </el-table>
                </div>
              </el-tab-pane>
              <el-tab-pane label="再次随访" name="second">
                <div class="your-table-container">
                  <el-table ref="exportTableSecond" id="exportTableidSecond" v-loading="loadingSecond"
                    :data="secondFollowUpList" :border="true" @selection-change="handleSelectionChangeSecond"
                    @expand-change="handleRowClickSecond" :row-key="getRowKey" show-summary
                    :summary-method="getSummariesSecond" :expand-row-keys="expandsSecond">
                    <!-- å±•开行箭头列 -->
                    <el-table-column type="expand">
                      <template slot-scope="props">
                        <el-table :data="props.row.doctorStats" border style="width: 95%; margin: 0 auto"
                          class="inner-table" show-summary :summary-method="getInnerSummariesSecond">
                          <el-table-column label="医生姓名" prop="drname" align="center" />
                          <el-table-column label="科室" width="120" prop="deptname" align="center" />
                          <el-table-column label="出院人次" prop="dischargeCount" align="center" />
                          <el-table-column label="出院人次" align="center" key="dischargeCount" prop="dischargeCount">
                          </el-table-column>
                          <el-table-column label="无需随访人次" align="center" width="100" key="nonFollowUp"
                            prop="nonFollowUp">
                          </el-table-column>
                          <el-table-column label="应随访人次" align="center" width="100" key="followUpNeeded"
                            prop="followUpNeeded">
                          </el-table-column>
                          <el-table-column align="center" label="再次出院随访">
                            <el-table-column label="需随访" align="center" key="needFollowUpAgain"
                              prop="needFollowUpAgain">
                            </el-table-column>
                            <el-table-column label="待随访" align="center" key="pendingFollowUpAgain"
                              prop="pendingFollowUpAgain">
                            </el-table-column>
                            <el-table-column label="随访成功" align="center" key="followUpSuccessAgain"
                              prop="followUpSuccessAgain">
                            </el-table-column>
                            <el-table-column label="随访失败" align="center" key="followUpFailAgain"
                              prop="followUpFailAgain">
                            </el-table-column>
                            <el-table-column label="随访率" align="center" width="120" key="followUpRateAgain"
                              prop="followUpRateAgain">
                            </el-table-column>
                            <el-table-column label="人工" align="center" key="manualAgain" prop="manualAgain">
                            </el-table-column>
                            <el-table-column label="短信" align="center" key="smsAgain" prop="smsAgain">
                            </el-table-column>
                            <el-table-column label="微信" align="center" key="weChatAgain" prop="weChatAgain">
                            </el-table-column>
                          </el-table-column>
                        </el-table>
                      </template>
                    </el-table-column>
                    <el-table-column label="出院病区" align="center" sortable key="leavehospitaldistrictname"
                      prop="leavehospitaldistrictname" width="150" :show-overflow-tooltip="true"
                      :sort-method="sortChineseNumber" />
                    <el-table-column label="科室" align="center" key="deptname" prop="deptname"
                      :show-overflow-tooltip="true" />
                    <el-table-column label="出院人次" align="center" key="dischargeCount" prop="dischargeCount">
                    </el-table-column>
                    <el-table-column label="无需随访人次" align="center" width="100" key="nonFollowUp" prop="nonFollowUp">
                    </el-table-column>
                    <el-table-column label="应随访人次" align="center" width="100" key="followUpNeeded"
                      prop="followUpNeeded">
                    </el-table-column>
                    <el-table-column align="center" label="再次出院随访">
                      <el-table-column label="需随访" align="center" key="needFollowUpAgain" prop="needFollowUpAgain">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.needFollowUpAgainInfo,
                              scope.row.leavehospitaldistrictname +
                              '再次随访需随访列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.needFollowUpAgain
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="待随访" align="center" key="pendingFollowUpAgain"
                        prop="pendingFollowUpAgain">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.pendingFollowUpAgainInfo,
                              scope.row.leavehospitaldistrictname +
                              '再次随访待随访列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.pendingFollowUpAgain
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="随访成功" align="center" key="followUpSuccessAgain"
                        prop="followUpSuccessAgain">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.followUpSuccessAgainInfo,
                              scope.row.leavehospitaldistrictname +
                              '再次随访随访成功列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.followUpSuccessAgain
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="随访失败" align="center" key="followUpFailAgain" prop="followUpFailAgain">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.followUpFailAgainInfo,
                              scope.row.leavehospitaldistrictname +
                              '再次随访随访失败列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.followUpFailAgain
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="随访率" align="center" width="120" key="followUpRateAgain"
                        prop="followUpRateAgain">
                      </el-table-column>
                      <el-table-column label="人工" align="center" key="manualAgain" prop="manualAgain">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.manualAgainInfo,
                              scope.row.leavehospitaldistrictname +
                              '再次随访人工随访列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.manualAgain
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="短信" align="center" key="smsAgain" prop="smsAgain">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.smsAgainInfo,
                              scope.row.leavehospitaldistrictname +
                              '再次随访短信随访列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.smsAgain
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                      <el-table-column label="微信" align="center" key="weChatAgain" prop="weChatAgain">
                        <template slot-scope="scope">
                          <el-button size="medium" type="text" @click="
                            viewDetails(
                              scope.row.weChatAgainInfo,
                              scope.row.leavehospitaldistrictname +
                              '再次随访微信随访列表'
                            )
                            "><span class="button-zx">{{
                              scope.row.weChatAgain
                            }}</span></el-button>
                        </template>
                      </el-table-column>
                    </el-table-column>
                  </el-table>
                </div>
              </el-tab-pane>
            </el-tabs>
          </el-col>
        </el-row>
      </div>
    </div>
    <!-- ç»Ÿè®¡è¶‹åŠ¿å›¾å¼¹çª— -->
    <el-dialog title="随访统计趋势图" :visible.sync="chartDialogVisible" width="80%" :close-on-click-modal="false">
      <div class="chart-container">
        <el-row :gutter="20">
          <el-col :span="12">
            <div class="chart-title">随访状态分布</div>
            <div id="pieChart" style="width: 100%; height: 400px"></div>
          </el-col>
          <el-col :span="12">
            <div class="chart-title">随访趋势分析</div>
            <div id="barLineChart" style="width: 100%; height: 400px"></div>
          </el-col>
        </el-row>
      </div>
    </el-dialog>
    <el-dialog title="未及时随访患者服务" :visible.sync="SeedetailsVisible" v-loading="Seedloading" width="70%"
      :close-on-click-modal="false">
      <div class="examine-jic">
        <div class="jic-value">
          <el-row :gutter="20">
            <!--用户数据-->
            <el-form :model="patientqueryParams" ref="queryForm" size="small" :inline="true" label-width="98px">
              <el-form-item label="患者:">
                <el-input v-model="patientqueryParams.name" @keyup.enter.native="handleQuery"></el-input>
              </el-form-item>
              <el-form-item label="患者诊断:">
                <el-input v-model="patientqueryParams.leavediagname" @keyup.enter.native="handleQuery"></el-input>
              </el-form-item>
              <el-form-item>
                <el-button type="primary" icon="el-icon-search" size="medium" @click="handleQuery">搜索</el-button>
                <el-button icon="el-icon-refresh" size="medium" @click="resetQuery">取消创建</el-button>
              </el-form-item>
            </el-form>
            <!-- é€‰æ‹©æ‚£è€…列表 -->
            <el-table :data="logsheetlist" style="width: 100%">
              <el-table-column prop="sendname" align="center" label="姓名" width="100">
              </el-table-column>
              <el-table-column prop="taskName" align="center" width="200" show-overflow-tooltip label="任务名称">
              </el-table-column>
              <el-table-column prop="sendstate" align="center" width="200" label="任务状态">
                <template slot-scope="scope">
                  <div v-if="scope.row.sendstate == 1">
                    <el-tag type="primary" :disable-transitions="false">表单已领取</el-tag>
                  </div>
                  <div v-if="scope.row.sendstate == 2">
                    <el-tag type="primary" :disable-transitions="false">待随访</el-tag>
                  </div>
                  <div v-if="scope.row.sendstate == 3">
                    <el-tag type="success" :disable-transitions="false">表单已发送</el-tag>
                  </div>
                  <div v-if="scope.row.sendstate == 4">
                    <el-tag type="info" :disable-transitions="false">不执行</el-tag>
                  </div>
                  <div v-if="scope.row.sendstate == 5">
                    <el-tag type="danger" :disable-transitions="false">发送失败</el-tag>
                  </div>
                  <div v-if="scope.row.sendstate == 6">
                    <el-tag type="success" :disable-transitions="false">已完成</el-tag>
                  </div>
                </template>
              </el-table-column>
              <el-table-column prop="visitTime" align="center" label="应随访时间" width="200" show-overflow-tooltip>
              </el-table-column>
              <el-table-column prop="finishtime" align="center" label="随访完成时间" width="200" show-overflow-tooltip>
              </el-table-column>
              <el-table-column label="出院日期" width="200" align="center" key="endtime" prop="endtime">
                <template slot-scope="scope">
                  <span>{{ formatTime(scope.row.endtime) }}</span>
                </template></el-table-column>
              <el-table-column label="责任护士" width="120" align="center" key="nurseName" prop="nurseName" />
              <el-table-column label="主治医生" width="120" align="center" key="drname" prop="drname" />
              <el-table-column label="结果状态" align="center" key="excep" prop="excep" width="120">
                <template slot-scope="scope">
                  <dict-tag :options="dict.type.sys_yujing" :value="scope.row.excep" />
                </template>
              </el-table-column>
              <el-table-column label="处理意见" align="center" key="suggest" prop="suggest" width="120">
                <template slot-scope="scope">
                  <dict-tag :options="dict.type.sys_suggest" :value="scope.row.suggest" />
                </template>
              </el-table-column>
              <el-table-column prop="templatename" align="center" label="服务模板" width="200" show-overflow-tooltip>
              </el-table-column>
              <el-table-column prop="remark" align="center" label="服务记录" width="200" show-overflow-tooltip>
              </el-table-column>
              <el-table-column prop="bankcardno" align="center" label="呼叫状态" width="210">
              </el-table-column>
              <el-table-column label="操作" fixed="right" align="center" width="200"
                class-name="small-padding fixed-width">
                <template slot-scope="scope">
                  <el-button size="medium" type="text" @click="SeedetailsgGo(scope.row)"><span class="button-zx"><i
                        class="el-icon-s-order"></i>查看</span></el-button>
                </template>
              </el-table-column>
            </el-table>
          </el-row>
          <pagination v-show="patienttotal > 0 && this.patientqueryParams.allhosp != 6" :total="patienttotal"
            :page.sync="patientqueryParams.pn" :limit.sync="patientqueryParams.ps" @pagination="Seedetailstion" />
        </div>
      </div>
    </el-dialog>
    <!-- å„类详情 -->
    <el-dialog :title="infotitle" :visible.sync="infotitleVisible" v-loading="infotitloading" width="70%"
      :close-on-click-modal="false">
      <div style="margin-bottom: 16px; display: flex; align-items: center">
        <span style="margin-right: 10px; font-weight: bold">患者姓名查询:</span>
        <el-input v-model="searchName" placeholder="请输入患者姓名进行筛选" clearable style="width: 300px" @input="handleSearch"
          @clear="handleSearch">
        </el-input>
        <span style="margin-left: 10px; color: rgb(35, 81, 233); font-size: 16px">
          å…± {{ infotitlelist.length }} æ¡è®°å½•
        </span>
      </div>
      <div class="examine-jic">
        <div class="jic-value">
          <el-row :gutter="20">
            <!-- é€‰æ‹©æ‚£è€…列表 -->
            <div class="data-list" ref="dataList" @scroll="handleScroll" v-loading="infotitloading">
              <el-table :data="currentDisplayList" height="660" style="width: 100%">
                <el-table-column prop="sendname" align="center" label="姓名" width="100">
                </el-table-column>
                <el-table-column prop="taskName" align="center" width="200" show-overflow-tooltip label="任务名称">
                </el-table-column>
                <el-table-column prop="sendstate" align="center" width="200" label="任务状态">
                  <template slot-scope="scope">
                    <div v-if="scope.row.sendstate == 1">
                      <el-tag type="primary" :disable-transitions="false">表单已领取</el-tag>
                    </div>
                    <div v-if="scope.row.sendstate == 2">
                      <el-tag type="primary" :disable-transitions="false">待随访</el-tag>
                    </div>
                    <div v-if="scope.row.sendstate == 3">
                      <el-tag type="success" :disable-transitions="false">表单已发送</el-tag>
                    </div>
                    <div v-if="scope.row.sendstate == 4">
                      <el-tag type="info" :disable-transitions="false">不执行</el-tag>
                    </div>
                    <div v-if="scope.row.sendstate == 5">
                      <el-tag type="danger" :disable-transitions="false">发送失败</el-tag>
                    </div>
                    <div v-if="scope.row.sendstate == 6">
                      <el-tag type="success" :disable-transitions="false">已完成</el-tag>
                    </div>
                  </template>
                </el-table-column>
                <el-table-column label="任务执行方式" align="center" key="preachform" prop="preachform" width="160"
                  :show-overflow-tooltip="true">
                  <template slot-scope="scope">
                    <span v-for="item in scope.row.preachform">{{ item }}、
                    </span>
                  </template>
                </el-table-column>
                <el-table-column prop="visitTime" align="center" label="应随访时间" width="200" show-overflow-tooltip>
                </el-table-column>
                <el-table-column prop="finishtime" align="center" label="随访完成时间" width="200" show-overflow-tooltip>
                </el-table-column>
                <el-table-column label="出院日期" width="200" align="center" key="endtime" prop="endtime">
                  <template slot-scope="scope">
                    <span>{{ formatTime(scope.row.endtime) }}</span>
                  </template></el-table-column>
                <el-table-column label="责任护士" width="120" align="center" key="nurseName" prop="nurseName" />
                <el-table-column label="主治医生" width="120" align="center" key="drname" prop="drname" />
                <el-table-column label="结果状态" align="center" key="excep" prop="excep" width="120">
                  <template slot-scope="scope">
                    <dict-tag :options="dict.type.sys_yujing" :value="scope.row.excep" />
                  </template>
                </el-table-column>
                <el-table-column label="处理意见" align="center" key="suggest" prop="suggest" width="120">
                  <template slot-scope="scope">
                    <dict-tag :options="dict.type.sys_suggest" :value="scope.row.suggest" />
                  </template>
                </el-table-column>
                <el-table-column prop="templatename" align="center" label="服务模板" width="200" show-overflow-tooltip>
                </el-table-column>
                <el-table-column prop="remark" align="center" label="服务记录" width="200" show-overflow-tooltip>
                </el-table-column>
                <el-table-column prop="bankcardno" align="center" label="呼叫状态" width="210">
                </el-table-column>
                <el-table-column label="操作" fixed="right" align="center" width="200"
                  class-name="small-padding fixed-width">
                  <template slot-scope="scope">
                    <el-button size="medium" type="text" @click="SeedetailsgGo(scope.row)"><span class="button-zx"><i
                          class="el-icon-s-order"></i>查看</span></el-button>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </el-row>
        </div>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import {
  toamendtag,
  addapitag,
  deletetag,
  changetagcategory,
} from "@/api/system/label";
import store from "@/store";
import { getSfStatistics, selectTimelyRate } from "@/api/system/user";
import * as XLSX from "xlsx";
import FileSaver from "file-saver";
import ExcelJS from "exceljs";
import { saveAs } from "file-saver";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
const shortcuts = [
  {
    text: "今天",
    onClick(picker) {
      picker.$emit("pick", new Date());
    },
  },
  {
    text: "昨天",
    onClick(picker) {
      const date = new Date();
      date.setTime(date.getTime() - 3600 * 1000 * 24);
      picker.$emit("pick", date);
    },
  },
  {
    text: "一周前",
    onClick(picker) {
      const date = new Date();
      date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
      picker.$emit("pick", date);
    },
  },
];
export default {
  name: "Percentage",
  dicts: ["sys_normal_disable", "sys_user_sex"],
  components: { Treeselect },
  data() {
    return {
      // æ–°å¢žï¼šTab标签页控制
      activeTab: "first", // å½“前激活的tab,first-首次随访,second-再次随访
      // åˆ†ç¦»çš„æ•°æ®åˆ—表
      firstFollowUpList: [], // é¦–次随访数据
      secondFollowUpList: [], // å†æ¬¡éšè®¿æ•°æ®
      // åˆ†ç¦»çš„加载状态
      loading: false, // é¦–次随访表格加载状态
      loadingSecond: false, // å†æ¬¡éšè®¿è¡¨æ ¼åŠ è½½çŠ¶æ€
      // åˆ†ç¦»çš„展开状态
      expands: [], // é¦–次随访表格展开行
      expandsSecond: [], // å†æ¬¡éšè®¿è¡¨æ ¼å±•开行
      // åˆ†ç¦»çš„选择状态
      ids: [], // é¦–次随访选中项
      idsSecond: [], // å†æ¬¡éšè®¿é€‰ä¸­é¡¹
      orgname: "",
      infotitlelist: [],
      currentDisplayList: [],
      loadIndex: 0,
      pageSize: 100,
      isLoading: false,
      Seedloading: false,
      chartDialogVisible: false,
      infotitleVisible: false,
      searchName: "",
      infotitloading: false,
      infotitle: "",
      pieChart: null,
      barLineChart: null,
      single: true,
      multiple: true,
      showSearch: true,
      idds: "",
      total: 0,
      flatArrayhospit: [],
      flatArraydept: [],
      patienttotal: 0,
      logsheetlist: [],
      Statisticallist: [
        {
          label: "病区统计",
          value: 1,
        },
        {
          label: "科室统计",
          value: 2,
        },
      ],
      patientqueryParams: {
        pn: 1,
        ps: 10,
      },
      amendtag: false,
      lstamendtag: false,
      scavisible: false,
      deleteVisible: false,
      deletefenl: "高血压",
      tagform: {
        isupload: "",
        tagname: "",
        tagcategoryid: "",
        tagdescription: "",
      },
      classifyform: {
        categoryname: "",
      },
      title: "",
      open: false,
      dateRange: [],
      postOptions: [],
      roleOptions: [],
      allDeptCodes: [],
      allWardCodes: [],
      checkboxlist: [],
      form: {},
      forms: {
        name: "",
      },
      numberlb: 22,
      dialogFormVisible: false,
      lstamendtagVisible: false,
      goQRCodeVisible: false,
      sidecolumnval: "",
      propss: { multiple: true },
      SeedetailsVisible: false,
      options: store.getters.tasktypes,
      pickerOptions: {
        disabledDate(time) {
          return time.getTime() < Date.now() - 3600 * 1000 * 24;
        },
        shortcuts: shortcuts,
      },
      pickerOptionsa: {
        disabledDate(time) {
          return time.getTime() > Date.now();
        },
        shortcuts: shortcuts,
      },
      queryParams: {
        serviceType: [2],
        dateRange: [],
        statisticaltype: 1,
        leavehospitaldistrictcodes: ["all"],
        deptcodes: [],
        visitCount: 1, // æ–°å¢žï¼šéšè®¿æ¬¡æ•°å‚数,1-首次,2-再次
      },
      columns: [
        { key: 0, label: `标签编号`, visible: true },
        { key: 1, label: `标签名称`, visible: true },
        { key: 2, label: `标签昵称`, visible: true },
        { key: 3, label: `部门`, visible: true },
        { key: 4, label: `手机号码`, visible: true },
        { key: 5, label: `状态`, visible: true },
        { key: 6, label: `创建时间`, visible: true },
      ],
    };
  },
  watch: {},
  created() {
    this.getDeptTree();
    this.getFirstFollowUpList(); // é»˜è®¤åŠ è½½é¦–æ¬¡éšè®¿æ•°æ®
    this.checkboxlist = store.getters.checkboxlist;
    this.orgname = localStorage.getItem("orgname");
  },
  methods: {
    /** æŸ¥è¯¢é¦–次随访列表 */
    async getFirstFollowUpList() {
      this.loading = true;
      this.queryParams.visitCount = 1; // è®¾ç½®éšè®¿æ¬¡æ•°ä¸ºé¦–次
      const params = {
        ...this.queryParams,
        leavehospitaldistrictcodes:
          this.queryParams.leavehospitaldistrictcodes.includes("all")
            ? this.allWardCodes
            : this.queryParams.leavehospitaldistrictcodes,
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.allDeptCodes
          : this.queryParams.deptcodes,
      };
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      try {
        const response = await getSfStatistics(params);
        this.firstFollowUpList = this.customSort(response.data);
        this.total = response.total;
      } catch (error) {
        console.error("获取首次随访数据失败:", error);
        this.$message.error("获取首次随访数据失败");
      } finally {
        this.loading = false;
      }
    },
    /** æŸ¥è¯¢å†æ¬¡éšè®¿åˆ—表 */
    async getSecondFollowUpList() {
      this.loadingSecond = true;
      this.queryParams.visitCount = 2; // è®¾ç½®éšè®¿æ¬¡æ•°ä¸ºå†æ¬¡
      const params = {
        ...this.queryParams,
        leavehospitaldistrictcodes:
          this.queryParams.leavehospitaldistrictcodes.includes("all")
            ? this.allWardCodes
            : this.queryParams.leavehospitaldistrictcodes,
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.allDeptCodes
          : this.queryParams.deptcodes,
      };
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      try {
        const response = await getSfStatistics(params);
        this.secondFollowUpList = this.customSort(response.data);
        this.total = response.total;
      } catch (error) {
        console.error("获取再次随访数据失败:", error);
        this.$message.error("获取再次随访数据失败");
      } finally {
        this.loadingSecond = false;
      }
    },
    /** Tab切换事件 */
    handleTabClick(tab) {
      if (tab.name === "first") {
        if (this.firstFollowUpList.length === 0) {
          this.getFirstFollowUpList();
        }
      } else if (tab.name === "second") {
        if (this.secondFollowUpList.length === 0) {
          this.getSecondFollowUpList();
        }
      }
    },
    sortChineseNumber(aRow, bRow) {
      const a = aRow.leavehospitaldistrictname;
      const b = bRow.leavehospitaldistrictname;
      // ä¸­æ–‡æ•°å­—到阿拉伯数字的映射(扩展到45)
      const chineseNumMap = {
        ä¸€: 1,
        äºŒ: 2,
        ä¸‰: 3,
        å››: 4,
        äº”: 5,
        å…­: 6,
        ä¸ƒ: 7,
        å…«: 8,
        ä¹: 9,
        å: 10,
        åä¸€: 11,
        åäºŒ: 12,
        åä¸‰: 13,
        åå››: 14,
        åäº”: 15,
        åå…­: 16,
        åä¸ƒ: 17,
        åå…«: 18,
        åä¹: 19,
        äºŒå: 20,
        äºŒåä¸€: 21,
        äºŒåäºŒ: 22,
        äºŒåä¸‰: 23,
        äºŒåå››: 24,
        äºŒåäº”: 25,
        äºŒåå…­: 26,
        äºŒåä¸ƒ: 27,
        äºŒåå…«: 28,
        äºŒåä¹: 29,
        ä¸‰å: 30,
        ä¸‰åä¸€: 31,
        ä¸‰åäºŒ: 32,
        ä¸‰åä¸‰: 33,
        ä¸‰åå››: 34,
        ä¸‰åäº”: 35,
        ä¸‰åå…­: 36,
        ä¸‰åä¸ƒ: 37,
        ä¸‰åå…«: 38,
        ä¸‰åä¹: 39,
        å››å: 40,
        å››åä¸€: 41,
        å››åäºŒ: 42,
        å››åä¸‰: 43,
        å››åå››: 44,
        å››åäº”: 45,
      };
      // æå–中文数字
      const getNumberFromText = (text) => {
        if (!text || typeof text !== "string") return -1;
        // åŒ¹é…ä¸­æ–‡æ•°å­—,支持一到四十五
        const match = text.match(/^([一二三四五六七八九十]+)/);
        if (match && match[1]) {
          const chineseNum = match[1];
          return chineseNumMap[chineseNum] !== undefined
            ? chineseNumMap[chineseNum]
            : -1;
        }
        // å¦‚果没有匹配到中文数字,尝试匹配阿拉伯数字
        const arabicMatch = text.match(/^(\d+)/);
        if (arabicMatch && arabicMatch[1]) {
          const num = parseInt(arabicMatch[1], 10);
          return num >= 1 && num <= 45 ? num : -1;
        }
        return -1;
      };
      const numA = getNumberFromText(a);
      const numB = getNumberFromText(b);
      // å¤„理无法解析的情况
      if (numA === -1 && numB === -1) {
        return (a || "").localeCompare(b || "");
      }
      if (numA === -1) return 1;
      if (numB === -1) return -1;
      return numA - numB;
    },
    // æœç´¢å¤„理函数
    handleSearch() {
      if (!this.searchName.trim()) {
        // å¦‚果搜索框为空,显示所有数据
        this.currentDisplayList = [...this.infotitlelist];
      } else {
        // æ ¹æ®æ‚£è€…姓名进行筛选(不区分大小写)
        const keyword = this.searchName.toLowerCase();
        this.currentDisplayList = this.infotitlelist.filter((item) => {
          return item.sendname && item.sendname.toLowerCase().includes(keyword);
        });
      }
    },
    customSort(data) {
      // å®šä¹‰æ‚¨æœŸæœ›çš„病区顺序(扩展到四十五)
      const order = [
        "一",
        "二",
        "三",
        "四",
        "五",
        "六",
        "七",
        "八",
        "九",
        "十",
        "十一",
        "十二",
        "十三",
        "十四",
        "十五",
        "十六",
        "十七",
        "十八",
        "十九",
        "二十",
        "二十一",
        "二十二",
        "二十三",
        "二十四",
        "二十五",
        "二十六",
        "二十七",
        "二十八",
        "二十九",
        "三十",
        "三十一",
        "三十二",
        "三十三",
        "三十四",
        "三十五",
        "三十六",
        "三十七",
        "三十八",
        "三十九",
        "四十",
        "四十一",
        "四十二",
        "四十三",
        "四十四",
        "四十五",
      ];
      return data.sort((a, b) => {
        // æå–病区名称中的中文数字部分
        const getIndex = (name) => {
          if (!name || typeof name !== "string") return -1;
          // åŒ¹é…ä¸­æ–‡æ•°å­—
          const chineseMatch = name.match(/^([一二三四五六七八九十]+)/);
          if (chineseMatch && chineseMatch[1]) {
            return order.indexOf(chineseMatch[1]);
          }
          // åŒ¹é…é˜¿æ‹‰ä¼¯æ•°å­—
          const arabicMatch = name.match(/^(\d+)/);
          if (arabicMatch && arabicMatch[1]) {
            const num = parseInt(arabicMatch[1], 10);
            if (num >= 1 && num <= 45) {
              return num - 1; // å› ä¸ºæ•°ç»„索引从0开始
            }
          }
          return -1;
        };
        const indexA = getIndex(a.leavehospitaldistrictname);
        const indexB = getIndex(b.leavehospitaldistrictname);
        // æŽ’序逻辑
        if (indexA === -1 && indexB === -1) {
          return (a.leavehospitaldistrictname || "").localeCompare(
            b.leavehospitaldistrictname || ""
          );
        }
        if (indexA === -1) return 1;
        if (indexB === -1) return -1;
        return indexA - indexB;
      });
    },
    getRowKey(row) {
      return row.statisticaltype === 1
        ? row.leavehospitaldistrictcode
        : row.deptcode;
    },
    // å¤„理行点击展开
    handleRowClick(row) {
      console.log(row, "row");
      // å¦‚果已经展开则收起
      if (this.expands.includes(this.getRowKey(row))) {
        this.expands = [];
        return;
      }
      // å¤„理查询参数
      const params = {
        ...this.queryParams,
        // å¦‚果选择了"全部",则传所有病区/科室代码
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.allDeptCodes
          : this.queryParams.deptcodes,
        leavehospitaldistrictcodes: [row.leavehospitaldistrictcode],
        drcode: "1",
        visitCount: 1, // è®¾ç½®ä¸ºé¦–次随访
      };
      // ç§»é™¤å¯èƒ½å­˜åœ¨çš„"all"值
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      // å¦‚果该行还没有加载医生数据,则加载
      if (!row.doctorStats) {
        this.loading = true;
        getSfStatistics(params).then((res) => {
          this.$set(row, "doctorStats", res.data);
          this.expands = [this.getRowKey(row)];
          this.loading = false;
        });
      } else {
        this.expands = [this.getRowKey(row)];
      }
    },
    getSummaries(param) {
      const { columns, data } = param;
      const sums = [];
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "合计";
          return;
        }
        if (index === 1 || index === 2) {
          sums[index] = "/";
          return;
        }
        // å¯¹ç™¾åˆ†æ¯”字段特殊处理 - å–平均值
        if (
          column.property === "followUpRate" ||
          column.property === "rate" ||
          column.property === "followUpRateAgain"
        ) {
          // æå–所有有效百分比值并转换为小数
          const percentageValues = data
            .map((item) => {
              const value = item[column.property];
              if (!value || value === "-" || value === "0%") return null;
              // å¤„理带百分号的数据
              if (typeof value === "string" && value.includes("%")) {
                // åŽ»é™¤ç™¾åˆ†å·å¹¶è½¬æ¢ä¸ºå°æ•°
                const numValue = parseFloat(value.replace("%", "")) / 100;
                return isNaN(numValue) ? null : numValue;
              } else {
                // å¤„理已经是小数的数据
                const numValue = parseFloat(value);
                return isNaN(numValue) ? null : numValue;
              }
            })
            .filter((value) => value !== null && value !== 0); // è¿‡æ»¤æŽ‰null和0值
          if (percentageValues.length > 0) {
            const average =
              percentageValues.reduce((sum, value) => sum + value, 0) /
              percentageValues.length;
            sums[index] = (average * 100).toFixed(2) + "%";
          } else {
            sums[index] = "0.00%";
          }
        } else {
          // æ™®é€šæ•°å­—字段 - æ±‚å’Œ
          const values = data.map((item) => {
            const value = item[column.property];
            if (value === "-" || value === "" || value === null) return 0;
            return Number(value) || 0;
          });
          if (!values.every((value) => isNaN(value))) {
            sums[index] = values.reduce((prev, curr) => prev + curr, 0);
            sums[index] = this.formatNumber(sums[index]);
          } else {
            sums[index] = "-";
          }
        }
      });
      return sums;
    },
    // å†…部表格合计行计算方法
    getInnerSummaries(param) {
      const { columns, data } = param;
      const sums = [];
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "小计";
          return;
        }
        if (column.property === "drname" || column.property === "deptname") {
          sums[index] = "-";
          return;
        }
        // å¯¹ç™¾åˆ†æ¯”字段特殊处理 - å–平均值
        if (column.property === "followUpRate" || column.property === "rate") {
          // æå–所有有效百分比值并转换为小数
          const percentageValues = data
            .map((item) => {
              const value = item[column.property];
              if (!value || value === "-" || value === "0%") return null;
              // å¤„理带百分号的数据
              if (typeof value === "string" && value.includes("%")) {
                // åŽ»é™¤ç™¾åˆ†å·å¹¶è½¬æ¢ä¸ºå°æ•°
                const numValue = parseFloat(value.replace("%", "")) / 100;
                return isNaN(numValue) ? null : numValue;
              } else {
                // å¤„理已经是小数的数据
                const numValue = parseFloat(value);
                return isNaN(numValue) ? null : numValue;
              }
            })
            .filter((value) => value !== null && value !== 0);
          if (percentageValues.length > 0) {
            const average =
              percentageValues.reduce((sum, value) => sum + value, 0) /
              percentageValues.length;
            sums[index] = (average * 100).toFixed(2) + "%";
          } else {
            sums[index] = "0.00%";
          }
        } else {
          // æ™®é€šæ•°å­—字段 - æ±‚å’Œ
          const values = data.map((item) => {
            const value = item[column.property];
            if (value === "-" || value === "" || value === null) return 0;
            return Number(value) || 0;
          });
          if (!values.every((value) => isNaN(value))) {
            sums[index] = values.reduce((prev, curr) => prev + curr, 0);
            sums[index] = this.formatNumber(sums[index]);
          } else {
            sums[index] = "-";
          }
        }
      });
      return sums;
    },
    // è¾…助方法:提取百分比数值
    extractPercentageValue(value) {
      if (!value) return null;
      if (typeof value === "string") {
        // å¤„理带百分号的字符串
        if (value.includes("%")) {
          const num = parseFloat(value.replace("%", ""));
          return isNaN(num) ? null : num / 100;
        }
        // å¤„理纯数字字符串
        const num = parseFloat(value);
        return isNaN(num) ? null : num;
      }
      // å¤„理数字类型
      return typeof value === "number" ? value : null;
    },
    // æ•°å­—格式化方法
    formatNumber(num) {
      if (isNaN(num)) return "-";
      return Number.isInteger(num) ? num.toString() : num.toFixed(0);
    },
    /** ä¿®æ”¹æ ‡ç­¾ */
    handleUpdate(row) {
      console.log(row, "修改标签");
      this.lstamendtagVisible = true;
      this.lstamendtag = true;
      this.tagform = {
        isupload: row.isupload,
        tagname: row.tagname,
        tagcategoryid: row.tagcategoryid,
        tagdescription: row.tagdescription,
        tagid: row.tagid,
      };
    },
    // èŽ·å–ç§‘å®¤æ ‘
    getDeptTree() {
      // ç§‘室列表
      this.flatArraydept = store.getters.belongDepts.map((dept) => {
        return {
          label: dept.deptName,
          value: dept.deptCode,
        };
      });
      // å­˜å‚¨æ‰€æœ‰ç§‘室代码
      this.allDeptCodes = store.getters.belongDepts.map(
        (dept) => dept.deptCode
      );
      // ç—…区列表
      this.flatArrayhospit = store.getters.belongWards.map((ward) => {
        return {
          label: ward.districtName,
          value: ward.districtCode,
        };
      });
      // å­˜å‚¨æ‰€æœ‰ç—…区代码
      this.allWardCodes = store.getters.belongWards.map(
        (ward) => ward.districtCode
      );
      this.flatArraydept.push({ label: "全部", value: "all" });
      this.flatArrayhospit.push({ label: "全部", value: "all" });
    },
    flattenArray(multiArray) {
      let result = [];
      // é€’归函数,用于将多级数组转换为一维数组,只包含最底层的元素
      function flatten(element) {
        // å¦‚果当前元素有子元素,继续递归
        if (element.children && element.children.length > 0) {
          element.children.forEach((child) => flatten(child));
        } else {
          // å…‹éš†å…ƒç´ ä»¥é¿å…ä¿®æ”¹åŽŸå§‹æ•°æ®
          let item = JSON.parse(JSON.stringify(element));
          result.push(item); // å°†æœ€åº•层的元素添加到结果数组
        }
      }
      // ä»Žé¡¶å±‚元素开始递归
      multiArray.forEach((element) => flatten(element));
      return result; // è¿”回只包含最底层元素的一维数组
    },
    addladeltag() {
      this.lstamendtagVisible = true;
      this.lstamendtag = false;
      this.tagform = {
        isupload: "",
        tagname: "",
        tagcategoryid: "",
        tagdescription: "",
        tagid: "",
      };
    },
    Seedetails(row) {
      this.SeedetailsVisible = true;
      this.Seedloading = true;
      this.patientqueryParams.starttime = this.parseTime(
        this.queryParams.dateRange[0]
      );
      this.patientqueryParams.endtime = this.parseTime(
        this.queryParams.dateRange[1]
      );
      this.patientqueryParams.deptcode = row.deptcode;
      selectTimelyRate(this.patientqueryParams).then((response) => {
        this.logsheetlist = response.data.detail;
        this.patienttotal = response.data.total;
        this.Seedloading = false;
      });
    },
    Seedetailstion() {
      selectTimelyRate(this.patientqueryParams).then((response) => {
        this.logsheetlist = response.data.detail;
        this.patienttotal = response.data.total;
        this.Seedloading = false;
      });
    },
    viewDetails(row, title) {
      this.infotitleVisible = true;
      this.infotitle = title;
      this.infotitlelist = row; // å‡è®¾row就是需要展示的详细数组
      console.log(this.infotitlelist, "this.infotitlelist");
      this.infotitlelist.forEach((item) => {
        let idArray = null;
        if (item.preachform) {
          if (item.endtime) {
            item.preachformson = item.preachform;
            idArray = item.preachform.split(",");
          }
          item.preachform = idArray.map((value) => {
            // æŸ¥æ‰¾id对应的对象
            const item = this.checkboxlist.find((item) => item.value == value);
            // å¦‚果找到对应的id,返回label值,否则返回null
            return item ? item.label : null;
          });
        }
      });
      // åˆå§‹åŒ–加载
      this.loadIndex = 0;
      this.currentDisplayList = [];
      this.$nextTick(() => {
        this.loadMoreData();
      });
    },
    loadMoreData() {
      if (this.isLoading) return;
      this.isLoading = true;
      // æ¨¡æ‹Ÿå¼‚步加载,实际可能是直接切片本地数据
      setTimeout(() => {
        console.log(this.infotitlelist, "this.infotitlelist");
        const nextChunk = this.infotitlelist.slice(
          this.loadIndex,
          this.loadIndex + this.pageSize
        );
        this.currentDisplayList = this.currentDisplayList.concat(nextChunk);
        this.loadIndex += this.pageSize;
        this.isLoading = false;
      }, 200);
    },
    handleScroll(event) {
      const scrollContainer = event.target;
      // åˆ¤æ–­æ˜¯å¦æ»šåŠ¨åˆ°åº•éƒ¨
      const isAtBottom =
        scrollContainer.scrollTop + scrollContainer.clientHeight >=
        scrollContainer.scrollHeight - 10;
      if (
        isAtBottom &&
        !this.isLoading &&
        this.loadIndex < this.infotitlelist.length
      ) {
        this.loadMoreData();
      }
    },
    SeedetailsgGo(row) {
      this.SeedetailsVisible = false;
      let type = "";
      if (row.preachformson && row.preachformson.includes("3")) {
        type = 1;
      }
      setTimeout(() => {
        this.$router.push({
          path: "/followvisit/record/detailpage/",
          query: {
            taskid: row.taskid,
            patid: row.patid,
            id: row.id,
            Voicetype: type,
            // visitCount: this.topqueryParams.visitCount,
          },
        });
      }, 300);
    },
    // æ·»åŠ /修改标签
    Maintenancetag() {
      if (this.lstamendtag) {
        toamendtag(this.addDateRange(this.tagform)).then((response) => {
          console.log(response);
          this.getList();
        });
      } else {
        addapitag(this.addDateRange(this.tagform)).then((response) => {
          console.log(response);
          this.getList();
        });
      }
      this.tagform = {
        isupload: "",
        tagname: "",
        tagcategoryid: "",
        tagdescription: "",
        tagid: "",
      };
    },
    routerErr(row) {
      console.log(row, "跳转异常");
      this.$router.push({
        path: "/followvisit/discharge",
        query: {
          errtype: 1,
          leavehospitaldistrictcode: row.leavehospitaldistrictcode,
        },
      });
    },
    // è¡¨å•重置
    reset() {
      this.form = {
        userId: undefined,
        deptId: undefined,
        userName: undefined,
        nickName: undefined,
        password: undefined,
        phonenumber: undefined,
        email: undefined,
        sex: undefined,
        status: "0",
        remark: undefined,
        postIds: [],
        roleIds: [],
      };
      this.resetForm("form");
    },
    // æ ‡ç­¾çŠ¶æ€ä¿®æ”¹
    handleStatusChange(row) {
      console.log(row.isupload);
      let text = row.isupload === "0" ? "启用" : "停用";
      this.$modal
        .confirm('确认要"' + text + '""' + row.tagname + '"标签吗?')
        .then(function () {
          return changetagcategory(row.tagid, row.isupload);
        })
        .then(() => {
          this.$modal.msgSuccess(text + "成功");
        })
        .catch(function () {
          row.isupload = row.isupload === "0" ? "1" : "0";
        });
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ - ä¿®æ”¹ä¸ºæœç´¢å½“前激活的tab数据 */
    handleQuery() {
      this.queryParams.pageNum = 1;
      if (!this.queryParams.dateRange) this.queryParams.dateRange = [];
      if (this.queryParams.statisticaltype == 1) {
        this.queryParams.deptcodes = [];
      } else if (this.queryParams.statisticaltype == 2) {
        this.queryParams.leavehospitaldistrictcodes = [];
      }
      this.queryParams.startTime = this.parseTime(
        this.queryParams.dateRange[0]
      );
      this.queryParams.endTime = this.parseTime(this.queryParams.dateRange[1]);
      // æ ¹æ®å½“前激活的tab加载对应数据
      if (this.activeTab === "first") {
        this.getFirstFollowUpList();
      } else {
        this.getSecondFollowUpList();
      }
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    resetQuery() {
      this.queryParams.dateRange = [];
      this.queryParams.leavehospitaldistrictcodes = [];
      this.handleQuery();
    },
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      this.ids = selection.map((item) => item.tagid);
      this.single = selection.length != 1;
      this.multiple = !selection.length;
    },
    /** åˆ é™¤æŒ‰é’®æ“ä½œ */
    handleDelete(row) {
      console.log(row, "删除弹窗");
      const tagids = row.tagid || this.ids;
      console.log(tagids);
      const tagname = row.tagname;
      this.$modal
        .confirm(
          tagname
            ? '是否确认删除标签名称为"' + tagname + '"的数据项?'
            : "是否确认删除选中的数据项?"
        )
        .then(function () {
          return deletetag(tagids);
        })
        .then(() => {
          this.getList();
          this.$modal.msgSuccess("删除成功");
        })
        .catch(() => { });
    },
    // å¯¼å‡ºæ–¹æ³•
    async exportTable() {
      try {
        // 1. èŽ·å–å¹¶æ ¼å¼åŒ–æ—¥æœŸèŒƒå›´
        let dateRangeString = "";
        let sheetNameSuffix = "";
        if (
          this.queryParams.dateRange &&
          this.queryParams.dateRange.length === 2
        ) {
          const startDateStr = this.queryParams.dateRange[0];
          const endDateStr = this.queryParams.dateRange[1];
          const formatDateForDisplay = (dateTimeStr) => {
            return dateTimeStr.split(" ")[0];
          };
          const startDateFormatted = formatDateForDisplay(startDateStr);
          const endDateFormatted = formatDateForDisplay(endDateStr);
          dateRangeString = `${startDateFormatted}至${endDateFormatted}`;
          sheetNameSuffix = `${startDateFormatted}至${endDateFormatted}`;
        } else {
          const now = new Date();
          const currentMonth = now.getMonth() + 1;
          dateRangeString = `${currentMonth}月`;
          sheetNameSuffix = `${currentMonth}月`;
        }
        // 2. æ ¹æ®å½“前激活的tab确定导出的数据
        const isFirstFollowUp = this.activeTab === "first";
        let excelName, worksheetName, dataToExport;
        if (isFirstFollowUp) {
          excelName = `首次出院随访统计表_${dateRangeString}.xlsx`;
          worksheetName = `首次随访统计_${sheetNameSuffix}`;
          dataToExport = this.firstFollowUpList;
          if (!dataToExport || dataToExport.length === 0) {
            this.$message.warning("暂无首次随访数据可导出");
            return false;
          }
        } else {
          excelName = `再次出院随访统计表_${dateRangeString}.xlsx`;
          worksheetName = `再次随访统计_${sheetNameSuffix}`;
          dataToExport = this.secondFollowUpList;
          if (!dataToExport || dataToExport.length === 0) {
            this.$message.warning("暂无再次随访数据可导出");
            return false;
          }
        }
        // 3. åˆ›å»ºå·¥ä½œç°¿å’Œå·¥ä½œè¡¨
        const workbook = new ExcelJS.Workbook();
        const worksheet = workbook.addWorksheet(worksheetName);
        // 4. æž„建表格
        if (isFirstFollowUp) {
          this.buildFirstFollowUpExportSheet(
            worksheet,
            dataToExport,
            sheetNameSuffix
          );
        } else {
          this.buildSecondFollowUpExportSheet(
            worksheet,
            dataToExport,
            sheetNameSuffix
          );
        }
        // 5. ç”Ÿæˆå¹¶ä¸‹è½½æ–‡ä»¶
        const buffer = await workbook.xlsx.writeBuffer();
        const blob = new Blob([buffer], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        });
        saveAs(blob, excelName);
        this.$message.success("导出成功");
        return true;
      } catch (error) {
        console.error("导出失败:", error);
        this.$message.error(`导出失败: ${error.message}`);
        return false;
      }
    },
    /** æž„建首次随访导出表格 */
    buildFirstFollowUpExportSheet(worksheet, data, sheetNameSuffix) {
      const titleStyle = {
        font: {
          name: "微软雅黑",
          size: 16,
          bold: true,
          color: { argb: "FF000000" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFE6F3FF" },
        },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const headerStyle = {
        font: {
          name: "微软雅黑",
          size: 11,
          bold: true,
          color: { argb: "FF000000" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFF5F7FA" },
        },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const cellStyle = {
        font: { name: "宋体", size: 10, color: { argb: "FF000000" } },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const summaryStyle = {
        font: {
          name: "宋体",
          size: 10,
          bold: true,
          color: { argb: "FF409EFF" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFF5F7FA" },
        },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      // 1. æ·»åŠ æ€»æ ‡é¢˜è¡Œ
      worksheet.mergeCells(1, 1, 1, 16); // åˆå¹¶A1到P1
      const titleCell = worksheet.getCell(1, 1);
      titleCell.value = `首次出院随访统计表_${sheetNameSuffix}`;
      titleCell.style = titleStyle;
      worksheet.getRow(1).height = 35;
      // 2. åˆ›å»ºè¡¨å¤´
      const secondRowHeaders = [
        "", // A2 å±•开列占位
        "出院病区",
        "科室",
        "出院人次",
        "无需随访人次",
        "应随访人次", // B2 to F2
        // é¦–次出院随访子表头
        "需随访",
        "待随访",
        "随访成功",
        "随访失败",
        "随访率",
        "及时率",
        "人工",
        "短信",
        "微信",
      ];
      // æ·»åŠ ç¬¬äºŒè¡Œ
      secondRowHeaders.forEach((header, index) => {
        const cell = worksheet.getCell(3, index + 1);
        cell.value = header;
        cell.style = headerStyle;
      });
      // 3. åˆå¹¶å•元格
      // åˆå¹¶ A2:A3, B2:B3, C2:C3, D2:D3, E2:E3, F2:F3
      for (let i = 1; i <= 6; i++) {
        worksheet.mergeCells(2, i, 3, i);
        const cell = worksheet.getCell(2, i);
        cell.style = headerStyle;
      }
      // è®¾ç½®ç¬¬ä¸€è¡Œåˆå¹¶å•元格的值
      worksheet.getCell(2, 1).value = "";
      worksheet.getCell(2, 2).value = "出院病区";
      worksheet.getCell(2, 3).value = "科室";
      worksheet.getCell(2, 4).value = "出院人次";
      worksheet.getCell(2, 5).value = "无需随访人次";
      worksheet.getCell(2, 6).value = "应随访人次";
      // 4. åˆå¹¶"首次出院随访"标题
      worksheet.mergeCells(2, 7, 2, 15); // G2:O2
      worksheet.getCell(2, 7).value = "首次出院随访";
      worksheet.getCell(2, 7).style = headerStyle;
      // 5. è®¾ç½®è¡Œé«˜
      worksheet.getRow(2).height = 28;
      worksheet.getRow(3).height = 25;
      // 6. æ·»åŠ æ•°æ®è¡Œ
      data.forEach((item, rowIndex) => {
        const dataRow = worksheet.addRow(
          [
            "", // å±•开列
            item.leavehospitaldistrictname || "",
            item.deptname || "",
            item.dischargeCount || 0,
            item.nonFollowUp || 0,
            item.followUpNeeded || 0,
            // é¦–次出院随访数据
            item.needFollowUp || 0,
            item.pendingFollowUp || 0,
            item.followUpSuccess || 0,
            item.followUpFail || 0,
            item.followUpRate || "0%",
            item.rate ? (Number(item.rate) * 100).toFixed(2) + "%" : "0%",
            item.manual || 0,
            item.sms || 0,
            item.weChat || 0,
          ],
          rowIndex + 4
        );
        // åº”用数据行样式
        dataRow.eachCell((cell) => {
          cell.style = cellStyle;
        });
        dataRow.height = 24;
      });
      // 7. æ·»åŠ åˆè®¡è¡Œ
      const summaries = this.getFirstFollowUpSummaries(data);
      const summaryRow = worksheet.addRow(summaries);
      summaryRow.eachCell((cell, colNumber) => {
        cell.style = summaryStyle;
        if (colNumber === 1) {
          cell.value = "合计";
        }
      });
      summaryRow.height = 28;
      // 8. è®¾ç½®åˆ—宽
      worksheet.columns = [
        { width: 8 }, // å±•开列
        { width: 20 }, // å‡ºé™¢ç—…区
        { width: 15 }, // ç§‘室
        { width: 12 }, // å‡ºé™¢äººæ¬¡
        { width: 12 }, // æ— éœ€éšè®¿äººæ¬¡
        { width: 12 }, // åº”随访人次
        // é¦–次出院随访列
        { width: 10 }, // éœ€éšè®¿
        { width: 10 }, // å¾…随访
        { width: 10 }, // éšè®¿æˆåŠŸ
        { width: 10 }, // éšè®¿å¤±è´¥
        { width: 12 }, // éšè®¿çއ
        { width: 12 }, // åŠæ—¶çއ
        { width: 8 }, // äººå·¥
        { width: 8 }, // çŸ­ä¿¡
        { width: 8 }, // å¾®ä¿¡
      ];
    },
    /** é¦–次随访数据合计行计算 */
    getFirstFollowUpSummaries(data) {
      const summaries = [
        "合计",
        "/",
        "/",
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        "0%",
        "0%",
        0,
        0,
        0,
      ];
      data.forEach((item) => {
        // æ•°å€¼å­—段求和
        summaries[3] += Number(item.dischargeCount) || 0;
        summaries[4] += Number(item.nonFollowUp) || 0;
        summaries[5] += Number(item.followUpNeeded) || 0;
        summaries[6] += Number(item.needFollowUp) || 0;
        summaries[7] += Number(item.pendingFollowUp) || 0;
        summaries[8] += Number(item.followUpSuccess) || 0;
        summaries[9] += Number(item.followUpFail) || 0;
        summaries[12] += Number(item.manual) || 0;
        summaries[13] += Number(item.sms) || 0;
        summaries[14] += Number(item.weChat) || 0;
      });
      // è®¡ç®—百分比字段的平均值
      const followUpRateValues = data
        .map((item) => this.extractPercentageValue(item.followUpRate))
        .filter((value) => value !== null);
      const rateValues = data
        .map((item) => this.extractPercentageValue(item.rate))
        .filter((value) => value !== null);
      if (followUpRateValues.length > 0) {
        const avgFollowUpRate =
          followUpRateValues.reduce((sum, val) => sum + val, 0) /
          followUpRateValues.length;
        summaries[10] = (avgFollowUpRate * 100).toFixed(2) + "%";
      }
      if (rateValues.length > 0) {
        const avgRate =
          rateValues.reduce((sum, val) => sum + val, 0) / rateValues.length;
        summaries[11] = (avgRate * 100).toFixed(2) + "%";
      }
      // æ ¼å¼åŒ–æ•°å­—
      summaries[3] = this.formatNumber(summaries[3]);
      summaries[4] = this.formatNumber(summaries[4]);
      summaries[5] = this.formatNumber(summaries[5]);
      summaries[6] = this.formatNumber(summaries[6]);
      summaries[7] = this.formatNumber(summaries[7]);
      summaries[8] = this.formatNumber(summaries[8]);
      summaries[9] = this.formatNumber(summaries[9]);
      summaries[12] = this.formatNumber(summaries[12]);
      summaries[13] = this.formatNumber(summaries[13]);
      summaries[14] = this.formatNumber(summaries[14]);
      return summaries;
    },
    /** æž„建再次随访导出表格 */
    buildSecondFollowUpExportSheet(worksheet, data, sheetNameSuffix) {
      const titleStyle = {
        font: {
          name: "微软雅黑",
          size: 16,
          bold: true,
          color: { argb: "FF000000" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFE6F3FF" },
        },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const headerStyle = {
        font: {
          name: "微软雅黑",
          size: 11,
          bold: true,
          color: { argb: "FF000000" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFF5F7FA" },
        },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const cellStyle = {
        font: { name: "宋体", size: 10, color: { argb: "FF000000" } },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const summaryStyle = {
        font: {
          name: "宋体",
          size: 10,
          bold: true,
          color: { argb: "FF409EFF" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFF5F7FA" },
        },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      // 1. æ·»åŠ æ€»æ ‡é¢˜è¡Œ
      worksheet.mergeCells(1, 1, 1, 15); // åˆå¹¶A1到O1
      const titleCell = worksheet.getCell(1, 1);
      titleCell.value = `再次出院随访统计表_${sheetNameSuffix}`;
      titleCell.style = titleStyle;
      worksheet.getRow(1).height = 35;
      // 2. åˆ›å»ºè¡¨å¤´
      const secondRowHeaders = [
        "", // A2 å±•开列占位
        "出院病区",
        "科室",
        "出院人次",
        "无需随访人次",
        "应随访人次", // B2 to F2
        // å†æ¬¡å‡ºé™¢éšè®¿å­è¡¨å¤´
        "需随访",
        "待随访",
        "随访成功",
        "随访失败",
        "随访率",
        "人工",
        "短信",
        "微信",
      ];
      // æ·»åŠ ç¬¬äºŒè¡Œ
      secondRowHeaders.forEach((header, index) => {
        const cell = worksheet.getCell(3, index + 1);
        cell.value = header;
        cell.style = headerStyle;
      });
      // 3. åˆå¹¶å•元格
      // åˆå¹¶ A2:A3, B2:B3, C2:C3, D2:D3, E2:E3, F2:F3
      for (let i = 1; i <= 6; i++) {
        worksheet.mergeCells(2, i, 3, i);
        const cell = worksheet.getCell(2, i);
        cell.style = headerStyle;
      }
      // è®¾ç½®ç¬¬ä¸€è¡Œåˆå¹¶å•元格的值
      worksheet.getCell(2, 1).value = "";
      worksheet.getCell(2, 2).value = "出院病区";
      worksheet.getCell(2, 3).value = "科室";
      worksheet.getCell(2, 4).value = "出院人次";
      worksheet.getCell(2, 5).value = "无需随访人次";
      worksheet.getCell(2, 6).value = "应随访人次";
      // 4. åˆå¹¶"再次出院随访"标题
      worksheet.mergeCells(2, 7, 2, 14); // G2:N2
      worksheet.getCell(2, 7).value = "再次出院随访";
      worksheet.getCell(2, 7).style = headerStyle;
      // 5. è®¾ç½®è¡Œé«˜
      worksheet.getRow(2).height = 28;
      worksheet.getRow(3).height = 25;
      // 6. æ·»åŠ æ•°æ®è¡Œ
      data.forEach((item, rowIndex) => {
        const dataRow = worksheet.addRow(
          [
            "", // å±•开列
            item.leavehospitaldistrictname || "",
            item.deptname || "",
            item.dischargeCount || 0,
            item.nonFollowUp || 0,
            item.followUpNeeded || 0,
            // å†æ¬¡å‡ºé™¢éšè®¿æ•°æ®
            item.needFollowUpAgain || 0,
            item.pendingFollowUpAgain || 0,
            item.followUpSuccessAgain || 0,
            item.followUpFailAgain || 0,
            item.followUpRateAgain || "0%",
            item.manualAgain || 0,
            item.smsAgain || 0,
            item.weChatAgain || 0,
          ],
          rowIndex + 4
        );
        // åº”用数据行样式
        dataRow.eachCell((cell) => {
          cell.style = cellStyle;
        });
        dataRow.height = 24;
      });
      // 7. æ·»åŠ åˆè®¡è¡Œ
      const summaries = this.getSecondFollowUpSummaries(data);
      const summaryRow = worksheet.addRow(summaries);
      summaryRow.eachCell((cell, colNumber) => {
        cell.style = summaryStyle;
        if (colNumber === 1) {
          cell.value = "合计";
        }
      });
      summaryRow.height = 28;
      // 8. è®¾ç½®åˆ—宽
      worksheet.columns = [
        { width: 8 }, // å±•开列
        { width: 20 }, // å‡ºé™¢ç—…区
        { width: 15 }, // ç§‘室
        { width: 12 }, // å‡ºé™¢äººæ¬¡
        { width: 12 }, // æ— éœ€éšè®¿äººæ¬¡
        { width: 12 }, // åº”随访人次
        // å†æ¬¡å‡ºé™¢éšè®¿åˆ—
        { width: 10 }, // éœ€éšè®¿
        { width: 10 }, // å¾…随访
        { width: 10 }, // éšè®¿æˆåŠŸ
        { width: 10 }, // éšè®¿å¤±è´¥
        { width: 12 }, // éšè®¿çއ
        { width: 8 }, // äººå·¥
        { width: 8 }, // çŸ­ä¿¡
        { width: 8 }, // å¾®ä¿¡
      ];
    },
    /** å†æ¬¡éšè®¿æ•°æ®åˆè®¡è¡Œè®¡ç®— */
    getSecondFollowUpSummaries(data) {
      const summaries = ["合计", "/", "/", 0, 0, 0, 0, 0, 0, 0, "0%", 0, 0, 0];
      data.forEach((item) => {
        // æ•°å€¼å­—段求和
        summaries[3] += Number(item.dischargeCount) || 0;
        summaries[4] += Number(item.nonFollowUp) || 0;
        summaries[5] += Number(item.followUpNeeded) || 0;
        summaries[6] += Number(item.needFollowUpAgain) || 0;
        summaries[7] += Number(item.pendingFollowUpAgain) || 0;
        summaries[8] += Number(item.followUpSuccessAgain) || 0;
        summaries[9] += Number(item.followUpFailAgain) || 0;
        summaries[11] += Number(item.manualAgain) || 0;
        summaries[12] += Number(item.smsAgain) || 0;
        summaries[13] += Number(item.weChatAgain) || 0;
      });
      // è®¡ç®—随访率百分比字段的平均值
      const followUpRateAgainValues = data
        .map((item) => this.extractPercentageValue(item.followUpRateAgain))
        .filter((value) => value !== null);
      if (followUpRateAgainValues.length > 0) {
        const avgFollowUpRateAgain =
          followUpRateAgainValues.reduce((sum, val) => sum + val, 0) /
          followUpRateAgainValues.length;
        summaries[10] = (avgFollowUpRateAgain * 100).toFixed(2) + "%";
      }
      // æ ¼å¼åŒ–æ•°å­—
      summaries[3] = this.formatNumber(summaries[3]);
      summaries[4] = this.formatNumber(summaries[4]);
      summaries[5] = this.formatNumber(summaries[5]);
      summaries[6] = this.formatNumber(summaries[6]);
      summaries[7] = this.formatNumber(summaries[7]);
      summaries[8] = this.formatNumber(summaries[8]);
      summaries[9] = this.formatNumber(summaries[9]);
      summaries[11] = this.formatNumber(summaries[11]);
      summaries[12] = this.formatNumber(summaries[12]);
      summaries[13] = this.formatNumber(summaries[13]);
      return summaries;
    },
    /** å†æ¬¡éšè®¿è¡¨æ ¼çš„合计行计算方法 */
    getSummariesSecond(param) {
      const { columns, data } = param;
      const sums = [];
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "合计";
          return;
        }
        if (index === 1 || index === 2) {
          sums[index] = "/";
          return;
        }
        if (column.property === "followUpRateAgain") {
          const percentageValues = data
            .map((item) => {
              const value = item[column.property];
              if (!value || value === "-" || value === "0%") return null;
              if (typeof value === "string" && value.includes("%")) {
                const numValue = parseFloat(value.replace("%", "")) / 100;
                return isNaN(numValue) ? null : numValue;
              } else {
                const numValue = parseFloat(value);
                return isNaN(numValue) ? null : numValue;
              }
            })
            .filter((value) => value !== null && value !== 0);
          if (percentageValues.length > 0) {
            const average =
              percentageValues.reduce((sum, value) => sum + value, 0) /
              percentageValues.length;
            sums[index] = (average * 100).toFixed(2) + "%";
          } else {
            sums[index] = "0.00%";
          }
        } else {
          const values = data.map((item) => {
            const value = item[column.property];
            if (value === "-" || value === "" || value === null) return 0;
            return Number(value) || 0;
          });
          if (!values.every((value) => isNaN(value))) {
            sums[index] = values.reduce((prev, curr) => prev + curr, 0);
            sums[index] = this.formatNumber(sums[index]);
          } else {
            sums[index] = "-";
          }
        }
      });
      return sums;
    },
    /** å†æ¬¡éšè®¿å†…部表格合计行计算方法 */
    getInnerSummariesSecond(param) {
      const { columns, data } = param;
      const sums = [];
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "小计";
          return;
        }
        if (column.property === "drname" || column.property === "deptname") {
          sums[index] = "-";
          return;
        }
        if (column.property === "followUpRateAgain") {
          const percentageValues = data
            .map((item) => {
              const value = item[column.property];
              if (!value || value === "-" || value === "0%") return null;
              if (typeof value === "string" && value.includes("%")) {
                const numValue = parseFloat(value.replace("%", "")) / 100;
                return isNaN(numValue) ? null : numValue;
              } else {
                const numValue = parseFloat(value);
                return isNaN(numValue) ? null : numValue;
              }
            })
            .filter((value) => value !== null && value !== 0);
          if (percentageValues.length > 0) {
            const average =
              percentageValues.reduce((sum, value) => sum + value, 0) /
              percentageValues.length;
            sums[index] = (average * 100).toFixed(2) + "%";
          } else {
            sums[index] = "0.00%";
          }
        } else {
          const values = data.map((item) => {
            const value = item[column.property];
            if (value === "-" || value === "" || value === null) return 0;
            return Number(value) || 0;
          });
          if (!values.every((value) => isNaN(value))) {
            sums[index] = values.reduce((prev, curr) => prev + curr, 0);
            sums[index] = this.formatNumber(sums[index]);
          } else {
            sums[index] = "-";
          }
        }
      });
      return sums;
    },
    /** å†æ¬¡éšè®¿è¡¨æ ¼çš„行点击展开 */
    handleRowClickSecond(row) {
      if (this.expandsSecond.includes(this.getRowKey(row))) {
        this.expandsSecond = [];
        return;
      }
      const params = {
        ...this.queryParams,
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.allDeptCodes
          : this.queryParams.deptcodes,
        leavehospitaldistrictcodes: [row.leavehospitaldistrictcode],
        drcode: "1",
        visitCount: 2, // è®¾ç½®ä¸ºå†æ¬¡éšè®¿
      };
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      if (!row.doctorStats) {
        this.loadingSecond = true;
        getSfStatistics(params).then((res) => {
          this.$set(row, "doctorStats", res.data);
          this.expandsSecond = [this.getRowKey(row)];
          this.loadingSecond = false;
        });
      } else {
        this.expandsSecond = [this.getRowKey(row)];
      }
    },
    /** å†æ¬¡éšè®¿è¡¨æ ¼çš„多选框选中数据 */
    handleSelectionChangeSecond(selection) {
      this.idsSecond = selection.map((item) => item.tagid);
      this.single = selection.length != 1;
      this.multiple = !selection.length;
    },
    // æ˜¾ç¤ºå›¾è¡¨å¼¹çª—
    showChartDialog() {
      this.chartDialogVisible = true;
      this.$nextTick(() => {
        this.initPieChart();
        this.initBarLineChart();
      });
    },
    // åœ¨methods中修改统计方法
    showChartDialog() {
      this.chartDialogVisible = true;
      this.$nextTick(() => {
        console.log(this.userList, "this.userList");
        this.initCharts();
      });
    },
    // æ–°å¢žåˆå§‹åŒ–图表方法
    initCharts() {
      this.initPieChart();
      this.initBarLineChart();
    },
    // åˆå§‹åŒ–饼图
    initPieChart() {
      const echarts = require("echarts");
      const pieDom = document.getElementById("pieChart");
      if (!pieDom) return;
      if (this.pieChart) {
        this.pieChart.dispose();
      }
      this.pieChart = echarts.init(pieDom);
      // è®¡ç®—饼图数据
      const followUpData = {
        pending: 0,
        success: 0,
        fail: 0,
      };
      this.userList.forEach((item) => {
        followUpData.pending += item.pendingFollowUp || 0;
        followUpData.success += item.followUpSuccess || 0;
        followUpData.fail += item.followUpFail || 0;
      });
      // ä½¿ç”¨æ›´ç¾Žè§‚的颜色方案
      const pieOption = {
        title: {
          text: "随访状态分布",
          left: "center",
          textStyle: {
            color: "#333",
            fontSize: 16,
          },
        },
        tooltip: {
          trigger: "item",
          formatter: "{a} <br/>{b}: {c} ({d}%)",
        },
        legend: {
          orient: "vertical",
          left: "left",
          data: ["待随访", "随访成功", "随访失败"],
          textStyle: {
            color: "#666",
          },
        },
        color: ["#FF9D4D", "#36B37E", "#FF5C5C"], // æ–°çš„配色方案
        series: [
          {
            name: "随访状态",
            type: "pie",
            radius: ["40%", "70%"],
            avoidLabelOverlap: true,
            itemStyle: {
              borderRadius: 10,
              borderColor: "#fff",
              borderWidth: 2,
            },
            label: {
              show: true,
              formatter: "{b}: {c} ({d}%)",
              color: "#333",
            },
            emphasis: {
              label: {
                show: true,
                fontSize: "18",
                fontWeight: "bold",
              },
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: "rgba(0, 0, 0, 0.5)",
              },
            },
            data: [
              {
                value: followUpData.pending,
                name: "待随访",
              },
              {
                value: followUpData.success,
                name: "随访成功",
              },
              {
                value: followUpData.fail,
                name: "随访失败",
              },
            ],
          },
        ],
      };
      this.pieChart.setOption(pieOption);
      window.addEventListener("resize", this.resizePieChart);
    },
    // åˆå§‹åŒ–柱状折线图
    initBarLineChart() {
      const echarts = require("echarts");
      const barDom = document.getElementById("barLineChart");
      if (!barDom) return;
      if (this.barLineChart) {
        this.barLineChart.dispose();
      }
      this.barLineChart = echarts.init(barDom);
      // å‡†å¤‡æ•°æ®
      const categories = this.userList.map(
        (item) => item.leavehospitaldistrictname || item.deptname
      );
      const dischargeData = this.userList.map(
        (item) => item.dischargeCount || 0
      );
      const followUpData = this.userList.map(
        (item) => item.followUpNeeded || 0
      );
      // æ–°å¢žä¸¤æ¡æŠ˜çº¿æ•°æ®
      const followUpRateData = this.userList.map((item) => {
        if (!item.followUpRate) return 0;
        // åŽ»æŽ‰ç™¾åˆ†å·å¹¶è½¬ä¸ºæ•°å­—
        const rateStr = String(item.followUpRate).replace("%", "");
        return parseFloat(rateStr) || 0;
      });
      const timelyRateData = this.userList.map((item) =>
        item.rate ? (Number(item.rate) * 100).toFixed(2) : 0
      );
      const option = {
        title: {
          text: "科室/病区随访趋势",
          left: "center",
          textStyle: {
            color: "#333",
            fontSize: 16,
          },
        },
        tooltip: {
          trigger: "axis",
          axisPointer: {
            type: "cross",
            crossStyle: {
              color: "#999",
            },
          },
        },
        legend: {
          data: ["出院人次", "应随访人次", "随访率(%)", "及时率(%)"],
          top: "bottom",
          textStyle: {
            color: "#666",
          },
        },
        color: ["#5470C6", "#91CC75", "#EE6666", "#9A60B4"], // æ–°å¢žç´«è‰²ç”¨äºŽåŠæ—¶çއ
        xAxis: {
          type: "category",
          data: categories,
          axisLabel: {
            interval: 0,
            rotate: 30,
            color: "#666",
          },
          axisLine: {
            lineStyle: {
              color: "#ddd",
            },
          },
        },
        yAxis: [
          {
            type: "value",
            name: "人次",
            min: 0,
            axisLabel: {
              color: "#666",
            },
            axisLine: {
              lineStyle: {
                color: "#ddd",
              },
            },
            splitLine: {
              lineStyle: {
                color: "#f0f0f0",
              },
            },
          },
          {
            type: "value",
            name: "百分比(%)",
            min: 0,
            max: 100,
            axisLabel: {
              color: "#666",
              formatter: "{value}%",
            },
            axisLine: {
              lineStyle: {
                color: "#ddd",
              },
            },
            splitLine: {
              show: false,
            },
          },
        ],
        series: [
          {
            name: "出院人次",
            type: "bar",
            barWidth: "25%",
            data: dischargeData,
            itemStyle: {
              borderRadius: [4, 4, 0, 0],
            },
          },
          {
            name: "应随访人次",
            type: "bar",
            barWidth: "25%",
            data: followUpData,
            itemStyle: {
              borderRadius: [4, 4, 0, 0],
            },
          },
          {
            name: "随访率(%)",
            type: "line",
            yAxisIndex: 1,
            data: followUpRateData,
            symbolSize: 8,
            lineStyle: {
              width: 3,
            },
            markLine: {
              silent: true,
              data: [
                {
                  yAxis: 80,
                  lineStyle: {
                    color: "#EE6666",
                    type: "dashed",
                  },
                  // label: {
                  //   position: 'end',
                  //   formatter: '目标80%'
                  // }
                },
              ],
            },
          },
          {
            name: "及时率(%)",
            type: "line",
            yAxisIndex: 1,
            data: timelyRateData,
            symbolSize: 8,
            lineStyle: {
              width: 3,
              type: "dotted", // ä½¿ç”¨è™šçº¿åŒºåˆ†
            },
            markLine: {
              silent: true,
              data: [
                {
                  yAxis: 90,
                  lineStyle: {
                    color: "#9A60B4",
                    type: "dashed",
                  },
                  // label: {
                  //   position: 'end',
                  //   formatter: '目标90%'
                  // }
                },
              ],
            },
          },
        ],
        grid: {
          top: "15%",
          left: "3%",
          right: "4%",
          bottom: "15%",
          containLabel: true,
        },
      };
      this.barLineChart.setOption(option);
      window.addEventListener("resize", this.resizeBarLineChart);
    },
    // å›¾è¡¨å“åº”式调整方法
    resizePieChart() {
      if (this.pieChart) {
        this.pieChart.resize();
      }
    },
    resizeBarLineChart() {
      if (this.barLineChart) {
        this.barLineChart.resize();
      }
    },
    // åœ¨ç»„件销毁时清理
    beforeDestroy() {
      // ç§»é™¤äº‹ä»¶ç›‘听
      window.removeEventListener("resize", this.resizePieChart);
      window.removeEventListener("resize", this.resizeBarLineChart);
      // é”€æ¯å›¾è¡¨å®žä¾‹
      if (this.pieChart) {
        this.pieChart.dispose();
        this.pieChart = null;
      }
      if (this.barLineChart) {
        this.barLineChart.dispose();
        this.barLineChart = null;
      }
    },
  },
};
</script>
<style lang="scss" scoped>
::v-deep .el-tabs__header {
  margin-bottom: 20px;
}
::v-deep .el-tabs__item {
  font-size: 16px;
  padding: 0 20px;
  height: 40px;
  line-height: 40px;
}
::v-deep .el-tabs__active-bar {
  height: 3px;
}
/* Tab内容区域样式 */
.el-tab-pane {
  .your-table-container {
    margin-top: 10px;
  }
}
.sidecolumn {
  width: 180px;
  min-height: 100vh;
  text-align: center;
  //   display: flex;
  margin-top: 20px;
  margin: 20px;
  padding: 30px;
  background: #edf1f7;
  border: 1px solid #dcdfe6;
  -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12),
    0 0 6px 0 rgba(0, 0, 0, 0.04);
  .sidecolumn-top {
    display: flex;
    justify-content: space-between;
    .top-wj {
      font-size: 20px;
    }
    .top-tj {
      font-size: 18px;
      color: rgb(0, 89, 255);
      cursor: pointer;
    }
  }
  .center-ss {
    margin-top: 30px;
    .input-with-select {
      height: 40px !important;
    }
  }
  .bottom-fl {
    margin-top: 30px;
    display: center !important;
  }
}
.qrcode-dialo {
  text-align: center;
  //   display: flex;
  margin: 20px;
  padding: 30px;
  background: #edf1f7;
  border: 1px solid #dcdfe6;
  -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12),
    0 0 6px 0 rgba(0, 0, 0, 0.04);
  .qrcode-text {
    font-size: 20px;
    span {
      margin-left: 20px;
    }
  }
  .qrcode-img {
    width: 300px;
    height: 400px;
  }
}
::v-deep.el-tabs--left,
.el-tabs--right {
  overflow: hidden;
  align-items: center;
  display: flex;
}
::v-deep.el-input--medium .el-input__inner {
  height: 40px !important;
}
::v-deep.el-tabs--right .el-tabs__active-bar.is-right {
  height: 40px;
  width: 5px;
  left: 0;
}
::v-deep.el-tabs--right .el-tabs__item.is-right {
  display: block;
  text-align: left;
  font-size: 20px;
}
// ç¾ŽåŒ–合计行样式
::v-deep .el-table__footer {
  .el-table__cell {
    background-color: #f5f7fa;
    font-weight: 600;
    color: #409eff;
    .cell {
      font-weight: 600;
      color: #409eff;
    }
  }
}
// å†…部表格合计行样式
::v-deep .inner-table .el-table__footer {
  .el-table__cell {
    background-color: #ecf5ff;
    font-weight: 500;
    color: #67c23a;
    .cell {
      font-weight: 500;
      color: #67c23a;
    }
  }
}
// ç™¾åˆ†æ¯”字段特殊样式
.your-table-container ::v-deep .el-table__footer .el-table__cell[data-field="followUpRate"] .cell,
.your-table-container ::v-deep .el-table__footer .el-table__cell[data-field="rate"] .cell,
.your-table-container ::v-deep .el-table__footer .el-table__cell[data-field="followUpRateAgain"] .cell {
  color: #e6a23c !important;
  font-weight: 700 !important;
}
.leftvlue {
  //   display: flex;
  //   flex: 1;
  // width: 80%;
  // margin-top: 20px;
  margin: 20px;
  padding: 30px;
  background: #ffff;
  border: 1px solid #dcdfe6;
  -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12),
    0 0 6px 0 rgba(0, 0, 0, 0.04);
  .mulsz {
    font-size: 20px;
  }
}
/* ä½¿è¡Œæœ‰æ‰‹åž‹æŒ‡é’ˆ */
.el-table__row {
  cursor: pointer;
}
/* å†…层医生表格样式 */
.inner-table {
  // è¡¨å¤´èƒŒæ™¯è‰²
  ::v-deep .el-table__header-wrapper {
    background-color: #f0f7ff !important;
    th {
      background-color: #f0f7ff !important;
    }
  }
  // è¡¨æ ¼è¡ŒèƒŒæ™¯è‰²
  ::v-deep .el-table__body-wrapper {
    tr {
      background-color: #f9fbfe !important;
      &:hover {
        background-color: #e6f1ff !important;
      }
    }
  }
  // è¾¹æ¡†é¢œè‰²
  ::v-deep .el-table--border {
    border-color: #d9e8ff !important;
    td,
    th {
      border-color: #d9e8ff !important;
    }
  }
  // æ–‘马纹效果
  ::v-deep .el-table--striped .el-table__body tr.el-table__row--striped td {
    background-color: #f5f9ff !important;
  }
}
/* å±•开行样式 */
.el-table__expanded-cell {
  padding: 10px 0 !important;
  background: #f8f8f8;
}
.document {
  width: 100px;
  height: 50px;
}
.data-list {
  max-height: 800px;
  overflow-y: auto;
}
.documentf {
  display: flex;
  justify-content: flex-end;
}
.button-text {
  color: rgb(70, 204, 238);
}
.button-textck {
  color: rgb(39, 167, 67);
}
.button-textxg {
  color: rgb(35, 81, 233);
}
.button-textsc {
  color: rgb(235, 23, 23);
}
</style>
src/views/sfstatistics/percentage/index.vue
@@ -1,3679 +1,369 @@
<template>
  <div class="Questionnairemanagement">
    <div class="leftvlue">
      <div class="leftvlue-bg">
        <el-row :gutter="20">
          <!--标签数据-->
          <el-col :span="24" :xs="24">
            <el-form
              :model="queryParams"
              ref="queryForm"
              size="small"
              :inline="true"
              v-show="showSearch"
              label-width="98px"
            >
              <el-form-item label="统计类型" prop="userName">
                <el-select
                  v-model="queryParams.statisticaltype"
                  placeholder="请选择统计类型"
                >
                  <el-option
                    v-for="item in Statisticallist"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  >
                  </el-option>
                </el-select>
                <el-select
                  style="margin-left: 10px"
                  v-if="queryParams.statisticaltype == 1"
                  v-model="queryParams.leavehospitaldistrictcodes"
                  size="medium"
                  multiple
                  filterable
                  placeholder="请选择病区"
                >
                  <el-option
                    v-for="item in flatArrayhospit"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  >
                  </el-option>
                </el-select>
                <el-select
                  v-else-if="queryParams.statisticaltype == 2"
                  v-model="queryParams.deptcodes"
                  size="medium"
                  multiple
                  filterable
                  placeholder="请选择科室"
                >
                  <el-option
                    v-for="item in flatArraydept"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  >
                  </el-option>
                </el-select>
              </el-form-item>
  <div class="follow-up-statistics">
    <!-- æœç´¢è¡¨å•区域 -->
    <div class="search-section">
      <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="98px">
        <el-form-item label="统计类型" prop="userName">
          <el-select v-model="queryParams.statisticaltype" placeholder="请选择统计类型">
            <el-option v-for="item in Statisticallist" :key="item.value" :label="item.label" :value="item.value" />
          </el-select>
          <el-select
            style="margin-left: 10px"
            v-if="queryParams.statisticaltype == 1"
            v-model="queryParams.leavehospitaldistrictcodes"
            size="medium"
            multiple
            filterable
            placeholder="请选择病区"
          >
            <el-option v-for="item in flatArrayhospit" :key="item.value" :label="item.label" :value="item.value" />
          </el-select>
          <el-select
            v-else-if="queryParams.statisticaltype == 2"
            v-model="queryParams.deptcodes"
            size="medium"
            multiple
            filterable
            placeholder="请选择科室"
          >
            <el-option v-for="item in flatArraydept" :key="item.value" :label="item.label" :value="item.value" />
          </el-select>
        </el-form-item>
              <el-form-item label="服务类型" prop="userName">
                <el-select
                  v-model="queryParams.serviceType"
                  multiple
                  placeholder="请选择"
                >
                  <el-option
                    v-for="item in options"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  >
                  </el-option>
                </el-select>
              </el-form-item>
              <el-form-item
                label-width="200"
                label="应随访时间范围"
                prop="userName"
              >
                <el-date-picker
                  v-model="queryParams.dateRange"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  type="daterange"
                  range-separator="至"
                  start-placeholder="开始日期"
                  end-placeholder="结束日期"
                  :default-time="['00:00:00', '23:59:59']"
                >
                </el-date-picker>
              </el-form-item>
        <el-form-item label="服务类型" prop="userName">
          <el-select v-model="queryParams.serviceType" multiple placeholder="请选择">
            <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
          </el-select>
        </el-form-item>
              <el-form-item>
                <el-button
                  type="primary"
                  icon="el-icon-search"
                  size="medium"
                  @click="handleQuery"
                  >搜索</el-button
                >
                <el-button
                  icon="el-icon-refresh"
                  size="medium"
                  @click="resetQuery"
                  >重置</el-button
                >
              </el-form-item>
              <el-button
                type="warning"
                plain
                icon="el-icon-download"
                size="medium"
                @click="exportTable"
                >导出</el-button
              >
              <el-button
                type="primary"
                plain
                icon="el-icon-data-line"
                size="medium"
                @click="showChartDialog"
                >统计趋势图</el-button
              >
            </el-form>
            <!-- æ–°å¢žï¼šTab标签页 -->
            <el-tabs v-model="activeTab" @tab-click="handleTabClick">
              <el-tab-pane label="首次随访" name="first">
                <div class="your-table-container">
                  <el-table
                    ref="exportTable"
                    id="exportTableid"
                    v-loading="loading"
                    :data="firstFollowUpList"
                    :border="true"
                    @selection-change="handleSelectionChange"
                    @expand-change="handleRowClick"
                    :row-key="getRowKey"
                    show-summary
                    :summary-method="getSummaries"
                    :expand-row-keys="expands"
                  >
                    <!-- å±•开行箭头列 -->
                    <el-table-column type="expand">
                      <template slot-scope="props">
                        <el-table
                          :data="props.row.doctorStats"
                          border
                          style="width: 95%; margin: 0 auto"
                          class="inner-table"
                          show-summary
                          :summary-method="getInnerSummaries"
                        >
                          <el-table-column
                            label="医生姓名"
                            prop="drname"
                            align="center"
                          />
                          <el-table-column
                            label="科室"
                            width="120"
                            prop="deptname"
                            align="center"
                          />
                          <el-table-column
                            label="出院人次"
                            prop="dischargeCount"
                            align="center"
                          />
                          <el-table-column
                            label="出院人次"
                            align="center"
                            key="dischargeCount"
                            prop="dischargeCount"
                          >
                          </el-table-column>
                          <el-table-column
                            label="无需随访人次"
                            align="center"
                            width="100"
                            key="nonFollowUp"
                            prop="nonFollowUp"
                          >
                          </el-table-column>
                          <el-table-column
                            label="应随访人次"
                            align="center"
                            width="100"
                            key="followUpNeeded"
                            prop="followUpNeeded"
                          >
                          </el-table-column>
                          <el-table-column align="center" label="首次出院随访">
                            <el-table-column
                              label="需随访"
                              align="center"
                              key="needFollowUp"
                              prop="needFollowUp"
                            >
                            </el-table-column>
                            <el-table-column
                              label="待随访"
                              align="center"
                              key="pendingFollowUp"
                              prop="pendingFollowUp"
                            >
                            </el-table-column>
                            <el-table-column
                              label="随访成功"
                              align="center"
                              key="followUpSuccess"
                              prop="followUpSuccess"
                            >
                            </el-table-column>
                            <el-table-column
                              label="随访失败"
                              align="center"
                              key="followUpFail"
                              prop="followUpFail"
                            >
                            </el-table-column>
                            <el-table-column
                              label="随访率"
                              align="center"
                              width="120"
                              key="followUpRate"
                              prop="followUpRate"
                            >
                            </el-table-column>
                            <el-table-column
                              v-if="orgname != '丽水市中医院'"
                              label="及时率"
                              align="center"
                              width="120"
                              key="rate"
                              prop="rate"
                            >
                              <template slot-scope="scope">
                                <el-button
                                  size="medium"
                                  type="text"
                                  @click="Seedetails(scope.row)"
                                  ><span class="button-zx"
                                    >{{
                                      (Number(scope.row.rate) * 100).toFixed(2)
                                    }}%</span
                                  ></el-button
                                >
                              </template>
                            </el-table-column>
                            <el-table-column
                              label="人工"
                              align="center"
                              key="manual"
                              prop="manual"
                            >
                            </el-table-column>
                            <el-table-column
                              label="短信"
                              align="center"
                              key="sms"
                              prop="sms"
                            >
                            </el-table-column>
                            <el-table-column
                              label="微信"
                              align="center"
                              key="weChat"
                              prop="weChat"
                            >
                            </el-table-column>
                          </el-table-column>
                        </el-table>
                      </template>
                    </el-table-column>
                    <el-table-column
                      label="出院病区"
                      align="center"
                      sortable
                      key="leavehospitaldistrictname"
                      prop="leavehospitaldistrictname"
                      width="150"
                      :show-overflow-tooltip="true"
                      :sort-method="sortChineseNumber"
                    />
                    <el-table-column
                      label="科室"
                      align="center"
                      key="deptname"
                      prop="deptname"
                      :show-overflow-tooltip="true"
                    />
                    <el-table-column
                      label="出院人次"
                      align="center"
                      key="dischargeCount"
                      prop="dischargeCount"
                    >
                    </el-table-column>
                    <el-table-column
                      label="无需随访人次"
                      align="center"
                      width="100"
                      key="nonFollowUp"
                      prop="nonFollowUp"
                    >
                    </el-table-column>
                    <el-table-column
                      label="应随访人次"
                      align="center"
                      width="100"
                      key="followUpNeeded"
                      prop="followUpNeeded"
                    >
                    </el-table-column>
                    <el-table-column align="center" label="首次出院随访">
                      <el-table-column
                        label="需随访"
                        align="center"
                        key="needFollowUp"
                        prop="needFollowUp"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.needFollowUpInfo,
                                scope.row.leavehospitaldistrictname +
                                  '需随访列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.needFollowUp
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="待随访"
                        align="center"
                        key="pendingFollowUp"
                        prop="pendingFollowUp"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.pendingFollowUpInfo,
                                scope.row.leavehospitaldistrictname +
                                  '待随访列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.pendingFollowUp
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="随访成功"
                        align="center"
                        key="followUpSuccess"
                        prop="followUpSuccess"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.followUpSuccessInfo,
                                scope.row.leavehospitaldistrictname +
                                  '随访成功列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.followUpSuccess
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="随访失败"
                        align="center"
                        key="followUpFail"
                        prop="followUpFail"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.followUpFailInfo,
                                scope.row.leavehospitaldistrictname +
                                  '随访失败列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.followUpFail
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="随访率"
                        align="center"
                        width="120"
                        key="followUpRate"
                        prop="followUpRate"
                      >
                      </el-table-column>
                      <el-table-column
                        v-if="orgname != '丽水市中医院'"
                        label="及时率"
                        align="center"
                        width="120"
                        key="rate"
                        prop="rate"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="Seedetails(scope.row)"
                            ><span class="button-zx"
                              >{{
                                (Number(scope.row.rate) * 100).toFixed(2)
                              }}%</span
                            ></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="人工"
                        align="center"
                        key="manual"
                        prop="manual"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.manualInfo,
                                scope.row.leavehospitaldistrictname +
                                  '人工随访列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.manual
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="短信"
                        align="center"
                        key="sms"
                        prop="sms"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.smsInfo,
                                scope.row.leavehospitaldistrictname +
                                  '短信随访列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.sms
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="微信"
                        align="center"
                        key="weChat"
                        prop="weChat"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.weChatInfo,
                                scope.row.leavehospitaldistrictname +
                                  '微信随访列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.weChat
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                    </el-table-column>
                    <!-- éšè®¿æƒ…况列(仅丽水市中医院显示) -->
                    <el-table-column
                      v-if="orgname == '丽水市中医院'"
                      align="center"
                      label="随访情况"
                    >
                      <el-table-column
                        label="正常语音"
                        align="center"
                        width="100"
                        key="taskSituation1"
                        prop="taskSituation1"
                      >
                      </el-table-column
                      ><el-table-column
                        label="患者拒接或拒访"
                        align="center"
                        width="100"
                        key="taskSituation2"
                        prop="taskSituation2"
                      >
                      </el-table-column
                      ><el-table-column
                        label="面访或者接诊"
                        align="center"
                        width="100"
                        key="taskSituation3"
                        prop="taskSituation3"
                      >
                      </el-table-column
                      ><el-table-column
                        label="微信随访"
                        align="center"
                        width="100"
                        key="taskSituation4"
                        prop="taskSituation4"
                      >
                      </el-table-column
                      ><el-table-column
                        label="随访电话不正确"
                        align="center"
                        width="100"
                        key="taskSituation5"
                        prop="taskSituation5"
                      >
                      </el-table-column
                      ><el-table-column
                        label="其他情况不宜随访"
                        align="center"
                        width="100"
                        key="taskSituation6"
                        prop="taskSituation6"
                      >
                      </el-table-column>
                    </el-table-column>
                  </el-table>
                </div>
              </el-tab-pane>
              <el-tab-pane label="再次随访" name="second">
                <div class="your-table-container">
                  <el-table
                    ref="exportTableSecond"
                    id="exportTableidSecond"
                    v-loading="loadingSecond"
                    :data="secondFollowUpList"
                    :border="true"
                    @selection-change="handleSelectionChangeSecond"
                    @expand-change="handleRowClickSecond"
                    :row-key="getRowKey"
                    show-summary
                    :summary-method="getSummariesSecond"
                    :expand-row-keys="expandsSecond"
                  >
                    <!-- å±•开行箭头列 -->
                    <el-table-column type="expand">
                      <template slot-scope="props">
                        <el-table
                          :data="props.row.doctorStats"
                          border
                          style="width: 95%; margin: 0 auto"
                          class="inner-table"
                          show-summary
                          :summary-method="getInnerSummariesSecond"
                        >
                          <el-table-column
                            label="医生姓名"
                            prop="drname"
                            align="center"
                          />
                          <el-table-column
                            label="科室"
                            width="120"
                            prop="deptname"
                            align="center"
                          />
                          <el-table-column
                            label="出院人次"
                            prop="dischargeCount"
                            align="center"
                          />
                          <el-table-column
                            label="出院人次"
                            align="center"
                            key="dischargeCount"
                            prop="dischargeCount"
                          >
                          </el-table-column>
                          <el-table-column
                            label="无需随访人次"
                            align="center"
                            width="100"
                            key="nonFollowUp"
                            prop="nonFollowUp"
                          >
                          </el-table-column>
                          <el-table-column
                            label="应随访人次"
                            align="center"
                            width="100"
                            key="followUpNeeded"
                            prop="followUpNeeded"
                          >
                          </el-table-column>
                          <el-table-column align="center" label="再次出院随访">
                            <el-table-column
                              label="需随访"
                              align="center"
                              key="needFollowUpAgain"
                              prop="needFollowUpAgain"
                            >
                            </el-table-column>
                            <el-table-column
                              label="待随访"
                              align="center"
                              key="pendingFollowUpAgain"
                              prop="pendingFollowUpAgain"
                            >
                            </el-table-column>
                            <el-table-column
                              label="随访成功"
                              align="center"
                              key="followUpSuccessAgain"
                              prop="followUpSuccessAgain"
                            >
                            </el-table-column>
                            <el-table-column
                              label="随访失败"
                              align="center"
                              key="followUpFailAgain"
                              prop="followUpFailAgain"
                            >
                            </el-table-column>
                            <el-table-column
                              label="随访率"
                              align="center"
                              width="120"
                              key="followUpRateAgain"
                              prop="followUpRateAgain"
                            >
                            </el-table-column>
                            <el-table-column
                              label="人工"
                              align="center"
                              key="manualAgain"
                              prop="manualAgain"
                            >
                            </el-table-column>
                            <el-table-column
                              label="短信"
                              align="center"
                              key="smsAgain"
                              prop="smsAgain"
                            >
                            </el-table-column>
                            <el-table-column
                              label="微信"
                              align="center"
                              key="weChatAgain"
                              prop="weChatAgain"
                            >
                            </el-table-column>
                          </el-table-column>
                        </el-table>
                      </template>
                    </el-table-column>
                    <el-table-column
                      label="出院病区"
                      align="center"
                      sortable
                      key="leavehospitaldistrictname"
                      prop="leavehospitaldistrictname"
                      width="150"
                      :show-overflow-tooltip="true"
                      :sort-method="sortChineseNumber"
                    />
                    <el-table-column
                      label="科室"
                      align="center"
                      key="deptname"
                      prop="deptname"
                      :show-overflow-tooltip="true"
                    />
                    <el-table-column
                      label="出院人次"
                      align="center"
                      key="dischargeCount"
                      prop="dischargeCount"
                    >
                    </el-table-column>
                    <el-table-column
                      label="无需随访人次"
                      align="center"
                      width="100"
                      key="nonFollowUp"
                      prop="nonFollowUp"
                    >
                    </el-table-column>
                    <el-table-column
                      label="应随访人次"
                      align="center"
                      width="100"
                      key="followUpNeeded"
                      prop="followUpNeeded"
                    >
                    </el-table-column>
                    <el-table-column align="center" label="再次出院随访">
                      <el-table-column
                        label="需随访"
                        align="center"
                        key="needFollowUpAgain"
                        prop="needFollowUpAgain"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.needFollowUpAgainInfo,
                                scope.row.leavehospitaldistrictname +
                                  '再次随访需随访列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.needFollowUpAgain
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="待随访"
                        align="center"
                        key="pendingFollowUpAgain"
                        prop="pendingFollowUpAgain"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.pendingFollowUpAgainInfo,
                                scope.row.leavehospitaldistrictname +
                                  '再次随访待随访列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.pendingFollowUpAgain
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="随访成功"
                        align="center"
                        key="followUpSuccessAgain"
                        prop="followUpSuccessAgain"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.followUpSuccessAgainInfo,
                                scope.row.leavehospitaldistrictname +
                                  '再次随访随访成功列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.followUpSuccessAgain
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="随访失败"
                        align="center"
                        key="followUpFailAgain"
                        prop="followUpFailAgain"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.followUpFailAgainInfo,
                                scope.row.leavehospitaldistrictname +
                                  '再次随访随访失败列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.followUpFailAgain
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="随访率"
                        align="center"
                        width="120"
                        key="followUpRateAgain"
                        prop="followUpRateAgain"
                      >
                      </el-table-column>
                      <el-table-column
                        label="人工"
                        align="center"
                        key="manualAgain"
                        prop="manualAgain"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.manualAgainInfo,
                                scope.row.leavehospitaldistrictname +
                                  '再次随访人工随访列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.manualAgain
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="短信"
                        align="center"
                        key="smsAgain"
                        prop="smsAgain"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.smsAgainInfo,
                                scope.row.leavehospitaldistrictname +
                                  '再次随访短信随访列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.smsAgain
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                      <el-table-column
                        label="微信"
                        align="center"
                        key="weChatAgain"
                        prop="weChatAgain"
                      >
                        <template slot-scope="scope">
                          <el-button
                            size="medium"
                            type="text"
                            @click="
                              viewDetails(
                                scope.row.weChatAgainInfo,
                                scope.row.leavehospitaldistrictname +
                                  '再次随访微信随访列表'
                              )
                            "
                            ><span class="button-zx">{{
                              scope.row.weChatAgain
                            }}</span></el-button
                          >
                        </template>
                      </el-table-column>
                    </el-table-column>
                  </el-table>
                </div>
              </el-tab-pane>
            </el-tabs>
            <!-- åŽŸæ¥çš„åˆ†é¡µç»„ä»¶ä¿ç•™ï¼ˆå¦‚æžœéœ€è¦ï¼‰ -->
            <!-- <pagination
              v-show="total > 0"
              :total="total"
              :page.sync="queryParams.pageNum"
              :limit.sync="queryParams.pageSize"
              @pagination="getList"
            /> -->
          </el-col>
        </el-row>
      </div>
    </div>
    <!-- ç»Ÿè®¡è¶‹åŠ¿å›¾å¼¹çª— -->
    <el-dialog
      title="随访统计趋势图"
      :visible.sync="chartDialogVisible"
      width="80%"
      :close-on-click-modal="false"
    >
      <div class="chart-container">
        <el-row :gutter="20">
          <el-col :span="12">
            <div class="chart-title">随访状态分布</div>
            <div id="pieChart" style="width: 100%; height: 400px"></div>
          </el-col>
          <el-col :span="12">
            <div class="chart-title">随访趋势分析</div>
            <div id="barLineChart" style="width: 100%; height: 400px"></div>
          </el-col>
        </el-row>
      </div>
    </el-dialog>
    <el-dialog
      title="未及时随访患者服务"
      :visible.sync="SeedetailsVisible"
      v-loading="Seedloading"
      width="70%"
      :close-on-click-modal="false"
    >
      <div class="examine-jic">
        <div class="jic-value">
          <el-row :gutter="20">
            <!--用户数据-->
            <el-form
              :model="patientqueryParams"
              ref="queryForm"
              size="small"
              :inline="true"
              label-width="98px"
            >
              <el-form-item label="患者:">
                <el-input
                  v-model="patientqueryParams.name"
                  @keyup.enter.native="handleQuery"
                ></el-input>
              </el-form-item>
              <el-form-item label="患者诊断:">
                <el-input
                  v-model="patientqueryParams.leavediagname"
                  @keyup.enter.native="handleQuery"
                ></el-input>
              </el-form-item>
              <el-form-item>
                <el-button
                  type="primary"
                  icon="el-icon-search"
                  size="medium"
                  @click="handleQuery"
                  >搜索</el-button
                >
                <el-button
                  icon="el-icon-refresh"
                  size="medium"
                  @click="resetQuery"
                  >取消创建</el-button
                >
              </el-form-item>
            </el-form>
            <!-- é€‰æ‹©æ‚£è€…列表 -->
            <el-table :data="logsheetlist" style="width: 100%">
              <el-table-column
                prop="sendname"
                align="center"
                label="姓名"
                width="100"
              >
              </el-table-column>
              <el-table-column
                prop="taskName"
                align="center"
                width="200"
                show-overflow-tooltip
                label="任务名称"
              >
              </el-table-column>
              <el-table-column
                prop="sendstate"
                align="center"
                width="200"
                label="任务状态"
              >
                <template slot-scope="scope">
                  <div v-if="scope.row.sendstate == 1">
                    <el-tag type="primary" :disable-transitions="false"
                      >表单已领取</el-tag
                    >
                  </div>
                  <div v-if="scope.row.sendstate == 2">
                    <el-tag type="primary" :disable-transitions="false"
                      >待随访</el-tag
                    >
                  </div>
                  <div v-if="scope.row.sendstate == 3">
                    <el-tag type="success" :disable-transitions="false"
                      >表单已发送</el-tag
                    >
                  </div>
                  <div v-if="scope.row.sendstate == 4">
                    <el-tag type="info" :disable-transitions="false"
                      >不执行</el-tag
                    >
                  </div>
                  <div v-if="scope.row.sendstate == 5">
                    <el-tag type="danger" :disable-transitions="false"
                      >发送失败</el-tag
                    >
                  </div>
                  <div v-if="scope.row.sendstate == 6">
                    <el-tag type="success" :disable-transitions="false"
                      >已完成</el-tag
                    >
                  </div>
                </template>
              </el-table-column>
              <el-table-column
                prop="visitTime"
                align="center"
                label="应随访时间"
                width="200"
                show-overflow-tooltip
              >
              </el-table-column>
              <el-table-column
                prop="finishtime"
                align="center"
                label="随访完成时间"
                width="200"
                show-overflow-tooltip
              >
              </el-table-column>
              <el-table-column
                label="出院日期"
                width="200"
                align="center"
                key="endtime"
                prop="endtime"
              >
                <template slot-scope="scope">
                  <span>{{ formatTime(scope.row.endtime) }}</span>
                </template></el-table-column
              >
              <el-table-column
                label="责任护士"
                width="120"
                align="center"
                key="nurseName"
                prop="nurseName"
              />
              <el-table-column
                label="主治医生"
                width="120"
                align="center"
                key="drname"
                prop="drname"
              />
              <el-table-column
                label="结果状态"
                align="center"
                key="excep"
                prop="excep"
                width="120"
              >
                <template slot-scope="scope">
                  <dict-tag
                    :options="dict.type.sys_yujing"
                    :value="scope.row.excep"
                  />
                </template>
              </el-table-column>
              <el-table-column
                label="处理意见"
                align="center"
                key="suggest"
                prop="suggest"
                width="120"
              >
                <template slot-scope="scope">
                  <dict-tag
                    :options="dict.type.sys_suggest"
                    :value="scope.row.suggest"
                  />
                </template>
              </el-table-column>
              <el-table-column
                prop="templatename"
                align="center"
                label="服务模板"
                width="200"
                show-overflow-tooltip
              >
              </el-table-column>
              <el-table-column
                prop="remark"
                align="center"
                label="服务记录"
                width="200"
                show-overflow-tooltip
              >
              </el-table-column>
              <el-table-column
                prop="bankcardno"
                align="center"
                label="呼叫状态"
                width="210"
              >
              </el-table-column>
              <el-table-column
                label="操作"
                fixed="right"
                align="center"
                width="200"
                class-name="small-padding fixed-width"
              >
                <template slot-scope="scope">
                  <el-button
                    size="medium"
                    type="text"
                    @click="SeedetailsgGo(scope.row)"
                    ><span class="button-zx"
                      ><i class="el-icon-s-order"></i>查看</span
                    ></el-button
                  >
                </template>
              </el-table-column>
            </el-table>
          </el-row>
          <pagination
            v-show="patienttotal > 0 && this.patientqueryParams.allhosp != 6"
            :total="patienttotal"
            :page.sync="patientqueryParams.pn"
            :limit.sync="patientqueryParams.ps"
            @pagination="Seedetailstion"
        <el-form-item label-width="200" label="应随访时间范围" prop="userName">
          <el-date-picker
            v-model="queryParams.dateRange"
            value-format="yyyy-MM-dd HH:mm:ss"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            :default-time="['00:00:00', '23:59:59']"
          />
        </div>
      </div>
    </el-dialog>
    <!-- å„类详情 -->
    <el-dialog
        </el-form-item>
        <el-form-item>
          <el-button type="primary" icon="el-icon-search" size="medium" @click="handleQuery">搜索</el-button>
          <el-button icon="el-icon-refresh" size="medium" @click="resetQuery">重置</el-button>
        </el-form-item>
        <el-button type="warning" plain icon="el-icon-download" size="medium" @click="handleExport">导出</el-button>
        <el-button type="primary" plain icon="el-icon-data-line" size="medium" @click="showChartDialog">统计趋势图</el-button>
      </el-form>
    </div>
    <!-- Tab切换区域 -->
    <div class="tab-section">
      <el-tabs v-model="activeTab" @tab-click="handleTabClick">
        <el-tab-pane label="首次随访" name="first">
          <FirstFollowUp
            ref="firstFollowUp"
            :query-params="queryParams"
            :flat-array-hospit="flatArrayhospit"
            :flat-array-dept="flatArraydept"
            :options="options"
            :orgname="orgname"
            @view-details="viewDetails"
            @see-details="Seedetails"
          />
        </el-tab-pane>
        <el-tab-pane label="再次随访" name="second">
          <SecondFollowUp
            ref="secondFollowUp"
            :query-params="queryParams"
            :flat-array-hospit="flatArrayhospit"
            :flat-array-dept="flatArraydept"
            :options="options"
            :orgname="orgname"
            @view-details="viewDetails"
          />
        </el-tab-pane>
        <el-tab-pane label="延续护理统计" name="continued" v-if="orgname == '省立同德翠苑院区'">
          <ContinuedCare
            ref="continuedCare"
            :query-params="queryParams"
            :flat-array-hospit="flatArrayhospit"
            :flat-array-dept="flatArraydept"
            :options="options"
            :orgname="orgname"
            @view-details="viewDetails"
          />
        </el-tab-pane>
      </el-tabs>
    </div>
    <!-- å¼¹çª—区域 -->
    <ChartDialog
      :visible="chartDialogVisible"
      :data="chartData"
      :active-tab="activeTab"
      @close="chartDialogVisible = false"
    />
    <DetailDialog
      :visible="infotitleVisible"
      :title="infotitle"
      :visible.sync="infotitleVisible"
      v-loading="infotitloading"
      width="70%"
      :close-on-click-modal="false"
    >
      <div style="margin-bottom: 16px; display: flex; align-items: center">
        <span style="margin-right: 10px; font-weight: bold">患者姓名查询:</span>
        <el-input
          v-model="searchName"
          placeholder="请输入患者姓名进行筛选"
          clearable
          style="width: 300px"
          @input="handleSearch"
          @clear="handleSearch"
        >
        </el-input>
        <span
          style="margin-left: 10px; color: rgb(35, 81, 233); font-size: 16px"
        >
          å…± {{ infotitlelist.length }} æ¡è®°å½•
        </span>
      </div>
      <div class="examine-jic">
        <div class="jic-value">
          <el-row :gutter="20">
            <!-- é€‰æ‹©æ‚£è€…列表 -->
            <div
              class="data-list"
              ref="dataList"
              @scroll="handleScroll"
              v-loading="infotitloading"
            >
              <el-table
                :data="currentDisplayList"
                height="660"
                style="width: 100%"
              >
                <el-table-column
                  prop="sendname"
                  align="center"
                  label="姓名"
                  width="100"
                >
                </el-table-column>
                <el-table-column
                  prop="taskName"
                  align="center"
                  width="200"
                  show-overflow-tooltip
                  label="任务名称"
                >
                </el-table-column>
                <el-table-column
                  prop="sendstate"
                  align="center"
                  width="200"
                  label="任务状态"
                >
                  <template slot-scope="scope">
                    <div v-if="scope.row.sendstate == 1">
                      <el-tag type="primary" :disable-transitions="false"
                        >表单已领取</el-tag
                      >
                    </div>
                    <div v-if="scope.row.sendstate == 2">
                      <el-tag type="primary" :disable-transitions="false"
                        >待随访</el-tag
                      >
                    </div>
                    <div v-if="scope.row.sendstate == 3">
                      <el-tag type="success" :disable-transitions="false"
                        >表单已发送</el-tag
                      >
                    </div>
                    <div v-if="scope.row.sendstate == 4">
                      <el-tag type="info" :disable-transitions="false"
                        >不执行</el-tag
                      >
                    </div>
                    <div v-if="scope.row.sendstate == 5">
                      <el-tag type="danger" :disable-transitions="false"
                        >发送失败</el-tag
                      >
                    </div>
                    <div v-if="scope.row.sendstate == 6">
                      <el-tag type="success" :disable-transitions="false"
                        >已完成</el-tag
                      >
                    </div>
                  </template>
                </el-table-column>
                <el-table-column
                  label="任务执行方式"
                  align="center"
                  key="preachform"
                  prop="preachform"
                  width="160"
                  :show-overflow-tooltip="true"
                >
                  <template slot-scope="scope">
                    <span v-for="item in scope.row.preachform"
                      >{{ item }}、
                    </span>
                  </template>
                </el-table-column>
                <el-table-column
                  prop="visitTime"
                  align="center"
                  label="应随访时间"
                  width="200"
                  show-overflow-tooltip
                >
                </el-table-column>
                <el-table-column
                  prop="finishtime"
                  align="center"
                  label="随访完成时间"
                  width="200"
                  show-overflow-tooltip
                >
                </el-table-column>
                <el-table-column
                  label="出院日期"
                  width="200"
                  align="center"
                  key="endtime"
                  prop="endtime"
                >
                  <template slot-scope="scope">
                    <span>{{ formatTime(scope.row.endtime) }}</span>
                  </template></el-table-column
                >
                <el-table-column
                  label="责任护士"
                  width="120"
                  align="center"
                  key="nurseName"
                  prop="nurseName"
                />
                <el-table-column
                  label="主治医生"
                  width="120"
                  align="center"
                  key="drname"
                  prop="drname"
                />
      :data="infotitlelist"
      :search-name="searchName"
      @close="infotitleVisible = false"
      @search="handleSearch"
      @details-go="SeedetailsgGo"
    />
                <el-table-column
                  label="结果状态"
                  align="center"
                  key="excep"
                  prop="excep"
                  width="120"
                >
                  <template slot-scope="scope">
                    <dict-tag
                      :options="dict.type.sys_yujing"
                      :value="scope.row.excep"
                    />
                  </template>
                </el-table-column>
                <el-table-column
                  label="处理意见"
                  align="center"
                  key="suggest"
                  prop="suggest"
                  width="120"
                >
                  <template slot-scope="scope">
                    <dict-tag
                      :options="dict.type.sys_suggest"
                      :value="scope.row.suggest"
                    />
                  </template>
                </el-table-column>
                <el-table-column
                  prop="templatename"
                  align="center"
                  label="服务模板"
                  width="200"
                  show-overflow-tooltip
                >
                </el-table-column>
                <el-table-column
                  prop="remark"
                  align="center"
                  label="服务记录"
                  width="200"
                  show-overflow-tooltip
                >
                </el-table-column>
                <el-table-column
                  prop="bankcardno"
                  align="center"
                  label="呼叫状态"
                  width="210"
                >
                </el-table-column>
                <el-table-column
                  label="操作"
                  fixed="right"
                  align="center"
                  width="200"
                  class-name="small-padding fixed-width"
                >
                  <template slot-scope="scope">
                    <el-button
                      size="medium"
                      type="text"
                      @click="SeedetailsgGo(scope.row)"
                      ><span class="button-zx"
                        ><i class="el-icon-s-order"></i>查看</span
                      ></el-button
                    >
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </el-row>
        </div>
      </div>
    </el-dialog>
    <TimelyRateDialog
      :visible="SeedetailsVisible"
      :loading="Seedloading"
      :data="logsheetlist"
      :total="patienttotal"
      :query-params="patientqueryParams"
      @close="SeedetailsVisible = false"
      @search="Seedetailstion"
      @details-go="SeedetailsgGo"
    />
  </div>
</template>
<script>
import {
  toamendtag,
  addapitag,
  deletetag,
  changetagcategory,
} from "@/api/system/label";
import store from "@/store";
import { getSfStatistics, selectTimelyRate } from "@/api/system/user";
import * as XLSX from "xlsx";
import FileSaver from "file-saver";
import ExcelJS from "exceljs";
import { saveAs } from "file-saver";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
import FirstFollowUp from './components/FirstFollowUp.vue'
import SecondFollowUp from './components/SecondFollowUp.vue'
import ContinuedCare from './components/ContinuedCare.vue'
import ChartDialog from './components/ChartDialog.vue'
import DetailDialog from './components/DetailDialog.vue'
import TimelyRateDialog from './components/TimelyRateDialog.vue'
const shortcuts = [
  {
    text: "今天",
    onClick(picker) {
      picker.$emit("pick", new Date());
    },
  },
  {
    text: "昨天",
    onClick(picker) {
      const date = new Date();
      date.setTime(date.getTime() - 3600 * 1000 * 24);
      picker.$emit("pick", date);
    },
  },
  {
    text: "一周前",
    onClick(picker) {
      const date = new Date();
      date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
      picker.$emit("pick", date);
    },
  },
];
export default {
  name: "Percentage",
  dicts: ["sys_normal_disable", "sys_user_sex"],
  components: { Treeselect },
  name: 'FollowUpStatistics',
  components: {
    FirstFollowUp,
    SecondFollowUp,
    ContinuedCare,
    ChartDialog,
    DetailDialog,
    TimelyRateDialog
  },
  data() {
    return {
      // æ–°å¢žï¼šTab标签页控制
      activeTab: "first", // å½“前激活的tab,first-首次随访,second-再次随访
      // åˆ†ç¦»çš„æ•°æ®åˆ—表
      firstFollowUpList: [], // é¦–次随访数据
      secondFollowUpList: [], // å†æ¬¡éšè®¿æ•°æ®
      // åˆ†ç¦»çš„加载状态
      loading: false, // é¦–次随访表格加载状态
      loadingSecond: false, // å†æ¬¡éšè®¿è¡¨æ ¼åŠ è½½çŠ¶æ€
      // åˆ†ç¦»çš„展开状态
      expands: [], // é¦–次随访表格展开行
      expandsSecond: [], // å†æ¬¡éšè®¿è¡¨æ ¼å±•开行
      // åˆ†ç¦»çš„选择状态
      ids: [], // é¦–次随访选中项
      idsSecond: [], // å†æ¬¡éšè®¿é€‰ä¸­é¡¹
      orgname: "",
      infotitlelist: [],
      currentDisplayList: [],
      loadIndex: 0,
      pageSize: 100,
      isLoading: false,
      Seedloading: false,
      chartDialogVisible: false,
      infotitleVisible: false,
      searchName: "",
      infotitloading: false,
      infotitle: "",
      pieChart: null,
      barLineChart: null,
      single: true,
      multiple: true,
      showSearch: true,
      idds: "",
      total: 0,
      flatArrayhospit: [],
      flatArraydept: [],
      patienttotal: 0,
      logsheetlist: [],
      activeTab: 'first',
      orgname: localStorage.getItem('orgname') || '',
      Statisticallist: [
        {
          label: "病区统计",
          value: 1,
        },
        {
          label: "科室统计",
          value: 2,
        },
        { label: '病区统计', value: 1 },
        { label: '科室统计', value: 2 }
      ],
      patientqueryParams: {
        pn: 1,
        ps: 10,
      },
      amendtag: false,
      lstamendtag: false,
      scavisible: false,
      deleteVisible: false,
      deletefenl: "高血压",
      tagform: {
        isupload: "",
        tagname: "",
        tagcategoryid: "",
        tagdescription: "",
      },
      classifyform: {
        categoryname: "",
      },
      title: "",
      open: false,
      dateRange: [],
      postOptions: [],
      roleOptions: [],
      allDeptCodes: [],
      allWardCodes: [],
      checkboxlist: [],
      form: {},
      forms: {
        name: "",
      },
      numberlb: 22,
      dialogFormVisible: false,
      lstamendtagVisible: false,
      goQRCodeVisible: false,
      sidecolumnval: "",
      propss: { multiple: true },
      SeedetailsVisible: false,
      options: store.getters.tasktypes,
      pickerOptions: {
        disabledDate(time) {
          return time.getTime() < Date.now() - 3600 * 1000 * 24;
        },
        shortcuts: shortcuts,
      },
      pickerOptionsa: {
        disabledDate(time) {
          return time.getTime() > Date.now();
        },
        shortcuts: shortcuts,
      },
      options: this.$store.getters.tasktypes,
      queryParams: {
        serviceType: [2],
        dateRange: [],
        statisticaltype: 1,
        leavehospitaldistrictcodes: ["all"],
        deptcodes: [],
        visitCount: 1, // æ–°å¢žï¼šéšè®¿æ¬¡æ•°å‚数,1-首次,2-再次
        leavehospitaldistrictcodes: ['all'],
        deptcodes: []
      },
      columns: [
        { key: 0, label: `标签编号`, visible: true },
        { key: 1, label: `标签名称`, visible: true },
        { key: 2, label: `标签昵称`, visible: true },
        { key: 3, label: `部门`, visible: true },
        { key: 4, label: `手机号码`, visible: true },
        { key: 5, label: `状态`, visible: true },
        { key: 6, label: `创建时间`, visible: true },
      ],
    };
      flatArrayhospit: [],
      flatArraydept: [],
      allDeptCodes: [],
      allWardCodes: [],
      showSearch: true,
      // å¼¹çª—相关状态
      chartDialogVisible: false,
      chartData: [],
      infotitleVisible: false,
      SeedetailsVisible: false,
      searchName: '',
      infotitle: '',
      infotitlelist: [],
      patienttotal: 0,
      logsheetlist: [],
      Seedloading: false,
      patientqueryParams: {
        pn: 1,
        ps: 10
      }
    }
  },
  watch: {},
  created() {
    this.getDeptTree();
    this.getFirstFollowUpList(); // é»˜è®¤åŠ è½½é¦–æ¬¡éšè®¿æ•°æ®
    this.checkboxlist = store.getters.checkboxlist;
    this.orgname = localStorage.getItem("orgname");
    this.getDeptTree()
  },
  methods: {
    /** æŸ¥è¯¢é¦–次随访列表 */
    async getFirstFollowUpList() {
      this.loading = true;
      this.queryParams.visitCount = 1; // è®¾ç½®éšè®¿æ¬¡æ•°ä¸ºé¦–次
      const params = {
        ...this.queryParams,
        leavehospitaldistrictcodes:
          this.queryParams.leavehospitaldistrictcodes.includes("all")
            ? this.allWardCodes
            : this.queryParams.leavehospitaldistrictcodes,
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.allDeptCodes
          : this.queryParams.deptcodes,
      };
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      try {
        const response = await getSfStatistics(params);
        this.firstFollowUpList = this.customSort(response.data);
        this.total = response.total;
      } catch (error) {
        console.error("获取首次随访数据失败:", error);
        this.$message.error("获取首次随访数据失败");
      } finally {
        this.loading = false;
      }
    },
    /** æŸ¥è¯¢å†æ¬¡éšè®¿åˆ—表 */
    async getSecondFollowUpList() {
      this.loadingSecond = true;
      this.queryParams.visitCount = 2; // è®¾ç½®éšè®¿æ¬¡æ•°ä¸ºå†æ¬¡
      const params = {
        ...this.queryParams,
        leavehospitaldistrictcodes:
          this.queryParams.leavehospitaldistrictcodes.includes("all")
            ? this.allWardCodes
            : this.queryParams.leavehospitaldistrictcodes,
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.allDeptCodes
          : this.queryParams.deptcodes,
      };
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      try {
        const response = await getSfStatistics(params);
        this.secondFollowUpList = this.customSort(response.data);
        this.total = response.total;
      } catch (error) {
        console.error("获取再次随访数据失败:", error);
        this.$message.error("获取再次随访数据失败");
      } finally {
        this.loadingSecond = false;
      }
    },
    /** Tab切换事件 */
    handleTabClick(tab) {
      if (tab.name === "first") {
        if (this.firstFollowUpList.length === 0) {
          this.getFirstFollowUpList();
        }
      } else if (tab.name === "second") {
        if (this.secondFollowUpList.length === 0) {
          this.getSecondFollowUpList();
        }
      }
    },
    sortChineseNumber(aRow, bRow) {
      const a = aRow.leavehospitaldistrictname;
      const b = bRow.leavehospitaldistrictname;
      // ä¸­æ–‡æ•°å­—到阿拉伯数字的映射(扩展到45)
      const chineseNumMap = {
        ä¸€: 1,
        äºŒ: 2,
        ä¸‰: 3,
        å››: 4,
        äº”: 5,
        å…­: 6,
        ä¸ƒ: 7,
        å…«: 8,
        ä¹: 9,
        å: 10,
        åä¸€: 11,
        åäºŒ: 12,
        åä¸‰: 13,
        åå››: 14,
        åäº”: 15,
        åå…­: 16,
        åä¸ƒ: 17,
        åå…«: 18,
        åä¹: 19,
        äºŒå: 20,
        äºŒåä¸€: 21,
        äºŒåäºŒ: 22,
        äºŒåä¸‰: 23,
        äºŒåå››: 24,
        äºŒåäº”: 25,
        äºŒåå…­: 26,
        äºŒåä¸ƒ: 27,
        äºŒåå…«: 28,
        äºŒåä¹: 29,
        ä¸‰å: 30,
        ä¸‰åä¸€: 31,
        ä¸‰åäºŒ: 32,
        ä¸‰åä¸‰: 33,
        ä¸‰åå››: 34,
        ä¸‰åäº”: 35,
        ä¸‰åå…­: 36,
        ä¸‰åä¸ƒ: 37,
        ä¸‰åå…«: 38,
        ä¸‰åä¹: 39,
        å››å: 40,
        å››åä¸€: 41,
        å››åäºŒ: 42,
        å››åä¸‰: 43,
        å››åå››: 44,
        å››åäº”: 45,
      };
      // æå–中文数字
      const getNumberFromText = (text) => {
        if (!text || typeof text !== "string") return -1;
        // åŒ¹é…ä¸­æ–‡æ•°å­—,支持一到四十五
        const match = text.match(/^([一二三四五六七八九十]+)/);
        if (match && match[1]) {
          const chineseNum = match[1];
          return chineseNumMap[chineseNum] !== undefined
            ? chineseNumMap[chineseNum]
            : -1;
        }
        // å¦‚果没有匹配到中文数字,尝试匹配阿拉伯数字
        const arabicMatch = text.match(/^(\d+)/);
        if (arabicMatch && arabicMatch[1]) {
          const num = parseInt(arabicMatch[1], 10);
          return num >= 1 && num <= 45 ? num : -1;
        }
        return -1;
      };
      const numA = getNumberFromText(a);
      const numB = getNumberFromText(b);
      // å¤„理无法解析的情况
      if (numA === -1 && numB === -1) {
        return (a || "").localeCompare(b || "");
      }
      if (numA === -1) return 1;
      if (numB === -1) return -1;
      return numA - numB;
    },
    // æœç´¢å¤„理函数
    handleSearch() {
      if (!this.searchName.trim()) {
        // å¦‚果搜索框为空,显示所有数据
        this.currentDisplayList = [...this.infotitlelist];
      } else {
        // æ ¹æ®æ‚£è€…姓名进行筛选(不区分大小写)
        const keyword = this.searchName.toLowerCase();
        this.currentDisplayList = this.infotitlelist.filter((item) => {
          return item.sendname && item.sendname.toLowerCase().includes(keyword);
        });
      }
    },
    customSort(data) {
      // å®šä¹‰æ‚¨æœŸæœ›çš„病区顺序(扩展到四十五)
      const order = [
        "一",
        "二",
        "三",
        "四",
        "五",
        "六",
        "七",
        "八",
        "九",
        "十",
        "十一",
        "十二",
        "十三",
        "十四",
        "十五",
        "十六",
        "十七",
        "十八",
        "十九",
        "二十",
        "二十一",
        "二十二",
        "二十三",
        "二十四",
        "二十五",
        "二十六",
        "二十七",
        "二十八",
        "二十九",
        "三十",
        "三十一",
        "三十二",
        "三十三",
        "三十四",
        "三十五",
        "三十六",
        "三十七",
        "三十八",
        "三十九",
        "四十",
        "四十一",
        "四十二",
        "四十三",
        "四十四",
        "四十五",
      ];
      return data.sort((a, b) => {
        // æå–病区名称中的中文数字部分
        const getIndex = (name) => {
          if (!name || typeof name !== "string") return -1;
          // åŒ¹é…ä¸­æ–‡æ•°å­—
          const chineseMatch = name.match(/^([一二三四五六七八九十]+)/);
          if (chineseMatch && chineseMatch[1]) {
            return order.indexOf(chineseMatch[1]);
          }
          // åŒ¹é…é˜¿æ‹‰ä¼¯æ•°å­—
          const arabicMatch = name.match(/^(\d+)/);
          if (arabicMatch && arabicMatch[1]) {
            const num = parseInt(arabicMatch[1], 10);
            if (num >= 1 && num <= 45) {
              return num - 1; // å› ä¸ºæ•°ç»„索引从0开始
            }
          }
          return -1;
        };
        const indexA = getIndex(a.leavehospitaldistrictname);
        const indexB = getIndex(b.leavehospitaldistrictname);
        // æŽ’序逻辑
        if (indexA === -1 && indexB === -1) {
          return (a.leavehospitaldistrictname || "").localeCompare(
            b.leavehospitaldistrictname || ""
          );
        }
        if (indexA === -1) return 1;
        if (indexB === -1) return -1;
        return indexA - indexB;
      });
    },
    getRowKey(row) {
      return row.statisticaltype === 1
        ? row.leavehospitaldistrictcode
        : row.deptcode;
    },
    // å¤„理行点击展开
    handleRowClick(row) {
      console.log(row, "row");
      // å¦‚果已经展开则收起
      if (this.expands.includes(this.getRowKey(row))) {
        this.expands = [];
        return;
      }
      // å¤„理查询参数
      const params = {
        ...this.queryParams,
        // å¦‚果选择了"全部",则传所有病区/科室代码
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.allDeptCodes
          : this.queryParams.deptcodes,
        leavehospitaldistrictcodes: [row.leavehospitaldistrictcode],
        drcode: "1",
        visitCount: 1, // è®¾ç½®ä¸ºé¦–次随访
      };
      // ç§»é™¤å¯èƒ½å­˜åœ¨çš„"all"值
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      // å¦‚果该行还没有加载医生数据,则加载
      if (!row.doctorStats) {
        this.loading = true;
        getSfStatistics(params).then((res) => {
          this.$set(row, "doctorStats", res.data);
          this.expands = [this.getRowKey(row)];
          this.loading = false;
        });
      } else {
        this.expands = [this.getRowKey(row)];
      }
    },
    getSummaries(param) {
      const { columns, data } = param;
      const sums = [];
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "合计";
          return;
        }
        if (index === 1 || index === 2) {
          sums[index] = "/";
          return;
        }
        // å¯¹ç™¾åˆ†æ¯”字段特殊处理 - å–平均值
        if (
          column.property === "followUpRate" ||
          column.property === "rate" ||
          column.property === "followUpRateAgain"
        ) {
          // æå–所有有效百分比值并转换为小数
          const percentageValues = data
            .map((item) => {
              const value = item[column.property];
              if (!value || value === "-" || value === "0%") return null;
              // å¤„理带百分号的数据
              if (typeof value === "string" && value.includes("%")) {
                // åŽ»é™¤ç™¾åˆ†å·å¹¶è½¬æ¢ä¸ºå°æ•°
                const numValue = parseFloat(value.replace("%", "")) / 100;
                return isNaN(numValue) ? null : numValue;
              } else {
                // å¤„理已经是小数的数据
                const numValue = parseFloat(value);
                return isNaN(numValue) ? null : numValue;
              }
            })
            .filter((value) => value !== null && value !== 0); // è¿‡æ»¤æŽ‰null和0值
          if (percentageValues.length > 0) {
            const average =
              percentageValues.reduce((sum, value) => sum + value, 0) /
              percentageValues.length;
            sums[index] = (average * 100).toFixed(2) + "%";
          } else {
            sums[index] = "0.00%";
          }
        } else {
          // æ™®é€šæ•°å­—字段 - æ±‚å’Œ
          const values = data.map((item) => {
            const value = item[column.property];
            if (value === "-" || value === "" || value === null) return 0;
            return Number(value) || 0;
          });
          if (!values.every((value) => isNaN(value))) {
            sums[index] = values.reduce((prev, curr) => prev + curr, 0);
            sums[index] = this.formatNumber(sums[index]);
          } else {
            sums[index] = "-";
          }
        }
      });
      return sums;
    },
    // å†…部表格合计行计算方法
    getInnerSummaries(param) {
      const { columns, data } = param;
      const sums = [];
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "小计";
          return;
        }
        if (column.property === "drname" || column.property === "deptname") {
          sums[index] = "-";
          return;
        }
        // å¯¹ç™¾åˆ†æ¯”字段特殊处理 - å–平均值
        if (column.property === "followUpRate" || column.property === "rate") {
          // æå–所有有效百分比值并转换为小数
          const percentageValues = data
            .map((item) => {
              const value = item[column.property];
              if (!value || value === "-" || value === "0%") return null;
              // å¤„理带百分号的数据
              if (typeof value === "string" && value.includes("%")) {
                // åŽ»é™¤ç™¾åˆ†å·å¹¶è½¬æ¢ä¸ºå°æ•°
                const numValue = parseFloat(value.replace("%", "")) / 100;
                return isNaN(numValue) ? null : numValue;
              } else {
                // å¤„理已经是小数的数据
                const numValue = parseFloat(value);
                return isNaN(numValue) ? null : numValue;
              }
            })
            .filter((value) => value !== null && value !== 0);
          if (percentageValues.length > 0) {
            const average =
              percentageValues.reduce((sum, value) => sum + value, 0) /
              percentageValues.length;
            sums[index] = (average * 100).toFixed(2) + "%";
          } else {
            sums[index] = "0.00%";
          }
        } else {
          // æ™®é€šæ•°å­—字段 - æ±‚å’Œ
          const values = data.map((item) => {
            const value = item[column.property];
            if (value === "-" || value === "" || value === null) return 0;
            return Number(value) || 0;
          });
          if (!values.every((value) => isNaN(value))) {
            sums[index] = values.reduce((prev, curr) => prev + curr, 0);
            sums[index] = this.formatNumber(sums[index]);
          } else {
            sums[index] = "-";
          }
        }
      });
      return sums;
    },
    // è¾…助方法:提取百分比数值
    extractPercentageValue(value) {
      if (!value) return null;
      if (typeof value === "string") {
        // å¤„理带百分号的字符串
        if (value.includes("%")) {
          const num = parseFloat(value.replace("%", ""));
          return isNaN(num) ? null : num / 100;
        }
        // å¤„理纯数字字符串
        const num = parseFloat(value);
        return isNaN(num) ? null : num;
      }
      // å¤„理数字类型
      return typeof value === "number" ? value : null;
    },
    // æ•°å­—格式化方法
    formatNumber(num) {
      if (isNaN(num)) return "-";
      return Number.isInteger(num) ? num.toString() : num.toFixed(0);
    },
    /** ä¿®æ”¹æ ‡ç­¾ */
    handleUpdate(row) {
      console.log(row, "修改标签");
      this.lstamendtagVisible = true;
      this.lstamendtag = true;
      this.tagform = {
        isupload: row.isupload,
        tagname: row.tagname,
        tagcategoryid: row.tagcategoryid,
        tagdescription: row.tagdescription,
        tagid: row.tagid,
      };
    },
    // èŽ·å–ç§‘å®¤æ ‘
    getDeptTree() {
      // ç§‘室列表
      this.flatArraydept = store.getters.belongDepts.map((dept) => {
      this.flatArraydept = this.$store.getters.belongDepts.map((dept) => {
        return {
          label: dept.deptName,
          value: dept.deptCode,
        };
      });
      // å­˜å‚¨æ‰€æœ‰ç§‘室代码
      this.allDeptCodes = store.getters.belongDepts.map(
        (dept) => dept.deptCode
      );
          value: dept.deptCode
        }
      })
      this.allDeptCodes = this.$store.getters.belongDepts.map((dept) => dept.deptCode)
      // ç—…区列表
      this.flatArrayhospit = store.getters.belongWards.map((ward) => {
      this.flatArrayhospit = this.$store.getters.belongWards.map((ward) => {
        return {
          label: ward.districtName,
          value: ward.districtCode,
        };
      });
      // å­˜å‚¨æ‰€æœ‰ç—…区代码
      this.allWardCodes = store.getters.belongWards.map(
        (ward) => ward.districtCode
      );
      this.flatArraydept.push({ label: "全部", value: "all" });
      this.flatArrayhospit.push({ label: "全部", value: "all" });
    },
    flattenArray(multiArray) {
      let result = [];
      // é€’归函数,用于将多级数组转换为一维数组,只包含最底层的元素
      function flatten(element) {
        // å¦‚果当前元素有子元素,继续递归
        if (element.children && element.children.length > 0) {
          element.children.forEach((child) => flatten(child));
        } else {
          // å…‹éš†å…ƒç´ ä»¥é¿å…ä¿®æ”¹åŽŸå§‹æ•°æ®
          let item = JSON.parse(JSON.stringify(element));
          result.push(item); // å°†æœ€åº•层的元素添加到结果数组
          value: ward.districtCode
        }
      })
      this.allWardCodes = this.$store.getters.belongWards.map((ward) => ward.districtCode)
      this.flatArraydept.push({ label: '全部', value: 'all' })
      this.flatArrayhospit.push({ label: '全部', value: 'all' })
    },
    handleTabClick(tab) {
      this.activeTab = tab.name
      this.loadCurrentTabData()
    },
    loadCurrentTabData() {
      switch (this.activeTab) {
        case 'first':
          this.$refs.firstFollowUp.loadData()
          break
        case 'second':
          this.$refs.secondFollowUp.loadData()
          break
        case 'continued':
          this.$refs.continuedCare.loadData()
          break
      }
    },
    handleQuery() {
      this.queryParams.startTime = this.parseTime(this.queryParams.dateRange[0])
      this.queryParams.endTime = this.parseTime(this.queryParams.dateRange[1])
      if (this.queryParams.statisticaltype == 1) {
        this.queryParams.deptcodes = []
      } else if (this.queryParams.statisticaltype == 2) {
        this.queryParams.leavehospitaldistrictcodes = []
      }
      // ä»Žé¡¶å±‚元素开始递归
      multiArray.forEach((element) => flatten(element));
      return result; // è¿”回只包含最底层元素的一维数组
      this.loadCurrentTabData()
    },
    addladeltag() {
      this.lstamendtagVisible = true;
      this.lstamendtag = false;
      this.tagform = {
        isupload: "",
        tagname: "",
        tagcategoryid: "",
        tagdescription: "",
        tagid: "",
      };
    resetQuery() {
      this.queryParams.dateRange = []
      this.queryParams.leavehospitaldistrictcodes = []
      this.handleQuery()
    },
    Seedetails(row) {
      this.SeedetailsVisible = true;
      this.Seedloading = true;
      this.patientqueryParams.starttime = this.parseTime(
        this.queryParams.dateRange[0]
      );
      this.patientqueryParams.endtime = this.parseTime(
        this.queryParams.dateRange[1]
      );
      this.patientqueryParams.deptcode = row.deptcode;
      selectTimelyRate(this.patientqueryParams).then((response) => {
        this.logsheetlist = response.data.detail;
        this.patienttotal = response.data.total;
        this.Seedloading = false;
      });
    async handleExport() {
      switch (this.activeTab) {
        case 'first':
          await this.$refs.firstFollowUp.exportTable()
          break
        case 'second':
          await this.$refs.secondFollowUp.exportTable()
          break
        case 'continued':
          await this.$refs.continuedCare.exportTable()
          break
      }
    },
    Seedetailstion() {
      selectTimelyRate(this.patientqueryParams).then((response) => {
        this.logsheetlist = response.data.detail;
        this.patienttotal = response.data.total;
        this.Seedloading = false;
      });
    showChartDialog() {
      this.chartData = this.getCurrentTabData()
      this.chartDialogVisible = true
    },
    getCurrentTabData() {
      switch (this.activeTab) {
        case 'first':
          return this.$refs.firstFollowUp.tableData
        case 'second':
          return this.$refs.secondFollowUp.tableData
        case 'continued':
          return this.$refs.continuedCare.tableData
        default:
          return []
      }
    },
    viewDetails(row, title) {
      this.infotitleVisible = true;
      this.infotitle = title;
      this.infotitlelist = row; // å‡è®¾row就是需要展示的详细数组
      console.log(this.infotitlelist, "this.infotitlelist");
      this.infotitlelist.forEach((item) => {
        let idArray = null;
        if (item.preachform) {
          if (item.endtime) {
            item.preachformson = item.preachform;
            idArray = item.preachform.split(",");
          }
          item.preachform = idArray.map((value) => {
            // æŸ¥æ‰¾id对应的对象
            const item = this.checkboxlist.find((item) => item.value == value);
            // å¦‚果找到对应的id,返回label值,否则返回null
            return item ? item.label : null;
          });
        }
      });
      // åˆå§‹åŒ–加载
      this.loadIndex = 0;
      this.currentDisplayList = [];
      this.$nextTick(() => {
        this.loadMoreData();
      });
      this.infotitle = title
      this.infotitlelist = row
      this.infotitleVisible = true
    },
    loadMoreData() {
      if (this.isLoading) return;
      this.isLoading = true;
      // æ¨¡æ‹Ÿå¼‚步加载,实际可能是直接切片本地数据
      setTimeout(() => {
        console.log(this.infotitlelist, "this.infotitlelist");
    Seedetails(row) {
      this.SeedetailsVisible = true
      this.Seedloading = true
        const nextChunk = this.infotitlelist.slice(
          this.loadIndex,
          this.loadIndex + this.pageSize
        );
        this.currentDisplayList = this.currentDisplayList.concat(nextChunk);
        this.loadIndex += this.pageSize;
        this.isLoading = false;
      }, 200);
      this.$refs.firstFollowUp.selectTimelyRate(row, this.queryParams.dateRange)
        .then(response => {
          this.logsheetlist = response.data.detail
          this.patienttotal = response.data.total
          this.Seedloading = false
        })
    },
    handleScroll(event) {
      const scrollContainer = event.target;
      // åˆ¤æ–­æ˜¯å¦æ»šåŠ¨åˆ°åº•éƒ¨
      const isAtBottom =
        scrollContainer.scrollTop + scrollContainer.clientHeight >=
        scrollContainer.scrollHeight - 10;
      if (
        isAtBottom &&
        !this.isLoading &&
        this.loadIndex < this.infotitlelist.length
      ) {
        this.loadMoreData();
      }
    Seedetailstion() {
      this.$refs.firstFollowUp.selectTimelyRate(this.patientqueryParams)
        .then(response => {
          this.logsheetlist = response.data.detail
          this.patienttotal = response.data.total
        })
    },
    SeedetailsgGo(row) {
      this.SeedetailsVisible = false;
      let type = "";
      if (row.preachformson && row.preachformson.includes("3")) {
        type = 1;
      this.SeedetailsVisible = false
      let type = ''
      if (row.preachformson && row.preachformson.includes('3')) {
        type = 1
      }
      setTimeout(() => {
        this.$router.push({
          path: "/followvisit/record/detailpage/",
          path: '/followvisit/record/detailpage/',
          query: {
            taskid: row.taskid,
            patid: row.patid,
            id: row.id,
            Voicetype: type,
            // visitCount: this.topqueryParams.visitCount,
          },
        });
      }, 300);
    },
    // æ·»åŠ /修改标签
    Maintenancetag() {
      if (this.lstamendtag) {
        toamendtag(this.addDateRange(this.tagform)).then((response) => {
          console.log(response);
          this.getList();
        });
      } else {
        addapitag(this.addDateRange(this.tagform)).then((response) => {
          console.log(response);
          this.getList();
        });
      }
      this.tagform = {
        isupload: "",
        tagname: "",
        tagcategoryid: "",
        tagdescription: "",
        tagid: "",
      };
    },
    routerErr(row) {
      console.log(row, "跳转异常");
      this.$router.push({
        path: "/followvisit/discharge",
        query: {
          errtype: 1,
          leavehospitaldistrictcode: row.leavehospitaldistrictcode,
        },
      });
    },
    // è¡¨å•重置
    reset() {
      this.form = {
        userId: undefined,
        deptId: undefined,
        userName: undefined,
        nickName: undefined,
        password: undefined,
        phonenumber: undefined,
        email: undefined,
        sex: undefined,
        status: "0",
        remark: undefined,
        postIds: [],
        roleIds: [],
      };
      this.resetForm("form");
    },
    // æ ‡ç­¾çŠ¶æ€ä¿®æ”¹
    handleStatusChange(row) {
      console.log(row.isupload);
      let text = row.isupload === "0" ? "启用" : "停用";
      this.$modal
        .confirm('确认要"' + text + '""' + row.tagname + '"标签吗?')
        .then(function () {
          return changetagcategory(row.tagid, row.isupload);
            Voicetype: type
          }
        })
        .then(() => {
          this.$modal.msgSuccess(text + "成功");
        })
        .catch(function () {
          row.isupload = row.isupload === "0" ? "1" : "0";
        });
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ - ä¿®æ”¹ä¸ºæœç´¢å½“前激活的tab数据 */
    handleQuery() {
      this.queryParams.pageNum = 1;
      if (!this.queryParams.dateRange) this.queryParams.dateRange = [];
      if (this.queryParams.statisticaltype == 1) {
        this.queryParams.deptcodes = [];
      } else if (this.queryParams.statisticaltype == 2) {
        this.queryParams.leavehospitaldistrictcodes = [];
      }
      this.queryParams.startTime = this.parseTime(
        this.queryParams.dateRange[0]
      );
      this.queryParams.endTime = this.parseTime(this.queryParams.dateRange[1]);
      // æ ¹æ®å½“前激活的tab加载对应数据
      if (this.activeTab === "first") {
        this.getFirstFollowUpList();
      } else {
        this.getSecondFollowUpList();
      }
      }, 300)
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    resetQuery() {
      this.queryParams.dateRange = [];
      this.queryParams.leavehospitaldistrictcodes = [];
      this.handleQuery();
    },
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      this.ids = selection.map((item) => item.tagid);
      this.single = selection.length != 1;
      this.multiple = !selection.length;
    },
    /** åˆ é™¤æŒ‰é’®æ“ä½œ */
    handleDelete(row) {
      console.log(row, "删除弹窗");
      const tagids = row.tagid || this.ids;
      console.log(tagids);
      const tagname = row.tagname;
      this.$modal
        .confirm(
          tagname
            ? '是否确认删除标签名称为"' + tagname + '"的数据项?'
            : "是否确认删除选中的数据项?"
        )
        .then(function () {
          return deletetag(tagids);
        })
        .then(() => {
          this.getList();
          this.$modal.msgSuccess("删除成功");
        })
        .catch(() => {});
    },
    // å¯¼å‡ºæ–¹æ³•
    async exportTable() {
      try {
        // 1. èŽ·å–å¹¶æ ¼å¼åŒ–æ—¥æœŸèŒƒå›´
        let dateRangeString = "";
        let sheetNameSuffix = "";
        if (
          this.queryParams.dateRange &&
          this.queryParams.dateRange.length === 2
        ) {
          const startDateStr = this.queryParams.dateRange[0];
          const endDateStr = this.queryParams.dateRange[1];
          const formatDateForDisplay = (dateTimeStr) => {
            return dateTimeStr.split(" ")[0];
          };
          const startDateFormatted = formatDateForDisplay(startDateStr);
          const endDateFormatted = formatDateForDisplay(endDateStr);
          dateRangeString = `${startDateFormatted}至${endDateFormatted}`;
          sheetNameSuffix = `${startDateFormatted}至${endDateFormatted}`;
        } else {
          const now = new Date();
          const currentMonth = now.getMonth() + 1;
          dateRangeString = `${currentMonth}月`;
          sheetNameSuffix = `${currentMonth}月`;
        }
        // 2. æ ¹æ®å½“前激活的tab确定导出的数据
        const isFirstFollowUp = this.activeTab === "first";
        let excelName, worksheetName, dataToExport;
        if (isFirstFollowUp) {
          excelName = `首次出院随访统计表_${dateRangeString}.xlsx`;
          worksheetName = `首次随访统计_${sheetNameSuffix}`;
          dataToExport = this.firstFollowUpList;
          if (!dataToExport || dataToExport.length === 0) {
            this.$message.warning("暂无首次随访数据可导出");
            return false;
          }
        } else {
          excelName = `再次出院随访统计表_${dateRangeString}.xlsx`;
          worksheetName = `再次随访统计_${sheetNameSuffix}`;
          dataToExport = this.secondFollowUpList;
          if (!dataToExport || dataToExport.length === 0) {
            this.$message.warning("暂无再次随访数据可导出");
            return false;
          }
        }
        // 3. åˆ›å»ºå·¥ä½œç°¿å’Œå·¥ä½œè¡¨
        const workbook = new ExcelJS.Workbook();
        const worksheet = workbook.addWorksheet(worksheetName);
        // 4. æž„建表格
        if (isFirstFollowUp) {
          this.buildFirstFollowUpExportSheet(
            worksheet,
            dataToExport,
            sheetNameSuffix
          );
        } else {
          this.buildSecondFollowUpExportSheet(
            worksheet,
            dataToExport,
            sheetNameSuffix
          );
        }
        // 5. ç”Ÿæˆå¹¶ä¸‹è½½æ–‡ä»¶
        const buffer = await workbook.xlsx.writeBuffer();
        const blob = new Blob([buffer], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        });
        saveAs(blob, excelName);
        this.$message.success("导出成功");
        return true;
      } catch (error) {
        console.error("导出失败:", error);
        this.$message.error(`导出失败: ${error.message}`);
        return false;
      }
    },
    /** æž„建首次随访导出表格 */
    buildFirstFollowUpExportSheet(worksheet, data, sheetNameSuffix) {
      const titleStyle = {
        font: {
          name: "微软雅黑",
          size: 16,
          bold: true,
          color: { argb: "FF000000" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFE6F3FF" },
        },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const headerStyle = {
        font: {
          name: "微软雅黑",
          size: 11,
          bold: true,
          color: { argb: "FF000000" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFF5F7FA" },
        },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const cellStyle = {
        font: { name: "宋体", size: 10, color: { argb: "FF000000" } },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const summaryStyle = {
        font: {
          name: "宋体",
          size: 10,
          bold: true,
          color: { argb: "FF409EFF" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFF5F7FA" },
        },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      // 1. æ·»åŠ æ€»æ ‡é¢˜è¡Œ
      worksheet.mergeCells(1, 1, 1, 16); // åˆå¹¶A1到P1
      const titleCell = worksheet.getCell(1, 1);
      titleCell.value = `首次出院随访统计表_${sheetNameSuffix}`;
      titleCell.style = titleStyle;
      worksheet.getRow(1).height = 35;
      // 2. åˆ›å»ºè¡¨å¤´
      const secondRowHeaders = [
        "", // A2 å±•开列占位
        "出院病区",
        "科室",
        "出院人次",
        "无需随访人次",
        "应随访人次", // B2 to F2
        // é¦–次出院随访子表头
        "需随访",
        "待随访",
        "随访成功",
        "随访失败",
        "随访率",
        "及时率",
        "人工",
        "短信",
        "微信",
      ];
      // æ·»åŠ ç¬¬äºŒè¡Œ
      secondRowHeaders.forEach((header, index) => {
        const cell = worksheet.getCell(3, index + 1);
        cell.value = header;
        cell.style = headerStyle;
      });
      // 3. åˆå¹¶å•元格
      // åˆå¹¶ A2:A3, B2:B3, C2:C3, D2:D3, E2:E3, F2:F3
      for (let i = 1; i <= 6; i++) {
        worksheet.mergeCells(2, i, 3, i);
        const cell = worksheet.getCell(2, i);
        cell.style = headerStyle;
      }
      // è®¾ç½®ç¬¬ä¸€è¡Œåˆå¹¶å•元格的值
      worksheet.getCell(2, 1).value = "";
      worksheet.getCell(2, 2).value = "出院病区";
      worksheet.getCell(2, 3).value = "科室";
      worksheet.getCell(2, 4).value = "出院人次";
      worksheet.getCell(2, 5).value = "无需随访人次";
      worksheet.getCell(2, 6).value = "应随访人次";
      // 4. åˆå¹¶"首次出院随访"标题
      worksheet.mergeCells(2, 7, 2, 15); // G2:O2
      worksheet.getCell(2, 7).value = "首次出院随访";
      worksheet.getCell(2, 7).style = headerStyle;
      // 5. è®¾ç½®è¡Œé«˜
      worksheet.getRow(2).height = 28;
      worksheet.getRow(3).height = 25;
      // 6. æ·»åŠ æ•°æ®è¡Œ
      data.forEach((item, rowIndex) => {
        const dataRow = worksheet.addRow(
          [
            "", // å±•开列
            item.leavehospitaldistrictname || "",
            item.deptname || "",
            item.dischargeCount || 0,
            item.nonFollowUp || 0,
            item.followUpNeeded || 0,
            // é¦–次出院随访数据
            item.needFollowUp || 0,
            item.pendingFollowUp || 0,
            item.followUpSuccess || 0,
            item.followUpFail || 0,
            item.followUpRate || "0%",
            item.rate ? (Number(item.rate) * 100).toFixed(2) + "%" : "0%",
            item.manual || 0,
            item.sms || 0,
            item.weChat || 0,
          ],
          rowIndex + 4
        );
        // åº”用数据行样式
        dataRow.eachCell((cell) => {
          cell.style = cellStyle;
        });
        dataRow.height = 24;
      });
      // 7. æ·»åŠ åˆè®¡è¡Œ
      const summaries = this.getFirstFollowUpSummaries(data);
      const summaryRow = worksheet.addRow(summaries);
      summaryRow.eachCell((cell, colNumber) => {
        cell.style = summaryStyle;
        if (colNumber === 1) {
          cell.value = "合计";
        }
      });
      summaryRow.height = 28;
      // 8. è®¾ç½®åˆ—宽
      worksheet.columns = [
        { width: 8 }, // å±•开列
        { width: 20 }, // å‡ºé™¢ç—…区
        { width: 15 }, // ç§‘室
        { width: 12 }, // å‡ºé™¢äººæ¬¡
        { width: 12 }, // æ— éœ€éšè®¿äººæ¬¡
        { width: 12 }, // åº”随访人次
        // é¦–次出院随访列
        { width: 10 }, // éœ€éšè®¿
        { width: 10 }, // å¾…随访
        { width: 10 }, // éšè®¿æˆåŠŸ
        { width: 10 }, // éšè®¿å¤±è´¥
        { width: 12 }, // éšè®¿çއ
        { width: 12 }, // åŠæ—¶çއ
        { width: 8 }, // äººå·¥
        { width: 8 }, // çŸ­ä¿¡
        { width: 8 }, // å¾®ä¿¡
      ];
    },
    /** é¦–次随访数据合计行计算 */
    getFirstFollowUpSummaries(data) {
      const summaries = [
        "合计",
        "/",
        "/",
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        "0%",
        "0%",
        0,
        0,
        0,
      ];
      data.forEach((item) => {
        // æ•°å€¼å­—段求和
        summaries[3] += Number(item.dischargeCount) || 0;
        summaries[4] += Number(item.nonFollowUp) || 0;
        summaries[5] += Number(item.followUpNeeded) || 0;
        summaries[6] += Number(item.needFollowUp) || 0;
        summaries[7] += Number(item.pendingFollowUp) || 0;
        summaries[8] += Number(item.followUpSuccess) || 0;
        summaries[9] += Number(item.followUpFail) || 0;
        summaries[12] += Number(item.manual) || 0;
        summaries[13] += Number(item.sms) || 0;
        summaries[14] += Number(item.weChat) || 0;
      });
      // è®¡ç®—百分比字段的平均值
      const followUpRateValues = data
        .map((item) => this.extractPercentageValue(item.followUpRate))
        .filter((value) => value !== null);
      const rateValues = data
        .map((item) => this.extractPercentageValue(item.rate))
        .filter((value) => value !== null);
      if (followUpRateValues.length > 0) {
        const avgFollowUpRate =
          followUpRateValues.reduce((sum, val) => sum + val, 0) /
          followUpRateValues.length;
        summaries[10] = (avgFollowUpRate * 100).toFixed(2) + "%";
      }
      if (rateValues.length > 0) {
        const avgRate =
          rateValues.reduce((sum, val) => sum + val, 0) / rateValues.length;
        summaries[11] = (avgRate * 100).toFixed(2) + "%";
      }
      // æ ¼å¼åŒ–æ•°å­—
      summaries[3] = this.formatNumber(summaries[3]);
      summaries[4] = this.formatNumber(summaries[4]);
      summaries[5] = this.formatNumber(summaries[5]);
      summaries[6] = this.formatNumber(summaries[6]);
      summaries[7] = this.formatNumber(summaries[7]);
      summaries[8] = this.formatNumber(summaries[8]);
      summaries[9] = this.formatNumber(summaries[9]);
      summaries[12] = this.formatNumber(summaries[12]);
      summaries[13] = this.formatNumber(summaries[13]);
      summaries[14] = this.formatNumber(summaries[14]);
      return summaries;
    },
    /** æž„建再次随访导出表格 */
    buildSecondFollowUpExportSheet(worksheet, data, sheetNameSuffix) {
      const titleStyle = {
        font: {
          name: "微软雅黑",
          size: 16,
          bold: true,
          color: { argb: "FF000000" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFE6F3FF" },
        },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const headerStyle = {
        font: {
          name: "微软雅黑",
          size: 11,
          bold: true,
          color: { argb: "FF000000" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFF5F7FA" },
        },
        alignment: { vertical: "middle", horizontal: "center", wrapText: true },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const cellStyle = {
        font: { name: "宋体", size: 10, color: { argb: "FF000000" } },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      const summaryStyle = {
        font: {
          name: "宋体",
          size: 10,
          bold: true,
          color: { argb: "FF409EFF" },
        },
        fill: {
          type: "pattern",
          pattern: "solid",
          fgColor: { argb: "FFF5F7FA" },
        },
        alignment: { vertical: "middle", horizontal: "center" },
        border: {
          top: { style: "thin", color: { argb: "FFD0D0D0" } },
          left: { style: "thin", color: { argb: "FFD0D0D0" } },
          bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      // 1. æ·»åŠ æ€»æ ‡é¢˜è¡Œ
      worksheet.mergeCells(1, 1, 1, 15); // åˆå¹¶A1到O1
      const titleCell = worksheet.getCell(1, 1);
      titleCell.value = `再次出院随访统计表_${sheetNameSuffix}`;
      titleCell.style = titleStyle;
      worksheet.getRow(1).height = 35;
      // 2. åˆ›å»ºè¡¨å¤´
      const secondRowHeaders = [
        "", // A2 å±•开列占位
        "出院病区",
        "科室",
        "出院人次",
        "无需随访人次",
        "应随访人次", // B2 to F2
        // å†æ¬¡å‡ºé™¢éšè®¿å­è¡¨å¤´
        "需随访",
        "待随访",
        "随访成功",
        "随访失败",
        "随访率",
        "人工",
        "短信",
        "微信",
      ];
      // æ·»åŠ ç¬¬äºŒè¡Œ
      secondRowHeaders.forEach((header, index) => {
        const cell = worksheet.getCell(3, index + 1);
        cell.value = header;
        cell.style = headerStyle;
      });
      // 3. åˆå¹¶å•元格
      // åˆå¹¶ A2:A3, B2:B3, C2:C3, D2:D3, E2:E3, F2:F3
      for (let i = 1; i <= 6; i++) {
        worksheet.mergeCells(2, i, 3, i);
        const cell = worksheet.getCell(2, i);
        cell.style = headerStyle;
      }
      // è®¾ç½®ç¬¬ä¸€è¡Œåˆå¹¶å•元格的值
      worksheet.getCell(2, 1).value = "";
      worksheet.getCell(2, 2).value = "出院病区";
      worksheet.getCell(2, 3).value = "科室";
      worksheet.getCell(2, 4).value = "出院人次";
      worksheet.getCell(2, 5).value = "无需随访人次";
      worksheet.getCell(2, 6).value = "应随访人次";
      // 4. åˆå¹¶"再次出院随访"标题
      worksheet.mergeCells(2, 7, 2, 14); // G2:N2
      worksheet.getCell(2, 7).value = "再次出院随访";
      worksheet.getCell(2, 7).style = headerStyle;
      // 5. è®¾ç½®è¡Œé«˜
      worksheet.getRow(2).height = 28;
      worksheet.getRow(3).height = 25;
      // 6. æ·»åŠ æ•°æ®è¡Œ
      data.forEach((item, rowIndex) => {
        const dataRow = worksheet.addRow(
          [
            "", // å±•开列
            item.leavehospitaldistrictname || "",
            item.deptname || "",
            item.dischargeCount || 0,
            item.nonFollowUp || 0,
            item.followUpNeeded || 0,
            // å†æ¬¡å‡ºé™¢éšè®¿æ•°æ®
            item.needFollowUpAgain || 0,
            item.pendingFollowUpAgain || 0,
            item.followUpSuccessAgain || 0,
            item.followUpFailAgain || 0,
            item.followUpRateAgain || "0%",
            item.manualAgain || 0,
            item.smsAgain || 0,
            item.weChatAgain || 0,
          ],
          rowIndex + 4
        );
        // åº”用数据行样式
        dataRow.eachCell((cell) => {
          cell.style = cellStyle;
        });
        dataRow.height = 24;
      });
      // 7. æ·»åŠ åˆè®¡è¡Œ
      const summaries = this.getSecondFollowUpSummaries(data);
      const summaryRow = worksheet.addRow(summaries);
      summaryRow.eachCell((cell, colNumber) => {
        cell.style = summaryStyle;
        if (colNumber === 1) {
          cell.value = "合计";
        }
      });
      summaryRow.height = 28;
      // 8. è®¾ç½®åˆ—宽
      worksheet.columns = [
        { width: 8 }, // å±•开列
        { width: 20 }, // å‡ºé™¢ç—…区
        { width: 15 }, // ç§‘室
        { width: 12 }, // å‡ºé™¢äººæ¬¡
        { width: 12 }, // æ— éœ€éšè®¿äººæ¬¡
        { width: 12 }, // åº”随访人次
        // å†æ¬¡å‡ºé™¢éšè®¿åˆ—
        { width: 10 }, // éœ€éšè®¿
        { width: 10 }, // å¾…随访
        { width: 10 }, // éšè®¿æˆåŠŸ
        { width: 10 }, // éšè®¿å¤±è´¥
        { width: 12 }, // éšè®¿çއ
        { width: 8 }, // äººå·¥
        { width: 8 }, // çŸ­ä¿¡
        { width: 8 }, // å¾®ä¿¡
      ];
    },
    /** å†æ¬¡éšè®¿æ•°æ®åˆè®¡è¡Œè®¡ç®— */
    getSecondFollowUpSummaries(data) {
      const summaries = ["合计", "/", "/", 0, 0, 0, 0, 0, 0, 0, "0%", 0, 0, 0];
      data.forEach((item) => {
        // æ•°å€¼å­—段求和
        summaries[3] += Number(item.dischargeCount) || 0;
        summaries[4] += Number(item.nonFollowUp) || 0;
        summaries[5] += Number(item.followUpNeeded) || 0;
        summaries[6] += Number(item.needFollowUpAgain) || 0;
        summaries[7] += Number(item.pendingFollowUpAgain) || 0;
        summaries[8] += Number(item.followUpSuccessAgain) || 0;
        summaries[9] += Number(item.followUpFailAgain) || 0;
        summaries[11] += Number(item.manualAgain) || 0;
        summaries[12] += Number(item.smsAgain) || 0;
        summaries[13] += Number(item.weChatAgain) || 0;
      });
      // è®¡ç®—随访率百分比字段的平均值
      const followUpRateAgainValues = data
        .map((item) => this.extractPercentageValue(item.followUpRateAgain))
        .filter((value) => value !== null);
      if (followUpRateAgainValues.length > 0) {
        const avgFollowUpRateAgain =
          followUpRateAgainValues.reduce((sum, val) => sum + val, 0) /
          followUpRateAgainValues.length;
        summaries[10] = (avgFollowUpRateAgain * 100).toFixed(2) + "%";
      }
      // æ ¼å¼åŒ–æ•°å­—
      summaries[3] = this.formatNumber(summaries[3]);
      summaries[4] = this.formatNumber(summaries[4]);
      summaries[5] = this.formatNumber(summaries[5]);
      summaries[6] = this.formatNumber(summaries[6]);
      summaries[7] = this.formatNumber(summaries[7]);
      summaries[8] = this.formatNumber(summaries[8]);
      summaries[9] = this.formatNumber(summaries[9]);
      summaries[11] = this.formatNumber(summaries[11]);
      summaries[12] = this.formatNumber(summaries[12]);
      summaries[13] = this.formatNumber(summaries[13]);
      return summaries;
    },
    /** å†æ¬¡éšè®¿è¡¨æ ¼çš„合计行计算方法 */
    getSummariesSecond(param) {
      const { columns, data } = param;
      const sums = [];
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "合计";
          return;
        }
        if (index === 1 || index === 2) {
          sums[index] = "/";
          return;
        }
        if (column.property === "followUpRateAgain") {
          const percentageValues = data
            .map((item) => {
              const value = item[column.property];
              if (!value || value === "-" || value === "0%") return null;
              if (typeof value === "string" && value.includes("%")) {
                const numValue = parseFloat(value.replace("%", "")) / 100;
                return isNaN(numValue) ? null : numValue;
              } else {
                const numValue = parseFloat(value);
                return isNaN(numValue) ? null : numValue;
              }
            })
            .filter((value) => value !== null && value !== 0);
          if (percentageValues.length > 0) {
            const average =
              percentageValues.reduce((sum, value) => sum + value, 0) /
              percentageValues.length;
            sums[index] = (average * 100).toFixed(2) + "%";
          } else {
            sums[index] = "0.00%";
          }
        } else {
          const values = data.map((item) => {
            const value = item[column.property];
            if (value === "-" || value === "" || value === null) return 0;
            return Number(value) || 0;
          });
          if (!values.every((value) => isNaN(value))) {
            sums[index] = values.reduce((prev, curr) => prev + curr, 0);
            sums[index] = this.formatNumber(sums[index]);
          } else {
            sums[index] = "-";
          }
        }
      });
      return sums;
    },
    /** å†æ¬¡éšè®¿å†…部表格合计行计算方法 */
    getInnerSummariesSecond(param) {
      const { columns, data } = param;
      const sums = [];
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "小计";
          return;
        }
        if (column.property === "drname" || column.property === "deptname") {
          sums[index] = "-";
          return;
        }
        if (column.property === "followUpRateAgain") {
          const percentageValues = data
            .map((item) => {
              const value = item[column.property];
              if (!value || value === "-" || value === "0%") return null;
              if (typeof value === "string" && value.includes("%")) {
                const numValue = parseFloat(value.replace("%", "")) / 100;
                return isNaN(numValue) ? null : numValue;
              } else {
                const numValue = parseFloat(value);
                return isNaN(numValue) ? null : numValue;
              }
            })
            .filter((value) => value !== null && value !== 0);
          if (percentageValues.length > 0) {
            const average =
              percentageValues.reduce((sum, value) => sum + value, 0) /
              percentageValues.length;
            sums[index] = (average * 100).toFixed(2) + "%";
          } else {
            sums[index] = "0.00%";
          }
        } else {
          const values = data.map((item) => {
            const value = item[column.property];
            if (value === "-" || value === "" || value === null) return 0;
            return Number(value) || 0;
          });
          if (!values.every((value) => isNaN(value))) {
            sums[index] = values.reduce((prev, curr) => prev + curr, 0);
            sums[index] = this.formatNumber(sums[index]);
          } else {
            sums[index] = "-";
          }
        }
      });
      return sums;
    },
    /** å†æ¬¡éšè®¿è¡¨æ ¼çš„行点击展开 */
    handleRowClickSecond(row) {
      if (this.expandsSecond.includes(this.getRowKey(row))) {
        this.expandsSecond = [];
        return;
      }
      const params = {
        ...this.queryParams,
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.allDeptCodes
          : this.queryParams.deptcodes,
        leavehospitaldistrictcodes: [row.leavehospitaldistrictcode],
        drcode: "1",
        visitCount: 2, // è®¾ç½®ä¸ºå†æ¬¡éšè®¿
      };
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      if (!row.doctorStats) {
        this.loadingSecond = true;
        getSfStatistics(params).then((res) => {
          this.$set(row, "doctorStats", res.data);
          this.expandsSecond = [this.getRowKey(row)];
          this.loadingSecond = false;
        });
      } else {
        this.expandsSecond = [this.getRowKey(row)];
      }
    },
    /** å†æ¬¡éšè®¿è¡¨æ ¼çš„多选框选中数据 */
    handleSelectionChangeSecond(selection) {
      this.idsSecond = selection.map((item) => item.tagid);
      this.single = selection.length != 1;
      this.multiple = !selection.length;
    },
    // æ˜¾ç¤ºå›¾è¡¨å¼¹çª—
    showChartDialog() {
      this.chartDialogVisible = true;
      this.$nextTick(() => {
        this.initPieChart();
        this.initBarLineChart();
      });
    },
    // åœ¨methods中修改统计方法
    showChartDialog() {
      this.chartDialogVisible = true;
      this.$nextTick(() => {
        console.log(this.userList, "this.userList");
        this.initCharts();
      });
    },
    // æ–°å¢žåˆå§‹åŒ–图表方法
    initCharts() {
      this.initPieChart();
      this.initBarLineChart();
    },
    // åˆå§‹åŒ–饼图
    initPieChart() {
      const echarts = require("echarts");
      const pieDom = document.getElementById("pieChart");
      if (!pieDom) return;
      if (this.pieChart) {
        this.pieChart.dispose();
      }
      this.pieChart = echarts.init(pieDom);
      // è®¡ç®—饼图数据
      const followUpData = {
        pending: 0,
        success: 0,
        fail: 0,
      };
      this.userList.forEach((item) => {
        followUpData.pending += item.pendingFollowUp || 0;
        followUpData.success += item.followUpSuccess || 0;
        followUpData.fail += item.followUpFail || 0;
      });
      // ä½¿ç”¨æ›´ç¾Žè§‚的颜色方案
      const pieOption = {
        title: {
          text: "随访状态分布",
          left: "center",
          textStyle: {
            color: "#333",
            fontSize: 16,
          },
        },
        tooltip: {
          trigger: "item",
          formatter: "{a} <br/>{b}: {c} ({d}%)",
        },
        legend: {
          orient: "vertical",
          left: "left",
          data: ["待随访", "随访成功", "随访失败"],
          textStyle: {
            color: "#666",
          },
        },
        color: ["#FF9D4D", "#36B37E", "#FF5C5C"], // æ–°çš„配色方案
        series: [
          {
            name: "随访状态",
            type: "pie",
            radius: ["40%", "70%"],
            avoidLabelOverlap: true,
            itemStyle: {
              borderRadius: 10,
              borderColor: "#fff",
              borderWidth: 2,
            },
            label: {
              show: true,
              formatter: "{b}: {c} ({d}%)",
              color: "#333",
            },
            emphasis: {
              label: {
                show: true,
                fontSize: "18",
                fontWeight: "bold",
              },
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: "rgba(0, 0, 0, 0.5)",
              },
            },
            data: [
              {
                value: followUpData.pending,
                name: "待随访",
              },
              {
                value: followUpData.success,
                name: "随访成功",
              },
              {
                value: followUpData.fail,
                name: "随访失败",
              },
            ],
          },
        ],
      };
      this.pieChart.setOption(pieOption);
      window.addEventListener("resize", this.resizePieChart);
    },
    // åˆå§‹åŒ–柱状折线图
    initBarLineChart() {
      const echarts = require("echarts");
      const barDom = document.getElementById("barLineChart");
      if (!barDom) return;
      if (this.barLineChart) {
        this.barLineChart.dispose();
      }
      this.barLineChart = echarts.init(barDom);
      // å‡†å¤‡æ•°æ®
      const categories = this.userList.map(
        (item) => item.leavehospitaldistrictname || item.deptname
      );
      const dischargeData = this.userList.map(
        (item) => item.dischargeCount || 0
      );
      const followUpData = this.userList.map(
        (item) => item.followUpNeeded || 0
      );
      // æ–°å¢žä¸¤æ¡æŠ˜çº¿æ•°æ®
      const followUpRateData = this.userList.map((item) => {
        if (!item.followUpRate) return 0;
        // åŽ»æŽ‰ç™¾åˆ†å·å¹¶è½¬ä¸ºæ•°å­—
        const rateStr = String(item.followUpRate).replace("%", "");
        return parseFloat(rateStr) || 0;
      });
      const timelyRateData = this.userList.map((item) =>
        item.rate ? (Number(item.rate) * 100).toFixed(2) : 0
      );
      const option = {
        title: {
          text: "科室/病区随访趋势",
          left: "center",
          textStyle: {
            color: "#333",
            fontSize: 16,
          },
        },
        tooltip: {
          trigger: "axis",
          axisPointer: {
            type: "cross",
            crossStyle: {
              color: "#999",
            },
          },
        },
        legend: {
          data: ["出院人次", "应随访人次", "随访率(%)", "及时率(%)"],
          top: "bottom",
          textStyle: {
            color: "#666",
          },
        },
        color: ["#5470C6", "#91CC75", "#EE6666", "#9A60B4"], // æ–°å¢žç´«è‰²ç”¨äºŽåŠæ—¶çއ
        xAxis: {
          type: "category",
          data: categories,
          axisLabel: {
            interval: 0,
            rotate: 30,
            color: "#666",
          },
          axisLine: {
            lineStyle: {
              color: "#ddd",
            },
          },
        },
        yAxis: [
          {
            type: "value",
            name: "人次",
            min: 0,
            axisLabel: {
              color: "#666",
            },
            axisLine: {
              lineStyle: {
                color: "#ddd",
              },
            },
            splitLine: {
              lineStyle: {
                color: "#f0f0f0",
              },
            },
          },
          {
            type: "value",
            name: "百分比(%)",
            min: 0,
            max: 100,
            axisLabel: {
              color: "#666",
              formatter: "{value}%",
            },
            axisLine: {
              lineStyle: {
                color: "#ddd",
              },
            },
            splitLine: {
              show: false,
            },
          },
        ],
        series: [
          {
            name: "出院人次",
            type: "bar",
            barWidth: "25%",
            data: dischargeData,
            itemStyle: {
              borderRadius: [4, 4, 0, 0],
            },
          },
          {
            name: "应随访人次",
            type: "bar",
            barWidth: "25%",
            data: followUpData,
            itemStyle: {
              borderRadius: [4, 4, 0, 0],
            },
          },
          {
            name: "随访率(%)",
            type: "line",
            yAxisIndex: 1,
            data: followUpRateData,
            symbolSize: 8,
            lineStyle: {
              width: 3,
            },
            markLine: {
              silent: true,
              data: [
                {
                  yAxis: 80,
                  lineStyle: {
                    color: "#EE6666",
                    type: "dashed",
                  },
                  // label: {
                  //   position: 'end',
                  //   formatter: '目标80%'
                  // }
                },
              ],
            },
          },
          {
            name: "及时率(%)",
            type: "line",
            yAxisIndex: 1,
            data: timelyRateData,
            symbolSize: 8,
            lineStyle: {
              width: 3,
              type: "dotted", // ä½¿ç”¨è™šçº¿åŒºåˆ†
            },
            markLine: {
              silent: true,
              data: [
                {
                  yAxis: 90,
                  lineStyle: {
                    color: "#9A60B4",
                    type: "dashed",
                  },
                  // label: {
                  //   position: 'end',
                  //   formatter: '目标90%'
                  // }
                },
              ],
            },
          },
        ],
        grid: {
          top: "15%",
          left: "3%",
          right: "4%",
          bottom: "15%",
          containLabel: true,
        },
      };
      this.barLineChart.setOption(option);
      window.addEventListener("resize", this.resizeBarLineChart);
    },
    // å›¾è¡¨å“åº”式调整方法
    resizePieChart() {
      if (this.pieChart) {
        this.pieChart.resize();
      }
    },
    resizeBarLineChart() {
      if (this.barLineChart) {
        this.barLineChart.resize();
      }
    },
    // åœ¨ç»„件销毁时清理
    beforeDestroy() {
      // ç§»é™¤äº‹ä»¶ç›‘听
      window.removeEventListener("resize", this.resizePieChart);
      window.removeEventListener("resize", this.resizeBarLineChart);
      // é”€æ¯å›¾è¡¨å®žä¾‹
      if (this.pieChart) {
        this.pieChart.dispose();
        this.pieChart = null;
      }
      if (this.barLineChart) {
        this.barLineChart.dispose();
        this.barLineChart = null;
      }
    },
  },
};
    handleSearch() {
      // æœç´¢é€»è¾‘
    }
  }
}
</script>
<style lang="scss" scoped>
::v-deep .el-tabs__header {
  margin-bottom: 20px;
}
::v-deep .el-tabs__item {
  font-size: 16px;
  padding: 0 20px;
  height: 40px;
  line-height: 40px;
}
::v-deep .el-tabs__active-bar {
  height: 3px;
}
/* Tab内容区域样式 */
.el-tab-pane {
  .your-table-container {
    margin-top: 10px;
  }
}
.sidecolumn {
  width: 180px;
  min-height: 100vh;
  text-align: center;
  //   display: flex;
  margin-top: 20px;
  margin: 20px;
  padding: 30px;
  background: #edf1f7;
  border: 1px solid #dcdfe6;
  -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12),
    0 0 6px 0 rgba(0, 0, 0, 0.04);
  .sidecolumn-top {
    display: flex;
    justify-content: space-between;
    .top-wj {
      font-size: 20px;
    }
    .top-tj {
      font-size: 18px;
      color: rgb(0, 89, 255);
      cursor: pointer;
    }
  }
  .center-ss {
    margin-top: 30px;
    .input-with-select {
      height: 40px !important;
    }
  }
  .bottom-fl {
    margin-top: 30px;
    display: center !important;
  }
}
.qrcode-dialo {
  text-align: center;
  //   display: flex;
  margin: 20px;
  padding: 30px;
  background: #edf1f7;
  border: 1px solid #dcdfe6;
  -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12),
    0 0 6px 0 rgba(0, 0, 0, 0.04);
  .qrcode-text {
    font-size: 20px;
    span {
      margin-left: 20px;
    }
  }
  .qrcode-img {
    width: 300px;
    height: 400px;
  }
}
::v-deep.el-tabs--left,
.el-tabs--right {
  overflow: hidden;
  align-items: center;
  display: flex;
}
::v-deep.el-input--medium .el-input__inner {
  height: 40px !important;
}
::v-deep.el-tabs--right .el-tabs__active-bar.is-right {
  height: 40px;
  width: 5px;
  left: 0;
}
::v-deep.el-tabs--right .el-tabs__item.is-right {
  display: block;
  text-align: left;
  font-size: 20px;
}
// ç¾ŽåŒ–合计行样式
::v-deep .el-table__footer {
  .el-table__cell {
    background-color: #f5f7fa;
    font-weight: 600;
    color: #409eff;
    .cell {
      font-weight: 600;
      color: #409eff;
    }
  }
}
// å†…部表格合计行样式
::v-deep .inner-table .el-table__footer {
  .el-table__cell {
    background-color: #ecf5ff;
    font-weight: 500;
    color: #67c23a;
    .cell {
      font-weight: 500;
      color: #67c23a;
    }
  }
}
// ç™¾åˆ†æ¯”字段特殊样式
.your-table-container
  ::v-deep
  .el-table__footer
  .el-table__cell[data-field="followUpRate"]
  .cell,
.your-table-container
  ::v-deep
  .el-table__footer
  .el-table__cell[data-field="rate"]
  .cell,
.your-table-container
  ::v-deep
  .el-table__footer
  .el-table__cell[data-field="followUpRateAgain"]
  .cell {
  color: #e6a23c !important;
  font-weight: 700 !important;
}
.leftvlue {
  //   display: flex;
  //   flex: 1;
  // width: 80%;
  // margin-top: 20px;
  margin: 20px;
  padding: 30px;
.follow-up-statistics {
  padding: 20px;
  background: #ffff;
  border: 1px solid #dcdfe6;
  -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12),
    0 0 6px 0 rgba(0, 0, 0, 0.04);
  .mulsz {
    font-size: 20px;
  }
}
/* ä½¿è¡Œæœ‰æ‰‹åž‹æŒ‡é’ˆ */
.el-table__row {
  cursor: pointer;
}
/* å†…层医生表格样式 */
.inner-table {
  // è¡¨å¤´èƒŒæ™¯è‰²
  ::v-deep .el-table__header-wrapper {
    background-color: #f0f7ff !important;
  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12), 0 0 6px 0 rgba(0, 0, 0, 0.04);
    th {
      background-color: #f0f7ff !important;
  .search-section {
    margin-bottom: 20px;
  }
  .tab-section {
    ::v-deep .el-tabs__header {
      margin-bottom: 20px;
    }
    ::v-deep .el-tabs__item {
      font-size: 16px;
      padding: 0 20px;
      height: 40px;
      line-height: 40px;
    }
    ::v-deep .el-tabs__active-bar {
      height: 3px;
    }
  }
  // è¡¨æ ¼è¡ŒèƒŒæ™¯è‰²
  ::v-deep .el-table__body-wrapper {
    tr {
      background-color: #f9fbfe !important;
      &:hover {
        background-color: #e6f1ff !important;
      }
    }
  }
  // è¾¹æ¡†é¢œè‰²
  ::v-deep .el-table--border {
    border-color: #d9e8ff !important;
    td,
    th {
      border-color: #d9e8ff !important;
    }
  }
  // æ–‘马纹效果
  ::v-deep .el-table--striped .el-table__body tr.el-table__row--striped td {
    background-color: #f5f9ff !important;
  }
}
/* å±•开行样式 */
.el-table__expanded-cell {
  padding: 10px 0 !important;
  background: #f8f8f8;
}
.document {
  width: 100px;
  height: 50px;
}
.data-list {
  max-height: 800px;
  overflow-y: auto;
}
.documentf {
  display: flex;
  justify-content: flex-end;
}
.button-text {
  color: rgb(70, 204, 238);
}
.button-textck {
  color: rgb(39, 167, 67);
}
.button-textxg {
  color: rgb(35, 81, 233);
}
.button-textsc {
  color: rgb(235, 23, 23);
}
</style>
vue.config.js
@@ -36,9 +36,10 @@
      // 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`,
        // target:`http://35z1t16164.qicp.vip`,
        // target: `http://192.168.100.172:8095`,
ÊÐÒ».zip
Binary files differ