From 0c7cc21d8a51e164dd2fe4ce73ab566b3a9081a9 Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期四, 26 三月 2026 10:25:05 +0800
Subject: [PATCH] 测试完成

---
 src/components/Assistant/index.vue | 1139 ++++++++++++++++++++++++++++++++++++++---------------------
 1 files changed, 731 insertions(+), 408 deletions(-)

diff --git a/src/components/Assistant/index.vue b/src/components/Assistant/index.vue
index 7328736..2ee2946 100644
--- a/src/components/Assistant/index.vue
+++ b/src/components/Assistant/index.vue
@@ -1,486 +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)"
+    <!-- 涓荤悆浣� -->
+    <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: 700,
-    },
-    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/涓嬭浇.png"),
-          label: "杩炴帴CTI",
+          id: "sms",
+          label: "鍒涘缓闂嵎浠诲姟",
+          icon: "IconMessageCircle",
+          url: "/followvisit/QuestionnaireTask?type=2&serviceType=2",
         },
         {
-          path: require("@/assets/images/涓嬭浇.png"),
-          label: "绛惧叆",
+          id: "call",
+          label: "鍒涘缓璇煶浠诲姟",
+          icon: "IconPhone",
+          url: "/followvisit/particty?type=1&serviceType=2",
         },
-        {
-          path: require("@/assets/images/涓嬭浇.png"),
-          label: "绛惧嚭",
-        },
-        {
-          path: require("@/assets/images/涓嬭浇.png"),
-          label: "缃棽",
-        },
-        {
-          path: require("@/assets/images/涓嬭浇.png"),
-          label: "缃繖",
-        },
-        {
-          path: require("@/assets/images/涓嬭浇.png"),
-          label: "鎷ㄥ彿",
-        },
-        {
-          path: require("@/assets/images/涓嬭浇.png"),
-          label: "鍜ㄨ",
-        },
-        {
-          path: require("@/assets/images/涓嬭浇.png"),
-          label: "淇濇寔",
-        },
-        {
-          path: require("@/assets/images/涓嬭浇.png"),
-          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", 700);
-      } else {
-        this.buffer(this.box, "height", 70);
-      }
-      this.flag = !this.flag;
-      console.log("鏄惁灞曞紑", this.flag);
-    },
-    // 鐐瑰嚮鍝釜power
-    activeHandle(index) {
-      //鎶婃垜浠嚜瀹氫箟鐨勪笅鏍囪祴鍊�
-      this.activeIndex = index;
-      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();
-    },
-    /**
-     * 鍒濆鍖杁raggable
-     */
-    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; // 瑙e喅鐐瑰嚮浜嬩欢鍜宼ouch浜嬩欢鍐茬獊鐨勯棶棰�
-      this.floatDrag.style.transition = "all 0.3s";
-      this.checkDraggablePosition();
-    },
-    /**
-     * 鍒ゆ柇鍏冪礌鏄剧ず浣嶇疆
-     * 鍦ㄧ獥鍙f敼鍙樺拰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: 600px;
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      flex-direction: column;
-
-      .power-item {
-        width: 40px;
-        height: 40px;
-        border-radius: 50%;
-        background-color: #69707a;
-        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: #1a1818 !important;
+.action-item:hover {
+  background: #f8fafc;
 }
-.active-des {
-  color: #1a1818 !important;
-  font-weight: bold !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>

--
Gitblit v1.9.3