一次性发布表单数据和文件 Nuxt 3 中的 Fetch 调用

问题描述 投票:0回答:2

我想在 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
调用发布所有内容(表单数据和文件)会很棒。

nuxt.js nuxtjs3 nuxt3
2个回答
1
投票

您必须通过

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"},
  });


1
投票

我做了一个测试,但对你来说更简单,但想法就在那里。这将仅使用一个

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)
  }
}

© www.soinside.com 2019 - 2024. All rights reserved.