Files
accounts-manager-web/lib/hooks/use-accounts.ts
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

281 lines
8.0 KiB
TypeScript
Raw 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.

import { useState, useEffect } from 'react';
import {
Account,
ListAccountsBody,
ListAccountsResponse,
StatsOverview,
BusinessCode,
BatchUpdateBody,
BatchDeleteBody,
ScriptUploadItem
} from '@/lib/types';
import { apiClient } from '@/lib/api';
import { toast } from 'sonner';
export interface UseAccountsReturn {
// 数据状态
accounts: Account[];
stats: StatsOverview | null;
loading: boolean;
selectedIds: number[];
// 分页和筛选
pagination: {
page: number;
pageSize: number;
total: number;
totalPages: number;
};
filters: {
platform: string;
status: string[];
ownerId: string;
search: string;
};
sort: {
field: keyof Account;
order: 'asc' | 'desc';
};
// 操作方法
fetchAccounts: () => Promise<void>;
fetchStats: () => Promise<void>;
handleFilterChange: (key: string, value: any) => void;
handlePageChange: (page: number) => void;
handlePageSizeChange: (pageSize: number) => void;
handleSortChange: (field: keyof Account, order: 'asc' | 'desc') => void;
handleSelectAll: (checked: boolean) => void;
handleSelectOne: (id: number, checked: boolean) => void;
handleBatchDelete: (showConfirm?: (options: any) => void) => Promise<void>;
handleBatchUpdate: (payload: Partial<Pick<Account, 'status' | 'ownerId' | 'notes' | 'platform'>>, targetIds?: number[]) => Promise<void>;
handleUploadAccounts: (accounts: ScriptUploadItem[], ownerId: string) => Promise<void>;
setSelectedIds: (ids: number[]) => void;
}
export function useAccounts(): UseAccountsReturn {
const [accounts, setAccounts] = useState<Account[]>([]);
const [stats, setStats] = useState<StatsOverview | null>(null);
const [loading, setLoading] = useState(false);
const [selectedIds, setSelectedIds] = useState<number[]>([]);
const [pagination, setPagination] = useState({
page: 1,
pageSize: 20,
total: 0,
totalPages: 0
});
const [filters, setFilters] = useState({
platform: '',
status: [] as string[],
ownerId: '',
search: ''
});
const [sort, setSort] = useState({
field: 'id' as keyof Account,
order: 'desc' as 'asc' | 'desc'
});
// 获取统计数据
const fetchStats = async () => {
try {
const response = await apiClient.getStatsOverview();
if (response.code === BusinessCode.Success && response.data) {
setStats(response.data);
}
} catch (error) {
console.error('Failed to fetch stats:', error);
toast.error('获取统计数据失败');
}
};
// 获取账户列表
const fetchAccounts = async () => {
setLoading(true);
try {
const body: ListAccountsBody = {
filters,
pagination: {
page: pagination.page,
pageSize: pagination.pageSize
},
sort
};
const response = await apiClient.getAccountsList(body);
if (response.code === BusinessCode.Success && response.data) {
setAccounts(response.data.list);
setPagination(prev => ({
...prev,
total: response.data!.pagination.total,
totalPages: response.data!.pagination.totalPages
}));
}
} catch (error) {
console.error('Failed to fetch accounts:', error);
toast.error('获取账户列表失败');
} finally {
setLoading(false);
}
};
// 处理筛选变化
const handleFilterChange = (key: string, value: any) => {
setFilters({ ...filters, [key]: value });
setPagination({ ...pagination, page: 1 });
};
// 处理分页
const handlePageChange = (page: number) => {
setPagination({ ...pagination, page });
};
// 处理每页大小变化
const handlePageSizeChange = (pageSize: number) => {
setPagination({ ...pagination, pageSize, page: 1 });
};
// 处理排序
const handleSortChange = (field: keyof Account, order: 'asc' | 'desc') => {
setSort({ field, order });
};
// 处理全选
const handleSelectAll = (checked: boolean) => {
if (checked) {
setSelectedIds(accounts.map(account => account.id));
} else {
setSelectedIds([]);
}
};
// 处理单选
const handleSelectOne = (id: number, checked: boolean) => {
if (checked) {
setSelectedIds([...selectedIds, id]);
} else {
setSelectedIds(selectedIds.filter(selectedId => selectedId !== id));
}
};
// 批量删除
const handleBatchDelete = async (showConfirm?: (options: any) => void) => {
if (selectedIds.length === 0) return;
const executeDelete = async () => {
try {
const response = await apiClient.batchDeleteAccounts({ ids: selectedIds });
if (response.code === BusinessCode.Success) {
setSelectedIds([]);
await fetchAccounts();
await fetchStats();
toast.success(`成功删除 ${response.data?.deletedCount || 0} 个账户`);
}
} catch (error) {
console.error('Failed to delete accounts:', error);
toast.error('删除失败,请检查网络连接后重试');
}
};
if (showConfirm) {
showConfirm({
title: '确认删除',
description: `确认删除 ${selectedIds.length} 个账户?此操作不可撤销。`,
confirmText: '确认删除',
cancelText: '取消',
variant: 'destructive',
onConfirm: executeDelete
});
} else {
// 如果没有提供确认函数,直接执行删除(用于兼容)
if (confirm(`确认删除 ${selectedIds.length} 个账户?`)) {
await executeDelete();
}
}
};
// 批量更新
const handleBatchUpdate = async (payload: Partial<Pick<Account, 'status' | 'ownerId' | 'notes' | 'platform'>>, targetIds?: number[]) => {
const idsToUpdate = targetIds || selectedIds;
console.log('handleBatchUpdate 被调用, idsToUpdate:', idsToUpdate);
console.log('payload:', payload);
if (idsToUpdate.length === 0) {
console.log('idsToUpdate为空直接返回');
return;
}
if (Object.keys(payload).length === 0) {
toast.warning('请至少选择一个字段进行更新');
return;
}
try {
console.log('发送批量更新请求...');
const response = await apiClient.batchUpdateAccounts({ ids: idsToUpdate, payload });
console.log('批量更新响应:', response);
if (response.code === BusinessCode.Success) {
if (!targetIds) {
// 只有在没有指定目标ID时才清空选中状态批量操作时
setSelectedIds([]);
}
await fetchAccounts();
await fetchStats();
toast.success(`成功更新 ${response.data?.updatedCount || 0} 个账户`);
}
} catch (error) {
console.error('Failed to update accounts:', error);
toast.error('更新失败,请检查网络连接后重试');
}
};
// 上传账户
const handleUploadAccounts = async (uploadAccounts: ScriptUploadItem[], ownerId: string) => {
try {
const response = await apiClient.uploadAccounts(uploadAccounts, ownerId);
if (response.code === BusinessCode.Success) {
await fetchAccounts();
await fetchStats();
toast.success(`成功处理 ${response.data?.processedCount || 0} 个账户 (${response.data?.createdCount || 0} 个新建, ${response.data?.updatedCount || 0} 个更新)`);
return;
}
} catch (error) {
console.error('Failed to upload accounts:', error);
toast.error('上传失败,请检查网络连接后重试');
}
};
// 初始化数据
useEffect(() => {
fetchAccounts();
}, [filters, pagination.page, pagination.pageSize, sort]);
// 只在开始时获取统计数据
useEffect(() => {
fetchStats();
}, []);
return {
accounts,
stats,
loading,
selectedIds,
pagination,
filters,
sort,
fetchAccounts,
fetchStats,
handleFilterChange,
handlePageChange,
handlePageSizeChange,
handleSortChange,
handleSelectAll,
handleSelectOne,
handleBatchDelete,
handleBatchUpdate,
handleUploadAccounts,
setSelectedIds
};
}