在Nuxt 3中,在一个useFetch调用中提交表单数据和文件。

huangapple go评论60阅读模式
英文:

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 Blobs). 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 = &quot;GET&quot;,
  token: string | null = null,
  formData: FormData | Object | undefined = undefined,
) {
  const config = useRuntimeConfig();

  const get: typeof useFetch = (url) =&gt; {
    return useFetch(url, {
      params: query,
      baseURL: config.public.apiBase + &quot;/api/v1&quot;,
      key: url.toString(),
      method: method as any,
      headers: token ? [[&quot;Authorization&quot;, &quot;Token &quot; + 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) =&gt; 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, &quot;POST&quot;, authStore.token, formData);
  return await get&lt;Book&gt;(&quot;/books/create/&quot;);
}

export async function updateBookCover(bookId: number, coverImage: File) {
  const authStore = useAuthStore();
  const formData = new FormData();
  formData.append(&quot;cover_image&quot;, coverImage);

  const { get } = useApi(undefined, &quot;PATCH&quot;, authStore.token, formData);
  return await get&lt;Book&gt;(`/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(&quot;file&quot;, 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(&quot;YOUR-API-URL&quot;, {
    method: &quot;PUT&quot;,
    body: formData,
    headers: {&quot;cache-control&quot;: &quot;no-cache&quot;},
  });

答案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 -->

&lt;template&gt;
&lt;div&gt;
&lt;div&gt;
&lt;input
type=&quot;file&quot;
@change=&quot;fileChange&quot;
/&gt;
&lt;/div&gt;
&lt;button @click.prevent=&quot;sendDataAndUploadFile&quot;&gt;Send Data and Upload file&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

<!-- end snippet -->

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

&lt;script lang=&quot;ts&quot; setup&gt;
interface IBookData {
[key: string]: string | Blob
}
const uploadedFile = ref&lt;File | null&gt;(null)
function fileChange(file: Event): void {
const fileData = file.target as HTMLInputElement
if (fileData.files) {
uploadedFile.value = fileData.files[0]
}
}
const BookData: IBookData = {
title: &#39;The Adam&#39;,
author: &#39;John Doe&#39;,
publisher: &#39;John Doe&#39;,
}
async function sendDataAndUploadFile() {
const formData = new FormData()
if (uploadedFile.value) {
formData.append(&#39;cover_image&#39;, uploadedFile.value)
for (const item in BookData) {
formData.append(item, BookData[item]);
}
}
await useFetch(&#39;/api/upload&#39;, {
method: &#39;POST&#39;,
body: formData
})
}
&lt;/script&gt;
&lt;template&gt;
&lt;div&gt;
&lt;div&gt;
&lt;input
type=&quot;file&quot;
@change=&quot;fileChange&quot;
/&gt;
&lt;/div&gt;
&lt;button @click.prevent=&quot;sendDataAndUploadFile&quot;&gt;Send Data and Upload file&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;style scoped lang=&quot;css&quot;&gt;&lt;/style&gt;

<!-- end snippet -->

Edit: This will be the output

在Nuxt 3中,在一个useFetch调用中提交表单数据和文件。

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: &#39;The Adam&#39;,
author: &#39;John Doe&#39;,
publisher: &#39;John Doe&#39;,
ratings: [4, 5, 4.5]
}
async function sendDataAndUploadFile() {
const formData = new FormData()
if (uploadedFile.value) {
formData.append(&#39;cover_image&#39;, uploadedFile.value)
for (const [key, value] of Object.entries(BookData)) {
if (key === &#39;ratings&#39;) {
formData.append(key, JSON.stringify(value))
} else {
formData.append(key, value as string)
}
}
}
await useFetch(&#39;/api/upload&#39;, {
method: &#39;POST&#39;,
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 -->

huangapple
  • 本文由 发表于 2023年5月10日 19:06:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76217664.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定