这节继续来写图书管理模块的前端部分。
就是一个页面和几个弹窗:
我们先写下图书列表:
改下 BookManage/index.tsx
javascript
import { Button, Card, Form, Input } from 'antd';
import './index.css';
export function BookManage(){
return <div id="bookManage">
<h1>图书管理系统</h1>
<div className="content">
<div className='book-search'>
<Form
name="search"
layout='inline'
colon={false}
>
<Form.Item label="图书名称" name="name">
<Input />
</Form.Item>
<Form.Item label=" ">
<Button type="primary" htmlType="submit">
搜索图书
</Button>
<Button type="primary" htmlType="submit" style={{background: 'green'}} >
添加图书
</Button>
</Form.Item>
</Form>
</div>
<div className="book-list">
{
[1,2,3,4,5,6,7].map(item => {
return <Card
className='card'
hoverable
style={{ width: 300 }}
cover={<img alt="example" src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png" />}
>
<h2>西游记</h2>
<div>神说要有光</div>
<div className='links'>
<a href="#">详情</a>
<a href="#">编辑</a>
<a href="#">删除</a>
</div>
</Card>
})
}
</div>
</div>
</div>
}
上面是一个 Form,下面是 Card 的列表。
在 index.css 写下样式:
css
#bookManage {
display: flex;
flex-direction: column;
}
#bookManage h1 {
height: 80px;
line-height: 80px;
border-bottom: 2px solid #ccc;
padding-left: 20px;
}
#bookManage .content {
padding: 20px;
}
#bookManage .book-list{
padding: 20px;
display: flex;
flex-wrap: wrap;
}
#bookManage .book-list .card{
margin-left: 30px;
margin-bottom: 30px;
}
#bookManage .book-list .links{
display: flex;
flex-direction: row;
justify-content: space-around;
}
这里还需要重置样式,在 main.tsx 里引入下:
看下效果:
然后我们在 interfaces/index.ts 里加下图书列表的请求:
javascript
export async function list() {
return await axiosInstance.get('/book/list');
}
然后在组件里调用下:
javascript
import { Button, Card, Form, Input, message } from 'antd';
import './index.css';
import { useEffect, useState } from 'react';
import { list } from '../../interfaces';
interface Book {
id: number;
name: string;
author: string;
description: string;
cover: string;
}
export function BookManage(){
const [bookList, setBookList] = useState<Array<Book>>([]);
async function fetchData() {
try {
const data = await list();
if(data.status === 201 || data.status === 200) {
setBookList(data.data);
}
} catch(e: any) {
message.error(e.response.data.message);
}
}
useEffect(() => {
fetchData();
}, []);
return <div id="bookManage">
<h1>图书管理系统</h1>
<div className="content">
<div className='book-search'>
<Form
name="search"
layout='inline'
colon={false}
>
<Form.Item label="图书名称" name="name">
<Input />
</Form.Item>
<Form.Item label=" ">
<Button type="primary" htmlType="submit">
搜索图书
</Button>
<Button type="primary" htmlType="submit" style={{background: 'green'}} >
添加图书
</Button>
</Form.Item>
</Form>
</div>
<div className="book-list">
{
bookList.map(book => {
return <Card
className='card'
hoverable
style={{ width: 300 }}
cover={<img alt="example" src={`http://localhost:3000/${book.cover}`} />}
>
<h2>{book.name}</h2>
<div>{book.author}</div>
<div className='links'>
<a href="#">详情</a>
<a href="#">编辑</a>
<a href="#">删除</a>
</div>
</Card>
})
}
</div>
</div>
</div>
}
我现在服务端的 books.json 是这样的:
json
[
{
"id": 328555,
"author": "曹雪芹",
"name": "红楼梦",
"description": "这是中国古代四大名著之一,被认为是中国文学的巅峰之作。它以贾、史、王、薛四大家族的婚姻和命运为线索,描绘了中国封建社会的荣辱兴衰、人情世故、爱恨情仇等。",
"cover": "uploads/1721191961112-224327292-qiong.png"
},
{
"id": 281167,
"author": "施耐庵",
"name": "水浒传",
"description": "这是中国古代四大名著之一,以108位好汉的故事为主线,反映了宋朝末年民变斗争与各阶层人民的痛苦生活。该作品强调了义气、反抗不平等待遇等社会主题。",
"cover": "uploads/1721191961112-224327292-qiong.png"
},
{
"id": 197528,
"author": "吴承恩",
"name": "西游记",
"description": "这是中国古代四大名著之一,讲述了唐僧师徒四人西天取经、斩妖除魔的故事。作品深入探讨了佛教和道教的思想,同时也展示了人性的善恶之间的斗争。",
"cover": "uploads/1721191961112-224327292-qiong.png"
}
]
看下效果:
可以看到,图书列表加载并渲染了出来。
然后来实现下搜索,之前的 list 接口没支持搜索,我们改造下:
在 /book/list 接口添加一个 name 参数。
javascript
@Get('list')
async list(@Query('name') name: string) {
return this.bookService.list(name);
}
然后在 BookService 里实现下搜索:
javascript
async list(name: string) {
const books: Book[] = await this.dbService.read();
return name ? books.filter(book => {
return book.name.includes(name);
}) : books;
}
在 postman 里测试下:
当没传参数时:
传 name=西 时:
传 name=水 时:
然后我们在前端代码里调用下:
javascript
export async function list(name: string) {
return await axiosInstance.get('/book/list', {
params: {
name
}
});
}
当 form 提交的时候,修改 name 的 state,然后 name 的 state 改变触发重新搜索。
javascript
import { Button, Card, Form, Input, message } from 'antd';
import './index.css';
import { useEffect, useState } from 'react';
import { list } from '../../interfaces';
interface Book {
id: number;
name: string;
author: string;
description: string;
cover: string;
}
export function BookManage(){
const [bookList, setBookList] = useState<Array<Book>>([]);
const [name, setName] = useState('');
async function fetchData() {
try {
const data = await list(name);
if(data.status === 201 || data.status === 200) {
setBookList(data.data);
}
} catch(e: any) {
message.error(e.response.data.message);
}
}
useEffect(() => {
fetchData();
}, [name]);
async function searchBook(values: { name: string}) {
setName(values.name);
}
return <div id="bookManage">
<h1>图书管理系统</h1>
<div className="content">
<div className='book-search'>
<Form
onFinish={searchBook}
name="search"
layout='inline'
colon={false}
>
<Form.Item label="图书名称" name="name">
<Input />
</Form.Item>
<Form.Item label=" ">
<Button type="primary" htmlType="submit">
搜索图书
</Button>
<Button type="primary" htmlType="submit" style={{background: 'green'}} >
添加图书
</Button>
</Form.Item>
</Form>
</div>
<div className="book-list">
{
bookList.map(book => {
return <Card
className='card'
hoverable
style={{ width: 300 }}
cover={<img alt="example" src={`http://localhost:3000/${book.cover}`} />}
>
<h2>{book.name}</h2>
<div>{book.author}</div>
<div className='links'>
<a href="#">详情</a>
<a href="#">编辑</a>
<a href="#">删除</a>
</div>
</Card>
})
}
</div>
</div>
</div>
}
测试下:
案例代码上传了小册仓库
总结
这节我们新增了图书列表页面,改造了 /book/list 接口支持搜索,然后在前端项目调用,实现了图书列表和搜索。
下节我们继续实现增删改的功能。