Initial project setup with Next.js accounts manager
- 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>
This commit is contained in:
		
							
								
								
									
										265
									
								
								lib/hooks/use-accounts.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										265
									
								
								lib/hooks/use-accounts.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,265 @@ | ||||
| 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'>>) => 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 | ||||
|   }; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Your Name
					Your Name