如何发送包含图像的表单数据

How to send formData that includes image

我正在尝试使用 React 前端将我的表单发送到 NodeJS 后端,但我没有在 formData() 对象中获取任何数据,这是我的 React 代码:

import { useState, useEffect } from 'react'
import Axios from 'axios'
import Notification from '../../../components/Notification'

const DashboardWorkAdd = ({ subTitle = 'works' }) => {
  //Form States
  const [workName, setWorkName] = useState('')
  const [workGithub, setWorkGithub] = useState('')
  const [workOnlineLink, setWorkOnlineLink] = useState('')
  const [workDate, setWorkDate] = useState('')
  const [workDesc, setWorkDesc] = useState('')
  const [workAdded, setworkAdded] = useState('')
  const [workAddedMsg, setworkAddedMsg] = useState('')

  const [workImgFile, setWorkImgFile] = useState('')
  const [preview, setPreview] = useState()

  const formMsg = document.querySelector('.notification__msg')

  const updateWorkImg = (e) => {
    const file = e.target.files[0]

    if (file) {
      const fileType = file.type.split('/')[0]
      const fileSizeToMB = file.size / 1000000
      const MAX_FILE_SIZE = 1 //mb

      if (formMsg) {
        formMsg.classList.remove('hidden')

        if (fileType !== 'image') {
          formMsg.textContent = 'you can add only image file'
        } else if (fileSizeToMB > MAX_FILE_SIZE) {
          formMsg.textContent = `you can't add more than ${MAX_FILE_SIZE} MB`
          return
        } else {
          formMsg.classList.add('hidden')
          formMsg.textContent = ''
          setWorkImgFile(file)
        }
      }
    }
  }

  useEffect(() => {
    // if there's an image
    if (workImgFile) {
      const reader = new FileReader()

      reader.onloadend = () => setPreview(reader.result)

      reader.readAsDataURL(workImgFile)
    } else {
      setPreview(null)
    }
  }, [workImgFile])

  const handleAddWork = async (e) => {
    e.preventDefault()

    //using FormData to send constructed data
    const formData = new FormData()
    formData.append('workImg', workImgFile)
    formData.append('workName', workName)
    formData.append('workGithub', workGithub)
    formData.append('workOnlineLink', workOnlineLink)
    formData.append('workDate', workDate)
    formData.append('workDesc', workDesc)

    console.log(formData)

    if (
      (workName === '' || workGithub === '' || workOnlineLink === '' || workDate === '',
      workDesc === '')
    ) {
      formMsg.textContent = 'please add all data'
    } else {
      try {
        const response = await Axios.post(
          `${
            process.env.NODE_ENV === 'development'
              ? process.env.REACT_APP_API_LOCAL_URL
              : process.env.REACT_APP_API_URL
          }/workAdd`,
          { workImgFile, workName, workGithub, workOnlineLink, workDate, workDesc }
        )

        const { workAdded, message } = response.data
        setworkAdded(workAdded)
        setworkAddedMsg(message)
      } catch (err) {
        console.error(err)
      }
    }
  }

  return (
    <>
      <h3 className='text-3xl mt-20 mb-12 text-center font-semibold'>{subTitle}</h3>

      <div className='h-full'>
        <div className='flex flex-col gap-3 py-4 text-sm font-semibold'>
          <Notification sendStatus={workAdded} sendStatusMsg={workAddedMsg} />
          <div className='notification__msg'></div>
          <form
            method='POST'
            className='flex flex-col gap-14'
            encType='multipart/form-data'
            onSubmit={handleAddWork}
          >
            <label
              htmlFor='workImg'
              className='flex flex-wrap justify-center gap-5 md:justify-between items-center cursor-pointer'
            >
              <img
                src={
                  preview === null ? 'https://source.unsplash.com/random?webdev' : preview
                }
                alt='Work Portfolio Preview'
                className='w-36 h-36'
              />
              <input
                type='file'
                name='workImg'
                id='workImg'
                className='bg-blue-500 py-6 px-28 rounded-lg text-white uppercase font-semibold cursor-pointer'
                accept='image/*'
                onChange={updateWorkImg}
                multiple
                required
              />
            </label>
            <div className='dashboard-group'>
              <label htmlFor='workName'>work name</label>
              <input
                type='text'
                id='workName'
                autoFocus
                onChange={(e) => {
                  setWorkName(e.target.value.trim())
                }}
                required
              />
            </div>
            <div className='dashboard-group'>
              <label htmlFor='workLinkGithub'>Github Link</label>
              <input
                type='text'
                id='workLinkGithub'
                min='5'
                max='500'
                onChange={(e) => setWorkGithub(e.target.value.trim())}
                required
              />
            </div>
            <div className='dashboard-group'>
              <label htmlFor='workLinkOnline'>Online Linl</label>
              <input
                type='text'
                id='workLinkOnline'
                min='5'
                max='500'
                onChange={(e) => setWorkOnlineLink(e.target.value.trim())}
                required
              />
            </div>
            <div className='dashboard-group'>
              <label htmlFor='workDate'>Work Date</label>
              <input
                type='date'
                id='workDate'
                min='5'
                max='500'
                onChange={(e) => setWorkDate(e.target.value.trim())}
                required
              />
            </div>
            <div className='dashboard-group'>
              <label htmlFor='workDescription'>word description</label>
              <textarea
                name='workDescription'
                id='workDescription'
                minLength='10'
                maxLength='300'
                className=''
                onChange={(e) => setWorkDesc(e.target.value.trim())}
                required
              ></textarea>
            </div>
            <div className='flex justify-around text-lg'>
              <button
                type='submit'
                className='bg-green-500 hover:bg-green-600 py-2 px-20 rounded-lg text-white transition-colors'
              >
                Add
              </button>
            </div>
          </form>
        </div>
      </div>
    </>
  )
}

export default DashboardWorkAdd

这是我的 NodeJS 代码:

const WorksModel = require('../models/WorkModel')
const { v4: uuidv4 } = require('uuid')

module.exports = async (req, res) => {
  const { workName, workGithub, workOnlineLink, workDate, workDesc } = req.body
  const { workImg } = req.files

  const workImgName = uuidv4() + workImg.name

  const workImgMovePath = `${__dirname}/../../client/public/uploads/${workImgName}`
  const workImgDisplayPath = `/uploads/${workImgName}`

  const works = new WorksModel({
    workImgDisplayPath,
    workName,
    workGithub,
    workOnlineLink,
    workDate,
    workDesc
  })

  try {
    await works.save()
    workImg.mv(workImgMovePath, (err) => {
      if (err) {
        res.send({ message: `sorry, something wrong with the server: ${error}` })
        return res.status(500).send(err)
      }
    })

    res.send({
      message: 'added succesfully',
      workAdded: 1
    })
  } catch (error) {
    res.send({
      message: `something went wrong ${error}`,
      workAdded: 0
    })
  }
}

我的 WorkModel 文件是:

const mongoose = require('mongoose')

const WorkSchema = new mongoose.Schema({
  workImgDisplayPath: {
    type: String,
    required: true
  },
  workName: {
    type: String,
    required: true
  },
  workGithub: {
    type: String,
    required: true
  },
  workOnlineLink: {
    type: String,
    required: true
  },
  workDate: {
    type: Date,
    required: true,
    default: new Date().toLocaleDateString('ar-EG', {
      weekday: 'long',
      year: 'numeric',
      month: 'short',
      day: 'numeric'
    })
  },
  workDesc: {
    type: String,
    required: true
  }
})

const WorkModel = mongoose.model('mhmdhidrPortfolio', WorkSchema)

module.exports = WorkModel

我在后端获取数据时遇到的问题是 req.files 未定义,我在谷歌上搜索了很多,但我并不确切知道这个问题。

感谢您的帮助。

我解决了我的问题,我只需要添加:

const fileUpload = require('express-fileupload')

app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.use(fileUpload())

在我的 index.js(服务器)文件中