| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- <template>
- <!-- 悬浮按钮 - 任务中心入口 -->
- <div class="task-fab" :class="{ 'has-running': runningCount > 0, 'attention': attention }" @click="handleClick">
- <div class="fab-icon">
- <el-icon :size="24"><List /></el-icon>
- </div>
- <span v-if="runningCount > 0" class="fab-badge">{{ runningCount }}</span>
- <div class="fab-ripple" v-if="attention"></div>
- </div>
- </template>
- <script setup>
- import { computed, ref, watch } from 'vue'
- import { List } from '@element-plus/icons-vue'
- import { useTaskCenterStore } from '@/stores/taskCenter'
- const store = useTaskCenterStore()
- const attention = ref(false)
- const runningCount = computed(() => store.runningCount)
- function handleClick() {
- store.toggleOpen()
- }
- // 当有新任务开始时,显示注意动画
- watch(() => store.runningTotal, (newVal, oldVal) => {
- if (newVal > oldVal) {
- attention.value = true
- setTimeout(() => {
- attention.value = false
- }, 2000)
- }
- })
- </script>
- <style scoped>
- .task-fab {
- position: fixed;
- right: 24px;
- bottom: 24px;
- width: 56px;
- height: 56px;
- border-radius: 50%;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- transition: all 0.3s ease;
- z-index: 3999;
- }
- .task-fab:hover {
- transform: scale(1.1);
- box-shadow: 0 6px 28px rgba(102, 126, 234, 0.5);
- }
- .task-fab.has-running {
- background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
- box-shadow: 0 4px 20px rgba(79, 172, 254, 0.4);
- animation: pulse 2s infinite;
- }
- .task-fab.attention {
- animation: bounce 0.5s ease-in-out;
- }
- .fab-icon {
- color: #fff;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .fab-badge {
- position: absolute;
- top: -4px;
- right: -4px;
- min-width: 20px;
- height: 20px;
- padding: 0 6px;
- border-radius: 10px;
- background: #f56c6c;
- color: #fff;
- font-size: 12px;
- font-weight: 600;
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 2px 8px rgba(245, 108, 108, 0.4);
- }
- .fab-ripple {
- position: absolute;
- width: 100%;
- height: 100%;
- border-radius: 50%;
- border: 2px solid rgba(255, 255, 255, 0.6);
- animation: ripple 1s ease-out infinite;
- }
- @keyframes pulse {
- 0%, 100% {
- box-shadow: 0 4px 20px rgba(79, 172, 254, 0.4);
- }
- 50% {
- box-shadow: 0 4px 30px rgba(79, 172, 254, 0.6);
- }
- }
- @keyframes bounce {
- 0%, 100% {
- transform: scale(1);
- }
- 25% {
- transform: scale(1.2);
- }
- 50% {
- transform: scale(0.95);
- }
- 75% {
- transform: scale(1.1);
- }
- }
- @keyframes ripple {
- 0% {
- transform: scale(1);
- opacity: 1;
- }
- 100% {
- transform: scale(1.5);
- opacity: 0;
- }
- }
- </style>
|