为什么捐赠
API 浏览器
联系站长
上传器

Quasar 通过 QUploader 组件提供了一种上传文件的方式。

TIP

如果您只是想要一个输入文件的组件,那么您可能需要考虑使用 QFile 文件选择器组件。

正在加载 QUploader API...

用法

WARNING

QUploader 需要一个后端服务器来接收文件。下面的示例不会真正地上传。

TIP

QUploader 兼容"拖放"操作。

WARNING

当使用 vee-validate 时,您需要重命名 vee-validate 的 “fieldBagName” 配置以使 q-uploader 正常工作。

设计

基础


强制暗色模式


上传多个文件

默认情况下,多个文件将被单独上传(每个文件一个线程)。如果您希望所有文件都在单个线程中上传,请使用 batch 属性(下面示例中的第二个 QUploader)。

多个文件


限制上传

基础限制


TIP

在上面的示例中,我们使用的是 accept 属性。其值必须是以逗号分隔的唯一文件类型说明符列表。映射到原生 input type=file 元素的 ‘accept’ 属性。更多信息

WARNING

accept 属性的推荐格式为 <mediatype>/<extension>。示例:“image/jpeg”、“image/png”。QUploader 在底层使用了一个 <input type="file">,它完全依赖于宿主浏览器来触发文件选择器。如果 accept 属性(应用于 input)不正确,则不会在屏幕上显示文件选择器,或者它会出现但会接受所有文件类型。

您还可以应用自定义过滤器(在用户选择文件后执行):

过滤


添加请求头

使用 headers 来设置要与上传请求一起发送的额外 XHR 请求头。如果您需要嵌入其他字段,也请查看 API 中的 form-fields 属性。

请求头


TIP

这两个属性(headersform-fields)也可以用作函数((files) => Array),允许您根据要上传的文件动态设置它们。

还有 with-credentials 属性,它将上传过程中使用的 XHR 的 withCredentials 设置为 true

处理上传

选择文件后自动上传


自定义上传 URL


TIP

您还可以通过 headersmethod 属性自定义 HTTP 请求头和 HTTP 方法。查看 QUploader API 部分。

工厂函数

有一个 factory 属性,它必须是一个函数。此函数可以返回一个对象或解析为对象的 Promise(如果 Promise 失败,则会触发 @factory-failed 事件)。

上述对象可以覆盖以下 QUploader 属性:urlmethodheadersformFieldsfieldNamewithCredentialssendRaw。此对象的属性也可以是函数(形式为 (file[s]) => value):

基于 Promise 的工厂函数


您也可以使用 factory 函数属性并立即返回相同的对象。如果您想同时设置多个属性(如上所述),这很有用:

立即返回的工厂函数


插槽

在下面的示例中,我们展示了默认头部的等效实现。还注意一些您可以使用的布尔作用域属性:scope.canAddFilesscope.canUploadscope.isUploading

WARNING

请注意,您必须安装并使用另一个组件(QUploaderAddTrigger)才能将文件添加到队列中。此组件需要放置在具有 position: relative 的 DOM 节点下(提示:QBtn 已经具有此属性),并且当用户点击其父元素时会自动注入必要的事件(不要手动添加 @click="scope.pickFiles")。如果触发器不起作用,请检查是否有元素渲染在其上方,并相应地更改 QUploaderAddTrigger 的 zIndex。

自定义头部


自定义列表


服务器端点示例

QUploader 默认使用 HTTP(S) 协议上传文件(但不限于此,您将在下一节中看到)。

TIP

您并不一定要像下面那样使用 Nodejs 服务器、Spring 或 ASP.NET – 您可以使用任何您想要的方式来处理文件上传,只要您使用的方法符合 HTTP 协议即可。例如 PHP

Nodejs

下面是一个用 Nodejs 编写的基本服务器示例。它除了接收文件之外什么都不做,所以请将它作为起点。

import fs from "node:fs";
import path from "node:path";
import express from "express";
import formidable from "formidable";
import throttle from "express-throttle-bandwidth";

const app = express();

const port = process.env.PORT || 4444;
const folder = path.join(import.meta.dirname, 'files')

if (!fs.existsSync(folder)) {
  fs.mkdirSync(folder);
}

app.set("port", port);
app.use(throttle(1024 * 128)); // throttling bandwidth

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

app.post("/upload", (req, res) => {
  const form = new formidable.IncomingForm();

  form.uploadDir = folder;
  form.parse(req, (_, fields, files) => {
    console.log("\n-----------");
    console.log("Fields", fields);
    console.log("Received:", Object.keys(files));
    console.log();
    res.send("Thank you");
  });
});

app.listen(port, () => {
  console.log("\nUpload server running on http://localhost:" + port);
});

ASP.NET MVC/Core

QUploader 可以与 Microsoft ASP.NET MVC/Core 2.x Web API 后端无缝集成。 在您的 Vue 文件中,配置 QUploader 组件并设置所需的 Web API 端点:

<q-uploader
  url="http://localhost:4444/fileuploader/upload"
  label="Upload"
  style="max-width: 300px"
/>

如果您的服务器需要身份验证(例如 JWT 令牌),请使用 QUploader 的工厂函数来指定 QUploader 使用的 xhr 请求头。例如:

<template>
  <q-uploader label="Upload" :factory="factoryFn" style="max-width: 300px" />
</template>

<script>
  export default {
    methods: {
      factoryFn(file) {
        return new Promise((resolve, reject) => {
          // Retrieve JWT token from your store.
          const token = "myToken";
          resolve({
            url: "http://localhost:4444/fileuploader/upload",
            method: "POST",
            headers: [{ name: "Authorization", value: `Bearer ${token}` }],
          });
        });
      },
    },
  };
</script>

QUploader 的文件载荷将是一个格式正确的 IFormFileCollection 对象,您可以通过 ASP.NET Web API 控制器的 .Request 属性读取它。 ASP.NET Core 2.2 Controller:

[Route("api/[controller]")]
[ApiController]
public class FileUploaderController : ControllerBase
{
    [HttpPost]
    public async Task upload()
    {
        // Request's .Form.Files property will
        // contain QUploader's files.
        var files = this.Request.Form.Files;
        foreach (var file in files)
        {
            if (file == null || file.Length == 0)
                continue;

            // Do something with the file.
            var fileName = file.FileName;
            var fileSize = file.Length;
            // save to server...
            // ...
        }
    }
}

Spring

下面是一个 Spring 的示例。属性 fieldName="file" 映射到 @RequestPart(value = "file")

// java
@RestController
public class UploadRest {
	@PostMapping("/upload")
	public void handleFileUpload(@RequestPart(value = "file") final MultipartFile uploadfile) throws IOException {
		saveUploadedFiles(uploadfile);
	}

	private String saveUploadedFiles(final MultipartFile file) throws IOException {
		final byte[] bytes = file.getBytes();
		final Path path = Paths.get("YOUR_ABSOLUTE_PATH" + file.getOriginalFilename());
		Files.write(path, bytes);
	}
}

// html
<q-uploader field-name="file" url="YOUR_URL_BACK/upload" with-credentials />

Python/Flask

// python
from flask import Flask, request
from werkzeug import secure_filename
from flask_cors import CORS
import os

app = Flask(__name__)

# This is necessary because QUploader uses an AJAX request
# to send the file
cors = CORS()
cors.init_app(app, resource={r"/api/*": {"origins": "*"}})

@app.route('/upload', methods=['POST'])
def upload():
    for fname in request.files:
        f = request.files.get(fname)
        print(f)
        f.save('./uploads/%s' % secure_filename(fname))

    return 'Okay!'

if __name__ == '__main__':
    if not os.path.exists('./uploads'):
        os.mkdir('./uploads')
    app.run(debug=True)

Julia/Genie

# Julia Genie

using Genie, Genie.Requests, Genie.Renderer

Genie.config.cors_headers["Access-Control-Allow-Origin"]  =  "*"
Genie.config.cors_headers["Access-Control-Allow-Headers"] = "Content-Type"
Genie.config.cors_headers["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE,OPTIONS"
Genie.config.cors_allowed_origins = ["*"]

#== server ==#

route("/") do
  "File Upload"
end

route("/upload", method = POST) do
  if infilespayload(:img)                 # :img is file-name
    @info filename(filespayload(:img))    # file-name="img"
    @info filespayload(:img).data

    open("upload/file.jpg", "w") do io
      write(io, filespayload(:img).data)
    end
  else
    @info "No image uploaded"
  end

  Genie.Renderer.redirect(:get)
end

isrunning(:webserver) || up()

Perl/Mojolicious

# Perl

use Mojolicious::Lite -signatures;

# CORS
app->hook(after_dispatch => sub {
    my $c = shift;
    $c->res->headers->header('Access-Control-Allow-Origin' => '*');
});
options '*' => sub ($c) {
   $c->res->headers->header('Access-Control-Allow-Methods' => 'GET, OPTIONS, POST, DELETE, PUT');
   $c->res->headers->header('Access-Control-Allow-Headers' => 'Content-Type');
   $c->render(text => '');
};

post '/upload' => sub ($c) {
   my $uploads = $c->req->uploads('files');

   foreach my $f (@{$uploads}) {
      $f->move_to('/tmp/' . $f->filename);
   }

   $c->render(text => 'Saved!');
};

app->start;

支持其他的服务

QUploader 目前支持通过 HTTP(S) 协议上传。但您也可以扩展组件以支持其他服务。例如 Firebase。下面是您可以做的。

感谢您的帮助

我们很乐意接受支持其他上传服务的 PR,这样其他人也能从中受益。点击此页面底部的 Edit this page in browser 链接或者页面顶部的铅笔图标。

下面是一个示例,其中包含需要提供给 createUploaderComponent() Quasar 工具函数的 API。这将创建一个 Vue 组件,您可以在应用程序中导入它。

MyUploader.js

import { createUploaderComponent } from "quasar";
import { computed } from "vue";

// export a Vue component
export default createUploaderComponent({
  // defining the QUploader plugin here

  name: "MyUploader", // your component's name

  props: {
    // ...your custom props
  },

  emits: [
    // ...your custom events name list
  ],

  injectPlugin({ props, emit, helpers }) {
    // can call any other composables here
    // as this function will run in the component's setup()

    // [ REQUIRED! ]
    // We're working on uploading files
    const isUploading = computed(() => {
      // return <Boolean>
    });

    // [ optional ]
    // Shows overlay on top of the
    // uploader signaling it's waiting
    // on something (blocks all controls)
    const isBusy = computed(() => {
      // return <Boolean>
    });

    // [ REQUIRED! ]
    // Abort and clean up any process
    // that is in progress
    function abort() {
      // ...
    }

    // [ REQUIRED! ]
    // Start the uploading process
    function upload() {
      // ...
    }

    return {
      isUploading,
      isBusy,

      abort,
      upload,
    };
  },
});

TIPS

  • 对于这种插件形式的默认 XHR 实现,请查看源代码
  • 对于 UMD 版本,请使用 Quasar.createUploaderComponent({ ... })

然后向 Vue 全局注册该组件,或者导入该组件并将其添加到 Vue 组件中的 “components: {}” 中。

// globally registering your component in a boot file
import { defineBoot } from '#q-app'
import MyUploader from '../../path/to/MyUploader' // the file from above

export default defineBoot(({ app }) {
  app.component('MyUploader', MyUploader)
})

// or declaring it in a .vue file
import MyUploader from '../../path/to/MyUploader' // the file from above
export default {
  // ...
  components: {
    // ...
    MyUploader
  }
}

如果您使用的是 TypeScript,则需要注册新的组件类型,以允许 Volar 为您自动补全属性和插槽。

import {
  GlobalComponentConstructor,
  QUploaderProps,
  QUploaderSlots,
} from 'quasar';

interface MyUploaderProps extends QUploaderProps {
  // .. add custom props
  freeze: boolean;
  // .. add custom events
  onFreeze: boolean;
}

declare module 'vue' {
  interface GlobalComponents {
    MyUploader: GlobalComponentConstructor<MyUploaderProps, QUploaderSlots>;
  }
}

Accessibility
v2.25+

QUploader’s controls are real buttons — adding, uploading, aborting and removing files all go through QBtns that respond to the keyboard as usual — and per-file upload progress is exposed with progressbar semantics.

The header and per-file buttons are icon-only, so they carry localized accessible names from the Quasar Language Pack (uploader.*), as does the hidden native file input. The rest still needs your attention when accessibility matters: file status changes (uploading, uploaded, failed, rejected) are conveyed visually only, with no screen reader announcement. Use the header slot if you need different wording, and listen to the upload lifecycle events (@uploaded, @failed, @rejected, …) to feed a live region of your own. Drag-and-drop is a pointer-only convenience — the “add files” button is the keyboard path.