Next.js + Formik实现单图与多图本地文件夹上传方案咨询
Next.js + Formik 实现单/多张图片上传方案
针对你的需求,直接在前端用FileReader配合Formik不是最优解——浏览器环境没有权限直接写入项目文件夹,必须通过Next.js的API路由处理文件存储。以下是最简实现方案:
一、前端Formik适配文件上传
修改现有代码中的Formik配置和文件输入逻辑:
1. 调整Formik初始值与验证规则
// 修改Formik initialValues initialValues= {{ title: '', brand: '', category: '', price: '', quantity: '', mImage: null, // 单张文件用null初始值 aImages: [], // 多张文件用空数组 }} // 更新Yup验证规则 const validateSchema = Yup.object().shape({ title: Yup.string().required('Field is required'), brand: Yup.string().required('Field is required'), category: Yup.string().required('Field is required'), price: Yup.string().required('Field is required'), quantity: Yup.string().required('Field is required'), mImage: Yup.mixed().required('主图不能为空'), // 验证文件存在 aImages: Yup.array().of(Yup.mixed()), // 多张文件数组验证 });
2. 修改文件输入的onChange逻辑
不用Formik默认的handleChange,改用setFieldValue手动更新文件值,并开启多张图片的multiple属性:
// 主图字段 <Field dot={true} error={props.touched?.mImage && props.errors?.mImage} label="Main Image" name="mImage" onChange={(e) => props.setFieldValue('mImage', e.target.files[0])} // 自定义onChange type="file" /> // 额外图片字段 <Field dot={false} error={props.touched?.aImages && props.errors?.aImages} label="Addtional Images" name="aImages" onChange={(e) => props.setFieldValue('aImages', Array.from(e.target.files))} // 转成数组 type="file" multiple="multiple" // 开启多选 />
3. 重构表单提交逻辑
将JSON格式改为FormData,调用API路由上传文件:
onSubmit={ async(values, { setSubmitting }) => { const formData = new FormData(); // 追加普通表单字段 Object.keys(values).forEach(key => { if (key !== 'mImage' && key !== 'aImages') { formData.append(key, values[key]); } }); // 追加单张主图 if (values.mImage) { formData.append('mImage', values.mImage); } // 追加多张额外图片 values.aImages.forEach(file => { formData.append('aImages', file); }); try { const res = await fetch("/api/upload", { method: 'POST', body: formData, headers: { "Authorization": `Bearer ${session.user.accessToken}` } }); if (res.ok) { const result = await res.json(); console.log("产品创建成功", result); Router.push("/products"); // 上传成功后跳转 } else { console.error("上传失败"); } } catch (err) { console.error("请求错误", err); } finally { setSubmitting(false); } }}
二、创建API路由处理文件存储
在pages/api目录下新建upload.js,用multer处理文件上传(先执行npm install multer安装依赖):
import multer from 'multer'; import fs from 'fs'; import path from 'path'; // 确保public/uploads目录存在 const uploadDir = path.join(process.cwd(), 'public', 'uploads'); if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } // 配置multer存储规则:生成唯一文件名避免覆盖 const storage = multer.diskStorage({ destination: (req, file, cb) => cb(null, uploadDir), filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); cb(null, `${uniqueSuffix}-${file.originalname}`); } }); const upload = multer({ storage }); // 配置接收单张主图和多张额外图片 const uploadMiddleware = upload.fields([ { name: 'mImage', maxCount: 1 }, { name: 'aImages', maxCount: 10 } // 可调整最多上传数量 ]); export default function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ message: "仅支持POST请求" }); } uploadMiddleware(req, res, (err) => { if (err) { return res.status(500).json({ message: "文件上传失败", error: err.message }); } // 提取文件相对路径(用于数据库存储) const mImageUrl = req.files?.mImage?.[0]?.path.replace(process.cwd() + '/public', '') || ''; const aImagesUrls = req.files?.aImages?.map(file => file.path.replace(process.cwd() + '/public', '')) || []; // 这里可以将产品信息+文件路径写入数据库 // 例如:await Product.create({ ...req.body, mImage: mImageUrl, aImages: aImagesUrls }) res.status(200).json({ message: "上传成功", product: { ...req.body, mImage: mImageUrl, aImages: aImagesUrls } }); }); } // 禁用Next.js默认的body解析,让multer处理FormData export const config = { api: { bodyParser: false } };
三、注意事项
- 确保
public/uploads目录存在,API路由会自动创建,但手动创建更稳妥 - 生产环境建议将文件上传到云存储(如阿里云OSS、AWS S3),避免占用服务器磁盘空间
- 可在API路由中添加文件类型验证(仅允许jpg/png等图片格式)
内容的提问来源于stack exchange,提问作者Xyz.WebDeveloper
相关产品推荐
相关产品推荐

