可用版本
This commit is contained in:
parent
a7bc39c70f
commit
582b7669af
@ -1,62 +1,104 @@
|
||||
import {useState, useCallback, useEffect} from 'react';
|
||||
import {message, Modal} from 'antd';
|
||||
import type {UseTableDataProps, TableState} from '@/types/table';
|
||||
import {createInitialState, handleTableError} from '@/utils/table';
|
||||
import {Modal} from 'antd';
|
||||
import type {TableState} from '@/types/table';
|
||||
import {createInitialState} from '@/utils/table';
|
||||
import request from '@/utils/request';
|
||||
import type {Page} from '@/types/base/page';
|
||||
|
||||
export interface TableConfig<T> {
|
||||
message?: {
|
||||
createSuccess?: string;
|
||||
updateSuccess?: string;
|
||||
deleteSuccess?: string;
|
||||
loadError?: string;
|
||||
};
|
||||
// ... 其他配置
|
||||
// 修改TableService接口
|
||||
export interface TableService<T, P = any> {
|
||||
baseUrl: string; // 基础URL
|
||||
}
|
||||
|
||||
export function useTableData<T extends { id: number }, P = any>({
|
||||
service,
|
||||
defaultParams,
|
||||
config = {}
|
||||
}: UseTableDataProps<T, P>) {
|
||||
const {
|
||||
message: messageConfig = {},
|
||||
defaultPageSize = 10,
|
||||
selection = false,
|
||||
onDataChange
|
||||
} = config;
|
||||
|
||||
service,
|
||||
defaultParams,
|
||||
config = {}
|
||||
}: {
|
||||
service: TableService<T, P>;
|
||||
defaultParams?: Partial<P>;
|
||||
config?: any;
|
||||
}) {
|
||||
const [state, setState] = useState<TableState<T>>(() =>
|
||||
createInitialState<T>(defaultPageSize)
|
||||
createInitialState(config.defaultPageSize || 10)
|
||||
);
|
||||
|
||||
// 加载数据
|
||||
const loadData = useCallback(async (params?: Partial<P>) => {
|
||||
setState(prev => ({...prev, loading: true}));
|
||||
setState(prev => ({ ...prev, loading: true }));
|
||||
try {
|
||||
const pageData = await service.list({
|
||||
...defaultParams,
|
||||
...params,
|
||||
pageNum: state.pagination.current,
|
||||
pageSize: state.pagination.pageSize
|
||||
} as P);
|
||||
const pageData = await request.get<Page<T>>(`${service.baseUrl}/page`, {
|
||||
params: {
|
||||
...defaultParams,
|
||||
...params,
|
||||
pageNum: state.pagination.current,
|
||||
pageSize: state.pagination.pageSize
|
||||
},
|
||||
transform: true
|
||||
});
|
||||
|
||||
const newList = pageData?.content || [];
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
list: newList,
|
||||
list: pageData?.content || [],
|
||||
pagination: {
|
||||
...prev.pagination,
|
||||
total: pageData?.totalElements || 0
|
||||
},
|
||||
loading: false
|
||||
}));
|
||||
|
||||
onDataChange?.(newList);
|
||||
} catch (error) {
|
||||
setState(prev => ({...prev, loading: false}));
|
||||
message.error(messageConfig.loadError || '加载数据失败');
|
||||
setState(prev => ({ ...prev, loading: false }));
|
||||
}
|
||||
}, [service, defaultParams, state.pagination, onDataChange]);
|
||||
}, [service, defaultParams, state.pagination]);
|
||||
|
||||
// CRUD操作
|
||||
const handleCreate = useCallback(async (data: Partial<T>) => {
|
||||
try {
|
||||
await request.post<T>(service.baseUrl, data, {
|
||||
successMessage: '创建成功'
|
||||
});
|
||||
loadData();
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}, [service, loadData]);
|
||||
|
||||
const handleUpdate = useCallback(async (id: number, data: Partial<T>) => {
|
||||
try {
|
||||
await request.put<T>(`${service.baseUrl}/${id}`, data, {
|
||||
successMessage: '更新成功'
|
||||
});
|
||||
loadData();
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}, [service, loadData]);
|
||||
|
||||
const handleDelete = useCallback(async (id: number) => {
|
||||
return new Promise((resolve) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除该记录吗?',
|
||||
okText: '确定',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await request.delete(`${service.baseUrl}/${id}`, {
|
||||
successMessage: '删除成功'
|
||||
});
|
||||
loadData();
|
||||
resolve(true);
|
||||
} catch (error) {
|
||||
resolve(false);
|
||||
}
|
||||
},
|
||||
onCancel: () => resolve(false)
|
||||
});
|
||||
});
|
||||
}, [service, loadData]);
|
||||
|
||||
// 分页变化
|
||||
const onPageChange = useCallback((page: number, pageSize?: number) => {
|
||||
@ -70,66 +112,15 @@ export function useTableData<T extends { id: number }, P = any>({
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// CRUD操作
|
||||
const handleCreate = useCallback(async (data: Partial<T>) => {
|
||||
if (!service.create) return false;
|
||||
try {
|
||||
await service.create(data);
|
||||
message.success(messageConfig.createSuccess || '创建成功');
|
||||
loadData();
|
||||
return true;
|
||||
} catch (error) {
|
||||
return handleTableError(error, '创建失败');
|
||||
}
|
||||
}, [service, loadData]);
|
||||
|
||||
const handleUpdate = useCallback(async (id: number, data: Partial<T>) => {
|
||||
if (!service.update) return false;
|
||||
try {
|
||||
await service.update(id, data);
|
||||
message.success(messageConfig.updateSuccess || '更新成功');
|
||||
loadData();
|
||||
return true;
|
||||
} catch (error) {
|
||||
return handleTableError(error, '更新失败');
|
||||
}
|
||||
}, [service, loadData]);
|
||||
|
||||
const handleDelete = useCallback(async (id: number) => {
|
||||
if (!service.delete) return false;
|
||||
return new Promise((resolve) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除该记录吗?',
|
||||
okText: '确定',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await service.delete(id);
|
||||
message.success(messageConfig.deleteSuccess || '删除成功');
|
||||
loadData();
|
||||
resolve(true);
|
||||
} catch (error) {
|
||||
resolve(false);
|
||||
}
|
||||
},
|
||||
onCancel: () => {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}, [service, loadData]);
|
||||
|
||||
// 选择行
|
||||
const onSelectChange = useCallback((selectedRowKeys: React.Key[], selectedRows: T[]) => {
|
||||
if (!selection) return;
|
||||
if (!config.selection) return;
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
selectedRowKeys,
|
||||
selectedRows
|
||||
}));
|
||||
}, [selection]);
|
||||
}, [config.selection]);
|
||||
|
||||
// 监听分页变化自动加载数据
|
||||
useEffect(() => {
|
||||
@ -144,7 +135,6 @@ export function useTableData<T extends { id: number }, P = any>({
|
||||
handleUpdate,
|
||||
handleDelete,
|
||||
onSelectChange,
|
||||
// 提供重置方法
|
||||
reset: () => setState(createInitialState<T>(defaultPageSize))
|
||||
reset: () => setState(createInitialState(config.defaultPageSize || 10))
|
||||
};
|
||||
}
|
||||
@ -4,37 +4,34 @@ import { PlusOutlined, EditOutlined, DeleteOutlined, QuestionCircleOutlined } fr
|
||||
import * as AntdIcons from '@ant-design/icons';
|
||||
import { getMenuTree, createMenu, updateMenu, deleteMenu } from './service';
|
||||
import IconSelect from '@/components/IconSelect';
|
||||
import { useTableData } from '@/hooks/useTableData';
|
||||
|
||||
const MenuPage: React.FC = () => {
|
||||
const [menus, setMenus] = useState<MenuDTO[]>([]);
|
||||
const {
|
||||
list: menus,
|
||||
pagination,
|
||||
loading,
|
||||
loadData: fetchMenus,
|
||||
onPageChange,
|
||||
handleCreate,
|
||||
handleUpdate,
|
||||
handleDelete
|
||||
} = useTableData({
|
||||
service: {
|
||||
baseUrl: '/api/v1/menu'
|
||||
},
|
||||
defaultParams: {
|
||||
sortField: 'sort',
|
||||
sortOrder: 'asc'
|
||||
}
|
||||
});
|
||||
|
||||
const [menuTreeData, setMenuTreeData] = useState<MenuDTO[]>([]);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [iconSelectVisible, setIconSelectVisible] = useState(false);
|
||||
const [editingMenu, setEditingMenu] = useState<MenuDTO | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [menuList, treeData] = await Promise.all([
|
||||
getMenuTree(),
|
||||
getMenuTreeWithoutButtons()
|
||||
]);
|
||||
setMenus(menuList);
|
||||
setMenuTreeData(treeData);
|
||||
} catch (error) {
|
||||
console.error('获取菜单列表失败:', error);
|
||||
message.error('获取菜单列表失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const getIcon = (iconName: string | undefined) => {
|
||||
if (!iconName) return null;
|
||||
const iconKey = `${iconName.charAt(0).toUpperCase() + iconName.slice(1)}Outlined`;
|
||||
@ -63,22 +60,6 @@ const MenuPage: React.FC = () => {
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这个菜单吗?',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await deleteMenu(id);
|
||||
message.success('删除成功');
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
@ -95,7 +76,7 @@ const MenuPage: React.FC = () => {
|
||||
message.success('创建成功');
|
||||
}
|
||||
setModalVisible(false);
|
||||
fetchData();
|
||||
fetchMenus();
|
||||
} catch (error) {
|
||||
message.error('操作失败');
|
||||
}
|
||||
@ -201,9 +182,10 @@ const MenuPage: React.FC = () => {
|
||||
columns={columns}
|
||||
dataSource={menus}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
pagination={pagination}
|
||||
size="middle"
|
||||
bordered
|
||||
onChange={onPageChange}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@ -1,37 +1,42 @@
|
||||
import request from '@/utils/request';
|
||||
import type { Page } from '@/types/base/page';
|
||||
import type { MenuResponse, MenuRequest, MenuQuery } from './types';
|
||||
|
||||
export const getMenus = async (params?: MenuQuery) => {
|
||||
return request.get('/api/v1/menu', {
|
||||
params,
|
||||
errorMessage: '获取菜单列表失败,请刷新重试'
|
||||
});
|
||||
};
|
||||
const BASE_URL = '/api/v1/menu';
|
||||
|
||||
export const createMenu = async (data: MenuRequest) => {
|
||||
return request.post('/api/v1/menu', data, {
|
||||
errorMessage: '创建菜单失败,请稍后重试'
|
||||
});
|
||||
};
|
||||
// 获取菜单列表(分页)
|
||||
export const getMenus = (params?: MenuQuery) =>
|
||||
request.get<Page<MenuResponse>>(`${BASE_URL}/page`, {
|
||||
params,
|
||||
transform: true
|
||||
});
|
||||
|
||||
export const updateMenu = async (id: number, data: MenuRequest) => {
|
||||
return request.put(`/api/v1/menu/${id}`, data, {
|
||||
errorMessage: '更新菜单失败,请稍后重试'
|
||||
});
|
||||
};
|
||||
// 获取菜单树
|
||||
export const getMenuTree = () =>
|
||||
request.get<MenuResponse[]>(`${BASE_URL}/tree`, {
|
||||
transform: true
|
||||
});
|
||||
|
||||
export const deleteMenu = async (id: number) => {
|
||||
return request.delete(`/api/v1/menu/${id}`, {
|
||||
errorMessage: '删除菜单失败,请稍后重试'
|
||||
});
|
||||
};
|
||||
// 获取当前用户菜单
|
||||
export const getCurrentUserMenus = () =>
|
||||
request.get<MenuResponse[]>(`${BASE_URL}/current`, {
|
||||
transform: true
|
||||
});
|
||||
|
||||
export const getMenuTree = async () => {
|
||||
return request.get('/api/v1/menu/tree', {
|
||||
errorMessage: '获取菜单树失败,请刷新重试'
|
||||
});
|
||||
};
|
||||
// 创建菜单
|
||||
export const createMenu = (data: MenuRequest) =>
|
||||
request.post<MenuResponse>(BASE_URL, data, {
|
||||
successMessage: '创建菜单成功'
|
||||
});
|
||||
|
||||
export const getCurrentUserMenus = async (): Promise<MenuResponse[]> => {
|
||||
return request.get('/api/v1/menu/current');
|
||||
};
|
||||
// 更新菜单
|
||||
export const updateMenu = (id: number, data: MenuRequest) =>
|
||||
request.put<MenuResponse>(`${BASE_URL}/${id}`, data, {
|
||||
successMessage: '更新菜单成功'
|
||||
});
|
||||
|
||||
// 删除菜单
|
||||
export const deleteMenu = (id: number) =>
|
||||
request.delete(`${BASE_URL}/${id}`, {
|
||||
successMessage: '删除菜单成功'
|
||||
});
|
||||
@ -20,20 +20,11 @@ const UserPage: React.FC = () => {
|
||||
handleDelete
|
||||
} = useTableData({
|
||||
service: {
|
||||
list: getUsers,
|
||||
create: createUser,
|
||||
update: updateUser,
|
||||
delete: deleteUser
|
||||
baseUrl: '/api/v1/user'
|
||||
},
|
||||
defaultParams: {
|
||||
sortField: 'createTime',
|
||||
sortOrder: 'desc'
|
||||
},
|
||||
message: {
|
||||
createSuccess: '用户创建成功',
|
||||
updateSuccess: '用户更新成功',
|
||||
deleteSuccess: '用户删除成功',
|
||||
loadError: '获取用户列表失败'
|
||||
}
|
||||
});
|
||||
|
||||
@ -72,39 +63,29 @@ const UserPage: React.FC = () => {
|
||||
const values = await passwordForm.validateFields();
|
||||
if (editingUser) {
|
||||
await resetPassword(editingUser.id, values.password);
|
||||
message.success('密码重置成功');
|
||||
setResetPasswordModalVisible(false);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('密码重置失败');
|
||||
console.error('密码重置失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const values = await form.validateFields()
|
||||
.catch(() => {
|
||||
// 表单验证失败,直接返回
|
||||
return Promise.reject();
|
||||
});
|
||||
|
||||
const request = editingUser
|
||||
? updateUser(editingUser.id, {
|
||||
...values,
|
||||
version: editingUser.version
|
||||
})
|
||||
: createUser(values);
|
||||
|
||||
return request
|
||||
.then(res => {
|
||||
message.success(editingUser ? '更新用户成功' : '新增用户成功');
|
||||
setModalVisible(false);
|
||||
fetchUsers();
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.code) {
|
||||
message.error(error.message);
|
||||
}
|
||||
});
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
if (editingUser) {
|
||||
await handleUpdate(editingUser.id, {
|
||||
...values,
|
||||
version: editingUser.version
|
||||
});
|
||||
} else {
|
||||
await handleCreate(values);
|
||||
}
|
||||
setModalVisible(false);
|
||||
fetchUsers();
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const getTreeData = (deps: DepartmentDTO[]): any[] => {
|
||||
|
||||
@ -1,43 +1,36 @@
|
||||
import request from '@/utils/request';
|
||||
import type {BaseResponse} from '@/types/api';
|
||||
import type {UserResponse, UserRequest, UserQuery} from './types';
|
||||
import type {Page} from '@/types/base/page';
|
||||
import type { Page } from '@/types/base/page';
|
||||
import type { UserResponse, UserRequest, UserQuery } from './types';
|
||||
|
||||
export const getUsers = async (params?: UserQuery) => {
|
||||
return request.get<Page<UserResponse>>('/api/v1/user/page', {
|
||||
const BASE_URL = '/api/v1/user';
|
||||
|
||||
// 获取用户列表(分页)
|
||||
export const getUsers = (params?: UserQuery) =>
|
||||
request.get<Page<UserResponse>>(`${BASE_URL}/page`, {
|
||||
params,
|
||||
retryCount: 3,
|
||||
retryDelay: 1000,
|
||||
cancelPrevious: true,
|
||||
transform: true,
|
||||
customMessage: '获取用户列表失败',
|
||||
beforeRequest: () => {
|
||||
console.log('开始请求');
|
||||
},
|
||||
afterResponse: () => {
|
||||
console.log('请求完成');
|
||||
}
|
||||
transform: true
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserList = async (params?: UserQuery) => {
|
||||
return request.get<BaseResponse<UserResponse[]>>('/api/v1/user/list', {
|
||||
params
|
||||
// 创建用户
|
||||
export const createUser = (data: UserRequest) =>
|
||||
request.post<UserResponse>(BASE_URL, data, {
|
||||
successMessage: '创建用户成功'
|
||||
});
|
||||
};
|
||||
|
||||
export const createUser = async (data: UserRequest): Promise<Response> => {
|
||||
return request.post('/api/v1/user', data, { hideMessage: true });
|
||||
};
|
||||
// 更新用户
|
||||
export const updateUser = (id: number, data: UserRequest) =>
|
||||
request.put<UserResponse>(`${BASE_URL}/${id}`, data, {
|
||||
successMessage: '更新用户成功'
|
||||
});
|
||||
|
||||
export const updateUser = async (id: number, data: UserRequest) => {
|
||||
return request.put(`/api/v1/user/${id}`, data);
|
||||
};
|
||||
// 删除用户
|
||||
export const deleteUser = (id: number) =>
|
||||
request.delete(`${BASE_URL}/${id}`, {
|
||||
successMessage: '删除用户成功'
|
||||
});
|
||||
|
||||
export const deleteUser = async (id: number) => {
|
||||
return request.delete<void>(`/api/v1/user/${id}`, {});
|
||||
};
|
||||
|
||||
export const resetPassword = async (id: number, password: string) => {
|
||||
return request.post(`/api/v1/user/${id}/reset-password`, password);
|
||||
};
|
||||
// 重置密码
|
||||
export const resetPassword = (id: number, password: string) =>
|
||||
request.post(`${BASE_URL}/${id}/reset-password`, password, {
|
||||
successMessage: '密码重置成功'
|
||||
});
|
||||
|
||||
@ -9,20 +9,22 @@ export interface Response<T = any> {
|
||||
}
|
||||
|
||||
export interface RequestOptions extends AxiosRequestConfig {
|
||||
hideMessage?: boolean;
|
||||
customMessage?: string;
|
||||
successMessage?: string;
|
||||
errorMessage?: string;
|
||||
transform?: boolean;
|
||||
retryCount?: number;
|
||||
retryDelay?: number;
|
||||
retryAttempt?: number;
|
||||
cancelPrevious?: boolean;
|
||||
skipQueue?: boolean;
|
||||
onUploadProgress?: (progressEvent: any) => void;
|
||||
onDownloadProgress?: (progressEvent: any) => void;
|
||||
beforeRequest?: (config: AxiosRequestConfig) => void | Promise<void>;
|
||||
afterResponse?: (response: any) => void | Promise<void>;
|
||||
}
|
||||
|
||||
// 默认请求配置
|
||||
const defaultConfig: Partial<RequestOptions> = {
|
||||
transform: true,
|
||||
retryCount: 3,
|
||||
retryDelay: 1000,
|
||||
timeout: 30000
|
||||
};
|
||||
|
||||
// 创建请求实例
|
||||
const request = axios.create({
|
||||
baseURL: '',
|
||||
timeout: 30000,
|
||||
@ -32,15 +34,23 @@ const request = axios.create({
|
||||
}
|
||||
});
|
||||
|
||||
// 创建请求配置
|
||||
const createRequestConfig = (options?: Partial<RequestOptions>): RequestOptions => {
|
||||
return {
|
||||
...defaultConfig,
|
||||
...options
|
||||
};
|
||||
};
|
||||
|
||||
const responseHandler = (response: AxiosResponse<Response<any>>) => {
|
||||
const res = response.data;
|
||||
const config = response.config as RequestOptions;
|
||||
|
||||
if (res.success && res.code === 200) {
|
||||
!config.hideMessage && message.success(config.customMessage || res.message);
|
||||
config.successMessage && message.success(config.successMessage);
|
||||
return config.transform ? res.data : res;
|
||||
} else {
|
||||
!config.hideMessage && message.error(config.customMessage || res.message);
|
||||
message.error(config.errorMessage || res.message);
|
||||
return Promise.reject(res);
|
||||
}
|
||||
};
|
||||
@ -76,7 +86,7 @@ const errorHandler = (error: any) => {
|
||||
msg = '服务器异常,请稍后再试!';
|
||||
}
|
||||
|
||||
!config.hideMessage && message.error(config.customMessage || msg);
|
||||
message.error(config.errorMessage || msg);
|
||||
return Promise.reject(error);
|
||||
};
|
||||
|
||||
@ -95,31 +105,31 @@ request.interceptors.response.use(responseHandler, errorHandler);
|
||||
|
||||
const http = {
|
||||
get: <T = any>(url: string, config?: RequestOptions) =>
|
||||
request.get<any, Response<T>>(url, { transform: true, ...config }),
|
||||
request.get<any, Response<T>>(url, createRequestConfig({transform: true, ...config})),
|
||||
|
||||
post: <T = any>(url: string, data?: any, config?: RequestOptions) =>
|
||||
request.post<any, Response<T>>(url, data, { transform: true, ...config }),
|
||||
request.post<any, Response<T>>(url, data, createRequestConfig({transform: true, ...config})),
|
||||
|
||||
put: <T = any>(url: string, data?: any, config?: RequestOptions) =>
|
||||
request.put<any, Response<T>>(url, data, { transform: true, ...config }),
|
||||
request.put<any, Response<T>>(url, data, createRequestConfig({transform: true, ...config})),
|
||||
|
||||
delete: <T = any>(url: string, config?: RequestOptions) =>
|
||||
request.delete<any, Response<T>>(url, { transform: true, ...config }),
|
||||
request.delete<any, Response<T>>(url, createRequestConfig({transform: true, ...config})),
|
||||
|
||||
upload: <T = any>(url: string, file: File, config?: RequestOptions) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return request.post<any, Response<T>>(url, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
return request.post<any, Response<T>>(url, formData, createRequestConfig({
|
||||
headers: {'Content-Type': 'multipart/form-data'},
|
||||
...config
|
||||
});
|
||||
}));
|
||||
},
|
||||
|
||||
download: (url: string, filename?: string, config?: RequestOptions) =>
|
||||
request.get(url, {
|
||||
request.get(url, createRequestConfig({
|
||||
responseType: 'blob',
|
||||
...config
|
||||
}).then(response => {
|
||||
})).then(response => {
|
||||
const blob = new Blob([response.data]);
|
||||
const downloadUrl = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
@ -132,182 +142,4 @@ const http = {
|
||||
})
|
||||
};
|
||||
|
||||
export class RequestCancel {
|
||||
private static pendingMap = new Map<string, AbortController>();
|
||||
|
||||
static add(config: AxiosRequestConfig) {
|
||||
const url = [config.method, config.url].join('&');
|
||||
this.remove(url);
|
||||
const controller = new AbortController();
|
||||
config.signal = controller.signal;
|
||||
this.pendingMap.set(url, controller);
|
||||
}
|
||||
|
||||
static remove(url: string) {
|
||||
const controller = this.pendingMap.get(url);
|
||||
controller?.abort();
|
||||
this.pendingMap.delete(url);
|
||||
}
|
||||
|
||||
static removeAll() {
|
||||
this.pendingMap.forEach(controller => controller.abort());
|
||||
this.pendingMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
const retryRequest = async (error: any) => {
|
||||
const config = error.config as RequestOptions;
|
||||
if (!config || !config.retryCount) return Promise.reject(error);
|
||||
|
||||
config.__retryCount = config.__retryCount || 0;
|
||||
if (config.__retryCount >= config.retryCount) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
config.__retryCount += 1;
|
||||
await new Promise(resolve => setTimeout(resolve, config.retryDelay || 1000));
|
||||
return request(config);
|
||||
};
|
||||
|
||||
const logRequest = (config: AxiosRequestConfig) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(`[Request] ${config.method?.toUpperCase()} ${config.url}`, {
|
||||
data: config.data,
|
||||
params: config.params
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const logResponse = (response: AxiosResponse) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(`[Response] ${response.config.url}`, response.data);
|
||||
}
|
||||
};
|
||||
|
||||
export enum ErrorType {
|
||||
Timeout = 'TIMEOUT',
|
||||
Network = 'NETWORK',
|
||||
Business = 'BUSINESS',
|
||||
Auth = 'AUTH',
|
||||
Server = 'SERVER'
|
||||
}
|
||||
|
||||
export interface RequestError extends Error {
|
||||
type: ErrorType;
|
||||
code?: number;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
const createRequestError = (type: ErrorType, message: string, data?: any): RequestError => {
|
||||
const error = new Error(message) as RequestError;
|
||||
error.type = type;
|
||||
error.data = data;
|
||||
return error;
|
||||
};
|
||||
|
||||
class RequestQueue {
|
||||
private queue: Set<string> = new Set();
|
||||
|
||||
add(config: AxiosRequestConfig) {
|
||||
const url = [config.method, config.url].join('&');
|
||||
this.queue.add(url);
|
||||
}
|
||||
|
||||
remove(config: AxiosRequestConfig) {
|
||||
const url = [config.method, config.url].join('&');
|
||||
this.queue.delete(url);
|
||||
}
|
||||
|
||||
size() {
|
||||
return this.queue.size;
|
||||
}
|
||||
}
|
||||
|
||||
const requestQueue = new RequestQueue();
|
||||
|
||||
export interface CancelablePromise<T> extends Promise<T> {
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
const createCancelableRequest = <T>(promise: Promise<T>, controller: AbortController): CancelablePromise<T> => {
|
||||
const cancelablePromise = promise as CancelablePromise<T>;
|
||||
cancelablePromise.cancel = () => controller.abort();
|
||||
return cancelablePromise;
|
||||
};
|
||||
|
||||
const debounceRequest = <T>(fn: () => Promise<T>, delay: number): (() => Promise<T>) => {
|
||||
let timer: NodeJS.Timeout;
|
||||
return () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
fn().then(resolve).catch(reject);
|
||||
}, delay);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
class RequestCache {
|
||||
private cache = new Map<string, {
|
||||
data: any;
|
||||
timestamp: number;
|
||||
ttl: number;
|
||||
}>();
|
||||
|
||||
set(key: string, data: any, ttl: number = 5000) {
|
||||
this.cache.set(key, {
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
ttl
|
||||
});
|
||||
}
|
||||
|
||||
get(key: string) {
|
||||
const cached = this.cache.get(key);
|
||||
if (!cached) return null;
|
||||
if (Date.now() - cached.timestamp > cached.ttl) {
|
||||
this.cache.delete(key);
|
||||
return null;
|
||||
}
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
interface RequestInterceptors {
|
||||
beforeRequest?: (config: RequestOptions) => RequestOptions | Promise<RequestOptions>;
|
||||
afterResponse?: (response: any) => any;
|
||||
onError?: (error: any) => any;
|
||||
}
|
||||
|
||||
interface PriorityRequest {
|
||||
priority: number;
|
||||
request: () => Promise<any>;
|
||||
}
|
||||
|
||||
class PriorityQueue {
|
||||
private queue: PriorityRequest[] = [];
|
||||
private processing = false;
|
||||
|
||||
add(request: PriorityRequest) {
|
||||
this.queue.push(request);
|
||||
this.queue.sort((a, b) => b.priority - a.priority);
|
||||
if (!this.processing) {
|
||||
this.process();
|
||||
}
|
||||
}
|
||||
|
||||
private async process() {
|
||||
this.processing = true;
|
||||
while (this.queue.length > 0) {
|
||||
const { request } = this.queue.shift()!;
|
||||
await request();
|
||||
}
|
||||
this.processing = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default http;
|
||||
Loading…
Reference in New Issue
Block a user