From fbb61549bf96e9e0910b676a5524b0760d29c4be Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期二, 07 四月 2026 15:16:54 +0800
Subject: [PATCH] 测试完成

---
 src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue | 1030 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1,030 insertions(+), 0 deletions(-)

diff --git a/src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue b/src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue
new file mode 100644
index 0000000..8de4434
--- /dev/null
+++ b/src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue
@@ -0,0 +1,1030 @@
+<template>
+  <div class="followup-statistics">
+    <div class="query-section">
+      <el-form
+        :model="queryParams"
+        ref="queryForm"
+        size="medium"
+        :inline="true"
+        label-width="100px"
+        class="query-form"
+      >
+        <el-form-item label="缁熻绫诲瀷" prop="statisticaltype">
+          <el-select
+            v-model="queryParams.statisticaltype"
+            placeholder="璇烽�夋嫨缁熻绫诲瀷"
+            clearable
+            @change="handleStatisticalTypeChange"
+          >
+            <el-option
+              v-for="item in Statisticallist"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+
+        <!-- 鐥呭尯閫夋嫨 -->
+        <el-form-item
+          v-if="queryParams.statisticaltype == 1"
+          label="鐥呭尯"
+          prop="leavehospitaldistrictcodes"
+        >
+          <el-select
+            v-model="queryParams.leavehospitaldistrictcodes"
+            placeholder="璇烽�夋嫨鐥呭尯"
+            multiple
+            collapse-tags
+            filterable
+            clearable
+            style="width: 300px"
+          >
+            <el-option
+              v-for="item in flatArrayhospit"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+
+        <!-- 绉戝閫夋嫨 -->
+        <el-form-item
+          v-if="queryParams.statisticaltype == 2"
+          label="绉戝"
+          prop="deptcodes"
+        >
+          <el-select
+            v-model="queryParams.deptcodes"
+            placeholder="璇烽�夋嫨绉戝"
+            multiple
+            collapse-tags
+            filterable
+            clearable
+            style="width: 300px"
+          >
+            <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="serviceType">
+          <el-select
+            v-model="queryParams.serviceType"
+            placeholder="璇烽�夋嫨鏈嶅姟绫诲瀷"
+            multiple
+            collapse-tags
+            clearable
+            style="width: 300px"
+          >
+            <el-option
+              v-for="item in options"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="闅忚鏃堕棿" prop="dateRange">
+          <el-date-picker
+            v-model="queryParams.dateRange"
+            type="daterange"
+            range-separator="鑷�"
+            start-placeholder="寮�濮嬫棩鏈�"
+            end-placeholder="缁撴潫鏃ユ湡"
+            value-format="yyyy-MM-dd"
+            :picker-options="pickerOptions"
+            style="width: 380px"
+          />
+        </el-form-item>
+
+        <el-form-item>
+          <el-button
+            type="primary"
+            icon="el-icon-search"
+            @click="handleQuery"
+            :loading="loading"
+          >
+            鎼滅储
+          </el-button>
+          <el-button icon="el-icon-refresh" @click="resetQuery">
+            閲嶇疆
+          </el-button>
+          <el-button
+            type="warning"
+            icon="el-icon-download"
+            @click="handleExport"
+            :disabled="!userList.length"
+          >
+            瀵煎嚭
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="table-section">
+      <el-table
+        v-loading="loading"
+        :data="userList"
+        :border="true"
+        style="width: 100%"
+        @selection-change="handleSelectionChange"
+        :row-key="getRowKey"
+      >
+        <!-- 鐥呭尯鍒� -->
+        <el-table-column
+          v-if="queryParams.statisticaltype == 1"
+          label="鍑洪櫌鐥呭尯"
+          align="center"
+          sortable
+          key="leavehospitaldistrictname"
+          prop="leavehospitaldistrictname"
+          :show-overflow-tooltip="true"
+          :sort-method="sortChineseNumber"
+          min-width="120"
+        />
+
+        <!-- 绉戝鍒� -->
+        <el-table-column
+          v-if="queryParams.statisticaltype == 2"
+          label="绉戝"
+          align="center"
+          key="deptname"
+          prop="deptname"
+          :show-overflow-tooltip="true"
+          min-width="120"
+        />
+
+        <el-table-column
+          label="鍑洪櫌浜烘"
+          align="center"
+          key="dischargeCount"
+          prop="dischargeCount"
+          min-width="100"
+        />
+
+        <el-table-column
+          label="鏃犻渶闅忚浜烘"
+          align="center"
+          key="nonFollowUp"
+          prop="nonFollowUp"
+          min-width="120"
+        />
+
+        <el-table-column
+          label="搴旈殢璁夸汉娆�"
+          align="center"
+          key="followUpNeeded"
+          prop="followUpNeeded"
+          min-width="120"
+        />
+
+        <el-table-column
+          label="闅忚鐜�"
+          align="center"
+          key="followUpRate"
+          prop="followUpRate"
+          min-width="100"
+        >
+          <template slot-scope="scope">
+            <span
+              v-if="
+                scope.row.followUpRate !== null &&
+                scope.row.followUpRate !== undefined
+              "
+            >
+              {{ scope.row.followUpRate }}
+            </span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column
+          label="鍙婃椂鐜�"
+          align="center"
+          key="rate"
+          prop="rate"
+          min-width="100"
+        >
+          <template slot-scope="scope">
+            <el-button
+              v-if="scope.row.rate !== null && scope.row.rate !== undefined"
+              type="text"
+              @click="handleSeedetails(scope.row)"
+            >
+              {{ formatPercent(scope.row.rate) }}
+            </el-button>
+            <span v-else style="color: #909399">-</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column
+          label="婊℃剰搴﹂鐩�婚噺"
+          align="center"
+          key="joyAllCount"
+          prop="joyAllCount"
+          min-width="140"
+        />
+
+        <el-table-column
+          label="婊℃剰搴﹀~鎶ラ噺"
+          align="center"
+          key="joyCount"
+          prop="joyCount"
+          min-width="120"
+        />
+
+        <el-table-column
+          label="瀹屾垚姣旂巼"
+          align="center"
+          key="joyTotal"
+          prop="joyTotal"
+          min-width="100"
+        >
+          <template slot-scope="scope">
+            <span
+              v-if="
+                scope.row.joyTotal !== null && scope.row.joyTotal !== undefined
+              "
+            >
+              {{ formatPercent(scope.row.joyTotal) }}
+            </span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="鎿嶄綔" align="center" fixed="right" width="120">
+          <template slot-scope="scope">
+            <el-button type="text" @click="getinfo(scope.row)">
+              <i class="el-icon-s-order" style="margin-right: 4px"></i>
+              鏌ョ湅璇︽儏
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- 鍒嗛〉 -->
+    <div class="pagination-section" v-if="total > 0">
+      <el-pagination
+        background
+        layout="total, sizes, prev, pager, next, jumper"
+        :current-page="queryParams.pageNum"
+        :page-size="queryParams.pageSize"
+        :page-sizes="[10, 20, 30, 50]"
+        :total="total"
+        @size-change="handleSizeChange"
+        @current-change="handlePageChange"
+      />
+    </div>
+
+    <!-- 鏈強鏃堕殢璁胯鎯呭璇濇 -->
+    <el-dialog
+      title="鏈強鏃堕殢璁挎偅鑰呮湇鍔�"
+      :visible.sync="SeedetailsVisible"
+      width="80%"
+      :close-on-click-modal="false"
+    >
+      <SeedetailsDialog
+        v-if="SeedetailsVisible"
+        :row-data="currentRow"
+        :query-params="queryParams"
+        @close="SeedetailsVisible = false"
+      />
+    </el-dialog>
+
+    <!-- 婊℃剰搴﹁鎯呭璇濇 -->
+    <el-dialog
+      :visible.sync="topicVisible"
+      width="60%"
+      :close-on-click-modal="false"
+    >
+      <template #title>
+        <div style="display: flex; align-items: center">
+          <i
+            class="el-icon-s-data"
+            style="margin-right: 8px; color: #409eff"
+          ></i>
+          <span>{{ topicvalue.name }}</span>
+          <span style="margin-left: 10px; color: #666; font-size: 14px"
+            >婊℃剰搴︽寚鏍囪鎯�</span
+          >
+        </div>
+      </template>
+      <topic-dialog
+        v-if="topicVisible"
+        :row-data="currentRow"
+        :topicList="topiclist"
+        :query-params="queryParams"
+        @close="topicVisible = false"
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  getSfStatisticsJoy,
+  getSfStatisticsJoyInfo,
+  selectTimelyRate,
+} from "@/api/system/user";
+import ExcelJS from "exceljs";
+import { saveAs } from "file-saver";
+import SeedetailsDialog from "./components/SeedetailsDialog.vue";
+import TopicDialog from "./components/TopicDialog.vue";
+
+export default {
+  name: "FollowupStatistics",
+  components: {
+    SeedetailsDialog,
+    TopicDialog,
+  },
+  data() {
+    return {
+      // 鏌ヨ鍙傛暟
+      queryParams: {
+        statisticaltype: 1,
+        leavehospitaldistrictcodes: ["all"],
+        deptcodes: [],
+        serviceType: [2],
+        dateRange: [],
+        pageNum: 1,
+        pageSize: 20,
+      },
+
+      // 缁熻绫诲瀷鍒楄〃
+      Statisticallist: [
+        { label: "鐥呭尯缁熻", value: 1 },
+        { label: "绉戝缁熻", value: 2 },
+      ],
+
+      // 鐥呭尯鍒楄〃
+      flatArrayhospit: [],
+
+      // 绉戝鍒楄〃
+      flatArraydept: [],
+
+      // 鏈嶅姟绫诲瀷閫夐」
+      options: [],
+
+      // 琛ㄦ牸鏁版嵁
+      userList: [],
+
+      // 鎬绘潯鏁�
+      total: 0,
+
+      // 鍔犺浇鐘舵��
+      loading: false,
+
+      // 閫変腑鐨勮
+      ids: [],
+      single: true,
+      multiple: true,
+
+      // 褰撳墠鎿嶄綔鐨勮
+      currentRow: null,
+
+      // 瀵硅瘽妗嗘樉绀烘帶鍒�
+      SeedetailsVisible: false,
+      topicVisible: false,
+
+      // 婊℃剰搴﹁鎯呮暟鎹�
+      topiclist: [],
+      topicvalue: {
+        name: "",
+      },
+
+      // 鏃ユ湡閫夋嫨鍣ㄩ�夐」
+      pickerOptions: {
+        shortcuts: [
+          {
+            text: "鏈�杩戜竴鍛�",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+              picker.$emit("pick", [start, end]);
+            },
+          },
+          {
+            text: "鏈�杩戜竴涓湀",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+              picker.$emit("pick", [start, end]);
+            },
+          },
+          {
+            text: "鏈�杩戜笁涓湀",
+            onClick(picker) {
+              const end = new Date();
+              const start = new Date();
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+              picker.$emit("pick", [start, end]);
+            },
+          },
+        ],
+        disabledDate(time) {
+          return time.getTime() > Date.now();
+        },
+      },
+    };
+  },
+
+  created() {
+    this.initData();
+  },
+
+  methods: {
+    // 鍒濆鍖栨暟鎹�
+    async initData() {
+      await this.getDeptTree();
+      await this.getList();
+    },
+
+    // 鑾峰彇绉戝鏍�
+    getDeptTree() {
+      // 鑾峰彇鏈嶅姟绫诲瀷
+      this.options = this.$store.getters.tasktypes || [];
+
+      // 鑾峰彇绉戝鍒楄〃
+      this.flatArraydept = (this.$store.getters.belongDepts || []).map(
+        (dept) => {
+          return {
+            label: dept.deptName,
+            value: dept.deptCode,
+          };
+        }
+      );
+
+      // 鑾峰彇鐥呭尯鍒楄〃
+      this.flatArrayhospit = (this.$store.getters.belongWards || []).map(
+        (ward) => {
+          return {
+            label: ward.districtName,
+            value: ward.districtCode,
+          };
+        }
+      );
+
+      // 娣诲姞鍏ㄩ儴閫夐」
+      this.flatArraydept.push({ label: "鍏ㄩ儴", value: "all" });
+      this.flatArrayhospit.push({ label: "鍏ㄩ儴", value: "all" });
+    },
+
+    // 鑾峰彇缁熻鍒楄〃
+    async getList() {
+      this.loading = true;
+      try {
+        // 澶勭悊鏌ヨ鍙傛暟
+        const params = {
+          configKey: "joyCount",
+          ...this.queryParams,
+        };
+
+        // 澶勭悊鏃ユ湡鑼冨洿
+        if (
+          this.queryParams.dateRange &&
+          this.queryParams.dateRange.length === 2
+        ) {
+          params.startTime = this.queryParams.dateRange[0];
+          params.endTime = this.queryParams.dateRange[1];
+        }
+
+        // 澶勭悊鐥呭尯/绉戝閫夋嫨
+        if (params.statisticaltype == 1) {
+          // 鐥呭尯缁熻
+          if (params.leavehospitaldistrictcodes.includes("all")) {
+            // 濡傛灉閫夋嫨浜�"鍏ㄩ儴"锛屽垯绉婚櫎"all"鍊�
+            params.leavehospitaldistrictcodes =
+              params.leavehospitaldistrictcodes.filter(
+                (item) => item !== "all"
+              );
+            // 濡傛灉闇�瑕佷紶鎵�鏈夌梾鍖轰唬鐮侊紝鍙互浠巗tore涓幏鍙�
+            params.leavehospitaldistrictcodes = (
+              this.$store.getters.belongWards || []
+            ).map((ward) => ward.districtCode);
+          }
+        } else if (params.statisticaltype == 2) {
+          // 绉戝缁熻
+          if (params.deptcodes.includes("all")) {
+            // 濡傛灉閫夋嫨浜�"鍏ㄩ儴"锛屽垯绉婚櫎"all"鍊�
+            params.deptcodes = params.deptcodes.filter(
+              (item) => item !== "all"
+            );
+            // 濡傛灉闇�瑕佷紶鎵�鏈夌瀹や唬鐮侊紝鍙互浠巗tore涓幏鍙�
+            params.deptcodes = (this.$store.getters.belongDepts || []).map(
+              (dept) => dept.deptCode
+            );
+          }
+        }
+
+        const response = await getSfStatisticsJoy(params);
+        this.userList = this.customSort(response.data) || [];
+        this.total = response.total || 0;
+      } catch (error) {
+        console.error("鑾峰彇缁熻鍒楄〃澶辫触:", error);
+        this.$message.error("鑾峰彇鏁版嵁澶辫触");
+      } finally {
+        this.loading = false;
+      }
+    },
+    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);
+
+      // 澶勭悊鏃犳硶瑙f瀽鐨勬儏鍐�
+      if (numA === -1 && numB === -1) {
+        return (a || "").localeCompare(b || "");
+      }
+      if (numA === -1) return 1;
+      if (numB === -1) return -1;
+
+      return numA - numB;
+    },
+    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;
+      });
+    },
+    // 澶勭悊缁熻绫诲瀷鍙樺寲
+    handleStatisticalTypeChange(value) {
+      if (value === 1) {
+        this.queryParams.deptcodes = [];
+      } else {
+        this.queryParams.leavehospitaldistrictcodes = [];
+      }
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+
+    // 澶勭悊鏌ヨ
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+
+    // 閲嶇疆鏌ヨ
+    resetQuery() {
+      this.queryParams = {
+        statisticaltype: 1,
+        leavehospitaldistrictcodes: [],
+        deptcodes: [],
+        serviceType: [2],
+        dateRange: [],
+        pageNum: 1,
+        pageSize: 20,
+      };
+      this.getList();
+    },
+
+    // 澶勭悊鍒嗛〉澶у皬鍙樺寲
+    handleSizeChange(size) {
+      this.queryParams.pageSize = size;
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+
+    // 澶勭悊椤电爜鍙樺寲
+    handlePageChange(page) {
+      this.queryParams.pageNum = page;
+      this.getList();
+    },
+
+    // 澶勭悊琛岄�夋嫨
+    handleSelectionChange(selection) {
+      this.ids = selection.map((item) => item.id);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+
+    // 鑾峰彇琛宬ey
+    getRowKey(row) {
+      return row.statisticaltype === 1
+        ? row.leavehospitaldistrictcode
+        : row.deptcode;
+    },
+
+    // 鏍煎紡鍖栫櫨鍒嗘瘮
+    formatPercent(value) {
+      if (value === null || value === undefined) return "-";
+      const num = parseFloat(value);
+      if (isNaN(num)) return "-";
+      return `${(num * 100).toFixed(2)}%`;
+    },
+
+    // 鏌ョ湅鏈強鏃堕殢璁胯鎯�
+    handleSeedetails(row) {
+      this.currentRow = row;
+      this.SeedetailsVisible = true;
+    },
+
+    // 鏌ョ湅婊℃剰搴﹁鎯�
+    async getinfo(row) {
+      this.currentRow = row;
+
+      try {
+        // 澶勭悊鏌ヨ鍙傛暟
+        const params = {
+          configKey: "joyCount",
+          ...this.queryParams,
+        };
+
+        // 澶勭悊鏃ユ湡鑼冨洿
+        if (
+          this.queryParams.dateRange &&
+          this.queryParams.dateRange.length === 2
+        ) {
+          params.startTime = this.queryParams.dateRange[0];
+          params.endTime = this.queryParams.dateRange[1];
+        }
+
+        if (this.queryParams.statisticaltype == 1) {
+          this.topicvalue.name = row.leavehospitaldistrictname;
+          params.leavehospitaldistrictcodes = [row.leavehospitaldistrictcode];
+        } else {
+          this.topicvalue.name = row.deptname;
+          params.deptcodes = [row.deptcode];
+        }
+
+        const response = await getSfStatisticsJoyInfo(params);
+        this.topiclist = response.data || [];
+      this.topicVisible = true;
+
+      } catch (error) {
+        console.error("鑾峰彇婊℃剰搴﹁鎯呭け璐�:", error);
+        this.$message.error("鑾峰彇璇︽儏澶辫触");
+      }
+    },
+
+    // 瀵煎嚭鏁版嵁
+    async handleExport() {
+      if (!this.userList.length) {
+        this.$message.warning("娌℃湁鏁版嵁鍙鍑�");
+        return;
+      }
+
+      try {
+        this.loading = true;
+
+        // 鏋勫缓鏃ユ湡鑼冨洿瀛楃涓�
+        let dateRangeString = "";
+        let sheetNameSuffix = "";
+
+        if (
+          this.queryParams.dateRange &&
+          this.queryParams.dateRange.length === 2
+        ) {
+          const startDateFormatted = this.queryParams.dateRange[0];
+          const endDateFormatted = this.queryParams.dateRange[1];
+          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}`;
+
+        // 鍒涘缓Excel宸ヤ綔绨�
+        const workbook = new ExcelJS.Workbook();
+        const worksheet = workbook.addWorksheet(worksheetName);
+
+        // 瀹氫箟鏍峰紡
+        const titleStyle = {
+          font: { name: "寰蒋闆呴粦", size: 16, bold: true },
+          fill: {
+            type: "pattern",
+            pattern: "solid",
+            fgColor: { argb: "FFE6F3FF" },
+          },
+          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 headerStyle = {
+          font: { name: "寰蒋闆呴粦", size: 11, bold: true },
+          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" } },
+          },
+        };
+
+        const cellStyle = {
+          font: { name: "瀹嬩綋", size: 10 },
+          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, 10);
+        const titleCell = worksheet.getCell(1, 1);
+        titleCell.value = `闅忚缁熻琛紙${sheetNameSuffix}锛塦;
+        titleCell.style = titleStyle;
+        worksheet.getRow(1).height = 35;
+
+        // 娣诲姞琛ㄥご
+        const headers = [
+          this.queryParams.statisticaltype == 1 ? "鍑洪櫌鐥呭尯" : "绉戝",
+          "鍑洪櫌浜烘",
+          "鏃犻渶闅忚浜烘",
+          "搴旈殢璁夸汉娆�",
+          "闅忚鐜�",
+          "鍙婃椂鐜�",
+          "婊℃剰搴﹂鐩�婚噺",
+          "婊℃剰搴﹀~鎶ラ噺",
+          "瀹屾垚姣旂巼",
+        ];
+
+        const headerRow = worksheet.addRow(headers);
+        headerRow.eachCell((cell) => {
+          cell.style = headerStyle;
+        });
+        headerRow.height = 25;
+
+        // 娣诲姞鏁版嵁琛�
+        this.userList.forEach((item) => {
+          const dataRow = worksheet.addRow([
+            this.queryParams.statisticaltype == 1
+              ? item.leavehospitaldistrictname
+              : item.deptname,
+            item.dischargeCount || 0,
+            item.nonFollowUp || 0,
+            item.followUpNeeded || 0,
+            item.followUpRate || "0%",
+            item.rate ? this.formatPercent(item.rate) : "0%",
+            item.joyAllCount || 0,
+            item.joyCount || 0,
+            item.joyTotal ? this.formatPercent(item.joyTotal) : "0%",
+          ]);
+
+          dataRow.eachCell((cell) => {
+            cell.style = cellStyle;
+          });
+          dataRow.height = 22;
+        });
+
+        // 璁剧疆鍒楀
+        worksheet.columns = [
+          { width: 20 },
+          { width: 12 },
+          { width: 12 },
+          { width: 12 },
+          { width: 12 },
+          { width: 12 },
+          { width: 15 },
+          { width: 15 },
+          { width: 12 },
+        ];
+
+        // 鐢熸垚骞朵笅杞芥枃浠�
+        const buffer = await workbook.xlsx.writeBuffer();
+        const blob = new Blob([buffer], {
+          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+        });
+
+        saveAs(blob, excelName);
+        this.$message.success("瀵煎嚭鎴愬姛");
+      } catch (error) {
+        console.error("瀵煎嚭澶辫触:", error);
+        this.$message.error(`瀵煎嚭澶辫触: ${error.message}`);
+      } finally {
+        this.loading = false;
+      }
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.followup-statistics {
+  .query-section {
+    background: #fff;
+    padding: 20px;
+    border-radius: 4px;
+    margin-bottom: 20px;
+
+    .query-form {
+      display: flex;
+      flex-wrap: wrap;
+
+      ::v-deep .el-form-item {
+        margin-bottom: 20px;
+
+        &:not(:last-child) {
+          margin-right: 20px;
+        }
+      }
+    }
+  }
+
+  .table-section {
+    background: #fff;
+    padding: 20px;
+    border-radius: 4px;
+    margin-bottom: 20px;
+
+    ::v-deep .el-table {
+      th {
+        background-color: #f8f9fa;
+        font-weight: 600;
+        color: #333;
+      }
+    }
+  }
+
+  .pagination-section {
+    display: flex;
+    justify-content: flex-end;
+    background: #fff;
+    padding: 20px;
+    border-radius: 4px;
+  }
+}
+</style>

--
Gitblit v1.9.3