| 1 | 'use client'; |
| 2 | |
| 3 | import React from 'react'; |
| 4 | import { Loader } from 'lucide-react'; |
| 5 | |
| 6 | interface StageProgressProps { |
| 7 | /** progressMessage from StageState */ |
| 8 | message: string; |
| 9 | /** fallback text when message is empty */ |
| 10 | fallback?: string; |
| 11 | /** 0-100 */ |
| 12 | progress: number; |
| 13 | /** accent color class, default blue */ |
| 14 | color?: 'blue' | 'amber' | 'emerald' | 'violet' | 'rose' | 'cyan'; |
| 15 | } |
| 16 | |
| 17 | const BAR_COLORS: Record<string, { bg: string; bar: string; text: string; ring: string }> = { |
| 18 | blue: { bg: 'bg-blue-100', bar: 'bg-gradient-to-r from-blue-400 to-blue-600', text: 'text-blue-600', ring: 'ring-blue-200' }, |
| 19 | amber: { bg: 'bg-amber-100', bar: 'bg-gradient-to-r from-amber-400 to-amber-600', text: 'text-amber-600', ring: 'ring-amber-200' }, |
| 20 | emerald: { bg: 'bg-emerald-100', bar: 'bg-gradient-to-r from-emerald-400 to-emerald-600', text: 'text-emerald-600', ring: 'ring-emerald-200' }, |
| 21 | violet: { bg: 'bg-violet-100', bar: 'bg-gradient-to-r from-violet-400 to-violet-600', text: 'text-violet-600', ring: 'ring-violet-200' }, |
| 22 | rose: { bg: 'bg-rose-100', bar: 'bg-gradient-to-r from-rose-400 to-rose-600', text: 'text-rose-600', ring: 'ring-rose-200' }, |
| 23 | cyan: { bg: 'bg-cyan-100', bar: 'bg-gradient-to-r from-cyan-400 to-cyan-600', text: 'text-cyan-600', ring: 'ring-cyan-200' }, |
| 24 | }; |
| 25 | |
| 26 | export default function StageProgress({ |
| 27 | message, |
| 28 | fallback = '处理中...', |
| 29 | progress, |
| 30 | color = 'blue', |
| 31 | }: StageProgressProps) { |
| 32 | const p = Math.min(100, Math.max(0, Math.round(progress))); |
| 33 | const c = BAR_COLORS[color] || BAR_COLORS.blue; |
| 34 | |
| 35 | // Extract step description: progressMessage format is "阶段名: 步骤描述" |
| 36 | const stepDesc = message?.includes(': ') ? message.split(': ').slice(1).join(': ') : (message || fallback); |
| 37 | |
| 38 | return ( |
| 39 | <div className="mb-6"> |
| 40 | <div className="flex items-center justify-between mb-2"> |
| 41 | <div className={`flex items-center gap-2 text-sm font-medium ${c.text}`}> |
| 42 | <Loader className="w-4 h-4 animate-spin" /> |
| 43 | <span className="truncate">{stepDesc}</span> |
| 44 | </div> |
| 45 | <span className={`text-xs font-mono tabular-nums ${c.text} opacity-70`}>{p}%</span> |
| 46 | </div> |
| 47 | <div className={`w-full ${c.bg} rounded-full h-2.5 overflow-hidden ring-1 ${c.ring}`}> |
| 48 | <div |
| 49 | className={`${c.bar} h-2.5 rounded-full transition-all duration-700 ease-out`} |
| 50 | style={{ width: `${p}%` }} |
| 51 | /> |
| 52 | </div> |
| 53 | </div> |
| 54 | ); |
| 55 | } |
| 56 |