Refine v5 中的 Base64 图片上传实战:基于 Mantine useForm 与 Dropzone 的完整实现 Refine v5 中的 Base64 图片上传实战基于 Mantine useForm 与 Dropzone 的完整实现【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine本文以 Refine 官方示例 examples/upload-mantine-base64 为蓝本讲解如何在 Refine v5 Mantine 技术栈下通过refinedev/mantine的useForm配合mantine/dropzone的Dropzone组件实现选择图片 → 转换为 Base64 → 存入表单字段 → 随表单一并提交的完整上传流程。读完本文你将掌握 Base64 上传的核心原理FileReader.readAsDataURL、创建/编辑页面的落地写法、图片预览的实现方式以及useForm在 Refine 内部的封装机制可直接复用到自己的管理后台项目中。示例概览与技术栈该示例是一个完整的文章postsCRUD 管理页面其中创建Create与编辑Edit页面都集成了图片上传能力。示例的完整运行说明见 examples/upload-mantine-base64/README.md可通过如下命令在本地启动npm create refine-applatest -- --example upload-mantine-base64从 package.json 可以看到示例的核心依赖依赖包版本职责refinedev/core^5.0.12Refine 核心hooks、数据提供者、路由等refinedev/mantine^3.0.2Mantine 集成层useForm、Create/Edit页面组件、RefineThemesmantine/dropzone^5.4.1拖拽/点击选择文件的 Dropzone 组件mantine/form^5.10.4Mantine 原生表单库被 RefineuseForm包装mantine/core^5.10.4Mantine 基础组件库refinedev/simple-rest^6.0.1REST 数据提供者refinedev/react-router^2.0.4路由提供者在 App.tsx 中应用以MantineProvider使用RefineThemes.Blue主题包裹Refine配置了simple-rest数据提供者API 地址为https://api.fake-rest.refine.dev并注册了posts资源的list、create、edit、show四个页面路由。Base64 上传的核心思路与传统的multipart/form-data文件上传不同Base64 上传将文件内容编码为 Data URL 字符串形如data:image/png;base64,iVBORw0KGgo...并作为普通字符串字段存放在表单数据中。当用户点击保存时这个字符串会随其他字段一起通过数据提供者发送到后端。后端解析字符串后即可还原为图片二进制内容。这种方案的优点是实现简单、不依赖任何文件上传专用组件或服务端中间件适合图片体积小、数量少、后端仅需单次请求即可完成 CRUD 提交的场景。需要留意的是Base64 编码会使数据体积膨胀约 33%且图片以字符串形式保存在内存与请求体中因此不适合大图或批量上传此类场景更适合本仓库中的 multipart 方案见 multipart.md。通用工具函数convertBase64示例将文件转 Base64 的逻辑抽离为可复用的工具函数见 src/utils/convertBase64.tsexport const convertBase64 (file: File): Promisestring { return new Promise((resolve, reject) { const fileReader new FileReader(); fileReader.readAsDataURL(file); fileReader.onload () { if (typeof fileReader.result string) { resolve(fileReader.result); } }; fileReader.onerror (error) { reject(error); }; }); };该函数基于浏览器内置的FileReaderAPIfileReader.readAsDataURL(file)以异步方式将文件读取为 Data URLonload回调中fileReader.result即为data:...;base64,...字符串通过resolve返回onerror回调将读取失败的错误透传给调用方便于上层做异常处理返回类型为Promisestring配合async/await使用非常顺手。创建页面Dropzone setFieldValue 组合创建页面的完整实现位于 src/pages/posts/create.tsx其核心是useForm与Dropzone的配合。1. 定义表单结构与校验规则useForm接受泛型FormValues明确表单字段类型其中images为string[]存放 Base64 字符串数组interface FormValues { title: string; status: string; category: { id: string }; content: string; images: string[]; } const { saveButtonProps, getInputProps, setFieldValue, values, errors } useFormIPost, 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), }, });这里可以看到 RefineuseForm的三个关键返回值saveButtonProps直接透传给Create页面的保存按钮自动接管提交逻辑含 loading 状态与提交处理getInputProps(fieldName)将字段值与变更处理绑定到任意输入组件如TextInput、Select、MDEditor配合 Mantine 的受控表单体系setFieldValue/values用于程序化读写字段值——这正是 Base64 上传与表单联动的关键。validate对象采用 Mantine 表单风格的字段级校验函数客户端校验不通过时errors会携带对应错误信息可在界面上展示示例中对content字段做了红色错误提示渲染。2. Dropzone 拖拽上传与 Base64 转换表单中嵌入mantine/dropzone的Dropzone组件通过accept{IMAGE_MIME_TYPE}限定仅接受图片类型文件Dropzone accept{IMAGE_MIME_TYPE} onDrop{handleOnDrop} loading{isUploadLoading} Text aligncenterDrop images here/Text /DropzonehandleOnDrop是上传逻辑的核心create.tsx 第 57-75 行const handleOnDrop (files: FileWithPath[]) { try { setIsUploadLoading(true); files.map(async (file) { const base64 await convertBase64(file); if (values.images) { setFieldValue(images, [...values.images, base64]); } else { setFieldValue(images, [base64]); } }); setIsUploadLoading(false); } catch (error) { setIsUploadLoading(false); } };这段代码值得逐行解读Dropzone的onDrop回调接收FileWithPath[]继承自浏览器File先通过setIsUploadLoading(true)打开 Dropzone 的loading动画避免用户重复操作对每个文件调用convertBase64(file)得到 Base64 字符串通过setFieldValue(images, [...values.images, base64])将新 Base64追加到images数组——注意这里使用展开语法保留了已上传的图片支持一次拖入多张图片转换或追加过程出错时进入catch关闭 loading 并保持表单原状。3. 实时图片预览由于images字段本身就存的是 Data URL预览无需任何额外请求const previews values.images?.map((base64, index) { return Image key{index} src{base64} /; }); SimpleGrid cols{4} breakpoints{[{ maxWidth: sm, cols: 2 }]} mt{previews?.length 0 ? xl : 0} {previews} /SimpleGridvalues.images一旦变化React 即重新渲染Image src{base64}直接以 Data URL 作为图片来源渲染缩略图并用SimpleGrid做 4 列小屏 2 列的网格布局。整个流程完全在客户端完成所见即所得。4. 保存提交页面根节点使用refinedev/mantine的Create saveButtonProps{saveButtonProps}包装表单点击保存时images数组会作为普通字段随POST /posts请求发送到 API。对后端而言它只是收到一个包含 Base64 字符串数组的 JSON 对象无需处理文件流。编辑页面数据回填与增量追加编辑页面 src/pages/posts/edit.tsx 与创建页面几乎一致差异点在于已有数据的回填const { saveButtonProps, getInputProps, setFieldValue, values, errors, refineCore: { query: queryResult }, } useFormIPost, HttpError, FormValues({ initialValues: { /* 与创建页一致 */ }, validate: { /* 与创建页一致 */ }, });refineCore.query暴露了 Refine 内部通过数据提供者拉取当前记录的结果。useForm在编辑模式下会自动将查询到的记录填充进表单包括images字段从而保证进入编辑页时已上传图片的 Base64 会作为初始预览显示useSelect通过defaultValue: queryResult?.data?.data.category.id正确选中当前分类用户追加新图片时handleOnDrop中的展开语法会保留原有图片实现旧图 新图共存。深入原理Refine 的 Mantine useForm 是如何工作的要真正理解示例需要知道refinedev/mantine的useForm并不只是简单封装。查看源码 packages/mantine/src/hooks/form/useForm/index.ts可以看到它的实现层次useForm (refinedev/mantine) ├── useMantineForm (mantine/form) → 表单状态、校验、getInputProps/setFieldValue └── useFormCore (refinedev/core) → 数据获取与提交getOne/create/update几个关键实现细节1. 双重参数通道。顶层useForm接收refineCoreProps与其余参数源码第 93-113 行前者透传给useFormCore负责数据请求、提交后者透传给useMantineForm负责表单状态因此一套 hooks 同时管理了表单 UI 状态与服务端数据状态。2. 表单状态委托给 Mantine。源码第 127-141 行 将除refineCoreProps外的配置initialValues、validate等原样交给mantine/form的useForm并从其结果中解构出setValues、onSubmit、isDirty、resetDirty、setFieldError、values——示例中使用的setFieldValue与values正是来自这一层。3. 服务端错误自动映射到字段。源码第 152-164 行 在onMutationError中读取error.errors逐字段调用setFieldError将后端返回的字段级错误映射到对应表单字段上可通过disableServerSideValidation关闭。这意味着 Base64 图片字段若被后端校验拒绝同样能以表单错误的形式反馈给用户。4. 返回值的组装。源码第 253-259 行 最终返回{ ...useMantineFormResult, onSubmit, refineCore: useFormCoreResult }——这就是为什么示例中既能直接拿到getInputProps、setFieldValue、errors又能通过refineCore.query访问数据查询结果。类型定义与资源结构示例的类型定义见 src/interfaces/index.d.tsexport interface ICategory { id: number; title: string; } export interface IPost { id: number; title: string; content: string; status: published | draft | rejected; category: { id: number }; }注意IPost中并未声明images字段——表单层通过FormValues类型承载images: string[]体现了数据模型与表单模型分离的写法数据库记录关心业务字段表单可以携带额外的提交数据图片、确认密码、临时状态等。列表页 list.tsx 与详情页 show.tsx 仍按IPost渲染标准字段不受影响。实战注意事项体积膨胀与内存占用Base64 会使数据体积增加约 33%且整个字符串常驻表单状态中多张大图可能导致页面卡顿与请求体过大建议限制文件数量与大小IMAGE_MIME_TYPE约束Dropzone 的accept属性只做客户端过滤服务端仍需校验 MIME 与解码合法性key稳定性预览使用index作为key若需支持删除中间图片建议改为稳定唯一标识后端解码后端需能解析 Data URL截取base64,之后的部分再atob/Buffer.from解码或直接以字符串形式存入数据库具体取决于业务设计增量追加语义handleOnDrop中的[...values.images, base64]依赖闭包中的最新values多文件拖拽时逐次追加逻辑上安全若追求更严格的并发一致性可改用setValues基于函数式更新。总结Base64 上传是 Refine 管理后台中最轻量的文件上传方案之一前端仅需FileReader.readAsDataURL完成编码、setFieldValue写入表单、Image完成预览后端无需任何上传中间件即可随 CRUD 请求一并处理。结合 examples/upload-mantine-base64 示例与 useForm 源码 可以看出Refine 的useForm通过Mantine 表单层 Refine 数据层的双层架构让开发者可以用最小的胶水代码把任意组件包括文件选择组件接入完整的 CRUD 数据流。对于大文件与批量上传场景则建议参考同目录下的 multipart 方案 与仓库中的 upload-mantine-multipart 示例按业务需求权衡选择。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考