可用版本
This commit is contained in:
parent
ada0b0316c
commit
b01d9a630a
71
frontend/src/hooks/usePageData.ts
Normal file
71
frontend/src/hooks/usePageData.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
import type { Page } from '@/types/base/page';
|
||||||
|
|
||||||
|
interface UsePageDataProps<T, P = any> {
|
||||||
|
service: (params: P) => Promise<Page<T>>;
|
||||||
|
defaultParams?: Partial<P>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageState<T> {
|
||||||
|
list: T[];
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
pageSize: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePageData<T, P = any>({ service, defaultParams }: UsePageDataProps<T, P>) {
|
||||||
|
const [state, setState] = useState<PageState<T>>({
|
||||||
|
list: [],
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadData = useCallback(async (params?: Partial<P>) => {
|
||||||
|
setState(prev => ({ ...prev, loading: true }));
|
||||||
|
try {
|
||||||
|
const pageData = await service({
|
||||||
|
...defaultParams,
|
||||||
|
...params,
|
||||||
|
pageNum: state.pagination.current,
|
||||||
|
pageSize: state.pagination.pageSize
|
||||||
|
} as P);
|
||||||
|
|
||||||
|
setState(prev => ({
|
||||||
|
list: pageData?.content || [],
|
||||||
|
pagination: {
|
||||||
|
current: prev.pagination.current,
|
||||||
|
pageSize: prev.pagination.pageSize,
|
||||||
|
total: pageData?.totalElements || 0
|
||||||
|
},
|
||||||
|
loading: false
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
setState(prev => ({ ...prev, loading: false }));
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}, [service, defaultParams, state.pagination]);
|
||||||
|
|
||||||
|
const onPageChange = useCallback((page: number, pageSize?: number) => {
|
||||||
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
pagination: {
|
||||||
|
...prev.pagination,
|
||||||
|
current: page,
|
||||||
|
pageSize: pageSize || prev.pagination.pageSize
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loadData,
|
||||||
|
onPageChange
|
||||||
|
};
|
||||||
|
}
|
||||||
150
frontend/src/hooks/useTableData.ts
Normal file
150
frontend/src/hooks/useTableData.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
import { message, Modal } from 'antd';
|
||||||
|
import type { UseTableDataProps, TableState } from '@/types/table';
|
||||||
|
import { createInitialState, handleTableError } from '@/utils/table';
|
||||||
|
|
||||||
|
export interface TableConfig<T> {
|
||||||
|
message?: {
|
||||||
|
createSuccess?: string;
|
||||||
|
updateSuccess?: string;
|
||||||
|
deleteSuccess?: string;
|
||||||
|
loadError?: string;
|
||||||
|
};
|
||||||
|
// ... 其他配置
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTableData<T extends { id: number }, P = any>({
|
||||||
|
service,
|
||||||
|
defaultParams,
|
||||||
|
config = {}
|
||||||
|
}: UseTableDataProps<T, P>) {
|
||||||
|
const {
|
||||||
|
message: messageConfig = {},
|
||||||
|
defaultPageSize = 10,
|
||||||
|
selection = false,
|
||||||
|
onDataChange
|
||||||
|
} = config;
|
||||||
|
|
||||||
|
const [state, setState] = useState<TableState<T>>(() =>
|
||||||
|
createInitialState<T>(defaultPageSize)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
const loadData = useCallback(async (params?: Partial<P>) => {
|
||||||
|
setState(prev => ({ ...prev, loading: true }));
|
||||||
|
try {
|
||||||
|
const pageData = await service.list({
|
||||||
|
...defaultParams,
|
||||||
|
...params,
|
||||||
|
pageNum: state.pagination.current,
|
||||||
|
pageSize: state.pagination.pageSize
|
||||||
|
} as P);
|
||||||
|
|
||||||
|
const newList = pageData?.content || [];
|
||||||
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
list: newList,
|
||||||
|
pagination: {
|
||||||
|
...prev.pagination,
|
||||||
|
total: pageData?.totalElements || 0
|
||||||
|
},
|
||||||
|
loading: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
onDataChange?.(newList);
|
||||||
|
} catch (error) {
|
||||||
|
setState(prev => ({ ...prev, loading: false }));
|
||||||
|
message.error(messageConfig.loadError || '加载数据失败');
|
||||||
|
}
|
||||||
|
}, [service, defaultParams, state.pagination, onDataChange]);
|
||||||
|
|
||||||
|
// 分页变化
|
||||||
|
const onPageChange = useCallback((page: number, pageSize?: number) => {
|
||||||
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
pagination: {
|
||||||
|
...prev.pagination,
|
||||||
|
current: page,
|
||||||
|
pageSize: pageSize || prev.pagination.pageSize
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
selectedRowKeys,
|
||||||
|
selectedRows
|
||||||
|
}));
|
||||||
|
}, [selection]);
|
||||||
|
|
||||||
|
// 监听分页变化自动加载数据
|
||||||
|
useEffect(() => {
|
||||||
|
loadData();
|
||||||
|
}, [state.pagination.current, state.pagination.pageSize]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loadData,
|
||||||
|
onPageChange,
|
||||||
|
handleCreate,
|
||||||
|
handleUpdate,
|
||||||
|
handleDelete,
|
||||||
|
onSelectChange,
|
||||||
|
// 提供重置方法
|
||||||
|
reset: () => setState(createInitialState<T>(defaultPageSize))
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -13,9 +13,10 @@ import {
|
|||||||
import * as AntdIcons from '@ant-design/icons';
|
import * as AntdIcons from '@ant-design/icons';
|
||||||
import { logout, setMenus, setUserInfo } from '../store/userSlice';
|
import { logout, setMenus, setUserInfo } from '../store/userSlice';
|
||||||
import type { MenuProps } from 'antd';
|
import type { MenuProps } from 'antd';
|
||||||
import { getCurrentUser, getUserMenus } from '../pages/Login/service';
|
import { getCurrentUser } from '../pages/Login/service';
|
||||||
|
import { getCurrentUserMenus } from '@/pages/System/Menu/service';
|
||||||
import { getWeather } from '../services/weather';
|
import { getWeather } from '../services/weather';
|
||||||
import type { MenuDTO } from '../pages/System/Menu/types';
|
import type { MenuResponse } from '@/pages/System/Menu/types';
|
||||||
import type { RootState } from '../store';
|
import type { RootState } from '../store';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import 'dayjs/locale/zh-cn';
|
import 'dayjs/locale/zh-cn';
|
||||||
@ -63,8 +64,7 @@ const BasicLayout: React.FC = () => {
|
|||||||
const userData = await getCurrentUser();
|
const userData = await getCurrentUser();
|
||||||
dispatch(setUserInfo(userData));
|
dispatch(setUserInfo(userData));
|
||||||
|
|
||||||
// 获取并更新用户菜单
|
const menuData = await getCurrentUserMenus();
|
||||||
const menuData = await getUserMenus();
|
|
||||||
dispatch(setMenus(menuData));
|
dispatch(setMenus(menuData));
|
||||||
|
|
||||||
setIsInitialized(true);
|
setIsInitialized(true);
|
||||||
@ -134,7 +134,7 @@ const BasicLayout: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 将菜单数据转换为antd Menu需要的格式
|
// 将菜单数据转换为antd Menu需要的格式
|
||||||
const getMenuItems = (menuList: MenuDTO[]): MenuProps['items'] => {
|
const getMenuItems = (menuList: MenuResponse[]): MenuProps['items'] => {
|
||||||
return menuList.map(menu => ({
|
return menuList.map(menu => ({
|
||||||
key: menu.path || menu.id.toString(),
|
key: menu.path || menu.id.toString(),
|
||||||
icon: getIcon(menu.icon),
|
icon: getIcon(menu.icon),
|
||||||
|
|||||||
@ -2,9 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { Table, Button, Modal, Form, Input, Space, message, Switch, Select, TreeSelect, Tooltip, InputNumber } from 'antd';
|
import { Table, Button, Modal, Form, Input, Space, message, Switch, Select, TreeSelect, Tooltip, InputNumber } from 'antd';
|
||||||
import { PlusOutlined, EditOutlined, DeleteOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
import { PlusOutlined, EditOutlined, DeleteOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
import * as AntdIcons from '@ant-design/icons';
|
import * as AntdIcons from '@ant-design/icons';
|
||||||
import type { MenuDTO } from './types';
|
import { getMenuTree, createMenu, updateMenu, deleteMenu } from './service';
|
||||||
import { MenuTypeEnum, MenuTypeNames } from './types';
|
|
||||||
import { getMenuTree, createMenu, updateMenu, deleteMenu, getMenuTreeWithoutButtons } from './service';
|
|
||||||
import IconSelect from '@/components/IconSelect';
|
import IconSelect from '@/components/IconSelect';
|
||||||
|
|
||||||
const MenuPage: React.FC = () => {
|
const MenuPage: React.FC = () => {
|
||||||
|
|||||||
@ -31,3 +31,9 @@ export const getMenuTree = async () => {
|
|||||||
errorMessage: '获取菜单树失败,请刷新重试'
|
errorMessage: '获取菜单树失败,请刷新重试'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getCurrentUserMenus = async (): Promise<MenuResponse[]> => {
|
||||||
|
return request.get('/api/v1/menu/current', {
|
||||||
|
errorMessage: '获取菜单失败,请刷新页面重试'
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,10 +1,12 @@
|
|||||||
import { BaseResponse } from '@/types/base/response';
|
import { BaseResponse } from '@/types/base/response';
|
||||||
|
|
||||||
export interface MenuResponse extends BaseResponse {
|
export interface MenuResponse extends BaseResponse {
|
||||||
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
parentId?: number;
|
parentId?: number;
|
||||||
sort: number;
|
sort: number;
|
||||||
|
enabled: boolean;
|
||||||
children?: MenuResponse[];
|
children?: MenuResponse[];
|
||||||
}
|
}
|
||||||
@ -1,51 +1,53 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Table, Button, Modal, Form, Input, Space, message, Switch, TreeSelect } from 'antd';
|
import { Table, Button, Modal, Form, Input, Space, message, Switch, TreeSelect } from 'antd';
|
||||||
import { PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, TeamOutlined } from '@ant-design/icons';
|
import { PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, TeamOutlined } from '@ant-design/icons';
|
||||||
import type { UserDTO } from './types';
|
import type { UserResponse } from './types';
|
||||||
import type { DepartmentDTO } from '../Department/types';
|
import type { DepartmentDTO } from '../Department/types';
|
||||||
import { getUsers, createUser, updateUser, deleteUser, resetPassword } from './service';
|
import { getUsers, createUser, updateUser, deleteUser, resetPassword } from './service';
|
||||||
import { getDepartmentTree } from '../Department/service';
|
import { getDepartmentTree } from '../Department/service';
|
||||||
import RoleModal from './components/RoleModal';
|
import { convertToPageParams, convertToPageInfo } from '@/utils/page';
|
||||||
|
import { useTableData } from '@/hooks/useTableData';
|
||||||
|
// import RoleModal from './components/RoleModal';
|
||||||
|
|
||||||
const UserPage: React.FC = () => {
|
const UserPage: React.FC = () => {
|
||||||
const [users, setUsers] = useState<UserDTO[]>([]);
|
const {
|
||||||
|
list: users,
|
||||||
|
pagination,
|
||||||
|
loading,
|
||||||
|
loadData: fetchUsers,
|
||||||
|
onPageChange,
|
||||||
|
handleCreate,
|
||||||
|
handleUpdate,
|
||||||
|
handleDelete
|
||||||
|
} = useTableData({
|
||||||
|
service: {
|
||||||
|
list: getUsers,
|
||||||
|
create: createUser,
|
||||||
|
update: updateUser,
|
||||||
|
delete: deleteUser
|
||||||
|
},
|
||||||
|
defaultParams: {
|
||||||
|
sortField: 'createTime',
|
||||||
|
sortOrder: 'desc'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
createSuccess: '用户创建成功',
|
||||||
|
updateSuccess: '用户更新成功',
|
||||||
|
deleteSuccess: '用户删除成功',
|
||||||
|
loadError: '获取用户列表失败'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const [departments, setDepartments] = useState<DepartmentDTO[]>([]);
|
const [departments, setDepartments] = useState<DepartmentDTO[]>([]);
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
const [roleModalVisible, setRoleModalVisible] = useState(false);
|
|
||||||
const [resetPasswordModalVisible, setResetPasswordModalVisible] = useState(false);
|
const [resetPasswordModalVisible, setResetPasswordModalVisible] = useState(false);
|
||||||
const [editingUser, setEditingUser] = useState<UserDTO | null>(null);
|
const [editingUser, setEditingUser] = useState<UserResponse | null>(null);
|
||||||
const [selectedUser, setSelectedUser] = useState<UserDTO | null>(null);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [passwordForm] = Form.useForm();
|
const [passwordForm] = Form.useForm();
|
||||||
|
|
||||||
const fetchUsers = async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const data = await getUsers({ page: 1, size: 100 });
|
|
||||||
setUsers(data || []);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取用户列表失败:', error);
|
|
||||||
message.error('获取用户列表失败');
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchDepartments = async () => {
|
|
||||||
try {
|
|
||||||
const data = await getDepartmentTree();
|
|
||||||
setDepartments(data || []);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取部门列表失败:', error);
|
|
||||||
message.error('获取部门列表失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUsers();
|
fetchUsers();
|
||||||
fetchDepartments();
|
}, [pagination.current, pagination.pageSize]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
setEditingUser(null);
|
setEditingUser(null);
|
||||||
@ -56,32 +58,16 @@ const UserPage: React.FC = () => {
|
|||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (record: UserDTO) => {
|
const handleEdit = (record: UserResponse) => {
|
||||||
setEditingUser(record);
|
setEditingUser(record);
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
...record,
|
...record,
|
||||||
password: undefined
|
password: 'UNCHANGED_PASSWORD'
|
||||||
});
|
});
|
||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (id: number) => {
|
const handleResetPassword = (record: UserResponse) => {
|
||||||
Modal.confirm({
|
|
||||||
title: '确认删除',
|
|
||||||
content: '确定要删除这个用户吗?',
|
|
||||||
onOk: async () => {
|
|
||||||
try {
|
|
||||||
await deleteUser(id);
|
|
||||||
message.success('删除成功');
|
|
||||||
fetchUsers();
|
|
||||||
} catch (error) {
|
|
||||||
message.error('删除失败');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleResetPassword = (record: UserDTO) => {
|
|
||||||
setEditingUser(record);
|
setEditingUser(record);
|
||||||
passwordForm.resetFields();
|
passwordForm.resetFields();
|
||||||
setResetPasswordModalVisible(true);
|
setResetPasswordModalVisible(true);
|
||||||
@ -91,7 +77,7 @@ const UserPage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const values = await passwordForm.validateFields();
|
const values = await passwordForm.validateFields();
|
||||||
if (editingUser) {
|
if (editingUser) {
|
||||||
await resetPassword(editingUser.id, values.password);
|
await resetPassword(editingUser.id);
|
||||||
message.success('密码重置成功');
|
message.success('密码重置成功');
|
||||||
setResetPasswordModalVisible(false);
|
setResetPasswordModalVisible(false);
|
||||||
}
|
}
|
||||||
@ -106,6 +92,7 @@ const UserPage: React.FC = () => {
|
|||||||
if (editingUser) {
|
if (editingUser) {
|
||||||
await updateUser(editingUser.id, {
|
await updateUser(editingUser.id, {
|
||||||
...values,
|
...values,
|
||||||
|
password: values.password === 'UNCHANGED_PASSWORD' ? editingUser.password : values.password,
|
||||||
version: editingUser.version
|
version: editingUser.version
|
||||||
});
|
});
|
||||||
message.success('更新成功');
|
message.success('更新成功');
|
||||||
@ -128,23 +115,23 @@ const UserPage: React.FC = () => {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAssignRoles = (record: UserDTO) => {
|
// const handleAssignRoles = (record: UserResponse) => {
|
||||||
setSelectedUser(record);
|
// setSelectedUser(record);
|
||||||
setRoleModalVisible(true);
|
// setRoleModalVisible(true);
|
||||||
};
|
// };
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '用户名',
|
title: '用户名',
|
||||||
dataIndex: 'username',
|
dataIndex: 'username',
|
||||||
key: 'username',
|
key: 'username',
|
||||||
width: '15%',
|
width: '12%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '昵称',
|
title: '昵称',
|
||||||
dataIndex: 'nickname',
|
dataIndex: 'nickname',
|
||||||
key: 'nickname',
|
key: 'nickname',
|
||||||
width: '15%',
|
width: '12%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '邮箱',
|
title: '邮箱',
|
||||||
@ -156,19 +143,26 @@ const UserPage: React.FC = () => {
|
|||||||
title: '手机号',
|
title: '手机号',
|
||||||
dataIndex: 'phone',
|
dataIndex: 'phone',
|
||||||
key: 'phone',
|
key: 'phone',
|
||||||
width: '15%',
|
width: '12%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '所属部门',
|
title: '角色',
|
||||||
dataIndex: 'deptName',
|
dataIndex: 'roles',
|
||||||
key: 'deptName',
|
key: 'roles',
|
||||||
|
width: '15%',
|
||||||
|
render: (roles: Role[]) => roles?.map(role => role.name).join(', ') || '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
key: 'createTime',
|
||||||
width: '15%',
|
width: '15%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
dataIndex: 'enabled',
|
dataIndex: 'enabled',
|
||||||
key: 'enabled',
|
key: 'enabled',
|
||||||
width: '10%',
|
width: '8%',
|
||||||
render: (enabled: boolean) => (
|
render: (enabled: boolean) => (
|
||||||
<Switch checked={enabled} disabled />
|
<Switch checked={enabled} disabled />
|
||||||
),
|
),
|
||||||
@ -177,7 +171,7 @@ const UserPage: React.FC = () => {
|
|||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
width: '280px',
|
width: '280px',
|
||||||
render: (_: any, record: UserDTO) => (
|
render: (_: any, record: UserResponse) => (
|
||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
@ -186,6 +180,7 @@ const UserPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
|
{/* 注释掉角色分配按钮
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
icon={<TeamOutlined />}
|
icon={<TeamOutlined />}
|
||||||
@ -194,6 +189,7 @@ const UserPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
分配角色
|
分配角色
|
||||||
</Button>
|
</Button>
|
||||||
|
*/}
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
icon={<KeyOutlined />}
|
icon={<KeyOutlined />}
|
||||||
@ -228,7 +224,13 @@ const UserPage: React.FC = () => {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={users}
|
dataSource={users}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
pagination={false}
|
pagination={{
|
||||||
|
...pagination,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total) => `共 ${total} 条记录`,
|
||||||
|
onChange: onPageChange
|
||||||
|
}}
|
||||||
size="middle"
|
size="middle"
|
||||||
bordered
|
bordered
|
||||||
/>
|
/>
|
||||||
@ -252,6 +254,11 @@ const UserPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<Input placeholder="请输入用户名" disabled={!!editingUser} />
|
<Input placeholder="请输入用户名" disabled={!!editingUser} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
{editingUser && (
|
||||||
|
<Form.Item name="password" hidden>
|
||||||
|
<Input type="hidden" />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
{!editingUser && (
|
{!editingUser && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="password"
|
name="password"
|
||||||
@ -303,6 +310,7 @@ const UserPage: React.FC = () => {
|
|||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* 注释掉RoleModal相关代码
|
||||||
{selectedUser && (
|
{selectedUser && (
|
||||||
<RoleModal
|
<RoleModal
|
||||||
userId={selectedUser.id}
|
userId={selectedUser.id}
|
||||||
@ -318,6 +326,7 @@ const UserPage: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
*/}
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="重置密码"
|
title="重置密码"
|
||||||
|
|||||||
@ -1,8 +1,17 @@
|
|||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
|
import type { BaseResponse } from '@/types/api';
|
||||||
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) => {
|
export const getUsers = async (params?: UserQuery) => {
|
||||||
return request.get('/api/v1/user', {
|
return request.get<Page<UserResponse>>('/api/v1/user/page', {
|
||||||
|
params,
|
||||||
|
errorMessage: '获取用户列表失败,请刷新重试'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUserList = async (params?: UserQuery) => {
|
||||||
|
return request.get<BaseResponse<UserResponse[]>>('/api/v1/user/list', {
|
||||||
params,
|
params,
|
||||||
errorMessage: '获取用户列表失败,请刷新重试'
|
errorMessage: '获取用户列表失败,请刷新重试'
|
||||||
});
|
});
|
||||||
@ -21,8 +30,8 @@ export const updateUser = async (id: number, data: UserRequest) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const deleteUser = async (id: number) => {
|
export const deleteUser = async (id: number) => {
|
||||||
return request.delete(`/api/v1/user/${id}`, {
|
return request.delete<void>(`/api/v1/user/${id}`, {
|
||||||
errorMessage: '删除用户失败,请稍后重试'
|
errorMessage: '删除用户失败'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
|
import type { BaseResponse } from '@/types/base/response';
|
||||||
|
import type { Page } from '@/types/base/page';
|
||||||
|
|
||||||
export interface UserQuery {
|
export interface UserQuery {
|
||||||
|
current?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
sortField?: string;
|
||||||
|
sortOrder?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
nickname?: string;
|
nickname?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
pageNum?: number;
|
|
||||||
pageSize?: number;
|
|
||||||
sortField?: string;
|
|
||||||
sortOrder?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserRequest {
|
export interface UserRequest {
|
||||||
@ -18,13 +21,22 @@ export interface UserRequest {
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserResponse {
|
// 角色类型定义
|
||||||
|
export interface Role {
|
||||||
id: number;
|
id: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
type: number;
|
||||||
|
sort: number;
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserResponse extends BaseResponse {
|
||||||
username: string;
|
username: string;
|
||||||
nickname?: string;
|
nickname?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
createTime: string;
|
roles: Role[];
|
||||||
updateTime: string;
|
|
||||||
}
|
}
|
||||||
29
frontend/src/types/base/page.ts
Normal file
29
frontend/src/types/base/page.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 分页请求参数
|
||||||
|
export interface PageParams {
|
||||||
|
pageNum: number; // 页码(从1开始)
|
||||||
|
pageSize: number; // 每页大小
|
||||||
|
sortField?: string; // 排序字段
|
||||||
|
sortOrder?: string; // 排序方向
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页响应数据
|
||||||
|
export interface Page<T> {
|
||||||
|
content: T[]; // 数据内容
|
||||||
|
totalElements: number; // 总记录数
|
||||||
|
totalPages: number; // 总页数
|
||||||
|
size: number; // 每页大小
|
||||||
|
number: number; // 当前页码(从0开始)
|
||||||
|
numberOfElements: number; // 当前页记录数
|
||||||
|
first: boolean; // 是否第一页
|
||||||
|
last: boolean; // 是否最后一页
|
||||||
|
empty: boolean; // 是否为空
|
||||||
|
pageable: {
|
||||||
|
pageNumber: number;
|
||||||
|
pageSize: number;
|
||||||
|
sort: {
|
||||||
|
empty: boolean;
|
||||||
|
sorted: boolean;
|
||||||
|
unsorted: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
42
frontend/src/types/table.ts
Normal file
42
frontend/src/types/table.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import type { Page } from './base/page';
|
||||||
|
|
||||||
|
// 表格服务接口
|
||||||
|
export interface TableService<T, P = any> {
|
||||||
|
list: (params: P) => Promise<Page<T>>;
|
||||||
|
create?: (data: T) => Promise<T>;
|
||||||
|
update?: (id: number, data: T) => Promise<T>;
|
||||||
|
delete?: (id: number) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格配置项
|
||||||
|
export interface TableConfig<T> {
|
||||||
|
message?: {
|
||||||
|
createSuccess?: string;
|
||||||
|
updateSuccess?: string;
|
||||||
|
deleteSuccess?: string;
|
||||||
|
loadError?: string;
|
||||||
|
};
|
||||||
|
defaultPageSize?: number;
|
||||||
|
selection?: boolean; // 是否支持选择
|
||||||
|
onDataChange?: (data: T[]) => void; // 数据变化回调
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格状态
|
||||||
|
export interface TableState<T> {
|
||||||
|
list: T[];
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
pageSize: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
loading: boolean;
|
||||||
|
selectedRows?: T[];
|
||||||
|
selectedRowKeys?: React.Key[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook参数
|
||||||
|
export interface UseTableDataProps<T, P = any> {
|
||||||
|
service: TableService<T, P>;
|
||||||
|
defaultParams?: Partial<P>;
|
||||||
|
config?: TableConfig<T>;
|
||||||
|
}
|
||||||
29
frontend/src/utils/page.ts
Normal file
29
frontend/src/utils/page.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import type { Page, PageParams } from '@/types/base/page';
|
||||||
|
|
||||||
|
// 默认分页参数
|
||||||
|
const DEFAULT_PAGE_SIZE = 10;
|
||||||
|
const DEFAULT_CURRENT = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换前端分页参数为后端分页参数
|
||||||
|
*/
|
||||||
|
export const convertToPageParams = (params?: {
|
||||||
|
current?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
sortField?: string;
|
||||||
|
sortOrder?: string;
|
||||||
|
}): PageParams => ({
|
||||||
|
pageNum: Math.max(1, params?.current || DEFAULT_CURRENT) - 1, // 转换为从0开始的页码
|
||||||
|
pageSize: params?.pageSize || DEFAULT_PAGE_SIZE,
|
||||||
|
sortField: params?.sortField,
|
||||||
|
sortOrder: params?.sortOrder?.replace('end', '')
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换后端分页数据为前端分页数据
|
||||||
|
*/
|
||||||
|
export const convertToPageInfo = (page?: Page<any>) => ({
|
||||||
|
current: (page?.number || 0) + 1,
|
||||||
|
pageSize: page?.size || DEFAULT_PAGE_SIZE,
|
||||||
|
total: page?.totalElements || 0
|
||||||
|
});
|
||||||
20
frontend/src/utils/table.ts
Normal file
20
frontend/src/utils/table.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import type { TableState } from '@/types/table';
|
||||||
|
|
||||||
|
// 创建初始状态
|
||||||
|
export const createInitialState = <T>(pageSize: number = 10): TableState<T> => ({
|
||||||
|
list: [],
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
pageSize,
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
selectedRows: [],
|
||||||
|
selectedRowKeys: []
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理错误
|
||||||
|
export const handleTableError = (error: any, message: string) => {
|
||||||
|
console.error(message, error);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user