后端接口写完后,我们来写前端页面。
先写登录、注册页面:


用 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 发请求。
在后端项目开启跨域之后,在前端项目里调用登录、注册接口来实现功能。
下节,我们继续写其他前端页面。
