英文:
Post form data and file in one useFetch call in Nuxt 3
问题
我想要使用一个 useFetch()
调用同时将表单数据(包括字符串、数字、数组)和文件发送到后端,在 TypeScript 中。
我尝试过的
如果我将字符串和文件添加到 FormData
实例中,一切都运行正常。
但是,如果我尝试将数字或数组添加到 FormData
实例中,我会从 TypeScript 中收到错误(因为 FormData.append()
仅接受字符串或 Blob
)。我尝试将数字和数组转换为 JSON 字符串并将它们作为字符串发送,但然后我会在后端收到错误(我不想在后端进行任何转换)。
我尝试将表单数据作为简单的 JavaScript 对象发送 - 这也可以工作,但不适用于文件。
最终,我采用了以下代码,其中我决定将字符串/数字/数组作为 JavaScript 对象进行发送,以在后端创建新的数据库记录(createNewBook
),然后使用 PATCH
方法进行更新,并在 FormData
实例中传递文件(updateBookCover
)。
function useApi(
query: Object | undefined = undefined,
method: string = "GET",
token: string | null = null,
formData: FormData | Object | undefined = undefined,
) {
const config = useRuntimeConfig();
const get: typeof useFetch = (url) => {
return useFetch(url, {
params: query,
baseURL: config.public.apiBase + "/api/v1",
key: url.toString(),
method: method as any,
headers: token ? [["Authorization", "Token " + token]] : undefined,
body: formData,
});
};
return { get };
}
export async function createNewBook(book: Book) {
const authStore = useAuthStore();
let authorIds: number[] = [];
// Convert array of `Authors` to array of ids for backend.
book.authors.forEach((a) => authorIds.push(a.id));
const formData = {
title: book.title,
authors: authorIds,
publisher: book.publisher?.id,
year: book.year,
pages: book.pages,
description: book.description,
contents: book.contents,
};
const { get } = useApi(undefined, "POST", authStore.token, formData);
return await get<Book>("/books/create/");
}
export async function updateBookCover(bookId: number, coverImage: File) {
const authStore = useAuthStore();
const formData = new FormData();
formData.append("cover_image", coverImage);
const { get } = useApi(undefined, "PATCH", authStore.token, formData);
return await get<Book>(`/books/${bookId}/`);
}
完整模块代码 - https://github.com/hazadus/drf-nuxt-library/blob/main/frontend/useApi.ts
我想要实现的目标
上面的代码运行良好,但希望能够使用一个 useFetch
调用同时发送所有内容(表单数据和文件)。
英文:
I want to post form data (uncluding strings, numbers, arrays) AND a file to backend using one useFetch()
call, in TypeScript.
What I tried
If I add strings and a file to the FormData
instance, all works great.
But if I try to add numbers, or arrays to the FormData
instance, I get errors from TypeScript (because FormData.append()
accepts only strings or Blob
s). I tried to JSONify numbers and arrays and post them as strings, but then I get errors on backend (I don't want to convert anything on backend).
I tried to post form data as simple JS Object - it works, too, but not for a file.
I ended up with the following code, where I decided to post strings/numbers/arrays as JS Object to create new DB record on backend (createNewBook
), then update it using PATCH
method and pass file in FormData
instance (updateBookCover
).
function useApi(
query: Object | undefined = undefined,
method: string = "GET",
token: string | null = null,
formData: FormData | Object | undefined = undefined,
) {
const config = useRuntimeConfig();
const get: typeof useFetch = (url) => {
return useFetch(url, {
params: query,
baseURL: config.public.apiBase + "/api/v1",
key: url.toString(),
method: method as any,
headers: token ? [["Authorization", "Token " + token]] : undefined,
body: formData,
});
};
return { get };
}
export async function createNewBook(book: Book) {
const authStore = useAuthStore();
let authorIds: number[] = [];
// Convert array of `Authors` to array of ids for backend.
book.authors.forEach((a) => authorIds.push(a.id));
const formData = {
title: book.title,
authors: authorIds,
publisher: book.publisher?.id,
year: book.year,
pages: book.pages,
description: book.description,
contents: book.contents,
};
const { get } = useApi(undefined, "POST", authStore.token, formData);
return await get<Book>("/books/create/");
}
export async function updateBookCover(bookId: number, coverImage: File) {
const authStore = useAuthStore();
const formData = new FormData();
formData.append("cover_image", coverImage);
const { get } = useApi(undefined, "PATCH", authStore.token, formData);
return await get<Book>(`/books/${bookId}/`);
}
Full module code - https://github.com/hazadus/drf-nuxt-library/blob/main/frontend/useApi.ts
What I want to achieve
The code above works well, but it would be great to post everything (form data and files) using one useFetch
call.
答案1
得分: 2
你需要按照以下方式传递FormData()
:
let formData = new FormData();
// 添加你的文件或图像
formData.append("file", yourfileorimage);
// 添加其他数据
const formdata = {
title: book.title,
authors: authorIds,
publisher: book.publisher?.id,
year: book.year,
pages: book.pages,
description: book.description,
contents: book.contents
};
for (const item in formdata) {
formData.append(item, formdata[item]);
}
return await useFetch("YOUR-API-URL", {
method: "PUT",
body: formData,
headers: {"cache-control": "no-cache"},
});
英文:
You must need to pass FormData()
as below
let formData = new FormData();
//append your file or image
formData.append("file", yourfileorimage);
//append another data
const formdata = {
title: book.title,
authors: authorIds,
publisher: book.publisher?.id,
year: book.year,
pages: book.pages,
description: book.description,
contents: book.contents
};
for (const item in formdata) {
formData.append(item, formdata[item]);
}
return await useFetch("YOUR-API-URL", {
method: "PUT",
body: formData,
headers: {"cache-control": "no-cache"},
});
答案2
得分: 1
以下是您要翻译的内容:
"I made a test but simpler to your but the idea is there. This will send a POST using one useFetch
only."
<template>
<div>
<div>
<input
type="file"
@change="fileChange"
/>
</div>
<button @click.prevent="sendDataAndUploadFile">Send Data and Upload file</button>
</div>
</template>
<script lang="ts" setup>
interface IBookData {
[key: string]: string | Blob
}
const uploadedFile = ref<File | null>(null)
function fileChange(file: Event): void {
const fileData = file.target as HTMLInputElement
if (fileData.files) {
uploadedFile.value = fileData.files[0]
}
}
const BookData: IBookData = {
title: 'The Adam',
author: 'John Doe',
publisher: 'John Doe',
}
async function sendDataAndUploadFile() {
const formData = new FormData()
if (uploadedFile.value) {
formData.append('cover_image', uploadedFile.value)
for (const item in BookData) {
formData.append(item, BookData[item]);
}
}
await useFetch('/api/upload', {
method: 'POST',
body: formData
})
}
</script>
<template>
<div>
<div>
<input
type="file"
@change="fileChange"
/>
</div>
<button @click.prevent="sendDataAndUploadFile">Send Data and Upload file</button>
</div>
</template>
<style scoped lang="css"></style>
"Example of arrays inside the BookData
"
interface IBookData {
[key: string]: string | number[] | Blob
}
const BookData: IBookData = {
title: 'The Adam',
author: 'John Doe',
publisher: 'John Doe',
ratings: [4, 5, 4.5]
}
async function sendDataAndUploadFile() {
const formData = new FormData()
if (uploadedFile.value) {
formData.append('cover_image', uploadedFile.value)
for (const [key, value] of Object.entries(BookData)) {
if (key === 'ratings') {
formData.append(key, JSON.stringify(value))
} else {
formData.append(key, value as string)
}
}
}
await useFetch('/api/upload', {
method: 'POST',
body: formData
})
}
"Edit 2"
for (const [key, value] of Object.entries(BookData)) {
if (Array.isArray(value)) {
for (const rating of value) {
formData.append(`${key}[]`, rating.toString())
}
} else {
formData.append(key, value)
}
}
英文:
I made a test but simpler to your but the idea is there. This will send a POST using one useFetch
only.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
<template>
<div>
<div>
<input
type="file"
@change="fileChange"
/>
</div>
<button @click.prevent="sendDataAndUploadFile">Send Data and Upload file</button>
</div>
</template>
<!-- end snippet -->
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
<script lang="ts" setup>
interface IBookData {
[key: string]: string | Blob
}
const uploadedFile = ref<File | null>(null)
function fileChange(file: Event): void {
const fileData = file.target as HTMLInputElement
if (fileData.files) {
uploadedFile.value = fileData.files[0]
}
}
const BookData: IBookData = {
title: 'The Adam',
author: 'John Doe',
publisher: 'John Doe',
}
async function sendDataAndUploadFile() {
const formData = new FormData()
if (uploadedFile.value) {
formData.append('cover_image', uploadedFile.value)
for (const item in BookData) {
formData.append(item, BookData[item]);
}
}
await useFetch('/api/upload', {
method: 'POST',
body: formData
})
}
</script>
<template>
<div>
<div>
<input
type="file"
@change="fileChange"
/>
</div>
<button @click.prevent="sendDataAndUploadFile">Send Data and Upload file</button>
</div>
</template>
<style scoped lang="css"></style>
<!-- end snippet -->
Edit: This will be the output
Edit
Example of arrays inside the BookData
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
interface IBookData {
[key: string]: string | number[] | Blob
}
const BookData: IBookData = {
title: 'The Adam',
author: 'John Doe',
publisher: 'John Doe',
ratings: [4, 5, 4.5]
}
async function sendDataAndUploadFile() {
const formData = new FormData()
if (uploadedFile.value) {
formData.append('cover_image', uploadedFile.value)
for (const [key, value] of Object.entries(BookData)) {
if (key === 'ratings') {
formData.append(key, JSON.stringify(value))
} else {
formData.append(key, value as string)
}
}
}
await useFetch('/api/upload', {
method: 'POST',
body: formData
})
}
<!-- end snippet -->
Edit 2
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
for (const [key, value] of Object.entries(BookData)) {
if (Array.isArray(value)) {
for (const rating of value) {
formData.append(`${key}[]`, rating.toString())
}
} else {
formData.append(key, value)
}
}
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论