跳到主要内容

图书管理系统:用户模块前端开发

后端接口写完后,我们来写前端页面。

先写登录、注册页面:

用 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 发请求。

在后端项目开启跨域之后,在前端项目里调用登录、注册接口来实现功能。

下节,我们继续写其他前端页面。