TaskCenterFab.vue 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. <template>
  2. <!-- 悬浮按钮 - 任务中心入口 -->
  3. <div class="task-fab" :class="{ 'has-running': runningCount > 0, 'attention': attention }" @click="handleClick">
  4. <div class="fab-icon">
  5. <el-icon :size="24"><List /></el-icon>
  6. </div>
  7. <span v-if="runningCount > 0" class="fab-badge">{{ runningCount }}</span>
  8. <div class="fab-ripple" v-if="attention"></div>
  9. </div>
  10. </template>
  11. <script setup>
  12. import { computed, ref, watch } from 'vue'
  13. import { List } from '@element-plus/icons-vue'
  14. import { useTaskCenterStore } from '@/stores/taskCenter'
  15. const store = useTaskCenterStore()
  16. const attention = ref(false)
  17. const runningCount = computed(() => store.runningCount)
  18. function handleClick() {
  19. store.toggleOpen()
  20. }
  21. // 当有新任务开始时,显示注意动画
  22. watch(() => store.runningTotal, (newVal, oldVal) => {
  23. if (newVal > oldVal) {
  24. attention.value = true
  25. setTimeout(() => {
  26. attention.value = false
  27. }, 2000)
  28. }
  29. })
  30. </script>
  31. <style scoped>
  32. .task-fab {
  33. position: fixed;
  34. right: 24px;
  35. bottom: 24px;
  36. width: 56px;
  37. height: 56px;
  38. border-radius: 50%;
  39. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  40. box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
  41. display: flex;
  42. align-items: center;
  43. justify-content: center;
  44. cursor: pointer;
  45. transition: all 0.3s ease;
  46. z-index: 3999;
  47. }
  48. .task-fab:hover {
  49. transform: scale(1.1);
  50. box-shadow: 0 6px 28px rgba(102, 126, 234, 0.5);
  51. }
  52. .task-fab.has-running {
  53. background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  54. box-shadow: 0 4px 20px rgba(79, 172, 254, 0.4);
  55. animation: pulse 2s infinite;
  56. }
  57. .task-fab.attention {
  58. animation: bounce 0.5s ease-in-out;
  59. }
  60. .fab-icon {
  61. color: #fff;
  62. display: flex;
  63. align-items: center;
  64. justify-content: center;
  65. }
  66. .fab-badge {
  67. position: absolute;
  68. top: -4px;
  69. right: -4px;
  70. min-width: 20px;
  71. height: 20px;
  72. padding: 0 6px;
  73. border-radius: 10px;
  74. background: #f56c6c;
  75. color: #fff;
  76. font-size: 12px;
  77. font-weight: 600;
  78. display: flex;
  79. align-items: center;
  80. justify-content: center;
  81. box-shadow: 0 2px 8px rgba(245, 108, 108, 0.4);
  82. }
  83. .fab-ripple {
  84. position: absolute;
  85. width: 100%;
  86. height: 100%;
  87. border-radius: 50%;
  88. border: 2px solid rgba(255, 255, 255, 0.6);
  89. animation: ripple 1s ease-out infinite;
  90. }
  91. @keyframes pulse {
  92. 0%, 100% {
  93. box-shadow: 0 4px 20px rgba(79, 172, 254, 0.4);
  94. }
  95. 50% {
  96. box-shadow: 0 4px 30px rgba(79, 172, 254, 0.6);
  97. }
  98. }
  99. @keyframes bounce {
  100. 0%, 100% {
  101. transform: scale(1);
  102. }
  103. 25% {
  104. transform: scale(1.2);
  105. }
  106. 50% {
  107. transform: scale(0.95);
  108. }
  109. 75% {
  110. transform: scale(1.1);
  111. }
  112. }
  113. @keyframes ripple {
  114. 0% {
  115. transform: scale(1);
  116. opacity: 1;
  117. }
  118. 100% {
  119. transform: scale(1.5);
  120. opacity: 0;
  121. }
  122. }
  123. </style>