Skip to content

这节继续来写图书管理模块的前端部分。

就是一个页面和几个弹窗:

我们先写下图书列表:

改下 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 接口支持搜索,然后在前端项目调用,实现了图书列表和搜索。

下节我们继续实现增删改的功能。