ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Refine v5 多文件上传实战:基于 Mantine 的 Multipart/form-data 上传方案

2026/9/12 6:04:16 拓冰建站 浏览量
Refine v5 多文件上传实战:基于 Mantine 的 Multipart/form-data 上传方案 Refine v5 多文件上传实战基于 Mantine 的 Multipart/form-data 上传方案【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine导读在 Refine v5 中实现文件上传有多种形态其中multipart/form-dataMultipart Upload是后端接口最通用的上传协议。本文以仓库中的upload-mantine-multipart示例为主体讲解如何在 Refine 的useForm表单流程中接入 MantineDropzone手动构造FormData上传文件、回填表单字段并展示预览覆盖新建与编辑两个完整场景最终交付一个可复制的 React 后台管理文件上传方案。示例概览与运行方式关联文档 documentation/docs/examples/upload/mantine/multipart.md 指向一个完整的可运行示例upload-mantine-multipart。示例采用的技术栈如下UI 框架Mantinemantine/core、mantine/dropzoneRefine 包refinedev/core、refinedev/mantine、refinedev/react-table、refinedev/simple-rest路由refinedev/react-routerreact-routerHTTP 客户端axiosMarkdown 编辑器uiw/react-md-editor按示例 README.md 的说明可以通过 Refine CLI 在本地直接创建该示例工程npm create refine-applatest -- --example upload-mantine-multipart应用入口 App.tsx 使用simple-rest数据提供器连接https://api.fake-rest.refine.dev并注册了posts资源的 list / create / edit / show 四个页面路由const API_URL https://api.fake-rest.refine.dev; Refine routerProvider{routerProvider} dataProvider{dataProvider(API_URL)} notificationProvider{useNotificationProvider} resources{[ { name: posts, list: /posts, create: /posts/create, edit: /posts/edit/:id, show: /posts/show/:id, }, ]} Multipart 上传的核心思路手动构造 FormData与 Ant Design 版本Upload组件自带action直传不同Mantine 版本的Dropzone只负责收集文件不负责网络请求。上传动作需要开发者自行完成这也让它成为理解multipart/form-data协议本质的最佳示例。整个流程可以拆解为四步用户把文件拖入Dropzone触发onDrop回调在回调中把文件对象append进FormData字段名为file用axios.post把FormData发送到上传端点/media/upload拿到返回的{ url }将带url的文件对象写入表单字段images提交表单时随记录一并入库。FormData是浏览器原生 APIaxios 在检测到请求体为FormData时会自动设置Content-Type: multipart/form-data并生成正确的 boundary无需手动指定。Create 表单拖拽上传 预览回填新建页完整实现位于 create.tsx关键代码如下export const PostCreate: React.FC () { const [files, setFiles] useStateFileWithURL[]([]); const [isUploadLoading, setIsUploadLoading] useState(false); const { saveButtonProps, getInputProps, setFieldValue, errors } useForm IPost, HttpError, FormValues ({ initialValues: { title: , status: , category: { id: }, content: , images: [], }, validate: { title: (value) (value.length 2 ? Too short title : null), status: (value) (value.length 0 ? Status is required : null), category: { id: (value) (value.length 0 ? Category is required : null), }, content: (value) (value.length 10 ? Too short content : null), }, }); const apiUrl useApiUrl(); const handleOnDrop (files: FileWithPath[]) { try { setIsUploadLoading(true); files.map(async (file) { const formData new FormData(); formData.append(file, file); const res await axios.post{ url: string }( ${apiUrl}/media/upload, formData, { withCredentials: false, headers: { Access-Control-Allow-Origin: *, }, }, ); setFiles( (prev) [...prev, { url: res.data.url, ...file }] as FileWithURL[], ); }); setIsUploadLoading(false); } catch (error) { setIsUploadLoading(false); } }; useEffect(() { setFieldValue(images, files); }, [files]); // ... Dropzone accept{IMAGE_MIME_TYPE} onDrop{handleOnDrop} loading{isUploadLoading} Text aligncenterDrop images here/Text /Dropzone要点逐条说明useForm的泛型参数useFormIPost, HttpError, FormValues中第三个泛型FormValues是表单的实际值类型其中images字段被定义为FileWithURL[]即带url的文件对象数组。useApiUrl()来自refinedev/core返回当前数据提供器配置的 API 根地址避免把 URL 硬编码在页面组件里。这里的apiUrl即https://api.fake-rest.refine.dev。FileWithURL类型示例定义了interface FileWithURL extends FileWithPath { url?: string }在mantine/dropzone的FileWithPath基础上扩展出上传成功后服务端返回的url字段见 interfaces/index.d.ts 同目录类型定义。setFieldValue同步表单Dropzone与 Refine 表单没有直接绑定示例通过useEffect监听本地files状态再调用useForm返回的setFieldValue(images, files)把文件列表写入表单值提交时随记录一起发送。预览渲染files.map生成Image src{file.url} /列表再用SimpleGrid以四列网格展示编辑页则从values.images读取已有图片实现已上传图片的回显。IMAGE_MIME_TYPEmantine/dropzone导出的图片 MIME 类型常量限定只接受图片文件。Edit 表单已有图片回显编辑页 edit.tsx 与新建页几乎一致差异主要体现在两处const { saveButtonProps, getInputProps, setFieldValue, values, errors, refineCore: { query: queryResult }, } useFormIPost, HttpError, FormValues({ // initialValues 与 validate 同 Create }); // 分类下拉框默认值取自当前记录 const { selectProps } useSelectICategory({ resource: categories, defaultValue: queryResult?.data?.data.category.id, pagination: { mode: server }, }); // 预览直接读表单值中的已有图片 const previews values.images?.map((file, index) { return Image key{index} src{file.url} /; });通过refineCore.query拿到当前编辑记录的数据用于初始化分类等关联字段图片预览改为读取values.images表单当前值因此编辑页进入时即可回显记录中已保存的图片新拖入文件后handleOnDrop与setFieldValue的逻辑与新建页完全复用新旧文件会合并进images字段。编辑提交时Refine 会以PATCH/PUT请求把包含images含url的完整表单值发送到/posts/:id。上传端点的接口契约无论前端如何实现multipart 上传的成功都依赖后端端点遵守统一契约。参考 multipart-upload.md 中给出的规范请求方向[POST] https://api.fake-rest.refine.dev/media/upload { file: binary }该端点必须是Content-Type: multipart/form-data且表单字段名为file值为文件二进制数据。响应方向示例中axios.post{ url: string }的泛型即对应此结构{ url: https://example.com/uploaded-file.jpeg }前端拿到url后与文件对象合并存储。最终随posts记录提交到 API 的image/images字段形态大致如下以 Ant Design 版本教程文档展示的数据为例Mantine 示例的images数组语义相同均以url为核心字段{ title: Test, images: [ { name: greg-bulla-6RD0mcpY8f8-unsplash.jpg, url: https://refine.ams3.digitaloceanspaces.com/78c82c0b2203e670d77372f4c20fc0e2, type: image/jpeg, size: 70922 } ] }与 Ant Design 版本实现方式的对比同一主题在 Refine 仓库中还有 Ant Design 实现multipart-upload.md 教程主体基于refinedev/antd示例为upload-antd-multipart两者对比能帮助理解不同 UI 库下的接入差异环节Mantine 版本本文Ant Design 版本文件选择组件mantine/dropzone的Dropzoneantd的Upload.Dragger上传动作手动axios.postFormData组件action属性直传端点表单接入onDrop回调 setFieldValuegetValueFromEvent转换事件为UploadFile数组上传中状态本地isUploadLoading控制DropzoneloadinguseFileUploadState()返回isLoading/onChange可禁用保存按钮其中 Ant Design 版本的useFileUploadState是 Refine 为上传场景提供的便捷 Hook通过saveButtonProps.disabled isLoading在文件上传过程中禁用保存按钮避免表单在图片尚未传完时被提交。Mantine 版本由于上传逻辑完全自控等价地使用本地isUploadLoading状态即可达到相同效果。从源码看数据流文件如何进入 Refine 表单综合 create.tsx 与 edit.tsx 的实现可以梳理出一条完整的数据链路Dropzone.onDrop(files) └─ 每个 file append 进 FormData(file, file) └─ axios.post(${apiUrl}/media/upload, formData) └─ 响应 { url } 与 file 合并为 FileWithURL └─ setFiles(prev [...prev, {url, ...file}]) └─ useEffect 触发 setFieldValue(images, files) └─ 表单 values.images 更新 └─ 提交时随记录发送 /posts └─ 编辑页回显 values.images - Image src{url} /这一链路体现了 RefineuseForm的一个核心特性表单值与 UI 组件之间通过getInputProps/setFieldValue双向桥接第三方组件如Dropzone、MDEditor无需依赖 Refine 内部实现只要把数据写入表单值即可无缝参与saveButtonProps触发的提交流程。上传过程与表单提交解耦也是保证先传文件、后存记录顺序正确性的关键。延伸阅读完整理论教程multipart-upload.md含 Ant Design 版本的逐步讲解与useFileUploadState用法Mantine 示例工程examples/upload-mantine-multipart包含新建、编辑、列表页完整代码同主题 Base64 上传对比documentation/docs/examples/upload/mantine/base64.md【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考