Files
accounts-manager-web/lib/hooks/use-accounts.ts
cloud370 43a828a2c6 feat: 增强表格交互体验和数据展示
- 新增每页数量localStorage缓存,刷新后保持用户选择
- 增加表格显示高度从600px到800px,显示更多数据
- 支持点击表格行进行选择,提升操作便利性
- 格式化创建时间显示为详细时间(年/月/日 时:分)
- 优化复选框和操作按钮的点击事件冲突

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

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

305 lines
8.7 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, 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
};
}