Skip to page content

useFilePicker 组合式 API
v2.34+

useFilePicker() composable 让你从自己的代码里直接打开浏览器的文件选择框,并把用户挑选的 File 对象交给你,而无需渲染任何文件输入框。你在点击处理函数(或任何其它用户交互)中调用 openFilePicker(),就能通过一个 Promise、以及响应式数组 acceptedPickerFiles 和 onChange 钩子拿回这些文件。

挑选的文件会经过与 QFile 和 QUploader 相同的校验(accept、maxFileSize、maxTotalSize、maxFiles 和 filter),未通过校验的文件也会以这两个组件触发 @rejected 事件时相同的方式上报。

当你希望用一个按钮、一个菜单项、一个键盘快捷键或一张卡片来挑选文件,而 QFile 的表单外观或 QUploader 的上传队列反而碍事时,就用它。若你还想接受拖入的文件,可以搭配 useDropZone composable,它与本 composable 共享同一套校验选项。

TIP

在 SSR 或 SSG 模式的服务端,无法打开文件选择框:openFilePicker() 会解析为 null,并且在客户端接管之前 acceptedPickerFiles 始终为空。

在组件之外使用

该 composable 也可以在 setup() 之外调用:在 boot 文件、store 或普通模块里都行。它在那里的行为完全一致,并且没有任何东西需要你手动释放。

语法

import { useFilePicker } from 'quasar'

setup () {
  const {
    acceptedPickerFiles,
    rejectedPickerFiles,
    openFilePicker,
    resetFilePicker
  } = useFilePicker({
    // all optional:
    multiple: false,        // allow picking more than one file
    accept: 'image/*,.pdf', // same format as the native "accept" attribute
    capture: 'environment', // 'user' or 'environment'; asks mobile devices for the camera
    directory: false,       // pick a folder instead of files

    maxFileSize: 1048576,   // bytes
    maxTotalSize: 10485760, // bytes
    maxFiles: 5,
    filter (files) {        // keep only the files you return
      return files.filter(file => file.name.endsWith('.jpg'))
    },

    onChange (files) {},      // the accepted files of a selection
    onRejected (rejected) {}, // [{ failedPropValidation, file }, ...]
    onCancel () {}            // the dialog was dismissed
  })

  // ...
}
function useFilePicker(
  options?: MaybeRefOrGetter<{
    multiple?: boolean
    accept?: string
    capture?: 'user' | 'environment'
    directory?: boolean
    maxFileSize?: string | number
    maxTotalSize?: string | number
    maxFiles?: string | number
    filter?: (files: readonly File[]) => readonly File[]
    onChange?: (files: File[]) => void
    onRejected?: (rejected: QRejectedEntry[]) => void
    onCancel?: () => void
  }>
): {
  acceptedPickerFiles: ShallowRef<File[]>
  rejectedPickerFiles: ShallowRef<QRejectedEntry[]>
  openFilePicker: (overrides?: UseFilePickerOptions) => Promise<File[] | null>
  resetFilePicker: () => void
}

// UseFilePickerOptions is the type of the "options" parameter above;
// QRejectedEntry is the type of the entries of the QFile/QUploader "rejected" event
interface QRejectedEntry {
  failedPropValidation:
    | 'accept'
    | 'max-file-size'
    | 'max-total-size'
    | 'filter'
    | 'max-files'
    | 'duplicate'
  file: File
}

openFilePicker() 必须作为用户交互(例如一个 click 或 keyup 处理函数)的直接结果来调用。否则浏览器会拒绝弹出文件选择框,包括从一个已经 await 过其它操作的 async 回调中调用的情况。

openFilePicker() 返回的 Promise 会解析为被接受的文件(当所有挑选的文件都被拒绝时是一个空数组),或在用户关闭选择框时解析为 null。此外,当上一个选择框尚未上报结果就再次调用 openFilePicker(),或你的组件被销毁时,它也会解析为 null。

acceptedPickerFiles 保存最近一次选择中被接受的文件。如果某次选择里的文件全部被拒绝,则 acceptedPickerFiles 保持不变;而 rejectedPickerFiles 始终反映最近一次选择的结果。resetFilePicker() 会把两者都清空。

onChange 仅在至少有一个文件被接受时调用,onRejected 仅在至少有一个文件被拒绝时调用(同一次选择可能同时触发这两者),onCancel 则在选择框被关闭时调用。

设置 directory 会改为挑选一个文件夹:数组会包含其中(递归地)的每一个文件,每个文件相对于所选文件夹的路径可通过 file.webkitRelativePath 获取。此时 multiple 选项不起作用。Safari 的选择框在这种情况下也允许用户挑选单个文件,这些文件的 webkitRelativePath 为空字符串。

被拒绝条目的 failedPropValidation 是 accept、max-file-size、max-total-size、max-files 或 filter 之一,指明该文件未通过的那个选项(duplicate 也属于同一个 QRejectedEntry 类型,但只有 QFile 和 QUploader 在向列表追加文件时才会上报它)。

运行时更改选项

选项可以是一个普通对象、一个 Ref 或一个 getter 函数,并且每次调用 openFilePicker() 时都会重新读取它们。你也可以把覆盖项直接交给 openFilePicker() 本身,这些覆盖项对那一次调用具有更高优先级:

import { ref } from 'vue'
import { useFilePicker } from 'quasar'

setup () {
  const allowVideos = ref(false)

  const { openFilePicker } = useFilePicker(() => ({
    multiple: true,
    accept: allowVideos.value ? 'image/*,video/*' : 'image/*'
  }))

  function pickAvatar () {
    // only one image for the avatar, whatever the current options say
    return openFilePicker({ multiple: false, accept: 'image/*' })
  }

  // ...
}

示例

下面是一个按钮,点击后打开图片文件选择框、列出被接受的文件,并通过通知上报被拒绝的文件以及被关闭的对话框:

<template>
  <div class="q-pa-md q-gutter-md">
    <q-btn
      color="primary"
      push
      icon="attach_file"
      label="Pick images (up to 1MB each)"
      @click="attach"
    />

    <q-list
      v-if="acceptedPickerFiles.length !== 0"
      bordered
      separator
      class="rounded-borders"
    >
      <q-item v-for="file in acceptedPickerFiles" :key="file.name">
        <q-item-section>{{ file.name }}</q-item-section>
        <q-item-section side>{{
          format.humanStorageSize(file.size)
        }}</q-item-section>
      </q-item>
    </q-list>
  </div>
</template>

<script setup>
import { format, useFilePicker, useQuasar } from 'quasar'

const $q = useQuasar()

const { acceptedPickerFiles, openFilePicker } = useFilePicker({
  multiple: true,
  accept: 'image/*',
  maxFileSize: 1024 * 1024,
  onRejected(rejected) {
    $q.notify({
      type: 'negative',
      message: `${rejected.length} file(s) did not pass the validation`
    })
  }
})

async function attach() {
  const picked = await openFilePicker()

  if (picked === null) {
    $q.notify({ message: 'The dialog was dismissed' })
  }
}
</script>

同一个选择器可以通过把覆盖项交给 openFilePicker() 来满足不同的需求。下面第二个按钮会挑选整个文件夹,并列出其中各文件相对于该文件夹的路径:

<template>
  <div class="q-pa-md q-gutter-md">
    <div class="q-gutter-sm">
      <q-btn
        color="primary"
        push
        label="Pick a document"
        @click="pickDocument"
      />
      <q-btn color="secondary" push label="Pick a folder" @click="pickFolder" />
    </div>

    <div v-if="acceptedPickerFiles.length !== 0" class="q-gutter-sm">
      <q-badge :label="`${acceptedPickerFiles.length} file(s)`" />
      <div
        v-for="file in acceptedPickerFiles.slice(0, 10)"
        :key="file.webkitRelativePath || file.name"
      >
        {{ file.webkitRelativePath || file.name }}
      </div>
      <div v-if="acceptedPickerFiles.length > 10">...</div>
    </div>
  </div>
</template>

<script setup>
import { useFilePicker } from 'quasar'

const { acceptedPickerFiles, openFilePicker } = useFilePicker({
  accept: '.pdf,.doc,.docx,.txt'
})

function pickDocument() {
  openFilePicker()
}

function pickFolder() {
  // the overrides apply to this one call only
  openFilePicker({ directory: true, accept: void 0 })
}
</script>

如果想预览挑选到的图片(或把任意挑选到的文件交给一个 <img>、一个 <video> 或一个下载链接),可以搭配 useObjectUrl composable,它会为该 File 创建对象 URL,并在不再需要时将其释放。