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

Next.js 13批量传图至Cloudinary并同步URL到Supabase求助

解决Next.js 13批量上传图片到Cloudinary并同步所有URL到Supabase的问题

问题根源

  1. Cloudinary单条上传请求仅支持处理一个文件,原代码一次性将所有文件放入同一个FormData提交,只会上传最后一个选中的图片
  2. 未遍历收集所有图片的上传返回URL,仅获取了单个图片地址
  3. 存入Supabase时仅保存了单个URL,未批量存储所有图片链接

修改后的完整代码

'use client';

import { useState, useRef } from 'react'
import { useUser } from '@/contexts/AuthContext';
import { Database } from '@/db_types'
import { redirect } from 'next/navigation';

import Button from "@/components/shared/Button"
import Editor from "@/components/shared/Editor"

type Posts = Database['public']['Tables']['posts']['Row']

const initialState = {
  title: "",
  description: "",
  slug: "",
  images: [],
};

export default function AddPost() {

  const { user, userDetails , supabase } = useUser();
  const [loading, setLoading] = useState(false)
  const [title, setTitle] = useState<Posts['title']>(null)
  const [description, setDescription] = useState<Posts['description']>(null)
  const [slug, setSlug] = useState<Posts['slug']>(null)
  const [imageData, setImageData] = useState(initialState);
  const imageInputRef = useRef<HTMLInputElement>(null);

  if (user) {
    redirect('/');
  }

  // 单个图片上传函数
  const uploadSingleImage = async (file: File) => {
    const data = new FormData();
    data.append("file", file);
    data.append("upload_preset", "c_tags");
    
    const res = await fetch(`${process.env.NEXT_PUBLIC_CLOUDINARY_API}`, {
      method: "POST",
      body: data,
    });
    
    const result = await res.json();
    return result.secure_url;
  }

  async function addPost() {
    try {
      const files = imageInputRef.current?.files;
      if (!files || files.length === 0) {
        alert("请选择至少一张图片");
        return;
      }
  
      setLoading(true);

      // 批量上传所有图片,收集所有URL
      const imageUrls = await Promise.all(
        Array.from(files).map(file => uploadSingleImage(file))
      );
      
      const updates = {
        title,
        description,
        slug,
        // 根据你的Supabase字段类型选择:如果是JSON数组就直接存imageUrls,否则用逗号分隔字符串
        images: JSON.stringify(imageUrls), 
        // 或者用逗号分隔:images: imageUrls.join(',')
        created_at: new Date().toISOString(),
        user_id: userDetails?.id,
      };
  
      let { error } = await supabase.from("posts").upsert(updates);
      if (error) throw error;
      alert("发布成功!");
      // 重置表单
      setTitle(null);
      setDescription(null);
      setSlug(null);
      setImageData(initialState);
      if (imageInputRef.current) imageInputRef.current.value = "";
    } catch (error) {
      alert("数据更新失败!");
      console.log(error);
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className="">
      <div>
      <form
          className="mt-3"
          onSubmit={(e) => {
            e.preventDefault();
            addPost()
          }}
        >
        <input
          id="images"
          type="file"
          className="relative block w-full appearance-none rounded-none rounded-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
          accept="image/*"
          ref={imageInputRef}
          multiple
          />

        <label htmlFor="title">Title</label>
        <input
          id="title"
          type="text"
          className="relative block w-full appearance-none rounded-none rounded-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
          value={title || ''}
          onChange={(e) => setTitle(e.target.value)}
        />
   
        
        <label htmlFor="Description">Content</label>
        <Editor
            description={description}
            setDescription={setDescription}
        />
    
        <label htmlFor="slug">Slug</label>
        <input
          id="slug"
          type="text"
          className="relative block w-full appearance-none rounded-none rounded-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
          value={slug || ''}
          onChange={(e) => setSlug(e.target.value)}
        />
          <div>
          <Button
            className="mt-5 bg-red-500"
            type="submit"
            disabled={loading}
          >
            {loading ? "发布中..." : "Publish"}
          </Button>
          </div>
        </form>
      </div>
    </div>
  );
}

关键修改说明

  • 拆分出uploadSingleImage函数,负责单个图片的上传逻辑
  • 使用Promise.all批量处理所有选中图片的上传请求,并行提升效率
  • 收集所有上传后的图片URL,根据Supabase的images字段类型选择存储格式(JSON数组或逗号分隔字符串)
  • 优化表单重置逻辑,上传成功后清空输入状态
  • 添加加载状态禁用按钮,防止重复提交

内容的提问来源于stack exchange,提问作者Tan Huynh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 04:38:09