You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 13:55:36