WXL (wul)
21 小时以前 0c7cc21d8a51e164dd2fe4ce73ab566b3a9081a9
测试完成
已重命名1个文件
已修改31个文件
已添加8个文件
8153 ■■■■■ 文件已修改
ls.zip 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/AiCentre/EChartsdata.js 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Assistant/index.vue 1132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/1.vue 1814 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/configurationmyd/index.vue 1246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/particulars/index.vue 889 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue 809 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/SatisfactionStatistics.vue 990 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/components/SeedetailsDialog.vue 401 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/components/TopicDialog.vue 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/index.vue 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/Continue/index.vue 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/HistoricalFollow/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/OutpatientAgain/index.vue 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/SpecificDisease/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/Tracking/index.vue 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/again/index.vue 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/complaint/index.vue 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/discharge/index.vue 125 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/discharge/outpatientService.vue 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/mzsatisfaction/index.vue 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/outpatient/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/record/detailpage/index.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/record/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/technology/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/zbAgain/index.vue 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/zysatisfaction/index.vue 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login-sy.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/patient/indexls.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/physical/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/propaganda/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/questionnaire/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/shadow/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/subsequent/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/index.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/satisfaction.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ls.zip
Binary files differ
src/App.vue
@@ -3,6 +3,13 @@
    <router-view />
    <theme-picker />
    <!-- <Assistant v-if="routertf" /> -->
    <Assistant
      v-if="Assvite"
      :initial-position="{ x: 50, y: 200 }"
      :auto-hide="false"
      :hide-delay="3000"
      primary-color="#1890ff"
    />
  </div>
</template>
@@ -13,17 +20,29 @@
  name: "App",
  components: {
    ThemePicker,
    Assistant: () => import("./components/Assistant"), //异步组件加载方式
    Assistant: () => import("./components/Assistant"),
  },
  data() {
    return {
      routers: window.location.href,
      routertf: true,
      Assvite: true,
    };
  },
  created() {
    var startIndex = this.routers.indexOf("param5=") + "param5=".length; // æ‰¾åˆ°ç¬¬ä¸€ä¸ªå­—符的位置
    this.routertf = JSON.parse(this.routers.substring(startIndex)); // æˆªå–从 'param5=' ä¹‹åŽçš„内容
    // åˆå§‹åŒ–判断
    this.checkAndUpdateAssvite();
  },
  watch: {
    // ç›‘听路由变化
    '$route'(to, from) {
      this.checkAndUpdateAssvite();
    }
  },
  methods: {
    checkAndUpdateAssvite() {
      const isLoginPage = window.location.pathname.includes("/login");
      this.Assvite = !isLoginPage;
      console.log('当前路由:', this.$route.path, '是否登录页:', isLoginPage, '显示悬浮球:', this.Assvite);
    }
  },
  metaInfo() {
    return {
src/api/AiCentre/EChartsdata.js
@@ -1,6 +1,5 @@
import request from "@/utils/request";
// æŸ¥è¯¢é—¨è¯Šçœ‹ç—…人次和人数
export function getEChartsPatMedOuthospCount(data) {
  return request({
@@ -73,6 +72,14 @@
  return request({
    url: "/sms/send",
    method: "post",
    data: data
    data: data,
  });
}
// å‘送短信
export function getCurrentUserServiceSubtaskCount(data) {
  return request({
    url: "/smartor/serviceSubtask/getCurrentUserServiceSubtaskCount",
    method: "post",
    data: data,
  });
}
src/components/Assistant/index.vue
@@ -1,479 +1,809 @@
<template>
  <div
    ref="floatDrag"
    class="float-position"
    id="float-box"
    :style="{
      left: left + 'px',
      top: top + 'px',
      right: right + 'px !important',
      zIndex: zIndex,
    ref="floatBall"
    class="float-ball"
    :class="{
      'float-ball-hidden': isHidden && !isHovering,
      'float-ball-expanded': isExpanded,
    }"
    @touchmove.prevent
    @mousemove.prevent
    @mousedown="mouseDown"
    @mouseup="mouseUp"
    :style="{
      left: position.x + 'px',
      top: position.y + 'px',
      '--primary-color': primaryColor,
      '--hover-color': hoverColor,
    }"
    @mouseenter="handleMouseEnter"
    @mouseleave="handleMouseLeave"
  >
    <div class="drag">
      <svg
        t="1682058484158"
        class="icon"
        viewBox="0 0 1024 1024"
        version="1.1"
        xmlns="http://www.w3.org/2000/svg"
        p-id="2023"
        width="32"
        height="32"
      >
        <path
          d="M556.297 172.715a42.407 42.407 0 0 1 42.426 42.398l0.837 267.69c-0.118 1.703 0.63 2.737 1.408 2.737 0.63 0 1.29-0.699 1.506-2.284l37.74-208.953c3.732-20.672 21.844-36.166 42.162-36.166a40.074 40.074 0 0 1 7.136 0.64c23.064 4.164 38.391 27.562 34.217 50.587l-33.656 244.529c0 2.559 0.483 4.478 1.32 4.478 0.58 0 1.328-0.935 2.175-3.218l50.144-134.063c6.27-17.65 23.034-29.403 40.793-29.403A39.798 39.798 0 0 1 797.892 374c22.08 7.875 33.626 33.41 25.78 55.47l-87.904 287.191c-0.453 1.585-0.984 3.16-1.437 4.725l-0.187 0.591v0.128a187.031 187.031 0 0 1-177.847 129.1c-53.156 0-108.42-18.752-150.472-51-45.419-27.336-190.968-183.783-190.968-183.783-22.09-22.07-18.792-55.882 3.297-77.962 11.537-11.537 25.919-17.6 40.173-17.6 13.033 0 25.967 5.05 36.51 15.592l63.138 63.157c8.603 8.594 18.132 12.699 26.922 12.699a26.952 26.952 0 0 0 20.88-9.893c7.658-9.037 4.635-36.914 2.49-54.594l-31.668-260.259c-2.825-23.26 13.781-45.724 37.003-48.549a40.497 40.497 0 0 1 4.853-0.295c21.282 0 39.749 16.98 42.387 38.597l34.926 204.425c0.905 2.54 2.342 4.036 3.602 4.036s2.353-1.496 2.58-4.922l11.88-265.741a42.417 42.417 0 0 1 42.467-42.398m0-70.875a113.36 113.36 0 0 0-104.344 69.153c-0.246 0.57-0.482 1.152-0.718 1.732a111.234 111.234 0 0 0-90.022 10.976 113.597 113.597 0 0 0-32.415 29.207 115.23 115.23 0 0 0-19.067 38.489 113.843 113.843 0 0 0-3.465 44.68l21.36 175.77a120.842 120.842 0 0 0-69.3-21.863c-33.468 0-65.549 13.614-90.286 38.332-23.212 23.202-36.993 53.363-38.863 84.952a120.92 120.92 0 0 0 34.502 92.216c5.532 5.906 39.64 42.407 79.203 82.412 74.586 75.422 105.328 99.648 122.702 110.771 53.973 40.36 123.254 63.414 190.674 63.414A257.906 257.906 0 0 0 801.14 745.1c0.247-0.709 0.483-1.417 0.7-2.136l0.117-0.374a178.56 178.56 0 0 0 1.723-5.64l87.413-285.578a113.203 113.203 0 0 0 5.729-42.86 115.585 115.585 0 0 0-35.772-77.135 111.431 111.431 0 0 0-67.45-30.19l0.148-0.985a113.676 113.676 0 0 0-1.201-43.155 115.408 115.408 0 0 0-16.872-39.523 113.774 113.774 0 0 0-30.703-30.968 111.077 111.077 0 0 0-84.981-17.06 113.203 113.203 0 0 0-103.694-67.656z"
          fill="#ffffff"
          p-id="2024"
        ></path>
      </svg>
    </div>
    <div class="content" id="content" @click="handelFlex">
      <!-- <img src="../../../../assets/image/alarm.png" alt="" /> -->
      <div class="label">
        <div v-if="flag">展开</div>
        <div v-else>收起</div>
      </div>
      <div class="item-container">
        <div
          v-for="(item, index) in powerList"
          :key="index"
          @click.stop="activeHandle(index,item.url)"
    <!-- ä¸»çƒä½“ -->
    <div
      class="ball-main"
      :class="{ 'ball-main-expanded': isExpanded }"
      @click="toggleExpand"
      @mousedown="startDrag"
      @touchstart="startDrag"
    >
      <!-- æŠ˜å çŠ¶æ€å›¾æ ‡ -->
      <div v-if="!isExpanded" class="ball-icon">
        <svg
          class="fold-icon"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
        >
          <path d="M4 6h16M4 12h16M4 18h16" />
        </svg>
      </div>
      <!-- å±•开状态关闭按钮 -->
      <div v-else class="close-btn" @click.stop="toggleExpand">
        <svg
          class="close-icon"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
        >
          <path d="M6 18L18 6M6 6l12 12" />
        </svg>
      </div>
      <!-- è§’标提示(有未读数时显示) -->
      <div v-if="totalUnread > 0" class="ball-badge">
        {{ totalUnread > 99 ? "99+" : totalUnread }}
      </div>
    </div>
    <!-- å±•开的内容面板 -->
    <transition name="ball-expand">
      <div v-if="isExpanded" class="ball-content">
        <div class="content-header">
          <h3>随访工作台</h3>
          <div class="update-time">更新于 {{ updateTime }}</div>
        </div>
        <div class="stats-grid">
          <div
            :class="activeIndex == index ? 'active power-item' : 'power-item'"
            v-for="(item, index) in statsItems"
            :key="index"
            class="stat-item"
            :class="{ 'stat-item-highlight': item.highlight }"
            @click="handleItemClick(item)"
          >
            <img :src="item.path" alt="" style="width: 26px" />
            <div class="stat-icon">
              <svg
                v-if="item.icon === 'IconUsers'"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
              >
                <path
                  d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13-7.157a4 4 0 11-8 0 4 4 0 018 0z"
                />
              </svg>
              <svg
                v-else-if="item.icon === 'IconAlertCircle'"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
              >
                <path d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
              </svg>
              <svg
                v-else-if="item.icon === 'IconTask'"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
              >
                <path
                  d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
                />
              </svg>
            </div>
            <div class="stat-info">
              <div class="stat-label">{{ item.label }}</div>
              <div class="stat-value">{{ item.value }}</div>
              <div
                v-if="item.trend"
                class="stat-trend"
                :class="'trend-' + item.trend.type"
              >
                <span class="trend-arrow">{{ item.trend.arrow }}</span>
                <span class="trend-value">{{ item.trend.value }}</span>
              </div>
            </div>
            <div v-if="item.unread > 0" class="stat-badge">
              {{ item.unread > 99 ? "99+" : item.unread }}
            </div>
          </div>
          <div :class="activeIndex == index ? 'active-des des' : 'des'">
            {{ item.label }}
        </div>
        <div class="quick-actions">
          <div
            v-for="(action, index) in quickActions"
            :key="index"
            class="action-item"
            @click="handleActionClick(action)"
          >
            <div class="action-icon">
              <svg
                v-if="action.icon === 'IconMessageCircle'"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
              >
                <path
                  d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
                />
              </svg>
              <svg
                v-else-if="action.icon === 'IconPhone'"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
              >
                <path
                  d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"
                />
              </svg>
            </div>
            <div class="action-label">{{ action.label }}</div>
          </div>
        </div>
      </div>
    </div>
    </transition>
  </div>
</template>
<script>
import { getCurrentUserServiceSubtaskCount } from "@/api/AiCentre/index";
export default {
  name: "DragBall",
  name: "FloatBall",
  props: {
    distanceRight: {
      type: Number,
      default: 36,
    // åˆå§‹ä½ç½®
    initialPosition: {
      type: Object,
      default: () => ({ x: 20, y: 100 }),
    },
    distanceBottom: {
      type: Number,
      default: 600,
    },
    isScrollHidden: {
      type: Boolean,
      default: false,
    },
    isCanDraggable: {
    // æ˜¯å¦è‡ªåŠ¨éšè—
    autoHide: {
      type: Boolean,
      default: true,
    },
    zIndex: {
    // éšè—å»¶è¿Ÿï¼ˆæ¯«ç§’)
    hideDelay: {
      type: Number,
      default: 50,
      default: 2000,
    },
    value: {
    // ä¸»é¢˜é¢œè‰²
    primaryColor: {
      type: String,
      default: "悬浮球!",
      default: "#4f46e5",
    },
    // æ‚¬åœé¢œè‰²
    hoverColor: {
      type: String,
      default: "#4338ca",
    },
    // æ•°æ®æºï¼ˆå¯ä»Žå¤–部传入)
    statsData: {
      type: Object,
      default: null,
    },
  },
  data() {
    return {
      clientWidth: null,
      clientHeight: null,
      left: null,
      top: null,
      right: null,
      timer: null,
      currentTop: 0,
      mousedownX: 0,
      mousedownY: 0,
      isExpanded: false,
      isHovering: false,
      isHidden: false,
      isDragging: false,
      position: { ...this.initialPosition },
      dragStart: { x: 0, y: 0 },
      hideTimer: null,
      updateTime: "",
      roles: null,
      // ç»Ÿè®¡æ•°æ®
      statsItems: [
        {
          id: "pending",
          label: "待随访",
          value: "0",
          unread: 0,
          urltype: 2,
          icon: "IconUsers",
          url: "/followvisit/discharge",
          trend: { type: "up", arrow: "", value: "" },
          highlight: true,
        },
        {
          id: "failed",
          label: "随访失败",
          value: "0",
          unread: 0,
          urltype: 3,
          icon: "IconAlertCircle",
          url: "/followvisit/discharge",
          trend: { type: "down", arrow: "", value: "" },
        },
        {
          id: "abnormal",
          label: "任务异常",
          value: "0",
          unread: 0,
          urltype: 4,
          icon: "IconAlertCircle",
          url: "/followvisit/discharge",
          trend: { type: "up", arrow: "", value: "" },
        },
        {
          id: "myTasks",
          label: "我的任务",
          value: "0",
          unread: 0,
          urltype: 5,
          icon: "IconTask",
          url: "/followvisit/discharge",
          trend: { type: "stable", arrow: "", value: "" },
        },
      ],
      flag: true, // æŽ§åˆ¶æ‚¬æµ®æ¡†æ˜¯å¦å±•å¼€
      box: "", // æ‚¬æµ®çƒçš„dom
      activeIndex: 0, //高亮显示
      powerList: [
      // å¿«æ·æ“ä½œ
      quickActions: [
        {
          path: require("@/assets/images/huanzheliebiao.png"),
          url:'/patient/patient/',
          label: "患者",
          id: "sms",
          label: "创建问卷任务",
          icon: "IconMessageCircle",
          url: "/followvisit/QuestionnaireTask?type=2&serviceType=2",
        },
        {
          path: require("@/assets/images/fwwu.png"),
          url:'/followvisit/tasklist/',
          label: "任务",
          id: "call",
          label: "创建语音任务",
          icon: "IconPhone",
          url: "/followvisit/particty?type=1&serviceType=2",
        },
        {
          path: require("@/assets/images/duanxinjilu.png"),
          url:'',
          label: "短信",
        },
        {
          path: require("@/assets/images/dianhua.png"),
          url:'',
          label: "电话",
        },
        {
          path: require("@/assets/images/zxlt.png"),
          url:'',
          label: "在线聊天",
        },
        // {
        //   id: 'chat',
        //   label: '在线聊天',
        //   icon: 'IconMessageCircle',
        //   url: '/chat'
        // }
      ],
    };
  },
  created() {
    this.clientWidth = document.documentElement.clientWidth;
    this.clientHeight = document.documentElement.clientHeight;
  computed: {
    totalUnread() {
      return this.statsItems.reduce((sum, item) => sum + item.unread, 0);
    },
  },
  mounted() {
    this.isCanDraggable &&
      this.$nextTick(() => {
        this.floatDrag = this.$refs.floatDrag;
        // èŽ·å–å…ƒç´ ä½ç½®å±žæ€§
        this.floatDragDom = this.floatDrag.getBoundingClientRect();
        // è®¾ç½®åˆå§‹ä½ç½®
        this.left = this.clientWidth - this.floatDragDom.width - this.distanceRight;
        // this.right = 0;
        this.top =
          this.clientHeight - this.floatDragDom.height - this.distanceBottom;
        this.initDraggable();
      });
    // this.isScrollHidden && window.addEventListener('scroll', this.handleScroll);
    this.roles = this.$store.state.user.roles;
    this.loadPosition();
    if (this.autoHide) {
      this.startAutoHide();
    }
    // ç‚¹å‡»å¤–部关闭
    document.addEventListener("click", this.handleClickOutside);
    // çª—口大小变化时重新定位
    window.addEventListener("resize", this.handleResize);
    this.box = document.getElementById("float-box");
  },
  beforeUnmount() {
    window.removeEventListener("scroll", this.handleScroll);
  beforeDestroy() {
    document.removeEventListener("click", this.handleClickOutside);
    window.removeEventListener("resize", this.handleResize);
    clearTimeout(this.hideTimer);
  },
  methods: {
    // ä¼¸ç¼©æ‚¬æµ®çƒ
    handelFlex() {
      if (this.flag) {
        this.buffer(this.box, "height", 600);
      } else {
        this.buffer(this.box, "height", 70);
      }
      this.flag = !this.flag;
      console.log("是否展开", this.flag);
    },
    // ç‚¹å‡»å“ªä¸ªpower
    activeHandle(index,url) {
      //把我们自定义的下标赋值
      this.activeIndex = index;
      this.$router.push({
        path: url,
      })
      console.log("HHHH", index);
    },
    // èŽ·å–è¦æ”¹å˜å¾—æ ·å¼å±žæ€§
    getStyleAttr(obj, attr) {
      if (obj.currentStyle) {
        // IE å’Œ opera
        return obj.currentStyle[attr];
      } else {
        return window.getComputedStyle(obj, null)[attr];
      }
    },
    // åŠ¨ç”»å‡½æ•°
    buffer(eleObj, attr, target) {
      // setInterval方式开启动画
      //先清后设
      // clearInterval(eleObj.timer);
      // let speed = 0
      // let begin = 0
      // //设置定时器
      // eleObj.timer = setInterval(() => {
      //     //获取动画属性的初始值
      //     begin = parseInt(this.getStyleAttr(eleObj, attr));
      //     speed = (target - begin) * 0.2;
      //     speed = target > begin ? Math.ceil(speed) : Math.floor(speed);
      //     eleObj.style[attr] = begin + speed + "px";
      //     if (begin === target) {
      //         clearInterval(eleObj.timer);
      //     }
      // }, 20);
      // cancelAnimationFrame开启动画
      // å…ˆæ¸…后设
      cancelAnimationFrame(eleObj.timer);
      let speed = 0;
      let begin = 0;
      let _this = this;
      eleObj.timer = requestAnimationFrame(function fn() {
        begin = parseInt(_this.getStyleAttr(eleObj, attr));
        // åŠ¨ç”»é€Ÿåº¦
        speed = (target - begin) * 0.9;
        speed = target > begin ? Math.ceil(speed) : Math.floor(speed);
        eleObj.style[attr] = begin + speed + "px";
        eleObj.timer = requestAnimationFrame(fn);
        if (begin === target) {
          cancelAnimationFrame(eleObj.timer);
        }
      });
    },
    /**
     * çª—口resize监听
     */
    handleResize() {
      // this.clientWidth = document.documentElement.clientWidth;
      // this.clientHeight = document.documentElement.clientHeight;
      // console.log(window.innerWidth);
      // console.log(document.documentElement.clientWidth);
      this.checkDraggablePosition();
    },
    /**
     * åˆå§‹åŒ–draggable
     */
    initDraggable() {
      this.floatDrag.addEventListener("touchstart", this.toucheStart);
      this.floatDrag.addEventListener("touchmove", (e) => this.touchMove(e));
      this.floatDrag.addEventListener("touchend", this.touchEnd);
    },
    mouseDown(e) {
      const event = e || window.event;
      this.mousedownX = event.screenX;
      this.mousedownY = event.screenY;
      const that = this;
      let floatDragWidth = this.floatDragDom.width / 2;
      let floatDragHeight = this.floatDragDom.height / 2;
      if (event.preventDefault) {
        event.preventDefault();
  methods: {
    toggleExpand() {
      this.isExpanded = !this.isExpanded;
      if (this.isExpanded) {
        this.isHidden = false;
        clearTimeout(this.hideTimer);
        this.updateStats();
      }
      this.canClick = false;
      this.floatDrag.style.transition = "none";
      document.onmousemove = function (e) {
        var event = e || window.event;
        that.left = event.clientX - floatDragWidth;
        that.top = event.clientY - floatDragHeight;
        if (that.left < 0) that.left = 0;
        if (that.top < 0) that.top = 0;
        // é¼ æ ‡ç§»å‡ºå¯è§†åŒºåŸŸåŽç»™æŒ‰é’®è¿˜åŽŸ
        if (
          event.clientY < 0 ||
          event.clientY > Number(this.clientHeight) ||
          event.clientX > Number(this.clientWidth) ||
          event.clientX < 0
        ) {
          this.right = 0;
          this.top =
            this.clientHeight - this.floatDragDom.height - this.distanceBottom;
          document.onmousemove = null;
          this.floatDrag.style.transition = "all 0.3s";
          return;
    },
    handleMouseEnter() {
      this.isHovering = true;
      if (this.autoHide) {
        clearTimeout(this.hideTimer);
        this.isHidden = false;
      }
    },
    handleMouseLeave() {
      this.isHovering = false;
      if (this.autoHide && !this.isExpanded) {
        this.startAutoHide();
      }
    },
    startAutoHide() {
      this.hideTimer = setTimeout(() => {
        if (!this.isExpanded && !this.isHovering) {
          this.isHidden = true;
        }
        if (
          that.left >=
          document.documentElement.clientWidth - floatDragWidth * 2
        ) {
          that.left = document.documentElement.clientWidth - floatDragWidth * 2;
      }, this.hideDelay);
    },
    startDrag(e) {
      e.preventDefault();
      e.stopPropagation();
      this.isDragging = true;
      const clientX = e.type.includes("touch")
        ? e.touches[0].clientX
        : e.clientX;
      const clientY = e.type.includes("touch")
        ? e.touches[0].clientY
        : e.clientY;
      this.dragStart = {
        x: clientX - this.position.x,
        y: clientY - this.position.y,
      };
      const onMove = (moveEvent) => {
        if (!this.isDragging) return;
        const moveX = moveEvent.type.includes("touch")
          ? moveEvent.touches[0].clientX
          : moveEvent.clientX;
        const moveY = moveEvent.type.includes("touch")
          ? moveEvent.touches[0].clientY
          : moveEvent.clientY;
        const newX = moveX - this.dragStart.x;
        const newY = moveY - this.dragStart.y;
        // è¾¹ç•Œæ£€æŸ¥
        const maxX = window.innerWidth - 60;
        const maxY = window.innerHeight - 60;
        this.position.x = Math.max(0, Math.min(newX, maxX));
        this.position.y = Math.max(0, Math.min(newY, maxY));
      };
      const onEnd = () => {
        this.isDragging = false;
        document.removeEventListener("mousemove", onMove);
        document.removeEventListener("mouseup", onEnd);
        document.removeEventListener("touchmove", onMove);
        document.removeEventListener("touchend", onEnd);
        // å¦‚果靠近边缘,自动吸附
        if (this.position.x < 20) {
          this.position.x = 0;
        } else if (this.position.x > window.innerWidth - 80) {
          this.position.x = window.innerWidth - 60;
        }
        if (that.top >= that.clientHeight - floatDragHeight * 2) {
          that.top = that.clientHeight - floatDragHeight * 2;
        // ä¿å­˜ä½ç½®åˆ°æœ¬åœ°å­˜å‚¨
        try {
          localStorage.setItem(
            "floatBallPosition",
            JSON.stringify(this.position)
          );
        } catch (e) {
          console.error("保存位置失败:", e);
        }
      };
      document.addEventListener("mousemove", onMove);
      document.addEventListener("mouseup", onEnd);
      document.addEventListener("touchmove", onMove, { passive: false });
      document.addEventListener("touchend", onEnd);
    },
    mouseUp(e) {
      const event = e || window.event;
      //判断只是单纯的点击,没有拖拽
    handleItemClick(item) {
      if (item.url) {
        console.log(item.url, "item.url");
        // this.$router.push(item.url);
        this.$router.replace({
          path: item.url,
          query: {
            errtype: item.urltype,
          },
        });
        this.toggleExpand();
      }
    },
    handleActionClick(action) {
      console.log(this.roles, "this.roles");
      if (
        this.mousedownY == event.screenY &&
        this.mousedownX == event.screenX
        action.url &&
        (this.roles.includes("admin") || this.roles.includes("sysadmin"))
      ) {
        this.$emit("handlepaly");
      }
      document.onmousemove = null;
      this.checkDraggablePosition();
      this.floatDrag.style.transition = "all 0.3s";
    },
    toucheStart() {
      this.canClick = false;
      this.floatDrag.style.transition = "none";
    },
    touchMove(e) {
      this.canClick = true;
      if (e.targetTouches.length === 1) {
        // å•指拖动
        let touch = event.targetTouches[0];
        this.left = touch.clientX - this.floatDragDom.width / 2;
        this.top = touch.clientY - this.floatDragDom.height / 2;
      }
    },
    touchEnd() {
      if (!this.canClick) return; // è§£å†³ç‚¹å‡»äº‹ä»¶å’Œtouch事件冲突的问题
      this.floatDrag.style.transition = "all 0.3s";
      this.checkDraggablePosition();
    },
    /**
     * åˆ¤æ–­å…ƒç´ æ˜¾ç¤ºä½ç½®
     * åœ¨çª—口改变和move end时调用
     */
    checkDraggablePosition() {
      this.clientWidth = document.documentElement.clientWidth;
      this.clientHeight = document.documentElement.clientHeight;
      if (this.left + this.floatDragDom.width / 2 >= this.clientWidth / 2) {
        // åˆ¤æ–­ä½ç½®æ˜¯å¾€å·¦å¾€å³æ»‘动
        this.left = this.clientWidth - this.floatDragDom.width;
        this.$router.replace(action.url);
        this.toggleExpand();
      } else {
        this.left = 0;
        this.$modal.msgError("非管理员用户暂无创建任务权限");
      }
      if (this.top < 0) {
        // åˆ¤æ–­æ˜¯å¦è¶…出屏幕上沿
        this.top = 0;
    },
    async updateStats() {
      try {
        // è¿™é‡Œå¯ä»¥æ›¿æ¢ä¸ºå®žé™…çš„ API è°ƒç”¨
        // const response = await this.$api.getFollowupStats()
        // this.statsItems = response.data
        // æ¨¡æ‹Ÿæ•°æ®æ›´æ–°
        const mockData = {
          pending: {
            value: "128",
            unread: null,
            trend: { type: "up", arrow: "↑", value: "5" },
          },
          failed: {
            value: "24",
            unread: null,
            trend: { type: "down", arrow: "↓", value: "2" },
          },
          abnormal: {
            value: "8",
            unread: null,
            trend: { type: "up", arrow: "↑", value: "3" },
          },
          myTasks: {
            value: "156",
            unread: null,
            trend: { type: "stable", arrow: "→", value: "0" },
          },
        };
        const response = await getCurrentUserServiceSubtaskCount();
        mockData.pending.value = response.pendingVisitCount;
        mockData.failed.value = response.failedVisitCount;
        mockData.abnormal.value = response.abnormalVisitCount;
        mockData.myTasks.value = response.allVisitCount;
        this.statsItems = this.statsItems.map((item) => {
          const data = mockData[item.id] || {};
          return {
            ...item,
            value: data.value || item.value,
            unread: data.unread || item.unread,
            trend: data.trend || item.trend,
          };
        });
        // æ›´æ–°æ—¶é—´
        const now = new Date();
        this.updateTime = `${now.getHours().toString().padStart(2, "0")}:${now
          .getMinutes()
          .toString()
          .padStart(2, "0")}`;
      } catch (error) {
        console.error("更新统计数据失败:", error);
      }
      if (this.top + this.floatDragDom.height >= this.clientHeight) {
        // åˆ¤æ–­æ˜¯å¦è¶…出屏幕下沿
        this.top = this.clientHeight - this.floatDragDom.height;
    },
    loadPosition() {
      try {
        const savedPosition = localStorage.getItem("floatBallPosition");
        if (savedPosition) {
          const parsed = JSON.parse(savedPosition);
          this.position = parsed;
        }
      } catch (e) {
        console.error("加载位置失败:", e);
      }
    },
    handleClickOutside(e) {
      if (
        this.isExpanded &&
        this.$refs.floatBall &&
        !this.$refs.floatBall.contains(e.target)
      ) {
        this.toggleExpand();
      }
    },
    handleResize() {
      const maxX = window.innerWidth - 60;
      const maxY = window.innerHeight - 60;
      this.position.x = Math.min(this.position.x, maxX);
      this.position.y = Math.min(this.position.y, maxY);
    },
  },
};
</script>
<style>
html,
body {
  overflow: hidden;
}
</style>
<style scoped lang="scss">
.float-position {
<style scoped>
.float-ball {
  position: fixed;
  z-index: 10003 !important;
  left: 0;
  top: 20%;
  width: 70px;
  height: 70px;
  border-radius: 32px;
  z-index: 9999;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: auto;
}
.float-ball-hidden {
  opacity: 0.3;
  transform: translateX(10px);
}
.float-ball-hidden:hover {
  opacity: 1;
  transform: translateX(0);
}
.ball-main {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--primary-color), #7c3aed);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: move;
  box-shadow: 0 4px 20px rgba(79, 70, 229, 0.3);
  transition: all 0.3s ease;
  position: relative;
  z-index: 10000;
}
.ball-main:hover {
  background: linear-gradient(135deg, var(--hover-color), #6d28d9);
  box-shadow: 0 6px 25px rgba(79, 70, 229, 0.4);
  transform: scale(1.05);
}
.ball-main-expanded {
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
}
.ball-icon {
  width: 24px;
  height: 24px;
  color: white;
}
.fold-icon {
  width: 100%;
  height: 100%;
}
.close-btn {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  overflow: hidden;
  user-select: none;
  color: white;
  transition: transform 0.2s ease;
}
  display: block;
  background: black;
  background: -webkit-radial-gradient(100px 100px, circle, #5788FE, #292929);
  //   background: -moz-radial-gradient(100px 100px, circle, #35a1a1, #000);Firefox æµè§ˆå™¨çš„实现
  //   background: radial-gradient(100px 100px, circle, #35a1a1, #000);标准 HTML5 å±žæ€§
  margin: 0;
  .drag {
    width: 70px;
    height: 35px;
    // background: #f2e96a;
    text-align: center;
    line-height: 35px;
    border-bottom: 1px solid #fff;
.close-btn:hover {
  transform: rotate(90deg);
}
.close-icon {
  width: 20px;
  height: 20px;
}
.ball-badge {
  position: absolute;
  top: -5px;
  right: -5px;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  background: #ef4444;
  color: white;
  font-size: 12px;
  font-weight: 600;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid white;
  animation: pulse 2s infinite;
}
@keyframes pulse {
  0%,
  100% {
    transform: scale(1);
  }
  .content {
    width: 70px;
    height: 35px;
    // background: #716af2;
    .label {
      width: 70px;
      height: 35px;
      text-align: center;
      line-height: 35px;
      color: white;
    }
    .label:hover {
      color: rgb(19, 217, 243);
      transition: all 0.5;
    }
    .item-container {
      margin-top: 10px;
      width: 70px;
      height: 500px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      flex-direction: column;
      .power-item {
        width: 40px;
        height: 40px;
        border-radius: 50%;
        background-color: #f1f7ff;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
      }
      .des {
        width: 40px;
        text-align: center;
        margin-bottom: 5px;
        font-size: 10px;
        color: #fff;
      }
    }
  }
  .close {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    background: rgba(0, 0, 0, 0.6);
    position: absolute;
    right: -10px;
    top: -12px;
    cursor: pointer;
  50% {
    transform: scale(1.1);
  }
}
.cart {
  border-radius: 50%;
  width: 5em;
  height: 5em;
.ball-content {
  position: absolute;
  top: 70px;
  left: 0;
  width: 320px;
  background: white;
  border-radius: 16px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
  overflow: hidden;
  z-index: 9999;
}
.ball-expand-enter-active,
.ball-expand-leave-active {
  transition: all 0.3s ease;
}
.ball-expand-enter,
.ball-expand-leave-to {
  opacity: 0;
  transform: translateY(-10px);
}
.content-header {
  padding: 20px 20px 16px;
  background: linear-gradient(135deg, var(--primary-color), #7c3aed);
  color: white;
}
.content-header h3 {
  margin: 0 0 8px 0;
  font-size: 18px;
  font-weight: 600;
}
.update-time {
  font-size: 12px;
  opacity: 0.9;
}
.stats-grid {
  padding: 16px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.stat-item {
  padding: 16px;
  background: #f8fafc;
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.2s ease;
  position: relative;
  border: 2px solid transparent;
}
.stat-item:hover {
  background: #f1f5f9;
  border-color: #e2e8f0;
  transform: translateY(-2px);
}
.stat-item-highlight {
  border-color: var(--primary-color);
  background: linear-gradient(to bottom right, #f0f9ff, #f8fafc);
}
.stat-icon {
  width: 32px;
  height: 32px;
  background: white;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 12px;
  color: var(--primary-color);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.stat-icon svg {
  width: 18px;
  height: 18px;
}
.stat-label {
  font-size: 12px;
  color: #64748b;
  margin-bottom: 4px;
}
.stat-value {
  font-size: 20px;
  font-weight: 700;
  color: #1e293b;
  margin-bottom: 4px;
}
.stat-trend {
  font-size: 11px;
  display: flex;
  align-items: center;
  gap: 2px;
}
.trend-up {
  color: #10b981;
}
.trend-down {
  color: #ef4444;
}
.trend-stable {
  color: #64748b;
}
.trend-arrow {
  font-size: 10px;
}
.stat-badge {
  position: absolute;
  top: 12px;
  right: 12px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  background: #ef4444;
  color: white;
  font-size: 10px;
  font-weight: 600;
  border-radius: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.header-notice {
  display: inline-block;
  transition: all 0.3s;
  span {
    vertical-align: initial;
  }
  .notice-badge {
    color: inherit;
    .header-notice-icon {
      font-size: 16px;
      padding: 4px;
    }
  }
.quick-actions {
  padding: 12px 20px 20px;
  border-top: 1px solid #f1f5f9;
  display: flex;
  gap: 12px;
  justify-content: center;
}
.drag-ball .drag-content {
  overflow-wrap: break-word;
  font-size: 14px;
  color: #fff;
  letter-spacing: 2px;
.action-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  padding: 12px;
  border-radius: 8px;
  transition: all 0.2s ease;
  flex: 1;
}
.active {
  background-color: #f9f1db !important;
.action-item:hover {
  background: #f8fafc;
}
.active-des {
  color: #71dcfa !important;
  font-size: 20px !important;
  font-weight: 500 !important;
.action-icon {
  width: 24px;
  height: 24px;
  color: var(--primary-color);
}
.action-icon svg {
  width: 20px;
  height: 20px;
}
.action-label {
  font-size: 12px;
  color: #475569;
  font-weight: 500;
}
</style>
src/store/modules/user.js
@@ -205,7 +205,7 @@
            } else if (orgid == "47231077933110211A1101") {
              localStorage.setItem("orgname", "莲都区人民医院");
              localStorage.setItem("ZuHuID", "1429338802177000011");
              localStorage.setItem("deptCode", "");
              localStorage.setItem("deptCode", "01020901");
              localStorage.setItem("YongHuID", "1512710152715767808");
              localStorage.setItem("YongHuXM", "LDRMYY");
            } else if (orgid == "1" && campusid == 1) {
src/views/Satisfaction/1.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1814 @@
<template>
  <div class="Questionnairemanagement">
    <div class="leftvlue">
      <div class="leftvlue-bg">
        <el-row :gutter="20">
          <!--标签数据-->
          <el-col :span="24" :xs="24">
            <el-form
              :model="queryParams"
              ref="queryForm"
              size="small"
              :inline="true"
              v-show="showSearch"
              label-width="98px"
            >
              <el-form-item label="统计类型" prop="userName">
                <el-select
                  v-model="queryParams.statisticaltype"
                  placeholder="请选择统计类型"
                >
                  <el-option
                    v-for="item in Statisticallist"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  >
                  </el-option>
                </el-select>
                <el-select
                  style="margin-left: 10px"
                  v-if="queryParams.statisticaltype == 1"
                  v-model="queryParams.leavehospitaldistrictcodes"
                  size="medium"
                  multiple
                  filterable
                  placeholder="请选择病区"
                >
                  <el-option
                    v-for="item in flatArrayhospit"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  >
                  </el-option>
                </el-select>
                <el-select
                  v-else-if="queryParams.statisticaltype == 2"
                  v-model="queryParams.deptcodes"
                  size="medium"
                  multiple
                  filterable
                  placeholder="请选择科室"
                >
                  <el-option
                    v-for="item in flatArraydept"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  >
                  </el-option>
                </el-select>
              </el-form-item>
              <el-form-item label="服务类型" prop="userName">
                <el-select
                  v-model="queryParams.serviceType"
                  multiple
                  placeholder="请选择"
                >
                  <el-option
                    v-for="item in options"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  >
                  </el-option>
                </el-select>
              </el-form-item>
              <el-form-item
                label-width="200"
                label="随访时间范围"
                prop="userName"
              >
                <el-date-picker
                  v-model="queryParams.dateRange"
                  value-format="yyyy-MM-dd"
                  type="daterange"
                  range-separator="至"
                  start-placeholder="开始日期"
                  end-placeholder="结束日期"
                >
                </el-date-picker>
              </el-form-item>
              <el-form-item>
                <el-button
                  type="primary"
                  icon="el-icon-search"
                  size="medium"
                  @click="handleQuery"
                  >搜索</el-button
                >
                <el-button
                  icon="el-icon-refresh"
                  size="medium"
                  @click="resetQuery"
                  >重置</el-button
                >
              </el-form-item>
              <el-col :span="19">
                <el-button
                  type="warning"
                  plain
                  icon="el-icon-download"
                  size="medium"
                  @click="handleExport"
                  >导出</el-button
                >
                <el-button
                  type="primary"
                  plain
                  icon="el-icon-data-line"
                  size="medium"
                  @click="showChartDialog"
                  >统计趋势图</el-button
                >
              </el-col>
            </el-form>
            <el-table
              v-loading="loading"
              :data="userList"
              :border="true"
              @selection-change="handleSelectionChange"
              :row-key="getRowKey"
              :expand-row-keys="expands"
            >
              <el-table-column
                label="出院病区"
                align="center"
                sortable
                key="leavehospitaldistrictname"
                prop="leavehospitaldistrictname"
                :show-overflow-tooltip="true"
              />
              <el-table-column
                label="科室"
                align="center"
                key="deptname"
                prop="deptname"
                :show-overflow-tooltip="true"
              />
              <el-table-column
                label="出院人次"
                align="center"
                key="dischargeCount"
                prop="dischargeCount"
              >
              </el-table-column>
              <el-table-column
                label="无需随访人次"
                align="center"
                key="nonFollowUp"
                prop="nonFollowUp"
              >
              </el-table-column>
              <el-table-column
                label="应随访人次"
                align="center"
                key="followUpNeeded"
                prop="followUpNeeded"
              >
              </el-table-column>
              <el-table-column
                label="随访率"
                align="center"
                key="followUpRate"
                prop="followUpRate"
              >
                <!-- <template slot-scope="scope">
                    <span
                      >{{
                        (Number(scope.row.followUpRate) * 100).toFixed(2)
                      }}%</span
                    >
                  </template> -->
              </el-table-column>
              <el-table-column
                label="及时率"
                align="center"
                key="rate"
                prop="rate"
              >
                <template slot-scope="scope">
                  <el-button
                    size="medium"
                    type="text"
                    @click="Seedetails(scope.row)"
                    ><span class="button-zx"
                      >{{ (Number(scope.row.rate) * 100).toFixed(2) }}%</span
                    ></el-button
                  >
                </template>
              </el-table-column>
              <el-table-column
                label="满意度题目总量"
                align="center"
                key="joyAllCount"
                prop="joyAllCount"
              >
              </el-table-column>
              <el-table-column
                label="满意度填报量"
                align="center"
                key="joyCount"
                prop="joyCount"
              >
              </el-table-column>
              <el-table-column
                label="完成比率"
                align="center"
                key="joyTotal"
                prop="joyTotal"
              >
                <template slot-scope="scope">
                  <span class="button-zx"
                    >{{ (Number(scope.row.joyTotal) * 100).toFixed(2) }}%</span
                  >
                </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-button
                    size="medium"
                    type="text"
                    @click="getinfo(scope.row)"
                    ><span class="button-zx"
                      ><i class="el-icon-s-order"></i>查看详情</span
                    ></el-button
                  >
                </template>
              </el-table-column>
            </el-table>
            <!-- <pagination
              v-show="total > 0"
              :total="total"
              :page.sync="queryParams.pageNum"
              :limit.sync="queryParams.pageSize"
              @pagination="getList"
            /> -->
          </el-col>
        </el-row>
      </div>
    </div>
    <!-- ç»Ÿè®¡è¶‹åŠ¿å›¾å¼¹çª— -->
    <el-dialog
      title="随访统计趋势图"
      :visible.sync="chartDialogVisible"
      width="80%"
      :close-on-click-modal="false"
    >
      <div class="chart-container">
        <el-row :gutter="20">
          <el-col :span="12">
            <div class="chart-title">随访状态分布</div>
            <div id="pieChart" style="width: 100%; height: 400px"></div>
          </el-col>
          <el-col :span="12">
            <div class="chart-title">随访趋势分析</div>
            <div id="barLineChart" style="width: 100%; height: 400px"></div>
          </el-col>
        </el-row>
      </div>
    </el-dialog>
    <el-dialog
      title="未及时随访患者服务"
      :visible.sync="SeedetailsVisible"
      v-loading="Seedloading"
      width="70%"
      :close-on-click-modal="false"
    >
      <div class="examine-jic">
        <div class="jic-value">
          <el-row :gutter="20">
            <!--用户数据-->
            <el-form
              :model="patientqueryParams"
              ref="queryForm"
              size="small"
              :inline="true"
              label-width="98px"
            >
              <el-form-item label="患者:">
                <el-input
                  v-model="patientqueryParams.name"
                  @keyup.enter.native="handleQuery"
                ></el-input>
              </el-form-item>
              <el-form-item label="患者诊断:">
                <el-input
                  v-model="patientqueryParams.leavediagname"
                  @keyup.enter.native="handleQuery"
                ></el-input>
              </el-form-item>
              <el-form-item>
                <el-button
                  type="primary"
                  icon="el-icon-search"
                  size="medium"
                  @click="handleQuery"
                  >搜索</el-button
                >
                <el-button
                  icon="el-icon-refresh"
                  size="medium"
                  @click="resetQuery"
                  >取消创建</el-button
                >
              </el-form-item>
            </el-form>
            <!-- é€‰æ‹©æ‚£è€…列表 -->
            <el-table :data="logsheetlist" style="width: 100%">
              <el-table-column
                prop="sendname"
                align="center"
                label="姓名"
                width="100"
              >
              </el-table-column>
              <el-table-column
                prop="taskName"
                align="center"
                width="200"
                show-overflow-tooltip
                label="任务名称"
              >
              </el-table-column>
              <el-table-column
                prop="sendstate"
                align="center"
                width="200"
                label="任务状态"
              >
                <template slot-scope="scope">
                  <div v-if="scope.row.sendstate == 1">
                    <el-tag type="primary" :disable-transitions="false"
                      >表单已领取</el-tag
                    >
                  </div>
                  <div v-if="scope.row.sendstate == 2">
                    <el-tag type="primary" :disable-transitions="false"
                      >待随访</el-tag
                    >
                  </div>
                  <div v-if="scope.row.sendstate == 3">
                    <el-tag type="success" :disable-transitions="false"
                      >表单已发送</el-tag
                    >
                  </div>
                  <div v-if="scope.row.sendstate == 4">
                    <el-tag type="info" :disable-transitions="false"
                      >不执行</el-tag
                    >
                  </div>
                  <div v-if="scope.row.sendstate == 5">
                    <el-tag type="danger" :disable-transitions="false"
                      >发送失败</el-tag
                    >
                  </div>
                  <div v-if="scope.row.sendstate == 6">
                    <el-tag type="success" :disable-transitions="false"
                      >已完成</el-tag
                    >
                  </div>
                </template>
              </el-table-column>
              <el-table-column
                prop="visitTime"
                align="center"
                label="应随访时间"
                width="200"
                show-overflow-tooltip
              >
              </el-table-column>
              <el-table-column
                prop="finishtime"
                align="center"
                label="随访完成时间"
                width="200"
                show-overflow-tooltip
              >
              </el-table-column>
              <el-table-column
                label="出院日期"
                width="200"
                align="center"
                key="endtime"
                prop="endtime"
              >
                <template slot-scope="scope">
                  <span>{{ formatTime(scope.row.endtime) }}</span>
                </template></el-table-column
              >
              <el-table-column
                label="责任护士"
                width="120"
                align="center"
                key="nurseName"
                prop="nurseName"
              />
              <el-table-column
                label="主治医生"
                width="120"
                align="center"
                key="drname"
                prop="drname"
              />
              <el-table-column
                label="结果状态"
                align="center"
                key="excep"
                prop="excep"
                width="120"
              >
                <template slot-scope="scope">
                  <dict-tag
                    :options="dict.type.sys_yujing"
                    :value="scope.row.excep"
                  />
                </template>
              </el-table-column>
              <el-table-column
                label="处理意见"
                align="center"
                key="suggest"
                prop="suggest"
                width="120"
              >
                <template slot-scope="scope">
                  <dict-tag
                    :options="dict.type.sys_suggest"
                    :value="scope.row.suggest"
                  />
                </template>
              </el-table-column>
              <el-table-column
                prop="templatename"
                align="center"
                label="服务模板"
                width="200"
                show-overflow-tooltip
              >
              </el-table-column>
              <el-table-column
                prop="remark"
                align="center"
                label="服务记录"
                width="200"
                show-overflow-tooltip
              >
              </el-table-column>
              <el-table-column
                prop="bankcardno"
                align="center"
                label="呼叫状态"
                width="210"
              >
              </el-table-column>
              <el-table-column
                label="操作"
                fixed="right"
                align="center"
                width="200"
                class-name="small-padding fixed-width"
              >
                <template slot-scope="scope">
                  <el-button
                    size="medium"
                    type="text"
                    @click="SeedetailsgGo(scope.row)"
                    ><span class="button-zx"
                      ><i class="el-icon-s-order"></i>查看</span
                    ></el-button
                  >
                </template>
              </el-table-column>
            </el-table>
          </el-row>
          <pagination
            v-show="patienttotal > 0 && this.patientqueryParams.allhosp != 6"
            :total="patienttotal"
            :page.sync="patientqueryParams.pn"
            :limit.sync="patientqueryParams.ps"
            @pagination="Seedetails"
          />
        </div>
      </div>
    </el-dialog>
    <!-- å•科室统计详情 -->
    <el-dialog :visible.sync="topicVisible" width="45%">
      <div class="topicdia">
        <div class="top-text">
          {{ topicvalue.name }}<span>满意度指标详情</span>
        </div>
        <div style="overflow-x: hidden; overflow-y: auto; max-height: 65vh">
          <div
            class="ttaabbcc"
            v-for="(item, index) in topiclist"
            :key="item.name"
          >
            <div class="describe">
              ç¬¬{{ index }}题: {{ item.scriptContent }}?<span
                >[{{ item.scriptType == 1 ? "单选题" : "多选题" }}]</span
              >
            </div>
            <div>
              <el-table :data="item.details" style="width: 100%">
                <el-table-column prop="optionText" label="问题选项">
                </el-table-column>
                <el-table-column prop="chosenQuantity" label="选择人数">
                </el-table-column>
                <el-table-column prop="chosenPercentage" label="比例">
                  <template slot-scope="scope">
                    <span class="button-zx"
                      >{{
                        (Number(scope.row.chosenPercentage) * 100).toFixed(2)
                      }}%</span
                    >
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </div>
        </div>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="topicVisible = false">关 é—­</el-button>
      </span>
    </el-dialog>
  </div>
</template>
<script>
import {
  toamendtag,
  addapitag,
  deletetag,
  changetagcategory,
} from "@/api/system/label";
import store from "@/store";
import {
  getSfStatisticsJoy,
  getSfStatisticsJoyInfo,
  selectTimelyRate,
} from "@/api/system/user";
import ExcelJS from "exceljs";
import { saveAs } from "file-saver";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
const shortcuts = [
  {
    text: "今天",
    onClick(picker) {
      picker.$emit("pick", new Date());
    },
  },
  {
    text: "昨天",
    onClick(picker) {
      const date = new Date();
      date.setTime(date.getTime() - 3600 * 1000 * 24);
      picker.$emit("pick", date);
    },
  },
  {
    text: "一周前",
    onClick(picker) {
      const date = new Date();
      date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
      picker.$emit("pick", date);
    },
  },
];
export default {
  name: "Percentage",
  dicts: ["sys_normal_disable", "sys_user_sex"],
  components: { Treeselect },
  data() {
    return {
      topactiveName: "Local", //顶部选择
      activeName: "first", //侧边选择
      expands: [],
      // é®ç½©å±‚
      loading: false,
      Seedloading: false,
      chartDialogVisible: false,
      pieChart: null,
      barLineChart: null,
      // é€‰ä¸­æ•°ç»„
      ids: [],
      // éžå•个禁用
      single: true,
      // éžå¤šä¸ªç¦ç”¨
      multiple: true,
      // æ˜¾ç¤ºæœç´¢æ¡ä»¶
      showSearch: true,
      idds: "", //分类id
      // æ€»æ¡æ•°
      total: 0,
      flatArrayhospit: [],
      flatArraydept: [],
      patienttotal: 0,
      logsheetlist: [],
      Statisticallist: [
        {
          label: "病区统计",
          value: 1,
        },
        {
          label: "科室统计",
          value: 2,
        },
      ],
      patientqueryParams: {
        pn: 1,
        ps: 10,
      },
      topiclist: [
        {
          name: "您的身体康复情况如何",
          number: 1,
          type: 1,
        },
        {
          name: "您的饮食情况如何",
          number: 2,
          type: 2,
        },
        {
          name: "您的恢复情况如何",
          number: 3,
          type: 1,
        },
      ],
      tableData: [
        {
          date: "好",
          name: 12,
          address: "50%",
        },
        {
          date: "一般",
          name: 2,
          address: "6.2%",
        },
        {
          date: "å·®",
          name: 0,
          address: "0%",
        },
      ],
      amendtag: false, //是否修改类别
      lstamendtag: false, //是否修改标签
      scavisible: false, //删除弹框
      deleteVisible: false, //分类删除弹框
      deletefenl: "高血压", //删除项
      //修改添加标签弹框数据
      tagform: {
        isupload: "",
        tagname: "",
        tagcategoryid: "",
        tagdescription: "",
      },
      classifyform: {
        categoryname: "",
      },
      // æ ‡ç­¾è¡¨æ ¼æ•°æ®
      userList: [],
      // å¼¹å‡ºå±‚标题
      title: "",
      // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚
      open: false,
      // æ—¥æœŸèŒƒå›´
      dateRange: [],
      // å²—位选项
      postOptions: [],
      // è§’色选项
      roleOptions: [],
      // å­˜å‚¨æ‰€æœ‰ç§‘室代码
      allDeptCodes: [],
      // å­˜å‚¨æ‰€æœ‰ç—…区代码
      allWardCodes: [],
      // è¡¨å•参数
      form: {},
      forms: {
        name: "",
      },
      numberlb: 22,
      dialogFormVisible: false, //添加、修改类别弹框
      lstamendtagVisible: false, //添加、修改标签弹框
      goQRCodeVisible: false, //二维码弹框
      topicVisible: false, //控制单题弹框
      topicvalue: {
        name: "骨科随访模板",
        number: 222,
      },
      sidecolumnval: "", //类别搜索
      propss: { multiple: true },
      SeedetailsVisible: false,
      options: store.getters.tasktypes,
      pickerOptions: {
        disabledDate(time) {
          return time.getTime() < Date.now() - 3600 * 1000 * 24;
        },
        shortcuts: shortcuts,
      },
      pickerOptionsa: {
        disabledDate(time) {
          return time.getTime() > Date.now();
        },
        shortcuts: shortcuts,
      },
      // æŸ¥è¯¢æ ‡ç­¾åˆ—表参数
      queryParams: {
        serviceType: [2],
        dateRange: [],
        statisticaltype: 1,
        leavehospitaldistrictcodes: ["all"], // é»˜è®¤é€‰ä¸­å…¨éƒ¨ç—…区
        deptcodes: [], // é»˜è®¤é€‰ä¸­å…¨éƒ¨ç§‘室
      },
      // åˆ—信息
      columns: [
        { key: 0, label: `标签编号`, visible: true },
        { key: 1, label: `标签名称`, visible: true },
        { key: 2, label: `标签昵称`, visible: true },
        { key: 3, label: `部门`, visible: true },
        { key: 4, label: `手机号码`, visible: true },
        { key: 5, label: `状态`, visible: true },
        { key: 6, label: `创建时间`, visible: true },
      ],
    };
  },
  watch: {},
  created() {
    this.getDeptTree();
    this.getList();
  },
  methods: {
    /** æŸ¥è¯¢æ ‡ç­¾åˆ—表 */
    getList() {
      // å¤„理查询参数
      const params = {
        configKey: "joyCount",
        ...this.queryParams,
        // å¦‚果选择了"全部",则传所有病区/科室代码
        leavehospitaldistrictcodes:
          this.queryParams.leavehospitaldistrictcodes.includes("all")
            ? this.allWardCodes
            : this.queryParams.leavehospitaldistrictcodes,
        deptcodes: this.queryParams.deptcodes.includes("all")
          ? this.allDeptCodes
          : this.queryParams.deptcodes,
      };
      this.loading = true;
      // ç§»é™¤å¯èƒ½å­˜åœ¨çš„"all"值
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      getSfStatisticsJoy(params).then((response) => {
        this.loading = false;
        this.total = response.total;
        this.userList = response.data;
      });
    },
    getRowKey(row) {
      return row.statisticaltype === 1
        ? row.leavehospitaldistrictcode
        : row.deptcode;
    },
    /** ä¿®æ”¹æ ‡ç­¾ */
    handleUpdate(row) {
      console.log(row, "修改标签");
      this.lstamendtagVisible = true;
      this.lstamendtag = true;
      this.tagform = {
        isupload: row.isupload,
        tagname: row.tagname,
        tagcategoryid: row.tagcategoryid,
        tagdescription: row.tagdescription,
        tagid: row.tagid,
      };
    },
    // èŽ·å–ç§‘å®¤æ ‘
    getDeptTree() {
      // ç§‘室列表
      this.flatArraydept = store.getters.belongDepts.map((dept) => {
        return {
          label: dept.deptName,
          value: dept.deptCode,
        };
      });
      // å­˜å‚¨æ‰€æœ‰ç§‘室代码
      this.allDeptCodes = store.getters.belongDepts.map(
        (dept) => dept.deptCode
      );
      // ç—…区列表
      this.flatArrayhospit = store.getters.belongWards.map((ward) => {
        return {
          label: ward.districtName,
          value: ward.districtCode,
        };
      });
      // å­˜å‚¨æ‰€æœ‰ç—…区代码
      this.allWardCodes = store.getters.belongWards.map(
        (ward) => ward.districtCode
      );
      this.flatArraydept.push({ label: "全部", value: "all" });
      this.flatArrayhospit.push({ label: "全部", value: "all" });
    },
    flattenArray(multiArray) {
      let result = [];
      // é€’归函数,用于将多级数组转换为一维数组,只包含最底层的元素
      function flatten(element) {
        // å¦‚果当前元素有子元素,继续递归
        if (element.children && element.children.length > 0) {
          element.children.forEach((child) => flatten(child));
        } else {
          // å…‹éš†å…ƒç´ ä»¥é¿å…ä¿®æ”¹åŽŸå§‹æ•°æ®
          let item = JSON.parse(JSON.stringify(element));
          result.push(item); // å°†æœ€åº•层的元素添加到结果数组
        }
      }
      // ä»Žé¡¶å±‚元素开始递归
      multiArray.forEach((element) => flatten(element));
      return result; // è¿”回只包含最底层元素的一维数组
    },
    addladeltag() {
      this.lstamendtagVisible = true;
      this.lstamendtag = false;
      this.tagform = {
        isupload: "",
        tagname: "",
        tagcategoryid: "",
        tagdescription: "",
        tagid: "",
      };
    },
    Seedetails(row) {
      this.SeedetailsVisible = true;
      this.Seedloading = true;
      this.patientqueryParams.starttime = this.parseTime(
        this.queryParams.dateRange[0]
      );
      this.patientqueryParams.endtime = this.parseTime(
        this.queryParams.dateRange[1]
      );
      this.patientqueryParams.deptcode = row.deptcode;
      selectTimelyRate(this.patientqueryParams).then((response) => {
        this.logsheetlist = response.data.detail;
        this.patienttotal = response.data.total;
        this.Seedloading = false;
      });
    },
    SeedetailsgGo(row) {
      this.SeedetailsVisible = false;
      let type = "";
      if (row.preachformson && row.preachformson.includes("3")) {
        type = 1;
      }
      setTimeout(() => {
        this.$router.push({
          path: "/followvisit/record/detailpage/",
          query: {
            taskid: row.taskid,
            patid: row.patid,
            id: row.id,
            Voicetype: type,
            // visitCount: this.topqueryParams.visitCount,
          },
        });
      }, 300);
    },
    // è°ƒèµ·è¯¦æƒ…
    getinfo(row) {
      this.topicVisible = true;
      // å¤„理查询参数
      const params = {
        configKey: "joyCount",
        ...this.queryParams,
      };
      if (this.queryParams.statisticaltype == 1) {
        this.topicvalue.name = row.leavehospitaldistrictname;
        params.leavehospitaldistrictcodes = [row.leavehospitaldistrictcode];
      } else {
        this.topicvalue.name = row.deptname;
        params.deptcodes = [row.deptcode];
      }
      // ç§»é™¤å¯èƒ½å­˜åœ¨çš„"all"值
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      getSfStatisticsJoyInfo(params).then((response) => {
        console.log(response);
        this.topiclist = response.data;
      });
    },
    // æ·»åŠ /修改标签
    Maintenancetag() {
      if (this.lstamendtag) {
        toamendtag(this.addDateRange(this.tagform)).then((response) => {
          console.log(response);
          this.getList();
        });
      } else {
        addapitag(this.addDateRange(this.tagform)).then((response) => {
          console.log(response);
          this.getList();
        });
      }
      this.tagform = {
        isupload: "",
        tagname: "",
        tagcategoryid: "",
        tagdescription: "",
        tagid: "",
      };
    },
    routerErr(row) {
      console.log(row, "跳转异常");
      this.$router.push({
        path: "/followvisit/discharge",
        query: {
          errtype: 1,
          leavehospitaldistrictcode: row.leavehospitaldistrictcode,
        },
      });
    },
    // è¡¨å•重置
    reset() {
      this.form = {
        userId: undefined,
        deptId: undefined,
        userName: undefined,
        nickName: undefined,
        password: undefined,
        phonenumber: undefined,
        email: undefined,
        sex: undefined,
        status: "0",
        remark: undefined,
        postIds: [],
        roleIds: [],
      };
      this.resetForm("form");
    },
    // æ ‡ç­¾çŠ¶æ€ä¿®æ”¹
    handleStatusChange(row) {
      console.log(row.isupload);
      let text = row.isupload === "0" ? "启用" : "停用";
      this.$modal
        .confirm('确认要"' + text + '""' + row.tagname + '"标签吗?')
        .then(function () {
          return changetagcategory(row.tagid, row.isupload);
        })
        .then(() => {
          this.$modal.msgSuccess(text + "成功");
        })
        .catch(function () {
          row.isupload = row.isupload === "0" ? "1" : "0";
        });
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ */
    handleQuery() {
      this.queryParams.pageNum = 1;
      if (!this.queryParams.dateRange) this.queryParams.dateRange = [];
      if (this.queryParams.statisticaltype == 1) {
        this.queryParams.deptcodes = [];
      } else if (this.queryParams.statisticaltype == 2) {
        this.queryParams.leavehospitaldistrictcodes = [];
      }
      console.log(this.queryParams.dateRange);
      this.queryParams.startTime = this.parseTime(
        this.queryParams.dateRange[0]
      );
      this.queryParams.endTime = this.parseTime(this.queryParams.dateRange[1]);
      this.getList();
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    resetQuery() {
      this.queryParams.dateRange = [];
      this.queryParams.leavehospitaldistrictcodes = [];
      this.handleQuery();
    },
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      this.ids = selection.map((item) => item.tagid);
      this.single = selection.length != 1;
      this.multiple = !selection.length;
    },
    /** åˆ é™¤æŒ‰é’®æ“ä½œ */
    handleDelete(row) {
      console.log(row, "删除弹窗");
      const tagids = row.tagid || this.ids;
      console.log(tagids);
      const tagname = row.tagname;
      this.$modal
        .confirm(
          tagname
            ? '是否确认删除标签名称为"' + tagname + '"的数据项?'
            : "是否确认删除选中的数据项?"
        )
        .then(function () {
          return deletetag(tagids);
        })
        .then(() => {
          this.getList();
          this.$modal.msgSuccess("删除成功");
        })
        .catch(() => {});
    },
    /** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
    /** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
    async handleExport() {
      try {
        // èŽ·å–å¹¶æ ¼å¼åŒ–æ—¥æœŸèŒƒå›´
        let dateRangeString = "";
        let sheetNameSuffix = "";
        // æ£€æŸ¥æ˜¯å¦å­˜åœ¨é€‰ä¸­çš„æ—¥æœŸèŒƒå›´
        if (
          this.queryParams.dateRange &&
          this.queryParams.dateRange.length === 2
        ) {
          const startDateStr = this.queryParams.dateRange[0]; // å¼€å§‹æ—¥æœŸï¼Œæ ¼å¼ "yyyy-MM-dd"
          const endDateStr = this.queryParams.dateRange[1]; // ç»“束日期
          // ç›´æŽ¥ä½¿ç”¨æ—¥æœŸéƒ¨åˆ†ï¼ˆå·²ç»æ˜¯ yyyy-MM-dd æ ¼å¼ï¼‰
          const startDateFormatted = startDateStr;
          const endDateFormatted = endDateStr;
          // æž„建日期范围字符串
          dateRangeString = `${startDateFormatted}至${endDateFormatted}`;
          sheetNameSuffix = `${startDateFormatted}至${endDateFormatted}`;
        } else {
          // å¦‚果没有选择日期范围,则使用当前月份作为备选方案
          const now = new Date();
          const currentMonth = now.getMonth() + 1;
          dateRangeString = `${currentMonth}月`;
          sheetNameSuffix = `${currentMonth}月`;
        }
        // æž„建文件名和工作表名
        const excelName = `满意度统计表_${dateRangeString}.xlsx`;
        const worksheetName = `满意度统计_${sheetNameSuffix}`;
        // åˆ›å»ºå·¥ä½œç°¿å’Œå·¥ä½œè¡¨
        const workbook = new ExcelJS.Workbook();
        const worksheet = workbook.addWorksheet(worksheetName);
        // å®šä¹‰æ ·å¼
        const titleStyle = {
          font: {
            name: "微软雅黑",
            size: 16,
            bold: true,
            color: { argb: "FF000000" },
          },
          fill: {
            type: "pattern",
            pattern: "solid",
            fgColor: { argb: "FFE6F3FF" },
          },
          alignment: {
            vertical: "middle",
            horizontal: "center",
            wrapText: true,
          },
          border: {
            top: { style: "thin", color: { argb: "FFD0D0D0" } },
            left: { style: "thin", color: { argb: "FFD0D0D0" } },
            bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
            right: { style: "thin", color: { argb: "FFD0D0D0" } },
          },
        };
        const headerStyle = {
          font: {
            name: "微软雅黑",
            size: 11,
            bold: true,
            color: { argb: "FF000000" },
          },
          fill: {
            type: "pattern",
            pattern: "solid",
            fgColor: { argb: "FFF5F7FA" },
          },
          alignment: {
            vertical: "middle",
            horizontal: "center",
            wrapText: true,
          },
          border: {
            top: { style: "thin", color: { argb: "FFD0D0D0" } },
            left: { style: "thin", color: { argb: "FFD0D0D0" } },
            bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
            right: { style: "thin", color: { argb: "FFD0D0D0" } },
          },
        };
        const cellStyle = {
          font: {
            name: "宋体",
            size: 10,
            color: { argb: "FF000000" },
          },
          alignment: {
            vertical: "middle",
            horizontal: "center",
          },
          border: {
            top: { style: "thin", color: { argb: "FFD0D0D0" } },
            left: { style: "thin", color: { argb: "FFD0D0D0" } },
            bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
            right: { style: "thin", color: { argb: "FFD0D0D0" } },
          },
        };
        // æ·»åŠ æ€»æ ‡é¢˜
        worksheet.mergeCells(1, 1, 1, 10);
        const titleCell = worksheet.getCell(1, 1);
        titleCell.value = `满意度统计表(${sheetNameSuffix})`;
        titleCell.style = titleStyle;
        worksheet.getRow(1).height = 35;
        // æ·»åŠ è¡¨å¤´
        const headers = [
          "出院病区",
          "科室",
          "出院人次",
          "无需随访人次",
          "应随访人次",
          "随访率",
          "及时率",
          "满意度题目总量",
          "满意度填报量",
          "完成比率",
        ];
        const headerRow = worksheet.addRow(headers);
        headerRow.eachCell((cell) => {
          cell.style = headerStyle;
        });
        headerRow.height = 25;
        // æ·»åŠ æ•°æ®è¡Œ
        this.userList.forEach((item) => {
          const dataRow = worksheet.addRow([
            item.leavehospitaldistrictname || "",
            item.deptname || "",
            item.dischargeCount || 0,
            item.nonFollowUp || 0,
            item.followUpNeeded || 0,
            item.followUpRate || "0%",
            item.rate ? (Number(item.rate) * 100).toFixed(2) + "%" : "0%",
            item.joyAllCount || 0,
            item.joyCount || 0,
            item.joyTotal
              ? (Number(item.joyTotal) * 100).toFixed(2) + "%"
              : "0%",
          ]);
          dataRow.eachCell((cell) => {
            cell.style = cellStyle;
          });
          dataRow.height = 22;
        });
        // è®¾ç½®åˆ—宽
        worksheet.columns = [
          { width: 20 }, // å‡ºé™¢ç—…区
          { width: 15 }, // ç§‘室
          { width: 12 }, // å‡ºé™¢äººæ¬¡
          { width: 12 }, // æ— éœ€éšè®¿äººæ¬¡
          { width: 12 }, // åº”随访人次
          { width: 12 }, // éšè®¿çއ
          { width: 12 }, // åŠæ—¶çއ
          { width: 15 }, // æ»¡æ„åº¦é¢˜ç›®æ€»é‡
          { width: 15 }, // æ»¡æ„åº¦å¡«æŠ¥é‡
          { width: 12 }, // å®Œæˆæ¯”率
        ];
        // ç”Ÿæˆå¹¶ä¸‹è½½æ–‡ä»¶
        const buffer = await workbook.xlsx.writeBuffer();
        const blob = new Blob([buffer], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        });
        // ä½¿ç”¨FileSaver.js保存文件[2,3](@ref)
        saveAs(blob, excelName);
        this.$message.success("导出成功");
      } catch (error) {
        console.error("导出失败:", error);
        this.$message.error(`导出失败: ${error.message}`);
      }
    },
    // æ˜¾ç¤ºå›¾è¡¨å¼¹çª—
    showChartDialog() {
      this.chartDialogVisible = true;
      this.$nextTick(() => {
        this.initPieChart();
        this.initBarLineChart();
      });
    },
    // åœ¨methods中修改统计方法
    showChartDialog() {
      this.chartDialogVisible = true;
      this.$nextTick(() => {
        console.log(this.userList, "this.userList");
        this.initCharts();
      });
    },
    // æ–°å¢žåˆå§‹åŒ–图表方法
    initCharts() {
      this.initPieChart();
      this.initBarLineChart();
    },
    // åˆå§‹åŒ–饼图
    initPieChart() {
      const echarts = require("echarts");
      const pieDom = document.getElementById("pieChart");
      if (!pieDom) return;
      if (this.pieChart) {
        this.pieChart.dispose();
      }
      this.pieChart = echarts.init(pieDom);
      // è®¡ç®—饼图数据
      const followUpData = {
        pending: 0,
        success: 0,
        fail: 0,
      };
      this.userList.forEach((item) => {
        followUpData.pending += item.pendingFollowUp || 0;
        followUpData.success += item.followUpSuccess || 0;
        followUpData.fail += item.followUpFail || 0;
      });
      // ä½¿ç”¨æ›´ç¾Žè§‚的颜色方案
      const pieOption = {
        title: {
          text: "随访状态分布",
          left: "center",
          textStyle: {
            color: "#333",
            fontSize: 16,
          },
        },
        tooltip: {
          trigger: "item",
          formatter: "{a} <br/>{b}: {c} ({d}%)",
        },
        legend: {
          orient: "vertical",
          left: "left",
          data: ["待随访", "随访成功", "随访失败"],
          textStyle: {
            color: "#666",
          },
        },
        color: ["#FF9D4D", "#36B37E", "#FF5C5C"], // æ–°çš„配色方案
        series: [
          {
            name: "随访状态",
            type: "pie",
            radius: ["40%", "70%"],
            avoidLabelOverlap: true,
            itemStyle: {
              borderRadius: 10,
              borderColor: "#fff",
              borderWidth: 2,
            },
            label: {
              show: true,
              formatter: "{b}: {c} ({d}%)",
              color: "#333",
            },
            emphasis: {
              label: {
                show: true,
                fontSize: "18",
                fontWeight: "bold",
              },
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: "rgba(0, 0, 0, 0.5)",
              },
            },
            data: [
              {
                value: followUpData.pending,
                name: "待随访",
              },
              {
                value: followUpData.success,
                name: "随访成功",
              },
              {
                value: followUpData.fail,
                name: "随访失败",
              },
            ],
          },
        ],
      };
      this.pieChart.setOption(pieOption);
      window.addEventListener("resize", this.resizePieChart);
    },
    // åˆå§‹åŒ–柱状折线图
    initBarLineChart() {
      const echarts = require("echarts");
      const barDom = document.getElementById("barLineChart");
      if (!barDom) return;
      if (this.barLineChart) {
        this.barLineChart.dispose();
      }
      this.barLineChart = echarts.init(barDom);
      // å‡†å¤‡æ•°æ®
      const categories = this.userList.map(
        (item) => item.leavehospitaldistrictname || item.deptname
      );
      const dischargeData = this.userList.map(
        (item) => item.dischargeCount || 0
      );
      const followUpData = this.userList.map(
        (item) => item.followUpNeeded || 0
      );
      // æ–°å¢žä¸¤æ¡æŠ˜çº¿æ•°æ®
      const followUpRateData = this.userList.map((item) => {
        if (!item.followUpRate) return 0;
        // åŽ»æŽ‰ç™¾åˆ†å·å¹¶è½¬ä¸ºæ•°å­—
        const rateStr = String(item.followUpRate).replace("%", "");
        return parseFloat(rateStr) || 0;
      });
      const timelyRateData = this.userList.map((item) =>
        item.rate ? (Number(item.rate) * 100).toFixed(2) : 0
      );
      const option = {
        title: {
          text: "科室/病区随访趋势",
          left: "center",
          textStyle: {
            color: "#333",
            fontSize: 16,
          },
        },
        tooltip: {
          trigger: "axis",
          axisPointer: {
            type: "cross",
            crossStyle: {
              color: "#999",
            },
          },
        },
        legend: {
          data: ["出院人次", "应随访人次", "随访率(%)", "及时率(%)"],
          top: "bottom",
          textStyle: {
            color: "#666",
          },
        },
        color: ["#5470C6", "#91CC75", "#EE6666", "#9A60B4"], // æ–°å¢žç´«è‰²ç”¨äºŽåŠæ—¶çއ
        xAxis: {
          type: "category",
          data: categories,
          axisLabel: {
            interval: 0,
            rotate: 30,
            color: "#666",
          },
          axisLine: {
            lineStyle: {
              color: "#ddd",
            },
          },
        },
        yAxis: [
          {
            type: "value",
            name: "人次",
            min: 0,
            axisLabel: {
              color: "#666",
            },
            axisLine: {
              lineStyle: {
                color: "#ddd",
              },
            },
            splitLine: {
              lineStyle: {
                color: "#f0f0f0",
              },
            },
          },
          {
            type: "value",
            name: "百分比(%)",
            min: 0,
            max: 100,
            axisLabel: {
              color: "#666",
              formatter: "{value}%",
            },
            axisLine: {
              lineStyle: {
                color: "#ddd",
              },
            },
            splitLine: {
              show: false,
            },
          },
        ],
        series: [
          {
            name: "出院人次",
            type: "bar",
            barWidth: "25%",
            data: dischargeData,
            itemStyle: {
              borderRadius: [4, 4, 0, 0],
            },
          },
          {
            name: "应随访人次",
            type: "bar",
            barWidth: "25%",
            data: followUpData,
            itemStyle: {
              borderRadius: [4, 4, 0, 0],
            },
          },
          {
            name: "随访率(%)",
            type: "line",
            yAxisIndex: 1,
            data: followUpRateData,
            symbolSize: 8,
            lineStyle: {
              width: 3,
            },
            markLine: {
              silent: true,
              data: [
                {
                  yAxis: 80,
                  lineStyle: {
                    color: "#EE6666",
                    type: "dashed",
                  },
                  // label: {
                  //   position: 'end',
                  //   formatter: '目标80%'
                  // }
                },
              ],
            },
          },
          {
            name: "及时率(%)",
            type: "line",
            yAxisIndex: 1,
            data: timelyRateData,
            symbolSize: 8,
            lineStyle: {
              width: 3,
              type: "dotted", // ä½¿ç”¨è™šçº¿åŒºåˆ†
            },
            markLine: {
              silent: true,
              data: [
                {
                  yAxis: 90,
                  lineStyle: {
                    color: "#9A60B4",
                    type: "dashed",
                  },
                  // label: {
                  //   position: 'end',
                  //   formatter: '目标90%'
                  // }
                },
              ],
            },
          },
        ],
        grid: {
          top: "15%",
          left: "3%",
          right: "4%",
          bottom: "15%",
          containLabel: true,
        },
      };
      this.barLineChart.setOption(option);
      window.addEventListener("resize", this.resizeBarLineChart);
    },
    // å›¾è¡¨å“åº”式调整方法
    resizePieChart() {
      if (this.pieChart) {
        this.pieChart.resize();
      }
    },
    resizeBarLineChart() {
      if (this.barLineChart) {
        this.barLineChart.resize();
      }
    },
    // åœ¨ç»„件销毁时清理
    beforeDestroy() {
      // ç§»é™¤äº‹ä»¶ç›‘听
      window.removeEventListener("resize", this.resizePieChart);
      window.removeEventListener("resize", this.resizeBarLineChart);
      // é”€æ¯å›¾è¡¨å®žä¾‹
      if (this.pieChart) {
        this.pieChart.dispose();
        this.pieChart = null;
      }
      if (this.barLineChart) {
        this.barLineChart.dispose();
        this.barLineChart = null;
      }
    },
  },
};
</script>
<style lang="scss" scoped>
.sidecolumn {
  width: 180px;
  min-height: 100vh;
  text-align: center;
  //   display: flex;
  margin-top: 20px;
  margin: 20px;
  padding: 30px;
  background: #edf1f7;
  border: 1px solid #dcdfe6;
  -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12),
    0 0 6px 0 rgba(0, 0, 0, 0.04);
  .sidecolumn-top {
    display: flex;
    justify-content: space-between;
    .top-wj {
      font-size: 20px;
    }
    .top-tj {
      font-size: 18px;
      color: rgb(0, 89, 255);
      cursor: pointer;
    }
  }
  .center-ss {
    margin-top: 30px;
    .input-with-select {
      height: 40px !important;
    }
  }
  .bottom-fl {
    margin-top: 30px;
    display: center !important;
  }
}
.qrcode-dialo {
  text-align: center;
  //   display: flex;
  margin: 20px;
  padding: 30px;
  background: #edf1f7;
  border: 1px solid #dcdfe6;
  -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12),
    0 0 6px 0 rgba(0, 0, 0, 0.04);
  .qrcode-text {
    font-size: 20px;
    span {
      margin-left: 20px;
    }
  }
  .qrcode-img {
    width: 300px;
    height: 400px;
  }
}
.topicdia {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  color: #333; /* ä¸»æ–‡å­—色 */
}
/* å¤´éƒ¨æ ‡é¢˜æ ·å¼ */
.top-text {
  font-size: 18px;
  font-weight: 600;
  padding-bottom: 16px;
  margin-bottom: 20px;
  border-bottom: 1px solid #e8e8e8; /* çº¤ç»†çš„分隔线 */
  color: #1f2d3d; /* æ·±è‰²æ ‡é¢˜ */
}
.top-text span {
  font-size: 14px;
  font-weight: normal;
  color: #666; /* å‰¯æ ‡é¢˜é¢œè‰²ç¨æµ… */
  margin-left: 10px;
}
/* é¢˜ç›®å®¹å™¨æ ·å¼ */
.ttaabbcc {
  background: #fafafa; /* éžå¸¸æµ…的灰色背景 */
  border-radius: 6px;
  padding: 16px;
  margin-bottom: 20px;
  border-left: 4px solid #4794c5; /* å·¦ä¾§è£…饰色条,增加层次感 */
}
/* é¢˜ç›®æè¿°æ ·å¼ */
.describe {
  font-size: 15px;
  line-height: 1.6;
  margin-bottom: 12px;
  color: #1f2d3d;
}
.describe span {
  font-size: 13px;
  color: #999; /* é¢˜åž‹æç¤ºä¿¡æ¯é¢œè‰²æ›´æµ… */
  font-style: italic;
  margin-left: 8px;
}
/* è¡¨æ ¼æ•´ä½“样式调整 */
.ttaabbcc .el-table {
  border-radius: 4px;
  overflow: hidden;
  font-size: 14px;
}
/* è¡¨å¤´æ ·å¼ */
.ttaabbcc .el-table th {
  background-color: #f1f5f9; /* æµ…蓝色表头背景 */
  color: #333;
  font-weight: 600;
}
/* å•元格样式 */
.ttaabbcc .el-table td {
  border-bottom: 1px solid #f0f0f0; /* çº¤ç»†çš„行分隔线 */
  padding: 12px 0;
}
/* æ¯”例数据样式 */
.button-zx {
  color: #4794c5; /* ä½¿ç”¨ä¸Žä¸»é¢˜å‘¼åº”的蓝色 */
  font-weight: 500;
}
::v-deep.el-tabs--left,
.el-tabs--right {
  overflow: hidden;
  align-items: center;
  display: flex;
}
::v-deep.el-input--medium .el-input__inner {
  height: 40px !important;
}
::v-deep.el-tabs--right .el-tabs__active-bar.is-right {
  height: 40px;
  width: 5px;
  left: 0;
}
::v-deep.el-tabs--right .el-tabs__item.is-right {
  display: block;
  text-align: left;
  font-size: 20px;
}
.leftvlue {
  //   display: flex;
  //   flex: 1;
  // width: 80%;
  // margin-top: 20px;
  margin: 20px;
  padding: 30px;
  background: #ffff;
  border: 1px solid #dcdfe6;
  -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12),
    0 0 6px 0 rgba(0, 0, 0, 0.04);
  .mulsz {
    font-size: 20px;
  }
}
/* ä½¿è¡Œæœ‰æ‰‹åž‹æŒ‡é’ˆ */
.el-table__row {
  cursor: pointer;
}
/* å†…层医生表格样式 */
.inner-table {
  // è¡¨å¤´èƒŒæ™¯è‰²
  ::v-deep .el-table__header-wrapper {
    background-color: #f0f7ff !important;
    th {
      background-color: #f0f7ff !important;
    }
  }
  // è¡¨æ ¼è¡ŒèƒŒæ™¯è‰²
  ::v-deep .el-table__body-wrapper {
    tr {
      background-color: #f9fbfe !important;
      &:hover {
        background-color: #e6f1ff !important;
      }
    }
  }
  // è¾¹æ¡†é¢œè‰²
  ::v-deep .el-table--border {
    border-color: #d9e8ff !important;
    td,
    th {
      border-color: #d9e8ff !important;
    }
  }
  // æ–‘马纹效果
  ::v-deep .el-table--striped .el-table__body tr.el-table__row--striped td {
    background-color: #f5f9ff !important;
  }
}
/* å±•开行样式 */
.el-table__expanded-cell {
  padding: 10px 0 !important;
  background: #f8f8f8;
}
.document {
  width: 100px;
  height: 50px;
}
.documentf {
  display: flex;
  justify-content: flex-end;
}
.button-text {
  color: rgb(70, 204, 238);
}
.button-textck {
  color: rgb(39, 167, 67);
}
.button-textxg {
  color: rgb(35, 81, 233);
}
.button-textsc {
  color: rgb(235, 23, 23);
}
</style>
src/views/Satisfaction/configurationmyd/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1246 @@
<template>
  <div class="satisfaction-exception-config">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <div class="header-content">
        <h2 class="page-title">满意度题目异常处理配置</h2>
        <p class="page-description">
          ä¸ºæ»¡æ„åº¦é¢˜ç›®é…ç½®è´£ä»»ç§‘室和报备科室,优化异常反馈流程
        </p>
      </div>
    </div>
    <!-- æœç´¢åŒºåŸŸ -->
    <div class="search-card">
      <el-card shadow="never" class="search-container">
        <el-form :model="queryParams" :inline="true" size="medium">
          <el-form-item label="问题主题">
            <el-input
              v-model="queryParams.scriptTopic"
              placeholder="请输入问题主题"
              clearable
              @keyup.enter.native="handleQuery"
            />
          </el-form-item>
          <el-form-item label="问题内容">
            <el-input
              v-model="queryParams.scriptContent"
              placeholder="请输入问题内容"
              clearable
              @keyup.enter.native="handleQuery"
            />
          </el-form-item>
          <el-form-item label="是否可用">
            <el-select
              v-model="queryParams.isavailable"
              placeholder="请选择"
              clearable
            >
              <el-option
                v-for="item in qyoptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button
              type="primary"
              icon="el-icon-search"
              @click="handleQuery"
            >
              æœç´¢
            </el-button>
            <el-button icon="el-icon-refresh" @click="resetQuery">
              é‡ç½®
            </el-button>
          </el-form-item>
        </el-form>
      </el-card>
    </div>
    <!-- é…ç½®åˆ—表 -->
    <div class="config-content">
      <!-- æ‰¹é‡æ“ä½œæ  -->
      <div v-if="questionList.length > 0" class="batch-actions-card">
        <el-card shadow="never">
          <div class="batch-actions">
            <el-button
              type="success"
              icon="el-icon-check"
              :loading="batchSaving"
              :disabled="!hasChanges || batchSaving"
              @click="handleBatchSave"
              size="medium"
            >
              {{ batchSaving ? "批量保存中..." : "批量保存配置" }}
            </el-button>
            <span v-if="changedCount > 0" class="change-count">
              æœ‰ {{ changedCount }} é¡¹é…ç½®éœ€è¦ä¿å­˜
            </span>
            <div class="total-count">共 {{ total }} æ¡è®°å½•</div>
          </div>
        </el-card>
      </div>
      <div v-if="loading" class="loading-wrapper">
        <div class="loading-spinner">
          <i class="el-icon-loading"></i>
          <span>加载中...</span>
        </div>
      </div>
      <div v-else-if="questionList.length === 0" class="empty-wrapper">
        <el-empty description="暂无满意度题目数据">
          <el-button type="primary" @click="getQuestionList"
            >刷新数据</el-button
          >
        </el-empty>
      </div>
      <!-- ä¸€è¡Œä¸€è¡Œçš„卡片列表 -->
      <div v-else class="question-list">
        <div
          v-for="(question, index) in questionList"
          :key="question.id"
          class="question-item"
        >
          <el-card
            shadow="hover"
            class="question-card"
            :class="{ 'has-changes': question.hasChanges }"
          >
            <!-- å¡ç‰‡å¤´éƒ¨ -->
            <div class="card-header">
              <div class="header-left">
                <div class="question-index">
                  <span class="index-number">{{ index + 1 }}</span>
                  <div class="index-line"></div>
                </div>
                <div class="question-basic-info">
                  <div class="question-title-section">
                    <h3 class="question-topic" :title="question.scriptTopic">
                      {{ question.scriptTopic || "无主题" }}
                    </h3>
                    <div class="question-tags">
                      <el-tag
                        :type="question.isavailable == 1 ? 'danger' : 'success'"
                        size="small"
                      >
                        {{ question.isavailable == 1 ? "不可用" : "可用" }}
                      </el-tag>
                      <dict-tag
                        :options="askvaluetype"
                        :value="question.scriptType"
                        size="small"
                      />
                      <el-tag
                        v-if="question.targetname"
                        size="small"
                        type="info"
                      >
                        {{ question.targetname }}
                      </el-tag>
                    </div>
                  </div>
                  <div class="question-content-section">
                    <span class="content-label">题目内容:</span>
                    <span class="content-text">{{
                      question.scriptContent
                    }}</span>
                  </div>
                </div>
              </div>
              <div class="header-right">
                <el-button
                  type="text"
                  icon="el-icon-view"
                  @click="previewQuestion(question)"
                  size="small"
                >
                  é¢„览
                </el-button>
              </div>
            </div>
            <!-- å¼‚常处理配置 -->
            <div class="config-section">
              <div class="config-title">
                <i class="el-icon-setting"></i>
                <span>异常处理配置</span>
              </div>
              <el-form
                :model="question.exceptionConfig"
                :rules="configRules"
                ref="configForm"
                label-width="100px"
                size="small"
                class="config-form"
              >
                <div class="config-fields">
                  <!-- è´£ä»»ç§‘室 -->
                  <div class="config-field">
                    <el-form-item
                      label="责任科室"
                      prop="responsibilityDept"
                      class="config-item"
                    >
                      <el-select
                        v-model="question.exceptionConfig.responsibilityDept"
                        placeholder="请选择责任科室"
                        filterable
                        clearable
                        style="width: 100%"
                        @change="handleConfigChange(question)"
                      >
                        <el-option
                          v-for="dept in deptOptions"
                          :key="dept.id"
                          :label="dept.name"
                          :value="dept.id"
                        />
                      </el-select>
                      <div class="config-tip">负责处理该题目反馈的科室</div>
                    </el-form-item>
                  </div>
                  <!-- æŠ¥å¤‡ç§‘室 -->
                  <div class="config-field">
                    <el-form-item
                      label="报备科室"
                      prop="reportDept"
                      class="config-item"
                    >
                      <el-select
                        v-model="question.exceptionConfig.reportDept"
                        placeholder="请选择报备科室"
                        filterable
                        clearable
                        multiple
                        collapse-tags
                        style="width: 100%"
                        @change="handleConfigChange(question)"
                      >
                        <el-option
                          v-for="dept in deptOptions"
                          :key="dept.id"
                          :label="dept.name"
                          :value="dept.id"
                        />
                      </el-select>
                      <div class="config-tip">
                        éœ€è¦æŽ¥æ”¶å¼‚常反馈的科室,可多选
                      </div>
                    </el-form-item>
                  </div>
                  <!-- é€šçŸ¥æ–¹å¼ -->
                  <div class="config-field">
                    <el-form-item
                      label="通知方式"
                      prop="notifyTypes"
                      class="config-item"
                    >
                      <el-checkbox-group
                        v-model="question.exceptionConfig.notifyTypes"
                        @change="handleConfigChange(question)"
                      >
                        <el-checkbox label="system">系统消息</el-checkbox>
                        <el-checkbox label="sms">短信</el-checkbox>
                        <el-checkbox label="email">邮件</el-checkbox>
                        <el-checkbox label="wechat">企业微信</el-checkbox>
                      </el-checkbox-group>
                    </el-form-item>
                  </div>
                </div>
                <!-- é…ç½®çŠ¶æ€å’Œæ“ä½œæŒ‰é’® -->
                <div class="config-footer">
                  <div v-if="question.saveStatus" class="save-status">
                    <el-alert
                      :type="question.saveStatus.type"
                      :title="question.saveStatus.message"
                      :closable="false"
                      show-icon
                      :effect="
                        question.saveStatus.type === 'success'
                          ? 'dark'
                          : 'light'
                      "
                      size="small"
                    />
                  </div>
                  <div class="config-actions">
                    <el-button
                      type="primary"
                      :loading="question.saving"
                      :disabled="!question.hasChanges"
                      @click="saveSingleConfig(question)"
                      size="small"
                      icon="el-icon-check"
                    >
                      {{ question.saving ? "保存中..." : "保存配置" }}
                    </el-button>
                    <el-button
                      v-if="question.hasChanges"
                      type="text"
                      @click="resetSingleConfig(question)"
                      size="small"
                    >
                      é‡ç½®
                    </el-button>
                  </div>
                </div>
              </el-form>
            </div>
          </el-card>
        </div>
      </div>
      <!-- åˆ†é¡µ -->
      <div v-if="questionList.length > 0" class="pagination-wrapper">
        <pagination
          v-show="total > 0"
          :total="total"
          :page.sync="queryParams.pageNum"
          :limit.sync="queryParams.pageSize"
          @pagination="getQuestionList"
        />
      </div>
    </div>
    <!-- é¢˜ç›®é¢„览对话框 -->
    <el-dialog
      title="题目预览"
      :visible.sync="previewVisible"
      width="600px"
      center
    >
      <div v-if="currentPreview" class="preview-wrapper">
        <div class="preview-header">
          <h4>{{ currentPreview.scriptTopic || "无主题" }}</h4>
          <div class="preview-tags">
            <dict-tag
              :options="askvaluetype"
              :value="currentPreview.scriptType"
              size="small"
            />
            <el-tag
              :type="currentPreview.isavailable === 1 ? 'success' : 'danger'"
              size="small"
            >
              {{ currentPreview.isavailable === 1 ? "可用" : "不可用" }}
            </el-tag>
            <el-tag v-if="currentPreview.targetname" size="small" type="info">
              {{ currentPreview.targetname }}
            </el-tag>
          </div>
        </div>
        <div class="preview-content">
          <p class="preview-question">{{ currentPreview.scriptContent }}</p>
          <div
            v-if="
              currentPreview.scriptType != 3 && currentPreview.scriptType != 4
            "
            class="preview-options"
          >
            <el-radio-group v-model="previewAnswer">
              <el-radio
                v-for="(option, idx) in currentPreview.svyLibScriptOptions ||
                []"
                :key="idx"
                :label="option.optioncontent"
                class="option-item"
              >
                {{ option.optioncontent }}
              </el-radio>
            </el-radio-group>
          </div>
          <div v-else class="preview-textarea">
            <el-input
              type="textarea"
              placeholder="请输入回答"
              v-model="previewAnswer"
              :rows="4"
            />
          </div>
        </div>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="previewVisible = false">关闭</el-button>
      </span>
    </el-dialog>
    <!-- ä¿å­˜æˆåŠŸæç¤º -->
    <el-dialog
      title="保存成功"
      :visible.sync="saveSuccessVisible"
      width="400px"
      center
    >
      <div class="success-content">
        <i class="el-icon-success success-icon"></i>
        <p class="success-text">配置已成功保存!</p>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button type="primary" @click="saveSuccessVisible = false"
          >确定</el-button
        >
      </span>
    </el-dialog>
  </div>
</template>
<script>
import {
  getissuelist,
  compileissue,
  getissueclassify,
} from "@/api/AiCentre/index";
import store from "@/store";
import Pagination from "@/components/Pagination";
export default {
  name: "SatisfactionExceptionConfig",
  components: { Pagination },
  data() {
    return {
      // æŸ¥è¯¢å‚æ•°
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        scriptTopic: "",
        scriptContent: "",
        targetname: "",
        isavailable: "",
        categoryids: "404,405,406", // å›ºå®šæŸ¥è¯¢æ»¡æ„åº¦ç±»åž‹
      },
      // æ•°æ®åˆ—表
      questionList: [],
      total: 0,
      loading: false,
      batchSaving: false,
      // å­—典数据
      askvaluetype: store.getters.askvaluetype || [],
      qyoptions: store.getters.usable || [],
      mode: store.getters.mode || [],
      languagelist: store.getters.languagelist || [],
      // ç§‘室选项
      deptOptions: [],
      // é¢„览相关
      previewVisible: false,
      currentPreview: null,
      previewAnswer: "",
      // ä¿å­˜ç›¸å…³
      saveSuccessVisible: false,
      hasChanges: false,
      changedCount: 0,
      // è¡¨å•验证规则
      configRules: {
        responsibilityDept: [
          { required: true, message: "请选择责任科室", trigger: "change" },
        ],
        reportDept: [
          {
            required: true,
            message: "请选择至少一个报备科室",
            trigger: "change",
          },
          {
            validator: (rule, value, callback) => {
              if (!value || value.length === 0) {
                callback(new Error("请选择至少一个报备科室"));
              } else {
                callback();
              }
            },
            trigger: "change",
          },
        ],
        notifyTypes: [
          {
            validator: (rule, value, callback) => {
              if (!value || value.length === 0) {
                callback(new Error("请至少选择一种通知方式"));
              } else {
                callback();
              }
            },
            trigger: "change",
          },
        ],
      },
    };
  },
  created() {
    this.getDeptOptions();
    this.getQuestionList();
  },
  methods: {
    /** æŸ¥è¯¢ç§‘室列表 */
    getDeptOptions() {
      getissueclassify({})
        .then((res) => {
          if (res.code === 200) {
            this.deptOptions = res.rows || [];
          }
        })
        .catch((error) => {
          console.error("获取科室列表失败:", error);
          this.$message.error("获取科室列表失败");
        });
    },
    /** æŸ¥è¯¢æ»¡æ„åº¦é¢˜ç›®åˆ—表 */
    getQuestionList() {
      this.loading = true;
      this.questionList = [];
      getissuelist(this.queryParams)
        .then((res) => {
          this.loading = false;
          if (res.code === 200) {
            this.questionList = (res.rows || []).map((item) => {
              // è§£æžå¼‚常处理配置
              let exceptionConfig = {
                responsibilityDept: "",
                reportDept: [],
                notifyTypes: ["system"],
              };
              try {
                if (item.otherdata) {
                  const otherData = JSON.parse(item.otherdata);
                  if (otherData.exceptionConfig) {
                    exceptionConfig = {
                      ...exceptionConfig,
                      ...otherData.exceptionConfig,
                    };
                  }
                }
              } catch (error) {
                console.warn("解析异常配置失败:", error);
              }
              return {
                ...item,
                originalConfig: JSON.parse(JSON.stringify(exceptionConfig)),
                exceptionConfig: exceptionConfig,
                hasChanges: false,
                saving: false,
                saveStatus: null,
              };
            });
            this.total = res.total || 0;
            this.updateChangedStatus();
          } else {
            this.$message.error(res.msg || "获取数据失败");
          }
        })
        .catch((error) => {
          this.loading = false;
          console.error("查询失败:", error);
          this.$message.error("获取数据失败");
        });
    },
    /** é…ç½®å˜æ›´å¤„理 */
    handleConfigChange(question) {
      this.$nextTick(() => {
        const index = this.questionList.findIndex((q) => q.id === question.id);
        if (index !== -1) {
          const formRef = this.$refs.configForm && this.$refs.configForm[index];
          if (formRef) {
            formRef.validate((valid) => {
              if (valid) {
                question.hasChanges = !this.isConfigEqual(
                  question.exceptionConfig,
                  question.originalConfig
                );
                this.updateChangedStatus();
              }
            });
          }
        }
      });
    },
    /** æ¯”较配置是否改变 */
    isConfigEqual(config1, config2) {
      if (!config1 || !config2) return false;
      const report1 = [...(config1.reportDept || [])].sort().join(",");
      const report2 = [...(config2.reportDept || [])].sort().join(",");
      const notify1 = [...(config1.notifyTypes || [])].sort().join(",");
      const notify2 = [...(config2.notifyTypes || [])].sort().join(",");
      return (
        config1.responsibilityDept === config2.responsibilityDept &&
        report1 === report2 &&
        notify1 === notify2
      );
    },
    /** æ›´æ–°å˜æ›´çŠ¶æ€ */
    updateChangedStatus() {
      const changedItems = this.questionList.filter((q) => q.hasChanges);
      this.changedCount = changedItems.length;
      this.hasChanges = changedItems.length > 0;
    },
    /** ä¿å­˜å•个题目配置 */
    async saveSingleConfig(question) {
      if (!question.hasChanges) return;
      const index = this.questionList.findIndex((q) => q.id === question.id);
      if (index === -1) return;
      const formRef = this.$refs.configForm && this.$refs.configForm[index];
      if (!formRef) return;
      const valid = await formRef.validate();
      if (!valid) {
        this.$message.warning("请先完成必填项");
        return;
      }
      question.saving = true;
      question.saveStatus = null;
      try {
        // æž„建保存数据
        const saveData = {
          id: question.id,
          isoperation: 2, // ä¿®æ”¹æ“ä½œ
          ...question,
        };
        // å°†å¼‚常配置保存到 otherdata
        const otherData = JSON.parse(question.otherdata || "{}");
        otherData.exceptionConfig = question.exceptionConfig;
        saveData.otherdata = JSON.stringify(otherData);
        // ç§»é™¤ä¸éœ€è¦çš„字段
        delete saveData.originalConfig;
        delete saveData.hasChanges;
        delete saveData.saving;
        delete saveData.saveStatus;
        delete saveData.exceptionConfig;
        const response = await compileissue(saveData);
        if (response.code === 200) {
          // æ›´æ–°åŽŸå§‹é…ç½®
          question.originalConfig = JSON.parse(
            JSON.stringify(question.exceptionConfig)
          );
          question.hasChanges = false;
          question.saveStatus = {
            type: "success",
            message: "配置保存成功",
          };
          this.updateChangedStatus();
          this.$message.success("配置保存成功");
          // 5秒后清除成功提示
          setTimeout(() => {
            question.saveStatus = null;
          }, 5000);
        } else {
          question.saveStatus = {
            type: "error",
            message: response.msg || "保存失败",
          };
          this.$message.error(response.msg || "保存失败");
        }
      } catch (error) {
        console.error("保存失败:", error);
        question.saveStatus = {
          type: "error",
          message: "保存失败,请稍后重试",
        };
        this.$message.error("保存失败,请稍后重试");
      } finally {
        question.saving = false;
      }
    },
    /** é‡ç½®å•个题目配置 */
    resetSingleConfig(question) {
      this.$confirm("确定要重置当前题目的配置吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          question.exceptionConfig = JSON.parse(
            JSON.stringify(question.originalConfig)
          );
          question.hasChanges = false;
          question.saveStatus = null;
          this.updateChangedStatus();
          this.$message.success("配置已重置");
        })
        .catch(() => {});
    },
    /** æ‰¹é‡ä¿å­˜é…ç½® */
    async handleBatchSave() {
      if (!this.hasChanges || this.batchSaving) return;
      this.$confirm("确定要保存所有修改过的配置吗?", "批量保存", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(async () => {
          this.batchSaving = true;
          const changedQuestions = this.questionList.filter(
            (q) => q.hasChanges
          );
          const results = [];
          for (const question of changedQuestions) {
            try {
              await this.saveSingleConfig(question);
              results.push({
                id: question.id,
                success:
                  !question.hasChanges &&
                  question.saveStatus?.type === "success",
              });
            } catch (error) {
              results.push({
                id: question.id,
                success: false,
              });
            }
          }
          this.batchSaving = false;
          const successCount = results.filter((r) => r.success).length;
          const failCount = results.length - successCount;
          if (failCount === 0) {
            this.saveSuccessVisible = true;
            this.$message.success(`成功保存 ${successCount} ä¸ªé…ç½®`);
          } else {
            this.$message.warning(
              `成功保存 ${successCount} ä¸ªï¼Œå¤±è´¥ ${failCount} ä¸ª`
            );
          }
        })
        .catch(() => {
          this.batchSaving = false;
        });
    },
    /** é¢„览题目 */
    previewQuestion(question) {
      this.currentPreview = { ...question };
      this.previewAnswer = "";
      this.previewVisible = true;
    },
    /** æœç´¢ */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getQuestionList();
    },
    /** é‡ç½®æœç´¢ */
    resetQuery() {
      this.queryParams = {
        pageNum: 1,
        pageSize: 10,
        scriptTopic: "",
        scriptContent: "",
        targetname: "",
        isavailable: "",
        categoryids: "404,405,406",
      };
      this.getQuestionList();
    },
  },
};
</script>
<style lang="scss" scoped>
.satisfaction-exception-config {
  min-height: 100%;
  background-color: #f5f7fa;
  padding: 20px;
  .page-header {
    margin-bottom: 20px;
    padding: 20px;
    background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
    border-radius: 8px;
    color: white;
    .header-content {
      .page-title {
        margin: 0 0 8px 0;
        font-size: 20px;
        font-weight: 600;
      }
      .page-description {
        margin: 0;
        opacity: 0.9;
        font-size: 14px;
      }
    }
  }
  .search-card {
    margin-bottom: 20px;
    .search-container {
      border-radius: 8px;
      .el-form {
        display: flex;
        flex-wrap: wrap;
        gap: 16px;
        align-items: center;
      }
    }
  }
  .config-content {
    .batch-actions-card {
      margin-bottom: 20px;
      .batch-actions {
        display: flex;
        align-items: center;
        gap: 20px;
        padding: 8px 0;
        .change-count {
          color: #e6a23c;
          font-size: 14px;
          font-weight: 500;
        }
        .total-count {
          margin-left: auto;
          color: #909399;
          font-size: 14px;
        }
      }
    }
    .loading-wrapper {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 400px;
      .loading-spinner {
        text-align: center;
        color: #409eff;
        i {
          font-size: 24px;
          margin-right: 8px;
        }
        span {
          font-size: 16px;
        }
      }
    }
    .empty-wrapper {
      min-height: 400px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .question-list {
      display: flex;
      flex-direction: column;
      gap: 16px;
    }
    .question-item {
      .question-card {
        border-radius: 8px;
        border: 1px solid #ebeef5;
        transition: all 0.3s ease;
        &.has-changes {
          border-color: #409eff;
          box-shadow: 0 2px 12px 0 rgba(64, 158, 255, 0.1);
        }
        &:hover {
          box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
        }
        .card-header {
          display: flex;
          justify-content: space-between;
          align-items: flex-start;
          margin-bottom: 20px;
          padding-bottom: 20px;
          border-bottom: 1px solid #f0f0f0;
          .header-left {
            display: flex;
            gap: 20px;
            flex: 1;
            .question-index {
              display: flex;
              flex-direction: column;
              align-items: center;
              min-width: 40px;
              .index-number {
                display: flex;
                align-items: center;
                justify-content: center;
                width: 32px;
                height: 32px;
                background: #409eff;
                color: white;
                border-radius: 50%;
                font-size: 14px;
                font-weight: 600;
                margin-bottom: 8px;
              }
              .index-line {
                width: 2px;
                height: 100%;
                background: #e0e0e0;
                border-radius: 1px;
              }
            }
            .question-basic-info {
              flex: 1;
              .question-title-section {
                margin-bottom: 12px;
                .question-topic {
                  margin: 0 0 8px 0;
                  font-size: 16px;
                  font-weight: 600;
                  color: #303133;
                  line-height: 1.4;
                }
                .question-tags {
                  display: flex;
                  gap: 8px;
                  flex-wrap: wrap;
                }
              }
              .question-content-section {
                display: flex;
                align-items: flex-start;
                gap: 8px;
                .content-label {
                  color: #606266;
                  font-size: 13px;
                  font-weight: 500;
                  min-width: 80px;
                }
                .content-text {
                  color: #303133;
                  font-size: 13px;
                  line-height: 1.6;
                  flex: 1;
                }
              }
            }
          }
          .header-right {
            flex-shrink: 0;
          }
        }
        .config-section {
          .config-title {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 20px;
            padding: 8px 12px;
            background: #f8f9fa;
            border-radius: 4px;
            i {
              color: #409eff;
              font-size: 16px;
            }
            span {
              color: #303133;
              font-weight: 600;
              font-size: 14px;
            }
          }
          .config-form {
            .config-fields {
              display: grid;
              grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
              gap: 20px;
              margin-bottom: 20px;
              .config-field {
                .config-item {
                  margin-bottom: 0;
                  :deep(.el-form-item__label) {
                    font-weight: 500;
                    color: #606266;
                    padding-right: 12px;
                  }
                  .config-tip {
                    font-size: 12px;
                    color: #909399;
                    margin-top: 4px;
                    line-height: 1.4;
                  }
                }
              }
            }
            .config-footer {
              display: flex;
              justify-content: space-between;
              align-items: center;
              padding-top: 20px;
              border-top: 1px dashed #dcdfe6;
              .save-status {
                flex: 1;
                margin-right: 20px;
                .el-alert {
                  padding: 8px 16px;
                  border-radius: 4px;
                }
              }
              .config-actions {
                display: flex;
                align-items: center;
                gap: 12px;
                flex-shrink: 0;
                .el-button {
                  min-width: 100px;
                }
              }
            }
          }
        }
      }
    }
    .pagination-wrapper {
      margin-top: 20px;
      padding: 20px;
      background: white;
      border-radius: 8px;
      display: flex;
      justify-content: center;
    }
  }
  .preview-wrapper {
    .preview-header {
      margin-bottom: 20px;
      h4 {
        margin: 0 0 12px 0;
        color: #303133;
        font-size: 18px;
        font-weight: 600;
      }
      .preview-tags {
        display: flex;
        gap: 8px;
        flex-wrap: wrap;
      }
    }
    .preview-content {
      .preview-question {
        margin-bottom: 20px;
        padding: 16px;
        background: #f8f9fa;
        border-radius: 4px;
        color: #606266;
        line-height: 1.6;
      }
      .preview-options {
        .option-item {
          display: block;
          margin-bottom: 12px;
          padding: 12px;
          border-radius: 4px;
          border: 1px solid #ebeef5;
          transition: all 0.3s;
          &:hover {
            background: #f5f7fa;
            border-color: #409eff;
          }
          &:last-child {
            margin-bottom: 0;
          }
        }
      }
      .preview-textarea {
        .el-textarea__inner {
          resize: none;
        }
      }
    }
  }
  .success-content {
    text-align: center;
    padding: 20px 0;
    .success-icon {
      color: #67c23a;
      font-size: 48px;
      margin-bottom: 20px;
    }
    .success-text {
      font-size: 16px;
      color: #606266;
      margin: 0;
    }
  }
}
@media (max-width: 768px) {
  .satisfaction-exception-config {
    padding: 12px;
    .page-header {
      padding: 16px;
      margin-bottom: 16px;
    }
    .search-card {
      margin-bottom: 16px;
    }
    .config-content {
      .batch-actions-card {
        margin-bottom: 16px;
      }
      .question-item {
        .question-card {
          .card-header {
            flex-direction: column;
            gap: 12px;
            .header-left {
              flex-direction: column;
              gap: 12px;
              .question-index {
                flex-direction: row;
                align-items: center;
                min-width: auto;
                .index-number {
                  margin-bottom: 0;
                  margin-right: 12px;
                }
                .index-line {
                  width: 100%;
                  height: 2px;
                }
              }
            }
          }
          .config-section {
            .config-form {
              .config-fields {
                grid-template-columns: 1fr;
                gap: 16px;
              }
              .config-footer {
                flex-direction: column;
                align-items: stretch;
                gap: 12px;
                .save-status {
                  margin-right: 0;
                  margin-bottom: 8px;
                }
              }
            }
          }
        }
      }
    }
  }
}
</style>
<style lang="scss">
.config-form {
  .el-checkbox-group {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
  }
  .el-checkbox {
    margin: 0;
  }
}
.option-item {
  .el-radio__label {
    display: block;
    padding-left: 8px;
  }
}
</style>
src/views/Satisfaction/particulars/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,889 @@
<template>
  <div class="Questionnairemanagement">
    <!-- ä¸Šä¾§æ  -->
    <div class="sidecolumn">
      <div>
        <el-steps simple :active="Editprogress">
          <el-step
            icon="el-icon-edit"
            title="基础信息"
            description="选择宣教模板、形式等基础信息"
          ></el-step>
          <el-step
            icon="el-icon-user"
            title="宣教对象"
            description="在本部选择宣教病人"
          ></el-step>
        </el-steps>
      </div>
    </div>
    <!-- ä¸‹ä¾§æ•°æ® -->
    <div class="leftvlue" style="margin: 0 20px">
      <!-- åŸºæœ¬ä¿¡æ¯ -->
      <div v-if="Editprogress == 1">
        <el-alert
          title="选择宣教模板、形式等基础信息"
          type="success"
          effect="dark"
        >
        </el-alert>
        <div class="leftvlue-jbxx">
          <!-- åŸºç¡€ä¿¡æ¯ -->
          <div class="examine-jic">
            <div class="headline">
              <div>基础信息</div>
            </div>
            <div class="jic-value">
              <el-form ref="form" :model="form" label-width="105px">
                <el-form-item label="发送时间:" v-if="currenttype != 2">
                  <el-date-picker
                    v-model="form.name"
                    type="date"
                    placeholder="选择日期"
                  >
                  </el-date-picker>
                </el-form-item>
                <el-form-item label="发送时间段:" v-if="currenttype != 2">
                  <el-checkbox-group v-model="checkList">
                    <el-checkbox label="上午(8:30-11:30)"></el-checkbox>
                    <el-checkbox label="下午(14:30-16:30)"></el-checkbox>
                    <el-checkbox label="夜间(18:30-20:30)"></el-checkbox>
                  </el-checkbox-group>
                </el-form-item>
                <el-form-item label="服务形式">
                  <el-checkbox-group v-model="checkList">
                    <el-checkbox
                      v-for="(item, index) in checkboxlist"
                      :key="index"
                      :label="item"
                    ></el-checkbox>
                  </el-checkbox-group>
                </el-form-item>
                <el-form-item label="组织形式">
                  <el-radio-group v-model="form.radio">
                    <el-radio :label="3">单人</el-radio>
                    <el-radio :label="6">多人</el-radio>
                  </el-radio-group>
                </el-form-item>
                <el-form-item label="语音模板" prop="region">
                  <el-select v-model="form.region" placeholder="请选择模板">
                    <el-option label="一号模板" value="shanghai"></el-option>
                    <el-option label="二号模板" value="beijing"></el-option>
                  </el-select>
                </el-form-item>
              </el-form>
            </div>
          </div>
          <div class="examine-jic">
            <div class="headline">
              <div>{{ title }}</div>
            </div>
            <div class="examine-jic">
              <div class="jic-value">
                <el-row :gutter="20">
                  <!--用户数据-->
                  <el-form
                    :model="topqueryParams"
                    ref="queryForm"
                    size="small"
                    :inline="true"
                    v-show="showSearch"
                    label-width="98px"
                  >
                    <el-form-item label="执行状态" prop="status">
                      <el-select
                        v-model="topqueryParams.topic"
                        placeholder="请选择"
                      >
                        <el-option
                          v-for="item in taskoptions"
                          :key="item.value"
                          :label="item.label"
                          :value="item.value"
                        >
                        </el-option>
                      </el-select>
                    </el-form-item>
                    <el-form-item
                      label="科室名称"
                      v-if="currenttype == 1 || currenttype == 3"
                    >
                      <el-input
                        v-model="topqueryParams.name"
                      ></el-input> </el-form-item
                    ><el-form-item label="病区名称" v-if="currenttype == 2">
                      <el-input v-model="topqueryParams.name"></el-input>
                    </el-form-item>
                    <el-form-item
                      label="患者姓名"
                      v-if="currenttype == 1 || currenttype == 2"
                    >
                      <el-input v-model="topqueryParams.name"></el-input>
                    </el-form-item>
                    <el-form-item
                      label="主治医生"
                      v-if="currenttype == 1 || currenttype == 2"
                    >
                      <el-input v-model="topqueryParams.name"></el-input>
                    </el-form-item>
                    <el-form-item
                      label="管床护士"
                      v-if="currenttype == 1 || currenttype == 2"
                    >
                      <el-input v-model="topqueryParams.name"></el-input>
                    </el-form-item>
                    <el-form-item>
                      <el-button
                        type="primary"
                        icon="el-icon-search"
                        size="medium"
                        @click="handleQuery"
                        >搜索</el-button
                      >
                      <el-button
                        icon="el-icon-refresh"
                        size="medium"
                        @click="resetQuery"
                        >重置</el-button
                      >
                    </el-form-item>
                  </el-form>
                  <el-divider></el-divider>
                  <!-- é€‰æ‹©ä»»åŠ¡åˆ—è¡¨ -->
                  <SFtable
                    @handleUpdate="handleUpdate"
                    @handleSelectionChange="handleSelectionChange"
                    :currentList="userList"
                    :tableLabel="tableLabelxj"
                    :controlsc="false"
                    :multiplechoice="false"
                  />
                  <pagination
                    v-show="total > 0"
                    :total="total"
                    :page.sync="topqueryParams.pageNum"
                    :limit.sync="topqueryParams.pageSize"
                    @pagination="getList"
                  />
                </el-row>
              </div>
            </div>
          </div>
        </div>
        <el-button type="success" @click="submitForm('ruleForm')">{{
          quote ? "立即创建" : "任务详情设置"
        }}</el-button>
        <el-button @click="resetForm('ruleForm')">重置</el-button>
      </div>
      <!-- ä»»åŠ¡è¯¦æƒ… -->
      <div v-if="Editprogress == 2">
        <el-alert title="在本阶段选择病人" type="success" effect="dark">
        </el-alert>
        <div class="leftvlue-jbxx">
          <div class="examine-jic">
            <div class="headline">
              <div>患者列表</div>
            </div>
            <div class="examine-jic">
              <div class="jic-value">
                <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.name"></el-input>
                    </el-form-item>
                    <el-form-item label="患者范围" prop="status">
                      <el-select
                        v-model="topqueryParams.searchscope"
                        placeholder="请选择"
                      >
                        <el-option
                          v-for="item in source"
                          :key="item.value"
                          :label="item.label"
                          :value="item.value"
                        >
                        </el-option>
                      </el-select>
                    </el-form-item>
                    <el-form-item label="患者状态" prop="status">
                      <el-select
                        v-model="topqueryParams.topic"
                        placeholder="请选择"
                      >
                        <el-option
                          v-for="item in topicoptions"
                          :key="item.value"
                          :label="item.label"
                          :value="item.value"
                        >
                        </el-option>
                      </el-select>
                    </el-form-item>
                    <el-form-item label="随访结果" prop="status">
                      <el-select
                        v-model="topqueryParams.topic"
                        placeholder="请选择"
                      >
                        <el-option
                          v-for="item in topicoptions"
                          :key="item.value"
                          :label="item.label"
                          :value="item.value"
                        >
                        </el-option>
                      </el-select>
                    </el-form-item>
                    <el-form-item label="患者电话">
                      <el-input v-model="topqueryParams.name"></el-input>
                    </el-form-item>
                    <el-form-item>
                      <el-button
                        type="primary"
                        icon="el-icon-search"
                        size="medium"
                        @click="handleQuery"
                        >搜索</el-button
                      >
                      <el-button
                        icon="el-icon-refresh"
                        size="medium"
                        @click="resetQuery"
                        >重置</el-button
                      >
                      <el-button
                        icon="el-icon-upload2"
                        size="medium"
                        type="warning"
                        >当前患者一键发送</el-button
                      >
                    </el-form-item>
                  </el-form>
                  <el-divider></el-divider>
                  <el-row :gutter="10" class="mb8">
                    <el-col :span="1.5">
                      <el-select
                        v-model="tasktopic"
                        placeholder="请选择新增类型"
                      >
                        <el-option
                          v-for="item in taskoptions"
                          :key="item.value"
                          :label="item.label"
                          :value="item.value"
                        >
                        </el-option>
                      </el-select>
                    </el-col>
                    <el-col :span="1.5">
                      <el-button
                        type="primary"
                        plain
                        icon="el-icon-plus"
                        size="medium"
                        :disabled="!tasktopic"
                        @click="handleAddpatient"
                        >新增</el-button
                      >
                    </el-col>
                    <el-col :span="1.5">
                      <el-button
                        type="danger"
                        plain
                        icon="el-icon-delete"
                        size="medium"
                        :disabled="multiple"
                        @click="handleDelete"
                        >删除</el-button
                      >
                    </el-col>
                    <!-- <el-col :span="1.5"> </el-col> -->
                  </el-row>
                  <!-- é€‰ä¸­æ‚£è€…列表 -->
                  <SFtable
                    @handleUpdate="handleUpdate"
                    @handleSelectionChange="handleSelectionChange"
                    :currentList="sonuserList"
                    :tableLabel="tableLabelhz"
                    :controlxz="false"
                  />
                  <pagination
                    v-show="total > 0"
                    :total="total"
                    :page.sync="topqueryParams.pageNum"
                    :limit.sync="topqueryParams.pageSize"
                    @pagination="getList"
                  />
                </el-row>
              </div>
            </div>
          </div>
        </div>
        <el-button type="primary" @click="laststep()">上一步</el-button>
        <el-button type="success" @click="submitForm('ruleForm')"
          >立即创建</el-button
        >
        <el-button @click="resetForm('ruleForm')">重置</el-button>
      </div>
    </div>
    <!-- æ·»åŠ æ‚£è€… -->
    <el-dialog
      title="选择患者"
      :visible.sync="dialogVisiblepatient"
      width="70%"
      :before-close="handleClosehz"
    >
      <div class="examine-jic">
        <div class="jic-value">
          <el-row :gutter="20">
            <!--用户数据-->
            <el-form
              :model="patientqueryParams"
              ref="queryForm"
              size="small"
              :inline="true"
              v-show="showSearch"
              label-width="98px"
            >
              <el-form-item label="患者名称:">
                <el-input v-model="patientqueryParams.name"></el-input>
              </el-form-item>
              <el-form-item label="患者范围" prop="status">
                <el-select
                  v-model="patientqueryParams.topic"
                  placeholder="请选择"
                >
                  <el-option
                    v-for="item in topicoptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  >
                  </el-option>
                </el-select>
              </el-form-item>
              <el-form-item label="出院科室" prop="status">
                <el-select
                  v-model="patientqueryParams.topic"
                  placeholder="请选择"
                >
                  <el-option
                    v-for="item in topicoptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  >
                  </el-option>
                </el-select>
              </el-form-item>
              <el-form-item label="出院病区" prop="status">
                <el-select
                  v-model="patientqueryParams.topic"
                  placeholder="请选择"
                >
                  <el-option
                    v-for="item in topicoptions"
                    :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"
                  >搜索</el-button
                >
                <el-button
                  icon="el-icon-refresh"
                  size="medium"
                  @click="resetQuery"
                  >取消创建</el-button
                >
              </el-form-item>
            </el-form>
            <!-- é€‰æ‹©æ‚£è€…列表 -->
            <SFtable
              @handleUpdate="handleUpdate"
              @handleSelectionChange="handleSelectionChange"
              :currentList="patientuserList"
              :tableLabel="tableLabelhz"
              :controlsc="false"
            />
          </el-row>
          <pagination
            v-show="patienttotal > 0"
            :total="patienttotal"
            :page.sync="patientqueryParams.pageNum"
            :limit.sync="patientqueryParams.pageSize"
            @pagination="handleAddpatient"
          />
        </div>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisiblepatient = false">取 æ¶ˆ</el-button>
        <el-button type="primary" @click="AddDispatchpatients"
          >确定添加</el-button
        >
      </span>
    </el-dialog>
  </div>
</template>
<script>
import { messagelistpatient } from "@/api/patient/homepage";
import SFtable from "@/components/SFtable"; //表格组件
export default {
  name: "ServiceDetails",
  data() {
    return {
      title: "宣教内容列表",
      currenttype: 1, //1宣教2门诊3出院4复诊5体检6问卷
      Editprogress: 1, //编辑进度
      loading: false, // é®ç½©å±‚
      patientloading: false, // é®ç½©å±‚
      dialogVisiblepatient: false, //添加患者弹框
      checkboxlist: [],
      tableLabel: [],
      // æ‚£è€…表单
      tableLabelhz: [
        { label: "患者名称", width: "", prop: "name" },
        { label: "性别", width: "", prop: "sex" },
        { label: "年龄", width: "", prop: "age" },
        { label: "就诊科室", width: "", prop: "impTemplate" },
        { label: "入院日期", width: "", prop: "create_time" },
        { label: "创建人", width: "", prop: "update_by" },
      ],
      tableLabelxj: [
        { label: "门诊编号", width: "", prop: "name" },
        { label: "姓名", width: "", prop: "name" },
        { label: "年龄", width: "", prop: "age" },
        { label: "联系电话", width: "", prop: "telcode" },
        { label: "就诊科室", width: "", prop: "impTemplate" },
        { label: "诊断", width: "", prop: "name" },
        { label: "出院时间", width: "", prop: "name" },
        { label: "发起时间", width: "", prop: "create_time" },
        { label: "状态", width: "", prop: "sex" },
        { label: "重复次数", width: "", prop: "update_by" },
        { label: "任务来源", width: "", prop: "update_by" },
        { label: "创建人", width: "", prop: "update_by" },
      ],
      tableLabelmz: [
        { label: "门诊编号", width: "", prop: "name" },
        { label: "姓名", width: "", prop: "name" },
        { label: "年龄", width: "", prop: "age" },
        { label: "联系电话", width: "", prop: "telcode" },
        { label: "就诊科室", width: "", prop: "impTemplate" },
        { label: "诊断", width: "", prop: "name" },
        { label: "出院时间", width: "", prop: "name" },
        { label: "发起时间", width: "", prop: "create_time" },
        { label: "状态", width: "", prop: "sex" },
        { label: "重复次数", width: "", prop: "update_by" },
        { label: "任务来源", width: "", prop: "update_by" },
        { label: "创建人", width: "", prop: "update_by" },
      ],
      tableLabelcy: [
        { label: "员工编号", width: "", prop: "name" },
        { label: "姓名", width: "", prop: "name" },
        { label: "年龄", width: "", prop: "age" },
        { label: "联系电话", width: "", prop: "telcode" },
        { label: "所在科室", width: "", prop: "impTemplate" },
        { label: "完成时间", width: "", prop: "finishtime" },
        { label: "状态", width: "", prop: "sex" },
        { label: "重复次数", width: "", prop: "update_by" },
        { label: "任务来源", width: "", prop: "update_by" },
        { label: "创建人", width: "", prop: "update_by" },
      ],
      topqueryParams: {
        pageNum: 1, //
        pageSize: 10,
        searchscope:2,
      },
      checkList: [],
      deliverytopqueryParams: {
        pageNum: 1, //
        pageSize: 10,
      },
      patientqueryParams: {
        pageNum: 1, //
        pageSize: 10,
      },
      topicoptions: [],
      showSearch: true, //
      total: 0, //
      sontotal: 0, //
      patienttotal: 0, //
      // é€‰ä¸­æ•°ç»„
      ids: [],
      // éžå•个禁用
      single: true,
      // éžå¤šä¸ªç¦ç”¨
      multiple: true,
      // ç”¨æˆ·è¡¨æ ¼æ•°æ®
      userList: [], //模板列表
      patientuserList: [], //选择患者列表
      sonuserList: [], //选中患者列表
      tasktopic: null, //新增类型
      form: {
        name: "",
        region: "",
        date1: "",
        date2: "",
        delivery: false,
        type: [],
        resource: "",
        desc: "",
      },
      source: [
        {
          value: 0,
          label: "所属患者",
        },
        {
          value: 1,
          label: "科室患者",
        },
        {
          value: 2,
          label: "病区患者",
        },
      ],
      options: [
        {
          value: "选项1",
          label: "黄金糕",
        },
        {
          value: "选项2",
          label: "双皮奶",
        },
        {
          value: "选项5",
          label: "北京烤鸭",
        },
      ],
      taskoptions: [
        {
          value: "1",
          label: "通知",
        },
        {
          value: "2",
          label: "随访",
        },
        {
          value: "3",
          label: "问卷",
        },
        {
          value: "4",
          label: "宣教",
        },
      ],
      quote: false,
    };
  },
  components: { SFtable },
  created() {
    this.Addsubtask();
    this.Getsubtask();
    this.Acquisitiontype();
  },
  methods: {
    // èŽ·å–å½“å‰ç±»åž‹
    Acquisitiontype() {
      this.currenttype = this.$route.query.type;
      console.log(this.currenttype);
      if (this.currenttype == 1) {
        this.title = "门诊病人任务";
        this.tableLabel = this.tableLabelxj;
        this.checkboxlist = [
          "当面",
          "多媒体",
          "纸质",
          "电话",
          "短信",
          "微信公众号",
          "微信小程序",
          "钉钉",
        ];
      } else if (this.currenttype == 2) {
        this.title = "出院病人任务";
        this.tableLabel = this.tableLabelmz;
        this.checkboxlist = ["当面", "纸质", "电话", "短信", "微信公众号"];
      } else if (this.currenttype == 3) {
        this.title = "医务人员任务";
        this.tableLabel = this.tableLabelcy;
        this.checkboxlist = ["当面", "纸质", "电话", "短信", "微信公众号"];
      }
    },
    // ä¸‹ä¸€æ­¥
    submitForm(formName) {
      if (this.Editprogress <= 3) {
        return this.Editprogress++;
      }
      // æäº¤
      // this.$refs[formName].validate((valid, object) => {
      //   if (valid) {
      //     alert("submit!");
      //   } else {
      //     console.log("error submit!!", object);
      //     return false;
      //   }
      // });
    },
    // å­ä»»åŠ¡äºŒçº§å¼¹æ¡†
    handleAddpatient(row) {
      console.log(row, "子组件数据");
      messagelistpatient(this.patientqueryParams).then((response) => {
        console.log(response);
        this.patientuserList = response.rows;
        this.patienttotal = response.total;
        this.loading = false;
      });
      this.dialogVisiblepatient = true;
    },
    handleUpdate() {},
    handleDelete() {},
    handleExport() {},
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      this.ids = null;
      this.ids = selection.map((item) => item.patid).join(",");
      // let result = this.ids.join(",");
      this.multiple = !selection.length;
      console.log(this.ids);
    },
    getList() {},
    handleQuery() {},
    resetQuery() {},
    handleClosehz() {
      this.dialogVisiblepatient = false;
    },
    // ä¸Šä¸€æ­¥
    laststep() {
      this.Editprogress--;
    },
    // æäº¤è¡¨å•
    resetForm(formName) {
      this.$refs[formName].resetFields();
    },
    // é¢„览模板
    PreviewTemplate() {},
    Acknowledgereference() {
      this.quote = true;
    },
    // æ–°å¢žå­ä»»åŠ¡
    Addsubtask() {
      this.topqueryParams.pguid = 2;
      // addsvr_prjtask(this.topqueryParams).then((res) => {
      //   console.log(res);
      // });
    },
    // æ–°å¢žæ´¾é€æ‚£è€…
    AddDispatchpatients() {
      let objictpint = {};
      objictpint.patientes = this.ids;
      objictpint.pguid = 2;
      // Addpatienttask(objictpint).then((res) => {
      //   console.log(res);
      // });
      this.dialogVisiblepatient = false;
    },
    // æŸ¥è¯¢å­ä»»åŠ¡åˆ—è¡¨
    Getsubtask() {
      this.topqueryParams.pguid = 2;
      console.log(this.topqueryParams);
      messagelistpatient(this.topqueryParams).then((res) => {
        this.userList = res.rows;
        this.total = res.total;
        console.log(this.userList);
      });
    },
    /** æŸ¥è¯¢æ‚£è€…列表 */
  },
};
</script>
<style lang="scss" scoped>
.Questionnairemanagement {
}
.leftvlue-jbxx {
  margin-top: 10px;
}
.sidecolumn {
  width: 100%;
  // min-height: 12vh;
  margin: 20px;
  margin-bottom: 0;
  padding: 20px;
  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);
}
.leftvlue {
  //   display: flex;
  //   flex: 1;
  width: 100%;
  margin-top: 20px;
  //   margin: 20px;
  padding: 30px;
  background: #ffff;
  border: 1px solid #dcdfe6;
  -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12),
    0 0 6px 0 rgba(0, 0, 0, 0.04);
}
.examine-jic {
  .headline {
    font-size: 24px;
    border-left: 5px solid #41a1be;
    padding-left: 5px;
    margin-bottom: 10px;
    display: flex;
    justify-content: space-between;
    .Add-details {
      font-size: 18px;
      color: #02a7f0;
      cursor: pointer;
    }
  }
  .jic-value {
    font-size: 20px;
    border-top: 1px solid #a7abac;
    padding: 10px;
    margin-bottom: 10px;
    .details-jic {
      padding: 10px 15px;
      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);
      .details-title {
        display: flex;
        justify-content: space-between;
        margin-bottom: 10px;
        div:nth-child(2) {
          color: #02a7f0;
          cursor: pointer;
        }
      }
      .details-renw {
        background: #e4ebfc;
        padding: 15px 5px;
        border-radius: 5px;
        margin-bottom: 20px;
      }
    }
  }
}
// .leftvlue-jbxx {
//   margin-bottom: 50px;
//   font-size: 20px;
//   span {
//     position: absolute;
//     right: 80px;
//   }
//   .demo-cascader {
//     margin-right: 20px;
//   }
//   .PreviewTemplate {
//     color: #02a7f0;
//     cursor: pointer;
//     font-size: 20px;
//     margin: 0 20px;
//   }
// }
.jic-value {
  font-size: 20px;
  border-top: 1px solid #a7abac;
  padding: 10px;
  margin-bottom: 10px;
  .details-jic {
    padding: 10px 15px;
    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);
    .details-title {
      display: flex;
      justify-content: space-between;
      margin-bottom: 10px;
      div:nth-child(2) {
        color: #02a7f0;
        cursor: pointer;
      }
    }
    .details-renw {
      background: #e4ebfc;
      padding: 15px 5px;
      border-radius: 5px;
      margin-bottom: 20px;
    }
  }
}
::v-deep .addtopic-input {
  input {
    background: #02a7f0;
    color: #edf1f7;
    width: 150px;
  }
}
::v-deep.el-step.is-vertical .el-step__title {
  font-size: 25px;
}
::v-deep.el-row {
  margin-bottom: 10px;
}
// ::v-deep.el-input--medium {
//   font-size: 24px !important;
// }
::v-deep.ruleFormaa.el-select {
  display: inline-block;
  position: relative;
  width: 700px;
}
.el-select__tags {
  font-size: 20px;
  max-width: 888px !important;
}
::v-deep.el-radio__inner {
  width: 22px;
  height: 22px;
}
// ::v-deep.topic-dev.el-radio__label {
//   font-size: 24px;
// }
::v-deep.el-radio-group {
  span {
    font-size: 24px;
  }
}
::v-deep.el-checkbox-group {
  span {
    font-size: 24px;
  }
}
</style>
src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,809 @@
<template>
  <div class="followup-statistics">
    <div class="query-section">
      <el-form
        :model="queryParams"
        ref="queryForm"
        size="medium"
        :inline="true"
        label-width="100px"
        class="query-form"
      >
        <el-form-item label="统计类型" prop="statisticaltype">
          <el-select
            v-model="queryParams.statisticaltype"
            placeholder="请选择统计类型"
            clearable
            @change="handleStatisticalTypeChange"
          >
            <el-option
              v-for="item in Statisticallist"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <!-- ç—…区选择 -->
        <el-form-item
          v-if="queryParams.statisticaltype == 1"
          label="病区"
          prop="leavehospitaldistrictcodes"
        >
          <el-select
            v-model="queryParams.leavehospitaldistrictcodes"
            placeholder="请选择病区"
            multiple
            collapse-tags
            filterable
            clearable
            style="width: 300px"
          >
            <el-option
              v-for="item in flatArrayhospit"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <!-- ç§‘室选择 -->
        <el-form-item
          v-if="queryParams.statisticaltype == 2"
          label="科室"
          prop="deptcodes"
        >
          <el-select
            v-model="queryParams.deptcodes"
            placeholder="请选择科室"
            multiple
            collapse-tags
            filterable
            clearable
            style="width: 300px"
          >
            <el-option
              v-for="item in flatArraydept"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="服务类型" prop="serviceType">
          <el-select
            v-model="queryParams.serviceType"
            placeholder="请选择服务类型"
            multiple
            collapse-tags
            clearable
            style="width: 300px"
          >
            <el-option
              v-for="item in options"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="随访时间" prop="dateRange">
          <el-date-picker
            v-model="queryParams.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            value-format="yyyy-MM-dd"
            :picker-options="pickerOptions"
            style="width: 380px"
          />
        </el-form-item>
        <el-form-item>
          <el-button
            type="primary"
            icon="el-icon-search"
            @click="handleQuery"
            :loading="loading"
          >
            æœç´¢
          </el-button>
          <el-button icon="el-icon-refresh" @click="resetQuery">
            é‡ç½®
          </el-button>
          <el-button
            type="warning"
            icon="el-icon-download"
            @click="handleExport"
            :disabled="!userList.length"
          >
            å¯¼å‡º
          </el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="table-section">
      <el-table
        v-loading="loading"
        :data="userList"
        :border="true"
        style="width: 100%"
        @selection-change="handleSelectionChange"
        :row-key="getRowKey"
      >
        <!-- ç—…区列 -->
        <el-table-column
          v-if="queryParams.statisticaltype == 1"
          label="出院病区"
          align="center"
          key="leavehospitaldistrictname"
          prop="leavehospitaldistrictname"
          :show-overflow-tooltip="true"
          min-width="120"
        />
        <!-- ç§‘室列 -->
        <el-table-column
          v-if="queryParams.statisticaltype == 2"
          label="科室"
          align="center"
          key="deptname"
          prop="deptname"
          :show-overflow-tooltip="true"
          min-width="120"
        />
        <el-table-column
          label="出院人次"
          align="center"
          key="dischargeCount"
          prop="dischargeCount"
          min-width="100"
        />
        <el-table-column
          label="无需随访人次"
          align="center"
          key="nonFollowUp"
          prop="nonFollowUp"
          min-width="120"
        />
        <el-table-column
          label="应随访人次"
          align="center"
          key="followUpNeeded"
          prop="followUpNeeded"
          min-width="120"
        />
        <el-table-column
          label="随访率"
          align="center"
          key="followUpRate"
          prop="followUpRate"
          min-width="100"
        >
          <template slot-scope="scope">
            <span v-if="scope.row.followUpRate !== null && scope.row.followUpRate !== undefined">
              {{ formatPercent(scope.row.followUpRate) }}
            </span>
            <span v-else>-</span>
          </template>
        </el-table-column>
        <el-table-column
          label="及时率"
          align="center"
          key="rate"
          prop="rate"
          min-width="100"
        >
          <template slot-scope="scope">
            <el-button
              v-if="scope.row.rate !== null && scope.row.rate !== undefined"
              type="text"
              @click="handleSeedetails(scope.row)"
            >
              {{ formatPercent(scope.row.rate) }}
            </el-button>
            <span v-else style="color: #909399">-</span>
          </template>
        </el-table-column>
        <el-table-column
          label="满意度题目总量"
          align="center"
          key="joyAllCount"
          prop="joyAllCount"
          min-width="140"
        />
        <el-table-column
          label="满意度填报量"
          align="center"
          key="joyCount"
          prop="joyCount"
          min-width="120"
        />
        <el-table-column
          label="完成比率"
          align="center"
          key="joyTotal"
          prop="joyTotal"
          min-width="100"
        >
          <template slot-scope="scope">
            <span v-if="scope.row.joyTotal !== null && scope.row.joyTotal !== undefined">
              {{ formatPercent(scope.row.joyTotal) }}
            </span>
            <span v-else>-</span>
          </template>
        </el-table-column>
        <el-table-column
          label="操作"
          align="center"
          fixed="right"
          width="120"
        >
          <template slot-scope="scope">
            <el-button
              type="text"
              @click="getinfo(scope.row)"
            >
              <i class="el-icon-s-order" style="margin-right: 4px"></i>
              æŸ¥çœ‹è¯¦æƒ…
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <!-- åˆ†é¡µ -->
    <div class="pagination-section" v-if="total > 0">
      <el-pagination
        background
        layout="total, sizes, prev, pager, next, jumper"
        :current-page="queryParams.pageNum"
        :page-size="queryParams.pageSize"
        :page-sizes="[10, 20, 30, 50]"
        :total="total"
        @size-change="handleSizeChange"
        @current-change="handlePageChange"
      />
    </div>
    <!-- æœªåŠæ—¶éšè®¿è¯¦æƒ…对话框 -->
    <el-dialog
      title="未及时随访患者服务"
      :visible.sync="SeedetailsVisible"
      width="80%"
      :close-on-click-modal="false"
    >
      <SeedetailsDialog
        v-if="SeedetailsVisible"
        :row-data="currentRow"
        :query-params="queryParams"
        @close="SeedetailsVisible = false"
      />
    </el-dialog>
    <!-- æ»¡æ„åº¦è¯¦æƒ…对话框 -->
    <el-dialog
      :visible.sync="topicVisible"
      width="60%"
      :close-on-click-modal="false"
    >
      <template #title>
        <div style="display: flex; align-items: center;">
          <i class="el-icon-s-data" style="margin-right: 8px; color: #409EFF;"></i>
          <span>{{ topicvalue.name }}</span>
          <span style="margin-left: 10px; color: #666; font-size: 14px;">满意度指标详情</span>
        </div>
      </template>
      <topic-dialog
        v-if="topicVisible"
        :row-data="currentRow"
        :query-params="queryParams"
        @close="topicVisible = false"
      />
    </el-dialog>
  </div>
</template>
<script>
import { getSfStatisticsJoy, getSfStatisticsJoyInfo, selectTimelyRate } from "@/api/system/user";
import ExcelJS from "exceljs";
import { saveAs } from "file-saver";
import SeedetailsDialog from './components/SeedetailsDialog.vue';
import TopicDialog from './components/TopicDialog.vue';
export default {
  name: 'FollowupStatistics',
  components: {
    SeedetailsDialog,
    TopicDialog
  },
  data() {
    return {
      // æŸ¥è¯¢å‚æ•°
      queryParams: {
        statisticaltype: 1,
        leavehospitaldistrictcodes: [],
        deptcodes: [],
        serviceType: [2],
        dateRange: [],
        pageNum: 1,
        pageSize: 20
      },
      // ç»Ÿè®¡ç±»åž‹åˆ—表
      Statisticallist: [
        { label: "病区统计", value: 1 },
        { label: "科室统计", value: 2 }
      ],
      // ç—…区列表
      flatArrayhospit: [],
      // ç§‘室列表
      flatArraydept: [],
      // æœåŠ¡ç±»åž‹é€‰é¡¹
      options: [],
      // è¡¨æ ¼æ•°æ®
      userList: [],
      // æ€»æ¡æ•°
      total: 0,
      // åŠ è½½çŠ¶æ€
      loading: false,
      // é€‰ä¸­çš„行
      ids: [],
      single: true,
      multiple: true,
      // å½“前操作的行
      currentRow: null,
      // å¯¹è¯æ¡†æ˜¾ç¤ºæŽ§åˆ¶
      SeedetailsVisible: false,
      topicVisible: false,
      // æ»¡æ„åº¦è¯¦æƒ…数据
      topiclist: [],
      topicvalue: {
        name: ''
      },
      // æ—¥æœŸé€‰æ‹©å™¨é€‰é¡¹
      pickerOptions: {
        shortcuts: [
          {
            text: '最近一周',
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
              picker.$emit('pick', [start, end]);
            }
          },
          {
            text: '最近一个月',
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
              picker.$emit('pick', [start, end]);
            }
          },
          {
            text: '最近三个月',
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
              picker.$emit('pick', [start, end]);
            }
          }
        ],
        disabledDate(time) {
          return time.getTime() > Date.now();
        }
      }
    };
  },
  created() {
    this.initData();
  },
  methods: {
    // åˆå§‹åŒ–数据
    async initData() {
      await this.getDeptTree();
      await this.getList();
    },
    // èŽ·å–ç§‘å®¤æ ‘
    getDeptTree() {
      // èŽ·å–æœåŠ¡ç±»åž‹
      this.options = this.$store.getters.tasktypes || [];
      // èŽ·å–ç§‘å®¤åˆ—è¡¨
      this.flatArraydept = (this.$store.getters.belongDepts || []).map((dept) => {
        return {
          label: dept.deptName,
          value: dept.deptCode
        };
      });
      // èŽ·å–ç—…åŒºåˆ—è¡¨
      this.flatArrayhospit = (this.$store.getters.belongWards || []).map((ward) => {
        return {
          label: ward.districtName,
          value: ward.districtCode
        };
      });
      // æ·»åŠ å…¨éƒ¨é€‰é¡¹
      this.flatArraydept.push({ label: "全部", value: "all" });
      this.flatArrayhospit.push({ label: "全部", value: "all" });
    },
    // èŽ·å–ç»Ÿè®¡åˆ—è¡¨
    async getList() {
      this.loading = true;
      try {
        // å¤„理查询参数
        const params = {
          configKey: "joyCount",
          ...this.queryParams
        };
        // å¤„理日期范围
        if (this.queryParams.dateRange && this.queryParams.dateRange.length === 2) {
          params.startTime = this.queryParams.dateRange[0];
          params.endTime = this.queryParams.dateRange[1];
        }
        // å¤„理病区/科室选择
        if (params.statisticaltype == 1) {
          // ç—…区统计
          if (params.leavehospitaldistrictcodes.includes("all")) {
            // å¦‚果选择了"全部",则移除"all"值
            params.leavehospitaldistrictcodes = params.leavehospitaldistrictcodes.filter(item => item !== "all");
            // å¦‚果需要传所有病区代码,可以从store中获取
            params.leavehospitaldistrictcodes = (this.$store.getters.belongWards || []).map(ward => ward.districtCode);
          }
        } else if (params.statisticaltype == 2) {
          // ç§‘室统计
          if (params.deptcodes.includes("all")) {
            // å¦‚果选择了"全部",则移除"all"值
            params.deptcodes = params.deptcodes.filter(item => item !== "all");
            // å¦‚果需要传所有科室代码,可以从store中获取
            params.deptcodes = (this.$store.getters.belongDepts || []).map(dept => dept.deptCode);
          }
        }
        const response = await getSfStatisticsJoy(params);
        this.userList = response.data || [];
        this.total = response.total || 0;
      } catch (error) {
        console.error('获取统计列表失败:', error);
        this.$message.error('获取数据失败');
      } finally {
        this.loading = false;
      }
    },
    // å¤„理统计类型变化
    handleStatisticalTypeChange(value) {
      if (value === 1) {
        this.queryParams.deptcodes = [];
      } else {
        this.queryParams.leavehospitaldistrictcodes = [];
      }
      this.queryParams.pageNum = 1;
      this.getList();
    },
    // å¤„理查询
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    // é‡ç½®æŸ¥è¯¢
    resetQuery() {
      this.queryParams = {
        statisticaltype: 1,
        leavehospitaldistrictcodes: [],
        deptcodes: [],
        serviceType: [2],
        dateRange: [],
        pageNum: 1,
        pageSize: 20
      };
      this.getList();
    },
    // å¤„理分页大小变化
    handleSizeChange(size) {
      this.queryParams.pageSize = size;
      this.queryParams.pageNum = 1;
      this.getList();
    },
    // å¤„理页码变化
    handlePageChange(page) {
      this.queryParams.pageNum = page;
      this.getList();
    },
    // å¤„理行选择
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.id);
      this.single = selection.length !== 1;
      this.multiple = !selection.length;
    },
    // èŽ·å–è¡Œkey
    getRowKey(row) {
      return row.statisticaltype === 1
        ? row.leavehospitaldistrictcode
        : row.deptcode;
    },
    // æ ¼å¼åŒ–百分比
    formatPercent(value) {
      if (value === null || value === undefined) return '-';
      const num = parseFloat(value);
      if (isNaN(num)) return '-';
      return `${(num * 100).toFixed(2)}%`;
    },
    // æŸ¥çœ‹æœªåŠæ—¶éšè®¿è¯¦æƒ…
    handleSeedetails(row) {
      this.currentRow = row;
      this.SeedetailsVisible = true;
    },
    // æŸ¥çœ‹æ»¡æ„åº¦è¯¦æƒ…
    async getinfo(row) {
      this.currentRow = row;
      this.topicVisible = true;
      try {
        // å¤„理查询参数
        const params = {
          configKey: "joyCount",
          ...this.queryParams
        };
        // å¤„理日期范围
        if (this.queryParams.dateRange && this.queryParams.dateRange.length === 2) {
          params.startTime = this.queryParams.dateRange[0];
          params.endTime = this.queryParams.dateRange[1];
        }
        if (this.queryParams.statisticaltype == 1) {
          this.topicvalue.name = row.leavehospitaldistrictname;
          params.leavehospitaldistrictcodes = [row.leavehospitaldistrictcode];
        } else {
          this.topicvalue.name = row.deptname;
          params.deptcodes = [row.deptcode];
        }
        const response = await getSfStatisticsJoyInfo(params);
        this.topiclist = response.data || [];
      } catch (error) {
        console.error('获取满意度详情失败:', error);
        this.$message.error('获取详情失败');
      }
    },
    // å¯¼å‡ºæ•°æ®
    async handleExport() {
      if (!this.userList.length) {
        this.$message.warning('没有数据可导出');
        return;
      }
      try {
        this.loading = true;
        // æž„建日期范围字符串
        let dateRangeString = "";
        let sheetNameSuffix = "";
        if (this.queryParams.dateRange && this.queryParams.dateRange.length === 2) {
          const startDateFormatted = this.queryParams.dateRange[0];
          const endDateFormatted = this.queryParams.dateRange[1];
          dateRangeString = `${startDateFormatted}至${endDateFormatted}`;
          sheetNameSuffix = `${startDateFormatted}至${endDateFormatted}`;
        } else {
          const now = new Date();
          const currentMonth = now.getMonth() + 1;
          dateRangeString = `${currentMonth}月`;
          sheetNameSuffix = `${currentMonth}月`;
        }
        const excelName = `随访统计表_${dateRangeString}.xlsx`;
        const worksheetName = `随访统计_${sheetNameSuffix}`;
        // åˆ›å»ºExcel工作簿
        const workbook = new ExcelJS.Workbook();
        const worksheet = workbook.addWorksheet(worksheetName);
        // å®šä¹‰æ ·å¼
        const titleStyle = {
          font: { name: "微软雅黑", size: 16, bold: true },
          fill: { type: "pattern", pattern: "solid", fgColor: { argb: "FFE6F3FF" } },
          alignment: { vertical: "middle", horizontal: "center" },
          border: {
            top: { style: "thin", color: { argb: "FFD0D0D0" } },
            left: { style: "thin", color: { argb: "FFD0D0D0" } },
            bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
            right: { style: "thin", color: { argb: "FFD0D0D0" } }
          }
        };
        const headerStyle = {
          font: { name: "微软雅黑", size: 11, bold: true },
          fill: { type: "pattern", pattern: "solid", fgColor: { argb: "FFF5F7FA" } },
          alignment: { vertical: "middle", horizontal: "center" },
          border: {
            top: { style: "thin", color: { argb: "FFD0D0D0" } },
            left: { style: "thin", color: { argb: "FFD0D0D0" } },
            bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
            right: { style: "thin", color: { argb: "FFD0D0D0" } }
          }
        };
        const cellStyle = {
          font: { name: "宋体", size: 10 },
          alignment: { vertical: "middle", horizontal: "center" },
          border: {
            top: { style: "thin", color: { argb: "FFD0D0D0" } },
            left: { style: "thin", color: { argb: "FFD0D0D0" } },
            bottom: { style: "thin", color: { argb: "FFD0D0D0" } },
            right: { style: "thin", color: { argb: "FFD0D0D0" } }
          }
        };
        // æ·»åŠ æ€»æ ‡é¢˜
        worksheet.mergeCells(1, 1, 1, 10);
        const titleCell = worksheet.getCell(1, 1);
        titleCell.value = `随访统计表(${sheetNameSuffix})`;
        titleCell.style = titleStyle;
        worksheet.getRow(1).height = 35;
        // æ·»åŠ è¡¨å¤´
        const headers = [
          this.queryParams.statisticaltype == 1 ? "出院病区" : "科室",
          "出院人次",
          "无需随访人次",
          "应随访人次",
          "随访率",
          "及时率",
          "满意度题目总量",
          "满意度填报量",
          "完成比率"
        ];
        const headerRow = worksheet.addRow(headers);
        headerRow.eachCell((cell) => {
          cell.style = headerStyle;
        });
        headerRow.height = 25;
        // æ·»åŠ æ•°æ®è¡Œ
        this.userList.forEach((item) => {
          const dataRow = worksheet.addRow([
            this.queryParams.statisticaltype == 1 ? item.leavehospitaldistrictname : item.deptname,
            item.dischargeCount || 0,
            item.nonFollowUp || 0,
            item.followUpNeeded || 0,
            item.followUpRate || "0%",
            item.rate ? this.formatPercent(item.rate) : "0%",
            item.joyAllCount || 0,
            item.joyCount || 0,
            item.joyTotal ? this.formatPercent(item.joyTotal) : "0%"
          ]);
          dataRow.eachCell((cell) => {
            cell.style = cellStyle;
          });
          dataRow.height = 22;
        });
        // è®¾ç½®åˆ—宽
        worksheet.columns = [
          { width: 20 },
          { width: 12 },
          { width: 12 },
          { width: 12 },
          { width: 12 },
          { width: 12 },
          { width: 15 },
          { width: 15 },
          { width: 12 }
        ];
        // ç”Ÿæˆå¹¶ä¸‹è½½æ–‡ä»¶
        const buffer = await workbook.xlsx.writeBuffer();
        const blob = new Blob([buffer], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        });
        saveAs(blob, excelName);
        this.$message.success("导出成功");
      } catch (error) {
        console.error("导出失败:", error);
        this.$message.error(`导出失败: ${error.message}`);
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>
<style lang="scss" scoped>
.followup-statistics {
  .query-section {
    background: #fff;
    padding: 20px;
    border-radius: 4px;
    margin-bottom: 20px;
    .query-form {
      display: flex;
      flex-wrap: wrap;
      ::v-deep .el-form-item {
        margin-bottom: 20px;
        &:not(:last-child) {
          margin-right: 20px;
        }
      }
    }
  }
  .table-section {
    background: #fff;
    padding: 20px;
    border-radius: 4px;
    margin-bottom: 20px;
    ::v-deep .el-table {
      th {
        background-color: #f8f9fa;
        font-weight: 600;
        color: #333;
      }
    }
  }
  .pagination-section {
    display: flex;
    justify-content: flex-end;
    background: #fff;
    padding: 20px;
    border-radius: 4px;
  }
}
</style>
src/views/Satisfaction/sfstatistics/components/SatisfactionStatistics.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,990 @@
<template>
  <div class="satisfaction-statistics">
    <div class="query-section">
      <el-form
        :model="queryParams"
        ref="queryForm"
        size="medium"
        :inline="true"
        label-width="100px"
        class="query-form"
      >
        <el-form-item label="患者来源" prop="patientSource">
          <el-select
            v-model="queryParams.patientSource"
            placeholder="请选择患者来源"
            clearable
            style="width: 200px"
          >
            <el-option
              v-for="source in patientSourceList"
              :key="source.value"
              :label="source.label"
              :value="source.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="科室" prop="deptCode">
          <el-select
            v-model="queryParams.deptCode"
            placeholder="请选择科室"
            clearable
            filterable
            style="width: 200px"
          >
            <el-option
              v-for="dept in deptList"
              :key="dept.value"
              :label="dept.label"
              :value="dept.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="病区" prop="wardCode">
          <el-select
            v-model="queryParams.wardCode"
            placeholder="请选择病区"
            clearable
            filterable
            style="width: 200px"
          >
            <el-option
              v-for="ward in wardList"
              :key="ward.value"
              :label="ward.label"
              :value="ward.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="统计时间" prop="dateRange">
          <el-date-picker
            v-model="queryParams.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            value-format="yyyy-MM-dd"
            :picker-options="pickerOptions"
            style="width: 380px"
          />
        </el-form-item>
        <el-form-item>
          <el-button
            type="primary"
            icon="el-icon-search"
            @click="handleSearch"
            :loading="loading"
          >
            æŸ¥è¯¢
          </el-button>
          <el-button icon="el-icon-refresh" @click="handleReset">
            é‡ç½®
          </el-button>
        </el-form-item>
      </el-form>
    </div>
    <!-- æ»¡æ„åº¦åˆ†ç±»ç»Ÿè®¡å›¾è¡¨ -->
    <div class="chart-section">
      <div class="chart-container">
        <div class="chart-title">满意度类型统计</div>
        <div id="satisfactionBarChart" style="width: 100%; height: 400px"></div>
      </div>
    </div>
    <!-- é¢˜ç›®æ˜Žç»†è¡¨æ ¼ -->
    <div class="detail-table-section">
      <div class="section-title">题目明细统计</div>
      <el-table
        v-loading="detailLoading"
        :data="questionDetailData"
        :border="true"
        style="width: 100%"
        row-class-name="question-row"
      >
        <el-table-column
          type="expand"
          width="60"
        >
          <template slot-scope="{ row }">
            <div class="option-detail">
              <el-table
                :data="row.options"
                :border="true"
                style="width: 100%"
                class="inner-table"
              >
                <el-table-column
                  label="选项"
                  prop="optionText"
                  align="center"
                  min-width="200"
                />
                <el-table-column
                  label="选择人数"
                  prop="chosenQuantity"
                  align="center"
                  min-width="120"
                />
                <el-table-column
                  label="选择比例"
                  prop="chosenPercentage"
                  align="center"
                  min-width="120"
                >
                  <template slot-scope="{ row: option }">
                    {{ formatPercent(option.chosenPercentage) }}
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </template>
        </el-table-column>
        <el-table-column
          label="序号"
          type="index"
          align="center"
          width="60"
        />
        <el-table-column
          label="题目"
          prop="scriptContent"
          align="center"
          min-width="300"
        >
          <template slot-scope="{ row }">
            <span>{{ row.scriptContent }}?</span>
            <el-tag
              :type="row.scriptType === 1 ? 'primary' : 'success'"
              size="mini"
              style="margin-left: 5px"
            >
              {{ row.scriptType === 1 ? '单选题' : '多选题' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          label="平均得分"
          prop="averageScore"
          align="center"
          width="120"
        >
          <template slot-scope="{ row }">
            <span class="score-text">{{ row.averageScore.toFixed(1) }}</span>
          </template>
        </el-table-column>
        <el-table-column
          label="最高得分"
          prop="maxScore"
          align="center"
          width="120"
        >
          <template slot-scope="{ row }">
            <span class="score-text">{{ row.maxScore.toFixed(1) }}</span>
          </template>
        </el-table-column>
        <el-table-column
          label="最低得分"
          prop="minScore"
          align="center"
          width="120"
        >
          <template slot-scope="{ row }">
            <span class="score-text">{{ row.minScore.toFixed(1) }}</span>
          </template>
        </el-table-column>
        <el-table-column
          label="答题人数"
          prop="answerCount"
          align="center"
          width="100"
        />
        <el-table-column
          label="未答题人数"
          prop="unanswerCount"
          align="center"
          width="100"
        >
          <template slot-scope="{ row }">
            {{ row.totalCount - row.answerCount }}
          </template>
        </el-table-column>
        <el-table-column
          label="答题率"
          prop="answerRate"
          align="center"
          width="100"
        >
          <template slot-scope="{ row }">
            {{ formatPercent(row.answerCount / row.totalCount) }}
          </template>
        </el-table-column>
      </el-table>
      <!-- ç»¼åˆå¾—分行 -->
      <div class="summary-row">
        <div class="summary-content">
          <div class="summary-item">
            <span class="label">综合得分:</span>
            <span class="value">{{ totalScore.toFixed(1) }}</span>
          </div>
          <div class="summary-item">
            <span class="label">总答题人数:</span>
            <span class="value">{{ totalAnswerCount }}</span>
          </div>
          <div class="summary-item">
            <span class="label">总答题率:</span>
            <span class="value">{{ formatPercent(totalAnswerRate) }}</span>
          </div>
        </div>
      </div>
    </div>
    <!-- åˆ†é¡µ -->
    <div class="pagination-section" v-if="questionDetailData.length > 0">
      <el-pagination
        background
        layout="total, sizes, prev, pager, next, jumper"
        :current-page="detailQueryParams.pageNum"
        :page-size="detailQueryParams.pageSize"
        :page-sizes="[10, 20, 30]"
        :total="detailTotal"
        @size-change="handleDetailSizeChange"
        @current-change="handleDetailPageChange"
      />
    </div>
  </div>
</template>
<script>
export default {
  name: 'SatisfactionStatistics',
  data() {
    return {
      // æŸ¥è¯¢å‚æ•°
      queryParams: {
        patientSource: '',
        deptCode: '',
        wardCode: '',
        dateRange: []
      },
      // æ‚£è€…来源选项
      patientSourceList: [
        { value: '1', label: '门诊' },
        { value: '2', label: '住院' },
        { value: '3', label: '急诊' },
        { value: '4', label: '体检' }
      ],
      // ç§‘室列表
      deptList: [],
      // ç—…区列表
      wardList: [],
      // å›¾è¡¨å®žä¾‹
      barChart: null,
      // åŠ è½½çŠ¶æ€
      loading: false,
      detailLoading: false,
      // é¢˜ç›®æ˜Žç»†æ•°æ®
      questionDetailData: [],
      // é¢˜ç›®æ˜Žç»†æŸ¥è¯¢å‚æ•°
      detailQueryParams: {
        pageNum: 1,
        pageSize: 10
      },
      // é¢˜ç›®æ˜Žç»†æ€»æ•°
      detailTotal: 0,
      // ç»¼åˆå¾—分
      totalScore: 0,
      totalAnswerCount: 0,
      totalAnswerRate: 0,
      // æ—¥æœŸé€‰æ‹©å™¨é€‰é¡¹
      pickerOptions: {
        shortcuts: [
          {
            text: '最近一周',
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
              picker.$emit('pick', [start, end]);
            }
          },
          {
            text: '最近一个月',
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
              picker.$emit('pick', [start, end]);
            }
          },
          {
            text: '最近三个月',
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
              picker.$emit('pick', [start, end]);
            }
          }
        ],
        disabledDate(time) {
          return time.getTime() > Date.now();
        }
      },
      // Mock数据 - æ»¡æ„åº¦åˆ†ç±»
      mockSatisfactionCategories: ['服务态度', '技术水平', '环境设施', '沟通效果', '等待时间', '收费合理性'],
      // Mock数据 - æ»¡æ„åº¦ç±»åž‹
      mockSatisfactionTypes: [
        { name: '非常满意', value: 85, color: '#36B37E' },
        { name: '满意', value: 72, color: '#4CAF50' },
        { name: '一般', value: 60, color: '#FF9D4D' },
        { name: '不满意', value: 15, color: '#FF5C5C' },
        { name: '非常不满意', value: 5, color: '#F44336' }
      ]
    };
  },
  mounted() {
    this.initData();
    this.initChart();
  },
  beforeDestroy() {
    if (this.barChart) {
      this.barChart.dispose();
      this.barChart = null;
    }
  },
  methods: {
    // åˆå§‹åŒ–数据
    async initData() {
      await this.getDeptList();
      await this.getWardList();
      await this.loadData();
    },
    // èŽ·å–ç§‘å®¤åˆ—è¡¨
    getDeptList() {
      // æ¨¡æ‹ŸAPI调用获取科室列表
      return new Promise((resolve) => {
        setTimeout(() => {
          this.deptList = [
            { value: 'dept001', label: '心血管内科' },
            { value: 'dept002', label: '神经内科' },
            { value: 'dept003', label: '普外科' },
            { value: 'dept004', label: '骨科' },
            { value: 'dept005', label: '妇产科' },
            { value: 'dept006', label: '儿科' }
          ];
          resolve();
        }, 100);
      });
    },
    // èŽ·å–ç—…åŒºåˆ—è¡¨
    getWardList() {
      // æ¨¡æ‹ŸAPI调用获取病区列表
      return new Promise((resolve) => {
        setTimeout(() => {
          this.wardList = [
            { value: 'ward001', label: '内科一病区' },
            { value: 'ward002', label: '内科二病区' },
            { value: 'ward003', label: '外科一病区' },
            { value: 'ward004', label: '外科二病区' },
            { value: 'ward005', label: '妇产科病区' },
            { value: 'ward006', label: '儿科病区' }
          ];
          resolve();
        }, 100);
      });
    },
    // åŠ è½½æ•°æ®
    async loadData() {
      await Promise.all([
        this.loadChartData(),
        this.loadQuestionDetailData()
      ]);
    },
    // åŠ è½½å›¾è¡¨æ•°æ®
    loadChartData() {
      this.loading = true;
      return new Promise((resolve) => {
        setTimeout(() => {
          this.renderChart(this.generateChartData());
          this.loading = false;
          resolve();
        }, 500);
      });
    },
    // åŠ è½½é¢˜ç›®æ˜Žç»†æ•°æ®
    loadQuestionDetailData() {
      this.detailLoading = true;
      return new Promise((resolve) => {
        setTimeout(() => {
          const mockData = this.generateMockQuestionDetail();
          this.questionDetailData = mockData.list;
          this.detailTotal = mockData.total;
          // è®¡ç®—综合得分
          this.calculateSummary(mockData);
          this.detailLoading = false;
          resolve();
        }, 500);
      });
    },
    // è®¡ç®—综合得分
    calculateSummary(data) {
      let totalScore = 0;
      let totalAnswerCount = 0;
      let totalCount = 0;
      data.list.forEach(item => {
        totalScore += item.averageScore;
        totalAnswerCount += item.answerCount;
        totalCount += item.totalCount;
      });
      this.totalScore = data.list.length > 0 ? totalScore / data.list.length : 0;
      this.totalAnswerCount = totalAnswerCount;
      this.totalAnswerRate = totalCount > 0 ? totalAnswerCount / totalCount : 0;
    },
    // åˆå§‹åŒ–图表
    initChart() {
      const echarts = require('echarts');
      const chartDom = document.getElementById('satisfactionBarChart');
      if (!chartDom) return;
      this.barChart = echarts.init(chartDom);
      // ç›‘听窗口变化
      window.addEventListener('resize', this.handleChartResize);
    },
    // ç”Ÿæˆå›¾è¡¨æ•°æ®
    generateChartData() {
      const categories = this.mockSatisfactionCategories;
      const series = this.mockSatisfactionTypes.map(type => ({
        name: type.name,
        type: 'bar',
        barWidth: 25,
        stack: '满意度',
        data: categories.map(() => Math.floor(Math.random() * 20) + 10), // éšæœºæ•°æ®
        itemStyle: {
          color: type.color
        }
      }));
      return {
        categories,
        legend: this.mockSatisfactionTypes.map(type => type.name),
        series
      };
    },
    // æ¸²æŸ“图表
    renderChart(chartData) {
      if (!this.barChart) return;
      const option = {
        title: {
          text: '满意度类型统计',
          left: 'center',
          textStyle: {
            fontSize: 16,
            fontWeight: 'normal',
            color: '#333'
          }
        },
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          },
          formatter: (params) => {
            let result = `<div style="margin-bottom: 5px; font-weight: bold;">${params[0].name}</div>`;
            let total = 0;
            params.forEach(param => {
              result += `<div style="margin: 2px 0;">
                <span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:5px;"></span>
                ${param.seriesName}: <strong>${param.value}%</strong>
              </div>`;
              total += param.value;
            });
            result += `<div style="margin-top: 5px; padding-top: 5px; border-top: 1px solid #eee;">
              <strong>总计: ${total}%</strong>
            </div>`;
            return result;
          }
        },
        legend: {
          data: chartData.legend,
          top: 20,
          textStyle: {
            fontSize: 12,
            color: '#666'
          }
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          top: 80,
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: chartData.categories,
          axisLabel: {
            interval: 0,
            rotate: 0,
            fontSize: 12,
            color: '#666'
          },
          axisLine: {
            lineStyle: {
              color: '#DCDFE6'
            }
          },
          axisTick: {
            alignWithLabel: true
          }
        },
        yAxis: {
          type: 'value',
          name: '百分比 (%)',
          min: 0,
          max: 100,
          axisLabel: {
            formatter: '{value}%',
            color: '#666'
          },
          axisLine: {
            lineStyle: {
              color: '#DCDFE6'
            }
          },
          splitLine: {
            lineStyle: {
              type: 'dashed',
              color: '#E4E7ED'
            }
          }
        },
        series: chartData.series
      };
      this.barChart.setOption(option);
    },
    // ç”ŸæˆMock题目详情数据
    generateMockQuestionDetail() {
      const questions = [
        {
          scriptContent: '您对医护人员的服务态度是否满意',
          scriptType: 1, // 1: å•选题, 2: å¤šé€‰é¢˜
          totalCount: 156,
          answerCount: 145,
          averageScore: 4.5,
          maxScore: 5,
          minScore: 3,
          options: [
            { optionText: '非常满意', chosenQuantity: 89, chosenPercentage: 0.61 },
            { optionText: '满意', chosenQuantity: 45, chosenPercentage: 0.31 },
            { optionText: '一般', chosenQuantity: 8, chosenPercentage: 0.06 },
            { optionText: '不满意', chosenQuantity: 2, chosenPercentage: 0.01 },
            { optionText: '非常不满意', chosenQuantity: 1, chosenPercentage: 0.01 }
          ]
        },
        {
          scriptContent: '您对医生的诊疗水平和技术能力评价如何',
          scriptType: 1,
          totalCount: 156,
          answerCount: 142,
          averageScore: 4.7,
          maxScore: 5,
          minScore: 3,
          options: [
            { optionText: '非常专业', chosenQuantity: 95, chosenPercentage: 0.67 },
            { optionText: '比较专业', chosenQuantity: 40, chosenPercentage: 0.28 },
            { optionText: '一般', chosenQuantity: 5, chosenPercentage: 0.04 },
            { optionText: '不够专业', chosenQuantity: 2, chosenPercentage: 0.01 },
            { optionText: '非常不专业', chosenQuantity: 0, chosenPercentage: 0 }
          ]
        },
        {
          scriptContent: '您对医院的环境和卫生状况是否满意',
          scriptType: 1,
          totalCount: 156,
          answerCount: 138,
          averageScore: 4.3,
          maxScore: 5,
          minScore: 2,
          options: [
            { optionText: '非常满意', chosenQuantity: 75, chosenPercentage: 0.54 },
            { optionText: '满意', chosenQuantity: 50, chosenPercentage: 0.36 },
            { optionText: '一般', chosenQuantity: 10, chosenPercentage: 0.07 },
            { optionText: '不满意', chosenQuantity: 3, chosenPercentage: 0.02 },
            { optionText: '非常不满意', chosenQuantity: 0, chosenPercentage: 0 }
          ]
        },
        {
          scriptContent: '您认为医护人员与您的沟通是否充分',
          scriptType: 1,
          totalCount: 156,
          answerCount: 140,
          averageScore: 4.6,
          maxScore: 5,
          minScore: 3,
          options: [
            { optionText: '沟通非常充分', chosenQuantity: 85, chosenPercentage: 0.61 },
            { optionText: '沟通比较充分', chosenQuantity: 45, chosenPercentage: 0.32 },
            { optionText: '沟通一般', chosenQuantity: 8, chosenPercentage: 0.06 },
            { optionText: '沟通不够充分', chosenQuantity: 2, chosenPercentage: 0.01 },
            { optionText: '沟通非常不充分', chosenQuantity: 0, chosenPercentage: 0 }
          ]
        },
        {
          scriptContent: '您对等待就诊和治疗的时间是否满意',
          scriptType: 1,
          totalCount: 156,
          answerCount: 135,
          averageScore: 4.0,
          maxScore: 5,
          minScore: 1,
          options: [
            { optionText: '等待时间很短', chosenQuantity: 60, chosenPercentage: 0.44 },
            { optionText: '等待时间合理', chosenQuantity: 55, chosenPercentage: 0.41 },
            { optionText: '等待时间较长', chosenQuantity: 15, chosenPercentage: 0.11 },
            { optionText: '等待时间很长', chosenQuantity: 5, chosenPercentage: 0.04 },
            { optionText: '无法忍受的等待', chosenQuantity: 0, chosenPercentage: 0 }
          ]
        },
        {
          scriptContent: '您对医院收费的透明度和合理性评价如何',
          scriptType: 1,
          totalCount: 156,
          answerCount: 130,
          averageScore: 4.2,
          maxScore: 5,
          minScore: 2,
          options: [
            { optionText: '非常透明合理', chosenQuantity: 70, chosenPercentage: 0.54 },
            { optionText: '比较透明合理', chosenQuantity: 45, chosenPercentage: 0.35 },
            { optionText: '一般', chosenQuantity: 10, chosenPercentage: 0.08 },
            { optionText: '不太透明', chosenQuantity: 5, chosenPercentage: 0.04 },
            { optionText: '非常不透明', chosenQuantity: 0, chosenPercentage: 0 }
          ]
        },
        {
          scriptContent: '您会向亲友推荐我们医院吗',
          scriptType: 1,
          totalCount: 156,
          answerCount: 148,
          averageScore: 4.8,
          maxScore: 5,
          minScore: 3,
          options: [
            { optionText: '非常愿意推荐', chosenQuantity: 100, chosenPercentage: 0.68 },
            { optionText: '比较愿意推荐', chosenQuantity: 40, chosenPercentage: 0.27 },
            { optionText: '一般', chosenQuantity: 6, chosenPercentage: 0.04 },
            { optionText: '不太愿意推荐', chosenQuantity: 2, chosenPercentage: 0.01 },
            { optionText: '绝对不会推荐', chosenQuantity: 0, chosenPercentage: 0 }
          ]
        },
        {
          scriptContent: '您对以下哪些方面比较满意(多选)',
          scriptType: 2, // å¤šé€‰é¢˜
          totalCount: 156,
          answerCount: 150,
          averageScore: 4.4,
          maxScore: 5,
          minScore: 3,
          options: [
            { optionText: '医疗技术水平', chosenQuantity: 120, chosenPercentage: 0.8 },
            { optionText: '服务态度', chosenQuantity: 110, chosenPercentage: 0.73 },
            { optionText: '环境卫生', chosenQuantity: 90, chosenPercentage: 0.6 },
            { optionText: '医疗设备', chosenQuantity: 85, chosenPercentage: 0.57 },
            { optionText: '收费透明度', chosenQuantity: 70, chosenPercentage: 0.47 },
            { optionText: '等待时间', chosenQuantity: 60, chosenPercentage: 0.4 }
          ]
        },
        {
          scriptContent: '您认为医院哪些方面需要改进(多选)',
          scriptType: 2,
          totalCount: 156,
          answerCount: 125,
          averageScore: 3.8,
          maxScore: 5,
          minScore: 2,
          options: [
            { optionText: '等待时间过长', chosenQuantity: 80, chosenPercentage: 0.64 },
            { optionText: '就诊流程复杂', chosenQuantity: 70, chosenPercentage: 0.56 },
            { optionText: '费用较高', chosenQuantity: 60, chosenPercentage: 0.48 },
            { optionText: '停车困难', chosenQuantity: 50, chosenPercentage: 0.4 },
            { optionText: '指引标识不清', chosenQuantity: 40, chosenPercentage: 0.32 },
            { optionText: '网络预约不便', chosenQuantity: 30, chosenPercentage: 0.24 }
          ]
        },
        {
          scriptContent: '您对本次住院的整体体验评分',
          scriptType: 1,
          totalCount: 156,
          answerCount: 152,
          averageScore: 4.6,
          maxScore: 5,
          minScore: 3,
          options: [
            { optionText: '5分(非常满意)', chosenQuantity: 90, chosenPercentage: 0.59 },
            { optionText: '4分(满意)', chosenQuantity: 50, chosenPercentage: 0.33 },
            { optionText: '3分(一般)', chosenQuantity: 10, chosenPercentage: 0.07 },
            { optionText: '2分(不满意)', chosenQuantity: 2, chosenPercentage: 0.01 },
            { optionText: '1分(非常不满意)', chosenQuantity: 0, chosenPercentage: 0 }
          ]
        }
      ];
      // åˆ†é¡µå¤„理
      const startIndex = (this.detailQueryParams.pageNum - 1) * this.detailQueryParams.pageSize;
      const endIndex = startIndex + this.detailQueryParams.pageSize;
      const paginatedData = questions.slice(startIndex, endIndex);
      return {
        list: paginatedData,
        total: questions.length
      };
    },
    // å¤„理图表响应式
    handleChartResize() {
      if (this.barChart) {
        this.barChart.resize();
      }
    },
    // å¤„理查询
    handleSearch() {
      this.detailQueryParams.pageNum = 1;
      this.loadData();
    },
    // å¤„理重置
    handleReset() {
      this.$refs.queryForm.resetFields();
      this.queryParams.dateRange = [];
      this.detailQueryParams.pageNum = 1;
      this.loadData();
    },
    // å¤„理明细分页大小变化
    handleDetailSizeChange(size) {
      this.detailQueryParams.pageSize = size;
      this.detailQueryParams.pageNum = 1;
      this.loadQuestionDetailData();
    },
    // å¤„理明细页码变化
    handleDetailPageChange(page) {
      this.detailQueryParams.pageNum = page;
      this.loadQuestionDetailData();
    },
    // æ ¼å¼åŒ–百分比
    formatPercent(value) {
      if (value === null || value === undefined) return '-';
      const num = parseFloat(value);
      if (isNaN(num)) return '-';
      return `${(num * 100).toFixed(2)}%`;
    }
  }
};
</script>
<style lang="scss" scoped>
.satisfaction-statistics {
  .query-section {
    background: #fff;
    padding: 20px;
    border-radius: 4px;
    margin-bottom: 20px;
    .query-form {
      display: flex;
      flex-wrap: wrap;
      ::v-deep .el-form-item {
        margin-bottom: 20px;
        &:not(:last-child) {
          margin-right: 20px;
        }
      }
    }
  }
  .chart-section {
    background: #fff;
    padding: 20px;
    border-radius: 4px;
    margin-bottom: 20px;
    .chart-container {
      width: 100%;
      .chart-title {
        font-size: 16px;
        font-weight: 600;
        color: #333;
        margin-bottom: 20px;
        padding-bottom: 10px;
        border-bottom: 1px solid #f0f0f0;
      }
      #satisfactionBarChart {
        width: 100%;
        height: 400px;
      }
    }
  }
  .detail-table-section {
    background: #fff;
    padding: 20px;
    border-radius: 4px;
    .section-title {
      font-size: 16px;
      font-weight: 600;
      color: #333;
      margin-bottom: 20px;
      padding-bottom: 10px;
      border-bottom: 1px solid #f0f0f0;
    }
    .option-detail {
      padding: 20px;
      background: #f8f9fa;
      border-radius: 4px;
      margin: 10px 0;
    }
    ::v-deep .el-table {
      th {
        background-color: #f8f9fa;
        font-weight: 600;
        color: #333;
      }
      .question-row {
        td {
          background-color: #fff;
        }
        &:hover {
          td {
            background-color: #f5f7fa;
          }
        }
      }
    }
    .score-text {
      font-weight: 600;
      color: #1890ff;
      font-size: 16px;
    }
    .summary-row {
      margin-top: 20px;
      padding: 20px;
      background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
      border-radius: 4px;
      border: 1px solid #dee2e6;
      .summary-content {
        display: flex;
        justify-content: space-around;
        align-items: center;
        .summary-item {
          text-align: center;
          .label {
            font-size: 16px;
            color: #666;
            margin-right: 8px;
          }
          .value {
            font-size: 24px;
            font-weight: 600;
            color: #1890ff;
          }
        }
      }
    }
  }
  .pagination-section {
    display: flex;
    justify-content: flex-end;
    background: #fff;
    padding: 20px;
    border-radius: 4px;
    margin-top: 20px;
  }
  // å†…层表格样式
  .inner-table {
    ::v-deep .el-table__header-wrapper {
      th {
        background-color: #f0f7ff !important;
        color: #333;
        font-weight: 600;
      }
    }
    ::v-deep .el-table__body-wrapper {
      tr {
        background-color: #fff;
        &:hover {
          background-color: #f5f7fa;
        }
      }
    }
  }
}
</style>
src/views/Satisfaction/sfstatistics/components/components/SeedetailsDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,401 @@
<template>
  <div class="seedetails-dialog">
    <div class="examine-jic">
      <div class="jic-value">
        <!-- æŸ¥è¯¢è¡¨å• -->
        <el-form
          :model="patientqueryParams"
          ref="patientQueryForm"
          size="small"
          :inline="true"
          class="detail-query-form"
        >
          <el-form-item label="患者:" prop="name">
            <el-input
              v-model="patientqueryParams.name"
              placeholder="请输入患者姓名"
              clearable
              @keyup.enter.native="handleSearch"
              style="width: 180px"
            />
          </el-form-item>
          <el-form-item label="患者诊断:" prop="leavediagname">
            <el-input
              v-model="patientqueryParams.leavediagname"
              placeholder="请输入患者诊断"
              clearable
              @keyup.enter.native="handleSearch"
              style="width: 200px"
            />
          </el-form-item>
          <el-form-item>
            <el-button
              type="primary"
              icon="el-icon-search"
              @click="handleSearch"
              :loading="loading"
            >
              æœç´¢
            </el-button>
            <el-button icon="el-icon-refresh" @click="handleReset">
              é‡ç½®
            </el-button>
          </el-form-item>
        </el-form>
        <!-- æ‚£è€…列表表格 -->
        <el-table
          v-loading="loading"
          :data="logsheetlist"
          style="width: 100%"
          :border="true"
          class="patient-table"
        >
          <el-table-column
            prop="sendname"
            label="姓名"
            align="center"
            width="100"
          />
          <el-table-column
            prop="taskName"
            label="任务名称"
            align="center"
            width="200"
            show-overflow-tooltip
          />
          <el-table-column
            prop="sendstate"
            label="任务状态"
            align="center"
            width="120"
          >
            <template slot-scope="{ row }">
              <div v-if="row.sendstate == 1">
                <el-tag type="primary" size="small">表单已领取</el-tag>
              </div>
              <div v-if="row.sendstate == 2">
                <el-tag type="primary" size="small">待随访</el-tag>
              </div>
              <div v-if="row.sendstate == 3">
                <el-tag type="success" size="small">表单已发送</el-tag>
              </div>
              <div v-if="row.sendstate == 4">
                <el-tag type="info" size="small">不执行</el-tag>
              </div>
              <div v-if="row.sendstate == 5">
                <el-tag type="danger" size="small">发送失败</el-tag>
              </div>
              <div v-if="row.sendstate == 6">
                <el-tag type="success" size="small">已完成</el-tag>
              </div>
            </template>
          </el-table-column>
          <el-table-column
            prop="visitTime"
            label="应随访时间"
            align="center"
            width="180"
            show-overflow-tooltip
          />
          <el-table-column
            prop="finishtime"
            label="随访完成时间"
            align="center"
            width="180"
            show-overflow-tooltip
          >
            <template slot-scope="{ row }">
              <span v-if="row.finishtime">{{ row.finishtime }}</span>
              <span v-else style="color: #f56c6c">未完成</span>
            </template>
          </el-table-column>
          <el-table-column
            label="出院日期"
            width="120"
            align="center"
            key="endtime"
            prop="endtime"
          >
            <template slot-scope="{ row }">
              <span v-if="row.endtime">{{ formatTime(row.endtime) }}</span>
              <span v-else>-</span>
            </template>
          </el-table-column>
          <el-table-column
            label="责任护士"
            width="120"
            align="center"
            key="nurseName"
            prop="nurseName"
          />
          <el-table-column
            label="主治医生"
            width="120"
            align="center"
            key="drname"
            prop="drname"
          />
          <el-table-column
            label="结果状态"
            align="center"
            key="excep"
            prop="excep"
            width="120"
          >
            <template slot-scope="{ row }">
              <dict-tag
                :options="dict.type.sys_yujing"
                :value="row.excep"
                size="small"
              />
            </template>
          </el-table-column>
          <el-table-column
            label="处理意见"
            align="center"
            key="suggest"
            prop="suggest"
            width="120"
          >
            <template slot-scope="{ row }">
              <dict-tag
                :options="dict.type.sys_suggest"
                :value="row.suggest"
                size="small"
              />
            </template>
          </el-table-column>
          <el-table-column
            prop="templatename"
            label="服务模板"
            width="150"
            align="center"
            show-overflow-tooltip
          />
          <el-table-column
            prop="remark"
            label="服务记录"
            width="150"
            align="center"
            show-overflow-tooltip
          />
          <el-table-column
            prop="bankcardno"
            label="呼叫状态"
            width="120"
            align="center"
          />
          <el-table-column
            label="操作"
            fixed="right"
            align="center"
            width="100"
          >
            <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 class="pagination" v-if="patienttotal > 0">
          <el-pagination
            background
            layout="total, sizes, prev, pager, next, jumper"
            :current-page="patientqueryParams.pn"
            :page-size="patientqueryParams.ps"
            :page-sizes="[10, 20, 30]"
            :total="patienttotal"
            @size-change="handleSizeChange"
            @current-change="handlePageChange"
          />
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { selectTimelyRate } from "@/api/system/user";
export default {
  name: 'SeedetailsDialog',
  dicts: ['sys_yujing', 'sys_suggest'],
  props: {
    rowData: {
      type: Object,
      default: () => ({})
    },
    queryParams: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      // æŸ¥è¯¢å‚æ•°
      patientqueryParams: {
        pn: 1,
        ps: 10,
        name: '',
        leavediagname: ''
      },
      // åŠ è½½çŠ¶æ€
      loading: false,
      // æ‚£è€…列表
      logsheetlist: [],
      // æ€»æ¡æ•°
      patienttotal: 0
    };
  },
  mounted() {
    this.loadData();
  },
  methods: {
    // åŠ è½½æ•°æ®
    async loadData() {
      this.loading = true;
      try {
        const params = {
          ...this.patientqueryParams,
          deptcode: this.rowData.deptcode || '',
          starttime: this.queryParams.dateRange?.[0] ? this.parseTime(this.queryParams.dateRange[0]) : '',
          endtime: this.queryParams.dateRange?.[1] ? this.parseTime(this.queryParams.dateRange[1]) : ''
        };
        const response = await selectTimelyRate(params);
        this.logsheetlist = response.data?.detail || [];
        this.patienttotal = response.data?.total || 0;
      } catch (error) {
        console.error('获取未及时随访详情失败:', error);
        this.$message.error('获取数据失败');
      } finally {
        this.loading = false;
      }
    },
    // å¤„理搜索
    handleSearch() {
      this.patientqueryParams.pn = 1;
      this.loadData();
    },
    // å¤„理重置
    handleReset() {
      this.patientqueryParams = {
        pn: 1,
        ps: 10,
        name: '',
        leavediagname: ''
      };
      this.loadData();
    },
    // å¤„理分页大小变化
    handleSizeChange(size) {
      this.patientqueryParams.ps = size;
      this.patientqueryParams.pn = 1;
      this.loadData();
    },
    // å¤„理页码变化
    handlePageChange(page) {
      this.patientqueryParams.pn = page;
      this.loadData();
    },
    // æ ¼å¼åŒ–æ—¶é—´
    formatTime(time) {
      if (!time) return '-';
      return time;
    },
    // è§£æžæ—¶é—´
    parseTime(time) {
      if (!time) return '';
      return time;
    },
    // æŸ¥çœ‹è¯¦æƒ…
    handleViewDetail(row) {
      this.$emit('close');
      let type = "";
      if (row.preachformson && row.preachformson.includes("3")) {
        type = 1;
      }
      setTimeout(() => {
        this.$router.push({
          path: "/followvisit/record/detailpage/",
          query: {
            taskid: row.taskid,
            patid: row.patid,
            id: row.id,
            Voicetype: type
          }
        });
      }, 300);
    }
  }
};
</script>
<style lang="scss" scoped>
.seedetails-dialog {
  .detail-query-form {
    margin-bottom: 20px;
    padding-bottom: 20px;
    border-bottom: 1px solid #f0f0f0;
    ::v-deep .el-form-item {
      margin-bottom: 0;
      margin-right: 20px;
    }
  }
  .patient-table {
    margin-bottom: 20px;
    ::v-deep .el-table__header-wrapper th {
      background-color: #f8f9fa;
      font-weight: 600;
      color: #333;
    }
  }
  .pagination {
    display: flex;
    justify-content: flex-end;
    padding-top: 20px;
    border-top: 1px solid #f0f0f0;
  }
}
</style>
src/views/Satisfaction/sfstatistics/components/components/TopicDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,151 @@
<template>
  <div class="topic-dialog">
    <div class="topicdia">
      <div style="overflow-x: hidden; overflow-y: auto; max-height: 65vh">
        <div
          v-for="(item, index) in topiclist"
          :key="index"
          class="ttaabbcc"
        >
          <div class="describe">
            ç¬¬{{ index + 1 }}题: {{ item.scriptContent }}?
            <span>[{{ item.scriptType == 1 ? "单选题" : "多选题" }}]</span>
          </div>
          <div>
            <el-table :data="item.details" style="width: 100%">
              <el-table-column
                prop="optionText"
                label="问题选项"
                align="center"
                min-width="200"
              />
              <el-table-column
                prop="chosenQuantity"
                label="选择人数"
                align="center"
                min-width="120"
              />
              <el-table-column
                prop="chosenPercentage"
                label="比例"
                align="center"
                min-width="120"
              >
                <template slot-scope="{ row }">
                  <span v-if="row.chosenPercentage !== null && row.chosenPercentage !== undefined">
                    {{ formatPercent(row.chosenPercentage) }}
                  </span>
                  <span v-else>-</span>
                </template>
              </el-table-column>
            </el-table>
          </div>
        </div>
      </div>
    </div>
    <div slot="footer" class="dialog-footer" style="text-align: center; padding-top: 20px;">
      <el-button @click="handleClose">关 é—­</el-button>
    </div>
  </div>
</template>
<script>
export default {
  name: 'TopicDialog',
  props: {
    rowData: {
      type: Object,
      default: () => ({})
    },
    queryParams: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      topiclist: []
    };
  },
  mounted() {
    this.loadData();
  },
  methods: {
    // åŠ è½½æ•°æ®
    async loadData() {
      try {
        // è¿™é‡Œä»Žçˆ¶ç»„件传递数据,不需要重新调用API
        this.topiclist = this.$parent.topiclist || [];
      } catch (error) {
        console.error('加载题目详情失败:', error);
        this.$message.error('加载题目详情失败');
      }
    },
    // æ ¼å¼åŒ–百分比
    formatPercent(value) {
      if (value === null || value === undefined) return '-';
      const num = parseFloat(value);
      if (isNaN(num)) return '-';
      return `${(num * 100).toFixed(2)}%`;
    },
    // å…³é—­å¯¹è¯æ¡†
    handleClose() {
      this.$emit('close');
    }
  }
};
</script>
<style lang="scss" scoped>
.topic-dialog {
  .topicdia {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
      "Helvetica Neue", Arial, sans-serif;
    color: #333;
  }
  .ttaabbcc {
    background: #fafafa;
    border-radius: 6px;
    padding: 16px;
    margin-bottom: 20px;
    border-left: 4px solid #4794c5;
  }
  .describe {
    font-size: 15px;
    line-height: 1.6;
    margin-bottom: 12px;
    color: #1f2d3d;
  }
  .describe span {
    font-size: 13px;
    color: #999;
    font-style: italic;
    margin-left: 8px;
  }
  ::v-deep .el-table {
    border-radius: 4px;
    overflow: hidden;
    font-size: 14px;
  }
  ::v-deep .el-table th {
    background-color: #f1f5f9;
    color: #333;
    font-weight: 600;
  }
  ::v-deep .el-table td {
    border-bottom: 1px solid #f0f0f0;
    padding: 12px 0;
  }
}
</style>
src/views/Satisfaction/sfstatistics/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
<!-- StatisticsMain.vue -->
<template>
  <div class="statistics-main">
    <el-tabs v-model="activeTab" @tab-click="handleTabChange">
      <el-tab-pane label="随访统计" name="followup">
        <followup-statistics
          v-if="activeTab === 'followup'"
          ref="followupRef"
        />
      </el-tab-pane>
      <el-tab-pane label="满意度统计" name="satisfaction">
        <satisfaction-statistics
          v-if="activeTab === 'satisfaction'"
          ref="satisfactionRef"
        />
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
<script>
import FollowupStatistics from './components/FollowupStatistics.vue';
import SatisfactionStatistics from './components/SatisfactionStatistics.vue';
export default {
  name: 'StatisticsMain',
  components: {
    FollowupStatistics,
    SatisfactionStatistics
  },
  data() {
    return {
      activeTab: 'followup'
    };
  },
  methods: {
    handleTabChange(tab) {
      console.log('切换到:', tab.name);
    }
  }
};
</script>
<style lang="scss" scoped>
.statistics-main {
  padding: 20px;
  background: #fff;
  min-height: calc(100vh - 84px);
  ::v-deep .el-tabs__header {
    margin-bottom: 20px;
  }
  ::v-deep .el-tabs__item {
    font-size: 16px;
    font-weight: 500;
  }
  ::v-deep .el-tabs__nav-wrap::after {
    height: 1px;
  }
}
</style>
src/views/followvisit/Continue/index.vue
@@ -354,9 +354,14 @@
                  >发送失败</el-tag
                >
              </div>
              <div v-if="scope.row.sendstate == 6">
            <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>
@@ -411,11 +416,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -1183,7 +1188,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -1191,6 +1196,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      sextype: [
        {
src/views/followvisit/HistoricalFollow/index.vue
@@ -201,11 +201,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -662,7 +662,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -670,6 +670,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      topicoptionsyj: [
        {
src/views/followvisit/OutpatientAgain/index.vue
@@ -180,7 +180,7 @@
        <el-col :span="1.5">
          <el-button
            type="primary"
                        icon="el-icon-plus"
            icon="el-icon-plus"
            size="medium"
            @click="handleAdd"
            >新增</el-button
@@ -325,9 +325,14 @@
                  >发送失败</el-tag
                >
              </div>
              <div v-if="scope.row.sendstate == 6">
            <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>
@@ -382,11 +387,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -644,7 +649,7 @@
            </el-form-item>
          </el-col>
        </el-row>
<el-row >
        <el-row>
          <el-col :span="8">
            <el-form-item label="过滤医生" width="100" prop="filterDrname">
              <el-input
@@ -1002,7 +1007,7 @@
        visitCount: 2,
        scopetype: [],
        visitDeptCodes: [],
        leaveldeptcodes:[],
        // leaveldeptcodes:[],
        leavehospitaldistrictcodes: [],
      },
      propss: { multiple: true },
@@ -1029,13 +1034,17 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
        {
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      sextype: [
@@ -1141,9 +1150,9 @@
        this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leaveldeptcodes = 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);
      }
@@ -1157,9 +1166,9 @@
      this.loading = true;
      if (
        this.topqueryParams.leavehospitaldistrictcodes[0] &&
        this.topqueryParams.visitDeptCodes[0]&&this.topqueryParams.leaveldeptcodes[0]
        this.topqueryParams.visitDeptCodes[0]
      ) {
        this.topqueryParams.deptOrDistrict = 2;
        this.topqueryParams.deptOrDistrict = 4;
      } else {
        this.topqueryParams.deptOrDistrict = 1;
      }
@@ -1204,7 +1213,7 @@
      });
    },
    affiliation() {
      this.topqueryParams.managementDoctorCode= store.getters.hisUserId;
      this.topqueryParams.managementDoctorCode = store.getters.hisUserId;
      this.getList(1);
    },
@@ -1222,9 +1231,9 @@
        this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
          this.topqueryParams.leaveldeptcodes = 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);
      }
@@ -1241,7 +1250,7 @@
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        visitDeptCodes: this.topqueryParams.visitDeptCodes,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
        // leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
        this.userList = response.rows[0].serviceSubtaskList;
@@ -1341,9 +1350,9 @@
        this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leaveldeptcodes = 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);
      }
@@ -1361,16 +1370,16 @@
      let code = value.slice(-1)[0];
      this.topqueryParams.leavehospitaldistrictcodes = [];
      this.topqueryParams.visitDeptCodes = [];
      this.topqueryParams.leaveldeptcodes = [];
      // this.topqueryParams.leaveldeptcodes = [];
      if (type == 1) {
        this.topqueryParams.visitDeptCodes.push(code);
        this.topqueryParams.leaveldeptcodes.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.leaveldeptcodes = [];
        this.topqueryParams.searchscope = 2;
      } else {
        this.topqueryParams.searchscope = 3;
@@ -1390,7 +1399,7 @@
        visitCount: 2,
        scopetype: [],
        visitDeptCodes: [],
        leaveldeptcodes:[],
        // leaveldeptcodes:[],
        leavehospitaldistrictcodes: [],
      };
      this.handleQuery(1);
@@ -1478,7 +1487,7 @@
            .then((response) => {
              console.log(response);
            })
              .then(() => {
            .then(() => {
              this.getList(1);
              this.$modal.msgSuccess("患者过滤成功");
            });
@@ -1547,11 +1556,11 @@
    },
    // è·³è½¬è¯¦æƒ…页
    Seedetails(row) {
    let type = "";
      let type = "";
      console.log(row, "rwo");
        if (row.type == 1) {
          type = 1;
        }
      if (row.type == 1) {
        type = 1;
      }
      this.$router.push({
        path: "/followvisit/record/detailpage/",
        query: {
@@ -1777,11 +1786,11 @@
  }
}
::v-deep.leftvlue .el-card__body {
  background: #F2F8FF;
  color: #324A9B;
  background: #f2f8ff;
  color: #324a9b;
}
::v-deep.leftvlue .el-card__body:hover {
  background: #3664D9;
  background: #3664d9;
  color: #fff;
  cursor: pointer; /* é¼ æ ‡æ‚¬æµ®æ—¶å˜ä¸ºæ‰‹å½¢ */
}
src/views/followvisit/SpecificDisease/index.vue
@@ -324,11 +324,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -892,7 +892,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -900,6 +900,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      topicoptionsyj: [
        {
src/views/followvisit/Tracking/index.vue
@@ -264,9 +264,14 @@
                  >发送失败</el-tag
                >
              </div>
              <div v-if="scope.row.sendstate == 6">
            <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>
@@ -321,11 +326,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -968,7 +973,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -976,6 +981,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      sextype: [
        {
src/views/followvisit/again/index.vue
@@ -325,9 +325,14 @@
                  >发送失败</el-tag
                >
              </div>
              <div v-if="scope.row.sendstate == 6">
            <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>
@@ -382,11 +387,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -1003,7 +1008,7 @@
        visitCount: 2,
        scopetype: [],
        visitDeptCodes: [],
        leaveldeptcodes:[],
        // leaveldeptcodes:[],
        leavehospitaldistrictcodes: [],
      },
      propss: { multiple: true },
@@ -1030,13 +1035,17 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
        {
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      sextype: [
@@ -1142,9 +1151,9 @@
        this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leaveldeptcodes = 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);
      }
@@ -1158,9 +1167,9 @@
      this.loading = true;
      if (
        this.topqueryParams.leavehospitaldistrictcodes[0] &&
        this.topqueryParams.visitDeptCodes[0]&&this.topqueryParams.leaveldeptcodes[0]
        this.topqueryParams.visitDeptCodes[0]
      ) {
        this.topqueryParams.deptOrDistrict = 2;
        this.topqueryParams.deptOrDistrict = 4;
      } else {
        this.topqueryParams.deptOrDistrict = 1;
      }
@@ -1223,9 +1232,9 @@
        this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
          this.topqueryParams.leaveldeptcodes = 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);
      }
@@ -1242,7 +1251,7 @@
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        visitDeptCodes: this.topqueryParams.visitDeptCodes,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
        // leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
        this.userList = response.rows[0].serviceSubtaskList;
@@ -1342,9 +1351,9 @@
        this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leaveldeptcodes = 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);
      }
@@ -1362,16 +1371,16 @@
      let code = value.slice(-1)[0];
      this.topqueryParams.leavehospitaldistrictcodes = [];
      this.topqueryParams.visitDeptCodes = [];
      this.topqueryParams.leaveldeptcodes = [];
      // this.topqueryParams.leaveldeptcodes = [];
      if (type == 1) {
        this.topqueryParams.visitDeptCodes.push(code);
        this.topqueryParams.leaveldeptcodes.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.leaveldeptcodes = [];
        this.topqueryParams.searchscope = 2;
      } else {
        this.topqueryParams.searchscope = 3;
@@ -1391,7 +1400,7 @@
        visitCount: 2,
        scopetype: [],
        visitDeptCodes: [],
        leaveldeptcodes:[],
        // leaveldeptcodes:[],
        leavehospitaldistrictcodes: [],
      };
      this.handleQuery(1);
src/views/followvisit/complaint/index.vue
@@ -335,9 +335,14 @@
                  >发送失败</el-tag
                >
              </div>
              <div v-if="scope.row.sendstate == 6">
            <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>
@@ -1123,7 +1128,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -1131,6 +1136,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      sextype: [
        {
src/views/followvisit/discharge/index.vue
@@ -405,6 +405,11 @@
                  >已完成</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>
@@ -457,13 +462,14 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <!-- åŽŸ -->
        <el-table-column
          label="主治医生"
          width="120"
@@ -1249,7 +1255,8 @@
      topqueryParams: {
        pageNum: 1,
        pageSize: 10,
        sendstate: 2,
        sendstate:
          localStorage.getItem("orgname") == "省立同德翠苑院区" ? null : 2,
        sort: localStorage.getItem("orgname") == "丽水市中医院" ? 8 : 2, //0 å‡ºé™¢æ—¶é—´(正序)    1 å‡ºé™¢æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)  7应随访日期(倒序) åº”随访日期(正序)
        serviceType: 2,
        searchscope: 3,
@@ -1290,6 +1297,10 @@
        {
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      sextype: [
@@ -1359,12 +1370,25 @@
      rules: {},
    };
  },
  watch: {},
  watch: {
    // ç›‘听路由参数变化
    "$route.query": {
      handler(newQuery, oldQuery) {
        if (newQuery.errtype !== oldQuery.errtype) {
          console.log(22);
          this.loadData(); // é‡æ–°åŠ è½½æ•°æ®
        }
      },
      immediate: true,
    },
  },
  created() {
    this.serviceState = store.getters.serviceState;
    this.checkboxlist = store.getters.checkboxlist;
    this.errtype = this.$route.query.errtype;
    this.orgname = localStorage.getItem("orgname");
    this.errtype = this.$route.query.errtype;
    this.leavehospitaldistrictcode =
      this.$route.query.leavehospitaldistrictcode;
    this.sourcetype[0].children = store.getters.belongDepts.map((dept) => {
@@ -1379,8 +1403,20 @@
        value: dept.districtCode,
      };
    });
    if (this.errtype) {
    if (this.errtype == 1) {
      this.toleadExport(2);
    } else if (this.errtype == 2) {
      // å¾…随访
      this.toleadExport(3);
    } else if (this.errtype == 3) {
      // å¤±è´¥
      this.toleadExport(4);
    } else if (this.errtype == 4) {
      // å¼‚常
      this.toleadExport(2);
    } else if (this.errtype == 5) {
      // å…¨éƒ¨
      this.toleadExport(5);
    } else {
      this.getList(1);
    }
@@ -1389,7 +1425,24 @@
    });
  },
  activated() {
    this.getList(1);
    this.errtype = this.$route.query.errtype;
    if (this.errtype == 1) {
      this.toleadExport(2);
    } else if (this.errtype == 2) {
      // å¾…随访
      this.toleadExport(3);
    } else if (this.errtype == 3) {
      // å¤±è´¥
      this.toleadExport(4);
    } else if (this.errtype == 4) {
      // å¼‚常
      this.toleadExport(2);
    } else if (this.errtype == 5) {
      // å…¨éƒ¨
      this.toleadExport(5);
    } else {
      this.getList(1);
    }
  },
  methods: {
    /** æŸ¥è¯¢éšè®¿æœåŠ¡åˆ—è¡¨ */
@@ -1414,7 +1467,6 @@
        this.topqueryParams.leavehospitaldistrictcodes.push(
          this.leavehospitaldistrictcode
        );
        console.log(this.topqueryParams.leavehospitaldistrictcodes, "11");
      }
      this.loading = true;
      if (
@@ -1430,7 +1482,9 @@
        this.total = response.total;
        if (refresh) {
          this.cardlist[0].value =
            Number(response.rows[0].wzx) + Number(response.rows[0].ysf);
            Number(response.rows[0].wzx) +
            Number(response.rows[0].ysf) +
            Number(response.rows[0].fssb);
          // this.cardlist[1].value = response.rows[0].wzx;
          this.cardlist[1].value = response.rows[0].ysf;
          this.ycvalue = response.rows[0].yc;
@@ -1465,6 +1519,26 @@
        });
        this.total = response.total;
      });
    },
    loadData() {
      this.errtype = this.$route.query.errtype;
      if (this.errtype == 1) {
        this.toleadExport(2);
      } else if (this.errtype == 2) {
        // å¾…随访
        this.toleadExport(3);
      } else if (this.errtype == 3) {
        // å¤±è´¥
        this.toleadExport(4);
      } else if (this.errtype == 4) {
        // å¼‚常
        this.toleadExport(2);
      } else if (this.errtype == 5) {
        // å…¨éƒ¨
        this.toleadExport(5);
      } else {
        this.getList(1);
      }
    },
    // æ—¶é—´
    getEndOfDay() {
@@ -1618,10 +1692,13 @@
          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];
      // åˆ¤æ–­æ˜¯ä¸æ˜¯å·¥ä½œå°å¿«æ·æŸ¥è¯¢
      if (this.errtype != 2) {
        this.topqueryParams.startOutHospTime = this.dateRange[0];
        this.topqueryParams.endOutHospTime = this.dateRange[1];
        this.topqueryParams.startSendDateTime = this.dateRangefs[0];
        this.topqueryParams.endSendDateTime = this.dateRangefs[1];
      }
      this.getList(refresh);
    },
    // æ‚£è€…范围处理
@@ -1929,11 +2006,31 @@
    },
    // ä¾¿æ·æŒ‰é’®
    toleadExport(too) {
      console.log(too, "too");
      if (too == 1) {
        this.topqueryParams.sendstate = 4;
        this.topqueryParams.excep = null;
      } else if (too == 2) {
        this.topqueryParams.excep = 1;
        this.topqueryParams.sendstate = null;
      } else if (too == 3) {
        this.topqueryParams.endSendDateTime = this.formatDateToYYYYMMDDHHMMSS(
          this.getEndOfDay()
        );
        console.log(1111, this.topqueryParams.endSendDateTime);
        this.topqueryParams.excep = null;
        this.topqueryParams.sendstate = 2;
        this.topqueryParams.scopetype = null;
      } else if (too == 4) {
        this.topqueryParams.excep = null;
        this.topqueryParams.sendstate = 5;
        this.topqueryParams.scopetype = null;
      } else if (too == 5) {
        this.topqueryParams.excep = null;
        this.topqueryParams.sendstate = null;
        this.topqueryParams.scopetype = null;
      }
      this.handleQuery(1);
    },
src/views/followvisit/discharge/outpatientService.vue
@@ -331,9 +331,14 @@
                  >发送失败</el-tag
                >
              </div>
              <div v-if="scope.row.sendstate == 6">
            <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>
@@ -389,11 +394,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -1016,7 +1021,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -1024,6 +1029,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      sextype: [
        {
src/views/followvisit/mzsatisfaction/index.vue
@@ -334,9 +334,14 @@
                  >发送失败</el-tag
                >
              </div>
              <div v-if="scope.row.sendstate == 6">
            <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>
@@ -1167,7 +1172,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -1175,6 +1180,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      sextype: [
        {
src/views/followvisit/outpatient/index.vue
@@ -334,11 +334,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -803,7 +803,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -811,6 +811,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      topicoptionsyj: [
        {
src/views/followvisit/record/detailpage/index.vue
@@ -128,6 +128,11 @@
                  >已完成</el-tag
                >
              </div>
              <div v-if="scope.row.sendstate == 7">
                <el-tag type="danger" :disable-transitions="false"
                  >超时</el-tag
                >
              </div>
            </template>
          </el-table-column>
          <el-table-column
@@ -2449,7 +2454,9 @@
          form.finishtime = "";
          if (form.resource) {
            if (form.resource == 2) {
              form.visitDeptCode = localStorage.getItem("deptCode");
              form.visitDeptCode = localStorage.getItem("deptCode")
                ? localStorage.getItem("deptCode")
                : form.deptcode;
              form.visitDeptName = "随访中心";
            } else {
              form.visitDeptCode = form.deptcode;
src/views/followvisit/record/index.vue
@@ -372,11 +372,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -968,7 +968,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -976,6 +976,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      topicoptionsyj: [
        {
src/views/followvisit/technology/index.vue
@@ -337,11 +337,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -905,7 +905,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -913,6 +913,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      topicoptionsyj: [
        {
src/views/followvisit/zbAgain/index.vue
@@ -325,9 +325,14 @@
                  >发送失败</el-tag
                >
              </div>
              <div v-if="scope.row.sendstate == 6">
            <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>
@@ -382,11 +387,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -1019,7 +1024,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -1027,6 +1032,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      sextype: [
        {
src/views/followvisit/zysatisfaction/index.vue
@@ -333,9 +333,14 @@
                  >发送失败</el-tag
                >
              </div>
              <div v-if="scope.row.sendstate == 6">
            <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>
@@ -1126,7 +1131,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -1134,6 +1139,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      sextype: [
        {
src/views/login-sy.vue
@@ -36,7 +36,7 @@
          />
        </el-input>
      </el-form-item>
      <!-- ä¸½===================æ°´ -->
      <!-- å¸‚一===================一 -->
      <el-form-item prop="medicalCode">
        <el-select
          style="width: 100%"
src/views/patient/patient/indexls.vue
@@ -143,7 +143,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -151,6 +151,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      topicoptionsyj: [
        {
src/views/patient/physical/index.vue
@@ -334,11 +334,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -803,7 +803,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -811,6 +811,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      topicoptionsyj: [
        {
src/views/patient/propaganda/index.vue
@@ -776,7 +776,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -784,6 +784,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      topicoptionsyj: [
        {
src/views/patient/questionnaire/index.vue
@@ -303,11 +303,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -789,7 +789,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -797,6 +797,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      topicoptionsyj: [
        {
src/views/patient/shadow/index.vue
@@ -303,11 +303,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -789,7 +789,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -797,6 +797,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      topicoptionsyj: [
        {
src/views/patient/subsequent/index.vue
@@ -334,11 +334,11 @@
          label="应随访日期"
          width="200"
          align="center"
          key="longSendTime"
          prop="longSendTime"
          key="visitTime"
          prop="visitTime"
        >
          <template slot-scope="scope">
            <span>{{ formatTime(scope.row.longSendTime) }}</span>
            <span>{{ formatTime(scope.row.visitTime) }}</span>
          </template></el-table-column
        >
        <el-table-column
@@ -803,7 +803,7 @@
          value: 4,
          label: "不执行",
        },
        {
         {
          value: 5,
          label: "发送失败",
        },
@@ -811,6 +811,10 @@
          value: 6,
          label: "已完成",
        },
         {
          value: 7,
          label: "超时",
        },
      ],
      topicoptionsyj: [
        {
src/views/sfstatistics/percentage/index.vue
@@ -356,6 +356,7 @@
                  prop="leavehospitaldistrictname"
                  width="150"
                  :show-overflow-tooltip="true"
                  :sort-method="sortChineseNumber"
                />
                <el-table-column
                  label="科室"
@@ -1508,13 +1509,48 @@
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      getSfStatistics(params).then((response) => {
        console.log(response);
        this.loading = false;
        // this.total = response.total;
        this.userList = this.customSort(response.data);
      });
    },
    sortChineseNumber(a, b) {
      // æå–中文数字
      const chineseNumbers = [
        "一",
        "二",
        "三",
        "四",
        "五",
        "六",
        "七",
        "八",
        "九",
        "十",
        "十一",
        "十二",
      ];
      // ä»Žå­—符串中提取病区数字,如"四病区" -> "四"
      const getNumberFromText = (text) => {
        if (!text) return -1;
        const match = text.match(/^([一二三四五六七八九十]+)/);
        if (match && match[1]) {
          return chineseNumbers.indexOf(match[1]);
        }
        return -1;
      };
      const numA = getNumberFromText(a);
      const numB = getNumberFromText(b);
      if (numA === -1 && numB === -1) return 0;
      if (numA === -1) return 1; // æ— æ³•解析的放到后面
      if (numB === -1) return -1; // æ— æ³•解析的放到后面
      return numA - numB;
    },
    // æœç´¢å¤„理函数
    handleSearch() {
      if (!this.searchName.trim()) {
src/views/sfstatistics/percentage/satisfaction.vue
@@ -552,12 +552,7 @@
</template>
<script>
import {
  toamendtag,
  addapitag,
  deletetag,
  changetagcategory,
} from "@/api/system/label";
import store from "@/store";
import {
  getSfStatisticsJoy,
@@ -773,12 +768,14 @@
          ? this.allDeptCodes
          : this.queryParams.deptcodes,
      };
      this.loading = true;
      // ç§»é™¤å¯èƒ½å­˜åœ¨çš„"all"值
      delete params.leavehospitaldistrictcodes.all;
      delete params.deptcodes.all;
      getSfStatisticsJoy(params).then((response) => {
        console.log(response);
        this.loading = false;
        this.total = response.total;
        this.userList = response.data;
      });
@@ -921,27 +918,6 @@
        this.topiclist = response.data;
      });
    },
    // æ·»åŠ /修改标签
    Maintenancetag() {
      if (this.lstamendtag) {
        toamendtag(this.addDateRange(this.tagform)).then((response) => {
          console.log(response);
          this.getList();
        });
      } else {
        addapitag(this.addDateRange(this.tagform)).then((response) => {
          console.log(response);
          this.getList();
        });
      }
      this.tagform = {
        isupload: "",
        tagname: "",
        tagcategoryid: "",
        tagdescription: "",
        tagid: "",
      };
    },
    routerErr(row) {
      console.log(row, "跳转异常");
      this.$router.push({
@@ -971,22 +947,7 @@
      };
      this.resetForm("form");
    },
    // æ ‡ç­¾çŠ¶æ€ä¿®æ”¹
    handleStatusChange(row) {
      console.log(row.isupload);
      let text = row.isupload === "0" ? "启用" : "停用";
      this.$modal
        .confirm('确认要"' + text + '""' + row.tagname + '"标签吗?')
        .then(function () {
          return changetagcategory(row.tagid, row.isupload);
        })
        .then(() => {
          this.$modal.msgSuccess(text + "成功");
        })
        .catch(function () {
          row.isupload = row.isupload === "0" ? "1" : "0";
        });
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ */
    handleQuery() {
      this.queryParams.pageNum = 1;
@@ -1017,27 +978,7 @@
      this.multiple = !selection.length;
    },
    /** åˆ é™¤æŒ‰é’®æ“ä½œ */
    handleDelete(row) {
      console.log(row, "删除弹窗");
      const tagids = row.tagid || this.ids;
      console.log(tagids);
      const tagname = row.tagname;
      this.$modal
        .confirm(
          tagname
            ? '是否确认删除标签名称为"' + tagname + '"的数据项?'
            : "是否确认删除选中的数据项?"
        )
        .then(function () {
          return deletetag(tagids);
        })
        .then(() => {
          this.getList();
          this.$modal.msgSuccess("删除成功");
        })
        .catch(() => {});
    },
    /** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
    /** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
    async handleExport() {
src/views/system/user/index.vue
@@ -907,7 +907,7 @@
            updateUser(this.form).then((response) => {
              this.$modal.msgSuccess("修改成功");
              this.open = false;
              this.adduserdept();
              // this.adduserdept();
            });
          } else {
            addUser(this.form).then((response) => {
@@ -916,7 +916,7 @@
              this.form.userId = response.data;
              console.log("开始加部门");
              this.adduserdept();
              // this.adduserdept();
            });
          }
        }
vue.config.js
@@ -37,9 +37,9 @@
      [process.env.VUE_APP_BASE_API]: {
        // target: `https://www.health-y.cn/lssf`,
        // target: `http://192.168.100.10:8096`,
        // target: `http://192.168.100.10:8094`,//省立同德
        target: `http://192.168.100.10:8094`,//省立同德
        // target: `http://192.168.100.10:8095`,//新华
        target:`http://localhost:8095`,
        // target:`http://localhost:8095`,
        // target:`http://35z1t16164.qicp.vip`,
        // target: `http://192.168.100.172:8095`,
        // target: `http://192.168.101.166:8093`,