- 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>
|
|
);
|
|
} |