- 更新UseAccountsReturn接口中handleBatchUpdate的类型签名,添加可选的targetIds参数 - 更新BatchOperationsProps接口中onBatchUpdate的类型定义以匹配实际函数签名 - 修复response.data可能为null的TypeScript严格检查错误 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			265 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			265 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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: () => 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 () => {
 | ||
|     if (selectedIds.length === 0) return;
 | ||
|     
 | ||
|     if (!confirm(`确认删除 ${selectedIds.length} 个账户?`)) return;
 | ||
|     
 | ||
|     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('删除失败,请检查网络连接后重试');
 | ||
|     }
 | ||
|   };
 | ||
| 
 | ||
|   // 批量更新
 | ||
|   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
 | ||
|   };
 | ||
| } |