我用这个页面来创建帖子。它最初是为了上传多个图像而创建的,但是在更改了一些内容以使其接受单个图像之后,代码似乎在提交过程中中断,但在选择图像时工作正常。
当我点击提交时,我从控制台中的cloudinary api收到错误消息
Failed to load resource: the server responded with a status of 400 (Bad Request) client.js:26 Error uploading image: AxiosError
。
下面是对应的代码 创建帖子页面
import styles from "@/styles/products.module.scss";
import Layout from "@/components/admin/layout";
import db from "@/utils/db";
import Category from "@/models/Category";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
import axios from "axios";
import * as Yup from "yup";
import { Form, Formik } from "formik";
import SingularSelect from "@/components/selects/SingularSelect";
import AdminInput from "@/components/inputs/adminInput";
import DialogModal from "@/components/dialogModal";
import { useDispatch } from "react-redux";
import Images from "@/components/admin/createProduct/images";
import { uploadImages } from "@/requests/upload";
import DotLoader from "@/components/loaders/dotLoader";
import { useRouter } from "next/router";
import News from "@/models/News";
const initialState = {
name: "",
description: "",
post: "",
image: "",
parent: "",
category: "",
subCategories: [],
};
export default function create({ parents, categories }) {
const [product, setProduct] = useState(initialState);
const [subs, setSubs] = useState([]);
const [image, setImage] = useState("");
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();
const router = useRouter();
useEffect(() => {
const getParentData = async () => {
if (product.parent) {
try {
const { data } = await axios.get(`/api/product/${product.parent}`);
if (data) {
setProduct({
...product,
name: data.name,
description: data.description,
category: data.category,
subCategories: data.subCategories,
});
}
} catch (error) {
console.error("Error fetching parent data:", error);
}
}
};
getParentData();
}, [product.parent]);
const handleChange = (e) => {
const { value, name } = e.target;
setProduct({ ...product, [name]: value });
};
const validate = Yup.object({
name: Yup.string()
.required("Please add a name")
.min(10, "Product name must bewteen 10 and 300 characters.")
.max(300, "Product name must bewteen 10 and 300 characters."),
category: Yup.string().required("Please select a category."),
description: Yup.string().required("Please add a description"),
});
const createProductHandler = async () => {
try {
setLoading(true);
let uploaded_image = "";
if (image) {
const path = "product images";
let formData = new FormData();
formData.append("path", path);
formData.append("file", image);
const response = await uploadImages(formData);
console.log(response);
uploaded_image = response;
}
setLoading(false);
console.log("successful", data);
toast.success(data.message);
router.push("/admin/dashboard/product/all");
router.push("/admin/dashboard/product/all");
} catch (error) {
setLoading(false);
toast.error(error);
}
};
return (
<>
<Layout>
{loading && <DotLoader loading={loading} />}
<div className={styles.header}>Create Product</div>
<DialogModal />
<Formik
enableReinitialize
initialValues={{
name: product.name,
post: product.post,
description: product.description,
category: product.category,
subCategories: product.subCategories,
parent: product.parent,
imageInputFile: "",
}}
validationSchema={validate}
onSubmit={() => {
createProductHandler();
}}
>
{(formik) => (
<Form>
<Images
name="imageInputFile"
header="Post Image"
text="Add images"
image={image}
setImage={setImage}
/>
<AdminInput
type="text"
label="Name"
name="name"
placholder="Post name"
onChange={handleChange}
/>
<AdminInput
type="text"
label="Post"
name="post"
placholder="Post story"
onChange={handleChange}
/>
<SingularSelect
name="category"
value={product.category}
placeholder="Category"
data={categories}
header="Select a Category"
handleChange={handleChange}
disabled={product.parent}
/>
<div className={styles.header}>Basic Infos</div>
<AdminInput
type="text"
label="Description"
name="description"
placholder="Post description"
onChange={handleChange}
/>
<button
className={`${styles.btn} ${styles.btn__primary} ${styles.submit_btn}`}
type="submit"
>
Create Product
</button>
</Form>
)}
</Formik>
</Layout>
</>
);
}
export async function getServerSideProps(ctx) {
await db.connectDb();
const results = await News.find().select("name").lean();
const categories = await Category.find().lean();
// db.disconnectDb();
return {
props: {
parents: JSON.parse(JSON.stringify(results)),
categories: JSON.parse(JSON.stringify(categories)),
},
};
}
选择图像页面
import { ErrorMessage, useField } from "formik";
import { useRef } from "react";
import { FaStaylinked } from "react-icons/fa";
import { RiDeleteBin7Fill, RiShape2Line } from "react-icons/ri";
import { useDispatch } from "react-redux";
import { showDialog } from "@/store/DialogSlice";
import styles from "./styles.module.scss";
import { GiExtractionOrb } from "react-icons/gi";
export default function Images({
image,
setImage,
header,
text,
name,
setColorImage,
...props
}) {
const dispatch = useDispatch();
const fileInput = useRef(null);
const [meta, field] = useField(props);
const handleImages = (e) => {
const file = e.target.files[0]; // Get the first selected file from the input
const reader = new FileReader();
reader.readAsDataURL(file); // Read the file as a data URL
reader.onload = (e) => {
setImage(e.target.result); // Update the state with the image data URL
};
};
// const handleRemove = (image) => {
// setImage((image) => image.filter((item) => item !== image));
// };
const handleRemove = () => {
setImage(""); // Set the image state to an empty string
};
return (
<div className={styles.images}>
<div
className={`${styles.header} ${meta.error ? styles.header__error : ""}`}
>
<div className={styles.flex}>
{meta.error && <img src="../../../images/warning.png" alt="" />}
{header}
</div>
<span>
{meta.touched && meta.error && (
<div className={styles.error__msg}>
<span></span>
<ErrorMessage name={name} />
</div>
)}
</span>
</div>
<input
type="file"
name={name}
ref={fileInput}
hidden
// multiple
accept="image/jpeg,image/png,image/webp"
onChange={handleImages}
// {...field}
/>
<div className={styles.images__main}>
<div className={`${styles.images__main_grid}`}>
{!image?.length ? (
<img src="../../../images/no_image.png" alt="" />
) : (
<div className={styles.images__main_grid_wrap}>
<div className={styles.blur}></div>
<img src={image} alt="" />
<div className={styles.images__main_grid_actions}>
<button onClick={() => handleRemove(image)}>
<RiDeleteBin7Fill />
</button>
<button>
<RiShape2Line />
</button>
</div>
</div>
)}
</div>
</div>
<button
type="reset"
style={{ opacity: `${image?.length >= 1 && "0.5"}` }}
onClick={() => fileInput.current.click()}
className={`${styles.btn} ${styles.btn__primary}`}
// disabled={image?.length >= 1}
>
{text}
</button>
</div>
);
}
上传至云端页面
import axios from "axios";
export const uploadImages = async (formData) => {
try {
const response = await axios.post("/api/cloudinary", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
console.log(response);
return response.data;
} catch (error) {
console.error("Error uploading image:", error);
throw new Error("Failed to upload image to Cloudinary.");
}
};
发布 Cloudinary api 路由页面
import { createRouter } from "next-connect";
import cloudinary from "cloudinary";
import bodyParser from "body-parser";
import fs from "fs";
import fileUpload from "express-fileupload";
import { imgMiddleware } from "../../../middleware/imgMiddleware";
cloudinary.config({
cloud_name: process.env.CLOUDINARY_NAME,
api_key: process.env.CLOUDINARY_KEY,
api_secret: process.env.CLOUDINARY_SECRET,
});
const router = createRouter()
.use(
fileUpload({
useTempFiles: true,
tempFileDir: "/tmp/",
})
)
.use(imgMiddleware);
export const config = {
api: {
bodyParser: false,
},
};
router.post(async (req, res) => {
try {
const { path } = req.body || {};
// let files = Object.values(req.files).flat();
let file = req.file;
console.log(file);
// let image = "";
// for (const file of files) {
const img = await uploadToCloudinaryHandler(file, path);
// image.push(img);
removeTmp(file.tempFilePath);
// }
res.json(img);
} catch (error) {
console.log(error);
return res.status(500).json({ message: error.message });
}
});
router.delete(async (req, res) => {
let image_id = req.body.public_id;
cloudinary.v2.uploader.destroy(image_id, (err, res) => {
if (err) return res.status(400).json({ success: false, err });
res.json({ success: true });
});
});
const uploadToCloudinaryHandler = async (file, path) => {
return new Promise((resolve) => {
cloudinary.v2.uploader.upload(
file.tempFilePath,
{
folder: path,
},
(err, res) => {
if (err) {
removeTmp(file.tempFilePath);
console.log(err);
return res.status(400).json({ message: "Upload image failed." });
}
resolve({
url: res.secure_url,
public_url: res.public_id,
});
}
);
});
};
const removeTmp = (path) => {
fs.unlink(path, (err) => {
if (err) throw err;
});
};
export default router.handler();
最后,post 路由处理程序页面 api
import { createRouter } from "next-connect";
import db from "@/utils/db";
import Product from "@/models/News";
import auth from "@/middleware/auth";
import admin from "@/middleware/admin";
import slugify from "slugify";
const router = createRouter().use(auth).use(admin);
router.post(async (req, res) => {
try {
db.connectDb();
req.body.slug = slugify(req.body.name);
const newProduct = new Product({
name: req.body.name,
description: req.body.description,
brand: req.body.brand,
slug: slugify(req.body.name),
category: req.body.category,
subCategories: req.body.subCategories,
});
await newProduct.save();
res.status(200).json({ message: "Post created Successfully." });
db.disconnectDb();
} catch (error) {
res.status(500).json({ message: error.message });
}
});
显然,在云页面中,我仍然必须通过重组图像来接收图像以接收文件,即
const file = req.files.file
而不是之前的req.file