夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 任务管理日历

  • 无代码平台

  • 图书管理系统

    • 图书管理系统:需求分析和原型图
    • 图书管理系统:用户模块后端开发
    • 图书管理系统:图书模块后端开发
    • 图书管理系统:用户模块前端开发
    • 图书管理系统:图书搜索前端开发
    • 图书管理系统:图书增删改前端开发
      • 新增图书弹窗
      • 文件上传
      • 更新图书弹窗
      • 删除图书
      • 总结
    • 图书管理系统:项目总结
  • 《全栈项目》
  • 图书管理系统
神说要有光
2025-03-10
目录

图书管理系统:图书增删改前端开发

这节我们来写图书新增、修改、删除、详情功能:

# 新增图书弹窗

我们创建一个新的组件:

BookManage/CreateBookModal.tsx

import { Button, Form, Input, Modal, message, TextArea } from "antd";
import { useForm } from "antd/es/form/Form";
import TextArea from "antd/es/input/TextArea";

interface CreateBookModalProps {
    isOpen: boolean;
    handleClose: Function
}
const layout = {
    labelCol: { span: 6 },
    wrapperCol: { span: 18 }
}

export interface CreateBook {
    name: string;
    author: string;
    description: string;
    cover: string;
}

export function CreateBookModal(props: CreateBookModalProps) {

    const [form] = useForm<CreateBook>();

    const handleOk = async function() {

    }

    return <Modal title="新增图书" open={props.isOpen} onOk={handleOk} onCancel={() => props.handleClose()} okText={'创建'}>
        <Form
            form={form}
            colon={false}
            {...layout}
        >
            <Form.Item
                label="图书名称"
                name="name"
                rules={[
                    { required: true, message: '请输入图书名称!' },
                ]}
            >
                <Input />
            </Form.Item>
            <Form.Item
                label="作者"
                name="author"
                rules={[
                    { required: true, message: '请输入图书作者!' },
                ]}
            >
                <Input />
            </Form.Item>
            <Form.Item
                label="描述"
                name="description"
                rules={[
                    { required: true, message: '请输入图书描述!' },
                ]}
            >
                <TextArea/>
            </Form.Item>
            <Form.Item
                label="封面"
                name="cover"
                rules={[
                    { required: true, message: '请上传图书封面!' },
                ]}
            >
                <Input/>
            </Form.Item>
        </Form>
    </Modal>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

创建一个 Modal,包含一个 Form,有 4 个字段:name、author、description、cover。

传入 isOpen 来控制 Modal 开启,handleClose 处理关闭的回调。

在 BookManage/index.tsx 调用下:

const [isCreateBookModalOpen, setCraeteBookModalOpen] = useState(false);
1
<CreateBookModal isOpen={isCreateBookModalOpen} handleClose={() => {
    setCraeteBookModalOpen(false);
}}></CreateBookModal>
1
2
3

接下来在 interfaces/index.ts 里添加 /book/create 接口:

export async function create(book: CreateBook) {
    return await axiosInstance.post('/book/create', {
        name: book.name,
        author: book.author,
        description: book.description,
        cover: book.cover
    });
}
1
2
3
4
5
6
7
8

然后在 CreateBookModal 组件调用下:

const handleOk = async function() {
    await form.validateFields();

    const values = form.getFieldsValue();

    try {
        const res = await create(values);

        if(res.status === 201 || res.status === 200) {
            message.success('创建成功');
            form.resetFields();
            props.handleClose();
        }
    } catch(e: any) {
        message.error(e.response.data.message);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

测试下:

添加成功,刷新页面就可以看到新的图书了。

只是这个封面的路径还没上传文件。

我们在 handleClose 的回调里调用下 setState 触发刷新:

这样创建成功就会自动刷新列表:

# 文件上传

然后我们来做一下文件上传:

新建 BookManage/Coverupload.tsx

import { InboxOutlined } from "@ant-design/icons";
import { message } from "antd";
import Dragger, { DraggerProps } from "antd/es/upload/Dragger";

interface CoverUploadProps {
    value?: string;
    onChange?: Function
}

let onChange: Function;

const props: DraggerProps = {
    name: 'file',
    action: 'http://localhost:3000/book/upload',
    method: 'post',
    onChange(info) {
        const { status } = info.file;
        if (status === 'done') {
            onChange(info.file.response);
            message.success(`${info.file.name} 文件上传成功`);
        } else if (status === 'error') {
            message.error(`${info.file.name} 文件上传失败`);
        }
    }
};

const dragger = <Dragger {...props}>
    <p className="ant-upload-drag-icon">
        <InboxOutlined />
    </p>
    <p className="ant-upload-text">点击或拖拽文件到这个区域来上传</p>
</Dragger>

export function CoverUpload(props: CoverUploadProps) {

    onChange = props.onChange!

    return props?.value ? <div>
        <img src={'http://localhost:3000/' + props.value} alt="封面" width="100" height="100"/>
        {dragger}
    </div>: <div>
        {dragger}
    </div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

用 antd 的 Dragger 组件做拖拽上传。

当拖拽文件过去的时候,会自动上传,然后在 onChange 方法里拿到上传状态来做提示。

我们在 CreateBookModal 里用一下:

试下效果:

封面上传成功。

在服务器 uploads 目录下也可以看到这个文件:

这样,新增图书功能就完成了。

# 更新图书弹窗

我们继续来做下修改:

和新增差不多,创建一个 BookManage/UpdateBookModal.tsx 组件:

import { Button, Form, Input, Modal, message } from "antd";
import { useForm } from "antd/es/form/Form";
import TextArea from "antd/es/input/TextArea";
import { CoverUpload } from "./CoverUpload";

interface UpdateBookModalProps {
    id: number;
    isOpen: boolean;
    handleClose: Function
}
const layout = {
    labelCol: { span: 6 },
    wrapperCol: { span: 18 }
}

export interface UpdateBook {
    id: number;
    name: string;
    author: string;
    description: string;
    cover: string;
}

export function UpdateBookModal(props: UpdateBookModalProps) {

    const [form] = useForm<UpdateBook>();

    const handleOk = async function() {

    }

    return <Modal title="更新图书" open={props.isOpen} onOk={handleOk} onCancel={() => props.handleClose()} okText={'更新'}>
        <Form
            form={form}
            colon={false}
            {...layout}
        >
            <Form.Item
                label="图书名称"
                name="name"
                rules={[
                    { required: true, message: '请输入图书名称!' },
                ]}
            >
                <Input />
            </Form.Item>
            <Form.Item
                label="作者"
                name="author"
                rules={[
                    { required: true, message: '请输入图书作者!' },
                ]}
            >
                <Input />
            </Form.Item>
            <Form.Item
                label="描述"
                name="description"
                rules={[
                    { required: true, message: '请输入图书描述!' },
                ]}
            >
                <TextArea/>
            </Form.Item>
            <Form.Item
                label="封面"
                name="cover"
                rules={[
                    { required: true, message: '请上传图书封面!' },
                ]}
            >
                <CoverUpload></CoverUpload>
            </Form.Item>
        </Form>
    </Modal>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

和 CreateBookModal 的区别是参数多了个 id。

在页面里引入下:

就是声明一个 isUpdateModalOpen 的 state 来控制弹窗的显示隐藏。

然后声明一个 updateId 的 state 来记录当前更新的 id。

点击图书的编辑按钮的时候,设置 updateId,并打开弹窗。

const [isUpdateBookModalOpen, setUpdateBookModalOpen] = useState(false);
const [updateId, setUpdateId] = useState(0);
1
2
<UpdateBookModal id={updateId} isOpen={isUpdateBookModalOpen} handleClose={() => {
    setUpdateBookModalOpen(false);
    setName('');
}}></UpdateBookModal>
1
2
3
4
<a href="#" onClick={() => {
    setUpdateId(book.id);
    setUpdateBookModalOpen(true);
}}>编辑</a>
1
2
3
4

然后在弹窗里根据 id 请求下数据:

在 interfaces/index.ts 里加一下接口:

export async function detail(id: number) {
    return await axiosInstance.get(`/book/${id}`);
}
1
2
3

async function query() {
    if(!props.id) {
        return;
    }
    try{
        const res = await detail(props.id);
        const { data } = res;
        debugger;
        if(res.status === 200 || res.status === 201) {
            form.setFieldValue('id', data.id);
            form.setFieldValue('name', data.name);
            form.setFieldValue('author', data.author);
            form.setFieldValue('description', data.description);
            form.setFieldValue('cover', data.cover);
        } 
    } catch(e: any){
        message.error(e.response.data.message);
    }
}

useEffect(() => {
    query();
}, [props.id]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

试一下:

然后点击更新按钮时候调用下更新接口:

改下 interfaces/index.ts

export async function update(book: UpdateBook) {
    return await axiosInstance.put('/book/update', {
        id: book.id,
        name: book.name,
        author: book.author,
        description: book.description,
        cover: book.cover
    });
}
1
2
3
4
5
6
7
8
9

组件里调用下:

const handleOk = async function() {
    await form.validateFields();

    const values = form.getFieldsValue();

    try {
        const res = await update({...values, id: props.id});

        if(res.status === 201 || res.status === 200) {
            message.success('更新成功');
            props.handleClose();
        }
    } catch(e: any) {
        message.error(e.response.data.message);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

试下效果:

更新成功。

但是我们要手动刷新下页面,因为 name 没变,没触发重新请求:

改一下:

加一个随机数的状态,当这个状态变了就重新请求。

这样就好了。

更新功能完成了。

详情也是一样的弹窗,比较简单,和更新差不多,我们就不实现了。

# 删除图书

再来实现下删除:

点击按钮按钮的有个二次确认的弹窗,确认后执行删除逻辑:

<Popconfirm
    title="图书删除"
    description="确认删除吗?"
    onConfirm={() => handleDelete(book.id)}
    okText="Yes"
    cancelText="No"
>  
    <a href="#">删除</a>
</Popconfirm>
1
2
3
4
5
6
7
8
9

在 interfaces/index.ts 加一下删除接口:

export async function deleteBook(id: number) {
    return await axiosInstance.delete(`/book/delete/${id}`);
}
1
2
3

在组件里调用下:

async function handleDelete(id: number) {
    try {
        await deleteBook(id);        
        message.success('删除成功');
        setNum(Math.random())
    } catch(e: any) {
        message.error(e.response.data.message);
    }
}
1
2
3
4
5
6
7
8
9

删除功能完成。

案例代码上传了小册仓库 (opens new window)

# 总结

这节我们实现了图书的新增、编辑、删除功能。

用 antd 的 Modal 创建弹窗,在父组件里通过 isOpen 控制显示隐藏,并做 onClose 时的处理。

然后用 antd 的 Dragger 实现了封面图片的上传。

创建图书的弹窗就是输入内容后调用 /book/create 接口。

更新图书的弹窗就是打开弹窗的时候根据 id 查询内容,点更新的时候调用 /book/udpate 接口。

删除的时候用 Popconfirm 组件做下二次确认。

这样,图书模块的前端部分就完成了。

编辑 (opens new window)
上次更新: 2025/5/7 18:16:53
图书管理系统:图书搜索前端开发
图书管理系统:项目总结

← 图书管理系统:图书搜索前端开发 图书管理系统:项目总结→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式