useFilePicker() composable 让你从自己的代码里直接打开浏览器的文件选择框,并把用户挑选的 File 对象交给你,而无需渲染任何文件输入框。你在点击处理函数(或任何其它用户交互)中调用 openFilePicker(),就能通过一个 Promise、以及响应式数组 acceptedPickerFiles 和 onChange 钩子拿回这些文件。
挑选的文件会经过与 QFile 和 QUploader 相同的校验(accept、maxFileSize、maxTotalSize、maxFiles 和 filter),未通过校验的文件也会以这两个组件触发 @rejected 事件时相同的方式上报。
当你希望用一个按钮、一个菜单项、一个键盘快捷键或一张卡片来挑选文件,而 QFile 的表单外观或 QUploader 的上传队列反而碍事时,就用它。若你还想接受拖入的文件,可以搭配 useDropZone composable,它与本 composable 共享同一套校验选项。
在 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,并在不再需要时将其释放。