tio-boot 案例 - 整合 ant design pro 增删改查
前端界面
data.d.ts
// data.d.ts
export interface Post {
id: string;
user_id: string;
title: string;
content: string;
attached_images: string[];
images: string[];
share_num: number;
like_num: number;
review_num: number;
created_at: string;
}
export interface PagePost {
data: any;
total: number;
list: Post[];
}
service.ts
// service.ts
import {request} from '@umijs/max';
import type {Post, PagePost} from './data.d';
export async function page(data: any) {
return request<PagePost>('/api/posts/page', {
method: 'POST',
data,
});
}
export async function createPost(data: Post) {
return request<Post>('/api/posts/create', {
method: 'POST',
data,
});
}
export async function deletePost(id: string) {
return request<void>(`/api/posts/delete/${id}`, {
method: 'GET',
});
}
export async function uploadFile(file: any) {
const formData = new FormData();
formData.append('file', file);
return request('/api/system/file/uploadImageToGoogle', {
method: 'POST',
data: formData,
});
}
column.tsx
import React from 'react';
import {ProColumns} from '@ant-design/pro-components';
import {Upload} from 'antd';
import type {Post} from './data.d'; // Adjust the import path as necessary
export const postColumns = (
handleShowDetail: (record: Post) => void,
handleDelete: (id: string) => Promise<void>,
handleShowEditModal: (record: Post) => void,
): ProColumns<Post>[] => [
{
title: 'Id',
dataIndex: 'id',
render: (dom, entity) => {
return (
<a
onClick={() => handleShowDetail(entity)}
>
{dom}
</a>
);
},
},
{
title: 'Title',
dataIndex: 'title',
},
{
title: 'Content',
dataIndex: 'content',
},
{
title: 'Share Count',
dataIndex: 'share_num',
},
{
title: 'Like Count',
dataIndex: 'like_num',
},
{
title: 'Review Count',
dataIndex: 'review_num',
},
{
title: 'Attached Images',
dataIndex: 'attached_images',
search: false,
render: (_, record) => (
<Upload
listType="picture-card"
fileList={
(record.attached_images || []).map((url) => ({
uid: url,
name: 'image',
status: 'done',
url,
}))
}
/>
),
},
{
title: 'Operation',
valueType: 'option',
render: (text, record) => [
<a key="delete" onClick={() => handleDelete(record.id)}>Delete</a>,
<a key="edit" onClick={() => handleShowEditModal(record)}>Edit</a>,
],
},
];
PostCreateForm.tsx
import React, {useState} from 'react';
import {ModalForm, ProFormList, ProFormText, ProFormTextArea, ProFormUploadButton} from '@ant-design/pro-components';
import {Button, Form, Image, Upload} from 'antd';
import {UploadFile, UploadProps} from 'antd/lib/upload/interface';
import {PlusOutlined} from "@ant-design/icons";
import {uploadFile} from '../service';
type CreateFormProps = {
visible: boolean;
title: string;
formData: any;
onVisibleChange: (visible: boolean) => void;
onFinish: (value: any) => Promise<boolean>;
};
const PostCreateForm: React.FC<CreateFormProps> = ({
visible,
title,
formData,
onVisibleChange,
onFinish,
}) => {
const [form] = Form.useForm();
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [previewImage, setPreviewImage] = useState('');
const [previewOpen, setPreviewOpen] = useState(false);
const [imageIds, setImageIds] = useState([]);
const [imageUrls, setImageUrls] = useState([]);
const getBase64 = (file: Blob): Promise<string> =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as Blob);
}
setPreviewImage(file.url || (file.preview as string));
setPreviewOpen(true);
};
const handleChange: UploadProps['onChange'] = ({fileList: newFileList}) => {
// Assuming setFileList is defined elsewhere to update the state for fileList
setFileList(newFileList);
// Extracting 'id' from each file's response.data and appending to imageIds
const newIds = newFileList.map(file => file.response?.data.id).filter(id => id !== undefined);
const imageUrls = newFileList.map(file => file.response?.data.url).filter(url => url !== undefined);
// Updating imageIds state by appending newIds
// @ts-ignore
setImageIds(newIds);
// @ts-ignore
setImageUrls(imageUrls);
}
const uploadButton = (
<div>
<PlusOutlined/>
<div style={{marginTop: 8}}>Upload</div>
</div>
);
const debug = () => {
console.log("imageIds", imageIds)
}
const debugButton = (
<Button type="primary" onClick={debug}>
Debug
</Button>
);
const customUploadRequest = (options: any) => {
const {file, onSuccess, onError} = options;
uploadFile(file)
.then(response => {
//必须回调
onSuccess(response, file);
})
.catch(onError); // 上传失败
};
const onFormVisibleChange = (newVisible: boolean) => {
onVisibleChange(newVisible);
if (newVisible) {
form.setFieldsValue(formData || {});
if(formData){
let attachedImages = formData['attached_images'];
let fileList:UploadFile[] = [];
for (const url of attachedImages) {
fileList.push({
uid: url,
name: 'image',
status: 'done',
url
});
}
setFileList(fileList);
}
} else {
form.resetFields();
setFileList([]);
}
};
const onFormFinish = async (formValues: any) => {
// 将imageIds添加到表单提交的数据中
const submissionData = {
...formValues,
attached_images: imageUrls,
idType:'long',
like_numType:'long',
review_numType:'long',
share_numType:'long'
};
if(await onFinish(submissionData)){
form.resetFields();
setFileList([]);
}
};
return (
<ModalForm
title={title}
visible={visible}
onVisibleChange={onFormVisibleChange}
onFinish={onFormFinish}
form={form}
>
<ProFormText
name="id"
label="id"
hidden
/>
<ProFormText
rules={[{required: true, message: 'Required'}]}
name="title"
label="Title"
/>
<ProFormTextArea
rules={[{required: true, message: 'Required'}]}
name="content"
label="Content"
/>
<ProFormText
name="share_num"
label="share num"
/>
<ProFormText
name="like_num"
label="like num"
/>
<ProFormText
name="review_num"
label="review num"
/>
<Upload
listType="picture-card"
fileList={fileList}
onPreview={handlePreview}
onChange={handleChange}
customRequest={customUploadRequest}
>
{fileList.length >= 8 ? null : uploadButton}
</Upload>
{previewImage && (
<Image
wrapperStyle={{display: 'none'}}
preview={{
visible: previewOpen,
onVisibleChange: setPreviewOpen,
afterOpenChange: (visible) => !visible && setPreviewOpen(false),
}}
src={previewImage}
/>
)}
{/*{debugButton}*/}
</ModalForm>
)
}
export default PostCreateForm;
index.tsx
import React, {useRef, useState} from 'react';
import type {ActionType, ProColumns} from '@ant-design/pro-components';
import {ProDescriptions, ProDescriptionsItemProps, ProTable} from '@ant-design/pro-components';
import {Button, Drawer, message, Upload} from 'antd';
import {PlusOutlined} from "@ant-design/icons";
import type {Post} from './data.d';
import {createPost, deletePost, page, uploadFile} from './service';
import PostCreateForm from "@/pages/business/posts/components/PostCreateForm";
import {postColumns} from "@/pages/business/posts/column";
const handleAdd = async (fields: Post) => {
const hide = message.loading('loading...');
try {
await createPost({...fields});
hide();
message.success('success');
return true;
} catch (error) {
hide();
message.error('fail!');
return false;
}
};
const PostManagement: React.FC = () => {
const [editingPost, setEditingPost] = useState<Post | null>(null);
const actionRef = useRef<ActionType>();
const [showDetail, setShowDetail] = useState<boolean>(false);
const [currentRow, setCurrentRow] = useState<Post>();
const [formTitle, setFormTitle] = useState<string>('');
/** 新建窗口的弹窗 */
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const handleModalVisible = (visible: boolean) => {
setCreateModalVisible(visible);
};
const [modalVisible, setModalVisible] = useState(false);
const handleModalClose = () => {
setModalVisible(false);
};
const handleDelete = async (id: string) => {
try {
await deletePost(id);
message.success('Post deleted successfully');
actionRef.current?.reload();
} catch (error) {
message.error('Failed to delete post');
}
};
const handleCreate = () => {
setModalVisible(true);
setEditingPost(null)
setFormTitle("New")
};
const handleShowEditModal = (record: Post) => {
setEditingPost(record);
setModalVisible(true);
setFormTitle("Edit")
};
const handleShowDetail = (entity: Post) => {
setCurrentRow(entity);
setShowDetail(true);
};
let toolBars = [
<Button icon={<PlusOutlined/>} type="primary" onClick={handleCreate}>
New
</Button>,
];
const columns = postColumns(handleShowDetail, handleDelete, handleShowEditModal);
const onFinishForm = async (value: Post) => {
const success = await handleAdd(value);
if (success) {
handleModalClose();
if (actionRef.current) {
actionRef.current.reload(); //重新加载数据
}
}
return success;
};
return (
<>
<ProTable<Post>
columns={columns}
actionRef={actionRef}
request={async (params) => {
params.idType = 'long';
params.like_numType = 'long';
params.review_numType = 'long';
params.share_numType = 'long';
params.titleOp = "ct";
params.contentOp = "ct";
params.orderBy = "created_at";
params.isAsc = "false";
const response = await page(params);
return {
data: response.data.list,
success: true,
total: response.data.total,
};
}}
rowKey="id"
search={{
labelWidth: 'auto',
defaultCollapsed: false,
}}
form={{initialValues: {pageSize: 10}}}
pagination={{
pageSize: 10,
}}
toolBarRender={() => toolBars}
>
</ProTable>
<Drawer
width={600}
open={showDetail}
onClose={() => {
setCurrentRow(undefined);
setShowDetail(false);
}}
closable={false}
>
{currentRow?.id && (
<ProDescriptions<Post>
column={2}
title={currentRow?.id}
request={async () => ({
data: currentRow || {},
})}
params={{
id: currentRow?.id,
}}
columns={columns as ProDescriptionsItemProps<Post>[]}
/>
)}
</Drawer>
<PostCreateForm
title={formTitle}
visible={modalVisible}
onVisibleChange={setModalVisible}
onFinish={onFinishForm}
formData={editingPost}
/>
</>
);
};
export default PostManagement;