moki 4 дней назад
Родитель
Сommit
670c3883a2
2 измененных файлов с 89 добавлено и 1 удалено
  1. 86 0
      src/components/BusyCard.vue
  2. 3 1
      src/views/Dashboard.vue

+ 86 - 0
src/components/BusyCard.vue

@@ -0,0 +1,86 @@
+<script lang="ts" setup>
+const orbCount = 8
+const baseColor = '#1890ff'
+const colors = [
+  {from: '#1890ff', to: '#69c0ff'},
+  {from: '#13c2c2', to: '#36cfc9'},
+  {from: '#722ed1', to: '#b37feb'},
+  {from: '#1890ff', to: '#40a9ff'},
+  {from: '#eb2f96', to: '#f759ab'},
+  {from: '#13c2c2', to: '#87e8de'},
+  {from: '#9254de', to: '#d3adf7'},
+  {from: '#1890ff', to: '#91d5ff'},
+]
+
+const totalDuration = 2.4
+const delayStep = 0.15
+</script>
+
+<template>
+  <div class="busy-card">
+    <div class="orb-row">
+      <div
+          v-for="i in orbCount"
+          :key="i"
+          class="orb-wrapper"
+      >
+        <div
+            :style="{
+            background: `radial-gradient(circle at 35% 35%, ${colors[i - 1].to}, ${colors[i - 1].from})`,
+            animationDuration: `${totalDuration}s`,
+            animationDelay: `${(i - 1) * delayStep}s`,
+          }"
+            class="orb"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.busy-card {
+  position: relative;
+  aspect-ratio: 3 / 1;
+  background: #0f1923;
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid rgba(24, 144, 255, 0.25);
+  box-shadow: 0 0 12px rgba(24, 144, 255, 0.12),
+  inset 0 0 20px rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.orb-row {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+.orb-wrapper {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.orb {
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+  animation: breathe ease-in-out infinite alternate;
+  box-shadow: 0 0 16px rgba(24, 144, 255, 0.4),
+  0 0 4px rgba(24, 144, 255, 0.6);
+}
+
+@keyframes breathe {
+  0% {
+    transform: scale(1);
+    opacity: 1;
+  }
+  100% {
+    transform: scale(0.35);
+    opacity: 0.4;
+  }
+}
+</style>

+ 3 - 1
src/views/Dashboard.vue

@@ -5,6 +5,7 @@ import {getClients} from '@/api/client'
 import type {PortState, StatusMessage} from '@/types'
 import PortStatusCard from '@/components/PortStatusCard.vue'
 import ReasoningCard from '@/components/ReasoningCard.vue'
+import BusyCard from '@/components/BusyCard.vue'
 
 const { connected, onMessage, onDisconnect } = useSSE('/api/events')
 
@@ -113,9 +114,10 @@ onUnmounted(removeDisconnectListener)
     </div>
 
     <div style="margin-top: 24px;">
-      <h3 style="margin-bottom: 12px; color: var(--text-secondary);">调试:ReasoningCard 预览</h3>
+      <h3 style="margin-bottom: 12px; color: var(--text-secondary);">调试:动画卡片预览</h3>
       <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px;">
         <ReasoningCard style="grid-column: span 2;"/>
+        <BusyCard style="grid-column: span 2;"/>
       </div>
     </div>
   </div>