 0b95ca36f1
			
		
	
	0b95ca36f1
	
	
	
		
			
			- Set up Next.js project structure - Added UI components and styling - Configured package dependencies - Added feature documentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			246 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| "use client";
 | |
| 
 | |
| import { useState } from 'react';
 | |
| import { Button } from '@/components/ui/button';
 | |
| import { Input } from '@/components/ui/input';
 | |
| import { Textarea } from '@/components/ui/textarea';
 | |
| import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
 | |
| import { Badge } from '@/components/ui/badge';
 | |
| import { Account, StatsOverview } from '@/lib/types';
 | |
| import { PlatformSelector, OwnerSelector, StatusSelector } from '@/components/shared';
 | |
| 
 | |
| interface AccountDetailsProps {
 | |
|   account: Account | null;
 | |
|   open: boolean;
 | |
|   mode: 'view' | 'edit';
 | |
|   stats: StatsOverview | null;
 | |
|   onOpenChange: (open: boolean) => void;
 | |
|   onSave?: (account: Account) => Promise<void>;
 | |
|   onDelete?: (account: Account) => Promise<void>;
 | |
| }
 | |
| 
 | |
| export function AccountDetails({ 
 | |
|   account, 
 | |
|   open, 
 | |
|   mode, 
 | |
|   stats,
 | |
|   onOpenChange, 
 | |
|   onSave,
 | |
|   onDelete 
 | |
| }: AccountDetailsProps) {
 | |
|   const [editData, setEditData] = useState<Partial<Account>>({});
 | |
|   const [loading, setLoading] = useState(false);
 | |
| 
 | |
|   const isEditing = mode === 'edit';
 | |
| 
 | |
| 
 | |
|   const handleSave = async () => {
 | |
|     if (!account || !onSave) return;
 | |
|     
 | |
|     setLoading(true);
 | |
|     try {
 | |
|       const updatedAccount = { ...account, ...editData };
 | |
|       await onSave(updatedAccount);
 | |
|       onOpenChange(false);
 | |
|       setEditData({});
 | |
|     } finally {
 | |
|       setLoading(false);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   const handleDelete = async () => {
 | |
|     if (!account || !onDelete) return;
 | |
|     
 | |
|     if (!confirm('确认删除此账户?')) return;
 | |
|     
 | |
|     setLoading(true);
 | |
|     try {
 | |
|       await onDelete(account);
 | |
|       onOpenChange(false);
 | |
|     } finally {
 | |
|       setLoading(false);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   const getStatusBadge = (status: string) => {
 | |
|     switch (status) {
 | |
|       case 'available':
 | |
|         return <Badge variant="default" className="bg-green-500">可用</Badge>;
 | |
|       case 'locked':
 | |
|         return <Badge variant="secondary">已锁定</Badge>;
 | |
|       case 'banned':
 | |
|         return <Badge variant="destructive">已封禁</Badge>;
 | |
|       default:
 | |
|         return <Badge variant="outline">{status}</Badge>;
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   const formatData = (data: string) => {
 | |
|     try {
 | |
|       const parsed = JSON.parse(data);
 | |
|       return JSON.stringify(parsed, null, 2);
 | |
|     } catch {
 | |
|       return data;
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   if (!account) return null;
 | |
| 
 | |
|   return (
 | |
|     <Dialog open={open} onOpenChange={onOpenChange}>
 | |
|       <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
 | |
|         <DialogHeader>
 | |
|           <DialogTitle>
 | |
|             {isEditing ? '编辑账户' : '账户详情'}
 | |
|           </DialogTitle>
 | |
|           <DialogDescription>
 | |
|             账户ID: {account.id}
 | |
|           </DialogDescription>
 | |
|         </DialogHeader>
 | |
|         
 | |
|         <div className="space-y-4">
 | |
|           <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
 | |
|             <div>
 | |
|               <label className="text-sm font-medium">平台</label>
 | |
|               {isEditing ? (
 | |
|                 <PlatformSelector
 | |
|                   value={editData.platform ?? account.platform}
 | |
|                   onValueChange={(value) => setEditData({...editData, platform: value})}
 | |
|                   stats={stats}
 | |
|                   placeholder="选择平台"
 | |
|                   inputPlaceholder="或直接输入平台名称"
 | |
|                 />
 | |
|               ) : (
 | |
|                 <div className="p-2 bg-muted rounded">{account.platform}</div>
 | |
|               )}
 | |
|             </div>
 | |
|             
 | |
|             <div>
 | |
|               <label className="text-sm font-medium">自定义ID</label>
 | |
|               {isEditing ? (
 | |
|                 <Input
 | |
|                   value={editData.customId ?? account.customId}
 | |
|                   onChange={(e) => setEditData({...editData, customId: e.target.value})}
 | |
|                 />
 | |
|               ) : (
 | |
|                 <div className="p-2 bg-muted rounded font-mono">{account.customId}</div>
 | |
|               )}
 | |
|             </div>
 | |
|           </div>
 | |
|           
 | |
|           <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
 | |
|             <div>
 | |
|               <label className="text-sm font-medium">所有者ID</label>
 | |
|               {isEditing ? (
 | |
|                 <OwnerSelector
 | |
|                   value={editData.ownerId ?? account.ownerId}
 | |
|                   onValueChange={(value) => setEditData({...editData, ownerId: value})}
 | |
|                   stats={stats}
 | |
|                   placeholder="选择所有者"
 | |
|                   inputPlaceholder="或直接输入所有者ID"
 | |
|                 />
 | |
|               ) : (
 | |
|                 <div className="p-2 bg-muted rounded">{account.ownerId}</div>
 | |
|               )}
 | |
|             </div>
 | |
|             
 | |
|             <div>
 | |
|               <label className="text-sm font-medium">状态</label>
 | |
|               {isEditing ? (
 | |
|                 <StatusSelector
 | |
|                   value={editData.status ?? account.status}
 | |
|                   onValueChange={(value) => setEditData({...editData, status: value})}
 | |
|                   stats={stats}
 | |
|                   placeholder="选择状态"
 | |
|                   inputPlaceholder="或直接输入状态"
 | |
|                 />
 | |
|               ) : (
 | |
|                 <div className="p-2">{getStatusBadge(account.status)}</div>
 | |
|               )}
 | |
|             </div>
 | |
|           </div>
 | |
|           
 | |
|           <div>
 | |
|             <label className="text-sm font-medium">账户数据</label>
 | |
|             {isEditing ? (
 | |
|               <Textarea
 | |
|                 value={editData.data ?? account.data}
 | |
|                 onChange={(e) => setEditData({...editData, data: e.target.value})}
 | |
|                 className="min-h-[120px] font-mono text-sm"
 | |
|               />
 | |
|             ) : (
 | |
|               <pre className="p-3 bg-muted rounded text-sm font-mono whitespace-pre-wrap break-all max-h-[200px] overflow-y-auto">
 | |
|                 {formatData(account.data)}
 | |
|               </pre>
 | |
|             )}
 | |
|           </div>
 | |
|           
 | |
|           <div>
 | |
|             <label className="text-sm font-medium">备注</label>
 | |
|             {isEditing ? (
 | |
|               <Textarea
 | |
|                 value={editData.notes ?? account.notes ?? ''}
 | |
|                 onChange={(e) => setEditData({...editData, notes: e.target.value})}
 | |
|                 placeholder="输入备注..."
 | |
|               />
 | |
|             ) : (
 | |
|               <div className="p-2 bg-muted rounded min-h-[40px]">
 | |
|                 {account.notes || '无备注'}
 | |
|               </div>
 | |
|             )}
 | |
|           </div>
 | |
|           
 | |
|           {!isEditing && (
 | |
|             <div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-muted-foreground">
 | |
|               <div>
 | |
|                 <label className="font-medium">创建时间</label>
 | |
|                 <div>{new Date(account.createdAt).toLocaleString('zh-CN')}</div>
 | |
|               </div>
 | |
|               <div>
 | |
|                 <label className="font-medium">更新时间</label>
 | |
|                 <div>{new Date(account.updatedAt).toLocaleString('zh-CN')}</div>
 | |
|               </div>
 | |
|               {account.lockedAt && (
 | |
|                 <div className="col-span-full md:col-span-2">
 | |
|                   <label className="font-medium">锁定时间</label>
 | |
|                   <div>{new Date(account.lockedAt).toLocaleString('zh-CN')}</div>
 | |
|                 </div>
 | |
|               )}
 | |
|             </div>
 | |
|           )}
 | |
|         </div>
 | |
|         
 | |
|         <DialogFooter>
 | |
|           <Button 
 | |
|             variant="outline" 
 | |
|             onClick={() => onOpenChange(false)}
 | |
|             disabled={loading}
 | |
|           >
 | |
|             {isEditing ? '取消' : '关闭'}
 | |
|           </Button>
 | |
|           
 | |
|           {isEditing ? (
 | |
|             <Button 
 | |
|               onClick={handleSave}
 | |
|               disabled={loading}
 | |
|             >
 | |
|               {loading ? '保存中...' : '保存'}
 | |
|             </Button>
 | |
|           ) : (
 | |
|             <div className="space-x-2">
 | |
|               {onDelete && (
 | |
|                 <Button 
 | |
|                   variant="destructive"
 | |
|                   onClick={handleDelete}
 | |
|                   disabled={loading}
 | |
|                 >
 | |
|                   {loading ? '删除中...' : '删除'}
 | |
|                 </Button>
 | |
|               )}
 | |
|             </div>
 | |
|           )}
 | |
|         </DialogFooter>
 | |
|       </DialogContent>
 | |
|     </Dialog>
 | |
|   );
 | |
| } |