moki 4 дней назад
Родитель
Сommit
98f8df8623
2 измененных файлов с 119 добавлено и 4 удалено
  1. 107 0
      src/components/ReasoningCard.vue
  2. 12 4
      src/views/Dashboard.vue

+ 107 - 0
src/components/ReasoningCard.vue

@@ -0,0 +1,107 @@
+<script lang="ts" setup>
+import {computed} from 'vue'
+
+interface WaveConfig {
+  color: string
+  amplitude: number
+  period: number
+  speed: number
+  opacity: number
+  strokeWidth: number
+  phaseOffset: number
+}
+
+const waves: WaveConfig[] = [
+  {color: '#722ed1', amplitude: 18, period: 200, speed: 4, opacity: 0.7, strokeWidth: 1.2, phaseOffset: 0},
+  {color: '#9254de', amplitude: 14, period: 160, speed: 5.5, opacity: 0.6, strokeWidth: 1, phaseOffset: 40},
+  {color: '#b37feb', amplitude: 10, period: 130, speed: 7, opacity: 0.45, strokeWidth: 0.8, phaseOffset: 90},
+  {color: '#1890ff', amplitude: 20, period: 240, speed: 3.5, opacity: 0.65, strokeWidth: 1.2, phaseOffset: 80},
+  {color: '#40a9ff', amplitude: 12, period: 170, speed: 6, opacity: 0.5, strokeWidth: 0.8, phaseOffset: 150},
+  {color: '#13c2c2', amplitude: 12, period: 180, speed: 6, opacity: 0.55, strokeWidth: 1, phaseOffset: 120},
+  {color: '#36cfc9', amplitude: 8, period: 140, speed: 7.5, opacity: 0.4, strokeWidth: 0.8, phaseOffset: 200},
+  {color: '#eb2f96', amplitude: 16, period: 220, speed: 4.5, opacity: 0.5, strokeWidth: 1, phaseOffset: 60},
+  {color: '#f759ab', amplitude: 9, period: 150, speed: 6.5, opacity: 0.4, strokeWidth: 0.8, phaseOffset: 170},
+  {color: '#597ef7', amplitude: 15, period: 190, speed: 5, opacity: 0.5, strokeWidth: 1, phaseOffset: 110},
+]
+
+const viewBoxWidth = 600
+const viewBoxHeight = 100
+const centerY = viewBoxHeight / 2
+
+function generateWavePath(wave: WaveConfig): string {
+  const totalWidth = viewBoxWidth * 2
+  const step = 4
+  const points: string[] = []
+
+  for (let x = 0; x <= totalWidth; x += step) {
+    const y = centerY + wave.amplitude * Math.sin((2 * Math.PI * (x + wave.phaseOffset)) / wave.period)
+    points.push(`${x},${y.toFixed(2)}`)
+  }
+
+  return `M ${points[0]} ` + points.slice(1).map(p => `L ${p}`).join(' ')
+}
+
+const wavePaths = computed(() =>
+    waves.map(w => ({
+      ...w,
+      path: generateWavePath(w),
+    }))
+)
+</script>
+
+<template>
+  <div class="reasoning-card">
+    <svg
+        :viewBox="`0 0 ${viewBoxWidth} ${viewBoxHeight}`"
+        class="wave-svg"
+        preserveAspectRatio="none"
+    >
+      <g v-for="(wave, i) in wavePaths" :key="i">
+        <path
+            :d="wave.path"
+            :stroke="wave.color"
+            :stroke-opacity="wave.opacity"
+            :stroke-width="wave.strokeWidth"
+            :style="{ animationDuration: `${wave.speed}s` }"
+            class="wave-path"
+            fill="none"
+            stroke-linecap="round"
+        />
+      </g>
+    </svg>
+  </div>
+</template>
+
+<style scoped>
+.reasoning-card {
+  position: relative;
+  aspect-ratio: 3 / 1;
+  background: #1a1a2e;
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid rgba(114, 46, 209, 0.3);
+  box-shadow: 0 0 12px rgba(114, 46, 209, 0.15),
+  inset 0 0 20px rgba(0, 0, 0, 0.3);
+}
+
+.wave-svg {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 200%;
+  height: 100%;
+}
+
+.wave-path {
+  animation: wave-scroll linear infinite;
+}
+
+@keyframes wave-scroll {
+  from {
+    transform: translateX(0);
+  }
+  to {
+    transform: translateX(-50%);
+  }
+}
+</style>

+ 12 - 4
src/views/Dashboard.vue

@@ -1,9 +1,10 @@
 <script setup lang="ts">
-import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
-import { useSSE } from '@/composables/useSSE'
-import { getClients } from '@/api/client'
-import type { PortState, StatusMessage } from '@/types'
+import {computed, onMounted, onUnmounted, reactive} from 'vue'
+import {useSSE} from '@/composables/useSSE'
+import {getClients} from '@/api/client'
+import type {PortState, StatusMessage} from '@/types'
 import PortStatusCard from '@/components/PortStatusCard.vue'
+import ReasoningCard from '@/components/ReasoningCard.vue'
 
 const { connected, onMessage, onDisconnect } = useSSE('/api/events')
 
@@ -110,6 +111,13 @@ onUnmounted(removeDisconnectListener)
     <div class="port-grid">
       <PortStatusCard v-for="port in portList" :key="port.port" :state="port" />
     </div>
+
+    <div style="margin-top: 24px;">
+      <h3 style="margin-bottom: 12px; color: var(--text-secondary);">调试:ReasoningCard 预览</h3>
+      <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px;">
+        <ReasoningCard style="grid-column: span 2;"/>
+      </div>
+    </div>
   </div>
 </template>