From 7efbb63d310d9c17a978273fef6ef5f55ed4a669 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:41:04 +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/layouts/BasicLayout.tsx | 426 ++++++++++++----------- frontend/src/pages/Dashboard/index.tsx | 8 +- frontend/src/pages/System/User/index.tsx | 92 ++--- frontend/src/services/weather.ts | 50 +-- frontend/src/store/index.ts | 8 +- frontend/src/store/userSlice.ts | 72 ++-- frontend/src/utils/page.ts | 24 +- frontend/src/utils/request.ts | 26 +- frontend/src/utils/table.ts | 24 +- 9 files changed, 371 insertions(+), 359 deletions(-) diff --git a/frontend/src/layouts/BasicLayout.tsx b/frontend/src/layouts/BasicLayout.tsx index 354c5b6b..d7d761e6 100644 --- a/frontend/src/layouts/BasicLayout.tsx +++ b/frontend/src/layouts/BasicLayout.tsx @@ -1,243 +1,245 @@ -import React, { useEffect, useState } from 'react'; -import { Layout, Menu, Dropdown, Modal, message, Spin, Space, Tooltip } from 'antd'; -import { useNavigate, useLocation, Outlet } from 'react-router-dom'; -import { useDispatch, useSelector } from 'react-redux'; -import { - UserOutlined, - LogoutOutlined, - DownOutlined, - ExclamationCircleOutlined, - ClockCircleOutlined, - CloudOutlined +import React, {useEffect, useState, useCallback} from 'react'; +import {Layout, Menu, Dropdown, Modal, message, Spin, Space, Tooltip} from 'antd'; +import {useNavigate, useLocation, Outlet} from 'react-router-dom'; +import {useDispatch, useSelector} from 'react-redux'; +import { + UserOutlined, + LogoutOutlined, + DownOutlined, + ExclamationCircleOutlined, + ClockCircleOutlined, + CloudOutlined } from '@ant-design/icons'; import * as AntdIcons from '@ant-design/icons'; -import { logout, setMenus, setUserInfo } from '../store/userSlice'; -import type { MenuProps } from 'antd'; -import { getCurrentUser } from '../pages/Login/service'; -import { getCurrentUserMenus } from '@/pages/System/Menu/service'; -import { getWeather } from '../services/weather'; -import type { MenuResponse } from '@/pages/System/Menu/types'; -import type { RootState } from '../store'; +import {logout, setMenus, setUserInfo} from '../store/userSlice'; +import type {MenuProps} from 'antd'; +import {getCurrentUser} from '../pages/Login/service'; +import {getCurrentUserMenus} from '@/pages/System/Menu/service'; +import {getWeather} from '../services/weather'; +import type {MenuResponse} from '@/pages/System/Menu/types'; +import type {RootState} from '../store'; import dayjs from 'dayjs'; import 'dayjs/locale/zh-cn'; -const { Header, Content, Sider } = Layout; -const { confirm } = Modal; +const {Header, Content, Sider} = Layout; +const {confirm} = Modal; // 设置中文语言 dayjs.locale('zh-cn'); const BasicLayout: React.FC = () => { - const navigate = useNavigate(); - const location = useLocation(); - const dispatch = useDispatch(); - const userInfo = useSelector((state: RootState) => state.user.userInfo); - const menus = useSelector((state: RootState) => state.user.menus); - const [loading, setLoading] = useState(true); - const [currentTime, setCurrentTime] = useState(dayjs()); - const [weather, setWeather] = useState({ temp: '--', weather: '未知', city: '未知' }); - const [isInitialized, setIsInitialized] = useState(false); + const navigate = useNavigate(); + const location = useLocation(); + const dispatch = useDispatch(); + const userInfo = useSelector((state: RootState) => state.user.userInfo); + const menus = useSelector((state: RootState) => state.user.menus); + const [loading, setLoading] = useState(true); + const [currentTime, setCurrentTime] = useState(dayjs()); + const [weather, setWeather] = useState({temp: '--', weather: '未知', city: '未知'}); - useEffect(() => { - // 每秒更新时间 - const timer = setInterval(() => { - setCurrentTime(dayjs()); - }, 1000); - - // 获取天气信息 - const fetchWeather = async () => { - try { - const data = await getWeather(); - setWeather(data); - } catch (error) { - console.error('获取天气信息失败:', error); - } - }; + // 将天气获取逻辑提取为useCallback + const fetchWeather = useCallback(async () => { + try { + const data = await getWeather(); + setWeather(data); + } catch (error) { + console.error('获取天气信息失败:', error); + } + }, []); // 初始化用户数据 - const initializeUserData = async () => { - if (isInitialized) return; - try { - setLoading(true); - - // 获取并更新用户信息 - const userData = await getCurrentUser(); - dispatch(setUserInfo(userData)); + useEffect(() => { + const initializeUserData = async () => { + try { + setLoading(true); + const userData = await getCurrentUser(); + dispatch(setUserInfo(userData)); + const menuData = await getCurrentUserMenus(); + dispatch(setMenus(menuData)); + } catch (error) { + message.error('初始化用户数据失败'); + dispatch(logout()); + navigate('/login', {replace: true}); + } finally { + setLoading(false); + } + }; - const menuData = await getCurrentUserMenus(); - dispatch(setMenus(menuData)); + if (!userInfo) { + initializeUserData(); + } else { + setLoading(false); + } + }, []); - setIsInitialized(true); - } catch (error) { - message.error('初始化用户数据失败'); - } finally { - setLoading(false); - } + // 处理时间和天气更新 + useEffect(() => { + // 每秒更新时间 + const timer = setInterval(() => { + setCurrentTime(dayjs()); + }, 1000); + + // 初始化天气并设置定时更新 + fetchWeather(); + const weatherTimer = setInterval(fetchWeather, 30 * 60 * 1000); + + return () => { + clearInterval(timer); + clearInterval(weatherTimer); + }; + }, [fetchWeather]); + + const handleLogout = () => { + confirm({ + title: '确认退出', + icon: , + content: '确定要退出系统吗?', + okText: '确定', + cancelText: '取消', + onOk: () => { + dispatch(logout()); + message.success('退出成功'); + navigate('/login', {replace: true}); + } + }); }; - fetchWeather(); - initializeUserData(); + const userMenuItems: MenuProps['items'] = [ + { + key: 'userInfo', + icon: , + label: ( +
+
当前用户:{userInfo?.nickname || userInfo?.username}
+ {userInfo?.email &&
{userInfo.email}
} +
+ ), + disabled: true + }, + { + type: 'divider' + }, + { + key: 'logout', + icon: , + label: '退出登录', + onClick: handleLogout + } + ]; - // 每半小时更新一次天气 - const weatherTimer = setInterval(fetchWeather, 30 * 60 * 1000); - - return () => { - clearInterval(timer); - clearInterval(weatherTimer); + // 获取图标组件 + const getIcon = (iconName: string | undefined) => { + if (!iconName) return null; + const iconKey = `${iconName.charAt(0).toUpperCase() + iconName.slice(1)}Outlined`; + const Icon = (AntdIcons as any)[iconKey]; + return Icon ? : null; }; - }, [isInitialized]); - const handleLogout = () => { - confirm({ - title: '确认退出', - icon: , - content: '确定要退出系统吗?', - okText: '确定', - cancelText: '取消', - onOk: () => { - dispatch(logout()); - message.success('退出成功'); - navigate('/login', { replace: true }); - } - }); - }; + // 将菜单数据转换为antd Menu需要的格式 + const getMenuItems = (menuList: MenuResponse[]): MenuProps['items'] => { + return menuList.map(menu => ({ + key: menu.path || menu.id.toString(), + icon: getIcon(menu.icon), + label: menu.name, + children: menu.children && menu.children.length > 0 ? getMenuItems(menu.children) : undefined + })); + }; - const userMenuItems: MenuProps['items'] = [ - { - key: 'userInfo', - icon: , - label: ( -
-
当前用户:{userInfo?.nickname || userInfo?.username}
- {userInfo?.email &&
{userInfo.email}
} -
- ), - disabled: true - }, - { - type: 'divider' - }, - { - key: 'logout', - icon: , - label: '退出登录', - onClick: handleLogout - } - ]; - - // 获取图标组件 - const getIcon = (iconName: string | undefined) => { - if (!iconName) return null; - const iconKey = `${iconName.charAt(0).toUpperCase() + iconName.slice(1)}Outlined`; - const Icon = (AntdIcons as any)[iconKey]; - return Icon ? : null; - }; - - // 将菜单数据转换为antd Menu需要的格式 - const getMenuItems = (menuList: MenuResponse[]): MenuProps['items'] => { - return menuList.map(menu => ({ - key: menu.path || menu.id.toString(), - icon: getIcon(menu.icon), - label: menu.name, - children: menu.children && menu.children.length > 0 ? getMenuItems(menu.children) : undefined - })); - }; - - if (loading) { - return ( -
- -
-

正在为您准备系统资源

-

请稍候,马上就好...

-
-
- ); - } - - return ( - - -
- navigate(key)} - /> - - -
- - - - - {currentTime.format('YYYY年MM月DD日 HH:mm:ss')} 星期{currentTime.format('dd')} - - - + +
+

正在为您准备系统资源

+

请稍候,马上就好...

+
+
+ ); + } + + return ( + + +
+ navigate(key)} + /> + + +
+ + + - - {weather.weather} {weather.temp}℃ - - - - - - - {userInfo?.nickname || userInfo?.username} - + + {currentTime.format('YYYY年MM月DD日 HH:mm:ss')} 星期{currentTime.format('dd')} - - -
- - - -
- - ); + + + + {weather.weather} {weather.temp}℃ + + + + + + + {userInfo?.nickname || userInfo?.username} + + + + + + + + + + + ); }; export default BasicLayout; \ No newline at end of file diff --git a/frontend/src/pages/Dashboard/index.tsx b/frontend/src/pages/Dashboard/index.tsx index e139b683..79abf74a 100644 --- a/frontend/src/pages/Dashboard/index.tsx +++ b/frontend/src/pages/Dashboard/index.tsx @@ -1,8 +1,14 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Card, Row, Col, Statistic } from 'antd'; import { UserOutlined, ShoppingCartOutlined, FileOutlined } from '@ant-design/icons'; const Dashboard: React.FC = () => { + useEffect(() => { + // 检查这里的数据加载逻辑 + // 是否有多个useEffect都在加载相同的数据 + // 或者依赖项是否设置正确 + }, []); // 检查依赖项 + return (
diff --git a/frontend/src/pages/System/User/index.tsx b/frontend/src/pages/System/User/index.tsx index 384e6351..5b3418df 100644 --- a/frontend/src/pages/System/User/index.tsx +++ b/frontend/src/pages/System/User/index.tsx @@ -1,12 +1,13 @@ 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 { UserResponse } from './types'; +import type { UserResponse, Role } from './types'; import type { DepartmentDTO } from '../Department/types'; import { getUsers, createUser, updateUser, deleteUser, resetPassword } from './service'; import { getDepartmentTree } from '../Department/service'; import { convertToPageParams, convertToPageInfo } from '@/utils/page'; import { useTableData } from '@/hooks/useTableData'; +import type { FixedType, AlignType } from 'rc-table/lib/interface'; // import RoleModal from './components/RoleModal'; const UserPage: React.FC = () => { @@ -45,10 +46,6 @@ const UserPage: React.FC = () => { const [form] = Form.useForm(); const [passwordForm] = Form.useForm(); - useEffect(() => { - fetchUsers(); - }, [pagination.current, pagination.pageSize]); - const handleAdd = () => { setEditingUser(null); form.resetFields(); @@ -61,8 +58,7 @@ const UserPage: React.FC = () => { const handleEdit = (record: UserResponse) => { setEditingUser(record); form.setFieldsValue({ - ...record, - password: 'UNCHANGED_PASSWORD' + ...record }); setModalVisible(true); }; @@ -77,7 +73,7 @@ const UserPage: React.FC = () => { try { const values = await passwordForm.validateFields(); if (editingUser) { - await resetPassword(editingUser.id); + await resetPassword(editingUser.id, values.password); message.success('密码重置成功'); setResetPasswordModalVisible(false); } @@ -92,7 +88,6 @@ 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('更新成功'); @@ -121,77 +116,86 @@ const UserPage: React.FC = () => { // }; const columns = [ + { + title: 'ID', + dataIndex: 'id', + key: 'id', + width: 80, + fixed: 'left' as FixedType + }, { title: '用户名', dataIndex: 'username', key: 'username', - width: '12%', + width: 120, }, { title: '昵称', dataIndex: 'nickname', key: 'nickname', - width: '12%', + width: 120, }, { title: '邮箱', dataIndex: 'email', key: 'email', - width: '15%', + width: 200, + ellipsis: true, }, { title: '手机号', dataIndex: 'phone', key: 'phone', - width: '12%', + width: 120, }, { title: '角色', dataIndex: 'roles', key: 'roles', - width: '15%', + width: 150, + ellipsis: true, render: (roles: Role[]) => roles?.map(role => role.name).join(', ') || '-' }, { title: '创建时间', dataIndex: 'createTime', key: 'createTime', - width: '15%', + width: 180, + }, + { + title: '更新时间', + dataIndex: 'updateTime', + key: 'updateTime', + width: 180, }, { title: '状态', dataIndex: 'enabled', key: 'enabled', - width: '8%', + width: 80, + align: 'center' as AlignType, render: (enabled: boolean) => ( - + ), }, { title: '操作', key: 'action', - width: '280px', + width: 180, + fixed: 'right' as FixedType, render: (_: any, record: UserResponse) => ( - + - {/* 注释掉角色分配按钮 - - */}