eagleye

Quasar QFile 组件企业级应用指南

Quasar QFile 组件企业级应用指南

1. 组件概述

QFile是 Quasar 框架提供的高级文件选择器组件,封装了原生<input type="file">并扩展了丰富功能:

  • 支持单文件/多文件选择
  • 文件类型/大小/数量限制
  • 拖放上传功能
  • 自定义UI与交互反馈
  • 表单验证集成

2. 快速开始

2.1 基础用法

<template>

<q-file

v-model="selectedFile"

label="选择文件"

filled

/>

</template>

<script setup>

import { ref } from 'vue'

const selectedFile = ref(null)

</script>

2.2 核心属性配置

属性名

类型

说明

v-model

File / File[] / null

绑定选中的文件

accept

String

允许的文件类型,如"image/*,.pdf"

multiple

Boolean

是否允许多选

max-files

Number

最大文件数量限制

max-file-size

Number

单个文件大小限制(字节)

counter

Boolean

是否显示文件计数器

counter-label

Function

自定义计数器显示文本

filter

Function

自定义文件过滤逻辑

3. 高级功能实现

3.1 文件过滤与验证

<template>

<q-file

v-model="files"

multiple

accept="image/*,.pdf"

:max-files="5"

:max-file-size="10 * 1024 * 1024" <!-- 10MB -->

:filter="validateFiles"

@rejected="handleRejection"

label="选择最多5个文件(图片或PDF,单个不超过10MB)"

/>

</template>

<script setup>

import { ref } from 'vue'

import { useQuasar } from 'quasar'

const $q = useQuasar()

const files = ref(null)

// 自定义文件过滤逻辑

const validateFiles = (files) => {

return files.filter(file => {

// 额外验证:PDF文件不超过5MB

if (file.type === 'application/pdf' && file.size > 5 * 1024 * 1024) {

return false

}

return true

})

}

// 处理文件验证失败

const handleRejection = (rejections) => {

rejections.forEach(rej => {

$q.notify({

type: 'negative',

message: `文件 ${rej.file.name} 被拒绝: ${rej.reason}`

})

})

}

</script>

3.2 自定义计数器标签

基础实现

<template>

<q-file

v-model="files"

multiple

counter

:max-files="10"

:counter-label="counterLabel"

label="带自定义计数器的文件选择器"

/>

</template>

<script setup>

const counterLabel = (props) => {

const { totalsize, filesNumber, maxFiles } = props

return `已选择 ${filesNumber}/${maxFiles} 个文件 (${totalsize})`

}

</script>

高级实现(带状态指示)

<template>

<q-file

v-model="files"

multiple

counter

:max-files="10"

:counter-label="counterLabel"

label="带状态指示的计数器"

/>

</template>

<script setup>

const counterLabel = (props) => {

const { totalsize, filesNumber, maxFiles } = props

const maxFilesNum = Number(maxFiles) // 确保数字类型

const percentage = Math.round((filesNumber / maxFilesNum) * 100)

// 根据选择比例返回不同样式

let statusClass = 'text-primary'

if (percentage > 80) statusClass = 'text-negative'

else if (percentage > 60) statusClass = 'text-warning'

return `<span class="${statusClass}">${filesNumber}/${maxFilesNum}</span> 个文件 (${totalsize})`

}

</script>

<style scoped>

/* 需要深度选择器修改计数器样式 */

::v-deep .q-file__counter {

.text-primary { color: var(--q-color-primary); }

.text-warning { color: var(--q-color-warning); }

.text-negative { color: var(--q-color-negative); }

}

</style>

类型安全提示counter-label函数仅接收一个对象参数,包含以下属性:

  • totalsize: string - 总大小格式化字符串
  • filesNumber: number - 当前选择文件数
  • maxFiles: string|number - 最大允许文件数

3.3 文件上传实现

带进度跟踪的上传

<template>

<div class="q-gutter-md">

<q-file

v-model="selectedFiles"

multiple

accept="image/*,.pdf"

label="选择文件上传"

@update:model-value="handleFilesSelected"

/>

<q-linear-progress

v-if="uploadProgress > 0 && uploadProgress < 100"

:value="uploadProgress"

height="8px"

color="primary"

/>

<div v-if="uploadStatus" class="text-caption">

{{ uploadStatus }}

</div>

</div>

</template>

<script setup>

import { ref } from 'vue'

import { useQuasar } from 'quasar'

import { uploadService } from 'src/services'

const $q = useQuasar()

const selectedFiles = ref(null)

const uploadProgress = ref(0)

const uploadStatus = ref('')

const handleFilesSelected = async (files) => {

if (!files) return

uploadProgress.value = 0

uploadStatus.value = '准备上传...'

try {

const formData = new FormData()

Array.from(files).forEach(file => {

formData.append('files', file)

})

await uploadService.uploadFiles(formData, {

onUploadProgress: (e) => {

uploadProgress.value = Math.round((e.loaded / e.total) * 100)

uploadStatus.value = `上传中: ${uploadProgress.value}%`

}

})

uploadStatus.value = '上传成功!'

$q.notify({

type: 'positive',

message: `成功上传 ${files.length} 个文件`

})

} catch (err) {

uploadStatus.value = '上传失败'

$q.notify({

type: 'negative',

message: `上传失败: ${err.message}`

})

}

}

</script>

3.4 图片预览功能

<template>

<div class="q-gutter-md">

<q-file

v-model="selectedImage"

accept="image/*"

label="选择图片"

@update:model-value="handleImageSelected"

/>

<q-img

v-if="previewUrl"

:src="previewUrl"

style="max-width: 100%; height: 200px; object-fit: contain"

spinner-color="primary"

/>

</div>

</template>

<script setup>

import { ref, onBeforeUnmount } from 'vue'

const selectedImage = ref(null)

const previewUrl = ref('')

const handleImageSelected = (file) => {

// 清理之前的预览URL

if (previewUrl.value) {

URL.revokeObjectURL(previewUrl.value)

}

if (file && file.type.startsWith('image/')) {

previewUrl.value = URL.createObjectURL(file)

}

}

// 组件卸载前清理URL对象,避免内存泄漏

onBeforeUnmount(() => {

if (previewUrl.value) {

URL.revokeObjectURL(previewUrl.value)

}

})

</script>

4. 表单集成

4.1 基础表单验证

<template>

<q-form @submit="onSubmit" class="q-gutter-md">

<q-file

v-model="form.file"

label="选择文件"

:rules="[val => !!val || '请选择文件']"

filled

/>

<q-btn label="提交" type="submit" color="primary" />

</q-form>

</template>

<script setup>

import { ref } from 'vue'

const form = ref({

file: null

})

const onSubmit = () => {

// 处理表单提交

console.log('提交文件:', form.value.file)

}

</script>

4.2 高级验证规则

<q-file

v-model="form.document"

label="上传合同文件"

accept=".pdf,.doc,.docx"

:max-file-size="5 * 1024 * 1024" <!-- 5MB -->

:rules="[

val => !!val || '合同文件为必填项',

val => val?.type.includes('pdf') || '优先推荐PDF格式',

val => val?.size <= 5*1024*1024 || '文件大小不能超过5MB'

]"

/>

5. 样式定制

5.1 基础样式配置

<q-file

v-model="files"

label="自定义样式"

filled

color="primary"

bg-color="grey-50"

dark

square

bordered

style="width: 100%; max-width: 400px"

/>

5.2 深度样式定制

<template>

<q-file

v-model="files"

label="深度定制样式"

class="custom-file-picker"

filled

/>

</template>

<style scoped>

/* 自定义输入框样式 */

::v-deep .custom-file-picker .q-field__control {

border-radius: 12px;

border: 2px dashed #ccc;

transition: all 0.3s ease;

}

/* 悬停状态 */

::v-deep .custom-file-picker .q-field__control:hover {

border-color: var(--q-color-primary);

 

}

/* 选中状态 */

::v-deep .custom-file-picker .q-field--focused .q-field__control {

box-shadow: 0 0 0 2px var(--q-color-primary);

border-style: solid;

}

/* 自定义标签样式 */

::v-deep .custom-file-picker .q-field__label {

color: var(--q-color-primary);

font-weight: 600;

}

</style>

6. 企业级最佳实践

6.1 性能优化

1. 内存管理

// 及时清理Object URL避免内存泄漏

onBeforeUnmount(() => {

if (previewUrl.value) {

URL.revokeObjectURL(previewUrl.value)

}

})

2. 大文件处理

// 大文件分块上传

const CHUNK_SIZE = 2 * 1024 * 1024; // 2MB分块

async function uploadLargeFile(file) {

const totalChunks = Math.ceil(file.size / CHUNK_SIZE);

const fileId = generateUniqueId();

for (let i = 0; i < totalChunks; i++) {

const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);

await uploadChunk(fileId, chunk, i, totalChunks);

}

return await completeUpload(fileId, file.name);

}

6.2 安全措施

1. 服务器端验证

// 永远不要信任客户端验证

// 服务端必须重新验证文件类型、大小和内容

app.post('/upload', upload.single('file'), (req, res) => {

const file = req.file;

// 验证文件类型

if (!['image/jpeg', 'image/png'].includes(file.mimetype)) {

return res.status(400).send('不支持的文件类型');

}

// 验证文件大小

if (file.size > 10 * 1024 * 1024) {

return res.status(400).send('文件大小超过限制');

}

// 验证文件内容(防恶意文件)

if (!isValidFileContent(file.path)) {

return res.status(400).send('文件内容无效');

}

// 处理合法文件...

});

2. 文件命名安全

// 上传文件重命名(避免路径遍历攻击)

function generateSafeFilename(originalName) {

const ext = path.extname(originalName);

const base = crypto.randomBytes(16).toString('hex');

return `${base}${ext}`;

}

6.3 可访问性优化

<q-file

v-model="files"

label="无障碍文件选择器"

aria-required="true"

aria-label="选择文件以上传"

:aria-describedby="fileHelpText"

/>

<div id="fileHelpText" class="text-caption q-mt-xs">

支持JPG、PNG和PDF文件,最大5MB

</div>

7. 常见问题解决方案

7.1 计数器标签类型错误

问题counter-label函数参数类型不匹配

// 错误示例

const counterLabel = (totalSize, filesNumber, maxFiles) => { ... }

解决方案

// 正确示例(接收单个对象参数)

const counterLabel = (props) => {

// 注意属性名是totalsize(小写s)

const { totalsize, filesNumber, maxFiles } = props;

// 类型转换确保计算正确

const max = Number(maxFiles);

const percent = Math.round((filesNumber / max) * 100);

return `已选择 ${filesNumber}/${max} 个文件 (${totalsize})`;

}

7.2 拖放功能不工作

解决方案

<q-file

v-model="files"

multiple

label="拖放文件到此处"

class="q-pa-lg"

style="min-height: 150px; border: 2px dashed #ccc"

:disable="isDragging"

@dragover.prevent="isDragging = true"

@dragleave.prevent="isDragging = false"

@drop.prevent="handleDrop"

/>

<script setup>

const isDragging = ref(false)

const handleDrop = (e) => {

isDragging.value = false

// 处理拖放的文件

const files = e.dataTransfer.files

if (files.length) {

selectedFiles.value = files

}

}

</script>

7.3 移动端兼容性问题

解决方案

<q-file

v-model="files"

:label="$q.platform.is.mobile ? '点击选择文件' : '拖放文件或点击选择'"

:disable-drag="!$q.platform.is.desktop"

/>

8. API 参考

核心属性

属性

类型

默认值

说明

accept

String

null

允许的文件类型

capture

String

null

移动设备摄像头捕获类型

counter

Boolean

false

是否显示计数器

counter-label

Function

null

自定义计数器文本

disable-drag

Boolean

false

禁用拖放功能

max-file-size

Number

0

单个文件最大字节数

max-files

Number

0

最大文件数量

multiple

Boolean

false

允许多文件选择

事件

事件

参数

说明

update:model-value

File / File[] / null

文件选择变化时触发

rejected

{ file: File, reason: String }[]

文件被拒绝时触发

dragover

Event

文件拖过时触发

dragleave

Event

文件拖离时触发

drop

Event

文件放下时触发

通过本指南,您可以充分利用 Quasar QFile 组件构建企业级文件上传功能,兼顾用户体验、性能和安全性。组件的高度可定制性

 

posted on 2025-08-21 20:39  GoGrid  阅读(22)  评论(0)    收藏  举报

导航