这一篇完成新用户注册功能,下一篇完成“用户管理”以及“删除用户”功能。
我们需要做的事情有:
- 创建注册页面组件
- 添加注册表单
- 添加路由
- 在登录页面添加注册链接
前端页面的修改:
1. 新建登录页面(Register.tsx)
import React, { useState } from 'react';
import {
Box,
Paper,
TextField,
Button,
Typography,
Container,
Alert,
Link,
MenuItem
} from '@mui/material';
import { useNavigate } from 'react-router-dom';
const roles = [
{ value: 'admin', label: '管理员' },
{ value: 'staff', label: '办公室人员' },
{ value: 'warehouse', label: '仓库人员' }
];
const Register: React.FC = () => {
const navigate = useNavigate();
const [formData, setFormData] = useState({
username: '',
password: '',
confirmPassword: '',
fullName: '',
role: 'staff'
});
const [error, setError] = useState('');
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
// 验证密码
if (formData.password !== formData.confirmPassword) {
setError('两次输入的密码不一致');
return;
}
try {
console.log('开始注册请求...');
const response = await fetch('http://localhost:8080/api/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: formData.username,
password: formData.password,
fullName: formData.fullName,
role: formData.role
}),
});
console.log('收到响应:', response.status);
// 检查响应的Content-Type
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
const data = await response.json();
console.log('响应数据:', data);
if (response.ok) {
console.log('注册成功,准备跳转...');
navigate('/login', { state: { message: '注册成功,请登录' } });
} else {
console.log('注册失败:', data.message);
setError(data.message || '注册失败,请检查输入信息');
}
} else {
// 如果不是JSON响应,直接读取文本
const text = await response.text();
console.log('非JSON响应:', text);
if (response.ok) {
console.log('注册成功,准备跳转...');
navigate('/login', { state: { message: '注册成功,请登录' } });
} else {
setError(text || '注册失败,请检查输入信息');
}
}
} catch (err) {
console.error('注册过程发生错误:', err);
setError(`网络错误: ${err instanceof Error ? err.message : '请稍后重试'}`);
}
};
return (
<Container component="main" maxWidth="xs">
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Paper
elevation={3}
sx={{
padding: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
width: '100%',
}}
>
<Typography component="h1" variant="h5">
注册新用户
</Typography>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1, width: '100%' }}>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<TextField
margin="normal"
required
fullWidth
id="username"
label="用户名"
name="username"
autoComplete="username"
autoFocus
value={formData.username}
onChange={handleChange}
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label="密码"
type="password"
id="password"
autoComplete="new-password"
value={formData.password}
onChange={handleChange}
/>
<TextField
margin="normal"
required
fullWidth
name="confirmPassword"
label="确认密码"
type="password"
id="confirmPassword"
autoComplete="new-password"
value={formData.confirmPassword}
onChange={handleChange}
/>
<TextField
margin="normal"
required
fullWidth
name="fullName"
label="姓名"
id="fullName"
value={formData.fullName}
onChange={handleChange}
/>
<TextField
margin="normal"
required
fullWidth
select
name="role"
label="角色"
id="role"
value={formData.role}
onChange={handleChange}
>
{roles.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
注册
</Button>
<Box sx={{ textAlign: 'center' }}>
<Link href="/login" variant="body2">
已有账号?返回登录
</Link>
</Box>
</Box>
</Paper>
</Box>
</Container>
);
};
export default Register;
2. 修改登录页面,添加注册链接(Login.tsx)
import React, { useState } from 'react';
import {
Box,
Paper,
TextField,
Button,
Typography,
Container,
Alert,
Link
} from '@mui/material';
import { useNavigate, useLocation } from 'react-router-dom';
const Login: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
const [formData, setFormData] = useState({
username: '',
password: ''
});
const [error, setError] = useState('');
const [successMessage, setSuccessMessage] = useState('');
// 从路由状态中获取注册成功消息
React.useEffect(() => {
const state = location.state as { message?: string };
if (state?.message) {
setSuccessMessage(state.message);
}
}, [location]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
try {
const response = await fetch('http://localhost:8080/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
const data = await response.json();
if (response.ok) {
// 保存token和用户信息到localStorage
localStorage.setItem('token', data.token);
localStorage.setItem('user', JSON.stringify(data.user));
// 跳转到仪表盘
navigate('/');
} else {
setError(data.message || '登录失败');
}
} catch (err) {
setError('网络错误,请稍后重试');
}
};
return (
<Container component="main" maxWidth="xs">
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Paper
elevation={3}
sx={{
padding: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
width: '100%',
}}
>
<Typography component="h1" variant="h5">
仓库管理系统
</Typography>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1, width: '100%' }}>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
{successMessage && (
<Alert severity="success" sx={{ mb: 2 }}>
{successMessage}
</Alert>
)}
<TextField
margin="normal"
required
fullWidth
id="username"
label="用户名"
name="username"
autoComplete="username"
autoFocus
value={formData.username}
onChange={handleChange}
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label="密码"
type="password"
id="password"
autoComplete="current-password"
value={formData.password}
onChange={handleChange}
/>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
登录
</Button>
<Box sx={{ textAlign: 'center' }}>
<Link href="/register" variant="body2">
没有账号?立即注册
</Link>
</Box>
</Box>
</Paper>
</Box>
</Container>
);
};
export default Login;
3. 配置路由,使用户能访问到这个页面(APP.tsx)
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 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>
<PlaceholderPage />
</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;
至此,前端注册页面已完成,它包括了:
- 注册页面功能:
- 用户名输入
- 密码和确认密码
- 姓名输入
- 角色选择(管理员/办公室人员/仓库人员)
- 表单验证
- 错误提示
- 页面导航:
- 登录页面添加了注册链接
- 注册页面添加了返回登录链接
- 注册成功后自动跳转到登录页面
- 表单验证:
- 所有字段都是必填的
- 密码和确认密码必须一致
- 用户名必须是唯一的(由后端验证)
使用方法:
- 在登录页面点击"没有账号?立即注册"
- 填写注册表单
- 点击注册按钮
- 注册成功后自动跳转到登录页面
注意事项:
- 确保后端实现了 /api/auth/register 接口
- 后端需要验证用户名唯一性
- 密码应该在传输前进行加密
后端页面的修改:
前面“使用Postman进行用户注册”的时候,已经完成了后端部分。接口路径正确,“验证用户名唯一性”完成,“密码加密”完成。
所以本次后端并无修改。
用户注册页面完成!
下一篇,用户管理(用户编辑,用户删除)功能。