将图像从 React 上传到 Laravel 后端会清空请求正文

Uploading images from React to the Laravel backend empties the request body

提问人:Imperfectcircle 提问时间:10/11/2023 最后编辑:Imperfectcircle 更新时间:10/11/2023 访问量:55

问:

我正在使用 Inertia.js 使用 Laravel 和 React 开发一个电子商务网站。我使用 Dropzone 创建了一个用于图像上传的组件。此组件用于表单中的页面,用于创建和更新产品。

关于产品创建,没有问题,一切都按预期进行。当我尝试更新产品时出现问题。如果我只修改字段而不添加图像,则更新可以完美运行。但是,当我添加一个或多个图像时,所有字段都会触发验证错误,就好像它们留空一样。

我尝试在 Request 类中对$request进行“转储和死亡”,并注意到当包含图像时它是空的。我还在前端使用了console.log语句来检查数据是否正确发送并且一切似乎都很好。我认为问题在于我如何发送数据,但我无法确定确切的错误。

这是我的代码

图片上传组件

export default function ImageUploader({ className, setData, data }) {
    const [files, setFiles] = useState([]);
    const [rejected, setRejected] = useState([]);

    useEffect(() => {
        setData({ ...data, images: files });
    }, [files]);
    //[JSON.stringify(...files)]
    const onDrop = useCallback((acceptedFiles, rejectedFiles) => {
        if (acceptedFiles?.length) {
            setFiles((previousFiles) => [
                ...previousFiles,
                ...acceptedFiles.map((file) =>
                    Object.assign(file, { preview: URL.createObjectURL(file) }),
                ),
            ]);
        }

        if (rejectedFiles?.length) {
            setRejected((previousFiles) => [
                ...previousFiles,
                ...rejectedFiles,
            ]);
        }
    }, []);
    const { getRootProps, getInputProps, isDragActive } = useDropzone({
        onDrop,
        accept: {
            'image/*': [],
        },
        maxSize: 1024 * 1000,
    });

    const removeFile = (name) => {
        setFiles((files) => files.filter((file) => file.name !== name));
    };

    const removeRejected = (name) => {
        setRejected((files) => files.filter(({ file }) => file.name !== name));
    };

    return (
        <>
            <InputLabel
                className={`text-xl`}
                htmlFor="images"
                value="Immagini"
            />

            <p className="pt-2">Dimensioni massime: 1 Mb</p>

            <div
                {...getRootProps({
                    className,
                })}
            >
                <input
                    {...getInputProps()}
                    id="images"
                    name="images"
                    form="productForm"
                />
                {isDragActive ? (
                    <p>Trascina i file qui ...</p>
                ) : (
                    <p>Trascina qui i file, o clicca per selezionarli</p>
                )}
            </div>

            ....
            ....
            ....
}

数据表单

export default function ProductForm({ auth, product, categories }) {
    const { data, setData, post, put, processing, errors } = useForm(
        product
            ? {
                  name: product.name,
                  description: product.description,
                  sku: product.sku,
                  price: product.price,
                  discounted_price: product.discounted_price,
                  cost: product.cost,
                  quantity: product.quantity,
                  track_quantity: product.track_quantity,
                  sell_out_of_stock: product.sell_out_of_stock,
                  status: product.status,
                  category_id: product.category_id,
                  images: [],
              }
            : {
                  name: '',
                  description: '',
                  sku: '',
                  price: '',
                  discounted_price: '',
                  cost: '',
                  quantity: '',
                  track_quantity: true,
                  sell_out_of_stock: false,
                  status: '',
                  category_id: '',
                  images: [],
              },
    );

    const submit = (event) => {
        event.preventDefault();
        if (product) {
            put(route('admin.products.update', product));
            return;
        }
        post(route('admin.products.store'));
    };

    ....
    ....
    ....
<ImageUploader
    setData={setData}
    data={data}
    className="mt-2 rounded-lg border border-gray-400 bg-gray-100 p-16 text-center shadow-lg"
/>

请求类

public function authorize(): bool
    {
        return $this->user()->can('update', $this->route('product'));
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        dd($this->request);
        return [
            'name' => 'required|string|max:255',
            'description' => 'required|string',
            'sku' =>
                'required|string|unique:products,sku,'.$this->route('product')->id,
            'track_quantity' => 'sometimes|nullable|boolean',
            'quantity' => 'required_if:track_quantity,true|nullable|int',
            'sell_out_of_stock' => 'required_if:track_quantity,true|boolean',
            'category_id' => 'required|int|exists:categories,id',
            'price' => 'required|numeric|min:0',
            'cost' => 'sometimes|nullable|numeric',
            'discounted_price' => 'sometimes|nullable|numeric',
            'status' => 'required|string|in:active,draft,review',
            'images' => 'sometimes|nullable|array',
        ];
    }

Update 方法

public function update(UpdateProductRequest $request, Product $product)
    {
        $product->update(
            $request
                ->safe()
                ->collect()
                ->filter(fn($value) => !is_null($value))
                ->except(['images'])
                ->all()
        );

        $images = $request->file('images');

        if ($images !== null) {
            foreach ($images as $image) {
                Cloudinary::upload($image->getRealPath(), [
                    'transformation' => [
                        'width' => '700',
                        'quality' => 'auto',
                        'crop' => 'scale',
                    ]
                ])->getSecurePath();

                $product->attachMedia($image);
            }
        }

        return to_route('admin.products.index')->with('message', 'Prodotto aggiornato con successo');
    }

console.log 数据

Reactjs Laravel Validation inertiajs

评论


答:

0赞 waterloomatt 10/11/2023 #1

惯性会自动将您的表单转换为对象。FormData

https://inertiajs.com/file-uploads#form-data-conversion

当发出包含文件(甚至是嵌套文件)的惯性请求时, Inertia 会自动将请求数据转换为 FormData 对象。此转换是必要的,以便提交 通过 XHR 的 multipart/form-data 请求。

但是 Laravel 不支持该方法,因此您必须使用 .PUTmultipart/form-dataPOST_method: 'put'

https://inertiajs.com/file-uploads#multipart-limitations

使用 multipart/form-data 请求上传文件不是原生的 在某些服务器端框架中使用 PUT、PATCH 或 删除 HTTP 方法。此限制的最简单解决方法是 只需使用 POST 请求上传文件即可。

但是,一些框架,如 Laravel 和 Rails,支持表单 方法欺骗,它允许您使用 POST 上传文件,但 让框架将请求作为 PUT 或 PATCH 请求处理。这 是通过在请求的数据中包含_method属性来完成的。

因此,您的代码应如下所示。我相信 Laravel 会自动将其解释为 PUT 请求。

// Post the form, but tell Laravel to treat it as a PUT request.
post(route('admin.products.update', product), {
    _method: 'put',
});

评论

0赞 Imperfectcircle 10/12/2023
谢谢,你已经指出了问题所在。我尝试修改代码并使用 /admin/products/${product.id}/,因为 useForm 的 post 方法似乎不接受 _method 参数。现在的问题是,当我提交表单时,我总是被重定向到我来自的页面,而不会收到任何错误。我尝试在控制器的更新方法中使用 dump 和 die,但它没有触发,所以我认为它甚至没有到达该方法。router.post(, { _method: 'put', data })
0赞 waterloomatt 10/12/2023
您的网络选项卡显示什么?什么是HTTP代码?403?日志中有什么内容吗?
1赞 Imperfectcircle 10/12/2023
我设法解决了它,解决方案是直接在数据对象中添加_method键。感谢您的帮助。