- 新增每页数量localStorage缓存,刷新后保持用户选择 - 增加表格显示高度从600px到800px,显示更多数据 - 支持点击表格行进行选择,提升操作便利性 - 格式化创建时间显示为详细时间(年/月/日 时:分) - 优化复选框和操作按钮的点击事件冲突 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
305 lines
8.7 KiB
TypeScript
305 lines
8.7 KiB
TypeScript
import { useState, useEffect, useCallback } 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[]>([]);
|
||
|
||
// 从 localStorage 获取缓存的分页大小,默认为 20
|
||
const getInitialPageSize = () => {
|
||
if (typeof window !== 'undefined') {
|
||
const saved = localStorage.getItem('accounts-page-size');
|
||
if (saved) {
|
||
const pageSize = parseInt(saved, 10);
|
||
// 验证是否为有效值
|
||
if ([20, 100, 1000, 10000].includes(pageSize)) {
|
||
return pageSize;
|
||
}
|
||
}
|
||
}
|
||
return 20;
|
||
};
|
||
|
||
const [pagination, setPagination] = useState({
|
||
page: 1,
|
||
pageSize: getInitialPageSize(),
|
||
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 = useCallback(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);
|
||
}
|
||
}, [filters, pagination.page, pagination.pageSize, sort]);
|
||
|
||
// 处理筛选变化
|
||
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) => {
|
||
// 保存到 localStorage
|
||
if (typeof window !== 'undefined') {
|
||
localStorage.setItem('accounts-page-size', pageSize.toString());
|
||
}
|
||
|
||
setPagination(prev => ({
|
||
...prev,
|
||
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();
|
||
}, [fetchAccounts]);
|
||
|
||
// 只在开始时获取统计数据
|
||
useEffect(() => {
|
||
fetchStats();
|
||
}, []);
|
||
|
||
return {
|
||
accounts,
|
||
stats,
|
||
loading,
|
||
selectedIds,
|
||
pagination,
|
||
filters,
|
||
sort,
|
||
fetchAccounts,
|
||
fetchStats,
|
||
handleFilterChange,
|
||
handlePageChange,
|
||
handlePageSizeChange,
|
||
handleSortChange,
|
||
handleSelectAll,
|
||
handleSelectOne,
|
||
handleBatchDelete,
|
||
handleBatchUpdate,
|
||
handleUploadAccounts,
|
||
setSelectedIds
|
||
};
|
||
} |