From b01d9a630a9d62f08f79a1fdfd6571c7d3125b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=9A=E8=BE=B0=E5=85=88=E7=94=9F?= Date: Sat, 30 Nov 2024 17:24:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=AF=E7=94=A8=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/usePageData.ts | 71 ++++++++ frontend/src/hooks/useTableData.ts | 150 +++++++++++++++++ frontend/src/layouts/BasicLayout.tsx | 10 +- frontend/src/pages/System/Menu/index.tsx | 4 +- frontend/src/pages/System/Menu/service.ts | 6 + .../src/pages/System/Menu/types/response.ts | 2 + frontend/src/pages/System/User/index.tsx | 153 +++++++++--------- frontend/src/pages/System/User/service.ts | 15 +- frontend/src/pages/System/User/types.ts | 26 ++- frontend/src/types/base/page.ts | 29 ++++ frontend/src/types/table.ts | 42 +++++ frontend/src/utils/page.ts | 29 ++++ frontend/src/utils/table.ts | 20 +++ 13 files changed, 467 insertions(+), 90 deletions(-) create mode 100644 frontend/src/hooks/usePageData.ts create mode 100644 frontend/src/hooks/useTableData.ts create mode 100644 frontend/src/types/base/page.ts create mode 100644 frontend/src/types/table.ts create mode 100644 frontend/src/utils/page.ts create mode 100644 frontend/src/utils/table.ts diff --git a/frontend/src/hooks/usePageData.ts b/frontend/src/hooks/usePageData.ts new file mode 100644 index 00000000..c5b1e210 --- /dev/null +++ b/frontend/src/hooks/usePageData.ts @@ -0,0 +1,71 @@ +import { useState, useCallback } from 'react'; +import type { Page } from '@/types/base/page'; + +interface UsePageDataProps { + service: (params: P) => Promise>; + defaultParams?: Partial

; +} + +interface PageState { + list: T[]; + pagination: { + current: number; + pageSize: number; + total: number; + }; + loading: boolean; +} + +export function usePageData({ service, defaultParams }: UsePageDataProps) { + const [state, setState] = useState>({ + list: [], + pagination: { + current: 1, + pageSize: 10, + total: 0 + }, + loading: false + }); + + const loadData = useCallback(async (params?: Partial

) => { + 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 + }; +} \ No newline at end of file diff --git a/frontend/src/hooks/useTableData.ts b/frontend/src/hooks/useTableData.ts new file mode 100644 index 00000000..4bedc181 --- /dev/null +++ b/frontend/src/hooks/useTableData.ts @@ -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 { + message?: { + createSuccess?: string; + updateSuccess?: string; + deleteSuccess?: string; + loadError?: string; + }; + // ... 其他配置 +} + +export function useTableData({ + service, + defaultParams, + config = {} +}: UseTableDataProps) { + const { + message: messageConfig = {}, + defaultPageSize = 10, + selection = false, + onDataChange + } = config; + + const [state, setState] = useState>(() => + createInitialState(defaultPageSize) + ); + + // 加载数据 + const loadData = useCallback(async (params?: Partial

) => { + 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) => { + 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) => { + 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(defaultPageSize)) + }; +} \ No newline at end of file diff --git a/frontend/src/layouts/BasicLayout.tsx b/frontend/src/layouts/BasicLayout.tsx index 903cbf10..354c5b6b 100644 --- a/frontend/src/layouts/BasicLayout.tsx +++ b/frontend/src/layouts/BasicLayout.tsx @@ -13,9 +13,10 @@ import { import * as AntdIcons from '@ant-design/icons'; import { logout, setMenus, setUserInfo } from '../store/userSlice'; 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 type { MenuDTO } from '../pages/System/Menu/types'; +import type { MenuResponse } from '@/pages/System/Menu/types'; import type { RootState } from '../store'; import dayjs from 'dayjs'; import 'dayjs/locale/zh-cn'; @@ -63,8 +64,7 @@ const BasicLayout: React.FC = () => { const userData = await getCurrentUser(); dispatch(setUserInfo(userData)); - // 获取并更新用户菜单 - const menuData = await getUserMenus(); + const menuData = await getCurrentUserMenus(); dispatch(setMenus(menuData)); setIsInitialized(true); @@ -134,7 +134,7 @@ const BasicLayout: React.FC = () => { }; // 将菜单数据转换为antd Menu需要的格式 - const getMenuItems = (menuList: MenuDTO[]): MenuProps['items'] => { + const getMenuItems = (menuList: MenuResponse[]): MenuProps['items'] => { return menuList.map(menu => ({ key: menu.path || menu.id.toString(), icon: getIcon(menu.icon), diff --git a/frontend/src/pages/System/Menu/index.tsx b/frontend/src/pages/System/Menu/index.tsx index e3f20c27..aff7a468 100644 --- a/frontend/src/pages/System/Menu/index.tsx +++ b/frontend/src/pages/System/Menu/index.tsx @@ -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 { PlusOutlined, EditOutlined, DeleteOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import * as AntdIcons from '@ant-design/icons'; -import type { MenuDTO } from './types'; -import { MenuTypeEnum, MenuTypeNames } from './types'; -import { getMenuTree, createMenu, updateMenu, deleteMenu, getMenuTreeWithoutButtons } from './service'; +import { getMenuTree, createMenu, updateMenu, deleteMenu } from './service'; import IconSelect from '@/components/IconSelect'; const MenuPage: React.FC = () => { diff --git a/frontend/src/pages/System/Menu/service.ts b/frontend/src/pages/System/Menu/service.ts index 5da5af05..884a3842 100644 --- a/frontend/src/pages/System/Menu/service.ts +++ b/frontend/src/pages/System/Menu/service.ts @@ -30,4 +30,10 @@ export const getMenuTree = async () => { return request.get('/api/v1/menu/tree', { errorMessage: '获取菜单树失败,请刷新重试' }); +}; + +export const getCurrentUserMenus = async (): Promise => { + return request.get('/api/v1/menu/current', { + errorMessage: '获取菜单失败,请刷新页面重试' + }); }; \ No newline at end of file diff --git a/frontend/src/pages/System/Menu/types/response.ts b/frontend/src/pages/System/Menu/types/response.ts index 7e95b044..bd1d0e12 100644 --- a/frontend/src/pages/System/Menu/types/response.ts +++ b/frontend/src/pages/System/Menu/types/response.ts @@ -1,10 +1,12 @@ import { BaseResponse } from '@/types/base/response'; export interface MenuResponse extends BaseResponse { + id: number; name: string; path?: string; icon?: string; parentId?: number; sort: number; + enabled: boolean; children?: MenuResponse[]; } \ No newline at end of file diff --git a/frontend/src/pages/System/User/index.tsx b/frontend/src/pages/System/User/index.tsx index ad60a31d..384e6351 100644 --- a/frontend/src/pages/System/User/index.tsx +++ b/frontend/src/pages/System/User/index.tsx @@ -1,51 +1,53 @@ import React, { useEffect, useState } from 'react'; import { Table, Button, Modal, Form, Input, Space, message, Switch, TreeSelect } from 'antd'; 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 { getUsers, createUser, updateUser, deleteUser, resetPassword } from './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 [users, setUsers] = useState([]); + 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([]); const [modalVisible, setModalVisible] = useState(false); - const [roleModalVisible, setRoleModalVisible] = useState(false); const [resetPasswordModalVisible, setResetPasswordModalVisible] = useState(false); - const [editingUser, setEditingUser] = useState(null); - const [selectedUser, setSelectedUser] = useState(null); - const [loading, setLoading] = useState(false); + const [editingUser, setEditingUser] = useState(null); const [form] = 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(() => { fetchUsers(); - fetchDepartments(); - }, []); + }, [pagination.current, pagination.pageSize]); const handleAdd = () => { setEditingUser(null); @@ -56,32 +58,16 @@ const UserPage: React.FC = () => { setModalVisible(true); }; - const handleEdit = (record: UserDTO) => { + const handleEdit = (record: UserResponse) => { setEditingUser(record); form.setFieldsValue({ ...record, - password: undefined + password: 'UNCHANGED_PASSWORD' }); setModalVisible(true); }; - const handleDelete = async (id: number) => { - Modal.confirm({ - title: '确认删除', - content: '确定要删除这个用户吗?', - onOk: async () => { - try { - await deleteUser(id); - message.success('删除成功'); - fetchUsers(); - } catch (error) { - message.error('删除失败'); - } - }, - }); - }; - - const handleResetPassword = (record: UserDTO) => { + const handleResetPassword = (record: UserResponse) => { setEditingUser(record); passwordForm.resetFields(); setResetPasswordModalVisible(true); @@ -91,7 +77,7 @@ const UserPage: React.FC = () => { try { const values = await passwordForm.validateFields(); if (editingUser) { - await resetPassword(editingUser.id, values.password); + await resetPassword(editingUser.id); message.success('密码重置成功'); setResetPasswordModalVisible(false); } @@ -106,6 +92,7 @@ const UserPage: React.FC = () => { if (editingUser) { await updateUser(editingUser.id, { ...values, + password: values.password === 'UNCHANGED_PASSWORD' ? editingUser.password : values.password, version: editingUser.version }); message.success('更新成功'); @@ -128,23 +115,23 @@ const UserPage: React.FC = () => { })); }; - const handleAssignRoles = (record: UserDTO) => { - setSelectedUser(record); - setRoleModalVisible(true); - }; + // const handleAssignRoles = (record: UserResponse) => { + // setSelectedUser(record); + // setRoleModalVisible(true); + // }; const columns = [ { title: '用户名', dataIndex: 'username', key: 'username', - width: '15%', + width: '12%', }, { title: '昵称', dataIndex: 'nickname', key: 'nickname', - width: '15%', + width: '12%', }, { title: '邮箱', @@ -156,19 +143,26 @@ const UserPage: React.FC = () => { title: '手机号', dataIndex: 'phone', key: 'phone', - width: '15%', + width: '12%', }, { - title: '所属部门', - dataIndex: 'deptName', - key: 'deptName', + title: '角色', + dataIndex: 'roles', + key: 'roles', + width: '15%', + render: (roles: Role[]) => roles?.map(role => role.name).join(', ') || '-' + }, + { + title: '创建时间', + dataIndex: 'createTime', + key: 'createTime', width: '15%', }, { title: '状态', dataIndex: 'enabled', key: 'enabled', - width: '10%', + width: '8%', render: (enabled: boolean) => ( ), @@ -177,33 +171,35 @@ const UserPage: React.FC = () => { title: '操作', key: 'action', width: '280px', - render: (_: any, record: UserDTO) => ( + render: (_: any, record: UserResponse) => ( - -