WXL (wul)
5 天以前 da65acd978c10cf75c91334ee1a768e7d82884f7
测试完成
已删除1个文件
已修改27个文件
已添加7个文件
9060 ■■■■■ 文件已修改
11.3.zip 补丁 | 查看 | 原始文档 | blame | 历史
src/api/patient/record.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue 417 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/components/TopicDialog.vue 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/components/TopicPatientListDialog.vue 322 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/visitStatistics.vue 324 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/discharge/index copy.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/discharge/index.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/operationAgain/index.vue 1933 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/record/detailpage/index.vue 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/record/index.vue 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/apiAdapter.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/childHealth/detail.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/generateConfig/index.vue 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/maternalHealth/detail.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/follow/index.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/patient/AwaitingAdmission.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/patient/behospitalized.vue 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/patient/hospital.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/patient/outpatient.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/propaganda/Missioncreation.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/propaganda/QuestionnaireTask.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/propaganda/particty.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/CombinedFollowUp.vue 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/FirstFollowUp.vue 848 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/SecondFollowUp.vue 550 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/index copy.vue 3708 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/index.vue 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/index.vue 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
随访适用丽水市一11.3.zip 补丁 | 查看 | 原始文档 | blame | 历史
随访适用丽水市一11.4.zip 补丁 | 查看 | 原始文档 | blame | 历史
随访适用丽水市一11.5 (2).zip 补丁 | 查看 | 原始文档 | blame | 历史
随访适用丽水市一11.5.zip 补丁 | 查看 | 原始文档 | blame | 历史
11.3.zip
Binary files differ
src/api/patient/record.js
@@ -42,3 +42,11 @@
    data: data
  })
};
// è¯­éŸ³æœåŠ¡è½¬äººå·¥ï¼šåˆ é™¤æ‚£è€…æœªå®Œæˆéšè®¿ä»»åŠ¡å¹¶é‡æ–°ç”Ÿæˆäººå·¥éšè®¿ä»»åŠ¡
export function addManualVisit(data) {
  return request({
    url: '/smartor/patinhosp/addManualVisit',
    method: 'post',
    data: data
  })
};
src/api/system/user.js
@@ -126,6 +126,14 @@
    data: data,
  });
}
// éšè®¿é¢˜ç›®é€‰é¡¹ä¸‹é’»æ‚£è€…服务列表
export function getSfStatisticsCountDetailsHyperLink(data) {
  return request({
    url: "/smartor/serviceSubtask/getSfStatisticsCountDetailsHyperLink",
    method: "post",
    data: data,
  });
}
// æœªåŠæ—¶æŸ¥è¯¢
export function selectTimelyRate(data) {
  return request({
src/store/modules/user.js
@@ -256,6 +256,12 @@
              localStorage.setItem("deptCode", "");
              localStorage.setItem("YongHuID", "");
              localStorage.setItem("YongHuXM", "");
            } else if (orgid == "1" && campusid == 8) {
              localStorage.setItem("orgname", "第一人民医院城东院区");
              localStorage.setItem("ZuHuID", "");
              localStorage.setItem("deptCode", "");
              localStorage.setItem("YongHuID", "");
              localStorage.setItem("YongHuXM", "");
            } else if (orgid == "47246116333112211A1001") {
              localStorage.setItem("orgname", "缙云县人民医院");
              localStorage.setItem("ZuHuID", "1429338802177000010");
src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue
@@ -149,6 +149,8 @@
        style="width: 100%"
        @selection-change="handleSelectionChange"
        :row-key="getRowKey"
        show-summary
        :summary-method="getSummaries"
      >
        <!-- ç—…区统计:在院病区 + ç¦»é™¢ç—…区 -->
        <el-table-column
@@ -1291,6 +1293,92 @@
      return `${(num * 100).toFixed(2)}%`;
    },
    // è¡¨æ ¼åˆè®¡è¡Œ
    getSummaries(param) {
      const { columns, data } = param;
      const sums = [];
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "合计";
          return;
        }
        const prop = column.property;
        // æ•°å€¼åˆ—求和
        if (
          [
            "dischargeCount",
            "nonFollowUp",
            "followUpNeeded",
            "joyAllCount",
            "joyCount",
          ].includes(prop)
        ) {
          sums[index] = data.reduce(
            (sum, item) => sum + (Number(item[prop]) || 0),
            0
          );
          return;
        }
        // å®Œæˆæ¯”率:满意度填报量 / æ»¡æ„åº¦é¢˜ç›®æ€»é‡
        if (prop === "joyTotal") {
          const totalJoyAllCount = data.reduce(
            (sum, item) => sum + (Number(item.joyAllCount) || 0),
            0
          );
          const totalJoyCount = data.reduce(
            (sum, item) => sum + (Number(item.joyCount) || 0),
            0
          );
          sums[index] =
            totalJoyAllCount > 0
              ? ((totalJoyCount / totalJoyAllCount) * 100).toFixed(2) + "%"
              : "0.00%";
          return;
        }
        // ç™¾åˆ†æ¯”列(随访率/及时率):取平均值(0 å€¼ä¹Ÿå‚与平均)
        if (prop === "followUpRate" || prop === "rate") {
          const percentageValues = data
            .map((item) => {
              const value = item[prop];
              if (
                value === null ||
                value === undefined ||
                value === "" ||
                value === "-"
              ) {
                return null;
              }
              if (typeof value === "string" && value.includes("%")) {
                const num = parseFloat(value.replace("%", "")) / 100;
                return isNaN(num) ? null : num;
              }
              const num = parseFloat(value);
              return isNaN(num) ? null : num;
            })
            .filter((value) => value !== null);
          sums[index] =
            percentageValues.length > 0
              ? (
                  (percentageValues.reduce((sum, value) => sum + value, 0) /
                    percentageValues.length) *
                  100
                ).toFixed(2) + "%"
              : "0.00%";
          return;
        }
        sums[index] = "";
      });
      return sums;
    },
    // æŸ¥çœ‹æœªåŠæ—¶éšè®¿è¯¦æƒ…
    handleSeedetails(row) {
      this.currentRow = row;
@@ -1353,6 +1441,113 @@
        console.error("获取满意度详情失败:", error);
        this.$message.error("获取详情失败");
      }
    },
    // èŽ·å–æŒ‡å®šç—…åŒº/科室的题目详情
    async fetchTopicDetail(row) {
      const params = {
        configKey: "joyCount",
        ...this.queryParams,
      };
      // å¯¼å‡ºéœ€è¦è¯¥ç—…区/科室的全部题目,覆盖主表分页参数
      params.pageNum = 1;
      params.pageSize = 1000;
      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) {
        params.leavehospitaldistrictcodes = [row.leavehospitaldistrictcode];
      } else {
        params.deptcodes = [row.deptcode];
      }
      const response = await getSfStatisticsJoyInfo(params);
      return response.data || {};
    },
    // ä¸»è¡¨å¯¼å‡ºè¡Œæ•°æ®
    getMainExportRow(item) {
      return [
        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%",
      ];
    },
    // è®¡ç®—文本显示宽度(中文按 2、英文/数字按 1)
    textWidth(str) {
      if (!str) return 0;
      let width = 0;
      for (const ch of String(str)) {
        width += ch.charCodeAt(0) > 255 ? 2 : 1;
      }
      return width;
    },
    // ä¸½æ°´å¸‚中医院:拉取所有科室题目明细,扁平化为「题目选项」列结构
    async buildLishuiTopicColumns() {
      const topicMap = new Map();
      const rowQuantities = [];
      for (const item of this.userList) {
        const topiclist = await this.fetchTopicDetail(item);
        const qMap = new Map();
        if (topiclist && Object.keys(topiclist).length) {
          Object.keys(topiclist).forEach((key) => {
            const topic = topiclist[key];
            if (!topic || !topic.scriptContent) return;
            const tKey =
              topic.scriptid != null && topic.scriptid !== ""
                ? String(topic.scriptid)
                : topic.scriptContent;
            if (!topicMap.has(tKey)) {
              topicMap.set(tKey, {
                key: tKey,
                scriptContent: topic.scriptContent,
                options: [],
                optionSet: new Set(),
              });
            }
            const t = topicMap.get(tKey);
            const topicQ = new Map();
            (topic.details || []).forEach((detail) => {
              if (!detail || !detail.optionText) return;
              if (!t.optionSet.has(detail.optionText)) {
                t.optionSet.add(detail.optionText);
                t.options.push(detail.optionText);
              }
              topicQ.set(detail.optionText, Number(detail.chosenQuantity) || 0);
            });
            qMap.set(tKey, topicQ);
          });
        }
        rowQuantities.push(qMap);
      }
      const topics = Array.from(topicMap.values());
      // æ²¡æœ‰é€‰é¡¹çš„题目默认填充一个空选项列,避免表头/数据错位
      topics.forEach((t) => {
        if (!t.options.length) {
          t.options = [""];
        }
      });
      return { topics, rowQuantities };
    },
    // å¯¼å‡ºæ•°æ®
@@ -1446,7 +1641,11 @@
            pattern: "solid",
            fgColor: { argb: "FFF5F7FA" },
          },
          alignment: { vertical: "middle", horizontal: "center" },
          alignment: {
            vertical: "middle",
            horizontal: "center",
            wrapText: true,
          },
          border: {
            top: { style: "thin", color: { argb: "FFD0D0D0" } },
            left: { style: "thin", color: { argb: "FFD0D0D0" } },
@@ -1466,74 +1665,182 @@
          },
        };
        // è¡¨å¤´æŒ‰é¡µé¢åˆ—动态生成:科室/病区统计列不同,「及时率」只有景宁院区页面才显示
        const isWardStatistics = this.queryParams.statisticaltype == 1;
        const showTimelyRate = this.orgname === "景宁畲族自治县人民医院";
        const headers = [
          isWardStatistics ? "在院病区" : "在院科室",
          isWardStatistics ? "离院病区" : "离院科室",
        // ä¸½æ°´å¸‚中医院:预先拉取题目明细,扁平化为「题目选项」列
        let lishuiTopics = [];
        let lishuiRowQuantities = [];
        if (isLishuiHospital) {
          const built = await this.buildLishuiTopicColumns();
          lishuiTopics = built.topics;
          lishuiRowQuantities = built.rowQuantities;
        }
        // ä¸»è¡¨åˆ—
        const mainHeaders = [
          this.queryParams.statisticaltype == 1 ? "出院病区" : "科室",
          "出院人次",
          "无需随访人次",
          "应随访人次",
          "随访率",
          "及时率",
          "满意度题目总量",
          "满意度填报量",
          "完成比率",
        ];
        if (showTimelyRate) {
          headers.push("及时率");
        }
        headers.push("满意度题目总量", "满意度填报量", "完成比率");
        const colCount = headers.length;
        const mainColumnCount = mainHeaders.length;
        // æ€»åˆ—数(丽水市中医院在末尾追加题目选项列)
        const topicColumnCount = lishuiTopics.reduce(
          (sum, t) => sum + (t.options.length || 1),
          0
        );
        const totalColumns = mainColumnCount + topicColumnCount;
        // æ·»åŠ æ€»æ ‡é¢˜
        worksheet.mergeCells(1, 1, 1, colCount);
        worksheet.mergeCells(1, 1, 1, totalColumns);
        const titleCell = worksheet.getCell(1, 1);
        titleCell.value = `满意度随访统计表(${sheetNameSuffix})`;
        titleCell.style = titleStyle;
        worksheet.getRow(1).height = 35;
        const headerRow = worksheet.addRow(headers);
        headerRow.eachCell((cell) => {
          cell.style = headerStyle;
        });
        headerRow.height = 25;
        // æ·»åŠ æ•°æ®è¡Œï¼ˆç©ºå€¼ç»Ÿä¸€æ˜¾ç¤ºâ€œ-”,与页面保持一致)
        const dash = (v, formatter) => {
          if (v === null || v === undefined || v === "") return "-";
          return formatter ? formatter(v) : v;
        };
        this.userList.forEach((item) => {
          const rowData = [
            isWardStatistics
              ? item.hospitaldistrictname || "-"
              : item.hospitaldeptname || "-",
            isWardStatistics
              ? item.leavehospitaldistrictname || "-"
              : item.deptname || "-",
            item.dischargeCount || 0,
            item.nonFollowUp || 0,
            item.followUpNeeded || 0,
            dash(item.followUpRate),
          ];
          if (showTimelyRate) {
            rowData.push(dash(item.rate, (v) => this.formatPercent(v)));
          }
          rowData.push(
            item.joyAllCount || 0,
            item.joyCount || 0,
            dash(item.joyTotal, (v) => this.formatPercent(v))
          );
          const dataRow = worksheet.addRow(rowData);
          dataRow.eachCell((cell) => {
            cell.style = cellStyle;
        if (isLishuiHospital && lishuiTopics.length) {
          // è®¡ç®—每个题目的选项列宽(选项文字自适应,题干过长时摊宽列、必要时换行)
          const topicColumns = lishuiTopics.map((topic) => {
            const n = topic.options.length || 1;
            const baseWidths = topic.options.map((opt) =>
              opt === "" ? 10 : Math.max(10, this.textWidth(opt) + 4)
            );
            const sumWidth = baseWidths.reduce((s, w) => s + w, 0);
            const topicWidth = this.textWidth(topic.scriptContent) + 4;
            const groupWidth = Math.max(sumWidth, topicWidth);
            const extra = (groupWidth - sumWidth) / n;
            const widths = baseWidths.map((w) => w + extra);
            const lines = Math.max(1, Math.ceil(topicWidth / groupWidth));
            return { widths, lines };
          });
          dataRow.height = 22;
        });
          const maxHeaderLines = topicColumns.reduce(
            (m, t) => Math.max(m, t.lines),
            1
          );
        // è®¾ç½®åˆ—宽(跟列数一致)
        worksheet.columns = headers.map((h, i) => ({
          width: i < 2 ? 20 : h.length > 6 ? 15 : 12,
        }));
          // ä¸¤è¡Œè¡¨å¤´ï¼šç¬¬ä¸€è¡Œ = ä¸»è¡¨åˆ— + é¢˜ç›®åï¼›ç¬¬äºŒè¡Œ = é€‰é¡¹å
          const headerRow1 = worksheet.addRow([]);
          const headerRow2 = worksheet.addRow([]);
          mainHeaders.forEach((h, i) => {
            const col = i + 1;
            headerRow1.getCell(col).value = h;
            headerRow1.getCell(col).style = headerStyle;
            headerRow2.getCell(col).style = headerStyle;
            worksheet.mergeCells(
              headerRow1.number,
              col,
              headerRow2.number,
              col
            );
          });
          let colIndex = mainColumnCount + 1;
          lishuiTopics.forEach((topic) => {
            const optCount = topic.options.length || 1;
            for (let k = 0; k < optCount; k++) {
              headerRow1.getCell(colIndex + k).style = headerStyle;
            }
            headerRow1.getCell(colIndex).value = topic.scriptContent;
            if (optCount > 1) {
              worksheet.mergeCells(
                headerRow1.number,
                colIndex,
                headerRow1.number,
                colIndex + optCount - 1
              );
            }
            topic.options.forEach((opt, k) => {
              headerRow2.getCell(colIndex + k).value = opt;
              headerRow2.getCell(colIndex + k).style = headerStyle;
            });
            colIndex += optCount;
          });
          // è¡¨å¤´é«˜åº¦ï¼šé¢˜å¹²æ¢è¡Œæ—¶æŒ‰æœ€é•¿é¢˜ç›®é¢„ç•™
          headerRow1.height = Math.max(30, 15 * maxHeaderLines + 8);
          headerRow2.height = 25;
          // æ•°æ®è¡Œ
          this.userList.forEach((item, idx) => {
            const row = worksheet.addRow([]);
            const main = this.getMainExportRow(item);
            main.forEach((v, i) => {
              const cell = row.getCell(i + 1);
              cell.value = v;
              cell.style = cellStyle;
            });
            const qMap = lishuiRowQuantities[idx] || new Map();
            let cIndex = mainColumnCount + 1;
            for (const topic of lishuiTopics) {
              const topicQ = qMap.get(topic.key);
              topic.options.forEach((opt) => {
                const cell = row.getCell(cIndex);
                if (opt === "") {
                  // ç©ºé€‰é¡¹å ä½åˆ—不填数字
                  cell.value = "";
                } else {
                  const val = topicQ ? topicQ.get(opt) : undefined;
                  cell.value = val === undefined || val === null ? 0 : val;
                }
                cell.style = cellStyle;
                cIndex++;
              });
            }
            row.height = 22;
          });
          // åˆ—宽:主表列 + é¢˜ç›®é€‰é¡¹åˆ—
          const columns = [
            { width: 20 },
            { width: 12 },
            { width: 12 },
            { width: 12 },
            { width: 12 },
            { width: 12 },
            { width: 15 },
            { width: 15 },
            { width: 12 },
          ];
          lishuiTopics.forEach((topic, ti) => {
            topicColumns[ti].widths.forEach((w) => {
              columns.push({ width: w });
            });
          });
          worksheet.columns = columns;
        } else {
          // å…¶ä»–医院:单行表头
          const headerRow = worksheet.addRow(mainHeaders);
          headerRow.eachCell((cell) => {
            cell.style = headerStyle;
          });
          headerRow.height = 25;
          this.userList.forEach((item) => {
            const dataRow = worksheet.addRow(this.getMainExportRow(item));
            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();
src/views/Satisfaction/sfstatistics/components/components/TopicDialog.vue
@@ -40,7 +40,14 @@
                min-width="120"
              >
                <template slot-scope="{ row }">
                  {{ row.chosenQuantity || 0 }}
                  <el-button
                    v-if="row.chosenQuantity"
                    type="text"
                    @click="handleOptionClick(item, row)"
                  >
                    {{ row.chosenQuantity }}
                  </el-button>
                  <span v-else>0</span>
                </template>
              </el-table-column>
              <el-table-column
@@ -83,15 +90,26 @@
    >
      <el-button @click="handleClose">关 é—­</el-button>
    </div>
    <!-- é€‰é¡¹ä¸‹é’»ï¼šæ‚£è€…服务列表 -->
    <TopicPatientListDialog
      :visible.sync="patientListVisible"
      :title="patientListTitle"
      :drill-params="patientDrillParams"
    />
  </div>
</template>
<script>
import ExcelJS from "exceljs";
import { saveAs } from "file-saver";
import TopicPatientListDialog from "./TopicPatientListDialog.vue";
export default {
  name: "TopicDialog",
  components: {
    TopicPatientListDialog,
  },
  props: {
    rowData: {
      type: Object,
@@ -113,12 +131,18 @@
    return {
      processedTopicList: [],
      orgname: "", // æ–°å¢žï¼šåŒ»é™¢åç§°
      patientListVisible: false,
      patientListTitle: "患者服务列表",
      patientDrillParams: {},
    };
  },
  computed: {
    configTitle() {
      const key = this.queryParams?.configKey;
      return key === "returnVisitCount" ? "复诊通知" : "满意度";
      return this.topType == 1 ? "复诊通知" : "满意度";
    },
    // ä¸‹é’»æŽ¥å£éœ€è¦çš„ configKey:1 å¤è¯Šé€šçŸ¥ï¼Œ2 æ»¡æ„åº¦
    configKey() {
      return this.topType == 1 ? "returnVisitCount" : "joyCount";
    },
  },
  created() {
@@ -158,6 +182,49 @@
      this.processedTopicList = result;
    },
    // ç‚¹å‡»é€‰é¡¹â€œé€‰æ‹©äººæ•°â€ä¸‹é’»åˆ°æ‚£è€…服务列表
    handleOptionClick(topicItem, detail) {
      if (!detail || !detail.optionText) return;
      const params = {
        scriptid: topicItem.scriptid,
        scriptContent: topicItem.scriptContent,
        subtaskType: topicItem.subtaskType,
        optionText: detail.optionText,
        statisticaltype: this.queryParams.statisticaltype,
        serviceType: this.queryParams.serviceType || [],
        configKey: this.configKey,
      };
      // æ—¶é—´èŒƒå›´
      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) {
        // æŒ‰ç—…区
        params.deptcode = this.rowData.leavehospitaldistrictcode;
        params.leavehospitaldistrictcode =
          this.rowData.leavehospitaldistrictcode;
        params.leavehospitaldistrictcodes = [
          this.rowData.leavehospitaldistrictcode,
        ];
      } else {
        // æŒ‰ç§‘室
        params.deptcode = this.rowData.deptcode;
        params.deptcodes = [this.rowData.deptcode];
      }
      this.patientDrillParams = params;
      this.patientListTitle = `「${detail.optionText}」患者服务列表`;
      this.patientListVisible = true;
    },
    // æ ¼å¼åŒ–日期范围字符串(与主页面一致)
    formatDateRangeForExport() {
      let dateRangeString = "";
src/views/Satisfaction/sfstatistics/components/components/TopicPatientListDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,322 @@
<template>
  <el-dialog
    :title="title"
    :visible.sync="dialogVisible"
    width="80%"
    :close-on-click-modal="false"
    append-to-body
    @close="handleClose"
  >
    <div class="topic-patient-dialog">
      <div class="query-bar">
        <span class="label">患者姓名查询:</span>
        <el-input
          v-model="searchName"
          placeholder="请输入患者姓名进行筛选"
          clearable
          style="width: 300px"
          @input="handleSearch"
          @clear="handleSearch"
        />
        <span class="count">共 {{ filteredList.length }} æ¡è®°å½•</span>
      </div>
      <el-table
        v-loading="loading"
        :data="filteredList"
        style="width: 100%"
        :border="true"
        max-height="560"
      >
        <el-table-column
          prop="sendname"
          label="姓名"
          align="center"
          width="100"
        />
        <el-table-column
          prop="phone"
          label="联系电话"
          align="center"
          width="130"
        />
        <el-table-column
          prop="taskName"
          label="任务名称"
          align="center"
          width="200"
          show-overflow-tooltip
        />
        <el-table-column
          prop="sendstate"
          label="任务状态"
          align="center"
          width="110"
        >
          <template slot-scope="{ row }">
            <el-tag :type="getStateTagType(row.sendstate)" size="small">
              {{ getStateText(row.sendstate) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          prop="visitTime"
          label="应随访时间"
          align="center"
          width="170"
          show-overflow-tooltip
        />
        <el-table-column
          prop="finishtime"
          label="随访完成时间"
          align="center"
          width="170"
          show-overflow-tooltip
        />
        <el-table-column
          prop="endtime"
          label="出院日期"
          align="center"
          width="120"
        >
          <template slot-scope="{ row }">
            <span>{{ formatTime(row.endtime) }}</span>
          </template>
        </el-table-column>
        <el-table-column
          prop="nurseName"
          label="责任护士"
          align="center"
          width="110"
        />
        <el-table-column
          prop="drname"
          label="主治医生"
          align="center"
          width="110"
        />
        <el-table-column
          prop="excep"
          label="结果状态"
          align="center"
          width="110"
        >
          <template slot-scope="{ row }">
            <dict-tag :options="dict.type.sys_yujing" :value="row.excep" />
          </template>
        </el-table-column>
        <el-table-column
          prop="suggest"
          label="处理意见"
          align="center"
          width="110"
        >
          <template slot-scope="{ row }">
            <dict-tag :options="dict.type.sys_suggest" :value="row.suggest" />
          </template>
        </el-table-column>
        <el-table-column
          prop="templatename"
          label="服务模板"
          align="center"
          width="160"
          show-overflow-tooltip
        />
        <el-table-column
          prop="remark"
          label="服务记录"
          align="center"
          width="160"
          show-overflow-tooltip
        />
        <el-table-column
          label="操作"
          fixed="right"
          align="center"
          width="90"
        >
          <template slot-scope="{ row }">
            <el-button type="text" size="small" @click="handleViewDetail(row)">
              <i class="el-icon-view"></i> æŸ¥çœ‹
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <div v-if="hasMore" class="load-more">
        <el-button type="text" :loading="loading" @click="loadMore">
          åŠ è½½æ›´å¤š
        </el-button>
      </div>
    </div>
  </el-dialog>
</template>
<script>
import { getSfStatisticsCountDetailsHyperLink } from "@/api/system/user";
export default {
  name: "TopicPatientListDialog",
  dicts: ["sys_yujing", "sys_suggest"],
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
    title: {
      type: String,
      default: "患者服务列表",
    },
    // ä¸‹é’»è¯·æ±‚参数(不含分页),由父组件构建
    drillParams: {
      type: Object,
      default: () => ({}),
    },
  },
  data() {
    return {
      pageSize: 200,
      offset: 0,
      hasMore: false,
      loading: false,
      allList: [],
      searchName: "",
    };
  },
  computed: {
    dialogVisible: {
      get() {
        return this.visible;
      },
      set(val) {
        this.$emit("update:visible", val);
      },
    },
    filteredList() {
      const keyword = this.searchName.trim();
      if (!keyword) return this.allList;
      return this.allList.filter(
        (item) => item.sendname && item.sendname.indexOf(keyword) !== -1
      );
    },
  },
  watch: {
    visible(newVal) {
      if (newVal) {
        this.resetAndLoad();
      }
    },
  },
  methods: {
    resetAndLoad() {
      this.offset = 0;
      this.allList = [];
      this.searchName = "";
      this.hasMore = false;
      this.fetchPage(false);
    },
    async fetchPage(isLoadMore) {
      this.loading = true;
      try {
        const params = {
          ...this.drillParams,
          pageNum: this.offset,
          pageSize: this.pageSize,
        };
        const response = await getSfStatisticsCountDetailsHyperLink(params);
        const batch = response.data || [];
        this.allList = isLoadMore ? this.allList.concat(batch) : batch;
        this.offset += batch.length;
        this.hasMore = batch.length >= this.pageSize;
      } catch (error) {
        console.error("获取患者服务列表失败:", error);
        this.$message.error("获取患者服务列表失败");
      } finally {
        this.loading = false;
      }
    },
    loadMore() {
      this.fetchPage(true);
    },
    handleSearch() {
      // å§“名筛选为前端本地过滤,无需重新请求
    },
    getStateTagType(state) {
      const map = {
        1: "primary",
        2: "primary",
        3: "success",
        4: "info",
        5: "danger",
        6: "success",
        7: "danger",
      };
      return map[state] || "info";
    },
    getStateText(state) {
      const map = {
        1: "表单已领取",
        2: "待随访",
        3: "表单已发送",
        4: "不执行",
        5: "发送失败",
        6: "已完成",
        7: "超时",
      };
      return map[state] || "未知状态";
    },
    formatTime(time) {
      if (!time) return "-";
      return time;
    },
    handleViewDetail(row) {
      this.$emit("update:visible", false);
      let type = "";
      if (row.preachform && row.preachform.includes("3")) {
        type = 1;
      }
      setTimeout(() => {
        this.$router.push({
          path: "/followvisit/record/detailpage/",
          query: {
            taskid: row.taskid,
            patid: row.patid,
            id: row.id,
            Voicetype: type,
          },
        });
      }, 300);
    },
    handleClose() {
      this.$emit("close");
    },
  },
};
</script>
<style lang="scss" scoped>
.topic-patient-dialog {
  .query-bar {
    display: flex;
    align-items: center;
    margin-bottom: 16px;
    .label {
      margin-right: 10px;
      font-weight: bold;
    }
    .count {
      margin-left: 16px;
      color: rgb(35, 81, 233);
      font-size: 15px;
    }
  }
  .load-more {
    text-align: center;
    padding-top: 12px;
  }
}
</style>
src/views/Satisfaction/sfstatistics/components/visitStatistics.vue
@@ -383,6 +383,7 @@
      // åŠ è½½çŠ¶æ€
      loading: false,
      orgname: "",
      // é€‰ä¸­çš„行
      ids: [],
@@ -441,6 +442,7 @@
  },
  created() {
    this.orgname = localStorage.getItem("orgname");
    this.initData();
  },
@@ -824,6 +826,113 @@
      }
    },
    // èŽ·å–æŒ‡å®šç—…åŒº/科室的题目详情
    async fetchTopicDetail(row) {
      const params = {
        configKey: "returnVisitCount",
        ...this.queryParams,
      };
      // å¯¼å‡ºéœ€è¦è¯¥ç—…区/科室的全部题目,覆盖主表分页参数
      params.pageNum = 1;
      params.pageSize = 1000;
      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) {
        params.leavehospitaldistrictcodes = [row.leavehospitaldistrictcode];
      } else {
        params.deptcodes = [row.deptcode];
      }
      const response = await getSfStatisticsJoyInfo(params);
      return response.data || {};
    },
    // ä¸»è¡¨å¯¼å‡ºè¡Œæ•°æ®
    getMainExportRow(item) {
      return [
        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%",
      ];
    },
    // è®¡ç®—文本显示宽度(中文按 2、英文/数字按 1)
    textWidth(str) {
      if (!str) return 0;
      let width = 0;
      for (const ch of String(str)) {
        width += ch.charCodeAt(0) > 255 ? 2 : 1;
      }
      return width;
    },
    // ä¸½æ°´å¸‚中医院:拉取所有科室题目明细,扁平化为「题目选项」列结构
    async buildLishuiTopicColumns() {
      const topicMap = new Map();
      const rowQuantities = [];
      for (const item of this.userList) {
        const topiclist = await this.fetchTopicDetail(item);
        const qMap = new Map();
        if (topiclist && Object.keys(topiclist).length) {
          Object.keys(topiclist).forEach((key) => {
            const topic = topiclist[key];
            if (!topic || !topic.scriptContent) return;
            const tKey =
              topic.scriptid != null && topic.scriptid !== ""
                ? String(topic.scriptid)
                : topic.scriptContent;
            if (!topicMap.has(tKey)) {
              topicMap.set(tKey, {
                key: tKey,
                scriptContent: topic.scriptContent,
                options: [],
                optionSet: new Set(),
              });
            }
            const t = topicMap.get(tKey);
            const topicQ = new Map();
            (topic.details || []).forEach((detail) => {
              if (!detail || !detail.optionText) return;
              if (!t.optionSet.has(detail.optionText)) {
                t.optionSet.add(detail.optionText);
                t.options.push(detail.optionText);
              }
              topicQ.set(detail.optionText, Number(detail.chosenQuantity) || 0);
            });
            qMap.set(tKey, topicQ);
          });
        }
        rowQuantities.push(qMap);
      }
      const topics = Array.from(topicMap.values());
      // æ²¡æœ‰é€‰é¡¹çš„题目默认填充一个空选项列,避免表头/数据错位
      topics.forEach((t) => {
        if (!t.options.length) {
          t.options = [""];
        }
      });
      return { topics, rowQuantities };
    },
    // å¯¼å‡ºæ•°æ®
    async handleExport() {
      if (!this.userList.length) {
@@ -916,7 +1025,11 @@
            pattern: "solid",
            fgColor: { argb: "FFF5F7FA" },
          },
          alignment: { vertical: "middle", horizontal: "center" },
          alignment: {
            vertical: "middle",
            horizontal: "center",
            wrapText: true,
          },
          border: {
            top: { style: "thin", color: { argb: "FFD0D0D0" } },
            left: { style: "thin", color: { argb: "FFD0D0D0" } },
@@ -936,69 +1049,182 @@
          },
        };
        // è¡¨å¤´æŒ‰é¡µé¢åˆ—动态生成:「及时率」只有景宁院区页面才显示
        const showTimelyRate = this.orgname === "景宁畲族自治县人民医院";
        const headers = [
        // ä¸½æ°´å¸‚中医院:预先拉取题目明细,扁平化为「题目选项」列
        let lishuiTopics = [];
        let lishuiRowQuantities = [];
        if (isLishuiHospital) {
          const built = await this.buildLishuiTopicColumns();
          lishuiTopics = built.topics;
          lishuiRowQuantities = built.rowQuantities;
        }
        // ä¸»è¡¨åˆ—
        const mainHeaders = [
          this.queryParams.statisticaltype == 1 ? "出院病区" : "科室",
          "出院人次",
          "无需随访人次",
          "应随访人次",
          "随访率",
          "及时率",
          "复诊通知题目总量",
          "复诊通知填报量",
          "完成比率",
        ];
        if (showTimelyRate) {
          headers.push("及时率");
        }
        headers.push("复诊通知题目总量", "复诊通知填报量", "完成比率");
        const colCount = headers.length;
        const mainColumnCount = mainHeaders.length;
        // æ€»åˆ—数(丽水市中医院在末尾追加题目选项列)
        const topicColumnCount = lishuiTopics.reduce(
          (sum, t) => sum + (t.options.length || 1),
          0
        );
        const totalColumns = mainColumnCount + topicColumnCount;
        // æ·»åŠ æ€»æ ‡é¢˜
        worksheet.mergeCells(1, 1, 1, colCount);
        worksheet.mergeCells(1, 1, 1, totalColumns);
        const titleCell = worksheet.getCell(1, 1);
        titleCell.value = `复诊通知统计表(${sheetNameSuffix})`;
        titleCell.style = titleStyle;
        worksheet.getRow(1).height = 35;
        const headerRow = worksheet.addRow(headers);
        headerRow.eachCell((cell) => {
          cell.style = headerStyle;
        });
        headerRow.height = 25;
        // æ·»åŠ æ•°æ®è¡Œï¼ˆç©ºå€¼ç»Ÿä¸€æ˜¾ç¤ºâ€œ-”,与页面保持一致)
        const dash = (v, formatter) => {
          if (v === null || v === undefined || v === "") return "-";
          return formatter ? formatter(v) : v;
        };
        this.userList.forEach((item) => {
          const rowData = [
            this.queryParams.statisticaltype == 1
              ? item.leavehospitaldistrictname
              : item.deptname,
            item.dischargeCount || 0,
            item.nonFollowUp || 0,
            item.followUpNeeded || 0,
            dash(item.followUpRate),
          ];
          if (showTimelyRate) {
            rowData.push(dash(item.rate, (v) => this.formatPercent(v)));
          }
          rowData.push(
            item.joyAllCount || 0,
            item.joyCount || 0,
            dash(item.joyTotal, (v) => this.formatPercent(v))
          );
          const dataRow = worksheet.addRow(rowData);
          dataRow.eachCell((cell) => {
            cell.style = cellStyle;
        if (isLishuiHospital && lishuiTopics.length) {
          // è®¡ç®—每个题目的选项列宽(选项文字自适应,题干过长时摊宽列、必要时换行)
          const topicColumns = lishuiTopics.map((topic) => {
            const n = topic.options.length || 1;
            const baseWidths = topic.options.map((opt) =>
              opt === "" ? 10 : Math.max(10, this.textWidth(opt) + 4)
            );
            const sumWidth = baseWidths.reduce((s, w) => s + w, 0);
            const topicWidth = this.textWidth(topic.scriptContent) + 4;
            const groupWidth = Math.max(sumWidth, topicWidth);
            const extra = (groupWidth - sumWidth) / n;
            const widths = baseWidths.map((w) => w + extra);
            const lines = Math.max(1, Math.ceil(topicWidth / groupWidth));
            return { widths, lines };
          });
          dataRow.height = 22;
        });
          const maxHeaderLines = topicColumns.reduce(
            (m, t) => Math.max(m, t.lines),
            1
          );
        // è®¾ç½®åˆ—宽(跟列数一致)
        worksheet.columns = headers.map((h, i) => ({
          width: i === 0 ? 20 : h.length > 6 ? 15 : 12,
        }));
          // ä¸¤è¡Œè¡¨å¤´ï¼šç¬¬ä¸€è¡Œ = ä¸»è¡¨åˆ— + é¢˜ç›®åï¼›ç¬¬äºŒè¡Œ = é€‰é¡¹å
          const headerRow1 = worksheet.addRow([]);
          const headerRow2 = worksheet.addRow([]);
          mainHeaders.forEach((h, i) => {
            const col = i + 1;
            headerRow1.getCell(col).value = h;
            headerRow1.getCell(col).style = headerStyle;
            headerRow2.getCell(col).style = headerStyle;
            worksheet.mergeCells(
              headerRow1.number,
              col,
              headerRow2.number,
              col
            );
          });
          let colIndex = mainColumnCount + 1;
          lishuiTopics.forEach((topic) => {
            const optCount = topic.options.length || 1;
            for (let k = 0; k < optCount; k++) {
              headerRow1.getCell(colIndex + k).style = headerStyle;
            }
            headerRow1.getCell(colIndex).value = topic.scriptContent;
            if (optCount > 1) {
              worksheet.mergeCells(
                headerRow1.number,
                colIndex,
                headerRow1.number,
                colIndex + optCount - 1
              );
            }
            topic.options.forEach((opt, k) => {
              headerRow2.getCell(colIndex + k).value = opt;
              headerRow2.getCell(colIndex + k).style = headerStyle;
            });
            colIndex += optCount;
          });
          // è¡¨å¤´é«˜åº¦ï¼šé¢˜å¹²æ¢è¡Œæ—¶æŒ‰æœ€é•¿é¢˜ç›®é¢„ç•™
          headerRow1.height = Math.max(30, 15 * maxHeaderLines + 8);
          headerRow2.height = 25;
          // æ•°æ®è¡Œ
          this.userList.forEach((item, idx) => {
            const row = worksheet.addRow([]);
            const main = this.getMainExportRow(item);
            main.forEach((v, i) => {
              const cell = row.getCell(i + 1);
              cell.value = v;
              cell.style = cellStyle;
            });
            const qMap = lishuiRowQuantities[idx] || new Map();
            let cIndex = mainColumnCount + 1;
            for (const topic of lishuiTopics) {
              const topicQ = qMap.get(topic.key);
              topic.options.forEach((opt) => {
                const cell = row.getCell(cIndex);
                if (opt === "") {
                  // ç©ºé€‰é¡¹å ä½åˆ—不填数字
                  cell.value = "";
                } else {
                  const val = topicQ ? topicQ.get(opt) : undefined;
                  cell.value = val === undefined || val === null ? 0 : val;
                }
                cell.style = cellStyle;
                cIndex++;
              });
            }
            row.height = 22;
          });
          // åˆ—宽:主表列 + é¢˜ç›®é€‰é¡¹åˆ—
          const columns = [
            { width: 20 },
            { width: 12 },
            { width: 12 },
            { width: 12 },
            { width: 12 },
            { width: 12 },
            { width: 15 },
            { width: 15 },
            { width: 12 },
          ];
          lishuiTopics.forEach((topic, ti) => {
            topicColumns[ti].widths.forEach((w) => {
              columns.push({ width: w });
            });
          });
          worksheet.columns = columns;
        } else {
          // å…¶ä»–医院:单行表头
          const headerRow = worksheet.addRow(mainHeaders);
          headerRow.eachCell((cell) => {
            cell.style = headerStyle;
          });
          headerRow.height = 25;
          this.userList.forEach((item) => {
            const dataRow = worksheet.addRow(this.getMainExportRow(item));
            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();
src/views/followvisit/discharge/index copy.vue
@@ -911,7 +911,7 @@
      },
      // endOut: 1,
      endOut:
        localStorage.getItem("orgname") == "景宁畲族自治县人民医院" ? 0 : 1, //0 å‡ºé™¢æ—¶é—´(正序)    1 å‡ºé™¢æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)  7应随访日期(倒序) åº”随访日期(正序)
        localStorage.getItem("orgname") == "丽水市中医院" ? 0 : 1, //0 å‡ºé™¢æ—¶é—´(正序)    1 å‡ºé™¢æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)  7应随访日期(倒序) åº”随访日期(正序)
      endOuts: [
        {
          value: 0,
src/views/followvisit/discharge/index.vue
@@ -75,7 +75,7 @@
        <el-form-item label="经管医生" prop="managementDoctor">
          <el-input
            v-model="topqueryParams.managementDoctor"
            placeholder="请输入主治医生"
            placeholder="请输入经管医生"
          ></el-input>
        </el-form-item>
        <el-form-item label="日期限制" prop="status">
@@ -117,6 +117,24 @@
          <el-select v-model="topqueryParams.sort" placeholder="请选择">
            <el-option
              v-for="item in topicoptionssort"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            >
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="当前执行方式" prop="currentPreachforms">
          <el-select
            v-model="topqueryParams.currentPreachforms"
            placeholder="请选择"
            multiple
            clearable
            collapse-tags
          >
            <el-option
              v-for="item in currentPreachformOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
@@ -1336,7 +1354,7 @@
      },
      // endOut: 1,
      endOut:
        localStorage.getItem("orgname") == "景宁畲族自治县人民医院" ? 0 : 1, //0 å‡ºé™¢æ—¶é—´(正序)    1 å‡ºé™¢æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)  7应随访日期(倒序) åº”随访日期(正序)
        localStorage.getItem("orgname") == "丽水市中医院" ? 0 : 1, //0 å‡ºé™¢æ—¶é—´(正序)    1 å‡ºé™¢æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)  7应随访日期(倒序) åº”随访日期(正序)
      endOuts: [
        {
          value: 0,
@@ -1400,6 +1418,7 @@
            : "",
        leaveldeptcodes: [],
        leavehospitaldistrictcodes: [],
        currentPreachforms: [],
      },
      orgname: "",
      propss: { multiple: true },
@@ -1430,6 +1449,16 @@
          value: 5,
          label: "无需随访",
        },
      ],
      currentPreachformOptions: [
        { value: "1", label: "人工" },
        { value: "2", label: "纸质" },
        { value: "3", label: "智能语音" },
        { value: "4", label: "短信" },
        { value: "5", label: "微信公众号" },
        { value: "6", label: "微信小程序" },
        { value: "7", label: "术后随访" },
      ],
      sextype: [
@@ -1648,9 +1677,12 @@
      }
      try {
        // é¡¶éƒ¨å¡ç‰‡ç‚¹å‡»çš„临时条件只加在本次请求上,不写回表单
        const query = this.applyCardFilter({ ...this.topqueryParams });
        const response = await getTaskservelist(query);
        // å½“前执行方式为多选,需转成逗号分隔字符串(如 1,2,5)
        const params = { ...this.topqueryParams };
        if (Array.isArray(params.currentPreachforms)) {
          params.currentPreachforms = params.currentPreachforms.join(",");
        }
        const response = await getTaskservelist(params);
        // âœ… ç¬¬ä¸€æ­¥ï¼šå…ˆé”å®šæ¢å¤çŠ¶æ€
        this.isRestoring = true;
@@ -1692,10 +1724,11 @@
            });
          }
          if (item.currentPreachform) {
            item.currentPreachform = this.checkboxlist.find(
              (item) => item.value == item.currentPreachform
            )?.value;
            console.log(item.currentPreachform, "mi");
              (items) => items.value == item.currentPreachform
            )?.label;
          }
        });
@@ -2115,6 +2148,7 @@
        scopetype: [],
        leaveldeptcodes: [],
        leavehospitaldistrictcodes: [],
        currentPreachforms: [],
      };
      this.handleQuery(1);
    },
@@ -2135,6 +2169,7 @@
        scopetype: [],
        leaveldeptcodes: [],
        leavehospitaldistrictcodes: [],
        currentPreachforms: [],
      };
    },
    handleSelectionChange(rows) {
src/views/followvisit/operationAgain/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1933 @@
<template>
  <div class="app-container">
     <statistics-cards
      :cardlist="cardlist"
      :ycvalue="ycvalue"
      :jgvalue="jgvalue"
      :show-warning-condition="orgname == '省立同德翠苑院区'"
    />
    <el-row :gutter="20">
      <!--用户数据-->
      <el-form
        :model="topqueryParams"
        ref="queryForm"
        size="small"
        :inline="true"
        v-show="showSearch"
        label-width="98px"
      >
        <el-form-item label="任务名称">
          <el-input
            v-model="topqueryParams.taskName"
            placeholder="请选择任务名称"
          ></el-input>
        </el-form-item>
        <el-form-item label="服务时间">
          <el-date-picker
            v-model="dateRange"
            style="width: 240px"
            value-format="yyyy-MM-dd"
            type="daterange"
            range-separator="-"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
          ></el-date-picker>
        </el-form-item>
        <el-form-item label="应随访时间">
          <el-date-picker
            v-model="dateRangefs"
            style="width: 240px"
            value-format="yyyy-MM-dd"
            type="daterange"
            range-separator="-"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
          ></el-date-picker>
        </el-form-item>
        <el-form-item label="患者姓名" prop="sendname">
          <el-input
            v-model="topqueryParams.sendname"
            placeholder="请输入患者姓名"
          ></el-input>
        </el-form-item>
        <el-form-item label="诊断名称" prop="leavediagname">
          <el-input
            v-model="topqueryParams.leavediagname"
            placeholder="请输入诊断名称"
          ></el-input>
        </el-form-item>
        <el-form-item label="患者范围" prop="status">
          <el-cascader
            v-model="topqueryParams.scopetype"
            placeholder="默认全部"
            :options="sourcetype"
            filterable
            clearable
            :props="{ expandTrigger: 'hover' }"
            @change="handleChange"
          ></el-cascader>
        </el-form-item>
        <el-form-item label="随访状态" prop="status">
          <el-cascader
            v-model="serviceStatusValue"
            placeholder="请选择"
            :options="serviceStatusOptions"
            :props="cascaderProps"
            @change="handleServiceStatusChange"
            clearable
          ></el-cascader>
        </el-form-item>
        <el-form-item label="排序方式" prop="status">
          <el-select v-model="topqueryParams.sort" placeholder="请选择">
            <el-option
              v-for="item in topicoptionssort"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            >
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button
            type="primary"
            icon="el-icon-search"
            size="medium"
            @click="handleQuery(1)"
            >搜索</el-button
          >
          <el-button icon="el-icon-refresh" size="medium" @click="resetQuery"
            >重置</el-button
          >
        </el-form-item>
      </el-form>
      <el-divider></el-divider>
      <el-row :gutter="10" class="mb8">
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
                type="warning"
                plain
                icon="el-icon-upload2"
                size="medium"
                @click="handleExport"
                >导出</el-button
              >
            </div>
          </div>
        </el-col>
        <!-- <el-col :span="1.5">
          <el-button
            type="primary"
            icon="el-icon-plus"
            size="medium"
            @click="handleAdd"
            >新增</el-button
          >
        </el-col> -->
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
                type="warning"
                plain
                icon="el-icon-warning-outline"
                size="medium"
                @click="toleadExport(1)"
                >执行失败</el-button
              >
            </div>
          </div>
        </el-col>
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
                type="danger"
                plain
                icon="el-icon-warning"
                size="medium"
                @click="toleadExport(2)"
                >结果异常</el-button
              >
            </div>
          </div>
        </el-col>
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
                type="success"
                plain
                size="medium"
                @click="buidegetTasklist()"
                >待办服务</el-button
              >
            </div>
          </div>
        </el-col>
        <!-- <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
                type="primary"
                plain
                size="medium"
                @click="affiliation()"
                >本人所属服务</el-button
              >
            </div>
          </div>
        </el-col> -->
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button type="success" size="medium" @click="onthatday()"
                >今日服务</el-button
              >
            </div>
          </div>
        </el-col>
      </el-row>
      <el-table
        v-loading="loading"
        :data="userList"
        :row-class-name="tableRowClassName"
        @selection-change="handleSelectionChange"
      >
        <el-table-column type="selection" width="50" align="center" />
        <el-table-column
          label="任务名称"
          fixed
          width="150"
          show-overflow-tooltip
          align="center"
          key="taskName"
          prop="taskName"
        />
        <!-- <el-table-column label="序号" fixed align="center" key="id" prop="id" /> -->
        <el-table-column
          label="姓名"
          width="100"
          align="center"
          key="sendname"
          prop="sendname"
        >
          <template slot-scope="scope">
            <el-button
              size="medium"
              type="text"
              @click="
                gettoken360(
                  scope.row.sfzh,
                  scope.row.drcode,
                  scope.row.drname,
                  scope.row.patid
                )
              "
              ><span class="button-textsc">{{
                scope.row.sendname
              }}</span></el-button
            >
          </template>
        </el-table-column>
        <el-table-column
          label="随访状态"
          align="center"
          key="sendstateView"
          prop="sendstateView"
          width="120"
        >
          <template slot-scope="scope">
            <el-tooltip
              class="item"
              effect="dark"
              :content="scope.row.remark"
              placement="top-start"
            >
              <div v-if="scope.row.sendstateView == 1">
                <el-tag type="primary" :disable-transitions="false"
                  >待随访</el-tag
                >
              </div>
              <div v-if="scope.row.sendstateView == 2">
                <el-tag type="success" :disable-transitions="false"
                  >已完成</el-tag
                >
              </div>
              <div v-if="scope.row.sendstateView == 3">
                <el-tag type="warning" :disable-transitions="false"
                  >无需随访</el-tag
                >
              </div>
            </el-tooltip>
          </template>
        </el-table-column>
        <!-- <el-table-column
          label="任务异常说明"
          width="120"
          align="center"
          key="remark"
          prop="remark" -->
        />
        <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
          label="随访完成时间"
          sortable
          align="center"
          prop="finishtime"
          width="160"
        >
          <template slot-scope="scope">
            <span>{{ parseTime(scope.row.finishtime) }}</span>
          </template>
        </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="146"
          align="center"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
          label="主治医生"
          width="120"
          align="center"
          key="drname"
          prop="drname"
        />
        <!-- <el-table-column
          label="服务天数"
          width="120"
          align="center"
          key="endDay"
          prop="endDay"
        >
          <template slot-scope="scope">
            <span>{{ scope.row.endDay ? scope.row.endDay + "天" : "" }}</span>
          </template>
        </el-table-column> -->
        <el-table-column
          label="身份证号码"
          width="200"
          align="center"
          key="sfzh"
          prop="sfzh"
        />
        <el-table-column
          label="联系电话"
          width="200"
          align="center"
          key="phone"
          prop="phone"
        />
        <el-table-column
          label="责任护士"
          width="120"
          align="center"
          key="nurseName"
          prop="nurseName"
        />
        <!-- <el-table-column
          label="病历号"
          align="center"
          sortable
          key="medicalRecordNo"
          prop="medicalRecordNo"
          width="120"
        /> -->
        <!-- <el-table-column label="年龄" align="center" key="age" prop="age" /> -->
        <!-- <el-table-column label="性别"width="100" align="center" key="sex" prop="sex" /> -->
        <!-- <el-table-column label="床号" align="center" key="badNo" prop="badNo" /> -->
        <el-table-column
          label="首次随访科室"
          align="center"
          key="deptname"
          prop="deptname"
          width="120"
        >
        </el-table-column>
        <el-table-column
          label="本次科室"
          align="center"
          key="visitDeptName"
          prop="visitDeptName"
          width="120"
        >
        </el-table-column>
        <el-table-column
          label="病区"
          align="center"
          key="leavehospitaldistrictname"
          prop="leavehospitaldistrictname"
          width="120"
        >
        </el-table-column>
        <el-table-column
          label="诊断名称"
          align="center"
          key="leavediagname"
          prop="leavediagname"
          width="120"
          :show-overflow-tooltip="true"
        >
        </el-table-column>
        <el-table-column
          label="随访人员"
          align="center"
          key="updateBy"
          prop="updateBy"
          width="120"
        />
        <el-table-column
          label="服务随访模板名称"
          align="center"
          key="templatename"
          prop="templatename"
          width="200"
        />
        <el-table-column
          label="任务状态"
          align="center"
          key="sendstate"
          prop="sendstate"
          width="120"
        >
          <template slot-scope="scope">
            <el-tooltip
              class="item"
              effect="dark"
              :content="scope.row.remark"
              placement="top-start"
            >
              <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>
              <div v-if="scope.row.sendstate == 7">
                <el-tag type="danger" :disable-transitions="false">超时</el-tag>
              </div>
            </el-tooltip>
          </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
          label="任务发送流程"
          align="center"
          key="serviceSubtaskRecordList"
          prop="serviceSubtaskRecordList"
          width="160"
          :show-overflow-tooltip="true"
        >
          <template slot-scope="scope">
            <span v-for="item in scope.row.serviceSubtaskRecordList"
              >{{ item.remark }}、
            </span>
          </template>
        </el-table-column> -->
        <el-table-column
          label="任务结果说明"
          width="220"
          align="center"
          key="remark"
          prop="remark"
        >
          <template slot-scope="scope" v-if="scope.row.remark">
            <el-tooltip
              :content="scope.row.remark"
              placement="top"
              effect="dark"
            >
              <el-tag
                type="warning"
                v-if="scope.row.sendstate != 5 && scope.row.sendstate != 4"
                >{{ scope.row.remark }}</el-tag
              >
              <el-tag type="warning" v-else>{{ scope.row.remark }}</el-tag>
            </el-tooltip>
          </template>
        </el-table-column>
        <el-table-column
          label="操作"
          align="center"
          fixed="right"
          width="300"
          class-name="small-padding fixed-width"
        >
          <template slot-scope="scope">
            <!-- <el-tooltip
              class="item"
              effect="dark"
              content="再次随访"
              placement="top"
            >
              <el-button
                size="medium"
                type="text"
                v-if="scope.row.isVisitAgain!=2"
                @click="followupvisit(scope.row)"
                ><span class="button-bb"
                  ><i class="el-icon-s-promotion"></i>再次随访</span
                ></el-button
              >
            </el-tooltip>
            <el-tooltip
              v-if="scope.row.sendstate == 1 || scope.row.sendstate == 2"
              class="item"
              effect="dark"
              content="暂停服务"
              placement="top"
            >
              <el-button
                size="medium"
                type="text"
                @click="handlestop(scope.row)"
                ><span class="button-sc"
                  ><i class="el-icon-remove-outline"></i>暂停服务</span
                ></el-button
              >
            </el-tooltip> -->
            <el-button size="medium" type="text" @click="Seedetails(scope.row)"
              ><span class="button-zx"
                ><i class="el-icon-s-order"></i>查看详情</span
              ></el-button
            >
            <el-button
              size="medium"
              type="text"
              @click="handleUpdate(scope.row)"
              ><span class="button-textxga"
                ><i class="el-icon-edit"></i>患者过滤</span
              ></el-button
            >
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
        :total="total"
        :page.sync="topqueryParams.pageNum"
        :limit.sync="topqueryParams.pageSize"
        @pagination="getList"
      />
    </el-row>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹å½±åƒéšè®¿å¯¹è¯æ¡† -->
    <el-dialog
      :title="amendtag ? '修改患者信息' : '新增患者'"
      :visible.sync="Labelchange"
      width="900px"
    >
      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
        <el-row>
          <el-col :span="8">
            <el-form-item label="姓名" width="100" prop="name">
              <el-input
                v-model="form.name"
                placeholder="请输入姓名"
                maxlength="30"
              />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="性别" width="100" prop="sex">
              <el-select v-model="form.sex" placeholder="请选择性别">
                <el-option
                  v-for="dict in sextype"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
                ></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="年龄" prop="age">
              <el-input
                v-model="form.age"
                placeholder="请输入年龄"
                maxlength="30"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="8">
            <el-form-item label="过滤医生" width="100" prop="filterDrname">
              <el-input
                v-model="form.filterDrname"
                placeholder="请输入医生姓名"
                maxlength="30"
              />
            </el-form-item>
          </el-col>
        </el-row>
       <el-row>
  <el-col :span="24">
    <el-form-item label="过滤原因">
      <el-input
        v-model="form.notrequiredreason"
        type="textarea"
        placeholder="请输入过滤原因"
      ></el-input>
      <!-- æé†’文字 -->
      <div class="filter-warning">
        <i class="el-icon-warning-outline"></i>
        è¯¥åŠŸèƒ½é€‚ç”¨äºŽæ­»äº¡ã€åˆ—å…¥åŒ»é™¢é»‘åå•ã€æ˜Žç¡®æ‹’ç»éšè®¿ç­‰æ‚£è€…çš„è¿‡æ»¤æŽ’é™¤ï¼Œè¿‡æ»¤åŽè¯¥æ‚£è€…æ‰€æœ‰è¿›è¡Œä¸­ä»»åŠ¡å…¨éƒ¨åœæ­¢ä¸”æ— æ³•åŒ¹é…æ–°çš„éšè®¿ä»»åŠ¡ï¼Œè¯·è°¨æ…Žæ“ä½œï¼
      </div>
    </el-form-item>
  </el-col>
</el-row>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
        <el-button @click="cancel">取 æ¶ˆ</el-button>
      </div>
    </el-dialog>
    <!-- ä¿®æ”¹å‘送时间对话框 -->
    <el-dialog
      title="发送时间设置"
      :visible.sync="modificationVisible"
      width="45%"
    >
      <div style="margin-bottom: 20px; color: red">
        ç»Ÿä¸€ä¿®æ”¹å½“天未发送的任务时间
      </div>
      <el-form
        :model="ruleForm"
        :rules="rules"
        ref="ruleForm"
        label-width="120px"
        class="demo-ruleForm"
      >
        <el-form-item label="发送日期">
          <el-date-picker
            v-model="ruleForm.value1"
            type="date"
            placeholder="选择日期"
          >
          </el-date-picker>
        </el-form-item>
        <el-form-item label="时间段" prop="type">
          <el-checkbox-group v-model="ruleForm.type">
            <el-checkbox label="上午" name="type"></el-checkbox>
            <el-checkbox label="下午" name="type"></el-checkbox>
            <el-checkbox label="晚上" name="type"></el-checkbox>
          </el-checkbox-group>
        </el-form-item>
        <el-form-item label="上午时间区间" required>
          <el-time-picker
            is-range
            v-model="ruleForm.value2"
            range-separator="至"
            start-placeholder="开始时间"
            end-placeholder="结束时间"
            placeholder="选择时间范围"
          >
          </el-time-picker>
        </el-form-item>
        <el-form-item label="下午时间区间" required>
          <el-time-picker
            is-range
            v-model="ruleForm.value3"
            range-separator="至"
            start-placeholder="开始时间"
            end-placeholder="结束时间"
            placeholder="选择时间范围"
          >
          </el-time-picker>
        </el-form-item>
        <el-form-item label="晚上时间区间" required>
          <el-time-picker
            is-range
            v-model="ruleForm.value4"
            range-separator="至"
            start-placeholder="开始时间"
            end-placeholder="结束时间"
            placeholder="选择时间范围"
          >
          </el-time-picker>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="modificationVisible = false">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="modificationVisible = false"
          >ç¡® å®š</el-button
        >
      </span>
    </el-dialog>
    <!-- å†æ¬¡éšè®¿ -->
    <el-dialog title="患者再次随访" :visible.sync="dialogFormVisible">
      <el-form ref="zcform" :rules="zcrules" :model="zcform" label-width="80px">
        <el-form-item label="任务名称">
          <el-input
            style="width: 400px"
            disabled
            v-model="zcform.taskName"
          ></el-input>
        </el-form-item>
        <el-form-item label="患者名称">
          <el-input
            style="width: 400px"
            disabled
            v-model="zcform.sendname"
          ></el-input>
        </el-form-item>
        <el-form-item label="年龄">
          <el-input
            style="width: 400px"
            disabled
            v-model="zcform.age"
          ></el-input>
        </el-form-item>
        <el-form-item label="科室">
          <el-input
            style="width: 400px"
            disabled
            v-model="zcform.deptname"
          ></el-input>
        </el-form-item>
        <el-form-item label="病区">
          <el-input
            style="width: 400px"
            disabled
            v-model="zcform.leavehospitaldistrictname"
          ></el-input>
        </el-form-item>
        <el-form-item label="随访方式" prop="resource">
          <el-radio-group v-model="zcform.resource">
            <el-radio label="1">本病区随访</el-radio>
            <el-radio label="2">随访中心随访</el-radio>
          </el-radio-group>
        </el-form-item>
        <!-- <el-form-item label="即刻发送">
          <el-switch v-model="zcform.delivery"></el-switch>
        </el-form-item> -->
        <el-form-item label="服务时间">
          <el-input
            style="width: 400px"
            disabled
            v-model="zcform.endtime"
          ></el-input>
        </el-form-item>
        <el-form-item label="随访完成时间" prop="date1">
          <el-date-picker
            type="date"
            placeholder="选择日期"
            v-model="zcform.date1"
            style="width: 100%"
          ></el-date-picker>
        </el-form-item>
        <el-form-item label="随访记录">
          <el-input type="textarea" v-model="zcform.remark"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible = false">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="setupsubtask">确认创建服务</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import {
  delUser,
  addUser,
  updateUser,
  resetUserPwd,
  changeUserStatus,
} from "@/api/system/user";
import {
  getTaskservelist,
  buidegetTasklist,
  addserviceSubtask,
  query360PatInfo,
  query360PatInfonh,
} from "@/api/AiCentre/index";
import { alterpatient, particularpatient } from "@/api/patient/homepage";
import Treeselect from "@riophae/vue-treeselect";
import store from "@/store";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
import StatisticsCards from "@/components/StatisticsCards";
export default {
  name: "Discharge",
  dicts: ["sys_normal_disable", "sys_user_sex", "sys_yujing", "sys_suggest"],
  components: { Treeselect,StatisticsCards },
  data() {
    return {
      // é®ç½©å±‚
      loading: true,
      // é€‰ä¸­æ•°ç»„
      ids: [],
      // éžå•个禁用
      single: true,
      // éžå¤šä¸ªç¦ç”¨
      multiple: true,
      // æ˜¾ç¤ºæœç´¢æ¡ä»¶
      showSearch: true,
      dialogFormVisible: false,
      // æ€»æ¡æ•°
      total: 0,
      // ç”¨æˆ·è¡¨æ ¼æ•°æ®
      userList: null,
      // å¼¹å‡ºå±‚标题
      title: "新增影像随访",
      // æ˜¯å¦æ˜¾ç¤ºä¿®æ”¹ã€æ·»åŠ å¼¹å‡ºå±‚
      addalteropen: false,
      // ä¿®æ”¹å‘送时间对话框
      modificationVisible: false,
      // éƒ¨é—¨åç§°
      deptName: undefined,
      // é»˜è®¤å¯†ç 
      initPassword: undefined,
      // æ—¥æœŸèŒƒå›´
      dateRange: [],
      dateRangefs: [],
      // å²—位选项
      postOptions: [],
      ruleForm: {
        type: [],
      },
      zcform: {},
      dynamicTags: ["选项一", "选项二", "选项三"], //选项
      inputVisible: false,
      Labelchange: false,
      ycvalue: 0,
      yfsvalue: "",
      inputValue: "",
      preachform: "",
      previewVisible: false, //影像随访预览弹框
      radio: "",
      radios: [],
      total: 0, // æ€»æ¡æ•°
      ImportQuantity: 999, //导影像随访数量
      //预览影像随访信息
      previewvalue: {
        username: "这个医生对你怎么样",
      },
      value: [],
      list: [],
      jgvalue: 0,
      sourcetype: [
        {
          value: 1,
          label: "科室",
          children: [],
        },
        {
          value: 2,
          label: "病区",
          children: [],
        },
        {
          value: 3,
          label: "全部",
        },
      ],
      loading: false,
      cardlist: [
        {
          name: "患者服务总量",
          value: 0,
        },
        {
          name: "无需随访",
          value: 0,
        },
        {
          name: "需随访",
          value: 0,
        },
        {
          name: "待随访",
          value: 0,
        },
        {
          name: "已完成",
          value: 0,
        },
      ],
      zcrules: {
        date1: [
          { required: true, message: "请选择随访方式", trigger: "change" },
        ],
        resource: [
          { required: true, message: "请选择随访时间", trigger: "blur" },
        ],
      },
      // çº§è”选择器绑定值
      serviceStatusValue: 10,
      cascaderProps: {
        expandTrigger: "hover", // æ‚¬åœå±•å¼€
        checkStrictly: true, // âœ… å…³é”®ï¼šå…è®¸é€‰ä¸­ä»»æ„ä¸€çº§
        emitPath: false, // âœ… åªè¿”回选中的值,不返回路径数组
      },
      // æœåŠ¡çŠ¶æ€é€‰é¡¹ï¼ˆä¸€çº§å’ŒäºŒçº§å€¼ä¸å†²çªï¼‰
      serviceStatusOptions: [
        {
          value: null,
          label: "全部",
        },
        {
          value: 10, // ä¸€çº§ï¼šå¾…随访
          label: "待随访",
          children: [
            { value: 1, label: "被领取" },
            { value: 2, label: "待发送" },
            { value: 3, label: "已发送" },
            { value: 5, label: "发送失败" },
            { value: 7, label: "超时" },
          ],
        },
        {
          value: 20, // ä¸€çº§ï¼šå·²å®Œæˆ
          label: "已完成",
          children: [{ value: 6, label: "已完成" }],
        },
        {
          value: 30, // ä¸€çº§ï¼šæ— éœ€éšè®¿
          label: "无需随访",
          children: [{ value: 4, label: "不执行" }],
        },
      ],
      // è¡¨å•参数
      form: {
        phonenumber: "",
        totagid: "",
        types: "",
        nickName: "",
        qystatus: "",
        btstatus: "",
      },
      topicoptionssort: [
        {
          value: 0,
          label: "服务时间(正序)",
        },
        {
          value: 1,
          label: "服务时间(倒序)",
        },
        {
          value: 2,
          label: "发送时间(正序)",
        },
        {
          value: 3,
          label: "发送时间(倒序)",
        },
        {
          value: 7,
          label: "应随访日期(正序)",
        },
        {
          value: 8,
          label: "应随访日期(倒序)",
        },
      ],
      // æŸ¥è¯¢å‚æ•°
      topqueryParams: {
        pageNum: 1,
        pageSize: 10,
        sendstateView:1,
        sort: localStorage.getItem("orgname") == "丽水市中医院" ? 8 : 2, //0 æœåŠ¡æ—¶é—´(正序)    1 æœåŠ¡æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)  7应随访日期(倒序) åº”随访日期(正序)
        serviceType: 19,
        searchscope: 3,
        visitCount: 2,
        scopetype: [],
        visitDeptCodes: [],
        // leaveldeptcodes:[],
        leavehospitaldistrictcodes: [],
      },
      propss: { multiple: true },
      options: [],
      topicoptions: [
        {
          value: null,
          label: "全部",
        },
        {
          value: 1,
          label: "待随访",
        },
        {
          value: 2,
          label: "随访中",
        },
        {
          value: 3,
          label: "未完成",
        },
        {
          value: 4,
          label: "已完成",
        },
        {
          value: 5,
          label: "无需随访",
        },
      ],
      sextype: [
        {
          value: 1,
          label: "男",
        },
        {
          value: 2,
          label: "女",
        },
      ],
      topicoptionsyj: [
        {
          value: 1,
          label: "异常",
        },
        {
          value: 0,
          label: "正常",
        },
      ],
      url: "http://9.208.2.190:8090/smartor/serviceExternal/query360PatInfo",
      postData: {
        XiaoXiTou: {
          FaSongFCSJC: "ZJHES",
          FaSongJGID: localStorage.getItem("orgid"),
          FaSongJGMC: localStorage.getItem("orgname"),
          FaSongSJ: "2025-01-09 17:29:36",
          FaSongXTJC: "SUIFANGXT",
          FaSongXTMC: "随访系统",
          XiaoXiID: "5FA92AFB-9833-4608-87C7-F56A654AC171",
          XiaoXiLX: "SC_LC_360STCX",
          XiaoXiMC: "360 视图查询",
          ZuHuID: localStorage.getItem("ZuHuID"),
          ZuHuMC: localStorage.getItem("orgname"),
        },
        YeWuXX: {
          BingRenXX: {
            ZhengJianHM: "",
            ZhengJianLXDM: "01",
            ZhengJianLXMC: "居民身份证",
            ZuZhiJGID: localStorage.getItem("orgid"),
            ZuZhiJGMC: localStorage.getItem("orgname"),
          },
          YongHuXX: {
            XiTongID: "SUIFANGXT",
            XiTongMC: "随访系统",
            YongHuID: localStorage.getItem("YongHuID"),
            YongHuXM: localStorage.getItem("YongHuXM"),
            ZuZhiJGID: localStorage.getItem("orgid"),
            ZuZhiJGMC: localStorage.getItem("orgname"),
            idp: "lyra",
          },
        },
      },
      amendtag: false,
      errtype: "",
      leavehospitaldistrictcode: "",
      serviceState: [],
      checkboxlist: [],
      // è¡¨å•校验
      rules: {},
    };
  },
  watch: {},
  created() {
    this.serviceState = store.getters.serviceState;
    this.checkboxlist = store.getters.checkboxlist;
    this.errtype = this.$route.query.errtype;
    this.leavehospitaldistrictcode =
      this.$route.query.leavehospitaldistrictcode;
    this.sourcetype[0].children = store.getters.belongDepts.map((dept) => {
      return {
        label: dept.deptName,
        value: dept.deptCode,
      };
    });
    this.sourcetype[1].children = store.getters.belongWards.map((dept) => {
      return {
        label: dept.districtName,
        value: dept.districtCode,
      };
    });
    if (this.errtype) {
      this.toleadExport(2);
    } else {
      this.getList(1);
    }
    this.getConfigKey("sys.user.initPassword").then((response) => {
      this.initPassword = response.msg;
    });
  },
  activated() {
    this.getList(1);
  },
  methods: {
    /** æŸ¥è¯¢éšè®¿æœåŠ¡åˆ—è¡¨ */
    getList(refresh) {
      // é»˜è®¤å…¨éƒ¨
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        // this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        //   (obj) => obj.deptCode
        // );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
          this.leavehospitaldistrictcode
        );
        console.log(this.topqueryParams.leavehospitaldistrictcodes, "11");
      }
      this.loading = true;
      if (
        this.topqueryParams.leavehospitaldistrictcodes[0] &&
        this.topqueryParams.visitDeptCodes[0]
      ) {
        this.topqueryParams.deptOrDistrict = 4;
      } else {
        this.topqueryParams.deptOrDistrict = 1;
      }
      if (!this.followupAuthority()) {
        this.$message.warning("未配置科室/病区相关权限不可查询");
        return Promise.reject(new Error("无权限查询"));
      }
      getTaskservelist(this.topqueryParams).then((response) => {
        this.userList = response.rows[0].serviceSubtaskList;
        this.total = response.total;
        if (refresh) {
          this.cardlist[0].value =
            Number(response.rows[0].wxsf) + Number(response.rows[0].xsf) || 0;
          // this.cardlist[1].value = response.rows[0].wzx;
          this.cardlist[1].value = response.rows[0].wxsf || 0;
          this.ycvalue = response.rows[0].yc;
          this.cardlist[2].value = response.rows[0].xsf || 0;
          this.cardlist[3].value = response.rows[0].dsf || 0;
          this.cardlist[4].value = response.rows[0].ywc || 0;
          this.yfsvalue = response.rows[0].yfs;
        }
        this.loading = false;
        this.userList.forEach((item) => {
          let idArray = null;
          if (item.endtime) {
            item.endDay = this.daysBetween(item.endtime);
          }
          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.total = response.total;
      });
    },
    affiliation() {
      this.topqueryParams.managementDoctorCode = store.getters.hisUserId;
      this.getList(1);
    },
    onthatday() {
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
      const now = new Date();
      return now.toISOString().slice(0, 10); // æˆªå–前10个字符,即 YYYY-MM-DD
    },
    buidegetTasklist(type) {
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
          this.leavehospitaldistrictcode
        );
      }
      let obj = {
        pageNum: 1,
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstateView: 1,
        visitDeptCodes: this.topqueryParams.visitDeptCodes,
        // leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
        this.userList = response.rows[0].serviceSubtaskList;
        this.total = response.total;
        if (refresh) {
          this.cardlist[0].value =
            Number(response.rows[0].wxsf) + Number(response.rows[0].xsf) || 0;
          this.cardlist[1].value = response.rows[0].wzx;
          this.cardlist[2].value = response.rows[0].ysf;
          this.ycvalue = response.rows[0].yc;
          this.cardlist[3].value = response.rows[0].fssb;
          this.cardlist[4].value = response.rows[0].dsf;
          // this.cardlist[5].value = response.rows[0].yfs2;
          this.yfsvalue = response.rows[0].yfs;
        }
        this.loading = false;
        this.userList.forEach((item) => {
          let idArray = null;
          if (item.endtime) {
            item.endDay = this.daysBetween(item.endtime);
          }
          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.total = response.total;
      });
    },
    // æŸ¥çœ‹æœåŠ¡éšè®¿è¯¦æƒ…
    Referencequestion(row) {
      this.previewVisible = true;
    },
    // æ·»åŠ å¼¹æ¡†æœç´¢
    remoteMethod(query) {
      if (query !== "") {
        this.loading = true;
        setTimeout(() => {
          this.loading = false;
          this.options = this.list.filter((item) => {
            return item.label.toLowerCase().indexOf(query.toLowerCase()) > -1;
          });
        }, 200);
      } else {
        this.options = [];
      }
    },
    // å½±åƒéšè®¿çŠ¶æ€ä¿®æ”¹
    handleStatusChange(row) {
      let text = row.status === "0" ? "启用" : "停用";
      this.$modal
        .confirm('确认要"' + text + '""' + row.userName + '"用户吗?')
        .then(function () {
          return changeUserStatus(row.userId, row.status);
        })
        .then(() => {
          this.$modal.msgSuccess(text + "成功");
        })
        .catch(function () {
          row.status = row.status === "0" ? "1" : "0";
        });
    },
    // è¡¨å•重置
    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");
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ */
    handleQuery(refresh) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        // this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        //   (obj) => obj.deptCode
        // );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      this.topqueryParams.pageNum = 1;
      this.topqueryParams.startOutHospTime = this.dateRange[0];
      this.topqueryParams.endOutHospTime = this.dateRange[1];
      this.topqueryParams.startSendDateTime = this.dateRangefs[0];
      this.topqueryParams.endSendDateTime = this.dateRangefs[1];
      console.log("2");
      this.getList(refresh);
    },
    // æ‚£è€…范围处理
    handleChange(value) {
      let type = value[0];
      let code = value.slice(-1)[0];
      this.topqueryParams.leavehospitaldistrictcodes = [];
      this.topqueryParams.visitDeptCodes = [];
      // this.topqueryParams.leaveldeptcodes = [];
      if (type == 1) {
        this.topqueryParams.visitDeptCodes.push(code);
        // this.topqueryParams.leaveldeptcodes.push(code);
        this.topqueryParams.leavehospitaldistrictcodes = [];
        this.topqueryParams.searchscope = 1;
      } else if (type == 2) {
        this.topqueryParams.leavehospitaldistrictcodes.push(code);
        this.topqueryParams.visitDeptCodes = [];
        // this.topqueryParams.leaveldeptcodes = [];
        this.topqueryParams.searchscope = 2;
      } else {
        this.topqueryParams.searchscope = 3;
      }
    },
    // éšè®¿çŠ¶æ€å˜æ›´å¤„ç†
    handleServiceStatusChange(value) {
      // æ¸…空选择
      if (value === null || value === undefined || value === "") {
        this.topqueryParams.sendstateView = null;
        this.topqueryParams.sendstate = null;
        return;
      }
      console.log(value, "value");
      // ä¸€çº§èŠ‚ç‚¹çš„å€¼ï¼ˆå¤§äºŽç­‰äºŽ10)
      if (value >= 10) {
        // é€‰ä¸­äº†ä¸€çº§
        switch (value) {
          case 10:
            this.topqueryParams.sendstateView = 1; // å¾…随访
            break;
          case 20:
            this.topqueryParams.sendstateView = 2; // å·²å®Œæˆ
            break;
          case 30:
            this.topqueryParams.sendstateView = 3; // æ— éœ€éšè®¿
            break;
        }
        this.topqueryParams.sendstate = null;
      } else {
        // é€‰ä¸­äº†äºŒçº§ï¼ˆå…·ä½“状态)
        this.topqueryParams.sendstateView = null;
        this.topqueryParams.sendstate = value;
      }
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    resetQuery() {
      this.dateRange = [];
      this.dateRangefs = [];
      this.serviceStatusValue = 10;
      this.topqueryParams = {
        pageNum: 1,
        pageSize: 10,
        sendstateView:1,
        sort: 2, //0 æœåŠ¡æ—¶é—´(正序)    1 æœåŠ¡æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)
        serviceType: 19,
        searchscope: 3,
        visitCount: 2,
        scopetype: [],
        visitDeptCodes: [],
        // leaveldeptcodes:[],
        leavehospitaldistrictcodes: [],
      };
      this.handleQuery(1);
    },
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      this.ids = selection.map((item) => item.userId);
      this.single = selection.length != 1;
      this.multiple = !selection.length;
    },
    //删除选项
    handleClose(tag) {
      this.dynamicTags.splice(this.dynamicTags.indexOf(tag), 1);
    },
    //触发新增输入
    showInput() {
      this.inputVisible = true;
      this.$nextTick((_) => {
        this.$refs.saveTagInput.$refs.input.focus();
      });
    },
    //获取失去焦点触发
    handleInputConfirm() {
      let inputValue = this.inputValue;
      if (inputValue) {
        this.dynamicTags.push(inputValue);
      }
      this.inputVisible = false;
      this.inputValue = "";
    },
    /** æ–°å¢žæŒ‰é’®æ“ä½œ */
    handleAdd() {
      this.$router.push({
        path: "/followvisit/QuestionnaireTask",
        query: {
          type: 2,
          serviceType: 19,
        },
      });
    },
    //患者360跳转
    gettoken360(sfzh, drcode, drname, id) {
      // const orgname = localStorage.getItem("orgname");
      if (orgname == "南华大学附属第一医院") {
        query360PatInfonh(id).then((res) => {
          if (res.data) {
            window.open(res.data, "_blank");
          } else {
            this.$modal.msgWarning("360查询无结果");
          }
        });
        return;
      } else if (
        orgname == "第一人民医院湖滨院区" ||
        orgname == "第一人民医院吴山院区"
      ) {
        let url = `http://192.200.81.189:9100/blj/view?BINGRENID=${id}&YONGHUID=DBA`;
        window.open(url, "_blank");
        return;
      }
      this.postData.YeWuXX.BingRenXX.ZhengJianHM = sfzh;
      query360PatInfo(this.postData).then((res) => {
        if (res.data.url) {
          window.open(res.data.url, "_blank");
          // this.linkUrl = res.data.url;
        } else {
          this.$modal.msgWarning("360查询无结果");
        }
      });
    },
    /** é‡ç½®å¯†ç æŒ‰é’®æ“ä½œ */
    handleResetPwd(row) {
      this.$prompt('请输入"' + row.userName + '"的新密码', "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        closeOnClickModal: false,
        inputPattern: /^.{5,20}$/,
        inputErrorMessage: "用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´",
      })
        .then(({ value }) => {
          resetUserPwd(row.userId, value).then((response) => {
            this.$modal.msgSuccess("修改成功,新密码是:" + value);
          });
        })
        .catch(() => {});
    },
    // å–消按钮
    cancel() {
      this.Labelchange = false;
      this.reset();
    },
    /** æäº¤æŒ‰é’® */
    submitForm: function () {
      this.$refs["form"].validate((valid) => {
        if (valid) {
          this.form.isoperation = 2;
          this.form.notrequiredFlag = 1;
          alterpatient(this.form)
            .then((response) => {
              console.log(response);
            })
            .then(() => {
              this.getList(1);
              this.$modal.msgSuccess("患者过滤成功");
            });
          this.reset();
          this.Labelchange = false;
        }
      });
    },
    /** åˆ é™¤æŒ‰é’®æ“ä½œ */
    handleDelete(row) {
      const userIds = row.userId || this.ids;
      this.$modal
        .confirm('是否确认删除用户编号为"' + userIds + '"的数据项?')
        .then(function () {
          return delUser(userIds);
        })
        .then(() => {
          this.getList(1);
          this.$modal.msgSuccess("删除成功");
        })
        .catch(() => {});
    },
    // å…¨éƒ¨åœæ­¢
    AllStop() {
      this.$modal
        .confirm("是否停止全部任务?")
        .then(function () {
          return console.log("停止成功");
        })
        .then(() => {
          this.getList(1);
          this.$modal.msgWarning("停止成功");
        })
        .catch(() => {});
    },
    // å…¨éƒ¨å¼€å§‹
    AllStarted() {
      this.$modal
        .confirm("是否开启全部任务?")
        .then(function () {
          return console.log("开启成功");
        })
        .then(() => {
          this.getList(1);
          this.$modal.msgSuccess("开启成功");
        })
        .catch(() => {});
    },
    // ä»»åŠ¡é‡ç½®
    TaskReset() {
      this.$modal
        .confirm("是否重置选中的任务项?")
        .then(function () {
          return console.log("选中成功");
        })
        .then(() => {
          this.getList(1);
          this.$modal.msgSuccess("重置成功");
        })
        .catch(() => {});
    },
    // è®¾ç½®å‘送时间
    Sendtimesetting() {
      this.modificationVisible = true;
    },
    // è·³è½¬è¯¦æƒ…页
    Seedetails(row) {
      let type = "";
      console.log(row, "rwo");
      if (row.type == 1) {
        type = 1;
      }
      this.$router.push({
        path: "/followvisit/record/detailpage/",
        query: {
          taskid: row.taskid,
          patid: row.patid,
          id: row.id,
          Voicetype: type,
          visitCount: this.topqueryParams.visitCount,
        },
      });
    },
    // å†æ¬¡éšè®¿
    followupvisit(row) {
      this.zcform = row;
      this.zcform.endtime = this.formatTime(this.zcform.endtime);
      this.dialogFormVisible = true;
    },
    onSubmit() {},
    // æš‚停服务
    handlestop(row) {
      let objson = row;
      this.$modal
        .confirm(
          '是否确认暂停任务名称为"' +
            row.taskName +
            '患者名称为"' +
            row.sendname +
            '"的数据项?'
        )
        .then(() => {
          getTaskservelist({
            patid: row.patid,
            taskid: row.taskid,
          }).then((res) => {
            if (res.code == 200) {
              objson.sendstate = 4;
              objson.remark = "服务暂停";
              Editsingletaskson(objson).then((res) => {
                if (res.code) {
                  this.$modal.msgSuccess("记录成功");
                  this.getList(1);
                }
              });
            }
          });
        })
        .catch(() => {});
    },
    // æ‚£è€…过滤触发
    handleUpdate(row) {
      particularpatient(row.patid).then((response) => {
        this.form = response.data;
        this.form.filterDrname = store.getters.nickName;
      });
      this.amendtag = true;
      this.Labelchange = true;
    },
    // ä¾¿æ·æŒ‰é’®
    toleadExport(too) {
      if (too == 1) {
        this.topqueryParams.sendstate = 4;
        this.topqueryParams.excep = null;
      } else if (too == 2) {
        this.topqueryParams.excep = 1;
      }
      this.handleQuery();
    },
    /** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
    handleExport() {
      this.topqueryParams.pageNum = null;
      this.topqueryParams.pageSize = null;
      this.download(
        "smartor/serviceSubtask/patItemExport",
        {
          ...this.topqueryParams,
        },
        `user_${new Date().getTime()}.xlsx`
      );
    },
    // å¼‚常列渲染
    tableRowClassName({ row, rowIndex }) {
      if (row.excep == 1) {
        return "warning-row";
      }
      return "";
    },
    // åˆ›å»ºå†æ¬¡éšè®¿æœåŠ¡
    setupsubtask() {
      this.$refs["zcform"].validate((valid) => {
        if (valid) {
          this.zcform.remark =
            this.zcform.remark + "【" + this.getCurrentTime() + "】";
          let form = structuredClone(this.zcform);
          form.visitTime = this.formatTime(form.date1);
          form.finishtime = "";
          if (form.resource) {
            if (form.resource == 2) {
              form.serviceType = 13;
            }
          } else {
            this.$modal.msgError("未选择随访方式");
          }
          form.id = null;
          form.sendstate = 2;
          form.preachform = form.preachformson;
          form.longTask = 0;
          addserviceSubtask(form).then((res) => {
            if (res.code == 200) {
              this.$modal.msgSuccess("创建成功");
            } else {
              this.$modal.msgError("创建失败");
            }
            this.dialogFormVisible = false;
          });
        }
      });
    },
    getCurrentTime() {
      const now = new Date();
      const year = now.getFullYear();
      const month = String(now.getMonth() + 1).padStart(2, "0");
      const day = String(now.getDate()).padStart(2, "0");
      const hours = String(now.getHours()).padStart(2, "0");
      const minutes = String(now.getMinutes()).padStart(2, "0");
      const seconds = String(now.getSeconds()).padStart(2, "0");
      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    },
  },
};
</script>
<style lang="scss" scoped>
.el-button--primary.is-plain {
  color: #ffffff;
  background: #409eff;
  border-color: #4fabe9;
}
.document {
  // width: 100px;
  height: 50px;
}
::v-deep.el-table .warning-row {
  background: #eec4c4;
}
.documentf {
  display: flex;
  justify-content: flex-end;
}
.download {
  text-align: center;
  .el-upload__tip {
    font-size: 23px;
  }
  .el-upload__text {
    font-size: 23px;
  }
}
.uploading {
  margin-top: 20px;
  margin: 20px;
  padding: 30px;
  background: #ffffff;
  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);
}
.el-tag + .el-tag {
  margin-left: 10px;
}
.button-new-tag {
  margin-left: 10px;
  height: 32px;
  line-height: 30px;
  padding-top: 0;
  padding-bottom: 0;
}
.input-new-tag {
  width: 90px;
  margin-left: 10px;
  vertical-align: bottom;
}
.drexamine {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 30px;
  background: #daeaf5;
  img {
    width: 100px;
    height: 100px;
  }
}
.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);
  .topic-dev {
    margin-bottom: 25px;
    font-size: 20px !important;
    .dev-text {
      margin-bottom: 10px;
    }
  }
}
::v-deep.leftvlue .el-card__body {
  background: #f2f8ff;
  color: #324a9b;
}
::v-deep.leftvlue .el-card__body:hover {
  background: #3664d9;
  color: #fff;
  cursor: pointer; /* é¼ æ ‡æ‚¬æµ®æ—¶å˜ä¸ºæ‰‹å½¢ */
}
::v-deep.errleftvlue .el-card__body {
  background: #fdd0d7;
}
::v-deep.errleftvlue .el-card__body:hover {
  background: #f88d96;
  cursor: pointer; /* é¼ æ ‡æ‚¬æµ®æ—¶å˜ä¸ºæ‰‹å½¢ */
}
::v-deep.ysfleftvlue .el-card__body {
  background: #d0fdd8;
}
::v-deep.ysfleftvlue .el-card__body:hover {
  background: #8df8a4;
  cursor: pointer; /* é¼ æ ‡æ‚¬æµ®æ—¶å˜ä¸ºæ‰‹å½¢ */
}
.button-bb {
  font-weight: 500;
  background-color: #2ba05c;
  padding: 5px;
  border-radius: 1px;
  color: #ffffff;
}
.button-xq {
  font-weight: 500;
  background-color: #409eff;
  padding: 5px;
  border-radius: 1px;
  color: #ffffff;
}
.button-sc {
  font-weight: 500;
  background-color: #b3a21f;
  padding: 5px;
  border-radius: 1px;
  color: #ffffff;
}
.button-zx {
  background: #4fabe9;
  padding: 5px;
  border-radius: 1px;
  color: #ffffff;
}
.button-textxga {
  color: #de7897;
}
::v-deep.el-radio-group {
  span {
    font-size: 24px;
  }
}
.filter-warning {
  margin-top: 8px;
  padding: 10px 14px;
  background: #fff7e6;
  border: 1px solid #ffe58f;
  border-radius: 6px;
  color: #d46b08;
  font-size: 18px;
  line-height: 1.6;
  display: flex;
  align-items: flex-start;
  gap: 6px;
}
.filter-warning .el-icon-warning-outline {
  font-size: 16px;
  color: #faad14;
  flex-shrink: 0;
  margin-top: 2px;
}
// é€‰é¡¹å­—体放大
// ::v-deep.el-checkbox-group {
//   span {
//     font-size: 24px;
//   }
// }
</style>
src/views/followvisit/record/detailpage/index.vue
@@ -127,10 +127,9 @@
              </el-tooltip>
            </template>
          </el-table-column>
                <el-table-column
          <el-table-column
            v-if="
              orgname == '景宁畲族自治县人民医院' ||
              orgname == '丽水市中医院'
              orgname == '景宁畲族自治县人民医院' || orgname == '丽水市中医院'
            "
            label="拨号状态"
            width="120"
@@ -305,8 +304,6 @@
            show-overflow-tooltip
          >
          </el-table-column>
          <el-table-column
            prop="bankcardno"
@@ -761,7 +758,13 @@
          <!-- é¡¶éƒ¨æ“ä½œåŒºåŸŸ -->
          <div class="section-header">
            <h3><i class="el-icon-s-operation"></i> äººå·¥å¤„理</h3>
            <div class="header-actions">
              <div v-if="form.doctorOpinion2" class="last-opinion">
                <i class="el-icon-sunny"></i>
                <span class="last-opinion-label">上次的处理意见:</span>
                <span class="last-opinion-text">{{ form.doctorOpinion2 }}</span>
              </div>
              <!-- åŸºç¡€æ“ä½œæŒ‰é’®ç»„ -->
              <div class="action-group basic-actions">
                <el-button type="primary" plain @click="alterpatient('')">
@@ -841,6 +844,8 @@
              </div>
            </div>
          </div>
          <!-- è¡¨å•区域 -->
          <div class="form-content">
@@ -1060,6 +1065,14 @@
                  </el-button>
                  <el-button plain type="info" @click="Editsingletaskson('5')">
                    äººå·¥éšè®¿
                  </el-button>
                  <el-button
                    plain
                    v-if="orgname != '缙云第二人民医院'"
                    type="primary"
                    @click="Editsingletaskson('7')"
                  >
                    å›žé™¢å¤æŸ¥å¤è¯Š
                  </el-button>
                  <el-button
                    type="primary"
@@ -1321,6 +1334,15 @@
                v-model="form.remark2"
              ></el-input>
            </el-form-item>
            <el-form-item label="处理意见">
              <el-input
                class="custom-disabled"
                type="textarea"
                disabled
                v-model="form.doctorOpinion2"
                placeholder="无"
              ></el-input>
            </el-form-item>
          </el-collapse-item>
          <!-- ä¸‹æ¬¡éšè®¿ä¿¡æ¯ - é»˜è®¤å±•å¼€ -->
@@ -1371,7 +1393,10 @@
              </el-col>
            </el-row>
            <el-form-item label="随访类型" prop="resource">
              <el-radio-group v-model="form.resource">
              <el-radio-group
                v-model="form.resource"
                @change="handleResourceChange"
              >
                <el-radio label="1">本病区随访</el-radio>
                <el-radio label="2">随访中心随访</el-radio>
              </el-radio-group>
@@ -1379,11 +1404,12 @@
            <el-form-item label="随访记录" :required="form.resource == '2'">
              <el-input type="textarea" v-model="form.remark"></el-input>
            </el-form-item>
            <el-form-item label="医生处理意见" prop="doctorOpinion">
              <el-input
                type="textarea"
                v-model="form.doctorOpinion"
              ></el-input>
            <el-form-item
              label="医生处理意见"
              prop="doctorOpinion"
              :required="isDoctorOpinionRequired"
            >
              <el-input type="textarea" v-model="form.doctorOpinion"></el-input>
            </el-form-item>
          </el-collapse-item>
        </el-collapse>
@@ -1688,8 +1714,19 @@
            trigger: "change",
          },
        ],
        // åŒ»ç”Ÿå¤„理意见:仅「随访中心随访」+「丽水市中医院」时必填,其余情况不校验
        doctorOpinion: [
          { required: true, message: "请输入医生处理意见", trigger: "blur" },
          {
            validator: (rule, value, callback) => {
              const needRequired =
                this.form.resource == "2" && this.orgname === "丽水市中医院";
              if (needRequired && !String(value || "").trim()) {
                return callback(new Error("请输入医生处理意见"));
              }
              callback();
            },
            trigger: "blur",
          },
        ],
      },
      userrules: {
@@ -1833,6 +1870,10 @@
        failed: "error",
      };
      return typeMap[this.callStatus];
    },
    // åŒ»ç”Ÿå¤„理意见是否必填:随访类型选「随访中心随访」且当前机构为丽水市中医院
    isDoctorOpinionRequired() {
      return this.form.resource == "2" && this.orgname === "丽水市中医院";
    },
    // æ˜¯å¦ä»¥é—®å·å½¢å¼å±•示(非语音服务,或语音服务但无完整语音数据需手动填报)
    showQuestionnaireForm() {
@@ -2394,6 +2435,26 @@
            path: "/followvisit/discharge",
          });
        }
      } else if (this.form.serviceType == 3) {
        if (this.visitCount != 1) {
          this.$router.push({
            path: "/logisticsservice/OutpatientAgain",
          });
        } else {
          this.$router.push({
            path: "/followvisit/discharge",
          });
        }
      } else if (this.form.serviceType == 19) {
        if (this.visitCount != 1) {
          this.$router.push({
            path: "/logisticsservice/operationAgain",
          });
        } else {
          this.$router.push({
            path: "/followvisit/discharge",
          });
        }
      } else {
        // location.reload();
        this.$router.go(-1);
@@ -2762,7 +2823,8 @@
          console.log(this.form.templatename, "templatename");
          this.form.remark = this.form.remark || "";
          this.form.doctorOpinion = this.form.doctorOpinion || "";
          this.form.doctorOpinion2 = this.form.doctorOpinion || "";
          this.form.doctorOpinion = "";
          this.logsheetlist = res.rows[0].serviceSubtaskList;
          this.templateid = this.form.templateid;
          this.selectedTag = this.form.excep;
@@ -3196,6 +3258,14 @@
        }
      });
    },
    // åˆ‡æ¢éšè®¿ç±»åž‹åŽï¼Œé‡ç½®åŒ»ç”Ÿå¤„理意见的校验状态(必填条件随类型变化)
    handleResourceChange() {
      this.$nextTick(() => {
        if (this.$refs.zcform) {
          this.$refs.zcform.clearValidate("doctorOpinion");
        }
      });
    },
    // åˆ›å»ºå†æ¬¡éšè®¿æœåŠ¡
    setupsubtask() {
      this.$refs["zcform"].validate((valid) => {
@@ -3211,6 +3281,10 @@
          this.form.remark =
            this.form.remark + "【" + this.getCurrentTime() + "】";
          let form = structuredClone(this.form);
          // å†æ¬¡éšè®¿å³â€œå›žé™¢å¤æŸ¥å¤è¯Šâ€ï¼šå¤„理意见置为 suggest=7
          if (String(form.suggest) !== "7") {
            form.suggest = "7";
          }
          form.visitTime = this.parseTime(form.date1);
          form.finishtime = "";
          if (form.resource) {
@@ -4248,6 +4322,41 @@
  }
}
/* ä¸Šæ¬¡çš„处理意见 */
.last-opinion {
  display: flex;
  gap: 8px;
  margin-bottom: 20px;
  padding: 8px 14px;
  background: linear-gradient(135deg, #ecfdf5 0%, #f0fdfa 100%);
  border: 1px solid #d1fae5;
  border-left: 4px solid #10b981;
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(16, 185, 129, 0.08);
  line-height: 1.5;
  > i {
    font-size: 18px;
    color: #10b981;
    flex-shrink: 0;
  }
  .last-opinion-label {
    font-size: 13px;
    color: #059669;
    font-weight: 600;
    white-space: nowrap;
    flex-shrink: 0;
  }
  .last-opinion-text {
    font-size: 14px;
    color: #334155;
    word-break: break-word;
    white-space: pre-wrap;
  }
}
/* é€šè¯æŽ§åˆ¶åŒºåŸŸ */
.call-controls {
  margin: 15px 0;
src/views/followvisit/record/index.vue
@@ -70,6 +70,18 @@
            placeholder="请输入诊断名称"
          ></el-input>
        </el-form-item>
        <el-form-item label="主治医生" prop="drname">
          <el-input
            v-model="topqueryParams.drname"
            placeholder="请输入主治医生"
          ></el-input>
        </el-form-item>
        <el-form-item label="经管医生" prop="managementDoctor">
          <el-input
            v-model="topqueryParams.managementDoctor"
            placeholder="请输入经管医生"
          ></el-input>
        </el-form-item>
        <!-- <el-form-item label="服务状态" prop="status">
          <el-select
            v-model="topqueryParams.sendstateView"
@@ -368,6 +380,13 @@
          align="center"
          key="drname"
          prop="drname"
        />
        <el-table-column
          label="经管医生"
          align="center"
          key="managementDoctor"
          prop="managementDoctor"
          width="120"
        />
        <el-table-column
          label="诊断名称"
@@ -964,15 +983,20 @@
        serviceType: 13,
        searchscope: 3,
        sendstateView: 1,
        visitCount: 1,
        sendstate: null,
 sort:
        sort:
          localStorage.getItem("orgname") == "丽水市中医院"
            ? 8
            : localStorage.getItem("orgname") == "景宁畲族自治县人民医院"
            ? 2
            : 1, //0 å‡ºé™¢æ—¶é—´(正序)    1 å‡ºé™¢æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)  7应随访日期(倒序) åº”随访日期(正序)        scopetype: [],
        leaveldeptcodes: [],
        managementDoctor:
          localStorage.getItem("orgname") == "缙云县人民医院" ||
          localStorage.getItem("orgname") == "丽水市中医院"
            ? store.getters.nickName
            : "",
        leavehospitaldistrictcodes: [],
      },
      propss: { multiple: true },
@@ -1399,6 +1423,8 @@
        sort: 2,
        searchscope: 2,
        sendstateView: 1,
        visitCount: 1,
        scopetype: [],
        leaveldeptcodes: [],
        leavehospitaldistrictcodes: [],
@@ -1563,6 +1589,8 @@
          Voicetype: type,
          visitCount: 1,
          again: 1,
          visitCount: this.topqueryParams.visitCount,
        },
      });
    },
src/views/patient/archives/apiAdapter.js
@@ -115,6 +115,11 @@
      address: row.parentAddress || "",
    });
  }
  // éšè®¿å‘¨æœŸï¼šè¯¦æƒ…表 followupPeriod å­˜ JSON æ•°ç»„,统一还原到 archive.followupCycle
  // ï¼ˆåˆ—表主表行没有该字段,这里静默跳过)
  if (row.followupPeriod) {
    target.followupCycle = unpackJson(row.followupPeriod, []);
  }
  if (row.sourceType === "1" || row.sourceType === 1) target.sourcePage = "outpatient";
  return target;
}
@@ -266,6 +271,12 @@
    closeStatus: archive.closeStatus == null ? "0" : String(archive.closeStatus),
  });
  // éšè®¿å‘¨æœŸï¼š5 ç§ä¸“案统一存 followupPeriod(JSON æ•°ç»„)。
  // pregnancy çš„ followupCycle æ”¾åœ¨ pregFormData é‡Œï¼Œç”±ä¸‹æ–¹ pregnancy åˆ†æ”¯å•独处理,这里排除以免被覆盖。
  if (key !== "pregnancy") {
    payload.followupPeriod = packJson(archive.followupCycle || []);
  }
  if (key === "pregnancy") {
    const p = archive.pregFormData || {};
    const reg = p.registration || {};
src/views/patient/archives/childHealth/detail.vue
@@ -173,6 +173,12 @@
        </el-form>
      </el-card>
      <!-- éšè®¿å‘¨æœŸï¼ˆå„¿ä¿ä¸‰ç§ä¸“案通用) -->
      <el-card v-if="archive.subType === 'highrisk' || archive.subType === 'nutrition' || archive.subType === 'learning'" class="info-card" shadow="never" style="margin-top:14px">
        <div slot="header" class="card-header"><span>随访周期</span></div>
        <EditableTable :data.sync="followupCycle" :editing="editing" :columns="followupColumns" auto-seq="seq" />
      </el-card>
        </el-tab-pane>
        <el-tab-pane label="服务记录" name="service">
@@ -205,6 +211,16 @@
        trainDate:"", trainingType:"", trainContent:"", duration:40, trainer:"", progress:"良好" },
      growthData: [], neuroData: [], interventionData: [],
      measureData: [], dietData: [], planData: [],
      followupCycle: [],
      followupColumns: [
        { prop:"seq", label:"序号", width:60 },
        { prop:"planDate", label:"拟随访日期", width:180, type:"date" },
        { prop:"status", label:"状况", width:130, type:"select", options:["已完成","未完成"], default:"未完成" },
        { prop:"method", label:"随访方式", width:110 },
        { prop:"situation", label:"随访情况", minWidth:160 },
        { prop:"followDate", label:"随访日期", width:130, type:"date" },
        { prop:"staff", label:"随访人员", width:110 },
      ],
      growthColumns: [
        { prop:"checkDate", label:"检查日期", width:180, type:"date" },{ prop:"monthAge", label:"月龄", width:80 },
        { prop:"weight", label:"体重(kg)", width:100, type:"number" },{ prop:"height", label:"身高(cm)", width:100, type:"number" },
@@ -297,6 +313,7 @@
          this.archive = archive || null;
          if (this.archive) {
            if (!this.archive.parentInfo) this.archive.parentInfo = {};
            this.followupCycle = this.archive.followupCycle || [];
            if (this.archive.subType === 'highrisk') {
              this.activeTab = "growth";
              this.growthData = this.archive.growthData || getDefaultGrowthData();
@@ -333,6 +350,7 @@
          this.archive.dietData = JSON.parse(JSON.stringify(this.dietData));
          this.archive.planData = JSON.parse(JSON.stringify(this.planData));
        }
        this.archive.followupCycle = JSON.parse(JSON.stringify(this.followupCycle));
        this.loading = true;
        saveArchive(this.archive)
          .then(() => {
src/views/patient/archives/generateConfig/index.vue
@@ -182,6 +182,20 @@
            </el-table-column>
            <el-table-column
              label="随访周期"
              align="center"
              min-width="180"
              :show-overflow-tooltip="true"
            >
              <template slot-scope="scope">
                <span v-if="formatFollowupCycle(scope.row)">
                  {{ formatFollowupCycle(scope.row) }}
                </span>
                <span v-else style="color: #c0c4cc">—</span>
              </template>
            </el-table-column>
            <el-table-column
              label="备注"
              align="center"
              prop="remark"
@@ -241,7 +255,7 @@
    <el-dialog
      :title="dialogTitle"
      :visible.sync="dialogVisible"
      width="45%"
      width="60%"
      :close-on-click-modal="false"
      @close="cancelForm"
    >
@@ -337,6 +351,16 @@
          <div class="form-tip">从疾病诊断表中选择,按 ICD10 ç¼–码匹配患者</div>
        </el-form-item>
        <el-form-item label="随访周期配置">
          <EditableTable
            :data.sync="form.followupCycle"
            :editing="true"
            :columns="followupColumns"
            auto-seq="seq"
          />
          <div class="form-tip">定义该规则匹配后建档的随访计划(随访周期 / æ–¹å¼ / å†…容)</div>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input
            v-model="form.remark"
@@ -375,6 +399,7 @@
} from "@/api/smartor/specificArchiveGenerateConfig";
import { getillnesslist } from "@/api/AiCentre/index";
import OptionalForm from "@/components/OptionalForm";
import EditableTable from "../components/EditableTable.vue";
// éƒ¨é—¨ç±»åž‹ï¼š1 ç§‘室,2 ç—…区
const DEPT_TYPE_OPTIONS = [
@@ -403,7 +428,7 @@
export default {
  name: "GenerateConfig",
  components: { OptionalForm },
  components: { OptionalForm, EditableTable },
  data() {
    // ç§‘室/病区编码 ä¸Ž è¯Šæ–­ç¼–码 è‡³å°‘填写一项,否则该规则无法匹配任何患者
    const validateDeptOrIcd = (rule, value, callback) => {
@@ -420,6 +445,14 @@
      // å¸¸é‡
      DEPT_TYPE_OPTIONS,
      DEPT_CATEGORY_OPTIONS,
      // éšè®¿å‘¨æœŸé…ç½®åˆ—(随访计划模板:序号 / éšè®¿å‘¨æœŸ / éšè®¿æ–¹å¼ / éšè®¿å†…容)
      followupColumns: [
        { prop: "seq", label: "序号", width: 60 },
        { prop: "cycle", label: "随访周期", width: 180 },
        { prop: "method", label: "随访方式", width: 160 },
        { prop: "content", label: "随访内容", minWidth: 240 },
      ],
      // é®ç½©å±‚
      loading: false,
@@ -461,6 +494,7 @@
        archiveType: "",
        deptCodes: [],
        icdList: [],
        followupCycle: [],
        remark: "",
      },
      // è¡¨å•校验
@@ -583,6 +617,7 @@
        archiveType: row.archiveType || "",
        deptCodes: this.parseJsonArray(row.deptCodes),
        icdList: this.resolveIcdCodes(row.icd10Codes),
        followupCycle: this.parseJsonArray(row.followupPeriod),
        remark: row.remark || "",
      };
    },
@@ -657,6 +692,10 @@
          this.form.icdList && this.form.icdList.length
            ? JSON.stringify(this.form.icdList.map((d) => d.icdcode))
            : "[]";
        data.followupPeriod =
          this.form.followupCycle && this.form.followupCycle.length
            ? JSON.stringify(this.form.followupCycle)
            : "[]";
        saveSpecificArchiveGenerateConfig(data)
          .then((response) => {
@@ -694,6 +733,7 @@
        archiveType: "",
        deptCodes: [],
        icdList: [],
        followupCycle: [],
        remark: "",
      };
    },
@@ -759,6 +799,20 @@
        .join("、");
    },
    /** éšè®¿å‘¨æœŸ -> å±•示文本(随访周期 / éšè®¿æ–¹å¼ æ‹¼æŽ¥ï¼‰ */
    formatFollowupCycle(row) {
      const list = this.parseJsonArray(row.followupPeriod);
      if (!list.length) return "";
      return list
        .map((c) => {
          const cycle = c.cycle || "";
          const method = c.method ? "(" + c.method + ")" : "";
          return cycle + method;
        })
        .filter((s) => s.trim())
        .join("、");
    },
    /** è§£æžåŽç«¯è¿”回的 JSON å­—符串(deptCodes / icd10Codes) */
    parseJsonArray(val) {
      if (!val) return [];
src/views/patient/archives/maternalHealth/detail.vue
@@ -173,6 +173,19 @@
            </el-card>
          </template>
          <!-- éšè®¿å‘¨æœŸï¼ˆå›´ç»ç»æœŸMHI) -->
          <template v-if="archive.subType === 'menopause'">
            <el-card class="info-card" shadow="never" style="margin-top:14px">
              <div slot="header" class="card-header"><span>随访周期</span></div>
              <EditableTable
                :data.sync="followupCycle"
                :editing="editing"
                :columns="followupColumns"
                auto-seq="seq"
              />
            </el-card>
          </template>
          <!-- ===== å›´ç»ç»æœŸMHI:6 ä¸ªé‡è¡¨ tab ===== -->
          <template v-if="archive.subType === 'menopause'">
            <el-card class="info-card" shadow="never" style="margin-top:14px">
@@ -404,6 +417,7 @@
    return {
      PREGNANCY_OPTIONS, PREGNANCY_DETAIL_OPTIONS, PREGNANCY_DETAIL_HINTS, UNREGISTERED_REASONS,
      loading: false, archive: null, editing: true, activeTab: "sysQ", mainTab: "detail",
      followupCycle: [],
      followupColumns: [
        { prop: "seq", label: "序号", width: 60 },
        { prop: "planDate", label: "拟随访日期", width: 180, type: "date" },
@@ -536,6 +550,7 @@
                  if (this.archive.formData[k]) Object.assign(this.form[k], this.archive.formData[k]);
                });
              }
              this.followupCycle = this.archive.followupCycle || [];
            }
          }
          if (autoEdit) this.editing = true;
@@ -611,7 +626,10 @@
      if (this.editing) {
        // å…ˆæŠŠé¡µé¢è¡¨å•回填到档案对象,再整体提交
        if (this.archive.subType === "pregnancy") this.archive.pregFormData = JSON.parse(JSON.stringify(this.pregForm));
        if (this.archive.subType === "menopause") this.archive.formData = JSON.parse(JSON.stringify(this.form));
        if (this.archive.subType === "menopause") {
          this.archive.formData = JSON.parse(JSON.stringify(this.form));
          this.archive.followupCycle = JSON.parse(JSON.stringify(this.followupCycle));
        }
        this.loading = true;
        saveArchive(this.archive)
          .then(() => {
src/views/patient/follow/index.vue
@@ -388,11 +388,39 @@
    },
  },
  created() {
    // å½“前机构为丽水市中医院时,建档日期默认最近一个月
    if (localStorage.getItem("orgname") == "丽水市中医院") {
      this.dateRange = this.getLastMonthRange();
      this.queryParams.createTimeStart = this.dateRange[0];
      this.queryParams.createTimeEnd = this.dateRange[1];
    }
    this.getList();
    this.gettabList();
    this.Filterreason = store.getters.Filterreason;
  },
  methods: {
    /** èŽ·å–æœ€è¿‘ä¸€ä¸ªæœˆæ—¶é—´èŒƒå›´ */
    getLastMonthRange() {
      const end = new Date();
      const start = new Date();
      start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
      const formatDate = (date) => {
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, "0");
        const day = String(date.getDate()).padStart(2, "0");
        return `${year}-${month}-${day} 00:00:00`;
      };
      const formatEndDate = (date) => {
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, "0");
        const day = String(date.getDate()).padStart(2, "0");
        return `${year}-${month}-${day} 23:59:59`;
      };
      return [formatDate(start), formatEndDate(end)];
    },
    /** æŸ¥è¯¢æ‚£è€…列表 */
    getList() {
      this.loading = true;
src/views/patient/patient/AwaitingAdmission.vue
@@ -433,6 +433,13 @@
                </div>
              </template>
            </el-table>
            <pagination
              v-show="taskTotal > 0"
              :total="taskTotal"
              :page.sync="taskQueryParams.pageNum"
              :limit.sync="taskQueryParams.pageSize"
              @pagination="loadTaskList"
            />
          </div>
        </el-col>
      </el-row>
@@ -631,6 +638,11 @@
      taskList: [], // ä»»åŠ¡åˆ—è¡¨
      selectedTask: null, // é€‰ä¸­çš„任务列表
      batchLoading: false, // æ‰¹é‡æäº¤åŠ è½½çŠ¶æ€
      taskQueryParams: {
        pageNum: 1,
        pageSize: 20,
      },
      taskTotal: 0,
      deptcode: "",
      Patientrange: [
        {
@@ -1070,22 +1082,24 @@
      // æ˜¾ç¤ºå¼¹çª—
      this.batchTaskVisible = true;
      // èŽ·å–ä»»åŠ¡åˆ—è¡¨
      this.loadTaskList(deptcode);
      // é‡ç½®ä»»åŠ¡åˆ†é¡µå¹¶èŽ·å–ä»»åŠ¡åˆ—è¡¨
      this.taskQueryParams.pageNum = 1;
      this.loadTaskList();
    },
    /** åŠ è½½ä»»åŠ¡åˆ—è¡¨ */
    loadTaskList(deptcode) {
    loadTaskList() {
      this.batchLoading = true;
      let topqueryParams = {
        pageNum: 1,
        pageSize: 10,
        pageNum: this.taskQueryParams.pageNum,
        pageSize: this.taskQueryParams.pageSize,
        serviceType: 3,
        type: 2,
      };
      //         deptcode: deptcode,取消科室限制
      getTasklist(topqueryParams).then((response) => {
        this.taskList = response.rows;
        this.taskTotal = response.total;
        this.batchLoading = false;
      });
    },
src/views/patient/patient/behospitalized.vue
@@ -122,6 +122,20 @@
              æ·»åŠ å»¶ç»­æŠ¤ç†ä»»åŠ¡
            </el-button>
          </el-col>
          <!-- è¯­éŸ³æœåŠ¡è½¬äººå·¥æŒ‰é’® -->
          <el-col :span="1.5">
            <el-button
              type="primary"
              plain
              icon="el-icon-refresh-right"
              size="medium"
              :disabled="multiple"
              v-if="orgname === '第一人民医院湖滨院区'||orgname === '第一人民医院吴山院区'||orgname === '第一人民医院城东院区'"
              @click="handleManualVisit"
            >
              è¯­éŸ³æœåŠ¡è½¬äººå·¥
            </el-button>
          </el-col>
          <!-- <el-col :span="1.5">
            <el-button
              type="success"
@@ -314,7 +328,7 @@
    <!-- æ‰¹é‡æ·»åŠ å»¶ç»­æŠ¤ç†ä»»åŠ¡å¼¹çª— -->
    <!-- æ‰¹é‡æ·»åŠ å»¶ç»­æŠ¤ç†ä»»åŠ¡å¼¹çª— -->
    <el-dialog
      title="批量添加延续护理"
      :title="batchMode === 'manual' ? '语音服务转人工' : '批量添加延续护理'"
      :visible.sync="batchTaskVisible"
      width="90%"
      append-to-body
@@ -391,7 +405,9 @@
        <el-col :span="12">
          <div class="batch-task-section">
            <div class="section-header">
              <h4>延续护理任务</h4>
              <h4>
                {{ batchMode === "manual" ? "人工随访任务" : "延续护理任务" }}
              </h4>
              <div v-if="selectedTask" class="task-selected-hint selected">
                <i class="el-icon-success"></i>
                <span>已选择:{{ selectedTask.taskName }}</span>
@@ -453,7 +469,11 @@
              <template #empty>
                <div class="empty-message">
                  <i class="el-icon-document"></i>
                  <span>暂无可用延续护理任务</span>
                  <span>{{
                    batchMode === "manual"
                      ? "暂无可用人工随访任务"
                      : "暂无可用延续护理任务"
                  }}</span>
                  <span class="empty-tips">请联系管理员创建任务</span>
                </div>
              </template>
@@ -473,7 +493,15 @@
          @click="submitBatchTask"
          :disabled="!selectedTask || batchLoading"
        >
          {{ batchLoading ? "创建中..." : "创建延续护理任务" }}
          {{
            batchLoading
              ? batchMode === "manual"
                ? "转换中..."
                : "创建中..."
              : batchMode === "manual"
              ? "确认转人工"
              : "创建延续护理任务"
          }}
        </el-button>
      </div>
    </el-dialog>
@@ -581,7 +609,7 @@
import { delUser } from "@/api/system/user";
import formatTime from "@/filters/index";
import { listpatient } from "@/api/patient/record";
import { listpatient, addManualVisit } from "@/api/patient/record";
import {
  messagelistpatient,
  alterpatient,
@@ -639,6 +667,7 @@
      initPassword: undefined,
      amendtag: false, //是否修改
      // æ‰¹é‡ä»»åŠ¡ç›¸å…³æ•°æ®
      batchMode: "continue", // å¼¹çª—模式:continue å»¶ç»­æŠ¤ç† / manual è¯­éŸ³æœåŠ¡è½¬äººå·¥
      batchTaskVisible: false, // å¼¹çª—可见性
      selectedPatients: [], // é€‰ä¸­çš„æ‚£è€…列表
      taskList: [], // ä»»åŠ¡åˆ—è¡¨
@@ -669,7 +698,7 @@
          label: "病区患者",
        },
      ],
      orgName: localStorage.getItem("orgname"),
      orgname: localStorage.getItem("orgname"),
      postData: {
        XiaoXiTou: {
@@ -811,6 +840,8 @@
    this.queryParams.leavehospitaldistrictcodes = store.getters.belongWards.map(
      (obj) => obj.districtCode
    );
    console.log(this.orgname);
    this.getList();
    // this.listDept();
    this.gettabList();
@@ -929,37 +960,52 @@
        return;
      }
      // èŽ·å–é€‰ä¸­æ‚£è€…çš„ç§‘å®¤ä¿¡æ¯
      const patientDepts = new Set();
      let deptcode = "";
      this.ids.forEach((patId) => {
        const patient = this.userList.find((item) => item.patid === patId);
        if (patient) {
          patientDepts.add(patient.deptname);
          deptcode = patient.deptcode;
        }
      });
      this.batchMode = "continue";
      // èŽ·å–é€‰ä¸­æ‚£è€…åˆ—è¡¨
      this.selectedPatients = this.userList.filter((item) =>
        this.ids.includes(item.patid)
      );
      this.selectedTask = null;
      // æ˜¾ç¤ºå¼¹çª—
      this.batchTaskVisible = true;
      // èŽ·å–ä»»åŠ¡åˆ—è¡¨
      this.loadTaskList(deptcode);
      this.loadTaskList();
    },
    /** è¯­éŸ³æœåŠ¡è½¬äººå·¥æŒ‰é’®ç‚¹å‡» */
    handleManualVisit() {
      // æ ¡éªŒæ˜¯å¦é€‰ä¸­æ‚£è€…
      if (this.ids.length === 0) {
        this.$modal.msgWarning("请至少选中1名患者");
        return;
      }
      this.batchMode = "manual";
      // èŽ·å–é€‰ä¸­æ‚£è€…åˆ—è¡¨
      this.selectedPatients = this.userList.filter((item) =>
        this.ids.includes(item.patid)
      );
      this.selectedTask = null;
      // æ˜¾ç¤ºå¼¹çª—
      this.batchTaskVisible = true;
      // èŽ·å–ä»»åŠ¡åˆ—è¡¨
      this.loadTaskList();
    },
    /** åŠ è½½ä»»åŠ¡åˆ—è¡¨ */
    loadTaskList(deptcode) {
    loadTaskList() {
      this.batchLoading = true;
      let topqueryParams = {
        pageNum: 1,
        pageSize: 100, // è®¾ç½®è¾ƒå¤§çš„分页获取更多任务
        type: 2, // å»¶ç»­æŠ¤ç†ä»»åŠ¡ç±»åž‹
        pageSize: 1000, // è®¾ç½®è¾ƒå¤§çš„分页获取更多任务
      };
      if (this.batchMode !== "manual") {
        topqueryParams.type = 2; // å»¶ç»­æŠ¤ç†ä»»åŠ¡ç±»åž‹
      }
      topqueryParams.deptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
@@ -968,11 +1014,21 @@
      );
      getTasklist(topqueryParams)
        .then((response) => {
          this.taskList = response.rows;
          this.taskList = response.rows || [];
          if (this.batchMode === "manual") {
            // åªä¿ç•™ã€Œäººå·¥ã€ä»»åŠ¡å½¢å¼ï¼ˆpreachform å« 1)
            this.taskList = this.taskList.filter((task) =>
              this.isManualTask(task)
            );
          }
          this.batchLoading = false;
          if (this.taskList.length === 0) {
            this.$modal.msgWarning("当前科室无可用延续护理任务");
            this.$modal.msgWarning(
              this.batchMode === "manual"
                ? "当前科室无可用人工随访任务"
                : "当前科室无可用延续护理任务"
            );
          }
        })
        .catch((error) => {
@@ -981,16 +1037,38 @@
        });
    },
    /** åˆ¤æ–­æ˜¯å¦ä¸ºã€Œäººå·¥ã€ä»»åŠ¡å½¢å¼ï¼ˆpreachform å« 1) */
    isManualTask(task) {
      if (task.preachform != null && task.preachform !== "") {
        const forms = String(task.preachform)
          .split(",")
          .map((s) => s.trim());
        if (forms.includes("1")) return true;
      }
      if (Array.isArray(task.preachformList)) {
        return task.preachformList.some(
          (item) => String(item.preachform) === "1"
        );
      }
      return false;
    },
    /** å¤„理任务选择变化 */
    handleTaskSelectionChange(currentRow) {
      this.selectedTask = currentRow;
    },
    /** æ‰¹é‡æäº¤å»¶ç»­æŠ¤ç†ä»»åŠ¡ */
    /** æ‰¹é‡æäº¤ï¼ˆå»¶ç»­æŠ¤ç† / è¯­éŸ³æœåŠ¡è½¬äººå·¥ï¼‰ */
    async submitBatchTask() {
      // æ ¡éªŒæ˜¯å¦é€‰ä¸­ä»»åŠ¡
      if (!this.selectedTask) {
        this.$modal.msgWarning("请选择1个任务");
        return;
      }
      // è¯­éŸ³æœåŠ¡è½¬äººå·¥ï¼šèµ°æ–°æŽ¥å£
      if (this.batchMode === "manual") {
        this.submitManualVisit();
        return;
      }
@@ -1066,6 +1144,40 @@
      }
    },
    /** è¯­éŸ³æœåŠ¡è½¬äººå·¥ï¼šåˆ é™¤æœªå®Œæˆéšè®¿ä»»åŠ¡å¹¶é‡æ–°ç”Ÿæˆäººå·¥éšè®¿ä»»åŠ¡ */
    async submitManualVisit() {
      this.batchLoading = true;
      try {
        const params = {
          patids: this.selectedPatients.map((item) => item.patid),
          taskid: this.selectedTask.taskid,
        };
        const response = await addManualVisit(params);
        if (response.code === 200) {
          const data = response.data || {};
          const generated = data.generatedCount || 0;
          const noDischarge = (data.noDischargeRecordPatids || []).length;
          const noArchive = (data.noArchivePatids || []).length;
          const failed = (data.generateFailedPatids || []).length;
          let msg = `成功生成 ${generated} ä¸ªäººå·¥éšè®¿ä»»åŠ¡`;
          if (noDischarge) msg += `,${noDischarge} äººæ— å‡ºé™¢è®°å½•`;
          if (noArchive) msg += `,${noArchive} äººæ— æ‚£è€…档案`;
          if (failed) msg += `,${failed} äººç”Ÿæˆå¤±è´¥`;
          this.$modal.msgSuccess(msg);
          this.batchTaskVisible = false;
          this.getList(); // åˆ·æ–°æ‚£è€…列表
        } else {
          this.$modal.msgError(response.msg || "语音服务转人工失败");
        }
      } catch (error) {
        this.$modal.msgError(
          "语音服务转人工失败:" + (error.message || "网络错误")
        );
      } finally {
        this.batchLoading = false;
      }
    },
    /** è®¡ç®—随访结束时间(在院患者:出院后7天) */
    getFollowUpEndTime(admitDate) {
      if (!admitDate) return "";
src/views/patient/patient/hospital.vue
@@ -500,6 +500,13 @@
                </div>
              </template>
            </el-table>
            <pagination
              v-show="taskTotal > 0"
              :total="taskTotal"
              :page.sync="taskQueryParams.pageNum"
              :limit.sync="taskQueryParams.pageSize"
              @pagination="loadTaskList"
            />
          </div>
        </el-col>
      </el-row>
@@ -676,6 +683,11 @@
      taskList: [], // ä»»åŠ¡åˆ—è¡¨
      selectedTask: null, // é€‰ä¸­çš„任务
      batchLoading: false, // æ‰¹é‡æäº¤åŠ è½½çŠ¶æ€
      taskQueryParams: {
        pageNum: 1,
        pageSize: 20,
      },
      taskTotal: 0,
      // æ—¥æœŸèŒƒå›´
      dateRange: [],
      source: [
@@ -920,8 +932,9 @@
      // æ˜¾ç¤ºå¼¹çª—
      this.batchTaskVisible = true;
      // èŽ·å–ä»»åŠ¡åˆ—è¡¨
      this.loadTaskList(deptcode);
      // é‡ç½®ä»»åŠ¡åˆ†é¡µå¹¶èŽ·å–ä»»åŠ¡åˆ—è¡¨
      this.taskQueryParams.pageNum = 1;
      this.loadTaskList();
    },
    /** è¡¨æ ¼è¡Œæ ·å¼ */
    tableRowClassName({ row, rowIndex }) {
@@ -941,11 +954,11 @@
      }
    },
    /** åŠ è½½ä»»åŠ¡åˆ—è¡¨ */
    loadTaskList(deptcode) {
    loadTaskList() {
      this.batchLoading = true;
      let topqueryParams = {
        pageNum: 1,
        pageSize: 100, // è®¾ç½®è¾ƒå¤§çš„分页获取更多任务
        pageNum: this.taskQueryParams.pageNum,
        pageSize: this.taskQueryParams.pageSize,
        type: 2, // æ ¹æ®å®žé™…情况调整
      };
      topqueryParams.deptcodes = store.getters.belongDepts.map(
@@ -957,6 +970,7 @@
      getTasklist(topqueryParams)
        .then((response) => {
          this.taskList = response.rows;
          this.taskTotal = response.total;
          this.batchLoading = false;
          if (this.taskList.length === 0) {
src/views/patient/patient/outpatient.vue
@@ -168,6 +168,7 @@
          </el-col>
          <el-col :span="1.5">
            <el-button
            v-if="orgname='丽水市妇幼保健院'"
              type="success"
              plain
              icon="el-icon-folder-add"
@@ -475,6 +476,13 @@
                </div>
              </template>
            </el-table>
            <pagination
              v-show="taskTotal > 0"
              :total="taskTotal"
              :page.sync="taskQueryParams.pageNum"
              :limit.sync="taskQueryParams.pageSize"
              @pagination="loadTaskList"
            />
          </div>
        </el-col>
      </el-row>
@@ -681,6 +689,11 @@
      taskList: [], // ä»»åŠ¡åˆ—è¡¨
      selectedTask: null, // é€‰ä¸­çš„任务列表
      batchLoading: false, // æ‰¹é‡æäº¤åŠ è½½çŠ¶æ€
      taskQueryParams: {
        pageNum: 1,
        pageSize: 20,
      },
      taskTotal: 0,
      // æ‚£è€…建档
      archiveDialogVisible: false,
      archivePatients: [],
@@ -695,7 +708,7 @@
          id: 1,
        },
      ],
      orgName: localStorage.getItem("orgname"),
      orgname: localStorage.getItem("orgname"),
      postData: {
        XiaoXiTou: {
          FaSongFCSJC: "ZJHES",
@@ -1149,22 +1162,24 @@
      // æ˜¾ç¤ºå¼¹çª—
      this.batchTaskVisible = true;
      // èŽ·å–ä»»åŠ¡åˆ—è¡¨
      this.loadTaskList(deptcode);
      // é‡ç½®ä»»åŠ¡åˆ†é¡µå¹¶èŽ·å–ä»»åŠ¡åˆ—è¡¨
      this.taskQueryParams.pageNum = 1;
      this.loadTaskList();
    },
    /** åŠ è½½ä»»åŠ¡åˆ—è¡¨ */
    loadTaskList(deptcode) {
    loadTaskList() {
      this.batchLoading = true;
      let topqueryParams = {
        pageNum: 1,
        pageSize: 10,
        pageNum: this.taskQueryParams.pageNum,
        pageSize: this.taskQueryParams.pageSize,
        serviceType: 3,
        type: 2,
      };
      //         deptcode: deptcode,取消科室限制
      getTasklist(topqueryParams).then((response) => {
        this.taskList = response.rows;
        this.taskTotal = response.total;
        this.batchLoading = false;
      });
    },
src/views/patient/propaganda/Missioncreation.vue
@@ -1741,6 +1741,7 @@
      this.leavehospitaldistrictcodes = [];
      this.deptcodesWards = [];
      this.queryParamsdept.taskId = this.form.taskid;
      this.queryParamsdept.taskid = this.form.taskid;
      console.log(1, this.queryParamsdept);
      taskdepthospgetsonlist(this.queryParamsdept).then((res) => {
src/views/patient/propaganda/QuestionnaireTask.vue
@@ -2256,8 +2256,8 @@
      this.deptcodesWards = [];
      this.medicalCodes = [];
      this.medicalOptions = [];
      // this.queryParamsdept.taskId = this.form.taskid;
      this.queryParamsdept.taskId = this.form.taskid;
      this.queryParamsdept.taskid = this.form.taskid;
      if (this.form.appltype == 5) {
        rwztaskdepthospgetsonlist(this.queryParamsdept).then((res) => {
src/views/patient/propaganda/particty.vue
@@ -2280,6 +2280,7 @@
      this.deptcodesWards = [];
      this.medicalCodes = [];
      this.medicalOptions = [];
      this.queryParamsdept.taskid = this.form.taskid;
      this.queryParamsdept.taskId = this.form.taskid;
      if (this.form.appltype == 5) {
src/views/sfstatistics/percentage/components/CombinedFollowUp.vue
@@ -621,6 +621,46 @@
          </el-table-column>
        </el-table-column>
        <!-- ===== éšè®¿æƒ…况(丽水市中医院,从首次随访移入) ===== -->
        <el-table-column align="center" label="随访情况">
          <el-table-column
            label="正常语音"
            align="center"
            width="100"
            prop="taskSituation1"
          />
          <el-table-column
            label="患者拒接或拒访"
            align="center"
            width="100"
            prop="taskSituation2"
          />
          <el-table-column
            label="面访或者接诊"
            align="center"
            width="100"
            prop="taskSituation3"
          />
          <el-table-column
            label="微信随访"
            align="center"
            width="100"
            prop="taskSituation4"
          />
          <el-table-column
            label="随访电话不正确"
            align="center"
            width="100"
            prop="taskSituation5"
          />
          <el-table-column
            label="其他情况不宜随访"
            align="center"
            width="100"
            prop="taskSituation6"
          />
        </el-table-column>
        <!-- ===== æ–°å¢žè®¡ç®—指标 ===== -->
        <el-table-column
          label="随访率"
@@ -662,6 +702,39 @@
            >
              <span class="button-zx">{{ scope.row.manuallyCalled }}</span>
            </el-button>
          </template>
        </el-table-column>
        <el-table-column
          label="再次随访推送人数"
          align="center"
          prop="againFollowUpPushCount"
        >
          <template slot-scope="scope">
            <el-button
              type="text"
              @click="
                handleViewDetails(
                  scope.row,
                  'againFollowUpPushCountInfo',
                  '再次随访推送列表',
                  '1'
                )
              "
            >
              <span class="button-zx">{{
                scope.row.againFollowUpPushCount
              }}</span>
            </el-button>
          </template>
        </el-table-column>
        <el-table-column
          label="追踪随访推送率"
          align="center"
          prop="againFollowUpPushRate"
          :render-header="TooltipAgainFollowUpPushRate"
        >
          <template slot-scope="scope">
            <span>{{ calculateAgainFollowUpPushRate(scope.row) }}</span>
          </template>
        </el-table-column>
      </el-table>
@@ -979,6 +1052,14 @@
      return ((successAgain / denominator) * 100).toFixed(2) + "%";
    },
    // è¿½è¸ªéšè®¿æŽ¨é€çއ = å†æ¬¡éšè®¿æŽ¨é€äººæ•° / å‡ºé™¢äººæ¬¡
    calculateAgainFollowUpPushRate(row) {
      const pushCount = Number(row.againFollowUpPushCount) || 0;
      const discharge = Number(row.dischargeCount) || 0;
      if (discharge <= 0) return "0.00%";
      return ((pushCount / discharge) * 100).toFixed(2) + "%";
    },
    // ===================== æ±‡æ€» =====================
    getSummaries(param) {
      const { columns, data } = param;
@@ -1020,6 +1101,19 @@
            sums[index] =
              (values.reduce((a, b) => a + b, 0) / values.length).toFixed(2) +
              "%";
        } else if (prop === "againFollowUpPushRate") {
          const totalPush = data.reduce(
            (s, row) => s + (Number(row.againFollowUpPushCount) || 0),
            0
          );
          const totalDischarge = data.reduce(
            (s, row) => s + (Number(row.dischargeCount) || 0),
            0
          );
          sums[index] =
            totalDischarge > 0
              ? ((totalPush / totalDischarge) * 100).toFixed(2) + "%"
              : "0.00%";
        } else {
          // æ•°å­—列求和
          const values = data.map((item) => Number(item[prop]) || 0);
@@ -1714,6 +1808,19 @@
        [h("span", column.label)]
      );
    },
    TooltipAgainFollowUpPushRate(h, { column }) {
      return h(
        "el-tooltip",
        {
          props: {
            content: "再次随访推送人数/出院人次",
            placement: "top",
            effect: "dark",
          },
        },
        [h("span", column.label)]
      );
    },
  },
};
</script>
src/views/sfstatistics/percentage/components/FirstFollowUp.vue
@@ -156,6 +156,7 @@
                  </template>
                </el-table-column>
                <el-table-column
                  v-if="!isTargetHospital"
                  label="待随访"
                  align="center"
                  key="pendingFollowUp"
@@ -179,6 +180,62 @@
                      }}</span>
                    </el-button>
                  </template>
                </el-table-column>
                <el-table-column
                  v-if="isTargetHospital"
                  label="待随访"
                  align="center"
                >
                  <el-table-column
                    label="待发送"
                    align="center"
                    key="pendingFollowUpWaitSend"
                    prop="pendingFollowUpWaitSend"
                  >
                    <template slot-scope="scope">
                      <el-button
                        size="medium"
                        type="text"
                        @click="
                          handleViewDetails(
                            scope.row,
                            'pendingFollowUpWaitSendInfo',
                            '待发送列表',
                            '1'
                          )
                        "
                      >
                        <span class="button-zx">{{
                          scope.row.pendingFollowUpWaitSend
                        }}</span>
                      </el-button>
                    </template>
                  </el-table-column>
                  <el-table-column
                    label="已发送"
                    align="center"
                    key="pendingFollowUpSent"
                    prop="pendingFollowUpSent"
                  >
                    <template slot-scope="scope">
                      <el-button
                        size="medium"
                        type="text"
                        @click="
                          handleViewDetails(
                            scope.row,
                            'pendingFollowUpSentInfo',
                            '已发送列表',
                            '1'
                          )
                        "
                      >
                        <span class="button-zx">{{
                          scope.row.pendingFollowUpSent
                        }}</span>
                      </el-button>
                    </template>
                  </el-table-column>
                </el-table-column>
                <el-table-column
                  label="随访成功"
@@ -206,6 +263,7 @@
                  </template>
                </el-table-column>
                <el-table-column
                  v-if="!isTargetHospital"
                  label="随访失败"
                  align="center"
                  key="followUpFail"
@@ -272,9 +330,9 @@
                      type="text"
                      @click="handleSeeDetails(scope.row)"
                    >
                      <span class="button-zx"
                        >{{ (Number(scope.row.rate) * 100).toFixed(2) }}%</span
                      >
                      <span class="button-zx">{{
                        getTimelyRateText(scope.row)
                      }}</span>
                    </el-button>
                  </template>
                </el-table-column>
@@ -476,6 +534,7 @@
            </template>
          </el-table-column>
          <el-table-column
            v-if="!isTargetHospital"
            label="待随访"
            align="center"
            key="pendingFollowUp"
@@ -496,6 +555,60 @@
                <span class="button-zx">{{ scope.row.pendingFollowUp }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="待随访"
            align="center"
          >
            <el-table-column
              label="待发送"
              align="center"
              key="pendingFollowUpWaitSend"
              prop="pendingFollowUpWaitSend"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'pendingFollowUpWaitSendInfo',
                      '待发送列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.pendingFollowUpWaitSend
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
            <el-table-column
              label="已发送"
              align="center"
              key="pendingFollowUpSent"
              prop="pendingFollowUpSent"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'pendingFollowUpSentInfo',
                      '已发送列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.pendingFollowUpSent
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            label="随访成功"
@@ -520,6 +633,7 @@
            </template>
          </el-table-column>
          <el-table-column
            v-if="!isTargetHospital"
            label="随访失败"
            align="center"
            key="followUpFail"
@@ -583,73 +697,39 @@
                type="text"
                @click="handleSeeDetails(scope.row)"
              >
                <span class="button-zx"
                  >{{ (Number(scope.row.rate) * 100).toFixed(2) }}%</span
                >
                <span class="button-zx">{{
                  getTimelyRateText(scope.row)
                }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="人工"
            v-if="orgname == '缙云县人民医院'"
            label="及时随访人次"
            align="center"
            key="timelyFollowUp"
            prop="timelyFollowUp"
            width="138"
          >
            <el-table-column
              label="发送完成"
              align="center"
              key="manual"
              prop="manual"
              :render-header="Tooltipfswc"
            >
              <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="manualSuccess"
              prop="manualSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'manualSuccessInfo',
                      '最终完成人工随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.manualSuccess
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
            <template slot-scope="scope">
              <el-button
                size="medium"
                type="text"
                @click="
                  handleViewDetails(
                    scope.row,
                    'timelyFollowUpInfo',
                    '及时随访列表'
                  )
                "
              >
                <span class="button-zx">{{ scope.row.timelyFollowUp }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="语音"
            align="center"
          >
          <el-table-column v-if="isTargetHospital" label="语音" align="center">
            <el-table-column
              label="发送完成"
              label="发送数"
              align="center"
              key="voice"
              prop="voice"
@@ -672,7 +752,7 @@
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              label="成功数"
              align="center"
              key="voiceSuccess"
              prop="voiceSuccess"
@@ -690,74 +770,14 @@
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.voiceSuccess
                  }}</span>
                  <span class="button-zx">{{ scope.row.voiceSuccess }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="短信"
            align="center"
          >
          <el-table-column v-if="isTargetHospital" label="微信" align="center">
            <el-table-column
              label="发送完成"
              align="center"
              key="sms"
              prop="sms"
              :render-header="Tooltipfswc"
            >
              <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="smsSuccess"
              prop="smsSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'smsSuccessInfo',
                      '最终完成短信随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.smsSuccess
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="微信"
            align="center"
          >
            <el-table-column
              label="发送完成"
              label="发送数"
              align="center"
              key="weChat"
              prop="weChat"
@@ -780,7 +800,7 @@
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              label="成功数"
              align="center"
              key="weChatSuccess"
              prop="weChatSuccess"
@@ -798,13 +818,108 @@
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.weChatSuccess
                  }}</span>
                  <span class="button-zx">{{ scope.row.weChatSuccess }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column v-if="isTargetHospital" label="短信" align="center">
            <el-table-column
              label="发送数"
              align="center"
              key="sms"
              prop="sms"
              :render-header="Tooltipfswc"
            >
              <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="smsSuccess"
              prop="smsSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'smsSuccessInfo',
                      '最终完成短信随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{ scope.row.smsSuccess }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column v-if="isTargetHospital" label="人工" align="center">
            <el-table-column
              label="发送数"
              align="center"
              key="manual"
              prop="manual"
              :render-header="Tooltipfswc"
            >
              <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="manualSuccess"
              prop="manualSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'manualSuccessInfo',
                      '最终完成人工随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{ scope.row.manualSuccess }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            v-if="!isTargetHospital"
            label="人工"
@@ -881,11 +996,9 @@
          </el-table-column>
        </el-table-column>
        <!-- éšè®¿æƒ…况列(仅丽水市中医院显示) -->
        <!-- éšè®¿æƒ…况列(丽水市中医院已移到综合统计,此处仅景宁畲族自治县人民医院显示) -->
        <el-table-column
          v-if="
            orgname == '丽水市中医院' || orgname == '景宁畲族自治县人民医院'
          "
          v-if="orgname == '景宁畲族自治县人民医院'"
          align="center"
          label="随访情况"
        >
@@ -1090,8 +1203,8 @@
      const need = Number(needFollowUp) || 0;
      const pending = Number(pendingFollowUp) || 0;
      // åˆ†æ¯ = éœ€éšè®¿ - å¾…随访
      const denominator = need - pending;
      // ç›®æ ‡åŒ»é™¢ï¼šæˆåŠŸçŽ‡ = éšè®¿æˆåŠŸ / éœ€éšè®¿ï¼›å…¶ä»–:随访成功 / (需随访 - å¾…随访)
      const denominator = this.isTargetHospital ? need : need - pending;
      if (denominator <= 0) {
        return "0.00%";
@@ -1099,6 +1212,26 @@
      const rate = (success / denominator) * 100;
      return rate.toFixed(2) + "%";
    },
    // è®¡ç®—及时率(目标医院):(需随访 - äººå·¥è§„定时间内没打过电话) / éœ€éšè®¿
    calculateTimelyRate(needFollowUp, manualCallTimeout) {
      const need = Number(needFollowUp) || 0;
      const timeout = Number(manualCallTimeout) || 0;
      if (need <= 0) {
        return "0.00%";
      }
      const rate = ((need - timeout) / need) * 100;
      return (rate < 0 ? 0 : rate).toFixed(2) + "%";
    },
    // åŠæ—¶çŽ‡å±•ç¤ºæ–‡æ¡ˆï¼šç›®æ ‡åŒ»é™¢å‰ç«¯è®¡ç®—ï¼Œå…¶ä»–ç”¨åŽç«¯ rate å­—段
    getTimelyRateText(row) {
      if (this.isTargetHospital) {
        return this.calculateTimelyRate(
          row.needFollowUp,
          row.manualCallTimeout
        );
      }
      return (Number(row.rate) * 100).toFixed(2) + "%";
    },
    // åœ¨ç»Ÿè®¡æ±‡æ€»æ–¹æ³•中处理成功率
    getSummaries(param) {
@@ -1129,17 +1262,51 @@
            return sum + (Number(item.pendingFollowUp) || 0);
          }, 0);
          const denominator = totalNeed - totalPending;
          const denominator = this.isTargetHospital
            ? totalNeed
            : totalNeed - totalPending;
          if (denominator > 0) {
            sums[index] = ((totalSuccess / denominator) * 100).toFixed(2) + "%";
          } else {
            sums[index] = "0.00%";
          }
        } else if (
          column.property === "followUpRate" ||
          column.property === "rate"
        ) {
        } else if (column.property === "rate") {
          if (this.isTargetHospital) {
            const totalNeed = data.reduce(
              (sum, item) => sum + (Number(item.needFollowUp) || 0),
              0
            );
            const totalTimeout = data.reduce(
              (sum, item) => sum + (Number(item.manualCallTimeout) || 0),
              0
            );
            sums[index] = this.calculateTimelyRate(totalNeed, totalTimeout);
          } else {
            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 if (column.property === "followUpRate") {
          const percentageValues = data
            .map((item) => {
              const value = item[column.property];
@@ -1185,7 +1352,9 @@
        "el-tooltip",
        {
          props: {
            content: "随访成功/(需随访-待随访)",
            content: this.isTargetHospital
              ? "随访成功/需随访"
              : "随访成功/(需随访-待随访)",
            placement: "top",
            effect: "dark",
          },
@@ -1198,8 +1367,9 @@
        "el-tooltip",
        {
          props: {
            content:
              "(出院时间+任务周期)小于完成时间即为超时;(需随访-超时)/需随访",
            content: this.isTargetHospital
              ? "及时率=(需随访-人工规定时间内没打过电话)/需随访"
              : "(出院时间+任务周期)小于完成时间即为超时;(需随访-超时)/需随访",
            placement: "top",
            effect: "dark",
          },
@@ -1275,17 +1445,51 @@
            return sum + (Number(item.pendingFollowUp) || 0);
          }, 0);
          const denominator = totalNeed - totalPending;
          const denominator = this.isTargetHospital
            ? totalNeed
            : totalNeed - totalPending;
          if (denominator > 0) {
            sums[index] = ((totalSuccess / denominator) * 100).toFixed(2) + "%";
          } else {
            sums[index] = "0.00%";
          }
        } else if (
          column.property === "followUpRate" ||
          column.property === "rate"
        ) {
        } else if (column.property === "rate") {
          if (this.isTargetHospital) {
            const totalNeed = data.reduce(
              (sum, item) => sum + (Number(item.needFollowUp) || 0),
              0
            );
            const totalTimeout = data.reduce(
              (sum, item) => sum + (Number(item.manualCallTimeout) || 0),
              0
            );
            sums[index] = this.calculateTimelyRate(totalNeed, totalTimeout);
          } else {
            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 if (column.property === "followUpRate") {
          const percentageValues = data
            .map((item) => {
              const value = item[column.property];
@@ -1629,13 +1833,12 @@
        },
      };
      // æ ‡é¢˜
      worksheet.mergeCells(1, 1, 1, 10);
      worksheet.getCell(1, 1).value = `${areaName}医生随访列表`;
      worksheet.getCell(1, 1).style = titleStyle;
      worksheet.getRow(1).height = 30;
      const graded = this.isTargetHospital;
      // æˆåŠŸçŽ‡ï¼šæ™¯å®ç•²æ—è‡ªæ²»åŽ¿äººæ°‘åŒ»é™¢ä¸å±•ç¤ºï¼›åŠæ—¶çŽ‡ï¼šä¸½æ°´å¸‚ä¸­åŒ»é™¢ä¸å±•ç¤º
      const showSuccessRate = this.orgname != "景宁畲族自治县人民医院";
      const showTimelyRate = this.orgname != "丽水市中医院";
      // è¡¨å¤´
      // åˆ—定义(与展开的医生子表保持一致)
      const headers = [
        "医生姓名",
        "科室",
@@ -1643,13 +1846,26 @@
        "无需随访",
        "应随访",
        "需随访",
        "待随访",
        ...(graded ? ["待发送", "已发送"] : ["待随访"]),
        "随访成功",
        "随访失败",
        "成功率", // æ–°å¢ž
        "随访率", // åŽŸæ¥åœ¨æˆåŠŸçŽ‡ä½ç½®
        ...(graded ? [] : ["随访失败"]),
        ...(showSuccessRate ? ["成功率"] : []),
        "随访率",
        ...(showTimelyRate ? ["及时率"] : []),
        "人工",
        "语音",
        "短信",
        "微信",
      ];
      const totalCols = headers.length;
      // æ ‡é¢˜
      worksheet.mergeCells(1, 1, 1, totalCols);
      worksheet.getCell(1, 1).value = `${areaName}医生随访列表`;
      worksheet.getCell(1, 1).style = titleStyle;
      worksheet.getRow(1).height = 30;
      // è¡¨å¤´
      const headerRow = worksheet.addRow(headers);
      headerRow.eachCell((cell) => {
        cell.style = headerStyle;
@@ -1665,15 +1881,26 @@
          item.nonFollowUp,
          item.followUpNeeded,
          item.needFollowUp,
          item.pendingFollowUp,
          ...(graded
            ? [item.pendingFollowUpWaitSend || 0, item.pendingFollowUpSent || 0]
            : [item.pendingFollowUp || 0]),
          item.followUpSuccess,
          item.followUpFail,
          this.calculateSuccessRate(
            item.followUpSuccess,
            item.needFollowUp,
            item.pendingFollowUp
          ),
          ...(graded ? [] : [item.followUpFail || 0]),
          ...(showSuccessRate
            ? [
                this.calculateSuccessRate(
                  item.followUpSuccess,
                  item.needFollowUp,
                  item.pendingFollowUp
                ),
              ]
            : []),
          item.followUpRate,
          ...(showTimelyRate ? [this.getTimelyRateText(item)] : []),
          item.manual || 0,
          item.voice || 0,
          item.sms || 0,
          item.weChat || 0,
        ]);
        row.eachCell((cell) => {
          cell.style = cellStyle;
@@ -1691,58 +1918,66 @@
        };
      });
      // åˆ—宽
      worksheet.columns = [
        { width: 15 },
        { width: 15 },
        { width: 12 },
        { width: 12 },
        { width: 12 },
        { width: 12 },
        { width: 12 },
        { width: 12 },
        { width: 12 },
        { width: 12 }, // æˆåŠŸçŽ‡
        { width: 12 }, // éšè®¿çއ
      ];
      // åˆ—宽(前两列姓名/科室较宽,其余统一)
      const widths = [15, 15];
      headers.slice(2).forEach(() => widths.push(12));
      worksheet.columns = widths.map((width) => ({ width }));
    },
    getDoctorExportSummary(data) {
      const sums = ["小计"];
      const graded = this.isTargetHospital;
      const showSuccessRate = this.orgname != "景宁畲族自治县人民医院";
      const showTimelyRate = this.orgname != "丽水市中医院";
      const keys = [
        "dischargeCount",
        "nonFollowUp",
        "followUpNeeded",
        "needFollowUp",
        "pendingFollowUp",
        "followUpSuccess",
        "followUpFail",
      ];
      const sums = ["小计", "-"];
      // æ•°å€¼åˆ—求和
      const keys = graded
        ? [
            "dischargeCount",
            "nonFollowUp",
            "followUpNeeded",
            "needFollowUp",
            "pendingFollowUpWaitSend",
            "pendingFollowUpSent",
            "followUpSuccess",
          ]
        : [
            "dischargeCount",
            "nonFollowUp",
            "followUpNeeded",
            "needFollowUp",
            "pendingFollowUp",
            "followUpSuccess",
            "followUpFail",
          ];
      keys.forEach((key) => {
        sums.push(data.reduce((t, r) => t + (Number(r[key]) || 0), 0));
      });
      // æˆåŠŸçŽ‡ï¼ˆå¹³å‡å€¼ï¼‰
      const successRates = data
        .map((item) => {
          const success = Number(item.followUpSuccess) || 0;
          const need = Number(item.needFollowUp) || 0;
          const pending = Number(item.pendingFollowUp) || 0;
          const denominator = need - pending;
          if (denominator <= 0) return 0;
          return success / denominator;
        })
        .filter((rate) => !isNaN(rate));
      if (showSuccessRate) {
        const successRates = data
          .map((item) => {
            const success = Number(item.followUpSuccess) || 0;
            const need = Number(item.needFollowUp) || 0;
            const pending = Number(item.pendingFollowUp) || 0;
            const denominator = graded ? need : need - pending;
            if (denominator <= 0) return 0;
            return success / denominator;
          })
          .filter((rate) => !isNaN(rate));
      sums.push(
        successRates.length
          ? (
              (successRates.reduce((a, b) => a + b, 0) / successRates.length) *
              100
            ).toFixed(2) + "%"
          : "0.00%"
      );
        sums.push(
          successRates.length
            ? (
                (successRates.reduce((a, b) => a + b, 0) /
                  successRates.length) *
                100
              ).toFixed(2) + "%"
            : "0.00%"
        );
      }
      // éšè®¿çŽ‡ï¼ˆå¹³å‡å€¼ï¼‰
      const followUpRates = data
@@ -1758,6 +1993,38 @@
            ).toFixed(2) + "%"
          : "0.00%"
      );
      // åŠæ—¶çŽ‡ï¼ˆç›®æ ‡åŒ»é™¢è®¡ç®—ï¼Œå…¶ä»–å– rate å¹³å‡ï¼‰
      if (showTimelyRate) {
        if (graded) {
          const totalNeed = data.reduce(
            (s, r) => s + (Number(r.needFollowUp) || 0),
            0
          );
          const totalTimeout = data.reduce(
            (s, r) => s + (Number(r.manualCallTimeout) || 0),
            0
          );
          sums.push(this.calculateTimelyRate(totalNeed, totalTimeout));
        } else {
          const rateValues = data
            .map((i) => this.extractPercentageValue(i.rate))
            .filter(Boolean);
          sums.push(
            rateValues.length
              ? (
                  (rateValues.reduce((a, b) => a + b, 0) / rateValues.length) *
                  100
                ).toFixed(2) + "%"
              : "0.00%"
          );
        }
      }
      // äººå·¥/语音/短信/微信
      ["manual", "voice", "sms", "weChat"].forEach((key) => {
        sums.push(data.reduce((t, r) => t + (Number(r[key]) || 0), 0));
      });
      return sums;
    },
@@ -1835,7 +2102,7 @@
          right: { style: "thin", color: { argb: "FFD0D0D0" } },
        },
      };
      // æ˜¯å¦ä¸ºç›®æ ‡åŒ»é™¢ï¼ˆäººå·¥/语音/短信/微信 æŒ‰ å‘送完成/最终完成 åˆ†çº§å±•示)
      // æ˜¯å¦ä¸ºç›®æ ‡åŒ»é™¢ï¼ˆäººå·¥/语音/短信/微信 æŒ‰ å‘送数/成功数 åˆ†çº§å±•示)
      const graded = this.isTargetHospital;
      const baseHeaders = [
@@ -1846,15 +2113,25 @@
        "无需随访人次",
        "应随访人次",
      ];
      const coreHeaders = [
        "需随访",
        "待随访",
        "随访成功",
        "随访失败",
        "成功率",
        "随访率",
        "及时率",
      ];
      const coreHeaders = this.isTargetHospital
        ? [
            "需随访",
            "待发送",
            "已发送",
            "随访成功",
            "成功率",
            "随访率",
            "及时率",
          ]
        : [
            "需随访",
            "待随访",
            "随访成功",
            "随访失败",
            "成功率",
            "随访率",
            "及时率",
          ];
      const methodGroups = this.getMethodColumns();
      const baseCount = baseHeaders.length; // 6
@@ -1890,14 +2167,37 @@
        worksheet.getCell(2, coreStartCol).value = serviceTypeName;
        worksheet.getCell(2, coreStartCol).style = headerStyle;
        // ç¬¬3行:随访核心列纵向合并 + éšè®¿æ–¹å¼åˆ†ç»„横向合并
        for (let k = 0; k < coreCount; k++) {
          const col = coreStartCol + k;
        // ç¬¬3行:随访核心列(目标医院“待随访”下嵌套“待发送/已发送”)
        worksheet.mergeCells(3, coreStartCol, headerEndRow, coreStartCol);
        worksheet.getCell(3, coreStartCol).value = "需随访";
        worksheet.getCell(3, coreStartCol).style = headerStyle;
        worksheet.mergeCells(3, coreStartCol + 1, 3, coreStartCol + 2);
        worksheet.getCell(3, coreStartCol + 1).value = "待随访";
        worksheet.getCell(3, coreStartCol + 1).style = headerStyle;
        worksheet.mergeCells(
          3,
          coreStartCol + 3,
          headerEndRow,
          coreStartCol + 3
        );
        worksheet.getCell(3, coreStartCol + 3).value = "随访成功";
        worksheet.getCell(3, coreStartCol + 3).style = headerStyle;
        ["成功率", "随访率", "及时率"].forEach((label, i) => {
          const col = coreStartCol + 4 + i;
          worksheet.mergeCells(3, col, headerEndRow, col);
          const cell = worksheet.getCell(3, col);
          cell.value = coreHeaders[k];
          cell.style = headerStyle;
        }
          worksheet.getCell(3, col).value = label;
          worksheet.getCell(3, col).style = headerStyle;
        });
        // ç¬¬4行:待发送/已发送 å­åˆ—
        worksheet.getCell(4, coreStartCol + 1).value = "待发送";
        worksheet.getCell(4, coreStartCol + 1).style = headerStyle;
        worksheet.getCell(4, coreStartCol + 2).value = "已发送";
        worksheet.getCell(4, coreStartCol + 2).style = headerStyle;
        let mc = methodStartCol;
        methodGroups.forEach((g) => {
          if (g.fields.length > 1) {
@@ -1909,7 +2209,7 @@
          mc += g.fields.length;
        });
        // ç¬¬4行:随访方式子列(发送完成/最终完成)
        // ç¬¬4行:随访方式子列(发送数/成功数)
        mc = methodStartCol;
        methodGroups.forEach((g) => {
          g.fields.forEach((field, idx) => {
@@ -1966,9 +2266,13 @@
          item.nonFollowUp || 0,
          item.followUpNeeded || 0,
          item.needFollowUp || 0,
          item.pendingFollowUp || 0,
          // å¾…随访:目标医院拆为待发送/已发送,其他用待随访
          ...(this.isTargetHospital
            ? [item.pendingFollowUpWaitSend || 0, item.pendingFollowUpSent || 0]
            : [item.pendingFollowUp || 0]),
          item.followUpSuccess || 0,
          item.followUpFail || 0,
          // éšè®¿å¤±è´¥ï¼šç›®æ ‡åŒ»é™¢ä¸æ˜¾ç¤º
          ...(this.isTargetHospital ? [] : [item.followUpFail || 0]),
          // æˆåŠŸçŽ‡ - éœ€è¦åŠ¨æ€è®¡ç®—
          this.calculateSuccessRate(
            item.followUpSuccess,
@@ -1976,7 +2280,7 @@
            item.pendingFollowUp
          ),
          item.followUpRate || "0%", // éšè®¿çއ
          item.rate ? (Number(item.rate) * 100).toFixed(2) + "%" : "0%", // åŠæ—¶çއ
          this.getTimelyRateText(item), // åŠæ—¶çއ
        ];
        methodGroups.forEach((g) => {
          g.fields.forEach((field) => {
@@ -2010,13 +2314,29 @@
      worksheet.columns = widths.map((width) => ({ width }));
    },
    getMethodColumns() {
      // éšè®¿æ–¹å¼åˆ—配置:目标医院按“发送完成/最终完成”分级展示
      // éšè®¿æ–¹å¼åˆ—配置:目标医院按“发送数/成功数”分级展示
      if (this.isTargetHospital) {
        return [
          { label: "人工", fields: ["manual", "manualSuccess"], sub: ["发送完成", "最终完成"] },
          { label: "语音", fields: ["voice", "voiceSuccess"], sub: ["发送完成", "最终完成"] },
          { label: "短信", fields: ["sms", "smsSuccess"], sub: ["发送完成", "最终完成"] },
          { label: "微信", fields: ["weChat", "weChatSuccess"], sub: ["发送完成", "最终完成"] },
          {
            label: "人工",
            fields: ["manual", "manualSuccess"],
            sub: ["发送数", "成功数"],
          },
          {
            label: "语音",
            fields: ["voice", "voiceSuccess"],
            sub: ["发送数", "成功数"],
          },
          {
            label: "短信",
            fields: ["sms", "smsSuccess"],
            sub: ["发送数", "成功数"],
          },
          {
            label: "微信",
            fields: ["weChat", "weChatSuccess"],
            sub: ["发送数", "成功数"],
          },
        ];
      }
      return [
@@ -2036,9 +2356,9 @@
        0, // 4: nonFollowUp
        0, // 5: followUpNeeded
        0, // 6: needFollowUp
        0, // 7: pendingFollowUp
        0, // 8: followUpSuccess
        0, // 9: followUpFail
        0, // 7: å¾…发送/待随访
        0, // 8: å·²å‘送/随访成功
        0, // 9: éšè®¿æˆåŠŸ/随访失败
        "0%", // 10: æˆåŠŸçŽ‡
        "0%", // 11: éšè®¿çއ
        "0%", // 12: åŠæ—¶çއ
@@ -2056,19 +2376,27 @@
        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;
        if (this.isTargetHospital) {
          summaries[7] += Number(item.pendingFollowUpWaitSend) || 0;
          summaries[8] += Number(item.pendingFollowUpSent) || 0;
          summaries[9] += Number(item.followUpSuccess) || 0;
        } else {
          summaries[7] += Number(item.pendingFollowUp) || 0;
          summaries[8] += Number(item.followUpSuccess) || 0;
          summaries[9] += Number(item.followUpFail) || 0;
        }
        methodFields.forEach((field, i) => {
          summaries[methodStartIndex + i] += Number(item[field]) || 0;
        });
      });
      // æˆåŠŸçŽ‡è®¡ç®—
      const totalSuccess = summaries[8]; // followUpSuccess的总和
      // æˆåŠŸçŽ‡è®¡ç®—ï¼šéšè®¿æˆåŠŸ / éœ€éšè®¿ï¼ˆç›®æ ‡åŒ»é™¢ï¼‰ï¼Œå¦åˆ™ éšè®¿æˆåŠŸ / (需随访-待随访)
      const totalSuccess = this.isTargetHospital ? summaries[9] : summaries[8];
      const totalNeed = summaries[6]; // needFollowUp的总和
      const totalPending = summaries[7]; // pendingFollowUp的总和
      const denominator = totalNeed - totalPending;
      const totalPending = summaries[7]; // å¾…发送/待随访的总和
      const denominator = this.isTargetHospital
        ? totalNeed
        : totalNeed - totalPending;
      if (denominator > 0) {
        summaries[10] = ((totalSuccess / denominator) * 100).toFixed(2) + "%";
@@ -2088,15 +2416,23 @@
        summaries[11] = (avgFollowUpRate * 100).toFixed(2) + "%";
      }
      // åŠæ—¶çŽ‡è®¡ç®—
      const rateValues = this.tableData
        .map((item) => this.extractPercentageValue(item.rate))
        .filter((value) => value !== null);
      // åŠæ—¶çŽ‡è®¡ç®—ï¼šç›®æ ‡åŒ»é™¢ (需随访-人工超时)/需随访,其他取后端 rate å¹³å‡
      if (this.isTargetHospital) {
        const totalTimeout = this.tableData.reduce(
          (sum, item) => sum + (Number(item.manualCallTimeout) || 0),
          0
        );
        summaries[12] = this.calculateTimelyRate(totalNeed, totalTimeout);
      } else {
        const rateValues = this.tableData
          .map((item) => this.extractPercentageValue(item.rate))
          .filter((value) => value !== null);
      if (rateValues.length > 0) {
        const avgRate =
          rateValues.reduce((sum, val) => sum + val, 0) / rateValues.length;
        summaries[12] = (avgRate * 100).toFixed(2) + "%";
        if (rateValues.length > 0) {
          const avgRate =
            rateValues.reduce((sum, val) => sum + val, 0) / rateValues.length;
          summaries[12] = (avgRate * 100).toFixed(2) + "%";
        }
      }
      // æ ¼å¼åŒ–æ•°å­—
@@ -2137,14 +2473,14 @@
      }
      // å‡ºé™¢æ—¶é—´èŒƒå›´
      if (queryParams.hospitalEndTime) {
        this.patientqueryParams.hospitalStartTime =
        this.patientqueryParams.startOutHospTime =
          queryParams.hospitalStartTime;
        this.patientqueryParams.hospitalEndTime = queryParams.hospitalEndTime;
        this.patientqueryParams.endOutHospTime = queryParams.hospitalEndTime;
      } else {
        this.patientqueryParams.hospitalStartTime = this.parseTime(
        this.patientqueryParams.startOutHospTime = this.parseTime(
          queryParams.dateRangecy[0]
        );
        this.patientqueryParams.hospitalEndTime = this.parseTime(
        this.patientqueryParams.endOutHospTime = this.parseTime(
          queryParams.dateRangecy[1]
        );
      }
src/views/sfstatistics/percentage/components/SecondFollowUp.vue
@@ -154,6 +154,7 @@
                  </template>
                </el-table-column>
                <el-table-column
                  v-if="!isTargetHospital"
                  label="待随访"
                  align="center"
                  key="pendingFollowUpAgain"
@@ -177,6 +178,62 @@
                      }}</span>
                    </el-button>
                  </template>
                </el-table-column>
                <el-table-column
                  v-if="isTargetHospital"
                  label="待随访"
                  align="center"
                >
                  <el-table-column
                    label="待发送"
                    align="center"
                    key="pendingFollowUpWaitSendAgain"
                    prop="pendingFollowUpWaitSendAgain"
                  >
                    <template slot-scope="scope">
                      <el-button
                        size="medium"
                        type="text"
                        @click="
                          handleViewDetails(
                            scope.row,
                            'pendingFollowUpWaitSendAgainInfo',
                            '待发送列表',
                            '1'
                          )
                        "
                      >
                        <span class="button-zx">{{
                          scope.row.pendingFollowUpWaitSendAgain
                        }}</span>
                      </el-button>
                    </template>
                  </el-table-column>
                  <el-table-column
                    label="已发送"
                    align="center"
                    key="pendingFollowUpSentAgain"
                    prop="pendingFollowUpSentAgain"
                  >
                    <template slot-scope="scope">
                      <el-button
                        size="medium"
                        type="text"
                        @click="
                          handleViewDetails(
                            scope.row,
                            'pendingFollowUpSentAgainInfo',
                            '已发送列表',
                            '1'
                          )
                        "
                      >
                        <span class="button-zx">{{
                          scope.row.pendingFollowUpSentAgain
                        }}</span>
                      </el-button>
                    </template>
                  </el-table-column>
                </el-table-column>
                <el-table-column
                  label="随访成功"
@@ -204,6 +261,7 @@
                  </template>
                </el-table-column>
                <el-table-column
                  v-if="!isTargetHospital"
                  label="随访失败"
                  align="center"
                  key="followUpFailAgain"
@@ -457,6 +515,7 @@
            </template>
          </el-table-column>
          <el-table-column
            v-if="!isTargetHospital"
            label="待随访"
            align="center"
            key="pendingFollowUpAgain"
@@ -479,6 +538,60 @@
                }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="待随访"
            align="center"
          >
            <el-table-column
              label="待发送"
              align="center"
              key="pendingFollowUpWaitSendAgain"
              prop="pendingFollowUpWaitSendAgain"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'pendingFollowUpWaitSendAgainInfo',
                      '再次随访待发送列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.pendingFollowUpWaitSendAgain
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
            <el-table-column
              label="已发送"
              align="center"
              key="pendingFollowUpSentAgain"
              prop="pendingFollowUpSentAgain"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'pendingFollowUpSentAgainInfo',
                      '再次随访已发送列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.pendingFollowUpSentAgain
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            label="随访成功"
@@ -505,6 +618,7 @@
            </template>
          </el-table-column>
          <el-table-column
            v-if="!isTargetHospital"
            label="随访失败"
            align="center"
            key="followUpFailAgain"
@@ -553,67 +667,40 @@
            prop="followUpRateAgain"
            :render-header="Tooltipsfl"
          />
          <el-table-column
            v-if="isTargetHospital"
            label="人工"
            v-if="orgname == '缙云县人民医院'"
            label="复诊复查数量"
            align="center"
            key="suggest7CountAgain"
            prop="suggest7CountAgain"
          >
            <el-table-column
              label="发送完成"
              align="center"
              key="manualAgain"
              prop="manualAgain"
              :render-header="Tooltipfswc"
            >
              <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="manualAgainSuccess"
              prop="manualAgainSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'manualAgainSuccessInfo',
                      '最终完成人工随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.manualAgainSuccess
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
            <template slot-scope="scope">
              <el-button
                size="medium"
                type="text"
                @click="
                  handleViewDetails(
                    scope.row,
                    'suggest7CountAgainInfo',
                    '再次随访复诊复查列表'
                  )
                "
              >
                <span class="button-zx">{{
                  scope.row.suggest7CountAgain
                }}</span>
              </el-button>
            </template>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="语音"
            align="center"
          >
            <el-table-column
              label="发送完成"
              label="发送数"
              align="center"
              key="voiceAgain"
              prop="voiceAgain"
@@ -636,7 +723,7 @@
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              label="成功数"
              align="center"
              key="voiceAgainSuccess"
              prop="voiceAgainSuccess"
@@ -661,13 +748,67 @@
              </template>
            </el-table-column>
          </el-table-column>
                  <el-table-column
            v-if="isTargetHospital"
            label="微信"
            align="center"
          >
            <el-table-column
              label="发送数"
              align="center"
              key="weChatAgain"
              prop="weChatAgain"
              :render-header="Tooltipfswc"
            >
              <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
              label="成功数"
              align="center"
              key="weChatAgainSuccess"
              prop="weChatAgainSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'wechatAgainSuccessInfo',
                      '最终完成微信随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.weChatAgainSuccess
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="短信"
            align="center"
          >
            <el-table-column
              label="发送完成"
              label="发送数"
              align="center"
              key="smsAgain"
              prop="smsAgain"
@@ -690,7 +831,7 @@
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              label="成功数"
              align="center"
              key="smsAgainSuccess"
              prop="smsAgainSuccess"
@@ -715,16 +856,17 @@
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
                    <el-table-column
            v-if="isTargetHospital"
            label="微信"
            label="人工"
            align="center"
          >
            <el-table-column
              label="发送完成"
              label="发送数"
              align="center"
              key="weChatAgain"
              prop="weChatAgain"
              key="manualAgain"
              prop="manualAgain"
              :render-header="Tooltipfswc"
            >
              <template slot-scope="scope">
@@ -734,20 +876,20 @@
                  @click="
                    handleViewDetails(
                      scope.row,
                      'weChatAgainInfo',
                      '发送完成微信随访列表'
                      'manualAgainInfo',
                      '发送完成人工随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{ scope.row.weChatAgain }}</span>
                  <span class="button-zx">{{ scope.row.manualAgain }}</span>
                </el-button>
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              label="成功数"
              align="center"
              key="weChatAgainSuccess"
              prop="weChatAgainSuccess"
              key="manualAgainSuccess"
              prop="manualAgainSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
@@ -757,13 +899,13 @@
                  @click="
                    handleViewDetails(
                      scope.row,
                      'wechatAgainSuccessInfo',
                      '最终完成微信随访列表'
                      'manualAgainSuccessInfo',
                      '最终完成人工随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.weChatAgainSuccess
                    scope.row.manualAgainSuccess
                  }}</span>
                </el-button>
              </template>
@@ -946,7 +1088,9 @@
        "el-tooltip",
        {
          props: {
            content: "随访成功/(需随访-待随访)",
            content: this.isTargetHospital
              ? "随访成功/需随访"
              : "随访成功/(需随访-待随访)",
            placement: "top",
            effect: "dark",
          },
@@ -1103,7 +1247,7 @@
            return sum + (Number(item.pendingFollowUpAgain) || 0);
          }, 0);
          const denominator = totalNeed - totalPending;
          const denominator = this.isTargetHospital ? totalNeed : totalNeed - totalPending;
          if (denominator > 0) {
            sums[index] = ((totalSuccess / denominator) * 100).toFixed(2) + "%";
@@ -1181,7 +1325,7 @@
            return sum + (Number(item.pendingFollowUpAgain) || 0);
          }, 0);
          const denominator = totalNeed - totalPending;
          const denominator = this.isTargetHospital ? totalNeed : totalNeed - totalPending;
          if (denominator > 0) {
            sums[index] = ((totalSuccess / denominator) * 100).toFixed(2) + "%";
@@ -1312,8 +1456,8 @@
      const need = Number(needFollowUp) || 0;
      const pending = Number(pendingFollowUp) || 0;
      // åˆ†æ¯ = éœ€éšè®¿ - å¾…随访
      const denominator = need - pending;
      // åˆ†æ¯ = éœ€éšè®¿ï¼ˆç›®æ ‡åŒ»é™¢ï¼‰/ éœ€éšè®¿ - å¾…随访(其他)
      const denominator = this.isTargetHospital ? need : need - pending;
      if (denominator <= 0) {
        return "0.00%";
@@ -1535,13 +1679,11 @@
        },
      };
      // æ ‡é¢˜
      worksheet.mergeCells(1, 1, 1, 10);
      worksheet.getCell(1, 1).value = `${areaName}医生再次随访列表`;
      worksheet.getCell(1, 1).style = titleStyle;
      worksheet.getRow(1).height = 30;
      const graded = this.isTargetHospital;
      // æˆåŠŸçŽ‡ï¼šæ™¯å®ç•²æ—è‡ªæ²»åŽ¿äººæ°‘åŒ»é™¢ä¸å±•ç¤º
      const showSuccessRate = this.orgname != "景宁畲族自治县人民医院";
      // è¡¨å¤´
      // åˆ—定义(与展开的医生子表保持一致)
      const headers = [
        "医生姓名",
        "科室",
@@ -1549,13 +1691,25 @@
        "无需随访",
        "应随访",
        "需随访",
        "待随访",
        ...(graded ? ["待发送", "已发送"] : ["待随访"]),
        "随访成功",
        "随访失败",
        "成功率", // æ–°å¢ž
        "随访率", // åŽŸæ¥åœ¨æˆåŠŸçŽ‡ä½ç½®
        ...(graded ? [] : ["随访失败"]),
        ...(showSuccessRate ? ["成功率"] : []),
        "随访率",
        "人工",
        "语音",
        "短信",
        "微信",
      ];
      const totalCols = headers.length;
      // æ ‡é¢˜
      worksheet.mergeCells(1, 1, 1, totalCols);
      worksheet.getCell(1, 1).value = `${areaName}医生再次随访列表`;
      worksheet.getCell(1, 1).style = titleStyle;
      worksheet.getRow(1).height = 30;
      // è¡¨å¤´
      const headerRow = worksheet.addRow(headers);
      headerRow.eachCell((cell) => {
        cell.style = headerStyle;
@@ -1571,15 +1725,28 @@
          item.nonFollowUp,
          item.followUpNeeded,
          item.needFollowUpAgain,
          item.pendingFollowUpAgain,
          ...(graded
            ? [
                item.pendingFollowUpWaitSendAgain || 0,
                item.pendingFollowUpSentAgain || 0,
              ]
            : [item.pendingFollowUpAgain || 0]),
          item.followUpSuccessAgain,
          item.followUpFailAgain,
          this.calculateSuccessRate(
            item.followUpSuccessAgain,
            item.needFollowUpAgain,
            item.pendingFollowUpAgain
          ),
          ...(graded ? [] : [item.followUpFailAgain || 0]),
          ...(showSuccessRate
            ? [
                this.calculateSuccessRate(
                  item.followUpSuccessAgain,
                  item.needFollowUpAgain,
                  item.pendingFollowUpAgain
                ),
              ]
            : []),
          item.followUpRateAgain,
          item.manualAgain || 0,
          item.voiceAgain || 0,
          item.smsAgain || 0,
          item.weChatAgain || 0,
        ]);
        row.eachCell((cell) => {
          cell.style = cellStyle;
@@ -1599,33 +1766,28 @@
        };
      });
      // åˆ—宽
      worksheet.columns = [
        { width: 15 },
        { width: 15 },
        { width: 12 },
        { width: 12 },
        { width: 12 },
        { width: 12 },
        { width: 12 },
        { width: 12 },
        { width: 12 },
        { width: 12 }, // æˆåŠŸçŽ‡
        { width: 12 }, // éšè®¿çއ
      ];
      // åˆ—宽(前两列姓名/科室较宽,其余统一)
      const widths = [15, 15];
      headers.slice(2).forEach(() => widths.push(12));
      worksheet.columns = widths.map((width) => ({ width }));
    },
    /** å†æ¬¡éšè®¿ - åŒ»ç”Ÿå­è¡¨å¯¼å‡ºå°è®¡ */
    getDoctorAgainExportSummary(data) {
      const sums = ["小计"];
      const graded = this.isTargetHospital;
      const showSuccessRate = this.orgname != "景宁畲族自治县人民医院";
      const sums = ["小计", "-"];
      const keys = [
        "dischargeCount",
        "nonFollowUp",
        "followUpNeeded",
        "needFollowUpAgain",
        "pendingFollowUpAgain",
        ...(graded
          ? ["pendingFollowUpWaitSendAgain", "pendingFollowUpSentAgain"]
          : ["pendingFollowUpAgain"]),
        "followUpSuccessAgain",
        "followUpFailAgain",
        ...(graded ? [] : ["followUpFailAgain"]),
      ];
      keys.forEach((key) => {
@@ -1633,25 +1795,28 @@
      });
      // æˆåŠŸçŽ‡ï¼ˆå¹³å‡å€¼ï¼‰
      const successRates = data
        .map((item) => {
          const success = Number(item.followUpSuccessAgain) || 0;
          const need = Number(item.needFollowUpAgain) || 0;
          const pending = Number(item.pendingFollowUpAgain) || 0;
          const denominator = need - pending;
          if (denominator <= 0) return 0;
          return success / denominator;
        })
        .filter((rate) => !isNaN(rate));
      if (showSuccessRate) {
        const successRates = data
          .map((item) => {
            const success = Number(item.followUpSuccessAgain) || 0;
            const need = Number(item.needFollowUpAgain) || 0;
            const pending = Number(item.pendingFollowUpAgain) || 0;
            const denominator = graded ? need : need - pending;
            if (denominator <= 0) return 0;
            return success / denominator;
          })
          .filter((rate) => !isNaN(rate));
      sums.push(
        successRates.length
          ? (
              (successRates.reduce((a, b) => a + b, 0) / successRates.length) *
              100
            ).toFixed(2) + "%"
          : "0.00%"
      );
        sums.push(
          successRates.length
            ? (
                (successRates.reduce((a, b) => a + b, 0) /
                  successRates.length) *
                100
              ).toFixed(2) + "%"
            : "0.00%"
        );
      }
      // éšè®¿çŽ‡ï¼ˆå¹³å‡å€¼ï¼‰
      const followUpRates = data
@@ -1666,6 +1831,13 @@
              100
            ).toFixed(2) + "%"
          : "0.00%"
      );
      // äººå·¥/语音/短信/微信
      ["manualAgain", "voiceAgain", "smsAgain", "weChatAgain"].forEach(
        (key) => {
          sums.push(data.reduce((t, r) => t + (Number(r[key]) || 0), 0));
        }
      );
      return sums;
@@ -1745,7 +1917,7 @@
        },
      };
      // æ˜¯å¦ä¸ºç›®æ ‡åŒ»é™¢ï¼ˆäººå·¥/语音/短信/微信 æŒ‰ å‘送完成/最终完成 åˆ†çº§å±•示)
      // æ˜¯å¦ä¸ºç›®æ ‡åŒ»é™¢ï¼ˆäººå·¥/语音/短信/微信 æŒ‰ å‘送数/成功数 åˆ†çº§å±•示)
      const graded = this.isTargetHospital;
      const baseHeaders = [
@@ -1758,9 +1930,9 @@
      ];
      const coreHeaders = [
        "需随访",
        "待随访",
        ...(this.isTargetHospital ? ["待发送", "已发送"] : ["待随访"]),
        "随访成功",
        "随访失败",
        ...(this.isTargetHospital ? [] : ["随访失败"]),
        "成功率",
        "随访率",
      ];
@@ -1799,14 +1971,37 @@
        worksheet.getCell(2, coreStartCol).value = "再次出院随访";
        worksheet.getCell(2, coreStartCol).style = headerStyle;
        // ç¬¬3行:随访核心列纵向合并 + éšè®¿æ–¹å¼åˆ†ç»„横向合并
        for (let k = 0; k < coreCount; k++) {
          const col = coreStartCol + k;
        // ç¬¬3行:随访核心列(目标医院“待随访”下嵌套“待发送/已发送”)
        worksheet.mergeCells(3, coreStartCol, headerEndRow, coreStartCol);
        worksheet.getCell(3, coreStartCol).value = "需随访";
        worksheet.getCell(3, coreStartCol).style = headerStyle;
        worksheet.mergeCells(3, coreStartCol + 1, 3, coreStartCol + 2);
        worksheet.getCell(3, coreStartCol + 1).value = "待随访";
        worksheet.getCell(3, coreStartCol + 1).style = headerStyle;
        worksheet.mergeCells(
          3,
          coreStartCol + 3,
          headerEndRow,
          coreStartCol + 3
        );
        worksheet.getCell(3, coreStartCol + 3).value = "随访成功";
        worksheet.getCell(3, coreStartCol + 3).style = headerStyle;
        ["成功率", "随访率"].forEach((label, i) => {
          const col = coreStartCol + 4 + i;
          worksheet.mergeCells(3, col, headerEndRow, col);
          const cell = worksheet.getCell(3, col);
          cell.value = coreHeaders[k];
          cell.style = headerStyle;
        }
          worksheet.getCell(3, col).value = label;
          worksheet.getCell(3, col).style = headerStyle;
        });
        // ç¬¬4行:待发送/已发送 å­åˆ—
        worksheet.getCell(4, coreStartCol + 1).value = "待发送";
        worksheet.getCell(4, coreStartCol + 1).style = headerStyle;
        worksheet.getCell(4, coreStartCol + 2).value = "已发送";
        worksheet.getCell(4, coreStartCol + 2).style = headerStyle;
        let mc = methodStartCol;
        methodGroups.forEach((g) => {
          if (g.fields.length > 1) {
@@ -1818,7 +2013,7 @@
          mc += g.fields.length;
        });
        // ç¬¬4行:随访方式子列(发送完成/最终完成)
        // ç¬¬4行:随访方式子列(发送数/成功数)
        mc = methodStartCol;
        methodGroups.forEach((g) => {
          g.fields.forEach((field, idx) => {
@@ -1875,9 +2070,14 @@
          item.nonFollowUp || 0,
          item.followUpNeeded || 0,
          item.needFollowUpAgain || 0,
          item.pendingFollowUpAgain || 0,
          ...(this.isTargetHospital
            ? [
                item.pendingFollowUpWaitSendAgain || 0,
                item.pendingFollowUpSentAgain || 0,
              ]
            : [item.pendingFollowUpAgain || 0]),
          item.followUpSuccessAgain || 0,
          item.followUpFailAgain || 0,
          ...(this.isTargetHospital ? [] : [item.followUpFailAgain || 0]),
          // æˆåŠŸçŽ‡ - éœ€è¦åŠ¨æ€è®¡ç®—
          this.calculateSuccessRate(
            item.followUpSuccessAgain,
@@ -1918,13 +2118,13 @@
      worksheet.columns = widths.map((width) => ({ width }));
    },
    getMethodColumns() {
      // éšè®¿æ–¹å¼åˆ—配置:目标医院按“发送完成/最终完成”分级展示
      // éšè®¿æ–¹å¼åˆ—配置:目标医院按“发送数/成功数”分级展示
      if (this.isTargetHospital) {
        return [
          { label: "人工", fields: ["manualAgain", "manualAgainSuccess"], sub: ["发送完成", "最终完成"] },
          { label: "语音", fields: ["voiceAgain", "voiceAgainSuccess"], sub: ["发送完成", "最终完成"] },
          { label: "短信", fields: ["smsAgain", "smsAgainSuccess"], sub: ["发送完成", "最终完成"] },
          { label: "微信", fields: ["weChatAgain", "weChatAgainSuccess"], sub: ["发送完成", "最终完成"] },
          { label: "人工", fields: ["manualAgain", "manualAgainSuccess"], sub: ["发送数", "成功数"] },
          { label: "语音", fields: ["voiceAgain", "voiceAgainSuccess"], sub: ["发送数", "成功数"] },
          { label: "短信", fields: ["smsAgain", "smsAgainSuccess"], sub: ["发送数", "成功数"] },
          { label: "微信", fields: ["weChatAgain", "weChatAgainSuccess"], sub: ["发送数", "成功数"] },
        ];
      }
      return [
@@ -1936,20 +2136,35 @@
    },
    getExportSummaries() {
      const summaries = [
        "合计",
        "/",
        "/",
        0, // 3: dischargeCount
        0, // 4: nonFollowUp
        0, // 5: followUpNeeded
        0, // 6: needFollowUpAgain
        0, // 7: pendingFollowUpAgain
        0, // 8: followUpSuccessAgain
        0, // 9: followUpFailAgain
        "0%", // 10: æˆåŠŸçŽ‡
        "0%", // 11: éšè®¿çއ
      ];
      const summaries = this.isTargetHospital
        ? [
            "合计",
            "/",
            "/",
            0, // 3: dischargeCount
            0, // 4: nonFollowUp
            0, // 5: followUpNeeded
            0, // 6: needFollowUpAgain
            0, // 7: pendingFollowUpWaitSendAgain
            0, // 8: pendingFollowUpSentAgain
            0, // 9: followUpSuccessAgain
            "0%", // 10: æˆåŠŸçŽ‡
            "0%", // 11: éšè®¿çއ
          ]
        : [
            "合计",
            "/",
            "/",
            0, // 3: dischargeCount
            0, // 4: nonFollowUp
            0, // 5: followUpNeeded
            0, // 6: needFollowUpAgain
            0, // 7: pendingFollowUpAgain
            0, // 8: followUpSuccessAgain
            0, // 9: followUpFailAgain
            "0%", // 10: æˆåŠŸçŽ‡
            "0%", // 11: éšè®¿çއ
          ];
      const methodFields = this.getMethodColumns().reduce(
        (arr, g) => arr.concat(g.fields),
@@ -1963,19 +2178,28 @@
        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;
        if (this.isTargetHospital) {
          summaries[7] += Number(item.pendingFollowUpWaitSendAgain) || 0;
          summaries[8] += Number(item.pendingFollowUpSentAgain) || 0;
          summaries[9] += Number(item.followUpSuccessAgain) || 0;
        } else {
          summaries[7] += Number(item.pendingFollowUpAgain) || 0;
          summaries[8] += Number(item.followUpSuccessAgain) || 0;
          summaries[9] += Number(item.followUpFailAgain) || 0;
        }
        methodFields.forEach((field, i) => {
          summaries[methodStartIndex + i] += Number(item[field]) || 0;
        });
      });
      // æˆåŠŸçŽ‡è®¡ç®—
      const totalSuccess = summaries[8]; // followUpSuccessAgain的总和
      const totalSuccess = this.isTargetHospital
        ? summaries[9] // followUpSuccessAgain的总和
        : summaries[8]; // followUpSuccessAgain的总和
      const totalNeed = summaries[6]; // needFollowUpAgain的总和
      const totalPending = summaries[7]; // pendingFollowUpAgain的总和
      const denominator = totalNeed - totalPending;
      const denominator = this.isTargetHospital
        ? totalNeed
        : totalNeed - summaries[7];
      if (denominator > 0) {
        summaries[10] = ((totalSuccess / denominator) * 100).toFixed(2) + "%";
src/views/sfstatistics/percentage/index copy.vue
ÎļþÒÑɾ³ý
src/views/sfstatistics/percentage/index.vue
@@ -70,6 +70,25 @@
          </el-select>
        </el-form-item>
        <el-form-item label="任务" prop="taskIds">
          <el-select
            v-model="queryParams.taskIds"
            multiple
            filterable
            clearable
            collapse-tags
            placeholder="请选择任务"
            style="width: 220px"
          >
            <el-option
              v-for="item in taskList"
              :key="item.taskid"
              :label="item.taskName"
              :value="item.taskid"
            />
          </el-select>
        </el-form-item>
        <el-form-item label-width="200" label="应随访时间范围" prop="userName">
          <el-date-picker
            v-model="queryParams.dateRange"
@@ -291,7 +310,7 @@
import QuestionRatioTable from "@/views/Satisfaction/sfstatistics/components/QuestionRatioTable.vue";
import QuestionChart from "@/views/Satisfaction/sfstatistics/components/QuestionChart.vue";
import followupAnalysisMixin from "./followupAnalysisMixin";
import { getSfStatisticsHyperlink } from "@/api/AiCentre/index";
import { getSfStatisticsHyperlink, getTasklist } from "@/api/AiCentre/index";
export default {
  name: "Percentage",
@@ -319,8 +338,11 @@
      options: this.$store.getters.tasktypes,
      queryParams: {
        serviceType: [2],
        dateRange: [],
        dateRangecy: this.getLastMonthRange(),
        dateRange:
          localStorage.getItem("orgname") == "丽水市中医院"
            ? this.getLastMonthRange()
            : [],
        dateRangecy: '',
        statisticaltype:
          localStorage.getItem("orgname") == "缙云县人民医院" ||
          localStorage.getItem("orgname") == "景宁畲族自治县人民医院"
@@ -328,6 +350,7 @@
            : 1,
        leavehospitaldistrictcodes: ["all"],
        deptcodes: ["all"],
        taskIds: [],
      },
      pickerOptions: {
        shortcuts: [
@@ -362,6 +385,7 @@
      },
      flatArrayhospit: [],
      flatArraydept: [],
      taskList: [],
      allDeptCodes: [],
      allWardCodes: [],
      showSearch: true,
@@ -397,6 +421,15 @@
  },
  created() {
    this.getDeptTree();
    // å½“前机构为丽水市中医院时,应随访时间范围默认最近一个月并直接应用
    if (localStorage.getItem("orgname") == "丽水市中医院") {
      this.queryParams.startTime = this.parseTime(
        this.queryParams.dateRange[0]
      );
      this.queryParams.endTime = this.parseTime(
        this.queryParams.dateRange[1]
      );
    }
    this.loadCurrentTabData();
  },
  methods: {
@@ -425,6 +458,11 @@
      this.flatArraydept.push({ label: "全部", value: "all" });
      this.flatArrayhospit.push({ label: "全部", value: "all" });
      // ä»»åŠ¡åˆ—è¡¨ï¼ˆç”¨äºŽä»»åŠ¡ç­›é€‰ï¼‰
      getTasklist({ pageNum: 1, pageSize: 100 }).then((response) => {
        this.taskList = response.rows || [];
      });
    },
    // æ·»åŠ èŽ·å–æœ€è¿‘ä¸€ä¸ªæœˆæ—¶é—´èŒƒå›´çš„æ–¹æ³•
    getLastMonthRange() {
@@ -518,6 +556,7 @@
    resetQuery() {
      this.queryParams.dateRange = [];
      this.queryParams.leavehospitaldistrictcodes = [];
      this.queryParams.taskIds = [];
      this.handleQuery();
    },
src/views/system/user/index.vue
@@ -199,14 +199,39 @@
            v-if="columns[2].visible"
            :show-overflow-tooltip="true"
          />
          <!-- <el-table-column
            label="部门"
          <el-table-column
            label="科室"
            align="center"
            key="deptName"
            prop="dept.deptName"
            key="deptNames"
            v-if="columns[3].visible"
            :show-overflow-tooltip="true"
          /> -->
          >
            <template slot-scope="scope">
              <span>{{ scope.row._deptNames || "-" }}</span>
            </template>
          </el-table-column>
          <el-table-column
            label="病区"
            align="center"
            key="wardNames"
            v-if="columns[7].visible"
            :show-overflow-tooltip="true"
          >
            <template slot-scope="scope">
              <span>{{ scope.row._wardNames || "-" }}</span>
            </template>
          </el-table-column>
          <el-table-column
            label="院区"
            align="center"
            key="campusName"
            v-if="columns[8].visible && (orgname === '第一人民医院湖滨院区' || orgname === '第一人民医院吴山院区' || orgname === '第一人民医院城东院区')"
            :show-overflow-tooltip="true"
          >
            <template slot-scope="scope">
              <span>{{ scope.row._campusName || "-" }}</span>
            </template>
          </el-table-column>
          <el-table-column
            label="手机号码"
            align="center"
@@ -552,6 +577,7 @@
  deptTreeSelect,
} from "@/api/system/user";
import { getToken } from "@/utils/auth";
import { getorganization } from "@/api/AiCentre/index";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
import { type } from "jquery";
@@ -572,6 +598,8 @@
      multiple: true,
      // æ˜¾ç¤ºæœç´¢æ¡ä»¶
      showSearch: true,
      // å½“前机构名称
      orgname: localStorage.getItem("orgname") || "",
      // æ€»æ¡æ•°
      total: 0,
      // ç”¨æˆ·è¡¨æ ¼æ•°æ®
@@ -581,6 +609,12 @@
      // éƒ¨é—¨æ ‘选项
      deptOptionsks: undefined,
      deptOptionsbq: undefined,
      // ç§‘室编码 -> åç§°æ˜ å°„
      deptNameMap: {},
      // ç—…区编码 -> åç§°æ˜ å°„
      wardNameMap: {},
      // é™¢åŒºç¼–码 -> åç§°æ˜ å°„
      campusNameMap: {},
      // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚
      open: false,
      // éƒ¨é—¨åç§°
@@ -636,10 +670,12 @@
        { key: 0, label: `用户编号`, visible: true },
        { key: 1, label: `用户工号`, visible: true },
        { key: 2, label: `用户昵称`, visible: true },
        { key: 3, label: `部门`, visible: true },
        { key: 3, label: `科室`, visible: true },
        { key: 4, label: `手机号码`, visible: true },
        { key: 5, label: `状态`, visible: true },
        { key: 6, label: `创建时间`, visible: true },
        { key: 7, label: `病区`, visible: true },
        { key: 8, label: `院区`, visible: true },
      ],
      // è¡¨å•校验
@@ -689,7 +725,6 @@
    },
  },
  created() {
    this.getList();
    this.getDeptTree();
    this.getConfigKey("sys.user.initPassword").then((response) => {
      this.initPassword = response.msg;
@@ -712,6 +747,7 @@
        (response) => {
          this.userList = response.rows;
          this.total = response.total;
          this.enrichUserDept();
          this.loading = false;
        }
      );
@@ -719,17 +755,74 @@
    /** æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构 */
    getDeptTree() {
      // ç§‘室列表
      deptTreeSelect(1).then((response) => {
      const ksPromise = deptTreeSelect(1).then((response) => {
        this.deptOptionsks = response.data;
        this.deptNameMap = this.flattenDeptTree(response.data);
      });
      // ç—…区列表
      deptTreeSelect(2).then((response) => {
      const bqPromise = deptTreeSelect(2).then((response) => {
        this.deptOptionsbq = response.data;
        this.wardNameMap = this.flattenDeptTree(response.data);
      });
      // é™¢åŒºåˆ—表
      //  deptTreeSelect().then((response) => {
      //   this.deptOptions = response.data;
      // });
      const campusPromise = getorganization({ pageSize: 100 }).then((res) => {
        if (res.code == 200) {
          (res.rows || []).forEach((item) => {
            this.campusNameMap[String(item.organizationID)] =
              item.organizationName;
          });
        }
      });
      // ç§‘室/病区/院区映射就绪后再加载列表,保证首屏能显示名称
      Promise.all([ksPromise, bqPromise, campusPromise]).then(() => {
        this.getList();
      });
    },
    /** å°†ç§‘室/病区树拍平为 { deptCode: label } æ˜ å°„ */
    flattenDeptTree(nodes) {
      const map = {};
      const walk = (list) => {
        (list || []).forEach((node) => {
          if (node.deptCode) {
            map[node.deptCode] = node.label;
          }
          if (node.children && node.children.length) {
            walk(node.children);
          }
        });
      };
      walk(nodes);
      return map;
    },
    /** æŠŠ hospInfo/deptInfo ä¸­çš„编码转成名称,写入行的展示字段 */
    enrichUserDept() {
      (this.userList || []).forEach((row) => {
        row._deptNames = this.mapCodesToNames(row.hospInfo, this.deptNameMap);
        row._wardNames = this.mapCodesToNames(row.deptInfo, this.wardNameMap);
        row._campusName = this.campusNameMap[String(row.campusid)] || "-";
      });
    },
    /** è§£æžç¼–码 JSON å¹¶æ˜ å°„为名称:级联路径各层用 "/" è¿žæŽ¥ï¼Œå¤šæ¡ç”¨ "、" è¿žæŽ¥ */
    mapCodesToNames(jsonStr, nameMap) {
      if (!jsonStr || !nameMap) return "";
      let data = [];
      try {
        data = JSON.parse(jsonStr);
      } catch (e) {
        return "";
      }
      if (!Array.isArray(data)) data = [data];
      return data
        .map((item) => {
          // çº§è”多选的值是整条路径(数组),旧数据可能是单个编码
          const path = Array.isArray(item) ? item : [item];
          return path
            .map((code) => nameMap[code] || code)
            .filter(Boolean)
            .join(" / ");
        })
        .filter(Boolean)
        .join("、");
    },
    // ç­›é€‰èŠ‚ç‚¹
    filterNode(value, data) {
Ëæ·ÃÊÊÓÃÀöË®ÊÐÒ»11.3.zip
Binary files differ
Ëæ·ÃÊÊÓÃÀöË®ÊÐÒ»11.4.zip
Binary files differ
Ëæ·ÃÊÊÓÃÀöË®ÊÐÒ»11.5 (2).zip
Binary files differ
Ëæ·ÃÊÊÓÃÀöË®ÊÐÒ»11.5.zip
Binary files differ