Files
accounts-manager-web/components/accounts-manager.tsx
cloud370 7aaeffa498 feat: 实现账户批量导出功能和界面优化
- 新增批量导出功能,支持text模式导出账户数据
- 添加导出弹窗,支持文本全选和文件下载
- 移动刷新按钮到全局位置,统一刷新账户和统计数据
- 在统计卡片中将已导出状态计入可用账户
- 创建自定义确认对话框替换系统confirm弹窗
- 统一按钮尺寸,修复刷新和上传按钮大小不一致
- 添加已导出状态的中文映射和样式

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-24 05:28:45 +08:00

212 lines
6.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { RefreshCw } from 'lucide-react';
import { Account } from '@/lib/types';
import { useAccounts } from '@/lib/hooks/use-accounts';
import { useConfirmDialog } from '@/components/ui/confirm-dialog';
// 组件导入
import { StatsCards } from './accounts/stats-cards';
import { AccountFilters } from './accounts/account-filters';
import { BatchOperations } from './accounts/batch-operations';
import { AccountUpload } from './accounts/account-upload';
import { AccountTable } from './accounts/account-table';
import { AccountDetails } from './accounts/account-details';
export function AccountsManager() {
const { showConfirm, ConfirmDialogComponent } = useConfirmDialog();
const {
accounts,
stats,
loading,
selectedIds,
pagination,
filters,
fetchAccounts,
fetchStats,
handleFilterChange,
handlePageChange,
handlePageSizeChange,
handleSelectAll,
handleSelectOne,
handleBatchDelete,
handleBatchUpdate,
handleUploadAccounts,
setSelectedIds
} = useAccounts();
// 全局刷新函数
const handleGlobalRefresh = async () => {
await Promise.all([fetchAccounts(), fetchStats()]);
};
// 账户详情状态
const [detailsDialog, setDetailsDialog] = useState(false);
const [detailsMode, setDetailsMode] = useState<'view' | 'edit'>('view');
const [selectedAccount, setSelectedAccount] = useState<Account | null>(null);
// 处理查看详情
const handleViewAccount = (account: Account) => {
setSelectedAccount(account);
setDetailsMode('view');
setDetailsDialog(true);
};
// 处理编辑账户
const handleEditAccount = (account: Account) => {
setSelectedAccount(account);
setDetailsMode('edit');
setDetailsDialog(true);
};
// 处理删除单个账户
const handleDeleteAccount = async (account: Account) => {
showConfirm({
title: '确认删除',
description: `确认删除账户 "${account.customId}"?此操作不可撤销。`,
confirmText: '确认删除',
cancelText: '取消',
variant: 'destructive',
onConfirm: async () => {
try {
// 设置选中的账户ID
setSelectedIds([account.id]);
// 执行删除
await handleBatchDelete();
} catch (error) {
console.error('Failed to delete account:', error);
}
}
});
};
// 处理保存编辑
const handleSaveAccount = async (account: Account) => {
try {
console.log('开始保存账户:', account);
// 构建payload将null/undefined转换为空字符串
const payload: Partial<Pick<Account, 'status' | 'ownerId' | 'notes' | 'platform'>> = {};
if (account.status !== null && account.status !== undefined) {
payload.status = account.status;
}
if (account.ownerId !== null && account.ownerId !== undefined) {
payload.ownerId = account.ownerId;
}
if (account.notes !== null && account.notes !== undefined) {
payload.notes = account.notes || ''; // 将null转换为空字符串
}
if (account.platform !== null && account.platform !== undefined) {
payload.platform = account.platform;
}
console.log('更新payload:', payload);
console.log('账户ID:', account.id);
if (Object.keys(payload).length === 0) {
console.log('没有有效的字段需要更新');
return;
}
// 直接传递账户ID数组避免状态竞态条件
await handleBatchUpdate(payload, [account.id]);
console.log('账户保存完成');
} catch (error) {
console.error('Failed to save account:', error);
}
};
return (
<div className="flex flex-col">
<div className="container mx-auto p-6 flex flex-col">
{/* 页面标题 */}
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-3xl font-bold tracking-tight"></h1>
<p className="text-muted-foreground"></p>
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={handleGlobalRefresh}
disabled={loading}
>
<RefreshCw className={`h-4 w-4 mr-1 ${loading ? 'animate-spin' : ''}`} />
</Button>
<AccountUpload onUpload={handleUploadAccounts} stats={stats} />
</div>
</div>
{/* 统计卡片 */}
<div className="mb-6">
<StatsCards stats={stats} loading={loading} />
</div>
{/* 账户管理区域 */}
<Card className="flex flex-col">
<CardHeader className="pb-4">
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="flex flex-col space-y-4">
{/* 筛选条件 */}
<AccountFilters
filters={filters}
stats={stats}
onFilterChange={handleFilterChange}
/>
{/* 批量操作 */}
<BatchOperations
selectedCount={selectedIds.length}
selectedAccounts={accounts.filter(account => selectedIds.includes(account.id))}
stats={stats}
onBatchUpdate={handleBatchUpdate}
onBatchDelete={() => handleBatchDelete(showConfirm)}
onRefresh={handleGlobalRefresh}
/>
{/* 账户表格 - 移除高度限制 */}
<div className="">
<AccountTable
accounts={accounts}
loading={loading}
selectedIds={selectedIds}
pagination={pagination}
onSelectAll={handleSelectAll}
onSelectOne={handleSelectOne}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
onView={handleViewAccount}
onEdit={handleEditAccount}
onDelete={handleDeleteAccount}
/>
</div>
</CardContent>
</Card>
</div>
{/* 账户详情对话框 */}
<AccountDetails
account={selectedAccount}
open={detailsDialog}
mode={detailsMode}
stats={stats}
onOpenChange={setDetailsDialog}
onSave={handleSaveAccount}
onDelete={handleDeleteAccount}
/>
{/* 确认删除对话框 */}
<ConfirmDialogComponent />
</div>
);
}