Nest 项目小实践之图书增删改查
写图书新增、修改、删除、详情功能
新建 BookManage/CreateBookModal.tsx
import { Button, Form, Input, Modal, message } 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>
}
在 BookManage/index.tsx 调用
const [isCreateBookModalOpen, setCreateBookModalOpen] = useState(false);
<Button type="primary" htmlType="submit" style={{background: 'green'}} onClick={ () =>setCreateBookModalOpen(true)}>
添加图书
</Button>
<CreateBookModal isOpen={isCreateBookModalOpen} handleClose={() => {
setCreateBookModalOpen(false);
}}></CreateBookModal>
点击添加图书就会展示
在 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,
});
}
在 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);
}
}
可以新增图书
只是没有封面图
添加 BookManage/Coverupload.tsx 组件
import { InboxOutlined } from "@ant-design/icons";
import { message } from "antd";
import Dragger, { type 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>
}
添加成功
修改如何做 ? 新建 BookManage/UpdateBookModal.tsx
需要带上 id
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>
}
在 BookManage index.tsx 引入使用
const [isUpdateBookModalOpen, setUpdateBookModalOpen] = useState(false); const [updateId, setUpdateId] = useState(0);
<UpdateBookModal id={updateId} isOpen={isUpdateBookModalOpen} handleClose={() => { setUpdateBookModalOpen(false); setName(''); }}></UpdateBookModal>
<a href="#" onClick={() => { setUpdateId(book.id); setUpdateBookModalOpen(true); }}>编辑</a>
点击编辑
interfaces/index.ts 里加一下接口
export async function detail(id: number) {
return await axiosInstance.get(`/book/${id}`);
}
UpdateBookModal.tsx 添加
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]);
点击编辑 就会带出已有的信息
更新 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
});
}
UpdateBookModal.tsx 调用
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);
}
}
可以成功更新
BookManage index.tsx
<Popconfirm
title="图书删除"
description="确认删除吗?"
onConfirm={() => handleDelete(book.id)}
okText="Yes"
cancelText="No"
>
<a href="#">删除</a>
</Popconfirm>
async function handleDelete(id: number) {
try {
await deleteBook(id);
message.success('删除成功');
setNum(Math.random())
} catch(e: any) {
message.error(e.response.data.message);
}
}
interfaces/index.ts
export async function deleteBook(id: number) { return await axiosInstance.delete(`/book/delete/${id}`); }
成功删除
后续会新增一些优化部分
- 登录之后怎么保存登录状态?比如有的接口需要登录才能访问,怎么控制?
这需要用 session + cookie 或 jwt 的方式来实现登录状态的保存。
- 数据保存在文件里并不方便,还有啥更好的方式?
保存在 mysql 数据库,用 TypeORM 作为 ORM 框架。
- 后端接口怎么提供 api 文档?
这需要用 swagger
- 文件保存在文件目录下,如果磁盘空间满了怎么办?
可以换用 minio 或者阿里 OSS 等对象存储服务。
- 怎么部署?
前端用 nginx,后端代码用 docker 和 docker compose
- 如何实现验证码?
可以用 nodemailer 发送邮件,然后用 redis 保存验证码数据。