1. 用户管理功能主要包括了:
- 用户列表展示
- 用户编辑
- 用户删除
1. 添加Users.tsx页面
import React, { useState, useEffect } from 'react';
import {
Box,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Button,
IconButton,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
MenuItem,
Typography,
Alert,
Snackbar
} from '@mui/material';
import { Edit as EditIcon, Delete as DeleteIcon } from '@mui/icons-material';
interface User {
id: number;
username: string;
fullName: string;
role: string;
createdAt: string;
}
const roles = [
{ value: 'admin', label: '管理员' },
{ value: 'staff', label: '办公室人员' },
{ value: 'warehouse', label: '仓库人员' }
];
const Users: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
const [openDialog, setOpenDialog] = useState(false);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [formData, setFormData] = useState({
fullName: '',
role: 'staff'
});
const [error, setError] = useState('');
const [successMessage, setSuccessMessage] = useState('');
// 获取用户列表
const fetchUsers = async () => {
try {
const response = await fetch('http://localhost:8080/api/users', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (response.ok) {
const data = await response.json();
setUsers(data);
} else {
setError('获取用户列表失败');
}
} catch (err) {
setError('网络错误,请稍后重试');
}
};
useEffect(() => {
fetchUsers();
}, []);
// 处理编辑用户
const handleEdit = (user: User) => {
setSelectedUser(user);
setFormData({
fullName: user.fullName,
role: user.role
});
setOpenDialog(true);
};
// 处理删除用户
const handleDelete = async (userId: number) => {
if (window.confirm('确定要删除这个用户吗?')) {
try {
const response = await fetch(`http://localhost:8080/api/users/${userId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (response.ok) {
setSuccessMessage('用户删除成功');
fetchUsers();
} else {
setError('删除用户失败');
}
} catch (err) {
setError('网络错误,请稍后重试');
}
}
};
// 处理表单提交
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedUser) return;
try {
const response = await fetch(`http://localhost:8080/api/users/${selectedUser.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify(formData)
});
if (response.ok) {
setSuccessMessage('用户更新成功');
setOpenDialog(false);
fetchUsers();
} else {
const data = await response.json();
setError(data.message || '更新失败');
}
} catch (err) {
setError('网络错误,请稍后重试');
}
};
return (
<Box sx={{ p: 3 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 3 }}>
<Typography variant="h5">用户管理</Typography>
</Box>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>用户名</TableCell>
<TableCell>姓名</TableCell>
<TableCell>角色</TableCell>
<TableCell>创建时间</TableCell>
<TableCell>操作</TableCell>
</TableRow>
</TableHead>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.username}</TableCell>
<TableCell>{user.fullName}</TableCell>
<TableCell>
{roles.find(r => r.value === user.role)?.label || user.role}
</TableCell>
<TableCell>{new Date(user.createdAt).toLocaleString()}</TableCell>
<TableCell>
<IconButton onClick={() => handleEdit(user)} color="primary">
<EditIcon />
</IconButton>
<IconButton onClick={() => handleDelete(user.id)} color="error">
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<Dialog open={openDialog} onClose={() => setOpenDialog(false)}>
<DialogTitle>
编辑用户
</DialogTitle>
<DialogContent>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 2 }}>
<TextField
fullWidth
label="用户名"
value={selectedUser?.username || ''}
margin="normal"
disabled
/>
<TextField
fullWidth
label="姓名"
value={formData.fullName}
onChange={(e) => setFormData({ ...formData, fullName: e.target.value })}
margin="normal"
required
/>
<TextField
fullWidth
select
label="角色"
value={formData.role}
onChange={(e) => setFormData({ ...formData, role: e.target.value })}
margin="normal"
required
>
{roles.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenDialog(false)}>取消</Button>
<Button onClick={handleSubmit} variant="contained" color="primary">
保存
</Button>
</DialogActions>
</Dialog>
<Snackbar
open={!!error}
autoHideDuration={6000}
onClose={() => setError('')}
>
<Alert severity="error" onClose={() => setError('')}>
{error}
</Alert>
</Snackbar>
<Snackbar
open={!!successMessage}
autoHideDuration={6000}
onClose={() => setSuccessMessage('')}
>
<Alert severity="success" onClose={() => setSuccessMessage('')}>
{successMessage}
</Alert>
</Snackbar>
</Box>
);
};
export default Users;
2. 修改App.tsx页面和Users部分相关的代码
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { ThemeProvider, createTheme } from '@mui/material';
import MainLayout from './layouts/MainLayout';
import Dashboard from './pages/Dashboard';
import Login from './pages/Login';
import Register from './pages/Register';
import Users from './pages/Users';
import ProtectedRoute from './components/ProtectedRoute';
// 临时占位组件,后续会替换为实际页面组件
const PlaceholderPage = () => <div>Page under construction</div>;
const theme = createTheme({
palette: {
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
},
});
function App() {
return (
<ThemeProvider theme={theme}>
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route
path="/"
element={
<ProtectedRoute>
<MainLayout>
<Dashboard />
</MainLayout>
</ProtectedRoute>
}
/>
<Route
path="/products"
element={
<ProtectedRoute>
<MainLayout>
<PlaceholderPage />
</MainLayout>
</ProtectedRoute>
}
/>
<Route
path="/orders"
element={
<ProtectedRoute>
<MainLayout>
<PlaceholderPage />
</MainLayout>
</ProtectedRoute>
}
/>
<Route
path="/inbound"
element={
<ProtectedRoute>
<MainLayout>
<PlaceholderPage />
</MainLayout>
</ProtectedRoute>
}
/>
<Route
path="/customers"
element={
<ProtectedRoute>
<MainLayout>
<PlaceholderPage />
</MainLayout>
</ProtectedRoute>
}
/>
<Route
path="/delivery"
element={
<ProtectedRoute>
<MainLayout>
<PlaceholderPage />
</MainLayout>
</ProtectedRoute>
}
/>
<Route
path="/users"
element={
<ProtectedRoute>
<MainLayout>
<Users />
</MainLayout>
</ProtectedRoute>
}
/>
<Route
path="/reports"
element={
<ProtectedRoute>
<MainLayout>
<PlaceholderPage />
</MainLayout>
</ProtectedRoute>
}
/>
<Route
path="/settings"
element={
<ProtectedRoute>
<MainLayout>
<PlaceholderPage />
</MainLayout>
</ProtectedRoute>
}
/>
</Routes>
</Router>
</ThemeProvider>
);
}
export default App;
3. 修改左侧边框样式(MainLayout.tsx,非功能性修改)
import React, { useState } from 'react';
import {
Box,
Drawer,
AppBar,
Toolbar,
List,
Typography,
Divider,
IconButton,
ListItem,
ListItemIcon,
ListItemText,
ListItemButton,
useTheme,
useMediaQuery,
Button
} from '@mui/material';
import {
Menu as MenuIcon,
Dashboard as DashboardIcon,
Inventory as InventoryIcon,
ShoppingCart as ShoppingCartIcon,
LocalShipping as LocalShippingIcon,
People as PeopleIcon,
Person as PersonIcon,
Settings as SettingsIcon,
Assessment as AssessmentIcon,
Logout as LogoutIcon
} from '@mui/icons-material';
import { useNavigate, useLocation } from 'react-router-dom';
const drawerWidth = 240;
const menuItems = [
{ text: 'Dashboard', icon: <DashboardIcon />, path: '/' },
{ text: 'Products', icon: <InventoryIcon />, path: '/products' },
{ text: 'Orders', icon: <ShoppingCartIcon />, path: '/orders' },
{ text: 'Inbound', icon: <LocalShippingIcon />, path: '/inbound' },
{ text: 'Customers', icon: <PeopleIcon />, path: '/customers' },
{ text: 'Delivery', icon: <LocalShippingIcon />, path: '/delivery' },
{ text: 'Users', icon: <PersonIcon />, path: '/users' },
{ text: 'Reports', icon: <AssessmentIcon />, path: '/reports' },
{ text: 'Settings', icon: <SettingsIcon />, path: '/settings' }
];
const MainLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [mobileOpen, setMobileOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const navigate = useNavigate();
const location = useLocation();
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
const handleLogout = () => {
localStorage.removeItem('token');
localStorage.removeItem('user');
navigate('/login');
};
const drawer = (
<div>
<Toolbar>
<Typography variant="h6" noWrap component="div">
QuickStore
</Typography>
</Toolbar>
<Divider />
<List>
{menuItems.map((item) => (
<ListItem key={item.text} disablePadding>
<ListItemButton
selected={location.pathname === item.path}
onClick={() => {
navigate(item.path);
if (isMobile) {
setMobileOpen(false);
}
}}
sx={{
'&.Mui-selected': {
backgroundColor: 'rgba(25, 118, 210, 0.08)',
'&:hover': {
backgroundColor: 'rgba(25, 118, 210, 0.12)',
},
'& .MuiListItemIcon-root': {
color: 'primary.main',
},
'& .MuiListItemText-primary': {
color: 'primary.main',
fontWeight: 'bold',
},
},
}}
>
<ListItemIcon sx={{ minWidth: 40 }}>
{item.icon}
</ListItemIcon>
<ListItemText primary={item.text} />
</ListItemButton>
</ListItem>
))}
</List>
</div>
);
return (
<Box sx={{ display: 'flex' }}>
<AppBar
position="fixed"
sx={{
width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` },
}}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { sm: 'none' } }}
>
<MenuIcon />
</IconButton>
<Box sx={{ flexGrow: 1 }} />
<Button
color="inherit"
startIcon={<LogoutIcon />}
onClick={handleLogout}
>
Logout
</Button>
</Toolbar>
</AppBar>
<Box
component="nav"
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
>
<Drawer
variant={isMobile ? 'temporary' : 'permanent'}
open={isMobile ? mobileOpen : true}
onClose={handleDrawerToggle}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
sx={{
'& .MuiDrawer-paper': {
boxSizing: 'border-box',
width: drawerWidth,
},
}}
>
{drawer}
</Drawer>
</Box>
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: { sm: `calc(100% - ${drawerWidth}px)` },
mt: '64px',
}}
>
{children}
</Box>
</Box>
);
};
export default MainLayout;
2. 测试相关功能
1. 查看所有用户
登录。
点击左侧Users栏目,即可显示所有用户。
2. 编辑用户
点击某个用户右侧的“编辑”按钮,即可编辑用户。
只可以编辑“姓名”和“角色”。
左下角会有“成功”或者“失败”的提示。
3. 删除用户
点击右侧“删除”按钮。出现:
点击“OK”,即可确定删除请求。
左下角会有“成功”或者“失败”的提示。
至此,“用户管理”功能的前端页面也完成了。
下一篇:修改密码功能。包括用户自主修改密码,以及管理员为用户设置临时密码。