后端接口写完后,我们来写前端页面。
先写登录、注册页面:
用 create-vite 新建个 react 项目:
npx create-vite book-management-system-frontend
进入项目目录,把开发服务跑起来:
npm install
npm run dev
浏览器访问下:
然后我们添加 router:
npm install --save react-router-dom
在 main.tsx 加上路由的配置:
import ReactDOM from 'react-dom/client';
import { RouterProvider, createBrowserRouter} from 'react-router-dom';
function BookManage() {
return <div>book</div>;
}
function Login(){
return <div>login</div>;
}
function Register(){
return <div>register</div>;
}
const routes = [
{
path: "/login",
element: <Login/>,
},
{
path: "/register",
element: <Register/>,
},
{
path: "/",
element: <BookManage/>,
},
];
const router = createBrowserRouter(routes);
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(<RouterProvider router={router}/>);
配置了 3 个路由:
访问 / 的时候,渲染 BookManage 组件。
访问 /login 的时候,渲染 Login 组件。
访问 /register 的时候,渲染 Register 组件。
测试下:
都没问题。
然后在 src 下创建 3 个组件:Login、Register、BookManage,把其余无用文件去掉:
然后来写 Register 页面:
引入 Ant Design 组件库:
npm install antd --save
在 Login 组件引入 Button 组件:
import { Button } from "antd";
export function Login(){
return <div>
login
<Button type="primary">按钮</Button>
</div>
}
没啥问题,说明 antd 引入成功了。
然后我们把注册页面写一下:
import { Button, Form, Input } from 'antd';
import './index.css';
interface RegisterUser {
username: string;
password: string;
password2: string;
}
const onFinish = (values: RegisterUser) => {
console.log(values);
};
const layout1 = {
labelCol: { span: 4 },
wrapperCol: { span: 20 }
}
const layout2 = {
labelCol: { span: 0 },
wrapperCol: { span: 24 }
}
export function Register() {
return <div id="register-container">
<h1>图书管理系统</h1>
<Form
{...layout1}
onFinish={onFinish}
colon={false}
autoComplete="off"
>
<Form.Item
label="用户名"
name="username"
rules={[{ required: true, message: '请输入用户名!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="密码"
name="password"
rules={[{ required: true, message: '请输入密码!' }]}
>
<Input.Password />
</Form.Item>
<Form.Item
label="确认密码"
name="password2"
rules={[{ required: true, message: '请输入确认密码!' }]}
>
<Input.Password />
</Form.Item>
<Form.Item
{...layout2}
>
<div className='links'>
<a href='/login'>已有账号?去登录</a>
</div>
</Form.Item>
<Form.Item
{...layout2}
>
<Button className='btn' type="primary" htmlType="submit">
注册
</Button>
</Form.Item>
</Form>
</div>
}
layout 是指定 label 和 input 部分的比例分配的,总共是 24。
写下 index.css 的样式:
#register-container {
width: 400px;
margin: 100px auto 0 auto;
text-align: center;
}
#register-container .links {
display: flex;
justify-content: center;
}
#register-container .btn {
width: 100%;
}
看下现在的注册页面:
输入用户名、密码、确认密码,点击注册:
控制台打印了拿到的表单值。
然后我们调用下后端接口,安装下 axios:
npm install --save axios
创建 interfaces/index.ts
import axios from "axios";
const axiosInstance = axios.create({
baseURL: 'http://localhost:3000/',
timeout: 3000
});
export async function register(username: string, password: string) {
return await axiosInstance.post('/user/register', {
username, password
});
}
在这里集中管理接口。
暴露 register 方法,里面调用 /user/register 接口。
然后在 Register 组件的 onFinish 里调用:
const onFinish = async (values: RegisterUser) => {
if(values.password !== values.password2) {
message.error('两次密码不一致');
return;
}
try {
const res = await register(values.username, values.password);
if(res.status === 201 || res.status === 200) {
message.success('注册成功');
setTimeout(() => {
window.location.href = '/login';
}, 1000);
}
} catch(e: any) {
message.error(e.response.data.message);
}
}
两次密码不一致提示错误。
然后请求注册接口,如果有错误就提示错误,注册成功跳转登录页。
注册下:
提示跨域。
在后端项目支持下跨域访问:
再试下:
没啥问题。
这样,注册就完成了。
我们再来写下登录页面:
修改下 Login/index.tsx
import { Button, Form, Input, message } from 'antd';
import './index.css';
interface LoginUser {
username: string;
password: string;
}
const onFinish = async (values: LoginUser) => {
console.log(values);
}
const layout1 = {
labelCol: { span: 4 },
wrapperCol: { span: 20 }
}
const layout2 = {
labelCol: { span: 0 },
wrapperCol: { span: 24 }
}
export function Login() {
return <div id="login-container">
<h1>图书管理系统</h1>
<Form
{...layout1}
onFinish={onFinish}
colon={false}
autoComplete="off"
>
<Form.Item
label="用户名"
name="username"
rules={[{ required: true, message: '请输入用户名!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="密码"
name="password"
rules={[{ required: true, message: '请输入密码!' }]}
>
<Input.Password />
</Form.Item>
<Form.Item
{...layout2}
>
<div className='links'>
<a href='/register'>没有账号?去注册</a>
</div>
</Form.Item>
<Form.Item
{...layout2}
>
<Button className='btn' type="primary" htmlType="submit">
登录
</Button>
</Form.Item>
</Form>
</div>
}
还有样式 index.css
#login-container {
width: 400px;
margin: 100px auto 0 auto;
text-align: center;
}
#login-container .links {
display: flex;
justify-content: center;
}
#login-container .btn {
width: 100%;
}
试一下:
没啥问题。
然后在 interfaces/index.ts 里添加 login 接口:
export async function login(username: string, password: string) {
return await axiosInstance.post('/user/login', {
username, password
});
}
在页面调用下:
const onFinish = async (values: LoginUser) => {
try {
const res = await login(values.username, values.password);
if(res.status === 201 || res.status === 200) {
message.success('登录成功');
setTimeout(() => {
window.location.href = '/';
}, 1000);
}
} catch(e: any) {
message.error(e.response.data.message);
}
}
试下效果:
至此,注册、登录的前后端都完成了。
案例代码上传了小册仓库
总结
这节我们写了下注册、登录的前端页面。
通过 create-vite 创建项目,引入了 react-router-dom 实现了路由,然后使用 antd 作为组件库,引入了 axios 发请求。
在后端项目开启跨域之后,在前端项目里调用登录、注册接口来实现功能。
下节,我们继续写其他前端页面。