我想在 TypeScript 中使用一个
useFetch()
调用将表单数据(不包括字符串、数字、数组)和一个文件发布到后端。
我试过的
如果我将字符串和文件添加到
FormData
实例,一切都很好。
但是,如果我尝试向
FormData
实例添加数字或数组,我会从 TypeScript 中得到错误(因为 FormData.append()
只接受字符串或 Blob
s)。我尝试对数字和数组进行 JSONify 化并将它们作为字符串发布,但随后在后端出现错误(我不想在后端转换任何内容)。
我尝试将表单数据作为简单的 JS 对象发布 - 它也有效,但不适用于文件。
我最终得到以下代码,我决定将字符串/数字/数组作为 JS 对象发布以在后端创建新的数据库记录 (
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
调用发布所有内容(表单数据和文件)会很棒。
您必须通过
FormData()
如下
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"},
});
我做了一个测试,但对你来说更简单,但想法就在那里。这将仅使用一个
useFetch
发送 POST。
<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>
编辑:这将是输出
编辑
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
})
}
编辑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)
}
}