我想创建一个产品页面,接下来我可以使用 Cloudinary 上传多个图像。这里我创建了一个上传图片的组件
"use client";
import { CldUploadWidget } from 'next-cloudinary';
import { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import Image from 'next/image';
import { ImagePlus, Trash } from 'lucide-react';
interface ImageUploadProps {
disabled?: boolean;
onChange: (value: string) => void;
onRemove: (value: string) => void;
value: string[];
}
const ImageUpload: React.FC<ImageUploadProps> = ({
disabled,
onChange,
onRemove,
value
}) => {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
const onUpload = (result: any) => {
onChange(result.info.secure_url);
};
if (!isMounted) {
return null;
}
return (
<div>
<div className="mb-4 flex items-center gap-4">
{value.map((url) => (
<div key={url} className="relative w-[200px] h-[200px] rounded-md overflow-hidden">
<div className="z-10 absolute top-2 right-2">
<Button type="button" onClick={() => onRemove(url)} variant="destructive" size="sm">
<Trash className="h-4 w-4" />
</Button>
</div>
<Image
fill
sizes=''
className="object-cover"
alt="Image"
src={url}
/>
</div>
))
}
</div>
<CldUploadWidget onSuccess={onUpload} uploadPreset="ox48luzl">
{({ open }) => {
const onClick = () => {
open();
};
return (
<Button
type="button"
disabled={disabled}
variant="secondary"
onClick={onClick}
>
<ImagePlus className="h-4 w-4 mr-2" />
Upload an Image
</Button>
);
}}
</CldUploadWidget>
</div>
);
}
export default ImageUpload;
和
我称之为图像上传
'use client'
import { Button } from "@/components/ui/button"
import { Heading } from "@/components/ui/heading"
import { Product, Image, Category } from "@prisma/client";
import { Trash } from "lucide-react"
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form";
import * as z from "zod"
import { AlertModal } from "@/components/modals/alert-model";
import axios from "axios";
import toast from "react-hot-toast";
import { Separator } from "@/components/ui/separator";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import ImageUpload from "@/components/ui/image-upload";
const formSchema = z.object({
name: z.string().min(1),
promocode: z.string().min(2),
affiliateLink: z.string().min(1),
description: z.string().min(1),
images:z.object({url:z.string()}).array(),
categoryId: z.string().min(1),
price: z.coerce.number().min(1),
})
type ProductFormValues = z.infer<typeof formSchema>;
interface ProductFormProps {
initialData: Product & {
images: Image[]
} | null;
categories: Category[]
};
const ProductForm: React.FC<ProductFormProps> = ({
initialData,
categories
}) => {
const params = useParams();
const router = useRouter();
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const title = initialData ? 'Edit Product' : 'Create Product'
const description = initialData ? 'Edit a Product' : 'Add a new Product';
const toastMassege = initialData ? 'Product Update' : 'Product Created';
const action = initialData ? 'Save Changes' : 'Create';
const defaultValues = initialData ? {
...initialData,
price: parseFloat(String(initialData?.price)),
promocode: initialData.promocode || "",
} : {
name: '',
images:[],
price:0,
description: '',
catogoryId: '',
promocode: '',
affiliateLink: '',
}
const form = useForm<ProductFormValues>({
resolver: zodResolver(formSchema),defaultValues
})
const onDelete = async () => {
try {
setLoading(true)
await axios.delete(`/api/products/${params.productId}`)
router.push('/products')
toast.success('Product Deleted Successfully!')
} catch (error: any) {
toast.error('something wen wrong')
}
finally {
setLoading(false)
}
}
return (
<>
<AlertModal
isOpen={open}
onClose={() => setOpen(true)}
onConfirm={onDelete}
loading={loading}
/>
<div className="flex item-center justify-between">
<Heading title={title} description={description} />
{initialData &&(
<Button
disabled={loading}
variant="destructive"
size="sm"
>
<Trash className="h-4 w-4" />
</Button>
)}
</div>
<Separator/>
<Form {...form}>
<form className="space-y-8 w-full">
<FormField
control={form.control}
name="images"
render={({ field }) => (
<FormItem>
<FormLabel>Images</FormLabel>
<FormControl>
<ImageUpload
value={field.value.map((image)=>image.url)}
disabled={loading}
onChange={(url) => field.onChange([...field.value, { url }])}
onRemove={(url) => field.onChange([...field.value.filter((current) => current.url !== url)])}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="name"
render={({field})=>(
<FormItem>
<FormLabel>Product Name</FormLabel>
<FormControl>
<Input disabled={loading} placeholder="Enter Product Name" {...field}/>
</FormControl>
</FormItem>
)} />
</form>
</Form>
</>
)
}
export default ProductForm
在这段代码中,几乎一切都工作正常,但是当尝试在 Cloudinary 小部件中上传多个图像时,只有第一个或第一个上传的图像显示并存储在值中
想要实现上传和存储的图像 URL 数组。
您需要将
multiple
参数添加到上传小部件。