可用版本
This commit is contained in:
parent
a7bc39c70f
commit
582b7669af
@ -1,62 +1,104 @@
|
|||||||
import {useState, useCallback, useEffect} from 'react';
|
import {useState, useCallback, useEffect} from 'react';
|
||||||
import {message, Modal} from 'antd';
|
import {Modal} from 'antd';
|
||||||
import type {UseTableDataProps, TableState} from '@/types/table';
|
import type {TableState} from '@/types/table';
|
||||||
import {createInitialState, handleTableError} from '@/utils/table';
|
import {createInitialState} from '@/utils/table';
|
||||||
|
import request from '@/utils/request';
|
||||||
|
import type {Page} from '@/types/base/page';
|
||||||
|
|
||||||
export interface TableConfig<T> {
|
// 修改TableService接口
|
||||||
message?: {
|
export interface TableService<T, P = any> {
|
||||||
createSuccess?: string;
|
baseUrl: string; // 基础URL
|
||||||
updateSuccess?: string;
|
|
||||||
deleteSuccess?: string;
|
|
||||||
loadError?: string;
|
|
||||||
};
|
|
||||||
// ... 其他配置
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTableData<T extends { id: number }, P = any>({
|
export function useTableData<T extends { id: number }, P = any>({
|
||||||
service,
|
service,
|
||||||
defaultParams,
|
defaultParams,
|
||||||
config = {}
|
config = {}
|
||||||
}: UseTableDataProps<T, P>) {
|
}: {
|
||||||
const {
|
service: TableService<T, P>;
|
||||||
message: messageConfig = {},
|
defaultParams?: Partial<P>;
|
||||||
defaultPageSize = 10,
|
config?: any;
|
||||||
selection = false,
|
}) {
|
||||||
onDataChange
|
|
||||||
} = config;
|
|
||||||
|
|
||||||
const [state, setState] = useState<TableState<T>>(() =>
|
const [state, setState] = useState<TableState<T>>(() =>
|
||||||
createInitialState<T>(defaultPageSize)
|
createInitialState(config.defaultPageSize || 10)
|
||||||
);
|
);
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const loadData = useCallback(async (params?: Partial<P>) => {
|
const loadData = useCallback(async (params?: Partial<P>) => {
|
||||||
setState(prev => ({...prev, loading: true}));
|
setState(prev => ({ ...prev, loading: true }));
|
||||||
try {
|
try {
|
||||||
const pageData = await service.list({
|
const pageData = await request.get<Page<T>>(`${service.baseUrl}/page`, {
|
||||||
|
params: {
|
||||||
...defaultParams,
|
...defaultParams,
|
||||||
...params,
|
...params,
|
||||||
pageNum: state.pagination.current,
|
pageNum: state.pagination.current,
|
||||||
pageSize: state.pagination.pageSize
|
pageSize: state.pagination.pageSize
|
||||||
} as P);
|
},
|
||||||
|
transform: true
|
||||||
|
});
|
||||||
|
|
||||||
const newList = pageData?.content || [];
|
|
||||||
setState(prev => ({
|
setState(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
list: newList,
|
list: pageData?.content || [],
|
||||||
pagination: {
|
pagination: {
|
||||||
...prev.pagination,
|
...prev.pagination,
|
||||||
total: pageData?.totalElements || 0
|
total: pageData?.totalElements || 0
|
||||||
},
|
},
|
||||||
loading: false
|
loading: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
onDataChange?.(newList);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setState(prev => ({...prev, loading: false}));
|
setState(prev => ({ ...prev, loading: false }));
|
||||||
message.error(messageConfig.loadError || '加载数据失败');
|
|
||||||
}
|
}
|
||||||
}, [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) => {
|
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[]) => {
|
const onSelectChange = useCallback((selectedRowKeys: React.Key[], selectedRows: T[]) => {
|
||||||
if (!selection) return;
|
if (!config.selection) return;
|
||||||
setState(prev => ({
|
setState(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
selectedRows
|
selectedRows
|
||||||
}));
|
}));
|
||||||
}, [selection]);
|
}, [config.selection]);
|
||||||
|
|
||||||
// 监听分页变化自动加载数据
|
// 监听分页变化自动加载数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -144,7 +135,6 @@ export function useTableData<T extends { id: number }, P = any>({
|
|||||||
handleUpdate,
|
handleUpdate,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
onSelectChange,
|
onSelectChange,
|
||||||
// 提供重置方法
|
reset: () => setState(createInitialState(config.defaultPageSize || 10))
|
||||||
reset: () => setState(createInitialState<T>(defaultPageSize))
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -4,37 +4,34 @@ import { PlusOutlined, EditOutlined, DeleteOutlined, QuestionCircleOutlined } fr
|
|||||||
import * as AntdIcons from '@ant-design/icons';
|
import * as AntdIcons from '@ant-design/icons';
|
||||||
import { getMenuTree, createMenu, updateMenu, deleteMenu } from './service';
|
import { getMenuTree, createMenu, updateMenu, deleteMenu } from './service';
|
||||||
import IconSelect from '@/components/IconSelect';
|
import IconSelect from '@/components/IconSelect';
|
||||||
|
import { useTableData } from '@/hooks/useTableData';
|
||||||
|
|
||||||
const MenuPage: React.FC = () => {
|
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 [menuTreeData, setMenuTreeData] = useState<MenuDTO[]>([]);
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
const [iconSelectVisible, setIconSelectVisible] = useState(false);
|
const [iconSelectVisible, setIconSelectVisible] = useState(false);
|
||||||
const [editingMenu, setEditingMenu] = useState<MenuDTO | null>(null);
|
const [editingMenu, setEditingMenu] = useState<MenuDTO | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [form] = Form.useForm();
|
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) => {
|
const getIcon = (iconName: string | undefined) => {
|
||||||
if (!iconName) return null;
|
if (!iconName) return null;
|
||||||
const iconKey = `${iconName.charAt(0).toUpperCase() + iconName.slice(1)}Outlined`;
|
const iconKey = `${iconName.charAt(0).toUpperCase() + iconName.slice(1)}Outlined`;
|
||||||
@ -63,22 +60,6 @@ const MenuPage: React.FC = () => {
|
|||||||
setModalVisible(true);
|
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 () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
@ -95,7 +76,7 @@ const MenuPage: React.FC = () => {
|
|||||||
message.success('创建成功');
|
message.success('创建成功');
|
||||||
}
|
}
|
||||||
setModalVisible(false);
|
setModalVisible(false);
|
||||||
fetchData();
|
fetchMenus();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('操作失败');
|
message.error('操作失败');
|
||||||
}
|
}
|
||||||
@ -201,9 +182,10 @@ const MenuPage: React.FC = () => {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={menus}
|
dataSource={menus}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
pagination={false}
|
pagination={pagination}
|
||||||
size="middle"
|
size="middle"
|
||||||
bordered
|
bordered
|
||||||
|
onChange={onPageChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@ -1,37 +1,42 @@
|
|||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
|
import type { Page } from '@/types/base/page';
|
||||||
import type { MenuResponse, MenuRequest, MenuQuery } from './types';
|
import type { MenuResponse, MenuRequest, MenuQuery } from './types';
|
||||||
|
|
||||||
export const getMenus = async (params?: MenuQuery) => {
|
const BASE_URL = '/api/v1/menu';
|
||||||
return request.get('/api/v1/menu', {
|
|
||||||
|
// 获取菜单列表(分页)
|
||||||
|
export const getMenus = (params?: MenuQuery) =>
|
||||||
|
request.get<Page<MenuResponse>>(`${BASE_URL}/page`, {
|
||||||
params,
|
params,
|
||||||
errorMessage: '获取菜单列表失败,请刷新重试'
|
transform: true
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export const createMenu = async (data: MenuRequest) => {
|
// 获取菜单树
|
||||||
return request.post('/api/v1/menu', data, {
|
export const getMenuTree = () =>
|
||||||
errorMessage: '创建菜单失败,请稍后重试'
|
request.get<MenuResponse[]>(`${BASE_URL}/tree`, {
|
||||||
|
transform: true
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export const updateMenu = async (id: number, data: MenuRequest) => {
|
// 获取当前用户菜单
|
||||||
return request.put(`/api/v1/menu/${id}`, data, {
|
export const getCurrentUserMenus = () =>
|
||||||
errorMessage: '更新菜单失败,请稍后重试'
|
request.get<MenuResponse[]>(`${BASE_URL}/current`, {
|
||||||
|
transform: true
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteMenu = async (id: number) => {
|
// 创建菜单
|
||||||
return request.delete(`/api/v1/menu/${id}`, {
|
export const createMenu = (data: MenuRequest) =>
|
||||||
errorMessage: '删除菜单失败,请稍后重试'
|
request.post<MenuResponse>(BASE_URL, data, {
|
||||||
|
successMessage: '创建菜单成功'
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export const getMenuTree = async () => {
|
// 更新菜单
|
||||||
return request.get('/api/v1/menu/tree', {
|
export const updateMenu = (id: number, data: MenuRequest) =>
|
||||||
errorMessage: '获取菜单树失败,请刷新重试'
|
request.put<MenuResponse>(`${BASE_URL}/${id}`, data, {
|
||||||
|
successMessage: '更新菜单成功'
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export const getCurrentUserMenus = async (): Promise<MenuResponse[]> => {
|
// 删除菜单
|
||||||
return request.get('/api/v1/menu/current');
|
export const deleteMenu = (id: number) =>
|
||||||
};
|
request.delete(`${BASE_URL}/${id}`, {
|
||||||
|
successMessage: '删除菜单成功'
|
||||||
|
});
|
||||||
@ -20,20 +20,11 @@ const UserPage: React.FC = () => {
|
|||||||
handleDelete
|
handleDelete
|
||||||
} = useTableData({
|
} = useTableData({
|
||||||
service: {
|
service: {
|
||||||
list: getUsers,
|
baseUrl: '/api/v1/user'
|
||||||
create: createUser,
|
|
||||||
update: updateUser,
|
|
||||||
delete: deleteUser
|
|
||||||
},
|
},
|
||||||
defaultParams: {
|
defaultParams: {
|
||||||
sortField: 'createTime',
|
sortField: 'createTime',
|
||||||
sortOrder: 'desc'
|
sortOrder: 'desc'
|
||||||
},
|
|
||||||
message: {
|
|
||||||
createSuccess: '用户创建成功',
|
|
||||||
updateSuccess: '用户更新成功',
|
|
||||||
deleteSuccess: '用户删除成功',
|
|
||||||
loadError: '获取用户列表失败'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -72,39 +63,29 @@ const UserPage: React.FC = () => {
|
|||||||
const values = await passwordForm.validateFields();
|
const values = await passwordForm.validateFields();
|
||||||
if (editingUser) {
|
if (editingUser) {
|
||||||
await resetPassword(editingUser.id, values.password);
|
await resetPassword(editingUser.id, values.password);
|
||||||
message.success('密码重置成功');
|
|
||||||
setResetPasswordModalVisible(false);
|
setResetPasswordModalVisible(false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('密码重置失败');
|
console.error('密码重置失败:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const values = await form.validateFields()
|
try {
|
||||||
.catch(() => {
|
const values = await form.validateFields();
|
||||||
// 表单验证失败,直接返回
|
if (editingUser) {
|
||||||
return Promise.reject();
|
await handleUpdate(editingUser.id, {
|
||||||
});
|
|
||||||
|
|
||||||
const request = editingUser
|
|
||||||
? updateUser(editingUser.id, {
|
|
||||||
...values,
|
...values,
|
||||||
version: editingUser.version
|
version: editingUser.version
|
||||||
})
|
});
|
||||||
: createUser(values);
|
} else {
|
||||||
|
await handleCreate(values);
|
||||||
return request
|
}
|
||||||
.then(res => {
|
|
||||||
message.success(editingUser ? '更新用户成功' : '新增用户成功');
|
|
||||||
setModalVisible(false);
|
setModalVisible(false);
|
||||||
fetchUsers();
|
fetchUsers();
|
||||||
})
|
} catch (error) {
|
||||||
.catch(error => {
|
console.error('操作失败:', error);
|
||||||
if (error.code) {
|
|
||||||
message.error(error.message);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTreeData = (deps: DepartmentDTO[]): any[] => {
|
const getTreeData = (deps: DepartmentDTO[]): any[] => {
|
||||||
|
|||||||
@ -1,43 +1,36 @@
|
|||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
import type {BaseResponse} from '@/types/api';
|
import type { Page } from '@/types/base/page';
|
||||||
import type {UserResponse, UserRequest, UserQuery} from './types';
|
import type { UserResponse, UserRequest, UserQuery } from './types';
|
||||||
import type {Page} from '@/types/base/page';
|
|
||||||
|
|
||||||
export const getUsers = async (params?: UserQuery) => {
|
const BASE_URL = '/api/v1/user';
|
||||||
return request.get<Page<UserResponse>>('/api/v1/user/page', {
|
|
||||||
|
// 获取用户列表(分页)
|
||||||
|
export const getUsers = (params?: UserQuery) =>
|
||||||
|
request.get<Page<UserResponse>>(`${BASE_URL}/page`, {
|
||||||
params,
|
params,
|
||||||
retryCount: 3,
|
transform: true
|
||||||
retryDelay: 1000,
|
|
||||||
cancelPrevious: true,
|
|
||||||
transform: true,
|
|
||||||
customMessage: '获取用户列表失败',
|
|
||||||
beforeRequest: () => {
|
|
||||||
console.log('开始请求');
|
|
||||||
},
|
|
||||||
afterResponse: () => {
|
|
||||||
console.log('请求完成');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export const getUserList = async (params?: UserQuery) => {
|
// 创建用户
|
||||||
return request.get<BaseResponse<UserResponse[]>>('/api/v1/user/list', {
|
export const createUser = (data: UserRequest) =>
|
||||||
params
|
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 = (id: number, password: string) =>
|
||||||
};
|
request.post(`${BASE_URL}/${id}/reset-password`, password, {
|
||||||
|
successMessage: '密码重置成功'
|
||||||
export const resetPassword = async (id: number, password: string) => {
|
});
|
||||||
return request.post(`/api/v1/user/${id}/reset-password`, password);
|
|
||||||
};
|
|
||||||
|
|||||||
@ -9,20 +9,22 @@ export interface Response<T = any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestOptions extends AxiosRequestConfig {
|
export interface RequestOptions extends AxiosRequestConfig {
|
||||||
hideMessage?: boolean;
|
successMessage?: string;
|
||||||
customMessage?: string;
|
errorMessage?: string;
|
||||||
transform?: boolean;
|
transform?: boolean;
|
||||||
retryCount?: number;
|
retryCount?: number;
|
||||||
retryDelay?: 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({
|
const request = axios.create({
|
||||||
baseURL: '',
|
baseURL: '',
|
||||||
timeout: 30000,
|
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 responseHandler = (response: AxiosResponse<Response<any>>) => {
|
||||||
const res = response.data;
|
const res = response.data;
|
||||||
const config = response.config as RequestOptions;
|
const config = response.config as RequestOptions;
|
||||||
|
|
||||||
if (res.success && res.code === 200) {
|
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;
|
return config.transform ? res.data : res;
|
||||||
} else {
|
} else {
|
||||||
!config.hideMessage && message.error(config.customMessage || res.message);
|
message.error(config.errorMessage || res.message);
|
||||||
return Promise.reject(res);
|
return Promise.reject(res);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -76,7 +86,7 @@ const errorHandler = (error: any) => {
|
|||||||
msg = '服务器异常,请稍后再试!';
|
msg = '服务器异常,请稍后再试!';
|
||||||
}
|
}
|
||||||
|
|
||||||
!config.hideMessage && message.error(config.customMessage || msg);
|
message.error(config.errorMessage || msg);
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -95,31 +105,31 @@ request.interceptors.response.use(responseHandler, errorHandler);
|
|||||||
|
|
||||||
const http = {
|
const http = {
|
||||||
get: <T = any>(url: string, config?: RequestOptions) =>
|
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) =>
|
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) =>
|
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) =>
|
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) => {
|
upload: <T = any>(url: string, file: File, config?: RequestOptions) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
return request.post<any, Response<T>>(url, formData, {
|
return request.post<any, Response<T>>(url, formData, createRequestConfig({
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
headers: {'Content-Type': 'multipart/form-data'},
|
||||||
...config
|
...config
|
||||||
});
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
download: (url: string, filename?: string, config?: RequestOptions) =>
|
download: (url: string, filename?: string, config?: RequestOptions) =>
|
||||||
request.get(url, {
|
request.get(url, createRequestConfig({
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
...config
|
...config
|
||||||
}).then(response => {
|
})).then(response => {
|
||||||
const blob = new Blob([response.data]);
|
const blob = new Blob([response.data]);
|
||||||
const downloadUrl = window.URL.createObjectURL(blob);
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
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;
|
export default http;
|
||||||
Loading…
Reference in New Issue
Block a user